* [PATCH v2] net: dsa: mxl-gsw1xx: deassert internal PHY resets before MDIO registration
@ 2026-09-08 10:30 A. Sverdlin
2026-09-08 10:56 ` Daniel Golle
0 siblings, 1 reply; 3+ messages in thread
From: A. Sverdlin @ 2026-09-08 10:30 UTC (permalink / raw)
To: netdev
Cc: Alexander Sverdlin, Daniel Golle, Hauke Mehrtens, Andrew Lunn,
Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Russell King, linux-kernel
From: Alexander Sverdlin <alexander.sverdlin@siemens.com>
When the switch is bootstrapped with PS_NOWAIT = 0, the internal PHYs
default to a held-in-reset state. The driver previously only worked with
PS_NOWAIT = 1 where PHYs are released from reset automatically.
Deassert the PHY reset lines via the RST_REQ register before the MDIO bus
is registered, so that PHY IDs can be read during bus scan. Only release
PHYs for ports that have a phy-handle in the device tree, so unused ports
do not consume power. A single 300ms delay after deasserting all needed
resets ensures the PHYs are ready for MDIO communication (refer to Intel
XWAY, Maxlinear GPY111 datasheets or the Lantiq counterpart of the driver).
Signed-off-by: Alexander Sverdlin <alexander.sverdlin@siemens.com>
---
Changelog:
v2:
- Reverse Christmas tree.
v1:
- https://lore.kernel.org/all/20260901165049.672446-1-alexander.sverdlin@siemens.com/
drivers/net/dsa/lantiq/lantiq_gswip.h | 2 +
drivers/net/dsa/lantiq/lantiq_gswip_common.c | 10 +++++
drivers/net/dsa/lantiq/mxl-gsw1xx.c | 43 ++++++++++++++++++++
drivers/net/dsa/lantiq/mxl-gsw1xx.h | 1 +
4 files changed, 56 insertions(+)
diff --git a/drivers/net/dsa/lantiq/lantiq_gswip.h b/drivers/net/dsa/lantiq/lantiq_gswip.h
index bc3686faad0d6..7af828577c27d 100644
--- a/drivers/net/dsa/lantiq/lantiq_gswip.h
+++ b/drivers/net/dsa/lantiq/lantiq_gswip.h
@@ -261,6 +261,8 @@ struct gswip_hw_info {
const struct gswip_pce_microcode (*pce_microcode)[];
size_t pce_microcode_size;
enum dsa_tag_protocol tag_protocol;
+
+ int (*setup)(struct dsa_switch *ds);
void (*phylink_get_caps)(struct dsa_switch *ds, int port,
struct phylink_config *config);
struct phylink_pcs *(*mac_select_pcs)(struct phylink_config *config,
diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c
index 0e8eedf64d3a3..61624f251aeb3 100644
--- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c
+++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c
@@ -689,6 +689,16 @@ static int gswip_setup(struct dsa_switch *ds)
*/
regmap_write(priv->mdio, GSWIP_MDIO_MDC_CFG0, 0x0);
+ /* GSW1xx will wake up the PHYs here, so it makes sense that it happens
+ * after the auto-polling deactivation above, but before the MDIO bus
+ * registration below
+ */
+ if (priv->hw_info->setup) {
+ err = priv->hw_info->setup(ds);
+ if (err)
+ return err;
+ }
+
/* Configure the MDIO Clock 2.5 MHz */
regmap_write_bits(priv->mdio, GSWIP_MDIO_MDC_CFG1, 0xff, 0x09);
diff --git a/drivers/net/dsa/lantiq/mxl-gsw1xx.c b/drivers/net/dsa/lantiq/mxl-gsw1xx.c
index 347e16e9fdc1f..f4b3322181417 100644
--- a/drivers/net/dsa/lantiq/mxl-gsw1xx.c
+++ b/drivers/net/dsa/lantiq/mxl-gsw1xx.c
@@ -13,6 +13,7 @@
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_mdio.h>
#include <linux/phy/phy-common-props.h>
@@ -588,6 +589,44 @@ static void gsw150_phylink_get_caps(struct dsa_switch *ds, int port,
gsw1xx_phylink_get_lpi_caps(config);
}
+static int gsw1xx_setup(struct dsa_switch *ds)
+{
+ struct gsw1xx_priv *priv = container_of(ds->priv, struct gsw1xx_priv, gswip);
+ struct gswip_priv *gswip_priv = ds->priv;
+ struct device_node *phy_np;
+ u32 phy_reset_mask = 0;
+ struct dsa_port *dp;
+ int ret;
+
+ dsa_switch_for_each_user_port(dp, ds) {
+ struct phylink_config cfg = {};
+
+ /* Is there an internal PHY on this port? */
+ gswip_priv->hw_info->phylink_get_caps(ds, dp->index, &cfg);
+ if (!test_bit(PHY_INTERFACE_MODE_INTERNAL, cfg.supported_interfaces))
+ continue;
+
+ /* Will the PHY be really used? */
+ phy_np = of_parse_phandle(dp->dn, "phy-handle", 0);
+ if (!phy_np)
+ continue;
+
+ of_node_put(phy_np);
+ phy_reset_mask |= GSW1XX_RST_REQ_PHY(dp->index);
+ }
+
+ if (!phy_reset_mask)
+ return 0;
+
+ /* Deassert resets only for PHYs referenced by active ports */
+ ret = regmap_clear_bits(priv->shell, GSW1XX_SHELL_RST_REQ, phy_reset_mask);
+ if (ret)
+ return ret;
+ msleep(300);
+
+ return 0;
+}
+
static struct phylink_pcs *gsw1xx_phylink_mac_select_pcs(struct phylink_config *config,
phy_interface_t interface)
{
@@ -829,6 +868,7 @@ static const struct gswip_hw_info gsw12x_data = {
[GSW1XX_MII_PORT] = GSWIP_MII_PCDU0,
[GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1,
},
+ .setup = gsw1xx_setup,
.mac_select_pcs = gsw1xx_phylink_mac_select_pcs,
.phylink_get_caps = &gsw1xx_phylink_get_caps,
.supports_2500m = true,
@@ -851,6 +891,7 @@ static const struct gswip_hw_info gsw140_data = {
[GSW1XX_MII_PORT] = GSWIP_MII_PCDU0,
[GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1,
},
+ .setup = gsw1xx_setup,
.mac_select_pcs = gsw1xx_phylink_mac_select_pcs,
.phylink_get_caps = &gsw1xx_phylink_get_caps,
.supports_2500m = true,
@@ -873,6 +914,7 @@ static const struct gswip_hw_info gsw141_data = {
[GSW1XX_MII_PORT] = GSWIP_MII_PCDU0,
[GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1,
},
+ .setup = gsw1xx_setup,
.mac_select_pcs = gsw1xx_phylink_mac_select_pcs,
.phylink_get_caps = gsw1xx_phylink_get_caps,
.port_setup = gsw1xx_port_setup,
@@ -894,6 +936,7 @@ static const struct gswip_hw_info gsw150_data = {
[5] = 1,
[6] = 11,
},
+ .setup = gsw1xx_setup,
.phylink_get_caps = gsw150_phylink_get_caps,
/* There is only a single RGMII_SLEW_CFG register in GSW150 and it is
* unknown if RGMII slew configuration affects both RGMII ports
diff --git a/drivers/net/dsa/lantiq/mxl-gsw1xx.h b/drivers/net/dsa/lantiq/mxl-gsw1xx.h
index caa8f1008587a..a7976a5988bd8 100644
--- a/drivers/net/dsa/lantiq/mxl-gsw1xx.h
+++ b/drivers/net/dsa/lantiq/mxl-gsw1xx.h
@@ -110,6 +110,7 @@
#define GSW1XX_SHELL_BASE 0xfa00
#define GSW1XX_SHELL_RST_REQ 0x01
#define GSW1XX_RST_REQ_SGMII_SHELL BIT(5)
+#define GSW1XX_RST_REQ_PHY(p) BIT(p)
#define GSW1XX_SHELL_MANU_ID 0x10
#define GSW1XX_SHELL_MANU_ID_PNUML GENMASK(15, 12)
#define GSW1XX_SHELL_MANU_ID_MANID GENMASK(11, 1)
--
2.55.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH v2] net: dsa: mxl-gsw1xx: deassert internal PHY resets before MDIO registration
2026-09-08 10:30 [PATCH v2] net: dsa: mxl-gsw1xx: deassert internal PHY resets before MDIO registration A. Sverdlin
@ 2026-09-08 10:56 ` Daniel Golle
2026-09-11 8:37 ` Sverdlin, Alexander
0 siblings, 1 reply; 3+ messages in thread
From: Daniel Golle @ 2026-09-08 10:56 UTC (permalink / raw)
To: A. Sverdlin
Cc: netdev, Hauke Mehrtens, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Russell King, linux-kernel
On Tue, Sep 08, 2026 at 12:30:28PM +0200, A. Sverdlin wrote:
> From: Alexander Sverdlin <alexander.sverdlin@siemens.com>
>
> When the switch is bootstrapped with PS_NOWAIT = 0, the internal PHYs
> default to a held-in-reset state. The driver previously only worked with
> PS_NOWAIT = 1 where PHYs are released from reset automatically.
>
> Deassert the PHY reset lines via the RST_REQ register before the MDIO bus
> is registered, so that PHY IDs can be read during bus scan. Only release
> PHYs for ports that have a phy-handle in the device tree, so unused ports
> do not consume power. A single 300ms delay after deasserting all needed
> resets ensures the PHYs are ready for MDIO communication (refer to Intel
> XWAY, Maxlinear GPY111 datasheets or the Lantiq counterpart of the driver).
>
> Signed-off-by: Alexander Sverdlin <alexander.sverdlin@siemens.com>
>
> [...]
> +static int gsw1xx_setup(struct dsa_switch *ds)
> +{
> + struct gsw1xx_priv *priv = container_of(ds->priv, struct gsw1xx_priv, gswip);
> + struct gswip_priv *gswip_priv = ds->priv;
> + struct device_node *phy_np;
> + u32 phy_reset_mask = 0;
> + struct dsa_port *dp;
> + int ret;
> +
> + dsa_switch_for_each_user_port(dp, ds) {
> + struct phylink_config cfg = {};
> +
> + /* Is there an internal PHY on this port? */
> + gswip_priv->hw_info->phylink_get_caps(ds, dp->index, &cfg);
> + if (!test_bit(PHY_INTERFACE_MODE_INTERNAL, cfg.supported_interfaces))
> + continue;
> +
> + /* Will the PHY be really used? */
> + phy_np = of_parse_phandle(dp->dn, "phy-handle", 0);
> + if (!phy_np)
> + continue;
> +
> + of_node_put(phy_np);
> + phy_reset_mask |= GSW1XX_RST_REQ_PHY(dp->index);
> + }
> +
> + if (!phy_reset_mask)
> + return 0;
> +
> + /* Deassert resets only for PHYs referenced by active ports */
> + ret = regmap_clear_bits(priv->shell, GSW1XX_SHELL_RST_REQ, phy_reset_mask);
Likewise doing the other change may also be a good idea:
Disable unused PHYs for a switch with PS_NOWAIT = 1 strapped.
Ie. use regmap_update_bits on the inverted phy_reset_mask.
This would make us end up with the identical state independently
of PS_NOWAIT being strapped or not.
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2] net: dsa: mxl-gsw1xx: deassert internal PHY resets before MDIO registration
2026-09-08 10:56 ` Daniel Golle
@ 2026-09-11 8:37 ` Sverdlin, Alexander
0 siblings, 0 replies; 3+ messages in thread
From: Sverdlin, Alexander @ 2026-09-11 8:37 UTC (permalink / raw)
To: daniel@makrotopia.org
Cc: hauke@hauke-m.de, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org
Hi Daniel!
On Tue, 2026-09-08 at 11:56 +0100, Daniel Golle wrote:
> > When the switch is bootstrapped with PS_NOWAIT = 0, the internal PHYs
> > default to a held-in-reset state. The driver previously only worked with
> > PS_NOWAIT = 1 where PHYs are released from reset automatically.
> >
> > Deassert the PHY reset lines via the RST_REQ register before the MDIO bus
> > is registered, so that PHY IDs can be read during bus scan. Only release
> > PHYs for ports that have a phy-handle in the device tree, so unused ports
> > do not consume power. A single 300ms delay after deasserting all needed
> > resets ensures the PHYs are ready for MDIO communication (refer to Intel
> > XWAY, Maxlinear GPY111 datasheets or the Lantiq counterpart of the driver).
> >
> > Signed-off-by: Alexander Sverdlin <alexander.sverdlin@siemens.com>
> >
> > [...]
> > +static int gsw1xx_setup(struct dsa_switch *ds)
> > +{
> > + struct gsw1xx_priv *priv = container_of(ds->priv, struct gsw1xx_priv, gswip);
> > + struct gswip_priv *gswip_priv = ds->priv;
> > + struct device_node *phy_np;
> > + u32 phy_reset_mask = 0;
> > + struct dsa_port *dp;
> > + int ret;
> > +
> > + dsa_switch_for_each_user_port(dp, ds) {
> > + struct phylink_config cfg = {};
> > +
> > + /* Is there an internal PHY on this port? */
> > + gswip_priv->hw_info->phylink_get_caps(ds, dp->index, &cfg);
> > + if (!test_bit(PHY_INTERFACE_MODE_INTERNAL, cfg.supported_interfaces))
> > + continue;
> > +
> > + /* Will the PHY be really used? */
> > + phy_np = of_parse_phandle(dp->dn, "phy-handle", 0);
> > + if (!phy_np)
> > + continue;
> > +
> > + of_node_put(phy_np);
> > + phy_reset_mask |= GSW1XX_RST_REQ_PHY(dp->index);
> > + }
> > +
> > + if (!phy_reset_mask)
> > + return 0;
> > +
> > + /* Deassert resets only for PHYs referenced by active ports */
> > + ret = regmap_clear_bits(priv->shell, GSW1XX_SHELL_RST_REQ, phy_reset_mask);
>
> Likewise doing the other change may also be a good idea:
> Disable unused PHYs for a switch with PS_NOWAIT = 1 strapped.
> Ie. use regmap_update_bits on the inverted phy_reset_mask.
> This would make us end up with the identical state independently
> of PS_NOWAIT being strapped or not.
Makes sense! Will do in v3.
--
Alexander Sverdlin
Siemens AG
www.siemens.com
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-11 8:37 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-08 10:30 [PATCH v2] net: dsa: mxl-gsw1xx: deassert internal PHY resets before MDIO registration A. Sverdlin
2026-09-08 10:56 ` Daniel Golle
2026-09-11 8:37 ` Sverdlin, Alexander
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox