Contributors: 4
Author Tokens Token Proportion Commits Commit Proportion
Vladimir Oltean 1795 86.38% 18 81.82%
Ioana Ciornei 274 13.19% 2 9.09%
Josua Mayer 8 0.38% 1 4.55%
Krzysztof Kozlowski 1 0.05% 1 4.55%
Total 2078 22


// SPDX-License-Identifier: GPL-2.0+
/* Copyright 2025-2026 NXP */

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

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

const char *lynx_lane_mode_str(enum lynx_lane_mode lane_mode)
{
	switch (lane_mode) {
	case LANE_MODE_1000BASEX_SGMII:
		return "1000Base-X/SGMII";
	case LANE_MODE_2500BASEX:
		return "2500Base-X";
	case LANE_MODE_QSGMII:
		return "QSGMII";
	case LANE_MODE_10G_QXGMII:
		return "10G-QXGMII";
	case LANE_MODE_10GBASER:
		return "10GBase-R";
	case LANE_MODE_USXGMII:
		return "USXGMII";
	case LANE_MODE_25GBASER:
		return "25GBase-R";
	default:
		return "unknown";
	}
}
EXPORT_SYMBOL_NS_GPL(lynx_lane_mode_str, "PHY_FSL_LYNX");

enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf)
{
	switch (intf) {
	case PHY_INTERFACE_MODE_SGMII:
	case PHY_INTERFACE_MODE_1000BASEX:
		return LANE_MODE_1000BASEX_SGMII;
	case PHY_INTERFACE_MODE_2500BASEX:
		return LANE_MODE_2500BASEX;
	case PHY_INTERFACE_MODE_QSGMII:
		return LANE_MODE_QSGMII;
	case PHY_INTERFACE_MODE_10G_QXGMII:
		return LANE_MODE_10G_QXGMII;
	case PHY_INTERFACE_MODE_10GBASER:
		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;
	}
}
EXPORT_SYMBOL_NS_GPL(phy_interface_to_lane_mode, "PHY_FSL_LYNX");

/* By default, assume that if we know how to get the PCCR register and
 * protocol converter for a lane, that protocol is supported.
 */
static bool lynx_lane_supports_mode_default(struct lynx_lane *lane,
					    enum lynx_lane_mode mode)
{
	struct lynx_priv *priv = lane->priv;
	struct lynx_pccr pccr;

	if (!priv->info->get_pccr || !priv->info->get_pcvt_offset)
		return false;

	if (priv->info->get_pccr(mode, lane->id, &pccr) < 0)
		return false;

	if (priv->info->get_pcvt_offset(lane->id, mode) < 0)
		return false;

	return true;
}

/* 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.
 */
bool lynx_lane_supports_mode(struct lynx_lane *lane, enum lynx_lane_mode mode)
{
	struct lynx_priv *priv = lane->priv;
	int i;

	if (priv->info->lane_supports_mode) {
		if (!priv->info->lane_supports_mode(lane->id, mode))
			return false;
	} else if (!lynx_lane_supports_mode_default(lane, mode)) {
		return false;
	}

	for (i = 0; i < LYNX_NUM_PLL; i++) {
		if (!priv->pll[i].enabled)
			continue;

		if (test_bit(mode, priv->pll[i].supported))
			return true;
	}

	return false;
}
EXPORT_SYMBOL_NS_GPL(lynx_lane_supports_mode, "PHY_FSL_LYNX");

/* The quad protocols are fixed because the lane has multiple consumers, and
 * one phy_set_mode_ext() affects the other consumers as well. We have no use
 * case for dynamic protocol changing here, so disallow it.
 */
static enum lynx_lane_mode lynx_fixed_protocols[] = {
	LANE_MODE_QSGMII,
	LANE_MODE_10G_QXGMII,
};

static bool lynx_lane_restrict_fixed_mode_change(struct lynx_lane *lane,
						 enum lynx_lane_mode new)
{
	enum lynx_lane_mode curr = lane->mode;

	for (int i = 0; i < ARRAY_SIZE(lynx_fixed_protocols); i++)
		if ((curr == lynx_fixed_protocols[i] ||
		     new == lynx_fixed_protocols[i]) &&
		     curr != new)
			return true;

	return false;
}

/* Translate the mode/submode from phy_validate() and phy_set_mode_ext() to a
 * lane_mode and return 0 if it is supported and we can transition to it from
 * the current lane mode, or return negative error otherwise.
 */
int lynx_phy_mode_to_lane_mode(struct phy *phy, enum phy_mode mode,
			       int submode, enum lynx_lane_mode *lane_mode)
{
	struct lynx_lane *lane = phy_get_drvdata(phy);
	enum lynx_lane_mode tmp_lane_mode;

	/* The protocol configuration tables are incomplete for full lane
	 * reconfiguration from an arbitrary protocol.
	 */
	if (lane->mode == LANE_MODE_UNKNOWN)
		return -EINVAL;

	if (mode != PHY_MODE_ETHERNET)
		return -EINVAL;

	tmp_lane_mode = phy_interface_to_lane_mode(submode);
	if (!lynx_lane_supports_mode(lane, tmp_lane_mode))
		return -EINVAL;

	if (lynx_lane_restrict_fixed_mode_change(lane, tmp_lane_mode))
		return -EINVAL;

	if (lane_mode)
		*lane_mode = tmp_lane_mode;

	return 0;
}
EXPORT_SYMBOL_NS_GPL(lynx_phy_mode_to_lane_mode, "PHY_FSL_LYNX");

struct lynx_pll *lynx_pll_get(struct lynx_priv *priv, enum lynx_lane_mode mode)
{
	struct lynx_pll *pll;
	int i;

	for (i = 0; i < LYNX_NUM_PLL; i++) {
		pll = &priv->pll[i];

		if (!pll->enabled)
			continue;

		if (test_bit(mode, pll->supported))
			return pll;
	}

	/* no pll supports requested mode, either caller forgot to check
	 * lynx_lane_supports_mode(), or this is a bug.
	 */
	dev_WARN_ONCE(priv->dev, 1, "no pll for lane mode %s\n",
		      lynx_lane_mode_str(mode));
	return NULL;
}
EXPORT_SYMBOL_NS_GPL(lynx_pll_get, "PHY_FSL_LYNX");

int lynx_pccr_read(struct lynx_lane *lane, enum lynx_lane_mode mode, u32 *val)
{
	struct lynx_priv *priv = lane->priv;
	struct lynx_pccr pccr;
	u32 tmp;
	int err;

	err = priv->info->get_pccr(mode, lane->id, &pccr);
	if (err)
		return err;

	tmp = lynx_read(priv, pccr.offset);
	*val = (tmp >> pccr.shift) & GENMASK(pccr.width - 1, 0);

	return 0;
}
EXPORT_SYMBOL_NS_GPL(lynx_pccr_read, "PHY_FSL_LYNX");

int lynx_pccr_write(struct lynx_lane *lane, enum lynx_lane_mode mode, u32 val)
{
	struct lynx_priv *priv = lane->priv;
	struct lynx_pccr pccr;
	u32 old, tmp, mask;
	int err;

	err = priv->info->get_pccr(mode, lane->id, &pccr);
	if (err)
		return err;

	old = lynx_read(priv, pccr.offset);
	mask = GENMASK(pccr.width - 1, 0) << pccr.shift;
	tmp = (old & ~mask) | (val << pccr.shift);
	lynx_write(priv, pccr.offset, tmp);

	dev_dbg(&lane->phy->dev, "PCCR@0x%x: 0x%x -> 0x%x\n",
		pccr.offset, old, tmp);

	return 0;
}
EXPORT_SYMBOL_NS_GPL(lynx_pccr_write, "PHY_FSL_LYNX");

int lynx_pcvt_read(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr,
		   u32 *val)
{
	struct lynx_priv *priv = lane->priv;
	int offset;

	offset = priv->info->get_pcvt_offset(lane->id, mode);
	if (offset < 0)
		return offset;

	*val = lynx_read(priv, offset + cr);

	return 0;
}
EXPORT_SYMBOL_NS_GPL(lynx_pcvt_read, "PHY_FSL_LYNX");

int lynx_pcvt_write(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr,
		    u32 val)
{
	struct lynx_priv *priv = lane->priv;
	int offset;

	offset = priv->info->get_pcvt_offset(lane->id, mode);
	if (offset < 0)
		return offset;

	lynx_write(priv, offset + cr, val);

	return 0;
}
EXPORT_SYMBOL_NS_GPL(lynx_pcvt_write, "PHY_FSL_LYNX");

int lynx_pcvt_rmw(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr,
		  u32 val, u32 mask)
{
	int err;
	u32 tmp;

	err = lynx_pcvt_read(lane, mode, cr, &tmp);
	if (err)
		return err;

	tmp &= ~mask;
	tmp |= val;

	return lynx_pcvt_write(lane, mode, cr, tmp);
}
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;
	priv->big_endian = device_property_read_bool(dev, "big-endian");
	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");