diff options
Diffstat (limited to 'drivers/net/dsa/microchip/ksz_common.c')
| -rw-r--r-- | drivers/net/dsa/microchip/ksz_common.c | 1418 |
1 files changed, 1245 insertions, 173 deletions
diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index b074b4bb0629..0c10351fe5eb 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -2,7 +2,7 @@ /* * Microchip switch driver main logic * - * Copyright (C) 2017-2019 Microchip Technology Inc. + * Copyright (C) 2017-2025 Microchip Technology Inc. */ #include <linux/delay.h> @@ -23,6 +23,7 @@ #include <linux/of_mdio.h> #include <linux/of_net.h> #include <linux/micrel_phy.h> +#include <linux/pinctrl/consumer.h> #include <net/dsa.h> #include <net/ieee8021q.h> #include <net/pkt_cls.h> @@ -246,16 +247,16 @@ static const struct ksz_drive_strength ksz9477_drive_strengths[] = { { SW_DRIVE_STRENGTH_28MA, 28000 }, }; -/* ksz8830_drive_strengths - Drive strength mapping for KSZ8830, KSZ8873, .. +/* ksz88x3_drive_strengths - Drive strength mapping for KSZ8863, KSZ8873, .. * variants. * This values are documented in KSZ8873 and KSZ8863 datasheets. */ -static const struct ksz_drive_strength ksz8830_drive_strengths[] = { +static const struct ksz_drive_strength ksz88x3_drive_strengths[] = { { 0, 8000 }, { KSZ8873_DRIVE_STRENGTH_16MA, 16000 }, }; -static void ksz8830_phylink_mac_config(struct phylink_config *config, +static void ksz88x3_phylink_mac_config(struct phylink_config *config, unsigned int mode, const struct phylink_link_state *state); static void ksz_phylink_mac_config(struct phylink_config *config, @@ -265,19 +266,105 @@ static void ksz_phylink_mac_link_down(struct phylink_config *config, unsigned int mode, phy_interface_t interface); -static const struct phylink_mac_ops ksz8830_phylink_mac_ops = { - .mac_config = ksz8830_phylink_mac_config, +/** + * ksz_phylink_mac_disable_tx_lpi() - Callback to signal LPI support (Dummy) + * @config: phylink config structure + * + * This function is a dummy handler. See ksz_phylink_mac_enable_tx_lpi() for + * a detailed explanation of EEE/LPI handling in KSZ switches. + */ +static void ksz_phylink_mac_disable_tx_lpi(struct phylink_config *config) +{ +} + +/** + * ksz_phylink_mac_enable_tx_lpi() - Callback to signal LPI support (Dummy) + * @config: phylink config structure + * @timer: timer value before entering LPI (unused) + * @tx_clock_stop: whether to stop the TX clock in LPI mode (unused) + * + * This function signals to phylink that the driver architecture supports + * LPI management, enabling phylink to control EEE advertisement during + * negotiation according to IEEE Std 802.3 (Clause 78). + * + * Hardware Management of EEE/LPI State: + * For KSZ switch ports with integrated PHYs (e.g., KSZ9893R ports 1-2), + * observation and testing suggest that the actual EEE / Low Power Idle (LPI) + * state transitions are managed autonomously by the hardware based on + * the auto-negotiation results. (Note: While the datasheet describes EEE + * operation based on negotiation, it doesn't explicitly detail the internal + * MAC/PHY interaction, so autonomous hardware management of the MAC state + * for LPI is inferred from observed behavior). + * This hardware control, consistent with the switch's ability to operate + * autonomously via strapping, means MAC-level software intervention is not + * required or exposed for managing the LPI state once EEE is negotiated. + * (Ref: KSZ9893R Data Sheet DS00002420D, primarily Section 4.7.5 explaining + * EEE, also Sections 4.1.7 on Auto-Negotiation and 3.2.1 on Configuration + * Straps). + * + * Additionally, ports configured as MAC interfaces (e.g., KSZ9893R port 3) + * lack documented MAC-level LPI control. + * + * Therefore, this callback performs no action and serves primarily to inform + * phylink of LPI awareness and to document the inferred hardware behavior. + * + * Returns: 0 (Always success) + */ +static int ksz_phylink_mac_enable_tx_lpi(struct phylink_config *config, + u32 timer, bool tx_clock_stop) +{ + return 0; +} + +static const struct phylink_mac_ops ksz88x3_phylink_mac_ops = { + .mac_config = ksz88x3_phylink_mac_config, .mac_link_down = ksz_phylink_mac_link_down, .mac_link_up = ksz8_phylink_mac_link_up, + .mac_disable_tx_lpi = ksz_phylink_mac_disable_tx_lpi, + .mac_enable_tx_lpi = ksz_phylink_mac_enable_tx_lpi, }; static const struct phylink_mac_ops ksz8_phylink_mac_ops = { .mac_config = ksz_phylink_mac_config, .mac_link_down = ksz_phylink_mac_link_down, .mac_link_up = ksz8_phylink_mac_link_up, + .mac_disable_tx_lpi = ksz_phylink_mac_disable_tx_lpi, + .mac_enable_tx_lpi = ksz_phylink_mac_enable_tx_lpi, +}; + +static const struct ksz_dev_ops ksz8463_dev_ops = { + .setup = ksz8_setup, + .get_port_addr = ksz8463_get_port_addr, + .cfg_port_member = ksz8_cfg_port_member, + .flush_dyn_mac_table = ksz8_flush_dyn_mac_table, + .port_setup = ksz8_port_setup, + .r_phy = ksz8463_r_phy, + .w_phy = ksz8463_w_phy, + .r_mib_cnt = ksz8_r_mib_cnt, + .r_mib_pkt = ksz8_r_mib_pkt, + .r_mib_stat64 = ksz88xx_r_mib_stats64, + .freeze_mib = ksz8_freeze_mib, + .port_init_cnt = ksz8_port_init_cnt, + .fdb_dump = ksz8_fdb_dump, + .fdb_add = ksz8_fdb_add, + .fdb_del = ksz8_fdb_del, + .mdb_add = ksz8_mdb_add, + .mdb_del = ksz8_mdb_del, + .vlan_filtering = ksz8_port_vlan_filtering, + .vlan_add = ksz8_port_vlan_add, + .vlan_del = ksz8_port_vlan_del, + .mirror_add = ksz8_port_mirror_add, + .mirror_del = ksz8_port_mirror_del, + .get_caps = ksz8_get_caps, + .config_cpu_port = ksz8_config_cpu_port, + .enable_stp_addr = ksz8_enable_stp_addr, + .reset = ksz8_reset_switch, + .init = ksz8_switch_init, + .exit = ksz8_switch_exit, + .change_mtu = ksz8_change_mtu, }; -static const struct ksz_dev_ops ksz8_dev_ops = { +static const struct ksz_dev_ops ksz88xx_dev_ops = { .setup = ksz8_setup, .get_port_addr = ksz8_get_port_addr, .cfg_port_member = ksz8_cfg_port_member, @@ -307,6 +394,44 @@ static const struct ksz_dev_ops ksz8_dev_ops = { .init = ksz8_switch_init, .exit = ksz8_switch_exit, .change_mtu = ksz8_change_mtu, + .pme_write8 = ksz8_pme_write8, + .pme_pread8 = ksz8_pme_pread8, + .pme_pwrite8 = ksz8_pme_pwrite8, +}; + +static const struct ksz_dev_ops ksz87xx_dev_ops = { + .setup = ksz8_setup, + .get_port_addr = ksz8_get_port_addr, + .cfg_port_member = ksz8_cfg_port_member, + .flush_dyn_mac_table = ksz8_flush_dyn_mac_table, + .port_setup = ksz8_port_setup, + .r_phy = ksz8_r_phy, + .w_phy = ksz8_w_phy, + .r_mib_cnt = ksz8_r_mib_cnt, + .r_mib_pkt = ksz8_r_mib_pkt, + .r_mib_stat64 = ksz_r_mib_stats64, + .freeze_mib = ksz8_freeze_mib, + .port_init_cnt = ksz8_port_init_cnt, + .fdb_dump = ksz8_fdb_dump, + .fdb_add = ksz8_fdb_add, + .fdb_del = ksz8_fdb_del, + .mdb_add = ksz8_mdb_add, + .mdb_del = ksz8_mdb_del, + .vlan_filtering = ksz8_port_vlan_filtering, + .vlan_add = ksz8_port_vlan_add, + .vlan_del = ksz8_port_vlan_del, + .mirror_add = ksz8_port_mirror_add, + .mirror_del = ksz8_port_mirror_del, + .get_caps = ksz8_get_caps, + .config_cpu_port = ksz8_config_cpu_port, + .enable_stp_addr = ksz8_enable_stp_addr, + .reset = ksz8_reset_switch, + .init = ksz8_switch_init, + .exit = ksz8_switch_exit, + .change_mtu = ksz8_change_mtu, + .pme_write8 = ksz8_pme_write8, + .pme_pread8 = ksz8_pme_pread8, + .pme_pwrite8 = ksz8_pme_pwrite8, }; static void ksz9477_phylink_mac_link_up(struct phylink_config *config, @@ -316,10 +441,29 @@ static void ksz9477_phylink_mac_link_up(struct phylink_config *config, int speed, int duplex, bool tx_pause, bool rx_pause); +static struct phylink_pcs * +ksz_phylink_mac_select_pcs(struct phylink_config *config, + phy_interface_t interface) +{ + struct dsa_port *dp = dsa_phylink_to_port(config); + struct ksz_device *dev = dp->ds->priv; + struct ksz_port *p = &dev->ports[dp->index]; + + if (ksz_is_sgmii_port(dev, dp->index) && + (interface == PHY_INTERFACE_MODE_SGMII || + interface == PHY_INTERFACE_MODE_1000BASEX)) + return p->pcs; + + return NULL; +} + static const struct phylink_mac_ops ksz9477_phylink_mac_ops = { .mac_config = ksz_phylink_mac_config, .mac_link_down = ksz_phylink_mac_link_down, .mac_link_up = ksz9477_phylink_mac_link_up, + .mac_disable_tx_lpi = ksz_phylink_mac_disable_tx_lpi, + .mac_enable_tx_lpi = ksz_phylink_mac_enable_tx_lpi, + .mac_select_pcs = ksz_phylink_mac_select_pcs, }; static const struct ksz_dev_ops ksz9477_dev_ops = { @@ -348,21 +492,24 @@ static const struct ksz_dev_ops ksz9477_dev_ops = { .mdb_add = ksz9477_mdb_add, .mdb_del = ksz9477_mdb_del, .change_mtu = ksz9477_change_mtu, - .get_wol = ksz9477_get_wol, - .set_wol = ksz9477_set_wol, - .wol_pre_shutdown = ksz9477_wol_pre_shutdown, + .pme_write8 = ksz_write8, + .pme_pread8 = ksz_pread8, + .pme_pwrite8 = ksz_pwrite8, .config_cpu_port = ksz9477_config_cpu_port, .tc_cbs_set_cinc = ksz9477_tc_cbs_set_cinc, .enable_stp_addr = ksz9477_enable_stp_addr, .reset = ksz9477_reset_switch, .init = ksz9477_switch_init, .exit = ksz9477_switch_exit, + .pcs_create = ksz9477_pcs_create, }; static const struct phylink_mac_ops lan937x_phylink_mac_ops = { .mac_config = ksz_phylink_mac_config, .mac_link_down = ksz_phylink_mac_link_down, .mac_link_up = ksz9477_phylink_mac_link_up, + .mac_disable_tx_lpi = ksz_phylink_mac_disable_tx_lpi, + .mac_enable_tx_lpi = ksz_phylink_mac_enable_tx_lpi, }; static const struct ksz_dev_ops lan937x_dev_ops = { @@ -373,6 +520,8 @@ static const struct ksz_dev_ops lan937x_dev_ops = { .flush_dyn_mac_table = ksz9477_flush_dyn_mac_table, .port_setup = lan937x_port_setup, .set_ageing_time = lan937x_set_ageing_time, + .mdio_bus_preinit = lan937x_mdio_bus_preinit, + .create_phy_addr_map = lan937x_create_phy_addr_map, .r_phy = lan937x_r_phy, .w_phy = lan937x_w_phy, .r_mib_cnt = ksz9477_r_mib_cnt, @@ -401,6 +550,60 @@ static const struct ksz_dev_ops lan937x_dev_ops = { .exit = lan937x_switch_exit, }; +static const u16 ksz8463_regs[] = { + [REG_SW_MAC_ADDR] = 0x10, + [REG_IND_CTRL_0] = 0x30, + [REG_IND_DATA_8] = 0x26, + [REG_IND_DATA_CHECK] = 0x26, + [REG_IND_DATA_HI] = 0x28, + [REG_IND_DATA_LO] = 0x2C, + [REG_IND_MIB_CHECK] = 0x2F, + [P_FORCE_CTRL] = 0x0C, + [P_LINK_STATUS] = 0x0E, + [P_LOCAL_CTRL] = 0x0C, + [P_NEG_RESTART_CTRL] = 0x0D, + [P_REMOTE_STATUS] = 0x0E, + [P_SPEED_STATUS] = 0x0F, + [S_TAIL_TAG_CTRL] = 0xAD, + [P_STP_CTRL] = 0x6F, + [S_START_CTRL] = 0x01, + [S_BROADCAST_CTRL] = 0x06, + [S_MULTICAST_CTRL] = 0x04, +}; + +static const u32 ksz8463_masks[] = { + [PORT_802_1P_REMAPPING] = BIT(3), + [SW_TAIL_TAG_ENABLE] = BIT(0), + [MIB_COUNTER_OVERFLOW] = BIT(7), + [MIB_COUNTER_VALID] = BIT(6), + [VLAN_TABLE_FID] = GENMASK(15, 12), + [VLAN_TABLE_MEMBERSHIP] = GENMASK(18, 16), + [VLAN_TABLE_VALID] = BIT(19), + [STATIC_MAC_TABLE_VALID] = BIT(19), + [STATIC_MAC_TABLE_USE_FID] = BIT(21), + [STATIC_MAC_TABLE_FID] = GENMASK(25, 22), + [STATIC_MAC_TABLE_OVERRIDE] = BIT(20), + [STATIC_MAC_TABLE_FWD_PORTS] = GENMASK(18, 16), + [DYNAMIC_MAC_TABLE_ENTRIES_H] = GENMASK(1, 0), + [DYNAMIC_MAC_TABLE_MAC_EMPTY] = BIT(2), + [DYNAMIC_MAC_TABLE_NOT_READY] = BIT(7), + [DYNAMIC_MAC_TABLE_ENTRIES] = GENMASK(31, 24), + [DYNAMIC_MAC_TABLE_FID] = GENMASK(19, 16), + [DYNAMIC_MAC_TABLE_SRC_PORT] = GENMASK(21, 20), + [DYNAMIC_MAC_TABLE_TIMESTAMP] = GENMASK(23, 22), +}; + +static u8 ksz8463_shifts[] = { + [VLAN_TABLE_MEMBERSHIP_S] = 16, + [STATIC_MAC_FWD_PORTS] = 16, + [STATIC_MAC_FID] = 22, + [DYNAMIC_MAC_ENTRIES_H] = 8, + [DYNAMIC_MAC_ENTRIES] = 24, + [DYNAMIC_MAC_FID] = 16, + [DYNAMIC_MAC_TIMESTAMP] = 22, + [DYNAMIC_MAC_SRC_PORT] = 20, +}; + static const u16 ksz8795_regs[] = { [REG_SW_MAC_ADDR] = 0x68, [REG_IND_CTRL_0] = 0x6E, @@ -423,6 +626,9 @@ static const u16 ksz8795_regs[] = { [S_MULTICAST_CTRL] = 0x04, [P_XMII_CTRL_0] = 0x06, [P_XMII_CTRL_1] = 0x06, + [REG_SW_PME_CTRL] = 0x8003, + [REG_PORT_PME_STATUS] = 0x8003, + [REG_PORT_PME_CTRL] = 0x8007, }; static const u32 ksz8795_masks[] = { @@ -531,6 +737,61 @@ static u8 ksz8863_shifts[] = { [DYNAMIC_MAC_SRC_PORT] = 20, }; +static const u16 ksz8895_regs[] = { + [REG_SW_MAC_ADDR] = 0x68, + [REG_IND_CTRL_0] = 0x6E, + [REG_IND_DATA_8] = 0x70, + [REG_IND_DATA_CHECK] = 0x72, + [REG_IND_DATA_HI] = 0x71, + [REG_IND_DATA_LO] = 0x75, + [REG_IND_MIB_CHECK] = 0x75, + [P_FORCE_CTRL] = 0x0C, + [P_LINK_STATUS] = 0x0E, + [P_LOCAL_CTRL] = 0x0C, + [P_NEG_RESTART_CTRL] = 0x0D, + [P_REMOTE_STATUS] = 0x0E, + [P_SPEED_STATUS] = 0x09, + [S_TAIL_TAG_CTRL] = 0x0C, + [P_STP_CTRL] = 0x02, + [S_START_CTRL] = 0x01, + [S_BROADCAST_CTRL] = 0x06, + [S_MULTICAST_CTRL] = 0x04, +}; + +static const u32 ksz8895_masks[] = { + [PORT_802_1P_REMAPPING] = BIT(7), + [SW_TAIL_TAG_ENABLE] = BIT(1), + [MIB_COUNTER_OVERFLOW] = BIT(7), + [MIB_COUNTER_VALID] = BIT(6), + [VLAN_TABLE_FID] = GENMASK(6, 0), + [VLAN_TABLE_MEMBERSHIP] = GENMASK(11, 7), + [VLAN_TABLE_VALID] = BIT(12), + [STATIC_MAC_TABLE_VALID] = BIT(21), + [STATIC_MAC_TABLE_USE_FID] = BIT(23), + [STATIC_MAC_TABLE_FID] = GENMASK(30, 24), + [STATIC_MAC_TABLE_OVERRIDE] = BIT(22), + [STATIC_MAC_TABLE_FWD_PORTS] = GENMASK(20, 16), + [DYNAMIC_MAC_TABLE_ENTRIES_H] = GENMASK(6, 0), + [DYNAMIC_MAC_TABLE_MAC_EMPTY] = BIT(7), + [DYNAMIC_MAC_TABLE_NOT_READY] = BIT(7), + [DYNAMIC_MAC_TABLE_ENTRIES] = GENMASK(31, 29), + [DYNAMIC_MAC_TABLE_FID] = GENMASK(22, 16), + [DYNAMIC_MAC_TABLE_SRC_PORT] = GENMASK(26, 24), + [DYNAMIC_MAC_TABLE_TIMESTAMP] = GENMASK(28, 27), +}; + +static const u8 ksz8895_shifts[] = { + [VLAN_TABLE_MEMBERSHIP_S] = 7, + [VLAN_TABLE] = 13, + [STATIC_MAC_FWD_PORTS] = 16, + [STATIC_MAC_FID] = 24, + [DYNAMIC_MAC_ENTRIES_H] = 3, + [DYNAMIC_MAC_ENTRIES] = 29, + [DYNAMIC_MAC_FID] = 16, + [DYNAMIC_MAC_TIMESTAMP] = 27, + [DYNAMIC_MAC_SRC_PORT] = 24, +}; + static const u16 ksz9477_regs[] = { [REG_SW_MAC_ADDR] = 0x0302, [P_STP_CTRL] = 0x0B04, @@ -539,11 +800,16 @@ static const u16 ksz9477_regs[] = { [S_MULTICAST_CTRL] = 0x0331, [P_XMII_CTRL_0] = 0x0300, [P_XMII_CTRL_1] = 0x0301, + [REG_SW_PME_CTRL] = 0x0006, + [REG_PORT_PME_STATUS] = 0x0013, + [REG_PORT_PME_CTRL] = 0x0017, }; static const u32 ksz9477_masks[] = { [ALU_STAT_WRITE] = 0, [ALU_STAT_READ] = 1, + [ALU_STAT_DIRECT] = 0, + [ALU_RESV_MCAST_ADDR] = BIT(1), [P_MII_TX_FLOW_CTRL] = BIT(5), [P_MII_RX_FLOW_CTRL] = BIT(3), }; @@ -571,6 +837,8 @@ static const u8 ksz9477_xmii_ctrl1[] = { static const u32 lan937x_masks[] = { [ALU_STAT_WRITE] = 1, [ALU_STAT_READ] = 2, + [ALU_STAT_DIRECT] = BIT(3), + [ALU_RESV_MCAST_ADDR] = BIT(2), [P_MII_TX_FLOW_CTRL] = BIT(5), [P_MII_RX_FLOW_CTRL] = BIT(3), }; @@ -934,8 +1202,7 @@ static const struct regmap_range ksz9477_valid_regs[] = { regmap_reg_range(0x701b, 0x701b), regmap_reg_range(0x701f, 0x7020), regmap_reg_range(0x7030, 0x7030), - regmap_reg_range(0x7200, 0x7203), - regmap_reg_range(0x7206, 0x7207), + regmap_reg_range(0x7200, 0x7207), regmap_reg_range(0x7300, 0x7301), regmap_reg_range(0x7400, 0x7401), regmap_reg_range(0x7403, 0x7403), @@ -999,10 +1266,9 @@ static const struct regmap_range ksz9896_valid_regs[] = { regmap_reg_range(0x1030, 0x1030), regmap_reg_range(0x1100, 0x1115), regmap_reg_range(0x111a, 0x111f), - regmap_reg_range(0x1122, 0x1127), - regmap_reg_range(0x112a, 0x112b), - regmap_reg_range(0x1136, 0x1139), - regmap_reg_range(0x113e, 0x113f), + regmap_reg_range(0x1120, 0x112b), + regmap_reg_range(0x1134, 0x113b), + regmap_reg_range(0x113c, 0x113f), regmap_reg_range(0x1400, 0x1401), regmap_reg_range(0x1403, 0x1403), regmap_reg_range(0x1410, 0x1417), @@ -1029,10 +1295,9 @@ static const struct regmap_range ksz9896_valid_regs[] = { regmap_reg_range(0x2030, 0x2030), regmap_reg_range(0x2100, 0x2115), regmap_reg_range(0x211a, 0x211f), - regmap_reg_range(0x2122, 0x2127), - regmap_reg_range(0x212a, 0x212b), - regmap_reg_range(0x2136, 0x2139), - regmap_reg_range(0x213e, 0x213f), + regmap_reg_range(0x2120, 0x212b), + regmap_reg_range(0x2134, 0x213b), + regmap_reg_range(0x213c, 0x213f), regmap_reg_range(0x2400, 0x2401), regmap_reg_range(0x2403, 0x2403), regmap_reg_range(0x2410, 0x2417), @@ -1059,10 +1324,9 @@ static const struct regmap_range ksz9896_valid_regs[] = { regmap_reg_range(0x3030, 0x3030), regmap_reg_range(0x3100, 0x3115), regmap_reg_range(0x311a, 0x311f), - regmap_reg_range(0x3122, 0x3127), - regmap_reg_range(0x312a, 0x312b), - regmap_reg_range(0x3136, 0x3139), - regmap_reg_range(0x313e, 0x313f), + regmap_reg_range(0x3120, 0x312b), + regmap_reg_range(0x3134, 0x313b), + regmap_reg_range(0x313c, 0x313f), regmap_reg_range(0x3400, 0x3401), regmap_reg_range(0x3403, 0x3403), regmap_reg_range(0x3410, 0x3417), @@ -1089,10 +1353,9 @@ static const struct regmap_range ksz9896_valid_regs[] = { regmap_reg_range(0x4030, 0x4030), regmap_reg_range(0x4100, 0x4115), regmap_reg_range(0x411a, 0x411f), - regmap_reg_range(0x4122, 0x4127), - regmap_reg_range(0x412a, 0x412b), - regmap_reg_range(0x4136, 0x4139), - regmap_reg_range(0x413e, 0x413f), + regmap_reg_range(0x4120, 0x412b), + regmap_reg_range(0x4134, 0x413b), + regmap_reg_range(0x413c, 0x413f), regmap_reg_range(0x4400, 0x4401), regmap_reg_range(0x4403, 0x4403), regmap_reg_range(0x4410, 0x4417), @@ -1119,10 +1382,9 @@ static const struct regmap_range ksz9896_valid_regs[] = { regmap_reg_range(0x5030, 0x5030), regmap_reg_range(0x5100, 0x5115), regmap_reg_range(0x511a, 0x511f), - regmap_reg_range(0x5122, 0x5127), - regmap_reg_range(0x512a, 0x512b), - regmap_reg_range(0x5136, 0x5139), - regmap_reg_range(0x513e, 0x513f), + regmap_reg_range(0x5120, 0x512b), + regmap_reg_range(0x5134, 0x513b), + regmap_reg_range(0x513c, 0x513f), regmap_reg_range(0x5400, 0x5401), regmap_reg_range(0x5403, 0x5403), regmap_reg_range(0x5410, 0x5417), @@ -1149,10 +1411,9 @@ static const struct regmap_range ksz9896_valid_regs[] = { regmap_reg_range(0x6030, 0x6030), regmap_reg_range(0x6100, 0x6115), regmap_reg_range(0x611a, 0x611f), - regmap_reg_range(0x6122, 0x6127), - regmap_reg_range(0x612a, 0x612b), - regmap_reg_range(0x6136, 0x6139), - regmap_reg_range(0x613e, 0x613f), + regmap_reg_range(0x6120, 0x612b), + regmap_reg_range(0x6134, 0x613b), + regmap_reg_range(0x613c, 0x613f), regmap_reg_range(0x6300, 0x6301), regmap_reg_range(0x6400, 0x6401), regmap_reg_range(0x6403, 0x6403), @@ -1191,6 +1452,7 @@ static const struct regmap_range ksz8873_valid_regs[] = { regmap_reg_range(0x3f, 0x3f), /* advanced control registers */ + regmap_reg_range(0x43, 0x43), regmap_reg_range(0x60, 0x6f), regmap_reg_range(0x70, 0x75), regmap_reg_range(0x76, 0x78), @@ -1217,6 +1479,29 @@ static const struct regmap_access_table ksz8873_register_set = { }; const struct ksz_chip_data ksz_switch_chips[] = { + [KSZ8463] = { + .chip_id = KSZ8463_CHIP_ID, + .dev_name = "KSZ8463", + .num_vlans = 16, + .num_alus = 0, + .num_statics = 8, + .cpu_ports = 0x4, /* can be configured as cpu port */ + .port_cnt = 3, + .num_tx_queues = 4, + .num_ipms = 4, + .ops = &ksz8463_dev_ops, + .phylink_mac_ops = &ksz88x3_phylink_mac_ops, + .mib_names = ksz88xx_mib_names, + .mib_cnt = ARRAY_SIZE(ksz88xx_mib_names), + .reg_mib_cnt = MIB_COUNTER_NUM, + .regs = ksz8463_regs, + .masks = ksz8463_masks, + .shifts = ksz8463_shifts, + .supports_mii = {false, false, true}, + .supports_rmii = {false, false, true}, + .internal_phy = {true, true, false}, + }, + [KSZ8563] = { .chip_id = KSZ8563_CHIP_ID, .dev_name = "KSZ8563", @@ -1244,6 +1529,7 @@ const struct ksz_chip_data ksz_switch_chips[] = { .supports_rgmii = {false, false, true}, .internal_phy = {true, true, false}, .gbit_capable = {false, false, true}, + .ptp_capable = true, .wr_table = &ksz8563_register_set, .rd_table = &ksz8563_register_set, }, @@ -1253,12 +1539,12 @@ const struct ksz_chip_data ksz_switch_chips[] = { .dev_name = "KSZ8795", .num_vlans = 4096, .num_alus = 0, - .num_statics = 8, + .num_statics = 32, .cpu_ports = 0x10, /* can be configured as cpu port */ .port_cnt = 5, /* total cpu and user ports */ .num_tx_queues = 4, .num_ipms = 4, - .ops = &ksz8_dev_ops, + .ops = &ksz87xx_dev_ops, .phylink_mac_ops = &ksz8_phylink_mac_ops, .ksz87xx_eee_link_erratum = true, .mib_names = ksz9477_mib_names, @@ -1294,12 +1580,12 @@ const struct ksz_chip_data ksz_switch_chips[] = { .dev_name = "KSZ8794", .num_vlans = 4096, .num_alus = 0, - .num_statics = 8, + .num_statics = 32, .cpu_ports = 0x10, /* can be configured as cpu port */ .port_cnt = 5, /* total cpu and user ports */ .num_tx_queues = 4, .num_ipms = 4, - .ops = &ksz8_dev_ops, + .ops = &ksz87xx_dev_ops, .phylink_mac_ops = &ksz8_phylink_mac_ops, .ksz87xx_eee_link_erratum = true, .mib_names = ksz9477_mib_names, @@ -1321,12 +1607,12 @@ const struct ksz_chip_data ksz_switch_chips[] = { .dev_name = "KSZ8765", .num_vlans = 4096, .num_alus = 0, - .num_statics = 8, + .num_statics = 32, .cpu_ports = 0x10, /* can be configured as cpu port */ .port_cnt = 5, /* total cpu and user ports */ .num_tx_queues = 4, .num_ipms = 4, - .ops = &ksz8_dev_ops, + .ops = &ksz87xx_dev_ops, .phylink_mac_ops = &ksz8_phylink_mac_ops, .ksz87xx_eee_link_erratum = true, .mib_names = ksz9477_mib_names, @@ -1343,8 +1629,8 @@ const struct ksz_chip_data ksz_switch_chips[] = { .internal_phy = {true, true, true, true, false}, }, - [KSZ8830] = { - .chip_id = KSZ8830_CHIP_ID, + [KSZ88X3] = { + .chip_id = KSZ88X3_CHIP_ID, .dev_name = "KSZ8863/KSZ8873", .num_vlans = 16, .num_alus = 0, @@ -1353,8 +1639,8 @@ const struct ksz_chip_data ksz_switch_chips[] = { .port_cnt = 3, .num_tx_queues = 4, .num_ipms = 4, - .ops = &ksz8_dev_ops, - .phylink_mac_ops = &ksz8830_phylink_mac_ops, + .ops = &ksz88xx_dev_ops, + .phylink_mac_ops = &ksz88x3_phylink_mac_ops, .mib_names = ksz88xx_mib_names, .mib_cnt = ARRAY_SIZE(ksz88xx_mib_names), .reg_mib_cnt = MIB_COUNTER_NUM, @@ -1368,6 +1654,61 @@ const struct ksz_chip_data ksz_switch_chips[] = { .rd_table = &ksz8873_register_set, }, + [KSZ8864] = { + /* WARNING + * ======= + * KSZ8864 is similar to KSZ8895, except the first port + * does not exist. + * external cpu + * KSZ8864 1,2,3 4 + * KSZ8895 0,1,2,3 4 + * port_cnt is configured as 5, even though it is 4 + */ + .chip_id = KSZ8864_CHIP_ID, + .dev_name = "KSZ8864", + .num_vlans = 4096, + .num_alus = 0, + .num_statics = 32, + .cpu_ports = 0x10, /* can be configured as cpu port */ + .port_cnt = 5, /* total cpu and user ports */ + .num_tx_queues = 4, + .num_ipms = 4, + .ops = &ksz88xx_dev_ops, + .phylink_mac_ops = &ksz88x3_phylink_mac_ops, + .mib_names = ksz88xx_mib_names, + .mib_cnt = ARRAY_SIZE(ksz88xx_mib_names), + .reg_mib_cnt = MIB_COUNTER_NUM, + .regs = ksz8895_regs, + .masks = ksz8895_masks, + .shifts = ksz8895_shifts, + .supports_mii = {false, false, false, false, true}, + .supports_rmii = {false, false, false, false, true}, + .internal_phy = {false, true, true, true, false}, + }, + + [KSZ8895] = { + .chip_id = KSZ8895_CHIP_ID, + .dev_name = "KSZ8895", + .num_vlans = 4096, + .num_alus = 0, + .num_statics = 32, + .cpu_ports = 0x10, /* can be configured as cpu port */ + .port_cnt = 5, /* total cpu and user ports */ + .num_tx_queues = 4, + .num_ipms = 4, + .ops = &ksz88xx_dev_ops, + .phylink_mac_ops = &ksz88x3_phylink_mac_ops, + .mib_names = ksz88xx_mib_names, + .mib_cnt = ARRAY_SIZE(ksz88xx_mib_names), + .reg_mib_cnt = MIB_COUNTER_NUM, + .regs = ksz8895_regs, + .masks = ksz8895_masks, + .shifts = ksz8895_shifts, + .supports_mii = {false, false, false, false, true}, + .supports_rmii = {false, false, false, false, true}, + .internal_phy = {true, true, true, true, false}, + }, + [KSZ9477] = { .chip_id = KSZ9477_CHIP_ID, .dev_name = "KSZ9477", @@ -1400,6 +1741,8 @@ const struct ksz_chip_data ksz_switch_chips[] = { .internal_phy = {true, true, true, true, true, false, false}, .gbit_capable = {true, true, true, true, true, true, true}, + .ptp_capable = true, + .sgmii_port = 7, .wr_table = &ksz9477_register_set, .rd_table = &ksz9477_register_set, }, @@ -1527,6 +1870,7 @@ const struct ksz_chip_data ksz_switch_chips[] = { .supports_rgmii = {false, false, true}, .internal_phy = {true, true, false}, .gbit_capable = {true, true, true}, + .ptp_capable = true, }, [KSZ8567] = { @@ -1562,6 +1906,7 @@ const struct ksz_chip_data ksz_switch_chips[] = { true, false, false}, .gbit_capable = {false, false, false, false, false, true, true}, + .ptp_capable = true, }, [KSZ9567] = { @@ -1594,6 +1939,7 @@ const struct ksz_chip_data ksz_switch_chips[] = { .internal_phy = {true, true, true, true, true, false, false}, .gbit_capable = {true, true, true, true, true, true, true}, + .ptp_capable = true, }, [LAN9370] = { @@ -1608,6 +1954,7 @@ const struct ksz_chip_data ksz_switch_chips[] = { .num_tx_queues = 8, .num_ipms = 8, .tc_cbs_supported = true, + .phy_side_mdio_supported = true, .ops = &lan937x_dev_ops, .phylink_mac_ops = &lan937x_phylink_mac_ops, .mib_names = ksz9477_mib_names, @@ -1622,6 +1969,7 @@ const struct ksz_chip_data ksz_switch_chips[] = { .supports_rmii = {false, false, false, false, true}, .supports_rgmii = {false, false, false, false, true}, .internal_phy = {true, true, true, true, false}, + .ptp_capable = true, }, [LAN9371] = { @@ -1636,6 +1984,7 @@ const struct ksz_chip_data ksz_switch_chips[] = { .num_tx_queues = 8, .num_ipms = 8, .tc_cbs_supported = true, + .phy_side_mdio_supported = true, .ops = &lan937x_dev_ops, .phylink_mac_ops = &lan937x_phylink_mac_ops, .mib_names = ksz9477_mib_names, @@ -1650,6 +1999,7 @@ const struct ksz_chip_data ksz_switch_chips[] = { .supports_rmii = {false, false, false, false, true, true}, .supports_rgmii = {false, false, false, false, true, true}, .internal_phy = {true, true, true, true, false, false}, + .ptp_capable = true, }, [LAN9372] = { @@ -1664,6 +2014,7 @@ const struct ksz_chip_data ksz_switch_chips[] = { .num_tx_queues = 8, .num_ipms = 8, .tc_cbs_supported = true, + .phy_side_mdio_supported = true, .ops = &lan937x_dev_ops, .phylink_mac_ops = &lan937x_phylink_mac_ops, .mib_names = ksz9477_mib_names, @@ -1682,6 +2033,7 @@ const struct ksz_chip_data ksz_switch_chips[] = { true, true, false, false}, .internal_phy = {true, true, true, true, false, false, true, true}, + .ptp_capable = true, }, [LAN9373] = { @@ -1696,6 +2048,7 @@ const struct ksz_chip_data ksz_switch_chips[] = { .num_tx_queues = 8, .num_ipms = 8, .tc_cbs_supported = true, + .phy_side_mdio_supported = true, .ops = &lan937x_dev_ops, .phylink_mac_ops = &lan937x_phylink_mac_ops, .mib_names = ksz9477_mib_names, @@ -1714,6 +2067,7 @@ const struct ksz_chip_data ksz_switch_chips[] = { true, true, false, false}, .internal_phy = {true, true, true, false, false, false, true, true}, + .ptp_capable = true, }, [LAN9374] = { @@ -1728,6 +2082,7 @@ const struct ksz_chip_data ksz_switch_chips[] = { .num_tx_queues = 8, .num_ipms = 8, .tc_cbs_supported = true, + .phy_side_mdio_supported = true, .ops = &lan937x_dev_ops, .phylink_mac_ops = &lan937x_phylink_mac_ops, .mib_names = ksz9477_mib_names, @@ -1746,6 +2101,43 @@ const struct ksz_chip_data ksz_switch_chips[] = { true, true, false, false}, .internal_phy = {true, true, true, true, false, false, true, true}, + .ptp_capable = true, + }, + + [LAN9646] = { + .chip_id = LAN9646_CHIP_ID, + .dev_name = "LAN9646", + .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 */ + .port_nirqs = 4, + .num_tx_queues = 4, + .num_ipms = 8, + .ops = &ksz9477_dev_ops, + .phylink_mac_ops = &ksz9477_phylink_mac_ops, + .phy_errata_9477 = true, + .mib_names = ksz9477_mib_names, + .mib_cnt = ARRAY_SIZE(ksz9477_mib_names), + .reg_mib_cnt = MIB_COUNTER_NUM, + .regs = ksz9477_regs, + .masks = ksz9477_masks, + .shifts = ksz9477_shifts, + .xmii_ctrl0 = ksz9477_xmii_ctrl0, + .xmii_ctrl1 = ksz9477_xmii_ctrl1, + .supports_mii = {false, false, false, false, + false, true, true}, + .supports_rmii = {false, false, false, false, + false, true, true}, + .supports_rgmii = {false, false, false, false, + false, true, true}, + .internal_phy = {true, true, true, true, + true, false, false}, + .gbit_capable = {true, true, true, true, true, true, true}, + .sgmii_port = 7, + .wr_table = &ksz9477_register_set, + .rd_table = &ksz9477_register_set, }, }; EXPORT_SYMBOL_GPL(ksz_switch_chips); @@ -1816,6 +2208,18 @@ static void ksz_phylink_get_caps(struct dsa_switch *ds, int port, if (dev->dev_ops->get_caps) dev->dev_ops->get_caps(dev, port, config); + + if (ds->ops->support_eee && ds->ops->support_eee(ds, port)) { + memcpy(config->lpi_interfaces, config->supported_interfaces, + sizeof(config->lpi_interfaces)); + + config->lpi_capabilities = MAC_100FD; + if (dev->info->gbit_capable[port]) + config->lpi_capabilities |= MAC_1000FD; + + /* EEE is fully operational */ + config->eee_enabled_default = true; + } } void ksz_r_mib_stats64(struct ksz_device *dev, int port) @@ -1867,7 +2271,7 @@ void ksz_r_mib_stats64(struct ksz_device *dev, int port) spin_unlock(&mib->stats64_lock); - if (dev->info->phy_errata_9477) { + if (dev->info->phy_errata_9477 && !ksz_is_sgmii_port(dev, port)) { ret = ksz9477_errata_monitor(dev, port, raw->tx_late_col); if (ret) dev_err(dev->dev, "Failed to monitor transmission halt\n"); @@ -1958,10 +2362,8 @@ static void ksz_get_strings(struct dsa_switch *ds, int port, if (stringset != ETH_SS_STATS) return; - for (i = 0; i < dev->info->mib_cnt; i++) { - memcpy(buf + i * ETH_GSTRING_LEN, - dev->info->mib_names[i].string, ETH_GSTRING_LEN); - } + for (i = 0; i < dev->info->mib_cnt; i++) + ethtool_puts(&buf, dev->info->mib_names[i].string); } /** @@ -2060,6 +2462,12 @@ static void ksz_update_port_member(struct ksz_device *dev, int port) dev->dev_ops->cfg_port_member(dev, i, val | cpu_port); } + /* HSR ports are setup once so need to use the assigned membership + * when the port is enabled. + */ + if (!port_member && p->stp_state == BR_STATE_FORWARDING && + (dev->hsr_ports & BIT(port))) + port_member = dev->hsr_ports; dev->dev_ops->cfg_port_member(dev, port, port_member | cpu_port); } @@ -2084,19 +2492,103 @@ static int ksz_sw_mdio_write(struct mii_bus *bus, int addr, int regnum, return dev->dev_ops->w_phy(dev, addr, regnum, val); } +/** + * ksz_parent_mdio_read - Read data from a PHY register on the parent MDIO bus. + * @bus: MDIO bus structure. + * @addr: PHY address on the parent MDIO bus. + * @regnum: Register number to read. + * + * This function provides a direct read operation on the parent MDIO bus for + * accessing PHY registers. By bypassing SPI or I2C, it uses the parent MDIO bus + * to retrieve data from the PHY registers at the specified address and register + * number. + * + * Return: Value of the PHY register, or a negative error code on failure. + */ +static int ksz_parent_mdio_read(struct mii_bus *bus, int addr, int regnum) +{ + struct ksz_device *dev = bus->priv; + + return mdiobus_read_nested(dev->parent_mdio_bus, addr, regnum); +} + +/** + * ksz_parent_mdio_write - Write data to a PHY register on the parent MDIO bus. + * @bus: MDIO bus structure. + * @addr: PHY address on the parent MDIO bus. + * @regnum: Register number to write to. + * @val: Value to write to the PHY register. + * + * This function provides a direct write operation on the parent MDIO bus for + * accessing PHY registers. Bypassing SPI or I2C, it uses the parent MDIO bus + * to modify the PHY register values at the specified address. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int ksz_parent_mdio_write(struct mii_bus *bus, int addr, int regnum, + u16 val) +{ + struct ksz_device *dev = bus->priv; + + return mdiobus_write_nested(dev->parent_mdio_bus, addr, regnum, val); +} + +/** + * ksz_phy_addr_to_port - Map a PHY address to the corresponding switch port. + * @dev: Pointer to device structure. + * @addr: PHY address to map to a port. + * + * This function finds the corresponding switch port for a given PHY address by + * iterating over all user ports on the device. It checks if a port's PHY + * address in `phy_addr_map` matches the specified address and if the port + * contains an internal PHY. If a match is found, the index of the port is + * returned. + * + * Return: Port index on success, or -EINVAL if no matching port is found. + */ +static int ksz_phy_addr_to_port(struct ksz_device *dev, int addr) +{ + struct dsa_switch *ds = dev->ds; + struct dsa_port *dp; + + dsa_switch_for_each_user_port(dp, ds) { + if (dev->info->internal_phy[dp->index] && + dev->phy_addr_map[dp->index] == addr) + return dp->index; + } + + return -EINVAL; +} + +/** + * ksz_irq_phy_setup - Configure IRQs for PHYs in the KSZ device. + * @dev: Pointer to the KSZ device structure. + * + * Sets up IRQs for each active PHY connected to the KSZ switch by mapping the + * appropriate IRQs for each PHY and assigning them to the `user_mii_bus` in + * the DSA switch structure. Each IRQ is mapped based on the port's IRQ domain. + * + * Return: 0 on success, or a negative error code on failure. + */ static int ksz_irq_phy_setup(struct ksz_device *dev) { struct dsa_switch *ds = dev->ds; - int phy; + int phy, port; int irq; int ret; - for (phy = 0; phy < KSZ_MAX_NUM_PORTS; phy++) { + for (phy = 0; phy < PHY_MAX_ADDR; phy++) { if (BIT(phy) & ds->phys_mii_mask) { - irq = irq_find_mapping(dev->ports[phy].pirq.domain, + port = ksz_phy_addr_to_port(dev, phy); + if (port < 0) { + ret = port; + goto out; + } + + irq = irq_find_mapping(dev->ports[port].pirq.domain, PORT_SRC_PHY_INT); - if (irq < 0) { - ret = irq; + if (!irq) { + ret = -EINVAL; goto out; } ds->user_mii_bus->irq[phy] = irq; @@ -2111,49 +2603,187 @@ out: return ret; } +/** + * ksz_irq_phy_free - Release IRQ mappings for PHYs in the KSZ device. + * @dev: Pointer to the KSZ device structure. + * + * Releases any IRQ mappings previously assigned to active PHYs in the KSZ + * switch by disposing of each mapped IRQ in the `user_mii_bus` structure. + */ static void ksz_irq_phy_free(struct ksz_device *dev) { struct dsa_switch *ds = dev->ds; int phy; - for (phy = 0; phy < KSZ_MAX_NUM_PORTS; phy++) + for (phy = 0; phy < PHY_MAX_ADDR; phy++) if (BIT(phy) & ds->phys_mii_mask) irq_dispose_mapping(ds->user_mii_bus->irq[phy]); } +/** + * ksz_parse_dt_phy_config - Parse and validate PHY configuration from DT + * @dev: pointer to the KSZ device structure + * @bus: pointer to the MII bus structure + * @mdio_np: pointer to the MDIO node in the device tree + * + * This function parses and validates PHY configurations for each user port + * defined in the device tree for a KSZ switch device. It verifies that the + * `phy-handle` properties are correctly set and that the internal PHYs match + * expected addresses and parent nodes. Sets up the PHY mask in the MII bus if + * all validations pass. Logs error messages for any mismatches or missing data. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int ksz_parse_dt_phy_config(struct ksz_device *dev, struct mii_bus *bus, + struct device_node *mdio_np) +{ + struct device_node *phy_node, *phy_parent_node; + bool phys_are_valid = true; + struct dsa_port *dp; + u32 phy_addr; + int ret; + + dsa_switch_for_each_user_port(dp, dev->ds) { + if (!dev->info->internal_phy[dp->index]) + continue; + + phy_node = of_parse_phandle(dp->dn, "phy-handle", 0); + if (!phy_node) { + dev_err(dev->dev, "failed to parse phy-handle for port %d.\n", + dp->index); + phys_are_valid = false; + continue; + } + + phy_parent_node = of_get_parent(phy_node); + if (!phy_parent_node) { + dev_err(dev->dev, "failed to get PHY-parent node for port %d\n", + dp->index); + phys_are_valid = false; + } else if (phy_parent_node != mdio_np) { + dev_err(dev->dev, "PHY-parent node mismatch for port %d, expected %pOF, got %pOF\n", + dp->index, mdio_np, phy_parent_node); + phys_are_valid = false; + } else { + ret = of_property_read_u32(phy_node, "reg", &phy_addr); + if (ret < 0) { + dev_err(dev->dev, "failed to read PHY address for port %d. Error %d\n", + dp->index, ret); + phys_are_valid = false; + } else if (phy_addr != dev->phy_addr_map[dp->index]) { + dev_err(dev->dev, "PHY address mismatch for port %d, expected 0x%x, got 0x%x\n", + dp->index, dev->phy_addr_map[dp->index], + phy_addr); + phys_are_valid = false; + } else { + bus->phy_mask |= BIT(phy_addr); + } + } + + of_node_put(phy_node); + of_node_put(phy_parent_node); + } + + if (!phys_are_valid) + return -EINVAL; + + return 0; +} + +/** + * ksz_mdio_register - Register and configure the MDIO bus for the KSZ device. + * @dev: Pointer to the KSZ device structure. + * + * This function sets up and registers an MDIO bus for the KSZ switch device, + * allowing access to its internal PHYs. If the device supports side MDIO, + * the function will configure the external MDIO controller specified by the + * "mdio-parent-bus" device tree property to directly manage internal PHYs. + * Otherwise, SPI or I2C access is set up for PHY access. + * + * Return: 0 on success, or a negative error code on failure. + */ static int ksz_mdio_register(struct ksz_device *dev) { + struct device_node *parent_bus_node; + struct mii_bus *parent_bus = NULL; struct dsa_switch *ds = dev->ds; struct device_node *mdio_np; struct mii_bus *bus; - int ret; + int ret, i; mdio_np = of_get_child_by_name(dev->dev->of_node, "mdio"); if (!mdio_np) return 0; + parent_bus_node = of_parse_phandle(mdio_np, "mdio-parent-bus", 0); + if (parent_bus_node && !dev->info->phy_side_mdio_supported) { + dev_err(dev->dev, "Side MDIO bus is not supported for this HW, ignoring 'mdio-parent-bus' property.\n"); + ret = -EINVAL; + + goto put_mdio_node; + } else if (parent_bus_node) { + parent_bus = of_mdio_find_bus(parent_bus_node); + if (!parent_bus) { + ret = -EPROBE_DEFER; + + goto put_mdio_node; + } + + dev->parent_mdio_bus = parent_bus; + } + bus = devm_mdiobus_alloc(ds->dev); if (!bus) { - of_node_put(mdio_np); - return -ENOMEM; + ret = -ENOMEM; + goto put_mdio_node; + } + + if (dev->dev_ops->mdio_bus_preinit) { + ret = dev->dev_ops->mdio_bus_preinit(dev, !!parent_bus); + if (ret) + goto put_mdio_node; + } + + if (dev->dev_ops->create_phy_addr_map) { + ret = dev->dev_ops->create_phy_addr_map(dev, !!parent_bus); + if (ret) + goto put_mdio_node; + } else { + for (i = 0; i < dev->info->port_cnt; i++) + dev->phy_addr_map[i] = i; } bus->priv = dev; - bus->read = ksz_sw_mdio_read; - bus->write = ksz_sw_mdio_write; - bus->name = "ksz user smi"; - snprintf(bus->id, MII_BUS_ID_SIZE, "SMI-%d", ds->index); + if (parent_bus) { + bus->read = ksz_parent_mdio_read; + bus->write = ksz_parent_mdio_write; + bus->name = "KSZ side MDIO"; + snprintf(bus->id, MII_BUS_ID_SIZE, "ksz-side-mdio-%d", + ds->index); + } else { + bus->read = ksz_sw_mdio_read; + bus->write = ksz_sw_mdio_write; + bus->name = "ksz user smi"; + if (ds->dst->index != 0) { + snprintf(bus->id, MII_BUS_ID_SIZE, "SMI-%d-%d", ds->dst->index, ds->index); + } else { + snprintf(bus->id, MII_BUS_ID_SIZE, "SMI-%d", ds->index); + } + } + + ret = ksz_parse_dt_phy_config(dev, bus, mdio_np); + if (ret) + goto put_mdio_node; + + ds->phys_mii_mask = bus->phy_mask; bus->parent = ds->dev; - bus->phy_mask = ~ds->phys_mii_mask; ds->user_mii_bus = bus; if (dev->irq > 0) { ret = ksz_irq_phy_setup(dev); - if (ret) { - of_node_put(mdio_np); - return ret; - } + if (ret) + goto put_mdio_node; } ret = devm_of_mdiobus_register(ds->dev, bus, mdio_np); @@ -2164,7 +2794,9 @@ static int ksz_mdio_register(struct ksz_device *dev) ksz_irq_phy_free(dev); } +put_mdio_node: of_node_put(mdio_np); + of_node_put(parent_bus_node); return ret; } @@ -2275,8 +2907,8 @@ static int ksz_irq_common_setup(struct ksz_device *dev, struct ksz_irq *kirq) kirq->dev = dev; kirq->masked = ~0; - kirq->domain = irq_domain_add_simple(dev->dev->of_node, kirq->nirqs, 0, - &ksz_irq_domain_ops, kirq); + kirq->domain = irq_domain_create_simple(dev_fwnode(dev->dev), kirq->nirqs, 0, + &ksz_irq_domain_ops, kirq); if (!kirq->domain) return -ENOMEM; @@ -2320,8 +2952,8 @@ static int ksz_pirq_setup(struct ksz_device *dev, u8 p) snprintf(pirq->name, sizeof(pirq->name), "port_irq-%d", p); pirq->irq_num = irq_find_mapping(dev->girq.domain, p); - if (pirq->irq_num < 0) - return pirq->irq_num; + if (!pirq->irq_num) + return -EINVAL; return ksz_irq_common_setup(dev, pirq); } @@ -2331,6 +2963,7 @@ static int ksz_parse_drive_strength(struct ksz_device *dev); static int ksz_setup(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; + u16 storm_mask, storm_rate; struct dsa_port *dp; struct ksz_port *p; const u16 *regs; @@ -2353,11 +2986,21 @@ static int ksz_setup(struct dsa_switch *ds) if (ret) return ret; + if (ksz_has_sgmii_port(dev) && dev->dev_ops->pcs_create) { + ret = dev->dev_ops->pcs_create(dev); + if (ret) + return ret; + } + /* set broadcast storm protection 10% rate */ + storm_mask = BROADCAST_STORM_RATE; + storm_rate = (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100; + if (ksz_is_ksz8463(dev)) { + storm_mask = swab16(storm_mask); + storm_rate = swab16(storm_rate); + } regmap_update_bits(ksz_regmap_16(dev), regs[S_BROADCAST_CTRL], - BROADCAST_STORM_RATE, - (BROADCAST_STORM_VALUE * - BROADCAST_STORM_PROT_RATE) / 100); + storm_mask, storm_rate); dev->dev_ops->config_cpu_port(ds); @@ -2395,18 +3038,23 @@ static int ksz_setup(struct dsa_switch *ds) dsa_switch_for_each_user_port(dp, dev->ds) { ret = ksz_pirq_setup(dev, dp->index); if (ret) - goto out_girq; + goto port_release; - ret = ksz_ptp_irq_setup(ds, dp->index); - if (ret) - goto out_pirq; + if (dev->info->ptp_capable) { + ret = ksz_ptp_irq_setup(ds, dp->index); + if (ret) + goto pirq_release; + } } } - ret = ksz_ptp_clock_register(ds); - if (ret) { - dev_err(dev->dev, "Failed to register PTP clock: %d\n", ret); - goto out_ptpirq; + if (dev->info->ptp_capable) { + ret = ksz_ptp_clock_register(ds); + if (ret) { + dev_err(dev->dev, "Failed to register PTP clock: %d\n", + ret); + goto port_release; + } } ret = ksz_mdio_register(dev); @@ -2426,18 +3074,18 @@ static int ksz_setup(struct dsa_switch *ds) return 0; out_ptp_clock_unregister: - ksz_ptp_clock_unregister(ds); -out_ptpirq: - if (dev->irq > 0) - dsa_switch_for_each_user_port(dp, dev->ds) - ksz_ptp_irq_free(ds, dp->index); -out_pirq: - if (dev->irq > 0) - dsa_switch_for_each_user_port(dp, dev->ds) + if (dev->info->ptp_capable) + ksz_ptp_clock_unregister(ds); +port_release: + if (dev->irq > 0) { + dsa_switch_for_each_user_port_continue_reverse(dp, dev->ds) { + if (dev->info->ptp_capable) + ksz_ptp_irq_free(ds, dp->index); +pirq_release: ksz_irq_free(&dev->ports[dp->index].pirq); -out_girq: - if (dev->irq > 0) + } ksz_irq_free(&dev->girq); + } return ret; } @@ -2447,11 +3095,13 @@ static void ksz_teardown(struct dsa_switch *ds) struct ksz_device *dev = ds->priv; struct dsa_port *dp; - ksz_ptp_clock_unregister(ds); + if (dev->info->ptp_capable) + ksz_ptp_clock_unregister(ds); if (dev->irq > 0) { dsa_switch_for_each_user_port(dp, dev->ds) { - ksz_ptp_irq_free(ds, dp->index); + if (dev->info->ptp_capable) + ksz_ptp_irq_free(ds, dp->index); ksz_irq_free(&dev->ports[dp->index].pirq); } @@ -2570,7 +3220,7 @@ static u32 ksz_get_phy_flags(struct dsa_switch *ds, int port) struct ksz_device *dev = ds->priv; switch (dev->chip_id) { - case KSZ8830_CHIP_ID: + case KSZ88X3_CHIP_ID: /* Silicon Errata Sheet (DS80000830A): * Port 1 does not work with LinkMD Cable-Testing. * Port 1 does not respond to received PAUSE control frames. @@ -2578,18 +3228,6 @@ static u32 ksz_get_phy_flags(struct dsa_switch *ds, int port) if (!port) return MICREL_KSZ8_P1_ERRATA; break; - case KSZ9477_CHIP_ID: - /* KSZ9477 Errata DS80000754C - * - * Module 4: Energy Efficient Ethernet (EEE) feature select must - * be manually disabled - * The EEE feature is enabled by default, but it is not fully - * operational. It must be manually disabled through register - * controls. If not disabled, the PHY ports can auto-negotiate - * to enable EEE, and this feature can cause link drops when - * linked to another device supporting EEE. - */ - return MICREL_NO_EEE; } return 0; @@ -2839,6 +3477,7 @@ static void ksz_port_teardown(struct dsa_switch *ds, int port) case KSZ9893_CHIP_ID: case KSZ9896_CHIP_ID: case KSZ9897_CHIP_ID: + case LAN9646_CHIP_ID: if (dsa_is_user_port(ds, port)) ksz9477_port_acl_free(dev, port); } @@ -2882,12 +3521,11 @@ static enum dsa_tag_protocol ksz_get_tag_protocol(struct dsa_switch *ds, struct ksz_device *dev = ds->priv; enum dsa_tag_protocol proto = DSA_TAG_PROTO_NONE; - if (dev->chip_id == KSZ8795_CHIP_ID || - dev->chip_id == KSZ8794_CHIP_ID || - dev->chip_id == KSZ8765_CHIP_ID) + if (ksz_is_ksz87xx(dev) || ksz_is_8895_family(dev)) proto = DSA_TAG_PROTO_KSZ8795; - if (dev->chip_id == KSZ8830_CHIP_ID || + if (dev->chip_id == KSZ88X3_CHIP_ID || + dev->chip_id == KSZ8463_CHIP_ID || dev->chip_id == KSZ8563_CHIP_ID || dev->chip_id == KSZ9893_CHIP_ID || dev->chip_id == KSZ9563_CHIP_ID) @@ -2897,7 +3535,8 @@ static enum dsa_tag_protocol ksz_get_tag_protocol(struct dsa_switch *ds, dev->chip_id == KSZ9477_CHIP_ID || dev->chip_id == KSZ9896_CHIP_ID || dev->chip_id == KSZ9897_CHIP_ID || - dev->chip_id == KSZ9567_CHIP_ID) + dev->chip_id == KSZ9567_CHIP_ID || + dev->chip_id == LAN9646_CHIP_ID) proto = DSA_TAG_PROTO_KSZ9477; if (is_lan937x(dev)) @@ -2999,7 +3638,10 @@ static int ksz_max_mtu(struct dsa_switch *ds, int port) case KSZ8794_CHIP_ID: case KSZ8765_CHIP_ID: return KSZ8795_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; - case KSZ8830_CHIP_ID: + case KSZ8463_CHIP_ID: + case KSZ88X3_CHIP_ID: + case KSZ8864_CHIP_ID: + case KSZ8895_CHIP_ID: return KSZ8863_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; case KSZ8563_CHIP_ID: case KSZ8567_CHIP_ID: @@ -3014,63 +3656,74 @@ static int ksz_max_mtu(struct dsa_switch *ds, int port) case LAN9372_CHIP_ID: case LAN9373_CHIP_ID: case LAN9374_CHIP_ID: + case LAN9646_CHIP_ID: return KSZ9477_MAX_FRAME_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; } return -EOPNOTSUPP; } -static int ksz_validate_eee(struct dsa_switch *ds, int port) +/** + * ksz_support_eee - Determine Energy Efficient Ethernet (EEE) support for a + * port + * @ds: Pointer to the DSA switch structure + * @port: Port number to check + * + * This function also documents devices where EEE was initially advertised but + * later withdrawn due to reliability issues, as described in official errata + * documents. These devices are explicitly listed to record known limitations, + * even if there is no technical necessity for runtime checks. + * + * Returns: true if the internal PHY on the given port supports fully + * operational EEE, false otherwise. + */ +static bool ksz_support_eee(struct dsa_switch *ds, int port) { struct ksz_device *dev = ds->priv; if (!dev->info->internal_phy[port]) - return -EOPNOTSUPP; + return false; switch (dev->chip_id) { case KSZ8563_CHIP_ID: + case KSZ9563_CHIP_ID: + case KSZ9893_CHIP_ID: + return true; case KSZ8567_CHIP_ID: + /* KSZ8567R Errata DS80000752C Module 4 */ + case KSZ8765_CHIP_ID: + case KSZ8794_CHIP_ID: + case KSZ8795_CHIP_ID: + /* KSZ879x/KSZ877x/KSZ876x Errata DS80000687C Module 2 */ case KSZ9477_CHIP_ID: - case KSZ9563_CHIP_ID: + /* KSZ9477S Errata DS80000754A Module 4 */ case KSZ9567_CHIP_ID: - case KSZ9893_CHIP_ID: + /* KSZ9567S Errata DS80000756A Module 4 */ case KSZ9896_CHIP_ID: + /* KSZ9896C Errata DS80000757A Module 3 */ case KSZ9897_CHIP_ID: - return 0; + case LAN9646_CHIP_ID: + /* KSZ9897R Errata DS80000758C Module 4 */ + /* Energy Efficient Ethernet (EEE) feature select must be + * manually disabled + * The EEE feature is enabled by default, but it is not fully + * operational. It must be manually disabled through register + * controls. If not disabled, the PHY ports can auto-negotiate + * to enable EEE, and this feature can cause link drops when + * linked to another device supporting EEE. + * + * The same item appears in the errata for all switches above. + */ + break; } - return -EOPNOTSUPP; -} - -static int ksz_get_mac_eee(struct dsa_switch *ds, int port, - struct ethtool_keee *e) -{ - int ret; - - ret = ksz_validate_eee(ds, port); - if (ret) - return ret; - - /* There is no documented control of Tx LPI configuration. */ - e->tx_lpi_enabled = true; - - /* There is no documented control of Tx LPI timer. According to tests - * Tx LPI timer seems to be set by default to minimal value. - */ - e->tx_lpi_timer = 0; - - return 0; + return false; } static int ksz_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_keee *e) { struct ksz_device *dev = ds->priv; - int ret; - - ret = ksz_validate_eee(ds, port); - if (ret) - return ret; if (!e->tx_lpi_enabled) { dev_err(dev->dev, "Disabling EEE Tx LPI is not supported\n"); @@ -3169,7 +3822,7 @@ phy_interface_t ksz_get_xmii(struct ksz_device *dev, int port, bool gbit) return interface; } -static void ksz8830_phylink_mac_config(struct phylink_config *config, +static void ksz88x3_phylink_mac_config(struct phylink_config *config, unsigned int mode, const struct phylink_link_state *state) { @@ -3191,6 +3844,10 @@ static void ksz_phylink_mac_config(struct phylink_config *config, if (dev->info->internal_phy[port]) return; + /* No need to configure XMII control register when using SGMII. */ + if (ksz_is_sgmii_port(dev, port)) + return; + if (phylink_autoneg_inband(mode)) { dev_err(dev->dev, "In-band AN not supported!\n"); return; @@ -3336,6 +3993,9 @@ static int ksz_switch_detect(struct ksz_device *dev) id2 = FIELD_GET(SW_CHIP_ID_M, id16); switch (id1) { + case KSZ84_FAMILY_ID: + dev->chip_id = KSZ8463_CHIP_ID; + break; case KSZ87_FAMILY_ID: if (id2 == KSZ87_CHIP_ID_95) { u8 val; @@ -3353,10 +4013,22 @@ static int ksz_switch_detect(struct ksz_device *dev) break; case KSZ88_FAMILY_ID: if (id2 == KSZ88_CHIP_ID_63) - dev->chip_id = KSZ8830_CHIP_ID; + dev->chip_id = KSZ88X3_CHIP_ID; else return -ENODEV; break; + case KSZ8895_FAMILY_ID: + if (id2 == KSZ8895_CHIP_ID_95 || + id2 == KSZ8895_CHIP_ID_95R) + dev->chip_id = KSZ8895_CHIP_ID; + else + return -ENODEV; + ret = ksz_read8(dev, REG_KSZ8864_CHIP_ID, &id4); + if (ret) + return ret; + if (id4 & SW_KSZ8864) + dev->chip_id = KSZ8864_CHIP_ID; + break; default: ret = ksz_read32(dev, REG_CHIP_ID0, &id32); if (ret) @@ -3376,7 +4048,10 @@ static int ksz_switch_detect(struct ksz_device *dev) case LAN9372_CHIP_ID: case LAN9373_CHIP_ID: case LAN9374_CHIP_ID: - dev->chip_id = id32; + + /* LAN9646 does not have its own chip id. */ + if (dev->chip_id != LAN9646_CHIP_ID) + dev->chip_id = id32; break; case KSZ9893_CHIP_ID: ret = ksz_read8(dev, REG_CHIP_ID4, @@ -3415,6 +4090,7 @@ static int ksz_cls_flower_add(struct dsa_switch *ds, int port, case KSZ9893_CHIP_ID: case KSZ9896_CHIP_ID: case KSZ9897_CHIP_ID: + case LAN9646_CHIP_ID: return ksz9477_cls_flower_add(ds, port, cls, ingress); } @@ -3435,6 +4111,7 @@ static int ksz_cls_flower_del(struct dsa_switch *ds, int port, case KSZ9893_CHIP_ID: case KSZ9896_CHIP_ID: case KSZ9897_CHIP_ID: + case LAN9646_CHIP_ID: return ksz9477_cls_flower_del(ds, port, cls, ingress); } @@ -3560,6 +4237,104 @@ static int ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p, return p->bands - 1 - band; } +static u8 ksz8463_tc_ctrl(int port, int queue) +{ + u8 reg; + + reg = 0xC8 + port * 4; + reg += ((3 - queue) / 2) * 2; + reg++; + reg -= (queue & 1); + return reg; +} + +/** + * ksz88x3_tc_ets_add - Configure ETS (Enhanced Transmission Selection) + * for a port on KSZ88x3 switch + * @dev: Pointer to the KSZ switch device structure + * @port: Port number to configure + * @p: Pointer to offload replace parameters describing ETS bands and mapping + * + * The KSZ88x3 supports two scheduling modes: Strict Priority and + * Weighted Fair Queuing (WFQ). Both modes have fixed behavior: + * - No configurable queue-to-priority mapping + * - No weight adjustment in WFQ mode + * + * This function configures the switch to use strict priority mode by + * clearing the WFQ enable bit for all queues associated with ETS bands. + * If strict priority is not explicitly requested, the switch will default + * to WFQ mode. + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz88x3_tc_ets_add(struct ksz_device *dev, int port, + struct tc_ets_qopt_offload_replace_params *p) +{ + int ret, band; + + /* Only strict priority mode is supported for now. + * WFQ is implicitly enabled when strict mode is disabled. + */ + for (band = 0; band < p->bands; band++) { + int queue = ksz_ets_band_to_queue(p, band); + u8 reg; + + /* Calculate TXQ Split Control register address for this + * port/queue + */ + reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); + if (ksz_is_ksz8463(dev)) + reg = ksz8463_tc_ctrl(port, queue); + + /* Clear WFQ enable bit to select strict priority scheduling */ + ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, 0); + if (ret) + return ret; + } + + return 0; +} + +/** + * ksz88x3_tc_ets_del - Reset ETS (Enhanced Transmission Selection) config + * for a port on KSZ88x3 switch + * @dev: Pointer to the KSZ switch device structure + * @port: Port number to reset + * + * The KSZ88x3 supports only fixed scheduling modes: Strict Priority or + * Weighted Fair Queuing (WFQ), with no reconfiguration of weights or + * queue mapping. This function resets the port’s scheduling mode to + * the default, which is WFQ, by enabling the WFQ bit for all queues. + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz88x3_tc_ets_del(struct ksz_device *dev, int port) +{ + int ret, queue; + + /* Iterate over all transmit queues for this port */ + for (queue = 0; queue < dev->info->num_tx_queues; queue++) { + u8 reg; + + /* Calculate TXQ Split Control register address for this + * port/queue + */ + reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); + if (ksz_is_ksz8463(dev)) + reg = ksz8463_tc_ctrl(port, queue); + + /* Set WFQ enable bit to revert back to default scheduling + * mode + */ + ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, + KSZ8873_TXQ_WFQ_ENABLE); + if (ret) + return ret; + } + + return 0; +} + static int ksz_queue_set_strict(struct ksz_device *dev, int port, int queue) { int ret; @@ -3641,6 +4416,7 @@ static int ksz_tc_ets_del(struct ksz_device *dev, int port) for (queue = 0; queue < dev->info->num_tx_queues; queue++) { ret = ksz_queue_set_wrr(dev, port, queue, KSZ9477_DEFAULT_WRR_WEIGHT); + if (ret) return ret; } @@ -3693,7 +4469,7 @@ static int ksz_tc_setup_qdisc_ets(struct dsa_switch *ds, int port, struct ksz_device *dev = ds->priv; int ret; - if (is_ksz8(dev)) + if (is_ksz8(dev) && !(ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev))) return -EOPNOTSUPP; if (qopt->parent != TC_H_ROOT) { @@ -3707,9 +4483,16 @@ static int ksz_tc_setup_qdisc_ets(struct dsa_switch *ds, int port, if (ret) return ret; - return ksz_tc_ets_add(dev, port, &qopt->replace_params); + if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) + return ksz88x3_tc_ets_add(dev, port, + &qopt->replace_params); + else + return ksz_tc_ets_add(dev, port, &qopt->replace_params); case TC_ETS_DESTROY: - return ksz_tc_ets_del(dev, port); + if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) + return ksz88x3_tc_ets_del(dev, port); + else + return ksz_tc_ets_del(dev, port); case TC_ETS_STATS: case TC_ETS_GRAFT: return -EOPNOTSUPP; @@ -3731,24 +4514,214 @@ static int ksz_setup_tc(struct dsa_switch *ds, int port, } } +/** + * ksz_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. + */ +int ksz_handle_wake_reason(struct ksz_device *dev, int port) +{ + const struct ksz_dev_ops *ops = dev->dev_ops; + const u16 *regs = dev->info->regs; + u8 pme_status; + int ret; + + ret = ops->pme_pread8(dev, port, regs[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 ops->pme_pwrite8(dev, port, regs[REG_PORT_PME_STATUS], + pme_status); +} + +/** + * ksz_get_wol - Get Wake-on-LAN settings for a specified port. + * @ds: The dsa_switch structure. + * @port: The port number. + * @wol: Pointer to ethtool Wake-on-LAN settings structure. + * + * This function checks the device PME wakeup_source flag and chip_id. + * If enabled and supported, it sets the supported and active WoL + * flags. + */ static void ksz_get_wol(struct dsa_switch *ds, int port, struct ethtool_wolinfo *wol) { struct ksz_device *dev = ds->priv; + const u16 *regs = dev->info->regs; + u8 pme_ctrl; + int ret; + + if (!is_ksz9477(dev) && !ksz_is_ksz87xx(dev)) + return; + + if (!dev->wakeup_source) + return; + + wol->supported = WAKE_PHY; - if (dev->dev_ops->get_wol) - dev->dev_ops->get_wol(dev, port, wol); + /* 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 = dev->dev_ops->pme_pread8(dev, port, regs[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; } +/** + * ksz_set_wol - Set Wake-on-LAN settings for a specified port. + * @ds: The dsa_switch 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 and supported, 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. + */ static int ksz_set_wol(struct dsa_switch *ds, int port, struct ethtool_wolinfo *wol) { + u8 pme_ctrl = 0, pme_ctrl_old = 0; struct ksz_device *dev = ds->priv; + const u16 *regs = dev->info->regs; + bool magic_switched_off; + bool magic_switched_on; + int ret; - if (dev->dev_ops->set_wol) - return dev->dev_ops->set_wol(dev, port, wol); + if (wol->wolopts & ~(WAKE_PHY | WAKE_MAGIC)) + return -EINVAL; - return -EOPNOTSUPP; + if (!is_ksz9477(dev) && !ksz_is_ksz87xx(dev)) + return -EOPNOTSUPP; + + if (!dev->wakeup_source) + return -EOPNOTSUPP; + + ret = ksz_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 = dev->dev_ops->pme_pread8(dev, port, regs[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 = dev->dev_ops->pme_pwrite8(dev, port, regs[REG_PORT_PME_CTRL], + pme_ctrl); + if (ret) { + if (magic_switched_on) + ksz_switch_macaddr_put(dev->ds); + return ret; + } + + return 0; +} + +/** + * ksz_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. + */ +static void ksz_wol_pre_shutdown(struct ksz_device *dev, bool *wol_enabled) +{ + const struct ksz_dev_ops *ops = dev->dev_ops; + const u16 *regs = dev->info->regs; + u8 pme_pin_en = PME_ENABLE; + struct dsa_port *dp; + int ret; + + *wol_enabled = false; + + if (!is_ksz9477(dev) && !ksz_is_ksz87xx(dev)) + return; + + if (!dev->wakeup_source) + return; + + dsa_switch_for_each_user_port(dp, dev->ds) { + u8 pme_ctrl = 0; + + ret = ops->pme_pread8(dev, dp->index, + regs[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. + */ + ksz_handle_wake_reason(dev, dp->index); + } + + /* Now we are save to enable PME pin. */ + if (*wol_enabled) { + if (dev->pme_active_high) + pme_pin_en |= PME_POLARITY; + ops->pme_write8(dev, regs[REG_SW_PME_CTRL], pme_pin_en); + if (ksz_is_ksz87xx(dev)) + ksz_write8(dev, KSZ87XX_REG_INT_EN, KSZ87XX_INT_PME_MASK); + } } static int ksz_port_set_mac_address(struct dsa_switch *ds, int port, @@ -3764,6 +4737,11 @@ static int ksz_port_set_mac_address(struct dsa_switch *ds, int port, return -EBUSY; } + /* Need to initialize variable as the code to fill in settings may + * not be executed. + */ + wol.wolopts = 0; + ksz_get_wol(ds, dp->index, &wol); if (wol.wolopts & WAKE_MAGIC) { dev_err(ds->dev, @@ -3854,7 +4832,16 @@ int ksz_switch_macaddr_get(struct dsa_switch *ds, int port, /* 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 (ksz_is_ksz8463(dev)) { + u16 addr16 = ((u16)addr[i] << 8) | addr[i + 1]; + + ret = ksz_write16(dev, regs[REG_SW_MAC_ADDR] + i, + addr16); + i++; + } else { + ret = ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i, + addr[i]); + } if (ret) goto macaddr_drop; } @@ -3956,6 +4943,23 @@ static int ksz_hsr_leave(struct dsa_switch *ds, int port, return 0; } +static int ksz_suspend(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + + cancel_delayed_work_sync(&dev->mib_read); + return 0; +} + +static int ksz_resume(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + + if (dev->mib_read_interval) + schedule_delayed_work(&dev->mib_read, dev->mib_read_interval); + 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, @@ -3996,6 +5000,8 @@ static const struct dsa_switch_ops ksz_switch_ops = { .port_max_mtu = ksz_max_mtu, .get_wol = ksz_get_wol, .set_wol = ksz_set_wol, + .suspend = ksz_suspend, + .resume = ksz_resume, .get_ts_info = ksz_get_ts_info, .port_hwtstamp_get = ksz_hwtstamp_get, .port_hwtstamp_set = ksz_hwtstamp_set, @@ -4004,7 +5010,7 @@ static const struct dsa_switch_ops ksz_switch_ops = { .cls_flower_add = ksz_cls_flower_add, .cls_flower_del = ksz_cls_flower_del, .port_setup_tc = ksz_setup_tc, - .get_mac_eee = ksz_get_mac_eee, + .support_eee = ksz_support_eee, .set_mac_eee = ksz_set_mac_eee, .port_get_default_prio = ksz_port_get_default_prio, .port_set_default_prio = ksz_port_set_default_prio, @@ -4056,8 +5062,7 @@ 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); + ksz_wol_pre_shutdown(dev, &wol_enabled); if (dev->dev_ops->reset && !wol_enabled) dev->dev_ops->reset(dev); @@ -4221,24 +5226,24 @@ static int ksz9477_drive_strength_write(struct ksz_device *dev, } /** - * ksz8830_drive_strength_write() - Set the drive strength configuration for - * KSZ8830 compatible chip variants. + * ksz88x3_drive_strength_write() - Set the drive strength configuration for + * KSZ8863 compatible chip variants. * @dev: ksz device * @props: Array of drive strength properties to be set * @num_props: Number of properties in the array * - * This function applies the specified drive strength settings to KSZ8830 chip + * This function applies the specified drive strength settings to KSZ88X3 chip * variants (KSZ8873, KSZ8863). * It ensures the configurations align with what the chip variant supports and * warns or errors out on unsupported settings. * * Return: 0 on success, error code otherwise */ -static int ksz8830_drive_strength_write(struct ksz_device *dev, +static int ksz88x3_drive_strength_write(struct ksz_device *dev, struct ksz_driver_strength_prop *props, int num_props) { - size_t array_size = ARRAY_SIZE(ksz8830_drive_strengths); + size_t array_size = ARRAY_SIZE(ksz88x3_drive_strengths); int microamp; int i, ret; @@ -4251,10 +5256,10 @@ static int ksz8830_drive_strength_write(struct ksz_device *dev, } microamp = props[KSZ_DRIVER_STRENGTH_IO].value; - ret = ksz_drive_strength_to_reg(ksz8830_drive_strengths, array_size, + ret = ksz_drive_strength_to_reg(ksz88x3_drive_strengths, array_size, microamp); if (ret < 0) { - ksz_drive_strength_error(dev, ksz8830_drive_strengths, + ksz_drive_strength_error(dev, ksz88x3_drive_strengths, array_size, microamp); return ret; } @@ -4314,8 +5319,8 @@ static int ksz_parse_drive_strength(struct ksz_device *dev) return 0; switch (dev->chip_id) { - case KSZ8830_CHIP_ID: - return ksz8830_drive_strength_write(dev, of_props, + case KSZ88X3_CHIP_ID: + return ksz88x3_drive_strength_write(dev, of_props, ARRAY_SIZE(of_props)); case KSZ8795_CHIP_ID: case KSZ8794_CHIP_ID: @@ -4328,6 +5333,7 @@ static int ksz_parse_drive_strength(struct ksz_device *dev) case KSZ9893_CHIP_ID: case KSZ9896_CHIP_ID: case KSZ9897_CHIP_ID: + case LAN9646_CHIP_ID: return ksz9477_drive_strength_write(dev, of_props, ARRAY_SIZE(of_props)); default: @@ -4343,10 +5349,42 @@ static int ksz_parse_drive_strength(struct ksz_device *dev) return 0; } +static int ksz8463_configure_straps_spi(struct ksz_device *dev) +{ + struct pinctrl *pinctrl; + struct gpio_desc *rxd0; + struct gpio_desc *rxd1; + + rxd0 = devm_gpiod_get_index_optional(dev->dev, "straps-rxd", 0, GPIOD_OUT_LOW); + if (IS_ERR(rxd0)) + return PTR_ERR(rxd0); + + rxd1 = devm_gpiod_get_index_optional(dev->dev, "straps-rxd", 1, GPIOD_OUT_HIGH); + if (IS_ERR(rxd1)) + return PTR_ERR(rxd1); + + if (!rxd0 && !rxd1) + return 0; + + if ((rxd0 && !rxd1) || (rxd1 && !rxd0)) + return -EINVAL; + + pinctrl = devm_pinctrl_get_select(dev->dev, "reset"); + if (IS_ERR(pinctrl)) + return PTR_ERR(pinctrl); + + return 0; +} + +static int ksz8463_release_straps_spi(struct ksz_device *dev) +{ + return pinctrl_select_default_state(dev->dev); +} + int ksz_switch_register(struct ksz_device *dev) { const struct ksz_chip_data *info; - struct device_node *port, *ports; + struct device_node *ports; phy_interface_t interface; unsigned int port_num; int ret; @@ -4358,10 +5396,22 @@ int ksz_switch_register(struct ksz_device *dev) return PTR_ERR(dev->reset_gpio); if (dev->reset_gpio) { + if (of_device_is_compatible(dev->dev->of_node, "microchip,ksz8463")) { + ret = ksz8463_configure_straps_spi(dev); + if (ret) + return ret; + } + gpiod_set_value_cansleep(dev->reset_gpio, 1); usleep_range(10000, 12000); gpiod_set_value_cansleep(dev->reset_gpio, 0); msleep(100); + + if (of_device_is_compatible(dev->dev->of_node, "microchip,ksz8463")) { + ret = ksz8463_release_straps_spi(dev); + if (ret) + return ret; + } } mutex_init(&dev->dev_mutex); @@ -4432,12 +5482,11 @@ int ksz_switch_register(struct ksz_device *dev) if (!ports) ports = of_get_child_by_name(dev->dev->of_node, "ports"); if (ports) { - for_each_available_child_of_node(ports, port) { + for_each_available_child_of_node_scoped(ports, port) { if (of_property_read_u32(port, "reg", &port_num)) continue; if (!(dev->port_mask & BIT(port_num))) { - of_node_put(port); of_node_put(ports); return -EINVAL; } @@ -4445,6 +5494,9 @@ int ksz_switch_register(struct ksz_device *dev) &dev->ports[port_num].interface); ksz_parse_rgmii_delay(dev, port_num, port); + dev->ports[port_num].fiber = + of_property_read_bool(port, + "micrel,fiber-mode"); } of_node_put(ports); } @@ -4459,6 +5511,8 @@ int ksz_switch_register(struct ksz_device *dev) dev->wakeup_source = of_property_read_bool(dev->dev->of_node, "wakeup-source"); + dev->pme_active_high = of_property_read_bool(dev->dev->of_node, + "microchip,pme-active-high"); } ret = dsa_register_switch(dev->ds); @@ -4494,6 +5548,24 @@ void ksz_switch_remove(struct ksz_device *dev) } EXPORT_SYMBOL(ksz_switch_remove); +#ifdef CONFIG_PM_SLEEP +int ksz_switch_suspend(struct device *dev) +{ + struct ksz_device *priv = dev_get_drvdata(dev); + + return dsa_switch_suspend(priv->ds); +} +EXPORT_SYMBOL(ksz_switch_suspend); + +int ksz_switch_resume(struct device *dev) +{ + struct ksz_device *priv = dev_get_drvdata(dev); + + return dsa_switch_resume(priv->ds); +} +EXPORT_SYMBOL(ksz_switch_resume); +#endif + MODULE_AUTHOR("Woojung Huh <Woojung.Huh@microchip.com>"); MODULE_DESCRIPTION("Microchip KSZ Series Switch DSA Driver"); MODULE_LICENSE("GPL"); |
