root/drivers/phy/freescale/phy-fsl-lynx-28g.c
// SPDX-License-Identifier: GPL-2.0+
/* Copyright (c) 2021-2022 NXP. */

#include <linux/bitfield.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>

#include "phy-fsl-lynx-core.h"

#define LYNX_28G_NUM_LANE                       8

/* SoC IP wrapper for protocol converters */
#define PCC8                                    0x10a0
#define PCC8_SGMIIa_KX                          BIT(3)
#define PCC8_SGMIIa_CFG                         BIT(0)

#define PCCC                                    0x10b0
#define PCCC_SXGMIIn_XFI                        BIT(3)
#define PCCC_SXGMIIn_CFG                        BIT(0)

#define PCCD                                    0x10b4
#define PCCD_E25Gn_CFG                          BIT(0)

#define PCCE                                    0x10b8
#define PCCE_E40Gn_LRV                          BIT(3)
#define PCCE_E40Gn_CFG                          BIT(0)
#define PCCE_E50Gn_LRV                          BIT(3)
#define PCCE_E50GnCFG                           BIT(0)
#define PCCE_E100Gn_LRV                         BIT(3)
#define PCCE_E100Gn_CFG                         BIT(0)

#define SGMII_CFG(id)                           (28 - (id) * 4) /* Offset into PCC8 */
#define SXGMII_CFG(id)                          (28 - (id) * 4) /* Offset into PCCC */
#define E25G_CFG(id)                            (28 - (id) * 4) /* Offset into PCCD */
#define E40G_CFG(id)                            (28 - (id) * 4) /* Offset into PCCE */
#define E50G_CFG(id)                            (20 - (id) * 4) /* Offset into PCCE */
#define E100G_CFG(id)                           (12 - (id) * 4) /* Offset into PCCE */

/* Per PLL registers */
#define PLLnRSTCTL(pll)                         (0x400 + (pll) * 0x100 + 0x0)
#define PLLnRSTCTL_DIS                          BIT(24)
#define PLLnRSTCTL_LOCK                         BIT(23)

#define PLLnCR0(pll)                            (0x400 + (pll) * 0x100 + 0x4)
#define PLLnCR0_REFCLK_SEL                      GENMASK(20, 16)
#define PLLnCR0_REFCLK_SEL_100MHZ               0x0
#define PLLnCR0_REFCLK_SEL_125MHZ               0x1
#define PLLnCR0_REFCLK_SEL_156MHZ               0x2
#define PLLnCR0_REFCLK_SEL_150MHZ               0x3
#define PLLnCR0_REFCLK_SEL_161MHZ               0x4

#define PLLnCR1(pll)                            (0x400 + (pll) * 0x100 + 0x8)
#define PLLnCR1_FRATE_SEL                       GENMASK(28, 24)
#define PLLnCR1_FRATE_5G_10GVCO                 0x0
#define PLLnCR1_FRATE_5G_25GVCO                 0x10
#define PLLnCR1_FRATE_10G_20GVCO                0x6
#define PLLnCR1_FRATE_12G_25GVCO                0x16

/* Per SerDes lane registers */
/* Lane a General Control Register */
#define LNaGCR0(lane)                           (0x800 + (lane) * 0x100 + 0x0)
#define LNaGCR0_PROTO_SEL                       GENMASK(7, 3)
#define LNaGCR0_PROTO_SEL_SGMII                 0x1
#define LNaGCR0_PROTO_SEL_XFI                   0xa
#define LNaGCR0_PROTO_SEL_25G                   0x1a
#define LNaGCR0_IF_WIDTH                        GENMASK(2, 0)
#define LNaGCR0_IF_WIDTH_10_BIT                 0x0
#define LNaGCR0_IF_WIDTH_20_BIT                 0x2
#define LNaGCR0_IF_WIDTH_40_BIT                 0x4

/* Lane a Tx Reset Control Register */
#define LNaTRSTCTL(lane)                        (0x800 + (lane) * 0x100 + 0x20)
#define LNaTRSTCTL_RST_REQ                      BIT(31)
#define LNaTRSTCTL_RST_DONE                     BIT(30)
#define LNaTRSTCTL_HLT_REQ                      BIT(27)
#define LNaTRSTCTL_STP_REQ                      BIT(26)
#define LNaTRSTCTL_DIS                          BIT(24)

/* Lane a Tx General Control Register */
#define LNaTGCR0(lane)                          (0x800 + (lane) * 0x100 + 0x24)
#define LNaTGCR0_USE_PLL                        BIT(28)
#define LNaTGCR0_USE_PLLF                       0x0
#define LNaTGCR0_USE_PLLS                       0x1
#define LNaTGCR0_N_RATE                         GENMASK(26, 24)
#define LNaTGCR0_N_RATE_FULL                    0x0
#define LNaTGCR0_N_RATE_HALF                    0x1
#define LNaTGCR0_N_RATE_QUARTER                 0x2
#define LNaTGCR0_N_RATE_DOUBLE                  0x3

#define LNaTECR0(lane)                          (0x800 + (lane) * 0x100 + 0x30)
#define LNaTECR0_EQ_TYPE                        GENMASK(30, 28)
#define LNaTECR0_EQ_SGN_PREQ                    BIT(23)
#define LNaTECR0_EQ_PREQ                        GENMASK(19, 16)
#define LNaTECR0_EQ_SGN_POST1Q                  BIT(15)
#define LNaTECR0_EQ_POST1Q                      GENMASK(12, 8)
#define LNaTECR0_EQ_AMP_RED                     GENMASK(5, 0)

#define LNaTECR1(lane)                          (0x800 + (lane) * 0x100 + 0x34)
#define LNaTECR1_EQ_ADPT_EQ_DRVR_DIS            BIT(31)
#define LNaTECR1_EQ_ADPT_EQ                     GENMASK(29, 24)

/* Lane a Rx Reset Control Register */
#define LNaRRSTCTL(lane)                        (0x800 + (lane) * 0x100 + 0x40)
#define LNaRRSTCTL_RST_REQ                      BIT(31)
#define LNaRRSTCTL_RST_DONE                     BIT(30)
#define LNaRRSTCTL_HLT_REQ                      BIT(27)
#define LNaRRSTCTL_STP_REQ                      BIT(26)
#define LNaRRSTCTL_DIS                          BIT(24)
#define LNaRRSTCTL_CDR_LOCK                     BIT(12)

/* Lane a Rx General Control Register */
#define LNaRGCR0(lane)                          (0x800 + (lane) * 0x100 + 0x44)
#define LNaRGCR0_USE_PLL                        BIT(28)
#define LNaRGCR0_USE_PLLF                       0x0
#define LNaRGCR0_USE_PLLS                       0x1
#define LNaRGCR0_N_RATE                         GENMASK(26, 24)
#define LNaRGCR0_N_RATE_FULL                    0x0
#define LNaRGCR0_N_RATE_HALF                    0x1
#define LNaRGCR0_N_RATE_QUARTER                 0x2
#define LNaRGCR0_N_RATE_DOUBLE                  0x3

#define LNaRGCR1(lane)                          (0x800 + (lane) * 0x100 + 0x48)
#define LNaRGCR1_RX_ORD_ELECIDLE                BIT(31)
#define LNaRGCR1_DATA_LOST_FLT                  BIT(30)
#define LNaRGCR1_DATA_LOST                      BIT(29)
#define LNaRGCR1_IDLE_CONFIG                    BIT(28)
#define LNaRGCR1_ENTER_IDLE_FLT_SEL             GENMASK(26, 24)
#define LNaRGCR1_EXIT_IDLE_FLT_SEL              GENMASK(22, 20)
#define LNaRGCR1_DATA_LOST_TH_SEL               GENMASK(18, 16)
#define LNaRGCR1_EXT_REC_CLK_SEL                GENMASK(10, 8)
#define LNaRGCR1_WAKE_TX_DIS                    BIT(5)
#define LNaRGCR1_PHY_RDY                        BIT(4)
#define LNaRGCR1_CHANGE_RX_CLK                  BIT(3)
#define LNaRGCR1_PWR_MGT                        GENMASK(2, 0)

#define LNaRECR0(lane)                          (0x800 + (lane) * 0x100 + 0x50)
#define LNaRECR0_EQ_GAINK2_HF_OV_EN             BIT(31)
#define LNaRECR0_EQ_GAINK2_HF_OV                GENMASK(28, 24)
#define LNaRECR0_EQ_GAINK3_MF_OV_EN             BIT(23)
#define LNaRECR0_EQ_GAINK3_MF_OV                GENMASK(20, 16)
#define LNaRECR0_EQ_GAINK4_LF_OV_EN             BIT(7)
#define LNaRECR0_EQ_GAINK4_LF_DIS               BIT(6)
#define LNaRECR0_EQ_GAINK4_LF_OV                GENMASK(4, 0)

#define LNaRECR1(lane)                          (0x800 + (lane) * 0x100 + 0x54)
#define LNaRECR1_EQ_BLW_OV_EN                   BIT(31)
#define LNaRECR1_EQ_BLW_OV                      GENMASK(28, 24)
#define LNaRECR1_EQ_OFFSET_OV_EN                BIT(23)
#define LNaRECR1_EQ_OFFSET_OV                   GENMASK(21, 16)

#define LNaRECR2(lane)                          (0x800 + (lane) * 0x100 + 0x58)
#define LNaRECR2_EQ_OFFSET_RNG_DBL              BIT(31)
#define LNaRECR2_EQ_BOOST                       GENMASK(29, 28)
#define LNaRECR2_EQ_BLW_SEL                     GENMASK(25, 24)
#define LNaRECR2_EQ_ZERO                        GENMASK(17, 16)
#define LNaRECR2_EQ_IND                         GENMASK(13, 12)
#define LNaRECR2_EQ_BIN_DATA_AVG_TC             GENMASK(5, 4)
#define LNaRECR2_SPARE_IN                       GENMASK(1, 0)

#define LNaRECR3(lane)                          (0x800 + (lane) * 0x100 + 0x5c)
#define LNaRECR3_EQ_SNAP_START                  BIT(31)
#define LNaRECR3_EQ_SNAP_DONE                   BIT(30)
#define LNaRECR3_EQ_GAINK2_HF_STAT              GENMASK(28, 24)
#define LNaRECR3_EQ_GAINK3_MF_STAT              GENMASK(20, 16)
#define LNaRECR3_SPARE_OUT                      GENMASK(13, 12)
#define LNaRECR3_EQ_GAINK4_LF_STAT              GENMASK(4, 0)

#define LNaRECR4(lane)                          (0x800 + (lane) * 0x100 + 0x60)
#define LNaRECR4_BLW_STAT                       GENMASK(28, 24)
#define LNaRECR4_EQ_OFFSET_STAT                 GENMASK(21, 16)
#define LNaRECR4_EQ_BIN_DATA_SEL                GENMASK(15, 12)
#define LNaRECR4_EQ_BIN_DATA                    GENMASK(8, 0) /* bit 9 is reserved */
#define LNaRECR4_EQ_BIN_DATA_SGN                BIT(8)

#define LNaRCCR0(lane)                          (0x800 + (lane) * 0x100 + 0x68)
#define LNaRCCR0_CAL_EN                         BIT(31)
#define LNaRCCR0_MEAS_EN                        BIT(30)
#define LNaRCCR0_CAL_BIN_SEL                    BIT(28)
#define LNaRCCR0_CAL_DC3_DIS                    BIT(27)
#define LNaRCCR0_CAL_DC2_DIS                    BIT(26)
#define LNaRCCR0_CAL_DC1_DIS                    BIT(25)
#define LNaRCCR0_CAL_DC0_DIS                    BIT(24)
#define LNaRCCR0_CAL_AC3_OV_EN                  BIT(15)
#define LNaRCCR0_CAL_AC3_OV                     GENMASK(11, 8)
#define LNaRCCR0_CAL_AC2_OV_EN                  BIT(7)

#define LNaRSCCR0(lane)                         (0x800 + (lane) * 0x100 + 0x74)
#define LNaRSCCR0_SMP_OFF_EN                    BIT(31)
#define LNaRSCCR0_SMP_OFF_OV_EN                 BIT(30)
#define LNaRSCCR0_SMP_MAN_OFF_EN                BIT(29)
#define LNaRSCCR0_SMP_OFF_RNG_OV_EN             BIT(27)
#define LNaRSCCR0_SMP_OFF_RNG_4X_OV             BIT(25)
#define LNaRSCCR0_SMP_OFF_RNG_2X_OV             BIT(24)
#define LNaRSCCR0_SMP_AUTOZ_PD                  BIT(23)
#define LNaRSCCR0_SMP_AUTOZ_CTRL                GENMASK(19, 16)
#define LNaRSCCR0_SMP_AUTOZ_D1R                 GENMASK(13, 12)
#define LNaRSCCR0_SMP_AUTOZ_D1F                 GENMASK(9, 8)
#define LNaRSCCR0_SMP_AUTOZ_EG1R                GENMASK(5, 4)
#define LNaRSCCR0_SMP_AUTOZ_EG1F                GENMASK(1, 0)

#define LNaTTLCR0(lane)                         (0x800 + (lane) * 0x100 + 0x80)
#define LNaTTLCR0_TTL_FLT_SEL                   GENMASK(29, 24)
#define LNaTTLCR0_TTL_SLO_PM_BYP                BIT(22)
#define LNaTTLCR0_STALL_DET_DIS                 BIT(21)
#define LNaTTLCR0_INACT_MON_DIS                 BIT(20)
#define LNaTTLCR0_CDR_OV                        GENMASK(18, 16)
#define LNaTTLCR0_DATA_IN_SSC                   BIT(15)
#define LNaTTLCR0_CDR_MIN_SMP_ON                GENMASK(1, 0)

#define LNaTCSR0(lane)                          (0x800 + (lane) * 0x100 + 0xa0)
#define LNaTCSR0_SD_STAT_OBS_EN                 BIT(31)
#define LNaTCSR0_SD_LPBK_SEL                    GENMASK(29, 28)

#define LNaPSS(lane)                            (0x1000 + (lane) * 0x4)
#define LNaPSS_TYPE                             GENMASK(30, 24)
#define LNaPSS_TYPE_SGMII                       (PROTO_SEL_SGMII_BASEX_KX << 2)
#define LNaPSS_TYPE_XFI                         (PROTO_SEL_XFI_10GBASER_KR_SXGMII << 2)
#define LNaPSS_TYPE_40G                         ((PROTO_SEL_XFI_10GBASER_KR_SXGMII << 2) | 3)
#define LNaPSS_TYPE_25G                         (PROTO_SEL_25G_50G_100G << 2)
#define LNaPSS_TYPE_100G                        ((PROTO_SEL_25G_50G_100G << 2) | 2)

/* MDEV_PORT is at the same bitfield address for all protocol converters */
#define MDEV_PORT                               GENMASK(31, 27)

#define SGMIIaCR0(lane)                         (0x1800 + (lane) * 0x10)
#define SGMIIaCR1(lane)                         (0x1804 + (lane) * 0x10)
#define SGMIIaCR1_SGPCS_EN                      BIT(11)

#define ANLTaCR0(lane)                          (0x1a00 + (lane) * 0x10)
#define ANLTaCR1(lane)                          (0x1a04 + (lane) * 0x10)

#define SXGMIIaCR0(lane)                        (0x1a80 + (lane) * 0x10)
#define SXGMIIaCR0_RST                          BIT(31)
#define SXGMIIaCR0_PD                           BIT(30)

#define SXGMIIaCR1(lane)                        (0x1a84 + (lane) * 0x10)

#define E25GaCR0(lane)                          (0x1b00 + (lane) * 0x10)
#define E25GaCR0_RST                            BIT(31)
#define E25GaCR0_PD                             BIT(30)

#define E25GaCR1(lane)                          (0x1b04 + (lane) * 0x10)

#define E25GaCR2(lane)                          (0x1b08 + (lane) * 0x10)
#define E25GaCR2_FEC_ENA                        BIT(23)
#define E25GaCR2_FEC_ERR_ENA                    BIT(22)
#define E25GaCR2_FEC91_ENA                      BIT(20)

#define E40GaCR0(pcvt)                          (0x1b40 + (pcvt) * 0x20)
#define E40GaCR1(pcvt)                          (0x1b44 + (pcvt) * 0x20)

#define E50GaCR1(pcvt)                          (0x1b84 + (pcvt) * 0x10)

#define E100GaCR1(pcvt)                         (0x1c04 + (pcvt) * 0x20)

#define CR(x)                                   ((x) * 4)

#define LYNX_28G_LANE_HALT_SLEEP_US             100
#define LYNX_28G_LANE_HALT_TIMEOUT_US           1000000

#define LYNX_28G_LANE_RESET_SLEEP_US            100
#define LYNX_28G_LANE_RESET_TIMEOUT_US          1000000

#define LYNX_28G_LANE_STOP_SLEEP_US             100
#define LYNX_28G_LANE_STOP_TIMEOUT_US           1000000

#define lynx_28g_lane_rmw                       lynx_lane_rmw
#define lynx_28g_lane_read                      lynx_lane_read
#define lynx_28g_lane_write                     lynx_lane_write

#define lynx_28g_priv                           lynx_priv
#define lynx_28g_lane                           lynx_lane
#define lynx_28g_pll                            lynx_pll

enum lynx_28g_eq_type {
        EQ_TYPE_NO_EQ = 0,
        EQ_TYPE_2TAP = 1,
        EQ_TYPE_3TAP = 2,
};

enum lynx_28g_proto_sel {
        PROTO_SEL_PCIE = 0,
        PROTO_SEL_SGMII_BASEX_KX = 1,
        PROTO_SEL_SATA = 2,
        PROTO_SEL_XAUI = 4,
        PROTO_SEL_XFI_10GBASER_KR_SXGMII = 0xa,
        PROTO_SEL_25G_50G_100G = 0x1a,
};

struct lynx_28g_proto_conf {
        /* LNaGCR0 */
        int proto_sel;
        int if_width;
        /* LNaTECR0 */
        int teq_type;
        int sgn_preq;
        int ratio_preq;
        int sgn_post1q;
        int ratio_post1q;
        int amp_red;
        /* LNaTECR1 */
        int adpt_eq;
        /* LNaRGCR1 */
        int enter_idle_flt_sel;
        int exit_idle_flt_sel;
        int data_lost_th_sel;
        /* LNaRECR0 */
        int gk2ovd;
        int gk3ovd;
        int gk4ovd;
        int gk2ovd_en;
        int gk3ovd_en;
        int gk4ovd_en;
        /* LNaRECR1 ? */
        int eq_offset_ovd;
        int eq_offset_ovd_en;
        /* LNaRECR2 */
        int eq_offset_rng_dbl;
        int eq_blw_sel;
        int eq_boost;
        int spare_in;
        /* LNaRSCCR0 */
        int smp_autoz_d1r;
        int smp_autoz_eg1r;
        /* LNaRCCR0 */
        int rccr0;
        /* LNaTTLCR0 */
        int ttlcr0;
};

static const struct lynx_28g_proto_conf lynx_28g_proto_conf[LANE_MODE_MAX] = {
        [LANE_MODE_1000BASEX_SGMII] = {
                .proto_sel = LNaGCR0_PROTO_SEL_SGMII,
                .if_width = LNaGCR0_IF_WIDTH_10_BIT,
                .teq_type = EQ_TYPE_NO_EQ,
                .sgn_preq = 1,
                .ratio_preq = 0,
                .sgn_post1q = 1,
                .ratio_post1q = 0,
                .amp_red = 6,
                .adpt_eq = 48,
                .enter_idle_flt_sel = 4,
                .exit_idle_flt_sel = 3,
                .data_lost_th_sel = 1,
                .gk2ovd = 0x1f,
                .gk3ovd = 0,
                .gk4ovd = 0,
                .gk2ovd_en = 1,
                .gk3ovd_en = 1,
                .gk4ovd_en = 0,
                .eq_offset_ovd = 0x1f,
                .eq_offset_ovd_en = 0,
                .eq_offset_rng_dbl = 0,
                .eq_blw_sel = 0,
                .eq_boost = 0,
                .spare_in = 0,
                .smp_autoz_d1r = 0,
                .smp_autoz_eg1r = 0,
                .rccr0 = LNaRCCR0_CAL_EN,
                .ttlcr0 = LNaTTLCR0_TTL_SLO_PM_BYP |
                          LNaTTLCR0_DATA_IN_SSC,
        },
        [LANE_MODE_USXGMII] = {
                .proto_sel = LNaGCR0_PROTO_SEL_XFI,
                .if_width = LNaGCR0_IF_WIDTH_20_BIT,
                .teq_type = EQ_TYPE_2TAP,
                .sgn_preq = 1,
                .ratio_preq = 0,
                .sgn_post1q = 1,
                .ratio_post1q = 3,
                .amp_red = 7,
                .adpt_eq = 48,
                .enter_idle_flt_sel = 0,
                .exit_idle_flt_sel = 0,
                .data_lost_th_sel = 0,
                .gk2ovd = 0,
                .gk3ovd = 0,
                .gk4ovd = 0,
                .gk2ovd_en = 0,
                .gk3ovd_en = 0,
                .gk4ovd_en = 0,
                .eq_offset_ovd = 0x1f,
                .eq_offset_ovd_en = 0,
                .eq_offset_rng_dbl = 1,
                .eq_blw_sel = 1,
                .eq_boost = 0,
                .spare_in = 0,
                .smp_autoz_d1r = 2,
                .smp_autoz_eg1r = 0,
                .rccr0 = LNaRCCR0_CAL_EN,
                .ttlcr0 = LNaTTLCR0_TTL_SLO_PM_BYP |
                          LNaTTLCR0_DATA_IN_SSC,
        },
        [LANE_MODE_10GBASER] = {
                .proto_sel = LNaGCR0_PROTO_SEL_XFI,
                .if_width = LNaGCR0_IF_WIDTH_20_BIT,
                .teq_type = EQ_TYPE_2TAP,
                .sgn_preq = 1,
                .ratio_preq = 0,
                .sgn_post1q = 1,
                .ratio_post1q = 3,
                .amp_red = 7,
                .adpt_eq = 48,
                .enter_idle_flt_sel = 0,
                .exit_idle_flt_sel = 0,
                .data_lost_th_sel = 0,
                .gk2ovd = 0,
                .gk3ovd = 0,
                .gk4ovd = 0,
                .gk2ovd_en = 0,
                .gk3ovd_en = 0,
                .gk4ovd_en = 0,
                .eq_offset_ovd = 0x1f,
                .eq_offset_ovd_en = 0,
                .eq_offset_rng_dbl = 1,
                .eq_blw_sel = 1,
                .eq_boost = 0,
                .spare_in = 0,
                .smp_autoz_d1r = 2,
                .smp_autoz_eg1r = 0,
                .rccr0 = LNaRCCR0_CAL_EN,
                .ttlcr0 = LNaTTLCR0_TTL_SLO_PM_BYP |
                          LNaTTLCR0_DATA_IN_SSC,
        },
        [LANE_MODE_25GBASER] = {
                .proto_sel = LNaGCR0_PROTO_SEL_25G,
                .if_width = LNaGCR0_IF_WIDTH_40_BIT,
                .teq_type = EQ_TYPE_3TAP,
                .sgn_preq = 1,
                .ratio_preq = 2,
                .sgn_post1q = 1,
                .ratio_post1q = 7,
                .amp_red = 0,
                .adpt_eq = 48,
                .enter_idle_flt_sel = 0,
                .exit_idle_flt_sel = 0,
                .data_lost_th_sel = 0,
                .gk2ovd = 0,
                .gk3ovd = 0,
                .gk4ovd = 5,
                .gk2ovd_en = 0,
                .gk3ovd_en = 0,
                .gk4ovd_en = 1,
                .eq_offset_ovd = 0x1f,
                .eq_offset_ovd_en = 0,
                .eq_offset_rng_dbl = 1,
                .eq_blw_sel = 1,
                .eq_boost = 2,
                .spare_in = 3,
                .smp_autoz_d1r = 2,
                .smp_autoz_eg1r = 2,
                .rccr0 = LNaRCCR0_CAL_EN |
                         LNaRCCR0_CAL_DC3_DIS |
                         LNaRCCR0_CAL_DC2_DIS |
                         LNaRCCR0_CAL_DC1_DIS |
                         LNaRCCR0_CAL_DC0_DIS,
                .ttlcr0 = LNaTTLCR0_DATA_IN_SSC |
                          FIELD_PREP_CONST(LNaTTLCR0_CDR_MIN_SMP_ON, 1),
        },
};

static void lynx_28g_lane_set_nrate(struct lynx_28g_lane *lane,
                                    struct lynx_28g_pll *pll,
                                    enum lynx_lane_mode lane_mode)
{
        switch (pll->frate_sel) {
        case PLLnCR1_FRATE_5G_10GVCO:
        case PLLnCR1_FRATE_5G_25GVCO:
                switch (lane_mode) {
                case LANE_MODE_1000BASEX_SGMII:
                        lynx_28g_lane_rmw(lane, LNaTGCR0,
                                          FIELD_PREP(LNaTGCR0_N_RATE, LNaTGCR0_N_RATE_QUARTER),
                                          LNaTGCR0_N_RATE);
                        lynx_28g_lane_rmw(lane, LNaRGCR0,
                                          FIELD_PREP(LNaRGCR0_N_RATE, LNaRGCR0_N_RATE_QUARTER),
                                          LNaRGCR0_N_RATE);
                        break;
                default:
                        break;
                }
                break;
        case PLLnCR1_FRATE_10G_20GVCO:
                switch (lane_mode) {
                case LANE_MODE_10GBASER:
                case LANE_MODE_USXGMII:
                        lynx_28g_lane_rmw(lane, LNaTGCR0,
                                          FIELD_PREP(LNaTGCR0_N_RATE, LNaTGCR0_N_RATE_FULL),
                                          LNaTGCR0_N_RATE);
                        lynx_28g_lane_rmw(lane, LNaRGCR0,
                                          FIELD_PREP(LNaRGCR0_N_RATE, LNaRGCR0_N_RATE_FULL),
                                          LNaRGCR0_N_RATE);
                        break;
                default:
                        break;
                }
                break;
        case PLLnCR1_FRATE_12G_25GVCO:
                switch (lane_mode) {
                case LANE_MODE_25GBASER:
                        lynx_28g_lane_rmw(lane, LNaTGCR0,
                                          FIELD_PREP(LNaTGCR0_N_RATE, LNaTGCR0_N_RATE_DOUBLE),
                                          LNaTGCR0_N_RATE);
                        lynx_28g_lane_rmw(lane, LNaRGCR0,
                                          FIELD_PREP(LNaRGCR0_N_RATE, LNaRGCR0_N_RATE_DOUBLE),
                                          LNaRGCR0_N_RATE);
                        break;
                default:
                        break;
                }
                break;
        default:
                break;
        }
}

static void lynx_28g_lane_set_pll(struct lynx_28g_lane *lane,
                                  struct lynx_28g_pll *pll)
{
        if (pll->id == 0) {
                lynx_28g_lane_rmw(lane, LNaTGCR0,
                                  FIELD_PREP(LNaTGCR0_USE_PLL, LNaTGCR0_USE_PLLF),
                                  LNaTGCR0_USE_PLL);
                lynx_28g_lane_rmw(lane, LNaRGCR0,
                                  FIELD_PREP(LNaRGCR0_USE_PLL, LNaRGCR0_USE_PLLF),
                                  LNaRGCR0_USE_PLL);
        } else {
                lynx_28g_lane_rmw(lane, LNaTGCR0,
                                  FIELD_PREP(LNaTGCR0_USE_PLL, LNaTGCR0_USE_PLLS),
                                  LNaTGCR0_USE_PLL);
                lynx_28g_lane_rmw(lane, LNaRGCR0,
                                  FIELD_PREP(LNaRGCR0_USE_PLL, LNaRGCR0_USE_PLLS),
                                  LNaRGCR0_USE_PLL);
        }
}

static bool lynx_28g_lane_halt_done(struct lynx_28g_lane *lane)
{
        u32 trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL);
        u32 rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);

        return !(trstctl & LNaTRSTCTL_HLT_REQ) &&
               !(rrstctl & LNaRRSTCTL_HLT_REQ);
}

static bool lynx_28g_lane_stop_done(struct lynx_28g_lane *lane)
{
        u32 trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL);
        u32 rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);

        return !(trstctl & LNaTRSTCTL_STP_REQ) &&
               !(rrstctl & LNaRRSTCTL_STP_REQ);
}

static bool lynx_28g_lane_reset_done(struct lynx_28g_lane *lane)
{
        u32 trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL);
        u32 rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);

        return (trstctl & LNaTRSTCTL_RST_DONE) &&
               (rrstctl & LNaRRSTCTL_RST_DONE);
}

/* Halting puts the lane in a mode in which it can be reconfigured */
static int lynx_28g_lane_halt(struct phy *phy)
{
        struct lynx_28g_lane *lane = phy_get_drvdata(phy);
        bool done;
        int err;

        /* Issue a halt request */
        lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_HLT_REQ,
                          LNaTRSTCTL_HLT_REQ);
        lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_HLT_REQ,
                          LNaRRSTCTL_HLT_REQ);

        /* Wait until the halting process is complete */
        err = read_poll_timeout(lynx_28g_lane_halt_done, done, done,
                                LYNX_28G_LANE_HALT_SLEEP_US,
                                LYNX_28G_LANE_HALT_TIMEOUT_US,
                                false, lane);
        if (err) {
                dev_err(&phy->dev, "Lane %c halt failed: %pe\n",
                        'A' + lane->id, ERR_PTR(err));
        }

        return err;
}

static int lynx_28g_lane_reset(struct phy *phy)
{
        struct lynx_28g_lane *lane = phy_get_drvdata(phy);
        bool done;
        int err;

        /* Issue a reset request on the lane */
        lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_RST_REQ,
                          LNaTRSTCTL_RST_REQ);
        lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_RST_REQ,
                          LNaRRSTCTL_RST_REQ);

        /* Wait until the reset sequence is completed */
        err = read_poll_timeout(lynx_28g_lane_reset_done, done, done,
                                LYNX_28G_LANE_RESET_SLEEP_US,
                                LYNX_28G_LANE_RESET_TIMEOUT_US,
                                false, lane);
        if (err) {
                dev_err(&phy->dev, "Lane %c reset failed: %pe\n",
                        'A' + lane->id, ERR_PTR(err));
        }

        return err;
}

static int lynx_28g_power_off(struct phy *phy)
{
        struct lynx_28g_lane *lane = phy_get_drvdata(phy);
        bool done;
        int err;

        if (!lane->powered_up)
                return 0;

        /* Issue a stop request */
        lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_STP_REQ,
                          LNaTRSTCTL_STP_REQ);
        lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_STP_REQ,
                          LNaRRSTCTL_STP_REQ);

        /* Wait until the stop process is complete */
        err = read_poll_timeout(lynx_28g_lane_stop_done, done, done,
                                LYNX_28G_LANE_STOP_SLEEP_US,
                                LYNX_28G_LANE_STOP_TIMEOUT_US,
                                false, lane);
        if (err) {
                dev_err(&phy->dev, "Lane %c stop failed: %pe\n",
                        'A' + lane->id, ERR_PTR(err));
        }

        /* Power down the RX and TX portions of the lane */
        lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_DIS,
                          LNaRRSTCTL_DIS);
        lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_DIS,
                          LNaTRSTCTL_DIS);

        lane->powered_up = false;

        return 0;
}

static int lynx_28g_power_on(struct phy *phy)
{
        struct lynx_28g_lane *lane = phy_get_drvdata(phy);
        int err;

        if (lane->powered_up)
                return 0;

        /* Power up the RX and TX portions of the lane */
        lynx_28g_lane_rmw(lane, LNaRRSTCTL, 0, LNaRRSTCTL_DIS);
        lynx_28g_lane_rmw(lane, LNaTRSTCTL, 0, LNaTRSTCTL_DIS);

        err = lynx_28g_lane_reset(phy);
        if (err)
                return err;

        lane->powered_up = true;

        return 0;
}

static int lynx_28g_e25g_pcvt(int lane)
{
        return 7 - lane;
}

static int lynx_28g_get_pccr(enum lynx_lane_mode lane_mode, int lane,
                             struct lynx_pccr *pccr)
{
        switch (lane_mode) {
        case LANE_MODE_1000BASEX_SGMII:
                pccr->offset = PCC8;
                pccr->width = 4;
                pccr->shift = SGMII_CFG(lane);
                break;
        case LANE_MODE_USXGMII:
        case LANE_MODE_10GBASER:
                pccr->offset = PCCC;
                pccr->width = 4;
                pccr->shift = SXGMII_CFG(lane);
                break;
        case LANE_MODE_25GBASER:
                pccr->offset = PCCD;
                pccr->width = 4;
                pccr->shift = E25G_CFG(lynx_28g_e25g_pcvt(lane));
                break;
        default:
                return -EOPNOTSUPP;
        }

        return 0;
}

static int lynx_28g_get_pcvt_offset(int lane, enum lynx_lane_mode lane_mode)
{
        switch (lane_mode) {
        case LANE_MODE_1000BASEX_SGMII:
                return SGMIIaCR0(lane);
        case LANE_MODE_USXGMII:
        case LANE_MODE_10GBASER:
                return SXGMIIaCR0(lane);
        case LANE_MODE_25GBASER:
                return E25GaCR0(lynx_28g_e25g_pcvt(lane));
        default:
                return -EOPNOTSUPP;
        }
}

static bool lx2160a_serdes1_lane_supports_mode(int lane,
                                               enum lynx_lane_mode mode)
{
        switch (mode) {
        case LANE_MODE_25GBASER:
                return lane != 2 && lane != 3;
        default:
                return true;
        }
}

static bool lx2160a_serdes2_lane_supports_mode(int lane,
                                               enum lynx_lane_mode mode)
{
        switch (mode) {
        case LANE_MODE_1000BASEX_SGMII:
                return true;
        case LANE_MODE_USXGMII:
        case LANE_MODE_10GBASER:
                return lane == 6 || lane == 7;
        default:
                return false;
        }
}

static bool lx2160a_serdes3_lane_supports_mode(int lane,
                                               enum lynx_lane_mode mode)
{
        /*
         * Non-networking SerDes, and this driver supports only
         * networking protocols
         */
        return false;
}

static bool lx2162a_serdes1_lane_supports_mode(int lane,
                                               enum lynx_lane_mode mode)
{
        return true;
}

static bool lx2162a_serdes2_lane_supports_mode(int lane,
                                               enum lynx_lane_mode mode)
{
        return lx2160a_serdes2_lane_supports_mode(lane, mode);
}

/* Feature set is not expected to grow for the deprecated compatible string */
static bool lynx_28g_compat_lane_supports_mode(int lane,
                                               enum lynx_lane_mode mode)
{
        switch (mode) {
        case LANE_MODE_1000BASEX_SGMII:
        case LANE_MODE_USXGMII:
        case LANE_MODE_10GBASER:
                return true;
        default:
                return false;
        }
}

static void lynx_28g_cdr_lock_check(struct lynx_lane *lane)
{
        u32 rrstctl;
        int err;

        rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
        if (!!(rrstctl & LNaRRSTCTL_CDR_LOCK))
                return;

        lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_RST_REQ,
                          LNaRRSTCTL_RST_REQ);

        err = read_poll_timeout(lynx_28g_lane_read, rrstctl,
                                !!(rrstctl & LNaRRSTCTL_RST_DONE),
                                LYNX_28G_LANE_RESET_SLEEP_US,
                                LYNX_28G_LANE_RESET_TIMEOUT_US,
                                false, lane, LNaRRSTCTL);
        if (err) {
                dev_warn_once(&lane->phy->dev,
                              "Lane %c receiver reset failed: %pe\n",
                              'A' + lane->id, ERR_PTR(err));
        }
}

static void lynx_28g_lane_remap_pll(struct lynx_lane *lane,
                                    enum lynx_lane_mode lane_mode)
{
        struct lynx_priv *priv = lane->priv;
        struct lynx_pll *pll;

        /* Switch to the PLL that works with this interface type */
        pll = lynx_pll_get(priv, lane_mode);
        if (unlikely(!pll))
                return;

        lynx_28g_lane_set_pll(lane, pll);

        /* Choose the portion of clock net to be used on this lane */
        lynx_28g_lane_set_nrate(lane, pll, lane_mode);
}

static void lynx_28g_lane_change_proto_conf(struct lynx_28g_lane *lane,
                                            enum lynx_lane_mode lane_mode)
{
        const struct lynx_28g_proto_conf *conf = &lynx_28g_proto_conf[lane_mode];

        lynx_28g_lane_rmw(lane, LNaGCR0,
                          FIELD_PREP(LNaGCR0_PROTO_SEL, conf->proto_sel) |
                          FIELD_PREP(LNaGCR0_IF_WIDTH, conf->if_width),
                          LNaGCR0_PROTO_SEL | LNaGCR0_IF_WIDTH);

        lynx_28g_lane_rmw(lane, LNaTECR0,
                          FIELD_PREP(LNaTECR0_EQ_TYPE, conf->teq_type) |
                          FIELD_PREP(LNaTECR0_EQ_SGN_PREQ, conf->sgn_preq) |
                          FIELD_PREP(LNaTECR0_EQ_PREQ, conf->ratio_preq) |
                          FIELD_PREP(LNaTECR0_EQ_SGN_POST1Q, conf->sgn_post1q) |
                          FIELD_PREP(LNaTECR0_EQ_POST1Q, conf->ratio_post1q) |
                          FIELD_PREP(LNaTECR0_EQ_AMP_RED, conf->amp_red),
                          LNaTECR0_EQ_TYPE |
                          LNaTECR0_EQ_SGN_PREQ |
                          LNaTECR0_EQ_PREQ |
                          LNaTECR0_EQ_SGN_POST1Q |
                          LNaTECR0_EQ_POST1Q |
                          LNaTECR0_EQ_AMP_RED);

        lynx_28g_lane_rmw(lane, LNaTECR1,
                          FIELD_PREP(LNaTECR1_EQ_ADPT_EQ, conf->adpt_eq),
                          LNaTECR1_EQ_ADPT_EQ);

        lynx_28g_lane_rmw(lane, LNaRGCR1,
                          FIELD_PREP(LNaRGCR1_ENTER_IDLE_FLT_SEL, conf->enter_idle_flt_sel) |
                          FIELD_PREP(LNaRGCR1_EXIT_IDLE_FLT_SEL, conf->exit_idle_flt_sel) |
                          FIELD_PREP(LNaRGCR1_DATA_LOST_TH_SEL, conf->data_lost_th_sel),
                          LNaRGCR1_ENTER_IDLE_FLT_SEL |
                          LNaRGCR1_EXIT_IDLE_FLT_SEL |
                          LNaRGCR1_DATA_LOST_TH_SEL);

        lynx_28g_lane_rmw(lane, LNaRECR0,
                          FIELD_PREP(LNaRECR0_EQ_GAINK2_HF_OV_EN, conf->gk2ovd_en) |
                          FIELD_PREP(LNaRECR0_EQ_GAINK3_MF_OV_EN, conf->gk3ovd_en) |
                          FIELD_PREP(LNaRECR0_EQ_GAINK4_LF_OV_EN, conf->gk4ovd_en) |
                          FIELD_PREP(LNaRECR0_EQ_GAINK2_HF_OV, conf->gk2ovd) |
                          FIELD_PREP(LNaRECR0_EQ_GAINK3_MF_OV, conf->gk3ovd) |
                          FIELD_PREP(LNaRECR0_EQ_GAINK4_LF_OV, conf->gk4ovd),
                          LNaRECR0_EQ_GAINK2_HF_OV |
                          LNaRECR0_EQ_GAINK3_MF_OV |
                          LNaRECR0_EQ_GAINK4_LF_OV |
                          LNaRECR0_EQ_GAINK2_HF_OV_EN |
                          LNaRECR0_EQ_GAINK3_MF_OV_EN |
                          LNaRECR0_EQ_GAINK4_LF_OV_EN);

        lynx_28g_lane_rmw(lane, LNaRECR1,
                          FIELD_PREP(LNaRECR1_EQ_OFFSET_OV, conf->eq_offset_ovd) |
                          FIELD_PREP(LNaRECR1_EQ_OFFSET_OV_EN, conf->eq_offset_ovd_en),
                          LNaRECR1_EQ_OFFSET_OV |
                          LNaRECR1_EQ_OFFSET_OV_EN);

        lynx_28g_lane_rmw(lane, LNaRECR2,
                          FIELD_PREP(LNaRECR2_EQ_OFFSET_RNG_DBL, conf->eq_offset_rng_dbl) |
                          FIELD_PREP(LNaRECR2_EQ_BLW_SEL, conf->eq_blw_sel) |
                          FIELD_PREP(LNaRECR2_EQ_BOOST, conf->eq_boost) |
                          FIELD_PREP(LNaRECR2_SPARE_IN, conf->spare_in),
                          LNaRECR2_EQ_OFFSET_RNG_DBL |
                          LNaRECR2_EQ_BLW_SEL |
                          LNaRECR2_EQ_BOOST |
                          LNaRECR2_SPARE_IN);

        lynx_28g_lane_rmw(lane, LNaRSCCR0,
                          FIELD_PREP(LNaRSCCR0_SMP_AUTOZ_D1R, conf->smp_autoz_d1r) |
                          FIELD_PREP(LNaRSCCR0_SMP_AUTOZ_EG1R, conf->smp_autoz_eg1r),
                          LNaRSCCR0_SMP_AUTOZ_D1R |
                          LNaRSCCR0_SMP_AUTOZ_EG1R);

        lynx_28g_lane_write(lane, LNaRCCR0, conf->rccr0);
        lynx_28g_lane_write(lane, LNaTTLCR0, conf->ttlcr0);
}

static int lynx_28g_lane_disable_pcvt(struct lynx_28g_lane *lane,
                                      enum lynx_lane_mode lane_mode)
{
        struct lynx_28g_priv *priv = lane->priv;
        int err;

        spin_lock(&priv->pcc_lock);

        err = lynx_pccr_write(lane, lane_mode, 0);
        if (err)
                goto out;

        switch (lane_mode) {
        case LANE_MODE_1000BASEX_SGMII:
                err = lynx_pcvt_rmw(lane, lane_mode, CR(1), 0,
                                    SGMIIaCR1_SGPCS_EN);
                break;
        default:
                err = 0;
        }

out:
        spin_unlock(&priv->pcc_lock);

        return err;
}

static int lynx_28g_lane_enable_pcvt(struct lynx_28g_lane *lane,
                                     enum lynx_lane_mode lane_mode)
{
        struct lynx_28g_priv *priv = lane->priv;
        u32 val;
        int err;

        spin_lock(&priv->pcc_lock);

        switch (lane_mode) {
        case LANE_MODE_1000BASEX_SGMII:
                err = lynx_pcvt_rmw(lane, lane_mode, CR(1), SGMIIaCR1_SGPCS_EN,
                                    SGMIIaCR1_SGPCS_EN);
                break;
        default:
                err = 0;
        }

        val = 0;

        switch (lane_mode) {
        case LANE_MODE_1000BASEX_SGMII:
                val |= PCC8_SGMIIa_CFG;
                break;
        case LANE_MODE_10GBASER:
                val |= PCCC_SXGMIIn_XFI;
                fallthrough;
        case LANE_MODE_USXGMII:
                val |= PCCC_SXGMIIn_CFG;
                break;
        case LANE_MODE_25GBASER:
                val |= PCCD_E25Gn_CFG;
                break;
        default:
                break;
        }

        err = lynx_pccr_write(lane, lane_mode, val);

        spin_unlock(&priv->pcc_lock);

        return err;
}

static int lynx_28g_validate(struct phy *phy, enum phy_mode mode, int submode,
                             union phy_configure_opts *opts)
{
        return lynx_phy_mode_to_lane_mode(phy, mode, submode, NULL);
}

static int lynx_28g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
{
        struct lynx_lane *lane = phy_get_drvdata(phy);
        int powered_up = lane->powered_up;
        enum lynx_lane_mode lane_mode;
        int err;

        err = lynx_phy_mode_to_lane_mode(phy, mode, submode, &lane_mode);
        if (err)
                return err;

        if (lane_mode == lane->mode)
                return 0;

        /* If the lane is powered up, put the lane into the halt state while
         * the reconfiguration is being done.
         */
        if (powered_up) {
                err = lynx_28g_lane_halt(phy);
                if (err)
                        goto out;
        }

        err = lynx_28g_lane_disable_pcvt(lane, lane->mode);
        if (err)
                goto out;

        lynx_28g_lane_change_proto_conf(lane, lane_mode);
        lynx_28g_lane_remap_pll(lane, lane_mode);
        WARN_ON(lynx_28g_lane_enable_pcvt(lane, lane_mode));

        lane->mode = lane_mode;

out:
        if (powered_up)
                lynx_28g_lane_reset(phy);

        return err;
}

static int lynx_28g_init(struct phy *phy)
{
        struct lynx_28g_lane *lane = phy_get_drvdata(phy);

        /* Mark the fact that the lane was init */
        lane->init = true;

        /* SerDes lanes are powered on at boot time.  Any lane that is managed
         * by this driver will get powered down at init time aka at dpaa2-eth
         * probe time.
         */
        lane->powered_up = true;
        lynx_28g_power_off(phy);

        return 0;
}

static int lynx_28g_exit(struct phy *phy)
{
        struct lynx_28g_lane *lane = phy_get_drvdata(phy);

        /* The lane returns to the state where it isn't managed by the
         * consumer, so we must treat is as if it isn't initialized, and
         * always powered on.
         */
        lane->init = false;
        lane->powered_up = false;

        lynx_28g_power_on(phy);

        return 0;
}

static const struct phy_ops lynx_28g_ops = {
        .init           = lynx_28g_init,
        .exit           = lynx_28g_exit,
        .power_on       = lynx_28g_power_on,
        .power_off      = lynx_28g_power_off,
        .set_mode       = lynx_28g_set_mode,
        .validate       = lynx_28g_validate,
        .owner          = THIS_MODULE,
};

static void lynx_28g_pll_read_configuration(struct lynx_pll *pll)
{
        u32 val;

        val = lynx_pll_read(pll, PLLnRSTCTL);
        pll->enabled = !(val & PLLnRSTCTL_DIS);
        pll->locked = !!(val & PLLnRSTCTL_LOCK);

        val = lynx_pll_read(pll, PLLnCR0);
        pll->refclk_sel = FIELD_GET(PLLnCR0_REFCLK_SEL, val);

        val = lynx_pll_read(pll, PLLnCR1);
        pll->frate_sel = FIELD_GET(PLLnCR1_FRATE_SEL, val);

        if (!pll->enabled)
                return;

        switch (pll->frate_sel) {
        case PLLnCR1_FRATE_5G_10GVCO:
        case PLLnCR1_FRATE_5G_25GVCO:
                /* 5GHz clock net */
                __set_bit(LANE_MODE_1000BASEX_SGMII, pll->supported);
                break;
        case PLLnCR1_FRATE_10G_20GVCO:
                /* 10.3125GHz clock net */
                __set_bit(LANE_MODE_10GBASER, pll->supported);
                __set_bit(LANE_MODE_USXGMII, pll->supported);
                break;
        case PLLnCR1_FRATE_12G_25GVCO:
                /* 12.890625GHz clock net */
                __set_bit(LANE_MODE_25GBASER, pll->supported);
                break;
        default:
                /* 6GHz, 8GHz */
                break;
        }
}

static void lynx_28g_lane_read_configuration(struct lynx_28g_lane *lane)
{
        u32 pccr, pss, protocol;

        pss = lynx_28g_lane_read(lane, LNaPSS);
        protocol = FIELD_GET(LNaPSS_TYPE, pss);
        switch (protocol) {
        case LNaPSS_TYPE_SGMII:
                lane->mode = LANE_MODE_1000BASEX_SGMII;
                break;
        case LNaPSS_TYPE_XFI:
                lynx_pccr_read(lane, LANE_MODE_10GBASER, &pccr);
                if (pccr & PCCC_SXGMIIn_XFI)
                        lane->mode = LANE_MODE_10GBASER;
                else
                        lane->mode = LANE_MODE_USXGMII;
                break;
        case LNaPSS_TYPE_25G:
                lane->mode = LANE_MODE_25GBASER;
                break;
        default:
                lane->mode = LANE_MODE_UNKNOWN;
        }
}

static const struct lynx_info lynx_info_compat = {
        .get_pccr = lynx_28g_get_pccr,
        .get_pcvt_offset = lynx_28g_get_pcvt_offset,
        .lane_supports_mode = lynx_28g_compat_lane_supports_mode,
        .pll_read_configuration = lynx_28g_pll_read_configuration,
        .lane_read_configuration = lynx_28g_lane_read_configuration,
        .cdr_lock_check = lynx_28g_cdr_lock_check,
        .num_lanes = LYNX_28G_NUM_LANE,
};

static const struct lynx_info lynx_info_lx2160a_serdes1 = {
        .get_pccr = lynx_28g_get_pccr,
        .get_pcvt_offset = lynx_28g_get_pcvt_offset,
        .lane_supports_mode = lx2160a_serdes1_lane_supports_mode,
        .pll_read_configuration = lynx_28g_pll_read_configuration,
        .lane_read_configuration = lynx_28g_lane_read_configuration,
        .cdr_lock_check = lynx_28g_cdr_lock_check,
        .num_lanes = LYNX_28G_NUM_LANE,
};

static const struct lynx_info lynx_info_lx2160a_serdes2 = {
        .get_pccr = lynx_28g_get_pccr,
        .get_pcvt_offset = lynx_28g_get_pcvt_offset,
        .lane_supports_mode = lx2160a_serdes2_lane_supports_mode,
        .pll_read_configuration = lynx_28g_pll_read_configuration,
        .lane_read_configuration = lynx_28g_lane_read_configuration,
        .cdr_lock_check = lynx_28g_cdr_lock_check,
        .num_lanes = LYNX_28G_NUM_LANE,
};

static const struct lynx_info lynx_info_lx2160a_serdes3 = {
        .get_pccr = lynx_28g_get_pccr,
        .get_pcvt_offset = lynx_28g_get_pcvt_offset,
        .lane_supports_mode = lx2160a_serdes3_lane_supports_mode,
        .pll_read_configuration = lynx_28g_pll_read_configuration,
        .lane_read_configuration = lynx_28g_lane_read_configuration,
        .cdr_lock_check = lynx_28g_cdr_lock_check,
        .num_lanes = LYNX_28G_NUM_LANE,
};

static const struct lynx_info lynx_info_lx2162a_serdes1 = {
        .get_pccr = lynx_28g_get_pccr,
        .get_pcvt_offset = lynx_28g_get_pcvt_offset,
        .lane_supports_mode = lx2162a_serdes1_lane_supports_mode,
        .pll_read_configuration = lynx_28g_pll_read_configuration,
        .lane_read_configuration = lynx_28g_lane_read_configuration,
        .cdr_lock_check = lynx_28g_cdr_lock_check,
        .first_lane = 4,
        .num_lanes = LYNX_28G_NUM_LANE,
};

static const struct lynx_info lynx_info_lx2162a_serdes2 = {
        .get_pccr = lynx_28g_get_pccr,
        .get_pcvt_offset = lynx_28g_get_pcvt_offset,
        .lane_supports_mode = lx2162a_serdes2_lane_supports_mode,
        .pll_read_configuration = lynx_28g_pll_read_configuration,
        .lane_read_configuration = lynx_28g_lane_read_configuration,
        .cdr_lock_check = lynx_28g_cdr_lock_check,
        .num_lanes = LYNX_28G_NUM_LANE,
};

static int lynx_28g_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        const struct lynx_info *info;

        /*
         * If we get here it means we probed on a device tree where
         * "fsl,lynx-28g" wasn't the fallback, but the sole compatible string.
         */
        info = of_device_get_match_data(dev);
        if (info == &lynx_info_compat)
                dev_warn(dev, "Please update device tree to use per-device compatible strings\n");

        return lynx_probe(pdev, info, &lynx_28g_ops);
}

static const struct of_device_id lynx_28g_of_match_table[] = {
        { .compatible = "fsl,lx2160a-serdes1", .data = &lynx_info_lx2160a_serdes1 },
        { .compatible = "fsl,lx2160a-serdes2", .data = &lynx_info_lx2160a_serdes2 },
        { .compatible = "fsl,lx2160a-serdes3", .data = &lynx_info_lx2160a_serdes3 },
        { .compatible = "fsl,lx2162a-serdes1", .data = &lynx_info_lx2162a_serdes1 },
        { .compatible = "fsl,lx2162a-serdes2", .data = &lynx_info_lx2162a_serdes2 },
        { .compatible = "fsl,lynx-28g", .data = &lynx_info_compat }, /* fallback, keep last */
        { },
};
MODULE_DEVICE_TABLE(of, lynx_28g_of_match_table);

static struct platform_driver lynx_28g_driver = {
        .probe = lynx_28g_probe,
        .remove = lynx_remove,
        .driver = {
                .name = "lynx-28g",
                .of_match_table = lynx_28g_of_match_table,
        },
};
module_platform_driver(lynx_28g_driver);

MODULE_IMPORT_NS("PHY_FSL_LYNX");
MODULE_AUTHOR("Ioana Ciornei <ioana.ciornei@nxp.com>");
MODULE_DESCRIPTION("Lynx 28G SerDes PHY driver for Layerscape SoCs");
MODULE_LICENSE("GPL v2");