From nobody Tue Dec 16 05:55:06 2025 Received: from esa.microchip.iphmx.com (esa.microchip.iphmx.com [68.232.153.233]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 833981DFD84; Tue, 20 May 2025 23:08:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=68.232.153.233 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1747782483; cv=none; b=siuilcau/dMwaJ9Ex3k7Mf5/imrMNQwIV9z3dhNCmmQWXv6T8VUjrwJYKtitPCDIauOVtMTAKLwSlSBz3+Vl68lQLdd1WyUrpfwhd+yAK4rl01vjSS/pYNRcTATdpxsSaGndBU9yUtNWD1gagdovZfCzEBTjxwp/YWkymrqrWsc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1747782483; c=relaxed/simple; bh=vgxi+U83dG2yMvwjJ+Dtit2QWaexktPHwN2yVtyIhlQ=; h=From:To:CC:Subject:Date:Message-ID:MIME-Version:Content-Type; b=cgWQV09vDnhptgRBzENwXkzLJP+PuhK2mVQGiZeZgh33s6u+8f3qIPnaWctfiJ++fpqsX7u76rLR/ErMsnfbDYAP/nKRyYwbq9NXDEwoaVuWV+FWbQXLXGzkbXzvX2NWV80xbqjAihoWORhQl7jYLrTi334UhIjy425p2GAy68o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=microchip.com; spf=pass smtp.mailfrom=microchip.com; dkim=pass (2048-bit key) header.d=microchip.com header.i=@microchip.com header.b=Yq7bFhd3; arc=none smtp.client-ip=68.232.153.233 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=microchip.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=microchip.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=microchip.com header.i=@microchip.com header.b="Yq7bFhd3" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=microchip.com; i=@microchip.com; q=dns/txt; s=mchp; t=1747782482; x=1779318482; h=from:to:cc:subject:date:message-id:mime-version: content-transfer-encoding; bh=vgxi+U83dG2yMvwjJ+Dtit2QWaexktPHwN2yVtyIhlQ=; b=Yq7bFhd3PhnfjLIYv74c/ZtyJrw9H6cLN3I2SY/zRJt0xo6j+u224fve 1pnTdbkDU2AZwTrtPCZ81JTlCGo1jgBCDhzWUIwBxOH4xfBqd65ENuHZ0 e7c8WX235IFKQLAoUDSo4CPrzmbW5IXCzYruRLgO7NwRhfzfDkYQrg1TG nEuVMqHFgkaP5DG6q8tGaeQMhw8v11cVsAr3vcgVvcPUEIAli5ViFQ9Wr 9uH3uUA5MZsnVT2K8LFxMWgjfDvQP29iUCNmv1JOnS3EqS6wvI2iivWV4 wUqvjlZpOSnN6E3JyVMSIIXN4pz3uVxtR4vdc0k+WBBKH5naYxlJknsGc g==; X-CSE-ConnectionGUID: YjXhzVLbQz+WCBiz8JZUDQ== X-CSE-MsgGUID: LKqa3f4TTeeHpyneUti8cA== X-IronPort-AV: E=Sophos;i="6.15,303,1739862000"; d="scan'208";a="41876370" X-Amp-Result: SKIPPED(no attachment in message) Received: from unknown (HELO email.microchip.com) ([170.129.1.10]) by esa3.microchip.iphmx.com with ESMTP/TLS/ECDHE-RSA-AES128-GCM-SHA256; 20 May 2025 16:08:01 -0700 Received: from chn-vm-ex02.mchp-main.com (10.10.85.144) by chn-vm-ex04.mchp-main.com (10.10.85.152) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.44; Tue, 20 May 2025 16:07:20 -0700 Received: from pop-os.microchip.com (10.10.85.11) by chn-vm-ex02.mchp-main.com (10.10.85.144) with Microsoft SMTP Server id 15.1.2507.44 via Frontend Transport; Tue, 20 May 2025 16:07:20 -0700 From: To: Andrew Lunn , Woojung Huh , Russell King , Vladimir Oltean CC: ALOK TIWARI , Heiner Kallweit , Maxime Chevallier , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , , , , Tristram Ha Subject: [PATCH net-next v5] net: dsa: microchip: Add SGMII port support to KSZ9477 switch Date: Tue, 20 May 2025 16:07:20 -0700 Message-ID: <20250520230720.23425-1-Tristram.Ha@microchip.com> X-Mailer: git-send-email 2.34.1 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Tristram Ha The KSZ9477 switch driver uses the XPCS driver to operate its SGMII port. However there are some hardware bugs in the KSZ9477 SGMII module so workarounds are needed. There was a proposal to update the XPCS driver to accommodate KSZ9477, but the new code is not generic enough to be used by other vendors. It is better to do all these workarounds inside the KSZ9477 driver instead of modifying the XPCS driver. There are 3 hardware issues. The first is the MII_ADVERTISE register needs to be write once after reset for the correct code word to be sent. The XPCS driver disables auto-negotiation first before configuring the SGMII/1000BASE-X mode and then enables it back. The KSZ9477 driver then writes the MII_ADVERTISE register before enabling auto-negotiation. In 1000BASE-X mode the MII_ADVERTISE register will be set, so KSZ9477 driver does not need to write it. The second issue is the MII_BMCR register needs to set the exact speed and duplex mode when running in SGMII mode. During link polling the KSZ9477 will check the speed and duplex mode are different from previous ones and update the MII_BMCR register accordingly. The last issue is 1000BASE-X mode does not work with auto-negotiation on. The cause is the local port hardware does not know the link is up and so network traffic is not forwarded. The workaround is to write 2 additional bits when 1000BASE-X mode is configured. Note the SGMII interrupt in the port cannot be masked. As that interrupt is not handled in the KSZ9477 driver the SGMII interrupt bit will not be set even when the XPCS driver sets it. Signed-off-by: Tristram Ha Reviewed-by: Maxime Chevallier Tested-by: Maxime Chevallier --- v5 - Add Maxime's review - Update with Alok's recommendations v4 - update for last ksz_common.c merge v3 - rebase with latest commit v2 - add Kconfig for required XPCS driver build drivers/net/dsa/microchip/Kconfig | 1 + drivers/net/dsa/microchip/ksz9477.c | 194 ++++++++++++++++++++++++- drivers/net/dsa/microchip/ksz9477.h | 4 +- drivers/net/dsa/microchip/ksz_common.c | 37 ++++- drivers/net/dsa/microchip/ksz_common.h | 23 ++- 5 files changed, 252 insertions(+), 7 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/ksz9477.c b/drivers/net/dsa/microchi= p/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. */ =20 #include @@ -161,6 +161,190 @@ static int ksz9477_wait_alu_sta_ready(struct ksz_devi= ce *dev) 10, 1000); } =20 +static void port_sgmii_s(struct ksz_device *dev, uint port, u16 devid, u16= reg) +{ + u32 data; + + data =3D (devid & MII_MMD_CTRL_DEVAD_MASK) << 16; + data |=3D 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 =3D bus->priv; + int port =3D 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 =3D=3D MDIO_MMD_PMAPMD) { + if (reg =3D=3D MDIO_DEVID1) + val =3D 0x9477; + else if (reg =3D=3D MDIO_DEVID2) + val =3D 0x22 << 10; + } else if (mmd =3D=3D MDIO_MMD_VEND2) { + struct ksz_port *p =3D &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 =3D=3D MMD_SR_MII_AUTO_NEG_STATUS) { + int duplex, speed; + + if (val & SR_MII_STAT_LINK_UP) { + speed =3D (val >> SR_MII_STAT_S) & SR_MII_STAT_M; + if (speed =3D=3D SR_MII_STAT_1000_MBPS) + speed =3D SPEED_1000; + else if (speed =3D=3D SR_MII_STAT_100_MBPS) + speed =3D SPEED_100; + else + speed =3D SPEED_10; + + if (val & SR_MII_STAT_FULL_DUPLEX) + duplex =3D DUPLEX_FULL; + else + duplex =3D DUPLEX_HALF; + + if (!p->phydev.link || + p->phydev.speed !=3D speed || + p->phydev.duplex !=3D duplex) { + u16 ctrl; + + p->phydev.link =3D 1; + p->phydev.speed =3D speed; + p->phydev.duplex =3D duplex; + port_sgmii_r(dev, port, mmd, MII_BMCR, + &ctrl); + ctrl &=3D BMCR_ANENABLE; + ctrl |=3D mii_bmcr_encode_fixed(speed, + duplex); + port_sgmii_w(dev, port, mmd, MII_BMCR, + ctrl); + } + } else { + p->phydev.link =3D 0; + } + } else if (reg =3D=3D MII_BMSR) { + p->phydev.link =3D (val & BMSR_LSTATUS); + } + } + + return val; +} + +static int ksz9477_pcs_write(struct mii_bus *bus, int phy, int mmd, int re= g, + u16 val) +{ + struct ksz_device *dev =3D bus->priv; + int port =3D ksz_get_sgmii_port(dev); + + if (mmd =3D=3D MDIO_MMD_VEND2) { + struct ksz_port *p =3D &dev->ports[port]; + + if (reg =3D=3D MMD_SR_MII_AUTO_NEG_CTRL) { + u16 sgmii_mode =3D 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 |=3D 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 &=3D ~SR_MII_AUTO_NEG_COMPLETE_INTR; + } else if (reg =3D=3D 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 |=3D ADVERTISE_1000XPAUSE; + adv &=3D ~ADVERTISE_1000XPSE_ASYM; + port_sgmii_w(dev, port, mmd, MII_ADVERTISE, + adv); + p->sgmii_adv_write =3D 1; + } else if (val & BMCR_RESET) { + p->sgmii_adv_write =3D 0; + } + } else if (reg =3D=3D MII_ADVERTISE) { + /* XPCS driver writes to this register so there is no + * need to update it for the errata. + */ + p->sgmii_adv_write =3D 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 =3D ksz_get_sgmii_port(dev); + struct ksz_port *p =3D &dev->ports[port]; + struct phylink_pcs *pcs; + struct mii_bus *bus; + int ret; + + bus =3D devm_mdiobus_alloc(dev->dev); + if (!bus) + return -ENOMEM; + + bus->name =3D "ksz_pcs_mdio_bus"; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-pcs", + dev_name(dev->dev)); + bus->read_c45 =3D &ksz9477_pcs_read; + bus->write_c45 =3D &ksz9477_pcs_write; + bus->parent =3D dev->dev; + bus->phy_mask =3D ~0; + bus->priv =3D dev; + + ret =3D devm_mdiobus_register(dev->dev, bus); + if (ret) + return ret; + + pcs =3D xpcs_create_pcs_mdiodev(bus, 0); + if (IS_ERR(pcs)) + return PTR_ERR(pcs); + p->pcs =3D 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 por= t, =20 if (dev->info->gbit_capable[port]) config->mac_capabilities |=3D MAC_1000FD; + + if (ksz_is_sgmii_port(dev, port)) { + struct ksz_port *p =3D &dev->ports[port]; + + phy_interface_or(config->supported_interfaces, + config->supported_interfaces, + p->pcs->supported_interfaces); + } } =20 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/microchi= p/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. */ =20 #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); =20 +int ksz9477_pcs_create(struct ksz_device *dev); + #endif diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/micro= chip/ksz_common.c index f492fa9f6dd4..f424b3d10781 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. */ =20 #include @@ -408,12 +408,29 @@ static void ksz9477_phylink_mac_link_up(struct phylin= k_config *config, int speed, int duplex, bool tx_pause, bool rx_pause); =20 +static struct phylink_pcs * +ksz_phylink_mac_select_pcs(struct phylink_config *config, + phy_interface_t interface) +{ + struct dsa_port *dp =3D dsa_phylink_to_port(config); + struct ksz_device *dev =3D dp->ds->priv; + struct ksz_port *p =3D &dev->ports[dp->index]; + + if (ksz_is_sgmii_port(dev, dp->index) && + (interface =3D=3D PHY_INTERFACE_MODE_SGMII || + interface =3D=3D PHY_INTERFACE_MODE_1000BASEX)) + return p->pcs; + + return NULL; +} + static const struct phylink_mac_ops ksz9477_phylink_mac_ops =3D { .mac_config =3D ksz_phylink_mac_config, .mac_link_down =3D ksz_phylink_mac_link_down, .mac_link_up =3D ksz9477_phylink_mac_link_up, .mac_disable_tx_lpi =3D ksz_phylink_mac_disable_tx_lpi, .mac_enable_tx_lpi =3D ksz_phylink_mac_enable_tx_lpi, + .mac_select_pcs =3D ksz_phylink_mac_select_pcs, }; =20 static const struct ksz_dev_ops ksz9477_dev_ops =3D { @@ -451,6 +468,7 @@ static const struct ksz_dev_ops ksz9477_dev_ops =3D { .reset =3D ksz9477_reset_switch, .init =3D ksz9477_switch_init, .exit =3D ksz9477_switch_exit, + .pcs_create =3D ksz9477_pcs_create, }; =20 static const struct phylink_mac_ops lan937x_phylink_mac_ops =3D { @@ -1093,8 +1111,7 @@ static const struct regmap_range ksz9477_valid_regs[]= =3D { 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), @@ -1610,6 +1627,7 @@ const struct ksz_chip_data ksz_switch_chips[] =3D { true, false, false}, .gbit_capable =3D {true, true, true, true, true, true, true}, .ptp_capable =3D true, + .sgmii_port =3D 7, .wr_table =3D &ksz9477_register_set, .rd_table =3D &ksz9477_register_set, }, @@ -2002,6 +2020,7 @@ const struct ksz_chip_data ksz_switch_chips[] =3D { .internal_phy =3D {true, true, true, true, true, false, false}, .gbit_capable =3D {true, true, true, true, true, true, true}, + .sgmii_port =3D 7, .wr_table =3D &ksz9477_register_set, .rd_table =3D &ksz9477_register_set, }, @@ -2137,7 +2156,7 @@ void ksz_r_mib_stats64(struct ksz_device *dev, int po= rt) =20 spin_unlock(&mib->stats64_lock); =20 - if (dev->info->phy_errata_9477) { + if (dev->info->phy_errata_9477 && !ksz_is_sgmii_port(dev, port)) { ret =3D ksz9477_errata_monitor(dev, port, raw->tx_late_col); if (ret) dev_err(dev->dev, "Failed to monitor transmission halt\n"); @@ -2845,6 +2864,12 @@ static int ksz_setup(struct dsa_switch *ds) if (ret) return ret; =20 + if (ksz_has_sgmii_port(dev) && dev->dev_ops->pcs_create) { + ret =3D dev->dev_ops->pcs_create(dev); + if (ret) + return ret; + } + /* set broadcast storm protection 10% rate */ regmap_update_bits(ksz_regmap_16(dev), regs[S_BROADCAST_CTRL], BROADCAST_STORM_RATE, @@ -3692,6 +3717,10 @@ static void ksz_phylink_mac_config(struct phylink_co= nfig *config, if (dev->info->internal_phy[port]) return; =20 + /* 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; diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/micro= chip/ksz_common.h index a034017568cd..a08417df2ca4 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. */ =20 #ifndef __KSZ_COMMON_H @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -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; =20 struct ksz_port_mib mib; phy_interface_t interface; @@ -141,6 +144,7 @@ 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 kernel_hwtstamp_config tstamp_config; bool hwts_tx_en; @@ -440,6 +444,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); }; =20 struct ksz_device *ksz_switch_alloc(struct device *base, void *priv); @@ -731,6 +737,21 @@ static inline bool is_lan937x_tx_phy(struct ksz_device= *dev, int port) dev->chip_id =3D=3D LAN9372_CHIP_ID) && port =3D=3D KSZ_PORT_4; } =20 +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 =3D=3D port + 1; +} + /* STP State Defines */ #define PORT_TX_ENABLE BIT(2) #define PORT_RX_ENABLE BIT(1) --=20 2.34.1