diff options
Diffstat (limited to 'drivers/net/dsa/microchip/ksz9477.c')
| -rw-r--r-- | drivers/net/dsa/microchip/ksz9477.c | 1599 |
1 files changed, 797 insertions, 802 deletions
diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c index 9a51b8a4de5d..5facffbb9c9a 100644 --- a/drivers/net/dsa/microchip/ksz9477.c +++ b/drivers/net/dsa/microchip/ksz9477.c @@ -2,7 +2,7 @@ /* * Microchip KSZ9477 switch driver main logic * - * Copyright (C) 2017-2019 Microchip Technology Inc. + * Copyright (C) 2017-2025 Microchip Technology Inc. */ #include <linux/kernel.h> @@ -11,88 +11,56 @@ #include <linux/platform_data/microchip-ksz.h> #include <linux/phy.h> #include <linux/if_bridge.h> +#include <linux/if_vlan.h> #include <net/dsa.h> #include <net/switchdev.h> #include "ksz9477_reg.h" #include "ksz_common.h" - -/* Used with variable features to indicate capabilities. */ -#define GBIT_SUPPORT BIT(0) -#define NEW_XMII BIT(1) -#define IS_9893 BIT(2) - -static const struct { - int index; - char string[ETH_GSTRING_LEN]; -} ksz9477_mib_names[TOTAL_SWITCH_COUNTER_NUM] = { - { 0x00, "rx_hi" }, - { 0x01, "rx_undersize" }, - { 0x02, "rx_fragments" }, - { 0x03, "rx_oversize" }, - { 0x04, "rx_jabbers" }, - { 0x05, "rx_symbol_err" }, - { 0x06, "rx_crc_err" }, - { 0x07, "rx_align_err" }, - { 0x08, "rx_mac_ctrl" }, - { 0x09, "rx_pause" }, - { 0x0A, "rx_bcast" }, - { 0x0B, "rx_mcast" }, - { 0x0C, "rx_ucast" }, - { 0x0D, "rx_64_or_less" }, - { 0x0E, "rx_65_127" }, - { 0x0F, "rx_128_255" }, - { 0x10, "rx_256_511" }, - { 0x11, "rx_512_1023" }, - { 0x12, "rx_1024_1522" }, - { 0x13, "rx_1523_2000" }, - { 0x14, "rx_2001" }, - { 0x15, "tx_hi" }, - { 0x16, "tx_late_col" }, - { 0x17, "tx_pause" }, - { 0x18, "tx_bcast" }, - { 0x19, "tx_mcast" }, - { 0x1A, "tx_ucast" }, - { 0x1B, "tx_deferred" }, - { 0x1C, "tx_total_col" }, - { 0x1D, "tx_exc_col" }, - { 0x1E, "tx_single_col" }, - { 0x1F, "tx_mult_col" }, - { 0x80, "rx_total" }, - { 0x81, "tx_total" }, - { 0x82, "rx_discards" }, - { 0x83, "tx_discards" }, -}; +#include "ksz9477.h" static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set) { - regmap_update_bits(dev->regmap[0], addr, bits, set ? bits : 0); + regmap_update_bits(ksz_regmap_8(dev), addr, bits, set ? bits : 0); } static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits, bool set) { - regmap_update_bits(dev->regmap[0], PORT_CTRL_ADDR(port, offset), + regmap_update_bits(ksz_regmap_8(dev), PORT_CTRL_ADDR(port, offset), bits, set ? bits : 0); } static void ksz9477_cfg32(struct ksz_device *dev, u32 addr, u32 bits, bool set) { - regmap_update_bits(dev->regmap[2], addr, bits, set ? bits : 0); + regmap_update_bits(ksz_regmap_32(dev), addr, bits, set ? bits : 0); } static void ksz9477_port_cfg32(struct ksz_device *dev, int port, int offset, u32 bits, bool set) { - regmap_update_bits(dev->regmap[2], PORT_CTRL_ADDR(port, offset), + regmap_update_bits(ksz_regmap_32(dev), PORT_CTRL_ADDR(port, offset), bits, set ? bits : 0); } +int ksz9477_change_mtu(struct ksz_device *dev, int port, int mtu) +{ + u16 frame_size; + + if (!dsa_is_cpu_port(dev->ds, port)) + return 0; + + frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; + + return regmap_update_bits(ksz_regmap_16(dev), REG_SW_MTU__2, + REG_SW_MTU_MASK, frame_size); +} + static int ksz9477_wait_vlan_ctrl_ready(struct ksz_device *dev) { unsigned int val; - return regmap_read_poll_timeout(dev->regmap[0], REG_SW_VLAN_CTRL, + return regmap_read_poll_timeout(ksz_regmap_8(dev), REG_SW_VLAN_CTRL, val, !(val & VLAN_START), 10, 1000); } @@ -179,7 +147,7 @@ static int ksz9477_wait_alu_ready(struct ksz_device *dev) { unsigned int val; - return regmap_read_poll_timeout(dev->regmap[2], REG_SW_ALU_CTRL__4, + return regmap_read_poll_timeout(ksz_regmap_32(dev), REG_SW_ALU_CTRL__4, val, !(val & ALU_START), 10, 1000); } @@ -187,13 +155,197 @@ static int ksz9477_wait_alu_sta_ready(struct ksz_device *dev) { unsigned int val; - return regmap_read_poll_timeout(dev->regmap[2], + return regmap_read_poll_timeout(ksz_regmap_32(dev), REG_SW_ALU_STAT_CTRL__4, val, !(val & ALU_STAT_START), 10, 1000); } -static int ksz9477_reset_switch(struct ksz_device *dev) +static void port_sgmii_s(struct ksz_device *dev, uint port, u16 devid, u16 reg) +{ + u32 data; + + data = (devid & MII_MMD_CTRL_DEVAD_MASK) << 16; + data |= reg; + ksz_pwrite32(dev, port, REG_PORT_SGMII_ADDR__4, data); +} + +static void port_sgmii_r(struct ksz_device *dev, uint port, u16 devid, u16 reg, + u16 *buf) +{ + port_sgmii_s(dev, port, devid, reg); + ksz_pread16(dev, port, REG_PORT_SGMII_DATA__4 + 2, buf); +} + +static void port_sgmii_w(struct ksz_device *dev, uint port, u16 devid, u16 reg, + u16 buf) +{ + port_sgmii_s(dev, port, devid, reg); + ksz_pwrite32(dev, port, REG_PORT_SGMII_DATA__4, buf); +} + +static int ksz9477_pcs_read(struct mii_bus *bus, int phy, int mmd, int reg) +{ + struct ksz_device *dev = bus->priv; + int port = ksz_get_sgmii_port(dev); + u16 val; + + port_sgmii_r(dev, port, mmd, reg, &val); + + /* Simulate a value to activate special code in the XPCS driver if + * supported. + */ + if (mmd == MDIO_MMD_PMAPMD) { + if (reg == MDIO_DEVID1) + val = 0x9477; + else if (reg == MDIO_DEVID2) + val = 0x22 << 10; + } else if (mmd == MDIO_MMD_VEND2) { + struct ksz_port *p = &dev->ports[port]; + + /* Need to update MII_BMCR register with the exact speed and + * duplex mode when running in SGMII mode and this register is + * used to detect connected speed in that mode. + */ + if (reg == MMD_SR_MII_AUTO_NEG_STATUS) { + int duplex, speed; + + if (val & SR_MII_STAT_LINK_UP) { + speed = (val >> SR_MII_STAT_S) & SR_MII_STAT_M; + if (speed == SR_MII_STAT_1000_MBPS) + speed = SPEED_1000; + else if (speed == SR_MII_STAT_100_MBPS) + speed = SPEED_100; + else + speed = SPEED_10; + + if (val & SR_MII_STAT_FULL_DUPLEX) + duplex = DUPLEX_FULL; + else + duplex = DUPLEX_HALF; + + if (!p->phydev.link || + p->phydev.speed != speed || + p->phydev.duplex != duplex) { + u16 ctrl; + + p->phydev.link = 1; + p->phydev.speed = speed; + p->phydev.duplex = duplex; + port_sgmii_r(dev, port, mmd, MII_BMCR, + &ctrl); + ctrl &= BMCR_ANENABLE; + ctrl |= mii_bmcr_encode_fixed(speed, + duplex); + port_sgmii_w(dev, port, mmd, MII_BMCR, + ctrl); + } + } else { + p->phydev.link = 0; + } + } else if (reg == MII_BMSR) { + p->phydev.link = !!(val & BMSR_LSTATUS); + } + } + + return val; +} + +static int ksz9477_pcs_write(struct mii_bus *bus, int phy, int mmd, int reg, + u16 val) +{ + struct ksz_device *dev = bus->priv; + int port = ksz_get_sgmii_port(dev); + + if (mmd == MDIO_MMD_VEND2) { + struct ksz_port *p = &dev->ports[port]; + + if (reg == MMD_SR_MII_AUTO_NEG_CTRL) { + u16 sgmii_mode = SR_MII_PCS_SGMII << SR_MII_PCS_MODE_S; + + /* Need these bits for 1000BASE-X mode to work with + * AN on. + */ + if (!(val & sgmii_mode)) + val |= SR_MII_SGMII_LINK_UP | + SR_MII_TX_CFG_PHY_MASTER; + + /* SGMII interrupt in the port cannot be masked, so + * make sure interrupt is not enabled as it is not + * handled. + */ + val &= ~SR_MII_AUTO_NEG_COMPLETE_INTR; + } else if (reg == MII_BMCR) { + /* The MII_ADVERTISE register needs to write once + * before doing auto-negotiation for the correct + * config_word to be sent out after reset. + */ + if ((val & BMCR_ANENABLE) && !p->sgmii_adv_write) { + u16 adv; + + /* The SGMII port cannot disable flow control + * so it is better to just advertise symmetric + * pause. + */ + port_sgmii_r(dev, port, mmd, MII_ADVERTISE, + &adv); + adv |= ADVERTISE_1000XPAUSE; + adv &= ~ADVERTISE_1000XPSE_ASYM; + port_sgmii_w(dev, port, mmd, MII_ADVERTISE, + adv); + p->sgmii_adv_write = 1; + } else if (val & BMCR_RESET) { + p->sgmii_adv_write = 0; + } + } else if (reg == MII_ADVERTISE) { + /* XPCS driver writes to this register so there is no + * need to update it for the errata. + */ + p->sgmii_adv_write = 1; + } + } + port_sgmii_w(dev, port, mmd, reg, val); + + return 0; +} + +int ksz9477_pcs_create(struct ksz_device *dev) +{ + /* This chip has a SGMII port. */ + if (ksz_has_sgmii_port(dev)) { + int port = ksz_get_sgmii_port(dev); + struct ksz_port *p = &dev->ports[port]; + struct phylink_pcs *pcs; + struct mii_bus *bus; + int ret; + + bus = devm_mdiobus_alloc(dev->dev); + if (!bus) + return -ENOMEM; + + bus->name = "ksz_pcs_mdio_bus"; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-pcs", + dev_name(dev->dev)); + bus->read_c45 = &ksz9477_pcs_read; + bus->write_c45 = &ksz9477_pcs_write; + bus->parent = dev->dev; + bus->phy_mask = ~0; + bus->priv = dev; + + ret = devm_mdiobus_register(dev->dev, bus); + if (ret) + return ret; + + pcs = xpcs_create_pcs_mdiodev(bus, 0); + if (IS_ERR(pcs)) + return PTR_ERR(pcs); + p->pcs = pcs; + } + + return 0; +} + +int ksz9477_reset_switch(struct ksz_device *dev) { u8 data8; u32 data32; @@ -202,35 +354,35 @@ static int ksz9477_reset_switch(struct ksz_device *dev) ksz_cfg(dev, REG_SW_OPERATION, SW_RESET, true); /* turn off SPI DO Edge select */ - regmap_update_bits(dev->regmap[0], REG_SW_GLOBAL_SERIAL_CTRL_0, + regmap_update_bits(ksz_regmap_8(dev), REG_SW_GLOBAL_SERIAL_CTRL_0, SPI_AUTO_EDGE_DETECTION, 0); /* default configuration */ - ksz_read8(dev, REG_SW_LUE_CTRL_1, &data8); - data8 = SW_AGING_ENABLE | SW_LINK_AUTO_AGING | - SW_SRC_ADDR_FILTER | SW_FLUSH_STP_TABLE | SW_FLUSH_MSTP_TABLE; - ksz_write8(dev, REG_SW_LUE_CTRL_1, data8); + ksz_write8(dev, REG_SW_LUE_CTRL_1, + SW_AGING_ENABLE | SW_LINK_AUTO_AGING | SW_SRC_ADDR_FILTER); /* disable interrupts */ ksz_write32(dev, REG_SW_INT_MASK__4, SWITCH_INT_MASK); ksz_write32(dev, REG_SW_PORT_INT_MASK__4, 0x7F); ksz_read32(dev, REG_SW_PORT_INT_STATUS__4, &data32); - /* set broadcast storm protection 10% rate */ - regmap_update_bits(dev->regmap[1], REG_SW_MAC_CTRL_2, - BROADCAST_STORM_RATE, - (BROADCAST_STORM_VALUE * - BROADCAST_STORM_PROT_RATE) / 100); + /* KSZ9893 compatible chips do not support refclk configuration */ + if (dev->chip_id == KSZ9893_CHIP_ID || + dev->chip_id == KSZ8563_CHIP_ID || + dev->chip_id == KSZ9563_CHIP_ID) + return 0; - if (dev->synclko_125) - ksz_write8(dev, REG_SW_GLOBAL_OUTPUT_CTRL__1, - SW_ENABLE_REFCLKO | SW_REFCLKO_IS_125MHZ); + data8 = SW_ENABLE_REFCLKO; + if (dev->synclko_disable) + data8 = 0; + else if (dev->synclko_125) + data8 = SW_ENABLE_REFCLKO | SW_REFCLKO_IS_125MHZ; + ksz_write8(dev, REG_SW_GLOBAL_OUTPUT_CTRL__1, data8); return 0; } -static void ksz9477_r_mib_cnt(struct ksz_device *dev, int port, u16 addr, - u64 *cnt) +void ksz9477_r_mib_cnt(struct ksz_device *dev, int port, u16 addr, u64 *cnt) { struct ksz_port *p = &dev->ports[port]; unsigned int val; @@ -243,7 +395,7 @@ static void ksz9477_r_mib_cnt(struct ksz_device *dev, int port, u16 addr, data |= (addr << MIB_COUNTER_INDEX_S); ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, data); - ret = regmap_read_poll_timeout(dev->regmap[2], + ret = regmap_read_poll_timeout(ksz_regmap_32(dev), PORT_CTRL_ADDR(port, REG_PORT_MIB_CTRL_STAT__4), val, !(val & MIB_COUNTER_READ), 10, 1000); /* failed to read MIB. get out of loop */ @@ -257,14 +409,14 @@ static void ksz9477_r_mib_cnt(struct ksz_device *dev, int port, u16 addr, *cnt += data; } -static void ksz9477_r_mib_pkt(struct ksz_device *dev, int port, u16 addr, - u64 *dropped, u64 *cnt) +void ksz9477_r_mib_pkt(struct ksz_device *dev, int port, u16 addr, + u64 *dropped, u64 *cnt) { - addr = ksz9477_mib_names[addr].index; + addr = dev->info->mib_names[addr].index; ksz9477_r_mib_cnt(dev, port, addr, cnt); } -static void ksz9477_freeze_mib(struct ksz_device *dev, int port, bool freeze) +void ksz9477_freeze_mib(struct ksz_device *dev, int port, bool freeze) { u32 val = freeze ? MIB_COUNTER_FLUSH_FREEZE : 0; struct ksz_port *p = &dev->ports[port]; @@ -278,7 +430,74 @@ static void ksz9477_freeze_mib(struct ksz_device *dev, int port, bool freeze) mutex_unlock(&p->mib.cnt_mutex); } -static void ksz9477_port_init_cnt(struct ksz_device *dev, int port) +static int ksz9477_half_duplex_monitor(struct ksz_device *dev, int port, + u64 tx_late_col) +{ + u8 lue_ctrl; + u32 pmavbc; + u16 pqm; + int ret; + + /* Errata DS80000754 recommends monitoring potential faults in + * half-duplex mode. The switch might not be able to communicate anymore + * in these states. If you see this message, please read the + * errata-sheet for more information: + * https://ww1.microchip.com/downloads/aemDocuments/documents/UNG/ProductDocuments/Errata/KSZ9477S-Errata-DS80000754.pdf + * To workaround this issue, half-duplex mode should be avoided. + * A software reset could be implemented to recover from this state. + */ + dev_warn_once(dev->dev, + "Half-duplex detected on port %d, transmission halt may occur\n", + port); + if (tx_late_col != 0) { + /* Transmission halt with late collisions */ + dev_crit_once(dev->dev, + "TX late collisions detected, transmission may be halted on port %d\n", + port); + } + ret = ksz_read8(dev, REG_SW_LUE_CTRL_0, &lue_ctrl); + if (ret) + return ret; + if (lue_ctrl & SW_VLAN_ENABLE) { + ret = ksz_pread16(dev, port, REG_PORT_QM_TX_CNT_0__4, &pqm); + if (ret) + return ret; + + ret = ksz_read32(dev, REG_PMAVBC, &pmavbc); + if (ret) + return ret; + + if ((FIELD_GET(PMAVBC_MASK, pmavbc) <= PMAVBC_MIN) || + (FIELD_GET(PORT_QM_TX_CNT_M, pqm) >= PORT_QM_TX_CNT_MAX)) { + /* Transmission halt with Half-Duplex and VLAN */ + dev_crit_once(dev->dev, + "resources out of limits, transmission may be halted\n"); + } + } + + return ret; +} + +int ksz9477_errata_monitor(struct ksz_device *dev, int port, + u64 tx_late_col) +{ + u8 status; + int ret; + + ret = ksz_pread8(dev, port, REG_PORT_STATUS_0, &status); + if (ret) + return ret; + + if (!(FIELD_GET(PORT_INTF_SPEED_MASK, status) + == PORT_INTF_SPEED_NONE) && + !(status & PORT_INTF_FULL_DUPLEX)) { + ret = ksz9477_half_duplex_monitor(dev, port, tx_late_col); + } + + return ret; +} + +void ksz9477_port_init_cnt(struct ksz_device *dev, int port) { struct ksz_port_mib *mib = &dev->ports[port].mib; @@ -289,27 +508,22 @@ static void ksz9477_port_init_cnt(struct ksz_device *dev, int port) ksz_write8(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FLUSH); ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, 0); mutex_unlock(&mib->cnt_mutex); - - mib->cnt_ptr = 0; - memset(mib->counters, 0, dev->mib_cnt * sizeof(u64)); } -static enum dsa_tag_protocol ksz9477_get_tag_protocol(struct dsa_switch *ds, - int port, - enum dsa_tag_protocol mp) +static void ksz9477_r_phy_quirks(struct ksz_device *dev, u16 addr, u16 reg, + u16 *data) { - enum dsa_tag_protocol proto = DSA_TAG_PROTO_KSZ9477; - struct ksz_device *dev = ds->priv; - - if (dev->features & IS_9893) - proto = DSA_TAG_PROTO_KSZ9893; - return proto; + /* KSZ8563R do not have extended registers but BMSR_ESTATEN and + * BMSR_ERCAP bits are set. + */ + if (dev->chip_id == KSZ8563_CHIP_ID && reg == MII_BMSR) + *data &= ~(BMSR_ESTATEN | BMSR_ERCAP); } -static int ksz9477_phy_read16(struct dsa_switch *ds, int addr, int reg) +int ksz9477_r_phy(struct ksz_device *dev, u16 addr, u16 reg, u16 *data) { - struct ksz_device *dev = ds->priv; u16 val = 0xffff; + int ret; /* No real PHY after this. Simulate the PHY. * A fixed PHY can be setup in the device tree, but this function is @@ -317,7 +531,7 @@ static int ksz9477_phy_read16(struct dsa_switch *ds, int addr, int reg) * For RGMII PHY there is no way to access it so the fixed PHY should * be used. For SGMII PHY the supporting code will be added later. */ - if (addr >= dev->phy_port_cnt) { + if (!dev->info->internal_phy[addr]) { struct ksz_port *p = &dev->ports[addr]; switch (reg) { @@ -350,162 +564,74 @@ static int ksz9477_phy_read16(struct dsa_switch *ds, int addr, int reg) break; } } else { - ksz_pread16(dev, addr, 0x100 + (reg << 1), &val); + ret = ksz_pread16(dev, addr, 0x100 + (reg << 1), &val); + if (ret) + return ret; + + ksz9477_r_phy_quirks(dev, addr, reg, &val); } - return val; + *data = val; + + return 0; } -static int ksz9477_phy_write16(struct dsa_switch *ds, int addr, int reg, - u16 val) +int ksz9477_w_phy(struct ksz_device *dev, u16 addr, u16 reg, u16 val) { - struct ksz_device *dev = ds->priv; + u32 mask, val32; /* No real PHY after this. */ - if (addr >= dev->phy_port_cnt) - return 0; - - /* No gigabit support. Do not write to this register. */ - if (!(dev->features & GBIT_SUPPORT) && reg == MII_CTRL1000) + if (!dev->info->internal_phy[addr]) return 0; - ksz_pwrite16(dev, addr, 0x100 + (reg << 1), val); - - return 0; -} - -static void ksz9477_get_strings(struct dsa_switch *ds, int port, - u32 stringset, uint8_t *buf) -{ - int i; - if (stringset != ETH_SS_STATS) - return; + if (reg < 0x10) + return ksz_pwrite16(dev, addr, 0x100 + (reg << 1), val); - for (i = 0; i < TOTAL_SWITCH_COUNTER_NUM; i++) { - memcpy(buf + i * ETH_GSTRING_LEN, ksz9477_mib_names[i].string, - ETH_GSTRING_LEN); + /* Errata: When using SPI, I2C, or in-band register access, + * writes to certain PHY registers should be performed as + * 32-bit writes instead of 16-bit writes. + */ + val32 = val; + mask = 0xffff; + if ((reg & 1) == 0) { + val32 <<= 16; + mask <<= 16; } + reg &= ~1; + return ksz_prmw32(dev, addr, 0x100 + (reg << 1), mask, val32); } -static void ksz9477_cfg_port_member(struct ksz_device *dev, int port, - u8 member) +void ksz9477_cfg_port_member(struct ksz_device *dev, int port, u8 member) { ksz_pwrite32(dev, port, REG_PORT_VLAN_MEMBERSHIP__4, member); - dev->ports[port].member = member; -} - -static void ksz9477_port_stp_state_set(struct dsa_switch *ds, int port, - u8 state) -{ - struct ksz_device *dev = ds->priv; - struct ksz_port *p = &dev->ports[port]; - u8 data; - int member = -1; - int forward = dev->member; - - ksz_pread8(dev, port, P_STP_CTRL, &data); - data &= ~(PORT_TX_ENABLE | PORT_RX_ENABLE | PORT_LEARN_DISABLE); - - switch (state) { - case BR_STATE_DISABLED: - data |= PORT_LEARN_DISABLE; - if (port != dev->cpu_port) - member = 0; - break; - case BR_STATE_LISTENING: - data |= (PORT_RX_ENABLE | PORT_LEARN_DISABLE); - if (port != dev->cpu_port && - p->stp_state == BR_STATE_DISABLED) - member = dev->host_mask | p->vid_member; - break; - case BR_STATE_LEARNING: - data |= PORT_RX_ENABLE; - break; - case BR_STATE_FORWARDING: - data |= (PORT_TX_ENABLE | PORT_RX_ENABLE); - - /* This function is also used internally. */ - if (port == dev->cpu_port) - break; - - member = dev->host_mask | p->vid_member; - mutex_lock(&dev->dev_mutex); - - /* Port is a member of a bridge. */ - if (dev->br_member & (1 << port)) { - dev->member |= (1 << port); - member = dev->member; - } - mutex_unlock(&dev->dev_mutex); - break; - case BR_STATE_BLOCKING: - data |= PORT_LEARN_DISABLE; - if (port != dev->cpu_port && - p->stp_state == BR_STATE_DISABLED) - member = dev->host_mask | p->vid_member; - break; - default: - dev_err(ds->dev, "invalid STP state: %d\n", state); - return; - } - - ksz_pwrite8(dev, port, P_STP_CTRL, data); - p->stp_state = state; - mutex_lock(&dev->dev_mutex); - if (data & PORT_RX_ENABLE) - dev->rx_ports |= (1 << port); - else - dev->rx_ports &= ~(1 << port); - if (data & PORT_TX_ENABLE) - dev->tx_ports |= (1 << port); - else - dev->tx_ports &= ~(1 << port); - - /* Port membership may share register with STP state. */ - if (member >= 0 && member != p->member) - ksz9477_cfg_port_member(dev, port, (u8)member); - - /* Check if forwarding needs to be updated. */ - if (state != BR_STATE_FORWARDING) { - if (dev->br_member & (1 << port)) - dev->member &= ~(1 << port); - } - - /* When topology has changed the function ksz_update_port_member - * should be called to modify port forwarding behavior. - */ - if (forward != dev->member) - ksz_update_port_member(dev, port); - mutex_unlock(&dev->dev_mutex); } -static void ksz9477_flush_dyn_mac_table(struct ksz_device *dev, int port) +void ksz9477_flush_dyn_mac_table(struct ksz_device *dev, int port) { + const u16 *regs = dev->info->regs; u8 data; - regmap_update_bits(dev->regmap[0], REG_SW_LUE_CTRL_2, + regmap_update_bits(ksz_regmap_8(dev), REG_SW_LUE_CTRL_2, SW_FLUSH_OPTION_M << SW_FLUSH_OPTION_S, SW_FLUSH_OPTION_DYN_MAC << SW_FLUSH_OPTION_S); - if (port < dev->mib_port_cnt) { + if (port < dev->info->port_cnt) { /* flush individual port */ - ksz_pread8(dev, port, P_STP_CTRL, &data); + ksz_pread8(dev, port, regs[P_STP_CTRL], &data); if (!(data & PORT_LEARN_DISABLE)) - ksz_pwrite8(dev, port, P_STP_CTRL, + ksz_pwrite8(dev, port, regs[P_STP_CTRL], data | PORT_LEARN_DISABLE); ksz_cfg(dev, S_FLUSH_TABLE_CTRL, SW_FLUSH_DYN_MAC_TABLE, true); - ksz_pwrite8(dev, port, P_STP_CTRL, data); + ksz_pwrite8(dev, port, regs[P_STP_CTRL], data); } else { /* flush all */ ksz_cfg(dev, S_FLUSH_TABLE_CTRL, SW_FLUSH_STP_TABLE, true); } } -static int ksz9477_port_vlan_filtering(struct dsa_switch *ds, int port, - bool flag) +int ksz9477_port_vlan_filtering(struct ksz_device *dev, int port, + bool flag, struct netlink_ext_ack *extack) { - struct ksz_device *dev = ds->priv; - if (flag) { ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_VLAN_LOOKUP_VID_0, true); @@ -519,70 +645,68 @@ static int ksz9477_port_vlan_filtering(struct dsa_switch *ds, int port, return 0; } -static void ksz9477_port_vlan_add(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan) +int ksz9477_port_vlan_add(struct ksz_device *dev, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack) { - struct ksz_device *dev = ds->priv; u32 vlan_table[3]; - u16 vid; bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; + int err; - for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { - if (ksz9477_get_vlan_table(dev, vid, vlan_table)) { - dev_dbg(dev->dev, "Failed to get vlan table\n"); - return; - } - - vlan_table[0] = VLAN_VALID | (vid & VLAN_FID_M); - if (untagged) - vlan_table[1] |= BIT(port); - else - vlan_table[1] &= ~BIT(port); - vlan_table[1] &= ~(BIT(dev->cpu_port)); + err = ksz9477_get_vlan_table(dev, vlan->vid, vlan_table); + if (err) { + NL_SET_ERR_MSG_MOD(extack, "Failed to get vlan table"); + return err; + } - vlan_table[2] |= BIT(port) | BIT(dev->cpu_port); + vlan_table[0] = VLAN_VALID | (vlan->vid & VLAN_FID_M); + if (untagged) + vlan_table[1] |= BIT(port); + else + vlan_table[1] &= ~BIT(port); + vlan_table[1] &= ~(BIT(dev->cpu_port)); - if (ksz9477_set_vlan_table(dev, vid, vlan_table)) { - dev_dbg(dev->dev, "Failed to set vlan table\n"); - return; - } + vlan_table[2] |= BIT(port) | BIT(dev->cpu_port); - /* change PVID */ - if (vlan->flags & BRIDGE_VLAN_INFO_PVID) - ksz_pwrite16(dev, port, REG_PORT_DEFAULT_VID, vid); + err = ksz9477_set_vlan_table(dev, vlan->vid, vlan_table); + if (err) { + NL_SET_ERR_MSG_MOD(extack, "Failed to set vlan table"); + return err; } + + /* change PVID */ + if (vlan->flags & BRIDGE_VLAN_INFO_PVID) + ksz_pwrite16(dev, port, REG_PORT_DEFAULT_VID, vlan->vid); + + return 0; } -static int ksz9477_port_vlan_del(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan) +int ksz9477_port_vlan_del(struct ksz_device *dev, int port, + const struct switchdev_obj_port_vlan *vlan) { - struct ksz_device *dev = ds->priv; bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; u32 vlan_table[3]; - u16 vid; u16 pvid; ksz_pread16(dev, port, REG_PORT_DEFAULT_VID, &pvid); pvid = pvid & 0xFFF; - for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { - if (ksz9477_get_vlan_table(dev, vid, vlan_table)) { - dev_dbg(dev->dev, "Failed to get vlan table\n"); - return -ETIMEDOUT; - } + if (ksz9477_get_vlan_table(dev, vlan->vid, vlan_table)) { + dev_dbg(dev->dev, "Failed to get vlan table\n"); + return -ETIMEDOUT; + } - vlan_table[2] &= ~BIT(port); + vlan_table[2] &= ~BIT(port); - if (pvid == vid) - pvid = 1; + if (pvid == vlan->vid) + pvid = 1; - if (untagged) - vlan_table[1] &= ~BIT(port); + if (untagged) + vlan_table[1] &= ~BIT(port); - if (ksz9477_set_vlan_table(dev, vid, vlan_table)) { - dev_dbg(dev->dev, "Failed to set vlan table\n"); - return -ETIMEDOUT; - } + if (ksz9477_set_vlan_table(dev, vlan->vid, vlan_table)) { + dev_dbg(dev->dev, "Failed to set vlan table\n"); + return -ETIMEDOUT; } ksz_pwrite16(dev, port, REG_PORT_DEFAULT_VID, pvid); @@ -590,10 +714,9 @@ static int ksz9477_port_vlan_del(struct dsa_switch *ds, int port, return 0; } -static int ksz9477_port_fdb_add(struct dsa_switch *ds, int port, - const unsigned char *addr, u16 vid) +int ksz9477_fdb_add(struct ksz_device *dev, int port, + const unsigned char *addr, u16 vid, struct dsa_db db) { - struct ksz_device *dev = ds->priv; u32 alu_table[4]; u32 data; int ret = 0; @@ -647,10 +770,9 @@ exit: return ret; } -static int ksz9477_port_fdb_del(struct dsa_switch *ds, int port, - const unsigned char *addr, u16 vid) +int ksz9477_fdb_del(struct ksz_device *dev, int port, + const unsigned char *addr, u16 vid, struct dsa_db db) { - struct ksz_device *dev = ds->priv; u32 alu_table[4]; u32 data; int ret = 0; @@ -683,10 +805,10 @@ static int ksz9477_port_fdb_del(struct dsa_switch *ds, int port, ksz_read32(dev, REG_SW_ALU_VAL_D, &alu_table[3]); /* clear forwarding port */ - alu_table[2] &= ~BIT(port); + alu_table[1] &= ~BIT(port); /* if there is no port to forward, clear table */ - if ((alu_table[2] & ALU_V_PORT_MAP) == 0) { + if ((alu_table[1] & ALU_V_PORT_MAP) == 0) { alu_table[0] = 0; alu_table[1] = 0; alu_table[2] = 0; @@ -737,10 +859,9 @@ static void ksz9477_convert_alu(struct alu_struct *alu, u32 *alu_table) alu->mac[5] = alu_table[3] & 0xFF; } -static int ksz9477_port_fdb_dump(struct dsa_switch *ds, int port, - dsa_fdb_dump_cb_t *cb, void *data) +int ksz9477_fdb_dump(struct ksz_device *dev, int port, + dsa_fdb_dump_cb_t *cb, void *data) { - struct ksz_device *dev = ds->priv; int ret = 0; u32 ksz_data; u32 alu_table[4]; @@ -767,6 +888,9 @@ static int ksz9477_port_fdb_dump(struct dsa_switch *ds, int port, goto exit; } + if (!(ksz_data & ALU_VALID)) + continue; + /* read ALU table */ ksz9477_read_table(dev, alu_table); @@ -789,14 +913,19 @@ exit: return ret; } -static void ksz9477_port_mdb_add(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_mdb *mdb) +int ksz9477_mdb_add(struct ksz_device *dev, int port, + const struct switchdev_obj_port_mdb *mdb, struct dsa_db db) { - struct ksz_device *dev = ds->priv; u32 static_table[4]; + const u8 *shifts; + const u32 *masks; u32 data; int index; u32 mac_hi, mac_lo; + int err = 0; + + shifts = dev->info->shifts; + masks = dev->info->masks; mac_hi = ((mdb->addr[0] << 8) | mdb->addr[1]); mac_lo = ((mdb->addr[2] << 24) | (mdb->addr[3] << 16)); @@ -804,14 +933,15 @@ static void ksz9477_port_mdb_add(struct dsa_switch *ds, int port, mutex_lock(&dev->alu_mutex); - for (index = 0; index < dev->num_statics; index++) { + for (index = 0; index < dev->info->num_statics; index++) { /* find empty slot first */ - data = (index << ALU_STAT_INDEX_S) | - ALU_STAT_READ | ALU_STAT_START; + data = (index << shifts[ALU_STAT_INDEX]) | + masks[ALU_STAT_READ] | ALU_STAT_START; ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); /* wait to be finished */ - if (ksz9477_wait_alu_sta_ready(dev)) { + err = ksz9477_wait_alu_sta_ready(dev); + if (err) { dev_dbg(dev->dev, "Failed to read ALU STATIC\n"); goto exit; } @@ -834,8 +964,10 @@ static void ksz9477_port_mdb_add(struct dsa_switch *ds, int port, } /* no available entry */ - if (index == dev->num_statics) + if (index == dev->info->num_statics) { + err = -ENOSPC; goto exit; + } /* add entry */ static_table[0] = ALU_V_STATIC_VALID; @@ -848,7 +980,7 @@ static void ksz9477_port_mdb_add(struct dsa_switch *ds, int port, ksz9477_write_table(dev, static_table); - data = (index << ALU_STAT_INDEX_S) | ALU_STAT_START; + data = (index << shifts[ALU_STAT_INDEX]) | ALU_STAT_START; ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); /* wait to be finished */ @@ -857,28 +989,33 @@ static void ksz9477_port_mdb_add(struct dsa_switch *ds, int port, exit: mutex_unlock(&dev->alu_mutex); + return err; } -static int ksz9477_port_mdb_del(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_mdb *mdb) +int ksz9477_mdb_del(struct ksz_device *dev, int port, + const struct switchdev_obj_port_mdb *mdb, struct dsa_db db) { - struct ksz_device *dev = ds->priv; u32 static_table[4]; + const u8 *shifts; + const u32 *masks; u32 data; int index; int ret = 0; u32 mac_hi, mac_lo; + shifts = dev->info->shifts; + masks = dev->info->masks; + mac_hi = ((mdb->addr[0] << 8) | mdb->addr[1]); mac_lo = ((mdb->addr[2] << 24) | (mdb->addr[3] << 16)); mac_lo |= ((mdb->addr[4] << 8) | mdb->addr[5]); mutex_lock(&dev->alu_mutex); - for (index = 0; index < dev->num_statics; index++) { + for (index = 0; index < dev->info->num_statics; index++) { /* find empty slot first */ - data = (index << ALU_STAT_INDEX_S) | - ALU_STAT_READ | ALU_STAT_START; + data = (index << shifts[ALU_STAT_INDEX]) | + masks[ALU_STAT_READ] | ALU_STAT_START; ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); /* wait to be finished */ @@ -904,7 +1041,7 @@ static int ksz9477_port_mdb_del(struct dsa_switch *ds, int port, } /* no available entry */ - if (index == dev->num_statics) + if (index == dev->info->num_statics) goto exit; /* clear port */ @@ -920,7 +1057,7 @@ static int ksz9477_port_mdb_del(struct dsa_switch *ds, int port, ksz9477_write_table(dev, static_table); - data = (index << ALU_STAT_INDEX_S) | ALU_STAT_START; + data = (index << shifts[ALU_STAT_INDEX]) | ALU_STAT_START; ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); /* wait to be finished */ @@ -934,19 +1071,36 @@ exit: return ret; } -static int ksz9477_port_mirror_add(struct dsa_switch *ds, int port, - struct dsa_mall_mirror_tc_entry *mirror, - bool ingress) +int ksz9477_port_mirror_add(struct ksz_device *dev, int port, + struct dsa_mall_mirror_tc_entry *mirror, + bool ingress, struct netlink_ext_ack *extack) { - struct ksz_device *dev = ds->priv; + u8 data; + int p; + + /* Limit to one sniffer port + * Check if any of the port is already set for sniffing + * If yes, instruct the user to remove the previous entry & exit + */ + for (p = 0; p < dev->info->port_cnt; p++) { + /* Skip the current sniffing port */ + if (p == mirror->to_local_port) + continue; + + ksz_pread8(dev, p, P_MIRROR_CTRL, &data); + + if (data & PORT_MIRROR_SNIFFER) { + NL_SET_ERR_MSG_MOD(extack, + "Sniffer port is already configured, delete existing rules & retry"); + return -EBUSY; + } + } if (ingress) ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, true); else ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, true); - ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_SNIFFER, false); - /* configure mirror port */ ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL, PORT_MIRROR_SNIFFER, true); @@ -956,248 +1110,148 @@ static int ksz9477_port_mirror_add(struct dsa_switch *ds, int port, return 0; } -static void ksz9477_port_mirror_del(struct dsa_switch *ds, int port, - struct dsa_mall_mirror_tc_entry *mirror) +void ksz9477_port_mirror_del(struct ksz_device *dev, int port, + struct dsa_mall_mirror_tc_entry *mirror) { - struct ksz_device *dev = ds->priv; + bool in_use = false; u8 data; + int p; if (mirror->ingress) ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, false); else ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, false); - ksz_pread8(dev, port, P_MIRROR_CTRL, &data); - if (!(data & (PORT_MIRROR_RX | PORT_MIRROR_TX))) + /* Check if any of the port is still referring to sniffer port */ + for (p = 0; p < dev->info->port_cnt; p++) { + ksz_pread8(dev, p, P_MIRROR_CTRL, &data); + + if ((data & (PORT_MIRROR_RX | PORT_MIRROR_TX))) { + in_use = true; + break; + } + } + + /* delete sniffing if there are no other mirroring rules */ + if (!in_use) ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL, PORT_MIRROR_SNIFFER, false); } -static void ksz9477_phy_setup(struct ksz_device *dev, int port, - struct phy_device *phy) +static phy_interface_t ksz9477_get_interface(struct ksz_device *dev, int port) { - /* Only apply to port with PHY. */ - if (port >= dev->phy_port_cnt) - return; + phy_interface_t interface; + bool gbit; - /* The MAC actually cannot run in 1000 half-duplex mode. */ - phy_remove_link_mode(phy, - ETHTOOL_LINK_MODE_1000baseT_Half_BIT); + if (dev->info->internal_phy[port]) + return PHY_INTERFACE_MODE_NA; - /* PHY does not support gigabit. */ - if (!(dev->features & GBIT_SUPPORT)) - phy_remove_link_mode(phy, - ETHTOOL_LINK_MODE_1000baseT_Full_BIT); -} + gbit = ksz_get_gbit(dev, port); -static bool ksz9477_get_gbit(struct ksz_device *dev, u8 data) -{ - bool gbit; + interface = ksz_get_xmii(dev, port, gbit); - if (dev->features & NEW_XMII) - gbit = !(data & PORT_MII_NOT_1GBIT); - else - gbit = !!(data & PORT_MII_1000MBIT_S1); - return gbit; + return interface; } -static void ksz9477_set_gbit(struct ksz_device *dev, bool gbit, u8 *data) +void ksz9477_get_caps(struct ksz_device *dev, int port, + struct phylink_config *config) { - if (dev->features & NEW_XMII) { - if (gbit) - *data &= ~PORT_MII_NOT_1GBIT; - else - *data |= PORT_MII_NOT_1GBIT; - } else { - if (gbit) - *data |= PORT_MII_1000MBIT_S1; - else - *data &= ~PORT_MII_1000MBIT_S1; - } -} + config->mac_capabilities = MAC_10 | MAC_100 | MAC_ASYM_PAUSE | + MAC_SYM_PAUSE; -static int ksz9477_get_xmii(struct ksz_device *dev, u8 data) -{ - int mode; + if (dev->info->gbit_capable[port]) + config->mac_capabilities |= MAC_1000FD; - if (dev->features & NEW_XMII) { - switch (data & PORT_MII_SEL_M) { - case PORT_MII_SEL: - mode = 0; - break; - case PORT_RMII_SEL: - mode = 1; - break; - case PORT_GMII_SEL: - mode = 2; - break; - default: - mode = 3; - } - } else { - switch (data & PORT_MII_SEL_M) { - case PORT_MII_SEL_S1: - mode = 0; - break; - case PORT_RMII_SEL_S1: - mode = 1; - break; - case PORT_GMII_SEL_S1: - mode = 2; - break; - default: - mode = 3; - } + if (ksz_is_sgmii_port(dev, port)) { + struct ksz_port *p = &dev->ports[port]; + + phy_interface_or(config->supported_interfaces, + config->supported_interfaces, + p->pcs->supported_interfaces); } - return mode; } -static void ksz9477_set_xmii(struct ksz_device *dev, int mode, u8 *data) +int ksz9477_set_ageing_time(struct ksz_device *dev, unsigned int msecs) { - u8 xmii; + u32 secs = msecs / 1000; + u8 data, mult, value; + u32 max_val; + int ret; - if (dev->features & NEW_XMII) { - switch (mode) { - case 0: - xmii = PORT_MII_SEL; - break; - case 1: - xmii = PORT_RMII_SEL; - break; - case 2: - xmii = PORT_GMII_SEL; - break; - default: - xmii = PORT_RGMII_SEL; - break; - } - } else { - switch (mode) { - case 0: - xmii = PORT_MII_SEL_S1; - break; - case 1: - xmii = PORT_RMII_SEL_S1; - break; - case 2: - xmii = PORT_GMII_SEL_S1; - break; - default: - xmii = PORT_RGMII_SEL_S1; - break; - } - } - *data &= ~PORT_MII_SEL_M; - *data |= xmii; -} +#define MAX_TIMER_VAL ((1 << 8) - 1) -static phy_interface_t ksz9477_get_interface(struct ksz_device *dev, int port) -{ - phy_interface_t interface; - bool gbit; - int mode; - u8 data8; + /* The aging timer comprises a 3-bit multiplier and an 8-bit second + * value. Either of them cannot be zero. The maximum timer is then + * 7 * 255 = 1785 seconds. + */ + if (!secs) + secs = 1; - if (port < dev->phy_port_cnt) - return PHY_INTERFACE_MODE_NA; - ksz_pread8(dev, port, REG_PORT_XMII_CTRL_1, &data8); - gbit = ksz9477_get_gbit(dev, data8); - mode = ksz9477_get_xmii(dev, data8); - switch (mode) { - case 2: - interface = PHY_INTERFACE_MODE_GMII; - if (gbit) - break; - /* fall through */ - case 0: - interface = PHY_INTERFACE_MODE_MII; - break; - case 1: - interface = PHY_INTERFACE_MODE_RMII; - break; - default: - interface = PHY_INTERFACE_MODE_RGMII; - if (data8 & PORT_RGMII_ID_EG_ENABLE) - interface = PHY_INTERFACE_MODE_RGMII_TXID; - if (data8 & PORT_RGMII_ID_IG_ENABLE) { - interface = PHY_INTERFACE_MODE_RGMII_RXID; - if (data8 & PORT_RGMII_ID_EG_ENABLE) - interface = PHY_INTERFACE_MODE_RGMII_ID; - } - break; + /* Return error if too large. */ + else if (secs > 7 * MAX_TIMER_VAL) + return -EINVAL; + + ret = ksz_read8(dev, REG_SW_LUE_CTRL_0, &value); + if (ret < 0) + return ret; + + /* Check whether there is need to update the multiplier. */ + mult = FIELD_GET(SW_AGE_CNT_M, value); + max_val = MAX_TIMER_VAL; + if (mult > 0) { + /* Try to use the same multiplier already in the register as + * the hardware default uses multiplier 4 and 75 seconds for + * 300 seconds. + */ + max_val = DIV_ROUND_UP(secs, mult); + if (max_val > MAX_TIMER_VAL || max_val * mult != secs) + max_val = MAX_TIMER_VAL; } - return interface; -} -static void ksz9477_port_mmd_write(struct ksz_device *dev, int port, - u8 dev_addr, u16 reg_addr, u16 val) -{ - ksz_pwrite16(dev, port, REG_PORT_PHY_MMD_SETUP, - MMD_SETUP(PORT_MMD_OP_INDEX, dev_addr)); - ksz_pwrite16(dev, port, REG_PORT_PHY_MMD_INDEX_DATA, reg_addr); - ksz_pwrite16(dev, port, REG_PORT_PHY_MMD_SETUP, - MMD_SETUP(PORT_MMD_OP_DATA_NO_INCR, dev_addr)); - ksz_pwrite16(dev, port, REG_PORT_PHY_MMD_INDEX_DATA, val); + data = DIV_ROUND_UP(secs, max_val); + if (mult != data) { + value &= ~SW_AGE_CNT_M; + value |= FIELD_PREP(SW_AGE_CNT_M, data); + ret = ksz_write8(dev, REG_SW_LUE_CTRL_0, value); + if (ret < 0) + return ret; + } + + value = DIV_ROUND_UP(secs, data); + return ksz_write8(dev, REG_SW_LUE_CTRL_3, value); } -static void ksz9477_phy_errata_setup(struct ksz_device *dev, int port) +void ksz9477_port_queue_split(struct ksz_device *dev, int port) { - /* Apply PHY settings to address errata listed in - * KSZ9477, KSZ9897, KSZ9896, KSZ9567, KSZ8565 - * Silicon Errata and Data Sheet Clarification documents: - * - * Register settings are needed to improve PHY receive performance - */ - ksz9477_port_mmd_write(dev, port, 0x01, 0x6f, 0xdd0b); - ksz9477_port_mmd_write(dev, port, 0x01, 0x8f, 0x6032); - ksz9477_port_mmd_write(dev, port, 0x01, 0x9d, 0x248c); - ksz9477_port_mmd_write(dev, port, 0x01, 0x75, 0x0060); - ksz9477_port_mmd_write(dev, port, 0x01, 0xd3, 0x7777); - ksz9477_port_mmd_write(dev, port, 0x1c, 0x06, 0x3008); - ksz9477_port_mmd_write(dev, port, 0x1c, 0x08, 0x2001); - - /* Transmit waveform amplitude can be improved - * (1000BASE-T, 100BASE-TX, 10BASE-Te) - */ - ksz9477_port_mmd_write(dev, port, 0x1c, 0x04, 0x00d0); + u8 data; - /* Energy Efficient Ethernet (EEE) feature select must - * be manually disabled (except on KSZ8565 which is 100Mbit) - */ - if (dev->features & GBIT_SUPPORT) - ksz9477_port_mmd_write(dev, port, 0x07, 0x3c, 0x0000); + if (dev->info->num_tx_queues == 8) + data = PORT_EIGHT_QUEUE; + else if (dev->info->num_tx_queues == 4) + data = PORT_FOUR_QUEUE; + else if (dev->info->num_tx_queues == 2) + data = PORT_TWO_QUEUE; + else + data = PORT_SINGLE_QUEUE; - /* Register settings are required to meet data sheet - * supply current specifications - */ - ksz9477_port_mmd_write(dev, port, 0x1c, 0x13, 0x6eff); - ksz9477_port_mmd_write(dev, port, 0x1c, 0x14, 0xe6ff); - ksz9477_port_mmd_write(dev, port, 0x1c, 0x15, 0x6eff); - ksz9477_port_mmd_write(dev, port, 0x1c, 0x16, 0xe6ff); - ksz9477_port_mmd_write(dev, port, 0x1c, 0x17, 0x00ff); - ksz9477_port_mmd_write(dev, port, 0x1c, 0x18, 0x43ff); - ksz9477_port_mmd_write(dev, port, 0x1c, 0x19, 0xc3ff); - ksz9477_port_mmd_write(dev, port, 0x1c, 0x1a, 0x6fff); - ksz9477_port_mmd_write(dev, port, 0x1c, 0x1b, 0x07ff); - ksz9477_port_mmd_write(dev, port, 0x1c, 0x1c, 0x0fff); - ksz9477_port_mmd_write(dev, port, 0x1c, 0x1d, 0xe7ff); - ksz9477_port_mmd_write(dev, port, 0x1c, 0x1e, 0xefff); - ksz9477_port_mmd_write(dev, port, 0x1c, 0x20, 0xeeee); + ksz_prmw8(dev, port, REG_PORT_CTRL_0, PORT_QUEUE_SPLIT_MASK, data); } -static void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port) +void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port) { - u8 data8; - u8 member; + const u16 *regs = dev->info->regs; + struct dsa_switch *ds = dev->ds; u16 data16; - struct ksz_port *p = &dev->ports[port]; + u8 member; /* enable tag tail for host port */ if (cpu_port) ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_TAIL_TAG_ENABLE, true); + ksz9477_port_queue_split(dev, port); + ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_MAC_LOOPBACK, false); /* set back pressure */ @@ -1206,165 +1260,211 @@ static void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port) /* enable broadcast storm limit */ ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true); - /* disable DiffServ priority */ - ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_DIFFSERV_PRIO_ENABLE, false); - /* replace priority */ ksz_port_cfg(dev, port, REG_PORT_MRI_MAC_CTRL, PORT_USER_PRIO_CEILING, false); ksz9477_port_cfg32(dev, port, REG_PORT_MTI_QUEUE_CTRL_0__4, MTI_PVID_REPLACE, false); - /* enable 802.1p priority */ - ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_802_1P_PRIO_ENABLE, true); - - if (port < dev->phy_port_cnt) { - /* do not force flow control */ - ksz_port_cfg(dev, port, REG_PORT_CTRL_0, - PORT_FORCE_TX_FLOW_CTRL | PORT_FORCE_RX_FLOW_CTRL, - false); - - if (dev->phy_errata_9477) - ksz9477_phy_errata_setup(dev, port); - } else { - /* force flow control */ - ksz_port_cfg(dev, port, REG_PORT_CTRL_0, - PORT_FORCE_TX_FLOW_CTRL | PORT_FORCE_RX_FLOW_CTRL, - true); + /* force flow control for non-PHY ports only */ + ksz_port_cfg(dev, port, REG_PORT_CTRL_0, + PORT_FORCE_TX_FLOW_CTRL | PORT_FORCE_RX_FLOW_CTRL, + !dev->info->internal_phy[port]); - /* configure MAC to 1G & RGMII mode */ - ksz_pread8(dev, port, REG_PORT_XMII_CTRL_1, &data8); - switch (dev->interface) { - case PHY_INTERFACE_MODE_MII: - ksz9477_set_xmii(dev, 0, &data8); - ksz9477_set_gbit(dev, false, &data8); - p->phydev.speed = SPEED_100; - break; - case PHY_INTERFACE_MODE_RMII: - ksz9477_set_xmii(dev, 1, &data8); - ksz9477_set_gbit(dev, false, &data8); - p->phydev.speed = SPEED_100; - break; - case PHY_INTERFACE_MODE_GMII: - ksz9477_set_xmii(dev, 2, &data8); - ksz9477_set_gbit(dev, true, &data8); - p->phydev.speed = SPEED_1000; - break; - default: - ksz9477_set_xmii(dev, 3, &data8); - ksz9477_set_gbit(dev, true, &data8); - data8 &= ~PORT_RGMII_ID_IG_ENABLE; - data8 &= ~PORT_RGMII_ID_EG_ENABLE; - if (dev->interface == PHY_INTERFACE_MODE_RGMII_ID || - dev->interface == PHY_INTERFACE_MODE_RGMII_RXID) - data8 |= PORT_RGMII_ID_IG_ENABLE; - if (dev->interface == PHY_INTERFACE_MODE_RGMII_ID || - dev->interface == PHY_INTERFACE_MODE_RGMII_TXID) - data8 |= PORT_RGMII_ID_EG_ENABLE; - p->phydev.speed = SPEED_1000; - break; - } - ksz_pwrite8(dev, port, REG_PORT_XMII_CTRL_1, data8); - p->phydev.duplex = 1; - } - mutex_lock(&dev->dev_mutex); - if (cpu_port) { - member = dev->port_mask; - dev->on_ports = dev->host_mask; - dev->live_ports = dev->host_mask; - } else { - member = dev->host_mask | p->vid_member; - dev->on_ports |= (1 << port); + if (cpu_port) + member = dsa_user_ports(ds); + else + member = BIT(dsa_upstream_port(ds, port)); - /* Link was detected before port is enabled. */ - if (p->phydev.link) - dev->live_ports |= (1 << port); - } - mutex_unlock(&dev->dev_mutex); ksz9477_cfg_port_member(dev, port, member); /* clear pending interrupts */ - if (port < dev->phy_port_cnt) + if (dev->info->internal_phy[port]) ksz_pread16(dev, port, REG_PORT_PHY_INT_ENABLE, &data16); + + ksz9477_port_acl_init(dev, port); + + /* clear pending wake flags */ + ksz_handle_wake_reason(dev, port); + + /* Disable all WoL options by default. Otherwise + * ksz_switch_macaddr_get/put logic will not work properly. + */ + ksz_pwrite8(dev, port, regs[REG_PORT_PME_CTRL], 0); } -static void ksz9477_config_cpu_port(struct dsa_switch *ds) +void ksz9477_config_cpu_port(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; struct ksz_port *p; int i; - ds->num_ports = dev->port_cnt; - - for (i = 0; i < dev->port_cnt; i++) { - if (dsa_is_cpu_port(ds, i) && (dev->cpu_ports & (1 << i))) { + for (i = 0; i < dev->info->port_cnt; i++) { + if (dsa_is_cpu_port(ds, i) && + (dev->info->cpu_ports & (1 << i))) { phy_interface_t interface; + const char *prev_msg; + const char *prev_mode; dev->cpu_port = i; - dev->host_mask = (1 << dev->cpu_port); - dev->port_mask |= dev->host_mask; + p = &dev->ports[i]; /* Read from XMII register to determine host port * interface. If set specifically in device tree * note the difference to help debugging. */ interface = ksz9477_get_interface(dev, i); - if (!dev->interface) - dev->interface = interface; - if (interface && interface != dev->interface) - dev_info(dev->dev, - "use %s instead of %s\n", - phy_modes(dev->interface), - phy_modes(interface)); + if (!p->interface) { + if (dev->compat_interface) { + dev_warn(dev->dev, + "Using legacy switch \"phy-mode\" property, because it is missing on port %d node. " + "Please update your device tree.\n", + i); + p->interface = dev->compat_interface; + } else { + p->interface = interface; + } + } + if (interface && interface != p->interface) { + prev_msg = " instead of "; + prev_mode = phy_modes(interface); + } else { + prev_msg = ""; + prev_mode = ""; + } + dev_info(dev->dev, + "Port%d: using phy mode %s%s%s\n", + i, + phy_modes(p->interface), + prev_msg, + prev_mode); /* enable cpu port */ ksz9477_port_setup(dev, i, true); - p = &dev->ports[dev->cpu_port]; - p->vid_member = dev->port_mask; - p->on = 1; } } - dev->member = dev->host_mask; - - for (i = 0; i < dev->mib_port_cnt; i++) { + for (i = 0; i < dev->info->port_cnt; i++) { if (i == dev->cpu_port) continue; - p = &dev->ports[i]; + ksz_port_stp_state_set(ds, i, BR_STATE_DISABLED); - /* Initialize to non-zero so that ksz_cfg_port_member() will - * be called. - */ - p->vid_member = (1 << i); - p->member = dev->port_mask; - ksz9477_port_stp_state_set(ds, i, BR_STATE_DISABLED); - p->on = 1; - if (i < dev->phy_port_cnt) - p->phy = 1; - if (dev->chip_id == 0x00947700 && i == 6) { - p->sgmii = 1; - - /* SGMII PHY detection code is not implemented yet. */ - p->phy = 0; + /* Power down the internal PHY if port is unused. */ + if (dsa_is_unused_port(ds, i) && dev->info->internal_phy[i]) + ksz_pwrite16(dev, i, 0x100, BMCR_PDOWN); + } +} + +#define RESV_MCAST_CNT 8 + +static u8 reserved_mcast_map[RESV_MCAST_CNT] = { 0, 1, 3, 16, 32, 33, 2, 17 }; + +int ksz9477_enable_stp_addr(struct ksz_device *dev) +{ + u8 i, ports, update; + const u32 *masks; + bool override; + u32 data; + int ret; + + masks = dev->info->masks; + + /* Enable Reserved multicast table */ + ksz_cfg(dev, REG_SW_LUE_CTRL_0, SW_RESV_MCAST_ENABLE, true); + + /* The reserved multicast address table has 8 entries. Each entry has + * a default value of which port to forward. It is assumed the host + * port is the last port in most of the switches, but that is not the + * case for KSZ9477 or maybe KSZ9897. For LAN937X family the default + * port is port 5, the first RGMII port. It is okay for LAN9370, a + * 5-port switch, but may not be correct for the other 8-port + * versions. It is necessary to update the whole table to forward to + * the right ports. + * Furthermore PTP messages can use a reserved multicast address and + * the host will not receive them if this table is not correct. + */ + for (i = 0; i < RESV_MCAST_CNT; i++) { + data = reserved_mcast_map[i] << + dev->info->shifts[ALU_STAT_INDEX]; + data |= ALU_STAT_START | + masks[ALU_STAT_DIRECT] | + masks[ALU_RESV_MCAST_ADDR] | + masks[ALU_STAT_READ]; + ret = ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); + if (ret < 0) + return ret; + + /* wait to be finished */ + ret = ksz9477_wait_alu_sta_ready(dev); + if (ret < 0) + return ret; + + ret = ksz_read32(dev, REG_SW_ALU_VAL_B, &data); + if (ret < 0) + return ret; + + override = false; + ports = data & dev->port_mask; + switch (i) { + case 0: + case 6: + /* Change the host port. */ + update = BIT(dev->cpu_port); + override = true; + break; + case 2: + /* Change the host port. */ + update = BIT(dev->cpu_port); + break; + case 4: + case 5: + case 7: + /* Skip the host port. */ + update = dev->port_mask & ~BIT(dev->cpu_port); + break; + default: + update = ports; + break; + } + if (update != ports || override) { + data &= ~dev->port_mask; + data |= update; + /* Set Override bit to receive frame even when port is + * closed. + */ + if (override) + data |= ALU_V_OVERRIDE; + ret = ksz_write32(dev, REG_SW_ALU_VAL_B, data); + if (ret < 0) + return ret; + + data = reserved_mcast_map[i] << + dev->info->shifts[ALU_STAT_INDEX]; + data |= ALU_STAT_START | + masks[ALU_STAT_DIRECT] | + masks[ALU_RESV_MCAST_ADDR] | + masks[ALU_STAT_WRITE]; + ret = ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); + if (ret < 0) + return ret; + + /* wait to be finished */ + ret = ksz9477_wait_alu_sta_ready(dev); + if (ret < 0) + return ret; } } + + return 0; } -static int ksz9477_setup(struct dsa_switch *ds) +int ksz9477_setup(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; + const u16 *regs = dev->info->regs; int ret = 0; - dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table), - dev->num_vlans, GFP_KERNEL); - if (!dev->vlan_cache) - return -ENOMEM; - - ret = ksz9477_reset_switch(dev); - if (ret) { - dev_err(ds->dev, "failed to reset switch\n"); - return ret; - } + ds->mtu_enforcement_ingress = true; /* Required for port partitioning. */ ksz9477_cfg32(dev, REG_SW_QM_CTRL__4, UNICAST_VLAN_BOUNDARY, @@ -1373,12 +1473,18 @@ static int ksz9477_setup(struct dsa_switch *ds) /* Do not work correctly with tail tagging. */ ksz_cfg(dev, REG_SW_MAC_CTRL_0, SW_CHECK_LENGTH, false); - /* accept packet up to 2000bytes */ - ksz_cfg(dev, REG_SW_MAC_CTRL_1, SW_LEGAL_PACKET_DISABLE, true); + /* Enable REG_SW_MTU__2 reg by setting SW_JUMBO_PACKET */ + ksz_cfg(dev, REG_SW_MAC_CTRL_1, SW_JUMBO_PACKET, true); - ksz9477_config_cpu_port(ds); + /* Use collision based back pressure mode. */ + ksz_cfg(dev, REG_SW_MAC_CTRL_1, SW_BACK_PRESSURE, + SW_BACK_PRESSURE_COLLISION); - ksz_cfg(dev, REG_SW_MAC_CTRL_1, MULTICAST_STORM_DISABLE, true); + /* Now we can configure default MTU value */ + ret = regmap_update_bits(ksz_regmap_16(dev), REG_SW_MTU__2, REG_SW_MTU_MASK, + VLAN_ETH_FRAME_LEN + ETH_FCS_LEN); + if (ret) + return ret; /* queue based egress rate limit */ ksz_cfg(dev, REG_SW_MAC_CTRL_5, SW_OUT_RATE_LIMIT_QUEUE_BASED, true); @@ -1386,238 +1492,127 @@ static int ksz9477_setup(struct dsa_switch *ds) /* enable global MIB counter freeze function */ ksz_cfg(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FREEZE, true); - /* start switch */ - ksz_cfg(dev, REG_SW_OPERATION, SW_START, true); - - ksz_init_mib_timer(dev); - - return 0; + /* Make sure PME (WoL) is not enabled. If requested, it will + * be enabled by ksz_wol_pre_shutdown(). Otherwise, some PMICs + * do not like PME events changes before shutdown. + */ + return ksz_write8(dev, regs[REG_SW_PME_CTRL], 0); } -static const struct dsa_switch_ops ksz9477_switch_ops = { - .get_tag_protocol = ksz9477_get_tag_protocol, - .setup = ksz9477_setup, - .phy_read = ksz9477_phy_read16, - .phy_write = ksz9477_phy_write16, - .adjust_link = ksz_adjust_link, - .port_enable = ksz_enable_port, - .port_disable = ksz_disable_port, - .get_strings = ksz9477_get_strings, - .get_ethtool_stats = ksz_get_ethtool_stats, - .get_sset_count = ksz_sset_count, - .port_bridge_join = ksz_port_bridge_join, - .port_bridge_leave = ksz_port_bridge_leave, - .port_stp_state_set = ksz9477_port_stp_state_set, - .port_fast_age = ksz_port_fast_age, - .port_vlan_filtering = ksz9477_port_vlan_filtering, - .port_vlan_prepare = ksz_port_vlan_prepare, - .port_vlan_add = ksz9477_port_vlan_add, - .port_vlan_del = ksz9477_port_vlan_del, - .port_fdb_dump = ksz9477_port_fdb_dump, - .port_fdb_add = ksz9477_port_fdb_add, - .port_fdb_del = ksz9477_port_fdb_del, - .port_mdb_prepare = ksz_port_mdb_prepare, - .port_mdb_add = ksz9477_port_mdb_add, - .port_mdb_del = ksz9477_port_mdb_del, - .port_mirror_add = ksz9477_port_mirror_add, - .port_mirror_del = ksz9477_port_mirror_del, -}; - -static u32 ksz9477_get_port_addr(int port, int offset) +u32 ksz9477_get_port_addr(int port, int offset) { return PORT_CTRL_ADDR(port, offset); } -static int ksz9477_switch_detect(struct ksz_device *dev) +int ksz9477_tc_cbs_set_cinc(struct ksz_device *dev, int port, u32 val) { - u8 data8; - u8 id_hi; - u8 id_lo; - u32 id32; - int ret; - - /* turn off SPI DO Edge select */ - ret = ksz_read8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, &data8); - if (ret) - return ret; - - data8 &= ~SPI_AUTO_EDGE_DETECTION; - ret = ksz_write8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, data8); - if (ret) - return ret; + val = val >> 8; - /* read chip id */ - ret = ksz_read32(dev, REG_CHIP_ID0__1, &id32); - if (ret) - return ret; - ret = ksz_read8(dev, REG_GLOBAL_OPTIONS, &data8); - if (ret) - return ret; + return ksz_pwrite16(dev, port, REG_PORT_MTI_CREDIT_INCREMENT, val); +} - /* Number of ports can be reduced depending on chip. */ - dev->mib_port_cnt = TOTAL_PORT_NUM; - dev->phy_port_cnt = 5; +/* The KSZ9477 provides following HW features to accelerate + * HSR frames handling: + * + * 1. TX PACKET DUPLICATION FROM HOST TO SWITCH + * 2. RX PACKET DUPLICATION DISCARDING + * 3. PREVENTING PACKET LOOP IN THE RING BY SELF-ADDRESS FILTERING + * + * Only one from point 1. has the NETIF_F* flag available. + * + * Ones from point 2 and 3 are "best effort" - i.e. those will + * work correctly most of the time, but it may happen that some + * frames will not be caught - to be more specific; there is a race + * condition in hardware such that, when duplicate packets are received + * on member ports very close in time to each other, the hardware fails + * to detect that they are duplicates. + * + * Hence, the SW needs to handle those special cases. However, the speed + * up gain is considerable when above features are used. + * + * Moreover, the NETIF_F_HW_HSR_FWD feature is also enabled, as HSR frames + * can be forwarded in the switch fabric between HSR ports. + */ +#define KSZ9477_SUPPORTED_HSR_FEATURES (NETIF_F_HW_HSR_DUP | NETIF_F_HW_HSR_FWD) - /* Default capability is gigabit capable. */ - dev->features = GBIT_SUPPORT; +void ksz9477_hsr_join(struct dsa_switch *ds, int port, struct net_device *hsr) +{ + struct ksz_device *dev = ds->priv; + struct net_device *user; + struct dsa_port *hsr_dp; + u8 data, hsr_ports = 0; - id_hi = (u8)(id32 >> 16); - id_lo = (u8)(id32 >> 8); - if ((id_lo & 0xf) == 3) { - /* Chip is from KSZ9893 design. */ - dev->features |= IS_9893; + /* Program which port(s) shall support HSR */ + ksz_rmw32(dev, REG_HSR_PORT_MAP__4, BIT(port), BIT(port)); - /* Chip does not support gigabit. */ - if (data8 & SW_QW_ABLE) - dev->features &= ~GBIT_SUPPORT; - dev->mib_port_cnt = 3; - dev->phy_port_cnt = 2; - } else { - /* Chip uses new XMII register definitions. */ - dev->features |= NEW_XMII; + /* Forward frames between HSR ports (i.e. bridge together HSR ports) */ + if (dev->hsr_ports) { + dsa_hsr_foreach_port(hsr_dp, ds, hsr) + hsr_ports |= BIT(hsr_dp->index); - /* Chip does not support gigabit. */ - if (!(data8 & SW_GIGABIT_ABLE)) - dev->features &= ~GBIT_SUPPORT; + hsr_ports |= BIT(dsa_upstream_port(ds, port)); + dsa_hsr_foreach_port(hsr_dp, ds, hsr) + ksz9477_cfg_port_member(dev, hsr_dp->index, hsr_ports); } - /* Change chip id to known ones so it can be matched against them. */ - id32 = (id_hi << 16) | (id_lo << 8); + if (!dev->hsr_ports) { + /* Enable discarding of received HSR frames */ + ksz_read8(dev, REG_HSR_ALU_CTRL_0__1, &data); + data |= HSR_DUPLICATE_DISCARD; + data &= ~HSR_NODE_UNICAST; + ksz_write8(dev, REG_HSR_ALU_CTRL_0__1, data); + } - dev->chip_id = id32; + /* Enable per port self-address filtering. + * The global self-address filtering has already been enabled in the + * ksz9477_reset_switch() function. + */ + ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_SRC_ADDR_FILTER, true); - return 0; + /* Setup HW supported features for lan HSR ports */ + user = dsa_to_port(ds, port)->user; + user->features |= KSZ9477_SUPPORTED_HSR_FEATURES; } -struct ksz_chip_data { - u32 chip_id; - const char *dev_name; - int num_vlans; - int num_alus; - int num_statics; - int cpu_ports; - int port_cnt; - bool phy_errata_9477; -}; - -static const struct ksz_chip_data ksz9477_switch_chips[] = { - { - .chip_id = 0x00947700, - .dev_name = "KSZ9477", - .num_vlans = 4096, - .num_alus = 4096, - .num_statics = 16, - .cpu_ports = 0x7F, /* can be configured as cpu port */ - .port_cnt = 7, /* total physical port count */ - .phy_errata_9477 = true, - }, - { - .chip_id = 0x00989700, - .dev_name = "KSZ9897", - .num_vlans = 4096, - .num_alus = 4096, - .num_statics = 16, - .cpu_ports = 0x7F, /* can be configured as cpu port */ - .port_cnt = 7, /* total physical port count */ - .phy_errata_9477 = true, - }, - { - .chip_id = 0x00989300, - .dev_name = "KSZ9893", - .num_vlans = 4096, - .num_alus = 4096, - .num_statics = 16, - .cpu_ports = 0x07, /* can be configured as cpu port */ - .port_cnt = 3, /* total port count */ - }, - { - .chip_id = 0x00956700, - .dev_name = "KSZ9567", - .num_vlans = 4096, - .num_alus = 4096, - .num_statics = 16, - .cpu_ports = 0x7F, /* can be configured as cpu port */ - .port_cnt = 7, /* total physical port count */ - }, -}; - -static int ksz9477_switch_init(struct ksz_device *dev) +void ksz9477_hsr_leave(struct dsa_switch *ds, int port, struct net_device *hsr) { - int i; + struct ksz_device *dev = ds->priv; - dev->ds->ops = &ksz9477_switch_ops; + /* Clear port HSR support */ + ksz_rmw32(dev, REG_HSR_PORT_MAP__4, BIT(port), 0); - for (i = 0; i < ARRAY_SIZE(ksz9477_switch_chips); i++) { - const struct ksz_chip_data *chip = &ksz9477_switch_chips[i]; + /* Disable forwarding frames between HSR ports */ + ksz9477_cfg_port_member(dev, port, BIT(dsa_upstream_port(ds, port))); - if (dev->chip_id == chip->chip_id) { - dev->name = chip->dev_name; - dev->num_vlans = chip->num_vlans; - dev->num_alus = chip->num_alus; - dev->num_statics = chip->num_statics; - dev->port_cnt = chip->port_cnt; - dev->cpu_ports = chip->cpu_ports; - dev->phy_errata_9477 = chip->phy_errata_9477; + /* Disable per port self-address filtering */ + ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_SRC_ADDR_FILTER, false); +} - break; - } - } +int ksz9477_switch_init(struct ksz_device *dev) +{ + u8 data8; + int ret; - /* no switch found */ - if (!dev->port_cnt) - return -ENODEV; - - dev->port_mask = (1 << dev->port_cnt) - 1; - - dev->reg_mib_cnt = SWITCH_COUNTER_NUM; - dev->mib_cnt = TOTAL_SWITCH_COUNTER_NUM; - - i = dev->mib_port_cnt; - dev->ports = devm_kzalloc(dev->dev, sizeof(struct ksz_port) * i, - GFP_KERNEL); - if (!dev->ports) - return -ENOMEM; - for (i = 0; i < dev->mib_port_cnt; i++) { - mutex_init(&dev->ports[i].mib.cnt_mutex); - dev->ports[i].mib.counters = - devm_kzalloc(dev->dev, - sizeof(u64) * - (TOTAL_SWITCH_COUNTER_NUM + 1), - GFP_KERNEL); - if (!dev->ports[i].mib.counters) - return -ENOMEM; - } + dev->port_mask = (1 << dev->info->port_cnt) - 1; + + /* turn off SPI DO Edge select */ + ret = ksz_read8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, &data8); + if (ret) + return ret; + + data8 &= ~SPI_AUTO_EDGE_DETECTION; + ret = ksz_write8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, data8); + if (ret) + return ret; return 0; } -static void ksz9477_switch_exit(struct ksz_device *dev) +void ksz9477_switch_exit(struct ksz_device *dev) { ksz9477_reset_switch(dev); } -static const struct ksz_dev_ops ksz9477_dev_ops = { - .get_port_addr = ksz9477_get_port_addr, - .cfg_port_member = ksz9477_cfg_port_member, - .flush_dyn_mac_table = ksz9477_flush_dyn_mac_table, - .phy_setup = ksz9477_phy_setup, - .port_setup = ksz9477_port_setup, - .r_mib_cnt = ksz9477_r_mib_cnt, - .r_mib_pkt = ksz9477_r_mib_pkt, - .freeze_mib = ksz9477_freeze_mib, - .port_init_cnt = ksz9477_port_init_cnt, - .shutdown = ksz9477_reset_switch, - .detect = ksz9477_switch_detect, - .init = ksz9477_switch_init, - .exit = ksz9477_switch_exit, -}; - -int ksz9477_switch_register(struct ksz_device *dev) -{ - return ksz_switch_register(dev, &ksz9477_dev_ops); -} -EXPORT_SYMBOL(ksz9477_switch_register); - MODULE_AUTHOR("Woojung Huh <Woojung.Huh@microchip.com>"); MODULE_DESCRIPTION("Microchip KSZ9477 Series Switch DSA Driver"); MODULE_LICENSE("GPL"); |
