diff options
Diffstat (limited to 'drivers/net/dsa/microchip')
| -rw-r--r-- | drivers/net/dsa/microchip/Kconfig | 1 | ||||
| -rw-r--r-- | drivers/net/dsa/microchip/ksz8.c | 191 | ||||
| -rw-r--r-- | drivers/net/dsa/microchip/ksz8.h | 4 | ||||
| -rw-r--r-- | drivers/net/dsa/microchip/ksz8_reg.h | 53 | ||||
| -rw-r--r-- | drivers/net/dsa/microchip/ksz9477.c | 194 | ||||
| -rw-r--r-- | drivers/net/dsa/microchip/ksz9477.h | 4 | ||||
| -rw-r--r-- | drivers/net/dsa/microchip/ksz_common.c | 292 | ||||
| -rw-r--r-- | drivers/net/dsa/microchip/ksz_common.h | 81 | ||||
| -rw-r--r-- | drivers/net/dsa/microchip/ksz_dcb.c | 10 | ||||
| -rw-r--r-- | drivers/net/dsa/microchip/ksz_ptp.c | 30 | ||||
| -rw-r--r-- | drivers/net/dsa/microchip/ksz_ptp.h | 7 | ||||
| -rw-r--r-- | drivers/net/dsa/microchip/ksz_spi.c | 104 | 
12 files changed, 908 insertions, 63 deletions
diff --git a/drivers/net/dsa/microchip/Kconfig b/drivers/net/dsa/microchip/Kconfig index 12a86585a77f..c71d3fd5dfeb 100644 --- a/drivers/net/dsa/microchip/Kconfig +++ b/drivers/net/dsa/microchip/Kconfig @@ -6,6 +6,7 @@ menuconfig NET_DSA_MICROCHIP_KSZ_COMMON  	select NET_DSA_TAG_NONE  	select NET_IEEE8021Q_HELPERS  	select DCB +	select PCS_XPCS  	help  	  This driver adds support for Microchip KSZ8, KSZ9 and  	  LAN937X series switch chips, being KSZ8863/8873, diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index be433b4e2b1c..76e490070e9c 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -3,6 +3,7 @@   * Microchip KSZ8XXX series switch driver   *   * It supports the following switches: + * - KSZ8463   * - KSZ8863, KSZ8873 aka KSZ88X3   * - KSZ8895, KSZ8864 aka KSZ8895 family   * - KSZ8794, KSZ8795, KSZ8765 aka KSZ87XX @@ -41,7 +42,8 @@ static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set)  static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits,  			 bool set)  { -	regmap_update_bits(ksz_regmap_8(dev), PORT_CTRL_ADDR(port, offset), +	regmap_update_bits(ksz_regmap_8(dev), +			   dev->dev_ops->get_port_addr(port, offset),  			   bits, set ? bits : 0);  } @@ -140,6 +142,11 @@ int ksz8_reset_switch(struct ksz_device *dev)  			KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, true);  		ksz_cfg(dev, KSZ8863_REG_SW_RESET,  			KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, false); +	} else if (ksz_is_ksz8463(dev)) { +		ksz_cfg(dev, KSZ8463_REG_SW_RESET, +			KSZ8463_GLOBAL_SOFTWARE_RESET, true); +		ksz_cfg(dev, KSZ8463_REG_SW_RESET, +			KSZ8463_GLOBAL_SOFTWARE_RESET, false);  	} else {  		/* reset switch */  		ksz_write8(dev, REG_POWER_MANAGEMENT_1, @@ -194,6 +201,7 @@ 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 KSZ8463_CHIP_ID:  	case KSZ88X3_CHIP_ID:  	case KSZ8864_CHIP_ID:  	case KSZ8895_CHIP_ID: @@ -227,6 +235,11 @@ static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues)  			       WEIGHTED_FAIR_QUEUE_ENABLE);  		if (ret)  			return ret; +	} else if (ksz_is_ksz8463(dev)) { +		mask_4q = KSZ8873_PORT_4QUEUE_SPLIT_EN; +		mask_2q = KSZ8873_PORT_2QUEUE_SPLIT_EN; +		reg_4q = P1CR1; +		reg_2q = P1CR1 + 1;  	} else {  		mask_4q = KSZ8795_PORT_4QUEUE_SPLIT_EN;  		mask_2q = KSZ8795_PORT_2QUEUE_SPLIT_EN; @@ -371,6 +384,9 @@ static void ksz8863_r_mib_pkt(struct ksz_device *dev, int port, u16 addr,  	addr -= dev->info->reg_mib_cnt;  	ctrl_addr = addr ? KSZ8863_MIB_PACKET_DROPPED_TX_0 :  			   KSZ8863_MIB_PACKET_DROPPED_RX_0; +	if (ksz_is_8895_family(dev) && +	    ctrl_addr == KSZ8863_MIB_PACKET_DROPPED_RX_0) +		ctrl_addr = KSZ8895_MIB_PACKET_DROPPED_RX_0;  	ctrl_addr += port;  	ctrl_addr |= IND_ACC_TABLE(TABLE_MIB | TABLE_READ); @@ -1265,12 +1281,15 @@ int ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)  void ksz8_cfg_port_member(struct ksz_device *dev, int port, u8 member)  { +	int offset = P_MIRROR_CTRL;  	u8 data; -	ksz_pread8(dev, port, P_MIRROR_CTRL, &data); -	data &= ~PORT_VLAN_MEMBERSHIP; +	if (ksz_is_ksz8463(dev)) +		offset = P1CR2; +	ksz_pread8(dev, port, offset, &data); +	data &= ~dev->port_mask;  	data |= (member & dev->port_mask); -	ksz_pwrite8(dev, port, P_MIRROR_CTRL, data); +	ksz_pwrite8(dev, port, offset, data);  }  void ksz8_flush_dyn_mac_table(struct ksz_device *dev, int port) @@ -1278,6 +1297,8 @@ void ksz8_flush_dyn_mac_table(struct ksz_device *dev, int port)  	u8 learn[DSA_MAX_PORTS];  	int first, index, cnt;  	const u16 *regs; +	int reg = S_FLUSH_TABLE_CTRL; +	int mask = SW_FLUSH_DYN_MAC_TABLE;  	regs = dev->info->regs; @@ -1295,7 +1316,11 @@ void ksz8_flush_dyn_mac_table(struct ksz_device *dev, int port)  			ksz_pwrite8(dev, index, regs[P_STP_CTRL],  				    learn[index] | PORT_LEARN_DISABLE);  	} -	ksz_cfg(dev, S_FLUSH_TABLE_CTRL, SW_FLUSH_DYN_MAC_TABLE, true); +	if (ksz_is_ksz8463(dev)) { +		reg = KSZ8463_FLUSH_TABLE_CTRL; +		mask = KSZ8463_FLUSH_DYN_MAC_TABLE; +	} +	ksz_cfg(dev, reg, mask, true);  	for (index = first; index < cnt; index++) {  		if (!(learn[index] & PORT_LEARN_DISABLE))  			ksz_pwrite8(dev, index, regs[P_STP_CTRL], learn[index]); @@ -1434,7 +1459,7 @@ int ksz8_fdb_del(struct ksz_device *dev, int port, const unsigned char *addr,  int ksz8_port_vlan_filtering(struct ksz_device *dev, int port, bool flag,  			     struct netlink_ext_ack *extack)  { -	if (ksz_is_ksz88x3(dev)) +	if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev))  		return -ENOTSUPP;  	/* Discard packets with VID not enabled on the switch */ @@ -1450,9 +1475,12 @@ int ksz8_port_vlan_filtering(struct ksz_device *dev, int port, bool flag,  static void ksz8_port_enable_pvid(struct ksz_device *dev, int port, bool state)  { -	if (ksz_is_ksz88x3(dev)) { -		ksz_cfg(dev, REG_SW_INSERT_SRC_PVID, -			0x03 << (4 - 2 * port), state); +	if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) { +		int reg = REG_SW_INSERT_SRC_PVID; + +		if (ksz_is_ksz8463(dev)) +			reg = KSZ8463_REG_SW_CTRL_9; +		ksz_cfg(dev, reg, 0x03 << (4 - 2 * port), state);  	} else {  		ksz_pwrite8(dev, port, REG_PORT_CTRL_12, state ? 0x0f : 0x00);  	} @@ -1467,7 +1495,7 @@ int ksz8_port_vlan_add(struct ksz_device *dev, int port,  	u16 data, new_pvid = 0;  	u8 fid, member, valid; -	if (ksz_is_ksz88x3(dev)) +	if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev))  		return -ENOTSUPP;  	/* If a VLAN is added with untagged flag different from the @@ -1536,7 +1564,7 @@ int ksz8_port_vlan_del(struct ksz_device *dev, int port,  	u16 data, pvid;  	u8 fid, member, valid; -	if (ksz_is_ksz88x3(dev)) +	if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev))  		return -ENOTSUPP;  	ksz_pread16(dev, port, REG_PORT_CTRL_VID, &pvid); @@ -1566,19 +1594,23 @@ int ksz8_port_mirror_add(struct ksz_device *dev, int port,  			 struct dsa_mall_mirror_tc_entry *mirror,  			 bool ingress, struct netlink_ext_ack *extack)  { +	int offset = P_MIRROR_CTRL; + +	if (ksz_is_ksz8463(dev)) +		offset = P1CR2;  	if (ingress) { -		ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, true); +		ksz_port_cfg(dev, port, offset, PORT_MIRROR_RX, true);  		dev->mirror_rx |= BIT(port);  	} else { -		ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, true); +		ksz_port_cfg(dev, port, offset, PORT_MIRROR_TX, true);  		dev->mirror_tx |= BIT(port);  	} -	ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_SNIFFER, false); +	ksz_port_cfg(dev, port, offset, PORT_MIRROR_SNIFFER, false);  	/* configure mirror port */  	if (dev->mirror_rx || dev->mirror_tx) -		ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL, +		ksz_port_cfg(dev, mirror->to_local_port, offset,  			     PORT_MIRROR_SNIFFER, true);  	return 0; @@ -1587,20 +1619,23 @@ int ksz8_port_mirror_add(struct ksz_device *dev, int port,  void ksz8_port_mirror_del(struct ksz_device *dev, int port,  			  struct dsa_mall_mirror_tc_entry *mirror)  { +	int offset = P_MIRROR_CTRL;  	u8 data; +	if (ksz_is_ksz8463(dev)) +		offset = P1CR2;  	if (mirror->ingress) { -		ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, false); +		ksz_port_cfg(dev, port, offset, PORT_MIRROR_RX, false);  		dev->mirror_rx &= ~BIT(port);  	} else { -		ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, false); +		ksz_port_cfg(dev, port, offset, PORT_MIRROR_TX, false);  		dev->mirror_tx &= ~BIT(port);  	} -	ksz_pread8(dev, port, P_MIRROR_CTRL, &data); +	ksz_pread8(dev, port, offset, &data);  	if (!dev->mirror_rx && !dev->mirror_tx) -		ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL, +		ksz_port_cfg(dev, mirror->to_local_port, offset,  			     PORT_MIRROR_SNIFFER, false);  } @@ -1625,17 +1660,24 @@ 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 offset;  	u8 member;  	masks = dev->info->masks;  	/* enable broadcast storm limit */ -	ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true); +	offset = P_BCAST_STORM_CTRL; +	if (ksz_is_ksz8463(dev)) +		offset = P1CR1; +	ksz_port_cfg(dev, port, offset, PORT_BROADCAST_STORM, true);  	ksz8_port_queue_split(dev, port, dev->info->num_tx_queues);  	/* replace priority */ -	ksz_port_cfg(dev, port, P_802_1P_CTRL, +	offset = P_802_1P_CTRL; +	if (ksz_is_ksz8463(dev)) +		offset = P1CR2; +	ksz_port_cfg(dev, port, offset,  		     masks[PORT_802_1P_REMAPPING], false);  	if (cpu_port) @@ -1675,6 +1717,7 @@ void ksz8_config_cpu_port(struct dsa_switch *ds)  	const u32 *masks;  	const u16 *regs;  	u8 remote; +	u8 fiber_ports = 0;  	int i;  	masks = dev->info->masks; @@ -1705,6 +1748,32 @@ void ksz8_config_cpu_port(struct dsa_switch *ds)  		else  			ksz_port_cfg(dev, i, regs[P_STP_CTRL],  				     PORT_FORCE_FLOW_CTRL, false); +		if (p->fiber) +			fiber_ports |= (1 << i); +	} +	if (ksz_is_ksz8463(dev)) { +		/* Setup fiber ports. */ +		if (fiber_ports) { +			fiber_ports &= 3; +			regmap_update_bits(ksz_regmap_16(dev), +					   KSZ8463_REG_CFG_CTRL, +					   fiber_ports << PORT_COPPER_MODE_S, +					   0); +			regmap_update_bits(ksz_regmap_16(dev), +					   KSZ8463_REG_DSP_CTRL_6, +					   COPPER_RECEIVE_ADJUSTMENT, 0); +		} + +		/* Turn off PTP function as the switch's proprietary way of +		 * handling timestamp is not supported in current Linux PTP +		 * stack implementation. +		 */ +		regmap_update_bits(ksz_regmap_16(dev), +				   KSZ8463_PTP_MSG_CONF1, +				   PTP_ENABLE, 0); +		regmap_update_bits(ksz_regmap_16(dev), +				   KSZ8463_PTP_CLK_CTRL, +				   PTP_CLK_ENABLE, 0);  	}  } @@ -1901,7 +1970,7 @@ int ksz8_setup(struct dsa_switch *ds)  	ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false); -	if (!ksz_is_ksz88x3(dev)) +	if (!ksz_is_ksz88x3(dev) && !ksz_is_ksz8463(dev))  		ksz_cfg(dev, REG_SW_CTRL_19, SW_INS_TAG_ENABLE, true);  	for (i = 0; i < (dev->info->num_vlans / 4); i++) @@ -1947,6 +2016,84 @@ u32 ksz8_get_port_addr(int port, int offset)  	return PORT_CTRL_ADDR(port, offset);  } +u32 ksz8463_get_port_addr(int port, int offset) +{ +	return offset + 0x18 * port; +} + +static u16 ksz8463_get_phy_addr(u16 phy, u16 reg, u16 offset) +{ +	return offset + reg * 2 + phy * (P2MBCR - P1MBCR); +} + +int ksz8463_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val) +{ +	u16 sw_reg = 0; +	u16 data = 0; +	int ret; + +	if (phy > 1) +		return -ENOSPC; +	switch (reg) { +	case MII_PHYSID1: +		sw_reg = ksz8463_get_phy_addr(phy, 0, PHY1IHR); +		break; +	case MII_PHYSID2: +		sw_reg = ksz8463_get_phy_addr(phy, 0, PHY1ILR); +		break; +	case MII_BMCR: +	case MII_BMSR: +	case MII_ADVERTISE: +	case MII_LPA: +		sw_reg = ksz8463_get_phy_addr(phy, reg, P1MBCR); +		break; +	case MII_TPISTATUS: +		/* This register holds the PHY interrupt status for simulated +		 * Micrel KSZ PHY. +		 */ +		data = 0x0505; +		break; +	default: +		break; +	} +	if (sw_reg) { +		ret = ksz_read16(dev, sw_reg, &data); +		if (ret) +			return ret; +	} +	*val = data; + +	return 0; +} + +int ksz8463_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val) +{ +	u16 sw_reg = 0; +	int ret; + +	if (phy > 1) +		return -ENOSPC; + +	/* No write to fiber port. */ +	if (dev->ports[phy].fiber) +		return 0; +	switch (reg) { +	case MII_BMCR: +	case MII_ADVERTISE: +		sw_reg = ksz8463_get_phy_addr(phy, reg, P1MBCR); +		break; +	default: +		break; +	} +	if (sw_reg) { +		ret = ksz_write16(dev, sw_reg, val); +		if (ret) +			return ret; +	} + +	return 0; +} +  int ksz8_switch_init(struct ksz_device *dev)  {  	dev->cpu_port = fls(dev->info->cpu_ports) - 1; diff --git a/drivers/net/dsa/microchip/ksz8.h b/drivers/net/dsa/microchip/ksz8.h index e1c79ff97123..0f2cd1474b44 100644 --- a/drivers/net/dsa/microchip/ksz8.h +++ b/drivers/net/dsa/microchip/ksz8.h @@ -63,4 +63,8 @@ void ksz8_phylink_mac_link_up(struct phylink_config *config,  			      bool tx_pause, bool rx_pause);  int ksz8_all_queues_split(struct ksz_device *dev, int queues); +u32 ksz8463_get_port_addr(int port, int offset); +int ksz8463_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val); +int ksz8463_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val); +  #endif diff --git a/drivers/net/dsa/microchip/ksz8_reg.h b/drivers/net/dsa/microchip/ksz8_reg.h index 329688603a58..332408567b47 100644 --- a/drivers/net/dsa/microchip/ksz8_reg.h +++ b/drivers/net/dsa/microchip/ksz8_reg.h @@ -729,6 +729,55 @@  #define PHY_POWER_SAVING_ENABLE		BIT(2)  #define PHY_REMOTE_LOOPBACK		BIT(1) +/* KSZ8463 specific registers. */ +#define P1MBCR				0x4C +#define P1MBSR				0x4E +#define PHY1ILR				0x50 +#define PHY1IHR				0x52 +#define P1ANAR				0x54 +#define P1ANLPR				0x56 +#define P2MBCR				0x58 +#define P2MBSR				0x5A +#define PHY2ILR				0x5C +#define PHY2IHR				0x5E +#define P2ANAR				0x60 +#define P2ANLPR				0x62 + +#define P1CR1				0x6C +#define P1CR2				0x6E +#define P1CR3				0x72 +#define P1CR4				0x7E +#define P1SR				0x80 + +#define KSZ8463_FLUSH_TABLE_CTRL	0xAD + +#define KSZ8463_FLUSH_DYN_MAC_TABLE	BIT(2) +#define KSZ8463_FLUSH_STA_MAC_TABLE	BIT(1) + +#define KSZ8463_REG_SW_CTRL_9		0xAE + +#define KSZ8463_REG_CFG_CTRL		0xD8 + +#define PORT_2_COPPER_MODE		BIT(7) +#define PORT_1_COPPER_MODE		BIT(6) +#define PORT_COPPER_MODE_S		6 + +#define KSZ8463_REG_SW_RESET		0x126 + +#define KSZ8463_GLOBAL_SOFTWARE_RESET	BIT(0) + +#define KSZ8463_PTP_CLK_CTRL		0x600 + +#define PTP_CLK_ENABLE			BIT(1) + +#define KSZ8463_PTP_MSG_CONF1		0x620 + +#define PTP_ENABLE			BIT(6) + +#define KSZ8463_REG_DSP_CTRL_6		0x734 + +#define COPPER_RECEIVE_ADJUSTMENT	BIT(13) +  /* Chip resource */  #define PRIO_QUEUES			4 @@ -784,7 +833,9 @@  #define KSZ8795_MIB_TOTAL_TX_1		0x105  #define KSZ8863_MIB_PACKET_DROPPED_TX_0 0x100 -#define KSZ8863_MIB_PACKET_DROPPED_RX_0 0x105 +#define KSZ8863_MIB_PACKET_DROPPED_RX_0 0x103 + +#define KSZ8895_MIB_PACKET_DROPPED_RX_0 0x105  #define MIB_PACKET_DROPPED		0x0000FFFF diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c index 29fe79ea74cd..d747ea1c41a7 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-2024 Microchip Technology Inc. + * Copyright (C) 2017-2025 Microchip Technology Inc.   */  #include <linux/kernel.h> @@ -161,6 +161,190 @@ static int ksz9477_wait_alu_sta_ready(struct ksz_device *dev)  					10, 1000);  } +static void port_sgmii_s(struct ksz_device *dev, uint port, u16 devid, u16 reg) +{ +	u32 data; + +	data = (devid & MII_MMD_CTRL_DEVAD_MASK) << 16; +	data |= reg; +	ksz_pwrite32(dev, port, REG_PORT_SGMII_ADDR__4, data); +} + +static void port_sgmii_r(struct ksz_device *dev, uint port, u16 devid, u16 reg, +			 u16 *buf) +{ +	port_sgmii_s(dev, port, devid, reg); +	ksz_pread16(dev, port, REG_PORT_SGMII_DATA__4 + 2, buf); +} + +static void port_sgmii_w(struct ksz_device *dev, uint port, u16 devid, u16 reg, +			 u16 buf) +{ +	port_sgmii_s(dev, port, devid, reg); +	ksz_pwrite32(dev, port, REG_PORT_SGMII_DATA__4, buf); +} + +static int ksz9477_pcs_read(struct mii_bus *bus, int phy, int mmd, int reg) +{ +	struct ksz_device *dev = bus->priv; +	int port = ksz_get_sgmii_port(dev); +	u16 val; + +	port_sgmii_r(dev, port, mmd, reg, &val); + +	/* Simulate a value to activate special code in the XPCS driver if +	 * supported. +	 */ +	if (mmd == MDIO_MMD_PMAPMD) { +		if (reg == MDIO_DEVID1) +			val = 0x9477; +		else if (reg == MDIO_DEVID2) +			val = 0x22 << 10; +	} else if (mmd == MDIO_MMD_VEND2) { +		struct ksz_port *p = &dev->ports[port]; + +		/* Need to update MII_BMCR register with the exact speed and +		 * duplex mode when running in SGMII mode and this register is +		 * used to detect connected speed in that mode. +		 */ +		if (reg == MMD_SR_MII_AUTO_NEG_STATUS) { +			int duplex, speed; + +			if (val & SR_MII_STAT_LINK_UP) { +				speed = (val >> SR_MII_STAT_S) & SR_MII_STAT_M; +				if (speed == SR_MII_STAT_1000_MBPS) +					speed = SPEED_1000; +				else if (speed == SR_MII_STAT_100_MBPS) +					speed = SPEED_100; +				else +					speed = SPEED_10; + +				if (val & SR_MII_STAT_FULL_DUPLEX) +					duplex = DUPLEX_FULL; +				else +					duplex = DUPLEX_HALF; + +				if (!p->phydev.link || +				    p->phydev.speed != speed || +				    p->phydev.duplex != duplex) { +					u16 ctrl; + +					p->phydev.link = 1; +					p->phydev.speed = speed; +					p->phydev.duplex = duplex; +					port_sgmii_r(dev, port, mmd, MII_BMCR, +						     &ctrl); +					ctrl &= BMCR_ANENABLE; +					ctrl |= mii_bmcr_encode_fixed(speed, +								      duplex); +					port_sgmii_w(dev, port, mmd, MII_BMCR, +						     ctrl); +				} +			} else { +				p->phydev.link = 0; +			} +		} else if (reg == MII_BMSR) { +			p->phydev.link = (val & BMSR_LSTATUS); +		} +	} + +	return val; +} + +static int ksz9477_pcs_write(struct mii_bus *bus, int phy, int mmd, int reg, +			     u16 val) +{ +	struct ksz_device *dev = bus->priv; +	int port = ksz_get_sgmii_port(dev); + +	if (mmd == MDIO_MMD_VEND2) { +		struct ksz_port *p = &dev->ports[port]; + +		if (reg == MMD_SR_MII_AUTO_NEG_CTRL) { +			u16 sgmii_mode = SR_MII_PCS_SGMII << SR_MII_PCS_MODE_S; + +			/* Need these bits for 1000BASE-X mode to work with +			 * AN on. +			 */ +			if (!(val & sgmii_mode)) +				val |= SR_MII_SGMII_LINK_UP | +				       SR_MII_TX_CFG_PHY_MASTER; + +			/* SGMII interrupt in the port cannot be masked, so +			 * make sure interrupt is not enabled as it is not +			 * handled. +			 */ +			val &= ~SR_MII_AUTO_NEG_COMPLETE_INTR; +		} else if (reg == MII_BMCR) { +			/* The MII_ADVERTISE register needs to write once +			 * before doing auto-negotiation for the correct +			 * config_word to be sent out after reset. +			 */ +			if ((val & BMCR_ANENABLE) && !p->sgmii_adv_write) { +				u16 adv; + +				/* The SGMII port cannot disable flow control +				 * so it is better to just advertise symmetric +				 * pause. +				 */ +				port_sgmii_r(dev, port, mmd, MII_ADVERTISE, +					     &adv); +				adv |= ADVERTISE_1000XPAUSE; +				adv &= ~ADVERTISE_1000XPSE_ASYM; +				port_sgmii_w(dev, port, mmd, MII_ADVERTISE, +					     adv); +				p->sgmii_adv_write = 1; +			} else if (val & BMCR_RESET) { +				p->sgmii_adv_write = 0; +			} +		} else if (reg == MII_ADVERTISE) { +			/* XPCS driver writes to this register so there is no +			 * need to update it for the errata. +			 */ +			p->sgmii_adv_write = 1; +		} +	} +	port_sgmii_w(dev, port, mmd, reg, val); + +	return 0; +} + +int ksz9477_pcs_create(struct ksz_device *dev) +{ +	/* This chip has a SGMII port. */ +	if (ksz_has_sgmii_port(dev)) { +		int port = ksz_get_sgmii_port(dev); +		struct ksz_port *p = &dev->ports[port]; +		struct phylink_pcs *pcs; +		struct mii_bus *bus; +		int ret; + +		bus = devm_mdiobus_alloc(dev->dev); +		if (!bus) +			return -ENOMEM; + +		bus->name = "ksz_pcs_mdio_bus"; +		snprintf(bus->id, MII_BUS_ID_SIZE, "%s-pcs", +			 dev_name(dev->dev)); +		bus->read_c45 = &ksz9477_pcs_read; +		bus->write_c45 = &ksz9477_pcs_write; +		bus->parent = dev->dev; +		bus->phy_mask = ~0; +		bus->priv = dev; + +		ret = devm_mdiobus_register(dev->dev, bus); +		if (ret) +			return ret; + +		pcs = xpcs_create_pcs_mdiodev(bus, 0); +		if (IS_ERR(pcs)) +			return PTR_ERR(pcs); +		p->pcs = pcs; +	} + +	return 0; +} +  int ksz9477_reset_switch(struct ksz_device *dev)  {  	u8 data8; @@ -978,6 +1162,14 @@ void ksz9477_get_caps(struct ksz_device *dev, int port,  	if (dev->info->gbit_capable[port])  		config->mac_capabilities |= MAC_1000FD; + +	if (ksz_is_sgmii_port(dev, port)) { +		struct ksz_port *p = &dev->ports[port]; + +		phy_interface_or(config->supported_interfaces, +				 config->supported_interfaces, +				 p->pcs->supported_interfaces); +	}  }  int ksz9477_set_ageing_time(struct ksz_device *dev, unsigned int msecs) diff --git a/drivers/net/dsa/microchip/ksz9477.h b/drivers/net/dsa/microchip/ksz9477.h index d2166b0d881e..0d1a6dfda23e 100644 --- a/drivers/net/dsa/microchip/ksz9477.h +++ b/drivers/net/dsa/microchip/ksz9477.h @@ -2,7 +2,7 @@  /*   * Microchip KSZ9477 series Header file   * - * Copyright (C) 2017-2022 Microchip Technology Inc. + * Copyright (C) 2017-2025 Microchip Technology Inc.   */  #ifndef __KSZ9477_H @@ -97,4 +97,6 @@ void ksz9477_acl_match_process_l2(struct ksz_device *dev, int port,  				  u16 ethtype, u8 *src_mac, u8 *dst_mac,  				  unsigned long cookie, u32 prio); +int ksz9477_pcs_create(struct ksz_device *dev); +  #endif diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index f95a9aac56ee..7292bfe2f7ca 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-2024 Microchip Technology Inc. + * Copyright (C) 2017-2025 Microchip Technology Inc.   */  #include <linux/delay.h> @@ -331,6 +331,38 @@ static const struct phylink_mac_ops ksz8_phylink_mac_ops = {  	.mac_enable_tx_lpi = ksz_phylink_mac_enable_tx_lpi,  }; +static const struct ksz_dev_ops ksz8463_dev_ops = { +	.setup = ksz8_setup, +	.get_port_addr = ksz8463_get_port_addr, +	.cfg_port_member = ksz8_cfg_port_member, +	.flush_dyn_mac_table = ksz8_flush_dyn_mac_table, +	.port_setup = ksz8_port_setup, +	.r_phy = ksz8463_r_phy, +	.w_phy = ksz8463_w_phy, +	.r_mib_cnt = ksz8_r_mib_cnt, +	.r_mib_pkt = ksz8_r_mib_pkt, +	.r_mib_stat64 = ksz88xx_r_mib_stats64, +	.freeze_mib = ksz8_freeze_mib, +	.port_init_cnt = ksz8_port_init_cnt, +	.fdb_dump = ksz8_fdb_dump, +	.fdb_add = ksz8_fdb_add, +	.fdb_del = ksz8_fdb_del, +	.mdb_add = ksz8_mdb_add, +	.mdb_del = ksz8_mdb_del, +	.vlan_filtering = ksz8_port_vlan_filtering, +	.vlan_add = ksz8_port_vlan_add, +	.vlan_del = ksz8_port_vlan_del, +	.mirror_add = ksz8_port_mirror_add, +	.mirror_del = ksz8_port_mirror_del, +	.get_caps = ksz8_get_caps, +	.config_cpu_port = ksz8_config_cpu_port, +	.enable_stp_addr = ksz8_enable_stp_addr, +	.reset = ksz8_reset_switch, +	.init = ksz8_switch_init, +	.exit = ksz8_switch_exit, +	.change_mtu = ksz8_change_mtu, +}; +  static const struct ksz_dev_ops ksz88xx_dev_ops = {  	.setup = ksz8_setup,  	.get_port_addr = ksz8_get_port_addr, @@ -408,12 +440,29 @@ static void ksz9477_phylink_mac_link_up(struct phylink_config *config,  					int speed, int duplex, bool tx_pause,  					bool rx_pause); +static struct phylink_pcs * +ksz_phylink_mac_select_pcs(struct phylink_config *config, +			   phy_interface_t interface) +{ +	struct dsa_port *dp = dsa_phylink_to_port(config); +	struct ksz_device *dev = dp->ds->priv; +	struct ksz_port *p = &dev->ports[dp->index]; + +	if (ksz_is_sgmii_port(dev, dp->index) && +	    (interface == PHY_INTERFACE_MODE_SGMII || +	    interface == PHY_INTERFACE_MODE_1000BASEX)) +		return p->pcs; + +	return NULL; +} +  static const struct phylink_mac_ops ksz9477_phylink_mac_ops = {  	.mac_config	= ksz_phylink_mac_config,  	.mac_link_down	= ksz_phylink_mac_link_down,  	.mac_link_up	= ksz9477_phylink_mac_link_up,  	.mac_disable_tx_lpi = ksz_phylink_mac_disable_tx_lpi,  	.mac_enable_tx_lpi = ksz_phylink_mac_enable_tx_lpi, +	.mac_select_pcs	= ksz_phylink_mac_select_pcs,  };  static const struct ksz_dev_ops ksz9477_dev_ops = { @@ -451,6 +500,7 @@ static const struct ksz_dev_ops ksz9477_dev_ops = {  	.reset = ksz9477_reset_switch,  	.init = ksz9477_switch_init,  	.exit = ksz9477_switch_exit, +	.pcs_create = ksz9477_pcs_create,  };  static const struct phylink_mac_ops lan937x_phylink_mac_ops = { @@ -499,6 +549,60 @@ static const struct ksz_dev_ops lan937x_dev_ops = {  	.exit = lan937x_switch_exit,  }; +static const u16 ksz8463_regs[] = { +	[REG_SW_MAC_ADDR]		= 0x10, +	[REG_IND_CTRL_0]		= 0x30, +	[REG_IND_DATA_8]		= 0x26, +	[REG_IND_DATA_CHECK]		= 0x26, +	[REG_IND_DATA_HI]		= 0x28, +	[REG_IND_DATA_LO]		= 0x2C, +	[REG_IND_MIB_CHECK]		= 0x2F, +	[P_FORCE_CTRL]			= 0x0C, +	[P_LINK_STATUS]			= 0x0E, +	[P_LOCAL_CTRL]			= 0x0C, +	[P_NEG_RESTART_CTRL]		= 0x0D, +	[P_REMOTE_STATUS]		= 0x0E, +	[P_SPEED_STATUS]		= 0x0F, +	[S_TAIL_TAG_CTRL]		= 0xAD, +	[P_STP_CTRL]			= 0x6F, +	[S_START_CTRL]			= 0x01, +	[S_BROADCAST_CTRL]		= 0x06, +	[S_MULTICAST_CTRL]		= 0x04, +}; + +static const u32 ksz8463_masks[] = { +	[PORT_802_1P_REMAPPING]		= BIT(3), +	[SW_TAIL_TAG_ENABLE]		= BIT(0), +	[MIB_COUNTER_OVERFLOW]		= BIT(7), +	[MIB_COUNTER_VALID]		= BIT(6), +	[VLAN_TABLE_FID]		= GENMASK(15, 12), +	[VLAN_TABLE_MEMBERSHIP]		= GENMASK(18, 16), +	[VLAN_TABLE_VALID]		= BIT(19), +	[STATIC_MAC_TABLE_VALID]	= BIT(19), +	[STATIC_MAC_TABLE_USE_FID]	= BIT(21), +	[STATIC_MAC_TABLE_FID]		= GENMASK(25, 22), +	[STATIC_MAC_TABLE_OVERRIDE]	= BIT(20), +	[STATIC_MAC_TABLE_FWD_PORTS]	= GENMASK(18, 16), +	[DYNAMIC_MAC_TABLE_ENTRIES_H]	= GENMASK(1, 0), +	[DYNAMIC_MAC_TABLE_MAC_EMPTY]	= BIT(2), +	[DYNAMIC_MAC_TABLE_NOT_READY]	= BIT(7), +	[DYNAMIC_MAC_TABLE_ENTRIES]	= GENMASK(31, 24), +	[DYNAMIC_MAC_TABLE_FID]		= GENMASK(19, 16), +	[DYNAMIC_MAC_TABLE_SRC_PORT]	= GENMASK(21, 20), +	[DYNAMIC_MAC_TABLE_TIMESTAMP]	= GENMASK(23, 22), +}; + +static u8 ksz8463_shifts[] = { +	[VLAN_TABLE_MEMBERSHIP_S]	= 16, +	[STATIC_MAC_FWD_PORTS]		= 16, +	[STATIC_MAC_FID]		= 22, +	[DYNAMIC_MAC_ENTRIES_H]		= 8, +	[DYNAMIC_MAC_ENTRIES]		= 24, +	[DYNAMIC_MAC_FID]		= 16, +	[DYNAMIC_MAC_TIMESTAMP]		= 22, +	[DYNAMIC_MAC_SRC_PORT]		= 20, +}; +  static const u16 ksz8795_regs[] = {  	[REG_SW_MAC_ADDR]		= 0x68,  	[REG_IND_CTRL_0]		= 0x6E, @@ -1093,8 +1197,7 @@ static const struct regmap_range ksz9477_valid_regs[] = {  	regmap_reg_range(0x701b, 0x701b),  	regmap_reg_range(0x701f, 0x7020),  	regmap_reg_range(0x7030, 0x7030), -	regmap_reg_range(0x7200, 0x7203), -	regmap_reg_range(0x7206, 0x7207), +	regmap_reg_range(0x7200, 0x7207),  	regmap_reg_range(0x7300, 0x7301),  	regmap_reg_range(0x7400, 0x7401),  	regmap_reg_range(0x7403, 0x7403), @@ -1370,6 +1473,29 @@ static const struct regmap_access_table ksz8873_register_set = {  };  const struct ksz_chip_data ksz_switch_chips[] = { +	[KSZ8463] = { +		.chip_id = KSZ8463_CHIP_ID, +		.dev_name = "KSZ8463", +		.num_vlans = 16, +		.num_alus = 0, +		.num_statics = 8, +		.cpu_ports = 0x4,	/* can be configured as cpu port */ +		.port_cnt = 3, +		.num_tx_queues = 4, +		.num_ipms = 4, +		.ops = &ksz8463_dev_ops, +		.phylink_mac_ops = &ksz88x3_phylink_mac_ops, +		.mib_names = ksz88xx_mib_names, +		.mib_cnt = ARRAY_SIZE(ksz88xx_mib_names), +		.reg_mib_cnt = MIB_COUNTER_NUM, +		.regs = ksz8463_regs, +		.masks = ksz8463_masks, +		.shifts = ksz8463_shifts, +		.supports_mii = {false, false, true}, +		.supports_rmii = {false, false, true}, +		.internal_phy = {true, true, false}, +	}, +  	[KSZ8563] = {  		.chip_id = KSZ8563_CHIP_ID,  		.dev_name = "KSZ8563", @@ -1610,6 +1736,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {  				   true, false, false},  		.gbit_capable	= {true, true, true, true, true, true, true},  		.ptp_capable = true, +		.sgmii_port = 7,  		.wr_table = &ksz9477_register_set,  		.rd_table = &ksz9477_register_set,  	}, @@ -2002,6 +2129,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}, +		.sgmii_port = 7,  		.wr_table = &ksz9477_register_set,  		.rd_table = &ksz9477_register_set,  	}, @@ -2137,7 +2265,7 @@ void ksz_r_mib_stats64(struct ksz_device *dev, int port)  	spin_unlock(&mib->stats64_lock); -	if (dev->info->phy_errata_9477) { +	if (dev->info->phy_errata_9477 && !ksz_is_sgmii_port(dev, port)) {  		ret = ksz9477_errata_monitor(dev, port, raw->tx_late_col);  		if (ret)  			dev_err(dev->dev, "Failed to monitor transmission halt\n"); @@ -2767,8 +2895,8 @@ static int ksz_irq_common_setup(struct ksz_device *dev, struct ksz_irq *kirq)  	kirq->dev = dev;  	kirq->masked = ~0; -	kirq->domain = irq_domain_add_simple(dev->dev->of_node, kirq->nirqs, 0, -					     &ksz_irq_domain_ops, kirq); +	kirq->domain = irq_domain_create_simple(dev_fwnode(dev->dev), kirq->nirqs, 0, +						&ksz_irq_domain_ops, kirq);  	if (!kirq->domain)  		return -ENOMEM; @@ -2823,6 +2951,7 @@ static int ksz_parse_drive_strength(struct ksz_device *dev);  static int ksz_setup(struct dsa_switch *ds)  {  	struct ksz_device *dev = ds->priv; +	u16 storm_mask, storm_rate;  	struct dsa_port *dp;  	struct ksz_port *p;  	const u16 *regs; @@ -2845,11 +2974,21 @@ static int ksz_setup(struct dsa_switch *ds)  	if (ret)  		return ret; +	if (ksz_has_sgmii_port(dev) && dev->dev_ops->pcs_create) { +		ret = dev->dev_ops->pcs_create(dev); +		if (ret) +			return ret; +	} +  	/* set broadcast storm protection 10% rate */ +	storm_mask = BROADCAST_STORM_RATE; +	storm_rate = (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100; +	if (ksz_is_ksz8463(dev)) { +		storm_mask = swab16(storm_mask); +		storm_rate = swab16(storm_rate); +	}  	regmap_update_bits(ksz_regmap_16(dev), regs[S_BROADCAST_CTRL], -			   BROADCAST_STORM_RATE, -			   (BROADCAST_STORM_VALUE * -			   BROADCAST_STORM_PROT_RATE) / 100); +			   storm_mask, storm_rate);  	dev->dev_ops->config_cpu_port(ds); @@ -3375,6 +3514,7 @@ static enum dsa_tag_protocol ksz_get_tag_protocol(struct dsa_switch *ds,  		proto = DSA_TAG_PROTO_KSZ8795;  	if (dev->chip_id == KSZ88X3_CHIP_ID || +	    dev->chip_id == KSZ8463_CHIP_ID ||  	    dev->chip_id == KSZ8563_CHIP_ID ||  	    dev->chip_id == KSZ9893_CHIP_ID ||  	    dev->chip_id == KSZ9563_CHIP_ID) @@ -3487,6 +3627,7 @@ 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 KSZ8463_CHIP_ID:  	case KSZ88X3_CHIP_ID:  	case KSZ8864_CHIP_ID:  	case KSZ8895_CHIP_ID: @@ -3692,6 +3833,10 @@ static void ksz_phylink_mac_config(struct phylink_config *config,  	if (dev->info->internal_phy[port])  		return; +	/* No need to configure XMII control register when using SGMII. */ +	if (ksz_is_sgmii_port(dev, port)) +		return; +  	if (phylink_autoneg_inband(mode)) {  		dev_err(dev->dev, "In-band AN not supported!\n");  		return; @@ -3837,6 +3982,9 @@ static int ksz_switch_detect(struct ksz_device *dev)  	id2 = FIELD_GET(SW_CHIP_ID_M, id16);  	switch (id1) { +	case KSZ84_FAMILY_ID: +		dev->chip_id = KSZ8463_CHIP_ID; +		break;  	case KSZ87_FAMILY_ID:  		if (id2 == KSZ87_CHIP_ID_95) {  			u8 val; @@ -4078,6 +4226,104 @@ static int ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p,  	return p->bands - 1 - band;  } +static u8 ksz8463_tc_ctrl(int port, int queue) +{ +	u8 reg; + +	reg = 0xC8 + port * 4; +	reg += ((3 - queue) / 2) * 2; +	reg++; +	reg -= (queue & 1); +	return reg; +} + +/** + * ksz88x3_tc_ets_add - Configure ETS (Enhanced Transmission Selection) + *                      for a port on KSZ88x3 switch + * @dev: Pointer to the KSZ switch device structure + * @port: Port number to configure + * @p: Pointer to offload replace parameters describing ETS bands and mapping + * + * The KSZ88x3 supports two scheduling modes: Strict Priority and + * Weighted Fair Queuing (WFQ). Both modes have fixed behavior: + *   - No configurable queue-to-priority mapping + *   - No weight adjustment in WFQ mode + * + * This function configures the switch to use strict priority mode by + * clearing the WFQ enable bit for all queues associated with ETS bands. + * If strict priority is not explicitly requested, the switch will default + * to WFQ mode. + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz88x3_tc_ets_add(struct ksz_device *dev, int port, +			      struct tc_ets_qopt_offload_replace_params *p) +{ +	int ret, band; + +	/* Only strict priority mode is supported for now. +	 * WFQ is implicitly enabled when strict mode is disabled. +	 */ +	for (band = 0; band < p->bands; band++) { +		int queue = ksz_ets_band_to_queue(p, band); +		u8 reg; + +		/* Calculate TXQ Split Control register address for this +		 * port/queue +		 */ +		reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); +		if (ksz_is_ksz8463(dev)) +			reg = ksz8463_tc_ctrl(port, queue); + +		/* Clear WFQ enable bit to select strict priority scheduling */ +		ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, 0); +		if (ret) +			return ret; +	} + +	return 0; +} + +/** + * ksz88x3_tc_ets_del - Reset ETS (Enhanced Transmission Selection) config + *                      for a port on KSZ88x3 switch + * @dev: Pointer to the KSZ switch device structure + * @port: Port number to reset + * + * The KSZ88x3 supports only fixed scheduling modes: Strict Priority or + * Weighted Fair Queuing (WFQ), with no reconfiguration of weights or + * queue mapping. This function resets the port’s scheduling mode to + * the default, which is WFQ, by enabling the WFQ bit for all queues. + * + * Return: 0 on success, or a negative error code on failure + */ +static int ksz88x3_tc_ets_del(struct ksz_device *dev, int port) +{ +	int ret, queue; + +	/* Iterate over all transmit queues for this port */ +	for (queue = 0; queue < dev->info->num_tx_queues; queue++) { +		u8 reg; + +		/* Calculate TXQ Split Control register address for this +		 * port/queue +		 */ +		reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); +		if (ksz_is_ksz8463(dev)) +			reg = ksz8463_tc_ctrl(port, queue); + +		/* Set WFQ enable bit to revert back to default scheduling +		 * mode +		 */ +		ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, +			       KSZ8873_TXQ_WFQ_ENABLE); +		if (ret) +			return ret; +	} + +	return 0; +} +  static int ksz_queue_set_strict(struct ksz_device *dev, int port, int queue)  {  	int ret; @@ -4159,6 +4405,7 @@ static int ksz_tc_ets_del(struct ksz_device *dev, int port)  	for (queue = 0; queue < dev->info->num_tx_queues; queue++) {  		ret = ksz_queue_set_wrr(dev, port, queue,  					KSZ9477_DEFAULT_WRR_WEIGHT); +  		if (ret)  			return ret;  	} @@ -4211,7 +4458,7 @@ static int ksz_tc_setup_qdisc_ets(struct dsa_switch *ds, int port,  	struct ksz_device *dev = ds->priv;  	int ret; -	if (is_ksz8(dev)) +	if (is_ksz8(dev) && !(ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)))  		return -EOPNOTSUPP;  	if (qopt->parent != TC_H_ROOT) { @@ -4225,9 +4472,16 @@ static int ksz_tc_setup_qdisc_ets(struct dsa_switch *ds, int port,  		if (ret)  			return ret; -		return ksz_tc_ets_add(dev, port, &qopt->replace_params); +		if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) +			return ksz88x3_tc_ets_add(dev, port, +						  &qopt->replace_params); +		else +			return ksz_tc_ets_add(dev, port, &qopt->replace_params);  	case TC_ETS_DESTROY: -		return ksz_tc_ets_del(dev, port); +		if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) +			return ksz88x3_tc_ets_del(dev, port); +		else +			return ksz_tc_ets_del(dev, port);  	case TC_ETS_STATS:  	case TC_ETS_GRAFT:  		return -EOPNOTSUPP; @@ -4567,7 +4821,16 @@ int ksz_switch_macaddr_get(struct dsa_switch *ds, int port,  	/* Program the switch MAC address to hardware */  	for (i = 0; i < ETH_ALEN; i++) { -		ret = ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i, addr[i]); +		if (ksz_is_ksz8463(dev)) { +			u16 addr16 = ((u16)addr[i] << 8) | addr[i + 1]; + +			ret = ksz_write16(dev, regs[REG_SW_MAC_ADDR] + i, +					  addr16); +			i++; +		} else { +			ret = ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i, +					 addr[i]); +		}  		if (ret)  			goto macaddr_drop;  	} @@ -5176,6 +5439,9 @@ int ksz_switch_register(struct ksz_device *dev)  						&dev->ports[port_num].interface);  				ksz_parse_rgmii_delay(dev, port_num, port); +				dev->ports[port_num].fiber = +					of_property_read_bool(port, +							      "micrel,fiber-mode");  			}  			of_node_put(ports);  		} diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index af17a9c030d4..a1eb39771bb9 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-2024 Microchip Technology Inc. + * Copyright (C) 2017-2025 Microchip Technology Inc.   */  #ifndef __KSZ_COMMON_H @@ -10,6 +10,7 @@  #include <linux/etherdevice.h>  #include <linux/kernel.h>  #include <linux/mutex.h> +#include <linux/pcs/pcs-xpcs.h>  #include <linux/phy.h>  #include <linux/regmap.h>  #include <net/dsa.h> @@ -93,6 +94,7 @@ struct ksz_chip_data {  	bool internal_phy[KSZ_MAX_NUM_PORTS];  	bool gbit_capable[KSZ_MAX_NUM_PORTS];  	bool ptp_capable; +	u8 sgmii_port;  	const struct regmap_access_table *wr_table;  	const struct regmap_access_table *rd_table;  }; @@ -132,6 +134,7 @@ struct ksz_port {  	u32 force:1;  	u32 read:1;			/* read MIB counters in background */  	u32 freeze:1;			/* MIB counter freeze is enabled */ +	u32 sgmii_adv_write:1;  	struct ksz_port_mib mib;  	phy_interface_t interface; @@ -141,8 +144,9 @@ struct ksz_port {  	void *acl_priv;  	struct ksz_irq pirq;  	u8 num; +	struct phylink_pcs *pcs;  #if IS_ENABLED(CONFIG_NET_DSA_MICROCHIP_KSZ_PTP) -	struct hwtstamp_config tstamp_config; +	struct kernel_hwtstamp_config tstamp_config;  	bool hwts_tx_en;  	bool hwts_rx_en;  	struct ksz_irq ptpirq; @@ -218,6 +222,7 @@ struct ksz_device {  /* List of supported models */  enum ksz_model { +	KSZ8463,  	KSZ8563,  	KSZ8567,  	KSZ8795, @@ -440,6 +445,8 @@ struct ksz_dev_ops {  	int (*reset)(struct ksz_device *dev);  	int (*init)(struct ksz_device *dev);  	void (*exit)(struct ksz_device *dev); + +	int (*pcs_create)(struct ksz_device *dev);  };  struct ksz_device *ksz_switch_alloc(struct device *base, void *priv); @@ -478,6 +485,11 @@ static inline struct regmap *ksz_regmap_32(struct ksz_device *dev)  	return dev->regmap[KSZ_REGMAP_32];  } +static inline bool ksz_is_ksz8463(struct ksz_device *dev) +{ +	return dev->chip_id == KSZ8463_CHIP_ID; +} +  static inline int ksz_read8(struct ksz_device *dev, u32 reg, u8 *val)  {  	unsigned int value; @@ -703,12 +715,13 @@ static inline bool ksz_is_8895_family(struct ksz_device *dev)  static inline bool is_ksz8(struct ksz_device *dev)  {  	return ksz_is_ksz87xx(dev) || ksz_is_ksz88x3(dev) || -	       ksz_is_8895_family(dev); +	       ksz_is_8895_family(dev) || ksz_is_ksz8463(dev);  }  static inline bool is_ksz88xx(struct ksz_device *dev)  { -	return ksz_is_ksz88x3(dev) || ksz_is_8895_family(dev); +	return ksz_is_ksz88x3(dev) || ksz_is_8895_family(dev) || +	       ksz_is_ksz8463(dev);  }  static inline bool is_ksz9477(struct ksz_device *dev) @@ -731,6 +744,21 @@ static inline bool is_lan937x_tx_phy(struct ksz_device *dev, int port)  		dev->chip_id == LAN9372_CHIP_ID) && port == KSZ_PORT_4;  } +static inline int ksz_get_sgmii_port(struct ksz_device *dev) +{ +	return dev->info->sgmii_port - 1; +} + +static inline bool ksz_has_sgmii_port(struct ksz_device *dev) +{ +	return dev->info->sgmii_port > 0; +} + +static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port) +{ +	return dev->info->sgmii_port == port + 1; +} +  /* STP State Defines */  #define PORT_TX_ENABLE			BIT(2)  #define PORT_RX_ENABLE			BIT(1) @@ -740,6 +768,7 @@ static inline bool is_lan937x_tx_phy(struct ksz_device *dev, int port)  #define REG_CHIP_ID0			0x00  #define SW_FAMILY_ID_M			GENMASK(15, 8) +#define KSZ84_FAMILY_ID			0x84  #define KSZ87_FAMILY_ID			0x87  #define KSZ88_FAMILY_ID			0x88  #define KSZ8895_FAMILY_ID		0x95 @@ -836,6 +865,25 @@ static inline bool is_lan937x_tx_phy(struct ksz_device *dev, int port)  #define SW_HI_SPEED_DRIVE_STRENGTH_S	4  #define SW_LO_SPEED_DRIVE_STRENGTH_S	0 +/* TXQ Split Control Register for per-port, per-queue configuration. + * Register 0xAF is TXQ Split for Q3 on Port 1. + * Register offset formula: 0xAF + (port * 4) + (3 - queue) + *   where: port = 0..2, queue = 0..3 + */ +#define KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue) \ +	(0xAF + ((port) * 4) + (3 - (queue))) + +/* Bit 7 selects between: + *   0 = Strict priority mode (highest-priority queue first) + *   1 = Weighted Fair Queuing (WFQ) mode: + *       Queue weights: Q3:Q2:Q1:Q0 = 8:4:2:1 + *       If any queues are empty, weight is redistributed. + * + * Note: This is referred to as "Weighted Fair Queuing" (WFQ) in KSZ8863/8873 + * documentation, and as "Weighted Round Robin" (WRR) in KSZ9477 family docs. + */ +#define KSZ8873_TXQ_WFQ_ENABLE		BIT(7) +  #define KSZ9477_REG_PORT_OUT_RATE_0	0x0420  #define KSZ9477_OUT_RATE_NO_LIMIT	0 @@ -899,4 +947,29 @@ static inline bool is_lan937x_tx_phy(struct ksz_device *dev, int port)  		[KSZ_REGMAP_32] = KSZ_REGMAP_ENTRY(32, swp, (regbits), (regpad), (regalign)), \  	} +#define KSZ8463_REGMAP_ENTRY(width, regbits, regpad, regalign)		\ +	{								\ +		.name = #width,						\ +		.val_bits = (width),					\ +		.reg_stride = (width / 8),				\ +		.reg_bits = (regbits) + (regalign),			\ +		.pad_bits = (regpad),					\ +		.read = ksz8463_spi_read,				\ +		.write = ksz8463_spi_write,				\ +		.max_register = BIT(regbits) - 1,			\ +		.cache_type = REGCACHE_NONE,				\ +		.zero_flag_mask = 1,					\ +		.use_single_read = 1,					\ +		.use_single_write = 1,					\ +		.lock = ksz_regmap_lock,				\ +		.unlock = ksz_regmap_unlock,				\ +	} + +#define KSZ8463_REGMAP_TABLE(ksz, regbits, regpad, regalign)		\ +	static const struct regmap_config ksz##_regmap_config[] = {	\ +		[KSZ_REGMAP_8] = KSZ8463_REGMAP_ENTRY(8, (regbits), (regpad), (regalign)), \ +		[KSZ_REGMAP_16] = KSZ8463_REGMAP_ENTRY(16, (regbits), (regpad), (regalign)), \ +		[KSZ_REGMAP_32] = KSZ8463_REGMAP_ENTRY(32, (regbits), (regpad), (regalign)), \ +	} +  #endif diff --git a/drivers/net/dsa/microchip/ksz_dcb.c b/drivers/net/dsa/microchip/ksz_dcb.c index c3b501997ac9..7131c5caac54 100644 --- a/drivers/net/dsa/microchip/ksz_dcb.c +++ b/drivers/net/dsa/microchip/ksz_dcb.c @@ -16,10 +16,12 @@   * Therefore, we define the base offset as 0x00 here to align with that logic.   */  #define KSZ8_REG_PORT_1_CTRL_0			0x00 +#define KSZ8463_REG_PORT_1_CTRL_0		0x6C  #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 KSZ8463_REG_TOS_DSCP_CTRL		0x16  #define KSZ88X3_REG_TOS_DSCP_CTRL		0x60  #define KSZ8765_REG_TOS_DSCP_CTRL		0x90 @@ -98,6 +100,8 @@ static void ksz_get_default_port_prio_reg(struct ksz_device *dev, int *reg,  		*reg = KSZ8_REG_PORT_1_CTRL_0;  		*mask = KSZ8_PORT_BASED_PRIO_M;  		*shift = __bf_shf(KSZ8_PORT_BASED_PRIO_M); +		if (ksz_is_ksz8463(dev)) +			*reg = KSZ8463_REG_PORT_1_CTRL_0;  	} else {  		*reg = KSZ9477_REG_PORT_MRI_MAC_CTRL;  		*mask = KSZ9477_PORT_BASED_PRIO_M; @@ -122,10 +126,12 @@ static void ksz_get_dscp_prio_reg(struct ksz_device *dev, int *reg,  		*reg = KSZ8765_REG_TOS_DSCP_CTRL;  		*per_reg = 4;  		*mask = GENMASK(1, 0); -	} else if (ksz_is_ksz88x3(dev)) { +	} else if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) {  		*reg = KSZ88X3_REG_TOS_DSCP_CTRL;  		*per_reg = 4;  		*mask = GENMASK(1, 0); +		if (ksz_is_ksz8463(dev)) +			*reg = KSZ8463_REG_TOS_DSCP_CTRL;  	} else {  		*reg = KSZ9477_REG_DIFFSERV_PRIO_MAP;  		*per_reg = 2; @@ -151,6 +157,8 @@ static void ksz_get_apptrust_map_and_reg(struct ksz_device *dev,  		*map = ksz8_apptrust_map_to_bit;  		*reg = KSZ8_REG_PORT_1_CTRL_0;  		*mask = KSZ8_PORT_DIFFSERV_ENABLE | KSZ8_PORT_802_1P_ENABLE; +		if (ksz_is_ksz8463(dev)) +			*reg = KSZ8463_REG_PORT_1_CTRL_0;  	} else {  		*map = ksz9477_apptrust_map_to_bit;  		*reg = KSZ9477_REG_PORT_MRI_PRIO_CTRL; diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c index 22fb9ef4645c..35fc21b1ee48 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.c +++ b/drivers/net/dsa/microchip/ksz_ptp.c @@ -319,22 +319,21 @@ int ksz_get_ts_info(struct dsa_switch *ds, int port, struct kernel_ethtool_ts_in  	return 0;  } -int ksz_hwtstamp_get(struct dsa_switch *ds, int port, struct ifreq *ifr) +int ksz_hwtstamp_get(struct dsa_switch *ds, int port, +		     struct kernel_hwtstamp_config *config)  {  	struct ksz_device *dev = ds->priv; -	struct hwtstamp_config *config;  	struct ksz_port *prt;  	prt = &dev->ports[port]; -	config = &prt->tstamp_config; +	*config = prt->tstamp_config; -	return copy_to_user(ifr->ifr_data, config, sizeof(*config)) ? -		-EFAULT : 0; +	return 0;  }  static int ksz_set_hwtstamp_config(struct ksz_device *dev,  				   struct ksz_port *prt, -				   struct hwtstamp_config *config) +				   struct kernel_hwtstamp_config *config)  {  	int ret; @@ -404,26 +403,21 @@ static int ksz_set_hwtstamp_config(struct ksz_device *dev,  	return ksz_ptp_enable_mode(dev);  } -int ksz_hwtstamp_set(struct dsa_switch *ds, int port, struct ifreq *ifr) +int ksz_hwtstamp_set(struct dsa_switch *ds, int port, +		     struct kernel_hwtstamp_config *config, +		     struct netlink_ext_ack *extack)  {  	struct ksz_device *dev = ds->priv; -	struct hwtstamp_config config;  	struct ksz_port *prt;  	int ret;  	prt = &dev->ports[port]; -	if (copy_from_user(&config, ifr->ifr_data, sizeof(config))) -		return -EFAULT; - -	ret = ksz_set_hwtstamp_config(dev, prt, &config); +	ret = ksz_set_hwtstamp_config(dev, prt, config);  	if (ret)  		return ret; -	memcpy(&prt->tstamp_config, &config, sizeof(config)); - -	if (copy_to_user(ifr->ifr_data, &config, sizeof(config))) -		return -EFAULT; +	prt->tstamp_config = *config;  	return 0;  } @@ -1136,8 +1130,8 @@ int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p)  	init_completion(&port->tstamp_msg_comp); -	ptpirq->domain = irq_domain_add_linear(dev->dev->of_node, ptpirq->nirqs, -					       &ksz_ptp_irq_domain_ops, ptpirq); +	ptpirq->domain = irq_domain_create_linear(dev_fwnode(dev->dev), ptpirq->nirqs, +						  &ksz_ptp_irq_domain_ops, ptpirq);  	if (!ptpirq->domain)  		return -ENOMEM; diff --git a/drivers/net/dsa/microchip/ksz_ptp.h b/drivers/net/dsa/microchip/ksz_ptp.h index 2f1783c0d723..3086e519b1b6 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.h +++ b/drivers/net/dsa/microchip/ksz_ptp.h @@ -39,8 +39,11 @@ void ksz_ptp_clock_unregister(struct dsa_switch *ds);  int ksz_get_ts_info(struct dsa_switch *ds, int port,  		    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); +int ksz_hwtstamp_get(struct dsa_switch *ds, int port, +		     struct kernel_hwtstamp_config *config); +int ksz_hwtstamp_set(struct dsa_switch *ds, int port, +		     struct kernel_hwtstamp_config *config, +		     struct netlink_ext_ack *extack);  void ksz_port_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb);  void ksz_port_deferred_xmit(struct kthread_work *work);  bool ksz_port_rxtstamp(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 b633d263098c..d8001734b057 100644 --- a/drivers/net/dsa/microchip/ksz_spi.c +++ b/drivers/net/dsa/microchip/ksz_spi.c @@ -16,6 +16,10 @@  #include "ksz_common.h" +#define KSZ8463_SPI_ADDR_SHIFT			13 +#define KSZ8463_SPI_ADDR_ALIGN			3 +#define KSZ8463_SPI_TURNAROUND_SHIFT		2 +  #define KSZ8795_SPI_ADDR_SHIFT			12  #define KSZ8795_SPI_ADDR_ALIGN			3  #define KSZ8795_SPI_TURNAROUND_SHIFT		1 @@ -37,6 +41,99 @@ KSZ_REGMAP_TABLE(ksz8863, 16, KSZ8863_SPI_ADDR_SHIFT,  KSZ_REGMAP_TABLE(ksz9477, 32, KSZ9477_SPI_ADDR_SHIFT,  		 KSZ9477_SPI_TURNAROUND_SHIFT, KSZ9477_SPI_ADDR_ALIGN); +static u16 ksz8463_reg(u16 reg, size_t size) +{ +	switch (size) { +	case 1: +		reg = ((reg >> 2) << 4) | (1 << (reg & 3)); +		break; +	case 2: +		reg = ((reg >> 2) << 4) | (reg & 2 ? 0x0c : 0x03); +		break; +	default: +		reg = ((reg >> 2) << 4) | 0xf; +		break; +	} +	reg <<= KSZ8463_SPI_TURNAROUND_SHIFT; +	return reg; +} + +static int ksz8463_spi_read(void *context, +			    const void *reg, size_t reg_size, +			    void *val, size_t val_size) +{ +	struct device *dev = context; +	struct spi_device *spi = to_spi_device(dev); +	u8 bytes[2]; +	u16 cmd; +	int rc; + +	if (reg_size > 2 || val_size > 4) +		return -EINVAL; +	memcpy(&cmd, reg, sizeof(u16)); +	cmd = ksz8463_reg(cmd, val_size); +	/* SPI command uses big-endian format. */ +	put_unaligned_be16(cmd, bytes); +	rc = spi_write_then_read(spi, bytes, reg_size, val, val_size); +#if defined(__BIG_ENDIAN) +	/* Register value uses little-endian format so need to convert when +	 * running in big-endian system. +	 */ +	if (!rc && val_size > 1) { +		if (val_size == 2) { +			u16 v = get_unaligned_le16(val); + +			memcpy(val, &v, sizeof(v)); +		} else if (val_size == 4) { +			u32 v = get_unaligned_le32(val); + +			memcpy(val, &v, sizeof(v)); +		} +	} +#endif +	return rc; +} + +static int ksz8463_spi_write(void *context, const void *data, size_t count) +{ +	struct device *dev = context; +	struct spi_device *spi = to_spi_device(dev); +	size_t val_size = count - 2; +	u8 bytes[6]; +	u16 cmd; + +	if (count <= 2 || count > 6) +		return -EINVAL; +	memcpy(bytes, data, count); +	memcpy(&cmd, data, sizeof(u16)); +	cmd = ksz8463_reg(cmd, val_size); +	cmd |= (1 << (KSZ8463_SPI_ADDR_SHIFT + KSZ8463_SPI_TURNAROUND_SHIFT)); +	/* SPI command uses big-endian format. */ +	put_unaligned_be16(cmd, bytes); +#if defined(__BIG_ENDIAN) +	/* Register value uses little-endian format so need to convert when +	 * running in big-endian system. +	 */ +	if (val_size == 2) { +		u8 *val = &bytes[2]; +		u16 v; + +		memcpy(&v, val, sizeof(v)); +		put_unaligned_le16(v, val); +	} else if (val_size == 4) { +		u8 *val = &bytes[2]; +		u32 v; + +		memcpy(&v, val, sizeof(v)); +		put_unaligned_le32(v, val); +	} +#endif +	return spi_write(spi, bytes, count); +} + +KSZ8463_REGMAP_TABLE(ksz8463, KSZ8463_SPI_ADDR_SHIFT, 0, +		     KSZ8463_SPI_ADDR_ALIGN); +  static int ksz_spi_probe(struct spi_device *spi)  {  	const struct regmap_config *regmap_config; @@ -58,6 +155,8 @@ static int ksz_spi_probe(struct spi_device *spi)  	dev->chip_id = chip->chip_id;  	if (chip->chip_id == KSZ88X3_CHIP_ID)  		regmap_config = ksz8863_regmap_config; +	else if (chip->chip_id == KSZ8463_CHIP_ID) +		regmap_config = ksz8463_regmap_config;  	else if (chip->chip_id == KSZ8795_CHIP_ID ||  		 chip->chip_id == KSZ8794_CHIP_ID ||  		 chip->chip_id == KSZ8765_CHIP_ID) @@ -126,6 +225,10 @@ static void ksz_spi_shutdown(struct spi_device *spi)  static const struct of_device_id ksz_dt_ids[] = {  	{ +		.compatible = "microchip,ksz8463", +		.data = &ksz_switch_chips[KSZ8463] +	}, +	{  		.compatible = "microchip,ksz8765",  		.data = &ksz_switch_chips[KSZ8765]  	}, @@ -214,6 +317,7 @@ static const struct of_device_id ksz_dt_ids[] = {  MODULE_DEVICE_TABLE(of, ksz_dt_ids);  static const struct spi_device_id ksz_spi_ids[] = { +	{ "ksz8463" },  	{ "ksz8765" },  	{ "ksz8794" },  	{ "ksz8795" },  | 
