linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH RFC net-next 22/23] net: dsa: add driver for MaxLinear GSW1xx switch family
@ 2025-08-16 19:57 Daniel Golle
  2025-08-21 18:53 ` Sverdlin, Alexander
  0 siblings, 1 reply; 5+ messages in thread
From: Daniel Golle @ 2025-08-16 19:57 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Hauke Mehrtens, Simon Horman,
	Russell King, Florian Fainelli, Arkadi Sharshevsky, linux-kernel,
	netdev
  Cc: Andreas Schirm, Lukas Stockmann, Alexander Sverdlin,
	Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu,
	Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng,
	Livia M. Rosu, John Crispin

Add driver for the MaxLinear GSW1xx family of Ethernet switch ICs which
are based on the same IP as the Lantiq/Intel GSWIP found in the Lantiq VR9
and Intel GRX MIPS router SoCs. The main difference is that instead of
using memory-mapped I/O to communicate with the host CPU these ICs are
connected via MDIO (or SPI, which isn't supported by this driver).
Implement the regmap API to access the switch registers over MDIO to allow
reusing lantiq_gswip_common for all core functionality.

The GSW1xx also comes with a SerDes port capable of 1000Base-X, SGMII and
2500Base-X, which can either be used to connect an external PHY or SFP
cage, or as the CPU port. Support for the SerDes interface is implemented
in this driver using the phylink_pcs interface.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
 drivers/net/dsa/Kconfig          |  12 +
 drivers/net/dsa/Makefile         |   1 +
 drivers/net/dsa/mxl-gsw1xx.c     | 710 +++++++++++++++++++++++++++++++
 drivers/net/dsa/mxl-gsw1xx_pce.h | 160 +++++++
 4 files changed, 883 insertions(+)
 create mode 100644 drivers/net/dsa/mxl-gsw1xx.c
 create mode 100644 drivers/net/dsa/mxl-gsw1xx_pce.h

diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 95275f7bc701..f4e412f9540e 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -39,6 +39,18 @@ config NET_DSA_LANTIQ_GSWIP
 	  This enables support for the Lantiq / Intel GSWIP 2.1 found in
 	  the xrx200 / VR9 SoC.
 
+config NET_DSA_MXL_GSW1XX
+	tristate "MaxLinear GSW1xx Ethernet switch support"
+	select NET_DSA_TAG_MXL_GSW1XX
+	select NET_DSA_LANTIQ_COMMON
+	help
+	  This enables support for the MaxLinear GSW1xx family of 1GE switches
+	    GSW120 4 port, 2 PHYs, RGMII & SGMII/2500Base-X
+	    GSW125 4 port, 2 PHYs, RGMII & SGMII/2500Base-X, industrial temperature
+	    GSW140 6 port, 4 PHYs, RGMII & SGMII/2500Base-X
+	    GSW141 6 port, 4 PHYs, RGMII & SGMII
+	    GSW145 6 port, 4 PHYs, RGMII & SGMII/2500Base-X, industrial temperature
+
 config NET_DSA_MT7530
 	tristate "MediaTek MT7530 and MT7531 Ethernet switch support"
 	select NET_DSA_TAG_MTK
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index ac54bf870cd6..2c26e91c7c73 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_FIXED_PHY)		+= dsa_loop_bdinfo.o
 endif
 obj-$(CONFIG_NET_DSA_LANTIQ_GSWIP) += lantiq_gswip.o
 obj-$(CONFIG_NET_DSA_LANTIQ_COMMON) += lantiq_gswip_common.o
+obj-$(CONFIG_NET_DSA_MXL_GSW1XX) += mxl-gsw1xx.o
 obj-$(CONFIG_NET_DSA_MT7530)	+= mt7530.o
 obj-$(CONFIG_NET_DSA_MT7530_MDIO) += mt7530-mdio.o
 obj-$(CONFIG_NET_DSA_MT7530_MMIO) += mt7530-mmio.o
diff --git a/drivers/net/dsa/mxl-gsw1xx.c b/drivers/net/dsa/mxl-gsw1xx.c
new file mode 100644
index 000000000000..719218cc92ce
--- /dev/null
+++ b/drivers/net/dsa/mxl-gsw1xx.c
@@ -0,0 +1,710 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DSA Driver for MaxLinear GSW1xx switch devices
+ *
+ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
+ * Copyright (C) 2023 - 2024 MaxLinear Inc.
+ * Copyright (C) 2022 Snap One, LLC.  All rights reserved.
+ * Copyright (C) 2017 - 2019 Hauke Mehrtens <hauke@hauke-m.de>
+ * Copyright (C) 2012 Avinash Jayaraman <ajayaraman@maxlinear.com>, Bing tao Xu <bxu@maxlinear.com>, Liang Xu <lxu@maxlinear.com>, Juraj Povazanec <jpovazanec@maxlinear.com>, "Fanni (Fang-Yi) Chan" <fchan@maxlinear.com>, "Benny (Ying-Tsan) Weng" <yweng@maxlinear.com>, "Livia M. Rosu" <lrosu@maxlinear.com>, John Crispin <john@phrozen.org>
+ * Copyright (C) 2010 Lantiq Deutschland
+ */
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_mdio.h>
+#include <linux/regmap.h>
+#include <net/dsa.h>
+
+#include "lantiq_gswip.h"
+#include "mxl-gsw1xx_pce.h"
+
+struct gsw1xx_priv {
+	struct gswip_priv	gswip;
+	struct mdio_device	*mdio_dev;
+	int			smdio_badr;
+	struct regmap_config	regmap_config[7];
+	int			regmap_config_count;
+	struct			regmap *sgmii;
+	struct			regmap *gpio;
+	struct			regmap *clk;
+	struct			regmap *shell;
+	struct			phylink_pcs sgmii_pcs;
+};
+
+static int gsw1xx_config_smdio_badr(struct gsw1xx_priv *priv,
+				    unsigned int reg)
+{
+	struct mii_bus *bus = priv->mdio_dev->bus;
+	int sw_addr = priv->mdio_dev->addr;
+	int smdio_badr = priv->smdio_badr;
+	int res;
+
+	if (smdio_badr == GSW1XX_SMDIO_BADR_UNKNOWN ||
+	    reg - smdio_badr >= GSW1XX_SMDIO_BADR ||
+	    smdio_badr > reg) {
+		/* Configure the Switch Base Address */
+		smdio_badr = reg & ~GENMASK(3, 0);
+		res = __mdiobus_write(bus, sw_addr, GSW1XX_SMDIO_BADR, smdio_badr);
+		if (res < 0) {
+			dev_err(&priv->mdio_dev->dev,
+				"%s: Error %d, configuring switch base\n",
+				__func__, res);
+			return res;
+		}
+		priv->smdio_badr = smdio_badr;
+	}
+
+	return smdio_badr;
+}
+
+static int gsw1xx_regmap_read(void *context, unsigned int reg,
+			      unsigned int *val)
+{
+	struct gsw1xx_priv *priv = context;
+	struct mii_bus *bus = priv->mdio_dev->bus;
+	int sw_addr = priv->mdio_dev->addr;
+	int smdio_badr;
+	int res;
+
+	smdio_badr = gsw1xx_config_smdio_badr(priv, reg);
+	if (smdio_badr < 0)
+		return smdio_badr;
+
+	res = __mdiobus_read(bus, sw_addr, reg - smdio_badr);
+	if (res < 0) {
+		dev_err(&priv->mdio_dev->dev, "%s: Error %d reading 0x%x\n",
+			__func__, res, reg);
+		return res;
+	}
+
+	*val = res;
+
+	return 0;
+}
+
+static int gsw1xx_regmap_write(void *context, unsigned int reg,
+			       unsigned int val)
+{
+	struct gsw1xx_priv *priv = context;
+	struct mii_bus *bus = priv->mdio_dev->bus;
+	int sw_addr = priv->mdio_dev->addr;
+	int smdio_badr;
+	int res;
+
+	smdio_badr = gsw1xx_config_smdio_badr(priv, reg);
+	if (smdio_badr < 0)
+		return smdio_badr;
+
+	res = __mdiobus_write(bus, sw_addr, reg - smdio_badr, val);
+	if (res < 0)
+		dev_err(&priv->mdio_dev->dev,
+			"%s: Error %d, writing 0x%x:0x%x\n", __func__, res, reg,
+			val);
+
+	return res;
+}
+
+static const struct regmap_bus gsw1xx_regmap_bus = {
+	.reg_write = gsw1xx_regmap_write,
+	.reg_read = gsw1xx_regmap_read,
+};
+
+static void gsw1xx_mdio_regmap_lock(void *mdio_lock)
+{
+	mutex_lock_nested(mdio_lock, MDIO_MUTEX_NESTED);
+}
+
+static void gsw1xx_mdio_regmap_unlock(void *mdio_lock)
+{
+	mutex_unlock(mdio_lock);
+}
+
+static int gsw1xx_sgmii_phy_xaui_write(struct gsw1xx_priv *priv, u16 addr,
+				       u16 data)
+{
+	int ret, val;
+
+	ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_D, data);
+	if (ret < 0)
+		return ret;
+
+	ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_A, addr);
+	if (ret < 0)
+		return ret;
+
+	ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_C,
+			   GSW1XX_SGMII_PHY_WRITE |
+			   GSW1XX_SGMII_PHY_RESET_N);
+	if (ret < 0)
+		return ret;
+
+	return regmap_read_poll_timeout(priv->sgmii, GSW1XX_SGMII_PHY_C,
+					val, val & GSW1XX_SGMII_PHY_STATUS,
+					1000, 100000);
+}
+
+static struct gsw1xx_priv *sgmii_pcs_to_gsw1xx(struct phylink_pcs *pcs)
+{
+	return container_of(pcs, struct gsw1xx_priv, sgmii_pcs);
+}
+
+static int gsw1xx_sgmii_pcs_config(struct phylink_pcs *pcs,
+				   unsigned int neg_mode,
+				   phy_interface_t interface,
+				   const unsigned long *advertising,
+				   bool permit_pause_to_mac)
+{
+	struct gsw1xx_priv *priv = sgmii_pcs_to_gsw1xx(pcs);
+	bool sgmii_mac_mode = dsa_is_user_port(priv->gswip.ds, GSW1XX_SGMII_PORT);
+	u16 txaneg, anegctl, val, nco_ctrl;
+	int ret;
+
+	/* Assert and deassert SGMII shell reset */
+	ret = regmap_set_bits(priv->shell, GSW1XX_SHELL_RST_REQ,
+			      GSW1XX_RST_REQ_SGMII_SHELL);
+	if (ret < 0)
+		return ret;
+
+	ret = regmap_clear_bits(priv->shell, GSW1XX_SHELL_RST_REQ,
+				GSW1XX_RST_REQ_SGMII_SHELL);
+	if (ret < 0)
+		return ret;
+
+	/* Hardware Bringup FSM Enable  */
+	ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_HWBU_CTRL,
+			   GSW1XX_SGMII_PHY_HWBU_CTRL_EN_HWBU_FSM |
+			   GSW1XX_SGMII_PHY_HWBU_CTRL_HW_FSM_EN);
+	if (ret < 0)
+		return ret;
+
+	/* Configure SGMII PHY Receiver */
+	val = FIELD_PREP(GSW1XX_SGMII_PHY_RX0_CFG2_EQ,
+			 GSW1XX_SGMII_PHY_RX0_CFG2_EQ_DEF) |
+	      GSW1XX_SGMII_PHY_RX0_CFG2_LOS_EN |
+	      GSW1XX_SGMII_PHY_RX0_CFG2_TERM_EN |
+	      FIELD_PREP(GSW1XX_SGMII_PHY_RX0_CFG2_FILT_CNT,
+			 GSW1XX_SGMII_PHY_RX0_CFG2_FILT_CNT_DEF);
+
+	// if (!priv->dts.sgmii_rx_invert)
+		val |= GSW1XX_SGMII_PHY_RX0_CFG2_INVERT;
+
+	ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_RX0_CFG2, val);
+	if (ret < 0)
+		return ret;
+
+	/* Reset and Release TBI */
+	val = GSW1XX_SGMII_TBI_TBICTL_INITTBI | GSW1XX_SGMII_TBI_TBICTL_ENTBI |
+	      GSW1XX_SGMII_TBI_TBICTL_CRSTRR | GSW1XX_SGMII_TBI_TBICTL_CRSOFF;
+	ret = regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_TBICTL, val);
+	if (ret < 0)
+		return ret;
+	val &= ~GSW1XX_SGMII_TBI_TBICTL_INITTBI;
+	ret = regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_TBICTL, val);
+	if (ret < 0)
+		return ret;
+
+	/* Release Tx Data Buffers */
+	ret = regmap_set_bits(priv->sgmii, GSW1XX_SGMII_PCS_TXB_CTL,
+			      GSW1XX_SGMII_PCS_TXB_CTL_INIT_TX_TXB);
+	if (ret < 0)
+		return ret;
+	ret = regmap_clear_bits(priv->sgmii, GSW1XX_SGMII_PCS_TXB_CTL,
+				GSW1XX_SGMII_PCS_TXB_CTL_INIT_TX_TXB);
+	if (ret < 0)
+		return ret;
+
+	/* Release Rx Data Buffers */
+	ret = regmap_set_bits(priv->sgmii, GSW1XX_SGMII_PCS_RXB_CTL,
+			      GSW1XX_SGMII_PCS_RXB_CTL_INIT_RX_RXB);
+	if (ret < 0)
+		return ret;
+	ret = regmap_clear_bits(priv->sgmii, GSW1XX_SGMII_PCS_RXB_CTL,
+				GSW1XX_SGMII_PCS_RXB_CTL_INIT_RX_RXB);
+	if (ret < 0)
+		return ret;
+
+	anegctl = GSW1XX_SGMII_TBI_ANEGCTL_OVRANEG;
+	if (neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED)
+		anegctl |= GSW1XX_SGMII_TBI_ANEGCTL_OVRABL;
+
+	switch (phylink_get_link_timer_ns(interface)) {
+	case 10000:
+		anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_LT,
+				      GSW1XX_SGMII_TBI_ANEGCTL_LT_10US);
+		break;
+	case 1600000:
+		anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_LT,
+				      GSW1XX_SGMII_TBI_ANEGCTL_LT_1_6MS);
+		break;
+	case 5000000:
+		anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_LT,
+				      GSW1XX_SGMII_TBI_ANEGCTL_LT_5MS);
+		break;
+	case 10000000:
+		anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_LT,
+				      GSW1XX_SGMII_TBI_ANEGCTL_LT_10MS);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (interface == PHY_INTERFACE_MODE_SGMII) {
+		txaneg = ADVERTISE_SGMII;
+		if (sgmii_mac_mode) {
+			txaneg |= BIT(14); /* MAC should always send BIT 14 */
+			anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_ANMODE,
+					      GSW1XX_SGMII_TBI_ANEGCTL_ANMODE_SGMII_MAC);
+		} else {
+			txaneg |= LPA_SGMII_1000FULL;
+			anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_ANMODE,
+					      GSW1XX_SGMII_TBI_ANEGCTL_ANMODE_SGMII_PHY);
+		}
+
+		if (neg_mode & PHYLINK_PCS_NEG_INBAND)
+			anegctl |= GSW1XX_SGMII_TBI_ANEGCTL_ANEGEN;
+	} else if (interface == PHY_INTERFACE_MODE_1000BASEX ||
+		   interface == PHY_INTERFACE_MODE_2500BASEX) {
+		txaneg = BIT(5) | BIT(7);
+		anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_ANMODE,
+				      GSW1XX_SGMII_TBI_ANEGCTL_ANMODE_1000BASEX);
+	} else {
+		dev_err(priv->gswip.dev, "%s: SGMII wrong interface mode %s\n",
+			__func__, phy_modes(interface));
+		return -EINVAL;
+	}
+
+	ret = regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_TXANEGH,
+			   FIELD_GET(GENMASK(15, 8), txaneg));
+	if (ret < 0)
+		return ret;
+	ret = regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_TXANEGL,
+			   FIELD_GET(GENMASK(7, 0), txaneg));
+	if (ret < 0)
+		return ret;
+	ret = regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_ANEGCTL, anegctl);
+	if (ret < 0)
+		return ret;
+
+	/* setup SerDes clock speed */
+	if (interface == PHY_INTERFACE_MODE_2500BASEX)
+		nco_ctrl = GSW1XX_SGMII_2G5 | GSW1XX_SGMII_2G5_NCO2;
+	else
+		nco_ctrl = GSW1XX_SGMII_1G | GSW1XX_SGMII_1G_NCO1;
+
+	ret = regmap_update_bits(priv->clk, GSW1XX_CLK_NCO_CTRL,
+				 GSW1XX_SGMII_HSP_MASK | GSW1XX_SGMII_SEL,
+				 nco_ctrl);
+	if (ret)
+		return ret;
+
+	ret = gsw1xx_sgmii_phy_xaui_write(priv, 0x30, 0x80);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static void gsw1xx_sgmii_pcs_link_up(struct phylink_pcs *pcs,
+				     unsigned int neg_mode,
+				     phy_interface_t interface, int speed,
+				     int duplex)
+{
+	struct gsw1xx_priv *priv = sgmii_pcs_to_gsw1xx(pcs);
+	u16 lpstat;
+
+	/* When in-band AN is enabled hardware will set lpstat */
+	if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
+		return;
+
+	/* Force speed and duplex settings */
+	if (interface == PHY_INTERFACE_MODE_SGMII) {
+		if (speed == SPEED_10)
+			lpstat = FIELD_PREP(GSW1XX_SGMII_TBI_LPSTAT_SPEED,
+					    GSW1XX_SGMII_TBI_LPSTAT_SPEED_10);
+		else if (speed == SPEED_100)
+			lpstat = FIELD_PREP(GSW1XX_SGMII_TBI_LPSTAT_SPEED,
+					    GSW1XX_SGMII_TBI_LPSTAT_SPEED_100);
+		else
+			lpstat = FIELD_PREP(GSW1XX_SGMII_TBI_LPSTAT_SPEED,
+					    GSW1XX_SGMII_TBI_LPSTAT_SPEED_1000);
+	} else {
+		lpstat = FIELD_PREP(GSW1XX_SGMII_TBI_LPSTAT_SPEED,
+				    GSW1XX_SGMII_TBI_LPSTAT_SPEED_NOSGMII);
+	}
+
+	if (duplex == DUPLEX_FULL)
+		lpstat |= GSW1XX_SGMII_TBI_LPSTAT_DUPLEX;
+
+	regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_LPSTAT, lpstat);
+}
+
+static void gsw1xx_sgmii_pcs_get_state(struct phylink_pcs *pcs,
+				       unsigned int neg_mode,
+				       struct phylink_link_state *state)
+{
+	struct gsw1xx_priv *priv = sgmii_pcs_to_gsw1xx(pcs);
+	int ret;
+	u32 val;
+
+	ret = regmap_read(priv->sgmii, GSW1XX_SGMII_TBI_TBISTAT, &val);
+	if (ret < 0)
+		return;
+
+	state->link = !!(val & GSW1XX_SGMII_TBI_TBISTAT_LINK);
+	state->an_complete = !!(val & GSW1XX_SGMII_TBI_TBISTAT_AN_COMPLETE);
+
+	ret = regmap_read(priv->sgmii, GSW1XX_SGMII_TBI_LPSTAT, &val);
+	if (ret < 0)
+		return;
+
+	state->duplex = (val & GSW1XX_SGMII_TBI_LPSTAT_DUPLEX) ?
+			 DUPLEX_FULL : DUPLEX_HALF;
+	if (val & GSW1XX_SGMII_TBI_LPSTAT_PAUSE_RX)
+		state->pause |= MLO_PAUSE_RX;
+
+	if (val & GSW1XX_SGMII_TBI_LPSTAT_PAUSE_TX)
+		state->pause |= MLO_PAUSE_TX;
+
+	switch (FIELD_GET(GSW1XX_SGMII_TBI_LPSTAT_SPEED, val)) {
+	case GSW1XX_SGMII_TBI_LPSTAT_SPEED_10:
+		state->speed = SPEED_10;
+		break;
+	case GSW1XX_SGMII_TBI_LPSTAT_SPEED_100:
+		state->speed = SPEED_100;
+		break;
+	case GSW1XX_SGMII_TBI_LPSTAT_SPEED_1000:
+		state->speed = SPEED_1000;
+		break;
+	case GSW1XX_SGMII_TBI_LPSTAT_SPEED_NOSGMII:
+		if (state->interface == PHY_INTERFACE_MODE_1000BASEX)
+			state->speed = SPEED_1000;
+		else if (state->interface == PHY_INTERFACE_MODE_2500BASEX)
+			state->speed = SPEED_2500;
+		else
+			state->speed = SPEED_UNKNOWN;
+		break;
+	}
+}
+
+static void gsw1xx_sgmii_pcs_an_restart(struct phylink_pcs *pcs)
+{
+	struct gsw1xx_priv *priv = sgmii_pcs_to_gsw1xx(pcs);
+
+	regmap_set_bits(priv->sgmii, GSW1XX_SGMII_TBI_ANEGCTL,
+			GSW1XX_SGMII_TBI_ANEGCTL_RANEG);
+}
+
+static const struct phylink_pcs_ops gsw1xx_sgmii_pcs_ops = {
+	.pcs_get_state = gsw1xx_sgmii_pcs_get_state,
+	.pcs_config = gsw1xx_sgmii_pcs_config,
+	.pcs_an_restart = gsw1xx_sgmii_pcs_an_restart,
+	.pcs_link_up = gsw1xx_sgmii_pcs_link_up,
+};
+
+static void gsw1xx_phylink_get_caps(struct dsa_switch *ds, int port,
+				    struct phylink_config *config)
+{
+	struct gswip_priv *priv = ds->priv;
+
+	config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+		MAC_10 | MAC_100 | MAC_1000;
+
+	switch (port) {
+	case 0:
+	case 1:
+	case 2:
+	case 3:
+		__set_bit(PHY_INTERFACE_MODE_INTERNAL,
+			  config->supported_interfaces);
+		break;
+	case 4: /* port 4: SGMII */
+		__set_bit(PHY_INTERFACE_MODE_SGMII,
+			  config->supported_interfaces);
+		__set_bit(PHY_INTERFACE_MODE_1000BASEX,
+			  config->supported_interfaces);
+		if (priv->hw_info->supports_2500m) {
+			__set_bit(PHY_INTERFACE_MODE_2500BASEX,
+				  config->supported_interfaces);
+			config->mac_capabilities |= MAC_2500FD;
+		}
+		break;
+	case 5: /* port 5: RGMII or RMII */
+		__set_bit(PHY_INTERFACE_MODE_RMII,
+			  config->supported_interfaces);
+		phy_interface_set_rgmii(config->supported_interfaces);
+		break;
+	}
+}
+
+static struct phylink_pcs *gsw1xx_phylink_mac_select_pcs(struct phylink_config *config,
+							 phy_interface_t interface)
+{
+	struct dsa_port *dp = dsa_phylink_to_port(config);
+	struct gswip_priv *gswip_priv = dp->ds->priv;
+	struct gsw1xx_priv *gsw1xx_priv = container_of(gswip_priv,
+						       struct gsw1xx_priv,
+						       gswip);
+
+	switch (dp->index) {
+	case GSW1XX_SGMII_PORT:
+		return &gsw1xx_priv->sgmii_pcs;
+	default:
+		return NULL;
+	}
+}
+
+static struct regmap *gsw1xx_regmap_init(struct gsw1xx_priv *priv,
+					 const char *name,
+					 unsigned int reg_base,
+					 unsigned int max_register)
+{
+	int count = priv->regmap_config_count++;
+	struct regmap_config *config;
+
+	if (WARN_ON(count >= ARRAY_SIZE(priv->regmap_config)))
+		return NULL;
+
+	config = &priv->regmap_config[count];
+	config->name = name;
+	config->reg_bits = 16;
+	config->val_bits = 16;
+	config->reg_base = reg_base;
+	config->max_register = max_register;
+	config->lock = gsw1xx_mdio_regmap_lock;
+	config->unlock = gsw1xx_mdio_regmap_unlock;
+	config->lock_arg = &priv->mdio_dev->bus->mdio_lock;
+
+	return devm_regmap_init(&priv->mdio_dev->dev, &gsw1xx_regmap_bus,
+				priv, config);
+}
+
+static int gsw1xx_probe(struct mdio_device *mdiodev)
+{
+	struct device *dev = &mdiodev->dev;
+	struct gsw1xx_priv *priv;
+	struct dsa_switch *ds;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->mdio_dev = mdiodev;
+	priv->smdio_badr = GSW1XX_SMDIO_BADR_UNKNOWN;
+
+	priv->gswip.dev = dev;
+	priv->gswip.hw_info = of_device_get_match_data(dev);
+	if (!priv->gswip.hw_info)
+		return -EINVAL;
+
+	mutex_init(&priv->gswip.pce_table_lock);
+
+	priv->gswip.gswip = gsw1xx_regmap_init(priv, "switch",
+					       GSW1XX_SWITCH_BASE, 0xfff);
+	if (IS_ERR(priv->gswip.gswip))
+		return PTR_ERR(priv->gswip.gswip);
+
+	priv->gswip.mdio = gsw1xx_regmap_init(priv, "mdio", GSW1XX_MMDIO_BASE,
+					      0xff);
+	if (IS_ERR(priv->gswip.mdio))
+		return PTR_ERR(priv->gswip.mdio);
+
+	priv->gswip.mii = gsw1xx_regmap_init(priv, "mii", GSW1XX_RGMII_BASE,
+					     0xff);
+	if (IS_ERR(priv->gswip.mii))
+		return PTR_ERR(priv->gswip.mii);
+
+	priv->sgmii = gsw1xx_regmap_init(priv, "sgmii", GSW1XX_SGMII_BASE,
+					 0xfff);
+	if (IS_ERR(priv->sgmii))
+		return PTR_ERR(priv->sgmii);
+
+	priv->gpio = gsw1xx_regmap_init(priv, "gpio", GSW1XX_GPIO_BASE,
+					0xff);
+	if (IS_ERR(priv->gpio))
+		return PTR_ERR(priv->gpio);
+
+	priv->clk = gsw1xx_regmap_init(priv, "clk", GSW1XX_CLK_BASE, 0xff);
+	if (IS_ERR(priv->clk))
+		return PTR_ERR(priv->clk);
+
+	priv->shell = gsw1xx_regmap_init(priv, "shell", GSW1XX_SHELL_BASE,
+					 0xff);
+	if (IS_ERR(priv->shell))
+		return PTR_ERR(priv->shell);
+
+	priv->sgmii_pcs.ops = &gsw1xx_sgmii_pcs_ops;
+	priv->sgmii_pcs.poll = 1;
+	__set_bit(PHY_INTERFACE_MODE_SGMII,
+		  priv->sgmii_pcs.supported_interfaces);
+	__set_bit(PHY_INTERFACE_MODE_1000BASEX,
+		  priv->sgmii_pcs.supported_interfaces);
+	if (priv->gswip.hw_info->supports_2500m)
+		__set_bit(PHY_INTERFACE_MODE_2500BASEX,
+			  priv->sgmii_pcs.supported_interfaces);
+
+	ret = regmap_read(priv->gswip.gswip, GSWIP_VERSION, &priv->gswip.version);
+	if (ret < 0)
+		return ret;
+
+	ds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL);
+	if (!ds)
+		return -ENOMEM;
+
+	priv->gswip.ds = ds;
+
+	ret = gswip_allocate_vlans(&priv->gswip);
+	if (ret)
+		return ret;
+
+	ds->dev = dev;
+	ds->num_ports = GSW1XX_PORTS;
+	ds->priv = &priv->gswip;
+	ds->ops = &gswip_switch_ops;
+	ds->phylink_mac_ops = &gswip_phylink_mac_ops;
+	ds->fdb_isolation = true;
+
+	/* configure GPIO pin-mux for MMDIO in case of external PHY connected to
+	 * SGMII or RGMII as slave interface
+	 */
+	regmap_set_bits(priv->gpio, GPIO_ALTSEL0, 3);
+	regmap_set_bits(priv->gpio, GPIO_ALTSEL1, 3);
+
+	dev_set_drvdata(dev, ds);
+
+	ret = dsa_register_switch(ds);
+	if (ret) {
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "%s: Error %d register DSA switch\n",
+				__func__, ret);
+		return ret;
+	}
+
+	ret = gswip_validate_cpu_port(ds);
+	if (ret)
+		goto disable_switch;
+
+	dev_info(dev, "probed GSWIP version %lx mod %lx\n",
+		 (priv->gswip.version & GSWIP_VERSION_REV_MASK) >> GSWIP_VERSION_REV_SHIFT,
+		 (priv->gswip.version & GSWIP_VERSION_MOD_MASK) >> GSWIP_VERSION_MOD_SHIFT);
+
+	return 0;
+
+disable_switch:
+	gswip_disable_switch(&priv->gswip);
+	dsa_unregister_switch(ds);
+
+	return ret;
+}
+
+static void gsw1xx_remove(struct mdio_device *mdiodev)
+{
+	struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
+	struct gswip_priv *priv;
+
+	if (!ds)
+		return;
+
+	priv = ds->priv;
+	if (!priv)
+		return;
+
+	gswip_disable_switch(priv);
+
+	dsa_unregister_switch(ds);
+}
+
+static void gsw1xx_shutdown(struct mdio_device *mdiodev)
+{
+	struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
+	struct gswip_priv *priv;
+
+	dev_set_drvdata(&mdiodev->dev, NULL);
+
+	if (!ds)
+		return;
+
+	priv = ds->priv;
+	if (!priv)
+		return;
+
+	gswip_disable_switch(priv);
+}
+
+static const struct gswip_hw_info gsw12x_data = {
+	.max_ports		= GSW1XX_PORTS,
+	.allowed_cpu_ports	= BIT(GSW1XX_MII_PORT) | BIT(GSW1XX_SGMII_PORT),
+	.phy_ports		= BIT(0) | BIT(1),
+	.mii_ports		= BIT(GSW1XX_MII_PORT),
+	.mii_port_reg_offset	= -GSW1XX_MII_PORT,
+	.sgmii_ports		= BIT(GSW1XX_SGMII_PORT),
+	.mac_select_pcs		= gsw1xx_phylink_mac_select_pcs,
+	.phylink_get_caps	= &gsw1xx_phylink_get_caps,
+	.supports_2500m		= true,
+	.pce_microcode		= &gsw1xx_pce_microcode,
+	.pce_microcode_size	= ARRAY_SIZE(gsw1xx_pce_microcode),
+	.tag_protocol		= DSA_TAG_PROTO_MXL_GSW1XX,
+};
+
+static const struct gswip_hw_info gsw140_data = {
+	.max_ports		= GSW1XX_PORTS,
+	.allowed_cpu_ports	= BIT(GSW1XX_MII_PORT) | BIT(GSW1XX_SGMII_PORT),
+	.phy_ports		= BIT(0) | BIT(1) | BIT(2) | BIT(3),
+	.mii_ports		= BIT(GSW1XX_MII_PORT),
+	.mii_port_reg_offset	= -GSW1XX_MII_PORT,
+	.sgmii_ports		= BIT(GSW1XX_SGMII_PORT),
+	.mac_select_pcs		= gsw1xx_phylink_mac_select_pcs,
+	.phylink_get_caps	= &gsw1xx_phylink_get_caps,
+	.supports_2500m		= true,
+	.pce_microcode		= &gsw1xx_pce_microcode,
+	.pce_microcode_size	= ARRAY_SIZE(gsw1xx_pce_microcode),
+	.tag_protocol		= DSA_TAG_PROTO_MXL_GSW1XX,
+};
+
+static const struct gswip_hw_info gsw141_data = {
+	.max_ports		= GSW1XX_PORTS,
+	.allowed_cpu_ports	= BIT(GSW1XX_MII_PORT) | BIT(GSW1XX_SGMII_PORT),
+	.phy_ports		= BIT(0) | BIT(1) | BIT(2) | BIT(3),
+	.mii_ports		= BIT(GSW1XX_MII_PORT),
+	.mii_port_reg_offset	= -GSW1XX_MII_PORT,
+	.sgmii_ports		= BIT(GSW1XX_SGMII_PORT),
+	.mac_select_pcs		= gsw1xx_phylink_mac_select_pcs,
+	.phylink_get_caps	= gsw1xx_phylink_get_caps,
+	.pce_microcode		= &gsw1xx_pce_microcode,
+	.pce_microcode_size	= ARRAY_SIZE(gsw1xx_pce_microcode),
+	.tag_protocol		= DSA_TAG_PROTO_MXL_GSW1XX,
+};
+
+/*
+ * GSW125 is the industrial temperature version of GSW120.
+ * GSW145 is the industrial temperature version of GSW140.
+ */
+static const struct of_device_id gsw1xx_of_match[] = {
+	{ .compatible = "maxlinear,gsw120", .data = &gsw12x_data },
+	{ .compatible = "maxlinear,gsw125", .data = &gsw12x_data },
+	{ .compatible = "maxlinear,gsw140", .data = &gsw140_data },
+	{ .compatible = "maxlinear,gsw141", .data = &gsw141_data },
+	{ .compatible = "maxlinear,gsw145", .data = &gsw140_data },
+	{ /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, gsw1xx_of_match);
+
+static struct mdio_driver gsw1xx_driver = {
+	.probe		= gsw1xx_probe,
+	.remove		= gsw1xx_remove,
+	.shutdown	= gsw1xx_shutdown,
+	.mdiodrv.driver	= {
+		.name = "mxl-gsw1xx",
+		.of_match_table = gsw1xx_of_match,
+	},
+};
+
+mdio_module_driver(gsw1xx_driver);
+
+MODULE_DESCRIPTION("Driver for MaxLinear GSW1xx ethernet switch");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:mxl-gsw1xx");
diff --git a/drivers/net/dsa/mxl-gsw1xx_pce.h b/drivers/net/dsa/mxl-gsw1xx_pce.h
new file mode 100644
index 000000000000..d2cbad83faec
--- /dev/null
+++ b/drivers/net/dsa/mxl-gsw1xx_pce.h
@@ -0,0 +1,160 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * PCE microcode code update for driver for MaxLinear GSW1xx switch chips
+ *
+ * Copyright (C) 2023 - 2024 MaxLinear Inc.
+ * Copyright (C) 2022 Snap One, LLC.  All rights reserved.
+ * Copyright (C) 2017 - 2019 Hauke Mehrtens <hauke@hauke-m.de>
+ * Copyright (C) 2012 Avinash Jayaraman <ajayaraman@maxlinear.com>, Bing tao Xu <bxu@maxlinear.com>, Liang Xu <lxu@maxlinear.com>, Juraj Povazanec <jpovazanec@maxlinear.com>, "Fanni (Fang-Yi) Chan" <fchan@maxlinear.com>, "Benny (Ying-Tsan) Weng" <yweng@maxlinear.com>, "Livia M. Rosu" <lrosu@maxlinear.com>, John Crispin <john@phrozen.org>
+ * Copyright (C) 2010 Lantiq Deutschland
+ */
+
+/**************************************************************************/
+/*      DEFINES:                                                          */
+/**************************************************************************/
+
+#include "lantiq_gswip.h"
+
+#define INSTR 0
+#define IPV6 1
+#define LENACCU 2
+
+/* GSWIP_2.X */
+enum {
+	GOUT_MAC0 = 0,
+	GOUT_MAC1,
+	GOUT_MAC2,
+	GOUT_MAC3,
+	GOUT_MAC4,
+	GOUT_MAC5,
+	GOUT_ETHTYP,
+	GOUT_VTAG0,
+	GOUT_VTAG1,
+	GOUT_ITAG0,
+	GOUT_ITAG1,	/* 10 */
+	GOUT_ITAG2,
+	GOUT_ITAG3,
+	GOUT_IP0,
+	GOUT_IP1,
+	GOUT_IP2,
+	GOUT_IP3,
+	GOUT_SIP0,
+	GOUT_SIP1,
+	GOUT_SIP2,
+	GOUT_SIP3,	/* 20 */
+	GOUT_SIP4,
+	GOUT_SIP5,
+	GOUT_SIP6,
+	GOUT_SIP7,
+	GOUT_DIP0,
+	GOUT_DIP1,
+	GOUT_DIP2,
+	GOUT_DIP3,
+	GOUT_DIP4,
+	GOUT_DIP5,	/* 30 */
+	GOUT_DIP6,
+	GOUT_DIP7,
+	GOUT_SESID,
+	GOUT_PROT,
+	GOUT_APP0,
+	GOUT_APP1,
+	GOUT_IGMP0,
+	GOUT_IGMP1,
+	GOUT_STAG0 = 61,
+	GOUT_STAG1 = 62,
+	GOUT_NONE = 63,
+};
+
+/* parser's microcode flag type */
+enum {
+	GFLAG_ITAG = 0,
+	GFLAG_VLAN,
+	GFLAG_SNAP,
+	GFLAG_PPPOE,
+	GFLAG_IPV6,
+	GFLAG_IPV6FL,
+	GFLAG_IPV4,
+	GFLAG_IGMP,
+	GFLAG_TU,
+	GFLAG_HOP,
+	GFLAG_NN1,	/* 10 */
+	GFLAG_NN2,
+	GFLAG_END,
+	GFLAG_NO,	/* 13 */
+	GFLAG_SVLAN,	/* 14 */
+};
+
+#define PCE_MC_M(val, msk, ns, out, len, type, flags, ipv4_len) \
+	{ (val), (msk), ((ns) << 10 | (out) << 4 | (len) >> 1),\
+	 ((len) & 1) << 15 | (type) << 13 | (flags) << 9 | (ipv4_len) << 8 }
+
+static const struct gswip_pce_microcode gsw1xx_pce_microcode[] = {
+	/*-----------------------------------------------------------------*/
+	/**   value    mask   ns  out_fields   L  type   flags   ipv4_len **/
+	/*-----------------------------------------------------------------*/
+	/* V22_2X (IPv6 issue fixed) */
+	PCE_MC_M(0x88c3, 0xFFFF, 1,  GOUT_ITAG0,  4, INSTR,   GFLAG_ITAG,  0),
+	PCE_MC_M(0x8100, 0xFFFF, 4,  GOUT_STAG0,  2, INSTR,   GFLAG_SVLAN, 0),
+	PCE_MC_M(0x88A8, 0xFFFF, 4,  GOUT_STAG0,  2, INSTR,   GFLAG_SVLAN, 0),
+	PCE_MC_M(0x9100, 0xFFFF, 4,  GOUT_STAG0,  2, INSTR,   GFLAG_SVLAN, 0),
+	PCE_MC_M(0x8100, 0xFFFF, 5,  GOUT_VTAG0,  2, INSTR,   GFLAG_VLAN,  0),
+	PCE_MC_M(0x88A8, 0xFFFF, 6,  GOUT_VTAG0,  2, INSTR,   GFLAG_VLAN,  0),
+	PCE_MC_M(0x9100, 0xFFFF, 4,  GOUT_VTAG0,  2, INSTR,   GFLAG_VLAN,  0),
+	PCE_MC_M(0x8864, 0xFFFF, 20, GOUT_ETHTYP, 1, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0800, 0xFFFF, 24, GOUT_ETHTYP, 1, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x86DD, 0xFFFF, 25, GOUT_ETHTYP, 1, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x8863, 0xFFFF, 19, GOUT_ETHTYP, 1, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0xF800, 13, GOUT_NONE,   0, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0x0000, 44, GOUT_ETHTYP, 1, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0600, 0x0600, 44, GOUT_ETHTYP, 1, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0x0000, 15, GOUT_NONE,   1, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0xAAAA, 0xFFFF, 17, GOUT_NONE,   1, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0300, 0xFF00, 45, GOUT_NONE,   0, INSTR,   GFLAG_SNAP,  0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_DIP7,   3, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0x0000, 21, GOUT_DIP7,   3, INSTR,   GFLAG_PPPOE, 0),
+	PCE_MC_M(0x0021, 0xFFFF, 24, GOUT_NONE,   1, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0057, 0xFFFF, 25, GOUT_NONE,   1, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0x0000, 44, GOUT_NONE,   0, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x4000, 0xF000, 27, GOUT_IP0,    4, INSTR,   GFLAG_IPV4,  1),
+	PCE_MC_M(0x6000, 0xF000, 30, GOUT_IP0,    3, INSTR,   GFLAG_IPV6,  0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0x0000, 28, GOUT_IP3,    2, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0x0000, 29, GOUT_SIP0,   4, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0x0000, 44, GOUT_NONE,   0, LENACCU, GFLAG_NO,    0),
+	PCE_MC_M(0x1100, 0xFF00, 43, GOUT_PROT,   1, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0600, 0xFF00, 43, GOUT_PROT,   1, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0xFF00, 36, GOUT_IP3,   17, INSTR,   GFLAG_HOP,   0),
+	PCE_MC_M(0x2B00, 0xFF00, 36, GOUT_IP3,   17, INSTR,   GFLAG_NN1,   0),
+	PCE_MC_M(0x3C00, 0xFF00, 36, GOUT_IP3,   17, INSTR,   GFLAG_NN2,   0),
+	PCE_MC_M(0x0000, 0x0000, 43, GOUT_PROT,   1, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0x00F0, 38, GOUT_NONE,   0, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0x0000, 44, GOUT_NONE,   0, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0xFF00, 36, GOUT_NONE,   0, IPV6,    GFLAG_HOP,   0),
+	PCE_MC_M(0x2B00, 0xFF00, 36, GOUT_NONE,   0, IPV6,    GFLAG_NN1,   0),
+	PCE_MC_M(0x3C00, 0xFF00, 36, GOUT_NONE,   0, IPV6,    GFLAG_NN2,   0),
+	PCE_MC_M(0x0000, 0x00FC, 44, GOUT_PROT,   0, IPV6,    GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0x0000, 44, GOUT_NONE,   0, IPV6,    GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0x0000, 44, GOUT_SIP0,  16, INSTR,   GFLAG_NO,    0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_APP0,   4, INSTR,   GFLAG_IGMP,  0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+	PCE_MC_M(0x0000, 0x0000, 45, GOUT_NONE,   0, INSTR,   GFLAG_END,   0),
+};
-- 
2.50.1

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH RFC net-next 22/23] net: dsa: add driver for MaxLinear GSW1xx switch family
  2025-08-16 19:57 [PATCH RFC net-next 22/23] net: dsa: add driver for MaxLinear GSW1xx switch family Daniel Golle
@ 2025-08-21 18:53 ` Sverdlin, Alexander
  2025-08-21 19:18   ` Daniel Golle
  0 siblings, 1 reply; 5+ messages in thread
From: Sverdlin, Alexander @ 2025-08-21 18:53 UTC (permalink / raw)
  To: hauke@hauke-m.de, olteanv@gmail.com, davem@davemloft.net,
	andrew@lunn.ch, linux@armlinux.org.uk,
	linux-kernel@vger.kernel.org, arkadis@mellanox.com,
	daniel@makrotopia.org, kuba@kernel.org, pabeni@redhat.com,
	edumazet@google.com, f.fainelli@gmail.com, horms@kernel.org,
	netdev@vger.kernel.org
  Cc: john@phrozen.org, Stockmann, Lukas, yweng@maxlinear.com,
	fchan@maxlinear.com, lxu@maxlinear.com, jpovazanec@maxlinear.com,
	Schirm, Andreas, Christen, Peter, ajayaraman@maxlinear.com,
	bxu@maxlinear.com, lrosu@maxlinear.com

Hi Daniel,

On Sat, 2025-08-16 at 20:57 +0100, Daniel Golle wrote:
> Add driver for the MaxLinear GSW1xx family of Ethernet switch ICs which
> are based on the same IP as the Lantiq/Intel GSWIP found in the Lantiq VR9
> and Intel GRX MIPS router SoCs. The main difference is that instead of
> using memory-mapped I/O to communicate with the host CPU these ICs are
> connected via MDIO (or SPI, which isn't supported by this driver).
> Implement the regmap API to access the switch registers over MDIO to allow
> reusing lantiq_gswip_common for all core functionality.
> 
> The GSW1xx also comes with a SerDes port capable of 1000Base-X, SGMII and
> 2500Base-X, which can either be used to connect an external PHY or SFP
> cage, or as the CPU port. Support for the SerDes interface is implemented
> in this driver using the phylink_pcs interface.

...

> --- /dev/null
> +++ b/drivers/net/dsa/mxl-gsw1xx.c

...

> static int gsw1xx_sgmii_pcs_config(struct phylink_pcs *pcs,
> +				   unsigned int neg_mode,
> +				   phy_interface_t interface,
> +				   const unsigned long *advertising,
> +				   bool permit_pause_to_mac)
> +{
> +	struct gsw1xx_priv *priv = sgmii_pcs_to_gsw1xx(pcs);
> +	bool sgmii_mac_mode = dsa_is_user_port(priv->gswip.ds, GSW1XX_SGMII_PORT);
> +	u16 txaneg, anegctl, val, nco_ctrl;
> +	int ret;
> +
> +	/* Assert and deassert SGMII shell reset */
> +	ret = regmap_set_bits(priv->shell, GSW1XX_SHELL_RST_REQ,
> +			      GSW1XX_RST_REQ_SGMII_SHELL);

Can this be moved into gsw1xx_probe() maybe?

The thing is, if the switch is bootstrapped in
"Self-start Mode: Managed Switch Sub-Mode", SGMII will be already
brought out of reset (by bootloader?) (GSWIP_CFG register), refer
to "Table 12 Registers Configuration for Self-start Mode: Managed Switch Sub-Mode"
in datasheet. And nobody would disable SGMII if it's unused otherwise.

> +	if (ret < 0)
> +		return ret;
> +
> +	ret = regmap_clear_bits(priv->shell, GSW1XX_SHELL_RST_REQ,
> +				GSW1XX_RST_REQ_SGMII_SHELL);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Hardware Bringup FSM Enable  */
> +	ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_HWBU_CTRL,
> +			   GSW1XX_SGMII_PHY_HWBU_CTRL_EN_HWBU_FSM |
> +			   GSW1XX_SGMII_PHY_HWBU_CTRL_HW_FSM_EN);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Configure SGMII PHY Receiver */
> +	val = FIELD_PREP(GSW1XX_SGMII_PHY_RX0_CFG2_EQ,
> +			 GSW1XX_SGMII_PHY_RX0_CFG2_EQ_DEF) |
> +	      GSW1XX_SGMII_PHY_RX0_CFG2_LOS_EN |
> +	      GSW1XX_SGMII_PHY_RX0_CFG2_TERM_EN |
> +	      FIELD_PREP(GSW1XX_SGMII_PHY_RX0_CFG2_FILT_CNT,
> +			 GSW1XX_SGMII_PHY_RX0_CFG2_FILT_CNT_DEF);
> +
> +	// if (!priv->dts.sgmii_rx_invert)
        ^^
There is still a room for some cleanup ;-)

> +		val |= GSW1XX_SGMII_PHY_RX0_CFG2_INVERT;
> +
> +	ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_RX0_CFG2, val);
> +	if (ret < 0)
> +		return ret;
> +

...

> +static int gsw1xx_probe(struct mdio_device *mdiodev)
> +{
> +	struct device *dev = &mdiodev->dev;
> +	struct gsw1xx_priv *priv;
> +	struct dsa_switch *ds;
> +	int ret;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->mdio_dev = mdiodev;
> +	priv->smdio_badr = GSW1XX_SMDIO_BADR_UNKNOWN;
> +
> +	priv->gswip.dev = dev;
> +	priv->gswip.hw_info = of_device_get_match_data(dev);
> +	if (!priv->gswip.hw_info)
> +		return -EINVAL;
> +
> +	mutex_init(&priv->gswip.pce_table_lock);
> +
> +	priv->gswip.gswip = gsw1xx_regmap_init(priv, "switch",
> +					       GSW1XX_SWITCH_BASE, 0xfff);
> +	if (IS_ERR(priv->gswip.gswip))
> +		return PTR_ERR(priv->gswip.gswip);
> +
> +	priv->gswip.mdio = gsw1xx_regmap_init(priv, "mdio", GSW1XX_MMDIO_BASE,
> +					      0xff);
> +	if (IS_ERR(priv->gswip.mdio))
> +		return PTR_ERR(priv->gswip.mdio);
> +
> +	priv->gswip.mii = gsw1xx_regmap_init(priv, "mii", GSW1XX_RGMII_BASE,
> +					     0xff);
> +	if (IS_ERR(priv->gswip.mii))
> +		return PTR_ERR(priv->gswip.mii);
> +
> +	priv->sgmii = gsw1xx_regmap_init(priv, "sgmii", GSW1XX_SGMII_BASE,
> +					 0xfff);
> +	if (IS_ERR(priv->sgmii))
> +		return PTR_ERR(priv->sgmii);
> +
> +	priv->gpio = gsw1xx_regmap_init(priv, "gpio", GSW1XX_GPIO_BASE,
> +					0xff);
> +	if (IS_ERR(priv->gpio))
> +		return PTR_ERR(priv->gpio);
> +
> +	priv->clk = gsw1xx_regmap_init(priv, "clk", GSW1XX_CLK_BASE, 0xff);
> +	if (IS_ERR(priv->clk))
> +		return PTR_ERR(priv->clk);
> +
> +	priv->shell = gsw1xx_regmap_init(priv, "shell", GSW1XX_SHELL_BASE,
> +					 0xff);
> +	if (IS_ERR(priv->shell))
> +		return PTR_ERR(priv->shell);
> +
> +	priv->sgmii_pcs.ops = &gsw1xx_sgmii_pcs_ops;
> +	priv->sgmii_pcs.poll = 1;
> +	__set_bit(PHY_INTERFACE_MODE_SGMII,
> +		  priv->sgmii_pcs.supported_interfaces);
> +	__set_bit(PHY_INTERFACE_MODE_1000BASEX,
> +		  priv->sgmii_pcs.supported_interfaces);
> +	if (priv->gswip.hw_info->supports_2500m)
> +		__set_bit(PHY_INTERFACE_MODE_2500BASEX,
> +			  priv->sgmii_pcs.supported_interfaces);
> +
> +	ret = regmap_read(priv->gswip.gswip, GSWIP_VERSION, &priv->gswip.version);
> +	if (ret < 0)
> +		return ret;
> +
> +	ds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL);
> +	if (!ds)
> +		return -ENOMEM;
> +
> +	priv->gswip.ds = ds;
> +
> +	ret = gswip_allocate_vlans(&priv->gswip);
> +	if (ret)
> +		return ret;
> +
> +	ds->dev = dev;
> +	ds->num_ports = GSW1XX_PORTS;
> +	ds->priv = &priv->gswip;
> +	ds->ops = &gswip_switch_ops;
> +	ds->phylink_mac_ops = &gswip_phylink_mac_ops;
> +	ds->fdb_isolation = true;
> +
> +	/* configure GPIO pin-mux for MMDIO in case of external PHY connected to
> +	 * SGMII or RGMII as slave interface
> +	 */
> +	regmap_set_bits(priv->gpio, GPIO_ALTSEL0, 3);
> +	regmap_set_bits(priv->gpio, GPIO_ALTSEL1, 3);
> +
> +	dev_set_drvdata(dev, ds);
> +
> +	ret = dsa_register_switch(ds);
> +	if (ret) {
> +		if (ret != -EPROBE_DEFER)
> +			dev_err(dev, "%s: Error %d register DSA switch\n",
> +				__func__, ret);
> +		return ret;
> +	}
> +
> +	ret = gswip_validate_cpu_port(ds);
> +	if (ret)
> +		goto disable_switch;
> +
> +	dev_info(dev, "probed GSWIP version %lx mod %lx\n",
> +		 (priv->gswip.version & GSWIP_VERSION_REV_MASK) >> GSWIP_VERSION_REV_SHIFT,
> +		 (priv->gswip.version & GSWIP_VERSION_MOD_MASK) >> GSWIP_VERSION_MOD_SHIFT);
> +
> +	return 0;
> +
> +disable_switch:
> +	gswip_disable_switch(&priv->gswip);
> +	dsa_unregister_switch(ds);
> +
> +	return ret;
> +}

...

> +static struct mdio_driver gsw1xx_driver = {
> +	.probe		= gsw1xx_probe,
> +	.remove		= gsw1xx_remove,
> +	.shutdown	= gsw1xx_shutdown,
> +	.mdiodrv.driver	= {
> +		.name = "mxl-gsw1xx",
> +		.of_match_table = gsw1xx_of_match,
> +	},
> +};

-- 
Alexander Sverdlin
Siemens AG
www.siemens.com

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH RFC net-next 22/23] net: dsa: add driver for MaxLinear GSW1xx switch family
  2025-08-21 18:53 ` Sverdlin, Alexander
@ 2025-08-21 19:18   ` Daniel Golle
  2025-08-21 20:13     ` Sverdlin, Alexander
  0 siblings, 1 reply; 5+ messages in thread
From: Daniel Golle @ 2025-08-21 19:18 UTC (permalink / raw)
  To: Sverdlin, Alexander
  Cc: hauke@hauke-m.de, olteanv@gmail.com, davem@davemloft.net,
	andrew@lunn.ch, linux@armlinux.org.uk,
	linux-kernel@vger.kernel.org, arkadis@mellanox.com,
	kuba@kernel.org, pabeni@redhat.com, edumazet@google.com,
	f.fainelli@gmail.com, horms@kernel.org, netdev@vger.kernel.org,
	john@phrozen.org, Stockmann, Lukas, yweng@maxlinear.com,
	fchan@maxlinear.com, lxu@maxlinear.com, jpovazanec@maxlinear.com,
	Schirm, Andreas, Christen, Peter, ajayaraman@maxlinear.com,
	bxu@maxlinear.com, lrosu@maxlinear.com

On Thu, Aug 21, 2025 at 06:53:22PM +0000, Sverdlin, Alexander wrote:
> Hi Daniel,
> 
> On Sat, 2025-08-16 at 20:57 +0100, Daniel Golle wrote:
> > Add driver for the MaxLinear GSW1xx family of Ethernet switch ICs which
> > are based on the same IP as the Lantiq/Intel GSWIP found in the Lantiq VR9
> > and Intel GRX MIPS router SoCs. The main difference is that instead of
> > using memory-mapped I/O to communicate with the host CPU these ICs are
> > connected via MDIO (or SPI, which isn't supported by this driver).
> > Implement the regmap API to access the switch registers over MDIO to allow
> > reusing lantiq_gswip_common for all core functionality.
> > 
> > The GSW1xx also comes with a SerDes port capable of 1000Base-X, SGMII and
> > 2500Base-X, which can either be used to connect an external PHY or SFP
> > cage, or as the CPU port. Support for the SerDes interface is implemented
> > in this driver using the phylink_pcs interface.
> 
> ...
> 
> > --- /dev/null
> > +++ b/drivers/net/dsa/mxl-gsw1xx.c
> 
> ...
> 
> > static int gsw1xx_sgmii_pcs_config(struct phylink_pcs *pcs,
> > +				   unsigned int neg_mode,
> > +				   phy_interface_t interface,
> > +				   const unsigned long *advertising,
> > +				   bool permit_pause_to_mac)
> > +{
> > +	struct gsw1xx_priv *priv = sgmii_pcs_to_gsw1xx(pcs);
> > +	bool sgmii_mac_mode = dsa_is_user_port(priv->gswip.ds, GSW1XX_SGMII_PORT);
> > +	u16 txaneg, anegctl, val, nco_ctrl;
> > +	int ret;
> > +
> > +	/* Assert and deassert SGMII shell reset */
> > +	ret = regmap_set_bits(priv->shell, GSW1XX_SHELL_RST_REQ,
> > +			      GSW1XX_RST_REQ_SGMII_SHELL);
> 
> Can this be moved into gsw1xx_probe() maybe?
> 
> The thing is, if the switch is bootstrapped in
> "Self-start Mode: Managed Switch Sub-Mode", SGMII will be already
> brought out of reset (by bootloader?) (GSWIP_CFG register), refer
> to "Table 12 Registers Configuration for Self-start Mode: Managed Switch Sub-Mode"
> in datasheet. And nobody would disable SGMII if it's unused otherwise.

What you say is true if the SGMII interface is used as the CPU port or
to connect a (1000M/100M/10M) PHY. However, it can also be used to connect
SFP modules, which can be hot-plugged. Or a 2500M/1000M/100M/10M PHY which
requires switching to 2500Base-X mode in case of a 2500M link on the UTP
interface comes up, but uses SGMII for all lower speeds.

We can probably do this similar to drivers/net/pcs/pcs-mtk-lynxi.c and
only do a full reconf including reset if there are major changes which
actually require that, but as the impact is minimal and the vendor
implementation also carries out a reset as the first thing when
configuring the SGMII interface, I'd just keep it like that for now.
Optimization can come later if actually required.

Another good thing would probably be to implement pcs_enable() and
pcs_disable() ops which put the whole SGMII port into a low-power state
(stopping clocks, maybe asserting reset as you suggest...) and bring it
back up. However, also this can be done after initial support has been
merged and verified to work in all cases (I only got the MxL8611x
evaluation board on which GSW145 is connected to MxL86111, so I can't
really do anything too fancy with the SGMII interface other than making
sure it works with that PHY with both, enabled and disabled SGMII in-band
negotiation).

> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	ret = regmap_clear_bits(priv->shell, GSW1XX_SHELL_RST_REQ,
> > +				GSW1XX_RST_REQ_SGMII_SHELL);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	/* Hardware Bringup FSM Enable  */
> > +	ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_HWBU_CTRL,
> > +			   GSW1XX_SGMII_PHY_HWBU_CTRL_EN_HWBU_FSM |
> > +			   GSW1XX_SGMII_PHY_HWBU_CTRL_HW_FSM_EN);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	/* Configure SGMII PHY Receiver */
> > +	val = FIELD_PREP(GSW1XX_SGMII_PHY_RX0_CFG2_EQ,
> > +			 GSW1XX_SGMII_PHY_RX0_CFG2_EQ_DEF) |
> > +	      GSW1XX_SGMII_PHY_RX0_CFG2_LOS_EN |
> > +	      GSW1XX_SGMII_PHY_RX0_CFG2_TERM_EN |
> > +	      FIELD_PREP(GSW1XX_SGMII_PHY_RX0_CFG2_FILT_CNT,
> > +			 GSW1XX_SGMII_PHY_RX0_CFG2_FILT_CNT_DEF);
> > +
> > +	// if (!priv->dts.sgmii_rx_invert)
>         ^^
> There is still a room for some cleanup ;-)

Ooops... I forgot about that, it should become a vendor DT property.


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH RFC net-next 22/23] net: dsa: add driver for MaxLinear GSW1xx switch family
  2025-08-21 19:18   ` Daniel Golle
@ 2025-08-21 20:13     ` Sverdlin, Alexander
  2025-08-21 22:37       ` Daniel Golle
  0 siblings, 1 reply; 5+ messages in thread
From: Sverdlin, Alexander @ 2025-08-21 20:13 UTC (permalink / raw)
  To: daniel@makrotopia.org
  Cc: olteanv@gmail.com, andrew@lunn.ch, Christen, Peter,
	lxu@maxlinear.com, john@phrozen.org, davem@davemloft.net,
	yweng@maxlinear.com, linux-kernel@vger.kernel.org,
	pabeni@redhat.com, edumazet@google.com, bxu@maxlinear.com,
	linux@armlinux.org.uk, fchan@maxlinear.com,
	ajayaraman@maxlinear.com, hauke@hauke-m.de, arkadis@mellanox.com,
	kuba@kernel.org, horms@kernel.org, jpovazanec@maxlinear.com,
	netdev@vger.kernel.org, Stockmann, Lukas, lrosu@maxlinear.com,
	f.fainelli@gmail.com, Schirm, Andreas

Hello Daniel,

On Thu, 2025-08-21 at 20:18 +0100, Daniel Golle wrote:
> > > Add driver for the MaxLinear GSW1xx family of Ethernet switch ICs which
> > > are based on the same IP as the Lantiq/Intel GSWIP found in the Lantiq VR9
> > > and Intel GRX MIPS router SoCs. The main difference is that instead of
> > > using memory-mapped I/O to communicate with the host CPU these ICs are
> > > connected via MDIO (or SPI, which isn't supported by this driver).
> > > Implement the regmap API to access the switch registers over MDIO to allow
> > > reusing lantiq_gswip_common for all core functionality.
> > > 
> > > The GSW1xx also comes with a SerDes port capable of 1000Base-X, SGMII and
> > > 2500Base-X, which can either be used to connect an external PHY or SFP
> > > cage, or as the CPU port. Support for the SerDes interface is implemented
> > > in this driver using the phylink_pcs interface.
> > 
> > ...
> > 
> > > --- /dev/null
> > > +++ b/drivers/net/dsa/mxl-gsw1xx.c
> > 
> > ...
> > 
> > > static int gsw1xx_sgmii_pcs_config(struct phylink_pcs *pcs,
> > > +				   unsigned int neg_mode,
> > > +				   phy_interface_t interface,
> > > +				   const unsigned long *advertising,
> > > +				   bool permit_pause_to_mac)
> > > +{
> > > +	struct gsw1xx_priv *priv = sgmii_pcs_to_gsw1xx(pcs);
> > > +	bool sgmii_mac_mode = dsa_is_user_port(priv->gswip.ds, GSW1XX_SGMII_PORT);
> > > +	u16 txaneg, anegctl, val, nco_ctrl;
> > > +	int ret;
> > > +
> > > +	/* Assert and deassert SGMII shell reset */
> > > +	ret = regmap_set_bits(priv->shell, GSW1XX_SHELL_RST_REQ,
> > > +			      GSW1XX_RST_REQ_SGMII_SHELL);
> > 
> > Can this be moved into gsw1xx_probe() maybe?
> > 
> > The thing is, if the switch is bootstrapped in
> > "Self-start Mode: Managed Switch Sub-Mode", SGMII will be already
> > brought out of reset (by bootloader?) (GSWIP_CFG register), refer
> > to "Table 12 Registers Configuration for Self-start Mode: Managed Switch Sub-Mode"
> > in datasheet. And nobody would disable SGMII if it's unused otherwise.
> 
> What you say is true if the SGMII interface is used as the CPU port or
> to connect a (1000M/100M/10M) PHY. However, it can also be used to connect
> SFP modules, which can be hot-plugged. Or a 2500M/1000M/100M/10M PHY which
> requires switching to 2500Base-X mode in case of a 2500M link on the UTP
> interface comes up, but uses SGMII for all lower speeds.

I'm actually concerned about use-cases where SGMII is unused.
In "Self-start Mode" SGMII block is being brought up and driver will never disable it.
I'm not proposing to move the de-assertion of the reset, but either
the assertion can be done unconditionally somewhere around probe
or struct dsa_switch_ops::setup callback or the assertion can remain
here and be duplicated somewhere around init.

> We can probably do this similar to drivers/net/pcs/pcs-mtk-lynxi.c and
> only do a full reconf including reset if there are major changes which
> actually require that, but as the impact is minimal and the vendor
> implementation also carries out a reset as the first thing when
> configuring the SGMII interface, I'd just keep it like that for now.
> Optimization can come later if actually required.

Sure, it goes a bit beyond basic support as it's a power consumption
optimization, but I thought I'll bring this up now as the re-spin will happen
anyway and if you agree on moving the reset assertion, then later patching
will not be required.

-- 
Alexander Sverdlin
Siemens AG
www.siemens.com

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH RFC net-next 22/23] net: dsa: add driver for MaxLinear GSW1xx switch family
  2025-08-21 20:13     ` Sverdlin, Alexander
@ 2025-08-21 22:37       ` Daniel Golle
  0 siblings, 0 replies; 5+ messages in thread
From: Daniel Golle @ 2025-08-21 22:37 UTC (permalink / raw)
  To: Sverdlin, Alexander
  Cc: olteanv@gmail.com, andrew@lunn.ch, Christen, Peter,
	lxu@maxlinear.com, john@phrozen.org, davem@davemloft.net,
	yweng@maxlinear.com, linux-kernel@vger.kernel.org,
	pabeni@redhat.com, edumazet@google.com, bxu@maxlinear.com,
	linux@armlinux.org.uk, fchan@maxlinear.com,
	ajayaraman@maxlinear.com, hauke@hauke-m.de, arkadis@mellanox.com,
	kuba@kernel.org, horms@kernel.org, jpovazanec@maxlinear.com,
	netdev@vger.kernel.org, Stockmann, Lukas, lrosu@maxlinear.com,
	f.fainelli@gmail.com, Schirm, Andreas

On Thu, Aug 21, 2025 at 08:13:24PM +0000, Sverdlin, Alexander wrote:
> Hello Daniel,
> 
> On Thu, 2025-08-21 at 20:18 +0100, Daniel Golle wrote:
> > > > Add driver for the MaxLinear GSW1xx family of Ethernet switch ICs which
> > > > are based on the same IP as the Lantiq/Intel GSWIP found in the Lantiq VR9
> > > > and Intel GRX MIPS router SoCs. The main difference is that instead of
> > > > using memory-mapped I/O to communicate with the host CPU these ICs are
> > > > connected via MDIO (or SPI, which isn't supported by this driver).
> > > > Implement the regmap API to access the switch registers over MDIO to allow
> > > > reusing lantiq_gswip_common for all core functionality.
> > > > 
> > > > The GSW1xx also comes with a SerDes port capable of 1000Base-X, SGMII and
> > > > 2500Base-X, which can either be used to connect an external PHY or SFP
> > > > cage, or as the CPU port. Support for the SerDes interface is implemented
> > > > in this driver using the phylink_pcs interface.
> > > 
> > > ...
> > > 
> > > > --- /dev/null
> > > > +++ b/drivers/net/dsa/mxl-gsw1xx.c
> > > 
> > > ...
> > > 
> > > > static int gsw1xx_sgmii_pcs_config(struct phylink_pcs *pcs,
> > > > +				   unsigned int neg_mode,
> > > > +				   phy_interface_t interface,
> > > > +				   const unsigned long *advertising,
> > > > +				   bool permit_pause_to_mac)
> > > > +{
> > > > +	struct gsw1xx_priv *priv = sgmii_pcs_to_gsw1xx(pcs);
> > > > +	bool sgmii_mac_mode = dsa_is_user_port(priv->gswip.ds, GSW1XX_SGMII_PORT);
> > > > +	u16 txaneg, anegctl, val, nco_ctrl;
> > > > +	int ret;
> > > > +
> > > > +	/* Assert and deassert SGMII shell reset */
> > > > +	ret = regmap_set_bits(priv->shell, GSW1XX_SHELL_RST_REQ,
> > > > +			      GSW1XX_RST_REQ_SGMII_SHELL);
> > > 
> > > Can this be moved into gsw1xx_probe() maybe?
> > > 
> > > The thing is, if the switch is bootstrapped in
> > > "Self-start Mode: Managed Switch Sub-Mode", SGMII will be already
> > > brought out of reset (by bootloader?) (GSWIP_CFG register), refer
> > > to "Table 12 Registers Configuration for Self-start Mode: Managed Switch Sub-Mode"
> > > in datasheet. And nobody would disable SGMII if it's unused otherwise.
> > 
> > What you say is true if the SGMII interface is used as the CPU port or
> > to connect a (1000M/100M/10M) PHY. However, it can also be used to connect
> > SFP modules, which can be hot-plugged. Or a 2500M/1000M/100M/10M PHY which
> > requires switching to 2500Base-X mode in case of a 2500M link on the UTP
> > interface comes up, but uses SGMII for all lower speeds.
> 
> I'm actually concerned about use-cases where SGMII is unused.
> In "Self-start Mode" SGMII block is being brought up and driver will never disable it.
> I'm not proposing to move the de-assertion of the reset, but either
> the assertion can be done unconditionally somewhere around probe
> or struct dsa_switch_ops::setup callback or the assertion can remain
> here and be duplicated somewhere around init.

Lets assert the SGMII in the probe() function and let .pcs_enable() and
.pcs_disable() handle deassertion and assertion at runtime. That's easy
and obvious, and makes sure the SGMII reset is always asserted if the
SGMII unit isn't used. We can later optmize more and also stop clocks
or do whatever MaxLinear folks are telling us would be good to further
reduce power consumption and potentially also EM noise.

> 
> > We can probably do this similar to drivers/net/pcs/pcs-mtk-lynxi.c and
> > only do a full reconf including reset if there are major changes which
> > actually require that, but as the impact is minimal and the vendor
> > implementation also carries out a reset as the first thing when
> > configuring the SGMII interface, I'd just keep it like that for now.
> > Optimization can come later if actually required.
> 
> Sure, it goes a bit beyond basic support as it's a power consumption
> optimization, but I thought I'll bring this up now as the re-spin will happen
> anyway and if you agree on moving the reset assertion, then later patching
> will not be required.

Convinced me ;)


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2025-08-21 22:38 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-16 19:57 [PATCH RFC net-next 22/23] net: dsa: add driver for MaxLinear GSW1xx switch family Daniel Golle
2025-08-21 18:53 ` Sverdlin, Alexander
2025-08-21 19:18   ` Daniel Golle
2025-08-21 20:13     ` Sverdlin, Alexander
2025-08-21 22:37       ` Daniel Golle

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).