Contributors: 6
Author Tokens Token Proportion Commits Commit Proportion
Vladimir Oltean 2987 57.99% 14 66.67%
Ioana Ciornei 2136 41.47% 3 14.29%
Josua Mayer 23 0.45% 1 4.76%
Thierry Reding 3 0.06% 1 4.76%
Uwe Kleine-König 1 0.02% 1 4.76%
Krzysztof Kozlowski 1 0.02% 1 4.76%
Total 5151 21


// SPDX-License-Identifier: GPL-2.0+
/* Copyright (c) 2021-2022 NXP. */

#include <linux/bitfield.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>

#define LYNX_28G_NUM_LANE			8
#define LYNX_28G_NUM_PLL			2

/* SoC IP wrapper for protocol converters */
#define PCC8					0x10a0
#define PCC8_SGMIIa_KX				BIT(3)
#define PCC8_SGMIIa_CFG				BIT(0)

#define PCCC					0x10b0
#define PCCC_SXGMIIn_XFI			BIT(3)
#define PCCC_SXGMIIn_CFG			BIT(0)

#define PCCD					0x10b4
#define PCCD_E25Gn_CFG				BIT(0)

#define PCCE					0x10b8
#define PCCE_E40Gn_LRV				BIT(3)
#define PCCE_E40Gn_CFG				BIT(0)
#define PCCE_E50Gn_LRV				BIT(3)
#define PCCE_E50GnCFG				BIT(0)
#define PCCE_E100Gn_LRV				BIT(3)
#define PCCE_E100Gn_CFG				BIT(0)

#define SGMII_CFG(id)				(28 - (id) * 4) /* Offset into PCC8 */
#define SXGMII_CFG(id)				(28 - (id) * 4) /* Offset into PCCC */
#define E25G_CFG(id)				(28 - (id) * 4) /* Offset into PCCD */
#define E40G_CFG(id)				(28 - (id) * 4) /* Offset into PCCE */
#define E50G_CFG(id)				(20 - (id) * 4) /* Offset into PCCE */
#define E100G_CFG(id)				(12 - (id) * 4) /* Offset into PCCE */

/* Per PLL registers */
#define PLLnRSTCTL(pll)				(0x400 + (pll) * 0x100 + 0x0)
#define PLLnRSTCTL_DIS(rstctl)			(((rstctl) & BIT(24)) >> 24)
#define PLLnRSTCTL_LOCK(rstctl)			(((rstctl) & BIT(23)) >> 23)

#define PLLnCR0(pll)				(0x400 + (pll) * 0x100 + 0x4)
#define PLLnCR0_REFCLK_SEL			GENMASK(20, 16)
#define PLLnCR0_REFCLK_SEL_100MHZ		0x0
#define PLLnCR0_REFCLK_SEL_125MHZ		0x1
#define PLLnCR0_REFCLK_SEL_156MHZ		0x2
#define PLLnCR0_REFCLK_SEL_150MHZ		0x3
#define PLLnCR0_REFCLK_SEL_161MHZ		0x4

#define PLLnCR1(pll)				(0x400 + (pll) * 0x100 + 0x8)
#define PLLnCR1_FRATE_SEL			GENMASK(28, 24)
#define PLLnCR1_FRATE_5G_10GVCO			0x0
#define PLLnCR1_FRATE_5G_25GVCO			0x10
#define PLLnCR1_FRATE_10G_20GVCO		0x6

/* Per SerDes lane registers */
/* Lane a General Control Register */
#define LNaGCR0(lane)				(0x800 + (lane) * 0x100 + 0x0)
#define LNaGCR0_PROTO_SEL			GENMASK(7, 3)
#define LNaGCR0_PROTO_SEL_SGMII			0x1
#define LNaGCR0_PROTO_SEL_XFI			0xa
#define LNaGCR0_IF_WIDTH			GENMASK(2, 0)
#define LNaGCR0_IF_WIDTH_10_BIT			0x0
#define LNaGCR0_IF_WIDTH_20_BIT			0x2

/* Lane a Tx Reset Control Register */
#define LNaTRSTCTL(lane)			(0x800 + (lane) * 0x100 + 0x20)
#define LNaTRSTCTL_HLT_REQ			BIT(27)
#define LNaTRSTCTL_RST_DONE			BIT(30)
#define LNaTRSTCTL_RST_REQ			BIT(31)

/* Lane a Tx General Control Register */
#define LNaTGCR0(lane)				(0x800 + (lane) * 0x100 + 0x24)
#define LNaTGCR0_USE_PLL			BIT(28)
#define LNaTGCR0_USE_PLLF			0x0
#define LNaTGCR0_USE_PLLS			0x1
#define LNaTGCR0_N_RATE				GENMASK(26, 24)
#define LNaTGCR0_N_RATE_FULL			0x0
#define LNaTGCR0_N_RATE_HALF			0x1
#define LNaTGCR0_N_RATE_QUARTER			0x2

#define LNaTECR0(lane)				(0x800 + (lane) * 0x100 + 0x30)
#define LNaTECR0_EQ_TYPE			GENMASK(30, 28)
#define LNaTECR0_EQ_SGN_PREQ			BIT(23)
#define LNaTECR0_EQ_PREQ			GENMASK(19, 16)
#define LNaTECR0_EQ_SGN_POST1Q			BIT(15)
#define LNaTECR0_EQ_POST1Q			GENMASK(12, 8)
#define LNaTECR0_EQ_AMP_RED			GENMASK(5, 0)

#define LNaTECR1(lane)				(0x800 + (lane) * 0x100 + 0x34)
#define LNaTECR1_EQ_ADPT_EQ_DRVR_DIS		BIT(31)
#define LNaTECR1_EQ_ADPT_EQ			GENMASK(29, 24)

/* Lane a Rx Reset Control Register */
#define LNaRRSTCTL(lane)			(0x800 + (lane) * 0x100 + 0x40)
#define LNaRRSTCTL_HLT_REQ			BIT(27)
#define LNaRRSTCTL_RST_DONE			BIT(30)
#define LNaRRSTCTL_RST_REQ			BIT(31)
#define LNaRRSTCTL_CDR_LOCK			BIT(12)

/* Lane a Rx General Control Register */
#define LNaRGCR0(lane)				(0x800 + (lane) * 0x100 + 0x44)
#define LNaRGCR0_USE_PLL			BIT(28)
#define LNaRGCR0_USE_PLLF			0x0
#define LNaRGCR0_USE_PLLS			0x1
#define LNaRGCR0_N_RATE				GENMASK(26, 24)
#define LNaRGCR0_N_RATE_FULL			0x0
#define LNaRGCR0_N_RATE_HALF			0x1
#define LNaRGCR0_N_RATE_QUARTER			0x2

#define LNaRGCR1(lane)				(0x800 + (lane) * 0x100 + 0x48)
#define LNaRGCR1_RX_ORD_ELECIDLE		BIT(31)
#define LNaRGCR1_DATA_LOST_FLT			BIT(30)
#define LNaRGCR1_DATA_LOST			BIT(29)
#define LNaRGCR1_IDLE_CONFIG			BIT(28)
#define LNaRGCR1_ENTER_IDLE_FLT_SEL		GENMASK(26, 24)
#define LNaRGCR1_EXIT_IDLE_FLT_SEL		GENMASK(22, 20)
#define LNaRGCR1_DATA_LOST_TH_SEL		GENMASK(18, 16)
#define LNaRGCR1_EXT_REC_CLK_SEL		GENMASK(10, 8)
#define LNaRGCR1_WAKE_TX_DIS			BIT(5)
#define LNaRGCR1_PHY_RDY			BIT(4)
#define LNaRGCR1_CHANGE_RX_CLK			BIT(3)
#define LNaRGCR1_PWR_MGT			GENMASK(2, 0)

#define LNaRECR0(lane)				(0x800 + (lane) * 0x100 + 0x50)
#define LNaRECR0_EQ_GAINK2_HF_OV_EN		BIT(31)
#define LNaRECR0_EQ_GAINK2_HF_OV		GENMASK(28, 24)
#define LNaRECR0_EQ_GAINK3_MF_OV_EN		BIT(23)
#define LNaRECR0_EQ_GAINK3_MF_OV		GENMASK(20, 16)
#define LNaRECR0_EQ_GAINK4_LF_OV_EN		BIT(7)
#define LNaRECR0_EQ_GAINK4_LF_DIS		BIT(6)
#define LNaRECR0_EQ_GAINK4_LF_OV		GENMASK(4, 0)

#define LNaRECR1(lane)				(0x800 + (lane) * 0x100 + 0x54)
#define LNaRECR1_EQ_BLW_OV_EN			BIT(31)
#define LNaRECR1_EQ_BLW_OV			GENMASK(28, 24)
#define LNaRECR1_EQ_OFFSET_OV_EN		BIT(23)
#define LNaRECR1_EQ_OFFSET_OV			GENMASK(21, 16)

#define LNaRECR2(lane)				(0x800 + (lane) * 0x100 + 0x58)
#define LNaRECR2_EQ_OFFSET_RNG_DBL		BIT(31)
#define LNaRECR2_EQ_BOOST			GENMASK(29, 28)
#define LNaRECR2_EQ_BLW_SEL			GENMASK(25, 24)
#define LNaRECR2_EQ_ZERO			GENMASK(17, 16)
#define LNaRECR2_EQ_IND				GENMASK(13, 12)
#define LNaRECR2_EQ_BIN_DATA_AVG_TC		GENMASK(5, 4)
#define LNaRECR2_SPARE_IN			GENMASK(1, 0)

#define LNaRECR3(lane)				(0x800 + (lane) * 0x100 + 0x5c)
#define LNaRECR3_EQ_SNAP_START			BIT(31)
#define LNaRECR3_EQ_SNAP_DONE			BIT(30)
#define LNaRECR3_EQ_GAINK2_HF_STAT		GENMASK(28, 24)
#define LNaRECR3_EQ_GAINK3_MF_STAT		GENMASK(20, 16)
#define LNaRECR3_SPARE_OUT			GENMASK(13, 12)
#define LNaRECR3_EQ_GAINK4_LF_STAT		GENMASK(4, 0)

#define LNaRECR4(lane)				(0x800 + (lane) * 0x100 + 0x60)
#define LNaRECR4_BLW_STAT			GENMASK(28, 24)
#define LNaRECR4_EQ_OFFSET_STAT			GENMASK(21, 16)
#define LNaRECR4_EQ_BIN_DATA_SEL		GENMASK(15, 12)
#define LNaRECR4_EQ_BIN_DATA			GENMASK(8, 0) /* bit 9 is reserved */
#define LNaRECR4_EQ_BIN_DATA_SGN		BIT(8)

#define LNaRCCR0(lane)				(0x800 + (lane) * 0x100 + 0x68)
#define LNaRCCR0_CAL_EN				BIT(31)
#define LNaRCCR0_MEAS_EN			BIT(30)
#define LNaRCCR0_CAL_BIN_SEL			BIT(28)
#define LNaRCCR0_CAL_DC3_DIS			BIT(27)
#define LNaRCCR0_CAL_DC2_DIS			BIT(26)
#define LNaRCCR0_CAL_DC1_DIS			BIT(25)
#define LNaRCCR0_CAL_DC0_DIS			BIT(24)
#define LNaRCCR0_CAL_AC3_OV_EN			BIT(15)
#define LNaRCCR0_CAL_AC3_OV			GENMASK(11, 8)
#define LNaRCCR0_CAL_AC2_OV_EN			BIT(7)

#define LNaRSCCR0(lane)				(0x800 + (lane) * 0x100 + 0x74)
#define LNaRSCCR0_SMP_OFF_EN			BIT(31)
#define LNaRSCCR0_SMP_OFF_OV_EN			BIT(30)
#define LNaRSCCR0_SMP_MAN_OFF_EN		BIT(29)
#define LNaRSCCR0_SMP_OFF_RNG_OV_EN		BIT(27)
#define LNaRSCCR0_SMP_OFF_RNG_4X_OV		BIT(25)
#define LNaRSCCR0_SMP_OFF_RNG_2X_OV		BIT(24)
#define LNaRSCCR0_SMP_AUTOZ_PD			BIT(23)
#define LNaRSCCR0_SMP_AUTOZ_CTRL		GENMASK(19, 16)
#define LNaRSCCR0_SMP_AUTOZ_D1R			GENMASK(13, 12)
#define LNaRSCCR0_SMP_AUTOZ_D1F			GENMASK(9, 8)
#define LNaRSCCR0_SMP_AUTOZ_EG1R		GENMASK(5, 4)
#define LNaRSCCR0_SMP_AUTOZ_EG1F		GENMASK(1, 0)

#define LNaTTLCR0(lane)				(0x800 + (lane) * 0x100 + 0x80)
#define LNaTTLCR0_TTL_FLT_SEL			GENMASK(29, 24)
#define LNaTTLCR0_TTL_SLO_PM_BYP		BIT(22)
#define LNaTTLCR0_STALL_DET_DIS			BIT(21)
#define LNaTTLCR0_INACT_MON_DIS			BIT(20)
#define LNaTTLCR0_CDR_OV			GENMASK(18, 16)
#define LNaTTLCR0_DATA_IN_SSC			BIT(15)
#define LNaTTLCR0_CDR_MIN_SMP_ON		GENMASK(1, 0)

#define LNaTCSR0(lane)				(0x800 + (lane) * 0x100 + 0xa0)
#define LNaTCSR0_SD_STAT_OBS_EN			BIT(31)
#define LNaTCSR0_SD_LPBK_SEL			GENMASK(29, 28)

#define LNaPSS(lane)				(0x1000 + (lane) * 0x4)
#define LNaPSS_TYPE				GENMASK(30, 24)
#define LNaPSS_TYPE_SGMII			(PROTO_SEL_SGMII_BASEX_KX << 2)
#define LNaPSS_TYPE_XFI				(PROTO_SEL_XFI_10GBASER_KR_SXGMII << 2)
#define LNaPSS_TYPE_40G				((PROTO_SEL_XFI_10GBASER_KR_SXGMII << 2) | 3)
#define LNaPSS_TYPE_25G				(PROTO_SEL_25G_50G_100G << 2)
#define LNaPSS_TYPE_100G			((PROTO_SEL_25G_50G_100G << 2) | 2)

/* MDEV_PORT is at the same bitfield address for all protocol converters */
#define MDEV_PORT				GENMASK(31, 27)

#define SGMIIaCR0(lane)				(0x1800 + (lane) * 0x10)
#define SGMIIaCR1(lane)				(0x1804 + (lane) * 0x10)
#define SGMIIaCR1_SGPCS_EN			BIT(11)

#define ANLTaCR0(lane)				(0x1a00 + (lane) * 0x10)
#define ANLTaCR1(lane)				(0x1a04 + (lane) * 0x10)

#define SXGMIIaCR0(lane)			(0x1a80 + (lane) * 0x10)
#define SXGMIIaCR0_RST				BIT(31)
#define SXGMIIaCR0_PD				BIT(30)

#define SXGMIIaCR1(lane)			(0x1a84 + (lane) * 0x10)

#define E25GaCR0(lane)				(0x1b00 + (lane) * 0x10)
#define E25GaCR0_RST				BIT(31)
#define E25GaCR0_PD				BIT(30)

#define E25GaCR1(lane)				(0x1b04 + (lane) * 0x10)

#define E25GaCR2(lane)				(0x1b08 + (lane) * 0x10)
#define E25GaCR2_FEC_ENA			BIT(23)
#define E25GaCR2_FEC_ERR_ENA			BIT(22)
#define E25GaCR2_FEC91_ENA			BIT(20)

#define E40GaCR0(pcvt)				(0x1b40 + (pcvt) * 0x20)
#define E40GaCR1(pcvt)				(0x1b44 + (pcvt) * 0x20)

#define E50GaCR1(pcvt)				(0x1b84 + (pcvt) * 0x10)

#define E100GaCR1(pcvt)				(0x1c04 + (pcvt) * 0x20)

#define CR(x)					((x) * 4)

enum lynx_28g_eq_type {
	EQ_TYPE_NO_EQ = 0,
	EQ_TYPE_2TAP = 1,
	EQ_TYPE_3TAP = 2,
};

enum lynx_28g_proto_sel {
	PROTO_SEL_PCIE = 0,
	PROTO_SEL_SGMII_BASEX_KX = 1,
	PROTO_SEL_SATA = 2,
	PROTO_SEL_XAUI = 4,
	PROTO_SEL_XFI_10GBASER_KR_SXGMII = 0xa,
	PROTO_SEL_25G_50G_100G = 0x1a,
};

enum lynx_lane_mode {
	LANE_MODE_UNKNOWN,
	LANE_MODE_1000BASEX_SGMII,
	LANE_MODE_10GBASER,
	LANE_MODE_USXGMII,
	LANE_MODE_MAX,
};

struct lynx_28g_proto_conf {
	/* LNaGCR0 */
	int proto_sel;
	int if_width;
	/* LNaTECR0 */
	int teq_type;
	int sgn_preq;
	int ratio_preq;
	int sgn_post1q;
	int ratio_post1q;
	int amp_red;
	/* LNaTECR1 */
	int adpt_eq;
	/* LNaRGCR1 */
	int enter_idle_flt_sel;
	int exit_idle_flt_sel;
	int data_lost_th_sel;
	/* LNaRECR0 */
	int gk2ovd;
	int gk3ovd;
	int gk4ovd;
	int gk2ovd_en;
	int gk3ovd_en;
	int gk4ovd_en;
	/* LNaRECR1 ? */
	int eq_offset_ovd;
	int eq_offset_ovd_en;
	/* LNaRECR2 */
	int eq_offset_rng_dbl;
	int eq_blw_sel;
	int eq_boost;
	int spare_in;
	/* LNaRSCCR0 */
	int smp_autoz_d1r;
	int smp_autoz_eg1r;
	/* LNaRCCR0 */
	int rccr0;
	/* LNaTTLCR0 */
	int ttlcr0;
};

static const struct lynx_28g_proto_conf lynx_28g_proto_conf[LANE_MODE_MAX] = {
	[LANE_MODE_1000BASEX_SGMII] = {
		.proto_sel = LNaGCR0_PROTO_SEL_SGMII,
		.if_width = LNaGCR0_IF_WIDTH_10_BIT,
		.teq_type = EQ_TYPE_NO_EQ,
		.sgn_preq = 1,
		.ratio_preq = 0,
		.sgn_post1q = 1,
		.ratio_post1q = 0,
		.amp_red = 6,
		.adpt_eq = 48,
		.enter_idle_flt_sel = 4,
		.exit_idle_flt_sel = 3,
		.data_lost_th_sel = 1,
		.gk2ovd = 0x1f,
		.gk3ovd = 0,
		.gk4ovd = 0,
		.gk2ovd_en = 1,
		.gk3ovd_en = 1,
		.gk4ovd_en = 0,
		.eq_offset_ovd = 0x1f,
		.eq_offset_ovd_en = 0,
		.eq_offset_rng_dbl = 0,
		.eq_blw_sel = 0,
		.eq_boost = 0,
		.spare_in = 0,
		.smp_autoz_d1r = 0,
		.smp_autoz_eg1r = 0,
		.rccr0 = LNaRCCR0_CAL_EN,
		.ttlcr0 = LNaTTLCR0_TTL_SLO_PM_BYP |
			  LNaTTLCR0_DATA_IN_SSC,
	},
	[LANE_MODE_USXGMII] = {
		.proto_sel = LNaGCR0_PROTO_SEL_XFI,
		.if_width = LNaGCR0_IF_WIDTH_20_BIT,
		.teq_type = EQ_TYPE_2TAP,
		.sgn_preq = 1,
		.ratio_preq = 0,
		.sgn_post1q = 1,
		.ratio_post1q = 3,
		.amp_red = 7,
		.adpt_eq = 48,
		.enter_idle_flt_sel = 0,
		.exit_idle_flt_sel = 0,
		.data_lost_th_sel = 0,
		.gk2ovd = 0,
		.gk3ovd = 0,
		.gk4ovd = 0,
		.gk2ovd_en = 0,
		.gk3ovd_en = 0,
		.gk4ovd_en = 0,
		.eq_offset_ovd = 0x1f,
		.eq_offset_ovd_en = 0,
		.eq_offset_rng_dbl = 1,
		.eq_blw_sel = 1,
		.eq_boost = 0,
		.spare_in = 0,
		.smp_autoz_d1r = 2,
		.smp_autoz_eg1r = 0,
		.rccr0 = LNaRCCR0_CAL_EN,
		.ttlcr0 = LNaTTLCR0_TTL_SLO_PM_BYP |
			  LNaTTLCR0_DATA_IN_SSC,
	},
	[LANE_MODE_10GBASER] = {
		.proto_sel = LNaGCR0_PROTO_SEL_XFI,
		.if_width = LNaGCR0_IF_WIDTH_20_BIT,
		.teq_type = EQ_TYPE_2TAP,
		.sgn_preq = 1,
		.ratio_preq = 0,
		.sgn_post1q = 1,
		.ratio_post1q = 3,
		.amp_red = 7,
		.adpt_eq = 48,
		.enter_idle_flt_sel = 0,
		.exit_idle_flt_sel = 0,
		.data_lost_th_sel = 0,
		.gk2ovd = 0,
		.gk3ovd = 0,
		.gk4ovd = 0,
		.gk2ovd_en = 0,
		.gk3ovd_en = 0,
		.gk4ovd_en = 0,
		.eq_offset_ovd = 0x1f,
		.eq_offset_ovd_en = 0,
		.eq_offset_rng_dbl = 1,
		.eq_blw_sel = 1,
		.eq_boost = 0,
		.spare_in = 0,
		.smp_autoz_d1r = 2,
		.smp_autoz_eg1r = 0,
		.rccr0 = LNaRCCR0_CAL_EN,
		.ttlcr0 = LNaTTLCR0_TTL_SLO_PM_BYP |
			  LNaTTLCR0_DATA_IN_SSC,
	},
};

struct lynx_pccr {
	int offset;
	int width;
	int shift;
};

struct lynx_28g_priv;

struct lynx_28g_pll {
	struct lynx_28g_priv *priv;
	u32 rstctl, cr0, cr1;
	int id;
	DECLARE_BITMAP(supported, LANE_MODE_MAX);
};

struct lynx_28g_lane {
	struct lynx_28g_priv *priv;
	struct phy *phy;
	bool powered_up;
	bool init;
	unsigned int id;
	enum lynx_lane_mode mode;
};

struct lynx_28g_priv {
	void __iomem *base;
	struct device *dev;
	/* Serialize concurrent access to registers shared between lanes,
	 * like PCCn
	 */
	spinlock_t pcc_lock;
	struct lynx_28g_pll pll[LYNX_28G_NUM_PLL];
	struct lynx_28g_lane lane[LYNX_28G_NUM_LANE];

	struct delayed_work cdr_check;
};

static void lynx_28g_rmw(struct lynx_28g_priv *priv, unsigned long off,
			 u32 val, u32 mask)
{
	void __iomem *reg = priv->base + off;
	u32 orig, tmp;

	orig = ioread32(reg);
	tmp = orig & ~mask;
	tmp |= val;
	iowrite32(tmp, reg);
}

#define lynx_28g_read(priv, off) \
	ioread32((priv)->base + (off))
#define lynx_28g_write(priv, off, val) \
	iowrite32(val, (priv)->base + (off))
#define lynx_28g_lane_rmw(lane, reg, val, mask)	\
	lynx_28g_rmw((lane)->priv, reg(lane->id), val, mask)
#define lynx_28g_lane_read(lane, reg)			\
	ioread32((lane)->priv->base + reg((lane)->id))
#define lynx_28g_lane_write(lane, reg, val)		\
	iowrite32(val, (lane)->priv->base + reg((lane)->id))
#define lynx_28g_pll_read(pll, reg)			\
	ioread32((pll)->priv->base + reg((pll)->id))

static 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_10GBASER:
		return "10GBase-R";
	case LANE_MODE_USXGMII:
		return "USXGMII";
	default:
		return "unknown";
	}
}

static 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_10GBASER:
		return LANE_MODE_10GBASER;
	case PHY_INTERFACE_MODE_USXGMII:
		return LANE_MODE_USXGMII;
	default:
		return LANE_MODE_UNKNOWN;
	}
}

static bool lynx_28g_supports_lane_mode(struct lynx_28g_priv *priv,
					enum lynx_lane_mode mode)
{
	int i;

	for (i = 0; i < LYNX_28G_NUM_PLL; i++) {
		if (PLLnRSTCTL_DIS(priv->pll[i].rstctl))
			continue;

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

	return false;
}

static struct lynx_28g_pll *lynx_28g_pll_get(struct lynx_28g_priv *priv,
					     enum lynx_lane_mode mode)
{
	struct lynx_28g_pll *pll;
	int i;

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

		if (PLLnRSTCTL_DIS(pll->rstctl))
			continue;

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

	/* no pll supports requested mode, either caller forgot to check
	 * lynx_28g_supports_lane_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;
}

static void lynx_28g_lane_set_nrate(struct lynx_28g_lane *lane,
				    struct lynx_28g_pll *pll,
				    enum lynx_lane_mode lane_mode)
{
	switch (FIELD_GET(PLLnCR1_FRATE_SEL, pll->cr1)) {
	case PLLnCR1_FRATE_5G_10GVCO:
	case PLLnCR1_FRATE_5G_25GVCO:
		switch (lane_mode) {
		case LANE_MODE_1000BASEX_SGMII:
			lynx_28g_lane_rmw(lane, LNaTGCR0,
					  FIELD_PREP(LNaTGCR0_N_RATE, LNaTGCR0_N_RATE_QUARTER),
					  LNaTGCR0_N_RATE);
			lynx_28g_lane_rmw(lane, LNaRGCR0,
					  FIELD_PREP(LNaRGCR0_N_RATE, LNaRGCR0_N_RATE_QUARTER),
					  LNaRGCR0_N_RATE);
			break;
		default:
			break;
		}
		break;
	case PLLnCR1_FRATE_10G_20GVCO:
		switch (lane_mode) {
		case LANE_MODE_10GBASER:
		case LANE_MODE_USXGMII:
			lynx_28g_lane_rmw(lane, LNaTGCR0,
					  FIELD_PREP(LNaTGCR0_N_RATE, LNaTGCR0_N_RATE_FULL),
					  LNaTGCR0_N_RATE);
			lynx_28g_lane_rmw(lane, LNaRGCR0,
					  FIELD_PREP(LNaRGCR0_N_RATE, LNaRGCR0_N_RATE_FULL),
					  LNaRGCR0_N_RATE);
			break;
		default:
			break;
		}
		break;
	default:
		break;
	}
}

static void lynx_28g_lane_set_pll(struct lynx_28g_lane *lane,
				  struct lynx_28g_pll *pll)
{
	if (pll->id == 0) {
		lynx_28g_lane_rmw(lane, LNaTGCR0,
				  FIELD_PREP(LNaTGCR0_USE_PLL, LNaTGCR0_USE_PLLF),
				  LNaTGCR0_USE_PLL);
		lynx_28g_lane_rmw(lane, LNaRGCR0,
				  FIELD_PREP(LNaRGCR0_USE_PLL, LNaRGCR0_USE_PLLF),
				  LNaRGCR0_USE_PLL);
	} else {
		lynx_28g_lane_rmw(lane, LNaTGCR0,
				  FIELD_PREP(LNaTGCR0_USE_PLL, LNaTGCR0_USE_PLLS),
				  LNaTGCR0_USE_PLL);
		lynx_28g_lane_rmw(lane, LNaRGCR0,
				  FIELD_PREP(LNaRGCR0_USE_PLL, LNaRGCR0_USE_PLLS),
				  LNaRGCR0_USE_PLL);
	}
}

static int lynx_28g_power_off(struct phy *phy)
{
	struct lynx_28g_lane *lane = phy_get_drvdata(phy);
	u32 trstctl, rrstctl;

	if (!lane->powered_up)
		return 0;

	/* Issue a halt request */
	lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_HLT_REQ,
			  LNaTRSTCTL_HLT_REQ);
	lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_HLT_REQ,
			  LNaRRSTCTL_HLT_REQ);

	/* Wait until the halting process is complete */
	do {
		trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL);
		rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
	} while ((trstctl & LNaTRSTCTL_HLT_REQ) ||
		 (rrstctl & LNaRRSTCTL_HLT_REQ));

	lane->powered_up = false;

	return 0;
}

static int lynx_28g_power_on(struct phy *phy)
{
	struct lynx_28g_lane *lane = phy_get_drvdata(phy);
	u32 trstctl, rrstctl;

	if (lane->powered_up)
		return 0;

	/* Issue a reset request on the lane */
	lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_RST_REQ,
			  LNaTRSTCTL_RST_REQ);
	lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_RST_REQ,
			  LNaRRSTCTL_RST_REQ);

	/* Wait until the reset sequence is completed */
	do {
		trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL);
		rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
	} while (!(trstctl & LNaTRSTCTL_RST_DONE) ||
		 !(rrstctl & LNaRRSTCTL_RST_DONE));

	lane->powered_up = true;

	return 0;
}

static int lynx_28g_get_pccr(enum lynx_lane_mode lane_mode, int lane,
			     struct lynx_pccr *pccr)
{
	switch (lane_mode) {
	case LANE_MODE_1000BASEX_SGMII:
		pccr->offset = PCC8;
		pccr->width = 4;
		pccr->shift = SGMII_CFG(lane);
		break;
	case LANE_MODE_USXGMII:
	case LANE_MODE_10GBASER:
		pccr->offset = PCCC;
		pccr->width = 4;
		pccr->shift = SXGMII_CFG(lane);
		break;
	default:
		return -EOPNOTSUPP;
	}

	return 0;
}

static int lynx_28g_get_pcvt_offset(int lane, enum lynx_lane_mode lane_mode)
{
	switch (lane_mode) {
	case LANE_MODE_1000BASEX_SGMII:
		return SGMIIaCR0(lane);
	case LANE_MODE_USXGMII:
	case LANE_MODE_10GBASER:
		return SXGMIIaCR0(lane);
	default:
		return -EOPNOTSUPP;
	}
}

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

	err = lynx_28g_get_pccr(mode, lane->id, &pccr);
	if (err)
		return err;

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

	return 0;
}

static int lynx_pccr_write(struct lynx_28g_lane *lane,
			   enum lynx_lane_mode lane_mode, u32 val)
{
	struct lynx_28g_priv *priv = lane->priv;
	struct lynx_pccr pccr;
	u32 old, tmp, mask;
	int err;

	err = lynx_28g_get_pccr(lane_mode, lane->id, &pccr);
	if (err)
		return err;

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

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

	return 0;
}

static int lynx_pcvt_read(struct lynx_28g_lane *lane,
			  enum lynx_lane_mode lane_mode, int cr, u32 *val)
{
	struct lynx_28g_priv *priv = lane->priv;
	int offset;

	offset = lynx_28g_get_pcvt_offset(lane->id, lane_mode);
	if (offset < 0)
		return offset;

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

	return 0;
}

static int lynx_pcvt_write(struct lynx_28g_lane *lane,
			   enum lynx_lane_mode lane_mode, int cr, u32 val)
{
	struct lynx_28g_priv *priv = lane->priv;
	int offset;

	offset = lynx_28g_get_pcvt_offset(lane->id, lane_mode);
	if (offset < 0)
		return offset;

	lynx_28g_write(priv, offset + cr, val);

	return 0;
}

static int lynx_pcvt_rmw(struct lynx_28g_lane *lane,
			 enum lynx_lane_mode lane_mode,
			 int cr, u32 val, u32 mask)
{
	int err;
	u32 tmp;

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

	tmp &= ~mask;
	tmp |= val;

	return lynx_pcvt_write(lane, lane_mode, cr, tmp);
}

static void lynx_28g_lane_remap_pll(struct lynx_28g_lane *lane,
				    enum lynx_lane_mode lane_mode)
{
	struct lynx_28g_priv *priv = lane->priv;
	struct lynx_28g_pll *pll;

	/* Switch to the PLL that works with this interface type */
	pll = lynx_28g_pll_get(priv, lane_mode);
	if (unlikely(pll == NULL))
		return;

	lynx_28g_lane_set_pll(lane, pll);

	/* Choose the portion of clock net to be used on this lane */
	lynx_28g_lane_set_nrate(lane, pll, lane_mode);
}

static void lynx_28g_lane_change_proto_conf(struct lynx_28g_lane *lane,
					    enum lynx_lane_mode lane_mode)
{
	const struct lynx_28g_proto_conf *conf = &lynx_28g_proto_conf[lane_mode];

	lynx_28g_lane_rmw(lane, LNaGCR0,
			  FIELD_PREP(LNaGCR0_PROTO_SEL, conf->proto_sel) |
			  FIELD_PREP(LNaGCR0_IF_WIDTH, conf->if_width),
			  LNaGCR0_PROTO_SEL | LNaGCR0_IF_WIDTH);

	lynx_28g_lane_rmw(lane, LNaTECR0,
			  FIELD_PREP(LNaTECR0_EQ_TYPE, conf->teq_type) |
			  FIELD_PREP(LNaTECR0_EQ_SGN_PREQ, conf->sgn_preq) |
			  FIELD_PREP(LNaTECR0_EQ_PREQ, conf->ratio_preq) |
			  FIELD_PREP(LNaTECR0_EQ_SGN_POST1Q, conf->sgn_post1q) |
			  FIELD_PREP(LNaTECR0_EQ_POST1Q, conf->ratio_post1q) |
			  FIELD_PREP(LNaTECR0_EQ_AMP_RED, conf->amp_red),
			  LNaTECR0_EQ_TYPE |
			  LNaTECR0_EQ_SGN_PREQ |
			  LNaTECR0_EQ_PREQ |
			  LNaTECR0_EQ_SGN_POST1Q |
			  LNaTECR0_EQ_POST1Q |
			  LNaTECR0_EQ_AMP_RED);

	lynx_28g_lane_rmw(lane, LNaTECR1,
			  FIELD_PREP(LNaTECR1_EQ_ADPT_EQ, conf->adpt_eq),
			  LNaTECR1_EQ_ADPT_EQ);

	lynx_28g_lane_rmw(lane, LNaRGCR1,
			  FIELD_PREP(LNaRGCR1_ENTER_IDLE_FLT_SEL, conf->enter_idle_flt_sel) |
			  FIELD_PREP(LNaRGCR1_EXIT_IDLE_FLT_SEL, conf->exit_idle_flt_sel) |
			  FIELD_PREP(LNaRGCR1_DATA_LOST_TH_SEL, conf->data_lost_th_sel),
			  LNaRGCR1_ENTER_IDLE_FLT_SEL |
			  LNaRGCR1_EXIT_IDLE_FLT_SEL |
			  LNaRGCR1_DATA_LOST_TH_SEL);

	lynx_28g_lane_rmw(lane, LNaRECR0,
			  FIELD_PREP(LNaRECR0_EQ_GAINK2_HF_OV_EN, conf->gk2ovd_en) |
			  FIELD_PREP(LNaRECR0_EQ_GAINK3_MF_OV_EN, conf->gk3ovd_en) |
			  FIELD_PREP(LNaRECR0_EQ_GAINK4_LF_OV_EN, conf->gk4ovd_en) |
			  FIELD_PREP(LNaRECR0_EQ_GAINK2_HF_OV, conf->gk2ovd) |
			  FIELD_PREP(LNaRECR0_EQ_GAINK3_MF_OV, conf->gk3ovd) |
			  FIELD_PREP(LNaRECR0_EQ_GAINK4_LF_OV, conf->gk4ovd),
			  LNaRECR0_EQ_GAINK2_HF_OV |
			  LNaRECR0_EQ_GAINK3_MF_OV |
			  LNaRECR0_EQ_GAINK4_LF_OV |
			  LNaRECR0_EQ_GAINK2_HF_OV_EN |
			  LNaRECR0_EQ_GAINK3_MF_OV_EN |
			  LNaRECR0_EQ_GAINK4_LF_OV_EN);

	lynx_28g_lane_rmw(lane, LNaRECR1,
			  FIELD_PREP(LNaRECR1_EQ_OFFSET_OV, conf->eq_offset_ovd) |
			  FIELD_PREP(LNaRECR1_EQ_OFFSET_OV_EN, conf->eq_offset_ovd_en),
			  LNaRECR1_EQ_OFFSET_OV |
			  LNaRECR1_EQ_OFFSET_OV_EN);

	lynx_28g_lane_rmw(lane, LNaRECR2,
			  FIELD_PREP(LNaRECR2_EQ_OFFSET_RNG_DBL, conf->eq_offset_rng_dbl) |
			  FIELD_PREP(LNaRECR2_EQ_BLW_SEL, conf->eq_blw_sel) |
			  FIELD_PREP(LNaRECR2_EQ_BOOST, conf->eq_boost) |
			  FIELD_PREP(LNaRECR2_SPARE_IN, conf->spare_in),
			  LNaRECR2_EQ_OFFSET_RNG_DBL |
			  LNaRECR2_EQ_BLW_SEL |
			  LNaRECR2_EQ_BOOST |
			  LNaRECR2_SPARE_IN);

	lynx_28g_lane_rmw(lane, LNaRSCCR0,
			  FIELD_PREP(LNaRSCCR0_SMP_AUTOZ_D1R, conf->smp_autoz_d1r) |
			  FIELD_PREP(LNaRSCCR0_SMP_AUTOZ_EG1R, conf->smp_autoz_eg1r),
			  LNaRSCCR0_SMP_AUTOZ_D1R |
			  LNaRSCCR0_SMP_AUTOZ_EG1R);

	lynx_28g_lane_write(lane, LNaRCCR0, conf->rccr0);
	lynx_28g_lane_write(lane, LNaTTLCR0, conf->ttlcr0);
}

static int lynx_28g_lane_disable_pcvt(struct lynx_28g_lane *lane,
				      enum lynx_lane_mode lane_mode)
{
	struct lynx_28g_priv *priv = lane->priv;
	int err;

	spin_lock(&priv->pcc_lock);

	err = lynx_pccr_write(lane, lane_mode, 0);
	if (err)
		goto out;

	switch (lane_mode) {
	case LANE_MODE_1000BASEX_SGMII:
		err = lynx_pcvt_rmw(lane, lane_mode, CR(1), 0,
				    SGMIIaCR1_SGPCS_EN);
		break;
	default:
		err = 0;
	}

out:
	spin_unlock(&priv->pcc_lock);

	return err;
}

static int lynx_28g_lane_enable_pcvt(struct lynx_28g_lane *lane,
				     enum lynx_lane_mode lane_mode)
{
	struct lynx_28g_priv *priv = lane->priv;
	u32 val;
	int err;

	spin_lock(&priv->pcc_lock);

	switch (lane_mode) {
	case LANE_MODE_1000BASEX_SGMII:
		err = lynx_pcvt_rmw(lane, lane_mode, CR(1), SGMIIaCR1_SGPCS_EN,
				    SGMIIaCR1_SGPCS_EN);
		break;
	default:
		err = 0;
	}

	val = 0;

	switch (lane_mode) {
	case LANE_MODE_1000BASEX_SGMII:
		val |= PCC8_SGMIIa_CFG;
		break;
	case LANE_MODE_10GBASER:
		val |= PCCC_SXGMIIn_XFI;
		fallthrough;
	case LANE_MODE_USXGMII:
		val |= PCCC_SXGMIIn_CFG;
		break;
	default:
		break;
	}

	err = lynx_pccr_write(lane, lane_mode, val);

	spin_unlock(&priv->pcc_lock);

	return err;
}

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;

	if (mode != PHY_MODE_ETHERNET)
		return -EOPNOTSUPP;

	if (lane->mode == LANE_MODE_UNKNOWN)
		return -EOPNOTSUPP;

	lane_mode = phy_interface_to_lane_mode(submode);
	if (!lynx_28g_supports_lane_mode(priv, lane_mode))
		return -EOPNOTSUPP;

	if (lane_mode == lane->mode)
		return 0;

	/* If the lane is powered up, put the lane into the halt state while
	 * the reconfiguration is being done.
	 */
	if (powered_up)
		lynx_28g_power_off(phy);

	err = lynx_28g_lane_disable_pcvt(lane, lane->mode);
	if (err)
		goto out;

	lynx_28g_lane_change_proto_conf(lane, lane_mode);
	lynx_28g_lane_remap_pll(lane, lane_mode);
	WARN_ON(lynx_28g_lane_enable_pcvt(lane, lane_mode));

	lane->mode = lane_mode;

out:
	if (powered_up)
		lynx_28g_power_on(phy);

	return err;
}

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))
		return -EOPNOTSUPP;

	return 0;
}

static int lynx_28g_init(struct phy *phy)
{
	struct lynx_28g_lane *lane = phy_get_drvdata(phy);

	/* Mark the fact that the lane was init */
	lane->init = true;

	/* SerDes lanes are powered on at boot time.  Any lane that is managed
	 * by this driver will get powered down at init time aka at dpaa2-eth
	 * probe time.
	 */
	lane->powered_up = true;
	lynx_28g_power_off(phy);

	return 0;
}

static const struct phy_ops lynx_28g_ops = {
	.init		= lynx_28g_init,
	.power_on	= lynx_28g_power_on,
	.power_off	= lynx_28g_power_off,
	.set_mode	= lynx_28g_set_mode,
	.validate	= lynx_28g_validate,
	.owner		= THIS_MODULE,
};

static void lynx_28g_pll_read_configuration(struct lynx_28g_priv *priv)
{
	struct lynx_28g_pll *pll;
	int i;

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

		pll->rstctl = lynx_28g_pll_read(pll, PLLnRSTCTL);
		pll->cr0 = lynx_28g_pll_read(pll, PLLnCR0);
		pll->cr1 = lynx_28g_pll_read(pll, PLLnCR1);

		if (PLLnRSTCTL_DIS(pll->rstctl))
			continue;

		switch (FIELD_GET(PLLnCR1_FRATE_SEL, pll->cr1)) {
		case PLLnCR1_FRATE_5G_10GVCO:
		case PLLnCR1_FRATE_5G_25GVCO:
			/* 5GHz clock net */
			__set_bit(LANE_MODE_1000BASEX_SGMII, pll->supported);
			break;
		case PLLnCR1_FRATE_10G_20GVCO:
			/* 10.3125GHz clock net */
			__set_bit(LANE_MODE_10GBASER, pll->supported);
			__set_bit(LANE_MODE_USXGMII, pll->supported);
			break;
		default:
			/* 6GHz, 12.890625GHz, 8GHz */
			break;
		}
	}
}

#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 i;

	for (i = 0; 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);
			do {
				rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
			} while (!(rrstctl & LNaRRSTCTL_RST_DONE));
		}

		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;

	pss = lynx_28g_lane_read(lane, LNaPSS);
	protocol = FIELD_GET(LNaPSS_TYPE, pss);
	switch (protocol) {
	case LNaPSS_TYPE_SGMII:
		lane->mode = LANE_MODE_1000BASEX_SGMII;
		break;
	case LNaPSS_TYPE_XFI:
		lynx_pccr_read(lane, LANE_MODE_10GBASER, &pccr);
		if (pccr & PCCC_SXGMIIn_XFI)
			lane->mode = LANE_MODE_10GBASER;
		else
			lane->mode = LANE_MODE_USXGMII;
		break;
	default:
		lane->mode = LANE_MODE_UNKNOWN;
	}
}

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))
		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 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(dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	priv->dev = dev;
	dev_set_drvdata(dev, priv);
	spin_lock_init(&priv->pcc_lock);
	INIT_DELAYED_WORK(&priv->cdr_check, lynx_28g_cdr_lock_check);

	priv->base = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(priv->base))
		return PTR_ERR(priv->base);

	lynx_28g_pll_read_configuration(priv);

	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;
		}
	}

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

static const struct of_device_id lynx_28g_of_match_table[] = {
	{ .compatible = "fsl,lynx-28g" },
	{ },
};
MODULE_DEVICE_TABLE(of, lynx_28g_of_match_table);

static struct platform_driver lynx_28g_driver = {
	.probe = lynx_28g_probe,
	.remove = lynx_28g_remove,
	.driver = {
		.name = "lynx-28g",
		.of_match_table = lynx_28g_of_match_table,
	},
};
module_platform_driver(lynx_28g_driver);

MODULE_AUTHOR("Ioana Ciornei <ioana.ciornei@nxp.com>");
MODULE_DESCRIPTION("Lynx 28G SerDes PHY driver for Layerscape SoCs");
MODULE_LICENSE("GPL v2");