* [PATCH 2/2] phy: mediatek: Add support for PCI-Express Gen4 S-PHY
2026-07-16 15:13 [PATCH 0/2] PHY: Add MediaTek PCI-Express Gen4 S-PHY Driver AngeloGioacchino Del Regno
2026-07-16 15:13 ` [PATCH 1/2] dt-bindings: phy: Document MT8196 MediaTek PCI-Express Gen4 S-PHY AngeloGioacchino Del Regno
@ 2026-07-16 15:13 ` AngeloGioacchino Del Regno
2026-07-16 15:24 ` sashiko-bot
1 sibling, 1 reply; 5+ messages in thread
From: AngeloGioacchino Del Regno @ 2026-07-16 15:13 UTC (permalink / raw)
To: chunfeng.yun
Cc: vkoul, neil.armstrong, robh, krzk+dt, conor+dt, matthias.bgg,
angelogioacchino.delregno, linux-arm-kernel, linux-mediatek,
linux-phy, devicetree, linux-kernel, kernel
Add support for the PCI-Express Gen4 S-PHY found in the latest
MediaTek SoCs, including MT8196, MT8894 and similar.
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
drivers/phy/mediatek/Kconfig | 9 +
drivers/phy/mediatek/Makefile | 1 +
drivers/phy/mediatek/phy-mtk-pcie-sphy.c | 560 +++++++++++++++++++++++
3 files changed, 570 insertions(+)
create mode 100644 drivers/phy/mediatek/phy-mtk-pcie-sphy.c
diff --git a/drivers/phy/mediatek/Kconfig b/drivers/phy/mediatek/Kconfig
index ba6461350951..77236f3084c8 100644
--- a/drivers/phy/mediatek/Kconfig
+++ b/drivers/phy/mediatek/Kconfig
@@ -13,6 +13,15 @@ config PHY_MTK_PCIE
callback for PCIe GEN3 port, it supports software efuse
initialization.
+config PHY_MTK_PCIE_SPHY
+ tristate "MediaTek PCIe S-PHY Driver"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on OF
+ select GENERIC_PHY
+ help
+ Say 'Y' here to add support for MediaTek PCIe S-PHY driver for
+ PCI-Express Gen4 controllers as found in MT6991, MT8196 and others.
+
config PHY_MTK_XFI_TPHY
tristate "MediaTek 10GE SerDes XFI T-PHY driver"
depends on ARCH_MEDIATEK || COMPILE_TEST
diff --git a/drivers/phy/mediatek/Makefile b/drivers/phy/mediatek/Makefile
index ed0da708759b..7e984c7cfea3 100644
--- a/drivers/phy/mediatek/Makefile
+++ b/drivers/phy/mediatek/Makefile
@@ -5,6 +5,7 @@
obj-$(CONFIG_PHY_MTK_DP) += phy-mtk-dp.o
obj-$(CONFIG_PHY_MTK_PCIE) += phy-mtk-pcie.o
+obj-$(CONFIG_PHY_MTK_PCIE_SPHY) += phy-mtk-pcie-sphy.o
obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
obj-$(CONFIG_PHY_MTK_UFS) += phy-mtk-ufs.o
obj-$(CONFIG_PHY_MTK_XSPHY) += phy-mtk-xsphy.o
diff --git a/drivers/phy/mediatek/phy-mtk-pcie-sphy.c b/drivers/phy/mediatek/phy-mtk-pcie-sphy.c
new file mode 100644
index 000000000000..c794505060a8
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-pcie-sphy.c
@@ -0,0 +1,560 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2022 MediaTek Inc.
+ * Copyright (c) 2026 Collabora Ltd.
+ * AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include "phy-mtk-io.h"
+
+/* PHY System Interface (SIF) registers */
+#define PEXTP_DIG_GLB_TOP 0x20
+# define RG_XTP_BYPASS_PIPE_RST_RC BIT(17)
+#define PEXTP_DIG_GLB_CKBG0 0x30
+# define RG_XTP_CKBG_XTAL_STABLE_TIME_SEL GENMASK(25, 16)
+#define PEXTP_DIG_GLB_TPLL_CTL0 0x38
+# define RG_XTP_TPLL_SET_STABLE_TIME_SEL GENMASK(7, 2)
+# define RG_XTP_TPLL_PWE_ON_STABLE_TIME_SEL GENMASK(9, 8)
+#define PEXTP_DIG_GLB_CLKREQ_CTL 0x50
+# define RG_XTP_CKM_EN_L1S0 BIT(13)
+# define RG_XTP_CKM_EN_L1S1 BIT(14)
+#define PEXTP_DIG_GLB_TPLL_CTL2 0xf4
+# define RG_XTP_TPLL_ISO_EN_STABLE_TIME_SEL GENMASK(13, 12)
+
+/* PHY System Interface Digital registers */
+#define PEXTP_DIG_LN_TRX_PIPE_IF_17 0x30e8
+# define RG_XTP_LN_RX_LF_CTLE_CSEL_GEN4 GENMASK(14, 12)
+#define PEXTP_DIG_LN_RX_F0 0x50f0
+# define RG_XTP_LN_RX_GEN1_CTLE1_CSEL GENMASK(3, 0)
+# define RG_XTP_LN_RX_GEN2_CTLE1_CSEL GENMASK(7, 4)
+# define RG_XTP_LN_RX_GEN3_CTLE1_CSEL GENMASK(11, 8)
+# define RG_XTP_LN_RX_GEN4_CTLE1_CSEL GENMASK(15, 12)
+#define PEXTP_DIG_LN_RX2_AEQ_EDGE_0 0x6004
+# define RG_XTP_LN_RX_AEQ_EGEQ_RATIO_GEN3 GENMASK(21, 16)
+# define RG_XTP_LN_RX_AEQ_EGEQ_RATIO_GEN4 GENMASK(29, 24)
+# define AEQ_EGEQ_RATIO_GEN3_TO_22 0x16
+# define AEQ_EGEQ_RATIO_GEN4_TO_22 0x16
+
+/* PHY System Interface Analog registers */
+#define PEXTP_ANA_GLB_TPLL1_RSVD 0x902c
+# define RG_XTP_GLB_TPLL1_P_PATH_GAIN GENMASK(2, 0)
+#define PEXTP_ANA_GLB_BIAS_0 0x9060
+# define RG_XTP_GLB_BIAS_INTR_CTRL GENMASK(5, 0)
+#define PEXTP_ANA_GLB_BIAS_1 0x90c0
+# define RG_XTP_GLB_BIAS_V2V_VTRIM GENMASK(9, 6)
+#define PEXTP_ANA_LN_TRX_0C 0xa00c
+# define RG_XTP_LN_TX_RSWN_IMPSEL GENMASK(20, 16)
+#define PEXTP_ANA_LN_TRX_34 0xa034
+# define RG_XTP_LN_RX_FE BIT(15)
+#define PEXTP_ANA_LN_TRX_6C 0xa06c
+# define RG_XTP_LN_RX_AEQ_CTLE_ERR_TYPE GENMASK(14, 13)
+# define AEQ_CTLE_SEARCH_ERR_TYPE_H1P5 0
+# define AEQ_CTLE_SEARCH_ERR_TYPE_H1P5_H2P5 1
+# define AEQ_CTLE_SEARCH_ERR_TYPE_P1P5_H2P5_H3P5 2
+#define PEXTP_ANA_LN_TRX_A0 0xa0a0
+# define RG_XTP_LN_TX_IMPSEL_PMOS GENMASK(4, 0)
+# define RG_XTP_LN_TX_IMPSEL_NMOS GENMASK(11, 7)
+# define RG_XTP_LN_RX_IMPSEL GENMASK(15, 12)
+#define PEXTP_ANA_LN_TRX_A8 0xa0a8
+# define RG_XTP_LN_RX_LEQ_RL_CTLE_CAL GENMASK(6, 2)
+# define RG_XTP_LN_RX_LEQ_RL_VGA_CAL GENMASK(11, 7)
+# define RG_XTP_LN_RX_LEQ_RL_DFE_CAL GENMASK(23, 19)
+#define PEXTP_DIG_LN_TX_LC_TABLE_RSWN_4 0xb004
+#define PEXTP_DIG_LN_TX_LC_TABLE_RSWN_8 0xb008
+#define PEXTP_DIG_LN_TX_LC_TABLE_RSWN_C 0xb00c
+#define PEXTP_DIG_LN_TX_LC_TABLE_RSWN_10 0xb010
+#define PEXTP_DIG_LN_TX_LC_TABLE_RSWN_14 0xb014
+#define PEXTP_DIG_LN_TX_LC_TABLE_RSWN_18 0xb018
+# define RG_XTP_LN_TX_LC_PRESET_MGx_Px_CM1 GENMASK(5, 0)
+# define RG_XTP_LN_TX_LC_PRESET_MGx_Px_C0 GENMASK(13, 8)
+# define RG_XTP_LN_TX_LC_PRESET_MGx_Px_CP1 GENMASK(21, 16)
+#define PEXTP_REG_LANE(x) ((x) * 0x100)
+
+/* PHY Clock Management (CKM) registers */
+#define XTP_CKM_FORCE_6 0x38
+# define RG_CKM_BIAS_WAIT_PRD_US GENMASK(21, 16)
+#define XTP_CKM_REG_SPLL_FBKDIV_5 0xd4
+# define RG_CKM_CKTX_IMPSEL_PMOS GENMASK(19, 16)
+# define RG_CKM_CKTX_IMPSEL_NMOS GENMASK(23, 20)
+# define RG_CKM_CKTX_IMPSEL_SW GENMASK(27, 24)
+
+/* Calibration data from eFuses */
+#define MTK_PCIE_SPHY_CALIBRATION_MAX_DATA_LANES 2
+#define MTK_PCIE_SPHY_CALIBRATION_LAST_QUIRK_VER 4
+
+/**
+ * struct mtk_pcie_sphy_imp_sel - Impedance Selection parameters
+ * @pmos: Impedance selection for P-Channel MOSFET
+ * @nmos: Impedance selection for N-Channel MOSFET
+ */
+struct mtk_pcie_sphy_imp_sel {
+ u8 pmos : 4;
+ u8 nmos : 4;
+};
+
+/**
+ * struct mtk_pcie_sphy_efuse - eFuse calibration data for S-PHY
+ * @int_r_ctrl: Internal resistor selection of TX Bias Current
+ * @xtp_vtrim: XTP Bias V2V voltage calibration
+ * @cktx_impsel: SPLL CKTX Impedance Selection (P and N MOSFET)
+ * @cktx_r_mid: SPLL CKTX Intermediate Transition Impedance (Rmid)
+ * @rx_leq_rl_ctle: RX Front-End Return Loss Continuous Time Linear Equalization value
+ * @rx_leq_rl_vga: RX Front-End Return Loss Variable Gain Amplifier value
+ * @rx_leq_rl_dfe: RX Front-End Return Loss Decision Feedback Equalization value
+ * @rx_impsel: RX Impedance Selection
+ * @tx_impsel: TX Impedance Selection (P and N MOSFET)
+ * @tx_rswn_impsel: TX RSWn (Switch Resistance) impedance selection
+ * @supported: eFuse calibration data is supported
+ */
+struct mtk_pcie_sphy_efuse {
+ u8 int_r_ctrl;
+ u8 xtp_vtrim;
+ struct mtk_pcie_sphy_imp_sel cktx_impsel;
+ u8 cktx_r_mid;
+ u8 rx_leq_rl_ctle;
+ u8 rx_leq_rl_vga;
+ u8 rx_leq_rl_dfe;
+ u8 rx_impsel;
+ struct mtk_pcie_sphy_imp_sel tx_impsel;
+ u8 tx_rswn_impsel[MTK_PCIE_SPHY_CALIBRATION_MAX_DATA_LANES];
+ bool supported;
+};
+
+/**
+ * struct mtk_pcie_sphy - PCI-Express S-PHY driver main structure
+ * @dev: Pointer to device structure
+ * @phy: Pointer to generic phy structure
+ * @sif_base: IO mapped register base address of system interface
+ * @ckm_base: IO mapped register base address of clock management interface
+ * @num_lanes: Number of lanes
+ * @calibration: eFuse calibration data for S-PHY
+ */
+struct mtk_pcie_sphy {
+ struct device *dev;
+ struct phy *phy;
+ void __iomem *sif_base;
+ void __iomem *ckm_base;
+ u8 num_lanes;
+ struct mtk_pcie_sphy_efuse calibration;
+};
+
+static void mtk_pcie_sphy_apply_calibration(struct mtk_pcie_sphy *pcie_sphy)
+{
+ struct mtk_pcie_sphy_efuse *cal = &pcie_sphy->calibration;
+ int i;
+
+ mtk_phy_update_field(pcie_sphy->sif_base + PEXTP_ANA_GLB_BIAS_0,
+ RG_XTP_GLB_BIAS_INTR_CTRL, cal->int_r_ctrl);
+
+ mtk_phy_update_field(pcie_sphy->sif_base + PEXTP_ANA_GLB_BIAS_1,
+ RG_XTP_GLB_BIAS_V2V_VTRIM, cal->xtp_vtrim);
+
+ mtk_phy_update_field(pcie_sphy->ckm_base + XTP_CKM_REG_SPLL_FBKDIV_5,
+ RG_CKM_CKTX_IMPSEL_PMOS, cal->cktx_impsel.pmos);
+
+ mtk_phy_update_field(pcie_sphy->ckm_base + XTP_CKM_REG_SPLL_FBKDIV_5,
+ RG_CKM_CKTX_IMPSEL_NMOS, cal->cktx_impsel.nmos);
+
+ mtk_phy_update_field(pcie_sphy->ckm_base + XTP_CKM_REG_SPLL_FBKDIV_5,
+ RG_CKM_CKTX_IMPSEL_SW, cal->cktx_r_mid);
+
+ for (i = 0; i < pcie_sphy->num_lanes; i++) {
+ void __iomem *sif_lane_base = pcie_sphy->sif_base + PEXTP_REG_LANE(i);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_0C,
+ RG_XTP_LN_TX_RSWN_IMPSEL, cal->tx_rswn_impsel[i]);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_A8,
+ RG_XTP_LN_RX_LEQ_RL_CTLE_CAL, cal->rx_leq_rl_ctle);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_A8,
+ RG_XTP_LN_RX_LEQ_RL_VGA_CAL, cal->rx_leq_rl_vga);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_A8,
+ RG_XTP_LN_RX_LEQ_RL_DFE_CAL, cal->rx_leq_rl_dfe);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_A0,
+ RG_XTP_LN_RX_IMPSEL, cal->rx_impsel);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_A0,
+ RG_XTP_LN_TX_IMPSEL_PMOS, cal->tx_impsel.pmos);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_A0,
+ RG_XTP_LN_TX_IMPSEL_NMOS, cal->tx_impsel.nmos);
+ }
+}
+
+/**
+ * mtk_pcie_sphy_init() - Initialize the PCI-Express S-PHY
+ * @phy: the phy to be initialized
+ *
+ * The hardware settings will be reset during suspend, it should be
+ * reinitialized when the consumer calls phy_init() again on resume.
+ */
+static int mtk_pcie_sphy_init(struct phy *phy)
+{
+ struct mtk_pcie_sphy *pcie_sphy = phy_get_drvdata(phy);
+ struct mtk_pcie_sphy_imp_sel tx_impsel;
+ int i;
+
+ /* Set CKM Bias wait time to 4 microseconds */
+ mtk_phy_update_field(pcie_sphy->ckm_base + XTP_CKM_FORCE_6,
+ RG_CKM_BIAS_WAIT_PRD_US, 4);
+
+ /* TPLL needs 63 ref_ck ticks to stabilize when setting frequency */
+ mtk_phy_update_field(pcie_sphy->sif_base + PEXTP_DIG_GLB_TPLL_CTL0,
+ RG_XTP_TPLL_SET_STABLE_TIME_SEL, 63);
+
+ /* TPLL needs 3 ref_ck ticks to stabilize when powering on... */
+ mtk_phy_update_field(pcie_sphy->sif_base + PEXTP_DIG_GLB_TPLL_CTL0,
+ RG_XTP_TPLL_PWE_ON_STABLE_TIME_SEL, 3);
+
+ /* ...and the same goes for setting isolation */
+ mtk_phy_update_field(pcie_sphy->sif_base + PEXTP_DIG_GLB_TPLL_CTL2,
+ RG_XTP_TPLL_ISO_EN_STABLE_TIME_SEL, 3);
+
+ /* XTAL doesn't need any stabilization time */
+ mtk_phy_update_field(pcie_sphy->sif_base + PEXTP_DIG_GLB_CKBG0,
+ RG_XTP_CKBG_XTAL_STABLE_TIME_SEL, 0);
+
+ /* Keep pextp_ckm enabled when in L1SS_L1S1 state */
+ mtk_phy_clear_bits(pcie_sphy->sif_base + PEXTP_DIG_GLB_CLKREQ_CTL, RG_XTP_CKM_EN_L1S1);
+
+ /* Set PIPE to reset TPLL */
+ mtk_phy_clear_bits(pcie_sphy->sif_base + PEXTP_DIG_GLB_TOP, RG_XTP_BYPASS_PIPE_RST_RC);
+
+ /* Set TPLL P-Path gain compensation to 1 */
+ mtk_phy_update_field(pcie_sphy->sif_base + PEXTP_ANA_GLB_TPLL1_RSVD,
+ RG_XTP_GLB_TPLL1_P_PATH_GAIN, 1);
+
+ for (i = 0; i < pcie_sphy->num_lanes; i++) {
+ void __iomem *sif_lane_base = pcie_sphy->sif_base + PEXTP_REG_LANE(i);
+
+ /* Set RX Lane AEQ CTRL-E Search Error type to h1.5 + h2.5 */
+ mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_6C,
+ RG_XTP_LN_RX_AEQ_CTLE_ERR_TYPE,
+ AEQ_CTLE_SEARCH_ERR_TYPE_H1P5_H2P5);
+
+ mtk_phy_set_bits(sif_lane_base + PEXTP_ANA_LN_TRX_34, RG_XTP_LN_RX_FE);
+
+ /* TRX: Select CTLE1 for RX Lane AutoEQ CTRL-E Setting on Gen4 */
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TRX_PIPE_IF_17,
+ RG_XTP_LN_RX_LF_CTLE_CSEL_GEN4, 1);
+
+ /* Set RX Lane AutoEQ CTRL-E for PCI-Express Gen1 to Gen 4 */
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_RX_F0,
+ RG_XTP_LN_RX_GEN1_CTLE1_CSEL, 13);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_RX_F0,
+ RG_XTP_LN_RX_GEN2_CTLE1_CSEL, 13);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_RX_F0,
+ RG_XTP_LN_RX_GEN3_CTLE1_CSEL, 13);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_RX_F0,
+ RG_XTP_LN_RX_GEN4_CTLE1_CSEL, 0);
+
+ /* Set RX Lane AutoEQ's Edge EQ Ratio to 22 * 0.0625 = 1.375 */
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_RX2_AEQ_EDGE_0,
+ RG_XTP_LN_RX_AEQ_EGEQ_RATIO_GEN3, 22);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_RX2_AEQ_EDGE_0,
+ RG_XTP_LN_RX_AEQ_EGEQ_RATIO_GEN4, 22);
+
+ /* Setup Digital lane TX Link Characteristics Table */
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_4,
+ RG_XTP_LN_TX_LC_PRESET_MGx_Px_C0, 10);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_4,
+ RG_XTP_LN_TX_LC_PRESET_MGx_Px_CP1, 2);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_8,
+ RG_XTP_LN_TX_LC_PRESET_MGx_Px_C0, 11);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_8,
+ RG_XTP_LN_TX_LC_PRESET_MGx_Px_CP1, 1);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_C,
+ RG_XTP_LN_TX_LC_PRESET_MGx_Px_C0, 12);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_10,
+ RG_XTP_LN_TX_LC_PRESET_MGx_Px_C0, 13);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_10,
+ RG_XTP_LN_TX_LC_PRESET_MGx_Px_CM1, 1);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_14,
+ RG_XTP_LN_TX_LC_PRESET_MGx_Px_C0, 11);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_14,
+ RG_XTP_LN_TX_LC_PRESET_MGx_Px_CM1, 1);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_18,
+ RG_XTP_LN_TX_LC_PRESET_MGx_Px_C0, 10);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_DIG_LN_TX_LC_TABLE_RSWN_18,
+ RG_XTP_LN_TX_LC_PRESET_MGx_Px_CM1, 2);
+ }
+
+ if (pcie_sphy->calibration.supported) {
+ mtk_pcie_sphy_apply_calibration(pcie_sphy);
+
+ tx_impsel.pmos = pcie_sphy->calibration.tx_impsel.pmos;
+ tx_impsel.nmos = pcie_sphy->calibration.tx_impsel.nmos;
+ } else {
+ /* Set P=10, N=9 to prevent EMI if no calibration present */
+ tx_impsel.pmos = 10;
+ tx_impsel.nmos = 9;
+ }
+
+ /* Select TX Impedance on N and P MOSFETs */
+ for (i = 0; i < pcie_sphy->num_lanes; i++) {
+ void __iomem *sif_lane_base = pcie_sphy->sif_base + PEXTP_REG_LANE(i);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_A0,
+ RG_XTP_LN_TX_IMPSEL_PMOS, tx_impsel.pmos);
+
+ mtk_phy_update_field(sif_lane_base + PEXTP_ANA_LN_TRX_A0,
+ RG_XTP_LN_TX_IMPSEL_NMOS, tx_impsel.nmos);
+ }
+
+ return 0;
+}
+
+static const struct phy_ops mtk_pcie_sphy_ops = {
+ .init = mtk_pcie_sphy_init,
+ .owner = THIS_MODULE,
+};
+
+static int mtk_pcie_sphy_get_one_cal_para(struct device *dev, const char *name, u8 max_val)
+{
+ u16 buf;
+ u8 tmp;
+ int ret;
+
+ /*
+ * All of the calibrations are always max 8 bits long, but some may
+ * be split between two different 8-bits cells: handle this corner
+ * case by retrying reading as u16.
+ */
+ ret = nvmem_cell_read_u8(dev, name, &tmp);
+ if (ret == 0)
+ buf = tmp;
+ else
+ ret = nvmem_cell_read_u16(dev, name, &buf);
+
+ if (ret) {
+ dev_err(dev, "Cannot get calibration data for %s: %d\n", name, ret);
+ return ret;
+ };
+
+ if (buf == 0) {
+ dev_warn(dev, "No calibration for %s. Using defaults\n", name);
+ return -ENOENT;
+ }
+
+ if (buf > max_val) {
+ dev_err(dev, "Bad value %u retrieved for %s. Returning.\n", buf, name);
+ return -ERANGE;
+ };
+
+ return buf;
+}
+
+static int mtk_pcie_sphy_get_calibration_data(struct mtk_pcie_sphy *pcie_sphy)
+{
+ struct mtk_pcie_sphy_efuse *cal = &pcie_sphy->calibration;
+ struct device *dev = pcie_sphy->dev;
+ u8 version;
+ int ret;
+
+ ret = mtk_pcie_sphy_get_one_cal_para(dev, "int-r",
+ FIELD_MAX(RG_XTP_GLB_BIAS_INTR_CTRL));
+ if (ret < 0)
+ goto end;
+ cal->int_r_ctrl = ret;
+
+ ret = mtk_pcie_sphy_get_one_cal_para(dev, "xtp-vtrim",
+ FIELD_MAX(RG_XTP_GLB_BIAS_V2V_VTRIM));
+ if (ret < 0)
+ goto end;
+ cal->xtp_vtrim = ret;
+
+ ret = mtk_pcie_sphy_get_one_cal_para(dev, "cktx-pmos",
+ FIELD_MAX(RG_CKM_CKTX_IMPSEL_PMOS));
+ if (ret < 0)
+ goto end;
+ cal->cktx_impsel.pmos = ret;
+
+ ret = mtk_pcie_sphy_get_one_cal_para(dev, "cktx-nmos",
+ FIELD_MAX(RG_CKM_CKTX_IMPSEL_NMOS));
+ if (ret < 0)
+ goto end;
+ cal->cktx_impsel.nmos = ret;
+
+ ret = mtk_pcie_sphy_get_one_cal_para(dev, "cktx-r-mid",
+ FIELD_MAX(RG_CKM_CKTX_IMPSEL_SW));
+ if (ret < 0)
+ goto end;
+ cal->cktx_r_mid = ret;
+
+ ret = mtk_pcie_sphy_get_one_cal_para(dev, "rxfe-lanes-rl-ctle",
+ FIELD_MAX(RG_XTP_LN_RX_LEQ_RL_CTLE_CAL));
+ if (ret < 0)
+ goto end;
+ cal->rx_leq_rl_ctle = ret;
+
+ ret = mtk_pcie_sphy_get_one_cal_para(dev, "rxfe-lanes-rl-vga",
+ FIELD_MAX(RG_XTP_LN_RX_LEQ_RL_VGA_CAL));
+ if (ret < 0)
+ goto end;
+ cal->rx_leq_rl_vga = ret;
+
+ ret = mtk_pcie_sphy_get_one_cal_para(dev, "rxfe-lanes-rl-dfe",
+ FIELD_MAX(RG_XTP_LN_RX_LEQ_RL_DFE_CAL));
+ if (ret < 0)
+ goto end;
+ cal->rx_leq_rl_dfe = ret;
+
+ ret = mtk_pcie_sphy_get_one_cal_para(dev, "rx-lanes-imp",
+ FIELD_MAX(RG_XTP_LN_RX_IMPSEL));
+ if (ret < 0)
+ goto end;
+ cal->rx_impsel = ret;
+
+ ret = mtk_pcie_sphy_get_one_cal_para(dev, "tx-lanes-pmos",
+ FIELD_MAX(RG_XTP_LN_TX_IMPSEL_PMOS));
+ if (ret < 0)
+ goto end;
+ cal->tx_impsel.pmos = ret;
+
+ ret = mtk_pcie_sphy_get_one_cal_para(dev, "tx-lanes-nmos",
+ FIELD_MAX(RG_XTP_LN_TX_IMPSEL_NMOS));
+ if (ret < 0)
+ goto end;
+ cal->tx_impsel.nmos = ret;
+
+ ret = mtk_pcie_sphy_get_one_cal_para(dev, "tx-ln0-rswn",
+ FIELD_MAX(RG_XTP_LN_TX_RSWN_IMPSEL));
+ if (ret < 0)
+ goto end;
+ cal->tx_rswn_impsel[0] = ret;
+
+ ret = mtk_pcie_sphy_get_one_cal_para(dev, "tx-ln1-rswn",
+ FIELD_MAX(RG_XTP_LN_TX_RSWN_IMPSEL));
+ if (ret < 0)
+ goto end;
+ cal->tx_rswn_impsel[1] = ret;
+
+ ret = mtk_pcie_sphy_get_one_cal_para(dev, "cal-version", 15);
+ if (ret < 0)
+ goto end;
+ version = ret;
+
+ /* Quirk for eFuse calibration table versions 0 to 4 */
+ if ((version <= MTK_PCIE_SPHY_CALIBRATION_LAST_QUIRK_VER) &&
+ cal->rx_leq_rl_ctle == 10) {
+ cal->rx_leq_rl_vga = cal->rx_leq_rl_ctle;
+ cal->rx_leq_rl_dfe = cal->rx_leq_rl_ctle;
+ }
+
+end:
+ if (ret < 0) {
+ /*
+ * If any of the calibration values is missing, or if there is
+ * no calibration at all in the eFuses, this is not a problem,
+ * as the PHY doesn't require one to actually work.
+ */
+ if (ret == -ENOENT) {
+ cal->supported = false;
+ return 0;
+ }
+ return ret;
+ };
+ cal->supported = true;
+
+ return 0;
+}
+
+static int mtk_pcie_sphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy_provider *provider;
+ struct mtk_pcie_sphy *pcie_sphy;
+ u32 num_lanes;
+ int ret;
+
+ pcie_sphy = devm_kzalloc(dev, sizeof(*pcie_sphy), GFP_KERNEL);
+ if (!pcie_sphy)
+ return -ENOMEM;
+
+ pcie_sphy->sif_base = devm_platform_ioremap_resource_byname(pdev, "sif");
+ if (IS_ERR(pcie_sphy->sif_base))
+ return dev_err_probe(dev, PTR_ERR(pcie_sphy->sif_base),
+ "Failed to map phy-sif base\n");
+
+ pcie_sphy->ckm_base = devm_platform_ioremap_resource_byname(pdev, "ckm");
+ if (IS_ERR(pcie_sphy->ckm_base))
+ return dev_err_probe(dev, PTR_ERR(pcie_sphy->ckm_base),
+ "Failed to map phy-ckm base\n");
+
+ pcie_sphy->phy = devm_phy_create(dev, dev->of_node, &mtk_pcie_sphy_ops);
+ if (IS_ERR(pcie_sphy->phy))
+ return dev_err_probe(dev, PTR_ERR(pcie_sphy->phy),
+ "Failed to create PCIe phy\n");
+
+ ret = of_property_read_u32(dev->of_node, "num-lanes", &num_lanes);
+ if (ret)
+ num_lanes = 1;
+ else if (num_lanes > 4)
+ return dev_err_probe(dev, -EINVAL, "Invalid number of lanes.\n");
+
+ pcie_sphy->num_lanes = num_lanes;
+ pcie_sphy->dev = dev;
+
+ if (pcie_sphy->num_lanes <= MTK_PCIE_SPHY_CALIBRATION_MAX_DATA_LANES) {
+ ret = mtk_pcie_sphy_get_calibration_data(pcie_sphy);
+ if (ret)
+ return ret;
+ }
+
+ phy_set_drvdata(pcie_sphy->phy, pcie_sphy);
+
+ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(provider))
+ return dev_err_probe(dev, PTR_ERR(provider),
+ "Could not register PCI-Express S-PHY\n");
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct of_device_id mtk_pcie_sphy_of_match[] = {
+ { .compatible = "mediatek,mt8196-pcie-sphy" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mtk_pcie_sphy_of_match);
+
+static struct platform_driver mtk_pcie_sphy_driver = {
+ .probe = mtk_pcie_sphy_probe,
+ .driver = {
+ .name = "mtk-pcie-sphy",
+ .of_match_table = mtk_pcie_sphy_of_match,
+ },
+};
+module_platform_driver(mtk_pcie_sphy_driver);
+
+MODULE_DESCRIPTION("MediaTek PCIe SPHY driver");
+MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>");
+MODULE_LICENSE("GPL");
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread