From: Jan Petrous via B4 Relay <devnull+jan.petrous.oss.nxp.com@kernel.org>
To: "Ciprian Marian Costea" <ciprianmarian.costea@oss.nxp.com>,
"NXP S32 Linux Team" <s32@nxp.com>,
"Vinod Koul" <vkoul@kernel.org>,
"Neil Armstrong" <neil.armstrong@linaro.org>,
"Manivannan Sadhasivam" <mani@kernel.org>,
"Rob Herring" <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
"Ghennadi Procopciuc" <ghennadi.procopciuc@nxp.com>,
"Andrew Lunn" <andrew+netdev@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
"Eric Dumazet" <edumazet@google.com>,
"Jakub Kicinski" <kuba@kernel.org>,
"Paolo Abeni" <pabeni@redhat.com>,
"Geert Uytterhoeven" <geert+renesas@glider.be>,
"Magnus Damm" <magnus.damm@gmail.com>,
"Lorenzo Pieralisi" <lpieralisi@kernel.org>,
"Krzysztof Wilczyński" <kwilczynski@kernel.org>,
"Bjorn Helgaas" <bhelgaas@google.com>,
"Bogdan Hamciuc" <bogdan.hamciuc@nxp.com>,
"Ionut Vicovan" <ionut.vicovan@nxp.com>,
"Andrew Lunn" <andrew@lunn.ch>,
"Heiner Kallweit" <hkallweit1@gmail.com>,
"Russell King" <linux@armlinux.org.uk>,
"Clark Wang" <xiaoning.wang@nxp.com>,
"Philipp Zabel" <p.zabel@pengutronix.de>,
"Maxime Chevallier" <maxime.chevallier@bootlin.com>,
"Maxime Coquelin" <mcoquelin.stm32@gmail.com>,
"Alexandre Torgue" <alexandre.torgue@foss.st.com>,
"Chester Lin" <chester62515@gmail.com>,
"Matthias Brugger" <mbrugger@suse.com>,
"Ghennadi Procopciuc" <ghennadi.procopciuc@oss.nxp.com>,
"Frank Li" <Frank.Li@nxp.com>,
"Sascha Hauer" <s.hauer@pengutronix.de>,
"Pengutronix Kernel Team" <kernel@pengutronix.de>,
"Fabio Estevam" <festevam@gmail.com>,
"Richard Cochran" <richardcochran@gmail.com>
Cc: linux-arm-kernel@lists.infradead.org,
linux-phy@lists.infradead.org, netdev@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-renesas-soc@vger.kernel.org, imx@lists.linux.dev,
linux-pci@vger.kernel.org,
linux-stm32@st-md-mailman.stormreply.com,
Vincent Guittot <vincent.guittot@linaro.org>,
"Jan Petrous (OSS)" <jan.petrous@oss.nxp.com>,
Alexandru-Catalin Ionita <alexandru-catalin.ionita@nxp.com>,
Ionut Vicovan <Ionut.Vicovan@nxp.com>,
Bogdan Roman <bogdan-gabriel.roman@nxp.com>
Subject: [PATCH RFC v3 06/12] phy: freescale: s32g: Add SerDes subsystem PHY
Date: Sat, 19 Sep 2026 08:54:34 +0200 [thread overview]
Message-ID: <20260919-s32g_serdes-v3-6-9d68868c1e89@oss.nxp.com> (raw)
In-Reply-To: <20260919-s32g_serdes-v3-0-9d68868c1e89@oss.nxp.com>
From: Vincent Guittot <vincent.guittot@linaro.org>
The S32G SoC family includes two SerDes subsystems, each made of one
PCIe controller, two XPCS and a shared 2-lane combo PHY. The lanes can
be configured to carry PCIe and/or SGMII.
Add the SerDes subsystem PHY driver: the PCIe PHY, the shared combo-PHY
bring-up, and the setup of the XPCS instances added by the previous
patch. The MAC obtains its phylink PCS through
s32g_serdes_pcs_create(), which also creates a device link so the MAC
is unbound and suspended before the SerDes it depends on.
The SS_RW_REG_0[SUBSYS_MODE] value is not taken from the devicetree.
It is derived at probe from the per-lane child nodes - the lane
function and, for an SGMII lane, the XPCS instance it feeds. Each
working mode in scope has a unique lane mux, so the first match is the
only match. The reference-clock rate is validated against the derived
mode; it is never used to select it. Per-lane link speed is a runtime
property and does not influence mode selection.
After the subsystem reset is released the PHY needs up to 5.2 ms to
become operational (MPLL_STATE). Poll it before any further PHY or
XPCS PMA access, on every working mode - relying on incidental timing
made the init sequence fragile.
SUBMODE_MASK is GENMASK(2, 0): valid SUBSYS_MODE values are 0 to 4 and
the vendor driver masks the field with 0x7.
Tested on an S32G3-VNP-RDB3 board in both modes the driver derives from
the lane children: mode 1 (PCIe on lane 0, XPCS0 on lane 1) and mode 3
(XPCS0 on lane 0, XPCS1 on lane 1). In mode 1 both consumers of the
shared subsystem are up at the same time - both PCIe root complexes
enumerate and GMAC0 links at 1G over SGMII. Mode 3 routes both lanes to
the XPCS instances, so it has no PCIe consumer.
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 <alexandru-catalin.ionita@nxp.com>
Signed-off-by: Alexandru-Catalin Ionita <alexandru-catalin.ionita@nxp.com>
Co-developed-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
Signed-off-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
Co-developed-by: Ionut Vicovan <Ionut.Vicovan@nxp.com>
Signed-off-by: Ionut Vicovan <Ionut.Vicovan@nxp.com>
Co-developed-by: Bogdan Roman <bogdan-gabriel.roman@nxp.com>
Signed-off-by: Bogdan Roman <bogdan-gabriel.roman@nxp.com>
Co-developed-by: Jan Petrous (OSS) <jan.petrous@oss.nxp.com>
Signed-off-by: Jan Petrous (OSS) <jan.petrous@oss.nxp.com>
---
drivers/phy/freescale/Kconfig | 9 +
drivers/phy/freescale/Makefile | 1 +
drivers/phy/freescale/phy-nxp-s32g-serdes.c | 1057 +++++++++++++++++++++++++++
include/linux/phy/nxp-s32g-serdes.h | 25 +
4 files changed, 1092 insertions(+)
diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig
index 7557b624be8f..59a86597eaa0 100644
--- a/drivers/phy/freescale/Kconfig
+++ b/drivers/phy/freescale/Kconfig
@@ -77,3 +77,12 @@ config PHY_FSL_LYNX_28G
found on NXP's Layerscape platforms such as LX2160A.
Used to change the protocol running on SerDes lanes at runtime.
Only useful for a restricted set of Ethernet protocols.
+
+config PHY_S32G_SERDES
+ tristate "NXP S32G SERDES support"
+ depends on ARCH_S32 || COMPILE_TEST
+ depends on PCS_NXP_S32G_XPCS
+ select GENERIC_PHY
+ help
+ This option enables support for S32G SerDes PHY used for
+ PCIe & Ethernet
diff --git a/drivers/phy/freescale/Makefile b/drivers/phy/freescale/Makefile
index 5b0e180d6972..1a0aec790917 100644
--- a/drivers/phy/freescale/Makefile
+++ b/drivers/phy/freescale/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_PHY_FSL_LYNX_CORE) += phy-fsl-lynx-core.o
obj-$(CONFIG_PHY_FSL_LYNX_10G) += phy-fsl-lynx-10g.o
obj-$(CONFIG_PHY_FSL_LYNX_28G) += phy-fsl-lynx-28g.o
obj-$(CONFIG_PHY_FSL_SAMSUNG_HDMI_PHY) += phy-fsl-samsung-hdmi.o
+obj-$(CONFIG_PHY_S32G_SERDES) += phy-nxp-s32g-serdes.o
diff --git a/drivers/phy/freescale/phy-nxp-s32g-serdes.c b/drivers/phy/freescale/phy-nxp-s32g-serdes.c
new file mode 100644
index 000000000000..e81878137f2e
--- /dev/null
+++ b/drivers/phy/freescale/phy-nxp-s32g-serdes.c
@@ -0,0 +1,1057 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SerDes driver for S32G SoCs
+ *
+ * Copyright 2021-2026 NXP
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/pcs/pcs-nxp-s32g-xpcs.h>
+#include <linux/phy/nxp-s32g-serdes.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/units.h>
+
+#define S32G_SERDES_XPCS_MAX 2
+
+#define EXTERNAL_CLK_NAME "ext"
+#define INTERNAL_CLK_NAME "ref"
+
+/* Serdes Sub system registers */
+
+#define S32G_PCIE_PHY_GEN_CTRL 0x0
+#define REF_USE_PAD BIT(17)
+#define RX_SRIS_MODE BIT(9)
+
+#define S32G_PCIE_PHY_MPLLA_CTRL 0x10
+#define MPLL_STATE BIT(30)
+
+#define S32G_SS_RW_REG_0 0xF0
+#define SUBMODE_MASK GENMASK(2, 0)
+#define CLKEN_MASK BIT(23)
+#define PHY0_CR_PARA_SEL BIT(9)
+
+/* PCIe phy subsystem registers */
+
+#define S32G_PHY_REG_ADDR 0x0
+#define PHY_REG_EN BIT(31)
+
+#define S32G_PHY_REG_DATA 0x4
+
+#define RAWLANE0_DIG_PCS_XF_RX_EQ_DELTA_IQ_OVRD_IN 0x3019
+#define RAWLANE1_DIG_PCS_XF_RX_EQ_DELTA_IQ_OVRD_IN 0x3119
+
+/*
+ * DesignWare PCIe PHY reference-clock architecture, passed as the submode
+ * to phy_set_mode_ext(phy, PHY_MODE_PCIE, submode).
+ *
+ * These describe how the reference clock is distributed and whether spread
+ * spectrum is used, a different axis from the role submodes in
+ * include/linux/phy/pcie.h (PHY_MODE_PCIE_RC/_EP/_BIFURCATION, 20..22), which
+ * they do not overlap. Kept driver-private until a second driver needs a
+ * non-default architecture; pcie-nxp-s32g.c always asks for CRNS.
+ */
+enum pcie_phy_mode {
+ CRNS = 0, /* Common Reference Clock, No Spread Spectrum */
+ CRSS = 1, /* Common Reference Clock, Spread Spectrum */
+ SRNS = 2, /* Separate Reference Clock, No Spread Spectrum */
+ SRIS = 3 /* Separate Reference Clock, Spread Spectrum */
+};
+
+/*
+ * SS_RW_REG_0[SUBSYS_MODE] holds the reference-manual "Mode N" number. It is
+ * not in the devicetree: it is derived from the per-lane child nodes (lane
+ * function + XPCS instance). The reference clock is validated against the
+ * result, never used to select it.
+ */
+
+enum s32g_lane_fn {
+ S32G_LANE_UNUSED = 0,
+ S32G_LANE_PCIE,
+ S32G_LANE_XPCS,
+};
+
+struct s32g_lane_desc {
+ enum s32g_lane_fn fn;
+ u32 xpcs_instance; /* valid iff fn == S32G_LANE_XPCS */
+};
+
+struct s32g_serdes_mode {
+ u32 submode; /* SS_RW_REG_0[SUBSYS_MODE] value */
+ struct s32g_lane_desc lane[2];
+ unsigned long refclk[2]; /* permitted reference clock rates (Hz), 0-terminated */
+};
+
+struct s32g_serdes_soc_data {
+ const struct s32g_serdes_mode *modes;
+ unsigned int n_modes;
+};
+
+#define S32G_LANE_IS_PCIE { .fn = S32G_LANE_PCIE }
+#define S32G_LANE_IS_XPCS(i) { .fn = S32G_LANE_XPCS, .xpcs_instance = (i) }
+
+#define S32G_REFCLK_100MHZ (100 * HZ_PER_MHZ)
+#define S32G_REFCLK_125MHZ (125 * HZ_PER_MHZ)
+
+/*
+ * Supported working modes (S32G2/S32G3, 1G). Each row has a unique lane mux.
+ * PCIe needs a 100 MHz reference, so modes 0/1/2 take 100 MHz only; the
+ * dual-XPCS mode 3 takes 100 or 125 MHz.
+ */
+static const struct s32g_serdes_mode s32g_serdes_modes[] = {
+ { 0, { S32G_LANE_IS_PCIE, S32G_LANE_IS_PCIE },
+ { S32G_REFCLK_100MHZ } },
+ { 1, { S32G_LANE_IS_PCIE, S32G_LANE_IS_XPCS(0) },
+ { S32G_REFCLK_100MHZ } },
+ { 2, { S32G_LANE_IS_PCIE, S32G_LANE_IS_XPCS(1) },
+ { S32G_REFCLK_100MHZ } },
+ { 3, { S32G_LANE_IS_XPCS(0), S32G_LANE_IS_XPCS(1) },
+ { S32G_REFCLK_100MHZ, S32G_REFCLK_125MHZ } },
+};
+
+/*
+ * S32G2 and S32G3 share this table for now but keep distinct compatibles with
+ * no fallback: the full RM mode tables differ per SoC and per SerDes instance
+ * (only S32G3 SerDes_1 has the 3.125G dual-XPCS mode 4) and will diverge once
+ * 2500BASE-X lands.
+ */
+static const struct s32g_serdes_soc_data s32g2_serdes_soc_data = {
+ .modes = s32g_serdes_modes,
+ .n_modes = ARRAY_SIZE(s32g_serdes_modes),
+};
+
+static const struct s32g_serdes_soc_data s32g3_serdes_soc_data = {
+ .modes = s32g_serdes_modes,
+ .n_modes = ARRAY_SIZE(s32g_serdes_modes),
+};
+
+struct s32g_serdes_ctrl {
+ void __iomem *ss_base;
+ struct reset_control *rst;
+ struct clk_bulk_data *clks;
+ int nclks;
+ u32 ss_mode;
+ unsigned long ref_clk_rate;
+ bool ext_clk;
+};
+
+struct s32g_pcie_ctrl {
+ void __iomem *phy_base;
+ struct reset_control *rst;
+ struct phy *phy;
+ enum pcie_phy_mode phy_mode;
+ bool powered_on;
+};
+
+struct s32g_xpcs_ctrl {
+ struct s32g_xpcs *phys[2]; /* indexed by XPCS instance */
+ void __iomem *base[2]; /* xpcs0 / xpcs1 register blocks */
+};
+
+struct s32g_serdes {
+ struct s32g_serdes_ctrl ctrl;
+ struct s32g_pcie_ctrl pcie;
+ struct s32g_xpcs_ctrl xpcs;
+ struct s32g_lane_desc lane[2];
+ const struct s32g_serdes_soc_data *soc_data;
+ struct device *dev;
+};
+
+/* PCIe phy subsystem */
+
+#define S32G_SERDES_PCIE_FREQ (100 * HZ_PER_MHZ)
+
+static int s32g_pcie_check_clk(struct s32g_serdes *serdes)
+{
+ struct s32g_serdes_ctrl *sctrl = &serdes->ctrl;
+ unsigned long rate = sctrl->ref_clk_rate;
+
+ if (rate != S32G_SERDES_PCIE_FREQ) {
+ dev_err(serdes->dev, "PCIe PHY cannot operate at %lu HZ\n", rate);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Serdes RM says the PHY is operational within 3.4 to 5.2 ms after reset
+ * release, depending on the reference clock (MPLL_STATE = 1).
+ */
+#define S32G_SERDES_LOCK_TIMEOUT_US (10 * USEC_PER_MSEC)
+#define S32G_SERDES_LOCK_SLEEP_US 50
+
+/*
+ * MPLL_STATE signals "PHY operational" after the subsystem reset is released.
+ * It must be observed before any PHY or XPCS PMA access in every mode, not
+ * just on the PCIe path: skipping it on the XPCS-only modes left init
+ * dependent on incidental timing, and a printk between reset release and XPCS
+ * setup was enough to break it.
+ */
+static int s32g_serdes_wait_phy_operational(struct s32g_serdes *serdes)
+{
+ void __iomem *reg = serdes->ctrl.ss_base + S32G_PCIE_PHY_MPLLA_CTRL;
+ u32 val;
+ int ret;
+
+ ret = readl_poll_timeout(reg, val, (val & MPLL_STATE),
+ S32G_SERDES_LOCK_SLEEP_US,
+ S32G_SERDES_LOCK_TIMEOUT_US);
+ if (ret)
+ dev_err(serdes->dev, "SerDes PHY not operational\n");
+
+ return ret;
+}
+
+static int s32g_pcie_phy_power_on_common(struct s32g_serdes *serdes)
+{
+ struct s32g_serdes_ctrl *sctrl = &serdes->ctrl;
+ struct s32g_pcie_ctrl *pcie = &serdes->pcie;
+ u32 reg;
+ int ret;
+
+ ret = s32g_pcie_check_clk(serdes);
+ if (ret)
+ return ret;
+
+ reg = readl(sctrl->ss_base + S32G_PCIE_PHY_GEN_CTRL);
+
+ if (pcie->phy_mode == SRIS)
+ reg |= RX_SRIS_MODE;
+ else
+ reg &= ~RX_SRIS_MODE;
+
+ if (sctrl->ext_clk)
+ reg |= REF_USE_PAD;
+ else
+ reg &= ~REF_USE_PAD;
+
+ writel(reg, sctrl->ss_base + S32G_PCIE_PHY_GEN_CTRL);
+
+ ret = s32g_serdes_wait_phy_operational(serdes);
+ if (ret)
+ return ret;
+
+ /* Set PHY register access to CR interface */
+ reg = readl(sctrl->ss_base + S32G_SS_RW_REG_0);
+ reg |= PHY0_CR_PARA_SEL;
+ writel(reg, sctrl->ss_base + S32G_SS_RW_REG_0);
+
+ return 0;
+}
+
+static void s32g_pcie_phy_write(struct s32g_serdes *serdes, u32 reg, u32 val)
+{
+ writel(PHY_REG_EN, serdes->pcie.phy_base + S32G_PHY_REG_ADDR);
+ writel(reg | PHY_REG_EN, serdes->pcie.phy_base + S32G_PHY_REG_ADDR);
+ usleep_range(100, 110);
+ writel(val, serdes->pcie.phy_base + S32G_PHY_REG_DATA);
+ usleep_range(100, 110);
+ writel(0, serdes->pcie.phy_base + S32G_PHY_REG_ADDR);
+}
+
+static int s32g_pcie_phy_power_on(struct s32g_serdes *serdes)
+{
+ struct s32g_pcie_ctrl *pcie = &serdes->pcie;
+ struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
+ u32 iq_ovrd_in;
+ int ret;
+
+ ret = s32g_pcie_phy_power_on_common(serdes);
+ if (ret)
+ return ret;
+
+ /* RX_EQ_DELTA_IQ_OVRD enable and override value for PCIe lanes */
+ iq_ovrd_in = RAWLANE0_DIG_PCS_XF_RX_EQ_DELTA_IQ_OVRD_IN;
+
+ s32g_pcie_phy_write(serdes, iq_ovrd_in, 0x3);
+ s32g_pcie_phy_write(serdes, iq_ovrd_in, 0x13);
+
+ if (ctrl->ss_mode == 0) {
+ iq_ovrd_in = RAWLANE1_DIG_PCS_XF_RX_EQ_DELTA_IQ_OVRD_IN;
+
+ s32g_pcie_phy_write(serdes, iq_ovrd_in, 0x3);
+ s32g_pcie_phy_write(serdes, iq_ovrd_in, 0x13);
+ }
+
+ pcie->powered_on = true;
+
+ return 0;
+}
+
+/* PCIe phy ops function */
+
+static int s32g_serdes_phy_power_on(struct phy *p)
+{
+ struct s32g_serdes *serdes = phy_get_drvdata(p);
+
+ return s32g_pcie_phy_power_on(serdes);
+}
+
+static int s32g_serdes_phy_power_off(struct phy *p)
+{
+ struct s32g_serdes *serdes = phy_get_drvdata(p);
+
+ serdes->pcie.powered_on = false;
+
+ return 0;
+}
+
+static inline bool is_pcie_phy_mode_valid(int mode)
+{
+ switch (mode) {
+ case CRNS:
+ case CRSS:
+ case SRNS:
+ case SRIS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int s32g_serdes_phy_set_mode_ext(struct phy *p,
+ enum phy_mode mode, int submode)
+{
+ struct s32g_serdes *serdes = phy_get_drvdata(p);
+
+ if (mode != PHY_MODE_PCIE)
+ return -EINVAL;
+
+ if (!is_pcie_phy_mode_valid(submode))
+ return -EINVAL;
+
+ /*
+ * Spread-spectrum and common-reference clocking both need an
+ * external shared reference, and neither may be combined with an
+ * SGMII lane on the same subsystem.
+ */
+ if ((submode == CRSS || submode == SRIS) &&
+ (serdes->ctrl.ss_mode != 0 || !serdes->ctrl.ext_clk))
+ return -EINVAL;
+
+ if (serdes->pcie.powered_on)
+ dev_warn(serdes->dev, "The phy is already powered on.\n");
+
+ serdes->pcie.phy_mode = submode;
+
+ return 0;
+}
+
+static const struct phy_ops serdes_pcie_ops = {
+ .power_on = s32g_serdes_phy_power_on,
+ .power_off = s32g_serdes_phy_power_off,
+ .set_mode = s32g_serdes_phy_set_mode_ext,
+};
+
+static struct phy *s32g_serdes_phy_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ struct s32g_serdes *serdes;
+
+ serdes = dev_get_drvdata(dev);
+ if (!serdes)
+ return ERR_PTR(-EINVAL);
+
+ return serdes->pcie.phy;
+}
+
+/* XPCS subsystem */
+
+static int s32g_serdes_xpcs_setup(struct s32g_serdes *serdes, u32 instance)
+{
+ struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
+ struct s32g_xpcs_ctrl *xpcs_ctrl = &serdes->xpcs;
+ enum s32g_xpcs_shared shared = S32G_NOT_SHARED;
+ struct device *dev = serdes->dev;
+ struct s32g_xpcs *xpcs;
+
+ /*
+ * In the PCIe + SGMII modes the XPCS shares the combo PHY with the
+ * PCIe lane. The 2.5G shared variant (S32G_PCIE_XPCS_2G5) is selected at
+ * run time and is not wired up yet.
+ */
+ if (ctrl->ss_mode == 1 || ctrl->ss_mode == 2)
+ shared = S32G_PCIE_XPCS_1G;
+
+ xpcs = s32g_xpcs_create(dev, instance, xpcs_ctrl->base[instance],
+ ctrl->ext_clk, ctrl->ref_clk_rate, shared);
+ if (IS_ERR(xpcs))
+ return PTR_ERR(xpcs);
+
+ xpcs_ctrl->phys[instance] = xpcs;
+
+ return 0;
+}
+
+static int s32g_serdes_init_xpcs(struct s32g_serdes *serdes)
+{
+ struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
+ struct s32g_xpcs_ctrl *xpcs = &serdes->xpcs;
+ struct s32g_xpcs *order[2];
+ size_t i;
+ int ret;
+
+ /*
+ * Mode 3 initialises XPCS1 before XPCS0. Carried over from the vendor
+ * driver and undocumented in the RM. Do not reorder.
+ */
+ switch (ctrl->ss_mode) {
+ case 0:
+ return 0;
+ case 1:
+ order[0] = xpcs->phys[0];
+ order[1] = NULL;
+ break;
+ case 2:
+ order[0] = xpcs->phys[1];
+ order[1] = NULL;
+ break;
+ case 3:
+ order[0] = xpcs->phys[1];
+ order[1] = xpcs->phys[0];
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(order); i++) {
+ if (!order[i])
+ continue;
+
+ ret = s32g_xpcs_init_plls(order[i]);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(order); i++) {
+ if (!order[i])
+ continue;
+
+ s32g_xpcs_vreset(order[i]);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(order); i++) {
+ if (!order[i])
+ continue;
+
+ ret = s32g_xpcs_wait_vreset(order[i]);
+ if (ret)
+ return ret;
+
+ ret = s32g_xpcs_reset_rx(order[i]);
+ if (ret)
+ return ret;
+
+ s32g_xpcs_disable_an(order[i]);
+ }
+
+ return 0;
+}
+
+/* Serdes subsystem */
+
+static int s32g_serdes_assert_reset(struct s32g_serdes *serdes)
+{
+ struct device *dev = serdes->dev;
+ int ret;
+
+ ret = reset_control_assert(serdes->pcie.rst);
+ if (ret) {
+ dev_err(dev, "Failed to assert PCIE reset: %d\n", ret);
+ return ret;
+ }
+
+ ret = reset_control_assert(serdes->ctrl.rst);
+ if (ret) {
+ dev_err(dev, "Failed to assert SerDes reset: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int s32g_serdes_deassert_reset(struct s32g_serdes *serdes)
+{
+ struct device *dev = serdes->dev;
+ int ret;
+
+ ret = reset_control_deassert(serdes->pcie.rst);
+ if (ret) {
+ dev_err(dev, "Failed to deassert PCIE reset: %d\n", ret);
+ return ret;
+ }
+
+ ret = reset_control_deassert(serdes->ctrl.rst);
+ if (ret) {
+ dev_err(dev, "Failed to deassert SerDes reset: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static bool s32g_serdes_lane_matches(const struct s32g_lane_desc *mode_lane,
+ const struct s32g_lane_desc *req_lane)
+{
+ if (mode_lane->fn != req_lane->fn)
+ return false;
+
+ if (mode_lane->fn == S32G_LANE_XPCS)
+ return mode_lane->xpcs_instance == req_lane->xpcs_instance;
+
+ return true;
+}
+
+static const char *s32g_lane_fn_str(const struct s32g_lane_desc *lane)
+{
+ switch (lane->fn) {
+ case S32G_LANE_PCIE:
+ return "PCIe";
+ case S32G_LANE_XPCS:
+ return lane->xpcs_instance ? "XPCS1" : "XPCS0";
+ default:
+ return "unused";
+ }
+}
+
+static int s32g_serdes_derive_mode(struct s32g_serdes *serdes)
+{
+ const struct s32g_serdes_mode *match = NULL;
+ unsigned int i;
+ bool refclk_ok = false;
+
+ for (i = 0; i < serdes->soc_data->n_modes; i++) {
+ const struct s32g_serdes_mode *m = &serdes->soc_data->modes[i];
+
+ if (s32g_serdes_lane_matches(&m->lane[0], &serdes->lane[0]) &&
+ s32g_serdes_lane_matches(&m->lane[1], &serdes->lane[1])) {
+ match = m;
+ break;
+ }
+ }
+
+ if (!match)
+ return dev_err_probe(serdes->dev, -EINVAL,
+ "No SerDes mode matches the lane configuration\n");
+
+ for (i = 0; i < ARRAY_SIZE(match->refclk) && match->refclk[i]; i++) {
+ if (serdes->ctrl.ref_clk_rate == match->refclk[i]) {
+ refclk_ok = true;
+ break;
+ }
+ }
+
+ if (!refclk_ok)
+ return dev_err_probe(serdes->dev, -EINVAL,
+ "Reference clock %lu Hz invalid for mode %u\n",
+ serdes->ctrl.ref_clk_rate, match->submode);
+
+ serdes->ctrl.ss_mode = match->submode;
+
+ dev_dbg(serdes->dev,
+ "SerDes mode %u selected: lane0=%s lane1=%s, ref-clk %lu MHz (%s)\n",
+ match->submode,
+ s32g_lane_fn_str(&serdes->lane[0]),
+ s32g_lane_fn_str(&serdes->lane[1]),
+ serdes->ctrl.ref_clk_rate / HZ_PER_MHZ,
+ serdes->ctrl.ext_clk ? "external" : "internal");
+
+ return 0;
+}
+
+static int s32g_serdes_init(struct s32g_serdes *serdes)
+{
+ struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
+ u32 reg0;
+ int ret;
+
+ ret = clk_bulk_prepare_enable(ctrl->nclks, ctrl->clks);
+ if (ret) {
+ dev_err(serdes->dev, "Failed to enable SerDes clocks\n");
+ return ret;
+ }
+
+ ret = s32g_serdes_assert_reset(serdes);
+ if (ret)
+ goto disable_clks;
+
+ /* Set serdes mode */
+ reg0 = readl(ctrl->ss_base + S32G_SS_RW_REG_0);
+ reg0 &= ~SUBMODE_MASK;
+ reg0 |= FIELD_PREP(SUBMODE_MASK, ctrl->ss_mode);
+ writel(reg0, ctrl->ss_base + S32G_SS_RW_REG_0);
+
+ /* Set Clock source: internal or external */
+ reg0 = readl(ctrl->ss_base + S32G_SS_RW_REG_0);
+ if (ctrl->ext_clk)
+ reg0 &= ~CLKEN_MASK;
+ else
+ reg0 |= CLKEN_MASK;
+
+ writel(reg0, ctrl->ss_base + S32G_SS_RW_REG_0);
+
+ /*
+ * Route the combo-PHY reference: REF_USE_PAD is 0 for the internal
+ * reference, 1 for the external pad. s32g_pcie_phy_power_on_common()
+ * programs it too, but the XPCS lane is brought up before any PCIe
+ * power-on. Left at the bootloader default the XPCS PMA never powers
+ * up, VR_RST never self-clears and every register reads back constant.
+ */
+ reg0 = readl(ctrl->ss_base + S32G_PCIE_PHY_GEN_CTRL);
+ if (ctrl->ext_clk)
+ reg0 |= REF_USE_PAD;
+ else
+ reg0 &= ~REF_USE_PAD;
+ writel(reg0, ctrl->ss_base + S32G_PCIE_PHY_GEN_CTRL);
+ dev_dbg(serdes->dev,
+ "GEN_CTRL after ref-clk route: 0x%08x (REF_USE_PAD=%d)\n",
+ reg0, !!(reg0 & REF_USE_PAD));
+
+ /* Wait for the selection of working mode (as per the manual specs) */
+ usleep_range(100, 110);
+
+ ret = s32g_serdes_deassert_reset(serdes);
+ if (ret)
+ goto disable_clks;
+
+ /*
+ * The PHY needs up to 5.2 ms after reset release to become
+ * operational. Nothing may touch the PHY or the XPCS PMA before
+ * that point.
+ */
+ ret = s32g_serdes_wait_phy_operational(serdes);
+ if (ret)
+ goto disable_clks;
+
+ ret = s32g_serdes_init_xpcs(serdes);
+ if (ret) {
+ dev_err(serdes->dev, "XPCS init failed\n");
+ goto disable_clks;
+ }
+
+ dev_info(serdes->dev, "SerDes subsystem mode %u (refclk=%lu MHz)\n",
+ ctrl->ss_mode, ctrl->ref_clk_rate / HZ_PER_MHZ);
+
+ return 0;
+
+disable_clks:
+ /* Already on an error path; ignore the return value. */
+ s32g_serdes_assert_reset(serdes);
+ clk_bulk_disable_unprepare(serdes->ctrl.nclks,
+ serdes->ctrl.clks);
+
+ return ret;
+}
+
+static void s32g_serdes_disable(void *data)
+{
+ struct s32g_serdes *serdes = data;
+
+ s32g_serdes_assert_reset(serdes);
+ clk_bulk_disable_unprepare(serdes->ctrl.nclks, serdes->ctrl.clks);
+}
+
+static int s32g_serdes_get_ctrl_resources(struct platform_device *pdev,
+ struct s32g_serdes *serdes)
+{
+ struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
+ struct device *dev = &pdev->dev;
+ int ret, idx, i;
+
+ ctrl->ss_base = devm_platform_ioremap_resource_byname(pdev, "ss-pcie");
+ if (IS_ERR(ctrl->ss_base))
+ return dev_err_probe(dev, PTR_ERR(ctrl->ss_base),
+ "Failed to map 'ss-pcie'\n");
+
+ ctrl->rst = devm_reset_control_get_exclusive(dev, "serdes");
+ if (IS_ERR(ctrl->rst))
+ return dev_err_probe(dev, PTR_ERR(ctrl->rst),
+ "Failed to get 'serdes' reset control\n");
+
+ ctrl->nclks = devm_clk_bulk_get_all(dev, &ctrl->clks);
+ if (ctrl->nclks < 1) {
+ ret = ctrl->nclks ? : -EINVAL;
+ return dev_err_probe(dev, ret,
+ "Failed to get SerDes clocks\n");
+ }
+
+ idx = -1;
+ for (i = 0; i < ctrl->nclks; i++) {
+ if (!ctrl->clks[i].id)
+ continue;
+
+ if (!strcmp(ctrl->clks[i].id, EXTERNAL_CLK_NAME)) {
+ idx = i;
+ ctrl->ext_clk = true;
+ break;
+ }
+
+ if (!strcmp(ctrl->clks[i].id, INTERNAL_CLK_NAME))
+ idx = i;
+ }
+
+ if (idx < 0) {
+ dev_err(dev, "Failed to get Phy reference clock source\n");
+ return -EINVAL;
+ }
+
+ ctrl->ref_clk_rate = clk_get_rate(ctrl->clks[idx].clk);
+ if (!ctrl->ref_clk_rate) {
+ dev_err(dev, "Failed to get Phy reference clock rate\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int s32g_serdes_get_pcie_resources(struct platform_device *pdev,
+ struct s32g_serdes *serdes)
+{
+ struct s32g_pcie_ctrl *pcie = &serdes->pcie;
+ struct device *dev = &pdev->dev;
+
+ pcie->phy_base = devm_platform_ioremap_resource_byname(pdev,
+ "pcie-phy");
+ if (IS_ERR(pcie->phy_base))
+ return dev_err_probe(dev, PTR_ERR(pcie->phy_base),
+ "Failed to map 'pcie-phy'\n");
+
+ pcie->rst = devm_reset_control_get_exclusive(dev, "pcie");
+ if (IS_ERR(pcie->rst))
+ return dev_err_probe(dev, PTR_ERR(pcie->rst),
+ "Failed to get 'pcie' reset control\n");
+
+ return 0;
+}
+
+static int s32g_serdes_get_xpcs_resources(struct platform_device *pdev,
+ struct s32g_serdes *serdes)
+{
+ struct s32g_xpcs_ctrl *xpcs = &serdes->xpcs;
+ struct device *dev = &pdev->dev;
+
+ xpcs->base[0] = devm_platform_ioremap_resource_byname(pdev, "xpcs0");
+ if (IS_ERR(xpcs->base[0]))
+ return dev_err_probe(dev, PTR_ERR(xpcs->base[0]),
+ "Failed to map 'xpcs0'\n");
+
+ xpcs->base[1] = devm_platform_ioremap_resource_byname(pdev, "xpcs1");
+ if (IS_ERR(xpcs->base[1]))
+ return dev_err_probe(dev, PTR_ERR(xpcs->base[1]),
+ "Failed to map 'xpcs1'\n");
+
+ return 0;
+}
+
+static int s32g_serdes_parse_lane(struct s32g_serdes *serdes,
+ struct device_node *child_node)
+{
+ struct device *dev = serdes->dev;
+ u32 index, instance;
+ int ret;
+
+ ret = of_property_read_u32(child_node, "reg", &index);
+ if (ret || index > 1)
+ return dev_err_probe(dev, ret ? : -EINVAL,
+ "Invalid lane index in %pOFn\n",
+ child_node);
+
+ if (serdes->lane[index].fn != S32G_LANE_UNUSED)
+ return dev_err_probe(dev, -EINVAL,
+ "Duplicate lane %u\n", index);
+
+ if (of_device_is_compatible(child_node, "nxp,s32g-serdes-pcie-phy")) {
+ serdes->lane[index].fn = S32G_LANE_PCIE;
+ return 0;
+ }
+
+ if (of_device_is_compatible(child_node, "nxp,s32g-serdes-xpcs")) {
+ ret = of_property_read_u32(child_node, "nxp,xpcs-instance",
+ &instance);
+ if (ret || instance > 1)
+ return dev_err_probe(dev, ret ? : -EINVAL,
+ "Invalid nxp,xpcs-instance in %pOFn\n",
+ child_node);
+
+ serdes->lane[index].fn = S32G_LANE_XPCS;
+ serdes->lane[index].xpcs_instance = instance;
+ return 0;
+ }
+
+ dev_warn(dev, "Skipping unknown child node %pOFn\n", child_node);
+
+ return 0;
+}
+
+static int s32g_serdes_create_pcie_phy(struct s32g_serdes *serdes,
+ struct device_node *child_node)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = serdes->dev;
+ struct phy *phy;
+
+ phy = devm_phy_create(dev, child_node, &serdes_pcie_ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ phy_set_drvdata(phy, serdes);
+
+ phy->attrs.mode = PHY_MODE_PCIE;
+ serdes->pcie.phy = phy;
+
+ phy_provider = devm_of_phy_provider_register(&phy->dev,
+ s32g_serdes_phy_xlate);
+ if (IS_ERR(phy_provider))
+ return PTR_ERR(phy_provider);
+
+ return 0;
+}
+
+static int s32g_serdes_parse_lanes(struct device *dev,
+ struct s32g_serdes *serdes)
+{
+ int ret;
+
+ for_each_available_child_of_node_scoped(dev->of_node, of_port) {
+ ret = s32g_serdes_parse_lane(serdes, of_port);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * In the x2 mode both lane children are PCIe, but they form one link
+ * behind one controller, so create a single phy bound to the first
+ * PCIe child. One phy per child would register two providers for one
+ * link.
+ */
+ for_each_available_child_of_node_scoped(dev->of_node, of_port) {
+ if (!of_device_is_compatible(of_port,
+ "nxp,s32g-serdes-pcie-phy"))
+ continue;
+
+ ret = s32g_serdes_create_pcie_phy(serdes, of_port);
+ if (ret)
+ return ret;
+
+ break;
+ }
+
+ return 0;
+}
+
+static int s32g_serdes_setup_xpcs_lanes(struct s32g_serdes *serdes)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(serdes->lane); i++) {
+ if (serdes->lane[i].fn != S32G_LANE_XPCS)
+ continue;
+
+ ret = s32g_serdes_xpcs_setup(serdes,
+ serdes->lane[i].xpcs_instance);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int s32g_serdes_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct s32g_serdes_soc_data *soc_data;
+ struct s32g_serdes *serdes;
+ int ret;
+
+ serdes = devm_kzalloc(dev, sizeof(*serdes), GFP_KERNEL);
+ if (!serdes)
+ return -ENOMEM;
+
+ serdes->dev = dev;
+
+ soc_data = of_device_get_match_data(dev);
+ if (!soc_data)
+ return -EINVAL;
+
+ serdes->soc_data = soc_data;
+
+ ret = s32g_serdes_get_ctrl_resources(pdev, serdes);
+ if (ret)
+ return ret;
+
+ ret = s32g_serdes_get_pcie_resources(pdev, serdes);
+ if (ret)
+ return ret;
+
+ ret = s32g_serdes_get_xpcs_resources(pdev, serdes);
+ if (ret)
+ return ret;
+
+ ret = s32g_serdes_parse_lanes(dev, serdes);
+ if (ret)
+ return ret;
+
+ ret = s32g_serdes_derive_mode(serdes);
+ if (ret)
+ return ret;
+
+ ret = s32g_serdes_setup_xpcs_lanes(serdes);
+ if (ret)
+ return ret;
+
+ ret = s32g_serdes_init(serdes);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, s32g_serdes_disable, serdes);
+ if (ret)
+ return ret;
+
+ /*
+ * Publish drvdata last: s32g_serdes_pcs_create() takes a NULL drvdata
+ * as its -EPROBE_DEFER condition, so setting it earlier can hand a MAC
+ * a PCS while the PLLs are still down, or after a failed probe has
+ * released it.
+ */
+ platform_set_drvdata(pdev, serdes);
+
+ return 0;
+}
+
+static int s32g_serdes_suspend(struct device *device)
+{
+ struct s32g_serdes *serdes = dev_get_drvdata(device);
+
+ clk_bulk_disable_unprepare(serdes->ctrl.nclks, serdes->ctrl.clks);
+
+ return 0;
+}
+
+static int s32g_serdes_resume(struct device *device)
+{
+ struct s32g_serdes *serdes = dev_get_drvdata(device);
+ struct s32g_pcie_ctrl *pcie = &serdes->pcie;
+ int ret;
+
+ ret = s32g_serdes_init(serdes);
+ if (ret) {
+ dev_err(device, "Failed to initialize\n");
+ return ret;
+ }
+
+ /* Restore PCIe phy power */
+ if (pcie->powered_on) {
+ ret = s32g_pcie_phy_power_on(serdes);
+ if (ret)
+ dev_err(device, "Failed to power-on PCIe phy\n");
+ }
+
+ return ret;
+}
+
+/**
+ * s32g_serdes_pcs_create() - look up the phylink PCS for a SerDes XPCS lane
+ * @dev: the consumer (MAC) device
+ * @np: the SerDes XPCS lane child node
+ *
+ * A device link is created between @dev and the SerDes device so that the
+ * MAC is unbound before the SerDes it depends on. If the SerDes device has
+ * not probed (far enough) yet, -EPROBE_DEFER is returned so the caller
+ * retries later.
+ *
+ * Return: the phylink_pcs on success, or an ERR_PTR() on failure. The
+ * returned phylink_pcs is owned by the SerDes device and must not be freed
+ * by the caller.
+ */
+struct phylink_pcs *s32g_serdes_pcs_create(struct device *dev,
+ struct device_node *np)
+{
+ struct platform_device *pdev;
+ struct device_node *pcs_np;
+ struct s32g_serdes *serdes;
+ struct device_link *link;
+ u32 instance;
+
+ /*
+ * The lane child's unit address is the physical lane; the PCS is
+ * identified by the XPCS instance routed to that lane.
+ */
+ if (of_property_read_u32(np, "nxp,xpcs-instance", &instance))
+ return ERR_PTR(-EINVAL);
+
+ if (instance >= S32G_SERDES_XPCS_MAX)
+ return ERR_PTR(-EINVAL);
+
+ pcs_np = of_get_parent(np);
+ if (!pcs_np)
+ return ERR_PTR(-ENODEV);
+
+ if (!of_device_is_available(pcs_np)) {
+ of_node_put(pcs_np);
+ return ERR_PTR(-ENODEV);
+ }
+
+ pdev = of_find_device_by_node(pcs_np);
+ of_node_put(pcs_np);
+ if (!pdev)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ serdes = platform_get_drvdata(pdev);
+ if (!serdes) {
+ put_device(&pdev->dev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ if (!serdes->xpcs.phys[instance]) {
+ put_device(&pdev->dev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ link = device_link_add(dev, &pdev->dev, DL_FLAG_AUTOREMOVE_CONSUMER);
+ put_device(&pdev->dev);
+ if (!link)
+ return ERR_PTR(-EINVAL);
+
+ return s32g_xpcs_pcs(serdes->xpcs.phys[instance]);
+}
+EXPORT_SYMBOL_GPL(s32g_serdes_pcs_create);
+
+static const struct of_device_id s32g_serdes_match[] = {
+ {
+ .compatible = "nxp,s32g2-serdes",
+ .data = &s32g2_serdes_soc_data,
+ },
+ {
+ .compatible = "nxp,s32g3-serdes",
+ .data = &s32g3_serdes_soc_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, s32g_serdes_match);
+
+/* noirq, like the PCIe controller using this PHY */
+static const struct dev_pm_ops s32g_serdes_pm_ops = {
+ NOIRQ_SYSTEM_SLEEP_PM_OPS(s32g_serdes_suspend,
+ s32g_serdes_resume)
+};
+
+static struct platform_driver s32g_serdes_driver = {
+ .probe = s32g_serdes_probe,
+ .driver = {
+ .name = "phy-s32g-serdes",
+ .of_match_table = s32g_serdes_match,
+ .pm = pm_sleep_ptr(&s32g_serdes_pm_ops),
+ },
+};
+module_platform_driver(s32g_serdes_driver);
+
+MODULE_AUTHOR("Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>");
+MODULE_DESCRIPTION("NXP S32G SerDes driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/phy/nxp-s32g-serdes.h b/include/linux/phy/nxp-s32g-serdes.h
new file mode 100644
index 000000000000..d14fcde1d933
--- /dev/null
+++ b/include/linux/phy/nxp-s32g-serdes.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2021-2026 NXP
+ */
+#ifndef PHY_NXP_S32G_SERDES_H
+#define PHY_NXP_S32G_SERDES_H
+
+#include <linux/err.h>
+
+struct device;
+struct device_node;
+struct phylink_pcs;
+
+#if IS_REACHABLE(CONFIG_PHY_S32G_SERDES)
+struct phylink_pcs *s32g_serdes_pcs_create(struct device *dev,
+ struct device_node *np);
+#else
+static inline struct phylink_pcs *
+s32g_serdes_pcs_create(struct device *dev, struct device_node *np)
+{
+ return ERR_PTR(-ENODEV);
+}
+#endif
+
+#endif /* PHY_NXP_S32G_SERDES_H */
--
2.55.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
next prev parent reply other threads:[~2026-09-19 6:54 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-19 6:54 [PATCH RFC v3 00/12] Add support for the NXP S32G SerDes subsystem Jan Petrous via B4 Relay
2026-09-19 6:54 ` [PATCH RFC v3 01/12] dt-bindings: phy: Add " Jan Petrous via B4 Relay
2026-09-20 6:54 ` sashiko-bot
2026-09-19 6:54 ` [PATCH RFC v3 02/12] dt-bindings: net: nxp,s32-dwmac: Document pcs-handle Jan Petrous via B4 Relay
2026-09-20 6:54 ` sashiko-bot
2026-09-19 6:54 ` [PATCH RFC v3 03/12] dt-bindings: PCI: nxp,s32g-pcie: Fix SerDes PHY phandle in example Jan Petrous via B4 Relay
2026-09-20 6:54 ` sashiko-bot
2026-09-19 6:54 ` [PATCH RFC v3 04/12] net: pcs: add NXP SerDes XPCS shared core Jan Petrous via B4 Relay
2026-09-19 15:31 ` Maxime Chevallier
2026-09-19 16:31 ` Coia Prant
2026-09-20 6:54 ` sashiko-bot
2026-09-20 18:39 ` Andrew Lunn
2026-09-19 6:54 ` [PATCH RFC v3 05/12] net: pcs: Add NXP S32G XPCS driver Jan Petrous via B4 Relay
2026-09-20 6:54 ` sashiko-bot
2026-09-20 17:07 ` Andrew Lunn
2026-09-19 6:54 ` Jan Petrous via B4 Relay [this message]
2026-09-20 6:54 ` [PATCH RFC v3 06/12] phy: freescale: s32g: Add SerDes subsystem PHY sashiko-bot
2026-09-19 6:54 ` [PATCH RFC v3 07/12] net: stmmac: dwmac-s32: Add SGMII support Jan Petrous via B4 Relay
2026-09-19 12:04 ` Maxime Chevallier
2026-09-20 6:54 ` sashiko-bot
2026-09-19 6:54 ` [PATCH RFC v3 08/12] MAINTAINERS: Add NXP S32G SerDes and SerDes xPCS core entries Jan Petrous via B4 Relay
2026-09-19 6:54 ` [PATCH RFC v3 09/12] arm64: dts: s32g: Add SCMI reset controller Jan Petrous via B4 Relay
2026-09-20 6:54 ` sashiko-bot
2026-09-19 6:54 ` [PATCH RFC v3 10/12] arm64: dts: s32g: Add SerDes controller nodes Jan Petrous via B4 Relay
2026-09-20 6:55 ` sashiko-bot
2026-09-19 6:54 ` [PATCH RFC v3 11/12] arm64: dts: s32g: Add PCIe " Jan Petrous via B4 Relay
2026-09-20 6:55 ` sashiko-bot
2026-09-19 6:54 ` [PATCH RFC v3 12/12] arm64: dts: s32g: Add S32G3-RDB3 SerDes routing variants Jan Petrous via B4 Relay
2026-09-20 6:55 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260919-s32g_serdes-v3-6-9d68868c1e89@oss.nxp.com \
--to=devnull+jan.petrous.oss.nxp.com@kernel.org \
--cc=Frank.Li@nxp.com \
--cc=alexandre.torgue@foss.st.com \
--cc=alexandru-catalin.ionita@nxp.com \
--cc=andrew+netdev@lunn.ch \
--cc=andrew@lunn.ch \
--cc=bhelgaas@google.com \
--cc=bogdan-gabriel.roman@nxp.com \
--cc=bogdan.hamciuc@nxp.com \
--cc=chester62515@gmail.com \
--cc=ciprianmarian.costea@oss.nxp.com \
--cc=conor+dt@kernel.org \
--cc=davem@davemloft.net \
--cc=devicetree@vger.kernel.org \
--cc=edumazet@google.com \
--cc=festevam@gmail.com \
--cc=geert+renesas@glider.be \
--cc=ghennadi.procopciuc@nxp.com \
--cc=ghennadi.procopciuc@oss.nxp.com \
--cc=hkallweit1@gmail.com \
--cc=imx@lists.linux.dev \
--cc=ionut.vicovan@nxp.com \
--cc=jan.petrous@oss.nxp.com \
--cc=kernel@pengutronix.de \
--cc=krzk+dt@kernel.org \
--cc=kuba@kernel.org \
--cc=kwilczynski@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pci@vger.kernel.org \
--cc=linux-phy@lists.infradead.org \
--cc=linux-renesas-soc@vger.kernel.org \
--cc=linux-stm32@st-md-mailman.stormreply.com \
--cc=linux@armlinux.org.uk \
--cc=lpieralisi@kernel.org \
--cc=magnus.damm@gmail.com \
--cc=mani@kernel.org \
--cc=maxime.chevallier@bootlin.com \
--cc=mbrugger@suse.com \
--cc=mcoquelin.stm32@gmail.com \
--cc=neil.armstrong@linaro.org \
--cc=netdev@vger.kernel.org \
--cc=p.zabel@pengutronix.de \
--cc=pabeni@redhat.com \
--cc=richardcochran@gmail.com \
--cc=robh@kernel.org \
--cc=s.hauer@pengutronix.de \
--cc=s32@nxp.com \
--cc=vincent.guittot@linaro.org \
--cc=vkoul@kernel.org \
--cc=xiaoning.wang@nxp.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox