]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
phy: lynx-28g: support individual lanes as OF PHY providers
authorVladimir Oltean <vladimir.oltean@nxp.com>
Tue, 25 Nov 2025 11:48:35 +0000 (13:48 +0200)
committerVinod Koul <vkoul@kernel.org>
Tue, 23 Dec 2025 17:41:06 +0000 (23:11 +0530)
Currently, the bindings of this multi-lane SerDes are such that
consumers specify the lane index in the PHY cell, and the lane itself is
not described in the device tree.

It is desirable to describe individual Lynx 28G SerDes lanes in the
device tree, in order to be able to customize electrical properties such
as those in Documentation/devicetree/bindings/phy/transmit-amplitude.yaml
(or others).

If each lane may have an OF node, it appears natural for consumers to
have their "phys" phandle point to that OF node.

The problem is that transitioning between one format and another is a
breaking change. The bindings of the 28G Lynx SerDes can themselves be
extended in a backward-compatible way, but the consumers cannot be
modified without breaking them.

Namely, if we have:

&mac {
phys = <&serdes1 0>;
};

we cannot update the device tree to:

&mac {
phys = <&serdes1_lane_0>;
};

because old kernels cannot resolve this phandle to a valid PHY.

The proposal here is to keep tolerating existing device trees, which are
not supposed to be changed, but modify lynx_28g_xlate() to also resolve
the new format with #phy-cells = <0> in the lanes.

This way we support 3 modes:
- Legacy device trees, no OF nodes for lanes
- New device trees, OF nodes for lanes and "phys" phandle points towards
  them
- Hybrid device trees, OF nodes for lanes (to describe electrical
  parameters), but "phys" phandle points towards the SerDes top-level
  provider

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Link: https://patch.msgid.link/20251125114847.804961-4-vladimir.oltean@nxp.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
drivers/phy/freescale/phy-fsl-lynx-28g.c

index 901240bbcade43a3b33deeaf791e012cb7745325..61a992ff274f2956fed28b42b176b2e5a1e7200d 100644 (file)
@@ -571,7 +571,14 @@ static struct phy *lynx_28g_xlate(struct device *dev,
                                  const struct of_phandle_args *args)
 {
        struct lynx_28g_priv *priv = dev_get_drvdata(dev);
-       int idx = args->args[0];
+       int idx;
+
+       if (args->args_count == 0)
+               return of_phy_simple_xlate(dev, args);
+       else if (args->args_count != 1)
+               return ERR_PTR(-ENODEV);
+
+       idx = args->args[0];
 
        if (WARN_ON(idx >= LYNX_28G_NUM_LANE))
                return ERR_PTR(-EINVAL);
@@ -605,6 +612,7 @@ static int lynx_28g_probe(struct platform_device *pdev)
        struct device *dev = &pdev->dev;
        struct phy_provider *provider;
        struct lynx_28g_priv *priv;
+       struct device_node *dn;
        int err;
 
        priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
@@ -618,10 +626,41 @@ static int lynx_28g_probe(struct platform_device *pdev)
 
        lynx_28g_pll_read_configuration(priv);
 
-       for (int i = 0; i < LYNX_28G_NUM_LANE; i++) {
-               err = lynx_28g_probe_lane(priv, i, NULL);
-               if (err)
-                       return err;
+       dn = dev_of_node(dev);
+       if (of_get_child_count(dn)) {
+               struct device_node *child;
+
+               for_each_available_child_of_node(dn, child) {
+                       u32 reg;
+
+                       /* PHY subnode name must be 'phy'. */
+                       if (!(of_node_name_eq(child, "phy")))
+                               continue;
+
+                       if (of_property_read_u32(child, "reg", &reg)) {
+                               dev_err(dev, "No \"reg\" property for %pOF\n", child);
+                               of_node_put(child);
+                               return -EINVAL;
+                       }
+
+                       if (reg >= LYNX_28G_NUM_LANE) {
+                               dev_err(dev, "\"reg\" property out of range for %pOF\n", child);
+                               of_node_put(child);
+                               return -EINVAL;
+                       }
+
+                       err = lynx_28g_probe_lane(priv, reg, child);
+                       if (err) {
+                               of_node_put(child);
+                               return err;
+                       }
+               }
+       } else {
+               for (int i = 0; i < LYNX_28G_NUM_LANE; i++) {
+                       err = lynx_28g_probe_lane(priv, i, NULL);
+                       if (err)
+                               return err;
+               }
        }
 
        dev_set_drvdata(dev, priv);