From nobody Tue Feb 10 14:49:49 2026 Received: from mail-wm1-f43.google.com (mail-wm1-f43.google.com [209.85.128.43]) (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 511063D3300 for ; Tue, 3 Feb 2026 16:19:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770135570; cv=none; b=vDA3Nj6j2NGcqO9eKs1q7CNQnpALaDCj0ZeuSF+QLL6iDZtvR+bDg4nW6Q2uHkyjyzu+8zt6wa3yOo+vez2COdyjLxc7Y5u9rO6Y26/A9utPvjknoPCRJ1amzAylmTpR1T6QR72firmhHVjoZh5S5B7UUbbWQ3OpxYPOhtBZAo8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770135570; c=relaxed/simple; bh=ZQbYMt/urwbjfEqsKR25v+W+m2VsUcljpKeOrhzfNHU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=rV3jPeeMe9wRRIec3tnyQhcnVKBvrWjJv9zzotgKGnYPwsq4mfGUBNtjSWx72qJNWn2Zpgyv83fkpq1voyx46tr5AYBElMDX2kAbVPdSMgBZnQLvG8ZvnWExo1v5n1Buj9kZ7w1aGAymwby6xTcVCu2uf/wDe40fa3/PSLEM7I0= 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=yI3TRWxw; arc=none smtp.client-ip=209.85.128.43 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="yI3TRWxw" Received: by mail-wm1-f43.google.com with SMTP id 5b1f17b1804b1-47edd6111b4so61925125e9.1 for ; Tue, 03 Feb 2026 08:19:26 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1770135565; x=1770740365; 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=Ld5W7O5+DZjm6MAnTZUl+YMvXmBb7jCpcTxIrIErWHI=; b=yI3TRWxwv9Vvg1Ey9v7f+qNigyooKnTefgMO38lzTC5PChigQ2igLOHXBQky8jkIDU bwnL+PMtnb0iwmrIR77kNB6YI2DDUHv3A4/en85rb7/r0gG29NlEq6G4Ckwm7BGndD0q co/Li18VGZVkJKYHJDXmifHHkPcEmTdSMCjn4MLZG+TGnSXXLmceUKzcY81dW81gjIlN mzdGfa4EjLpuQScEKvEKkkcfDUIIM2+fQswFBgpP9tEIh5fysTISMlXa/wuHt1tXD+Nc i/nCyH1zcjGf8S549sQZ2q0FKcwszELoBjYxrH+3Areio96B4QBuEvSWim34dQAjexv9 gJCw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1770135565; x=1770740365; 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=Ld5W7O5+DZjm6MAnTZUl+YMvXmBb7jCpcTxIrIErWHI=; b=pCiv+VWPiCosO8ykYnX2KCEhbM3UfxVKADf3STsymyDGrV7soxu7wM+LbxZ5XBegO3 15nnAmIro+SOn20jRZFyInmOjnqdcJlspzYW5+255yQs8hHUhpUun/FHz9Pe0uK/erYd maanrBnljQgUgRn3XGOOGqgkOgMl81+JxcmBwIIFB7rk0Hne7R0c7TESCqaCizQS8acf EhZ8aY/wFEUK8GsZlpvfHPaU+VP9+Uui1jyOjNRGYA5oLoIPz2SEqZy3p/67Hp1aT1FN A/gMeGny+moTkyAKc3GDrSF/zf3W7RJzn5rt6hXxuvM6jsBW5pcnu1UfZVyiyJ4Z+KWK qBYw== X-Forwarded-Encrypted: i=1; AJvYcCVPZ167zh/khdmpKntWPHdwR76wPsO0rZI2tR2hOEOfLoGJRzIfTwQJhEMxrHRzvJTzk4n7HXc/GBTuOUQ=@vger.kernel.org X-Gm-Message-State: AOJu0Yzgvg3dgetFOCCN/kNkOinnXxwZbWzJ/bgvBIS4CiDEDLgvlAHP 3GY/YdhbtIu41Rh+/ldMyaYdakUVRhbY3cfYI7mzP63pO6Y9s6Io30u0dHxv4bkM3LA= X-Gm-Gg: AZuq6aIsHwjJFDeMRwI7GE/0cKzGv3YWRKauFRjirZYaSjELxy8LMPOqSr0rJ9lZexY ZIlIFonN/dA7lzGYmRMFsOxglpTCKSmGm1VOMDSdvGRbYmqCr+MYVKnweKbkjvl6L5wpYwfbS5W ouaGnQ8xqKvlr6ZueB6rYswUclVGhLgo4k5a+0m/QsLMMmsESR5Xx2lIyqHNlzEq+t0rJXcSa7e 9xFMSePUOJ5UrgaeRmqd39MPkCjUjMTJPLRr36Brd0G2ij8Fr1Cia4vRA4tTl7l1oMgDpxLWKzZ mB5i1acjqP5sPbq9GHvpVAnlZvocl+8jY/LgnRnOK4nJv6Un5h1UX7GujW7E+dhwgC/x9qcLBQg yGB5x8xFFzqpUOn/P+oippgATNSBtpnVqw6M0g2ARfMY3/08r6TXZ6gDSlRszYDBx9HRAQKlFfe RSGhkEaIVpEHnWn9N8RUGeU6tvETWseA== X-Received: by 2002:a05:600c:4513:b0:47d:6856:9bd9 with SMTP id 5b1f17b1804b1-4830e97ae1emr2031415e9.23.1770135564315; Tue, 03 Feb 2026 08:19:24 -0800 (PST) Received: from vingu-cube.. ([2a01:e0a:f:6020:81d1:b874:c1dc:42e5]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-482dbd21f5dsm144120785e9.8.2026.02.03.08.19.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 03 Feb 2026 08:19:22 -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, Ionut.Vicovan@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, horms@kernel.org Cc: Frank.li@nxp.com Subject: [PATCH 3/4 v2] phy: s32g: Add serdes xpcs subsystem Date: Tue, 3 Feb 2026 17:19:16 +0100 Message-ID: <20260203161917.1666696-4-vincent.guittot@linaro.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260203161917.1666696-1-vincent.guittot@linaro.org> References: <20260203161917.1666696-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. Add XPCS and SGMII 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/net/pcs/Makefile | 1 + drivers/net/pcs/pcs-nxp-s32g-xpcs.c | 1006 +++++++++++++++++++ drivers/phy/freescale/Kconfig | 1 + drivers/phy/freescale/phy-nxp-s32g-serdes.c | 374 ++++++- include/linux/pcs/pcs-nxp-s32g-xpcs.h | 50 + 5 files changed, 1430 insertions(+), 2 deletions(-) create mode 100644 drivers/net/pcs/pcs-nxp-s32g-xpcs.c create mode 100644 include/linux/pcs/pcs-nxp-s32g-xpcs.h diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile index 4f7920618b90..55107cbaa6d5 100644 --- a/drivers/net/pcs/Makefile +++ b/drivers/net/pcs/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_PCS_XPCS) +=3D pcs_xpcs.o obj-$(CONFIG_PCS_LYNX) +=3D pcs-lynx.o obj-$(CONFIG_PCS_MTK_LYNXI) +=3D pcs-mtk-lynxi.o obj-$(CONFIG_PCS_RZN1_MIIC) +=3D pcs-rzn1-miic.o +obj-$(CONFIG_PHY_S32G_SERDES) +=3D 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..5636440ed38d --- /dev/null +++ b/drivers/net/pcs/pcs-nxp-s32g-xpcs.c @@ -0,0 +1,1006 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Copyright 2021-2026 NXP + */ + +#include +#include +#include +#include +#include +#include +#include + +#define XPCS_TIMEOUT_MS 300 + +#define ADDR1_OFS 0x3FC + +#define SR_MII_CTRL 0x1F0000 +#define SR_RST BIT(15) +#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 AN_ABL BIT(3) +#define AN_CMPL BIT(5) +#define SR_MII_DEV_ID1 0x1F0002 +#define SR_MII_DEV_ID2 0x1F0003 +#define SR_MII_EXT_STS 0x1F000F +#define CAP_1G_T_FD BIT(13) +#define CAP_1G_T_HD BIT(12) +#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 CS_EN BIT(10) +#define PWRSV BIT(11) +#define EN_VSMMD1 BIT(13) +#define R2TLBE BIT(14) +#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 VBOOST_EN_0 BIT(4) +#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 CDR_SSC_EN_0 BIT(4) +#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_OFF 0 +#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_RX_LSTS 0x1F8020 +#define RX_VALID_0 BIT(12) +#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_CLK_EN BIT(0) +#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 RANGE_78_104_MHZ 0x3 +#define REF_MPLLA_DIV2 BIT(6) +#define REF_MPLLB_DIV2 BIT(7) +#define REF_RPT_CLK_EN BIT(8) +#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. + */ + +static const struct regmap_range s32g_xpcs_wr_ranges[] =3D { + 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 =3D { + .yes_ranges =3D s32g_xpcs_wr_ranges, + .n_yes_ranges =3D ARRAY_SIZE(s32g_xpcs_wr_ranges), +}; + +static const struct regmap_range s32g_xpcs_rd_ranges[] =3D { + 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 =3D { + .yes_ranges =3D s32g_xpcs_rd_ranges, + .n_yes_ranges =3D 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 =3D context; + u16 ofsleft =3D (reg >> 8) & 0xffffU; + u16 ofsright =3D (reg & 0xffU); + + writew(ofsleft, xpcs->base + ADDR1_OFS); + *result =3D 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 =3D context; + u16 ofsleft =3D (reg >> 8) & 0xffffU; + u16 ofsright =3D (reg & 0xffU); + + writew(ofsleft, xpcs->base + ADDR1_OFS); + writew(val, xpcs->base + (ofsright * 4)); + + return 0; +} + +static const struct regmap_config s32g_xpcs_regmap_config =3D { + .reg_bits =3D 16, + .val_bits =3D 16, + .reg_read =3D s32g_xpcs_regmap_reg_read, + .reg_write =3D s32g_xpcs_regmap_reg_write, + .wr_table =3D &s32g_xpcs_wr_table, + .rd_table =3D &s32g_xpcs_rd_table, + .max_register =3D 0x1F80E1, +}; + +static void s32g_xpcs_write_bits(struct s32g_xpcs *xpcs, unsigned int reg, + unsigned int mask, unsigned int value) +{ + int ret =3D 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 =3D 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 re= g) +{ + unsigned int val =3D 0; + int ret; + + ret =3D 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 bool s32g_xpcs_poll_timeout(struct s32g_xpcs *xpcs, xpcs_poll_func_= t func, + ktime_t timeout) +{ + ktime_t cur =3D ktime_get(); + + return func(xpcs) || ktime_after(cur, timeout); +} + +static int s32g_xpcs_wait(struct s32g_xpcs *xpcs, xpcs_poll_func_t func) +{ + ktime_t timeout =3D ktime_add_ms(ktime_get(), XPCS_TIMEOUT_MS); + + spin_until_cond(s32g_xpcs_poll_timeout(xpcs, func, timeout)); + if (!func(xpcs)) + return -ETIMEDOUT; + + return 0; +} + +static int s32g_xpcs_wait_bits(struct s32g_xpcs *xpcs, unsigned int reg, + unsigned int mask, unsigned int bits) +{ + ktime_t cur; + ktime_t timeout =3D ktime_add_ms(ktime_get(), XPCS_TIMEOUT_MS); + + spin_until_cond((cur =3D ktime_get(), + (s32g_xpcs_read(xpcs, reg) & mask) =3D=3D bits || + ktime_after(cur, timeout))); + if ((s32g_xpcs_read(xpcs, reg) & mask) !=3D bits) + return -ETIMEDOUT; + + return 0; +} + +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) =3D=3D POWER_GOOD_STATE, + 0, + XPCS_TIMEOUT_MS, 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); +} + +static bool s32g_xpcs_is_not_in_reset(struct s32g_xpcs *xpcs) +{ + unsigned int val; + + val =3D 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 =3D 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; +} + +int s32g_xpcs_reset_rx(struct s32g_xpcs *xpcs) +{ + int ret =3D 0; + + ret =3D 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] =3D 1 */ + + return ret; +} + +static int s32g_xpcs_ref_clk_sel(struct s32g_xpcs *xpcs, + enum s32g_xpcs_pll ref_pll) +{ + switch (ref_pll) { + case XPCS_PLLA: + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLL_CMN_CTRL, + MPLLB_SEL_0, 0); + xpcs->ref =3D XPCS_PLLA; + break; + case XPCS_PLLB: + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLL_CMN_CTRL, + MPLLB_SEL_0, MPLLB_SEL_0); + xpcs->ref =3D 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 vc= o_pll) +{ + unsigned int vco_ld =3D 0; + unsigned int vco_ref =3D 0; + unsigned int rx_baud =3D 0; + unsigned int tx_baud =3D 0; + + switch (vco_pll) { + case XPCS_PLLA: + if (xpcs->mhz125) { + vco_ld =3D FIELD_PREP(VCO_LD_VAL_0_MASK, 1360); + vco_ref =3D FIELD_PREP(VCO_REF_LD_0_MASK, 17); + } else { + vco_ld =3D FIELD_PREP(VCO_LD_VAL_0_MASK, 1350); + vco_ref =3D FIELD_PREP(VCO_REF_LD_0_MASK, 27); + } + + rx_baud =3D FIELD_PREP(RX0_RATE_MASK, RX0_BAUD_DIV_8); + tx_baud =3D FIELD_PREP(TX0_RATE_MASK, TX0_BAUD_DIV_4); + break; + case XPCS_PLLB: + if (xpcs->mhz125) { + vco_ld =3D FIELD_PREP(VCO_LD_VAL_0_MASK, 1350); + vco_ref =3D FIELD_PREP(VCO_REF_LD_0_MASK, 27); + } else { + vco_ld =3D FIELD_PREP(VCO_LD_VAL_0_MASK, 1344); + vco_ref =3D FIELD_PREP(VCO_REF_LD_0_MASK, 43); + } + + rx_baud =3D FIELD_PREP(RX0_RATE_MASK, RX0_BAUD_DIV_2); + tx_baud =3D 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 =3D=3D 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 int s32g_xpcs_init_mplla(struct s32g_xpcs *xpcs) +{ + unsigned int val; + + if (!xpcs) + return -EINVAL; + + /* Step 7 */ + val =3D 0; + if (xpcs->ext_clk) + val |=3D REF_USE_PAD; + + if (xpcs->mhz125) { + val |=3D REF_MPLLA_DIV2; + val |=3D REF_CLK_DIV2; + val |=3D FIELD_PREP(REF_RANGE_MASK, RANGE_52_78_MHZ); + } else { + val |=3D 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 =3D FIELD_PREP(MLLA_MULTIPLIER_MASK, 80); + else + val =3D 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 =3D FIELD_PREP(MPLLA_BANDWIDTH_MASK, 43); + else + val =3D FIELD_PREP(MPLLA_BANDWIDTH_MASK, 357); + + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_MPLLA_CTRL3, + MPLLA_BANDWIDTH_MASK, val); + + return 0; +} + +static int s32g_xpcs_init_mpllb(struct s32g_xpcs *xpcs) +{ + unsigned int val; + + if (!xpcs) + return -EINVAL; + + /* Step 7 */ + val =3D 0; + if (xpcs->ext_clk) + val |=3D REF_USE_PAD; + + if (xpcs->mhz125) { + val |=3D REF_MPLLB_DIV2; + val |=3D REF_CLK_DIV2; + val |=3D FIELD_PREP(REF_RANGE_MASK, RANGE_52_78_MHZ); + } else { + val |=3D 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 =3D 125 << MLLB_MULTIPLIER_OFF; + else + val =3D 39 << MLLB_MULTIPLIER_OFF; + + 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 =3D FIELD_PREP(MPLLB_FRACN_CTRL_MASK, 0); + else + val =3D 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 =3D FIELD_PREP(MPLLB_BANDWIDTH_MASK, 68); + else + val =3D FIELD_PREP(MPLLB_BANDWIDTH_MASK, 102); + + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_MPLLB_CTRL3, + MPLLB_BANDWIDTH_MASK, val); + + return 0; +} + +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_C= TL, + 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 =3D 0; + struct device *dev =3D xpcs->dev; + + /* Configure XPCS speed and VCO */ + if (target_pll =3D=3D XPCS_PLLA) { + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, EN_2_5G_MODE, 0); + s32g_xpcs_vco_cfg(xpcs, 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, 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 =3D=3D XPCS_PLLA) + s32g_xpcs_ref_clk_sel(xpcs, XPCS_PLLA); + else + s32g_xpcs_ref_clk_sel(xpcs, 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 =3D 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 =3D 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 =3D s32g_xpcs_wait_bits(xpcs, VR_MII_DIG_CTRL1, INIT, 0); + if (ret) { + dev_err(dev, "XPCS INIT failed\n"); + return ret; + } + + return ret; +} + +/* + * phylink_pcs_ops + */ + +static unsigned int s32cc_phylink_pcs_inband_caps(struct phylink_pcs *pcs, + phy_interface_t interface) +{ + switch (interface) { + case PHY_INTERFACE_MODE_SGMII: + return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; + + default: + return 0; + } +} + +static int s32cc_phylink_pcs_config(struct phylink_pcs *pcs, + unsigned int neg_mode, + phy_interface_t interface, + const unsigned long *advertising, + bool permit_pause_to_mac) +{ + struct s32g_xpcs *xpcs =3D phylink_pcs_to_s32g_xpcs(pcs); + + /* Step 1: Disable SGMII AN */ + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, AN_ENABLE, 0); + + s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL, + MII_AN_INTR_EN, + 0); + + if (!(neg_mode =3D=3D PHYLINK_PCS_NEG_INBAND_ENABLED)) + return 0; + + /* Step 2 */ + s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL, + PCS_MODE_MASK, + FIELD_PREP(PCS_MODE_MASK, PCS_MODE_SGMII)); + + /* Step 3 */ + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, + SS13 | SS6, + SS6); + + /* Step 4 */ + s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL, + MII_CTRL, + 0); + /* Step 5 and 8 */ + if (xpcs->pcie_shared =3D=3D 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); + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, MAC_AUTO_SW, 0); + } + + /* Step 6 */ + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, + CL37_TMR_OVRRIDE, CL37_TMR_OVRRIDE); + + /* Step 7 */ + s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL, + MII_AN_INTR_EN, + MII_AN_INTR_EN); + + /* Step 9: Enable SGMII AN */ + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, AN_ENABLE, AN_ENABLE); + + return 0; +} + +static void s32cc_phylink_pcs_get_state(struct phylink_pcs *pcs, unsigned = int neg_mode, + struct phylink_link_state *state) +{ + struct s32g_xpcs *xpcs =3D phylink_pcs_to_s32g_xpcs(pcs); + bool ss6, ss13, an_enabled; + struct device *dev =3D xpcs->dev; + unsigned int val, ss; + + an_enabled =3D (neg_mode =3D=3D PHYLINK_PCS_NEG_INBAND_ENABLED); + + if (an_enabled) { + state->link =3D 0; + val =3D s32g_xpcs_read(xpcs, VR_MII_AN_INTR_STS); + + /* Interrupt is raised with each SGMII AN that is in cases + * Link down - Every SGMII link timer expire + * Link up - Once before link goes up + * So either linkup or raised interrupt mean AN was completed + */ + if ((val & CL37_ANCMPLT_INTR) || (val & CL37_ANSGM_STS_LINK)) { + state->an_complete =3D 1; + if (val & CL37_ANSGM_STS_LINK) + state->link =3D 1; + else + return; + if (val & CL37_ANSGM_STS_DUPLEX) + state->duplex =3D DUPLEX_FULL; + else + state->duplex =3D DUPLEX_HALF; + ss =3D FIELD_GET(CL37_ANSGM_STS_SPEED_MASK, val); + } else { + return; + } + + } else { + val =3D s32g_xpcs_read(xpcs, SR_MII_STS); + state->link =3D !!(val & LINK_STS); + state->an_complete =3D 0; + state->pause =3D MLO_PAUSE_NONE; + + val =3D s32g_xpcs_read(xpcs, SR_MII_CTRL); + if (val & DUPLEX_MODE) + state->duplex =3D DUPLEX_FULL; + else + state->duplex =3D DUPLEX_HALF; + + /* + * Build similar value as CL37_ANSGM_STS_SPEED with + * SS6 and SS13 of SR_MII_CTRL: + * - 0 for 10 Mbps + * - 1 for 100 Mbps + * - 2 for 1000 Mbps + */ + ss6 =3D !!(val & SS6); + ss13 =3D !!(val & SS13); + ss =3D ss6 << 1 | ss13; + } + + switch (ss) { + case CL37_ANSGM_10MBPS: + state->speed =3D SPEED_10; + break; + case CL37_ANSGM_100MBPS: + state->speed =3D SPEED_100; + break; + case CL37_ANSGM_1000MBPS: + state->speed =3D SPEED_1000; + break; + default: + dev_err(dev, "Failed to interpret the value of SR_MII_CTRL\n"); + break; + } + + val =3D s32g_xpcs_read(xpcs, VR_MII_DIG_CTRL1); + if ((val & EN_2_5G_MODE) && state->speed =3D=3D SPEED_1000) + state->speed =3D SPEED_2500; + + /* Cover SGMII AN inability to distigunish between 1G and 2.5G */ + if ((val & EN_2_5G_MODE) && + state->speed !=3D SPEED_2500 && an_enabled) { + dev_err(dev, "Speed not supported in SGMII AN mode\n"); + } +} + +static void s32cc_phylink_pcs_link_up(struct phylink_pcs *pcs, + unsigned int neg_mode, + phy_interface_t interface, int speed, + int duplex) +{ + struct s32g_xpcs *xpcs =3D phylink_pcs_to_s32g_xpcs(pcs); + struct device *dev =3D xpcs->dev; + bool an_enabled =3D (neg_mode =3D=3D PHYLINK_PCS_NEG_INBAND_ENABLED); + unsigned int val; + int ret; + + dev_dbg(dev, "xpcs_%d: speed=3D%u duplex=3D%d an=3D%d\n", xpcs->id, + speed, duplex, an_enabled); + + if (an_enabled) + return; + + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, AN_ENABLE, 0); + s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL, MII_AN_INTR_EN, 0); + s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL, MII_CTRL, 0); + + if (duplex =3D=3D DUPLEX_FULL) + val =3D DUPLEX_MODE; + else + val =3D 0; + + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, DUPLEX_MODE, val); + + switch (speed) { + case SPEED_10: + val =3D 0; + break; + case SPEED_100: + val =3D SS13; + break; + case SPEED_1000: + val =3D SS6; + break; + case SPEED_2500: + val =3D SS6; + break; + default: + dev_err(dev, "Speed not supported\n"); + break; + } + + if (speed =3D=3D SPEED_2500) { + ret =3D s32g_serdes_bifurcation_pll_transit(xpcs, XPCS_PLLB); + if (ret) + dev_err(dev, "Switch to PLLB failed\n"); + } else { + ret =3D s32g_serdes_bifurcation_pll_transit(xpcs, XPCS_PLLA); + if (ret) + dev_err(dev, "Switch to PLLA failed\n"); + } + + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, SS6 | SS13, val); +} + +static const struct phylink_pcs_ops s32cc_phylink_pcs_ops =3D { + .pcs_inband_caps =3D s32cc_phylink_pcs_inband_caps, + .pcs_get_state =3D s32cc_phylink_pcs_get_state, + .pcs_config =3D s32cc_phylink_pcs_config, + .pcs_link_up =3D s32cc_phylink_pcs_link_up, +}; + +/* + * Serdes functions for initializing/configuring/releasing the xpcs + */ + +int s32g_xpcs_pre_pcie_2g5(struct s32g_xpcs *xpcs) +{ + int ret; + + /* Enable voltage boost */ + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_GENCTRL1, VBOOST_EN_0, + VBOOST_EN_0); + + /* TX rate baud */ + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_RATE_CTRL, 0x7, 0x0U); + + /* Rx rate baud/2 */ + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_RX_RATE_CTRL, 0x3U, 0x1U); + + /* Set low-frequency operating band */ + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_CDR_CTRL, CDR_SSC_EN_0, + VCO_LOW_FREQ_0); + + ret =3D s32g_serdes_bifurcation_pll_transit(xpcs, XPCS_PLLB); + if (ret) + dev_err(xpcs->dev, "Switch to PLLB failed\n"); + + return ret; +} + +int s32g_xpcs_init_plls(struct s32g_xpcs *xpcs) +{ + int ret; + struct device *dev =3D xpcs->dev; + + if (!xpcs->ext_clk) { + /* Step 1 */ + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, BYP_PWRUP, BYP_PWRUP); + } else if (xpcs->pcie_shared =3D=3D NOT_SHARED) { + ret =3D s32g_xpcs_wait_power_good_state(xpcs); + if (ret) + return ret; + } else if (xpcs->pcie_shared =3D=3D PCIE_XPCS_2G5) { + ret =3D 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, XPCS_PLLA); + ret =3D s32g_xpcs_init_mplla(xpcs); + if (ret) { + dev_err(dev, "Failed to initialize PLLA\n"); + return ret; + } + ret =3D s32g_xpcs_init_mpllb(xpcs); + if (ret) { + dev_err(dev, "Failed to initialize PLLB\n"); + return ret; + } + s32g_xpcs_vco_cfg(xpcs, XPCS_PLLA); + + /* Step 18 */ + if (!xpcs->ext_clk) + 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 ret; +} + +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); +} + +int s32g_xpcs_init(struct s32g_xpcs *xpcs, struct device *dev, + unsigned char id, void __iomem *base, bool ext_clk, + unsigned long rate, enum s32g_xpcs_shared pcie_shared) +{ + struct regmap_config conf; + + if (rate !=3D (125 * HZ_PER_MHZ) && rate !=3D (100 * HZ_PER_MHZ)) { + dev_err(dev, "XPCS cannot operate @%lu HZ\n", rate); + return -EINVAL; + } + + xpcs->base =3D base; + xpcs->ext_clk =3D ext_clk; + xpcs->id =3D id; + xpcs->dev =3D dev; + xpcs->pcie_shared =3D pcie_shared; + + if (rate =3D=3D (125 * HZ_PER_MHZ)) + xpcs->mhz125 =3D true; + else + xpcs->mhz125 =3D false; + + conf =3D s32g_xpcs_regmap_config; + + if (!id) + conf.name =3D "xpcs0"; + else + conf.name =3D "xpcs1"; + + xpcs->regmap =3D devm_regmap_init(dev, NULL, xpcs, &conf); + if (IS_ERR(xpcs->regmap)) + return dev_err_probe(dev, PTR_ERR(xpcs->regmap), + "Failed to init register amp\n"); + + /* Phylink PCS */ + xpcs->pcs.ops =3D &s32cc_phylink_pcs_ops; + xpcs->pcs.poll =3D true; + __set_bit(PHY_INTERFACE_MODE_SGMII, xpcs->pcs.supported_interfaces); + + return 0; +} diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig index 45184a3cdd69..bb7f59897faf 100644 --- a/drivers/phy/freescale/Kconfig +++ b/drivers/phy/freescale/Kconfig @@ -66,6 +66,7 @@ config PHY_S32G_SERDES tristate "NXP S32G SERDES support" depends on ARCH_S32 || COMPILE_TEST select GENERIC_PHY + select REGMAP help This option enables support for S23G SerDes PHY used for PCIe & Ethernet diff --git a/drivers/phy/freescale/phy-nxp-s32g-serdes.c b/drivers/phy/free= scale/phy-nxp-s32g-serdes.c index 321a80c02be5..f2f7eb5aa327 100644 --- a/drivers/phy/freescale/phy-nxp-s32g-serdes.c +++ b/drivers/phy/freescale/phy-nxp-s32g-serdes.c @@ -12,12 +12,14 @@ #include #include #include +#include #include #include #include #include #include =20 +#define S32G_SERDES_XPCS_MAX 2 #define S32G_SERDES_MODE_MAX 5 =20 #define EXTERNAL_CLK_NAME "ext" @@ -32,6 +34,52 @@ #define S32G_PCIE_PHY_MPLLA_CTRL 0x10 #define MPLL_STATE BIT(30) =20 +#define S32G_PCIE_PHY_MPLLB_CTRL 0x14 +#define MPLLB_SSC_EN BIT(1) + +#define S32G_PCIE_PHY_EXT_CTRL_SEL 0x18 +#define EXT_PHY_CTRL_SEL BIT(0) + +#define S32G_PCIE_PHY_EXT_BS_CTRL 0x1C +#define EXT_BS_TX_LOWSWING BIT(6) +#define EXT_BS_RX_BIGSWING BIT(5) +#define EXT_BS_RX_LEVEL_MASK GENMASK(4, 0) + +#define S32G_PCIE_PHY_REF_CLK_CTRL 0x20 +#define EXT_REF_RANGE_MASK GENMASK(5, 3) +#define REF_CLK_DIV2_EN BIT(2) +#define REF_CLK_MPLLB_DIV2_EN BIT(1) + +#define S32G_PCIE_PHY_EXT_MPLLA_CTRL_1 0x30 +#define EXT_MPLLA_BANDWIDTH_MASK GENMASK(15, 0) + +#define S32G_PCIE_PHY_EXT_MPLLB_CTRL_1 0x40 +#define EXT_MPLLB_DIV_MULTIPLIER_MASK GENMASK(31, 24) +#define EXT_MPLLB_DIV_CLK_EN BIT(19) +#define EXT_MPLLB_DIV8_CLK_EN BIT(18) +#define EXT_MPLLB_DIV10_CLK_EN BIT(16) +#define EXT_MPLLB_BANDWIDTH_MASK GENMASK(15, 0) + +#define S32G_PCIE_PHY_EXT_MPLLB_CTRL_2 0x44 +#define EXT_MPLLB_FRACN_CTRL_MASK GENMASK(22, 12) +#define MPLLB_MULTIPLIER_MASK GENMASK(8, 0) + +#define S32G_PCIE_PHY_EXT_MPLLB_CTRL_3 0x48 +#define EXT_MPLLB_WORD_DIV2_EN BIT(31) +#define EXT_MPLLB_TX_CLK_DIV_MASK GENMASK(30, 28) + +#define S32G_PCIE_PHY_EXT_MISC_CTRL_1 0xA0 +#define EXT_RX_LOS_THRESHOLD_MASK GENMASK(7, 1) +#define EXT_RX_VREF_CTRL_MASK GENMASK(28, 24) + +#define S32G_PCIE_PHY_EXT_MISC_CTRL_2 0xA4 +#define EXT_TX_VBOOST_LVL_MASK GENMASK(18, 16) +#define EXT_TX_TERM_CTRL_MASK GENMASK(26, 24) + +#define S32G_PCIE_PHY_XPCS1_RX_OVRD_CTRL 0xD0 +#define XPCS1_RX_VCO_LD_VAL_MASK GENMASK(28, 16) +#define XPCS1_RX_REF_LD_VAL_MASK GENMASK(14, 8) + #define S32G_SS_RW_REG_0 0xF0 #define SUBMODE_MASK GENMASK(3, 0) #define CLKEN_MASK BIT(23) @@ -44,6 +92,9 @@ =20 #define S32G_PHY_REG_DATA 0x4 =20 +#define S32G_PHY_RST_CTRL 0x8 +#define WARM_RST BIT(1) + #define RAWLANE0_DIG_PCS_XF_RX_EQ_DELTA_IQ_OVRD_IN 0x3019 #define RAWLANE1_DIG_PCS_XF_RX_EQ_DELTA_IQ_OVRD_IN 0x3119 =20 @@ -76,16 +127,33 @@ struct s32g_pcie_ctrl { bool powered_on; }; =20 +struct s32g_xpcs_ctrl { + struct s32g_xpcs *phys[2]; + void __iomem *base0, *base1; +}; + struct s32g_serdes { struct s32g_serdes_ctrl ctrl; struct s32g_pcie_ctrl pcie; + struct s32g_xpcs_ctrl xpcs; struct device *dev; + u8 lanes_status; }; =20 /* PCIe phy subsystem */ =20 #define S32G_SERDES_PCIE_FREQ (100 * HZ_PER_MHZ) =20 +static void s32g_pcie_phy_reset(struct s32g_serdes *serdes) +{ + u32 val; + + val =3D readl(serdes->pcie.phy_base + S32G_PHY_RST_CTRL); + writel(val | WARM_RST, serdes->pcie.phy_base + S32G_PHY_RST_CTRL); + usleep_range(1000, 1100); + writel(val, serdes->pcie.phy_base + S32G_PHY_RST_CTRL); +} + static int s32g_pcie_check_clk(struct s32g_serdes *serdes) { struct s32g_serdes_ctrl *sctrl =3D &serdes->ctrl; @@ -277,6 +345,192 @@ static struct phy *s32g_serdes_phy_xlate(struct devic= e *dev, return phy; } =20 +/* XPCS subsystem */ + +static int s32g_serdes_xpcs_init(struct s32g_serdes *serdes, int id) +{ + struct s32g_serdes_ctrl *ctrl =3D &serdes->ctrl; + struct s32g_xpcs_ctrl *xpcs =3D &serdes->xpcs; + enum s32g_xpcs_shared shared =3D NOT_SHARED; + unsigned long rate =3D ctrl->ref_clk_rate; + struct device *dev =3D serdes->dev; + void __iomem *base; + + if (!id) + base =3D xpcs->base0; + else + base =3D xpcs->base1; + + if (ctrl->ss_mode =3D=3D 1 || ctrl->ss_mode =3D=3D 2) + shared =3D PCIE_XPCS_1G; + else if (ctrl->ss_mode =3D=3D 5) + shared =3D PCIE_XPCS_2G5; + + return s32g_xpcs_init(xpcs->phys[id], dev, id, base, + ctrl->ext_clk, rate, shared); +} + +static void s32g_serdes_prepare_pma_mode5(struct s32g_serdes *serdes) +{ + u32 val; + /* Configure TX_VBOOST_LVL and TX_TERM_CTRL */ + val =3D readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MISC_CTRL_2); + val &=3D ~(EXT_TX_VBOOST_LVL_MASK | EXT_TX_TERM_CTRL_MASK); + val |=3D FIELD_PREP(EXT_TX_VBOOST_LVL_MASK, 0x3) | + FIELD_PREP(EXT_TX_TERM_CTRL_MASK, 0x4); + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MISC_CTRL_2); + + /* Enable phy external control */ + val =3D readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_CTRL_SEL); + val |=3D EXT_PHY_CTRL_SEL; + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_CTRL_SEL); + + /* Configure ref range, disable PLLB/ref div2 */ + val =3D readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_REF_CLK_CTRL); + val &=3D ~(REF_CLK_DIV2_EN | REF_CLK_MPLLB_DIV2_EN | EXT_REF_RANGE_MASK); + val |=3D FIELD_PREP(EXT_REF_RANGE_MASK, 0x3); + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_REF_CLK_CTRL); + + /* Configure multiplier */ + val =3D readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_2); + val &=3D ~(MPLLB_MULTIPLIER_MASK | EXT_MPLLB_FRACN_CTRL_MASK | BIT(24) | = BIT(28)); + val |=3D FIELD_PREP(MPLLB_MULTIPLIER_MASK, 0x27U) | + FIELD_PREP(EXT_MPLLB_FRACN_CTRL_MASK, 0x414); + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_2); + + val =3D readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_MPLLB_CTRL); + val &=3D ~MPLLB_SSC_EN; + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_MPLLB_CTRL); + + /* Configure tx lane division, disable word clock div2*/ + val =3D readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_3); + val &=3D ~(EXT_MPLLB_WORD_DIV2_EN | EXT_MPLLB_TX_CLK_DIV_MASK); + val |=3D FIELD_PREP(EXT_MPLLB_TX_CLK_DIV_MASK, 0x5); + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_3); + + /* Configure bandwidth for filtering and div10*/ + val =3D readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_1); + val &=3D ~(EXT_MPLLB_BANDWIDTH_MASK | EXT_MPLLB_DIV_CLK_EN | + EXT_MPLLB_DIV8_CLK_EN | EXT_MPLLB_DIV_MULTIPLIER_MASK); + val |=3D FIELD_PREP(EXT_MPLLB_BANDWIDTH_MASK, 0x5f) | EXT_MPLLB_DIV10_CLK= _EN; + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_1); + + val =3D readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLA_CTRL_1); + val &=3D ~(EXT_MPLLA_BANDWIDTH_MASK); + val |=3D FIELD_PREP(EXT_MPLLA_BANDWIDTH_MASK, 0xc5); + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLA_CTRL_1); + + /* Configure VCO */ + val =3D readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_XPCS1_RX_OVRD_CTRL); + val &=3D ~(XPCS1_RX_VCO_LD_VAL_MASK | XPCS1_RX_REF_LD_VAL_MASK); + val |=3D FIELD_PREP(XPCS1_RX_VCO_LD_VAL_MASK, 0x540) | + FIELD_PREP(XPCS1_RX_REF_LD_VAL_MASK, 0x2b); + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_XPCS1_RX_OVRD_CTRL); + + /* Boundary scan control */ + val =3D readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_BS_CTRL); + val &=3D ~(EXT_BS_RX_LEVEL_MASK | EXT_BS_TX_LOWSWING); + val |=3D FIELD_PREP(EXT_BS_RX_LEVEL_MASK, 0xB) | EXT_BS_RX_BIGSWING; + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_BS_CTRL); + + /* Rx loss threshold */ + val =3D readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MISC_CTRL_1); + val &=3D ~(EXT_RX_LOS_THRESHOLD_MASK | EXT_RX_VREF_CTRL_MASK); + val |=3D FIELD_PREP(EXT_RX_LOS_THRESHOLD_MASK, 0x3U) | + FIELD_PREP(EXT_RX_VREF_CTRL_MASK, 0x11U); + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MISC_CTRL_1); +} + +static int s32g_serdes_enable_mode5(struct s32g_serdes *serdes, struct s32= g_xpcs *xpcs) +{ + int ret; + + s32g_serdes_prepare_pma_mode5(serdes); + + ret =3D s32g_xpcs_pre_pcie_2g5(xpcs); + if (ret) { + dev_err(serdes->dev, + "Failed to prepare SerDes for PCIE & XPCS @ 2G5 mode\n"); + return ret; + } + + s32g_pcie_phy_reset(serdes); + + return 0; +} + +static int s32g_serdes_init_clks(struct s32g_serdes *serdes) +{ + struct s32g_serdes_ctrl *ctrl =3D &serdes->ctrl; + struct s32g_xpcs_ctrl *xpcs =3D &serdes->xpcs; + struct s32g_xpcs *order[2]; + size_t i; + int ret; + + switch (ctrl->ss_mode) { + case 0: + return 0; + case 1: + order[0] =3D xpcs->phys[0]; + order[1] =3D NULL; + break; + case 2: + case 5: + order[0] =3D xpcs->phys[1]; + order[1] =3D NULL; + break; + case 3: + order[0] =3D xpcs->phys[1]; + order[1] =3D xpcs->phys[0]; + break; + case 4: + order[0] =3D xpcs->phys[0]; + order[1] =3D xpcs->phys[1]; + break; + default: + return -EINVAL; + } + + for (i =3D 0; i < ARRAY_SIZE(order); i++) { + if (!order[i]) + continue; + + ret =3D s32g_xpcs_init_plls(order[i]); + if (ret) + return ret; + } + + for (i =3D 0; i < ARRAY_SIZE(order); i++) { + if (!order[i]) + continue; + + if (ctrl->ss_mode =3D=3D 5) { + ret =3D s32g_serdes_enable_mode5(serdes, order[i]); + if (ret) + return ret; + } else { + s32g_xpcs_vreset(order[i]); + } + } + + for (i =3D 0; i < ARRAY_SIZE(order); i++) { + if (!order[i]) + continue; + + ret =3D s32g_xpcs_wait_vreset(order[i]); + if (ret) + return ret; + + ret =3D s32g_xpcs_reset_rx(order[i]); + if (ret) + return ret; + + s32g_xpcs_disable_an(order[i]); + } + + return 0; +} + /* Serdes subsystem */ =20 static int s32g_serdes_assert_reset(struct s32g_serdes *serdes) @@ -331,6 +585,10 @@ static int s32g_serdes_init(struct s32g_serdes *serdes) return ret; } =20 + /* + * We have a tight timing for the init sequence and any delay linked to + * printk as an example can fail the init after reset + */ ret =3D s32g_serdes_assert_reset(serdes); if (ret) goto disable_clks; @@ -363,7 +621,13 @@ static int s32g_serdes_init(struct s32g_serdes *serdes) dev_info(serdes->dev, "Using mode %d for SerDes subsystem\n", ctrl->ss_mode); =20 - return 0; + ret =3D s32g_serdes_init_clks(serdes); + if (ret) { + dev_err(serdes->dev, "XPCS init failed\n"); + goto disable_clks; + } + + return ret; =20 disable_clks: clk_bulk_disable_unprepare(serdes->ctrl.nclks, @@ -449,12 +713,32 @@ static int s32g_serdes_get_pcie_resources(struct plat= form_device *pdev, struct s return 0; } =20 +static int s32g_serdes_get_xpcs_resources(struct platform_device *pdev, st= ruct s32g_serdes *serdes) +{ + struct s32g_xpcs_ctrl *xpcs =3D &serdes->xpcs; + struct device *dev =3D &pdev->dev; + + xpcs->base0 =3D devm_platform_ioremap_resource_byname(pdev, "xpcs0"); + if (IS_ERR(xpcs->base0)) { + dev_err(dev, "Failed to map 'xpcs0'\n"); + return PTR_ERR(xpcs->base0); + } + + xpcs->base1 =3D devm_platform_ioremap_resource_byname(pdev, "xpcs1"); + if (IS_ERR(xpcs->base1)) { + dev_err(dev, "Failed to map 'xpcs1'\n"); + return PTR_ERR(xpcs->base1); + } + + 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; + int ret, ss_mode =3D ctrl->ss_mode; struct phy *phy; =20 if (of_device_is_compatible(child_node, "nxp,s32g2-serdes-pcie-phy")) { @@ -476,6 +760,37 @@ static int s32g2_serdes_create_phy(struct s32g_serdes = *serdes, struct device_nod if (IS_ERR(phy_provider)) return PTR_ERR(phy_provider); =20 + } else if (of_device_is_compatible(child_node, "nxp,s32g2-serdes-xpcs")) { + struct s32g_xpcs_ctrl *xpcs_ctrl =3D &serdes->xpcs; + struct s32g_xpcs *xpcs; + int port; + + /* no Ethernet phy lane */ + if (ss_mode =3D=3D 0) + return 0; + + /* Get XPCS port number connected to the lane */ + if (of_property_read_u32(child_node, "reg", &port)) + return -EINVAL; + + /* XPCS1 is not used */ + if (ss_mode =3D=3D 1 && port =3D=3D 1) + return -EINVAL; + + /* XPCS0 is not used */ + if (ss_mode =3D=3D 2 && port =3D=3D 0) + return -EINVAL; + + xpcs =3D devm_kmalloc(dev, sizeof(*xpcs), GFP_KERNEL); + if (!xpcs) + return -ENOMEM; + + xpcs_ctrl->phys[port] =3D xpcs; + + ret =3D s32g_serdes_xpcs_init(serdes, port); + if (ret) + return ret; + } else { dev_warn(dev, "Skipping unknown child node %pOFn\n", child_node); } @@ -517,6 +832,10 @@ static int s32g_serdes_probe(struct platform_device *p= dev) if (ret) return ret; =20 + ret =3D s32g_serdes_get_xpcs_resources(pdev, serdes); + if (ret) + return ret; + ret =3D s32g_serdes_parse_lanes(dev, serdes); if (ret) return ret; @@ -555,6 +874,57 @@ static int __maybe_unused s32g_serdes_resume(struct de= vice *device) return ret; } =20 +struct phylink_pcs *s32g_serdes_pcs_create(struct device *dev, struct devi= ce_node *np) +{ + struct platform_device *pdev; + struct device_node *pcs_np; + struct s32g_serdes *serdes; + u32 port; + + if (of_property_read_u32(np, "reg", &port)) + return ERR_PTR(-EINVAL); + + if (port >=3D S32G_SERDES_XPCS_MAX) + return ERR_PTR(-EINVAL); + + /* The PCS pdev is attached to the parent node */ + pcs_np =3D 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 =3D of_find_device_by_node(pcs_np); + of_node_put(pcs_np); + if (!pdev) + return ERR_PTR(-EPROBE_DEFER); + + serdes =3D platform_get_drvdata(pdev); + if (!serdes) { + put_device(&pdev->dev); + return ERR_PTR(-EPROBE_DEFER); + } + + if (!serdes->xpcs.phys[port]) { + put_device(&pdev->dev); + return ERR_PTR(-EPROBE_DEFER); + } + + return &serdes->xpcs.phys[port]->pcs; +} +EXPORT_SYMBOL(s32g_serdes_pcs_create); + +void s32g_serdes_pcs_destroy(struct phylink_pcs *pcs) +{ + struct s32g_xpcs *xpcs =3D phylink_pcs_to_s32g_xpcs(pcs); + + put_device(xpcs->dev); +} +EXPORT_SYMBOL(s32g_serdes_pcs_destroy); + static const struct of_device_id s32g_serdes_match[] =3D { { .compatible =3D "nxp,s32g2-serdes", 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..96a0049b93a6 --- /dev/null +++ b/include/linux/pcs/pcs-nxp-s32g-xpcs.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/** + * Copyright 2021-2026 NXP + */ +#ifndef PCS_NXP_S32G_XPCS_H +#define PCS_NXP_S32G_XPCS_H + +#include + +enum s32g_xpcs_shared { + NOT_SHARED, + PCIE_XPCS_1G, + PCIE_XPCS_2G5, +}; + +enum s32g_xpcs_pll { + XPCS_PLLA, /* Slow PLL */ + 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; + struct phylink_pcs pcs; +}; + +#define phylink_pcs_to_s32g_xpcs(pl_pcs) \ + container_of((pl_pcs), struct s32g_xpcs, pcs) + +int s32g_xpcs_init(struct s32g_xpcs *xpcs, struct device *dev, + unsigned char id, void __iomem *base, bool ext_clk, + unsigned long rate, enum s32g_xpcs_shared pcie_shared); +int s32g_xpcs_init_plls(struct s32g_xpcs *xpcs); +int s32g_xpcs_pre_pcie_2g5(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); + +struct phylink_pcs *s32g_serdes_pcs_create(struct device *dev, struct devi= ce_node *np); +void s32g_serdes_pcs_destroy(struct phylink_pcs *pcs); + +#endif /* PCS_NXP_S32G_XPCS_H */ + --=20 2.43.0