From nobody Mon Feb 9 23:39:24 2026 Received: from mail-wm1-f54.google.com (mail-wm1-f54.google.com [209.85.128.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 253E63081A4 for ; Mon, 26 Jan 2026 09:22:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769419328; cv=none; b=Qu0geEDbNxfsu1EAbfq5YBAYAiHZQJ0nlLyoLTOW5Lm+PUw/GOdL8aytQ3ZNTvLDpcr2xz93MGx3bjWcVThssvrjHeQGyxjPWnke7vL8RV+nFDtt5SjF5F3PofeAy7t+TRkhLBSR8AbgOAKQ6GBioWIGc2hFKgNfYaHGUylCCWY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769419328; c=relaxed/simple; bh=Nj5xoIHsFwz4mwN35mQYGJFp7jFOthXPcVMX2Svf5Lc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=XRPBvsuapsFABPpU5aVcuF/5XRK122AtiKL7NN3fR658XBNw8xG+3Djug9VdbuDG3tJWgMh4abP1ZJbx4eziq8wONNPl4xde+2n7QR+3mOFpmmlBQkqgyUwCPFFNbJC6zaGLD9IDVQiO0EvKi3HtQyWLsRPufubiY6NCNGB4bdc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linaro.org; spf=pass smtp.mailfrom=linaro.org; dkim=pass (2048-bit key) header.d=linaro.org header.i=@linaro.org header.b=rvPufoni; arc=none smtp.client-ip=209.85.128.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linaro.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linaro.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=linaro.org header.i=@linaro.org header.b="rvPufoni" Received: by mail-wm1-f54.google.com with SMTP id 5b1f17b1804b1-4801bc32725so32997625e9.0 for ; Mon, 26 Jan 2026 01:22:05 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1769419324; x=1770024124; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=DTHA/4uN9KLXd+UFlDqR2tTJgWE1rHWO6ROsUhtjyd0=; b=rvPufonipOKGGezOLhRnSJmz2Msnb2QVeSDazVJLBzQ+hd94Q9dCk4L3Utx7gTFhGh SdReeNe2Ws68cDqKsr3KvgWbQONEw+R5u1v6QWrN1v8C9nNzLyvDXIKole7uPUGY135/ tPLOxuc/gUrGUpFDUOyDAmVDhh6rLGXtYHbZip7oZu65heM6H1C/Tckq6ehaux+qRW3Y 6o8T4J7Hqs1odRjm6cYoOFLF6vlMFxben/P1Dazatw+hxb7M973gV0lkTzoQ7ugq7C6O OgrXK02Mu9SITz2TXQFSmCeJf63j3rpr3ipbl2NI7YlcDbVfLJ7yTtEwDjr60gyI4FG5 r3cg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769419324; x=1770024124; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=DTHA/4uN9KLXd+UFlDqR2tTJgWE1rHWO6ROsUhtjyd0=; b=wXVoYc8owH3xu7QWmf7UNNCSXeAqt5yvtpOQZWnl6Sg4sJqD70F5VzTEB8+xh+Y4kL 4hRDMmfYEy/0TP8Ty5wwqMgp9YrTYGFEaXoIi2NJAJgaIzA6C1GNwgteUDYy8FwHLJy0 AVoMO0lVTyosYSHgIZlNokNqIHkoWC465lWN6QlEJujUGGQEbv6rUMKJXZ1JuPQ2SecV mJNlFSlHAOhl/hpkEYAF38Rhu7coy1fZz/+cRWqYiB/CP7OA0SgA+QkvjZ4xsHi5NiRT TGIdOUPRMCirTSAbajy6Zy860pNrcV+O/+WTNUctc0UqCdJ/1LGp7kss9lGardYNbNwk a+yA== X-Forwarded-Encrypted: i=1; AJvYcCWin44xV/RRxcc/P3k4eM3cpEveGECi70gMGRpSVFDbn8+8OOGMIZNQq/GgRnlDk1Xl/aitpCEksp+Q+m8=@vger.kernel.org X-Gm-Message-State: AOJu0Yy+ds3Kkpu1Q26ijTRnHLaMTTIkRQ3/qsvwwqKvyIE0sKB9QR99 TrTU9FFHEwIRt4O65I3QAns3LiNM7hT+ItRSuebQtd/mw3gM8DSFWADx7IXARvPHqkU= X-Gm-Gg: AZuq6aKuxCT0VHnnqyNi6qrXg1SZHOMQ8eEck1IeD96HmrBYaSxR8hNBdkI9NMdh7Da fpGJmoZEiub/oCcJiFSxmD9sZMPh34V/n4XXdrRlZfwZPuLnuo4BPYWNKABR7wotFIZ28wZw/Zl MP+xtfuA5zHe4JKhZ5e+8ATmNwfl3AVOeiRGvLYeuWfycvKQNM7Stho0gJRZM19p7l5YTgrJI99 9iS/bZIu3GMt2yEgzNSZJVYt3YEZbU1KoiWEQJ3hree1ZYbbyctLh52uXCfRkYTnZqQZAsziSkw kHsLXkKg8gSxrRW/uQcC/uUVHzCbjp0cjPJ85MMVm7mMTnOM/395NbKEK+soIRQ7czm4cARVe3M G61rNWSpg8Il4kiY8y1pLv5DkTfTkvFhR5BAcaCVln2VAgpxa/rqDIYMCysejc7jWEIOttM+jaF 24cfuJPOhhj62StoQv3Mk= X-Received: by 2002:a05:600c:1e2a:b0:47e:e952:86c9 with SMTP id 5b1f17b1804b1-4805ccd1198mr65140045e9.0.1769419324298; Mon, 26 Jan 2026 01:22:04 -0800 (PST) Received: from vingu-cube.. ([2a01:e0a:f:6020:e270:a43a:f2fa:900a]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-480470cf1acsm346669855e9.14.2026.01.26.01.22.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 26 Jan 2026 01:22:03 -0800 (PST) From: Vincent Guittot To: vkoul@kernel.org, neil.armstrong@linaro.org, krzk+dt@kernel.org, conor+dt@kernel.org, ciprianmarian.costea@oss.nxp.com, s32@nxp.com, p.zabel@pengutronix.de, linux@armlinux.org.uk, ghennadi.procopciuc@nxp.com, bogdan-gabriel.roman@nxp.com, Ionut.Vicovan@nxp.com, alexandru-catalin.ionita@nxp.com, linux-phy@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, netdev@vger.kernel.org Cc: Frank.li@nxp.com Subject: [PATCH 2/4] phy: s32g: Add serdes subsystem phy Date: Mon, 26 Jan 2026 10:21:57 +0100 Message-ID: <20260126092159.815968-3-vincent.guittot@linaro.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260126092159.815968-1-vincent.guittot@linaro.org> References: <20260126092159.815968-1-vincent.guittot@linaro.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" s32g SoC family includes 2 serdes subsystems which are made of one PCIe controller, 2 XPCS and one Phy. The Phy got 2 lanes that can be configure to output PCIe lanes and/or SGMII. Implement PCIe phy support Co-developed-by: Ciprian Marian Costea Signed-off-by: Ciprian Marian Costea Co-developed-by: Alexandru-Catalin Ionita Signed-off-by: Alexandru-Catalin Ionita Co-developed-by: Ghennadi Procopciuc Signed-off-by: Ghennadi Procopciuc Co-developed-by: Ionut Vicovan Signed-off-by: Ionut Vicovan Co-developed-by: Bogdan Roman Signed-off-by: Bogdan Roman Signed-off-by: Vincent Guittot --- drivers/phy/freescale/Kconfig | 9 + drivers/phy/freescale/Makefile | 1 + drivers/phy/freescale/phy-nxp-s32g-serdes.c | 569 ++++++++++++++++++++ 3 files changed, 579 insertions(+) create mode 100644 drivers/phy/freescale/phy-nxp-s32g-serdes.c diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig index 81f53564ee15..45184a3cdd69 100644 --- a/drivers/phy/freescale/Kconfig +++ b/drivers/phy/freescale/Kconfig @@ -61,3 +61,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 + select GENERIC_PHY + help + This option enables support for S23G SerDes PHY used for + PCIe & Ethernet + diff --git a/drivers/phy/freescale/Makefile b/drivers/phy/freescale/Makefile index 658eac7d0a62..86d948417252 100644 --- a/drivers/phy/freescale/Makefile +++ b/drivers/phy/freescale/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_PHY_FSL_IMX8M_PCIE) +=3D phy-fsl-imx8m-pcie.o obj-$(CONFIG_PHY_FSL_IMX8QM_HSIO) +=3D phy-fsl-imx8qm-hsio.o obj-$(CONFIG_PHY_FSL_LYNX_28G) +=3D phy-fsl-lynx-28g.o obj-$(CONFIG_PHY_FSL_SAMSUNG_HDMI_PHY) +=3D phy-fsl-samsung-hdmi.o +obj-$(CONFIG_PHY_S32G_SERDES) +=3D phy-nxp-s32g-serdes.o diff --git a/drivers/phy/freescale/phy-nxp-s32g-serdes.c b/drivers/phy/free= scale/phy-nxp-s32g-serdes.c new file mode 100644 index 000000000000..8336c868c8dc --- /dev/null +++ b/drivers/phy/freescale/phy-nxp-s32g-serdes.c @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * SerDes driver for S32G SoCs + * + * Copyright 2021-2026 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define S32G_SERDES_MODE_MAX 5 + +#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(3, 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 + +/* + * Until now, there is no generic way to describe and set PCIe clock mode. + * PCIe controller uses the default CRNS =3D 0 mode. + */ +enum pcie_phy_mode { + CRNS =3D 0, /* Common Reference Clock, No Spread Spectrum */ + CRSS =3D 1, /* Common Reference Clock, Spread Spectrum */ + SRNS =3D 2, /* Separate Reference Clock, No Spread Spectrum */ + SRIS =3D 3 /* Separate Reference Clock, Spread Spectrum */ +}; + +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_serdes { + struct s32g_serdes_ctrl ctrl; + struct s32g_pcie_ctrl pcie; + 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 =3D &serdes->ctrl; + unsigned long rate =3D sctrl->ref_clk_rate; + + if (rate !=3D S32G_SERDES_PCIE_FREQ) { + dev_err(serdes->dev, "PCIe PHY cannot operate at %lu HZ\n", rate); + return -EINVAL; + } + + return 0; +} + +static bool s32g_pcie_phy_is_locked(struct s32g_serdes *serdes) +{ + u32 mplla =3D readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_MPLLA_CTRL); + const u32 mask =3D MPLL_STATE; + + return (mplla & mask) =3D=3D mask; +} + +/* Serdes RFM says between 3.4 and 5.2ms depending of pll */ +#define S32G_SERDES_LOCK_TIMEOUT_MS 6 + +static int s32g_pcie_phy_power_on_common(struct s32g_serdes *serdes) +{ + struct s32g_serdes_ctrl *sctrl =3D &serdes->ctrl; + struct s32g_pcie_ctrl *pcie =3D &serdes->pcie; + u32 reg; + int val, ret =3D 0; + + ret =3D s32g_pcie_check_clk(serdes); + if (ret) + return ret; + + reg =3D readl(sctrl->ss_base + S32G_PCIE_PHY_GEN_CTRL); + + /* if PCIE PHY is in SRIS mode */ + if (pcie->phy_mode =3D=3D SRIS) + reg |=3D RX_SRIS_MODE; + + if (sctrl->ext_clk) + reg |=3D REF_USE_PAD; + else + reg &=3D ~REF_USE_PAD; + + writel(reg, sctrl->ss_base + S32G_PCIE_PHY_GEN_CTRL); + + /* Monitor Serdes MPLL state */ + ret =3D read_poll_timeout(s32g_pcie_phy_is_locked, val, + (val), + 0, + S32G_SERDES_LOCK_TIMEOUT_MS, false, serdes); + if (ret) { + dev_err(serdes->dev, "Failed to lock PCIE phy\n"); + return -ETIMEDOUT; + } + + /* Set PHY register access to CR interface */ + reg =3D readl(sctrl->ss_base + S32G_SS_RW_REG_0); + reg |=3D PHY0_CR_PARA_SEL; + writel(reg, sctrl->ss_base + S32G_SS_RW_REG_0); + + return ret; +} + +static void s32g_pcie_phy_write(struct s32g_serdes *serdes, u32 reg, u32 v= al) +{ + 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 =3D &serdes->pcie; + struct s32g_serdes_ctrl *ctrl =3D &serdes->ctrl; + u32 iq_ovrd_in; + int ret =3D 0; + + ret =3D 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 =3D 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 =3D=3D 0) { + iq_ovrd_in =3D 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 =3D true; + + return 0; +} + +/* PCIe phy ops function */ + +static int s32g_serdes_phy_power_on(struct phy *p) +{ + struct s32g_serdes *serdes =3D phy_get_drvdata(p); + + return s32g_pcie_phy_power_on(serdes); +} + +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 =3D phy_get_drvdata(p); + + if (mode =3D=3D PHY_MODE_PCIE) + return -EINVAL; + + if (!is_pcie_phy_mode_valid(submode)) + return -EINVAL; + + /* + * Do not configure SRIS or CRSS PHY MODE in conjunction + * with any SGMII mode on the same SerDes subsystem + */ + if ((submode =3D=3D CRSS || submode =3D=3D SRIS) && + serdes->ctrl.ss_mode !=3D 0) + return -EINVAL; + + /* + * Internal reference clock cannot be used with either Common clock + * or Spread spectrum, leaving only SRNSS + */ + if (submode !=3D SRNS && !serdes->ctrl.ext_clk) + return -EINVAL; + + serdes->pcie.phy_mode =3D submode; + + return 0; +} + +static const struct phy_ops serdes_pcie_ops =3D { + .power_on =3D s32g_serdes_phy_power_on, + .set_mode =3D 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; + struct phy *phy; + + serdes =3D dev_get_drvdata(dev); + if (!serdes) + return ERR_PTR(-EINVAL); + + phy =3D serdes->pcie.phy; + + return phy; +} + +/* Serdes subsystem */ + +static int s32g_serdes_assert_reset(struct s32g_serdes *serdes) +{ + struct device *dev =3D serdes->dev; + int ret; + + ret =3D reset_control_assert(serdes->pcie.rst); + if (ret) { + dev_err(dev, "Failed to assert PCIE reset: %d\n", ret); + return ret; + } + + ret =3D 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 =3D serdes->dev; + int ret; + + ret =3D reset_control_deassert(serdes->pcie.rst); + if (ret) { + dev_err(dev, "Failed to assert PCIE reset: %d\n", ret); + return ret; + } + + ret =3D reset_control_deassert(serdes->ctrl.rst); + if (ret) { + dev_err(dev, "Failed to assert SerDes reset: %d\n", ret); + return ret; + } + + return ret; +} + +static int s32g_serdes_init(struct s32g_serdes *serdes) +{ + struct s32g_serdes_ctrl *ctrl =3D &serdes->ctrl; + u32 reg0; + int ret; + + ret =3D clk_bulk_prepare_enable(ctrl->nclks, ctrl->clks); + if (ret) { + dev_err(serdes->dev, "Failed to enable SerDes clocks\n"); + return ret; + } + + ret =3D s32g_serdes_assert_reset(serdes); + if (ret) + goto disable_clks; + + /* Set serdes mode */ + reg0 =3D readl(ctrl->ss_base + S32G_SS_RW_REG_0); + reg0 &=3D ~SUBMODE_MASK; + if (ctrl->ss_mode =3D=3D 5) + reg0 |=3D 2; + else + reg0 |=3D ctrl->ss_mode; + writel(reg0, ctrl->ss_base + S32G_SS_RW_REG_0); + + /* Set Clock source: internal or external */ + reg0 =3D readl(ctrl->ss_base + S32G_SS_RW_REG_0); + if (ctrl->ext_clk) + reg0 &=3D ~CLKEN_MASK; + else + reg0 |=3D CLKEN_MASK; + + writel(reg0, ctrl->ss_base + S32G_SS_RW_REG_0); + + /* Wait for the selection of working mode (as per the manual specs) */ + usleep_range(100, 110); + + ret =3D s32g_serdes_deassert_reset(serdes); + if (ret) + goto disable_clks; + + dev_info(serdes->dev, "Using mode %d for SerDes subsystem\n", + ctrl->ss_mode); + + return 0; + +disable_clks: + clk_bulk_disable_unprepare(serdes->ctrl.nclks, + serdes->ctrl.clks); + + return ret; +} + +static int s32g_serdes_get_ctrl_resources(struct platform_device *pdev, st= ruct s32g_serdes *serdes) +{ + struct s32g_serdes_ctrl *ctrl =3D &serdes->ctrl; + struct device *dev =3D &pdev->dev; + int ret, idx; + + ret =3D of_property_read_u32(dev->of_node, "nxp,sys-mode", + &ctrl->ss_mode); + if (ret) { + dev_err(dev, "Failed to get SerDes subsystem mode\n"); + return -EINVAL; + } + + if (ctrl->ss_mode > S32G_SERDES_MODE_MAX) { + dev_err(dev, "Invalid SerDes subsystem mode %u\n", + ctrl->ss_mode); + return -EINVAL; + } + + ctrl->ss_base =3D devm_platform_ioremap_resource_byname(pdev, "ss_pcie"); + if (IS_ERR(ctrl->ss_base)) { + dev_err(dev, "Failed to map 'ss_pcie'\n"); + return PTR_ERR(ctrl->ss_base); + } + + ctrl->rst =3D devm_reset_control_get(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 =3D devm_clk_bulk_get_all(dev, &ctrl->clks); + if (ctrl->nclks < 1) + return dev_err_probe(dev, ctrl->nclks, + "Failed to get SerDes clocks\n"); + + idx =3D of_property_match_string(dev->of_node, "clock-names", EXTERNAL_CL= K_NAME); + if (idx < 0) + idx =3D of_property_match_string(dev->of_node, "clock-names", INTERNAL_C= LK_NAME); + else + ctrl->ext_clk =3D true; + + if (idx < 0) { + dev_err(dev, "Failed to get Phy reference clock source\n"); + return -EINVAL; + } + + ctrl->ref_clk_rate =3D 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 ret; +} + +static int s32g_serdes_get_pcie_resources(struct platform_device *pdev, st= ruct s32g_serdes *serdes) +{ + struct s32g_pcie_ctrl *pcie =3D &serdes->pcie; + struct device *dev =3D &pdev->dev; + + pcie->phy_base =3D devm_platform_ioremap_resource_byname(pdev, + "pcie_phy"); + if (IS_ERR(pcie->phy_base)) { + dev_err(dev, "Failed to map 'pcie_phy'\n"); + return PTR_ERR(pcie->phy_base); + } + + pcie->rst =3D devm_reset_control_get(dev, "pcie"); + if (IS_ERR(pcie->rst)) + return dev_err_probe(dev, IS_ERR(pcie->rst), + "Failed to get 'pcie' reset control\n"); + + return 0; +} + +static int s32g2_serdes_create_phy(struct s32g_serdes *serdes, struct devi= ce_node *child_node) +{ + struct s32g_serdes_ctrl *ctrl =3D &serdes->ctrl; + struct phy_provider *phy_provider; + struct device *dev =3D serdes->dev; + int ss_mode =3D ctrl->ss_mode; + struct phy *phy; + + if (of_device_is_compatible(child_node, "nxp,s32g2-serdes-pcie-phy")) { + /* no PCIe phy lane */ + if (ss_mode > 2) + return 0; + + phy =3D 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 =3D PHY_MODE_PCIE; + phy->id =3D 0; + serdes->pcie.phy =3D phy; + + phy_provider =3D devm_of_phy_provider_register(&phy->dev, s32g_serdes_ph= y_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + } else { + dev_warn(dev, "Skipping unknown child node %pOFn\n", child_node); + } + + 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 =3D s32g2_serdes_create_phy(serdes, of_port); + if (ret) + break; + } + + return ret; +} + +static int s32g_serdes_probe(struct platform_device *pdev) +{ + struct s32g_serdes *serdes; + struct device *dev =3D &pdev->dev; + int ret; + + serdes =3D devm_kzalloc(dev, sizeof(*serdes), GFP_KERNEL); + if (!serdes) + return -ENOMEM; + + platform_set_drvdata(pdev, serdes); + serdes->dev =3D dev; + + ret =3D s32g_serdes_get_ctrl_resources(pdev, serdes); + if (ret) + return ret; + + ret =3D s32g_serdes_get_pcie_resources(pdev, serdes); + if (ret) + return ret; + + ret =3D s32g_serdes_parse_lanes(dev, serdes); + if (ret) + return ret; + + ret =3D s32g_serdes_init(serdes); + + return ret; +} + +static int __maybe_unused s32g_serdes_suspend(struct device *device) +{ + struct s32g_serdes *serdes =3D dev_get_drvdata(device); + + clk_bulk_disable_unprepare(serdes->ctrl.nclks, serdes->ctrl.clks); + + return 0; +} + +static int __maybe_unused s32g_serdes_resume(struct device *device) +{ + struct s32g_serdes *serdes =3D dev_get_drvdata(device); + struct s32g_pcie_ctrl *pcie =3D &serdes->pcie; + int ret; + + ret =3D s32g_serdes_init(serdes); + if (ret) { + dev_err(device, "Failed to initialize\n"); + return ret; + } + + /* Restore PCIe phy power */ + if (pcie->powered_on) { + ret =3D s32g_pcie_phy_power_on(serdes); + if (ret) + dev_err(device, "Failed to power-on PCIe phy\n"); + } + + return ret; +} + +static const struct of_device_id s32g_serdes_match[] =3D { + { + .compatible =3D "nxp,s32g2-serdes", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, s32g_serdes_match); + +static const struct dev_pm_ops s32g_serdes_pm_ops =3D { + NOIRQ_SYSTEM_SLEEP_PM_OPS(s32g_serdes_suspend, + s32g_serdes_resume) +}; + +static struct platform_driver s32g_serdes_driver =3D { + .probe =3D s32g_serdes_probe, + .driver =3D { + .name =3D "phy-s32g-serdes", + .of_match_table =3D s32g_serdes_match, + .pm =3D &s32g_serdes_pm_ops, + }, +}; +module_platform_driver(s32g_serdes_driver); + +MODULE_AUTHOR("Ghennadi Procopciuc "); +MODULE_DESCRIPTION("S32CC SerDes driver"); +MODULE_LICENSE("GPL"); --=20 2.43.0