--- /dev/null
+From 8cc5f4cb94c0b1c7c1ba8013c14fd02ffb1a25f3 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 8 Nov 2024 16:01:44 +0000
+Subject: [PATCH 1/5] net: phylink: move manual flow control setting
+
+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) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1t9RQe-002Feh-T1@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1484,7 +1484,6 @@ static void phylink_resolve(struct work_
+ switch (pl->cur_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;
+
+@@ -1545,11 +1544,13 @@ static void phylink_resolve(struct work_
+ link_state.pause = pl->phy_state.pause;
+ mac_config = true;
+ }
+- phylink_apply_manual_flow(pl, &link_state);
+ break;
+ }
+ }
+
++ if (pl->cur_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
--- /dev/null
+From 92abfcb4ced482afbe65d18980e6734fe1e62a34 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 8 Nov 2024 16:01:50 +0000
+Subject: [PATCH 2/5] net: phylink: move MLO_AN_FIXED resolve handling to if()
+ statement
+
+The switch() statement doesn't sit very well with the preceeding if()
+statements, and results in excessive indentation that spoils code
+readability. Begin cleaning this up by converting the MLO_AN_FIXED case
+to an if() statement.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1t9RQk-002Fen-1A@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1480,6 +1480,9 @@ static void phylink_resolve(struct work_
+ } else if (pl->link_failed) {
+ link_state.link = false;
+ retrigger = true;
++ } else if (pl->cur_link_an_mode == MLO_AN_FIXED) {
++ phylink_get_fixed_state(pl, &link_state);
++ mac_config = link_state.link;
+ } else {
+ switch (pl->cur_link_an_mode) {
+ case MLO_AN_PHY:
+@@ -1487,11 +1490,6 @@ static void phylink_resolve(struct work_
+ mac_config = link_state.link;
+ break;
+
+- case MLO_AN_FIXED:
+- phylink_get_fixed_state(pl, &link_state);
+- mac_config = link_state.link;
+- break;
+-
+ case MLO_AN_INBAND:
+ phylink_mac_pcs_get_state(pl, &link_state);
+
--- /dev/null
+From f0f46c2a3d8ea9d1427298c8103a777d9e616c29 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 8 Nov 2024 16:01:55 +0000
+Subject: [PATCH 3/5] net: phylink: move MLO_AN_PHY resolve handling to if()
+ statement
+
+The switch() statement doesn't sit very well with the preceeding if()
+statements, and results in excessive indentation that spoils code
+readability. Continue cleaning this up by converting the MLO_AN_PHY
+case to use an if() statmeent.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1t9RQp-002Fet-5W@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1483,13 +1483,11 @@ static void phylink_resolve(struct work_
+ } else if (pl->cur_link_an_mode == MLO_AN_FIXED) {
+ phylink_get_fixed_state(pl, &link_state);
+ mac_config = link_state.link;
++ } else if (pl->cur_link_an_mode == MLO_AN_PHY) {
++ link_state = pl->phy_state;
++ mac_config = link_state.link;
+ } else {
+ switch (pl->cur_link_an_mode) {
+- case MLO_AN_PHY:
+- link_state = pl->phy_state;
+- mac_config = link_state.link;
+- break;
+-
+ case MLO_AN_INBAND:
+ phylink_mac_pcs_get_state(pl, &link_state);
+
--- /dev/null
+From d1a16dbbd84e02d2a6dcfcb8d5c4b8b2c0289f00 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 8 Nov 2024 16:02:00 +0000
+Subject: [PATCH 4/5] net: phylink: remove switch() statement in resolve
+ handling
+
+The switch() statement doesn't sit very well with the preceeding if()
+statements, so let's just convert everything to if()s. As a result of
+the two preceding commits, there is now only one case in the switch()
+statement. Remove the switch statement and reduce the code indentation.
+Code reformatting will be in the following commit.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1t9RQu-002Fez-AA@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 94 +++++++++++++++++++--------------------
+ 1 file changed, 45 insertions(+), 49 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1487,60 +1487,56 @@ static void phylink_resolve(struct work_
+ link_state = pl->phy_state;
+ mac_config = link_state.link;
+ } else {
+- switch (pl->cur_link_an_mode) {
+- case MLO_AN_INBAND:
+- phylink_mac_pcs_get_state(pl, &link_state);
+-
+- /* 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.
++ phylink_mac_pcs_get_state(pl, &link_state);
++
++ /* 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);
++ }
++
++ /* 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;
++
++ /* 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;
+ }
+ }
+
--- /dev/null
+From bc08ce37d99a3992e975a0f397503cb23404f25a Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 8 Nov 2024 16:02:05 +0000
+Subject: [PATCH 5/5] net: phylink: clean up phylink_resolve()
+
+Now that we have reduced the indentation level, clean up the code
+formatting.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1t9RQz-002Ff5-EA@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 35 ++++++++++++++++-------------------
+ 1 file changed, 16 insertions(+), 19 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1489,51 +1489,48 @@ static void phylink_resolve(struct work_
+ } else {
+ phylink_mac_pcs_get_state(pl, &link_state);
+
+- /* 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.
++ /* 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);
++ 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 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;
+
+ /* 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 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) {
++ 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 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;
++ link_state.duplex = pl->phy_state.duplex;
+ }
+
+- /* If we have a PHY, we need to update with
+- * the PHY flow control bits.
++ /* 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;
--- /dev/null
+From 17ed1911f9c8d4f9af8e13b2c95103ee06dadc0f Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:30:47 +0000
+Subject: [PATCH 01/13] net: phylink: pass phylink and pcs into
+ phylink_pcs_neg_mode()
+
+Move the call to phylink_pcs_neg_mode() in phylink_major_config() after
+we have selected the appropriate PCS to allow the PCS to be passed in.
+
+Add struct phylink and struct phylink_pcs pointers to
+phylink_pcs_neg_mode() and pass in the appropriate structures. Set
+pl->pcs_neg_mode before returning, and remove the return value.
+
+This will allow the capabilities of the PCS and any PHY to be used when
+deciding which pcs_neg_mode should be used.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUrP-006ITh-6u@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 26 +++++++++++++-------------
+ 1 file changed, 13 insertions(+), 13 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1102,7 +1102,8 @@ static void phylink_pcs_an_restart(struc
+
+ /**
+ * phylink_pcs_neg_mode() - helper to determine PCS inband mode
+- * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
++ * @pl: a pointer to a &struct phylink returned from phylink_create()
++ * @pcs: a pointer to &struct phylink_pcs
+ * @interface: interface mode to be used
+ * @advertising: adertisement ethtool link mode mask
+ *
+@@ -1119,11 +1120,13 @@ static void phylink_pcs_an_restart(struc
+ * 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,
+- phy_interface_t interface,
+- const unsigned long *advertising)
++static void phylink_pcs_neg_mode(struct phylink *pl, struct phylink_pcs *pcs,
++ phy_interface_t interface,
++ const unsigned long *advertising)
+ {
+- unsigned int neg_mode;
++ unsigned int neg_mode, mode;
++
++ mode = pl->cur_link_an_mode;
+
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+@@ -1164,7 +1167,7 @@ static unsigned int phylink_pcs_neg_mode
+ break;
+ }
+
+- return neg_mode;
++ pl->pcs_neg_mode = neg_mode;
+ }
+
+ static void phylink_major_config(struct phylink *pl, bool restart,
+@@ -1178,10 +1181,6 @@ static void phylink_major_config(struct
+
+ phylink_dbg(pl, "major config %s\n", phy_modes(state->interface));
+
+- pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
+- state->interface,
+- state->advertising);
+-
+ if (pl->using_mac_select_pcs) {
+ pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+ if (IS_ERR(pcs)) {
+@@ -1194,6 +1193,8 @@ static void phylink_major_config(struct
+ pcs_changed = pcs && pl->pcs != pcs;
+ }
+
++ phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
++
+ phylink_pcs_poll_stop(pl);
+
+ if (pl->mac_ops->mac_prepare) {
+@@ -1284,9 +1285,8 @@ static int phylink_change_inband_advert(
+ pl->link_config.pause);
+
+ /* Recompute the PCS neg mode */
+- pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
+- pl->link_config.interface,
+- pl->link_config.advertising);
++ phylink_pcs_neg_mode(pl, pl->pcs, pl->link_config.interface,
++ pl->link_config.advertising);
+
+ neg_mode = pl->cur_link_an_mode;
+ if (pl->pcs->neg_mode)
--- /dev/null
+From 1f92ead7e15003f632b5f138e8138095e0997d3d Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:30:52 +0000
+Subject: [PATCH 02/13] net: phylink: split cur_link_an_mode into requested and
+ active
+
+There is an interdependence between the current link_an_mode and
+pcs_neg_mode that some drivers rely upon to know whether inband or PHY
+mode will be used.
+
+In order to support detection of PCS and PHY inband capabilities
+resulting in automatic selection of inband or PHY mode, we need to
+cater for this, and support changing the MAC link_an_mode. However, we
+end up with an inter-dependency between the current link_an_mode and
+pcs_neg_mode.
+
+To solve this, split the current link_an_mode into the requested
+link_an_mode and active link_an_mode. The requested link_an_mode will
+always be passed to phylink_pcs_neg_mode(), and the active link_an_mode
+will be used for everything else, and only updated during
+phylink_major_config(). This will ensure that phylink_pcs_neg_mode()'s
+link_an_mode will not depend on the active link_an_mode that will,
+in a future patch, depend on pcs_neg_mode.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUrU-006ITn-Ai@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 60 ++++++++++++++++++++-------------------
+ 1 file changed, 31 insertions(+), 29 deletions(-)
+
+--- 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);
+
+@@ -1082,13 +1083,13 @@ static void phylink_mac_config(struct ph
+
+ 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)
+@@ -1096,7 +1097,7 @@ static void phylink_pcs_an_restart(struc
+ 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);
+ }
+
+@@ -1126,7 +1127,7 @@ static void phylink_pcs_neg_mode(struct
+ {
+ unsigned int neg_mode, mode;
+
+- mode = pl->cur_link_an_mode;
++ mode = pl->req_link_an_mode;
+
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+@@ -1168,6 +1169,7 @@ static void phylink_pcs_neg_mode(struct
+ }
+
+ pl->pcs_neg_mode = neg_mode;
++ pl->act_link_an_mode = mode;
+ }
+
+ static void phylink_major_config(struct phylink *pl, bool restart,
+@@ -1198,7 +1200,7 @@ static void phylink_major_config(struct
+ 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",
+@@ -1232,7 +1234,7 @@ static void phylink_major_config(struct
+ 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;
+
+@@ -1248,7 +1250,7 @@ static void phylink_major_config(struct
+ 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",
+@@ -1279,7 +1281,7 @@ static int phylink_change_inband_advert(
+ 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);
+@@ -1288,7 +1290,7 @@ static int phylink_change_inband_advert(
+ phylink_pcs_neg_mode(pl, pl->pcs, pl->link_config.interface,
+ pl->link_config.advertising);
+
+- neg_mode = pl->cur_link_an_mode;
++ neg_mode = pl->act_link_an_mode;
+ if (pl->pcs->neg_mode)
+ neg_mode = pl->pcs_neg_mode;
+
+@@ -1353,7 +1355,7 @@ static void phylink_mac_initial_config(s
+ {
+ 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;
+@@ -1427,14 +1429,14 @@ static void phylink_link_up(struct phyli
+
+ 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);
+
+@@ -1454,7 +1456,7 @@ static void phylink_link_down(struct phy
+
+ if (ndev)
+ netif_carrier_off(ndev);
+- 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");
+ }
+@@ -1480,10 +1482,10 @@ static void phylink_resolve(struct work_
+ } else if (pl->link_failed) {
+ link_state.link = false;
+ retrigger = true;
+- } else if (pl->cur_link_an_mode == MLO_AN_FIXED) {
++ } 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->cur_link_an_mode == MLO_AN_PHY) {
++ } else if (pl->act_link_an_mode == MLO_AN_PHY) {
+ link_state = pl->phy_state;
+ mac_config = link_state.link;
+ } else {
+@@ -1537,7 +1539,7 @@ static void phylink_resolve(struct work_
+ }
+ }
+
+- if (pl->cur_link_an_mode != MLO_AN_FIXED)
++ if (pl->act_link_an_mode != MLO_AN_FIXED)
+ phylink_apply_manual_flow(pl, &link_state);
+
+ if (mac_config) {
+@@ -1661,7 +1663,7 @@ int phylink_set_fixed_link(struct phylin
+ pl->link_config.an_complete = 1;
+
+ pl->cfg_link_an_mode = MLO_AN_FIXED;
+- pl->cur_link_an_mode = pl->cfg_link_an_mode;
++ pl->req_link_an_mode = pl->cfg_link_an_mode;
+
+ return 0;
+ }
+@@ -1756,7 +1758,7 @@ struct phylink *phylink_create(struct ph
+ }
+ }
+
+- pl->cur_link_an_mode = pl->cfg_link_an_mode;
++ pl->req_link_an_mode = pl->cfg_link_an_mode;
+
+ ret = phylink_register_sfp(pl, fwnode);
+ if (ret < 0) {
+@@ -2213,7 +2215,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 */
+@@ -2472,7 +2474,7 @@ int phylink_ethtool_ksettings_get(struct
+
+ 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
+@@ -2564,7 +2566,7 @@ int phylink_ethtool_ksettings_set(struct
+ /* 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;
+@@ -2580,7 +2582,7 @@ int phylink_ethtool_ksettings_set(struct
+ * 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;
+@@ -2620,7 +2622,7 @@ int phylink_ethtool_ksettings_set(struct
+ 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;
+@@ -2720,7 +2722,7 @@ int phylink_ethtool_set_pauseparam(struc
+
+ 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) &&
+@@ -2984,7 +2986,7 @@ static int phylink_mii_read(struct phyli
+ 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);
+@@ -3009,7 +3011,7 @@ static int phylink_mii_read(struct phyli
+ 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;
+
+@@ -3199,9 +3201,9 @@ static void phylink_sfp_set_config(struc
+ 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;
--- /dev/null
+From 4e7d000286fe8e12f2d88032711ffab3ab658b12 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:30:57 +0000
+Subject: [PATCH 03/13] net: phylink: add debug for phylink_major_config()
+
+Now that we have a more complexity in phylink_major_config(), augment
+the debugging so we can see what's going on there.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUrZ-006ITt-Fa@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 27 ++++++++++++++++++++++++++-
+ 1 file changed, 26 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -176,6 +176,24 @@ static const char *phylink_an_mode_str(u
+ 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) {
+@@ -1181,7 +1199,9 @@ static void phylink_major_config(struct
+ 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));
+
+ if (pl->using_mac_select_pcs) {
+ pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+@@ -1197,6 +1217,11 @@ static void phylink_major_config(struct
+
+ phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
+
++ 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);
+
+ if (pl->mac_ops->mac_prepare) {
--- /dev/null
+From b4c7698dd95f253c6958d8c6ac219098009bf28a Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:02 +0000
+Subject: [PATCH 04/13] net: phy: add phy_inband_caps()
+
+Add a method to query the PHY's in-band capabilities for a PHY
+interface mode.
+
+Where the interface mode does not have in-band capability, or the PHY
+driver has not been updated to return this information, then
+phy_inband_caps() should return zero. Otherwise, PHY drivers will
+return a value consisting of the following flags:
+
+LINK_INBAND_DISABLE indicates that the hardware does not support
+in-band signalling, or can have in-band signalling configured via
+software to be disabled.
+
+LINK_INBAND_ENABLE indicates that the hardware will use in-band
+signalling, or can have in-band signalling configured via software
+to be enabled.
+
+LINK_INBAND_BYPASS indicates that the hardware has the ability to
+bypass in-band signalling when enabled after a timeout if the link
+partner does not respond to its in-band signalling.
+
+This reports the PHY capabilities for the particular interface mode,
+not the current configuration.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUre-006ITz-KF@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phy.c | 21 +++++++++++++++++++++
+ include/linux/phy.h | 28 ++++++++++++++++++++++++++++
+ 2 files changed, 49 insertions(+)
+
+--- a/drivers/net/phy/phy.c
++++ b/drivers/net/phy/phy.c
+@@ -1049,6 +1049,27 @@ static int phy_check_link_status(struct
+ }
+
+ /**
++ * phy_inband_caps - 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
++ * by the PHY for this interface mode.
++ */
++unsigned int phy_inband_caps(struct phy_device *phydev,
++ phy_interface_t interface)
++{
++ if (phydev->drv && phydev->drv->inband_caps)
++ return phydev->drv->inband_caps(phydev, interface);
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(phy_inband_caps);
++
++/**
+ * _phy_start_aneg - start auto-negotiation for this PHY device
+ * @phydev: the phy_device struct
+ *
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -816,6 +816,24 @@ struct phy_tdr_config {
+ #define PHY_PAIR_ALL -1
+
+ /**
++ * enum link_inband_signalling - in-band signalling modes that are supported
++ *
++ * @LINK_INBAND_DISABLE: in-band signalling can be disabled
++ * @LINK_INBAND_ENABLE: in-band signalling can be enabled without bypass
++ * @LINK_INBAND_BYPASS: in-band signalling can be enabled with 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.
+ *
+@@ -954,6 +972,14 @@ struct phy_driver {
+ int (*get_features)(struct phy_device *phydev);
+
+ /**
++ * @inband_caps: query whether in-band is supported for the given PHY
++ * interface mode. Returns a bitmask of bits defined by enum
++ * link_inband_signalling.
++ */
++ unsigned int (*inband_caps)(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
+ * whether to advertise lower-speed modes for that interface. It is
+@@ -1832,6 +1858,8 @@ int phy_config_aneg(struct phy_device *p
+ 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_inband_caps(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);
--- /dev/null
+From c64c7fa0a774d9da72071a8517e359992baac982 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:07 +0000
+Subject: [PATCH 05/13] net: phy: bcm84881: implement phy_inband_caps() method
+
+BCM84881 has no support for inband signalling, so this is a trivial
+implementation that returns no support for inband.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Acked-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/E1tIUrj-006IU6-ON@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/bcm84881.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+--- a/drivers/net/phy/bcm84881.c
++++ b/drivers/net/phy/bcm84881.c
+@@ -235,11 +235,21 @@ static int bcm84881_read_status(struct p
+ 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_inband_caps(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",
++ .inband_caps = bcm84881_inband_caps,
+ .config_init = bcm84881_config_init,
+ .probe = bcm84881_probe,
+ .get_features = bcm84881_get_features,
--- /dev/null
+From 1c86828dff88e28b8ade6bddeee0163a023faf91 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:12 +0000
+Subject: [PATCH 06/13] net: phy: marvell: implement phy_inband_caps() method
+
+Provide an implementation for phy_inband_caps() for Marvell PHYs used
+on SFP modules, so that phylink knows the PHYs capabilities.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUro-006IUC-Rq@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/marvell.c | 17 +++++++++++++++++
+ 1 file changed, 17 insertions(+)
+
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -716,6 +716,20 @@ static int marvell_config_aneg_fiber(str
+ return genphy_check_and_restart_aneg(phydev, changed);
+ }
+
++static unsigned int m88e1111_inband_caps(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 |
++ LINK_INBAND_BYPASS;
++
++ return 0;
++}
++
+ static int m88e1111_config_aneg(struct phy_device *phydev)
+ {
+ int extsr = phy_read(phydev, MII_M1111_PHY_EXT_SR);
+@@ -3667,6 +3681,7 @@ static struct phy_driver marvell_drivers
+ .name = "Marvell 88E1112",
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
++ .inband_caps = m88e1111_inband_caps,
+ .config_init = m88e1112_config_init,
+ .config_aneg = marvell_config_aneg,
+ .config_intr = marvell_config_intr,
+@@ -3688,6 +3703,7 @@ static struct phy_driver marvell_drivers
+ /* PHY_GBIT_FEATURES */
+ .flags = PHY_POLL_CABLE_TEST,
+ .probe = marvell_probe,
++ .inband_caps = m88e1111_inband_caps,
+ .config_init = m88e1111gbe_config_init,
+ .config_aneg = m88e1111_config_aneg,
+ .read_status = marvell_read_status,
+@@ -3711,6 +3727,7 @@ static struct phy_driver marvell_drivers
+ .name = "Marvell 88E1111 (Finisar)",
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
++ .inband_caps = m88e1111_inband_caps,
+ .config_init = m88e1111gbe_config_init,
+ .config_aneg = m88e1111_config_aneg,
+ .read_status = marvell_read_status,
--- /dev/null
+From 5d58a890c02770ba8d790b1f3c6e8c0e20514dc2 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:18 +0000
+Subject: [PATCH 07/13] net: phy: add phy_config_inband()
+
+Add a method to configure the PHY's in-band mode.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUru-006IUI-08@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phy.c | 32 ++++++++++++++++++++++++++++++++
+ include/linux/phy.h | 6 ++++++
+ 2 files changed, 38 insertions(+)
+
+--- a/drivers/net/phy/phy.c
++++ b/drivers/net/phy/phy.c
+@@ -1070,6 +1070,38 @@ unsigned int phy_inband_caps(struct phy_
+ EXPORT_SYMBOL_GPL(phy_inband_caps);
+
+ /**
++ * phy_config_inband - configure the desired PHY in-band mode
++ * @phydev: the phy_device struct
++ * @modes: in-band modes to configure
++ *
++ * Description: disables, enables or enables-with-bypass in-band signalling
++ * between the PHY and host system.
++ *
++ * Returns: zero on success, or negative errno value.
++ */
++int phy_config_inband(struct phy_device *phydev, unsigned int modes)
++{
++ int err;
++
++ if (!!(modes & LINK_INBAND_DISABLE) +
++ !!(modes & LINK_INBAND_ENABLE) +
++ !!(modes & LINK_INBAND_BYPASS) != 1)
++ return -EINVAL;
++
++ mutex_lock(&phydev->lock);
++ if (!phydev->drv)
++ err = -EIO;
++ else if (!phydev->drv->config_inband)
++ err = -EOPNOTSUPP;
++ else
++ err = phydev->drv->config_inband(phydev, modes);
++ mutex_unlock(&phydev->lock);
++
++ return err;
++}
++EXPORT_SYMBOL(phy_config_inband);
++
++/**
+ * _phy_start_aneg - start auto-negotiation for this PHY device
+ * @phydev: the phy_device struct
+ *
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -980,6 +980,11 @@ struct phy_driver {
+ phy_interface_t interface);
+
+ /**
++ * @config_inband: configure in-band mode for the PHY
++ */
++ int (*config_inband)(struct phy_device *phydev, unsigned int modes);
++
++ /**
+ * @get_rate_matching: Get the supported type of rate matching for a
+ * particular phy interface. This is used by phy consumers to determine
+ * whether to advertise lower-speed modes for that interface. It is
+@@ -1860,6 +1865,7 @@ int phy_start_aneg(struct phy_device *ph
+ int phy_aneg_done(struct phy_device *phydev);
+ unsigned int phy_inband_caps(struct phy_device *phydev,
+ phy_interface_t interface);
++int phy_config_inband(struct phy_device *phydev, unsigned int modes);
+ 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);
--- /dev/null
+From a219912e0fec73c346e64ef47013cb2e152f88fc Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:23 +0000
+Subject: [PATCH 08/13] net: phy: marvell: implement config_inband() method
+
+Implement the config_inband() method for Marvell 88E1112, 88E1111,
+and Finisar's 88E1111 variant.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUrz-006IUO-3r@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/marvell.c | 31 +++++++++++++++++++++++++++++++
+ 1 file changed, 31 insertions(+)
+
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -730,6 +730,34 @@ static unsigned int m88e1111_inband_caps
+ return 0;
+ }
+
++static int m88e1111_config_inband(struct phy_device *phydev, unsigned int modes)
++{
++ u16 extsr, bmcr;
++ int err;
++
++ if (phydev->interface != PHY_INTERFACE_MODE_1000BASEX &&
++ phydev->interface != PHY_INTERFACE_MODE_SGMII)
++ return -EINVAL;
++
++ if (modes == LINK_INBAND_BYPASS)
++ extsr = MII_M1111_HWCFG_SERIAL_AN_BYPASS;
++ else
++ extsr = 0;
++
++ if (modes == LINK_INBAND_DISABLE)
++ bmcr = 0;
++ else
++ bmcr = BMCR_ANENABLE;
++
++ err = phy_modify(phydev, MII_M1111_PHY_EXT_SR,
++ MII_M1111_HWCFG_SERIAL_AN_BYPASS, extsr);
++ if (err < 0)
++ return extsr;
++
++ return phy_modify_paged(phydev, MII_MARVELL_FIBER_PAGE, MII_BMCR,
++ BMCR_ANENABLE, bmcr);
++}
++
+ static int m88e1111_config_aneg(struct phy_device *phydev)
+ {
+ int extsr = phy_read(phydev, MII_M1111_PHY_EXT_SR);
+@@ -3682,6 +3710,7 @@ static struct phy_driver marvell_drivers
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .inband_caps = m88e1111_inband_caps,
++ .config_inband = m88e1111_config_inband,
+ .config_init = m88e1112_config_init,
+ .config_aneg = marvell_config_aneg,
+ .config_intr = marvell_config_intr,
+@@ -3704,6 +3733,7 @@ static struct phy_driver marvell_drivers
+ .flags = PHY_POLL_CABLE_TEST,
+ .probe = marvell_probe,
+ .inband_caps = m88e1111_inband_caps,
++ .config_inband = m88e1111_config_inband,
+ .config_init = m88e1111gbe_config_init,
+ .config_aneg = m88e1111_config_aneg,
+ .read_status = marvell_read_status,
+@@ -3728,6 +3758,7 @@ static struct phy_driver marvell_drivers
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .inband_caps = m88e1111_inband_caps,
++ .config_inband = m88e1111_config_inband,
+ .config_init = m88e1111gbe_config_init,
+ .config_aneg = m88e1111_config_aneg,
+ .read_status = marvell_read_status,
--- /dev/null
+From df874f9e52c340cc6f0a0014a97b778f67d46849 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:28 +0000
+Subject: [PATCH 09/13] net: phylink: add pcs_inband_caps() method
+
+Add a pcs_inband_caps() 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_inband_caps()
+method is implemented by a PCS driver.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUs4-006IUU-7K@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 60 +++++++++++++++++++++++++++++++++++++++
+ include/linux/phylink.h | 17 +++++++++++
+ 2 files changed, 77 insertions(+)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1007,6 +1007,15 @@ static void phylink_resolve_an_pause(str
+ }
+ }
+
++static unsigned int phylink_pcs_inband_caps(struct phylink_pcs *pcs,
++ phy_interface_t interface)
++{
++ if (pcs && pcs->ops->pcs_inband_caps)
++ return pcs->ops->pcs_inband_caps(pcs, interface);
++
++ return 0;
++}
++
+ static void phylink_pcs_pre_config(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+ {
+@@ -1060,6 +1069,24 @@ static void phylink_pcs_link_up(struct p
+ 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_inband_caps(struct phylink *pl,
++ phy_interface_t interface)
++{
++ struct phylink_pcs *pcs;
++
++ if (!pl->mac_ops->mac_select_pcs)
++ return 0;
++
++ pcs = pl->mac_ops->mac_select_pcs(pl->config, interface);
++ if (!pcs)
++ return 0;
++
++ return phylink_pcs_inband_caps(pcs, interface);
++}
++
+ static void phylink_pcs_poll_stop(struct phylink *pl)
+ {
+ if (pl->cfg_link_an_mode == MLO_AN_INBAND)
+@@ -2530,6 +2557,26 @@ int phylink_ethtool_ksettings_get(struct
+ }
+ 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_inband_caps(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()
+@@ -2665,6 +2712,13 @@ int phylink_ethtool_ksettings_set(struct
+ 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;
+@@ -3349,6 +3403,12 @@ static int phylink_sfp_config_optical(st
+ 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 */
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -419,6 +419,7 @@ struct phylink_pcs {
+ /**
+ * struct phylink_pcs_ops - MAC PCS operations structure.
+ * @pcs_validate: validate the link configuration.
++ * @pcs_inband_caps: 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)
+@@ -434,6 +435,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_inband_caps)(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,
+@@ -471,6 +474,20 @@ int pcs_validate(struct phylink_pcs *pcs
+ const struct phylink_link_state *state);
+
+ /**
++ * pcs_inband_caps - query PCS in-band capabilities 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_inband_caps(struct phylink_pcs *pcs,
++ phy_interface_t interface);
++
++/**
+ * pcs_enable() - enable the PCS.
+ * @pcs: a pointer to a &struct phylink_pcs.
+ */
--- /dev/null
+From 513e8fb8fa32035b3325e2e14fb9598f8cb545e9 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:33 +0000
+Subject: [PATCH 10/13] net: mvneta: implement pcs_inband_caps() method
+
+Report the PCS in-band capabilities to phylink for Marvell NETA
+interfaces.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUs9-006IUb-Au@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/marvell/mvneta.c | 27 +++++++++++++++++----------
+ 1 file changed, 17 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3960,20 +3960,27 @@ static struct mvneta_port *mvneta_pcs_to
+ 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_inband_caps(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,
+@@ -4071,7 +4078,7 @@ static void mvneta_pcs_an_restart(struct
+ }
+
+ static const struct phylink_pcs_ops mvneta_phylink_pcs_ops = {
+- .pcs_validate = mvneta_pcs_validate,
++ .pcs_inband_caps = mvneta_pcs_inband_caps,
+ .pcs_get_state = mvneta_pcs_get_state,
+ .pcs_config = mvneta_pcs_config,
+ .pcs_an_restart = mvneta_pcs_an_restart,
--- /dev/null
+From d4169f0c7665afb8d8adb5e1b1df3db88517d0ad Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:38 +0000
+Subject: [PATCH 11/13] net: mvpp2: implement pcs_inband_caps() method
+
+Report the PCS in-band capabilities to phylink for Marvell PP2
+interfaces.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUsE-006IUh-E7@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ .../net/ethernet/marvell/mvpp2/mvpp2_main.c | 25 ++++++++++++-------
+ 1 file changed, 16 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
++++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
+@@ -6237,19 +6237,26 @@ static const struct phylink_pcs_ops mvpp
+ .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_inband_caps(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,
+@@ -6356,7 +6363,7 @@ static void mvpp2_gmac_pcs_an_restart(st
+ }
+
+ static const struct phylink_pcs_ops mvpp2_phylink_gmac_pcs_ops = {
+- .pcs_validate = mvpp2_gmac_pcs_validate,
++ .pcs_inband_caps = mvpp2_gmac_pcs_inband_caps,
+ .pcs_get_state = mvpp2_gmac_pcs_get_state,
+ .pcs_config = mvpp2_gmac_pcs_config,
+ .pcs_an_restart = mvpp2_gmac_pcs_an_restart,
--- /dev/null
+From 5fd0f1a02e750e2db4038dee60edea669ce5aab1 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:43 +0000
+Subject: [PATCH 12/13] net: phylink: add negotiation of in-band capabilities
+
+Support for in-band signalling with Serdes links is uncertain. Some
+PHYs do not support in-band for e.g. SGMII. Some PCS do not support
+in-band for 2500Base-X. Some PCS require in-band for Base-X protocols.
+
+Simply using what is in DT is insufficient when we have hot-pluggable
+PHYs e.g. in the form of SFP modules, which may not provide the
+in-band signalling.
+
+In order to address this, we have introduced phy_inband_caps() and
+pcs_inband_caps() functions to allow phylink to retrieve the
+capabilities from each end of the PCS/PHY link. This commit adds code
+to resolve whether in-band will be used in the various scenarios that
+we have: In-band not being used, PHY present using SGMII or Base-X,
+PHY not present. We also deal with no capabilties provided.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUsJ-006IUn-H3@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 154 +++++++++++++++++++++++++++++++++++---
+ 1 file changed, 144 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -75,6 +75,7 @@ struct phylink {
+
+ struct mutex state_mutex;
+ struct phylink_link_state phy_state;
++ unsigned int phy_ib_mode;
+ struct work_struct resolve;
+ unsigned int pcs_neg_mode;
+ unsigned int pcs_state;
+@@ -1170,10 +1171,18 @@ static void phylink_pcs_neg_mode(struct
+ phy_interface_t interface,
+ const unsigned long *advertising)
+ {
++ unsigned int pcs_ib_caps = 0;
++ unsigned int phy_ib_caps = 0;
+ unsigned int neg_mode, mode;
++ enum {
++ INBAND_CISCO_SGMII,
++ INBAND_BASEX,
++ } type;
+
+ mode = pl->req_link_an_mode;
+
++ pl->phy_ib_mode = 0;
++
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+@@ -1185,10 +1194,7 @@ static void phylink_pcs_neg_mode(struct
+ * 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:
+@@ -1199,18 +1205,139 @@ static void phylink_pcs_neg_mode(struct
+ * as well, but drivers may not support this, so may
+ * need to override this.
+ */
+- if (!phylink_autoneg_inband(mode))
++ type = INBAND_BASEX;
++ break;
++
++ default:
++ pl->pcs_neg_mode = PHYLINK_PCS_NEG_NONE;
++ pl->act_link_an_mode = mode;
++ return;
++ }
++
++ if (pcs)
++ pcs_ib_caps = phylink_pcs_inband_caps(pcs, interface);
++
++ if (pl->phydev)
++ phy_ib_caps = phy_inband_caps(pl->phydev, interface);
++
++ phylink_dbg(pl, "interface %s inband modes: pcs=%02x phy=%02x\n",
++ phy_modes(interface), pcs_ib_caps, phy_ib_caps);
++
++ if (!phylink_autoneg_inband(mode)) {
++ bool pcs_ib_only = false;
++ bool phy_ib_only = false;
++
++ if (pcs_ib_caps && pcs_ib_caps != LINK_INBAND_DISABLE) {
++ /* PCS supports reporting in-band capabilities, and
++ * supports more than disable mode.
++ */
++ if (pcs_ib_caps & LINK_INBAND_DISABLE)
++ neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++ else if (pcs_ib_caps & LINK_INBAND_ENABLE)
++ pcs_ib_only = true;
++ }
++
++ if (phy_ib_caps && phy_ib_caps != LINK_INBAND_DISABLE) {
++ /* PHY supports in-band capabilities, and supports
++ * more than disable mode.
++ */
++ if (phy_ib_caps & LINK_INBAND_DISABLE)
++ pl->phy_ib_mode = LINK_INBAND_DISABLE;
++ else if (phy_ib_caps & LINK_INBAND_BYPASS)
++ pl->phy_ib_mode = LINK_INBAND_BYPASS;
++ else if (phy_ib_caps & LINK_INBAND_ENABLE)
++ phy_ib_only = true;
++ }
++
++ /* 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 (pcs_ib_only || phy_ib_only)
++ phylink_warn(pl,
++ "firmware wants %s mode, but %s%s%s requires inband\n",
++ phylink_an_mode_str(mode),
++ pcs_ib_only ? "PCS" : "",
++ pcs_ib_only && phy_ib_only ? " and " : "",
++ phy_ib_only ? "PHY" : "");
++
++ neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++ } else if (type == INBAND_CISCO_SGMII || pl->phydev) {
++ /* For SGMII modes which are designed to be used with PHYs, or
++ * Base-X with a PHY, we try to use in-band mode where-ever
++ * possible. However, there are some PHYs e.g. BCM84881 which
++ * do not support in-band.
++ */
++ const unsigned int inband_ok = LINK_INBAND_ENABLE |
++ LINK_INBAND_BYPASS;
++ const unsigned int outband_ok = LINK_INBAND_DISABLE |
++ LINK_INBAND_BYPASS;
++ /* 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_ib_caps || pcs_ib_caps & inband_ok) &&
++ (!phy_ib_caps || phy_ib_caps & inband_ok)) {
++ /* In-band supported or unknown at both ends. Enable
++ * in-band mode with or without bypass at the PHY.
++ */
++ if (phy_ib_caps & LINK_INBAND_ENABLE)
++ pl->phy_ib_mode = LINK_INBAND_ENABLE;
++ else if (phy_ib_caps & LINK_INBAND_BYPASS)
++ pl->phy_ib_mode = LINK_INBAND_BYPASS;
++
++ neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
++ } else if ((!pcs_ib_caps || pcs_ib_caps & outband_ok) &&
++ (!phy_ib_caps || phy_ib_caps & outband_ok)) {
++ /* Either in-band not supported at at least one end.
++ * In-band bypass at the other end is possible.
++ */
++ if (phy_ib_caps & LINK_INBAND_DISABLE)
++ pl->phy_ib_mode = LINK_INBAND_DISABLE;
++ else if (phy_ib_caps & LINK_INBAND_BYPASS)
++ pl->phy_ib_mode = LINK_INBAND_BYPASS;
++
+ neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++ if (pl->phydev)
++ mode = MLO_AN_PHY;
++ } else {
++ /* invalid */
++ phylink_warn(pl, "%s: incompatible in-band capabilities, trying in-band",
++ phy_modes(interface));
++ neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
++ }
++ } else {
++ /* For Base-X without a PHY */
++ if (pcs_ib_caps == LINK_INBAND_DISABLE)
++ /* If the PCS doesn't support inband, then inband must
++ * be disabled.
++ */
++ neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
++ else if (pcs_ib_caps == LINK_INBAND_ENABLE)
++ /* If the PCS requires inband, then inband must always
++ * be enabled.
++ */
++ neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
+ 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;
+- break;
+-
+- default:
+- neg_mode = PHYLINK_PCS_NEG_NONE;
+- break;
+ }
+
+ pl->pcs_neg_mode = neg_mode;
+@@ -1309,6 +1436,13 @@ static void phylink_major_config(struct
+ ERR_PTR(err));
+ }
+
++ if (pl->phydev && pl->phy_ib_mode) {
++ err = phy_config_inband(pl->phydev, pl->phy_ib_mode);
++ if (err < 0)
++ phylink_err(pl, "phy_config_inband: %pe\n",
++ ERR_PTR(err));
++ }
++
+ if (pl->sfp_bus) {
+ rate_kbd = phylink_interface_signal_rate(state->interface);
+ if (rate_kbd)
--- /dev/null
+From 77ac9a8b2536e0eaca6c6f21070068458bf55981 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:48 +0000
+Subject: [PATCH 13/13] net: phylink: remove phylink_phy_no_inband()
+
+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.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUsO-006IUt-KN@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 27 ++++++---------------------
+ 1 file changed, 6 insertions(+), 21 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -3394,10 +3394,11 @@ static phy_interface_t phylink_choose_sf
+ 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",
+@@ -3431,8 +3432,7 @@ static void phylink_sfp_set_config(struc
+ 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(support1);
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(support);
+@@ -3472,7 +3472,7 @@ static int phylink_sfp_config_phy(struct
+ if (ret) {
+ phylink_err(pl,
+ "validation of %s/%s with support %*pb failed: %pe\n",
+- phylink_an_mode_str(mode),
++ phylink_an_mode_str(pl->req_link_an_mode),
+ phy_modes(config.interface),
+ __ETHTOOL_LINK_MODE_MASK_NBITS, support,
+ ERR_PTR(ret));
+@@ -3481,7 +3481,7 @@ static int phylink_sfp_config_phy(struct
+
+ pl->link_port = pl->sfp_port;
+
+- phylink_sfp_set_config(pl, mode, support, &config);
++ phylink_sfp_set_config(pl, support, &config);
+
+ return 0;
+ }
+@@ -3556,7 +3556,7 @@ static int phylink_sfp_config_optical(st
+
+ 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;
+ }
+@@ -3627,20 +3627,10 @@ static void phylink_sfp_link_up(void *up
+ 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;
+ phy_interface_t interface;
+- u8 mode;
+ int ret;
+
+ /*
+@@ -3652,17 +3642,12 @@ static int phylink_sfp_connect_phy(void
+ */
+ 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 */
+- ret = phylink_sfp_config_phy(pl, mode, phy);
++ ret = phylink_sfp_config_phy(pl, phy);
+ if (ret < 0)
+ return ret;
+
--- /dev/null
+From 6561f0e547be221f411fda5eddfcc5bd8bb058a5 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 5 Dec 2024 09:42:24 +0000
+Subject: [PATCH 1/3] net: pcs: pcs-lynx: implement pcs_inband_caps() method
+
+Report the PCS in-band capabilities to phylink for the Lynx PCS.
+
+Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tJ8NM-006L5J-AH@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/pcs/pcs-lynx.c | 22 ++++++++++++++++++++++
+ 1 file changed, 22 insertions(+)
+
+--- a/drivers/net/pcs/pcs-lynx.c
++++ b/drivers/net/pcs/pcs-lynx.c
+@@ -35,6 +35,27 @@ enum sgmii_speed {
+ #define phylink_pcs_to_lynx(pl_pcs) container_of((pl_pcs), struct lynx_pcs, pcs)
+ #define lynx_to_phylink_pcs(lynx) (&(lynx)->pcs)
+
++static unsigned int lynx_pcs_inband_caps(struct phylink_pcs *pcs,
++ phy_interface_t interface)
++{
++ switch (interface) {
++ case PHY_INTERFACE_MODE_1000BASEX:
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_QSGMII:
++ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++ case PHY_INTERFACE_MODE_10GBASER:
++ case PHY_INTERFACE_MODE_2500BASEX:
++ return LINK_INBAND_DISABLE;
++
++ case PHY_INTERFACE_MODE_USXGMII:
++ return LINK_INBAND_ENABLE;
++
++ default:
++ return 0;
++ }
++}
++
+ static void lynx_pcs_get_state_usxgmii(struct mdio_device *pcs,
+ struct phylink_link_state *state)
+ {
+@@ -306,6 +327,7 @@ static void lynx_pcs_link_up(struct phyl
+ }
+
+ static const struct phylink_pcs_ops lynx_pcs_phylink_ops = {
++ .pcs_inband_caps = lynx_pcs_inband_caps,
+ .pcs_get_state = lynx_pcs_get_state,
+ .pcs_config = lynx_pcs_config,
+ .pcs_an_restart = lynx_pcs_an_restart,
--- /dev/null
+From 520d29bdda86915b3caf8c72825a574bff212553 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 5 Dec 2024 09:42:29 +0000
+Subject: [PATCH 2/3] net: pcs: pcs-mtk-lynxi: implement pcs_inband_caps()
+ method
+
+Report the PCS in-band capabilities to phylink for the LynxI PCS.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tJ8NR-006L5P-E3@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/pcs/pcs-mtk-lynxi.c | 16 ++++++++++++++++
+ 1 file changed, 16 insertions(+)
+
+--- a/drivers/net/pcs/pcs-mtk-lynxi.c
++++ b/drivers/net/pcs/pcs-mtk-lynxi.c
+@@ -88,6 +88,21 @@ static struct mtk_pcs_lynxi *pcs_to_mtk_
+ return container_of(pcs, struct mtk_pcs_lynxi, pcs);
+ }
+
++static unsigned int mtk_pcs_lynxi_inband_caps(struct phylink_pcs *pcs,
++ phy_interface_t interface)
++{
++ switch (interface) {
++ case PHY_INTERFACE_MODE_1000BASEX:
++ case PHY_INTERFACE_MODE_2500BASEX:
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_QSGMII:
++ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++ default:
++ return 0;
++ }
++}
++
+ static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs,
+ struct phylink_link_state *state)
+ {
+@@ -241,6 +256,7 @@ static void mtk_pcs_lynxi_disable(struct
+ }
+
+ static const struct phylink_pcs_ops mtk_pcs_lynxi_ops = {
++ .pcs_inband_caps = mtk_pcs_lynxi_inband_caps,
+ .pcs_get_state = mtk_pcs_lynxi_get_state,
+ .pcs_config = mtk_pcs_lynxi_config,
+ .pcs_an_restart = mtk_pcs_lynxi_restart_an,
--- /dev/null
+From 484d0170d6c6bbb5213d037664e9a551f793bacd Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 5 Dec 2024 09:42:34 +0000
+Subject: [PATCH 3/3] net: pcs: xpcs: implement pcs_inband_caps() method
+
+Report the PCS inband capabilities to phylink for XPCS.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tJ8NW-006L5V-I9@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/pcs/pcs-xpcs.c | 28 ++++++++++++++++++++++++++++
+ 1 file changed, 28 insertions(+)
+
+--- a/drivers/net/pcs/pcs-xpcs.c
++++ b/drivers/net/pcs/pcs-xpcs.c
+@@ -608,6 +608,33 @@ static int xpcs_validate(struct phylink_
+ return 0;
+ }
+
++static unsigned int xpcs_inband_caps(struct phylink_pcs *pcs,
++ phy_interface_t interface)
++{
++ struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
++ const struct dw_xpcs_compat *compat;
++
++ compat = xpcs_find_compat(xpcs->desc, interface);
++ if (!compat)
++ return 0;
++
++ switch (compat->an_mode) {
++ case DW_AN_C73:
++ return LINK_INBAND_ENABLE;
++
++ case DW_AN_C37_SGMII:
++ case DW_AN_C37_1000BASEX:
++ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++ case DW_10GBASER:
++ case DW_2500BASEX:
++ return LINK_INBAND_DISABLE;
++
++ default:
++ return 0;
++ }
++}
++
+ void xpcs_get_interfaces(struct dw_xpcs *xpcs, unsigned long *interfaces)
+ {
+ int i, j;
+@@ -1365,6 +1392,7 @@ static const struct dw_xpcs_desc xpcs_de
+
+ static const struct phylink_pcs_ops xpcs_phylink_ops = {
+ .pcs_validate = xpcs_validate,
++ .pcs_inband_caps = xpcs_inband_caps,
+ .pcs_config = xpcs_config,
+ .pcs_get_state = xpcs_get_state,
+ .pcs_an_restart = xpcs_an_restart,
{ PHY_ID_BCM72165, 0xfffffff0, },
--- a/drivers/net/phy/bcm84881.c
+++ b/drivers/net/phy/bcm84881.c
-@@ -252,7 +252,7 @@ static struct phy_driver bcm84881_driver
+@@ -262,7 +262,7 @@ static struct phy_driver bcm84881_driver
module_phy_driver(bcm84881_drivers);
/* FIXME: module auto-loading for Clause 45 PHYs seems non-functional */
};
--- a/drivers/net/phy/marvell.c
+++ b/drivers/net/phy/marvell.c
-@@ -4133,7 +4133,7 @@ static struct phy_driver marvell_drivers
+@@ -4181,7 +4181,7 @@ static struct phy_driver marvell_drivers
module_phy_driver(marvell_drivers);
if (!phydev->drv->led_polarity_set)
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -874,8 +874,9 @@ struct phy_plca_status {
+@@ -892,8 +892,9 @@ struct phy_plca_status {
/* Modes for PHY LED configuration */
enum phy_led_modes {
}
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -1004,7 +1004,8 @@ struct phy_driver {
+@@ -1035,7 +1035,8 @@ struct phy_driver {
* driver for the given phydev. If NULL, matching is based on
* phy_id and phy_id_mask.
*/
static ssize_t
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -1906,6 +1906,9 @@ char *phy_attached_info_irq(struct phy_d
+@@ -1940,6 +1940,9 @@ char *phy_attached_info_irq(struct phy_d
__malloc;
void phy_attached_info(struct phy_device *phydev);
--- /dev/null
+From 8cc5f4cb94c0b1c7c1ba8013c14fd02ffb1a25f3 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 8 Nov 2024 16:01:44 +0000
+Subject: [PATCH 1/5] net: phylink: move manual flow control setting
+
+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) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1t9RQe-002Feh-T1@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1433,7 +1433,6 @@ static void phylink_resolve(struct work_
+ switch (pl->cur_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;
+
+@@ -1494,11 +1493,13 @@ static void phylink_resolve(struct work_
+ link_state.pause = pl->phy_state.pause;
+ mac_config = true;
+ }
+- phylink_apply_manual_flow(pl, &link_state);
+ break;
+ }
+ }
+
++ if (pl->cur_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
--- /dev/null
+From 92abfcb4ced482afbe65d18980e6734fe1e62a34 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 8 Nov 2024 16:01:50 +0000
+Subject: [PATCH 2/5] net: phylink: move MLO_AN_FIXED resolve handling to if()
+ statement
+
+The switch() statement doesn't sit very well with the preceeding if()
+statements, and results in excessive indentation that spoils code
+readability. Begin cleaning this up by converting the MLO_AN_FIXED case
+to an if() statement.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1t9RQk-002Fen-1A@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1429,6 +1429,9 @@ static void phylink_resolve(struct work_
+ } else if (pl->mac_link_dropped) {
+ link_state.link = false;
+ retrigger = true;
++ } else if (pl->cur_link_an_mode == MLO_AN_FIXED) {
++ phylink_get_fixed_state(pl, &link_state);
++ mac_config = link_state.link;
+ } else {
+ switch (pl->cur_link_an_mode) {
+ case MLO_AN_PHY:
+@@ -1436,11 +1439,6 @@ static void phylink_resolve(struct work_
+ mac_config = link_state.link;
+ break;
+
+- case MLO_AN_FIXED:
+- phylink_get_fixed_state(pl, &link_state);
+- mac_config = link_state.link;
+- break;
+-
+ case MLO_AN_INBAND:
+ phylink_mac_pcs_get_state(pl, &link_state);
+
--- /dev/null
+From f0f46c2a3d8ea9d1427298c8103a777d9e616c29 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 8 Nov 2024 16:01:55 +0000
+Subject: [PATCH 3/5] net: phylink: move MLO_AN_PHY resolve handling to if()
+ statement
+
+The switch() statement doesn't sit very well with the preceeding if()
+statements, and results in excessive indentation that spoils code
+readability. Continue cleaning this up by converting the MLO_AN_PHY
+case to use an if() statmeent.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1t9RQp-002Fet-5W@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1432,13 +1432,11 @@ static void phylink_resolve(struct work_
+ } else if (pl->cur_link_an_mode == MLO_AN_FIXED) {
+ phylink_get_fixed_state(pl, &link_state);
+ mac_config = link_state.link;
++ } else if (pl->cur_link_an_mode == MLO_AN_PHY) {
++ link_state = pl->phy_state;
++ mac_config = link_state.link;
+ } else {
+ switch (pl->cur_link_an_mode) {
+- case MLO_AN_PHY:
+- link_state = pl->phy_state;
+- mac_config = link_state.link;
+- break;
+-
+ case MLO_AN_INBAND:
+ phylink_mac_pcs_get_state(pl, &link_state);
+
--- /dev/null
+From d1a16dbbd84e02d2a6dcfcb8d5c4b8b2c0289f00 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 8 Nov 2024 16:02:00 +0000
+Subject: [PATCH 4/5] net: phylink: remove switch() statement in resolve
+ handling
+
+The switch() statement doesn't sit very well with the preceeding if()
+statements, so let's just convert everything to if()s. As a result of
+the two preceding commits, there is now only one case in the switch()
+statement. Remove the switch statement and reduce the code indentation.
+Code reformatting will be in the following commit.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1t9RQu-002Fez-AA@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 94 +++++++++++++++++++--------------------
+ 1 file changed, 45 insertions(+), 49 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1436,60 +1436,56 @@ static void phylink_resolve(struct work_
+ link_state = pl->phy_state;
+ mac_config = link_state.link;
+ } else {
+- switch (pl->cur_link_an_mode) {
+- case MLO_AN_INBAND:
+- phylink_mac_pcs_get_state(pl, &link_state);
+-
+- /* 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.
++ phylink_mac_pcs_get_state(pl, &link_state);
++
++ /* 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);
++ }
++
++ /* 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;
++
++ /* 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;
+ }
+ }
+
--- /dev/null
+From bc08ce37d99a3992e975a0f397503cb23404f25a Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 8 Nov 2024 16:02:05 +0000
+Subject: [PATCH 5/5] net: phylink: clean up phylink_resolve()
+
+Now that we have reduced the indentation level, clean up the code
+formatting.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1t9RQz-002Ff5-EA@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 35 ++++++++++++++++-------------------
+ 1 file changed, 16 insertions(+), 19 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1438,51 +1438,48 @@ static void phylink_resolve(struct work_
+ } else {
+ phylink_mac_pcs_get_state(pl, &link_state);
+
+- /* 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.
++ /* 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);
++ 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 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;
+
+ /* 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 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) {
++ 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 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;
++ link_state.duplex = pl->phy_state.duplex;
+ }
+
+- /* If we have a PHY, we need to update with
+- * the PHY flow control bits.
++ /* 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;
--- /dev/null
+From 5e5401d6612ef599ad45785b941eebda7effc90f Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 4 Jan 2024 09:47:36 +0000
+Subject: [PATCH] net: phylink: move phylink_pcs_neg_mode() into phylink.c
+
+Move phylink_pcs_neg_mode() from the header file into the .c file since
+nothing should be using it.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phylink.c | 66 +++++++++++++++++++++++++++++++++++++++
+ include/linux/phylink.h | 66 ---------------------------------------
+ 2 files changed, 66 insertions(+), 66 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1116,6 +1116,72 @@ static void phylink_pcs_an_restart(struc
+ pl->pcs->ops->pcs_an_restart(pl->pcs);
+ }
+
++/**
++ * phylink_pcs_neg_mode() - helper to determine PCS inband mode
++ * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
++ * @interface: interface mode to be used
++ * @advertising: adertisement ethtool link mode mask
++ *
++ * Determines the negotiation mode to be used by the PCS, and returns
++ * one of:
++ *
++ * - %PHYLINK_PCS_NEG_NONE: interface mode does not support inband
++ * - %PHYLINK_PCS_NEG_OUTBAND: an out of band mode (e.g. reading the PHY)
++ * will be used.
++ * - %PHYLINK_PCS_NEG_INBAND_DISABLED: inband mode selected but autoneg
++ * disabled
++ * - %PHYLINK_PCS_NEG_INBAND_ENABLED: inband mode selected and autoneg enabled
++ *
++ * 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,
++ phy_interface_t interface,
++ const unsigned long *advertising)
++{
++ unsigned int neg_mode;
++
++ switch (interface) {
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_QUSGMII:
++ case PHY_INTERFACE_MODE_USXGMII:
++ /* These protocols are designed for use with a PHY which
++ * communicates its negotiation result back to the MAC via
++ * 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;
++ break;
++
++ case PHY_INTERFACE_MODE_1000BASEX:
++ case PHY_INTERFACE_MODE_2500BASEX:
++ /* 1000base-X is designed for use media-side for Fibre
++ * connections, and thus the Autoneg bit needs to be
++ * taken into account. We also do this for 2500base-X
++ * 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;
++ break;
++
++ default:
++ neg_mode = PHYLINK_PCS_NEG_NONE;
++ break;
++ }
++
++ return neg_mode;
++}
++
+ static void phylink_major_config(struct phylink *pl, bool restart,
+ const struct phylink_link_state *state)
+ {
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -99,72 +99,6 @@ static inline bool phylink_autoneg_inban
+ }
+
+ /**
+- * phylink_pcs_neg_mode() - helper to determine PCS inband mode
+- * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
+- * @interface: interface mode to be used
+- * @advertising: adertisement ethtool link mode mask
+- *
+- * Determines the negotiation mode to be used by the PCS, and returns
+- * one of:
+- *
+- * - %PHYLINK_PCS_NEG_NONE: interface mode does not support inband
+- * - %PHYLINK_PCS_NEG_OUTBAND: an out of band mode (e.g. reading the PHY)
+- * will be used.
+- * - %PHYLINK_PCS_NEG_INBAND_DISABLED: inband mode selected but autoneg
+- * disabled
+- * - %PHYLINK_PCS_NEG_INBAND_ENABLED: inband mode selected and autoneg enabled
+- *
+- * Note: this is for cases where the PCS itself is involved in negotiation
+- * (e.g. Clause 37, SGMII and similar) not Clause 73.
+- */
+-static inline unsigned int phylink_pcs_neg_mode(unsigned int mode,
+- phy_interface_t interface,
+- const unsigned long *advertising)
+-{
+- unsigned int neg_mode;
+-
+- switch (interface) {
+- case PHY_INTERFACE_MODE_SGMII:
+- case PHY_INTERFACE_MODE_QSGMII:
+- case PHY_INTERFACE_MODE_QUSGMII:
+- case PHY_INTERFACE_MODE_USXGMII:
+- /* These protocols are designed for use with a PHY which
+- * communicates its negotiation result back to the MAC via
+- * 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;
+- break;
+-
+- case PHY_INTERFACE_MODE_1000BASEX:
+- case PHY_INTERFACE_MODE_2500BASEX:
+- /* 1000base-X is designed for use media-side for Fibre
+- * connections, and thus the Autoneg bit needs to be
+- * taken into account. We also do this for 2500base-X
+- * 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;
+- break;
+-
+- default:
+- neg_mode = PHYLINK_PCS_NEG_NONE;
+- break;
+- }
+-
+- return neg_mode;
+-}
+-
+-/**
+ * struct phylink_link_state - link state structure
+ * @advertising: ethtool bitmask containing advertised link modes
+ * @lp_advertising: ethtool bitmask containing link partner advertised link
--- /dev/null
+From 17ed1911f9c8d4f9af8e13b2c95103ee06dadc0f Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:30:47 +0000
+Subject: [PATCH 01/13] net: phylink: pass phylink and pcs into
+ phylink_pcs_neg_mode()
+
+Move the call to phylink_pcs_neg_mode() in phylink_major_config() after
+we have selected the appropriate PCS to allow the PCS to be passed in.
+
+Add struct phylink and struct phylink_pcs pointers to
+phylink_pcs_neg_mode() and pass in the appropriate structures. Set
+pl->pcs_neg_mode before returning, and remove the return value.
+
+This will allow the capabilities of the PCS and any PHY to be used when
+deciding which pcs_neg_mode should be used.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUrP-006ITh-6u@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 26 +++++++++++++-------------
+ 1 file changed, 13 insertions(+), 13 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1118,7 +1118,8 @@ static void phylink_pcs_an_restart(struc
+
+ /**
+ * phylink_pcs_neg_mode() - helper to determine PCS inband mode
+- * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
++ * @pl: a pointer to a &struct phylink returned from phylink_create()
++ * @pcs: a pointer to &struct phylink_pcs
+ * @interface: interface mode to be used
+ * @advertising: adertisement ethtool link mode mask
+ *
+@@ -1135,11 +1136,13 @@ static void phylink_pcs_an_restart(struc
+ * 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,
+- phy_interface_t interface,
+- const unsigned long *advertising)
++static void phylink_pcs_neg_mode(struct phylink *pl, struct phylink_pcs *pcs,
++ phy_interface_t interface,
++ const unsigned long *advertising)
+ {
+- unsigned int neg_mode;
++ unsigned int neg_mode, mode;
++
++ mode = pl->cur_link_an_mode;
+
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+@@ -1179,7 +1182,7 @@ static unsigned int phylink_pcs_neg_mode
+ break;
+ }
+
+- return neg_mode;
++ pl->pcs_neg_mode = neg_mode;
+ }
+
+ static void phylink_major_config(struct phylink *pl, bool restart,
+@@ -1193,10 +1196,6 @@ static void phylink_major_config(struct
+
+ phylink_dbg(pl, "major config %s\n", phy_modes(state->interface));
+
+- pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
+- state->interface,
+- state->advertising);
+-
+ if (pl->using_mac_select_pcs) {
+ pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+ if (IS_ERR(pcs)) {
+@@ -1209,6 +1208,8 @@ static void phylink_major_config(struct
+ pcs_changed = pcs && pl->pcs != pcs;
+ }
+
++ phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
++
+ phylink_pcs_poll_stop(pl);
+
+ if (pl->mac_ops->mac_prepare) {
+@@ -1299,9 +1300,8 @@ static int phylink_change_inband_advert(
+ pl->link_config.pause);
+
+ /* Recompute the PCS neg mode */
+- pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
+- pl->link_config.interface,
+- pl->link_config.advertising);
++ phylink_pcs_neg_mode(pl, pl->pcs, pl->link_config.interface,
++ pl->link_config.advertising);
+
+ neg_mode = pl->cur_link_an_mode;
+ if (pl->pcs->neg_mode)
--- /dev/null
+From 1f92ead7e15003f632b5f138e8138095e0997d3d Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:30:52 +0000
+Subject: [PATCH 02/13] net: phylink: split cur_link_an_mode into requested and
+ active
+
+There is an interdependence between the current link_an_mode and
+pcs_neg_mode that some drivers rely upon to know whether inband or PHY
+mode will be used.
+
+In order to support detection of PCS and PHY inband capabilities
+resulting in automatic selection of inband or PHY mode, we need to
+cater for this, and support changing the MAC link_an_mode. However, we
+end up with an inter-dependency between the current link_an_mode and
+pcs_neg_mode.
+
+To solve this, split the current link_an_mode into the requested
+link_an_mode and active link_an_mode. The requested link_an_mode will
+always be passed to phylink_pcs_neg_mode(), and the active link_an_mode
+will be used for everything else, and only updated during
+phylink_major_config(). This will ensure that phylink_pcs_neg_mode()'s
+link_an_mode will not depend on the active link_an_mode that will,
+in a future patch, depend on pcs_neg_mode.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUrU-006ITn-Ai@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 60 ++++++++++++++++++++-------------------
+ 1 file changed, 31 insertions(+), 29 deletions(-)
+
+--- 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);
+
+@@ -1098,13 +1099,13 @@ static void phylink_mac_config(struct ph
+
+ 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)
+@@ -1112,7 +1113,7 @@ static void phylink_pcs_an_restart(struc
+ 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);
+ }
+
+@@ -1142,7 +1143,7 @@ static void phylink_pcs_neg_mode(struct
+ {
+ unsigned int neg_mode, mode;
+
+- mode = pl->cur_link_an_mode;
++ mode = pl->req_link_an_mode;
+
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+@@ -1183,6 +1184,7 @@ static void phylink_pcs_neg_mode(struct
+ }
+
+ pl->pcs_neg_mode = neg_mode;
++ pl->act_link_an_mode = mode;
+ }
+
+ static void phylink_major_config(struct phylink *pl, bool restart,
+@@ -1213,7 +1215,7 @@ static void phylink_major_config(struct
+ 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",
+@@ -1247,7 +1249,7 @@ static void phylink_major_config(struct
+ 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;
+
+@@ -1263,7 +1265,7 @@ static void phylink_major_config(struct
+ 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",
+@@ -1294,7 +1296,7 @@ static int phylink_change_inband_advert(
+ 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);
+@@ -1303,7 +1305,7 @@ static int phylink_change_inband_advert(
+ phylink_pcs_neg_mode(pl, pl->pcs, pl->link_config.interface,
+ pl->link_config.advertising);
+
+- neg_mode = pl->cur_link_an_mode;
++ neg_mode = pl->act_link_an_mode;
+ if (pl->pcs->neg_mode)
+ neg_mode = pl->pcs_neg_mode;
+
+@@ -1368,7 +1370,7 @@ static void phylink_mac_initial_config(s
+ {
+ 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;
+@@ -1442,14 +1444,14 @@ static void phylink_link_up(struct phyli
+
+ 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);
+
+@@ -1469,7 +1471,7 @@ static void phylink_link_down(struct phy
+
+ if (ndev)
+ netif_carrier_off(ndev);
+- 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");
+ }
+@@ -1495,10 +1497,10 @@ static void phylink_resolve(struct work_
+ } else if (pl->mac_link_dropped) {
+ link_state.link = false;
+ retrigger = true;
+- } else if (pl->cur_link_an_mode == MLO_AN_FIXED) {
++ } 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->cur_link_an_mode == MLO_AN_PHY) {
++ } else if (pl->act_link_an_mode == MLO_AN_PHY) {
+ link_state = pl->phy_state;
+ mac_config = link_state.link;
+ } else {
+@@ -1552,7 +1554,7 @@ static void phylink_resolve(struct work_
+ }
+ }
+
+- if (pl->cur_link_an_mode != MLO_AN_FIXED)
++ if (pl->act_link_an_mode != MLO_AN_FIXED)
+ phylink_apply_manual_flow(pl, &link_state);
+
+ if (mac_config) {
+@@ -1729,7 +1731,7 @@ struct phylink *phylink_create(struct ph
+ }
+ }
+
+- pl->cur_link_an_mode = pl->cfg_link_an_mode;
++ pl->req_link_an_mode = pl->cfg_link_an_mode;
+
+ ret = phylink_register_sfp(pl, fwnode);
+ if (ret < 0) {
+@@ -2126,7 +2128,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 */
+@@ -2385,7 +2387,7 @@ int phylink_ethtool_ksettings_get(struct
+
+ 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
+@@ -2477,7 +2479,7 @@ int phylink_ethtool_ksettings_set(struct
+ /* 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;
+@@ -2493,7 +2495,7 @@ int phylink_ethtool_ksettings_set(struct
+ * 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;
+@@ -2533,7 +2535,7 @@ int phylink_ethtool_ksettings_set(struct
+ 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;
+@@ -2633,7 +2635,7 @@ int phylink_ethtool_set_pauseparam(struc
+
+ 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) &&
+@@ -2897,7 +2899,7 @@ static int phylink_mii_read(struct phyli
+ 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);
+@@ -2922,7 +2924,7 @@ static int phylink_mii_read(struct phyli
+ 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;
+
+@@ -3125,9 +3127,9 @@ static void phylink_sfp_set_config(struc
+ 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;
--- /dev/null
+From 4e7d000286fe8e12f2d88032711ffab3ab658b12 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:30:57 +0000
+Subject: [PATCH 03/13] net: phylink: add debug for phylink_major_config()
+
+Now that we have a more complexity in phylink_major_config(), augment
+the debugging so we can see what's going on there.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUrZ-006ITt-Fa@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 27 ++++++++++++++++++++++++++-
+ 1 file changed, 26 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -163,6 +163,24 @@ static const char *phylink_an_mode_str(u
+ 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) {
+@@ -1196,7 +1214,9 @@ static void phylink_major_config(struct
+ 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));
+
+ if (pl->using_mac_select_pcs) {
+ pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+@@ -1212,6 +1232,11 @@ static void phylink_major_config(struct
+
+ phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
+
++ 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);
+
+ if (pl->mac_ops->mac_prepare) {
--- /dev/null
+From b4c7698dd95f253c6958d8c6ac219098009bf28a Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:02 +0000
+Subject: [PATCH 04/13] net: phy: add phy_inband_caps()
+
+Add a method to query the PHY's in-band capabilities for a PHY
+interface mode.
+
+Where the interface mode does not have in-band capability, or the PHY
+driver has not been updated to return this information, then
+phy_inband_caps() should return zero. Otherwise, PHY drivers will
+return a value consisting of the following flags:
+
+LINK_INBAND_DISABLE indicates that the hardware does not support
+in-band signalling, or can have in-band signalling configured via
+software to be disabled.
+
+LINK_INBAND_ENABLE indicates that the hardware will use in-band
+signalling, or can have in-band signalling configured via software
+to be enabled.
+
+LINK_INBAND_BYPASS indicates that the hardware has the ability to
+bypass in-band signalling when enabled after a timeout if the link
+partner does not respond to its in-band signalling.
+
+This reports the PHY capabilities for the particular interface mode,
+not the current configuration.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUre-006ITz-KF@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phy.c | 21 +++++++++++++++++++++
+ include/linux/phy.h | 28 ++++++++++++++++++++++++++++
+ 2 files changed, 49 insertions(+)
+
+--- a/drivers/net/phy/phy.c
++++ b/drivers/net/phy/phy.c
+@@ -973,6 +973,27 @@ static int phy_check_link_status(struct
+ }
+
+ /**
++ * phy_inband_caps - 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
++ * by the PHY for this interface mode.
++ */
++unsigned int phy_inband_caps(struct phy_device *phydev,
++ phy_interface_t interface)
++{
++ if (phydev->drv && phydev->drv->inband_caps)
++ return phydev->drv->inband_caps(phydev, interface);
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(phy_inband_caps);
++
++/**
+ * _phy_start_aneg - start auto-negotiation for this PHY device
+ * @phydev: the phy_device struct
+ *
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -790,6 +790,24 @@ struct phy_tdr_config {
+ #define PHY_PAIR_ALL -1
+
+ /**
++ * enum link_inband_signalling - in-band signalling modes that are supported
++ *
++ * @LINK_INBAND_DISABLE: in-band signalling can be disabled
++ * @LINK_INBAND_ENABLE: in-band signalling can be enabled without bypass
++ * @LINK_INBAND_BYPASS: in-band signalling can be enabled with 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.
+ *
+@@ -919,6 +937,14 @@ struct phy_driver {
+ int (*get_features)(struct phy_device *phydev);
+
+ /**
++ * @inband_caps: query whether in-band is supported for the given PHY
++ * interface mode. Returns a bitmask of bits defined by enum
++ * link_inband_signalling.
++ */
++ unsigned int (*inband_caps)(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
+ * whether to advertise lower-speed modes for that interface. It is
+@@ -1735,6 +1761,8 @@ void phy_stop(struct phy_device *phydev)
+ int phy_config_aneg(struct phy_device *phydev);
+ int phy_start_aneg(struct phy_device *phydev);
+ int phy_aneg_done(struct phy_device *phydev);
++unsigned int phy_inband_caps(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);
--- /dev/null
+From c64c7fa0a774d9da72071a8517e359992baac982 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:07 +0000
+Subject: [PATCH 05/13] net: phy: bcm84881: implement phy_inband_caps() method
+
+BCM84881 has no support for inband signalling, so this is a trivial
+implementation that returns no support for inband.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Acked-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Link: https://patch.msgid.link/E1tIUrj-006IU6-ON@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/bcm84881.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+--- a/drivers/net/phy/bcm84881.c
++++ b/drivers/net/phy/bcm84881.c
+@@ -223,11 +223,21 @@ static int bcm84881_read_status(struct p
+ 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_inband_caps(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",
++ .inband_caps = bcm84881_inband_caps,
+ .config_init = bcm84881_config_init,
+ .probe = bcm84881_probe,
+ .get_features = bcm84881_get_features,
--- /dev/null
+From 1c86828dff88e28b8ade6bddeee0163a023faf91 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:12 +0000
+Subject: [PATCH 06/13] net: phy: marvell: implement phy_inband_caps() method
+
+Provide an implementation for phy_inband_caps() for Marvell PHYs used
+on SFP modules, so that phylink knows the PHYs capabilities.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUro-006IUC-Rq@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/marvell.c | 17 +++++++++++++++++
+ 1 file changed, 17 insertions(+)
+
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -673,6 +673,20 @@ static int marvell_config_aneg_fiber(str
+ return genphy_check_and_restart_aneg(phydev, changed);
+ }
+
++static unsigned int m88e1111_inband_caps(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 |
++ LINK_INBAND_BYPASS;
++
++ return 0;
++}
++
+ static int m88e1111_config_aneg(struct phy_device *phydev)
+ {
+ int extsr = phy_read(phydev, MII_M1111_PHY_EXT_SR);
+@@ -3292,6 +3306,7 @@ static struct phy_driver marvell_drivers
+ .name = "Marvell 88E1112",
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
++ .inband_caps = m88e1111_inband_caps,
+ .config_init = m88e1112_config_init,
+ .config_aneg = marvell_config_aneg,
+ .config_intr = marvell_config_intr,
+@@ -3312,6 +3327,7 @@ static struct phy_driver marvell_drivers
+ .name = "Marvell 88E1111",
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
++ .inband_caps = m88e1111_inband_caps,
+ .config_init = m88e1111gbe_config_init,
+ .config_aneg = m88e1111_config_aneg,
+ .read_status = marvell_read_status,
+@@ -3333,6 +3349,7 @@ static struct phy_driver marvell_drivers
+ .name = "Marvell 88E1111 (Finisar)",
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
++ .inband_caps = m88e1111_inband_caps,
+ .config_init = m88e1111gbe_config_init,
+ .config_aneg = m88e1111_config_aneg,
+ .read_status = marvell_read_status,
--- /dev/null
+From 5d58a890c02770ba8d790b1f3c6e8c0e20514dc2 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:18 +0000
+Subject: [PATCH 07/13] net: phy: add phy_config_inband()
+
+Add a method to configure the PHY's in-band mode.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUru-006IUI-08@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phy.c | 32 ++++++++++++++++++++++++++++++++
+ include/linux/phy.h | 6 ++++++
+ 2 files changed, 38 insertions(+)
+
+--- a/drivers/net/phy/phy.c
++++ b/drivers/net/phy/phy.c
+@@ -994,6 +994,38 @@ unsigned int phy_inband_caps(struct phy_
+ EXPORT_SYMBOL_GPL(phy_inband_caps);
+
+ /**
++ * phy_config_inband - configure the desired PHY in-band mode
++ * @phydev: the phy_device struct
++ * @modes: in-band modes to configure
++ *
++ * Description: disables, enables or enables-with-bypass in-band signalling
++ * between the PHY and host system.
++ *
++ * Returns: zero on success, or negative errno value.
++ */
++int phy_config_inband(struct phy_device *phydev, unsigned int modes)
++{
++ int err;
++
++ if (!!(modes & LINK_INBAND_DISABLE) +
++ !!(modes & LINK_INBAND_ENABLE) +
++ !!(modes & LINK_INBAND_BYPASS) != 1)
++ return -EINVAL;
++
++ mutex_lock(&phydev->lock);
++ if (!phydev->drv)
++ err = -EIO;
++ else if (!phydev->drv->config_inband)
++ err = -EOPNOTSUPP;
++ else
++ err = phydev->drv->config_inband(phydev, modes);
++ mutex_unlock(&phydev->lock);
++
++ return err;
++}
++EXPORT_SYMBOL(phy_config_inband);
++
++/**
+ * _phy_start_aneg - start auto-negotiation for this PHY device
+ * @phydev: the phy_device struct
+ *
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -945,6 +945,11 @@ struct phy_driver {
+ phy_interface_t interface);
+
+ /**
++ * @config_inband: configure in-band mode for the PHY
++ */
++ int (*config_inband)(struct phy_device *phydev, unsigned int modes);
++
++ /**
+ * @get_rate_matching: Get the supported type of rate matching for a
+ * particular phy interface. This is used by phy consumers to determine
+ * whether to advertise lower-speed modes for that interface. It is
+@@ -1763,6 +1768,7 @@ int phy_start_aneg(struct phy_device *ph
+ int phy_aneg_done(struct phy_device *phydev);
+ unsigned int phy_inband_caps(struct phy_device *phydev,
+ phy_interface_t interface);
++int phy_config_inband(struct phy_device *phydev, unsigned int modes);
+ 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);
--- /dev/null
+From a219912e0fec73c346e64ef47013cb2e152f88fc Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:23 +0000
+Subject: [PATCH 08/13] net: phy: marvell: implement config_inband() method
+
+Implement the config_inband() method for Marvell 88E1112, 88E1111,
+and Finisar's 88E1111 variant.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUrz-006IUO-3r@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/marvell.c | 31 +++++++++++++++++++++++++++++++
+ 1 file changed, 31 insertions(+)
+
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -687,6 +687,34 @@ static unsigned int m88e1111_inband_caps
+ return 0;
+ }
+
++static int m88e1111_config_inband(struct phy_device *phydev, unsigned int modes)
++{
++ u16 extsr, bmcr;
++ int err;
++
++ if (phydev->interface != PHY_INTERFACE_MODE_1000BASEX &&
++ phydev->interface != PHY_INTERFACE_MODE_SGMII)
++ return -EINVAL;
++
++ if (modes == LINK_INBAND_BYPASS)
++ extsr = MII_M1111_HWCFG_SERIAL_AN_BYPASS;
++ else
++ extsr = 0;
++
++ if (modes == LINK_INBAND_DISABLE)
++ bmcr = 0;
++ else
++ bmcr = BMCR_ANENABLE;
++
++ err = phy_modify(phydev, MII_M1111_PHY_EXT_SR,
++ MII_M1111_HWCFG_SERIAL_AN_BYPASS, extsr);
++ if (err < 0)
++ return extsr;
++
++ return phy_modify_paged(phydev, MII_MARVELL_FIBER_PAGE, MII_BMCR,
++ BMCR_ANENABLE, bmcr);
++}
++
+ static int m88e1111_config_aneg(struct phy_device *phydev)
+ {
+ int extsr = phy_read(phydev, MII_M1111_PHY_EXT_SR);
+@@ -3307,6 +3335,7 @@ static struct phy_driver marvell_drivers
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .inband_caps = m88e1111_inband_caps,
++ .config_inband = m88e1111_config_inband,
+ .config_init = m88e1112_config_init,
+ .config_aneg = marvell_config_aneg,
+ .config_intr = marvell_config_intr,
+@@ -3328,6 +3357,7 @@ static struct phy_driver marvell_drivers
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .inband_caps = m88e1111_inband_caps,
++ .config_inband = m88e1111_config_inband,
+ .config_init = m88e1111gbe_config_init,
+ .config_aneg = m88e1111_config_aneg,
+ .read_status = marvell_read_status,
+@@ -3350,6 +3380,7 @@ static struct phy_driver marvell_drivers
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .inband_caps = m88e1111_inband_caps,
++ .config_inband = m88e1111_config_inband,
+ .config_init = m88e1111gbe_config_init,
+ .config_aneg = m88e1111_config_aneg,
+ .read_status = marvell_read_status,
--- /dev/null
+From df874f9e52c340cc6f0a0014a97b778f67d46849 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:28 +0000
+Subject: [PATCH 09/13] net: phylink: add pcs_inband_caps() method
+
+Add a pcs_inband_caps() 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_inband_caps()
+method is implemented by a PCS driver.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUs4-006IUU-7K@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 60 +++++++++++++++++++++++++++++++++++++++
+ include/linux/phylink.h | 17 +++++++++++
+ 2 files changed, 77 insertions(+)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1038,6 +1038,15 @@ static void phylink_resolve_an_pause(str
+ }
+ }
+
++static unsigned int phylink_pcs_inband_caps(struct phylink_pcs *pcs,
++ phy_interface_t interface)
++{
++ if (pcs && pcs->ops->pcs_inband_caps)
++ return pcs->ops->pcs_inband_caps(pcs, interface);
++
++ return 0;
++}
++
+ static void phylink_pcs_pre_config(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+ {
+@@ -1091,6 +1100,24 @@ static void phylink_pcs_link_up(struct p
+ 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_inband_caps(struct phylink *pl,
++ phy_interface_t interface)
++{
++ struct phylink_pcs *pcs;
++
++ if (!pl->mac_ops->mac_select_pcs)
++ return 0;
++
++ pcs = pl->mac_ops->mac_select_pcs(pl->config, interface);
++ if (!pcs)
++ return 0;
++
++ return phylink_pcs_inband_caps(pcs, interface);
++}
++
+ static void phylink_pcs_poll_stop(struct phylink *pl)
+ {
+ if (pl->cfg_link_an_mode == MLO_AN_INBAND)
+@@ -2443,6 +2470,26 @@ int phylink_ethtool_ksettings_get(struct
+ }
+ 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_inband_caps(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()
+@@ -2578,6 +2625,13 @@ int phylink_ethtool_ksettings_set(struct
+ 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;
+@@ -3274,6 +3328,12 @@ static int phylink_sfp_config_optical(st
+ 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 */
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -432,6 +432,7 @@ struct phylink_pcs {
+ /**
+ * struct phylink_pcs_ops - MAC PCS operations structure.
+ * @pcs_validate: validate the link configuration.
++ * @pcs_inband_caps: 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)
+@@ -445,6 +446,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_inband_caps)(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,
+@@ -481,6 +484,20 @@ int pcs_validate(struct phylink_pcs *pcs
+ const struct phylink_link_state *state);
+
+ /**
++ * pcs_inband_caps - query PCS in-band capabilities 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_inband_caps(struct phylink_pcs *pcs,
++ phy_interface_t interface);
++
++/**
+ * pcs_enable() - enable the PCS.
+ * @pcs: a pointer to a &struct phylink_pcs.
+ */
--- /dev/null
+From 513e8fb8fa32035b3325e2e14fb9598f8cb545e9 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:33 +0000
+Subject: [PATCH 10/13] net: mvneta: implement pcs_inband_caps() method
+
+Report the PCS in-band capabilities to phylink for Marvell NETA
+interfaces.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUs9-006IUb-Au@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/marvell/mvneta.c | 27 +++++++++++++++++----------
+ 1 file changed, 17 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3959,20 +3959,27 @@ static struct mvneta_port *mvneta_pcs_to
+ 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_inband_caps(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,
+@@ -4070,7 +4077,7 @@ static void mvneta_pcs_an_restart(struct
+ }
+
+ static const struct phylink_pcs_ops mvneta_phylink_pcs_ops = {
+- .pcs_validate = mvneta_pcs_validate,
++ .pcs_inband_caps = mvneta_pcs_inband_caps,
+ .pcs_get_state = mvneta_pcs_get_state,
+ .pcs_config = mvneta_pcs_config,
+ .pcs_an_restart = mvneta_pcs_an_restart,
--- /dev/null
+From d4169f0c7665afb8d8adb5e1b1df3db88517d0ad Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:38 +0000
+Subject: [PATCH 11/13] net: mvpp2: implement pcs_inband_caps() method
+
+Report the PCS in-band capabilities to phylink for Marvell PP2
+interfaces.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUsE-006IUh-E7@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ .../net/ethernet/marvell/mvpp2/mvpp2_main.c | 25 ++++++++++++-------
+ 1 file changed, 16 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
++++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
+@@ -6214,19 +6214,26 @@ static const struct phylink_pcs_ops mvpp
+ .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_inband_caps(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,
+@@ -6333,7 +6340,7 @@ static void mvpp2_gmac_pcs_an_restart(st
+ }
+
+ static const struct phylink_pcs_ops mvpp2_phylink_gmac_pcs_ops = {
+- .pcs_validate = mvpp2_gmac_pcs_validate,
++ .pcs_inband_caps = mvpp2_gmac_pcs_inband_caps,
+ .pcs_get_state = mvpp2_gmac_pcs_get_state,
+ .pcs_config = mvpp2_gmac_pcs_config,
+ .pcs_an_restart = mvpp2_gmac_pcs_an_restart,
--- /dev/null
+From 5fd0f1a02e750e2db4038dee60edea669ce5aab1 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:43 +0000
+Subject: [PATCH 12/13] net: phylink: add negotiation of in-band capabilities
+
+Support for in-band signalling with Serdes links is uncertain. Some
+PHYs do not support in-band for e.g. SGMII. Some PCS do not support
+in-band for 2500Base-X. Some PCS require in-band for Base-X protocols.
+
+Simply using what is in DT is insufficient when we have hot-pluggable
+PHYs e.g. in the form of SFP modules, which may not provide the
+in-band signalling.
+
+In order to address this, we have introduced phy_inband_caps() and
+pcs_inband_caps() functions to allow phylink to retrieve the
+capabilities from each end of the PCS/PHY link. This commit adds code
+to resolve whether in-band will be used in the various scenarios that
+we have: In-band not being used, PHY present using SGMII or Base-X,
+PHY not present. We also deal with no capabilties provided.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUsJ-006IUn-H3@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 154 +++++++++++++++++++++++++++++++++++---
+ 1 file changed, 144 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -75,6 +75,7 @@ struct phylink {
+
+ struct mutex state_mutex;
+ struct phylink_link_state phy_state;
++ unsigned int phy_ib_mode;
+ struct work_struct resolve;
+ unsigned int pcs_neg_mode;
+ unsigned int pcs_state;
+@@ -1186,10 +1187,18 @@ static void phylink_pcs_neg_mode(struct
+ phy_interface_t interface,
+ const unsigned long *advertising)
+ {
++ unsigned int pcs_ib_caps = 0;
++ unsigned int phy_ib_caps = 0;
+ unsigned int neg_mode, mode;
++ enum {
++ INBAND_CISCO_SGMII,
++ INBAND_BASEX,
++ } type;
+
+ mode = pl->req_link_an_mode;
+
++ pl->phy_ib_mode = 0;
++
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+@@ -1200,10 +1209,7 @@ static void phylink_pcs_neg_mode(struct
+ * 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:
+@@ -1214,18 +1220,139 @@ static void phylink_pcs_neg_mode(struct
+ * as well, but drivers may not support this, so may
+ * need to override this.
+ */
+- if (!phylink_autoneg_inband(mode))
++ type = INBAND_BASEX;
++ break;
++
++ default:
++ pl->pcs_neg_mode = PHYLINK_PCS_NEG_NONE;
++ pl->act_link_an_mode = mode;
++ return;
++ }
++
++ if (pcs)
++ pcs_ib_caps = phylink_pcs_inband_caps(pcs, interface);
++
++ if (pl->phydev)
++ phy_ib_caps = phy_inband_caps(pl->phydev, interface);
++
++ phylink_dbg(pl, "interface %s inband modes: pcs=%02x phy=%02x\n",
++ phy_modes(interface), pcs_ib_caps, phy_ib_caps);
++
++ if (!phylink_autoneg_inband(mode)) {
++ bool pcs_ib_only = false;
++ bool phy_ib_only = false;
++
++ if (pcs_ib_caps && pcs_ib_caps != LINK_INBAND_DISABLE) {
++ /* PCS supports reporting in-band capabilities, and
++ * supports more than disable mode.
++ */
++ if (pcs_ib_caps & LINK_INBAND_DISABLE)
++ neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++ else if (pcs_ib_caps & LINK_INBAND_ENABLE)
++ pcs_ib_only = true;
++ }
++
++ if (phy_ib_caps && phy_ib_caps != LINK_INBAND_DISABLE) {
++ /* PHY supports in-band capabilities, and supports
++ * more than disable mode.
++ */
++ if (phy_ib_caps & LINK_INBAND_DISABLE)
++ pl->phy_ib_mode = LINK_INBAND_DISABLE;
++ else if (phy_ib_caps & LINK_INBAND_BYPASS)
++ pl->phy_ib_mode = LINK_INBAND_BYPASS;
++ else if (phy_ib_caps & LINK_INBAND_ENABLE)
++ phy_ib_only = true;
++ }
++
++ /* 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 (pcs_ib_only || phy_ib_only)
++ phylink_warn(pl,
++ "firmware wants %s mode, but %s%s%s requires inband\n",
++ phylink_an_mode_str(mode),
++ pcs_ib_only ? "PCS" : "",
++ pcs_ib_only && phy_ib_only ? " and " : "",
++ phy_ib_only ? "PHY" : "");
++
++ neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++ } else if (type == INBAND_CISCO_SGMII || pl->phydev) {
++ /* For SGMII modes which are designed to be used with PHYs, or
++ * Base-X with a PHY, we try to use in-band mode where-ever
++ * possible. However, there are some PHYs e.g. BCM84881 which
++ * do not support in-band.
++ */
++ const unsigned int inband_ok = LINK_INBAND_ENABLE |
++ LINK_INBAND_BYPASS;
++ const unsigned int outband_ok = LINK_INBAND_DISABLE |
++ LINK_INBAND_BYPASS;
++ /* 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_ib_caps || pcs_ib_caps & inband_ok) &&
++ (!phy_ib_caps || phy_ib_caps & inband_ok)) {
++ /* In-band supported or unknown at both ends. Enable
++ * in-band mode with or without bypass at the PHY.
++ */
++ if (phy_ib_caps & LINK_INBAND_ENABLE)
++ pl->phy_ib_mode = LINK_INBAND_ENABLE;
++ else if (phy_ib_caps & LINK_INBAND_BYPASS)
++ pl->phy_ib_mode = LINK_INBAND_BYPASS;
++
++ neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
++ } else if ((!pcs_ib_caps || pcs_ib_caps & outband_ok) &&
++ (!phy_ib_caps || phy_ib_caps & outband_ok)) {
++ /* Either in-band not supported at at least one end.
++ * In-band bypass at the other end is possible.
++ */
++ if (phy_ib_caps & LINK_INBAND_DISABLE)
++ pl->phy_ib_mode = LINK_INBAND_DISABLE;
++ else if (phy_ib_caps & LINK_INBAND_BYPASS)
++ pl->phy_ib_mode = LINK_INBAND_BYPASS;
++
+ neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++ if (pl->phydev)
++ mode = MLO_AN_PHY;
++ } else {
++ /* invalid */
++ phylink_warn(pl, "%s: incompatible in-band capabilities, trying in-band",
++ phy_modes(interface));
++ neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
++ }
++ } else {
++ /* For Base-X without a PHY */
++ if (pcs_ib_caps == LINK_INBAND_DISABLE)
++ /* If the PCS doesn't support inband, then inband must
++ * be disabled.
++ */
++ neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
++ else if (pcs_ib_caps == LINK_INBAND_ENABLE)
++ /* If the PCS requires inband, then inband must always
++ * be enabled.
++ */
++ neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
+ 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;
+- break;
+-
+- default:
+- neg_mode = PHYLINK_PCS_NEG_NONE;
+- break;
+ }
+
+ pl->pcs_neg_mode = neg_mode;
+@@ -1324,6 +1451,13 @@ static void phylink_major_config(struct
+ ERR_PTR(err));
+ }
+
++ if (pl->phydev && pl->phy_ib_mode) {
++ err = phy_config_inband(pl->phydev, pl->phy_ib_mode);
++ if (err < 0)
++ phylink_err(pl, "phy_config_inband: %pe\n",
++ ERR_PTR(err));
++ }
++
+ if (pl->sfp_bus) {
+ rate_kbd = phylink_interface_signal_rate(state->interface);
+ if (rate_kbd)
--- /dev/null
+From 77ac9a8b2536e0eaca6c6f21070068458bf55981 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 3 Dec 2024 15:31:48 +0000
+Subject: [PATCH 13/13] net: phylink: remove phylink_phy_no_inband()
+
+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.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tIUsO-006IUt-KN@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 27 ++++++---------------------
+ 1 file changed, 6 insertions(+), 21 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -3320,10 +3320,11 @@ static phy_interface_t phylink_choose_sf
+ 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",
+@@ -3357,8 +3358,7 @@ static void phylink_sfp_set_config(struc
+ 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(support1);
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(support);
+@@ -3398,7 +3398,7 @@ static int phylink_sfp_config_phy(struct
+ if (ret) {
+ phylink_err(pl,
+ "validation of %s/%s with support %*pb failed: %pe\n",
+- phylink_an_mode_str(mode),
++ phylink_an_mode_str(pl->req_link_an_mode),
+ phy_modes(config.interface),
+ __ETHTOOL_LINK_MODE_MASK_NBITS, support,
+ ERR_PTR(ret));
+@@ -3407,7 +3407,7 @@ static int phylink_sfp_config_phy(struct
+
+ pl->link_port = pl->sfp_port;
+
+- phylink_sfp_set_config(pl, mode, support, &config);
++ phylink_sfp_set_config(pl, support, &config);
+
+ return 0;
+ }
+@@ -3481,7 +3481,7 @@ static int phylink_sfp_config_optical(st
+
+ 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;
+ }
+@@ -3552,20 +3552,10 @@ static void phylink_sfp_link_up(void *up
+ 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;
+ phy_interface_t interface;
+- u8 mode;
+ int ret;
+
+ /*
+@@ -3577,17 +3567,12 @@ static int phylink_sfp_connect_phy(void
+ */
+ 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 */
+- ret = phylink_sfp_config_phy(pl, mode, phy);
++ ret = phylink_sfp_config_phy(pl, phy);
+ if (ret < 0)
+ return ret;
+
--- /dev/null
+From 6561f0e547be221f411fda5eddfcc5bd8bb058a5 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 5 Dec 2024 09:42:24 +0000
+Subject: [PATCH 1/3] net: pcs: pcs-lynx: implement pcs_inband_caps() method
+
+Report the PCS in-band capabilities to phylink for the Lynx PCS.
+
+Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tJ8NM-006L5J-AH@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/pcs/pcs-lynx.c | 22 ++++++++++++++++++++++
+ 1 file changed, 22 insertions(+)
+
+--- a/drivers/net/pcs/pcs-lynx.c
++++ b/drivers/net/pcs/pcs-lynx.c
+@@ -35,6 +35,27 @@ enum sgmii_speed {
+ #define phylink_pcs_to_lynx(pl_pcs) container_of((pl_pcs), struct lynx_pcs, pcs)
+ #define lynx_to_phylink_pcs(lynx) (&(lynx)->pcs)
+
++static unsigned int lynx_pcs_inband_caps(struct phylink_pcs *pcs,
++ phy_interface_t interface)
++{
++ switch (interface) {
++ case PHY_INTERFACE_MODE_1000BASEX:
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_QSGMII:
++ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++ case PHY_INTERFACE_MODE_10GBASER:
++ case PHY_INTERFACE_MODE_2500BASEX:
++ return LINK_INBAND_DISABLE;
++
++ case PHY_INTERFACE_MODE_USXGMII:
++ return LINK_INBAND_ENABLE;
++
++ default:
++ return 0;
++ }
++}
++
+ static void lynx_pcs_get_state_usxgmii(struct mdio_device *pcs,
+ struct phylink_link_state *state)
+ {
+@@ -307,6 +328,7 @@ static void lynx_pcs_link_up(struct phyl
+ }
+
+ static const struct phylink_pcs_ops lynx_pcs_phylink_ops = {
++ .pcs_inband_caps = lynx_pcs_inband_caps,
+ .pcs_get_state = lynx_pcs_get_state,
+ .pcs_config = lynx_pcs_config,
+ .pcs_an_restart = lynx_pcs_an_restart,
--- /dev/null
+From 520d29bdda86915b3caf8c72825a574bff212553 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 5 Dec 2024 09:42:29 +0000
+Subject: [PATCH 2/3] net: pcs: pcs-mtk-lynxi: implement pcs_inband_caps()
+ method
+
+Report the PCS in-band capabilities to phylink for the LynxI PCS.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tJ8NR-006L5P-E3@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/pcs/pcs-mtk-lynxi.c | 16 ++++++++++++++++
+ 1 file changed, 16 insertions(+)
+
+--- a/drivers/net/pcs/pcs-mtk-lynxi.c
++++ b/drivers/net/pcs/pcs-mtk-lynxi.c
+@@ -88,6 +88,21 @@ static struct mtk_pcs_lynxi *pcs_to_mtk_
+ return container_of(pcs, struct mtk_pcs_lynxi, pcs);
+ }
+
++static unsigned int mtk_pcs_lynxi_inband_caps(struct phylink_pcs *pcs,
++ phy_interface_t interface)
++{
++ switch (interface) {
++ case PHY_INTERFACE_MODE_1000BASEX:
++ case PHY_INTERFACE_MODE_2500BASEX:
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_QSGMII:
++ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++ default:
++ return 0;
++ }
++}
++
+ static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs,
+ struct phylink_link_state *state)
+ {
+@@ -241,6 +256,7 @@ static void mtk_pcs_lynxi_disable(struct
+ }
+
+ static const struct phylink_pcs_ops mtk_pcs_lynxi_ops = {
++ .pcs_inband_caps = mtk_pcs_lynxi_inband_caps,
+ .pcs_get_state = mtk_pcs_lynxi_get_state,
+ .pcs_config = mtk_pcs_lynxi_config,
+ .pcs_an_restart = mtk_pcs_lynxi_restart_an,
--- /dev/null
+From 484d0170d6c6bbb5213d037664e9a551f793bacd Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Thu, 5 Dec 2024 09:42:34 +0000
+Subject: [PATCH 3/3] net: pcs: xpcs: implement pcs_inband_caps() method
+
+Report the PCS inband capabilities to phylink for XPCS.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tJ8NW-006L5V-I9@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/pcs/pcs-xpcs.c | 28 ++++++++++++++++++++++++++++
+ 1 file changed, 28 insertions(+)
+
+--- a/drivers/net/pcs/pcs-xpcs.c
++++ b/drivers/net/pcs/pcs-xpcs.c
+@@ -628,6 +628,33 @@ static int xpcs_validate(struct phylink_
+ return 0;
+ }
+
++static unsigned int xpcs_inband_caps(struct phylink_pcs *pcs,
++ phy_interface_t interface)
++{
++ struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
++ const struct xpcs_compat *compat;
++
++ compat = xpcs_find_compat(xpcs->id, interface);
++ if (!compat)
++ return 0;
++
++ switch (compat->an_mode) {
++ case DW_AN_C73:
++ return LINK_INBAND_ENABLE;
++
++ case DW_AN_C37_SGMII:
++ case DW_AN_C37_1000BASEX:
++ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++ case DW_10GBASER:
++ case DW_2500BASEX:
++ return LINK_INBAND_DISABLE;
++
++ default:
++ return 0;
++ }
++}
++
+ void xpcs_get_interfaces(struct dw_xpcs *xpcs, unsigned long *interfaces)
+ {
+ int i, j;
+@@ -1331,6 +1358,7 @@ static const struct xpcs_id xpcs_id_list
+
+ static const struct phylink_pcs_ops xpcs_phylink_ops = {
+ .pcs_validate = xpcs_validate,
++ .pcs_inband_caps = xpcs_inband_caps,
+ .pcs_config = xpcs_config,
+ .pcs_get_state = xpcs_get_state,
+ .pcs_an_restart = xpcs_an_restart,
refcount_t refcnt;
unsigned long flags;
size_t priv_size;
-@@ -1966,10 +1967,10 @@ int phy_ethtool_get_link_ksettings(struc
+@@ -2000,10 +2001,10 @@ int phy_ethtool_get_link_ksettings(struc
int phy_ethtool_set_link_ksettings(struct net_device *ndev,
const struct ethtool_link_ksettings *cmd);
int phy_ethtool_nway_reset(struct net_device *ndev);
int __init mdio_bus_init(void);
void mdio_bus_exit(void);
-@@ -1992,46 +1993,65 @@ int __phy_hwtstamp_set(struct phy_device
+@@ -2026,46 +2027,65 @@ int __phy_hwtstamp_set(struct phy_device
struct kernel_hwtstamp_config *config,
struct netlink_ext_ack *extack);
* @regnum: register number to modify
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -2054,6 +2054,22 @@ static inline int __phy_package_write(st
+@@ -2088,6 +2088,22 @@ static inline int __phy_package_write(st
return __mdiobus_write(phydev->mdio.bus, addr, regnum, val);
}
refcount_t refcnt;
unsigned long flags;
size_t priv_size;
-@@ -1968,9 +1971,12 @@ int phy_ethtool_set_link_ksettings(struc
+@@ -2002,9 +2005,12 @@ int phy_ethtool_set_link_ksettings(struc
const struct ethtool_link_ksettings *cmd);
int phy_ethtool_nway_reset(struct net_device *ndev);
int phy_package_join(struct phy_device *phydev, int base_addr, size_t priv_size);
}
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -1846,7 +1846,7 @@ int genphy_write_mmd_unsupported(struct
+@@ -1880,7 +1880,7 @@ int genphy_write_mmd_unsupported(struct
/* Clause 37 */
int genphy_c37_config_aneg(struct phy_device *phydev);
}
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -972,7 +972,8 @@ struct phy_driver {
+@@ -1003,7 +1003,8 @@ struct phy_driver {
* driver for the given phydev. If NULL, matching is based on
* phy_id and phy_id_mask.
*/
static ssize_t
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -1812,6 +1812,9 @@ char *phy_attached_info_irq(struct phy_d
+@@ -1846,6 +1846,9 @@ char *phy_attached_info_irq(struct phy_d
__malloc;
void phy_attached_info(struct phy_device *phydev);
cdev->brightness_set_blocking = phy_led_set_brightness;
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -867,6 +867,15 @@ struct phy_led {
+@@ -885,6 +885,15 @@ struct phy_led {
#define to_phy_led(d) container_of(d, struct phy_led, led_cdev)
/**
* struct phy_driver - Driver structure for a particular PHY type
*
-@@ -1144,6 +1153,19 @@ struct phy_driver {
+@@ -1175,6 +1184,19 @@ struct phy_driver {
int (*led_hw_control_get)(struct phy_device *dev, u8 index,
unsigned long *rules);
if (!phydev->drv->led_polarity_set)
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -869,8 +869,9 @@ struct phy_led {
+@@ -887,8 +887,9 @@ struct phy_led {
/* Modes for PHY LED configuration */
enum phy_led_modes {
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -712,18 +712,16 @@ static int phylink_validate_mask(struct
+@@ -732,18 +732,16 @@ static int phylink_validate_mask(struct
__ETHTOOL_DECLARE_LINK_MODE_MASK(all_s) = { 0, };
__ETHTOOL_DECLARE_LINK_MODE_MASK(s);
struct phylink_link_state t;
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -704,26 +704,44 @@ static int phylink_validate_mac_and_pcs(
+@@ -724,26 +724,44 @@ static int phylink_validate_mac_and_pcs(
return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
}
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -704,7 +704,7 @@ static int phylink_validate_mac_and_pcs(
+@@ -724,7 +724,7 @@ static int phylink_validate_mac_and_pcs(
return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
}
const unsigned long *supported,
const struct phylink_link_state *state,
phy_interface_t interface,
-@@ -719,6 +719,9 @@ static void phylink_validate_one(struct
+@@ -739,6 +739,9 @@ static void phylink_validate_one(struct
tmp_state = *state;
tmp_state.interface = interface;
if (!phylink_validate_mac_and_pcs(pl, tmp_supported, &tmp_state)) {
phylink_dbg(pl, " interface %u (%s) rate match %s supports %*pbl\n",
interface, phy_modes(interface),
-@@ -740,7 +743,7 @@ static int phylink_validate_mask(struct
+@@ -760,7 +763,7 @@ static int phylink_validate_mask(struct
int interface;
for_each_set_bit(interface, interfaces, PHY_INTERFACE_MODE_MAX)
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -734,7 +734,8 @@ static void phylink_validate_one(struct
+@@ -754,7 +754,8 @@ static void phylink_validate_one(struct
}
}
struct phylink_link_state *state,
const unsigned long *interfaces)
{
-@@ -743,7 +744,7 @@ static int phylink_validate_mask(struct
+@@ -763,7 +764,7 @@ static int phylink_validate_mask(struct
int interface;
for_each_set_bit(interface, interfaces, PHY_INTERFACE_MODE_MAX)
all_s, all_adv);
linkmode_copy(supported, all_s);
-@@ -758,7 +759,8 @@ static int phylink_validate(struct phyli
+@@ -778,7 +779,8 @@ static int phylink_validate(struct phyli
const unsigned long *interfaces = pl->config->supported_interfaces;
if (state->interface == PHY_INTERFACE_MODE_NA)
if (!test_bit(state->interface, interfaces))
return -EINVAL;
-@@ -3194,7 +3196,8 @@ static int phylink_sfp_config_optical(st
+@@ -3465,7 +3467,8 @@ static int phylink_sfp_config_optical(st
/* For all the interfaces that are supported, reduce the sfp_support
* mask to only those link modes that can be supported.
*/
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -1775,6 +1775,35 @@ static void phylink_phy_change(struct ph
+@@ -2019,6 +2019,35 @@ static void phylink_phy_change(struct ph
phylink_pause_to_str(pl->phy_state.pause));
}
static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
phy_interface_t interface)
{
-@@ -1795,32 +1824,9 @@ static int phylink_bringup_phy(struct ph
+@@ -2039,32 +2068,9 @@ static int phylink_bringup_phy(struct ph
memset(&config, 0, sizeof(config));
linkmode_copy(supported, phy->supported);
linkmode_copy(config.advertising, phy->advertising);
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -121,6 +121,19 @@ do { \
+@@ -123,6 +123,19 @@ do { \
})
#endif
/**
* phylink_set_port_modes() - set the port type modes in the ethtool mask
* @mask: ethtool link mode mask
-@@ -1779,6 +1792,47 @@ static int phylink_validate_phy(struct p
+@@ -2023,6 +2036,47 @@ static int phylink_validate_phy(struct p
unsigned long *supported,
struct phylink_link_state *state)
{
/* Check whether we would use rate matching for the proposed interface
* mode.
*/
-@@ -3047,19 +3101,6 @@ static void phylink_sfp_detach(void *ups
+@@ -3318,19 +3372,6 @@ static void phylink_sfp_detach(void *ups
pl->netdev->sfp_bus = NULL;
}
--- a/drivers/net/pcs/pcs-mtk-lynxi.c
+++ b/drivers/net/pcs/pcs-mtk-lynxi.c
-@@ -114,14 +114,23 @@ static void mtk_pcs_lynxi_get_state(stru
+@@ -129,14 +129,23 @@ static void mtk_pcs_lynxi_get_state(stru
struct phylink_link_state *state)
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
}
static void mtk_sgmii_reset(struct mtk_pcs_lynxi *mpcs)
-@@ -142,7 +151,7 @@ static int mtk_pcs_lynxi_config(struct p
+@@ -157,7 +166,7 @@ static int mtk_pcs_lynxi_config(struct p
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
bool mode_changed = false, changed;
int advertise, link_timer;
advertise = phylink_mii_c22_pcs_encode_advertisement(interface,
-@@ -165,9 +174,8 @@ static int mtk_pcs_lynxi_config(struct p
+@@ -180,9 +189,8 @@ static int mtk_pcs_lynxi_config(struct p
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
if (interface == PHY_INTERFACE_MODE_SGMII)
sgm_mode |= SGMII_SPEED_DUPLEX_AN;
--- a/drivers/net/pcs/pcs-mtk-lynxi.c
+++ b/drivers/net/pcs/pcs-mtk-lynxi.c
-@@ -114,14 +114,23 @@ static void mtk_pcs_lynxi_get_state(stru
+@@ -129,14 +129,23 @@ static void mtk_pcs_lynxi_get_state(stru
struct phylink_link_state *state)
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
}
static void mtk_sgmii_reset(struct mtk_pcs_lynxi *mpcs)
-@@ -142,7 +151,7 @@ static int mtk_pcs_lynxi_config(struct p
+@@ -157,7 +166,7 @@ static int mtk_pcs_lynxi_config(struct p
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
bool mode_changed = false, changed;
int advertise, link_timer;
advertise = phylink_mii_c22_pcs_encode_advertisement(interface,
-@@ -165,9 +174,8 @@ static int mtk_pcs_lynxi_config(struct p
+@@ -180,9 +189,8 @@ static int mtk_pcs_lynxi_config(struct p
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
if (interface == PHY_INTERFACE_MODE_SGMII)
sgm_mode |= SGMII_SPEED_DUPLEX_AN;
sysfs_remove_link(&dev->dev.kobj, "phydev");
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -996,6 +996,12 @@ struct phy_driver {
+@@ -1027,6 +1027,12 @@ struct phy_driver {
/** @handle_interrupt: Override default interrupt handling */
irqreturn_t (*handle_interrupt)(struct phy_device *phydev);
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -2086,7 +2086,7 @@ int phylink_fwnode_phy_connect(struct ph
+@@ -2264,7 +2264,7 @@ int phylink_fwnode_phy_connect(struct ph
{
struct fwnode_handle *phy_fwnode;
struct phy_device *phy_dev;
/* Fixed links and 802.3z are handled without needing a PHY */
if (pl->cfg_link_an_mode == MLO_AN_FIXED ||
-@@ -2116,6 +2116,25 @@ int phylink_fwnode_phy_connect(struct ph
+@@ -2294,6 +2294,25 @@ int phylink_fwnode_phy_connect(struct ph
if (pl->config->mac_requires_rxc)
flags |= PHY_F_RXC_ALWAYS_ON;
static struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs)
{
return container_of(pcs, struct mtk_pcs_lynxi, pcs);
-@@ -102,6 +124,17 @@ static void mtk_pcs_lynxi_get_state(stru
+@@ -117,6 +139,17 @@ static void mtk_pcs_lynxi_get_state(stru
FIELD_GET(SGMII_LPA, adv));
}
static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
-@@ -147,6 +180,7 @@ static int mtk_pcs_lynxi_config(struct p
+@@ -162,6 +195,7 @@ static int mtk_pcs_lynxi_config(struct p
SGMII_PHYA_PWD);
/* Reset SGMII PCS state */
regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0,
SGMII_SW_RESET);
-@@ -233,10 +267,29 @@ static void mtk_pcs_lynxi_link_up(struct
+@@ -248,10 +282,29 @@ static void mtk_pcs_lynxi_link_up(struct
}
}
mpcs->interface = PHY_INTERFACE_MODE_NA;
}
-@@ -246,11 +299,12 @@ static const struct phylink_pcs_ops mtk_
+@@ -262,11 +315,12 @@ static const struct phylink_pcs_ops mtk_
.pcs_an_restart = mtk_pcs_lynxi_restart_an,
.pcs_link_up = mtk_pcs_lynxi_link_up,
.pcs_disable = mtk_pcs_lynxi_disable,
{
struct mtk_pcs_lynxi *mpcs;
u32 id, ver;
-@@ -258,29 +312,33 @@ struct phylink_pcs *mtk_pcs_lynxi_create
+@@ -274,29 +328,33 @@ struct phylink_pcs *mtk_pcs_lynxi_create
ret = regmap_read(regmap, SGMSYS_PCS_DEVICE_ID, &id);
if (ret < 0)
mpcs->ana_rgc3 = ana_rgc3;
mpcs->regmap = regmap;
-@@ -291,6 +349,13 @@ struct phylink_pcs *mtk_pcs_lynxi_create
+@@ -307,6 +365,13 @@ struct phylink_pcs *mtk_pcs_lynxi_create
mpcs->interface = PHY_INTERFACE_MODE_NA;
return &mpcs->pcs;
}
EXPORT_SYMBOL(mtk_pcs_lynxi_create);
-@@ -303,5 +368,142 @@ void mtk_pcs_lynxi_destroy(struct phylin
+@@ -319,5 +384,142 @@ void mtk_pcs_lynxi_destroy(struct phylin
}
EXPORT_SYMBOL(mtk_pcs_lynxi_destroy);
sysfs_remove_link(&dev->dev.kobj, "phydev");
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -977,6 +977,12 @@ struct phy_driver {
+@@ -1008,6 +1008,12 @@ struct phy_driver {
/** @handle_interrupt: Override default interrupt handling */
irqreturn_t (*handle_interrupt)(struct phy_device *phydev);
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -2017,7 +2017,7 @@ int phylink_fwnode_phy_connect(struct ph
+@@ -2261,7 +2261,7 @@ int phylink_fwnode_phy_connect(struct ph
{
struct fwnode_handle *phy_fwnode;
struct phy_device *phy_dev;
/* Fixed links and 802.3z are handled without needing a PHY */
if (pl->cfg_link_an_mode == MLO_AN_FIXED ||
-@@ -2044,6 +2044,25 @@ int phylink_fwnode_phy_connect(struct ph
+@@ -2288,6 +2288,25 @@ int phylink_fwnode_phy_connect(struct ph
pl->link_config.interface = pl->link_interface;
}
static struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs)
{
return container_of(pcs, struct mtk_pcs_lynxi, pcs);
-@@ -102,6 +124,17 @@ static void mtk_pcs_lynxi_get_state(stru
+@@ -117,6 +139,17 @@ static void mtk_pcs_lynxi_get_state(stru
FIELD_GET(SGMII_LPA, adv));
}
static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
-@@ -147,6 +180,7 @@ static int mtk_pcs_lynxi_config(struct p
+@@ -162,6 +195,7 @@ static int mtk_pcs_lynxi_config(struct p
SGMII_PHYA_PWD);
/* Reset SGMII PCS state */
regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0,
SGMII_SW_RESET);
-@@ -233,10 +267,29 @@ static void mtk_pcs_lynxi_link_up(struct
+@@ -248,10 +282,29 @@ static void mtk_pcs_lynxi_link_up(struct
}
}
mpcs->interface = PHY_INTERFACE_MODE_NA;
}
-@@ -246,11 +299,12 @@ static const struct phylink_pcs_ops mtk_
+@@ -262,11 +315,12 @@ static const struct phylink_pcs_ops mtk_
.pcs_an_restart = mtk_pcs_lynxi_restart_an,
.pcs_link_up = mtk_pcs_lynxi_link_up,
.pcs_disable = mtk_pcs_lynxi_disable,
{
struct mtk_pcs_lynxi *mpcs;
u32 id, ver;
-@@ -258,29 +312,33 @@ struct phylink_pcs *mtk_pcs_lynxi_create
+@@ -274,29 +328,33 @@ struct phylink_pcs *mtk_pcs_lynxi_create
ret = regmap_read(regmap, SGMSYS_PCS_DEVICE_ID, &id);
if (ret < 0)
mpcs->ana_rgc3 = ana_rgc3;
mpcs->regmap = regmap;
-@@ -291,6 +349,13 @@ struct phylink_pcs *mtk_pcs_lynxi_create
+@@ -307,6 +365,13 @@ struct phylink_pcs *mtk_pcs_lynxi_create
mpcs->interface = PHY_INTERFACE_MODE_NA;
return &mpcs->pcs;
}
EXPORT_SYMBOL(mtk_pcs_lynxi_create);
-@@ -303,4 +368,142 @@ void mtk_pcs_lynxi_destroy(struct phylin
+@@ -319,4 +384,142 @@ void mtk_pcs_lynxi_destroy(struct phylin
}
EXPORT_SYMBOL(mtk_pcs_lynxi_destroy);
case PHY_INTERFACE_MODE_QUSGMII:
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -230,6 +230,7 @@ static int phylink_interface_max_speed(p
+@@ -250,6 +250,7 @@ static int phylink_interface_max_speed(p
case PHY_INTERFACE_MODE_GMII:
return SPEED_1000;
case PHY_INTERFACE_MODE_2500BASEX:
case PHY_INTERFACE_MODE_10G_QXGMII:
return SPEED_2500;
-@@ -544,6 +545,7 @@ static unsigned long phylink_get_capabil
+@@ -564,6 +565,7 @@ static unsigned long phylink_get_capabil
break;
case PHY_INTERFACE_MODE_2500BASEX:
case PHY_INTERFACE_MODE_QUSGMII:
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -231,6 +231,7 @@ static int phylink_interface_max_speed(p
+@@ -251,6 +251,7 @@ static int phylink_interface_max_speed(p
return SPEED_1000;
case PHY_INTERFACE_MODE_2500BASEX:
return SPEED_2500;
case PHY_INTERFACE_MODE_5GBASER:
-@@ -539,6 +540,7 @@ unsigned long phylink_get_capabilities(p
+@@ -559,6 +560,7 @@ unsigned long phylink_get_capabilities(p
break;
case PHY_INTERFACE_MODE_2500BASEX:
+++ /dev/null
-From: "Russell King (Oracle)" <linux@armlinux.org.uk>
-To: Andrew Lunn <andrew@lunn.ch>, Heiner Kallweit <hkallweit1@gmail.com>
-Cc: Alexander Couzens <lynxis@fe80.eu>,
- Andrew Lunn <andrew+netdev@lunn.ch>,
- AngeloGioacchino Del Regno
- <angelogioacchino.delregno@collabora.com>,
- Broadcom internal kernel review list
- <bcm-kernel-feedback-list@broadcom.com>,
- Daniel Golle <daniel@makrotopia.org>,
- "David S. Miller" <davem@davemloft.net>,
- Eric Dumazet <edumazet@google.com>,
- Florian Fainelli <florian.fainelli@broadcom.com>,
- Ioana Ciornei <ioana.ciornei@nxp.com>,
- Jakub Kicinski <kuba@kernel.org>,
- Jose Abreu <Jose.Abreu@synopsys.com>,
- linux-arm-kernel@lists.infradead.org,
- linux-mediatek@lists.infradead.org,
- Marcin Wojtas <marcin.s.wojtas@gmail.com>,
- Matthias Brugger <matthias.bgg@gmail.com>,
- netdev@vger.kernel.org, Paolo Abeni <pabeni@redhat.com>
-Subject: [PATCH RFC net-next 00/16] net: add negotiation of in-band capabilities
-Date: Tue, 26 Nov 2024 09:23:48 +0000 [thread overview]
-Message-ID: <Z0WTpE8wkpjMiv_J@shell.armlinux.org.uk> (raw)
-
-Hi,
-
-Yes, this is one patch over the limit of 15 for netdev - but I think it's
-important to include the last patch to head off review comments like "why
-don't you remove phylink_phy_no_inband() in this series?"
-
-Phylink's handling of in-band has been deficient for a long time, and
-people keep hitting problems with it. Notably, situations with the way-
-to-late standardized 2500Base-X and whether that should or should not
-have in-band enabled. We have also been carrying a hack in the form of
-phylink_phy_no_inband() for a PHY that has been used on a SFP module,
-but has no in-band capabilities, not even for SGMII.
-
-When phylink is trying to operate in in-band mode, this series will look
-at the capabilities of the MAC-side PCS and PHY, and work out whether
-in-band can or should be used, programming the PHY as appropriate. This
-includes in-band bypass mode at the PHY.
-
-We don't... yet... support that on the MAC side PCS, because that
-requires yet more complexity.
-
-Patch 1 passes struct phylink and struct phylink_pcs into
-phylink_pcs_neg_mode() so we can look at more state in this function in
-a future patch.
-
-Patch 2 splits "cur_link_an_mode" (the MLO_AN_* mode) into two separate
-purposes - a requested and an active mode. The active mode is the one
-we will be using for the MAC, which becomes dependent on the result of
-in-band negotiation.
-
-Patch 3 adds debug to phylink_major_config() so we can see what is going
-on with the requested and active AN modes.
-
-Patch 4 adds to phylib a method to get the in-band capabilities of the
-PHY from phylib. Patches 5 and 6 add implementations for BCM84881 and
-some Marvell PHYs found on SFPs.
-
-Patch 7 adds to phylib a method to configure the PHY in-band signalling,
-and patch 8 implements it for those Marvell PHYs that support the method
-in patch 4.
-
-Patch 9 does the same as patch 4 but for the MAC-side PCS, with patches
-10 through 14 adding support to several PCS.
-
-Patch 15 adds the code to phylink_pcs_neg_mode() which looks at the
-capabilities, and works out whether to use in-band or out-band mode for
-driving the link between the MAC PCS and PHY.
-
-Patch 16 removes the phylink_phy_no_inband() hack now that we are
-publishing the in-band capabilities from the BCM84881 PHY driver.
-
- drivers/net/ethernet/marvell/mvneta.c | 27 +-
- drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c | 25 +-
- drivers/net/pcs/pcs-lynx.c | 22 ++
- drivers/net/pcs/pcs-mtk-lynxi.c | 16 ++
- drivers/net/pcs/pcs-xpcs.c | 28 ++
- drivers/net/phy/bcm84881.c | 10 +
- drivers/net/phy/marvell.c | 48 ++++
- drivers/net/phy/phy.c | 52 ++++
- drivers/net/phy/phylink.c | 352 +++++++++++++++++++-----
- include/linux/phy.h | 34 +++
- include/linux/phylink.h | 17 ++
- 11 files changed, 539 insertions(+), 92 deletions(-)
-
---- 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);
-
-@@ -74,6 +75,7 @@ struct phylink {
-
- struct mutex state_mutex;
- struct phylink_link_state phy_state;
-+ unsigned int phy_ib_mode;
- struct work_struct resolve;
- unsigned int pcs_neg_mode;
- unsigned int pcs_state;
-@@ -175,6 +177,24 @@ static const char *phylink_an_mode_str(u
- 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) {
-@@ -988,6 +1008,15 @@ static void phylink_resolve_an_pause(str
- }
- }
-
-+static unsigned int phylink_pcs_inband_caps(struct phylink_pcs *pcs,
-+ phy_interface_t interface)
-+{
-+ if (pcs && pcs->ops->pcs_inband_caps)
-+ return pcs->ops->pcs_inband_caps(pcs, interface);
-+
-+ return 0;
-+}
-+
- static void phylink_pcs_pre_config(struct phylink_pcs *pcs,
- phy_interface_t interface)
- {
-@@ -1041,6 +1070,24 @@ static void phylink_pcs_link_up(struct p
- 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_inband_caps(struct phylink *pl,
-+ phy_interface_t interface)
-+{
-+ struct phylink_pcs *pcs;
-+
-+ if (!pl->mac_ops->mac_select_pcs)
-+ return 0;
-+
-+ pcs = pl->mac_ops->mac_select_pcs(pl->config, interface);
-+ if (!pcs)
-+ return 0;
-+
-+ return phylink_pcs_inband_caps(pcs, interface);
-+}
-+
- static void phylink_pcs_poll_stop(struct phylink *pl)
- {
- if (pl->cfg_link_an_mode == MLO_AN_INBAND)
-@@ -1082,13 +1129,13 @@ static void phylink_mac_config(struct ph
-
- 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)
-@@ -1096,13 +1143,14 @@ static void phylink_pcs_an_restart(struc
- 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);
- }
-
- /**
- * phylink_pcs_neg_mode() - helper to determine PCS inband mode
-- * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
-+ * @pl: a pointer to a &struct phylink returned from phylink_create()
-+ * @pcs: a pointer to &struct phylink_pcs
- * @interface: interface mode to be used
- * @advertising: adertisement ethtool link mode mask
- *
-@@ -1119,11 +1167,21 @@ static void phylink_pcs_an_restart(struc
- * 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,
-- phy_interface_t interface,
-- const unsigned long *advertising)
-+static void phylink_pcs_neg_mode(struct phylink *pl, struct phylink_pcs *pcs,
-+ phy_interface_t interface,
-+ const unsigned long *advertising)
- {
-- unsigned int neg_mode;
-+ unsigned int pcs_ib_caps = 0;
-+ unsigned int phy_ib_caps = 0;
-+ unsigned int neg_mode, mode;
-+ enum {
-+ INBAND_CISCO_SGMII,
-+ INBAND_BASEX,
-+ } type;
-+
-+ mode = pl->req_link_an_mode;
-+
-+ pl->phy_ib_mode = 0;
-
- switch (interface) {
- case PHY_INTERFACE_MODE_SGMII:
-@@ -1136,10 +1194,7 @@ static unsigned int phylink_pcs_neg_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:
-@@ -1150,21 +1205,143 @@ static unsigned int phylink_pcs_neg_mode
- * as well, but drivers may not support this, so may
- * need to override this.
- */
-- if (!phylink_autoneg_inband(mode))
-+ type = INBAND_BASEX;
-+ break;
-+
-+ default:
-+ pl->pcs_neg_mode = PHYLINK_PCS_NEG_NONE;
-+ pl->act_link_an_mode = mode;
-+ return;
-+ }
-+
-+ if (pcs)
-+ pcs_ib_caps = phylink_pcs_inband_caps(pcs, interface);
-+
-+ if (pl->phydev)
-+ phy_ib_caps = phy_inband_caps(pl->phydev, interface);
-+
-+ phylink_dbg(pl, "interface %s inband modes: pcs=%02x phy=%02x\n",
-+ phy_modes(interface), pcs_ib_caps, phy_ib_caps);
-+
-+ if (!phylink_autoneg_inband(mode)) {
-+ bool pcs_ib_only = false;
-+ bool phy_ib_only = false;
-+
-+ if (pcs_ib_caps && pcs_ib_caps != LINK_INBAND_DISABLE) {
-+ /* PCS supports reporting in-band capabilities, and
-+ * supports more than disable mode.
-+ */
-+ if (pcs_ib_caps & LINK_INBAND_DISABLE)
-+ neg_mode = PHYLINK_PCS_NEG_OUTBAND;
-+ else if (pcs_ib_caps & LINK_INBAND_ENABLE)
-+ pcs_ib_only = true;
-+ }
-+
-+ if (phy_ib_caps && phy_ib_caps != LINK_INBAND_DISABLE) {
-+ /* PHY supports in-band capabilities, and supports
-+ * more than disable mode.
-+ */
-+ if (phy_ib_caps & LINK_INBAND_DISABLE)
-+ pl->phy_ib_mode = LINK_INBAND_DISABLE;
-+ else if (phy_ib_caps & LINK_INBAND_BYPASS)
-+ pl->phy_ib_mode = LINK_INBAND_BYPASS;
-+ else if (phy_ib_caps & LINK_INBAND_ENABLE)
-+ phy_ib_only = true;
-+ }
-+
-+ /* 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 (pcs_ib_only || phy_ib_only)
-+ phylink_warn(pl,
-+ "firmware wants %s mode, but %s%s%s requires inband\n",
-+ phylink_an_mode_str(mode),
-+ pcs_ib_only ? "PCS" : "",
-+ pcs_ib_only && phy_ib_only ? " and " : "",
-+ phy_ib_only ? "PHY" : "");
-+
-+ neg_mode = PHYLINK_PCS_NEG_OUTBAND;
-+ } else if (type == INBAND_CISCO_SGMII || pl->phydev) {
-+ /* For SGMII modes which are designed to be used with PHYs, or
-+ * Base-X with a PHY, we try to use in-band mode where-ever
-+ * possible. However, there are some PHYs e.g. BCM84881 which
-+ * do not support in-band.
-+ */
-+ const unsigned int inband_ok = LINK_INBAND_ENABLE |
-+ LINK_INBAND_BYPASS;
-+ const unsigned int outband_ok = LINK_INBAND_DISABLE |
-+ LINK_INBAND_BYPASS;
-+ /* 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_ib_caps || pcs_ib_caps & inband_ok) &&
-+ (!phy_ib_caps || phy_ib_caps & inband_ok)) {
-+ /* In-band supported or unknown at both ends. Enable
-+ * in-band mode with or without bypass at the PHY.
-+ */
-+ if (phy_ib_caps & LINK_INBAND_ENABLE)
-+ pl->phy_ib_mode = LINK_INBAND_ENABLE;
-+ else if (phy_ib_caps & LINK_INBAND_BYPASS)
-+ pl->phy_ib_mode = LINK_INBAND_BYPASS;
-+
-+ neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
-+ } else if ((!pcs_ib_caps || pcs_ib_caps & outband_ok) &&
-+ (!phy_ib_caps || phy_ib_caps & outband_ok)) {
-+ /* Either in-band not supported at at least one end.
-+ * In-band bypass at the other end is possible.
-+ */
-+ if (phy_ib_caps & LINK_INBAND_DISABLE)
-+ pl->phy_ib_mode = LINK_INBAND_DISABLE;
-+ else if (phy_ib_caps & LINK_INBAND_BYPASS)
-+ pl->phy_ib_mode = LINK_INBAND_BYPASS;
-+
- neg_mode = PHYLINK_PCS_NEG_OUTBAND;
-+ if (pl->phydev)
-+ mode = MLO_AN_PHY;
-+ } else {
-+ /* invalid */
-+ phylink_warn(pl, "%s: incompatible in-band capabilities, trying in-band",
-+ phy_modes(interface));
-+ neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
-+ }
-+ } else {
-+ /* For Base-X without a PHY */
-+ if (pcs_ib_caps == LINK_INBAND_DISABLE)
-+ /* If the PCS doesn't support inband, then inband must
-+ * be disabled.
-+ */
-+ neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
-+ else if (pcs_ib_caps == LINK_INBAND_ENABLE)
-+ /* If the PCS requires inband, then inband must always
-+ * be enabled.
-+ */
-+ neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
- 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;
-- break;
--
-- default:
-- neg_mode = PHYLINK_PCS_NEG_NONE;
-- break;
- }
-
-- return neg_mode;
-+ pl->pcs_neg_mode = neg_mode;
-+ pl->act_link_an_mode = mode;
- }
-
- static void phylink_major_config(struct phylink *pl, bool restart,
-@@ -1176,11 +1353,9 @@ static void phylink_major_config(struct
- unsigned int neg_mode;
- int err;
-
-- phylink_dbg(pl, "major config %s\n", phy_modes(state->interface));
--
-- pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
-- state->interface,
-- state->advertising);
-+ phylink_dbg(pl, "major config, requested %s/%s\n",
-+ phylink_an_mode_str(pl->req_link_an_mode),
-+ phy_modes(state->interface));
-
- if (pl->using_mac_select_pcs) {
- pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
-@@ -1194,10 +1369,17 @@ static void phylink_major_config(struct
- pcs_changed = pcs && pl->pcs != pcs;
- }
-
-+ phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
-+
-+ 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);
-
- 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",
-@@ -1231,7 +1413,7 @@ static void phylink_major_config(struct
- 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;
-
-@@ -1247,13 +1429,20 @@ static void phylink_major_config(struct
- 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",
- ERR_PTR(err));
- }
-
-+ if (pl->phydev && pl->phy_ib_mode) {
-+ err = phy_config_inband(pl->phydev, pl->phy_ib_mode);
-+ if (err < 0)
-+ phylink_err(pl, "phy_config_inband: %pe\n",
-+ ERR_PTR(err));
-+ }
-+
- if (pl->sfp_bus) {
- rate_kbd = phylink_interface_signal_rate(state->interface);
- if (rate_kbd)
-@@ -1278,17 +1467,16 @@ static int phylink_change_inband_advert(
- 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->link_config.interface,
-- pl->link_config.advertising);
-+ phylink_pcs_neg_mode(pl, pl->pcs, pl->link_config.interface,
-+ pl->link_config.advertising);
-
-- neg_mode = pl->cur_link_an_mode;
-+ neg_mode = pl->act_link_an_mode;
- if (pl->pcs->neg_mode)
- neg_mode = pl->pcs_neg_mode;
-
-@@ -1353,7 +1541,7 @@ static void phylink_mac_initial_config(s
- {
- 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;
-@@ -1427,14 +1615,14 @@ static void phylink_link_up(struct phyli
-
- 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);
-
-@@ -1454,7 +1642,7 @@ static void phylink_link_down(struct phy
-
- if (ndev)
- netif_carrier_off(ndev);
-- 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");
- }
-@@ -1481,7 +1669,7 @@ static void phylink_resolve(struct work_
- 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);
-@@ -1671,7 +1859,7 @@ int phylink_set_fixed_link(struct phylin
- pl->link_config.an_complete = 1;
-
- pl->cfg_link_an_mode = MLO_AN_FIXED;
-- pl->cur_link_an_mode = pl->cfg_link_an_mode;
-+ pl->req_link_an_mode = pl->cfg_link_an_mode;
-
- return 0;
- }
-@@ -1766,7 +1954,7 @@ struct phylink *phylink_create(struct ph
- }
- }
-
-- pl->cur_link_an_mode = pl->cfg_link_an_mode;
-+ pl->req_link_an_mode = pl->cfg_link_an_mode;
-
- ret = phylink_register_sfp(pl, fwnode);
- if (ret < 0) {
-@@ -2242,7 +2430,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 */
-@@ -2501,7 +2689,7 @@ int phylink_ethtool_ksettings_get(struct
-
- 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
-@@ -2532,6 +2720,26 @@ int phylink_ethtool_ksettings_get(struct
- }
- 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_inband_caps(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()
-@@ -2593,7 +2801,7 @@ int phylink_ethtool_ksettings_set(struct
- /* 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;
-@@ -2609,7 +2817,7 @@ int phylink_ethtool_ksettings_set(struct
- * 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;
-@@ -2649,7 +2857,7 @@ int phylink_ethtool_ksettings_set(struct
- 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;
-@@ -2667,6 +2875,13 @@ int phylink_ethtool_ksettings_set(struct
- 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;
-@@ -2749,7 +2964,7 @@ int phylink_ethtool_set_pauseparam(struc
-
- 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) &&
-@@ -3013,7 +3228,7 @@ static int phylink_mii_read(struct phyli
- 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);
-@@ -3038,7 +3253,7 @@ static int phylink_mii_read(struct phyli
- 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;
-
-@@ -3208,10 +3423,11 @@ static phy_interface_t phylink_choose_sf
- 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",
-@@ -3228,9 +3444,9 @@ static void phylink_sfp_set_config(struc
- 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;
-@@ -3245,8 +3461,7 @@ static void phylink_sfp_set_config(struc
- 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(support1);
- __ETHTOOL_DECLARE_LINK_MODE_MASK(support);
-@@ -3285,8 +3500,7 @@ static int phylink_sfp_config_phy(struct
- 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),
-+ "validation of %s with support %*pb failed: %pe\n",
- phy_modes(config.interface),
- __ETHTOOL_LINK_MODE_MASK_NBITS, support,
- ERR_PTR(ret));
-@@ -3295,7 +3509,7 @@ static int phylink_sfp_config_phy(struct
-
- pl->link_port = pl->sfp_port;
-
-- phylink_sfp_set_config(pl, mode, support, &config);
-+ phylink_sfp_set_config(pl, support, &config);
-
- return 0;
- }
-@@ -3351,6 +3565,12 @@ static int phylink_sfp_config_optical(st
- 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 */
-@@ -3364,7 +3584,7 @@ static int phylink_sfp_config_optical(st
-
- 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;
- }
-@@ -3435,20 +3655,10 @@ static void phylink_sfp_link_up(void *up
- 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;
- phy_interface_t interface;
-- u8 mode;
- int ret;
-
- /*
-@@ -3460,17 +3670,12 @@ static int phylink_sfp_connect_phy(void
- */
- 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 */
-- ret = phylink_sfp_config_phy(pl, mode, phy);
-+ ret = phylink_sfp_config_phy(pl, phy);
- if (ret < 0)
- return ret;
-
---- a/drivers/net/phy/phy.c
-+++ b/drivers/net/phy/phy.c
-@@ -1049,6 +1049,58 @@ static int phy_check_link_status(struct
- }
-
- /**
-+ * phy_inband_caps - 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
-+ * by the PHY for this interface mode.
-+ */
-+unsigned int phy_inband_caps(struct phy_device *phydev,
-+ phy_interface_t interface)
-+{
-+ if (phydev->drv && phydev->drv->inband_caps)
-+ return phydev->drv->inband_caps(phydev, interface);
-+
-+ return 0;
-+}
-+EXPORT_SYMBOL_GPL(phy_inband_caps);
-+
-+/**
-+ * phy_config_inband - configure the desired PHY in-band mode
-+ * @phydev: the phy_device struct
-+ * @modes: in-band modes to configure
-+ *
-+ * Description: disables, enables or enables-with-bypass in-band signalling
-+ * between the PHY and host system.
-+ *
-+ * Returns: zero on success, or negative errno value.
-+ */
-+int phy_config_inband(struct phy_device *phydev, unsigned int modes)
-+{
-+ int err;
-+
-+ if (!!(modes & LINK_INBAND_DISABLE) +
-+ !!(modes & LINK_INBAND_ENABLE) +
-+ !!(modes & LINK_INBAND_BYPASS) != 1)
-+ return -EINVAL;
-+
-+ mutex_lock(&phydev->lock);
-+ if (!phydev->drv)
-+ err = -EIO;
-+ else if (!phydev->drv->config_inband)
-+ err = -EOPNOTSUPP;
-+ else
-+ err = phydev->drv->config_inband(phydev, modes);
-+ mutex_unlock(&phydev->lock);
-+
-+ return err;
-+}
-+
-+/**
- * _phy_start_aneg - start auto-negotiation for this PHY device
- * @phydev: the phy_device struct
- *
---- a/include/linux/phy.h
-+++ b/include/linux/phy.h
-@@ -816,6 +816,24 @@ struct phy_tdr_config {
- #define PHY_PAIR_ALL -1
-
- /**
-+ * enum link_inband_signalling - in-band signalling modes that are supported
-+ *
-+ * @LINK_INBAND_DISABLE: in-band signalling can be disabled
-+ * @LINK_INBAND_ENABLE: in-band signalling can be enabled without bypass
-+ * @LINK_INBAND_BYPASS: in-band signalling can be enabled with 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.
- *
-@@ -955,6 +973,19 @@ struct phy_driver {
- int (*get_features)(struct phy_device *phydev);
-
- /**
-+ * @inband_caps: query whether in-band is supported for the given PHY
-+ * interface mode. Returns a bitmask of bits defined by enum
-+ * link_inband_signalling.
-+ */
-+ unsigned int (*inband_caps)(struct phy_device *phydev,
-+ phy_interface_t interface);
-+
-+ /**
-+ * @config_inband: configure in-band mode for the PHY
-+ */
-+ int (*config_inband)(struct phy_device *phydev, unsigned int modes);
-+
-+ /**
- * @get_rate_matching: Get the supported type of rate matching for a
- * particular phy interface. This is used by phy consumers to determine
- * whether to advertise lower-speed modes for that interface. It is
-@@ -1840,6 +1871,9 @@ int phy_config_aneg(struct phy_device *p
- 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_inband_caps(struct phy_device *phydev,
-+ phy_interface_t interface);
-+int phy_config_inband(struct phy_device *phydev, unsigned int modes);
- 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);
---- a/drivers/net/phy/bcm84881.c
-+++ b/drivers/net/phy/bcm84881.c
-@@ -235,11 +235,21 @@ static int bcm84881_read_status(struct p
- 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_inband_caps(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",
-+ .inband_caps = bcm84881_inband_caps,
- .config_init = bcm84881_config_init,
- .probe = bcm84881_probe,
- .get_features = bcm84881_get_features,
---- a/drivers/net/phy/marvell.c
-+++ b/drivers/net/phy/marvell.c
-@@ -716,6 +716,48 @@ static int marvell_config_aneg_fiber(str
- return genphy_check_and_restart_aneg(phydev, changed);
- }
-
-+static unsigned int m88e1111_inband_caps(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 |
-+ LINK_INBAND_BYPASS;
-+
-+ return 0;
-+}
-+
-+static int m88e1111_config_inband(struct phy_device *phydev, unsigned int modes)
-+{
-+ u16 extsr, bmcr;
-+ int err;
-+
-+ if (phydev->interface != PHY_INTERFACE_MODE_1000BASEX &&
-+ phydev->interface != PHY_INTERFACE_MODE_SGMII)
-+ return -EINVAL;
-+
-+ if (modes == LINK_INBAND_BYPASS)
-+ extsr = MII_M1111_HWCFG_SERIAL_AN_BYPASS;
-+ else
-+ extsr = 0;
-+
-+ if (modes == LINK_INBAND_DISABLE)
-+ bmcr = 0;
-+ else
-+ bmcr = BMCR_ANENABLE;
-+
-+ err = phy_modify(phydev, MII_M1111_PHY_EXT_SR,
-+ MII_M1111_HWCFG_SERIAL_AN_BYPASS, extsr);
-+ if (err < 0)
-+ return extsr;
-+
-+ return phy_modify_paged(phydev, MII_MARVELL_FIBER_PAGE, MII_BMCR,
-+ BMCR_ANENABLE, bmcr);
-+}
-+
- static int m88e1111_config_aneg(struct phy_device *phydev)
- {
- int extsr = phy_read(phydev, MII_M1111_PHY_EXT_SR);
-@@ -3667,6 +3709,8 @@ static struct phy_driver marvell_drivers
- .name = "Marvell 88E1112",
- /* PHY_GBIT_FEATURES */
- .probe = marvell_probe,
-+ .inband_caps = m88e1111_inband_caps,
-+ .config_inband = m88e1111_config_inband,
- .config_init = m88e1112_config_init,
- .config_aneg = marvell_config_aneg,
- .config_intr = marvell_config_intr,
-@@ -3688,6 +3732,8 @@ static struct phy_driver marvell_drivers
- /* PHY_GBIT_FEATURES */
- .flags = PHY_POLL_CABLE_TEST,
- .probe = marvell_probe,
-+ .inband_caps = m88e1111_inband_caps,
-+ .config_inband = m88e1111_config_inband,
- .config_init = m88e1111gbe_config_init,
- .config_aneg = m88e1111_config_aneg,
- .read_status = marvell_read_status,
-@@ -3711,6 +3757,8 @@ static struct phy_driver marvell_drivers
- .name = "Marvell 88E1111 (Finisar)",
- /* PHY_GBIT_FEATURES */
- .probe = marvell_probe,
-+ .inband_caps = m88e1111_inband_caps,
-+ .config_inband = m88e1111_config_inband,
- .config_init = m88e1111gbe_config_init,
- .config_aneg = m88e1111_config_aneg,
- .read_status = marvell_read_status,
---- a/include/linux/phylink.h
-+++ b/include/linux/phylink.h
-@@ -419,6 +419,7 @@ struct phylink_pcs {
- /**
- * struct phylink_pcs_ops - MAC PCS operations structure.
- * @pcs_validate: validate the link configuration.
-+ * @pcs_inband_caps: 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)
-@@ -434,6 +435,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_inband_caps)(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,
-@@ -471,6 +474,20 @@ int pcs_validate(struct phylink_pcs *pcs
- const struct phylink_link_state *state);
-
- /**
-+ * pcs_inband_caps - query PCS in-band capabilities 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_inband_caps(struct phylink_pcs *pcs,
-+ phy_interface_t interface);
-+
-+/**
- * pcs_enable() - enable the PCS.
- * @pcs: a pointer to a &struct phylink_pcs.
- */
---- a/drivers/net/ethernet/marvell/mvneta.c
-+++ b/drivers/net/ethernet/marvell/mvneta.c
-@@ -3960,20 +3960,27 @@ static struct mvneta_port *mvneta_pcs_to
- 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_inband_caps(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,
-@@ -4071,7 +4078,7 @@ static void mvneta_pcs_an_restart(struct
- }
-
- static const struct phylink_pcs_ops mvneta_phylink_pcs_ops = {
-- .pcs_validate = mvneta_pcs_validate,
-+ .pcs_inband_caps = mvneta_pcs_inband_caps,
- .pcs_get_state = mvneta_pcs_get_state,
- .pcs_config = mvneta_pcs_config,
- .pcs_an_restart = mvneta_pcs_an_restart,
---- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
-+++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
-@@ -6237,19 +6237,26 @@ static const struct phylink_pcs_ops mvpp
- .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_inband_caps(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,
-@@ -6356,7 +6363,7 @@ static void mvpp2_gmac_pcs_an_restart(st
- }
-
- static const struct phylink_pcs_ops mvpp2_phylink_gmac_pcs_ops = {
-- .pcs_validate = mvpp2_gmac_pcs_validate,
-+ .pcs_inband_caps = mvpp2_gmac_pcs_inband_caps,
- .pcs_get_state = mvpp2_gmac_pcs_get_state,
- .pcs_config = mvpp2_gmac_pcs_config,
- .pcs_an_restart = mvpp2_gmac_pcs_an_restart,
---- a/drivers/net/pcs/pcs-lynx.c
-+++ b/drivers/net/pcs/pcs-lynx.c
-@@ -35,6 +35,27 @@ enum sgmii_speed {
- #define phylink_pcs_to_lynx(pl_pcs) container_of((pl_pcs), struct lynx_pcs, pcs)
- #define lynx_to_phylink_pcs(lynx) (&(lynx)->pcs)
-
-+static unsigned int lynx_pcs_inband_caps(struct phylink_pcs *pcs,
-+ phy_interface_t interface)
-+{
-+ switch (interface) {
-+ case PHY_INTERFACE_MODE_1000BASEX:
-+ case PHY_INTERFACE_MODE_SGMII:
-+ case PHY_INTERFACE_MODE_QSGMII:
-+ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
-+
-+ case PHY_INTERFACE_MODE_10GBASER:
-+ case PHY_INTERFACE_MODE_2500BASEX:
-+ return LINK_INBAND_DISABLE;
-+
-+ case PHY_INTERFACE_MODE_USXGMII:
-+ return LINK_INBAND_ENABLE;
-+
-+ default:
-+ return 0;
-+ }
-+}
-+
- static void lynx_pcs_get_state_usxgmii(struct mdio_device *pcs,
- struct phylink_link_state *state)
- {
-@@ -306,6 +327,7 @@ static void lynx_pcs_link_up(struct phyl
- }
-
- static const struct phylink_pcs_ops lynx_pcs_phylink_ops = {
-+ .pcs_inband_caps = lynx_pcs_inband_caps,
- .pcs_get_state = lynx_pcs_get_state,
- .pcs_config = lynx_pcs_config,
- .pcs_an_restart = lynx_pcs_an_restart,
---- a/drivers/net/pcs/pcs-mtk-lynxi.c
-+++ b/drivers/net/pcs/pcs-mtk-lynxi.c
-@@ -110,6 +110,21 @@ static struct mtk_pcs_lynxi *pcs_to_mtk_
- return container_of(pcs, struct mtk_pcs_lynxi, pcs);
- }
-
-+static unsigned int mtk_pcs_lynxi_inband_caps(struct phylink_pcs *pcs,
-+ phy_interface_t interface)
-+{
-+ switch (interface) {
-+ case PHY_INTERFACE_MODE_1000BASEX:
-+ case PHY_INTERFACE_MODE_2500BASEX:
-+ case PHY_INTERFACE_MODE_SGMII:
-+ case PHY_INTERFACE_MODE_QSGMII:
-+ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
-+
-+ default:
-+ return 0;
-+ }
-+}
-+
- static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs,
- struct phylink_link_state *state)
- {
-@@ -302,6 +317,7 @@ static void mtk_pcs_lynxi_disable(struct
- }
-
- static const struct phylink_pcs_ops mtk_pcs_lynxi_ops = {
-+ .pcs_inband_caps = mtk_pcs_lynxi_inband_caps,
- .pcs_get_state = mtk_pcs_lynxi_get_state,
- .pcs_config = mtk_pcs_lynxi_config,
- .pcs_an_restart = mtk_pcs_lynxi_restart_an,
---- a/drivers/net/pcs/pcs-xpcs.c
-+++ b/drivers/net/pcs/pcs-xpcs.c
-@@ -608,6 +608,33 @@ static int xpcs_validate(struct phylink_
- return 0;
- }
-
-+static unsigned int xpcs_inband_caps(struct phylink_pcs *pcs,
-+ phy_interface_t interface)
-+{
-+ struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
-+ const struct dw_xpcs_compat *compat;
-+
-+ compat = xpcs_find_compat(xpcs, interface);
-+ if (!compat)
-+ return 0;
-+
-+ switch (compat->an_mode) {
-+ case DW_AN_C73:
-+ return LINK_INBAND_ENABLE;
-+
-+ case DW_AN_C37_SGMII:
-+ case DW_AN_C37_1000BASEX:
-+ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
-+
-+ case DW_10GBASER:
-+ case DW_2500BASEX:
-+ return LINK_INBAND_DISABLE;
-+
-+ default:
-+ return 0;
-+ }
-+}
-+
- void xpcs_get_interfaces(struct dw_xpcs *xpcs, unsigned long *interfaces)
- {
- int i, j;
-@@ -1365,6 +1392,7 @@ static const struct dw_xpcs_desc xpcs_de
-
- static const struct phylink_pcs_ops xpcs_phylink_ops = {
- .pcs_validate = xpcs_validate,
-+ .pcs_inband_caps = xpcs_inband_caps,
- .pcs_config = xpcs_config,
- .pcs_get_state = xpcs_get_state,
- .pcs_an_restart = xpcs_an_restart,
---
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
-@@ -5296,6 +5296,16 @@ static int mvneta_setup_tc(struct net_de
+@@ -5303,6 +5303,16 @@ static int mvneta_setup_tc(struct net_de
}
}
static const struct net_device_ops mvneta_netdev_ops = {
.ndo_open = mvneta_open,
.ndo_stop = mvneta_stop,
-@@ -5306,6 +5316,9 @@ static const struct net_device_ops mvnet
+@@ -5313,6 +5323,9 @@ static const struct net_device_ops mvnet
.ndo_fix_features = mvneta_fix_features,
.ndo_get_stats64 = mvneta_get_stats64,
.ndo_eth_ioctl = mvneta_ioctl,
/* This is optional functionality. If not supported, we may get an error
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -1897,6 +1897,7 @@ int genphy_c45_an_config_aneg(struct phy
+@@ -1931,6 +1931,7 @@ int genphy_c45_an_config_aneg(struct phy
int genphy_c45_an_disable_aneg(struct phy_device *phydev);
int genphy_c45_read_mdix(struct phy_device *phydev);
int genphy_c45_pma_read_abilities(struct phy_device *phydev);