diff options
Diffstat (limited to 'drivers/net/dsa/mv88e6xxx/mv88e6xxx.c')
-rw-r--r-- | drivers/net/dsa/mv88e6xxx/mv88e6xxx.c | 3954 |
1 files changed, 3954 insertions, 0 deletions
diff --git a/drivers/net/dsa/mv88e6xxx/mv88e6xxx.c b/drivers/net/dsa/mv88e6xxx/mv88e6xxx.c new file mode 100644 index 000000000000..2073f7b991a6 --- /dev/null +++ b/drivers/net/dsa/mv88e6xxx/mv88e6xxx.c @@ -0,0 +1,3954 @@ +/* + * Marvell 88e6xxx Ethernet switch single-chip support + * + * Copyright (c) 2008 Marvell Semiconductor + * + * Copyright (c) 2015 CMC Electronics, Inc. + * Added support for VLAN Table Unit operations + * + * Copyright (c) 2016 Andrew Lunn <andrew@lunn.ch> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/delay.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/if_bridge.h> +#include <linux/jiffies.h> +#include <linux/list.h> +#include <linux/mdio.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_mdio.h> +#include <linux/netdevice.h> +#include <linux/gpio/consumer.h> +#include <linux/phy.h> +#include <net/dsa.h> +#include <net/switchdev.h> +#include "mv88e6xxx.h" + +static void assert_reg_lock(struct mv88e6xxx_priv_state *ps) +{ + if (unlikely(!mutex_is_locked(&ps->reg_lock))) { + dev_err(ps->dev, "Switch registers lock not held!\n"); + dump_stack(); + } +} + +/* The switch ADDR[4:1] configuration pins define the chip SMI device address + * (ADDR[0] is always zero, thus only even SMI addresses can be strapped). + * + * When ADDR is all zero, the chip uses Single-chip Addressing Mode, assuming it + * is the only device connected to the SMI master. In this mode it responds to + * all 32 possible SMI addresses, and thus maps directly the internal devices. + * + * When ADDR is non-zero, the chip uses Multi-chip Addressing Mode, allowing + * multiple devices to share the SMI interface. In this mode it responds to only + * 2 registers, used to indirectly access the internal SMI devices. + */ + +static int mv88e6xxx_smi_read(struct mv88e6xxx_priv_state *ps, + int addr, int reg, u16 *val) +{ + if (!ps->smi_ops) + return -EOPNOTSUPP; + + return ps->smi_ops->read(ps, addr, reg, val); +} + +static int mv88e6xxx_smi_write(struct mv88e6xxx_priv_state *ps, + int addr, int reg, u16 val) +{ + if (!ps->smi_ops) + return -EOPNOTSUPP; + + return ps->smi_ops->write(ps, addr, reg, val); +} + +static int mv88e6xxx_smi_single_chip_read(struct mv88e6xxx_priv_state *ps, + int addr, int reg, u16 *val) +{ + int ret; + + ret = mdiobus_read_nested(ps->bus, addr, reg); + if (ret < 0) + return ret; + + *val = ret & 0xffff; + + return 0; +} + +static int mv88e6xxx_smi_single_chip_write(struct mv88e6xxx_priv_state *ps, + int addr, int reg, u16 val) +{ + int ret; + + ret = mdiobus_write_nested(ps->bus, addr, reg, val); + if (ret < 0) + return ret; + + return 0; +} + +static const struct mv88e6xxx_ops mv88e6xxx_smi_single_chip_ops = { + .read = mv88e6xxx_smi_single_chip_read, + .write = mv88e6xxx_smi_single_chip_write, +}; + +static int mv88e6xxx_smi_multi_chip_wait(struct mv88e6xxx_priv_state *ps) +{ + int ret; + int i; + + for (i = 0; i < 16; i++) { + ret = mdiobus_read_nested(ps->bus, ps->sw_addr, SMI_CMD); + if (ret < 0) + return ret; + + if ((ret & SMI_CMD_BUSY) == 0) + return 0; + } + + return -ETIMEDOUT; +} + +static int mv88e6xxx_smi_multi_chip_read(struct mv88e6xxx_priv_state *ps, + int addr, int reg, u16 *val) +{ + int ret; + + /* Wait for the bus to become free. */ + ret = mv88e6xxx_smi_multi_chip_wait(ps); + if (ret < 0) + return ret; + + /* Transmit the read command. */ + ret = mdiobus_write_nested(ps->bus, ps->sw_addr, SMI_CMD, + SMI_CMD_OP_22_READ | (addr << 5) | reg); + if (ret < 0) + return ret; + + /* Wait for the read command to complete. */ + ret = mv88e6xxx_smi_multi_chip_wait(ps); + if (ret < 0) + return ret; + + /* Read the data. */ + ret = mdiobus_read_nested(ps->bus, ps->sw_addr, SMI_DATA); + if (ret < 0) + return ret; + + *val = ret & 0xffff; + + return 0; +} + +static int mv88e6xxx_smi_multi_chip_write(struct mv88e6xxx_priv_state *ps, + int addr, int reg, u16 val) +{ + int ret; + + /* Wait for the bus to become free. */ + ret = mv88e6xxx_smi_multi_chip_wait(ps); + if (ret < 0) + return ret; + + /* Transmit the data to write. */ + ret = mdiobus_write_nested(ps->bus, ps->sw_addr, SMI_DATA, val); + if (ret < 0) + return ret; + + /* Transmit the write command. */ + ret = mdiobus_write_nested(ps->bus, ps->sw_addr, SMI_CMD, + SMI_CMD_OP_22_WRITE | (addr << 5) | reg); + if (ret < 0) + return ret; + + /* Wait for the write command to complete. */ + ret = mv88e6xxx_smi_multi_chip_wait(ps); + if (ret < 0) + return ret; + + return 0; +} + +static const struct mv88e6xxx_ops mv88e6xxx_smi_multi_chip_ops = { + .read = mv88e6xxx_smi_multi_chip_read, + .write = mv88e6xxx_smi_multi_chip_write, +}; + +static int mv88e6xxx_read(struct mv88e6xxx_priv_state *ps, + int addr, int reg, u16 *val) +{ + int err; + + assert_reg_lock(ps); + + err = mv88e6xxx_smi_read(ps, addr, reg, val); + if (err) + return err; + + dev_dbg(ps->dev, "<- addr: 0x%.2x reg: 0x%.2x val: 0x%.4x\n", + addr, reg, *val); + + return 0; +} + +static int mv88e6xxx_write(struct mv88e6xxx_priv_state *ps, + int addr, int reg, u16 val) +{ + int err; + + assert_reg_lock(ps); + + err = mv88e6xxx_smi_write(ps, addr, reg, val); + if (err) + return err; + + dev_dbg(ps->dev, "-> addr: 0x%.2x reg: 0x%.2x val: 0x%.4x\n", + addr, reg, val); + + return 0; +} + +static int _mv88e6xxx_reg_read(struct mv88e6xxx_priv_state *ps, + int addr, int reg) +{ + u16 val; + int err; + + err = mv88e6xxx_read(ps, addr, reg, &val); + if (err) + return err; + + return val; +} + +static int mv88e6xxx_reg_read(struct mv88e6xxx_priv_state *ps, int addr, + int reg) +{ + int ret; + + mutex_lock(&ps->reg_lock); + ret = _mv88e6xxx_reg_read(ps, addr, reg); + mutex_unlock(&ps->reg_lock); + + return ret; +} + +static int _mv88e6xxx_reg_write(struct mv88e6xxx_priv_state *ps, int addr, + int reg, u16 val) +{ + return mv88e6xxx_write(ps, addr, reg, val); +} + +static int mv88e6xxx_reg_write(struct mv88e6xxx_priv_state *ps, int addr, + int reg, u16 val) +{ + int ret; + + mutex_lock(&ps->reg_lock); + ret = _mv88e6xxx_reg_write(ps, addr, reg, val); + mutex_unlock(&ps->reg_lock); + + return ret; +} + +static int mv88e6xxx_set_addr_direct(struct dsa_switch *ds, u8 *addr) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int err; + + err = mv88e6xxx_reg_write(ps, REG_GLOBAL, GLOBAL_MAC_01, + (addr[0] << 8) | addr[1]); + if (err) + return err; + + err = mv88e6xxx_reg_write(ps, REG_GLOBAL, GLOBAL_MAC_23, + (addr[2] << 8) | addr[3]); + if (err) + return err; + + return mv88e6xxx_reg_write(ps, REG_GLOBAL, GLOBAL_MAC_45, + (addr[4] << 8) | addr[5]); +} + +static int mv88e6xxx_set_addr_indirect(struct dsa_switch *ds, u8 *addr) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int ret; + int i; + + for (i = 0; i < 6; i++) { + int j; + + /* Write the MAC address byte. */ + ret = mv88e6xxx_reg_write(ps, REG_GLOBAL2, GLOBAL2_SWITCH_MAC, + GLOBAL2_SWITCH_MAC_BUSY | + (i << 8) | addr[i]); + if (ret) + return ret; + + /* Wait for the write to complete. */ + for (j = 0; j < 16; j++) { + ret = mv88e6xxx_reg_read(ps, REG_GLOBAL2, + GLOBAL2_SWITCH_MAC); + if (ret < 0) + return ret; + + if ((ret & GLOBAL2_SWITCH_MAC_BUSY) == 0) + break; + } + if (j == 16) + return -ETIMEDOUT; + } + + return 0; +} + +static int mv88e6xxx_set_addr(struct dsa_switch *ds, u8 *addr) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + + if (mv88e6xxx_has(ps, MV88E6XXX_FLAG_SWITCH_MAC)) + return mv88e6xxx_set_addr_indirect(ds, addr); + else + return mv88e6xxx_set_addr_direct(ds, addr); +} + +static int mv88e6xxx_mdio_read_direct(struct mv88e6xxx_priv_state *ps, + int addr, int regnum) +{ + if (addr >= 0) + return _mv88e6xxx_reg_read(ps, addr, regnum); + return 0xffff; +} + +static int mv88e6xxx_mdio_write_direct(struct mv88e6xxx_priv_state *ps, + int addr, int regnum, u16 val) +{ + if (addr >= 0) + return _mv88e6xxx_reg_write(ps, addr, regnum, val); + return 0; +} + +static int mv88e6xxx_ppu_disable(struct mv88e6xxx_priv_state *ps) +{ + int ret; + unsigned long timeout; + + ret = _mv88e6xxx_reg_read(ps, REG_GLOBAL, GLOBAL_CONTROL); + if (ret < 0) + return ret; + + ret = _mv88e6xxx_reg_write(ps, REG_GLOBAL, GLOBAL_CONTROL, + ret & ~GLOBAL_CONTROL_PPU_ENABLE); + if (ret) + return ret; + + timeout = jiffies + 1 * HZ; + while (time_before(jiffies, timeout)) { + ret = _mv88e6xxx_reg_read(ps, REG_GLOBAL, GLOBAL_STATUS); + if (ret < 0) + return ret; + + usleep_range(1000, 2000); + if ((ret & GLOBAL_STATUS_PPU_MASK) != + GLOBAL_STATUS_PPU_POLLING) + return 0; + } + + return -ETIMEDOUT; +} + +static int mv88e6xxx_ppu_enable(struct mv88e6xxx_priv_state *ps) +{ + int ret, err; + unsigned long timeout; + + ret = _mv88e6xxx_reg_read(ps, REG_GLOBAL, GLOBAL_CONTROL); + if (ret < 0) + return ret; + + err = _mv88e6xxx_reg_write(ps, REG_GLOBAL, GLOBAL_CONTROL, + ret | GLOBAL_CONTROL_PPU_ENABLE); + if (err) + return err; + + timeout = jiffies + 1 * HZ; + while (time_before(jiffies, timeout)) { + ret = _mv88e6xxx_reg_read(ps, REG_GLOBAL, GLOBAL_STATUS); + if (ret < 0) + return ret; + + usleep_range(1000, 2000); + if ((ret & GLOBAL_STATUS_PPU_MASK) == + GLOBAL_STATUS_PPU_POLLING) + return 0; + } + + return -ETIMEDOUT; +} + +static void mv88e6xxx_ppu_reenable_work(struct work_struct *ugly) +{ + struct mv88e6xxx_priv_state *ps; + + ps = container_of(ugly, struct mv88e6xxx_priv_state, ppu_work); + + mutex_lock(&ps->reg_lock); + + if (mutex_trylock(&ps->ppu_mutex)) { + if (mv88e6xxx_ppu_enable(ps) == 0) + ps->ppu_disabled = 0; + mutex_unlock(&ps->ppu_mutex); + } + + mutex_unlock(&ps->reg_lock); +} + +static void mv88e6xxx_ppu_reenable_timer(unsigned long _ps) +{ + struct mv88e6xxx_priv_state *ps = (void *)_ps; + + schedule_work(&ps->ppu_work); +} + +static int mv88e6xxx_ppu_access_get(struct mv88e6xxx_priv_state *ps) +{ + int ret; + + mutex_lock(&ps->ppu_mutex); + + /* If the PHY polling unit is enabled, disable it so that + * we can access the PHY registers. If it was already + * disabled, cancel the timer that is going to re-enable + * it. + */ + if (!ps->ppu_disabled) { + ret = mv88e6xxx_ppu_disable(ps); + if (ret < 0) { + mutex_unlock(&ps->ppu_mutex); + return ret; + } + ps->ppu_disabled = 1; + } else { + del_timer(&ps->ppu_timer); + ret = 0; + } + + return ret; +} + +static void mv88e6xxx_ppu_access_put(struct mv88e6xxx_priv_state *ps) +{ + /* Schedule a timer to re-enable the PHY polling unit. */ + mod_timer(&ps->ppu_timer, jiffies + msecs_to_jiffies(10)); + mutex_unlock(&ps->ppu_mutex); +} + +static void mv88e6xxx_ppu_state_init(struct mv88e6xxx_priv_state *ps) +{ + mutex_init(&ps->ppu_mutex); + INIT_WORK(&ps->ppu_work, mv88e6xxx_ppu_reenable_work); + init_timer(&ps->ppu_timer); + ps->ppu_timer.data = (unsigned long)ps; + ps->ppu_timer.function = mv88e6xxx_ppu_reenable_timer; +} + +static int mv88e6xxx_mdio_read_ppu(struct mv88e6xxx_priv_state *ps, int addr, + int regnum) +{ + int ret; + + ret = mv88e6xxx_ppu_access_get(ps); + if (ret >= 0) { + ret = _mv88e6xxx_reg_read(ps, addr, regnum); + mv88e6xxx_ppu_access_put(ps); + } + + return ret; +} + +static int mv88e6xxx_mdio_write_ppu(struct mv88e6xxx_priv_state *ps, int addr, + int regnum, u16 val) +{ + int ret; + + ret = mv88e6xxx_ppu_access_get(ps); + if (ret >= 0) { + ret = _mv88e6xxx_reg_write(ps, addr, regnum, val); + mv88e6xxx_ppu_access_put(ps); + } + + return ret; +} + +static bool mv88e6xxx_6065_family(struct mv88e6xxx_priv_state *ps) +{ + return ps->info->family == MV88E6XXX_FAMILY_6065; +} + +static bool mv88e6xxx_6095_family(struct mv88e6xxx_priv_state *ps) +{ + return ps->info->family == MV88E6XXX_FAMILY_6095; +} + +static bool mv88e6xxx_6097_family(struct mv88e6xxx_priv_state *ps) +{ + return ps->info->family == MV88E6XXX_FAMILY_6097; +} + +static bool mv88e6xxx_6165_family(struct mv88e6xxx_priv_state *ps) +{ + return ps->info->family == MV88E6XXX_FAMILY_6165; +} + +static bool mv88e6xxx_6185_family(struct mv88e6xxx_priv_state *ps) +{ + return ps->info->family == MV88E6XXX_FAMILY_6185; +} + +static bool mv88e6xxx_6320_family(struct mv88e6xxx_priv_state *ps) +{ + return ps->info->family == MV88E6XXX_FAMILY_6320; +} + +static bool mv88e6xxx_6351_family(struct mv88e6xxx_priv_state *ps) +{ + return ps->info->family == MV88E6XXX_FAMILY_6351; +} + +static bool mv88e6xxx_6352_family(struct mv88e6xxx_priv_state *ps) +{ + return ps->info->family == MV88E6XXX_FAMILY_6352; +} + +static unsigned int mv88e6xxx_num_databases(struct mv88e6xxx_priv_state *ps) +{ + return ps->info->num_databases; +} + +static bool mv88e6xxx_has_fid_reg(struct mv88e6xxx_priv_state *ps) +{ + /* Does the device have dedicated FID registers for ATU and VTU ops? */ + if (mv88e6xxx_6097_family(ps) || mv88e6xxx_6165_family(ps) || + mv88e6xxx_6351_family(ps) || mv88e6xxx_6352_family(ps)) + return true; + + return false; +} + +/* We expect the switch to perform auto negotiation if there is a real + * phy. However, in the case of a fixed link phy, we force the port + * settings from the fixed link settings. + */ +static void mv88e6xxx_adjust_link(struct dsa_switch *ds, int port, + struct phy_device *phydev) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + u32 reg; + int ret; + + if (!phy_is_pseudo_fixed_link(phydev)) + return; + + mutex_lock(&ps->reg_lock); + + ret = _mv88e6xxx_reg_read(ps, REG_PORT(port), PORT_PCS_CTRL); + if (ret < 0) + goto out; + + reg = ret & ~(PORT_PCS_CTRL_LINK_UP | + PORT_PCS_CTRL_FORCE_LINK | + PORT_PCS_CTRL_DUPLEX_FULL | + PORT_PCS_CTRL_FORCE_DUPLEX | + PORT_PCS_CTRL_UNFORCED); + + reg |= PORT_PCS_CTRL_FORCE_LINK; + if (phydev->link) + reg |= PORT_PCS_CTRL_LINK_UP; + + if (mv88e6xxx_6065_family(ps) && phydev->speed > SPEED_100) + goto out; + + switch (phydev->speed) { + case SPEED_1000: + reg |= PORT_PCS_CTRL_1000; + break; + case SPEED_100: + reg |= PORT_PCS_CTRL_100; + break; + case SPEED_10: + reg |= PORT_PCS_CTRL_10; + break; + default: + pr_info("Unknown speed"); + goto out; + } + + reg |= PORT_PCS_CTRL_FORCE_DUPLEX; + if (phydev->duplex == DUPLEX_FULL) + reg |= PORT_PCS_CTRL_DUPLEX_FULL; + + if ((mv88e6xxx_6352_family(ps) || mv88e6xxx_6351_family(ps)) && + (port >= ps->info->num_ports - 2)) { + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) + reg |= PORT_PCS_CTRL_RGMII_DELAY_RXCLK; + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) + reg |= PORT_PCS_CTRL_RGMII_DELAY_TXCLK; + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) + reg |= (PORT_PCS_CTRL_RGMII_DELAY_RXCLK | + PORT_PCS_CTRL_RGMII_DELAY_TXCLK); + } + _mv88e6xxx_reg_write(ps, REG_PORT(port), PORT_PCS_CTRL, reg); + +out: + mutex_unlock(&ps->reg_lock); +} + +static int _mv88e6xxx_stats_wait(struct mv88e6xxx_priv_state *ps) +{ + int ret; + int i; + + for (i = 0; i < 10; i++) { + ret = _mv88e6xxx_reg_read(ps, REG_GLOBAL, GLOBAL_STATS_OP); + if ((ret & GLOBAL_STATS_OP_BUSY) == 0) + return 0; + } + + return -ETIMEDOUT; +} + +static int _mv88e6xxx_stats_snapshot(struct mv88e6xxx_priv_state *ps, + int port) +{ + int ret; + + if (mv88e6xxx_6320_family(ps) || mv88e6xxx_6352_family(ps)) + port = (port + 1) << 5; + + /* Snapshot the hardware statistics counters for this port. */ + ret = _mv88e6xxx_reg_write(ps, REG_GLOBAL, GLOBAL_STATS_OP, + GLOBAL_STATS_OP_CAPTURE_PORT | + GLOBAL_STATS_OP_HIST_RX_TX | port); + if (ret < 0) + return ret; + + /* Wait for the snapshotting to complete. */ + ret = _mv88e6xxx_stats_wait(ps); + if (ret < 0) + return ret; + + return 0; +} + +static void _mv88e6xxx_stats_read(struct mv88e6xxx_priv_state *ps, + int stat, u32 *val) +{ + u32 _val; + int ret; + + *val = 0; + + ret = _mv88e6xxx_reg_write(ps, REG_GLOBAL, GLOBAL_STATS_OP, + GLOBAL_STATS_OP_READ_CAPTURED | + GLOBAL_STATS_OP_HIST_RX_TX | stat); + if (ret < 0) + return; + + ret = _mv88e6xxx_stats_wait(ps); + if (ret < 0) + return; + + ret = _mv88e6xxx_reg_read(ps, REG_GLOBAL, GLOBAL_STATS_COUNTER_32); + if (ret < 0) + return; + + _val = ret << 16; + + ret = _mv88e6xxx_reg_read(ps, REG_GLOBAL, GLOBAL_STATS_COUNTER_01); + if (ret < 0) + return; + + *val = _val | ret; +} + +static struct mv88e6xxx_hw_stat mv88e6xxx_hw_stats[] = { + { "in_good_octets", 8, 0x00, BANK0, }, + { "in_bad_octets", 4, 0x02, BANK0, }, + { "in_unicast", 4, 0x04, BANK0, }, + { "in_broadcasts", 4, 0x06, BANK0, }, + { "in_multicasts", 4, 0x07, BANK0, }, + { "in_pause", 4, 0x16, BANK0, }, + { "in_undersize", 4, 0x18, BANK0, }, + { "in_fragments", 4, 0x19, BANK0, }, + { "in_oversize", 4, 0x1a, BANK0, }, + { "in_jabber", 4, 0x1b, BANK0, }, + { "in_rx_error", 4, 0x1c, BANK0, }, + { "in_fcs_error", 4, 0x1d, BANK0, }, + { "out_octets", 8, 0x0e, BANK0, }, + { "out_unicast", 4, 0x10, BANK0, }, + { "out_broadcasts", 4, 0x13, BANK0, }, + { "out_multicasts", 4, 0x12, BANK0, }, + { "out_pause", 4, 0x15, BANK0, }, + { "excessive", 4, 0x11, BANK0, }, + { "collisions", 4, 0x1e, BANK0, }, + { "deferred", 4, 0x05, BANK0, }, + { "single", 4, 0x14, BANK0, }, + { "multiple", 4, 0x17, BANK0, }, + { "out_fcs_error", 4, 0x03, BANK0, }, + { "late", 4, 0x1f, BANK0, }, + { "hist_64bytes", 4, 0x08, BANK0, }, + { "hist_65_127bytes", 4, 0x09, BANK0, }, + { "hist_128_255bytes", 4, 0x0a, BANK0, }, + { "hist_256_511bytes", 4, 0x0b, BANK0, }, + { "hist_512_1023bytes", 4, 0x0c, BANK0, }, + { "hist_1024_max_bytes", 4, 0x0d, BANK0, }, + { "sw_in_discards", 4, 0x10, PORT, }, + { "sw_in_filtered", 2, 0x12, PORT, }, + { "sw_out_filtered", 2, 0x13, PORT, }, + { "in_discards", 4, 0x00 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "in_filtered", 4, 0x01 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "in_accepted", 4, 0x02 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "in_bad_accepted", 4, 0x03 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "in_good_avb_class_a", 4, 0x04 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "in_good_avb_class_b", 4, 0x05 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "in_bad_avb_class_a", 4, 0x06 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "in_bad_avb_class_b", 4, 0x07 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "tcam_counter_0", 4, 0x08 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "tcam_counter_1", 4, 0x09 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "tcam_counter_2", 4, 0x0a | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "tcam_counter_3", 4, 0x0b | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "in_da_unknown", 4, 0x0e | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "in_management", 4, 0x0f | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "out_queue_0", 4, 0x10 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "out_queue_1", 4, 0x11 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "out_queue_2", 4, 0x12 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "out_queue_3", 4, 0x13 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "out_queue_4", 4, 0x14 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "out_queue_5", 4, 0x15 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "out_queue_6", 4, 0x16 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "out_queue_7", 4, 0x17 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "out_cut_through", 4, 0x18 | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "out_octets_a", 4, 0x1a | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "out_octets_b", 4, 0x1b | GLOBAL_STATS_OP_BANK_1, BANK1, }, + { "out_management", 4, 0x1f | GLOBAL_STATS_OP_BANK_1, BANK1, }, +}; + +static bool mv88e6xxx_has_stat(struct mv88e6xxx_priv_state *ps, + struct mv88e6xxx_hw_stat *stat) +{ + switch (stat->type) { + case BANK0: + return true; + case BANK1: + return mv88e6xxx_6320_family(ps); + case PORT: + return mv88e6xxx_6095_family(ps) || + mv88e6xxx_6185_family(ps) || + mv88e6xxx_6097_family(ps) || + mv88e6xxx_6165_family(ps) || + mv88e6xxx_6351_family(ps) || + mv88e6xxx_6352_family(ps); + } + return false; +} + +static uint64_t _mv88e6xxx_get_ethtool_stat(struct mv88e6xxx_priv_state *ps, + struct mv88e6xxx_hw_stat *s, + int port) +{ + u32 low; + u32 high = 0; + int ret; + u64 value; + + switch (s->type) { + case PORT: + ret = _mv88e6xxx_reg_read(ps, REG_PORT(port), s->reg); + if (ret < 0) + return UINT64_MAX; + + low = ret; + if (s->sizeof_stat == 4) { + ret = _mv88e6xxx_reg_read(ps, REG_PORT(port), + s->reg + 1); + if (ret < 0) + return UINT64_MAX; + high = ret; + } + break; + case BANK0: + case BANK1: + _mv88e6xxx_stats_read(ps, s->reg, &low); + if (s->sizeof_stat == 8) + _mv88e6xxx_stats_read(ps, s->reg + 1, &high); + } + value = (((u64)high) << 16) | low; + return value; +} + +static void mv88e6xxx_get_strings(struct dsa_switch *ds, int port, + uint8_t *data) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + 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 (mv88e6xxx_has_stat(ps, stat)) { + memcpy(data + j * ETH_GSTRING_LEN, stat->string, + ETH_GSTRING_LEN); + j++; + } + } +} + +static int mv88e6xxx_get_sset_count(struct dsa_switch *ds) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + 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 (mv88e6xxx_has_stat(ps, stat)) + j++; + } + return j; +} + +static void mv88e6xxx_get_ethtool_stats(struct dsa_switch *ds, int port, + uint64_t *data) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + struct mv88e6xxx_hw_stat *stat; + int ret; + int i, j; + + mutex_lock(&ps->reg_lock); + + ret = _mv88e6xxx_stats_snapshot(ps, port); + if (ret < 0) { + mutex_unlock(&ps->reg_lock); + return; + } + for (i = 0, j = 0; i < ARRAY_SIZE(mv88e6xxx_hw_stats); i++) { + stat = &mv88e6xxx_hw_stats[i]; + if (mv88e6xxx_has_stat(ps, stat)) { + data[j] = _mv88e6xxx_get_ethtool_stat(ps, stat, port); + j++; + } + } + + mutex_unlock(&ps->reg_lock); +} + +static int mv88e6xxx_get_regs_len(struct dsa_switch *ds, int port) +{ + return 32 * sizeof(u16); +} + +static void mv88e6xxx_get_regs(struct dsa_switch *ds, int port, + struct ethtool_regs *regs, void *_p) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + u16 *p = _p; + int i; + + regs->version = 0; + + memset(p, 0xff, 32 * sizeof(u16)); + + mutex_lock(&ps->reg_lock); + + for (i = 0; i < 32; i++) { + int ret; + + ret = _mv88e6xxx_reg_read(ps, REG_PORT(port), i); + if (ret >= 0) + p[i] = ret; + } + + mutex_unlock(&ps->reg_lock); +} + +static int _mv88e6xxx_wait(struct mv88e6xxx_priv_state *ps, int reg, int offset, + u16 mask) +{ + unsigned long timeout = jiffies + HZ / 10; + + while (time_before(jiffies, timeout)) { + int ret; + + ret = _mv88e6xxx_reg_read(ps, reg, offset); + if (ret < 0) + return ret; + if (!(ret & mask)) + return 0; + + usleep_range(1000, 2000); + } + return -ETIMEDOUT; +} + +static int mv88e6xxx_wait(struct mv88e6xxx_priv_state *ps, int reg, + int offset, u16 mask) +{ + int ret; + + mutex_lock(&ps->reg_lock); + ret = _mv88e6xxx_wait(ps, reg, offset, mask); + mutex_unlock(&ps->reg_lock); + + return ret; +} + +static int mv88e6xxx_mdio_wait(struct mv88e6xxx_priv_state *ps) +{ + return _mv88e6xxx_wait(ps, REG_GLOBAL2, GLOBAL2_SMI_OP, + GLOBAL2_SMI_OP_BUSY); +} + +static int mv88e6xxx_eeprom_load_wait(struct dsa_switch *ds) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + + return mv88e6xxx_wait(ps, REG_GLOBAL2, GLOBAL2_EEPROM_OP, + GLOBAL2_EEPROM_OP_LOAD); +} + +static int mv88e6xxx_eeprom_busy_wait(struct dsa_switch *ds) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + + return mv88e6xxx_wait(ps, REG_GLOBAL2, GLOBAL2_EEPROM_OP, + GLOBAL2_EEPROM_OP_BUSY); +} + +static int mv88e6xxx_read_eeprom_word(struct dsa_switch *ds, int addr) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int ret; + + mutex_lock(&ps->eeprom_mutex); + + ret = mv88e6xxx_reg_write(ps, REG_GLOBAL2, GLOBAL2_EEPROM_OP, + GLOBAL2_EEPROM_OP_READ | + (addr & GLOBAL2_EEPROM_OP_ADDR_MASK)); + if (ret < 0) + goto error; + + ret = mv88e6xxx_eeprom_busy_wait(ds); + if (ret < 0) + goto error; + + ret = mv88e6xxx_reg_read(ps, REG_GLOBAL2, GLOBAL2_EEPROM_DATA); +error: + mutex_unlock(&ps->eeprom_mutex); + return ret; +} + +static int mv88e6xxx_get_eeprom_len(struct dsa_switch *ds) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + + if (mv88e6xxx_has(ps, MV88E6XXX_FLAG_EEPROM)) + return ps->eeprom_len; + + return 0; +} + +static int mv88e6xxx_get_eeprom(struct dsa_switch *ds, + struct ethtool_eeprom *eeprom, u8 *data) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int offset; + int len; + int ret; + + if (!mv88e6xxx_has(ps, MV88E6XXX_FLAG_EEPROM)) + return -EOPNOTSUPP; + + offset = eeprom->offset; + len = eeprom->len; + eeprom->len = 0; + + eeprom->magic = 0xc3ec4951; + + ret = mv88e6xxx_eeprom_load_wait(ds); + if (ret < 0) + return ret; + + if (offset & 1) { + int word; + + word = mv88e6xxx_read_eeprom_word(ds, offset >> 1); + if (word < 0) + return word; + + *data++ = (word >> 8) & 0xff; + + offset++; + len--; + eeprom->len++; + } + + while (len >= 2) { + int word; + + word = mv88e6xxx_read_eeprom_word(ds, offset >> 1); + if (word < 0) + return word; + + *data++ = word & 0xff; + *data++ = (word >> 8) & 0xff; + + offset += 2; + len -= 2; + eeprom->len += 2; + } + + if (len) { + int word; + + word = mv88e6xxx_read_eeprom_word(ds, offset >> 1); + if (word < 0) + return word; + + *data++ = word & 0xff; + + offset++; + len--; + eeprom->len++; + } + + return 0; +} + +static int mv88e6xxx_eeprom_is_readonly(struct dsa_switch *ds) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int ret; + + ret = mv88e6xxx_reg_read(ps, REG_GLOBAL2, GLOBAL2_EEPROM_OP); + if (ret < 0) + return ret; + + if (!(ret & GLOBAL2_EEPROM_OP_WRITE_EN)) + return -EROFS; + + return 0; +} + +static int mv88e6xxx_write_eeprom_word(struct dsa_switch *ds, int addr, + u16 data) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int ret; + + mutex_lock(&ps->eeprom_mutex); + + ret = mv88e6xxx_reg_write(ps, REG_GLOBAL2, GLOBAL2_EEPROM_DATA, data); + if (ret < 0) + goto error; + + ret = mv88e6xxx_reg_write(ps, REG_GLOBAL2, GLOBAL2_EEPROM_OP, + GLOBAL2_EEPROM_OP_WRITE | + (addr & GLOBAL2_EEPROM_OP_ADDR_MASK)); + if (ret < 0) + goto error; + + ret = mv88e6xxx_eeprom_busy_wait(ds); +error: + mutex_unlock(&ps->eeprom_mutex); + return ret; +} + +static int mv88e6xxx_set_eeprom(struct dsa_switch *ds, + struct ethtool_eeprom *eeprom, u8 *data) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int offset; + int ret; + int len; + + if (!mv88e6xxx_has(ps, MV88E6XXX_FLAG_EEPROM)) + return -EOPNOTSUPP; + + if (eeprom->magic != 0xc3ec4951) + return -EINVAL; + + ret = mv88e6xxx_eeprom_is_readonly(ds); + if (ret) + return ret; + + offset = eeprom->offset; + len = eeprom->len; + eeprom->len = 0; + + ret = mv88e6xxx_eeprom_load_wait(ds); + if (ret < 0) + return ret; + + if (offset & 1) { + int word; + + word = mv88e6xxx_read_eeprom_word(ds, offset >> 1); + if (word < 0) + return word; + + word = (*data++ << 8) | (word & 0xff); + + ret = mv88e6xxx_write_eeprom_word(ds, offset >> 1, word); + if (ret < 0) + return ret; + + offset++; + len--; + eeprom->len++; + } + + while (len >= 2) { + int word; + + word = *data++; + word |= *data++ << 8; + + ret = mv88e6xxx_write_eeprom_word(ds, offset >> 1, word); + if (ret < 0) + return ret; + + offset += 2; + len -= 2; + eeprom->len += 2; + } + + if (len) { + int word; + + word = mv88e6xxx_read_eeprom_word(ds, offset >> 1); + if (word < 0) + return word; + + word = (word & 0xff00) | *data++; + + ret = mv88e6xxx_write_eeprom_word(ds, offset >> 1, word); + if (ret < 0) + return ret; + + offset++; + len--; + eeprom->len++; + } + + return 0; +} + +static int _mv88e6xxx_atu_wait(struct mv88e6xxx_priv_state *ps) +{ + return _mv88e6xxx_wait(ps, REG_GLOBAL, GLOBAL_ATU_OP, + GLOBAL_ATU_OP_BUSY); +} + +static int mv88e6xxx_mdio_read_indirect(struct mv88e6xxx_priv_state *ps, + int addr, int regnum) +{ + int ret; + + ret = _mv88e6xxx_reg_write(ps, REG_GLOBAL2, GLOBAL2_SMI_OP, + GLOBAL2_SMI_OP_22_READ | (addr << 5) | + regnum); + if (ret < 0) + return ret; + + ret = mv88e6xxx_mdio_wait(ps); + if (ret < 0) + return ret; + + ret = _mv88e6xxx_reg_read(ps, REG_GLOBAL2, GLOBAL2_SMI_DATA); + + return ret; +} + +static int mv88e6xxx_mdio_write_indirect(struct mv88e6xxx_priv_state *ps, + int addr, int regnum, u16 val) +{ + int ret; + + ret = _mv88e6xxx_reg_write(ps, REG_GLOBAL2, GLOBAL2_SMI_DATA, val); + if (ret < 0) + return ret; + + ret = _mv88e6xxx_reg_write(ps, REG_GLOBAL2, GLOBAL2_SMI_OP, + GLOBAL2_SMI_OP_22_WRITE | (addr << 5) | + regnum); + + return mv88e6xxx_mdio_wait(ps); +} + +static int mv88e6xxx_get_eee(struct dsa_switch *ds, int port, + struct ethtool_eee *e) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int reg; + + if (!mv88e6xxx_has(ps, MV88E6XXX_FLAG_EEE)) + return -EOPNOTSUPP; + + mutex_lock(&ps->reg_lock); + + reg = mv88e6xxx_mdio_read_indirect(ps, port, 16); + if (reg < 0) + goto out; + + e->eee_enabled = !!(reg & 0x0200); + e->tx_lpi_enabled = !!(reg & 0x0100); + + reg = _mv88e6xxx_reg_read(ps, REG_PORT(port), PORT_STATUS); + if (reg < 0) + goto out; + + e->eee_active = !!(reg & PORT_STATUS_EEE); + reg = 0; + +out: + mutex_unlock(&ps->reg_lock); + return reg; +} + +static int mv88e6xxx_set_eee(struct dsa_switch *ds, int port, + struct phy_device *phydev, struct ethtool_eee *e) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int reg; + int ret; + + if (!mv88e6xxx_has(ps, MV88E6XXX_FLAG_EEE)) + return -EOPNOTSUPP; + + mutex_lock(&ps->reg_lock); + + ret = mv88e6xxx_mdio_read_indirect(ps, port, 16); + if (ret < 0) + goto out; + + reg = ret & ~0x0300; + if (e->eee_enabled) + reg |= 0x0200; + if (e->tx_lpi_enabled) + reg |= 0x0100; + + ret = mv88e6xxx_mdio_write_indirect(ps, port, 16, reg); +out: + mutex_unlock(&ps->reg_lock); + + return ret; +} + +static int _mv88e6xxx_atu_cmd(struct mv88e6xxx_priv_state *ps, u16 fid, u16 cmd) +{ + int ret; + + if (mv88e6xxx_has_fid_reg(ps)) { + ret = _mv88e6xxx_reg_write(ps, REG_GLOBAL, GLOBAL_ATU_FID, fid); + if (ret < 0) + return ret; + } else if (mv88e6xxx_num_databases(ps) == 256) { + /* ATU DBNum[7:4] are located in ATU Control 15:12 */ + ret = _mv88e6xxx_reg_read(ps, REG_GLOBAL, GLOBAL_ATU_CONTROL); + if (ret < 0) + return ret; + + ret = _mv88e6 |