// SPDX-License-Identifier: GPL-2.0-or-later /* * Marvell 88e6xxx Ethernet switch single-chip support * * Copyright (c) 2008 Marvell Semiconductor * * Copyright (c) 2016 Andrew Lunn * * Copyright (c) 2016-2017 Savoir-faire Linux Inc. * Vivien Didelot */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "chip.h" #include "devlink.h" #include "global1.h" #include "global2.h" #include "hwtstamp.h" #include "phy.h" #include "port.h" #include "ptp.h" #include "serdes.h" #include "smi.h" static void assert_reg_lock(struct mv88e6xxx_chip *chip) { if (unlikely(!mutex_is_locked(&chip->reg_lock))) { dev_err(chip->dev, "Switch registers lock not held!\n"); dump_stack(); } } int mv88e6xxx_read(struct mv88e6xxx_chip *chip, int addr, int reg, u16 *val) { int err; assert_reg_lock(chip); err = mv88e6xxx_smi_read(chip, addr, reg, val); if (err) return err; dev_dbg(chip->dev, "<- addr: 0x%.2x reg: 0x%.2x val: 0x%.4x\n", addr, reg, *val); return 0; } int mv88e6xxx_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val) { int err; assert_reg_lock(chip); err = mv88e6xxx_smi_write(chip, addr, reg, val); if (err) return err; dev_dbg(chip->dev, "-> addr: 0x%.2x reg: 0x%.2x val: 0x%.4x\n", addr, reg, val); return 0; } int mv88e6xxx_wait_mask(struct mv88e6xxx_chip *chip, int addr, int reg, u16 mask, u16 val) { const unsigned long timeout = jiffies + msecs_to_jiffies(50); u16 data; int err; int i; /* There's no bus specific operation to wait for a mask. Even * if the initial poll takes longer than 50ms, always do at * least one more attempt. */ for (i = 0; time_before(jiffies, timeout) || (i < 2); i++) { err = mv88e6xxx_read(chip, addr, reg, &data); if (err) return err; if ((data & mask) == val) return 0; if (i < 2) cpu_relax(); else usleep_range(1000, 2000); } err = mv88e6xxx_read(chip, addr, reg, &data); if (err) return err; if ((data & mask) == val) return 0; dev_err(chip->dev, "Timeout while waiting for switch\n"); return -ETIMEDOUT; } int mv88e6xxx_wait_bit(struct mv88e6xxx_chip *chip, int addr, int reg, int bit, int val) { return mv88e6xxx_wait_mask(chip, addr, reg, BIT(bit), val ? BIT(bit) : 0x0000); } struct mii_bus *mv88e6xxx_default_mdio_bus(struct mv88e6xxx_chip *chip) { struct mv88e6xxx_mdio_bus *mdio_bus; mdio_bus = list_first_entry(&chip->mdios, struct mv88e6xxx_mdio_bus, list); if (!mdio_bus) return NULL; return mdio_bus->bus; } static void mv88e6xxx_g1_irq_mask(struct irq_data *d) { struct mv88e6xxx_chip *chip = irq_data_get_irq_chip_data(d); unsigned int n = d->hwirq; chip->g1_irq.masked |= (1 << n); } static void mv88e6xxx_g1_irq_unmask(struct irq_data *d) { struct mv88e6xxx_chip *chip = irq_data_get_irq_chip_data(d); unsigned int n = d->hwirq; chip->g1_irq.masked &= ~(1 << n); } static irqreturn_t mv88e6xxx_g1_irq_thread_work(struct mv88e6xxx_chip *chip) { unsigned int nhandled = 0; unsigned int sub_irq; unsigned int n; u16 reg; u16 ctl1; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_STS, ®); mv88e6xxx_reg_unlock(chip); if (err) goto out; do { for (n = 0; n < chip->g1_irq.nirqs; ++n) { if (reg & (1 << n)) { sub_irq = irq_find_mapping(chip->g1_irq.domain, n); handle_nested_irq(sub_irq); ++nhandled; } } mv88e6xxx_reg_lock(chip); err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_CTL1, &ctl1); if (err) goto unlock; err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_STS, ®); unlock: mv88e6xxx_reg_unlock(chip); if (err) goto out; ctl1 &= GENMASK(chip->g1_irq.nirqs, 0); } while (reg & ctl1); out: return (nhandled > 0 ? IRQ_HANDLED : IRQ_NONE); } static irqreturn_t mv88e6xxx_g1_irq_thread_fn(int irq, void *dev_id) { struct mv88e6xxx_chip *chip = dev_id; return mv88e6xxx_g1_irq_thread_work(chip); } static void mv88e6xxx_g1_irq_bus_lock(struct irq_data *d) { struct mv88e6xxx_chip *chip = irq_data_get_irq_chip_data(d); mv88e6xxx_reg_lock(chip); } static void mv88e6xxx_g1_irq_bus_sync_unlock(struct irq_data *d) { struct mv88e6xxx_chip *chip = irq_data_get_irq_chip_data(d); u16 mask = GENMASK(chip->g1_irq.nirqs, 0); u16 reg; int err; err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_CTL1, ®); if (err) goto out; reg &= ~mask; reg |= (~chip->g1_irq.masked & mask); err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_CTL1, reg); if (err) goto out; out: mv88e6xxx_reg_unlock(chip); } static const struct irq_chip mv88e6xxx_g1_irq_chip = { .name = "mv88e6xxx-g1", .irq_mask = mv88e6xxx_g1_irq_mask, .irq_unmask = mv88e6xxx_g1_irq_unmask, .irq_bus_lock = mv88e6xxx_g1_irq_bus_lock, .irq_bus_sync_unlock = mv88e6xxx_g1_irq_bus_sync_unlock, }; static int mv88e6xxx_g1_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hwirq) { struct mv88e6xxx_chip *chip = d->host_data; irq_set_chip_data(irq, d->host_data); irq_set_chip_and_handler(irq, &chip->g1_irq.chip, handle_level_irq); irq_set_noprobe(irq); return 0; } static const struct irq_domain_ops mv88e6xxx_g1_irq_domain_ops = { .map = mv88e6xxx_g1_irq_domain_map, .xlate = irq_domain_xlate_twocell, }; /* To be called with reg_lock held */ static void mv88e6xxx_g1_irq_free_common(struct mv88e6xxx_chip *chip) { int irq, virq; u16 mask; mv88e6xxx_g1_read(chip, MV88E6XXX_G1_CTL1, &mask); mask &= ~GENMASK(chip->g1_irq.nirqs, 0); mv88e6xxx_g1_write(chip, MV88E6XXX_G1_CTL1, mask); for (irq = 0; irq < chip->g1_irq.nirqs; irq++) { virq = irq_find_mapping(chip->g1_irq.domain, irq); irq_dispose_mapping(virq); } irq_domain_remove(chip->g1_irq.domain); } static void mv88e6xxx_g1_irq_free(struct mv88e6xxx_chip *chip) { /* * free_irq must be called without reg_lock taken because the irq * handler takes this lock, too. */ free_irq(chip->irq, chip); mv88e6xxx_reg_lock(chip); mv88e6xxx_g1_irq_free_common(chip); mv88e6xxx_reg_unlock(chip); } static int mv88e6xxx_g1_irq_setup_common(struct mv88e6xxx_chip *chip) { int err, irq, virq; u16 reg, mask; chip->g1_irq.nirqs = chip->info->g1_irqs; chip->g1_irq.domain = irq_domain_add_simple( NULL, chip->g1_irq.nirqs, 0, &mv88e6xxx_g1_irq_domain_ops, chip); if (!chip->g1_irq.domain) return -ENOMEM; for (irq = 0; irq < chip->g1_irq.nirqs; irq++) irq_create_mapping(chip->g1_irq.domain, irq); chip->g1_irq.chip = mv88e6xxx_g1_irq_chip; chip->g1_irq.masked = ~0; err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_CTL1, &mask); if (err) goto out_mapping; mask &= ~GENMASK(chip->g1_irq.nirqs, 0); err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_CTL1, mask); if (err) goto out_disable; /* Reading the interrupt status clears (most of) them */ err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_STS, ®); if (err) goto out_disable; return 0; out_disable: mask &= ~GENMASK(chip->g1_irq.nirqs, 0); mv88e6xxx_g1_write(chip, MV88E6XXX_G1_CTL1, mask); out_mapping: for (irq = 0; irq < 16; irq++) { virq = irq_find_mapping(chip->g1_irq.domain, irq); irq_dispose_mapping(virq); } irq_domain_remove(chip->g1_irq.domain); return err; } static int mv88e6xxx_g1_irq_setup(struct mv88e6xxx_chip *chip) { static struct lock_class_key lock_key; static struct lock_class_key request_key; int err; err = mv88e6xxx_g1_irq_setup_common(chip); if (err) return err; /* These lock classes tells lockdep that global 1 irqs are in * a different category than their parent GPIO, so it won't * report false recursion. */ irq_set_lockdep_class(chip->irq, &lock_key, &request_key); snprintf(chip->irq_name, sizeof(chip->irq_name), "mv88e6xxx-%s", dev_name(chip->dev)); mv88e6xxx_reg_unlock(chip); err = request_threaded_irq(chip->irq, NULL, mv88e6xxx_g1_irq_thread_fn, IRQF_ONESHOT | IRQF_SHARED, chip->irq_name, chip); mv88e6xxx_reg_lock(chip); if (err) mv88e6xxx_g1_irq_free_common(chip); return err; } static void mv88e6xxx_irq_poll(struct kthread_work *work) { struct mv88e6xxx_chip *chip = container_of(work, struct mv88e6xxx_chip, irq_poll_work.work); mv88e6xxx_g1_irq_thread_work(chip); kthread_queue_delayed_work(chip->kworker, &chip->irq_poll_work, msecs_to_jiffies(100)); } static int mv88e6xxx_irq_poll_setup(struct mv88e6xxx_chip *chip) { int err; err = mv88e6xxx_g1_irq_setup_common(chip); if (err) return err; kthread_init_delayed_work(&chip->irq_poll_work, mv88e6xxx_irq_poll); chip->kworker = kthread_create_worker(0, "%s", dev_name(chip->dev)); if (IS_ERR(chip->kworker)) return PTR_ERR(chip->kworker); kthread_queue_delayed_work(chip->kworker, &chip->irq_poll_work, msecs_to_jiffies(100)); return 0; } static void mv88e6xxx_irq_poll_free(struct mv88e6xxx_chip *chip) { kthread_cancel_delayed_work_sync(&chip->irq_poll_work); kthread_destroy_worker(chip->kworker); mv88e6xxx_reg_lock(chip); mv88e6xxx_g1_irq_free_common(chip); mv88e6xxx_reg_unlock(chip); } static int mv88e6xxx_port_config_interface(struct mv88e6xxx_chip *chip, int port, phy_interface_t interface) { int err; if (chip->info->ops->port_set_rgmii_delay) { err = chip->info->ops->port_set_rgmii_delay(chip, port, interface); if (err && err != -EOPNOTSUPP) return err; } if (chip->info->ops->port_set_cmode) { err = chip->info->ops->port_set_cmode(chip, port, interface); if (err && err != -EOPNOTSUPP) return err; } return 0; } static int mv88e6xxx_port_setup_mac(struct mv88e6xxx_chip *chip, int port, int link, int speed, int duplex, int pause, phy_interface_t mode) { int err; if (!chip->info->ops->port_set_link) return 0; /* Port's MAC control must not be changed unless the link is down */ err = chip->info->ops->port_set_link(chip, port, LINK_FORCED_DOWN); if (err) return err; if (chip->info->ops->port_set_speed_duplex) { err = chip->info->ops->port_set_speed_duplex(chip, port, speed, duplex); if (err && err != -EOPNOTSUPP) goto restore_link; } if (chip->info->ops->port_set_pause) { err = chip->info->ops->port_set_pause(chip, port, pause); if (err) goto restore_link; } err = mv88e6xxx_port_config_interface(chip, port, mode); restore_link: if (chip->info->ops->port_set_link(chip, port, link)) dev_err(chip->dev, "p%d: failed to restore MAC's link\n", port); return err; } static int mv88e6xxx_phy_is_internal(struct dsa_switch *ds, int port) { struct mv88e6xxx_chip *chip = ds->priv; return port < chip->info->num_internal_phys; } static int mv88e6xxx_port_ppu_updates(struct mv88e6xxx_chip *chip, int port) { u16 reg; int err; /* The 88e6250 family does not have the PHY detect bit. Instead, * report whether the port is internal. */ if (chip->info->family == MV88E6XXX_FAMILY_6250) return port < chip->info->num_internal_phys; err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_STS, ®); if (err) { dev_err(chip->dev, "p%d: %s: failed to read port status\n", port, __func__); return err; } return !!(reg & MV88E6XXX_PORT_STS_PHY_DETECT); } static int mv88e6xxx_serdes_pcs_get_state(struct dsa_switch *ds, int port, struct phylink_link_state *state) { struct mv88e6xxx_chip *chip = ds->priv; int lane; int err; mv88e6xxx_reg_lock(chip); lane = mv88e6xxx_serdes_get_lane(chip, port); if (lane >= 0 && chip->info->ops->serdes_pcs_get_state) err = chip->info->ops->serdes_pcs_get_state(chip, port, lane, state); else err = -EOPNOTSUPP; mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_serdes_pcs_config(struct mv88e6xxx_chip *chip, int port, unsigned int mode, phy_interface_t interface, const unsigned long *advertise) { const struct mv88e6xxx_ops *ops = chip->info->ops; int lane; if (ops->serdes_pcs_config) { lane = mv88e6xxx_serdes_get_lane(chip, port); if (lane >= 0) return ops->serdes_pcs_config(chip, port, lane, mode, interface, advertise); } return 0; } static void mv88e6xxx_serdes_pcs_an_restart(struct dsa_switch *ds, int port) { struct mv88e6xxx_chip *chip = ds->priv; const struct mv88e6xxx_ops *ops; int err = 0; int lane; ops = chip->info->ops; if (ops->serdes_pcs_an_restart) { mv88e6xxx_reg_lock(chip); lane = mv88e6xxx_serdes_get_lane(chip, port); if (lane >= 0) err = ops->serdes_pcs_an_restart(chip, port, lane); mv88e6xxx_reg_unlock(chip); if (err) dev_err(ds->dev, "p%d: failed to restart AN\n", port); } } static int mv88e6xxx_serdes_pcs_link_up(struct mv88e6xxx_chip *chip, int port, unsigned int mode, int speed, int duplex) { const struct mv88e6xxx_ops *ops = chip->info->ops; int lane; if (!phylink_autoneg_inband(mode) && ops->serdes_pcs_link_up) { lane = mv88e6xxx_serdes_get_lane(chip, port); if (lane >= 0) return ops->serdes_pcs_link_up(chip, port, lane, speed, duplex); } return 0; } static const u8 mv88e6185_phy_interface_modes[] = { [MV88E6185_PORT_STS_CMODE_GMII_FD] = PHY_INTERFACE_MODE_GMII, [MV88E6185_PORT_STS_CMODE_MII_100_FD_PS] = PHY_INTERFACE_MODE_MII, [MV88E6185_PORT_STS_CMODE_MII_100] = PHY_INTERFACE_MODE_MII, [MV88E6185_PORT_STS_CMODE_MII_10] = PHY_INTERFACE_MODE_MII, [MV88E6185_PORT_STS_CMODE_SERDES] = PHY_INTERFACE_MODE_1000BASEX, [MV88E6185_PORT_STS_CMODE_1000BASE_X] = PHY_INTERFACE_MODE_1000BASEX, [MV88E6185_PORT_STS_CMODE_PHY] = PHY_INTERFACE_MODE_SGMII, }; static void mv88e6095_phylink_get_caps(struct mv88e6xxx_chip *chip, int port, struct phylink_config *config) { u8 cmode = chip->ports[port].cmode; config->mac_capabilities = MAC_SYM_PAUSE | MAC_10 | MAC_100; if (mv88e6xxx_phy_is_internal(chip->ds, port)) { __set_bit(PHY_INTERFACE_MODE_MII, config->supported_interfaces); } else { if (cmode < ARRAY_SIZE(mv88e6185_phy_interface_modes) && mv88e6185_phy_interface_modes[cmode]) __set_bit(mv88e6185_phy_interface_modes[cmode], config->supported_interfaces); config->mac_capabilities |= MAC_1000FD; } } static void mv88e6185_phylink_get_caps(struct mv88e6xxx_chip *chip, int port, struct phylink_config *config) { u8 cmode = chip->ports[port].cmode; if (cmode < ARRAY_SIZE(mv88e6185_phy_interface_modes) && mv88e6185_phy_interface_modes[cmode]) __set_bit(mv88e6185_phy_interface_modes[cmode], config->supported_interfaces); config->mac_capabilities = MAC_SYM_PAUSE | MAC_10 | MAC_100 | MAC_1000FD; } static const u8 mv88e6xxx_phy_interface_modes[] = { [MV88E6XXX_PORT_STS_CMODE_MII_PHY] = PHY_INTERFACE_MODE_MII, [MV88E6XXX_PORT_STS_CMODE_MII] = PHY_INTERFACE_MODE_MII, [MV88E6XXX_PORT_STS_CMODE_GMII] = PHY_INTERFACE_MODE_GMII, [MV88E6XXX_PORT_STS_CMODE_RMII_PHY] = PHY_INTERFACE_MODE_RMII, [MV88E6XXX_PORT_STS_CMODE_RMII] = PHY_INTERFACE_MODE_RMII, [MV88E6XXX_PORT_STS_CMODE_100BASEX] = PHY_INTERFACE_MODE_100BASEX, [MV88E6XXX_PORT_STS_CMODE_1000BASEX] = PHY_INTERFACE_MODE_1000BASEX, [MV88E6XXX_PORT_STS_CMODE_SGMII] = PHY_INTERFACE_MODE_SGMII, /* higher interface modes are not needed here, since ports supporting * them are writable, and so the supported interfaces are filled in the * corresponding .phylink_set_interfaces() implementation below */ }; static void mv88e6xxx_translate_cmode(u8 cmode, unsigned long *supported) { if (cmode < ARRAY_SIZE(mv88e6xxx_phy_interface_modes) && mv88e6xxx_phy_interface_modes[cmode]) __set_bit(mv88e6xxx_phy_interface_modes[cmode], supported); else if (cmode == MV88E6XXX_PORT_STS_CMODE_RGMII) phy_interface_set_rgmii(supported); } static void mv88e6250_phylink_get_caps(struct mv88e6xxx_chip *chip, int port, struct phylink_config *config) { unsigned long *supported = config->supported_interfaces; /* Translate the default cmode */ mv88e6xxx_translate_cmode(chip->ports[port].cmode, supported); config->mac_capabilities = MAC_SYM_PAUSE | MAC_10 | MAC_100; } static int mv88e6352_get_port4_serdes_cmode(struct mv88e6xxx_chip *chip) { u16 reg, val; int err; err = mv88e6xxx_port_read(chip, 4, MV88E6XXX_PORT_STS, ®); if (err) return err; /* If PHY_DETECT is zero, then we are not in auto-media mode */ if (!(reg & MV88E6XXX_PORT_STS_PHY_DETECT)) return 0xf; val = reg & ~MV88E6XXX_PORT_STS_PHY_DETECT; err = mv88e6xxx_port_write(chip, 4, MV88E6XXX_PORT_STS, val); if (err) return err; err = mv88e6xxx_port_read(chip, 4, MV88E6XXX_PORT_STS, &val); if (err) return err; /* Restore PHY_DETECT value */ err = mv88e6xxx_port_write(chip, 4, MV88E6XXX_PORT_STS, reg); if (err) return err; return val & MV88E6XXX_PORT_STS_CMODE_MASK; } static void mv88e6352_phylink_get_caps(struct mv88e6xxx_chip *chip, int port, struct phylink_config *config) { unsigned long *supported = config->supported_interfaces; int err, cmode; /* Translate the default cmode */ mv88e6xxx_translate_cmode(chip->ports[port].cmode, supported); config->mac_capabilities = MAC_SYM_PAUSE | MAC_10 | MAC_100 | MAC_1000FD; /* Port 4 supports automedia if the serdes is associated with it. */ if (port == 4) { err = mv88e6352_g2_scratch_port_has_serdes(chip, port); if (err < 0) dev_err(chip->dev, "p%d: failed to read scratch\n", port); if (err <= 0) return; cmode = mv88e6352_get_port4_serdes_cmode(chip); if (cmode < 0) dev_err(chip->dev, "p%d: failed to read serdes cmode\n", port); else mv88e6xxx_translate_cmode(cmode, supported); } } static void mv88e6341_phylink_get_caps(struct mv88e6xxx_chip *chip, int port, struct phylink_config *config) { unsigned long *supported = config->supported_interfaces; /* Translate the default cmode */ mv88e6xxx_translate_cmode(chip->ports[port].cmode, supported); /* No ethtool bits for 200Mbps */ config->mac_capabilities = MAC_SYM_PAUSE | MAC_10 | MAC_100 | MAC_1000FD; /* The C_Mode field is programmable on port 5 */ if (port == 5) { __set_bit(PHY_INTERFACE_MODE_SGMII, supported); __set_bit(PHY_INTERFACE_MODE_1000BASEX, supported); __set_bit(PHY_INTERFACE_MODE_2500BASEX, supported); config->mac_capabilities |= MAC_2500FD; } } static void mv88e6390_phylink_get_caps(struct mv88e6xxx_chip *chip, int port, struct phylink_config *config) { unsigned long *supported = config->supported_interfaces; /* Translate the default cmode */ mv88e6xxx_translate_cmode(chip->ports[port].cmode, supported); /* No ethtool bits for 200Mbps */ config->mac_capabilities = MAC_SYM_PAUSE | MAC_10 | MAC_100 | MAC_1000FD; /* The C_Mode field is programmable on ports 9 and 10 */ if (port == 9 || port == 10) { __set_bit(PHY_INTERFACE_MODE_SGMII, supported); __set_bit(PHY_INTERFACE_MODE_1000BASEX, supported); __set_bit(PHY_INTERFACE_MODE_2500BASEX, supported); config->mac_capabilities |= MAC_2500FD; } } static void mv88e6390x_phylink_get_caps(struct mv88e6xxx_chip *chip, int port, struct phylink_config *config) { unsigned long *supported = config->supported_interfaces; mv88e6390_phylink_get_caps(chip, port, config); /* For the 6x90X, ports 2-7 can be in automedia mode. * (Note that 6x90 doesn't support RXAUI nor XAUI). * * Port 2 can also support 1000BASE-X in automedia mode if port 9 is * configured for 1000BASE-X, SGMII or 2500BASE-X. * Port 3-4 can also support 1000BASE-X in automedia mode if port 9 is * configured for RXAUI, 1000BASE-X, SGMII or 2500BASE-X. * * Port 5 can also support 1000BASE-X in automedia mode if port 10 is * configured for 1000BASE-X, SGMII or 2500BASE-X. * Port 6-7 can also support 1000BASE-X in automedia mode if port 10 is * configured for RXAUI, 1000BASE-X, SGMII or 2500BASE-X. * * For now, be permissive (as the old code was) and allow 1000BASE-X * on ports 2..7. */ if (port >= 2 && port <= 7) __set_bit(PHY_INTERFACE_MODE_1000BASEX, supported); /* The C_Mode field can also be programmed for 10G speeds */ if (port == 9 || port == 10) { __set_bit(PHY_INTERFACE_MODE_XAUI, supported); __set_bit(PHY_INTERFACE_MODE_RXAUI, supported); config->mac_capabilities |= MAC_10000FD; } } static void mv88e6393x_phylink_get_caps(struct mv88e6xxx_chip *chip, int port, struct phylink_config *config) { unsigned long *supported = config->supported_interfaces; bool is_6191x = chip->info->prod_num == MV88E6XXX_PORT_SWITCH_ID_PROD_6191X; mv88e6xxx_translate_cmode(chip->ports[port].cmode, supported); config->mac_capabilities = MAC_SYM_PAUSE | MAC_10 | MAC_100 | MAC_1000FD; /* The C_Mode field can be programmed for ports 0, 9 and 10 */ if (port == 0 || port == 9 || port == 10) { __set_bit(PHY_INTERFACE_MODE_SGMII, supported); __set_bit(PHY_INTERFACE_MODE_1000BASEX, supported); /* 6191X supports >1G modes only on port 10 */ if (!is_6191x || port == 10) { __set_bit(PHY_INTERFACE_MODE_2500BASEX, supported); __set_bit(PHY_INTERFACE_MODE_5GBASER, supported); __set_bit(PHY_INTERFACE_MODE_10GBASER, supported); /* FIXME: USXGMII is not supported yet */ /* __set_bit(PHY_INTERFACE_MODE_USXGMII, supported); */ config->mac_capabilities |= MAC_2500FD | MAC_5000FD | MAC_10000FD; } } if (port == 0) { __set_bit(PHY_INTERFACE_MODE_RMII, supported); __set_bit(PHY_INTERFACE_MODE_RGMII, supported); __set_bit(PHY_INTERFACE_MODE_RGMII_ID, supported); __set_bit(PHY_INTERFACE_MODE_RGMII_RXID, supported); __set_bit(PHY_INTERFACE_MODE_RGMII_TXID, supported); } } static void mv88e6xxx_get_caps(struct dsa_switch *ds, int port, struct phylink_config *config) { struct mv88e6xxx_chip *chip = ds->priv; mv88e6xxx_reg_lock(chip); chip->info->ops->phylink_get_caps(chip, port, config); mv88e6xxx_reg_unlock(chip); if (mv88e6xxx_phy_is_internal(ds, port)) { __set_bit(PHY_INTERFACE_MODE_INTERNAL, config->supported_interfaces); /* Internal ports with no phy-mode need GMII for PHYLIB */ __set_bit(PHY_INTERFACE_MODE_GMII, config->supported_interfaces); } } static void mv88e6xxx_mac_config(struct dsa_switch *ds, int port, unsigned int mode, const struct phylink_link_state *state) { struct mv88e6xxx_chip *chip = ds->priv; struct mv88e6xxx_port *p; int err = 0; p = &chip->ports[port]; mv88e6xxx_reg_lock(chip); if (mode != MLO_AN_PHY || !mv88e6xxx_phy_is_internal(ds, port)) { /* In inband mode, the link may come up at any time while the * link is not forced down. Force the link down while we * reconfigure the interface mode. */ if (mode == MLO_AN_INBAND && p->interface != state->interface && chip->info->ops->port_set_link) chip->info->ops->port_set_link(chip, port, LINK_FORCED_DOWN); err = mv88e6xxx_port_config_interface(chip, port, state->interface); if (err && err != -EOPNOTSUPP) goto err_unlock; err = mv88e6xxx_serdes_pcs_config(chip, port, mode, state->interface, state->advertising); /* FIXME: we should restart negotiation if something changed - * which is something we get if we convert to using phylinks * PCS operations. */ if (err > 0) err = 0; } /* Undo the forced down state above after completing configuration * irrespective of its state on entry, which allows the link to come * up in the in-band case where there is no separate SERDES. Also * ensure that the link can come up if the PPU is in use and we are * in PHY mode (we treat the PPU as an effective in-band mechanism.) */ if (chip->info->ops->port_set_link && ((mode == MLO_AN_INBAND && p->interface != state->interface) || (mode == MLO_AN_PHY && mv88e6xxx_port_ppu_updates(chip, port)))) chip->info->ops->port_set_link(chip, port, LINK_UNFORCED); p->interface = state->interface; err_unlock: mv88e6xxx_reg_unlock(chip); if (err && err != -EOPNOTSUPP) dev_err(ds->dev, "p%d: failed to configure MAC/PCS\n", port); } static void mv88e6xxx_mac_link_down(struct dsa_switch *ds, int port, unsigned int mode, phy_interface_t interface) { struct mv88e6xxx_chip *chip = ds->priv; const struct mv88e6xxx_ops *ops; int err = 0; ops = chip->info->ops; mv88e6xxx_reg_lock(chip); /* Force the link down if we know the port may not be automatically * updated by the switch or if we are using fixed-link mode. */ if ((!mv88e6xxx_port_ppu_updates(chip, port) || mode == MLO_AN_FIXED) && ops->port_sync_link) err = ops->port_sync_link(chip, port, mode, false); if (!err && ops->port_set_speed_duplex) err = ops->port_set_speed_duplex(chip, port, SPEED_UNFORCED, DUPLEX_UNFORCED); mv88e6xxx_reg_unlock(chip); if (err) dev_err(chip->dev, "p%d: failed to force MAC link down\n", port); } static void mv88e6xxx_mac_link_up(struct dsa_switch *ds, int port, unsigned int mode, phy_interface_t interface, struct phy_device *phydev, int speed, int duplex, bool tx_pause, bool rx_pause) { struct mv88e6xxx_chip *chip = ds->priv; const struct mv88e6xxx_ops *ops; int err = 0; ops = chip->info->ops; mv88e6xxx_reg_lock(chip); /* Configure and force the link up if we know that the port may not * automatically updated by the switch or if we are using fixed-link * mode. */ if (!mv88e6xxx_port_ppu_updates(chip, port) || mode == MLO_AN_FIXED) { /* FIXME: for an automedia port, should we force the link * down here - what if the link comes up due to "other" media * while we're bringing the port up, how is the exclusivity * handled in the Marvell hardware? E.g. port 2 on 88E6390 * shared between internal PHY and Serdes. */ err = mv88e6xxx_serdes_pcs_link_up(chip, port, mode, speed, duplex); if (err) goto error; if (ops->port_set_speed_duplex) { err = ops->port_set_speed_duplex(chip, port, speed, duplex); if (err && err != -EOPNOTSUPP) goto error; } if (ops->port_sync_link) err = ops->port_sync_link(chip, port, mode, true); } error: mv88e6xxx_reg_unlock(chip); if (err && err != -EOPNOTSUPP) dev_err(ds->dev, "p%d: failed to configure MAC link up\n", port); } static int mv88e6xxx_stats_snapshot(struct mv88e6xxx_chip *chip, int port) { if (!chip->info->ops->stats_snapshot) return -EOPNOTSUPP; return chip->info->ops->stats_snapshot(chip, port); } static struct mv88e6xxx_hw_stat mv88e6xxx_hw_stats[] = { { "in_good_octets", 8, 0x00, STATS_TYPE_BANK0, }, { "in_bad_octets", 4, 0x02, STATS_TYPE_BANK0, }, { "in_unicast", 4, 0x04, STATS_TYPE_BANK0, }, { "in_broadcasts", 4, 0x06, STATS_TYPE_BANK0, }, { "in_multicasts", 4, 0x07, STATS_TYPE_BANK0, }, { "in_pause", 4, 0x16, STATS_TYPE_BANK0, }, { "in_undersize", 4, 0x18, STATS_TYPE_BANK0, }, { "in_fragments", 4, 0x19, STATS_TYPE_BANK0, }, { "in_oversize", 4, 0x1a, STATS_TYPE_BANK0, }, { "in_jabber", 4, 0x1b, STATS_TYPE_BANK0, }, { "in_rx_error", 4, 0x1c, STATS_TYPE_BANK0, }, { "in_fcs_error", 4, 0x1d, STATS_TYPE_BANK0, }, { "out_octets", 8, 0x0e, STATS_TYPE_BANK0, }, { "out_unicast", 4, 0x10, STATS_TYPE_BANK0, }, { "out_broadcasts", 4, 0x13, STATS_TYPE_BANK0, }, { "out_multicasts", 4, 0x12, STATS_TYPE_BANK0, }, { "out_pause", 4, 0x15, STATS_TYPE_BANK0, }, { "excessive", 4, 0x11, STATS_TYPE_BANK0, }, { "collisions", 4, 0x1e, STATS_TYPE_BANK0, }, { "deferred", 4, 0x05, STATS_TYPE_BANK0, }, { "single", 4, 0x14, STATS_TYPE_BANK0, }, { "multiple", 4, 0x17, STATS_TYPE_BANK0, }, { "out_fcs_error", 4, 0x03, STATS_TYPE_BANK0, }, { "late", 4, 0x1f, STATS_TYPE_BANK0, }, { "hist_64bytes", 4, 0x08, STATS_TYPE_BANK0, }, { "hist_65_127bytes", 4, 0x09, STATS_TYPE_BANK0, }, { "hist_128_255bytes", 4, 0x0a, STATS_TYPE_BANK0, }, { "hist_256_511bytes", 4, 0x0b, STATS_TYPE_BANK0, }, { "hist_512_1023bytes", 4, 0x0c, STATS_TYPE_BANK0, }, { "hist_1024_max_bytes", 4, 0x0d, STATS_TYPE_BANK0, }, { "sw_in_discards", 4, 0x10, STATS_TYPE_PORT, }, { "sw_in_filtered", 2, 0x12, STATS_TYPE_PORT, }, { "sw_out_filtered", 2, 0x13, STATS_TYPE_PORT, }, { "in_discards", 4, 0x00, STATS_TYPE_BANK1, }, { "in_filtered", 4, 0x01, STATS_TYPE_BANK1, }, { "in_accepted", 4, 0x02, STATS_TYPE_BANK1, }, { "in_bad_accepted", 4, 0x03, STATS_TYPE_BANK1, }, { "in_good_avb_class_a", 4, 0x04, STATS_TYPE_BANK1, }, { "in_good_avb_class_b", 4, 0x05, STATS_TYPE_BANK1, }, { "in_bad_avb_class_a", 4, 0x06, STATS_TYPE_BANK1, }, { "in_bad_avb_class_b", 4, 0x07, STATS_TYPE_BANK1, }, { "tcam_counter_0", 4, 0x08, STATS_TYPE_BANK1, }, { "tcam_counter_1", 4, 0x09, STATS_TYPE_BANK1, }, { "tcam_counter_2", 4, 0x0a, STATS_TYPE_BANK1, }, { "tcam_counter_3", 4, 0x0b, STATS_TYPE_BANK1, }, { "in_da_unknown", 4, 0x0e, STATS_TYPE_BANK1, }, { "in_management", 4, 0x0f, STATS_TYPE_BANK1, }, { "out_queue_0", 4, 0x10, STATS_TYPE_BANK1, }, { "out_queue_1", 4, 0x11, STATS_TYPE_BANK1, }, { "out_queue_2", 4, 0x12, STATS_TYPE_BANK1, }, { "out_queue_3", 4, 0x13, STATS_TYPE_BANK1, }, { "out_queue_4", 4, 0x14, STATS_TYPE_BANK1, }, { "out_queue_5", 4, 0x15, STATS_TYPE_BANK1, }, { "out_queue_6", 4, 0x16, STATS_TYPE_BANK1, }, { "out_queue_7", 4, 0x17, STATS_TYPE_BANK1, }, { "out_cut_through", 4, 0x18, STATS_TYPE_BANK1, }, { "out_octets_a", 4, 0x1a, STATS_TYPE_BANK1, }, { "out_octets_b", 4, 0x1b, STATS_TYPE_BANK1, }, { "out_management", 4, 0x1f, STATS_TYPE_BANK1, }, }; static uint64_t _mv88e6xxx_get_ethtool_stat(struct mv88e6xxx_chip *chip, struct mv88e6xxx_hw_stat *s, int port, u16 bank1_select, u16 histogram) { u32 low; u32 high = 0; u16 reg = 0; int err; u64 value; switch (s->type) { case STATS_TYPE_PORT: err = mv88e6xxx_port_read(chip, port, s->reg, ®); if (err) return U64_MAX; low = reg; if (s->size == 4) { err = mv88e6xxx_port_read(chip, port, s->reg + 1, ®); if (err) return U64_MAX; low |= ((u32)reg) << 16; } break; case STATS_TYPE_BANK1: reg = bank1_select; fallthrough; case STATS_TYPE_BANK0: reg |= s->reg | histogram; mv88e6xxx_g1_stats_read(chip, reg, &low); if (s->size == 8) mv88e6xxx_g1_stats_read(chip, reg + 1, &high); break; default: return U64_MAX; } value = (((u64)high) << 32) | low; return value; } static int mv88e6xxx_stats_get_strings(struct mv88e6xxx_chip *chip, uint8_t *data, int types) { struct mv88e6xxx_hw_stat *stat; int i, j; for (i = 0, j = 0; i < ARRAY_SIZE(mv88e6xxx_hw_stats); i++) { stat = &mv88e6xxx_hw_stats[i]; if (stat->type & types) { memcpy(data + j * ETH_GSTRING_LEN, stat->string, ETH_GSTRING_LEN); j++; } } return j; } static int mv88e6095_stats_get_strings(struct mv88e6xxx_chip *chip, uint8_t *data) { return mv88e6xxx_stats_get_strings(chip, data, STATS_TYPE_BANK0 | STATS_TYPE_PORT); } static int mv88e6250_stats_get_strings(struct mv88e6xxx_chip *chip, uint8_t *data) { return mv88e6xxx_stats_get_strings(chip, data, STATS_TYPE_BANK0); } static int mv88e6320_stats_get_strings(struct mv88e6xxx_chip *chip, uint8_t *data) { return mv88e6xxx_stats_get_strings(chip, data, STATS_TYPE_BANK0 | STATS_TYPE_BANK1); } static const uint8_t *mv88e6xxx_atu_vtu_stats_strings[] = { "atu_member_violation", "atu_miss_violation", "atu_full_violation", "vtu_member_violation", "vtu_miss_violation", }; static void mv88e6xxx_atu_vtu_get_strings(uint8_t *data) { unsigned int i; for (i = 0; i < ARRAY_SIZE(mv88e6xxx_atu_vtu_stats_strings); i++) strscpy(data + i * ETH_GSTRING_LEN, mv88e6xxx_atu_vtu_stats_strings[i], ETH_GSTRING_LEN); } static void mv88e6xxx_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data) { struct mv88e6xxx_chip *chip = ds->priv; int count = 0; if (stringset != ETH_SS_STATS) return; mv88e6xxx_reg_lock(chip); if (chip->info->ops->stats_get_strings) count = chip->info->ops->stats_get_strings(chip, data); if (chip->info->ops->serdes_get_strings) { data += count * ETH_GSTRING_LEN; count = chip->info->ops->serdes_get_strings(chip, port, data); } data += count * ETH_GSTRING_LEN; mv88e6xxx_atu_vtu_get_strings(data); mv88e6xxx_reg_unlock(chip); } static int mv88e6xxx_stats_get_sset_count(struct mv88e6xxx_chip *chip, int types) { struct mv88e6xxx_hw_stat *stat; int i, j; for (i = 0, j = 0; i < ARRAY_SIZE(mv88e6xxx_hw_stats); i++) { stat = &mv88e6xxx_hw_stats[i]; if (stat->type & types) j++; } return j; } static int mv88e6095_stats_get_sset_count(struct mv88e6xxx_chip *chip) { return mv88e6xxx_stats_get_sset_count(chip, STATS_TYPE_BANK0 | STATS_TYPE_PORT); } static int mv88e6250_stats_get_sset_count(struct mv88e6xxx_chip *chip) { return mv88e6xxx_stats_get_sset_count(chip, STATS_TYPE_BANK0); } static int mv88e6320_stats_get_sset_count(struct mv88e6xxx_chip *chip) { return mv88e6xxx_stats_get_sset_count(chip, STATS_TYPE_BANK0 | STATS_TYPE_BANK1); } static int mv88e6xxx_get_sset_count(struct dsa_switch *ds, int port, int sset) { struct mv88e6xxx_chip *chip = ds->priv; int serdes_count = 0; int count = 0; if (sset != ETH_SS_STATS) return 0; mv88e6xxx_reg_lock(chip); if (chip->info->ops->stats_get_sset_count) count = chip->info->ops->stats_get_sset_count(chip); if (count < 0) goto out; if (chip->info->ops->serdes_get_sset_count) serdes_count = chip->info->ops->serdes_get_sset_count(chip, port); if (serdes_count < 0) { count = serdes_count; goto out; } count += serdes_count; count += ARRAY_SIZE(mv88e6xxx_atu_vtu_stats_strings); out: mv88e6xxx_reg_unlock(chip); return count; } static int mv88e6xxx_stats_get_stats(struct mv88e6xxx_chip *chip, int port, uint64_t *data, int types, u16 bank1_select, u16 histogram) { struct mv88e6xxx_hw_stat *stat; int i, j; for (i = 0, j = 0; i < ARRAY_SIZE(mv88e6xxx_hw_stats); i++) { stat = &mv88e6xxx_hw_stats[i]; if (stat->type & types) { mv88e6xxx_reg_lock(chip); data[j] = _mv88e6xxx_get_ethtool_stat(chip, stat, port, bank1_select, histogram); mv88e6xxx_reg_unlock(chip); j++; } } return j; } static int mv88e6095_stats_get_stats(struct mv88e6xxx_chip *chip, int port, uint64_t *data) { return mv88e6xxx_stats_get_stats(chip, port, data, STATS_TYPE_BANK0 | STATS_TYPE_PORT, 0, MV88E6XXX_G1_STATS_OP_HIST_RX_TX); } static int mv88e6250_stats_get_stats(struct mv88e6xxx_chip *chip, int port, uint64_t *data) { return mv88e6xxx_stats_get_stats(chip, port, data, STATS_TYPE_BANK0, 0, MV88E6XXX_G1_STATS_OP_HIST_RX_TX); } static int mv88e6320_stats_get_stats(struct mv88e6xxx_chip *chip, int port, uint64_t *data) { return mv88e6xxx_stats_get_stats(chip, port, data, STATS_TYPE_BANK0 | STATS_TYPE_BANK1, MV88E6XXX_G1_STATS_OP_BANK_1_BIT_9, MV88E6XXX_G1_STATS_OP_HIST_RX_TX); } static int mv88e6390_stats_get_stats(struct mv88e6xxx_chip *chip, int port, uint64_t *data) { return mv88e6xxx_stats_get_stats(chip, port, data, STATS_TYPE_BANK0 | STATS_TYPE_BANK1, MV88E6XXX_G1_STATS_OP_BANK_1_BIT_10, 0); } static void mv88e6xxx_atu_vtu_get_stats(struct mv88e6xxx_chip *chip, int port, uint64_t *data) { *data++ = chip->ports[port].atu_member_violation; *data++ = chip->ports[port].atu_miss_violation; *data++ = chip->ports[port].atu_full_violation; *data++ = chip->ports[port].vtu_member_violation; *data++ = chip->ports[port].vtu_miss_violation; } static void mv88e6xxx_get_stats(struct mv88e6xxx_chip *chip, int port, uint64_t *data) { int count = 0; if (chip->info->ops->stats_get_stats) count = chip->info->ops->stats_get_stats(chip, port, data); mv88e6xxx_reg_lock(chip); if (chip->info->ops->serdes_get_stats) { data += count; count = chip->info->ops->serdes_get_stats(chip, port, data); } data += count; mv88e6xxx_atu_vtu_get_stats(chip, port, data); mv88e6xxx_reg_unlock(chip); } static void mv88e6xxx_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data) { struct mv88e6xxx_chip *chip = ds->priv; int ret; mv88e6xxx_reg_lock(chip); ret = mv88e6xxx_stats_snapshot(chip, port); mv88e6xxx_reg_unlock(chip); if (ret < 0) return; mv88e6xxx_get_stats(chip, port, data); } static int mv88e6xxx_get_regs_len(struct dsa_switch *ds, int port) { struct mv88e6xxx_chip *chip = ds->priv; int len; len = 32 * sizeof(u16); if (chip->info->ops->serdes_get_regs_len) len += chip->info->ops->serdes_get_regs_len(chip, port); return len; } static void mv88e6xxx_get_regs(struct dsa_switch *ds, int port, struct ethtool_regs *regs, void *_p) { struct mv88e6xxx_chip *chip = ds->priv; int err; u16 reg; u16 *p = _p; int i; regs->version = chip->info->prod_num; memset(p, 0xff, 32 * sizeof(u16)); mv88e6xxx_reg_lock(chip); for (i = 0; i < 32; i++) { err = mv88e6xxx_port_read(chip, port, i, ®); if (!err) p[i] = reg; } if (chip->info->ops->serdes_get_regs) chip->info->ops->serdes_get_regs(chip, port, &p[i]); mv88e6xxx_reg_unlock(chip); } static int mv88e6xxx_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e) { /* Nothing to do on the port's MAC */ return 0; } static int mv88e6xxx_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e) { /* Nothing to do on the port's MAC */ return 0; } /* Mask of the local ports allowed to receive frames from a given fabric port */ static u16 mv88e6xxx_port_vlan(struct mv88e6xxx_chip *chip, int dev, int port) { struct dsa_switch *ds = chip->ds; struct dsa_switch_tree *dst = ds->dst; struct dsa_port *dp, *other_dp; bool found = false; u16 pvlan; /* dev is a physical switch */ if (dev <= dst->last_switch) { list_for_each_entry(dp, &dst->ports, list) { if (dp->ds->index == dev && dp->index == port) { /* dp might be a DSA link or a user port, so it * might or might not have a bridge. * Use the "found" variable for both cases. */ found = true; break; } } /* dev is a virtual bridge */ } else { list_for_each_entry(dp, &dst->ports, list) { unsigned int bridge_num = dsa_port_bridge_num_get(dp); if (!bridge_num) continue; if (bridge_num + dst->last_switch != dev) continue; found = true; break; } } /* Prevent frames from unknown switch or virtual bridge */ if (!found) return 0; /* Frames from DSA links and CPU ports can egress any local port */ if (dp->type == DSA_PORT_TYPE_CPU || dp->type == DSA_PORT_TYPE_DSA) return mv88e6xxx_port_mask(chip); pvlan = 0; /* Frames from standalone user ports can only egress on the * upstream port. */ if (!dsa_port_bridge_dev_get(dp)) return BIT(dsa_switch_upstream_port(ds)); /* Frames from bridged user ports can egress any local DSA * links and CPU ports, as well as any local member of their * bridge group. */ dsa_switch_for_each_port(other_dp, ds) if (other_dp->type == DSA_PORT_TYPE_CPU || other_dp->type == DSA_PORT_TYPE_DSA || dsa_port_bridge_same(dp, other_dp)) pvlan |= BIT(other_dp->index); return pvlan; } static int mv88e6xxx_port_vlan_map(struct mv88e6xxx_chip *chip, int port) { u16 output_ports = mv88e6xxx_port_vlan(chip, chip->ds->index, port); /* prevent frames from going back out of the port they came in on */ output_ports &= ~BIT(port); return mv88e6xxx_port_set_vlan_map(chip, port, output_ports); } static void mv88e6xxx_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) { struct mv88e6xxx_chip *chip = ds->priv; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_set_state(chip, port, state); mv88e6xxx_reg_unlock(chip); if (err) dev_err(ds->dev, "p%d: failed to update state\n", port); } static int mv88e6xxx_pri_setup(struct mv88e6xxx_chip *chip) { int err; if (chip->info->ops->ieee_pri_map) { err = chip->info->ops->ieee_pri_map(chip); if (err) return err; } if (chip->info->ops->ip_pri_map) { err = chip->info->ops->ip_pri_map(chip); if (err) return err; } return 0; } static int mv88e6xxx_devmap_setup(struct mv88e6xxx_chip *chip) { struct dsa_switch *ds = chip->ds; int target, port; int err; if (!chip->info->global2_addr) return 0; /* Initialize the routing port to the 32 possible target devices */ for (target = 0; target < 32; target++) { port = dsa_routing_port(ds, target); if (port == ds->num_ports) port = 0x1f; err = mv88e6xxx_g2_device_mapping_write(chip, target, port); if (err) return err; } if (chip->info->ops->set_cascade_port) { port = MV88E6XXX_CASCADE_PORT_MULTIPLE; err = chip->info->ops->set_cascade_port(chip, port); if (err) return err; } err = mv88e6xxx_g1_set_device_number(chip, chip->ds->index); if (err) return err; return 0; } static int mv88e6xxx_trunk_setup(struct mv88e6xxx_chip *chip) { /* Clear all trunk masks and mapping */ if (chip->info->global2_addr) return mv88e6xxx_g2_trunk_clear(chip); return 0; } static int mv88e6xxx_rmu_setup(struct mv88e6xxx_chip *chip) { if (chip->info->ops->rmu_disable) return chip->info->ops->rmu_disable(chip); return 0; } static int mv88e6xxx_pot_setup(struct mv88e6xxx_chip *chip) { if (chip->info->ops->pot_clear) return chip->info->ops->pot_clear(chip); return 0; } static int mv88e6xxx_rsvd2cpu_setup(struct mv88e6xxx_chip *chip) { if (chip->info->ops->mgmt_rsvd2cpu) return chip->info->ops->mgmt_rsvd2cpu(chip); return 0; } static int mv88e6xxx_atu_setup(struct mv88e6xxx_chip *chip) { int err; err = mv88e6xxx_g1_atu_flush(chip, 0, true); if (err) return err; /* The chips that have a "learn2all" bit in Global1, ATU * Control are precisely those whose port registers have a * Message Port bit in Port Control 1 and hence implement * ->port_setup_message_port. */ if (chip->info->ops->port_setup_message_port) { err = mv88e6xxx_g1_atu_set_learn2all(chip, true); if (err) return err; } return mv88e6xxx_g1_atu_set_age_time(chip, 300000); } static int mv88e6xxx_irl_setup(struct mv88e6xxx_chip *chip) { int port; int err; if (!chip->info->ops->irl_init_all) return 0; for (port = 0; port < mv88e6xxx_num_ports(chip); port++) { /* Disable ingress rate limiting by resetting all per port * ingress rate limit resources to their initial state. */ err = chip->info->ops->irl_init_all(chip, port); if (err) return err; } return 0; } static int mv88e6xxx_mac_setup(struct mv88e6xxx_chip *chip) { if (chip->info->ops->set_switch_mac) { u8 addr[ETH_ALEN]; eth_random_addr(addr); return chip->info->ops->set_switch_mac(chip, addr); } return 0; } static int mv88e6xxx_pvt_map(struct mv88e6xxx_chip *chip, int dev, int port) { struct dsa_switch_tree *dst = chip->ds->dst; struct dsa_switch *ds; struct dsa_port *dp; u16 pvlan = 0; if (!mv88e6xxx_has_pvt(chip)) return 0; /* Skip the local source device, which uses in-chip port VLAN */ if (dev != chip->ds->index) { pvlan = mv88e6xxx_port_vlan(chip, dev, port); ds = dsa_switch_find(dst->index, dev); dp = ds ? dsa_to_port(ds, port) : NULL; if (dp && dp->lag) { /* As the PVT is used to limit flooding of * FORWARD frames, which use the LAG ID as the * source port, we must translate dev/port to * the special "LAG device" in the PVT, using * the LAG ID (one-based) as the port number * (zero-based). */ dev = MV88E6XXX_G2_PVT_ADDR_DEV_TRUNK; port = dsa_port_lag_id_get(dp) - 1; } } return mv88e6xxx_g2_pvt_write(chip, dev, port, pvlan); } static int mv88e6xxx_pvt_setup(struct mv88e6xxx_chip *chip) { int dev, port; int err; if (!mv88e6xxx_has_pvt(chip)) return 0; /* Clear 5 Bit Port for usage with Marvell Link Street devices: * use 4 bits for the Src_Port/Src_Trunk and 5 bits for the Src_Dev. */ err = mv88e6xxx_g2_misc_4_bit_port(chip); if (err) return err; for (dev = 0; dev < MV88E6XXX_MAX_PVT_SWITCHES; ++dev) { for (port = 0; port < MV88E6XXX_MAX_PVT_PORTS; ++port) { err = mv88e6xxx_pvt_map(chip, dev, port); if (err) return err; } } return 0; } static int mv88e6xxx_port_fast_age_fid(struct mv88e6xxx_chip *chip, int port, u16 fid) { if (dsa_to_port(chip->ds, port)->lag) /* Hardware is incapable of fast-aging a LAG through a * regular ATU move operation. Until we have something * more fancy in place this is a no-op. */ return -EOPNOTSUPP; return mv88e6xxx_g1_atu_remove(chip, fid, port, false); } static void mv88e6xxx_port_fast_age(struct dsa_switch *ds, int port) { struct mv88e6xxx_chip *chip = ds->priv; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_fast_age_fid(chip, port, 0); mv88e6xxx_reg_unlock(chip); if (err) dev_err(chip->ds->dev, "p%d: failed to flush ATU: %d\n", port, err); } static int mv88e6xxx_vtu_setup(struct mv88e6xxx_chip *chip) { if (!mv88e6xxx_max_vid(chip)) return 0; return mv88e6xxx_g1_vtu_flush(chip); } static int mv88e6xxx_vtu_get(struct mv88e6xxx_chip *chip, u16 vid, struct mv88e6xxx_vtu_entry *entry) { int err; if (!chip->info->ops->vtu_getnext) return -EOPNOTSUPP; entry->vid = vid ? vid - 1 : mv88e6xxx_max_vid(chip); entry->valid = false; err = chip->info->ops->vtu_getnext(chip, entry); if (entry->vid != vid) entry->valid = false; return err; } static int mv88e6xxx_vtu_walk(struct mv88e6xxx_chip *chip, int (*cb)(struct mv88e6xxx_chip *chip, const struct mv88e6xxx_vtu_entry *entry, void *priv), void *priv) { struct mv88e6xxx_vtu_entry entry = { .vid = mv88e6xxx_max_vid(chip), .valid = false, }; int err; if (!chip->info->ops->vtu_getnext) return -EOPNOTSUPP; do { err = chip->info->ops->vtu_getnext(chip, &entry); if (err) return err; if (!entry.valid) break; err = cb(chip, &entry, priv); if (err) return err; } while (entry.vid < mv88e6xxx_max_vid(chip)); return 0; } static int mv88e6xxx_vtu_loadpurge(struct mv88e6xxx_chip *chip, struct mv88e6xxx_vtu_entry *entry) { if (!chip->info->ops->vtu_loadpurge) return -EOPNOTSUPP; return chip->info->ops->vtu_loadpurge(chip, entry); } static int mv88e6xxx_fid_map_vlan(struct mv88e6xxx_chip *chip, const struct mv88e6xxx_vtu_entry *entry, void *_fid_bitmap) { unsigned long *fid_bitmap = _fid_bitmap; set_bit(entry->fid, fid_bitmap); return 0; } int mv88e6xxx_fid_map(struct mv88e6xxx_chip *chip, unsigned long *fid_bitmap) { bitmap_zero(fid_bitmap, MV88E6XXX_N_FID); /* Every FID has an associated VID, so walking the VTU * will discover the full set of FIDs in use. */ return mv88e6xxx_vtu_walk(chip, mv88e6xxx_fid_map_vlan, fid_bitmap); } static int mv88e6xxx_atu_new(struct mv88e6xxx_chip *chip, u16 *fid) { DECLARE_BITMAP(fid_bitmap, MV88E6XXX_N_FID); int err; err = mv88e6xxx_fid_map(chip, fid_bitmap); if (err) return err; *fid = find_first_zero_bit(fid_bitmap, MV88E6XXX_N_FID); if (unlikely(*fid >= mv88e6xxx_num_databases(chip))) return -ENOSPC; /* Clear the database */ return mv88e6xxx_g1_atu_flush(chip, *fid, true); } static int mv88e6xxx_stu_loadpurge(struct mv88e6xxx_chip *chip, struct mv88e6xxx_stu_entry *entry) { if (!chip->info->ops->stu_loadpurge) return -EOPNOTSUPP; return chip->info->ops->stu_loadpurge(chip, entry); } static int mv88e6xxx_stu_setup(struct mv88e6xxx_chip *chip) { struct mv88e6xxx_stu_entry stu = { .valid = true, .sid = 0 }; if (!mv88e6xxx_has_stu(chip)) return 0; /* Make sure that SID 0 is always valid. This is used by VTU * entries that do not make use of the STU, e.g. when creating * a VLAN upper on a port that is also part of a VLAN * filtering bridge. */ return mv88e6xxx_stu_loadpurge(chip, &stu); } static int mv88e6xxx_sid_get(struct mv88e6xxx_chip *chip, u8 *sid) { DECLARE_BITMAP(busy, MV88E6XXX_N_SID) = { 0 }; struct mv88e6xxx_mst *mst; __set_bit(0, busy); list_for_each_entry(mst, &chip->msts, node) __set_bit(mst->stu.sid, busy); *sid = find_first_zero_bit(busy, MV88E6XXX_N_SID); return (*sid >= mv88e6xxx_max_sid(chip)) ? -ENOSPC : 0; } static int mv88e6xxx_mst_put(struct mv88e6xxx_chip *chip, u8 sid) { struct mv88e6xxx_mst *mst, *tmp; int err; if (!sid) return 0; list_for_each_entry_safe(mst, tmp, &chip->msts, node) { if (mst->stu.sid != sid) continue; if (!refcount_dec_and_test(&mst->refcnt)) return 0; mst->stu.valid = false; err = mv88e6xxx_stu_loadpurge(chip, &mst->stu); if (err) { refcount_set(&mst->refcnt, 1); return err; } list_del(&mst->node); kfree(mst); return 0; } return -ENOENT; } static int mv88e6xxx_mst_get(struct mv88e6xxx_chip *chip, struct net_device *br, u16 msti, u8 *sid) { struct mv88e6xxx_mst *mst; int err, i; if (!mv88e6xxx_has_stu(chip)) { err = -EOPNOTSUPP; goto err; } if (!msti) { *sid = 0; return 0; } list_for_each_entry(mst, &chip->msts, node) { if (mst->br == br && mst->msti == msti) { refcount_inc(&mst->refcnt); *sid = mst->stu.sid; return 0; } } err = mv88e6xxx_sid_get(chip, sid); if (err) goto err; mst = kzalloc(sizeof(*mst), GFP_KERNEL); if (!mst) { err = -ENOMEM; goto err; } INIT_LIST_HEAD(&mst->node); refcount_set(&mst->refcnt, 1); mst->br = br; mst->msti = msti; mst->stu.valid = true; mst->stu.sid = *sid; /* The bridge starts out all ports in the disabled state. But * a STU state of disabled means to go by the port-global * state. So we set all user port's initial state to blocking, * to match the bridge's behavior. */ for (i = 0; i < mv88e6xxx_num_ports(chip); i++) mst->stu.state[i] = dsa_is_user_port(chip->ds, i) ? MV88E6XXX_PORT_CTL0_STATE_BLOCKING : MV88E6XXX_PORT_CTL0_STATE_DISABLED; err = mv88e6xxx_stu_loadpurge(chip, &mst->stu); if (err) goto err_free; list_add_tail(&mst->node, &chip->msts); return 0; err_free: kfree(mst); err: return err; } static int mv88e6xxx_port_mst_state_set(struct dsa_switch *ds, int port, const struct switchdev_mst_state *st) { struct dsa_port *dp = dsa_to_port(ds, port); struct mv88e6xxx_chip *chip = ds->priv; struct mv88e6xxx_mst *mst; u8 state; int err; if (!mv88e6xxx_has_stu(chip)) return -EOPNOTSUPP; switch (st->state) { case BR_STATE_DISABLED: case BR_STATE_BLOCKING: case BR_STATE_LISTENING: state = MV88E6XXX_PORT_CTL0_STATE_BLOCKING; break; case BR_STATE_LEARNING: state = MV88E6XXX_PORT_CTL0_STATE_LEARNING; break; case BR_STATE_FORWARDING: state = MV88E6XXX_PORT_CTL0_STATE_FORWARDING; break; default: return -EINVAL; } list_for_each_entry(mst, &chip->msts, node) { if (mst->br == dsa_port_bridge_dev_get(dp) && mst->msti == st->msti) { if (mst->stu.state[port] == state) return 0; mst->stu.state[port] = state; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_stu_loadpurge(chip, &mst->stu); mv88e6xxx_reg_unlock(chip); return err; } } return -ENOENT; } static int mv88e6xxx_port_check_hw_vlan(struct dsa_switch *ds, int port, u16 vid) { struct dsa_port *dp = dsa_to_port(ds, port), *other_dp; struct mv88e6xxx_chip *chip = ds->priv; struct mv88e6xxx_vtu_entry vlan; int err; /* DSA and CPU ports have to be members of multiple vlans */ if (dsa_port_is_dsa(dp) || dsa_port_is_cpu(dp)) return 0; err = mv88e6xxx_vtu_get(chip, vid, &vlan); if (err) return err; if (!vlan.valid) return 0; dsa_switch_for_each_user_port(other_dp, ds) { struct net_device *other_br; if (vlan.member[other_dp->index] == MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_NON_MEMBER) continue; if (dsa_port_bridge_same(dp, other_dp)) break; /* same bridge, check next VLAN */ other_br = dsa_port_bridge_dev_get(other_dp); if (!other_br) continue; dev_err(ds->dev, "p%d: hw VLAN %d already used by port %d in %s\n", port, vlan.vid, other_dp->index, netdev_name(other_br)); return -EOPNOTSUPP; } return 0; } static int mv88e6xxx_port_commit_pvid(struct mv88e6xxx_chip *chip, int port) { struct dsa_port *dp = dsa_to_port(chip->ds, port); struct net_device *br = dsa_port_bridge_dev_get(dp); struct mv88e6xxx_port *p = &chip->ports[port]; u16 pvid = MV88E6XXX_VID_STANDALONE; bool drop_untagged = false; int err; if (br) { if (br_vlan_enabled(br)) { pvid = p->bridge_pvid.vid; drop_untagged = !p->bridge_pvid.valid; } else { pvid = MV88E6XXX_VID_BRIDGED; } } err = mv88e6xxx_port_set_pvid(chip, port, pvid); if (err) return err; return mv88e6xxx_port_drop_untagged(chip, port, drop_untagged); } static int mv88e6xxx_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering, struct netlink_ext_ack *extack) { struct mv88e6xxx_chip *chip = ds->priv; u16 mode = vlan_filtering ? MV88E6XXX_PORT_CTL2_8021Q_MODE_SECURE : MV88E6XXX_PORT_CTL2_8021Q_MODE_DISABLED; int err; if (!mv88e6xxx_max_vid(chip)) return -EOPNOTSUPP; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_set_8021q_mode(chip, port, mode); if (err) goto unlock; err = mv88e6xxx_port_commit_pvid(chip, port); if (err) goto unlock; unlock: mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_port_vlan_prepare(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan) { struct mv88e6xxx_chip *chip = ds->priv; int err; if (!mv88e6xxx_max_vid(chip)) return -EOPNOTSUPP; /* If the requested port doesn't belong to the same bridge as the VLAN * members, do not support it (yet) and fallback to software VLAN. */ mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_check_hw_vlan(ds, port, vlan->vid); mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_port_db_load_purge(struct mv88e6xxx_chip *chip, int port, const unsigned char *addr, u16 vid, u8 state) { struct mv88e6xxx_atu_entry entry; struct mv88e6xxx_vtu_entry vlan; u16 fid; int err; /* Ports have two private address databases: one for when the port is * standalone and one for when the port is under a bridge and the * 802.1Q mode is disabled. When the port is standalone, DSA wants its * address database to remain 100% empty, so we never load an ATU entry * into a standalone port's database. Therefore, translate the null * VLAN ID into the port's database used for VLAN-unaware bridging. */ if (vid == 0) { fid = MV88E6XXX_FID_BRIDGED; } else { err = mv88e6xxx_vtu_get(chip, vid, &vlan); if (err) return err; /* switchdev expects -EOPNOTSUPP to honor software VLANs */ if (!vlan.valid) return -EOPNOTSUPP; fid = vlan.fid; } entry.state = 0; ether_addr_copy(entry.mac, addr); eth_addr_dec(entry.mac); err = mv88e6xxx_g1_atu_getnext(chip, fid, &entry); if (err) return err; /* Initialize a fresh ATU entry if it isn't found */ if (!entry.state || !ether_addr_equal(entry.mac, addr)) { memset(&entry, 0, sizeof(entry)); ether_addr_copy(entry.mac, addr); } /* Purge the ATU entry only if no port is using it anymore */ if (!state) { entry.portvec &= ~BIT(port); if (!entry.portvec) entry.state = 0; } else { if (state == MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC) entry.portvec = BIT(port); else entry.portvec |= BIT(port); entry.state = state; } return mv88e6xxx_g1_atu_loadpurge(chip, fid, &entry); } static int mv88e6xxx_policy_apply(struct mv88e6xxx_chip *chip, int port, const struct mv88e6xxx_policy *policy) { enum mv88e6xxx_policy_mapping mapping = policy->mapping; enum mv88e6xxx_policy_action action = policy->action; const u8 *addr = policy->addr; u16 vid = policy->vid; u8 state; int err; int id; if (!chip->info->ops->port_set_policy) return -EOPNOTSUPP; switch (mapping) { case MV88E6XXX_POLICY_MAPPING_DA: case MV88E6XXX_POLICY_MAPPING_SA: if (action == MV88E6XXX_POLICY_ACTION_NORMAL) state = 0; /* Dissociate the port and address */ else if (action == MV88E6XXX_POLICY_ACTION_DISCARD && is_multicast_ether_addr(addr)) state = MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_POLICY; else if (action == MV88E6XXX_POLICY_ACTION_DISCARD && is_unicast_ether_addr(addr)) state = MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_POLICY; else return -EOPNOTSUPP; err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid, state); if (err) return err; break; default: return -EOPNOTSUPP; } /* Skip the port's policy clearing if the mapping is still in use */ if (action == MV88E6XXX_POLICY_ACTION_NORMAL) idr_for_each_entry(&chip->policies, policy, id) if (policy->port == port && policy->mapping == mapping && policy->action != action) return 0; return chip->info->ops->port_set_policy(chip, port, mapping, action); } static int mv88e6xxx_policy_insert(struct mv88e6xxx_chip *chip, int port, struct ethtool_rx_flow_spec *fs) { struct ethhdr *mac_entry = &fs->h_u.ether_spec; struct ethhdr *mac_mask = &fs->m_u.ether_spec; enum mv88e6xxx_policy_mapping mapping; enum mv88e6xxx_policy_action action; struct mv88e6xxx_policy *policy; u16 vid = 0; u8 *addr; int err; int id; if (fs->location != RX_CLS_LOC_ANY) return -EINVAL; if (fs->ring_cookie == RX_CLS_FLOW_DISC) action = MV88E6XXX_POLICY_ACTION_DISCARD; else return -EOPNOTSUPP; switch (fs->flow_type & ~FLOW_EXT) { case ETHER_FLOW: if (!is_zero_ether_addr(mac_mask->h_dest) && is_zero_ether_addr(mac_mask->h_source)) { mapping = MV88E6XXX_POLICY_MAPPING_DA; addr = mac_entry->h_dest; } else if (is_zero_ether_addr(mac_mask->h_dest) && !is_zero_ether_addr(mac_mask->h_source)) { mapping = MV88E6XXX_POLICY_MAPPING_SA; addr = mac_entry->h_source; } else { /* Cannot support DA and SA mapping in the same rule */ return -EOPNOTSUPP; } break; default: return -EOPNOTSUPP; } if ((fs->flow_type & FLOW_EXT) && fs->m_ext.vlan_tci) { if (fs->m_ext.vlan_tci != htons(0xffff)) return -EOPNOTSUPP; vid = be16_to_cpu(fs->h_ext.vlan_tci) & VLAN_VID_MASK; } idr_for_each_entry(&chip->policies, policy, id) { if (policy->port == port && policy->mapping == mapping && policy->action == action && policy->vid == vid && ether_addr_equal(policy->addr, addr)) return -EEXIST; } policy = devm_kzalloc(chip->dev, sizeof(*policy), GFP_KERNEL); if (!policy) return -ENOMEM; fs->location = 0; err = idr_alloc_u32(&chip->policies, policy, &fs->location, 0xffffffff, GFP_KERNEL); if (err) { devm_kfree(chip->dev, policy); return err; } memcpy(&policy->fs, fs, sizeof(*fs)); ether_addr_copy(policy->addr, addr); policy->mapping = mapping; policy->action = action; policy->port = port; policy->vid = vid; err = mv88e6xxx_policy_apply(chip, port, policy); if (err) { idr_remove(&chip->policies, fs->location); devm_kfree(chip->dev, policy); return err; } return 0; } static int mv88e6xxx_get_rxnfc(struct dsa_switch *ds, int port, struct ethtool_rxnfc *rxnfc, u32 *rule_locs) { struct ethtool_rx_flow_spec *fs = &rxnfc->fs; struct mv88e6xxx_chip *chip = ds->priv; struct mv88e6xxx_policy *policy; int err; int id; mv88e6xxx_reg_lock(chip); switch (rxnfc->cmd) { case ETHTOOL_GRXCLSRLCNT: rxnfc->data = 0; rxnfc->data |= RX_CLS_LOC_SPECIAL; rxnfc->rule_cnt = 0; idr_for_each_entry(&chip->policies, policy, id) if (policy->port == port) rxnfc->rule_cnt++; err = 0; break; case ETHTOOL_GRXCLSRULE: err = -ENOENT; policy = idr_find(&chip->policies, fs->location); if (policy) { memcpy(fs, &policy->fs, sizeof(*fs)); err = 0; } break; case ETHTOOL_GRXCLSRLALL: rxnfc->data = 0; rxnfc->rule_cnt = 0; idr_for_each_entry(&chip->policies, policy, id) if (policy->port == port) rule_locs[rxnfc->rule_cnt++] = id; err = 0; break; default: err = -EOPNOTSUPP; break; } mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_set_rxnfc(struct dsa_switch *ds, int port, struct ethtool_rxnfc *rxnfc) { struct ethtool_rx_flow_spec *fs = &rxnfc->fs; struct mv88e6xxx_chip *chip = ds->priv; struct mv88e6xxx_policy *policy; int err; mv88e6xxx_reg_lock(chip); switch (rxnfc->cmd) { case ETHTOOL_SRXCLSRLINS: err = mv88e6xxx_policy_insert(chip, port, fs); break; case ETHTOOL_SRXCLSRLDEL: err = -ENOENT; policy = idr_remove(&chip->policies, fs->location); if (policy) { policy->action = MV88E6XXX_POLICY_ACTION_NORMAL; err = mv88e6xxx_policy_apply(chip, port, policy); devm_kfree(chip->dev, policy); } break; default: err = -EOPNOTSUPP; break; } mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_port_add_broadcast(struct mv88e6xxx_chip *chip, int port, u16 vid) { u8 state = MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC; u8 broadcast[ETH_ALEN]; eth_broadcast_addr(broadcast); return mv88e6xxx_port_db_load_purge(chip, port, broadcast, vid, state); } static int mv88e6xxx_broadcast_setup(struct mv88e6xxx_chip *chip, u16 vid) { int port; int err; for (port = 0; port < mv88e6xxx_num_ports(chip); port++) { struct dsa_port *dp = dsa_to_port(chip->ds, port); struct net_device *brport; if (dsa_is_unused_port(chip->ds, port)) continue; brport = dsa_port_to_bridge_port(dp); if (brport && !br_port_flag_is_set(brport, BR_BCAST_FLOOD)) /* Skip bridged user ports where broadcast * flooding is disabled. */ continue; err = mv88e6xxx_port_add_broadcast(chip, port, vid); if (err) return err; } return 0; } struct mv88e6xxx_port_broadcast_sync_ctx { int port; bool flood; }; static int mv88e6xxx_port_broadcast_sync_vlan(struct mv88e6xxx_chip *chip, const struct mv88e6xxx_vtu_entry *vlan, void *_ctx) { struct mv88e6xxx_port_broadcast_sync_ctx *ctx = _ctx; u8 broadcast[ETH_ALEN]; u8 state; if (ctx->flood) state = MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC; else state = MV88E6XXX_G1_ATU_DATA_STATE_MC_UNUSED; eth_broadcast_addr(broadcast); return mv88e6xxx_port_db_load_purge(chip, ctx->port, broadcast, vlan->vid, state); } static int mv88e6xxx_port_broadcast_sync(struct mv88e6xxx_chip *chip, int port, bool flood) { struct mv88e6xxx_port_broadcast_sync_ctx ctx = { .port = port, .flood = flood, }; struct mv88e6xxx_vtu_entry vid0 = { .vid = 0, }; int err; /* Update the port's private database... */ err = mv88e6xxx_port_broadcast_sync_vlan(chip, &vid0, &ctx); if (err) return err; /* ...and the database for all VLANs. */ return mv88e6xxx_vtu_walk(chip, mv88e6xxx_port_broadcast_sync_vlan, &ctx); } static int mv88e6xxx_port_vlan_join(struct mv88e6xxx_chip *chip, int port, u16 vid, u8 member, bool warn) { const u8 non_member = MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_NON_MEMBER; struct mv88e6xxx_vtu_entry vlan; int i, err; err = mv88e6xxx_vtu_get(chip, vid, &vlan); if (err) return err; if (!vlan.valid) { memset(&vlan, 0, sizeof(vlan)); if (vid == MV88E6XXX_VID_STANDALONE) vlan.policy = true; err = mv88e6xxx_atu_new(chip, &vlan.fid); if (err) return err; for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) if (i == port) vlan.member[i] = member; else vlan.member[i] = non_member; vlan.vid = vid; vlan.valid = true; err = mv88e6xxx_vtu_loadpurge(chip, &vlan); if (err) return err; err = mv88e6xxx_broadcast_setup(chip, vlan.vid); if (err) return err; } else if (vlan.member[port] != member) { vlan.member[port] = member; err = mv88e6xxx_vtu_loadpurge(chip, &vlan); if (err) return err; } else if (warn) { dev_info(chip->dev, "p%d: already a member of VLAN %d\n", port, vid); } return 0; } static int mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan, struct netlink_ext_ack *extack) { struct mv88e6xxx_chip *chip = ds->priv; bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; struct mv88e6xxx_port *p = &chip->ports[port]; bool warn; u8 member; int err; if (!vlan->vid) return 0; err = mv88e6xxx_port_vlan_prepare(ds, port, vlan); if (err) return err; if (dsa_is_dsa_port(ds, port) || dsa_is_cpu_port(ds, port)) member = MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_UNMODIFIED; else if (untagged) member = MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_UNTAGGED; else member = MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_TAGGED; /* net/dsa/slave.c will call dsa_port_vlan_add() for the affected port * and then the CPU port. Do not warn for duplicates for the CPU port. */ warn = !dsa_is_cpu_port(ds, port) && !dsa_is_dsa_port(ds, port); mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_vlan_join(chip, port, vlan->vid, member, warn); if (err) { dev_err(ds->dev, "p%d: failed to add VLAN %d%c\n", port, vlan->vid, untagged ? 'u' : 't'); goto out; } if (pvid) { p->bridge_pvid.vid = vlan->vid; p->bridge_pvid.valid = true; err = mv88e6xxx_port_commit_pvid(chip, port); if (err) goto out; } else if (vlan->vid && p->bridge_pvid.vid == vlan->vid) { /* The old pvid was reinstalled as a non-pvid VLAN */ p->bridge_pvid.valid = false; err = mv88e6xxx_port_commit_pvid(chip, port); if (err) goto out; } out: mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_port_vlan_leave(struct mv88e6xxx_chip *chip, int port, u16 vid) { struct mv88e6xxx_vtu_entry vlan; int i, err; if (!vid) return 0; err = mv88e6xxx_vtu_get(chip, vid, &vlan); if (err) return err; /* If the VLAN doesn't exist in hardware or the port isn't a member, * tell switchdev that this VLAN is likely handled in software. */ if (!vlan.valid || vlan.member[port] == MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_NON_MEMBER) return -EOPNOTSUPP; vlan.member[port] = MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_NON_MEMBER; /* keep the VLAN unless all ports are excluded */ vlan.valid = false; for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) { if (vlan.member[i] != MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_NON_MEMBER) { vlan.valid = true; break; } } err = mv88e6xxx_vtu_loadpurge(chip, &vlan); if (err) return err; if (!vlan.valid) { err = mv88e6xxx_mst_put(chip, vlan.sid); if (err) return err; } return mv88e6xxx_g1_atu_remove(chip, vlan.fid, port, false); } static int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan) { struct mv88e6xxx_chip *chip = ds->priv; struct mv88e6xxx_port *p = &chip->ports[port]; int err = 0; u16 pvid; if (!mv88e6xxx_max_vid(chip)) return -EOPNOTSUPP; /* The ATU removal procedure needs the FID to be mapped in the VTU, * but FDB deletion runs concurrently with VLAN deletion. Flush the DSA * switchdev workqueue to ensure that all FDB entries are deleted * before we remove the VLAN. */ dsa_flush_workqueue(); mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_get_pvid(chip, port, &pvid); if (err) goto unlock; err = mv88e6xxx_port_vlan_leave(chip, port, vlan->vid); if (err) goto unlock; if (vlan->vid == pvid) { p->bridge_pvid.valid = false; err = mv88e6xxx_port_commit_pvid(chip, port); if (err) goto unlock; } unlock: mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_port_vlan_fast_age(struct dsa_switch *ds, int port, u16 vid) { struct mv88e6xxx_chip *chip = ds->priv; struct mv88e6xxx_vtu_entry vlan; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_vtu_get(chip, vid, &vlan); if (err) goto unlock; err = mv88e6xxx_port_fast_age_fid(chip, port, vlan.fid); unlock: mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_vlan_msti_set(struct dsa_switch *ds, struct dsa_bridge bridge, const struct switchdev_vlan_msti *msti) { struct mv88e6xxx_chip *chip = ds->priv; struct mv88e6xxx_vtu_entry vlan; u8 old_sid, new_sid; int err; if (!mv88e6xxx_has_stu(chip)) return -EOPNOTSUPP; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_vtu_get(chip, msti->vid, &vlan); if (err) goto unlock; if (!vlan.valid) { err = -EINVAL; goto unlock; } old_sid = vlan.sid; err = mv88e6xxx_mst_get(chip, bridge.dev, msti->msti, &new_sid); if (err) goto unlock; if (new_sid != old_sid) { vlan.sid = new_sid; err = mv88e6xxx_vtu_loadpurge(chip, &vlan); if (err) { mv88e6xxx_mst_put(chip, new_sid); goto unlock; } } err = mv88e6xxx_mst_put(chip, old_sid); unlock: mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_port_fdb_add(struct dsa_switch *ds, int port, const unsigned char *addr, u16 vid, struct dsa_db db) { struct mv88e6xxx_chip *chip = ds->priv; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid, MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC); mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_port_fdb_del(struct dsa_switch *ds, int port, const unsigned char *addr, u16 vid, struct dsa_db db) { struct mv88e6xxx_chip *chip = ds->priv; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid, 0); mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_port_db_dump_fid(struct mv88e6xxx_chip *chip, u16 fid, u16 vid, int port, dsa_fdb_dump_cb_t *cb, void *data) { struct mv88e6xxx_atu_entry addr; bool is_static; int err; addr.state = 0; eth_broadcast_addr(addr.mac); do { err = mv88e6xxx_g1_atu_getnext(chip, fid, &addr); if (err) return err; if (!addr.state) break; if (addr.trunk || (addr.portvec & BIT(port)) == 0) continue; if (!is_unicast_ether_addr(addr.mac)) continue; is_static = (addr.state == MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC); err = cb(addr.mac, vid, is_static, data); if (err) return err; } while (!is_broadcast_ether_addr(addr.mac)); return err; } struct mv88e6xxx_port_db_dump_vlan_ctx { int port; dsa_fdb_dump_cb_t *cb; void *data; }; static int mv88e6xxx_port_db_dump_vlan(struct mv88e6xxx_chip *chip, const struct mv88e6xxx_vtu_entry *entry, void *_data) { struct mv88e6xxx_port_db_dump_vlan_ctx *ctx = _data; return mv88e6xxx_port_db_dump_fid(chip, entry->fid, entry->vid, ctx->port, ctx->cb, ctx->data); } static int mv88e6xxx_port_db_dump(struct mv88e6xxx_chip *chip, int port, dsa_fdb_dump_cb_t *cb, void *data) { struct mv88e6xxx_port_db_dump_vlan_ctx ctx = { .port = port, .cb = cb, .data = data, }; u16 fid; int err; /* Dump port's default Filtering Information Database (VLAN ID 0) */ err = mv88e6xxx_port_get_fid(chip, port, &fid); if (err) return err; err = mv88e6xxx_port_db_dump_fid(chip, fid, 0, port, cb, data); if (err) return err; return mv88e6xxx_vtu_walk(chip, mv88e6xxx_port_db_dump_vlan, &ctx); } static int mv88e6xxx_port_fdb_dump(struct dsa_switch *ds, int port, dsa_fdb_dump_cb_t *cb, void *data) { struct mv88e6xxx_chip *chip = ds->priv; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_db_dump(chip, port, cb, data); mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_bridge_map(struct mv88e6xxx_chip *chip, struct dsa_bridge bridge) { struct dsa_switch *ds = chip->ds; struct dsa_switch_tree *dst = ds->dst; struct dsa_port *dp; int err; list_for_each_entry(dp, &dst->ports, list) { if (dsa_port_offloads_bridge(dp, &bridge)) { if (dp->ds == ds) { /* This is a local bridge group member, * remap its Port VLAN Map. */ err = mv88e6xxx_port_vlan_map(chip, dp->index); if (err) return err; } else { /* This is an external bridge group member, * remap its cross-chip Port VLAN Table entry. */ err = mv88e6xxx_pvt_map(chip, dp->ds->index, dp->index); if (err) return err; } } } return 0; } /* Treat the software bridge as a virtual single-port switch behind the * CPU and map in the PVT. First dst->last_switch elements are taken by * physical switches, so start from beyond that range. */ static int mv88e6xxx_map_virtual_bridge_to_pvt(struct dsa_switch *ds, unsigned int bridge_num) { u8 dev = bridge_num + ds->dst->last_switch; struct mv88e6xxx_chip *chip = ds->priv; return mv88e6xxx_pvt_map(chip, dev, 0); } static int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port, struct dsa_bridge bridge, bool *tx_fwd_offload, struct netlink_ext_ack *extack) { struct mv88e6xxx_chip *chip = ds->priv; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_bridge_map(chip, bridge); if (err) goto unlock; err = mv88e6xxx_port_set_map_da(chip, port, true); if (err) goto unlock; err = mv88e6xxx_port_commit_pvid(chip, port); if (err) goto unlock; if (mv88e6xxx_has_pvt(chip)) { err = mv88e6xxx_map_virtual_bridge_to_pvt(ds, bridge.num); if (err) goto unlock; *tx_fwd_offload = true; } unlock: mv88e6xxx_reg_unlock(chip); return err; } static void mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port, struct dsa_bridge bridge) { struct mv88e6xxx_chip *chip = ds->priv; int err; mv88e6xxx_reg_lock(chip); if (bridge.tx_fwd_offload && mv88e6xxx_map_virtual_bridge_to_pvt(ds, bridge.num)) dev_err(ds->dev, "failed to remap cross-chip Port VLAN\n"); if (mv88e6xxx_bridge_map(chip, bridge) || mv88e6xxx_port_vlan_map(chip, port)) dev_err(ds->dev, "failed to remap in-chip Port VLAN\n"); err = mv88e6xxx_port_set_map_da(chip, port, false); if (err) dev_err(ds->dev, "port %d failed to restore map-DA: %pe\n", port, ERR_PTR(err)); err = mv88e6xxx_port_commit_pvid(chip, port); if (err) dev_err(ds->dev, "port %d failed to restore standalone pvid: %pe\n", port, ERR_PTR(err)); mv88e6xxx_reg_unlock(chip); } static int mv88e6xxx_crosschip_bridge_join(struct dsa_switch *ds, int tree_index, int sw_index, int port, struct dsa_bridge bridge, struct netlink_ext_ack *extack) { struct mv88e6xxx_chip *chip = ds->priv; int err; if (tree_index != ds->dst->index) return 0; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_pvt_map(chip, sw_index, port); err = err ? : mv88e6xxx_map_virtual_bridge_to_pvt(ds, bridge.num); mv88e6xxx_reg_unlock(chip); return err; } static void mv88e6xxx_crosschip_bridge_leave(struct dsa_switch *ds, int tree_index, int sw_index, int port, struct dsa_bridge bridge) { struct mv88e6xxx_chip *chip = ds->priv; if (tree_index != ds->dst->index) return; mv88e6xxx_reg_lock(chip); if (mv88e6xxx_pvt_map(chip, sw_index, port) || mv88e6xxx_map_virtual_bridge_to_pvt(ds, bridge.num)) dev_err(ds->dev, "failed to remap cross-chip Port VLAN\n"); mv88e6xxx_reg_unlock(chip); } static int mv88e6xxx_software_reset(struct mv88e6xxx_chip *chip) { if (chip->info->ops->reset) return chip->info->ops->reset(chip); return 0; } static void mv88e6xxx_hardware_reset(struct mv88e6xxx_chip *chip) { struct gpio_desc *gpiod = chip->reset; /* If there is a GPIO connected to the reset pin, toggle it */ if (gpiod) { /* If the switch has just been reset and not yet completed * loading EEPROM, the reset may interrupt the I2C transaction * mid-byte, causing the first EEPROM read after the reset * from the wrong location resulting in the switch booting * to wrong mode and inoperable. */ mv88e6xxx_g1_wait_eeprom_done(chip); gpiod_set_value_cansleep(gpiod, 1); usleep_range(10000, 20000); gpiod_set_value_cansleep(gpiod, 0); usleep_range(10000, 20000); mv88e6xxx_g1_wait_eeprom_done(chip); } } static int mv88e6xxx_disable_ports(struct mv88e6xxx_chip *chip) { int i, err; /* Set all ports to the Disabled state */ for (i = 0; i < mv88e6xxx_num_ports(chip); i++) { err = mv88e6xxx_port_set_state(chip, i, BR_STATE_DISABLED); if (err) return err; } /* Wait for transmit queues to drain, * i.e. 2ms for a maximum frame to be transmitted at 10 Mbps. */ usleep_range(2000, 4000); return 0; } static int mv88e6xxx_switch_reset(struct mv88e6xxx_chip *chip) { int err; err = mv88e6xxx_disable_ports(chip); if (err) return err; mv88e6xxx_hardware_reset(chip); return mv88e6xxx_software_reset(chip); } static int mv88e6xxx_set_port_mode(struct mv88e6xxx_chip *chip, int port, enum mv88e6xxx_frame_mode frame, enum mv88e6xxx_egress_mode egress, u16 etype) { int err; if (!chip->info->ops->port_set_frame_mode) return -EOPNOTSUPP; err = mv88e6xxx_port_set_egress_mode(chip, port, egress); if (err) return err; err = chip->info->ops->port_set_frame_mode(chip, port, frame); if (err) return err; if (chip->info->ops->port_set_ether_type) return chip->info->ops->port_set_ether_type(chip, port, etype); return 0; } static int mv88e6xxx_set_port_mode_normal(struct mv88e6xxx_chip *chip, int port) { return mv88e6xxx_set_port_mode(chip, port, MV88E6XXX_FRAME_MODE_NORMAL, MV88E6XXX_EGRESS_MODE_UNMODIFIED, MV88E6XXX_PORT_ETH_TYPE_DEFAULT); } static int mv88e6xxx_set_port_mode_dsa(struct mv88e6xxx_chip *chip, int port) { return mv88e6xxx_set_port_mode(chip, port, MV88E6XXX_FRAME_MODE_DSA, MV88E6XXX_EGRESS_MODE_UNMODIFIED, MV88E6XXX_PORT_ETH_TYPE_DEFAULT); } static int mv88e6xxx_set_port_mode_edsa(struct mv88e6xxx_chip *chip, int port) { return mv88e6xxx_set_port_mode(chip, port, MV88E6XXX_FRAME_MODE_ETHERTYPE, MV88E6XXX_EGRESS_MODE_ETHERTYPE, ETH_P_EDSA); } static int mv88e6xxx_setup_port_mode(struct mv88e6xxx_chip *chip, int port) { if (dsa_is_dsa_port(chip->ds, port)) return mv88e6xxx_set_port_mode_dsa(chip, port); if (dsa_is_user_port(chip->ds, port)) return mv88e6xxx_set_port_mode_normal(chip, port); /* Setup CPU port mode depending on its supported tag format */ if (chip->tag_protocol == DSA_TAG_PROTO_DSA) return mv88e6xxx_set_port_mode_dsa(chip, port); if (chip->tag_protocol == DSA_TAG_PROTO_EDSA) return mv88e6xxx_set_port_mode_edsa(chip, port); return -EINVAL; } static int mv88e6xxx_setup_message_port(struct mv88e6xxx_chip *chip, int port) { bool message = dsa_is_dsa_port(chip->ds, port); return mv88e6xxx_port_set_message_port(chip, port, message); } static int mv88e6xxx_setup_egress_floods(struct mv88e6xxx_chip *chip, int port) { int err; if (chip->info->ops->port_set_ucast_flood) { err = chip->info->ops->port_set_ucast_flood(chip, port, true); if (err) return err; } if (chip->info->ops->port_set_mcast_flood) { err = chip->info->ops->port_set_mcast_flood(chip, port, true); if (err) return err; } return 0; } static irqreturn_t mv88e6xxx_serdes_irq_thread_fn(int irq, void *dev_id) { struct mv88e6xxx_port *mvp = dev_id; struct mv88e6xxx_chip *chip = mvp->chip; irqreturn_t ret = IRQ_NONE; int port = mvp->port; int lane; mv88e6xxx_reg_lock(chip); lane = mv88e6xxx_serdes_get_lane(chip, port); if (lane >= 0) ret = mv88e6xxx_serdes_irq_status(chip, port, lane); mv88e6xxx_reg_unlock(chip); return ret; } static int mv88e6xxx_serdes_irq_request(struct mv88e6xxx_chip *chip, int port, int lane) { struct mv88e6xxx_port *dev_id = &chip->ports[port]; unsigned int irq; int err; /* Nothing to request if this SERDES port has no IRQ */ irq = mv88e6xxx_serdes_irq_mapping(chip, port); if (!irq) return 0; snprintf(dev_id->serdes_irq_name, sizeof(dev_id->serdes_irq_name), "mv88e6xxx-%s-serdes-%d", dev_name(chip->dev), port); /* Requesting the IRQ will trigger IRQ callbacks, so release the lock */ mv88e6xxx_reg_unlock(chip); err = request_threaded_irq(irq, NULL, mv88e6xxx_serdes_irq_thread_fn, IRQF_ONESHOT, dev_id->serdes_irq_name, dev_id); mv88e6xxx_reg_lock(chip); if (err) return err; dev_id->serdes_irq = irq; return mv88e6xxx_serdes_irq_enable(chip, port, lane); } static int mv88e6xxx_serdes_irq_free(struct mv88e6xxx_chip *chip, int port, int lane) { struct mv88e6xxx_port *dev_id = &chip->ports[port]; unsigned int irq = dev_id->serdes_irq; int err; /* Nothing to free if no IRQ has been requested */ if (!irq) return 0; err = mv88e6xxx_serdes_irq_disable(chip, port, lane); /* Freeing the IRQ will trigger IRQ callbacks, so release the lock */ mv88e6xxx_reg_unlock(chip); free_irq(irq, dev_id); mv88e6xxx_reg_lock(chip); dev_id->serdes_irq = 0; return err; } static int mv88e6xxx_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on) { int lane; int err; lane = mv88e6xxx_serdes_get_lane(chip, port); if (lane < 0) return 0; if (on) { err = mv88e6xxx_serdes_power_up(chip, port, lane); if (err) return err; err = mv88e6xxx_serdes_irq_request(chip, port, lane); } else { err = mv88e6xxx_serdes_irq_free(chip, port, lane); if (err) return err; err = mv88e6xxx_serdes_power_down(chip, port, lane); } return err; } static int mv88e6xxx_set_egress_port(struct mv88e6xxx_chip *chip, enum mv88e6xxx_egress_direction direction, int port) { int err; if (!chip->info->ops->set_egress_port) return -EOPNOTSUPP; err = chip->info->ops->set_egress_port(chip, direction, port); if (err) return err; if (direction == MV88E6XXX_EGRESS_DIR_INGRESS) chip->ingress_dest_port = port; else chip->egress_dest_port = port; return 0; } static int mv88e6xxx_setup_upstream_port(struct mv88e6xxx_chip *chip, int port) { struct dsa_switch *ds = chip->ds; int upstream_port; int err; upstream_port = dsa_upstream_port(ds, port); if (chip->info->ops->port_set_upstream_port) { err = chip->info->ops->port_set_upstream_port(chip, port, upstream_port); if (err) return err; } if (port == upstream_port) { if (chip->info->ops->set_cpu_port) { err = chip->info->ops->set_cpu_port(chip, upstream_port); if (err) return err; } err = mv88e6xxx_set_egress_port(chip, MV88E6XXX_EGRESS_DIR_INGRESS, upstream_port); if (err && err != -EOPNOTSUPP) return err; err = mv88e6xxx_set_egress_port(chip, MV88E6XXX_EGRESS_DIR_EGRESS, upstream_port); if (err && err != -EOPNOTSUPP) return err; } return 0; } static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port) { struct device_node *phy_handle = NULL; struct dsa_switch *ds = chip->ds; phy_interface_t mode; struct dsa_port *dp; int tx_amp, speed; int err; u16 reg; chip->ports[port].chip = chip; chip->ports[port].port = port; dp = dsa_to_port(ds, port); /* MAC Forcing register: don't force link, speed, duplex or flow control * state to any particular values on physical ports, but force the CPU * port and all DSA ports to their maximum bandwidth and full duplex. */ if (dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port)) { struct phylink_config pl_config = {}; unsigned long caps; chip->info->ops->phylink_get_caps(chip, port, &pl_config); caps = pl_config.mac_capabilities; if (chip->info->ops->port_max_speed_mode) mode = chip->info->ops->port_max_speed_mode(port); else mode = PHY_INTERFACE_MODE_NA; if (caps & MAC_10000FD) speed = SPEED_10000; else if (caps & MAC_5000FD) speed = SPEED_5000; else if (caps & MAC_2500FD) speed = SPEED_2500; else if (caps & MAC_1000) speed = SPEED_1000; else if (caps & MAC_100) speed = SPEED_100; else speed = SPEED_10; err = mv88e6xxx_port_setup_mac(chip, port, LINK_FORCED_UP, speed, DUPLEX_FULL, PAUSE_OFF, mode); } else { err = mv88e6xxx_port_setup_mac(chip, port, LINK_UNFORCED, SPEED_UNFORCED, DUPLEX_UNFORCED, PAUSE_ON, PHY_INTERFACE_MODE_NA); } if (err) return err; /* Port Control: disable Drop-on-Unlock, disable Drop-on-Lock, * disable Header mode, enable IGMP/MLD snooping, disable VLAN * tunneling, determine priority by looking at 802.1p and IP * priority fields (IP prio has precedence), and set STP state * to Forwarding. * * If this is the CPU link, use DSA or EDSA tagging depending * on which tagging mode was configured. * * If this is a link to another switch, use DSA tagging mode. * * If this is the upstream port for this switch, enable * forwarding of unknown unicasts and multicasts. */ reg = MV88E6185_PORT_CTL0_USE_TAG | MV88E6185_PORT_CTL0_USE_IP | MV88E6XXX_PORT_CTL0_STATE_FORWARDING; /* Forward any IPv4 IGMP or IPv6 MLD frames received * by a USER port to the CPU port to allow snooping. */ if (dsa_is_user_port(ds, port)) reg |= MV88E6XXX_PORT_CTL0_IGMP_MLD_SNOOP; err = mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_CTL0, reg); if (err) return err; err = mv88e6xxx_setup_port_mode(chip, port); if (err) return err; err = mv88e6xxx_setup_egress_floods(chip, port); if (err) return err; /* Port Control 2: don't force a good FCS, set the MTU size to * 10222 bytes, disable 802.1q tags checking, don't discard * tagged or untagged frames on this port, skip destination * address lookup on user ports, disable ARP mirroring and don't * send a copy of all transmitted/received frames on this port * to the CPU. */ err = mv88e6xxx_port_set_map_da(chip, port, !dsa_is_user_port(ds, port)); if (err) return err; err = mv88e6xxx_setup_upstream_port(chip, port); if (err) return err; /* On chips that support it, set all downstream DSA ports' * VLAN policy to TRAP. In combination with loading * MV88E6XXX_VID_STANDALONE as a policy entry in the VTU, this * provides a better isolation barrier between standalone * ports, as the ATU is bypassed on any intermediate switches * between the incoming port and the CPU. */ if (dsa_is_downstream_port(ds, port) && chip->info->ops->port_set_policy) { err = chip->info->ops->port_set_policy(chip, port, MV88E6XXX_POLICY_MAPPING_VTU, MV88E6XXX_POLICY_ACTION_TRAP); if (err) return err; } /* User ports start out in standalone mode and 802.1Q is * therefore disabled. On DSA ports, all valid VIDs are always * loaded in the VTU - therefore, enable 802.1Q in order to take * advantage of VLAN policy on chips that supports it. */ err = mv88e6xxx_port_set_8021q_mode(chip, port, dsa_is_user_port(ds, port) ? MV88E6XXX_PORT_CTL2_8021Q_MODE_DISABLED : MV88E6XXX_PORT_CTL2_8021Q_MODE_SECURE); if (err) return err; /* Bind MV88E6XXX_VID_STANDALONE to MV88E6XXX_FID_STANDALONE by * virtue of the fact that mv88e6xxx_atu_new() will pick it as * the first free FID. This will be used as the private PVID for * unbridged ports. Shared (DSA and CPU) ports must also be * members of this VID, in order to trap all frames assigned to * it to the CPU. */ err = mv88e6xxx_port_vlan_join(chip, port, MV88E6XXX_VID_STANDALONE, MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_UNMODIFIED, false); if (err) return err; /* Associate MV88E6XXX_VID_BRIDGED with MV88E6XXX_FID_BRIDGED in the * ATU by virtue of the fact that mv88e6xxx_atu_new() will pick it as * the first free FID after MV88E6XXX_FID_STANDALONE. This will be used * as the private PVID on ports under a VLAN-unaware bridge. * Shared (DSA and CPU) ports must also be members of it, to translate * the VID from the DSA tag into MV88E6XXX_FID_BRIDGED, instead of * relying on their port default FID. */ err = mv88e6xxx_port_vlan_join(chip, port, MV88E6XXX_VID_BRIDGED, MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_UNMODIFIED, false); if (err) return err; if (chip->info->ops->port_set_jumbo_size) { err = chip->info->ops->port_set_jumbo_size(chip, port, 10218); if (err) return err; } /* Port Association Vector: disable automatic address learning * on all user ports since they start out in standalone * mode. When joining a bridge, learning will be configured to * match the bridge port settings. Enable learning on all * DSA/CPU ports. NOTE: FROM_CPU frames always bypass the * learning process. * * Disable HoldAt1, IntOnAgeOut, LockedPort, IgnoreWrongData, * and RefreshLocked. I.e. setup standard automatic learning. */ if (dsa_is_user_port(ds, port)) reg = 0; else reg = 1 << port; err = mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_ASSOC_VECTOR, reg); if (err) return err; /* Egress rate control 2: disable egress rate control. */ err = mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_EGRESS_RATE_CTL2, 0x0000); if (err) return err; if (chip->info->ops->port_pause_limit) { err = chip->info->ops->port_pause_limit(chip, port, 0, 0); if (err) return err; } if (chip->info->ops->port_disable_learn_limit) { err = chip->info->ops->port_disable_learn_limit(chip, port); if (err) return err; } if (chip->info->ops->port_disable_pri_override) { err = chip->info->ops->port_disable_pri_override(chip, port); if (err) return err; } if (chip->info->ops->port_tag_remap) { err = chip->info->ops->port_tag_remap(chip, port); if (err) return err; } if (chip->info->ops->port_egress_rate_limiting) { err = chip->info->ops->port_egress_rate_limiting(chip, port); if (err) return err; } if (chip->info->ops->port_setup_message_port) { err = chip->info->ops->port_setup_message_port(chip, port); if (err) return err; } if (chip->info->ops->serdes_set_tx_amplitude) { if (dp) phy_handle = of_parse_phandle(dp->dn, "phy-handle", 0); if (phy_handle && !of_property_read_u32(phy_handle, "tx-p2p-microvolt", &tx_amp)) err = chip->info->ops->serdes_set_tx_amplitude(chip, port, tx_amp); if (phy_handle) { of_node_put(phy_handle); if (err) return err; } } /* Port based VLAN map: give each port the same default address * database, and allow bidirectional communication between the * CPU and DSA port(s), and the other ports. */ err = mv88e6xxx_port_set_fid(chip, port, MV88E6XXX_FID_STANDALONE); if (err) return err; err = mv88e6xxx_port_vlan_map(chip, port); if (err) return err; /* Default VLAN ID and priority: don't set a default VLAN * ID, and set the default packet priority to zero. */ return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_DEFAULT_VLAN, 0); } static int mv88e6xxx_get_max_mtu(struct dsa_switch *ds, int port) { struct mv88e6xxx_chip *chip = ds->priv; if (chip->info->ops->port_set_jumbo_size) return 10240 - VLAN_ETH_HLEN - EDSA_HLEN - ETH_FCS_LEN; else if (chip->info->ops->set_max_frame_size) return 1632 - VLAN_ETH_HLEN - EDSA_HLEN - ETH_FCS_LEN; return ETH_DATA_LEN; } static int mv88e6xxx_change_mtu(struct dsa_switch *ds, int port, int new_mtu) { struct mv88e6xxx_chip *chip = ds->priv; int ret = 0; /* For families where we don't know how to alter the MTU, * just accept any value up to ETH_DATA_LEN */ if (!chip->info->ops->port_set_jumbo_size && !chip->info->ops->set_max_frame_size) { if (new_mtu > ETH_DATA_LEN) return -EINVAL; return 0; } if (dsa_is_dsa_port(ds, port) || dsa_is_cpu_port(ds, port)) new_mtu += EDSA_HLEN; mv88e6xxx_reg_lock(chip); if (chip->info->ops->port_set_jumbo_size) ret = chip->info->ops->port_set_jumbo_size(chip, port, new_mtu); else if (chip->info->ops->set_max_frame_size) ret = chip->info->ops->set_max_frame_size(chip, new_mtu); mv88e6xxx_reg_unlock(chip); return ret; } static int mv88e6xxx_port_enable(struct dsa_switch *ds, int port, struct phy_device *phydev) { struct mv88e6xxx_chip *chip = ds->priv; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_serdes_power(chip, port, true); mv88e6xxx_reg_unlock(chip); return err; } static void mv88e6xxx_port_disable(struct dsa_switch *ds, int port) { struct mv88e6xxx_chip *chip = ds->priv; mv88e6xxx_reg_lock(chip); if (mv88e6xxx_serdes_power(chip, port, false)) dev_err(chip->dev, "failed to power off SERDES\n"); mv88e6xxx_reg_unlock(chip); } static int mv88e6xxx_set_ageing_time(struct dsa_switch *ds, unsigned int ageing_time) { struct mv88e6xxx_chip *chip = ds->priv; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_g1_atu_set_age_time(chip, ageing_time); mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_stats_setup(struct mv88e6xxx_chip *chip) { int err; /* Initialize the statistics unit */ if (chip->info->ops->stats_set_histogram) { err = chip->info->ops->stats_set_histogram(chip); if (err) return err; } return mv88e6xxx_g1_stats_clear(chip); } /* Check if the errata has already been applied. */ static bool mv88e6390_setup_errata_applied(struct mv88e6xxx_chip *chip) { int port; int err; u16 val; for (port = 0; port < mv88e6xxx_num_ports(chip); port++) { err = mv88e6xxx_port_hidden_read(chip, 0xf, port, 0, &val); if (err) { dev_err(chip->dev, "Error reading hidden register: %d\n", err); return false; } if (val != 0x01c0) return false; } return true; } /* The 6390 copper ports have an errata which require poking magic * values into undocumented hidden registers and then performing a * software reset. */ static int mv88e6390_setup_errata(struct mv88e6xxx_chip *chip) { int port; int err; if (mv88e6390_setup_errata_applied(chip)) return 0; /* Set the ports into blocking mode */ for (port = 0; port < mv88e6xxx_num_ports(chip); port++) { err = mv88e6xxx_port_set_state(chip, port, BR_STATE_DISABLED); if (err) return err; } for (port = 0; port < mv88e6xxx_num_ports(chip); port++) { err = mv88e6xxx_port_hidden_write(chip, 0xf, port, 0, 0x01c0); if (err) return err; } return mv88e6xxx_software_reset(chip); } static void mv88e6xxx_teardown(struct dsa_switch *ds) { mv88e6xxx_teardown_devlink_params(ds); dsa_devlink_resources_unregister(ds); mv88e6xxx_teardown_devlink_regions_global(ds); } static int mv88e6xxx_setup(struct dsa_switch *ds) { struct mv88e6xxx_chip *chip = ds->priv; u8 cmode; int err; int i; chip->ds = ds; ds->slave_mii_bus = mv88e6xxx_default_mdio_bus(chip); /* Since virtual bridges are mapped in the PVT, the number we support * depends on the physical switch topology. We need to let DSA figure * that out and therefore we cannot set this at dsa_register_switch() * time. */ if (mv88e6xxx_has_pvt(chip)) ds->max_num_bridges = MV88E6XXX_MAX_PVT_SWITCHES - ds->dst->last_switch - 1; mv88e6xxx_reg_lock(chip); if (chip->info->ops->setup_errata) { err = chip->info->ops->setup_errata(chip); if (err) goto unlock; } /* Cache the cmode of each port. */ for (i = 0; i < mv88e6xxx_num_ports(chip); i++) { if (chip->info->ops->port_get_cmode) { err = chip->info->ops->port_get_cmode(chip, i, &cmode); if (err) goto unlock; chip->ports[i].cmode = cmode; } } err = mv88e6xxx_vtu_setup(chip); if (err) goto unlock; /* Must be called after mv88e6xxx_vtu_setup (which flushes the * VTU, thereby also flushing the STU). */ err = mv88e6xxx_stu_setup(chip); if (err) goto unlock; /* Setup Switch Port Registers */ for (i = 0; i < mv88e6xxx_num_ports(chip); i++) { if (dsa_is_unused_port(ds, i)) continue; /* Prevent the use of an invalid port. */ if (mv88e6xxx_is_invalid_port(chip, i)) { dev_err(chip->dev, "port %d is invalid\n", i); err = -EINVAL; goto unlock; } err = mv88e6xxx_setup_port(chip, i); if (err) goto unlock; } err = mv88e6xxx_irl_setup(chip); if (err) goto unlock; err = mv88e6xxx_mac_setup(chip); if (err) goto unlock; err = mv88e6xxx_phy_setup(chip); if (err) goto unlock; err = mv88e6xxx_pvt_setup(chip); if (err) goto unlock; err = mv88e6xxx_atu_setup(chip); if (err) goto unlock; err = mv88e6xxx_broadcast_setup(chip, 0); if (err) goto unlock; err = mv88e6xxx_pot_setup(chip); if (err) goto unlock; err = mv88e6xxx_rmu_setup(chip); if (err) goto unlock; err = mv88e6xxx_rsvd2cpu_setup(chip); if (err) goto unlock; err = mv88e6xxx_trunk_setup(chip); if (err) goto unlock; err = mv88e6xxx_devmap_setup(chip); if (err) goto unlock; err = mv88e6xxx_pri_setup(chip); if (err) goto unlock; /* Setup PTP Hardware Clock and timestamping */ if (chip->info->ptp_support) { err = mv88e6xxx_ptp_setup(chip); if (err) goto unlock; err = mv88e6xxx_hwtstamp_setup(chip); if (err) goto unlock; } err = mv88e6xxx_stats_setup(chip); if (err) goto unlock; unlock: mv88e6xxx_reg_unlock(chip); if (err) return err; /* Have to be called without holding the register lock, since * they take the devlink lock, and we later take the locks in * the reverse order when getting/setting parameters or * resource occupancy. */ err = mv88e6xxx_setup_devlink_resources(ds); if (err) return err; err = mv88e6xxx_setup_devlink_params(ds); if (err) goto out_resources; err = mv88e6xxx_setup_devlink_regions_global(ds); if (err) goto out_params; return 0; out_params: mv88e6xxx_teardown_devlink_params(ds); out_resources: dsa_devlink_resources_unregister(ds); return err; } static int mv88e6xxx_port_setup(struct dsa_switch *ds, int port) { return mv88e6xxx_setup_devlink_regions_port(ds, port); } static void mv88e6xxx_port_teardown(struct dsa_switch *ds, int port) { mv88e6xxx_teardown_devlink_regions_port(ds, port); } /* prod_id for switch families which do not have a PHY model number */ static const u16 family_prod_id_table[] = { [MV88E6XXX_FAMILY_6341] = MV88E6XXX_PORT_SWITCH_ID_PROD_6341, [MV88E6XXX_FAMILY_6390] = MV88E6XXX_PORT_SWITCH_ID_PROD_6390, [MV88E6XXX_FAMILY_6393] = MV88E6XXX_PORT_SWITCH_ID_PROD_6393X, }; static int mv88e6xxx_mdio_read(struct mii_bus *bus, int phy, int reg) { struct mv88e6xxx_mdio_bus *mdio_bus = bus->priv; struct mv88e6xxx_chip *chip = mdio_bus->chip; u16 prod_id; u16 val; int err; if (!chip->info->ops->phy_read) return -EOPNOTSUPP; mv88e6xxx_reg_lock(chip); err = chip->info->ops->phy_read(chip, bus, phy, reg, &val); mv88e6xxx_reg_unlock(chip); /* Some internal PHYs don't have a model number. */ if (reg == MII_PHYSID2 && !(val & 0x3f0) && chip->info->family < ARRAY_SIZE(family_prod_id_table)) { prod_id = family_prod_id_table[chip->info->family]; if (prod_id) val |= prod_id >> 4; } return err ? err : val; } static int mv88e6xxx_mdio_write(struct mii_bus *bus, int phy, int reg, u16 val) { struct mv88e6xxx_mdio_bus *mdio_bus = bus->priv; struct mv88e6xxx_chip *chip = mdio_bus->chip; int err; if (!chip->info->ops->phy_write) return -EOPNOTSUPP; mv88e6xxx_reg_lock(chip); err = chip->info->ops->phy_write(chip, bus, phy, reg, val); mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_mdio_register(struct mv88e6xxx_chip *chip, struct device_node *np, bool external) { static int index; struct mv88e6xxx_mdio_bus *mdio_bus; struct mii_bus *bus; int err; if (external) { mv88e6xxx_reg_lock(chip); err = mv88e6xxx_g2_scratch_gpio_set_smi(chip, true); mv88e6xxx_reg_unlock(chip); if (err) return err; } bus = mdiobus_alloc_size(sizeof(*mdio_bus)); if (!bus) return -ENOMEM; mdio_bus = bus->priv; mdio_bus->bus = bus; mdio_bus->chip = chip; INIT_LIST_HEAD(&mdio_bus->list); mdio_bus->external = external; if (np) { bus->name = np->full_name; snprintf(bus->id, MII_BUS_ID_SIZE, "%pOF", np); } else { bus->name = "mv88e6xxx SMI"; snprintf(bus->id, MII_BUS_ID_SIZE, "mv88e6xxx-%d", index++); } bus->read = mv88e6xxx_mdio_read; bus->write = mv88e6xxx_mdio_write; bus->parent = chip->dev; if (!external) { err = mv88e6xxx_g2_irq_mdio_setup(chip, bus); if (err) goto out; } err = of_mdiobus_register(bus, np); if (err) { dev_err(chip->dev, "Cannot register MDIO bus (%d)\n", err); mv88e6xxx_g2_irq_mdio_free(chip, bus); goto out; } if (external) list_add_tail(&mdio_bus->list, &chip->mdios); else list_add(&mdio_bus->list, &chip->mdios); return 0; out: mdiobus_free(bus); return err; } static void mv88e6xxx_mdios_unregister(struct mv88e6xxx_chip *chip) { struct mv88e6xxx_mdio_bus *mdio_bus, *p; struct mii_bus *bus; list_for_each_entry_safe(mdio_bus, p, &chip->mdios, list) { bus = mdio_bus->bus; if (!mdio_bus->external) mv88e6xxx_g2_irq_mdio_free(chip, bus); mdiobus_unregister(bus); mdiobus_free(bus); } } static int mv88e6xxx_mdios_register(struct mv88e6xxx_chip *chip, struct device_node *np) { struct device_node *child; int err; /* Always register one mdio bus for the internal/default mdio * bus. This maybe represented in the device tree, but is * optional. */ child = of_get_child_by_name(np, "mdio"); err = mv88e6xxx_mdio_register(chip, child, false); of_node_put(child); if (err) return err; /* Walk the device tree, and see if there are any other nodes * which say they are compatible with the external mdio * bus. */ for_each_available_child_of_node(np, child) { if (of_device_is_compatible( child, "marvell,mv88e6xxx-mdio-external")) { err = mv88e6xxx_mdio_register(chip, child, true); if (err) { mv88e6xxx_mdios_unregister(chip); of_node_put(child); return err; } } } return 0; } static int mv88e6xxx_get_eeprom_len(struct dsa_switch *ds) { struct mv88e6xxx_chip *chip = ds->priv; return chip->eeprom_len; } static int mv88e6xxx_get_eeprom(struct dsa_switch *ds, struct ethtool_eeprom *eeprom, u8 *data) { struct mv88e6xxx_chip *chip = ds->priv; int err; if (!chip->info->ops->get_eeprom) return -EOPNOTSUPP; mv88e6xxx_reg_lock(chip); err = chip->info->ops->get_eeprom(chip, eeprom, data); mv88e6xxx_reg_unlock(chip); if (err) return err; eeprom->magic = 0xc3ec4951; return 0; } static int mv88e6xxx_set_eeprom(struct dsa_switch *ds, struct ethtool_eeprom *eeprom, u8 *data) { struct mv88e6xxx_chip *chip = ds->priv; int err; if (!chip->info->ops->set_eeprom) return -EOPNOTSUPP; if (eeprom->magic != 0xc3ec4951) return -EINVAL; mv88e6xxx_reg_lock(chip); err = chip->info->ops->set_eeprom(chip, eeprom, data); mv88e6xxx_reg_unlock(chip); return err; } static const struct mv88e6xxx_ops mv88e6085_ops = { /* MV88E6XXX_FAMILY_6097 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .set_switch_mac = mv88e6xxx_g1_set_switch_mac, .phy_read = mv88e6185_phy_ppu_read, .phy_write = mv88e6185_phy_ppu_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_speed_duplex = mv88e6185_port_set_speed_duplex, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6185_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6xxx_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6097_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .ppu_enable = mv88e6185_g1_ppu_enable, .ppu_disable = mv88e6185_g1_ppu_disable, .reset = mv88e6185_g1_reset, .rmu_disable = mv88e6085_g1_rmu_disable, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .stu_getnext = mv88e6352_g1_stu_getnext, .stu_loadpurge = mv88e6352_g1_stu_loadpurge, .phylink_get_caps = mv88e6185_phylink_get_caps, .set_max_frame_size = mv88e6185_g1_set_max_frame_size, }; static const struct mv88e6xxx_ops mv88e6095_ops = { /* MV88E6XXX_FAMILY_6095 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .set_switch_mac = mv88e6xxx_g1_set_switch_mac, .phy_read = mv88e6185_phy_ppu_read, .phy_write = mv88e6185_phy_ppu_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6185_port_sync_link, .port_set_speed_duplex = mv88e6185_port_set_speed_duplex, .port_set_frame_mode = mv88e6085_port_set_frame_mode, .port_set_ucast_flood = mv88e6185_port_set_forward_unknown, .port_set_mcast_flood = mv88e6185_port_set_default_forward, .port_set_upstream_port = mv88e6095_port_set_upstream_port, .port_get_cmode = mv88e6185_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6xxx_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .mgmt_rsvd2cpu = mv88e6185_g2_mgmt_rsvd2cpu, .serdes_power = mv88e6185_serdes_power, .serdes_get_lane = mv88e6185_serdes_get_lane, .serdes_pcs_get_state = mv88e6185_serdes_pcs_get_state, .ppu_enable = mv88e6185_g1_ppu_enable, .ppu_disable = mv88e6185_g1_ppu_disable, .reset = mv88e6185_g1_reset, .vtu_getnext = mv88e6185_g1_vtu_getnext, .vtu_loadpurge = mv88e6185_g1_vtu_loadpurge, .phylink_get_caps = mv88e6095_phylink_get_caps, .set_max_frame_size = mv88e6185_g1_set_max_frame_size, }; static const struct mv88e6xxx_ops mv88e6097_ops = { /* MV88E6XXX_FAMILY_6097 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6185_port_sync_link, .port_set_speed_duplex = mv88e6185_port_set_speed_duplex, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_policy = mv88e6352_port_set_policy, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_egress_rate_limiting = mv88e6095_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6185_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6xxx_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6097_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .serdes_power = mv88e6185_serdes_power, .serdes_get_lane = mv88e6185_serdes_get_lane, .serdes_pcs_get_state = mv88e6185_serdes_pcs_get_state, .serdes_irq_mapping = mv88e6390_serdes_irq_mapping, .serdes_irq_enable = mv88e6097_serdes_irq_enable, .serdes_irq_status = mv88e6097_serdes_irq_status, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .rmu_disable = mv88e6085_g1_rmu_disable, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .phylink_get_caps = mv88e6095_phylink_get_caps, .stu_getnext = mv88e6352_g1_stu_getnext, .stu_loadpurge = mv88e6352_g1_stu_loadpurge, .set_max_frame_size = mv88e6185_g1_set_max_frame_size, }; static const struct mv88e6xxx_ops mv88e6123_ops = { /* MV88E6XXX_FAMILY_6165 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_speed_duplex = mv88e6185_port_set_speed_duplex, .port_set_frame_mode = mv88e6085_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6185_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6320_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6097_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .stu_getnext = mv88e6352_g1_stu_getnext, .stu_loadpurge = mv88e6352_g1_stu_loadpurge, .phylink_get_caps = mv88e6185_phylink_get_caps, .set_max_frame_size = mv88e6185_g1_set_max_frame_size, }; static const struct mv88e6xxx_ops mv88e6131_ops = { /* MV88E6XXX_FAMILY_6185 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .set_switch_mac = mv88e6xxx_g1_set_switch_mac, .phy_read = mv88e6185_phy_ppu_read, .phy_write = mv88e6185_phy_ppu_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_speed_duplex = mv88e6185_port_set_speed_duplex, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6185_port_set_forward_unknown, .port_set_mcast_flood = mv88e6185_port_set_default_forward, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_upstream_port = mv88e6095_port_set_upstream_port, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_set_pause = mv88e6185_port_set_pause, .port_get_cmode = mv88e6185_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6xxx_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6097_watchdog_ops, .mgmt_rsvd2cpu = mv88e6185_g2_mgmt_rsvd2cpu, .ppu_enable = mv88e6185_g1_ppu_enable, .set_cascade_port = mv88e6185_g1_set_cascade_port, .ppu_disable = mv88e6185_g1_ppu_disable, .reset = mv88e6185_g1_reset, .vtu_getnext = mv88e6185_g1_vtu_getnext, .vtu_loadpurge = mv88e6185_g1_vtu_loadpurge, .phylink_get_caps = mv88e6185_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6141_ops = { /* MV88E6XXX_FAMILY_6341 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom8, .set_eeprom = mv88e6xxx_g2_set_eeprom8, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6341_port_set_speed_duplex, .port_max_speed_mode = mv88e6341_port_max_speed_mode, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_policy = mv88e6352_port_set_policy, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_set_cmode = mv88e6341_port_set_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6390_g1_stats_snapshot, .stats_set_histogram = mv88e6390_g1_stats_set_histogram, .stats_get_sset_count = mv88e6320_stats_get_sset_count, .stats_get_strings = mv88e6320_stats_get_strings, .stats_get_stats = mv88e6390_stats_get_stats, .set_cpu_port = mv88e6390_g1_set_cpu_port, .set_egress_port = mv88e6390_g1_set_egress_port, .watchdog_ops = &mv88e6390_watchdog_ops, .mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .rmu_disable = mv88e6390_g1_rmu_disable, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .stu_getnext = mv88e6352_g1_stu_getnext, .stu_loadpurge = mv88e6352_g1_stu_loadpurge, .serdes_power = mv88e6390_serdes_power, .serdes_get_lane = mv88e6341_serdes_get_lane, /* Check status register pause & lpa register */ .serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state, .serdes_pcs_config = mv88e6390_serdes_pcs_config, .serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart, .serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up, .serdes_irq_mapping = mv88e6390_serdes_irq_mapping, .serdes_irq_enable = mv88e6390_serdes_irq_enable, .serdes_irq_status = mv88e6390_serdes_irq_status, .gpio_ops = &mv88e6352_gpio_ops, .serdes_get_sset_count = mv88e6390_serdes_get_sset_count, .serdes_get_strings = mv88e6390_serdes_get_strings, .serdes_get_stats = mv88e6390_serdes_get_stats, .serdes_get_regs_len = mv88e6390_serdes_get_regs_len, .serdes_get_regs = mv88e6390_serdes_get_regs, .phylink_get_caps = mv88e6341_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6161_ops = { /* MV88E6XXX_FAMILY_6165 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_speed_duplex = mv88e6185_port_set_speed_duplex, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6185_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6xxx_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6097_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .stu_getnext = mv88e6352_g1_stu_getnext, .stu_loadpurge = mv88e6352_g1_stu_loadpurge, .avb_ops = &mv88e6165_avb_ops, .ptp_ops = &mv88e6165_ptp_ops, .phylink_get_caps = mv88e6185_phylink_get_caps, .set_max_frame_size = mv88e6185_g1_set_max_frame_size, }; static const struct mv88e6xxx_ops mv88e6165_ops = { /* MV88E6XXX_FAMILY_6165 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6165_phy_read, .phy_write = mv88e6165_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_speed_duplex = mv88e6185_port_set_speed_duplex, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6185_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6xxx_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6097_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .stu_getnext = mv88e6352_g1_stu_getnext, .stu_loadpurge = mv88e6352_g1_stu_loadpurge, .avb_ops = &mv88e6165_avb_ops, .ptp_ops = &mv88e6165_ptp_ops, .phylink_get_caps = mv88e6185_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6171_ops = { /* MV88E6XXX_FAMILY_6351 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6185_port_set_speed_duplex, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6320_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6097_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .stu_getnext = mv88e6352_g1_stu_getnext, .stu_loadpurge = mv88e6352_g1_stu_loadpurge, .phylink_get_caps = mv88e6185_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6172_ops = { /* MV88E6XXX_FAMILY_6352 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom16, .set_eeprom = mv88e6xxx_g2_set_eeprom16, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6352_port_set_speed_duplex, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_policy = mv88e6352_port_set_policy, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6320_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6097_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .rmu_disable = mv88e6352_g1_rmu_disable, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .stu_getnext = mv88e6352_g1_stu_getnext, .stu_loadpurge = mv88e6352_g1_stu_loadpurge, .serdes_get_lane = mv88e6352_serdes_get_lane, .serdes_pcs_get_state = mv88e6352_serdes_pcs_get_state, .serdes_pcs_config = mv88e6352_serdes_pcs_config, .serdes_pcs_an_restart = mv88e6352_serdes_pcs_an_restart, .serdes_pcs_link_up = mv88e6352_serdes_pcs_link_up, .serdes_power = mv88e6352_serdes_power, .serdes_get_regs_len = mv88e6352_serdes_get_regs_len, .serdes_get_regs = mv88e6352_serdes_get_regs, .gpio_ops = &mv88e6352_gpio_ops, .phylink_get_caps = mv88e6352_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6175_ops = { /* MV88E6XXX_FAMILY_6351 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6185_port_set_speed_duplex, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6320_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6097_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .stu_getnext = mv88e6352_g1_stu_getnext, .stu_loadpurge = mv88e6352_g1_stu_loadpurge, .phylink_get_caps = mv88e6185_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6176_ops = { /* MV88E6XXX_FAMILY_6352 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom16, .set_eeprom = mv88e6xxx_g2_set_eeprom16, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6352_port_set_speed_duplex, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_policy = mv88e6352_port_set_policy, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6320_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6097_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .rmu_disable = mv88e6352_g1_rmu_disable, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .stu_getnext = mv88e6352_g1_stu_getnext, .stu_loadpurge = mv88e6352_g1_stu_loadpurge, .serdes_get_lane = mv88e6352_serdes_get_lane, .serdes_pcs_get_state = mv88e6352_serdes_pcs_get_state, .serdes_pcs_config = mv88e6352_serdes_pcs_config, .serdes_pcs_an_restart = mv88e6352_serdes_pcs_an_restart, .serdes_pcs_link_up = mv88e6352_serdes_pcs_link_up, .serdes_power = mv88e6352_serdes_power, .serdes_irq_mapping = mv88e6352_serdes_irq_mapping, .serdes_irq_enable = mv88e6352_serdes_irq_enable, .serdes_irq_status = mv88e6352_serdes_irq_status, .serdes_get_regs_len = mv88e6352_serdes_get_regs_len, .serdes_get_regs = mv88e6352_serdes_get_regs, .serdes_set_tx_amplitude = mv88e6352_serdes_set_tx_amplitude, .gpio_ops = &mv88e6352_gpio_ops, .phylink_get_caps = mv88e6352_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6185_ops = { /* MV88E6XXX_FAMILY_6185 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .set_switch_mac = mv88e6xxx_g1_set_switch_mac, .phy_read = mv88e6185_phy_ppu_read, .phy_write = mv88e6185_phy_ppu_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6185_port_sync_link, .port_set_speed_duplex = mv88e6185_port_set_speed_duplex, .port_set_frame_mode = mv88e6085_port_set_frame_mode, .port_set_ucast_flood = mv88e6185_port_set_forward_unknown, .port_set_mcast_flood = mv88e6185_port_set_default_forward, .port_egress_rate_limiting = mv88e6095_port_egress_rate_limiting, .port_set_upstream_port = mv88e6095_port_set_upstream_port, .port_set_pause = mv88e6185_port_set_pause, .port_get_cmode = mv88e6185_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6xxx_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6097_watchdog_ops, .mgmt_rsvd2cpu = mv88e6185_g2_mgmt_rsvd2cpu, .serdes_power = mv88e6185_serdes_power, .serdes_get_lane = mv88e6185_serdes_get_lane, .serdes_pcs_get_state = mv88e6185_serdes_pcs_get_state, .set_cascade_port = mv88e6185_g1_set_cascade_port, .ppu_enable = mv88e6185_g1_ppu_enable, .ppu_disable = mv88e6185_g1_ppu_disable, .reset = mv88e6185_g1_reset, .vtu_getnext = mv88e6185_g1_vtu_getnext, .vtu_loadpurge = mv88e6185_g1_vtu_loadpurge, .phylink_get_caps = mv88e6185_phylink_get_caps, .set_max_frame_size = mv88e6185_g1_set_max_frame_size, }; static const struct mv88e6xxx_ops mv88e6190_ops = { /* MV88E6XXX_FAMILY_6390 */ .setup_errata = mv88e6390_setup_errata, .irl_init_all = mv88e6390_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom8, .set_eeprom = mv88e6xxx_g2_set_eeprom8, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6390_port_set_speed_duplex, .port_max_speed_mode = mv88e6390_port_max_speed_mode, .port_tag_remap = mv88e6390_port_tag_remap, .port_set_policy = mv88e6352_port_set_policy, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_pause_limit = mv88e6390_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_set_cmode = mv88e6390_port_set_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6390_g1_stats_snapshot, .stats_set_histogram = mv88e6390_g1_stats_set_histogram, .stats_get_sset_count = mv88e6320_stats_get_sset_count, .stats_get_strings = mv88e6320_stats_get_strings, .stats_get_stats = mv88e6390_stats_get_stats, .set_cpu_port = mv88e6390_g1_set_cpu_port, .set_egress_port = mv88e6390_g1_set_egress_port, .watchdog_ops = &mv88e6390_watchdog_ops, .mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .rmu_disable = mv88e6390_g1_rmu_disable, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6390_g1_vtu_getnext, .vtu_loadpurge = mv88e6390_g1_vtu_loadpurge, .stu_getnext = mv88e6390_g1_stu_getnext, .stu_loadpurge = mv88e6390_g1_stu_loadpurge, .serdes_power = mv88e6390_serdes_power, .serdes_get_lane = mv88e6390_serdes_get_lane, /* Check status register pause & lpa register */ .serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state, .serdes_pcs_config = mv88e6390_serdes_pcs_config, .serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart, .serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up, .serdes_irq_mapping = mv88e6390_serdes_irq_mapping, .serdes_irq_enable = mv88e6390_serdes_irq_enable, .serdes_irq_status = mv88e6390_serdes_irq_status, .serdes_get_strings = mv88e6390_serdes_get_strings, .serdes_get_stats = mv88e6390_serdes_get_stats, .serdes_get_regs_len = mv88e6390_serdes_get_regs_len, .serdes_get_regs = mv88e6390_serdes_get_regs, .gpio_ops = &mv88e6352_gpio_ops, .phylink_get_caps = mv88e6390_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6190x_ops = { /* MV88E6XXX_FAMILY_6390 */ .setup_errata = mv88e6390_setup_errata, .irl_init_all = mv88e6390_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom8, .set_eeprom = mv88e6xxx_g2_set_eeprom8, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6390x_port_set_speed_duplex, .port_max_speed_mode = mv88e6390x_port_max_speed_mode, .port_tag_remap = mv88e6390_port_tag_remap, .port_set_policy = mv88e6352_port_set_policy, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_pause_limit = mv88e6390_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_set_cmode = mv88e6390x_port_set_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6390_g1_stats_snapshot, .stats_set_histogram = mv88e6390_g1_stats_set_histogram, .stats_get_sset_count = mv88e6320_stats_get_sset_count, .stats_get_strings = mv88e6320_stats_get_strings, .stats_get_stats = mv88e6390_stats_get_stats, .set_cpu_port = mv88e6390_g1_set_cpu_port, .set_egress_port = mv88e6390_g1_set_egress_port, .watchdog_ops = &mv88e6390_watchdog_ops, .mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .rmu_disable = mv88e6390_g1_rmu_disable, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6390_g1_vtu_getnext, .vtu_loadpurge = mv88e6390_g1_vtu_loadpurge, .stu_getnext = mv88e6390_g1_stu_getnext, .stu_loadpurge = mv88e6390_g1_stu_loadpurge, .serdes_power = mv88e6390_serdes_power, .serdes_get_lane = mv88e6390x_serdes_get_lane, /* Check status register pause & lpa register */ .serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state, .serdes_pcs_config = mv88e6390_serdes_pcs_config, .serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart, .serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up, .serdes_irq_mapping = mv88e6390_serdes_irq_mapping, .serdes_irq_enable = mv88e6390_serdes_irq_enable, .serdes_irq_status = mv88e6390_serdes_irq_status, .serdes_get_strings = mv88e6390_serdes_get_strings, .serdes_get_stats = mv88e6390_serdes_get_stats, .serdes_get_regs_len = mv88e6390_serdes_get_regs_len, .serdes_get_regs = mv88e6390_serdes_get_regs, .gpio_ops = &mv88e6352_gpio_ops, .phylink_get_caps = mv88e6390x_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6191_ops = { /* MV88E6XXX_FAMILY_6390 */ .setup_errata = mv88e6390_setup_errata, .irl_init_all = mv88e6390_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom8, .set_eeprom = mv88e6xxx_g2_set_eeprom8, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6390_port_set_speed_duplex, .port_max_speed_mode = mv88e6390_port_max_speed_mode, .port_tag_remap = mv88e6390_port_tag_remap, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_pause_limit = mv88e6390_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_set_cmode = mv88e6390_port_set_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6390_g1_stats_snapshot, .stats_set_histogram = mv88e6390_g1_stats_set_histogram, .stats_get_sset_count = mv88e6320_stats_get_sset_count, .stats_get_strings = mv88e6320_stats_get_strings, .stats_get_stats = mv88e6390_stats_get_stats, .set_cpu_port = mv88e6390_g1_set_cpu_port, .set_egress_port = mv88e6390_g1_set_egress_port, .watchdog_ops = &mv88e6390_watchdog_ops, .mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .rmu_disable = mv88e6390_g1_rmu_disable, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6390_g1_vtu_getnext, .vtu_loadpurge = mv88e6390_g1_vtu_loadpurge, .stu_getnext = mv88e6390_g1_stu_getnext, .stu_loadpurge = mv88e6390_g1_stu_loadpurge, .serdes_power = mv88e6390_serdes_power, .serdes_get_lane = mv88e6390_serdes_get_lane, /* Check status register pause & lpa register */ .serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state, .serdes_pcs_config = mv88e6390_serdes_pcs_config, .serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart, .serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up, .serdes_irq_mapping = mv88e6390_serdes_irq_mapping, .serdes_irq_enable = mv88e6390_serdes_irq_enable, .serdes_irq_status = mv88e6390_serdes_irq_status, .serdes_get_strings = mv88e6390_serdes_get_strings, .serdes_get_stats = mv88e6390_serdes_get_stats, .serdes_get_regs_len = mv88e6390_serdes_get_regs_len, .serdes_get_regs = mv88e6390_serdes_get_regs, .avb_ops = &mv88e6390_avb_ops, .ptp_ops = &mv88e6352_ptp_ops, .phylink_get_caps = mv88e6390_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6240_ops = { /* MV88E6XXX_FAMILY_6352 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom16, .set_eeprom = mv88e6xxx_g2_set_eeprom16, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6352_port_set_speed_duplex, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_policy = mv88e6352_port_set_policy, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6320_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6097_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .rmu_disable = mv88e6352_g1_rmu_disable, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .stu_getnext = mv88e6352_g1_stu_getnext, .stu_loadpurge = mv88e6352_g1_stu_loadpurge, .serdes_get_lane = mv88e6352_serdes_get_lane, .serdes_pcs_get_state = mv88e6352_serdes_pcs_get_state, .serdes_pcs_config = mv88e6352_serdes_pcs_config, .serdes_pcs_an_restart = mv88e6352_serdes_pcs_an_restart, .serdes_pcs_link_up = mv88e6352_serdes_pcs_link_up, .serdes_power = mv88e6352_serdes_power, .serdes_irq_mapping = mv88e6352_serdes_irq_mapping, .serdes_irq_enable = mv88e6352_serdes_irq_enable, .serdes_irq_status = mv88e6352_serdes_irq_status, .serdes_get_regs_len = mv88e6352_serdes_get_regs_len, .serdes_get_regs = mv88e6352_serdes_get_regs, .serdes_set_tx_amplitude = mv88e6352_serdes_set_tx_amplitude, .gpio_ops = &mv88e6352_gpio_ops, .avb_ops = &mv88e6352_avb_ops, .ptp_ops = &mv88e6352_ptp_ops, .phylink_get_caps = mv88e6352_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6250_ops = { /* MV88E6XXX_FAMILY_6250 */ .ieee_pri_map = mv88e6250_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom16, .set_eeprom = mv88e6xxx_g2_set_eeprom16, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6250_port_set_speed_duplex, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .stats_snapshot = mv88e6320_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6250_stats_get_sset_count, .stats_get_strings = mv88e6250_stats_get_strings, .stats_get_stats = mv88e6250_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6250_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6250_g1_reset, .vtu_getnext = mv88e6185_g1_vtu_getnext, .vtu_loadpurge = mv88e6185_g1_vtu_loadpurge, .avb_ops = &mv88e6352_avb_ops, .ptp_ops = &mv88e6250_ptp_ops, .phylink_get_caps = mv88e6250_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6290_ops = { /* MV88E6XXX_FAMILY_6390 */ .setup_errata = mv88e6390_setup_errata, .irl_init_all = mv88e6390_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom8, .set_eeprom = mv88e6xxx_g2_set_eeprom8, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6390_port_set_speed_duplex, .port_max_speed_mode = mv88e6390_port_max_speed_mode, .port_tag_remap = mv88e6390_port_tag_remap, .port_set_policy = mv88e6352_port_set_policy, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_pause_limit = mv88e6390_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_set_cmode = mv88e6390_port_set_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6390_g1_stats_snapshot, .stats_set_histogram = mv88e6390_g1_stats_set_histogram, .stats_get_sset_count = mv88e6320_stats_get_sset_count, .stats_get_strings = mv88e6320_stats_get_strings, .stats_get_stats = mv88e6390_stats_get_stats, .set_cpu_port = mv88e6390_g1_set_cpu_port, .set_egress_port = mv88e6390_g1_set_egress_port, .watchdog_ops = &mv88e6390_watchdog_ops, .mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .rmu_disable = mv88e6390_g1_rmu_disable, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6390_g1_vtu_getnext, .vtu_loadpurge = mv88e6390_g1_vtu_loadpurge, .stu_getnext = mv88e6390_g1_stu_getnext, .stu_loadpurge = mv88e6390_g1_stu_loadpurge, .serdes_power = mv88e6390_serdes_power, .serdes_get_lane = mv88e6390_serdes_get_lane, /* Check status register pause & lpa register */ .serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state, .serdes_pcs_config = mv88e6390_serdes_pcs_config, .serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart, .serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up, .serdes_irq_mapping = mv88e6390_serdes_irq_mapping, .serdes_irq_enable = mv88e6390_serdes_irq_enable, .serdes_irq_status = mv88e6390_serdes_irq_status, .serdes_get_strings = mv88e6390_serdes_get_strings, .serdes_get_stats = mv88e6390_serdes_get_stats, .serdes_get_regs_len = mv88e6390_serdes_get_regs_len, .serdes_get_regs = mv88e6390_serdes_get_regs, .gpio_ops = &mv88e6352_gpio_ops, .avb_ops = &mv88e6390_avb_ops, .ptp_ops = &mv88e6352_ptp_ops, .phylink_get_caps = mv88e6390_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6320_ops = { /* MV88E6XXX_FAMILY_6320 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom16, .set_eeprom = mv88e6xxx_g2_set_eeprom16, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6320_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6185_port_set_speed_duplex, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6320_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6320_stats_get_sset_count, .stats_get_strings = mv88e6320_stats_get_strings, .stats_get_stats = mv88e6320_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6390_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .vtu_getnext = mv88e6185_g1_vtu_getnext, .vtu_loadpurge = mv88e6185_g1_vtu_loadpurge, .gpio_ops = &mv88e6352_gpio_ops, .avb_ops = &mv88e6352_avb_ops, .ptp_ops = &mv88e6352_ptp_ops, .phylink_get_caps = mv88e6185_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6321_ops = { /* MV88E6XXX_FAMILY_6320 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom16, .set_eeprom = mv88e6xxx_g2_set_eeprom16, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6320_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6185_port_set_speed_duplex, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6320_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6320_stats_get_sset_count, .stats_get_strings = mv88e6320_stats_get_strings, .stats_get_stats = mv88e6320_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6390_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .reset = mv88e6352_g1_reset, .vtu_getnext = mv88e6185_g1_vtu_getnext, .vtu_loadpurge = mv88e6185_g1_vtu_loadpurge, .gpio_ops = &mv88e6352_gpio_ops, .avb_ops = &mv88e6352_avb_ops, .ptp_ops = &mv88e6352_ptp_ops, .phylink_get_caps = mv88e6185_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6341_ops = { /* MV88E6XXX_FAMILY_6341 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom8, .set_eeprom = mv88e6xxx_g2_set_eeprom8, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6341_port_set_speed_duplex, .port_max_speed_mode = mv88e6341_port_max_speed_mode, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_policy = mv88e6352_port_set_policy, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_set_cmode = mv88e6341_port_set_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6390_g1_stats_snapshot, .stats_set_histogram = mv88e6390_g1_stats_set_histogram, .stats_get_sset_count = mv88e6320_stats_get_sset_count, .stats_get_strings = mv88e6320_stats_get_strings, .stats_get_stats = mv88e6390_stats_get_stats, .set_cpu_port = mv88e6390_g1_set_cpu_port, .set_egress_port = mv88e6390_g1_set_egress_port, .watchdog_ops = &mv88e6390_watchdog_ops, .mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .rmu_disable = mv88e6390_g1_rmu_disable, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .stu_getnext = mv88e6352_g1_stu_getnext, .stu_loadpurge = mv88e6352_g1_stu_loadpurge, .serdes_power = mv88e6390_serdes_power, .serdes_get_lane = mv88e6341_serdes_get_lane, /* Check status register pause & lpa register */ .serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state, .serdes_pcs_config = mv88e6390_serdes_pcs_config, .serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart, .serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up, .serdes_irq_mapping = mv88e6390_serdes_irq_mapping, .serdes_irq_enable = mv88e6390_serdes_irq_enable, .serdes_irq_status = mv88e6390_serdes_irq_status, .gpio_ops = &mv88e6352_gpio_ops, .avb_ops = &mv88e6390_avb_ops, .ptp_ops = &mv88e6352_ptp_ops, .serdes_get_sset_count = mv88e6390_serdes_get_sset_count, .serdes_get_strings = mv88e6390_serdes_get_strings, .serdes_get_stats = mv88e6390_serdes_get_stats, .serdes_get_regs_len = mv88e6390_serdes_get_regs_len, .serdes_get_regs = mv88e6390_serdes_get_regs, .phylink_get_caps = mv88e6341_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6350_ops = { /* MV88E6XXX_FAMILY_6351 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6185_port_set_speed_duplex, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6320_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6097_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .stu_getnext = mv88e6352_g1_stu_getnext, .stu_loadpurge = mv88e6352_g1_stu_loadpurge, .phylink_get_caps = mv88e6185_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6351_ops = { /* MV88E6XXX_FAMILY_6351 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6185_port_set_speed_duplex, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6320_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6097_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .stu_getnext = mv88e6352_g1_stu_getnext, .stu_loadpurge = mv88e6352_g1_stu_loadpurge, .avb_ops = &mv88e6352_avb_ops, .ptp_ops = &mv88e6352_ptp_ops, .phylink_get_caps = mv88e6185_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6352_ops = { /* MV88E6XXX_FAMILY_6352 */ .ieee_pri_map = mv88e6085_g1_ieee_pri_map, .ip_pri_map = mv88e6085_g1_ip_pri_map, .irl_init_all = mv88e6352_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom16, .set_eeprom = mv88e6xxx_g2_set_eeprom16, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6352_port_set_speed_duplex, .port_tag_remap = mv88e6095_port_tag_remap, .port_set_policy = mv88e6352_port_set_policy, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6097_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6320_g1_stats_snapshot, .stats_set_histogram = mv88e6095_g1_stats_set_histogram, .stats_get_sset_count = mv88e6095_stats_get_sset_count, .stats_get_strings = mv88e6095_stats_get_strings, .stats_get_stats = mv88e6095_stats_get_stats, .set_cpu_port = mv88e6095_g1_set_cpu_port, .set_egress_port = mv88e6095_g1_set_egress_port, .watchdog_ops = &mv88e6097_watchdog_ops, .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .rmu_disable = mv88e6352_g1_rmu_disable, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .stu_getnext = mv88e6352_g1_stu_getnext, .stu_loadpurge = mv88e6352_g1_stu_loadpurge, .serdes_get_lane = mv88e6352_serdes_get_lane, .serdes_pcs_get_state = mv88e6352_serdes_pcs_get_state, .serdes_pcs_config = mv88e6352_serdes_pcs_config, .serdes_pcs_an_restart = mv88e6352_serdes_pcs_an_restart, .serdes_pcs_link_up = mv88e6352_serdes_pcs_link_up, .serdes_power = mv88e6352_serdes_power, .serdes_irq_mapping = mv88e6352_serdes_irq_mapping, .serdes_irq_enable = mv88e6352_serdes_irq_enable, .serdes_irq_status = mv88e6352_serdes_irq_status, .gpio_ops = &mv88e6352_gpio_ops, .avb_ops = &mv88e6352_avb_ops, .ptp_ops = &mv88e6352_ptp_ops, .serdes_get_sset_count = mv88e6352_serdes_get_sset_count, .serdes_get_strings = mv88e6352_serdes_get_strings, .serdes_get_stats = mv88e6352_serdes_get_stats, .serdes_get_regs_len = mv88e6352_serdes_get_regs_len, .serdes_get_regs = mv88e6352_serdes_get_regs, .serdes_set_tx_amplitude = mv88e6352_serdes_set_tx_amplitude, .phylink_get_caps = mv88e6352_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6390_ops = { /* MV88E6XXX_FAMILY_6390 */ .setup_errata = mv88e6390_setup_errata, .irl_init_all = mv88e6390_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom8, .set_eeprom = mv88e6xxx_g2_set_eeprom8, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6390_port_set_speed_duplex, .port_max_speed_mode = mv88e6390_port_max_speed_mode, .port_tag_remap = mv88e6390_port_tag_remap, .port_set_policy = mv88e6352_port_set_policy, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6390_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_set_cmode = mv88e6390_port_set_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6390_g1_stats_snapshot, .stats_set_histogram = mv88e6390_g1_stats_set_histogram, .stats_get_sset_count = mv88e6320_stats_get_sset_count, .stats_get_strings = mv88e6320_stats_get_strings, .stats_get_stats = mv88e6390_stats_get_stats, .set_cpu_port = mv88e6390_g1_set_cpu_port, .set_egress_port = mv88e6390_g1_set_egress_port, .watchdog_ops = &mv88e6390_watchdog_ops, .mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .rmu_disable = mv88e6390_g1_rmu_disable, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6390_g1_vtu_getnext, .vtu_loadpurge = mv88e6390_g1_vtu_loadpurge, .stu_getnext = mv88e6390_g1_stu_getnext, .stu_loadpurge = mv88e6390_g1_stu_loadpurge, .serdes_power = mv88e6390_serdes_power, .serdes_get_lane = mv88e6390_serdes_get_lane, /* Check status register pause & lpa register */ .serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state, .serdes_pcs_config = mv88e6390_serdes_pcs_config, .serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart, .serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up, .serdes_irq_mapping = mv88e6390_serdes_irq_mapping, .serdes_irq_enable = mv88e6390_serdes_irq_enable, .serdes_irq_status = mv88e6390_serdes_irq_status, .gpio_ops = &mv88e6352_gpio_ops, .avb_ops = &mv88e6390_avb_ops, .ptp_ops = &mv88e6352_ptp_ops, .serdes_get_sset_count = mv88e6390_serdes_get_sset_count, .serdes_get_strings = mv88e6390_serdes_get_strings, .serdes_get_stats = mv88e6390_serdes_get_stats, .serdes_get_regs_len = mv88e6390_serdes_get_regs_len, .serdes_get_regs = mv88e6390_serdes_get_regs, .phylink_get_caps = mv88e6390_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6390x_ops = { /* MV88E6XXX_FAMILY_6390 */ .setup_errata = mv88e6390_setup_errata, .irl_init_all = mv88e6390_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom8, .set_eeprom = mv88e6xxx_g2_set_eeprom8, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6390x_port_set_speed_duplex, .port_max_speed_mode = mv88e6390x_port_max_speed_mode, .port_tag_remap = mv88e6390_port_tag_remap, .port_set_policy = mv88e6352_port_set_policy, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6351_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6390_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_set_cmode = mv88e6390x_port_set_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .stats_snapshot = mv88e6390_g1_stats_snapshot, .stats_set_histogram = mv88e6390_g1_stats_set_histogram, .stats_get_sset_count = mv88e6320_stats_get_sset_count, .stats_get_strings = mv88e6320_stats_get_strings, .stats_get_stats = mv88e6390_stats_get_stats, .set_cpu_port = mv88e6390_g1_set_cpu_port, .set_egress_port = mv88e6390_g1_set_egress_port, .watchdog_ops = &mv88e6390_watchdog_ops, .mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .rmu_disable = mv88e6390_g1_rmu_disable, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6390_g1_vtu_getnext, .vtu_loadpurge = mv88e6390_g1_vtu_loadpurge, .stu_getnext = mv88e6390_g1_stu_getnext, .stu_loadpurge = mv88e6390_g1_stu_loadpurge, .serdes_power = mv88e6390_serdes_power, .serdes_get_lane = mv88e6390x_serdes_get_lane, .serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state, .serdes_pcs_config = mv88e6390_serdes_pcs_config, .serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart, .serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up, .serdes_irq_mapping = mv88e6390_serdes_irq_mapping, .serdes_irq_enable = mv88e6390_serdes_irq_enable, .serdes_irq_status = mv88e6390_serdes_irq_status, .serdes_get_sset_count = mv88e6390_serdes_get_sset_count, .serdes_get_strings = mv88e6390_serdes_get_strings, .serdes_get_stats = mv88e6390_serdes_get_stats, .serdes_get_regs_len = mv88e6390_serdes_get_regs_len, .serdes_get_regs = mv88e6390_serdes_get_regs, .gpio_ops = &mv88e6352_gpio_ops, .avb_ops = &mv88e6390_avb_ops, .ptp_ops = &mv88e6352_ptp_ops, .phylink_get_caps = mv88e6390x_phylink_get_caps, }; static const struct mv88e6xxx_ops mv88e6393x_ops = { /* MV88E6XXX_FAMILY_6393 */ .setup_errata = mv88e6393x_serdes_setup_errata, .irl_init_all = mv88e6390_g2_irl_init_all, .get_eeprom = mv88e6xxx_g2_get_eeprom8, .set_eeprom = mv88e6xxx_g2_set_eeprom8, .set_switch_mac = mv88e6xxx_g2_set_switch_mac, .phy_read = mv88e6xxx_g2_smi_phy_read, .phy_write = mv88e6xxx_g2_smi_phy_write, .port_set_link = mv88e6xxx_port_set_link, .port_sync_link = mv88e6xxx_port_sync_link, .port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay, .port_set_speed_duplex = mv88e6393x_port_set_speed_duplex, .port_max_speed_mode = mv88e6393x_port_max_speed_mode, .port_tag_remap = mv88e6390_port_tag_remap, .port_set_policy = mv88e6393x_port_set_policy, .port_set_frame_mode = mv88e6351_port_set_frame_mode, .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, .port_set_ether_type = mv88e6393x_port_set_ether_type, .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, .port_pause_limit = mv88e6390_port_pause_limit, .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, .port_get_cmode = mv88e6352_port_get_cmode, .port_set_cmode = mv88e6393x_port_set_cmode, .port_setup_message_port = mv88e6xxx_setup_message_port, .port_set_upstream_port = mv88e6393x_port_set_upstream_port, .stats_snapshot = mv88e6390_g1_stats_snapshot, .stats_set_histogram = mv88e6390_g1_stats_set_histogram, .stats_get_sset_count = mv88e6320_stats_get_sset_count, .stats_get_strings = mv88e6320_stats_get_strings, .stats_get_stats = mv88e6390_stats_get_stats, /* .set_cpu_port is missing because this family does not support a global * CPU port, only per port CPU port which is set via * .port_set_upstream_port method. */ .set_egress_port = mv88e6393x_set_egress_port, .watchdog_ops = &mv88e6393x_watchdog_ops, .mgmt_rsvd2cpu = mv88e6393x_port_mgmt_rsvd2cpu, .pot_clear = mv88e6xxx_g2_pot_clear, .reset = mv88e6352_g1_reset, .rmu_disable = mv88e6390_g1_rmu_disable, .atu_get_hash = mv88e6165_g1_atu_get_hash, .atu_set_hash = mv88e6165_g1_atu_set_hash, .vtu_getnext = mv88e6390_g1_vtu_getnext, .vtu_loadpurge = mv88e6390_g1_vtu_loadpurge, .stu_getnext = mv88e6390_g1_stu_getnext, .stu_loadpurge = mv88e6390_g1_stu_loadpurge, .serdes_power = mv88e6393x_serdes_power, .serdes_get_lane = mv88e6393x_serdes_get_lane, .serdes_pcs_get_state = mv88e6393x_serdes_pcs_get_state, .serdes_pcs_config = mv88e6390_serdes_pcs_config, .serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart, .serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up, .serdes_irq_mapping = mv88e6390_serdes_irq_mapping, .serdes_irq_enable = mv88e6393x_serdes_irq_enable, .serdes_irq_status = mv88e6393x_serdes_irq_status, /* TODO: serdes stats */ .gpio_ops = &mv88e6352_gpio_ops, .avb_ops = &mv88e6390_avb_ops, .ptp_ops = &mv88e6352_ptp_ops, .phylink_get_caps = mv88e6393x_phylink_get_caps, }; static const struct mv88e6xxx_info mv88e6xxx_table[] = { [MV88E6085] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6085, .family = MV88E6XXX_FAMILY_6097, .name = "Marvell 88E6085", .num_databases = 4096, .num_macs = 8192, .num_ports = 10, .num_internal_phys = 5, .max_vid = 4095, .max_sid = 63, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 8, .g2_irqs = 10, .atu_move_port_mask = 0xf, .pvt = true, .multi_chip = true, .ops = &mv88e6085_ops, }, [MV88E6095] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6095, .family = MV88E6XXX_FAMILY_6095, .name = "Marvell 88E6095/88E6095F", .num_databases = 256, .num_macs = 8192, .num_ports = 11, .num_internal_phys = 0, .max_vid = 4095, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 8, .atu_move_port_mask = 0xf, .multi_chip = true, .ops = &mv88e6095_ops, }, [MV88E6097] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6097, .family = MV88E6XXX_FAMILY_6097, .name = "Marvell 88E6097/88E6097F", .num_databases = 4096, .num_macs = 8192, .num_ports = 11, .num_internal_phys = 8, .max_vid = 4095, .max_sid = 63, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 8, .g2_irqs = 10, .atu_move_port_mask = 0xf, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ops = &mv88e6097_ops, }, [MV88E6123] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6123, .family = MV88E6XXX_FAMILY_6165, .name = "Marvell 88E6123", .num_databases = 4096, .num_macs = 1024, .num_ports = 3, .num_internal_phys = 5, .max_vid = 4095, .max_sid = 63, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 9, .g2_irqs = 10, .atu_move_port_mask = 0xf, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ops = &mv88e6123_ops, }, [MV88E6131] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6131, .family = MV88E6XXX_FAMILY_6185, .name = "Marvell 88E6131", .num_databases = 256, .num_macs = 8192, .num_ports = 8, .num_internal_phys = 0, .max_vid = 4095, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 9, .atu_move_port_mask = 0xf, .multi_chip = true, .ops = &mv88e6131_ops, }, [MV88E6141] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6141, .family = MV88E6XXX_FAMILY_6341, .name = "Marvell 88E6141", .num_databases = 4096, .num_macs = 2048, .num_ports = 6, .num_internal_phys = 5, .num_gpio = 11, .max_vid = 4095, .max_sid = 63, .port_base_addr = 0x10, .phy_base_addr = 0x10, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 3750, .atu_move_port_mask = 0x1f, .g1_irqs = 9, .g2_irqs = 10, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ops = &mv88e6141_ops, }, [MV88E6161] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6161, .family = MV88E6XXX_FAMILY_6165, .name = "Marvell 88E6161", .num_databases = 4096, .num_macs = 1024, .num_ports = 6, .num_internal_phys = 5, .max_vid = 4095, .max_sid = 63, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 9, .g2_irqs = 10, .atu_move_port_mask = 0xf, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ptp_support = true, .ops = &mv88e6161_ops, }, [MV88E6165] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6165, .family = MV88E6XXX_FAMILY_6165, .name = "Marvell 88E6165", .num_databases = 4096, .num_macs = 8192, .num_ports = 6, .num_internal_phys = 0, .max_vid = 4095, .max_sid = 63, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 9, .g2_irqs = 10, .atu_move_port_mask = 0xf, .pvt = true, .multi_chip = true, .ptp_support = true, .ops = &mv88e6165_ops, }, [MV88E6171] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6171, .family = MV88E6XXX_FAMILY_6351, .name = "Marvell 88E6171", .num_databases = 4096, .num_macs = 8192, .num_ports = 7, .num_internal_phys = 5, .max_vid = 4095, .max_sid = 63, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 9, .g2_irqs = 10, .atu_move_port_mask = 0xf, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ops = &mv88e6171_ops, }, [MV88E6172] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6172, .family = MV88E6XXX_FAMILY_6352, .name = "Marvell 88E6172", .num_databases = 4096, .num_macs = 8192, .num_ports = 7, .num_internal_phys = 5, .num_gpio = 15, .max_vid = 4095, .max_sid = 63, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 9, .g2_irqs = 10, .atu_move_port_mask = 0xf, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ops = &mv88e6172_ops, }, [MV88E6175] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6175, .family = MV88E6XXX_FAMILY_6351, .name = "Marvell 88E6175", .num_databases = 4096, .num_macs = 8192, .num_ports = 7, .num_internal_phys = 5, .max_vid = 4095, .max_sid = 63, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 9, .g2_irqs = 10, .atu_move_port_mask = 0xf, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ops = &mv88e6175_ops, }, [MV88E6176] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6176, .family = MV88E6XXX_FAMILY_6352, .name = "Marvell 88E6176", .num_databases = 4096, .num_macs = 8192, .num_ports = 7, .num_internal_phys = 5, .num_gpio = 15, .max_vid = 4095, .max_sid = 63, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 9, .g2_irqs = 10, .atu_move_port_mask = 0xf, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ops = &mv88e6176_ops, }, [MV88E6185] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6185, .family = MV88E6XXX_FAMILY_6185, .name = "Marvell 88E6185", .num_databases = 256, .num_macs = 8192, .num_ports = 10, .num_internal_phys = 0, .max_vid = 4095, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 8, .atu_move_port_mask = 0xf, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ops = &mv88e6185_ops, }, [MV88E6190] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6190, .family = MV88E6XXX_FAMILY_6390, .name = "Marvell 88E6190", .num_databases = 4096, .num_macs = 16384, .num_ports = 11, /* 10 + Z80 */ .num_internal_phys = 9, .num_gpio = 16, .max_vid = 8191, .max_sid = 63, .port_base_addr = 0x0, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 3750, .g1_irqs = 9, .g2_irqs = 14, .pvt = true, .multi_chip = true, .atu_move_port_mask = 0x1f, .ops = &mv88e6190_ops, }, [MV88E6190X] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6190X, .family = MV88E6XXX_FAMILY_6390, .name = "Marvell 88E6190X", .num_databases = 4096, .num_macs = 16384, .num_ports = 11, /* 10 + Z80 */ .num_internal_phys = 9, .num_gpio = 16, .max_vid = 8191, .max_sid = 63, .port_base_addr = 0x0, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 3750, .g1_irqs = 9, .g2_irqs = 14, .atu_move_port_mask = 0x1f, .pvt = true, .multi_chip = true, .ops = &mv88e6190x_ops, }, [MV88E6191] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6191, .family = MV88E6XXX_FAMILY_6390, .name = "Marvell 88E6191", .num_databases = 4096, .num_macs = 16384, .num_ports = 11, /* 10 + Z80 */ .num_internal_phys = 9, .max_vid = 8191, .max_sid = 63, .port_base_addr = 0x0, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 3750, .g1_irqs = 9, .g2_irqs = 14, .atu_move_port_mask = 0x1f, .pvt = true, .multi_chip = true, .ptp_support = true, .ops = &mv88e6191_ops, }, [MV88E6191X] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6191X, .family = MV88E6XXX_FAMILY_6393, .name = "Marvell 88E6191X", .num_databases = 4096, .num_ports = 11, /* 10 + Z80 */ .num_internal_phys = 9, .max_vid = 8191, .max_sid = 63, .port_base_addr = 0x0, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 3750, .g1_irqs = 10, .g2_irqs = 14, .atu_move_port_mask = 0x1f, .pvt = true, .multi_chip = true, .ptp_support = true, .ops = &mv88e6393x_ops, }, [MV88E6193X] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6193X, .family = MV88E6XXX_FAMILY_6393, .name = "Marvell 88E6193X", .num_databases = 4096, .num_ports = 11, /* 10 + Z80 */ .num_internal_phys = 9, .max_vid = 8191, .max_sid = 63, .port_base_addr = 0x0, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 3750, .g1_irqs = 10, .g2_irqs = 14, .atu_move_port_mask = 0x1f, .pvt = true, .multi_chip = true, .ptp_support = true, .ops = &mv88e6393x_ops, }, [MV88E6220] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6220, .family = MV88E6XXX_FAMILY_6250, .name = "Marvell 88E6220", .num_databases = 64, /* Ports 2-4 are not routed to pins * => usable ports 0, 1, 5, 6 */ .num_ports = 7, .num_internal_phys = 2, .invalid_port_mask = BIT(2) | BIT(3) | BIT(4), .max_vid = 4095, .port_base_addr = 0x08, .phy_base_addr = 0x00, .global1_addr = 0x0f, .global2_addr = 0x07, .age_time_coeff = 15000, .g1_irqs = 9, .g2_irqs = 10, .atu_move_port_mask = 0xf, .dual_chip = true, .ptp_support = true, .ops = &mv88e6250_ops, }, [MV88E6240] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6240, .family = MV88E6XXX_FAMILY_6352, .name = "Marvell 88E6240", .num_databases = 4096, .num_macs = 8192, .num_ports = 7, .num_internal_phys = 5, .num_gpio = 15, .max_vid = 4095, .max_sid = 63, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 9, .g2_irqs = 10, .atu_move_port_mask = 0xf, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ptp_support = true, .ops = &mv88e6240_ops, }, [MV88E6250] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6250, .family = MV88E6XXX_FAMILY_6250, .name = "Marvell 88E6250", .num_databases = 64, .num_ports = 7, .num_internal_phys = 5, .max_vid = 4095, .port_base_addr = 0x08, .phy_base_addr = 0x00, .global1_addr = 0x0f, .global2_addr = 0x07, .age_time_coeff = 15000, .g1_irqs = 9, .g2_irqs = 10, .atu_move_port_mask = 0xf, .dual_chip = true, .ptp_support = true, .ops = &mv88e6250_ops, }, [MV88E6290] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6290, .family = MV88E6XXX_FAMILY_6390, .name = "Marvell 88E6290", .num_databases = 4096, .num_ports = 11, /* 10 + Z80 */ .num_internal_phys = 9, .num_gpio = 16, .max_vid = 8191, .max_sid = 63, .port_base_addr = 0x0, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 3750, .g1_irqs = 9, .g2_irqs = 14, .atu_move_port_mask = 0x1f, .pvt = true, .multi_chip = true, .ptp_support = true, .ops = &mv88e6290_ops, }, [MV88E6320] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6320, .family = MV88E6XXX_FAMILY_6320, .name = "Marvell 88E6320", .num_databases = 4096, .num_macs = 8192, .num_ports = 7, .num_internal_phys = 5, .num_gpio = 15, .max_vid = 4095, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 8, .g2_irqs = 10, .atu_move_port_mask = 0xf, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ptp_support = true, .ops = &mv88e6320_ops, }, [MV88E6321] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6321, .family = MV88E6XXX_FAMILY_6320, .name = "Marvell 88E6321", .num_databases = 4096, .num_macs = 8192, .num_ports = 7, .num_internal_phys = 5, .num_gpio = 15, .max_vid = 4095, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 8, .g2_irqs = 10, .atu_move_port_mask = 0xf, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ptp_support = true, .ops = &mv88e6321_ops, }, [MV88E6341] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6341, .family = MV88E6XXX_FAMILY_6341, .name = "Marvell 88E6341", .num_databases = 4096, .num_macs = 2048, .num_internal_phys = 5, .num_ports = 6, .num_gpio = 11, .max_vid = 4095, .max_sid = 63, .port_base_addr = 0x10, .phy_base_addr = 0x10, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 3750, .atu_move_port_mask = 0x1f, .g1_irqs = 9, .g2_irqs = 10, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ptp_support = true, .ops = &mv88e6341_ops, }, [MV88E6350] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6350, .family = MV88E6XXX_FAMILY_6351, .name = "Marvell 88E6350", .num_databases = 4096, .num_macs = 8192, .num_ports = 7, .num_internal_phys = 5, .max_vid = 4095, .max_sid = 63, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 9, .g2_irqs = 10, .atu_move_port_mask = 0xf, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ops = &mv88e6350_ops, }, [MV88E6351] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6351, .family = MV88E6XXX_FAMILY_6351, .name = "Marvell 88E6351", .num_databases = 4096, .num_macs = 8192, .num_ports = 7, .num_internal_phys = 5, .max_vid = 4095, .max_sid = 63, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 9, .g2_irqs = 10, .atu_move_port_mask = 0xf, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ops = &mv88e6351_ops, }, [MV88E6352] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6352, .family = MV88E6XXX_FAMILY_6352, .name = "Marvell 88E6352", .num_databases = 4096, .num_macs = 8192, .num_ports = 7, .num_internal_phys = 5, .num_gpio = 15, .max_vid = 4095, .max_sid = 63, .port_base_addr = 0x10, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 15000, .g1_irqs = 9, .g2_irqs = 10, .atu_move_port_mask = 0xf, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_SUPPORTED, .ptp_support = true, .ops = &mv88e6352_ops, }, [MV88E6390] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6390, .family = MV88E6XXX_FAMILY_6390, .name = "Marvell 88E6390", .num_databases = 4096, .num_macs = 16384, .num_ports = 11, /* 10 + Z80 */ .num_internal_phys = 9, .num_gpio = 16, .max_vid = 8191, .max_sid = 63, .port_base_addr = 0x0, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 3750, .g1_irqs = 9, .g2_irqs = 14, .atu_move_port_mask = 0x1f, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_UNDOCUMENTED, .ptp_support = true, .ops = &mv88e6390_ops, }, [MV88E6390X] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6390X, .family = MV88E6XXX_FAMILY_6390, .name = "Marvell 88E6390X", .num_databases = 4096, .num_macs = 16384, .num_ports = 11, /* 10 + Z80 */ .num_internal_phys = 9, .num_gpio = 16, .max_vid = 8191, .max_sid = 63, .port_base_addr = 0x0, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 3750, .g1_irqs = 9, .g2_irqs = 14, .atu_move_port_mask = 0x1f, .pvt = true, .multi_chip = true, .edsa_support = MV88E6XXX_EDSA_UNDOCUMENTED, .ptp_support = true, .ops = &mv88e6390x_ops, }, [MV88E6393X] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6393X, .family = MV88E6XXX_FAMILY_6393, .name = "Marvell 88E6393X", .num_databases = 4096, .num_ports = 11, /* 10 + Z80 */ .num_internal_phys = 9, .max_vid = 8191, .max_sid = 63, .port_base_addr = 0x0, .phy_base_addr = 0x0, .global1_addr = 0x1b, .global2_addr = 0x1c, .age_time_coeff = 3750, .g1_irqs = 10, .g2_irqs = 14, .atu_move_port_mask = 0x1f, .pvt = true, .multi_chip = true, .ptp_support = true, .ops = &mv88e6393x_ops, }, }; static const struct mv88e6xxx_info *mv88e6xxx_lookup_info(unsigned int prod_num) { int i; for (i = 0; i < ARRAY_SIZE(mv88e6xxx_table); ++i) if (mv88e6xxx_table[i].prod_num == prod_num) return &mv88e6xxx_table[i]; return NULL; } static int mv88e6xxx_detect(struct mv88e6xxx_chip *chip) { const struct mv88e6xxx_info *info; unsigned int prod_num, rev; u16 id; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_read(chip, 0, MV88E6XXX_PORT_SWITCH_ID, &id); mv88e6xxx_reg_unlock(chip); if (err) return err; prod_num = id & MV88E6XXX_PORT_SWITCH_ID_PROD_MASK; rev = id & MV88E6XXX_PORT_SWITCH_ID_REV_MASK; info = mv88e6xxx_lookup_info(prod_num); if (!info) return -ENODEV; /* Update the compatible info with the probed one */ chip->info = info; dev_info(chip->dev, "switch 0x%x detected: %s, revision %u\n", chip->info->prod_num, chip->info->name, rev); return 0; } static int mv88e6xxx_single_chip_detect(struct mv88e6xxx_chip *chip, struct mdio_device *mdiodev) { int err; /* dual_chip takes precedence over single/multi-chip modes */ if (chip->info->dual_chip) return -EINVAL; /* If the mdio addr is 16 indicating the first port address of a switch * (e.g. mv88e6*41) in single chip addressing mode the device may be * configured in single chip addressing mode. Setup the smi access as * single chip addressing mode and attempt to detect the model of the * switch, if this fails the device is not configured in single chip * addressing mode. */ if (mdiodev->addr != 16) return -EINVAL; err = mv88e6xxx_smi_init(chip, mdiodev->bus, 0); if (err) return err; return mv88e6xxx_detect(chip); } static struct mv88e6xxx_chip *mv88e6xxx_alloc_chip(struct device *dev) { struct mv88e6xxx_chip *chip; chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); if (!chip) return NULL; chip->dev = dev; mutex_init(&chip->reg_lock); INIT_LIST_HEAD(&chip->mdios); idr_init(&chip->policies); INIT_LIST_HEAD(&chip->msts); return chip; } static enum dsa_tag_protocol mv88e6xxx_get_tag_protocol(struct dsa_switch *ds, int port, enum dsa_tag_protocol m) { struct mv88e6xxx_chip *chip = ds->priv; return chip->tag_protocol; } static int mv88e6xxx_change_tag_protocol(struct dsa_switch *ds, enum dsa_tag_protocol proto) { struct mv88e6xxx_chip *chip = ds->priv; enum dsa_tag_protocol old_protocol; struct dsa_port *cpu_dp; int err; switch (proto) { case DSA_TAG_PROTO_EDSA: switch (chip->info->edsa_support) { case MV88E6XXX_EDSA_UNSUPPORTED: return -EPROTONOSUPPORT; case MV88E6XXX_EDSA_UNDOCUMENTED: dev_warn(chip->dev, "Relying on undocumented EDSA tagging behavior\n"); fallthrough; case MV88E6XXX_EDSA_SUPPORTED: break; } break; case DSA_TAG_PROTO_DSA: break; default: return -EPROTONOSUPPORT; } old_protocol = chip->tag_protocol; chip->tag_protocol = proto; mv88e6xxx_reg_lock(chip); dsa_switch_for_each_cpu_port(cpu_dp, ds) { err = mv88e6xxx_setup_port_mode(chip, cpu_dp->index); if (err) { mv88e6xxx_reg_unlock(chip); goto unwind; } } mv88e6xxx_reg_unlock(chip); return 0; unwind: chip->tag_protocol = old_protocol; mv88e6xxx_reg_lock(chip); dsa_switch_for_each_cpu_port_continue_reverse(cpu_dp, ds) mv88e6xxx_setup_port_mode(chip, cpu_dp->index); mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_port_mdb_add(struct dsa_switch *ds, int port, const struct switchdev_obj_port_mdb *mdb, struct dsa_db db) { struct mv88e6xxx_chip *chip = ds->priv; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_db_load_purge(chip, port, mdb->addr, mdb->vid, MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC); mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_port_mdb_del(struct dsa_switch *ds, int port, const struct switchdev_obj_port_mdb *mdb, struct dsa_db db) { struct mv88e6xxx_chip *chip = ds->priv; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_db_load_purge(chip, port, mdb->addr, mdb->vid, 0); mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_port_mirror_add(struct dsa_switch *ds, int port, struct dsa_mall_mirror_tc_entry *mirror, bool ingress, struct netlink_ext_ack *extack) { enum mv88e6xxx_egress_direction direction = ingress ? MV88E6XXX_EGRESS_DIR_INGRESS : MV88E6XXX_EGRESS_DIR_EGRESS; struct mv88e6xxx_chip *chip = ds->priv; bool other_mirrors = false; int i; int err; mutex_lock(&chip->reg_lock); if ((ingress ? chip->ingress_dest_port : chip->egress_dest_port) != mirror->to_local_port) { for (i = 0; i < mv88e6xxx_num_ports(chip); i++) other_mirrors |= ingress ? chip->ports[i].mirror_ingress : chip->ports[i].mirror_egress; /* Can't change egress port when other mirror is active */ if (other_mirrors) { err = -EBUSY; goto out; } err = mv88e6xxx_set_egress_port(chip, direction, mirror->to_local_port); if (err) goto out; } err = mv88e6xxx_port_set_mirror(chip, port, direction, true); out: mutex_unlock(&chip->reg_lock); return err; } static void mv88e6xxx_port_mirror_del(struct dsa_switch *ds, int port, struct dsa_mall_mirror_tc_entry *mirror) { enum mv88e6xxx_egress_direction direction = mirror->ingress ? MV88E6XXX_EGRESS_DIR_INGRESS : MV88E6XXX_EGRESS_DIR_EGRESS; struct mv88e6xxx_chip *chip = ds->priv; bool other_mirrors = false; int i; mutex_lock(&chip->reg_lock); if (mv88e6xxx_port_set_mirror(chip, port, direction, false)) dev_err(ds->dev, "p%d: failed to disable mirroring\n", port); for (i = 0; i < mv88e6xxx_num_ports(chip); i++) other_mirrors |= mirror->ingress ? chip->ports[i].mirror_ingress : chip->ports[i].mirror_egress; /* Reset egress port when no other mirror is active */ if (!other_mirrors) { if (mv88e6xxx_set_egress_port(chip, direction, dsa_upstream_port(ds, port))) dev_err(ds->dev, "failed to set egress port\n"); } mutex_unlock(&chip->reg_lock); } static int mv88e6xxx_port_pre_bridge_flags(struct dsa_switch *ds, int port, struct switchdev_brport_flags flags, struct netlink_ext_ack *extack) { struct mv88e6xxx_chip *chip = ds->priv; const struct mv88e6xxx_ops *ops; if (flags.mask & ~(BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD | BR_PORT_LOCKED)) return -EINVAL; ops = chip->info->ops; if ((flags.mask & BR_FLOOD) && !ops->port_set_ucast_flood) return -EINVAL; if ((flags.mask & BR_MCAST_FLOOD) && !ops->port_set_mcast_flood) return -EINVAL; return 0; } static int mv88e6xxx_port_bridge_flags(struct dsa_switch *ds, int port, struct switchdev_brport_flags flags, struct netlink_ext_ack *extack) { struct mv88e6xxx_chip *chip = ds->priv; int err = -EOPNOTSUPP; mv88e6xxx_reg_lock(chip); if (flags.mask & BR_LEARNING) { bool learning = !!(flags.val & BR_LEARNING); u16 pav = learning ? (1 << port) : 0; err = mv88e6xxx_port_set_assoc_vector(chip, port, pav); if (err) goto out; } if (flags.mask & BR_FLOOD) { bool unicast = !!(flags.val & BR_FLOOD); err = chip->info->ops->port_set_ucast_flood(chip, port, unicast); if (err) goto out; } if (flags.mask & BR_MCAST_FLOOD) { bool multicast = !!(flags.val & BR_MCAST_FLOOD); err = chip->info->ops->port_set_mcast_flood(chip, port, multicast); if (err) goto out; } if (flags.mask & BR_BCAST_FLOOD) { bool broadcast = !!(flags.val & BR_BCAST_FLOOD); err = mv88e6xxx_port_broadcast_sync(chip, port, broadcast); if (err) goto out; } if (flags.mask & BR_PORT_LOCKED) { bool locked = !!(flags.val & BR_PORT_LOCKED); err = mv88e6xxx_port_set_lock(chip, port, locked); if (err) goto out; } out: mv88e6xxx_reg_unlock(chip); return err; } static bool mv88e6xxx_lag_can_offload(struct dsa_switch *ds, struct dsa_lag lag, struct netdev_lag_upper_info *info, struct netlink_ext_ack *extack) { struct mv88e6xxx_chip *chip = ds->priv; struct dsa_port *dp; int members = 0; if (!mv88e6xxx_has_lag(chip)) { NL_SET_ERR_MSG_MOD(extack, "Chip does not support LAG offload"); return false; } if (!lag.id) return false; dsa_lag_foreach_port(dp, ds->dst, &lag) /* Includes the port joining the LAG */ members++; if (members > 8) { NL_SET_ERR_MSG_MOD(extack, "Cannot offload more than 8 LAG ports"); return false; } /* We could potentially relax this to include active * backup in the future. */ if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) { NL_SET_ERR_MSG_MOD(extack, "Can only offload LAG using hash TX type"); return false; } /* Ideally we would also validate that the hash type matches * the hardware. Alas, this is always set to unknown on team * interfaces. */ return true; } static int mv88e6xxx_lag_sync_map(struct dsa_switch *ds, struct dsa_lag lag) { struct mv88e6xxx_chip *chip = ds->priv; struct dsa_port *dp; u16 map = 0; int id; /* DSA LAG IDs are one-based, hardware is zero-based */ id = lag.id - 1; /* Build the map of all ports to distribute flows destined for * this LAG. This can be either a local user port, or a DSA * port if the LAG port is on a remote chip. */ dsa_lag_foreach_port(dp, ds->dst, &lag) map |= BIT(dsa_towards_port(ds, dp->ds->index, dp->index)); return mv88e6xxx_g2_trunk_mapping_write(chip, id, map); } static const u8 mv88e6xxx_lag_mask_table[8][8] = { /* Row number corresponds to the number of active members in a * LAG. Each column states which of the eight hash buckets are * mapped to the column:th port in the LAG. * * Example: In a LAG with three active ports, the second port * ([2][1]) would be selected for traffic mapped to buckets * 3,4,5 (0x38). */ { 0xff, 0, 0, 0, 0, 0, 0, 0 }, { 0x0f, 0xf0, 0, 0, 0, 0, 0, 0 }, { 0x07, 0x38, 0xc0, 0, 0, 0, 0, 0 }, { 0x03, 0x0c, 0x30, 0xc0, 0, 0, 0, 0 }, { 0x03, 0x0c, 0x30, 0x40, 0x80, 0, 0, 0 }, { 0x03, 0x0c, 0x10, 0x20, 0x40, 0x80, 0, 0 }, { 0x03, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0 }, { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }, }; static void mv88e6xxx_lag_set_port_mask(u16 *mask, int port, int num_tx, int nth) { u8 active = 0; int i; num_tx = num_tx <= 8 ? num_tx : 8; if (nth < num_tx) active = mv88e6xxx_lag_mask_table[num_tx - 1][nth]; for (i = 0; i < 8; i++) { if (BIT(i) & active) mask[i] |= BIT(port); } } static int mv88e6xxx_lag_sync_masks(struct dsa_switch *ds) { struct mv88e6xxx_chip *chip = ds->priv; unsigned int id, num_tx; struct dsa_port *dp; struct dsa_lag *lag; int i, err, nth; u16 mask[8]; u16 ivec; /* Assume no port is a member of any LAG. */ ivec = BIT(mv88e6xxx_num_ports(chip)) - 1; /* Disable all masks for ports that _are_ members of a LAG. */ dsa_switch_for_each_port(dp, ds) { if (!dp->lag) continue; ivec &= ~BIT(dp->index); } for (i = 0; i < 8; i++) mask[i] = ivec; /* Enable the correct subset of masks for all LAG ports that * are in the Tx set. */ dsa_lags_foreach_id(id, ds->dst) { lag = dsa_lag_by_id(ds->dst, id); if (!lag) continue; num_tx = 0; dsa_lag_foreach_port(dp, ds->dst, lag) { if (dp->lag_tx_enabled) num_tx++; } if (!num_tx) continue; nth = 0; dsa_lag_foreach_port(dp, ds->dst, lag) { if (!dp->lag_tx_enabled) continue; if (dp->ds == ds) mv88e6xxx_lag_set_port_mask(mask, dp->index, num_tx, nth); nth++; } } for (i = 0; i < 8; i++) { err = mv88e6xxx_g2_trunk_mask_write(chip, i, true, mask[i]); if (err) return err; } return 0; } static int mv88e6xxx_lag_sync_masks_map(struct dsa_switch *ds, struct dsa_lag lag) { int err; err = mv88e6xxx_lag_sync_masks(ds); if (!err) err = mv88e6xxx_lag_sync_map(ds, lag); return err; } static int mv88e6xxx_port_lag_change(struct dsa_switch *ds, int port) { struct mv88e6xxx_chip *chip = ds->priv; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_lag_sync_masks(ds); mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_port_lag_join(struct dsa_switch *ds, int port, struct dsa_lag lag, struct netdev_lag_upper_info *info, struct netlink_ext_ack *extack) { struct mv88e6xxx_chip *chip = ds->priv; int err, id; if (!mv88e6xxx_lag_can_offload(ds, lag, info, extack)) return -EOPNOTSUPP; /* DSA LAG IDs are one-based */ id = lag.id - 1; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_set_trunk(chip, port, true, id); if (err) goto err_unlock; err = mv88e6xxx_lag_sync_masks_map(ds, lag); if (err) goto err_clear_trunk; mv88e6xxx_reg_unlock(chip); return 0; err_clear_trunk: mv88e6xxx_port_set_trunk(chip, port, false, 0); err_unlock: mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_port_lag_leave(struct dsa_switch *ds, int port, struct dsa_lag lag) { struct mv88e6xxx_chip *chip = ds->priv; int err_sync, err_trunk; mv88e6xxx_reg_lock(chip); err_sync = mv88e6xxx_lag_sync_masks_map(ds, lag); err_trunk = mv88e6xxx_port_set_trunk(chip, port, false, 0); mv88e6xxx_reg_unlock(chip); return err_sync ? : err_trunk; } static int mv88e6xxx_crosschip_lag_change(struct dsa_switch *ds, int sw_index, int port) { struct mv88e6xxx_chip *chip = ds->priv; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_lag_sync_masks(ds); mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_crosschip_lag_join(struct dsa_switch *ds, int sw_index, int port, struct dsa_lag lag, struct netdev_lag_upper_info *info, struct netlink_ext_ack *extack) { struct mv88e6xxx_chip *chip = ds->priv; int err; if (!mv88e6xxx_lag_can_offload(ds, lag, info, extack)) return -EOPNOTSUPP; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_lag_sync_masks_map(ds, lag); if (err) goto unlock; err = mv88e6xxx_pvt_map(chip, sw_index, port); unlock: mv88e6xxx_reg_unlock(chip); return err; } static int mv88e6xxx_crosschip_lag_leave(struct dsa_switch *ds, int sw_index, int port, struct dsa_lag lag) { struct mv88e6xxx_chip *chip = ds->priv; int err_sync, err_pvt; mv88e6xxx_reg_lock(chip); err_sync = mv88e6xxx_lag_sync_masks_map(ds, lag); err_pvt = mv88e6xxx_pvt_map(chip, sw_index, port); mv88e6xxx_reg_unlock(chip); return err_sync ? : err_pvt; } static const struct dsa_switch_ops mv88e6xxx_switch_ops = { .get_tag_protocol = mv88e6xxx_get_tag_protocol, .change_tag_protocol = mv88e6xxx_change_tag_protocol, .setup = mv88e6xxx_setup, .teardown = mv88e6xxx_teardown, .port_setup = mv88e6xxx_port_setup, .port_teardown = mv88e6xxx_port_teardown, .phylink_get_caps = mv88e6xxx_get_caps, .phylink_mac_link_state = mv88e6xxx_serdes_pcs_get_state, .phylink_mac_config = mv88e6xxx_mac_config, .phylink_mac_an_restart = mv88e6xxx_serdes_pcs_an_restart, .phylink_mac_link_down = mv88e6xxx_mac_link_down, .phylink_mac_link_up = mv88e6xxx_mac_link_up, .get_strings = mv88e6xxx_get_strings, .get_ethtool_stats = mv88e6xxx_get_ethtool_stats, .get_sset_count = mv88e6xxx_get_sset_count, .port_enable = mv88e6xxx_port_enable, .port_disable = mv88e6xxx_port_disable, .port_max_mtu = mv88e6xxx_get_max_mtu, .port_change_mtu = mv88e6xxx_change_mtu, .get_mac_eee = mv88e6xxx_get_mac_eee, .set_mac_eee = mv88e6xxx_set_mac_eee, .get_eeprom_len = mv88e6xxx_get_eeprom_len, .get_eeprom = mv88e6xxx_get_eeprom, .set_eeprom = mv88e6xxx_set_eeprom, .get_regs_len = mv88e6xxx_get_regs_len, .get_regs = mv88e6xxx_get_regs, .get_rxnfc = mv88e6xxx_get_rxnfc, .set_rxnfc = mv88e6xxx_set_rxnfc, .set_ageing_time = mv88e6xxx_set_ageing_time, .port_bridge_join = mv88e6xxx_port_bridge_join, .port_bridge_leave = mv88e6xxx_port_bridge_leave, .port_pre_bridge_flags = mv88e6xxx_port_pre_bridge_flags, .port_bridge_flags = mv88e6xxx_port_bridge_flags, .port_stp_state_set = mv88e6xxx_port_stp_state_set, .port_mst_state_set = mv88e6xxx_port_mst_state_set, .port_fast_age = mv88e6xxx_port_fast_age, .port_vlan_fast_age = mv88e6xxx_port_vlan_fast_age, .port_vlan_filtering = mv88e6xxx_port_vlan_filtering, .port_vlan_add = mv88e6xxx_port_vlan_add, .port_vlan_del = mv88e6xxx_port_vlan_del, .vlan_msti_set = mv88e6xxx_vlan_msti_set, .port_fdb_add = mv88e6xxx_port_fdb_add, .port_fdb_del = mv88e6xxx_port_fdb_del, .port_fdb_dump = mv88e6xxx_port_fdb_dump, .port_mdb_add = mv88e6xxx_port_mdb_add, .port_mdb_del = mv88e6xxx_port_mdb_del, .port_mirror_add = mv88e6xxx_port_mirror_add, .port_mirror_del = mv88e6xxx_port_mirror_del, .crosschip_bridge_join = mv88e6xxx_crosschip_bridge_join, .crosschip_bridge_leave = mv88e6xxx_crosschip_bridge_leave, .port_hwtstamp_set = mv88e6xxx_port_hwtstamp_set, .port_hwtstamp_get = mv88e6xxx_port_hwtstamp_get, .port_txtstamp = mv88e6xxx_port_txtstamp, .port_rxtstamp = mv88e6xxx_port_rxtstamp, .get_ts_info = mv88e6xxx_get_ts_info, .devlink_param_get = mv88e6xxx_devlink_param_get, .devlink_param_set = mv88e6xxx_devlink_param_set, .devlink_info_get = mv88e6xxx_devlink_info_get, .port_lag_change = mv88e6xxx_port_lag_change, .port_lag_join = mv88e6xxx_port_lag_join, .port_lag_leave = mv88e6xxx_port_lag_leave, .crosschip_lag_change = mv88e6xxx_crosschip_lag_change, .crosschip_lag_join = mv88e6xxx_crosschip_lag_join, .crosschip_lag_leave = mv88e6xxx_crosschip_lag_leave, }; static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip) { struct device *dev = chip->dev; struct dsa_switch *ds; ds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL); if (!ds) return -ENOMEM; ds->dev = dev; ds->num_ports = mv88e6xxx_num_ports(chip); ds->priv = chip; ds->dev = dev; ds->ops = &mv88e6xxx_switch_ops; ds->ageing_time_min = chip->info->age_time_coeff; ds->ageing_time_max = chip->info->age_time_coeff * U8_MAX; /* Some chips support up to 32, but that requires enabling the * 5-bit port mode, which we do not support. 640k^W16 ought to * be enough for anyone. */ ds->num_lag_ids = mv88e6xxx_has_lag(chip) ? 16 : 0; dev_set_drvdata(dev, ds); return dsa_register_switch(ds); } static void mv88e6xxx_unregister_switch(struct mv88e6xxx_chip *chip) { dsa_unregister_switch(chip->ds); } static const void *pdata_device_get_match_data(struct device *dev) { const struct of_device_id *matches = dev->driver->of_match_table; const struct dsa_mv88e6xxx_pdata *pdata = dev->platform_data; for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) { if (!strcmp(pdata->compatible, matches->compatible)) return matches->data; } return NULL; } /* There is no suspend to RAM support at DSA level yet, the switch configuration * would be lost after a power cycle so prevent it to be suspended. */ static int __maybe_unused mv88e6xxx_suspend(struct device *dev) { return -EOPNOTSUPP; } static int __maybe_unused mv88e6xxx_resume(struct device *dev) { return 0; } static SIMPLE_DEV_PM_OPS(mv88e6xxx_pm_ops, mv88e6xxx_suspend, mv88e6xxx_resume); static int mv88e6xxx_probe(struct mdio_device *mdiodev) { struct dsa_mv88e6xxx_pdata *pdata = mdiodev->dev.platform_data; const struct mv88e6xxx_info *compat_info = NULL; struct device *dev = &mdiodev->dev; struct device_node *np = dev->of_node; struct mv88e6xxx_chip *chip; int port; int err; if (!np && !pdata) return -EINVAL; if (np) compat_info = of_device_get_match_data(dev); if (pdata) { compat_info = pdata_device_get_match_data(dev); if (!pdata->netdev) return -EINVAL; for (port = 0; port < DSA_MAX_PORTS; port++) { if (!(pdata->enabled_ports & (1 << port))) continue; if (strcmp(pdata->cd.port_names[port], "cpu")) continue; pdata->cd.netdev[port] = &pdata->netdev->dev; break; } } if (!compat_info) return -EINVAL; chip = mv88e6xxx_alloc_chip(dev); if (!chip) { err = -ENOMEM; goto out; } chip->info = compat_info; chip->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(chip->reset)) { err = PTR_ERR(chip->reset); goto out; } if (chip->reset) usleep_range(10000, 20000); /* Detect if the device is configured in single chip addressing mode, * otherwise continue with address specific smi init/detection. */ err = mv88e6xxx_single_chip_detect(chip, mdiodev); if (err) { err = mv88e6xxx_smi_init(chip, mdiodev->bus, mdiodev->addr); if (err) goto out; err = mv88e6xxx_detect(chip); if (err) goto out; } if (chip->info->edsa_support == MV88E6XXX_EDSA_SUPPORTED) chip->tag_protocol = DSA_TAG_PROTO_EDSA; else chip->tag_protocol = DSA_TAG_PROTO_DSA; mv88e6xxx_phy_init(chip); if (chip->info->ops->get_eeprom) { if (np) of_property_read_u32(np, "eeprom-length", &chip->eeprom_len); else chip->eeprom_len = pdata->eeprom_len; } mv88e6xxx_reg_lock(chip); err = mv88e6xxx_switch_reset(chip); mv88e6xxx_reg_unlock(chip); if (err) goto out; if (np) { chip->irq = of_irq_get(np, 0); if (chip->irq == -EPROBE_DEFER) { err = chip->irq; goto out; } } if (pdata) chip->irq = pdata->irq; /* Has to be performed before the MDIO bus is created, because * the PHYs will link their interrupts to these interrupt * controllers */ mv88e6xxx_reg_lock(chip); if (chip->irq > 0) err = mv88e6xxx_g1_irq_setup(chip); else err = mv88e6xxx_irq_poll_setup(chip); mv88e6xxx_reg_unlock(chip); if (err) goto out; if (chip->info->g2_irqs > 0) { err = mv88e6xxx_g2_irq_setup(chip); if (err) goto out_g1_irq; } err = mv88e6xxx_g1_atu_prob_irq_setup(chip); if (err) goto out_g2_irq; err = mv88e6xxx_g1_vtu_prob_irq_setup(chip); if (err) goto out_g1_atu_prob_irq; err = mv88e6xxx_mdios_register(chip, np); if (err) goto out_g1_vtu_prob_irq; err = mv88e6xxx_register_switch(chip); if (err) goto out_mdio; return 0; out_mdio: mv88e6xxx_mdios_unregister(chip); out_g1_vtu_prob_irq: mv88e6xxx_g1_vtu_prob_irq_free(chip); out_g1_atu_prob_irq: mv88e6xxx_g1_atu_prob_irq_free(chip); out_g2_irq: if (chip->info->g2_irqs > 0) mv88e6xxx_g2_irq_free(chip); out_g1_irq: if (chip->irq > 0) mv88e6xxx_g1_irq_free(chip); else mv88e6xxx_irq_poll_free(chip); out: if (pdata) dev_put(pdata->netdev); return err; } static void mv88e6xxx_remove(struct mdio_device *mdiodev) { struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev); struct mv88e6xxx_chip *chip; if (!ds) return; chip = ds->priv; if (chip->info->ptp_support) { mv88e6xxx_hwtstamp_free(chip); mv88e6xxx_ptp_free(chip); } mv88e6xxx_phy_destroy(chip); mv88e6xxx_unregister_switch(chip); mv88e6xxx_mdios_unregister(chip); mv88e6xxx_g1_vtu_prob_irq_free(chip); mv88e6xxx_g1_atu_prob_irq_free(chip); if (chip->info->g2_irqs > 0) mv88e6xxx_g2_irq_free(chip); if (chip->irq > 0) mv88e6xxx_g1_irq_free(chip); else mv88e6xxx_irq_poll_free(chip); } static void mv88e6xxx_shutdown(struct mdio_device *mdiodev) { struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev); if (!ds) return; dsa_switch_shutdown(ds); dev_set_drvdata(&mdiodev->dev, NULL); } static const struct of_device_id mv88e6xxx_of_match[] = { { .compatible = "marvell,mv88e6085", .data = &mv88e6xxx_table[MV88E6085], }, { .compatible = "marvell,mv88e6190", .data = &mv88e6xxx_table[MV88E6190], }, { .compatible = "marvell,mv88e6250", .data = &mv88e6xxx_table[MV88E6250], }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, mv88e6xxx_of_match); static struct mdio_driver mv88e6xxx_driver = { .probe = mv88e6xxx_probe, .remove = mv88e6xxx_remove, .shutdown = mv88e6xxx_shutdown, .mdiodrv.driver = { .name = "mv88e6085", .of_match_table = mv88e6xxx_of_match, .pm = &mv88e6xxx_pm_ops, }, }; mdio_module_driver(mv88e6xxx_driver); MODULE_AUTHOR("Lennert Buytenhek "); MODULE_DESCRIPTION("Driver for Marvell 88E6XXX ethernet switch chips"); MODULE_LICENSE("GPL");