Subject: Re: [PATCH] net: pcs: xpcs: fix the wrong register that was written back

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:35 +0100

From: Jiawen Wu <[email-masked]>

The value is read from the register TXGBE_RX_GEN_CTL3, and it should be
written back to TXGBE_RX_GEN_CTL3 when it changes some fields.

Cc: [email-masked]
Fixes: f629acc6f210 ("net: pcs: xpcs: support to switch mode for Wangxun NICs")
Signed-off-by: Jiawen Wu <[email-masked]>
Reported-by: Russell King (Oracle) <[email-masked]>
Reviewed-by: Russell King (Oracle) <[email-masked]>
Link: https://patch.msgid.link/[email-masked]
Signed-off-by: Paolo Abeni <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs-wx.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/pcs/pcs-xpcs-wx.c b/drivers/net/pcs/pcs-xpcs-wx.c
index 19c75886f070..5f5cd3596cb8 100644
--- a/drivers/net/pcs/pcs-xpcs-wx.c
+++ b/drivers/net/pcs/pcs-xpcs-wx.c
@@ -109,7 +109,7 @@ static void txgbe_pma_config_1g(struct dw_xpcs *xpcs)
 	txgbe_write_pma(xpcs, TXGBE_DFE_TAP_CTL0, 0);
 	val = txgbe_read_pma(xpcs, TXGBE_RX_GEN_CTL3);
 	val = u16_replace_bits(val, 0x4, TXGBE_RX_GEN_CTL3_LOS_TRSHLD0);
-	txgbe_write_pma(xpcs, TXGBE_RX_EQ_ATTN_CTL, val);
+	txgbe_write_pma(xpcs, TXGBE_RX_GEN_CTL3, val);
 
 	txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL0, 0x20);
 	txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL3, 0x46);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:35 +0100

From: Russell King (Oracle) <[email-masked]>

Move the PCS reset to .pcs_pre_config() rather than at creation time,
which means we call the reset function with the interface that we're
actually going to be using to talk to the downstream device.

Reviewed-by: Vladimir Oltean <[email-masked]>
Tested-by: Vladimir Oltean <[email-masked]> # sja1105
Signed-off-by: Russell King (Oracle) <[email-masked]>
Tested-by: for them?
Link: https://patch.msgid.link/[email-masked]
Signed-off-by: Jakub Kicinski <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.c   | 39 +++++++++++++++++++++++++++++----------
 include/linux/pcs/pcs-xpcs.h |  1 +
 2 files changed, 30 insertions(+), 10 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index 82463f9d50c8..7c6c40ddf722 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -659,6 +659,30 @@ int xpcs_config_eee(struct dw_xpcs *xpcs, int mult_fact_100ns, int enable)
 }
 EXPORT_SYMBOL_GPL(xpcs_config_eee);
 
+static void xpcs_pre_config(struct phylink_pcs *pcs, phy_interface_t interface)
+{
+	struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
+	const struct dw_xpcs_compat *compat;
+	int ret;
+
+	if (!xpcs->need_reset)
+		return;
+
+	compat = xpcs_find_compat(xpcs->desc, interface);
+	if (!compat) {
+		dev_err(&xpcs->mdiodev->dev, "unsupported interface %s\n",
+			phy_modes(interface));
+		return;
+	}
+
+	ret = xpcs_soft_reset(xpcs, compat);
+	if (ret)
+		dev_err(&xpcs->mdiodev->dev, "soft reset failed: %pe\n",
+			ERR_PTR(ret));
+
+	xpcs->need_reset = false;
+}
+
 static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,
 				      unsigned int neg_mode)
 {
@@ -1365,6 +1389,7 @@ static const struct dw_xpcs_desc xpcs_desc_list[] = {
 
 static const struct phylink_pcs_ops xpcs_phylink_ops = {
 	.pcs_validate = xpcs_validate,
+	.pcs_pre_config = xpcs_pre_config,
 	.pcs_config = xpcs_config,
 	.pcs_get_state = xpcs_get_state,
 	.pcs_an_restart = xpcs_an_restart,
@@ -1460,18 +1485,12 @@ static int xpcs_init_id(struct dw_xpcs *xpcs)
 
 static int xpcs_init_iface(struct dw_xpcs *xpcs, phy_interface_t interface)
 {
-	const struct dw_xpcs_compat *compat;
-
-	compat = xpcs_find_compat(xpcs->desc, interface);
-	if (!compat)
-		return -EINVAL;
-
-	if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID) {
+	if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID)
 		xpcs->pcs.poll = false;
-		return 0;
-	}
+	else
+		xpcs->need_reset = true;
 
-	return xpcs_soft_reset(xpcs, compat);
+	return 0;
 }
 
 static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev,
diff --git a/include/linux/pcs/pcs-xpcs.h b/include/linux/pcs/pcs-xpcs.h
index b4a4eb6c8866..fd75d0605bb6 100644
--- a/include/linux/pcs/pcs-xpcs.h
+++ b/include/linux/pcs/pcs-xpcs.h
@@ -61,6 +61,7 @@ struct dw_xpcs {
 	struct clk_bulk_data clks[DW_XPCS_NUM_CLKS];
 	struct phylink_pcs pcs;
 	phy_interface_t interface;
+	bool need_reset;
 };
 
 int xpcs_get_an_mode(struct dw_xpcs *xpcs, phy_interface_t interface);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:35 +0100

Now that we no longer use the "interface" argument when creating the
XPCS sub-driver, remove it from xpcs_create() and xpcs_init_iface().

Reviewed-by: Vladimir Oltean <[email-masked]>
Signed-off-by: Russell King (Oracle) <[email-masked]>
Link: https://patch.msgid.link/[email-masked]
Signed-off-by: Jakub Kicinski <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index 7c6c40ddf722..2d8cc3959b4c 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -1483,7 +1483,7 @@ static int xpcs_init_id(struct dw_xpcs *xpcs)
 	return 0;
 }
 
-static int xpcs_init_iface(struct dw_xpcs *xpcs, phy_interface_t interface)
+static int xpcs_init_iface(struct dw_xpcs *xpcs)
 {
 	if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID)
 		xpcs->pcs.poll = false;
@@ -1493,8 +1493,7 @@ static int xpcs_init_iface(struct dw_xpcs *xpcs, phy_interface_t interface)
 	return 0;
 }
 
-static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev,
-				   phy_interface_t interface)
+static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev)
 {
 	struct dw_xpcs *xpcs;
 	int ret;
@@ -1511,7 +1510,7 @@ static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev,
 	if (ret)
 		goto out_clear_clks;
 
-	ret = xpcs_init_iface(xpcs, interface);
+	ret = xpcs_init_iface(xpcs);
 	if (ret)
 		goto out_clear_clks;
 
@@ -1546,7 +1545,7 @@ struct dw_xpcs *xpcs_create_mdiodev(struct mii_bus *bus, int addr,
 	if (IS_ERR(mdiodev))
 		return ERR_CAST(mdiodev);
 
-	xpcs = xpcs_create(mdiodev, interface);
+	xpcs = xpcs_create(mdiodev);
 
 	/* xpcs_create() has taken a refcount on the mdiodev if it was
 	 * successful. If xpcs_create() fails, this will free the mdio
@@ -1584,7 +1583,7 @@ struct dw_xpcs *xpcs_create_fwnode(struct fwnode_handle *fwnode,
 	if (!mdiodev)
 		return ERR_PTR(-EPROBE_DEFER);
 
-	xpcs = xpcs_create(mdiodev, interface);
+	xpcs = xpcs_create(mdiodev);
 
 	/* xpcs_create() has taken a refcount on the mdiodev if it was
 	 * successful. If xpcs_create() fails, this will free the mdio

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:35 +0100

xpcs_init_iface() no longer does anything with the interface mode, and
now merely does configuration related to the PMA ID. Move this back
into xpcs_create() as it doesn't warrant being a separate function
anymore.

Reviewed-by: Vladimir Oltean <[email-masked]>
Signed-off-by: Russell King (Oracle) <[email-masked]>
Link: https://patch.msgid.link/[email-masked]
Signed-off-by: Jakub Kicinski <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.c | 17 ++++-------------
 1 file changed, 4 insertions(+), 13 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index 2d8cc3959b4c..8765b01c0b5d 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -1483,16 +1483,6 @@ static int xpcs_init_id(struct dw_xpcs *xpcs)
 	return 0;
 }
 
-static int xpcs_init_iface(struct dw_xpcs *xpcs)
-{
-	if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID)
-		xpcs->pcs.poll = false;
-	else
-		xpcs->need_reset = true;
-
-	return 0;
-}
-
 static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev)
 {
 	struct dw_xpcs *xpcs;
@@ -1510,9 +1500,10 @@ static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev)
 	if (ret)
 		goto out_clear_clks;
 
-	ret = xpcs_init_iface(xpcs);
-	if (ret)
-		goto out_clear_clks;
+	if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID)
+		xpcs->pcs.poll = false;
+	else
+		xpcs->need_reset = true;
 
 	return xpcs;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:36 +0100

Provide xpcs create/destroy functions that return and take a phylink_pcs
pointer instead of an xpcs pointer. This will be used by drivers that
have been converted to use phylink_pcs pointers internally, rather than
dw_xpcs pointers.

As xpcs_create_mdiodev() no longer makes use of its interface argument,
pass PHY_INTERFACE_MODE_NA into xpcs_create_mdiodev() until it is
removed later in the series.

Reviewed-by: Vladimir Oltean <[email-masked]>
Signed-off-by: Russell King (Oracle) <[email-masked]>
Link: https://patch.msgid.link/[email-masked]
Signed-off-by: Jakub Kicinski <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.c   | 18 ++++++++++++++++++
 include/linux/pcs/pcs-xpcs.h |  3 +++
 2 files changed, 21 insertions(+)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index 8765b01c0b5d..9b61f97222b9 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -1550,6 +1550,18 @@ struct dw_xpcs *xpcs_create_mdiodev(struct mii_bus *bus, int addr,
 }
 EXPORT_SYMBOL_GPL(xpcs_create_mdiodev);
 
+struct phylink_pcs *xpcs_create_pcs_mdiodev(struct mii_bus *bus, int addr)
+{
+	struct dw_xpcs *xpcs;
+
+	xpcs = xpcs_create_mdiodev(bus, addr, PHY_INTERFACE_MODE_NA);
+	if (IS_ERR(xpcs))
+		return ERR_CAST(xpcs);
+
+	return &xpcs->pcs;
+}
+EXPORT_SYMBOL_GPL(xpcs_create_pcs_mdiodev);
+
 /**
  * xpcs_create_fwnode() - Create a DW xPCS instance from @fwnode
  * @fwnode: fwnode handle poining to the DW XPCS device
@@ -1599,5 +1611,11 @@ void xpcs_destroy(struct dw_xpcs *xpcs)
 }
 EXPORT_SYMBOL_GPL(xpcs_destroy);
 
+void xpcs_destroy_pcs(struct phylink_pcs *pcs)
+{
+	xpcs_destroy(phylink_pcs_to_xpcs(pcs));
+}
+EXPORT_SYMBOL_GPL(xpcs_destroy_pcs);
+
 MODULE_DESCRIPTION("Synopsys DesignWare XPCS library");
 MODULE_LICENSE("GPL v2");
diff --git a/include/linux/pcs/pcs-xpcs.h b/include/linux/pcs/pcs-xpcs.h
index fd75d0605bb6..a4e2243ce647 100644
--- a/include/linux/pcs/pcs-xpcs.h
+++ b/include/linux/pcs/pcs-xpcs.h
@@ -78,4 +78,7 @@ struct dw_xpcs *xpcs_create_fwnode(struct fwnode_handle *fwnode,
 				   phy_interface_t interface);
 void xpcs_destroy(struct dw_xpcs *xpcs);
 
+struct phylink_pcs *xpcs_create_pcs_mdiodev(struct mii_bus *bus, int addr);
+void xpcs_destroy_pcs(struct phylink_pcs *pcs);
+
 #endif /* __LINUX_PCS_XPCS_H */

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:36 +0100

Use xpcs_create_pcs_mdiodev() to create the XPCS instance, storing
and using the phylink_pcs pointer internally, rather than dw_xpcs.
Use xpcs_destroy_pcs() to destroy the XPCS instance when we've
finished with it.

Signed-off-by: Russell King (Oracle) <[email-masked]>
Reviewed-by: Andrew Lunn <[email-masked]>
Link: https://patch.msgid.link/[email-masked]
Signed-off-by: Jakub Kicinski <[email-masked]>
---
 drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c  | 18 +++++++++---------
 drivers/net/ethernet/wangxun/txgbe/txgbe_type.h |  2 +-
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
index 5f502265f0a6..515e917333a6 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
@@ -122,7 +122,7 @@ static int txgbe_pcs_write(struct mii_bus *bus, int addr, int devnum, int regnum
 static int txgbe_mdio_pcs_init(struct txgbe *txgbe)
 {
 	struct mii_bus *mii_bus;
-	struct dw_xpcs *xpcs;
+	struct phylink_pcs *pcs;
 	struct pci_dev *pdev;
 	struct wx *wx;
 	int ret = 0;
@@ -147,11 +147,11 @@ static int txgbe_mdio_pcs_init(struct txgbe *txgbe)
 	if (ret)
 		return ret;
 
-	xpcs = xpcs_create_mdiodev(mii_bus, 0, PHY_INTERFACE_MODE_10GBASER);
-	if (IS_ERR(xpcs))
-		return PTR_ERR(xpcs);
+	pcs = xpcs_create_pcs_mdiodev(mii_bus, 0);
+	if (IS_ERR(pcs))
+		return PTR_ERR(pcs);
 
-	txgbe->xpcs = xpcs;
+	txgbe->pcs = pcs;
 
 	return 0;
 }
@@ -163,7 +163,7 @@ static struct phylink_pcs *txgbe_phylink_mac_select(struct phylink_config *confi
 	struct txgbe *txgbe = wx->priv;
 
 	if (interface == PHY_INTERFACE_MODE_10GBASER)
-		return &txgbe->xpcs->pcs;
+		return txgbe->pcs;
 
 	return NULL;
 }
@@ -302,7 +302,7 @@ irqreturn_t txgbe_link_irq_handler(int irq, void *data)
 	status = rd32(wx, TXGBE_CFG_PORT_ST);
 	up = !!(status & TXGBE_CFG_PORT_ST_LINK_UP);
 
-	phylink_pcs_change(&txgbe->xpcs->pcs, up);
+	phylink_pcs_change(txgbe->pcs, up);
 
 	return IRQ_HANDLED;
 }
@@ -779,7 +779,7 @@ int txgbe_init_phy(struct txgbe *txgbe)
 err_destroy_phylink:
 	phylink_destroy(wx->phylink);
 err_destroy_xpcs:
-	xpcs_destroy(txgbe->xpcs);
+	xpcs_destroy_pcs(txgbe->pcs);
 err_unregister_swnode:
 	software_node_unregister_node_group(txgbe->nodes.group);
 
@@ -799,6 +799,6 @@ void txgbe_remove_phy(struct txgbe *txgbe)
 	clkdev_drop(txgbe->clock);
 	clk_unregister(txgbe->clk);
 	phylink_destroy(txgbe->wx->phylink);
-	xpcs_destroy(txgbe->xpcs);
+	xpcs_destroy_pcs(txgbe->pcs);
 	software_node_unregister_node_group(txgbe->nodes.group);
 }
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
index 959102c4c379..cc3a7b62fe9e 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
@@ -329,7 +329,7 @@ struct txgbe {
 	struct wx *wx;
 	struct txgbe_nodes nodes;
 	struct txgbe_irq misc;
-	struct dw_xpcs *xpcs;
+	struct phylink_pcs *pcs;
 	struct platform_device *sfp_dev;
 	struct platform_device *i2c_dev;
 	struct clk_lookup *clock;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:36 +0100

The static configuration reload saves the port speed in the static
configuration tables by first converting it from the internal
respresentation to the SPEED_xxx ethtool representation, and then
converts it back to restore the setting. This is because
sja1105_adjust_port_config() takes the speed as SPEED_xxx.

However, this is unnecessarily complex. If we split
sja1105_adjust_port_config() up, we can simply save and restore the
mac[port].speed member in the static configuration tables.

Signed-off-by: Russell King (Oracle) <[email-masked]>
Link: https://patch.msgid.link/[email-masked]
Signed-off-by: Jakub Kicinski <[email-masked]>
---
 drivers/net/dsa/sja1105/sja1105_main.c | 65 ++++++++++++++++++----------------
 1 file changed, 34 insertions(+), 31 deletions(-)

diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c
index c7282ce3d11c..2815ee78e7fa 100644
--- a/drivers/net/dsa/sja1105/sja1105_main.c
+++ b/drivers/net/dsa/sja1105/sja1105_main.c
@@ -1263,29 +1263,11 @@ static int sja1105_parse_dt(struct sja1105_private *priv)
 	return rc;
 }
 
-/* Convert link speed from SJA1105 to ethtool encoding */
-static int sja1105_port_speed_to_ethtool(struct sja1105_private *priv,
-					 u64 speed)
-{
-	if (speed == priv->info->port_speed[SJA1105_SPEED_10MBPS])
-		return SPEED_10;
-	if (speed == priv->info->port_speed[SJA1105_SPEED_100MBPS])
-		return SPEED_100;
-	if (speed == priv->info->port_speed[SJA1105_SPEED_1000MBPS])
-		return SPEED_1000;
-	if (speed == priv->info->port_speed[SJA1105_SPEED_2500MBPS])
-		return SPEED_2500;
-	return SPEED_UNKNOWN;
-}
-
-/* Set link speed in the MAC configuration for a specific port. */
-static int sja1105_adjust_port_config(struct sja1105_private *priv, int port,
-				      int speed_mbps)
+static int sja1105_set_port_speed(struct sja1105_private *priv, int port,
+				  int speed_mbps)
 {
 	struct sja1105_mac_config_entry *mac;
-	struct device *dev = priv->ds->dev;
 	u64 speed;
-	int rc;
 
 	/* On P/Q/R/S, one can read from the device via the MAC reconfiguration
 	 * tables. On E/T, MAC reconfig tables are not readable, only writable.
@@ -1319,7 +1301,7 @@ static int sja1105_adjust_port_config(struct sja1105_private *priv, int port,
 		speed = priv->info->port_speed[SJA1105_SPEED_2500MBPS];
 		break;
 	default:
-		dev_err(dev, "Invalid speed %iMbps\n", speed_mbps);
+		dev_err(priv->ds->dev, "Invalid speed %iMbps\n", speed_mbps);
 		return -EINVAL;
 	}
 
@@ -1331,11 +1313,31 @@ static int sja1105_adjust_port_config(struct sja1105_private *priv, int port,
 	 * we need to configure the PCS only (if even that).
 	 */
 	if (priv->phy_mode[port] == PHY_INTERFACE_MODE_SGMII)
-		mac[port].speed = priv->info->port_speed[SJA1105_SPEED_1000MBPS];
+		speed = priv->info->port_speed[SJA1105_SPEED_1000MBPS];
 	else if (priv->phy_mode[port] == PHY_INTERFACE_MODE_2500BASEX)
-		mac[port].speed = priv->info->port_speed[SJA1105_SPEED_2500MBPS];
-	else
-		mac[port].speed = speed;
+		speed = priv->info->port_speed[SJA1105_SPEED_2500MBPS];
+
+	mac[port].speed = speed;
+
+	return 0;
+}
+
+/* Write the MAC Configuration Table entry and, if necessary, the CGU settings,
+ * after a link speedchange for this port.
+ */
+static int sja1105_set_port_config(struct sja1105_private *priv, int port)
+{
+	struct sja1105_mac_config_entry *mac;
+	struct device *dev = priv->ds->dev;
+	int rc;
+
+	/* On P/Q/R/S, one can read from the device via the MAC reconfiguration
+	 * tables. On E/T, MAC reconfig tables are not readable, only writable.
+	 * We have to *know* what the MAC looks like.  For the sake of keeping
+	 * the code common, we'll use the static configuration tables as a
+	 * reasonable approximation for both E/T and P/Q/R/S.
+	 */
+	mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries;
 
 	/* Write to the dynamic reconfiguration tables */
 	rc = sja1105_dynamic_config_write(priv, BLK_IDX_MAC_CONFIG, port,
@@ -1396,7 +1398,8 @@ static void sja1105_mac_link_up(struct phylink_config *config,
 	struct sja1105_private *priv = dp->ds->priv;
 	int port = dp->index;
 
-	sja1105_adjust_port_config(priv, port, speed);
+	if (!sja1105_set_port_speed(priv, port, speed))
+		sja1105_set_port_config(priv, port);
 
 	sja1105_inhibit_tx(priv, BIT(port), false);
 }
@@ -2299,8 +2302,8 @@ int sja1105_static_config_reload(struct sja1105_private *priv,
 {
 	struct ptp_system_timestamp ptp_sts_before;
 	struct ptp_system_timestamp ptp_sts_after;
-	int speed_mbps[SJA1105_MAX_NUM_PORTS];
 	u16 bmcr[SJA1105_MAX_NUM_PORTS] = {0};
+	u64 mac_speed[SJA1105_MAX_NUM_PORTS];
 	struct sja1105_mac_config_entry *mac;
 	struct dsa_switch *ds = priv->ds;
 	s64 t1, t2, t3, t4;
@@ -2313,14 +2316,13 @@ int sja1105_static_config_reload(struct sja1105_private *priv,
 
 	mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries;
 
-	/* Back up the dynamic link speed changed by sja1105_adjust_port_config
+	/* Back up the dynamic link speed changed by sja1105_set_port_speed()
 	 * in order to temporarily restore it to SJA1105_SPEED_AUTO - which the
 	 * switch wants to see in the static config in order to allow us to
 	 * change it through the dynamic interface later.
 	 */
 	for (i = 0; i < ds->num_ports; i++) {
-		speed_mbps[i] = sja1105_port_speed_to_ethtool(priv,
-							      mac[i].speed);
+		mac_speed[i] = mac[i].speed;
 		mac[i].speed = priv->info->port_speed[SJA1105_SPEED_AUTO];
 
 		if (priv->xpcs[i])
@@ -2383,7 +2385,8 @@ int sja1105_static_config_reload(struct sja1105_private *priv,
 		struct dw_xpcs *xpcs = priv->xpcs[i];
 		unsigned int neg_mode;
 
-		rc = sja1105_adjust_port_config(priv, i, speed_mbps[i]);
+		mac[i].speed = mac_speed[i];
+		rc = sja1105_set_port_config(priv, i);
 		if (rc < 0)
 			goto out;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:36 +0100

Call the PCS operations through the ops structure, which avoids needing
to export xpcs internal functions.

Signed-off-by: Russell King (Oracle) <[email-masked]>
Link: https://patch.msgid.link/[email-masked]
Signed-off-by: Jakub Kicinski <[email-masked]>
---
 drivers/net/dsa/sja1105/sja1105_main.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c
index 2815ee78e7fa..96fddad030b6 100644
--- a/drivers/net/dsa/sja1105/sja1105_main.c
+++ b/drivers/net/dsa/sja1105/sja1105_main.c
@@ -2383,6 +2383,7 @@ int sja1105_static_config_reload(struct sja1105_private *priv,

 		mac[i].speed = mac_speed[i];
@@ -2393,12 +2394,15 @@ int sja1105_static_config_reload(struct sja1105_private *priv,
 		if (!xpcs)
 			continue;
 
+		pcs = &xpcs->pcs;
+
 		if (bmcr[i] & BMCR_ANENABLE)
 			neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
 		else
 			neg_mode = PHYLINK_PCS_NEG_OUTBAND;
 
-		rc = xpcs_do_config(xpcs, priv->phy_mode[i], NULL, neg_mode);
+		rc = pcs->ops->pcs_config(pcs, neg_mode, priv->phy_mode[i],
+					  NULL, true);
 		if (rc < 0)
 			goto out;
 
@@ -2414,8 +2418,8 @@ int sja1105_static_config_reload(struct sja1105_private *priv,
 			else
 				speed = SPEED_10;
 
-			xpcs_link_up(&xpcs->pcs, neg_mode, priv->phy_mode[i],
-				     speed, DUPLEX_FULL);
+			pcs->ops->pcs_link_up(pcs, neg_mode, priv->phy_mode[i],
+					      speed, DUPLEX_FULL);
 		}
 	}

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:36 +0100

Signed-off-by: Russell King (Oracle) <[email-masked]>
Link: https://patch.msgid.link/[email-masked]
Signed-off-by: Jakub Kicinski <[email-masked]>
---
 drivers/net/dsa/sja1105/sja1105.h      |  2 +-
 drivers/net/dsa/sja1105/sja1105_main.c | 16 ++++------------
 drivers/net/dsa/sja1105/sja1105_mdio.c | 28 +++++++++++++---------------
 3 files changed, 18 insertions(+), 28 deletions(-)

diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h
index 8c66d3bf61f0..dceb96ae9c83 100644
--- a/drivers/net/dsa/sja1105/sja1105.h
+++ b/drivers/net/dsa/sja1105/sja1105.h
@@ -278,7 +278,7 @@ struct sja1105_private {
 	struct mii_bus *mdio_base_t1;
 	struct mii_bus *mdio_base_tx;
 	struct mii_bus *mdio_pcs;
-	struct dw_xpcs *xpcs[SJA1105_MAX_NUM_PORTS];
+	struct phylink_pcs *pcs[SJA1105_MAX_NUM_PORTS];
 	struct sja1105_ptp_data ptp_data;
 	struct sja1105_tas_data tas_data;
 };
diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c
index 96fddad030b6..4d7284b38c52 100644
--- a/drivers/net/dsa/sja1105/sja1105_main.c
+++ b/drivers/net/dsa/sja1105/sja1105_main.c
@@ -15,7 +15,6 @@
 #include <linux/of.h>
 #include <linux/of_net.h>
 #include <linux/of_mdio.h>
-#include <linux/pcs/pcs-xpcs.h>
 #include <linux/netdev_features.h>
 #include <linux/netdevice.h>
 #include <linux/if_bridge.h>
@@ -1364,12 +1363,8 @@ sja1105_mac_select_pcs(struct phylink_config *config, phy_interface_t iface)
 {
 	struct dsa_port *dp = dsa_phylink_to_port(config);
 	struct sja1105_private *priv = dp->ds->priv;
-	struct dw_xpcs *xpcs = priv->xpcs[dp->index];
 
-	if (xpcs)
-		return &xpcs->pcs;
-
-	return NULL;
+	return priv->pcs[dp->index];
 }
 
 static void sja1105_mac_config(struct phylink_config *config,
@@ -2325,7 +2320,7 @@ int sja1105_static_config_reload(struct sja1105_private *priv,
 		mac_speed[i] = mac[i].speed;
 		mac[i].speed = priv->info->port_speed[SJA1105_SPEED_AUTO];
 
-		if (priv->xpcs[i])
+		if (priv->pcs[i])
 			bmcr[i] = mdiobus_c45_read(priv->mdio_pcs, i,
 						   MDIO_MMD_VEND2, MDIO_CTRL1);
 	}
@@ -2382,8 +2377,7 @@ int sja1105_static_config_reload(struct sja1105_private *priv,
 	}
 
 	for (i = 0; i < ds->num_ports; i++) {
-		struct dw_xpcs *xpcs = priv->xpcs[i];
-		struct phylink_pcs *pcs;
+		struct phylink_pcs *pcs = priv->pcs[i];
 		unsigned int neg_mode;
 
 		mac[i].speed = mac_speed[i];
@@ -2391,11 +2385,9 @@ int sja1105_static_config_reload(struct sja1105_private *priv,
 		if (rc < 0)
 			goto out;
 
-		if (!xpcs)
+		if (!pcs)
 			continue;
 
-		pcs = &xpcs->pcs;
-
 		if (bmcr[i] & BMCR_ANENABLE)
 			neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
 		else
diff --git a/drivers/net/dsa/sja1105/sja1105_mdio.c b/drivers/net/dsa/sja1105/sja1105_mdio.c
index 52ddb4ef259e..84b7169f2974 100644
--- a/drivers/net/dsa/sja1105/sja1105_mdio.c
+++ b/drivers/net/dsa/sja1105/sja1105_mdio.c
@@ -400,7 +400,7 @@ static int sja1105_mdiobus_pcs_register(struct sja1105_private *priv)
 	}
 
 	for (port = 0; port < ds->num_ports; port++) {
-		struct dw_xpcs *xpcs;
+		struct phylink_pcs *pcs;
 
 		if (dsa_is_unused_port(ds, port))
 			continue;
@@ -409,13 +409,13 @@ static int sja1105_mdiobus_pcs_register(struct sja1105_private *priv)
 		    priv->phy_mode[port] != PHY_INTERFACE_MODE_2500BASEX)
 			continue;
 
-		xpcs = xpcs_create_mdiodev(bus, port, priv->phy_mode[port]);
-		if (IS_ERR(xpcs)) {
-			rc = PTR_ERR(xpcs);
+		pcs = xpcs_create_pcs_mdiodev(bus, port);
+		if (IS_ERR(pcs)) {
+			rc = PTR_ERR(pcs);
 			goto out_pcs_free;
 		}
 
-		priv->xpcs[port] = xpcs;
+		priv->pcs[port] = pcs;
 	}
 
 	priv->mdio_pcs = bus;
@@ -424,11 +424,10 @@ static int sja1105_mdiobus_pcs_register(struct sja1105_private *priv)
 
 out_pcs_free:
 	for (port = 0; port < ds->num_ports; port++) {
-		if (!priv->xpcs[port])
-			continue;
-
-		xpcs_destroy(priv->xpcs[port]);
-		priv->xpcs[port] = NULL;
+		if (priv->pcs[port]) {
+			xpcs_destroy_pcs(priv->pcs[port]);
+			priv->pcs[port] = NULL;
+		}
 	}
 
 	mdiobus_unregister(bus);
@@ -446,11 +445,10 @@ static void sja1105_mdiobus_pcs_unregister(struct sja1105_private *priv)
 		return;
 
 	for (port = 0; port < ds->num_ports; port++) {
-		if (!priv->xpcs[port])
-			continue;
-
-		xpcs_destroy(priv->xpcs[port]);
-		priv->xpcs[port] = NULL;
+		if (priv->pcs[port]) {
+			xpcs_destroy_pcs(priv->pcs[port]);
+			priv->pcs[port] = NULL;
+		}
 	}
 
 	mdiobus_unregister(priv->mdio_pcs);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:36 +0100

The XPCS sub-driver no longer uses the "interface" argument to the
xpcs_create_mdiodev() and xpcs_create_fwnode() functions. Remove
this now unnecessary argument, updating the stmmac driver
appropriately.

Reviewed-by: Vladimir Oltean <[email-masked]>
Signed-off-by: Russell King (Oracle) <[email-masked]>
Link: https://patch.msgid.link/[email-masked]
Signed-off-by: Jakub Kicinski <[email-masked]>
---
 drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c |  7 +++----
 drivers/net/pcs/pcs-xpcs.c                        | 10 +++-------
 include/linux/pcs/pcs-xpcs.h                      |  6 ++----
 3 files changed, 8 insertions(+), 15 deletions(-)

diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
index 03f90676b3ad..0c7d81ddd440 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
@@ -500,23 +500,22 @@ int stmmac_pcs_setup(struct net_device *ndev)
 	struct fwnode_handle *devnode, *pcsnode;
 	struct dw_xpcs *xpcs = NULL;
 	struct stmmac_priv *priv;
-	int addr, mode, ret;
+	int addr, ret;
 
 	priv = netdev_priv(ndev);
-	mode = priv->plat->phy_interface;
 	devnode = priv->plat->port_node;
 
 	if (priv->plat->pcs_init) {
 		ret = priv->plat->pcs_init(priv);
 	} else if (fwnode_property_present(devnode, "pcs-handle")) {
 		pcsnode = fwnode_find_reference(devnode, "pcs-handle", 0);
-		xpcs = xpcs_create_fwnode(pcsnode, mode);
+		xpcs = xpcs_create_fwnode(pcsnode);
 		fwnode_handle_put(pcsnode);
 		ret = PTR_ERR_OR_ZERO(xpcs);
 	} else if (priv->plat->mdio_bus_data &&
 		   priv->plat->mdio_bus_data->pcs_mask) {
 		addr = ffs(priv->plat->mdio_bus_data->pcs_mask) - 1;
-		xpcs = xpcs_create_mdiodev(priv->mii, addr, mode);
+		xpcs = xpcs_create_mdiodev(priv->mii, addr);
 		ret = PTR_ERR_OR_ZERO(xpcs);
 	} else {
 		return 0;
diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index 9b61f97222b9..f25e7afdfdf5 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -1520,14 +1520,12 @@ static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev)
  * xpcs_create_mdiodev() - create a DW xPCS instance with the MDIO @addr
  * @bus: pointer to the MDIO-bus descriptor for the device to be looked at
  * @addr: device MDIO-bus ID
- * @interface: requested PHY interface
  *
  * Return: a pointer to the DW XPCS handle if successful, otherwise -ENODEV if
  * the PCS device couldn't be found on the bus and other negative errno related
  * to the data allocation and MDIO-bus communications.
  */
-struct dw_xpcs *xpcs_create_mdiodev(struct mii_bus *bus, int addr,
-				    phy_interface_t interface)
+struct dw_xpcs *xpcs_create_mdiodev(struct mii_bus *bus, int addr)
 {
 	struct mdio_device *mdiodev;
 	struct dw_xpcs *xpcs;
@@ -1554,7 +1552,7 @@ struct phylink_pcs *xpcs_create_pcs_mdiodev(struct mii_bus *bus, int addr)
 {
 	struct dw_xpcs *xpcs;
 
-	xpcs = xpcs_create_mdiodev(bus, addr, PHY_INTERFACE_MODE_NA);
+	xpcs = xpcs_create_mdiodev(bus, addr);
 	if (IS_ERR(xpcs))
 		return ERR_CAST(xpcs);
 
@@ -1565,7 +1563,6 @@ EXPORT_SYMBOL_GPL(xpcs_create_pcs_mdiodev);
 /**
  * xpcs_create_fwnode() - Create a DW xPCS instance from @fwnode
  * @fwnode: fwnode handle poining to the DW XPCS device
- * @interface: requested PHY interface
  *
  * Return: a pointer to the DW XPCS handle if successful, otherwise -ENODEV if
  * the fwnode device is unavailable or the PCS device couldn't be found on the
@@ -1573,8 +1570,7 @@ EXPORT_SYMBOL_GPL(xpcs_create_pcs_mdiodev);
  * other negative errno related to the data allocations and MDIO-bus
  * communications.
  */
-struct dw_xpcs *xpcs_create_fwnode(struct fwnode_handle *fwnode,
-				   phy_interface_t interface)
+struct dw_xpcs *xpcs_create_fwnode(struct fwnode_handle *fwnode)
 {
 	struct mdio_device *mdiodev;
 	struct dw_xpcs *xpcs;
diff --git a/include/linux/pcs/pcs-xpcs.h b/include/linux/pcs/pcs-xpcs.h
index a4e2243ce647..758daabb76c7 100644
--- a/include/linux/pcs/pcs-xpcs.h
+++ b/include/linux/pcs/pcs-xpcs.h
@@ -72,10 +72,8 @@ int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface,
 void xpcs_get_interfaces(struct dw_xpcs *xpcs, unsigned long *interfaces);
 int xpcs_config_eee(struct dw_xpcs *xpcs, int mult_fact_100ns,
 		    int enable);
-struct dw_xpcs *xpcs_create_mdiodev(struct mii_bus *bus, int addr,
-				    phy_interface_t interface);
-struct dw_xpcs *xpcs_create_fwnode(struct fwnode_handle *fwnode,
-				   phy_interface_t interface);
+struct dw_xpcs *xpcs_create_mdiodev(struct mii_bus *bus, int addr);
+struct dw_xpcs *xpcs_create_fwnode(struct fwnode_handle *fwnode);
 void xpcs_destroy(struct dw_xpcs *xpcs);
 
 struct phylink_pcs *xpcs_create_pcs_mdiodev(struct mii_bus *bus, int addr);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:36 +0100

As nothing outside pcs-xpcs.c calls neither xpcs_do_config() nor
xpcs_link_up(), remove their exports and prototypes.

Reviewed-by: Vladimir Oltean <[email-masked]>
Signed-off-by: Russell King (Oracle) <[email-masked]>
Link: https://patch.msgid.link/[email-masked]
Signed-off-by: Jakub Kicinski <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.c   | 11 +++++------
 include/linux/pcs/pcs-xpcs.h |  4 ----
 2 files changed, 5 insertions(+), 10 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index f25e7afdfdf5..0a01c552f591 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -851,8 +851,9 @@ static int xpcs_config_2500basex(struct dw_xpcs *xpcs)
 	return xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL, ret);
 }
 
-int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface,
-		   const unsigned long *advertising, unsigned int neg_mode)
+static int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface,
+			  const unsigned long *advertising,
+			  unsigned int neg_mode)
 {
 	const struct dw_xpcs_compat *compat;
 	int ret;
@@ -905,7 +906,6 @@ int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface,
 
 	return 0;
 }
-EXPORT_SYMBOL_GPL(xpcs_do_config);
 
 static int xpcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
 		       phy_interface_t interface,
@@ -1207,8 +1207,8 @@ static void xpcs_link_up_1000basex(struct dw_xpcs *xpcs, unsigned int neg_mode,
 		pr_err("%s: xpcs_write returned %pe\n", __func__, ERR_PTR(ret));
 }
 
-void xpcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
-		  phy_interface_t interface, int speed, int duplex)
+static void xpcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
+			 phy_interface_t interface, int speed, int duplex)
 {
 	struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
 
@@ -1219,7 +1219,6 @@ void xpcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
 	if (interface == PHY_INTERFACE_MODE_1000BASEX)
 		return xpcs_link_up_1000basex(xpcs, neg_mode, speed, duplex);
 }
-EXPORT_SYMBOL_GPL(xpcs_link_up);
 
 static void xpcs_an_restart(struct phylink_pcs *pcs)
 {
diff --git a/include/linux/pcs/pcs-xpcs.h b/include/linux/pcs/pcs-xpcs.h
index 758daabb76c7..abda475111d1 100644
--- a/include/linux/pcs/pcs-xpcs.h
+++ b/include/linux/pcs/pcs-xpcs.h
@@ -65,10 +65,6 @@ struct dw_xpcs {
 };
 
 int xpcs_get_an_mode(struct dw_xpcs *xpcs, phy_interface_t interface);
-void xpcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
-		  phy_interface_t interface, int speed, int duplex);
-int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface,
-		   const unsigned long *advertising, unsigned int neg_mode);
 void xpcs_get_interfaces(struct dw_xpcs *xpcs, unsigned long *interfaces);
 int xpcs_config_eee(struct dw_xpcs *xpcs, int mult_fact_100ns,
 		    int enable);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:36 +0100

There is no reason for the struct dw_xpcs_compat arrays to be a fixed
size other than the way we iterate over them. The index into the array
isn't used for anything, and having them fixed size needlessly wastes
space.

Remove the enum that defines their size, and instead use an empty
array entry (with NULL ->supported) to mark the end of the array.

Signed-off-by: Russell King (Oracle) <[email-masked]>
Signed-off-by: David S. Miller <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.c | 69 +++++++++++++++++-----------------------------
 1 file changed, 25 insertions(+), 44 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index 0a01c552f591..e1f264039c91 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -135,17 +135,6 @@ static const phy_interface_t xpcs_2500basex_interfaces[] = {
 	PHY_INTERFACE_MODE_2500BASEX,
 };
 
-enum {
-	DW_XPCS_USXGMII,
-	DW_XPCS_10GKR,
-	DW_XPCS_XLGMII,
-	DW_XPCS_10GBASER,
-	DW_XPCS_SGMII,
-	DW_XPCS_1000BASEX,
-	DW_XPCS_2500BASEX,
-	DW_XPCS_INTERFACE_MAX,
-};
-
 struct dw_xpcs_compat {
 	const int *supported;
 	const phy_interface_t *interface;
@@ -163,15 +152,13 @@ struct dw_xpcs_desc {
 static const struct dw_xpcs_compat *
 xpcs_find_compat(const struct dw_xpcs_desc *desc, phy_interface_t interface)
 {
-	int i, j;
-
-	for (i = 0; i < DW_XPCS_INTERFACE_MAX; i++) {
-		const struct dw_xpcs_compat *compat = &desc->compat[i];
+	const struct dw_xpcs_compat *compat;
+	int j;
 
+	for (compat = desc->compat; compat->supported; compat++)
 		for (j = 0; j < compat->num_interfaces; j++)
 			if (compat->interface[j] == interface)
 				return compat;
-	}
 
 	return NULL;
 }
@@ -610,14 +597,12 @@ static int xpcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
 
 void xpcs_get_interfaces(struct dw_xpcs *xpcs, unsigned long *interfaces)
 {
-	int i, j;
-
-	for (i = 0; i < DW_XPCS_INTERFACE_MAX; i++) {
-		const struct dw_xpcs_compat *compat = &xpcs->desc->compat[i];
+	const struct dw_xpcs_compat *compat;
+	int j;
 
+	for (compat = xpcs->desc->compat; compat->supported; compat++)
 		for (j = 0; j < compat->num_interfaces; j++)
 			__set_bit(compat->interface[j], interfaces);
-	}
 }
 EXPORT_SYMBOL_GPL(xpcs_get_interfaces);
 
@@ -1298,76 +1283,72 @@ static int xpcs_get_id(struct dw_xpcs *xpcs)
 	return 0;
 }
 
-static const struct dw_xpcs_compat synopsys_xpcs_compat[DW_XPCS_INTERFACE_MAX] = {
-	[DW_XPCS_USXGMII] = {
+static const struct dw_xpcs_compat synopsys_xpcs_compat[] = {
+	{
 		.supported = xpcs_usxgmii_features,
 		.interface = xpcs_usxgmii_interfaces,
 		.num_interfaces = ARRAY_SIZE(xpcs_usxgmii_interfaces),
 		.an_mode = DW_AN_C73,
-	},
-	[DW_XPCS_10GKR] = {
+	}, {
 		.supported = xpcs_10gkr_features,
 		.interface = xpcs_10gkr_interfaces,
 		.num_interfaces = ARRAY_SIZE(xpcs_10gkr_interfaces),
 		.an_mode = DW_AN_C73,
-	},
-	[DW_XPCS_XLGMII] = {
+	}, {
 		.supported = xpcs_xlgmii_features,
 		.interface = xpcs_xlgmii_interfaces,
 		.num_interfaces = ARRAY_SIZE(xpcs_xlgmii_interfaces),
 		.an_mode = DW_AN_C73,
-	},
-	[DW_XPCS_10GBASER] = {
+	}, {
 		.supported = xpcs_10gbaser_features,
 		.interface = xpcs_10gbaser_interfaces,
 		.num_interfaces = ARRAY_SIZE(xpcs_10gbaser_interfaces),
 		.an_mode = DW_10GBASER,
-	},
-	[DW_XPCS_SGMII] = {
+	}, {
 		.supported = xpcs_sgmii_features,
 		.interface = xpcs_sgmii_interfaces,
 		.num_interfaces = ARRAY_SIZE(xpcs_sgmii_interfaces),
 		.an_mode = DW_AN_C37_SGMII,
-	},
-	[DW_XPCS_1000BASEX] = {
+	}, {
 		.supported = xpcs_1000basex_features,
 		.interface = xpcs_1000basex_interfaces,
 		.num_interfaces = ARRAY_SIZE(xpcs_1000basex_interfaces),
 		.an_mode = DW_AN_C37_1000BASEX,
-	},
-	[DW_XPCS_2500BASEX] = {
+	}, {
 		.supported = xpcs_2500basex_features,
 		.interface = xpcs_2500basex_interfaces,
 		.num_interfaces = ARRAY_SIZE(xpcs_2500basex_interfaces),
 		.an_mode = DW_2500BASEX,
-	},
+	}, {
+	}
 };
 
-static const struct dw_xpcs_compat nxp_sja1105_xpcs_compat[DW_XPCS_INTERFACE_MAX] = {
-	[DW_XPCS_SGMII] = {
+static const struct dw_xpcs_compat nxp_sja1105_xpcs_compat[] = {
+	{
 		.supported = xpcs_sgmii_features,
 		.interface = xpcs_sgmii_interfaces,
 		.num_interfaces = ARRAY_SIZE(xpcs_sgmii_interfaces),
 		.an_mode = DW_AN_C37_SGMII,
 		.pma_config = nxp_sja1105_sgmii_pma_config,
-	},
+	}, {
+	}
 };
 
-static const struct dw_xpcs_compat nxp_sja1110_xpcs_compat[DW_XPCS_INTERFACE_MAX] = {
-	[DW_XPCS_SGMII] = {
+static const struct dw_xpcs_compat nxp_sja1110_xpcs_compat[] = {
+	{
 		.supported = xpcs_sgmii_features,
 		.interface = xpcs_sgmii_interfaces,
 		.num_interfaces = ARRAY_SIZE(xpcs_sgmii_interfaces),
 		.an_mode = DW_AN_C37_SGMII,
 		.pma_config = nxp_sja1110_sgmii_pma_config,
-	},
-	[DW_XPCS_2500BASEX] = {
+	}, {
 		.supported = xpcs_2500basex_features,
 		.interface = xpcs_2500basex_interfaces,
 		.num_interfaces = ARRAY_SIZE(xpcs_2500basex_interfaces),
 		.an_mode = DW_2500BASEX,
 		.pma_config = nxp_sja1110_2500basex_pma_config,
-	},
+	}, {
+	}
 };
 
 static const struct dw_xpcs_desc xpcs_desc_list[] = {

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:36 +0100

Currently, xpcs uses an array of interfaces that each "compat" entry
supports. When looking up the compat entry for an interface, we
iterate over the compat entries and then over each interface.

Since each compat entry only has a single interface in its interfaces
array, replace the array with a single member in the compat structure.

Signed-off-by: Russell King (Oracle) <[email-masked]>
Signed-off-by: David S. Miller <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.c | 71 +++++++++-------------------------------------
 1 file changed, 14 insertions(+), 57 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index e1f264039c91..4fbf7c816ed5 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -107,38 +107,9 @@ static const int xpcs_2500basex_features[] = {
 	__ETHTOOL_LINK_MODE_MASK_NBITS,
 };
 
-static const phy_interface_t xpcs_usxgmii_interfaces[] = {
-	PHY_INTERFACE_MODE_USXGMII,
-};
-
-static const phy_interface_t xpcs_10gkr_interfaces[] = {
-	PHY_INTERFACE_MODE_10GKR,
-};
-
-static const phy_interface_t xpcs_xlgmii_interfaces[] = {
-	PHY_INTERFACE_MODE_XLGMII,
-};
-
-static const phy_interface_t xpcs_10gbaser_interfaces[] = {
-	PHY_INTERFACE_MODE_10GBASER,
-};
-
-static const phy_interface_t xpcs_sgmii_interfaces[] = {
-	PHY_INTERFACE_MODE_SGMII,
-};
-
-static const phy_interface_t xpcs_1000basex_interfaces[] = {
-	PHY_INTERFACE_MODE_1000BASEX,
-};
-
-static const phy_interface_t xpcs_2500basex_interfaces[] = {
-	PHY_INTERFACE_MODE_2500BASEX,
-};
-
 struct dw_xpcs_compat {
+	phy_interface_t interface;
 	const int *supported;
-	const phy_interface_t *interface;
-	int num_interfaces;
 	int an_mode;
 	int (*pma_config)(struct dw_xpcs *xpcs);
 };
@@ -153,12 +124,10 @@ static const struct dw_xpcs_compat *
 xpcs_find_compat(const struct dw_xpcs_desc *desc, phy_interface_t interface)
 {
 	const struct dw_xpcs_compat *compat;
-	int j;
 
 	for (compat = desc->compat; compat->supported; compat++)
-		for (j = 0; j < compat->num_interfaces; j++)
-			if (compat->interface[j] == interface)
-				return compat;
+		if (compat->interface == interface)
+			return compat;
 
 	return NULL;
 }
@@ -598,11 +567,9 @@ static int xpcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
 void xpcs_get_interfaces(struct dw_xpcs *xpcs, unsigned long *interfaces)
 {
 	const struct dw_xpcs_compat *compat;
-	int j;
 
 	for (compat = xpcs->desc->compat; compat->supported; compat++)
-		for (j = 0; j < compat->num_interfaces; j++)
-			__set_bit(compat->interface[j], interfaces);
+		__set_bit(compat->interface, interfaces);
 }
 EXPORT_SYMBOL_GPL(xpcs_get_interfaces);
 
@@ -1285,39 +1252,32 @@ static int xpcs_get_id(struct dw_xpcs *xpcs)
 
 static const struct dw_xpcs_compat synopsys_xpcs_compat[] = {
 	{
+		.interface = PHY_INTERFACE_MODE_USXGMII,
 		.supported = xpcs_usxgmii_features,
-		.interface = xpcs_usxgmii_interfaces,
-		.num_interfaces = ARRAY_SIZE(xpcs_usxgmii_interfaces),
 		.an_mode = DW_AN_C73,
 	}, {
+		.interface = PHY_INTERFACE_MODE_10GKR,
 		.supported = xpcs_10gkr_features,
-		.interface = xpcs_10gkr_interfaces,
-		.num_interfaces = ARRAY_SIZE(xpcs_10gkr_interfaces),
 		.an_mode = DW_AN_C73,
 	}, {
+		.interface = PHY_INTERFACE_MODE_XLGMII,
 		.supported = xpcs_xlgmii_features,
-		.interface = xpcs_xlgmii_interfaces,
-		.num_interfaces = ARRAY_SIZE(xpcs_xlgmii_interfaces),
 		.an_mode = DW_AN_C73,
 	}, {
+		.interface = PHY_INTERFACE_MODE_10GBASER,
 		.supported = xpcs_10gbaser_features,
-		.interface = xpcs_10gbaser_interfaces,
-		.num_interfaces = ARRAY_SIZE(xpcs_10gbaser_interfaces),
 		.an_mode = DW_10GBASER,
 	}, {
+		.interface = PHY_INTERFACE_MODE_SGMII,
 		.supported = xpcs_sgmii_features,
-		.interface = xpcs_sgmii_interfaces,
-		.num_interfaces = ARRAY_SIZE(xpcs_sgmii_interfaces),
 		.an_mode = DW_AN_C37_SGMII,
 	}, {
+		.interface = PHY_INTERFACE_MODE_1000BASEX,
 		.supported = xpcs_1000basex_features,
-		.interface = xpcs_1000basex_interfaces,
-		.num_interfaces = ARRAY_SIZE(xpcs_1000basex_interfaces),
 		.an_mode = DW_AN_C37_1000BASEX,
 	}, {
+		.interface = PHY_INTERFACE_MODE_2500BASEX,
 		.supported = xpcs_2500basex_features,
-		.interface = xpcs_2500basex_interfaces,
-		.num_interfaces = ARRAY_SIZE(xpcs_2500basex_interfaces),
 		.an_mode = DW_2500BASEX,
 	}, {
 	}
@@ -1325,9 +1285,8 @@ static const struct dw_xpcs_compat synopsys_xpcs_compat[] = {
 
 static const struct dw_xpcs_compat nxp_sja1105_xpcs_compat[] = {
 	{
+		.interface = PHY_INTERFACE_MODE_SGMII,
 		.supported = xpcs_sgmii_features,
-		.interface = xpcs_sgmii_interfaces,
-		.num_interfaces = ARRAY_SIZE(xpcs_sgmii_interfaces),
 		.an_mode = DW_AN_C37_SGMII,
 		.pma_config = nxp_sja1105_sgmii_pma_config,
 	}, {
@@ -1336,15 +1295,13 @@ static const struct dw_xpcs_compat nxp_sja1105_xpcs_compat[] = {
 
 static const struct dw_xpcs_compat nxp_sja1110_xpcs_compat[] = {
 	{
+		.interface = PHY_INTERFACE_MODE_SGMII,
 		.supported = xpcs_sgmii_features,
-		.interface = xpcs_sgmii_interfaces,
-		.num_interfaces = ARRAY_SIZE(xpcs_sgmii_interfaces),
 		.an_mode = DW_AN_C37_SGMII,
 		.pma_config = nxp_sja1110_sgmii_pma_config,
 	}, {
+		.interface = PHY_INTERFACE_MODE_2500BASEX,
 		.supported = xpcs_2500basex_features,
-		.interface = xpcs_2500basex_interfaces,
-		.num_interfaces = ARRAY_SIZE(xpcs_2500basex_interfaces),
 		.an_mode = DW_2500BASEX,
 		.pma_config = nxp_sja1110_2500basex_pma_config,
 	}, {

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:37 +0100

xpcs_find_compat() is now always passed xpcs->id. Rather than always
dereferencing this in the caller, move it into xpcs_find_compat(),
thus making this function consistent with most of the other xpcs
functions in taking an xpcs pointer.

Signed-off-by: Russell King (Oracle) <[email-masked]>
Signed-off-by: David S. Miller <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index 4fbf7c816ed5..8bde87ab971f 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -121,11 +121,11 @@ struct dw_xpcs_desc {
 };
 
 static const struct dw_xpcs_compat *
-xpcs_find_compat(const struct dw_xpcs_desc *desc, phy_interface_t interface)
+xpcs_find_compat(struct dw_xpcs *xpcs, phy_interface_t interface)
 {
 	const struct dw_xpcs_compat *compat;
 
-	for (compat = desc->compat; compat->supported; compat++)
+	for (compat = xpcs->desc->compat; compat->supported; compat++)
 		if (compat->interface == interface)
 			return compat;
 
@@ -136,7 +136,7 @@ int xpcs_get_an_mode(struct dw_xpcs *xpcs, phy_interface_t interface)
 {
 	const struct dw_xpcs_compat *compat;
 
-	compat = xpcs_find_compat(xpcs->desc, interface);
+	compat = xpcs_find_compat(xpcs, interface);
 	if (!compat)
 		return -ENODEV;
 
@@ -548,7 +548,7 @@ static int xpcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
 	int i;
 
 	xpcs = phylink_pcs_to_xpcs(pcs);
-	compat = xpcs_find_compat(xpcs->desc, state->interface);
+	compat = xpcs_find_compat(xpcs, state->interface);
 	if (!compat)
 		return -EINVAL;
 
@@ -620,7 +620,7 @@ static void xpcs_pre_config(struct phylink_pcs *pcs, phy_interface_t interface)
 	if (!xpcs->need_reset)
 		return;
 
-	compat = xpcs_find_compat(xpcs->desc, interface);
+	compat = xpcs_find_compat(xpcs, interface);
 	if (!compat) {
 		dev_err(&xpcs->mdiodev->dev, "unsupported interface %s\n",
 			phy_modes(interface));
@@ -810,7 +810,7 @@ static int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface,
 	const struct dw_xpcs_compat *compat;
 	int ret;
 
-	compat = xpcs_find_compat(xpcs->desc, interface);
+	compat = xpcs_find_compat(xpcs, interface);
 	if (!compat)
 		return -ENODEV;
 
@@ -1074,7 +1074,7 @@ static void xpcs_get_state(struct phylink_pcs *pcs,
 	const struct dw_xpcs_compat *compat;
 	int ret;
 
-	compat = xpcs_find_compat(xpcs->desc, state->interface);
+	compat = xpcs_find_compat(xpcs, state->interface);
 	if (!compat)
 		return;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:37 +0100

Provide a helper to provide the pointer to the phylink_pcs struct
given a valid xpcs pointer. This will be necessary when we make
struct dw_xpcs private to pcs-xpcs.c

Signed-off-by: Russell King (Oracle) <[email-masked]>
Signed-off-by: David S. Miller <[email-masked]>
---
 drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c | 2 +-
 drivers/net/pcs/pcs-xpcs.c                        | 6 ++++++
 include/linux/pcs/pcs-xpcs.h                      | 1 +
 3 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
index 83ad7c7935e3..48acba5eb178 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
@@ -451,7 +451,7 @@ static struct phylink_pcs *intel_mgbe_select_pcs(struct stmmac_priv *priv,
 	 * should always be an XPCS. The original code would always
 	 * return this if present.
 	 */
-	return &priv->hw->xpcs->pcs;
+	return xpcs_to_phylink_pcs(priv->hw->xpcs);
 }
 
 static int intel_mgbe_common_data(struct pci_dev *pdev,
diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index 8bde87ab971f..a7f6d56183a7 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -132,6 +132,12 @@ xpcs_find_compat(struct dw_xpcs *xpcs, phy_interface_t interface)
 	return NULL;
 }
 
+struct phylink_pcs *xpcs_to_phylink_pcs(struct dw_xpcs *xpcs)
+{
+	return &xpcs->pcs;
+}
+EXPORT_SYMBOL_GPL(xpcs_to_phylink_pcs);
+
 int xpcs_get_an_mode(struct dw_xpcs *xpcs, phy_interface_t interface)
 {
 	const struct dw_xpcs_compat *compat;
diff --git a/include/linux/pcs/pcs-xpcs.h b/include/linux/pcs/pcs-xpcs.h
index abda475111d1..868515f3cc88 100644
--- a/include/linux/pcs/pcs-xpcs.h
+++ b/include/linux/pcs/pcs-xpcs.h
@@ -64,6 +64,7 @@ struct dw_xpcs {
 	bool need_reset;
 };
 
+struct phylink_pcs *xpcs_to_phylink_pcs(struct dw_xpcs *xpcs);
 int xpcs_get_an_mode(struct dw_xpcs *xpcs, phy_interface_t interface);
 void xpcs_get_interfaces(struct dw_xpcs *xpcs, unsigned long *interfaces);
 int xpcs_config_eee(struct dw_xpcs *xpcs, int mult_fact_100ns,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:37 +0100

There should be no reason for anything outside the XPCS code to know
the contents of struct dw_xpcs - this is a private structure to XPCS.
Move the definition to the private pcs-xpcs.h header, leaving a
declaration in the global pcs/pcs-xpcs.h

Signed-off-by: Russell King (Oracle) <[email-masked]>
Signed-off-by: David S. Miller <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.h   | 18 ++++++++++++++++++
 include/linux/pcs/pcs-xpcs.h | 18 +-----------------
 2 files changed, 19 insertions(+), 17 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs.h b/drivers/net/pcs/pcs-xpcs.h
index fa05adfae220..1b546eae8280 100644
--- a/drivers/net/pcs/pcs-xpcs.h
+++ b/drivers/net/pcs/pcs-xpcs.h
@@ -123,6 +123,24 @@
 #define DW_XPCS_INFO_DECLARE(_name, _pcs, _pma)				\
 	static const struct dw_xpcs_info _name = { .pcs = _pcs, .pma = _pma }
 
+struct dw_xpcs_desc;
+
+enum dw_xpcs_clock {
+	DW_XPCS_CORE_CLK,
+	DW_XPCS_PAD_CLK,
+	DW_XPCS_NUM_CLKS,
+};
+
+struct dw_xpcs {
+	struct dw_xpcs_info info;
+	const struct dw_xpcs_desc *desc;
+	struct mdio_device *mdiodev;
+	struct clk_bulk_data clks[DW_XPCS_NUM_CLKS];
+	struct phylink_pcs pcs;
+	phy_interface_t interface;
+	bool need_reset;
+};
+
 int xpcs_read(struct dw_xpcs *xpcs, int dev, u32 reg);
 int xpcs_write(struct dw_xpcs *xpcs, int dev, u32 reg, u16 val);
 int xpcs_read_vpcs(struct dw_xpcs *xpcs, int reg);
diff --git a/include/linux/pcs/pcs-xpcs.h b/include/linux/pcs/pcs-xpcs.h
index 868515f3cc88..b5b5d17998b8 100644
--- a/include/linux/pcs/pcs-xpcs.h
+++ b/include/linux/pcs/pcs-xpcs.h
@@ -21,8 +21,6 @@
 #define DW_AN_C37_1000BASEX		4
 #define DW_10GBASER			5
 
-struct dw_xpcs_desc;
-
 enum dw_xpcs_pcs_id {
 	DW_XPCS_ID_NATIVE = 0,
 	NXP_SJA1105_XPCS_ID = 0x00000010,
@@ -48,21 +46,7 @@ struct dw_xpcs_info {
 	u32 pma;
 };
 
-enum dw_xpcs_clock {
-	DW_XPCS_CORE_CLK,
-	DW_XPCS_PAD_CLK,
-	DW_XPCS_NUM_CLKS,
-};
-
-struct dw_xpcs {
-	struct dw_xpcs_info info;
-	const struct dw_xpcs_desc *desc;
-	struct mdio_device *mdiodev;
-	struct clk_bulk_data clks[DW_XPCS_NUM_CLKS];
-	struct phylink_pcs pcs;
-	phy_interface_t interface;
-	bool need_reset;
-};
+struct dw_xpcs;
 
 struct phylink_pcs *xpcs_to_phylink_pcs(struct dw_xpcs *xpcs);
 int xpcs_get_an_mode(struct dw_xpcs *xpcs, phy_interface_t interface);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:37 +0100

Rename xpcs_get_id() to xpcs_read_id() which more closely reflects
the purpose of this function.

Signed-off-by: Russell King (Oracle) <[email-masked]>
Signed-off-by: David S. Miller <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index a7f6d56183a7..db3f50f195ab 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -1190,7 +1190,7 @@ static void xpcs_an_restart(struct phylink_pcs *pcs)
 	}
 }
 
-static int xpcs_get_id(struct dw_xpcs *xpcs)
+static int xpcs_read_ids(struct dw_xpcs *xpcs)
 {
 	int ret;
 	u32 id;
@@ -1405,7 +1405,7 @@ static int xpcs_init_id(struct dw_xpcs *xpcs)
 		xpcs->info = *info;
 	}
 
-	ret = xpcs_get_id(xpcs);
+	ret = xpcs_read_ids(xpcs);
 	if (ret < 0)
 		return ret;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:37 +0100

Move the searching of the physical ID out of xpcs_create() and into
its own xpcs_identify() function, which makes it self contained.
This reduces the complexity in xpcs_craete(), making it easier to
follow, rather than having a lot of once-run code in the big for()
loop.

Signed-off-by: Russell King (Oracle) <[email-masked]>
Signed-off-by: David S. Miller <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.c | 41 +++++++++++++++++++++--------------------
 1 file changed, 21 insertions(+), 20 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index db3f50f195ab..805856cabba1 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -1339,6 +1339,26 @@ static const struct phylink_pcs_ops xpcs_phylink_ops = {
 	.pcs_link_up = xpcs_link_up,
 };
 
+static int xpcs_identify(struct dw_xpcs *xpcs)
+{
+	int i, ret;
+
+	ret = xpcs_read_ids(xpcs);
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < ARRAY_SIZE(xpcs_desc_list); i++) {
+		const struct dw_xpcs_desc *entry = &xpcs_desc_list[i];
+
+		if ((xpcs->info.pcs & entry->mask) == entry->id) {
+			xpcs->desc = entry;
+			return 0;
+		}
+	}
+
+	return -ENODEV;
+}
+
 static struct dw_xpcs *xpcs_create_data(struct mdio_device *mdiodev)
 {
 	struct dw_xpcs *xpcs;
@@ -1395,7 +1415,6 @@ static void xpcs_clear_clks(struct dw_xpcs *xpcs)
 static int xpcs_init_id(struct dw_xpcs *xpcs)
 {
 	const struct dw_xpcs_info *info;
-	int i, ret;
 
 	info = dev_get_platdata(&xpcs->mdiodev->dev);
 	if (!info) {
@@ -1405,25 +1424,7 @@ static int xpcs_init_id(struct dw_xpcs *xpcs)
 		xpcs->info = *info;
 	}
 
-	ret = xpcs_read_ids(xpcs);
-	if (ret < 0)
-		return ret;
-
-	for (i = 0; i < ARRAY_SIZE(xpcs_desc_list); i++) {
-		const struct dw_xpcs_desc *desc = &xpcs_desc_list[i];
-
-		if ((xpcs->info.pcs & desc->mask) != desc->id)
-			continue;
-
-		xpcs->desc = desc;
-
-		break;
-	}
-
-	if (!xpcs->desc)
-		return -ENODEV;
-
-	return 0;
+	return xpcs_identify(xpcs);
 }

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:37 +0100

Convert xpcs to use the bitfield macros rather than definining the
bitfield shifts and open-coding the insertion and extraction of these
bitfields.

Signed-off-by: Russell King (Oracle) <[email-masked]>
Signed-off-by: David S. Miller <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.c | 14 ++++++--------
 drivers/net/pcs/pcs-xpcs.h |  4 ----
 2 files changed, 6 insertions(+), 12 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index 805856cabba1..f55bc180c624 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -592,7 +592,8 @@ int xpcs_config_eee(struct dw_xpcs *xpcs, int mult_fact_100ns, int enable)
 		ret = DW_VR_MII_EEE_LTX_EN | DW_VR_MII_EEE_LRX_EN |
 		      DW_VR_MII_EEE_TX_QUIET_EN | DW_VR_MII_EEE_RX_QUIET_EN |
 		      DW_VR_MII_EEE_TX_EN_CTRL | DW_VR_MII_EEE_RX_EN_CTRL |
-		      mult_fact_100ns << DW_VR_MII_EEE_MULT_FACT_100NS_SHIFT;
+		      FIELD_PREP(DW_VR_MII_EEE_MULT_FACT_100NS,
+				 mult_fact_100ns);
 	} else {
 		ret &= ~(DW_VR_MII_EEE_LTX_EN | DW_VR_MII_EEE_LRX_EN |
 		       DW_VR_MII_EEE_TX_QUIET_EN | DW_VR_MII_EEE_RX_QUIET_EN |
@@ -681,9 +682,8 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,
 		return ret;
 
 	ret &= ~(DW_VR_MII_PCS_MODE_MASK | DW_VR_MII_TX_CONFIG_MASK);
-	ret |= (DW_VR_MII_PCS_MODE_C37_SGMII <<
-		DW_VR_MII_AN_CTRL_PCS_MODE_SHIFT &
-		DW_VR_MII_PCS_MODE_MASK);
+	ret |= FIELD_PREP(DW_VR_MII_PCS_MODE_MASK,
+			  DW_VR_MII_PCS_MODE_C37_SGMII);
 	if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID) {
 		ret |= DW_VR_MII_AN_CTRL_8BIT;
 		/* Hardware requires it to be PHY side SGMII */
@@ -691,8 +691,7 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,
 	} else {
 		tx_conf = DW_VR_MII_TX_CONFIG_MAC_SIDE_SGMII;
 	}
-	ret |= tx_conf << DW_VR_MII_AN_CTRL_TX_CONFIG_SHIFT &
-		DW_VR_MII_TX_CONFIG_MASK;
+	ret |= FIELD_PREP(DW_VR_MII_TX_CONFIG_MASK, tx_conf);
 	ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL, ret);
 	if (ret < 0)
 		return ret;
@@ -971,8 +970,7 @@ static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs,
 
 		state->link = true;
 
-		speed_value = (ret & DW_VR_MII_AN_STS_C37_ANSGM_SP) >>
-			      DW_VR_MII_AN_STS_C37_ANSGM_SP_SHIFT;
+		speed_value = FIELD_GET(DW_VR_MII_AN_STS_C37_ANSGM_SP, ret);
 		if (speed_value == DW_VR_MII_C37_ANSGM_SP_1000)
 			state->speed = SPEED_1000;
 		else if (speed_value == DW_VR_MII_C37_ANSGM_SP_100)
diff --git a/drivers/net/pcs/pcs-xpcs.h b/drivers/net/pcs/pcs-xpcs.h
index 1b546eae8280..8902485730a2 100644
--- a/drivers/net/pcs/pcs-xpcs.h
+++ b/drivers/net/pcs/pcs-xpcs.h
@@ -77,11 +77,9 @@
 
 /* VR_MII_AN_CTRL */
 #define DW_VR_MII_AN_CTRL_8BIT			BIT(8)
-#define DW_VR_MII_AN_CTRL_TX_CONFIG_SHIFT	3
 #define DW_VR_MII_TX_CONFIG_MASK		BIT(3)
 #define DW_VR_MII_TX_CONFIG_PHY_SIDE_SGMII	0x1
 #define DW_VR_MII_TX_CONFIG_MAC_SIDE_SGMII	0x0
-#define DW_VR_MII_AN_CTRL_PCS_MODE_SHIFT	1
 #define DW_VR_MII_PCS_MODE_MASK			GENMASK(2, 1)
 #define DW_VR_MII_PCS_MODE_C37_1000BASEX	0x0
 #define DW_VR_MII_PCS_MODE_C37_SGMII		0x2
@@ -90,7 +88,6 @@
 /* VR_MII_AN_INTR_STS */
 #define DW_VR_MII_AN_STS_C37_ANCMPLT_INTR	BIT(0)
 #define DW_VR_MII_AN_STS_C37_ANSGM_FD		BIT(1)
-#define DW_VR_MII_AN_STS_C37_ANSGM_SP_SHIFT	2
 #define DW_VR_MII_AN_STS_C37_ANSGM_SP		GENMASK(3, 2)
 #define DW_VR_MII_C37_ANSGM_SP_10		0x0
 #define DW_VR_MII_C37_ANSGM_SP_100		0x1
@@ -114,7 +111,6 @@
 #define DW_VR_MII_EEE_TX_EN_CTRL		BIT(4)  /* Tx Control Enable */
 #define DW_VR_MII_EEE_RX_EN_CTRL		BIT(7)  /* Rx Control Enable */
 
-#define DW_VR_MII_EEE_MULT_FACT_100NS_SHIFT	8
 #define DW_VR_MII_EEE_MULT_FACT_100NS		GENMASK(11, 8)
 
 /* VR MII EEE Control 1 defines */

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:37 +0100

The xpcs driver does a lot of read-modify-write operations on
registers, which leads to long-winded code to read the register, check
whether the read was successful, modify the value in some way, and then
write it back.

We have a mdiodev _modify() accessor that encapsulates this, and does
the register modification under the MDIO bus lock ensuring that the
modification is atomic with respect to other bus operations. Convert
the xpcs driver to use this accessor.

Signed-off-by: Russell King (Oracle) <[email-masked]>
Signed-off-by: David S. Miller <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs-nxp.c |  24 ++----
 drivers/net/pcs/pcs-xpcs-wx.c  |  56 ++++++--------
 drivers/net/pcs/pcs-xpcs.c     | 165 ++++++++++++++++++-----------------------
 drivers/net/pcs/pcs-xpcs.h     |   1 +
 4 files changed, 104 insertions(+), 142 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs-nxp.c b/drivers/net/pcs/pcs-xpcs-nxp.c
index d16fc58cd48d..e8efe94cf4ec 100644
--- a/drivers/net/pcs/pcs-xpcs-nxp.c
+++ b/drivers/net/pcs/pcs-xpcs-nxp.c
@@ -152,26 +152,18 @@ static int nxp_sja1110_pma_config(struct dw_xpcs *xpcs,
 	/* Enable TX and RX PLLs and circuits.
 	 * Release reset of PMA to enable data flow to/from PCS.
 	 */
-	ret = xpcs_read(xpcs, MDIO_MMD_VEND2, SJA1110_POWERDOWN_ENABLE);
-	if (ret < 0)
-		return ret;
-
-	val = ret & ~(SJA1110_TXPLL_PD | SJA1110_TXPD | SJA1110_RXCH_PD |
-		      SJA1110_RXBIAS_PD | SJA1110_RESET_SER_EN |
-		      SJA1110_RESET_SER | SJA1110_RESET_DES);
-	val |= SJA1110_RXPKDETEN | SJA1110_RCVEN;
-
-	ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_POWERDOWN_ENABLE, val);
+	ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, SJA1110_POWERDOWN_ENABLE,
+			  SJA1110_TXPLL_PD | SJA1110_TXPD | SJA1110_RXCH_PD |
+			  SJA1110_RXBIAS_PD | SJA1110_RESET_SER_EN |
+			  SJA1110_RESET_SER | SJA1110_RESET_DES |
+			  SJA1110_RXPKDETEN | SJA1110_RCVEN,
+			  SJA1110_RXPKDETEN | SJA1110_RCVEN);
 	if (ret < 0)
 		return ret;
 
 	/* Program continuous-time linear equalizer (CTLE) settings. */
-	ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_RX_CDR_CTLE,
-			 rx_cdr_ctle);
-	if (ret < 0)
-		return ret;
-
-	return 0;
+	return xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_RX_CDR_CTLE,
+			  rx_cdr_ctle);
 }
 
 int nxp_sja1110_sgmii_pma_config(struct dw_xpcs *xpcs)
diff --git a/drivers/net/pcs/pcs-xpcs-wx.c b/drivers/net/pcs/pcs-xpcs-wx.c
index 5f5cd3596cb8..fc52f7aa5f59 100644
--- a/drivers/net/pcs/pcs-xpcs-wx.c
+++ b/drivers/net/pcs/pcs-xpcs-wx.c
@@ -46,25 +46,23 @@
 #define TXGBE_VCO_CAL_LD0		0x72
 #define TXGBE_VCO_CAL_REF0		0x76
 
-static int txgbe_read_pma(struct dw_xpcs *xpcs, int reg)
+static int txgbe_write_pma(struct dw_xpcs *xpcs, int reg, u16 val)
 {
-	return xpcs_read(xpcs, MDIO_MMD_PMAPMD, TXGBE_PMA_MMD + reg);
+	return xpcs_write(xpcs, MDIO_MMD_PMAPMD, TXGBE_PMA_MMD + reg, val);
 }
 
-static int txgbe_write_pma(struct dw_xpcs *xpcs, int reg, u16 val)
+static int txgbe_modify_pma(struct dw_xpcs *xpcs, int reg, u16 mask, u16 set)
 {
-	return xpcs_write(xpcs, MDIO_MMD_PMAPMD, TXGBE_PMA_MMD + reg, val);
+	return xpcs_modify(xpcs, MDIO_MMD_PMAPMD, TXGBE_PMA_MMD + reg, mask,
+			   set);
 }
 
 static void txgbe_pma_config_10gbaser(struct dw_xpcs *xpcs)
 {
-	int val;
-
 	txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL0, 0x21);
 	txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL3, 0);
-	val = txgbe_read_pma(xpcs, TXGBE_TX_GENCTL1);
-	val = u16_replace_bits(val, 0x5, TXGBE_TX_GENCTL1_VBOOST_LVL);
-	txgbe_write_pma(xpcs, TXGBE_TX_GENCTL1, val);
+	txgbe_modify_pma(xpcs, TXGBE_TX_GENCTL1, TXGBE_TX_GENCTL1_VBOOST_LVL,
+			 FIELD_PREP(TXGBE_TX_GENCTL1_VBOOST_LVL, 0x5));
 	txgbe_write_pma(xpcs, TXGBE_MISC_CTL0, TXGBE_MISC_CTL0_PLL |
 			TXGBE_MISC_CTL0_CR_PARA_SEL | TXGBE_MISC_CTL0_RX_VREF(0xF));
 	txgbe_write_pma(xpcs, TXGBE_VCO_CAL_LD0, 0x549);
@@ -78,38 +76,29 @@ static void txgbe_pma_config_10gbaser(struct dw_xpcs *xpcs)
 
 	txgbe_write_pma(xpcs, TXGBE_RX_EQ_CTL0, TXGBE_RX_EQ_CTL0_CTLE_POLE(2) |
 			TXGBE_RX_EQ_CTL0_CTLE_BOOST(5));
-	val = txgbe_read_pma(xpcs, TXGBE_RX_EQ_ATTN_CTL);
-	val &= ~TXGBE_RX_EQ_ATTN_LVL0;
-	txgbe_write_pma(xpcs, TXGBE_RX_EQ_ATTN_CTL, val);
+	txgbe_modify_pma(xpcs, TXGBE_RX_EQ_ATTN_CTL, TXGBE_RX_EQ_ATTN_LVL0, 0);
 	txgbe_write_pma(xpcs, TXGBE_DFE_TAP_CTL0, 0xBE);
-	val = txgbe_read_pma(xpcs, TXGBE_AFE_DFE_ENABLE);
-	val &= ~(TXGBE_DFE_EN_0 | TXGBE_AFE_EN_0);
-	txgbe_write_pma(xpcs, TXGBE_AFE_DFE_ENABLE, val);
-	val = txgbe_read_pma(xpcs, TXGBE_RX_EQ_CTL4);
-	val &= ~TXGBE_RX_EQ_CTL4_CONT_ADAPT0;
-	txgbe_write_pma(xpcs, TXGBE_RX_EQ_CTL4, val);
+	txgbe_modify_pma(xpcs, TXGBE_AFE_DFE_ENABLE,
+			 TXGBE_DFE_EN_0 | TXGBE_AFE_EN_0, 0);
+	txgbe_modify_pma(xpcs, TXGBE_RX_EQ_CTL4, TXGBE_RX_EQ_CTL4_CONT_ADAPT0,
+			 0);
 }
 
 static void txgbe_pma_config_1g(struct dw_xpcs *xpcs)
 {
-	int val;
-
-	val = txgbe_read_pma(xpcs, TXGBE_TX_GENCTL1);
-	val = u16_replace_bits(val, 0x5, TXGBE_TX_GENCTL1_VBOOST_LVL);
-	val &= ~TXGBE_TX_GENCTL1_VBOOST_EN0;
-	txgbe_write_pma(xpcs, TXGBE_TX_GENCTL1, val);
+	txgbe_modify_pma(xpcs, TXGBE_TX_GENCTL1,
+			 TXGBE_TX_GENCTL1_VBOOST_LVL |
+			 TXGBE_TX_GENCTL1_VBOOST_EN0,
+			 FIELD_PREP(TXGBE_TX_GENCTL1_VBOOST_LVL, 0x5));
 	txgbe_write_pma(xpcs, TXGBE_MISC_CTL0, TXGBE_MISC_CTL0_PLL |
 			TXGBE_MISC_CTL0_CR_PARA_SEL | TXGBE_MISC_CTL0_RX_VREF(0xF));
 
 	txgbe_write_pma(xpcs, TXGBE_RX_EQ_CTL0, TXGBE_RX_EQ_CTL0_VGA1_GAIN(7) |
 			TXGBE_RX_EQ_CTL0_VGA2_GAIN(7) | TXGBE_RX_EQ_CTL0_CTLE_BOOST(6));
-	val = txgbe_read_pma(xpcs, TXGBE_RX_EQ_ATTN_CTL);
-	val &= ~TXGBE_RX_EQ_ATTN_LVL0;
-	txgbe_write_pma(xpcs, TXGBE_RX_EQ_ATTN_CTL, val);
+	txgbe_modify_pma(xpcs, TXGBE_RX_EQ_ATTN_CTL, TXGBE_RX_EQ_ATTN_LVL0, 0);
 	txgbe_write_pma(xpcs, TXGBE_DFE_TAP_CTL0, 0);
-	val = txgbe_read_pma(xpcs, TXGBE_RX_GEN_CTL3);
-	val = u16_replace_bits(val, 0x4, TXGBE_RX_GEN_CTL3_LOS_TRSHLD0);
-	txgbe_write_pma(xpcs, TXGBE_RX_GEN_CTL3, val);
+	txgbe_modify_pma(xpcs, TXGBE_RX_GEN_CTL3, TXGBE_RX_GEN_CTL3_LOS_TRSHLD0,
+			 FIELD_PREP(TXGBE_RX_GEN_CTL3_LOS_TRSHLD0, 0x4));
 
 	txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL0, 0x20);
 	txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL3, 0x46);
@@ -172,7 +161,7 @@ static bool txgbe_xpcs_mode_quirk(struct dw_xpcs *xpcs)
 
 int txgbe_xpcs_switch_mode(struct dw_xpcs *xpcs, phy_interface_t interface)
 {
-	int val, ret;
+	int ret;
 
 	switch (interface) {
 	case PHY_INTERFACE_MODE_10GBASER:
@@ -194,9 +183,8 @@ int txgbe_xpcs_switch_mode(struct dw_xpcs *xpcs, phy_interface_t interface)
 
 	if (interface == PHY_INTERFACE_MODE_10GBASER) {
 		xpcs_write(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, MDIO_PCS_CTRL2_10GBR);
-		val = xpcs_read(xpcs, MDIO_MMD_PMAPMD, MDIO_CTRL1);
-		val |= MDIO_CTRL1_SPEED10G;
-		xpcs_write(xpcs, MDIO_MMD_PMAPMD, MDIO_CTRL1, val);
+		xpcs_modify(xpcs, MDIO_MMD_PMAPMD, MDIO_CTRL1,
+			    MDIO_CTRL1_SPEED10G, MDIO_CTRL1_SPEED10G);
 		txgbe_pma_config_10gbaser(xpcs);
 	} else {
 		xpcs_write(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, MDIO_PCS_CTRL2_10GBX);
diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index f55bc180c624..5ac8262ac264 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -175,6 +175,11 @@ int xpcs_write(struct dw_xpcs *xpcs, int dev, u32 reg, u16 val)
 	return mdiodev_c45_write(xpcs->mdiodev, dev, reg, val);
 }
 
+int xpcs_modify(struct dw_xpcs *xpcs, int dev, u32 reg, u16 mask, u16 set)
+{
+	return mdiodev_c45_modify(xpcs->mdiodev, dev, reg, mask, set);
+}
+
 static int xpcs_modify_changed(struct dw_xpcs *xpcs, int dev, u32 reg,
 			       u16 mask, u16 set)
 {
@@ -192,6 +197,12 @@ static int xpcs_write_vendor(struct dw_xpcs *xpcs, int dev, int reg,
 	return xpcs_write(xpcs, dev, DW_VENDOR | reg, val);
 }
 
+static int xpcs_modify_vendor(struct dw_xpcs *xpcs, int dev, int reg, u16 mask,
+			      u16 set)
+{
+	return xpcs_modify(xpcs, dev, DW_VENDOR | reg, mask, set);
+}
+
 int xpcs_read_vpcs(struct dw_xpcs *xpcs, int reg)
 {
 	return xpcs_read_vendor(xpcs, MDIO_MMD_PCS, reg);
@@ -202,6 +213,11 @@ int xpcs_write_vpcs(struct dw_xpcs *xpcs, int reg, u16 val)
 	return xpcs_write_vendor(xpcs, MDIO_MMD_PCS, reg, val);
 }
 
+static int xpcs_modify_vpcs(struct dw_xpcs *xpcs, int reg, u16 mask, u16 val)
+{
+	return xpcs_modify_vendor(xpcs, MDIO_MMD_PCS, reg, mask, val);
+}
+
 static int xpcs_poll_reset(struct dw_xpcs *xpcs, int dev)
 {
 	/* Poll until the reset bit clears (50ms per retry == 0.6 sec) */
@@ -326,30 +342,17 @@ static void xpcs_config_usxgmii(struct dw_xpcs *xpcs, int speed)
 		return;
 	}
 
-	ret = xpcs_read_vpcs(xpcs, MDIO_CTRL1);
+	ret = xpcs_modify_vpcs(xpcs, MDIO_CTRL1, DW_USXGMII_EN, DW_USXGMII_EN);
 	if (ret < 0)
 		goto out;
 
-	ret = xpcs_write_vpcs(xpcs, MDIO_CTRL1, ret | DW_USXGMII_EN);
+	ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1, DW_USXGMII_SS_MASK,
+			  speed_sel | DW_USXGMII_FULL);
 	if (ret < 0)
 		goto out;
 
-	ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1);
-	if (ret < 0)
-		goto out;
-
-	ret &= ~DW_USXGMII_SS_MASK;
-	ret |= speed_sel | DW_USXGMII_FULL;
-
-	ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1, ret);
-	if (ret < 0)
-		goto out;
-
-	ret = xpcs_read_vpcs(xpcs, MDIO_CTRL1);
-	if (ret < 0)
-		goto out;
-
-	ret = xpcs_write_vpcs(xpcs, MDIO_CTRL1, ret | DW_USXGMII_RST);
+	ret = xpcs_modify_vpcs(xpcs, MDIO_CTRL1, DW_USXGMII_RST,
+			       DW_USXGMII_RST);
 	if (ret < 0)
 		goto out;
 
@@ -413,13 +416,9 @@ static int xpcs_config_aneg_c73(struct dw_xpcs *xpcs,
 	if (ret < 0)
 		return ret;
 
-	ret = xpcs_read(xpcs, MDIO_MMD_AN, MDIO_CTRL1);
-	if (ret < 0)
-		return ret;
-
-	ret |= MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART;
-
-	return xpcs_write(xpcs, MDIO_MMD_AN, MDIO_CTRL1, ret);
+	return xpcs_modify(xpcs, MDIO_MMD_AN, MDIO_CTRL1,
+			   MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART,
+			   MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART);
 }
 
 static int xpcs_aneg_done_c73(struct dw_xpcs *xpcs,
@@ -581,40 +580,31 @@ EXPORT_SYMBOL_GPL(xpcs_get_interfaces);
 
 int xpcs_config_eee(struct dw_xpcs *xpcs, int mult_fact_100ns, int enable)
 {
+	u16 mask, val;
 	int ret;
 
-	ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL0);
-	if (ret < 0)
-		return ret;
+	mask = DW_VR_MII_EEE_LTX_EN | DW_VR_MII_EEE_LRX_EN |
+	       DW_VR_MII_EEE_TX_QUIET_EN | DW_VR_MII_EEE_RX_QUIET_EN |
+	       DW_VR_MII_EEE_TX_EN_CTRL | DW_VR_MII_EEE_RX_EN_CTRL |
+	       DW_VR_MII_EEE_MULT_FACT_100NS;
 
-	if (enable) {
-	/* Enable EEE */
-		ret = DW_VR_MII_EEE_LTX_EN | DW_VR_MII_EEE_LRX_EN |
+	if (enable)
+		val = DW_VR_MII_EEE_LTX_EN | DW_VR_MII_EEE_LRX_EN |
 		      DW_VR_MII_EEE_TX_QUIET_EN | DW_VR_MII_EEE_RX_QUIET_EN |
 		      DW_VR_MII_EEE_TX_EN_CTRL | DW_VR_MII_EEE_RX_EN_CTRL |
 		      FIELD_PREP(DW_VR_MII_EEE_MULT_FACT_100NS,
 				 mult_fact_100ns);
-	} else {
-		ret &= ~(DW_VR_MII_EEE_LTX_EN | DW_VR_MII_EEE_LRX_EN |
-		       DW_VR_MII_EEE_TX_QUIET_EN | DW_VR_MII_EEE_RX_QUIET_EN |
-		       DW_VR_MII_EEE_TX_EN_CTRL | DW_VR_MII_EEE_RX_EN_CTRL |
-		       DW_VR_MII_EEE_MULT_FACT_100NS);
-	}
-
-	ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL0, ret);
-	if (ret < 0)
-		return ret;
+	else
+		val = 0;
 
-	ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL1);
+	ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL0, mask,
+			  val);
 	if (ret < 0)
 		return ret;
 
-	if (enable)
-		ret |= DW_VR_MII_EEE_TRN_LPI;
-	else
-		ret &= ~DW_VR_MII_EEE_TRN_LPI;
-
-	return xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL1, ret);
+	return xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL1,
+			   DW_VR_MII_EEE_TRN_LPI,
+			   enable ? DW_VR_MII_EEE_TRN_LPI : 0);
 }
 EXPORT_SYMBOL_GPL(xpcs_config_eee);
 
@@ -646,6 +636,7 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,
 				      unsigned int neg_mode)
 {
 	int ret, mdio_ctrl, tx_conf;
+	u16 mask, val;
 
 	if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID)
 		xpcs_write_vpcs(xpcs, DW_VR_XS_PCS_DIG_CTRL1, DW_CL37_BP | DW_EN_VSMMD1);
@@ -677,38 +668,35 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,
 			return ret;
 	}
 
-	ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL);
-	if (ret < 0)
-		return ret;
+	mask = DW_VR_MII_PCS_MODE_MASK | DW_VR_MII_TX_CONFIG_MASK;
+	val = FIELD_PREP(DW_VR_MII_PCS_MODE_MASK,
+			 DW_VR_MII_PCS_MODE_C37_SGMII);
 
-	ret &= ~(DW_VR_MII_PCS_MODE_MASK | DW_VR_MII_TX_CONFIG_MASK);
-	ret |= FIELD_PREP(DW_VR_MII_PCS_MODE_MASK,
-			  DW_VR_MII_PCS_MODE_C37_SGMII);
 	if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID) {
-		ret |= DW_VR_MII_AN_CTRL_8BIT;
+		mask |= DW_VR_MII_AN_CTRL_8BIT;
+		val |= DW_VR_MII_AN_CTRL_8BIT;
 		/* Hardware requires it to be PHY side SGMII */
 		tx_conf = DW_VR_MII_TX_CONFIG_PHY_SIDE_SGMII;
 	} else {
 		tx_conf = DW_VR_MII_TX_CONFIG_MAC_SIDE_SGMII;
 	}
-	ret |= FIELD_PREP(DW_VR_MII_TX_CONFIG_MASK, tx_conf);
-	ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL, ret);
-	if (ret < 0)
-		return ret;
 
-	ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1);
+	val |= FIELD_PREP(DW_VR_MII_TX_CONFIG_MASK, tx_conf);
+
+	ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL, mask, val);
 	if (ret < 0)
 		return ret;
 
+	mask = DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW;
 	if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
-		ret |= DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW;
-	else
-		ret &= ~DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW;
+		val = DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW;
 
-	if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID)
-		ret |= DW_VR_MII_DIG_CTRL1_PHY_MODE_CTRL;
+	if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID) {
+		mask |= DW_VR_MII_DIG_CTRL1_PHY_MODE_CTRL;
+		val |= DW_VR_MII_DIG_CTRL1_PHY_MODE_CTRL;
+	}
 
-	ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1, ret);
+	ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1, mask, val);
 	if (ret < 0)
 		return ret;
 
@@ -726,6 +714,7 @@ static int xpcs_config_aneg_c37_1000basex(struct dw_xpcs *xpcs,
 	phy_interface_t interface = PHY_INTERFACE_MODE_1000BASEX;
 	int ret, mdio_ctrl, adv;
 	bool changed = 0;
+	u16 mask, val;
 
 	if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID)
 		xpcs_write_vpcs(xpcs, DW_VR_XS_PCS_DIG_CTRL1, DW_CL37_BP | DW_EN_VSMMD1);
@@ -746,14 +735,16 @@ static int xpcs_config_aneg_c37_1000basex(struct dw_xpcs *xpcs,
 			return ret;
 	}
 
-	ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL);
-	if (ret < 0)
-		return ret;
+	mask = DW_VR_MII_PCS_MODE_MASK;
+	val = FIELD_PREP(DW_VR_MII_PCS_MODE_MASK,
+			 DW_VR_MII_PCS_MODE_C37_1000BASEX);
+
+	if (!xpcs->pcs.poll) {
+		mask |= DW_VR_MII_AN_INTR_EN;
+		val |= DW_VR_MII_AN_INTR_EN;
+	}
 
-	ret &= ~DW_VR_MII_PCS_MODE_MASK;
-	if (!xpcs->pcs.poll)
-		ret |= DW_VR_MII_AN_INTR_EN;
-	ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL, ret);
+	ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL, mask, val);
 	if (ret < 0)
 		return ret;
 
@@ -790,22 +781,16 @@ static int xpcs_config_2500basex(struct dw_xpcs *xpcs)
 {
 	int ret;
 
-	ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1);
-	if (ret < 0)
-		return ret;
-	ret |= DW_VR_MII_DIG_CTRL1_2G5_EN;
-	ret &= ~DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW;
-	ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1, ret);
+	ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1,
+			  DW_VR_MII_DIG_CTRL1_2G5_EN |
+			  DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW,
+			  DW_VR_MII_DIG_CTRL1_2G5_EN);
 	if (ret < 0)
 		return ret;
 
-	ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL);
-	if (ret < 0)
-		return ret;
-	ret &= ~AN_CL37_EN;
-	ret |= SGMII_SPEED_SS6;
-	ret &= ~SGMII_SPEED_SS13;
-	return xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL, ret);
+	return xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL,
+			   AN_CL37_EN | SGMII_SPEED_SS6 | SGMII_SPEED_SS13,
+			   SGMII_SPEED_SS6);
 }
 
 static int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface,
@@ -1179,13 +1164,9 @@ static void xpcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
 static void xpcs_an_restart(struct phylink_pcs *pcs)
 {
 	struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
-	int ret;
 
-	ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1);
-	if (ret >= 0) {
-		ret |= BMCR_ANRESTART;
-		xpcs_write(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1, ret);
-	}
+	xpcs_modify(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1, BMCR_ANRESTART,
+		    BMCR_ANRESTART);
 }
 
 static int xpcs_read_ids(struct dw_xpcs *xpcs)
diff --git a/drivers/net/pcs/pcs-xpcs.h b/drivers/net/pcs/pcs-xpcs.h
index 8902485730a2..b80b956ec286 100644
--- a/drivers/net/pcs/pcs-xpcs.h
+++ b/drivers/net/pcs/pcs-xpcs.h
@@ -139,6 +139,7 @@ struct dw_xpcs {
 
 int xpcs_read(struct dw_xpcs *xpcs, int dev, u32 reg);
 int xpcs_write(struct dw_xpcs *xpcs, int dev, u32 reg, u16 val);
+int xpcs_modify(struct dw_xpcs *xpcs, int dev, u32 reg, u16 mask, u16 set);
 int xpcs_read_vpcs(struct dw_xpcs *xpcs, int reg);
 int xpcs_write_vpcs(struct dw_xpcs *xpcs, int reg, u16 val);
 int nxp_sja1105_sgmii_pma_config(struct dw_xpcs *xpcs);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:37 +0100

Convert the xpcs driver to use read_poll_timeout() when waiting for
reset to complete, rather than open-coding this.

Signed-off-by: Russell King (Oracle) <[email-masked]>
Signed-off-by: David S. Miller <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.c | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index 5ac8262ac264..06a495135418 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -220,18 +220,15 @@ static int xpcs_modify_vpcs(struct dw_xpcs *xpcs, int reg, u16 mask, u16 val)
 
 static int xpcs_poll_reset(struct dw_xpcs *xpcs, int dev)
 {
-	/* Poll until the reset bit clears (50ms per retry == 0.6 sec) */
-	unsigned int retries = 12;
-	int ret;
+	int ret, val;
 
-	do {
-		msleep(50);
-		ret = xpcs_read(xpcs, dev, MDIO_CTRL1);
-		if (ret < 0)
-			return ret;
-	} while (ret & MDIO_CTRL1_RESET && --retries);
+	ret = read_poll_timeout(xpcs_read, val,
+				val < 0 || !(val & MDIO_CTRL1_RESET),
+				50000, 600000, true, xpcs, dev, MDIO_CTRL1);
+	if (val < 0)
+		ret = val;
 
-	return (ret & MDIO_CTRL1_RESET) ? -ETIMEDOUT : 0;
+	return ret;
 }
 
 static int xpcs_soft_reset(struct dw_xpcs *xpcs,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:37 +0100

Use the dev_*() family of functions to print all messages from the XPCS
driver so we know which instance issues the messages.

Signed-off-by: Russell King (Oracle) <[email-masked]>
Signed-off-by: David S. Miller <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.c | 44 ++++++++++++++++++++++----------------------
 1 file changed, 22 insertions(+), 22 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index 06a495135418..d6e63f091995 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -356,7 +356,8 @@ static void xpcs_config_usxgmii(struct dw_xpcs *xpcs, int speed)
 	return;
 
 out:
-	pr_err("%s: XPCS access returned %pe\n", __func__, ERR_PTR(ret));
+	dev_err(&xpcs->mdiodev->dev, "%s: XPCS access returned %pe\n",
+		__func__, ERR_PTR(ret));
 }
 
 static int _xpcs_config_aneg_c73(struct dw_xpcs *xpcs,
@@ -1070,32 +1071,27 @@ static void xpcs_get_state(struct phylink_pcs *pcs,
 		break;
 	case DW_AN_C73:
 		ret = xpcs_get_state_c73(xpcs, state, compat);
-		if (ret) {
-			pr_err("xpcs_get_state_c73 returned %pe\n",
-			       ERR_PTR(ret));
-			return;
-		}
+		if (ret)
+			dev_err(&xpcs->mdiodev->dev, "%s returned %pe\n",
+				"xpcs_get_state_c73", ERR_PTR(ret));
 		break;
 	case DW_AN_C37_SGMII:
 		ret = xpcs_get_state_c37_sgmii(xpcs, state);
-		if (ret) {
-			pr_err("xpcs_get_state_c37_sgmii returned %pe\n",
-			       ERR_PTR(ret));
-		}
+		if (ret)
+			dev_err(&xpcs->mdiodev->dev, "%s returned %pe\n",
+				"xpcs_get_state_c37_sgmii", ERR_PTR(ret));
 		break;
 	case DW_AN_C37_1000BASEX:
 		ret = xpcs_get_state_c37_1000basex(xpcs, state);
-		if (ret) {
-			pr_err("xpcs_get_state_c37_1000basex returned %pe\n",
-			       ERR_PTR(ret));
-		}
+		if (ret)
+			dev_err(&xpcs->mdiodev->dev, "%s returned %pe\n",
+				"xpcs_get_state_c37_1000basex", ERR_PTR(ret));
 		break;
 	case DW_2500BASEX:
 		ret = xpcs_get_state_2500basex(xpcs, state);
-		if (ret) {
-			pr_err("xpcs_get_state_2500basex returned %pe\n",
-			       ERR_PTR(ret));
-		}
+		if (ret)
+			dev_err(&xpcs->mdiodev->dev, "%s returned %pe\n",
+				"xpcs_get_state_2500basex", ERR_PTR(ret));
 		break;
 	default:
 		return;
@@ -1113,7 +1109,8 @@ static void xpcs_link_up_sgmii(struct dw_xpcs *xpcs, unsigned int neg_mode,
 	val = mii_bmcr_encode_fixed(speed, duplex);
 	ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1, val);
 	if (ret)
-		pr_err("%s: xpcs_write returned %pe\n", __func__, ERR_PTR(ret));
+		dev_err(&xpcs->mdiodev->dev, "%s: xpcs_write returned %pe\n",
+			__func__, ERR_PTR(ret));
 }
 
 static void xpcs_link_up_1000basex(struct dw_xpcs *xpcs, unsigned int neg_mode,
@@ -1131,18 +1128,21 @@ static void xpcs_link_up_1000basex(struct dw_xpcs *xpcs, unsigned int neg_mode,
 	case SPEED_100:
 	case SPEED_10:
 	default:
-		pr_err("%s: speed = %d\n", __func__, speed);
+		dev_err(&xpcs->mdiodev->dev, "%s: speed = %d\n",
+			__func__, speed);
 		return;
 	}
 
 	if (duplex == DUPLEX_FULL)
 		val |= BMCR_FULLDPLX;
 	else
-		pr_err("%s: half duplex not supported\n", __func__);
+		dev_err(&xpcs->mdiodev->dev, "%s: half duplex not supported\n",
+			__func__);
 
 	ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1, val);
 	if (ret)
-		pr_err("%s: xpcs_write returned %pe\n", __func__, ERR_PTR(ret));
+		dev_err(&xpcs->mdiodev->dev, "%s: xpcs_write returned %pe\n",
+			__func__, ERR_PTR(ret));
 }
 
 static void xpcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:38 +0100

Place DW_VR_MII_DIG_CTRL1_2G5_EN with the other DW_VR_MII_DIG_CTRL1
definitions rather than in the middle of a register list.

Signed-off-by: Russell King (Oracle) <[email-masked]>
Signed-off-by: David S. Miller <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.h | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs.h b/drivers/net/pcs/pcs-xpcs.h
index b80b956ec286..9a22eed4404d 100644
--- a/drivers/net/pcs/pcs-xpcs.h
+++ b/drivers/net/pcs/pcs-xpcs.h
@@ -60,8 +60,6 @@
 #define DW_VR_MII_DIG_CTRL1		0x8000
 #define DW_VR_MII_AN_CTRL		0x8001
 #define DW_VR_MII_AN_INTR_STS		0x8002
-/* Enable 2.5G Mode */
-#define DW_VR_MII_DIG_CTRL1_2G5_EN	BIT(2)
 /* EEE Mode Control Register */
 #define DW_VR_MII_EEE_MCTRL0		0x8006
 #define DW_VR_MII_EEE_MCTRL1		0x800b
@@ -69,6 +67,7 @@
 
 /* VR_MII_DIG_CTRL1 */
 #define DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW		BIT(9)
+#define DW_VR_MII_DIG_CTRL1_2G5_EN		BIT(2)
 #define DW_VR_MII_DIG_CTRL1_PHY_MODE_CTRL	BIT(0)
 
 /* VR_MII_DIG_CTRL2 */

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:38 +0100

According to commits 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs
for SGMII mode") and 2deea43f386d ("net: pcs: xpcs: add 1000BASE-X AN
interrupt support"), Wangxun devices need special VR_XS_PCS_DIG_CTRL1
settings for SGMII and 1000BASE-X. Both SGMII and 1000BASE-X use the
same settings.

Rather than placing these in the individual xpcs_config_*() functions,
move it to where we already test for the Wangxun devices in
xpcs_do_config().

Signed-off-by: Russell King (Oracle) <[email-masked]>
Signed-off-by: David S. Miller <[email-masked]>
---
 drivers/net/pcs/pcs-xpcs.c | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index d6e63f091995..c69421e80d19 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -636,9 +636,6 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,
 	int ret, mdio_ctrl, tx_conf;
 	u16 mask, val;
 
-	if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID)
-		xpcs_write_vpcs(xpcs, DW_VR_XS_PCS_DIG_CTRL1, DW_CL37_BP | DW_EN_VSMMD1);
-
 	/* For AN for C37 SGMII mode, the settings are :-
 	 * 1) VR_MII_MMD_CTRL Bit(12) [AN_ENABLE] = 0b (Disable SGMII AN in case
 	      it is already enabled)
@@ -714,9 +711,6 @@ static int xpcs_config_aneg_c37_1000basex(struct dw_xpcs *xpcs,
 	bool changed = 0;
 	u16 mask, val;
 
-	if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID)
-		xpcs_write_vpcs(xpcs, DW_VR_XS_PCS_DIG_CTRL1, DW_CL37_BP | DW_EN_VSMMD1);
-
 	/* According to Chap 7.12, to set 1000BASE-X C37 AN, AN must
 	 * be disabled first:-
 	 * 1) VR_MII_MMD_CTRL Bit(12)[AN_ENABLE] = 0b
@@ -806,6 +800,14 @@ static int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface,
 		ret = txgbe_xpcs_switch_mode(xpcs, interface);
 		if (ret)
 			return ret;
+
+		/* Wangxun devices need backplane CL37 AN enabled for
+		 * SGMII and 1000base-X
+		 */
+		if (interface == PHY_INTERFACE_MODE_SGMII ||
+		    interface == PHY_INTERFACE_MODE_1000BASEX)
+			xpcs_write_vpcs(xpcs, DW_VR_XS_PCS_DIG_CTRL1,
+					DW_CL37_BP | DW_EN_VSMMD1);
 	}
 
 	switch (compat->an_mode) {

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:38 +0100

No driver now uses the DSA switch phylink members, so we can now remove
the method pointers, but we need to leave empty shim functions to allow
those drivers that do not provide phylink MAC operations structure to
continue functioning.

Signed-off-by: Russell King (oracle) <[email-masked]>
Reviewed-by: Vladimir Oltean <[email-masked]>
Tested-by: Vladimir Oltean <[email-masked]> # sja1105, felix, dsa_loop
Link: https://patch.msgid.link/[email-masked]
Signed-off-by: Jakub Kicinski <[email-masked]>
---
 include/net/dsa.h | 15 ---------------
 net/dsa/dsa.c     |  8 --------
 net/dsa/port.c    | 34 +---------------------------------
 3 files changed, 1 insertion(+), 56 deletions(-)

diff --git a/include/net/dsa.h b/include/net/dsa.h
index d7a6c2930277..72ae65e7246a 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -885,21 +885,6 @@ struct dsa_switch_ops {
 	 */
 	void	(*phylink_get_caps)(struct dsa_switch *ds, int port,
 				    struct phylink_config *config);
-	struct phylink_pcs *(*phylink_mac_select_pcs)(struct dsa_switch *ds,
-						      int port,
-						      phy_interface_t iface);
-	void	(*phylink_mac_config)(struct dsa_switch *ds, int port,
-				      unsigned int mode,
-				      const struct phylink_link_state *state);
-	void	(*phylink_mac_link_down)(struct dsa_switch *ds, int port,
-					 unsigned int mode,
-					 phy_interface_t interface);
-	void	(*phylink_mac_link_up)(struct dsa_switch *ds, int port,
-				       unsigned int mode,
-				       phy_interface_t interface,
-				       struct phy_device *phydev,
-				       int speed, int duplex,
-				       bool tx_pause, bool rx_pause);
 	void	(*phylink_fixed_state)(struct dsa_switch *ds, int port,
 				       struct phylink_link_state *state);
 	/*
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index 668c729946ea..09d2f5d4b3dd 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -1505,14 +1505,6 @@ static int dsa_switch_probe(struct dsa_switch *ds)
 	if (!ds->num_ports)
 		return -EINVAL;
 
-	if (ds->phylink_mac_ops) {
-		if (ds->ops->phylink_mac_select_pcs ||
-		    ds->ops->phylink_mac_config ||
-		    ds->ops->phylink_mac_link_down ||
-		    ds->ops->phylink_mac_link_up)
-			return -EINVAL;
-	}
-
 	if (np) {
 		err = dsa_switch_parse_of(ds, np);
 		if (err)
diff --git a/net/dsa/port.c b/net/dsa/port.c
index 25258b33e59e..f1e96706a701 100644
--- a/net/dsa/port.c
+++ b/net/dsa/port.c
@@ -1579,40 +1579,19 @@ static struct phylink_pcs *
 dsa_port_phylink_mac_select_pcs(struct phylink_config *config,
 				phy_interface_t interface)
 {
-	struct dsa_port *dp = dsa_phylink_to_port(config);
-	struct phylink_pcs *pcs = ERR_PTR(-EOPNOTSUPP);
-	struct dsa_switch *ds = dp->ds;
-
-	if (ds->ops->phylink_mac_select_pcs)
-		pcs = ds->ops->phylink_mac_select_pcs(ds, dp->index, interface);
-
-	return pcs;
+	return ERR_PTR(-EOPNOTSUPP);
 }
 
 static void dsa_port_phylink_mac_config(struct phylink_config *config,
 					unsigned int mode,
 					const struct phylink_link_state *state)
 {
-	struct dsa_port *dp = dsa_phylink_to_port(config);
-	struct dsa_switch *ds = dp->ds;
-
-	if (!ds->ops->phylink_mac_config)
-		return;
-
-	ds->ops->phylink_mac_config(ds, dp->index, mode, state);
 }
 
 static void dsa_port_phylink_mac_link_down(struct phylink_config *config,
 					   unsigned int mode,
 					   phy_interface_t interface)
 {
-	struct dsa_port *dp = dsa_phylink_to_port(config);
-	struct dsa_switch *ds = dp->ds;
-
-	if (!ds->ops->phylink_mac_link_down)
-		return;
-
-	ds->ops->phylink_mac_link_down(ds, dp->index, mode, interface);
 }
 
 static void dsa_port_phylink_mac_link_up(struct phylink_config *config,
@@ -1622,14 +1601,6 @@ static void dsa_port_phylink_mac_link_up(struct phylink_config *config,
 					 int speed, int duplex,
 					 bool tx_pause, bool rx_pause)
 {
-	struct dsa_port *dp = dsa_phylink_to_port(config);
-	struct dsa_switch *ds = dp->ds;
-
-	if (!ds->ops->phylink_mac_link_up)
-		return;
-
-	ds->ops->phylink_mac_link_up(ds, dp->index, mode, interface, phydev,
-				     speed, duplex, tx_pause, rx_pause);
 }
 
 static const struct phylink_mac_ops dsa_port_phylink_mac_ops = {
@@ -1871,9 +1842,6 @@ static void dsa_shared_port_link_down(struct dsa_port *dp)
 	if (ds->phylink_mac_ops && ds->phylink_mac_ops->mac_link_down)
 		ds->phylink_mac_ops->mac_link_down(&dp->pl_config, MLO_AN_FIXED,
 						   PHY_INTERFACE_MODE_NA);
-	else if (ds->ops->phylink_mac_link_down)
-		ds->ops->phylink_mac_link_down(ds, dp->index, MLO_AN_FIXED,
-					       PHY_INTERFACE_MODE_NA);
 }
 
 int dsa_shared_port_link_register_of(struct dsa_port *dp)

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:38 +0100

There is no longer any reason to implement the mac_select_pcs()
callback in DSA. Returning ERR_PTR(-EOPNOTSUPP) is functionally
equivalent to not providing the function.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 net/dsa/port.c | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/net/dsa/port.c b/net/dsa/port.c
index f1e96706a701..ee0aaec4c8e0 100644
--- a/net/dsa/port.c
+++ b/net/dsa/port.c
@@ -1575,13 +1575,6 @@ void dsa_port_set_tag_protocol(struct dsa_port *cpu_dp,
 	cpu_dp->tag_ops = tag_ops;
 }
 
-static struct phylink_pcs *
-dsa_port_phylink_mac_select_pcs(struct phylink_config *config,
-				phy_interface_t interface)
-{
-	return ERR_PTR(-EOPNOTSUPP);
-}
-
 static void dsa_port_phylink_mac_config(struct phylink_config *config,
 					unsigned int mode,
 					const struct phylink_link_state *state)
@@ -1604,7 +1597,6 @@ static void dsa_port_phylink_mac_link_up(struct phylink_config *config,
 }
 
 static const struct phylink_mac_ops dsa_port_phylink_mac_ops = {
-	.mac_select_pcs = dsa_port_phylink_mac_select_pcs,
 	.mac_config = dsa_port_phylink_mac_config,
 	.mac_link_down = dsa_port_phylink_mac_link_down,
 	.mac_link_up = dsa_port_phylink_mac_link_up,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:38 +0100

Rather than returning an EOPNOTSUPP error pointer when the switch
has no support for PCS, return NULL to indicate that no PCS is
required.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/dsa/mv88e6xxx/chip.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 5b4e2ce5470d..f005ffdbe1c7 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -867,7 +867,7 @@ mv88e6xxx_mac_select_pcs(struct phylink_config *config,
 {
 	struct dsa_port *dp = dsa_phylink_to_port(config);
 	struct mv88e6xxx_chip *chip = dp->ds->priv;
-	struct phylink_pcs *pcs = ERR_PTR(-EOPNOTSUPP);
+	struct phylink_pcs *pcs = NULL;
 
 	if (chip->info->ops->pcs_ops)
 		pcs = chip->info->ops->pcs_ops->pcs_select(chip, dp->index,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:38 +0100

phylink has historically not permitted a PCS to be removed. An attempt
to permit this with phylink_set_pcs() resulted in comments indicating
that there was no need for this. This behaviour has been propagated
forward to the mac_select_pcs() approach as it was believed from these
comments that changing this would be NAK'd.

However, with mac_select_pcs(), it takes more code and thus complexity
to maintain this behaviour, which can - and in this case has - resulted
in a bug. If mac_select_pcs() returns NULL for a particular interface
type, but there is already a PCS in-use, then we skip the pcs_validate()
method, but continue using the old PCS. Also, it wouldn't be expected
behaviour by implementers of mac_select_pcs().

Allow this by removing this old unnecessary restriction.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 51c526d227fa..ada283122018 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1191,7 +1191,7 @@ static void phylink_major_config(struct phylink *pl, bool restart,
 			return;
 		}
 
-		pcs_changed = pcs && pl->pcs != pcs;
+		pcs_changed = pl->pcs != pcs;
 	}
 
 	phylink_pcs_poll_stop(pl);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:38 +0100

When the mac_select_pcs() method is not implemented, there is no way
for pl->pcs to be set to a non-NULL value. This was here to support
the old phylink_set_pcs() method which has been removed a few years
ago. Simplify the code in phylink_validate_mac_and_pcs().

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index ada283122018..e2822fe59de6 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -656,8 +656,8 @@ static int phylink_validate_mac_and_pcs(struct phylink *pl,
 					unsigned long *supported,
 					struct phylink_link_state *state)
 {
+	struct phylink_pcs *pcs = NULL;
 	unsigned long capabilities;
-	struct phylink_pcs *pcs;
 	int ret;
 
 	/* Get the PCS for this interface mode */
@@ -665,8 +665,6 @@ static int phylink_validate_mac_and_pcs(struct phylink *pl,
 		pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
 		if (IS_ERR(pcs))
 			return PTR_ERR(pcs);
-	} else {
-		pcs = pl->pcs;
 	}
 
 	if (pcs) {

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:38 +0100

With DSA's implementation of the mac_select_pcs() method removed, we
can now remove the detection of mac_select_pcs() implementation.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 12 ++----------
 1 file changed, 2 insertions(+), 10 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index e2822fe59de6..efeb5f13a9b5 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -79,7 +79,6 @@ struct phylink {
 	unsigned int pcs_state;
 
 	bool mac_link_dropped;
-	bool using_mac_select_pcs;
 
 	struct sfp_bus *sfp_bus;
 	bool sfp_may_have_phy;
@@ -661,7 +660,7 @@ static int phylink_validate_mac_and_pcs(struct phylink *pl,
 	int ret;
 
 	/* Get the PCS for this interface mode */
-	if (pl->using_mac_select_pcs) {
+	if (pl->mac_ops->mac_select_pcs) {
 		pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
 		if (IS_ERR(pcs))
 			return PTR_ERR(pcs);
@@ -1180,7 +1179,7 @@ static void phylink_major_config(struct phylink *pl, bool restart,
 						state->interface,
 						state->advertising);
 
-	if (pl->using_mac_select_pcs) {
+	if (pl->mac_ops->mac_select_pcs) {
 		pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
 		if (IS_ERR(pcs)) {
 			phylink_err(pl,
@@ -1654,7 +1653,6 @@ struct phylink *phylink_create(struct phylink_config *config,
 			       phy_interface_t iface,
 			       const struct phylink_mac_ops *mac_ops)
 {
-	bool using_mac_select_pcs = false;
 	struct phylink *pl;
 	int ret;
 
@@ -1665,11 +1663,6 @@ struct phylink *phylink_create(struct phylink_config *config,
 		return ERR_PTR(-EINVAL);
 	}
 
-	if (mac_ops->mac_select_pcs &&
-	    mac_ops->mac_select_pcs(config, PHY_INTERFACE_MODE_NA) !=
-	      ERR_PTR(-EOPNOTSUPP))
-		using_mac_select_pcs = true;
-
 	pl = kzalloc(sizeof(*pl), GFP_KERNEL);
 	if (!pl)
 		return ERR_PTR(-ENOMEM);
@@ -1688,7 +1681,6 @@ struct phylink *phylink_create(struct phylink_config *config,
 		return ERR_PTR(-EINVAL);
 	}
 
-	pl->using_mac_select_pcs = using_mac_select_pcs;
 	pl->phy_state.interface = iface;
 	pl->link_interface = iface;
 	if (iface == PHY_INTERFACE_MODE_MOCA)

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:38 +0100

phylink_parse_fixedlink() wants to preserve the pause, asym_pause and
autoneg bits in pl->supported. Rather than reading the bits into
separate bools, zeroing pl->supported, and then setting them if they
were previously set, use a mask and linkmode_and() to achieve the same
result.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 19 ++++++-------------
 1 file changed, 6 insertions(+), 13 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index efeb5f13a9b5..3ca331b9206f 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -771,8 +771,8 @@ static int phylink_validate(struct phylink *pl, unsigned long *supported,
 static int phylink_parse_fixedlink(struct phylink *pl,
 				   const struct fwnode_handle *fwnode)
 {
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
 	struct fwnode_handle *fixed_node;
-	bool pause, asym_pause, autoneg;
 	const struct phy_setting *s;
 	struct gpio_desc *desc;
 	u32 speed;
@@ -845,22 +845,15 @@ static int phylink_parse_fixedlink(struct phylink *pl,
 	linkmode_copy(pl->link_config.advertising, pl->supported);
 	phylink_validate(pl, pl->supported, &pl->link_config);
 
-	pause = phylink_test(pl->supported, Pause);
-	asym_pause = phylink_test(pl->supported, Asym_Pause);
-	autoneg = phylink_test(pl->supported, Autoneg);
 	s = phy_lookup_setting(pl->link_config.speed, pl->link_config.duplex,
 			       pl->supported, true);
-	linkmode_zero(pl->supported);
-	phylink_set(pl->supported, MII);
 
-	if (pause)
-		phylink_set(pl->supported, Pause);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, mask);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, mask);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, mask);
+	linkmode_and(pl->supported, pl->supported, mask);
 
-	if (asym_pause)
-		phylink_set(pl->supported, Asym_Pause);
-
-	if (autoneg)
-		phylink_set(pl->supported, Autoneg);
+	phylink_set(pl->supported, MII);
 
 	if (s) {
 		__set_bit(s->bit, pl->supported);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:39 +0100

No driver now uses the DSA switch phylink members, so we can now remove
the shim functions and method pointers. Arrange to print an error
message and fail registration if a DSA driver does not provide the
phylink MAC operations structure.

Signed-off-by: Russell King (oracle) <[email-masked]>
---
 net/dsa/dsa.c  |  5 +++++
 net/dsa/port.c | 34 +---------------------------------
 2 files changed, 6 insertions(+), 33 deletions(-)

diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index 09d2f5d4b3dd..ceeadb52d1cc 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -1505,6 +1505,11 @@ static int dsa_switch_probe(struct dsa_switch *ds)
 	if (!ds->num_ports)
 		return -EINVAL;
 
+	if (!ds->phylink_mac_ops) {
+		dev_err(ds->dev, "DSA switch driver does not provide phylink MAC operations");
+		return -EINVAL;
+	}
+
 	if (np) {
 		err = dsa_switch_parse_of(ds, np);
 		if (err)
diff --git a/net/dsa/port.c b/net/dsa/port.c
index ee0aaec4c8e0..2fc432797fbd 100644
--- a/net/dsa/port.c
+++ b/net/dsa/port.c
@@ -1575,36 +1575,8 @@ void dsa_port_set_tag_protocol(struct dsa_port *cpu_dp,
 	cpu_dp->tag_ops = tag_ops;
 }
 
-static void dsa_port_phylink_mac_config(struct phylink_config *config,
-					unsigned int mode,
-					const struct phylink_link_state *state)
-{
-}
-
-static void dsa_port_phylink_mac_link_down(struct phylink_config *config,
-					   unsigned int mode,
-					   phy_interface_t interface)
-{
-}
-
-static void dsa_port_phylink_mac_link_up(struct phylink_config *config,
-					 struct phy_device *phydev,
-					 unsigned int mode,
-					 phy_interface_t interface,
-					 int speed, int duplex,
-					 bool tx_pause, bool rx_pause)
-{
-}
-
-static const struct phylink_mac_ops dsa_port_phylink_mac_ops = {
-	.mac_config = dsa_port_phylink_mac_config,
-	.mac_link_down = dsa_port_phylink_mac_link_down,
-	.mac_link_up = dsa_port_phylink_mac_link_up,
-};
-
 int dsa_port_phylink_create(struct dsa_port *dp)
 {
-	const struct phylink_mac_ops *mac_ops;
 	struct dsa_switch *ds = dp->ds;
 	phy_interface_t mode;
 	struct phylink *pl;
@@ -1628,12 +1600,8 @@ int dsa_port_phylink_create(struct dsa_port *dp)
 		}
 	}
 
-	mac_ops = &dsa_port_phylink_mac_ops;
-	if (ds->phylink_mac_ops)
-		mac_ops = ds->phylink_mac_ops;
-
 	pl = phylink_create(&dp->pl_config, of_fwnode_handle(dp->dn), mode,
-			    mac_ops);
+			    ds->phylink_mac_ops);
 	if (IS_ERR(pl)) {
 		pr_err("error creating PHYLINK: %ld\n", PTR_ERR(pl));
 		return PTR_ERR(pl);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:39 +0100

Add a helper to determine whether the link is up or down. Currently
this is only used in one location, but becomes necessary to test
when reconfiguring EEE.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 3ca331b9206f..4104473cc9e7 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1449,6 +1449,11 @@ static void phylink_link_down(struct phylink *pl)
 	phylink_info(pl, "Link is Down\n");
 }
 
+static bool phylink_link_is_up(struct phylink *pl)
+{
+	return pl->netdev ? netif_carrier_ok(pl->netdev) : pl->old_link_state;
+}
+
 static void phylink_resolve(struct work_struct *w)
 {
 	struct phylink *pl = container_of(w, struct phylink, resolve);
@@ -1459,10 +1464,7 @@ static void phylink_resolve(struct work_struct *w)
 	bool cur_link_state;
 
 	mutex_lock(&pl->state_mutex);
-	if (pl->netdev)
-		cur_link_state = netif_carrier_ok(ndev);
-	else
-		cur_link_state = pl->old_link_state;
+	cur_link_state = phylink_link_is_up(pl);
 
 	if (pl->phylink_disable_state) {
 		pl->mac_link_dropped = false;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:39 +0100

Add EEE management to phylink.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 135 +++++++++++++++++++++++++++++++++++++++++++++-
 include/linux/phylink.h   |  36 +++++++++++++
 2 files changed, 170 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 4104473cc9e7..46c3b98bec70 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -85,6 +85,9 @@ struct phylink {
 	DECLARE_PHY_INTERFACE_MASK(sfp_interfaces);
 	__ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
 	u8 sfp_port;
+
+	struct eee_config eee_cfg;
+	bool eee_active;
 };
 
 #define phylink_printk(level, pl, fmt, ...) \
@@ -1382,6 +1385,75 @@ static const char *phylink_pause_to_str(int pause)
 	}
 }
 
+static void phylink_disable_tx_lpi(struct phylink *pl)
+{
+	phylink_dbg(pl, "disabling tx_lpi\n");
+
+	if (pl->mac_ops->mac_disable_tx_lpi)
+		pl->mac_ops->mac_disable_tx_lpi(pl->config);
+}
+
+static void phylink_enable_tx_lpi(struct phylink *pl)
+{
+	phylink_dbg(pl, "enabling tx_lpi, timer %uus\n",
+		    pl->eee_cfg.tx_lpi_timer);
+
+	if (pl->mac_ops->mac_enable_tx_lpi)
+		pl->mac_ops->mac_enable_tx_lpi(pl->config,
+					       pl->eee_cfg.tx_lpi_timer);
+}
+
+static bool phylink_eee_is_active(struct phylink *pl)
+{
+	return phylink_init_eee(pl, pl->config->eee_clk_stop_enable) >= 0;
+}
+
+static void phylink_deactivate_eee(struct phylink *pl)
+{
+	phylink_dbg(pl, "deactivating EEE, was %sactive\n",
+		    pl->eee_active ? "" : "in");
+
+	if (pl->eee_active) {
+		pl->eee_active = false;
+		phylink_disable_tx_lpi(pl);
+	}
+}
+
+static void phylink_activate_eee(struct phylink *pl)
+{
+	pl->eee_active = phylink_eee_is_active(pl);
+
+	phylink_dbg(pl, "can LPI, EEE enabled, %sactive\n",
+		    pl->eee_active ? "" : "in");
+
+	if (pl->eee_active)
+		phylink_enable_tx_lpi(pl);
+}
+
+/* Determine whether the MAC has new EEE support. We detect this by checking
+ * for the two new methods being present, but for DSA it will populate these
+ * anyway, so also check that lpi_capabilities is non-zero.
+ */
+static bool phylink_mac_supports_eee(struct phylink *pl)
+{
+	return pl->mac_ops->mac_disable_tx_lpi &&
+	       pl->mac_ops->mac_enable_tx_lpi &&
+	       pl->config->lpi_capabilities;
+}
+
+static void phylink_phy_restrict_eee(struct phylink *pl, struct phy_device *phy)
+{
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(eee_supported);
+
+	/* Convert the MAC's LPI capabilities to linkmodes */
+	linkmode_zero(eee_supported);
+	phylink_caps_to_linkmodes(eee_supported, pl->config->lpi_capabilities);
+
+	/* Mask out EEE modes that are not supported */
+	linkmode_and(phy->supported_eee, phy->supported_eee, eee_supported);
+	linkmode_and(phy->advertising_eee, phy->advertising_eee, eee_supported);
+}
+
 static void phylink_link_up(struct phylink *pl,
 			    struct phylink_link_state link_state)
 {
@@ -1428,6 +1500,9 @@ static void phylink_link_up(struct phylink *pl,
 				 pl->cur_interface, speed, duplex,
 				 !!(link_state.pause & MLO_PAUSE_TX), rx_pause);
 
+	if (eeecfg_mac_can_tx_lpi(&pl->eee_cfg))
+		phylink_activate_eee(pl);
+
 	if (ndev)
 		netif_carrier_on(ndev);
 
@@ -1444,6 +1519,9 @@ static void phylink_link_down(struct phylink *pl)
 
 	if (ndev)
 		netif_carrier_off(ndev);
+
+	phylink_deactivate_eee(pl);
+
 	pl->mac_ops->mac_link_down(pl->config, pl->cur_link_an_mode,
 				   pl->cur_interface);
 	phylink_info(pl, "Link is Down\n");
@@ -1458,7 +1536,6 @@ static void phylink_resolve(struct work_struct *w)
 {
 	struct phylink *pl = container_of(w, struct phylink, resolve);
 	struct phylink_link_state link_state;
-	struct net_device *ndev = pl->netdev;
 	bool mac_config = false;
 	bool retrigger = false;
 	bool cur_link_state;
@@ -1695,6 +1772,9 @@ struct phylink *phylink_create(struct phylink_config *config,
 	linkmode_copy(pl->link_config.advertising, pl->supported);
 	phylink_validate(pl, pl->supported, &pl->link_config);
 
+	/* Set the default EEE configuration */
+	pl->eee_cfg = pl->config->eee;
+
 	ret = phylink_parse_mode(pl, fwnode);
 	if (ret < 0) {
 		kfree(pl);
@@ -1917,6 +1997,13 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
 
 	/* Restrict the phy advertisement according to the MAC support. */
 	linkmode_copy(phy->advertising, config.advertising);
+
+	/* If the MAC supports phylink managed EEE, restrict the EEE
+	 * advertisement according to the MAC's LPI capabilities.
+	 */
+	if (phylink_mac_supports_eee(pl))
+		phylink_phy_restrict_eee(pl, phy);
+
 	mutex_unlock(&pl->state_mutex);
 	mutex_unlock(&phy->lock);
 
@@ -2798,6 +2885,12 @@ int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_keee *eee)
 	if (pl->phydev)
 		ret = phy_ethtool_get_eee(pl->phydev, eee);
 
+	if (!ret && phylink_mac_supports_eee(pl)) {
+		/* Overwrite phylib's interpretation of configuration */
+		eeecfg_to_eee(eee, &pl->eee_cfg);
+		eee->eee_active = pl->eee_active;
+	}
+
 	return ret;
 }
 EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee);
@@ -2810,12 +2903,52 @@ EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee);
 int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_keee *eee)
 {
 	int ret = -EOPNOTSUPP;
+	bool mac_eee;
 
 	ASSERT_RTNL();
 
+	mac_eee = phylink_mac_supports_eee(pl);
+
+	phylink_dbg(pl, "mac %s phylink EEE%s, adv 0x%*pbl, LPI%s timer %uus\n",
+		    mac_eee ? "supports" : "does not support",
+		    eee->eee_enabled ? ", enabled" : "",
+		    __ETHTOOL_LINK_MODE_MASK_NBITS, eee->advertised,
+		    eee->tx_lpi_enabled ? " enabled" : "", eee->tx_lpi_timer);
+
+	/* Clamp the LPI timer maximum value */
+	if (mac_eee && eee->tx_lpi_timer > pl->config->lpi_timer_limit_us) {
+		eee->tx_lpi_timer = pl->config->lpi_timer_limit_us;
+		phylink_dbg(pl, "LPI timer limited to %uus\n",
+			    eee->tx_lpi_timer);
+	}
+
 	if (pl->phydev)
 		ret = phy_ethtool_set_eee(pl->phydev, eee);
 
+	if (!ret && mac_eee) {
+		bool can_lpi, old_can_lpi;
+
+		mutex_lock(&pl->state_mutex);
+		old_can_lpi = eeecfg_mac_can_tx_lpi(&pl->eee_cfg);
+		eee_to_eeecfg(&pl->eee_cfg, eee);
+		can_lpi = eeecfg_mac_can_tx_lpi(&pl->eee_cfg);
+
+		phylink_dbg(pl, "can_lpi %u -> %u\n", old_can_lpi, can_lpi);
+
+		/* If the link is up, and the configuration changes the
+		 * LPI permissive state, deal with the change at the MAC.
+		 */
+		if (phylink_link_is_up(pl) && old_can_lpi != can_lpi) {
+			phylink_dbg(pl, "link is up, lpi changed\n");
+			if (can_lpi)
+				phylink_activate_eee(pl);
+			else
+				phylink_deactivate_eee(pl);
+		}
+
+		mutex_unlock(&pl->state_mutex);
+	}
+
 	return ret;
 }
 EXPORT_SYMBOL_GPL(phylink_ethtool_set_eee);
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
index 2381e07429a2..0dddb11f3a20 100644
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -5,6 +5,8 @@
 #include <linux/spinlock.h>
 #include <linux/workqueue.h>
 
+#include <net/eee.h>
+
 struct device_node;
 struct ethtool_cmd;
 struct fwnode_handle;
@@ -143,11 +145,15 @@ enum phylink_op_type {
  *                    possible and avoid stopping it during suspend events.
  * @default_an_inband: if true, defaults to MLO_AN_INBAND rather than
  *		       MLO_AN_PHY. A fixed-link specification will override.
+ * @eee_clk_stop_enable: if true, PHY can stop the receive clock during LPI
  * @get_fixed_state: callback to execute to determine the fixed link state,
  *		     if MAC link is at %MLO_AN_FIXED mode.
  * @supported_interfaces: bitmap describing which PHY_INTERFACE_MODE_xxx
  *                        are supported by the MAC/PCS.
  * @mac_capabilities: MAC pause/speed/duplex capabilities.
+ * @lpi_capabilities: MAC speeds which can support LPI signalling
+ * @eee: default EEE configuration.
+ * @lpi_timer_limit_us: Maximum (inclusive) value of the EEE LPI timer.
  */
 struct phylink_config {
 	struct device *dev;
@@ -156,10 +162,14 @@ struct phylink_config {
 	bool mac_managed_pm;
 	bool mac_requires_rxc;
 	bool default_an_inband;
+	bool eee_clk_stop_enable;
 	void (*get_fixed_state)(struct phylink_config *config,
 				struct phylink_link_state *state);
 	DECLARE_PHY_INTERFACE_MASK(supported_interfaces);
 	unsigned long mac_capabilities;
+	unsigned long lpi_capabilities;
+	struct eee_config eee;
+	u32 lpi_timer_limit_us;
 };
 
 void phylink_limit_mac_speed(struct phylink_config *config, u32 max_speed);
@@ -173,6 +183,8 @@ void phylink_limit_mac_speed(struct phylink_config *config, u32 max_speed);
  * @mac_finish: finish a major reconfiguration of the interface.
  * @mac_link_down: take the link down.
  * @mac_link_up: allow the link to come up.
+ * @mac_disable_tx_lpi: disable LPI.
+ * @mac_enable_tx_lpi: enable and configure LPI.
  *
  * The individual methods are described more fully below.
  */
@@ -193,6 +205,8 @@ struct phylink_mac_ops {
 			    struct phy_device *phy, unsigned int mode,
 			    phy_interface_t interface, int speed, int duplex,
 			    bool tx_pause, bool rx_pause);
+	void (*mac_disable_tx_lpi)(struct phylink_config *config);
+	void (*mac_enable_tx_lpi)(struct phylink_config *config, u32 timer);
 };
 
 #if 0 /* For kernel-doc purposes only. */
@@ -387,6 +401,28 @@ void mac_link_down(struct phylink_config *config, unsigned int mode,
 void mac_link_up(struct phylink_config *config, struct phy_device *phy,
 		 unsigned int mode, phy_interface_t interface,
 		 int speed, int duplex, bool tx_pause, bool rx_pause);
+
+/**
+ * mac_disable_tx_lpi() - disable LPI generation at the MAC
+ * @config: a pointer to a &struct phylink_config.
+ *
+ * Disable generation of LPI at the MAC, effectively preventing the MAC
+ * from indicating that it is idle.
+ */
+void mac_disable_tx_lpi(struct phylink_config *config);
+
+/**
+ * mac_enable_tx_lpi() - configure and enable LPI generation at the MAC
+ * @config: a pointer to a &struct phylink_config.
+ * @timer: LPI timeout in microseconds.
+ *
+ * Configure the LPI timeout accordingly. This will only be called when
+ * the link is already up, to cater for situations where the hardware
+ * needs to be programmed according to the link speed.
+ *
+ * Enable LPI generation at the MAC.
+ */
+void mac_enable_tx_lpi(struct phylink_config *config, u32 timer);
 #endif
 
 struct phylink_pcs_ops;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:39 +0100

Convert mvneta to use phylink's EEE implementation, which means we just
need to implement the two methods for LPI control, and adding the
initial configuration.

Disabling LPI requires clearing a single bit. Enabling LPI needs a full
configuration of several values, as the timer values are dependent on
the MAC operating speed.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/ethernet/marvell/mvneta.c | 107 +++++++++++++++++++---------------
 1 file changed, 60 insertions(+), 47 deletions(-)

diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
index 41894834fb53..c1bf3e02d944 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -284,8 +284,10 @@
 					  MVNETA_TXQ_BUCKET_REFILL_PERIOD))
 
 #define MVNETA_LPI_CTRL_0                        0x2cc0
+#define      MVNETA_LPI_CTRL_0_TS                (0xff << 8)
 #define MVNETA_LPI_CTRL_1                        0x2cc4
 #define      MVNETA_LPI_REQUEST_ENABLE           BIT(0)
+#define      MVNETA_LPI_CTRL_1_TW                (0xfff << 4)
 #define MVNETA_LPI_CTRL_2                        0x2cc8
 #define MVNETA_LPI_STATUS                        0x2ccc
 
@@ -541,10 +543,6 @@ struct mvneta_port {
 	struct mvneta_bm_pool *pool_short;
 	int bm_win_id;
 
-	bool eee_enabled;
-	bool eee_active;
-	bool tx_lpi_enabled;
-
 	u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)];
 
 	u32 indir[MVNETA_RSS_LU_TABLE_SIZE];
@@ -4206,18 +4204,6 @@ static int mvneta_mac_finish(struct phylink_config *config, unsigned int mode,
 	return 0;
 }
 
-static void mvneta_set_eee(struct mvneta_port *pp, bool enable)
-{
-	u32 lpi_ctl1;
-
-	lpi_ctl1 = mvreg_read(pp, MVNETA_LPI_CTRL_1);
-	if (enable)
-		lpi_ctl1 |= MVNETA_LPI_REQUEST_ENABLE;
-	else
-		lpi_ctl1 &= ~MVNETA_LPI_REQUEST_ENABLE;
-	mvreg_write(pp, MVNETA_LPI_CTRL_1, lpi_ctl1);
-}
-
 static void mvneta_mac_link_down(struct phylink_config *config,
 				 unsigned int mode, phy_interface_t interface)
 {
@@ -4233,9 +4219,6 @@ static void mvneta_mac_link_down(struct phylink_config *config,
 		val |= MVNETA_GMAC_FORCE_LINK_DOWN;
 		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
 	}
-
-	pp->eee_active = false;
-	mvneta_set_eee(pp, false);
 }
 
 static void mvneta_mac_link_up(struct phylink_config *config,
@@ -4284,11 +4267,57 @@ static void mvneta_mac_link_up(struct phylink_config *config,
 	}
 
 	mvneta_port_up(pp);
+}
 
-	if (phy && pp->eee_enabled) {
-		pp->eee_active = phy_init_eee(phy, false) >= 0;
-		mvneta_set_eee(pp, pp->eee_active && pp->tx_lpi_enabled);
+static void mvneta_mac_disable_tx_lpi(struct phylink_config *config)
+{
+	struct mvneta_port *pp = netdev_priv(to_net_dev(config->dev));
+	u32 lpi1;
+
+	lpi1 = mvreg_read(pp, MVNETA_LPI_CTRL_1);
+	lpi1 &= ~MVNETA_LPI_REQUEST_ENABLE;
+	mvreg_write(pp, MVNETA_LPI_CTRL_1, lpi1);
+}
+
+static void mvneta_mac_enable_tx_lpi(struct phylink_config *config, u32 timer)
+{
+	struct mvneta_port *pp = netdev_priv(to_net_dev(config->dev));
+	u32 ts, tw, lpi0, lpi1, status;
+
+	status = mvreg_read(pp, MVNETA_GMAC_STATUS);
+
+	if (status & MVNETA_GMAC_SPEED_1000) {
+		/* At 1G speeds, the timer resolution are 1us, and
+		 * 802.3 says tw is 16.5us. Round up to 17us.
+		 */
+		tw = 17;
+		ts = timer;
+	} else {
+		/* At 100M speeds, the timer resolutions are 10us, and
+		 * 802.3 says tw is 30us.
+		 */
+		tw = 3;
+		ts = DIV_ROUND_UP(timer, 10);
 	}
+
+	if (ts > 255)
+		ts = 255;
+
+	/* Ensure LPI generation is disabled */
+	lpi1 = mvreg_read(pp, MVNETA_LPI_CTRL_1);
+	mvreg_write(pp, MVNETA_LPI_CTRL_1, lpi1 & ~MVNETA_LPI_REQUEST_ENABLE);
+
+	/* Configure ts */
+	lpi0 = mvreg_read(pp, MVNETA_LPI_CTRL_0) & ~MVNETA_LPI_CTRL_0_TS;
+	lpi0 |= FIELD_PREP(MVNETA_LPI_CTRL_0_TS, ts);
+	mvreg_write(pp, MVNETA_LPI_CTRL_0, lpi0);
+
+	/* Configure tw */
+	lpi1 &= ~MVNETA_LPI_CTRL_1_TW;
+	lpi1 |= FIELD_PREP(MVNETA_LPI_CTRL_1_TW, tw);
+
+	/* Enable LPI generation */
+	mvreg_write(pp, MVNETA_LPI_CTRL_1, lpi1 | MVNETA_LPI_REQUEST_ENABLE);
 }
 
 static const struct phylink_mac_ops mvneta_phylink_ops = {
@@ -4298,6 +4327,8 @@ static const struct phylink_mac_ops mvneta_phylink_ops = {
 	.mac_finish = mvneta_mac_finish,
 	.mac_link_down = mvneta_mac_link_down,
 	.mac_link_up = mvneta_mac_link_up,
+	.mac_disable_tx_lpi = mvneta_mac_disable_tx_lpi,
+	.mac_enable_tx_lpi = mvneta_mac_enable_tx_lpi,
 };
 
 static int mvneta_mdio_probe(struct mvneta_port *pp)
@@ -5101,14 +5132,6 @@ static int mvneta_ethtool_get_eee(struct net_device *dev,
 				  struct ethtool_keee *eee)
 {
 	struct mvneta_port *pp = netdev_priv(dev);
-	u32 lpi_ctl0;
-
-	lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0);
-
-	eee->eee_enabled = pp->eee_enabled;
-	eee->eee_active = pp->eee_active;
-	eee->tx_lpi_enabled = pp->tx_lpi_enabled;
-	eee->tx_lpi_timer = (lpi_ctl0) >> 8; // * scale;
 
 	return phylink_ethtool_get_eee(pp->phylink, eee);
 }
@@ -5117,23 +5140,6 @@ static int mvneta_ethtool_set_eee(struct net_device *dev,
 				  struct ethtool_keee *eee)
 {
 	struct mvneta_port *pp = netdev_priv(dev);
-	u32 lpi_ctl0;
-
-	/* The Armada 37x documents do not give limits for this other than
-	 * it being an 8-bit register.
-	 */
-	if (eee->tx_lpi_enabled && eee->tx_lpi_timer > 255)
-		return -EINVAL;
-
-	lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0);
-	lpi_ctl0 &= ~(0xff << 8);
-	lpi_ctl0 |= eee->tx_lpi_timer << 8;
-	mvreg_write(pp, MVNETA_LPI_CTRL_0, lpi_ctl0);
-
-	pp->eee_enabled = eee->eee_enabled;
-	pp->tx_lpi_enabled = eee->tx_lpi_enabled;
-
-	mvneta_set_eee(pp, eee->tx_lpi_enabled && eee->eee_enabled);
 
 	return phylink_ethtool_set_eee(pp->phylink, eee);
 }
@@ -5538,6 +5544,8 @@ static int mvneta_probe(struct platform_device *pdev)
 	pp->phylink_config.type = PHYLINK_NETDEV;
 	pp->phylink_config.mac_capabilities = MAC_SYM_PAUSE | MAC_10 |
 		MAC_100 | MAC_1000FD | MAC_2500FD;
+	pp->phylink_config.lpi_capabilities = MAC_100FD | MAC_1000FD;
+	pp->phylink_config.lpi_timer_limit_us = 255;
 
 	phy_interface_set_rgmii(pp->phylink_config.supported_interfaces);
 	__set_bit(PHY_INTERFACE_MODE_QSGMII,
@@ -5565,6 +5573,11 @@ static int mvneta_probe(struct platform_device *pdev)
 			  pp->phylink_config.supported_interfaces);
 	}
 
+	/* Setup EEE.  Choose 250us idle. */
+	pp->phylink_config.eee.eee_enabled = true;
+	pp->phylink_config.eee.tx_lpi_enabled = true;
+	pp->phylink_config.eee.tx_lpi_timer = 250;
+
 	phylink = phylink_create(&pp->phylink_config, pdev->dev.fwnode,
 				 phy_mode, &mvneta_phylink_ops);
 	if (IS_ERR(phylink)) {

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:39 +0100

Add EEE support for mvpp2, using phylink's EEE implementation, which
means we just need to implement the two methods for LPI control, and
with the initial configuration. Only the GMAC is supported, so only
100M, 1G and 2.5G speeds.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/ethernet/marvell/mvpp2/mvpp2.h      |  5 ++
 drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c | 85 +++++++++++++++++++++++++
 2 files changed, 90 insertions(+)

diff --git a/drivers/net/ethernet/marvell/mvpp2/mvpp2.h b/drivers/net/ethernet/marvell/mvpp2/mvpp2.h
index e809f91c08fb..299b996ac5df 100644
--- a/drivers/net/ethernet/marvell/mvpp2/mvpp2.h
+++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2.h
@@ -481,6 +481,11 @@
 #define MVPP22_GMAC_INT_SUM_MASK		0xa4
 #define     MVPP22_GMAC_INT_SUM_MASK_LINK_STAT	BIT(1)
 #define	    MVPP22_GMAC_INT_SUM_MASK_PTP	BIT(2)
+#define MVPP2_GMAC_LPI_CTRL0			0xc0
+#define     MVPP2_GMAC_LPI_CTRL0_TS_MASK	GENMASK(8, 8)
+#define MVPP2_GMAC_LPI_CTRL1			0xc4
+#define     MVPP2_GMAC_LPI_CTRL1_REQ_EN		BIT(0)
+#define     MVPP2_GMAC_LPI_CTRL1_TW_MASK	GENMASK(15, 4)
 
 /* Per-port XGMAC registers. PPv2.2 and PPv2.3, only for GOP port 0,
  * relative to port->base.
diff --git a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
index 0d62a33afa80..f841104f4a63 100644
--- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
+++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
@@ -5730,6 +5730,28 @@ static int mvpp2_ethtool_set_rxfh(struct net_device *dev,
 	return ret;
 }
 
+static int mvpp2_ethtool_get_eee(struct net_device *dev,
+				 struct ethtool_keee *eee)
+{
+	struct mvpp2_port *port = netdev_priv(dev);
+
+	if (!port->phylink)
+		return -EOPNOTSUPP;
+
+	return phylink_ethtool_get_eee(port->phylink, eee);
+}
+
+static int mvpp2_ethtool_set_eee(struct net_device *dev,
+				 struct ethtool_keee *eee)
+{
+	struct mvpp2_port *port = netdev_priv(dev);
+
+	if (!port->phylink)
+		return -EOPNOTSUPP;
+
+	return phylink_ethtool_set_eee(port->phylink, eee);
+}
+
 /* Device ops */
 
 static const struct net_device_ops mvpp2_netdev_ops = {
@@ -5772,6 +5794,8 @@ static const struct ethtool_ops mvpp2_eth_tool_ops = {
 	.get_rxfh_indir_size	= mvpp2_ethtool_get_rxfh_indir_size,
 	.get_rxfh		= mvpp2_ethtool_get_rxfh,
 	.set_rxfh		= mvpp2_ethtool_set_rxfh,
+	.get_eee		= mvpp2_ethtool_get_eee,
+	.set_eee		= mvpp2_ethtool_set_eee,
 };
 
 /* Used for PPv2.1, or PPv2.2 with the old Device Tree binding that
@@ -6635,6 +6659,57 @@ static void mvpp2_mac_link_down(struct phylink_config *config,
 	mvpp2_port_disable(port);
 }
 
+static void mvpp2_mac_disable_tx_lpi(struct phylink_config *config)
+{
+	struct mvpp2_port *port = mvpp2_phylink_to_port(config);
+
+	mvpp2_modify(port->base + MVPP2_GMAC_LPI_CTRL1,
+		     MVPP2_GMAC_LPI_CTRL1_REQ_EN, 0);
+}
+
+static void mvpp2_mac_enable_tx_lpi(struct phylink_config *config, u32 timer)
+{
+	struct mvpp2_port *port = mvpp2_phylink_to_port(config);
+	u32 ts, tw, lpi0, lpi1, status;
+
+	status = readl(port->base + MVPP2_GMAC_STATUS0);
+	if (status & MVPP2_GMAC_STATUS0_GMII_SPEED) {
+		/* At 1G speeds, the timer resolution are 1us, and
+		 * 802.3 says tw is 16.5us. Round up to 17us.
+		 */
+		tw = 17;
+		ts = timer;
+	} else {
+		/* At 100M speeds, the timer resolutions are 10us, and
+		 * 802.3 says tw is 30us.
+		 */
+		tw = 3;
+		ts = DIV_ROUND_UP(timer, 10);
+	}
+
+	if (ts > 255)
+		ts = 255;
+
+	/* Ensure LPI generation is disabled */
+	lpi1 = readl(port->base + MVPP2_GMAC_LPI_CTRL1);
+	writel(lpi1 & ~MVPP2_GMAC_LPI_CTRL1_REQ_EN,
+	       port->base + MVPP2_GMAC_LPI_CTRL1);
+
+	/* Configure ts */
+	lpi0 = readl(port->base + MVPP2_GMAC_LPI_CTRL0);
+	lpi0 &= ~MVPP2_GMAC_LPI_CTRL0_TS_MASK;
+	lpi0 |= FIELD_PREP(MVPP2_GMAC_LPI_CTRL0_TS_MASK, ts);
+	writel(lpi0, port->base + MVPP2_GMAC_LPI_CTRL0);
+
+	/* Configure tw */
+	lpi1 &= ~MVPP2_GMAC_LPI_CTRL1_TW_MASK;
+	lpi1 |= FIELD_PREP(MVPP2_GMAC_LPI_CTRL1_TW_MASK, tw);
+
+	/* Enable LPI generation */
+	writel(lpi1 | MVPP2_GMAC_LPI_CTRL1_REQ_EN,
+	       port->base + MVPP2_GMAC_LPI_CTRL1);
+}
+
 static const struct phylink_mac_ops mvpp2_phylink_ops = {
 	.mac_select_pcs = mvpp2_select_pcs,
 	.mac_prepare = mvpp2_mac_prepare,
@@ -6642,6 +6717,8 @@ static const struct phylink_mac_ops mvpp2_phylink_ops = {
 	.mac_finish = mvpp2_mac_finish,
 	.mac_link_up = mvpp2_mac_link_up,
 	.mac_link_down = mvpp2_mac_link_down,
+	.mac_enable_tx_lpi = mvpp2_mac_enable_tx_lpi,
+	.mac_disable_tx_lpi = mvpp2_mac_disable_tx_lpi,
 };
 
 /* Work-around for ACPI */
@@ -6919,6 +6996,9 @@ static int mvpp2_port_probe(struct platform_device *pdev,
 		port->phylink_config.type = PHYLINK_NETDEV;
 		port->phylink_config.mac_capabilities =
 			MAC_2500FD | MAC_1000FD | MAC_100 | MAC_10;
+		port->phylink_config.lpi_capabilities =
+			MAC_2500FD | MAC_1000FD | MAC_100FD;
+		port->phylink_config.lpi_timer_limit_us = 255;
 
 		if (port->priv->global_tx_fc)
 			port->phylink_config.mac_capabilities |=
@@ -6987,6 +7067,11 @@ static int mvpp2_port_probe(struct platform_device *pdev,
 				  port->phylink_config.supported_interfaces);
 		}
 
+		/* Setup EEE.  Choose 250us idle. */
+		port->phylink_config.eee.eee_enabled = true;
+		port->phylink_config.eee.tx_lpi_enabled = true;
+		port->phylink_config.eee.tx_lpi_timer = 250;
+
 		phylink = phylink_create(&port->phylink_config, port_fwnode,
 					 phy_mode, &mvpp2_phylink_ops);
 		if (IS_ERR(phylink)) {

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:39 +0100

Add a port_set_eee() method to allow the EEE settings for a port to be
configured.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/dsa/mv88e6xxx/chip.h | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index c34caf9815c5..c737bbc7d304 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -512,6 +512,16 @@ struct mv88e6xxx_ops {
 	 */
 	int (*port_set_link)(struct mv88e6xxx_chip *chip, int port, int link);
 
+#define EEE_FORCE_DISABLE	0
+#define EEE_FORCE_ENABLE	1
+#define EEE_UNFORCED		-1
+
+	/* Port's EEE state
+	 * Use EEE_FORCE_ENABLE or EEE_FORCE_DISABLE to force EEE to be enabled
+	 * or disabled, or EEE_UNFORCED for normal EEE.
+	 */
+	int (*port_set_eee)(struct mv88e6xxx_chip *chip, int port, int eee);
+
 	/* Synchronise the port link state with that of the SERDES
 	 */
 	int (*port_sync_link)(struct mv88e6xxx_chip *chip, int port, unsigned int mode, bool isup);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:39 +0100

Add support for EEE forcing using the MAC control register. Replace
the 88e6393x errata 4.5 EEE disable code with a call to the new EEE
forcing code.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/dsa/mv88e6xxx/port.c | 39 +++++++++++++++++++++++++++++++--------
 drivers/net/dsa/mv88e6xxx/port.h |  1 +
 2 files changed, 32 insertions(+), 8 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/port.c b/drivers/net/dsa/mv88e6xxx/port.c
index 5394a8cf7bf1..02870f001f5f 100644
--- a/drivers/net/dsa/mv88e6xxx/port.c
+++ b/drivers/net/dsa/mv88e6xxx/port.c
@@ -520,6 +520,36 @@ phy_interface_t mv88e6393x_port_max_speed_mode(struct mv88e6xxx_chip *chip,
 	return PHY_INTERFACE_MODE_10GBASER;
 }
 
+int mv88e6xxx_port_set_eee(struct mv88e6xxx_chip *chip, int port, int eee)
+{
+	u16 reg, val = 0;
+	int err;
+
+	switch (eee) {
+	case EEE_FORCE_ENABLE:
+		val = MV88E6XXX_PORT_MAC_CTL_EEE;
+		fallthrough;
+	case EEE_FORCE_DISABLE:
+		val |= MV88E6XXX_PORT_MAC_CTL_FORCE_EEE;
+		break;
+	default:
+		break;
+	}
+
+	dev_dbg(chip->dev, "p%d: %s eee %sable\n", port,
+		val & MV88E6XXX_PORT_MAC_CTL_FORCE_EEE ? "Force" : "Unforce",
+		val & MV88E6XXX_PORT_MAC_CTL_EEE ? "en" : "dis");
+
+	err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_MAC_CTL, &reg);
+	if (err < 0)
+		return err;
+
+	reg &= ~(MV88E6XXX_PORT_MAC_CTL_EEE | MV88E6XXX_PORT_MAC_CTL_FORCE_EEE);
+	reg |= val;
+
+	return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_MAC_CTL, reg);
+}
+
 static int mv88e6xxx_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
 				    phy_interface_t mode, bool force)
 {
@@ -629,7 +659,6 @@ int mv88e6393x_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
 			      phy_interface_t mode)
 {
 	int err;
-	u16 reg;
 
 	if (port != 0 && port != 9 && port != 10)
 		return -EOPNOTSUPP;
@@ -648,13 +677,7 @@ int mv88e6393x_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
 	}
 
 	/* mv88e6393x errata 4.5: EEE should be disabled on SERDES ports */
-	err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_MAC_CTL, &reg);
-	if (err)
-		return err;
-
-	reg &= ~MV88E6XXX_PORT_MAC_CTL_EEE;
-	reg |= MV88E6XXX_PORT_MAC_CTL_FORCE_EEE;
-	err = mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_MAC_CTL, reg);
+	err = mv88e6xxx_port_set_eee(chip, port, EEE_FORCE_DISABLE);
 	if (err)
 		return err;
 
diff --git a/drivers/net/dsa/mv88e6xxx/port.h b/drivers/net/dsa/mv88e6xxx/port.h
index ddadeb9bfdae..78ef7e2744a1 100644
--- a/drivers/net/dsa/mv88e6xxx/port.h
+++ b/drivers/net/dsa/mv88e6xxx/port.h
@@ -447,6 +447,7 @@ int mv88e6097_port_pause_limit(struct mv88e6xxx_chip *chip, int port, u8 in,
 			       u8 out);
 int mv88e6390_port_pause_limit(struct mv88e6xxx_chip *chip, int port, u8 in,
 			       u8 out);
+int mv88e6xxx_port_set_eee(struct mv88e6xxx_chip *chip, int port, int eee);
 int mv88e6341_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
 			     phy_interface_t mode);
 int mv88e6390_port_set_cmode(struct mv88e6xxx_chip *chip, int port,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:39 +0100

Add phylink EEE control methods to allow EEE to be configured. When
LPI is to be disabled, we force the port to have EEE disabled, but
when enabling EEE, if the port is under the control of the PPU, we
stop forcing it, otherwise we force-enable EEE.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/dsa/mv88e6xxx/chip.c | 45 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index f005ffdbe1c7..d4e83b04aeff 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -1023,6 +1023,49 @@ static void mv88e6xxx_mac_link_up(struct phylink_config *config,
 			"p%d: failed to configure MAC link up\n", port);
 }
 
+static void mv88e6xxx_mac_disable_tx_lpi(struct phylink_config *config)
+{
+	struct dsa_port *dp = dsa_phylink_to_port(config);
+	struct mv88e6xxx_chip *chip = dp->ds->priv;
+	int port = dp->index;
+	int err;
+
+	if (!chip->info->ops->port_set_eee)
+		return;
+
+	mv88e6xxx_reg_lock(chip);
+	err = chip->info->ops->port_set_eee(chip, port, EEE_FORCE_DISABLE);
+	mv88e6xxx_reg_unlock(chip);
+
+	if (err)
+		dev_err(chip->dev, "p%d: failed to set EEE mode: %pe\n", port,
+			ERR_PTR(err));
+}
+
+static void mv88e6xxx_mac_enable_tx_lpi(struct phylink_config *config,
+					u32 timer)
+{
+	struct dsa_port *dp = dsa_phylink_to_port(config);
+	struct mv88e6xxx_chip *chip = dp->ds->priv;
+	int port = dp->index;
+	int eee, err;
+
+	if (!chip->info->ops->port_set_eee)
+		return;
+
+	mv88e6xxx_reg_lock(chip);
+	if (mv88e6xxx_port_ppu_updates(chip, port))
+		eee = EEE_UNFORCED;
+	else
+		eee = EEE_FORCE_DISABLE;
+	err = chip->info->ops->port_set_eee(chip, port, eee);
+	mv88e6xxx_reg_unlock(chip);
+
+	if (err)
+		dev_err(chip->dev, "p%d: failed to set EEE mode: %pe\n", port,
+			ERR_PTR(err));
+}
+
 static int mv88e6xxx_stats_snapshot(struct mv88e6xxx_chip *chip, int port)
 {
 	int err;
@@ -7054,6 +7097,8 @@ static const struct phylink_mac_ops mv88e6xxx_phylink_mac_ops = {
 	.mac_finish		= mv88e6xxx_mac_finish,
 	.mac_link_down		= mv88e6xxx_mac_link_down,
 	.mac_link_up		= mv88e6xxx_mac_link_up,
+	.mac_disable_tx_lpi	= mv88e6xxx_mac_disable_tx_lpi,
+	.mac_enable_tx_lpi	= mv88e6xxx_mac_enable_tx_lpi,
 };
 
 static const struct dsa_switch_ops mv88e6xxx_switch_ops = {

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:39 +0100

Add support to allow DSA drivers to use phylink managed EEE, with only
needing support for controlling the LPI state.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 net/dsa/user.c | 42 ++++++++++++++++++++++++++----------------
 1 file changed, 26 insertions(+), 16 deletions(-)

diff --git a/net/dsa/user.c b/net/dsa/user.c
index f5adfa1d978a..f0e54631f6ab 100644
--- a/net/dsa/user.c
+++ b/net/dsa/user.c
@@ -1231,16 +1231,21 @@ static int dsa_user_set_eee(struct net_device *dev, struct ethtool_keee *e)
 	struct dsa_switch *ds = dp->ds;
 	int ret;
 
-	/* Port's PHY and MAC both need to be EEE capable */
-	if (!dev->phydev || !dp->pl)
-		return -ENODEV;
+	/* If the port is using phylink managed EEE, then get_mac_eee is
+	 * unnecessary.
+	 */
+	if (!dp->pl_config.lpi_capabilities) {
+		/* Port's PHY and MAC both need to be EEE capable */
+		if (!dev->phydev || !dp->pl)
+			return -ENODEV;
 
-	if (!ds->ops->set_mac_eee)
-		return -EOPNOTSUPP;
+		if (!ds->ops->set_mac_eee)
+			return -EOPNOTSUPP;
 
-	ret = ds->ops->set_mac_eee(ds, dp->index, e);
-	if (ret)
-		return ret;
+		ret = ds->ops->set_mac_eee(ds, dp->index, e);
+		if (ret)
+			return ret;
+	}
 
 	return phylink_ethtool_set_eee(dp->pl, e);
 }
@@ -1251,16 +1256,21 @@ static int dsa_user_get_eee(struct net_device *dev, struct ethtool_keee *e)
 	struct dsa_switch *ds = dp->ds;
 	int ret;
 
-	/* Port's PHY and MAC both need to be EEE capable */
-	if (!dev->phydev || !dp->pl)
-		return -ENODEV;
+	/* If the port is using phylink managed EEE, then get_mac_eee is
+	 * unnecessary.
+	 */
+	if (!dp->pl_config.lpi_capabilities) {
+		/* Port's PHY and MAC both need to be EEE capable */
+		if (!dev->phydev || !dp->pl)
+			return -ENODEV;
 
-	if (!ds->ops->get_mac_eee)
-		return -EOPNOTSUPP;
+		if (!ds->ops->get_mac_eee)
+			return -EOPNOTSUPP;
 
-	ret = ds->ops->get_mac_eee(ds, dp->index, e);
-	if (ret)
-		return ret;
+		ret = ds->ops->get_mac_eee(ds, dp->index, e);
+		if (ret)
+			return ret;
+	}
 
 	return phylink_ethtool_get_eee(dp->pl, e);
 }

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:39 +0100

Fixme: doesn't bit 25 and 26 also need to be set in the PMCR for
PMCR_FORCE_EEE100 and PMCR_FORCE_EEE1G to take effect?

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/dsa/mt7530.c | 98 ++++++++++++++++++++++++++----------------------
 drivers/net/dsa/mt7530.h |  6 +--
 2 files changed, 54 insertions(+), 50 deletions(-)

diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c
index ec18e68bf3a8..75a5ff988f0e 100644
--- a/drivers/net/dsa/mt7530.c
+++ b/drivers/net/dsa/mt7530.c
@@ -2439,7 +2439,8 @@ mt7530_setup(struct dsa_switch *ds)
 		 * on all ports until they're enabled later.
 		 */
 		mt7530_rmw(priv, MT753X_PMCR_P(i), PMCR_LINK_SETTINGS_MASK |
-			   MT7530_FORCE_MODE, MT7530_FORCE_MODE);
+			   MT7530_FORCE_MODE | PMCR_FORCE_EEE1G |
+			   PMCR_FORCE_EEE100, MT7530_FORCE_MODE);
 
 		/* Disable forwarding by default on all ports */
 		mt7530_rmw(priv, MT7530_PCR_P(i), PCR_MATRIX_MASK,
@@ -2929,28 +2930,65 @@ static void mt753x_phylink_mac_link_up(struct phylink_config *config,
 			mcr |= PMCR_FORCE_RX_FC_EN;
 	}
 
-	if (mode == MLO_AN_PHY && phydev && phy_init_eee(phydev, false) >= 0) {
-		switch (speed) {
-		case SPEED_1000:
-		case SPEED_2500:
-			mcr |= PMCR_FORCE_EEE1G;
-			break;
-		case SPEED_100:
-			mcr |= PMCR_FORCE_EEE100;
-			break;
-		}
-	}
-
 	mt7530_set(priv, MT753X_PMCR_P(dp->index), mcr);
 }
 
+static void mt753x_phylink_mac_disable_tx_lpi(struct phylink_config *config)
+{
+	struct dsa_port *dp = dsa_phylink_to_port(config);
+	struct mt7530_priv *priv = dp->ds->priv;
+
+	mt7530_clear(priv, MT753X_PMCR_P(dp->index),
+		     PMCR_FORCE_EEE1G | PMCR_FORCE_EEE100);
+}
+
+static void mt753x_phylink_mac_enable_tx_lpi(struct phylink_config *config,
+					     u32 timer)
+{
+	struct dsa_port *dp = dsa_phylink_to_port(config);
+	struct mt7530_priv *priv = dp->ds->priv;
+	u32 val;
+
+	/* If the timer is zero, then set LPI_MODE_EN, which allows the
+	 * system to enter LPI mode immediately rather than waiting for
+	 * the LPI threshold.
+	 */
+	if (!timer)
+		val = LPI_MODE_EN;
+	else if (FIELD_FIT(LPI_THRESH_MASK, timer))
+		val = FIELD_PREP(LPI_THRESH_MASK, timer);
+	else
+		val = LPI_THRESH_MASK;
+
+	mt7530_rmw(priv, MT753X_PMEEECR_P(port),
+		   LPI_THRESH_MASK | LPI_MODE_EN, val);
+
+	/* FIXME: mt7531 docs say that bits 26 and 25 need to be set to
+	 * enable EEE forcing. The PMCR_FORCE_EEE* bits just determine
+	 * whether we force-enable or force-disable these modes.
+	 */
+	mt7530_set(priv, MT753X_PMCR_P(port),
+		   PMCR_FORCE_EEE1G | PMCR_FORCE_EEE100);
+}
+
 static void mt753x_phylink_get_caps(struct dsa_switch *ds, int port,
 				    struct phylink_config *config)
 {
 	struct mt7530_priv *priv = ds->priv;
+	u32 eeecr;
 
 	config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE;
 
+	config->lpi_capabilities = MAC_100FD | MAC_1000FD  MAC_2500FD;
+	config->lpi_timer_limit_us = FIELD_GET(LPI_THRESH_MASK,
+					       LPI_THRESH_MASK);
+
+	eeecr = mt7530_read(priv, MT753X_PMEEECR_P(port));
+	/* tx_lpi_timer should be in microseconds. The time units for
+	 * LPI threshold are unspecified.
+	 */
+	config->eee.tx_lpi_timer = FIELD_GET(LPI_THRESH_MASK, eeecr);
+
 	priv->info->mac_port_get_caps(ds, port, config);
 }
 
@@ -3057,36 +3095,6 @@ mt753x_setup(struct dsa_switch *ds)
 	return ret;
 }
 
-static int mt753x_get_mac_eee(struct dsa_switch *ds, int port,
-			      struct ethtool_keee *e)
-{
-	struct mt7530_priv *priv = ds->priv;
-	u32 eeecr = mt7530_read(priv, MT753X_PMEEECR_P(port));
-
-	e->tx_lpi_enabled = !(eeecr & LPI_MODE_EN);
-	e->tx_lpi_timer = LPI_THRESH_GET(eeecr);
-
-	return 0;
-}
-
-static int mt753x_set_mac_eee(struct dsa_switch *ds, int port,
-			      struct ethtool_keee *e)
-{
-	struct mt7530_priv *priv = ds->priv;
-	u32 set, mask = LPI_THRESH_MASK | LPI_MODE_EN;
-
-	if (e->tx_lpi_timer > 0xFFF)
-		return -EINVAL;
-
-	set = LPI_THRESH_SET(e->tx_lpi_timer);
-	if (!e->tx_lpi_enabled)
-		/* Force LPI Mode without a delay */
-		set |= LPI_MODE_EN;
-	mt7530_rmw(priv, MT753X_PMEEECR_P(port), mask, set);
-
-	return 0;
-}
-
 static void
 mt753x_conduit_state_change(struct dsa_switch *ds,
 			    const struct net_device *conduit,
@@ -3163,8 +3171,6 @@ const struct dsa_switch_ops mt7530_switch_ops = {
 	.port_mirror_add	= mt753x_port_mirror_add,
 	.port_mirror_del	= mt753x_port_mirror_del,
 	.phylink_get_caps	= mt753x_phylink_get_caps,
-	.get_mac_eee		= mt753x_get_mac_eee,
-	.set_mac_eee		= mt753x_set_mac_eee,
 	.conduit_state_change	= mt753x_conduit_state_change,
 };
 EXPORT_SYMBOL_GPL(mt7530_switch_ops);
@@ -3174,6 +3180,8 @@ static const struct phylink_mac_ops mt753x_phylink_mac_ops = {
 	.mac_config	= mt753x_phylink_mac_config,
 	.mac_link_down	= mt753x_phylink_mac_link_down,
 	.mac_link_up	= mt753x_phylink_mac_link_up,
+	.mac_disable_tx_lpi = mt753x_phylink_mac_disable_tx_lpi,
+	.mac_enable_tx_lpi = mt753x_phylink_mac_enable_tx_lpi,
 };
 
 const struct mt753x_info mt753x_table[] = {
diff --git a/drivers/net/dsa/mt7530.h b/drivers/net/dsa/mt7530.h
index 28592123070b..9adb1a7a1fa0 100644
--- a/drivers/net/dsa/mt7530.h
+++ b/drivers/net/dsa/mt7530.h
@@ -352,12 +352,8 @@ enum mt7530_vlan_port_acc_frm {
 					 MT7531_FORCE_MODE_SPD | \
 					 MT7531_FORCE_MODE_DPX | \
 					 MT7531_FORCE_MODE_RX_FC | \
-					 MT7531_FORCE_MODE_TX_FC | \
-					 MT7531_FORCE_MODE_EEE100 | \
-					 MT7531_FORCE_MODE_EEE1G)
+					 MT7531_FORCE_MODE_TX_FC)
 #define  PMCR_LINK_SETTINGS_MASK	(PMCR_MAC_TX_EN | PMCR_MAC_RX_EN | \
-					 PMCR_FORCE_EEE1G | \
-					 PMCR_FORCE_EEE100 | \
 					 PMCR_FORCE_RX_FC_EN | \
 					 PMCR_FORCE_TX_FC_EN | \
 					 PMCR_FORCE_SPEED_1000 | \

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:40 +0100

Allow EEE management for SFPs without accessible PHYs.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 46c3b98bec70..120d8c385362 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -2866,6 +2866,8 @@ int phylink_init_eee(struct phylink *pl, bool clk_stop_enable)
 
 	if (pl->phydev)
 		ret = phy_init_eee(pl->phydev, clk_stop_enable);
+	else if (pl->sfp_bus && phylink_mac_supports_eee(pl))
+		ret = 0;
 
 	return ret;
 }
@@ -2882,8 +2884,13 @@ int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_keee *eee)
 
 	ASSERT_RTNL();
 
-	if (pl->phydev)
+	if (pl->phydev) {
 		ret = phy_ethtool_get_eee(pl->phydev, eee);
+	} else if (pl->sfp_bus && phylink_mac_supports_eee(pl)) {
+		/* For optical SFPs, ensure that eee->supported is nonzero. */
+		__set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, eee->supported);
+		ret = 0;
+	}
 
 	if (!ret && phylink_mac_supports_eee(pl)) {
 		/* Overwrite phylib's interpretation of configuration */
@@ -2924,6 +2931,8 @@ int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_keee *eee)
 
 	if (pl->phydev)
 		ret = phy_ethtool_set_eee(pl->phydev, eee);
+	else if (pl->sfp_bus && phylink_mac_supports_eee(pl))
+		ret = 0;
 
 	if (!ret && mac_eee) {
 		bool can_lpi, old_can_lpi;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:40 +0100

When configuring for a SFP PHY, validate that the MAC supports the
interface returned from sfp_select_interface(). This is a preparatory
step to reorganising how a PHY on a SFP module is handled.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 43 ++++++++++++++++++++++++++++++-------------
 1 file changed, 30 insertions(+), 13 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 120d8c385362..04ac09f566cc 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -2462,6 +2462,32 @@ int phylink_ethtool_set_wol(struct phylink *pl, struct ethtool_wolinfo *wol)
 }
 EXPORT_SYMBOL_GPL(phylink_ethtool_set_wol);
 
+static phy_interface_t phylink_sfp_select_interface(struct phylink *pl,
+						const unsigned long *link_modes)
+{
+	phy_interface_t interface;
+
+	interface = sfp_select_interface(pl->sfp_bus, link_modes);
+	if (interface == PHY_INTERFACE_MODE_NA) {
+		phylink_err(pl,
+			    "selection of interface failed, advertisement %*pb\n",
+			    __ETHTOOL_LINK_MODE_MASK_NBITS,
+			    link_modes);
+		return interface;
+	}
+
+	if (!test_bit(interface, pl->config->supported_interfaces)) {
+		phylink_err(pl,
+			    "selection of interface failed - SFP selected %s (%u) but MAC supports %*pbl\n",
+			    phy_modes(interface), interface,
+			    (int)PHY_INTERFACE_MODE_MAX,
+			    pl->config->supported_interfaces);
+		return PHY_INTERFACE_MODE_NA;
+	}
+
+	return interface;
+}
+
 static void phylink_merge_link_mode(unsigned long *dst, const unsigned long *b)
 {
 	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask);
@@ -2644,15 +2670,10 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
 	 * link can be configured correctly.
 	 */
 	if (pl->sfp_bus) {
-		config.interface = sfp_select_interface(pl->sfp_bus,
+		config.interface = phylink_sfp_select_interface(pl,
 							config.advertising);
-		if (config.interface == PHY_INTERFACE_MODE_NA) {
-			phylink_err(pl,
-				    "selection of interface failed, advertisement %*pb\n",
-				    __ETHTOOL_LINK_MODE_MASK_NBITS,
-				    config.advertising);
+		if (config.interface == PHY_INTERFACE_MODE_NA)
 			return -EINVAL;
-		}
 
 		/* Revalidate with the selected interface */
 		linkmode_copy(support, pl->supported);
@@ -3336,13 +3357,9 @@ static int phylink_sfp_config_phy(struct phylink *pl, u8 mode,
 		return ret;
 	}
 
-	iface = sfp_select_interface(pl->sfp_bus, config.advertising);
-	if (iface == PHY_INTERFACE_MODE_NA) {
-		phylink_err(pl,
-			    "selection of interface failed, advertisement %*pb\n",
-			    __ETHTOOL_LINK_MODE_MASK_NBITS, config.advertising);
+	iface = phylink_sfp_select_interface(pl, config.advertising);
+	if (iface == PHY_INTERFACE_MODE_NA)
 		return -EINVAL;
-	}
 
 	config.interface = iface;
 	linkmode_copy(support1, support);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:40 +0100

There are a few issues with how SFP PHYs are attached:

a) The phylink_sfp_connect_phy() and phylink_sfp_config_phy() code
   validates the configuration three times:

1. To discover the support/advertising masks that the PHY/PCS/MAC
   can support in order to select an interface.
2. To validate the selected interface.
3. When the PHY is brought up after being attached, another validation
   is done.

   This is needlessly complex.

b) The configuration is set prior to the PHY being attached, which
   means we don't have the PHY available in phylink_major_config()
   for phylink_pcs_neg_mode() to make decisions upon.

We have already added an extra step to validate the selected interface,
so we can now move the attachment and bringup of the PHY earlier,
inside phylink_sfp_config_phy(). This results in the validation at
step 2 above becoming entirely unnecessary, so remove that too.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 43 ++++++++++++++-----------------------------
 1 file changed, 14 insertions(+), 29 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 04ac09f566cc..4cd5e849dcb7 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -3333,10 +3333,8 @@ static void phylink_sfp_set_config(struct phylink *pl, u8 mode,
 static int phylink_sfp_config_phy(struct phylink *pl, u8 mode,
 				  struct phy_device *phy)
 {
-	__ETHTOOL_DECLARE_LINK_MODE_MASK(support1);
 	__ETHTOOL_DECLARE_LINK_MODE_MASK(support);
 	struct phylink_link_state config;
-	phy_interface_t iface;
 	int ret;
 
 	linkmode_copy(support, phy->supported);
@@ -3357,20 +3355,21 @@ static int phylink_sfp_config_phy(struct phylink *pl, u8 mode,
 		return ret;
 	}
 
-	iface = phylink_sfp_select_interface(pl, config.advertising);
-	if (iface == PHY_INTERFACE_MODE_NA)
+	config.interface = phylink_sfp_select_interface(pl, config.advertising);
+	if (config.interface == PHY_INTERFACE_MODE_NA)
 		return -EINVAL;
 
-	config.interface = iface;
-	linkmode_copy(support1, support);
-	ret = phylink_validate(pl, support1, &config);
-	if (ret) {
-		phylink_err(pl,
-			    "validation of %s/%s with support %*pb failed: %pe\n",
-			    phylink_an_mode_str(mode),
-			    phy_modes(config.interface),
-			    __ETHTOOL_LINK_MODE_MASK_NBITS, support,
-			    ERR_PTR(ret));
+	/* Attach the PHY so that the PHY is present when we do the major
+	 * configuration step.
+	 */
+	ret = phylink_attach_phy(pl, phy, config.interface);
+	if (ret < 0)
+		return ret;
+
+	/* This will validate the configuration for us. */
+	ret = phylink_bringup_phy(pl, phy, config.interface);
+	if (ret < 0) {
+		phy_detach(phy);
 		return ret;
 	}
 
@@ -3528,7 +3527,6 @@ static bool phylink_phy_no_inband(struct phy_device *phy)
 static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
 {
 	struct phylink *pl = upstream;
-	phy_interface_t interface;
 	u8 mode;
 	int ret;
 
@@ -3551,20 +3549,7 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
 			  pl->config->supported_interfaces);
 
 	/* Do the initial configuration */
-	ret = phylink_sfp_config_phy(pl, mode, phy);
-	if (ret < 0)
-		return ret;
-
-	interface = pl->link_config.interface;
-	ret = phylink_attach_phy(pl, phy, interface);
-	if (ret < 0)
-		return ret;
-
-	ret = phylink_bringup_phy(pl, phy, interface);
-	if (ret)
-		phy_detach(phy);
-
-	return ret;
+	return phylink_sfp_config_phy(pl, mode, phy);
 }
 
 static void phylink_sfp_disconnect_phy(void *upstream)

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:40 +0100

There is an interdependence between the link_an_mode and pcs_neg_mode
that at least drivers rely upon. In order to support changing the MAC
link_an_mode when we switch interfaces, we need to keep track of its
original setting so that we can appropriately re-calculate pcs_neg_mode
from the same starting point.

Split the current link_an_mode into a requested link_an_mode and an
active link_an_mode. This will allow us to keep the MAC's link_an_mode
synchronised with the pcs_neg_mode should we decide to switch to
outband with a PHY.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 60 ++++++++++++++++++++++++++---------------------
 1 file changed, 33 insertions(+), 27 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 4cd5e849dcb7..7682a2610388 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -56,7 +56,8 @@ struct phylink {
 	struct phy_device *phydev;
 	phy_interface_t link_interface;	/* PHY_INTERFACE_xxx */
 	u8 cfg_link_an_mode;		/* MLO_AN_xxx */
-	u8 cur_link_an_mode;
+	u8 req_link_an_mode;		/* Requested MLO_AN_xxx mode */
+	u8 act_link_an_mode;		/* Active MLO_AN_xxx mode */
 	u8 link_port;			/* The current non-phy ethtool port */
 	__ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
 
@@ -1075,13 +1076,13 @@ static void phylink_mac_config(struct phylink *pl,
 
 	phylink_dbg(pl,
 		    "%s: mode=%s/%s/%s adv=%*pb pause=%02x\n",
-		    __func__, phylink_an_mode_str(pl->cur_link_an_mode),
+		    __func__, phylink_an_mode_str(pl->act_link_an_mode),
 		    phy_modes(st.interface),
 		    phy_rate_matching_to_str(st.rate_matching),
 		    __ETHTOOL_LINK_MODE_MASK_NBITS, st.advertising,
 		    st.pause);
 
-	pl->mac_ops->mac_config(pl->config, pl->cur_link_an_mode, &st);
+	pl->mac_ops->mac_config(pl->config, pl->act_link_an_mode, &st);
 }
 
 static void phylink_pcs_an_restart(struct phylink *pl)
@@ -1089,7 +1090,7 @@ static void phylink_pcs_an_restart(struct phylink *pl)
 	if (pl->pcs && linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
 					 pl->link_config.advertising) &&
 	    phy_interface_mode_is_8023z(pl->link_config.interface) &&
-	    phylink_autoneg_inband(pl->cur_link_an_mode))
+	    phylink_autoneg_inband(pl->act_link_an_mode))
 		pl->pcs->ops->pcs_an_restart(pl->pcs);
 }
 
@@ -1171,7 +1172,7 @@ static void phylink_major_config(struct phylink *pl, bool restart,
 
 	phylink_dbg(pl, "major config %s\n", phy_modes(state->interface));
 
-	pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
+	pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->req_link_an_mode,
 						state->interface,
 						state->advertising);
 
@@ -1187,10 +1188,12 @@ static void phylink_major_config(struct phylink *pl, bool restart,
 		pcs_changed = pl->pcs != pcs;
 	}
 
+	pl->act_link_an_mode = pl->req_link_an_mode;
+
 	phylink_pcs_poll_stop(pl);
 
 	if (pl->mac_ops->mac_prepare) {
-		err = pl->mac_ops->mac_prepare(pl->config, pl->cur_link_an_mode,
+		err = pl->mac_ops->mac_prepare(pl->config, pl->act_link_an_mode,
 					       state->interface);
 		if (err < 0) {
 			phylink_err(pl, "mac_prepare failed: %pe\n",
@@ -1224,7 +1227,7 @@ static void phylink_major_config(struct phylink *pl, bool restart,
 	if (pl->pcs_state == PCS_STATE_STARTING || pcs_changed)
 		phylink_pcs_enable(pl->pcs);
 
-	neg_mode = pl->cur_link_an_mode;
+	neg_mode = pl->act_link_an_mode;
 	if (pl->pcs && pl->pcs->neg_mode)
 		neg_mode = pl->pcs_neg_mode;
 
@@ -1240,7 +1243,7 @@ static void phylink_major_config(struct phylink *pl, bool restart,
 		phylink_pcs_an_restart(pl);
 
 	if (pl->mac_ops->mac_finish) {
-		err = pl->mac_ops->mac_finish(pl->config, pl->cur_link_an_mode,
+		err = pl->mac_ops->mac_finish(pl->config, pl->act_link_an_mode,
 					      state->interface);
 		if (err < 0)
 			phylink_err(pl, "mac_finish failed: %pe\n",
@@ -1271,17 +1274,19 @@ static int phylink_change_inband_advert(struct phylink *pl)
 		return 0;
 
 	phylink_dbg(pl, "%s: mode=%s/%s adv=%*pb pause=%02x\n", __func__,
-		    phylink_an_mode_str(pl->cur_link_an_mode),
+		    phylink_an_mode_str(pl->req_link_an_mode),
 		    phy_modes(pl->link_config.interface),
 		    __ETHTOOL_LINK_MODE_MASK_NBITS, pl->link_config.advertising,
 		    pl->link_config.pause);
 
 	/* Recompute the PCS neg mode */
-	pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
+	pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->req_link_an_mode,
 					pl->link_config.interface,
 					pl->link_config.advertising);
 
-	neg_mode = pl->cur_link_an_mode;
+	pl->act_link_an_mode = pl->req_link_an_mode;
+
+	neg_mode = pl->act_link_an_mode;
 	if (pl->pcs->neg_mode)
 		neg_mode = pl->pcs_neg_mode;
 
@@ -1346,7 +1351,7 @@ static void phylink_mac_initial_config(struct phylink *pl, bool force_restart)
 {
 	struct phylink_link_state link_state;
 
-	switch (pl->cur_link_an_mode) {
+	switch (pl->req_link_an_mode) {
 	case MLO_AN_PHY:
 		link_state = pl->phy_state;
 		break;
@@ -1489,14 +1494,14 @@ static void phylink_link_up(struct phylink *pl,
 
 	pl->cur_interface = link_state.interface;
 
-	neg_mode = pl->cur_link_an_mode;
+	neg_mode = pl->act_link_an_mode;
 	if (pl->pcs && pl->pcs->neg_mode)
 		neg_mode = pl->pcs_neg_mode;
 
 	phylink_pcs_link_up(pl->pcs, neg_mode, pl->cur_interface, speed,
 			    duplex);
 
-	pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->cur_link_an_mode,
+	pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->act_link_an_mode,
 				 pl->cur_interface, speed, duplex,
 				 !!(link_state.pause & MLO_PAUSE_TX), rx_pause);
 
@@ -1522,7 +1527,7 @@ static void phylink_link_down(struct phylink *pl)
 
 	phylink_deactivate_eee(pl);
 
-	pl->mac_ops->mac_link_down(pl->config, pl->cur_link_an_mode,
+	pl->mac_ops->mac_link_down(pl->config, pl->act_link_an_mode,
 				   pl->cur_interface);
 	phylink_info(pl, "Link is Down\n");
 }
@@ -1550,7 +1555,7 @@ static void phylink_resolve(struct work_struct *w)
 		link_state.link = false;
 		retrigger = true;
 	} else {
-		switch (pl->cur_link_an_mode) {
+		switch (pl->act_link_an_mode) {
 		case MLO_AN_PHY:
 			link_state = pl->phy_state;
 			phylink_apply_manual_flow(pl, &link_state);
@@ -1789,7 +1794,8 @@ struct phylink *phylink_create(struct phylink_config *config,
 		}
 	}
 
-	pl->cur_link_an_mode = pl->cfg_link_an_mode;
+	pl->req_link_an_mode = pl->cfg_link_an_mode;
+	pl->act_link_an_mode = pl->req_link_an_mode;
 
 	ret = phylink_register_sfp(pl, fwnode);
 	if (ret < 0) {
@@ -2251,7 +2257,7 @@ void phylink_start(struct phylink *pl)
 	ASSERT_RTNL();
 
 	phylink_info(pl, "configuring for %s/%s link mode\n",
-		     phylink_an_mode_str(pl->cur_link_an_mode),
+		     phylink_an_mode_str(pl->req_link_an_mode),
 		     phy_modes(pl->link_config.interface));
 
 	/* Always set the carrier off */
@@ -2536,7 +2542,7 @@ int phylink_ethtool_ksettings_get(struct phylink *pl,
 
 	linkmode_copy(kset->link_modes.supported, pl->supported);
 
-	switch (pl->cur_link_an_mode) {
+	switch (pl->act_link_an_mode) {
 	case MLO_AN_FIXED:
 		/* We are using fixed settings. Report these as the
 		 * current link settings - and note that these also
@@ -2628,7 +2634,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
 		/* If we have a fixed link, refuse to change link parameters.
 		 * If the link parameters match, accept them but do nothing.
 		 */
-		if (pl->cur_link_an_mode == MLO_AN_FIXED) {
+		if (pl->req_link_an_mode == MLO_AN_FIXED) {
 			if (s->speed != pl->link_config.speed ||
 			    s->duplex != pl->link_config.duplex)
 				return -EINVAL;
@@ -2644,7 +2650,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
 		 * is our default case) but do not allow the advertisement to
 		 * be changed. If the advertisement matches, simply return.
 		 */
-		if (pl->cur_link_an_mode == MLO_AN_FIXED) {
+		if (pl->req_link_an_mode == MLO_AN_FIXED) {
 			if (!linkmode_equal(config.advertising,
 					    pl->link_config.advertising))
 				return -EINVAL;
@@ -2679,7 +2685,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
 		linkmode_copy(support, pl->supported);
 		if (phylink_validate(pl, support, &config)) {
 			phylink_err(pl, "validation of %s/%s with support %*pb failed\n",
-				    phylink_an_mode_str(pl->cur_link_an_mode),
+				    phylink_an_mode_str(pl->req_link_an_mode),
 				    phy_modes(config.interface),
 				    __ETHTOOL_LINK_MODE_MASK_NBITS, support);
 			return -EINVAL;
@@ -2779,7 +2785,7 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl,
 
 	ASSERT_RTNL();
 
-	if (pl->cur_link_an_mode == MLO_AN_FIXED)
+	if (pl->req_link_an_mode == MLO_AN_FIXED)
 		return -EOPNOTSUPP;
 
 	if (!phylink_test(pl->supported, Pause) &&
@@ -3098,7 +3104,7 @@ static int phylink_mii_read(struct phylink *pl, unsigned int phy_id,
 	struct phylink_link_state state;
 	int val = 0xffff;
 
-	switch (pl->cur_link_an_mode) {
+	switch (pl->act_link_an_mode) {
 	case MLO_AN_FIXED:
 		if (phy_id == 0) {
 			phylink_get_fixed_state(pl, &state);
@@ -3123,7 +3129,7 @@ static int phylink_mii_read(struct phylink *pl, unsigned int phy_id,
 static int phylink_mii_write(struct phylink *pl, unsigned int phy_id,
 			     unsigned int reg, unsigned int val)
 {
-	switch (pl->cur_link_an_mode) {
+	switch (pl->act_link_an_mode) {
 	case MLO_AN_FIXED:
 		break;
 
@@ -3313,9 +3319,9 @@ static void phylink_sfp_set_config(struct phylink *pl, u8 mode,
 		changed = true;
 	}
 
-	if (pl->cur_link_an_mode != mode ||
+	if (pl->req_link_an_mode != mode ||
 	    pl->link_config.interface != state->interface) {
-		pl->cur_link_an_mode = mode;
+		pl->req_link_an_mode = mode;
 		pl->link_config.interface = state->interface;
 
 		changed = true;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:40 +0100

phylink_pcs_neg_mode() currently only outband mode when we are using
MLO_AN_PHY or MLO_AN_FIXED. However, we want to extend the use of this
to MLO_AN_INBAND when the PCS and/or PHY are unable to use inband mode.

For example, when a PHY is not capable of supporting inband mode, we
need to operate in outband mode. However, using outband mode for the
PCS while using MLO_AN_INBAND causes drivers to misbehave as it is not
a situation they are expecting. Therefore, we need to switch to
MLO_AN_PHY when using outband mode.

Add the logic to select MLO_AN_PHY mode when phylink_pcs_neg_mode()
indicates that it will be using outband mode with a PHY and the
requested mode is MLO_AN_INBAND.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 7682a2610388..e69479c75705 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1161,6 +1161,15 @@ static unsigned int phylink_pcs_neg_mode(unsigned int mode,
 	return neg_mode;
 }
 
+static u8 phylink_choose_act_link_an_mode(struct phylink *pl)
+{
+	if (pl->req_link_an_mode == MLO_AN_INBAND && pl->phydev &&
+	    pl->pcs_neg_mode == PHYLINK_PCS_NEG_OUTBAND)
+		return MLO_AN_PHY;
+
+	return pl->req_link_an_mode;
+}
+
 static void phylink_major_config(struct phylink *pl, bool restart,
 				  const struct phylink_link_state *state)
 {
@@ -1188,7 +1197,10 @@ static void phylink_major_config(struct phylink *pl, bool restart,
 		pcs_changed = pl->pcs != pcs;
 	}
 
-	pl->act_link_an_mode = pl->req_link_an_mode;
+	/* set the active link AN mode, which may end up different from the
+	 * current link AN mode depending on the PCS and PHY capabilities.
+	 */
+	pl->act_link_an_mode = phylink_choose_act_link_an_mode(pl);

@@ -1284,7 +1296,10 @@ static int phylink_change_inband_advert(struct phylink *pl)
 					pl->link_config.interface,
 					pl->link_config.advertising);
 
-	pl->act_link_an_mode = pl->req_link_an_mode;
+	/* set the active link AN mode, which may end up different from the
+	 * current link AN mode depending on the PCS and PHY capabilities.
+	 */
+	pl->act_link_an_mode = phylink_choose_act_link_an_mode(pl);
 
 	neg_mode = pl->act_link_an_mode;
 	if (pl->pcs->neg_mode)

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:40 +0100

Now that we have a more complexity in phylink_major_config(), augment
the debugging so we can see what's going on there.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 27 ++++++++++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index e69479c75705..14522787b84e 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -178,6 +178,24 @@ static const char *phylink_an_mode_str(unsigned int mode)
 	return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
 }
 
+static const char *phylink_pcs_mode_str(unsigned int mode)
+{
+	if (!mode)
+		return "none";
+
+	if (mode & PHYLINK_PCS_NEG_OUTBAND)
+		return "outband";
+
+	if (mode & PHYLINK_PCS_NEG_INBAND) {
+		if (mode & PHYLINK_PCS_NEG_ENABLED)
+			return "inband/an-enabled";
+		else
+			return "inband/an-disabled";
+	}
+
+	return "unknown";
+}
+
 static unsigned int phylink_interface_signal_rate(phy_interface_t interface)
 {
 	switch (interface) {
@@ -1179,7 +1197,9 @@ static void phylink_major_config(struct phylink *pl, bool restart,
 	unsigned int neg_mode;
 	int err;
 
-	phylink_dbg(pl, "major config %s\n", phy_modes(state->interface));
+	phylink_dbg(pl, "major config, requested %s/%s\n",
+		    phylink_an_mode_str(pl->req_link_an_mode),
+		    phy_modes(state->interface));
 
 	pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->req_link_an_mode,
 						state->interface,
@@ -1202,6 +1222,11 @@ static void phylink_major_config(struct phylink *pl, bool restart,
 	 */
 	pl->act_link_an_mode = phylink_choose_act_link_an_mode(pl);
 
+	phylink_dbg(pl, "major config, active %s/%s/%s\n",
+		    phylink_an_mode_str(pl->act_link_an_mode),
+		    phylink_pcs_mode_str(pl->pcs_neg_mode),
+		    phy_modes(state->interface));
+
 	phylink_pcs_poll_stop(pl);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:40 +0100

Move the call to phylink_pcs_neg_mode() in phylink_major_config() after
we have selected the appropriate PCS to later allow the capabilities of
the PCS to be used when deciding which pcs_neg_mode should be used.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 14522787b84e..66a5212b8c90 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1201,10 +1201,6 @@ static void phylink_major_config(struct phylink *pl, bool restart,
 		    phylink_an_mode_str(pl->req_link_an_mode),
 		    phy_modes(state->interface));
 
-	pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->req_link_an_mode,
-						state->interface,
-						state->advertising);
-
 	if (pl->mac_ops->mac_select_pcs) {
 		pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
 		if (IS_ERR(pcs)) {
@@ -1217,6 +1213,10 @@ static void phylink_major_config(struct phylink *pl, bool restart,
 		pcs_changed = pl->pcs != pcs;
 	}
 
+	pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->req_link_an_mode,
+						state->interface,
+						state->advertising);
+
 	/* set the active link AN mode, which may end up different from the
 	 * current link AN mode depending on the PCS and PHY capabilities.
 	 */

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:40 +0100

Add a method to query the PHY's in-band capabilities for a PHY
interface mode. This can be used to determine for the specified
interface mode whether in-band signalling is supported, and whether
the PHY requires in-band signalling.

When not implemented, or the PHY driver doesn't report any modes
for the interface, LINK_INBAND_VALID will not be set. When set, the
remainder of the flags can be interpreted.

LINK_INBAND_POSSIBLE means that the device can be configured to use
or not use in-band signalling. Later patches may add support to
configure this at the PHY.

LINK_INBAND_REQUIRED means that the device uses in-band signalling
which can not be disabled.

When only LINK_INBAND_VALID has been set, this means that the device
does not support any in-band signalling, and can't be configured to
do so.

"Bypass" mode (where the device may be configured for in-band, but
may still bring the link up if there is no in-band received from the
link partner) is not considered in this patch.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phy.c | 21 +++++++++++++++++++++
 include/linux/phy.h   | 28 ++++++++++++++++++++++++++++
 2 files changed, 49 insertions(+)

diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index 785182fa5fe0..2f8721d8117d 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -1000,6 +1000,27 @@ static int phy_check_link_status(struct phy_device *phydev)
 	return 0;
 }
 
+/**
+ * phy_query_inband - query which in-band signalling modes are supported
+ * @phydev: a pointer to a &struct phy_device
+ * @interface: the interface mode for the PHY
+ *
+ * Returns zero if it is unknown what in-band signalling is supported by the
+ * PHY (e.g. because the PHY driver doesn't implement the method.) Otherwise,
+ * returns a bit mask of the LINK_INBAND_* values from
+ * &enum link_inband_signalling to describe which inband modes are supported
+ * for this interface mode.
+ */
+unsigned int phy_query_inband(struct phy_device *phydev,
+			      phy_interface_t interface)
+{
+	if (phydev->drv && phydev->drv->query_inband)
+		return phydev->drv->query_inband(phydev, interface);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(phy_query_inband);
+
 /**
  * _phy_start_aneg - start auto-negotiation for this PHY device
  * @phydev: the phy_device struct
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 6b7d40d49129..6a41cdfee21a 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -814,6 +814,24 @@ struct phy_tdr_config {
 };
 #define PHY_PAIR_ALL -1
 
+/**
+ * enum link_inband_signalling - inband signalling modes that are supported
+ *
+ * @LINK_INBAND_DISABLE: inband signalling can be disabled
+ * @LINK_INBAND_ENABLE: inband signalling can be enabled
+ * @LINK_INBAND_BYPASS: only valid with LINK_INBAND_ENABLE and supports bypass
+ *
+ * The possible and required bits can only be used if the valid bit is set.
+ * If possible is clear, that means inband signalling can not be used.
+ * Required is only valid when possible is set, and means that inband
+ * signalling must be used.
+ */
+enum link_inband_signalling {
+	LINK_INBAND_DISABLE		= BIT(0),
+	LINK_INBAND_ENABLE		= BIT(1),
+	LINK_INBAND_BYPASS		= BIT(2),
+};
+
 /**
  * struct phy_plca_cfg - Configuration of the PLCA (Physical Layer Collision
  * Avoidance) Reconciliation Sublayer.
@@ -952,6 +970,14 @@ struct phy_driver {
 	 */
 	int (*get_features)(struct phy_device *phydev);
 
+	/**
+	 * @query_inband: query whether inband is supported for the given PHY
+	 * interface mode. Returns a bitmask of bits defined by enum
+	 * link_inband_signalling.
+	 */
+	unsigned int (*query_inband)(struct phy_device *phydev,
+				     phy_interface_t interface);
+
 	/**
 	 * @get_rate_matching: Get the supported type of rate matching for a
 	 * particular phy interface. This is used by phy consumers to determine
@@ -1800,6 +1826,8 @@ int phy_config_aneg(struct phy_device *phydev);
 int _phy_start_aneg(struct phy_device *phydev);
 int phy_start_aneg(struct phy_device *phydev);
 int phy_aneg_done(struct phy_device *phydev);
+unsigned int phy_query_inband(struct phy_device *phydev,
+			      phy_interface_t interface);
 int phy_speed_down(struct phy_device *phydev, bool sync);
 int phy_speed_up(struct phy_device *phydev);
 bool phy_check_valid(int speed, int duplex, unsigned long *features);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:40 +0100

BCM84881 has no support for inband signalling, so this is a trivial
implementation that returns no support for inband.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/bcm84881.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/net/phy/bcm84881.c b/drivers/net/phy/bcm84881.c
index f1d47c264058..69540f1dd32e 100644
--- a/drivers/net/phy/bcm84881.c
+++ b/drivers/net/phy/bcm84881.c
@@ -235,11 +235,21 @@ static int bcm84881_read_status(struct phy_device *phydev)
 	return genphy_c45_read_mdix(phydev);
 }
 
+/* The Broadcom BCM84881 in the Methode DM7052 is unable to provide a SGMII
+ * or 802.3z control word, so inband will not work.
+ */
+static unsigned int bcm84881_query_inband(struct phy_device *phydev,
+					  phy_interface_t interface)
+{
+	return LINK_INBAND_DISABLE;
+}
+
 static struct phy_driver bcm84881_drivers[] = {
 	{
 		.phy_id		= 0xae025150,
 		.phy_id_mask	= 0xfffffff0,
 		.name		= "Broadcom BCM84881",
+		.query_inband	= bcm84881_query_inband,
 		.config_init	= bcm84881_config_init,
 		.probe		= bcm84881_probe,
 		.get_features	= bcm84881_get_features,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:41 +0100

Provide an implementation for phy_query_inband() for Marvell PHYs used
on SFP modules.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/marvell.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c
index b89fbffa6a93..963dcd6933d6 100644
--- a/drivers/net/phy/marvell.c
+++ b/drivers/net/phy/marvell.c
@@ -716,6 +716,19 @@ static int marvell_config_aneg_fiber(struct phy_device *phydev)
 	return genphy_check_and_restart_aneg(phydev, changed);
 }
 
+static unsigned int m88e1111_query_inband(struct phy_device *phydev,
+					  phy_interface_t interface)
+{
+	/* In 1000base-X and SGMII modes, the inband mode can be changed
+	 * through the Fibre page BMCR ANENABLE bit.
+	 */
+	if (interface == PHY_INTERFACE_MODE_1000BASEX ||
+	    interface == PHY_INTERFACE_MODE_SGMII)
+		return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+
+	return 0;
+}
+
 static int m88e1111_config_aneg(struct phy_device *phydev)
 {
 	int extsr = phy_read(phydev, MII_M1111_PHY_EXT_SR);
@@ -3665,6 +3678,7 @@ static struct phy_driver marvell_drivers[] = {
 		.name = "Marvell 88E1112",
 		/* PHY_GBIT_FEATURES */
 		.probe = marvell_probe,
+		.query_inband = m88e1111_query_inband,
 		.config_init = m88e1112_config_init,
 		.config_aneg = marvell_config_aneg,
 		.config_intr = marvell_config_intr,
@@ -3686,6 +3700,7 @@ static struct phy_driver marvell_drivers[] = {
 		/* PHY_GBIT_FEATURES */
 		.flags = PHY_POLL_CABLE_TEST,
 		.probe = marvell_probe,
+		.query_inband = m88e1111_query_inband,
 		.config_init = m88e1111gbe_config_init,
 		.config_aneg = m88e1111_config_aneg,
 		.read_status = marvell_read_status,
@@ -3709,6 +3724,7 @@ static struct phy_driver marvell_drivers[] = {
 		.name = "Marvell 88E1111 (Finisar)",
 		/* PHY_GBIT_FEATURES */
 		.probe = marvell_probe,
+		.query_inband = m88e1111_query_inband,
 		.config_init = m88e1111gbe_config_init,
 		.config_aneg = m88e1111_config_aneg,
 		.read_status = marvell_read_status,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:41 +0100

Add a pcs_query_inband() method to query the PCS for its inband link
capabilities, and use this to determine whether link modes used with
optical SFPs can be supported.

When a PCS does not provide a method, we allow inband negotiation to
be either on or off, making this a no-op until the pcs_query_inband()
method is implemented by a PCS driver.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/phylink.h   | 17 ++++++++++++++
 2 files changed, 77 insertions(+)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 66a5212b8c90..2ffd60687e0e 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1000,6 +1000,15 @@ static void phylink_resolve_an_pause(struct phylink_link_state *state)
 	}
 }
 
+static unsigned int phylink_pcs_query_inband(struct phylink_pcs *pcs,
+				    phy_interface_t interface)
+{
+	if (pcs && pcs->ops->pcs_query_inband)
+		return pcs->ops->pcs_query_inband(pcs, interface);
+
+	return 0;
+}
+
 static void phylink_pcs_pre_config(struct phylink_pcs *pcs,
 				   phy_interface_t interface)
 {
@@ -1053,6 +1062,24 @@ static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
 		pcs->ops->pcs_link_up(pcs, neg_mode, interface, speed, duplex);
 }
 
+/* Query inband for a specific interface mode, asking the MAC for the
+ * PCS which will be used to handle the interface mode.
+ */
+static unsigned int phylink_query_inband(struct phylink *pl,
+					 phy_interface_t interface)
+{
+	struct phylink_pcs *pcs;
+
+	if (!pl->using_mac_select_pcs)
+		return 0;
+
+	pcs = pl->mac_ops->mac_select_pcs(pl->config, interface);
+	if (!pcs)
+		return 0;
+
+	return phylink_pcs_query_inband(pcs, interface);
+}
+
 static void phylink_pcs_poll_stop(struct phylink *pl)
 {
 	if (pl->cfg_link_an_mode == MLO_AN_INBAND)
@@ -2613,6 +2640,26 @@ int phylink_ethtool_ksettings_get(struct phylink *pl,
 }
 EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_get);
 
+static bool phylink_validate_pcs_inband_autoneg(struct phylink *pl,
+					        phy_interface_t interface,
+						unsigned long *adv)
+{
+	unsigned int inband = phylink_query_inband(pl, interface);
+	unsigned int mask;
+
+	/* If the PCS doesn't implement inband support, be permissive. */
+	if (!inband)
+		return true;
+
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, adv))
+		mask = LINK_INBAND_ENABLE;
+	else
+		mask = LINK_INBAND_DISABLE;
+
+	/* Check whether the PCS implements the required mode */
+	return !!(inband & mask);
+}
+
 /**
  * phylink_ethtool_ksettings_set() - set the link settings
  * @pl: a pointer to a &struct phylink returned from phylink_create()
@@ -2743,6 +2790,13 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
 	    phylink_is_empty_linkmode(config.advertising))
 		return -EINVAL;
 
+	/* Validate the autonegotiation state. We don't have a PHY in this
+	 * situation, so the PCS is the media-facing entity.
+	 */
+	if (!phylink_validate_pcs_inband_autoneg(pl, config.interface,
+						 config.advertising))
+		return -EINVAL;
+
 	mutex_lock(&pl->state_mutex);
 	pl->link_config.speed = config.speed;
 	pl->link_config.duplex = config.duplex;
@@ -3477,6 +3531,12 @@ static int phylink_sfp_config_optical(struct phylink *pl)
 	phylink_dbg(pl, "optical SFP: chosen %s interface\n",
 		    phy_modes(interface));
 
+	if (!phylink_validate_pcs_inband_autoneg(pl, interface,
+						 config.advertising)) {
+		phylink_err(pl, "autoneg setting not compatible with PCS");
+		return -EINVAL;
+	}
+
 	config.interface = interface;
 
 	/* Ignore errors if we're expecting a PHY to attach later */
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
index 0dddb11f3a20..b0c450531f8d 100644
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -455,6 +455,7 @@ struct phylink_pcs {
 /**
  * struct phylink_pcs_ops - MAC PCS operations structure.
  * @pcs_validate: validate the link configuration.
+ * @pcs_query_inband: query inband support for interface mode.
  * @pcs_enable: enable the PCS.
  * @pcs_disable: disable the PCS.
  * @pcs_pre_config: pre-mac_config method (for errata)
@@ -470,6 +471,8 @@ struct phylink_pcs {
 struct phylink_pcs_ops {
 	int (*pcs_validate)(struct phylink_pcs *pcs, unsigned long *supported,
 			    const struct phylink_link_state *state);
+	unsigned int (*pcs_query_inband)(struct phylink_pcs *pcs,
+					 phy_interface_t interface);
 	int (*pcs_enable)(struct phylink_pcs *pcs);
 	void (*pcs_disable)(struct phylink_pcs *pcs);
 	void (*pcs_pre_config)(struct phylink_pcs *pcs,
@@ -506,6 +509,20 @@ struct phylink_pcs_ops {
 int pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
 		 const struct phylink_link_state *state);
 
+/**
+ * pcs_query_inband - query inband support for interface mode.
+ * @pcs: a pointer to a &struct phylink_pcs.
+ * @interface: interface mode to be queried
+ *
+ * Returns zero if it is unknown what in-band signalling is supported by the
+ * PHY (e.g. because the PHY driver doesn't implement the method.) Otherwise,
+ * returns a bit mask of the LINK_INBAND_* values from
+ * &enum link_inband_signalling to describe which inband modes are supported
+ * for this interface mode.
+ */
+unsigned int pcs_query_inband(struct phylink_pcs *pcs,
+			      phy_interface_t interface);
+
 /**
  * pcs_enable() - enable the PCS.
  * @pcs: a pointer to a &struct phylink_pcs.

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:41 +0100

Report the PCS inband properties to phylink for Marvell NETA
interfaces.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/ethernet/marvell/mvneta.c | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
index c1bf3e02d944..d421257c8e6e 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -3958,20 +3958,27 @@ static struct mvneta_port *mvneta_pcs_to_port(struct phylink_pcs *pcs)
 	return container_of(pcs, struct mvneta_port, phylink_pcs);
 }
 
-static int mvneta_pcs_validate(struct phylink_pcs *pcs,
-			       unsigned long *supported,
-			       const struct phylink_link_state *state)
+static unsigned int mvneta_pcs_query_inband(struct phylink_pcs *pcs,
+					    phy_interface_t interface)
 {
-	/* We only support QSGMII, SGMII, 802.3z and RGMII modes.
-	 * When in 802.3z mode, we must have AN enabled:
+	/* When operating in an 802.3z mode, we must have AN enabled:
 	 * "Bit 2 Field InBandAnEn In-band Auto-Negotiation enable. ...
 	 * When <PortType> = 1 (1000BASE-X) this field must be set to 1."
+	 * Therefore, inband is "required".
 	 */
-	if (phy_interface_mode_is_8023z(state->interface) &&
-	    !phylink_test(state->advertising, Autoneg))
-		return -EINVAL;
+	if (phy_interface_mode_is_8023z(interface))
+		return LINK_INBAND_ENABLE;
 
-	return 0;
+	/* QSGMII, SGMII and RGMII can be configured to use inband
+	 * signalling of the AN result. Indicate these as "possible".
+	 */
+	if (interface == PHY_INTERFACE_MODE_SGMII ||
+	    interface == PHY_INTERFACE_MODE_QSGMII ||
+	    phy_interface_mode_is_rgmii(interface))
+		return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+
+	/* For any other modes, indicate that inband is not supported. */
+	return LINK_INBAND_DISABLE;
 }
 
 static void mvneta_pcs_get_state(struct phylink_pcs *pcs,
@@ -4069,7 +4076,7 @@ static void mvneta_pcs_an_restart(struct phylink_pcs *pcs)
 }
 
 static const struct phylink_pcs_ops mvneta_phylink_pcs_ops = {
-	.pcs_validate = mvneta_pcs_validate,
+	.pcs_query_inband = mvneta_pcs_query_inband,
 	.pcs_get_state = mvneta_pcs_get_state,
 	.pcs_config = mvneta_pcs_config,
 	.pcs_an_restart = mvneta_pcs_an_restart,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:41 +0100

Report the PCS inband properties to phylink for Marvell PP2 interfaces.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c | 25 ++++++++++++++++---------
 1 file changed, 16 insertions(+), 9 deletions(-)

diff --git a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
index f841104f4a63..cdd900fe6fd2 100644
--- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
+++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
@@ -6218,19 +6218,26 @@ static const struct phylink_pcs_ops mvpp2_phylink_xlg_pcs_ops = {
 	.pcs_config = mvpp2_xlg_pcs_config,
 };
 
-static int mvpp2_gmac_pcs_validate(struct phylink_pcs *pcs,
-				   unsigned long *supported,
-				   const struct phylink_link_state *state)
+static unsigned int mvpp2_gmac_pcs_query_inband(struct phylink_pcs *pcs,
+						phy_interface_t interface)
 {
-	/* When in 802.3z mode, we must have AN enabled:
+	/* When operating in an 802.3z mode, we must have AN enabled:
 	 * Bit 2 Field InBandAnEn In-band Auto-Negotiation enable. ...
 	 * When <PortType> = 1 (1000BASE-X) this field must be set to 1.
+	 * Therefore, inband is "required".
 	 */
-	if (phy_interface_mode_is_8023z(state->interface) &&
-	    !phylink_test(state->advertising, Autoneg))
-		return -EINVAL;
+	if (phy_interface_mode_is_8023z(interface))
+		return LINK_INBAND_ENABLE;
 
-	return 0;
+	/* SGMII and RGMII can be configured to use inband signalling of the
+	 * AN result. Indicate these as "possible".
+	 */
+	if (interface == PHY_INTERFACE_MODE_SGMII ||
+	    phy_interface_mode_is_rgmii(interface))
+		return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+
+	/* For any other modes, indicate that inband is not supported. */
+	return LINK_INBAND_DISABLE;
 }
 
 static void mvpp2_gmac_pcs_get_state(struct phylink_pcs *pcs,
@@ -6337,7 +6344,7 @@ static void mvpp2_gmac_pcs_an_restart(struct phylink_pcs *pcs)
 }
 
 static const struct phylink_pcs_ops mvpp2_phylink_gmac_pcs_ops = {
-	.pcs_validate = mvpp2_gmac_pcs_validate,
+	.pcs_query_inband = mvpp2_gmac_pcs_query_inband,
 	.pcs_get_state = mvpp2_gmac_pcs_get_state,
 	.pcs_config = mvpp2_gmac_pcs_config,
 	.pcs_an_restart = mvpp2_gmac_pcs_an_restart,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:41 +0100

Add a pcs_query_inband() interface which reflects phy_query_inband()
for PHYs. This can be used to determine for the specified interface
mode whether in-band signalling is supported by the PCS, and whether
the PCS requires in-band signalling.

This is used to determine whether we should use inband autonegotiation
in inband mode, which may be required or may be unsupported in various
interface modes.

This revised version uses enable/disable/bypass flags to indicate the
capabilities.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 141 ++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 123 insertions(+), 18 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 2ffd60687e0e..b91129ed70d0 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1141,6 +1141,8 @@ static void phylink_pcs_an_restart(struct phylink *pl)
 
 /**
  * phylink_pcs_neg_mode() - helper to determine PCS inband mode
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @pcs: a pointer to &struct phylink_pcs
  * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
  * @interface: interface mode to be used
  * @advertising: adertisement ethtool link mode mask
@@ -1158,11 +1160,19 @@ static void phylink_pcs_an_restart(struct phylink *pl)
  * Note: this is for cases where the PCS itself is involved in negotiation
  * (e.g. Clause 37, SGMII and similar) not Clause 73.
  */
-static unsigned int phylink_pcs_neg_mode(unsigned int mode,
+static unsigned int phylink_pcs_neg_mode(struct phylink *pl,
+					 struct phylink_pcs *pcs,
+					 unsigned int mode,
 					 phy_interface_t interface,
 					 const unsigned long *advertising)
 {
+	unsigned int phy_link_mode = 0;
+	unsigned int pcs_link_mode;
 	unsigned int neg_mode;
+	enum {
+		INBAND_CISCO_SGMII,
+		INBAND_8023Z,
+	} type;
 
 	switch (interface) {
 	case PHY_INTERFACE_MODE_SGMII:
@@ -1175,10 +1185,7 @@ static unsigned int phylink_pcs_neg_mode(unsigned int mode,
 		 * inband communication. Note: there exist PHYs that run
 		 * with SGMII but do not send the inband data.
 		 */
-		if (!phylink_autoneg_inband(mode))
-			neg_mode = PHYLINK_PCS_NEG_OUTBAND;
-		else
-			neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
+		type = INBAND_CISCO_SGMII;
 		break;
 
 	case PHY_INTERFACE_MODE_1000BASEX:
@@ -1189,20 +1196,116 @@ static unsigned int phylink_pcs_neg_mode(unsigned int mode,
 		 * as well, but drivers may not support this, so may
 		 * need to override this.
 		 */
-		if (!phylink_autoneg_inband(mode))
-			neg_mode = PHYLINK_PCS_NEG_OUTBAND;
-		else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
-					   advertising))
-			neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
-		else
-			neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
+		type = INBAND_8023Z;
 		break;
 
 	default:
-		neg_mode = PHYLINK_PCS_NEG_NONE;
-		break;
+		return PHYLINK_PCS_NEG_NONE;
+	}
+
+	pcs_link_mode = phylink_pcs_query_inband(pcs, interface);
+	pcs_link_mode &= LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+	if (pl->phydev) {
+		phy_link_mode = phy_query_inband(pl->phydev, interface);
+		phy_link_mode &= LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+	}
+
+	if (!phylink_autoneg_inband(mode)) {
+		const char *s1, *s2, *s3, *empty = "";
+
+		s1 = s2 = s3 = empty;
+
+		if (pcs_link_mode == LINK_INBAND_ENABLE)
+			s1 = "PCS";
+
+		if (phy_link_mode == LINK_INBAND_ENABLE)
+			s3 = "PHY";
+
+		/* If either the PCS or PHY requires inband to be enabled,
+		 * this is an invalid configuration. Provide a diagnostic
+		 * message for this case, but don't try to force the issue.
+		 */
+		if (s1 != empty || s3 != empty) {
+			if (s1 != empty && s3 != empty)
+				s2 = " and ";
+
+			phylink_warn(pl,
+				     "firmware wants %s mode, but %s%s%s requires inband\n",
+				     phylink_an_mode_str(mode),
+				     s1, s2, s3);
+		}
+		return PHYLINK_PCS_NEG_OUTBAND;
 	}
 
+	/* For SGMII modes, which are designed to be used with PHYs, we
+	 * try to use inband mode where-ever possible. However, there are
+	 * some PHYs e.g. BCM84881 which do not support in-band.
+	 */
+	if (type == INBAND_CISCO_SGMII) {
+		/* PCS	PHY
+		 * D E	D E
+		 * 0 0  0 0	no information			inband enabled
+		 * 1 0  0 0	pcs doesn't support		outband
+		 * 0 1  0 0	pcs required			inband enabled
+		 * 1 1  0 0	pcs optional			inband enabled
+		 * 0 0  1 0	phy doesn't support		outband
+		 * 1 0  1 0	pcs+phy doesn't support		outband
+		 * 0 1  1 0	pcs required, phy doesn't support, invalid
+		 * 1 1  1 0	pcs optional, phy doesn't support, outband
+		 * 0 0  0 1	phy required			inband enabled
+		 * 1 0  0 1	pcs doesn't support, phy required, invalid
+		 * 0 1  0 1	pcs+phy required		inband enabled
+		 * 1 1  0 1	pcs optional, phy required	inband enabled
+		 * 0 0  1 1	phy optional			inband enabled
+		 * 1 0  1 1	pcs doesn't support, phy optional, outband
+		 * 0 1  1 1	pcs required, phy optional	inband enabled
+		 * 1 1  1 1	pcs+phy optional		inband enabled
+		 */
+		if (!pcs_link_mode)
+			pcs_link_mode = LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+
+		if (!phy_link_mode)
+			phy_link_mode = LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+
+		if (pcs_link_mode & phy_link_mode & LINK_INBAND_ENABLE) {
+			/* inband supported at both ends */
+			return PHYLINK_PCS_NEG_INBAND_ENABLED;
+		} else if (pcs_link_mode & phy_link_mode & LINK_INBAND_DISABLE) {
+			/* inband not supported at both ends */
+			return PHYLINK_PCS_NEG_OUTBAND;
+		} else {
+			/* invalid */
+		}
+	}
+
+	/* For 802.3z, handle the PHY present vs absent cases separately */
+	if (!pl->phydev) {
+		/* PHY absent: always use inband but inband may be disabled */
+		/* If the PCS doesn't support inband, then inband must be
+		 * disabled.
+		 */
+		if (pcs_link_mode == LINK_INBAND_DISABLE)
+			return PHYLINK_PCS_NEG_INBAND_DISABLED;
+
+		/* If the PCS requires inband, then inband must always be
+		 * enabled.
+		 */
+		if (pcs_link_mode == LINK_INBAND_ENABLE)
+			return PHYLINK_PCS_NEG_INBAND_ENABLED;
+
+		/* For the possible case, fall through to the "legacy" code. */
+	} else {
+		/* PHY present, inband mode depends on the capabilities
+		 * of both.
+		 */
+	}
+
+	/* Legacy, so determine inband depending on the advertising bit */
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, advertising))
+		neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
+	else
+		neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
+
 	return neg_mode;
 }
 
@@ -1240,7 +1343,8 @@ static void phylink_major_config(struct phylink *pl, bool restart,
 		pcs_changed = pl->pcs != pcs;
 	}
 
-	pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->req_link_an_mode,
+	pl->pcs_neg_mode = phylink_pcs_neg_mode(pl, pcs,
+						pl->req_link_an_mode,
 						state->interface,
 						state->advertising);
 
@@ -1344,9 +1448,10 @@ static int phylink_change_inband_advert(struct phylink *pl)
 		    pl->link_config.pause);
 
 	/* Recompute the PCS neg mode */
-	pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->req_link_an_mode,
-					pl->link_config.interface,
-					pl->link_config.advertising);
+	pl->pcs_neg_mode = phylink_pcs_neg_mode(pl, pl->pcs,
+						pl->req_link_an_mode,
+						pl->link_config.interface,
+						pl->link_config.advertising);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:41 +0100

Remove phylink_phy_no_inband() now that we are handling the lack of
inband negotiation by querying the capabilities of the PHY and PCS,
and the BCM84881 PHY driver provides us the information necessary to
make the decision.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 27 ++++++---------------------
 1 file changed, 6 insertions(+), 21 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index b91129ed70d0..a5af11414fbf 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -3498,10 +3498,11 @@ static phy_interface_t phylink_choose_sfp_interface(struct phylink *pl,
 	return interface;
 }
 
-static void phylink_sfp_set_config(struct phylink *pl, u8 mode,
+static void phylink_sfp_set_config(struct phylink *pl,
 				   unsigned long *supported,
 				   struct phylink_link_state *state)
 {
+	u8 mode = MLO_AN_INBAND;
 	bool changed = false;
 
 	phylink_dbg(pl, "requesting link mode %s/%s with support %*pb\n",
@@ -3535,8 +3536,7 @@ static void phylink_sfp_set_config(struct phylink *pl, u8 mode,
 		phylink_mac_initial_config(pl, false);
 }
 
-static int phylink_sfp_config_phy(struct phylink *pl, u8 mode,
-				  struct phy_device *phy)
+static int phylink_sfp_config_phy(struct phylink *pl, struct phy_device *phy)
 {
 	__ETHTOOL_DECLARE_LINK_MODE_MASK(support);
 	struct phylink_link_state config;
@@ -3580,7 +3580,7 @@ static int phylink_sfp_config_phy(struct phylink *pl, u8 mode,
 
 	pl->link_port = pl->sfp_port;
 
-	phylink_sfp_set_config(pl, mode, support, &config);
+	phylink_sfp_set_config(pl, support, &config);
 
 	return 0;
 }
@@ -3655,7 +3655,7 @@ static int phylink_sfp_config_optical(struct phylink *pl)
 
 	pl->link_port = pl->sfp_port;
 
-	phylink_sfp_set_config(pl, MLO_AN_INBAND, pl->sfp_support, &config);
+	phylink_sfp_set_config(pl, pl->sfp_support, &config);
 
 	return 0;
 }
@@ -3726,19 +3726,9 @@ static void phylink_sfp_link_up(void *upstream)
 	phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_LINK);
 }
 
-/* The Broadcom BCM84881 in the Methode DM7052 is unable to provide a SGMII
- * or 802.3z control word, so inband will not work.
- */
-static bool phylink_phy_no_inband(struct phy_device *phy)
-{
-	return phy->is_c45 && phy_id_compare(phy->c45_ids.device_ids[1],
-					     0xae025150, 0xfffffff0);
-}
-
 static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
 {
 	struct phylink *pl = upstream;
-	u8 mode;
 	int ret;
 
 	/*
@@ -3750,17 +3740,12 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
 	 */
 	phy_support_asym_pause(phy);
 
-	if (phylink_phy_no_inband(phy))
-		mode = MLO_AN_PHY;
-	else
-		mode = MLO_AN_INBAND;
-
 	/* Set the PHY's host supported interfaces */
 	phy_interface_and(phy->host_interfaces, phylink_sfp_interfaces,
 			  pl->config->supported_interfaces);
 
 	/* Do the initial configuration */
-	return phylink_sfp_config_phy(pl, mode, phy);
+	return phylink_sfp_config_phy(pl, phy);
 }

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:41 +0100

Move the handling of manual flow control configuration to a common
location during resolve. We currently evaluate this for all but
fixed links.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index a5af11414fbf..e5db2ba98175 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1730,7 +1730,6 @@ static void phylink_resolve(struct work_struct *w)
 		switch (pl->act_link_an_mode) {
 		case MLO_AN_PHY:
 			link_state = pl->phy_state;
-			phylink_apply_manual_flow(pl, &link_state);
 			mac_config = link_state.link;
 			break;
 
@@ -1791,11 +1790,13 @@ static void phylink_resolve(struct work_struct *w)
 				link_state.pause = pl->phy_state.pause;
 				mac_config = true;
 			}
-			phylink_apply_manual_flow(pl, &link_state);
 			break;
 		}
 	}
 
+	if (pl->act_link_an_mode != MLO_AN_FIXED)
+		phylink_apply_manual_flow(pl, &link_state);
+
 	if (mac_config) {
 		if (link_state.interface != pl->link_config.interface) {
 			/* The interface has changed, force the link down and

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:41 +0100

The switch() statement doesn't sit very well with the preceeding if()
statements, so let's just convert everything to if()s. This allows
better formatting of the code.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 108 ++++++++++++++++++++++------------------------
 1 file changed, 52 insertions(+), 56 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index e5db2ba98175..d98cd4fba8ee 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1726,71 +1726,67 @@ static void phylink_resolve(struct work_struct *w)
 	} else if (pl->mac_link_dropped) {
 		link_state.link = false;
 		retrigger = true;
-	} else {
-		switch (pl->act_link_an_mode) {
-		case MLO_AN_PHY:
+	} else if (pl->act_link_an_mode == MLO_AN_FIXED) {
+		phylink_get_fixed_state(pl, &link_state);
+		mac_config = link_state.link;
+	} else if (pl->act_link_an_mode == MLO_AN_PHY) {
+		/* PHY mode, or PCS configured for outband mode */
+		if (pl->phydev) {
 			link_state = pl->phy_state;
-			mac_config = link_state.link;
-			break;
+		} else {
+			/* No PHY - assume link is down */
+			link_state.link = false;
+		}
+		mac_config = link_state.link;
+	} else {
+		/* Inband mode */
+		phylink_mac_pcs_get_state(pl, &link_state);
 
-		case MLO_AN_FIXED:
-			phylink_get_fixed_state(pl, &link_state);
-			mac_config = link_state.link;
-			break;
+		/* The PCS may have a latching link-fail indicator.
+		 * If the link was up, bring the link down and
+		 * re-trigger the resolve. Otherwise, re-read the
+		 * PCS state to get the current status of the link.
+		 */
+		if (!link_state.link) {
+			if (cur_link_state)
+				retrigger = true;
+			else
+				phylink_mac_pcs_get_state(pl, &link_state);
+		}
 
-		case MLO_AN_INBAND:
-			phylink_mac_pcs_get_state(pl, &link_state);
+		/* If we have a phy, the "up" state is the union of
+		 * both the PHY and the MAC
+		 */
+		if (pl->phydev)
+			link_state.link &= pl->phy_state.link;
 
-			/* The PCS may have a latching link-fail indicator.
-			 * If the link was up, bring the link down and
-			 * re-trigger the resolve. Otherwise, re-read the
-			 * PCS state to get the current status of the link.
+		/* Only update if the PHY link is up */
+		if (pl->phydev && pl->phy_state.link) {
+			/* If the interface has changed, force a
+			 * link down event if the link isn't already
+			 * down, and re-resolve.
 			 */
-			if (!link_state.link) {
-				if (cur_link_state)
-					retrigger = true;
-				else
-					phylink_mac_pcs_get_state(pl,
-								  &link_state);
+			if (link_state.interface != pl->phy_state.interface) {
+				retrigger = true;
+				link_state.link = false;
 			}
+			link_state.interface = pl->phy_state.interface;
 
-			/* If we have a phy, the "up" state is the union of
-			 * both the PHY and the MAC
+			/* If we are doing rate matching, then the
+			 * link speed/duplex comes from the PHY
 			 */
-			if (pl->phydev)
-				link_state.link &= pl->phy_state.link;
-
-			/* Only update if the PHY link is up */
-			if (pl->phydev && pl->phy_state.link) {
-				/* If the interface has changed, force a
-				 * link down event if the link isn't already
-				 * down, and re-resolve.
-				 */
-				if (link_state.interface !=
-				    pl->phy_state.interface) {
-					retrigger = true;
-					link_state.link = false;
-				}
-				link_state.interface = pl->phy_state.interface;
-
-				/* If we are doing rate matching, then the
-				 * link speed/duplex comes from the PHY
-				 */
-				if (pl->phy_state.rate_matching) {
-					link_state.rate_matching =
-						pl->phy_state.rate_matching;
-					link_state.speed = pl->phy_state.speed;
-					link_state.duplex =
-						pl->phy_state.duplex;
-				}
-
-				/* If we have a PHY, we need to update with
-				 * the PHY flow control bits.
-				 */
-				link_state.pause = pl->phy_state.pause;
-				mac_config = true;
+			if (pl->phy_state.rate_matching) {
+				link_state.rate_matching =
+					pl->phy_state.rate_matching;
+				link_state.speed = pl->phy_state.speed;
+				link_state.duplex = pl->phy_state.duplex;
 			}
-			break;
+
+			/* If we have a PHY, we need to update with
+			 * the PHY flow control bits.
+			 */
+			link_state.pause = pl->phy_state.pause;
+			mac_config = true;
 		}
 	}

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:41 +0100

From: Russell King <[email-masked]>

Add a DT bindings document for the Marvell 10G driver, which will
augment the generic ethernet PHY binding by having LED mode
configuration.

Signed-off-by: Russell King <[email-masked]>
---
 .../devicetree/bindings/net/marvell,10g.yaml       | 36 ++++++++++++++++++++++
 1 file changed, 36 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/marvell,10g.yaml

diff --git a/Documentation/devicetree/bindings/net/marvell,10g.yaml b/Documentation/devicetree/bindings/net/marvell,10g.yaml
new file mode 100644
index 000000000000..9ec85333f4a4
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/marvell,10g.yaml
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/marvell,10g.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Marvell Alaska X family Ethernet PHYs
+
+maintainers:
+  - Russell King <[email-masked]>
+
+allOf:
+  - $ref: ethernet-phy.yaml#
+
+properties:
+  marvell,led-mode:
+    description:
+      An array of one to four 16-bit integers to write to the PHY LED
+      configuration registers.
+    $ref: /schemas/types.yaml#/definitions/uint16-array
+    minItems: 1
+    maxItems: 4
+
+additionalProperties: false
+
+examples:
+  - |
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+        ethernet-phy@0 {
+            reg = <0>;
+            compatible = "ethernet-phy-ieee802.3-c45";
+            marvell,led-mode = /bits/ 16 <0x0129 0x095d 0x0855>;
+        };
+    };

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:42 +0100

Add support for configuring the LEDs. Macchiatobin has an oddity in
that the left LED goes out when the cable is connected, and flashes
when there's link activity. This is because the reset default for
the LED outputs assume that the LED is connected to supply, not to
ground. Add support for configuring the LED modes and polarities.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/marvell10g.c | 62 +++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 55 insertions(+), 7 deletions(-)

diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c
index ad43e280930c..75322deeafbb 100644
--- a/drivers/net/phy/marvell10g.c
+++ b/drivers/net/phy/marvell10g.c
@@ -27,6 +27,7 @@
 #include <linux/delay.h>
 #include <linux/hwmon.h>
 #include <linux/marvell_phy.h>
+#include <linux/of.h>
 #include <linux/phy.h>
 #include <linux/sfp.h>
 #include <linux/netdevice.h>
@@ -171,6 +172,8 @@ struct mv3310_priv {
 
 	struct device *hwmon_dev;
 	char *hwmon_name;
+	u8 num_leds;
+	u16 led_mode[4];
 };
 
 static const struct mv3310_chip *to_mv3310_chip(struct phy_device *phydev)
@@ -506,6 +509,43 @@ static const struct sfp_upstream_ops mv3310_sfp_ops = {
 	.module_insert = mv3310_sfp_insert,
 };
 
+static int mv3310_leds_write(struct phy_device *phydev)
+{
+	struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+	int i, ret;
+
+	for (i = 0; i < priv->num_leds; i++) {
+		ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, 0xf020 + i,
+				    priv->led_mode[i]);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int mv3310_fw_config(struct phy_device *phydev)
+{
+	struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+	struct device_node *node;
+	int ret;
+
+	node = phydev->mdio.dev.of_node;
+	if (!node)
+		return 0;
+
+	ret = of_property_read_variable_u16_array(node, "marvell,led-mode",
+				priv->led_mode, 1, ARRAY_SIZE(priv->led_mode));
+	if (ret == -EINVAL)
+		ret = 0;
+	if (ret < 0)
+		return ret;
+
+	priv->num_leds = ret;
+
+	return 0;
+}
+
 static int mv3310_probe(struct phy_device *phydev)
 {
 	const struct mv3310_chip *chip = to_mv3310_chip(phydev);
@@ -517,6 +557,20 @@ static int mv3310_probe(struct phy_device *phydev)
 	    (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask)
 		return -ENODEV;
 
+	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	dev_set_drvdata(&phydev->mdio.dev, priv);
+
+	ret = mv3310_fw_config(phydev);
+	if (ret < 0)
+		return ret;
+
+	ret = mv3310_leds_write(phydev);
+	if (ret < 0)
+		return ret;
+
 	ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MV_PMA_BOOT);
 	if (ret < 0)
 		return ret;
@@ -527,12 +581,6 @@ static int mv3310_probe(struct phy_device *phydev)
 		return -ENODEV;
 	}
 
-	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
-	if (!priv)
-		return -ENOMEM;
-
-	dev_set_drvdata(&phydev->mdio.dev, priv);
-
 	ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MV_PMA_FW_VER0);
 	if (ret < 0)
 		return ret;
@@ -873,7 +921,7 @@ static int mv3310_config_init(struct phy_device *phydev)
 	if (err && err != -EOPNOTSUPP)
 		return err;
 
-	return 0;
+	return mv3310_leds_write(phydev);
 }
 
 static int mv3310_get_features(struct phy_device *phydev)

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:42 +0100

Configure the Macchiatobin 10G PHY LED modes to correct their polarity.
We keep the existing LED behaviours, but switch their polarity to
reflect how they are connected. Tweak the LED modes as well to be:

left:  off          = no link
       solid green  = RJ45 link up (not SFP+ cage)
       flash green  = traffic
right: off          = no link
       solid green  = 10G
       solid yellow = 1G
       flash green  = 100M
       flash yellow = 10M

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm64/boot/dts/marvell/armada-8040-mcbin.dts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dts b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dts
index 1766cf58101b..68c27f22ff57 100644
--- a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dts
+++ b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dts
@@ -21,12 +21,14 @@ phy0: ethernet-phy@0 {
 		compatible = "ethernet-phy-ieee802.3-c45";
 		reg = <0>;
 		sfp = <&sfp_eth0>;
+		marvell,led-mode = /bits/ 16 <0x0129 0x095d 0x0855>;
 	};
 
 	phy8: ethernet-phy@8 {
 		compatible = "ethernet-phy-ieee802.3-c45";
 		reg = <8>;
 		sfp = <&sfp_eth1>;
+		marvell,led-mode = /bits/ 16 <0x0129 0x095d 0x0855>;
 	};
 };

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:42 +0100

If MDIO_USXGMII_LINK is not set, it means that the PHYs media side
link is down. Indicate back to phylink that the link as a whole is
down.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/phylink.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index d98cd4fba8ee..ed1f99407aef 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -3861,12 +3861,18 @@ static void phylink_decode_sgmii_word(struct phylink_link_state *state,
  * @lpa: a 16 bit value which stores the USXGMII auto-negotiation word
  *
  * Helper for MAC PCS supporting the USXGMII protocol and the auto-negotiation
- * code word.  Decode the USXGMII code word and populate the corresponding fields
- * (speed, duplex) into the phylink_link_state structure.
+ * code word. Decode the USXGMII code word and populate the corresponding fields
+ * (speed, duplex) into the phylink_link_state structure. If the code word
+ * indicates link is down, or we are unable to decode it, set the link down.
  */
 void phylink_decode_usxgmii_word(struct phylink_link_state *state,
 				 uint16_t lpa)
 {
+	if (!(lpa & MDIO_USXGMII_LINK)) {
+		state->link = false;
+		return;
+	}
+
 	switch (lpa & MDIO_USXGMII_SPD_MASK) {
 	case MDIO_USXGMII_10:
 		state->speed = SPEED_10;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:42 +0100

Allow phylib drivers to pass the hardware-resolved pause state to MAC
drivers, rather than using the software-based pause resolution code.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/phy_device.c | 6 ++++++
 include/linux/phy.h          | 9 +++++++++
 2 files changed, 15 insertions(+)

diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 6bb2793de0a9..0718c6215aba 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -3058,6 +3058,12 @@ void phy_get_pause(struct phy_device *phydev, bool *tx_pause, bool *rx_pause)
 		return;
 	}
 
+	if (phydev->resolved_pause_valid) {
+		*tx_pause = phydev->resolved_tx_pause;
+		*rx_pause = phydev->resolved_rx_pause;
+		return;
+	}
+
 	return linkmode_resolve_pause(phydev->advertising,
 				      phydev->lp_advertising,
 				      tx_pause, rx_pause);
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 6a41cdfee21a..8eb178fe5ccc 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -707,6 +707,15 @@ struct phy_device {
 	u8 master_slave_set;
 	u8 master_slave_state;
 
+	/*
+	 * private to phylib: the resolved pause state - only valid if
+	 * resolved_pause_valid is true. only phy drivers and phylib
+	 * should touch this.
+	 */
+	bool resolved_pause_valid;
+	bool resolved_tx_pause;
+	bool resolved_rx_pause;
+
 	/* Union of PHY and Attached devices' supported link modes */
 	/* See ethtool.h for more info */
 	__ETHTOOL_DECLARE_LINK_MODE_MASK(supported);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:42 +0100

Support reporting the hardware resolved pause enablement states via
phylib, overriding our software implementation.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/marvell.c    | 40 ++++++++++++++++++++++++++++++++++------
 drivers/net/phy/marvell10g.c |  6 ++++++
 2 files changed, 40 insertions(+), 6 deletions(-)

diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c
index 963dcd6933d6..61af4b651aed 100644
--- a/drivers/net/phy/marvell.c
+++ b/drivers/net/phy/marvell.c
@@ -176,6 +176,10 @@
 #define MII_M1011_PHY_STATUS_FULLDUPLEX	0x2000
 #define MII_M1011_PHY_STATUS_RESOLVED	0x0800
 #define MII_M1011_PHY_STATUS_LINK	0x0400
+#define MII_M1111_PHY_STATUS_TX_PAUSE	0x0008
+#define MII_M1111_PHY_STATUS_RX_PAUSE	0x0004
+#define MII_88E151X_PHY_STATUS_TX_PAUSE	0x0200
+#define MII_88E151X_PHY_STATUS_RX_PAUSE	0x0100
 
 #define MII_88E3016_PHY_SPEC_CTRL	0x10
 #define MII_88E3016_DISABLE_SCRAMBLER	0x0200
@@ -353,6 +357,8 @@ struct marvell_priv {
 	u32 step;
 	s8 pair;
 	u8 vct_phase;
+	u16 tx_pause_mask;
+	u16 rx_pause_mask;
 };
 
 static int marvell_read_page(struct phy_device *phydev)
@@ -1636,6 +1642,7 @@ static void fiber_lpa_mod_linkmode_lpa_t(unsigned long *advertising, u32 lpa)
 static int marvell_read_status_page_an(struct phy_device *phydev,
 				       int fiber, int status)
 {
+	struct marvell_priv *priv = phydev->priv;
 	int lpa;
 	int err;
 
@@ -1691,6 +1698,11 @@ static int marvell_read_status_page_an(struct phy_device *phydev,
 		}
 	}
 
+	phydev->resolved_tx_pause = !!(status & priv->tx_pause_mask);
+	phydev->resolved_rx_pause = !!(status & priv->rx_pause_mask);
+	phydev->resolved_pause_valid = !fiber && priv->tx_pause_mask &&
+				       priv->rx_pause_mask;
+
 	return 0;
 }
 
@@ -1734,6 +1746,7 @@ static int marvell_read_status_page(struct phy_device *phydev, int page)
 	phydev->speed = SPEED_UNKNOWN;
 	phydev->duplex = DUPLEX_UNKNOWN;
 	phydev->port = fiber ? PORT_FIBRE : PORT_TP;
+	phydev->resolved_pause_valid = false;
 
 	if (phydev->autoneg == AUTONEG_ENABLE)
 		err = marvell_read_status_page_an(phydev, fiber, status);
@@ -3531,7 +3544,8 @@ static int m88e1318_led_hw_control_get(struct phy_device *phydev, u8 index,
 	return marvell_get_led_rules(index, rules, mode);
 }
 
-static int marvell_probe(struct phy_device *phydev)
+static int marvell_probe_pause(struct phy_device *phydev, u16 tx_pause_mask,
+			       u16 rx_pause_mask)
 {
 	struct marvell_priv *priv;
 
@@ -3539,6 +3553,8 @@ static int marvell_probe(struct phy_device *phydev)
 	if (!priv)
 		return -ENOMEM;
 
+	priv->tx_pause_mask = tx_pause_mask;
+	priv->rx_pause_mask = rx_pause_mask;
 	phydev->priv = priv;
 
 	return marvell_hwmon_probe(phydev);
@@ -3628,11 +3644,23 @@ static const struct sfp_upstream_ops m88e1510_sfp_ops = {
 	.detach = phy_sfp_detach,
 };
 
+static int marvell_probe(struct phy_device *phydev)
+{
+	return marvell_probe_pause(phydev, 0, 0);
+}
+
+static int m88e1111_probe(struct phy_device *phydev)
+{
+	return marvell_probe_pause(phydev, MII_M1111_PHY_STATUS_TX_PAUSE,
+				   MII_M1111_PHY_STATUS_RX_PAUSE);
+}
+
 static int m88e1510_probe(struct phy_device *phydev)
 {
 	int err;
 
-	err = marvell_probe(phydev);
+	err = marvell_probe_pause(phydev, MII_88E151X_PHY_STATUS_TX_PAUSE,
+				  MII_88E151X_PHY_STATUS_RX_PAUSE);
 	if (err)
 		return err;
 
@@ -3699,7 +3727,7 @@ static struct phy_driver marvell_drivers[] = {
 		.name = "Marvell 88E1111",
 		/* PHY_GBIT_FEATURES */
 		.flags = PHY_POLL_CABLE_TEST,
-		.probe = marvell_probe,
+		.probe = m88e1111_probe,
 		.query_inband = m88e1111_query_inband,
 		.config_init = m88e1111gbe_config_init,
 		.config_aneg = m88e1111_config_aneg,
@@ -3927,7 +3955,7 @@ static struct phy_driver marvell_drivers[] = {
 		.driver_data = DEF_MARVELL_HWMON_OPS(m88e1510_hwmon_ops),
 		/* PHY_GBIT_FEATURES */
 		.flags = PHY_POLL_CABLE_TEST,
-		.probe = marvell_probe,
+		.probe = m88e1510_probe,
 		.config_init = marvell_1011gbe_config_init,
 		.config_aneg = m88e1510_config_aneg,
 		.read_status = marvell_read_status,
@@ -3956,7 +3984,7 @@ static struct phy_driver marvell_drivers[] = {
 		.phy_id_mask = MARVELL_PHY_ID_MASK,
 		.name = "Marvell 88E1545",
 		.driver_data = DEF_MARVELL_HWMON_OPS(m88e1510_hwmon_ops),
-		.probe = marvell_probe,
+		.probe = m88e1510_probe,
 		/* PHY_GBIT_FEATURES */
 		.flags = PHY_POLL_CABLE_TEST,
 		.config_init = marvell_1011gbe_config_init,
@@ -4023,7 +4051,7 @@ static struct phy_driver marvell_drivers[] = {
 		.driver_data = DEF_MARVELL_HWMON_OPS(m88e1510_hwmon_ops),
 		/* PHY_GBIT_FEATURES */
 		.flags = PHY_POLL_CABLE_TEST,
-		.probe = marvell_probe,
+		.probe = m88e1510_probe,
 		.config_init = marvell_1011gbe_config_init,
 		.config_aneg = m88e6390_config_aneg,
 		.read_status = marvell_read_status,
diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c
index 75322deeafbb..f598cd4adc19 100644
--- a/drivers/net/phy/marvell10g.c
+++ b/drivers/net/phy/marvell10g.c
@@ -83,6 +83,8 @@ enum {
 	MV_PCS_CSSR1_SPD1_10	= 0x0000,
 	MV_PCS_CSSR1_DUPLEX_FULL= BIT(13),
 	MV_PCS_CSSR1_RESOLVED	= BIT(11),
+	MV_PCS_CSSR1_TX_PAUSE	= BIT(9),
+	MV_PCS_CSSR1_RX_PAUSE	= BIT(8),
 	MV_PCS_CSSR1_MDIX	= BIT(6),
 	MV_PCS_CSSR1_SPD2_MASK	= 0x000c,
 	MV_PCS_CSSR1_SPD2_5000	= 0x0008,
@@ -1138,6 +1140,10 @@ static int mv3310_read_status_copper(struct phy_device *phydev)
 	phydev->mdix = cssr1 & MV_PCS_CSSR1_MDIX ?
 		       ETH_TP_MDI_X : ETH_TP_MDI;
 
+	phydev->resolved_tx_pause = !!(cssr1 & MV_PCS_CSSR1_TX_PAUSE);
+	phydev->resolved_rx_pause = !!(cssr1 & MV_PCS_CSSR1_RX_PAUSE);
+	phydev->resolved_pause_valid = true;
+
 	if (val & MDIO_AN_STAT1_COMPLETE) {
 		val = genphy_c45_read_lpa(phydev);
 		if (val < 0)

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:42 +0100

Provide phy driver start/stop hooks so that the PHY driver knows when
the network driver is starting or stopping.  This will be used for the
Marvell 10G driver so that we can sanely refuse to start if the PHYs
firmware is not present, and also so that we can sanely support SFPs
behind the PHY.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/phy.c | 7 +++++++
 include/linux/phy.h   | 3 +++
 2 files changed, 10 insertions(+)

diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index 2f8721d8117d..f5f532f913f0 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -1541,6 +1541,10 @@ void phy_stop(struct phy_device *phydev)
 	phy_process_state_change(phydev, old_state);
 
 	state_work = _phy_state_machine(phydev);
+
+	if (phydev->drv->stop)
+		phydev->drv->stop(phydev);
+
 	mutex_unlock(&phydev->lock);
 
 	_phy_state_machine_post_work(phydev, state_work);
@@ -1573,6 +1577,9 @@ void phy_start(struct phy_device *phydev)
 		goto out;
 	}
 
+	if (phydev->drv->start && phydev->drv->start(phydev))
+		goto out;
+
 	if (phydev->sfp_bus)
 		sfp_upstream_start(phydev->sfp_bus);
 
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 8eb178fe5ccc..d9c4ff3344c3 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -1005,6 +1005,9 @@ struct phy_driver {
 	/** @resume: Resume the hardware, restoring state if needed */
 	int (*resume)(struct phy_device *phydev);
 
+	int (*start)(struct phy_device *phydev);
+	void (*stop)(struct phy_device *phydev);
+
 	/**
 	 * @config_aneg: Configures the advertisement and resets
 	 * autonegotiation if phydev->autoneg is on,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:42 +0100

Allow the PHY to probe when there is no firmware, but do not allow the
link to come up by forcing the PHY state to PHY_HALTED in a similar way
to phy_error().

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/marvell10g.c | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c
index f598cd4adc19..5aa810807b36 100644
--- a/drivers/net/phy/marvell10g.c
+++ b/drivers/net/phy/marvell10g.c
@@ -171,6 +171,7 @@ struct mv3310_priv {
 
 	u32 firmware_ver;
 	bool has_downshift;
+	bool firmware_failed;
 
 	struct device *hwmon_dev;
 	char *hwmon_name;
@@ -580,7 +581,7 @@ static int mv3310_probe(struct phy_device *phydev)
 	if (ret & MV_PMA_BOOT_FATAL) {
 		dev_warn(&phydev->mdio.dev,
 			 "PHY failed to boot firmware, status=%04x\n", ret);
-		return -ENODEV;
+		priv->firmware_failed = true;
 	}
 
 	ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MV_PMA_FW_VER0);
@@ -637,6 +638,19 @@ static int mv3310_resume(struct phy_device *phydev)
 	return mv3310_hwmon_config(phydev, true);
 }
 
+static int mv3310_start(struct phy_device *phydev)
+{
+	struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+
+	if (priv->firmware_failed) {
+		dev_warn(&phydev->mdio.dev,
+			 "PHY firmware failure: PHY not starting");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 /* Some PHYs in the Alaska family such as the 88X3310 and the 88E2010
  * don't set bit 14 in PMA Extended Abilities (1.11), although they do
  * support 2.5GBASET and 5GBASET. For these models, we can still read their
@@ -1486,6 +1500,7 @@ static struct phy_driver mv3310_drivers[] = {
 		.probe		= mv3310_probe,
 		.suspend	= mv3310_suspend,
 		.resume		= mv3310_resume,
+		.start		= mv3310_start,
 		.config_aneg	= mv3310_config_aneg,
 		.aneg_done	= mv3310_aneg_done,
 		.read_status	= mv3310_read_status,
@@ -1523,6 +1538,7 @@ static struct phy_driver mv3310_drivers[] = {
 		.probe		= mv3310_probe,
 		.suspend	= mv3310_suspend,
 		.resume		= mv3310_resume,
+		.start		= mv3310_start,
 		.config_init	= mv3310_config_init,
 		.config_aneg	= mv3310_config_aneg,
 		.aneg_done	= mv3310_aneg_done,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:42 +0100

phy_error() is called from phy_interrupt() or phy_state_machine(), and
uses WARN_ON() to print a backtrace. The backtrace is not useful when
reporting a PHY error.

However, a system may contain multiple ethernet PHYs, and phy_error()
gives no clue which one caused the problem.

Replace WARN_ON() with a call to phydev_err() so that we can see which
PHY had an error, and also inform the user that we are halting the PHY.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/phy.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index f5f532f913f0..0ae2b0dbf94f 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -1268,6 +1268,8 @@ void phy_stop_machine(struct phy_device *phydev)
 
 static void phy_process_error(struct phy_device *phydev)
 {
+	phydev_err(phydev, "Error detected, halting PHY\n");
+
 	/* phydev->lock must be held for the state change to be safe */
 	if (!mutex_is_locked(&phydev->lock))
 		phydev_err(phydev, "PHY-device data unsafe context\n");
@@ -1294,7 +1296,6 @@ static void phy_error_precise(struct phy_device *phydev,
  */
 void phy_error(struct phy_device *phydev)
 {
-	WARN_ON(1);
 	phy_process_error(phydev);
 }
 EXPORT_SYMBOL(phy_error);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:42 +0100

Cooled SFP+ transceivers need a longer initialisation (startup) time.
Select the initialisation time depending on the cooled option bit.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/sfp.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
index a5684ef5884b..e4526c122a6d 100644
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -172,6 +172,7 @@ static const enum gpiod_flags gpio_flags[] = {
 #define T_WAIT			msecs_to_jiffies(50)
 #define T_START_UP		msecs_to_jiffies(300)
 #define T_START_UP_BAD_GPON	msecs_to_jiffies(60000)
+#define T_START_UP_COOLED	msecs_to_jiffies(90000)
 
 /* t_reset is the time required to assert the TX_DISABLE signal to reset
  * an indicated TX_FAULT.
@@ -2345,8 +2346,11 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report)
 		mask |= SFP_F_RS0;
 	if (sfp->gpio[GPIO_RS1])
 		mask |= SFP_F_RS1;
+	if (id.ext.options & cpu_to_be16(SFP_OPTIONS_COOLED_XCVR))
+		sfp->module_t_start_up = T_START_UP_COOLED;
+	else
+		sfp->module_t_start_up = T_START_UP;
 
-	sfp->module_t_start_up = T_START_UP;
 	sfp->module_t_wait = T_WAIT;
 	sfp->phy_t_retry = T_PHY_RETRY;
 
@@ -2595,10 +2599,10 @@ static void sfp_sm_main(struct sfp *sfp, unsigned int event)
 			break;
 
 		if (sfp->state & SFP_F_TX_FAULT) {
-			/* Wait up to t_init (SFF-8472) or t_start_up (SFF-8431)
-			 * from the TX_DISABLE deassertion for the module to
-			 * initialise, which is indicated by TX_FAULT
-			 * deasserting.
+			/* Wait up to t_init (SFF-8472), t_start_up (SFF-8431),
+			 * or t_start_up_cooled (SFF-8431) from the TX_DISABLE
+			 * deassertion for the module to initialise, which is
+			 * indicated by TX_FAULT deasserting.
 			 */
 			timeout = sfp->module_t_start_up;
 			if (timeout > sfp->module_t_wait)
@@ -2617,8 +2621,8 @@ static void sfp_sm_main(struct sfp *sfp, unsigned int event)
 
 	case SFP_S_INIT:
 		if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) {
-			/* TX_FAULT is still asserted after t_init
-			 * or t_start_up, so assume there is a fault.
+			/* TX_FAULT is still asserted after t_init, t_start_up
+			 * or t_start_up_cooled, so assume there is a fault.
 			 */
 			sfp_sm_fault(sfp, SFP_S_INIT_TX_FAULT,
 				     sfp->sm_fault_retries == N_FAULT_INIT);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:43 +0100

Add a compatible for SFP+ cages.  SFP+ cages are backwards compatible,
but the ethernet device behind them may not support the slower speeds
of SFP modules.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/sfp.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
index e4526c122a6d..64233b999a40 100644
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -338,6 +338,7 @@ static const struct sff_data sfp_data = {
 static const struct of_device_id sfp_of_match[] = {
 	{ .compatible = "sff,sff", .data = &sff_data, },
 	{ .compatible = "sff,sfp", .data = &sfp_data, },
+	{ .compatible = "sff,sfp+", .data = &sfp_data, },
 	{ },
 };
 MODULE_DEVICE_TABLE(of, sfp_of_match);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:43 +0100

From: Russell King <[email-masked]>

Display SFP module information verbosely, splitting the generic parts
into a separate file.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/Makefile |   2 +-
 drivers/net/phy/sff.c    | 114 +++++++++++++++++++++++
 drivers/net/phy/sff.h    |  16 ++++
 drivers/net/phy/sfp.c    | 229 ++++++++++++++++++++++++++++++++++++++++++++---
 4 files changed, 350 insertions(+), 11 deletions(-)
 create mode 100644 drivers/net/phy/sff.c
 create mode 100644 drivers/net/phy/sff.h

diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 202ed7f450da..217e205d56f4 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -28,7 +28,7 @@ obj-$(CONFIG_PHYLIB)		+= libphy.o
 
 obj-$(CONFIG_NETWORK_PHY_TIMESTAMPING) += mii_timestamper.o
 
-obj-$(CONFIG_SFP)		+= sfp.o
+obj-$(CONFIG_SFP)		+= sff.o sfp.o
 sfp-obj-$(CONFIG_SFP)		+= sfp-bus.o
 obj-y				+= $(sfp-obj-y) $(sfp-obj-m)
 
diff --git a/drivers/net/phy/sff.c b/drivers/net/phy/sff.c
new file mode 100644
index 000000000000..a2eb56118dd4
--- /dev/null
+++ b/drivers/net/phy/sff.c
@@ -0,0 +1,114 @@
+#include <linux/kernel.h>
+#include <linux/sfp.h>
+#include "sff.h"
+
+const char *sff_link_len(char *buf, size_t size, unsigned int length,
+			 unsigned int multiplier)
+{
+	if (length == 0)
+		return "unsupported/unspecified";
+
+	if (length == 255) {
+		*buf++ = '>';
+		size -= 1;
+		length -= 1;
+	}
+
+	length *= multiplier;
+
+	if (length >= 1000)
+		snprintf(buf, size, "%u.%0*ukm",
+			length / 1000,
+			multiplier > 100 ? 1 :
+			multiplier > 10 ? 2 : 3,
+			length % 1000);
+	else
+		snprintf(buf, size, "%um", length);
+
+	return buf;
+}
+EXPORT_SYMBOL_GPL(sff_link_len);
+
+const char *sff_bitfield(char *buf, size_t size,
+			 const struct sff_bitfield *bits, unsigned int val)
+{
+	char *p = buf;
+	int n;
+
+	*p = '\0';
+	while (bits->mask) {
+		if ((val & bits->mask) == bits->val) {
+			n = snprintf(p, size, "%s%s",
+				     buf != p ? ", " : "",
+				     bits->str);
+			if (n == size)
+				break;
+			p += n;
+			size -= n;
+		}
+		bits++;
+	}
+
+	return buf;
+}
+EXPORT_SYMBOL_GPL(sff_bitfield);
+
+const char *sff_connector(unsigned int connector)
+{
+	switch (connector) {
+	case SFF8024_CONNECTOR_UNSPEC:
+		return "unknown/unspecified";
+	case SFF8024_CONNECTOR_SC:
+		return "SC";
+	case SFF8024_CONNECTOR_FIBERJACK:
+		return "Fiberjack";
+	case SFF8024_CONNECTOR_LC:
+		return "LC";
+	case SFF8024_CONNECTOR_MT_RJ:
+		return "MT-RJ";
+	case SFF8024_CONNECTOR_MU:
+		return "MU";
+	case SFF8024_CONNECTOR_SG:
+		return "SG";
+	case SFF8024_CONNECTOR_OPTICAL_PIGTAIL:
+		return "Optical pigtail";
+	case SFF8024_CONNECTOR_MPO_1X12:
+		return "MPO 1X12";
+	case SFF8024_CONNECTOR_MPO_2X16:
+		return "MPO 2X16";
+	case SFF8024_CONNECTOR_HSSDC_II:
+		return "HSSDC II";
+	case SFF8024_CONNECTOR_COPPER_PIGTAIL:
+		return "Copper pigtail";
+	case SFF8024_CONNECTOR_RJ45:
+		return "RJ45";
+	case SFF8024_CONNECTOR_MXC_2X16:
+		return "MXC 2X16";
+	default:
+		return "unknown";
+	}
+}
+EXPORT_SYMBOL_GPL(sff_connector);
+
+const char *sff_encoding(unsigned int encoding)
+{
+	switch (encoding) {
+	case SFF8024_ENCODING_UNSPEC:
+		return "unspecified";
+	case SFF8024_ENCODING_8472_64B66B:
+		return "64b66b";
+	case SFF8024_ENCODING_8B10B:
+		return "8b10b";
+	case SFF8024_ENCODING_4B5B:
+		return "4b5b";
+	case SFF8024_ENCODING_NRZ:
+		return "NRZ";
+	case SFF8024_ENCODING_8472_MANCHESTER:
+		return "MANCHESTER";
+	default:
+		return "unknown";
+	}
+}
+EXPORT_SYMBOL_GPL(sff_encoding);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/sff.h b/drivers/net/phy/sff.h
new file mode 100644
index 000000000000..cd7bb7c7ae4a
--- /dev/null
+++ b/drivers/net/phy/sff.h
@@ -0,0 +1,16 @@
+#ifndef SFF_H
+#define SFF_H
+
+struct sff_bitfield {
+	unsigned int mask;
+	unsigned int val;
+	const char *str;
+};
+
+const char *sff_link_len(char *buf, size_t size, unsigned int length,
+			 unsigned int multiplier);
+const char *sff_bitfield(char *buf, size_t size,
+			 const struct sff_bitfield *bits, unsigned int val);
+const char *sff_connector(unsigned int connector);
+const char *sff_encoding(unsigned int encoding);
+#endif
diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
index 64233b999a40..8cc2672d0d77 100644
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -16,6 +16,7 @@
 #include <linux/slab.h>
 #include <linux/workqueue.h>
 
+#include "sff.h"
 #include "sfp.h"
 #include "swphy.h"
 
@@ -1676,6 +1677,114 @@ static void sfp_hwmon_exit(struct sfp *sfp)
 }
 #endif
 
+static const struct sff_bitfield sfp_options[] = {
+	{
+		.mask = SFP_OPTIONS_HIGH_POWER_LEVEL,
+		.val = SFP_OPTIONS_HIGH_POWER_LEVEL,
+		.str = "hpl",
+	}, {
+		.mask = SFP_OPTIONS_PAGING_A2,
+		.val = SFP_OPTIONS_PAGING_A2,
+		.str = "paginga2",
+	}, {
+		.mask = SFP_OPTIONS_RETIMER,
+		.val = SFP_OPTIONS_RETIMER,
+		.str = "retimer",
+	}, {
+		.mask = SFP_OPTIONS_COOLED_XCVR,
+		.val = SFP_OPTIONS_COOLED_XCVR,
+		.str = "cooled",
+	}, {
+		.mask = SFP_OPTIONS_POWER_DECL,
+		.val = SFP_OPTIONS_POWER_DECL,
+		.str = "powerdecl",
+	}, {
+		.mask = SFP_OPTIONS_RX_LINEAR_OUT,
+		.val = SFP_OPTIONS_RX_LINEAR_OUT,
+		.str = "rxlinear",
+	}, {
+		.mask = SFP_OPTIONS_RX_DECISION_THRESH,
+		.val = SFP_OPTIONS_RX_DECISION_THRESH,
+		.str = "rxthresh",
+	}, {
+		.mask = SFP_OPTIONS_TUNABLE_TX,
+		.val = SFP_OPTIONS_TUNABLE_TX,
+		.str = "tunabletx",
+	}, {
+		.mask = SFP_OPTIONS_RATE_SELECT,
+		.val = SFP_OPTIONS_RATE_SELECT,
+		.str = "ratesel",
+	}, {
+		.mask = SFP_OPTIONS_TX_DISABLE,
+		.val = SFP_OPTIONS_TX_DISABLE,
+		.str = "txdisable",
+	}, {
+		.mask = SFP_OPTIONS_TX_FAULT,
+		.val = SFP_OPTIONS_TX_FAULT,
+		.str = "txfault",
+	}, {
+		.mask = SFP_OPTIONS_LOS_INVERTED,
+		.val = SFP_OPTIONS_LOS_INVERTED,
+		.str = "los-",
+	}, {
+		.mask = SFP_OPTIONS_LOS_NORMAL,
+		.val = SFP_OPTIONS_LOS_NORMAL,
+		.str = "los+",
+	}, { }
+};
+
+static const struct sff_bitfield diagmon[] = {
+	{
+		.mask = SFP_DIAGMON_DDM,
+		.val = SFP_DIAGMON_DDM,
+		.str = "ddm",
+	}, {
+		.mask = SFP_DIAGMON_INT_CAL,
+		.val = SFP_DIAGMON_INT_CAL,
+		.str = "intcal",
+	}, {
+		.mask = SFP_DIAGMON_EXT_CAL,
+		.val = SFP_DIAGMON_EXT_CAL,
+		.str = "extcal",
+	}, {
+		.mask = SFP_DIAGMON_RXPWR_AVG,
+		.val = SFP_DIAGMON_RXPWR_AVG,
+		.str = "rxpwravg",
+	}, { }
+};
+
+static const struct sff_bitfield sfp_enhopts[] = {
+	{
+		.mask = SFP_ENHOPTS_ALARMWARN,
+		.val = SFP_ENHOPTS_ALARMWARN,
+		.str = "alarmwarn",
+	}, {
+		.mask = SFP_ENHOPTS_SOFT_TX_DISABLE,
+		.val = SFP_ENHOPTS_SOFT_TX_DISABLE,
+		.str = "soft_tx_dis",
+	}, {
+		.mask = SFP_ENHOPTS_SOFT_TX_FAULT,
+		.val = SFP_ENHOPTS_SOFT_TX_FAULT,
+		.str = "soft_tx_fault",
+	}, {
+		.mask = SFP_ENHOPTS_SOFT_RX_LOS,
+		.val = SFP_ENHOPTS_SOFT_RX_LOS,
+		.str = "soft_rx_los",
+	}, {
+		.mask = SFP_ENHOPTS_SOFT_RATE_SELECT,
+		.val = SFP_ENHOPTS_SOFT_RATE_SELECT,
+		.str = "soft_rs",
+	}, {
+		.mask = SFP_ENHOPTS_APP_SELECT_SFF8079,
+		.val = SFP_ENHOPTS_APP_SELECT_SFF8079,
+		.str = "app_sel",
+	}, {
+		.mask = SFP_ENHOPTS_SOFT_RATE_SFF8431,
+		.val = SFP_ENHOPTS_SOFT_RATE_SFF8431,
+		.str = "soft_r8431",
+	}, { }
+};
+
 /* Helpers */
 static void sfp_module_tx_disable(struct sfp *sfp)
 {
@@ -2177,6 +2286,111 @@ static int sfp_cotsworks_fixup_check(struct sfp *sfp, struct sfp_eeprom_id *id)
 	return 0;
 }
 
+static void sfp_print_module_info(struct sfp *sfp,
+				  const struct sfp_eeprom_id *id, bool cotsworks)
+{
+	unsigned int br_nom, br_min, br_max;
+	char date[9];
+	char options[80];
+
+	/* Cotsworks also gets the date code wrong. */
+	date[0] = id->ext.datecode[4 - 2 * cotsworks];
+	date[1] = id->ext.datecode[5 - 2 * cotsworks];
+	date[2] = '-';
+	date[3] = id->ext.datecode[2 + 2 * cotsworks];
+	date[4] = id->ext.datecode[3 + 2 * cotsworks];
+	date[5] = '-';
+	date[6] = id->ext.datecode[0];
+	date[7] = id->ext.datecode[1];
+	date[8] = '\0';
+
+	if (id->base.br_nominal == 0) {
+		br_min = br_nom = br_max = 0;
+	} else if (id->base.br_nominal == 255) {
+		br_nom = 250 * id->ext.br_max;
+		br_max = br_nom + br_nom * id->ext.br_min / 100;
+		br_min = br_nom - br_nom * id->ext.br_min / 100;
+	} else {
+		br_nom = id->base.br_nominal * 100;
+		br_min = br_nom - id->base.br_nominal * id->ext.br_min;
+		br_max = br_nom + id->base.br_nominal * id->ext.br_max;
+	}
+
+	dev_info(sfp->dev, "module %.*s %.*s rev %.*s sn %.*s dc %s\n",
+		 (int)sizeof(id->base.vendor_name), id->base.vendor_name,
+		 (int)sizeof(id->base.vendor_pn), id->base.vendor_pn,
+		 (int)sizeof(id->base.vendor_rev), id->base.vendor_rev,
+		 (int)sizeof(id->ext.vendor_sn), id->ext.vendor_sn, date);
+	dev_info(sfp->dev, "  %s connector, encoding %s, bitrate %u.%03u (%u.%03u-%u.%03u) Gbps\n",
+		 sff_connector(id->base.connector),
+		 sff_encoding(id->base.encoding),
+		 br_nom / 1000, br_nom % 1000,
+		 br_min / 1000, br_min % 1000, br_max / 1000, br_max % 1000);
+	dev_info(sfp->dev, "  1000BaseSX%c 1000BaseLX%c 1000BaseCX%c 1000BaseT%c 100BaseLX%c 100BaseFX%c BaseBX10%c BasePX%c\n",
+		 id->base.e1000_base_sx ? '+' : '-',
+		 id->base.e1000_base_lx ? '+' : '-',
+		 id->base.e1000_base_cx ? '+' : '-',
+		 id->base.e1000_base_t ? '+' : '-',
+		 id->base.e100_base_lx ? '+' : '-',
+		 id->base.e100_base_fx ? '+' : '-',
+		 id->base.e_base_bx10 ? '+' : '-',
+		 id->base.e_base_px ? '+' : '-');
+	dev_info(sfp->dev, "  10GBaseSR%c 10GBaseLR%c 10GBaseLRM%c 10GBaseER%c\n",
+		 id->base.e10g_base_sr ? '+' : '-',
+		 id->base.e10g_base_lr ? '+' : '-',
+		 id->base.e10g_base_lrm ? '+' : '-',
+		 id->base.e10g_base_er ? '+' : '-');
+
+	if (!id->base.sfp_ct_passive && !id->base.sfp_ct_active &&
+	    !id->base.e1000_base_t) {
+		char len_9um[16], len_om[16];
+
+		dev_info(sfp->dev, "  Wavelength %unm, fiber lengths:\n",
+			 be16_to_cpup(&id->base.optical_wavelength));
+
+		if (id->base.link_len[0] == 255)
+			strcpy(len_9um, ">254km");
+		else if (id->base.link_len[1] && id->base.link_len[1] != 255)
+			sprintf(len_9um, "%um",
+				id->base.link_len[1] * 100);
+		else if (id->base.link_len[0])
+			sprintf(len_9um, "%ukm", id->base.link_len[0]);
+		else if (id->base.link_len[1] == 255)
+			strcpy(len_9um, ">25.4km");
+		else
+			strcpy(len_9um, "unsupported");
+
+		dev_info(sfp->dev, "    9µm SM    : %s\n", len_9um);
+		dev_info(sfp->dev, " 62.5µm MM OM1: %s\n",
+			 sff_link_len(len_om, sizeof(len_om),
+				      id->base.link_len[3], 10));
+		dev_info(sfp->dev, "   50µm MM OM2: %s\n",
+			 sff_link_len(len_om, sizeof(len_om),
+				      id->base.link_len[2], 10));
+		dev_info(sfp->dev, "   50µm MM OM3: %s\n",
+			 sff_link_len(len_om, sizeof(len_om),
+				      id->base.link_len[5], 10));
+		dev_info(sfp->dev, "   50µm MM OM4: %s\n",
+			 sff_link_len(len_om, sizeof(len_om),
+				      id->base.link_len[4], 10));
+	} else {
+		char len[16];
+		dev_info(sfp->dev, "  Copper length: %s\n",
+			 sff_link_len(len, sizeof(len),
+				      id->base.link_len[4], 1));
+	}
+
+	dev_info(sfp->dev, "  Options: %s\n",
+		 sff_bitfield(options, sizeof(options), sfp_options,
+			      be16_to_cpu(id->ext.options)));
+	dev_info(sfp->dev, "  Diagnostics: %s\n",
+		 sff_bitfield(options, sizeof(options), diagmon,
+			      id->ext.diagmon));
+	dev_info(sfp->dev, "  EnhOpts: %s\n",
+		 sff_bitfield(options, sizeof(options), sfp_enhopts,
+			      id->ext.enhopts));
+}
+
 static int sfp_module_parse_sff8472(struct sfp *sfp)
 {
 	/* If the module requires address swap mode, warn about it */
@@ -2243,9 +2457,9 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report)
 		}
 	}
 
-	/* Cotsworks do not seem to update the checksums when they
-	 * do the final programming with the final module part number,
-	 * serial number and date code.
+	/* Cotsworks do not seem to update the checksums when they update the
+	 * module part number, serial number and date code. They also format
+	 * the date code incorrectly.
 	 */
 	cotsworks = !memcmp(id.base.vendor_name, "COTSWORKS       ", 16);
 	cotsworks_sfbg = !memcmp(id.base.vendor_pn, "SFBG", 4);
@@ -2306,14 +2520,9 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report)
 		}
 	}
 
-	sfp->id = id;
+	sfp_print_module_info(sfp, &id, cotsworks);
 
-	dev_info(sfp->dev, "module %.*s %.*s rev %.*s sn %.*s dc %.*s\n",
-		 (int)sizeof(id.base.vendor_name), id.base.vendor_name,
-		 (int)sizeof(id.base.vendor_pn), id.base.vendor_pn,
-		 (int)sizeof(id.base.vendor_rev), id.base.vendor_rev,
-		 (int)sizeof(id.ext.vendor_sn), id.ext.vendor_sn,
-		 (int)sizeof(id.ext.datecode), id.ext.datecode);
+	sfp->id = id;
 
 	/* Check whether we support this module */
 	if (!sfp->type->module_supported(&id)) {

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:43 +0100

Add a supported_interfaces member to phylib so we know which
interfaces a PHY supports. Currently, set any unconverted driver
to indicate all interfaces are supported.

Signed-off-by: Russell King <[email-masked]>
---
 include/linux/phy.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/include/linux/phy.h b/include/linux/phy.h
index d9c4ff3344c3..e07deab4d3ad 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -731,6 +731,9 @@ struct phy_device {
 	/* Host supported PHY interface types. Should be ignored if empty. */
 	DECLARE_PHY_INTERFACE_MASK(host_interfaces);
 
+	/* supported PHY interface types */
+	DECLARE_PHY_INTERFACE_MASK(supported_interfaces);
+
 	/* Energy efficient ethernet modes which should be prohibited */
 	u32 eee_broken_modes;
 	bool enable_tx_lpi;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:43 +0100

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/bcm84881.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/net/phy/bcm84881.c b/drivers/net/phy/bcm84881.c
index 69540f1dd32e..95c239474b43 100644
--- a/drivers/net/phy/bcm84881.c
+++ b/drivers/net/phy/bcm84881.c
@@ -63,6 +63,10 @@ static int bcm84881_probe(struct phy_device *phydev)
 	    (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask)
 		return -ENODEV;
 
+	__set_bit(PHY_INTERFACE_MODE_SGMII, phydev->supported_interfaces);
+	__set_bit(PHY_INTERFACE_MODE_2500BASEX, phydev->supported_interfaces);
+	__set_bit(PHY_INTERFACE_MODE_10GBASER, phydev->supported_interfaces);
+
 	return 0;
 }

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:43 +0100

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/marvell.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c
index 61af4b651aed..9a24752382d5 100644
--- a/drivers/net/phy/marvell.c
+++ b/drivers/net/phy/marvell.c
@@ -3549,6 +3549,15 @@ static int marvell_probe_pause(struct phy_device *phydev, u16 tx_pause_mask,
 {
 	struct marvell_priv *priv;
 
+	__set_bit(PHY_INTERFACE_MODE_GMII, phydev->supported_interfaces);
+	__set_bit(PHY_INTERFACE_MODE_SGMII, phydev->supported_interfaces);
+	__set_bit(PHY_INTERFACE_MODE_TBI, phydev->supported_interfaces);
+	__set_bit(PHY_INTERFACE_MODE_RGMII, phydev->supported_interfaces);
+	__set_bit(PHY_INTERFACE_MODE_RGMII_ID, phydev->supported_interfaces);
+	__set_bit(PHY_INTERFACE_MODE_RGMII_RXID, phydev->supported_interfaces);
+	__set_bit(PHY_INTERFACE_MODE_RGMII_TXID, phydev->supported_interfaces);
+	__set_bit(PHY_INTERFACE_MODE_RTBI, phydev->supported_interfaces);
+
 	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
 	if (!priv)
 		return -ENOMEM;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:43 +0100

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/marvell10g.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c
index 5aa810807b36..eb05a578dbd7 100644
--- a/drivers/net/phy/marvell10g.c
+++ b/drivers/net/phy/marvell10g.c
@@ -166,7 +166,6 @@ struct mv3310_chip {
 };
 
 struct mv3310_priv {
-	DECLARE_BITMAP(supported_interfaces, PHY_INTERFACE_MODE_MAX);
 	const struct mv3310_mactype *mactype;
 
 	u32 firmware_ver;
@@ -612,7 +611,7 @@ static int mv3310_probe(struct phy_device *phydev)
 	if (ret)
 		return ret;
 
-	chip->init_supported_interfaces(priv->supported_interfaces);
+	chip->init_supported_interfaces(phydev->supported_interfaces);
 
 	return phy_sfp_probe(phydev, &mv3310_sfp_ops);
 }
@@ -890,7 +889,7 @@ static int mv3310_config_init(struct phy_device *phydev)
 	int err, mactype;
 
 	/* Check that the PHY interface type is compatible */
-	if (!test_bit(phydev->interface, priv->supported_interfaces))
+	if (!test_bit(phydev->interface, phydev->supported_interfaces))
 		return -ENODEV;
 
 	phydev->mdix_ctrl = ETH_TP_MDI_AUTO;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:43 +0100

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/aquantia/aquantia_main.c | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/aquantia/aquantia_main.c b/drivers/net/phy/aquantia/aquantia_main.c
index e982e9ce44a5..15021e36e468 100644
--- a/drivers/net/phy/aquantia/aquantia_main.c
+++ b/drivers/net/phy/aquantia/aquantia_main.c
@@ -767,6 +767,19 @@ static int aqr111_config_init(struct phy_device *phydev)
 	return aqr107_config_init(phydev);
 }
 
+static int aqr113c_probe(struct phy_device *phydev)
+{
+	unsigned long *supported = phydev->supported_interfaces;
+
+	__set_bit(PHY_INTERFACE_MODE_USXGMII, supported);
+	__set_bit(PHY_INTERFACE_MODE_10GBASER, supported);
+	__set_bit(PHY_INTERFACE_MODE_5GBASER, supported);
+	__set_bit(PHY_INTERFACE_MODE_2500BASEX, supported);
+	__set_bit(PHY_INTERFACE_MODE_SGMII, supported);
+
+	return aqr107_probe(phydev);
+}
+
 static struct phy_driver aqr_driver[] = {
 {
 	PHY_ID_MATCH_MODEL(PHY_ID_AQ1202),
@@ -974,7 +987,7 @@ static struct phy_driver aqr_driver[] = {
 {
 	PHY_ID_MATCH_MODEL(PHY_ID_AQR113C),
 	.name           = "Aquantia AQR113C",
-	.probe          = aqr107_probe,
+	.probe          = aqr113c_probe,
 	.get_rate_matching = aqr107_get_rate_matching,
 	.config_init    = aqr113c_config_init,
 	.config_aneg    = aqr_config_aneg,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:43 +0100

Select the initial SFP PHY interface mode from the PHY supported
interface bitmaps.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/phylink.c | 53 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 51 insertions(+), 2 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index ed1f99407aef..e39ea48a4c6d 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -3725,7 +3725,9 @@ static void phylink_sfp_link_up(void *upstream)
 
 static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
 {
+	DECLARE_PHY_INTERFACE_MASK(interfaces);
 	struct phylink *pl = upstream;
+	phy_interface_t interface;
 	int ret;
 
 	/*
@@ -3741,8 +3743,55 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
 	phy_interface_and(phy->host_interfaces, phylink_sfp_interfaces,
 			  pl->config->supported_interfaces);
 
-	/* Do the initial configuration */
-	return phylink_sfp_config_phy(pl, phy);
+	if (phy_interface_empty(phy->supported_interfaces)) {
+		phylink_dbg(pl, "copper SFP: PHY provides empty supported_interfaces\n");
+
+		/* Do the initial configuration */
+		return phylink_sfp_config_phy(pl, phy);
+	}
+
+	phylink_dbg(pl, "copper SFP: interfaces=[mac=%*pbl, sfp=%*pbl]\n",
+		    (int)PHY_INTERFACE_MODE_MAX,
+		    pl->config->supported_interfaces,
+		    (int)PHY_INTERFACE_MODE_MAX,
+		    phy->supported_interfaces);
+
+	phy_interface_and(interfaces, phy->supported_interfaces,
+			  pl->config->supported_interfaces);
+	interface = phylink_choose_sfp_interface(pl, interfaces);
+	if (interface == PHY_INTERFACE_MODE_NA) {
+		phylink_err(pl,
+			    "selection of interface for PHY failed\n");
+		return -EINVAL;
+	}
+
+	phylink_dbg(pl, "copper SFP: chosen %s interface\n",
+		    phy_modes(interface));
+
+	ret = phylink_attach_phy(pl, phy, interface);
+	if (ret < 0)
+		return ret;
+
+	ret = phylink_bringup_phy(pl, phy, interface);
+	if (ret) {
+		phy_detach(phy);
+		return ret;
+	}
+
+	if (pl->req_link_an_mode != MLO_AN_INBAND ||
+	    pl->link_config.interface != interface) {
+		pl->link_config.interface = interface;
+		pl->req_link_an_mode = MLO_AN_INBAND;
+
+		phylink_info(pl, "switched to %s/%s link mode\n",
+			     phylink_an_mode_str(pl->req_link_an_mode),
+			     phy_modes(pl->link_config.interface));
+	}
+
+	if (!test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state))
+		phylink_mac_initial_config(pl, false);
+
+	return 0;
 }

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:43 +0100

Add helpers to decode the mode argument passed to the various MAC and
PCS functions.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 include/linux/phylink.h | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/include/linux/phylink.h b/include/linux/phylink.h
index b0c450531f8d..a2d10259a134 100644
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -95,11 +95,26 @@ enum {
 	MAC_400000FD	= BIT(18),
 };
 
-static inline bool phylink_autoneg_inband(unsigned int mode)
+static inline bool phylink_mode_inband(unsigned int mode)
 {
 	return mode == MLO_AN_INBAND;
 }
 
+static inline bool phylink_mode_fixed(unsigned int mode)
+{
+	return mode == MLO_AN_FIXED;
+}
+
+static inline bool phylink_mode_phy(unsigned int mode)
+{
+	return mode == MLO_AN_PHY;
+}
+
+static inline bool phylink_autoneg_inband(unsigned int mode)
+{
+	return phylink_mode_inband(mode);
+}
+
 /**
  * struct phylink_link_state - link state structure
  * @advertising: ethtool bitmask containing advertised link modes

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:44 +0100

Use the phylink_mode_*() helpers in all drivers so we can change the
definition of the "mode" argument.

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/dsa/b53/b53_common.c         |  7 ++++---
 drivers/net/dsa/bcm_sf2.c                |  2 +-
 drivers/net/dsa/mv88e6xxx/chip.c         | 13 +++++++------
 drivers/net/dsa/mv88e6xxx/port.c         |  2 +-
 drivers/net/ethernet/cadence/macb_main.c |  2 +-
 drivers/net/phy/phylink.c                | 29 +++++++++++++++--------------
 6 files changed, 29 insertions(+), 26 deletions(-)

diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c
index 0783fc121bbb..be2e5ee6bc5f 100644
--- a/drivers/net/dsa/b53/b53_common.c
+++ b/drivers/net/dsa/b53/b53_common.c
@@ -1426,10 +1426,10 @@ static void b53_phylink_mac_link_down(struct phylink_config *config,
 	struct b53_device *dev = dp->ds->priv;
 	int port = dp->index;
 
-	if (mode == MLO_AN_PHY)
+	if (phylink_mode_phy(mode))
 		return;
 
-	if (mode == MLO_AN_FIXED) {
+	if (phylink_mode_fixed(mode)) {
 		b53_force_link(dev, port, false);
 		return;
 	}
@@ -1452,12 +1452,13 @@ static void b53_phylink_mac_link_up(struct phylink_config *config,
 	struct ethtool_keee *p = &dev->ports[dp->index].eee;
 	int port = dp->index;
 
-	if (mode == MLO_AN_PHY) {
+	if (phylink_mode_phy(mode)) {
 		/* Re-negotiate EEE if it was enabled already */
 		p->eee_enabled = b53_eee_init(ds, port, phydev);
 		return;
 	}
 
+	if (phylink_mode_fixed(mode)) {
 	if (mode == MLO_AN_FIXED) {
 		/* Force flow control on BCM5301x's CPU port */
 		if (is5301x(dev) && dsa_is_cpu_port(ds, port))
diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c
index 0e663ec0c12a..c3ba405b3c1a 100644
--- a/drivers/net/dsa/bcm_sf2.c
+++ b/drivers/net/dsa/bcm_sf2.c
@@ -897,7 +897,7 @@ static void bcm_sf2_sw_mac_link_up(struct phylink_config *config,
 
 	core_writel(priv, reg, offset);
 
-	if (mode == MLO_AN_PHY && phydev) {
+	if (phylink_mode_phy(mode) && phydev) {
 		p = &priv->dev->ports[port].eee;
 		p->eee_enabled = b53_eee_init(dp->ds, port, phydev);
 	}
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index d4e83b04aeff..8d63a84b94e1 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -888,7 +888,7 @@ static int mv88e6xxx_mac_prepare(struct phylink_config *config,
 	 * is not forced down. Force the link down while we reconfigure the
 	 * interface mode.
 	 */
-	if (mode == MLO_AN_INBAND &&
+	if (phylink_mode_inband(mode) &&
 	    chip->ports[port].interface != interface &&
 	    chip->info->ops->port_set_link) {
 		mv88e6xxx_reg_lock(chip);
@@ -911,7 +911,7 @@ static void mv88e6xxx_mac_config(struct phylink_config *config,
 
 	mv88e6xxx_reg_lock(chip);
 
-	if (mode != MLO_AN_PHY || !mv88e6xxx_phy_is_internal(chip, port)) {
+	if (!phylink_mode_phy(mode) || !mv88e6xxx_phy_is_internal(chip, port)) {
 		err = mv88e6xxx_port_config_interface(chip, port,
 						      state->interface);
 		if (err && err != -EOPNOTSUPP)
@@ -942,9 +942,10 @@ static int mv88e6xxx_mac_finish(struct phylink_config *config,
 	mv88e6xxx_reg_lock(chip);
 
 	if (chip->info->ops->port_set_link &&
-	    ((mode == MLO_AN_INBAND &&
+	    ((phylink_mode_inband(mode) &&
 	      chip->ports[port].interface != interface) ||
-	     (mode == MLO_AN_PHY && mv88e6xxx_port_ppu_updates(chip, port))))
+	     (phylink_mode_phy(mode) &&
+	      mv88e6xxx_port_ppu_updates(chip, port))))
 		err = chip->info->ops->port_set_link(chip, port, LINK_UNFORCED);
 
 	mv88e6xxx_reg_unlock(chip);
@@ -971,7 +972,7 @@ static void mv88e6xxx_mac_link_down(struct phylink_config *config,
 	 * updated by the switch or if we are using fixed-link mode.
 	 */
 	if ((!mv88e6xxx_port_ppu_updates(chip, port) ||
-	     mode == MLO_AN_FIXED) && ops->port_sync_link)
+	     phylink_mode_fixed(mode)) && ops->port_sync_link)
 		err = ops->port_sync_link(chip, port, mode, false);
 
 	if (!err && ops->port_set_speed_duplex)
@@ -1004,7 +1005,7 @@ static void mv88e6xxx_mac_link_up(struct phylink_config *config,
 	 * mode.
 	 */
 	if (!mv88e6xxx_port_ppu_updates(chip, port) ||
-	    mode == MLO_AN_FIXED) {
+	    phylink_mode_fixed(mode)) {
 		if (ops->port_set_speed_duplex) {
 			err = ops->port_set_speed_duplex(chip, port,
 							 speed, duplex);
diff --git a/drivers/net/dsa/mv88e6xxx/port.c b/drivers/net/dsa/mv88e6xxx/port.c
index 02870f001f5f..ef9bb3974f33 100644
--- a/drivers/net/dsa/mv88e6xxx/port.c
+++ b/drivers/net/dsa/mv88e6xxx/port.c
@@ -203,7 +203,7 @@ int mv88e6185_port_sync_link(struct mv88e6xxx_chip *chip, int port, unsigned int
 	int err = 0;
 	int link;
 
-	if (mode == MLO_AN_INBAND)
+	if (phylink_mode_inband(mode))
 		link = LINK_UNFORCED;
 	else if (isup)
 		link = LINK_FORCED_UP;
diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index dcd3f54ed0cf..05ec8da4a69d 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -687,7 +687,7 @@ static void macb_mac_config(struct phylink_config *config, unsigned int mode,
 		u32 pcsctrl, old_pcsctrl;
 
 		old_pcsctrl = gem_readl(bp, PCSCNTRL);
-		if (mode == MLO_AN_FIXED)
+		if (phylink_mode_fixed(mode))
 			pcsctrl = old_pcsctrl & ~GEM_BIT(PCSAUTONEG);
 		else
 			pcsctrl = old_pcsctrl | GEM_BIT(PCSAUTONEG);
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index e39ea48a4c6d..aa54eac2b9a0 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -912,7 +912,7 @@ static int phylink_parse_mode(struct phylink *pl,
 
 	if ((fwnode_property_read_string(fwnode, "managed", &managed) == 0 &&
 	     strcmp(managed, "in-band-status") == 0)) {
-		if (pl->cfg_link_an_mode == MLO_AN_FIXED) {
+		if (phylink_mode_fixed(pl->cfg_link_an_mode)) {
 			phylink_err(pl,
 				    "can't use both fixed-link and in-band-status\n");
 			return -EINVAL;
@@ -921,7 +921,7 @@ static int phylink_parse_mode(struct phylink *pl,
 		pl->cfg_link_an_mode = MLO_AN_INBAND;
 	}
 
-	if (pl->cfg_link_an_mode == MLO_AN_INBAND) {
+	if (phylink_mode_inband(pl->cfg_link_an_mode)) {
 		linkmode_zero(pl->supported);
 		phylink_set(pl->supported, MII);
 		phylink_set(pl->supported, Autoneg);
@@ -1082,13 +1082,14 @@ static unsigned int phylink_query_inband(struct phylink *pl,
 
 static void phylink_pcs_poll_stop(struct phylink *pl)
 {
-	if (pl->cfg_link_an_mode == MLO_AN_INBAND)
+	if (phylink_mode_inband(pl->cfg_link_an_mode))
 		del_timer(&pl->link_poll);
 }
 
 static void phylink_pcs_poll_start(struct phylink *pl)
 {
-	if (pl->pcs && pl->pcs->poll && pl->cfg_link_an_mode == MLO_AN_INBAND)
+	if (pl->pcs && pl->pcs->poll &&
+	    phylink_mode_inband(pl->cfg_link_an_mode))
 		mod_timer(&pl->link_poll, jiffies + HZ);
 }
 
@@ -1955,7 +1956,7 @@ struct phylink *phylink_create(struct phylink_config *config,
 		return ERR_PTR(ret);
 	}
 
-	if (pl->cfg_link_an_mode == MLO_AN_FIXED) {
+	if (phylink_mode_fixed(pl->cfg_link_an_mode)) {
 		ret = phylink_parse_fixedlink(pl, fwnode);
 		if (ret < 0) {
 			kfree(pl);
@@ -2202,8 +2203,8 @@ static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy,
 {
 	u32 flags = 0;
 
-	if (WARN_ON(pl->cfg_link_an_mode == MLO_AN_FIXED ||
-		    (pl->cfg_link_an_mode == MLO_AN_INBAND &&
+	if (WARN_ON(phylink_mode_fixed(pl->cfg_link_an_mode) ||
+		    (phylink_mode_inband(pl->cfg_link_an_mode) &&
 		     phy_interface_mode_is_8023z(interface) && !pl->sfp_bus)))
 		return -EINVAL;
 
@@ -2292,14 +2293,14 @@ int phylink_fwnode_phy_connect(struct phylink *pl,
 	int ret;
 
 	/* Fixed links and 802.3z are handled without needing a PHY */
-	if (pl->cfg_link_an_mode == MLO_AN_FIXED ||
-	    (pl->cfg_link_an_mode == MLO_AN_INBAND &&
+	if (phylink_mode_fixed(pl->cfg_link_an_mode) ||
+	    (phylink_mode_inband(pl->cfg_link_an_mode) &&
 	     phy_interface_mode_is_8023z(pl->link_interface)))
 		return 0;
 
 	phy_fwnode = fwnode_get_phy_node(fwnode);
 	if (IS_ERR(phy_fwnode)) {
-		if (pl->cfg_link_an_mode == MLO_AN_PHY)
+		if (phylink_mode_phy(pl->cfg_link_an_mode))
 			return -ENODEV;
 		return 0;
 	}
@@ -2449,7 +2450,7 @@ void phylink_start(struct phylink *pl)
 
 	phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_STOPPED);
 
-	if (pl->cfg_link_an_mode == MLO_AN_FIXED && pl->link_gpio) {
+	if (phylink_mode_fixed(pl->cfg_link_an_mode) && pl->link_gpio) {
 		int irq = gpiod_to_irq(pl->link_gpio);
 
 		if (irq > 0) {
@@ -2823,7 +2824,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
 		/* If we have a fixed link, refuse to change link parameters.
 		 * If the link parameters match, accept them but do nothing.
 		 */
-		if (pl->req_link_an_mode == MLO_AN_FIXED) {
+		if (phylink_mode_fixed(pl->req_link_an_mode)) {
 			if (s->speed != pl->link_config.speed ||
 			    s->duplex != pl->link_config.duplex)
 				return -EINVAL;
@@ -2839,7 +2840,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
 		 * is our default case) but do not allow the advertisement to
 		 * be changed. If the advertisement matches, simply return.
 		 */
-		if (pl->req_link_an_mode == MLO_AN_FIXED) {
+		if (phylink_mode_fixed(pl->req_link_an_mode)) {
 			if (!linkmode_equal(config.advertising,
 					    pl->link_config.advertising))
 				return -EINVAL;
@@ -2981,7 +2982,7 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl,
 
 	ASSERT_RTNL();
 
-	if (pl->req_link_an_mode == MLO_AN_FIXED)
+	if (phylink_mode_fixed(pl->req_link_an_mode))
 		return -EOPNOTSUPP;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:44 +0100

The modalias string provided in the uevent sysfs file does not conform
to the format used in PHY driver modules. One of the reasons is that
udev loading of PHY driver modules has not been an expected use case.

This patch changes the MODALIAS entry for only PHY devices from:
        MODALIAS=of:Nethernet-phyT(null)
to:
        MODALIAS=mdio:00000000001000100001010100010011

Other MDIO devices (such as DSA) remain as before.

However, having udev automatically load the module has the advantage
of making use of existing functionality to have the module loaded
before the device is bound to the driver, thus taking advantage of
multithreaded boot systems, potentially decreasing the boot time.

However, this patch will not solve any issues with the driver module
not being loaded prior to the network device needing to use the PHY.
This is something that is completely out of control of any patch to
change the uevent mechanism.

Reported-by: Yinbo Zhu <[email-masked]>
Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/phy/mdio_bus.c   |  8 ++++++++
 drivers/net/phy/phy_device.c | 14 ++++++++++++++
 include/linux/mdio.h         |  2 ++
 3 files changed, 24 insertions(+)

diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c
index 7e2f10182c0c..fada095a778e 100644
--- a/drivers/net/phy/mdio_bus.c
+++ b/drivers/net/phy/mdio_bus.c
@@ -1396,8 +1396,16 @@ static int mdio_bus_match(struct device *dev, const struct device_driver *drv)
 
 static int mdio_uevent(const struct device *dev, struct kobj_uevent_env *env)
 {
+	struct mdio_device *mdio = to_mdio_device(dev);
 	int rc;
 
+	/* Use the device-specific uevent if specified */
+	if (mdio->bus_uevent) {
+		rc = mdio->bus_uevent(mdio, env);
+		if (rc != -ENODEV)
+			return rc;
+	}
+
 	/* Some devices have extra OF data and an OF-style MODALIAS */
 	rc = of_device_uevent_modalias(dev, env);
 	if (rc != -ENODEV)
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 0718c6215aba..629aca88bbd2 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -642,6 +642,19 @@ static int phy_request_driver_module(struct phy_device *dev, u32 phy_id)
 	return 0;
 }
 
+static int phy_bus_uevent(struct mdio_device *mdiodev,
+			  struct kobj_uevent_env *env)
+{
+	struct phy_device *phydev;
+
+	phydev = container_of(mdiodev, struct phy_device, mdio);
+
+	add_uevent_var(env, "MODALIAS=" MDIO_MODULE_PREFIX MDIO_ID_FMT,
+		       MDIO_ID_ARGS(phydev->phy_id));
+
+	return 0;
+}
+
 struct phy_device *phy_device_create(struct mii_bus *bus, int addr, u32 phy_id,
 				     bool is_c45,
 				     struct phy_c45_device_ids *c45_ids)
@@ -661,6 +674,7 @@ struct phy_device *phy_device_create(struct mii_bus *bus, int addr, u32 phy_id,
 	mdiodev->dev.type = &mdio_bus_phy_type;
 	mdiodev->bus = bus;
 	mdiodev->bus_match = phy_bus_match;
+	mdiodev->bus_uevent = phy_bus_uevent;
 	mdiodev->addr = addr;
 	mdiodev->flags = MDIO_DEVICE_FLAG_PHY;
 	mdiodev->device_free = phy_mdio_device_free;
diff --git a/include/linux/mdio.h b/include/linux/mdio.h
index efeca5bd7600..e298cc4c8804 100644
--- a/include/linux/mdio.h
+++ b/include/linux/mdio.h
@@ -32,6 +32,8 @@ struct mdio_device {
 	char modalias[MDIO_NAME_SIZE];
 
 	int (*bus_match)(struct device *dev, const struct device_driver *drv);
+	int (*bus_uevent)(struct mdio_device *mdiodev,
+			  struct kobj_uevent_env *env);
 	void (*device_free)(struct mdio_device *mdiodev);
 	void (*device_remove)(struct mdio_device *mdiodev);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:44 +0100

Signed-off-by: Russell King (Oracle) <[email-masked]>
---
 drivers/net/dsa/mv88e6xxx/chip.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 8d63a84b94e1..8b1f4057fbe9 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -678,6 +678,9 @@ static void mv88e6352_phylink_get_caps(struct mv88e6xxx_chip *chip, int port,
 
 	config->mac_capabilities = MAC_SYM_PAUSE | MAC_10 | MAC_100 |
 				   MAC_1000FD;
+	config->lpi_capabilities = MAC_1000FD | MAC_100FD;
+	config->eee.eee_enabled = true;
+	config->eee.tx_lpi_enabled = true;
 
 	/* Port 4 supports automedia if the serdes is associated with it. */
 	if (port == 4) {
@@ -4626,6 +4629,7 @@ static const struct mv88e6xxx_ops mv88e6172_ops = {
 	.phy_read_c45 = mv88e6xxx_g2_smi_phy_read_c45,
 	.phy_write_c45 = mv88e6xxx_g2_smi_phy_write_c45,
 	.port_set_link = mv88e6xxx_port_set_link,
+	.port_set_eee = mv88e6xxx_port_set_eee,
 	.port_sync_link = mv88e6xxx_port_sync_link,
 	.port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay,
 	.port_set_speed_duplex = mv88e6352_port_set_speed_duplex,
@@ -4728,6 +4732,7 @@ static const struct mv88e6xxx_ops mv88e6176_ops = {
 	.phy_read_c45 = mv88e6xxx_g2_smi_phy_read_c45,
 	.phy_write_c45 = mv88e6xxx_g2_smi_phy_write_c45,
 	.port_set_link = mv88e6xxx_port_set_link,
+	.port_set_eee = mv88e6xxx_port_set_eee,
 	.port_sync_link = mv88e6xxx_port_sync_link,
 	.port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay,
 	.port_set_speed_duplex = mv88e6352_port_set_speed_duplex,
@@ -5003,6 +5008,7 @@ static const struct mv88e6xxx_ops mv88e6240_ops = {
 	.phy_read_c45 = mv88e6xxx_g2_smi_phy_read_c45,
 	.phy_write_c45 = mv88e6xxx_g2_smi_phy_write_c45,
 	.port_set_link = mv88e6xxx_port_set_link,
+	.port_set_eee = mv88e6xxx_port_set_eee,
 	.port_sync_link = mv88e6xxx_port_sync_link,
 	.port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay,
 	.port_set_speed_duplex = mv88e6352_port_set_speed_duplex,
@@ -5425,6 +5431,7 @@ static const struct mv88e6xxx_ops mv88e6352_ops = {
 	.phy_read_c45 = mv88e6xxx_g2_smi_phy_read_c45,
 	.phy_write_c45 = mv88e6xxx_g2_smi_phy_write_c45,
 	.port_set_link = mv88e6xxx_port_set_link,
+	.port_set_eee = mv88e6xxx_port_set_eee,
 	.port_sync_link = mv88e6xxx_port_sync_link,
 	.port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay,
 	.port_set_speed_duplex = mv88e6352_port_set_speed_duplex,
@@ -5614,6 +5621,7 @@ static const struct mv88e6xxx_ops mv88e6393x_ops = {
 	.phy_read_c45 = mv88e6xxx_g2_smi_phy_read_c45,
 	.phy_write_c45 = mv88e6xxx_g2_smi_phy_write_c45,
 	.port_set_link = mv88e6xxx_port_set_link,
+	/* no port_set_eee due to mv88e6393x errata 4.5 */
 	.port_sync_link = mv88e6xxx_port_sync_link,
 	.port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay,
 	.port_set_speed_duplex = mv88e6393x_port_set_speed_duplex,

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:44 +0100

Further augmentation of the comphy setup.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/boot/dts/marvell/armada-38x.dtsi  |   5 +-
 drivers/phy/marvell/phy-armada38x-comphy.c | 147 +++++++++++++++++++++++++----
 2 files changed, 132 insertions(+), 20 deletions(-)

diff --git a/arch/arm/boot/dts/marvell/armada-38x.dtsi b/arch/arm/boot/dts/marvell/armada-38x.dtsi
index 1181b13deabc..76d7d91c7264 100644
--- a/arch/arm/boot/dts/marvell/armada-38x.dtsi
+++ b/arch/arm/boot/dts/marvell/armada-38x.dtsi
@@ -356,8 +356,9 @@ gateclk: clock-gating-control@18220 {
 
 			comphy: phy@18300 {
 				compatible = "marvell,armada-380-comphy";
-				reg-names = "comphy", "conf";
-				reg = <0x18300 0x100>, <0x18460 4>;
+				reg-names = "comphy", "pipe", "conf";
+				reg = <0x18300 0x100>, <0xa0000 0x3000>,
+					<0x18460 4>;
 				#address-cells = <1>;
 				#size-cells = <0>;
 
diff --git a/drivers/phy/marvell/phy-armada38x-comphy.c b/drivers/phy/marvell/phy-armada38x-comphy.c
index 5063361b0120..a4533c924edd 100644
--- a/drivers/phy/marvell/phy-armada38x-comphy.c
+++ b/drivers/phy/marvell/phy-armada38x-comphy.c
@@ -16,32 +16,49 @@
 #define MAX_A38X_COMPHY	6
 #define MAX_A38X_PORTS	3
 
+/* Common PHY registers */
+#define COMPHY_REG_SIZE		0x28
 #define COMPHY_CFG1		0x00
+#define  COMPHY_CFG1_RX_INIT		BIT(30)
 #define  COMPHY_CFG1_GEN_TX(x)		((x) << 26)
 #define  COMPHY_CFG1_GEN_TX_MSK		COMPHY_CFG1_GEN_TX(15)
 #define  COMPHY_CFG1_GEN_RX(x)		((x) << 22)
 #define  COMPHY_CFG1_GEN_RX_MSK		COMPHY_CFG1_GEN_RX(15)
+#define  COMPHY_CFG1_POWER_UP_TX	BIT(18)
+#define  COMPHY_CFG1_POWER_UP_RX	BIT(17)
+#define  COMPHY_CFG1_POWER_UP_PLL	BIT(16)
 #define  GEN_SGMII_1_25GBPS		6
 #define  GEN_SGMII_3_125GBPS		8
 
 #define COMPHY_STAT1		0x18
 #define  COMPHY_STAT1_PLL_RDY_TX	BIT(3)
 #define  COMPHY_STAT1_PLL_RDY_RX	BIT(2)
+#define  COMPHY_STAT1_RX_INIT_DONE	BIT(0)
 
 #define COMPHY_SELECTOR		0xfc
 
+/* Common PHY and Pipe Registers */
+#define PIPE_REG_SIZE		0x800
+#define PIPE_POWER_CTRL		0x148
+#define  PIPE_POWER_CTRL_SFT_RST_NO_REG	BIT(10)
+/* u-boot sets bit 0 during comphy initialisation, but it is undocumented */
+#define  PIPE_POWER_CTRL_BIT0		BIT(0)
+
 struct a38x_comphy;
 
 struct a38x_comphy_lane {
 	void __iomem *base;
+	void __iomem *pipe;
 	struct a38x_comphy *priv;
 	unsigned int n;
 
 	int port;
+	u8 gen;
 };
 
 struct a38x_comphy {
 	void __iomem *base;
+	void __iomem *pipe;
 	void __iomem *conf;
 	struct device *dev;
 	struct a38x_comphy_lane lane[MAX_A38X_COMPHY];
@@ -94,6 +111,7 @@ static void a38x_comphy_set_speed(struct a38x_comphy_lane *lane,
 		            COMPHY_CFG1_GEN_RX(gen_rx));
 }
 
+/* Poll every 1ms for 150ms for a status */
 static int a38x_comphy_poll(struct a38x_comphy_lane *lane,
 			    unsigned int offset, u32 mask, u32 value)
 {
@@ -111,6 +129,97 @@ static int a38x_comphy_poll(struct a38x_comphy_lane *lane,
 	return ret;
 }
 
+static int a38x_comphy_power_up(struct a38x_comphy_lane *lane)
+{
+	/* Power up TX, RX and PLL */
+	a38x_comphy_set_reg(lane, COMPHY_CFG1, 0,
+			    COMPHY_CFG1_POWER_UP_TX | COMPHY_CFG1_POWER_UP_RX |
+			    COMPHY_CFG1_POWER_UP_PLL);
+
+	/* Wait for power up */
+	return a38x_comphy_poll(lane, COMPHY_STAT1,
+				COMPHY_STAT1_PLL_RDY_TX |
+				COMPHY_STAT1_PLL_RDY_RX,
+				COMPHY_STAT1_PLL_RDY_TX |
+				COMPHY_STAT1_PLL_RDY_RX);
+}
+
+static int a38x_comphy_rx_init(struct a38x_comphy_lane *lane)
+{
+	int ret;
+
+	/* Perform RX init */
+	a38x_comphy_set_reg(lane, COMPHY_CFG1, 0, COMPHY_CFG1_RX_INIT);
+
+	/* Wait for RX init done */
+	ret = a38x_comphy_poll(lane, COMPHY_STAT1, COMPHY_STAT1_RX_INIT_DONE,
+			       COMPHY_STAT1_RX_INIT_DONE);
+
+	/* Clear RX init */
+	a38x_comphy_set_reg(lane, COMPHY_CFG1, COMPHY_CFG1_RX_INIT, 0);
+
+	return ret;
+}
+
+static int a38x_comphy_power_off(struct phy *phy)
+{
+	struct a38x_comphy_lane *lane = phy_get_drvdata(phy);
+
+	a38x_set_conf(lane, false);
+
+	/* Soft reset */
+	if (lane->pipe) {
+		u32 rst = PIPE_POWER_CTRL_SFT_RST_NO_REG | PIPE_POWER_CTRL_BIT0;
+		u32 val;
+
+		val = readl_relaxed(lane->pipe + PIPE_POWER_CTRL);
+		writel(val | rst, lane->pipe + PIPE_POWER_CTRL);
+	}
+
+	a38x_comphy_set_reg(lane, COMPHY_CFG1,
+			    COMPHY_CFG1_POWER_UP_TX | COMPHY_CFG1_POWER_UP_RX |
+			    COMPHY_CFG1_POWER_UP_PLL, 0);
+
+	return 0;
+}
+
+static int a38x_comphy_power_on(struct phy *phy)
+{
+	struct a38x_comphy_lane *lane = phy_get_drvdata(phy);
+	int ret;
+
+	/* Program the GEN settings */
+	a38x_comphy_set_speed(lane, lane->gen, lane->gen);
+
+	/* Soft reset */
+	if (lane->pipe) {
+		u32 rst = PIPE_POWER_CTRL_SFT_RST_NO_REG | PIPE_POWER_CTRL_BIT0;
+		u32 val;
+
+		val = readl_relaxed(lane->pipe + PIPE_POWER_CTRL);
+		writel(val | rst, lane->pipe + PIPE_POWER_CTRL);
+		writel(val & ~rst, lane->pipe + PIPE_POWER_CTRL);
+	}
+
+	/* Power up TX, RX and PLL and wait */
+	ret = a38x_comphy_power_up(lane);
+	if (ret)
+		goto fail;
+
+	/* Perform RX init */
+	ret = a38x_comphy_rx_init(lane);
+	if (ret)
+		goto fail;
+
+	a38x_set_conf(lane, true);
+
+	return 0;
+
+fail:
+	a38x_comphy_power_off(phy);
+	return ret;
+}
+
 /*
  * We only support changing the speed for comphys configured for GBE.
  * Since that is all we do, we only poll for PLL ready status.
@@ -119,7 +228,6 @@ static int a38x_comphy_set_mode(struct phy *phy, enum phy_mode mode, int sub)
 {
 	struct a38x_comphy_lane *lane = phy_get_drvdata(phy);
 	unsigned int gen;
-	int ret;
 
 	if (mode != PHY_MODE_ETHERNET)
 		return -EINVAL;
@@ -138,23 +246,14 @@ static int a38x_comphy_set_mode(struct phy *phy, enum phy_mode mode, int sub)
 		return -EINVAL;
 	}
 
-	a38x_set_conf(lane, false);
-
-	a38x_comphy_set_speed(lane, gen, gen);
-
-	ret = a38x_comphy_poll(lane, COMPHY_STAT1,
-			       COMPHY_STAT1_PLL_RDY_TX |
-			       COMPHY_STAT1_PLL_RDY_RX,
-			       COMPHY_STAT1_PLL_RDY_TX |
-			       COMPHY_STAT1_PLL_RDY_RX);
-
-	if (ret == 0)
-		a38x_set_conf(lane, true);
+	lane->gen = gen;
 
-	return ret;
+	return 0;
 }
 
 static const struct phy_ops a38x_comphy_ops = {
+	.power_on	= a38x_comphy_power_on,
+	.power_off	= a38x_comphy_power_off,
 	.set_mode	= a38x_comphy_set_mode,
 	.owner		= THIS_MODULE,
 };
@@ -199,6 +298,7 @@ static int a38x_comphy_probe(struct platform_device *pdev)
 	struct a38x_comphy *priv;
 	struct resource *res;
 	void __iomem *base;
+	void __iomem *pipe = NULL;
 
 	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
 	if (!priv)
@@ -208,8 +308,16 @@ static int a38x_comphy_probe(struct platform_device *pdev)
 	if (IS_ERR(base))
 		return PTR_ERR(base);
 
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pipe");
+	if (res) {
+		pipe = devm_ioremap_resource(&pdev->dev, res);
+		if (IS_ERR(base))
+			return PTR_ERR(base);
+	}
+
 	priv->dev = &pdev->dev;
 	priv->base = base;
+	priv->pipe = pipe;
 
 	/* Optional */
 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "conf");
@@ -236,16 +344,19 @@ static int a38x_comphy_probe(struct platform_device *pdev)
 			continue;
 		}
 
+		priv->lane[val].base = base + COMPHY_REG_SIZE * val;
+		if (pipe)
+			priv->lane[val].pipe = pipe + PIPE_REG_SIZE * val;
+		priv->lane[val].priv = priv;
+		priv->lane[val].n = val;
+		priv->lane[val].port = -1;
+
 		phy = devm_phy_create(&pdev->dev, child, &a38x_comphy_ops);
 		if (IS_ERR(phy)) {
 			of_node_put(child);
 			return PTR_ERR(phy);
 		}
 
-		priv->lane[val].base = base + 0x28 * val;
-		priv->lane[val].priv = priv;
-		priv->lane[val].n = val;
-		priv->lane[val].port = -1;
 		phy_set_drvdata(phy, &priv->lane[val]);
 	}

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:44 +0100

Split out the code handling the GMAC from the rest of the driver.  This
block appears to be shared amongst several revisions of the IP.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/ethernet/marvell/Kconfig  |   4 +
 drivers/net/ethernet/marvell/Makefile |   1 +
 drivers/net/ethernet/marvell/mvgmac.c | 369 ++++++++++++++++++++++++++++++++++
 drivers/net/ethernet/marvell/mvgmac.h |  47 +++++
 drivers/net/ethernet/marvell/mvneta.c | 355 +++-----------------------------
 5 files changed, 450 insertions(+), 326 deletions(-)
 create mode 100644 drivers/net/ethernet/marvell/mvgmac.c
 create mode 100644 drivers/net/ethernet/marvell/mvgmac.h

diff --git a/drivers/net/ethernet/marvell/Kconfig b/drivers/net/ethernet/marvell/Kconfig
index 837295fecd17..c9c9b99e3e06 100644
--- a/drivers/net/ethernet/marvell/Kconfig
+++ b/drivers/net/ethernet/marvell/Kconfig
@@ -60,6 +60,7 @@ config MVNETA_BM_ENABLE
 config MVNETA
 	tristate "Marvell Armada 370/38x/XP/37xx network interface support"
 	depends on ARCH_MVEBU || COMPILE_TEST
+	select MVGMAC
 	select MVMDIO
 	select PHYLINK
 	select PAGE_POOL
@@ -73,6 +74,9 @@ config MVNETA
 	  driver, which should be used for the older Marvell SoCs
 	  (Dove, Orion, Discovery, Kirkwood).
 
+config MVGMAC
+	tristate
+
 config MVNETA_BM
 	tristate
 	depends on !64BIT
diff --git a/drivers/net/ethernet/marvell/Makefile b/drivers/net/ethernet/marvell/Makefile
index a399defe25fd..994996990dfb 100644
--- a/drivers/net/ethernet/marvell/Makefile
+++ b/drivers/net/ethernet/marvell/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_MVMDIO) += mvmdio.o
 obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o
 obj-$(CONFIG_MVNETA_BM) += mvneta_bm.o
 obj-$(CONFIG_MVNETA) += mvneta.o
+obj-$(CONFIG_MVGMAC) += mvgmac.o
 obj-$(CONFIG_MVPP2) += mvpp2/
 obj-$(CONFIG_PXA168_ETH) += pxa168_eth.o
 obj-$(CONFIG_SKGE) += skge.o
diff --git a/drivers/net/ethernet/marvell/mvgmac.c b/drivers/net/ethernet/marvell/mvgmac.c
new file mode 100644
index 000000000000..0c1354a1e397
--- /dev/null
+++ b/drivers/net/ethernet/marvell/mvgmac.c
@@ -0,0 +1,369 @@
+/*
+ * GMAC driver for Marvell network interfaces on Armada SoCs.
+ *
+ * Copyright (C) 2012 Marvell
+ *
+ * Rami Rosen <[email-masked]>
+ * Thomas Petazzoni <[email-masked]>
+ *
+ * Split from mvneta and mvpp2 by Russell King.
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+#include <linux/bitfield.h>
+#include <linux/export.h>
+#include <linux/io.h>
+#include <linux/phylink.h>
+
+#include "mvgmac.h"
+
+enum {
+	GMAC_CTRL0_REG		= 0x00,
+	GMAC_CTRL0_PORT_ENABLE		= BIT(0),
+	GMAC_CTRL0_PORT_1000BASE_X	= BIT(1),
+	GMAC_CTRL0_MAX_RX_SIZE_SHIFT	= 2,
+	GMAC_CTRL0_MAX_RX_SIZE_MASK	= 0x1fff << GMAC_CTRL0_MAX_RX_SIZE_SHIFT,
+	GMAC_CTRL0_MIB_CNTR_ENABLE	= BIT(15),
+
+	GMAC_CTRL2_REG		= 0x08,
+	GMAC_CTRL2_INBAND_AN_SGMII	= BIT(0),
+	GMAC_CTRL2_PCS_ENABLE		= BIT(3),
+	GMAC_CTRL2_PORT_RGMII		= BIT(4),
+	GMAC_CTRL2_PORT_RESET		= BIT(6),
+
+	GMAC_ANEG_REG		= 0x0c,
+	GMAC_ANEG_FORCE_LINK_DOWN	= BIT(0),
+	GMAC_ANEG_FORCE_LINK_PASS	= BIT(1),
+	GMAC_ANEG_INBAND_AN_ENABLE	= BIT(2),
+	GMAC_ANEG_AN_BYPASS_ENABLE	= BIT(3),
+	GMAC_ANEG_INBAND_RESTART_AN	= BIT(4),
+	GMAC_ANEG_MII_SPEED		= BIT(5),
+	GMAC_ANEG_GMII_SPEED		= BIT(6),
+	GMAC_ANEG_AN_SPEED_ENABLE	= BIT(7),
+	GMAC_ANEG_CONFIG_FLOW_CTRL	= BIT(8),
+	GMAC_ANEG_ADVERT_SYM_FLOW_CTRL	= BIT(9),
+	GMAC_ANEG_AN_FLOW_CTRL_ENABLE	= BIT(11),
+	GMAC_ANEG_FULL_DUPLEX		= BIT(12),
+	GMAC_ANEG_AN_DUPLEX_ENABLE	= BIT(13),
+
+	GMAC_STATUS_REG		= 0x10,
+	MVGMAC_LINK_UP			= BIT(0),
+	MVGMAC_SPEED_1000		= BIT(1),
+	MVGMAC_SPEED_100		= BIT(2),
+	MVGMAC_FULL_DUPLEX		= BIT(3),
+	MVGMAC_RX_FLOW_CTRL_ENABLE	= BIT(4),
+	MVGMAC_TX_FLOW_CTRL_ENABLE	= BIT(5),
+	MVGMAC_RX_FLOW_CTRL_ACTIVE	= BIT(6),
+	MVGMAC_TX_FLOW_CTRL_ACTIVE	= BIT(7),
+	MVGMAC_AN_COMPLETE		= BIT(11),
+	MVGMAC_SYNC_OK			= BIT(14),
+
+	GMAC_CTRL4_REG		= 0x90,
+	GMAC_CTRL4_SHORT_PREAMBLE_ENABLE = BIT(1),
+
+	GMAC_LPI_CTRL0_REG	= 0xc0,
+	GMAC_LPI_CTRL0_TS		= 0xff << 8,
+	GMAC_LPI_CTRL1_REG	= 0xc4,
+	GMAC_LPI_CTRL1_REQ_EN		= BIT(0),
+	GMAC_LPI_CTRL1_TW		= 0xfff << 4,
+	GMAC_LPI_CTRL2_REG	= 0xc8,
+	GMAC_LPI_STATUS_REG	= 0xcc,
+	GMAC_LPI_CNTR_REG	= 0xd0,
+};
+
+#define insert(var, mask, val) ({					\
+	u32 __mask = mask;						\
+	((var) & ~(__mask)) | (((val) << __ffs(__mask)) & (__mask));	\
+})
+
+static void mvgmac_modify(void __iomem *reg, u32 mask, u32 val)
+{
+	u32 v;
+
+	val &= mask;
+	v = readl_relaxed(reg) & ~mask;
+	writel_relaxed(v | val, reg);
+}
+
+#define mvgmac_set(reg, val) mvgmac_modify(reg, val, val)
+#define mvgmac_clear(reg, val) mvgmac_modify(reg, val, 0)
+
+/* Change maximum receive size of the port. */
+void mvgmac_set_max_rx_size(struct mvgmac *gmac, size_t max_rx_size)
+{
+	int size = (max_rx_size - MARVELL_HEADER_SIZE) / 2;
+
+	mvgmac_modify(gmac->base + GMAC_CTRL0_REG,
+		      GMAC_CTRL0_MAX_RX_SIZE_MASK,
+		      FIELD_PREP(GMAC_CTRL0_MAX_RX_SIZE_MASK, size));
+}
+EXPORT_SYMBOL_GPL(mvgmac_set_max_rx_size);
+
+/* Enable the port by setting the port enable bit of the MAC control register */
+void mvgmac_port_enable(struct mvgmac *gmac)
+{
+	mvgmac_set(gmac->base + GMAC_CTRL0_REG,
+		   GMAC_CTRL0_PORT_ENABLE | GMAC_CTRL0_MIB_CNTR_ENABLE);
+}
+EXPORT_SYMBOL_GPL(mvgmac_port_enable);
+
+/* Disable the port */
+void mvgmac_port_disable(struct mvgmac *gmac)
+{
+	mvgmac_clear(gmac->base + GMAC_CTRL0_REG, GMAC_CTRL0_PORT_ENABLE);
+}
+EXPORT_SYMBOL_GPL(mvgmac_port_disable);
+
+void mvgmac_link_unforce(struct mvgmac *gmac)
+{
+	mvgmac_clear(gmac->base + GMAC_ANEG_REG,
+		     GMAC_ANEG_FORCE_LINK_PASS | GMAC_ANEG_FORCE_LINK_DOWN);
+}
+EXPORT_SYMBOL_GPL(mvgmac_link_unforce);
+
+void mvgmac_link_force_down(struct mvgmac *gmac)
+{
+	mvgmac_modify(gmac->base + GMAC_ANEG_REG,
+		      GMAC_ANEG_FORCE_LINK_PASS | GMAC_ANEG_FORCE_LINK_DOWN,
+		      GMAC_ANEG_FORCE_LINK_DOWN);
+}
+EXPORT_SYMBOL_GPL(mvgmac_link_force_down);
+
+void mvgmac_link_down(struct mvgmac *gmac, int mode)
+{
+	if (!phylink_autoneg_inband(mode))
+		mvgmac_link_force_down(gmac);
+}
+EXPORT_SYMBOL_GPL(mvgmac_link_down);
+
+void mvgmac_link_up(struct mvgmac *gmac, int mode, int speed, int duplex,
+		    bool tx_pause, bool rx_pause)
+{
+	u32 an_mask, an_val;
+
+	an_mask = GMAC_ANEG_CONFIG_FLOW_CTRL;
+	an_val = FIELD_PREP(GMAC_ANEG_CONFIG_FLOW_CTRL, tx_pause || rx_pause);
+
+	if (!phylink_autoneg_inband(mode)) {
+		an_mask |= GMAC_ANEG_FORCE_LINK_DOWN |
+			   GMAC_ANEG_FORCE_LINK_PASS |
+			   GMAC_ANEG_MII_SPEED |
+			   GMAC_ANEG_GMII_SPEED |
+			   GMAC_ANEG_FULL_DUPLEX;
+		an_val |= GMAC_ANEG_FORCE_LINK_PASS;
+
+		if (speed == SPEED_1000 || speed == SPEED_2500)
+			an_val |= GMAC_ANEG_GMII_SPEED;
+		else if (speed == SPEED_100)
+			an_val |= GMAC_ANEG_MII_SPEED;
+
+		if (duplex == DUPLEX_FULL)
+			an_val |= GMAC_ANEG_FULL_DUPLEX;
+	}
+
+	mvgmac_modify(gmac->base + GMAC_ANEG_REG, an_mask, an_val);
+}
+EXPORT_SYMBOL_GPL(mvgmac_link_up);
+
+void mvgmac_link_change(struct mvgmac *gmac)
+{
+	u32 gmac_stat = readl_relaxed(gmac->base + GMAC_STATUS_REG);
+
+	phylink_pcs_change(&gmac->pcs, !!(gmac_stat & MVGMAC_LINK_UP));
+}
+EXPORT_SYMBOL_GPL(mvgmac_link_change);
+
+void mvgmac_pcs_get_state(struct phylink_pcs *pcs,
+			  struct phylink_link_state *state)
+{
+	struct mvgmac *gmac = pcs_to_mvgmac(pcs);
+	u32 gmac_stat = readl_relaxed(gmac->base + GMAC_STATUS_REG);
+
+	if (gmac_stat & MVGMAC_SPEED_1000)
+		state->speed =
+			state->interface == PHY_INTERFACE_MODE_2500BASEX ?
+			SPEED_2500 : SPEED_1000;
+	else if (gmac_stat & MVGMAC_SPEED_100)
+		state->speed = SPEED_100;
+	else
+		state->speed = SPEED_10;
+
+	state->an_complete = !!(gmac_stat & MVGMAC_AN_COMPLETE);
+	state->link = !!(gmac_stat & MVGMAC_LINK_UP);
+	state->duplex = !!(gmac_stat & MVGMAC_FULL_DUPLEX);
+
+	if (gmac_stat & MVGMAC_RX_FLOW_CTRL_ENABLE)
+		state->pause |= MLO_PAUSE_RX;
+	if (gmac_stat & MVGMAC_TX_FLOW_CTRL_ENABLE)
+		state->pause |= MLO_PAUSE_TX;
+}
+EXPORT_SYMBOL_GPL(mvgmac_pcs_get_state);
+
+void mvgmac_pcs_an_restart(struct phylink_pcs *pcs)
+{
+	struct mvgmac *gmac = pcs_to_mvgmac(pcs);
+	u32 gmac_an = readl_relaxed(gmac->base + GMAC_ANEG_REG);
+
+	writel_relaxed(gmac_an | GMAC_ANEG_INBAND_RESTART_AN,
+		       gmac->base + GMAC_ANEG_REG);
+	writel_relaxed(gmac_an & ~GMAC_ANEG_INBAND_RESTART_AN,
+		       gmac->base + GMAC_ANEG_REG);
+}
+EXPORT_SYMBOL_GPL(mvgmac_pcs_an_restart);
+
+int mvgmac_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+		      phy_interface_t interface,
+		      const unsigned long *advertising,
+		      bool permit_pause_to_mac)
+{
+	struct mvgmac *gmac = pcs_to_mvgmac(pcs);
+	u32 mask, val, an, old_an, changed;
+
+	mask = GMAC_ANEG_INBAND_AN_ENABLE |
+	       GMAC_ANEG_INBAND_RESTART_AN |
+	       GMAC_ANEG_AN_SPEED_ENABLE |
+	       GMAC_ANEG_AN_FLOW_CTRL_ENABLE |
+	       GMAC_ANEG_AN_DUPLEX_ENABLE;
+
+	if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
+		mask |= GMAC_ANEG_MII_SPEED |
+			GMAC_ANEG_GMII_SPEED |
+			GMAC_ANEG_FULL_DUPLEX;
+		val = GMAC_ANEG_INBAND_AN_ENABLE;
+
+		if (interface == PHY_INTERFACE_MODE_SGMII) {
+			/* SGMII mode receives the speed and duplex from PHY */
+			val |= GMAC_ANEG_AN_SPEED_ENABLE |
+			       GMAC_ANEG_AN_DUPLEX_ENABLE;
+		} else {
+			/* 802.3z mode has fixed speed and duplex */
+			val |= GMAC_ANEG_GMII_SPEED |
+			       GMAC_ANEG_FULL_DUPLEX;
+
+			/* The FLOW_CTRL_ENABLE bit selects either the hardware
+			 * automatically or the GMAC_ANEG_FLOW_CTRL manually
+			 * controls the GMAC pause mode.
+			 */
+			if (permit_pause_to_mac)
+				val |= GMAC_ANEG_AN_FLOW_CTRL_ENABLE;
+
+			/* Update the advertisement bits */
+			mask |= GMAC_ANEG_ADVERT_SYM_FLOW_CTRL;
+			if (phylink_test(advertising, Pause))
+				val |= GMAC_ANEG_ADVERT_SYM_FLOW_CTRL;
+		}
+	} else {
+		/* Phy or fixed speed - disable in-band AN modes */
+		val = 0;
+	}
+
+	old_an = an = readl_relaxed(gmac->base + GMAC_ANEG_REG);
+	an = (an & ~mask) | val;
+	changed = old_an ^ an;
+	if (changed)
+		writel_relaxed(an, gmac->base + GMAC_ANEG_REG);
+
+	/* We are only interested in the advertisement bits changing */
+	return !!(changed & GMAC_ANEG_ADVERT_SYM_FLOW_CTRL);
+}
+EXPORT_SYMBOL_GPL(mvgmac_pcs_config);
+
+void mvgmac_config_mac(struct mvgmac *gmac, unsigned int mode,
+		       const struct phylink_link_state *state)
+{
+	u32 new_ctrl0, gmac_ctrl0 = readl_relaxed(gmac->base + GMAC_CTRL0_REG);
+	u32 new_ctrl2, gmac_ctrl2 = readl_relaxed(gmac->base + GMAC_CTRL2_REG);
+	u32 new_ctrl4, gmac_ctrl4 = readl_relaxed(gmac->base + GMAC_CTRL4_REG);
+
+	new_ctrl0 = gmac_ctrl0 & ~GMAC_CTRL0_PORT_1000BASE_X;
+	new_ctrl2 = gmac_ctrl2 & ~(GMAC_CTRL2_INBAND_AN_SGMII |
+				   GMAC_CTRL2_PORT_RESET);
+	new_ctrl4 = gmac_ctrl4 & ~GMAC_CTRL4_SHORT_PREAMBLE_ENABLE;
+
+	/* Even though it might look weird, when we're configured in
+	 * SGMII or QSGMII mode, the RGMII bit needs to be set.
+	 */
+	new_ctrl2 |= GMAC_CTRL2_PORT_RGMII;
+
+	if (state->interface == PHY_INTERFACE_MODE_QSGMII ||
+	    state->interface == PHY_INTERFACE_MODE_SGMII ||
+	    phy_interface_mode_is_8023z(state->interface))
+		new_ctrl2 |= GMAC_CTRL2_PCS_ENABLE;
+
+	if (!phylink_autoneg_inband(mode)) {
+		/* Phy or fixed speed - nothing to do, leave the
+		 * configured speed, duplex and flow control as-is.
+		 */
+	} else if (state->interface == PHY_INTERFACE_MODE_SGMII) {
+		/* SGMII mode receives the state from the PHY */
+		new_ctrl2 |= GMAC_CTRL2_INBAND_AN_SGMII;
+	} else {
+		/* 802.3z negotiation - 1000BaseX */
+		new_ctrl0 |= GMAC_CTRL0_PORT_1000BASE_X;
+	}
+
+	/* When at 2.5G, the link partner can send frames with shortened
+	 * preambles.
+	 */
+	if (state->interface == PHY_INTERFACE_MODE_2500BASEX)
+		new_ctrl4 |= GMAC_CTRL4_SHORT_PREAMBLE_ENABLE;
+
+	if (new_ctrl0 != gmac_ctrl0)
+		writel_relaxed(new_ctrl0, gmac->base + GMAC_CTRL0_REG);
+	if (new_ctrl2 != gmac_ctrl2)
+		writel_relaxed(new_ctrl2, gmac->base + GMAC_CTRL2_REG);
+	if (new_ctrl4 != gmac_ctrl4)
+		writel_relaxed(new_ctrl4, gmac->base + GMAC_CTRL4_REG);
+
+	if (gmac_ctrl2 & GMAC_CTRL2_PORT_RESET) {
+		while ((readl_relaxed(gmac->base + GMAC_CTRL2_REG) &
+			GMAC_CTRL2_PORT_RESET) != 0)
+			continue;
+	}
+}
+EXPORT_SYMBOL_GPL(mvgmac_config_mac);
+
+void mvgmac_set_lpi_timers(struct mvgmac *gmac, unsigned int timer)
+{
+	u32 ts, tw, status;
+
+	status = readl_relaxed(gmac->base + GMAC_STATUS_REG);
+	if (status & MVGMAC_SPEED_1000) {
+		/* At 1G speeds, the timer resolution are 1us, and
+		 * 802.3 says tw is 16.5us. Round up to 17us.
+		 */
+		tw = 17;
+		ts = timer;
+	} else {
+		/* At 100M speeds, the timer resolutions are 10us, and
+		 * 802.3 says tw is 30us.
+		 */
+		tw = 3;
+		ts = DIV_ROUND_UP(timer, 10);
+	}
+
+	if (ts > 255)
+		ts = 255;
+
+	mvgmac_modify(gmac->base + GMAC_LPI_CTRL0_REG,
+		      GMAC_LPI_CTRL0_TS,
+		      FIELD_PREP(GMAC_LPI_CTRL0_TS, ts));
+
+	mvgmac_modify(gmac->base + GMAC_LPI_CTRL1_REG,
+		      GMAC_LPI_CTRL1_TW,
+		      FIELD_PREP(GMAC_LPI_CTRL1_TW, tw));
+}
+EXPORT_SYMBOL_GPL(mvgmac_set_lpi_timers);
+
+void mvgmac_set_eee(struct mvgmac *gmac, bool enable)
+{
+	mvgmac_modify(gmac->base + GMAC_LPI_CTRL1_REG,
+		      GMAC_LPI_CTRL1_REQ_EN,
+		      FIELD_PREP(GMAC_LPI_CTRL1_REQ_EN, enable));
+}
+EXPORT_SYMBOL_GPL(mvgmac_set_eee);
+
+MODULE_DESCRIPTION("Marvell GMAC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/marvell/mvgmac.h b/drivers/net/ethernet/marvell/mvgmac.h
new file mode 100644
index 000000000000..34081b4fe54b
--- /dev/null
+++ b/drivers/net/ethernet/marvell/mvgmac.h
@@ -0,0 +1,47 @@
+#ifndef MVGMAC_H
+#define MVGMAC_H
+
+#include <linux/phylink.h>
+
+/* The two bytes Marvell header. Either contains a special value used by
+ * Marvell switches when a specific hardware mode is enabled (not supported
+ * by this driver) or is filled automatically by zeroes on the RX side.
+ * Those two bytes being at the front of the Ethernet header, they allow
+ * to have the IP header aligned on a 4 bytes boundary automatically: the
+ * hardware skips those two bytes on its own.
+ */
+#define MARVELL_HEADER_SIZE 2
+
+struct mvgmac {
+	void __iomem *base;
+	struct phylink_pcs pcs;
+};
+
+static inline struct mvgmac *pcs_to_mvgmac(struct phylink_pcs *pcs)
+{
+	return container_of(pcs, struct mvgmac, pcs);
+}
+
+void mvgmac_set_max_rx_size(struct mvgmac *gmac, size_t max_rx_size);
+void mvgmac_port_enable(struct mvgmac *gmac);
+void mvgmac_port_disable(struct mvgmac *gmac);
+void mvgmac_link_unforce(struct mvgmac *gmac);
+void mvgmac_link_force_down(struct mvgmac *gmac);
+void mvgmac_link_down(struct mvgmac *gmac, int mode);
+void mvgmac_link_up(struct mvgmac *gmac, int mode, int speed, int duplex,
+		    bool tx_pause, bool rx_pause);
+void mvgmac_link_change(struct mvgmac *gmac);
+void mvgmac_pcs_get_state(struct phylink_pcs *pcs,
+			  struct phylink_link_state *state);
+void mvgmac_pcs_an_restart(struct phylink_pcs *pcs);
+int mvgmac_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+		      phy_interface_t interface,
+		      const unsigned long *advertising,
+		      bool permit_pause_to_mac);
+void mvgmac_config_mac(struct mvgmac *gmac, unsigned int mode,
+		       const struct phylink_link_state *state);
+
+void mvgmac_set_lpi_timers(struct mvgmac *gmac, unsigned int timer);
+void mvgmac_set_eee(struct mvgmac *gmac, bool enable);
+
+#endif
diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
index d421257c8e6e..f8fe8f3581bc 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -34,6 +34,7 @@
 #include <linux/skbuff.h>
 #include <net/hwbm.h>
 #include "mvneta_bm.h"
+#include "mvgmac.h"
 #include <net/ip.h>
 #include <net/ipv6.h>
 #include <net/tso.h>
@@ -196,43 +197,7 @@
 #define      MVNETA_RXQ_ENABLE_MASK              0x000000ff
 #define MVETH_TXQ_TOKEN_COUNT_REG(q)             (0x2700 + ((q) << 4))
 #define MVETH_TXQ_TOKEN_CFG_REG(q)               (0x2704 + ((q) << 4))
-#define MVNETA_GMAC_CTRL_0                       0x2c00
-#define      MVNETA_GMAC_MAX_RX_SIZE_SHIFT       2
-#define      MVNETA_GMAC_MAX_RX_SIZE_MASK        0x7ffc
-#define      MVNETA_GMAC0_PORT_1000BASE_X        BIT(1)
-#define      MVNETA_GMAC0_PORT_ENABLE            BIT(0)
-#define MVNETA_GMAC_CTRL_2                       0x2c08
-#define      MVNETA_GMAC2_INBAND_AN_ENABLE       BIT(0)
-#define      MVNETA_GMAC2_PCS_ENABLE             BIT(3)
-#define      MVNETA_GMAC2_PORT_RGMII             BIT(4)
-#define      MVNETA_GMAC2_PORT_RESET             BIT(6)
-#define MVNETA_GMAC_STATUS                       0x2c10
-#define      MVNETA_GMAC_LINK_UP                 BIT(0)
-#define      MVNETA_GMAC_SPEED_1000              BIT(1)
-#define      MVNETA_GMAC_SPEED_100               BIT(2)
-#define      MVNETA_GMAC_FULL_DUPLEX             BIT(3)
-#define      MVNETA_GMAC_RX_FLOW_CTRL_ENABLE     BIT(4)
-#define      MVNETA_GMAC_TX_FLOW_CTRL_ENABLE     BIT(5)
-#define      MVNETA_GMAC_RX_FLOW_CTRL_ACTIVE     BIT(6)
-#define      MVNETA_GMAC_TX_FLOW_CTRL_ACTIVE     BIT(7)
-#define      MVNETA_GMAC_AN_COMPLETE             BIT(11)
-#define      MVNETA_GMAC_SYNC_OK                 BIT(14)
-#define MVNETA_GMAC_AUTONEG_CONFIG               0x2c0c
-#define      MVNETA_GMAC_FORCE_LINK_DOWN         BIT(0)
-#define      MVNETA_GMAC_FORCE_LINK_PASS         BIT(1)
-#define      MVNETA_GMAC_INBAND_AN_ENABLE        BIT(2)
-#define      MVNETA_GMAC_AN_BYPASS_ENABLE        BIT(3)
-#define      MVNETA_GMAC_INBAND_RESTART_AN       BIT(4)
-#define      MVNETA_GMAC_CONFIG_MII_SPEED        BIT(5)
-#define      MVNETA_GMAC_CONFIG_GMII_SPEED       BIT(6)
-#define      MVNETA_GMAC_AN_SPEED_EN             BIT(7)
-#define      MVNETA_GMAC_CONFIG_FLOW_CTRL        BIT(8)
-#define      MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL    BIT(9)
-#define      MVNETA_GMAC_AN_FLOW_CTRL_EN         BIT(11)
-#define      MVNETA_GMAC_CONFIG_FULL_DUPLEX      BIT(12)
-#define      MVNETA_GMAC_AN_DUPLEX_EN            BIT(13)
-#define MVNETA_GMAC_CTRL_4                       0x2c90
-#define      MVNETA_GMAC4_SHORT_PREAMBLE_ENABLE  BIT(1)
+#define MVNETA_GMAC_BASE                         0x2c00
 #define MVNETA_MIB_COUNTERS_BASE                 0x3000
 #define      MVNETA_MIB_LATE_COLLISION           0x7c
 #define MVNETA_DA_FILT_SPEC_MCAST                0x3400
@@ -283,14 +248,6 @@
 					 (MVNETA_TXQ_BUCKET_REFILL_BASE_PERIOD_NS * \
 					  MVNETA_TXQ_BUCKET_REFILL_PERIOD))
 
-#define MVNETA_LPI_CTRL_0                        0x2cc0
-#define      MVNETA_LPI_CTRL_0_TS                (0xff << 8)
-#define MVNETA_LPI_CTRL_1                        0x2cc4
-#define      MVNETA_LPI_REQUEST_ENABLE           BIT(0)
-#define      MVNETA_LPI_CTRL_1_TW                (0xfff << 4)
-#define MVNETA_LPI_CTRL_2                        0x2cc8
-#define MVNETA_LPI_STATUS                        0x2ccc
-
 #define MVNETA_CAUSE_TXQ_SENT_DESC_ALL_MASK	 0xff
 
 /* Descriptor ring Macros */
@@ -312,7 +269,7 @@
  * boundary automatically: the hardware skips those two bytes on its
  * own.
  */
-#define MVNETA_MH_SIZE			2
+#define MVNETA_MH_SIZE			MARVELL_HEADER_SIZE
 
 #define MVNETA_VLAN_TAG_LEN             4
 
@@ -535,9 +492,9 @@ struct mvneta_port {
 	unsigned int tx_csum_limit;
 	struct phylink *phylink;
 	struct phylink_config phylink_config;
-	struct phylink_pcs phylink_pcs;
 	struct phy *comphy;
 
+	struct mvgmac gmac;
 	struct mvneta_bm *bm_priv;
 	struct mvneta_bm_pool *pool_long;
 	struct mvneta_bm_pool *pool_short;
@@ -926,19 +883,6 @@ mvneta_rxq_next_desc_get(struct mvneta_rx_queue *rxq)
 	return rxq->descs + rx_desc;
 }
 
-/* Change maximum receive size of the port. */
-static void mvneta_max_rx_size_set(struct mvneta_port *pp, int max_rx_size)
-{
-	u32 val;
-
-	val =  mvreg_read(pp, MVNETA_GMAC_CTRL_0);
-	val &= ~MVNETA_GMAC_MAX_RX_SIZE_MASK;
-	val |= ((max_rx_size - MVNETA_MH_SIZE) / 2) <<
-		MVNETA_GMAC_MAX_RX_SIZE_SHIFT;
-	mvreg_write(pp, MVNETA_GMAC_CTRL_0, val);
-}
-
-
 /* Set rx queue offset */
 static void mvneta_rxq_offset_set(struct mvneta_port *pp,
 				  struct mvneta_rx_queue *rxq,
@@ -1343,26 +1287,10 @@ static void mvneta_port_down(struct mvneta_port *pp)
 	udelay(200);
 }
 
-/* Enable the port by setting the port enable bit of the MAC control register */
-static void mvneta_port_enable(struct mvneta_port *pp)
-{
-	u32 val;
-
-	/* Enable port */
-	val = mvreg_read(pp, MVNETA_GMAC_CTRL_0);
-	val |= MVNETA_GMAC0_PORT_ENABLE;
-	mvreg_write(pp, MVNETA_GMAC_CTRL_0, val);
-}
-
 /* Disable the port and wait for about 200 usec before retuning */
 static void mvneta_port_disable(struct mvneta_port *pp)
 {
-	u32 val;
-
-	/* Reset the Enable bit in the Serial Control Register */
-	val = mvreg_read(pp, MVNETA_GMAC_CTRL_0);
-	val &= ~MVNETA_GMAC0_PORT_ENABLE;
-	mvreg_write(pp, MVNETA_GMAC_CTRL_0, val);
+	mvgmac_port_disable(&pp->gmac);
 
 	udelay(200);
 }
@@ -3253,14 +3181,6 @@ static irqreturn_t mvneta_percpu_isr(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
-static void mvneta_link_change(struct mvneta_port *pp)
-{
-	u32 gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS);
-
-	phylink_pcs_change(&pp->phylink_pcs,
-			   !!(gmac_stat & MVNETA_GMAC_LINK_UP));
-}
-
 /* NAPI handler
  * Bits 0 - 7 of the causeRxTx register indicate that are transmitted
  * packets on the corresponding TXQ (Bit 0 is for TX queue 1).
@@ -3290,7 +3210,7 @@ static int mvneta_poll(struct napi_struct *napi, int budget)
 
 		if (cause_misc & (MVNETA_CAUSE_PHY_STATUS_CHANGE |
 				  MVNETA_CAUSE_LINK_CHANGE))
-			mvneta_link_change(pp);
+			mvgmac_link_change(&pp->gmac);
 	}
 
 	/* Release Tx descriptors */
@@ -3747,11 +3667,11 @@ static void mvneta_start_dev(struct mvneta_port *pp)
 
 	WARN_ON(mvneta_config_interface(pp, pp->phy_interface));
 
-	mvneta_max_rx_size_set(pp, pp->pkt_size);
+	mvgmac_set_max_rx_size(&pp->gmac, pp->pkt_size);
 	mvneta_txq_max_tx_size_set(pp, pp->pkt_size);
 
 	/* start the Rx/Tx activity */
-	mvneta_port_enable(pp);
+	mvgmac_port_enable(&pp->gmac);
 
 	if (!pp->neta_armada3700) {
 		/* Enable polling on the port */
@@ -3953,11 +3873,6 @@ static int mvneta_set_mac_addr(struct net_device *dev, void *addr)
 	return 0;
 }
 
-static struct mvneta_port *mvneta_pcs_to_port(struct phylink_pcs *pcs)
-{
-	return container_of(pcs, struct mvneta_port, phylink_pcs);
-}
-
 static unsigned int mvneta_pcs_query_inband(struct phylink_pcs *pcs,
 					    phy_interface_t interface)
 {
@@ -3981,105 +3896,23 @@ static unsigned int mvneta_pcs_query_inband(struct phylink_pcs *pcs,
 	return LINK_INBAND_DISABLE;
 }
 
-static void mvneta_pcs_get_state(struct phylink_pcs *pcs,
-				 struct phylink_link_state *state)
-{
-	struct mvneta_port *pp = mvneta_pcs_to_port(pcs);
-	u32 gmac_stat;
-
-	gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS);
-
-	if (gmac_stat & MVNETA_GMAC_SPEED_1000)
-		state->speed =
-			state->interface == PHY_INTERFACE_MODE_2500BASEX ?
-			SPEED_2500 : SPEED_1000;
-	else if (gmac_stat & MVNETA_GMAC_SPEED_100)
-		state->speed = SPEED_100;
-	else
-		state->speed = SPEED_10;
-
-	state->an_complete = !!(gmac_stat & MVNETA_GMAC_AN_COMPLETE);
-	state->link = !!(gmac_stat & MVNETA_GMAC_LINK_UP);
-	state->duplex = !!(gmac_stat & MVNETA_GMAC_FULL_DUPLEX);
-
-	if (gmac_stat & MVNETA_GMAC_RX_FLOW_CTRL_ENABLE)
-		state->pause |= MLO_PAUSE_RX;
-	if (gmac_stat & MVNETA_GMAC_TX_FLOW_CTRL_ENABLE)
-		state->pause |= MLO_PAUSE_TX;
-}
-
 static int mvneta_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
 			     phy_interface_t interface,
 			     const unsigned long *advertising,
 			     bool permit_pause_to_mac)
 {
-	struct mvneta_port *pp = mvneta_pcs_to_port(pcs);
-	u32 mask, val, an, old_an, changed;
-
-	mask = MVNETA_GMAC_INBAND_AN_ENABLE |
-	       MVNETA_GMAC_INBAND_RESTART_AN |
-	       MVNETA_GMAC_AN_SPEED_EN |
-	       MVNETA_GMAC_AN_FLOW_CTRL_EN |
-	       MVNETA_GMAC_AN_DUPLEX_EN;
-
-	if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
-		mask |= MVNETA_GMAC_CONFIG_MII_SPEED |
-			MVNETA_GMAC_CONFIG_GMII_SPEED |
-			MVNETA_GMAC_CONFIG_FULL_DUPLEX;
-		val = MVNETA_GMAC_INBAND_AN_ENABLE;
-
-		if (interface == PHY_INTERFACE_MODE_SGMII) {
-			/* SGMII mode receives the speed and duplex from PHY */
-			val |= MVNETA_GMAC_AN_SPEED_EN |
-			       MVNETA_GMAC_AN_DUPLEX_EN;
-		} else {
-			/* 802.3z mode has fixed speed and duplex */
-			val |= MVNETA_GMAC_CONFIG_GMII_SPEED |
-			       MVNETA_GMAC_CONFIG_FULL_DUPLEX;
+	/* We should never see Asym_Pause set */
+	WARN_ON(phylink_test(advertising, Asym_Pause));
 
-			/* The FLOW_CTRL_EN bit selects either the hardware
-			 * automatically or the CONFIG_FLOW_CTRL manually
-			 * controls the GMAC pause mode.
-			 */
-			if (permit_pause_to_mac)
-				val |= MVNETA_GMAC_AN_FLOW_CTRL_EN;
-
-			/* Update the advertisement bits */
-			mask |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL;
-			if (phylink_test(advertising, Pause))
-				val |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL;
-		}
-	} else {
-		/* Phy or fixed speed - disable in-band AN modes */
-		val = 0;
-	}
-
-	old_an = an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
-	an = (an & ~mask) | val;
-	changed = old_an ^ an;
-	if (changed)
-		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, an);
-
-	/* We are only interested in the advertisement bits changing */
-	return !!(changed & MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL);
-}
-
-static void mvneta_pcs_an_restart(struct phylink_pcs *pcs)
-{
-	struct mvneta_port *pp = mvneta_pcs_to_port(pcs);
-	u32 gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
-
-	mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
-		    gmac_an | MVNETA_GMAC_INBAND_RESTART_AN);
-	mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
-		    gmac_an & ~MVNETA_GMAC_INBAND_RESTART_AN);
+	return mvgmac_pcs_config(pcs, neg_mode, interface, advertising,
+				 permit_pause_to_mac);
 }
 
 static const struct phylink_pcs_ops mvneta_phylink_pcs_ops = {
 	.pcs_query_inband = mvneta_pcs_query_inband,
-	.pcs_get_state = mvneta_pcs_get_state,
+	.pcs_get_state = mvgmac_pcs_get_state,
 	.pcs_config = mvneta_pcs_config,
-	.pcs_an_restart = mvneta_pcs_an_restart,
+	.pcs_an_restart = mvgmac_pcs_an_restart,
 };
 
 static struct phylink_pcs *mvneta_mac_select_pcs(struct phylink_config *config,
@@ -4088,7 +3921,7 @@ static struct phylink_pcs *mvneta_mac_select_pcs(struct phylink_config *config,
 	struct net_device *ndev = to_net_dev(config->dev);
 	struct mvneta_port *pp = netdev_priv(ndev);
 
-	return &pp->phylink_pcs;
+	return &pp->gmac.pcs;
 }
 
 static int mvneta_mac_prepare(struct phylink_config *config, unsigned int mode,
@@ -4096,7 +3929,6 @@ static int mvneta_mac_prepare(struct phylink_config *config, unsigned int mode,
 {
 	struct net_device *ndev = to_net_dev(config->dev);
 	struct mvneta_port *pp = netdev_priv(ndev);
-	u32 val;
 
 	if (pp->phy_interface != interface ||
 	    phylink_autoneg_inband(mode)) {
@@ -4105,10 +3937,7 @@ static int mvneta_mac_prepare(struct phylink_config *config, unsigned int mode,
 		 * can only change the port mode and in-band enable when the
 		 * link is down.
 		 */
-		val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
-		val &= ~MVNETA_GMAC_FORCE_LINK_PASS;
-		val |= MVNETA_GMAC_FORCE_LINK_DOWN;
-		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+		mvgmac_link_force_down(&pp->gmac);
 	}
 
 	if (pp->phy_interface != interface)
@@ -4130,55 +3959,8 @@ static void mvneta_mac_config(struct phylink_config *config, unsigned int mode,
 {
 	struct net_device *ndev = to_net_dev(config->dev);
 	struct mvneta_port *pp = netdev_priv(ndev);
-	u32 new_ctrl0, gmac_ctrl0 = mvreg_read(pp, MVNETA_GMAC_CTRL_0);
-	u32 new_ctrl2, gmac_ctrl2 = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
-	u32 new_ctrl4, gmac_ctrl4 = mvreg_read(pp, MVNETA_GMAC_CTRL_4);
-
-	new_ctrl0 = gmac_ctrl0 & ~MVNETA_GMAC0_PORT_1000BASE_X;
-	new_ctrl2 = gmac_ctrl2 & ~(MVNETA_GMAC2_INBAND_AN_ENABLE |
-				   MVNETA_GMAC2_PORT_RESET);
-	new_ctrl4 = gmac_ctrl4 & ~(MVNETA_GMAC4_SHORT_PREAMBLE_ENABLE);
-
-	/* Even though it might look weird, when we're configured in
-	 * SGMII or QSGMII mode, the RGMII bit needs to be set.
-	 */
-	new_ctrl2 |= MVNETA_GMAC2_PORT_RGMII;
-
-	if (state->interface == PHY_INTERFACE_MODE_QSGMII ||
-	    state->interface == PHY_INTERFACE_MODE_SGMII ||
-	    phy_interface_mode_is_8023z(state->interface))
-		new_ctrl2 |= MVNETA_GMAC2_PCS_ENABLE;
-
-	if (!phylink_autoneg_inband(mode)) {
-		/* Phy or fixed speed - nothing to do, leave the
-		 * configured speed, duplex and flow control as-is.
-		 */
-	} else if (state->interface == PHY_INTERFACE_MODE_SGMII) {
-		/* SGMII mode receives the state from the PHY */
-		new_ctrl2 |= MVNETA_GMAC2_INBAND_AN_ENABLE;
-	} else {
-		/* 802.3z negotiation - only 1000base-X */
-		new_ctrl0 |= MVNETA_GMAC0_PORT_1000BASE_X;
-	}
 
-	/* When at 2.5G, the link partner can send frames with shortened
-	 * preambles.
-	 */
-	if (state->interface == PHY_INTERFACE_MODE_2500BASEX)
-		new_ctrl4 |= MVNETA_GMAC4_SHORT_PREAMBLE_ENABLE;
-
-	if (new_ctrl0 != gmac_ctrl0)
-		mvreg_write(pp, MVNETA_GMAC_CTRL_0, new_ctrl0);
-	if (new_ctrl2 != gmac_ctrl2)
-		mvreg_write(pp, MVNETA_GMAC_CTRL_2, new_ctrl2);
-	if (new_ctrl4 != gmac_ctrl4)
-		mvreg_write(pp, MVNETA_GMAC_CTRL_4, new_ctrl4);
-
-	if (gmac_ctrl2 & MVNETA_GMAC2_PORT_RESET) {
-		while ((mvreg_read(pp, MVNETA_GMAC_CTRL_2) &
-			MVNETA_GMAC2_PORT_RESET) != 0)
-			continue;
-	}
+	mvgmac_config_mac(&pp->gmac, mode, state);
 }
 
 static int mvneta_mac_finish(struct phylink_config *config, unsigned int mode,
@@ -4186,7 +3968,7 @@ static int mvneta_mac_finish(struct phylink_config *config, unsigned int mode,
 {
 	struct net_device *ndev = to_net_dev(config->dev);
 	struct mvneta_port *pp = netdev_priv(ndev);
-	u32 val, clk;
+	u32 clk;
 
 	/* Disable 1ms clock if not in in-band mode */
 	if (!phylink_autoneg_inband(mode)) {
@@ -4202,11 +3984,8 @@ static int mvneta_mac_finish(struct phylink_config *config, unsigned int mode,
 	/* Allow the link to come up if in in-band mode, otherwise the
 	 * link is forced via mac_link_down()/mac_link_up()
 	 */
-	if (phylink_autoneg_inband(mode)) {
-		val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
-		val &= ~MVNETA_GMAC_FORCE_LINK_DOWN;
-		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
-	}
+	if (phylink_autoneg_inband(mode))
+		mvgmac_link_unforce(&pp->gmac);
 
 	return 0;
 }
@@ -4216,16 +3995,9 @@ static void mvneta_mac_link_down(struct phylink_config *config,
 {
 	struct net_device *ndev = to_net_dev(config->dev);
 	struct mvneta_port *pp = netdev_priv(ndev);
-	u32 val;
 
 	mvneta_port_down(pp);
-
-	if (!phylink_autoneg_inband(mode)) {
-		val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
-		val &= ~MVNETA_GMAC_FORCE_LINK_PASS;
-		val |= MVNETA_GMAC_FORCE_LINK_DOWN;
-		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
-	}
+	mvgmac_link_down(&pp->gmac, mode);
 }
 
 static void mvneta_mac_link_up(struct phylink_config *config,
@@ -4236,95 +4008,25 @@ static void mvneta_mac_link_up(struct phylink_config *config,
 {
 	struct net_device *ndev = to_net_dev(config->dev);
 	struct mvneta_port *pp = netdev_priv(ndev);
-	u32 val;
-
-	if (!phylink_autoneg_inband(mode)) {
-		val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
-		val &= ~(MVNETA_GMAC_FORCE_LINK_DOWN |
-			 MVNETA_GMAC_CONFIG_MII_SPEED |
-			 MVNETA_GMAC_CONFIG_GMII_SPEED |
-			 MVNETA_GMAC_CONFIG_FLOW_CTRL |
-			 MVNETA_GMAC_CONFIG_FULL_DUPLEX);
-		val |= MVNETA_GMAC_FORCE_LINK_PASS;
-
-		if (speed == SPEED_1000 || speed == SPEED_2500)
-			val |= MVNETA_GMAC_CONFIG_GMII_SPEED;
-		else if (speed == SPEED_100)
-			val |= MVNETA_GMAC_CONFIG_MII_SPEED;
-
-		if (duplex == DUPLEX_FULL)
-			val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
-
-		if (tx_pause || rx_pause)
-			val |= MVNETA_GMAC_CONFIG_FLOW_CTRL;
-
-		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
-	} else {
-		/* When inband doesn't cover flow control or flow control is
-		 * disabled, we need to manually configure it. This bit will
-		 * only have effect if MVNETA_GMAC_AN_FLOW_CTRL_EN is unset.
-		 */
-		val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
-		val &= ~MVNETA_GMAC_CONFIG_FLOW_CTRL;
-
-		if (tx_pause || rx_pause)
-			val |= MVNETA_GMAC_CONFIG_FLOW_CTRL;
-
-		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
-	}
 
+	mvgmac_link_up(&pp->gmac, mode, speed, duplex, tx_pause, rx_pause);
 	mvneta_port_up(pp);
 }
 
 static void mvneta_mac_disable_tx_lpi(struct phylink_config *config)
 {
 	struct mvneta_port *pp = netdev_priv(to_net_dev(config->dev));
-	u32 lpi1;
 
-	lpi1 = mvreg_read(pp, MVNETA_LPI_CTRL_1);
-	lpi1 &= ~MVNETA_LPI_REQUEST_ENABLE;
-	mvreg_write(pp, MVNETA_LPI_CTRL_1, lpi1);
+	mvgmac_set_eee(&pp->gmac, false);
 }
 
 static void mvneta_mac_enable_tx_lpi(struct phylink_config *config, u32 timer)
 {
 	struct mvneta_port *pp = netdev_priv(to_net_dev(config->dev));
-	u32 ts, tw, lpi0, lpi1, status;
-
-	status = mvreg_read(pp, MVNETA_GMAC_STATUS);
-
-	if (status & MVNETA_GMAC_SPEED_1000) {
-		/* At 1G speeds, the timer resolution are 1us, and
-		 * 802.3 says tw is 16.5us. Round up to 17us.
-		 */
-		tw = 17;
-		ts = timer;
-	} else {
-		/* At 100M speeds, the timer resolutions are 10us, and
-		 * 802.3 says tw is 30us.
-		 */
-		tw = 3;
-		ts = DIV_ROUND_UP(timer, 10);
-	}
-
-	if (ts > 255)
-		ts = 255;
-
-	/* Ensure LPI generation is disabled */
-	lpi1 = mvreg_read(pp, MVNETA_LPI_CTRL_1);
-	mvreg_write(pp, MVNETA_LPI_CTRL_1, lpi1 & ~MVNETA_LPI_REQUEST_ENABLE);
-
-	/* Configure ts */
-	lpi0 = mvreg_read(pp, MVNETA_LPI_CTRL_0) & ~MVNETA_LPI_CTRL_0_TS;
-	lpi0 |= FIELD_PREP(MVNETA_LPI_CTRL_0_TS, ts);
-	mvreg_write(pp, MVNETA_LPI_CTRL_0, lpi0);
-
-	/* Configure tw */
-	lpi1 &= ~MVNETA_LPI_CTRL_1_TW;
-	lpi1 |= FIELD_PREP(MVNETA_LPI_CTRL_1_TW, tw);
 
-	/* Enable LPI generation */
-	mvreg_write(pp, MVNETA_LPI_CTRL_1, lpi1 | MVNETA_LPI_REQUEST_ENABLE);
+	mvgmac_set_eee(&pp->gmac, false);
+	mvgmac_set_lpi_timers(&pp->gmac, timer);
+	mvgmac_set_eee(&pp->gmac, true);
 }
 
 static const struct phylink_mac_ops mvneta_phylink_ops = {
@@ -5544,8 +5246,9 @@ static int mvneta_probe(struct platform_device *pdev)
 	if (!IS_ERR(pp->clk_bus))
 		clk_prepare_enable(pp->clk_bus);
 
-	pp->phylink_pcs.ops = &mvneta_phylink_pcs_ops;
-	pp->phylink_pcs.neg_mode = true;
+	pp->gmac.base = pp->base + MVNETA_GMAC_BASE;
+	pp->gmac.pcs.ops = &mvneta_phylink_pcs_ops;
+	pp->gmac.pcs.neg_mode = true;
 
 	pp->phylink_config.dev = &dev->dev;
 	pp->phylink_config.type = PHYLINK_NETDEV;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:44 +0100

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/ethernet/marvell/mvgmac.c | 118 ++++++++++++++++++++++++++++++----
 drivers/net/ethernet/marvell/mvgmac.h |   9 +++
 drivers/net/ethernet/marvell/mvneta.c |   1 +
 3 files changed, 115 insertions(+), 13 deletions(-)

diff --git a/drivers/net/ethernet/marvell/mvgmac.c b/drivers/net/ethernet/marvell/mvgmac.c
index 0c1354a1e397..b5bf4f783396 100644
--- a/drivers/net/ethernet/marvell/mvgmac.c
+++ b/drivers/net/ethernet/marvell/mvgmac.c
@@ -20,6 +20,8 @@
 #include "mvgmac.h"
 
 enum {
+	/* N = Neta, 21 = PPV2.1, 22 = PPV2.2 */
+	/* N: 0-14	21: 0,2-15	22: 0-14 */
 	GMAC_CTRL0_REG		= 0x00,
 	GMAC_CTRL0_PORT_ENABLE		= BIT(0),
 	GMAC_CTRL0_PORT_1000BASE_X	= BIT(1),
@@ -27,12 +29,21 @@ enum {
 	GMAC_CTRL0_MAX_RX_SIZE_MASK	= 0x1fff << GMAC_CTRL0_MAX_RX_SIZE_SHIFT,
 	GMAC_CTRL0_MIB_CNTR_ENABLE	= BIT(15),
 
+	/* N:		21: 1,5,6	22:	*/
+	GMAC_CTRL1_REG		= 0x04,
+	GMAC_CTRL1_PERIODIC_XON_ENABLE	= BIT(1),
+	GMAC_CTRL1_GMII_LB_ENABLE	= BIT(5),
+	GMAC_CTRL1_PCS_LB_ENABLE	= BIT(6),
+
+	/* ALL: 0,3,4,6 */
 	GMAC_CTRL2_REG		= 0x08,
 	GMAC_CTRL2_INBAND_AN_SGMII	= BIT(0),
 	GMAC_CTRL2_PCS_ENABLE		= BIT(3),
 	GMAC_CTRL2_PORT_RGMII		= BIT(4),
 	GMAC_CTRL2_PORT_RESET		= BIT(6),
 
+	/* N:0-9,11-13	21:0,1,5-7,9,12,13	22:0-7,9-15 */
+	/* 22 bit 2 - EN_PCS_AN */
 	GMAC_ANEG_REG		= 0x0c,
 	GMAC_ANEG_FORCE_LINK_DOWN	= BIT(0),
 	GMAC_ANEG_FORCE_LINK_PASS	= BIT(1),
@@ -44,9 +55,12 @@ enum {
 	GMAC_ANEG_AN_SPEED_ENABLE	= BIT(7),
 	GMAC_ANEG_CONFIG_FLOW_CTRL	= BIT(8),
 	GMAC_ANEG_ADVERT_SYM_FLOW_CTRL	= BIT(9),
+	GMAC_ANEG_ADVERT_ASYM_FLOW_CTRL	= BIT(10),
 	GMAC_ANEG_AN_FLOW_CTRL_ENABLE	= BIT(11),
 	GMAC_ANEG_FULL_DUPLEX		= BIT(12),
 	GMAC_ANEG_AN_DUPLEX_ENABLE	= BIT(13),
+				/* pp22: bit 14 - phy mode */
+				/* pp22: bit 15 - choose sample tx config */
 
 	GMAC_STATUS_REG		= 0x10,
 	MVGMAC_LINK_UP			= BIT(0),
@@ -60,8 +74,21 @@ enum {
 	MVGMAC_AN_COMPLETE		= BIT(11),
 	MVGMAC_SYNC_OK			= BIT(14),
 
+	/* N:		21:6-13		22: */
+	GMAC_FIFO_CFG1_REG	= 0x1c,
+	GMAC_FIFO_CFG1_TX_MIN_TH_SHIFT	= 6,
+	GMAC_FIFO_CFG1_TX_MIN_TH_MASK	= 0x7f <<
+						GMAC_FIFO_CFG1_TX_MIN_TH_SHIFT,
+
+	/* N:1		21:		22:0,3-7 */
 	GMAC_CTRL4_REG		= 0x90,
+	GMAC_CTRL4_EXT_PIN_GMII_SEL	= BIT(0),
 	GMAC_CTRL4_SHORT_PREAMBLE_ENABLE = BIT(1),
+	GMAC_CTRL4_FC_RX_ENABLE		= BIT(3),
+	GMAC_CTRL4_FC_TX_ENABLE		= BIT(4),
+	GMAC_CTRL4_DP_CLK_SEL		= BIT(5),
+	GMAC_CTRL4_SYNC_BYPASS		= BIT(6),
+	GMAC_CTRL4_QSGMII_BYPASS	= BIT(7),
 
 	GMAC_LPI_CTRL0_REG	= 0xc0,
 	GMAC_LPI_CTRL0_TS		= 0xff << 8,
@@ -116,6 +143,49 @@ void mvgmac_port_disable(struct mvgmac *gmac)
 }
 EXPORT_SYMBOL_GPL(mvgmac_port_disable);
 
+int mvgmac_configure(struct mvgmac *gmac, phy_interface_t phy_mode)
+{
+	u32 ctrl4;
+
+	switch (phy_mode) {
+	case PHY_INTERFACE_MODE_QSGMII:
+		ctrl4 = 0;
+		break;
+
+	case PHY_INTERFACE_MODE_SGMII:
+		ctrl4 = GMAC_CTRL4_QSGMII_BYPASS;
+		break;
+
+	case PHY_INTERFACE_MODE_RGMII:
+	case PHY_INTERFACE_MODE_RGMII_ID:
+	case PHY_INTERFACE_MODE_RGMII_RXID:
+	case PHY_INTERFACE_MODE_RGMII_TXID:
+		ctrl4 = GMAC_CTRL4_QSGMII_BYPASS |
+			GMAC_CTRL4_EXT_PIN_GMII_SEL;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	if (gmac->version == MVGMAC_PP21) {
+		/* Min. TX threshold must be less than minimum packet length */
+		mvgmac_modify(gmac->base + GMAC_FIFO_CFG1_REG,
+			      GMAC_FIFO_CFG1_TX_MIN_TH_MASK,
+			      FIELD_PREP(GMAC_FIFO_CFG1_TX_MIN_TH_MASK,
+					 64 - 4 - 2));
+	} else if (gmac->version == MVGMAC_PP22) {
+		mvgmac_modify(gmac->base + GMAC_CTRL4_REG,
+			      GMAC_CTRL4_DP_CLK_SEL | GMAC_CTRL4_SYNC_BYPASS |
+			      GMAC_CTRL4_QSGMII_BYPASS |
+			      GMAC_CTRL4_EXT_PIN_GMII_SEL,
+			      GMAC_CTRL4_SYNC_BYPASS | ctrl4);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(mvgmac_configure);
+
 void mvgmac_link_unforce(struct mvgmac *gmac)
 {
 	mvgmac_clear(gmac->base + GMAC_ANEG_REG,
@@ -141,16 +211,21 @@ EXPORT_SYMBOL_GPL(mvgmac_link_down);
 void mvgmac_link_up(struct mvgmac *gmac, int mode, int speed, int duplex,
 		    bool tx_pause, bool rx_pause)
 {
-	u32 an_mask, an_val;
+	u32 an_mask, an_val, ctrl4;
 
-	an_mask = GMAC_ANEG_CONFIG_FLOW_CTRL;
-	an_val = FIELD_PREP(GMAC_ANEG_CONFIG_FLOW_CTRL, tx_pause || rx_pause);
+	if (gmac->version == MVGMAC_NETA) {
+		an_mask = GMAC_ANEG_CONFIG_FLOW_CTRL;
+		an_val = FIELD_PREP(GMAC_ANEG_CONFIG_FLOW_CTRL,
+				    tx_pause || rx_pause);
+	} else {
+		an_mask = 0;
+		an_val = 0;
+	}
 
 	if (!phylink_autoneg_inband(mode)) {
 		an_mask |= GMAC_ANEG_FORCE_LINK_DOWN |
 			   GMAC_ANEG_FORCE_LINK_PASS |
-			   GMAC_ANEG_MII_SPEED |
-			   GMAC_ANEG_GMII_SPEED |
+			   GMAC_ANEG_MII_SPEED | GMAC_ANEG_GMII_SPEED |
 			   GMAC_ANEG_FULL_DUPLEX;
 		an_val |= GMAC_ANEG_FORCE_LINK_PASS;
 
@@ -163,7 +238,15 @@ void mvgmac_link_up(struct mvgmac *gmac, int mode, int speed, int duplex,
 			an_val |= GMAC_ANEG_FULL_DUPLEX;
 	}
 
-	mvgmac_modify(gmac->base + GMAC_ANEG_REG, an_mask, an_val);
+	if (an_mask)
+		mvgmac_modify(gmac->base + GMAC_ANEG_REG, an_mask, an_val);
+
+	if (gmac->version == MVGMAC_PP22) {
+		ctrl4 = readl_relaxed(gmac->base + GMAC_CTRL4_REG);
+		ctrl4 = insert(ctrl4, GMAC_CTRL4_FC_TX_ENABLE, tx_pause);
+		ctrl4 = insert(ctrl4, GMAC_CTRL4_FC_RX_ENABLE, rx_pause);
+		writel_relaxed(ctrl4, gmac->base + GMAC_CTRL4_REG);
+	}
 }
 EXPORT_SYMBOL_GPL(mvgmac_link_up);
 
@@ -253,6 +336,11 @@ int mvgmac_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
 			mask |= GMAC_ANEG_ADVERT_SYM_FLOW_CTRL;
 			if (phylink_test(advertising, Pause))
 				val |= GMAC_ANEG_ADVERT_SYM_FLOW_CTRL;
+			if (gmac->version == MVGMAC_PP22) {
+				mask |= GMAC_ANEG_ADVERT_ASYM_FLOW_CTRL;
+				if (phylink_test(advertising, Asym_Pause))
+					val |= GMAC_ANEG_ADVERT_ASYM_FLOW_CTRL;
+			}
 		}
 	} else {
 		/* Phy or fixed speed - disable in-band AN modes */
@@ -266,7 +354,8 @@ int mvgmac_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
 		writel_relaxed(an, gmac->base + GMAC_ANEG_REG);
 
 	/* We are only interested in the advertisement bits changing */
-	return !!(changed & GMAC_ANEG_ADVERT_SYM_FLOW_CTRL);
+	return !!(changed & (GMAC_ANEG_ADVERT_SYM_FLOW_CTRL |
+			     GMAC_ANEG_ADVERT_ASYM_FLOW_CTRL));
 }
 EXPORT_SYMBOL_GPL(mvgmac_pcs_config);
 
@@ -280,7 +369,7 @@ void mvgmac_config_mac(struct mvgmac *gmac, unsigned int mode,
 	new_ctrl0 = gmac_ctrl0 & ~GMAC_CTRL0_PORT_1000BASE_X;
 	new_ctrl2 = gmac_ctrl2 & ~(GMAC_CTRL2_INBAND_AN_SGMII |
 				   GMAC_CTRL2_PORT_RESET);
-	new_ctrl4 = gmac_ctrl4 & ~GMAC_CTRL4_SHORT_PREAMBLE_ENABLE;
+	new_ctrl4 = gmac_ctrl4;
 
 	/* Even though it might look weird, when we're configured in
 	 * SGMII or QSGMII mode, the RGMII bit needs to be set.
@@ -304,11 +393,14 @@ void mvgmac_config_mac(struct mvgmac *gmac, unsigned int mode,
 		new_ctrl0 |= GMAC_CTRL0_PORT_1000BASE_X;
 	}
 
-	/* When at 2.5G, the link partner can send frames with shortened
-	 * preambles.
-	 */
-	if (state->interface == PHY_INTERFACE_MODE_2500BASEX)
-		new_ctrl4 |= GMAC_CTRL4_SHORT_PREAMBLE_ENABLE;
+	if (gmac->version == MVGMAC_NETA) {
+		/* When at 2.5G, the link partner can send frames with
+		 * shortened preambles.
+		 */
+		new_ctrl4 &= ~GMAC_CTRL4_SHORT_PREAMBLE_ENABLE;
+		if (state->interface == PHY_INTERFACE_MODE_2500BASEX)
+			new_ctrl4 |= GMAC_CTRL4_SHORT_PREAMBLE_ENABLE;
+	}
 
 	if (new_ctrl0 != gmac_ctrl0)
 		writel_relaxed(new_ctrl0, gmac->base + GMAC_CTRL0_REG);
diff --git a/drivers/net/ethernet/marvell/mvgmac.h b/drivers/net/ethernet/marvell/mvgmac.h
index 34081b4fe54b..e423b484f369 100644
--- a/drivers/net/ethernet/marvell/mvgmac.h
+++ b/drivers/net/ethernet/marvell/mvgmac.h
@@ -12,8 +12,16 @@
  */
 #define MARVELL_HEADER_SIZE 2
 
+enum {
+	/* GMAC version */
+	MVGMAC_NETA,
+	MVGMAC_PP21,
+	MVGMAC_PP22,
+};
+
 struct mvgmac {
 	void __iomem *base;
+	unsigned int version;
 	struct phylink_pcs pcs;
 };
 
@@ -25,6 +33,7 @@ static inline struct mvgmac *pcs_to_mvgmac(struct phylink_pcs *pcs)
 void mvgmac_set_max_rx_size(struct mvgmac *gmac, size_t max_rx_size);
 void mvgmac_port_enable(struct mvgmac *gmac);
 void mvgmac_port_disable(struct mvgmac *gmac);
+int mvgmac_configure(struct mvgmac *gmac, phy_interface_t phy_mode);
 void mvgmac_link_unforce(struct mvgmac *gmac);
 void mvgmac_link_force_down(struct mvgmac *gmac);
 void mvgmac_link_down(struct mvgmac *gmac, int mode);
diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
index f8fe8f3581bc..3dc65d2bfe11 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -5247,6 +5247,7 @@ static int mvneta_probe(struct platform_device *pdev)
 		clk_prepare_enable(pp->clk_bus);
 
 	pp->gmac.base = pp->base + MVNETA_GMAC_BASE;
+	pp->gmac.version = MVGMAC_NETA;
 	pp->gmac.pcs.ops = &mvneta_phylink_pcs_ops;
 	pp->gmac.pcs.neg_mode = true;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:44 +0100

From: Ofer Heifetz <[email-masked]>

The resume operation of mvebu xHCI host have some issues,
so The XHCI_RESET_ON_RESUME quirk is added for it.

Signed-off-by: Ofer Heifetz <[email-masked]>
Tested-by: Nadav Haklai <[email-masked]>
Signed-off-by: Grzegorz Jaszczyk <[email-masked]>
Reviewed-by: Lior Amsalem <[email-masked]>
Tested-by: Lior Amsalem <[email-masked]>
Signed-off-by: Russell King <[email-masked]>
---
 drivers/usb/host/xhci-mvebu.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/usb/host/xhci-mvebu.c b/drivers/usb/host/xhci-mvebu.c
index 87f1597a0e5a..e914170d131d 100644
--- a/drivers/usb/host/xhci-mvebu.c
+++ b/drivers/usb/host/xhci-mvebu.c
@@ -42,6 +42,14 @@ static void xhci_mvebu_mbus_config(void __iomem *base,
 	}
 }
 
+static void xhci_mvebu_quirks(struct platform_device *pdev)
+{
+	struct usb_hcd *hcd = platform_get_drvdata(pdev);
+	struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+	xhci->quirks |= XHCI_RESET_ON_RESUME;
+}
+
 int xhci_mvebu_mbus_init_quirk(struct usb_hcd *hcd)
 {
 	struct device *dev = hcd->self.controller;
@@ -71,6 +79,8 @@ int xhci_mvebu_mbus_init_quirk(struct usb_hcd *hcd)
 	 */
 	iounmap(base);
 
+	xhci_mvebu_quirks(pdev);
+
 	return 0;
 }

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:44 +0100

Honour deferred probing for devm_clk_get() so that we can get the clock
for PWM.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/gpio/gpio-mvebu.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/gpio/gpio-mvebu.c b/drivers/gpio/gpio-mvebu.c
index 8cfd3a89c018..febda47e5969 100644
--- a/drivers/gpio/gpio-mvebu.c
+++ b/drivers/gpio/gpio-mvebu.c
@@ -1159,6 +1159,9 @@ static int mvebu_gpio_probe(struct platform_device *pdev)
 	}
 
 	mvchip->clk = devm_clk_get(&pdev->dev, NULL);
+	if (mvchip->clk == ERR_PTR(-EPROBE_DEFER))
+		return -EPROBE_DEFER;
+
 	/* Not all SoCs require a clock.*/
 	if (!IS_ERR(mvchip->clk))
 		clk_prepare_enable(mvchip->clk);

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:45 +0100

Add pwm-fan support for controlling the fan speed, which allows the fan
speed to be controlled via sysfs. Alternatively, users can also add
their cooling maps to DT which will be specific to the environment that
they house the Macchiatobin, as well as their fan setup. For example,
one may connect PWM controlled case fans to this connector, and use
the PWM signal to control the case fans.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
index c864df9ec84d..48527be0d6d4 100644
--- a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
@@ -30,6 +30,13 @@ aliases {
 		ethernet3 = &cp1_eth2;
 	};
 
+	fan: pwm {
+		compatible = "pwm-fan";
+		pinctrl-names = "default";
+		pinctrl-0 = <&cp0_fan_pwm_pins>;
+		pwms = <&cp0_gpio2 16 40000>;
+	};
+
 	/* Regulator labels correspond with schematics */
 	v_3_3: regulator-3-3v {
 		compatible = "regulator-fixed";
@@ -211,6 +218,10 @@ cp0_xhci_vbus_pins: xhci0-vbus-pins {
 		marvell,pins = "mpp47";
 		marvell,function = "gpio";
 	};
+	cp0_fan_pwm_pins: fan-pwm-pins {
+		marvell,pins = "mpp48";
+		marvell,function = "gpio";
+	};
 	cp0_sfp_1g_pins: sfp-1g-pins {
 		marvell,pins = "mpp51", "mpp53", "mpp54";
 		marvell,function = "gpio";

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:45 +0100

Comment about the use of currently unconfigured MPP pins.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
index 48527be0d6d4..b9ae0c65dc74 100644
--- a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
@@ -202,6 +202,7 @@ cp0_ge_mdio_pins: ge-mdio-pins {
 		marvell,pins = "mpp32", "mpp34";
 		marvell,function = "ge";
 	};
+	/* mpp33 - mss? */
 	cp0_i2c1_pins: i2c1-pins {
 		marvell,pins = "mpp35", "mpp36";
 		marvell,function = "i2c1";

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:45 +0100

Add several pinctrls for functions brought out to connectors but not yet
usable with the core DT description.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
index b9ae0c65dc74..c1c2eeb656c9 100644
--- a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
@@ -61,7 +61,7 @@ v_5v0_usb3_hst_vbus: regulator-usb3-vbus0 {
 		enable-active-high;
 		gpio = <&cp0_gpio2 15 GPIO_ACTIVE_HIGH>;
 		pinctrl-names = "default";
-		pinctrl-0 = <&cp0_xhci_vbus_pins>;
+		pinctrl-0 = <&cp0_xhci_vbus_pins &cp1_xhci_vbus_pins>;
 		regulator-name = "v_5v0_usb3_hst_vbus";
 		regulator-min-microvolt = <5000000>;
 		regulator-max-microvolt = <5000000>;
@@ -211,6 +211,7 @@ cp0_i2c0_pins: i2c0-pins {
 		marvell,pins = "mpp37", "mpp38";
 		marvell,function = "i2c0";
 	};
+	/* mpp40 - mss? */
 	cp0_uart1_pins: uart1-pins {
 		marvell,pins = "mpp40", "mpp41";
 		marvell,function = "uart1";
@@ -322,6 +323,14 @@ &cp1_eth2 {
 };
 
 &cp1_pinctrl {
+	cp1_tdm_pins: tdm-pins { /* not currently implemented */
+		marvell,pins = "mpp0", "mpp1", "mpp2", "mpp3", "mpp4", "mpp5";
+		marvell,function = "tdm";
+	};
+	cp1_uart0_pins: uart0-pins { /* J27 - not currently implemented */
+		marvell,pins = "mpp6", "mpp7";
+		marvell,function = "uart0";
+	};
 	cp1_sfpp1_pins: sfpp1-pins {
 		marvell,pins = "mpp8", "mpp10", "mpp11";
 		marvell,function = "gpio";
@@ -342,6 +351,10 @@ cp1_sfpp0_pins: sfpp0-pins {
 		marvell,pins = "mpp26", "mpp27", "mpp28", "mpp29";
 		marvell,function = "gpio";
 	};
+	cp1_xhci_vbus_pins: xhci0-vbus-pins {
+		marvell,pins = "mpp30";
+		marvell,function = "gpio";
+	};
 };
 
 /* J27 UART header */

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:45 +0100

Add the pinctrl settings and interrupts for the 10G PHYs.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm64/boot/dts/marvell/armada-8040-mcbin.dts | 38 +++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dts b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dts
index 68c27f22ff57..2bf538d7aec6 100644
--- a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dts
+++ b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dts
@@ -14,20 +14,47 @@ / {
 			"marvell,armada-ap806-quad", "marvell,armada-ap806";
 };
 
+&cp0_pinctrl {
+	cp0_xg_mdio_pins: xg-mdio-pins {
+		marvell,pins = "mpp42", "mpp43";
+		marvell,function = "xg";
+	};
+	cp0_phy1_pins: phy1-pins { /* phy interrupt */
+		marvell,pins = "mpp49";
+		marvell,function = "gpio";
+	};
+	cp0_phy0_pins: phy0-pins { /* phy interrupt */
+		marvell,pins = "mpp50";
+		marvell,function = "gpio";
+	};
+};
+
 &cp0_xmdio {
+	pinctrl-names = "default";
+	pinctrl-0 = <&cp0_xg_mdio_pins>;
 	status = "okay";
 
 	phy0: ethernet-phy@0 {
 		compatible = "ethernet-phy-ieee802.3-c45";
+		pinctrl-names = "default";
+		pinctrl-0 = <&cp0_phy0_pins &cp1_phy0_pins>;
+		/* should use in-band negotiation */
 		reg = <0>;
 		sfp = <&sfp_eth0>;
+		interrupt-parent = <&cp0_gpio2>;
+		interrupts = <18 IRQ_TYPE_LEVEL_LOW>;
 		marvell,led-mode = /bits/ 16 <0x0129 0x095d 0x0855>;
 	};
 
 	phy8: ethernet-phy@8 {
 		compatible = "ethernet-phy-ieee802.3-c45";
+		pinctrl-names = "default";
+		pinctrl-0 = <&cp0_phy1_pins &cp1_phy1_pins>;
+		/* should use in-band negotiation */
 		reg = <8>;
 		sfp = <&sfp_eth1>;
+		interrupt-parent = <&cp0_gpio2>;
+		interrupts = <17 IRQ_TYPE_LEVEL_LOW>;
 		marvell,led-mode = /bits/ 16 <0x0129 0x095d 0x0855>;
 	};
 };
@@ -45,3 +72,14 @@ &cp1_eth0 {
 	phy = <&phy8>;
 	phy-mode = "10gbase-r";
 };
+
+&cp1_pinctrl {
+	cp1_phy1_pins: phy1-pins { /* phy reset */
+		marvell,pins = "mpp9";
+		marvell,function = "gpio";
+	};
+	cp1_phy0_pins: phy0-pins { /* phy reset */
+		marvell,pins = "mpp31";
+		marvell,function = "gpio";
+	};
+};

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:45 +0100

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
index c1c2eeb656c9..077f4a41063b 100644
--- a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
@@ -70,7 +70,7 @@ v_5v0_usb3_hst_vbus: regulator-usb3-vbus0 {
 
 	sfp_eth0: sfp-eth0 {
 		/* CON15,16 - CPM lane 4 */
-		compatible = "sff,sfp";
+		compatible = "sff,sfp+";
 		i2c-bus = <&sfpp0_i2c>;
 		los-gpios = <&cp1_gpio1 28 GPIO_ACTIVE_HIGH>;
 		mod-def0-gpios = <&cp1_gpio1 27 GPIO_ACTIVE_LOW>;
@@ -83,7 +83,7 @@ sfp_eth0: sfp-eth0 {
 
 	sfp_eth1: sfp-eth1 {
 		/* CON17,18 - CPS lane 4 */
-		compatible = "sff,sfp";
+		compatible = "sff,sfp+";
 		i2c-bus = <&sfpp1_i2c>;
 		los-gpios = <&cp1_gpio1 8 GPIO_ACTIVE_HIGH>;
 		mod-def0-gpios = <&cp1_gpio1 11 GPIO_ACTIVE_LOW>;

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:45 +0100

The I2C1 bus on early mcbin hardware is mis-wired, swapping SCL and SDA.
Work around this by using the i2c-gpio driver instead.  XXX Caught early
and this commit should be removed for mainline. XXX

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm64/boot/dts/marvell/Makefile                |  1 +
 .../arm64/boot/dts/marvell/armada-8040-mcbin-es.dts |  9 +++++++++
 arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi  | 21 +++++++++++++++++++--
 3 files changed, 29 insertions(+), 2 deletions(-)
 create mode 100644 arch/arm64/boot/dts/marvell/armada-8040-mcbin-es.dts

diff --git a/arch/arm64/boot/dts/marvell/Makefile b/arch/arm64/boot/dts/marvell/Makefile
index ce751b5028e2..52efab7cd27c 100644
--- a/arch/arm64/boot/dts/marvell/Makefile
+++ b/arch/arm64/boot/dts/marvell/Makefile
@@ -15,6 +15,7 @@ dtb-$(CONFIG_ARCH_MVEBU) += armada-7040-mochabin.dtb
 dtb-$(CONFIG_ARCH_MVEBU) += armada-8040-clearfog-gt-8k.dtb
 dtb-$(CONFIG_ARCH_MVEBU) += armada-8040-db.dtb
 dtb-$(CONFIG_ARCH_MVEBU) += armada-8040-mcbin.dtb
+dtb-$(CONFIG_ARCH_MVEBU) += armada-8040-mcbin-es.dtb
 dtb-$(CONFIG_ARCH_MVEBU) += armada-8040-mcbin-singleshot.dtb
 dtb-$(CONFIG_ARCH_MVEBU) += armada-8040-puzzle-m801.dtb
 dtb-$(CONFIG_ARCH_MVEBU) += armada-8080-db.dtb
diff --git a/arch/arm64/boot/dts/marvell/armada-8040-mcbin-es.dts b/arch/arm64/boot/dts/marvell/armada-8040-mcbin-es.dts
new file mode 100644
index 000000000000..2bc49b60cd06
--- /dev/null
+++ b/arch/arm64/boot/dts/marvell/armada-8040-mcbin-es.dts
@@ -0,0 +1,9 @@
+#define MCBIN_BROKEN_I2C1
+#include "armada-8040-mcbin.dts"
+
+/ {
+	model = "Marvell 8040 MACCHIATOBin Double shot (engineering sample)";
+	compatible = "marvell,armada8040-mcbin-doubleshot",
+			"marvell,armada8040-mcbin", "marvell,armada8040",
+			"marvell,armada-ap806-quad", "marvell,armada-ap806";
+};
diff --git a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
index 077f4a41063b..446e3f15b2a0 100644
--- a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
@@ -106,6 +106,23 @@ sfp_eth3: sfp-eth3 {
 		pinctrl-0 = <&cp0_sfp_1g_pins &cp1_sfp_1g_pins>;
 		maximum-power-milliwatt = <2000>;
 	};
+
+#ifdef MCBIN_BROKEN_I2C1
+	/* work-around the wrongly wired I2C bus on Rev 1.1 boards */
+	broken_i2c1: i2c-controller@0 {
+		compatible = "i2c-gpio";
+		gpios = <&cp0_gpio2 4 GPIO_ACTIVE_HIGH>,
+			<&cp0_gpio2 3 GPIO_ACTIVE_HIGH>;
+		i2c-gpio,delay-us = <2>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+	};
+#define I2C1 broken_i2c1
+#define I2C1_PINMUX "gpio"
+#else
+#define I2C1 cp0_i2c1
+#define I2C1_PINMUX "i2c1"
+#endif
 };
 
 &uart0 {
@@ -136,7 +153,7 @@ &cp0_i2c0 {
 	status = "okay";
 };
 
-&cp0_i2c1 {
+&I2C1 {
 	clock-frequency = <100000>;
 	pinctrl-names = "default";
 	pinctrl-0 = <&cp0_i2c1_pins>;
@@ -205,7 +222,7 @@ cp0_ge_mdio_pins: ge-mdio-pins {
 	/* mpp33 - mss? */
 	cp0_i2c1_pins: i2c1-pins {
 		marvell,pins = "mpp35", "mpp36";
-		marvell,function = "i2c1";
+		marvell,function = I2C1_PINMUX;
 	};
 	cp0_i2c0_pins: i2c0-pins {
 		marvell,pins = "mpp37", "mpp38";

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:45 +0100

Use in-band-status for the SGMII PHY on Macchiatobin platforms.

diff --git a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
index 446e3f15b2a0..162c03d9aa61 100644
--- a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
@@ -324,6 +324,7 @@ &cp1_eth1 {
 	/* Network PHY */
 	phy = <&ge_phy>;
 	phy-mode = "sgmii";
+	managed = "in-band-status";
 	/* Generic PHY, providing serdes lanes */
 	phys = <&cp1_comphy0 1>;
 };

---

From: Russell King <[email-masked]>
Date: Fri, 18 Oct 2024 11:08:45 +0100

This commit is to tell the 0-day builder to avoid building this branch.
---
 notes.mcbin | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 notes.mcbin

diff --git a/notes.mcbin b/notes.mcbin
new file mode 100644
index 000000000000..6abeba4d0777
--- /dev/null
+++ b/notes.mcbin
@@ -0,0 +1 @@
+Empty file for top commit