diff options
Diffstat (limited to 'drivers/net/dsa/microchip')
21 files changed, 2737 insertions, 650 deletions
diff --git a/drivers/net/dsa/microchip/Kconfig b/drivers/net/dsa/microchip/Kconfig index 394ca8678d2b..12a86585a77f 100644 --- a/drivers/net/dsa/microchip/Kconfig +++ b/drivers/net/dsa/microchip/Kconfig @@ -1,12 +1,17 @@ # SPDX-License-Identifier: GPL-2.0-only menuconfig NET_DSA_MICROCHIP_KSZ_COMMON - tristate "Microchip KSZ8795/KSZ9477/LAN937x series switch support" + tristate "Microchip KSZ8XXX/KSZ9XXX/LAN937X series switch support" depends on NET_DSA select NET_DSA_TAG_KSZ select NET_DSA_TAG_NONE + select NET_IEEE8021Q_HELPERS + select DCB help - This driver adds support for Microchip KSZ9477 series switch and - KSZ8795/KSZ88x3 switch chips. + This driver adds support for Microchip KSZ8, KSZ9 and + LAN937X series switch chips, being KSZ8863/8873, + KSZ8895/8864, KSZ8794/8795/8765, + KSZ9477/9897/9896/9567/8567, KSZ9893/9563/8563 and + LAN9370/9371/9372/9373/9374. config NET_DSA_MICROCHIP_KSZ9477_I2C tristate "KSZ series I2C connected switch driver" diff --git a/drivers/net/dsa/microchip/Makefile b/drivers/net/dsa/microchip/Makefile index 49459a50dbc8..9347cfb3d0b5 100644 --- a/drivers/net/dsa/microchip/Makefile +++ b/drivers/net/dsa/microchip/Makefile @@ -1,8 +1,8 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ_COMMON) += ksz_switch.o -ksz_switch-objs := ksz_common.o +ksz_switch-objs := ksz_common.o ksz_dcb.o ksz_switch-objs += ksz9477.o ksz9477_acl.o ksz9477_tc_flower.o -ksz_switch-objs += ksz8795.o +ksz_switch-objs += ksz8.o ksz_switch-objs += lan937x_main.o ifdef CONFIG_NET_DSA_MICROCHIP_KSZ_PTP diff --git a/drivers/net/dsa/microchip/ksz8795.c b/drivers/net/dsa/microchip/ksz8.c index 14923535ca7e..da7110d67558 100644 --- a/drivers/net/dsa/microchip/ksz8795.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -1,6 +1,13 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Microchip KSZ8795 switch driver + * Microchip KSZ8XXX series switch driver + * + * It supports the following switches: + * - KSZ8863, KSZ8873 aka KSZ88X3 + * - KSZ8895, KSZ8864 aka KSZ8895 family + * - KSZ8794, KSZ8795, KSZ8765 aka KSZ87XX + * Note that it does NOT support: + * - KSZ8563, KSZ8567 - see KSZ9477 driver * * Copyright (C) 2017 Microchip Technology Inc. * Tristram Ha <Tristram.Ha@microchip.com> @@ -23,7 +30,7 @@ #include <linux/phylink.h> #include "ksz_common.h" -#include "ksz8795_reg.h" +#include "ksz8_reg.h" #include "ksz8.h" static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set) @@ -38,6 +45,20 @@ static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits, bits, set ? bits : 0); } +/** + * ksz8_ind_write8 - EEE/ACL/PME indirect register write + * @dev: The device structure. + * @table: Function & table select, register 110. + * @addr: Indirect access control, register 111. + * @data: The data to be written. + * + * This function performs an indirect register write for EEE, ACL or + * PME switch functionalities. Both 8-bit registers 110 and 111 are + * written at once with ksz_write16, using the serial multiple write + * functionality. + * + * Return: 0 on success, or an error code on failure. + */ static int ksz8_ind_write8(struct ksz_device *dev, u8 table, u16 addr, u8 data) { const u16 *regs; @@ -58,6 +79,59 @@ static int ksz8_ind_write8(struct ksz_device *dev, u8 table, u16 addr, u8 data) return ret; } +/** + * ksz8_ind_read8 - EEE/ACL/PME indirect register read + * @dev: The device structure. + * @table: Function & table select, register 110. + * @addr: Indirect access control, register 111. + * @val: The value read. + * + * This function performs an indirect register read for EEE, ACL or + * PME switch functionalities. Both 8-bit registers 110 and 111 are + * written at once with ksz_write16, using the serial multiple write + * functionality. + * + * Return: 0 on success, or an error code on failure. + */ +static int ksz8_ind_read8(struct ksz_device *dev, u8 table, u16 addr, u8 *val) +{ + const u16 *regs; + u16 ctrl_addr; + int ret = 0; + + regs = dev->info->regs; + + mutex_lock(&dev->alu_mutex); + + ctrl_addr = IND_ACC_TABLE(table | TABLE_READ) | addr; + ret = ksz_write16(dev, regs[REG_IND_CTRL_0], ctrl_addr); + if (!ret) + ret = ksz_read8(dev, regs[REG_IND_BYTE], val); + + mutex_unlock(&dev->alu_mutex); + + return ret; +} + +int ksz8_pme_write8(struct ksz_device *dev, u32 reg, u8 value) +{ + return ksz8_ind_write8(dev, (u8)(reg >> 8), (u8)(reg), value); +} + +int ksz8_pme_pread8(struct ksz_device *dev, int port, int offset, u8 *data) +{ + u8 table = (u8)(offset >> 8 | (port + 1)); + + return ksz8_ind_read8(dev, table, (u8)(offset), data); +} + +int ksz8_pme_pwrite8(struct ksz_device *dev, int port, int offset, u8 data) +{ + u8 table = (u8)(offset >> 8 | (port + 1)); + + return ksz8_ind_write8(dev, table, (u8)(offset), data); +} + int ksz8_reset_switch(struct ksz_device *dev) { if (ksz_is_ksz88x3(dev)) { @@ -120,44 +194,80 @@ int ksz8_change_mtu(struct ksz_device *dev, int port, int mtu) case KSZ8794_CHIP_ID: case KSZ8765_CHIP_ID: return ksz8795_change_mtu(dev, frame_size); - case KSZ8830_CHIP_ID: + case KSZ88X3_CHIP_ID: + case KSZ8864_CHIP_ID: + case KSZ8895_CHIP_ID: return ksz8863_change_mtu(dev, frame_size); } return -EOPNOTSUPP; } -static void ksz8795_set_prio_queue(struct ksz_device *dev, int port, int queue) +static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues) { - u8 hi, lo; + u8 mask_4q, mask_2q; + u8 reg_4q, reg_2q; + u8 data_4q = 0; + u8 data_2q = 0; + int ret; - /* Number of queues can only be 1, 2, or 4. */ - switch (queue) { - case 4: - case 3: - queue = PORT_QUEUE_SPLIT_4; - break; - case 2: - queue = PORT_QUEUE_SPLIT_2; - break; - default: - queue = PORT_QUEUE_SPLIT_1; + if (ksz_is_ksz88x3(dev)) { + mask_4q = KSZ8873_PORT_4QUEUE_SPLIT_EN; + mask_2q = KSZ8873_PORT_2QUEUE_SPLIT_EN; + reg_4q = REG_PORT_CTRL_0; + reg_2q = REG_PORT_CTRL_2; + + /* KSZ8795 family switches have Weighted Fair Queueing (WFQ) + * enabled by default. Enable it for KSZ8873 family switches + * too. Default value for KSZ8873 family is strict priority, + * which should be enabled by using TC_SETUP_QDISC_ETS, not + * by default. + */ + ret = ksz_rmw8(dev, REG_SW_CTRL_3, WEIGHTED_FAIR_QUEUE_ENABLE, + WEIGHTED_FAIR_QUEUE_ENABLE); + if (ret) + return ret; + } else { + mask_4q = KSZ8795_PORT_4QUEUE_SPLIT_EN; + mask_2q = KSZ8795_PORT_2QUEUE_SPLIT_EN; + reg_4q = REG_PORT_CTRL_13; + reg_2q = REG_PORT_CTRL_0; + + /* TODO: this is legacy from initial KSZ8795 driver, should be + * moved to appropriate place in the future. + */ + ret = ksz_rmw8(dev, REG_SW_CTRL_19, + SW_OUT_RATE_LIMIT_QUEUE_BASED, + SW_OUT_RATE_LIMIT_QUEUE_BASED); + if (ret) + return ret; + } + + if (queues == 4) + data_4q = mask_4q; + else if (queues == 2) + data_2q = mask_2q; + + ret = ksz_prmw8(dev, port, reg_4q, mask_4q, data_4q); + if (ret) + return ret; + + return ksz_prmw8(dev, port, reg_2q, mask_2q, data_2q); +} + +int ksz8_all_queues_split(struct ksz_device *dev, int queues) +{ + struct dsa_switch *ds = dev->ds; + const struct dsa_port *dp; + + dsa_switch_for_each_port(dp, ds) { + int ret = ksz8_port_queue_split(dev, dp->index, queues); + + if (ret) + return ret; } - ksz_pread8(dev, port, REG_PORT_CTRL_0, &lo); - ksz_pread8(dev, port, P_DROP_TAG_CTRL, &hi); - lo &= ~PORT_QUEUE_SPLIT_L; - if (queue & PORT_QUEUE_SPLIT_2) - lo |= PORT_QUEUE_SPLIT_L; - hi &= ~PORT_QUEUE_SPLIT_H; - if (queue & PORT_QUEUE_SPLIT_4) - hi |= PORT_QUEUE_SPLIT_H; - ksz_pwrite8(dev, port, REG_PORT_CTRL_0, lo); - ksz_pwrite8(dev, port, P_DROP_TAG_CTRL, hi); - - /* Default is port based for egress rate limit. */ - if (queue != PORT_QUEUE_SPLIT_1) - ksz_cfg(dev, REG_SW_CTRL_19, SW_OUT_RATE_LIMIT_QUEUE_BASED, - true); + + return 0; } void ksz8_r_mib_cnt(struct ksz_device *dev, int port, u16 addr, u64 *cnt) @@ -283,7 +393,7 @@ static void ksz8863_r_mib_pkt(struct ksz_device *dev, int port, u16 addr, void ksz8_r_mib_pkt(struct ksz_device *dev, int port, u16 addr, u64 *dropped, u64 *cnt) { - if (ksz_is_ksz88x3(dev)) + if (is_ksz88xx(dev)) ksz8863_r_mib_pkt(dev, port, addr, dropped, cnt); else ksz8795_r_mib_pkt(dev, port, addr, dropped, cnt); @@ -291,7 +401,7 @@ void ksz8_r_mib_pkt(struct ksz_device *dev, int port, u16 addr, void ksz8_freeze_mib(struct ksz_device *dev, int port, bool freeze) { - if (ksz_is_ksz88x3(dev)) + if (is_ksz88xx(dev)) return; /* enable the port for flush/freeze function */ @@ -309,7 +419,8 @@ void ksz8_port_init_cnt(struct ksz_device *dev, int port) struct ksz_port_mib *mib = &dev->ports[port].mib; u64 *dropped; - if (!ksz_is_ksz88x3(dev)) { + /* For KSZ8795 family. */ + if (ksz_is_ksz87xx(dev)) { /* flush all enabled port MIB counters */ ksz_cfg(dev, REG_SW_CTRL_6, BIT(port), true); ksz_cfg(dev, REG_SW_CTRL_6, SW_MIB_COUNTER_FLUSH, true); @@ -385,39 +496,39 @@ static int ksz8_valid_dyn_entry(struct ksz_device *dev, u8 *data) int timeout = 100; const u32 *masks; const u16 *regs; + int ret; masks = dev->info->masks; regs = dev->info->regs; do { - ksz_read8(dev, regs[REG_IND_DATA_CHECK], data); + ret = ksz_read8(dev, regs[REG_IND_DATA_CHECK], data); + if (ret) + return ret; + timeout--; } while ((*data & masks[DYNAMIC_MAC_TABLE_NOT_READY]) && timeout); /* Entry is not ready for accessing. */ - if (*data & masks[DYNAMIC_MAC_TABLE_NOT_READY]) { - return -EAGAIN; - /* Entry is ready for accessing. */ - } else { - ksz_read8(dev, regs[REG_IND_DATA_8], data); + if (*data & masks[DYNAMIC_MAC_TABLE_NOT_READY]) + return -ETIMEDOUT; - /* There is no valid entry in the table. */ - if (*data & masks[DYNAMIC_MAC_TABLE_MAC_EMPTY]) - return -ENXIO; - } - return 0; + /* Entry is ready for accessing. */ + return ksz_read8(dev, regs[REG_IND_DATA_8], data); } -int ksz8_r_dyn_mac_table(struct ksz_device *dev, u16 addr, u8 *mac_addr, - u8 *fid, u8 *src_port, u8 *timestamp, u16 *entries) +static int ksz8_r_dyn_mac_table(struct ksz_device *dev, u16 addr, u8 *mac_addr, + u8 *fid, u8 *src_port, u16 *entries) { u32 data_hi, data_lo; const u8 *shifts; const u32 *masks; const u16 *regs; u16 ctrl_addr; + u64 buf = 0; u8 data; - int rc; + int cnt; + int ret; shifts = dev->info->shifts; masks = dev->info->masks; @@ -426,49 +537,50 @@ int ksz8_r_dyn_mac_table(struct ksz_device *dev, u16 addr, u8 *mac_addr, ctrl_addr = IND_ACC_TABLE(TABLE_DYNAMIC_MAC | TABLE_READ) | addr; mutex_lock(&dev->alu_mutex); - ksz_write16(dev, regs[REG_IND_CTRL_0], ctrl_addr); + ret = ksz_write16(dev, regs[REG_IND_CTRL_0], ctrl_addr); + if (ret) + goto unlock_alu; + + ret = ksz8_valid_dyn_entry(dev, &data); + if (ret) + goto unlock_alu; - rc = ksz8_valid_dyn_entry(dev, &data); - if (rc == -EAGAIN) { - if (addr == 0) - *entries = 0; - } else if (rc == -ENXIO) { + if (data & masks[DYNAMIC_MAC_TABLE_MAC_EMPTY]) { *entries = 0; - /* At least one valid entry in the table. */ - } else { - u64 buf = 0; - int cnt; - - ksz_read64(dev, regs[REG_IND_DATA_HI], &buf); - data_hi = (u32)(buf >> 32); - data_lo = (u32)buf; - - /* Check out how many valid entry in the table. */ - cnt = data & masks[DYNAMIC_MAC_TABLE_ENTRIES_H]; - cnt <<= shifts[DYNAMIC_MAC_ENTRIES_H]; - cnt |= (data_hi & masks[DYNAMIC_MAC_TABLE_ENTRIES]) >> - shifts[DYNAMIC_MAC_ENTRIES]; - *entries = cnt + 1; - - *fid = (data_hi & masks[DYNAMIC_MAC_TABLE_FID]) >> - shifts[DYNAMIC_MAC_FID]; - *src_port = (data_hi & masks[DYNAMIC_MAC_TABLE_SRC_PORT]) >> - shifts[DYNAMIC_MAC_SRC_PORT]; - *timestamp = (data_hi & masks[DYNAMIC_MAC_TABLE_TIMESTAMP]) >> - shifts[DYNAMIC_MAC_TIMESTAMP]; - - mac_addr[5] = (u8)data_lo; - mac_addr[4] = (u8)(data_lo >> 8); - mac_addr[3] = (u8)(data_lo >> 16); - mac_addr[2] = (u8)(data_lo >> 24); - - mac_addr[1] = (u8)data_hi; - mac_addr[0] = (u8)(data_hi >> 8); - rc = 0; + goto unlock_alu; } + + ret = ksz_read64(dev, regs[REG_IND_DATA_HI], &buf); + if (ret) + goto unlock_alu; + + data_hi = (u32)(buf >> 32); + data_lo = (u32)buf; + + /* Check out how many valid entry in the table. */ + cnt = data & masks[DYNAMIC_MAC_TABLE_ENTRIES_H]; + cnt <<= shifts[DYNAMIC_MAC_ENTRIES_H]; + cnt |= (data_hi & masks[DYNAMIC_MAC_TABLE_ENTRIES]) >> + shifts[DYNAMIC_MAC_ENTRIES]; + *entries = cnt + 1; + + *fid = (data_hi & masks[DYNAMIC_MAC_TABLE_FID]) >> + shifts[DYNAMIC_MAC_FID]; + *src_port = (data_hi & masks[DYNAMIC_MAC_TABLE_SRC_PORT]) >> + shifts[DYNAMIC_MAC_SRC_PORT]; + + mac_addr[5] = (u8)data_lo; + mac_addr[4] = (u8)(data_lo >> 8); + mac_addr[3] = (u8)(data_lo >> 16); + mac_addr[2] = (u8)(data_lo >> 24); + + mac_addr[1] = (u8)data_hi; + mac_addr[0] = (u8)(data_hi >> 8); + +unlock_alu: mutex_unlock(&dev->alu_mutex); - return rc; + return ret; } static int ksz8_r_sta_mac_table(struct ksz_device *dev, u16 addr, @@ -507,11 +619,11 @@ static int ksz8_r_sta_mac_table(struct ksz_device *dev, u16 addr, shifts[STATIC_MAC_FWD_PORTS]; alu->is_override = (data_hi & masks[STATIC_MAC_TABLE_OVERRIDE]) ? 1 : 0; - /* KSZ8795 family switches have STATIC_MAC_TABLE_USE_FID and + /* KSZ8795/KSZ8895 family switches have STATIC_MAC_TABLE_USE_FID and * STATIC_MAC_TABLE_FID definitions off by 1 when doing read on the * static MAC table compared to doing write. */ - if (ksz_is_ksz87xx(dev)) + if (ksz_is_ksz87xx(dev) || ksz_is_8895_family(dev)) data_hi >>= 1; alu->is_static = true; alu->is_use_fid = (data_hi & masks[STATIC_MAC_TABLE_USE_FID]) ? 1 : 0; @@ -1193,28 +1305,28 @@ void ksz8_flush_dyn_mac_table(struct ksz_device *dev, int port) int ksz8_fdb_dump(struct ksz_device *dev, int port, dsa_fdb_dump_cb_t *cb, void *data) { - int ret = 0; - u16 i = 0; - u16 entries = 0; - u8 timestamp = 0; - u8 fid; - u8 src_port; u8 mac[ETH_ALEN]; + u8 src_port, fid; + u16 entries = 0; + int ret, i; - do { + for (i = 0; i < KSZ8_DYN_MAC_ENTRIES; i++) { ret = ksz8_r_dyn_mac_table(dev, i, mac, &fid, &src_port, - ×tamp, &entries); - if (!ret && port == src_port) { + &entries); + if (ret) + return ret; + + if (i >= entries) + return 0; + + if (port == src_port) { ret = cb(mac, fid, false, data); if (ret) - break; + return ret; } - i++; - } while (i < entries); - if (i >= entries) - ret = 0; + } - return ret; + return 0; } static int ksz8_add_sta_mac(struct ksz_device *dev, int port, @@ -1510,8 +1622,10 @@ static void ksz8795_cpu_interface_select(struct ksz_device *dev, int port) void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port) { + const u16 *regs = dev->info->regs; struct dsa_switch *ds = dev->ds; const u32 *masks; + int queues; u8 member; masks = dev->info->masks; @@ -1519,25 +1633,33 @@ void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port) /* enable broadcast storm limit */ ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true); - if (!ksz_is_ksz88x3(dev)) - ksz8795_set_prio_queue(dev, port, 4); + /* For KSZ88x3 enable only one queue by default, otherwise we won't + * be able to get rid of PCP prios on Port 2. + */ + if (ksz_is_ksz88x3(dev)) + queues = 1; + else + queues = dev->info->num_tx_queues; - /* disable DiffServ priority */ - ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_DIFFSERV_ENABLE, false); + ksz8_port_queue_split(dev, port, queues); /* replace priority */ ksz_port_cfg(dev, port, P_802_1P_CTRL, masks[PORT_802_1P_REMAPPING], false); - /* enable 802.1p priority */ - ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_802_1P_ENABLE, true); - if (cpu_port) member = dsa_user_ports(ds); else member = BIT(dsa_upstream_port(ds, port)); ksz8_cfg_port_member(dev, port, member); + + /* Disable all WoL options by default. Otherwise + * ksz_switch_macaddr_get/put logic will not work properly. + * CPU port 4 has no WoL functionality. + */ + if (ksz_is_ksz87xx(dev) && !cpu_port) + ksz8_pme_pwrite8(dev, port, regs[REG_PORT_PME_CTRL], 0); } static void ksz88x3_config_rmii_clk(struct ksz_device *dev) @@ -1580,7 +1702,8 @@ void ksz8_config_cpu_port(struct dsa_switch *ds) for (i = 0; i < dev->phy_port_cnt; i++) { p = &dev->ports[i]; - if (!ksz_is_ksz88x3(dev)) { + /* For KSZ8795 family. */ + if (ksz_is_ksz87xx(dev)) { ksz_pread8(dev, i, regs[P_REMOTE_STATUS], &remote); if (remote & KSZ8_PORT_FIBER_MODE) p->fiber = 1; @@ -1701,11 +1824,15 @@ static void ksz8_cpu_port_link_up(struct ksz_device *dev, int speed, int duplex, SW_10_MBIT, ctrl); } -void ksz8_phylink_mac_link_up(struct ksz_device *dev, int port, - unsigned int mode, phy_interface_t interface, - struct phy_device *phydev, int speed, int duplex, +void ksz8_phylink_mac_link_up(struct phylink_config *config, + struct phy_device *phydev, unsigned int mode, + phy_interface_t interface, int speed, int duplex, bool tx_pause, bool rx_pause) { + struct dsa_port *dp = dsa_phylink_to_port(config); + struct ksz_device *dev = dp->ds->priv; + int port = dp->index; + /* If the port is the CPU port, apply special handling. Only the CPU * port is configured via global registers. */ @@ -1749,7 +1876,8 @@ int ksz8_enable_stp_addr(struct ksz_device *dev) int ksz8_setup(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; - int i; + const u16 *regs = dev->info->regs; + int i, ret = 0; ds->mtu_enforcement_ingress = true; @@ -1788,7 +1916,21 @@ int ksz8_setup(struct dsa_switch *ds) for (i = 0; i < (dev->info->num_vlans / 4); i++) ksz8_r_vlan_entries(dev, i); - return ksz8_handle_global_errata(ds); + /* Make sure PME (WoL) is not enabled. If requested, it will + * be enabled by ksz_wol_pre_shutdown(). Otherwise, some PMICs + * do not like PME events changes before shutdown. PME only + * available on KSZ87xx family. + */ + if (ksz_is_ksz87xx(dev)) { + ret = ksz8_pme_write8(dev, regs[REG_SW_PME_CTRL], 0); + if (!ret) + ret = ksz_rmw8(dev, REG_INT_ENABLE, INT_PME, 0); + } + + if (!ret) + return ksz8_handle_global_errata(ds); + else + return ret; } void ksz8_get_caps(struct ksz_device *dev, int port, diff --git a/drivers/net/dsa/microchip/ksz8.h b/drivers/net/dsa/microchip/ksz8.h index 1a5225264e6a..e1c79ff97123 100644 --- a/drivers/net/dsa/microchip/ksz8.h +++ b/drivers/net/dsa/microchip/ksz8.h @@ -19,8 +19,6 @@ void ksz8_flush_dyn_mac_table(struct ksz_device *dev, int port); void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port); int ksz8_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val); int ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val); -int ksz8_r_dyn_mac_table(struct ksz_device *dev, u16 addr, u8 *mac_addr, - u8 *fid, u8 *src_port, u8 *timestamp, u16 *entries); void ksz8_r_mib_cnt(struct ksz_device *dev, int port, u16 addr, u64 *cnt); void ksz8_r_mib_pkt(struct ksz_device *dev, int port, u16 addr, u64 *dropped, u64 *cnt); @@ -56,9 +54,13 @@ int ksz8_reset_switch(struct ksz_device *dev); int ksz8_switch_init(struct ksz_device *dev); void ksz8_switch_exit(struct ksz_device *dev); int ksz8_change_mtu(struct ksz_device *dev, int port, int mtu); -void ksz8_phylink_mac_link_up(struct ksz_device *dev, int port, - unsigned int mode, phy_interface_t interface, - struct phy_device *phydev, int speed, int duplex, +int ksz8_pme_write8(struct ksz_device *dev, u32 reg, u8 value); +int ksz8_pme_pread8(struct ksz_device *dev, int port, int offset, u8 *data); +int ksz8_pme_pwrite8(struct ksz_device *dev, int port, int offset, u8 data); +void ksz8_phylink_mac_link_up(struct phylink_config *config, + struct phy_device *phydev, unsigned int mode, + phy_interface_t interface, int speed, int duplex, bool tx_pause, bool rx_pause); +int ksz8_all_queues_split(struct ksz_device *dev, int queues); #endif diff --git a/drivers/net/dsa/microchip/ksz8863_smi.c b/drivers/net/dsa/microchip/ksz8863_smi.c index 5711a59e2ac9..a8bfcd917bf7 100644 --- a/drivers/net/dsa/microchip/ksz8863_smi.c +++ b/drivers/net/dsa/microchip/ksz8863_smi.c @@ -199,11 +199,11 @@ static void ksz8863_smi_shutdown(struct mdio_device *mdiodev) static const struct of_device_id ksz8863_dt_ids[] = { { .compatible = "microchip,ksz8863", - .data = &ksz_switch_chips[KSZ8830] + .data = &ksz_switch_chips[KSZ88X3] }, { .compatible = "microchip,ksz8873", - .data = &ksz_switch_chips[KSZ8830] + .data = &ksz_switch_chips[KSZ88X3] }, { }, }; diff --git a/drivers/net/dsa/microchip/ksz8795_reg.h b/drivers/net/dsa/microchip/ksz8_reg.h index 7c9341ef73b0..329688603a58 100644 --- a/drivers/net/dsa/microchip/ksz8795_reg.h +++ b/drivers/net/dsa/microchip/ksz8_reg.h @@ -1,13 +1,18 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* - * Microchip KSZ8795 register definitions + * Microchip KSZ8XXX series register definitions + * + * The base for these definitions is KSZ8795 but unless indicated + * differently by their prefix, they apply to all KSZ8 series + * devices. Registers and masks that do change are defined in + * dedicated structures in ksz_common.c. * * Copyright (c) 2017 Microchip Technology Inc. * Tristram Ha <Tristram.Ha@microchip.com> */ -#ifndef __KSZ8795_REG_H -#define __KSZ8795_REG_H +#ifndef __KSZ8_REG_H +#define __KSZ8_REG_H #define KS_PORT_M 0x1F @@ -124,7 +129,8 @@ #define PORT_BASED_PRIO_3 3 #define PORT_INSERT_TAG BIT(2) #define PORT_REMOVE_TAG BIT(1) -#define PORT_QUEUE_SPLIT_L BIT(0) +#define KSZ8795_PORT_2QUEUE_SPLIT_EN BIT(0) +#define KSZ8873_PORT_4QUEUE_SPLIT_EN BIT(0) #define REG_PORT_1_CTRL_1 0x11 #define REG_PORT_2_CTRL_1 0x21 @@ -143,6 +149,7 @@ #define REG_PORT_4_CTRL_2 0x42 #define REG_PORT_5_CTRL_2 0x52 +#define KSZ8873_PORT_2QUEUE_SPLIT_EN BIT(7) #define PORT_INGRESS_FILTER BIT(6) #define PORT_DISCARD_NON_VID BIT(5) #define PORT_FORCE_FLOW_CTRL BIT(4) @@ -357,8 +364,6 @@ #define REG_IND_DATA_1 0x77 #define REG_IND_DATA_0 0x78 -#define REG_IND_DATA_PME_EEE_ACL 0xA0 - #define REG_INT_STATUS 0x7C #define REG_INT_ENABLE 0x7D @@ -463,10 +468,7 @@ #define REG_PORT_4_CTRL_13 0xE1 #define REG_PORT_5_CTRL_13 0xF1 -#define PORT_QUEUE_SPLIT_H BIT(1) -#define PORT_QUEUE_SPLIT_1 0 -#define PORT_QUEUE_SPLIT_2 1 -#define PORT_QUEUE_SPLIT_4 2 +#define KSZ8795_PORT_4QUEUE_SPLIT_EN BIT(1) #define PORT_DROP_TAG BIT(0) #define REG_PORT_1_CTRL_14 0xB2 @@ -705,8 +707,6 @@ #define KSZ8795_ID_LO 0x1550 #define KSZ8863_ID_LO 0x1430 -#define KSZ8795_SW_ID 0x8795 - #define PHY_REG_LINK_MD 0x1D #define PHY_START_CABLE_DIAG BIT(15) @@ -794,5 +794,6 @@ #define TAIL_TAG_LOOKUP BIT(7) #define FID_ENTRIES 128 +#define KSZ8_DYN_MAC_ENTRIES 1024 #endif diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c index 7f745628c84d..29fe79ea74cd 100644 --- a/drivers/net/dsa/microchip/ksz9477.c +++ b/drivers/net/dsa/microchip/ksz9477.c @@ -2,7 +2,7 @@ /* * Microchip KSZ9477 switch driver main logic * - * Copyright (C) 2017-2019 Microchip Technology Inc. + * Copyright (C) 2017-2024 Microchip Technology Inc. */ #include <linux/kernel.h> @@ -56,187 +56,6 @@ 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; @@ -355,10 +174,8 @@ int ksz9477_reset_switch(struct ksz_device *dev) SPI_AUTO_EDGE_DETECTION, 0); /* default configuration */ - ksz_read8(dev, REG_SW_LUE_CTRL_1, &data8); - data8 = SW_AGING_ENABLE | SW_LINK_AUTO_AGING | - SW_SRC_ADDR_FILTER | SW_FLUSH_STP_TABLE | SW_FLUSH_MSTP_TABLE; - ksz_write8(dev, REG_SW_LUE_CTRL_1, data8); + ksz_write8(dev, REG_SW_LUE_CTRL_1, + SW_AGING_ENABLE | SW_LINK_AUTO_AGING | SW_SRC_ADDR_FILTER); /* disable interrupts */ ksz_write32(dev, REG_SW_INT_MASK__4, SWITCH_INT_MASK); @@ -429,6 +246,73 @@ void ksz9477_freeze_mib(struct ksz_device *dev, int port, bool freeze) mutex_unlock(&p->mib.cnt_mutex); } +static int ksz9477_half_duplex_monitor(struct ksz_device *dev, int port, + u64 tx_late_col) +{ + u8 lue_ctrl; + u32 pmavbc; + u16 pqm; + int ret; + + /* Errata DS80000754 recommends monitoring potential faults in + * half-duplex mode. The switch might not be able to communicate anymore + * in these states. If you see this message, please read the + * errata-sheet for more information: + * https://ww1.microchip.com/downloads/aemDocuments/documents/UNG/ProductDocuments/Errata/KSZ9477S-Errata-DS80000754.pdf + * To workaround this issue, half-duplex mode should be avoided. + * A software reset could be implemented to recover from this state. + */ + dev_warn_once(dev->dev, + "Half-duplex detected on port %d, transmission halt may occur\n", + port); + if (tx_late_col != 0) { + /* Transmission halt with late collisions */ + dev_crit_once(dev->dev, + "TX late collisions detected, transmission may be halted on port %d\n", + port); + } + ret = ksz_read8(dev, REG_SW_LUE_CTRL_0, &lue_ctrl); + if (ret) + return ret; + if (lue_ctrl & SW_VLAN_ENABLE) { + ret = ksz_pread16(dev, port, REG_PORT_QM_TX_CNT_0__4, &pqm); + if (ret) + return ret; + + ret = ksz_read32(dev, REG_PMAVBC, &pmavbc); + if (ret) + return ret; + + if ((FIELD_GET(PMAVBC_MASK, pmavbc) <= PMAVBC_MIN) || + (FIELD_GET(PORT_QM_TX_CNT_M, pqm) >= PORT_QM_TX_CNT_MAX)) { + /* Transmission halt with Half-Duplex and VLAN */ + dev_crit_once(dev->dev, + "resources out of limits, transmission may be halted\n"); + } + } + + return ret; +} + +int ksz9477_errata_monitor(struct ksz_device *dev, int port, + u64 tx_late_col) +{ + u8 status; + int ret; + + ret = ksz_pread8(dev, port, REG_PORT_STATUS_0, &status); + if (ret) + return ret; + + if (!(FIELD_GET(PORT_INTF_SPEED_MASK, status) + == PORT_INTF_SPEED_NONE) && + !(status & PORT_INTF_FULL_DUPLEX)) { + ret = ksz9477_half_duplex_monitor(dev, port, tx_late_col); + } + + return ret; +} + void ksz9477_port_init_cnt(struct ksz_device *dev, int port) { struct ksz_port_mib *mib = &dev->ports[port].mib; @@ -1099,26 +983,51 @@ void ksz9477_get_caps(struct ksz_device *dev, int port, int ksz9477_set_ageing_time(struct ksz_device *dev, unsigned int msecs) { u32 secs = msecs / 1000; - u8 value; - u8 data; + u8 data, mult, value; + u32 max_val; int ret; - value = FIELD_GET(SW_AGE_PERIOD_7_0_M, secs); +#define MAX_TIMER_VAL ((1 << 8) - 1) - ret = ksz_write8(dev, REG_SW_LUE_CTRL_3, value); - if (ret < 0) - return ret; + /* The aging timer comprises a 3-bit multiplier and an 8-bit second + * value. Either of them cannot be zero. The maximum timer is then + * 7 * 255 = 1785 seconds. + */ + if (!secs) + secs = 1; - data = FIELD_GET(SW_AGE_PERIOD_10_8_M, secs); + /* Return error if too large. */ + else if (secs > 7 * MAX_TIMER_VAL) + return -EINVAL; ret = ksz_read8(dev, REG_SW_LUE_CTRL_0, &value); if (ret < 0) return ret; - value &= ~SW_AGE_CNT_M; - value |= FIELD_PREP(SW_AGE_CNT_M, data); + /* Check whether there is need to update the multiplier. */ + mult = FIELD_GET(SW_AGE_CNT_M, value); + max_val = MAX_TIMER_VAL; + if (mult > 0) { + /* Try to use the same multiplier already in the register as + * the hardware default uses multiplier 4 and 75 seconds for + * 300 seconds. + */ + max_val = DIV_ROUND_UP(secs, mult); + if (max_val > MAX_TIMER_VAL || max_val * mult != secs) + max_val = MAX_TIMER_VAL; + } + + data = DIV_ROUND_UP(secs, max_val); + if (mult != data) { + value &= ~SW_AGE_CNT_M; + value |= FIELD_PREP(SW_AGE_CNT_M, data); + ret = ksz_write8(dev, REG_SW_LUE_CTRL_0, value); + if (ret < 0) + return ret; + } - return ksz_write8(dev, REG_SW_LUE_CTRL_0, value); + value = DIV_ROUND_UP(secs, data); + return ksz_write8(dev, REG_SW_LUE_CTRL_3, value); } void ksz9477_port_queue_split(struct ksz_device *dev, int port) @@ -1139,6 +1048,7 @@ void ksz9477_port_queue_split(struct ksz_device *dev, int port) void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port) { + const u16 *regs = dev->info->regs; struct dsa_switch *ds = dev->ds; u16 data16; u8 member; @@ -1158,18 +1068,12 @@ void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port) /* enable broadcast storm limit */ ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true); - /* disable DiffServ priority */ - ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_DIFFSERV_PRIO_ENABLE, false); - /* replace priority */ ksz_port_cfg(dev, port, REG_PORT_MRI_MAC_CTRL, PORT_USER_PRIO_CEILING, false); ksz9477_port_cfg32(dev, port, REG_PORT_MTI_QUEUE_CTRL_0__4, MTI_PVID_REPLACE, false); - /* enable 802.1p priority */ - ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_802_1P_PRIO_ENABLE, true); - /* force flow control for non-PHY ports only */ ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_FORCE_TX_FLOW_CTRL | PORT_FORCE_RX_FLOW_CTRL, @@ -1189,12 +1093,12 @@ void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port) ksz9477_port_acl_init(dev, port); /* clear pending wake flags */ - ksz9477_handle_wake_reason(dev, port); + ksz_handle_wake_reason(dev, port); /* Disable all WoL options by default. Otherwise * ksz_switch_macaddr_get/put logic will not work properly. */ - ksz_pwrite8(dev, port, REG_PORT_PME_CTRL, 0); + ksz_pwrite8(dev, port, regs[REG_PORT_PME_CTRL], 0); } void ksz9477_config_cpu_port(struct dsa_switch *ds) @@ -1252,6 +1156,10 @@ void ksz9477_config_cpu_port(struct dsa_switch *ds) if (i == dev->cpu_port) continue; ksz_port_stp_state_set(ds, i, BR_STATE_DISABLED); + + /* Power down the internal PHY if port is unused. */ + if (dsa_is_unused_port(ds, i) && dev->info->internal_phy[i]) + ksz_pwrite16(dev, i, 0x100, BMCR_PDOWN); } } @@ -1291,6 +1199,7 @@ int ksz9477_enable_stp_addr(struct ksz_device *dev) int ksz9477_setup(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; + const u16 *regs = dev->info->regs; int ret = 0; ds->mtu_enforcement_ingress = true; @@ -1305,6 +1214,10 @@ int ksz9477_setup(struct dsa_switch *ds) /* Enable REG_SW_MTU__2 reg by setting SW_JUMBO_PACKET */ ksz_cfg(dev, REG_SW_MAC_CTRL_1, SW_JUMBO_PACKET, true); + /* Use collision based back pressure mode. */ + ksz_cfg(dev, REG_SW_MAC_CTRL_1, SW_BACK_PRESSURE, + SW_BACK_PRESSURE_COLLISION); + /* Now we can configure default MTU value */ ret = regmap_update_bits(ksz_regmap_16(dev), REG_SW_MTU__2, REG_SW_MTU_MASK, VLAN_ETH_FRAME_LEN + ETH_FCS_LEN); @@ -1317,13 +1230,11 @@ 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. + /* Make sure PME (WoL) is not enabled. If requested, it will + * be enabled by ksz_wol_pre_shutdown(). Otherwise, some PMICs + * do not like PME events changes before shutdown. */ - ksz_write8(dev, REG_SW_PME_CTRL, 0); - - return 0; + return ksz_write8(dev, regs[REG_SW_PME_CTRL], 0); } u32 ksz9477_get_port_addr(int port, int offset) diff --git a/drivers/net/dsa/microchip/ksz9477.h b/drivers/net/dsa/microchip/ksz9477.h index ce1e656b800b..d2166b0d881e 100644 --- a/drivers/net/dsa/microchip/ksz9477.h +++ b/drivers/net/dsa/microchip/ksz9477.h @@ -36,6 +36,8 @@ int ksz9477_port_mirror_add(struct ksz_device *dev, int port, bool ingress, struct netlink_ext_ack *extack); void ksz9477_port_mirror_del(struct ksz_device *dev, int port, struct dsa_mall_mirror_tc_entry *mirror); +int ksz9477_errata_monitor(struct ksz_device *dev, int port, + u64 tx_late_col); void ksz9477_get_caps(struct ksz_device *dev, int port, struct phylink_config *config); int ksz9477_fdb_dump(struct ksz_device *dev, int port, @@ -58,11 +60,6 @@ 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_i2c.c b/drivers/net/dsa/microchip/ksz9477_i2c.c index 82bebee4615c..a2beb27459f1 100644 --- a/drivers/net/dsa/microchip/ksz9477_i2c.c +++ b/drivers/net/dsa/microchip/ksz9477_i2c.c @@ -2,7 +2,7 @@ /* * Microchip KSZ9477 series register access through I2C * - * Copyright (C) 2018-2019 Microchip Technology Inc. + * Copyright (C) 2018-2024 Microchip Technology Inc. */ #include <linux/i2c.h> @@ -16,6 +16,8 @@ KSZ_REGMAP_TABLE(ksz9477, not_used, 16, 0, 0); static int ksz9477_i2c_probe(struct i2c_client *i2c) { + const struct ksz_chip_data *chip; + struct device *ddev = &i2c->dev; struct regmap_config rc; struct ksz_device *dev; int i, ret; @@ -24,6 +26,12 @@ static int ksz9477_i2c_probe(struct i2c_client *i2c) if (!dev) return -ENOMEM; + chip = device_get_match_data(ddev); + if (!chip) + return -EINVAL; + + /* Save chip id to do special initialization when probing. */ + dev->chip_id = chip->chip_id; for (i = 0; i < __KSZ_NUM_REGMAPS; i++) { rc = ksz9477_regmap_config[i]; rc.lock_arg = &dev->regmap_mutex; @@ -72,8 +80,8 @@ static void ksz9477_i2c_shutdown(struct i2c_client *i2c) } static const struct i2c_device_id ksz9477_i2c_id[] = { - { "ksz9477-switch", 0 }, - {}, + { "ksz9477-switch" }, + {} }; MODULE_DEVICE_TABLE(i2c, ksz9477_i2c_id); @@ -111,14 +119,22 @@ static const struct of_device_id ksz9477_dt_ids[] = { .compatible = "microchip,ksz9567", .data = &ksz_switch_chips[KSZ9567] }, + { + .compatible = "microchip,lan9646", + .data = &ksz_switch_chips[LAN9646] + }, {}, }; MODULE_DEVICE_TABLE(of, ksz9477_dt_ids); +static DEFINE_SIMPLE_DEV_PM_OPS(ksz_i2c_pm_ops, + ksz_switch_suspend, ksz_switch_resume); + static struct i2c_driver ksz9477_i2c_driver = { .driver = { .name = "ksz9477-switch", .of_match_table = ksz9477_dt_ids, + .pm = &ksz_i2c_pm_ops, }, .probe = ksz9477_i2c_probe, .remove = ksz9477_i2c_remove, diff --git a/drivers/net/dsa/microchip/ksz9477_reg.h b/drivers/net/dsa/microchip/ksz9477_reg.h index f3a205ee483f..ff579920078e 100644 --- a/drivers/net/dsa/microchip/ksz9477_reg.h +++ b/drivers/net/dsa/microchip/ksz9477_reg.h @@ -2,7 +2,7 @@ /* * Microchip KSZ9477 register definitions * - * Copyright (C) 2017-2018 Microchip Technology Inc. + * Copyright (C) 2017-2024 Microchip Technology Inc. */ #ifndef __KSZ9477_REGS_H @@ -38,11 +38,6 @@ #define SWITCH_REVISION_S 4 #define SWITCH_RESET 0x01 -#define REG_SW_PME_CTRL 0x0006 - -#define PME_ENABLE BIT(1) -#define PME_POLARITY BIT(0) - #define REG_GLOBAL_OPTIONS 0x000F #define SW_GIGABIT_ABLE BIT(6) @@ -170,8 +165,6 @@ #define SW_VLAN_ENABLE BIT(7) #define SW_DROP_INVALID_VID BIT(6) #define SW_AGE_CNT_M GENMASK(5, 3) -#define SW_AGE_CNT_S 3 -#define SW_AGE_PERIOD_10_8_M GENMASK(10, 8) #define SW_RESV_MCAST_ENABLE BIT(2) #define SW_HASH_OPTION_M 0x03 #define SW_HASH_OPTION_CRC 1 @@ -247,6 +240,7 @@ #define REG_SW_MAC_CTRL_1 0x0331 #define SW_BACK_PRESSURE BIT(5) +#define SW_BACK_PRESSURE_COLLISION 0 #define FAIR_FLOW_CTRL BIT(4) #define NO_EXC_COLLISION_DROP BIT(3) #define SW_JUMBO_PACKET BIT(2) @@ -806,13 +800,6 @@ #define REG_PORT_AVB_SR_1_TYPE 0x0008 #define REG_PORT_AVB_SR_2_TYPE 0x000A -#define REG_PORT_PME_STATUS 0x0013 -#define REG_PORT_PME_CTRL 0x0017 - -#define PME_WOL_MAGICPKT BIT(2) -#define PME_WOL_LINKUP BIT(1) -#define PME_WOL_ENERGY BIT(0) - #define REG_PORT_INT_STATUS 0x001B #define REG_PORT_INT_MASK 0x001F @@ -842,8 +829,8 @@ #define REG_PORT_STATUS_0 0x0030 -#define PORT_INTF_SPEED_M 0x3 -#define PORT_INTF_SPEED_S 3 +#define PORT_INTF_SPEED_MASK GENMASK(4, 3) +#define PORT_INTF_SPEED_NONE GENMASK(1, 0) #define PORT_INTF_FULL_DUPLEX BIT(2) #define PORT_TX_FLOW_CTRL BIT(1) #define PORT_RX_FLOW_CTRL BIT(0) @@ -1167,6 +1154,11 @@ #define PORT_RMII_CLK_SEL BIT(7) #define PORT_MII_SEL_EDGE BIT(5) +#define REG_PMAVBC 0x03AC + +#define PMAVBC_MASK GENMASK(26, 16) +#define PMAVBC_MIN 0x580 + /* 4 - MAC */ #define REG_PORT_MAC_CTRL_0 0x0400 @@ -1494,6 +1486,7 @@ #define PORT_QM_TX_CNT_USED_S 0 #define PORT_QM_TX_CNT_M (BIT(11) - 1) +#define PORT_QM_TX_CNT_MAX 0x200 #define REG_PORT_QM_TX_CNT_1__4 0x0A14 diff --git a/drivers/net/dsa/microchip/ksz9477_tc_flower.c b/drivers/net/dsa/microchip/ksz9477_tc_flower.c index 8b2f5be667e0..ca7830ab168a 100644 --- a/drivers/net/dsa/microchip/ksz9477_tc_flower.c +++ b/drivers/net/dsa/microchip/ksz9477_tc_flower.c @@ -124,6 +124,9 @@ static int ksz9477_flower_parse_key(struct ksz_device *dev, int port, return -EOPNOTSUPP; } + if (flow_rule_match_has_control_flags(rule, extack)) + return -EOPNOTSUPP; + if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC) || flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) { ret = ksz9477_flower_parse_key_l2(dev, port, extack, rule, diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index 2b510f150dd8..89f0796894af 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-2024 Microchip Technology Inc. */ #include <linux/delay.h> @@ -24,10 +24,12 @@ #include <linux/of_net.h> #include <linux/micrel_phy.h> #include <net/dsa.h> +#include <net/ieee8021q.h> #include <net/pkt_cls.h> #include <net/switchdev.h> #include "ksz_common.h" +#include "ksz_dcb.h" #include "ksz_ptp.h" #include "ksz8.h" #include "ksz9477.h" @@ -244,16 +246,38 @@ 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 const struct ksz_dev_ops ksz8_dev_ops = { +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, + unsigned int mode, + const struct phylink_link_state *state); +static void ksz_phylink_mac_link_down(struct phylink_config *config, + unsigned int mode, + phy_interface_t interface); + +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, +}; + +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, +}; + +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, @@ -277,22 +301,65 @@ static const struct ksz_dev_ops ksz8_dev_ops = { .mirror_add = ksz8_port_mirror_add, .mirror_del = ksz8_port_mirror_del, .get_caps = ksz8_get_caps, - .phylink_mac_link_up = ksz8_phylink_mac_link_up, .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 ksz_device *dev, int port, +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, + struct phy_device *phydev, unsigned int mode, phy_interface_t interface, - struct phy_device *phydev, int speed, - int duplex, bool tx_pause, + int speed, int duplex, bool tx_pause, bool rx_pause); +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, +}; + static const struct ksz_dev_ops ksz9477_dev_ops = { .setup = ksz9477_setup, .get_port_addr = ksz9477_get_port_addr, @@ -319,10 +386,9 @@ static const struct ksz_dev_ops ksz9477_dev_ops = { .mdb_add = ksz9477_mdb_add, .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, + .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, @@ -331,6 +397,12 @@ static const struct ksz_dev_ops ksz9477_dev_ops = { .exit = ksz9477_switch_exit, }; +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, +}; + static const struct ksz_dev_ops lan937x_dev_ops = { .setup = lan937x_setup, .teardown = lan937x_teardown, @@ -339,6 +411,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, @@ -359,7 +433,6 @@ static const struct ksz_dev_ops lan937x_dev_ops = { .mdb_add = ksz9477_mdb_add, .mdb_del = ksz9477_mdb_del, .change_mtu = lan937x_change_mtu, - .phylink_mac_link_up = ksz9477_phylink_mac_link_up, .config_cpu_port = lan937x_config_cpu_port, .tc_cbs_set_cinc = lan937x_tc_cbs_set_cinc, .enable_stp_addr = ksz9477_enable_stp_addr, @@ -390,6 +463,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[] = { @@ -498,6 +574,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, @@ -506,6 +637,9 @@ 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[] = { @@ -966,10 +1100,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), @@ -996,10 +1129,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), @@ -1026,10 +1158,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), @@ -1056,10 +1187,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), @@ -1086,10 +1216,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), @@ -1116,10 +1245,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), @@ -1194,9 +1322,10 @@ const struct ksz_chip_data ksz_switch_chips[] = { .port_cnt = 3, /* total port count */ .port_nirqs = 3, .num_tx_queues = 4, + .num_ipms = 8, .tc_cbs_supported = true, - .tc_ets_supported = true, .ops = &ksz9477_dev_ops, + .phylink_mac_ops = &ksz9477_phylink_mac_ops, .mib_names = ksz9477_mib_names, .mib_cnt = ARRAY_SIZE(ksz9477_mib_names), .reg_mib_cnt = MIB_COUNTER_NUM, @@ -1210,6 +1339,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, }, @@ -1219,11 +1349,13 @@ 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, - .ops = &ksz8_dev_ops, + .num_ipms = 4, + .ops = &ksz87xx_dev_ops, + .phylink_mac_ops = &ksz8_phylink_mac_ops, .ksz87xx_eee_link_erratum = true, .mib_names = ksz9477_mib_names, .mib_cnt = ARRAY_SIZE(ksz9477_mib_names), @@ -1258,11 +1390,13 @@ 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, - .ops = &ksz8_dev_ops, + .num_ipms = 4, + .ops = &ksz87xx_dev_ops, + .phylink_mac_ops = &ksz8_phylink_mac_ops, .ksz87xx_eee_link_erratum = true, .mib_names = ksz9477_mib_names, .mib_cnt = ARRAY_SIZE(ksz9477_mib_names), @@ -1283,11 +1417,13 @@ 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, - .ops = &ksz8_dev_ops, + .num_ipms = 4, + .ops = &ksz87xx_dev_ops, + .phylink_mac_ops = &ksz8_phylink_mac_ops, .ksz87xx_eee_link_erratum = true, .mib_names = ksz9477_mib_names, .mib_cnt = ARRAY_SIZE(ksz9477_mib_names), @@ -1303,8 +1439,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, @@ -1312,7 +1448,9 @@ const struct ksz_chip_data ksz_switch_chips[] = { .cpu_ports = 0x4, /* can be configured as cpu port */ .port_cnt = 3, .num_tx_queues = 4, - .ops = &ksz8_dev_ops, + .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, @@ -1326,6 +1464,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", @@ -1336,9 +1529,11 @@ const struct ksz_chip_data ksz_switch_chips[] = { .port_cnt = 7, /* total physical port count */ .port_nirqs = 4, .num_tx_queues = 4, + .num_ipms = 8, .tc_cbs_supported = true, - .tc_ets_supported = true, .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, @@ -1356,6 +1551,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, .wr_table = &ksz9477_register_set, .rd_table = &ksz9477_register_set, }, @@ -1370,7 +1566,10 @@ const struct ksz_chip_data ksz_switch_chips[] = { .port_cnt = 6, /* total physical port count */ .port_nirqs = 2, .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, @@ -1402,7 +1601,10 @@ const struct ksz_chip_data ksz_switch_chips[] = { .port_cnt = 7, /* total physical port count */ .port_nirqs = 2, .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, @@ -1432,7 +1634,9 @@ const struct ksz_chip_data ksz_switch_chips[] = { .port_cnt = 3, /* total port count */ .port_nirqs = 2, .num_tx_queues = 4, + .num_ipms = 8, .ops = &ksz9477_dev_ops, + .phylink_mac_ops = &ksz9477_phylink_mac_ops, .mib_names = ksz9477_mib_names, .mib_cnt = ARRAY_SIZE(ksz9477_mib_names), .reg_mib_cnt = MIB_COUNTER_NUM, @@ -1458,9 +1662,10 @@ const struct ksz_chip_data ksz_switch_chips[] = { .port_cnt = 3, /* total port count */ .port_nirqs = 3, .num_tx_queues = 4, + .num_ipms = 8, .tc_cbs_supported = true, - .tc_ets_supported = true, .ops = &ksz9477_dev_ops, + .phylink_mac_ops = &ksz9477_phylink_mac_ops, .mib_names = ksz9477_mib_names, .mib_cnt = ARRAY_SIZE(ksz9477_mib_names), .reg_mib_cnt = MIB_COUNTER_NUM, @@ -1474,6 +1679,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] = { @@ -1486,9 +1692,11 @@ const struct ksz_chip_data ksz_switch_chips[] = { .port_cnt = 7, /* total port count */ .port_nirqs = 3, .num_tx_queues = 4, + .num_ipms = 8, .tc_cbs_supported = true, - .tc_ets_supported = true, .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, @@ -1507,6 +1715,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] = { @@ -1519,8 +1728,8 @@ const struct ksz_chip_data ksz_switch_chips[] = { .port_cnt = 7, /* total physical port count */ .port_nirqs = 3, .num_tx_queues = 4, + .num_ipms = 8, .tc_cbs_supported = true, - .tc_ets_supported = true, .ops = &ksz9477_dev_ops, .mib_names = ksz9477_mib_names, .mib_cnt = ARRAY_SIZE(ksz9477_mib_names), @@ -1539,6 +1748,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] = { @@ -1551,9 +1761,11 @@ const struct ksz_chip_data ksz_switch_chips[] = { .port_cnt = 5, /* total physical port count */ .port_nirqs = 6, .num_tx_queues = 8, + .num_ipms = 8, .tc_cbs_supported = true, - .tc_ets_supported = true, + .phy_side_mdio_supported = true, .ops = &lan937x_dev_ops, + .phylink_mac_ops = &lan937x_phylink_mac_ops, .mib_names = ksz9477_mib_names, .mib_cnt = ARRAY_SIZE(ksz9477_mib_names), .reg_mib_cnt = MIB_COUNTER_NUM, @@ -1566,6 +1778,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] = { @@ -1578,9 +1791,11 @@ const struct ksz_chip_data ksz_switch_chips[] = { .port_cnt = 6, /* total physical port count */ .port_nirqs = 6, .num_tx_queues = 8, + .num_ipms = 8, .tc_cbs_supported = true, - .tc_ets_supported = true, + .phy_side_mdio_supported = true, .ops = &lan937x_dev_ops, + .phylink_mac_ops = &lan937x_phylink_mac_ops, .mib_names = ksz9477_mib_names, .mib_cnt = ARRAY_SIZE(ksz9477_mib_names), .reg_mib_cnt = MIB_COUNTER_NUM, @@ -1593,6 +1808,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] = { @@ -1605,9 +1821,11 @@ const struct ksz_chip_data ksz_switch_chips[] = { .port_cnt = 8, /* total physical port count */ .port_nirqs = 6, .num_tx_queues = 8, + .num_ipms = 8, .tc_cbs_supported = true, - .tc_ets_supported = true, + .phy_side_mdio_supported = true, .ops = &lan937x_dev_ops, + .phylink_mac_ops = &lan937x_phylink_mac_ops, .mib_names = ksz9477_mib_names, .mib_cnt = ARRAY_SIZE(ksz9477_mib_names), .reg_mib_cnt = MIB_COUNTER_NUM, @@ -1624,6 +1842,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] = { @@ -1636,9 +1855,11 @@ const struct ksz_chip_data ksz_switch_chips[] = { .port_cnt = 5, /* total physical port count */ .port_nirqs = 6, .num_tx_queues = 8, + .num_ipms = 8, .tc_cbs_supported = true, - .tc_ets_supported = true, + .phy_side_mdio_supported = true, .ops = &lan937x_dev_ops, + .phylink_mac_ops = &lan937x_phylink_mac_ops, .mib_names = ksz9477_mib_names, .mib_cnt = ARRAY_SIZE(ksz9477_mib_names), .reg_mib_cnt = MIB_COUNTER_NUM, @@ -1655,6 +1876,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] = { @@ -1667,9 +1889,11 @@ const struct ksz_chip_data ksz_switch_chips[] = { .port_cnt = 8, /* total physical port count */ .port_nirqs = 6, .num_tx_queues = 8, + .num_ipms = 8, .tc_cbs_supported = true, - .tc_ets_supported = true, + .phy_side_mdio_supported = true, .ops = &lan937x_dev_ops, + .phylink_mac_ops = &lan937x_phylink_mac_ops, .mib_names = ksz9477_mib_names, .mib_cnt = ARRAY_SIZE(ksz9477_mib_names), .reg_mib_cnt = MIB_COUNTER_NUM, @@ -1686,6 +1910,42 @@ 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}, + .wr_table = &ksz9477_register_set, + .rd_table = &ksz9477_register_set, }, }; EXPORT_SYMBOL_GPL(ksz_switch_chips); @@ -1764,6 +2024,7 @@ void ksz_r_mib_stats64(struct ksz_device *dev, int port) struct rtnl_link_stats64 *stats; struct ksz_stats_raw *raw; struct ksz_port_mib *mib; + int ret; mib = &dev->ports[port].mib; stats = &mib->stats64; @@ -1805,6 +2066,12 @@ void ksz_r_mib_stats64(struct ksz_device *dev, int port) pstats->rx_pause_frames = raw->rx_pause; spin_unlock(&mib->stats64_lock); + + if (dev->info->phy_errata_9477) { + ret = ksz9477_errata_monitor(dev, port, raw->tx_late_col); + if (ret) + dev_err(dev->dev, "Failed to monitor transmission halt\n"); + } } void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port) @@ -1891,10 +2158,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); } /** @@ -2017,16 +2282,100 @@ 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; @@ -2044,49 +2393,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); @@ -2097,7 +2584,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; } @@ -2129,7 +2618,7 @@ static void ksz_irq_bus_sync_unlock(struct irq_data *d) struct ksz_device *dev = kirq->dev; int ret; - ret = ksz_write32(dev, kirq->reg_mask, kirq->masked); + ret = ksz_write8(dev, kirq->reg_mask, kirq->masked); if (ret) dev_err(dev->dev, "failed to change IRQ mask\n"); @@ -2304,6 +2793,7 @@ static int ksz_setup(struct dsa_switch *ds) ksz_init_mib_timer(dev); ds->configure_vlan_while_not_filtering = false; + ds->dscp_prio_mapping_is_global = true; if (dev->dev_ops->setup) { ret = dev->dev_ops->setup(ds); @@ -2329,16 +2819,21 @@ static int ksz_setup(struct dsa_switch *ds) if (ret) goto out_girq; - 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 out_pirq; + } } } - 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 out_ptpirq; + } } ret = ksz_mdio_register(dev); @@ -2347,6 +2842,10 @@ static int ksz_setup(struct dsa_switch *ds) goto out_ptp_clock_unregister; } + ret = ksz_dcb_init(dev); + if (ret) + goto out_ptp_clock_unregister; + /* start switch */ regmap_update_bits(ksz_regmap_8(dev), regs[S_START_CTRL], SW_START, SW_START); @@ -2354,9 +2853,10 @@ static int ksz_setup(struct dsa_switch *ds) return 0; out_ptp_clock_unregister: - ksz_ptp_clock_unregister(ds); + if (dev->info->ptp_capable) + ksz_ptp_clock_unregister(ds); out_ptpirq: - if (dev->irq > 0) + if (dev->irq > 0 && dev->info->ptp_capable) dsa_switch_for_each_user_port(dp, dev->ds) ksz_ptp_irq_free(ds, dp->index); out_pirq: @@ -2375,11 +2875,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); } @@ -2498,7 +3000,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. @@ -2506,16 +3008,29 @@ static u32 ksz_get_phy_flags(struct dsa_switch *ds, int port) if (!port) return MICREL_KSZ8_P1_ERRATA; break; + 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: - /* KSZ9477 Errata DS80000754C - * - * Module 4: Energy Efficient Ethernet (EEE) feature select must - * be manually disabled + /* KSZ9477S Errata DS80000754A Module 4 */ + case KSZ9567_CHIP_ID: + /* KSZ9567S Errata DS80000756A Module 4 */ + case KSZ9896_CHIP_ID: + /* KSZ9896C Errata DS80000757A Module 3 */ + case KSZ9897_CHIP_ID: + 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. */ return MICREL_NO_EEE; } @@ -2523,14 +3038,15 @@ static u32 ksz_get_phy_flags(struct dsa_switch *ds, int port) return 0; } -static void ksz_mac_link_down(struct dsa_switch *ds, int port, - unsigned int mode, phy_interface_t interface) +static void ksz_phylink_mac_link_down(struct phylink_config *config, + unsigned int mode, + phy_interface_t interface) { - struct ksz_device *dev = ds->priv; - struct ksz_port *p = &dev->ports[port]; + struct dsa_port *dp = dsa_phylink_to_port(config); + struct ksz_device *dev = dp->ds->priv; /* Read all MIB counters when the link is going down. */ - p->read = true; + dev->ports[dp->index].read = true; /* timer started */ if (dev->mib_read_interval) schedule_delayed_work(&dev->mib_read, 0); @@ -2660,9 +3176,33 @@ static int ksz_port_mdb_del(struct dsa_switch *ds, int port, return dev->dev_ops->mdb_del(dev, port, mdb, db); } +static int ksz9477_set_default_prio_queue_mapping(struct ksz_device *dev, + int port) +{ + u32 queue_map = 0; + int ipm; + + for (ipm = 0; ipm < dev->info->num_ipms; ipm++) { + int queue; + + /* Traffic Type (TT) is corresponding to the Internal Priority + * Map (IPM) in the switch. Traffic Class (TC) is + * corresponding to the queue in the switch. + */ + queue = ieee8021q_tt_to_tc(ipm, dev->info->num_tx_queues); + if (queue < 0) + return queue; + + queue_map |= queue << (ipm * KSZ9477_PORT_TC_MAP_S); + } + + return ksz_pwrite32(dev, port, KSZ9477_PORT_MRI_TC_MAP__4, queue_map); +} + static int ksz_port_setup(struct dsa_switch *ds, int port) { struct ksz_device *dev = ds->priv; + int ret; if (!dsa_is_user_port(ds, port)) return 0; @@ -2670,11 +3210,17 @@ static int ksz_port_setup(struct dsa_switch *ds, int port) /* setup user port */ dev->dev_ops->port_setup(dev, port, false); + if (!is_ksz8(dev)) { + ret = ksz9477_set_default_prio_queue_mapping(dev, port); + if (ret) + return ret; + } + /* port_stp_state_set() will be called after to enable the port so * there is no need to do anything. */ - return 0; + return ksz_dcb_init_port(dev, port); } void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) @@ -2736,6 +3282,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); } @@ -2779,12 +3326,10 @@ 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 == KSZ8563_CHIP_ID || dev->chip_id == KSZ9893_CHIP_ID || dev->chip_id == KSZ9563_CHIP_ID) @@ -2794,7 +3339,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)) @@ -2896,7 +3442,9 @@ 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 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: @@ -2911,18 +3459,19 @@ 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) +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: @@ -2933,41 +3482,17 @@ static int ksz_validate_eee(struct dsa_switch *ds, int port) case KSZ9893_CHIP_ID: case KSZ9896_CHIP_ID: case KSZ9897_CHIP_ID: - return 0; + case LAN9646_CHIP_ID: + return true; } - 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"); @@ -3013,7 +3538,8 @@ static void ksz_set_xmii(struct ksz_device *dev, int port, /* On KSZ9893, disable RGMII in-band status support */ if (dev->chip_id == KSZ9893_CHIP_ID || dev->chip_id == KSZ8563_CHIP_ID || - dev->chip_id == KSZ9563_CHIP_ID) + dev->chip_id == KSZ9563_CHIP_ID || + is_lan937x(dev)) data8 &= ~P_MII_MAC_MODE; break; default: @@ -3050,7 +3576,7 @@ phy_interface_t ksz_get_xmii(struct ksz_device *dev, int port, bool gbit) else interface = PHY_INTERFACE_MODE_MII; } else if (val == bitval[P_RMII_SEL]) { - interface = PHY_INTERFACE_MODE_RGMII; + interface = PHY_INTERFACE_MODE_RMII; } else { interface = PHY_INTERFACE_MODE_RGMII; if (data8 & P_RGMII_ID_EG_ENABLE) @@ -3065,16 +3591,23 @@ phy_interface_t ksz_get_xmii(struct ksz_device *dev, int port, bool gbit) return interface; } -static void ksz_phylink_mac_config(struct dsa_switch *ds, int port, +static void ksz88x3_phylink_mac_config(struct phylink_config *config, + unsigned int mode, + const struct phylink_link_state *state) +{ + struct dsa_port *dp = dsa_phylink_to_port(config); + struct ksz_device *dev = dp->ds->priv; + + dev->ports[dp->index].manual_flow = !(state->pause & MLO_PAUSE_AN); +} + +static void ksz_phylink_mac_config(struct phylink_config *config, unsigned int mode, const struct phylink_link_state *state) { - struct ksz_device *dev = ds->priv; - - if (ksz_is_ksz88x3(dev)) { - dev->ports[port].manual_flow = !(state->pause & MLO_PAUSE_AN); - return; - } + struct dsa_port *dp = dsa_phylink_to_port(config); + struct ksz_device *dev = dp->ds->priv; + int port = dp->index; /* Internal PHYs */ if (dev->info->internal_phy[port]) @@ -3087,9 +3620,6 @@ static void ksz_phylink_mac_config(struct dsa_switch *ds, int port, ksz_set_xmii(dev, port, state->interface); - if (dev->dev_ops->phylink_mac_config) - dev->dev_ops->phylink_mac_config(dev, port, mode, state); - if (dev->dev_ops->setup_rgmii_delay) dev->dev_ops->setup_rgmii_delay(dev, port); } @@ -3187,13 +3717,16 @@ static void ksz_duplex_flowctrl(struct ksz_device *dev, int port, int duplex, ksz_prmw8(dev, port, regs[P_XMII_CTRL_0], mask, val); } -static void ksz9477_phylink_mac_link_up(struct ksz_device *dev, int port, +static void ksz9477_phylink_mac_link_up(struct phylink_config *config, + struct phy_device *phydev, unsigned int mode, phy_interface_t interface, - struct phy_device *phydev, int speed, - int duplex, bool tx_pause, + int speed, int duplex, bool tx_pause, bool rx_pause) { + struct dsa_port *dp = dsa_phylink_to_port(config); + struct ksz_device *dev = dp->ds->priv; + int port = dp->index; struct ksz_port *p; p = &dev->ports[port]; @@ -3209,18 +3742,6 @@ static void ksz9477_phylink_mac_link_up(struct ksz_device *dev, int port, ksz_duplex_flowctrl(dev, port, duplex, tx_pause, rx_pause); } -static void ksz_phylink_mac_link_up(struct dsa_switch *ds, int port, - unsigned int mode, - phy_interface_t interface, - struct phy_device *phydev, int speed, - int duplex, bool tx_pause, bool rx_pause) -{ - struct ksz_device *dev = ds->priv; - - dev->dev_ops->phylink_mac_link_up(dev, port, mode, interface, phydev, - speed, duplex, tx_pause, rx_pause); -} - static int ksz_switch_detect(struct ksz_device *dev) { u8 id1, id2, id4; @@ -3254,9 +3775,21 @@ 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); @@ -3277,7 +3810,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, @@ -3316,6 +3852,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); } @@ -3336,6 +3873,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); } @@ -3522,7 +4060,7 @@ static int ksz_tc_ets_add(struct ksz_device *dev, int port, for (tc_prio = 0; tc_prio < ARRAY_SIZE(p->priomap); tc_prio++) { int queue; - if (tc_prio > KSZ9477_MAX_TC_PRIO) + if (tc_prio >= dev->info->num_ipms) break; queue = ksz_ets_band_to_queue(p, p->priomap[tc_prio]); @@ -3534,8 +4072,7 @@ static int ksz_tc_ets_add(struct ksz_device *dev, int port, static int ksz_tc_ets_del(struct ksz_device *dev, int port) { - int ret, queue, tc_prio, s; - u32 queue_map = 0; + int ret, queue; /* To restore the default chip configuration, set all queues to use the * WRR scheduler with a weight of 1. @@ -3547,31 +4084,10 @@ static int ksz_tc_ets_del(struct ksz_device *dev, int port) return ret; } - switch (dev->info->num_tx_queues) { - case 2: - s = 2; - break; - case 4: - s = 1; - break; - case 8: - s = 0; - break; - default: - return -EINVAL; - } - /* Revert the queue mapping for TC-priority to its default setting on * the chip. */ - for (tc_prio = 0; tc_prio <= KSZ9477_MAX_TC_PRIO; tc_prio++) { - int queue; - - queue = tc_prio >> s; - queue_map |= queue << (tc_prio * KSZ9477_PORT_TC_MAP_S); - } - - return ksz_pwrite32(dev, port, KSZ9477_PORT_MRI_TC_MAP__4, queue_map); + return ksz9477_set_default_prio_queue_mapping(dev, port); } static int ksz_tc_ets_validate(struct ksz_device *dev, int port, @@ -3616,7 +4132,7 @@ static int ksz_tc_setup_qdisc_ets(struct dsa_switch *ds, int port, struct ksz_device *dev = ds->priv; int ret; - if (!dev->info->tc_ets_supported) + if (is_ksz8(dev)) return -EOPNOTSUPP; if (qopt->parent != TC_H_ROOT) { @@ -3654,24 +4170,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->dev_ops->get_wol) - dev->dev_ops->get_wol(dev, port, wol); + 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 = 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, @@ -3687,6 +4393,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, @@ -3841,6 +4552,13 @@ static int ksz_hsr_join(struct dsa_switch *ds, int port, struct net_device *hsr, return -EOPNOTSUPP; } + /* KSZ9477 can only perform HSR offloading for up to two ports */ + if (hweight8(dev->hsr_ports) >= 2) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot offload more than two ports - using software HSR"); + return -EOPNOTSUPP; + } + /* Self MAC address filtering, to avoid frames traversing * the HSR ring more than once. */ @@ -3872,6 +4590,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, @@ -3881,9 +4616,6 @@ static const struct dsa_switch_ops ksz_switch_ops = { .phy_read = ksz_phy_read16, .phy_write = ksz_phy_write16, .phylink_get_caps = ksz_phylink_get_caps, - .phylink_mac_config = ksz_phylink_mac_config, - .phylink_mac_link_up = ksz_phylink_mac_link_up, - .phylink_mac_link_down = ksz_mac_link_down, .port_setup = ksz_port_setup, .set_ageing_time = ksz_set_ageing_time, .get_strings = ksz_get_strings, @@ -3915,6 +4647,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, @@ -3923,8 +4657,15 @@ 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, + .port_get_dscp_prio = ksz_port_get_dscp_prio, + .port_add_dscp_prio = ksz_port_add_dscp_prio, + .port_del_dscp_prio = ksz_port_del_dscp_prio, + .port_get_apptrust = ksz_port_get_apptrust, + .port_set_apptrust = ksz_port_set_apptrust, }; struct ksz_device *ksz_switch_alloc(struct device *base, void *priv) @@ -3968,8 +4709,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); @@ -4133,24 +4873,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; @@ -4163,10 +4903,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; } @@ -4226,8 +4966,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: @@ -4240,6 +4980,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: @@ -4258,7 +4999,7 @@ static int ksz_parse_drive_strength(struct ksz_device *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; @@ -4328,6 +5069,9 @@ int ksz_switch_register(struct ksz_device *dev) /* set the real number of ports */ dev->ds->num_ports = dev->info->port_cnt; + /* set the phylink ops */ + dev->ds->phylink_mac_ops = dev->info->phylink_mac_ops; + /* Host port interface will be self detected, or specifically set in * device tree. */ @@ -4341,12 +5085,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; } @@ -4368,6 +5111,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); @@ -4403,6 +5148,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"); diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index 40c11b0d6b62..af17a9c030d4 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* Microchip switch driver common header * - * Copyright (C) 2017-2019 Microchip Technology Inc. + * Copyright (C) 2017-2024 Microchip Technology Inc. */ #ifndef __KSZ_COMMON_H @@ -19,9 +19,14 @@ #include "ksz_ptp.h" #define KSZ_MAX_NUM_PORTS 8 +/* all KSZ switches count ports from 1 */ +#define KSZ_PORT_1 0 +#define KSZ_PORT_2 1 +#define KSZ_PORT_4 3 struct ksz_device; struct ksz_port; +struct phylink_mac_ops; enum ksz_regmap_width { KSZ_REGMAP_8, @@ -58,9 +63,17 @@ struct ksz_chip_data { int port_cnt; u8 port_nirqs; u8 num_tx_queues; + u8 num_ipms; /* number of Internal Priority Maps */ bool tc_cbs_supported; - bool tc_ets_supported; + + /** + * @phy_side_mdio_supported: Indicates if the chip supports an additional + * side MDIO channel for accessing integrated PHYs. + */ + bool phy_side_mdio_supported; const struct ksz_dev_ops *ops; + const struct phylink_mac_ops *phylink_mac_ops; + bool phy_errata_9477; bool ksz87xx_eee_link_erratum; const struct ksz_mib_names *mib_names; int mib_cnt; @@ -79,6 +92,7 @@ struct ksz_chip_data { bool supports_rgmii[KSZ_MAX_NUM_PORTS]; bool internal_phy[KSZ_MAX_NUM_PORTS]; bool gbit_capable[KSZ_MAX_NUM_PORTS]; + bool ptp_capable; const struct regmap_access_table *wr_table; const struct regmap_access_table *rd_table; }; @@ -167,6 +181,7 @@ struct ksz_device { bool synclko_125; bool synclko_disable; bool wakeup_source; + bool pme_active_high; struct vlan_table *vlan_cache; @@ -183,6 +198,22 @@ struct ksz_device { struct ksz_switch_macaddr *switch_macaddr; struct net_device *hsr_dev; /* HSR */ u8 hsr_ports; + + /** + * @phy_addr_map: Array mapping switch ports to their corresponding PHY + * addresses. + */ + u8 phy_addr_map[KSZ_MAX_NUM_PORTS]; + + /** + * @parent_mdio_bus: Pointer to the external MDIO bus controller. + * + * This points to an external MDIO bus controller that is used to access + * the PHYs integrated within the switch. Unlike an integrated MDIO + * bus, this external controller provides a direct path for managing + * the switch’s internal PHYs, bypassing the main SPI interface. + */ + struct mii_bus *parent_mdio_bus; }; /* List of supported models */ @@ -192,7 +223,9 @@ enum ksz_model { KSZ8795, KSZ8794, KSZ8765, - KSZ8830, + KSZ88X3, + KSZ8864, + KSZ8895, KSZ9477, KSZ9896, KSZ9897, @@ -204,6 +237,7 @@ enum ksz_model { LAN9372, LAN9373, LAN9374, + LAN9646, }; enum ksz_regs { @@ -228,6 +262,9 @@ enum ksz_regs { S_MULTICAST_CTRL, P_XMII_CTRL_0, P_XMII_CTRL_1, + REG_SW_PME_CTRL, + REG_PORT_PME_STATUS, + REG_PORT_PME_CTRL, }; enum ksz_masks { @@ -313,6 +350,43 @@ struct ksz_dev_ops { void (*port_cleanup)(struct ksz_device *dev, int port); void (*port_setup)(struct ksz_device *dev, int port, bool cpu_port); int (*set_ageing_time)(struct ksz_device *dev, unsigned int msecs); + + /** + * @mdio_bus_preinit: Function pointer to pre-initialize the MDIO bus + * for accessing PHYs. + * @dev: Pointer to device structure. + * @side_mdio: Boolean indicating if the PHYs are accessed over a side + * MDIO bus. + * + * This function pointer is used to configure the MDIO bus for PHY + * access before initiating regular PHY operations. It enables either + * SPI/I2C or side MDIO access modes by unlocking necessary registers + * and setting up access permissions for the selected mode. + * + * Return: + * - 0 on success. + * - Negative error code on failure. + */ + int (*mdio_bus_preinit)(struct ksz_device *dev, bool side_mdio); + + /** + * @create_phy_addr_map: Function pointer to create a port-to-PHY + * address map. + * @dev: Pointer to device structure. + * @side_mdio: Boolean indicating if the PHYs are accessed over a side + * MDIO bus. + * + * This function pointer is responsible for mapping switch ports to PHY + * addresses according to the configured access mode (SPI or side MDIO) + * and the device’s strap configuration. The mapping setup may vary + * depending on the chip variant and configuration. Ensures the correct + * address mapping for PHY communication. + * + * Return: + * - 0 on success. + * - Negative error code on failure (e.g., invalid configuration). + */ + int (*create_phy_addr_map)(struct ksz_device *dev, bool side_mdio); int (*r_phy)(struct ksz_device *dev, u16 phy, u16 reg, u16 *val); int (*w_phy)(struct ksz_device *dev, u16 phy, u16 reg, u16 val); void (*r_mib_cnt)(struct ksz_device *dev, int port, u16 addr, @@ -347,11 +421,13 @@ struct ksz_dev_ops { void (*get_caps)(struct ksz_device *dev, int port, struct phylink_config *config); int (*change_mtu)(struct ksz_device *dev, int port, int mtu); + int (*pme_write8)(struct ksz_device *dev, u32 reg, u8 value); + int (*pme_pread8)(struct ksz_device *dev, int port, int offset, + u8 *data); + int (*pme_pwrite8)(struct ksz_device *dev, int port, int offset, + u8 data); void (*freeze_mib)(struct ksz_device *dev, int port, bool freeze); void (*port_init_cnt)(struct ksz_device *dev, int port); - void (*phylink_mac_config)(struct ksz_device *dev, int port, - unsigned int mode, - const struct phylink_link_state *state); void (*phylink_mac_link_up)(struct ksz_device *dev, int port, unsigned int mode, phy_interface_t interface, @@ -359,11 +435,6 @@ 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); @@ -374,6 +445,8 @@ struct ksz_dev_ops { struct ksz_device *ksz_switch_alloc(struct device *base, void *priv); int ksz_switch_register(struct ksz_device *dev); void ksz_switch_remove(struct ksz_device *dev); +int ksz_switch_suspend(struct device *dev); +int ksz_switch_resume(struct device *dev); void ksz_init_mib_timer(struct ksz_device *dev); bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port); @@ -387,6 +460,7 @@ 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); +int ksz_handle_wake_reason(struct ksz_device *dev, int port); /* Common register access functions */ static inline struct regmap *ksz_regmap_8(struct ksz_device *dev) @@ -617,7 +691,29 @@ static inline bool ksz_is_ksz87xx(struct ksz_device *dev) static inline bool ksz_is_ksz88x3(struct ksz_device *dev) { - return dev->chip_id == KSZ8830_CHIP_ID; + return dev->chip_id == KSZ88X3_CHIP_ID; +} + +static inline bool ksz_is_8895_family(struct ksz_device *dev) +{ + return dev->chip_id == KSZ8895_CHIP_ID || + dev->chip_id == KSZ8864_CHIP_ID; +} + +static inline bool is_ksz8(struct ksz_device *dev) +{ + return ksz_is_ksz87xx(dev) || ksz_is_ksz88x3(dev) || + ksz_is_8895_family(dev); +} + +static inline bool is_ksz88xx(struct ksz_device *dev) +{ + return ksz_is_ksz88x3(dev) || ksz_is_8895_family(dev); +} + +static inline bool is_ksz9477(struct ksz_device *dev) +{ + return dev->chip_id == KSZ9477_CHIP_ID; } static inline int is_lan937x(struct ksz_device *dev) @@ -629,6 +725,12 @@ static inline int is_lan937x(struct ksz_device *dev) dev->chip_id == LAN9374_CHIP_ID; } +static inline bool is_lan937x_tx_phy(struct ksz_device *dev, int port) +{ + return (dev->chip_id == LAN9371_CHIP_ID || + dev->chip_id == LAN9372_CHIP_ID) && port == KSZ_PORT_4; +} + /* STP State Defines */ #define PORT_TX_ENABLE BIT(2) #define PORT_RX_ENABLE BIT(1) @@ -640,6 +742,7 @@ static inline int is_lan937x(struct ksz_device *dev) #define SW_FAMILY_ID_M GENMASK(15, 8) #define KSZ87_FAMILY_ID 0x87 #define KSZ88_FAMILY_ID 0x88 +#define KSZ8895_FAMILY_ID 0x95 #define KSZ8_PORT_STATUS_0 0x08 #define KSZ8_PORT_FIBER_MODE BIT(7) @@ -648,6 +751,12 @@ static inline int is_lan937x(struct ksz_device *dev) #define KSZ87_CHIP_ID_94 0x6 #define KSZ87_CHIP_ID_95 0x9 #define KSZ88_CHIP_ID_63 0x3 +#define KSZ8895_CHIP_ID_95 0x4 +#define KSZ8895_CHIP_ID_95R 0x6 + +/* KSZ8895 specific register */ +#define REG_KSZ8864_CHIP_ID 0xFE +#define SW_KSZ8864 BIT(7) #define SW_REV_ID_M GENMASK(7, 4) @@ -680,6 +789,17 @@ static inline int is_lan937x(struct ksz_device *dev) #define P_MII_MAC_MODE BIT(2) #define P_MII_SEL_M 0x3 +/* KSZ9477, KSZ87xx Wake-on-LAN (WoL) masks */ +#define PME_WOL_MAGICPKT BIT(2) +#define PME_WOL_LINKUP BIT(1) +#define PME_WOL_ENERGY BIT(0) + +#define PME_ENABLE BIT(1) +#define PME_POLARITY BIT(0) + +#define KSZ87XX_REG_INT_EN 0x7D +#define KSZ87XX_INT_PME_MASK BIT(4) + /* Interrupt */ #define REG_SW_PORT_INT_STATUS__1 0x001B #define REG_SW_PORT_INT_MASK__1 0x001F @@ -722,7 +842,6 @@ static inline int is_lan937x(struct ksz_device *dev) #define KSZ9477_PORT_MRI_TC_MAP__4 0x0808 #define KSZ9477_PORT_TC_MAP_S 4 -#define KSZ9477_MAX_TC_PRIO 7 /* CBS related registers */ #define REG_PORT_MTI_QUEUE_INDEX__4 0x0900 diff --git a/drivers/net/dsa/microchip/ksz_dcb.c b/drivers/net/dsa/microchip/ksz_dcb.c new file mode 100644 index 000000000000..30b4a6186e38 --- /dev/null +++ b/drivers/net/dsa/microchip/ksz_dcb.c @@ -0,0 +1,819 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2024 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> + +#include <linux/dsa/ksz_common.h> +#include <net/dsa.h> +#include <net/dscp.h> +#include <net/ieee8021q.h> + +#include "ksz_common.h" +#include "ksz_dcb.h" +#include "ksz8.h" + +#define KSZ8_REG_PORT_1_CTRL_0 0x10 +#define KSZ8_PORT_DIFFSERV_ENABLE BIT(6) +#define KSZ8_PORT_802_1P_ENABLE BIT(5) +#define KSZ8_PORT_BASED_PRIO_M GENMASK(4, 3) + +#define KSZ88X3_REG_TOS_DSCP_CTRL 0x60 +#define KSZ8765_REG_TOS_DSCP_CTRL 0x90 + +#define KSZ9477_REG_SW_MAC_TOS_CTRL 0x033e +#define KSZ9477_SW_TOS_DSCP_REMAP BIT(0) +#define KSZ9477_SW_TOS_DSCP_DEFAULT_PRIO_M GENMASK(5, 3) + +#define KSZ9477_REG_DIFFSERV_PRIO_MAP 0x0340 + +#define KSZ9477_REG_PORT_MRI_PRIO_CTRL 0x0801 +#define KSZ9477_PORT_HIGHEST_PRIO BIT(7) +#define KSZ9477_PORT_OR_PRIO BIT(6) +#define KSZ9477_PORT_MAC_PRIO_ENABLE BIT(4) +#define KSZ9477_PORT_VLAN_PRIO_ENABLE BIT(3) +#define KSZ9477_PORT_802_1P_PRIO_ENABLE BIT(2) +#define KSZ9477_PORT_DIFFSERV_PRIO_ENABLE BIT(1) +#define KSZ9477_PORT_ACL_PRIO_ENABLE BIT(0) + +#define KSZ9477_REG_PORT_MRI_MAC_CTRL 0x0802 +#define KSZ9477_PORT_BASED_PRIO_M GENMASK(2, 0) + +struct ksz_apptrust_map { + u8 apptrust; + u8 bit; +}; + +static const struct ksz_apptrust_map ksz8_apptrust_map_to_bit[] = { + { DCB_APP_SEL_PCP, KSZ8_PORT_802_1P_ENABLE }, + { IEEE_8021QAZ_APP_SEL_DSCP, KSZ8_PORT_DIFFSERV_ENABLE }, +}; + +static const struct ksz_apptrust_map ksz9477_apptrust_map_to_bit[] = { + { DCB_APP_SEL_PCP, KSZ9477_PORT_802_1P_PRIO_ENABLE }, + { IEEE_8021QAZ_APP_SEL_DSCP, KSZ9477_PORT_DIFFSERV_PRIO_ENABLE }, +}; + +/* ksz_supported_apptrust[] - Supported apptrust selectors and Priority Order + * of Internal Priority Map (IPM) sources. + * + * This array defines the apptrust selectors supported by the hardware, where + * the index within the array indicates the priority of the selector - lower + * indices correspond to higher priority. This fixed priority scheme is due to + * the hardware's design, which does not support configurable priority among + * different priority sources. + * + * The priority sources, including Tail Tag, ACL, VLAN PCP and DSCP are ordered + * by the hardware's fixed logic, as detailed below. The order reflects a + * non-configurable precedence where certain types of priority information + * override others: + * + * 1. Tail Tag - Highest priority, overrides ACL, VLAN PCP, and DSCP priorities. + * 2. ACL - Overrides VLAN PCP and DSCP priorities. + * 3. VLAN PCP - Overrides DSCP priority. + * 4. DSCP - Lowest priority, does not override any other priority source. + * + * In this context, the array's lower index (higher priority) for + * 'DCB_APP_SEL_PCP' suggests its relative priority over + * 'IEEE_8021QAZ_APP_SEL_DSCP' within the system's fixed priority scheme. + * + * DCB_APP_SEL_PCP - Priority Code Point selector + * IEEE_8021QAZ_APP_SEL_DSCP - Differentiated Services Code Point selector + */ +static const u8 ksz_supported_apptrust[] = { + DCB_APP_SEL_PCP, + IEEE_8021QAZ_APP_SEL_DSCP, +}; + +static const char * const ksz_supported_apptrust_variants[] = { + "empty", "dscp", "pcp", "dscp pcp" +}; + +static void ksz_get_default_port_prio_reg(struct ksz_device *dev, int *reg, + u8 *mask, int *shift) +{ + if (is_ksz8(dev)) { + *reg = KSZ8_REG_PORT_1_CTRL_0; + *mask = KSZ8_PORT_BASED_PRIO_M; + *shift = __bf_shf(KSZ8_PORT_BASED_PRIO_M); + } else { + *reg = KSZ9477_REG_PORT_MRI_MAC_CTRL; + *mask = KSZ9477_PORT_BASED_PRIO_M; + *shift = __bf_shf(KSZ9477_PORT_BASED_PRIO_M); + } +} + +/** + * ksz_get_dscp_prio_reg - Retrieves the DSCP-to-priority-mapping register + * @dev: Pointer to the KSZ switch device structure + * @reg: Pointer to the register address to be set + * @per_reg: Pointer to the number of DSCP values per register + * @mask: Pointer to the mask to be set + * + * This function retrieves the DSCP to priority mapping register, the number of + * DSCP values per register, and the mask to be set. + */ +static void ksz_get_dscp_prio_reg(struct ksz_device *dev, int *reg, + int *per_reg, u8 *mask) +{ + if (ksz_is_ksz87xx(dev) || ksz_is_8895_family(dev)) { + *reg = KSZ8765_REG_TOS_DSCP_CTRL; + *per_reg = 4; + *mask = GENMASK(1, 0); + } else if (ksz_is_ksz88x3(dev)) { + *reg = KSZ88X3_REG_TOS_DSCP_CTRL; + *per_reg = 4; + *mask = GENMASK(1, 0); + } else { + *reg = KSZ9477_REG_DIFFSERV_PRIO_MAP; + *per_reg = 2; + *mask = GENMASK(2, 0); + } +} + +/** + * ksz_get_apptrust_map_and_reg - Retrieves the apptrust map and register + * @dev: Pointer to the KSZ switch device structure + * @map: Pointer to the apptrust map to be set + * @reg: Pointer to the register address to be set + * @mask: Pointer to the mask to be set + * + * This function retrieves the apptrust map and register address for the + * apptrust configuration. + */ +static void ksz_get_apptrust_map_and_reg(struct ksz_device *dev, + const struct ksz_apptrust_map **map, + int *reg, u8 *mask) +{ + if (is_ksz8(dev)) { + *map = ksz8_apptrust_map_to_bit; + *reg = KSZ8_REG_PORT_1_CTRL_0; + *mask = KSZ8_PORT_DIFFSERV_ENABLE | KSZ8_PORT_802_1P_ENABLE; + } else { + *map = ksz9477_apptrust_map_to_bit; + *reg = KSZ9477_REG_PORT_MRI_PRIO_CTRL; + *mask = KSZ9477_PORT_802_1P_PRIO_ENABLE | + KSZ9477_PORT_DIFFSERV_PRIO_ENABLE; + } +} + +/** + * ksz_port_get_default_prio - Retrieves the default priority for a port on a + * KSZ switch + * @ds: Pointer to the DSA switch structure + * @port: Port number from which to get the default priority + * + * This function fetches the default priority for the specified port on a KSZ + * switch. + * + * Return: The default priority of the port on success, or a negative error + * code on failure. + */ +int ksz_port_get_default_prio(struct dsa_switch *ds, int port) +{ + struct ksz_device *dev = ds->priv; + int ret, reg, shift; + u8 data, mask; + + ksz_get_default_port_prio_reg(dev, ®, &mask, &shift); + + ret = ksz_pread8(dev, port, reg, &data); + if (ret) + return ret; + + return (data & mask) >> shift; +} + +/** + * ksz88x3_port_set_default_prio_quirks - Quirks for default priority + * @dev: Pointer to the KSZ switch device structure + * @port: Port number for which to set the default priority + * @prio: Priority value to set + * + * This function implements quirks for setting the default priority on KSZ88x3 + * devices. On Port 2, no other priority providers are working + * except of PCP. So, configuring default priority on Port 2 is not possible. + * On Port 1, it is not possible to configure port priority if PCP + * apptrust on Port 2 is disabled. Since we disable multiple queues on the + * switch to disable PCP on Port 2, we need to ensure that the default priority + * configuration on Port 1 is in agreement with the configuration on Port 2. + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz88x3_port_set_default_prio_quirks(struct ksz_device *dev, int port, + u8 prio) +{ + if (!prio) + return 0; + + if (port == KSZ_PORT_2) { + dev_err(dev->dev, "Port priority configuration is not working on Port 2\n"); + return -EINVAL; + } else if (port == KSZ_PORT_1) { + u8 port2_data; + int ret; + + ret = ksz_pread8(dev, KSZ_PORT_2, KSZ8_REG_PORT_1_CTRL_0, + &port2_data); + if (ret) + return ret; + + if (!(port2_data & KSZ8_PORT_802_1P_ENABLE)) { + dev_err(dev->dev, "Not possible to configure port priority on Port 1 if PCP apptrust on Port 2 is disabled\n"); + return -EINVAL; + } + } + + return 0; +} + +/** + * ksz_port_set_default_prio - Sets the default priority for a port on a KSZ + * switch + * @ds: Pointer to the DSA switch structure + * @port: Port number for which to set the default priority + * @prio: Priority value to set + * + * This function sets the default priority for the specified port on a KSZ + * switch. + * + * Return: 0 on success, or a negative error code on failure. + */ +int ksz_port_set_default_prio(struct dsa_switch *ds, int port, u8 prio) +{ + struct ksz_device *dev = ds->priv; + int reg, shift, ret; + u8 mask; + + if (prio >= dev->info->num_ipms) + return -EINVAL; + + if (ksz_is_ksz88x3(dev)) { + ret = ksz88x3_port_set_default_prio_quirks(dev, port, prio); + if (ret) + return ret; + } + + ksz_get_default_port_prio_reg(dev, ®, &mask, &shift); + + return ksz_prmw8(dev, port, reg, mask, (prio << shift) & mask); +} + +/** + * ksz_port_get_dscp_prio - Retrieves the priority for a DSCP value on a KSZ + * switch + * @ds: Pointer to the DSA switch structure + * @port: Port number for which to get the priority + * @dscp: DSCP value for which to get the priority + * + * This function fetches the priority value from switch global DSCP-to-priorty + * mapping table for the specified DSCP value. + * + * Return: The priority value for the DSCP on success, or a negative error + * code on failure. + */ +int ksz_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp) +{ + struct ksz_device *dev = ds->priv; + int reg, per_reg, ret, shift; + u8 data, mask; + + ksz_get_dscp_prio_reg(dev, ®, &per_reg, &mask); + + /* If DSCP remapping is disabled, DSCP bits 3-5 are used as Internal + * Priority Map (IPM) + */ + if (!is_ksz8(dev)) { + ret = ksz_read8(dev, KSZ9477_REG_SW_MAC_TOS_CTRL, &data); + if (ret) + return ret; + + /* If DSCP remapping is disabled, DSCP bits 3-5 are used as + * Internal Priority Map (IPM) + */ + if (!(data & KSZ9477_SW_TOS_DSCP_REMAP)) + return FIELD_GET(KSZ9477_SW_TOS_DSCP_DEFAULT_PRIO_M, + dscp); + } + + /* In case DSCP remapping is enabled, we need to write the DSCP to + * priority mapping table. + */ + reg += dscp / per_reg; + ret = ksz_read8(dev, reg, &data); + if (ret) + return ret; + + shift = (dscp % per_reg) * (8 / per_reg); + + return (data >> shift) & mask; +} + +/** + * ksz_set_global_dscp_entry - Sets the global DSCP-to-priority mapping entry + * @dev: Pointer to the KSZ switch device structure + * @dscp: DSCP value for which to set the priority + * @ipm: Priority value to set + * + * This function sets the global DSCP-to-priority mapping entry for the + * specified DSCP value. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int ksz_set_global_dscp_entry(struct ksz_device *dev, u8 dscp, u8 ipm) +{ + int reg, per_reg, shift; + u8 mask; + + ksz_get_dscp_prio_reg(dev, ®, &per_reg, &mask); + + shift = (dscp % per_reg) * (8 / per_reg); + + return ksz_rmw8(dev, reg + (dscp / per_reg), mask << shift, + ipm << shift); +} + +/** + * ksz_init_global_dscp_map - Initializes the global DSCP-to-priority mapping + * @dev: Pointer to the KSZ switch device structure + * + * This function initializes the global DSCP-to-priority mapping table for the + * switch. + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz_init_global_dscp_map(struct ksz_device *dev) +{ + int ret, dscp; + + /* On KSZ9xxx variants, DSCP remapping is disabled by default. + * Enable to have, predictable and reproducible behavior across + * different devices. + */ + if (!is_ksz8(dev)) { + ret = ksz_rmw8(dev, KSZ9477_REG_SW_MAC_TOS_CTRL, + KSZ9477_SW_TOS_DSCP_REMAP, + KSZ9477_SW_TOS_DSCP_REMAP); + if (ret) + return ret; + } + + for (dscp = 0; dscp < DSCP_MAX; dscp++) { + int ipm, tt; + + /* Map DSCP to Traffic Type, which is corresponding to the + * Internal Priority Map (IPM) in the switch. + */ + if (!is_ksz8(dev)) { + ipm = ietf_dscp_to_ieee8021q_tt(dscp); + } else { + /* On KSZ8xxx variants we do not have IPM to queue + * remapping table. We need to convert DSCP to Traffic + * Type and then to queue. + */ + tt = ietf_dscp_to_ieee8021q_tt(dscp); + if (tt < 0) + return tt; + + ipm = ieee8021q_tt_to_tc(tt, dev->info->num_tx_queues); + } + + if (ipm < 0) + return ipm; + + ret = ksz_set_global_dscp_entry(dev, dscp, ipm); + } + + return 0; +} + +/** + * ksz_port_add_dscp_prio - Adds a DSCP-to-priority mapping entry for a port on + * a KSZ switch. + * @ds: Pointer to the DSA switch structure + * @port: Port number for which to add the DSCP-to-priority mapping entry + * @dscp: DSCP value for which to add the priority + * @prio: Priority value to set + * + * Return: 0 on success, or a negative error code on failure + */ +int ksz_port_add_dscp_prio(struct dsa_switch *ds, int port, u8 dscp, u8 prio) +{ + struct ksz_device *dev = ds->priv; + + if (prio >= dev->info->num_ipms) + return -ERANGE; + + return ksz_set_global_dscp_entry(dev, dscp, prio); +} + +/** + * ksz_port_del_dscp_prio - Deletes a DSCP-to-priority mapping entry for a port + * on a KSZ switch. + * @ds: Pointer to the DSA switch structure + * @port: Port number for which to delete the DSCP-to-priority mapping entry + * @dscp: DSCP value for which to delete the priority + * @prio: Priority value to delete + * + * Return: 0 on success, or a negative error code on failure + */ +int ksz_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp, u8 prio) +{ + struct ksz_device *dev = ds->priv; + int ipm; + + if (ksz_port_get_dscp_prio(ds, port, dscp) != prio) + return 0; + + if (is_ksz8(dev)) { + ipm = ieee8021q_tt_to_tc(IEEE8021Q_TT_BE, + dev->info->num_tx_queues); + if (ipm < 0) + return ipm; + } else { + ipm = IEEE8021Q_TT_BE; + } + + return ksz_set_global_dscp_entry(dev, dscp, ipm); +} + +/** + * ksz_apptrust_error - Prints an error message for an invalid apptrust selector + * @dev: Pointer to the KSZ switch device structure + * + * This function prints an error message when an invalid apptrust selector is + * provided. + */ +static void ksz_apptrust_error(struct ksz_device *dev) +{ + char supported_apptrust_variants[64]; + int i; + + supported_apptrust_variants[0] = '\0'; + for (i = 0; i < ARRAY_SIZE(ksz_supported_apptrust_variants); i++) { + if (i > 0) + strlcat(supported_apptrust_variants, ", ", + sizeof(supported_apptrust_variants)); + strlcat(supported_apptrust_variants, + ksz_supported_apptrust_variants[i], + sizeof(supported_apptrust_variants)); + } + + dev_err(dev->dev, "Invalid apptrust selector or priority order. Supported: %s\n", + supported_apptrust_variants); +} + +/** + * ksz_port_set_apptrust_validate - Validates the apptrust selectors + * @dev: Pointer to the KSZ switch device structure + * @port: Port number for which to set the apptrust selectors + * @sel: Array of apptrust selectors to validate + * @nsel: Number of apptrust selectors in the array + * + * This function validates the apptrust selectors provided and ensures that + * they are in the correct order. + * + * This family of switches supports two apptrust selectors: DCB_APP_SEL_PCP and + * IEEE_8021QAZ_APP_SEL_DSCP. The priority order of the selectors is fixed and + * cannot be changed. The order is as follows: + * 1. DCB_APP_SEL_PCP - Priority Code Point selector (highest priority) + * 2. IEEE_8021QAZ_APP_SEL_DSCP - Differentiated Services Code Point selector + * (lowest priority) + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz_port_set_apptrust_validate(struct ksz_device *dev, int port, + const u8 *sel, int nsel) +{ + int i, j, found; + int j_prev = 0; + + /* Iterate through the requested selectors */ + for (i = 0; i < nsel; i++) { + found = 0; + + /* Check if the current selector is supported by the hardware */ + for (j = 0; j < sizeof(ksz_supported_apptrust); j++) { + if (sel[i] != ksz_supported_apptrust[j]) + continue; + + found = 1; + + /* Ensure that no higher priority selector (lower index) + * precedes a lower priority one + */ + if (i > 0 && j <= j_prev) + goto err_sel_not_vaild; + + j_prev = j; + break; + } + + if (!found) + goto err_sel_not_vaild; + } + + return 0; + +err_sel_not_vaild: + ksz_apptrust_error(dev); + + return -EINVAL; +} + +/** + * ksz88x3_port1_apptrust_quirk - Quirk for apptrust configuration on Port 1 + * of KSZ88x3 devices + * @dev: Pointer to the KSZ switch device structure + * @port: Port number for which to set the apptrust selectors + * @reg: Register address for the apptrust configuration + * @port1_data: Data to set for the apptrust configuration + * + * This function implements a quirk for apptrust configuration on Port 1 of + * KSZ88x3 devices. It ensures that apptrust configuration on Port 1 is not + * possible if PCP apptrust on Port 2 is disabled. This is because the Port 2 + * seems to be permanently hardwired to PCP classification, so we need to + * do Port 1 configuration always in agreement with Port 2 configuration. + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz88x3_port1_apptrust_quirk(struct ksz_device *dev, int port, + int reg, u8 port1_data) +{ + u8 port2_data; + int ret; + + /* If no apptrust is requested for Port 1, no need to care about Port 2 + * configuration. + */ + if (!(port1_data & (KSZ8_PORT_802_1P_ENABLE | KSZ8_PORT_DIFFSERV_ENABLE))) + return 0; + + /* We got request to enable any apptrust on Port 1. To make it possible, + * we need to enable multiple queues on the switch. If we enable + * multiqueue support, PCP classification on Port 2 will be + * automatically activated by HW. + */ + ret = ksz_pread8(dev, KSZ_PORT_2, reg, &port2_data); + if (ret) + return ret; + + /* If KSZ8_PORT_802_1P_ENABLE bit is set on Port 2, the driver showed + * the interest in PCP classification on Port 2. In this case, + * multiqueue support is enabled and we can enable any apptrust on + * Port 1. + * If KSZ8_PORT_802_1P_ENABLE bit is not set on Port 2, the PCP + * classification on Port 2 is still active, but the driver disabled + * multiqueue support and made frame prioritization inactive for + * all ports. In this case, we can't enable any apptrust on Port 1. + */ + if (!(port2_data & KSZ8_PORT_802_1P_ENABLE)) { + dev_err(dev->dev, "Not possible to enable any apptrust on Port 1 if PCP apptrust on Port 2 is disabled\n"); + return -EINVAL; + } + + return 0; +} + +/** + * ksz88x3_port2_apptrust_quirk - Quirk for apptrust configuration on Port 2 + * of KSZ88x3 devices + * @dev: Pointer to the KSZ switch device structure + * @port: Port number for which to set the apptrust selectors + * @reg: Register address for the apptrust configuration + * @port2_data: Data to set for the apptrust configuration + * + * This function implements a quirk for apptrust configuration on Port 2 of + * KSZ88x3 devices. It ensures that DSCP apptrust is not working on Port 2 and + * that it is not possible to disable PCP on Port 2. The only way to disable PCP + * on Port 2 is to disable multiple queues on the switch. + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz88x3_port2_apptrust_quirk(struct ksz_device *dev, int port, + int reg, u8 port2_data) +{ + struct dsa_switch *ds = dev->ds; + u8 port1_data; + int ret; + + /* First validate Port 2 configuration. DiffServ/DSCP is not working + * on this port. + */ + if (port2_data & KSZ8_PORT_DIFFSERV_ENABLE) { + dev_err(dev->dev, "DSCP apptrust is not working on Port 2\n"); + return -EINVAL; + } + + /* If PCP support is requested, we need to enable all queues on the + * switch to make PCP priority working on Port 2. + */ + if (port2_data & KSZ8_PORT_802_1P_ENABLE) + return ksz8_all_queues_split(dev, dev->info->num_tx_queues); + + /* We got request to disable PCP priority on Port 2. + * Now, we need to compare Port 2 configuration with Port 1 + * configuration. + */ + ret = ksz_pread8(dev, KSZ_PORT_1, reg, &port1_data); + if (ret) + return ret; + + /* If Port 1 has any apptrust enabled, we can't disable multiple queues + * on the switch, so we can't disable PCP on Port 2. + */ + if (port1_data & (KSZ8_PORT_802_1P_ENABLE | KSZ8_PORT_DIFFSERV_ENABLE)) { + dev_err(dev->dev, "Not possible to disable PCP on Port 2 if any apptrust is enabled on Port 1\n"); + return -EINVAL; + } + + /* Now we need to ensure that default priority on Port 1 is set to 0 + * otherwise we can't disable multiqueue support on the switch. + */ + ret = ksz_port_get_default_prio(ds, KSZ_PORT_1); + if (ret < 0) { + return ret; + } else if (ret) { + dev_err(dev->dev, "Not possible to disable PCP on Port 2 if non zero default priority is set on Port 1\n"); + return -EINVAL; + } + + /* Port 1 has no apptrust or default priority set and we got request to + * disable PCP on Port 2. We can disable multiqueue support to disable + * PCP on Port 2. + */ + return ksz8_all_queues_split(dev, 1); +} + +/** + * ksz88x3_port_apptrust_quirk - Quirk for apptrust configuration on KSZ88x3 + * devices + * @dev: Pointer to the KSZ switch device structure + * @port: Port number for which to set the apptrust selectors + * @reg: Register address for the apptrust configuration + * @data: Data to set for the apptrust configuration + * + * This function implements a quirk for apptrust configuration on KSZ88x3 + * devices. It ensures that apptrust configuration on Port 1 and + * Port 2 is done in agreement with each other. + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz88x3_port_apptrust_quirk(struct ksz_device *dev, int port, + int reg, u8 data) +{ + if (port == KSZ_PORT_1) + return ksz88x3_port1_apptrust_quirk(dev, port, reg, data); + else if (port == KSZ_PORT_2) + return ksz88x3_port2_apptrust_quirk(dev, port, reg, data); + + return 0; +} + +/** + * ksz_port_set_apptrust - Sets the apptrust selectors for a port on a KSZ + * switch + * @ds: Pointer to the DSA switch structure + * @port: Port number for which to set the apptrust selectors + * @sel: Array of apptrust selectors to set + * @nsel: Number of apptrust selectors in the array + * + * This function sets the apptrust selectors for the specified port on a KSZ + * switch. + * + * Return: 0 on success, or a negative error code on failure + */ +int ksz_port_set_apptrust(struct dsa_switch *ds, int port, + const u8 *sel, int nsel) +{ + const struct ksz_apptrust_map *map; + struct ksz_device *dev = ds->priv; + int reg, i, ret; + u8 data = 0; + u8 mask; + + ret = ksz_port_set_apptrust_validate(dev, port, sel, nsel); + if (ret) + return ret; + + ksz_get_apptrust_map_and_reg(dev, &map, ®, &mask); + + for (i = 0; i < nsel; i++) { + int j; + + for (j = 0; j < ARRAY_SIZE(ksz_supported_apptrust); j++) { + if (sel[i] != ksz_supported_apptrust[j]) + continue; + + data |= map[j].bit; + break; + } + } + + if (ksz_is_ksz88x3(dev)) { + ret = ksz88x3_port_apptrust_quirk(dev, port, reg, data); + if (ret) + return ret; + } + + return ksz_prmw8(dev, port, reg, mask, data); +} + +/** + * ksz_port_get_apptrust - Retrieves the apptrust selectors for a port on a KSZ + * switch + * @ds: Pointer to the DSA switch structure + * @port: Port number for which to get the apptrust selectors + * @sel: Array to store the apptrust selectors + * @nsel: Number of apptrust selectors in the array + * + * This function fetches the apptrust selectors for the specified port on a KSZ + * switch. + * + * Return: 0 on success, or a negative error code on failure + */ +int ksz_port_get_apptrust(struct dsa_switch *ds, int port, u8 *sel, int *nsel) +{ + const struct ksz_apptrust_map *map; + struct ksz_device *dev = ds->priv; + int reg, i, ret; + u8 data; + u8 mask; + + ksz_get_apptrust_map_and_reg(dev, &map, ®, &mask); + + ret = ksz_pread8(dev, port, reg, &data); + if (ret) + return ret; + + *nsel = 0; + for (i = 0; i < ARRAY_SIZE(ksz_supported_apptrust); i++) { + if (data & map[i].bit) + sel[(*nsel)++] = ksz_supported_apptrust[i]; + } + + return 0; +} + +/** + * ksz_dcb_init_port - Initializes the DCB configuration for a port on a KSZ + * @dev: Pointer to the KSZ switch device structure + * @port: Port number for which to initialize the DCB configuration + * + * This function initializes the DCB configuration for the specified port on a + * KSZ switch. Particular DCB configuration is set for the port, including the + * default priority and apptrust selectors. + * The default priority is set to Best Effort, and the apptrust selectors are + * set to all supported selectors. + * + * Return: 0 on success, or a negative error code on failure + */ +int ksz_dcb_init_port(struct ksz_device *dev, int port) +{ + const u8 ksz_default_apptrust[] = { DCB_APP_SEL_PCP }; + int ret, ipm; + + if (is_ksz8(dev)) { + ipm = ieee8021q_tt_to_tc(IEEE8021Q_TT_BE, + dev->info->num_tx_queues); + if (ipm < 0) + return ipm; + } else { + ipm = IEEE8021Q_TT_BE; + } + + /* Set the default priority for the port to Best Effort */ + ret = ksz_port_set_default_prio(dev->ds, port, ipm); + if (ret) + return ret; + + return ksz_port_set_apptrust(dev->ds, port, ksz_default_apptrust, + ARRAY_SIZE(ksz_default_apptrust)); +} + +/** + * ksz_dcb_init - Initializes the DCB configuration for a KSZ switch + * @dev: Pointer to the KSZ switch device structure + * + * This function initializes the DCB configuration for a KSZ switch. The global + * DSCP-to-priority mapping table is initialized. + * + * Return: 0 on success, or a negative error code on failure + */ +int ksz_dcb_init(struct ksz_device *dev) +{ + int ret; + + ret = ksz_init_global_dscp_map(dev); + if (ret) + return ret; + + /* Enable 802.1p priority control on Port 2 during switch initialization. + * This setup is critical for the apptrust functionality on Port 1, which + * relies on the priority settings of Port 2. Note: Port 1 is naturally + * configured before Port 2, necessitating this configuration order. + */ + if (ksz_is_ksz88x3(dev)) + return ksz_prmw8(dev, KSZ_PORT_2, KSZ8_REG_PORT_1_CTRL_0, + KSZ8_PORT_802_1P_ENABLE, + KSZ8_PORT_802_1P_ENABLE); + + return 0; +} diff --git a/drivers/net/dsa/microchip/ksz_dcb.h b/drivers/net/dsa/microchip/ksz_dcb.h new file mode 100644 index 000000000000..e2065223ba90 --- /dev/null +++ b/drivers/net/dsa/microchip/ksz_dcb.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2024 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> */ + +#ifndef __KSZ_DCB_H +#define __KSZ_DCB_H + +#include <net/dsa.h> + +#include "ksz_common.h" + +int ksz_port_get_default_prio(struct dsa_switch *ds, int port); +int ksz_port_set_default_prio(struct dsa_switch *ds, int port, u8 prio); +int ksz_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp); +int ksz_port_add_dscp_prio(struct dsa_switch *ds, int port, u8 dscp, u8 prio); +int ksz_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp, u8 prio); +int ksz_port_set_apptrust(struct dsa_switch *ds, int port, + const unsigned char *sel, + int nsel); +int ksz_port_get_apptrust(struct dsa_switch *ds, int port, u8 *sel, int *nsel); +int ksz_dcb_init_port(struct ksz_device *dev, int port); +int ksz_dcb_init(struct ksz_device *dev); + +#endif /* __KSZ_DCB_H */ diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c index 1fe105913c75..22fb9ef4645c 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.c +++ b/drivers/net/dsa/microchip/ksz_ptp.c @@ -266,7 +266,6 @@ static int ksz_ptp_enable_mode(struct ksz_device *dev) struct ksz_port *prt; struct dsa_port *dp; bool tag_en = false; - int ret; dsa_switch_for_each_user_port(dp, dev->ds) { prt = &dev->ports[dp->index]; @@ -277,9 +276,7 @@ static int ksz_ptp_enable_mode(struct ksz_device *dev) } if (tag_en) { - ret = ptp_schedule_worker(ptp_data->clock, 0); - if (ret) - return ret; + ptp_schedule_worker(ptp_data->clock, 0); } else { ptp_cancel_worker_sync(ptp_data->clock); } @@ -293,7 +290,7 @@ static int ksz_ptp_enable_mode(struct ksz_device *dev) /* The function is return back the capability of timestamping feature when * requested through ethtool -T <interface> utility */ -int ksz_get_ts_info(struct dsa_switch *ds, int port, struct ethtool_ts_info *ts) +int ksz_get_ts_info(struct dsa_switch *ds, int port, struct kernel_ethtool_ts_info *ts) { struct ksz_device *dev = ds->priv; struct ksz_ptp_data *ptp_data; @@ -1109,7 +1106,7 @@ static int ksz_ptp_msg_irq_setup(struct ksz_port *port, u8 n) ptpmsg_irq->port = port; ptpmsg_irq->ts_reg = ops->get_port_addr(port->num, ts_reg[n]); - snprintf(ptpmsg_irq->name, sizeof(ptpmsg_irq->name), name[n]); + strscpy(ptpmsg_irq->name, name[n]); ptpmsg_irq->num = irq_find_mapping(port->ptpirq.domain, n); if (ptpmsg_irq->num < 0) diff --git a/drivers/net/dsa/microchip/ksz_ptp.h b/drivers/net/dsa/microchip/ksz_ptp.h index 0ca8ca4f804e..2f1783c0d723 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.h +++ b/drivers/net/dsa/microchip/ksz_ptp.h @@ -38,7 +38,7 @@ int ksz_ptp_clock_register(struct dsa_switch *ds); void ksz_ptp_clock_unregister(struct dsa_switch *ds); int ksz_get_ts_info(struct dsa_switch *ds, int port, - struct ethtool_ts_info *ts); + struct kernel_ethtool_ts_info *ts); int ksz_hwtstamp_get(struct dsa_switch *ds, int port, struct ifreq *ifr); int ksz_hwtstamp_set(struct dsa_switch *ds, int port, struct ifreq *ifr); void ksz_port_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb); diff --git a/drivers/net/dsa/microchip/ksz_spi.c b/drivers/net/dsa/microchip/ksz_spi.c index c8166fb440ab..b633d263098c 100644 --- a/drivers/net/dsa/microchip/ksz_spi.c +++ b/drivers/net/dsa/microchip/ksz_spi.c @@ -2,11 +2,11 @@ /* * Microchip ksz series register access through SPI * - * Copyright (C) 2017 Microchip Technology Inc. + * Copyright (C) 2017-2024 Microchip Technology Inc. * Tristram Ha <Tristram.Ha@microchip.com> */ -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/delay.h> #include <linux/kernel.h> @@ -54,12 +54,17 @@ static int ksz_spi_probe(struct spi_device *spi) if (!chip) return -EINVAL; - if (chip->chip_id == KSZ8830_CHIP_ID) + /* Save chip id to do special initialization when probing. */ + dev->chip_id = chip->chip_id; + if (chip->chip_id == KSZ88X3_CHIP_ID) regmap_config = ksz8863_regmap_config; else if (chip->chip_id == KSZ8795_CHIP_ID || chip->chip_id == KSZ8794_CHIP_ID || chip->chip_id == KSZ8765_CHIP_ID) regmap_config = ksz8795_regmap_config; + else if (chip->chip_id == KSZ8895_CHIP_ID || + chip->chip_id == KSZ8864_CHIP_ID) + regmap_config = ksz8863_regmap_config; else regmap_config = ksz9477_regmap_config; @@ -134,11 +139,19 @@ static const struct of_device_id ksz_dt_ids[] = { }, { .compatible = "microchip,ksz8863", - .data = &ksz_switch_chips[KSZ8830] + .data = &ksz_switch_chips[KSZ88X3] + }, + { + .compatible = "microchip,ksz8864", + .data = &ksz_switch_chips[KSZ8864] }, { .compatible = "microchip,ksz8873", - .data = &ksz_switch_chips[KSZ8830] + .data = &ksz_switch_chips[KSZ88X3] + }, + { + .compatible = "microchip,ksz8895", + .data = &ksz_switch_chips[KSZ8895] }, { .compatible = "microchip,ksz9477", @@ -192,6 +205,10 @@ static const struct of_device_id ksz_dt_ids[] = { .compatible = "microchip,lan9374", .data = &ksz_switch_chips[LAN9374] }, + { + .compatible = "microchip,lan9646", + .data = &ksz_switch_chips[LAN9646] + }, {}, }; MODULE_DEVICE_TABLE(of, ksz_dt_ids); @@ -201,7 +218,9 @@ static const struct spi_device_id ksz_spi_ids[] = { { "ksz8794" }, { "ksz8795" }, { "ksz8863" }, + { "ksz8864" }, { "ksz8873" }, + { "ksz8895" }, { "ksz9477" }, { "ksz9896" }, { "ksz9897" }, @@ -215,15 +234,19 @@ static const struct spi_device_id ksz_spi_ids[] = { { "lan9372" }, { "lan9373" }, { "lan9374" }, + { "lan9646" }, { }, }; MODULE_DEVICE_TABLE(spi, ksz_spi_ids); +static DEFINE_SIMPLE_DEV_PM_OPS(ksz_spi_pm_ops, + ksz_switch_suspend, ksz_switch_resume); + static struct spi_driver ksz_spi_driver = { .driver = { .name = "ksz-switch", - .owner = THIS_MODULE, .of_match_table = ksz_dt_ids, + .pm = &ksz_spi_pm_ops, }, .id_table = ksz_spi_ids, .probe = ksz_spi_probe, @@ -233,13 +256,6 @@ static struct spi_driver ksz_spi_driver = { module_spi_driver(ksz_spi_driver); -MODULE_ALIAS("spi:ksz9477"); -MODULE_ALIAS("spi:ksz9896"); -MODULE_ALIAS("spi:ksz9897"); -MODULE_ALIAS("spi:ksz9893"); -MODULE_ALIAS("spi:ksz9563"); -MODULE_ALIAS("spi:ksz8563"); -MODULE_ALIAS("spi:ksz9567"); MODULE_ALIAS("spi:lan937x"); MODULE_AUTHOR("Tristram Ha <Tristram.Ha@microchip.com>"); MODULE_DESCRIPTION("Microchip ksz Series Switch SPI Driver"); diff --git a/drivers/net/dsa/microchip/lan937x.h b/drivers/net/dsa/microchip/lan937x.h index 3388d91dbc44..df13ebbd356f 100644 --- a/drivers/net/dsa/microchip/lan937x.h +++ b/drivers/net/dsa/microchip/lan937x.h @@ -13,6 +13,8 @@ void lan937x_port_setup(struct ksz_device *dev, int port, bool cpu_port); void lan937x_config_cpu_port(struct dsa_switch *ds); int lan937x_switch_init(struct ksz_device *dev); void lan937x_switch_exit(struct ksz_device *dev); +int lan937x_mdio_bus_preinit(struct ksz_device *dev, bool side_mdio); +int lan937x_create_phy_addr_map(struct ksz_device *dev, bool side_mdio); int lan937x_r_phy(struct ksz_device *dev, u16 addr, u16 reg, u16 *data); int lan937x_w_phy(struct ksz_device *dev, u16 addr, u16 reg, u16 val); int lan937x_change_mtu(struct ksz_device *dev, int port, int new_mtu); diff --git a/drivers/net/dsa/microchip/lan937x_main.c b/drivers/net/dsa/microchip/lan937x_main.c index b479a628b1ae..b1ae3b9de3d1 100644 --- a/drivers/net/dsa/microchip/lan937x_main.c +++ b/drivers/net/dsa/microchip/lan937x_main.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* Microchip LAN937X switch driver main logic - * Copyright (C) 2019-2022 Microchip Technology Inc. + * Copyright (C) 2019-2024 Microchip Technology Inc. */ #include <linux/kernel.h> #include <linux/module.h> @@ -18,6 +18,87 @@ #include "ksz9477.h" #include "lan937x.h" +/* marker for ports without built-in PHY */ +#define LAN937X_NO_PHY U8_MAX + +/* + * lan9370_phy_addr - Mapping of LAN9370 switch ports to PHY addresses. + * + * Each entry corresponds to a specific port on the LAN9370 switch, + * where ports 1-4 are connected to integrated 100BASE-T1 PHYs, and + * Port 5 is connected to an RGMII interface without a PHY. The values + * are based on the documentation (DS00003108E, section 3.3). + */ +static const u8 lan9370_phy_addr[] = { + [0] = 2, /* Port 1, T1 AFE0 */ + [1] = 3, /* Port 2, T1 AFE1 */ + [2] = 5, /* Port 3, T1 AFE3 */ + [3] = 6, /* Port 4, T1 AFE4 */ + [4] = LAN937X_NO_PHY, /* Port 5, RGMII 2 */ +}; + +/* + * lan9371_phy_addr - Mapping of LAN9371 switch ports to PHY addresses. + * + * The values are based on the documentation (DS00003109E, section 3.3). + */ +static const u8 lan9371_phy_addr[] = { + [0] = 2, /* Port 1, T1 AFE0 */ + [1] = 3, /* Port 2, T1 AFE1 */ + [2] = 5, /* Port 3, T1 AFE3 */ + [3] = 8, /* Port 4, TX PHY */ + [4] = LAN937X_NO_PHY, /* Port 5, RGMII 2 */ + [5] = LAN937X_NO_PHY, /* Port 6, RGMII 1 */ +}; + +/* + * lan9372_phy_addr - Mapping of LAN9372 switch ports to PHY addresses. + * + * The values are based on the documentation (DS00003110F, section 3.3). + */ +static const u8 lan9372_phy_addr[] = { + [0] = 2, /* Port 1, T1 AFE0 */ + [1] = 3, /* Port 2, T1 AFE1 */ + [2] = 5, /* Port 3, T1 AFE3 */ + [3] = 8, /* Port 4, TX PHY */ + [4] = LAN937X_NO_PHY, /* Port 5, RGMII 2 */ + [5] = LAN937X_NO_PHY, /* Port 6, RGMII 1 */ + [6] = 6, /* Port 7, T1 AFE4 */ + [7] = 4, /* Port 8, T1 AFE2 */ +}; + +/* + * lan9373_phy_addr - Mapping of LAN9373 switch ports to PHY addresses. + * + * The values are based on the documentation (DS00003110F, section 3.3). + */ +static const u8 lan9373_phy_addr[] = { + [0] = 2, /* Port 1, T1 AFE0 */ + [1] = 3, /* Port 2, T1 AFE1 */ + [2] = 5, /* Port 3, T1 AFE3 */ + [3] = LAN937X_NO_PHY, /* Port 4, SGMII */ + [4] = LAN937X_NO_PHY, /* Port 5, RGMII 2 */ + [5] = LAN937X_NO_PHY, /* Port 6, RGMII 1 */ + [6] = 6, /* Port 7, T1 AFE4 */ + [7] = 4, /* Port 8, T1 AFE2 */ +}; + +/* + * lan9374_phy_addr - Mapping of LAN9374 switch ports to PHY addresses. + * + * The values are based on the documentation (DS00003110F, section 3.3). + */ +static const u8 lan9374_phy_addr[] = { + [0] = 2, /* Port 1, T1 AFE0 */ + [1] = 3, /* Port 2, T1 AFE1 */ + [2] = 5, /* Port 3, T1 AFE3 */ + [3] = 7, /* Port 4, T1 AFE5 */ + [4] = LAN937X_NO_PHY, /* Port 5, RGMII 2 */ + [5] = LAN937X_NO_PHY, /* Port 6, RGMII 1 */ + [6] = 6, /* Port 7, T1 AFE4 */ + [7] = 4, /* Port 8, T1 AFE2 */ +}; + static int lan937x_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set) { return regmap_update_bits(ksz_regmap_8(dev), addr, bits, set ? bits : 0); @@ -30,24 +111,144 @@ static int lan937x_port_cfg(struct ksz_device *dev, int port, int offset, bits, set ? bits : 0); } -static int lan937x_enable_spi_indirect_access(struct ksz_device *dev) +/** + * lan937x_create_phy_addr_map - Create port-to-PHY address map for MDIO bus. + * @dev: Pointer to device structure. + * @side_mdio: Boolean indicating if the PHYs are accessed over a side MDIO bus. + * + * This function sets up the PHY address mapping for the LAN937x switches, + * which support two access modes for internal PHYs: + * 1. **SPI Access**: A straightforward one-to-one port-to-PHY address + * mapping is applied. + * 2. **MDIO Access**: The PHY address mapping varies based on chip variant + * and strap configuration. An offset is calculated based on strap settings + * to ensure correct PHY addresses are assigned. The offset calculation logic + * is based on Microchip's Article Number 000015828, available at: + * https://microchip.my.site.com/s/article/LAN9374-Virtual-PHY-PHY-Address-Mapping + * + * The function first checks if side MDIO access is disabled, in which case a + * simple direct mapping (port number = PHY address) is applied. If side MDIO + * access is enabled, it reads the strap configuration to determine the correct + * offset for PHY addresses. + * + * The appropriate mapping table is selected based on the chip ID, and the + * `phy_addr_map` is populated with the correct addresses for each port. Any + * port with no PHY is assigned a `LAN937X_NO_PHY` marker. + * + * Return: 0 on success, error code on failure. + */ +int lan937x_create_phy_addr_map(struct ksz_device *dev, bool side_mdio) +{ + static const u8 *phy_addr_map; + u32 strap_val; + u8 offset = 0; + size_t size; + int ret, i; + + if (!side_mdio) { + /* simple direct mapping */ + for (i = 0; i < dev->info->port_cnt; i++) + dev->phy_addr_map[i] = i; + + return 0; + } + + ret = ksz_read32(dev, REG_SW_CFG_STRAP_VAL, &strap_val); + if (ret < 0) + return ret; + + if (!(strap_val & SW_CASCADE_ID_CFG) && !(strap_val & SW_VPHY_ADD_CFG)) + offset = 0; + else if (!(strap_val & SW_CASCADE_ID_CFG) && (strap_val & SW_VPHY_ADD_CFG)) + offset = 7; + else if ((strap_val & SW_CASCADE_ID_CFG) && !(strap_val & SW_VPHY_ADD_CFG)) + offset = 15; + else + offset = 22; + + switch (dev->info->chip_id) { + case LAN9370_CHIP_ID: + phy_addr_map = lan9370_phy_addr; + size = ARRAY_SIZE(lan9370_phy_addr); + break; + case LAN9371_CHIP_ID: + phy_addr_map = lan9371_phy_addr; + size = ARRAY_SIZE(lan9371_phy_addr); + break; + case LAN9372_CHIP_ID: + phy_addr_map = lan9372_phy_addr; + size = ARRAY_SIZE(lan9372_phy_addr); + break; + case LAN9373_CHIP_ID: + phy_addr_map = lan9373_phy_addr; + size = ARRAY_SIZE(lan9373_phy_addr); + break; + case LAN9374_CHIP_ID: + phy_addr_map = lan9374_phy_addr; + size = ARRAY_SIZE(lan9374_phy_addr); + break; + default: + return -EINVAL; + } + + if (size < dev->info->port_cnt) + return -EINVAL; + + for (i = 0; i < dev->info->port_cnt; i++) { + if (phy_addr_map[i] == LAN937X_NO_PHY) + dev->phy_addr_map[i] = phy_addr_map[i]; + else + dev->phy_addr_map[i] = phy_addr_map[i] + offset; + } + + return 0; +} + +/** + * lan937x_mdio_bus_preinit - Pre-initialize MDIO bus for accessing PHYs. + * @dev: Pointer to device structure. + * @side_mdio: Boolean indicating if the PHYs are accessed over a side MDIO bus. + * + * This function configures the LAN937x switch for PHY access either through + * SPI or the side MDIO bus, unlocking the necessary registers for each access + * mode. + * + * Operation Modes: + * 1. **SPI Access**: Enables SPI indirect access to address clock domain + * crossing issues when SPI is used for PHY access. + * 2. **MDIO Access**: Grants access to internal PHYs over the side MDIO bus, + * required when using the MDIO bus for PHY management. + * + * Return: 0 on success, error code on failure. + */ +int lan937x_mdio_bus_preinit(struct ksz_device *dev, bool side_mdio) { u16 data16; int ret; - /* Enable Phy access through SPI */ + /* Unlock access to the PHYs, needed for SPI and side MDIO access */ ret = lan937x_cfg(dev, REG_GLOBAL_CTRL_0, SW_PHY_REG_BLOCK, false); if (ret < 0) - return ret; + goto print_error; - ret = ksz_read16(dev, REG_VPHY_SPECIAL_CTRL__2, &data16); - if (ret < 0) - return ret; + if (side_mdio) + /* Allow access to internal PHYs over MDIO bus */ + data16 = VPHY_MDIO_INTERNAL_ENABLE; + else + /* Enable SPI indirect access to address clock domain crossing + * issue + */ + data16 = VPHY_SPI_INDIRECT_ENABLE; - /* Allow SPI access */ - data16 |= VPHY_SPI_INDIRECT_ENABLE; + ret = ksz_rmw16(dev, REG_VPHY_SPECIAL_CTRL__2, + VPHY_SPI_INDIRECT_ENABLE | VPHY_MDIO_INTERNAL_ENABLE, + data16); - return ksz_write16(dev, REG_VPHY_SPECIAL_CTRL__2, data16); +print_error: + if (ret < 0) + dev_err(dev->dev, "failed to preinit the MDIO bus\n"); + + return ret; } static int lan937x_vphy_ind_addr_wr(struct ksz_device *dev, int addr, int reg) @@ -55,6 +256,9 @@ static int lan937x_vphy_ind_addr_wr(struct ksz_device *dev, int addr, int reg) u16 addr_base = REG_PORT_T1_PHY_CTRL_BASE; u16 temp; + if (is_lan937x_tx_phy(dev, addr)) + addr_base = REG_PORT_TX_PHY_CTRL_BASE; + /* get register address based on the logical port */ temp = PORT_CTRL_ADDR(addr, (addr_base + (reg << 2))); @@ -257,10 +461,66 @@ int lan937x_change_mtu(struct ksz_device *dev, int port, int new_mtu) int lan937x_set_ageing_time(struct ksz_device *dev, unsigned int msecs) { - u32 secs = msecs / 1000; - u32 value; + u8 data, mult, value8; + bool in_msec = false; + u32 max_val, value; + u32 secs = msecs; int ret; +#define MAX_TIMER_VAL ((1 << 20) - 1) + + /* The aging timer comprises a 3-bit multiplier and a 20-bit second + * value. Either of them cannot be zero. The maximum timer is then + * 7 * 1048575 = 7340025 seconds. As this value is too large for + * practical use it can be interpreted as microseconds, making the + * maximum timer 7340 seconds with finer control. This allows for + * maximum 122 minutes compared to 29 minutes in KSZ9477 switch. + */ + if (msecs % 1000) + in_msec = true; + else + secs /= 1000; + if (!secs) + secs = 1; + + /* Return error if too large. */ + else if (secs > 7 * MAX_TIMER_VAL) + return -EINVAL; + + /* Configure how to interpret the number value. */ + ret = ksz_rmw8(dev, REG_SW_LUE_CTRL_2, SW_AGE_CNT_IN_MICROSEC, + in_msec ? SW_AGE_CNT_IN_MICROSEC : 0); + if (ret < 0) + return ret; + + ret = ksz_read8(dev, REG_SW_LUE_CTRL_0, &value8); + if (ret < 0) + return ret; + + /* Check whether there is need to update the multiplier. */ + mult = FIELD_GET(SW_AGE_CNT_M, value8); + max_val = MAX_TIMER_VAL; + if (mult > 0) { + /* Try to use the same multiplier already in the register as + * the hardware default uses multiplier 4 and 75 seconds for + * 300 seconds. + */ + max_val = DIV_ROUND_UP(secs, mult); + if (max_val > MAX_TIMER_VAL || max_val * mult != secs) + max_val = MAX_TIMER_VAL; + } + + data = DIV_ROUND_UP(secs, max_val); + if (mult != data) { + value8 &= ~SW_AGE_CNT_M; + value8 |= FIELD_PREP(SW_AGE_CNT_M, data); + ret = ksz_write8(dev, REG_SW_LUE_CTRL_0, value8); + if (ret < 0) + return ret; + } + + secs = DIV_ROUND_UP(secs, data); + value = FIELD_GET(SW_AGE_PERIOD_7_0_M, secs); ret = ksz_write8(dev, REG_SW_AGE_PERIOD__1, value); @@ -320,6 +580,9 @@ void lan937x_phylink_get_caps(struct ksz_device *dev, int port, /* MII/RMII/RGMII ports */ config->mac_capabilities |= MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_100HD | MAC_10 | MAC_1000FD; + } else if (is_lan937x_tx_phy(dev, port)) { + config->mac_capabilities |= MAC_ASYM_PAUSE | MAC_SYM_PAUSE | + MAC_100HD | MAC_10; } } @@ -357,36 +620,39 @@ int lan937x_setup(struct dsa_switch *ds) struct ksz_device *dev = ds->priv; int ret; - /* enable Indirect Access from SPI to the VPHY registers */ - ret = lan937x_enable_spi_indirect_access(dev); - if (ret < 0) { - dev_err(dev->dev, "failed to enable spi indirect access"); - return ret; - } - /* The VLAN aware is a global setting. Mixed vlan * filterings are not supported. */ ds->vlan_filtering_is_global = true; /* Enable aggressive back off for half duplex & UNH mode */ - lan937x_cfg(dev, REG_SW_MAC_CTRL_0, - (SW_PAUSE_UNH_MODE | SW_NEW_BACKOFF | SW_AGGR_BACKOFF), - true); + ret = lan937x_cfg(dev, REG_SW_MAC_CTRL_0, (SW_PAUSE_UNH_MODE | + SW_NEW_BACKOFF | + SW_AGGR_BACKOFF), true); + if (ret < 0) + return ret; /* If NO_EXC_COLLISION_DROP bit is set, the switch will not drop * packets when 16 or more collisions occur */ - lan937x_cfg(dev, REG_SW_MAC_CTRL_1, NO_EXC_COLLISION_DROP, true); + ret = lan937x_cfg(dev, REG_SW_MAC_CTRL_1, NO_EXC_COLLISION_DROP, true); + if (ret < 0) + return ret; /* enable global MIB counter freeze function */ - lan937x_cfg(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FREEZE, true); + ret = lan937x_cfg(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FREEZE, true); + if (ret < 0) + return ret; /* disable CLK125 & CLK25, 1: disable, 0: enable */ - lan937x_cfg(dev, REG_SW_GLOBAL_OUTPUT_CTRL__1, - (SW_CLK125_ENB | SW_CLK25_ENB), true); + ret = lan937x_cfg(dev, REG_SW_GLOBAL_OUTPUT_CTRL__1, + (SW_CLK125_ENB | SW_CLK25_ENB), true); + if (ret < 0) + return ret; - return 0; + /* Disable global VPHY support. Related to CPU interface only? */ + return ksz_rmw32(dev, REG_SW_CFG_STRAP_OVR, SW_VPHY_DISABLE, + SW_VPHY_DISABLE); } void lan937x_teardown(struct dsa_switch *ds) diff --git a/drivers/net/dsa/microchip/lan937x_reg.h b/drivers/net/dsa/microchip/lan937x_reg.h index 45b606b6429f..72042fd64e5b 100644 --- a/drivers/net/dsa/microchip/lan937x_reg.h +++ b/drivers/net/dsa/microchip/lan937x_reg.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* Microchip LAN937X switch register definitions - * Copyright (C) 2019-2021 Microchip Technology Inc. + * Copyright (C) 2019-2024 Microchip Technology Inc. */ #ifndef __LAN937X_REG_H #define __LAN937X_REG_H @@ -37,6 +37,14 @@ #define SW_CLK125_ENB BIT(1) #define SW_CLK25_ENB BIT(0) +#define REG_SW_CFG_STRAP_VAL 0x0200 +#define SW_CASCADE_ID_CFG BIT(15) +#define SW_VPHY_ADD_CFG BIT(0) + +/* 2 - PHY Control */ +#define REG_SW_CFG_STRAP_OVR 0x0214 +#define SW_VPHY_DISABLE BIT(31) + /* 3 - Operation Control */ #define REG_SW_OPERATION 0x0300 @@ -48,8 +56,7 @@ #define SW_VLAN_ENABLE BIT(7) #define SW_DROP_INVALID_VID BIT(6) -#define SW_AGE_CNT_M 0x7 -#define SW_AGE_CNT_S 3 +#define SW_AGE_CNT_M GENMASK(5, 3) #define SW_RESV_MCAST_ENABLE BIT(2) #define REG_SW_LUE_CTRL_1 0x0311 @@ -62,6 +69,10 @@ #define SW_FAST_AGING BIT(1) #define SW_LINK_AUTO_AGING BIT(0) +#define REG_SW_LUE_CTRL_2 0x0312 + +#define SW_AGE_CNT_IN_MICROSEC BIT(7) + #define REG_SW_AGE_PERIOD__1 0x0313 #define SW_AGE_PERIOD_7_0_M GENMASK(7, 0) @@ -147,6 +158,7 @@ /* 1 - Phy */ #define REG_PORT_T1_PHY_CTRL_BASE 0x0100 +#define REG_PORT_TX_PHY_CTRL_BASE 0x0280 /* 3 - xMII */ #define PORT_SGMII_SEL BIT(7) |