| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Microchip KSZ9477 switch driver main logic |
| * |
| * Copyright (C) 2017-2019 Microchip Technology Inc. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/iopoll.h> |
| #include <linux/platform_data/microchip-ksz.h> |
| #include <linux/phy.h> |
| #include <linux/if_bridge.h> |
| #include <linux/if_vlan.h> |
| #include <net/dsa.h> |
| #include <net/switchdev.h> |
| |
| #include "ksz9477_reg.h" |
| #include "ksz_common.h" |
| #include "ksz9477.h" |
| |
| static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set) |
| { |
| regmap_update_bits(ksz_regmap_8(dev), addr, bits, set ? bits : 0); |
| } |
| |
| static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits, |
| bool set) |
| { |
| regmap_update_bits(ksz_regmap_8(dev), PORT_CTRL_ADDR(port, offset), |
| bits, set ? bits : 0); |
| } |
| |
| static void ksz9477_cfg32(struct ksz_device *dev, u32 addr, u32 bits, bool set) |
| { |
| regmap_update_bits(ksz_regmap_32(dev), addr, bits, set ? bits : 0); |
| } |
| |
| static void ksz9477_port_cfg32(struct ksz_device *dev, int port, int offset, |
| u32 bits, bool set) |
| { |
| regmap_update_bits(ksz_regmap_32(dev), PORT_CTRL_ADDR(port, offset), |
| bits, set ? bits : 0); |
| } |
| |
| 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); |
| } |
| |
| /** |
| * ksz9477_handle_wake_reason - Handle wake reason on a specified port. |
| * @dev: The device structure. |
| * @port: The port number. |
| * |
| * This function reads the PME (Power Management Event) status register of a |
| * specified port to determine the wake reason. If there is no wake event, it |
| * returns early. Otherwise, it logs the wake reason which could be due to a |
| * "Magic Packet", "Link Up", or "Energy Detect" event. The PME status register |
| * is then cleared to acknowledge the handling of the wake event. |
| * |
| * Return: 0 on success, or an error code on failure. |
| */ |
| static int ksz9477_handle_wake_reason(struct ksz_device *dev, int port) |
| { |
| u8 pme_status; |
| int ret; |
| |
| ret = ksz_pread8(dev, port, REG_PORT_PME_STATUS, &pme_status); |
| if (ret) |
| return ret; |
| |
| if (!pme_status) |
| return 0; |
| |
| dev_dbg(dev->dev, "Wake event on port %d due to:%s%s%s\n", port, |
| pme_status & PME_WOL_MAGICPKT ? " \"Magic Packet\"" : "", |
| pme_status & PME_WOL_LINKUP ? " \"Link Up\"" : "", |
| pme_status & PME_WOL_ENERGY ? " \"Energy detect\"" : ""); |
| |
| return ksz_pwrite8(dev, port, REG_PORT_PME_STATUS, pme_status); |
| } |
| |
| /** |
| * ksz9477_get_wol - Get Wake-on-LAN settings for a specified port. |
| * @dev: The device structure. |
| * @port: The port number. |
| * @wol: Pointer to ethtool Wake-on-LAN settings structure. |
| * |
| * This function checks the PME Pin Control Register to see if PME Pin Output |
| * Enable is set, indicating PME is enabled. If enabled, it sets the supported |
| * and active WoL flags. |
| */ |
| void ksz9477_get_wol(struct ksz_device *dev, int port, |
| struct ethtool_wolinfo *wol) |
| { |
| u8 pme_ctrl; |
| int ret; |
| |
| if (!dev->wakeup_source) |
| return; |
| |
| wol->supported = WAKE_PHY; |
| |
| /* Check if the current MAC address on this port can be set |
| * as global for WAKE_MAGIC support. The result may vary |
| * dynamically based on other ports configurations. |
| */ |
| if (ksz_is_port_mac_global_usable(dev->ds, port)) |
| wol->supported |= WAKE_MAGIC; |
| |
| ret = ksz_pread8(dev, port, REG_PORT_PME_CTRL, &pme_ctrl); |
| if (ret) |
| return; |
| |
| if (pme_ctrl & PME_WOL_MAGICPKT) |
| wol->wolopts |= WAKE_MAGIC; |
| if (pme_ctrl & (PME_WOL_LINKUP | PME_WOL_ENERGY)) |
| wol->wolopts |= WAKE_PHY; |
| } |
| |
| /** |
| * ksz9477_set_wol - Set Wake-on-LAN settings for a specified port. |
| * @dev: The device structure. |
| * @port: The port number. |
| * @wol: Pointer to ethtool Wake-on-LAN settings structure. |
| * |
| * This function configures Wake-on-LAN (WoL) settings for a specified port. |
| * It validates the provided WoL options, checks if PME is enabled via the |
| * switch's PME Pin Control Register, clears any previous wake reasons, |
| * and sets the Magic Packet flag in the port's PME control register if |
| * specified. |
| * |
| * Return: 0 on success, or other error codes on failure. |
| */ |
| int ksz9477_set_wol(struct ksz_device *dev, int port, |
| struct ethtool_wolinfo *wol) |
| { |
| u8 pme_ctrl = 0, pme_ctrl_old = 0; |
| bool magic_switched_off; |
| bool magic_switched_on; |
| int ret; |
| |
| if (wol->wolopts & ~(WAKE_PHY | WAKE_MAGIC)) |
| return -EINVAL; |
| |
| if (!dev->wakeup_source) |
| return -EOPNOTSUPP; |
| |
| ret = ksz9477_handle_wake_reason(dev, port); |
| if (ret) |
| return ret; |
| |
| if (wol->wolopts & WAKE_MAGIC) |
| pme_ctrl |= PME_WOL_MAGICPKT; |
| if (wol->wolopts & WAKE_PHY) |
| pme_ctrl |= PME_WOL_LINKUP | PME_WOL_ENERGY; |
| |
| ret = ksz_pread8(dev, port, REG_PORT_PME_CTRL, &pme_ctrl_old); |
| if (ret) |
| return ret; |
| |
| if (pme_ctrl_old == pme_ctrl) |
| return 0; |
| |
| magic_switched_off = (pme_ctrl_old & PME_WOL_MAGICPKT) && |
| !(pme_ctrl & PME_WOL_MAGICPKT); |
| magic_switched_on = !(pme_ctrl_old & PME_WOL_MAGICPKT) && |
| (pme_ctrl & PME_WOL_MAGICPKT); |
| |
| /* To keep reference count of MAC address, we should do this |
| * operation only on change of WOL settings. |
| */ |
| if (magic_switched_on) { |
| ret = ksz_switch_macaddr_get(dev->ds, port, NULL); |
| if (ret) |
| return ret; |
| } else if (magic_switched_off) { |
| ksz_switch_macaddr_put(dev->ds); |
| } |
| |
| ret = ksz_pwrite8(dev, port, REG_PORT_PME_CTRL, pme_ctrl); |
| if (ret) { |
| if (magic_switched_on) |
| ksz_switch_macaddr_put(dev->ds); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * ksz9477_wol_pre_shutdown - Prepares the switch device for shutdown while |
| * considering Wake-on-LAN (WoL) settings. |
| * @dev: The switch device structure. |
| * @wol_enabled: Pointer to a boolean which will be set to true if WoL is |
| * enabled on any port. |
| * |
| * This function prepares the switch device for a safe shutdown while taking |
| * into account the Wake-on-LAN (WoL) settings on the user ports. It updates |
| * the wol_enabled flag accordingly to reflect whether WoL is active on any |
| * port. |
| */ |
| void ksz9477_wol_pre_shutdown(struct ksz_device *dev, bool *wol_enabled) |
| { |
| struct dsa_port *dp; |
| int ret; |
| |
| *wol_enabled = false; |
| |
| if (!dev->wakeup_source) |
| return; |
| |
| dsa_switch_for_each_user_port(dp, dev->ds) { |
| u8 pme_ctrl = 0; |
| |
| ret = ksz_pread8(dev, dp->index, REG_PORT_PME_CTRL, &pme_ctrl); |
| if (!ret && pme_ctrl) |
| *wol_enabled = true; |
| |
| /* make sure there are no pending wake events which would |
| * prevent the device from going to sleep/shutdown. |
| */ |
| ksz9477_handle_wake_reason(dev, dp->index); |
| } |
| |
| /* Now we are save to enable PME pin. */ |
| if (*wol_enabled) |
| ksz_write8(dev, REG_SW_PME_CTRL, PME_ENABLE); |
| } |
| |
| static int ksz9477_wait_vlan_ctrl_ready(struct ksz_device *dev) |
| { |
| unsigned int val; |
| |
| return regmap_read_poll_timeout(ksz_regmap_8(dev), REG_SW_VLAN_CTRL, |
| val, !(val & VLAN_START), 10, 1000); |
| } |
| |
| static int ksz9477_get_vlan_table(struct ksz_device *dev, u16 vid, |
| u32 *vlan_table) |
| { |
| int ret; |
| |
| mutex_lock(&dev->vlan_mutex); |
| |
| ksz_write16(dev, REG_SW_VLAN_ENTRY_INDEX__2, vid & VLAN_INDEX_M); |
| ksz_write8(dev, REG_SW_VLAN_CTRL, VLAN_READ | VLAN_START); |
| |
| /* wait to be cleared */ |
| ret = ksz9477_wait_vlan_ctrl_ready(dev); |
| if (ret) { |
| dev_dbg(dev->dev, "Failed to read vlan table\n"); |
| goto exit; |
| } |
| |
| ksz_read32(dev, REG_SW_VLAN_ENTRY__4, &vlan_table[0]); |
| ksz_read32(dev, REG_SW_VLAN_ENTRY_UNTAG__4, &vlan_table[1]); |
| ksz_read32(dev, REG_SW_VLAN_ENTRY_PORTS__4, &vlan_table[2]); |
| |
| ksz_write8(dev, REG_SW_VLAN_CTRL, 0); |
| |
| exit: |
| mutex_unlock(&dev->vlan_mutex); |
| |
| return ret; |
| } |
| |
| static int ksz9477_set_vlan_table(struct ksz_device *dev, u16 vid, |
| u32 *vlan_table) |
| { |
| int ret; |
| |
| mutex_lock(&dev->vlan_mutex); |
| |
| ksz_write32(dev, REG_SW_VLAN_ENTRY__4, vlan_table[0]); |
| ksz_write32(dev, REG_SW_VLAN_ENTRY_UNTAG__4, vlan_table[1]); |
| ksz_write32(dev, REG_SW_VLAN_ENTRY_PORTS__4, vlan_table[2]); |
| |
| ksz_write16(dev, REG_SW_VLAN_ENTRY_INDEX__2, vid & VLAN_INDEX_M); |
| ksz_write8(dev, REG_SW_VLAN_CTRL, VLAN_START | VLAN_WRITE); |
| |
| /* wait to be cleared */ |
| ret = ksz9477_wait_vlan_ctrl_ready(dev); |
| if (ret) { |
| dev_dbg(dev->dev, "Failed to write vlan table\n"); |
| goto exit; |
| } |
| |
| ksz_write8(dev, REG_SW_VLAN_CTRL, 0); |
| |
| /* update vlan cache table */ |
| dev->vlan_cache[vid].table[0] = vlan_table[0]; |
| dev->vlan_cache[vid].table[1] = vlan_table[1]; |
| dev->vlan_cache[vid].table[2] = vlan_table[2]; |
| |
| exit: |
| mutex_unlock(&dev->vlan_mutex); |
| |
| return ret; |
| } |
| |
| static void ksz9477_read_table(struct ksz_device *dev, u32 *table) |
| { |
| ksz_read32(dev, REG_SW_ALU_VAL_A, &table[0]); |
| ksz_read32(dev, REG_SW_ALU_VAL_B, &table[1]); |
| ksz_read32(dev, REG_SW_ALU_VAL_C, &table[2]); |
| ksz_read32(dev, REG_SW_ALU_VAL_D, &table[3]); |
| } |
| |
| static void ksz9477_write_table(struct ksz_device *dev, u32 *table) |
| { |
| ksz_write32(dev, REG_SW_ALU_VAL_A, table[0]); |
| ksz_write32(dev, REG_SW_ALU_VAL_B, table[1]); |
| ksz_write32(dev, REG_SW_ALU_VAL_C, table[2]); |
| ksz_write32(dev, REG_SW_ALU_VAL_D, table[3]); |
| } |
| |
| static int ksz9477_wait_alu_ready(struct ksz_device *dev) |
| { |
| unsigned int val; |
| |
| return regmap_read_poll_timeout(ksz_regmap_32(dev), REG_SW_ALU_CTRL__4, |
| val, !(val & ALU_START), 10, 1000); |
| } |
| |
| static int ksz9477_wait_alu_sta_ready(struct ksz_device *dev) |
| { |
| unsigned int val; |
| |
| return regmap_read_poll_timeout(ksz_regmap_32(dev), |
| REG_SW_ALU_STAT_CTRL__4, |
| val, !(val & ALU_STAT_START), |
| 10, 1000); |
| } |
| |
| int ksz9477_reset_switch(struct ksz_device *dev) |
| { |
| u8 data8; |
| u32 data32; |
| |
| /* reset switch */ |
| ksz_cfg(dev, REG_SW_OPERATION, SW_RESET, true); |
| |
| /* turn off SPI DO Edge select */ |
| regmap_update_bits(ksz_regmap_8(dev), REG_SW_GLOBAL_SERIAL_CTRL_0, |
| SPI_AUTO_EDGE_DETECTION, 0); |
| |
| /* default configuration */ |
| ksz_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); |
| |
| /* disable interrupts */ |
| ksz_write32(dev, REG_SW_INT_MASK__4, SWITCH_INT_MASK); |
| ksz_write32(dev, REG_SW_PORT_INT_MASK__4, 0x7F); |
| ksz_read32(dev, REG_SW_PORT_INT_STATUS__4, &data32); |
| |
| /* KSZ9893 compatible chips do not support refclk configuration */ |
| if (dev->chip_id == KSZ9893_CHIP_ID || |
| dev->chip_id == KSZ8563_CHIP_ID || |
| dev->chip_id == KSZ9563_CHIP_ID) |
| return 0; |
| |
| data8 = SW_ENABLE_REFCLKO; |
| if (dev->synclko_disable) |
| data8 = 0; |
| else if (dev->synclko_125) |
| data8 = SW_ENABLE_REFCLKO | SW_REFCLKO_IS_125MHZ; |
| ksz_write8(dev, REG_SW_GLOBAL_OUTPUT_CTRL__1, data8); |
| |
| return 0; |
| } |
| |
| void ksz9477_r_mib_cnt(struct ksz_device *dev, int port, u16 addr, u64 *cnt) |
| { |
| struct ksz_port *p = &dev->ports[port]; |
| unsigned int val; |
| u32 data; |
| int ret; |
| |
| /* retain the flush/freeze bit */ |
| data = p->freeze ? MIB_COUNTER_FLUSH_FREEZE : 0; |
| data |= MIB_COUNTER_READ; |
| data |= (addr << MIB_COUNTER_INDEX_S); |
| ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, data); |
| |
| ret = regmap_read_poll_timeout(ksz_regmap_32(dev), |
| PORT_CTRL_ADDR(port, REG_PORT_MIB_CTRL_STAT__4), |
| val, !(val & MIB_COUNTER_READ), 10, 1000); |
| /* failed to read MIB. get out of loop */ |
| if (ret) { |
| dev_dbg(dev->dev, "Failed to get MIB\n"); |
| return; |
| } |
| |
| /* count resets upon read */ |
| ksz_pread32(dev, port, REG_PORT_MIB_DATA, &data); |
| *cnt += data; |
| } |
| |
| void ksz9477_r_mib_pkt(struct ksz_device *dev, int port, u16 addr, |
| u64 *dropped, u64 *cnt) |
| { |
| addr = dev->info->mib_names[addr].index; |
| ksz9477_r_mib_cnt(dev, port, addr, cnt); |
| } |
| |
| void ksz9477_freeze_mib(struct ksz_device *dev, int port, bool freeze) |
| { |
| u32 val = freeze ? MIB_COUNTER_FLUSH_FREEZE : 0; |
| struct ksz_port *p = &dev->ports[port]; |
| |
| /* enable/disable the port for flush/freeze function */ |
| mutex_lock(&p->mib.cnt_mutex); |
| ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, val); |
| |
| /* used by MIB counter reading code to know freeze is enabled */ |
| p->freeze = freeze; |
| mutex_unlock(&p->mib.cnt_mutex); |
| } |
| |
| void ksz9477_port_init_cnt(struct ksz_device *dev, int port) |
| { |
| struct ksz_port_mib *mib = &dev->ports[port].mib; |
| |
| /* flush all enabled port MIB counters */ |
| mutex_lock(&mib->cnt_mutex); |
| ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, |
| MIB_COUNTER_FLUSH_FREEZE); |
| ksz_write8(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FLUSH); |
| ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, 0); |
| mutex_unlock(&mib->cnt_mutex); |
| } |
| |
| static void ksz9477_r_phy_quirks(struct ksz_device *dev, u16 addr, u16 reg, |
| u16 *data) |
| { |
| /* KSZ8563R do not have extended registers but BMSR_ESTATEN and |
| * BMSR_ERCAP bits are set. |
| */ |
| if (dev->chip_id == KSZ8563_CHIP_ID && reg == MII_BMSR) |
| *data &= ~(BMSR_ESTATEN | BMSR_ERCAP); |
| } |
| |
| int ksz9477_r_phy(struct ksz_device *dev, u16 addr, u16 reg, u16 *data) |
| { |
| u16 val = 0xffff; |
| int ret; |
| |
| /* No real PHY after this. Simulate the PHY. |
| * A fixed PHY can be setup in the device tree, but this function is |
| * still called for that port during initialization. |
| * For RGMII PHY there is no way to access it so the fixed PHY should |
| * be used. For SGMII PHY the supporting code will be added later. |
| */ |
| if (!dev->info->internal_phy[addr]) { |
| struct ksz_port *p = &dev->ports[addr]; |
| |
| switch (reg) { |
| case MII_BMCR: |
| val = 0x1140; |
| break; |
| case MII_BMSR: |
| val = 0x796d; |
| break; |
| case MII_PHYSID1: |
| val = 0x0022; |
| break; |
| case MII_PHYSID2: |
| val = 0x1631; |
| break; |
| case MII_ADVERTISE: |
| val = 0x05e1; |
| break; |
| case MII_LPA: |
| val = 0xc5e1; |
| break; |
| case MII_CTRL1000: |
| val = 0x0700; |
| break; |
| case MII_STAT1000: |
| if (p->phydev.speed == SPEED_1000) |
| val = 0x3800; |
| else |
| val = 0; |
| break; |
| } |
| } else { |
| ret = ksz_pread16(dev, addr, 0x100 + (reg << 1), &val); |
| if (ret) |
| return ret; |
| |
| ksz9477_r_phy_quirks(dev, addr, reg, &val); |
| } |
| |
| *data = val; |
| |
| return 0; |
| } |
| |
| int ksz9477_w_phy(struct ksz_device *dev, u16 addr, u16 reg, u16 val) |
| { |
| u32 mask, val32; |
| |
| /* No real PHY after this. */ |
| if (!dev->info->internal_phy[addr]) |
| return 0; |
| |
| if (reg < 0x10) |
| return ksz_pwrite16(dev, addr, 0x100 + (reg << 1), val); |
| |
| /* Errata: When using SPI, I2C, or in-band register access, |
| * writes to certain PHY registers should be performed as |
| * 32-bit writes instead of 16-bit writes. |
| */ |
| val32 = val; |
| mask = 0xffff; |
| if ((reg & 1) == 0) { |
| val32 <<= 16; |
| mask <<= 16; |
| } |
| reg &= ~1; |
| return ksz_prmw32(dev, addr, 0x100 + (reg << 1), mask, val32); |
| } |
| |
| void ksz9477_cfg_port_member(struct ksz_device *dev, int port, u8 member) |
| { |
| ksz_pwrite32(dev, port, REG_PORT_VLAN_MEMBERSHIP__4, member); |
| } |
| |
| void ksz9477_flush_dyn_mac_table(struct ksz_device *dev, int port) |
| { |
| const u16 *regs = dev->info->regs; |
| u8 data; |
| |
| regmap_update_bits(ksz_regmap_8(dev), REG_SW_LUE_CTRL_2, |
| SW_FLUSH_OPTION_M << SW_FLUSH_OPTION_S, |
| SW_FLUSH_OPTION_DYN_MAC << SW_FLUSH_OPTION_S); |
| |
| if (port < dev->info->port_cnt) { |
| /* flush individual port */ |
| ksz_pread8(dev, port, regs[P_STP_CTRL], &data); |
| if (!(data & PORT_LEARN_DISABLE)) |
| ksz_pwrite8(dev, port, regs[P_STP_CTRL], |
| data | PORT_LEARN_DISABLE); |
| ksz_cfg(dev, S_FLUSH_TABLE_CTRL, SW_FLUSH_DYN_MAC_TABLE, true); |
| ksz_pwrite8(dev, port, regs[P_STP_CTRL], data); |
| } else { |
| /* flush all */ |
| ksz_cfg(dev, S_FLUSH_TABLE_CTRL, SW_FLUSH_STP_TABLE, true); |
| } |
| } |
| |
| int ksz9477_port_vlan_filtering(struct ksz_device *dev, int port, |
| bool flag, struct netlink_ext_ack *extack) |
| { |
| if (flag) { |
| ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, |
| PORT_VLAN_LOOKUP_VID_0, true); |
| ksz_cfg(dev, REG_SW_LUE_CTRL_0, SW_VLAN_ENABLE, true); |
| } else { |
| ksz_cfg(dev, REG_SW_LUE_CTRL_0, SW_VLAN_ENABLE, false); |
| ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, |
| PORT_VLAN_LOOKUP_VID_0, false); |
| } |
| |
| return 0; |
| } |
| |
| int ksz9477_port_vlan_add(struct ksz_device *dev, int port, |
| const struct switchdev_obj_port_vlan *vlan, |
| struct netlink_ext_ack *extack) |
| { |
| u32 vlan_table[3]; |
| bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; |
| int err; |
| |
| err = ksz9477_get_vlan_table(dev, vlan->vid, vlan_table); |
| if (err) { |
| NL_SET_ERR_MSG_MOD(extack, "Failed to get vlan table"); |
| return err; |
| } |
| |
| vlan_table[0] = VLAN_VALID | (vlan->vid & VLAN_FID_M); |
| if (untagged) |
| vlan_table[1] |= BIT(port); |
| else |
| vlan_table[1] &= ~BIT(port); |
| vlan_table[1] &= ~(BIT(dev->cpu_port)); |
| |
| vlan_table[2] |= BIT(port) | BIT(dev->cpu_port); |
| |
| err = ksz9477_set_vlan_table(dev, vlan->vid, vlan_table); |
| if (err) { |
| NL_SET_ERR_MSG_MOD(extack, "Failed to set vlan table"); |
| return err; |
| } |
| |
| /* change PVID */ |
| if (vlan->flags & BRIDGE_VLAN_INFO_PVID) |
| ksz_pwrite16(dev, port, REG_PORT_DEFAULT_VID, vlan->vid); |
| |
| return 0; |
| } |
| |
| int ksz9477_port_vlan_del(struct ksz_device *dev, int port, |
| const struct switchdev_obj_port_vlan *vlan) |
| { |
| bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; |
| u32 vlan_table[3]; |
| u16 pvid; |
| |
| ksz_pread16(dev, port, REG_PORT_DEFAULT_VID, &pvid); |
| pvid = pvid & 0xFFF; |
| |
| if (ksz9477_get_vlan_table(dev, vlan->vid, vlan_table)) { |
| dev_dbg(dev->dev, "Failed to get vlan table\n"); |
| return -ETIMEDOUT; |
| } |
| |
| vlan_table[2] &= ~BIT(port); |
| |
| if (pvid == vlan->vid) |
| pvid = 1; |
| |
| if (untagged) |
| vlan_table[1] &= ~BIT(port); |
| |
| if (ksz9477_set_vlan_table(dev, vlan->vid, vlan_table)) { |
| dev_dbg(dev->dev, "Failed to set vlan table\n"); |
| return -ETIMEDOUT; |
| } |
| |
| ksz_pwrite16(dev, port, REG_PORT_DEFAULT_VID, pvid); |
| |
| return 0; |
| } |
| |
| int ksz9477_fdb_add(struct ksz_device *dev, int port, |
| const unsigned char *addr, u16 vid, struct dsa_db db) |
| { |
| u32 alu_table[4]; |
| u32 data; |
| int ret = 0; |
| |
| mutex_lock(&dev->alu_mutex); |
| |
| /* find any entry with mac & vid */ |
| data = vid << ALU_FID_INDEX_S; |
| data |= ((addr[0] << 8) | addr[1]); |
| ksz_write32(dev, REG_SW_ALU_INDEX_0, data); |
| |
| data = ((addr[2] << 24) | (addr[3] << 16)); |
| data |= ((addr[4] << 8) | addr[5]); |
| ksz_write32(dev, REG_SW_ALU_INDEX_1, data); |
| |
| /* start read operation */ |
| ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_READ | ALU_START); |
| |
| /* wait to be finished */ |
| ret = ksz9477_wait_alu_ready(dev); |
| if (ret) { |
| dev_dbg(dev->dev, "Failed to read ALU\n"); |
| goto exit; |
| } |
| |
| /* read ALU entry */ |
| ksz9477_read_table(dev, alu_table); |
| |
| /* update ALU entry */ |
| alu_table[0] = ALU_V_STATIC_VALID; |
| alu_table[1] |= BIT(port); |
| if (vid) |
| alu_table[1] |= ALU_V_USE_FID; |
| alu_table[2] = (vid << ALU_V_FID_S); |
| alu_table[2] |= ((addr[0] << 8) | addr[1]); |
| alu_table[3] = ((addr[2] << 24) | (addr[3] << 16)); |
| alu_table[3] |= ((addr[4] << 8) | addr[5]); |
| |
| ksz9477_write_table(dev, alu_table); |
| |
| ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_WRITE | ALU_START); |
| |
| /* wait to be finished */ |
| ret = ksz9477_wait_alu_ready(dev); |
| if (ret) |
| dev_dbg(dev->dev, "Failed to write ALU\n"); |
| |
| exit: |
| mutex_unlock(&dev->alu_mutex); |
| |
| return ret; |
| } |
| |
| int ksz9477_fdb_del(struct ksz_device *dev, int port, |
| const unsigned char *addr, u16 vid, struct dsa_db db) |
| { |
| u32 alu_table[4]; |
| u32 data; |
| int ret = 0; |
| |
| mutex_lock(&dev->alu_mutex); |
| |
| /* read any entry with mac & vid */ |
| data = vid << ALU_FID_INDEX_S; |
| data |= ((addr[0] << 8) | addr[1]); |
| ksz_write32(dev, REG_SW_ALU_INDEX_0, data); |
| |
| data = ((addr[2] << 24) | (addr[3] << 16)); |
| data |= ((addr[4] << 8) | addr[5]); |
| ksz_write32(dev, REG_SW_ALU_INDEX_1, data); |
| |
| /* start read operation */ |
| ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_READ | ALU_START); |
| |
| /* wait to be finished */ |
| ret = ksz9477_wait_alu_ready(dev); |
| if (ret) { |
| dev_dbg(dev->dev, "Failed to read ALU\n"); |
| goto exit; |
| } |
| |
| ksz_read32(dev, REG_SW_ALU_VAL_A, &alu_table[0]); |
| if (alu_table[0] & ALU_V_STATIC_VALID) { |
| ksz_read32(dev, REG_SW_ALU_VAL_B, &alu_table[1]); |
| ksz_read32(dev, REG_SW_ALU_VAL_C, &alu_table[2]); |
| ksz_read32(dev, REG_SW_ALU_VAL_D, &alu_table[3]); |
| |
| /* clear forwarding port */ |
| alu_table[1] &= ~BIT(port); |
| |
| /* if there is no port to forward, clear table */ |
| if ((alu_table[1] & ALU_V_PORT_MAP) == 0) { |
| alu_table[0] = 0; |
| alu_table[1] = 0; |
| alu_table[2] = 0; |
| alu_table[3] = 0; |
| } |
| } else { |
| alu_table[0] = 0; |
| alu_table[1] = 0; |
| alu_table[2] = 0; |
| alu_table[3] = 0; |
| } |
| |
| ksz9477_write_table(dev, alu_table); |
| |
| ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_WRITE | ALU_START); |
| |
| /* wait to be finished */ |
| ret = ksz9477_wait_alu_ready(dev); |
| if (ret) |
| dev_dbg(dev->dev, "Failed to write ALU\n"); |
| |
| exit: |
| mutex_unlock(&dev->alu_mutex); |
| |
| return ret; |
| } |
| |
| static void ksz9477_convert_alu(struct alu_struct *alu, u32 *alu_table) |
| { |
| alu->is_static = !!(alu_table[0] & ALU_V_STATIC_VALID); |
| alu->is_src_filter = !!(alu_table[0] & ALU_V_SRC_FILTER); |
| alu->is_dst_filter = !!(alu_table[0] & ALU_V_DST_FILTER); |
| alu->prio_age = (alu_table[0] >> ALU_V_PRIO_AGE_CNT_S) & |
| ALU_V_PRIO_AGE_CNT_M; |
| alu->mstp = alu_table[0] & ALU_V_MSTP_M; |
| |
| alu->is_override = !!(alu_table[1] & ALU_V_OVERRIDE); |
| alu->is_use_fid = !!(alu_table[1] & ALU_V_USE_FID); |
| alu->port_forward = alu_table[1] & ALU_V_PORT_MAP; |
| |
| alu->fid = (alu_table[2] >> ALU_V_FID_S) & ALU_V_FID_M; |
| |
| alu->mac[0] = (alu_table[2] >> 8) & 0xFF; |
| alu->mac[1] = alu_table[2] & 0xFF; |
| alu->mac[2] = (alu_table[3] >> 24) & 0xFF; |
| alu->mac[3] = (alu_table[3] >> 16) & 0xFF; |
| alu->mac[4] = (alu_table[3] >> 8) & 0xFF; |
| alu->mac[5] = alu_table[3] & 0xFF; |
| } |
| |
| int ksz9477_fdb_dump(struct ksz_device *dev, int port, |
| dsa_fdb_dump_cb_t *cb, void *data) |
| { |
| int ret = 0; |
| u32 ksz_data; |
| u32 alu_table[4]; |
| struct alu_struct alu; |
| int timeout; |
| |
| mutex_lock(&dev->alu_mutex); |
| |
| /* start ALU search */ |
| ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_START | ALU_SEARCH); |
| |
| do { |
| timeout = 1000; |
| do { |
| ksz_read32(dev, REG_SW_ALU_CTRL__4, &ksz_data); |
| if ((ksz_data & ALU_VALID) || !(ksz_data & ALU_START)) |
| break; |
| usleep_range(1, 10); |
| } while (timeout-- > 0); |
| |
| if (!timeout) { |
| dev_dbg(dev->dev, "Failed to search ALU\n"); |
| ret = -ETIMEDOUT; |
| goto exit; |
| } |
| |
| if (!(ksz_data & ALU_VALID)) |
| continue; |
| |
| /* read ALU table */ |
| ksz9477_read_table(dev, alu_table); |
| |
| ksz9477_convert_alu(&alu, alu_table); |
| |
| if (alu.port_forward & BIT(port)) { |
| ret = cb(alu.mac, alu.fid, alu.is_static, data); |
| if (ret) |
| goto exit; |
| } |
| } while (ksz_data & ALU_START); |
| |
| exit: |
| |
| /* stop ALU search */ |
| ksz_write32(dev, REG_SW_ALU_CTRL__4, 0); |
| |
| mutex_unlock(&dev->alu_mutex); |
| |
| return ret; |
| } |
| |
| int ksz9477_mdb_add(struct ksz_device *dev, int port, |
| const struct switchdev_obj_port_mdb *mdb, struct dsa_db db) |
| { |
| u32 static_table[4]; |
| const u8 *shifts; |
| const u32 *masks; |
| u32 data; |
| int index; |
| u32 mac_hi, mac_lo; |
| int err = 0; |
| |
| shifts = dev->info->shifts; |
| masks = dev->info->masks; |
| |
| mac_hi = ((mdb->addr[0] << 8) | mdb->addr[1]); |
| mac_lo = ((mdb->addr[2] << 24) | (mdb->addr[3] << 16)); |
| mac_lo |= ((mdb->addr[4] << 8) | mdb->addr[5]); |
| |
| mutex_lock(&dev->alu_mutex); |
| |
| for (index = 0; index < dev->info->num_statics; index++) { |
| /* find empty slot first */ |
| data = (index << shifts[ALU_STAT_INDEX]) | |
| masks[ALU_STAT_READ] | ALU_STAT_START; |
| ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); |
| |
| /* wait to be finished */ |
| err = ksz9477_wait_alu_sta_ready(dev); |
| if (err) { |
| dev_dbg(dev->dev, "Failed to read ALU STATIC\n"); |
| goto exit; |
| } |
| |
| /* read ALU static table */ |
| ksz9477_read_table(dev, static_table); |
| |
| if (static_table[0] & ALU_V_STATIC_VALID) { |
| /* check this has same vid & mac address */ |
| if (((static_table[2] >> ALU_V_FID_S) == mdb->vid) && |
| ((static_table[2] & ALU_V_MAC_ADDR_HI) == mac_hi) && |
| static_table[3] == mac_lo) { |
| /* found matching one */ |
| break; |
| } |
| } else { |
| /* found empty one */ |
| break; |
| } |
| } |
| |
| /* no available entry */ |
| if (index == dev->info->num_statics) { |
| err = -ENOSPC; |
| goto exit; |
| } |
| |
| /* add entry */ |
| static_table[0] = ALU_V_STATIC_VALID; |
| static_table[1] |= BIT(port); |
| if (mdb->vid) |
| static_table[1] |= ALU_V_USE_FID; |
| static_table[2] = (mdb->vid << ALU_V_FID_S); |
| static_table[2] |= mac_hi; |
| static_table[3] = mac_lo; |
| |
| ksz9477_write_table(dev, static_table); |
| |
| data = (index << shifts[ALU_STAT_INDEX]) | ALU_STAT_START; |
| ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); |
| |
| /* wait to be finished */ |
| if (ksz9477_wait_alu_sta_ready(dev)) |
| dev_dbg(dev->dev, "Failed to read ALU STATIC\n"); |
| |
| exit: |
| mutex_unlock(&dev->alu_mutex); |
| return err; |
| } |
| |
| int ksz9477_mdb_del(struct ksz_device *dev, int port, |
| const struct switchdev_obj_port_mdb *mdb, struct dsa_db db) |
| { |
| 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->info->num_statics; index++) { |
| /* find empty slot first */ |
| data = (index << shifts[ALU_STAT_INDEX]) | |
| masks[ALU_STAT_READ] | ALU_STAT_START; |
| ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); |
| |
| /* wait to be finished */ |
| ret = ksz9477_wait_alu_sta_ready(dev); |
| if (ret) { |
| dev_dbg(dev->dev, "Failed to read ALU STATIC\n"); |
| goto exit; |
| } |
| |
| /* read ALU static table */ |
| ksz9477_read_table(dev, static_table); |
| |
| if (static_table[0] & ALU_V_STATIC_VALID) { |
| /* check this has same vid & mac address */ |
| |
| if (((static_table[2] >> ALU_V_FID_S) == mdb->vid) && |
| ((static_table[2] & ALU_V_MAC_ADDR_HI) == mac_hi) && |
| static_table[3] == mac_lo) { |
| /* found matching one */ |
| break; |
| } |
| } |
| } |
| |
| /* no available entry */ |
| if (index == dev->info->num_statics) |
| goto exit; |
| |
| /* clear port */ |
| static_table[1] &= ~BIT(port); |
| |
| if ((static_table[1] & ALU_V_PORT_MAP) == 0) { |
| /* delete entry */ |
| static_table[0] = 0; |
| static_table[1] = 0; |
| static_table[2] = 0; |
| static_table[3] = 0; |
| } |
| |
| ksz9477_write_table(dev, static_table); |
| |
| data = (index << shifts[ALU_STAT_INDEX]) | ALU_STAT_START; |
| ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); |
| |
| /* wait to be finished */ |
| ret = ksz9477_wait_alu_sta_ready(dev); |
| if (ret) |
| dev_dbg(dev->dev, "Failed to read ALU STATIC\n"); |
| |
| exit: |
| mutex_unlock(&dev->alu_mutex); |
| |
| return ret; |
| } |
| |
| int ksz9477_port_mirror_add(struct ksz_device *dev, int port, |
| struct dsa_mall_mirror_tc_entry *mirror, |
| bool ingress, struct netlink_ext_ack *extack) |
| { |
| u8 data; |
| int p; |
| |
| /* Limit to one sniffer port |
| * Check if any of the port is already set for sniffing |
| * If yes, instruct the user to remove the previous entry & exit |
| */ |
| for (p = 0; p < dev->info->port_cnt; p++) { |
| /* Skip the current sniffing port */ |
| if (p == mirror->to_local_port) |
| continue; |
| |
| ksz_pread8(dev, p, P_MIRROR_CTRL, &data); |
| |
| if (data & PORT_MIRROR_SNIFFER) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Sniffer port is already configured, delete existing rules & retry"); |
| return -EBUSY; |
| } |
| } |
| |
| if (ingress) |
| ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, true); |
| else |
| ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, true); |
| |
| /* configure mirror port */ |
| ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL, |
| PORT_MIRROR_SNIFFER, true); |
| |
| ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false); |
| |
| return 0; |
| } |
| |
| void ksz9477_port_mirror_del(struct ksz_device *dev, int port, |
| struct dsa_mall_mirror_tc_entry *mirror) |
| { |
| bool in_use = false; |
| u8 data; |
| int p; |
| |
| if (mirror->ingress) |
| ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, false); |
| else |
| ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, false); |
| |
| |
| /* Check if any of the port is still referring to sniffer port */ |
| for (p = 0; p < dev->info->port_cnt; p++) { |
| ksz_pread8(dev, p, P_MIRROR_CTRL, &data); |
| |
| if ((data & (PORT_MIRROR_RX | PORT_MIRROR_TX))) { |
| in_use = true; |
| break; |
| } |
| } |
| |
| /* delete sniffing if there are no other mirroring rules */ |
| if (!in_use) |
| ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL, |
| PORT_MIRROR_SNIFFER, false); |
| } |
| |
| static phy_interface_t ksz9477_get_interface(struct ksz_device *dev, int port) |
| { |
| phy_interface_t interface; |
| bool gbit; |
| |
| if (dev->info->internal_phy[port]) |
| return PHY_INTERFACE_MODE_NA; |
| |
| gbit = ksz_get_gbit(dev, port); |
| |
| interface = ksz_get_xmii(dev, port, gbit); |
| |
| return interface; |
| } |
| |
| void ksz9477_get_caps(struct ksz_device *dev, int port, |
| struct phylink_config *config) |
| { |
| config->mac_capabilities = MAC_10 | MAC_100 | MAC_ASYM_PAUSE | |
| MAC_SYM_PAUSE; |
| |
| if (dev->info->gbit_capable[port]) |
| config->mac_capabilities |= MAC_1000FD; |
| } |
| |
| int ksz9477_set_ageing_time(struct ksz_device *dev, unsigned int msecs) |
| { |
| u32 secs = msecs / 1000; |
| u8 value; |
| u8 data; |
| int ret; |
| |
| value = FIELD_GET(SW_AGE_PERIOD_7_0_M, secs); |
| |
| ret = ksz_write8(dev, REG_SW_LUE_CTRL_3, value); |
| if (ret < 0) |
| return ret; |
| |
| data = FIELD_GET(SW_AGE_PERIOD_10_8_M, secs); |
| |
| ret = ksz_read8(dev, REG_SW_LUE_CTRL_0, &value); |
| if (ret < 0) |
| return ret; |
| |
| value &= ~SW_AGE_CNT_M; |
| value |= FIELD_PREP(SW_AGE_CNT_M, data); |
| |
| return ksz_write8(dev, REG_SW_LUE_CTRL_0, value); |
| } |
| |
| void ksz9477_port_queue_split(struct ksz_device *dev, int port) |
| { |
| u8 data; |
| |
| if (dev->info->num_tx_queues == 8) |
| data = PORT_EIGHT_QUEUE; |
| else if (dev->info->num_tx_queues == 4) |
| data = PORT_FOUR_QUEUE; |
| else if (dev->info->num_tx_queues == 2) |
| data = PORT_TWO_QUEUE; |
| else |
| data = PORT_SINGLE_QUEUE; |
| |
| ksz_prmw8(dev, port, REG_PORT_CTRL_0, PORT_QUEUE_SPLIT_MASK, data); |
| } |
| |
| void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port) |
| { |
| struct dsa_switch *ds = dev->ds; |
| u16 data16; |
| u8 member; |
| |
| /* enable tag tail for host port */ |
| if (cpu_port) |
| ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_TAIL_TAG_ENABLE, |
| true); |
| |
| ksz9477_port_queue_split(dev, port); |
| |
| ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_MAC_LOOPBACK, false); |
| |
| /* set back pressure */ |
| ksz_port_cfg(dev, port, REG_PORT_MAC_CTRL_1, PORT_BACK_PRESSURE, true); |
| |
| /* enable broadcast storm limit */ |
| ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true); |
| |
| /* 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); |
| |
| /* force flow control for non-PHY ports only */ |
| ksz_port_cfg(dev, port, REG_PORT_CTRL_0, |
| PORT_FORCE_TX_FLOW_CTRL | PORT_FORCE_RX_FLOW_CTRL, |
| !dev->info->internal_phy[port]); |
| |
| if (cpu_port) |
| member = dsa_user_ports(ds); |
| else |
| member = BIT(dsa_upstream_port(ds, port)); |
| |
| ksz9477_cfg_port_member(dev, port, member); |
| |
| /* clear pending interrupts */ |
| if (dev->info->internal_phy[port]) |
| ksz_pread16(dev, port, REG_PORT_PHY_INT_ENABLE, &data16); |
| |
| ksz9477_port_acl_init(dev, port); |
| |
| /* clear pending wake flags */ |
| ksz9477_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, REG_PORT_PME_CTRL, 0); |
| } |
| |
| void ksz9477_config_cpu_port(struct dsa_switch *ds) |
| { |
| struct ksz_device *dev = ds->priv; |
| struct ksz_port *p; |
| int i; |
| |
| for (i = 0; i < dev->info->port_cnt; i++) { |
| if (dsa_is_cpu_port(ds, i) && |
| (dev->info->cpu_ports & (1 << i))) { |
| phy_interface_t interface; |
| const char *prev_msg; |
| const char *prev_mode; |
| |
| dev->cpu_port = i; |
| p = &dev->ports[i]; |
| |
| /* Read from XMII register to determine host port |
| * interface. If set specifically in device tree |
| * note the difference to help debugging. |
| */ |
| interface = ksz9477_get_interface(dev, i); |
| if (!p->interface) { |
| if (dev->compat_interface) { |
| dev_warn(dev->dev, |
| "Using legacy switch \"phy-mode\" property, because it is missing on port %d node. " |
| "Please update your device tree.\n", |
| i); |
| p->interface = dev->compat_interface; |
| } else { |
| p->interface = interface; |
| } |
| } |
| if (interface && interface != p->interface) { |
| prev_msg = " instead of "; |
| prev_mode = phy_modes(interface); |
| } else { |
| prev_msg = ""; |
| prev_mode = ""; |
| } |
| dev_info(dev->dev, |
| "Port%d: using phy mode %s%s%s\n", |
| i, |
| phy_modes(p->interface), |
| prev_msg, |
| prev_mode); |
| |
| /* enable cpu port */ |
| ksz9477_port_setup(dev, i, true); |
| } |
| } |
| |
| for (i = 0; i < dev->info->port_cnt; i++) { |
| if (i == dev->cpu_port) |
| continue; |
| ksz_port_stp_state_set(ds, i, BR_STATE_DISABLED); |
| } |
| } |
| |
| int ksz9477_enable_stp_addr(struct ksz_device *dev) |
| { |
| const u32 *masks; |
| 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); |
| |
| /* Set the Override bit for forwarding BPDU packet to CPU */ |
| ret = ksz_write32(dev, REG_SW_ALU_VAL_B, |
| ALU_V_OVERRIDE | BIT(dev->cpu_port)); |
| if (ret < 0) |
| return ret; |
| |
| data = ALU_STAT_START | 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) { |
| dev_err(dev->dev, "Failed to update Reserved Multicast table\n"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| int ksz9477_setup(struct dsa_switch *ds) |
| { |
| struct ksz_device *dev = ds->priv; |
| int ret = 0; |
| |
| ds->mtu_enforcement_ingress = true; |
| |
| /* Required for port partitioning. */ |
| ksz9477_cfg32(dev, REG_SW_QM_CTRL__4, UNICAST_VLAN_BOUNDARY, |
| true); |
| |
| /* Do not work correctly with tail tagging. */ |
| ksz_cfg(dev, REG_SW_MAC_CTRL_0, SW_CHECK_LENGTH, false); |
| |
| /* Enable REG_SW_MTU__2 reg by setting SW_JUMBO_PACKET */ |
| ksz_cfg(dev, REG_SW_MAC_CTRL_1, SW_JUMBO_PACKET, true); |
| |
| /* Now we can configure default MTU value */ |
| ret = regmap_update_bits(ksz_regmap_16(dev), REG_SW_MTU__2, REG_SW_MTU_MASK, |
| VLAN_ETH_FRAME_LEN + ETH_FCS_LEN); |
| if (ret) |
| return ret; |
| |
| /* queue based egress rate limit */ |
| ksz_cfg(dev, REG_SW_MAC_CTRL_5, SW_OUT_RATE_LIMIT_QUEUE_BASED, true); |
| |
| /* enable global MIB counter freeze function */ |
| ksz_cfg(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FREEZE, true); |
| |
| /* Make sure PME (WoL) is not enabled. If requested, it will be |
| * enabled by ksz9477_wol_pre_shutdown(). Otherwise, some PMICs do not |
| * like PME events changes before shutdown. |
| */ |
| ksz_write8(dev, REG_SW_PME_CTRL, 0); |
| |
| return 0; |
| } |
| |
| u32 ksz9477_get_port_addr(int port, int offset) |
| { |
| return PORT_CTRL_ADDR(port, offset); |
| } |
| |
| int ksz9477_tc_cbs_set_cinc(struct ksz_device *dev, int port, u32 val) |
| { |
| val = val >> 8; |
| |
| return ksz_pwrite16(dev, port, REG_PORT_MTI_CREDIT_INCREMENT, val); |
| } |
| |
| /* The KSZ9477 provides following HW features to accelerate |
| * HSR frames handling: |
| * |
| * 1. TX PACKET DUPLICATION FROM HOST TO SWITCH |
| * 2. RX PACKET DUPLICATION DISCARDING |
| * 3. PREVENTING PACKET LOOP IN THE RING BY SELF-ADDRESS FILTERING |
| * |
| * Only one from point 1. has the NETIF_F* flag available. |
| * |
| * Ones from point 2 and 3 are "best effort" - i.e. those will |
| * work correctly most of the time, but it may happen that some |
| * frames will not be caught - to be more specific; there is a race |
| * condition in hardware such that, when duplicate packets are received |
| * on member ports very close in time to each other, the hardware fails |
| * to detect that they are duplicates. |
| * |
| * Hence, the SW needs to handle those special cases. However, the speed |
| * up gain is considerable when above features are used. |
| * |
| * Moreover, the NETIF_F_HW_HSR_FWD feature is also enabled, as HSR frames |
| * can be forwarded in the switch fabric between HSR ports. |
| */ |
| #define KSZ9477_SUPPORTED_HSR_FEATURES (NETIF_F_HW_HSR_DUP | NETIF_F_HW_HSR_FWD) |
| |
| 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; |
| |
| /* Program which port(s) shall support HSR */ |
| ksz_rmw32(dev, REG_HSR_PORT_MAP__4, BIT(port), BIT(port)); |
| |
| /* Forward frames between HSR ports (i.e. bridge together HSR ports) */ |
| if (dev->hsr_ports) { |
| dsa_hsr_foreach_port(hsr_dp, ds, hsr) |
| hsr_ports |= BIT(hsr_dp->index); |
| |
| hsr_ports |= BIT(dsa_upstream_port(ds, port)); |
| dsa_hsr_foreach_port(hsr_dp, ds, hsr) |
| ksz9477_cfg_port_member(dev, hsr_dp->index, hsr_ports); |
| } |
| |
| if (!dev->hsr_ports) { |
| /* Enable discarding of received HSR frames */ |
| ksz_read8(dev, REG_HSR_ALU_CTRL_0__1, &data); |
| data |= HSR_DUPLICATE_DISCARD; |
| data &= ~HSR_NODE_UNICAST; |
| ksz_write8(dev, REG_HSR_ALU_CTRL_0__1, data); |
| } |
| |
| /* Enable per port self-address filtering. |
| * The global self-address filtering has already been enabled in the |
| * ksz9477_reset_switch() function. |
| */ |
| ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_SRC_ADDR_FILTER, true); |
| |
| /* Setup HW supported features for lan HSR ports */ |
| user = dsa_to_port(ds, port)->user; |
| user->features |= KSZ9477_SUPPORTED_HSR_FEATURES; |
| } |
| |
| void ksz9477_hsr_leave(struct dsa_switch *ds, int port, struct net_device *hsr) |
| { |
| struct ksz_device *dev = ds->priv; |
| |
| /* Clear port HSR support */ |
| ksz_rmw32(dev, REG_HSR_PORT_MAP__4, BIT(port), 0); |
| |
| /* Disable forwarding frames between HSR ports */ |
| ksz9477_cfg_port_member(dev, port, BIT(dsa_upstream_port(ds, port))); |
| |
| /* Disable per port self-address filtering */ |
| ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_SRC_ADDR_FILTER, false); |
| } |
| |
| int ksz9477_switch_init(struct ksz_device *dev) |
| { |
| u8 data8; |
| int ret; |
| |
| dev->port_mask = (1 << dev->info->port_cnt) - 1; |
| |
| /* turn off SPI DO Edge select */ |
| ret = ksz_read8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, &data8); |
| if (ret) |
| return ret; |
| |
| data8 &= ~SPI_AUTO_EDGE_DETECTION; |
| ret = ksz_write8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, data8); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| void ksz9477_switch_exit(struct ksz_device *dev) |
| { |
| ksz9477_reset_switch(dev); |
| } |
| |
| MODULE_AUTHOR("Woojung Huh <Woojung.Huh@microchip.com>"); |
| MODULE_DESCRIPTION("Microchip KSZ9477 Series Switch DSA Driver"); |
| MODULE_LICENSE("GPL"); |