mirror of
https://github.com/nxp-imx/linux-imx.git
synced 2025-09-03 02:16:09 +02:00

1000Base-KX lanes need a 312.5 MHz clock for backplane auto-negotiation. The reference manual suggests to generate this frequency from the PLL's FRATE_SEL/16, through the ex_dly_clk mechanism (note: we know that FRATE_SEL must be 5 GHz for 1000Base-KX, as for all other 1G protocols). The ex_dly_clk is used on some SoCs for other stuff as well: for example on LS1028A, it is used to derive the SYS_CLK for the Felix Ethernet switch when that has 1G SGMII or QSGMII lanes. For other SerDes protocols, the switch SYS_CLK is derived from other places. But when it comes from ex_dly_clk, the RCW will automatically set PLLnCR0[DLYDIV_SEL] to 1. To cope with that case, we bake logic into the driver to bump the usage count of ex_dly_clk at driver probe time. This way, it never really drops to zero even after all lanes went to 1000Base-KX and back to something else. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
2451 lines
59 KiB
C
2451 lines
59 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/* Copyright 2021-2023 NXP */
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/fsl/guts.h>
|
|
#include <linux/phy/phy-fsl-lynx.h>
|
|
|
|
#include "phy-fsl-lynx-xgkr-algorithm.h"
|
|
|
|
#define MAX_NUM_LANES 8
|
|
#define NUM_PLL 2
|
|
|
|
/* SoC IP wrapper for protocol converters */
|
|
#define PCCR8 0x220
|
|
#define PCCR8_SGMIIa_KX BIT(3)
|
|
#define PCCR8_SGMIIa_CFG BIT(0)
|
|
|
|
#define PCCR9 0x224
|
|
#define PCCR9_QSGMIIa_CFG BIT(0)
|
|
#define PCCR9_QXGMIIa_CFG BIT(0)
|
|
|
|
#define PCCRB 0x22c
|
|
#define PCCRB_XFIa_CFG BIT(0)
|
|
#define PCCRB_SXGMIIa_CFG BIT(0)
|
|
|
|
#define SGMII_CFG(id) (28 - (id) * 4)
|
|
#define QSGMII_CFG(id) (28 - (id) * 4)
|
|
#define SXGMII_CFG(id) (28 - (id) * 4)
|
|
#define QXGMII_CFG(id) (12 - (id) * 4)
|
|
#define XFI_CFG(id) (28 - (id) * 4)
|
|
|
|
#define CR(x) ((x) * 4)
|
|
|
|
#define A 0
|
|
#define B 1
|
|
#define C 2
|
|
#define D 3
|
|
#define E 4
|
|
#define F 5
|
|
#define G 6
|
|
#define H 7
|
|
|
|
#define SGMIIaCR0(id) (0x1800 + (id) * 0x10)
|
|
#define QSGMIIaCR0(id) (0x1880 + (id) * 0x10)
|
|
#define XAUIaCR0(id) (0x1900 + (id) * 0x10)
|
|
#define XFIaCR0(id) (0x1980 + (id) * 0x10)
|
|
#define SXGMIIaCR0(id) (0x1a80 + (id) * 0x10)
|
|
#define QXGMIIaCR0(id) (0x1b00 + (id) * 0x20)
|
|
|
|
#define SGMIIaCR0_RST_SGM BIT(31)
|
|
#define SGMIIaCR0_RST_SGM_OFF SGMIIaCR0_RST_SGM
|
|
#define SGMIIaCR0_RST_SGM_ON 0
|
|
#define SGMIIaCR0_PD_SGM BIT(30)
|
|
#define SGMIIaCR1_SGPCS_EN BIT(11)
|
|
#define SGMIIaCR1_SGPCS_DIS 0x0
|
|
|
|
#define QSGMIIaCR0_RST_QSGM BIT(31)
|
|
#define QSGMIIaCR0_RST_QSGM_OFF QSGMIIaCR0_RST_QSGM
|
|
#define QSGMIIaCR0_RST_QSGM_ON 0
|
|
#define QSGMIIaCR0_PD_QSGM BIT(30)
|
|
|
|
/* MDEV_PORT is at the same bitfield address for all protocol converters */
|
|
#define MDEV_PORT_MSK GENMASK(31, 27)
|
|
#define MDEV_PORT_X(x) (((x) & MDEV_PORT_MSK) >> 27)
|
|
|
|
/* Per PLL registers */
|
|
#define PLLnCR0(pll) ((pll) * 0x20 + 0x4)
|
|
|
|
#define PLLnCR0_POFF(cr0) (((cr0) & BIT(31)) >> 31)
|
|
|
|
#define PLLnCR0_REFCLK_SEL(cr0) (((cr0) & GENMASK(30, 28)) >> 28)
|
|
#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 PLLnCR0_PLL_LCK(cr0) (((cr0) & BIT(23)) >> 23)
|
|
|
|
#define PLLnCR0_FRATE_SEL(cr0) ((cr0) & GENMASK(19, 16))
|
|
#define PLLnCR0_FRATE_5G 0x0
|
|
#define PLLnCR0_FRATE_5_15625G 0x60000
|
|
#define PLLnCR0_FRATE_4G 0x70000
|
|
#define PLLnCR0_FRATE_3_125G 0x90000
|
|
#define PLLnCR0_FRATE_3G 0xa0000
|
|
|
|
#define PLLnCR0_DLYDIV_SEL(x) ((x) & GENMASK(1, 0))
|
|
#define DLYDIV_SEL_MSK PLLnCR0_DLYDIV_SEL(3)
|
|
#define DLYDIV_SEL_312_5_MHZ PLLnCR0_DLYDIV_SEL(1)
|
|
|
|
/* Per SerDes lane registers */
|
|
|
|
/* Lane a Protocol Select status register */
|
|
#define LNaPSSR0(lane) (0x100 + (lane) * 0x20)
|
|
#define LNaPSSR0_TYPE_X(x) (((x) & GENMASK(30, 26)) >> 26)
|
|
#define LNaPSSR0_IS_QUAD_X(x) (((x) & GENMASK(25, 24)) >> 24)
|
|
#define LNaPSSR0_MAC_X(x) (((x) & GENMASK(19, 16)) >> 16)
|
|
#define LNaPSSR0_PCS_X(x) (((x) & GENMASK(10, 8)) >> 8)
|
|
#define LNaPSSR0_LANE(x) ((x) & GENMASK(2, 0))
|
|
|
|
/* Lane a General Control Register */
|
|
#define LNaGCR0(lane) (0x800 + (lane) * 0x40 + 0x0)
|
|
#define LNaGCR0_RPLL_PLLF BIT(31)
|
|
#define LNaGCR0_RPLL_PLLS 0x0
|
|
#define LNaGCR0_RPLL_MSK BIT(31)
|
|
#define LNaGCR0_RRAT_SEL_MSK GENMASK(29, 28)
|
|
#define LNaGCR0_RRAT_SEL_X(x) (((x) & LNaGCR0_RRAT_SEL_MSK) >> 28)
|
|
#define LNaGCR0_RRAT_SEL(x) (((x) << 28) & LNaGCR0_RRAT_SEL_MSK)
|
|
#define LNaGCR0_TRAT_SEL_MSK GENMASK(25, 24)
|
|
#define LNaGCR0_TRAT_SEL_X(x) (((x) & LNaGCR0_TRAT_SEL_MSK) >> 24)
|
|
#define LNaGCR0_TRAT_SEL(x) (((x) << 24) & LNaGCR0_TRAT_SEL_MSK)
|
|
#define LNaGCR0_TPLL_PLLF BIT(27)
|
|
#define LNaGCR0_TPLL_PLLS 0x0
|
|
#define LNaGCR0_TPLL_MSK BIT(27)
|
|
#define LNaGCR0_RRST_OFF LNaGCR0_RRST
|
|
#define LNaGCR0_TRST_OFF LNaGCR0_TRST
|
|
#define LNaGCR0_RRST_ON 0x0
|
|
#define LNaGCR0_TRST_ON 0x0
|
|
#define LNaGCR0_RRST BIT(22)
|
|
#define LNaGCR0_TRST BIT(21)
|
|
#define LNaGCR0_RX_PD BIT(20)
|
|
#define LNaGCR0_TX_PD BIT(19)
|
|
#define LNaGCR0_IF20BIT_EN BIT(18)
|
|
#define LNaGCR0_PROTS_MSK GENMASK(11, 7)
|
|
#define LNaGCR0_PROTS(x) (((x) << 7) & LNaGCR0_PROTS_MSK)
|
|
|
|
#define LNaGCR1(lane) (0x800 + (lane) * 0x40 + 0x4)
|
|
#define LNaGCR1_RDAT_INV BIT(31)
|
|
#define LNaGCR1_TDAT_INV BIT(30)
|
|
#define LNaGCR1_OPAD_CTL BIT(26)
|
|
#define LNaGCR1_REIDL_TH_MSK GENMASK(22, 20)
|
|
#define LNaGCR1_REIDL_TH(x) (((x) << 20) & LNaGCR1_REIDL_TH_MSK)
|
|
#define LNaGCR1_REIDL_TH_X(x) (((x) & LNaGCR1_REIDL_TH_MSK) >> 20)
|
|
#define LNaGCR1_REIDL_EX_SEL_MSK GENMASK(19, 18)
|
|
#define LNaGCR1_REIDL_EX_SEL(x) (((x) << 18) & LNaGCR1_REIDL_EX_SEL_MSK)
|
|
#define LNaGCR1_REIDL_ET_SEL_MSK GENMASK(17, 16)
|
|
#define LNaGCR1_REIDL_ET_SEL(x) (((x) << 16) & LNaGCR1_REIDL_ET_SEL_MSK)
|
|
#define LNaGCR1_REIDL_EX_MSB BIT(15)
|
|
#define LNaGCR1_REIDL_ET_MSB BIT(14)
|
|
#define LNaGCR1_REQ_CTL_SNP BIT(13)
|
|
#define LNaGCR1_REQ_CDR_SNP BIT(12)
|
|
#define LNaGCR1_TRSTDIR BIT(7)
|
|
#define LNaGCR1_REQ_BIN_SNP BIT(6)
|
|
#define LNaGCR1_ISLEW_RCTL_MSK GENMASK(5, 4)
|
|
#define LNaGCR1_ISLEW_RCTL(x) (((x) << 4) & LNaGCR1_ISLEW_RCTL_MSK)
|
|
#define LNaGCR1_OSLEW_RCTL_MSK GENMASK(1, 0)
|
|
#define LNaGCR1_OSLEW_RCTL(x) ((x) & LNaGCR1_OSLEW_RCTL_MSK)
|
|
|
|
#define LNaRECR0(lane) (0x800 + (lane) * 0x40 + 0x10)
|
|
#define LNaRECR0_RXEQ_BST BIT(28)
|
|
#define LNaRECR0_GK2OVD_MSK GENMASK(27, 24)
|
|
#define LNaRECR0_GK2OVD(x) (((x) << 24) & LNaRECR0_GK2OVD_MSK)
|
|
#define LNaRECR0_GK3OVD_MSK GENMASK(19, 16)
|
|
#define LNaRECR0_GK3OVD(x) (((x) << 16) & LNaRECR0_GK3OVD_MSK)
|
|
#define LNaRECR0_GK2OVD_EN BIT(15)
|
|
#define LNaRECR0_GK3OVD_EN BIT(14)
|
|
#define LNaRECR0_OSETOVD_EN BIT(13)
|
|
#define LNaRECR0_BASE_WAND_MSK GENMASK(11, 10)
|
|
#define LNaRECR0_BASE_WAND(x) (((x) << 10) & LNaRECR0_BASE_WAND_MSK)
|
|
#define LNaRECR0_OSETOVD_MSK ((x) & GENMASK(6, 0))
|
|
#define LNaRECR0_OSETOVD(x) ((x) & LNaRECR0_OSETOVD_MSK)
|
|
|
|
#define LNaRECR1(lane) (0x800 + (lane) * 0x40 + 0x14)
|
|
#define LNaRECR1_GK2STAT_X(x) (((x) & GENMASK(27, 24)) >> 24)
|
|
#define LNaRECR1_GK3STAT_X(x) (((x) & GENMASK(19, 16)) >> 16)
|
|
#define LNaRECR1_OSETSTAT_X(x) (((x) & GENMASK(13, 7)) >> 7)
|
|
#define LNaRECR1_BIN_SNP_DONE BIT(2)
|
|
#define LNaRECR1_CTL_SNP_DONE BIT(1)
|
|
|
|
#define LNaTECR0(lane) (0x800 + (lane) * 0x40 + 0x18)
|
|
#define LNaTECR0_TEQ_TYPE_MSK GENMASK(29, 28)
|
|
#define LNaTECR0_TEQ_TYPE(x) (((x) << 28) & LNaTECR0_TEQ_TYPE_MSK)
|
|
#define LNaTECR0_TEQ_TYPE_X(x) (((x) & LNaTECR0_TEQ_TYPE_MSK) >> 28)
|
|
#define LNaTECR0_SGN_PREQ BIT(26)
|
|
#define LNaTECR0_RATIO_PREQ_MSK GENMASK(25, 22)
|
|
#define LNaTECR0_RATIO_PREQ(x) (((x) << 22) & LNaTECR0_RATIO_PREQ_MSK)
|
|
#define LNaTECR0_RATIO_PREQ_X(x) (((x) & LNaTECR0_RATIO_PREQ_MSK) >> 22)
|
|
#define LNaTECR0_SGN_POST1Q BIT(21)
|
|
#define LNaTECR0_RATIO_PST1Q_MSK GENMASK(20, 16)
|
|
#define LNaTECR0_RATIO_PST1Q(x) (((x) << 16) & LNaTECR0_RATIO_PST1Q_MSK)
|
|
#define LNaTECR0_RATIO_PST1Q_X(x) (((x) & LNaTECR0_RATIO_PST1Q_MSK) >> 16)
|
|
#define LNaTECR0_ADPT_EQ_MSK GENMASK(13, 8)
|
|
#define LNaTECR0_ADPT_EQ(x) (((x) << 8) & LNaTECR0_ADPT_EQ_MSK)
|
|
#define LNaTECR0_ADPT_EQ_X(x) (((x) & LNaTECR0_ADPT_EQ_MSK) >> 8)
|
|
#define LNaTECR0_AMP_RED_MSK GENMASK(5, 0)
|
|
#define LNaTECR0_AMP_RED(x) ((x) & LNaTECR0_AMP_RED_MSK)
|
|
|
|
#define LNaTTLCR0(lane) (0x800 + (lane) * 0x40 + 0x20)
|
|
#define LNaTTLCR1(lane) (0x800 + (lane) * 0x40 + 0x24)
|
|
#define LNaTTLCR2(lane) (0x800 + (lane) * 0x40 + 0x28)
|
|
|
|
#define LNaTCSR0(lane) (0x800 + (lane) * 0x40 + 0x30)
|
|
|
|
#define LNaTCSR1(lane) (0x800 + (lane) * 0x40 + 0x34)
|
|
#define LNaTCSR1_CDR_SEL_MSK GENMASK(18, 16)
|
|
#define LNaTCSR1_CDR_SEL(x) (((x) << 16) & LNaTCSR1_CDR_SEL_MSK)
|
|
#define LNaTCSR1_EQ_SNPBIN_DATA_X(x) (((x) & GENMASK(15, 6)) >> 6)
|
|
#define LNaTCSR1_EQ_SNPBIN_DATA_SGN BIT(8)
|
|
#define LNaTCSR1_CDR_STAT_X(x) (((x) & GENMASK(5, 1)) >> 1)
|
|
|
|
#define LNaTCSR2(lane) (0x800 + (lane) * 0x40 + 0x38)
|
|
#define LNaTCSR3(lane) (0x800 + (lane) * 0x40 + 0x3C)
|
|
#define LNaTCSR3_CDR_LCK BIT(27)
|
|
|
|
#define CDR_SLEEP_US 50
|
|
#define CDR_TIMEOUT_US 500
|
|
|
|
#define SNAPSHOT_SLEEP_US 1
|
|
#define SNAPSHOT_TIMEOUT_US 1000
|
|
|
|
enum lynx_10g_rat_sel {
|
|
RAT_SEL_FULL = 0x0,
|
|
RAT_SEL_HALF = 0x1,
|
|
RAT_SEL_QUARTER = 0x2,
|
|
RAT_SEL_DOUBLE = 0x3,
|
|
};
|
|
|
|
enum lynx_10g_eq_bin_data_type {
|
|
EQ_BIN_DATA_SEL_BIN_1 = 0,
|
|
EQ_BIN_DATA_SEL_BIN_2 = 1,
|
|
EQ_BIN_DATA_SEL_BIN_3 = 2,
|
|
EQ_BIN_DATA_SEL_OFFSET = 3,
|
|
EQ_BIN_DATA_SEL_BIN_BLW = 4,
|
|
EQ_BIN_DATA_SEL_BIN_DATA_AVG = 5,
|
|
EQ_BIN_DATA_SEL_BIN_M1 = 6,
|
|
EQ_BIN_DATA_SEL_BIN_LONG = 7,
|
|
};
|
|
|
|
enum lynx_10g_eq_type {
|
|
EQ_TYPE_NO_EQ = 0,
|
|
EQ_TYPE_2TAP = 1,
|
|
EQ_TYPE_3TAP = 2,
|
|
};
|
|
|
|
enum lynx_10g_proto_sel {
|
|
PROTO_SEL_PCIE = 0,
|
|
PROTO_SEL_SGMII_BASEX_KX_QSGMII = 1,
|
|
PROTO_SEL_SATA = 2,
|
|
PROTO_SEL_XAUI = 4,
|
|
PROTO_SEL_XFI_10GBASER_KR_SXGMII = 0xa,
|
|
};
|
|
|
|
struct lynx_10g_proto_conf {
|
|
int proto_sel;
|
|
int if20bit_en;
|
|
int reidl_th;
|
|
int reidl_et_msb;
|
|
int reidl_et_sel;
|
|
int reidl_ex_msb;
|
|
int reidl_ex_sel;
|
|
int islew_rctl;
|
|
int oslew_rctl;
|
|
int rxeq_bst;
|
|
int gk2ovd;
|
|
int gk3ovd;
|
|
int gk2ovd_en;
|
|
int gk3ovd_en;
|
|
int base_wand;
|
|
int teq_type;
|
|
int sgn_preq;
|
|
int ratio_preq;
|
|
int sgn_post1q;
|
|
int ratio_post1q;
|
|
int adpt_eq;
|
|
int amp_red;
|
|
int ttlcr0;
|
|
};
|
|
|
|
struct lynx_pccr {
|
|
int offset;
|
|
int width;
|
|
int shift;
|
|
};
|
|
|
|
static const struct lynx_10g_proto_conf lynx_10g_proto_conf[LANE_MODE_MAX] = {
|
|
[LANE_MODE_1000BASEX_SGMII] = {
|
|
.proto_sel = PROTO_SEL_SGMII_BASEX_KX_QSGMII,
|
|
.if20bit_en = 0,
|
|
.reidl_th = 1,
|
|
.reidl_ex_msb = 0,
|
|
.reidl_ex_sel = 3,
|
|
.reidl_et_msb = 1,
|
|
.reidl_et_sel = 0,
|
|
.islew_rctl = 1,
|
|
.oslew_rctl = 1,
|
|
.rxeq_bst = 0,
|
|
.gk2ovd = 15,
|
|
.gk3ovd = 15,
|
|
.gk2ovd_en = 1,
|
|
.gk3ovd_en = 1,
|
|
.base_wand = 0,
|
|
.teq_type = EQ_TYPE_NO_EQ,
|
|
.sgn_preq = 0,
|
|
.ratio_preq = 0,
|
|
.sgn_post1q = 0,
|
|
.ratio_post1q = 0,
|
|
.adpt_eq = 48,
|
|
.amp_red = 6,
|
|
.ttlcr0 = 0x39000400,
|
|
},
|
|
[LANE_MODE_1000BASEKX] = {
|
|
.proto_sel = PROTO_SEL_SGMII_BASEX_KX_QSGMII,
|
|
.if20bit_en = 0,
|
|
.reidl_th = 0,
|
|
.reidl_ex_msb = 0,
|
|
.reidl_ex_sel = 0,
|
|
.reidl_et_msb = 0,
|
|
.reidl_et_sel = 0,
|
|
.islew_rctl = 1,
|
|
.oslew_rctl = 1,
|
|
.rxeq_bst = 0,
|
|
.gk2ovd = 15,
|
|
.gk3ovd = 15,
|
|
.gk2ovd_en = 1,
|
|
.gk3ovd_en = 1,
|
|
.base_wand = 0,
|
|
.teq_type = EQ_TYPE_NO_EQ,
|
|
.sgn_preq = 0,
|
|
.ratio_preq = 0,
|
|
.sgn_post1q = 0,
|
|
.ratio_post1q = 0,
|
|
.adpt_eq = 48,
|
|
.amp_red = 0,
|
|
.ttlcr0 = 0x39000400,
|
|
},
|
|
[LANE_MODE_2500BASEX] = {
|
|
.proto_sel = PROTO_SEL_SGMII_BASEX_KX_QSGMII,
|
|
.if20bit_en = 0,
|
|
.reidl_th = 0,
|
|
.reidl_ex_msb = 0,
|
|
.reidl_ex_sel = 0,
|
|
.reidl_et_msb = 0,
|
|
.reidl_et_sel = 0,
|
|
.islew_rctl = 2,
|
|
.oslew_rctl = 2,
|
|
.rxeq_bst = 0,
|
|
.gk2ovd = 0,
|
|
.gk3ovd = 0,
|
|
.gk2ovd_en = 0,
|
|
.gk3ovd_en = 0,
|
|
.base_wand = 0,
|
|
.teq_type = EQ_TYPE_2TAP,
|
|
.sgn_preq = 0,
|
|
.ratio_preq = 0,
|
|
.sgn_post1q = 1,
|
|
.ratio_post1q = 6,
|
|
.adpt_eq = 48,
|
|
.amp_red = 0,
|
|
.ttlcr0 = 0x00000400,
|
|
},
|
|
[LANE_MODE_QSGMII] = {
|
|
.proto_sel = PROTO_SEL_SGMII_BASEX_KX_QSGMII,
|
|
.if20bit_en = 0,
|
|
.reidl_th = 0,
|
|
.reidl_ex_msb = 0,
|
|
.reidl_ex_sel = 0,
|
|
.reidl_et_msb = 0,
|
|
.reidl_et_sel = 0,
|
|
.islew_rctl = 1,
|
|
.oslew_rctl = 1,
|
|
.rxeq_bst = 0,
|
|
.gk2ovd = 0,
|
|
.gk3ovd = 0,
|
|
.gk2ovd_en = 0,
|
|
.gk3ovd_en = 0,
|
|
.base_wand = 0,
|
|
.teq_type = EQ_TYPE_2TAP,
|
|
.sgn_preq = 0,
|
|
.ratio_preq = 0,
|
|
.sgn_post1q = 1,
|
|
.ratio_post1q = 6,
|
|
.adpt_eq = 48,
|
|
.amp_red = 2,
|
|
.ttlcr0 = 0x00000400,
|
|
},
|
|
[LANE_MODE_10G_QXGMII] = {
|
|
.proto_sel = PROTO_SEL_XFI_10GBASER_KR_SXGMII,
|
|
.if20bit_en = 1,
|
|
.reidl_th = 0,
|
|
.reidl_ex_msb = 0,
|
|
.reidl_ex_sel = 0,
|
|
.reidl_et_msb = 0,
|
|
.reidl_et_sel = 0,
|
|
.islew_rctl = 1,
|
|
.oslew_rctl = 1,
|
|
.rxeq_bst = 0,
|
|
.gk2ovd = 0,
|
|
.gk3ovd = 0,
|
|
.gk2ovd_en = 0,
|
|
.gk3ovd_en = 0,
|
|
.base_wand = 1,
|
|
.teq_type = EQ_TYPE_NO_EQ,
|
|
.sgn_preq = 0,
|
|
.ratio_preq = 0,
|
|
.sgn_post1q = 0,
|
|
.ratio_post1q = 0,
|
|
.adpt_eq = 48,
|
|
.amp_red = 0,
|
|
.ttlcr0 = 0x00000400,
|
|
},
|
|
[LANE_MODE_USXGMII] = {
|
|
.proto_sel = PROTO_SEL_XFI_10GBASER_KR_SXGMII,
|
|
.if20bit_en = 1,
|
|
.reidl_th = 0,
|
|
.reidl_ex_msb = 0,
|
|
.reidl_ex_sel = 0,
|
|
.reidl_et_msb = 0,
|
|
.reidl_et_sel = 0,
|
|
.islew_rctl = 1,
|
|
.oslew_rctl = 1,
|
|
.rxeq_bst = 0,
|
|
.gk2ovd = 0,
|
|
.gk3ovd = 0,
|
|
.gk2ovd_en = 0,
|
|
.gk3ovd_en = 0,
|
|
.base_wand = 1,
|
|
.teq_type = EQ_TYPE_NO_EQ,
|
|
.sgn_preq = 0,
|
|
.ratio_preq = 0,
|
|
.sgn_post1q = 1,
|
|
.ratio_post1q = 0,
|
|
.adpt_eq = 48,
|
|
.amp_red = 0,
|
|
.ttlcr0 = 0x00000400,
|
|
},
|
|
[LANE_MODE_10GBASER] = {
|
|
.proto_sel = PROTO_SEL_XFI_10GBASER_KR_SXGMII,
|
|
.if20bit_en = 1,
|
|
.reidl_th = 0,
|
|
.reidl_ex_msb = 0,
|
|
.reidl_ex_sel = 0,
|
|
.reidl_et_msb = 0,
|
|
.reidl_et_sel = 0,
|
|
.islew_rctl = 2,
|
|
.oslew_rctl = 2,
|
|
.rxeq_bst = 1,
|
|
.gk2ovd = 0,
|
|
.gk3ovd = 0,
|
|
.gk2ovd_en = 0,
|
|
.gk3ovd_en = 0,
|
|
.base_wand = 1,
|
|
.teq_type = EQ_TYPE_2TAP,
|
|
.sgn_preq = 0,
|
|
.ratio_preq = 0,
|
|
.sgn_post1q = 1,
|
|
.ratio_post1q = 3,
|
|
.adpt_eq = 48,
|
|
.amp_red = 7,
|
|
.ttlcr0 = 0x00000400,
|
|
},
|
|
[LANE_MODE_10GBASEKR] = {
|
|
.proto_sel = PROTO_SEL_XFI_10GBASER_KR_SXGMII,
|
|
.if20bit_en = 1,
|
|
.reidl_th = 0,
|
|
.reidl_ex_msb = 0,
|
|
.reidl_ex_sel = 0,
|
|
.reidl_et_msb = 0,
|
|
.reidl_et_sel = 0,
|
|
.islew_rctl = 2,
|
|
.oslew_rctl = 2,
|
|
.rxeq_bst = 1,
|
|
.gk2ovd = 0,
|
|
.gk3ovd = 0,
|
|
.gk2ovd_en = 0,
|
|
.gk3ovd_en = 0,
|
|
.base_wand = 1,
|
|
.teq_type = EQ_TYPE_3TAP,
|
|
.sgn_preq = 1,
|
|
.ratio_preq = 2,
|
|
.sgn_post1q = 1,
|
|
.ratio_post1q = 5,
|
|
.adpt_eq = 41,
|
|
.amp_red = 7,
|
|
.ttlcr0 = 0x00000400,
|
|
},
|
|
};
|
|
|
|
struct lynx_10g_priv;
|
|
|
|
struct lynx_10g_pll {
|
|
struct lynx_10g_priv *priv;
|
|
u32 cr0;
|
|
int id;
|
|
int ex_dly_clk_use_count;
|
|
DECLARE_BITMAP(supported, LANE_MODE_MAX);
|
|
/*
|
|
* There are fewer PLLs than lanes. This serializes calls to
|
|
* lynx_10g_pll_get_ex_dly_clk() and lynx_10g_pll_put_ex_dly_clk().
|
|
*/
|
|
spinlock_t lock;
|
|
};
|
|
|
|
struct lynx_10g_lane {
|
|
struct lynx_10g_priv *priv;
|
|
struct phy *phy;
|
|
bool powered_up;
|
|
bool init;
|
|
unsigned id;
|
|
enum lynx_lane_mode mode;
|
|
struct lynx_xgkr_algorithm *algorithm;
|
|
u32 default_pccr[LANE_MODE_MAX];
|
|
};
|
|
|
|
struct lynx_info {
|
|
int (*get_pccr)(enum lynx_lane_mode lane_mode, int lane,
|
|
struct lynx_pccr *pccr);
|
|
int (*get_pcvt_offset)(int lane, enum lynx_lane_mode mode);
|
|
bool (*lane_supports_mode)(int lane, enum lynx_lane_mode mode);
|
|
int num_lanes;
|
|
bool has_hardcoded_usxgmii;
|
|
int index;
|
|
};
|
|
|
|
struct lynx_10g_priv {
|
|
void __iomem *base;
|
|
struct device *dev;
|
|
const struct lynx_info *info;
|
|
/* Serialize concurrent access to registers shared between lanes,
|
|
* like PCCn
|
|
*/
|
|
spinlock_t pccr_lock;
|
|
|
|
bool big_endian;
|
|
struct lynx_10g_pll pll[NUM_PLL];
|
|
struct lynx_10g_lane lane[MAX_NUM_LANES];
|
|
int num_lanes;
|
|
|
|
struct delayed_work cdr_check;
|
|
};
|
|
|
|
static const int lynx_10g_bin_type_to_bin_sel[] = {
|
|
[BIN_1] = EQ_BIN_DATA_SEL_BIN_1,
|
|
[BIN_2] = EQ_BIN_DATA_SEL_BIN_2,
|
|
[BIN_3] = EQ_BIN_DATA_SEL_BIN_3,
|
|
[BIN_4] = -EOPNOTSUPP,
|
|
[BIN_OFFSET] = EQ_BIN_DATA_SEL_OFFSET,
|
|
[BIN_BLW] = -EOPNOTSUPP,
|
|
[BIN_DATA_AVG] = -EOPNOTSUPP,
|
|
[BIN_M1] = EQ_BIN_DATA_SEL_BIN_M1,
|
|
[BIN_LONG] = EQ_BIN_DATA_SEL_BIN_LONG,
|
|
};
|
|
|
|
static u32 lynx_10g_read32(struct lynx_10g_priv *priv, unsigned long off)
|
|
{
|
|
void __iomem *reg = priv->base + off;
|
|
|
|
if (priv->big_endian)
|
|
return ioread32be(reg);
|
|
|
|
return ioread32(reg);
|
|
}
|
|
|
|
static void lynx_10g_write32(struct lynx_10g_priv *priv, unsigned long off,
|
|
u32 val)
|
|
{
|
|
void __iomem *reg = priv->base + off;
|
|
|
|
if (priv->big_endian)
|
|
return iowrite32be(val, reg);
|
|
|
|
return iowrite32(val, reg);
|
|
}
|
|
|
|
static void lynx_10g_rmw(struct lynx_10g_priv *priv, unsigned long off,
|
|
u32 val, u32 mask)
|
|
{
|
|
u32 orig, tmp;
|
|
|
|
orig = lynx_10g_read32(priv, off);
|
|
tmp = orig & ~mask;
|
|
tmp |= val;
|
|
lynx_10g_write32(priv, off, tmp);
|
|
}
|
|
|
|
#define lynx_10g_lane_rmw(lane, reg, val, mask) \
|
|
lynx_10g_rmw((lane)->priv, reg(lane->id), val, mask)
|
|
|
|
#define lynx_10g_lane_read(lane, reg) \
|
|
lynx_10g_read32((lane)->priv, reg((lane)->id))
|
|
|
|
#define lynx_10g_lane_write(lane, reg, val) \
|
|
lynx_10g_write32((lane)->priv, reg((lane)->id), val)
|
|
|
|
#define lynx_10g_pll_read(pll, reg) \
|
|
lynx_10g_read32((pll)->priv, reg((pll)->id))
|
|
|
|
#define lynx_10g_pll_write(pll, reg, val) \
|
|
lynx_10g_write32((pll)->priv, reg((pll)->id), val)
|
|
|
|
static int ls1028a_get_pccr(enum lynx_lane_mode lane_mode, int lane,
|
|
struct lynx_pccr *pccr)
|
|
{
|
|
switch (lane_mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
pccr->offset = PCCR8;
|
|
pccr->width = 4;
|
|
pccr->shift = SGMII_CFG(lane);
|
|
break;
|
|
case LANE_MODE_QSGMII:
|
|
if (lane != 1)
|
|
return -EOPNOTSUPP;
|
|
|
|
pccr->offset = PCCR9;
|
|
pccr->width = 3;
|
|
pccr->shift = QSGMII_CFG(A);
|
|
break;
|
|
case LANE_MODE_10G_QXGMII:
|
|
if (lane != 1)
|
|
return -EOPNOTSUPP;
|
|
|
|
pccr->offset = PCCR9;
|
|
pccr->width = 3;
|
|
pccr->shift = QXGMII_CFG(A);
|
|
break;
|
|
case LANE_MODE_USXGMII:
|
|
if (lane != 0)
|
|
return -EOPNOTSUPP;
|
|
|
|
pccr->offset = PCCRB;
|
|
pccr->width = 3;
|
|
pccr->shift = SXGMII_CFG(A);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ls1028a_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
|
|
{
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
return SGMIIaCR0(lane);
|
|
case LANE_MODE_QSGMII:
|
|
return lane == 1 ? QSGMIIaCR0(A) : -EOPNOTSUPP;
|
|
case LANE_MODE_USXGMII:
|
|
return lane == 0 ? SXGMIIaCR0(A) : -EOPNOTSUPP;
|
|
case LANE_MODE_10G_QXGMII:
|
|
return lane == 1 ? QXGMIIaCR0(A) : -EOPNOTSUPP;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
static bool ls1028a_lane_supports_mode(int lane, enum lynx_lane_mode mode)
|
|
{
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
return true;
|
|
case LANE_MODE_QSGMII:
|
|
return lane == 1;
|
|
case LANE_MODE_USXGMII:
|
|
return lane == 0;
|
|
case LANE_MODE_10G_QXGMII:
|
|
return lane == 1;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static const struct lynx_info lynx_info_ls1028a = {
|
|
.get_pccr = ls1028a_get_pccr,
|
|
.get_pcvt_offset = ls1028a_get_pcvt_offset,
|
|
.lane_supports_mode = ls1028a_lane_supports_mode,
|
|
.num_lanes = 4,
|
|
.has_hardcoded_usxgmii = true,
|
|
.index = 1,
|
|
};
|
|
|
|
static int ls1046a_serdes1_get_pccr(enum lynx_lane_mode lane_mode, int lane,
|
|
struct lynx_pccr *pccr)
|
|
{
|
|
switch (lane_mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
pccr->offset = PCCR8;
|
|
pccr->width = 4;
|
|
pccr->shift = SGMII_CFG(lane);
|
|
break;
|
|
case LANE_MODE_QSGMII:
|
|
if (lane != 1)
|
|
return -EOPNOTSUPP;
|
|
|
|
pccr->offset = PCCR9;
|
|
pccr->width = 3;
|
|
pccr->shift = QSGMII_CFG(B);
|
|
break;
|
|
case LANE_MODE_10GBASER:
|
|
case LANE_MODE_10GBASEKR:
|
|
switch (lane) {
|
|
case 2:
|
|
pccr->shift = XFI_CFG(A);
|
|
break;
|
|
case 3:
|
|
pccr->shift = XFI_CFG(B);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
pccr->offset = PCCRB;
|
|
pccr->width = 3;
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ls1046a_serdes1_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
|
|
{
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
return SGMIIaCR0(lane);
|
|
case LANE_MODE_QSGMII:
|
|
if (lane != 1)
|
|
return -EOPNOTSUPP;
|
|
|
|
return QSGMIIaCR0(B);
|
|
case LANE_MODE_10GBASER:
|
|
case LANE_MODE_10GBASEKR:
|
|
switch (lane) {
|
|
case 2:
|
|
return XFIaCR0(A);
|
|
case 3:
|
|
return XFIaCR0(B);
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
static bool ls1046a_serdes1_lane_supports_mode(int lane,
|
|
enum lynx_lane_mode mode)
|
|
{
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
return true;
|
|
case LANE_MODE_QSGMII:
|
|
return lane == 1;
|
|
case LANE_MODE_10GBASER:
|
|
case LANE_MODE_10GBASEKR:
|
|
return lane == 2 || lane == 3;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static const struct lynx_info lynx_info_ls1046a_serdes1 = {
|
|
.get_pccr = ls1046a_serdes1_get_pccr,
|
|
.get_pcvt_offset = ls1046a_serdes1_get_pcvt_offset,
|
|
.lane_supports_mode = ls1046a_serdes1_lane_supports_mode,
|
|
.num_lanes = 4,
|
|
.index = 1,
|
|
};
|
|
|
|
static int ls1046a_serdes2_get_pccr(enum lynx_lane_mode lane_mode, int lane,
|
|
struct lynx_pccr *pccr)
|
|
{
|
|
switch (lane_mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
if (lane != 1)
|
|
return -EOPNOTSUPP;
|
|
|
|
pccr->offset = PCCR8;
|
|
pccr->width = 4;
|
|
pccr->shift = SGMII_CFG(B);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ls1046a_serdes2_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
|
|
{
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
if (lane != 1)
|
|
return -EOPNOTSUPP;
|
|
|
|
return SGMIIaCR0(B);
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
static bool ls1046a_serdes2_lane_supports_mode(int lane,
|
|
enum lynx_lane_mode mode)
|
|
{
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
return lane == 1;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static const struct lynx_info lynx_info_ls1046a_serdes2 = {
|
|
.get_pccr = ls1046a_serdes2_get_pccr,
|
|
.get_pcvt_offset = ls1046a_serdes2_get_pcvt_offset,
|
|
.lane_supports_mode = ls1046a_serdes2_lane_supports_mode,
|
|
.num_lanes = 4,
|
|
.index = 2,
|
|
};
|
|
|
|
static int ls1088a_serdes1_get_pccr(enum lynx_lane_mode lane_mode, int lane,
|
|
struct lynx_pccr *pccr)
|
|
{
|
|
switch (lane_mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
pccr->offset = PCCR8;
|
|
pccr->width = 4;
|
|
pccr->shift = SGMII_CFG(lane);
|
|
break;
|
|
case LANE_MODE_QSGMII:
|
|
switch (lane) {
|
|
case 0:
|
|
pccr->shift = QSGMII_CFG(A);
|
|
break;
|
|
case 1:
|
|
case 3:
|
|
pccr->shift = QSGMII_CFG(B);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
pccr->offset = PCCR9;
|
|
pccr->width = 3;
|
|
break;
|
|
case LANE_MODE_10GBASER:
|
|
case LANE_MODE_10GBASEKR:
|
|
switch (lane) {
|
|
case 2:
|
|
pccr->shift = XFI_CFG(A);
|
|
break;
|
|
case 3:
|
|
pccr->shift = XFI_CFG(B);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
pccr->offset = PCCRB;
|
|
pccr->width = 3;
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ls1088a_serdes1_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
|
|
{
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
return SGMIIaCR0(lane);
|
|
case LANE_MODE_QSGMII:
|
|
switch (lane) {
|
|
case 0:
|
|
return QSGMIIaCR0(A);
|
|
case 1:
|
|
case 3:
|
|
return QSGMIIaCR0(B);
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
case LANE_MODE_10GBASER:
|
|
case LANE_MODE_10GBASEKR:
|
|
switch (lane) {
|
|
case 2:
|
|
return XFIaCR0(A);
|
|
case 3:
|
|
return XFIaCR0(B);
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
static bool ls1088a_serdes1_lane_supports_mode(int lane, enum lynx_lane_mode mode)
|
|
{
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
return true;
|
|
case LANE_MODE_QSGMII:
|
|
switch (lane) {
|
|
case 0:
|
|
case 1:
|
|
case 3:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
case LANE_MODE_10GBASER:
|
|
case LANE_MODE_10GBASEKR:
|
|
switch (lane) {
|
|
case 2:
|
|
case 3:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static const struct lynx_info lynx_info_ls1088a_serdes1 = {
|
|
.get_pccr = ls1088a_serdes1_get_pccr,
|
|
.get_pcvt_offset = ls1088a_serdes1_get_pcvt_offset,
|
|
.lane_supports_mode = ls1088a_serdes1_lane_supports_mode,
|
|
.num_lanes = 4,
|
|
.index = 1,
|
|
};
|
|
|
|
static int ls2088a_serdes1_get_pccr(enum lynx_lane_mode lane_mode, int lane,
|
|
struct lynx_pccr *pccr)
|
|
{
|
|
switch (lane_mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
pccr->offset = PCCR8;
|
|
pccr->width = 4;
|
|
pccr->shift = SGMII_CFG(lane);
|
|
break;
|
|
case LANE_MODE_QSGMII:
|
|
switch (lane) {
|
|
case 2:
|
|
case 6:
|
|
pccr->shift = QSGMII_CFG(A);
|
|
break;
|
|
case 7:
|
|
pccr->shift = QSGMII_CFG(B);
|
|
break;
|
|
case 0:
|
|
case 4:
|
|
pccr->shift = QSGMII_CFG(C);
|
|
break;
|
|
case 1:
|
|
case 5:
|
|
pccr->shift = QSGMII_CFG(D);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
pccr->offset = PCCR9;
|
|
pccr->width = 3;
|
|
break;
|
|
case LANE_MODE_10GBASER:
|
|
case LANE_MODE_10GBASEKR:
|
|
pccr->offset = PCCRB;
|
|
pccr->width = 3;
|
|
pccr->shift = XFI_CFG(lane);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ls2088a_serdes1_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
|
|
{
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
return SGMIIaCR0(lane);
|
|
case LANE_MODE_QSGMII:
|
|
switch (lane) {
|
|
case 2:
|
|
case 6:
|
|
return QSGMIIaCR0(A);
|
|
case 7:
|
|
return QSGMIIaCR0(B);
|
|
case 0:
|
|
case 4:
|
|
return QSGMIIaCR0(C);
|
|
case 1:
|
|
case 5:
|
|
return QSGMIIaCR0(D);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
case LANE_MODE_10GBASER:
|
|
case LANE_MODE_10GBASEKR:
|
|
return XFIaCR0(lane);
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
static bool ls2088a_serdes1_lane_supports_mode(int lane, enum lynx_lane_mode mode)
|
|
{
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
return true;
|
|
case LANE_MODE_QSGMII:
|
|
switch (lane) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
case LANE_MODE_10GBASER:
|
|
case LANE_MODE_10GBASEKR:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static const struct lynx_info lynx_info_ls2088a_serdes1 = {
|
|
.get_pccr = ls2088a_serdes1_get_pccr,
|
|
.get_pcvt_offset = ls2088a_serdes1_get_pcvt_offset,
|
|
.lane_supports_mode = ls2088a_serdes1_lane_supports_mode,
|
|
.num_lanes = 8,
|
|
.index = 1,
|
|
};
|
|
|
|
static int ls2088a_serdes2_get_pccr(enum lynx_lane_mode lane_mode, int lane,
|
|
struct lynx_pccr *pccr)
|
|
{
|
|
switch (lane_mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
pccr->offset = PCCR8;
|
|
pccr->width = 4;
|
|
pccr->shift = SGMII_CFG(lane);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ls2088a_serdes2_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
|
|
{
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
return SGMIIaCR0(lane);
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
static bool ls2088a_serdes2_lane_supports_mode(int lane, enum lynx_lane_mode mode)
|
|
{
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static const struct lynx_info lynx_info_ls2088a_serdes2 = {
|
|
.get_pccr = ls2088a_serdes2_get_pccr,
|
|
.get_pcvt_offset = ls2088a_serdes2_get_pcvt_offset,
|
|
.lane_supports_mode = ls2088a_serdes2_lane_supports_mode,
|
|
.num_lanes = 8,
|
|
.index = 2,
|
|
};
|
|
|
|
static int lynx_pccr_read(struct lynx_10g_lane *lane, enum lynx_lane_mode mode,
|
|
u32 *val)
|
|
{
|
|
struct lynx_10g_priv *priv = lane->priv;
|
|
struct lynx_pccr pccr;
|
|
u32 tmp;
|
|
int err;
|
|
|
|
err = priv->info->get_pccr(mode, lane->id, &pccr);
|
|
if (err)
|
|
return err;
|
|
|
|
tmp = lynx_10g_read32(priv, pccr.offset);
|
|
*val = (tmp >> pccr.shift) & GENMASK(pccr.width - 1, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lynx_pccr_write(struct lynx_10g_lane *lane,
|
|
enum lynx_lane_mode mode, u32 val)
|
|
{
|
|
struct lynx_10g_priv *priv = lane->priv;
|
|
struct lynx_pccr pccr;
|
|
u32 old, tmp, mask;
|
|
int err;
|
|
|
|
err = priv->info->get_pccr(mode, lane->id, &pccr);
|
|
if (err)
|
|
return err;
|
|
|
|
old = lynx_10g_read32(priv, pccr.offset);
|
|
mask = GENMASK(pccr.width - 1, 0) << pccr.shift;
|
|
tmp = (old & ~mask) | (val << pccr.shift);
|
|
lynx_10g_write32(priv, pccr.offset, tmp);
|
|
|
|
dev_dbg(&lane->phy->dev, "PCCR@0x%x: 0x%x -> 0x%x\n",
|
|
pccr.offset, old, tmp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lynx_pcvt_read(struct lynx_10g_lane *lane, enum lynx_lane_mode mode,
|
|
int cr, u32 *val)
|
|
{
|
|
struct lynx_10g_priv *priv = lane->priv;
|
|
int offset;
|
|
|
|
offset = priv->info->get_pcvt_offset(lane->id, mode);
|
|
if (offset < 0)
|
|
return offset;
|
|
|
|
*val = lynx_10g_read32(priv, offset + cr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lynx_pcvt_write(struct lynx_10g_lane *lane, enum lynx_lane_mode mode,
|
|
int cr, u32 val)
|
|
{
|
|
struct lynx_10g_priv *priv = lane->priv;
|
|
int offset;
|
|
|
|
offset = priv->info->get_pcvt_offset(lane->id, mode);
|
|
if (offset < 0)
|
|
return offset;
|
|
|
|
lynx_10g_write32(priv, offset + cr, val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lynx_pcvt_rmw(struct lynx_10g_lane *lane, enum lynx_lane_mode mode,
|
|
int cr, u32 val, u32 mask)
|
|
{
|
|
int err;
|
|
u32 tmp;
|
|
|
|
err = lynx_pcvt_read(lane, mode, cr, &tmp);
|
|
if (err)
|
|
return err;
|
|
|
|
tmp &= ~mask;
|
|
tmp |= val;
|
|
|
|
return lynx_pcvt_write(lane, mode, cr, tmp);
|
|
}
|
|
|
|
static enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf)
|
|
{
|
|
switch (intf) {
|
|
case PHY_INTERFACE_MODE_SGMII:
|
|
case PHY_INTERFACE_MODE_1000BASEX:
|
|
return LANE_MODE_1000BASEX_SGMII;
|
|
case PHY_INTERFACE_MODE_2500BASEX:
|
|
return LANE_MODE_2500BASEX;
|
|
case PHY_INTERFACE_MODE_10GBASER:
|
|
return LANE_MODE_10GBASER;
|
|
case PHY_INTERFACE_MODE_USXGMII:
|
|
return LANE_MODE_USXGMII;
|
|
case PHY_INTERFACE_MODE_10G_QXGMII:
|
|
return LANE_MODE_10G_QXGMII;
|
|
case PHY_INTERFACE_MODE_QSGMII:
|
|
return LANE_MODE_QSGMII;
|
|
default:
|
|
return LANE_MODE_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
static enum lynx_lane_mode
|
|
link_mode_to_lane_mode(enum ethtool_link_mode_bit_indices link_mode)
|
|
{
|
|
switch (link_mode) {
|
|
case ETHTOOL_LINK_MODE_1000baseKX_Full_BIT:
|
|
return LANE_MODE_1000BASEKX;
|
|
case ETHTOOL_LINK_MODE_10000baseKR_Full_BIT:
|
|
return LANE_MODE_10GBASEKR;
|
|
default:
|
|
return LANE_MODE_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
static bool lynx_lane_supports_mode(struct lynx_10g_lane *lane,
|
|
enum lynx_lane_mode mode)
|
|
{
|
|
struct lynx_10g_priv *priv = lane->priv;
|
|
int i;
|
|
|
|
if (!priv->info->lane_supports_mode(lane->id, mode))
|
|
return false;
|
|
|
|
for (i = 0; i < NUM_PLL; i++)
|
|
if (test_bit(mode, priv->pll[i].supported))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static struct lynx_10g_pll *lynx_10g_pll_get(struct lynx_10g_priv *priv,
|
|
enum lynx_lane_mode mode)
|
|
{
|
|
struct lynx_10g_pll *pll;
|
|
int i;
|
|
|
|
for (i = 0; i < NUM_PLL; i++) {
|
|
pll = &priv->pll[i];
|
|
if (test_bit(mode, pll->supported))
|
|
return pll;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool lynx_10g_cdr_lock_check(struct lynx_10g_lane *lane)
|
|
{
|
|
u32 tcsr3 = lynx_10g_lane_read(lane, LNaTCSR3);
|
|
|
|
if (tcsr3 & LNaTCSR3_CDR_LCK)
|
|
return true;
|
|
|
|
dev_dbg(&lane->phy->dev, "CDR unlocked, resetting lane receiver...\n");
|
|
|
|
lynx_10g_lane_rmw(lane, LNaGCR0, LNaGCR0_RRST_ON, LNaGCR0_RRST);
|
|
usleep_range(1, 2);
|
|
lynx_10g_lane_rmw(lane, LNaGCR0, LNaGCR0_RRST_OFF, LNaGCR0_RRST);
|
|
|
|
usleep_range(1, 2);
|
|
tcsr3 = lynx_10g_lane_read(lane, LNaTCSR3);
|
|
|
|
return !!(tcsr3 & LNaTCSR3_CDR_LCK);
|
|
}
|
|
|
|
#define work_to_lynx(w) container_of((w), struct lynx_10g_priv, cdr_check.work)
|
|
|
|
static void lynx_10g_cdr_lock_check_work(struct work_struct *work)
|
|
{
|
|
struct lynx_10g_priv *priv = work_to_lynx(work);
|
|
struct lynx_10g_lane *lane;
|
|
int i;
|
|
|
|
for (i = 0; i < priv->num_lanes; i++) {
|
|
lane = &priv->lane[i];
|
|
|
|
mutex_lock(&lane->phy->mutex);
|
|
|
|
if (!lane->init || !lane->powered_up) {
|
|
mutex_unlock(&lane->phy->mutex);
|
|
continue;
|
|
}
|
|
|
|
lynx_10g_cdr_lock_check(lane);
|
|
|
|
mutex_unlock(&lane->phy->mutex);
|
|
}
|
|
|
|
queue_delayed_work(system_power_efficient_wq, &priv->cdr_check,
|
|
msecs_to_jiffies(1000));
|
|
}
|
|
|
|
/* Halting puts the lane in a mode in which it can be reconfigured */
|
|
static void lynx_10g_lane_halt(struct phy *phy)
|
|
{
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
|
|
if (!lane->powered_up)
|
|
return;
|
|
|
|
/* Issue a reset request */
|
|
lynx_10g_lane_rmw(lane, LNaGCR0,
|
|
LNaGCR0_RRST_ON | LNaGCR0_TRST_ON,
|
|
LNaGCR0_RRST | LNaGCR0_TRST);
|
|
|
|
/* The RM says to wait for at least 50ns */
|
|
usleep_range(1, 2);
|
|
}
|
|
|
|
static void lynx_10g_lane_reset(struct phy *phy)
|
|
{
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
|
|
if (!lane->powered_up)
|
|
return;
|
|
|
|
/* Finalize the reset request */
|
|
lynx_10g_lane_rmw(lane, LNaGCR0,
|
|
LNaGCR0_RRST_OFF | LNaGCR0_TRST_OFF,
|
|
LNaGCR0_RRST | LNaGCR0_TRST);
|
|
|
|
/* The RM says to wait for at least 50ns */
|
|
usleep_range(1, 2);
|
|
}
|
|
|
|
static int lynx_10g_power_off(struct phy *phy)
|
|
{
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
|
|
if (!lane->powered_up)
|
|
return 0;
|
|
|
|
/* Issue a reset request with the power down bits set */
|
|
lynx_10g_lane_rmw(lane, LNaGCR0,
|
|
LNaGCR0_RRST_ON | LNaGCR0_TRST_ON |
|
|
LNaGCR0_RX_PD | LNaGCR0_TX_PD,
|
|
LNaGCR0_RRST | LNaGCR0_TRST |
|
|
LNaGCR0_RX_PD | LNaGCR0_TX_PD);
|
|
|
|
/* The RM says to wait for at least 50ns */
|
|
usleep_range(1, 2);
|
|
|
|
lane->powered_up = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lynx_10g_power_on(struct phy *phy)
|
|
{
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
|
|
if (lane->powered_up)
|
|
return 0;
|
|
|
|
/* The RM says to wait for at least 120ns between per lane setting have
|
|
* been changed and the lane is taken out of reset
|
|
*/
|
|
usleep_range(1, 2);
|
|
|
|
lynx_10g_lane_rmw(lane, LNaGCR0, LNaGCR0_RRST_OFF | LNaGCR0_TRST_OFF,
|
|
LNaGCR0_RRST | LNaGCR0_TRST |
|
|
LNaGCR0_RX_PD | LNaGCR0_TX_PD);
|
|
|
|
lane->powered_up = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void lynx_10g_lane_set_nrate(struct lynx_10g_lane *lane,
|
|
struct lynx_10g_pll *pll,
|
|
enum lynx_lane_mode mode)
|
|
{
|
|
switch (PLLnCR0_FRATE_SEL(pll->cr0)) {
|
|
case PLLnCR0_FRATE_5G:
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
lynx_10g_lane_rmw(lane, LNaGCR0,
|
|
LNaGCR0_TRAT_SEL(RAT_SEL_QUARTER) |
|
|
LNaGCR0_RRAT_SEL(RAT_SEL_QUARTER),
|
|
LNaGCR0_RRAT_SEL_MSK |
|
|
LNaGCR0_TRAT_SEL_MSK);
|
|
break;
|
|
case LANE_MODE_QSGMII:
|
|
lynx_10g_lane_rmw(lane, LNaGCR0,
|
|
LNaGCR0_TRAT_SEL(RAT_SEL_FULL) |
|
|
LNaGCR0_RRAT_SEL(RAT_SEL_FULL),
|
|
LNaGCR0_RRAT_SEL_MSK |
|
|
LNaGCR0_TRAT_SEL_MSK);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case PLLnCR0_FRATE_3_125G:
|
|
switch (mode) {
|
|
case LANE_MODE_2500BASEX:
|
|
lynx_10g_lane_rmw(lane, LNaGCR0,
|
|
LNaGCR0_TRAT_SEL(RAT_SEL_FULL) |
|
|
LNaGCR0_RRAT_SEL(RAT_SEL_FULL),
|
|
LNaGCR0_RRAT_SEL_MSK |
|
|
LNaGCR0_TRAT_SEL_MSK);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case PLLnCR0_FRATE_5_15625G:
|
|
switch (mode) {
|
|
case LANE_MODE_10GBASER:
|
|
case LANE_MODE_10GBASEKR:
|
|
case LANE_MODE_USXGMII:
|
|
case LANE_MODE_10G_QXGMII:
|
|
lynx_10g_lane_rmw(lane, LNaGCR0,
|
|
LNaGCR0_TRAT_SEL(RAT_SEL_DOUBLE) |
|
|
LNaGCR0_RRAT_SEL(RAT_SEL_DOUBLE),
|
|
LNaGCR0_RRAT_SEL_MSK |
|
|
LNaGCR0_TRAT_SEL_MSK);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void lynx_10g_lane_set_pll(struct lynx_10g_lane *lane,
|
|
struct lynx_10g_pll *pll)
|
|
{
|
|
if (pll->id == 0) {
|
|
lynx_10g_lane_rmw(lane, LNaGCR0,
|
|
LNaGCR0_RPLL_PLLF | LNaGCR0_TPLL_PLLF,
|
|
LNaGCR0_RPLL_MSK | LNaGCR0_TPLL_MSK);
|
|
} else {
|
|
lynx_10g_lane_rmw(lane, LNaGCR0,
|
|
LNaGCR0_RPLL_PLLS | LNaGCR0_TPLL_PLLS,
|
|
LNaGCR0_RPLL_MSK | LNaGCR0_TPLL_MSK);
|
|
}
|
|
}
|
|
|
|
/* Enabling ex_dly_clk does not require turning the PLL off, and does not
|
|
* affect the state of the lanes mapped to it. It is one of the few safe things
|
|
* that can be done with it at runtime.
|
|
*/
|
|
static void lynx_10g_pll_ex_dly_clk_enable(struct lynx_10g_pll *pll,
|
|
bool enable)
|
|
{
|
|
dev_err(pll->priv->dev, "Turning %s EX_DLY_CLK on PLL%c\n",
|
|
enable ? "on" : "off", pll->id == 0 ? 'F' : 'S');
|
|
|
|
pll->cr0 &= ~DLYDIV_SEL_MSK;
|
|
if (enable)
|
|
pll->cr0 |= DLYDIV_SEL_312_5_MHZ;
|
|
|
|
lynx_10g_pll_write(pll, PLLnCR0, pll->cr0);
|
|
}
|
|
|
|
static void lynx_10g_pll_get_ex_dly_clk(struct lynx_10g_pll *pll)
|
|
{
|
|
spin_lock(&pll->lock);
|
|
|
|
if (++pll->ex_dly_clk_use_count > 1) {
|
|
spin_unlock(&pll->lock);
|
|
return;
|
|
}
|
|
|
|
lynx_10g_pll_ex_dly_clk_enable(pll, true);
|
|
|
|
spin_unlock(&pll->lock);
|
|
}
|
|
|
|
static void lynx_10g_pll_put_ex_dly_clk(struct lynx_10g_pll *pll)
|
|
{
|
|
spin_lock(&pll->lock);
|
|
|
|
if (--pll->ex_dly_clk_use_count != 0) {
|
|
spin_unlock(&pll->lock);
|
|
return;
|
|
}
|
|
|
|
lynx_10g_pll_ex_dly_clk_enable(pll, false);
|
|
|
|
spin_unlock(&pll->lock);
|
|
}
|
|
|
|
static void lynx_10g_lane_remap_pll(struct lynx_10g_lane *lane,
|
|
enum lynx_lane_mode lane_mode)
|
|
{
|
|
struct lynx_10g_priv *priv = lane->priv;
|
|
struct lynx_10g_pll *pll;
|
|
|
|
/* Switch to the PLL that works with this interface type */
|
|
pll = lynx_10g_pll_get(priv, lane_mode);
|
|
lynx_10g_lane_set_pll(lane, pll);
|
|
|
|
/* Choose the portion of clock net to be used on this lane */
|
|
lynx_10g_lane_set_nrate(lane, pll, lane_mode);
|
|
}
|
|
|
|
static void lynx_10g_lane_change_proto_conf(struct lynx_10g_lane *lane,
|
|
enum lynx_lane_mode mode)
|
|
{
|
|
const struct lynx_10g_proto_conf *conf = &lynx_10g_proto_conf[mode];
|
|
|
|
lynx_10g_lane_rmw(lane, LNaGCR0, LNaGCR0_PROTS(conf->proto_sel) |
|
|
(conf->if20bit_en ? LNaGCR0_IF20BIT_EN : 0),
|
|
LNaGCR0_PROTS_MSK | LNaGCR0_IF20BIT_EN);
|
|
lynx_10g_lane_rmw(lane, LNaGCR1,
|
|
LNaGCR1_REIDL_TH(conf->reidl_th) |
|
|
(conf->reidl_et_msb ? LNaGCR1_REIDL_ET_MSB : 0) |
|
|
LNaGCR1_REIDL_ET_SEL(conf->reidl_et_sel) |
|
|
(conf->reidl_ex_msb ? LNaGCR1_REIDL_EX_MSB : 0) |
|
|
LNaGCR1_REIDL_EX_SEL(conf->reidl_ex_sel) |
|
|
LNaGCR1_ISLEW_RCTL(conf->islew_rctl) |
|
|
LNaGCR1_OSLEW_RCTL(conf->oslew_rctl),
|
|
LNaGCR1_REIDL_TH_MSK |
|
|
LNaGCR1_REIDL_ET_MSB | LNaGCR1_REIDL_ET_SEL_MSK |
|
|
LNaGCR1_REIDL_EX_MSB | LNaGCR1_REIDL_EX_SEL_MSK |
|
|
LNaGCR1_ISLEW_RCTL_MSK | LNaGCR1_OSLEW_RCTL_MSK);
|
|
lynx_10g_lane_rmw(lane, LNaRECR0,
|
|
(conf->rxeq_bst ? LNaRECR0_RXEQ_BST : 0) |
|
|
LNaRECR0_GK2OVD(conf->gk2ovd) |
|
|
LNaRECR0_GK3OVD(conf->gk3ovd) |
|
|
(conf->gk2ovd_en ? LNaRECR0_GK2OVD_EN : 0) |
|
|
(conf->gk3ovd_en ? LNaRECR0_GK3OVD_EN : 0) |
|
|
LNaRECR0_BASE_WAND(conf->base_wand),
|
|
LNaRECR0_RXEQ_BST |
|
|
LNaRECR0_GK2OVD_MSK | LNaRECR0_GK3OVD_MSK |
|
|
LNaRECR0_GK2OVD_EN | LNaRECR0_GK3OVD_EN |
|
|
LNaRECR0_BASE_WAND_MSK);
|
|
lynx_10g_lane_rmw(lane, LNaTECR0,
|
|
LNaTECR0_TEQ_TYPE(conf->teq_type) |
|
|
(conf->sgn_preq ? LNaTECR0_SGN_PREQ : 0) |
|
|
LNaTECR0_RATIO_PREQ(conf->ratio_preq) |
|
|
(conf->sgn_post1q ? LNaTECR0_SGN_POST1Q : 0) |
|
|
LNaTECR0_RATIO_PST1Q(conf->ratio_post1q) |
|
|
LNaTECR0_ADPT_EQ(conf->adpt_eq) |
|
|
LNaTECR0_AMP_RED(conf->amp_red),
|
|
LNaTECR0_TEQ_TYPE_MSK | LNaTECR0_SGN_PREQ |
|
|
LNaTECR0_RATIO_PREQ_MSK | LNaTECR0_SGN_POST1Q |
|
|
LNaTECR0_RATIO_PST1Q_MSK | LNaTECR0_ADPT_EQ_MSK |
|
|
LNaTECR0_AMP_RED_MSK);
|
|
lynx_10g_lane_write(lane, LNaTTLCR0, conf->ttlcr0);
|
|
}
|
|
|
|
static int lynx_10g_lane_disable_pcvt(struct lynx_10g_lane *lane,
|
|
enum lynx_lane_mode mode)
|
|
{
|
|
struct lynx_10g_priv *priv = lane->priv;
|
|
int err;
|
|
|
|
spin_lock(&priv->pccr_lock);
|
|
|
|
err = lynx_pccr_write(lane, mode, 0);
|
|
if (err)
|
|
goto out;
|
|
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
err = lynx_pcvt_rmw(lane, mode, CR(1), SGMIIaCR1_SGPCS_DIS,
|
|
SGMIIaCR1_SGPCS_EN);
|
|
if (err)
|
|
goto out;
|
|
|
|
lynx_pcvt_rmw(lane, mode, CR(0),
|
|
SGMIIaCR0_RST_SGM_ON | SGMIIaCR0_PD_SGM,
|
|
SGMIIaCR0_RST_SGM | SGMIIaCR0_PD_SGM);
|
|
break;
|
|
case LANE_MODE_QSGMII:
|
|
err = lynx_pcvt_rmw(lane, mode, CR(0),
|
|
QSGMIIaCR0_RST_QSGM_ON | QSGMIIaCR0_PD_QSGM,
|
|
QSGMIIaCR0_RST_QSGM | QSGMIIaCR0_PD_QSGM);
|
|
if (err)
|
|
goto out;
|
|
break;
|
|
default:
|
|
err = 0;
|
|
}
|
|
|
|
out:
|
|
spin_unlock(&priv->pccr_lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int lynx_10g_lane_enable_pcvt(struct lynx_10g_lane *lane,
|
|
enum lynx_lane_mode mode)
|
|
{
|
|
struct lynx_10g_priv *priv = lane->priv;
|
|
u32 val;
|
|
int err;
|
|
|
|
spin_lock(&priv->pccr_lock);
|
|
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
err = lynx_pcvt_rmw(lane, mode, CR(1), SGMIIaCR1_SGPCS_EN,
|
|
SGMIIaCR1_SGPCS_EN);
|
|
if (err)
|
|
goto out;
|
|
|
|
lynx_pcvt_rmw(lane, mode, CR(0), SGMIIaCR0_RST_SGM_OFF,
|
|
SGMIIaCR0_RST_SGM | SGMIIaCR0_PD_SGM);
|
|
break;
|
|
case LANE_MODE_QSGMII:
|
|
err = lynx_pcvt_rmw(lane, mode, CR(0), QSGMIIaCR0_RST_QSGM_OFF,
|
|
QSGMIIaCR0_RST_QSGM | QSGMIIaCR0_PD_QSGM);
|
|
if (err)
|
|
goto out;
|
|
break;
|
|
default:
|
|
err = 0;
|
|
}
|
|
|
|
if (lane->default_pccr[mode]) {
|
|
err = lynx_pccr_write(lane, mode, lane->default_pccr[mode]);
|
|
goto out;
|
|
}
|
|
|
|
val = 0;
|
|
|
|
switch (mode) {
|
|
case LANE_MODE_1000BASEKX:
|
|
val |= PCCR8_SGMIIa_KX;
|
|
fallthrough;
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_2500BASEX:
|
|
val |= PCCR8_SGMIIa_CFG;
|
|
break;
|
|
case LANE_MODE_QSGMII:
|
|
val |= PCCR9_QSGMIIa_CFG;
|
|
break;
|
|
case LANE_MODE_10G_QXGMII:
|
|
val |= PCCR9_QXGMIIa_CFG;
|
|
break;
|
|
case LANE_MODE_10GBASER:
|
|
case LANE_MODE_10GBASEKR:
|
|
val |= PCCRB_XFIa_CFG;
|
|
break;
|
|
case LANE_MODE_USXGMII:
|
|
val |= PCCRB_SXGMIIa_CFG;
|
|
break;
|
|
default:
|
|
err = 0;
|
|
goto out;
|
|
}
|
|
|
|
err = lynx_pccr_write(lane, mode, val);
|
|
out:
|
|
spin_unlock(&priv->pccr_lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
static bool lynx_10g_switch_needs_rcw_override(enum lynx_lane_mode crr,
|
|
enum lynx_lane_mode new)
|
|
{
|
|
if ((crr == LANE_MODE_1000BASEKX ||
|
|
crr == LANE_MODE_1000BASEX_SGMII ||
|
|
crr == LANE_MODE_2500BASEX) &&
|
|
(new == LANE_MODE_1000BASEKX ||
|
|
new == LANE_MODE_1000BASEX_SGMII ||
|
|
new == LANE_MODE_2500BASEX))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static int lynx_10g_set_lane_mode(struct phy *phy, enum lynx_lane_mode lane_mode)
|
|
{
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
struct lynx_10g_priv *priv = lane->priv;
|
|
bool powered_up = lane->powered_up;
|
|
int err;
|
|
|
|
if (lane->mode == LANE_MODE_UNKNOWN)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (!lynx_lane_supports_mode(lane, lane_mode))
|
|
return -EOPNOTSUPP;
|
|
|
|
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)
|
|
lynx_10g_lane_halt(phy);
|
|
|
|
if (lynx_10g_switch_needs_rcw_override(lane->mode, lane_mode)) {
|
|
err = fsl_guts_lane_set_mode(priv->info->index, lane->id, lane_mode);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
|
|
err = lynx_10g_lane_disable_pcvt(lane, lane->mode);
|
|
if (err)
|
|
goto out;
|
|
|
|
lynx_10g_lane_change_proto_conf(lane, lane_mode);
|
|
lynx_10g_lane_remap_pll(lane, lane_mode);
|
|
WARN_ON(lynx_10g_lane_enable_pcvt(lane, lane_mode));
|
|
|
|
/* 1000Base-KX lanes need their PLL to generate a 312.5 MHz frequency
|
|
* through EX_DLY_CLK.
|
|
*/
|
|
if (lane_mode == LANE_MODE_1000BASEKX)
|
|
lynx_10g_pll_get_ex_dly_clk(lynx_10g_pll_get(priv, lane_mode));
|
|
else if (lane->mode == LANE_MODE_1000BASEKX)
|
|
lynx_10g_pll_put_ex_dly_clk(lynx_10g_pll_get(priv, lane->mode));
|
|
|
|
lane->mode = lane_mode;
|
|
|
|
out:
|
|
if (powered_up)
|
|
lynx_10g_lane_reset(phy);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int lynx_10g_set_interface(struct phy *phy, phy_interface_t submode)
|
|
{
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
|
|
if (lane->algorithm)
|
|
lynx_xgkr_algorithm_destroy(lane->algorithm);
|
|
|
|
return lynx_10g_set_lane_mode(phy, phy_interface_to_lane_mode(submode));
|
|
}
|
|
|
|
static void lynx_10g_tune_tx_eq(struct phy *phy,
|
|
const struct lynx_xgkr_tx_eq *tx_eq)
|
|
{
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
|
|
lynx_10g_lane_rmw(lane, LNaTECR0,
|
|
LNaTECR0_RATIO_PREQ(tx_eq->ratio_preq) |
|
|
LNaTECR0_RATIO_PST1Q(tx_eq->ratio_post1q) |
|
|
LNaTECR0_ADPT_EQ(tx_eq->adapt_eq) |
|
|
LNaTECR0_AMP_RED(tx_eq->amp_reduction),
|
|
LNaTECR0_RATIO_PREQ_MSK |
|
|
LNaTECR0_RATIO_PST1Q_MSK |
|
|
LNaTECR0_ADPT_EQ_MSK |
|
|
LNaTECR0_AMP_RED_MSK);
|
|
}
|
|
|
|
static void lynx_10g_read_tx_eq(struct phy *phy, struct lynx_xgkr_tx_eq *tx_eq)
|
|
{
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
int val = lynx_10g_lane_read(lane, LNaTECR0);
|
|
|
|
tx_eq->ratio_preq = LNaTECR0_RATIO_PREQ_X(val);
|
|
tx_eq->ratio_post1q = LNaTECR0_RATIO_PST1Q_X(val);
|
|
tx_eq->amp_reduction = LNaTECR0_AMP_RED(val);
|
|
tx_eq->adapt_eq = LNaTECR0_ADPT_EQ_X(val);
|
|
}
|
|
|
|
static int lynx_10g_snapshot_rx_eq_gains(struct phy *phy, u8 *gaink2,
|
|
u8 *gaink3, u8 *eq_offset)
|
|
{
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
bool cdr_locked;
|
|
int err, val;
|
|
|
|
err = read_poll_timeout(lynx_10g_cdr_lock_check, cdr_locked,
|
|
cdr_locked, CDR_SLEEP_US, CDR_TIMEOUT_US,
|
|
false, lane);
|
|
if (err) {
|
|
dev_err(&phy->dev, "CDR not locked, cannot collect RX EQ snapshots\n");
|
|
return err;
|
|
}
|
|
|
|
/* wait until a previous snapshot has cleared */
|
|
err = read_poll_timeout(lynx_10g_lane_read, val,
|
|
!(val & LNaRECR1_CTL_SNP_DONE),
|
|
SNAPSHOT_SLEEP_US, SNAPSHOT_TIMEOUT_US,
|
|
false, lane, LNaRECR1);
|
|
if (err)
|
|
return err;
|
|
|
|
/* start snapshot of RX Equalization Control Gaink2, Gaink3
|
|
* and Offset Registers
|
|
*/
|
|
lynx_10g_lane_rmw(lane, LNaGCR1, LNaGCR1_REQ_CTL_SNP,
|
|
LNaGCR1_REQ_CTL_SNP);
|
|
|
|
/* wait for the snapshot to finish */
|
|
err = read_poll_timeout(lynx_10g_lane_read, val,
|
|
!!(val & LNaRECR1_CTL_SNP_DONE),
|
|
SNAPSHOT_SLEEP_US, SNAPSHOT_TIMEOUT_US,
|
|
false, lane, LNaRECR1);
|
|
if (err) {
|
|
dev_err(&phy->dev,
|
|
"Failed to snapshot RX EQ: undetected loss of CDR lock?\n");
|
|
lynx_10g_lane_rmw(lane, LNaGCR1, 0, LNaGCR1_REQ_CTL_SNP);
|
|
return err;
|
|
}
|
|
|
|
val = lynx_10g_lane_read(lane, LNaRECR1);
|
|
*gaink2 = LNaRECR1_GK2STAT_X(val);
|
|
*gaink3 = LNaRECR1_GK3STAT_X(val);
|
|
/* The algorithm should only be looking at offset_stat[5:0].
|
|
* Bit 6 is only used at higher bit rates to adjust the overall range
|
|
* of the internal offset DAC
|
|
*/
|
|
*eq_offset = LNaRECR1_OSETSTAT_X(val) & GENMASK(5, 0);
|
|
|
|
/* terminate the snapshot */
|
|
lynx_10g_lane_rmw(lane, LNaGCR1, 0, LNaGCR1_REQ_CTL_SNP);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lynx_10g_snapshot_rx_eq_bin(struct phy *phy, enum lynx_bin_type bin_type,
|
|
s16 *bin)
|
|
{
|
|
int bin_sel = lynx_10g_bin_type_to_bin_sel[bin_type];
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
bool cdr_locked;
|
|
int err, val;
|
|
|
|
if (WARN_ON(bin_sel < 0))
|
|
return bin_sel;
|
|
|
|
err = read_poll_timeout(lynx_10g_cdr_lock_check, cdr_locked,
|
|
cdr_locked, CDR_SLEEP_US, CDR_TIMEOUT_US,
|
|
false, lane);
|
|
if (err) {
|
|
dev_err(&phy->dev, "CDR not locked, cannot collect RX EQ snapshots\n");
|
|
return err;
|
|
}
|
|
|
|
/* wait until a previous snapshot has cleared */
|
|
err = read_poll_timeout(lynx_10g_lane_read, val,
|
|
!(val & LNaRECR1_BIN_SNP_DONE),
|
|
SNAPSHOT_SLEEP_US, SNAPSHOT_TIMEOUT_US,
|
|
false, lane, LNaRECR1);
|
|
if (err)
|
|
return err;
|
|
|
|
/* select the binning register we would like to snapshot */
|
|
lynx_10g_lane_rmw(lane, LNaTCSR1, LNaTCSR1_CDR_SEL(bin_sel),
|
|
LNaTCSR1_CDR_SEL_MSK);
|
|
|
|
/* start snapshot */
|
|
lynx_10g_lane_rmw(lane, LNaGCR1, LNaGCR1_REQ_BIN_SNP,
|
|
LNaGCR1_REQ_BIN_SNP);
|
|
|
|
/* wait for the snapshot to finish */
|
|
err = read_poll_timeout(lynx_10g_lane_read, val,
|
|
!!(val & LNaRECR1_BIN_SNP_DONE),
|
|
SNAPSHOT_SLEEP_US, SNAPSHOT_TIMEOUT_US,
|
|
false, lane, LNaRECR1);
|
|
if (err) {
|
|
dev_err(&phy->dev,
|
|
"Failed to snapshot RX EQ: undetected loss of CDR lock?\n");
|
|
lynx_10g_lane_rmw(lane, LNaGCR1, 0, LNaGCR1_REQ_BIN_SNP);
|
|
return err;
|
|
}
|
|
|
|
val = lynx_10g_lane_read(lane, LNaTCSR1);
|
|
val = LNaTCSR1_EQ_SNPBIN_DATA_X(val);
|
|
|
|
/* The snapshot is a 2's complement value stored on 9 bits
|
|
* (-256 to 255)
|
|
*/
|
|
if (val & LNaTCSR1_EQ_SNPBIN_DATA_SGN) {
|
|
val &= ~LNaTCSR1_EQ_SNPBIN_DATA_SGN;
|
|
val -= 256;
|
|
}
|
|
|
|
*bin = (s16)val;
|
|
|
|
/* terminate the snapshot */
|
|
lynx_10g_lane_rmw(lane, LNaGCR1, 0, LNaGCR1_REQ_BIN_SNP);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct lynx_xgkr_algorithm_ops lynx_10g_xgkr_ops = {
|
|
.tune_tx_eq = lynx_10g_tune_tx_eq,
|
|
.read_tx_eq = lynx_10g_read_tx_eq,
|
|
.snapshot_rx_eq_gains = lynx_10g_snapshot_rx_eq_gains,
|
|
.snapshot_rx_eq_bin = lynx_10g_snapshot_rx_eq_bin,
|
|
};
|
|
|
|
static int lynx_10g_set_link_mode(struct phy *phy,
|
|
enum ethtool_link_mode_bit_indices submode)
|
|
{
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
struct lynx_xgkr_algorithm *algorithm;
|
|
int err;
|
|
|
|
err = lynx_10g_set_lane_mode(phy, link_mode_to_lane_mode(submode));
|
|
if (err)
|
|
return err;
|
|
|
|
algorithm = lynx_xgkr_algorithm_create(phy, &lynx_10g_xgkr_ops);
|
|
if (!algorithm)
|
|
return -ENOMEM;
|
|
|
|
if (lane->algorithm)
|
|
lynx_xgkr_algorithm_destroy(lane->algorithm);
|
|
|
|
lane->algorithm = algorithm;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lynx_10g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
|
|
{
|
|
switch (mode) {
|
|
case PHY_MODE_ETHERNET:
|
|
return lynx_10g_set_interface(phy, submode);
|
|
case PHY_MODE_ETHERNET_LINKMODE:
|
|
return lynx_10g_set_link_mode(phy, submode);
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
static int lynx_10g_validate_interface(struct phy *phy, phy_interface_t submode)
|
|
{
|
|
enum lynx_lane_mode lane_mode = phy_interface_to_lane_mode(submode);
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
struct lynx_10g_priv *priv = lane->priv;
|
|
|
|
if (!lynx_lane_supports_mode(lane, lane_mode))
|
|
return -EOPNOTSUPP;
|
|
|
|
if (lynx_10g_switch_needs_rcw_override(lane->mode, lane_mode))
|
|
return fsl_guts_lane_validate(priv->info->index, lane->id, lane_mode);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lynx_10g_validate_link_mode(struct phy *phy,
|
|
enum ethtool_link_mode_bit_indices submode)
|
|
{
|
|
enum lynx_lane_mode lane_mode = link_mode_to_lane_mode(submode);
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
|
|
if (!lynx_lane_supports_mode(lane, lane_mode))
|
|
return -EOPNOTSUPP;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lynx_10g_validate(struct phy *phy, enum phy_mode mode, int submode,
|
|
union phy_configure_opts *opts __always_unused)
|
|
{
|
|
switch (mode) {
|
|
case PHY_MODE_ETHERNET:
|
|
return lynx_10g_validate_interface(phy, submode);
|
|
case PHY_MODE_ETHERNET_LINKMODE:
|
|
return lynx_10g_validate_link_mode(phy, submode);
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
static int lynx_10g_init(struct phy *phy)
|
|
{
|
|
struct lynx_10g_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 off when its consumer
|
|
* calls phy_init().
|
|
*/
|
|
lane->powered_up = true;
|
|
lynx_10g_power_off(phy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lynx_10g_exit(struct phy *phy)
|
|
{
|
|
struct lynx_10g_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_10g_power_on(phy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void lynx_10g_check_cdr_lock(struct phy *phy,
|
|
struct phy_status_opts_cdr *cdr)
|
|
{
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
|
|
cdr->cdr_locked = lynx_10g_cdr_lock_check(lane);
|
|
}
|
|
|
|
static void lynx_10g_get_pcvt_count(struct phy *phy,
|
|
struct phy_status_opts_pcvt_count *opts)
|
|
{
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
enum lynx_lane_mode lane_mode = lane->mode;
|
|
|
|
switch (opts->type) {
|
|
case PHY_PCVT_ETHERNET_PCS:
|
|
switch (lane_mode) {
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_2500BASEX:
|
|
case LANE_MODE_USXGMII:
|
|
case LANE_MODE_10GBASER:
|
|
case LANE_MODE_10GBASEKR:
|
|
opts->num_pcvt = 1;
|
|
break;
|
|
case LANE_MODE_QSGMII:
|
|
case LANE_MODE_10G_QXGMII:
|
|
opts->num_pcvt = 4;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case PHY_PCVT_ETHERNET_ANLT:
|
|
switch (lane_mode) {
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_10GBASEKR:
|
|
opts->num_pcvt = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void lynx_10g_get_pcvt_addr(struct phy *phy,
|
|
struct phy_status_opts_pcvt *pcvt)
|
|
{
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
u32 cr1;
|
|
|
|
switch (pcvt->type) {
|
|
case PHY_PCVT_ETHERNET_PCS:
|
|
case PHY_PCVT_ETHERNET_ANLT:
|
|
WARN_ON(lynx_pcvt_read(lane, lane->mode, CR(1), &cr1));
|
|
pcvt->addr.mdio = MDEV_PORT_X(cr1) + pcvt->index;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int lynx_10g_get_status(struct phy *phy, enum phy_status_type type,
|
|
union phy_status_opts *opts)
|
|
{
|
|
switch (type) {
|
|
case PHY_STATUS_CDR_LOCK:
|
|
lynx_10g_check_cdr_lock(phy, &opts->cdr);
|
|
break;
|
|
case PHY_STATUS_PCVT_COUNT:
|
|
lynx_10g_get_pcvt_count(phy, &opts->pcvt_count);
|
|
break;
|
|
case PHY_STATUS_PCVT_ADDR:
|
|
lynx_10g_get_pcvt_addr(phy, &opts->pcvt);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lynx_10g_configure(struct phy *phy, union phy_configure_opts *opts)
|
|
{
|
|
struct lynx_10g_lane *lane = phy_get_drvdata(phy);
|
|
|
|
return lynx_xgkr_algorithm_configure(lane->algorithm, &opts->ethernet);
|
|
}
|
|
|
|
static const struct phy_ops lynx_10g_ops = {
|
|
.init = lynx_10g_init,
|
|
.exit = lynx_10g_exit,
|
|
.power_on = lynx_10g_power_on,
|
|
.power_off = lynx_10g_power_off,
|
|
.set_mode = lynx_10g_set_mode,
|
|
.validate = lynx_10g_validate,
|
|
.get_status = lynx_10g_get_status,
|
|
.configure = lynx_10g_configure,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static const char *lynx_10g_refclk_str(int refclk)
|
|
{
|
|
switch (refclk) {
|
|
case PLLnCR0_REFCLK_SEL_100MHZ:
|
|
return "100MHz";
|
|
case PLLnCR0_REFCLK_SEL_125MHZ:
|
|
return "125MHz";
|
|
case PLLnCR0_REFCLK_SEL_156MHZ:
|
|
return "156.25MHz";
|
|
case PLLnCR0_REFCLK_SEL_150MHZ:
|
|
return "150MHz";
|
|
case PLLnCR0_REFCLK_SEL_161MHZ:
|
|
return "161.1328125MHz";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static const char *lynx_10g_clock_net_str(int frate)
|
|
{
|
|
switch (frate) {
|
|
case PLLnCR0_FRATE_5G:
|
|
return "5GHz";
|
|
case PLLnCR0_FRATE_3_125G:
|
|
return "3.125GHz";
|
|
case PLLnCR0_FRATE_5_15625G:
|
|
return "5.15625GHz";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static void lynx_10g_pll_dump(struct lynx_10g_pll *pll)
|
|
{
|
|
struct lynx_10g_priv *priv = pll->priv;
|
|
struct device *dev = priv->dev;
|
|
enum lynx_lane_mode mode;
|
|
int i;
|
|
|
|
dev_info(dev, "PLL%c: %s, %s, reference clock %s, clock net %s\n",
|
|
pll->id == 0 ? 'F' : 'S',
|
|
PLLnCR0_POFF(pll->cr0) ? "disabled" : "enabled",
|
|
PLLnCR0_PLL_LCK(pll->cr0) ? "locked" : "unlocked",
|
|
lynx_10g_refclk_str(PLLnCR0_REFCLK_SEL(pll->cr0)),
|
|
lynx_10g_clock_net_str(PLLnCR0_FRATE_SEL(pll->cr0)));
|
|
|
|
if (PLLnCR0_POFF(pll->cr0))
|
|
return;
|
|
|
|
dev_info(dev, "\tSupported interfaces and link modes:\n");
|
|
|
|
for (mode = LANE_MODE_UNKNOWN; mode < LANE_MODE_MAX; mode++) {
|
|
if (!test_bit(mode, pll->supported))
|
|
continue;
|
|
|
|
for (i = 0; i < priv->info->num_lanes; i++) {
|
|
if (lynx_lane_supports_mode(&priv->lane[i], mode)) {
|
|
enum ethtool_link_mode_bit_indices link_mode;
|
|
phy_interface_t intf;
|
|
|
|
for (intf = 0; intf < PHY_INTERFACE_MODE_MAX; intf++)
|
|
if (phy_interface_to_lane_mode(intf) == mode)
|
|
dev_info(dev, "\t\t%s\n", phy_modes(intf));
|
|
|
|
for (link_mode = 0; link_mode < __ETHTOOL_LINK_MODE_MASK_NBITS; link_mode++)
|
|
if (link_mode_to_lane_mode(link_mode) == mode)
|
|
dev_info(dev, "\t\t%s\n", ethtool_link_mode_str(link_mode));
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void lynx_10g_pll_read_configuration(struct lynx_10g_priv *priv)
|
|
{
|
|
struct lynx_10g_pll *pll;
|
|
int dlydiv_sel;
|
|
int i;
|
|
|
|
for (i = 0; i < NUM_PLL; i++) {
|
|
pll = &priv->pll[i];
|
|
pll->priv = priv;
|
|
pll->id = i;
|
|
|
|
pll->cr0 = lynx_10g_pll_read(pll, PLLnCR0);
|
|
if (PLLnCR0_POFF(pll->cr0))
|
|
continue;
|
|
|
|
dlydiv_sel = PLLnCR0_DLYDIV_SEL(pll->cr0);
|
|
if (dlydiv_sel) {
|
|
dev_dbg(priv->dev, "PLL%cCR0[DLYDIV_SEL] found set\n",
|
|
pll->id == 0 ? 'F' : 'S');
|
|
pll->ex_dly_clk_use_count = 1;
|
|
}
|
|
|
|
switch (PLLnCR0_FRATE_SEL(pll->cr0)) {
|
|
case PLLnCR0_FRATE_5G:
|
|
/* 5GHz clock net */
|
|
__set_bit(LANE_MODE_1000BASEX_SGMII, pll->supported);
|
|
__set_bit(LANE_MODE_QSGMII, pll->supported);
|
|
if (dlydiv_sel && dlydiv_sel != DLYDIV_SEL_312_5_MHZ) {
|
|
dev_dbg(priv->dev,
|
|
"PLL%c has ex_dly_clk provisioned for a frequency incompatible with 1000Base-KX\n",
|
|
pll->id == 0 ? 'F' : 'S');
|
|
} else {
|
|
__set_bit(LANE_MODE_1000BASEKX, pll->supported);
|
|
}
|
|
break;
|
|
case PLLnCR0_FRATE_3_125G:
|
|
__set_bit(LANE_MODE_2500BASEX, pll->supported);
|
|
break;
|
|
case PLLnCR0_FRATE_5_15625G:
|
|
/* 10.3125GHz clock net */
|
|
__set_bit(LANE_MODE_10GBASER, pll->supported);
|
|
__set_bit(LANE_MODE_10GBASEKR, pll->supported);
|
|
__set_bit(LANE_MODE_USXGMII, pll->supported);
|
|
__set_bit(LANE_MODE_10G_QXGMII, pll->supported);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* On LS1028A, SGMIIA_CFG, SGMIIB_CFG, and SGMIIC_CFG from PCCR8 have the
|
|
* ability to map either an ENETC PCS or a Felix switch PCS to the same lane.
|
|
* The PHY API lacks the capability to distinguish between one consumer and
|
|
* another, so we don't support changing the initial muxing done by the RCW.
|
|
* However, when disabling a PCS through PCCR8, we need to properly restore
|
|
* the original value to keep the same muxing, and for that we need to back
|
|
* it up (here).
|
|
*/
|
|
static void lynx_10g_backup_pccr_val(struct lynx_10g_lane *lane)
|
|
{
|
|
u32 val;
|
|
int err;
|
|
|
|
if (lane->mode == LANE_MODE_UNKNOWN)
|
|
return;
|
|
|
|
err = lynx_pccr_read(lane, lane->mode, &val);
|
|
if (err) {
|
|
dev_warn(&lane->phy->dev,
|
|
"The driver doesn't know how to access the PCCR for lane mode %d\n",
|
|
lane->mode);
|
|
lane->mode = LANE_MODE_UNKNOWN;
|
|
return;
|
|
}
|
|
|
|
lane->default_pccr[lane->mode] = val;
|
|
|
|
switch (lane->mode) {
|
|
case LANE_MODE_1000BASEKX:
|
|
case LANE_MODE_1000BASEX_SGMII:
|
|
case LANE_MODE_2500BASEX:
|
|
lane->default_pccr[LANE_MODE_1000BASEKX] = val | PCCR8_SGMIIa_KX;
|
|
lane->default_pccr[LANE_MODE_1000BASEX_SGMII] = val & ~PCCR8_SGMIIa_KX;
|
|
lane->default_pccr[LANE_MODE_2500BASEX] = val & ~PCCR8_SGMIIa_KX;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool lynx_10g_lane_is_3_125g(struct lynx_10g_lane *lane)
|
|
{
|
|
struct lynx_10g_priv *priv = lane->priv;
|
|
struct lynx_10g_pll *pll;
|
|
u32 gcr0;
|
|
|
|
gcr0 = lynx_10g_lane_read(lane, LNaGCR0);
|
|
|
|
if (gcr0 & LNaGCR0_TPLL_PLLF)
|
|
pll = &priv->pll[0];
|
|
else
|
|
pll = &priv->pll[1];
|
|
|
|
if (PLLnCR0_FRATE_SEL(pll->cr0) != PLLnCR0_FRATE_3_125G)
|
|
return false;
|
|
|
|
if (LNaGCR0_TRAT_SEL_X(gcr0) != RAT_SEL_FULL ||
|
|
LNaGCR0_RRAT_SEL_X(gcr0) != RAT_SEL_FULL)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void lynx_10g_lane_read_configuration(struct lynx_10g_lane *lane)
|
|
{
|
|
u32 pssr0 = lynx_10g_lane_read(lane, LNaPSSR0);
|
|
struct lynx_10g_priv *priv = lane->priv;
|
|
int proto = LNaPSSR0_TYPE_X(pssr0);
|
|
|
|
switch (proto) {
|
|
case PROTO_SEL_SGMII_BASEX_KX_QSGMII:
|
|
if (lynx_10g_lane_is_3_125g(lane))
|
|
lane->mode = LANE_MODE_2500BASEX;
|
|
else if (LNaPSSR0_IS_QUAD_X(pssr0))
|
|
lane->mode = LANE_MODE_QSGMII;
|
|
else
|
|
lane->mode = LANE_MODE_1000BASEX_SGMII;
|
|
break;
|
|
case PROTO_SEL_XFI_10GBASER_KR_SXGMII:
|
|
if (LNaPSSR0_IS_QUAD_X(pssr0))
|
|
lane->mode = LANE_MODE_10G_QXGMII;
|
|
else if (priv->info->has_hardcoded_usxgmii)
|
|
lane->mode = LANE_MODE_USXGMII;
|
|
else
|
|
lane->mode = LANE_MODE_10GBASER;
|
|
break;
|
|
case PROTO_SEL_PCIE:
|
|
case PROTO_SEL_SATA:
|
|
case PROTO_SEL_XAUI:
|
|
break;
|
|
default:
|
|
dev_warn(&lane->phy->dev, "Unknown lane protocol 0x%x\n", proto);
|
|
}
|
|
|
|
lynx_10g_backup_pccr_val(lane);
|
|
}
|
|
|
|
static struct phy *lynx_10g_xlate(struct device *dev,
|
|
struct of_phandle_args *args)
|
|
{
|
|
struct lynx_10g_priv *priv = dev_get_drvdata(dev);
|
|
int idx = args->args[0];
|
|
|
|
if (idx >= priv->info->num_lanes)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
return priv->lane[idx].phy;
|
|
}
|
|
|
|
static int lynx_10g_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct phy_provider *provider;
|
|
struct lynx_10g_priv *priv;
|
|
int i;
|
|
|
|
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
priv->dev = &pdev->dev;
|
|
priv->info = of_device_get_match_data(dev);
|
|
priv->big_endian = of_property_read_bool(dev->of_node, "big-endian");
|
|
|
|
priv->base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(priv->base))
|
|
return PTR_ERR(priv->base);
|
|
|
|
lynx_10g_pll_read_configuration(priv);
|
|
|
|
for (i = 0; i < priv->info->num_lanes; i++) {
|
|
struct lynx_10g_lane *lane = &priv->lane[i];
|
|
struct phy *phy;
|
|
|
|
memset(lane, 0, sizeof(*lane));
|
|
|
|
phy = devm_phy_create(&pdev->dev, NULL, &lynx_10g_ops);
|
|
if (IS_ERR(phy))
|
|
return PTR_ERR(phy);
|
|
|
|
lane->priv = priv;
|
|
lane->phy = phy;
|
|
lane->id = i;
|
|
phy_set_drvdata(phy, lane);
|
|
lynx_10g_lane_read_configuration(lane);
|
|
fsl_guts_lane_init(priv->info->index, lane->id, lane->mode);
|
|
}
|
|
|
|
for (i = 0; i < NUM_PLL; i++)
|
|
lynx_10g_pll_dump(&priv->pll[i]);
|
|
|
|
dev_set_drvdata(dev, priv);
|
|
|
|
spin_lock_init(&priv->pccr_lock);
|
|
INIT_DELAYED_WORK(&priv->cdr_check, lynx_10g_cdr_lock_check_work);
|
|
|
|
queue_delayed_work(system_power_efficient_wq, &priv->cdr_check,
|
|
msecs_to_jiffies(1000));
|
|
|
|
dev_set_drvdata(&pdev->dev, priv);
|
|
provider = devm_of_phy_provider_register(&pdev->dev, lynx_10g_xlate);
|
|
|
|
return PTR_ERR_OR_ZERO(provider);
|
|
}
|
|
|
|
static void lynx_10g_remove(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct lynx_10g_priv *priv = dev_get_drvdata(dev);
|
|
|
|
cancel_delayed_work_sync(&priv->cdr_check);
|
|
}
|
|
|
|
static const struct of_device_id lynx_10g_of_match_table[] = {
|
|
{ .compatible = "fsl,ls1028a-serdes", .data = &lynx_info_ls1028a },
|
|
{ .compatible = "fsl,ls1046a-serdes1", .data = &lynx_info_ls1046a_serdes1 },
|
|
{ .compatible = "fsl,ls1046a-serdes2", .data = &lynx_info_ls1046a_serdes2 },
|
|
{ .compatible = "fsl,ls1088a-serdes1", .data = &lynx_info_ls1088a_serdes1 },
|
|
{ .compatible = "fsl,ls2088a-serdes1", .data = &lynx_info_ls2088a_serdes1 },
|
|
{ .compatible = "fsl,ls2088a-serdes2", .data = &lynx_info_ls2088a_serdes2 },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, lynx_10g_of_match_table);
|
|
|
|
static struct platform_driver lynx_10g_driver = {
|
|
.probe = lynx_10g_probe,
|
|
.remove_new = lynx_10g_remove,
|
|
.driver = {
|
|
.name = "lynx-10g",
|
|
.of_match_table = lynx_10g_of_match_table,
|
|
},
|
|
};
|
|
module_platform_driver(lynx_10g_driver);
|
|
|
|
MODULE_AUTHOR("Ioana Ciornei <ioana.ciornei@nxp.com>");
|
|
MODULE_AUTHOR("Vladimir Oltean <vladimir.oltean@nxp.com>");
|
|
MODULE_DESCRIPTION("Lynx 10G SerDes PHY driver for Layerscape SoCs");
|
|
MODULE_LICENSE("GPL v2");
|