#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>
#define LYNX_28G_NUM_LANE 8
#define LYNX_28G_NUM_PLL 2
#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)
#define SXGMII_CFG(id) (28 - (id) * 4)
#define E25G_CFG(id) (28 - (id) * 4)
#define E40G_CFG(id) (28 - (id) * 4)
#define E50G_CFG(id) (20 - (id) * 4)
#define E100G_CFG(id) (12 - (id) * 4)
#define PLLnRSTCTL(pll) (0x400 + (pll) * 0x100 + 0x0)
#define PLLnRSTCTL_DIS(rstctl) (((rstctl) & BIT(24)) >> 24)
#define PLLnRSTCTL_LOCK(rstctl) (((rstctl) & BIT(23)) >> 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 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_IF_WIDTH GENMASK(2, 0)
#define LNaGCR0_IF_WIDTH_10_BIT 0x0
#define LNaGCR0_IF_WIDTH_20_BIT 0x2
#define LNaTRSTCTL(lane) (0x800 + (lane) * 0x100 + 0x20)
#define LNaTRSTCTL_HLT_REQ BIT(27)
#define LNaTRSTCTL_RST_DONE BIT(30)
#define LNaTRSTCTL_RST_REQ BIT(31)
#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 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)
#define LNaRRSTCTL(lane) (0x800 + (lane) * 0x100 + 0x40)
#define LNaRRSTCTL_HLT_REQ BIT(27)
#define LNaRRSTCTL_RST_DONE BIT(30)
#define LNaRRSTCTL_RST_REQ BIT(31)
#define LNaRRSTCTL_CDR_LOCK BIT(12)
#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 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)
#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)
#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)
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,
};
enum lynx_lane_mode {
LANE_MODE_UNKNOWN,
LANE_MODE_1000BASEX_SGMII,
LANE_MODE_10GBASER,
LANE_MODE_USXGMII,
LANE_MODE_MAX,
};
struct lynx_28g_proto_conf {
int proto_sel;
int if_width;
int teq_type;
int sgn_preq;
int ratio_preq;
int sgn_post1q;
int ratio_post1q;
int amp_red;
int adpt_eq;
int enter_idle_flt_sel;
int exit_idle_flt_sel;
int data_lost_th_sel;
int gk2ovd;
int gk3ovd;
int gk4ovd;
int gk2ovd_en;
int gk3ovd_en;
int gk4ovd_en;
int eq_offset_ovd;
int eq_offset_ovd_en;
int eq_offset_rng_dbl;
int eq_blw_sel;
int eq_boost;
int spare_in;
int smp_autoz_d1r;
int smp_autoz_eg1r;
int rccr0;
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,
},
};
struct lynx_pccr {
int offset;
int width;
int shift;
};
struct lynx_28g_priv;
struct lynx_28g_pll {
struct lynx_28g_priv *priv;
u32 rstctl, cr0, cr1;
int id;
DECLARE_BITMAP(supported, LANE_MODE_MAX);
};
struct lynx_28g_lane {
struct lynx_28g_priv *priv;
struct phy *phy;
bool powered_up;
bool init;
unsigned int id;
enum lynx_lane_mode mode;
};
struct lynx_28g_priv {
void __iomem *base;
struct device *dev;
spinlock_t pcc_lock;
struct lynx_28g_pll pll[LYNX_28G_NUM_PLL];
struct lynx_28g_lane lane[LYNX_28G_NUM_LANE];
struct delayed_work cdr_check;
};
static void lynx_28g_rmw(struct lynx_28g_priv *priv, unsigned long off,
u32 val, u32 mask)
{
void __iomem *reg = priv->base + off;
u32 orig, tmp;
orig = ioread32(reg);
tmp = orig & ~mask;
tmp |= val;
iowrite32(tmp, reg);
}
#define lynx_28g_read(priv, off) \
ioread32((priv)->base + (off))
#define lynx_28g_write(priv, off, val) \
iowrite32(val, (priv)->base + (off))
#define lynx_28g_lane_rmw(lane, reg, val, mask) \
lynx_28g_rmw((lane)->priv, reg(lane->id), val, mask)
#define lynx_28g_lane_read(lane, reg) \
ioread32((lane)->priv->base + reg((lane)->id))
#define lynx_28g_lane_write(lane, reg, val) \
iowrite32(val, (lane)->priv->base + reg((lane)->id))
#define lynx_28g_pll_read(pll, reg) \
ioread32((pll)->priv->base + reg((pll)->id))
static const char *lynx_lane_mode_str(enum lynx_lane_mode lane_mode)
{
switch (lane_mode) {
case LANE_MODE_1000BASEX_SGMII:
return "1000Base-X/SGMII";
case LANE_MODE_10GBASER:
return "10GBase-R";
case LANE_MODE_USXGMII:
return "USXGMII";
default:
return "unknown";
}
}
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_10GBASER:
return LANE_MODE_10GBASER;
case PHY_INTERFACE_MODE_USXGMII:
return LANE_MODE_USXGMII;
default:
return LANE_MODE_UNKNOWN;
}
}
static bool lynx_28g_supports_lane_mode(struct lynx_28g_priv *priv,
enum lynx_lane_mode mode)
{
int i;
for (i = 0; i < LYNX_28G_NUM_PLL; i++) {
if (PLLnRSTCTL_DIS(priv->pll[i].rstctl))
continue;
if (test_bit(mode, priv->pll[i].supported))
return true;
}
return false;
}
static struct lynx_28g_pll *lynx_28g_pll_get(struct lynx_28g_priv *priv,
enum lynx_lane_mode mode)
{
struct lynx_28g_pll *pll;
int i;
for (i = 0; i < LYNX_28G_NUM_PLL; i++) {
pll = &priv->pll[i];
if (PLLnRSTCTL_DIS(pll->rstctl))
continue;
if (test_bit(mode, pll->supported))
return pll;
}
dev_WARN_ONCE(priv->dev, 1, "no pll for lane mode %s\n",
lynx_lane_mode_str(mode));
return NULL;
}
static void lynx_28g_lane_set_nrate(struct lynx_28g_lane *lane,
struct lynx_28g_pll *pll,
enum lynx_lane_mode lane_mode)
{
switch (FIELD_GET(PLLnCR1_FRATE_SEL, pll->cr1)) {
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;
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 int lynx_28g_power_off(struct phy *phy)
{
struct lynx_28g_lane *lane = phy_get_drvdata(phy);
u32 trstctl, rrstctl;
if (!lane->powered_up)
return 0;
lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_HLT_REQ,
LNaTRSTCTL_HLT_REQ);
lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_HLT_REQ,
LNaRRSTCTL_HLT_REQ);
do {
trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL);
rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
} while ((trstctl & LNaTRSTCTL_HLT_REQ) ||
(rrstctl & LNaRRSTCTL_HLT_REQ));
lane->powered_up = false;
return 0;
}
static int lynx_28g_power_on(struct phy *phy)
{
struct lynx_28g_lane *lane = phy_get_drvdata(phy);
u32 trstctl, rrstctl;
if (lane->powered_up)
return 0;
lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_RST_REQ,
LNaTRSTCTL_RST_REQ);
lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_RST_REQ,
LNaRRSTCTL_RST_REQ);
do {
trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL);
rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
} while (!(trstctl & LNaTRSTCTL_RST_DONE) ||
!(rrstctl & LNaRRSTCTL_RST_DONE));
lane->powered_up = true;
return 0;
}
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;
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);
default:
return -EOPNOTSUPP;
}
}
static int lynx_pccr_read(struct lynx_28g_lane *lane, enum lynx_lane_mode mode,
u32 *val)
{
struct lynx_28g_priv *priv = lane->priv;
struct lynx_pccr pccr;
u32 tmp;
int err;
err = lynx_28g_get_pccr(mode, lane->id, &pccr);
if (err)
return err;
tmp = lynx_28g_read(priv, pccr.offset);
*val = (tmp >> pccr.shift) & GENMASK(pccr.width - 1, 0);
return 0;
}
static int lynx_pccr_write(struct lynx_28g_lane *lane,
enum lynx_lane_mode lane_mode, u32 val)
{
struct lynx_28g_priv *priv = lane->priv;
struct lynx_pccr pccr;
u32 old, tmp, mask;
int err;
err = lynx_28g_get_pccr(lane_mode, lane->id, &pccr);
if (err)
return err;
old = lynx_28g_read(priv, pccr.offset);
mask = GENMASK(pccr.width - 1, 0) << pccr.shift;
tmp = (old & ~mask) | (val << pccr.shift);
lynx_28g_write(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_28g_lane *lane,
enum lynx_lane_mode lane_mode, int cr, u32 *val)
{
struct lynx_28g_priv *priv = lane->priv;
int offset;
offset = lynx_28g_get_pcvt_offset(lane->id, lane_mode);
if (offset < 0)
return offset;
*val = lynx_28g_read(priv, offset + cr);
return 0;
}
static int lynx_pcvt_write(struct lynx_28g_lane *lane,
enum lynx_lane_mode lane_mode, int cr, u32 val)
{
struct lynx_28g_priv *priv = lane->priv;
int offset;
offset = lynx_28g_get_pcvt_offset(lane->id, lane_mode);
if (offset < 0)
return offset;
lynx_28g_write(priv, offset + cr, val);
return 0;
}
static int lynx_pcvt_rmw(struct lynx_28g_lane *lane,
enum lynx_lane_mode lane_mode,
int cr, u32 val, u32 mask)
{
int err;
u32 tmp;
err = lynx_pcvt_read(lane, lane_mode, cr, &tmp);
if (err)
return err;
tmp &= ~mask;
tmp |= val;
return lynx_pcvt_write(lane, lane_mode, cr, tmp);
}
static void lynx_28g_lane_remap_pll(struct lynx_28g_lane *lane,
enum lynx_lane_mode lane_mode)
{
struct lynx_28g_priv *priv = lane->priv;
struct lynx_28g_pll *pll;
pll = lynx_28g_pll_get(priv, lane_mode);
if (unlikely(pll == NULL))
return;
lynx_28g_lane_set_pll(lane, pll);
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;
default:
break;
}
err = lynx_pccr_write(lane, lane_mode, val);
spin_unlock(&priv->pcc_lock);
return err;
}
static int lynx_28g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
{
struct lynx_28g_lane *lane = phy_get_drvdata(phy);
struct lynx_28g_priv *priv = lane->priv;
int powered_up = lane->powered_up;
enum lynx_lane_mode lane_mode;
int err = 0;
if (mode != PHY_MODE_ETHERNET)
return -EOPNOTSUPP;
if (lane->mode == LANE_MODE_UNKNOWN)
return -EOPNOTSUPP;
lane_mode = phy_interface_to_lane_mode(submode);
if (!lynx_28g_supports_lane_mode(priv, lane_mode))
return -EOPNOTSUPP;
if (lane_mode == lane->mode)
return 0;
if (powered_up)
lynx_28g_power_off(phy);
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_power_on(phy);
return err;
}
static int lynx_28g_validate(struct phy *phy, enum phy_mode mode, int submode,
union phy_configure_opts *opts __always_unused)
{
struct lynx_28g_lane *lane = phy_get_drvdata(phy);
struct lynx_28g_priv *priv = lane->priv;
enum lynx_lane_mode lane_mode;
if (mode != PHY_MODE_ETHERNET)
return -EOPNOTSUPP;
lane_mode = phy_interface_to_lane_mode(submode);
if (!lynx_28g_supports_lane_mode(priv, lane_mode))
return -EOPNOTSUPP;
return 0;
}
static int lynx_28g_init(struct phy *phy)
{
struct lynx_28g_lane *lane = phy_get_drvdata(phy);
lane->init = true;
lane->powered_up = true;
lynx_28g_power_off(phy);
return 0;
}
static const struct phy_ops lynx_28g_ops = {
.init = lynx_28g_init,
.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_28g_priv *priv)
{
struct lynx_28g_pll *pll;
int i;
for (i = 0; i < LYNX_28G_NUM_PLL; i++) {
pll = &priv->pll[i];
pll->priv = priv;
pll->id = i;
pll->rstctl = lynx_28g_pll_read(pll, PLLnRSTCTL);
pll->cr0 = lynx_28g_pll_read(pll, PLLnCR0);
pll->cr1 = lynx_28g_pll_read(pll, PLLnCR1);
if (PLLnRSTCTL_DIS(pll->rstctl))
continue;
switch (FIELD_GET(PLLnCR1_FRATE_SEL, pll->cr1)) {
case PLLnCR1_FRATE_5G_10GVCO:
case PLLnCR1_FRATE_5G_25GVCO:
__set_bit(LANE_MODE_1000BASEX_SGMII, pll->supported);
break;
case PLLnCR1_FRATE_10G_20GVCO:
__set_bit(LANE_MODE_10GBASER, pll->supported);
__set_bit(LANE_MODE_USXGMII, pll->supported);
break;
default:
break;
}
}
}
#define work_to_lynx(w) container_of((w), struct lynx_28g_priv, cdr_check.work)
static void lynx_28g_cdr_lock_check(struct work_struct *work)
{
struct lynx_28g_priv *priv = work_to_lynx(work);
struct lynx_28g_lane *lane;
u32 rrstctl;
int i;
for (i = 0; i < LYNX_28G_NUM_LANE; i++) {
lane = &priv->lane[i];
if (!lane->phy)
continue;
mutex_lock(&lane->phy->mutex);
if (!lane->init || !lane->powered_up) {
mutex_unlock(&lane->phy->mutex);
continue;
}
rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
if (!(rrstctl & LNaRRSTCTL_CDR_LOCK)) {
lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_RST_REQ,
LNaRRSTCTL_RST_REQ);
do {
rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
} while (!(rrstctl & LNaRRSTCTL_RST_DONE));
}
mutex_unlock(&lane->phy->mutex);
}
queue_delayed_work(system_power_efficient_wq, &priv->cdr_check,
msecs_to_jiffies(1000));
}
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;
default:
lane->mode = LANE_MODE_UNKNOWN;
}
}
static struct phy *lynx_28g_xlate(struct device *dev,
const struct of_phandle_args *args)
{
struct lynx_28g_priv *priv = dev_get_drvdata(dev);
int idx;
if (args->args_count == 0)
return of_phy_simple_xlate(dev, args);
else if (args->args_count != 1)
return ERR_PTR(-ENODEV);
idx = args->args[0];
if (WARN_ON(idx >= LYNX_28G_NUM_LANE))
return ERR_PTR(-EINVAL);
return priv->lane[idx].phy;
}
static int lynx_28g_probe_lane(struct lynx_28g_priv *priv, int id,
struct device_node *dn)
{
struct lynx_28g_lane *lane = &priv->lane[id];
struct phy *phy;
phy = devm_phy_create(priv->dev, dn, &lynx_28g_ops);
if (IS_ERR(phy))
return PTR_ERR(phy);
lane->priv = priv;
lane->phy = phy;
lane->id = id;
phy_set_drvdata(phy, lane);
lynx_28g_lane_read_configuration(lane);
return 0;
}
static int lynx_28g_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct phy_provider *provider;
struct lynx_28g_priv *priv;
struct device_node *dn;
int err;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
dev_set_drvdata(dev, priv);
spin_lock_init(&priv->pcc_lock);
INIT_DELAYED_WORK(&priv->cdr_check, lynx_28g_cdr_lock_check);
priv->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
lynx_28g_pll_read_configuration(priv);
dn = dev_of_node(dev);
if (of_get_child_count(dn)) {
struct device_node *child;
for_each_available_child_of_node(dn, child) {
u32 reg;
if (!(of_node_name_eq(child, "phy")))
continue;
if (of_property_read_u32(child, "reg", ®)) {
dev_err(dev, "No \"reg\" property for %pOF\n", child);
of_node_put(child);
return -EINVAL;
}
if (reg >= LYNX_28G_NUM_LANE) {
dev_err(dev, "\"reg\" property out of range for %pOF\n", child);
of_node_put(child);
return -EINVAL;
}
err = lynx_28g_probe_lane(priv, reg, child);
if (err) {
of_node_put(child);
return err;
}
}
} else {
for (int i = 0; i < LYNX_28G_NUM_LANE; i++) {
err = lynx_28g_probe_lane(priv, i, NULL);
if (err)
return err;
}
}
provider = devm_of_phy_provider_register(dev, lynx_28g_xlate);
if (IS_ERR(provider))
return PTR_ERR(provider);
queue_delayed_work(system_power_efficient_wq, &priv->cdr_check,
msecs_to_jiffies(1000));
return 0;
}
static void lynx_28g_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct lynx_28g_priv *priv = dev_get_drvdata(dev);
cancel_delayed_work_sync(&priv->cdr_check);
}
static const struct of_device_id lynx_28g_of_match_table[] = {
{ .compatible = "fsl,lynx-28g" },
{ },
};
MODULE_DEVICE_TABLE(of, lynx_28g_of_match_table);
static struct platform_driver lynx_28g_driver = {
.probe = lynx_28g_probe,
.remove = lynx_28g_remove,
.driver = {
.name = "lynx-28g",
.of_match_table = lynx_28g_of_match_table,
},
};
module_platform_driver(lynx_28g_driver);
MODULE_AUTHOR("Ioana Ciornei <ioana.ciornei@nxp.com>");
MODULE_DESCRIPTION("Lynx 28G SerDes PHY driver for Layerscape SoCs");
MODULE_LICENSE("GPL v2");