root/drivers/net/dsa/microchip/ksz9477.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Microchip KSZ9477 switch driver main logic
 *
 * Copyright (C) 2017-2025 Microchip Technology Inc.
 */

#include <linux/dsa/ksz_common.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/iopoll.h>
#include <linux/platform_data/microchip-ksz.h>
#include <linux/phy.h>
#include <linux/if_bridge.h>
#include <linux/if_hsr.h>
#include <linux/if_vlan.h>
#include <net/dsa.h>
#include <net/switchdev.h>

#include "ksz9477_reg.h"
#include "ksz_common.h"
#include "ksz_dcb.h"
#include "ksz9477.h"

static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set)
{
        regmap_update_bits(ksz_regmap_8(dev), addr, bits, set ? bits : 0);
}

static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits,
                         bool set)
{
        regmap_update_bits(ksz_regmap_8(dev), PORT_CTRL_ADDR(port, offset),
                           bits, set ? bits : 0);
}

static void ksz9477_cfg32(struct ksz_device *dev, u32 addr, u32 bits, bool set)
{
        regmap_update_bits(ksz_regmap_32(dev), addr, bits, set ? bits : 0);
}

static void ksz9477_port_cfg32(struct ksz_device *dev, int port, int offset,
                               u32 bits, bool set)
{
        regmap_update_bits(ksz_regmap_32(dev), PORT_CTRL_ADDR(port, offset),
                           bits, set ? bits : 0);
}

static int ksz9477_change_mtu(struct dsa_switch *ds, int port, int mtu)
{
        struct ksz_device *dev = ds->priv;
        u16 frame_size;

        if (!dsa_is_cpu_port(dev->ds, port))
                return 0;

        frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN;

        return regmap_update_bits(ksz_regmap_16(dev), REG_SW_MTU__2,
                                  REG_SW_MTU_MASK, frame_size);
}

static int ksz9477_wait_vlan_ctrl_ready(struct ksz_device *dev)
{
        unsigned int val;

        return regmap_read_poll_timeout(ksz_regmap_8(dev), REG_SW_VLAN_CTRL,
                                        val, !(val & VLAN_START), 10, 1000);
}

static int ksz9477_get_vlan_table(struct ksz_device *dev, u16 vid,
                                  u32 *vlan_table)
{
        int ret;

        mutex_lock(&dev->vlan_mutex);

        ksz_write16(dev, REG_SW_VLAN_ENTRY_INDEX__2, vid & VLAN_INDEX_M);
        ksz_write8(dev, REG_SW_VLAN_CTRL, VLAN_READ | VLAN_START);

        /* wait to be cleared */
        ret = ksz9477_wait_vlan_ctrl_ready(dev);
        if (ret) {
                dev_dbg(dev->dev, "Failed to read vlan table\n");
                goto exit;
        }

        ksz_read32(dev, REG_SW_VLAN_ENTRY__4, &vlan_table[0]);
        ksz_read32(dev, REG_SW_VLAN_ENTRY_UNTAG__4, &vlan_table[1]);
        ksz_read32(dev, REG_SW_VLAN_ENTRY_PORTS__4, &vlan_table[2]);

        ksz_write8(dev, REG_SW_VLAN_CTRL, 0);

exit:
        mutex_unlock(&dev->vlan_mutex);

        return ret;
}

static int ksz9477_set_vlan_table(struct ksz_device *dev, u16 vid,
                                  u32 *vlan_table)
{
        int ret;

        mutex_lock(&dev->vlan_mutex);

        ksz_write32(dev, REG_SW_VLAN_ENTRY__4, vlan_table[0]);
        ksz_write32(dev, REG_SW_VLAN_ENTRY_UNTAG__4, vlan_table[1]);
        ksz_write32(dev, REG_SW_VLAN_ENTRY_PORTS__4, vlan_table[2]);

        ksz_write16(dev, REG_SW_VLAN_ENTRY_INDEX__2, vid & VLAN_INDEX_M);
        ksz_write8(dev, REG_SW_VLAN_CTRL, VLAN_START | VLAN_WRITE);

        /* wait to be cleared */
        ret = ksz9477_wait_vlan_ctrl_ready(dev);
        if (ret) {
                dev_dbg(dev->dev, "Failed to write vlan table\n");
                goto exit;
        }

        ksz_write8(dev, REG_SW_VLAN_CTRL, 0);

        /* update vlan cache table */
        dev->vlan_cache[vid].table[0] = vlan_table[0];
        dev->vlan_cache[vid].table[1] = vlan_table[1];
        dev->vlan_cache[vid].table[2] = vlan_table[2];

exit:
        mutex_unlock(&dev->vlan_mutex);

        return ret;
}

static void ksz9477_read_table(struct ksz_device *dev, u32 *table)
{
        ksz_read32(dev, REG_SW_ALU_VAL_A, &table[0]);
        ksz_read32(dev, REG_SW_ALU_VAL_B, &table[1]);
        ksz_read32(dev, REG_SW_ALU_VAL_C, &table[2]);
        ksz_read32(dev, REG_SW_ALU_VAL_D, &table[3]);
}

static void ksz9477_write_table(struct ksz_device *dev, u32 *table)
{
        ksz_write32(dev, REG_SW_ALU_VAL_A, table[0]);
        ksz_write32(dev, REG_SW_ALU_VAL_B, table[1]);
        ksz_write32(dev, REG_SW_ALU_VAL_C, table[2]);
        ksz_write32(dev, REG_SW_ALU_VAL_D, table[3]);
}

static int ksz9477_wait_alu_ready(struct ksz_device *dev)
{
        unsigned int val;

        return regmap_read_poll_timeout(ksz_regmap_32(dev), REG_SW_ALU_CTRL__4,
                                        val, !(val & ALU_START), 10, 1000);
}

static int ksz9477_wait_alu_sta_ready(struct ksz_device *dev)
{
        unsigned int val;

        return regmap_read_poll_timeout(ksz_regmap_32(dev),
                                        REG_SW_ALU_STAT_CTRL__4,
                                        val, !(val & ALU_STAT_START),
                                        10, 1000);
}

static void port_sgmii_s(struct ksz_device *dev, uint port, u16 devid, u16 reg)
{
        u32 data;

        data = (devid & MII_MMD_CTRL_DEVAD_MASK) << 16;
        data |= reg;
        ksz_pwrite32(dev, port, REG_PORT_SGMII_ADDR__4, data);
}

static void port_sgmii_r(struct ksz_device *dev, uint port, u16 devid, u16 reg,
                         u16 *buf)
{
        port_sgmii_s(dev, port, devid, reg);
        ksz_pread16(dev, port, REG_PORT_SGMII_DATA__4 + 2, buf);
}

static void port_sgmii_w(struct ksz_device *dev, uint port, u16 devid, u16 reg,
                         u16 buf)
{
        port_sgmii_s(dev, port, devid, reg);
        ksz_pwrite32(dev, port, REG_PORT_SGMII_DATA__4, buf);
}

static int ksz9477_pcs_read(struct mii_bus *bus, int phy, int mmd, int reg)
{
        struct ksz_device *dev = bus->priv;
        int port = ksz_get_sgmii_port(dev);
        u16 val;

        port_sgmii_r(dev, port, mmd, reg, &val);

        /* Simulate a value to activate special code in the XPCS driver if
         * supported.
         */
        if (mmd == MDIO_MMD_PMAPMD) {
                if (reg == MDIO_DEVID1)
                        val = 0x9477;
                else if (reg == MDIO_DEVID2)
                        val = 0x22 << 10;
        } else if (mmd == MDIO_MMD_VEND2) {
                struct ksz_port *p = &dev->ports[port];

                /* Need to update MII_BMCR register with the exact speed and
                 * duplex mode when running in SGMII mode and this register is
                 * used to detect connected speed in that mode.
                 */
                if (reg == MMD_SR_MII_AUTO_NEG_STATUS) {
                        int duplex, speed;

                        if (val & SR_MII_STAT_LINK_UP) {
                                speed = (val >> SR_MII_STAT_S) & SR_MII_STAT_M;
                                if (speed == SR_MII_STAT_1000_MBPS)
                                        speed = SPEED_1000;
                                else if (speed == SR_MII_STAT_100_MBPS)
                                        speed = SPEED_100;
                                else
                                        speed = SPEED_10;

                                if (val & SR_MII_STAT_FULL_DUPLEX)
                                        duplex = DUPLEX_FULL;
                                else
                                        duplex = DUPLEX_HALF;

                                if (!p->link || p->speed != speed ||
                                    p->duplex != duplex) {
                                        u16 ctrl;

                                        p->link = true;
                                        p->speed = speed;
                                        p->duplex = duplex;
                                        port_sgmii_r(dev, port, mmd, MII_BMCR,
                                                     &ctrl);
                                        ctrl &= BMCR_ANENABLE;
                                        ctrl |= mii_bmcr_encode_fixed(speed,
                                                                      duplex);
                                        port_sgmii_w(dev, port, mmd, MII_BMCR,
                                                     ctrl);
                                }
                        } else {
                                p->link = false;
                        }
                } else if (reg == MII_BMSR) {
                        p->link = !!(val & BMSR_LSTATUS);
                }
        }

        return val;
}

static int ksz9477_pcs_write(struct mii_bus *bus, int phy, int mmd, int reg,
                             u16 val)
{
        struct ksz_device *dev = bus->priv;
        int port = ksz_get_sgmii_port(dev);

        if (mmd == MDIO_MMD_VEND2) {
                struct ksz_port *p = &dev->ports[port];

                if (reg == MMD_SR_MII_AUTO_NEG_CTRL) {
                        u16 sgmii_mode = SR_MII_PCS_SGMII << SR_MII_PCS_MODE_S;

                        /* Need these bits for 1000BASE-X mode to work with
                         * AN on.
                         */
                        if (!(val & sgmii_mode))
                                val |= SR_MII_SGMII_LINK_UP |
                                       SR_MII_TX_CFG_PHY_MASTER;

                        /* SGMII interrupt in the port cannot be masked, so
                         * make sure interrupt is not enabled as it is not
                         * handled.
                         */
                        val &= ~SR_MII_AUTO_NEG_COMPLETE_INTR;
                } else if (reg == MII_BMCR) {
                        /* The MII_ADVERTISE register needs to write once
                         * before doing auto-negotiation for the correct
                         * config_word to be sent out after reset.
                         */
                        if ((val & BMCR_ANENABLE) && !p->sgmii_adv_write) {
                                u16 adv;

                                /* The SGMII port cannot disable flow control
                                 * so it is better to just advertise symmetric
                                 * pause.
                                 */
                                port_sgmii_r(dev, port, mmd, MII_ADVERTISE,
                                             &adv);
                                adv |= ADVERTISE_1000XPAUSE;
                                adv &= ~ADVERTISE_1000XPSE_ASYM;
                                port_sgmii_w(dev, port, mmd, MII_ADVERTISE,
                                             adv);
                                p->sgmii_adv_write = 1;
                        } else if (val & BMCR_RESET) {
                                p->sgmii_adv_write = 0;
                        }
                } else if (reg == MII_ADVERTISE) {
                        /* XPCS driver writes to this register so there is no
                         * need to update it for the errata.
                         */
                        p->sgmii_adv_write = 1;
                }
        }
        port_sgmii_w(dev, port, mmd, reg, val);

        return 0;
}

static int ksz9477_pcs_create(struct ksz_device *dev)
{
        int port = ksz_get_sgmii_port(dev);
        struct ksz_port *p = &dev->ports[port];
        struct phylink_pcs *pcs;
        struct mii_bus *bus;
        int ret;

        bus = devm_mdiobus_alloc(dev->dev);
        if (!bus)
                return -ENOMEM;

        bus->name = "ksz_pcs_mdio_bus";
        snprintf(bus->id, MII_BUS_ID_SIZE, "%s-pcs",
                 dev_name(dev->dev));
        bus->read_c45 = &ksz9477_pcs_read;
        bus->write_c45 = &ksz9477_pcs_write;
        bus->parent = dev->dev;
        bus->phy_mask = ~0;
        bus->priv = dev;

        ret = devm_mdiobus_register(dev->dev, bus);
        if (ret)
                return ret;

        pcs = xpcs_create_pcs_mdiodev(bus, 0);
        if (IS_ERR(pcs))
                return PTR_ERR(pcs);
        p->pcs = pcs;

        return 0;
}

static int ksz9477_reset_switch(struct ksz_device *dev)
{
        u8 data8;
        u32 data32;

        /* reset switch */
        ksz_cfg(dev, REG_SW_OPERATION, SW_RESET, true);

        /* turn off SPI DO Edge select */
        regmap_update_bits(ksz_regmap_8(dev), REG_SW_GLOBAL_SERIAL_CTRL_0,
                           SPI_AUTO_EDGE_DETECTION, 0);

        /* default configuration */
        ksz_write8(dev, REG_SW_LUE_CTRL_1,
                   SW_AGING_ENABLE | SW_LINK_AUTO_AGING | SW_SRC_ADDR_FILTER);

        /* disable interrupts */
        ksz_write32(dev, REG_SW_INT_MASK__4, SWITCH_INT_MASK);
        ksz_write32(dev, REG_SW_PORT_INT_MASK__4, 0x7F);
        ksz_read32(dev, REG_SW_PORT_INT_STATUS__4, &data32);

        /* KSZ9893 compatible chips do not support refclk configuration */
        if (dev->chip_id == KSZ9893_CHIP_ID ||
            dev->chip_id == KSZ8563_CHIP_ID ||
            dev->chip_id == KSZ9563_CHIP_ID)
                return 0;

        data8 = SW_ENABLE_REFCLKO;
        if (dev->synclko_disable)
                data8 = 0;
        else if (dev->synclko_125)
                data8 = SW_ENABLE_REFCLKO | SW_REFCLKO_IS_125MHZ;
        ksz_write8(dev, REG_SW_GLOBAL_OUTPUT_CTRL__1, data8);

        return 0;
}

void ksz9477_r_mib_cnt(struct ksz_device *dev, int port, u16 addr, u64 *cnt)
{
        struct ksz_port *p = &dev->ports[port];
        unsigned int val;
        u32 data;
        int ret;

        /* retain the flush/freeze bit */
        data = p->freeze ? MIB_COUNTER_FLUSH_FREEZE : 0;
        data |= MIB_COUNTER_READ;
        data |= (addr << MIB_COUNTER_INDEX_S);
        ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, data);

        ret = regmap_read_poll_timeout(ksz_regmap_32(dev),
                        PORT_CTRL_ADDR(port, REG_PORT_MIB_CTRL_STAT__4),
                        val, !(val & MIB_COUNTER_READ), 10, 1000);
        /* failed to read MIB. get out of loop */
        if (ret) {
                dev_dbg(dev->dev, "Failed to get MIB\n");
                return;
        }

        /* count resets upon read */
        ksz_pread32(dev, port, REG_PORT_MIB_DATA, &data);
        *cnt += data;
}

void ksz9477_r_mib_pkt(struct ksz_device *dev, int port, u16 addr,
                       u64 *dropped, u64 *cnt)
{
        addr = dev->info->mib_names[addr].index;
        ksz9477_r_mib_cnt(dev, port, addr, cnt);
}

void ksz9477_freeze_mib(struct ksz_device *dev, int port, bool freeze)
{
        u32 val = freeze ? MIB_COUNTER_FLUSH_FREEZE : 0;
        struct ksz_port *p = &dev->ports[port];

        /* enable/disable the port for flush/freeze function */
        mutex_lock(&p->mib.cnt_mutex);
        ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, val);

        /* used by MIB counter reading code to know freeze is enabled */
        p->freeze = freeze;
        mutex_unlock(&p->mib.cnt_mutex);
}

static int ksz9477_half_duplex_monitor(struct ksz_device *dev, int port,
                                       u64 tx_late_col)
{
        u8 lue_ctrl;
        u32 pmavbc;
        u16 pqm;
        int ret;

        /* Errata DS80000754 recommends monitoring potential faults in
         * half-duplex mode. The switch might not be able to communicate anymore
         * in these states. If you see this message, please read the
         * errata-sheet for more information:
         * https://ww1.microchip.com/downloads/aemDocuments/documents/UNG/ProductDocuments/Errata/KSZ9477S-Errata-DS80000754.pdf
         * To workaround this issue, half-duplex mode should be avoided.
         * A software reset could be implemented to recover from this state.
         */
        dev_warn_once(dev->dev,
                      "Half-duplex detected on port %d, transmission halt may occur\n",
                      port);
        if (tx_late_col != 0) {
                /* Transmission halt with late collisions */
                dev_crit_once(dev->dev,
                              "TX late collisions detected, transmission may be halted on port %d\n",
                              port);
        }
        ret = ksz_read8(dev, REG_SW_LUE_CTRL_0, &lue_ctrl);
        if (ret)
                return ret;
        if (lue_ctrl & SW_VLAN_ENABLE) {
                ret = ksz_pread16(dev, port, REG_PORT_QM_TX_CNT_0__4, &pqm);
                if (ret)
                        return ret;

                ret = ksz_read32(dev, REG_PMAVBC, &pmavbc);
                if (ret)
                        return ret;

                if ((FIELD_GET(PMAVBC_MASK, pmavbc) <= PMAVBC_MIN) ||
                    (FIELD_GET(PORT_QM_TX_CNT_M, pqm) >= PORT_QM_TX_CNT_MAX)) {
                        /* Transmission halt with Half-Duplex and VLAN */
                        dev_crit_once(dev->dev,
                                      "resources out of limits, transmission may be halted\n");
                }
        }

        return ret;
}

int ksz9477_errata_monitor(struct ksz_device *dev, int port,
                           u64 tx_late_col)
{
        u8 status;
        int ret;

        ret = ksz_pread8(dev, port, REG_PORT_STATUS_0, &status);
        if (ret)
                return ret;

        if (!(FIELD_GET(PORT_INTF_SPEED_MASK, status)
              == PORT_INTF_SPEED_NONE) &&
            !(status & PORT_INTF_FULL_DUPLEX)) {
                ret = ksz9477_half_duplex_monitor(dev, port, tx_late_col);
        }

        return ret;
}

void ksz9477_port_init_cnt(struct ksz_device *dev, int port)
{
        struct ksz_port_mib *mib = &dev->ports[port].mib;

        /* flush all enabled port MIB counters */
        mutex_lock(&mib->cnt_mutex);
        ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4,
                     MIB_COUNTER_FLUSH_FREEZE);
        ksz_write8(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FLUSH);
        ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, 0);
        mutex_unlock(&mib->cnt_mutex);
}

static void ksz9477_r_phy_quirks(struct ksz_device *dev, u16 addr, u16 reg,
                                 u16 *data)
{
        /* KSZ8563R do not have extended registers but BMSR_ESTATEN and
         * BMSR_ERCAP bits are set.
         */
        if (dev->chip_id == KSZ8563_CHIP_ID && reg == MII_BMSR)
                *data &= ~(BMSR_ESTATEN | BMSR_ERCAP);
}

static int ksz9477_r_phy(struct ksz_device *dev, u16 addr, u16 reg, u16 *data)
{
        u16 val = 0xffff;
        int ret;

        /* No real PHY after this. Simulate the PHY.
         * A fixed PHY can be setup in the device tree, but this function is
         * still called for that port during initialization.
         * For RGMII PHY there is no way to access it so the fixed PHY should
         * be used.
         */
        if (!dev->info->internal_phy[addr]) {
                struct ksz_port *p = &dev->ports[addr];

                switch (reg) {
                case MII_BMCR:
                        val = 0x1140;
                        break;
                case MII_BMSR:
                        val = 0x796d;
                        break;
                case MII_PHYSID1:
                        val = 0x0022;
                        break;
                case MII_PHYSID2:
                        val = 0x1631;
                        break;
                case MII_ADVERTISE:
                        val = 0x05e1;
                        break;
                case MII_LPA:
                        val = 0xc5e1;
                        break;
                case MII_CTRL1000:
                        val = 0x0700;
                        break;
                case MII_STAT1000:
                        if (p->speed == SPEED_1000)
                                val = 0x3800;
                        else
                                val = 0;
                        break;
                }
        } else {
                ret = ksz_pread16(dev, addr, 0x100 + (reg << 1), &val);
                if (ret)
                        return ret;

                ksz9477_r_phy_quirks(dev, addr, reg, &val);
        }

        *data = val;

        return 0;
}

static int ksz9477_phy_read16(struct dsa_switch *ds, int addr, int reg)
{
        struct ksz_device *dev = ds->priv;
        u16 val = 0xffff;
        int ret;

        ret = ksz9477_r_phy(dev, addr, reg, &val);
        if (ret)
                return ret;

        return val;
}

static int ksz9477_w_phy(struct ksz_device *dev, u16 addr, u16 reg, u16 val)
{
        u32 mask, val32;

        /* No real PHY after this. */
        if (!dev->info->internal_phy[addr])
                return 0;

        if (reg < 0x10)
                return ksz_pwrite16(dev, addr, 0x100 + (reg << 1), val);

        /* Errata: When using SPI, I2C, or in-band register access,
         * writes to certain PHY registers should be performed as
         * 32-bit writes instead of 16-bit writes.
         */
        val32 = val;
        mask = 0xffff;
        if ((reg & 1) == 0) {
                val32 <<= 16;
                mask <<= 16;
        }
        reg &= ~1;
        return ksz_prmw32(dev, addr, 0x100 + (reg << 1), mask, val32);
}

static int ksz9477_phy_write16(struct dsa_switch *ds, int addr, int reg, u16 val)
{
        struct ksz_device *dev = ds->priv;
        int ret;

        ret = ksz9477_w_phy(dev, addr, reg, val);
        if (ret)
                return ret;

        return 0;
}

void ksz9477_cfg_port_member(struct ksz_device *dev, int port, u8 member)
{
        ksz_pwrite32(dev, port, REG_PORT_VLAN_MEMBERSHIP__4, member);
}

void ksz9477_flush_dyn_mac_table(struct dsa_switch *ds, int port)
{
        struct ksz_device *dev = ds->priv;
        const u16 *regs = dev->info->regs;
        u8 data;

        regmap_update_bits(ksz_regmap_8(dev), REG_SW_LUE_CTRL_2,
                           SW_FLUSH_OPTION_M << SW_FLUSH_OPTION_S,
                           SW_FLUSH_OPTION_DYN_MAC << SW_FLUSH_OPTION_S);

        if (port < dev->info->port_cnt) {
                /* flush individual port */
                ksz_pread8(dev, port, regs[P_STP_CTRL], &data);
                if (!(data & PORT_LEARN_DISABLE))
                        ksz_pwrite8(dev, port, regs[P_STP_CTRL],
                                    data | PORT_LEARN_DISABLE);
                ksz_cfg(dev, S_FLUSH_TABLE_CTRL, SW_FLUSH_DYN_MAC_TABLE, true);
                ksz_pwrite8(dev, port, regs[P_STP_CTRL], data);
        } else {
                /* flush all */
                ksz_cfg(dev, S_FLUSH_TABLE_CTRL, SW_FLUSH_STP_TABLE, true);
        }
}

int ksz9477_port_vlan_filtering(struct dsa_switch *ds, int port,
                                bool flag, struct netlink_ext_ack *extack)
{
        struct ksz_device *dev = ds->priv;

        if (flag) {
                ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL,
                             PORT_VLAN_LOOKUP_VID_0, true);
                ksz_cfg(dev, REG_SW_LUE_CTRL_0, SW_VLAN_ENABLE, true);
        } else {
                ksz_cfg(dev, REG_SW_LUE_CTRL_0, SW_VLAN_ENABLE, false);
                ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL,
                             PORT_VLAN_LOOKUP_VID_0, false);
        }

        return 0;
}

int ksz9477_port_vlan_add(struct dsa_switch *ds, int port,
                          const struct switchdev_obj_port_vlan *vlan,
                          struct netlink_ext_ack *extack)
{
        bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
        struct ksz_device *dev = ds->priv;
        u32 vlan_table[3];
        int err;

        err = ksz9477_get_vlan_table(dev, vlan->vid, vlan_table);
        if (err) {
                NL_SET_ERR_MSG_MOD(extack, "Failed to get vlan table");
                return err;
        }

        vlan_table[0] = VLAN_VALID | (vlan->vid & VLAN_FID_M);
        if (untagged)
                vlan_table[1] |= BIT(port);
        else
                vlan_table[1] &= ~BIT(port);
        vlan_table[1] &= ~(BIT(dev->cpu_port));

        vlan_table[2] |= BIT(port) | BIT(dev->cpu_port);

        err = ksz9477_set_vlan_table(dev, vlan->vid, vlan_table);
        if (err) {
                NL_SET_ERR_MSG_MOD(extack, "Failed to set vlan table");
                return err;
        }

        /* change PVID */
        if (vlan->flags & BRIDGE_VLAN_INFO_PVID)
                ksz_pwrite16(dev, port, REG_PORT_DEFAULT_VID, vlan->vid);

        return 0;
}

int ksz9477_port_vlan_del(struct dsa_switch *ds, int port,
                          const struct switchdev_obj_port_vlan *vlan)
{
        bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
        struct ksz_device *dev = ds->priv;
        u32 vlan_table[3];
        u16 pvid;

        ksz_pread16(dev, port, REG_PORT_DEFAULT_VID, &pvid);
        pvid = pvid & 0xFFF;

        if (ksz9477_get_vlan_table(dev, vlan->vid, vlan_table)) {
                dev_dbg(dev->dev, "Failed to get vlan table\n");
                return -ETIMEDOUT;
        }

        vlan_table[2] &= ~BIT(port);

        if (pvid == vlan->vid)
                pvid = 1;

        if (untagged)
                vlan_table[1] &= ~BIT(port);

        if (ksz9477_set_vlan_table(dev, vlan->vid, vlan_table)) {
                dev_dbg(dev->dev, "Failed to set vlan table\n");
                return -ETIMEDOUT;
        }

        ksz_pwrite16(dev, port, REG_PORT_DEFAULT_VID, pvid);

        return 0;
}

int ksz9477_fdb_add(struct dsa_switch *ds, int port,
                    const unsigned char *addr, u16 vid, struct dsa_db db)
{
        struct ksz_device *dev = ds->priv;
        u32 alu_table[4];
        u32 data;
        int ret = 0;

        mutex_lock(&dev->alu_mutex);

        /* find any entry with mac & vid */
        data = vid << ALU_FID_INDEX_S;
        data |= ((addr[0] << 8) | addr[1]);
        ksz_write32(dev, REG_SW_ALU_INDEX_0, data);

        data = ((addr[2] << 24) | (addr[3] << 16));
        data |= ((addr[4] << 8) | addr[5]);
        ksz_write32(dev, REG_SW_ALU_INDEX_1, data);

        /* start read operation */
        ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_READ | ALU_START);

        /* wait to be finished */
        ret = ksz9477_wait_alu_ready(dev);
        if (ret) {
                dev_dbg(dev->dev, "Failed to read ALU\n");
                goto exit;
        }

        /* read ALU entry */
        ksz9477_read_table(dev, alu_table);

        /* update ALU entry */
        alu_table[0] = ALU_V_STATIC_VALID;
        alu_table[1] |= BIT(port);
        if (vid)
                alu_table[1] |= ALU_V_USE_FID;
        alu_table[2] = (vid << ALU_V_FID_S);
        alu_table[2] |= ((addr[0] << 8) | addr[1]);
        alu_table[3] = ((addr[2] << 24) | (addr[3] << 16));
        alu_table[3] |= ((addr[4] << 8) | addr[5]);

        ksz9477_write_table(dev, alu_table);

        ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_WRITE | ALU_START);

        /* wait to be finished */
        ret = ksz9477_wait_alu_ready(dev);
        if (ret)
                dev_dbg(dev->dev, "Failed to write ALU\n");

exit:
        mutex_unlock(&dev->alu_mutex);

        return ret;
}

int ksz9477_fdb_del(struct dsa_switch *ds, int port,
                    const unsigned char *addr, u16 vid, struct dsa_db db)
{
        struct ksz_device *dev = ds->priv;
        u32 alu_table[4];
        u32 data;
        int ret = 0;

        mutex_lock(&dev->alu_mutex);

        /* read any entry with mac & vid */
        data = vid << ALU_FID_INDEX_S;
        data |= ((addr[0] << 8) | addr[1]);
        ksz_write32(dev, REG_SW_ALU_INDEX_0, data);

        data = ((addr[2] << 24) | (addr[3] << 16));
        data |= ((addr[4] << 8) | addr[5]);
        ksz_write32(dev, REG_SW_ALU_INDEX_1, data);

        /* start read operation */
        ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_READ | ALU_START);

        /* wait to be finished */
        ret = ksz9477_wait_alu_ready(dev);
        if (ret) {
                dev_dbg(dev->dev, "Failed to read ALU\n");
                goto exit;
        }

        ksz_read32(dev, REG_SW_ALU_VAL_A, &alu_table[0]);
        if (alu_table[0] & ALU_V_STATIC_VALID) {
                ksz_read32(dev, REG_SW_ALU_VAL_B, &alu_table[1]);
                ksz_read32(dev, REG_SW_ALU_VAL_C, &alu_table[2]);
                ksz_read32(dev, REG_SW_ALU_VAL_D, &alu_table[3]);

                /* clear forwarding port */
                alu_table[1] &= ~BIT(port);

                /* if there is no port to forward, clear table */
                if ((alu_table[1] & ALU_V_PORT_MAP) == 0) {
                        alu_table[0] = 0;
                        alu_table[1] = 0;
                        alu_table[2] = 0;
                        alu_table[3] = 0;
                }
        } else {
                alu_table[0] = 0;
                alu_table[1] = 0;
                alu_table[2] = 0;
                alu_table[3] = 0;
        }

        ksz9477_write_table(dev, alu_table);

        ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_WRITE | ALU_START);

        /* wait to be finished */
        ret = ksz9477_wait_alu_ready(dev);
        if (ret)
                dev_dbg(dev->dev, "Failed to write ALU\n");

exit:
        mutex_unlock(&dev->alu_mutex);

        return ret;
}

static void ksz9477_convert_alu(struct alu_struct *alu, u32 *alu_table)
{
        alu->is_static = !!(alu_table[0] & ALU_V_STATIC_VALID);
        alu->is_src_filter = !!(alu_table[0] & ALU_V_SRC_FILTER);
        alu->is_dst_filter = !!(alu_table[0] & ALU_V_DST_FILTER);
        alu->prio_age = (alu_table[0] >> ALU_V_PRIO_AGE_CNT_S) &
                        ALU_V_PRIO_AGE_CNT_M;
        alu->mstp = alu_table[0] & ALU_V_MSTP_M;

        alu->is_override = !!(alu_table[1] & ALU_V_OVERRIDE);
        alu->is_use_fid = !!(alu_table[1] & ALU_V_USE_FID);
        alu->port_forward = alu_table[1] & ALU_V_PORT_MAP;

        alu->fid = (alu_table[2] >> ALU_V_FID_S) & ALU_V_FID_M;

        alu->mac[0] = (alu_table[2] >> 8) & 0xFF;
        alu->mac[1] = alu_table[2] & 0xFF;
        alu->mac[2] = (alu_table[3] >> 24) & 0xFF;
        alu->mac[3] = (alu_table[3] >> 16) & 0xFF;
        alu->mac[4] = (alu_table[3] >> 8) & 0xFF;
        alu->mac[5] = alu_table[3] & 0xFF;
}

int ksz9477_fdb_dump(struct dsa_switch *ds, int port,
                     dsa_fdb_dump_cb_t *cb, void *data)
{
        struct ksz_device *dev = ds->priv;
        struct alu_struct alu;
        u32 alu_table[4];
        u32 ksz_data;
        int ret = 0;
        int timeout;

        mutex_lock(&dev->alu_mutex);

        /* start ALU search */
        ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_START | ALU_SEARCH);

        do {
                timeout = 1000;
                do {
                        ksz_read32(dev, REG_SW_ALU_CTRL__4, &ksz_data);
                        if ((ksz_data & ALU_VALID) || !(ksz_data & ALU_START))
                                break;
                        usleep_range(1, 10);
                } while (timeout-- > 0);

                if (!timeout) {
                        dev_dbg(dev->dev, "Failed to search ALU\n");
                        ret = -ETIMEDOUT;
                        goto exit;
                }

                if (!(ksz_data & ALU_VALID))
                        continue;

                /* read ALU table */
                ksz9477_read_table(dev, alu_table);

                ksz9477_convert_alu(&alu, alu_table);

                if (alu.port_forward & BIT(port)) {
                        ret = cb(alu.mac, alu.fid, alu.is_static, data);
                        if (ret)
                                goto exit;
                }
        } while (ksz_data & ALU_START);

exit:

        /* stop ALU search */
        ksz_write32(dev, REG_SW_ALU_CTRL__4, 0);

        mutex_unlock(&dev->alu_mutex);

        return ret;
}

int ksz9477_mdb_add(struct dsa_switch *ds, int port,
                    const struct switchdev_obj_port_mdb *mdb, struct dsa_db db)
{
        struct ksz_device *dev = ds->priv;
        u32 static_table[4];
        const u8 *shifts;
        const u32 *masks;
        u32 data;
        int index;
        u32 mac_hi, mac_lo;
        int err = 0;

        shifts = dev->info->shifts;
        masks = dev->info->masks;

        mac_hi = ((mdb->addr[0] << 8) | mdb->addr[1]);
        mac_lo = ((mdb->addr[2] << 24) | (mdb->addr[3] << 16));
        mac_lo |= ((mdb->addr[4] << 8) | mdb->addr[5]);

        mutex_lock(&dev->alu_mutex);

        for (index = 0; index < dev->info->num_statics; index++) {
                /* find empty slot first */
                data = (index << shifts[ALU_STAT_INDEX]) |
                        masks[ALU_STAT_READ] | ALU_STAT_START;
                ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);

                /* wait to be finished */
                err = ksz9477_wait_alu_sta_ready(dev);
                if (err) {
                        dev_dbg(dev->dev, "Failed to read ALU STATIC\n");
                        goto exit;
                }

                /* read ALU static table */
                ksz9477_read_table(dev, static_table);

                if (static_table[0] & ALU_V_STATIC_VALID) {
                        /* check this has same vid & mac address */
                        if (((static_table[2] >> ALU_V_FID_S) == mdb->vid) &&
                            ((static_table[2] & ALU_V_MAC_ADDR_HI) == mac_hi) &&
                            static_table[3] == mac_lo) {
                                /* found matching one */
                                break;
                        }
                } else {
                        /* found empty one */
                        break;
                }
        }

        /* no available entry */
        if (index == dev->info->num_statics) {
                err = -ENOSPC;
                goto exit;
        }

        /* add entry */
        static_table[0] = ALU_V_STATIC_VALID;
        static_table[1] |= BIT(port);
        if (mdb->vid)
                static_table[1] |= ALU_V_USE_FID;
        static_table[2] = (mdb->vid << ALU_V_FID_S);
        static_table[2] |= mac_hi;
        static_table[3] = mac_lo;

        ksz9477_write_table(dev, static_table);

        data = (index << shifts[ALU_STAT_INDEX]) | ALU_STAT_START;
        ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);

        /* wait to be finished */
        if (ksz9477_wait_alu_sta_ready(dev))
                dev_dbg(dev->dev, "Failed to read ALU STATIC\n");

exit:
        mutex_unlock(&dev->alu_mutex);
        return err;
}

int ksz9477_mdb_del(struct dsa_switch *ds, int port,
                    const struct switchdev_obj_port_mdb *mdb, struct dsa_db db)
{
        struct ksz_device *dev = ds->priv;
        u32 static_table[4];
        u32 mac_hi, mac_lo;
        const u8 *shifts;
        const u32 *masks;
        int ret = 0;
        int index;
        u32 data;

        shifts = dev->info->shifts;
        masks = dev->info->masks;

        mac_hi = ((mdb->addr[0] << 8) | mdb->addr[1]);
        mac_lo = ((mdb->addr[2] << 24) | (mdb->addr[3] << 16));
        mac_lo |= ((mdb->addr[4] << 8) | mdb->addr[5]);

        mutex_lock(&dev->alu_mutex);

        for (index = 0; index < dev->info->num_statics; index++) {
                /* find empty slot first */
                data = (index << shifts[ALU_STAT_INDEX]) |
                        masks[ALU_STAT_READ] | ALU_STAT_START;
                ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);

                /* wait to be finished */
                ret = ksz9477_wait_alu_sta_ready(dev);
                if (ret) {
                        dev_dbg(dev->dev, "Failed to read ALU STATIC\n");
                        goto exit;
                }

                /* read ALU static table */
                ksz9477_read_table(dev, static_table);

                if (static_table[0] & ALU_V_STATIC_VALID) {
                        /* check this has same vid & mac address */

                        if (((static_table[2] >> ALU_V_FID_S) == mdb->vid) &&
                            ((static_table[2] & ALU_V_MAC_ADDR_HI) == mac_hi) &&
                            static_table[3] == mac_lo) {
                                /* found matching one */
                                break;
                        }
                }
        }

        /* no available entry */
        if (index == dev->info->num_statics)
                goto exit;

        /* clear port */
        static_table[1] &= ~BIT(port);

        if ((static_table[1] & ALU_V_PORT_MAP) == 0) {
                /* delete entry */
                static_table[0] = 0;
                static_table[1] = 0;
                static_table[2] = 0;
                static_table[3] = 0;
        }

        ksz9477_write_table(dev, static_table);

        data = (index << shifts[ALU_STAT_INDEX]) | ALU_STAT_START;
        ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);

        /* wait to be finished */
        ret = ksz9477_wait_alu_sta_ready(dev);
        if (ret)
                dev_dbg(dev->dev, "Failed to read ALU STATIC\n");

exit:
        mutex_unlock(&dev->alu_mutex);

        return ret;
}

int ksz9477_port_mirror_add(struct dsa_switch *ds, int port,
                            struct dsa_mall_mirror_tc_entry *mirror,
                            bool ingress, struct netlink_ext_ack *extack)
{
        struct ksz_device *dev = ds->priv;
        u8 data;
        int p;

        /* Limit to one sniffer port
         * Check if any of the port is already set for sniffing
         * If yes, instruct the user to remove the previous entry & exit
         */
        for (p = 0; p < dev->info->port_cnt; p++) {
                /* Skip the current sniffing port */
                if (p == mirror->to_local_port)
                        continue;

                ksz_pread8(dev, p, P_MIRROR_CTRL, &data);

                if (data & PORT_MIRROR_SNIFFER) {
                        NL_SET_ERR_MSG_MOD(extack,
                                           "Sniffer port is already configured, delete existing rules & retry");
                        return -EBUSY;
                }
        }

        if (ingress)
                ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, true);
        else
                ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, true);

        /* configure mirror port */
        ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL,
                     PORT_MIRROR_SNIFFER, true);

        ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false);

        return 0;
}

void ksz9477_port_mirror_del(struct dsa_switch *ds, int port,
                             struct dsa_mall_mirror_tc_entry *mirror)
{
        struct ksz_device *dev = ds->priv;
        bool in_use = false;
        u8 data;
        int p;

        if (mirror->ingress)
                ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, false);
        else
                ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, false);


        /* Check if any of the port is still referring to sniffer port */
        for (p = 0; p < dev->info->port_cnt; p++) {
                ksz_pread8(dev, p, P_MIRROR_CTRL, &data);

                if ((data & (PORT_MIRROR_RX | PORT_MIRROR_TX))) {
                        in_use = true;
                        break;
                }
        }

        /* delete sniffing if there are no other mirroring rules */
        if (!in_use)
                ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL,
                             PORT_MIRROR_SNIFFER, false);
}

static phy_interface_t ksz9477_get_interface(struct ksz_device *dev, int port)
{
        phy_interface_t interface;
        bool gbit;

        if (dev->info->internal_phy[port])
                return PHY_INTERFACE_MODE_NA;

        gbit = ksz_get_gbit(dev, port);

        interface = ksz_get_xmii(dev, port, gbit);

        return interface;
}

static void ksz9477_phylink_get_caps(struct dsa_switch *ds, int port,
                                     struct phylink_config *config)
{
        struct ksz_device *dev = ds->priv;

        config->mac_capabilities = MAC_10 | MAC_100 | MAC_ASYM_PAUSE |
                                   MAC_SYM_PAUSE;

        if (dev->info->gbit_capable[port])
                config->mac_capabilities |= MAC_1000FD;

        if (ksz_is_sgmii_port(dev, port)) {
                struct ksz_port *p = &dev->ports[port];

                phy_interface_or(config->supported_interfaces,
                                 config->supported_interfaces,
                                 p->pcs->supported_interfaces);
        }

        ksz_phylink_get_caps(ds, port, config);
}

static int ksz9477_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
{
        struct ksz_device *dev = ds->priv;
        u32 secs = msecs / 1000;
        u8 data, mult, value;
        u32 max_val;
        int ret;

#define MAX_TIMER_VAL   ((1 << 8) - 1)

        /* The aging timer comprises a 3-bit multiplier and an 8-bit second
         * value.  Either of them cannot be zero.  The maximum timer is then
         * 7 * 255 = 1785 seconds.
         */
        if (!secs)
                secs = 1;

        /* Return error if too large. */
        else if (secs > 7 * MAX_TIMER_VAL)
                return -EINVAL;

        ret = ksz_read8(dev, REG_SW_LUE_CTRL_0, &value);
        if (ret < 0)
                return ret;

        /* Check whether there is need to update the multiplier. */
        mult = FIELD_GET(SW_AGE_CNT_M, value);
        max_val = MAX_TIMER_VAL;
        if (mult > 0) {
                /* Try to use the same multiplier already in the register as
                 * the hardware default uses multiplier 4 and 75 seconds for
                 * 300 seconds.
                 */
                max_val = DIV_ROUND_UP(secs, mult);
                if (max_val > MAX_TIMER_VAL || max_val * mult != secs)
                        max_val = MAX_TIMER_VAL;
        }

        data = DIV_ROUND_UP(secs, max_val);
        if (mult != data) {
                value &= ~SW_AGE_CNT_M;
                value |= FIELD_PREP(SW_AGE_CNT_M, data);
                ret = ksz_write8(dev, REG_SW_LUE_CTRL_0, value);
                if (ret < 0)
                        return ret;
        }

        value = DIV_ROUND_UP(secs, data);
        return ksz_write8(dev, REG_SW_LUE_CTRL_3, value);
}

void ksz9477_port_queue_split(struct ksz_device *dev, int port)
{
        u8 data;

        if (dev->info->num_tx_queues == 8)
                data = PORT_EIGHT_QUEUE;
        else if (dev->info->num_tx_queues == 4)
                data = PORT_FOUR_QUEUE;
        else if (dev->info->num_tx_queues == 2)
                data = PORT_TWO_QUEUE;
        else
                data = PORT_SINGLE_QUEUE;

        ksz_prmw8(dev, port, REG_PORT_CTRL_0, PORT_QUEUE_SPLIT_MASK, data);
}

static void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port)
{
        const u16 *regs = dev->info->regs;
        struct dsa_switch *ds = dev->ds;
        u16 data16;
        u8 member;

        /* enable tag tail for host port */
        if (cpu_port)
                ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_TAIL_TAG_ENABLE,
                             true);

        ksz9477_port_queue_split(dev, port);

        ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_MAC_LOOPBACK, false);

        /* set back pressure */
        ksz_port_cfg(dev, port, REG_PORT_MAC_CTRL_1, PORT_BACK_PRESSURE, true);

        /* enable broadcast storm limit */
        ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true);

        /* replace priority */
        ksz_port_cfg(dev, port, REG_PORT_MRI_MAC_CTRL, PORT_USER_PRIO_CEILING,
                     false);
        ksz9477_port_cfg32(dev, port, REG_PORT_MTI_QUEUE_CTRL_0__4,
                           MTI_PVID_REPLACE, false);

        /* force flow control for non-PHY ports only */
        ksz_port_cfg(dev, port, REG_PORT_CTRL_0,
                     PORT_FORCE_TX_FLOW_CTRL | PORT_FORCE_RX_FLOW_CTRL,
                     !dev->info->internal_phy[port]);

        if (cpu_port)
                member = dsa_user_ports(ds);
        else
                member = BIT(dsa_upstream_port(ds, port));

        ksz9477_cfg_port_member(dev, port, member);

        /* clear pending interrupts */
        if (dev->info->internal_phy[port])
                ksz_pread16(dev, port, REG_PORT_PHY_INT_ENABLE, &data16);

        ksz9477_port_acl_init(dev, port);

        /* clear pending wake flags */
        ksz_handle_wake_reason(dev, port);

        /* Disable all WoL options by default. Otherwise
         * ksz_switch_macaddr_get/put logic will not work properly.
         */
        ksz_pwrite8(dev, port, regs[REG_PORT_PME_CTRL], 0);
}

static int ksz9477_dsa_port_setup(struct dsa_switch *ds, int port)
{
        struct ksz_device *dev = ds->priv;
        int ret;

        if (!dsa_is_user_port(ds, port))
                return 0;

        ksz9477_port_setup(dev, port, false);

        ret = ksz9477_set_default_prio_queue_mapping(dev, port);
        if (ret)
                return ret;

        return ksz_dcb_init_port(dev, port);
}

static void ksz9477_config_cpu_port(struct dsa_switch *ds)
{
        struct ksz_device *dev = ds->priv;
        struct ksz_port *p;
        int i;

        for (i = 0; i < dev->info->port_cnt; i++) {
                if (dsa_is_cpu_port(ds, i) &&
                    (dev->info->cpu_ports & (1 << i))) {
                        phy_interface_t interface;
                        const char *prev_msg;
                        const char *prev_mode;

                        dev->cpu_port = i;
                        p = &dev->ports[i];

                        /* Read from XMII register to determine host port
                         * interface.  If set specifically in device tree
                         * note the difference to help debugging.
                         */
                        interface = ksz9477_get_interface(dev, i);
                        if (!p->interface) {
                                if (dev->compat_interface) {
                                        dev_warn(dev->dev,
                                                 "Using legacy switch \"phy-mode\" property, because it is missing on port %d node. "
                                                 "Please update your device tree.\n",
                                                 i);
                                        p->interface = dev->compat_interface;
                                } else {
                                        p->interface = interface;
                                }
                        }
                        if (interface && interface != p->interface) {
                                prev_msg = " instead of ";
                                prev_mode = phy_modes(interface);
                        } else {
                                prev_msg = "";
                                prev_mode = "";
                        }
                        dev_info(dev->dev,
                                 "Port%d: using phy mode %s%s%s\n",
                                 i,
                                 phy_modes(p->interface),
                                 prev_msg,
                                 prev_mode);

                        /* enable cpu port */
                        ksz9477_port_setup(dev, i, true);
                }
        }

        for (i = 0; i < dev->info->port_cnt; i++) {
                if (i == dev->cpu_port)
                        continue;
                ksz_port_stp_state_set(ds, i, BR_STATE_DISABLED);

                /* Power down the internal PHY if port is unused. */
                if (dsa_is_unused_port(ds, i) && dev->info->internal_phy[i])
                        ksz_pwrite16(dev, i, 0x100, BMCR_PDOWN);
        }
}

#define RESV_MCAST_CNT  8

static u8 reserved_mcast_map[RESV_MCAST_CNT] = { 0, 1, 3, 16, 32, 33, 2, 17 };

int ksz9477_enable_stp_addr(struct ksz_device *dev)
{
        u8 i, ports, update;
        const u32 *masks;
        bool override;
        u32 data;
        int ret;

        masks = dev->info->masks;

        /* Enable Reserved multicast table */
        ksz_cfg(dev, REG_SW_LUE_CTRL_0, SW_RESV_MCAST_ENABLE, true);

        /* The reserved multicast address table has 8 entries.  Each entry has
         * a default value of which port to forward.  It is assumed the host
         * port is the last port in most of the switches, but that is not the
         * case for KSZ9477 or maybe KSZ9897.  For LAN937X family the default
         * port is port 5, the first RGMII port.  It is okay for LAN9370, a
         * 5-port switch, but may not be correct for the other 8-port
         * versions.  It is necessary to update the whole table to forward to
         * the right ports.
         * Furthermore PTP messages can use a reserved multicast address and
         * the host will not receive them if this table is not correct.
         */
        for (i = 0; i < RESV_MCAST_CNT; i++) {
                data = reserved_mcast_map[i] <<
                        dev->info->shifts[ALU_STAT_INDEX];
                data |= ALU_STAT_START |
                        masks[ALU_STAT_DIRECT] |
                        masks[ALU_RESV_MCAST_ADDR] |
                        masks[ALU_STAT_READ];
                ret = ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);
                if (ret < 0)
                        return ret;

                /* wait to be finished */
                ret = ksz9477_wait_alu_sta_ready(dev);
                if (ret < 0)
                        return ret;

                ret = ksz_read32(dev, REG_SW_ALU_VAL_B, &data);
                if (ret < 0)
                        return ret;

                override = false;
                ports = data & dev->port_mask;
                switch (i) {
                case 0:
                case 6:
                        /* Change the host port. */
                        update = BIT(dev->cpu_port);
                        override = true;
                        break;
                case 2:
                        /* Change the host port. */
                        update = BIT(dev->cpu_port);
                        break;
                case 4:
                case 5:
                case 7:
                        /* Skip the host port. */
                        update = dev->port_mask & ~BIT(dev->cpu_port);
                        break;
                default:
                        update = ports;
                        break;
                }
                if (update != ports || override) {
                        data &= ~dev->port_mask;
                        data |= update;
                        /* Set Override bit to receive frame even when port is
                         * closed.
                         */
                        if (override)
                                data |= ALU_V_OVERRIDE;
                        ret = ksz_write32(dev, REG_SW_ALU_VAL_B, data);
                        if (ret < 0)
                                return ret;

                        data = reserved_mcast_map[i] <<
                               dev->info->shifts[ALU_STAT_INDEX];
                        data |= ALU_STAT_START |
                                masks[ALU_STAT_DIRECT] |
                                masks[ALU_RESV_MCAST_ADDR] |
                                masks[ALU_STAT_WRITE];
                        ret = ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);
                        if (ret < 0)
                                return ret;

                        /* wait to be finished */
                        ret = ksz9477_wait_alu_sta_ready(dev);
                        if (ret < 0)
                                return ret;
                }
        }

        return 0;
}

static int ksz9477_setup(struct dsa_switch *ds)
{
        struct ksz_device *dev = ds->priv;
        u16 storm_mask, storm_rate;
        struct dsa_port *dp;
        struct ksz_port *p;
        const u16 *regs;
        int ret;

        regs = dev->info->regs;

        dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
                                       dev->info->num_vlans, GFP_KERNEL);
        if (!dev->vlan_cache)
                return -ENOMEM;

        ret = ksz9477_reset_switch(dev);
        if (ret) {
                dev_err(ds->dev, "failed to reset switch\n");
                return ret;
        }

        ret = ksz_parse_drive_strength(dev);
        if (ret)
                return ret;

        if (ksz_has_sgmii_port(dev)) {
                ret = ksz9477_pcs_create(dev);
                if (ret)
                        return ret;
        }

        /* set broadcast storm protection 10% rate */
        storm_mask = BROADCAST_STORM_RATE;
        storm_rate = (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100;
        regmap_update_bits(ksz_regmap_16(dev), regs[S_BROADCAST_CTRL],
                           storm_mask, storm_rate);

        ksz9477_config_cpu_port(ds);

        ksz9477_enable_stp_addr(dev);

        ds->num_tx_queues = dev->info->num_tx_queues;

        regmap_update_bits(ksz_regmap_8(dev), regs[S_MULTICAST_CTRL],
                           MULTICAST_STORM_DISABLE, MULTICAST_STORM_DISABLE);

        ksz_init_mib_timer(dev);

        ds->configure_vlan_while_not_filtering = false;
        ds->dscp_prio_mapping_is_global = true;
        ds->mtu_enforcement_ingress = true;

        /* Required for port partitioning. */
        ksz9477_cfg32(dev, REG_SW_QM_CTRL__4, UNICAST_VLAN_BOUNDARY,
                      true);

        /* Do not work correctly with tail tagging. */
        ksz_cfg(dev, REG_SW_MAC_CTRL_0, SW_CHECK_LENGTH, false);

        /* Enable REG_SW_MTU__2 reg by setting SW_JUMBO_PACKET */
        ksz_cfg(dev, REG_SW_MAC_CTRL_1, SW_JUMBO_PACKET, true);

        /* Use collision based back pressure mode. */
        ksz_cfg(dev, REG_SW_MAC_CTRL_1, SW_BACK_PRESSURE,
                SW_BACK_PRESSURE_COLLISION);

        /* Now we can configure default MTU value */
        ret = regmap_update_bits(ksz_regmap_16(dev), REG_SW_MTU__2, REG_SW_MTU_MASK,
                                 VLAN_ETH_FRAME_LEN + ETH_FCS_LEN);
        if (ret)
                return ret;

        /* queue based egress rate limit */
        ksz_cfg(dev, REG_SW_MAC_CTRL_5, SW_OUT_RATE_LIMIT_QUEUE_BASED, true);

        /* enable global MIB counter freeze function */
        ksz_cfg(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FREEZE, true);

        /* Make sure PME (WoL) is not enabled. If requested, it will
         * be enabled by ksz_wol_pre_shutdown(). Otherwise, some PMICs
         * do not like PME events changes before shutdown.
         */
        ret = ksz_write8(dev, regs[REG_SW_PME_CTRL], 0);
        if (ret < 0)
                return ret;

        /* Start with learning disabled on standalone user ports, and enabled
         * on the CPU port. In lack of other finer mechanisms, learning on the
         * CPU port will avoid flooding bridge local addresses on the network
         * in some cases.
         */
        p = &dev->ports[dev->cpu_port];
        p->learning = true;

        if (dev->irq > 0) {
                ret = ksz_girq_setup(dev);
                if (ret)
                        return ret;

                dsa_switch_for_each_user_port(dp, dev->ds) {
                        ret = ksz_pirq_setup(dev, dp->index);
                        if (ret)
                                goto port_release;

                        if (dev->info->ptp_capable) {
                                ret = ksz_ptp_irq_setup(ds, dp->index);
                                if (ret)
                                        goto pirq_release;
                        }
                }
        }

        if (dev->info->ptp_capable) {
                ret = ksz_ptp_clock_register(ds);
                if (ret) {
                        dev_err(dev->dev, "Failed to register PTP clock: %d\n",
                                ret);
                        goto port_release;
                }
        }

        ret = ksz_mdio_register(dev);
        if (ret < 0) {
                dev_err(dev->dev, "failed to register the mdio");
                goto out_ptp_clock_unregister;
        }

        ret = ksz_dcb_init(dev);
        if (ret)
                goto out_ptp_clock_unregister;

        /* start switch */
        regmap_update_bits(ksz_regmap_8(dev), regs[S_START_CTRL],
                           SW_START, SW_START);

        return 0;

out_ptp_clock_unregister:
        if (dev->info->ptp_capable)
                ksz_ptp_clock_unregister(ds);
port_release:
        if (dev->irq > 0) {
                dsa_switch_for_each_user_port_continue_reverse(dp, dev->ds) {
                        if (dev->info->ptp_capable)
                                ksz_ptp_irq_free(ds, dp->index);
pirq_release:
                        ksz_irq_free(&dev->ports[dp->index].pirq);
                }
                ksz_irq_free(&dev->girq);
        }

        return ret;
}

u32 ksz9477_get_port_addr(int port, int offset)
{
        return PORT_CTRL_ADDR(port, offset);
}

static int ksz9477_tc_cbs_set_cinc(struct ksz_device *dev, int port, u32 val)
{
        val = val >> 8;

        return ksz_pwrite16(dev, port, REG_PORT_MTI_CREDIT_INCREMENT, val);
}

/* The KSZ9477 provides following HW features to accelerate
 * HSR frames handling:
 *
 * 1. TX PACKET DUPLICATION FROM HOST TO SWITCH
 * 2. RX PACKET DUPLICATION DISCARDING
 * 3. PREVENTING PACKET LOOP IN THE RING BY SELF-ADDRESS FILTERING
 *
 * Only one from point 1. has the NETIF_F* flag available.
 *
 * Ones from point 2 and 3 are "best effort" - i.e. those will
 * work correctly most of the time, but it may happen that some
 * frames will not be caught - to be more specific; there is a race
 * condition in hardware such that, when duplicate packets are received
 * on member ports very close in time to each other, the hardware fails
 * to detect that they are duplicates.
 *
 * Hence, the SW needs to handle those special cases. However, the speed
 * up gain is considerable when above features are used.
 *
 * Moreover, the NETIF_F_HW_HSR_FWD feature is also enabled, as HSR frames
 * can be forwarded in the switch fabric between HSR ports.
 */
#define KSZ9477_SUPPORTED_HSR_FEATURES (NETIF_F_HW_HSR_DUP | NETIF_F_HW_HSR_FWD)

static int ksz9477_hsr_join(struct dsa_switch *ds, int port,
                            struct net_device *hsr,
                            struct netlink_ext_ack *extack)
{
        struct ksz_device *dev = ds->priv;
        struct net_device *user;
        struct dsa_port *hsr_dp;
        u8 data, hsr_ports = 0;
        enum hsr_version ver;
        int ret;

        ret = hsr_get_version(hsr, &ver);
        if (ret)
                return ret;

        if (dev->chip_id != KSZ9477_CHIP_ID) {
                NL_SET_ERR_MSG_MOD(extack, "Chip does not support HSR offload");
                return -EOPNOTSUPP;
        }

        /* KSZ9477 can support HW offloading of only 1 HSR device */
        if (dev->hsr_dev && hsr != dev->hsr_dev) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Offload supported for a single HSR");
                return -EOPNOTSUPP;
        }

        /* KSZ9477 only supports HSR v0 and v1 */
        if (!(ver == HSR_V0 || ver == HSR_V1)) {
                NL_SET_ERR_MSG_MOD(extack, "Only HSR v0 and v1 supported");
                return -EOPNOTSUPP;
        }

        /* KSZ9477 can only perform HSR offloading for up to two ports */
        if (hweight8(dev->hsr_ports) >= 2) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Cannot offload more than two ports - using software HSR");
                return -EOPNOTSUPP;
        }

        /* Self MAC address filtering, to avoid frames traversing
         * the HSR ring more than once.
         */
        ret = ksz_switch_macaddr_get(ds, port, extack);
        if (ret)
                return ret;

        /* Program which port(s) shall support HSR */
        ksz_rmw32(dev, REG_HSR_PORT_MAP__4, BIT(port), BIT(port));

        /* Forward frames between HSR ports (i.e. bridge together HSR ports) */
        if (dev->hsr_ports) {
                dsa_hsr_foreach_port(hsr_dp, ds, hsr)
                        hsr_ports |= BIT(hsr_dp->index);

                hsr_ports |= BIT(dsa_upstream_port(ds, port));
                dsa_hsr_foreach_port(hsr_dp, ds, hsr)
                        ksz9477_cfg_port_member(dev, hsr_dp->index, hsr_ports);
        }

        if (!dev->hsr_ports) {
                /* Enable discarding of received HSR frames */
                ksz_read8(dev, REG_HSR_ALU_CTRL_0__1, &data);
                data |= HSR_DUPLICATE_DISCARD;
                data &= ~HSR_NODE_UNICAST;
                ksz_write8(dev, REG_HSR_ALU_CTRL_0__1, data);
        }

        /* Enable per port self-address filtering.
         * The global self-address filtering has already been enabled in the
         * ksz9477_reset_switch() function.
         */
        ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_SRC_ADDR_FILTER, true);

        /* Setup HW supported features for lan HSR ports */
        user = dsa_to_port(ds, port)->user;
        user->features |= KSZ9477_SUPPORTED_HSR_FEATURES;

        dev->hsr_dev = hsr;
        dev->hsr_ports |= BIT(port);

        return 0;
}

static int ksz9477_hsr_leave(struct dsa_switch *ds, int port,
                             struct net_device *hsr)
{
        struct ksz_device *dev = ds->priv;

        WARN_ON(dev->chip_id != KSZ9477_CHIP_ID);

        /* Clear port HSR support */
        ksz_rmw32(dev, REG_HSR_PORT_MAP__4, BIT(port), 0);

        /* Disable forwarding frames between HSR ports */
        ksz9477_cfg_port_member(dev, port, BIT(dsa_upstream_port(ds, port)));

        /* Disable per port self-address filtering */
        ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_SRC_ADDR_FILTER, false);

        dev->hsr_ports &= ~BIT(port);
        if (!dev->hsr_ports)
                dev->hsr_dev = NULL;

        ksz_switch_macaddr_put(ds);

        return 0;
}

static int ksz9477_switch_init(struct ksz_device *dev)
{
        u8 data8;
        int ret;

        dev->port_mask = (1 << dev->info->port_cnt) - 1;

        /* turn off SPI DO Edge select */
        ret = ksz_read8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, &data8);
        if (ret)
                return ret;

        data8 &= ~SPI_AUTO_EDGE_DETECTION;
        ret = ksz_write8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, data8);
        if (ret)
                return ret;

        return 0;
}

static enum dsa_tag_protocol ksz9477_get_tag_protocol(struct dsa_switch *ds,
                                                      int port,
                                                      enum dsa_tag_protocol mp)
{
        struct ksz_device *dev = ds->priv;

        if (dev->chip_id == KSZ8563_CHIP_ID ||
            dev->chip_id == KSZ9893_CHIP_ID ||
            dev->chip_id == KSZ9563_CHIP_ID)
                return DSA_TAG_PROTO_KSZ9893;

        return DSA_TAG_PROTO_KSZ9477;
}

static int ksz9477_connect_tag_protocol(struct dsa_switch *ds,
                                        enum dsa_tag_protocol proto)
{
        struct ksz_tagger_data *tagger_data;

        if (proto != DSA_TAG_PROTO_KSZ9893 && proto != DSA_TAG_PROTO_KSZ9477)
                return -EPROTONOSUPPORT;

        tagger_data = ksz_tagger_data(ds);
        tagger_data->xmit_work_fn = ksz_port_deferred_xmit;

        return 0;
}

static void ksz9477_set_gbit(struct ksz_device *dev, int port, bool gbit)
{
        const u8 *bitval = dev->info->xmii_ctrl1;
        const u16 *regs = dev->info->regs;
        u8 data8;

        ksz_pread8(dev, port, regs[P_XMII_CTRL_1], &data8);

        data8 &= ~P_GMII_1GBIT_M;

        if (gbit)
                data8 |= FIELD_PREP(P_GMII_1GBIT_M, bitval[P_GMII_1GBIT]);
        else
                data8 |= FIELD_PREP(P_GMII_1GBIT_M, bitval[P_GMII_NOT_1GBIT]);

        /* Write the updated value */
        ksz_pwrite8(dev, port, regs[P_XMII_CTRL_1], data8);
}

static void ksz9477_set_100_10mbit(struct ksz_device *dev, int port, int speed)
{
        const u8 *bitval = dev->info->xmii_ctrl0;
        const u16 *regs = dev->info->regs;
        u8 data8;

        ksz_pread8(dev, port, regs[P_XMII_CTRL_0], &data8);

        data8 &= ~P_MII_100MBIT_M;

        if (speed == SPEED_100)
                data8 |= FIELD_PREP(P_MII_100MBIT_M, bitval[P_MII_100MBIT]);
        else
                data8 |= FIELD_PREP(P_MII_100MBIT_M, bitval[P_MII_10MBIT]);

        /* Write the updated value */
        ksz_pwrite8(dev, port, regs[P_XMII_CTRL_0], data8);
}

static void ksz9477_port_set_xmii_speed(struct ksz_device *dev, int port,
                                        int speed)
{
        if (speed == SPEED_1000)
                ksz9477_set_gbit(dev, port, true);
        else
                ksz9477_set_gbit(dev, port, false);

        if (speed == SPEED_100 || speed == SPEED_10)
                ksz9477_set_100_10mbit(dev, port, speed);
}

static void ksz9477_duplex_flowctrl(struct ksz_device *dev, int port, int duplex,
                                    bool tx_pause, bool rx_pause)
{
        const u8 *bitval = dev->info->xmii_ctrl0;
        const u32 *masks = dev->info->masks;
        const u16 *regs = dev->info->regs;
        u8 mask;
        u8 val;

        mask = P_MII_DUPLEX_M | masks[P_MII_TX_FLOW_CTRL] |
               masks[P_MII_RX_FLOW_CTRL];

        if (duplex == DUPLEX_FULL)
                val = FIELD_PREP(P_MII_DUPLEX_M, bitval[P_MII_FULL_DUPLEX]);
        else
                val = FIELD_PREP(P_MII_DUPLEX_M, bitval[P_MII_HALF_DUPLEX]);

        if (tx_pause)
                val |= masks[P_MII_TX_FLOW_CTRL];

        if (rx_pause)
                val |= masks[P_MII_RX_FLOW_CTRL];

        ksz_prmw8(dev, port, regs[P_XMII_CTRL_0], mask, val);
}

static void ksz9477_port_teardown(struct dsa_switch *ds, int port)
{
        struct ksz_device *dev = ds->priv;

        if (dsa_is_user_port(ds, port))
                ksz9477_port_acl_free(dev, port);
}

void ksz9477_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 ksz_device *dev = dp->ds->priv;
        int port = dp->index;
        struct ksz_port *p;

        p = &dev->ports[port];

        /* Internal PHYs */
        if (dev->info->internal_phy[port])
                return;

        p->speed = speed;

        ksz9477_port_set_xmii_speed(dev, port, speed);

        ksz9477_duplex_flowctrl(dev, port, duplex, tx_pause, rx_pause);
}

/**
 * ksz9477_support_eee - Determine Energy Efficient Ethernet (EEE) support for a
 *                       port
 * @ds: Pointer to the DSA switch structure
 * @port: Port number to check
 *
 * This function also documents devices where EEE was initially advertised but
 * later withdrawn due to reliability issues, as described in official errata
 * documents. These devices are explicitly listed to record known limitations,
 * even if there is no technical necessity for runtime checks.
 *
 * Returns: true if the internal PHY on the given port supports fully
 * operational EEE, false otherwise.
 */
static bool ksz9477_support_eee(struct dsa_switch *ds, int port)
{
        struct ksz_device *dev = ds->priv;

        if (!dev->info->internal_phy[port])
                return false;

        switch (dev->chip_id) {
        case KSZ8563_CHIP_ID:
        case KSZ9563_CHIP_ID:
        case KSZ9893_CHIP_ID:
                return true;
        default:
                /* KSZ8567R Errata DS80000752C Module 4 */
                /* KSZ9477S Errata DS80000754A Module 4 */
                /* KSZ9567S Errata DS80000756A Module 4 */
                /* KSZ9896C Errata DS80000757A Module 3 */
                /* KSZ9897R Errata DS80000758C Module 4 */
                /* Energy Efficient Ethernet (EEE) feature select must be
                 * manually disabled
                 *   The EEE feature is enabled by default, but it is not fully
                 *   operational. It must be manually disabled through register
                 *   controls. If not disabled, the PHY ports can auto-negotiate
                 *   to enable EEE, and this feature can cause link drops when
                 *   linked to another device supporting EEE.
                 *
                 * The same item appears in the errata for all switches above.
                 */
                break;
        }

        return false;
}

static struct phylink_pcs *
ksz9477_phylink_mac_select_pcs(struct phylink_config *config,
                               phy_interface_t interface)
{
        struct dsa_port *dp = dsa_phylink_to_port(config);
        struct ksz_device *dev = dp->ds->priv;
        struct ksz_port *p = &dev->ports[dp->index];

        if (ksz_is_sgmii_port(dev, dp->index) &&
            (interface == PHY_INTERFACE_MODE_SGMII ||
            interface == PHY_INTERFACE_MODE_1000BASEX))
                return p->pcs;

        return NULL;
}

const struct phylink_mac_ops ksz9477_phylink_mac_ops = {
        .mac_config     = ksz_phylink_mac_config,
        .mac_link_down  = ksz_phylink_mac_link_down,
        .mac_link_up    = ksz9477_phylink_mac_link_up,
        .mac_disable_tx_lpi = ksz_phylink_mac_disable_tx_lpi,
        .mac_enable_tx_lpi = ksz_phylink_mac_enable_tx_lpi,
        .mac_select_pcs = ksz9477_phylink_mac_select_pcs,
};

const struct ksz_dev_ops ksz9477_dev_ops = {
        .get_port_addr = ksz9477_get_port_addr,
        .cfg_port_member = ksz9477_cfg_port_member,
        .r_mib_cnt = ksz9477_r_mib_cnt,
        .r_mib_pkt = ksz9477_r_mib_pkt,
        .r_mib_stat64 = ksz_r_mib_stats64,
        .freeze_mib = ksz9477_freeze_mib,
        .port_init_cnt = ksz9477_port_init_cnt,
        .pme_write8 = ksz_write8,
        .pme_pread8 = ksz_pread8,
        .pme_pwrite8 = ksz_pwrite8,
        .tc_cbs_set_cinc = ksz9477_tc_cbs_set_cinc,
        .init = ksz9477_switch_init,
};

const struct dsa_switch_ops ksz9477_switch_ops = {
        .get_tag_protocol       = ksz9477_get_tag_protocol,
        .connect_tag_protocol   = ksz9477_connect_tag_protocol,
        .setup                  = ksz9477_setup,
        .teardown               = ksz_teardown,
        .phy_read               = ksz9477_phy_read16,
        .phy_write              = ksz9477_phy_write16,
        .phylink_get_caps       = ksz9477_phylink_get_caps,
        .port_setup             = ksz9477_dsa_port_setup,
        .set_ageing_time        = ksz9477_set_ageing_time,
        .get_strings            = ksz_get_strings,
        .get_ethtool_stats      = ksz_get_ethtool_stats,
        .get_sset_count         = ksz_sset_count,
        .port_bridge_join       = ksz_port_bridge_join,
        .port_bridge_leave      = ksz_port_bridge_leave,
        .port_hsr_join          = ksz9477_hsr_join,
        .port_hsr_leave         = ksz9477_hsr_leave,
        .port_set_mac_address   = ksz_port_set_mac_address,
        .port_stp_state_set     = ksz_port_stp_state_set,
        .port_teardown          = ksz9477_port_teardown,
        .port_pre_bridge_flags  = ksz_port_pre_bridge_flags,
        .port_bridge_flags      = ksz_port_bridge_flags,
        .port_fast_age          = ksz9477_flush_dyn_mac_table,
        .port_vlan_filtering    = ksz9477_port_vlan_filtering,
        .port_vlan_add          = ksz9477_port_vlan_add,
        .port_vlan_del          = ksz9477_port_vlan_del,
        .port_fdb_dump          = ksz9477_fdb_dump,
        .port_fdb_add           = ksz9477_fdb_add,
        .port_fdb_del           = ksz9477_fdb_del,
        .port_mdb_add           = ksz9477_mdb_add,
        .port_mdb_del           = ksz9477_mdb_del,
        .port_mirror_add        = ksz9477_port_mirror_add,
        .port_mirror_del        = ksz9477_port_mirror_del,
        .get_stats64            = ksz_get_stats64,
        .get_pause_stats        = ksz_get_pause_stats,
        .port_change_mtu        = ksz9477_change_mtu,
        .port_max_mtu           = ksz_max_mtu,
        .get_wol                = ksz_get_wol,
        .set_wol                = ksz_set_wol,
        .suspend                = ksz_suspend,
        .resume                 = ksz_resume,
        .get_ts_info            = ksz_get_ts_info,
        .port_hwtstamp_get      = ksz_hwtstamp_get,
        .port_hwtstamp_set      = ksz_hwtstamp_set,
        .port_txtstamp          = ksz_port_txtstamp,
        .port_rxtstamp          = ksz_port_rxtstamp,
        .cls_flower_add         = ksz9477_cls_flower_add,
        .cls_flower_del         = ksz9477_cls_flower_del,
        .port_setup_tc          = ksz_setup_tc,
        .support_eee            = ksz9477_support_eee,
        .set_mac_eee            = ksz_set_mac_eee,
        .port_get_default_prio  = ksz_port_get_default_prio,
        .port_set_default_prio  = ksz_port_set_default_prio,
        .port_get_dscp_prio     = ksz_port_get_dscp_prio,
        .port_add_dscp_prio     = ksz_port_add_dscp_prio,
        .port_del_dscp_prio     = ksz_port_del_dscp_prio,
        .port_get_apptrust      = ksz_port_get_apptrust,
        .port_set_apptrust      = ksz_port_set_apptrust,
};

MODULE_AUTHOR("Woojung Huh <Woojung.Huh@microchip.com>");
MODULE_DESCRIPTION("Microchip KSZ9477 Series Switch DSA Driver");
MODULE_LICENSE("GPL");