[PATCH RFC v3 05/12] net: pcs: Add NXP S32G XPCS driver
From: Jan Petrous via B4 Relay <devnull+jan.petrous.oss.nxp.com@kernel.org>
Date: 2026-09-19 06:54:41
Also in:
b4-sent, imx, linux-devicetree, linux-pci, linux-phy, linux-renesas-soc, lkml
Subsystem:
ethernet phy library, networking drivers, the rest · Maintainers:
Andrew Lunn, Heiner Kallweit, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
From: Vincent Guittot <vincent.guittot@linaro.org> The S32G SoC family includes two SerDes subsystems, each made of one PCIe controller, two DesignWare XPCS instances and a shared 2-lane combo PHY. Add the phylink PCS driver for the XPCS instances, with 1G SGMII support. The XPCS PMA/PLL bring-up is a property of the shared combo PHY: in the PCIe+SGMII modes the two XPCS instances share the PHY with the PCIe lane, so the XPCS cannot be a self-contained PCS platform device that owns its own reset/bring-up sequence. This driver therefore exposes a small init/bring-up API which the SerDes PHY driver sequences (it owns the shared PMA and enforces the required ordering, e.g. XPCS1 before XPCS0 in the dual-SGMII mode), while the phylink_pcs ops consumed by the MAC live here in the PCS driver. The MAC obtains its phylink PCS via s32g_serdes_pcs_create(). The busy-waits of the vendor driver are converted to read_poll_timeout(); the PMA is only touched after the SerDes PHY reports operational (MPLL_STATE), which the SerDes driver guarantees. 2.5G (2500BASE-X) register paths are retained but not wired up; they are enabled by a follow-up series, as there is currently no in-tree consumer of the 2.5G links. Tested on an S32G3-VNP-RDB3 board in both SerDes working modes this series describes: mode 1 (PCIe x1 on lane 0 + 1G SGMII via XPCS0 on lane 1) and mode 3 (dual SGMII, XPCS0 on lane 0 and XPCS1 on lane 1). GMAC0 links at 1G over XPCS0 and passes traffic in both. Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org> Co-developed-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com> Signed-off-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com> Co-developed-by: Alexandru-Catalin Ionita <redacted> Signed-off-by: Alexandru-Catalin Ionita <redacted> Co-developed-by: Ghennadi Procopciuc <redacted> Signed-off-by: Ghennadi Procopciuc <redacted> Co-developed-by: Ionut Vicovan <redacted> Signed-off-by: Ionut Vicovan <redacted> Co-developed-by: Bogdan Roman <redacted> Signed-off-by: Bogdan Roman <redacted> Co-developed-by: Jan Petrous (OSS) <jan.petrous@oss.nxp.com> Signed-off-by: Jan Petrous (OSS) <jan.petrous@oss.nxp.com> --- drivers/net/pcs/Kconfig | 14 + drivers/net/pcs/Makefile | 1 + drivers/net/pcs/pcs-nxp-s32g-xpcs.c | 947 ++++++++++++++++++++++++++++++++++ include/linux/pcs/pcs-nxp-s32g-xpcs.h | 48 ++ 4 files changed, 1010 insertions(+)
diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig
index ec4af294003f..7a4c0da406c9 100644
--- a/drivers/net/pcs/Kconfig
+++ b/drivers/net/pcs/Kconfig@@ -46,4 +46,18 @@ config PCS_RZN1_MIIC Renesas RZ/N1, RZ/N2H, and RZ/T2H SoCs. This PCS converts MII to RMII/RGMII, or can be set in pass-through mode for MII. +config PCS_NXP_S32G_XPCS + tristate "NXP S32G XPCS support" + select PHYLINK + select REGMAP + select PCS_NXP_SERDES_XPCS + help + This option enables the NXP S32G SerDes XPCS platform glue. It + provides the indirect-MMIO register transport and the S32G PMA/PLL + bring-up sequences on top of the shared NXP SerDes xPCS core + (PCS_NXP_SERDES_XPCS), which supplies the phylink PCS layer for the + SGMII / 2500BASE-X lanes of the shared SerDes combo PHY. It is + driven by the S32G SerDes PHY driver. Say Y or M here if you need + SGMII Ethernet on an NXP S32G2 or S32G3 SoC. + endmenu
diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile
index bbf063b18f1f..337e9b503553 100644
--- a/drivers/net/pcs/Makefile
+++ b/drivers/net/pcs/Makefile@@ -9,3 +9,4 @@ obj-$(CONFIG_PCS_NXP_SERDES_XPCS) += pcs-nxp-serdes-xpcs.o obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o +obj-$(CONFIG_PCS_NXP_S32G_XPCS) += pcs-nxp-s32g-xpcs.o
diff --git a/drivers/net/pcs/pcs-nxp-s32g-xpcs.c b/drivers/net/pcs/pcs-nxp-s32g-xpcs.c
new file mode 100644
index 000000000000..a44f1da54429
--- /dev/null
+++ b/drivers/net/pcs/pcs-nxp-s32g-xpcs.c@@ -0,0 +1,947 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2021-2026 NXP + * + * S32G SerDes XPCS platform glue for the NXP SerDes xPCS shared core. + * + * This module no longer implements its own phylink PCS. It provides the + * S32G specific parts the shared core (pcs-nxp-serdes-xpcs.c) consumes: + * - the indirect-MMIO regmap transport. The S32G flat register address + * 0x1fXXXX is already identical to the shared core's regmap key + * "(devad << 16) | reg" (devad 0x1f == MDIO_MMD_VEND2 for the VR_MII + * page, devad 0x1f reg 0x0000 == SR_MII_CTRL/MII_BMCR), so the same + * regmap serves both the S32G PMA sequences below and the shared core. + * - the S32G PMA/PLL bring-up sequences, exported to the SerDes PHY + * driver which drives the ordered dual-XPCS bring-up barrier. + * - a struct nxp_serdes_xpcs_desc describing the S32G revision, with a + * compat[] table (C37 SGMII + 2500BASE-X) and two pma_config() hooks + * (SGMII link-timer / CL37 override, and the 1G<->2.5G PLL switch). + */ + +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/pcs/pcs-nxp-s32g-xpcs.h> +#include <linux/pcs/pcs-nxp-serdes-xpcs.h> +#include <linux/phy.h> +#include <linux/phylink.h> +#include <linux/regmap.h> +#include <linux/units.h> + +enum s32g_xpcs_pll { + S32G_XPCS_PLLA, /* Slow PLL */ + S32G_XPCS_PLLB, /* Fast PLL */ +}; + +struct s32g_xpcs { + void __iomem *base; + struct device *dev; + unsigned char id; + struct regmap *regmap; + enum s32g_xpcs_pll ref; + bool ext_clk; + bool mhz125; + enum s32g_xpcs_shared pcie_shared; + /* phylink PCS owned by the shared core, created in s32g_xpcs_create() */ + struct phylink_pcs *pcs; +}; + +#define XPCS_TIMEOUT_US (300 * USEC_PER_MSEC) +#define XPCS_POLL_SLEEP_US 100 + +#define ADDR1_OFS 0x3fc + +#define SR_MII_CTRL 0x1f0000 +#define SS13 BIT(13) +#define AN_ENABLE BIT(12) +#define RESTART_AN BIT(9) +#define DUPLEX_MODE BIT(8) +#define SS6 BIT(6) +#define SR_MII_STS 0x1f0001 +#define LINK_STS BIT(2) +#define VR_MII_DIG_CTRL1 0x1f8000 +#define BYP_PWRUP BIT(1) +#define EN_2_5G_MODE BIT(2) +#define CL37_TMR_OVRRIDE BIT(3) +#define INIT BIT(8) +#define MAC_AUTO_SW BIT(9) +#define VR_RST BIT(15) +#define VR_MII_AN_CTRL 0x1f8001 +#define MII_AN_INTR_EN BIT(0) +#define PCS_MODE_MASK GENMASK(2, 1) +#define PCS_MODE_SGMII 2 +#define MII_CTRL BIT(8) +#define VR_MII_AN_INTR_STS 0x1f8002 +#define CL37_ANCMPLT_INTR BIT(0) +#define CL37_ANSGM_STS_DUPLEX BIT(1) +#define CL37_ANSGM_STS_SPEED_MASK GENMASK(3, 2) +#define CL37_ANSGM_10MBPS 0 +#define CL37_ANSGM_100MBPS 1 +#define CL37_ANSGM_1000MBPS 2 +#define CL37_ANSGM_STS_LINK BIT(4) +#define VR_MII_DBG_CTRL 0x1f8005 +#define SUPPRESS_LOS_DET BIT(4) +#define RX_DT_EN_CTL BIT(6) +#define VR_MII_LINK_TIMER_CTRL 0x1f800a +#define VR_MII_DIG_STS 0x1f8010 +#define PSEQ_STATE_MASK GENMASK(4, 2) +#define POWER_GOOD_STATE 0x4 +#define VR_MII_GEN5_12G_16G_TX_GENCTRL1 0x1f8031 +#define TX_CLK_RDY_0 BIT(12) +#define VR_MII_GEN5_12G_16G_TX_GENCTRL2 0x1f8032 +#define TX_REQ_0 BIT(0) +#define VR_MII_GEN5_12G_16G_TX_RATE_CTRL 0x1f8034 +#define TX0_RATE_MASK GENMASK(2, 0) +#define TX0_BAUD_DIV_1 0 +#define TX0_BAUD_DIV_4 2 +#define VR_MII_GEN5_12G_16G_TX_EQ_CTRL0 0x1f8036 +#define TX_EQ_MAIN_MASK GENMASK(13, 8) +#define VR_MII_GEN5_12G_16G_TX_EQ_CTRL1 0x1f8037 +#define TX_EQ_OVR_RIDE BIT(6) +#define VR_MII_CONSUMER_10G_TX_TERM_CTRL 0x1f803c +#define TX0_TERM_MASK GENMASK(2, 0) +#define VR_MII_GEN5_12G_16G_RX_GENCTRL1 0x1f8051 +#define RX_RST_0 BIT(4) +#define VR_MII_GEN5_12G_16G_RX_GENCTRL2 0x1f8052 +#define RX_REQ_0 BIT(0) +#define VR_MII_GEN5_12G_16G_RX_RATE_CTRL 0x1f8054 +#define RX0_RATE_MASK GENMASK(1, 0) +#define RX0_BAUD_DIV_2 0x1 +#define RX0_BAUD_DIV_8 0x3 +#define VR_MII_GEN5_12G_16G_CDR_CTRL 0x1f8056 +#define VCO_LOW_FREQ_0 BIT(8) +#define VR_MII_GEN5_12G_16G_MPLL_CMN_CTRL 0x1f8070 +#define MPLLB_SEL_0 BIT(4) +#define VR_MII_GEN5_12G_16G_MPLLA_CTRL0 0x1f8071 +#define MPLLA_CAL_DISABLE BIT(15) +#define MLLA_MULTIPLIER_MASK GENMASK(7, 0) +#define VR_MII_GEN5_12G_MPLLA_CTRL1 0x1f8072 +#define MPLLA_FRACN_CTRL_MASK GENMASK(15, 5) +#define VR_MII_GEN5_12G_16G_MPLLA_CTRL2 0x1f8073 +#define MPLLA_TX_CLK_DIV_MASK GENMASK(13, 11) +#define MPLLA_DIV10_CLK_EN BIT(9) +#define VR_MII_GEN5_12G_16G_MPLLB_CTRL0 0x1f8074 +#define MPLLB_CAL_DISABLE BIT(15) +#define MLLB_MULTIPLIER_MASK GENMASK(7, 0) +#define VR_MII_GEN5_12G_MPLLB_CTRL1 0x1f8075 +#define MPLLB_FRACN_CTRL_MASK GENMASK(15, 5) +#define VR_MII_GEN5_12G_16G_MPLLB_CTRL2 0x1f8076 +#define MPLLB_TX_CLK_DIV_MASK GENMASK(13, 11) +#define MPLLB_DIV10_CLK_EN BIT(9) +#define VR_MII_GEN5_12G_MPLLA_CTRL3 0x1f8077 +#define MPLLA_BANDWIDTH_MASK GENMASK(15, 0) +#define VR_MII_GEN5_12G_MPLLB_CTRL3 0x1f8078 +#define MPLLB_BANDWIDTH_MASK GENMASK(15, 0) +#define VR_MII_GEN5_12G_16G_MISC_CTRL0 0x1f8090 +#define PLL_CTRL BIT(15) +#define VR_MII_GEN5_12G_16G_REF_CLK_CTRL 0x1f8091 +#define REF_USE_PAD BIT(1) +#define REF_CLK_DIV2 BIT(2) +#define REF_RANGE_MASK GENMASK(5, 3) +#define RANGE_26_53_MHZ 0x1 +#define RANGE_52_78_MHZ 0x2 +#define REF_MPLLA_DIV2 BIT(6) +#define REF_MPLLB_DIV2 BIT(7) +#define VR_MII_GEN5_12G_16G_VCO_CAL_LD0 0x1f8092 +#define VCO_LD_VAL_0_MASK GENMASK(12, 0) +#define VR_MII_GEN5_12G_VCO_CAL_REF0 0x1f8096 +#define VCO_REF_LD_0_MASK GENMASK(5, 0) + +typedef bool (*xpcs_poll_func_t)(struct s32g_xpcs *); + +/* + * XPCS registers can't be accessed directly and an indirect address method + * must be used instead. The 32-bit "reg" is the flat XPCS address and is + * identical to the shared core's "(devad << 16) | reg" key. + */ + +static const struct regmap_range s32g_xpcs_wr_ranges[] = { + regmap_reg_range(0x1f0000, 0x1f0000), + regmap_reg_range(0x1f0004, 0x1f0004), + regmap_reg_range(0x1f8000, 0x1f8003), + regmap_reg_range(0x1f8005, 0x1f8005), + regmap_reg_range(0x1f800a, 0x1f800a), + regmap_reg_range(0x1f8012, 0x1f8012), + regmap_reg_range(0x1f8015, 0x1f8015), + regmap_reg_range(0x1f8030, 0x1f8037), + regmap_reg_range(0x1f803c, 0x1f803e), + regmap_reg_range(0x1f8050, 0x1f8058), + regmap_reg_range(0x1f805c, 0x1f805e), + regmap_reg_range(0x1f8064, 0x1f8064), + regmap_reg_range(0x1f806b, 0x1f806b), + regmap_reg_range(0x1f8070, 0x1f8078), + regmap_reg_range(0x1f8090, 0x1f8092), + regmap_reg_range(0x1f8096, 0x1f8096), + regmap_reg_range(0x1f8099, 0x1f8099), + regmap_reg_range(0x1f80a0, 0x1f80a2), + regmap_reg_range(0x1f80e1, 0x1f80e1), +}; + +static const struct regmap_access_table s32g_xpcs_wr_table = { + .yes_ranges = s32g_xpcs_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(s32g_xpcs_wr_ranges), +}; + +static const struct regmap_range s32g_xpcs_rd_ranges[] = { + regmap_reg_range(0x1f0000, 0x1f0006), + regmap_reg_range(0x1f000f, 0x1f000f), + regmap_reg_range(0x1f0708, 0x1f0710), + regmap_reg_range(0x1f8000, 0x1f8003), + regmap_reg_range(0x1f8005, 0x1f8005), + regmap_reg_range(0x1f800a, 0x1f800a), + regmap_reg_range(0x1f8010, 0x1f8012), + regmap_reg_range(0x1f8015, 0x1f8015), + regmap_reg_range(0x1f8018, 0x1f8018), + regmap_reg_range(0x1f8020, 0x1f8020), + regmap_reg_range(0x1f8030, 0x1f8037), + regmap_reg_range(0x1f803c, 0x1f803c), + regmap_reg_range(0x1f8040, 0x1f8040), + regmap_reg_range(0x1f8050, 0x1f8058), + regmap_reg_range(0x1f805c, 0x1f805e), + regmap_reg_range(0x1f8060, 0x1f8060), + regmap_reg_range(0x1f8064, 0x1f8064), + regmap_reg_range(0x1f806b, 0x1f806b), + regmap_reg_range(0x1f8070, 0x1f8078), + regmap_reg_range(0x1f8090, 0x1f8092), + regmap_reg_range(0x1f8096, 0x1f8096), + regmap_reg_range(0x1f8098, 0x1f8099), + regmap_reg_range(0x1f80a0, 0x1f80a2), + regmap_reg_range(0x1f80e1, 0x1f80e1), +}; + +static const struct regmap_access_table s32g_xpcs_rd_table = { + .yes_ranges = s32g_xpcs_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(s32g_xpcs_rd_ranges), +}; + +static int s32g_xpcs_regmap_reg_read(void *context, unsigned int reg, + unsigned int *result) +{ + struct s32g_xpcs *xpcs = context; + u16 ofsleft = (reg >> 8) & 0xffffU; + u16 ofsright = (reg & 0xffU); + + writew(ofsleft, xpcs->base + ADDR1_OFS); + *result = readw(xpcs->base + (ofsright * 4)); + + return 0; +} + +static int s32g_xpcs_regmap_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + struct s32g_xpcs *xpcs = context; + u16 ofsleft = (reg >> 8) & 0xffffU; + u16 ofsright = (reg & 0xffU); + + writew(ofsleft, xpcs->base + ADDR1_OFS); + writew(val, xpcs->base + (ofsright * 4)); + + return 0; +} + +static const struct regmap_config s32g_xpcs0_regmap_config = { + .reg_bits = 32, + .val_bits = 16, + .reg_read = s32g_xpcs_regmap_reg_read, + .reg_write = s32g_xpcs_regmap_reg_write, + .wr_table = &s32g_xpcs_wr_table, + .rd_table = &s32g_xpcs_rd_table, + .max_register = 0x1f80e1, + .name = "xpcs0", +}; + +static const struct regmap_config s32g_xpcs1_regmap_config = { + .reg_bits = 32, + .val_bits = 16, + .reg_read = s32g_xpcs_regmap_reg_read, + .reg_write = s32g_xpcs_regmap_reg_write, + .wr_table = &s32g_xpcs_wr_table, + .rd_table = &s32g_xpcs_rd_table, + .max_register = 0x1f80e1, + .name = "xpcs1", +}; + +static void s32g_xpcs_write_bits(struct s32g_xpcs *xpcs, unsigned int reg, + unsigned int mask, unsigned int value) +{ + int ret = regmap_write_bits(xpcs->regmap, reg, mask, value); + + if (ret) + dev_err(xpcs->dev, "Failed to write bits of XPCS reg: 0x%x\n", reg); +} + +static void s32g_xpcs_write(struct s32g_xpcs *xpcs, unsigned int reg, + unsigned int value) +{ + int ret = regmap_write(xpcs->regmap, reg, value); + + if (ret) + dev_err(xpcs->dev, "Failed to write XPCS reg: 0x%x\n", reg); +} + +static unsigned int s32g_xpcs_read(struct s32g_xpcs *xpcs, unsigned int reg) +{ + unsigned int val = 0; + int ret; + + ret = regmap_read(xpcs->regmap, reg, &val); + if (ret) + dev_err(xpcs->dev, "Failed to read XPCS reg: 0x%x\n", reg); + + return val; +} + +/* + * Internal XPCS function + */ + +static int s32g_xpcs_wait(struct s32g_xpcs *xpcs, xpcs_poll_func_t func) +{ + bool val; + + return read_poll_timeout(func, val, val, XPCS_POLL_SLEEP_US, + XPCS_TIMEOUT_US, false, xpcs); +} + +static int s32g_xpcs_wait_bits(struct s32g_xpcs *xpcs, unsigned int reg, + unsigned int mask, unsigned int bits) +{ + unsigned int val; + + return read_poll_timeout(s32g_xpcs_read, val, (val & mask) == bits, + XPCS_POLL_SLEEP_US, XPCS_TIMEOUT_US, false, + xpcs, reg); +} + +static unsigned int s32g_xpcs_digital_status(struct s32g_xpcs *xpcs) +{ + return s32g_xpcs_read(xpcs, VR_MII_DIG_STS); +} + +static int s32g_xpcs_wait_power_good_state(struct s32g_xpcs *xpcs) +{ + unsigned int val; + + return read_poll_timeout(s32g_xpcs_digital_status, val, + FIELD_GET(PSEQ_STATE_MASK, val) == POWER_GOOD_STATE, + XPCS_POLL_SLEEP_US, + XPCS_TIMEOUT_US, false, xpcs); +} + +void s32g_xpcs_vreset(struct s32g_xpcs *xpcs) +{ + /* Step 19 */ + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, VR_RST, VR_RST); +} +EXPORT_SYMBOL_GPL(s32g_xpcs_vreset); + +static bool s32g_xpcs_is_not_in_reset(struct s32g_xpcs *xpcs) +{ + unsigned int val; + + val = s32g_xpcs_read(xpcs, VR_MII_DIG_CTRL1); + + return !(val & VR_RST); +} + +int s32g_xpcs_wait_vreset(struct s32g_xpcs *xpcs) +{ + int ret; + + /* Step 20 */ + ret = s32g_xpcs_wait(xpcs, s32g_xpcs_is_not_in_reset); + if (ret) + dev_err(xpcs->dev, "XPCS%d is in reset\n", xpcs->id); + + return ret; +} +EXPORT_SYMBOL_GPL(s32g_xpcs_wait_vreset); + +int s32g_xpcs_reset_rx(struct s32g_xpcs *xpcs) +{ + int ret = 0; + + /* + * On the shared 1G combo lane the PMA power-up is gated by the PCIe + * side and BYP_PWRUP stays set, so the power sequencer never reaches + * POWER_GOOD on its own and the wait would always time out. + */ + if (xpcs->pcie_shared != S32G_PCIE_XPCS_1G) { + ret = s32g_xpcs_wait_power_good_state(xpcs); + if (ret) { + dev_err(xpcs->dev, "Failed to enter in PGOOD state after vendor reset\n"); + return ret; + } + } + + /* Step 21 */ + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_RX_GENCTRL1, + RX_RST_0, RX_RST_0); + + /* Step 22 */ + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_RX_GENCTRL1, + RX_RST_0, 0); + + /* Step 23 */ + /* Wait until SR_MII_STS[LINK_STS] = 1 */ + + return ret; +} +EXPORT_SYMBOL_GPL(s32g_xpcs_reset_rx); + +static int s32g_xpcs_ref_clk_sel(struct s32g_xpcs *xpcs, + enum s32g_xpcs_pll ref_pll) +{ + switch (ref_pll) { + case S32G_XPCS_PLLA: + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLL_CMN_CTRL, + MPLLB_SEL_0, 0); + xpcs->ref = S32G_XPCS_PLLA; + break; + case S32G_XPCS_PLLB: + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLL_CMN_CTRL, + MPLLB_SEL_0, MPLLB_SEL_0); + xpcs->ref = S32G_XPCS_PLLB; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void s32g_xpcs_electrical_configure(struct s32g_xpcs *xpcs) +{ + /* Step 2 */ + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_EQ_CTRL0, + TX_EQ_MAIN_MASK, FIELD_PREP(TX_EQ_MAIN_MASK, 0xc)); + + /* Step 3 */ + s32g_xpcs_write_bits(xpcs, VR_MII_CONSUMER_10G_TX_TERM_CTRL, + TX0_TERM_MASK, FIELD_PREP(TX0_TERM_MASK, 0x4)); +} + +static int s32g_xpcs_vco_cfg(struct s32g_xpcs *xpcs, enum s32g_xpcs_pll vco_pll) +{ + unsigned int vco_ld = 0; + unsigned int vco_ref = 0; + unsigned int rx_baud = 0; + unsigned int tx_baud = 0; + + switch (vco_pll) { + case S32G_XPCS_PLLA: + if (xpcs->mhz125) { + vco_ld = FIELD_PREP(VCO_LD_VAL_0_MASK, 1360); + vco_ref = FIELD_PREP(VCO_REF_LD_0_MASK, 17); + } else { + vco_ld = FIELD_PREP(VCO_LD_VAL_0_MASK, 1350); + vco_ref = FIELD_PREP(VCO_REF_LD_0_MASK, 27); + } + + rx_baud = FIELD_PREP(RX0_RATE_MASK, RX0_BAUD_DIV_8); + tx_baud = FIELD_PREP(TX0_RATE_MASK, TX0_BAUD_DIV_4); + break; + case S32G_XPCS_PLLB: + if (xpcs->mhz125) { + vco_ld = FIELD_PREP(VCO_LD_VAL_0_MASK, 1350); + vco_ref = FIELD_PREP(VCO_REF_LD_0_MASK, 27); + } else { + vco_ld = FIELD_PREP(VCO_LD_VAL_0_MASK, 1344); + vco_ref = FIELD_PREP(VCO_REF_LD_0_MASK, 43); + } + + rx_baud = FIELD_PREP(RX0_RATE_MASK, RX0_BAUD_DIV_2); + tx_baud = FIELD_PREP(TX0_RATE_MASK, TX0_BAUD_DIV_1); + break; + default: + return -EINVAL; + } + + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_VCO_CAL_LD0, + VCO_LD_VAL_0_MASK, vco_ld); + + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_VCO_CAL_REF0, + VCO_REF_LD_0_MASK, vco_ref); + + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_RATE_CTRL, + TX0_RATE_MASK, tx_baud); + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_RX_RATE_CTRL, + RX0_RATE_MASK, rx_baud); + + if (vco_pll == S32G_XPCS_PLLB) { + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_CDR_CTRL, + VCO_LOW_FREQ_0, VCO_LOW_FREQ_0); + } else { + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_CDR_CTRL, + VCO_LOW_FREQ_0, 0); + } + + return 0; +} + +static void s32g_xpcs_init_mplla(struct s32g_xpcs *xpcs) +{ + unsigned int val; + + /* Step 7 */ + val = 0; + if (xpcs->ext_clk) + val |= REF_USE_PAD; + + if (xpcs->mhz125) { + val |= REF_MPLLA_DIV2; + val |= REF_CLK_DIV2; + val |= FIELD_PREP(REF_RANGE_MASK, RANGE_52_78_MHZ); + } else { + /* + * A non-125 MHz reference is 100 MHz, which falls in the + * 26-53 MHz range after the internal divider, so select + * RANGE_26_53_MHZ. + */ + val |= FIELD_PREP(REF_RANGE_MASK, RANGE_26_53_MHZ); + } + + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_REF_CLK_CTRL, + REF_MPLLA_DIV2 | REF_USE_PAD | REF_RANGE_MASK | + REF_CLK_DIV2, val); + + /* Step 8 */ + if (xpcs->mhz125) + val = FIELD_PREP(MLLA_MULTIPLIER_MASK, 80); + else + val = FIELD_PREP(MLLA_MULTIPLIER_MASK, 25); + + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLLA_CTRL0, + MPLLA_CAL_DISABLE | MLLA_MULTIPLIER_MASK, + val); + + /* Step 9 */ + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_MPLLA_CTRL1, + MPLLA_FRACN_CTRL_MASK, 0); + + /* Step 10 */ + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLLA_CTRL2, + MPLLA_TX_CLK_DIV_MASK | MPLLA_DIV10_CLK_EN, + FIELD_PREP(MPLLA_TX_CLK_DIV_MASK, 1) | MPLLA_DIV10_CLK_EN); + + /* Step 11 */ + if (xpcs->mhz125) + val = FIELD_PREP(MPLLA_BANDWIDTH_MASK, 43); + else + val = FIELD_PREP(MPLLA_BANDWIDTH_MASK, 357); + + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_MPLLA_CTRL3, + MPLLA_BANDWIDTH_MASK, val); +} + +static void s32g_xpcs_init_mpllb(struct s32g_xpcs *xpcs) +{ + unsigned int val; + + /* Step 7 */ + val = 0; + if (xpcs->ext_clk) + val |= REF_USE_PAD; + + if (xpcs->mhz125) { + val |= REF_MPLLB_DIV2; + val |= REF_CLK_DIV2; + val |= FIELD_PREP(REF_RANGE_MASK, RANGE_52_78_MHZ); + } else { + /* + * REF_RANGE selects the reference-clock frequency band and is + * shared by both PLLs. For a 100 MHz reference this is the + * 26-53 MHz range after the internal divider: RANGE_26_53_MHZ. + */ + val |= FIELD_PREP(REF_RANGE_MASK, RANGE_26_53_MHZ); + } + + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_REF_CLK_CTRL, + REF_MPLLB_DIV2 | REF_USE_PAD | REF_RANGE_MASK | + REF_CLK_DIV2, val); + + /* Step 8 */ + if (xpcs->mhz125) + val = FIELD_PREP(MLLB_MULTIPLIER_MASK, 125); + else + val = FIELD_PREP(MLLB_MULTIPLIER_MASK, 39); + + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLLB_CTRL0, + MPLLB_CAL_DISABLE | MLLB_MULTIPLIER_MASK, + val); + + /* Step 9 */ + if (xpcs->mhz125) + val = FIELD_PREP(MPLLB_FRACN_CTRL_MASK, 0); + else + val = FIELD_PREP(MPLLB_FRACN_CTRL_MASK, 1044); + + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_MPLLB_CTRL1, + MPLLB_FRACN_CTRL_MASK, val); + + /* Step 10 */ + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLLB_CTRL2, + MPLLB_TX_CLK_DIV_MASK | MPLLB_DIV10_CLK_EN, + FIELD_PREP(MPLLB_TX_CLK_DIV_MASK, 5) | MPLLB_DIV10_CLK_EN); + + /* Step 11 */ + if (xpcs->mhz125) + val = FIELD_PREP(MPLLB_BANDWIDTH_MASK, 68); + else + val = FIELD_PREP(MPLLB_BANDWIDTH_MASK, 102); + + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_MPLLB_CTRL3, + MPLLB_BANDWIDTH_MASK, val); +} + +static void s32g_serdes_pma_high_freq_recovery(struct s32g_xpcs *xpcs) +{ + /* PCS signal protection, PLL railout recovery */ + s32g_xpcs_write_bits(xpcs, VR_MII_DBG_CTRL, SUPPRESS_LOS_DET | RX_DT_EN_CTL, + SUPPRESS_LOS_DET | RX_DT_EN_CTL); + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MISC_CTRL0, + PLL_CTRL, PLL_CTRL); +} + +static void s32g_serdes_pma_configure_tx_eq_post(struct s32g_xpcs *xpcs) +{ + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_EQ_CTRL1, + TX_EQ_OVR_RIDE, TX_EQ_OVR_RIDE); +} + +static int s32g_serdes_bifurcation_pll_transit(struct s32g_xpcs *xpcs, + enum s32g_xpcs_pll target_pll) +{ + int ret = 0; + struct device *dev = xpcs->dev; + + /* Configure XPCS speed and VCO */ + if (target_pll == S32G_XPCS_PLLA) { + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, EN_2_5G_MODE, 0); + s32g_xpcs_vco_cfg(xpcs, S32G_XPCS_PLLA); + } else { + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, + EN_2_5G_MODE, EN_2_5G_MODE); + s32g_xpcs_vco_cfg(xpcs, S32G_XPCS_PLLB); + } + + /* Signal that clock are not available */ + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_GENCTRL1, + TX_CLK_RDY_0, 0); + + /* Select PLL reference */ + if (target_pll == S32G_XPCS_PLLA) + s32g_xpcs_ref_clk_sel(xpcs, S32G_XPCS_PLLA); + else + s32g_xpcs_ref_clk_sel(xpcs, S32G_XPCS_PLLB); + + /* Initiate transmitter TX reconfiguration request */ + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_GENCTRL2, + TX_REQ_0, TX_REQ_0); + + /* Wait for transmitter to reconfigure */ + ret = s32g_xpcs_wait_bits(xpcs, VR_MII_GEN5_12G_16G_TX_GENCTRL2, + TX_REQ_0, 0); + if (ret) { + dev_err(dev, "Switch to TX_REQ_0 failed\n"); + return ret; + } + + /* Initiate transmitter RX reconfiguration request */ + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_RX_GENCTRL2, + RX_REQ_0, RX_REQ_0); + + /* Wait for receiver to reconfigure */ + ret = s32g_xpcs_wait_bits(xpcs, VR_MII_GEN5_12G_16G_RX_GENCTRL2, + RX_REQ_0, 0); + if (ret) { + dev_err(dev, "Switch to RX_REQ_0 failed\n"); + return ret; + } + + /* Signal that clock are available */ + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_GENCTRL1, + TX_CLK_RDY_0, TX_CLK_RDY_0); + + /* Flush internal logic */ + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, INIT, INIT); + + /* Wait for init */ + ret = s32g_xpcs_wait_bits(xpcs, VR_MII_DIG_CTRL1, INIT, 0); + if (ret) { + dev_err(dev, "XPCS INIT failed\n"); + return ret; + } + + return ret; +} + +/* + * PMA hooks, run from the shared core's pcs_config() after the generic C37 + * SGMII / 2500BASE-X setup. The S32G context hangs off the shared + * nxp_serdes_xpcs priv pointer, set in s32g_xpcs_create(). + */ + +static int s32g_xpcs_pma_sgmii(struct nxp_serdes_xpcs *nxpcs) +{ + struct s32g_xpcs *xpcs = nxp_serdes_xpcs_get_drvdata(nxpcs); + int ret; + + /* + * The shared core sets PCS_MODE_SGMII, TX_CONFIG and MAC_AUTO_SW. The + * link timer differs on the PCIe-shared 2.5G lane, where + * CL37_TMR_OVRRIDE must be set for the override value to take effect. + */ + if (xpcs->pcie_shared == S32G_PCIE_XPCS_2G5) { + s32g_xpcs_write(xpcs, VR_MII_LINK_TIMER_CTRL, 0x2faf); + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, + MAC_AUTO_SW, MAC_AUTO_SW); + } else { + s32g_xpcs_write(xpcs, VR_MII_LINK_TIMER_CTRL, 0x7a1); + + /* + * Clearing EN_2_5G_MODE does not reprogram the PMA line rate: + * the VCO load, the baud dividers and MPLLB_SEL stay at 2.5G if + * the lane last came up as 2500BASE-X. A 2.5G to 1G + * renegotiation then links at 1000 Mbps while the PMA keeps + * clocking at 3.125 Gbaud and no traffic passes, so run the + * full transition back to PLLA. Idempotent on a fresh 1G lane. + */ + ret = s32g_serdes_bifurcation_pll_transit(xpcs, + S32G_XPCS_PLLA); + if (ret) + return ret; + } + + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, + CL37_TMR_OVRRIDE, CL37_TMR_OVRRIDE); + + return 0; +} + +static int s32g_xpcs_pma_2500basex(struct nxp_serdes_xpcs *nxpcs) +{ + struct s32g_xpcs *xpcs = nxp_serdes_xpcs_get_drvdata(nxpcs); + + /* + * The shared core has set EN_2_5G_MODE (DW_VR_MII_DIG_CTRL1_2G5_EN) + * via nxp_serdes_xpcs_config_2500basex(). S32G additionally needs the + * PMA switched from the slow PLLA to the fast PLLB and re-initialised. + */ + return s32g_serdes_bifurcation_pll_transit(xpcs, S32G_XPCS_PLLB); +} + +static int s32g_xpcs_pma_link_up_sgmii(struct nxp_serdes_xpcs *nxpcs, + int speed, int duplex) +{ + struct s32g_xpcs *xpcs = nxp_serdes_xpcs_get_drvdata(nxpcs); + + /* + * A 2.5G SGMII fixed-link keeps interface == SGMII and never reaches + * the 2500BASE-X hook, so the PMA would stay on the 1G-class PLLA that + * pma_sgmii() programmed at config time. Move to PLLB (which also sets + * EN_2_5G_MODE) once the link resolves to 2.5G; below that the + * config-time PLLA setting is already right. + */ + if (speed != SPEED_2500) + return 0; + + return s32g_serdes_bifurcation_pll_transit(xpcs, S32G_XPCS_PLLB); +} + +static const struct nxp_serdes_xpcs_compat s32g_xpcs_compat[] = { + { + .interface = PHY_INTERFACE_MODE_SGMII, + .supported = nxp_serdes_xpcs_sgmii_features, + .an_mode = NXP_SERDES_AN_C37_SGMII, + .pma_config = s32g_xpcs_pma_sgmii, + .pma_link_up = s32g_xpcs_pma_link_up_sgmii, + }, + { + .interface = PHY_INTERFACE_MODE_2500BASEX, + .supported = nxp_serdes_xpcs_2500basex_features, + .an_mode = NXP_SERDES_2500BASEX, + .pma_config = s32g_xpcs_pma_2500basex, + }, + { } +}; + +static const struct nxp_serdes_xpcs_desc s32g_xpcs_desc = { + .name = "s32g-serdes-xpcs", + .compat = s32g_xpcs_compat, + /* + * S32G runs its own vendor reset (VR_RST) as part of the ordered + * dual-XPCS bring-up barrier in the SerDes PHY driver, so the shared + * core must not issue the generic BMCR soft reset. S32G3 does not + * implement the EEE VR_MII_EEE_MCTRL0/1 registers either. + */ + .quirks = NXP_SERDES_QUIRK_NO_SOFT_RESET | NXP_SERDES_QUIRK_NO_EEE, + .reset = NULL, +}; + +/* + * Serdes functions for initializing/configuring/releasing the xpcs + */ + +int s32g_xpcs_init_plls(struct s32g_xpcs *xpcs) +{ + int ret; + + if (!xpcs->ext_clk) { + /* Step 1 */ + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, BYP_PWRUP, BYP_PWRUP); + } else if (xpcs->pcie_shared == S32G_NOT_SHARED) { + ret = s32g_xpcs_wait_power_good_state(xpcs); + if (ret) + return ret; + } else if (xpcs->pcie_shared == S32G_PCIE_XPCS_2G5) { + ret = s32g_xpcs_wait_power_good_state(xpcs); + if (ret) + return ret; + /* Configure equalization */ + s32g_serdes_pma_configure_tx_eq_post(xpcs); + s32g_xpcs_electrical_configure(xpcs); + + /* Enable receiver recover */ + s32g_serdes_pma_high_freq_recovery(xpcs); + return 0; + } + + s32g_xpcs_electrical_configure(xpcs); + + s32g_xpcs_ref_clk_sel(xpcs, S32G_XPCS_PLLA); + s32g_xpcs_init_mplla(xpcs); + s32g_xpcs_init_mpllb(xpcs); + s32g_xpcs_vco_cfg(xpcs, S32G_XPCS_PLLA); + + /* + * Step 18: for an internal reference BYP_PWRUP was set in Step 1 to + * bypass the power-up sequencer; clear it so the PMA powers up. The + * shared 1G lane keeps it set - there the PCIe side drives power-up + * and clearing it stalls the XPCS bring-up. + */ + if (!xpcs->ext_clk && xpcs->pcie_shared != S32G_PCIE_XPCS_1G) + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, BYP_PWRUP, 0); + + /* Will be cleared by Step 19 Vreset */ + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, AN_ENABLE, 0); + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, DUPLEX_MODE, DUPLEX_MODE); + + return 0; +} +EXPORT_SYMBOL_GPL(s32g_xpcs_init_plls); + +void s32g_xpcs_disable_an(struct s32g_xpcs *xpcs) +{ + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, DUPLEX_MODE, DUPLEX_MODE); + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, AN_ENABLE, 0); +} +EXPORT_SYMBOL_GPL(s32g_xpcs_disable_an); + +static void s32g_xpcs_pcs_destroy(void *pcs) +{ + nxp_serdes_xpcs_destroy(pcs); +} + +/** + * s32g_xpcs_create() - allocate and set up one XPCS instance + * @dev: the SerDes device the XPCS belongs to; owns the allocation + * @id: XPCS instance number within the SerDes subsystem (0 or 1) + * @base: mapped register block of that instance + * @ext_clk: reference is taken from the external pad rather than internal + * @rate: reference clock rate, 100 or 125 MHz + * @pcie_shared: whether the lane shares the combo PHY with PCIe + * + * The instance is devm-allocated against @dev, so it lives as long as the + * SerDes device and must not be freed by the caller. The returned handle + * is opaque; use s32g_xpcs_pcs() to obtain the phylink PCS created on top + * of the NXP SerDes xPCS shared core. + * + * Return: the XPCS handle, or an ERR_PTR() on failure. + */ +struct s32g_xpcs *s32g_xpcs_create(struct device *dev, unsigned char id, + void __iomem *base, bool ext_clk, + unsigned long rate, + enum s32g_xpcs_shared pcie_shared) +{ + const struct regmap_config *conf; + struct nxp_serdes_xpcs *nxpcs; + struct s32g_xpcs *xpcs; + struct phylink_pcs *pcs; + int ret; + + if (rate != (125 * HZ_PER_MHZ) && rate != (100 * HZ_PER_MHZ)) { + dev_err(dev, "XPCS cannot operate @%lu HZ\n", rate); + return ERR_PTR(-EINVAL); + } + + xpcs = devm_kzalloc(dev, sizeof(*xpcs), GFP_KERNEL); + if (!xpcs) + return ERR_PTR(-ENOMEM); + + xpcs->base = base; + xpcs->ext_clk = ext_clk; + xpcs->id = id; + xpcs->dev = dev; + xpcs->pcie_shared = pcie_shared; + + if (rate == (125 * HZ_PER_MHZ)) + xpcs->mhz125 = true; + else + xpcs->mhz125 = false; + + if (!id) + conf = &s32g_xpcs0_regmap_config; + else + conf = &s32g_xpcs1_regmap_config; + + xpcs->regmap = devm_regmap_init(dev, NULL, xpcs, conf); + if (IS_ERR(xpcs->regmap)) + return ERR_PTR(dev_err_probe(dev, PTR_ERR(xpcs->regmap), + "Failed to init register map\n")); + + /* + * Create the phylink PCS on the shared core, handing it the S32G + * regmap as the register transport. SGMII is the initial interface; + * phylink re-runs pcs_config() with the real interface at link time. + */ + pcs = nxp_serdes_xpcs_create(dev, xpcs->regmap, NULL, &s32g_xpcs_desc, + id, PHY_INTERFACE_MODE_SGMII); + if (IS_ERR(pcs)) + return ERR_PTR(dev_err_probe(dev, PTR_ERR(pcs), + "Failed to create SerDes xPCS\n")); + + ret = devm_add_action_or_reset(dev, s32g_xpcs_pcs_destroy, pcs); + if (ret) + return ERR_PTR(ret); + + /* Let the S32G PMA hooks recover their context from the shared PCS. */ + nxpcs = phylink_pcs_to_nxp_serdes_xpcs(pcs); + nxp_serdes_xpcs_set_drvdata(nxpcs, xpcs); + + xpcs->pcs = pcs; + + return xpcs; +} +EXPORT_SYMBOL_GPL(s32g_xpcs_create); + +/** + * s32g_xpcs_pcs() - the phylink PCS of an XPCS instance + * @xpcs: handle from s32g_xpcs_create() + * + * Return: the phylink_pcs, owned by the SerDes device. + */ +struct phylink_pcs *s32g_xpcs_pcs(struct s32g_xpcs *xpcs) +{ + return xpcs->pcs; +} +EXPORT_SYMBOL_GPL(s32g_xpcs_pcs); + +MODULE_DESCRIPTION("NXP S32G SerDes XPCS driver"); +MODULE_AUTHOR("Jan Petrous (OSS) <jan.petrous@oss.nxp.com>"); +MODULE_LICENSE("GPL");
diff --git a/include/linux/pcs/pcs-nxp-s32g-xpcs.h b/include/linux/pcs/pcs-nxp-s32g-xpcs.h
new file mode 100644
index 000000000000..7e1dfb6b638f
--- /dev/null
+++ b/include/linux/pcs/pcs-nxp-s32g-xpcs.h@@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2021-2026 NXP + */ +#ifndef PCS_NXP_S32G_XPCS_H +#define PCS_NXP_S32G_XPCS_H + +#include <linux/types.h> + +struct device; +struct phylink_pcs; + +/* + * Opaque. The XPCS state, including its regmap and the embedded + * phylink_pcs, is private to the PCS driver; the SerDes PHY driver only + * ever holds pointers handed out by s32g_xpcs_create(). + */ +struct s32g_xpcs; + +/* + * Whether the XPCS shares the combo PHY with the PCIe lane. In the shared + * modes the PMA power-up is driven from the PCIe side, which changes the + * bring-up sequence. + */ +enum s32g_xpcs_shared { + S32G_NOT_SHARED, + S32G_PCIE_XPCS_1G, + S32G_PCIE_XPCS_2G5, +}; + +struct s32g_xpcs *s32g_xpcs_create(struct device *dev, unsigned char id, + void __iomem *base, bool ext_clk, + unsigned long rate, + enum s32g_xpcs_shared pcie_shared); +struct phylink_pcs *s32g_xpcs_pcs(struct s32g_xpcs *xpcs); + +/* + * Bring-up steps, called by the SerDes PHY driver. They are separate + * because the dual-XPCS mode needs cross-instance barriers: every + * instance must complete a step before any instance starts the next. + */ +int s32g_xpcs_init_plls(struct s32g_xpcs *xpcs); +void s32g_xpcs_vreset(struct s32g_xpcs *xpcs); +int s32g_xpcs_wait_vreset(struct s32g_xpcs *xpcs); +int s32g_xpcs_reset_rx(struct s32g_xpcs *xpcs); +void s32g_xpcs_disable_an(struct s32g_xpcs *xpcs); + +#endif /* PCS_NXP_S32G_XPCS_H */
--
2.55.0