--- a/drivers/net/pcs/Kconfig
+++ b/drivers/net/pcs/Kconfig
-@@ -44,4 +44,6 @@ config PCS_RZN1_MIIC
+@@ -51,4 +51,6 @@ config PCS_RZN1_MIIC
on RZ/N1 SoCs. This PCS converts MII to RMII/RGMII or can be set in
pass-through mode for MII.
endmenu
--- a/drivers/net/pcs/Makefile
+++ b/drivers/net/pcs/Makefile
-@@ -9,3 +9,5 @@ obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
+@@ -10,3 +10,5 @@ obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o
obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o
obj-$(CONFIG_PCS_MTK_USXGMII) += pcs-mtk-usxgmii.o
--- /dev/null
+From 486dc391ef439d45db3f7eda2229560fd2b52a78 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Wed, 16 Oct 2024 10:58:34 +0100
+Subject: [PATCH] net: phylink: allow mac_select_pcs() to remove a PCS
+
+phylink has historically not permitted a PCS to be removed. An attempt
+to permit this with phylink_set_pcs() resulted in comments indicating
+that there was no need for this. This behaviour has been propagated
+forward to the mac_select_pcs() approach as it was believed from these
+comments that changing this would be NAK'd.
+
+However, with mac_select_pcs(), it takes more code and thus complexity
+to maintain this behaviour, which can - and in this case has - resulted
+in a bug. If mac_select_pcs() returns NULL for a particular interface
+type, but there is already a PCS in-use, then we skip the pcs_validate()
+method, but continue using the old PCS. Also, it wouldn't be expected
+behaviour by implementers of mac_select_pcs().
+
+Allow this by removing this old unnecessary restriction.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ drivers/net/phy/phylink.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1375,7 +1375,7 @@ static void phylink_major_config(struct
+ return;
+ }
+
+- pcs_changed = pcs && pl->pcs != pcs;
++ pcs_changed = pl->pcs != pcs;
+ }
+
+ phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
--- /dev/null
+From fbb9a9d263a68f60a16c8ba5a51d6198d67171cd Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 3 Jan 2025 11:16:31 +0000
+Subject: [PATCH] net: phylink: add support for PCS supported_interfaces bitmap
+
+Add support for the PCS to specify which interfaces it supports, which
+can be used by MAC drivers to build the main supported_interfaces
+bitmap. Phylink also validates that the PCS returned by the MAC driver
+supports the interface that the MAC was asked for.
+
+An empty supported_interfaces bitmap from the PCS indicates that it
+does not provide this information, and we handle that appropriately.
+
+Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1tTffL-007RoD-1Y@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 11 +++++++++++
+ include/linux/phylink.h | 3 +++
+ 2 files changed, 14 insertions(+)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -701,6 +701,17 @@ static int phylink_validate_mac_and_pcs(
+ return -EINVAL;
+ }
+
++ /* Ensure that this PCS supports the interface which the MAC
++ * returned it for. It is an error for the MAC to return a PCS
++ * that does not support the interface mode.
++ */
++ if (!phy_interface_empty(pcs->supported_interfaces) &&
++ !test_bit(state->interface, pcs->supported_interfaces)) {
++ phylink_err(pl, "MAC returned PCS which does not support %s\n",
++ phy_modes(state->interface));
++ return -EINVAL;
++ }
++
+ /* Validate the link parameters with the PCS */
+ if (pcs->ops->pcs_validate) {
+ ret = pcs->ops->pcs_validate(pcs, supported, state);
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -393,6 +393,8 @@ struct phylink_pcs_ops;
+
+ /**
+ * struct phylink_pcs - PHYLINK PCS instance
++ * @supported_interfaces: describing which PHY_INTERFACE_MODE_xxx
++ * are supported by this PCS.
+ * @ops: a pointer to the &struct phylink_pcs_ops structure
+ * @phylink: pointer to &struct phylink_config
+ * @neg_mode: provide PCS neg mode via "mode" argument
+@@ -409,6 +411,7 @@ struct phylink_pcs_ops;
+ * the PCS driver.
+ */
+ struct phylink_pcs {
++ DECLARE_PHY_INTERFACE_MASK(supported_interfaces);
+ const struct phylink_pcs_ops *ops;
+ struct phylink *phylink;
+ bool neg_mode;
--- /dev/null
+From f1ae32a709e0b525d7963207eb3a4747626f4818 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Mon, 24 Mar 2025 16:40:08 +0000
+Subject: [PATCH] net: phylink: force link down on major_config failure
+
+If we fail to configure the MAC or PCS according to the desired mode,
+do not allow the network link to come up until we have successfully
+configured the MAC and PCS. This improves phylink's behaviour when an
+error occurs.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1twkqO-0006FI-Gm@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 42 +++++++++++++++++++++++++++++++--------
+ 1 file changed, 34 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -82,6 +82,7 @@ struct phylink {
+
+ bool link_failed;
+ bool using_mac_select_pcs;
++ bool major_config_failed;
+
+ struct sfp_bus *sfp_bus;
+ bool sfp_may_have_phy;
+@@ -1377,12 +1378,16 @@ static void phylink_major_config(struct
+ phylink_an_mode_str(pl->req_link_an_mode),
+ phy_modes(state->interface));
+
++ pl->major_config_failed = false;
++
+ if (pl->using_mac_select_pcs) {
+ pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+ if (IS_ERR(pcs)) {
+ phylink_err(pl,
+ "mac_select_pcs unexpectedly failed: %pe\n",
+ pcs);
++
++ pl->major_config_failed = true;
+ return;
+ }
+
+@@ -1404,6 +1409,7 @@ static void phylink_major_config(struct
+ if (err < 0) {
+ phylink_err(pl, "mac_prepare failed: %pe\n",
+ ERR_PTR(err));
++ pl->major_config_failed = true;
+ return;
+ }
+ }
+@@ -1427,8 +1433,15 @@ static void phylink_major_config(struct
+
+ phylink_mac_config(pl, state);
+
+- if (pl->pcs)
+- phylink_pcs_post_config(pl->pcs, state->interface);
++ if (pl->pcs) {
++ err = phylink_pcs_post_config(pl->pcs, state->interface);
++ if (err < 0) {
++ phylink_err(pl, "pcs_post_config failed: %pe\n",
++ ERR_PTR(err));
++
++ pl->major_config_failed = true;
++ }
++ }
+
+ if (pl->pcs_state == PCS_STATE_STARTING || pcs_changed)
+ phylink_pcs_enable(pl->pcs);
+@@ -1439,11 +1452,12 @@ static void phylink_major_config(struct
+
+ err = phylink_pcs_config(pl->pcs, neg_mode, state,
+ !!(pl->link_config.pause & MLO_PAUSE_AN));
+- if (err < 0)
+- phylink_err(pl, "pcs_config failed: %pe\n",
+- ERR_PTR(err));
+- else if (err > 0)
++ if (err < 0) {
++ phylink_err(pl, "pcs_config failed: %pe\n", ERR_PTR(err));
++ pl->major_config_failed = true;
++ } else if (err > 0) {
+ restart = true;
++ }
+
+ if (restart)
+ phylink_pcs_an_restart(pl);
+@@ -1451,16 +1465,22 @@ static void phylink_major_config(struct
+ if (pl->mac_ops->mac_finish) {
+ err = pl->mac_ops->mac_finish(pl->config, pl->act_link_an_mode,
+ state->interface);
+- if (err < 0)
++ if (err < 0) {
+ phylink_err(pl, "mac_finish failed: %pe\n",
+ ERR_PTR(err));
++
++ pl->major_config_failed = true;
++ }
+ }
+
+ if (pl->phydev && pl->phy_ib_mode) {
+ err = phy_config_inband(pl->phydev, pl->phy_ib_mode);
+- if (err < 0)
++ if (err < 0) {
+ phylink_err(pl, "phy_config_inband: %pe\n",
+ ERR_PTR(err));
++
++ pl->major_config_failed = true;
++ }
+ }
+
+ if (pl->sfp_bus) {
+@@ -1762,6 +1782,12 @@ static void phylink_resolve(struct work_
+ }
+ }
+
++ /* If configuration of the interface failed, force the link down
++ * until we get a successful configuration.
++ */
++ if (pl->major_config_failed)
++ link_state.link = false;
++
+ if (link_state.link != cur_link_state) {
+ pl->old_link_state = link_state.link;
+ if (!link_state.link)
--- /dev/null
+From 7bf588dc62a05c1866efe098e1b188fd879aa2cf Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Mon, 19 Jan 2026 14:19:51 +0200
+Subject: [PATCH] net: phylink: simplify phylink_resolve() ->
+ phylink_major_config() path
+
+This is a trivial change with no functional effect which replaces the
+pattern:
+
+if (a) {
+ if (b) {
+ do_stuff();
+ }
+}
+
+with:
+
+if (a && b) {
+ do_stuff();
+};
+
+The purpose is to reduce the delta of a subsequent functional change.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/20260119121954.1624535-2-vladimir.oltean@nxp.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 20 +++++++++-----------
+ 1 file changed, 9 insertions(+), 11 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1768,18 +1768,16 @@ static void phylink_resolve(struct work_
+ if (pl->act_link_an_mode != MLO_AN_FIXED)
+ phylink_apply_manual_flow(pl, &link_state);
+
+- if (mac_config) {
+- if (link_state.interface != pl->link_config.interface) {
+- /* The interface has changed, force the link down and
+- * then reconfigure.
+- */
+- if (cur_link_state) {
+- phylink_link_down(pl);
+- cur_link_state = false;
+- }
+- phylink_major_config(pl, false, &link_state);
+- pl->link_config.interface = link_state.interface;
++ if (mac_config && link_state.interface != pl->link_config.interface) {
++ /* The interface has changed, so force the link down and then
++ * reconfigure.
++ */
++ if (cur_link_state) {
++ phylink_link_down(pl);
++ cur_link_state = false;
+ }
++ phylink_major_config(pl, false, &link_state);
++ pl->link_config.interface = link_state.interface;
+ }
+
+ /* If configuration of the interface failed, force the link down
--- /dev/null
+From 96969b132bf1a5b875ab84fcb41a5c4972c3be9e Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Mon, 19 Jan 2026 14:19:52 +0200
+Subject: [PATCH 2/2] net: phylink: introduce helpers for replaying link
+ callbacks
+
+Some drivers of MAC + tightly integrated PCS (example: SJA1105 + XPCS
+covered by same reset domain) need to perform resets at runtime.
+
+The reset is triggered by the MAC driver, and it needs to restore its
+and the PCS' registers, all invisible to phylink.
+
+However, there is a desire to simplify the API through which the MAC and
+the PCS interact, so this becomes challenging.
+
+Phylink holds all the necessary state to help with this operation, and
+can offer two helpers which walk the MAC and PCS drivers again through
+the callbacks required during a destructive reset operation. The
+procedure is as follows:
+
+Before reset, MAC driver calls phylink_replay_link_begin():
+- Triggers phylink mac_link_down() and pcs_link_down() methods
+
+After reset, MAC driver calls phylink_replay_link_end():
+- Triggers phylink mac_config() -> pcs_config() -> mac_link_up() ->
+ pcs_link_up() methods.
+
+MAC and PCS registers are restored with no other custom driver code.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/20260119121954.1624535-3-vladimir.oltean@nxp.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 61 +++++++++++++++++++++++++++++++++++++--
+ include/linux/phylink.h | 5 ++++
+ 2 files changed, 63 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -34,6 +34,7 @@ enum {
+ PHYLINK_DISABLE_STOPPED,
+ PHYLINK_DISABLE_LINK,
+ PHYLINK_DISABLE_MAC_WOL,
++ PHYLINK_DISABLE_REPLAY,
+
+ PCS_STATE_DOWN = 0,
+ PCS_STATE_STARTING,
+@@ -83,6 +84,7 @@ struct phylink {
+ bool link_failed;
+ bool using_mac_select_pcs;
+ bool major_config_failed;
++ bool force_major_config;
+
+ struct sfp_bus *sfp_bus;
+ bool sfp_may_have_phy;
+@@ -1768,9 +1770,10 @@ static void phylink_resolve(struct work_
+ if (pl->act_link_an_mode != MLO_AN_FIXED)
+ phylink_apply_manual_flow(pl, &link_state);
+
+- if (mac_config && link_state.interface != pl->link_config.interface) {
+- /* The interface has changed, so force the link down and then
+- * reconfigure.
++ if ((mac_config && link_state.interface != pl->link_config.interface) ||
++ pl->force_major_config) {
++ /* The interface has changed or a forced major configuration
++ * was requested, so force the link down and then reconfigure.
+ */
+ if (cur_link_state) {
+ phylink_link_down(pl);
+@@ -1778,6 +1781,7 @@ static void phylink_resolve(struct work_
+ }
+ phylink_major_config(pl, false, &link_state);
+ pl->link_config.interface = link_state.interface;
++ pl->force_major_config = false;
+ }
+
+ /* If configuration of the interface failed, force the link down
+@@ -4130,6 +4134,57 @@ void phylink_mii_c45_pcs_get_state(struc
+ }
+ EXPORT_SYMBOL_GPL(phylink_mii_c45_pcs_get_state);
+
++/**
++ * phylink_replay_link_begin() - begin replay of link callbacks for driver
++ * which loses state
++ * @pl: a pointer to a &struct phylink returned from phylink_create()
++ *
++ * Helper for MAC drivers which may perform a destructive reset at runtime.
++ * Both the own driver's mac_link_down() method is called, as well as the
++ * pcs_link_down() method of the split PCS (if any).
++ *
++ * This is similar to phylink_stop(), except it does not alter the state of
++ * the phylib PHY (it is assumed that it is not affected by the MAC destructive
++ * reset).
++ */
++void phylink_replay_link_begin(struct phylink *pl)
++{
++ ASSERT_RTNL();
++
++ phylink_run_resolve_and_disable(pl, PHYLINK_DISABLE_REPLAY);
++}
++EXPORT_SYMBOL_GPL(phylink_replay_link_begin);
++
++/**
++ * phylink_replay_link_end() - end replay of link callbacks for driver
++ * which lost state
++ * @pl: a pointer to a &struct phylink returned from phylink_create()
++ *
++ * Helper for MAC drivers which may perform a destructive reset at runtime.
++ * Both the own driver's mac_config() and mac_link_up() methods, as well as the
++ * pcs_config() and pcs_link_up() method of the split PCS (if any), are called.
++ *
++ * This is similar to phylink_start(), except it does not alter the state of
++ * the phylib PHY.
++ *
++ * One must call this method only within the same rtnl_lock() critical section
++ * as a previous phylink_replay_link_start().
++ */
++void phylink_replay_link_end(struct phylink *pl)
++{
++ ASSERT_RTNL();
++
++ if (WARN(!test_bit(PHYLINK_DISABLE_REPLAY,
++ &pl->phylink_disable_state),
++ "phylink_replay_link_end() called without a prior phylink_replay_link_begin()\n"))
++ return;
++
++ pl->force_major_config = true;
++ phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_REPLAY);
++ flush_work(&pl->resolve);
++}
++EXPORT_SYMBOL_GPL(phylink_replay_link_end);
++
+ static int __init phylink_init(void)
+ {
+ for (int i = 0; i < ARRAY_SIZE(phylink_sfp_interface_preference); ++i)
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -707,4 +707,9 @@ void phylink_mii_c45_pcs_get_state(struc
+
+ void phylink_decode_usxgmii_word(struct phylink_link_state *state,
+ uint16_t lpa);
++
++void phylink_replay_link_begin(struct phylink *pl);
++
++void phylink_replay_link_end(struct phylink *pl);
++
+ #endif
--- /dev/null
+From 0e4d7df2f3b2e59c1bccc09ea099b7a6c2dda886 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Wed, 28 Jan 2026 10:51:56 +0000
+Subject: [PATCH] net: phylink: fix NULL pointer deref in
+ phylink_major_config()
+
+When a MAC driver returns a PCS for an interface mode, and then we
+attempt to switch to a different mode that doesn't require a PCS,
+this causes phylink to oops:
+
+Unable to handle kernel NULL pointer dereference at virtual address 0000000000000010
+Mem abort info:
+ ESR = 0x0000000096000044
+ EC = 0x25: DABT (current EL), IL = 32 bits
+ SET = 0, FnV = 0
+ EA = 0, S1PTW = 0
+ FSC = 0x04: level 0 translation fault
+Data abort info:
+ ISV = 0, ISS = 0x00000044, ISS2 = 0x00000000
+ CM = 0, WnR = 1, TnD = 0, TagAccess = 0
+ GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
+user pgtable: 4k pages, 48-bit VAs, pgdp=0000000137f96000
+[0000000000000010] pgd=0000000000000000, p4d=0000000000000000
+Internal error: Oops: 0000000096000044 [#1] SMP
+Modules linked in: --
+CPU: 1 UID: 0 PID: 55 Comm: kworker/u33:0 Not tainted 6.19.0-rc5-00581-g73cb8467a63e #1 PREEMPT
+Hardware name: Qualcomm Technologies, Inc. Lemans Ride Rev3 (DT)
+Workqueue: events_power_efficient phylink_resolve
+pstate: 60400005 (nZCv daif +PAN -UAO -TCO -DIT -SSBS +BTYPE=--)
+pc : phylink_major_config+0x408/0x948
+lr : phylink_major_config+0x3fc/0x948
+sp : ffff800080353c60
+x29: ffff800080353cb0 x28: ffffb305068a8a00 x27: ffffb305068a8000
+x26: ffff000080092100 x25: 0000000000000000 x24: 0000000000000000
+x23: 0000000000000001 x22: 0000000000000000 x21: ffffb3050555b3d0
+x20: ffff800080353d10 x19: ffff0000b6059400 x18: 00000000ffffffff
+x17: 74756f2f79687020 x16: ffffb305045e4f18 x15: 6769666e6f632072
+x14: 6f6a616d203a3168 x13: 782d657361623030 x12: ffffb305068c6a98
+x11: 0000000000000583 x10: 0000000000000018 x9 : ffffb305068c6a98
+x8 : 0000000100006583 x7 : 0000000000000000 x6 : ffff00008083cc40
+x5 : ffff00008083cc40 x4 : 0000000000000001 x3 : 0000000000000001
+x2 : 0000000000000000 x1 : 0000000000000000 x0 : ffff0000b269e5a8
+Call trace:
+ phylink_major_config+0x408/0x948 (P)
+ phylink_resolve+0x294/0x6e4
+ process_one_work+0x148/0x28c
+ worker_thread+0x2d8/0x3d8
+ kthread+0x134/0x208
+ ret_from_fork+0x10/0x20
+Code: d63f0020 f9400e60 b4000040 f900081f (f9000ad3)
+---[ end trace 0000000000000000 ]---
+
+This is caused by "pcs" being NULL when we attempt to execute:
+
+ pcs->phylink = pl;
+
+Make this conditional on pcs being non-null.
+
+Fixes: 486dc391ef43 ("net: phylink: allow mac_select_pcs() to remove a PCS")
+Reported-by: Mohd Ayaan Anwar <mohd.anwar@oss.qualcomm.com>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Link: https://patch.msgid.link/E1vl39Q-00000006utm-229h@rmk-PC.armlinux.org.uk
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/phylink.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1425,7 +1425,8 @@ static void phylink_major_config(struct
+ if (pl->pcs)
+ pl->pcs->phylink = NULL;
+
+- pcs->phylink = pl;
++ if (pcs)
++ pcs->phylink = pl;
+
+ pl->pcs = pcs;
+ }
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -2273,7 +2273,7 @@ int phylink_fwnode_phy_connect(struct ph
+@@ -2313,7 +2313,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 ||
-@@ -2303,6 +2303,25 @@ int phylink_fwnode_phy_connect(struct ph
+@@ -2343,6 +2343,25 @@ int phylink_fwnode_phy_connect(struct ph
if (pl->config->mac_requires_rxc)
flags |= PHY_F_RXC_ALWAYS_ON;
--- /dev/null
+From c6f6cb55c3c316f6169e07eacc5ccb214116719a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 31 Mar 2025 15:40:12 +0200
+Subject: [PATCH 1/7] net: phylink: keep and use MAC supported_interfaces in
+ phylink struct
+
+Add in phylink struct a copy of supported_interfaces from phylink_config
+and make use of that instead of relying on phylink_config value.
+
+This in preparation for support of PCS handling internally to phylink
+where a PCS can be removed or added after the phylink is created and we
+need both a reference of the supported_interfaces value from
+phylink_config and an internal value that can be updated with the new
+PCS info.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/phy/phylink.c | 22 +++++++++++++++-------
+ 1 file changed, 15 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -65,6 +65,11 @@ struct phylink {
+ /* The link configuration settings */
+ struct phylink_link_state link_config;
+
++ /* What interface are supported by the current link.
++ * Can change on removal or addition of new PCS.
++ */
++ DECLARE_PHY_INTERFACE_MASK(supported_interfaces);
++
+ /* The current settings */
+ phy_interface_t cur_interface;
+
+@@ -793,7 +798,7 @@ static int phylink_validate_mask(struct
+ static int phylink_validate(struct phylink *pl, unsigned long *supported,
+ struct phylink_link_state *state)
+ {
+- const unsigned long *interfaces = pl->config->supported_interfaces;
++ const unsigned long *interfaces = pl->supported_interfaces;
+
+ if (state->interface == PHY_INTERFACE_MODE_NA)
+ return phylink_validate_mask(pl, NULL, supported, state,
+@@ -1948,6 +1953,9 @@ struct phylink *phylink_create(struct ph
+ mutex_init(&pl->state_mutex);
+ INIT_WORK(&pl->resolve, phylink_resolve);
+
++ phy_interface_copy(pl->supported_interfaces,
++ config->supported_interfaces);
++
+ pl->config = config;
+ if (config->type == PHYLINK_NETDEV) {
+ pl->netdev = to_net_dev(config->dev);
+@@ -2091,7 +2099,7 @@ static int phylink_validate_phy(struct p
+ * those which the host supports.
+ */
+ phy_interface_and(interfaces, phy->possible_interfaces,
+- pl->config->supported_interfaces);
++ pl->supported_interfaces);
+
+ if (phy_interface_empty(interfaces)) {
+ phylink_err(pl, "PHY has no common interfaces\n");
+@@ -3565,14 +3573,14 @@ static int phylink_sfp_config_optical(st
+
+ phylink_dbg(pl, "optical SFP: interfaces=[mac=%*pbl, sfp=%*pbl]\n",
+ (int)PHY_INTERFACE_MODE_MAX,
+- pl->config->supported_interfaces,
++ pl->supported_interfaces,
+ (int)PHY_INTERFACE_MODE_MAX,
+ pl->sfp_interfaces);
+
+ /* Find the union of the supported interfaces by the PCS/MAC and
+ * the SFP module.
+ */
+- phy_interface_and(interfaces, pl->config->supported_interfaces,
++ phy_interface_and(interfaces, pl->supported_interfaces,
+ pl->sfp_interfaces);
+ if (phy_interface_empty(interfaces)) {
+ phylink_err(pl, "unsupported SFP module: no common interface modes\n");
+@@ -3730,7 +3738,7 @@ static int phylink_sfp_connect_phy(void
+
+ /* Set the PHY's host supported interfaces */
+ phy_interface_and(phy->host_interfaces, phylink_sfp_interfaces,
+- pl->config->supported_interfaces);
++ pl->supported_interfaces);
+
+ /* Do the initial configuration */
+ ret = phylink_sfp_config_phy(pl, phy);
--- /dev/null
+From d134e22b540226a7404cabb88c86a54857486b4f Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 31 Mar 2025 16:03:26 +0200
+Subject: [PATCH 2/7] net: phylink: introduce internal phylink PCS handling
+
+Introduce internal handling of PCS for phylink. This is an alternative
+to .mac_select_pcs that moves the selection logic of the PCS entirely to
+phylink with the usage of supported_interface value in the PCS struct.
+
+MAC should now provide an array of available PCS in phylink_config in
+.available_pcs and fill the .num_available_pcs with the number of
+elements in the array. MAC should also define a new bitmap,
+pcs_interfaces, in phylink_config to define for what interface mode a
+dedicated PCS is required.
+
+On phylink_create() this array is parsed and a linked list of PCS is
+created based on the PCS passed in phylink_config.
+Also the supported_interface value in phylink struct is updated with the
+new supported_interface from the provided PCS.
+
+On phylink_start() every PCS in phylink PCS list gets attached to the
+phylink instance. This is done by setting the phylink value in
+phylink_pcs struct to the phylink instance.
+
+On phylink_stop(), every PCS in phylink PCS list is detached from the
+phylink instance. This is done by setting the phylink value in
+phylink_pcs struct to NULL.
+
+On phylink_stop(), every PCS in phylink PCS list is removed from the
+list.
+
+phylink_validate_mac_and_pcs(), phylink_major_config() and
+phylink_inband_caps() are updated to support this new implementation
+with the PCS list stored in phylink.
+
+They will make use of phylink_validate_pcs_interface() that will loop
+for every PCS in the phylink PCS available list and find one that supports
+the passed interface.
+
+phylink_validate_pcs_interface() apply the same logic of .mac_select_pcs
+where if a supported_interface value is not set for the PCS struct, then
+it's assumed every interface is supported.
+
+It's required for a MAC that implement either a .mac_select_pcs or make
+use of the PCS list implementation. Implementing both will result in a fail
+on MAC/PCS validation.
+
+phylink value in phylink_pcs struct with this implementation is used to
+track from PCS side when it's attached to a phylink instance. PCS driver
+will make use of this information to correctly detach from a phylink
+instance if needed.
+
+The .mac_select_pcs implementation is not changed but it's expected that
+every MAC driver migrates to the new implementation to later deprecate
+and remove .mac_select_pcs.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/phy/phylink.c | 149 +++++++++++++++++++++++++++++++++-----
+ include/linux/phylink.h | 11 +++
+ 2 files changed, 141 insertions(+), 19 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -65,6 +65,9 @@ struct phylink {
+ /* The link configuration settings */
+ struct phylink_link_state link_config;
+
++ /* List of available PCS */
++ struct list_head pcs_list;
++
+ /* What interface are supported by the current link.
+ * Can change on removal or addition of new PCS.
+ */
+@@ -144,6 +147,8 @@ static const phy_interface_t phylink_sfp
+
+ static DECLARE_PHY_INTERFACE_MASK(phylink_sfp_interfaces);
+
++static void phylink_run_resolve(struct phylink *pl);
++
+ /**
+ * phylink_set_port_modes() - set the port type modes in the ethtool mask
+ * @mask: ethtool link mode mask
+@@ -680,24 +685,59 @@ static void phylink_validate_mask_caps(u
+ linkmode_and(state->advertising, state->advertising, mask);
+ }
+
++static int phylink_validate_pcs_interface(struct phylink_pcs *pcs,
++ phy_interface_t interface)
++{
++ /* If PCS define an empty supported_interfaces value, assume
++ * all interface are supported.
++ */
++ if (phy_interface_empty(pcs->supported_interfaces))
++ return 0;
++
++ /* Ensure that this PCS supports the interface mode */
++ if (!test_bit(interface, pcs->supported_interfaces))
++ return -EINVAL;
++
++ return 0;
++}
++
+ static int phylink_validate_mac_and_pcs(struct phylink *pl,
+ unsigned long *supported,
+ struct phylink_link_state *state)
+ {
+ unsigned long capabilities;
+ struct phylink_pcs *pcs;
++ bool pcs_found = false;
+ int ret;
+
+ /* Get the PCS for this interface mode */
+- if (pl->using_mac_select_pcs) {
++ if (pl->mac_ops->mac_select_pcs) {
++ /* Make sure either PCS internal validation or .mac_select_pcs
++ * is used. Return error if both are defined.
++ */
++ if (!list_empty(&pl->pcs_list)) {
++ phylink_err(pl, "either phylink_pcs_add() or .mac_select_pcs must be used\n");
++ return -EINVAL;
++ }
++
+ pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+ if (IS_ERR(pcs))
+ return PTR_ERR(pcs);
++
++ pcs_found = !!pcs;
+ } else {
+- pcs = pl->pcs;
++ /* Check every assigned PCS and search for one that supports
++ * the interface.
++ */
++ list_for_each_entry(pcs, &pl->pcs_list, list) {
++ if (!phylink_validate_pcs_interface(pcs, state->interface)) {
++ pcs_found = true;
++ break;
++ }
++ }
+ }
+
+- if (pcs) {
++ if (pcs_found) {
+ /* The PCS, if present, must be setup before phylink_create()
+ * has been called. If the ops is not initialised, print an
+ * error and backtrace rather than oopsing the kernel.
+@@ -709,13 +749,10 @@ static int phylink_validate_mac_and_pcs(
+ return -EINVAL;
+ }
+
+- /* Ensure that this PCS supports the interface which the MAC
+- * returned it for. It is an error for the MAC to return a PCS
+- * that does not support the interface mode.
+- */
+- if (!phy_interface_empty(pcs->supported_interfaces) &&
+- !test_bit(state->interface, pcs->supported_interfaces)) {
+- phylink_err(pl, "MAC returned PCS which does not support %s\n",
++ /* Recheck PCS to handle legacy way for .mac_select_pcs */
++ ret = phylink_validate_pcs_interface(pcs, state->interface);
++ if (ret) {
++ phylink_err(pl, "selected PCS does not support %s\n",
+ phy_modes(state->interface));
+ return -EINVAL;
+ }
+@@ -1096,12 +1133,22 @@ static unsigned int phylink_inband_caps(
+ phy_interface_t interface)
+ {
+ struct phylink_pcs *pcs;
++ bool pcs_found = false;
+
+- if (!pl->mac_ops->mac_select_pcs)
+- return 0;
++ if (pl->mac_ops->mac_select_pcs) {
++ pcs = pl->mac_ops->mac_select_pcs(pl->config,
++ interface);
++ pcs_found = !!pcs;
++ } else {
++ list_for_each_entry(pcs, &pl->pcs_list, list) {
++ if (!phylink_validate_pcs_interface(pcs, interface)) {
++ pcs_found = true;
++ break;
++ }
++ }
++ }
+
+- pcs = pl->mac_ops->mac_select_pcs(pl->config, interface);
+- if (!pcs)
++ if (!pcs_found)
+ return 0;
+
+ return phylink_pcs_inband_caps(pcs, interface);
+@@ -1397,10 +1444,36 @@ static void phylink_major_config(struct
+ pl->major_config_failed = true;
+ return;
+ }
++ /* Find a PCS in available PCS list for the requested interface.
++ * This doesn't overwrite the previous .mac_select_pcs as either
++ * .mac_select_pcs or PCS list implementation are permitted.
++ *
++ * Skip searching if the MAC doesn't require a dedicaed PCS for
++ * the requested interface.
++ */
++ } else if (test_bit(state->interface, pl->config->pcs_interfaces)) {
++ bool pcs_found = false;
++
++ list_for_each_entry(pcs, &pl->pcs_list, list) {
++ if (!phylink_validate_pcs_interface(pcs,
++ state->interface)) {
++ pcs_found = true;
++ break;
++ }
++ }
++
++ if (!pcs_found) {
++ phylink_err(pl,
++ "couldn't find a PCS for %s\n",
++ phy_modes(state->interface));
+
+- pcs_changed = pl->pcs != pcs;
++ pl->major_config_failed = true;
++ return;
++ }
+ }
+
++ pcs_changed = pl->pcs != pcs;
++
+ phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
+
+ phylink_dbg(pl, "major config, active %s/%s/%s\n",
+@@ -1427,11 +1500,13 @@ static void phylink_major_config(struct
+ if (pcs_changed) {
+ phylink_pcs_disable(pl->pcs);
+
+- if (pl->pcs)
+- pl->pcs->phylink = NULL;
++ if (pl->mac_ops->mac_select_pcs) {
++ if (pl->pcs)
++ pl->pcs->phylink = NULL;
+
+- if (pcs)
+- pcs->phylink = pl;
++ if (pcs)
++ pcs->phylink = pl;
++ }
+
+ pl->pcs = pcs;
+ }
+@@ -1931,8 +2006,9 @@ struct phylink *phylink_create(struct ph
+ const struct phylink_mac_ops *mac_ops)
+ {
+ bool using_mac_select_pcs = false;
++ struct phylink_pcs *pcs;
+ struct phylink *pl;
+- int ret;
++ int i, ret;
+
+ /* Validate the supplied configuration */
+ if (phy_interface_empty(config->supported_interfaces)) {
+@@ -1952,9 +2028,21 @@ struct phylink *phylink_create(struct ph
+
+ mutex_init(&pl->state_mutex);
+ INIT_WORK(&pl->resolve, phylink_resolve);
++ INIT_LIST_HEAD(&pl->pcs_list);
++
++ /* Fill the PCS list with available PCS from phylink config */
++ for (i = 0; i < config->num_available_pcs; i++) {
++ pcs = config->available_pcs[i];
++
++ list_add(&pcs->list, &pl->pcs_list);
++ }
+
+ phy_interface_copy(pl->supported_interfaces,
+ config->supported_interfaces);
++ list_for_each_entry(pcs, &pl->pcs_list, list)
++ phy_interface_or(pl->supported_interfaces,
++ pl->supported_interfaces,
++ pcs->supported_interfaces);
+
+ pl->config = config;
+ if (config->type == PHYLINK_NETDEV) {
+@@ -2024,10 +2112,16 @@ EXPORT_SYMBOL_GPL(phylink_create);
+ */
+ void phylink_destroy(struct phylink *pl)
+ {
++ struct phylink_pcs *pcs, *tmp;
++
+ sfp_bus_del_upstream(pl->sfp_bus);
+ if (pl->link_gpio)
+ gpiod_put(pl->link_gpio);
+
++ /* Remove every PCS from phylink PCS list */
++ list_for_each_entry_safe(pcs, tmp, &pl->pcs_list, list)
++ list_del(&pcs->list);
++
+ cancel_work_sync(&pl->resolve);
+ kfree(pl);
+ }
+@@ -2472,6 +2566,7 @@ static irqreturn_t phylink_link_handler(
+ */
+ void phylink_start(struct phylink *pl)
+ {
++ struct phylink_pcs *pcs;
+ bool poll = false;
+
+ ASSERT_RTNL();
+@@ -2498,6 +2593,10 @@ void phylink_start(struct phylink *pl)
+
+ pl->pcs_state = PCS_STATE_STARTED;
+
++ /* link available PCS to phylink struct */
++ list_for_each_entry(pcs, &pl->pcs_list, list)
++ pcs->phylink = pl;
++
+ phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_STOPPED);
+
+ if (pl->cfg_link_an_mode == MLO_AN_FIXED && pl->link_gpio) {
+@@ -2542,6 +2641,8 @@ EXPORT_SYMBOL_GPL(phylink_start);
+ */
+ void phylink_stop(struct phylink *pl)
+ {
++ struct phylink_pcs *pcs;
++
+ ASSERT_RTNL();
+
+ if (pl->sfp_bus)
+@@ -2559,6 +2660,14 @@ void phylink_stop(struct phylink *pl)
+ pl->pcs_state = PCS_STATE_DOWN;
+
+ phylink_pcs_disable(pl->pcs);
++
++ /* Drop link between phylink and PCS */
++ list_for_each_entry(pcs, &pl->pcs_list, list)
++ pcs->phylink = NULL;
++
++ /* Restore original supported interfaces */
++ phy_interface_copy(pl->supported_interfaces,
++ pl->config->supported_interfaces);
+ }
+ EXPORT_SYMBOL_GPL(phylink_stop);
+
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -147,7 +147,11 @@ enum phylink_op_type {
+ * if MAC link is at %MLO_AN_FIXED mode.
+ * @supported_interfaces: bitmap describing which PHY_INTERFACE_MODE_xxx
+ * are supported by the MAC/PCS.
++ * @pcs_interfaces: bitmap describing for which PHY_INTERFACE_MODE_xxx a
++ * dedicated PCS is required.
+ * @mac_capabilities: MAC pause/speed/duplex capabilities.
++ * @available_pcs: array of available phylink_pcs PCS
++ * @num_available_pcs: num of available phylink_pcs PCS
+ */
+ struct phylink_config {
+ struct device *dev;
+@@ -159,7 +163,11 @@ struct phylink_config {
+ void (*get_fixed_state)(struct phylink_config *config,
+ struct phylink_link_state *state);
+ DECLARE_PHY_INTERFACE_MASK(supported_interfaces);
++ DECLARE_PHY_INTERFACE_MASK(pcs_interfaces);
+ unsigned long mac_capabilities;
++
++ struct phylink_pcs **available_pcs;
++ unsigned int num_available_pcs;
+ };
+
+ void phylink_limit_mac_speed(struct phylink_config *config, u32 max_speed);
+@@ -417,6 +425,9 @@ struct phylink_pcs {
+ bool neg_mode;
+ bool poll;
+ bool rxc_always_on;
++
++ /* private: */
++ struct list_head list;
+ };
+
+ /**
--- /dev/null
+From 1cb4e56c3ba32ac1bce89dc9c34ef2dbc9b89ad4 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 31 Mar 2025 19:10:24 +0200
+Subject: [PATCH 3/7] net: phylink: add phylink_release_pcs() to externally
+ release a PCS
+
+Add phylink_release_pcs() to externally release a PCS from a phylink
+instance. This can be used to handle case when a single PCS needs to be
+removed and the phylink instance needs to be refreshed.
+
+On calling phylink_release_pcs(), the PCS will be removed from the
+phylink internal PCS list and the phylink supported_interfaces value is
+reparsed with the remaining PCS interfaces.
+
+Also a phylink resolve is triggered to handle the PCS removal.
+
+It's also added to phylink a flag to make phylink resolve reconfigure
+the interface mode (even if it didn't change). This is needed to handle
+the special case when the current PCS used by phylink is removed and a
+major_config is needed to propagae the configuration change. With this
+option enabled we also force mac_config even if the PHY link is not up
+for the in-band case.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/phy/phylink.c | 57 ++++++++++++++++++++++++++++++++++++++-
+ include/linux/phylink.h | 2 ++
+ 2 files changed, 58 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -93,6 +93,7 @@ struct phylink {
+ bool using_mac_select_pcs;
+ bool major_config_failed;
+ bool force_major_config;
++ bool reconfig_interface;
+
+ struct sfp_bus *sfp_bus;
+ bool sfp_may_have_phy;
+@@ -1064,6 +1065,55 @@ static void phylink_resolve_an_pause(str
+ }
+ }
+
++/**
++ * phylink_release_pcs - Removes a PCS from the phylink PCS available list
++ * @pcs: a pointer to the phylink_pcs struct to be released
++ *
++ * This function release a PCS from the phylink PCS available list if
++ * actually in use. It also refreshes the supported interfaces of the
++ * phylink instance by copying the supported interfaces from the phylink
++ * conf and merging the supported interfaces of the remaining available PCS
++ * in the list and trigger a resolve.
++ */
++void phylink_release_pcs(struct phylink_pcs *pcs)
++{
++ struct phylink *pl;
++
++ ASSERT_RTNL();
++
++ pl = pcs->phylink;
++ if (!pl)
++ return;
++
++ list_del(&pcs->list);
++ pcs->phylink = NULL;
++
++ /* Check if we are removing the PCS currently
++ * in use by phylink. If this is the case,
++ * force phylink resolve to reconfigure the interface
++ * mode and set the phylink PCS to NULL.
++ */
++ if (pl->pcs == pcs) {
++ mutex_lock(&pl->state_mutex);
++
++ pl->reconfig_interface = true;
++ pl->pcs = NULL;
++
++ mutex_unlock(&pl->state_mutex);
++ }
++
++ /* Refresh supported interfaces */
++ phy_interface_copy(pl->supported_interfaces,
++ pl->config->supported_interfaces);
++ list_for_each_entry(pcs, &pl->pcs_list, list)
++ phy_interface_or(pl->supported_interfaces,
++ pl->supported_interfaces,
++ pcs->supported_interfaces);
++
++ phylink_run_resolve(pl);
++}
++EXPORT_SYMBOL_GPL(phylink_release_pcs);
++
+ static unsigned int phylink_pcs_inband_caps(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+ {
+@@ -1818,6 +1868,10 @@ static void phylink_resolve(struct work_
+ if (pl->phydev)
+ link_state.link &= pl->phy_state.link;
+
++ /* Force mac_config if we need to reconfig the interface */
++ if (pl->reconfig_interface)
++ mac_config = true;
++
+ /* Only update if the PHY link is up */
+ if (pl->phydev && pl->phy_state.link) {
+ /* If the interface has changed, force a link down
+@@ -1852,7 +1906,7 @@ static void phylink_resolve(struct work_
+ phylink_apply_manual_flow(pl, &link_state);
+
+ if ((mac_config && link_state.interface != pl->link_config.interface) ||
+- pl->force_major_config) {
++ pl->force_major_config || pl->reconfig_interface) {
+ /* The interface has changed or a forced major configuration
+ * was requested, so force the link down and then reconfigure.
+ */
+@@ -1863,6 +1917,7 @@ static void phylink_resolve(struct work_
+ phylink_major_config(pl, false, &link_state);
+ pl->link_config.interface = link_state.interface;
+ pl->force_major_config = false;
++ pl->reconfig_interface = false;
+ }
+
+ /* If configuration of the interface failed, force the link down
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -632,6 +632,8 @@ void phylink_disconnect_phy(struct phyli
+ int phylink_set_fixed_link(struct phylink *,
+ const struct phylink_link_state *);
+
++void phylink_release_pcs(struct phylink_pcs *pcs);
++
+ void phylink_mac_change(struct phylink *, bool up);
+ void phylink_pcs_change(struct phylink_pcs *, bool up);
+
--- /dev/null
+From a90c644c73bbffd400cd3839fc17ffdfc69ea1e8 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 17 Mar 2025 01:46:53 +0100
+Subject: [PATCH 4/7] net: pcs: implement Firmware node support for PCS driver
+
+Implement the foundation of Firmware node support for PCS driver.
+
+To support this, implement a simple Provider API where a PCS driver can
+expose multiple PCS with an xlate .get function.
+
+PCS driver will have to call fwnode_pcs_add_provider() and pass the
+firmware node pointer and a xlate function to return the correct PCS for
+the passed #pcs-cells.
+
+This will register the PCS in a global list of providers so that
+consumer can access it.
+
+Consumer will then use fwnode_pcs_get() to get the actual PCS by passing
+the firmware node pointer and the index for #pcs-cells.
+
+For simple implementation where #pcs-cells is 0 and the PCS driver
+expose a single PCS, the xlate function fwnode_pcs_simple_get() is
+provided.
+
+For advanced implementation a custom xlate function is required.
+
+PCS driver on removal should first delete as a provider with
+the usage of fwnode_pcs_del_provider() and then call
+phylink_release_pcs() on every PCS the driver provides and
+
+A generic function fwnode_phylink_pcs_parse() is provided for any MAC
+driver that will declare PCS in DT (or ACPI).
+This function will parse "pcs-handle" property and fill the passed array
+with the parsed PCS in availabel_pcs up to the passed num_pcs value.
+It's also possible to pass NULL as array to only parse the PCS and
+update the num_pcs value with the count of scanned PCS.
+
+Co-developed-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/pcs/Kconfig | 7 ++
+ drivers/net/pcs/Makefile | 1 +
+ drivers/net/pcs/pcs.c | 201 +++++++++++++++++++++++++++++++
+ include/linux/pcs/pcs-provider.h | 41 +++++++
+ include/linux/pcs/pcs.h | 56 +++++++++
+ 5 files changed, 306 insertions(+)
+ create mode 100644 drivers/net/pcs/pcs.c
+ create mode 100644 include/linux/pcs/pcs-provider.h
+ create mode 100644 include/linux/pcs/pcs.h
+
+--- a/drivers/net/pcs/Kconfig
++++ b/drivers/net/pcs/Kconfig
+@@ -5,6 +5,13 @@
+
+ menu "PCS device drivers"
+
++config FWNODE_PCS
++ tristate
++ depends on (ACPI || OF)
++ depends on PHYLINK
++ help
++ Firmware node PCS accessors
++
+ config PCS_XPCS
+ tristate "Synopsys DesignWare Ethernet XPCS"
+ select PHYLINK
+--- a/drivers/net/pcs/Makefile
++++ b/drivers/net/pcs/Makefile
+@@ -1,6 +1,7 @@
+ # SPDX-License-Identifier: GPL-2.0
+ # Makefile for Linux PCS drivers
+
++obj-$(CONFIG_FWNODE_PCS) += pcs.o
+ pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-plat.o \
+ pcs-xpcs-nxp.o pcs-xpcs-wx.o
+
+--- /dev/null
++++ b/drivers/net/pcs/pcs.c
+@@ -0,0 +1,201 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++
++#include <linux/mutex.h>
++#include <linux/property.h>
++#include <linux/phylink.h>
++#include <linux/pcs/pcs.h>
++#include <linux/pcs/pcs-provider.h>
++
++MODULE_DESCRIPTION("PCS library");
++MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
++MODULE_LICENSE("GPL");
++
++struct fwnode_pcs_provider {
++ struct list_head link;
++
++ struct fwnode_handle *fwnode;
++ struct phylink_pcs *(*get)(struct fwnode_reference_args *pcsspec,
++ void *data);
++
++ void *data;
++};
++
++static LIST_HEAD(fwnode_pcs_providers);
++static DEFINE_MUTEX(fwnode_pcs_mutex);
++
++struct phylink_pcs *fwnode_pcs_simple_get(struct fwnode_reference_args *pcsspec,
++ void *data)
++{
++ return data;
++}
++EXPORT_SYMBOL_GPL(fwnode_pcs_simple_get);
++
++int fwnode_pcs_add_provider(struct fwnode_handle *fwnode,
++ struct phylink_pcs *(*get)(struct fwnode_reference_args *pcsspec,
++ void *data),
++ void *data)
++{
++ struct fwnode_pcs_provider *pp;
++
++ if (!fwnode)
++ return 0;
++
++ pp = kzalloc(sizeof(*pp), GFP_KERNEL);
++ if (!pp)
++ return -ENOMEM;
++
++ pp->fwnode = fwnode_handle_get(fwnode);
++ pp->data = data;
++ pp->get = get;
++
++ mutex_lock(&fwnode_pcs_mutex);
++ list_add(&pp->link, &fwnode_pcs_providers);
++ mutex_unlock(&fwnode_pcs_mutex);
++ pr_debug("Added pcs provider from %pfwf\n", fwnode);
++
++ fwnode_dev_initialized(fwnode, true);
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(fwnode_pcs_add_provider);
++
++void fwnode_pcs_del_provider(struct fwnode_handle *fwnode)
++{
++ struct fwnode_pcs_provider *pp;
++
++ if (!fwnode)
++ return;
++
++ mutex_lock(&fwnode_pcs_mutex);
++ list_for_each_entry(pp, &fwnode_pcs_providers, link) {
++ if (pp->fwnode == fwnode) {
++ list_del(&pp->link);
++ fwnode_dev_initialized(pp->fwnode, false);
++ fwnode_handle_put(pp->fwnode);
++ kfree(pp);
++ break;
++ }
++ }
++ mutex_unlock(&fwnode_pcs_mutex);
++}
++EXPORT_SYMBOL_GPL(fwnode_pcs_del_provider);
++
++static int fwnode_parse_pcsspec(const struct fwnode_handle *fwnode, int index,
++ const char *name,
++ struct fwnode_reference_args *out_args)
++{
++ int ret = -ENOENT;
++
++ if (!fwnode)
++ return -ENOENT;
++
++ if (name)
++ index = fwnode_property_match_string(fwnode, "pcs-names",
++ name);
++
++ ret = fwnode_property_get_reference_args(fwnode, "pcs-handle",
++ "#pcs-cells",
++ -1, index, out_args);
++ if (ret || (name && index < 0))
++ return ret;
++
++ return 0;
++}
++
++static struct phylink_pcs *
++fwnode_pcs_get_from_pcsspec(struct fwnode_reference_args *pcsspec)
++{
++ struct fwnode_pcs_provider *provider;
++ struct phylink_pcs *pcs = ERR_PTR(-EPROBE_DEFER);
++
++ if (!pcsspec)
++ return ERR_PTR(-EINVAL);
++
++ mutex_lock(&fwnode_pcs_mutex);
++ list_for_each_entry(provider, &fwnode_pcs_providers, link) {
++ if (provider->fwnode == pcsspec->fwnode) {
++ pcs = provider->get(pcsspec, provider->data);
++ if (!IS_ERR(pcs))
++ break;
++ }
++ }
++ mutex_unlock(&fwnode_pcs_mutex);
++
++ return pcs;
++}
++
++static struct phylink_pcs *__fwnode_pcs_get(struct fwnode_handle *fwnode,
++ int index, const char *con_id)
++{
++ struct fwnode_reference_args pcsspec;
++ struct phylink_pcs *pcs;
++ int ret;
++
++ ret = fwnode_parse_pcsspec(fwnode, index, con_id, &pcsspec);
++ if (ret)
++ return ERR_PTR(ret);
++
++ pcs = fwnode_pcs_get_from_pcsspec(&pcsspec);
++ fwnode_handle_put(pcsspec.fwnode);
++
++ return pcs;
++}
++
++struct phylink_pcs *fwnode_pcs_get(struct fwnode_handle *fwnode, int index)
++{
++ return __fwnode_pcs_get(fwnode, index, NULL);
++}
++EXPORT_SYMBOL_GPL(fwnode_pcs_get);
++
++static int fwnode_phylink_pcs_count(struct fwnode_handle *fwnode,
++ unsigned int *num_pcs)
++{
++ struct fwnode_reference_args out_args;
++ int index = 0;
++ int ret;
++
++ while (true) {
++ ret = fwnode_property_get_reference_args(fwnode, "pcs-handle",
++ "#pcs-cells",
++ -1, index, &out_args);
++ /* We expect to reach an -ENOENT error while counting */
++ if (ret)
++ break;
++
++ fwnode_handle_put(out_args.fwnode);
++ index++;
++ }
++
++ /* Update num_pcs with parsed PCS */
++ *num_pcs = index;
++
++ /* Return error if we didn't found any PCS */
++ return index > 0 ? 0 : -ENOENT;
++}
++
++int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode,
++ struct phylink_pcs **available_pcs,
++ unsigned int *num_pcs)
++{
++ int i;
++
++ if (!fwnode_property_present(fwnode, "pcs-handle"))
++ return -ENODEV;
++
++ /* With available_pcs NULL, only count the PCS */
++ if (!available_pcs)
++ return fwnode_phylink_pcs_count(fwnode, num_pcs);
++
++ for (i = 0; i < *num_pcs; i++) {
++ struct phylink_pcs *pcs;
++
++ pcs = fwnode_pcs_get(fwnode, i);
++ if (IS_ERR(pcs))
++ return PTR_ERR(pcs);
++
++ available_pcs[i] = pcs;
++ }
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(fwnode_phylink_pcs_parse);
+--- /dev/null
++++ b/include/linux/pcs/pcs-provider.h
+@@ -0,0 +1,41 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++#ifndef __LINUX_PCS_PROVIDER_H
++#define __LINUX_PCS_PROVIDER_H
++
++/**
++ * fwnode_pcs_simple_get - Simple xlate function to retrieve PCS
++ * @pcsspec: reference arguments
++ * @data: Context data (assumed assigned to the single PCS)
++ *
++ * Returns the PCS. (pointed by data)
++ */
++struct phylink_pcs *fwnode_pcs_simple_get(struct fwnode_reference_args *pcsspec,
++ void *data);
++
++/**
++ * fwnode_pcs_add_provider - Registers a new PCS provider
++ * @np: Firmware node
++ * @get: xlate function to retrieve the PCS
++ * @data: Context data
++ *
++ * Register and add a new PCS to the global providers list
++ * for the firmware node. A function to get the PCS from
++ * firmware node with the use fwnode reference arguments.
++ * To the get function is also passed the interface type
++ * requested for the PHY. PCS driver will use the passed
++ * interface to understand if the PCS can support it or not.
++ *
++ * Returns 0 on success or -ENOMEM on allocation failure.
++ */
++int fwnode_pcs_add_provider(struct fwnode_handle *fwnode,
++ struct phylink_pcs *(*get)(struct fwnode_reference_args *pcsspec,
++ void *data),
++ void *data);
++
++/**
++ * fwnode_pcs_del_provider - Removes a PCS provider
++ * @fwnode: Firmware node
++ */
++void fwnode_pcs_del_provider(struct fwnode_handle *fwnode);
++
++#endif /* __LINUX_PCS_PROVIDER_H */
+--- /dev/null
++++ b/include/linux/pcs/pcs.h
+@@ -0,0 +1,56 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++#ifndef __LINUX_PCS_H
++#define __LINUX_PCS_H
++
++#include <linux/phylink.h>
++
++#if IS_ENABLED(CONFIG_FWNODE_PCS)
++/**
++ * fwnode_pcs_get - Retrieves a PCS from a firmware node
++ * @fwnode: firmware node
++ * @index: index fwnode PCS handle in firmware node
++ *
++ * Get a PCS from the firmware node at index.
++ *
++ * Returns a pointer to the phylink_pcs or a negative
++ * error pointer. Can return -EPROBE_DEFER if the PCS is not
++ * present in global providers list (either due to driver
++ * still needs to be probed or it failed to probe/removed)
++ */
++struct phylink_pcs *fwnode_pcs_get(struct fwnode_handle *fwnode,
++ int index);
++
++/**
++ * fwnode_phylink_pcs_parse - generic PCS parse for fwnode PCS provider
++ * @fwnode: firmware node
++ * @available_pcs: pointer to preallocated array of PCS
++ * @num_pcs: where to store count of parsed PCS
++ *
++ * Generic helper function to fill available_pcs array with PCS parsed
++ * from a "pcs-handle" fwnode property defined in firmware node up to
++ * passed num_pcs.
++ *
++ * If available_pcs is NULL, num_pcs is updated with the count of the
++ * parsed PCS.
++ *
++ * Returns 0 or a negative error.
++ */
++int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode,
++ struct phylink_pcs **available_pcs,
++ unsigned int *num_pcs);
++#else
++static inline struct phylink_pcs *fwnode_pcs_get(struct fwnode_handle *fwnode,
++ int index)
++{
++ return ERR_PTR(-ENOENT);
++}
++
++static inline int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode,
++ struct phylink_pcs **available_pcs,
++ unsigned int *num_pcs)
++{
++ return -EOPNOTSUPP;
++}
++#endif
++
++#endif /* __LINUX_PCS_H */
--- /dev/null
+From 684e49a015f2c5ae95ba968bb21ffc8fc36a2c7f Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sun, 6 Apr 2025 03:28:22 +0200
+Subject: [PATCH 5/7] net: phylink: support late PCS provider attach
+
+Add support in phylink for late PCS provider attach to a phylink
+instance. This works by creating a global notifier for the PCS provider
+and making each phylink instance that makes use of fwnode subscribe to
+this notifier.
+
+The PCS notifier will emit the event FWNODE_PCS_PROVIDER_ADD every time
+a new PCS provider is added.
+
+phylink will then react to this event and will call the new function
+fwnode_phylink_pcs_get_from_fwnode() that will check if the PCS fwnode
+provided by the event is present in the phy-handle property of the
+phylink instance.
+
+If a related PCS is found, then such PCS is added to the phylink
+instance PCS list.
+
+Then we link the PCS to the phylink instance if it's not disable and we
+refresh the supported interfaces of the phylink instance.
+
+Finally we check if we are in a major_config_failed scenario and trigger
+an interface reconfiguration in the next phylink resolve.
+
+If link was previously torn down due to removal of PCS, the link will be
+established again as the PCS came back and is not available to phylink.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/pcs/pcs.c | 34 +++++++++++++++++++++++++
+ drivers/net/phy/phylink.c | 52 +++++++++++++++++++++++++++++++++++++++
+ include/linux/pcs/pcs.h | 48 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 134 insertions(+)
+
+--- a/drivers/net/pcs/pcs.c
++++ b/drivers/net/pcs/pcs.c
+@@ -22,6 +22,13 @@ struct fwnode_pcs_provider {
+
+ static LIST_HEAD(fwnode_pcs_providers);
+ static DEFINE_MUTEX(fwnode_pcs_mutex);
++static BLOCKING_NOTIFIER_HEAD(fwnode_pcs_notify_list);
++
++int register_fwnode_pcs_notifier(struct notifier_block *nb)
++{
++ return blocking_notifier_chain_register(&fwnode_pcs_notify_list, nb);
++}
++EXPORT_SYMBOL_GPL(register_fwnode_pcs_notifier);
+
+ struct phylink_pcs *fwnode_pcs_simple_get(struct fwnode_reference_args *pcsspec,
+ void *data)
+@@ -55,6 +62,10 @@ int fwnode_pcs_add_provider(struct fwnod
+
+ fwnode_dev_initialized(fwnode, true);
+
++ blocking_notifier_call_chain(&fwnode_pcs_notify_list,
++ FWNODE_PCS_PROVIDER_ADD,
++ fwnode);
++
+ return 0;
+ }
+ EXPORT_SYMBOL_GPL(fwnode_pcs_add_provider);
+@@ -147,6 +158,29 @@ struct phylink_pcs *fwnode_pcs_get(struc
+ }
+ EXPORT_SYMBOL_GPL(fwnode_pcs_get);
+
++struct phylink_pcs *
++fwnode_phylink_pcs_get_from_fwnode(struct fwnode_handle *fwnode,
++ struct fwnode_handle *pcs_fwnode)
++{
++ struct fwnode_reference_args pcsspec;
++ int i = 0;
++ int ret;
++
++ while (true) {
++ ret = fwnode_parse_pcsspec(fwnode, i, NULL, &pcsspec);
++ if (ret)
++ break;
++
++ if (pcsspec.fwnode == pcs_fwnode)
++ break;
++
++ i++;
++ }
++
++ return fwnode_pcs_get(fwnode, i);
++}
++EXPORT_SYMBOL_GPL(fwnode_phylink_pcs_get_from_fwnode);
++
+ static int fwnode_phylink_pcs_count(struct fwnode_handle *fwnode,
+ unsigned int *num_pcs)
+ {
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -12,6 +12,7 @@
+ #include <linux/netdevice.h>
+ #include <linux/of.h>
+ #include <linux/of_mdio.h>
++#include <linux/pcs/pcs.h>
+ #include <linux/phy.h>
+ #include <linux/phy_fixed.h>
+ #include <linux/phylink.h>
+@@ -67,6 +68,7 @@ struct phylink {
+
+ /* List of available PCS */
+ struct list_head pcs_list;
++ struct notifier_block fwnode_pcs_nb;
+
+ /* What interface are supported by the current link.
+ * Can change on removal or addition of new PCS.
+@@ -2039,6 +2041,51 @@ int phylink_set_fixed_link(struct phylin
+ }
+ EXPORT_SYMBOL_GPL(phylink_set_fixed_link);
+
++static int pcs_provider_notify(struct notifier_block *self,
++ unsigned long val, void *data)
++{
++ struct phylink *pl = container_of(self, struct phylink, fwnode_pcs_nb);
++ struct fwnode_handle *pcs_fwnode = data;
++ struct phylink_pcs *pcs;
++
++ /* Check if the just added PCS provider is
++ * in the phylink instance phy-handle property
++ */
++ pcs = fwnode_phylink_pcs_get_from_fwnode(dev_fwnode(pl->config->dev),
++ pcs_fwnode);
++ if (IS_ERR(pcs))
++ return NOTIFY_DONE;
++
++ /* Add the PCS */
++ rtnl_lock();
++
++ list_add(&pcs->list, &pl->pcs_list);
++
++ /* Link phylink if we are started */
++ if (!pl->phylink_disable_state)
++ pcs->phylink = pl;
++
++ /* Refresh supported interfaces */
++ phy_interface_copy(pl->supported_interfaces,
++ pl->config->supported_interfaces);
++ list_for_each_entry(pcs, &pl->pcs_list, list)
++ phy_interface_or(pl->supported_interfaces,
++ pl->supported_interfaces,
++ pcs->supported_interfaces);
++
++ mutex_lock(&pl->state_mutex);
++ /* Force an interface reconfig if major config fail */
++ if (pl->major_config_failed)
++ pl->reconfig_interface = true;
++ mutex_unlock(&pl->state_mutex);
++
++ rtnl_unlock();
++
++ phylink_run_resolve(pl);
++
++ return NOTIFY_OK;
++}
++
+ /**
+ * phylink_create() - create a phylink instance
+ * @config: a pointer to the target &struct phylink_config
+@@ -2099,6 +2146,11 @@ struct phylink *phylink_create(struct ph
+ pl->supported_interfaces,
+ pcs->supported_interfaces);
+
++ if (!phy_interface_empty(config->pcs_interfaces)) {
++ pl->fwnode_pcs_nb.notifier_call = pcs_provider_notify;
++ register_fwnode_pcs_notifier(&pl->fwnode_pcs_nb);
++ }
++
+ pl->config = config;
+ if (config->type == PHYLINK_NETDEV) {
+ pl->netdev = to_net_dev(config->dev);
+--- a/include/linux/pcs/pcs.h
++++ b/include/linux/pcs/pcs.h
+@@ -4,8 +4,25 @@
+
+ #include <linux/phylink.h>
+
++enum fwnode_pcs_notify_event {
++ FWNODE_PCS_PROVIDER_ADD,
++};
++
+ #if IS_ENABLED(CONFIG_FWNODE_PCS)
+ /**
++ * register_fwnode_pcs_notifier - Register a notifier block for fwnode
++ * PCS events
++ * @nb: pointer to the notifier block
++ *
++ * Registers a notifier block to the fwnode_pcs_notify_list blocking
++ * notifier chain. This allows phylink instance to subscribe for
++ * PCS provider events.
++ *
++ * Returns 0 or a negative error.
++ */
++int register_fwnode_pcs_notifier(struct notifier_block *nb);
++
++/**
+ * fwnode_pcs_get - Retrieves a PCS from a firmware node
+ * @fwnode: firmware node
+ * @index: index fwnode PCS handle in firmware node
+@@ -21,6 +38,25 @@ struct phylink_pcs *fwnode_pcs_get(struc
+ int index);
+
+ /**
++ * fwnode_phylink_pcs_get_from_fwnode - Retrieves the PCS provided
++ * by the firmware node from a
++ * firmware node
++ * @fwnode: firmware node
++ * @pcs_fwnode: PCS firmware node
++ *
++ * Parse 'pcs-handle' in 'fwnode' and get the PCS that match
++ * 'pcs_fwnode' firmware node.
++ *
++ * Returns a pointer to the phylink_pcs or a negative
++ * error pointer. Can return -EPROBE_DEFER if the PCS is not
++ * present in global providers list (either due to driver
++ * still needs to be probed or it failed to probe/removed)
++ */
++struct phylink_pcs *
++fwnode_phylink_pcs_get_from_fwnode(struct fwnode_handle *fwnode,
++ struct fwnode_handle *pcs_fwnode);
++
++/**
+ * fwnode_phylink_pcs_parse - generic PCS parse for fwnode PCS provider
+ * @fwnode: firmware node
+ * @available_pcs: pointer to preallocated array of PCS
+@@ -39,11 +75,23 @@ int fwnode_phylink_pcs_parse(struct fwno
+ struct phylink_pcs **available_pcs,
+ unsigned int *num_pcs);
+ #else
++static inline int register_fwnode_pcs_notifier(struct notifier_block *nb)
++{
++ return -EOPNOTSUPP;
++}
++
+ static inline struct phylink_pcs *fwnode_pcs_get(struct fwnode_handle *fwnode,
+ int index)
+ {
+ return ERR_PTR(-ENOENT);
+ }
++
++static inline struct phylink_pcs *
++fwnode_phylink_pcs_get_from_fwnode(struct fwnode_handle *fwnode,
++ struct fwnode_handle *pcs_fwnode)
++{
++ return ERR_PTR(-ENOENT);
++}
+
+ static inline int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode,
+ struct phylink_pcs **available_pcs,
--- /dev/null
+From c5d151dccce7deb62620a7b16418c0d6d6a59720 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 17 Mar 2025 23:07:45 +0100
+Subject: [PATCH 6/7] dt-bindings: net: ethernet-controller: permit to define
+ multiple PCS
+
+Drop the limitation of a single PCS in pcs-handle property. Multiple PCS
+can be defined for an ethrnet-controller node to support various PHY
+interface mode type.
+
+It's very common for SoCs to have a dedicated PCS for SGMII mode and one
+for USXGMII mode.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ Documentation/devicetree/bindings/net/ethernet-controller.yaml | 2 --
+ 1 file changed, 2 deletions(-)
+
+--- a/Documentation/devicetree/bindings/net/ethernet-controller.yaml
++++ b/Documentation/devicetree/bindings/net/ethernet-controller.yaml
+@@ -110,8 +110,6 @@ properties:
+
+ pcs-handle:
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+- items:
+- maxItems: 1
+ description:
+ Specifies a reference to a node representing a PCS PHY device on a MDIO
+ bus to link with an external PHY (phy-handle) if exists.
-From d5fb4ad1beec53ca5d3b44d9b88598ed4ab0b34d Mon Sep 17 00:00:00 2001
+From 4b1dde131a237455e41985fdc95306cd2f1b8a0a Mon Sep 17 00:00:00 2001
From: Christian Marangi <ansuelsmth@gmail.com>
Date: Fri, 9 May 2025 16:36:22 +0200
-Subject: [PATCH 1/6] net: phylink: add .pcs_link_down PCS OP
+Subject: [PATCH 7/7] net: phylink: add .pcs_link_down PCS OP
Permit for PCS driver to define specific operation to torn down the link
between the MAC and the PCS.
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -1088,6 +1088,12 @@ static unsigned int phylink_inband_caps(
- return phylink_pcs_inband_caps(pcs, interface);
+@@ -1178,6 +1178,12 @@ static void phylink_pcs_link_up(struct p
+ pcs->ops->pcs_link_up(pcs, neg_mode, interface, speed, duplex);
}
+static void phylink_pcs_link_down(struct phylink_pcs *pcs)
+ pcs->ops->pcs_link_down(pcs);
+}
+
- static void phylink_pcs_poll_stop(struct phylink *pl)
- {
- if (pl->cfg_link_an_mode == MLO_AN_INBAND)
-@@ -1651,6 +1657,8 @@ static void phylink_link_down(struct phy
+ /* Query inband for a specific interface mode, asking the MAC for the
+ * PCS which will be used to handle the interface mode.
+ */
+@@ -1817,6 +1823,8 @@ static void phylink_link_down(struct phy
if (ndev)
netif_carrier_off(ndev);
phylink_info(pl, "Link is Down\n");
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
-@@ -431,6 +431,7 @@ struct phylink_pcs {
+@@ -443,6 +443,7 @@ struct phylink_pcs {
+ * @pcs_an_restart: restart 802.3z BaseX autonegotiation.
+ * @pcs_link_up: program the PCS for the resolved link configuration
* (where necessary).
++ * @pcs_link_down: torn down link between MAC and PCS.
* @pcs_pre_init: configure PCS components necessary for MAC hardware
* initialization e.g. RX clock for stmmac.
-+ * @pcs_link_down: torn down link between MAC and PCS.
*/
- struct phylink_pcs_ops {
- int (*pcs_validate)(struct phylink_pcs *pcs, unsigned long *supported,
-@@ -453,6 +454,7 @@ struct phylink_pcs_ops {
+@@ -466,6 +467,7 @@ struct phylink_pcs_ops {
+ void (*pcs_an_restart)(struct phylink_pcs *pcs);
void (*pcs_link_up)(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface, int speed, int duplex);
- int (*pcs_pre_init)(struct phylink_pcs *pcs);
+ void (*pcs_link_down)(struct phylink_pcs *pcs);
+ int (*pcs_pre_init)(struct phylink_pcs *pcs);
};
- #if 0 /* For kernel-doc purposes only. */
case PHY_INTERFACE_MODE_QUSGMII:
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -250,6 +250,7 @@ static int phylink_interface_max_speed(p
+@@ -266,6 +266,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;
-@@ -564,6 +565,7 @@ static unsigned long phylink_get_capabil
+@@ -580,6 +581,7 @@ static unsigned long phylink_get_capabil
break;
case PHY_INTERFACE_MODE_2500BASEX: