]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
phy: lynx-28g: probe on per-SoC and per-instance compatible strings
authorVladimir Oltean <vladimir.oltean@nxp.com>
Mon, 11 May 2026 15:00:22 +0000 (18:00 +0300)
committerVinod Koul <vkoul@kernel.org>
Thu, 14 May 2026 15:28:32 +0000 (20:58 +0530)
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).

For old device trees with just "fsl,lynx-28g", the only things that
change are:

- a probe time warning/encouragement to update the device tree. This is
  warranted by the fact that using "fsl,lynx-28g" may already provide
  incorrect behaviour (undetected absent 10GbE support on LX2160A
  SerDes 2 lanes 0-5). But we retain bug compatibility nonetheless.

- the feature set is frozen in time (e.g. no 25GbE). Since we cannot
  guarantee that this protocol will work on a lane, just err on the safe
  side and don't offer it (and require a device tree update to get it).

In terms of code, 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().

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Ioana Ciornei <ioana.ciornei@nxp.com>
Tested-by: Josua Mayer <josua@solid-run.com>
Link: https://patch.msgid.link/20260511150023.1903577-5-vladimir.oltean@nxp.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
drivers/phy/freescale/phy-fsl-lynx-28g.c

index 6d0c395d20e506c911a8e51cebbe8c2fc43e16f5..5eddc2723e782af135e9a15fcc869caced6d83c4 100644 (file)
@@ -446,9 +446,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
         */
@@ -513,11 +519,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;
@@ -783,6 +796,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)
 {
@@ -1035,7 +1129,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;
@@ -1047,7 +1140,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)
@@ -1083,14 +1176,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;
@@ -1183,7 +1275,7 @@ static void lynx_28g_cdr_lock_check(struct work_struct *work)
        u32 rrstctl;
        int err, 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;
@@ -1253,7 +1345,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;
@@ -1297,10 +1390,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);
@@ -1323,7 +1424,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;
@@ -1336,7 +1437,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;
@@ -1362,7 +1463,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);