linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [net-next PATCH v6 00/10] Add PCS core support
@ 2025-06-10 23:31 Sean Anderson
  2025-06-10 23:31 ` [net-next PATCH v6 01/10] dt-bindings: net: Add Xilinx PCS Sean Anderson
                   ` (10 more replies)
  0 siblings, 11 replies; 21+ messages in thread
From: Sean Anderson @ 2025-06-10 23:31 UTC (permalink / raw)
  To: netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King
  Cc: Vineeth Karumanchi, Heiner Kallweit, linux-kernel, Kory Maincent,
	Daniel Golle, Simon Horman, Christian Marangi, Lei Wei,
	Sean Anderson, Alexandre Belloni, Claudiu Beznea, Claudiu Manoil,
	Conor Dooley, Ioana Ciornei, Jonathan Corbet, Krzysztof Kozlowski,
	Michal Simek, Nicolas Ferre, Radhey Shyam Pandey, Rob Herring,
	Rob Herring, Robert Hancock, Saravana Kannan, UNGLinuxDriver,
	Vladimir Oltean, devicetree, imx, linux-arm-kernel, linux-doc,
	linux-stm32

This series adds support for creating PCSs as devices on a bus with a
driver (patch 3). As initial users,

- The Lynx PCS (and all of its users) is converted to this system
  (patches 4-5)
- The Xilinx PCS is broken out from the AXI Ethernet driver (patches 6-7)
- The Cadence MACB driver is converted to support external PCSs (namely
  the Xilinx PCS) (patches 8-9).

Care has been taken to ensure backwards-compatibility. The main source
of this is that many PCS devices lack compatibles and get detected as
PHYs. To address this, pcs_get_by_fwnode_compat allows drivers to edit
the devicetree to add appropriate compatibles.

There is another series [1] with the same goal by Christian Marangi. In
comparison, I believe this series

- Implements a simpler and more-robust method of PCS access.
- Provides a more-direct upgrade path for existing MAC and PCS drivers.

[1] https://lore.kernel.org/netdev/20250511201250.3789083-1-ansuelsmth@gmail.com/

Changes in v6:
- Define lynx_pcs_of_match only when OF_MATCH is enabled
- Fix use of spin_lock instead of spin_unlock
- Introduce config for compatibility helpers
- Move axienet_pcs_fixup to AXI Ethernet commit
- Reduce line lengths to under 80 characters
- Remove duplicate include of phylink.h
- Remove unneccessary Kconfig selects
- Use an empty statement for next label
- Use guard(srcu)

Changes in v5:
- Export get_phy_c22_id when it is used
- Expose bind attributes, since there is no issue in doing so
- Rebase onto net-next
- Use MDIO_BUS instead of MDIO_DEVICE

Changes in v4:
- Add a note about the license
- Adjust variable ordering in pcs_find_fwnode
- Annotate pcs_wrapper.wrapped with __rcu
- Fix PCS lookup functions missing ERR_PTR casts
- Fix documentation for devm_pcs_register_full
- Fix incorrect condition in pcs_post_config
- Fix linking when PCS && !OF_DYNAMIC
- Fix linking when PCS && OF_DYNAMIC && PHYLIB=m
- Re-add documentation for axienet_xilinx_pcs_get that was accidentally
  removed
- Reduce line lengths to under 80 characters
- Remove unused dev parameter to pcs_put
- Use a spinlock instead of a mutex to protect pcs_wrappers

Changes in v3:
- Add '>' modifier for paragraph to description
- Adjust axienet_xilinx_pcs_get for changes to pcs_find_fwnode API
- Drop patches destined for other trees, as they have either already
  been applied or are no longer necessary.
- Edit description to reference clocks instead of resets
- Remove support for #pcs-cells. Upon further investigation, the
  requested functionality can be accomplished by specifying the PCS's
  fwnode manually.
- Select PCS_XILINX unconditionally

Changes in v2:
- Add fallbacks for pcs_get* and pcs_put
- Add support for #pcs-cells
- Change base compatible to just xlnx,pcs
- Change compatible to just xlnx,pcs
- Defer devicetree updates for another series
- Drop #clock-cells description
- Drop PCS_ALTERA_TSE which was accidentally added while rebasing
- Move #clock-cells after compatible
- Move update to macb_pcs_get_state to previous patch
- Remove outdated comment
- Remove second example
- Remove unused variable
- Remove unused variable lynx_properties
- Rename pcs-modes to xlnx,pcs-modes
- Reorder pcs_handle to come before suffix props
- Reword commit message
- Rework xilinx_pcs_validate to just clear out half-duplex modes instead
  of constraining modes based on the interface.

Sean Anderson (9):
  dt-bindings: net: Add Xilinx PCS
  net: phylink: Support setting PCS link change callbacks
  net: pcs: Add subsystem
  net: pcs: lynx: Convert to an MDIO driver
  net: pcs: Add Xilinx PCS driver
  net: axienet: Convert to use PCS subsystem
  net: macb: Move most of mac_config to mac_prepare
  net: macb: Support external PCSs
  of: property: Add device link support for PCS

Vladimir Oltean (1):
  net: dsa: ocelot: suppress PHY device scanning on the internal MDIO
    bus

 .../devicetree/bindings/net/xilinx,pcs.yaml   | 114 ++++
 Documentation/networking/index.rst            |   1 +
 Documentation/networking/kapi.rst             |   4 +
 Documentation/networking/pcs.rst              | 102 +++
 MAINTAINERS                                   |   8 +
 drivers/net/dsa/ocelot/felix_vsc9959.c        |  15 +-
 drivers/net/dsa/ocelot/seville_vsc9953.c      |  16 +-
 drivers/net/ethernet/altera/altera_tse_main.c |   7 +-
 drivers/net/ethernet/cadence/macb.h           |   1 +
 drivers/net/ethernet/cadence/macb_main.c      | 229 ++++---
 drivers/net/ethernet/freescale/dpaa2/Kconfig  |   1 +
 .../net/ethernet/freescale/dpaa2/dpaa2-mac.c  |  11 +-
 .../net/ethernet/freescale/enetc/enetc_pf.c   |   8 +-
 .../net/ethernet/freescale/enetc/enetc_pf.h   |   1 -
 .../freescale/enetc/enetc_pf_common.c         |   4 +-
 drivers/net/ethernet/freescale/fman/Kconfig   |   2 +-
 .../net/ethernet/freescale/fman/fman_memac.c  |  25 +-
 drivers/net/ethernet/stmicro/stmmac/Kconfig   |   1 +
 .../ethernet/stmicro/stmmac/dwmac-socfpga.c   |   6 +-
 drivers/net/ethernet/xilinx/Kconfig           |  13 +
 drivers/net/ethernet/xilinx/xilinx_axienet.h  |   4 +-
 .../net/ethernet/xilinx/xilinx_axienet_main.c | 160 +++--
 drivers/net/pcs/Kconfig                       |  47 +-
 drivers/net/pcs/Makefile                      |   4 +
 drivers/net/pcs/core.c                        | 628 ++++++++++++++++++
 drivers/net/pcs/pcs-lynx.c                    | 113 ++--
 drivers/net/pcs/pcs-xilinx.c                  | 427 ++++++++++++
 drivers/net/phy/phy_device.c                  |   3 +-
 drivers/net/phy/phylink.c                     |  24 +-
 drivers/of/property.c                         |   2 +
 include/linux/pcs-lynx.h                      |  13 +-
 include/linux/pcs.h                           | 205 ++++++
 include/linux/phy.h                           |   1 +
 include/linux/phylink.h                       |  27 +-
 34 files changed, 1931 insertions(+), 296 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/net/xilinx,pcs.yaml
 create mode 100644 Documentation/networking/pcs.rst
 create mode 100644 drivers/net/pcs/core.c
 create mode 100644 drivers/net/pcs/pcs-xilinx.c
 create mode 100644 include/linux/pcs.h

-- 
2.35.1.1320.gc452695387.dirty


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

* [net-next PATCH v6 01/10] dt-bindings: net: Add Xilinx PCS
  2025-06-10 23:31 [net-next PATCH v6 00/10] Add PCS core support Sean Anderson
@ 2025-06-10 23:31 ` Sean Anderson
  2025-06-10 23:31 ` [net-next PATCH v6 02/10] net: phylink: Support setting PCS link change callbacks Sean Anderson
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 21+ messages in thread
From: Sean Anderson @ 2025-06-10 23:31 UTC (permalink / raw)
  To: netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King
  Cc: Vineeth Karumanchi, Heiner Kallweit, linux-kernel, Kory Maincent,
	Daniel Golle, Simon Horman, Christian Marangi, Lei Wei,
	Sean Anderson, Rob Herring, Conor Dooley, Krzysztof Kozlowski,
	Michal Simek, Radhey Shyam Pandey, Robert Hancock, devicetree

Add a binding for the Xilinx 1G/2.5G Ethernet PCS/PMA or SGMII LogiCORE
IP. This device is a soft device typically used to adapt between GMII
and SGMII or 1000BASE-X (possbilty in combination with a serdes).
pcs-modes reflects the modes available with the as configured when the
device is synthesized. Multiple modes may be specified if dynamic
reconfiguration is supported.

One PCS may contain "shared logic in core" which can be connected to
other PCSs with "shared logic in example design." This primarily refers
to clocking resources, allowing a reference clock to be shared by a bank
of PCSs. To support this, if #clock-cells is defined then the PCS will
register itself as a clock provider for other PCSs.

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---

(no changes since v3)

Changes in v3:
- Add '>' modifier for paragraph to description
- Edit description to reference clocks instead of resets

Changes in v2:
- Change base compatible to just xlnx,pcs
- Drop #clock-cells description
- Move #clock-cells after compatible
- Remove second example
- Rename pcs-modes to xlnx,pcs-modes
- Reword commit message

 .../devicetree/bindings/net/xilinx,pcs.yaml   | 114 ++++++++++++++++++
 1 file changed, 114 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/xilinx,pcs.yaml

diff --git a/Documentation/devicetree/bindings/net/xilinx,pcs.yaml b/Documentation/devicetree/bindings/net/xilinx,pcs.yaml
new file mode 100644
index 000000000000..11bbae6936eb
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/xilinx,pcs.yaml
@@ -0,0 +1,114 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/xilinx,pcs.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Xilinx 1G/2.5G Ethernet PCS/PMA or SGMII LogiCORE IP
+
+maintainers:
+  - Sean Anderson <sean.anderson@seco.com>
+
+description: >
+  This is a soft device which implements the PCS and (depending on
+  configuration) PMA layers of an IEEE Ethernet PHY. On the MAC side, it
+  implements GMII. It may have an attached SERDES (internal or external), or
+  may directly use LVDS IO resources. Depending on the configuration, it may
+  implement 1000BASE-X, SGMII, 2500BASE-X, or 2.5G SGMII.
+
+  This device has a notion of "shared logic" such as reset and clocking
+  resources which must be shared between multiple PCSs using the same I/O
+  banks. Each PCS can be configured to have the shared logic in the "core"
+  (instantiated internally and made available to other PCSs) or in the "example
+  design" (provided by another PCS). PCSs with shared logic in the core provide
+  a clock for other PCSs in the same bank.
+
+properties:
+  compatible:
+    items:
+      - const: xlnx,pcs-16.2
+      - const: xlnx,pcs
+
+  reg:
+    maxItems: 1
+
+  "#clock-cells":
+    const: 0
+
+  clocks:
+    items:
+      - description:
+          The reference clock for the PCS. Depending on your setup, this may be
+          the gtrefclk, refclk, clk125m signal, or clocks from another PCS.
+
+  clock-names:
+    const: refclk
+
+  done-gpios:
+    maxItems: 1
+    description:
+      GPIO connected to the reset-done output, if present.
+
+  interrupts:
+    items:
+      - description:
+          The an_interrupt autonegotiation-complete interrupt.
+
+  interrupt-names:
+    const: an
+
+  xlnx,pcs-modes:
+    description:
+      The interfaces that the PCS supports. Multiple interfaces may be
+      specified if dynamic reconfiguration is enabled.
+    oneOf:
+      - const: sgmii
+      - const: 1000base-x
+      - const: 2500base-x
+      - items:
+          - const: sgmii
+          - const: 1000base-x
+
+  reset-gpios:
+    maxItems: 1
+    description:
+      GPIO connected to the reset input.
+
+required:
+  - compatible
+  - reg
+  - xlnx,pcs-modes
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        pcs0: ethernet-pcs@0 {
+            compatible = "xlnx,pcs-16.2", "xlnx,pcs";
+            reg = <0>;
+            #clock-cells = <0>;
+            clocks = <&si570>;
+            clock-names = "refclk";
+            interrupts-extended = <&gic GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH>;
+            interrupt-names = "an";
+            reset-gpios = <&gpio 5 GPIO_ACTIVE_HIGH>;
+            done-gpios = <&gpio 6 GPIO_ACTIVE_HIGH>;
+            xlnx,pcs-modes = "sgmii", "1000base-x";
+        };
+
+        pcs1: ethernet-pcs@1 {
+            compatible = "xlnx,pcs-16.2", "xlnx,pcs";
+            reg = <1>;
+            xlnx,pcs-modes = "sgmii";
+            clocks = <&pcs0>;
+            clock-names = "refclk";
+        };
+    };
-- 
2.35.1.1320.gc452695387.dirty


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

* [net-next PATCH v6 02/10] net: phylink: Support setting PCS link change callbacks
  2025-06-10 23:31 [net-next PATCH v6 00/10] Add PCS core support Sean Anderson
  2025-06-10 23:31 ` [net-next PATCH v6 01/10] dt-bindings: net: Add Xilinx PCS Sean Anderson
@ 2025-06-10 23:31 ` Sean Anderson
  2025-06-10 23:31 ` [net-next PATCH v6 03/10] net: pcs: Add subsystem Sean Anderson
                   ` (8 subsequent siblings)
  10 siblings, 0 replies; 21+ messages in thread
From: Sean Anderson @ 2025-06-10 23:31 UTC (permalink / raw)
  To: netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King
  Cc: Vineeth Karumanchi, Heiner Kallweit, linux-kernel, Kory Maincent,
	Daniel Golle, Simon Horman, Christian Marangi, Lei Wei,
	Sean Anderson

Support changing the link change callback, similar to how PHYs do it.
This will allow the PCS wrapper to forward link changes to the wrapped
PCS.

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

(no changes since v1)

 drivers/net/phy/phylink.c | 24 +++++++-----------------
 include/linux/phylink.h   | 27 ++++++++++++++++++++++-----
 2 files changed, 29 insertions(+), 22 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 0faa3d97e06b..78989adac68f 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1199,6 +1199,8 @@ static void phylink_pcs_neg_mode(struct phylink *pl, struct phylink_pcs *pcs,
 	pl->act_link_an_mode = mode;
 }
 
+static void pcs_change_callback(void *priv, bool up);
+
 static void phylink_major_config(struct phylink *pl, bool restart,
 				  const struct phylink_link_state *state)
 {
@@ -1254,10 +1256,10 @@ static void phylink_major_config(struct phylink *pl, bool restart,
 		phylink_pcs_disable(pl->pcs);
 
 		if (pl->pcs)
-			pl->pcs->phylink = NULL;
-
-		pcs->phylink = pl;
+			pl->pcs->link_change_priv = NULL;
 
+		pcs->link_change = pcs_change_callback;
+		pcs->link_change_priv = pl;
 		pl->pcs = pcs;
 	}
 
@@ -2327,25 +2329,13 @@ void phylink_mac_change(struct phylink *pl, bool up)
 }
 EXPORT_SYMBOL_GPL(phylink_mac_change);
 
-/**
- * phylink_pcs_change() - notify phylink of a change to PCS link state
- * @pcs: pointer to &struct phylink_pcs
- * @up: indicates whether the link is currently up.
- *
- * The PCS driver should call this when the state of its link changes
- * (e.g. link failure, new negotiation results, etc.) Note: it should
- * not determine "up" by reading the BMSR. If in doubt about the link
- * state at interrupt time, then pass true if pcs_get_state() returns
- * the latched link-down state, otherwise pass false.
- */
-void phylink_pcs_change(struct phylink_pcs *pcs, bool up)
+static void pcs_change_callback(void *priv, bool up)
 {
-	struct phylink *pl = pcs->phylink;
+	struct phylink *pl = priv;
 
 	if (pl)
 		phylink_link_changed(pl, up, "pcs");
 }
-EXPORT_SYMBOL_GPL(phylink_pcs_change);
 
 static irqreturn_t phylink_link_handler(int irq, void *data)
 {
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
index 30659b615fca..96cd3e7940fe 100644
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -450,7 +450,8 @@ struct phylink_pcs_ops;
  * @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
+ * @link_change: callback for when the link changes
+ * @link_change_priv: first argument to @link_change
  * @poll: poll the PCS for link changes
  * @rxc_always_on: The MAC driver requires the reference clock
  *                 to always be on. Standalone PCS drivers which
@@ -460,13 +461,14 @@ struct phylink_pcs_ops;
  * This structure is designed to be embedded within the PCS private data,
  * and will be passed between phylink and the PCS.
  *
- * The @phylink member is private to phylink and must not be touched by
- * the PCS driver.
+ * @link_change, @link_change_priv, and @rxc_always_on will be filled in by
+ * phylink.
  */
 struct phylink_pcs {
 	DECLARE_PHY_INTERFACE_MASK(supported_interfaces);
 	const struct phylink_pcs_ops *ops;
-	struct phylink *phylink;
+	void (*link_change)(void *priv, bool up);
+	void *link_change_priv;
 	bool poll;
 	bool rxc_always_on;
 };
@@ -708,7 +710,22 @@ int phylink_set_fixed_link(struct phylink *,
 			   const struct phylink_link_state *);
 
 void phylink_mac_change(struct phylink *, bool up);
-void phylink_pcs_change(struct phylink_pcs *, bool up);
+/**
+ * phylink_pcs_change() - notify phylink of a change to PCS link state
+ * @pcs: pointer to &struct phylink_pcs
+ * @up: indicates whether the link is currently up.
+ *
+ * The PCS driver should call this when the state of its link changes
+ * (e.g. link failure, new negotiation results, etc.) Note: it should
+ * not determine "up" by reading the BMSR. If in doubt about the link
+ * state at interrupt time, then pass true if pcs_get_state() returns
+ * the latched link-down state, otherwise pass false.
+ */
+static inline void phylink_pcs_change(struct phylink_pcs *pcs, bool up)
+{
+	if (pcs->link_change)
+		pcs->link_change(pcs->link_change_priv, up);
+}
 
 int phylink_pcs_pre_init(struct phylink *pl, struct phylink_pcs *pcs);
 
-- 
2.35.1.1320.gc452695387.dirty


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

* [net-next PATCH v6 03/10] net: pcs: Add subsystem
  2025-06-10 23:31 [net-next PATCH v6 00/10] Add PCS core support Sean Anderson
  2025-06-10 23:31 ` [net-next PATCH v6 01/10] dt-bindings: net: Add Xilinx PCS Sean Anderson
  2025-06-10 23:31 ` [net-next PATCH v6 02/10] net: phylink: Support setting PCS link change callbacks Sean Anderson
@ 2025-06-10 23:31 ` Sean Anderson
  2025-06-11  0:24   ` Randy Dunlap
  2025-06-10 23:31 ` [net-next PATCH v6 04/10] net: dsa: ocelot: suppress PHY device scanning on the internal MDIO bus Sean Anderson
                   ` (7 subsequent siblings)
  10 siblings, 1 reply; 21+ messages in thread
From: Sean Anderson @ 2025-06-10 23:31 UTC (permalink / raw)
  To: netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King
  Cc: Vineeth Karumanchi, Heiner Kallweit, linux-kernel, Kory Maincent,
	Daniel Golle, Simon Horman, Christian Marangi, Lei Wei,
	Sean Anderson, Jonathan Corbet, linux-doc

This adds support for getting PCS devices from the device tree. PCS
drivers must first register with phylink_register_pcs. After that, MAC
drivers may look up their PCS using phylink_get_pcs.

We wrap registered PCSs in another PCS. This wrapper PCS is refcounted
and can outlive the wrapped PCS (such as if the wrapped PCS's driver is
unbound). The wrapper forwards all PCS callbacks to the wrapped PCS,
first checking to make sure the wrapped PCS still exists. This design
was inspired by Bartosz Golaszewski's talk at LPC [1].

One downside of this approach is that if a PCS provider gets rebound,
the consumer must put the old PCS and re-get it. Typically this means
rebinding the consumer as well. I don't consider this a major downside:

- There is no guarantee that the PCS provider will support the same
  PHY interfaces as before. At the moment there is no way to handle this
  other than rebinding the consumer.
- Unbinding a PCS can generally happen in three ways:
  - The entire MAC and PCS is on a removable bus and everything is
    getting unbound because the whole thing is being removed. In this
    case we do not need to worry about the PCS coming back without the
    MAC having been unbound.
  - The PCS is on an FPGA that is being reconfigured but the MAC is not.
    This is a legitimate (if uncommon) use case. However, given that
    such arrangements were not possible at all before this series I
    think this is an acceptible limitation (at least initially).
  - The user has manually rebound the PCS (directly or indirectly)
    through sysfs. In this case I think it is fine to require them to
    manually rebind the MACs as well.

pcs_get_by_fwnode_compat is a bit hairy, but it's necessary for
compatibility with existing drivers, which often attach to (devicetree)
nodes directly. We use the devicetree changeset system instead of
adding a (secondary) software node because mdio_bus_match calls
of_driver_match_device to match devices, and that function only works on
devicetree nodes.

[1] https://lpc.events/event/17/contributions/1627/

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

Changes in v6:
- Reduce line lengths to under 80 characters
- Use guard(srcu)

Changes in v4:
- Adjust variable ordering in pcs_find_fwnode
- Annotate pcs_wrapper.wrapped with __rcu
- Fix PCS lookup functions missing ERR_PTR casts
- Fix documentation for devm_pcs_register_full
- Fix incorrect condition in pcs_post_config
- Fix linking when PCS && !OF_DYNAMIC
- Fix linking when PCS && OF_DYNAMIC && PHYLIB=m
- Reduce line lengths to under 80 characters
- Remove unused dev parameter to pcs_put
- Use a spinlock instead of a mutex to protect pcs_wrappers

Changes in v3:
- Remove support for #pcs-cells. Upon further investigation, the
  requested functionality can be accomplished by specifying the PCS's
  fwnode manually.

Changes in v2:
- Add fallbacks for pcs_get* and pcs_put
- Add support for #pcs-cells
- Remove outdated comment
- Remove unused variable

 Documentation/networking/index.rst |   1 +
 Documentation/networking/kapi.rst  |   4 +
 Documentation/networking/pcs.rst   | 102 +++++
 MAINTAINERS                        |   2 +
 drivers/net/pcs/Kconfig            |  13 +
 drivers/net/pcs/Makefile           |   2 +
 drivers/net/pcs/core.c             | 628 +++++++++++++++++++++++++++++
 include/linux/pcs.h                | 205 ++++++++++
 8 files changed, 957 insertions(+)
 create mode 100644 Documentation/networking/pcs.rst
 create mode 100644 drivers/net/pcs/core.c
 create mode 100644 include/linux/pcs.h

diff --git a/Documentation/networking/index.rst b/Documentation/networking/index.rst
index ac90b82f3ce9..ff0e5968850b 100644
--- a/Documentation/networking/index.rst
+++ b/Documentation/networking/index.rst
@@ -30,6 +30,7 @@ Contents:
    page_pool
    phy
    sfp-phylink
+   pcs
    alias
    bridge
    snmp_counter
diff --git a/Documentation/networking/kapi.rst b/Documentation/networking/kapi.rst
index 98682b9a13ee..7a48178649de 100644
--- a/Documentation/networking/kapi.rst
+++ b/Documentation/networking/kapi.rst
@@ -146,6 +146,10 @@ PHYLINK
 
 .. kernel-doc:: include/linux/phylink.h
    :internal:
+   :no-identifiers: phylink_pcs phylink_pcs_ops pcs_validate pcs_inband_caps
+      pcs_enable pcs_disable pcs_pre_config pcs_post_config pcs_get_state
+      pcs_config pcs_an_restart pcs_link_up pcs_disable_eee pcs_enable_eee
+      pcs_pre_init
 
 .. kernel-doc:: drivers/net/phy/phylink.c
 
diff --git a/Documentation/networking/pcs.rst b/Documentation/networking/pcs.rst
new file mode 100644
index 000000000000..4b41ba884160
--- /dev/null
+++ b/Documentation/networking/pcs.rst
@@ -0,0 +1,102 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=============
+PCS Subsystem
+=============
+
+The PCS (Physical Coding Sublayer) subsystem handles the registration and lookup
+of PCS devices. These devices contain the upper sublayers of the Ethernet
+physical layer, generally handling framing, scrambling, and encoding tasks. PCS
+devices may also include PMA (Physical Medium Attachment) components. PCS
+devices transfer data between the Link-layer MAC device, and the rest of the
+physical layer, typically via a serdes. The output of the serdes may be
+connected more-or-less directly to the medium when using fiber-optic or
+backplane connections (1000BASE-SX, 1000BASE-KX, etc). It may also communicate
+with a separate PHY (such as over SGMII) which handles the connection to the
+medium (such as 1000BASE-T).
+
+Looking up PCS Devices
+----------------------
+
+There are generally two ways to look up a PCS device. If the PCS device is
+internal to a larger device (such as a MAC or switch), and it does not share an
+implementation with an existing PCS, then it does not need to be registered with
+the PCS subsystem. Instead, you can populate a :c:type:`phylink_pcs`
+in your probe function. Otherwise, you must look up the PCS.
+
+If your device has a :c:type:`fwnode_handle`, you can add a PCS using the
+``pcs-handle`` property::
+
+    ethernet-controller {
+        // ...
+        pcs-handle = <&pcs>;
+        pcs-handle-names = "internal";
+    };
+
+Then, during your probe function, you can get the PCS using :c:func:`pcs_get`::
+
+    mac->pcs = pcs_get(dev, "internal");
+    if (IS_ERR(mac->pcs)) {
+        err = PTR_ERR(mac->pcs);
+        return dev_err_probe(dev, "Could not get PCS\n");
+    }
+
+If your device doesn't have a :c:type:`fwnode_handle`, you can get the PCS
+based on the providing device using :c:func:`pcs_get_by_dev`. Typically, you
+will create the device and bind your PCS driver to it before calling this
+function. This allows reuse of an existing PCS driver.
+
+Once you are done using the PCS, you must call :c:func:`pcs_put`.
+
+Using PCS Devices
+-----------------
+
+To select the PCS from a MAC driver, implement the ``mac_select_pcs`` callback
+of :c:type:`phylink_mac_ops`. In this example, the PCS is selected for SGMII
+and 1000BASE-X, and deselected for other interfaces::
+
+    static struct phylink_pcs *mac_select_pcs(struct phylink_config *config,
+                                              phy_interface_t iface)
+    {
+        struct mac *mac = config_to_mac(config);
+
+        switch (iface) {
+        case PHY_INTERFACE_MODE_SGMII:
+        case PHY_INTERFACE_MODE_1000BASEX:
+            return mac->pcs;
+        default:
+            return NULL;
+        }
+    }
+
+To do the same from a DSA driver, implement the ``phylink_mac_select_pcs``
+callback of :c:type:`dsa_switch_ops`.
+
+Writing PCS Drivers
+-------------------
+
+To write a PCS driver, first implement :c:type:`phylink_pcs_ops`. Then,
+register your PCS in your probe function using :c:func:`pcs_register`. If you
+need to provide multiple PCSs for the same device, then you can pass specific
+firmware nodes using :c:macro:`pcs_register_full`.
+
+You must call :c:func:`pcs_unregister` from your remove function. You can avoid
+this step by registering with :c:func:`devm_pcs_unregister`.
+
+API Reference
+-------------
+
+.. kernel-doc:: include/linux/phylink.h
+   :identifiers: phylink_pcs phylink_pcs_ops pcs_validate pcs_inband_caps
+      pcs_enable pcs_disable pcs_pre_config pcs_post_config pcs_get_state
+      pcs_config pcs_an_restart pcs_link_up pcs_disable_eee pcs_enable_eee
+      pcs_pre_init
+
+.. kernel-doc:: include/linux/pcs.h
+   :internal:
+
+.. kernel-doc:: drivers/net/pcs/core.c
+   :export:
+
+.. kernel-doc:: drivers/net/pcs/core.c
+   :internal:
diff --git a/MAINTAINERS b/MAINTAINERS
index f2668b81115c..0ac6ba5c40cb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8906,6 +8906,7 @@ F:	Documentation/ABI/testing/sysfs-class-net-phydev
 F:	Documentation/devicetree/bindings/net/ethernet-phy.yaml
 F:	Documentation/devicetree/bindings/net/mdio*
 F:	Documentation/devicetree/bindings/net/qca,ar803x.yaml
+F:	Documentation/networking/pcs.rst
 F:	Documentation/networking/phy.rst
 F:	drivers/net/mdio/
 F:	drivers/net/mdio/acpi_mdio.c
@@ -8919,6 +8920,7 @@ F:	include/linux/linkmode.h
 F:	include/linux/mdio/*.h
 F:	include/linux/mii.h
 F:	include/linux/of_net.h
+F:	include/linux/pcs.h
 F:	include/linux/phy.h
 F:	include/linux/phy_fixed.h
 F:	include/linux/phy_link_topology.h
diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig
index f6aa437473de..6d19625b696d 100644
--- a/drivers/net/pcs/Kconfig
+++ b/drivers/net/pcs/Kconfig
@@ -5,6 +5,19 @@
 
 menu "PCS device drivers"
 
+config PCS
+	bool "PCS subsystem"
+	select PHYLIB if OF_DYNAMIC
+	help
+	  This provides common helper functions for registering and looking up
+	  Physical Coding Sublayer (PCS) devices. PCS devices translate between
+	  different interface types. In some use cases, they may either
+	  translate between different types of Medium-Independent Interfaces
+	  (MIIs), such as translating GMII to SGMII. This allows using a fast
+	  serial interface to talk to the phy which translates the MII to the
+	  Medium-Dependent Interface. Alternatively, they may translate a MII
+	  directly to an MDI, such as translating GMII to 1000Base-X.
+
 config PCS_XPCS
 	tristate "Synopsys DesignWare Ethernet XPCS"
 	select PHYLINK
diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile
index 4f7920618b90..35e3324fc26e 100644
--- a/drivers/net/pcs/Makefile
+++ b/drivers/net/pcs/Makefile
@@ -1,6 +1,8 @@
 # SPDX-License-Identifier: GPL-2.0
 # Makefile for Linux PCS drivers
 
+obj-$(CONFIG_PCS)		+= core.o
+
 pcs_xpcs-$(CONFIG_PCS_XPCS)	:= pcs-xpcs.o pcs-xpcs-plat.o \
 				   pcs-xpcs-nxp.o pcs-xpcs-wx.o
 
diff --git a/drivers/net/pcs/core.c b/drivers/net/pcs/core.c
new file mode 100644
index 000000000000..0a9f31245bb9
--- /dev/null
+++ b/drivers/net/pcs/core.c
@@ -0,0 +1,628 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022-25 Sean Anderson <sean.anderson@seco.com>
+ */
+
+#define pr_fmt(fmt) "pcs-core: " fmt
+
+#include <linux/cleanup.h>
+#include <linux/fwnode.h>
+#include <linux/list.h>
+#include <linux/of.h>
+#include <linux/pcs.h>
+#include <linux/phylink.h>
+#include <linux/property.h>
+#include <linux/rcupdate.h>
+#include <linux/spinlock.h>
+
+static LIST_HEAD(pcs_wrappers);
+/* Protects PCS (un)registration i.e. pcs_wrappers */
+static DEFINE_SPINLOCK(pcs_lock);
+/* Protects pcs_wrapper.pcs from being unregistered while we are operating on
+ * it. One SRCU is shared by all PCSs, so drivers may wait on other drivers'
+ * PCSs. If this becomes a problem the SRCU can be made per-PCS.
+ */
+DEFINE_STATIC_SRCU(pcs_srcu);
+
+/**
+ * struct pcs_wrapper - Wrapper for a registered PCS
+ * @pcs: the wrapping PCS
+ * @refcnt: refcount for the wrapper
+ * @list: list head for pcs_wrappers
+ * @dev: the device associated with this PCS
+ * @fwnode: this PCS's firmware node; typically @dev.fwnode
+ * @wrapped: the backing PCS
+ */
+struct pcs_wrapper {
+	struct phylink_pcs pcs;
+	refcount_t refcnt;
+	struct list_head list;
+	struct device *dev;
+	struct fwnode_handle *fwnode;
+	struct phylink_pcs __rcu *wrapped;
+};
+
+static const struct phylink_pcs_ops pcs_ops;
+
+static struct pcs_wrapper *pcs_to_wrapper(struct phylink_pcs *pcs)
+{
+	WARN_ON(pcs->ops != &pcs_ops);
+	return container_of(pcs, struct pcs_wrapper, pcs);
+}
+
+static int pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
+			const struct phylink_link_state *state)
+{
+	struct pcs_wrapper *wrapper = pcs_to_wrapper(pcs);
+	struct phylink_pcs *wrapped;
+
+	if (!wrapper)
+		return 0;
+
+	guard(srcu)(&pcs_srcu);
+	wrapped = srcu_dereference(wrapper->wrapped, &pcs_srcu);
+	if (!wrapped)
+		return -ENODEV;
+
+	if (!wrapped->ops->pcs_validate)
+		return 0;
+
+	return wrapped->ops->pcs_validate(wrapped, supported, state);
+}
+
+static unsigned int pcs_inband_caps(struct phylink_pcs *pcs,
+				    phy_interface_t interface)
+{
+	struct pcs_wrapper *wrapper = pcs_to_wrapper(pcs);
+	struct phylink_pcs *wrapped;
+
+	guard(srcu)(&pcs_srcu);
+	wrapped = srcu_dereference(wrapper->wrapped, &pcs_srcu);
+	if (!wrapped || !wrapped->ops->pcs_inband_caps)
+		return 0;
+
+	return wrapped->ops->pcs_inband_caps(wrapped, interface);
+}
+
+static int pcs_enable(struct phylink_pcs *pcs)
+{
+	struct pcs_wrapper *wrapper = pcs_to_wrapper(pcs);
+	struct phylink_pcs *wrapped;
+
+	if (!wrapper)
+		return 0;
+
+	guard(srcu)(&pcs_srcu);
+	wrapped = srcu_dereference(wrapper->wrapped, &pcs_srcu);
+	if (!wrapped)
+		return -ENODEV;
+
+	if (!wrapped->ops->pcs_enable)
+		return 0;
+
+	return wrapped->ops->pcs_enable(wrapped);
+}
+
+static void pcs_disable(struct phylink_pcs *pcs)
+{
+	struct pcs_wrapper *wrapper = pcs_to_wrapper(pcs);
+	struct phylink_pcs *wrapped;
+
+	guard(srcu)(&pcs_srcu);
+	wrapped = srcu_dereference(wrapper->wrapped, &pcs_srcu);
+	if (wrapped && wrapped->ops->pcs_disable)
+		wrapped->ops->pcs_disable(wrapped);
+}
+
+static void pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
+			  struct phylink_link_state *state)
+{
+	struct pcs_wrapper *wrapper = pcs_to_wrapper(pcs);
+	struct phylink_pcs *wrapped;
+
+	guard(srcu)(&pcs_srcu);
+	wrapped = srcu_dereference(wrapper->wrapped, &pcs_srcu);
+	if (wrapped)
+		wrapped->ops->pcs_get_state(wrapped, neg_mode, state);
+	else
+		state->link = 0;
+}
+
+static void pcs_pre_config(struct phylink_pcs *pcs,
+			   phy_interface_t interface)
+{
+	struct pcs_wrapper *wrapper = pcs_to_wrapper(pcs);
+	struct phylink_pcs *wrapped;
+
+	guard(srcu)(&pcs_srcu);
+	wrapped = srcu_dereference(wrapper->wrapped, &pcs_srcu);
+	if (wrapped && wrapped->ops->pcs_pre_config)
+		wrapped->ops->pcs_pre_config(wrapped, interface);
+}
+
+static int pcs_post_config(struct phylink_pcs *pcs,
+			   phy_interface_t interface)
+{
+	struct pcs_wrapper *wrapper = pcs_to_wrapper(pcs);
+	struct phylink_pcs *wrapped;
+
+	guard(srcu)(&pcs_srcu);
+	wrapped = srcu_dereference(wrapper->wrapped, &pcs_srcu);
+	if (!wrapped)
+		return -ENODEV;
+
+	if (!wrapped->ops->pcs_post_config)
+		return 0;
+
+	return wrapped->ops->pcs_post_config(wrapped, interface);
+}
+
+static int pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+		      phy_interface_t interface,
+		      const unsigned long *advertising,
+		      bool permit_pause_to_mac)
+{
+	struct pcs_wrapper *wrapper = pcs_to_wrapper(pcs);
+	struct phylink_pcs *wrapped;
+
+	guard(srcu)(&pcs_srcu);
+	wrapped = srcu_dereference(wrapper->wrapped, &pcs_srcu);
+	if (!wrapped)
+		return -ENODEV;
+
+	return wrapped->ops->pcs_config(wrapped, neg_mode, interface,
+					advertising, permit_pause_to_mac);
+}
+
+static void pcs_an_restart(struct phylink_pcs *pcs)
+{
+	struct pcs_wrapper *wrapper = pcs_to_wrapper(pcs);
+	struct phylink_pcs *wrapped;
+
+	guard(srcu)(&pcs_srcu);
+	wrapped = srcu_dereference(wrapper->wrapped, &pcs_srcu);
+	if (wrapped)
+		wrapped->ops->pcs_an_restart(wrapped);
+}
+
+static void pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
+			phy_interface_t interface, int speed, int duplex)
+{
+	struct pcs_wrapper *wrapper = pcs_to_wrapper(pcs);
+	struct phylink_pcs *wrapped;
+
+	guard(srcu)(&pcs_srcu);
+	wrapped = srcu_dereference(wrapper->wrapped, &pcs_srcu);
+	if (wrapped && wrapped->ops->pcs_link_up)
+		wrapped->ops->pcs_link_up(wrapped, neg_mode, interface, speed,
+					  duplex);
+}
+
+static void pcs_disable_eee(struct phylink_pcs *pcs)
+{
+	struct pcs_wrapper *wrapper = pcs_to_wrapper(pcs);
+	struct phylink_pcs *wrapped;
+
+	guard(srcu)(&pcs_srcu);
+	wrapped = srcu_dereference(wrapper->wrapped, &pcs_srcu);
+	if (wrapped && wrapped->ops->pcs_disable_eee)
+		wrapped->ops->pcs_disable_eee(wrapped);
+}
+
+static void pcs_enable_eee(struct phylink_pcs *pcs)
+{
+	struct pcs_wrapper *wrapper = pcs_to_wrapper(pcs);
+	struct phylink_pcs *wrapped;
+
+	guard(srcu)(&pcs_srcu);
+	wrapped = srcu_dereference(wrapper->wrapped, &pcs_srcu);
+	if (wrapped && wrapped->ops->pcs_enable_eee)
+		wrapped->ops->pcs_enable_eee(wrapped);
+}
+
+static int pcs_pre_init(struct phylink_pcs *pcs)
+{
+	struct pcs_wrapper *wrapper = pcs_to_wrapper(pcs);
+	struct phylink_pcs *wrapped;
+
+	guard(srcu)(&pcs_srcu);
+	wrapped = srcu_dereference(wrapper->wrapped, &pcs_srcu);
+	if (!wrapped)
+		return -ENODEV;
+
+	wrapped->rxc_always_on = pcs->rxc_always_on;
+	if (!wrapped->ops->pcs_pre_init)
+		return 0;
+
+	return wrapped->ops->pcs_pre_init(wrapped);
+}
+
+static const struct phylink_pcs_ops pcs_ops = {
+	.pcs_validate = pcs_validate,
+	.pcs_inband_caps = pcs_inband_caps,
+	.pcs_enable = pcs_enable,
+	.pcs_disable = pcs_disable,
+	.pcs_pre_config = pcs_pre_config,
+	.pcs_post_config = pcs_post_config,
+	.pcs_get_state = pcs_get_state,
+	.pcs_config = pcs_config,
+	.pcs_an_restart = pcs_an_restart,
+	.pcs_link_up = pcs_link_up,
+	.pcs_disable_eee = pcs_disable_eee,
+	.pcs_enable_eee = pcs_enable_eee,
+	.pcs_pre_init = pcs_pre_init,
+};
+
+static void pcs_change_callback(void *priv, bool up)
+{
+	struct pcs_wrapper *wrapper = priv;
+
+	phylink_pcs_change(&wrapper->pcs, up);
+}
+
+/**
+ * pcs_register_full() - register a new PCS
+ * @dev: The device requesting the PCS
+ * @fwnode: The PCS's firmware node; typically @dev.fwnode
+ * @pcs: The PCS to register
+ *
+ * Registers a new PCS which can be attached to a phylink.
+ *
+ * Return: 0 on success, or -errno on error
+ */
+int pcs_register_full(struct device *dev, struct fwnode_handle *fwnode,
+		      struct phylink_pcs *pcs)
+{
+	struct pcs_wrapper *wrapper;
+
+	if (!dev || !pcs->ops)
+		return -EINVAL;
+
+	if (!pcs->ops->pcs_an_restart || !pcs->ops->pcs_config ||
+	    !pcs->ops->pcs_get_state)
+		return -EINVAL;
+
+	wrapper = kzalloc(sizeof(*wrapper), GFP_KERNEL);
+	if (!wrapper)
+		return -ENOMEM;
+
+	refcount_set(&wrapper->refcnt, 1);
+	INIT_LIST_HEAD(&wrapper->list);
+	wrapper->dev = get_device(dev);
+	wrapper->fwnode = fwnode_handle_get(fwnode);
+	RCU_INIT_POINTER(wrapper->wrapped, pcs);
+
+	wrapper->pcs.ops = &pcs_ops;
+	wrapper->pcs.poll = pcs->poll;
+	bitmap_copy(wrapper->pcs.supported_interfaces,
+		    pcs->supported_interfaces, PHY_INTERFACE_MODE_MAX);
+
+	pcs->link_change = pcs_change_callback;
+	pcs->link_change_priv = wrapper;
+
+	spin_lock(&pcs_lock);
+	list_add(&wrapper->list, &pcs_wrappers);
+	spin_unlock(&pcs_lock);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pcs_register_full);
+
+/**
+ * pcs_unregister() - unregister a PCS
+ * @pcs: a PCS previously registered with pcs_register()
+ */
+void pcs_unregister(struct phylink_pcs *pcs)
+{
+	struct pcs_wrapper *wrapper;
+
+	spin_lock(&pcs_lock);
+	list_for_each_entry(wrapper, &pcs_wrappers, list) {
+		if (rcu_access_pointer(wrapper->wrapped) == pcs)
+			goto found;
+	}
+
+	spin_unlock(&pcs_lock);
+	WARN(1, "trying to unregister an already-unregistered PCS\n");
+	return;
+
+found:
+	list_del(&wrapper->list);
+	spin_unlock(&pcs_lock);
+
+	put_device(wrapper->dev);
+	fwnode_handle_put(wrapper->fwnode);
+	rcu_replace_pointer(wrapper->wrapped, NULL, true);
+	synchronize_srcu(&pcs_srcu);
+
+	if (!wrapper->pcs.poll)
+		phylink_pcs_change(&wrapper->pcs, false);
+	if (refcount_dec_and_test(&wrapper->refcnt))
+		kfree(wrapper);
+}
+EXPORT_SYMBOL_GPL(pcs_unregister);
+
+static void devm_pcs_unregister(void *pcs)
+{
+	pcs_unregister(pcs);
+}
+
+/**
+ * devm_pcs_register_full - resource managed pcs_register()
+ * @dev: device that is registering this PCS
+ * @fwnode: The PCS's firmware node; typically @dev.fwnode
+ * @pcs: the PCS to register
+ *
+ * Managed pcs_register(). For PCSs registered by this function,
+ * pcs_unregister() is automatically called on driver detach. See
+ * pcs_register() for more information.
+ *
+ * Return: 0 on success, or -errno on failure
+ */
+int devm_pcs_register_full(struct device *dev, struct fwnode_handle *fwnode,
+			   struct phylink_pcs *pcs)
+{
+	int ret;
+
+	ret = pcs_register_full(dev, fwnode, pcs);
+	if (ret)
+		return ret;
+
+	return devm_add_action_or_reset(dev, devm_pcs_unregister, pcs);
+}
+EXPORT_SYMBOL_GPL(devm_pcs_register_full);
+
+/**
+ * _pcs_get_tail() - Look up and request a PCS
+ * @dev: The device requesting the PCS
+ * @fwnode: The PCS's fwnode
+ * @pcs_dev: The PCS's device
+ *
+ * Search PCSs registered with pcs_register() for one with a matching
+ * fwnode or device. Either @fwnode or @pcs_dev may be %NULL if matching
+ * against a fwnode or device is not desired (respectively).
+ *
+ * Once a PCS is found, perform common operations necessary when getting a PCS
+ * (increment reference counts, etc).
+ *
+ * You should probably call one of the pcs_get* functions instead of this one.
+ *
+ * Return: A PCS, or an error pointer on failure. If both @fwnode and @pcs_dev
+ *         are %NULL, returns %NULL to allow easier chaining.
+ */
+struct phylink_pcs *_pcs_get_tail(struct device *dev,
+				  const struct fwnode_handle *fwnode,
+				  const struct device *pcs_dev)
+{
+	struct pcs_wrapper *wrapper;
+
+	if (!fwnode && !pcs_dev)
+		return NULL;
+
+	pr_debug("looking for %pfwf or %s %s...\n", fwnode,
+		 pcs_dev ? dev_driver_string(pcs_dev) : "(null)",
+		 pcs_dev ? dev_name(pcs_dev) : "(null)");
+
+	spin_lock(&pcs_lock);
+	list_for_each_entry(wrapper, &pcs_wrappers, list) {
+		if (pcs_dev && wrapper->dev == pcs_dev)
+			goto found;
+		if (fwnode && wrapper->fwnode == fwnode)
+			goto found;
+	}
+	spin_unlock(&pcs_lock);
+	pr_debug("...not found\n");
+	return ERR_PTR(-EPROBE_DEFER);
+
+found:
+	refcount_inc(&wrapper->refcnt);
+	spin_unlock(&pcs_lock);
+	pr_debug("...found\n");
+	return &wrapper->pcs;
+}
+EXPORT_SYMBOL_GPL(_pcs_get_tail);
+
+/**
+ * pcs_find_fwnode() - Find a PCS's fwnode
+ * @mac_node: The fwnode referencing the PCS
+ * @id: The name of the PCS to get. May be %NULL to get the first PCS.
+ * @fallback: An optional fallback property to use if pcs-handle is absent
+ * @optional: Whether the PCS is optional
+ *
+ * Find a PCS's fwnode, as referenced by @mac_node. This fwnode can later be
+ * used with _pcs_get_tail() to get the actual PCS. ``pcs-handle-names`` is
+ * used to match @id, then the fwnode is found using ``pcs-handle``.
+ *
+ * This function is internal to the PCS subsystem from a consumer
+ * point-of-view. However, it may be used to implement fallbacks for legacy
+ * behavior in PCS providers.
+ *
+ * Return: %NULL if @optional is set and the PCS cannot be found. Otherwise,
+ *         returns a PCS if found or an error pointer on failure.
+ */
+struct fwnode_handle *pcs_find_fwnode(const struct fwnode_handle *mac_node,
+				      const char *id, const char *fallback,
+				      bool optional)
+{
+	struct fwnode_handle *pcs_fwnode;
+	int index;
+
+	if (!mac_node)
+		return optional ? NULL : ERR_PTR(-ENODEV);
+
+	if (id)
+		index = fwnode_property_match_string(mac_node,
+						     "pcs-handle-names", id);
+	else
+		index = 0;
+
+	if (index < 0) {
+		if (optional && (index == -EINVAL || index == -ENODATA))
+			return NULL;
+		return ERR_PTR(index);
+	}
+
+	/* First try pcs-handle, and if that doesn't work try the fallback */
+	pcs_fwnode = fwnode_find_reference(mac_node, "pcs-handle", index);
+	if (PTR_ERR(pcs_fwnode) == -ENOENT && fallback)
+		pcs_fwnode = fwnode_find_reference(mac_node, fallback, index);
+	if (optional && !id && PTR_ERR(pcs_fwnode) == -ENOENT)
+		return NULL;
+	return pcs_fwnode;
+}
+EXPORT_SYMBOL_GPL(pcs_find_fwnode);
+
+/**
+ * _pcs_get() - Get a PCS from a fwnode property
+ * @dev: The device to get a PCS for
+ * @fwnode: The fwnode to find the PCS with
+ * @id: The name of the PCS to get. May be %NULL to get the first PCS.
+ * @fallback: An optional fallback property to use if pcs-handle is absent
+ * @optional: Whether the PCS is optional
+ *
+ * Find a PCS referenced by @fwnode and return a reference to it. Every call
+ * to _pcs_get_by_fwnode() must be balanced with one to pcs_put().
+ *
+ * Return: a PCS if found, %NULL if not, or an error pointer on failure
+ */
+struct phylink_pcs *_pcs_get(struct device *dev, struct fwnode_handle *fwnode,
+			     const char *id, const char *fallback,
+			     bool optional)
+{
+	struct fwnode_handle *pcs_fwnode;
+	struct phylink_pcs *pcs;
+
+	pcs_fwnode = pcs_find_fwnode(fwnode, id, fallback, optional);
+	if (IS_ERR(pcs_fwnode))
+		return ERR_CAST(pcs_fwnode);
+
+	pcs = _pcs_get_tail(dev, pcs_fwnode, NULL);
+	fwnode_handle_put(pcs_fwnode);
+	return pcs;
+}
+EXPORT_SYMBOL_GPL(_pcs_get);
+
+#ifdef CONFIG_OF_DYNAMIC
+static void of_changeset_cleanup(void *data)
+{
+	struct of_changeset *ocs = data;
+
+	if (WARN(of_changeset_revert(ocs),
+		 "could not revert changeset; leaking memory\n"))
+		return;
+
+	of_changeset_destroy(ocs);
+	kfree(ocs);
+}
+
+/**
+ * pcs_get_by_fwnode_compat() - Get a PCS with a compatibility fallback
+ * @dev: The device requesting the PCS
+ * @fwnode: The &struct fwnode_handle of the PCS itself
+ * @fixup: Callback to fix up @fwnode for compatibility
+ * @data: Passed to @fixup
+ *
+ * This function looks up a PCS and retries on failure after fixing up @fwnode.
+ * It is intended to assist in backwards-compatible behavior for drivers that
+ * used to create a PCS directly from a &struct device_node. This function
+ * should NOT be used in new drivers.
+ *
+ * @fixup modifies a devicetree changeset to create any properties necessary to
+ * bind the PCS's &struct device_node. At the very least, it should use
+ * of_changeset_add_prop_string() to add a compatible property.
+ *
+ * Note that unlike pcs_get_by_fwnode, @fwnode is the &struct fwnode_handle of
+ * the PCS itself, and not that of the requesting device. @fwnode could be
+ * looked up with pcs_find_fwnode() or determined by some other means for
+ * compatibility.
+ *
+ * Return: A PCS on success or an error pointer on failure
+ */
+struct phylink_pcs *
+pcs_get_by_fwnode_compat(struct device *dev, struct fwnode_handle *fwnode,
+			 int (*fixup)(struct of_changeset *ocs,
+				      struct device_node *np, void *data),
+			 void *data)
+{
+	struct mdio_device *mdiodev;
+	struct of_changeset *ocs;
+	struct phylink_pcs *pcs;
+	struct device_node *np;
+	struct device *pcsdev;
+	int err;
+
+	/* First attempt */
+	pcs = _pcs_get_tail(dev, fwnode, NULL);
+	if (PTR_ERR(pcs) != -EPROBE_DEFER)
+		return pcs;
+
+	/* No luck? Maybe there's no compatible... */
+	np = to_of_node(fwnode);
+	if (!np || of_property_present(np, "compatible"))
+		return pcs;
+
+	/* OK, let's try fixing things up */
+	pr_warn("%pOF is missing a compatible\n", np);
+	ocs = kmalloc(sizeof(*ocs), GFP_KERNEL);
+	if (!ocs)
+		return ERR_PTR(-ENOMEM);
+
+	of_changeset_init(ocs);
+	err = fixup(ocs, np, data);
+	if (err)
+		goto err_ocs;
+
+	err = of_changeset_apply(ocs);
+	if (err)
+		goto err_ocs;
+
+	err = devm_add_action_or_reset(dev, of_changeset_cleanup, ocs);
+	if (err)
+		return ERR_PTR(err);
+
+	mdiodev = fwnode_mdio_find_device(fwnode);
+	if (mdiodev) {
+		/* Clear that pesky PHY flag so we can match PCS drivers */
+		device_lock(&mdiodev->dev);
+		mdiodev->flags &= ~MDIO_DEVICE_FLAG_PHY;
+		device_unlock(&mdiodev->dev);
+		pcsdev = &mdiodev->dev;
+	} else {
+		pcsdev = get_device(fwnode->dev);
+		if (!pcsdev)
+			return ERR_PTR(-EPROBE_DEFER);
+	}
+
+	err = device_reprobe(pcsdev);
+	put_device(pcsdev);
+	if (err)
+		return ERR_PTR(err);
+
+	return _pcs_get_tail(dev, fwnode, NULL);
+
+err_ocs:
+	of_changeset_destroy(ocs);
+	kfree(ocs);
+	return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(pcs_get_by_fwnode_compat);
+#endif
+
+/**
+ * pcs_put() - Release a previously-acquired PCS
+ * @pcs: The PCS to put
+ *
+ * This frees resources associated with the PCS which were acquired when it was
+ * gotten.
+ */
+void pcs_put(struct phylink_pcs *pcs)
+{
+	struct pcs_wrapper *wrapper;
+
+	if (!pcs)
+		return;
+
+	wrapper = pcs_to_wrapper(pcs);
+	if (refcount_dec_and_test(&wrapper->refcnt))
+		kfree(wrapper);
+}
+EXPORT_SYMBOL_GPL(pcs_put);
diff --git a/include/linux/pcs.h b/include/linux/pcs.h
new file mode 100644
index 000000000000..3ef5f22f7dec
--- /dev/null
+++ b/include/linux/pcs.h
@@ -0,0 +1,205 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 Sean Anderson <sean.anderson@seco.com>
+ */
+
+#ifndef _PCS_H
+#define _PCS_H
+
+#include <linux/property.h>
+
+struct device_node;
+struct of_changeset;
+struct phylink_pcs;
+
+int pcs_register_full(struct device *dev, struct fwnode_handle *fwnode,
+		      struct phylink_pcs *pcs);
+void pcs_unregister(struct phylink_pcs *pcs);
+int devm_pcs_register_full(struct device *dev, struct fwnode_handle *fwnode,
+			   struct phylink_pcs *pcs);
+
+/**
+ * pcs_register() - register a new PCS
+ * @dev: The device requesting the PCS
+ * @pcs: The PCS to register
+ *
+ * Registers a new PCS which can be attached to a phylink.
+ *
+ * Return: 0 on success, or -errno on error
+ */
+static inline int pcs_register(struct device *dev, struct phylink_pcs *pcs)
+{
+	return pcs_register_full(dev, dev_fwnode(dev), pcs);
+}
+
+/**
+ * devm_pcs_register - resource managed pcs_register()
+ * @dev: device that is registering this PCS
+ * @pcs: the PCS to register
+ *
+ * Managed pcs_register(). For PCSs registered by this function,
+ * pcs_unregister() is automatically called on driver detach. See
+ * pcs_register() for more information.
+ *
+ * Return: 0 on success, or -errno on failure
+ */
+static inline int devm_pcs_register(struct device *dev, struct phylink_pcs *pcs)
+{
+	return devm_pcs_register_full(dev, dev_fwnode(dev), pcs);
+}
+
+struct fwnode_handle *pcs_find_fwnode(const struct fwnode_handle *mac_node,
+				      const char *id, const char *fallback,
+				      bool optional);
+
+#ifdef CONFIG_PCS
+struct phylink_pcs *_pcs_get_tail(struct device *dev,
+				  const struct fwnode_handle *fwnode,
+				  const struct device *pcs_dev);
+struct phylink_pcs *_pcs_get(struct device *dev, struct fwnode_handle *fwnode,
+			     const char *id, const char *fallback,
+			     bool optional);
+void pcs_put(struct phylink_pcs *handle);
+
+/**
+ * pcs_get() - Get a PCS based on a fwnode
+ * @dev: The device requesting the PCS
+ * @id: The name of the PCS
+ *
+ * Find and get a PCS, as referenced by @dev's &struct fwnode_handle. See
+ * pcs_find_fwnode() for details. Each call to this function must be balanced
+ * with one to pcs_put().
+ *
+ * Return: A PCS on success or an error pointer on failure
+ */
+static inline struct phylink_pcs *pcs_get(struct device *dev, const char *id)
+{
+	return _pcs_get(dev, dev_fwnode(dev), id, NULL, false);
+}
+
+/**
+ * pcs_get_optional() - Optionally get a PCS based on a fwnode
+ * @dev: The device requesting the PCS
+ * @id: The name of the PCS
+ *
+ * Optionally find and get a PCS, as referenced by @dev's &struct
+ * fwnode_handle. See pcs_find_fwnode() for details. Each call to this function
+ * must be balanced with one to pcs_put().
+ *
+ * Return: A PCS on success, %NULL if none was found, or an error pointer on
+ * *       failure
+ */
+static inline struct phylink_pcs *pcs_get_optional(struct device *dev,
+						   const char *id)
+{
+	return _pcs_get(dev, dev_fwnode(dev), id, NULL, true);
+}
+
+/**
+ * pcs_get_by_fwnode() - Get a PCS based on a fwnode
+ * @dev: The device requesting the PCS
+ * @fwnode: The &struct fwnode_handle referencing the PCS
+ * @id: The name of the PCS
+ *
+ * Find and get a PCS, as referenced by @fwnode. See pcs_find_fwnode() for
+ * details. Each call to this function must be balanced with one to pcs_put().
+ *
+ * Return: A PCS on success or an error pointer on failure
+ */
+static inline struct phylink_pcs
+*pcs_get_by_fwnode(struct device *dev, struct fwnode_handle *fwnode,
+		   const char *id)
+{
+	return _pcs_get(dev, fwnode, id, NULL, false);
+}
+
+/**
+ * pcs_get_by_fwnode_optional() - Optionally get a PCS based on a fwnode
+ * @dev: The device requesting the PCS
+ * @fwnode: The &struct fwnode_handle referencing the PCS
+ * @id: The name of the PCS
+ *
+ * Optionally find and get a PCS, as referenced by @fwnode. See
+ * pcs_find_fwnode() for details. Each call to this function must be balanced
+ * with one to pcs_put().
+ *
+ * Return: A PCS on success, %NULL if none was found, or an error pointer on
+ * *       failure
+ */
+static inline struct phylink_pcs
+*pcs_get_by_fwnode_optional(struct device *dev, struct fwnode_handle *fwnode,
+			    const char *id)
+{
+	return _pcs_get(dev, fwnode, id, NULL, true);
+}
+
+/**
+ * pcs_get_by_dev() - Get a PCS from its providing device
+ * @dev: The device requesting the PCS
+ * @pcs_dev: The device providing the PCS
+ *
+ * Get the first PCS registered by @pcs_dev. Each call to this function must be
+ * balanced with one to pcs_put().
+ *
+ * Return: A PCS on success or an error pointer on failure
+ */
+static inline struct phylink_pcs *pcs_get_by_dev(struct device *dev,
+						 const struct device *pcs_dev)
+{
+	return _pcs_get_tail(dev, NULL, pcs_dev);
+}
+#else /* CONFIG_PCS */
+static inline void pcs_put(struct phylink_pcs *handle)
+{
+}
+
+static inline struct phylink_pcs *pcs_get(struct device *dev, const char *id)
+{
+	return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline struct phylink_pcs *pcs_get_optional(struct device *dev,
+						   const char *id)
+{
+	return NULL;
+}
+
+static inline struct phylink_pcs
+*pcs_get_by_fwnode(struct device *dev, struct fwnode_handle *fwnode,
+		   const char *id)
+{
+	return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline struct phylink_pcs
+*pcs_get_by_fwnode_optional(struct device *dev, struct fwnode_handle *fwnode,
+			    const char *id)
+{
+	return NULL;
+}
+
+static inline struct phylink_pcs *pcs_get_by_dev(struct device *dev,
+						 const struct device *pcs_dev)
+{
+	return ERR_PTR(-EOPNOTSUPP);
+}
+#endif
+
+#ifdef CONFIG_OF_DYNAMIC
+struct phylink_pcs *
+pcs_get_by_fwnode_compat(struct device *dev, struct fwnode_handle *fwnode,
+			 int (*fixup)(struct of_changeset *ocs,
+				      struct device_node *np, void *data),
+			 void *data);
+#else
+static inline struct phylink_pcs *
+pcs_get_by_fwnode_compat(struct device *dev, struct fwnode_handle *fwnode,
+			 int (*fixup)(struct of_changeset *ocs,
+				      struct device_node *np, void *data),
+			 void *data)
+{
+	return _pcs_get_tail(dev, fwnode, NULL);
+}
+#endif
+
+#endif /* PCS_H */
-- 
2.35.1.1320.gc452695387.dirty


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

* [net-next PATCH v6 04/10] net: dsa: ocelot: suppress PHY device scanning on the internal MDIO bus
  2025-06-10 23:31 [net-next PATCH v6 00/10] Add PCS core support Sean Anderson
                   ` (2 preceding siblings ...)
  2025-06-10 23:31 ` [net-next PATCH v6 03/10] net: pcs: Add subsystem Sean Anderson
@ 2025-06-10 23:31 ` Sean Anderson
  2025-06-10 23:31 ` [net-next PATCH v6 05/10] net: pcs: lynx: Convert to an MDIO driver Sean Anderson
                   ` (6 subsequent siblings)
  10 siblings, 0 replies; 21+ messages in thread
From: Sean Anderson @ 2025-06-10 23:31 UTC (permalink / raw)
  To: netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King
  Cc: Vineeth Karumanchi, Heiner Kallweit, linux-kernel, Kory Maincent,
	Daniel Golle, Simon Horman, Christian Marangi, Lei Wei,
	Vladimir Oltean, Sean Anderson, Alexandre Belloni, Claudiu Manoil,
	UNGLinuxDriver

From: Vladimir Oltean <vladimir.oltean@nxp.com>

This bus contains Lynx PCS devices, and if the lynx-pcs driver ever
decided to call mdio_device_register(), it would fail due to
mdiobus_scan() having created a dummy phydev for the same address
(the PCS responds to standard clause 22 PHY ID registers and can
therefore by autodetected by phylib which thinks it's a PHY).

On the Seville driver, things are a bit more complicated, since bus
creation is handled by mscc_miim_setup() and that is shared with the
dedicated mscc-miim driver. Suppress PHY scanning only for the Seville
internal MDIO bus rather than for the whole mscc-miim driver, since we
know that on NXP T1040, this bus only contains Lynx PCS devices.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

(no changes since v1)

 drivers/net/dsa/ocelot/felix_vsc9959.c   | 4 ++++
 drivers/net/dsa/ocelot/seville_vsc9953.c | 5 +++++
 2 files changed, 9 insertions(+)

diff --git a/drivers/net/dsa/ocelot/felix_vsc9959.c b/drivers/net/dsa/ocelot/felix_vsc9959.c
index 7b35d24c38d7..087d368a59e0 100644
--- a/drivers/net/dsa/ocelot/felix_vsc9959.c
+++ b/drivers/net/dsa/ocelot/felix_vsc9959.c
@@ -1001,6 +1001,10 @@ static int vsc9959_mdio_bus_alloc(struct ocelot *ocelot)
 	bus->read_c45 = enetc_mdio_read_c45;
 	bus->write_c45 = enetc_mdio_write_c45;
 	bus->parent = dev;
+	/* Suppress PHY device creation in mdiobus_scan(),
+	 * we have Lynx PCSs
+	 */
+	bus->phy_mask = ~0;
 	mdio_priv = bus->priv;
 	mdio_priv->hw = hw;
 	/* This gets added to imdio_regs, which already maps addresses
diff --git a/drivers/net/dsa/ocelot/seville_vsc9953.c b/drivers/net/dsa/ocelot/seville_vsc9953.c
index eb3944ba2a72..28bcdef34a6c 100644
--- a/drivers/net/dsa/ocelot/seville_vsc9953.c
+++ b/drivers/net/dsa/ocelot/seville_vsc9953.c
@@ -901,6 +901,11 @@ static int vsc9953_mdio_bus_alloc(struct ocelot *ocelot)
 		return rc;
 	}
 
+	/* Suppress PHY device creation in mdiobus_scan(),
+	 * we have Lynx PCSs
+	 */
+	bus->phy_mask = ~0;
+
 	/* Needed in order to initialize the bus mutex lock */
 	rc = devm_of_mdiobus_register(dev, bus, NULL);
 	if (rc < 0) {
-- 
2.35.1.1320.gc452695387.dirty


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

* [net-next PATCH v6 05/10] net: pcs: lynx: Convert to an MDIO driver
  2025-06-10 23:31 [net-next PATCH v6 00/10] Add PCS core support Sean Anderson
                   ` (3 preceding siblings ...)
  2025-06-10 23:31 ` [net-next PATCH v6 04/10] net: dsa: ocelot: suppress PHY device scanning on the internal MDIO bus Sean Anderson
@ 2025-06-10 23:31 ` Sean Anderson
  2025-06-13 11:27   ` kernel test robot
  2025-06-10 23:31 ` [net-next PATCH v6 06/10] net: pcs: Add Xilinx PCS driver Sean Anderson
                   ` (5 subsequent siblings)
  10 siblings, 1 reply; 21+ messages in thread
From: Sean Anderson @ 2025-06-10 23:31 UTC (permalink / raw)
  To: netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King
  Cc: Vineeth Karumanchi, Heiner Kallweit, linux-kernel, Kory Maincent,
	Daniel Golle, Simon Horman, Christian Marangi, Lei Wei,
	Sean Anderson, Ioana Ciornei, Vladimir Oltean, imx, linux-stm32

This converts the lynx PCS driver to a proper MDIO driver.
This allows using a more conventional driver lifecycle (e.g. with a
probe and remove). It will also make it easier to add interrupt support.

The existing helpers are converted to bind the MDIO driver instead of
creating the PCS directly. As lynx_pcs_create_mdiodev creates the PCS
device, we can just set the modalias. For lynx_pcs_create_fwnode, we try
to get the PCS the usual way, and if that fails we edit the devicetree
to add a compatible and reprobe the device.

To ensure my contributions remain free software, remove the BSD option
from the license. This is permitted because the SPDX uses "OR".

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

Changes in v6:
- Define lynx_pcs_of_match only when OF_MATCH is enabled
- Remove duplicate include of phylink.h
- Remove unneccessary Kconfig selects

Changes in v5:
- Use MDIO_BUS instead of MDIO_DEVICE

Changes in v4:
- Add a note about the license
- Convert to dev-less pcs_put

Changes in v3:
- Call devm_pcs_register instead of devm_pcs_register_provider

Changes in v2:
- Add support for #pcs-cells
- Remove unused variable lynx_properties

 drivers/net/dsa/ocelot/felix_vsc9959.c        |  11 +-
 drivers/net/dsa/ocelot/seville_vsc9953.c      |  11 +-
 drivers/net/ethernet/altera/altera_tse_main.c |   7 +-
 drivers/net/ethernet/freescale/dpaa2/Kconfig  |   1 +
 .../net/ethernet/freescale/dpaa2/dpaa2-mac.c  |  11 +-
 .../net/ethernet/freescale/enetc/enetc_pf.c   |   8 +-
 .../net/ethernet/freescale/enetc/enetc_pf.h   |   1 -
 .../freescale/enetc/enetc_pf_common.c         |   4 +-
 drivers/net/ethernet/freescale/fman/Kconfig   |   2 +-
 .../net/ethernet/freescale/fman/fman_memac.c  |  25 ++--
 drivers/net/ethernet/stmicro/stmmac/Kconfig   |   1 +
 .../ethernet/stmicro/stmmac/dwmac-socfpga.c   |   6 +-
 drivers/net/pcs/Kconfig                       |  12 +-
 drivers/net/pcs/pcs-lynx.c                    | 113 ++++++++++--------
 include/linux/pcs-lynx.h                      |  13 +-
 15 files changed, 116 insertions(+), 110 deletions(-)

diff --git a/drivers/net/dsa/ocelot/felix_vsc9959.c b/drivers/net/dsa/ocelot/felix_vsc9959.c
index 087d368a59e0..6feae845af10 100644
--- a/drivers/net/dsa/ocelot/felix_vsc9959.c
+++ b/drivers/net/dsa/ocelot/felix_vsc9959.c
@@ -12,6 +12,7 @@
 #include <net/tc_act/tc_gate.h>
 #include <soc/mscc/ocelot.h>
 #include <linux/dsa/ocelot.h>
+#include <linux/pcs.h>
 #include <linux/pcs-lynx.h>
 #include <net/pkt_sched.h>
 #include <linux/iopoll.h>
@@ -1033,7 +1034,7 @@ static int vsc9959_mdio_bus_alloc(struct ocelot *ocelot)
 		if (ocelot_port->phy_mode == PHY_INTERFACE_MODE_INTERNAL)
 			continue;
 
-		phylink_pcs = lynx_pcs_create_mdiodev(felix->imdio, port);
+		phylink_pcs = lynx_pcs_create_mdiodev(dev, felix->imdio, port);
 		if (IS_ERR(phylink_pcs))
 			continue;
 
@@ -1050,12 +1051,8 @@ static void vsc9959_mdio_bus_free(struct ocelot *ocelot)
 	struct felix *felix = ocelot_to_felix(ocelot);
 	int port;
 
-	for (port = 0; port < ocelot->num_phys_ports; port++) {
-		struct phylink_pcs *phylink_pcs = felix->pcs[port];
-
-		if (phylink_pcs)
-			lynx_pcs_destroy(phylink_pcs);
-	}
+	for (port = 0; port < ocelot->num_phys_ports; port++)
+		pcs_put(felix->pcs[port]);
 	mdiobus_unregister(felix->imdio);
 	mdiobus_free(felix->imdio);
 }
diff --git a/drivers/net/dsa/ocelot/seville_vsc9953.c b/drivers/net/dsa/ocelot/seville_vsc9953.c
index 28bcdef34a6c..627c0bd7a777 100644
--- a/drivers/net/dsa/ocelot/seville_vsc9953.c
+++ b/drivers/net/dsa/ocelot/seville_vsc9953.c
@@ -10,6 +10,7 @@
 #include <linux/mdio/mdio-mscc-miim.h>
 #include <linux/mod_devicetable.h>
 #include <linux/of_mdio.h>
+#include <linux/pcs.h>
 #include <linux/pcs-lynx.h>
 #include <linux/dsa/ocelot.h>
 #include <linux/iopoll.h>
@@ -926,7 +927,7 @@ static int vsc9953_mdio_bus_alloc(struct ocelot *ocelot)
 		if (ocelot_port->phy_mode == PHY_INTERFACE_MODE_INTERNAL)
 			continue;
 
-		phylink_pcs = lynx_pcs_create_mdiodev(felix->imdio, addr);
+		phylink_pcs = lynx_pcs_create_mdiodev(dev, felix->imdio, addr);
 		if (IS_ERR(phylink_pcs))
 			continue;
 
@@ -943,12 +944,8 @@ static void vsc9953_mdio_bus_free(struct ocelot *ocelot)
 	struct felix *felix = ocelot_to_felix(ocelot);
 	int port;
 
-	for (port = 0; port < ocelot->num_phys_ports; port++) {
-		struct phylink_pcs *phylink_pcs = felix->pcs[port];
-
-		if (phylink_pcs)
-			lynx_pcs_destroy(phylink_pcs);
-	}
+	for (port = 0; port < ocelot->num_phys_ports; port++)
+		pcs_put(felix->pcs[port]);
 
 	/* mdiobus_unregister and mdiobus_free handled by devres */
 }
diff --git a/drivers/net/ethernet/altera/altera_tse_main.c b/drivers/net/ethernet/altera/altera_tse_main.c
index 3f6204de9e6b..8bd4753a04bc 100644
--- a/drivers/net/ethernet/altera/altera_tse_main.c
+++ b/drivers/net/ethernet/altera/altera_tse_main.c
@@ -32,6 +32,7 @@
 #include <linux/of.h>
 #include <linux/of_mdio.h>
 #include <linux/of_net.h>
+#include <linux/pcs.h>
 #include <linux/pcs-lynx.h>
 #include <linux/phy.h>
 #include <linux/platform_device.h>
@@ -1412,7 +1413,7 @@ static int altera_tse_probe(struct platform_device *pdev)
 		goto err_init_pcs;
 	}
 
-	priv->pcs = lynx_pcs_create_mdiodev(pcs_bus, 0);
+	priv->pcs = lynx_pcs_create_mdiodev(&pdev->dev, pcs_bus, 0);
 	if (IS_ERR(priv->pcs)) {
 		ret = PTR_ERR(priv->pcs);
 		goto err_init_pcs;
@@ -1444,7 +1445,7 @@ static int altera_tse_probe(struct platform_device *pdev)
 
 	return 0;
 err_init_phylink:
-	lynx_pcs_destroy(priv->pcs);
+	pcs_put(priv->pcs);
 err_init_pcs:
 	unregister_netdev(ndev);
 err_register_netdev:
@@ -1466,7 +1467,7 @@ static void altera_tse_remove(struct platform_device *pdev)
 	altera_tse_mdio_destroy(ndev);
 	unregister_netdev(ndev);
 	phylink_destroy(priv->phylink);
-	lynx_pcs_destroy(priv->pcs);
+	pcs_put(priv->pcs);
 
 	free_netdev(ndev);
 }
diff --git a/drivers/net/ethernet/freescale/dpaa2/Kconfig b/drivers/net/ethernet/freescale/dpaa2/Kconfig
index d029b69c3f18..3309f5297255 100644
--- a/drivers/net/ethernet/freescale/dpaa2/Kconfig
+++ b/drivers/net/ethernet/freescale/dpaa2/Kconfig
@@ -2,6 +2,7 @@
 config FSL_DPAA2_ETH
 	tristate "Freescale DPAA2 Ethernet"
 	depends on FSL_MC_BUS && FSL_MC_DPIO
+	select OF_DYNAMIC
 	select PHYLINK
 	select PCS_LYNX
 	select FSL_XGMAC_MDIO
diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c
index 422ce13a7c94..0dc0a265db51 100644
--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c
+++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c
@@ -2,6 +2,7 @@
 /* Copyright 2019 NXP */
 
 #include <linux/acpi.h>
+#include <linux/pcs.h>
 #include <linux/pcs-lynx.h>
 #include <linux/phy/phy.h>
 #include <linux/property.h>
@@ -262,7 +263,7 @@ static int dpaa2_pcs_create(struct dpaa2_mac *mac,
 		return 0;
 	}
 
-	pcs = lynx_pcs_create_fwnode(node);
+	pcs = lynx_pcs_create_fwnode(&mac->mc_dev->dev, node);
 	fwnode_handle_put(node);
 
 	if (pcs == ERR_PTR(-EPROBE_DEFER)) {
@@ -288,12 +289,8 @@ static int dpaa2_pcs_create(struct dpaa2_mac *mac,
 
 static void dpaa2_pcs_destroy(struct dpaa2_mac *mac)
 {
-	struct phylink_pcs *phylink_pcs = mac->pcs;
-
-	if (phylink_pcs) {
-		lynx_pcs_destroy(phylink_pcs);
-		mac->pcs = NULL;
-	}
+	pcs_put(mac->pcs);
+	mac->pcs = NULL;
 }
 
 static void dpaa2_mac_set_supported_interfaces(struct dpaa2_mac *mac)
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c
index f63a29e2e031..8d0950c28190 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c
@@ -34,12 +34,7 @@ static void enetc_pf_set_primary_mac_addr(struct enetc_hw *hw, int si,
 static struct phylink_pcs *enetc_pf_create_pcs(struct enetc_pf *pf,
 					       struct mii_bus *bus)
 {
-	return lynx_pcs_create_mdiodev(bus, 0);
-}
-
-static void enetc_pf_destroy_pcs(struct phylink_pcs *pcs)
-{
-	lynx_pcs_destroy(pcs);
+	return lynx_pcs_create_mdiodev(&pf->si->pdev->dev, bus, 0);
 }
 
 static void enetc_set_vlan_promisc(struct enetc_hw *hw, char si_map)
@@ -914,7 +909,6 @@ static const struct enetc_pf_ops enetc_pf_ops = {
 	.set_si_primary_mac = enetc_pf_set_primary_mac_addr,
 	.get_si_primary_mac = enetc_pf_get_primary_mac_addr,
 	.create_pcs = enetc_pf_create_pcs,
-	.destroy_pcs = enetc_pf_destroy_pcs,
 	.enable_psfp = enetc_psfp_enable,
 };
 
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.h b/drivers/net/ethernet/freescale/enetc/enetc_pf.h
index ae407e9e9ee7..be22b036df42 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_pf.h
+++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.h
@@ -32,7 +32,6 @@ struct enetc_pf_ops {
 	void (*set_si_primary_mac)(struct enetc_hw *hw, int si, const u8 *addr);
 	void (*get_si_primary_mac)(struct enetc_hw *hw, int si, u8 *addr);
 	struct phylink_pcs *(*create_pcs)(struct enetc_pf *pf, struct mii_bus *bus);
-	void (*destroy_pcs)(struct phylink_pcs *pcs);
 	int (*enable_psfp)(struct enetc_ndev_priv *priv);
 };
 
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
index edf14a95cab7..1c53036d17df 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
@@ -4,6 +4,7 @@
 #include <linux/fsl/enetc_mdio.h>
 #include <linux/of_mdio.h>
 #include <linux/of_net.h>
+#include <linux/pcs.h>
 
 #include "enetc_pf_common.h"
 
@@ -248,8 +249,7 @@ static int enetc_imdio_create(struct enetc_pf *pf)
 
 static void enetc_imdio_remove(struct enetc_pf *pf)
 {
-	if (pf->pcs && pf->ops->destroy_pcs)
-		pf->ops->destroy_pcs(pf->pcs);
+	pcs_put(pf->pcs);
 
 	if (pf->imdio) {
 		mdiobus_unregister(pf->imdio);
diff --git a/drivers/net/ethernet/freescale/fman/Kconfig b/drivers/net/ethernet/freescale/fman/Kconfig
index a55542c1ad65..166fcde6100a 100644
--- a/drivers/net/ethernet/freescale/fman/Kconfig
+++ b/drivers/net/ethernet/freescale/fman/Kconfig
@@ -3,10 +3,10 @@ config FSL_FMAN
 	tristate "FMan support"
 	depends on FSL_SOC || ARCH_LAYERSCAPE || COMPILE_TEST
 	select GENERIC_ALLOCATOR
+	select OF_DYNAMIC
 	select PHYLINK
 	select PCS_LYNX
 	select CRC32
-	default n
 	help
 		Freescale Data-Path Acceleration Architecture Frame Manager
 		(FMan) support
diff --git a/drivers/net/ethernet/freescale/fman/fman_memac.c b/drivers/net/ethernet/freescale/fman/fman_memac.c
index 3925441143fa..a6064bc80ce7 100644
--- a/drivers/net/ethernet/freescale/fman/fman_memac.c
+++ b/drivers/net/ethernet/freescale/fman/fman_memac.c
@@ -11,6 +11,7 @@
 
 #include <linux/slab.h>
 #include <linux/io.h>
+#include <linux/pcs.h>
 #include <linux/pcs-lynx.h>
 #include <linux/phy.h>
 #include <linux/phy_fixed.h>
@@ -972,21 +973,21 @@ static int memac_init(struct fman_mac *memac)
 	return 0;
 }
 
-static void pcs_put(struct phylink_pcs *pcs)
+static void memac_pcs_put(struct phylink_pcs *pcs)
 {
 	if (IS_ERR_OR_NULL(pcs))
 		return;
 
-	lynx_pcs_destroy(pcs);
+	pcs_put(pcs);
 }
 
 static int memac_free(struct fman_mac *memac)
 {
 	free_init_resources(memac);
 
-	pcs_put(memac->sgmii_pcs);
-	pcs_put(memac->qsgmii_pcs);
-	pcs_put(memac->xfi_pcs);
+	memac_pcs_put(memac->sgmii_pcs);
+	memac_pcs_put(memac->qsgmii_pcs);
+	memac_pcs_put(memac->xfi_pcs);
 	kfree(memac->memac_drv_param);
 	kfree(memac);
 
@@ -1033,7 +1034,8 @@ static struct fman_mac *memac_config(struct mac_device *mac_dev,
 	return memac;
 }
 
-static struct phylink_pcs *memac_pcs_create(struct device_node *mac_node,
+static struct phylink_pcs *memac_pcs_create(struct device *dev,
+					    struct device_node *mac_node,
 					    int index)
 {
 	struct device_node *node;
@@ -1043,7 +1045,7 @@ static struct phylink_pcs *memac_pcs_create(struct device_node *mac_node,
 	if (!node)
 		return ERR_PTR(-ENODEV);
 
-	pcs = lynx_pcs_create_fwnode(of_fwnode_handle(node));
+	pcs = lynx_pcs_create_fwnode(dev, of_fwnode_handle(node));
 	of_node_put(node);
 
 	return pcs;
@@ -1100,7 +1102,7 @@ int memac_initialization(struct mac_device *mac_dev,
 
 	err = of_property_match_string(mac_node, "pcs-handle-names", "xfi");
 	if (err >= 0) {
-		memac->xfi_pcs = memac_pcs_create(mac_node, err);
+		memac->xfi_pcs = memac_pcs_create(mac_dev->dev, mac_node, err);
 		if (IS_ERR(memac->xfi_pcs)) {
 			err = PTR_ERR(memac->xfi_pcs);
 			dev_err_probe(mac_dev->dev, err, "missing xfi pcs\n");
@@ -1112,7 +1114,8 @@ int memac_initialization(struct mac_device *mac_dev,
 
 	err = of_property_match_string(mac_node, "pcs-handle-names", "qsgmii");
 	if (err >= 0) {
-		memac->qsgmii_pcs = memac_pcs_create(mac_node, err);
+		memac->qsgmii_pcs = memac_pcs_create(mac_dev->dev, mac_node,
+						     err);
 		if (IS_ERR(memac->qsgmii_pcs)) {
 			err = PTR_ERR(memac->qsgmii_pcs);
 			dev_err_probe(mac_dev->dev, err,
@@ -1128,11 +1131,11 @@ int memac_initialization(struct mac_device *mac_dev,
 	 */
 	err = of_property_match_string(mac_node, "pcs-handle-names", "sgmii");
 	if (err == -EINVAL || err == -ENODATA)
-		pcs = memac_pcs_create(mac_node, 0);
+		pcs = memac_pcs_create(mac_dev->dev, mac_node, 0);
 	else if (err < 0)
 		goto _return_fm_mac_free;
 	else
-		pcs = memac_pcs_create(mac_node, err);
+		pcs = memac_pcs_create(mac_dev->dev, mac_node, err);
 
 	if (IS_ERR(pcs)) {
 		err = PTR_ERR(pcs);
diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig
index 67fa879b1e52..cb4d5374d055 100644
--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
+++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
@@ -182,6 +182,7 @@ config DWMAC_SOCFPGA
 	tristate "SOCFPGA dwmac support"
 	default ARCH_INTEL_SOCFPGA
 	depends on OF && (ARCH_INTEL_SOCFPGA || COMPILE_TEST)
+	select OF_DYNAMIC
 	select MFD_SYSCON
 	select MDIO_REGMAP
 	select REGMAP_MMIO
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
index 72b50f6d72f4..325486c06511 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
@@ -8,6 +8,7 @@
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/of_net.h>
+#include <linux/pcs.h>
 #include <linux/phy.h>
 #include <linux/regmap.h>
 #include <linux/mdio/mdio-regmap.h>
@@ -414,7 +415,7 @@ static int socfpga_dwmac_pcs_init(struct stmmac_priv *priv)
 	if (IS_ERR(pcs_bus))
 		return PTR_ERR(pcs_bus);
 
-	pcs = lynx_pcs_create_mdiodev(pcs_bus, 0);
+	pcs = lynx_pcs_create_mdiodev(priv->device, pcs_bus, 0);
 	if (IS_ERR(pcs))
 		return PTR_ERR(pcs);
 
@@ -424,8 +425,7 @@ static int socfpga_dwmac_pcs_init(struct stmmac_priv *priv)
 
 static void socfpga_dwmac_pcs_exit(struct stmmac_priv *priv)
 {
-	if (priv->hw->phylink_pcs)
-		lynx_pcs_destroy(priv->hw->phylink_pcs);
+	pcs_put(priv->hw->phylink_pcs);
 }
 
 static struct phylink_pcs *socfpga_dwmac_select_pcs(struct stmmac_priv *priv,
diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig
index 6d19625b696d..f42839a0c332 100644
--- a/drivers/net/pcs/Kconfig
+++ b/drivers/net/pcs/Kconfig
@@ -26,10 +26,16 @@ config PCS_XPCS
 	  DesignWare XPCS controllers.
 
 config PCS_LYNX
-	tristate
+	tristate "NXP Lynx PCS driver"
+	select MDIO_BUS
+	select PCS
 	help
-	  This module provides helpers to phylink for managing the Lynx PCS
-	  which is part of the Layerscape and QorIQ Ethernet SERDES.
+	  This module provides driver support for the PCSs in Lynx 10g and 28g
+	  SerDes devices. These devices are present in NXP QorIQ SoCs,
+	  including the Layerscape series.
+
+	  If you want to use Ethernet on a QorIQ SoC, say "Y". If compiled as a
+	  module, it will be called "pcs-lynx".
 
 config PCS_MTK_LYNXI
 	tristate
diff --git a/drivers/net/pcs/pcs-lynx.c b/drivers/net/pcs/pcs-lynx.c
index 23b40e9eacbb..2989a6f3e3a4 100644
--- a/drivers/net/pcs/pcs-lynx.c
+++ b/drivers/net/pcs/pcs-lynx.c
@@ -1,11 +1,14 @@
-// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
-/* Copyright 2020 NXP
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
+ * Copyright 2020 NXP
  * Lynx PCS MDIO helpers
  */
 
 #include <linux/mdio.h>
-#include <linux/phylink.h>
+#include <linux/of.h>
+#include <linux/pcs.h>
 #include <linux/pcs-lynx.h>
+#include <linux/phylink.h>
 #include <linux/property.h>
 
 #define SGMII_CLOCK_PERIOD_NS		8 /* PCS is clocked at 125 MHz */
@@ -343,16 +346,16 @@ static const phy_interface_t lynx_interfaces[] = {
 	PHY_INTERFACE_MODE_USXGMII,
 };
 
-static struct phylink_pcs *lynx_pcs_create(struct mdio_device *mdio)
+static int lynx_pcs_probe(struct mdio_device *mdio)
 {
+	struct device *dev = &mdio->dev;
 	struct lynx_pcs *lynx;
-	int i;
+	int i, ret;
 
-	lynx = kzalloc(sizeof(*lynx), GFP_KERNEL);
+	lynx = devm_kzalloc(dev, sizeof(*lynx), GFP_KERNEL);
 	if (!lynx)
-		return ERR_PTR(-ENOMEM);
+		return -ENOMEM;
 
-	mdio_device_get(mdio);
 	lynx->mdio = mdio;
 	lynx->pcs.ops = &lynx_pcs_phylink_ops;
 	lynx->pcs.poll = true;
@@ -360,32 +363,66 @@ static struct phylink_pcs *lynx_pcs_create(struct mdio_device *mdio)
 	for (i = 0; i < ARRAY_SIZE(lynx_interfaces); i++)
 		__set_bit(lynx_interfaces[i], lynx->pcs.supported_interfaces);
 
-	return lynx_to_phylink_pcs(lynx);
+	ret = devm_pcs_register(dev, &lynx->pcs);
+	if (ret)
+		return dev_err_probe(dev, ret, "could not register PCS\n");
+	dev_info(dev, "probed\n");
+	return 0;
 }
 
-struct phylink_pcs *lynx_pcs_create_mdiodev(struct mii_bus *bus, int addr)
+#ifdef CONFIG_OF
+static const struct of_device_id lynx_pcs_of_match[] = {
+	{ .compatible = "fsl,lynx-pcs" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, lynx_pcs_of_match);
+#endif
+
+static struct mdio_driver lynx_pcs_driver = {
+	.probe = lynx_pcs_probe,
+	.mdiodrv.driver = {
+		.name = "lynx-pcs",
+		.of_match_table = of_match_ptr(lynx_pcs_of_match),
+	},
+};
+mdio_module_driver(lynx_pcs_driver);
+
+struct phylink_pcs *lynx_pcs_create_mdiodev(struct device *dev,
+					    struct mii_bus *bus, int addr)
 {
 	struct mdio_device *mdio;
 	struct phylink_pcs *pcs;
+	int err;
 
 	mdio = mdio_device_create(bus, addr);
 	if (IS_ERR(mdio))
 		return ERR_CAST(mdio);
 
-	pcs = lynx_pcs_create(mdio);
-
-	/* lynx_create() has taken a refcount on the mdiodev if it was
-	 * successful. If lynx_create() fails, this will free the mdio
-	 * device here. In any case, we don't need to hold our reference
-	 * anymore, and putting it here will allow mdio_device_put() in
-	 * lynx_destroy() to automatically free the mdio device.
-	 */
-	mdio_device_put(mdio);
+	mdio->bus_match = mdio_device_bus_match;
+	strscpy(mdio->modalias, "lynx-pcs");
+	err = mdio_device_register(mdio);
+	if (err) {
+		mdio_device_free(mdio);
+		return ERR_PTR(err);
+	}
 
+	pcs = pcs_get_by_dev(dev, &mdio->dev);
+	mdio_device_free(mdio);
 	return pcs;
 }
 EXPORT_SYMBOL(lynx_pcs_create_mdiodev);
 
+static int lynx_pcs_fixup(struct of_changeset *ocs,
+			  struct device_node *np, void *data)
+{
+#ifdef CONFIG_OF_DYNAMIC
+	return of_changeset_add_prop_string(ocs, np, "compatible",
+					    "fsl,lynx-pcs");
+#else
+	return -ENODEV;
+#endif
+}
+
 /*
  * lynx_pcs_create_fwnode() creates a lynx PCS instance from the fwnode
  * device indicated by node.
@@ -396,40 +433,12 @@ EXPORT_SYMBOL(lynx_pcs_create_mdiodev);
  *  -ENOMEM if we fail to allocate memory
  *  pointer to a phylink_pcs on success
  */
-struct phylink_pcs *lynx_pcs_create_fwnode(struct fwnode_handle *node)
+struct phylink_pcs *lynx_pcs_create_fwnode(struct device *dev,
+					   struct fwnode_handle *node)
 {
-	struct mdio_device *mdio;
-	struct phylink_pcs *pcs;
-
-	if (!fwnode_device_is_available(node))
-		return ERR_PTR(-ENODEV);
-
-	mdio = fwnode_mdio_find_device(node);
-	if (!mdio)
-		return ERR_PTR(-EPROBE_DEFER);
-
-	pcs = lynx_pcs_create(mdio);
-
-	/* lynx_create() has taken a refcount on the mdiodev if it was
-	 * successful. If lynx_create() fails, this will free the mdio
-	 * device here. In any case, we don't need to hold our reference
-	 * anymore, and putting it here will allow mdio_device_put() in
-	 * lynx_destroy() to automatically free the mdio device.
-	 */
-	mdio_device_put(mdio);
-
-	return pcs;
+	return pcs_get_by_fwnode_compat(dev, node, lynx_pcs_fixup, NULL);
 }
 EXPORT_SYMBOL_GPL(lynx_pcs_create_fwnode);
 
-void lynx_pcs_destroy(struct phylink_pcs *pcs)
-{
-	struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs);
-
-	mdio_device_put(lynx->mdio);
-	kfree(lynx);
-}
-EXPORT_SYMBOL(lynx_pcs_destroy);
-
-MODULE_DESCRIPTION("NXP Lynx PCS phylink library");
-MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("NXP Lynx PCS phylink driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/pcs-lynx.h b/include/linux/pcs-lynx.h
index 7958cccd16f2..a95801337205 100644
--- a/include/linux/pcs-lynx.h
+++ b/include/linux/pcs-lynx.h
@@ -6,12 +6,13 @@
 #ifndef __LINUX_PCS_LYNX_H
 #define __LINUX_PCS_LYNX_H
 
-#include <linux/mdio.h>
-#include <linux/phylink.h>
+struct device;
+struct mii_bus;
+struct phylink_pcs;
 
-struct phylink_pcs *lynx_pcs_create_mdiodev(struct mii_bus *bus, int addr);
-struct phylink_pcs *lynx_pcs_create_fwnode(struct fwnode_handle *node);
-
-void lynx_pcs_destroy(struct phylink_pcs *pcs);
+struct phylink_pcs *lynx_pcs_create_mdiodev(struct device *dev,
+					    struct mii_bus *bus, int addr);
+struct phylink_pcs *lynx_pcs_create_fwnode(struct device *dev,
+					   struct fwnode_handle *node);
 
 #endif /* __LINUX_PCS_LYNX_H */
-- 
2.35.1.1320.gc452695387.dirty


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

* [net-next PATCH v6 06/10] net: pcs: Add Xilinx PCS driver
  2025-06-10 23:31 [net-next PATCH v6 00/10] Add PCS core support Sean Anderson
                   ` (4 preceding siblings ...)
  2025-06-10 23:31 ` [net-next PATCH v6 05/10] net: pcs: lynx: Convert to an MDIO driver Sean Anderson
@ 2025-06-10 23:31 ` Sean Anderson
  2025-06-11  5:11   ` Maxime Chevallier
  2025-06-12  0:14   ` kernel test robot
  2025-06-10 23:31 ` [net-next PATCH v6 07/10] net: axienet: Convert to use PCS subsystem Sean Anderson
                   ` (4 subsequent siblings)
  10 siblings, 2 replies; 21+ messages in thread
From: Sean Anderson @ 2025-06-10 23:31 UTC (permalink / raw)
  To: netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King
  Cc: Vineeth Karumanchi, Heiner Kallweit, linux-kernel, Kory Maincent,
	Daniel Golle, Simon Horman, Christian Marangi, Lei Wei,
	Sean Anderson, Michal Simek, Radhey Shyam Pandey, Robert Hancock,
	linux-arm-kernel

This adds support for the Xilinx 1G/2.5G Ethernet PCS/PMA or SGMII device.
This is a soft device which converts between GMII and either SGMII,
1000Base-X, or 2500Base-X. If configured correctly, it can also switch
between SGMII and 1000BASE-X at runtime. Thoretically this is also possible
for 2500Base-X, but that requires reconfiguring the serdes. The exact
capabilities depend on synthesis parameters, so they are read from the
devicetree.

This device has a c22-compliant PHY interface, so for the most part we can
just use the phylink helpers. This device supports an interrupt which is
triggered on autonegotiation completion. I'm not sure how useful this is,
since we can never detect a link down (in the PCS).

This device supports sharing some logic between different implementations
of the device. In this case, one device contains the "shared logic" and the
clocks are connected to other devices. To coordinate this, one device
registers a clock that the other devices can request.  The clock is enabled
in the probe function by releasing the device from reset. There are no othe
software controls, so the clock ops are empty.

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

Changes in v6:
- Move axienet_pcs_fixup to AXI Ethernet commit
- Use an empty statement for next label

Changes in v5:
- Export get_phy_c22_id when it is used
- Expose bind attributes, since there is no issue in doing so
- Use MDIO_BUS instead of MDIO_DEVICE

Changes in v4:
- Re-add documentation for axienet_xilinx_pcs_get that was accidentally
  removed

Changes in v3:
- Adjust axienet_xilinx_pcs_get for changes to pcs_find_fwnode API
- Call devm_pcs_register instead of devm_pcs_register_provider

Changes in v2:
- Add support for #pcs-cells
- Change compatible to just xlnx,pcs
- Drop PCS_ALTERA_TSE which was accidentally added while rebasing
- Rework xilinx_pcs_validate to just clear out half-duplex modes instead
  of constraining modes based on the interface.

 MAINTAINERS                  |   6 +
 drivers/net/pcs/Kconfig      |  22 ++
 drivers/net/pcs/Makefile     |   2 +
 drivers/net/pcs/pcs-xilinx.c | 427 +++++++++++++++++++++++++++++++++++
 drivers/net/phy/phy_device.c |   3 +-
 include/linux/phy.h          |   1 +
 6 files changed, 460 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/pcs/pcs-xilinx.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 0ac6ba5c40cb..496513837921 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27060,6 +27060,12 @@ L:	netdev@vger.kernel.org
 S:	Orphan
 F:	drivers/net/ethernet/xilinx/ll_temac*
 
+XILINX PCS DRIVER
+M:	Sean Anderson <sean.anderson@linux.dev>
+S:	Maintained
+F:	Documentation/devicetree/bindings/net/xilinx,pcs.yaml
+F:	drivers/net/pcs/pcs-xilinx.c
+
 XILINX PWM DRIVER
 M:	Sean Anderson <sean.anderson@seco.com>
 S:	Maintained
diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig
index f42839a0c332..e0223914362b 100644
--- a/drivers/net/pcs/Kconfig
+++ b/drivers/net/pcs/Kconfig
@@ -52,4 +52,26 @@ 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.
 
+config PCS_XILINX
+	tristate "Xilinx PCS driver"
+	default XILINX_AXI_EMAC
+	select COMMON_CLK
+	select GPIOLIB
+	select MDIO_BUS
+	select OF
+	select PCS
+	select PHYLINK
+	help
+	  PCS driver for the Xilinx 1G/2.5G Ethernet PCS/PMA or SGMII device.
+	  This device can either act as a PCS+PMA for 1000BASE-X or 2500BASE-X,
+	  or as a GMII-to-SGMII bridge. It can also switch between 1000BASE-X
+	  and SGMII dynamically if configured correctly when synthesized.
+	  Typical applications use this device on an FPGA connected to a GEM or
+	  TEMAC on the GMII side. The other side is typically connected to
+	  on-device gigabit transceivers, off-device SERDES devices using TBI,
+	  or LVDS IO resources directly.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pcs-xilinx.
+
 endmenu
diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile
index 35e3324fc26e..347afd91f034 100644
--- a/drivers/net/pcs/Makefile
+++ b/drivers/net/pcs/Makefile
@@ -10,3 +10,5 @@ obj-$(CONFIG_PCS_XPCS)		+= pcs_xpcs.o
 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_ALTERA_TSE)	+= pcs-altera-tse.o
+obj-$(CONFIG_PCS_XILINX)	+= pcs-xilinx.o
diff --git a/drivers/net/pcs/pcs-xilinx.c b/drivers/net/pcs/pcs-xilinx.c
new file mode 100644
index 000000000000..217178fbefb2
--- /dev/null
+++ b/drivers/net/pcs/pcs-xilinx.c
@@ -0,0 +1,427 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021-25 Sean Anderson <sean.anderson@seco.com>
+ *
+ * This is the driver for the Xilinx 1G/2.5G Ethernet PCS/PMA or SGMII LogiCORE
+ * IP. A typical setup will look something like
+ *
+ * MAC <--GMII--> PCS/PMA <--1000BASE-X--> SFP module (PMD)
+ *
+ * The IEEE model mostly describes this device, but the PCS layer has a
+ * separate sublayer for 8b/10b en/decoding:
+ *
+ * - When using a device-specific transceiver (serdes), the serdes handles 8b/10b
+ *   en/decoding and PMA functions. The IP implements other PCS functions.
+ * - When using LVDS IO resources, the IP implements PCS and PMA functions,
+ *   including 8b/10b en/decoding and (de)serialization.
+ * - When using an external serdes (accessed via TBI), the IP implements all
+ *   PCS functions, including 8b/10b en/decoding.
+ *
+ * The link to the PMD is not modeled by this driver, except for refclk. It is
+ * assumed that the serdes (if present) needs no configuration, though it
+ * should be fairly easy to add support. It is also possible to go from SGMII
+ * to GMII (PHY mode), but this is not supported.
+ *
+ * This driver was written with reference to PG047:
+ * https://docs.amd.com/r/en-US/pg047-gig-eth-pcs-pma
+ */
+
+#include <linux/bitmap.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iopoll.h>
+#include <linux/mdio.h>
+#include <linux/of.h>
+#include <linux/pcs.h>
+#include <linux/phylink.h>
+#include <linux/property.h>
+
+#include "../phy/phy-caps.h"
+
+/* Vendor-specific MDIO registers */
+#define XILINX_PCS_ANICR 16 /* Auto-Negotiation Interrupt Control Register */
+#define XILINX_PCS_SSR   17 /* Standard Selection Register */
+
+#define XILINX_PCS_ANICR_IE BIT(0) /* Interrupt Enable */
+#define XILINX_PCS_ANICR_IS BIT(1) /* Interrupt Status */
+
+#define XILINX_PCS_SSR_SGMII BIT(0) /* Select SGMII standard */
+
+/**
+ * struct xilinx_pcs - Private data for Xilinx PCS devices
+ * @pcs: The phylink PCS
+ * @mdiodev: The mdiodevice used to access the PCS
+ * @refclk: The reference clock for the PMD
+ * @refclk_out: Optional reference clock for other PCSs using this PCS's shared
+ *              logic
+ * @reset: The reset line for the PCS
+ * @done: Optional GPIO for reset_done
+ * @irq: IRQ, or -EINVAL if polling
+ * @enabled: Set if @pcs.link_change is valid and we can call phylink_pcs_change()
+ */
+struct xilinx_pcs {
+	struct phylink_pcs pcs;
+	struct clk_hw refclk_out;
+	struct clk *refclk;
+	struct gpio_desc *reset, *done;
+	struct mdio_device *mdiodev;
+	int irq;
+	bool enabled;
+};
+
+static inline struct xilinx_pcs *pcs_to_xilinx(struct phylink_pcs *pcs)
+{
+	return container_of(pcs, struct xilinx_pcs, pcs);
+}
+
+static irqreturn_t xilinx_pcs_an_irq(int irq, void *dev_id)
+{
+	struct xilinx_pcs *xp = dev_id;
+
+	if (mdiodev_modify_changed(xp->mdiodev, XILINX_PCS_ANICR,
+				   XILINX_PCS_ANICR_IS, 0) <= 0)
+		return IRQ_NONE;
+
+	/* paired with xilinx_pcs_enable/disable; protects xp->pcs->link_change */
+	if (smp_load_acquire(&xp->enabled))
+		phylink_pcs_change(&xp->pcs, true);
+	return IRQ_HANDLED;
+}
+
+static int xilinx_pcs_enable(struct phylink_pcs *pcs)
+{
+	struct xilinx_pcs *xp = pcs_to_xilinx(pcs);
+	struct device *dev = &xp->mdiodev->dev;
+	int ret;
+
+	if (xp->irq < 0)
+		return 0;
+
+	ret = mdiodev_modify(xp->mdiodev, XILINX_PCS_ANICR, 0,
+			     XILINX_PCS_ANICR_IE);
+	if (ret)
+		dev_err(dev, "could not clear IRQ enable: %d\n", ret);
+	else
+		/* paired with xilinx_pcs_an_irq */
+		smp_store_release(&xp->enabled, true);
+	return ret;
+}
+
+static void xilinx_pcs_disable(struct phylink_pcs *pcs)
+{
+	struct xilinx_pcs *xp = pcs_to_xilinx(pcs);
+	struct device *dev = &xp->mdiodev->dev;
+	int err;
+
+	if (xp->irq < 0)
+		return;
+
+	WRITE_ONCE(xp->enabled, false);
+	/* paired with xilinx_pcs_an_irq */
+	smp_wmb();
+
+	err = mdiodev_modify(xp->mdiodev, XILINX_PCS_ANICR,
+			     XILINX_PCS_ANICR_IE, 0);
+	if (err)
+		dev_err(dev, "could not clear IRQ enable: %d\n", err);
+}
+
+static __ETHTOOL_DECLARE_LINK_MODE_MASK(half_duplex) __ro_after_init;
+
+static int xilinx_pcs_validate(struct phylink_pcs *pcs,
+			       unsigned long *supported,
+			       const struct phylink_link_state *state)
+{
+	linkmode_andnot(supported, supported, half_duplex);
+	return 0;
+}
+
+static void xilinx_pcs_get_state(struct phylink_pcs *pcs,
+				 unsigned int neg_mode,
+				 struct phylink_link_state *state)
+{
+	struct xilinx_pcs *xp = pcs_to_xilinx(pcs);
+
+	phylink_mii_c22_pcs_get_state(xp->mdiodev, neg_mode, state);
+}
+
+static int xilinx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+			     phy_interface_t interface,
+			     const unsigned long *advertising,
+			     bool permit_pause_to_mac)
+{
+	int ret, changed = 0;
+	struct xilinx_pcs *xp = pcs_to_xilinx(pcs);
+
+	if (test_bit(PHY_INTERFACE_MODE_SGMII, pcs->supported_interfaces) &&
+	    test_bit(PHY_INTERFACE_MODE_1000BASEX, pcs->supported_interfaces)) {
+		u16 ssr;
+
+		if (interface == PHY_INTERFACE_MODE_SGMII)
+			ssr = XILINX_PCS_SSR_SGMII;
+		else
+			ssr = 0;
+
+		changed = mdiodev_modify_changed(xp->mdiodev, XILINX_PCS_SSR,
+						 XILINX_PCS_SSR_SGMII, ssr);
+		if (changed < 0)
+			return changed;
+	}
+
+	ret = phylink_mii_c22_pcs_config(xp->mdiodev, interface, advertising,
+					 neg_mode);
+	return ret ?: changed;
+}
+
+static void xilinx_pcs_an_restart(struct phylink_pcs *pcs)
+{
+	struct xilinx_pcs *xp = pcs_to_xilinx(pcs);
+
+	phylink_mii_c22_pcs_an_restart(xp->mdiodev);
+}
+
+static void xilinx_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
+			       phy_interface_t interface, int speed, int duplex)
+{
+	int bmcr;
+	struct xilinx_pcs *xp = pcs_to_xilinx(pcs);
+
+	if (phylink_autoneg_inband(mode))
+		return;
+
+	bmcr = mdiodev_read(xp->mdiodev, MII_BMCR);
+	if (bmcr < 0) {
+		dev_err(&xp->mdiodev->dev, "could not read BMCR (err=%d)\n",
+			bmcr);
+		return;
+	}
+
+	bmcr &= ~(BMCR_SPEED1000 | BMCR_SPEED100);
+	switch (speed) {
+	case SPEED_2500:
+	case SPEED_1000:
+		bmcr |= BMCR_SPEED1000;
+		break;
+	case SPEED_100:
+		bmcr |= BMCR_SPEED100;
+		break;
+	case SPEED_10:
+		bmcr |= BMCR_SPEED10;
+		break;
+	default:
+		dev_err(&xp->mdiodev->dev, "invalid speed %d\n", speed);
+	}
+
+	bmcr = mdiodev_write(xp->mdiodev, MII_BMCR, bmcr);
+	if (bmcr < 0)
+		dev_err(&xp->mdiodev->dev, "could not write BMCR (err=%d)\n",
+			bmcr);
+}
+
+static const struct phylink_pcs_ops xilinx_pcs_ops = {
+	.pcs_validate = xilinx_pcs_validate,
+	.pcs_enable = xilinx_pcs_enable,
+	.pcs_disable = xilinx_pcs_disable,
+	.pcs_get_state = xilinx_pcs_get_state,
+	.pcs_config = xilinx_pcs_config,
+	.pcs_an_restart = xilinx_pcs_an_restart,
+	.pcs_link_up = xilinx_pcs_link_up,
+};
+
+static const struct clk_ops xilinx_pcs_clk_ops = { };
+
+static const phy_interface_t xilinx_pcs_interfaces[] = {
+	PHY_INTERFACE_MODE_SGMII,
+	PHY_INTERFACE_MODE_1000BASEX,
+	PHY_INTERFACE_MODE_2500BASEX,
+};
+
+static int xilinx_pcs_probe(struct mdio_device *mdiodev)
+{
+	struct device *dev = &mdiodev->dev;
+	struct fwnode_handle *fwnode = dev->fwnode;
+	int ret, i, j, mode_count;
+	struct xilinx_pcs *xp;
+	const char **modes;
+	u32 phy_id;
+
+	xp = devm_kzalloc(dev, sizeof(*xp), GFP_KERNEL);
+	if (!xp)
+		return -ENOMEM;
+	xp->mdiodev = mdiodev;
+	dev_set_drvdata(dev, xp);
+
+	xp->irq = fwnode_irq_get_byname(fwnode, "an");
+	/* There's no _optional variant, so this is the best we've got */
+	if (xp->irq < 0 && xp->irq != -EINVAL)
+		return dev_err_probe(dev, xp->irq, "could not get IRQ\n");
+
+	mode_count = fwnode_property_string_array_count(fwnode,
+							"xlnx,pcs-modes");
+	if (!mode_count)
+		mode_count = -ENODATA;
+	if (mode_count < 0) {
+		dev_err(dev, "could not read xlnx,pcs-modes: %d", mode_count);
+		return mode_count;
+	}
+
+	modes = kcalloc(mode_count, sizeof(*modes), GFP_KERNEL);
+	if (!modes)
+		return -ENOMEM;
+
+	ret = fwnode_property_read_string_array(fwnode, "xlnx,pcs-modes",
+						modes, mode_count);
+	if (ret < 0) {
+		dev_err(dev, "could not read xlnx,pcs-modes: %d\n", ret);
+		kfree(modes);
+		return ret;
+	}
+
+	for (i = 0; i < mode_count; i++) {
+		for (j = 0; j < ARRAY_SIZE(xilinx_pcs_interfaces); j++) {
+			if (!strcmp(phy_modes(xilinx_pcs_interfaces[j]), modes[i])) {
+				__set_bit(xilinx_pcs_interfaces[j],
+					  xp->pcs.supported_interfaces);
+				goto next;
+			}
+		}
+
+		dev_err(dev, "invalid pcs-mode \"%s\"\n", modes[i]);
+		kfree(modes);
+		return -EINVAL;
+next:
+		;
+	}
+
+	kfree(modes);
+	if ((test_bit(PHY_INTERFACE_MODE_SGMII, xp->pcs.supported_interfaces) ||
+	     test_bit(PHY_INTERFACE_MODE_1000BASEX, xp->pcs.supported_interfaces)) &&
+	    test_bit(PHY_INTERFACE_MODE_2500BASEX, xp->pcs.supported_interfaces)) {
+		dev_err(dev,
+			"Switching from SGMII or 1000Base-X to 2500Base-X not supported\n");
+		return -EINVAL;
+	}
+
+	xp->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(xp->reset))
+		return dev_err_probe(dev, PTR_ERR(xp->reset),
+				     "could not get reset gpio\n");
+
+	xp->done = devm_gpiod_get_optional(dev, "done", GPIOD_IN);
+	if (IS_ERR(xp->done))
+		return dev_err_probe(dev, PTR_ERR(xp->done),
+				     "could not get done gpio\n");
+
+	xp->refclk = devm_clk_get_optional_enabled(dev, "refclk");
+	if (IS_ERR(xp->refclk))
+		return dev_err_probe(dev, PTR_ERR(xp->refclk),
+				     "could not get/enable reference clock\n");
+
+	gpiod_set_value_cansleep(xp->reset, 0);
+	if (xp->done) {
+		if (read_poll_timeout(gpiod_get_value_cansleep, ret, ret, 1000,
+				      100000, true, xp->done))
+			return dev_err_probe(dev, -ETIMEDOUT,
+					     "timed out waiting for reset\n");
+	} else {
+		/* Just wait for a while and hope we're done */
+		usleep_range(50000, 100000);
+	}
+
+	if (fwnode_property_present(fwnode, "#clock-cells")) {
+		const char *parent = "refclk";
+		struct clk_init_data init = {
+			.name = fwnode_get_name(fwnode),
+			.ops = &xilinx_pcs_clk_ops,
+			.parent_names = &parent,
+			.num_parents = 1,
+			.flags = 0,
+		};
+
+		xp->refclk_out.init = &init;
+		ret = devm_clk_hw_register(dev, &xp->refclk_out);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "could not register refclk\n");
+
+		ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
+						  &xp->refclk_out);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "could not register refclk\n");
+	}
+
+	/* Sanity check */
+	ret = get_phy_c22_id(mdiodev->bus, mdiodev->addr, &phy_id);
+	if (ret)
+		return dev_err_probe(dev, ret, "could not read id\n");
+	if ((phy_id & 0xfffffff0) != 0x01740c00)
+		dev_warn(dev, "unknown phy id %x\n", phy_id);
+
+	if (xp->irq < 0) {
+		xp->pcs.poll = true;
+	} else {
+		/* The IRQ is enabled by default; turn it off */
+		ret = mdiodev_write(xp->mdiodev, XILINX_PCS_ANICR, 0);
+		if (ret) {
+			dev_err(dev, "could not disable IRQ: %d\n", ret);
+			return ret;
+		}
+
+		/* Some PCSs have a bad habit of re-enabling their IRQ!
+		 * Request the IRQ in probe so we don't end up triggering the
+		 * spurious IRQ logic.
+		 */
+		ret = devm_request_threaded_irq(dev, xp->irq, NULL, xilinx_pcs_an_irq,
+						IRQF_SHARED | IRQF_ONESHOT,
+						dev_name(dev), xp);
+		if (ret) {
+			dev_err(dev, "could not request IRQ: %d\n", ret);
+			return ret;
+		}
+	}
+
+	xp->pcs.ops = &xilinx_pcs_ops;
+	ret = devm_pcs_register(dev, &xp->pcs);
+	if (ret)
+		return dev_err_probe(dev, ret, "could not register PCS\n");
+
+	if (xp->irq < 0)
+		dev_info(dev, "probed with irq=poll\n");
+	else
+		dev_info(dev, "probed with irq=%d\n", xp->irq);
+	return 0;
+}
+
+static const struct of_device_id xilinx_pcs_of_match[] = {
+	{ .compatible = "xlnx,pcs", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, xilinx_pcs_of_match);
+
+static struct mdio_driver xilinx_pcs_driver = {
+	.probe = xilinx_pcs_probe,
+	.mdiodrv.driver = {
+		.name = "xilinx-pcs",
+		.of_match_table = of_match_ptr(xilinx_pcs_of_match),
+	},
+};
+
+static int __init xilinx_pcs_init(void)
+{
+	phy_caps_linkmodes(LINK_CAPA_10HD | LINK_CAPA_100HD | LINK_CAPA_1000HD,
+			   half_duplex);
+	return mdio_driver_register(&xilinx_pcs_driver);
+}
+module_init(xilinx_pcs_init);
+
+static void __exit xilinx_pcs_exit(void)
+{
+	mdio_driver_unregister(&xilinx_pcs_driver);
+}
+module_exit(xilinx_pcs_exit)
+
+MODULE_ALIAS("platform:xilinx-pcs");
+MODULE_DESCRIPTION("Xilinx PCS driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 73f9cb2e2844..ee8fb3806312 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -921,7 +921,7 @@ static int get_phy_c45_ids(struct mii_bus *bus, int addr,
  * valid, %-EIO on bus access error, or %-ENODEV if no device responds
  * or invalid ID.
  */
-static int get_phy_c22_id(struct mii_bus *bus, int addr, u32 *phy_id)
+int get_phy_c22_id(struct mii_bus *bus, int addr, u32 *phy_id)
 {
 	int phy_reg;
 
@@ -949,6 +949,7 @@ static int get_phy_c22_id(struct mii_bus *bus, int addr, u32 *phy_id)
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(get_phy_c22_id);
 
 /* Extract the phy ID from the compatible string of the form
  * ethernet-phy-idAAAA.BBBB.
diff --git a/include/linux/phy.h b/include/linux/phy.h
index e194dad1623d..71c066a718c3 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -1751,6 +1751,7 @@ int phy_modify_paged(struct phy_device *phydev, int page, u32 regnum,
 struct phy_device *phy_device_create(struct mii_bus *bus, int addr, u32 phy_id,
 				     bool is_c45,
 				     struct phy_c45_device_ids *c45_ids);
+int get_phy_c22_id(struct mii_bus *bus, int addr, u32 *phy_id);
 int fwnode_get_phy_id(struct fwnode_handle *fwnode, u32 *phy_id);
 struct mdio_device *fwnode_mdio_find_device(struct fwnode_handle *fwnode);
 struct phy_device *fwnode_phy_find_device(struct fwnode_handle *phy_fwnode);
-- 
2.35.1.1320.gc452695387.dirty


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

* [net-next PATCH v6 07/10] net: axienet: Convert to use PCS subsystem
  2025-06-10 23:31 [net-next PATCH v6 00/10] Add PCS core support Sean Anderson
                   ` (5 preceding siblings ...)
  2025-06-10 23:31 ` [net-next PATCH v6 06/10] net: pcs: Add Xilinx PCS driver Sean Anderson
@ 2025-06-10 23:31 ` Sean Anderson
  2025-06-11 20:56   ` kernel test robot
  2025-06-11 22:19   ` kernel test robot
  2025-06-10 23:31 ` [net-next PATCH v6 08/10] net: macb: Move most of mac_config to mac_prepare Sean Anderson
                   ` (3 subsequent siblings)
  10 siblings, 2 replies; 21+ messages in thread
From: Sean Anderson @ 2025-06-10 23:31 UTC (permalink / raw)
  To: netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King
  Cc: Vineeth Karumanchi, Heiner Kallweit, linux-kernel, Kory Maincent,
	Daniel Golle, Simon Horman, Christian Marangi, Lei Wei,
	Sean Anderson, Michal Simek, Radhey Shyam Pandey, Robert Hancock,
	linux-arm-kernel

Convert the AXI Ethernet driver to use the PCS subsystem, including the
new Xilinx PCA/PMA driver. Unfortunately, we must use a helper to work
with bare MDIO nodes without a compatible. This functionality is gated
behind a config to allow it to be disabled by vendors who have fixed all
of their devicetrees.

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>

---

Changes in v6:
- Introduce config for compatibility helpers
- Move axienet_pcs_fixup to this commit

Changes in v5:
- Use MDIO_BUS instead of MDIO_DEVICE

Changes in v4:
- Convert to dev-less pcs_put

Changes in v3:
- Select PCS_XILINX unconditionally

 drivers/net/ethernet/xilinx/Kconfig           |  13 ++
 drivers/net/ethernet/xilinx/xilinx_axienet.h  |   4 +-
 .../net/ethernet/xilinx/xilinx_axienet_main.c | 160 +++++++++---------
 3 files changed, 90 insertions(+), 87 deletions(-)

diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig
index 7502214cc7d5..d9c7a151bbaa 100644
--- a/drivers/net/ethernet/xilinx/Kconfig
+++ b/drivers/net/ethernet/xilinx/Kconfig
@@ -27,12 +27,25 @@ config XILINX_AXI_EMAC
 	tristate "Xilinx 10/100/1000 AXI Ethernet support"
 	depends on HAS_IOMEM
 	depends on XILINX_DMA
+	select MDIO_BUS
+	select OF_DYNAMIC
 	select PHYLINK
 	select DIMLIB
 	help
 	  This driver supports the 10/100/1000 Ethernet from Xilinx for the
 	  AXI bus interface used in Xilinx Virtex FPGAs and Soc's.
 
+config XILINX_AXI_EMAC_PCS_COMPAT
+	bool "Xilinx AXI Ethernet PCS compatibility shim"
+	default PCS_XILINX
+	depends on XILINX_AXI_EMAC
+	select OF_DYNAMIC
+	help
+	  Enable support for older devicetrees which do not have compatibles
+	  for the Xilinx PCS/PMA. If you enable this option, you should
+	  probably enable PCS_XILINX too. If you do not use 1G speeds, or you
+	  do not have a (Xilinx) PCS, say n. If unsure, say y.
+
 config XILINX_LL_TEMAC
 	tristate "Xilinx LL TEMAC (LocalLink Tri-mode Ethernet MAC) driver"
 	depends on HAS_IOMEM
diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h
index 5ff742103beb..f46e862245eb 100644
--- a/drivers/net/ethernet/xilinx/xilinx_axienet.h
+++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h
@@ -473,7 +473,6 @@ struct skbuf_dma_descriptor {
  * @dev:	Pointer to device structure
  * @phylink:	Pointer to phylink instance
  * @phylink_config: phylink configuration settings
- * @pcs_phy:	Reference to PCS/PMA PHY if used
  * @pcs:	phylink pcs structure for PCS PHY
  * @switch_x_sgmii: Whether switchable 1000BaseX/SGMII mode is enabled in the core
  * @axi_clk:	AXI4-Lite bus clock
@@ -553,8 +552,7 @@ struct axienet_local {
 	struct phylink *phylink;
 	struct phylink_config phylink_config;
 
-	struct mdio_device *pcs_phy;
-	struct phylink_pcs pcs;
+	struct phylink_pcs *pcs;
 
 	bool switch_x_sgmii;
 
diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
index 6011d7eae0c7..09852960713b 100644
--- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
+++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
@@ -35,6 +35,7 @@
 #include <linux/platform_device.h>
 #include <linux/skbuff.h>
 #include <linux/math64.h>
+#include <linux/pcs.h>
 #include <linux/phy.h>
 #include <linux/mii.h>
 #include <linux/ethtool.h>
@@ -2519,63 +2520,6 @@ static const struct ethtool_ops axienet_ethtool_ops = {
 	.get_rmon_stats = axienet_ethtool_get_rmon_stats,
 };
 
-static struct axienet_local *pcs_to_axienet_local(struct phylink_pcs *pcs)
-{
-	return container_of(pcs, struct axienet_local, pcs);
-}
-
-static void axienet_pcs_get_state(struct phylink_pcs *pcs,
-				  unsigned int neg_mode,
-				  struct phylink_link_state *state)
-{
-	struct mdio_device *pcs_phy = pcs_to_axienet_local(pcs)->pcs_phy;
-
-	phylink_mii_c22_pcs_get_state(pcs_phy, neg_mode, state);
-}
-
-static void axienet_pcs_an_restart(struct phylink_pcs *pcs)
-{
-	struct mdio_device *pcs_phy = pcs_to_axienet_local(pcs)->pcs_phy;
-
-	phylink_mii_c22_pcs_an_restart(pcs_phy);
-}
-
-static int axienet_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
-			      phy_interface_t interface,
-			      const unsigned long *advertising,
-			      bool permit_pause_to_mac)
-{
-	struct mdio_device *pcs_phy = pcs_to_axienet_local(pcs)->pcs_phy;
-	struct net_device *ndev = pcs_to_axienet_local(pcs)->ndev;
-	struct axienet_local *lp = netdev_priv(ndev);
-	int ret;
-
-	if (lp->switch_x_sgmii) {
-		ret = mdiodev_write(pcs_phy, XLNX_MII_STD_SELECT_REG,
-				    interface == PHY_INTERFACE_MODE_SGMII ?
-					XLNX_MII_STD_SELECT_SGMII : 0);
-		if (ret < 0) {
-			netdev_warn(ndev,
-				    "Failed to switch PHY interface: %d\n",
-				    ret);
-			return ret;
-		}
-	}
-
-	ret = phylink_mii_c22_pcs_config(pcs_phy, interface, advertising,
-					 neg_mode);
-	if (ret < 0)
-		netdev_warn(ndev, "Failed to configure PCS: %d\n", ret);
-
-	return ret;
-}
-
-static const struct phylink_pcs_ops axienet_pcs_ops = {
-	.pcs_get_state = axienet_pcs_get_state,
-	.pcs_config = axienet_pcs_config,
-	.pcs_an_restart = axienet_pcs_an_restart,
-};
-
 static struct phylink_pcs *axienet_mac_select_pcs(struct phylink_config *config,
 						  phy_interface_t interface)
 {
@@ -2583,8 +2527,8 @@ static struct phylink_pcs *axienet_mac_select_pcs(struct phylink_config *config,
 	struct axienet_local *lp = netdev_priv(ndev);
 
 	if (interface == PHY_INTERFACE_MODE_1000BASEX ||
-	    interface ==  PHY_INTERFACE_MODE_SGMII)
-		return &lp->pcs;
+	    interface == PHY_INTERFACE_MODE_SGMII)
+		return lp->pcs;
 
 	return NULL;
 }
@@ -2744,6 +2688,59 @@ static void axienet_dma_err_handler(struct work_struct *work)
 	axienet_setoptions(ndev, lp->options);
 }
 
+#ifdef CONFIG_XILINX_AXI_EMAC_PCS_COMPAT
+static int axienet_pcs_fixup(struct of_changeset *ocs, struct device_node *np,
+			     void *data)
+{
+	unsigned int interface, mode_count, mode = 0;
+	const unsigned long *interfaces = data;
+	const char **modes;
+	int ret;
+
+	mode_count = bitmap_weight(interfaces, PHY_INTERFACE_MODE_MAX);
+	WARN_ON_ONCE(!mode_count);
+	modes = kcalloc(mode_count, sizeof(*modes), GFP_KERNEL);
+	if (!modes)
+		return -ENOMEM;
+
+	for_each_set_bit(interface, interfaces, PHY_INTERFACE_MODE_MAX)
+		modes[mode++] = phy_modes(interface);
+	ret = of_changeset_add_prop_string_array(ocs, np, "xlnx,pcs-modes",
+						 modes, mode_count);
+	kfree(modes);
+	if (ret)
+		return ret;
+
+	return of_changeset_add_prop_string(ocs, np, "compatible", "xlnx,pcs");
+}
+
+/**
+ * axienet_pcs_get() - PCS Compatibility function for old device trees
+ * @dev: The MAC device
+ * @interfaces: The interfaces to use as a fallback
+ *
+ * This is a helper function to ensure backwards compatibility with device
+ * trees which do not include compatible strings for the PCS/PMA.
+ *
+ * Return: a PCS, or an error pointer
+ */
+static struct phylink_pcs *axienet_pcs_get(struct device *dev,
+					   const unsigned long *interfaces)
+{
+	struct fwnode_handle *fwnode;
+	struct phylink_pcs *pcs;
+
+	fwnode = pcs_find_fwnode(dev_fwnode(dev), NULL, "phy-handle", false);
+	if (IS_ERR(fwnode))
+		return ERR_CAST(fwnode);
+
+	pcs = pcs_get_by_fwnode_compat(dev, fwnode, axienet_pcs_fixup,
+				       (void *)interfaces);
+	fwnode_handle_put(fwnode);
+	return pcs;
+}
+#endif
+
 /**
  * axienet_probe - Axi Ethernet probe function.
  * @pdev:	Pointer to platform device structure.
@@ -3056,28 +3053,27 @@ static int axienet_probe(struct platform_device *pdev)
 
 	if (lp->phy_mode == PHY_INTERFACE_MODE_SGMII ||
 	    lp->phy_mode == PHY_INTERFACE_MODE_1000BASEX) {
-		np = of_parse_phandle(pdev->dev.of_node, "pcs-handle", 0);
-		if (!np) {
-			/* Deprecated: Always use "pcs-handle" for pcs_phy.
-			 * Falling back to "phy-handle" here is only for
-			 * backward compatibility with old device trees.
-			 */
-			np = of_parse_phandle(pdev->dev.of_node, "phy-handle", 0);
-		}
-		if (!np) {
-			dev_err(&pdev->dev, "pcs-handle (preferred) or phy-handle required for 1000BaseX/SGMII\n");
-			ret = -EINVAL;
-			goto cleanup_mdio;
-		}
-		lp->pcs_phy = of_mdio_find_device(np);
-		if (!lp->pcs_phy) {
-			ret = -EPROBE_DEFER;
-			of_node_put(np);
+#ifdef CONFIG_XILINX_AXI_EMAC_PCS_COMPAT
+		DECLARE_PHY_INTERFACE_MASK(interfaces);
+
+		phy_interface_zero(interfaces);
+		if (lp->switch_x_sgmii ||
+		    lp->phy_mode == PHY_INTERFACE_MODE_SGMII)
+			__set_bit(PHY_INTERFACE_MODE_SGMII, interfaces);
+		if (lp->switch_x_sgmii ||
+		    lp->phy_mode == PHY_INTERFACE_MODE_1000BASEX)
+			__set_bit(PHY_INTERFACE_MODE_1000BASEX, interfaces);
+
+		lp->pcs = axienet_pcs_get(&pdev->dev, interfaces);
+#else
+		lp->pcs = pcs_get(&pdev->dev, NULL);
+#endif
+		if (IS_ERR(lp->pcs)) {
+			ret = PTR_ERR(lp->pcs);
+			dev_err_probe(&pdev->dev, ret,
+				      "could not get PCS for 1000BASE-X/SGMII\n");
 			goto cleanup_mdio;
 		}
-		of_node_put(np);
-		lp->pcs.ops = &axienet_pcs_ops;
-		lp->pcs.poll = true;
 	}
 
 	lp->phylink_config.dev = &ndev->dev;
@@ -3115,8 +3111,6 @@ static int axienet_probe(struct platform_device *pdev)
 	phylink_destroy(lp->phylink);
 
 cleanup_mdio:
-	if (lp->pcs_phy)
-		put_device(&lp->pcs_phy->dev);
 	if (lp->mii_bus)
 		axienet_mdio_teardown(lp);
 cleanup_clk:
@@ -3139,9 +3133,7 @@ static void axienet_remove(struct platform_device *pdev)
 	if (lp->phylink)
 		phylink_destroy(lp->phylink);
 
-	if (lp->pcs_phy)
-		put_device(&lp->pcs_phy->dev);
-
+	pcs_put(lp->pcs);
 	axienet_mdio_teardown(lp);
 
 	clk_bulk_disable_unprepare(XAE_NUM_MISC_CLOCKS, lp->misc_clks);
-- 
2.35.1.1320.gc452695387.dirty


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

* [net-next PATCH v6 08/10] net: macb: Move most of mac_config to mac_prepare
  2025-06-10 23:31 [net-next PATCH v6 00/10] Add PCS core support Sean Anderson
                   ` (6 preceding siblings ...)
  2025-06-10 23:31 ` [net-next PATCH v6 07/10] net: axienet: Convert to use PCS subsystem Sean Anderson
@ 2025-06-10 23:31 ` Sean Anderson
  2025-06-10 23:35 ` Sean Anderson
                   ` (2 subsequent siblings)
  10 siblings, 0 replies; 21+ messages in thread
From: Sean Anderson @ 2025-06-10 23:31 UTC (permalink / raw)
  To: netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King
  Cc: Vineeth Karumanchi, Heiner Kallweit, linux-kernel, Kory Maincent,
	Daniel Golle, Simon Horman, Christian Marangi, Lei Wei,
	Sean Anderson, Claudiu Beznea, Nicolas Ferre

mac_prepare is called every time the interface is changed, so we can do
all of our configuration there, instead of in mac_config. This will be
useful for the next patch where we will set the PCS bit based on whether
we are using our internal PCS. No functional change intended.

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

Changes in v6:
- Fix use of spin_lock instead of spin_unlock

Changes in v2:
- Fix docs for macb_pcs_config_an
- Include change to macb_pcs_get_state which was previously in the next
  patch

 drivers/net/ethernet/cadence/macb_main.c | 209 ++++++++++++++---------
 1 file changed, 132 insertions(+), 77 deletions(-)

diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index d1f1ae5ea161..78433d8f3746 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -549,19 +549,91 @@ static void macb_set_tx_clk(struct macb *bp, int speed)
 		netdev_err(bp->dev, "adjusting tx_clk failed.\n");
 }
 
-static void macb_usx_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
-				 phy_interface_t interface, int speed,
-				 int duplex)
-{
-	struct macb *bp = container_of(pcs, struct macb, phylink_usx_pcs);
-	u32 config;
-
-	config = gem_readl(bp, USX_CONTROL);
-	config = GEM_BFINS(SERDES_RATE, MACB_SERDES_RATE_10G, config);
-	config = GEM_BFINS(USX_CTRL_SPEED, HS_SPEED_10000M, config);
-	config &= ~(GEM_BIT(TX_SCR_BYPASS) | GEM_BIT(RX_SCR_BYPASS));
-	config |= GEM_BIT(TX_EN);
-	gem_writel(bp, USX_CONTROL, config);
+static void macb_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
+			       struct phylink_link_state *state)
+{
+	struct macb *bp = container_of(pcs, struct macb, phylink_sgmii_pcs);
+
+	phylink_mii_c22_pcs_decode_state(state, neg_mode, gem_readl(bp, PCSSTS),
+					 gem_readl(bp, PCSANLPBASE));
+}
+
+/**
+ * macb_pcs_config_an() - Configure autonegotiation settings for PCSs
+ * @bp: The macb to operate on
+ * @neg_mode: The autonegotiation mode
+ * @interface: The interface to use
+ * @advertising: The advertisement mask
+ *
+ * This provides common configuration for PCS autonegotiation.
+ *
+ * Context: Call with @bp->lock held.
+ * Return: 1 if any registers were changed; 0 otherwise
+ */
+static int macb_pcs_config_an(struct macb *bp, unsigned int neg_mode,
+			      phy_interface_t interface,
+			      const unsigned long *advertising)
+{
+	bool changed = false;
+	int old, new;
+
+	old = gem_readl(bp, PCSANADV);
+	new = phylink_mii_c22_pcs_encode_advertisement(interface, advertising);
+	if (new != -EINVAL && old != new) {
+		changed = true;
+		gem_writel(bp, PCSANADV, new);
+	}
+
+	old = new = gem_readl(bp, PCSCNTRL);
+	if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
+		new |= BMCR_ANENABLE;
+	else
+		new &= ~BMCR_ANENABLE;
+	if (old != new) {
+		changed = true;
+		gem_writel(bp, PCSCNTRL, new);
+	}
+	return changed;
+}
+
+static int macb_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+			   phy_interface_t interface,
+			   const unsigned long *advertising,
+			   bool permit_pause_to_mac)
+{
+	struct macb *bp = container_of(pcs, struct macb, phylink_sgmii_pcs);
+	bool changed = false;
+	unsigned long flags;
+	u32 old, new;
+
+	spin_lock_irqsave(&bp->lock, flags);
+	old = new = gem_readl(bp, NCFGR);
+	new |= GEM_BIT(SGMIIEN);
+	if (old != new) {
+		changed = true;
+		gem_writel(bp, NCFGR, new);
+	}
+
+	if (macb_pcs_config_an(bp, mode, interface, advertising))
+		changed = true;
+
+	spin_unlock_irqrestore(&bp->lock, flags);
+	return changed;
+}
+
+static void macb_pcs_an_restart(struct phylink_pcs *pcs)
+{
+	struct macb *bp = container_of(pcs, struct macb, phylink_sgmii_pcs);
+	u32 bmcr;
+	unsigned long flags;
+
+	spin_lock_irqsave(&bp->lock, flags);
+
+	bmcr = gem_readl(bp, PCSCNTRL);
+	bmcr |= BMCR_ANENABLE;
+	gem_writel(bp, PCSCNTRL, bmcr);
+
+	spin_unlock_irqrestore(&bp->lock, flags);
 }
 
 static void macb_usx_pcs_get_state(struct phylink_pcs *pcs,
@@ -589,45 +661,60 @@ static int macb_usx_pcs_config(struct phylink_pcs *pcs,
 			       bool permit_pause_to_mac)
 {
 	struct macb *bp = container_of(pcs, struct macb, phylink_usx_pcs);
+	unsigned long flags;
+	bool changed;
+	u16 old, new;
 
-	gem_writel(bp, USX_CONTROL, gem_readl(bp, USX_CONTROL) |
-		   GEM_BIT(SIGNAL_OK));
+	spin_lock_irqsave(&bp->lock, flags);
+	if (macb_pcs_config_an(bp, neg_mode, interface, advertising))
+		changed = true;
 
-	return 0;
-}
+	old = new = gem_readl(bp, USX_CONTROL);
+	new |= GEM_BIT(SIGNAL_OK);
+	if (old != new) {
+		changed = true;
+		gem_writel(bp, USX_CONTROL, new);
+	}
 
-static void macb_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
-			       struct phylink_link_state *state)
-{
-	state->link = 0;
-}
+	old = new = gem_readl(bp, USX_CONTROL);
+	new = GEM_BFINS(SERDES_RATE, MACB_SERDES_RATE_10G, new);
+	new = GEM_BFINS(USX_CTRL_SPEED, HS_SPEED_10000M, new);
+	new &= ~(GEM_BIT(TX_SCR_BYPASS) | GEM_BIT(RX_SCR_BYPASS));
+	new |= GEM_BIT(TX_EN);
+	if (old != new) {
+		changed = true;
+		gem_writel(bp, USX_CONTROL, new);
+	}
 
-static void macb_pcs_an_restart(struct phylink_pcs *pcs)
-{
-	/* Not supported */
-}
-
-static int macb_pcs_config(struct phylink_pcs *pcs,
-			   unsigned int neg_mode,
-			   phy_interface_t interface,
-			   const unsigned long *advertising,
-			   bool permit_pause_to_mac)
-{
-	return 0;
+	spin_unlock_irqrestore(&bp->lock, flags);
+	return changed;
 }
 
 static const struct phylink_pcs_ops macb_phylink_usx_pcs_ops = {
 	.pcs_get_state = macb_usx_pcs_get_state,
 	.pcs_config = macb_usx_pcs_config,
-	.pcs_link_up = macb_usx_pcs_link_up,
 };
 
 static const struct phylink_pcs_ops macb_phylink_pcs_ops = {
 	.pcs_get_state = macb_pcs_get_state,
-	.pcs_an_restart = macb_pcs_an_restart,
 	.pcs_config = macb_pcs_config,
+	.pcs_an_restart = macb_pcs_an_restart,
 };
 
+static struct phylink_pcs *macb_mac_select_pcs(struct phylink_config *config,
+					       phy_interface_t interface)
+{
+	struct net_device *ndev = to_net_dev(config->dev);
+	struct macb *bp = netdev_priv(ndev);
+
+	if (interface == PHY_INTERFACE_MODE_10GBASER)
+		return &bp->phylink_usx_pcs;
+	else if (interface == PHY_INTERFACE_MODE_SGMII)
+		return &bp->phylink_sgmii_pcs;
+	else
+		return NULL;
+}
+
 static void macb_mac_config(struct phylink_config *config, unsigned int mode,
 			    const struct phylink_link_state *state)
 {
@@ -646,18 +733,14 @@ static void macb_mac_config(struct phylink_config *config, unsigned int mode,
 		if (state->interface == PHY_INTERFACE_MODE_RMII)
 			ctrl |= MACB_BIT(RM9200_RMII);
 	} else if (macb_is_gem(bp)) {
-		ctrl &= ~(GEM_BIT(SGMIIEN) | GEM_BIT(PCSSEL));
-		ncr &= ~GEM_BIT(ENABLE_HS_MAC);
-
-		if (state->interface == PHY_INTERFACE_MODE_SGMII) {
-			ctrl |= GEM_BIT(SGMIIEN) | GEM_BIT(PCSSEL);
-		} else if (state->interface == PHY_INTERFACE_MODE_10GBASER) {
+		if (macb_mac_select_pcs(config, state->interface))
 			ctrl |= GEM_BIT(PCSSEL);
-			ncr |= GEM_BIT(ENABLE_HS_MAC);
-		} else if (bp->caps & MACB_CAPS_MIIONRGMII &&
-			   bp->phy_interface == PHY_INTERFACE_MODE_MII) {
+		else
+			ctrl &= ~GEM_BIT(PCSSEL);
+
+		if (bp->caps & MACB_CAPS_MIIONRGMII &&
+		    bp->phy_interface == PHY_INTERFACE_MODE_MII)
 			ncr |= MACB_BIT(MIIONRGMII);
-		}
 	}
 
 	/* Apply the new configuration, if any */
@@ -667,22 +750,6 @@ static void macb_mac_config(struct phylink_config *config, unsigned int mode,
 	if (old_ncr ^ ncr)
 		macb_or_gem_writel(bp, NCR, ncr);
 
-	/* Disable AN for SGMII fixed link configuration, enable otherwise.
-	 * Must be written after PCSSEL is set in NCFGR,
-	 * otherwise writes will not take effect.
-	 */
-	if (macb_is_gem(bp) && state->interface == PHY_INTERFACE_MODE_SGMII) {
-		u32 pcsctrl, old_pcsctrl;
-
-		old_pcsctrl = gem_readl(bp, PCSCNTRL);
-		if (mode == MLO_AN_FIXED)
-			pcsctrl = old_pcsctrl & ~GEM_BIT(PCSAUTONEG);
-		else
-			pcsctrl = old_pcsctrl | GEM_BIT(PCSAUTONEG);
-		if (old_pcsctrl != pcsctrl)
-			gem_writel(bp, PCSCNTRL, pcsctrl);
-	}
-
 	spin_unlock_irqrestore(&bp->lock, flags);
 }
 
@@ -735,10 +802,12 @@ static void macb_mac_link_up(struct phylink_config *config,
 	if (!(bp->caps & MACB_CAPS_MACB_IS_EMAC)) {
 		ctrl &= ~MACB_BIT(PAE);
 		if (macb_is_gem(bp)) {
-			ctrl &= ~GEM_BIT(GBE);
+			ctrl &= ~(GEM_BIT(GBE) | GEM_BIT(ENABLE_HS_MAC));
 
 			if (speed == SPEED_1000)
 				ctrl |= GEM_BIT(GBE);
+			else if (speed == SPEED_10000)
+				ctrl |= GEM_BIT(ENABLE_HS_MAC);
 		}
 
 		if (rx_pause)
@@ -776,20 +845,6 @@ static void macb_mac_link_up(struct phylink_config *config,
 	netif_tx_wake_all_queues(ndev);
 }
 
-static struct phylink_pcs *macb_mac_select_pcs(struct phylink_config *config,
-					       phy_interface_t interface)
-{
-	struct net_device *ndev = to_net_dev(config->dev);
-	struct macb *bp = netdev_priv(ndev);
-
-	if (interface == PHY_INTERFACE_MODE_10GBASER)
-		return &bp->phylink_usx_pcs;
-	else if (interface == PHY_INTERFACE_MODE_SGMII)
-		return &bp->phylink_sgmii_pcs;
-	else
-		return NULL;
-}
-
 static const struct phylink_mac_ops macb_phylink_ops = {
 	.mac_select_pcs = macb_mac_select_pcs,
 	.mac_config = macb_mac_config,
-- 
2.35.1.1320.gc452695387.dirty


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

* [net-next PATCH v6 08/10] net: macb: Move most of mac_config to mac_prepare
  2025-06-10 23:31 [net-next PATCH v6 00/10] Add PCS core support Sean Anderson
                   ` (7 preceding siblings ...)
  2025-06-10 23:31 ` [net-next PATCH v6 08/10] net: macb: Move most of mac_config to mac_prepare Sean Anderson
@ 2025-06-10 23:35 ` Sean Anderson
  2025-06-10 23:36 ` [net-next PATCH v6 09/10] net: macb: Support external PCSs Sean Anderson
  2025-06-10 23:37 ` [net-next PATCH v6 10/10] of: property: Add device link support for PCS Sean Anderson
  10 siblings, 0 replies; 21+ messages in thread
From: Sean Anderson @ 2025-06-10 23:35 UTC (permalink / raw)
  To: netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King
  Cc: linux-kernel, Daniel Golle, Simon Horman, Kory Maincent,
	Heiner Kallweit, Lei Wei, Christian Marangi, Vineeth Karumanchi,
	Sean Anderson, Claudiu Beznea, Nicolas Ferre

mac_prepare is called every time the interface is changed, so we can do
all of our configuration there, instead of in mac_config. This will be
useful for the next patch where we will set the PCS bit based on whether
we are using our internal PCS. No functional change intended.

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

Changes in v6:
- Fix use of spin_lock instead of spin_unlock

Changes in v2:
- Fix docs for macb_pcs_config_an
- Include change to macb_pcs_get_state which was previously in the next
  patch

 drivers/net/ethernet/cadence/macb_main.c | 209 ++++++++++++++---------
 1 file changed, 132 insertions(+), 77 deletions(-)

diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index d1f1ae5ea161..78433d8f3746 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -549,19 +549,91 @@ static void macb_set_tx_clk(struct macb *bp, int speed)
 		netdev_err(bp->dev, "adjusting tx_clk failed.\n");
 }
 
-static void macb_usx_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
-				 phy_interface_t interface, int speed,
-				 int duplex)
-{
-	struct macb *bp = container_of(pcs, struct macb, phylink_usx_pcs);
-	u32 config;
-
-	config = gem_readl(bp, USX_CONTROL);
-	config = GEM_BFINS(SERDES_RATE, MACB_SERDES_RATE_10G, config);
-	config = GEM_BFINS(USX_CTRL_SPEED, HS_SPEED_10000M, config);
-	config &= ~(GEM_BIT(TX_SCR_BYPASS) | GEM_BIT(RX_SCR_BYPASS));
-	config |= GEM_BIT(TX_EN);
-	gem_writel(bp, USX_CONTROL, config);
+static void macb_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
+			       struct phylink_link_state *state)
+{
+	struct macb *bp = container_of(pcs, struct macb, phylink_sgmii_pcs);
+
+	phylink_mii_c22_pcs_decode_state(state, neg_mode, gem_readl(bp, PCSSTS),
+					 gem_readl(bp, PCSANLPBASE));
+}
+
+/**
+ * macb_pcs_config_an() - Configure autonegotiation settings for PCSs
+ * @bp: The macb to operate on
+ * @neg_mode: The autonegotiation mode
+ * @interface: The interface to use
+ * @advertising: The advertisement mask
+ *
+ * This provides common configuration for PCS autonegotiation.
+ *
+ * Context: Call with @bp->lock held.
+ * Return: 1 if any registers were changed; 0 otherwise
+ */
+static int macb_pcs_config_an(struct macb *bp, unsigned int neg_mode,
+			      phy_interface_t interface,
+			      const unsigned long *advertising)
+{
+	bool changed = false;
+	int old, new;
+
+	old = gem_readl(bp, PCSANADV);
+	new = phylink_mii_c22_pcs_encode_advertisement(interface, advertising);
+	if (new != -EINVAL && old != new) {
+		changed = true;
+		gem_writel(bp, PCSANADV, new);
+	}
+
+	old = new = gem_readl(bp, PCSCNTRL);
+	if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
+		new |= BMCR_ANENABLE;
+	else
+		new &= ~BMCR_ANENABLE;
+	if (old != new) {
+		changed = true;
+		gem_writel(bp, PCSCNTRL, new);
+	}
+	return changed;
+}
+
+static int macb_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+			   phy_interface_t interface,
+			   const unsigned long *advertising,
+			   bool permit_pause_to_mac)
+{
+	struct macb *bp = container_of(pcs, struct macb, phylink_sgmii_pcs);
+	bool changed = false;
+	unsigned long flags;
+	u32 old, new;
+
+	spin_lock_irqsave(&bp->lock, flags);
+	old = new = gem_readl(bp, NCFGR);
+	new |= GEM_BIT(SGMIIEN);
+	if (old != new) {
+		changed = true;
+		gem_writel(bp, NCFGR, new);
+	}
+
+	if (macb_pcs_config_an(bp, mode, interface, advertising))
+		changed = true;
+
+	spin_unlock_irqrestore(&bp->lock, flags);
+	return changed;
+}
+
+static void macb_pcs_an_restart(struct phylink_pcs *pcs)
+{
+	struct macb *bp = container_of(pcs, struct macb, phylink_sgmii_pcs);
+	u32 bmcr;
+	unsigned long flags;
+
+	spin_lock_irqsave(&bp->lock, flags);
+
+	bmcr = gem_readl(bp, PCSCNTRL);
+	bmcr |= BMCR_ANENABLE;
+	gem_writel(bp, PCSCNTRL, bmcr);
+
+	spin_unlock_irqrestore(&bp->lock, flags);
 }
 
 static void macb_usx_pcs_get_state(struct phylink_pcs *pcs,
@@ -589,45 +661,60 @@ static int macb_usx_pcs_config(struct phylink_pcs *pcs,
 			       bool permit_pause_to_mac)
 {
 	struct macb *bp = container_of(pcs, struct macb, phylink_usx_pcs);
+	unsigned long flags;
+	bool changed;
+	u16 old, new;
 
-	gem_writel(bp, USX_CONTROL, gem_readl(bp, USX_CONTROL) |
-		   GEM_BIT(SIGNAL_OK));
+	spin_lock_irqsave(&bp->lock, flags);
+	if (macb_pcs_config_an(bp, neg_mode, interface, advertising))
+		changed = true;
 
-	return 0;
-}
+	old = new = gem_readl(bp, USX_CONTROL);
+	new |= GEM_BIT(SIGNAL_OK);
+	if (old != new) {
+		changed = true;
+		gem_writel(bp, USX_CONTROL, new);
+	}
 
-static void macb_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
-			       struct phylink_link_state *state)
-{
-	state->link = 0;
-}
+	old = new = gem_readl(bp, USX_CONTROL);
+	new = GEM_BFINS(SERDES_RATE, MACB_SERDES_RATE_10G, new);
+	new = GEM_BFINS(USX_CTRL_SPEED, HS_SPEED_10000M, new);
+	new &= ~(GEM_BIT(TX_SCR_BYPASS) | GEM_BIT(RX_SCR_BYPASS));
+	new |= GEM_BIT(TX_EN);
+	if (old != new) {
+		changed = true;
+		gem_writel(bp, USX_CONTROL, new);
+	}
 
-static void macb_pcs_an_restart(struct phylink_pcs *pcs)
-{
-	/* Not supported */
-}
-
-static int macb_pcs_config(struct phylink_pcs *pcs,
-			   unsigned int neg_mode,
-			   phy_interface_t interface,
-			   const unsigned long *advertising,
-			   bool permit_pause_to_mac)
-{
-	return 0;
+	spin_unlock_irqrestore(&bp->lock, flags);
+	return changed;
 }
 
 static const struct phylink_pcs_ops macb_phylink_usx_pcs_ops = {
 	.pcs_get_state = macb_usx_pcs_get_state,
 	.pcs_config = macb_usx_pcs_config,
-	.pcs_link_up = macb_usx_pcs_link_up,
 };
 
 static const struct phylink_pcs_ops macb_phylink_pcs_ops = {
 	.pcs_get_state = macb_pcs_get_state,
-	.pcs_an_restart = macb_pcs_an_restart,
 	.pcs_config = macb_pcs_config,
+	.pcs_an_restart = macb_pcs_an_restart,
 };
 
+static struct phylink_pcs *macb_mac_select_pcs(struct phylink_config *config,
+					       phy_interface_t interface)
+{
+	struct net_device *ndev = to_net_dev(config->dev);
+	struct macb *bp = netdev_priv(ndev);
+
+	if (interface == PHY_INTERFACE_MODE_10GBASER)
+		return &bp->phylink_usx_pcs;
+	else if (interface == PHY_INTERFACE_MODE_SGMII)
+		return &bp->phylink_sgmii_pcs;
+	else
+		return NULL;
+}
+
 static void macb_mac_config(struct phylink_config *config, unsigned int mode,
 			    const struct phylink_link_state *state)
 {
@@ -646,18 +733,14 @@ static void macb_mac_config(struct phylink_config *config, unsigned int mode,
 		if (state->interface == PHY_INTERFACE_MODE_RMII)
 			ctrl |= MACB_BIT(RM9200_RMII);
 	} else if (macb_is_gem(bp)) {
-		ctrl &= ~(GEM_BIT(SGMIIEN) | GEM_BIT(PCSSEL));
-		ncr &= ~GEM_BIT(ENABLE_HS_MAC);
-
-		if (state->interface == PHY_INTERFACE_MODE_SGMII) {
-			ctrl |= GEM_BIT(SGMIIEN) | GEM_BIT(PCSSEL);
-		} else if (state->interface == PHY_INTERFACE_MODE_10GBASER) {
+		if (macb_mac_select_pcs(config, state->interface))
 			ctrl |= GEM_BIT(PCSSEL);
-			ncr |= GEM_BIT(ENABLE_HS_MAC);
-		} else if (bp->caps & MACB_CAPS_MIIONRGMII &&
-			   bp->phy_interface == PHY_INTERFACE_MODE_MII) {
+		else
+			ctrl &= ~GEM_BIT(PCSSEL);
+
+		if (bp->caps & MACB_CAPS_MIIONRGMII &&
+		    bp->phy_interface == PHY_INTERFACE_MODE_MII)
 			ncr |= MACB_BIT(MIIONRGMII);
-		}
 	}
 
 	/* Apply the new configuration, if any */
@@ -667,22 +750,6 @@ static void macb_mac_config(struct phylink_config *config, unsigned int mode,
 	if (old_ncr ^ ncr)
 		macb_or_gem_writel(bp, NCR, ncr);
 
-	/* Disable AN for SGMII fixed link configuration, enable otherwise.
-	 * Must be written after PCSSEL is set in NCFGR,
-	 * otherwise writes will not take effect.
-	 */
-	if (macb_is_gem(bp) && state->interface == PHY_INTERFACE_MODE_SGMII) {
-		u32 pcsctrl, old_pcsctrl;
-
-		old_pcsctrl = gem_readl(bp, PCSCNTRL);
-		if (mode == MLO_AN_FIXED)
-			pcsctrl = old_pcsctrl & ~GEM_BIT(PCSAUTONEG);
-		else
-			pcsctrl = old_pcsctrl | GEM_BIT(PCSAUTONEG);
-		if (old_pcsctrl != pcsctrl)
-			gem_writel(bp, PCSCNTRL, pcsctrl);
-	}
-
 	spin_unlock_irqrestore(&bp->lock, flags);
 }
 
@@ -735,10 +802,12 @@ static void macb_mac_link_up(struct phylink_config *config,
 	if (!(bp->caps & MACB_CAPS_MACB_IS_EMAC)) {
 		ctrl &= ~MACB_BIT(PAE);
 		if (macb_is_gem(bp)) {
-			ctrl &= ~GEM_BIT(GBE);
+			ctrl &= ~(GEM_BIT(GBE) | GEM_BIT(ENABLE_HS_MAC));
 
 			if (speed == SPEED_1000)
 				ctrl |= GEM_BIT(GBE);
+			else if (speed == SPEED_10000)
+				ctrl |= GEM_BIT(ENABLE_HS_MAC);
 		}
 
 		if (rx_pause)
@@ -776,20 +845,6 @@ static void macb_mac_link_up(struct phylink_config *config,
 	netif_tx_wake_all_queues(ndev);
 }
 
-static struct phylink_pcs *macb_mac_select_pcs(struct phylink_config *config,
-					       phy_interface_t interface)
-{
-	struct net_device *ndev = to_net_dev(config->dev);
-	struct macb *bp = netdev_priv(ndev);
-
-	if (interface == PHY_INTERFACE_MODE_10GBASER)
-		return &bp->phylink_usx_pcs;
-	else if (interface == PHY_INTERFACE_MODE_SGMII)
-		return &bp->phylink_sgmii_pcs;
-	else
-		return NULL;
-}
-
 static const struct phylink_mac_ops macb_phylink_ops = {
 	.mac_select_pcs = macb_mac_select_pcs,
 	.mac_config = macb_mac_config,
-- 
2.35.1.1320.gc452695387.dirty


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

* [net-next PATCH v6 09/10] net: macb: Support external PCSs
  2025-06-10 23:31 [net-next PATCH v6 00/10] Add PCS core support Sean Anderson
                   ` (8 preceding siblings ...)
  2025-06-10 23:35 ` Sean Anderson
@ 2025-06-10 23:36 ` Sean Anderson
  2025-06-13 14:50   ` kernel test robot
  2025-06-10 23:37 ` [net-next PATCH v6 10/10] of: property: Add device link support for PCS Sean Anderson
  10 siblings, 1 reply; 21+ messages in thread
From: Sean Anderson @ 2025-06-10 23:36 UTC (permalink / raw)
  To: netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King
  Cc: linux-kernel, Daniel Golle, Simon Horman, Kory Maincent,
	Heiner Kallweit, Lei Wei, Christian Marangi, Vineeth Karumanchi,
	Sean Anderson, Claudiu Beznea, Nicolas Ferre

This adds support for external PCSs. For example, the Xilinx UltraScale+
processor exposes its GMII interface to the FPGA fabric. This fabric may
implement PCS to convert GMII to a serial interface such as SGMII or
1000BASE-X. When present, the external PCS takes precedence over the
internal PCSs.

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

(no changes since v4)

Changes in v4:
- Convert to dev-less pcs_put

Changes in v2:
- Move update to macb_pcs_get_state to previous patch

 drivers/net/ethernet/cadence/macb.h      |  1 +
 drivers/net/ethernet/cadence/macb_main.c | 26 ++++++++++++++++++++++--
 2 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h
index c9a5c8beb2fa..9d310814f052 100644
--- a/drivers/net/ethernet/cadence/macb.h
+++ b/drivers/net/ethernet/cadence/macb.h
@@ -1291,6 +1291,7 @@ struct macb {
 	struct phylink_config	phylink_config;
 	struct phylink_pcs	phylink_usx_pcs;
 	struct phylink_pcs	phylink_sgmii_pcs;
+	struct phylink_pcs	*phylink_ext_pcs;
 
 	u32			caps;
 	unsigned int		dma_burst_length;
diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index 78433d8f3746..cf7034c92672 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -21,6 +21,7 @@
 #include <linux/netdevice.h>
 #include <linux/etherdevice.h>
 #include <linux/dma-mapping.h>
+#include <linux/pcs.h>
 #include <linux/platform_device.h>
 #include <linux/phylink.h>
 #include <linux/of.h>
@@ -707,7 +708,10 @@ static struct phylink_pcs *macb_mac_select_pcs(struct phylink_config *config,
 	struct net_device *ndev = to_net_dev(config->dev);
 	struct macb *bp = netdev_priv(ndev);
 
-	if (interface == PHY_INTERFACE_MODE_10GBASER)
+	if (bp->phylink_ext_pcs &&
+	    test_bit(interface, bp->phylink_ext_pcs->supported_interfaces))
+		return bp->phylink_ext_pcs;
+	else if (interface == PHY_INTERFACE_MODE_10GBASER)
 		return &bp->phylink_usx_pcs;
 	else if (interface == PHY_INTERFACE_MODE_SGMII)
 		return &bp->phylink_sgmii_pcs;
@@ -733,7 +737,10 @@ static void macb_mac_config(struct phylink_config *config, unsigned int mode,
 		if (state->interface == PHY_INTERFACE_MODE_RMII)
 			ctrl |= MACB_BIT(RM9200_RMII);
 	} else if (macb_is_gem(bp)) {
-		if (macb_mac_select_pcs(config, state->interface))
+		struct phylink_pcs *pcs = macb_mac_select_pcs(config,
+							      state->interface);
+
+		if (pcs && pcs != bp->phylink_ext_pcs)
 			ctrl |= GEM_BIT(PCSSEL);
 		else
 			ctrl &= ~GEM_BIT(PCSSEL);
@@ -907,6 +914,14 @@ static int macb_mii_probe(struct net_device *dev)
 	bp->phylink_sgmii_pcs.ops = &macb_phylink_pcs_ops;
 	bp->phylink_usx_pcs.ops = &macb_phylink_usx_pcs_ops;
 
+	bp->phylink_ext_pcs = pcs_get_by_fwnode_optional(&bp->pdev->dev,
+							 bp->pdev->dev.fwnode,
+							 NULL);
+	if (IS_ERR(bp->phylink_ext_pcs))
+		return dev_err_probe(&bp->pdev->dev,
+				     PTR_ERR(bp->phylink_ext_pcs),
+				     "Could not get external PCS\n");
+
 	bp->phylink_config.dev = &dev->dev;
 	bp->phylink_config.type = PHYLINK_NETDEV;
 	bp->phylink_config.mac_managed_pm = true;
@@ -924,6 +939,11 @@ static int macb_mii_probe(struct net_device *dev)
 	__set_bit(PHY_INTERFACE_MODE_RMII,
 		  bp->phylink_config.supported_interfaces);
 
+	if (bp->phylink_ext_pcs)
+		phy_interface_or(bp->phylink_config.supported_interfaces,
+				 bp->phylink_config.supported_interfaces,
+				 bp->phylink_ext_pcs->supported_interfaces);
+
 	/* Determine what modes are supported */
 	if (macb_is_gem(bp) && (bp->caps & MACB_CAPS_GIGABIT_MODE_AVAILABLE)) {
 		bp->phylink_config.mac_capabilities |= MAC_1000FD;
@@ -950,6 +970,7 @@ static int macb_mii_probe(struct net_device *dev)
 	if (IS_ERR(bp->phylink)) {
 		netdev_err(dev, "Could not create a phylink instance (%ld)\n",
 			   PTR_ERR(bp->phylink));
+		pcs_put(bp->phylink_ext_pcs);
 		return PTR_ERR(bp->phylink);
 	}
 
@@ -5459,6 +5480,7 @@ static void macb_remove(struct platform_device *pdev)
 					  bp->rx_clk, bp->tsu_clk);
 			pm_runtime_set_suspended(&pdev->dev);
 		}
+		pcs_put(bp->phylink_ext_pcs);
 		phylink_destroy(bp->phylink);
 		free_netdev(dev);
 	}
-- 
2.35.1.1320.gc452695387.dirty


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

* [net-next PATCH v6 10/10] of: property: Add device link support for PCS
  2025-06-10 23:31 [net-next PATCH v6 00/10] Add PCS core support Sean Anderson
                   ` (9 preceding siblings ...)
  2025-06-10 23:36 ` [net-next PATCH v6 09/10] net: macb: Support external PCSs Sean Anderson
@ 2025-06-10 23:37 ` Sean Anderson
  10 siblings, 0 replies; 21+ messages in thread
From: Sean Anderson @ 2025-06-10 23:37 UTC (permalink / raw)
  To: netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King
  Cc: linux-kernel, Daniel Golle, Simon Horman, Kory Maincent,
	Heiner Kallweit, Lei Wei, Christian Marangi, Vineeth Karumanchi,
	Sean Anderson, Rob Herring, Saravana Kannan, Rob Herring,
	devicetree

This adds device link support for PCS devices, providing
better probe ordering.

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
Reviewed-by: Saravana Kannan <saravanak@google.com>
---

(no changes since v2)

Changes in v2:
- Reorder pcs_handle to come before suffix props

 drivers/of/property.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/of/property.c b/drivers/of/property.c
index c1feb631e383..1aa28bfadb12 100644
--- a/drivers/of/property.c
+++ b/drivers/of/property.c
@@ -1377,6 +1377,7 @@ DEFINE_SIMPLE_PROP(post_init_providers, "post-init-providers", NULL)
 DEFINE_SIMPLE_PROP(access_controllers, "access-controllers", "#access-controller-cells")
 DEFINE_SIMPLE_PROP(pses, "pses", "#pse-cells")
 DEFINE_SIMPLE_PROP(power_supplies, "power-supplies", NULL)
+DEFINE_SIMPLE_PROP(pcs_handle, "pcs-handle", NULL)
 DEFINE_SUFFIX_PROP(regulators, "-supply", NULL)
 DEFINE_SUFFIX_PROP(gpio, "-gpio", "#gpio-cells")
 
@@ -1528,6 +1529,7 @@ static const struct supplier_bindings of_supplier_bindings[] = {
 	{ .parse_prop = parse_interrupts, },
 	{ .parse_prop = parse_interrupt_map, },
 	{ .parse_prop = parse_access_controllers, },
+	{ .parse_prop = parse_pcs_handle, },
 	{ .parse_prop = parse_regulators, },
 	{ .parse_prop = parse_gpio, },
 	{ .parse_prop = parse_gpios, },
-- 
2.35.1.1320.gc452695387.dirty


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

* Re: [net-next PATCH v6 03/10] net: pcs: Add subsystem
  2025-06-10 23:31 ` [net-next PATCH v6 03/10] net: pcs: Add subsystem Sean Anderson
@ 2025-06-11  0:24   ` Randy Dunlap
  2025-06-12 15:38     ` Sean Anderson
  0 siblings, 1 reply; 21+ messages in thread
From: Randy Dunlap @ 2025-06-11  0:24 UTC (permalink / raw)
  To: Sean Anderson, netdev, Andrew Lunn, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Russell King
  Cc: Vineeth Karumanchi, Heiner Kallweit, linux-kernel, Kory Maincent,
	Daniel Golle, Simon Horman, Christian Marangi, Lei Wei,
	Jonathan Corbet, linux-doc

Hi,


> diff --git a/Documentation/networking/pcs.rst b/Documentation/networking/pcs.rst
> new file mode 100644
> index 000000000000..4b41ba884160
> --- /dev/null
> +++ b/Documentation/networking/pcs.rst
> @@ -0,0 +1,102 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +=============
> +PCS Subsystem
> +=============
> +
> +The PCS (Physical Coding Sublayer) subsystem handles the registration and lookup
> +of PCS devices. These devices contain the upper sublayers of the Ethernet
> +physical layer, generally handling framing, scrambling, and encoding tasks. PCS
> +devices may also include PMA (Physical Medium Attachment) components. PCS
> +devices transfer data between the Link-layer MAC device, and the rest of the
> +physical layer, typically via a serdes. The output of the serdes may be
> +connected more-or-less directly to the medium when using fiber-optic or
> +backplane connections (1000BASE-SX, 1000BASE-KX, etc). It may also communicate
> +with a separate PHY (such as over SGMII) which handles the connection to the
> +medium (such as 1000BASE-T).
> +
> +Looking up PCS Devices
> +----------------------
> +
> +There are generally two ways to look up a PCS device. If the PCS device is
> +internal to a larger device (such as a MAC or switch), and it does not share an
> +implementation with an existing PCS, then it does not need to be registered with
> +the PCS subsystem. Instead, you can populate a :c:type:`phylink_pcs`
> +in your probe function. Otherwise, you must look up the PCS.
> +
> +If your device has a :c:type:`fwnode_handle`, you can add a PCS using the
> +``pcs-handle`` property::
> +
> +    ethernet-controller {
> +        // ...
> +        pcs-handle = <&pcs>;
> +        pcs-handle-names = "internal";
> +    };
> +
> +Then, during your probe function, you can get the PCS using :c:func:`pcs_get`::

It's preferable to use                               PCS using pcs_get()::
instead of the :c:func: notation to make the .rst file more human-readable.
They produce the same generated output.

> +
> +    mac->pcs = pcs_get(dev, "internal");
> +    if (IS_ERR(mac->pcs)) {
> +        err = PTR_ERR(mac->pcs);
> +        return dev_err_probe(dev, "Could not get PCS\n");
> +    }
> +
> +If your device doesn't have a :c:type:`fwnode_handle`, you can get the PCS
> +based on the providing device using :c:func:`pcs_get_by_dev`. Typically, you

ditto.

> +will create the device and bind your PCS driver to it before calling this
> +function. This allows reuse of an existing PCS driver.
> +
> +Once you are done using the PCS, you must call :c:func:`pcs_put`.

ditto.

> +
> +Using PCS Devices
> +-----------------
> +
> +To select the PCS from a MAC driver, implement the ``mac_select_pcs`` callback
> +of :c:type:`phylink_mac_ops`. In this example, the PCS is selected for SGMII
> +and 1000BASE-X, and deselected for other interfaces::
> +
> +    static struct phylink_pcs *mac_select_pcs(struct phylink_config *config,
> +                                              phy_interface_t iface)
> +    {
> +        struct mac *mac = config_to_mac(config);
> +
> +        switch (iface) {
> +        case PHY_INTERFACE_MODE_SGMII:
> +        case PHY_INTERFACE_MODE_1000BASEX:
> +            return mac->pcs;
> +        default:
> +            return NULL;
> +        }
> +    }
> +
> +To do the same from a DSA driver, implement the ``phylink_mac_select_pcs``
> +callback of :c:type:`dsa_switch_ops`.
> +
> +Writing PCS Drivers
> +-------------------
> +
> +To write a PCS driver, first implement :c:type:`phylink_pcs_ops`. Then,
> +register your PCS in your probe function using :c:func:`pcs_register`. If you

ditto

> +need to provide multiple PCSs for the same device, then you can pass specific
> +firmware nodes using :c:macro:`pcs_register_full`.
> +
> +You must call :c:func:`pcs_unregister` from your remove function. You can avoid

ditto.

> +this step by registering with :c:func:`devm_pcs_unregister`.
> +
> +API Reference
> +-------------
> +
> +.. kernel-doc:: include/linux/phylink.h
> +   :identifiers: phylink_pcs phylink_pcs_ops pcs_validate pcs_inband_caps
> +      pcs_enable pcs_disable pcs_pre_config pcs_post_config pcs_get_state
> +      pcs_config pcs_an_restart pcs_link_up pcs_disable_eee pcs_enable_eee
> +      pcs_pre_init
> +
> +.. kernel-doc:: include/linux/pcs.h
> +   :internal:
> +
> +.. kernel-doc:: drivers/net/pcs/core.c
> +   :export:
> +
> +.. kernel-doc:: drivers/net/pcs/core.c
> +   :internal:

Thanks.

-- 
~Randy


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

* Re: [net-next PATCH v6 06/10] net: pcs: Add Xilinx PCS driver
  2025-06-10 23:31 ` [net-next PATCH v6 06/10] net: pcs: Add Xilinx PCS driver Sean Anderson
@ 2025-06-11  5:11   ` Maxime Chevallier
  2025-06-12 15:34     ` Sean Anderson
  2025-06-12  0:14   ` kernel test robot
  1 sibling, 1 reply; 21+ messages in thread
From: Maxime Chevallier @ 2025-06-11  5:11 UTC (permalink / raw)
  To: Sean Anderson
  Cc: netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King, Vineeth Karumanchi,
	Heiner Kallweit, linux-kernel, Kory Maincent, Daniel Golle,
	Simon Horman, Christian Marangi, Lei Wei, Michal Simek,
	Radhey Shyam Pandey, Robert Hancock, linux-arm-kernel

Hi Sean,

I only 

On Tue, 10 Jun 2025 19:31:30 -0400
Sean Anderson <sean.anderson@linux.dev> wrote:

> This adds support for the Xilinx 1G/2.5G Ethernet PCS/PMA or SGMII device.
> This is a soft device which converts between GMII and either SGMII,
> 1000Base-X, or 2500Base-X. If configured correctly, it can also switch
> between SGMII and 1000BASE-X at runtime. Thoretically this is also possible
> for 2500Base-X, but that requires reconfiguring the serdes. The exact
> capabilities depend on synthesis parameters, so they are read from the
> devicetree.
> 
> This device has a c22-compliant PHY interface, so for the most part we can
> just use the phylink helpers. This device supports an interrupt which is
> triggered on autonegotiation completion. I'm not sure how useful this is,
> since we can never detect a link down (in the PCS).
> 
> This device supports sharing some logic between different implementations
> of the device. In this case, one device contains the "shared logic" and the
> clocks are connected to other devices. To coordinate this, one device
> registers a clock that the other devices can request.  The clock is enabled
> in the probe function by releasing the device from reset. There are no othe
> software controls, so the clock ops are empty.
> 
> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
> ---
> 
> Changes in v6:
> - Move axienet_pcs_fixup to AXI Ethernet commit
> - Use an empty statement for next label
> 
> Changes in v5:
> - Export get_phy_c22_id when it is used
> - Expose bind attributes, since there is no issue in doing so
> - Use MDIO_BUS instead of MDIO_DEVICE
> 
> Changes in v4:
> - Re-add documentation for axienet_xilinx_pcs_get that was accidentally
>   removed
> 
> Changes in v3:
> - Adjust axienet_xilinx_pcs_get for changes to pcs_find_fwnode API
> - Call devm_pcs_register instead of devm_pcs_register_provider
> 
> Changes in v2:
> - Add support for #pcs-cells
> - Change compatible to just xlnx,pcs
> - Drop PCS_ALTERA_TSE which was accidentally added while rebasing
> - Rework xilinx_pcs_validate to just clear out half-duplex modes instead
>   of constraining modes based on the interface.
> 
>  MAINTAINERS                  |   6 +
>  drivers/net/pcs/Kconfig      |  22 ++
>  drivers/net/pcs/Makefile     |   2 +
>  drivers/net/pcs/pcs-xilinx.c | 427 +++++++++++++++++++++++++++++++++++
>  drivers/net/phy/phy_device.c |   3 +-
>  include/linux/phy.h          |   1 +
>  6 files changed, 460 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/net/pcs/pcs-xilinx.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 0ac6ba5c40cb..496513837921 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -27060,6 +27060,12 @@ L:	netdev@vger.kernel.org
>  S:	Orphan
>  F:	drivers/net/ethernet/xilinx/ll_temac*
>  
> +XILINX PCS DRIVER
> +M:	Sean Anderson <sean.anderson@linux.dev>
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/net/xilinx,pcs.yaml
> +F:	drivers/net/pcs/pcs-xilinx.c
> +
>  XILINX PWM DRIVER
>  M:	Sean Anderson <sean.anderson@seco.com>
>  S:	Maintained
> diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig
> index f42839a0c332..e0223914362b 100644
> --- a/drivers/net/pcs/Kconfig
> +++ b/drivers/net/pcs/Kconfig
> @@ -52,4 +52,26 @@ 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.
>  
> +config PCS_XILINX
> +	tristate "Xilinx PCS driver"
> +	default XILINX_AXI_EMAC
> +	select COMMON_CLK
> +	select GPIOLIB
> +	select MDIO_BUS
> +	select OF
> +	select PCS
> +	select PHYLINK
> +	help
> +	  PCS driver for the Xilinx 1G/2.5G Ethernet PCS/PMA or SGMII device.
> +	  This device can either act as a PCS+PMA for 1000BASE-X or 2500BASE-X,
> +	  or as a GMII-to-SGMII bridge. It can also switch between 1000BASE-X
> +	  and SGMII dynamically if configured correctly when synthesized.
> +	  Typical applications use this device on an FPGA connected to a GEM or
> +	  TEMAC on the GMII side. The other side is typically connected to
> +	  on-device gigabit transceivers, off-device SERDES devices using TBI,
> +	  or LVDS IO resources directly.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pcs-xilinx.
> +
>  endmenu
> diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile
> index 35e3324fc26e..347afd91f034 100644
> --- a/drivers/net/pcs/Makefile
> +++ b/drivers/net/pcs/Makefile
> @@ -10,3 +10,5 @@ obj-$(CONFIG_PCS_XPCS)		+= pcs_xpcs.o
>  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_ALTERA_TSE)	+= pcs-altera-tse.o

There's something strange going-on here, as pcs-altera-tse was removed
in v6.4 :)

> +obj-$(CONFIG_PCS_XILINX)	+= pcs-xilinx.o

Maxime

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

* Re: [net-next PATCH v6 07/10] net: axienet: Convert to use PCS subsystem
  2025-06-10 23:31 ` [net-next PATCH v6 07/10] net: axienet: Convert to use PCS subsystem Sean Anderson
@ 2025-06-11 20:56   ` kernel test robot
  2025-06-11 22:19   ` kernel test robot
  1 sibling, 0 replies; 21+ messages in thread
From: kernel test robot @ 2025-06-11 20:56 UTC (permalink / raw)
  To: Sean Anderson, netdev, Andrew Lunn, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Russell King
  Cc: Paul Gazzillo, Necip Fazil Yildiran, oe-kbuild-all,
	Vineeth Karumanchi, Heiner Kallweit, linux-kernel, Kory Maincent,
	Daniel Golle, Simon Horman, Christian Marangi, Lei Wei,
	Sean Anderson, Michal Simek, Radhey Shyam Pandey, Robert Hancock,
	linux-arm-kernel

Hi Sean,

kernel test robot noticed the following build warnings:

[auto build test WARNING on net-next/main]

url:    https://github.com/intel-lab-lkp/linux/commits/Sean-Anderson/dt-bindings-net-Add-Xilinx-PCS/20250611-143544
base:   net-next/main
patch link:    https://lore.kernel.org/r/20250610233134.3588011-8-sean.anderson%40linux.dev
patch subject: [net-next PATCH v6 07/10] net: axienet: Convert to use PCS subsystem
config: alpha-kismet-CONFIG_OF_DYNAMIC-CONFIG_XILINX_AXI_EMAC-0-0 (https://download.01.org/0day-ci/archive/20250612/202506120442.emFFyhZj-lkp@intel.com/config)
reproduce: (https://download.01.org/0day-ci/archive/20250612/202506120442.emFFyhZj-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202506120442.emFFyhZj-lkp@intel.com/

kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for OF_DYNAMIC when selected by XILINX_AXI_EMAC
   WARNING: unmet direct dependencies detected for OF_DYNAMIC
     Depends on [n]: OF [=n]
     Selected by [y]:
     - XILINX_AXI_EMAC [=y] && NETDEVICES [=y] && ETHERNET [=y] && NET_VENDOR_XILINX [=y] && HAS_IOMEM [=y] && XILINX_DMA [=y]

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [net-next PATCH v6 07/10] net: axienet: Convert to use PCS subsystem
  2025-06-10 23:31 ` [net-next PATCH v6 07/10] net: axienet: Convert to use PCS subsystem Sean Anderson
  2025-06-11 20:56   ` kernel test robot
@ 2025-06-11 22:19   ` kernel test robot
  1 sibling, 0 replies; 21+ messages in thread
From: kernel test robot @ 2025-06-11 22:19 UTC (permalink / raw)
  To: Sean Anderson, netdev, Andrew Lunn, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Russell King
  Cc: Paul Gazzillo, Necip Fazil Yildiran, oe-kbuild-all,
	Vineeth Karumanchi, Heiner Kallweit, linux-kernel, Kory Maincent,
	Daniel Golle, Simon Horman, Christian Marangi, Lei Wei,
	Sean Anderson, Michal Simek, Radhey Shyam Pandey, Robert Hancock,
	linux-arm-kernel

Hi Sean,

kernel test robot noticed the following build warnings:

[auto build test WARNING on net-next/main]

url:    https://github.com/intel-lab-lkp/linux/commits/Sean-Anderson/dt-bindings-net-Add-Xilinx-PCS/20250611-143544
base:   net-next/main
patch link:    https://lore.kernel.org/r/20250610233134.3588011-8-sean.anderson%40linux.dev
patch subject: [net-next PATCH v6 07/10] net: axienet: Convert to use PCS subsystem
config: alpha-kismet-CONFIG_OF_DYNAMIC-CONFIG_XILINX_AXI_EMAC_PCS_COMPAT-0-0 (https://download.01.org/0day-ci/archive/20250612/202506120616.NvIbQEgS-lkp@intel.com/config)
reproduce: (https://download.01.org/0day-ci/archive/20250612/202506120616.NvIbQEgS-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202506120616.NvIbQEgS-lkp@intel.com/

kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for OF_DYNAMIC when selected by XILINX_AXI_EMAC_PCS_COMPAT
   WARNING: unmet direct dependencies detected for OF_DYNAMIC
     Depends on [n]: OF [=n]
     Selected by [y]:
     - XILINX_AXI_EMAC [=y] && NETDEVICES [=y] && ETHERNET [=y] && NET_VENDOR_XILINX [=y] && HAS_IOMEM [=y] && XILINX_DMA [=y]
     - XILINX_AXI_EMAC_PCS_COMPAT [=y] && NETDEVICES [=y] && ETHERNET [=y] && NET_VENDOR_XILINX [=y] && XILINX_AXI_EMAC [=y]

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [net-next PATCH v6 06/10] net: pcs: Add Xilinx PCS driver
  2025-06-10 23:31 ` [net-next PATCH v6 06/10] net: pcs: Add Xilinx PCS driver Sean Anderson
  2025-06-11  5:11   ` Maxime Chevallier
@ 2025-06-12  0:14   ` kernel test robot
  1 sibling, 0 replies; 21+ messages in thread
From: kernel test robot @ 2025-06-12  0:14 UTC (permalink / raw)
  To: Sean Anderson, netdev, Andrew Lunn, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Russell King
  Cc: Paul Gazzillo, Necip Fazil Yildiran, oe-kbuild-all,
	Vineeth Karumanchi, Heiner Kallweit, linux-kernel, Kory Maincent,
	Daniel Golle, Simon Horman, Christian Marangi, Lei Wei,
	Sean Anderson, Michal Simek, Radhey Shyam Pandey, Robert Hancock,
	linux-arm-kernel

Hi Sean,

kernel test robot noticed the following build warnings:

[auto build test WARNING on net-next/main]

url:    https://github.com/intel-lab-lkp/linux/commits/Sean-Anderson/dt-bindings-net-Add-Xilinx-PCS/20250611-143544
base:   net-next/main
patch link:    https://lore.kernel.org/r/20250610233134.3588011-7-sean.anderson%40linux.dev
patch subject: [net-next PATCH v6 06/10] net: pcs: Add Xilinx PCS driver
config: sh-kismet-CONFIG_COMMON_CLK-CONFIG_PCS_XILINX-0-0 (https://download.01.org/0day-ci/archive/20250612/202506120722.o7HB1e60-lkp@intel.com/config)
reproduce: (https://download.01.org/0day-ci/archive/20250612/202506120722.o7HB1e60-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202506120722.o7HB1e60-lkp@intel.com/

kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for COMMON_CLK when selected by PCS_XILINX
   WARNING: unmet direct dependencies detected for COMMON_CLK
     Depends on [n]: !HAVE_LEGACY_CLK [=y]
     Selected by [y]:
     - PCS_XILINX [=y] && NETDEVICES [=y]

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [net-next PATCH v6 06/10] net: pcs: Add Xilinx PCS driver
  2025-06-11  5:11   ` Maxime Chevallier
@ 2025-06-12 15:34     ` Sean Anderson
  0 siblings, 0 replies; 21+ messages in thread
From: Sean Anderson @ 2025-06-12 15:34 UTC (permalink / raw)
  To: Maxime Chevallier
  Cc: netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King, Vineeth Karumanchi,
	Heiner Kallweit, linux-kernel, Kory Maincent, Daniel Golle,
	Simon Horman, Christian Marangi, Lei Wei, Michal Simek,
	Radhey Shyam Pandey, Robert Hancock, linux-arm-kernel

On 6/11/25 01:11, Maxime Chevallier wrote:
> Hi Sean,
> 
> I only 
> 
> On Tue, 10 Jun 2025 19:31:30 -0400
> Sean Anderson <sean.anderson@linux.dev> wrote:
> 
>> This adds support for the Xilinx 1G/2.5G Ethernet PCS/PMA or SGMII device.
>> This is a soft device which converts between GMII and either SGMII,
>> 1000Base-X, or 2500Base-X. If configured correctly, it can also switch
>> between SGMII and 1000BASE-X at runtime. Thoretically this is also possible
>> for 2500Base-X, but that requires reconfiguring the serdes. The exact
>> capabilities depend on synthesis parameters, so they are read from the
>> devicetree.
>> 
>> This device has a c22-compliant PHY interface, so for the most part we can
>> just use the phylink helpers. This device supports an interrupt which is
>> triggered on autonegotiation completion. I'm not sure how useful this is,
>> since we can never detect a link down (in the PCS).
>> 
>> This device supports sharing some logic between different implementations
>> of the device. In this case, one device contains the "shared logic" and the
>> clocks are connected to other devices. To coordinate this, one device
>> registers a clock that the other devices can request.  The clock is enabled
>> in the probe function by releasing the device from reset. There are no othe
>> software controls, so the clock ops are empty.
>> 
>> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
>> ---
>> 
>> Changes in v6:
>> - Move axienet_pcs_fixup to AXI Ethernet commit
>> - Use an empty statement for next label
>> 
>> Changes in v5:
>> - Export get_phy_c22_id when it is used
>> - Expose bind attributes, since there is no issue in doing so
>> - Use MDIO_BUS instead of MDIO_DEVICE
>> 
>> Changes in v4:
>> - Re-add documentation for axienet_xilinx_pcs_get that was accidentally
>>   removed
>> 
>> Changes in v3:
>> - Adjust axienet_xilinx_pcs_get for changes to pcs_find_fwnode API
>> - Call devm_pcs_register instead of devm_pcs_register_provider
>> 
>> Changes in v2:
>> - Add support for #pcs-cells
>> - Change compatible to just xlnx,pcs
>> - Drop PCS_ALTERA_TSE which was accidentally added while rebasing
>> - Rework xilinx_pcs_validate to just clear out half-duplex modes instead
>>   of constraining modes based on the interface.
>> 
>>  MAINTAINERS                  |   6 +
>>  drivers/net/pcs/Kconfig      |  22 ++
>>  drivers/net/pcs/Makefile     |   2 +
>>  drivers/net/pcs/pcs-xilinx.c | 427 +++++++++++++++++++++++++++++++++++
>>  drivers/net/phy/phy_device.c |   3 +-
>>  include/linux/phy.h          |   1 +
>>  6 files changed, 460 insertions(+), 1 deletion(-)
>>  create mode 100644 drivers/net/pcs/pcs-xilinx.c
>> 
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 0ac6ba5c40cb..496513837921 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -27060,6 +27060,12 @@ L:	netdev@vger.kernel.org
>>  S:	Orphan
>>  F:	drivers/net/ethernet/xilinx/ll_temac*
>>  
>> +XILINX PCS DRIVER
>> +M:	Sean Anderson <sean.anderson@linux.dev>
>> +S:	Maintained
>> +F:	Documentation/devicetree/bindings/net/xilinx,pcs.yaml
>> +F:	drivers/net/pcs/pcs-xilinx.c
>> +
>>  XILINX PWM DRIVER
>>  M:	Sean Anderson <sean.anderson@seco.com>
>>  S:	Maintained
>> diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig
>> index f42839a0c332..e0223914362b 100644
>> --- a/drivers/net/pcs/Kconfig
>> +++ b/drivers/net/pcs/Kconfig
>> @@ -52,4 +52,26 @@ 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.
>>  
>> +config PCS_XILINX
>> +	tristate "Xilinx PCS driver"
>> +	default XILINX_AXI_EMAC
>> +	select COMMON_CLK
>> +	select GPIOLIB
>> +	select MDIO_BUS
>> +	select OF
>> +	select PCS
>> +	select PHYLINK
>> +	help
>> +	  PCS driver for the Xilinx 1G/2.5G Ethernet PCS/PMA or SGMII device.
>> +	  This device can either act as a PCS+PMA for 1000BASE-X or 2500BASE-X,
>> +	  or as a GMII-to-SGMII bridge. It can also switch between 1000BASE-X
>> +	  and SGMII dynamically if configured correctly when synthesized.
>> +	  Typical applications use this device on an FPGA connected to a GEM or
>> +	  TEMAC on the GMII side. The other side is typically connected to
>> +	  on-device gigabit transceivers, off-device SERDES devices using TBI,
>> +	  or LVDS IO resources directly.
>> +
>> +	  To compile this driver as a module, choose M here: the module
>> +	  will be called pcs-xilinx.
>> +
>>  endmenu
>> diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile
>> index 35e3324fc26e..347afd91f034 100644
>> --- a/drivers/net/pcs/Makefile
>> +++ b/drivers/net/pcs/Makefile
>> @@ -10,3 +10,5 @@ obj-$(CONFIG_PCS_XPCS)		+= pcs_xpcs.o
>>  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_ALTERA_TSE)	+= pcs-altera-tse.o
> 
> There's something strange going-on here, as pcs-altera-tse was removed
> in v6.4 :)

Ah, well as it happens I have been working on this series since at least
5.10, so I guess it is left over. I will remove it for v7.

--Sean


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

* Re: [net-next PATCH v6 03/10] net: pcs: Add subsystem
  2025-06-11  0:24   ` Randy Dunlap
@ 2025-06-12 15:38     ` Sean Anderson
  0 siblings, 0 replies; 21+ messages in thread
From: Sean Anderson @ 2025-06-12 15:38 UTC (permalink / raw)
  To: Randy Dunlap, netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Russell King
  Cc: Vineeth Karumanchi, Heiner Kallweit, linux-kernel, Kory Maincent,
	Daniel Golle, Simon Horman, Christian Marangi, Lei Wei,
	Jonathan Corbet, linux-doc

On 6/10/25 20:24, Randy Dunlap wrote:
> Hi,
> 
> 
>> diff --git a/Documentation/networking/pcs.rst b/Documentation/networking/pcs.rst
>> new file mode 100644
>> index 000000000000..4b41ba884160
>> --- /dev/null
>> +++ b/Documentation/networking/pcs.rst
>> @@ -0,0 +1,102 @@
>> +.. SPDX-License-Identifier: GPL-2.0
>> +
>> +=============
>> +PCS Subsystem
>> +=============
>> +
>> +The PCS (Physical Coding Sublayer) subsystem handles the registration and lookup
>> +of PCS devices. These devices contain the upper sublayers of the Ethernet
>> +physical layer, generally handling framing, scrambling, and encoding tasks. PCS
>> +devices may also include PMA (Physical Medium Attachment) components. PCS
>> +devices transfer data between the Link-layer MAC device, and the rest of the
>> +physical layer, typically via a serdes. The output of the serdes may be
>> +connected more-or-less directly to the medium when using fiber-optic or
>> +backplane connections (1000BASE-SX, 1000BASE-KX, etc). It may also communicate
>> +with a separate PHY (such as over SGMII) which handles the connection to the
>> +medium (such as 1000BASE-T).
>> +
>> +Looking up PCS Devices
>> +----------------------
>> +
>> +There are generally two ways to look up a PCS device. If the PCS device is
>> +internal to a larger device (such as a MAC or switch), and it does not share an
>> +implementation with an existing PCS, then it does not need to be registered with
>> +the PCS subsystem. Instead, you can populate a :c:type:`phylink_pcs`
>> +in your probe function. Otherwise, you must look up the PCS.
>> +
>> +If your device has a :c:type:`fwnode_handle`, you can add a PCS using the
>> +``pcs-handle`` property::
>> +
>> +    ethernet-controller {
>> +        // ...
>> +        pcs-handle = <&pcs>;
>> +        pcs-handle-names = "internal";
>> +    };
>> +
>> +Then, during your probe function, you can get the PCS using :c:func:`pcs_get`::
> 
> It's preferable to use                               PCS using pcs_get()::
> instead of the :c:func: notation to make the .rst file more human-readable.
> They produce the same generated output.

If you find this syntax useful, then you should update
Documentation/doc-guide/{kernel-doc,sphinx}.rst. I did not use it
because it I did not know about it because it is not documented.

--Sean

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

* Re: [net-next PATCH v6 05/10] net: pcs: lynx: Convert to an MDIO driver
  2025-06-10 23:31 ` [net-next PATCH v6 05/10] net: pcs: lynx: Convert to an MDIO driver Sean Anderson
@ 2025-06-13 11:27   ` kernel test robot
  0 siblings, 0 replies; 21+ messages in thread
From: kernel test robot @ 2025-06-13 11:27 UTC (permalink / raw)
  To: Sean Anderson, netdev, Andrew Lunn, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Russell King
  Cc: oe-kbuild-all, Vineeth Karumanchi, Heiner Kallweit, linux-kernel,
	Kory Maincent, Daniel Golle, Simon Horman, Christian Marangi,
	Lei Wei, Sean Anderson, Ioana Ciornei, Vladimir Oltean, imx,
	linux-stm32

Hi Sean,

kernel test robot noticed the following build errors:

[auto build test ERROR on net-next/main]

url:    https://github.com/intel-lab-lkp/linux/commits/Sean-Anderson/dt-bindings-net-Add-Xilinx-PCS/20250611-143544
base:   net-next/main
patch link:    https://lore.kernel.org/r/20250610233134.3588011-6-sean.anderson%40linux.dev
patch subject: [net-next PATCH v6 05/10] net: pcs: lynx: Convert to an MDIO driver
config: parisc-randconfig-002-20250613 (https://download.01.org/0day-ci/archive/20250613/202506131905.gKLnNnsQ-lkp@intel.com/config)
compiler: hppa-linux-gcc (GCC) 13.3.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250613/202506131905.gKLnNnsQ-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202506131905.gKLnNnsQ-lkp@intel.com/

All error/warnings (new ones prefixed by >>):

   In file included from drivers/regulator/mt6358-regulator.c:8:
   include/linux/of.h: In function 'of_changeset_attach_node':
>> include/linux/of.h:1616:41: error: 'OF_RECONFIG_ATTACH_NODE' undeclared (first use in this function); did you mean 'OF_RECONFIG_NO_CHANGE'?
    1616 |         return of_changeset_action(ocs, OF_RECONFIG_ATTACH_NODE, np, NULL);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~
         |                                         OF_RECONFIG_NO_CHANGE
   include/linux/of.h:1616:41: note: each undeclared identifier is reported only once for each function it appears in
   include/linux/of.h: In function 'of_changeset_detach_node':
>> include/linux/of.h:1622:41: error: 'OF_RECONFIG_DETACH_NODE' undeclared (first use in this function); did you mean 'OF_RECONFIG_NO_CHANGE'?
    1622 |         return of_changeset_action(ocs, OF_RECONFIG_DETACH_NODE, np, NULL);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~
         |                                         OF_RECONFIG_NO_CHANGE
   include/linux/of.h: In function 'of_changeset_add_property':
>> include/linux/of.h:1628:41: error: 'OF_RECONFIG_ADD_PROPERTY' undeclared (first use in this function)
    1628 |         return of_changeset_action(ocs, OF_RECONFIG_ADD_PROPERTY, np, prop);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/of.h: In function 'of_changeset_remove_property':
>> include/linux/of.h:1634:41: error: 'OF_RECONFIG_REMOVE_PROPERTY' undeclared (first use in this function)
    1634 |         return of_changeset_action(ocs, OF_RECONFIG_REMOVE_PROPERTY, np, prop);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/of.h: In function 'of_changeset_update_property':
>> include/linux/of.h:1640:41: error: 'OF_RECONFIG_UPDATE_PROPERTY' undeclared (first use in this function)
    1640 |         return of_changeset_action(ocs, OF_RECONFIG_UPDATE_PROPERTY, np, prop);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~
--
   In file included from drivers/regulator/pbias-regulator.c:27:
   include/linux/of.h: In function 'of_changeset_attach_node':
>> include/linux/of.h:1616:41: error: 'OF_RECONFIG_ATTACH_NODE' undeclared (first use in this function); did you mean 'OF_RECONFIG_NO_CHANGE'?
    1616 |         return of_changeset_action(ocs, OF_RECONFIG_ATTACH_NODE, np, NULL);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~
         |                                         OF_RECONFIG_NO_CHANGE
   include/linux/of.h:1616:41: note: each undeclared identifier is reported only once for each function it appears in
   include/linux/of.h: In function 'of_changeset_detach_node':
>> include/linux/of.h:1622:41: error: 'OF_RECONFIG_DETACH_NODE' undeclared (first use in this function); did you mean 'OF_RECONFIG_NO_CHANGE'?
    1622 |         return of_changeset_action(ocs, OF_RECONFIG_DETACH_NODE, np, NULL);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~
         |                                         OF_RECONFIG_NO_CHANGE
   include/linux/of.h: In function 'of_changeset_add_property':
>> include/linux/of.h:1628:41: error: 'OF_RECONFIG_ADD_PROPERTY' undeclared (first use in this function)
    1628 |         return of_changeset_action(ocs, OF_RECONFIG_ADD_PROPERTY, np, prop);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/of.h: In function 'of_changeset_remove_property':
>> include/linux/of.h:1634:41: error: 'OF_RECONFIG_REMOVE_PROPERTY' undeclared (first use in this function)
    1634 |         return of_changeset_action(ocs, OF_RECONFIG_REMOVE_PROPERTY, np, prop);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/of.h: In function 'of_changeset_update_property':
>> include/linux/of.h:1640:41: error: 'OF_RECONFIG_UPDATE_PROPERTY' undeclared (first use in this function)
    1640 |         return of_changeset_action(ocs, OF_RECONFIG_UPDATE_PROPERTY, np, prop);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/regulator/pbias-regulator.c: At top level:
   drivers/regulator/pbias-regulator.c:136:34: warning: 'pbias_of_match' defined but not used [-Wunused-const-variable=]
     136 | static const struct of_device_id pbias_of_match[] = {
         |                                  ^~~~~~~~~~~~~~
--
   In file included from drivers/regulator/twl-regulator.c:14:
   include/linux/of.h: In function 'of_changeset_attach_node':
>> include/linux/of.h:1616:41: error: 'OF_RECONFIG_ATTACH_NODE' undeclared (first use in this function); did you mean 'OF_RECONFIG_NO_CHANGE'?
    1616 |         return of_changeset_action(ocs, OF_RECONFIG_ATTACH_NODE, np, NULL);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~
         |                                         OF_RECONFIG_NO_CHANGE
   include/linux/of.h:1616:41: note: each undeclared identifier is reported only once for each function it appears in
   include/linux/of.h: In function 'of_changeset_detach_node':
>> include/linux/of.h:1622:41: error: 'OF_RECONFIG_DETACH_NODE' undeclared (first use in this function); did you mean 'OF_RECONFIG_NO_CHANGE'?
    1622 |         return of_changeset_action(ocs, OF_RECONFIG_DETACH_NODE, np, NULL);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~
         |                                         OF_RECONFIG_NO_CHANGE
   include/linux/of.h: In function 'of_changeset_add_property':
>> include/linux/of.h:1628:41: error: 'OF_RECONFIG_ADD_PROPERTY' undeclared (first use in this function)
    1628 |         return of_changeset_action(ocs, OF_RECONFIG_ADD_PROPERTY, np, prop);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/of.h: In function 'of_changeset_remove_property':
>> include/linux/of.h:1634:41: error: 'OF_RECONFIG_REMOVE_PROPERTY' undeclared (first use in this function)
    1634 |         return of_changeset_action(ocs, OF_RECONFIG_REMOVE_PROPERTY, np, prop);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/of.h: In function 'of_changeset_update_property':
>> include/linux/of.h:1640:41: error: 'OF_RECONFIG_UPDATE_PROPERTY' undeclared (first use in this function)
    1640 |         return of_changeset_action(ocs, OF_RECONFIG_UPDATE_PROPERTY, np, prop);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/regulator/twl-regulator.c: At top level:
   drivers/regulator/twl-regulator.c:552:34: warning: 'twl_of_match' defined but not used [-Wunused-const-variable=]
     552 | static const struct of_device_id twl_of_match[] = {
         |                                  ^~~~~~~~~~~~
--
   In file included from drivers/regulator/twl6030-regulator.c:15:
   include/linux/of.h: In function 'of_changeset_attach_node':
>> include/linux/of.h:1616:41: error: 'OF_RECONFIG_ATTACH_NODE' undeclared (first use in this function); did you mean 'OF_RECONFIG_NO_CHANGE'?
    1616 |         return of_changeset_action(ocs, OF_RECONFIG_ATTACH_NODE, np, NULL);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~
         |                                         OF_RECONFIG_NO_CHANGE
   include/linux/of.h:1616:41: note: each undeclared identifier is reported only once for each function it appears in
   include/linux/of.h: In function 'of_changeset_detach_node':
>> include/linux/of.h:1622:41: error: 'OF_RECONFIG_DETACH_NODE' undeclared (first use in this function); did you mean 'OF_RECONFIG_NO_CHANGE'?
    1622 |         return of_changeset_action(ocs, OF_RECONFIG_DETACH_NODE, np, NULL);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~
         |                                         OF_RECONFIG_NO_CHANGE
   include/linux/of.h: In function 'of_changeset_add_property':
>> include/linux/of.h:1628:41: error: 'OF_RECONFIG_ADD_PROPERTY' undeclared (first use in this function)
    1628 |         return of_changeset_action(ocs, OF_RECONFIG_ADD_PROPERTY, np, prop);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/of.h: In function 'of_changeset_remove_property':
>> include/linux/of.h:1634:41: error: 'OF_RECONFIG_REMOVE_PROPERTY' undeclared (first use in this function)
    1634 |         return of_changeset_action(ocs, OF_RECONFIG_REMOVE_PROPERTY, np, prop);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/of.h: In function 'of_changeset_update_property':
>> include/linux/of.h:1640:41: error: 'OF_RECONFIG_UPDATE_PROPERTY' undeclared (first use in this function)
    1640 |         return of_changeset_action(ocs, OF_RECONFIG_UPDATE_PROPERTY, np, prop);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/regulator/twl6030-regulator.c: At top level:
   drivers/regulator/twl6030-regulator.c:645:34: warning: 'twl_of_match' defined but not used [-Wunused-const-variable=]
     645 | static const struct of_device_id twl_of_match[] = {
         |                                  ^~~~~~~~~~~~
--
   In file included from include/linux/irqdomain.h:14,
                    from include/linux/i2c.h:21,
                    from drivers/i2c/i2c-core-of-prober.c:14:
   include/linux/of.h: In function 'of_changeset_attach_node':
>> include/linux/of.h:1616:41: error: 'OF_RECONFIG_ATTACH_NODE' undeclared (first use in this function); did you mean 'OF_RECONFIG_NO_CHANGE'?
    1616 |         return of_changeset_action(ocs, OF_RECONFIG_ATTACH_NODE, np, NULL);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~
         |                                         OF_RECONFIG_NO_CHANGE
   include/linux/of.h:1616:41: note: each undeclared identifier is reported only once for each function it appears in
   include/linux/of.h: In function 'of_changeset_detach_node':
>> include/linux/of.h:1622:41: error: 'OF_RECONFIG_DETACH_NODE' undeclared (first use in this function); did you mean 'OF_RECONFIG_NO_CHANGE'?
    1622 |         return of_changeset_action(ocs, OF_RECONFIG_DETACH_NODE, np, NULL);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~
         |                                         OF_RECONFIG_NO_CHANGE
   include/linux/of.h: In function 'of_changeset_add_property':
>> include/linux/of.h:1628:41: error: 'OF_RECONFIG_ADD_PROPERTY' undeclared (first use in this function)
    1628 |         return of_changeset_action(ocs, OF_RECONFIG_ADD_PROPERTY, np, prop);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/of.h: In function 'of_changeset_remove_property':
>> include/linux/of.h:1634:41: error: 'OF_RECONFIG_REMOVE_PROPERTY' undeclared (first use in this function)
    1634 |         return of_changeset_action(ocs, OF_RECONFIG_REMOVE_PROPERTY, np, prop);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/of.h: In function 'of_changeset_update_property':
>> include/linux/of.h:1640:41: error: 'OF_RECONFIG_UPDATE_PROPERTY' undeclared (first use in this function)
    1640 |         return of_changeset_action(ocs, OF_RECONFIG_UPDATE_PROPERTY, np, prop);
         |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/i2c/i2c-core-of-prober.c: In function 'i2c_of_probe_component':
>> include/linux/of.h:1478:14: error: implicit declaration of function 'of_get_next_child_with_prefix' [-Werror=implicit-function-declaration]
    1478 |              of_get_next_child_with_prefix(parent, NULL, prefix);       \
         |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/i2c/i2c-core-of-prober.c:146:9: note: in expansion of macro 'for_each_child_of_node_with_prefix'
     146 |         for_each_child_of_node_with_prefix(i2c_node, node, type)
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> include/linux/of.h:1478:14: warning: initialization of 'struct device_node *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
    1478 |              of_get_next_child_with_prefix(parent, NULL, prefix);       \
         |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/i2c/i2c-core-of-prober.c:146:9: note: in expansion of macro 'for_each_child_of_node_with_prefix'
     146 |         for_each_child_of_node_with_prefix(i2c_node, node, type)
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> include/linux/of.h:1477:9: error: declaration of non-variable 'of_get_next_child_with_prefix' in 'for' loop initial declaration
    1477 |         for (struct device_node *child __free(device_node) =            \
         |         ^~~
   drivers/i2c/i2c-core-of-prober.c:146:9: note: in expansion of macro 'for_each_child_of_node_with_prefix'
     146 |         for_each_child_of_node_with_prefix(i2c_node, node, type)
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> include/linux/of.h:1480:20: warning: assignment to 'struct device_node *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
    1480 |              child = of_get_next_child_with_prefix(parent, child, prefix))
         |                    ^
   drivers/i2c/i2c-core-of-prober.c:146:9: note: in expansion of macro 'for_each_child_of_node_with_prefix'
     146 |         for_each_child_of_node_with_prefix(i2c_node, node, type)
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> include/linux/of.h:1478:14: warning: initialization of 'struct device_node *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
    1478 |              of_get_next_child_with_prefix(parent, NULL, prefix);       \
         |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/i2c/i2c-core-of-prober.c:161:9: note: in expansion of macro 'for_each_child_of_node_with_prefix'
     161 |         for_each_child_of_node_with_prefix(i2c_node, node, type) {
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> include/linux/of.h:1477:9: error: declaration of non-variable 'of_get_next_child_with_prefix' in 'for' loop initial declaration
    1477 |         for (struct device_node *child __free(device_node) =            \
         |         ^~~
   drivers/i2c/i2c-core-of-prober.c:161:9: note: in expansion of macro 'for_each_child_of_node_with_prefix'
     161 |         for_each_child_of_node_with_prefix(i2c_node, node, type) {
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> include/linux/of.h:1480:20: warning: assignment to 'struct device_node *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
    1480 |              child = of_get_next_child_with_prefix(parent, child, prefix))
         |                    ^
   drivers/i2c/i2c-core-of-prober.c:161:9: note: in expansion of macro 'for_each_child_of_node_with_prefix'
     161 |         for_each_child_of_node_with_prefix(i2c_node, node, type) {
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   cc1: some warnings being treated as errors

Kconfig warnings: (for reference only)
   WARNING: unmet direct dependencies detected for OF_DYNAMIC
   Depends on [n]: OF [=n]
   Selected by [y]:
   - FSL_FMAN [=y] && NETDEVICES [=y] && ETHERNET [=y] && NET_VENDOR_FREESCALE [=y] && (FSL_SOC || ARCH_LAYERSCAPE || COMPILE_TEST [=y])


vim +1616 include/linux/of.h

e7a00e4210e4cc Sebastian Reichel         2014-04-06  1430  
f623ce95a51bae Joerg Roedel              2016-04-04  1431  #define of_for_each_phandle(it, err, np, ln, cn, cc)			\
f623ce95a51bae Joerg Roedel              2016-04-04  1432  	for (of_phandle_iterator_init((it), (np), (ln), (cn), (cc)),	\
f623ce95a51bae Joerg Roedel              2016-04-04  1433  	     err = of_phandle_iterator_next(it);			\
f623ce95a51bae Joerg Roedel              2016-04-04  1434  	     err == 0;							\
f623ce95a51bae Joerg Roedel              2016-04-04  1435  	     err = of_phandle_iterator_next(it))
f623ce95a51bae Joerg Roedel              2016-04-04  1436  
9722c3b66e21ff Luca Ceresoli             2024-07-24  1437  #define of_property_for_each_u32(np, propname, u)			\
9c63fea9acd077 Rob Herring (Arm          2024-10-10  1438) 	for (struct {const struct property *prop; const __be32 *item; } _it =	\
9722c3b66e21ff Luca Ceresoli             2024-07-24  1439  		{of_find_property(np, propname, NULL),			\
9722c3b66e21ff Luca Ceresoli             2024-07-24  1440  		 of_prop_next_u32(_it.prop, NULL, &u)};			\
9722c3b66e21ff Luca Ceresoli             2024-07-24  1441  	     _it.item;							\
9722c3b66e21ff Luca Ceresoli             2024-07-24  1442  	     _it.item = of_prop_next_u32(_it.prop, _it.item, &u))
2adfffa223500b Sebastian Andrzej Siewior 2013-06-17  1443  
2adfffa223500b Sebastian Andrzej Siewior 2013-06-17  1444  #define of_property_for_each_string(np, propname, prop, s)	\
2adfffa223500b Sebastian Andrzej Siewior 2013-06-17  1445  	for (prop = of_find_property(np, propname, NULL),	\
2adfffa223500b Sebastian Andrzej Siewior 2013-06-17  1446  		s = of_prop_next_string(prop, NULL);		\
2adfffa223500b Sebastian Andrzej Siewior 2013-06-17  1447  		s;						\
2adfffa223500b Sebastian Andrzej Siewior 2013-06-17  1448  		s = of_prop_next_string(prop, s))
2adfffa223500b Sebastian Andrzej Siewior 2013-06-17  1449  
662372e42e46d9 Rob Herring               2014-02-03  1450  #define for_each_node_by_name(dn, name) \
662372e42e46d9 Rob Herring               2014-02-03  1451  	for (dn = of_find_node_by_name(NULL, name); dn; \
662372e42e46d9 Rob Herring               2014-02-03  1452  	     dn = of_find_node_by_name(dn, name))
662372e42e46d9 Rob Herring               2014-02-03  1453  #define for_each_node_by_type(dn, type) \
662372e42e46d9 Rob Herring               2014-02-03  1454  	for (dn = of_find_node_by_type(NULL, type); dn; \
662372e42e46d9 Rob Herring               2014-02-03  1455  	     dn = of_find_node_by_type(dn, type))
662372e42e46d9 Rob Herring               2014-02-03  1456  #define for_each_compatible_node(dn, type, compatible) \
662372e42e46d9 Rob Herring               2014-02-03  1457  	for (dn = of_find_compatible_node(NULL, type, compatible); dn; \
662372e42e46d9 Rob Herring               2014-02-03  1458  	     dn = of_find_compatible_node(dn, type, compatible))
662372e42e46d9 Rob Herring               2014-02-03  1459  #define for_each_matching_node(dn, matches) \
662372e42e46d9 Rob Herring               2014-02-03  1460  	for (dn = of_find_matching_node(NULL, matches); dn; \
662372e42e46d9 Rob Herring               2014-02-03  1461  	     dn = of_find_matching_node(dn, matches))
662372e42e46d9 Rob Herring               2014-02-03  1462  #define for_each_matching_node_and_match(dn, matches, match) \
662372e42e46d9 Rob Herring               2014-02-03  1463  	for (dn = of_find_matching_node_and_match(NULL, matches, match); \
662372e42e46d9 Rob Herring               2014-02-03  1464  	     dn; dn = of_find_matching_node_and_match(dn, matches, match))
662372e42e46d9 Rob Herring               2014-02-03  1465  
662372e42e46d9 Rob Herring               2014-02-03  1466  #define for_each_child_of_node(parent, child) \
662372e42e46d9 Rob Herring               2014-02-03  1467  	for (child = of_get_next_child(parent, NULL); child != NULL; \
662372e42e46d9 Rob Herring               2014-02-03  1468  	     child = of_get_next_child(parent, child))
34af4554fb0ce1 Jonathan Cameron          2024-02-25  1469  
34af4554fb0ce1 Jonathan Cameron          2024-02-25  1470  #define for_each_child_of_node_scoped(parent, child) \
34af4554fb0ce1 Jonathan Cameron          2024-02-25  1471  	for (struct device_node *child __free(device_node) =		\
34af4554fb0ce1 Jonathan Cameron          2024-02-25  1472  	     of_get_next_child(parent, NULL);				\
34af4554fb0ce1 Jonathan Cameron          2024-02-25  1473  	     child != NULL;						\
34af4554fb0ce1 Jonathan Cameron          2024-02-25  1474  	     child = of_get_next_child(parent, child))
34af4554fb0ce1 Jonathan Cameron          2024-02-25  1475  
1fcc67e3a35486 Chen-Yu Tsai              2024-11-06  1476  #define for_each_child_of_node_with_prefix(parent, child, prefix)	\
1fcc67e3a35486 Chen-Yu Tsai              2024-11-06 @1477  	for (struct device_node *child __free(device_node) =		\
1fcc67e3a35486 Chen-Yu Tsai              2024-11-06 @1478  	     of_get_next_child_with_prefix(parent, NULL, prefix);	\
1fcc67e3a35486 Chen-Yu Tsai              2024-11-06  1479  	     child != NULL;						\
1fcc67e3a35486 Chen-Yu Tsai              2024-11-06 @1480  	     child = of_get_next_child_with_prefix(parent, child, prefix))
1fcc67e3a35486 Chen-Yu Tsai              2024-11-06  1481  
662372e42e46d9 Rob Herring               2014-02-03  1482  #define for_each_available_child_of_node(parent, child) \
662372e42e46d9 Rob Herring               2014-02-03  1483  	for (child = of_get_next_available_child(parent, NULL); child != NULL; \
662372e42e46d9 Rob Herring               2014-02-03  1484  	     child = of_get_next_available_child(parent, child))
28c5d4e40752fc Kuninori Morimoto         2024-01-10  1485  #define for_each_reserved_child_of_node(parent, child)			\
28c5d4e40752fc Kuninori Morimoto         2024-01-10  1486  	for (child = of_get_next_reserved_child(parent, NULL); child != NULL; \
28c5d4e40752fc Kuninori Morimoto         2024-01-10  1487  	     child = of_get_next_reserved_child(parent, child))
662372e42e46d9 Rob Herring               2014-02-03  1488  
34af4554fb0ce1 Jonathan Cameron          2024-02-25  1489  #define for_each_available_child_of_node_scoped(parent, child) \
34af4554fb0ce1 Jonathan Cameron          2024-02-25  1490  	for (struct device_node *child __free(device_node) =		\
34af4554fb0ce1 Jonathan Cameron          2024-02-25  1491  	     of_get_next_available_child(parent, NULL);			\
34af4554fb0ce1 Jonathan Cameron          2024-02-25  1492  	     child != NULL;						\
34af4554fb0ce1 Jonathan Cameron          2024-02-25  1493  	     child = of_get_next_available_child(parent, child))
662372e42e46d9 Rob Herring               2014-02-03  1494  
f1f207e43b8a49 Rob Herring               2018-08-22  1495  #define for_each_of_cpu_node(cpu) \
f1f207e43b8a49 Rob Herring               2018-08-22  1496  	for (cpu = of_get_next_cpu_node(NULL); cpu != NULL; \
f1f207e43b8a49 Rob Herring               2018-08-22  1497  	     cpu = of_get_next_cpu_node(cpu))
f1f207e43b8a49 Rob Herring               2018-08-22  1498  
662372e42e46d9 Rob Herring               2014-02-03  1499  #define for_each_node_with_property(dn, prop_name) \
662372e42e46d9 Rob Herring               2014-02-03  1500  	for (dn = of_find_node_with_property(NULL, prop_name); dn; \
662372e42e46d9 Rob Herring               2014-02-03  1501  	     dn = of_find_node_with_property(dn, prop_name))
662372e42e46d9 Rob Herring               2014-02-03  1502  
662372e42e46d9 Rob Herring               2014-02-03  1503  static inline int of_get_child_count(const struct device_node *np)
662372e42e46d9 Rob Herring               2014-02-03  1504  {
662372e42e46d9 Rob Herring               2014-02-03  1505  	struct device_node *child;
662372e42e46d9 Rob Herring               2014-02-03  1506  	int num = 0;
662372e42e46d9 Rob Herring               2014-02-03  1507  
662372e42e46d9 Rob Herring               2014-02-03  1508  	for_each_child_of_node(np, child)
662372e42e46d9 Rob Herring               2014-02-03  1509  		num++;
662372e42e46d9 Rob Herring               2014-02-03  1510  
662372e42e46d9 Rob Herring               2014-02-03  1511  	return num;
662372e42e46d9 Rob Herring               2014-02-03  1512  }
662372e42e46d9 Rob Herring               2014-02-03  1513  
662372e42e46d9 Rob Herring               2014-02-03  1514  static inline int of_get_available_child_count(const struct device_node *np)
662372e42e46d9 Rob Herring               2014-02-03  1515  {
662372e42e46d9 Rob Herring               2014-02-03  1516  	struct device_node *child;
662372e42e46d9 Rob Herring               2014-02-03  1517  	int num = 0;
662372e42e46d9 Rob Herring               2014-02-03  1518  
662372e42e46d9 Rob Herring               2014-02-03  1519  	for_each_available_child_of_node(np, child)
662372e42e46d9 Rob Herring               2014-02-03  1520  		num++;
662372e42e46d9 Rob Herring               2014-02-03  1521  
662372e42e46d9 Rob Herring               2014-02-03  1522  	return num;
662372e42e46d9 Rob Herring               2014-02-03  1523  }
662372e42e46d9 Rob Herring               2014-02-03  1524  
67a066b35765d1 Dmitry Osipenko           2021-06-10  1525  #define _OF_DECLARE_STUB(table, name, compat, fn, fn_type)		\
67a066b35765d1 Dmitry Osipenko           2021-06-10  1526  	static const struct of_device_id __of_table_##name		\
67a066b35765d1 Dmitry Osipenko           2021-06-10  1527  		__attribute__((unused))					\
67a066b35765d1 Dmitry Osipenko           2021-06-10  1528  		 = { .compatible = compat,				\
67a066b35765d1 Dmitry Osipenko           2021-06-10  1529  		     .data = (fn == (fn_type)NULL) ? fn : fn }
67a066b35765d1 Dmitry Osipenko           2021-06-10  1530  
71f50c6d9a2276 Masahiro Yamada           2016-01-22  1531  #if defined(CONFIG_OF) && !defined(MODULE)
54196ccbe0ba1f Rob Herring               2014-05-08  1532  #define _OF_DECLARE(table, name, compat, fn, fn_type)			\
54196ccbe0ba1f Rob Herring               2014-05-08  1533  	static const struct of_device_id __of_table_##name		\
33def8498fdde1 Joe Perches               2020-10-21  1534  		__used __section("__" #table "_of_table")		\
5812b32e01c6d8 Johan Hovold              2020-11-23  1535  		__aligned(__alignof__(struct of_device_id))		\
54196ccbe0ba1f Rob Herring               2014-05-08  1536  		 = { .compatible = compat,				\
54196ccbe0ba1f Rob Herring               2014-05-08  1537  		     .data = (fn == (fn_type)NULL) ? fn : fn  }
54196ccbe0ba1f Rob Herring               2014-05-08  1538  #else
54196ccbe0ba1f Rob Herring               2014-05-08  1539  #define _OF_DECLARE(table, name, compat, fn, fn_type)			\
67a066b35765d1 Dmitry Osipenko           2021-06-10  1540  	_OF_DECLARE_STUB(table, name, compat, fn, fn_type)
54196ccbe0ba1f Rob Herring               2014-05-08  1541  #endif
54196ccbe0ba1f Rob Herring               2014-05-08  1542  
54196ccbe0ba1f Rob Herring               2014-05-08  1543  typedef int (*of_init_fn_2)(struct device_node *, struct device_node *);
c35d9292fee047 Daniel Lezcano            2016-04-18  1544  typedef int (*of_init_fn_1_ret)(struct device_node *);
54196ccbe0ba1f Rob Herring               2014-05-08  1545  typedef void (*of_init_fn_1)(struct device_node *);
54196ccbe0ba1f Rob Herring               2014-05-08  1546  
54196ccbe0ba1f Rob Herring               2014-05-08  1547  #define OF_DECLARE_1(table, name, compat, fn) \
54196ccbe0ba1f Rob Herring               2014-05-08  1548  		_OF_DECLARE(table, name, compat, fn, of_init_fn_1)
c35d9292fee047 Daniel Lezcano            2016-04-18  1549  #define OF_DECLARE_1_RET(table, name, compat, fn) \
c35d9292fee047 Daniel Lezcano            2016-04-18  1550  		_OF_DECLARE(table, name, compat, fn, of_init_fn_1_ret)
54196ccbe0ba1f Rob Herring               2014-05-08  1551  #define OF_DECLARE_2(table, name, compat, fn) \
54196ccbe0ba1f Rob Herring               2014-05-08  1552  		_OF_DECLARE(table, name, compat, fn, of_init_fn_2)
54196ccbe0ba1f Rob Herring               2014-05-08  1553  
201c910bd6898d Pantelis Antoniou         2014-07-04  1554  /**
201c910bd6898d Pantelis Antoniou         2014-07-04  1555   * struct of_changeset_entry	- Holds a changeset entry
201c910bd6898d Pantelis Antoniou         2014-07-04  1556   *
201c910bd6898d Pantelis Antoniou         2014-07-04  1557   * @node:	list_head for the log list
201c910bd6898d Pantelis Antoniou         2014-07-04  1558   * @action:	notifier action
201c910bd6898d Pantelis Antoniou         2014-07-04  1559   * @np:		pointer to the device node affected
201c910bd6898d Pantelis Antoniou         2014-07-04  1560   * @prop:	pointer to the property affected
201c910bd6898d Pantelis Antoniou         2014-07-04  1561   * @old_prop:	hold a pointer to the original property
201c910bd6898d Pantelis Antoniou         2014-07-04  1562   *
201c910bd6898d Pantelis Antoniou         2014-07-04  1563   * Every modification of the device tree during a changeset
201c910bd6898d Pantelis Antoniou         2014-07-04  1564   * is held in a list of of_changeset_entry structures.
201c910bd6898d Pantelis Antoniou         2014-07-04  1565   * That way we can recover from a partial application, or we can
201c910bd6898d Pantelis Antoniou         2014-07-04  1566   * revert the changeset
201c910bd6898d Pantelis Antoniou         2014-07-04  1567   */
201c910bd6898d Pantelis Antoniou         2014-07-04  1568  struct of_changeset_entry {
201c910bd6898d Pantelis Antoniou         2014-07-04  1569  	struct list_head node;
201c910bd6898d Pantelis Antoniou         2014-07-04  1570  	unsigned long action;
201c910bd6898d Pantelis Antoniou         2014-07-04  1571  	struct device_node *np;
201c910bd6898d Pantelis Antoniou         2014-07-04  1572  	struct property *prop;
201c910bd6898d Pantelis Antoniou         2014-07-04  1573  	struct property *old_prop;
201c910bd6898d Pantelis Antoniou         2014-07-04  1574  };
201c910bd6898d Pantelis Antoniou         2014-07-04  1575  
201c910bd6898d Pantelis Antoniou         2014-07-04  1576  /**
201c910bd6898d Pantelis Antoniou         2014-07-04  1577   * struct of_changeset - changeset tracker structure
201c910bd6898d Pantelis Antoniou         2014-07-04  1578   *
201c910bd6898d Pantelis Antoniou         2014-07-04  1579   * @entries:	list_head for the changeset entries
201c910bd6898d Pantelis Antoniou         2014-07-04  1580   *
201c910bd6898d Pantelis Antoniou         2014-07-04  1581   * changesets are a convenient way to apply bulk changes to the
201c910bd6898d Pantelis Antoniou         2014-07-04  1582   * live tree. In case of an error, changes are rolled-back.
201c910bd6898d Pantelis Antoniou         2014-07-04  1583   * changesets live on after initial application, and if not
201c910bd6898d Pantelis Antoniou         2014-07-04  1584   * destroyed after use, they can be reverted in one single call.
201c910bd6898d Pantelis Antoniou         2014-07-04  1585   */
201c910bd6898d Pantelis Antoniou         2014-07-04  1586  struct of_changeset {
201c910bd6898d Pantelis Antoniou         2014-07-04  1587  	struct list_head entries;
201c910bd6898d Pantelis Antoniou         2014-07-04  1588  };
201c910bd6898d Pantelis Antoniou         2014-07-04  1589  
b53a2340d0d304 Pantelis Antoniou         2014-10-28  1590  enum of_reconfig_change {
b53a2340d0d304 Pantelis Antoniou         2014-10-28  1591  	OF_RECONFIG_NO_CHANGE = 0,
b53a2340d0d304 Pantelis Antoniou         2014-10-28  1592  	OF_RECONFIG_CHANGE_ADD,
b53a2340d0d304 Pantelis Antoniou         2014-10-28  1593  	OF_RECONFIG_CHANGE_REMOVE,
b53a2340d0d304 Pantelis Antoniou         2014-10-28  1594  };
b53a2340d0d304 Pantelis Antoniou         2014-10-28  1595  
2e8fff668dc14e Rob Herring               2023-03-29  1596  struct notifier_block;
2e8fff668dc14e Rob Herring               2023-03-29  1597  
201c910bd6898d Pantelis Antoniou         2014-07-04  1598  #ifdef CONFIG_OF_DYNAMIC
f6892d193fb9d6 Grant Likely              2014-11-21  1599  extern int of_reconfig_notifier_register(struct notifier_block *);
f6892d193fb9d6 Grant Likely              2014-11-21  1600  extern int of_reconfig_notifier_unregister(struct notifier_block *);
f5242e5a883bf1 Grant Likely              2014-11-24  1601  extern int of_reconfig_notify(unsigned long, struct of_reconfig_data *rd);
f5242e5a883bf1 Grant Likely              2014-11-24  1602  extern int of_reconfig_get_state_change(unsigned long action,
f5242e5a883bf1 Grant Likely              2014-11-24  1603  					struct of_reconfig_data *arg);
f6892d193fb9d6 Grant Likely              2014-11-21  1604  
201c910bd6898d Pantelis Antoniou         2014-07-04  1605  extern void of_changeset_init(struct of_changeset *ocs);
201c910bd6898d Pantelis Antoniou         2014-07-04  1606  extern void of_changeset_destroy(struct of_changeset *ocs);
201c910bd6898d Pantelis Antoniou         2014-07-04  1607  extern int of_changeset_apply(struct of_changeset *ocs);
201c910bd6898d Pantelis Antoniou         2014-07-04  1608  extern int of_changeset_revert(struct of_changeset *ocs);
201c910bd6898d Pantelis Antoniou         2014-07-04  1609  extern int of_changeset_action(struct of_changeset *ocs,
201c910bd6898d Pantelis Antoniou         2014-07-04  1610  		unsigned long action, struct device_node *np,
201c910bd6898d Pantelis Antoniou         2014-07-04  1611  		struct property *prop);
201c910bd6898d Pantelis Antoniou         2014-07-04  1612  
201c910bd6898d Pantelis Antoniou         2014-07-04  1613  static inline int of_changeset_attach_node(struct of_changeset *ocs,
201c910bd6898d Pantelis Antoniou         2014-07-04  1614  		struct device_node *np)
201c910bd6898d Pantelis Antoniou         2014-07-04  1615  {
201c910bd6898d Pantelis Antoniou         2014-07-04 @1616  	return of_changeset_action(ocs, OF_RECONFIG_ATTACH_NODE, np, NULL);
201c910bd6898d Pantelis Antoniou         2014-07-04  1617  }
201c910bd6898d Pantelis Antoniou         2014-07-04  1618  
201c910bd6898d Pantelis Antoniou         2014-07-04  1619  static inline int of_changeset_detach_node(struct of_changeset *ocs,
201c910bd6898d Pantelis Antoniou         2014-07-04  1620  		struct device_node *np)
201c910bd6898d Pantelis Antoniou         2014-07-04  1621  {
201c910bd6898d Pantelis Antoniou         2014-07-04 @1622  	return of_changeset_action(ocs, OF_RECONFIG_DETACH_NODE, np, NULL);
201c910bd6898d Pantelis Antoniou         2014-07-04  1623  }
201c910bd6898d Pantelis Antoniou         2014-07-04  1624  
201c910bd6898d Pantelis Antoniou         2014-07-04  1625  static inline int of_changeset_add_property(struct of_changeset *ocs,
201c910bd6898d Pantelis Antoniou         2014-07-04  1626  		struct device_node *np, struct property *prop)
201c910bd6898d Pantelis Antoniou         2014-07-04  1627  {
201c910bd6898d Pantelis Antoniou         2014-07-04 @1628  	return of_changeset_action(ocs, OF_RECONFIG_ADD_PROPERTY, np, prop);
201c910bd6898d Pantelis Antoniou         2014-07-04  1629  }
201c910bd6898d Pantelis Antoniou         2014-07-04  1630  
201c910bd6898d Pantelis Antoniou         2014-07-04  1631  static inline int of_changeset_remove_property(struct of_changeset *ocs,
201c910bd6898d Pantelis Antoniou         2014-07-04  1632  		struct device_node *np, struct property *prop)
201c910bd6898d Pantelis Antoniou         2014-07-04  1633  {
201c910bd6898d Pantelis Antoniou         2014-07-04 @1634  	return of_changeset_action(ocs, OF_RECONFIG_REMOVE_PROPERTY, np, prop);
201c910bd6898d Pantelis Antoniou         2014-07-04  1635  }
201c910bd6898d Pantelis Antoniou         2014-07-04  1636  
201c910bd6898d Pantelis Antoniou         2014-07-04  1637  static inline int of_changeset_update_property(struct of_changeset *ocs,
201c910bd6898d Pantelis Antoniou         2014-07-04  1638  		struct device_node *np, struct property *prop)
201c910bd6898d Pantelis Antoniou         2014-07-04  1639  {
201c910bd6898d Pantelis Antoniou         2014-07-04 @1640  	return of_changeset_action(ocs, OF_RECONFIG_UPDATE_PROPERTY, np, prop);
201c910bd6898d Pantelis Antoniou         2014-07-04  1641  }
b544fc2b8606d7 Lizhi Hou                 2023-08-15  1642  

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [net-next PATCH v6 09/10] net: macb: Support external PCSs
  2025-06-10 23:36 ` [net-next PATCH v6 09/10] net: macb: Support external PCSs Sean Anderson
@ 2025-06-13 14:50   ` kernel test robot
  0 siblings, 0 replies; 21+ messages in thread
From: kernel test robot @ 2025-06-13 14:50 UTC (permalink / raw)
  To: Sean Anderson, netdev, Andrew Lunn, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Russell King
  Cc: oe-kbuild-all, linux-kernel, Daniel Golle, Simon Horman,
	Kory Maincent, Heiner Kallweit, Lei Wei, Christian Marangi,
	Vineeth Karumanchi, Sean Anderson, Claudiu Beznea, Nicolas Ferre

Hi Sean,

kernel test robot noticed the following build warnings:

[auto build test WARNING on net-next/main]

url:    https://github.com/intel-lab-lkp/linux/commits/Sean-Anderson/dt-bindings-net-Add-Xilinx-PCS/20250611-143544
base:   net-next/main
patch link:    https://lore.kernel.org/r/20250610233642.3588414-1-sean.anderson%40linux.dev
patch subject: [net-next PATCH v6 09/10] net: macb: Support external PCSs
config: xtensa-randconfig-r062-20250613 (https://download.01.org/0day-ci/archive/20250613/202506132226.hNuGyG9l-lkp@intel.com/config)
compiler: xtensa-linux-gcc (GCC) 13.3.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250613/202506132226.hNuGyG9l-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202506132226.hNuGyG9l-lkp@intel.com/

All warnings (new ones prefixed by >>):

   In file included from drivers/net/ethernet/cadence/macb_main.c:24:
   include/linux/pcs.h: In function 'pcs_get_by_fwnode_compat':
   include/linux/pcs.h:201:16: error: implicit declaration of function '_pcs_get_tail' [-Werror=implicit-function-declaration]
     201 |         return _pcs_get_tail(dev, fwnode, NULL);
         |                ^~~~~~~~~~~~~
>> include/linux/pcs.h:201:16: warning: returning 'int' from a function with return type 'struct phylink_pcs *' makes pointer from integer without a cast [-Wint-conversion]
     201 |         return _pcs_get_tail(dev, fwnode, NULL);
         |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   cc1: some warnings being treated as errors


vim +201 include/linux/pcs.h

56201bc1ac71fc Sean Anderson 2025-06-10  187  
56201bc1ac71fc Sean Anderson 2025-06-10  188  #ifdef CONFIG_OF_DYNAMIC
56201bc1ac71fc Sean Anderson 2025-06-10  189  struct phylink_pcs *
56201bc1ac71fc Sean Anderson 2025-06-10  190  pcs_get_by_fwnode_compat(struct device *dev, struct fwnode_handle *fwnode,
56201bc1ac71fc Sean Anderson 2025-06-10  191  			 int (*fixup)(struct of_changeset *ocs,
56201bc1ac71fc Sean Anderson 2025-06-10  192  				      struct device_node *np, void *data),
56201bc1ac71fc Sean Anderson 2025-06-10  193  			 void *data);
56201bc1ac71fc Sean Anderson 2025-06-10  194  #else
56201bc1ac71fc Sean Anderson 2025-06-10  195  static inline struct phylink_pcs *
56201bc1ac71fc Sean Anderson 2025-06-10  196  pcs_get_by_fwnode_compat(struct device *dev, struct fwnode_handle *fwnode,
56201bc1ac71fc Sean Anderson 2025-06-10  197  			 int (*fixup)(struct of_changeset *ocs,
56201bc1ac71fc Sean Anderson 2025-06-10  198  				      struct device_node *np, void *data),
56201bc1ac71fc Sean Anderson 2025-06-10  199  			 void *data)
56201bc1ac71fc Sean Anderson 2025-06-10  200  {
56201bc1ac71fc Sean Anderson 2025-06-10 @201  	return _pcs_get_tail(dev, fwnode, NULL);
56201bc1ac71fc Sean Anderson 2025-06-10  202  }
56201bc1ac71fc Sean Anderson 2025-06-10  203  #endif
56201bc1ac71fc Sean Anderson 2025-06-10  204  

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

end of thread, other threads:[~2025-06-13 14:51 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-10 23:31 [net-next PATCH v6 00/10] Add PCS core support Sean Anderson
2025-06-10 23:31 ` [net-next PATCH v6 01/10] dt-bindings: net: Add Xilinx PCS Sean Anderson
2025-06-10 23:31 ` [net-next PATCH v6 02/10] net: phylink: Support setting PCS link change callbacks Sean Anderson
2025-06-10 23:31 ` [net-next PATCH v6 03/10] net: pcs: Add subsystem Sean Anderson
2025-06-11  0:24   ` Randy Dunlap
2025-06-12 15:38     ` Sean Anderson
2025-06-10 23:31 ` [net-next PATCH v6 04/10] net: dsa: ocelot: suppress PHY device scanning on the internal MDIO bus Sean Anderson
2025-06-10 23:31 ` [net-next PATCH v6 05/10] net: pcs: lynx: Convert to an MDIO driver Sean Anderson
2025-06-13 11:27   ` kernel test robot
2025-06-10 23:31 ` [net-next PATCH v6 06/10] net: pcs: Add Xilinx PCS driver Sean Anderson
2025-06-11  5:11   ` Maxime Chevallier
2025-06-12 15:34     ` Sean Anderson
2025-06-12  0:14   ` kernel test robot
2025-06-10 23:31 ` [net-next PATCH v6 07/10] net: axienet: Convert to use PCS subsystem Sean Anderson
2025-06-11 20:56   ` kernel test robot
2025-06-11 22:19   ` kernel test robot
2025-06-10 23:31 ` [net-next PATCH v6 08/10] net: macb: Move most of mac_config to mac_prepare Sean Anderson
2025-06-10 23:35 ` Sean Anderson
2025-06-10 23:36 ` [net-next PATCH v6 09/10] net: macb: Support external PCSs Sean Anderson
2025-06-13 14:50   ` kernel test robot
2025-06-10 23:37 ` [net-next PATCH v6 10/10] of: property: Add device link support for PCS Sean Anderson

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