diff options
Diffstat (limited to 'drivers/net/dsa/microchip')
-rw-r--r-- | drivers/net/dsa/microchip/ksz8795.c | 86 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz8795_reg.h | 7 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz9477.c | 272 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz9477.h | 7 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz9477_acl.c | 8 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz9477_i2c.c | 5 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz9477_reg.h | 7 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz_common.c | 273 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz_common.h | 21 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz_ptp.c | 2 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz_spi.c | 5 |
11 files changed, 655 insertions, 38 deletions
diff --git a/drivers/net/dsa/microchip/ksz8795.c b/drivers/net/dsa/microchip/ksz8795.c index 91aba470fb2f..4bf4d67557dc 100644 --- a/drivers/net/dsa/microchip/ksz8795.c +++ b/drivers/net/dsa/microchip/ksz8795.c @@ -632,6 +632,50 @@ static void ksz8_w_vlan_table(struct ksz_device *dev, u16 vid, u16 vlan) ksz8_w_table(dev, TABLE_VLAN, addr, buf); } +/** + * ksz8_r_phy_ctrl - Translates and reads from the SMI interface to a MIIM PHY + * Control register (Reg. 31). + * @dev: The KSZ device instance. + * @port: The port number to be read. + * @val: The value read from the SMI interface. + * + * This function reads the SMI interface and translates the hardware register + * bit values into their corresponding control settings for a MIIM PHY Control + * register. + * + * Return: 0 on success, error code on failure. + */ +static int ksz8_r_phy_ctrl(struct ksz_device *dev, int port, u16 *val) +{ + const u16 *regs = dev->info->regs; + u8 reg_val; + int ret; + + *val = 0; + + ret = ksz_pread8(dev, port, regs[P_LINK_STATUS], ®_val); + if (ret < 0) + return ret; + + if (reg_val & PORT_MDIX_STATUS) + *val |= KSZ886X_CTRL_MDIX_STAT; + + ret = ksz_pread8(dev, port, REG_PORT_LINK_MD_CTRL, ®_val); + if (ret < 0) + return ret; + + if (reg_val & PORT_FORCE_LINK) + *val |= KSZ886X_CTRL_FORCE_LINK; + + if (reg_val & PORT_POWER_SAVING) + *val |= KSZ886X_CTRL_PWRSAVE; + + if (reg_val & PORT_PHY_REMOTE_LOOPBACK) + *val |= KSZ886X_CTRL_REMOTE_LOOPBACK; + + return 0; +} + int ksz8_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val) { u8 restart, speed, ctrl, link; @@ -769,12 +813,10 @@ int ksz8_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val) FIELD_GET(PORT_CABLE_FAULT_COUNTER_L, val2)); break; case PHY_REG_PHY_CTRL: - ret = ksz_pread8(dev, p, regs[P_LINK_STATUS], &link); + ret = ksz8_r_phy_ctrl(dev, p, &data); if (ret) return ret; - if (link & PORT_MDIX_STATUS) - data |= KSZ886X_CTRL_MDIX_STAT; break; default: processed = false; @@ -786,6 +828,38 @@ int ksz8_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val) return 0; } +/** + * ksz8_w_phy_ctrl - Translates and writes to the SMI interface from a MIIM PHY + * Control register (Reg. 31). + * @dev: The KSZ device instance. + * @port: The port number to be configured. + * @val: The register value to be written. + * + * This function translates control settings from a MIIM PHY Control register + * into their corresponding hardware register bit values for the SMI + * interface. + * + * Return: 0 on success, error code on failure. + */ +static int ksz8_w_phy_ctrl(struct ksz_device *dev, int port, u16 val) +{ + u8 reg_val = 0; + int ret; + + if (val & KSZ886X_CTRL_FORCE_LINK) + reg_val |= PORT_FORCE_LINK; + + if (val & KSZ886X_CTRL_PWRSAVE) + reg_val |= PORT_POWER_SAVING; + + if (val & KSZ886X_CTRL_REMOTE_LOOPBACK) + reg_val |= PORT_PHY_REMOTE_LOOPBACK; + + ret = ksz_prmw8(dev, port, REG_PORT_LINK_MD_CTRL, PORT_FORCE_LINK | + PORT_POWER_SAVING | PORT_PHY_REMOTE_LOOPBACK, reg_val); + return ret; +} + int ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val) { u8 restart, speed, ctrl, data; @@ -926,6 +1000,12 @@ int ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val) if (val & PHY_START_CABLE_DIAG) ksz_port_cfg(dev, p, REG_PORT_LINK_MD_CTRL, PORT_START_CABLE_DIAG, true); break; + + case PHY_REG_PHY_CTRL: + ret = ksz8_w_phy_ctrl(dev, p, val); + if (ret) + return ret; + break; default: break; } diff --git a/drivers/net/dsa/microchip/ksz8795_reg.h b/drivers/net/dsa/microchip/ksz8795_reg.h index d33db4f86c64..3c9dae53e4d8 100644 --- a/drivers/net/dsa/microchip/ksz8795_reg.h +++ b/drivers/net/dsa/microchip/ksz8795_reg.h @@ -323,13 +323,6 @@ ((addr) + REG_PORT_1_CTRL_0 + (port) * \ (REG_PORT_2_CTRL_0 - REG_PORT_1_CTRL_0)) -#define REG_SW_MAC_ADDR_0 0x68 -#define REG_SW_MAC_ADDR_1 0x69 -#define REG_SW_MAC_ADDR_2 0x6A -#define REG_SW_MAC_ADDR_3 0x6B -#define REG_SW_MAC_ADDR_4 0x6C -#define REG_SW_MAC_ADDR_5 0x6D - #define TABLE_EXT_SELECT_S 5 #define TABLE_EEE_V 1 #define TABLE_ACL_V 2 diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c index 9e63ed820c92..7f745628c84d 100644 --- a/drivers/net/dsa/microchip/ksz9477.c +++ b/drivers/net/dsa/microchip/ksz9477.c @@ -56,6 +56,187 @@ int ksz9477_change_mtu(struct ksz_device *dev, int port, int mtu) 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; @@ -1006,6 +1187,14 @@ void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_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) @@ -1128,6 +1317,12 @@ 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); + /* 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; } @@ -1143,6 +1338,83 @@ int ksz9477_tc_cbs_set_cinc(struct ksz_device *dev, int port, u32 val) 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; diff --git a/drivers/net/dsa/microchip/ksz9477.h b/drivers/net/dsa/microchip/ksz9477.h index d7bbd9bd8893..ce1e656b800b 100644 --- a/drivers/net/dsa/microchip/ksz9477.h +++ b/drivers/net/dsa/microchip/ksz9477.h @@ -56,6 +56,13 @@ int ksz9477_reset_switch(struct ksz_device *dev); int ksz9477_switch_init(struct ksz_device *dev); void ksz9477_switch_exit(struct ksz_device *dev); void ksz9477_port_queue_split(struct ksz_device *dev, int port); +void ksz9477_hsr_join(struct dsa_switch *ds, int port, struct net_device *hsr); +void ksz9477_hsr_leave(struct dsa_switch *ds, int port, struct net_device *hsr); +void ksz9477_get_wol(struct ksz_device *dev, int port, + struct ethtool_wolinfo *wol); +int ksz9477_set_wol(struct ksz_device *dev, int port, + struct ethtool_wolinfo *wol); +void ksz9477_wol_pre_shutdown(struct ksz_device *dev, bool *wol_enabled); int ksz9477_port_acl_init(struct ksz_device *dev, int port); void ksz9477_port_acl_free(struct ksz_device *dev, int port); diff --git a/drivers/net/dsa/microchip/ksz9477_acl.c b/drivers/net/dsa/microchip/ksz9477_acl.c index 06d74c19eb94..7ba778df63ac 100644 --- a/drivers/net/dsa/microchip/ksz9477_acl.c +++ b/drivers/net/dsa/microchip/ksz9477_acl.c @@ -420,10 +420,6 @@ static int ksz9477_validate_and_get_src_count(struct ksz_device *dev, int port, return -EINVAL; } - /* Nothing to do */ - if (src_idx == dst_idx) - return 0; - /* Validate if the source entries are contiguous */ ret = ksz9477_acl_get_cont_entr(dev, port, src_idx); if (ret < 0) @@ -556,6 +552,10 @@ static int ksz9477_acl_move_entries(struct ksz_device *dev, int port, struct ksz9477_acl_entries *acles = &acl->acles; int src_count, ret, dst_count; + /* Nothing to do */ + if (src_idx == dst_idx) + return 0; + ret = ksz9477_validate_and_get_src_count(dev, port, src_idx, dst_idx, &src_count, &dst_count); if (ret) diff --git a/drivers/net/dsa/microchip/ksz9477_i2c.c b/drivers/net/dsa/microchip/ksz9477_i2c.c index 2710afad4f3a..cac4a607e54a 100644 --- a/drivers/net/dsa/microchip/ksz9477_i2c.c +++ b/drivers/net/dsa/microchip/ksz9477_i2c.c @@ -66,10 +66,7 @@ static void ksz9477_i2c_shutdown(struct i2c_client *i2c) if (!dev) return; - if (dev->dev_ops->reset) - dev->dev_ops->reset(dev); - - dsa_switch_shutdown(dev->ds); + ksz_switch_shutdown(dev); i2c_set_clientdata(i2c, NULL); } diff --git a/drivers/net/dsa/microchip/ksz9477_reg.h b/drivers/net/dsa/microchip/ksz9477_reg.h index 504e085aab52..f3a205ee483f 100644 --- a/drivers/net/dsa/microchip/ksz9477_reg.h +++ b/drivers/net/dsa/microchip/ksz9477_reg.h @@ -153,13 +153,6 @@ #define SW_DOUBLE_TAG BIT(7) #define SW_RESET BIT(1) -#define REG_SW_MAC_ADDR_0 0x0302 -#define REG_SW_MAC_ADDR_1 0x0303 -#define REG_SW_MAC_ADDR_2 0x0304 -#define REG_SW_MAC_ADDR_3 0x0305 -#define REG_SW_MAC_ADDR_4 0x0306 -#define REG_SW_MAC_ADDR_5 0x0307 - #define REG_SW_MTU__2 0x0308 #define REG_SW_MTU_MASK GENMASK(13, 0) diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index 173ad8f04671..3fed406fb46a 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -16,6 +16,7 @@ #include <linux/etherdevice.h> #include <linux/if_bridge.h> #include <linux/if_vlan.h> +#include <linux/if_hsr.h> #include <linux/irq.h> #include <linux/irqdomain.h> #include <linux/of.h> @@ -318,6 +319,9 @@ static const struct ksz_dev_ops ksz9477_dev_ops = { .mdb_del = ksz9477_mdb_del, .change_mtu = ksz9477_change_mtu, .phylink_mac_link_up = ksz9477_phylink_mac_link_up, + .get_wol = ksz9477_get_wol, + .set_wol = ksz9477_set_wol, + .wol_pre_shutdown = ksz9477_wol_pre_shutdown, .config_cpu_port = ksz9477_config_cpu_port, .tc_cbs_set_cinc = ksz9477_tc_cbs_set_cinc, .enable_stp_addr = ksz9477_enable_stp_addr, @@ -364,6 +368,7 @@ static const struct ksz_dev_ops lan937x_dev_ops = { }; static const u16 ksz8795_regs[] = { + [REG_SW_MAC_ADDR] = 0x68, [REG_IND_CTRL_0] = 0x6E, [REG_IND_DATA_8] = 0x70, [REG_IND_DATA_CHECK] = 0x72, @@ -439,6 +444,7 @@ static const u8 ksz8795_shifts[] = { }; static const u16 ksz8863_regs[] = { + [REG_SW_MAC_ADDR] = 0x70, [REG_IND_CTRL_0] = 0x79, [REG_IND_DATA_8] = 0x7B, [REG_IND_DATA_CHECK] = 0x7B, @@ -492,6 +498,7 @@ static u8 ksz8863_shifts[] = { }; static const u16 ksz9477_regs[] = { + [REG_SW_MAC_ADDR] = 0x0302, [P_STP_CTRL] = 0x0B04, [S_START_CTRL] = 0x0300, [S_BROADCAST_CTRL] = 0x0332, @@ -1942,14 +1949,14 @@ static int ksz_irq_phy_setup(struct ksz_device *dev) ret = irq; goto out; } - ds->slave_mii_bus->irq[phy] = irq; + ds->user_mii_bus->irq[phy] = irq; } } return 0; out: while (phy--) if (BIT(phy) & ds->phys_mii_mask) - irq_dispose_mapping(ds->slave_mii_bus->irq[phy]); + irq_dispose_mapping(ds->user_mii_bus->irq[phy]); return ret; } @@ -1961,7 +1968,7 @@ static void ksz_irq_phy_free(struct ksz_device *dev) for (phy = 0; phy < KSZ_MAX_NUM_PORTS; phy++) if (BIT(phy) & ds->phys_mii_mask) - irq_dispose_mapping(ds->slave_mii_bus->irq[phy]); + irq_dispose_mapping(ds->user_mii_bus->irq[phy]); } static int ksz_mdio_register(struct ksz_device *dev) @@ -1984,12 +1991,12 @@ static int ksz_mdio_register(struct ksz_device *dev) bus->priv = dev; bus->read = ksz_sw_mdio_read; bus->write = ksz_sw_mdio_write; - bus->name = "ksz slave smi"; + bus->name = "ksz user smi"; snprintf(bus->id, MII_BUS_ID_SIZE, "SMI-%d", ds->index); bus->parent = ds->dev; bus->phy_mask = ~ds->phys_mii_mask; - ds->slave_mii_bus = bus; + ds->user_mii_bus = bus; if (dev->irq > 0) { ret = ksz_irq_phy_setup(dev); @@ -2341,7 +2348,7 @@ static void ksz_mib_read_work(struct work_struct *work) if (!p->read) { const struct dsa_port *dp = dsa_to_port(dev->ds, i); - if (!netif_carrier_ok(dp->slave)) + if (!netif_carrier_ok(dp->user)) mib->cnt_ptr = dev->info->reg_mib_cnt; } port_r_cnt(dev, i); @@ -2461,7 +2468,7 @@ static void ksz_get_ethtool_stats(struct dsa_switch *ds, int port, mutex_lock(&mib->cnt_mutex); /* Only read dropped counters if no link. */ - if (!netif_carrier_ok(dp->slave)) + if (!netif_carrier_ok(dp->user)) mib->cnt_ptr = dev->info->reg_mib_cnt; port_r_cnt(dev, port); memcpy(buf, mib->counters, dev->info->mib_cnt * sizeof(u64)); @@ -2571,7 +2578,7 @@ static int ksz_port_setup(struct dsa_switch *ds, int port) if (!dsa_is_user_port(ds, port)) return 0; - /* setup slave port */ + /* setup user port */ dev->dev_ops->port_setup(dev, port, false); /* port_stp_state_set() will be called after to enable the port so @@ -3539,6 +3546,224 @@ static int ksz_setup_tc(struct dsa_switch *ds, int port, } } +static void ksz_get_wol(struct dsa_switch *ds, int port, + struct ethtool_wolinfo *wol) +{ + struct ksz_device *dev = ds->priv; + + if (dev->dev_ops->get_wol) + dev->dev_ops->get_wol(dev, port, wol); +} + +static int ksz_set_wol(struct dsa_switch *ds, int port, + struct ethtool_wolinfo *wol) +{ + struct ksz_device *dev = ds->priv; + + if (dev->dev_ops->set_wol) + return dev->dev_ops->set_wol(dev, port, wol); + + return -EOPNOTSUPP; +} + +static int ksz_port_set_mac_address(struct dsa_switch *ds, int port, + const unsigned char *addr) +{ + struct dsa_port *dp = dsa_to_port(ds, port); + struct ethtool_wolinfo wol; + + if (dp->hsr_dev) { + dev_err(ds->dev, + "Cannot change MAC address on port %d with active HSR offload\n", + port); + return -EBUSY; + } + + ksz_get_wol(ds, dp->index, &wol); + if (wol.wolopts & WAKE_MAGIC) { + dev_err(ds->dev, + "Cannot change MAC address on port %d with active Wake on Magic Packet\n", + port); + return -EBUSY; + } + + return 0; +} + +/** + * ksz_is_port_mac_global_usable - Check if the MAC address on a given port + * can be used as a global address. + * @ds: Pointer to the DSA switch structure. + * @port: The port number on which the MAC address is to be checked. + * + * This function examines the MAC address set on the specified port and + * determines if it can be used as a global address for the switch. + * + * Return: true if the port's MAC address can be used as a global address, false + * otherwise. + */ +bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port) +{ + struct net_device *user = dsa_to_port(ds, port)->user; + const unsigned char *addr = user->dev_addr; + struct ksz_switch_macaddr *switch_macaddr; + struct ksz_device *dev = ds->priv; + + ASSERT_RTNL(); + + switch_macaddr = dev->switch_macaddr; + if (switch_macaddr && !ether_addr_equal(switch_macaddr->addr, addr)) + return false; + + return true; +} + +/** + * ksz_switch_macaddr_get - Program the switch's MAC address register. + * @ds: DSA switch instance. + * @port: Port number. + * @extack: Netlink extended acknowledgment. + * + * This function programs the switch's MAC address register with the MAC address + * of the requesting user port. This single address is used by the switch for + * multiple features like HSR self-address filtering and WoL. Other user ports + * can share ownership of this address as long as their MAC address is the same. + * The MAC addresses of user ports must not change while they have ownership of + * the switch MAC address. + * + * Return: 0 on success, or other error codes on failure. + */ +int ksz_switch_macaddr_get(struct dsa_switch *ds, int port, + struct netlink_ext_ack *extack) +{ + struct net_device *user = dsa_to_port(ds, port)->user; + const unsigned char *addr = user->dev_addr; + struct ksz_switch_macaddr *switch_macaddr; + struct ksz_device *dev = ds->priv; + const u16 *regs = dev->info->regs; + int i, ret; + + /* Make sure concurrent MAC address changes are blocked */ + ASSERT_RTNL(); + + switch_macaddr = dev->switch_macaddr; + if (switch_macaddr) { + if (!ether_addr_equal(switch_macaddr->addr, addr)) { + NL_SET_ERR_MSG_FMT_MOD(extack, + "Switch already configured for MAC address %pM", + switch_macaddr->addr); + return -EBUSY; + } + + refcount_inc(&switch_macaddr->refcount); + return 0; + } + + switch_macaddr = kzalloc(sizeof(*switch_macaddr), GFP_KERNEL); + if (!switch_macaddr) + return -ENOMEM; + + ether_addr_copy(switch_macaddr->addr, addr); + refcount_set(&switch_macaddr->refcount, 1); + dev->switch_macaddr = switch_macaddr; + + /* Program the switch MAC address to hardware */ + for (i = 0; i < ETH_ALEN; i++) { + ret = ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i, addr[i]); + if (ret) + goto macaddr_drop; + } + + return 0; + +macaddr_drop: + dev->switch_macaddr = NULL; + refcount_set(&switch_macaddr->refcount, 0); + kfree(switch_macaddr); + + return ret; +} + +void ksz_switch_macaddr_put(struct dsa_switch *ds) +{ + struct ksz_switch_macaddr *switch_macaddr; + struct ksz_device *dev = ds->priv; + const u16 *regs = dev->info->regs; + int i; + + /* Make sure concurrent MAC address changes are blocked */ + ASSERT_RTNL(); + + switch_macaddr = dev->switch_macaddr; + if (!refcount_dec_and_test(&switch_macaddr->refcount)) + return; + + for (i = 0; i < ETH_ALEN; i++) + ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i, 0); + + dev->switch_macaddr = NULL; + kfree(switch_macaddr); +} + +static int ksz_hsr_join(struct dsa_switch *ds, int port, struct net_device *hsr, + struct netlink_ext_ack *extack) +{ + struct ksz_device *dev = ds->priv; + enum hsr_version ver; + int ret; + + ret = hsr_get_version(hsr, &ver); + if (ret) + return ret; + + if (dev->chip_id != KSZ9477_CHIP_ID) { + NL_SET_ERR_MSG_MOD(extack, "Chip does not support HSR offload"); + return -EOPNOTSUPP; + } + + /* KSZ9477 can support HW offloading of only 1 HSR device */ + if (dev->hsr_dev && hsr != dev->hsr_dev) { + NL_SET_ERR_MSG_MOD(extack, "Offload supported for a single HSR"); + return -EOPNOTSUPP; + } + + /* KSZ9477 only supports HSR v0 and v1 */ + if (!(ver == HSR_V0 || ver == HSR_V1)) { + NL_SET_ERR_MSG_MOD(extack, "Only HSR v0 and v1 supported"); + return -EOPNOTSUPP; + } + + /* Self MAC address filtering, to avoid frames traversing + * the HSR ring more than once. + */ + ret = ksz_switch_macaddr_get(ds, port, extack); + if (ret) + return ret; + + ksz9477_hsr_join(ds, port, hsr); + dev->hsr_dev = hsr; + dev->hsr_ports |= BIT(port); + + return 0; +} + +static int ksz_hsr_leave(struct dsa_switch *ds, int port, + struct net_device *hsr) +{ + struct ksz_device *dev = ds->priv; + + WARN_ON(dev->chip_id != KSZ9477_CHIP_ID); + + ksz9477_hsr_leave(ds, port, hsr); + dev->hsr_ports &= ~BIT(port); + if (!dev->hsr_ports) + dev->hsr_dev = NULL; + + ksz_switch_macaddr_put(ds); + + return 0; +} + static const struct dsa_switch_ops ksz_switch_ops = { .get_tag_protocol = ksz_get_tag_protocol, .connect_tag_protocol = ksz_connect_tag_protocol, @@ -3558,6 +3783,9 @@ static const struct dsa_switch_ops ksz_switch_ops = { .get_sset_count = ksz_sset_count, .port_bridge_join = ksz_port_bridge_join, .port_bridge_leave = ksz_port_bridge_leave, + .port_hsr_join = ksz_hsr_join, + .port_hsr_leave = ksz_hsr_leave, + .port_set_mac_address = ksz_port_set_mac_address, .port_stp_state_set = ksz_port_stp_state_set, .port_teardown = ksz_port_teardown, .port_pre_bridge_flags = ksz_port_pre_bridge_flags, @@ -3577,6 +3805,8 @@ static const struct dsa_switch_ops ksz_switch_ops = { .get_pause_stats = ksz_get_pause_stats, .port_change_mtu = ksz_change_mtu, .port_max_mtu = ksz_max_mtu, + .get_wol = ksz_get_wol, + .set_wol = ksz_set_wol, .get_ts_info = ksz_get_ts_info, .port_hwtstamp_get = ksz_hwtstamp_get, .port_hwtstamp_set = ksz_hwtstamp_set, @@ -3616,6 +3846,30 @@ struct ksz_device *ksz_switch_alloc(struct device *base, void *priv) } EXPORT_SYMBOL(ksz_switch_alloc); +/** + * ksz_switch_shutdown - Shutdown routine for the switch device. + * @dev: The switch device structure. + * + * This function is responsible for initiating a shutdown sequence for the + * switch device. It invokes the reset operation defined in the device + * operations, if available, to reset the switch. Subsequently, it calls the + * DSA framework's shutdown function to ensure a proper shutdown of the DSA + * switch. + */ +void ksz_switch_shutdown(struct ksz_device *dev) +{ + bool wol_enabled = false; + + if (dev->dev_ops->wol_pre_shutdown) + dev->dev_ops->wol_pre_shutdown(dev, &wol_enabled); + + if (dev->dev_ops->reset && !wol_enabled) + dev->dev_ops->reset(dev); + + dsa_switch_shutdown(dev->ds); +} +EXPORT_SYMBOL(ksz_switch_shutdown); + static void ksz_parse_rgmii_delay(struct ksz_device *dev, int port_num, struct device_node *port_dn) { @@ -4009,6 +4263,9 @@ int ksz_switch_register(struct ksz_device *dev) dev_err(dev->dev, "inconsistent synclko settings\n"); return -EINVAL; } + + dev->wakeup_source = of_property_read_bool(dev->dev->of_node, + "wakeup-source"); } ret = dsa_register_switch(dev->ds); diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index d180c8a34e27..b7e8a403a132 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -101,6 +101,11 @@ struct ksz_ptp_irq { int num; }; +struct ksz_switch_macaddr { + unsigned char addr[ETH_ALEN]; + refcount_t refcount; +}; + struct ksz_port { bool remove_tag; /* Remove Tag flag set, for ksz8795 only */ bool learning; @@ -158,6 +163,7 @@ struct ksz_device { phy_interface_t compat_interface; bool synclko_125; bool synclko_disable; + bool wakeup_source; struct vlan_table *vlan_cache; @@ -170,6 +176,10 @@ struct ksz_device { struct mutex lock_irq; /* IRQ Access */ struct ksz_irq girq; struct ksz_ptp_data ptp_data; + + struct ksz_switch_macaddr *switch_macaddr; + struct net_device *hsr_dev; /* HSR */ + u8 hsr_ports; }; /* List of supported models */ @@ -212,6 +222,7 @@ enum ksz_chip_id { }; enum ksz_regs { + REG_SW_MAC_ADDR, REG_IND_CTRL_0, REG_IND_DATA_8, REG_IND_DATA_CHECK, @@ -363,6 +374,11 @@ struct ksz_dev_ops { int duplex, bool tx_pause, bool rx_pause); void (*setup_rgmii_delay)(struct ksz_device *dev, int port); int (*tc_cbs_set_cinc)(struct ksz_device *dev, int port, u32 val); + void (*get_wol)(struct ksz_device *dev, int port, + struct ethtool_wolinfo *wol); + int (*set_wol)(struct ksz_device *dev, int port, + struct ethtool_wolinfo *wol); + void (*wol_pre_shutdown)(struct ksz_device *dev, bool *wol_enabled); void (*config_cpu_port)(struct dsa_switch *ds); int (*enable_stp_addr)(struct ksz_device *dev); int (*reset)(struct ksz_device *dev); @@ -375,12 +391,17 @@ int ksz_switch_register(struct ksz_device *dev); void ksz_switch_remove(struct ksz_device *dev); void ksz_init_mib_timer(struct ksz_device *dev); +bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port); void ksz_r_mib_stats64(struct ksz_device *dev, int port); void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port); void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state); bool ksz_get_gbit(struct ksz_device *dev, int port); phy_interface_t ksz_get_xmii(struct ksz_device *dev, int port, bool gbit); extern const struct ksz_chip_data ksz_switch_chips[]; +int ksz_switch_macaddr_get(struct dsa_switch *ds, int port, + struct netlink_ext_ack *extack); +void ksz_switch_macaddr_put(struct dsa_switch *ds); +void ksz_switch_shutdown(struct ksz_device *dev); /* Common register access functions */ static inline struct regmap *ksz_regmap_8(struct ksz_device *dev) diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c index 4e22a695a64c..1fe105913c75 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.c +++ b/drivers/net/dsa/microchip/ksz_ptp.c @@ -557,7 +557,7 @@ static void ksz_ptp_txtstamp_skb(struct ksz_device *dev, struct skb_shared_hwtstamps hwtstamps = {}; int ret; - /* timeout must include DSA master to transmit data, tstamp latency, + /* timeout must include DSA conduit to transmit data, tstamp latency, * IRQ latency and time for reading the time stamp. */ ret = wait_for_completion_timeout(&prt->tstamp_msg_comp, diff --git a/drivers/net/dsa/microchip/ksz_spi.c b/drivers/net/dsa/microchip/ksz_spi.c index 279338451621..6f6d878e742c 100644 --- a/drivers/net/dsa/microchip/ksz_spi.c +++ b/drivers/net/dsa/microchip/ksz_spi.c @@ -114,10 +114,7 @@ static void ksz_spi_shutdown(struct spi_device *spi) if (!dev) return; - if (dev->dev_ops->reset) - dev->dev_ops->reset(dev); - - dsa_switch_shutdown(dev->ds); + ksz_switch_shutdown(dev); spi_set_drvdata(spi, NULL); } |