]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
net: dsa: lantiq_gswip: split into common and MMIO parts
authorDaniel Golle <daniel@makrotopia.org>
Mon, 3 Nov 2025 12:18:41 +0000 (12:18 +0000)
committerJakub Kicinski <kuba@kernel.org>
Thu, 6 Nov 2025 22:16:16 +0000 (14:16 -0800)
Move all parts specific for the MMIO/SoC driver into a module of its own
to prepare for supporting MDIO-connected switch ICs.
Modify gswip_probe() functions by splitting it into a common function
gswip_probe_common() which covers allocating, initializing and registering
the DSA switch, while keeping transport-specific regmap initialization as
well as PHY firmware loading in the new MMIO/SoC-specific gswip_probe()
function.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Tested-by: Alexander Sverdlin <alexander.sverdlin@siemens.com>
Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
Link: https://patch.msgid.link/dc7da5b65ec220ba8e9bc4bd04fe1ed7de046656.1762170107.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/dsa/lantiq/Kconfig
drivers/net/dsa/lantiq/Makefile
drivers/net/dsa/lantiq/lantiq_gswip.c
drivers/net/dsa/lantiq/lantiq_gswip.h
drivers/net/dsa/lantiq/lantiq_gswip_common.c [new file with mode: 0644]

index 3cfa16840cf59b0452b805e1a6f35b910543aadb..78db82a47d092a93e305342751c2cdfa79f0df18 100644 (file)
@@ -1,8 +1,12 @@
+config NET_DSA_LANTIQ_COMMON
+       tristate
+       select REGMAP
+
 config NET_DSA_LANTIQ_GSWIP
        tristate "Lantiq / Intel GSWIP"
        depends on HAS_IOMEM
        select NET_DSA_TAG_GSWIP
-       select REGMAP
+       select NET_DSA_LANTIQ_COMMON
        help
          This enables support for the Lantiq / Intel GSWIP 2.1 found in
          the xrx200 / VR9 SoC.
index 849f85ebebd6cc1139969ba0b4e2ab0196cd69cb..65ffa7bb71aa759999b859a24ad0145b668e5b0c 100644 (file)
@@ -1 +1,2 @@
 obj-$(CONFIG_NET_DSA_LANTIQ_GSWIP) += lantiq_gswip.o
+obj-$(CONFIG_NET_DSA_LANTIQ_COMMON) += lantiq_gswip_common.o
index 38f7f6352e8d081258d00a8829e1d876c1095473..57dd063c0740352865d1b5c14aad3beb8e53aafa 100644 (file)
 /*
  * Lantiq / Intel GSWIP switch driver for VRX200, xRX300 and xRX330 SoCs
  *
- * Copyright (C) 2010 Lantiq Deutschland
- * Copyright (C) 2012 John Crispin <john@phrozen.org>
- * Copyright (C) 2017 - 2019 Hauke Mehrtens <hauke@hauke-m.de>
- *
- * The VLAN and bridge model the GSWIP hardware uses does not directly
- * matches the model DSA uses.
- *
- * The hardware has 64 possible table entries for bridges with one VLAN
- * ID, one flow id and a list of ports for each bridge. All entries which
- * match the same flow ID are combined in the mac learning table, they
- * act as one global bridge.
- * The hardware does not support VLAN filter on the port, but on the
- * bridge, this driver converts the DSA model to the hardware.
- *
- * The CPU gets all the exception frames which do not match any forwarding
- * rule and the CPU port is also added to all bridges. This makes it possible
- * to handle all the special cases easily in software.
- * At the initialization the driver allocates one bridge table entry for
- * each switch port which is used when the port is used without an
- * explicit bridge. This prevents the frames from being forwarded
- * between all LAN ports by default.
- */
-
-#include "lantiq_gswip.h"
-#include "lantiq_pce.h"
-
-#include <linux/delay.h>
-#include <linux/etherdevice.h>
-#include <linux/firmware.h>
-#include <linux/if_bridge.h>
-#include <linux/if_vlan.h>
-#include <linux/iopoll.h>
-#include <linux/mfd/syscon.h>
-#include <linux/module.h>
-#include <linux/of_mdio.h>
-#include <linux/of_net.h>
-#include <linux/of_platform.h>
-#include <linux/phy.h>
-#include <linux/phylink.h>
-#include <dt-bindings/mips/lantiq_rcu_gphy.h>
-
-struct xway_gphy_match_data {
-       char *fe_firmware_name;
-       char *ge_firmware_name;
-};
-
-struct gswip_pce_table_entry {
-       u16 index;      // PCE_TBL_ADDR.ADDR = pData->table_index
-       u16 table;      // PCE_TBL_CTRL.ADDR = pData->table
-       u16 key[8];
-       u16 val[5];
-       u16 mask;
-       u8 gmap;
-       bool type;
-       bool valid;
-       bool key_mode;
-};
-
-struct gswip_rmon_cnt_desc {
-       unsigned int size;
-       unsigned int offset;
-       const char *name;
-};
-
-#define MIB_DESC(_size, _offset, _name) {.size = _size, .offset = _offset, .name = _name}
-
-static const struct gswip_rmon_cnt_desc gswip_rmon_cnt[] = {
-       /** Receive Packet Count (only packets that are accepted and not discarded). */
-       MIB_DESC(1, 0x1F, "RxGoodPkts"),
-       MIB_DESC(1, 0x23, "RxUnicastPkts"),
-       MIB_DESC(1, 0x22, "RxMulticastPkts"),
-       MIB_DESC(1, 0x21, "RxFCSErrorPkts"),
-       MIB_DESC(1, 0x1D, "RxUnderSizeGoodPkts"),
-       MIB_DESC(1, 0x1E, "RxUnderSizeErrorPkts"),
-       MIB_DESC(1, 0x1B, "RxOversizeGoodPkts"),
-       MIB_DESC(1, 0x1C, "RxOversizeErrorPkts"),
-       MIB_DESC(1, 0x20, "RxGoodPausePkts"),
-       MIB_DESC(1, 0x1A, "RxAlignErrorPkts"),
-       MIB_DESC(1, 0x12, "Rx64BytePkts"),
-       MIB_DESC(1, 0x13, "Rx127BytePkts"),
-       MIB_DESC(1, 0x14, "Rx255BytePkts"),
-       MIB_DESC(1, 0x15, "Rx511BytePkts"),
-       MIB_DESC(1, 0x16, "Rx1023BytePkts"),
-       /** Receive Size 1024-1522 (or more, if configured) Packet Count. */
-       MIB_DESC(1, 0x17, "RxMaxBytePkts"),
-       MIB_DESC(1, 0x18, "RxDroppedPkts"),
-       MIB_DESC(1, 0x19, "RxFilteredPkts"),
-       MIB_DESC(2, 0x24, "RxGoodBytes"),
-       MIB_DESC(2, 0x26, "RxBadBytes"),
-       MIB_DESC(1, 0x11, "TxAcmDroppedPkts"),
-       MIB_DESC(1, 0x0C, "TxGoodPkts"),
-       MIB_DESC(1, 0x06, "TxUnicastPkts"),
-       MIB_DESC(1, 0x07, "TxMulticastPkts"),
-       MIB_DESC(1, 0x00, "Tx64BytePkts"),
-       MIB_DESC(1, 0x01, "Tx127BytePkts"),
-       MIB_DESC(1, 0x02, "Tx255BytePkts"),
-       MIB_DESC(1, 0x03, "Tx511BytePkts"),
-       MIB_DESC(1, 0x04, "Tx1023BytePkts"),
-       /** Transmit Size 1024-1522 (or more, if configured) Packet Count. */
-       MIB_DESC(1, 0x05, "TxMaxBytePkts"),
-       MIB_DESC(1, 0x08, "TxSingleCollCount"),
-       MIB_DESC(1, 0x09, "TxMultCollCount"),
-       MIB_DESC(1, 0x0A, "TxLateCollCount"),
-       MIB_DESC(1, 0x0B, "TxExcessCollCount"),
-       MIB_DESC(1, 0x0D, "TxPauseCount"),
-       MIB_DESC(1, 0x10, "TxDroppedPkts"),
-       MIB_DESC(2, 0x0E, "TxGoodBytes"),
-};
-
-static u32 gswip_switch_r_timeout(struct gswip_priv *priv, u32 offset,
-                                 u32 cleared)
-{
-       u32 val;
-
-       return regmap_read_poll_timeout(priv->gswip, offset, val,
-                                       !(val & cleared), 20, 50000);
-}
-
-static void gswip_mii_mask_cfg(struct gswip_priv *priv, u32 mask, u32 set,
-                              int port)
-{
-       int reg_port;
-
-       /* MII_CFG register only exists for MII ports */
-       if (!(priv->hw_info->mii_ports & BIT(port)))
-               return;
-
-       reg_port = port + priv->hw_info->mii_port_reg_offset;
-
-       regmap_write_bits(priv->mii, GSWIP_MII_CFGp(reg_port), mask,
-                         set);
-}
-
-static void gswip_mii_mask_pcdu(struct gswip_priv *priv, u32 mask, u32 set,
-                               int port)
-{
-       int reg_port;
-
-       /* MII_PCDU register only exists for MII ports */
-       if (!(priv->hw_info->mii_ports & BIT(port)))
-               return;
-
-       reg_port = port + priv->hw_info->mii_port_reg_offset;
-
-       switch (reg_port) {
-       case 0:
-               regmap_write_bits(priv->mii, GSWIP_MII_PCDU0, mask, set);
-               break;
-       case 1:
-               regmap_write_bits(priv->mii, GSWIP_MII_PCDU1, mask, set);
-               break;
-       case 5:
-               regmap_write_bits(priv->mii, GSWIP_MII_PCDU5, mask, set);
-               break;
-       }
-}
-
-static int gswip_mdio_poll(struct gswip_priv *priv)
-{
-       u32 ctrl;
-
-       return regmap_read_poll_timeout(priv->mdio, GSWIP_MDIO_CTRL, ctrl,
-                                       !(ctrl & GSWIP_MDIO_CTRL_BUSY), 40, 4000);
-}
-
-static int gswip_mdio_wr(struct mii_bus *bus, int addr, int reg, u16 val)
-{
-       struct gswip_priv *priv = bus->priv;
-       int err;
-
-       err = gswip_mdio_poll(priv);
-       if (err) {
-               dev_err(&bus->dev, "waiting for MDIO bus busy timed out\n");
-               return err;
-       }
-
-       regmap_write(priv->mdio, GSWIP_MDIO_WRITE, val);
-       regmap_write(priv->mdio, GSWIP_MDIO_CTRL,
-                    GSWIP_MDIO_CTRL_BUSY | GSWIP_MDIO_CTRL_WR |
-                    ((addr & GSWIP_MDIO_CTRL_PHYAD_MASK) << GSWIP_MDIO_CTRL_PHYAD_SHIFT) |
-                    (reg & GSWIP_MDIO_CTRL_REGAD_MASK));
-
-       return 0;
-}
-
-static int gswip_mdio_rd(struct mii_bus *bus, int addr, int reg)
-{
-       struct gswip_priv *priv = bus->priv;
-       u32 val;
-       int err;
-
-       err = gswip_mdio_poll(priv);
-       if (err) {
-               dev_err(&bus->dev, "waiting for MDIO bus busy timed out\n");
-               return err;
-       }
-
-       regmap_write(priv->mdio, GSWIP_MDIO_CTRL,
-                    GSWIP_MDIO_CTRL_BUSY | GSWIP_MDIO_CTRL_RD |
-                    ((addr & GSWIP_MDIO_CTRL_PHYAD_MASK) << GSWIP_MDIO_CTRL_PHYAD_SHIFT) |
-                    (reg & GSWIP_MDIO_CTRL_REGAD_MASK));
-
-       err = gswip_mdio_poll(priv);
-       if (err) {
-               dev_err(&bus->dev, "waiting for MDIO bus busy timed out\n");
-               return err;
-       }
-
-       err = regmap_read(priv->mdio, GSWIP_MDIO_READ, &val);
-       if (err)
-               return err;
-
-       return val;
-}
-
-static int gswip_mdio(struct gswip_priv *priv)
-{
-       struct device_node *mdio_np, *switch_np = priv->dev->of_node;
-       struct device *dev = priv->dev;
-       struct mii_bus *bus;
-       int err = 0;
-
-       mdio_np = of_get_compatible_child(switch_np, "lantiq,xrx200-mdio");
-       if (!mdio_np)
-               mdio_np = of_get_child_by_name(switch_np, "mdio");
-
-       if (!of_device_is_available(mdio_np))
-               goto out_put_node;
-
-       bus = devm_mdiobus_alloc(dev);
-       if (!bus) {
-               err = -ENOMEM;
-               goto out_put_node;
-       }
-
-       bus->priv = priv;
-       bus->read = gswip_mdio_rd;
-       bus->write = gswip_mdio_wr;
-       bus->name = "lantiq,xrx200-mdio";
-       snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(priv->dev));
-       bus->parent = priv->dev;
-
-       err = devm_of_mdiobus_register(dev, bus, mdio_np);
-
-out_put_node:
-       of_node_put(mdio_np);
-
-       return err;
-}
-
-static int gswip_pce_table_entry_read(struct gswip_priv *priv,
-                                     struct gswip_pce_table_entry *tbl)
-{
-       int i;
-       int err;
-       u32 crtl;
-       u32 tmp;
-       u16 addr_mode = tbl->key_mode ? GSWIP_PCE_TBL_CTRL_OPMOD_KSRD :
-                                       GSWIP_PCE_TBL_CTRL_OPMOD_ADRD;
-
-       mutex_lock(&priv->pce_table_lock);
-
-       err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL,
-                                    GSWIP_PCE_TBL_CTRL_BAS);
-       if (err)
-               goto out_unlock;
-
-       regmap_write(priv->gswip, GSWIP_PCE_TBL_ADDR, tbl->index);
-       regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL,
-                         GSWIP_PCE_TBL_CTRL_ADDR_MASK |
-                         GSWIP_PCE_TBL_CTRL_OPMOD_MASK |
-                         GSWIP_PCE_TBL_CTRL_BAS,
-                         tbl->table | addr_mode | GSWIP_PCE_TBL_CTRL_BAS);
-
-       err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL,
-                                    GSWIP_PCE_TBL_CTRL_BAS);
-       if (err)
-               goto out_unlock;
-
-       for (i = 0; i < ARRAY_SIZE(tbl->key); i++) {
-               err = regmap_read(priv->gswip, GSWIP_PCE_TBL_KEY(i), &tmp);
-               if (err)
-                       goto out_unlock;
-               tbl->key[i] = tmp;
-       }
-       for (i = 0; i < ARRAY_SIZE(tbl->val); i++) {
-               err = regmap_read(priv->gswip, GSWIP_PCE_TBL_VAL(i), &tmp);
-               if (err)
-                       goto out_unlock;
-               tbl->val[i] = tmp;
-       }
-
-       err = regmap_read(priv->gswip, GSWIP_PCE_TBL_MASK, &tmp);
-       if (err)
-               goto out_unlock;
-
-       tbl->mask = tmp;
-       err = regmap_read(priv->gswip, GSWIP_PCE_TBL_CTRL, &crtl);
-       if (err)
-               goto out_unlock;
-
-       tbl->type = !!(crtl & GSWIP_PCE_TBL_CTRL_TYPE);
-       tbl->valid = !!(crtl & GSWIP_PCE_TBL_CTRL_VLD);
-       tbl->gmap = (crtl & GSWIP_PCE_TBL_CTRL_GMAP_MASK) >> 7;
-
-out_unlock:
-       mutex_unlock(&priv->pce_table_lock);
-
-       return err;
-}
-
-static int gswip_pce_table_entry_write(struct gswip_priv *priv,
-                                      struct gswip_pce_table_entry *tbl)
-{
-       int i;
-       int err;
-       u32 crtl;
-       u16 addr_mode = tbl->key_mode ? GSWIP_PCE_TBL_CTRL_OPMOD_KSWR :
-                                       GSWIP_PCE_TBL_CTRL_OPMOD_ADWR;
-
-       mutex_lock(&priv->pce_table_lock);
-
-       err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL,
-                                    GSWIP_PCE_TBL_CTRL_BAS);
-       if (err) {
-               mutex_unlock(&priv->pce_table_lock);
-               return err;
-       }
-
-       regmap_write(priv->gswip, GSWIP_PCE_TBL_ADDR, tbl->index);
-       regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL,
-                         GSWIP_PCE_TBL_CTRL_ADDR_MASK |
-                         GSWIP_PCE_TBL_CTRL_OPMOD_MASK,
-                         tbl->table | addr_mode);
-
-       for (i = 0; i < ARRAY_SIZE(tbl->key); i++)
-               regmap_write(priv->gswip, GSWIP_PCE_TBL_KEY(i), tbl->key[i]);
-
-       for (i = 0; i < ARRAY_SIZE(tbl->val); i++)
-               regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(i), tbl->val[i]);
-
-       regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL,
-                         GSWIP_PCE_TBL_CTRL_ADDR_MASK |
-                         GSWIP_PCE_TBL_CTRL_OPMOD_MASK,
-                         tbl->table | addr_mode);
-
-       regmap_write(priv->gswip, GSWIP_PCE_TBL_MASK, tbl->mask);
-
-       regmap_read(priv->gswip, GSWIP_PCE_TBL_CTRL, &crtl);
-       crtl &= ~(GSWIP_PCE_TBL_CTRL_TYPE | GSWIP_PCE_TBL_CTRL_VLD |
-                 GSWIP_PCE_TBL_CTRL_GMAP_MASK);
-       if (tbl->type)
-               crtl |= GSWIP_PCE_TBL_CTRL_TYPE;
-       if (tbl->valid)
-               crtl |= GSWIP_PCE_TBL_CTRL_VLD;
-       crtl |= (tbl->gmap << 7) & GSWIP_PCE_TBL_CTRL_GMAP_MASK;
-       crtl |= GSWIP_PCE_TBL_CTRL_BAS;
-       regmap_write(priv->gswip, GSWIP_PCE_TBL_CTRL, crtl);
-
-       err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL,
-                                    GSWIP_PCE_TBL_CTRL_BAS);
-
-       mutex_unlock(&priv->pce_table_lock);
-
-       return err;
-}
-
-/* Add the LAN port into a bridge with the CPU port by
- * default. This prevents automatic forwarding of
- * packages between the LAN ports when no explicit
- * bridge is configured.
- */
-static int gswip_add_single_port_br(struct gswip_priv *priv, int port, bool add)
-{
-       struct gswip_pce_table_entry vlan_active = {0,};
-       struct gswip_pce_table_entry vlan_mapping = {0,};
-       int err;
-
-       vlan_active.index = port + 1;
-       vlan_active.table = GSWIP_TABLE_ACTIVE_VLAN;
-       vlan_active.key[0] = GSWIP_VLAN_UNAWARE_PVID;
-       vlan_active.val[0] = port + 1 /* fid */;
-       vlan_active.valid = add;
-       err = gswip_pce_table_entry_write(priv, &vlan_active);
-       if (err) {
-               dev_err(priv->dev, "failed to write active VLAN: %d\n", err);
-               return err;
-       }
-
-       if (!add)
-               return 0;
-
-       vlan_mapping.index = port + 1;
-       vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING;
-       vlan_mapping.val[0] = GSWIP_VLAN_UNAWARE_PVID;
-       vlan_mapping.val[1] = BIT(port) | dsa_cpu_ports(priv->ds);
-       vlan_mapping.val[2] = 0;
-       err = gswip_pce_table_entry_write(priv, &vlan_mapping);
-       if (err) {
-               dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err);
-               return err;
-       }
-
-       return 0;
-}
-
-static int gswip_port_setup(struct dsa_switch *ds, int port)
-{
-       struct gswip_priv *priv = ds->priv;
-       int err;
-
-       if (!dsa_is_cpu_port(ds, port)) {
-               err = gswip_add_single_port_br(priv, port, true);
-               if (err)
-                       return err;
-       }
-
-       return 0;
-}
-
-static int gswip_port_enable(struct dsa_switch *ds, int port,
-                            struct phy_device *phydev)
-{
-       struct gswip_priv *priv = ds->priv;
-
-       if (!dsa_is_cpu_port(ds, port)) {
-               u32 mdio_phy = 0;
-
-               if (phydev)
-                       mdio_phy = phydev->mdio.addr & GSWIP_MDIO_PHY_ADDR_MASK;
-
-               regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port),
-                                 GSWIP_MDIO_PHY_ADDR_MASK,
-                                 mdio_phy);
-       }
-
-       /* RMON Counter Enable for port */
-       regmap_write(priv->gswip, GSWIP_BM_PCFGp(port), GSWIP_BM_PCFG_CNTEN);
-
-       /* enable port fetch/store dma & VLAN Modification */
-       regmap_set_bits(priv->gswip, GSWIP_FDMA_PCTRLp(port),
-                       GSWIP_FDMA_PCTRL_EN | GSWIP_FDMA_PCTRL_VLANMOD_BOTH);
-       regmap_set_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port),
-                       GSWIP_SDMA_PCTRL_EN);
-
-       return 0;
-}
-
-static void gswip_port_disable(struct dsa_switch *ds, int port)
-{
-       struct gswip_priv *priv = ds->priv;
-
-       regmap_clear_bits(priv->gswip, GSWIP_FDMA_PCTRLp(port),
-                         GSWIP_FDMA_PCTRL_EN);
-       regmap_clear_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port),
-                         GSWIP_SDMA_PCTRL_EN);
-}
-
-static int gswip_pce_load_microcode(struct gswip_priv *priv)
-{
-       int i;
-       int err;
-
-       regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL,
-                         GSWIP_PCE_TBL_CTRL_ADDR_MASK |
-                         GSWIP_PCE_TBL_CTRL_OPMOD_MASK |
-                         GSWIP_PCE_TBL_CTRL_OPMOD_ADWR,
-                         GSWIP_PCE_TBL_CTRL_OPMOD_ADWR);
-       regmap_write(priv->gswip, GSWIP_PCE_TBL_MASK, 0);
-
-       for (i = 0; i < priv->hw_info->pce_microcode_size; i++) {
-               regmap_write(priv->gswip, GSWIP_PCE_TBL_ADDR, i);
-               regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(0),
-                            (*priv->hw_info->pce_microcode)[i].val_0);
-               regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(1),
-                            (*priv->hw_info->pce_microcode)[i].val_1);
-               regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(2),
-                            (*priv->hw_info->pce_microcode)[i].val_2);
-               regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(3),
-                            (*priv->hw_info->pce_microcode)[i].val_3);
-
-               /* start the table access: */
-               regmap_set_bits(priv->gswip, GSWIP_PCE_TBL_CTRL,
-                               GSWIP_PCE_TBL_CTRL_BAS);
-               err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL,
-                                            GSWIP_PCE_TBL_CTRL_BAS);
-               if (err)
-                       return err;
-       }
-
-       /* tell the switch that the microcode is loaded */
-       regmap_set_bits(priv->gswip, GSWIP_PCE_GCTRL_0,
-                       GSWIP_PCE_GCTRL_0_MC_VALID);
-
-       return 0;
-}
-
-static void gswip_port_commit_pvid(struct gswip_priv *priv, int port)
-{
-       struct dsa_port *dp = dsa_to_port(priv->ds, port);
-       struct net_device *br = dsa_port_bridge_dev_get(dp);
-       u32 vinr;
-       int idx;
-
-       if (!dsa_port_is_user(dp))
-               return;
-
-       if (br) {
-               u16 pvid = GSWIP_VLAN_UNAWARE_PVID;
-
-               if (br_vlan_enabled(br))
-                       br_vlan_get_pvid(br, &pvid);
-
-               /* VLAN-aware bridge ports with no PVID will use Active VLAN
-                * index 0. The expectation is that this drops all untagged and
-                * VID-0 tagged ingress traffic.
-                */
-               idx = 0;
-               for (int i = priv->hw_info->max_ports;
-                    i < ARRAY_SIZE(priv->vlans); i++) {
-                       if (priv->vlans[i].bridge == br &&
-                           priv->vlans[i].vid == pvid) {
-                               idx = i;
-                               break;
-                       }
-               }
-       } else {
-               /* The Active VLAN table index as configured by
-                * gswip_add_single_port_br()
-                */
-               idx = port + 1;
-       }
-
-       vinr = idx ? GSWIP_PCE_VCTRL_VINR_ALL : GSWIP_PCE_VCTRL_VINR_TAGGED;
-       regmap_write_bits(priv->gswip, GSWIP_PCE_VCTRL(port),
-                         GSWIP_PCE_VCTRL_VINR,
-                         FIELD_PREP(GSWIP_PCE_VCTRL_VINR, vinr));
-
-       /* Note that in GSWIP 2.2 VLAN mode the VID needs to be programmed
-        * directly instead of referencing the index in the Active VLAN Tablet.
-        * However, without the VLANMD bit (9) in PCE_GCTRL_1 (0x457) even
-        * GSWIP 2.2 and newer hardware maintain the GSWIP 2.1 behavior.
-        */
-       regmap_write(priv->gswip, GSWIP_PCE_DEFPVID(port), idx);
-}
-
-static int gswip_port_vlan_filtering(struct dsa_switch *ds, int port,
-                                    bool vlan_filtering,
-                                    struct netlink_ext_ack *extack)
-{
-       struct gswip_priv *priv = ds->priv;
-
-       if (vlan_filtering) {
-               /* Use tag based VLAN */
-               regmap_write_bits(priv->gswip, GSWIP_PCE_VCTRL(port),
-                                 GSWIP_PCE_VCTRL_VSR |
-                                 GSWIP_PCE_VCTRL_UVR |
-                                 GSWIP_PCE_VCTRL_VIMR |
-                                 GSWIP_PCE_VCTRL_VEMR |
-                                 GSWIP_PCE_VCTRL_VID0,
-                                 GSWIP_PCE_VCTRL_UVR |
-                                 GSWIP_PCE_VCTRL_VIMR |
-                                 GSWIP_PCE_VCTRL_VEMR |
-                                 GSWIP_PCE_VCTRL_VID0);
-               regmap_clear_bits(priv->gswip, GSWIP_PCE_PCTRL_0p(port),
-                                 GSWIP_PCE_PCTRL_0_TVM);
-       } else {
-               /* Use port based VLAN */
-               regmap_write_bits(priv->gswip, GSWIP_PCE_VCTRL(port),
-                                 GSWIP_PCE_VCTRL_UVR |
-                                 GSWIP_PCE_VCTRL_VIMR |
-                                 GSWIP_PCE_VCTRL_VEMR |
-                                 GSWIP_PCE_VCTRL_VID0 |
-                                 GSWIP_PCE_VCTRL_VSR,
-                                 GSWIP_PCE_VCTRL_VSR);
-               regmap_set_bits(priv->gswip, GSWIP_PCE_PCTRL_0p(port),
-                               GSWIP_PCE_PCTRL_0_TVM);
-       }
-
-       gswip_port_commit_pvid(priv, port);
-
-       return 0;
-}
-
-static int gswip_setup(struct dsa_switch *ds)
-{
-       unsigned int cpu_ports = dsa_cpu_ports(ds);
-       struct gswip_priv *priv = ds->priv;
-       struct dsa_port *cpu_dp;
-       int err, i;
-
-       regmap_write(priv->gswip, GSWIP_SWRES, GSWIP_SWRES_R0);
-       usleep_range(5000, 10000);
-       regmap_write(priv->gswip, GSWIP_SWRES, 0);
-
-       /* disable port fetch/store dma on all ports */
-       for (i = 0; i < priv->hw_info->max_ports; i++) {
-               gswip_port_disable(ds, i);
-               gswip_port_vlan_filtering(ds, i, false, NULL);
-       }
-
-       /* enable Switch */
-       regmap_set_bits(priv->mdio, GSWIP_MDIO_GLOB, GSWIP_MDIO_GLOB_ENABLE);
-
-       err = gswip_pce_load_microcode(priv);
-       if (err) {
-               dev_err(priv->dev, "writing PCE microcode failed, %i\n", err);
-               return err;
-       }
-
-       /* Default unknown Broadcast/Multicast/Unicast port maps */
-       regmap_write(priv->gswip, GSWIP_PCE_PMAP1, cpu_ports);
-       regmap_write(priv->gswip, GSWIP_PCE_PMAP2, cpu_ports);
-       regmap_write(priv->gswip, GSWIP_PCE_PMAP3, cpu_ports);
-
-       /* Deactivate MDIO PHY auto polling. Some PHYs as the AR8030 have an
-        * interoperability problem with this auto polling mechanism because
-        * their status registers think that the link is in a different state
-        * than it actually is. For the AR8030 it has the BMSR_ESTATEN bit set
-        * as well as ESTATUS_1000_TFULL and ESTATUS_1000_XFULL. This makes the
-        * auto polling state machine consider the link being negotiated with
-        * 1Gbit/s. Since the PHY itself is a Fast Ethernet RMII PHY this leads
-        * to the switch port being completely dead (RX and TX are both not
-        * working).
-        * Also with various other PHY / port combinations (PHY11G GPHY, PHY22F
-        * GPHY, external RGMII PEF7071/7072) any traffic would stop. Sometimes
-        * it would work fine for a few minutes to hours and then stop, on
-        * other device it would no traffic could be sent or received at all.
-        * Testing shows that when PHY auto polling is disabled these problems
-        * go away.
-        */
-       regmap_write(priv->mdio, GSWIP_MDIO_MDC_CFG0, 0x0);
-
-       /* Configure the MDIO Clock 2.5 MHz */
-       regmap_write_bits(priv->mdio, GSWIP_MDIO_MDC_CFG1, 0xff, 0x09);
-
-       /* bring up the mdio bus */
-       err = gswip_mdio(priv);
-       if (err) {
-               dev_err(priv->dev, "mdio bus setup failed\n");
-               return err;
-       }
-
-       /* Disable the xMII interface and clear it's isolation bit */
-       for (i = 0; i < priv->hw_info->max_ports; i++)
-               gswip_mii_mask_cfg(priv,
-                                  GSWIP_MII_CFG_EN | GSWIP_MII_CFG_ISOLATE,
-                                  0, i);
-
-       dsa_switch_for_each_cpu_port(cpu_dp, ds) {
-               /* enable special tag insertion on cpu port */
-               regmap_set_bits(priv->gswip, GSWIP_FDMA_PCTRLp(cpu_dp->index),
-                               GSWIP_FDMA_PCTRL_STEN);
-
-               /* accept special tag in ingress direction */
-               regmap_set_bits(priv->gswip,
-                               GSWIP_PCE_PCTRL_0p(cpu_dp->index),
-                               GSWIP_PCE_PCTRL_0_INGRESS);
-       }
-
-       regmap_set_bits(priv->gswip, GSWIP_BM_QUEUE_GCTRL,
-                       GSWIP_BM_QUEUE_GCTRL_GL_MOD);
-
-       /* VLAN aware Switching */
-       regmap_set_bits(priv->gswip, GSWIP_PCE_GCTRL_0,
-                       GSWIP_PCE_GCTRL_0_VLAN);
-
-       /* Flush MAC Table */
-       regmap_set_bits(priv->gswip, GSWIP_PCE_GCTRL_0,
-                       GSWIP_PCE_GCTRL_0_MTFL);
-
-       err = gswip_switch_r_timeout(priv, GSWIP_PCE_GCTRL_0,
-                                    GSWIP_PCE_GCTRL_0_MTFL);
-       if (err) {
-               dev_err(priv->dev, "MAC flushing didn't finish\n");
-               return err;
-       }
-
-       ds->mtu_enforcement_ingress = true;
-
-       return 0;
-}
-
-static enum dsa_tag_protocol gswip_get_tag_protocol(struct dsa_switch *ds,
-                                                   int port,
-                                                   enum dsa_tag_protocol mp)
-{
-       struct gswip_priv *priv = ds->priv;
-
-       return priv->hw_info->tag_protocol;
-}
-
-static int gswip_vlan_active_create(struct gswip_priv *priv,
-                                   struct net_device *bridge,
-                                   int fid, u16 vid)
-{
-       struct gswip_pce_table_entry vlan_active = {0,};
-       unsigned int max_ports = priv->hw_info->max_ports;
-       int idx = -1;
-       int err;
-       int i;
-
-       /* Look for a free slot */
-       for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) {
-               if (!priv->vlans[i].bridge) {
-                       idx = i;
-                       break;
-               }
-       }
-
-       if (idx == -1)
-               return -ENOSPC;
-
-       if (fid == -1)
-               fid = idx;
-
-       vlan_active.index = idx;
-       vlan_active.table = GSWIP_TABLE_ACTIVE_VLAN;
-       vlan_active.key[0] = vid;
-       vlan_active.val[0] = fid;
-       vlan_active.valid = true;
-
-       err = gswip_pce_table_entry_write(priv, &vlan_active);
-       if (err) {
-               dev_err(priv->dev, "failed to write active VLAN: %d\n", err);
-               return err;
-       }
-
-       priv->vlans[idx].bridge = bridge;
-       priv->vlans[idx].vid = vid;
-       priv->vlans[idx].fid = fid;
-
-       return idx;
-}
-
-static int gswip_vlan_active_remove(struct gswip_priv *priv, int idx)
-{
-       struct gswip_pce_table_entry vlan_active = {0,};
-       int err;
-
-       vlan_active.index = idx;
-       vlan_active.table = GSWIP_TABLE_ACTIVE_VLAN;
-       vlan_active.valid = false;
-       err = gswip_pce_table_entry_write(priv, &vlan_active);
-       if (err)
-               dev_err(priv->dev, "failed to delete active VLAN: %d\n", err);
-       priv->vlans[idx].bridge = NULL;
-
-       return err;
-}
-
-static int gswip_vlan_add(struct gswip_priv *priv, struct net_device *bridge,
-                         int port, u16 vid, bool untagged, bool pvid,
-                         bool vlan_aware)
-{
-       struct gswip_pce_table_entry vlan_mapping = {0,};
-       unsigned int max_ports = priv->hw_info->max_ports;
-       unsigned int cpu_ports = dsa_cpu_ports(priv->ds);
-       bool active_vlan_created = false;
-       int fid = -1, idx = -1;
-       int i, err;
-
-       /* Check if there is already a page for this bridge */
-       for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) {
-               if (priv->vlans[i].bridge == bridge) {
-                       if (vlan_aware) {
-                               if (fid != -1 && fid != priv->vlans[i].fid)
-                                       dev_err(priv->dev, "one bridge with multiple flow ids\n");
-                               fid = priv->vlans[i].fid;
-                       }
-                       if (priv->vlans[i].vid == vid) {
-                               idx = i;
-                               break;
-                       }
-               }
-       }
-
-       /* If this bridge is not programmed yet, add a Active VLAN table
-        * entry in a free slot and prepare the VLAN mapping table entry.
-        */
-       if (idx == -1) {
-               idx = gswip_vlan_active_create(priv, bridge, fid, vid);
-               if (idx < 0)
-                       return idx;
-               active_vlan_created = true;
-
-               vlan_mapping.index = idx;
-               vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING;
-       } else {
-               /* Read the existing VLAN mapping entry from the switch */
-               vlan_mapping.index = idx;
-               vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING;
-               err = gswip_pce_table_entry_read(priv, &vlan_mapping);
-               if (err) {
-                       dev_err(priv->dev, "failed to read VLAN mapping: %d\n",
-                               err);
-                       return err;
-               }
-       }
-
-       /* VLAN ID byte, maps to the VLAN ID of vlan active table */
-       vlan_mapping.val[0] = vid;
-       /* Update the VLAN mapping entry and write it to the switch */
-       vlan_mapping.val[1] |= cpu_ports;
-       vlan_mapping.val[1] |= BIT(port);
-       if (vlan_aware)
-               vlan_mapping.val[2] |= cpu_ports;
-       if (untagged)
-               vlan_mapping.val[2] &= ~BIT(port);
-       else
-               vlan_mapping.val[2] |= BIT(port);
-       err = gswip_pce_table_entry_write(priv, &vlan_mapping);
-       if (err) {
-               dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err);
-               /* In case an Active VLAN was creaetd delete it again */
-               if (active_vlan_created)
-                       gswip_vlan_active_remove(priv, idx);
-               return err;
-       }
-
-       gswip_port_commit_pvid(priv, port);
-
-       return 0;
-}
-
-static int gswip_vlan_remove(struct gswip_priv *priv,
-                            struct net_device *bridge, int port,
-                            u16 vid)
-{
-       struct gswip_pce_table_entry vlan_mapping = {0,};
-       unsigned int max_ports = priv->hw_info->max_ports;
-       int idx = -1;
-       int i;
-       int err;
-
-       /* Check if there is already a page for this bridge */
-       for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) {
-               if (priv->vlans[i].bridge == bridge &&
-                   priv->vlans[i].vid == vid) {
-                       idx = i;
-                       break;
-               }
-       }
-
-       if (idx == -1) {
-               dev_err(priv->dev, "Port %d cannot find VID %u of bridge %s\n",
-                       port, vid, bridge ? bridge->name : "(null)");
-               return -ENOENT;
-       }
-
-       vlan_mapping.index = idx;
-       vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING;
-       err = gswip_pce_table_entry_read(priv, &vlan_mapping);
-       if (err) {
-               dev_err(priv->dev, "failed to read VLAN mapping: %d\n", err);
-               return err;
-       }
-
-       vlan_mapping.val[1] &= ~BIT(port);
-       vlan_mapping.val[2] &= ~BIT(port);
-       err = gswip_pce_table_entry_write(priv, &vlan_mapping);
-       if (err) {
-               dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err);
-               return err;
-       }
-
-       /* In case all ports are removed from the bridge, remove the VLAN */
-       if (!(vlan_mapping.val[1] & ~dsa_cpu_ports(priv->ds))) {
-               err = gswip_vlan_active_remove(priv, idx);
-               if (err) {
-                       dev_err(priv->dev, "failed to write active VLAN: %d\n",
-                               err);
-                       return err;
-               }
-       }
-
-       gswip_port_commit_pvid(priv, port);
-
-       return 0;
-}
-
-static int gswip_port_bridge_join(struct dsa_switch *ds, int port,
-                                 struct dsa_bridge bridge,
-                                 bool *tx_fwd_offload,
-                                 struct netlink_ext_ack *extack)
-{
-       struct net_device *br = bridge.dev;
-       struct gswip_priv *priv = ds->priv;
-       int err;
-
-       /* Set up the VLAN for VLAN-unaware bridging for this port, and remove
-        * it from the "single-port bridge" through which it was operating as
-        * standalone.
-        */
-       err = gswip_vlan_add(priv, br, port, GSWIP_VLAN_UNAWARE_PVID,
-                            true, true, false);
-       if (err)
-               return err;
-
-       return gswip_add_single_port_br(priv, port, false);
-}
-
-static void gswip_port_bridge_leave(struct dsa_switch *ds, int port,
-                                   struct dsa_bridge bridge)
-{
-       struct net_device *br = bridge.dev;
-       struct gswip_priv *priv = ds->priv;
-
-       /* Add the port back to the "single-port bridge", and remove it from
-        * the VLAN-unaware PVID created for this bridge.
-        */
-       gswip_add_single_port_br(priv, port, true);
-       gswip_vlan_remove(priv, br, port, GSWIP_VLAN_UNAWARE_PVID);
-}
-
-static int gswip_port_vlan_prepare(struct dsa_switch *ds, int port,
-                                  const struct switchdev_obj_port_vlan *vlan,
-                                  struct netlink_ext_ack *extack)
-{
-       struct net_device *bridge = dsa_port_bridge_dev_get(dsa_to_port(ds, port));
-       struct gswip_priv *priv = ds->priv;
-       unsigned int max_ports = priv->hw_info->max_ports;
-       int pos = max_ports;
-       int i, idx = -1;
-
-       /* We only support VLAN filtering on bridges */
-       if (!dsa_is_cpu_port(ds, port) && !bridge)
-               return -EOPNOTSUPP;
-
-       /* Check if there is already a page for this VLAN */
-       for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) {
-               if (priv->vlans[i].bridge == bridge &&
-                   priv->vlans[i].vid == vlan->vid) {
-                       idx = i;
-                       break;
-               }
-       }
-
-       /* If this VLAN is not programmed yet, we have to reserve
-        * one entry in the VLAN table. Make sure we start at the
-        * next position round.
-        */
-       if (idx == -1) {
-               /* Look for a free slot */
-               for (; pos < ARRAY_SIZE(priv->vlans); pos++) {
-                       if (!priv->vlans[pos].bridge) {
-                               idx = pos;
-                               pos++;
-                               break;
-                       }
-               }
-
-               if (idx == -1) {
-                       NL_SET_ERR_MSG_MOD(extack, "No slot in VLAN table");
-                       return -ENOSPC;
-               }
-       }
-
-       return 0;
-}
-
-static int gswip_port_vlan_add(struct dsa_switch *ds, int port,
-                              const struct switchdev_obj_port_vlan *vlan,
-                              struct netlink_ext_ack *extack)
-{
-       struct net_device *bridge = dsa_port_bridge_dev_get(dsa_to_port(ds, port));
-       struct gswip_priv *priv = ds->priv;
-       bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
-       bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
-       int err;
-
-       if (vlan->vid == GSWIP_VLAN_UNAWARE_PVID)
-               return 0;
-
-       err = gswip_port_vlan_prepare(ds, port, vlan, extack);
-       if (err)
-               return err;
-
-       /* We have to receive all packets on the CPU port and should not
-        * do any VLAN filtering here. This is also called with bridge
-        * NULL and then we do not know for which bridge to configure
-        * this.
-        */
-       if (dsa_is_cpu_port(ds, port))
-               return 0;
-
-       return gswip_vlan_add(priv, bridge, port, vlan->vid, untagged, pvid,
-                             true);
-}
-
-static int gswip_port_vlan_del(struct dsa_switch *ds, int port,
-                              const struct switchdev_obj_port_vlan *vlan)
-{
-       struct net_device *bridge = dsa_port_bridge_dev_get(dsa_to_port(ds, port));
-       struct gswip_priv *priv = ds->priv;
-
-       if (vlan->vid == GSWIP_VLAN_UNAWARE_PVID)
-               return 0;
-
-       /* We have to receive all packets on the CPU port and should not
-        * do any VLAN filtering here. This is also called with bridge
-        * NULL and then we do not know for which bridge to configure
-        * this.
-        */
-       if (dsa_is_cpu_port(ds, port))
-               return 0;
-
-       return gswip_vlan_remove(priv, bridge, port, vlan->vid);
-}
-
-static void gswip_port_fast_age(struct dsa_switch *ds, int port)
-{
-       struct gswip_priv *priv = ds->priv;
-       struct gswip_pce_table_entry mac_bridge = {0,};
-       int i;
-       int err;
-
-       for (i = 0; i < 2048; i++) {
-               mac_bridge.table = GSWIP_TABLE_MAC_BRIDGE;
-               mac_bridge.index = i;
-
-               err = gswip_pce_table_entry_read(priv, &mac_bridge);
-               if (err) {
-                       dev_err(priv->dev, "failed to read mac bridge: %d\n",
-                               err);
-                       return;
-               }
-
-               if (!mac_bridge.valid)
-                       continue;
-
-               if (mac_bridge.val[1] & GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC)
-                       continue;
-
-               if (port != FIELD_GET(GSWIP_TABLE_MAC_BRIDGE_VAL0_PORT,
-                                     mac_bridge.val[0]))
-                       continue;
-
-               mac_bridge.valid = false;
-               err = gswip_pce_table_entry_write(priv, &mac_bridge);
-               if (err) {
-                       dev_err(priv->dev, "failed to write mac bridge: %d\n",
-                               err);
-                       return;
-               }
-       }
-}
-
-static void gswip_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
-{
-       struct gswip_priv *priv = ds->priv;
-       u32 stp_state;
-
-       switch (state) {
-       case BR_STATE_DISABLED:
-               regmap_clear_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port),
-                                 GSWIP_SDMA_PCTRL_EN);
-               return;
-       case BR_STATE_BLOCKING:
-       case BR_STATE_LISTENING:
-               stp_state = GSWIP_PCE_PCTRL_0_PSTATE_LISTEN;
-               break;
-       case BR_STATE_LEARNING:
-               stp_state = GSWIP_PCE_PCTRL_0_PSTATE_LEARNING;
-               break;
-       case BR_STATE_FORWARDING:
-               stp_state = GSWIP_PCE_PCTRL_0_PSTATE_FORWARDING;
-               break;
-       default:
-               dev_err(priv->dev, "invalid STP state: %d\n", state);
-               return;
-       }
-
-       regmap_set_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port),
-                       GSWIP_SDMA_PCTRL_EN);
-       regmap_write_bits(priv->gswip, GSWIP_PCE_PCTRL_0p(port),
-                         GSWIP_PCE_PCTRL_0_PSTATE_MASK,
-                         stp_state);
-}
-
-static int gswip_port_fdb(struct dsa_switch *ds, int port,
-                         struct net_device *bridge, const unsigned char *addr,
-                         u16 vid, bool add)
-{
-       struct gswip_priv *priv = ds->priv;
-       struct gswip_pce_table_entry mac_bridge = {0,};
-       unsigned int max_ports = priv->hw_info->max_ports;
-       int fid = -1;
-       int i;
-       int err;
-
-       for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) {
-               if (priv->vlans[i].bridge == bridge) {
-                       fid = priv->vlans[i].fid;
-                       break;
-               }
-       }
-
-       if (fid == -1) {
-               dev_err(priv->dev, "no FID found for bridge %s\n",
-                       bridge->name);
-               return -EINVAL;
-       }
-
-       mac_bridge.table = GSWIP_TABLE_MAC_BRIDGE;
-       mac_bridge.key_mode = true;
-       mac_bridge.key[0] = addr[5] | (addr[4] << 8);
-       mac_bridge.key[1] = addr[3] | (addr[2] << 8);
-       mac_bridge.key[2] = addr[1] | (addr[0] << 8);
-       mac_bridge.key[3] = FIELD_PREP(GSWIP_TABLE_MAC_BRIDGE_KEY3_FID, fid);
-       mac_bridge.val[0] = add ? BIT(port) : 0; /* port map */
-       mac_bridge.val[1] = GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC;
-       mac_bridge.valid = add;
-
-       err = gswip_pce_table_entry_write(priv, &mac_bridge);
-       if (err)
-               dev_err(priv->dev, "failed to write mac bridge: %d\n", err);
-
-       return err;
-}
-
-static int gswip_port_fdb_add(struct dsa_switch *ds, int port,
-                             const unsigned char *addr, u16 vid,
-                             struct dsa_db db)
-{
-       if (db.type != DSA_DB_BRIDGE)
-               return -EOPNOTSUPP;
-
-       return gswip_port_fdb(ds, port, db.bridge.dev, addr, vid, true);
-}
-
-static int gswip_port_fdb_del(struct dsa_switch *ds, int port,
-                             const unsigned char *addr, u16 vid,
-                             struct dsa_db db)
-{
-       if (db.type != DSA_DB_BRIDGE)
-               return -EOPNOTSUPP;
-
-       return gswip_port_fdb(ds, port, db.bridge.dev, addr, vid, false);
-}
-
-static int gswip_port_fdb_dump(struct dsa_switch *ds, int port,
-                              dsa_fdb_dump_cb_t *cb, void *data)
-{
-       struct gswip_priv *priv = ds->priv;
-       struct gswip_pce_table_entry mac_bridge = {0,};
-       unsigned char addr[ETH_ALEN];
-       int i;
-       int err;
-
-       for (i = 0; i < 2048; i++) {
-               mac_bridge.table = GSWIP_TABLE_MAC_BRIDGE;
-               mac_bridge.index = i;
-
-               err = gswip_pce_table_entry_read(priv, &mac_bridge);
-               if (err) {
-                       dev_err(priv->dev,
-                               "failed to read mac bridge entry %d: %d\n",
-                               i, err);
-                       return err;
-               }
-
-               if (!mac_bridge.valid)
-                       continue;
-
-               addr[5] = mac_bridge.key[0] & 0xff;
-               addr[4] = (mac_bridge.key[0] >> 8) & 0xff;
-               addr[3] = mac_bridge.key[1] & 0xff;
-               addr[2] = (mac_bridge.key[1] >> 8) & 0xff;
-               addr[1] = mac_bridge.key[2] & 0xff;
-               addr[0] = (mac_bridge.key[2] >> 8) & 0xff;
-               if (mac_bridge.val[1] & GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC) {
-                       if (mac_bridge.val[0] & BIT(port)) {
-                               err = cb(addr, 0, true, data);
-                               if (err)
-                                       return err;
-                       }
-               } else {
-                       if (port == FIELD_GET(GSWIP_TABLE_MAC_BRIDGE_VAL0_PORT,
-                                             mac_bridge.val[0])) {
-                               err = cb(addr, 0, false, data);
-                               if (err)
-                                       return err;
-                       }
-               }
-       }
-       return 0;
-}
-
-static int gswip_port_max_mtu(struct dsa_switch *ds, int port)
-{
-       /* Includes 8 bytes for special header. */
-       return GSWIP_MAX_PACKET_LENGTH - VLAN_ETH_HLEN - ETH_FCS_LEN;
-}
+ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
+ * Copyright (C) 2017 - 2019 Hauke Mehrtens <hauke@hauke-m.de>
+ * Copyright (C) 2012 John Crispin <john@phrozen.org>
+ * Copyright (C) 2010 Lantiq Deutschland
+ */
 
-static int gswip_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
-{
-       struct gswip_priv *priv = ds->priv;
+#include "lantiq_gswip.h"
+#include "lantiq_pce.h"
 
-       /* CPU port always has maximum mtu of user ports, so use it to set
-        * switch frame size, including 8 byte special header.
-        */
-       if (dsa_is_cpu_port(ds, port)) {
-               new_mtu += 8;
-               regmap_write(priv->gswip, GSWIP_MAC_FLEN,
-                            VLAN_ETH_HLEN + new_mtu + ETH_FCS_LEN);
-       }
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <dt-bindings/mips/lantiq_rcu_gphy.h>
 
-       /* Enable MLEN for ports with non-standard MTUs, including the special
-        * header on the CPU port added above.
-        */
-       if (new_mtu != ETH_DATA_LEN)
-               regmap_set_bits(priv->gswip, GSWIP_MAC_CTRL_2p(port),
-                               GSWIP_MAC_CTRL_2_MLEN);
-       else
-               regmap_clear_bits(priv->gswip, GSWIP_MAC_CTRL_2p(port),
-                                 GSWIP_MAC_CTRL_2_MLEN);
+#include <net/dsa.h>
 
-       return 0;
-}
+struct xway_gphy_match_data {
+       char *fe_firmware_name;
+       char *ge_firmware_name;
+};
 
 static void gswip_xrx200_phylink_get_caps(struct dsa_switch *ds, int port,
                                          struct phylink_config *config)
@@ -1291,327 +97,6 @@ static void gswip_xrx300_phylink_get_caps(struct dsa_switch *ds, int port,
                MAC_10 | MAC_100 | MAC_1000;
 }
 
-static void gswip_phylink_get_caps(struct dsa_switch *ds, int port,
-                                  struct phylink_config *config)
-{
-       struct gswip_priv *priv = ds->priv;
-
-       priv->hw_info->phylink_get_caps(ds, port, config);
-}
-
-static void gswip_port_set_link(struct gswip_priv *priv, int port, bool link)
-{
-       u32 mdio_phy;
-
-       if (link)
-               mdio_phy = GSWIP_MDIO_PHY_LINK_UP;
-       else
-               mdio_phy = GSWIP_MDIO_PHY_LINK_DOWN;
-
-       regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port),
-                         GSWIP_MDIO_PHY_LINK_MASK, mdio_phy);
-}
-
-static void gswip_port_set_speed(struct gswip_priv *priv, int port, int speed,
-                                phy_interface_t interface)
-{
-       u32 mdio_phy = 0, mii_cfg = 0, mac_ctrl_0 = 0;
-
-       switch (speed) {
-       case SPEED_10:
-               mdio_phy = GSWIP_MDIO_PHY_SPEED_M10;
-
-               if (interface == PHY_INTERFACE_MODE_RMII)
-                       mii_cfg = GSWIP_MII_CFG_RATE_M50;
-               else
-                       mii_cfg = GSWIP_MII_CFG_RATE_M2P5;
-
-               mac_ctrl_0 = GSWIP_MAC_CTRL_0_GMII_MII;
-               break;
-
-       case SPEED_100:
-               mdio_phy = GSWIP_MDIO_PHY_SPEED_M100;
-
-               if (interface == PHY_INTERFACE_MODE_RMII)
-                       mii_cfg = GSWIP_MII_CFG_RATE_M50;
-               else
-                       mii_cfg = GSWIP_MII_CFG_RATE_M25;
-
-               mac_ctrl_0 = GSWIP_MAC_CTRL_0_GMII_MII;
-               break;
-
-       case SPEED_1000:
-               mdio_phy = GSWIP_MDIO_PHY_SPEED_G1;
-
-               mii_cfg = GSWIP_MII_CFG_RATE_M125;
-
-               mac_ctrl_0 = GSWIP_MAC_CTRL_0_GMII_RGMII;
-               break;
-       }
-
-       regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port),
-                         GSWIP_MDIO_PHY_SPEED_MASK, mdio_phy);
-       gswip_mii_mask_cfg(priv, GSWIP_MII_CFG_RATE_MASK, mii_cfg, port);
-       regmap_write_bits(priv->gswip, GSWIP_MAC_CTRL_0p(port),
-                         GSWIP_MAC_CTRL_0_GMII_MASK, mac_ctrl_0);
-}
-
-static void gswip_port_set_duplex(struct gswip_priv *priv, int port, int duplex)
-{
-       u32 mac_ctrl_0, mdio_phy;
-
-       if (duplex == DUPLEX_FULL) {
-               mac_ctrl_0 = GSWIP_MAC_CTRL_0_FDUP_EN;
-               mdio_phy = GSWIP_MDIO_PHY_FDUP_EN;
-       } else {
-               mac_ctrl_0 = GSWIP_MAC_CTRL_0_FDUP_DIS;
-               mdio_phy = GSWIP_MDIO_PHY_FDUP_DIS;
-       }
-
-       regmap_write_bits(priv->gswip, GSWIP_MAC_CTRL_0p(port),
-                         GSWIP_MAC_CTRL_0_FDUP_MASK, mac_ctrl_0);
-       regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port),
-                         GSWIP_MDIO_PHY_FDUP_MASK, mdio_phy);
-}
-
-static void gswip_port_set_pause(struct gswip_priv *priv, int port,
-                                bool tx_pause, bool rx_pause)
-{
-       u32 mac_ctrl_0, mdio_phy;
-
-       if (tx_pause && rx_pause) {
-               mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_RXTX;
-               mdio_phy = GSWIP_MDIO_PHY_FCONTX_EN |
-                          GSWIP_MDIO_PHY_FCONRX_EN;
-       } else if (tx_pause) {
-               mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_TX;
-               mdio_phy = GSWIP_MDIO_PHY_FCONTX_EN |
-                          GSWIP_MDIO_PHY_FCONRX_DIS;
-       } else if (rx_pause) {
-               mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_RX;
-               mdio_phy = GSWIP_MDIO_PHY_FCONTX_DIS |
-                          GSWIP_MDIO_PHY_FCONRX_EN;
-       } else {
-               mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_NONE;
-               mdio_phy = GSWIP_MDIO_PHY_FCONTX_DIS |
-                          GSWIP_MDIO_PHY_FCONRX_DIS;
-       }
-
-       regmap_write_bits(priv->gswip, GSWIP_MAC_CTRL_0p(port),
-                         GSWIP_MAC_CTRL_0_FCON_MASK, mac_ctrl_0);
-       regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port),
-                         GSWIP_MDIO_PHY_FCONTX_MASK | GSWIP_MDIO_PHY_FCONRX_MASK,
-                         mdio_phy);
-}
-
-static void gswip_phylink_mac_config(struct phylink_config *config,
-                                    unsigned int mode,
-                                    const struct phylink_link_state *state)
-{
-       struct dsa_port *dp = dsa_phylink_to_port(config);
-       struct gswip_priv *priv = dp->ds->priv;
-       int port = dp->index;
-       u32 miicfg = 0;
-
-       miicfg |= GSWIP_MII_CFG_LDCLKDIS;
-
-       switch (state->interface) {
-       case PHY_INTERFACE_MODE_SGMII:
-       case PHY_INTERFACE_MODE_1000BASEX:
-       case PHY_INTERFACE_MODE_2500BASEX:
-               return;
-       case PHY_INTERFACE_MODE_MII:
-       case PHY_INTERFACE_MODE_INTERNAL:
-               miicfg |= GSWIP_MII_CFG_MODE_MIIM;
-               break;
-       case PHY_INTERFACE_MODE_REVMII:
-               miicfg |= GSWIP_MII_CFG_MODE_MIIP;
-               break;
-       case PHY_INTERFACE_MODE_RMII:
-               miicfg |= GSWIP_MII_CFG_MODE_RMIIM;
-               break;
-       case PHY_INTERFACE_MODE_RGMII:
-       case PHY_INTERFACE_MODE_RGMII_ID:
-       case PHY_INTERFACE_MODE_RGMII_RXID:
-       case PHY_INTERFACE_MODE_RGMII_TXID:
-               miicfg |= GSWIP_MII_CFG_MODE_RGMII;
-               break;
-       case PHY_INTERFACE_MODE_GMII:
-               miicfg |= GSWIP_MII_CFG_MODE_GMII;
-               break;
-       default:
-               dev_err(dp->ds->dev,
-                       "Unsupported interface: %d\n", state->interface);
-               return;
-       }
-
-       gswip_mii_mask_cfg(priv,
-                          GSWIP_MII_CFG_MODE_MASK | GSWIP_MII_CFG_RMII_CLK |
-                          GSWIP_MII_CFG_RGMII_IBS | GSWIP_MII_CFG_LDCLKDIS,
-                          miicfg, port);
-
-       switch (state->interface) {
-       case PHY_INTERFACE_MODE_RGMII_ID:
-               gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK |
-                                         GSWIP_MII_PCDU_RXDLY_MASK, 0, port);
-               break;
-       case PHY_INTERFACE_MODE_RGMII_RXID:
-               gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_RXDLY_MASK, 0, port);
-               break;
-       case PHY_INTERFACE_MODE_RGMII_TXID:
-               gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK, 0, port);
-               break;
-       default:
-               break;
-       }
-}
-
-static void gswip_phylink_mac_link_down(struct phylink_config *config,
-                                       unsigned int mode,
-                                       phy_interface_t interface)
-{
-       struct dsa_port *dp = dsa_phylink_to_port(config);
-       struct gswip_priv *priv = dp->ds->priv;
-       int port = dp->index;
-
-       gswip_mii_mask_cfg(priv, GSWIP_MII_CFG_EN, 0, port);
-
-       if (!dsa_port_is_cpu(dp))
-               gswip_port_set_link(priv, port, false);
-}
-
-static void gswip_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 dsa_port *dp = dsa_phylink_to_port(config);
-       struct gswip_priv *priv = dp->ds->priv;
-       int port = dp->index;
-
-       if (!dsa_port_is_cpu(dp)) {
-               gswip_port_set_link(priv, port, true);
-               gswip_port_set_speed(priv, port, speed, interface);
-               gswip_port_set_duplex(priv, port, duplex);
-               gswip_port_set_pause(priv, port, tx_pause, rx_pause);
-       }
-
-       gswip_mii_mask_cfg(priv, GSWIP_MII_CFG_EN, GSWIP_MII_CFG_EN, port);
-}
-
-static void gswip_get_strings(struct dsa_switch *ds, int port, u32 stringset,
-                             uint8_t *data)
-{
-       int i;
-
-       if (stringset != ETH_SS_STATS)
-               return;
-
-       for (i = 0; i < ARRAY_SIZE(gswip_rmon_cnt); i++)
-               ethtool_puts(&data, gswip_rmon_cnt[i].name);
-}
-
-static u32 gswip_bcm_ram_entry_read(struct gswip_priv *priv, u32 table,
-                                   u32 index)
-{
-       u32 result, val;
-       int err;
-
-       regmap_write(priv->gswip, GSWIP_BM_RAM_ADDR, index);
-       regmap_write_bits(priv->gswip, GSWIP_BM_RAM_CTRL,
-                         GSWIP_BM_RAM_CTRL_ADDR_MASK | GSWIP_BM_RAM_CTRL_OPMOD |
-                         GSWIP_BM_RAM_CTRL_BAS,
-                         table | GSWIP_BM_RAM_CTRL_BAS);
-
-       err = gswip_switch_r_timeout(priv, GSWIP_BM_RAM_CTRL,
-                                    GSWIP_BM_RAM_CTRL_BAS);
-       if (err) {
-               dev_err(priv->dev, "timeout while reading table: %u, index: %u\n",
-                       table, index);
-               return 0;
-       }
-
-       regmap_read(priv->gswip, GSWIP_BM_RAM_VAL(0), &result);
-       regmap_read(priv->gswip, GSWIP_BM_RAM_VAL(1), &val);
-       result |= val << 16;
-
-       return result;
-}
-
-static void gswip_get_ethtool_stats(struct dsa_switch *ds, int port,
-                                   uint64_t *data)
-{
-       struct gswip_priv *priv = ds->priv;
-       const struct gswip_rmon_cnt_desc *rmon_cnt;
-       int i;
-       u64 high;
-
-       for (i = 0; i < ARRAY_SIZE(gswip_rmon_cnt); i++) {
-               rmon_cnt = &gswip_rmon_cnt[i];
-
-               data[i] = gswip_bcm_ram_entry_read(priv, port,
-                                                  rmon_cnt->offset);
-               if (rmon_cnt->size == 2) {
-                       high = gswip_bcm_ram_entry_read(priv, port,
-                                                       rmon_cnt->offset + 1);
-                       data[i] |= high << 32;
-               }
-       }
-}
-
-static int gswip_get_sset_count(struct dsa_switch *ds, int port, int sset)
-{
-       if (sset != ETH_SS_STATS)
-               return 0;
-
-       return ARRAY_SIZE(gswip_rmon_cnt);
-}
-
-static struct phylink_pcs *gswip_phylink_mac_select_pcs(struct phylink_config *config,
-                                                       phy_interface_t interface)
-{
-       struct dsa_port *dp = dsa_phylink_to_port(config);
-       struct gswip_priv *priv = dp->ds->priv;
-
-       if (priv->hw_info->mac_select_pcs)
-               return priv->hw_info->mac_select_pcs(config, interface);
-
-       return NULL;
-}
-
-static const struct phylink_mac_ops gswip_phylink_mac_ops = {
-       .mac_config             = gswip_phylink_mac_config,
-       .mac_link_down          = gswip_phylink_mac_link_down,
-       .mac_link_up            = gswip_phylink_mac_link_up,
-       .mac_select_pcs         = gswip_phylink_mac_select_pcs,
-};
-
-static const struct dsa_switch_ops gswip_switch_ops = {
-       .get_tag_protocol       = gswip_get_tag_protocol,
-       .setup                  = gswip_setup,
-       .port_setup             = gswip_port_setup,
-       .port_enable            = gswip_port_enable,
-       .port_disable           = gswip_port_disable,
-       .port_bridge_join       = gswip_port_bridge_join,
-       .port_bridge_leave      = gswip_port_bridge_leave,
-       .port_fast_age          = gswip_port_fast_age,
-       .port_vlan_filtering    = gswip_port_vlan_filtering,
-       .port_vlan_add          = gswip_port_vlan_add,
-       .port_vlan_del          = gswip_port_vlan_del,
-       .port_stp_state_set     = gswip_port_stp_state_set,
-       .port_fdb_add           = gswip_port_fdb_add,
-       .port_fdb_del           = gswip_port_fdb_del,
-       .port_fdb_dump          = gswip_port_fdb_dump,
-       .port_change_mtu        = gswip_port_change_mtu,
-       .port_max_mtu           = gswip_port_max_mtu,
-       .phylink_get_caps       = gswip_phylink_get_caps,
-       .get_strings            = gswip_get_strings,
-       .get_ethtool_stats      = gswip_get_ethtool_stats,
-       .get_sset_count         = gswip_get_sset_count,
-};
-
 static const struct xway_gphy_match_data xrx200a1x_gphy_data = {
        .fe_firmware_name = "lantiq/xrx200_phy22f_a14.bin",
        .ge_firmware_name = "lantiq/xrx200_phy11g_a14.bin",
@@ -1832,30 +317,6 @@ remove_gphy:
        return err;
 }
 
-static int gswip_validate_cpu_port(struct dsa_switch *ds)
-{
-       struct gswip_priv *priv = ds->priv;
-       struct dsa_port *cpu_dp;
-       int cpu_port = -1;
-
-       dsa_switch_for_each_cpu_port(cpu_dp, ds) {
-               if (cpu_port != -1)
-                       return dev_err_probe(ds->dev, -EINVAL,
-                                            "only a single CPU port is supported\n");
-
-               cpu_port = cpu_dp->index;
-       }
-
-       if (cpu_port == -1)
-               return dev_err_probe(ds->dev, -EINVAL, "no CPU port defined\n");
-
-       if (BIT(cpu_port) & ~priv->hw_info->allowed_cpu_ports)
-               return dev_err_probe(ds->dev, -EINVAL,
-                                    "unsupported CPU port defined\n");
-
-       return 0;
-}
-
 static const struct regmap_config sw_regmap_config = {
        .name = "switch",
        .reg_bits = 32,
@@ -1929,24 +390,9 @@ static int gswip_probe(struct platform_device *pdev)
        if (!priv->ds)
                return -ENOMEM;
 
-       priv->ds->dev = dev;
-       priv->ds->num_ports = priv->hw_info->max_ports;
-       priv->ds->priv = priv;
-       priv->ds->ops = &gswip_switch_ops;
-       priv->ds->phylink_mac_ops = &gswip_phylink_mac_ops;
        priv->dev = dev;
-       mutex_init(&priv->pce_table_lock);
-       regmap_read(priv->gswip, GSWIP_VERSION, &version);
 
-       /* The hardware has the 'major/minor' version bytes in the wrong order
-        * preventing numerical comparisons. Construct a 16-bit unsigned integer
-        * having the REV field as most significant byte and the MOD field as
-        * least significant byte. This is effectively swapping the two bytes of
-        * the version variable, but other than using swab16 it doesn't affect
-        * the source variable.
-        */
-       priv->version = GSWIP_VERSION_REV(version) << 8 |
-                       GSWIP_VERSION_MOD(version);
+       regmap_read(priv->gswip, GSWIP_VERSION, &version);
 
        np = dev->of_node;
        switch (version) {
@@ -1976,25 +422,14 @@ static int gswip_probe(struct platform_device *pdev)
                                             "gphy fw probe failed\n");
        }
 
-       err = dsa_register_switch(priv->ds);
-       if (err) {
-               dev_err_probe(dev, err, "dsa switch registration failed\n");
-               goto gphy_fw_remove;
-       }
-
-       err = gswip_validate_cpu_port(priv->ds);
+       err = gswip_probe_common(priv, version);
        if (err)
-               goto disable_switch;
+               goto gphy_fw_remove;
 
        platform_set_drvdata(pdev, priv);
 
-       dev_info(dev, "probed GSWIP version %lx mod %lx\n",
-                GSWIP_VERSION_REV(version), GSWIP_VERSION_MOD(version));
        return 0;
 
-disable_switch:
-       regmap_clear_bits(priv->mdio, GSWIP_MDIO_GLOB, GSWIP_MDIO_GLOB_ENABLE);
-       dsa_unregister_switch(priv->ds);
 gphy_fw_remove:
        for (i = 0; i < priv->num_gphy_fw; i++)
                gswip_gphy_fw_remove(priv, &priv->gphy_fw[i]);
@@ -2010,7 +445,7 @@ static void gswip_remove(struct platform_device *pdev)
                return;
 
        /* disable the switch */
-       regmap_clear_bits(priv->mdio, GSWIP_MDIO_GLOB, GSWIP_MDIO_GLOB_ENABLE);
+       gswip_disable_switch(priv);
 
        dsa_unregister_switch(priv->ds);
 
index 24d759e06e153ff36ab097346a98f856ac13e75a..d86290db19b41b575f03df999ce8da1530431464 100644 (file)
@@ -278,4 +278,8 @@ struct gswip_priv {
        u16 version;
 };
 
+void gswip_disable_switch(struct gswip_priv *priv);
+
+int gswip_probe_common(struct gswip_priv *priv, u32 version);
+
 #endif /* __LANTIQ_GSWIP_H */
diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c
new file mode 100644 (file)
index 0000000..a0e3616
--- /dev/null
@@ -0,0 +1,1622 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Lantiq / Intel / MaxLinear GSWIP common function library
+ *
+ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
+ * Copyright (C) 2023 - 2024 MaxLinear Inc.
+ * Copyright (C) 2022 Snap One, LLC.  All rights reserved.
+ * Copyright (C) 2017 - 2019 Hauke Mehrtens <hauke@hauke-m.de>
+ * Copyright (C) 2012 John Crispin <john@phrozen.org>
+ * Copyright (C) 2010 Lantiq Deutschland
+ *
+ * The VLAN and bridge model the GSWIP hardware uses does not directly
+ * matches the model DSA uses.
+ *
+ * The hardware has 64 possible table entries for bridges with one VLAN
+ * ID, one flow id and a list of ports for each bridge. All entries which
+ * match the same flow ID are combined in the mac learning table, they
+ * act as one global bridge.
+ * The hardware does not support VLAN filter on the port, but on the
+ * bridge, this driver converts the DSA model to the hardware.
+ *
+ * The CPU gets all the exception frames which do not match any forwarding
+ * rule and the CPU port is also added to all bridges. This makes it possible
+ * to handle all the special cases easily in software.
+ * At the initialization the driver allocates one bridge table entry for
+ * each switch port which is used when the port is used without an
+ * explicit bridge. This prevents the frames from being forwarded
+ * between all LAN ports by default.
+ */
+
+#include "lantiq_gswip.h"
+
+#include <linux/delay.h>
+#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
+#include <linux/if_vlan.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/phy.h>
+#include <linux/phylink.h>
+#include <linux/regmap.h>
+#include <net/dsa.h>
+
+struct gswip_pce_table_entry {
+       u16 index;      // PCE_TBL_ADDR.ADDR = pData->table_index
+       u16 table;      // PCE_TBL_CTRL.ADDR = pData->table
+       u16 key[8];
+       u16 val[5];
+       u16 mask;
+       u8 gmap;
+       bool type;
+       bool valid;
+       bool key_mode;
+};
+
+struct gswip_rmon_cnt_desc {
+       unsigned int size;
+       unsigned int offset;
+       const char *name;
+};
+
+#define MIB_DESC(_size, _offset, _name) {.size = _size, .offset = _offset, .name = _name}
+
+static const struct gswip_rmon_cnt_desc gswip_rmon_cnt[] = {
+       /** Receive Packet Count (only packets that are accepted and not discarded). */
+       MIB_DESC(1, 0x1F, "RxGoodPkts"),
+       MIB_DESC(1, 0x23, "RxUnicastPkts"),
+       MIB_DESC(1, 0x22, "RxMulticastPkts"),
+       MIB_DESC(1, 0x21, "RxFCSErrorPkts"),
+       MIB_DESC(1, 0x1D, "RxUnderSizeGoodPkts"),
+       MIB_DESC(1, 0x1E, "RxUnderSizeErrorPkts"),
+       MIB_DESC(1, 0x1B, "RxOversizeGoodPkts"),
+       MIB_DESC(1, 0x1C, "RxOversizeErrorPkts"),
+       MIB_DESC(1, 0x20, "RxGoodPausePkts"),
+       MIB_DESC(1, 0x1A, "RxAlignErrorPkts"),
+       MIB_DESC(1, 0x12, "Rx64BytePkts"),
+       MIB_DESC(1, 0x13, "Rx127BytePkts"),
+       MIB_DESC(1, 0x14, "Rx255BytePkts"),
+       MIB_DESC(1, 0x15, "Rx511BytePkts"),
+       MIB_DESC(1, 0x16, "Rx1023BytePkts"),
+       /** Receive Size 1024-1522 (or more, if configured) Packet Count. */
+       MIB_DESC(1, 0x17, "RxMaxBytePkts"),
+       MIB_DESC(1, 0x18, "RxDroppedPkts"),
+       MIB_DESC(1, 0x19, "RxFilteredPkts"),
+       MIB_DESC(2, 0x24, "RxGoodBytes"),
+       MIB_DESC(2, 0x26, "RxBadBytes"),
+       MIB_DESC(1, 0x11, "TxAcmDroppedPkts"),
+       MIB_DESC(1, 0x0C, "TxGoodPkts"),
+       MIB_DESC(1, 0x06, "TxUnicastPkts"),
+       MIB_DESC(1, 0x07, "TxMulticastPkts"),
+       MIB_DESC(1, 0x00, "Tx64BytePkts"),
+       MIB_DESC(1, 0x01, "Tx127BytePkts"),
+       MIB_DESC(1, 0x02, "Tx255BytePkts"),
+       MIB_DESC(1, 0x03, "Tx511BytePkts"),
+       MIB_DESC(1, 0x04, "Tx1023BytePkts"),
+       /** Transmit Size 1024-1522 (or more, if configured) Packet Count. */
+       MIB_DESC(1, 0x05, "TxMaxBytePkts"),
+       MIB_DESC(1, 0x08, "TxSingleCollCount"),
+       MIB_DESC(1, 0x09, "TxMultCollCount"),
+       MIB_DESC(1, 0x0A, "TxLateCollCount"),
+       MIB_DESC(1, 0x0B, "TxExcessCollCount"),
+       MIB_DESC(1, 0x0D, "TxPauseCount"),
+       MIB_DESC(1, 0x10, "TxDroppedPkts"),
+       MIB_DESC(2, 0x0E, "TxGoodBytes"),
+};
+
+static u32 gswip_switch_r_timeout(struct gswip_priv *priv, u32 offset,
+                                 u32 cleared)
+{
+       u32 val;
+
+       return regmap_read_poll_timeout(priv->gswip, offset, val,
+                                       !(val & cleared), 20, 50000);
+}
+
+static void gswip_mii_mask_cfg(struct gswip_priv *priv, u32 mask, u32 set,
+                              int port)
+{
+       int reg_port;
+
+       /* MII_CFG register only exists for MII ports */
+       if (!(priv->hw_info->mii_ports & BIT(port)))
+               return;
+
+       reg_port = port + priv->hw_info->mii_port_reg_offset;
+
+       regmap_write_bits(priv->mii, GSWIP_MII_CFGp(reg_port), mask,
+                         set);
+}
+
+static void gswip_mii_mask_pcdu(struct gswip_priv *priv, u32 mask, u32 set,
+                               int port)
+{
+       int reg_port;
+
+       /* MII_PCDU register only exists for MII ports */
+       if (!(priv->hw_info->mii_ports & BIT(port)))
+               return;
+
+       reg_port = port + priv->hw_info->mii_port_reg_offset;
+
+       switch (reg_port) {
+       case 0:
+               regmap_write_bits(priv->mii, GSWIP_MII_PCDU0, mask, set);
+               break;
+       case 1:
+               regmap_write_bits(priv->mii, GSWIP_MII_PCDU1, mask, set);
+               break;
+       case 5:
+               regmap_write_bits(priv->mii, GSWIP_MII_PCDU5, mask, set);
+               break;
+       }
+}
+
+static int gswip_mdio_poll(struct gswip_priv *priv)
+{
+       u32 ctrl;
+
+       return regmap_read_poll_timeout(priv->mdio, GSWIP_MDIO_CTRL, ctrl,
+                                       !(ctrl & GSWIP_MDIO_CTRL_BUSY), 40, 4000);
+}
+
+static int gswip_mdio_wr(struct mii_bus *bus, int addr, int reg, u16 val)
+{
+       struct gswip_priv *priv = bus->priv;
+       int err;
+
+       err = gswip_mdio_poll(priv);
+       if (err) {
+               dev_err(&bus->dev, "waiting for MDIO bus busy timed out\n");
+               return err;
+       }
+
+       regmap_write(priv->mdio, GSWIP_MDIO_WRITE, val);
+       regmap_write(priv->mdio, GSWIP_MDIO_CTRL,
+                    GSWIP_MDIO_CTRL_BUSY | GSWIP_MDIO_CTRL_WR |
+                    ((addr & GSWIP_MDIO_CTRL_PHYAD_MASK) << GSWIP_MDIO_CTRL_PHYAD_SHIFT) |
+                    (reg & GSWIP_MDIO_CTRL_REGAD_MASK));
+
+       return 0;
+}
+
+static int gswip_mdio_rd(struct mii_bus *bus, int addr, int reg)
+{
+       struct gswip_priv *priv = bus->priv;
+       u32 val;
+       int err;
+
+       err = gswip_mdio_poll(priv);
+       if (err) {
+               dev_err(&bus->dev, "waiting for MDIO bus busy timed out\n");
+               return err;
+       }
+
+       regmap_write(priv->mdio, GSWIP_MDIO_CTRL,
+                    GSWIP_MDIO_CTRL_BUSY | GSWIP_MDIO_CTRL_RD |
+                    ((addr & GSWIP_MDIO_CTRL_PHYAD_MASK) << GSWIP_MDIO_CTRL_PHYAD_SHIFT) |
+                    (reg & GSWIP_MDIO_CTRL_REGAD_MASK));
+
+       err = gswip_mdio_poll(priv);
+       if (err) {
+               dev_err(&bus->dev, "waiting for MDIO bus busy timed out\n");
+               return err;
+       }
+
+       err = regmap_read(priv->mdio, GSWIP_MDIO_READ, &val);
+       if (err)
+               return err;
+
+       return val;
+}
+
+static int gswip_mdio(struct gswip_priv *priv)
+{
+       struct device_node *mdio_np, *switch_np = priv->dev->of_node;
+       struct device *dev = priv->dev;
+       struct mii_bus *bus;
+       int err = 0;
+
+       mdio_np = of_get_compatible_child(switch_np, "lantiq,xrx200-mdio");
+       if (!mdio_np)
+               mdio_np = of_get_child_by_name(switch_np, "mdio");
+
+       if (!of_device_is_available(mdio_np))
+               goto out_put_node;
+
+       bus = devm_mdiobus_alloc(dev);
+       if (!bus) {
+               err = -ENOMEM;
+               goto out_put_node;
+       }
+
+       bus->priv = priv;
+       bus->read = gswip_mdio_rd;
+       bus->write = gswip_mdio_wr;
+       bus->name = "lantiq,xrx200-mdio";
+       snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(priv->dev));
+       bus->parent = priv->dev;
+
+       err = devm_of_mdiobus_register(dev, bus, mdio_np);
+
+out_put_node:
+       of_node_put(mdio_np);
+
+       return err;
+}
+
+static int gswip_pce_table_entry_read(struct gswip_priv *priv,
+                                     struct gswip_pce_table_entry *tbl)
+{
+       int i;
+       int err;
+       u32 crtl;
+       u32 tmp;
+       u16 addr_mode = tbl->key_mode ? GSWIP_PCE_TBL_CTRL_OPMOD_KSRD :
+                                       GSWIP_PCE_TBL_CTRL_OPMOD_ADRD;
+
+       mutex_lock(&priv->pce_table_lock);
+
+       err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL,
+                                    GSWIP_PCE_TBL_CTRL_BAS);
+       if (err)
+               goto out_unlock;
+
+       regmap_write(priv->gswip, GSWIP_PCE_TBL_ADDR, tbl->index);
+       regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL,
+                         GSWIP_PCE_TBL_CTRL_ADDR_MASK |
+                         GSWIP_PCE_TBL_CTRL_OPMOD_MASK |
+                         GSWIP_PCE_TBL_CTRL_BAS,
+                         tbl->table | addr_mode | GSWIP_PCE_TBL_CTRL_BAS);
+
+       err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL,
+                                    GSWIP_PCE_TBL_CTRL_BAS);
+       if (err)
+               goto out_unlock;
+
+       for (i = 0; i < ARRAY_SIZE(tbl->key); i++) {
+               err = regmap_read(priv->gswip, GSWIP_PCE_TBL_KEY(i), &tmp);
+               if (err)
+                       goto out_unlock;
+               tbl->key[i] = tmp;
+       }
+       for (i = 0; i < ARRAY_SIZE(tbl->val); i++) {
+               err = regmap_read(priv->gswip, GSWIP_PCE_TBL_VAL(i), &tmp);
+               if (err)
+                       goto out_unlock;
+               tbl->val[i] = tmp;
+       }
+
+       err = regmap_read(priv->gswip, GSWIP_PCE_TBL_MASK, &tmp);
+       if (err)
+               goto out_unlock;
+
+       tbl->mask = tmp;
+       err = regmap_read(priv->gswip, GSWIP_PCE_TBL_CTRL, &crtl);
+       if (err)
+               goto out_unlock;
+
+       tbl->type = !!(crtl & GSWIP_PCE_TBL_CTRL_TYPE);
+       tbl->valid = !!(crtl & GSWIP_PCE_TBL_CTRL_VLD);
+       tbl->gmap = (crtl & GSWIP_PCE_TBL_CTRL_GMAP_MASK) >> 7;
+
+out_unlock:
+       mutex_unlock(&priv->pce_table_lock);
+
+       return err;
+}
+
+static int gswip_pce_table_entry_write(struct gswip_priv *priv,
+                                      struct gswip_pce_table_entry *tbl)
+{
+       int i;
+       int err;
+       u32 crtl;
+       u16 addr_mode = tbl->key_mode ? GSWIP_PCE_TBL_CTRL_OPMOD_KSWR :
+                                       GSWIP_PCE_TBL_CTRL_OPMOD_ADWR;
+
+       mutex_lock(&priv->pce_table_lock);
+
+       err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL,
+                                    GSWIP_PCE_TBL_CTRL_BAS);
+       if (err) {
+               mutex_unlock(&priv->pce_table_lock);
+               return err;
+       }
+
+       regmap_write(priv->gswip, GSWIP_PCE_TBL_ADDR, tbl->index);
+       regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL,
+                         GSWIP_PCE_TBL_CTRL_ADDR_MASK |
+                         GSWIP_PCE_TBL_CTRL_OPMOD_MASK,
+                         tbl->table | addr_mode);
+
+       for (i = 0; i < ARRAY_SIZE(tbl->key); i++)
+               regmap_write(priv->gswip, GSWIP_PCE_TBL_KEY(i), tbl->key[i]);
+
+       for (i = 0; i < ARRAY_SIZE(tbl->val); i++)
+               regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(i), tbl->val[i]);
+
+       regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL,
+                         GSWIP_PCE_TBL_CTRL_ADDR_MASK |
+                         GSWIP_PCE_TBL_CTRL_OPMOD_MASK,
+                         tbl->table | addr_mode);
+
+       regmap_write(priv->gswip, GSWIP_PCE_TBL_MASK, tbl->mask);
+
+       regmap_read(priv->gswip, GSWIP_PCE_TBL_CTRL, &crtl);
+       crtl &= ~(GSWIP_PCE_TBL_CTRL_TYPE | GSWIP_PCE_TBL_CTRL_VLD |
+                 GSWIP_PCE_TBL_CTRL_GMAP_MASK);
+       if (tbl->type)
+               crtl |= GSWIP_PCE_TBL_CTRL_TYPE;
+       if (tbl->valid)
+               crtl |= GSWIP_PCE_TBL_CTRL_VLD;
+       crtl |= (tbl->gmap << 7) & GSWIP_PCE_TBL_CTRL_GMAP_MASK;
+       crtl |= GSWIP_PCE_TBL_CTRL_BAS;
+       regmap_write(priv->gswip, GSWIP_PCE_TBL_CTRL, crtl);
+
+       err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL,
+                                    GSWIP_PCE_TBL_CTRL_BAS);
+
+       mutex_unlock(&priv->pce_table_lock);
+
+       return err;
+}
+
+/* Add the LAN port into a bridge with the CPU port by
+ * default. This prevents automatic forwarding of
+ * packages between the LAN ports when no explicit
+ * bridge is configured.
+ */
+static int gswip_add_single_port_br(struct gswip_priv *priv, int port, bool add)
+{
+       struct gswip_pce_table_entry vlan_active = {0,};
+       struct gswip_pce_table_entry vlan_mapping = {0,};
+       int err;
+
+       vlan_active.index = port + 1;
+       vlan_active.table = GSWIP_TABLE_ACTIVE_VLAN;
+       vlan_active.key[0] = GSWIP_VLAN_UNAWARE_PVID;
+       vlan_active.val[0] = port + 1 /* fid */;
+       vlan_active.valid = add;
+       err = gswip_pce_table_entry_write(priv, &vlan_active);
+       if (err) {
+               dev_err(priv->dev, "failed to write active VLAN: %d\n", err);
+               return err;
+       }
+
+       if (!add)
+               return 0;
+
+       vlan_mapping.index = port + 1;
+       vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING;
+       vlan_mapping.val[0] = GSWIP_VLAN_UNAWARE_PVID;
+       vlan_mapping.val[1] = BIT(port) | dsa_cpu_ports(priv->ds);
+       vlan_mapping.val[2] = 0;
+       err = gswip_pce_table_entry_write(priv, &vlan_mapping);
+       if (err) {
+               dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err);
+               return err;
+       }
+
+       return 0;
+}
+
+static int gswip_port_setup(struct dsa_switch *ds, int port)
+{
+       struct gswip_priv *priv = ds->priv;
+       int err;
+
+       if (!dsa_is_cpu_port(ds, port)) {
+               err = gswip_add_single_port_br(priv, port, true);
+               if (err)
+                       return err;
+       }
+
+       return 0;
+}
+
+static int gswip_port_enable(struct dsa_switch *ds, int port,
+                            struct phy_device *phydev)
+{
+       struct gswip_priv *priv = ds->priv;
+
+       if (!dsa_is_cpu_port(ds, port)) {
+               u32 mdio_phy = 0;
+
+               if (phydev)
+                       mdio_phy = phydev->mdio.addr & GSWIP_MDIO_PHY_ADDR_MASK;
+
+               regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port),
+                                 GSWIP_MDIO_PHY_ADDR_MASK,
+                                 mdio_phy);
+       }
+
+       /* RMON Counter Enable for port */
+       regmap_write(priv->gswip, GSWIP_BM_PCFGp(port), GSWIP_BM_PCFG_CNTEN);
+
+       /* enable port fetch/store dma & VLAN Modification */
+       regmap_set_bits(priv->gswip, GSWIP_FDMA_PCTRLp(port),
+                       GSWIP_FDMA_PCTRL_EN | GSWIP_FDMA_PCTRL_VLANMOD_BOTH);
+       regmap_set_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port),
+                       GSWIP_SDMA_PCTRL_EN);
+
+       return 0;
+}
+
+static void gswip_port_disable(struct dsa_switch *ds, int port)
+{
+       struct gswip_priv *priv = ds->priv;
+
+       regmap_clear_bits(priv->gswip, GSWIP_FDMA_PCTRLp(port),
+                         GSWIP_FDMA_PCTRL_EN);
+       regmap_clear_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port),
+                         GSWIP_SDMA_PCTRL_EN);
+}
+
+static int gswip_pce_load_microcode(struct gswip_priv *priv)
+{
+       int i;
+       int err;
+
+       regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL,
+                         GSWIP_PCE_TBL_CTRL_ADDR_MASK |
+                         GSWIP_PCE_TBL_CTRL_OPMOD_MASK |
+                         GSWIP_PCE_TBL_CTRL_OPMOD_ADWR,
+                         GSWIP_PCE_TBL_CTRL_OPMOD_ADWR);
+       regmap_write(priv->gswip, GSWIP_PCE_TBL_MASK, 0);
+
+       for (i = 0; i < priv->hw_info->pce_microcode_size; i++) {
+               regmap_write(priv->gswip, GSWIP_PCE_TBL_ADDR, i);
+               regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(0),
+                            (*priv->hw_info->pce_microcode)[i].val_0);
+               regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(1),
+                            (*priv->hw_info->pce_microcode)[i].val_1);
+               regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(2),
+                            (*priv->hw_info->pce_microcode)[i].val_2);
+               regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(3),
+                            (*priv->hw_info->pce_microcode)[i].val_3);
+
+               /* start the table access: */
+               regmap_set_bits(priv->gswip, GSWIP_PCE_TBL_CTRL,
+                               GSWIP_PCE_TBL_CTRL_BAS);
+               err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL,
+                                            GSWIP_PCE_TBL_CTRL_BAS);
+               if (err)
+                       return err;
+       }
+
+       /* tell the switch that the microcode is loaded */
+       regmap_set_bits(priv->gswip, GSWIP_PCE_GCTRL_0,
+                       GSWIP_PCE_GCTRL_0_MC_VALID);
+
+       return 0;
+}
+
+static void gswip_port_commit_pvid(struct gswip_priv *priv, int port)
+{
+       struct dsa_port *dp = dsa_to_port(priv->ds, port);
+       struct net_device *br = dsa_port_bridge_dev_get(dp);
+       u32 vinr;
+       int idx;
+
+       if (!dsa_port_is_user(dp))
+               return;
+
+       if (br) {
+               u16 pvid = GSWIP_VLAN_UNAWARE_PVID;
+
+               if (br_vlan_enabled(br))
+                       br_vlan_get_pvid(br, &pvid);
+
+               /* VLAN-aware bridge ports with no PVID will use Active VLAN
+                * index 0. The expectation is that this drops all untagged and
+                * VID-0 tagged ingress traffic.
+                */
+               idx = 0;
+               for (int i = priv->hw_info->max_ports;
+                    i < ARRAY_SIZE(priv->vlans); i++) {
+                       if (priv->vlans[i].bridge == br &&
+                           priv->vlans[i].vid == pvid) {
+                               idx = i;
+                               break;
+                       }
+               }
+       } else {
+               /* The Active VLAN table index as configured by
+                * gswip_add_single_port_br()
+                */
+               idx = port + 1;
+       }
+
+       vinr = idx ? GSWIP_PCE_VCTRL_VINR_ALL : GSWIP_PCE_VCTRL_VINR_TAGGED;
+       regmap_write_bits(priv->gswip, GSWIP_PCE_VCTRL(port),
+                         GSWIP_PCE_VCTRL_VINR,
+                         FIELD_PREP(GSWIP_PCE_VCTRL_VINR, vinr));
+
+       /* Note that in GSWIP 2.2 VLAN mode the VID needs to be programmed
+        * directly instead of referencing the index in the Active VLAN Tablet.
+        * However, without the VLANMD bit (9) in PCE_GCTRL_1 (0x457) even
+        * GSWIP 2.2 and newer hardware maintain the GSWIP 2.1 behavior.
+        */
+       regmap_write(priv->gswip, GSWIP_PCE_DEFPVID(port), idx);
+}
+
+static int gswip_port_vlan_filtering(struct dsa_switch *ds, int port,
+                                    bool vlan_filtering,
+                                    struct netlink_ext_ack *extack)
+{
+       struct gswip_priv *priv = ds->priv;
+
+       if (vlan_filtering) {
+               /* Use tag based VLAN */
+               regmap_write_bits(priv->gswip, GSWIP_PCE_VCTRL(port),
+                                 GSWIP_PCE_VCTRL_VSR |
+                                 GSWIP_PCE_VCTRL_UVR |
+                                 GSWIP_PCE_VCTRL_VIMR |
+                                 GSWIP_PCE_VCTRL_VEMR |
+                                 GSWIP_PCE_VCTRL_VID0,
+                                 GSWIP_PCE_VCTRL_UVR |
+                                 GSWIP_PCE_VCTRL_VIMR |
+                                 GSWIP_PCE_VCTRL_VEMR |
+                                 GSWIP_PCE_VCTRL_VID0);
+               regmap_clear_bits(priv->gswip, GSWIP_PCE_PCTRL_0p(port),
+                                 GSWIP_PCE_PCTRL_0_TVM);
+       } else {
+               /* Use port based VLAN */
+               regmap_write_bits(priv->gswip, GSWIP_PCE_VCTRL(port),
+                                 GSWIP_PCE_VCTRL_UVR |
+                                 GSWIP_PCE_VCTRL_VIMR |
+                                 GSWIP_PCE_VCTRL_VEMR |
+                                 GSWIP_PCE_VCTRL_VID0 |
+                                 GSWIP_PCE_VCTRL_VSR,
+                                 GSWIP_PCE_VCTRL_VSR);
+               regmap_set_bits(priv->gswip, GSWIP_PCE_PCTRL_0p(port),
+                               GSWIP_PCE_PCTRL_0_TVM);
+       }
+
+       gswip_port_commit_pvid(priv, port);
+
+       return 0;
+}
+
+static int gswip_setup(struct dsa_switch *ds)
+{
+       unsigned int cpu_ports = dsa_cpu_ports(ds);
+       struct gswip_priv *priv = ds->priv;
+       struct dsa_port *cpu_dp;
+       int err, i;
+
+       regmap_write(priv->gswip, GSWIP_SWRES, GSWIP_SWRES_R0);
+       usleep_range(5000, 10000);
+       regmap_write(priv->gswip, GSWIP_SWRES, 0);
+
+       /* disable port fetch/store dma on all ports */
+       for (i = 0; i < priv->hw_info->max_ports; i++) {
+               gswip_port_disable(ds, i);
+               gswip_port_vlan_filtering(ds, i, false, NULL);
+       }
+
+       /* enable Switch */
+       regmap_set_bits(priv->mdio, GSWIP_MDIO_GLOB, GSWIP_MDIO_GLOB_ENABLE);
+
+       err = gswip_pce_load_microcode(priv);
+       if (err) {
+               dev_err(priv->dev, "writing PCE microcode failed, %i\n", err);
+               return err;
+       }
+
+       /* Default unknown Broadcast/Multicast/Unicast port maps */
+       regmap_write(priv->gswip, GSWIP_PCE_PMAP1, cpu_ports);
+       regmap_write(priv->gswip, GSWIP_PCE_PMAP2, cpu_ports);
+       regmap_write(priv->gswip, GSWIP_PCE_PMAP3, cpu_ports);
+
+       /* Deactivate MDIO PHY auto polling. Some PHYs as the AR8030 have an
+        * interoperability problem with this auto polling mechanism because
+        * their status registers think that the link is in a different state
+        * than it actually is. For the AR8030 it has the BMSR_ESTATEN bit set
+        * as well as ESTATUS_1000_TFULL and ESTATUS_1000_XFULL. This makes the
+        * auto polling state machine consider the link being negotiated with
+        * 1Gbit/s. Since the PHY itself is a Fast Ethernet RMII PHY this leads
+        * to the switch port being completely dead (RX and TX are both not
+        * working).
+        * Also with various other PHY / port combinations (PHY11G GPHY, PHY22F
+        * GPHY, external RGMII PEF7071/7072) any traffic would stop. Sometimes
+        * it would work fine for a few minutes to hours and then stop, on
+        * other device it would no traffic could be sent or received at all.
+        * Testing shows that when PHY auto polling is disabled these problems
+        * go away.
+        */
+       regmap_write(priv->mdio, GSWIP_MDIO_MDC_CFG0, 0x0);
+
+       /* Configure the MDIO Clock 2.5 MHz */
+       regmap_write_bits(priv->mdio, GSWIP_MDIO_MDC_CFG1, 0xff, 0x09);
+
+       /* bring up the mdio bus */
+       err = gswip_mdio(priv);
+       if (err) {
+               dev_err(priv->dev, "mdio bus setup failed\n");
+               return err;
+       }
+
+       /* Disable the xMII interface and clear it's isolation bit */
+       for (i = 0; i < priv->hw_info->max_ports; i++)
+               gswip_mii_mask_cfg(priv,
+                                  GSWIP_MII_CFG_EN | GSWIP_MII_CFG_ISOLATE,
+                                  0, i);
+
+       dsa_switch_for_each_cpu_port(cpu_dp, ds) {
+               /* enable special tag insertion on cpu port */
+               regmap_set_bits(priv->gswip, GSWIP_FDMA_PCTRLp(cpu_dp->index),
+                               GSWIP_FDMA_PCTRL_STEN);
+
+               /* accept special tag in ingress direction */
+               regmap_set_bits(priv->gswip,
+                               GSWIP_PCE_PCTRL_0p(cpu_dp->index),
+                               GSWIP_PCE_PCTRL_0_INGRESS);
+       }
+
+       regmap_set_bits(priv->gswip, GSWIP_BM_QUEUE_GCTRL,
+                       GSWIP_BM_QUEUE_GCTRL_GL_MOD);
+
+       /* VLAN aware Switching */
+       regmap_set_bits(priv->gswip, GSWIP_PCE_GCTRL_0,
+                       GSWIP_PCE_GCTRL_0_VLAN);
+
+       /* Flush MAC Table */
+       regmap_set_bits(priv->gswip, GSWIP_PCE_GCTRL_0,
+                       GSWIP_PCE_GCTRL_0_MTFL);
+
+       err = gswip_switch_r_timeout(priv, GSWIP_PCE_GCTRL_0,
+                                    GSWIP_PCE_GCTRL_0_MTFL);
+       if (err) {
+               dev_err(priv->dev, "MAC flushing didn't finish\n");
+               return err;
+       }
+
+       ds->mtu_enforcement_ingress = true;
+
+       return 0;
+}
+
+static enum dsa_tag_protocol gswip_get_tag_protocol(struct dsa_switch *ds,
+                                                   int port,
+                                                   enum dsa_tag_protocol mp)
+{
+       struct gswip_priv *priv = ds->priv;
+
+       return priv->hw_info->tag_protocol;
+}
+
+static int gswip_vlan_active_create(struct gswip_priv *priv,
+                                   struct net_device *bridge,
+                                   int fid, u16 vid)
+{
+       struct gswip_pce_table_entry vlan_active = {0,};
+       unsigned int max_ports = priv->hw_info->max_ports;
+       int idx = -1;
+       int err;
+       int i;
+
+       /* Look for a free slot */
+       for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) {
+               if (!priv->vlans[i].bridge) {
+                       idx = i;
+                       break;
+               }
+       }
+
+       if (idx == -1)
+               return -ENOSPC;
+
+       if (fid == -1)
+               fid = idx;
+
+       vlan_active.index = idx;
+       vlan_active.table = GSWIP_TABLE_ACTIVE_VLAN;
+       vlan_active.key[0] = vid;
+       vlan_active.val[0] = fid;
+       vlan_active.valid = true;
+
+       err = gswip_pce_table_entry_write(priv, &vlan_active);
+       if (err) {
+               dev_err(priv->dev, "failed to write active VLAN: %d\n", err);
+               return err;
+       }
+
+       priv->vlans[idx].bridge = bridge;
+       priv->vlans[idx].vid = vid;
+       priv->vlans[idx].fid = fid;
+
+       return idx;
+}
+
+static int gswip_vlan_active_remove(struct gswip_priv *priv, int idx)
+{
+       struct gswip_pce_table_entry vlan_active = {0,};
+       int err;
+
+       vlan_active.index = idx;
+       vlan_active.table = GSWIP_TABLE_ACTIVE_VLAN;
+       vlan_active.valid = false;
+       err = gswip_pce_table_entry_write(priv, &vlan_active);
+       if (err)
+               dev_err(priv->dev, "failed to delete active VLAN: %d\n", err);
+       priv->vlans[idx].bridge = NULL;
+
+       return err;
+}
+
+static int gswip_vlan_add(struct gswip_priv *priv, struct net_device *bridge,
+                         int port, u16 vid, bool untagged, bool pvid,
+                         bool vlan_aware)
+{
+       struct gswip_pce_table_entry vlan_mapping = {0,};
+       unsigned int max_ports = priv->hw_info->max_ports;
+       unsigned int cpu_ports = dsa_cpu_ports(priv->ds);
+       bool active_vlan_created = false;
+       int fid = -1, idx = -1;
+       int i, err;
+
+       /* Check if there is already a page for this bridge */
+       for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) {
+               if (priv->vlans[i].bridge == bridge) {
+                       if (vlan_aware) {
+                               if (fid != -1 && fid != priv->vlans[i].fid)
+                                       dev_err(priv->dev, "one bridge with multiple flow ids\n");
+                               fid = priv->vlans[i].fid;
+                       }
+                       if (priv->vlans[i].vid == vid) {
+                               idx = i;
+                               break;
+                       }
+               }
+       }
+
+       /* If this bridge is not programmed yet, add a Active VLAN table
+        * entry in a free slot and prepare the VLAN mapping table entry.
+        */
+       if (idx == -1) {
+               idx = gswip_vlan_active_create(priv, bridge, fid, vid);
+               if (idx < 0)
+                       return idx;
+               active_vlan_created = true;
+
+               vlan_mapping.index = idx;
+               vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING;
+       } else {
+               /* Read the existing VLAN mapping entry from the switch */
+               vlan_mapping.index = idx;
+               vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING;
+               err = gswip_pce_table_entry_read(priv, &vlan_mapping);
+               if (err) {
+                       dev_err(priv->dev, "failed to read VLAN mapping: %d\n",
+                               err);
+                       return err;
+               }
+       }
+
+       /* VLAN ID byte, maps to the VLAN ID of vlan active table */
+       vlan_mapping.val[0] = vid;
+       /* Update the VLAN mapping entry and write it to the switch */
+       vlan_mapping.val[1] |= cpu_ports;
+       vlan_mapping.val[1] |= BIT(port);
+       if (vlan_aware)
+               vlan_mapping.val[2] |= cpu_ports;
+       if (untagged)
+               vlan_mapping.val[2] &= ~BIT(port);
+       else
+               vlan_mapping.val[2] |= BIT(port);
+       err = gswip_pce_table_entry_write(priv, &vlan_mapping);
+       if (err) {
+               dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err);
+               /* In case an Active VLAN was creaetd delete it again */
+               if (active_vlan_created)
+                       gswip_vlan_active_remove(priv, idx);
+               return err;
+       }
+
+       gswip_port_commit_pvid(priv, port);
+
+       return 0;
+}
+
+static int gswip_vlan_remove(struct gswip_priv *priv,
+                            struct net_device *bridge, int port,
+                            u16 vid)
+{
+       struct gswip_pce_table_entry vlan_mapping = {0,};
+       unsigned int max_ports = priv->hw_info->max_ports;
+       int idx = -1;
+       int i;
+       int err;
+
+       /* Check if there is already a page for this bridge */
+       for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) {
+               if (priv->vlans[i].bridge == bridge &&
+                   priv->vlans[i].vid == vid) {
+                       idx = i;
+                       break;
+               }
+       }
+
+       if (idx == -1) {
+               dev_err(priv->dev, "Port %d cannot find VID %u of bridge %s\n",
+                       port, vid, bridge ? bridge->name : "(null)");
+               return -ENOENT;
+       }
+
+       vlan_mapping.index = idx;
+       vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING;
+       err = gswip_pce_table_entry_read(priv, &vlan_mapping);
+       if (err) {
+               dev_err(priv->dev, "failed to read VLAN mapping: %d\n", err);
+               return err;
+       }
+
+       vlan_mapping.val[1] &= ~BIT(port);
+       vlan_mapping.val[2] &= ~BIT(port);
+       err = gswip_pce_table_entry_write(priv, &vlan_mapping);
+       if (err) {
+               dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err);
+               return err;
+       }
+
+       /* In case all ports are removed from the bridge, remove the VLAN */
+       if (!(vlan_mapping.val[1] & ~dsa_cpu_ports(priv->ds))) {
+               err = gswip_vlan_active_remove(priv, idx);
+               if (err) {
+                       dev_err(priv->dev, "failed to write active VLAN: %d\n",
+                               err);
+                       return err;
+               }
+       }
+
+       gswip_port_commit_pvid(priv, port);
+
+       return 0;
+}
+
+static int gswip_port_bridge_join(struct dsa_switch *ds, int port,
+                                 struct dsa_bridge bridge,
+                                 bool *tx_fwd_offload,
+                                 struct netlink_ext_ack *extack)
+{
+       struct net_device *br = bridge.dev;
+       struct gswip_priv *priv = ds->priv;
+       int err;
+
+       /* Set up the VLAN for VLAN-unaware bridging for this port, and remove
+        * it from the "single-port bridge" through which it was operating as
+        * standalone.
+        */
+       err = gswip_vlan_add(priv, br, port, GSWIP_VLAN_UNAWARE_PVID,
+                            true, true, false);
+       if (err)
+               return err;
+
+       return gswip_add_single_port_br(priv, port, false);
+}
+
+static void gswip_port_bridge_leave(struct dsa_switch *ds, int port,
+                                   struct dsa_bridge bridge)
+{
+       struct net_device *br = bridge.dev;
+       struct gswip_priv *priv = ds->priv;
+
+       /* Add the port back to the "single-port bridge", and remove it from
+        * the VLAN-unaware PVID created for this bridge.
+        */
+       gswip_add_single_port_br(priv, port, true);
+       gswip_vlan_remove(priv, br, port, GSWIP_VLAN_UNAWARE_PVID);
+}
+
+static int gswip_port_vlan_prepare(struct dsa_switch *ds, int port,
+                                  const struct switchdev_obj_port_vlan *vlan,
+                                  struct netlink_ext_ack *extack)
+{
+       struct net_device *bridge = dsa_port_bridge_dev_get(dsa_to_port(ds, port));
+       struct gswip_priv *priv = ds->priv;
+       unsigned int max_ports = priv->hw_info->max_ports;
+       int pos = max_ports;
+       int i, idx = -1;
+
+       /* We only support VLAN filtering on bridges */
+       if (!dsa_is_cpu_port(ds, port) && !bridge)
+               return -EOPNOTSUPP;
+
+       /* Check if there is already a page for this VLAN */
+       for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) {
+               if (priv->vlans[i].bridge == bridge &&
+                   priv->vlans[i].vid == vlan->vid) {
+                       idx = i;
+                       break;
+               }
+       }
+
+       /* If this VLAN is not programmed yet, we have to reserve
+        * one entry in the VLAN table. Make sure we start at the
+        * next position round.
+        */
+       if (idx == -1) {
+               /* Look for a free slot */
+               for (; pos < ARRAY_SIZE(priv->vlans); pos++) {
+                       if (!priv->vlans[pos].bridge) {
+                               idx = pos;
+                               pos++;
+                               break;
+                       }
+               }
+
+               if (idx == -1) {
+                       NL_SET_ERR_MSG_MOD(extack, "No slot in VLAN table");
+                       return -ENOSPC;
+               }
+       }
+
+       return 0;
+}
+
+static int gswip_port_vlan_add(struct dsa_switch *ds, int port,
+                              const struct switchdev_obj_port_vlan *vlan,
+                              struct netlink_ext_ack *extack)
+{
+       struct net_device *bridge = dsa_port_bridge_dev_get(dsa_to_port(ds, port));
+       struct gswip_priv *priv = ds->priv;
+       bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+       bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
+       int err;
+
+       if (vlan->vid == GSWIP_VLAN_UNAWARE_PVID)
+               return 0;
+
+       err = gswip_port_vlan_prepare(ds, port, vlan, extack);
+       if (err)
+               return err;
+
+       /* We have to receive all packets on the CPU port and should not
+        * do any VLAN filtering here. This is also called with bridge
+        * NULL and then we do not know for which bridge to configure
+        * this.
+        */
+       if (dsa_is_cpu_port(ds, port))
+               return 0;
+
+       return gswip_vlan_add(priv, bridge, port, vlan->vid, untagged, pvid,
+                             true);
+}
+
+static int gswip_port_vlan_del(struct dsa_switch *ds, int port,
+                              const struct switchdev_obj_port_vlan *vlan)
+{
+       struct net_device *bridge = dsa_port_bridge_dev_get(dsa_to_port(ds, port));
+       struct gswip_priv *priv = ds->priv;
+
+       if (vlan->vid == GSWIP_VLAN_UNAWARE_PVID)
+               return 0;
+
+       /* We have to receive all packets on the CPU port and should not
+        * do any VLAN filtering here. This is also called with bridge
+        * NULL and then we do not know for which bridge to configure
+        * this.
+        */
+       if (dsa_is_cpu_port(ds, port))
+               return 0;
+
+       return gswip_vlan_remove(priv, bridge, port, vlan->vid);
+}
+
+static void gswip_port_fast_age(struct dsa_switch *ds, int port)
+{
+       struct gswip_priv *priv = ds->priv;
+       struct gswip_pce_table_entry mac_bridge = {0,};
+       int i;
+       int err;
+
+       for (i = 0; i < 2048; i++) {
+               mac_bridge.table = GSWIP_TABLE_MAC_BRIDGE;
+               mac_bridge.index = i;
+
+               err = gswip_pce_table_entry_read(priv, &mac_bridge);
+               if (err) {
+                       dev_err(priv->dev, "failed to read mac bridge: %d\n",
+                               err);
+                       return;
+               }
+
+               if (!mac_bridge.valid)
+                       continue;
+
+               if (mac_bridge.val[1] & GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC)
+                       continue;
+
+               if (port != FIELD_GET(GSWIP_TABLE_MAC_BRIDGE_VAL0_PORT,
+                                     mac_bridge.val[0]))
+                       continue;
+
+               mac_bridge.valid = false;
+               err = gswip_pce_table_entry_write(priv, &mac_bridge);
+               if (err) {
+                       dev_err(priv->dev, "failed to write mac bridge: %d\n",
+                               err);
+                       return;
+               }
+       }
+}
+
+static void gswip_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
+{
+       struct gswip_priv *priv = ds->priv;
+       u32 stp_state;
+
+       switch (state) {
+       case BR_STATE_DISABLED:
+               regmap_clear_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port),
+                                 GSWIP_SDMA_PCTRL_EN);
+               return;
+       case BR_STATE_BLOCKING:
+       case BR_STATE_LISTENING:
+               stp_state = GSWIP_PCE_PCTRL_0_PSTATE_LISTEN;
+               break;
+       case BR_STATE_LEARNING:
+               stp_state = GSWIP_PCE_PCTRL_0_PSTATE_LEARNING;
+               break;
+       case BR_STATE_FORWARDING:
+               stp_state = GSWIP_PCE_PCTRL_0_PSTATE_FORWARDING;
+               break;
+       default:
+               dev_err(priv->dev, "invalid STP state: %d\n", state);
+               return;
+       }
+
+       regmap_set_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port),
+                       GSWIP_SDMA_PCTRL_EN);
+       regmap_write_bits(priv->gswip, GSWIP_PCE_PCTRL_0p(port),
+                         GSWIP_PCE_PCTRL_0_PSTATE_MASK,
+                         stp_state);
+}
+
+static int gswip_port_fdb(struct dsa_switch *ds, int port,
+                         struct net_device *bridge, const unsigned char *addr,
+                         u16 vid, bool add)
+{
+       struct gswip_priv *priv = ds->priv;
+       struct gswip_pce_table_entry mac_bridge = {0,};
+       unsigned int max_ports = priv->hw_info->max_ports;
+       int fid = -1;
+       int i;
+       int err;
+
+       for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) {
+               if (priv->vlans[i].bridge == bridge) {
+                       fid = priv->vlans[i].fid;
+                       break;
+               }
+       }
+
+       if (fid == -1) {
+               dev_err(priv->dev, "no FID found for bridge %s\n",
+                       bridge->name);
+               return -EINVAL;
+       }
+
+       mac_bridge.table = GSWIP_TABLE_MAC_BRIDGE;
+       mac_bridge.key_mode = true;
+       mac_bridge.key[0] = addr[5] | (addr[4] << 8);
+       mac_bridge.key[1] = addr[3] | (addr[2] << 8);
+       mac_bridge.key[2] = addr[1] | (addr[0] << 8);
+       mac_bridge.key[3] = FIELD_PREP(GSWIP_TABLE_MAC_BRIDGE_KEY3_FID, fid);
+       mac_bridge.val[0] = add ? BIT(port) : 0; /* port map */
+       mac_bridge.val[1] = GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC;
+       mac_bridge.valid = add;
+
+       err = gswip_pce_table_entry_write(priv, &mac_bridge);
+       if (err)
+               dev_err(priv->dev, "failed to write mac bridge: %d\n", err);
+
+       return err;
+}
+
+static int gswip_port_fdb_add(struct dsa_switch *ds, int port,
+                             const unsigned char *addr, u16 vid,
+                             struct dsa_db db)
+{
+       if (db.type != DSA_DB_BRIDGE)
+               return -EOPNOTSUPP;
+
+       return gswip_port_fdb(ds, port, db.bridge.dev, addr, vid, true);
+}
+
+static int gswip_port_fdb_del(struct dsa_switch *ds, int port,
+                             const unsigned char *addr, u16 vid,
+                             struct dsa_db db)
+{
+       if (db.type != DSA_DB_BRIDGE)
+               return -EOPNOTSUPP;
+
+       return gswip_port_fdb(ds, port, db.bridge.dev, addr, vid, false);
+}
+
+static int gswip_port_fdb_dump(struct dsa_switch *ds, int port,
+                              dsa_fdb_dump_cb_t *cb, void *data)
+{
+       struct gswip_priv *priv = ds->priv;
+       struct gswip_pce_table_entry mac_bridge = {0,};
+       unsigned char addr[ETH_ALEN];
+       int i;
+       int err;
+
+       for (i = 0; i < 2048; i++) {
+               mac_bridge.table = GSWIP_TABLE_MAC_BRIDGE;
+               mac_bridge.index = i;
+
+               err = gswip_pce_table_entry_read(priv, &mac_bridge);
+               if (err) {
+                       dev_err(priv->dev,
+                               "failed to read mac bridge entry %d: %d\n",
+                               i, err);
+                       return err;
+               }
+
+               if (!mac_bridge.valid)
+                       continue;
+
+               addr[5] = mac_bridge.key[0] & 0xff;
+               addr[4] = (mac_bridge.key[0] >> 8) & 0xff;
+               addr[3] = mac_bridge.key[1] & 0xff;
+               addr[2] = (mac_bridge.key[1] >> 8) & 0xff;
+               addr[1] = mac_bridge.key[2] & 0xff;
+               addr[0] = (mac_bridge.key[2] >> 8) & 0xff;
+               if (mac_bridge.val[1] & GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC) {
+                       if (mac_bridge.val[0] & BIT(port)) {
+                               err = cb(addr, 0, true, data);
+                               if (err)
+                                       return err;
+                       }
+               } else {
+                       if (port == FIELD_GET(GSWIP_TABLE_MAC_BRIDGE_VAL0_PORT,
+                                             mac_bridge.val[0])) {
+                               err = cb(addr, 0, false, data);
+                               if (err)
+                                       return err;
+                       }
+               }
+       }
+       return 0;
+}
+
+static int gswip_port_max_mtu(struct dsa_switch *ds, int port)
+{
+       /* Includes 8 bytes for special header. */
+       return GSWIP_MAX_PACKET_LENGTH - VLAN_ETH_HLEN - ETH_FCS_LEN;
+}
+
+static int gswip_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
+{
+       struct gswip_priv *priv = ds->priv;
+
+       /* CPU port always has maximum mtu of user ports, so use it to set
+        * switch frame size, including 8 byte special header.
+        */
+       if (dsa_is_cpu_port(ds, port)) {
+               new_mtu += 8;
+               regmap_write(priv->gswip, GSWIP_MAC_FLEN,
+                            VLAN_ETH_HLEN + new_mtu + ETH_FCS_LEN);
+       }
+
+       /* Enable MLEN for ports with non-standard MTUs, including the special
+        * header on the CPU port added above.
+        */
+       if (new_mtu != ETH_DATA_LEN)
+               regmap_set_bits(priv->gswip, GSWIP_MAC_CTRL_2p(port),
+                               GSWIP_MAC_CTRL_2_MLEN);
+       else
+               regmap_clear_bits(priv->gswip, GSWIP_MAC_CTRL_2p(port),
+                                 GSWIP_MAC_CTRL_2_MLEN);
+
+       return 0;
+}
+
+static void gswip_phylink_get_caps(struct dsa_switch *ds, int port,
+                                  struct phylink_config *config)
+{
+       struct gswip_priv *priv = ds->priv;
+
+       priv->hw_info->phylink_get_caps(ds, port, config);
+}
+
+static void gswip_port_set_link(struct gswip_priv *priv, int port, bool link)
+{
+       u32 mdio_phy;
+
+       if (link)
+               mdio_phy = GSWIP_MDIO_PHY_LINK_UP;
+       else
+               mdio_phy = GSWIP_MDIO_PHY_LINK_DOWN;
+
+       regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port),
+                         GSWIP_MDIO_PHY_LINK_MASK, mdio_phy);
+}
+
+static void gswip_port_set_speed(struct gswip_priv *priv, int port, int speed,
+                                phy_interface_t interface)
+{
+       u32 mdio_phy = 0, mii_cfg = 0, mac_ctrl_0 = 0;
+
+       switch (speed) {
+       case SPEED_10:
+               mdio_phy = GSWIP_MDIO_PHY_SPEED_M10;
+
+               if (interface == PHY_INTERFACE_MODE_RMII)
+                       mii_cfg = GSWIP_MII_CFG_RATE_M50;
+               else
+                       mii_cfg = GSWIP_MII_CFG_RATE_M2P5;
+
+               mac_ctrl_0 = GSWIP_MAC_CTRL_0_GMII_MII;
+               break;
+
+       case SPEED_100:
+               mdio_phy = GSWIP_MDIO_PHY_SPEED_M100;
+
+               if (interface == PHY_INTERFACE_MODE_RMII)
+                       mii_cfg = GSWIP_MII_CFG_RATE_M50;
+               else
+                       mii_cfg = GSWIP_MII_CFG_RATE_M25;
+
+               mac_ctrl_0 = GSWIP_MAC_CTRL_0_GMII_MII;
+               break;
+
+       case SPEED_1000:
+               mdio_phy = GSWIP_MDIO_PHY_SPEED_G1;
+
+               mii_cfg = GSWIP_MII_CFG_RATE_M125;
+
+               mac_ctrl_0 = GSWIP_MAC_CTRL_0_GMII_RGMII;
+               break;
+       }
+
+       regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port),
+                         GSWIP_MDIO_PHY_SPEED_MASK, mdio_phy);
+       gswip_mii_mask_cfg(priv, GSWIP_MII_CFG_RATE_MASK, mii_cfg, port);
+       regmap_write_bits(priv->gswip, GSWIP_MAC_CTRL_0p(port),
+                         GSWIP_MAC_CTRL_0_GMII_MASK, mac_ctrl_0);
+}
+
+static void gswip_port_set_duplex(struct gswip_priv *priv, int port, int duplex)
+{
+       u32 mac_ctrl_0, mdio_phy;
+
+       if (duplex == DUPLEX_FULL) {
+               mac_ctrl_0 = GSWIP_MAC_CTRL_0_FDUP_EN;
+               mdio_phy = GSWIP_MDIO_PHY_FDUP_EN;
+       } else {
+               mac_ctrl_0 = GSWIP_MAC_CTRL_0_FDUP_DIS;
+               mdio_phy = GSWIP_MDIO_PHY_FDUP_DIS;
+       }
+
+       regmap_write_bits(priv->gswip, GSWIP_MAC_CTRL_0p(port),
+                         GSWIP_MAC_CTRL_0_FDUP_MASK, mac_ctrl_0);
+       regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port),
+                         GSWIP_MDIO_PHY_FDUP_MASK, mdio_phy);
+}
+
+static void gswip_port_set_pause(struct gswip_priv *priv, int port,
+                                bool tx_pause, bool rx_pause)
+{
+       u32 mac_ctrl_0, mdio_phy;
+
+       if (tx_pause && rx_pause) {
+               mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_RXTX;
+               mdio_phy = GSWIP_MDIO_PHY_FCONTX_EN |
+                          GSWIP_MDIO_PHY_FCONRX_EN;
+       } else if (tx_pause) {
+               mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_TX;
+               mdio_phy = GSWIP_MDIO_PHY_FCONTX_EN |
+                          GSWIP_MDIO_PHY_FCONRX_DIS;
+       } else if (rx_pause) {
+               mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_RX;
+               mdio_phy = GSWIP_MDIO_PHY_FCONTX_DIS |
+                          GSWIP_MDIO_PHY_FCONRX_EN;
+       } else {
+               mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_NONE;
+               mdio_phy = GSWIP_MDIO_PHY_FCONTX_DIS |
+                          GSWIP_MDIO_PHY_FCONRX_DIS;
+       }
+
+       regmap_write_bits(priv->gswip, GSWIP_MAC_CTRL_0p(port),
+                         GSWIP_MAC_CTRL_0_FCON_MASK, mac_ctrl_0);
+       regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port),
+                         GSWIP_MDIO_PHY_FCONTX_MASK | GSWIP_MDIO_PHY_FCONRX_MASK,
+                         mdio_phy);
+}
+
+static void gswip_phylink_mac_config(struct phylink_config *config,
+                                    unsigned int mode,
+                                    const struct phylink_link_state *state)
+{
+       struct dsa_port *dp = dsa_phylink_to_port(config);
+       struct gswip_priv *priv = dp->ds->priv;
+       int port = dp->index;
+       u32 miicfg = 0;
+
+       miicfg |= GSWIP_MII_CFG_LDCLKDIS;
+
+       switch (state->interface) {
+       case PHY_INTERFACE_MODE_SGMII:
+       case PHY_INTERFACE_MODE_1000BASEX:
+       case PHY_INTERFACE_MODE_2500BASEX:
+               return;
+       case PHY_INTERFACE_MODE_MII:
+       case PHY_INTERFACE_MODE_INTERNAL:
+               miicfg |= GSWIP_MII_CFG_MODE_MIIM;
+               break;
+       case PHY_INTERFACE_MODE_REVMII:
+               miicfg |= GSWIP_MII_CFG_MODE_MIIP;
+               break;
+       case PHY_INTERFACE_MODE_RMII:
+               miicfg |= GSWIP_MII_CFG_MODE_RMIIM;
+               break;
+       case PHY_INTERFACE_MODE_RGMII:
+       case PHY_INTERFACE_MODE_RGMII_ID:
+       case PHY_INTERFACE_MODE_RGMII_RXID:
+       case PHY_INTERFACE_MODE_RGMII_TXID:
+               miicfg |= GSWIP_MII_CFG_MODE_RGMII;
+               break;
+       case PHY_INTERFACE_MODE_GMII:
+               miicfg |= GSWIP_MII_CFG_MODE_GMII;
+               break;
+       default:
+               dev_err(dp->ds->dev,
+                       "Unsupported interface: %d\n", state->interface);
+               return;
+       }
+
+       gswip_mii_mask_cfg(priv,
+                          GSWIP_MII_CFG_MODE_MASK | GSWIP_MII_CFG_RMII_CLK |
+                          GSWIP_MII_CFG_RGMII_IBS | GSWIP_MII_CFG_LDCLKDIS,
+                          miicfg, port);
+
+       switch (state->interface) {
+       case PHY_INTERFACE_MODE_RGMII_ID:
+               gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK |
+                                         GSWIP_MII_PCDU_RXDLY_MASK, 0, port);
+               break;
+       case PHY_INTERFACE_MODE_RGMII_RXID:
+               gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_RXDLY_MASK, 0, port);
+               break;
+       case PHY_INTERFACE_MODE_RGMII_TXID:
+               gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK, 0, port);
+               break;
+       default:
+               break;
+       }
+}
+
+static void gswip_phylink_mac_link_down(struct phylink_config *config,
+                                       unsigned int mode,
+                                       phy_interface_t interface)
+{
+       struct dsa_port *dp = dsa_phylink_to_port(config);
+       struct gswip_priv *priv = dp->ds->priv;
+       int port = dp->index;
+
+       gswip_mii_mask_cfg(priv, GSWIP_MII_CFG_EN, 0, port);
+
+       if (!dsa_port_is_cpu(dp))
+               gswip_port_set_link(priv, port, false);
+}
+
+static void gswip_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 dsa_port *dp = dsa_phylink_to_port(config);
+       struct gswip_priv *priv = dp->ds->priv;
+       int port = dp->index;
+
+       if (!dsa_port_is_cpu(dp)) {
+               gswip_port_set_link(priv, port, true);
+               gswip_port_set_speed(priv, port, speed, interface);
+               gswip_port_set_duplex(priv, port, duplex);
+               gswip_port_set_pause(priv, port, tx_pause, rx_pause);
+       }
+
+       gswip_mii_mask_cfg(priv, GSWIP_MII_CFG_EN, GSWIP_MII_CFG_EN, port);
+}
+
+static void gswip_get_strings(struct dsa_switch *ds, int port, u32 stringset,
+                             uint8_t *data)
+{
+       int i;
+
+       if (stringset != ETH_SS_STATS)
+               return;
+
+       for (i = 0; i < ARRAY_SIZE(gswip_rmon_cnt); i++)
+               ethtool_puts(&data, gswip_rmon_cnt[i].name);
+}
+
+static u32 gswip_bcm_ram_entry_read(struct gswip_priv *priv, u32 table,
+                                   u32 index)
+{
+       u32 result, val;
+       int err;
+
+       regmap_write(priv->gswip, GSWIP_BM_RAM_ADDR, index);
+       regmap_write_bits(priv->gswip, GSWIP_BM_RAM_CTRL,
+                         GSWIP_BM_RAM_CTRL_ADDR_MASK | GSWIP_BM_RAM_CTRL_OPMOD |
+                         GSWIP_BM_RAM_CTRL_BAS,
+                         table | GSWIP_BM_RAM_CTRL_BAS);
+
+       err = gswip_switch_r_timeout(priv, GSWIP_BM_RAM_CTRL,
+                                    GSWIP_BM_RAM_CTRL_BAS);
+       if (err) {
+               dev_err(priv->dev, "timeout while reading table: %u, index: %u\n",
+                       table, index);
+               return 0;
+       }
+
+       regmap_read(priv->gswip, GSWIP_BM_RAM_VAL(0), &result);
+       regmap_read(priv->gswip, GSWIP_BM_RAM_VAL(1), &val);
+       result |= val << 16;
+
+       return result;
+}
+
+static void gswip_get_ethtool_stats(struct dsa_switch *ds, int port,
+                                   uint64_t *data)
+{
+       struct gswip_priv *priv = ds->priv;
+       const struct gswip_rmon_cnt_desc *rmon_cnt;
+       int i;
+       u64 high;
+
+       for (i = 0; i < ARRAY_SIZE(gswip_rmon_cnt); i++) {
+               rmon_cnt = &gswip_rmon_cnt[i];
+
+               data[i] = gswip_bcm_ram_entry_read(priv, port,
+                                                  rmon_cnt->offset);
+               if (rmon_cnt->size == 2) {
+                       high = gswip_bcm_ram_entry_read(priv, port,
+                                                       rmon_cnt->offset + 1);
+                       data[i] |= high << 32;
+               }
+       }
+}
+
+static int gswip_get_sset_count(struct dsa_switch *ds, int port, int sset)
+{
+       if (sset != ETH_SS_STATS)
+               return 0;
+
+       return ARRAY_SIZE(gswip_rmon_cnt);
+}
+
+static struct phylink_pcs *gswip_phylink_mac_select_pcs(struct phylink_config *config,
+                                                       phy_interface_t interface)
+{
+       struct dsa_port *dp = dsa_phylink_to_port(config);
+       struct gswip_priv *priv = dp->ds->priv;
+
+       if (priv->hw_info->mac_select_pcs)
+               return priv->hw_info->mac_select_pcs(config, interface);
+
+       return NULL;
+}
+
+static const struct phylink_mac_ops gswip_phylink_mac_ops = {
+       .mac_config             = gswip_phylink_mac_config,
+       .mac_link_down          = gswip_phylink_mac_link_down,
+       .mac_link_up            = gswip_phylink_mac_link_up,
+       .mac_select_pcs         = gswip_phylink_mac_select_pcs,
+};
+
+static const struct dsa_switch_ops gswip_switch_ops = {
+       .get_tag_protocol       = gswip_get_tag_protocol,
+       .setup                  = gswip_setup,
+       .port_setup             = gswip_port_setup,
+       .port_enable            = gswip_port_enable,
+       .port_disable           = gswip_port_disable,
+       .port_bridge_join       = gswip_port_bridge_join,
+       .port_bridge_leave      = gswip_port_bridge_leave,
+       .port_fast_age          = gswip_port_fast_age,
+       .port_vlan_filtering    = gswip_port_vlan_filtering,
+       .port_vlan_add          = gswip_port_vlan_add,
+       .port_vlan_del          = gswip_port_vlan_del,
+       .port_stp_state_set     = gswip_port_stp_state_set,
+       .port_fdb_add           = gswip_port_fdb_add,
+       .port_fdb_del           = gswip_port_fdb_del,
+       .port_fdb_dump          = gswip_port_fdb_dump,
+       .port_change_mtu        = gswip_port_change_mtu,
+       .port_max_mtu           = gswip_port_max_mtu,
+       .phylink_get_caps       = gswip_phylink_get_caps,
+       .get_strings            = gswip_get_strings,
+       .get_ethtool_stats      = gswip_get_ethtool_stats,
+       .get_sset_count         = gswip_get_sset_count,
+};
+
+void gswip_disable_switch(struct gswip_priv *priv)
+{
+       regmap_clear_bits(priv->mdio, GSWIP_MDIO_GLOB, GSWIP_MDIO_GLOB_ENABLE);
+}
+EXPORT_SYMBOL_GPL(gswip_disable_switch);
+
+static int gswip_validate_cpu_port(struct dsa_switch *ds)
+{
+       struct gswip_priv *priv = ds->priv;
+       struct dsa_port *cpu_dp;
+       int cpu_port = -1;
+
+       dsa_switch_for_each_cpu_port(cpu_dp, ds) {
+               if (cpu_port != -1)
+                       return dev_err_probe(ds->dev, -EINVAL,
+                                            "only a single CPU port is supported\n");
+
+               cpu_port = cpu_dp->index;
+       }
+
+       if (cpu_port == -1)
+               return dev_err_probe(ds->dev, -EINVAL, "no CPU port defined\n");
+
+       if (BIT(cpu_port) & ~priv->hw_info->allowed_cpu_ports)
+               return dev_err_probe(ds->dev, -EINVAL,
+                                    "unsupported CPU port defined\n");
+
+       return 0;
+}
+
+int gswip_probe_common(struct gswip_priv *priv, u32 version)
+{
+       int err;
+
+       mutex_init(&priv->pce_table_lock);
+
+       priv->ds = devm_kzalloc(priv->dev, sizeof(*priv->ds), GFP_KERNEL);
+       if (!priv->ds)
+               return -ENOMEM;
+
+       priv->ds->dev = priv->dev;
+       priv->ds->num_ports = priv->hw_info->max_ports;
+       priv->ds->ops = &gswip_switch_ops;
+       priv->ds->phylink_mac_ops = &gswip_phylink_mac_ops;
+       priv->ds->priv = priv;
+
+       /* The hardware has the 'major/minor' version bytes in the wrong order
+        * preventing numerical comparisons. Construct a 16-bit unsigned integer
+        * having the REV field as most significant byte and the MOD field as
+        * least significant byte. This is effectively swapping the two bytes of
+        * the version variable, but other than using swab16 it doesn't affect
+        * the source variable.
+        */
+       priv->version = GSWIP_VERSION_REV(version) << 8 |
+                       GSWIP_VERSION_MOD(version);
+
+       err = dsa_register_switch(priv->ds);
+       if (err)
+               return dev_err_probe(priv->dev, err, "dsa switch registration failed\n");
+
+       err = gswip_validate_cpu_port(priv->ds);
+       if (err)
+               goto disable_switch;
+
+       dev_info(priv->dev, "probed GSWIP version %lx mod %lx\n",
+                GSWIP_VERSION_REV(version), GSWIP_VERSION_MOD(version));
+
+       return 0;
+
+disable_switch:
+       gswip_disable_switch(priv);
+       dsa_unregister_switch(priv->ds);
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(gswip_probe_common);
+
+MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>");
+MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
+MODULE_DESCRIPTION("Lantiq / Intel / MaxLinear GSWIP common functions");
+MODULE_LICENSE("GPL");