public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH phy 0/8] Lynx 28G improvements part 2
@ 2026-01-14 15:21 Vladimir Oltean
  2026-01-14 15:21 ` [PATCH phy 1/8] phy: lynx-28g: skip CDR lock workaround for lanes disabled in the device tree Vladimir Oltean
                   ` (8 more replies)
  0 siblings, 9 replies; 11+ messages in thread
From: Vladimir Oltean @ 2026-01-14 15:21 UTC (permalink / raw)
  To: linux-phy
  Cc: netdev, Ioana Ciornei, Vinod Koul, Kishon Vijay Abraham I,
	Neil Armstrong, Josua Mayer, linux-kernel

This contains a number of changes deferred from part 1 (patches 1-6):
https://lore.kernel.org/linux-phy/20251125114847.804961-1-vladimir.oltean@nxp.com/

They are reworked in ways detailed in their individual change logs.

I have a special request to linux-phy maintainers: after merging, please
provide a stable branch/tag of this plus part 1, that can be pulled into
netdev. It is needed because phy_exit() calls from consumers would
compile but would cause a functionally broken link, so we need a linear
git history to avoid (temporary) regressions.

Ioana Ciornei (1):
  phy: lynx-28g: add support for 25GBASER

Vladimir Oltean (7):
  phy: lynx-28g: skip CDR lock workaround for lanes disabled in the
    device tree
  dt-bindings: phy: lynx-28g: add compatible strings per SerDes and
    instantiation
  dt-bindings: phy: lynx-28g: add constraint on LX2162A lane indices
  phy: lynx-28g: probe on per-SoC and per-instance compatible strings
  phy: lynx-28g: use timeouts when waiting for lane halt and reset
  phy: lynx-28g: truly power the lanes up or down
  phy: lynx-28g: implement phy_exit() operation

 .../devicetree/bindings/phy/fsl,lynx-28g.yaml |  50 ++-
 drivers/phy/freescale/phy-fsl-lynx-28g.c      | 418 ++++++++++++++++--
 2 files changed, 420 insertions(+), 48 deletions(-)

-- 
2.34.1


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

* [PATCH phy 1/8] phy: lynx-28g: skip CDR lock workaround for lanes disabled in the device tree
  2026-01-14 15:21 [PATCH phy 0/8] Lynx 28G improvements part 2 Vladimir Oltean
@ 2026-01-14 15:21 ` Vladimir Oltean
  2026-01-14 15:21 ` [PATCH phy 2/8] dt-bindings: phy: lynx-28g: add compatible strings per SerDes and instantiation Vladimir Oltean
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Vladimir Oltean @ 2026-01-14 15:21 UTC (permalink / raw)
  To: linux-phy
  Cc: netdev, Ioana Ciornei, Vinod Koul, Kishon Vijay Abraham I,
	Neil Armstrong, Josua Mayer, linux-kernel

The blamed commit introduced support for specifying individual lanes as
OF nodes in the device, and these can have status = "disabled".

When that happens, for_each_available_child_of_node() skips them and
lynx_28g_probe_lane() -> devm_phy_create() is not called, so lane->phy
will be NULL. Yet it will be dereferenced in lynx_28g_cdr_lock_check(),
resulting in a crash.

This used to be well handled in v3 of that patch:
https://lore.kernel.org/linux-phy/20250926180505.760089-14-vladimir.oltean@nxp.com/
but until v5 was merged, the logic to support per-lane OF nodes was
split into a separate change, and the per-SoC compatible strings patch
was deferred to a "part 2" set. The splitting was done improperly, and
that handling of NULL lane->phy pointers was not integrated into the
proper commit.

Fixes: 7df7d58abbd6 ("phy: lynx-28g: support individual lanes as OF PHY providers")
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
part 1 -> part 2: patch is "new"

 drivers/phy/freescale/phy-fsl-lynx-28g.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 2b0fd95ba62f..63427fc34e26 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -1069,6 +1069,8 @@ static void lynx_28g_cdr_lock_check(struct work_struct *work)
 
 	for (i = 0; i < LYNX_28G_NUM_LANE; i++) {
 		lane = &priv->lane[i];
+		if (!lane->phy)
+			continue;
 
 		mutex_lock(&lane->phy->mutex);
 
-- 
2.34.1


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

* [PATCH phy 2/8] dt-bindings: phy: lynx-28g: add compatible strings per SerDes and instantiation
  2026-01-14 15:21 [PATCH phy 0/8] Lynx 28G improvements part 2 Vladimir Oltean
  2026-01-14 15:21 ` [PATCH phy 1/8] phy: lynx-28g: skip CDR lock workaround for lanes disabled in the device tree Vladimir Oltean
@ 2026-01-14 15:21 ` Vladimir Oltean
  2026-01-15 19:52   ` Rob Herring (Arm)
  2026-01-14 15:21 ` [PATCH phy 3/8] dt-bindings: phy: lynx-28g: add constraint on LX2162A lane indices Vladimir Oltean
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 11+ messages in thread
From: Vladimir Oltean @ 2026-01-14 15:21 UTC (permalink / raw)
  To: linux-phy
  Cc: netdev, Ioana Ciornei, Vinod Koul, Kishon Vijay Abraham I,
	Neil Armstrong, Josua Mayer, linux-kernel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree

The 28G Lynx SerDes is instantiated 3 times in the NXP LX2160A SoC and
twice in the NXP LX2162A. All these instances share the same register
map, but the number of lanes and the protocols supported by each lane
differs in a way that isn't detectable by the programming model.

Going by the generic "fsl,lynx-28g" compatible string and expecting all
SerDes instantiations to use it was a mistake that needs to be fixed.

The two major options considered are
(a) encode the SoC and the SerDes instance in the compatible string,
    everything else is the responsibility of the driver to derive based
    on this sufficient information
(b) add sufficient device tree properties to describe the per-lane
    differences, as well as the different lane count

Another important consideration is that any decision made here should
be consistent with the decisions taken for the yet-to-be-introduced
10G Lynx SerDes (older generation for older SoCs), because of how
similar they are.

I've seen option (b) at play in this unmerged patch set for the 10G Lynx
here, and I didn't like it:
https://lore.kernel.org/linux-phy/20230413160607.4128315-3-sean.anderson@seco.com/

This is because there, we have a higher degree of variability in the
PCCR register values that need to be written per protocol. This makes
that approach more drawn-out and more prone to errors, compared to (a)
which is more succinct and obviously correct.

So I've chosen option (a) through elimination, and this also reflects
how the SoC reference manual provides different tables with protocol
combinations for each SerDes. NXP clearly documents these as not
identical, and refers to them as such (SerDes 1, 2, etc).

The per-SoC compatible string is prepended to the "fsl,lynx-28g" generic
compatible, which is left there for compatibility with old kernels. An
exception would be LX2160A SerDes #3, which at the time of writing is
not described in fsl-lx2160a.dtsi, and is a non-networking SerDes, so
the existing Linux driver is useless for it. So there is no practical
reason to put the "fsl,lynx-28g" fallback for "fsl,lx2160a-serdes3".

The specific compatible strings give us the opportunity to express more
constraints in the schema that we weren't able to express before:
- We allow #phy-cells in the top-level SerDes node only for
  compatibility with old kernels that don't know how to translate
  "phys = <&serdes_1_lane_a>" to a PHY. We don't need that feature for
  the not-yet-introduced LX2160A SerDes #3, so make the presence of
  #phy-cells at the top level be dependent on the presence of the
  "fsl,lynx-28g" fallback compatible.
- The modernization of the compatible string should come together with
  per-lane OF nodes.
- LX2162A SerDes 1 has fewer lanes than the others, and trying to use
  lanes 0-3 would be a mistake that could be caught by the schema.

Cc: Rob Herring <robh@kernel.org>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: devicetree@vger.kernel.org
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
part 1 -> part 2:
- drop everything having to do with constraints (on #phy-cells,
  #address-cells, #size-cells) based on new compatible strings.

Patch made its last appearance in v4 from part 1:
https://lore.kernel.org/linux-phy/20251110092241.1306838-16-vladimir.oltean@nxp.com/

v3->v4:
- OF nodes per lane broken out as a separate "[PATCH v4 phy 01/16]
  dt-bindings: phy: lynx-28g: permit lane OF PHY providers"
- rewritten commit message
- s|"^phy@[0-9a-f]+$"|"^phy@[0-7]$"|g in patternProperties
- define "#address-cells" and "#size-cells" as part of common
  properties, only leave the part which marks them required in the allOf
  constraints area
v2->v3:
- re-add "fsl,lynx-28g" as fallback compatible, and #phy-cells = <1> in
  top-level "serdes" node
- drop useless description texts
- fix text formatting
- schema is more lax to allow overlaying old and new required properties
v1->v2:
- drop the usage of "fsl,lynx-28g" as a fallback compatible
- mark "fsl,lynx-28g" as deprecated
- implement Josua's request for per-lane OF nodes for the new compatible
  strings

 .../devicetree/bindings/phy/fsl,lynx-28g.yaml | 33 +++++++++++++++++--
 1 file changed, 30 insertions(+), 3 deletions(-)

diff --git a/Documentation/devicetree/bindings/phy/fsl,lynx-28g.yaml b/Documentation/devicetree/bindings/phy/fsl,lynx-28g.yaml
index e96229c2f8fb..8375bca810cc 100644
--- a/Documentation/devicetree/bindings/phy/fsl,lynx-28g.yaml
+++ b/Documentation/devicetree/bindings/phy/fsl,lynx-28g.yaml
@@ -9,10 +9,37 @@ title: Freescale Lynx 28G SerDes PHY
 maintainers:
   - Ioana Ciornei <ioana.ciornei@nxp.com>
 
+description:
+  The Lynx 28G is a multi-lane, multi-protocol SerDes (PCIe, SATA, Ethernet)
+  present in multiple instances on NXP LX2160A and LX2162A SoCs. All instances
+  share a common register map and programming model, however they differ in
+  supported protocols per lane in a way that is not detectable by said
+  programming model without prior knowledge. The distinction is made through
+  the compatible string.
+
 properties:
   compatible:
-    enum:
-      - fsl,lynx-28g
+    oneOf:
+      - const: fsl,lynx-28g
+        deprecated: true
+        description:
+          Legacy compatibility string for Lynx 28G SerDes. Any assumption
+          regarding whether a certain lane supports a certain protocol may
+          be incorrect. Deprecated except when used as a fallback. Use
+          device-specific strings instead.
+      - items:
+          - const: fsl,lx2160a-serdes1
+          - const: fsl,lynx-28g
+      - items:
+          - const: fsl,lx2160a-serdes2
+          - const: fsl,lynx-28g
+      - items:
+          - const: fsl,lx2162a-serdes1
+          - const: fsl,lynx-28g
+      - items:
+          - const: fsl,lx2162a-serdes2
+          - const: fsl,lynx-28g
+      - const: fsl,lx2160a-serdes3
 
   reg:
     maxItems: 1
@@ -60,7 +87,7 @@ examples:
       #size-cells = <2>;
 
       serdes@1ea0000 {
-        compatible = "fsl,lynx-28g";
+        compatible = "fsl,lx2160a-serdes1", "fsl,lynx-28g";
         reg = <0x0 0x1ea0000 0x0 0x1e30>;
         #address-cells = <1>;
         #size-cells = <0>;
-- 
2.34.1


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

* [PATCH phy 3/8] dt-bindings: phy: lynx-28g: add constraint on LX2162A lane indices
  2026-01-14 15:21 [PATCH phy 0/8] Lynx 28G improvements part 2 Vladimir Oltean
  2026-01-14 15:21 ` [PATCH phy 1/8] phy: lynx-28g: skip CDR lock workaround for lanes disabled in the device tree Vladimir Oltean
  2026-01-14 15:21 ` [PATCH phy 2/8] dt-bindings: phy: lynx-28g: add compatible strings per SerDes and instantiation Vladimir Oltean
@ 2026-01-14 15:21 ` Vladimir Oltean
  2026-01-14 15:21 ` [PATCH phy 4/8] phy: lynx-28g: probe on per-SoC and per-instance compatible strings Vladimir Oltean
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Vladimir Oltean @ 2026-01-14 15:21 UTC (permalink / raw)
  To: linux-phy
  Cc: netdev, Ioana Ciornei, Vinod Koul, Kishon Vijay Abraham I,
	Neil Armstrong, Josua Mayer, linux-kernel

The SerDes 1 of LX2162A has fewer lanes than all other instances, and
strangely, their indices are not 0-3, but 4-7.

This constraint was not possible to be imposed when the schema didn't
have per-SoC compatible strings and per-lane OF nodes, so depend on the
two to restrict the lane index.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
part 1 -> part 2: patch is "new"
This is a rewritten constraint from the previous
"[PATCH v4 phy 15/16] dt-bindings: phy: lynx-28g: add compatible strings
per SerDes and instantiation":
https://lore.kernel.org/linux-phy/20251110092241.1306838-16-vladimir.oltean@nxp.com/

 .../devicetree/bindings/phy/fsl,lynx-28g.yaml   | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/Documentation/devicetree/bindings/phy/fsl,lynx-28g.yaml b/Documentation/devicetree/bindings/phy/fsl,lynx-28g.yaml
index 8375bca810cc..2f245094a985 100644
--- a/Documentation/devicetree/bindings/phy/fsl,lynx-28g.yaml
+++ b/Documentation/devicetree/bindings/phy/fsl,lynx-28g.yaml
@@ -78,6 +78,23 @@ required:
   - reg
   - "#phy-cells"
 
+allOf:
+  # LX2162A SerDes 1 has fewer lanes than the others
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: fsl,lx2162a-serdes1
+      patternProperties:
+        "^phy@[0-7]$": true
+    then:
+      patternProperties:
+        "^phy@[0-7]$":
+          properties:
+            reg:
+              minimum: 4
+              maximum: 7
+
 additionalProperties: false
 
 examples:
-- 
2.34.1


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

* [PATCH phy 4/8] phy: lynx-28g: probe on per-SoC and per-instance compatible strings
  2026-01-14 15:21 [PATCH phy 0/8] Lynx 28G improvements part 2 Vladimir Oltean
                   ` (2 preceding siblings ...)
  2026-01-14 15:21 ` [PATCH phy 3/8] dt-bindings: phy: lynx-28g: add constraint on LX2162A lane indices Vladimir Oltean
@ 2026-01-14 15:21 ` Vladimir Oltean
  2026-01-14 15:21 ` [PATCH phy 5/8] phy: lynx-28g: add support for 25GBASER Vladimir Oltean
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Vladimir Oltean @ 2026-01-14 15:21 UTC (permalink / raw)
  To: linux-phy
  Cc: netdev, Ioana Ciornei, Vinod Koul, Kishon Vijay Abraham I,
	Neil Armstrong, Josua Mayer, linux-kernel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree

Add driver support for probing on the new, per-instance and per-SoC
bindings, which provide the main benefit that they allow rejecting
unsupported protocols per lane (10GbE on SerDes 2 lanes 0-5), but they
also allow avoiding the creation of PHYs for lanes that don't exist
(LX2162A lanes 0-3).

Implications of the new bindings:

1. Probing on "fsl,lynx-28g" is still supported, but the feature set is
   frozen in time to just 1GbE and 10GbE (essentially the feature set as
   of this change). However, we encourage the user at probe time to
   update the device tree (because of the inability to reject
   unsupported lane protocols, as described above).

2. We have a new priv->info->first_lane, set to 4 for LX2162A SerDes #1
   and to 0 for everybody else.

3. The lynx_28g_supports_lane_mode() function prototype changes. It was
   a SerDes-global function and now becomes per lane, to reflect the
   specific capabilities each instance may have. The implementation goes
   through priv->info->lane_supports_mode().

Cc: Rob Herring <robh@kernel.org>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: devicetree@vger.kernel.org
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
part 1 -> part 2:
- split out lynx_28g_cdr_lock_check() fix into separate patch

Patch made its last appearance in v4 from part 1:
https://lore.kernel.org/linux-phy/20251110092241.1306838-17-vladimir.oltean@nxp.com/

(old) part 1 change log:
v3->v4:
- the introduction of "bool lane_phy_providers" from lynx_28g_probe()
  disappeared, and the whole "is the SerDes a PHY provider, or the
  individual lanes?" question is now handled by "[PATCH v4 phy 03/16]
  phy: lynx-28g: support individual lanes as OF PHY providers"
v2->v3:
- reword commit message
- add some comments regarding the "fsl,lynx-28g" fallback mechanism
- skip CDR lock workaround for lanes with no PHY (disabled in the device
  tree in the new binding)
v1->v2:
- remove priv->info->get_pccr() and priv->info->get_pcvt_offset().
  These were always called directly as lynx_28g_get_pccr() and
  lynx_28g_get_pcvt_offset().
- Add forgotten priv->info->lane_supports_mode() test to
  lynx_28g_supports_lane_mode().
- Rename the "fsl,lynx-28g" drvdata as lynx_info_compat rather than
  lynx_info_lx2160a_serdes1, to reflect its treatment as less featured.
- Implement a separate lane probing path for the #phy-cells = <0> case.

 drivers/phy/freescale/phy-fsl-lynx-28g.c | 126 +++++++++++++++++++++--
 1 file changed, 116 insertions(+), 10 deletions(-)

diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 63427fc34e26..9e154313c99b 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -433,9 +433,15 @@ struct lynx_28g_lane {
 	enum lynx_lane_mode mode;
 };
 
+struct lynx_info {
+	bool (*lane_supports_mode)(int lane, enum lynx_lane_mode mode);
+	int first_lane;
+};
+
 struct lynx_28g_priv {
 	void __iomem *base;
 	struct device *dev;
+	const struct lynx_info *info;
 	/* Serialize concurrent access to registers shared between lanes,
 	 * like PCCn
 	 */
@@ -500,11 +506,18 @@ static enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf)
 	}
 }
 
-static bool lynx_28g_supports_lane_mode(struct lynx_28g_priv *priv,
+/* A lane mode is supported if we have a PLL that can provide its required
+ * clock net, and if there is a protocol converter for that mode on that lane.
+ */
+static bool lynx_28g_supports_lane_mode(struct lynx_28g_lane *lane,
 					enum lynx_lane_mode mode)
 {
+	struct lynx_28g_priv *priv = lane->priv;
 	int i;
 
+	if (!priv->info->lane_supports_mode(lane->id, mode))
+		return false;
+
 	for (i = 0; i < LYNX_28G_NUM_PLL; i++) {
 		if (PLLnRSTCTL_DIS(priv->pll[i].rstctl))
 			continue;
@@ -687,6 +700,87 @@ static int lynx_28g_get_pcvt_offset(int lane, enum lynx_lane_mode lane_mode)
 	}
 }
 
+static bool lx2160a_serdes1_lane_supports_mode(int lane,
+					       enum lynx_lane_mode mode)
+{
+	return true;
+}
+
+static bool lx2160a_serdes2_lane_supports_mode(int lane,
+					       enum lynx_lane_mode mode)
+{
+	switch (mode) {
+	case LANE_MODE_1000BASEX_SGMII:
+		return true;
+	case LANE_MODE_USXGMII:
+	case LANE_MODE_10GBASER:
+		return lane == 6 || lane == 7;
+	default:
+		return false;
+	}
+}
+
+static bool lx2160a_serdes3_lane_supports_mode(int lane,
+					       enum lynx_lane_mode mode)
+{
+	/*
+	 * Non-networking SerDes, and this driver supports only
+	 * networking protocols
+	 */
+	return false;
+}
+
+static bool lx2162a_serdes1_lane_supports_mode(int lane,
+					       enum lynx_lane_mode mode)
+{
+	return true;
+}
+
+static bool lx2162a_serdes2_lane_supports_mode(int lane,
+					       enum lynx_lane_mode mode)
+{
+	return lx2160a_serdes2_lane_supports_mode(lane, mode);
+}
+
+/* Feature set is not expected to grow for the deprecated compatible string */
+static bool lynx_28g_compat_lane_supports_mode(int lane,
+					       enum lynx_lane_mode mode)
+{
+	switch (mode) {
+	case LANE_MODE_1000BASEX_SGMII:
+	case LANE_MODE_USXGMII:
+	case LANE_MODE_10GBASER:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct lynx_info lynx_info_compat = {
+	.lane_supports_mode = lynx_28g_compat_lane_supports_mode,
+};
+
+static const struct lynx_info lynx_info_lx2160a_serdes1 = {
+	.lane_supports_mode = lx2160a_serdes1_lane_supports_mode,
+};
+
+static const struct lynx_info lynx_info_lx2160a_serdes2 = {
+	.lane_supports_mode = lx2160a_serdes2_lane_supports_mode,
+};
+
+static const struct lynx_info lynx_info_lx2160a_serdes3 = {
+	.lane_supports_mode = lx2160a_serdes3_lane_supports_mode,
+};
+
+static const struct lynx_info lynx_info_lx2162a_serdes1 = {
+	.lane_supports_mode = lx2162a_serdes1_lane_supports_mode,
+	.first_lane = 4,
+};
+
+static const struct lynx_info lynx_info_lx2162a_serdes2 = {
+	.lane_supports_mode = lx2162a_serdes2_lane_supports_mode,
+};
+
 static int lynx_pccr_read(struct lynx_28g_lane *lane, enum lynx_lane_mode mode,
 			  u32 *val)
 {
@@ -939,7 +1033,6 @@ static int lynx_28g_lane_enable_pcvt(struct lynx_28g_lane *lane,
 static int lynx_28g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
 {
 	struct lynx_28g_lane *lane = phy_get_drvdata(phy);
-	struct lynx_28g_priv *priv = lane->priv;
 	int powered_up = lane->powered_up;
 	enum lynx_lane_mode lane_mode;
 	int err = 0;
@@ -951,7 +1044,7 @@ static int lynx_28g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
 		return -EOPNOTSUPP;
 
 	lane_mode = phy_interface_to_lane_mode(submode);
-	if (!lynx_28g_supports_lane_mode(priv, lane_mode))
+	if (!lynx_28g_supports_lane_mode(lane, lane_mode))
 		return -EOPNOTSUPP;
 
 	if (lane_mode == lane->mode)
@@ -984,14 +1077,13 @@ static int lynx_28g_validate(struct phy *phy, enum phy_mode mode, int submode,
 			     union phy_configure_opts *opts __always_unused)
 {
 	struct lynx_28g_lane *lane = phy_get_drvdata(phy);
-	struct lynx_28g_priv *priv = lane->priv;
 	enum lynx_lane_mode lane_mode;
 
 	if (mode != PHY_MODE_ETHERNET)
 		return -EOPNOTSUPP;
 
 	lane_mode = phy_interface_to_lane_mode(submode);
-	if (!lynx_28g_supports_lane_mode(priv, lane_mode))
+	if (!lynx_28g_supports_lane_mode(lane, lane_mode))
 		return -EOPNOTSUPP;
 
 	return 0;
@@ -1067,7 +1159,7 @@ static void lynx_28g_cdr_lock_check(struct work_struct *work)
 	u32 rrstctl;
 	int i;
 
-	for (i = 0; i < LYNX_28G_NUM_LANE; i++) {
+	for (i = priv->info->first_lane; i < LYNX_28G_NUM_LANE; i++) {
 		lane = &priv->lane[i];
 		if (!lane->phy)
 			continue;
@@ -1129,7 +1221,8 @@ static struct phy *lynx_28g_xlate(struct device *dev,
 
 	idx = args->args[0];
 
-	if (WARN_ON(idx >= LYNX_28G_NUM_LANE))
+	if (WARN_ON(idx >= LYNX_28G_NUM_LANE ||
+		    idx < priv->info->first_lane))
 		return ERR_PTR(-EINVAL);
 
 	return priv->lane[idx].phy;
@@ -1167,10 +1260,18 @@ static int lynx_28g_probe(struct platform_device *pdev)
 		return -ENOMEM;
 
 	priv->dev = dev;
+	priv->info = of_device_get_match_data(dev);
 	dev_set_drvdata(dev, priv);
 	spin_lock_init(&priv->pcc_lock);
 	INIT_DELAYED_WORK(&priv->cdr_check, lynx_28g_cdr_lock_check);
 
+	/*
+	 * If we get here it means we probed on a device tree where
+	 * "fsl,lynx-28g" wasn't the fallback, but the sole compatible string.
+	 */
+	if (priv->info == &lynx_info_compat)
+		dev_warn(dev, "Please update device tree to use per-device compatible strings\n");
+
 	priv->base = devm_platform_ioremap_resource(pdev, 0);
 	if (IS_ERR(priv->base))
 		return PTR_ERR(priv->base);
@@ -1194,7 +1295,7 @@ static int lynx_28g_probe(struct platform_device *pdev)
 				return -EINVAL;
 			}
 
-			if (reg >= LYNX_28G_NUM_LANE) {
+			if (reg < priv->info->first_lane || reg >= LYNX_28G_NUM_LANE) {
 				dev_err(dev, "\"reg\" property out of range for %pOF\n", child);
 				of_node_put(child);
 				return -EINVAL;
@@ -1207,7 +1308,7 @@ static int lynx_28g_probe(struct platform_device *pdev)
 			}
 		}
 	} else {
-		for (int i = 0; i < LYNX_28G_NUM_LANE; i++) {
+		for (int i = priv->info->first_lane; i < LYNX_28G_NUM_LANE; i++) {
 			err = lynx_28g_probe_lane(priv, i, NULL);
 			if (err)
 				return err;
@@ -1233,7 +1334,12 @@ static void lynx_28g_remove(struct platform_device *pdev)
 }
 
 static const struct of_device_id lynx_28g_of_match_table[] = {
-	{ .compatible = "fsl,lynx-28g" },
+	{ .compatible = "fsl,lx2160a-serdes1", .data = &lynx_info_lx2160a_serdes1 },
+	{ .compatible = "fsl,lx2160a-serdes2", .data = &lynx_info_lx2160a_serdes2 },
+	{ .compatible = "fsl,lx2160a-serdes3", .data = &lynx_info_lx2160a_serdes3 },
+	{ .compatible = "fsl,lx2162a-serdes1", .data = &lynx_info_lx2162a_serdes1 },
+	{ .compatible = "fsl,lx2162a-serdes2", .data = &lynx_info_lx2162a_serdes2 },
+	{ .compatible = "fsl,lynx-28g", .data = &lynx_info_compat }, /* fallback, keep last */
 	{ },
 };
 MODULE_DEVICE_TABLE(of, lynx_28g_of_match_table);
-- 
2.34.1


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

* [PATCH phy 5/8] phy: lynx-28g: add support for 25GBASER
  2026-01-14 15:21 [PATCH phy 0/8] Lynx 28G improvements part 2 Vladimir Oltean
                   ` (3 preceding siblings ...)
  2026-01-14 15:21 ` [PATCH phy 4/8] phy: lynx-28g: probe on per-SoC and per-instance compatible strings Vladimir Oltean
@ 2026-01-14 15:21 ` Vladimir Oltean
  2026-01-14 15:21 ` [PATCH phy 6/8] phy: lynx-28g: use timeouts when waiting for lane halt and reset Vladimir Oltean
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Vladimir Oltean @ 2026-01-14 15:21 UTC (permalink / raw)
  To: linux-phy
  Cc: netdev, Ioana Ciornei, Vinod Koul, Kishon Vijay Abraham I,
	Neil Armstrong, Josua Mayer, linux-kernel

From: Ioana Ciornei <ioana.ciornei@nxp.com>

Add support for 25GBASE-R in the Lynx 28G SerDes PHY driver.

25GbE is sourced from a clock net frequency of 12.890625 GHz, as
produced by PLLF or PLLS, further multiplied by the lane by 2.

The change consists of:
- determining at probe time if any PLL was preconfigured for the
  required clock net frequency for 25GbE
- adding the default lane parameters for reconfiguring a lane to 25GbE
  irrespective of the original protocol
- allowing this operating mode only on supported lanes, i.e. all lanes
  of LX2162A SerDes #1, and LX2160A SerDes lanes 0-1, 4-7.

Signed-off-by: Ioana Ciornei <ioana.ciornei@nxp.com>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
part 1 -> part 2:
- rewrite commit message.

Patch made its last appearance in v3 from part 1:
https://lore.kernel.org/linux-phy/20250926180505.760089-15-vladimir.oltean@nxp.com/

(old) part 1 change log:

v2->v3: none
v1->v2: implement missing lane_supports_mode() restrictions for 25GbE

 drivers/phy/freescale/phy-fsl-lynx-28g.c | 90 +++++++++++++++++++++++-
 1 file changed, 88 insertions(+), 2 deletions(-)

diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 9e154313c99b..7ada581bbe4c 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -57,6 +57,7 @@
 #define PLLnCR1_FRATE_5G_10GVCO			0x0
 #define PLLnCR1_FRATE_5G_25GVCO			0x10
 #define PLLnCR1_FRATE_10G_20GVCO		0x6
+#define PLLnCR1_FRATE_12G_25GVCO		0x16
 
 /* Per SerDes lane registers */
 /* Lane a General Control Register */
@@ -64,9 +65,11 @@
 #define LNaGCR0_PROTO_SEL			GENMASK(7, 3)
 #define LNaGCR0_PROTO_SEL_SGMII			0x1
 #define LNaGCR0_PROTO_SEL_XFI			0xa
+#define LNaGCR0_PROTO_SEL_25G			0x1a
 #define LNaGCR0_IF_WIDTH			GENMASK(2, 0)
 #define LNaGCR0_IF_WIDTH_10_BIT			0x0
 #define LNaGCR0_IF_WIDTH_20_BIT			0x2
+#define LNaGCR0_IF_WIDTH_40_BIT			0x4
 
 /* Lane a Tx Reset Control Register */
 #define LNaTRSTCTL(lane)			(0x800 + (lane) * 0x100 + 0x20)
@@ -83,6 +86,7 @@
 #define LNaTGCR0_N_RATE_FULL			0x0
 #define LNaTGCR0_N_RATE_HALF			0x1
 #define LNaTGCR0_N_RATE_QUARTER			0x2
+#define LNaTGCR0_N_RATE_DOUBLE			0x3
 
 #define LNaTECR0(lane)				(0x800 + (lane) * 0x100 + 0x30)
 #define LNaTECR0_EQ_TYPE			GENMASK(30, 28)
@@ -112,6 +116,7 @@
 #define LNaRGCR0_N_RATE_FULL			0x0
 #define LNaRGCR0_N_RATE_HALF			0x1
 #define LNaRGCR0_N_RATE_QUARTER			0x2
+#define LNaRGCR0_N_RATE_DOUBLE			0x3
 
 #define LNaRGCR1(lane)				(0x800 + (lane) * 0x100 + 0x48)
 #define LNaRGCR1_RX_ORD_ELECIDLE		BIT(31)
@@ -269,6 +274,7 @@ enum lynx_lane_mode {
 	LANE_MODE_1000BASEX_SGMII,
 	LANE_MODE_10GBASER,
 	LANE_MODE_USXGMII,
+	LANE_MODE_25GBASER,
 	LANE_MODE_MAX,
 };
 
@@ -407,6 +413,41 @@ static const struct lynx_28g_proto_conf lynx_28g_proto_conf[LANE_MODE_MAX] = {
 		.ttlcr0 = LNaTTLCR0_TTL_SLO_PM_BYP |
 			  LNaTTLCR0_DATA_IN_SSC,
 	},
+	[LANE_MODE_25GBASER] = {
+		.proto_sel = LNaGCR0_PROTO_SEL_25G,
+		.if_width = LNaGCR0_IF_WIDTH_40_BIT,
+		.teq_type = EQ_TYPE_3TAP,
+		.sgn_preq = 1,
+		.ratio_preq = 2,
+		.sgn_post1q = 1,
+		.ratio_post1q = 7,
+		.amp_red = 0,
+		.adpt_eq = 48,
+		.enter_idle_flt_sel = 0,
+		.exit_idle_flt_sel = 0,
+		.data_lost_th_sel = 0,
+		.gk2ovd = 0,
+		.gk3ovd = 0,
+		.gk4ovd = 5,
+		.gk2ovd_en = 0,
+		.gk3ovd_en = 0,
+		.gk4ovd_en = 1,
+		.eq_offset_ovd = 0x1f,
+		.eq_offset_ovd_en = 0,
+		.eq_offset_rng_dbl = 1,
+		.eq_blw_sel = 1,
+		.eq_boost = 2,
+		.spare_in = 3,
+		.smp_autoz_d1r = 2,
+		.smp_autoz_eg1r = 2,
+		.rccr0 = LNaRCCR0_CAL_EN |
+			 LNaRCCR0_CAL_DC3_DIS |
+			 LNaRCCR0_CAL_DC2_DIS |
+			 LNaRCCR0_CAL_DC1_DIS |
+			 LNaRCCR0_CAL_DC0_DIS,
+		.ttlcr0 = LNaTTLCR0_DATA_IN_SSC |
+			  FIELD_PREP_CONST(LNaTTLCR0_CDR_MIN_SMP_ON, 1),
+	},
 };
 
 struct lynx_pccr {
@@ -486,6 +527,8 @@ static const char *lynx_lane_mode_str(enum lynx_lane_mode lane_mode)
 		return "10GBase-R";
 	case LANE_MODE_USXGMII:
 		return "USXGMII";
+	case LANE_MODE_25GBASER:
+		return "25GBase-R";
 	default:
 		return "unknown";
 	}
@@ -501,6 +544,8 @@ static enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf)
 		return LANE_MODE_10GBASER;
 	case PHY_INTERFACE_MODE_USXGMII:
 		return LANE_MODE_USXGMII;
+	case PHY_INTERFACE_MODE_25GBASER:
+		return LANE_MODE_25GBASER;
 	default:
 		return LANE_MODE_UNKNOWN;
 	}
@@ -588,6 +633,20 @@ static void lynx_28g_lane_set_nrate(struct lynx_28g_lane *lane,
 			break;
 		}
 		break;
+	case PLLnCR1_FRATE_12G_25GVCO:
+		switch (lane_mode) {
+		case LANE_MODE_25GBASER:
+			lynx_28g_lane_rmw(lane, LNaTGCR0,
+					  FIELD_PREP(LNaTGCR0_N_RATE, LNaTGCR0_N_RATE_DOUBLE),
+					  LNaTGCR0_N_RATE);
+			lynx_28g_lane_rmw(lane, LNaRGCR0,
+					  FIELD_PREP(LNaRGCR0_N_RATE, LNaRGCR0_N_RATE_DOUBLE),
+					  LNaRGCR0_N_RATE);
+			break;
+		default:
+			break;
+		}
+		break;
 	default:
 		break;
 	}
@@ -665,6 +724,11 @@ static int lynx_28g_power_on(struct phy *phy)
 	return 0;
 }
 
+static int lynx_28g_e25g_pcvt(int lane)
+{
+	return 7 - lane;
+}
+
 static int lynx_28g_get_pccr(enum lynx_lane_mode lane_mode, int lane,
 			     struct lynx_pccr *pccr)
 {
@@ -680,6 +744,11 @@ static int lynx_28g_get_pccr(enum lynx_lane_mode lane_mode, int lane,
 		pccr->width = 4;
 		pccr->shift = SXGMII_CFG(lane);
 		break;
+	case LANE_MODE_25GBASER:
+		pccr->offset = PCCD;
+		pccr->width = 4;
+		pccr->shift = E25G_CFG(lynx_28g_e25g_pcvt(lane));
+		break;
 	default:
 		return -EOPNOTSUPP;
 	}
@@ -695,6 +764,8 @@ static int lynx_28g_get_pcvt_offset(int lane, enum lynx_lane_mode lane_mode)
 	case LANE_MODE_USXGMII:
 	case LANE_MODE_10GBASER:
 		return SXGMIIaCR0(lane);
+	case LANE_MODE_25GBASER:
+		return E25GaCR0(lynx_28g_e25g_pcvt(lane));
 	default:
 		return -EOPNOTSUPP;
 	}
@@ -703,7 +774,12 @@ static int lynx_28g_get_pcvt_offset(int lane, enum lynx_lane_mode lane_mode)
 static bool lx2160a_serdes1_lane_supports_mode(int lane,
 					       enum lynx_lane_mode mode)
 {
-	return true;
+	switch (mode) {
+	case LANE_MODE_25GBASER:
+		return lane != 2 && lane != 3;
+	default:
+		return true;
+	}
 }
 
 static bool lx2160a_serdes2_lane_supports_mode(int lane,
@@ -1019,6 +1095,9 @@ static int lynx_28g_lane_enable_pcvt(struct lynx_28g_lane *lane,
 	case LANE_MODE_USXGMII:
 		val |= PCCC_SXGMIIn_CFG;
 		break;
+	case LANE_MODE_25GBASER:
+		val |= PCCD_E25Gn_CFG;
+		break;
 	default:
 		break;
 	}
@@ -1143,8 +1222,12 @@ static void lynx_28g_pll_read_configuration(struct lynx_28g_priv *priv)
 			__set_bit(LANE_MODE_10GBASER, pll->supported);
 			__set_bit(LANE_MODE_USXGMII, pll->supported);
 			break;
+		case PLLnCR1_FRATE_12G_25GVCO:
+			/* 12.890625GHz clock net */
+			__set_bit(LANE_MODE_25GBASER, pll->supported);
+			break;
 		default:
-			/* 6GHz, 12.890625GHz, 8GHz */
+			/* 6GHz, 8GHz */
 			break;
 		}
 	}
@@ -1203,6 +1286,9 @@ static void lynx_28g_lane_read_configuration(struct lynx_28g_lane *lane)
 		else
 			lane->mode = LANE_MODE_USXGMII;
 		break;
+	case LNaPSS_TYPE_25G:
+		lane->mode = LANE_MODE_25GBASER;
+		break;
 	default:
 		lane->mode = LANE_MODE_UNKNOWN;
 	}
-- 
2.34.1


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

* [PATCH phy 6/8] phy: lynx-28g: use timeouts when waiting for lane halt and reset
  2026-01-14 15:21 [PATCH phy 0/8] Lynx 28G improvements part 2 Vladimir Oltean
                   ` (4 preceding siblings ...)
  2026-01-14 15:21 ` [PATCH phy 5/8] phy: lynx-28g: add support for 25GBASER Vladimir Oltean
@ 2026-01-14 15:21 ` Vladimir Oltean
  2026-01-14 15:21 ` [PATCH phy 7/8] phy: lynx-28g: truly power the lanes up or down Vladimir Oltean
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Vladimir Oltean @ 2026-01-14 15:21 UTC (permalink / raw)
  To: linux-phy
  Cc: netdev, Ioana Ciornei, Vinod Koul, Kishon Vijay Abraham I,
	Neil Armstrong, Josua Mayer, linux-kernel

There are various circumstances in which a lane halt, or a lane reset,
will fail to complete. If this happens, it will hang the kernel, which
only implements a busy loop with no timeout.

The circumstances in which this will happen are all bugs in nature:
- if we try to power off a powered off lane
- if we try to power off a lane that uses a PLL locked onto the wrong
  refclk frequency (wrong RCW, but SoC boots anyway)

Actually, unbounded loops in the kernel are a bad practice, so let's use
read_poll_timeout() with a custom function that reads both LNaTRSTCTL
(lane transmit control register) and LNaRRSTCTL (lane receive control
register) and returns true when the request is done in both directions.

The HLT_REQ bit has to clear, whereas the RST_DONE bit has to get set.

Because of the new potential timeout error in lynx_28g_power_off() and
lynx_28g_power_on(), this needs to be checked for at call sites. Before,
these functions only returned zero.

Suggested-by: Josua Mayer <josua@solid-run.com>
Link: https://lore.kernel.org/lkml/d0c8bbf8-a0c5-469f-a148-de2235948c0f@solid-run.com/
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
part 1 -> part 2:
- minor commit message fixups

Patch made its last appearance in v3 from part 1:
https://lore.kernel.org/linux-phy/20250926180505.760089-16-vladimir.oltean@nxp.com/

(old) part 1 change log:

v2->v3: patch is new

 drivers/phy/freescale/phy-fsl-lynx-28g.c | 96 ++++++++++++++++++------
 1 file changed, 74 insertions(+), 22 deletions(-)

diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 7ada581bbe4c..048c24c48803 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -254,6 +254,12 @@
 
 #define CR(x)					((x) * 4)
 
+#define LYNX_28G_LANE_HALT_SLEEP_US		100
+#define LYNX_28G_LANE_HALT_TIMEOUT_US		1000000
+
+#define LYNX_28G_LANE_RESET_SLEEP_US		100
+#define LYNX_28G_LANE_RESET_TIMEOUT_US		1000000
+
 enum lynx_28g_eq_type {
 	EQ_TYPE_NO_EQ = 0,
 	EQ_TYPE_2TAP = 1,
@@ -672,10 +678,29 @@ static void lynx_28g_lane_set_pll(struct lynx_28g_lane *lane,
 	}
 }
 
+static bool lynx_28g_lane_halt_done(struct lynx_28g_lane *lane)
+{
+	u32 trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL);
+	u32 rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
+
+	return !(trstctl & LNaTRSTCTL_HLT_REQ) &&
+	       !(rrstctl & LNaRRSTCTL_HLT_REQ);
+}
+
+static bool lynx_28g_lane_reset_done(struct lynx_28g_lane *lane)
+{
+	u32 trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL);
+	u32 rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
+
+	return (trstctl & LNaTRSTCTL_RST_DONE) &&
+	       (rrstctl & LNaRRSTCTL_RST_DONE);
+}
+
 static int lynx_28g_power_off(struct phy *phy)
 {
 	struct lynx_28g_lane *lane = phy_get_drvdata(phy);
-	u32 trstctl, rrstctl;
+	bool done;
+	int err;
 
 	if (!lane->powered_up)
 		return 0;
@@ -687,11 +712,15 @@ static int lynx_28g_power_off(struct phy *phy)
 			  LNaRRSTCTL_HLT_REQ);
 
 	/* Wait until the halting process is complete */
-	do {
-		trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL);
-		rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
-	} while ((trstctl & LNaTRSTCTL_HLT_REQ) ||
-		 (rrstctl & LNaRRSTCTL_HLT_REQ));
+	err = read_poll_timeout(lynx_28g_lane_halt_done, done, done,
+				LYNX_28G_LANE_HALT_SLEEP_US,
+				LYNX_28G_LANE_HALT_TIMEOUT_US,
+				false, lane);
+	if (err) {
+		dev_err(&phy->dev, "Lane %c halt failed: %pe\n",
+			'A' + lane->id, ERR_PTR(err));
+		return err;
+	}
 
 	lane->powered_up = false;
 
@@ -701,7 +730,8 @@ static int lynx_28g_power_off(struct phy *phy)
 static int lynx_28g_power_on(struct phy *phy)
 {
 	struct lynx_28g_lane *lane = phy_get_drvdata(phy);
-	u32 trstctl, rrstctl;
+	bool done;
+	int err;
 
 	if (lane->powered_up)
 		return 0;
@@ -713,11 +743,15 @@ static int lynx_28g_power_on(struct phy *phy)
 			  LNaRRSTCTL_RST_REQ);
 
 	/* Wait until the reset sequence is completed */
-	do {
-		trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL);
-		rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
-	} while (!(trstctl & LNaTRSTCTL_RST_DONE) ||
-		 !(rrstctl & LNaRRSTCTL_RST_DONE));
+	err = read_poll_timeout(lynx_28g_lane_reset_done, done, done,
+				LYNX_28G_LANE_RESET_SLEEP_US,
+				LYNX_28G_LANE_RESET_TIMEOUT_US,
+				false, lane);
+	if (err) {
+		dev_err(&phy->dev, "Lane %c reset failed: %pe\n",
+			'A' + lane->id, ERR_PTR(err));
+		return err;
+	}
 
 	lane->powered_up = true;
 
@@ -1132,8 +1166,11 @@ static int lynx_28g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
 	/* If the lane is powered up, put the lane into the halt state while
 	 * the reconfiguration is being done.
 	 */
-	if (powered_up)
-		lynx_28g_power_off(phy);
+	if (powered_up) {
+		err = lynx_28g_power_off(phy);
+		if (err)
+			return err;
+	}
 
 	err = lynx_28g_lane_disable_pcvt(lane, lane->mode);
 	if (err)
@@ -1146,8 +1183,16 @@ static int lynx_28g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
 	lane->mode = lane_mode;
 
 out:
-	if (powered_up)
-		lynx_28g_power_on(phy);
+	if (powered_up) {
+		int err2 = lynx_28g_power_on(phy);
+		/*
+		 * Don't overwrite a failed protocol converter disable error
+		 * code with a successful lane power on error code, but
+		 * propagate a failed lane power on error.
+		 */
+		if (!err)
+			err = err2;
+	}
 
 	return err;
 }
@@ -1180,9 +1225,8 @@ static int lynx_28g_init(struct phy *phy)
 	 * probe time.
 	 */
 	lane->powered_up = true;
-	lynx_28g_power_off(phy);
 
-	return 0;
+	return lynx_28g_power_off(phy);
 }
 
 static const struct phy_ops lynx_28g_ops = {
@@ -1240,7 +1284,7 @@ static void lynx_28g_cdr_lock_check(struct work_struct *work)
 	struct lynx_28g_priv *priv = work_to_lynx(work);
 	struct lynx_28g_lane *lane;
 	u32 rrstctl;
-	int i;
+	int err, i;
 
 	for (i = priv->info->first_lane; i < LYNX_28G_NUM_LANE; i++) {
 		lane = &priv->lane[i];
@@ -1258,9 +1302,17 @@ static void lynx_28g_cdr_lock_check(struct work_struct *work)
 		if (!(rrstctl & LNaRRSTCTL_CDR_LOCK)) {
 			lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_RST_REQ,
 					  LNaRRSTCTL_RST_REQ);
-			do {
-				rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
-			} while (!(rrstctl & LNaRRSTCTL_RST_DONE));
+
+			err = read_poll_timeout(lynx_28g_lane_read, rrstctl,
+						!!(rrstctl & LNaRRSTCTL_RST_DONE),
+						LYNX_28G_LANE_RESET_SLEEP_US,
+						LYNX_28G_LANE_RESET_TIMEOUT_US,
+						false, lane, LNaRRSTCTL);
+			if (err) {
+				dev_warn_once(&lane->phy->dev,
+					      "Lane %c receiver reset failed: %pe\n",
+					      'A' + lane->id, ERR_PTR(err));
+			}
 		}
 
 		mutex_unlock(&lane->phy->mutex);
-- 
2.34.1


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

* [PATCH phy 7/8] phy: lynx-28g: truly power the lanes up or down
  2026-01-14 15:21 [PATCH phy 0/8] Lynx 28G improvements part 2 Vladimir Oltean
                   ` (5 preceding siblings ...)
  2026-01-14 15:21 ` [PATCH phy 6/8] phy: lynx-28g: use timeouts when waiting for lane halt and reset Vladimir Oltean
@ 2026-01-14 15:21 ` Vladimir Oltean
  2026-01-14 15:21 ` [PATCH phy 8/8] phy: lynx-28g: implement phy_exit() operation Vladimir Oltean
  2026-02-02 15:03 ` [PATCH phy 0/8] Lynx 28G improvements part 2 Vladimir Oltean
  8 siblings, 0 replies; 11+ messages in thread
From: Vladimir Oltean @ 2026-01-14 15:21 UTC (permalink / raw)
  To: linux-phy
  Cc: netdev, Ioana Ciornei, Vinod Koul, Kishon Vijay Abraham I,
	Neil Armstrong, Josua Mayer, linux-kernel

The current procedure for power_off() and power_on() is the same as the
one used for major lane reconfiguration, aka halting.

But one would expect that a powered off lane causes the CDR (clock and
data recovery) loop of the link partner to lose lock onto its RX stream
(which suggests there are no longer any bit transitions => the channel
is inactive). However, it can be observed that this does not take place
(the CDR lock is still there), which means that a halted lane is still
active.

Implement the procedure mentioned in the block guide for powering down
a lane, and then back on.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
part 1 -> part 2:
- minor commit message fixup

Patch last made its appearance in v3 from part 1:
https://lore.kernel.org/linux-phy/20250926180505.760089-17-vladimir.oltean@nxp.com/

(old) part 1 change log:

v2->v3: reimplement lynx_28g_power_off() using read_poll_timeout()
v1->v2: slight commit message reword

 drivers/phy/freescale/phy-fsl-lynx-28g.c | 99 ++++++++++++++++++++----
 1 file changed, 83 insertions(+), 16 deletions(-)

diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 048c24c48803..4c20d5d42983 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -73,9 +73,11 @@
 
 /* Lane a Tx Reset Control Register */
 #define LNaTRSTCTL(lane)			(0x800 + (lane) * 0x100 + 0x20)
-#define LNaTRSTCTL_HLT_REQ			BIT(27)
-#define LNaTRSTCTL_RST_DONE			BIT(30)
 #define LNaTRSTCTL_RST_REQ			BIT(31)
+#define LNaTRSTCTL_RST_DONE			BIT(30)
+#define LNaTRSTCTL_HLT_REQ			BIT(27)
+#define LNaTRSTCTL_STP_REQ			BIT(26)
+#define LNaTRSTCTL_DIS				BIT(24)
 
 /* Lane a Tx General Control Register */
 #define LNaTGCR0(lane)				(0x800 + (lane) * 0x100 + 0x24)
@@ -102,9 +104,11 @@
 
 /* Lane a Rx Reset Control Register */
 #define LNaRRSTCTL(lane)			(0x800 + (lane) * 0x100 + 0x40)
-#define LNaRRSTCTL_HLT_REQ			BIT(27)
-#define LNaRRSTCTL_RST_DONE			BIT(30)
 #define LNaRRSTCTL_RST_REQ			BIT(31)
+#define LNaRRSTCTL_RST_DONE			BIT(30)
+#define LNaRRSTCTL_HLT_REQ			BIT(27)
+#define LNaRRSTCTL_STP_REQ			BIT(26)
+#define LNaRRSTCTL_DIS				BIT(24)
 #define LNaRRSTCTL_CDR_LOCK			BIT(12)
 
 /* Lane a Rx General Control Register */
@@ -260,6 +264,9 @@
 #define LYNX_28G_LANE_RESET_SLEEP_US		100
 #define LYNX_28G_LANE_RESET_TIMEOUT_US		1000000
 
+#define LYNX_28G_LANE_STOP_SLEEP_US		100
+#define LYNX_28G_LANE_STOP_TIMEOUT_US		1000000
+
 enum lynx_28g_eq_type {
 	EQ_TYPE_NO_EQ = 0,
 	EQ_TYPE_2TAP = 1,
@@ -687,6 +694,15 @@ static bool lynx_28g_lane_halt_done(struct lynx_28g_lane *lane)
 	       !(rrstctl & LNaRRSTCTL_HLT_REQ);
 }
 
+static bool lynx_28g_lane_stop_done(struct lynx_28g_lane *lane)
+{
+	u32 trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL);
+	u32 rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
+
+	return !(trstctl & LNaTRSTCTL_STP_REQ) &&
+	       !(rrstctl & LNaRRSTCTL_STP_REQ);
+}
+
 static bool lynx_28g_lane_reset_done(struct lynx_28g_lane *lane)
 {
 	u32 trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL);
@@ -696,15 +712,13 @@ static bool lynx_28g_lane_reset_done(struct lynx_28g_lane *lane)
 	       (rrstctl & LNaRRSTCTL_RST_DONE);
 }
 
-static int lynx_28g_power_off(struct phy *phy)
+/* Halting puts the lane in a mode in which it can be reconfigured */
+static int lynx_28g_lane_halt(struct phy *phy)
 {
 	struct lynx_28g_lane *lane = phy_get_drvdata(phy);
 	bool done;
 	int err;
 
-	if (!lane->powered_up)
-		return 0;
-
 	/* Issue a halt request */
 	lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_HLT_REQ,
 			  LNaTRSTCTL_HLT_REQ);
@@ -727,15 +741,12 @@ static int lynx_28g_power_off(struct phy *phy)
 	return 0;
 }
 
-static int lynx_28g_power_on(struct phy *phy)
+static int lynx_28g_lane_reset(struct phy *phy)
 {
 	struct lynx_28g_lane *lane = phy_get_drvdata(phy);
 	bool done;
 	int err;
 
-	if (lane->powered_up)
-		return 0;
-
 	/* Issue a reset request on the lane */
 	lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_RST_REQ,
 			  LNaTRSTCTL_RST_REQ);
@@ -750,9 +761,64 @@ static int lynx_28g_power_on(struct phy *phy)
 	if (err) {
 		dev_err(&phy->dev, "Lane %c reset failed: %pe\n",
 			'A' + lane->id, ERR_PTR(err));
+	}
+
+	return err;
+}
+
+static int lynx_28g_power_off(struct phy *phy)
+{
+	struct lynx_28g_lane *lane = phy_get_drvdata(phy);
+	bool done;
+	int err;
+
+	if (!lane->powered_up)
+		return 0;
+
+	/* Issue a stop request */
+	lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_STP_REQ,
+			  LNaTRSTCTL_STP_REQ);
+	lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_STP_REQ,
+			  LNaRRSTCTL_STP_REQ);
+
+	/* Wait until the stop process is complete */
+	err = read_poll_timeout(lynx_28g_lane_stop_done, done, done,
+				LYNX_28G_LANE_STOP_SLEEP_US,
+				LYNX_28G_LANE_STOP_TIMEOUT_US,
+				false, lane);
+	if (err) {
+		dev_err(&phy->dev, "Lane %c stop failed: %pe\n",
+			'A' + lane->id, ERR_PTR(err));
 		return err;
 	}
 
+	/* Power down the RX and TX portions of the lane */
+	lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_DIS,
+			  LNaRRSTCTL_DIS);
+	lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_DIS,
+			  LNaTRSTCTL_DIS);
+
+	lane->powered_up = false;
+
+	return 0;
+}
+
+static int lynx_28g_power_on(struct phy *phy)
+{
+	struct lynx_28g_lane *lane = phy_get_drvdata(phy);
+	int err;
+
+	if (lane->powered_up)
+		return 0;
+
+	/* Power up the RX and TX portions of the lane */
+	lynx_28g_lane_rmw(lane, LNaRRSTCTL, 0, LNaRRSTCTL_DIS);
+	lynx_28g_lane_rmw(lane, LNaTRSTCTL, 0, LNaTRSTCTL_DIS);
+
+	err = lynx_28g_lane_reset(phy);
+	if (err)
+		return err;
+
 	lane->powered_up = true;
 
 	return 0;
@@ -1167,7 +1233,7 @@ static int lynx_28g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
 	 * the reconfiguration is being done.
 	 */
 	if (powered_up) {
-		err = lynx_28g_power_off(phy);
+		err = lynx_28g_lane_halt(phy);
 		if (err)
 			return err;
 	}
@@ -1183,12 +1249,13 @@ static int lynx_28g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
 	lane->mode = lane_mode;
 
 out:
+	/* Reset the lane if necessary */
 	if (powered_up) {
-		int err2 = lynx_28g_power_on(phy);
+		int err2 = lynx_28g_lane_reset(phy);
 		/*
 		 * Don't overwrite a failed protocol converter disable error
-		 * code with a successful lane power on error code, but
-		 * propagate a failed lane power on error.
+		 * code with a successful lane reset error code, but propagate
+		 * a failed lane reset error.
 		 */
 		if (!err)
 			err = err2;
-- 
2.34.1


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

* [PATCH phy 8/8] phy: lynx-28g: implement phy_exit() operation
  2026-01-14 15:21 [PATCH phy 0/8] Lynx 28G improvements part 2 Vladimir Oltean
                   ` (6 preceding siblings ...)
  2026-01-14 15:21 ` [PATCH phy 7/8] phy: lynx-28g: truly power the lanes up or down Vladimir Oltean
@ 2026-01-14 15:21 ` Vladimir Oltean
  2026-02-02 15:03 ` [PATCH phy 0/8] Lynx 28G improvements part 2 Vladimir Oltean
  8 siblings, 0 replies; 11+ messages in thread
From: Vladimir Oltean @ 2026-01-14 15:21 UTC (permalink / raw)
  To: linux-phy
  Cc: netdev, Ioana Ciornei, Vinod Koul, Kishon Vijay Abraham I,
	Neil Armstrong, Josua Mayer, linux-kernel

Managed lanes are supposed to have power management through
phy_power_on() and phy_power_off().

Unmanaged lanes are supposed to be always powered on, because they might
have a consumer which doesn't use this SerDes driver, and we don't want
to break it.

A lane is initially unmanaged, and becomes managed when phy_init() is
called on it.

It is normal for consumer drivers to call both phy_init() and
phy_exit(), in a balanced way. This ensures the phy->init_count from the
phy core is brought back to zero, for example during -EPROBE_DEFER in
the consumer, the lane temporarily becomes unmanaged and then managed
again.

Given the above requirement for consumers, it also imposes a requirement
for the SerDes driver to implement the exit() operation. Otherwise, a
balanced set of phy_init() and phy_exit() calls from the consumer will
effectively result in multiple lynx_28g_init() calls as seen by the
SerDes and nothing else. That actually doesn't work - the driver can't
power down a SerDes lane which is actually powered down, so such a call
sequence would hang the kernel.

No consumer driver currently uses phy_exit(), so the above problem does
not yet trigger, but in preparation for its introduction without any
regressions, it is necessary to add lynx_28g_exit() as the mirror of
lynx_28g_init().

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
part 1 -> part 2: none

Patch last made its appearance in v3 from part 1:
https://lore.kernel.org/linux-phy/20250926180505.760089-18-vladimir.oltean@nxp.com/

(old) part 1 change log:

v2->v3: propagate the potential -ETIMEDOUT error code from
        lynx_28g_power_on() to the caller
v1->v2: slight commit message reword

 drivers/phy/freescale/phy-fsl-lynx-28g.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 4c20d5d42983..f1d0e0f29fcf 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -1296,8 +1296,23 @@ static int lynx_28g_init(struct phy *phy)
 	return lynx_28g_power_off(phy);
 }
 
+static int lynx_28g_exit(struct phy *phy)
+{
+	struct lynx_28g_lane *lane = phy_get_drvdata(phy);
+
+	/* The lane returns to the state where it isn't managed by the
+	 * consumer, so we must treat is as if it isn't initialized, and always
+	 * powered on.
+	 */
+	lane->init = false;
+	lane->powered_up = false;
+
+	return lynx_28g_power_on(phy);
+}
+
 static const struct phy_ops lynx_28g_ops = {
 	.init		= lynx_28g_init,
+	.exit		= lynx_28g_exit,
 	.power_on	= lynx_28g_power_on,
 	.power_off	= lynx_28g_power_off,
 	.set_mode	= lynx_28g_set_mode,
-- 
2.34.1


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

* Re: [PATCH phy 2/8] dt-bindings: phy: lynx-28g: add compatible strings per SerDes and instantiation
  2026-01-14 15:21 ` [PATCH phy 2/8] dt-bindings: phy: lynx-28g: add compatible strings per SerDes and instantiation Vladimir Oltean
@ 2026-01-15 19:52   ` Rob Herring (Arm)
  0 siblings, 0 replies; 11+ messages in thread
From: Rob Herring (Arm) @ 2026-01-15 19:52 UTC (permalink / raw)
  To: Vladimir Oltean
  Cc: netdev, Kishon Vijay Abraham I, Vinod Koul, Krzysztof Kozlowski,
	linux-phy, Neil Armstrong, Josua Mayer, Conor Dooley,
	linux-kernel, devicetree, Ioana Ciornei


On Wed, 14 Jan 2026 17:21:05 +0200, Vladimir Oltean wrote:
> The 28G Lynx SerDes is instantiated 3 times in the NXP LX2160A SoC and
> twice in the NXP LX2162A. All these instances share the same register
> map, but the number of lanes and the protocols supported by each lane
> differs in a way that isn't detectable by the programming model.
> 
> Going by the generic "fsl,lynx-28g" compatible string and expecting all
> SerDes instantiations to use it was a mistake that needs to be fixed.
> 
> The two major options considered are
> (a) encode the SoC and the SerDes instance in the compatible string,
>     everything else is the responsibility of the driver to derive based
>     on this sufficient information
> (b) add sufficient device tree properties to describe the per-lane
>     differences, as well as the different lane count
> 
> Another important consideration is that any decision made here should
> be consistent with the decisions taken for the yet-to-be-introduced
> 10G Lynx SerDes (older generation for older SoCs), because of how
> similar they are.
> 
> I've seen option (b) at play in this unmerged patch set for the 10G Lynx
> here, and I didn't like it:
> https://lore.kernel.org/linux-phy/20230413160607.4128315-3-sean.anderson@seco.com/
> 
> This is because there, we have a higher degree of variability in the
> PCCR register values that need to be written per protocol. This makes
> that approach more drawn-out and more prone to errors, compared to (a)
> which is more succinct and obviously correct.
> 
> So I've chosen option (a) through elimination, and this also reflects
> how the SoC reference manual provides different tables with protocol
> combinations for each SerDes. NXP clearly documents these as not
> identical, and refers to them as such (SerDes 1, 2, etc).
> 
> The per-SoC compatible string is prepended to the "fsl,lynx-28g" generic
> compatible, which is left there for compatibility with old kernels. An
> exception would be LX2160A SerDes #3, which at the time of writing is
> not described in fsl-lx2160a.dtsi, and is a non-networking SerDes, so
> the existing Linux driver is useless for it. So there is no practical
> reason to put the "fsl,lynx-28g" fallback for "fsl,lx2160a-serdes3".
> 
> The specific compatible strings give us the opportunity to express more
> constraints in the schema that we weren't able to express before:
> - We allow #phy-cells in the top-level SerDes node only for
>   compatibility with old kernels that don't know how to translate
>   "phys = <&serdes_1_lane_a>" to a PHY. We don't need that feature for
>   the not-yet-introduced LX2160A SerDes #3, so make the presence of
>   #phy-cells at the top level be dependent on the presence of the
>   "fsl,lynx-28g" fallback compatible.
> - The modernization of the compatible string should come together with
>   per-lane OF nodes.
> - LX2162A SerDes 1 has fewer lanes than the others, and trying to use
>   lanes 0-3 would be a mistake that could be caught by the schema.
> 
> Cc: Rob Herring <robh@kernel.org>
> Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
> Cc: Conor Dooley <conor+dt@kernel.org>
> Cc: devicetree@vger.kernel.org
> Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
> ---
> part 1 -> part 2:
> - drop everything having to do with constraints (on #phy-cells,
>   #address-cells, #size-cells) based on new compatible strings.
> 
> Patch made its last appearance in v4 from part 1:
> https://lore.kernel.org/linux-phy/20251110092241.1306838-16-vladimir.oltean@nxp.com/
> 
> v3->v4:
> - OF nodes per lane broken out as a separate "[PATCH v4 phy 01/16]
>   dt-bindings: phy: lynx-28g: permit lane OF PHY providers"
> - rewritten commit message
> - s|"^phy@[0-9a-f]+$"|"^phy@[0-7]$"|g in patternProperties
> - define "#address-cells" and "#size-cells" as part of common
>   properties, only leave the part which marks them required in the allOf
>   constraints area
> v2->v3:
> - re-add "fsl,lynx-28g" as fallback compatible, and #phy-cells = <1> in
>   top-level "serdes" node
> - drop useless description texts
> - fix text formatting
> - schema is more lax to allow overlaying old and new required properties
> v1->v2:
> - drop the usage of "fsl,lynx-28g" as a fallback compatible
> - mark "fsl,lynx-28g" as deprecated
> - implement Josua's request for per-lane OF nodes for the new compatible
>   strings
> 
>  .../devicetree/bindings/phy/fsl,lynx-28g.yaml | 33 +++++++++++++++++--
>  1 file changed, 30 insertions(+), 3 deletions(-)
> 

Reviewed-by: Rob Herring (Arm) <robh@kernel.org>


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

* Re: [PATCH phy 0/8] Lynx 28G improvements part 2
  2026-01-14 15:21 [PATCH phy 0/8] Lynx 28G improvements part 2 Vladimir Oltean
                   ` (7 preceding siblings ...)
  2026-01-14 15:21 ` [PATCH phy 8/8] phy: lynx-28g: implement phy_exit() operation Vladimir Oltean
@ 2026-02-02 15:03 ` Vladimir Oltean
  8 siblings, 0 replies; 11+ messages in thread
From: Vladimir Oltean @ 2026-02-02 15:03 UTC (permalink / raw)
  To: linux-phy, Vinod Koul, Neil Armstrong
  Cc: netdev, Ioana Ciornei, Kishon Vijay Abraham I, Josua Mayer,
	linux-kernel

Hello PHY maintainers,

On Wed, Jan 14, 2026 at 05:21:03PM +0200, Vladimir Oltean wrote:
> This contains a number of changes deferred from part 1 (patches 1-6):
> https://lore.kernel.org/linux-phy/20251125114847.804961-1-vladimir.oltean@nxp.com/
> 
> They are reworked in ways detailed in their individual change logs.
> 
> I have a special request to linux-phy maintainers: after merging, please
> provide a stable branch/tag of this plus part 1, that can be pulled into
> netdev. It is needed because phy_exit() calls from consumers would
> compile but would cause a functionally broken link, so we need a linear
> git history to avoid (temporary) regressions.

Given how close to the merge window we are, I am dropping my special
request for a stable tag. But can you please take a look at this until
the merge window opens? It will simplify further development to have it
for v6.20 (or whatever next release number will be).

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

end of thread, other threads:[~2026-02-02 15:03 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-14 15:21 [PATCH phy 0/8] Lynx 28G improvements part 2 Vladimir Oltean
2026-01-14 15:21 ` [PATCH phy 1/8] phy: lynx-28g: skip CDR lock workaround for lanes disabled in the device tree Vladimir Oltean
2026-01-14 15:21 ` [PATCH phy 2/8] dt-bindings: phy: lynx-28g: add compatible strings per SerDes and instantiation Vladimir Oltean
2026-01-15 19:52   ` Rob Herring (Arm)
2026-01-14 15:21 ` [PATCH phy 3/8] dt-bindings: phy: lynx-28g: add constraint on LX2162A lane indices Vladimir Oltean
2026-01-14 15:21 ` [PATCH phy 4/8] phy: lynx-28g: probe on per-SoC and per-instance compatible strings Vladimir Oltean
2026-01-14 15:21 ` [PATCH phy 5/8] phy: lynx-28g: add support for 25GBASER Vladimir Oltean
2026-01-14 15:21 ` [PATCH phy 6/8] phy: lynx-28g: use timeouts when waiting for lane halt and reset Vladimir Oltean
2026-01-14 15:21 ` [PATCH phy 7/8] phy: lynx-28g: truly power the lanes up or down Vladimir Oltean
2026-01-14 15:21 ` [PATCH phy 8/8] phy: lynx-28g: implement phy_exit() operation Vladimir Oltean
2026-02-02 15:03 ` [PATCH phy 0/8] Lynx 28G improvements part 2 Vladimir Oltean

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox