[PATCH phy-next 09/13] phy: lynx-28g: common probe() and remove()

From: Vladimir Oltean

Date: Thu May 28 2026 - 13:32:43 EST


Factor the device-agnostic logic from lynx_28g_probe() and
lynx_28g_remove() into lynx_probe() and lynx_remove() inside
phy-fsl-lynx-core.c. These will be shared with the 10G Lynx driver.

Since the PLL configuration, lane configuration and CDR lock detection
procedure are going to be different, introduce lynx_info function
pointers so that this code remains in the 28G Lynx driver.

Signed-off-by: Vladimir Oltean <vladimir.oltean@xxxxxxx>
---
drivers/phy/freescale/phy-fsl-lynx-28g.c | 222 +++++-----------------
drivers/phy/freescale/phy-fsl-lynx-core.c | 167 ++++++++++++++++
drivers/phy/freescale/phy-fsl-lynx-core.h | 12 +-
3 files changed, 224 insertions(+), 177 deletions(-)

diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 31598f0042da..84e7a92c888d 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -12,7 +12,6 @@
#include "phy-fsl-lynx-core.h"

#define LYNX_28G_NUM_LANE 8
-#define LYNX_28G_NUM_PLL LYNX_NUM_PLL

/* SoC IP wrapper for protocol converters */
#define PCC8 0x10a0
@@ -781,6 +780,30 @@ static bool lynx_28g_compat_lane_supports_mode(int lane,
}
}

+static void lynx_28g_cdr_lock_check(struct lynx_lane *lane)
+{
+ u32 rrstctl;
+ int err;
+
+ rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
+ if (!!(rrstctl & LNaRRSTCTL_CDR_LOCK))
+ return;
+
+ lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_RST_REQ,
+ LNaRRSTCTL_RST_REQ);
+
+ 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));
+ }
+}
+
static void lynx_28g_lane_remap_pll(struct lynx_28g_lane *lane,
enum lynx_lane_mode lane_mode)
{
@@ -1088,50 +1111,6 @@ static void lynx_28g_pll_read_configuration(struct lynx_pll *pll)
}
}

-#define work_to_lynx(w) container_of((w), struct lynx_28g_priv, cdr_check.work)
-
-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 err, i;
-
- for (i = priv->info->first_lane; i < LYNX_28G_NUM_LANE; i++) {
- lane = &priv->lane[i];
- if (!lane->phy)
- continue;
-
- mutex_lock(&lane->phy->mutex);
-
- if (!lane->init || !lane->powered_up) {
- mutex_unlock(&lane->phy->mutex);
- continue;
- }
-
- rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
- if (!(rrstctl & LNaRRSTCTL_CDR_LOCK)) {
- lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_RST_REQ,
- LNaRRSTCTL_RST_REQ);
-
- 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);
- }
- queue_delayed_work(system_power_efficient_wq, &priv->cdr_check,
- msecs_to_jiffies(1000));
-}
-
static void lynx_28g_lane_read_configuration(struct lynx_28g_lane *lane)
{
u32 pccr, pss, protocol;
@@ -1157,49 +1136,13 @@ static void lynx_28g_lane_read_configuration(struct lynx_28g_lane *lane)
}
}

-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;
-
- 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 ||
- idx < priv->info->first_lane))
- return ERR_PTR(-EINVAL);
-
- return priv->lane[idx].phy;
-}
-
-static int lynx_28g_probe_lane(struct lynx_28g_priv *priv, int id,
- struct device_node *dn)
-{
- struct lynx_28g_lane *lane = &priv->lane[id];
- struct phy *phy;
-
- phy = devm_phy_create(priv->dev, dn, &lynx_28g_ops);
- if (IS_ERR(phy))
- return PTR_ERR(phy);
-
- lane->priv = priv;
- lane->phy = phy;
- lane->id = id;
- phy_set_drvdata(phy, lane);
- lynx_28g_lane_read_configuration(lane);
-
- return 0;
-}
-
static const struct lynx_info lynx_info_compat = {
.get_pccr = lynx_28g_get_pccr,
.get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lynx_28g_compat_lane_supports_mode,
+ .pll_read_configuration = lynx_28g_pll_read_configuration,
+ .lane_read_configuration = lynx_28g_lane_read_configuration,
+ .cdr_lock_check = lynx_28g_cdr_lock_check,
.num_lanes = LYNX_28G_NUM_LANE,
};

@@ -1207,6 +1150,9 @@ static const struct lynx_info lynx_info_lx2160a_serdes1 = {
.get_pccr = lynx_28g_get_pccr,
.get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lx2160a_serdes1_lane_supports_mode,
+ .pll_read_configuration = lynx_28g_pll_read_configuration,
+ .lane_read_configuration = lynx_28g_lane_read_configuration,
+ .cdr_lock_check = lynx_28g_cdr_lock_check,
.num_lanes = LYNX_28G_NUM_LANE,
};

@@ -1214,6 +1160,9 @@ static const struct lynx_info lynx_info_lx2160a_serdes2 = {
.get_pccr = lynx_28g_get_pccr,
.get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lx2160a_serdes2_lane_supports_mode,
+ .pll_read_configuration = lynx_28g_pll_read_configuration,
+ .lane_read_configuration = lynx_28g_lane_read_configuration,
+ .cdr_lock_check = lynx_28g_cdr_lock_check,
.num_lanes = LYNX_28G_NUM_LANE,
};

@@ -1221,6 +1170,9 @@ static const struct lynx_info lynx_info_lx2160a_serdes3 = {
.get_pccr = lynx_28g_get_pccr,
.get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lx2160a_serdes3_lane_supports_mode,
+ .pll_read_configuration = lynx_28g_pll_read_configuration,
+ .lane_read_configuration = lynx_28g_lane_read_configuration,
+ .cdr_lock_check = lynx_28g_cdr_lock_check,
.num_lanes = LYNX_28G_NUM_LANE,
};

@@ -1228,6 +1180,9 @@ static const struct lynx_info lynx_info_lx2162a_serdes1 = {
.get_pccr = lynx_28g_get_pccr,
.get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lx2162a_serdes1_lane_supports_mode,
+ .pll_read_configuration = lynx_28g_pll_read_configuration,
+ .lane_read_configuration = lynx_28g_lane_read_configuration,
+ .cdr_lock_check = lynx_28g_cdr_lock_check,
.first_lane = 4,
.num_lanes = LYNX_28G_NUM_LANE,
};
@@ -1236,109 +1191,26 @@ static const struct lynx_info lynx_info_lx2162a_serdes2 = {
.get_pccr = lynx_28g_get_pccr,
.get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lx2162a_serdes2_lane_supports_mode,
+ .pll_read_configuration = lynx_28g_pll_read_configuration,
+ .lane_read_configuration = lynx_28g_lane_read_configuration,
+ .cdr_lock_check = lynx_28g_cdr_lock_check,
.num_lanes = LYNX_28G_NUM_LANE,
};

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;
-
- dn = dev_of_node(dev);
- if (!dn) {
- dev_err(dev, "Device requires an OF node\n");
- return -EINVAL;
- }
-
- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- 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);
+ const struct lynx_info *info;

/*
* 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)
+ info = of_device_get_match_data(dev);
+ if (info == &lynx_info_compat)
dev_warn(dev, "Please update device tree to use per-device compatible strings\n");

- priv->lane = devm_kcalloc(dev, priv->info->num_lanes,
- sizeof(*priv->lane), GFP_KERNEL);
- if (!priv->lane)
- return -ENOMEM;
-
- priv->base = devm_platform_ioremap_resource(pdev, 0);
- if (IS_ERR(priv->base))
- return PTR_ERR(priv->base);
-
- for (int i = 0; i < LYNX_28G_NUM_PLL; i++) {
- struct lynx_28g_pll *pll = &priv->pll[i];
-
- pll->priv = priv;
- pll->id = i;
- lynx_28g_pll_read_configuration(pll);
- }
-
- 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 < 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;
- }
-
- err = lynx_28g_probe_lane(priv, reg, child);
- if (err) {
- of_node_put(child);
- return err;
- }
- }
- } else {
- 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;
- }
- }
-
- provider = devm_of_phy_provider_register(dev, lynx_28g_xlate);
- if (IS_ERR(provider))
- return PTR_ERR(provider);
-
- queue_delayed_work(system_power_efficient_wq, &priv->cdr_check,
- msecs_to_jiffies(1000));
-
- return 0;
-}
-
-static void lynx_28g_remove(struct platform_device *pdev)
-{
- struct device *dev = &pdev->dev;
- struct lynx_28g_priv *priv = dev_get_drvdata(dev);
-
- cancel_delayed_work_sync(&priv->cdr_check);
+ return lynx_probe(pdev, info, &lynx_28g_ops);
}

static const struct of_device_id lynx_28g_of_match_table[] = {
@@ -1354,7 +1226,7 @@ MODULE_DEVICE_TABLE(of, lynx_28g_of_match_table);

static struct platform_driver lynx_28g_driver = {
.probe = lynx_28g_probe,
- .remove = lynx_28g_remove,
+ .remove = lynx_remove,
.driver = {
.name = "lynx-28g",
.of_match_table = lynx_28g_of_match_table,
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.c b/drivers/phy/freescale/phy-fsl-lynx-core.c
index 802e32dc6dca..bd1cd78e80bb 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.c
@@ -2,6 +2,7 @@
/* Copyright 2025-2026 NXP */

#include <linux/module.h>
+#include <linux/platform_device.h>

#include "phy-fsl-lynx-core.h"

@@ -202,5 +203,171 @@ int lynx_pcvt_rmw(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr,
}
EXPORT_SYMBOL_NS_GPL(lynx_pcvt_rmw, "PHY_FSL_LYNX");

+#define work_to_lynx(w) container_of((w), struct lynx_priv, cdr_check.work)
+
+static void lynx_cdr_lock_check(struct work_struct *work)
+{
+ struct lynx_priv *priv = work_to_lynx(work);
+ struct lynx_lane *lane;
+
+ for (int i = priv->info->first_lane; i < priv->info->num_lanes; i++) {
+ lane = &priv->lane[i];
+ if (!lane->phy)
+ continue;
+
+ mutex_lock(&lane->phy->mutex);
+
+ if (!lane->init || !lane->powered_up) {
+ mutex_unlock(&lane->phy->mutex);
+ continue;
+ }
+
+ priv->info->cdr_lock_check(lane);
+
+ mutex_unlock(&lane->phy->mutex);
+ }
+
+ queue_delayed_work(system_power_efficient_wq, &priv->cdr_check,
+ msecs_to_jiffies(1000));
+}
+
+static struct phy *lynx_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ struct lynx_priv *priv = dev_get_drvdata(dev);
+ 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 >= priv->info->num_lanes ||
+ idx < priv->info->first_lane))
+ return ERR_PTR(-EINVAL);
+
+ return priv->lane[idx].phy;
+}
+
+static int lynx_probe_lane(struct lynx_priv *priv, int id,
+ struct device_node *dn,
+ const struct phy_ops *phy_ops)
+{
+ struct lynx_lane *lane = &priv->lane[id];
+ struct phy *phy;
+
+ phy = devm_phy_create(priv->dev, dn, phy_ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ lane->priv = priv;
+ lane->phy = phy;
+ lane->id = id;
+ phy_set_drvdata(phy, lane);
+ priv->info->lane_read_configuration(lane);
+
+ return 0;
+}
+
+int lynx_probe(struct platform_device *pdev, const struct lynx_info *info,
+ const struct phy_ops *phy_ops)
+{
+ struct device *dev = &pdev->dev;
+ struct phy_provider *provider;
+ struct device_node *dn;
+ struct lynx_priv *priv;
+ int err;
+
+ dn = dev_of_node(dev);
+ if (!dn) {
+ dev_err(dev, "Device requires an OF node\n");
+ return -EINVAL;
+ }
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->info = info;
+ dev_set_drvdata(dev, priv);
+ spin_lock_init(&priv->pcc_lock);
+ INIT_DELAYED_WORK(&priv->cdr_check, lynx_cdr_lock_check);
+
+ priv->lane = devm_kcalloc(dev, priv->info->num_lanes,
+ sizeof(*priv->lane), GFP_KERNEL);
+ if (!priv->lane)
+ return -ENOMEM;
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ for (int i = 0; i < LYNX_NUM_PLL; i++) {
+ struct lynx_pll *pll = &priv->pll[i];
+
+ pll->priv = priv;
+ pll->id = i;
+ priv->info->pll_read_configuration(pll);
+ }
+
+ 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 < priv->info->first_lane || reg >= priv->info->num_lanes) {
+ dev_err(dev, "\"reg\" property out of range for %pOF\n", child);
+ of_node_put(child);
+ return -EINVAL;
+ }
+
+ err = lynx_probe_lane(priv, reg, child, phy_ops);
+ if (err) {
+ of_node_put(child);
+ return err;
+ }
+ }
+ } else {
+ for (int i = priv->info->first_lane; i < priv->info->num_lanes; i++) {
+ err = lynx_probe_lane(priv, i, NULL, phy_ops);
+ if (err)
+ return err;
+ }
+ }
+
+ provider = devm_of_phy_provider_register(dev, lynx_xlate);
+ if (IS_ERR(provider))
+ return PTR_ERR(provider);
+
+ queue_delayed_work(system_power_efficient_wq, &priv->cdr_check,
+ msecs_to_jiffies(1000));
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(lynx_probe, "PHY_FSL_LYNX");
+
+void lynx_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct lynx_priv *priv = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&priv->cdr_check);
+}
+EXPORT_SYMBOL_NS_GPL(lynx_remove, "PHY_FSL_LYNX");
+
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Freescale Lynx SerDes core functionality");
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.h b/drivers/phy/freescale/phy-fsl-lynx-core.h
index 5cd86c9543cb..e8b280cc9b38 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.h
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.h
@@ -10,14 +10,15 @@

#define LYNX_NUM_PLL 2

+struct lynx_priv;
+struct lynx_lane;
+
struct lynx_pccr {
int offset;
int width;
int shift;
};

-struct lynx_priv;
-
struct lynx_pll {
struct lynx_priv *priv;
int id;
@@ -42,6 +43,9 @@ struct lynx_info {
struct lynx_pccr *pccr);
int (*get_pcvt_offset)(int lane, enum lynx_lane_mode mode);
bool (*lane_supports_mode)(int lane, enum lynx_lane_mode mode);
+ void (*pll_read_configuration)(struct lynx_pll *pll);
+ void (*lane_read_configuration)(struct lynx_lane *lane);
+ void (*cdr_lock_check)(struct lynx_lane *lane);
int first_lane;
int num_lanes;
};
@@ -85,6 +89,10 @@ static inline void lynx_rmw(struct lynx_priv *priv, unsigned long off, u32 val,
#define lynx_pll_read(pll, reg) \
ioread32((pll)->priv->base + reg((pll)->id))

+int lynx_probe(struct platform_device *pdev, const struct lynx_info *info,
+ const struct phy_ops *phy_ops);
+void lynx_remove(struct platform_device *pdev);
+
const char *lynx_lane_mode_str(enum lynx_lane_mode lane_mode);
enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf);
bool lynx_lane_supports_mode(struct lynx_lane *lane, enum lynx_lane_mode mode);
--
2.34.1