phy: lynx-28g: common probe() and remove()

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@nxp.com>
Link: https://patch.msgid.link/20260610151952.2141019-11-vladimir.oltean@nxp.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
Vladimir Oltean 2026-06-10 18:19:46 +03:00 committed by Vinod Koul
parent b1c4d866ed
commit 719a2da392
3 changed files with 227 additions and 180 deletions

View File

@ -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_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 ?: ERR_PTR(-ENODEV);
}
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,112 +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);
if (!priv->info)
return -ENODEV;
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[] = {
@ -1357,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,

View File

@ -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,174 @@ 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 ?: ERR_PTR(-ENODEV);
}
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;
}
if (!info)
return -ENODEV;
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");

View File

@ -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);