The four existing SD1 modes all keep the eight lanes on one clock: fast or slow (PLLF or PLLS). This one does not: lanes 0-3 stay 100G CAUI-4 at 25.78125 Gbaud on PLLF, and lanes 4-7 become four XFI ports at 10.3125 Gbaud on PLLS.
Checked with: grouping [LNA-LND] [LNE] [LNF] [LNG] [LNH] PSSR0 0-3 6a01xxxx CAUI@100G, MAC 0x01 -> dpmac.2 PSSR0 4-7 28xxxxxx XFI@10G, MAC 05/04/03/02 -> dpmac.6/.5/.4/.3 Signed-off-by: Vincent Jardin <[email protected]> --- (no changes since v1) board/nxp/lx2160a/serdes_split.c | 271 ++++++++++++++++++++++++++----- board/nxp/lx2160a/serdes_split.h | 11 ++ 2 files changed, 244 insertions(+), 38 deletions(-) diff --git a/board/nxp/lx2160a/serdes_split.c b/board/nxp/lx2160a/serdes_split.c index f48c882aa22..bf337355c7d 100644 --- a/board/nxp/lx2160a/serdes_split.c +++ b/board/nxp/lx2160a/serdes_split.c @@ -438,16 +438,16 @@ int lx2160a_serdes1_split(void) * The register values below were analyzed from the CPU for both cases: * protocol-13 vs protocol-20 run. */ -#define F40G_PLLS 1 /* PLLS index in PLLnxxx() */ -#define F40G_PLLF 0 -#define F40G_PLLS_CR0 0x00040000 /* REFCLK_SEL=4, 161.1328125 MHz */ -#define F40G_PLLS_CR1 0x86100008 /* 10.3125 GHz clock net */ -#define F40G_PLLS_CR5 0x00001000 +#define SRDS_PLLS 1 /* PLLS index in PLLnxxx() */ +#define SRDS_PLLF 0 +#define SLOW_PLLS_CR0 0x00040000 /* REFCLK_SEL=4, 161.1328125 MHz */ +#define SLOW_PLLS_CR1 0x86100008 /* 10.3125 GHz clock net */ +#define SLOW_PLLS_CR5 0x00001000 /* PROTO_SEL=01010b (XFI/10GBase-R/40GBase-R) + IF_WIDTH=010b (20-bit) */ #define F40G_GCR0_MASTER 0x00000052 #define F40G_GCR0_SLAVE 0x00010052 /* LNmTGCR0/LNmRGCR0: USE_SLOW_PLL=1 (bit 28), BY_N_RATE_SEL=000 full */ -#define F40G_LANE_GCR 0x10000000 +#define SLOW_LANE_GCR 0x10000000 #define F40G_PCCE 0x11000000 /* E40Ga+E40Gb on, E100G off */ static bool srds1_is_40g(unsigned long base) @@ -465,18 +465,14 @@ bool lx2160a_serdes1_is_40g(void) return srds1_is_40g(srds_base(FSL_SRDS_1)); } -/* - * Bring PLLS up at the 40GE clock net. Nothing uses it yet, so this - * cannot disturb the running 100GE lanes on PLLF. - */ -static int srds1_plls_up_40g(unsigned long base) +static int srds1_plls_up_slow(unsigned long base) { - unsigned long rstctl = SRDS_PLLnRSTCTL(base, F40G_PLLS); + unsigned long rstctl = SRDS_PLLnRSTCTL(base, SRDS_PLLS); u32 v; - out_le32((void __iomem *)SRDS_PLLnCR0(base, F40G_PLLS), F40G_PLLS_CR0); - out_le32((void __iomem *)SRDS_PLLnCR1(base, F40G_PLLS), F40G_PLLS_CR1); - out_le32((void __iomem *)SRDS_PLLnCR5(base, F40G_PLLS), F40G_PLLS_CR5); + out_le32((void __iomem *)SRDS_PLLnCR0(base, SRDS_PLLS), SLOW_PLLS_CR0); + out_le32((void __iomem *)SRDS_PLLnCR1(base, SRDS_PLLS), SLOW_PLLS_CR1); + out_le32((void __iomem *)SRDS_PLLnCR5(base, SRDS_PLLS), SLOW_PLLS_CR5); out_le32((void __iomem *)rstctl, 0); out_le32((void __iomem *)rstctl, RSTCTL_RST_REQ); @@ -504,7 +500,7 @@ static int srds1_plls_up_40g(unsigned long base) int lx2160a_serdes1_to_40g(void) { unsigned long base = srds_base(FSL_SRDS_1); - unsigned long pllf = SRDS_PLLnRSTCTL(base, F40G_PLLF); + unsigned long pllf = SRDS_PLLnRSTCTL(base, SRDS_PLLF); bool master[SRDS_LANES]; int m, ret = 0; @@ -528,7 +524,7 @@ int lx2160a_serdes1_to_40g(void) master[m] = !(in_le32((void __iomem *)SRDS_LNmGCR0(base, m)) & GCR0_PORT_LN0_B); - ret = srds1_plls_up_40g(base); + ret = srds1_plls_up_slow(base); if (ret) return ret; @@ -546,16 +542,16 @@ int lx2160a_serdes1_to_40g(void) for (m = 0; m < SRDS_LANES; m++) { out_le32((void __iomem *)SRDS_LNmGCR0(base, m), master[m] ? F40G_GCR0_MASTER : F40G_GCR0_SLAVE); - out_le32((void __iomem *)SRDS_LNmTGCR0(base, m), F40G_LANE_GCR); - out_le32((void __iomem *)SRDS_LNmRGCR0(base, m), F40G_LANE_GCR); + out_le32((void __iomem *)SRDS_LNmTGCR0(base, m), SLOW_LANE_GCR); + out_le32((void __iomem *)SRDS_LNmRGCR0(base, m), SLOW_LANE_GCR); } out_le32((void __iomem *)SRDS_PCCE(base), F40G_PCCE); /* Every lane now selects PLLS, so one PLL reset restarts them all. */ - out_le32((void __iomem *)SRDS_PLLnRSTCTL(base, F40G_PLLS), + out_le32((void __iomem *)SRDS_PLLnRSTCTL(base, SRDS_PLLS), RSTCTL_RST_REQ); - if (poll_clear(SRDS_PLLnRSTCTL(base, F40G_PLLS), RSTCTL_RST_REQ)) { + if (poll_clear(SRDS_PLLnRSTCTL(base, SRDS_PLLS), RSTCTL_RST_REQ)) { printf("lx2160a serdes: SD1 PLLS re-reset did not complete within %d us\n", SPLIT_POLL_US); ret = -ETIMEDOUT; @@ -592,11 +588,11 @@ int lx2160a_serdes1_to_40g(void) * 40GE lane LNmGCR0 = 0x00010052 LNmTGCR0 = 0x10000000 * ^ PORT_LN0_B */ -#define X10G_FIRST_LANE 4 -#define X10G_LAST_LANE 7 -#define X10G_GCR0 0x00000052 /* as 40GE, but own port master */ +#define XFI_FIRST_LANE 4 +#define XFI_LAST_LANE 7 +#define XFI_GCR0 0x00000052 /* as 40GE, but own port master */ #define X10G_PCCE 0x01000000 /* E40Gb kept (lanes 0-3) */ -#define X10G_PCCC 0x88889999 /* XFI converters for lanes 4-7 */ +#define XFI_PCCC 0x88889999 /* XFI converters for lanes 4-7 */ static bool srds1_is_40g_4x10g(unsigned long base) { @@ -636,14 +632,14 @@ int lx2160a_serdes1_40g_4x10g(void) */ /* The kept group's master, recorded before anything is halted. */ for (m = 0; m < SRDS_LANES; m++) { - if (m >= X10G_FIRST_LANE && m <= X10G_LAST_LANE) + if (m >= XFI_FIRST_LANE && m <= XFI_LAST_LANE) continue; if (!(in_le32((void __iomem *)SRDS_LNmGCR0(base, m)) & GCR0_PORT_LN0_B)) keep_master = m; } - for (m = X10G_FIRST_LANE; m <= X10G_LAST_LANE; m++) { + for (m = XFI_FIRST_LANE; m <= XFI_LAST_LANE; m++) { if (!(in_le32((void __iomem *)SRDS_LNmGCR0(base, m)) & GCR0_PORT_LN0_B)) master = m; @@ -665,14 +661,14 @@ int lx2160a_serdes1_40g_4x10g(void) ret = -ETIMEDOUT; } - for (m = X10G_FIRST_LANE; m <= X10G_LAST_LANE; m++) - out_le32((void __iomem *)SRDS_LNmGCR0(base, m), X10G_GCR0); + for (m = XFI_FIRST_LANE; m <= XFI_LAST_LANE; m++) + out_le32((void __iomem *)SRDS_LNmGCR0(base, m), XFI_GCR0); out_le32((void __iomem *)SRDS_PCCE(base), X10G_PCCE); - out_le32((void __iomem *)SRDS_PCCC(base), X10G_PCCC); + out_le32((void __iomem *)SRDS_PCCC(base), XFI_PCCC); /* Reset BOTH halves, not just the four lanes whose GCR0 changed */ - for (m = X10G_FIRST_LANE; m <= X10G_LAST_LANE; m++) { + for (m = XFI_FIRST_LANE; m <= XFI_LAST_LANE; m++) { out_le32((void __iomem *)SRDS_LNmTRSTCTL(base, m), RSTCTL_RST_REQ); out_le32((void __iomem *)SRDS_LNmRRSTCTL(base, m), @@ -680,7 +676,7 @@ int lx2160a_serdes1_40g_4x10g(void) } for (m = 0; m < SRDS_LANES; m++) { - if (m >= X10G_FIRST_LANE && m <= X10G_LAST_LANE) + if (m >= XFI_FIRST_LANE && m <= XFI_LAST_LANE) continue; if (m == keep_master) continue; @@ -733,22 +729,187 @@ int lx2160a_serdes1_40g_4x10g(void) return ret; } +/* 2x 100GE -> 1x 100GE + 4x XFI (10G): SD1 with 2 differents PLLF and PLLS */ +#define M100G_PCCE SPLIT_PCCE /* E100Gb kept (lanes 0-3) */ + +static bool srds1_is_100g_4x10g(unsigned long base) +{ + u32 pccc = in_le32((void __iomem *)SRDS_PCCC(base)); + u32 pcce = in_le32((void __iomem *)SRDS_PCCE(base)); + + /* + * Disjoint from every other predicate here by construction: the 4x + * 25G split matches the same PCCE but leaves PCCC alone, and both + * 40GE modes live in PCCE's E40G fields rather than its E100G ones. + */ + return PCCE_E100GB_CFG(pcce) != 0 && + PCCE_E100GA_CFG(pcce) == 0 && + (pccc & 0xffff) == 0x9999; +} + +bool lx2160a_serdes1_is_100g_4x10g(void) +{ + return srds1_is_100g_4x10g(srds_base(FSL_SRDS_1)); +} + +int lx2160a_serdes1_100g_4x10g(void) +{ + unsigned long base = srds_base(FSL_SRDS_1); + int m, master = -1, keep_master = -1, ret = 0; + + if (srds1_is_100g_4x10g(base)) { + debug("lx2160a serdes: SD1 already 1x 100GE + 4x 10G\n"); + return 0; + } + + if (!srds1_is_unsplit(base)) { + printf("lx2160a serdes: SD1 is not in the 2x 100GE state (PCCC=%08x PCCD=%08x PCCE=%08x), refusing\n", + in_le32((void __iomem *)SRDS_PCCC(base)), + in_le32((void __iomem *)SRDS_PCCD(base)), + in_le32((void __iomem *)SRDS_PCCE(base))); + return -EINVAL; + } + + /* The kept 100GE group's master, recorded before anything halts. */ + for (m = 0; m < SRDS_LANES; m++) { + if (m >= XFI_FIRST_LANE && m <= XFI_LAST_LANE) + continue; + if (!(in_le32((void __iomem *)SRDS_LNmGCR0(base, m)) & + GCR0_PORT_LN0_B)) + keep_master = m; + } + + ret = srds1_plls_up_slow(base); + if (ret) + return ret; + + /* + * Halt only the half that moves. PLLF is NOT halted here, which is + * the whole point of this mode and the one place it diverges from + * lx2160a_serdes1_to_40g(): halting PLLF would put the 100GE lanes + * into reset too, and they are meant to keep running. + * + * As everywhere else, only the port master's request can clear, so + * issue all four and wait on that one. + */ + for (m = XFI_FIRST_LANE; m <= XFI_LAST_LANE; m++) { + if (!(in_le32((void __iomem *)SRDS_LNmGCR0(base, m)) & + GCR0_PORT_LN0_B)) + master = m; + out_le32((void __iomem *)SRDS_LNmTRSTCTL(base, m), + RSTCTL_HLT_REQ); + out_le32((void __iomem *)SRDS_LNmRRSTCTL(base, m), + RSTCTL_HLT_REQ); + } + + if (master < 0) { + printf("lx2160a serdes: no master lane among the lanes to split, refusing\n"); + return -EINVAL; + } + + if (poll_clear(SRDS_LNmTRSTCTL(base, master), RSTCTL_HLT_REQ) || + poll_clear(SRDS_LNmRRSTCTL(base, master), RSTCTL_HLT_REQ)) { + printf("lx2160a serdes: lane %d did not halt within %d us, continuing\n", + master, SPLIT_POLL_US); + ret = -ETIMEDOUT; + } + + /* + * Four one-lane XFI ports on PLLS. The lanes' TGCR0/RGCR0 have to + * move too -- unlike the 4x 25G split, where the rate does not + * change and only the port grouping does. + */ + for (m = XFI_FIRST_LANE; m <= XFI_LAST_LANE; m++) { + out_le32((void __iomem *)SRDS_LNmGCR0(base, m), XFI_GCR0); + out_le32((void __iomem *)SRDS_LNmTGCR0(base, m), SLOW_LANE_GCR); + out_le32((void __iomem *)SRDS_LNmRGCR0(base, m), SLOW_LANE_GCR); + } + + out_le32((void __iomem *)SRDS_PCCE(base), M100G_PCCE); + out_le32((void __iomem *)SRDS_PCCC(base), XFI_PCCC); + + /* Reset both halves, slaves first and the kept group's master last */ + for (m = XFI_FIRST_LANE; m <= XFI_LAST_LANE; m++) { + out_le32((void __iomem *)SRDS_LNmTRSTCTL(base, m), + RSTCTL_RST_REQ); + out_le32((void __iomem *)SRDS_LNmRRSTCTL(base, m), + RSTCTL_RST_REQ); + } + + for (m = 0; m < SRDS_LANES; m++) { + if (m >= XFI_FIRST_LANE && m <= XFI_LAST_LANE) + continue; + if (m == keep_master) + continue; + out_le32((void __iomem *)SRDS_LNmTRSTCTL(base, m), + RSTCTL_RST_REQ); + out_le32((void __iomem *)SRDS_LNmRRSTCTL(base, m), + RSTCTL_RST_REQ); + } + + if (keep_master >= 0) { + out_le32((void __iomem *)SRDS_LNmTRSTCTL(base, keep_master), + RSTCTL_RST_REQ); + out_le32((void __iomem *)SRDS_LNmRRSTCTL(base, keep_master), + RSTCTL_RST_REQ); + } else { + printf("lx2160a serdes: no master lane in the kept 100GE group, it was not reset\n"); + ret = -EINVAL; + } + + for (m = 0; m < SRDS_LANES; m++) { + u32 t, r; + + if (poll_clear(SRDS_LNmTRSTCTL(base, m), RSTCTL_RST_REQ) || + poll_clear(SRDS_LNmRRSTCTL(base, m), RSTCTL_RST_REQ)) { + printf("lx2160a serdes: lane %d reset did not complete within %d us\n", + m, SPLIT_POLL_US); + ret = -ETIMEDOUT; + continue; + } + + t = in_le32((void __iomem *)SRDS_LNmTRSTCTL(base, m)); + r = in_le32((void __iomem *)SRDS_LNmRRSTCTL(base, m)); + if (!(t & RSTCTL_RST_DONE) || !(r & RSTCTL_RST_DONE)) { + printf("lx2160a serdes: lane %d reset finished without RST_DONE (TRSTCTL=%08x RRSTCTL=%08x)\n", + m, t, r); + ret = -EIO; + } + } + + if (!srds1_is_100g_4x10g(base)) { + printf("lx2160a serdes: 100G + 4x 10G conversion did not take (PCCC=%08x PCCE=%08x)\n", + in_le32((void __iomem *)SRDS_PCCC(base)), + in_le32((void __iomem *)SRDS_PCCE(base))); + return -EIO; + } + + printf("lx2160a serdes: SD1 now 1x 100GE (dpmac.2, PLLF) + 4x 10G (dpmac.3-6, PLLS)%s\n", + ret ? " (with warnings above)" : ""); + printf("lx2160a serdes: the two retimers need different CDR rates -- U21 25.78125, U25 10.3125 Gbd\n"); + + return ret; +} + /* * setenv hwconfig serdes1:100g4x25g * setenv hwconfig serdes1:2x40g + * setenv hwconfig serdes1:40g4x10g + * setenv hwconfig serdes1:100g4x10g * * Idempotent: a board whose PBI already split SD1 skip it too. * - * The two are mutually exclusive -- one re-points lanes 4-7 to 25G on - * PLLF, the other moves all eight to 40GE on PLLS -- so `split` wins - * and `40g` is refused alongside it rather than layered on top. + * All four are mutually exclusive, they are target STATES of the same + * eight lanes, not steps to be combined. So the first match wins and + * the rest are reported and ignored rather than layered on top. */ void lx2160a_serdes_apply_hwconfig(void) { if (hwconfig_sub("serdes1", "100g4x25g")) { if (hwconfig_sub("serdes1", "2x40g") || - hwconfig_sub("serdes1", "40g4x10g")) - printf("lx2160a serdes: hwconfig asks for serdes1:100g4x25g alongside a 40GE mode, doing the split only\n"); + hwconfig_sub("serdes1", "40g4x10g") || + hwconfig_sub("serdes1", "100g4x10g")) + printf("lx2160a serdes: hwconfig asks for serdes1:100g4x25g alongside another mode, doing the split only\n"); if (lx2160a_serdes1_is_split()) return; @@ -756,6 +917,19 @@ void lx2160a_serdes_apply_hwconfig(void) return; } + /* + * serdes1:100g4x10g keeps lanes 0-3 as the 100GE the RCW built and + * moves only lanes 4-7 to 10G, so it is a single step from the + * protocol-13 state, no 40GE detour, unlike serdes1:40g4x10g below. + */ + if (hwconfig_sub("serdes1", "100g4x10g")) { + if (lx2160a_serdes1_is_100g_4x10g()) + return; + + lx2160a_serdes1_100g_4x10g(); + return; + } + /* * serdes1:40g4x10g is a target MODE, not a second step the user has * to sequence by hand: it implies the 40GE conversion first, @@ -853,6 +1027,22 @@ static int do_lx2160_sd1(struct cmd_tbl *cmdtp, int flag, return CMD_RET_SUCCESS; } + if (!strcmp(argv[1], "100g4x10g")) { + if (!IS_ENABLED(CONFIG_ARCH_LX2160A)) { + printf("no 100GE on SerDes 1 to split on this SoC\n"); + return CMD_RET_FAILURE; + } + if (argc != 2) + return CMD_RET_USAGE; + if (lx2160a_serdes1_is_100g_4x10g()) { + printf("SD1 is already 1x 100GE + 4x 10G, nothing to do\n"); + return CMD_RET_SUCCESS; + } + if (lx2160a_serdes1_100g_4x10g()) + return CMD_RET_FAILURE; + return CMD_RET_SUCCESS; + } + return CMD_RET_USAGE; } @@ -899,7 +1089,12 @@ static int do_lx2160_serdes(struct cmd_tbl *cmdtp, int flag, " Undo with `reset`. Permanent: setenv hwconfig serdes1:2x40g\n" \ "lx2160_sd1 40g4x10g\n" \ " - split SD1's dpmac.1 40GE port into 4x 10G,\n" \ - " Permanent: setenv hwconfig serdes1:40g4x10g (implies 2x40g)" + " Permanent: setenv hwconfig serdes1:40g4x10g (implies 2x40g)\n" \ + "lx2160_sd1 100g4x10g\n" \ + " - keep dpmac.2 as 100GE on lanes 0-3 (PLLF, 25.78125 Gbaud)\n" \ + " and make lanes 4-7 four 10G ports (dpmac.3-6) on PLLS at\n" \ + " 10.3125 Gbaud.\n" \ + " Permanent: setenv hwconfig serdes1:100g4x10g" #else #define SD1_CMD_HELP \ " - no SerDes 1 Ethernet modes on this SoC" diff --git a/board/nxp/lx2160a/serdes_split.h b/board/nxp/lx2160a/serdes_split.h index 1b65848db2f..adacedb5058 100644 --- a/board/nxp/lx2160a/serdes_split.h +++ b/board/nxp/lx2160a/serdes_split.h @@ -45,6 +45,17 @@ bool lx2160a_serdes1_is_40g_4x10g(void); */ int lx2160a_serdes1_40g_4x10g(void); +/* Is SerDes 1 already 1x 100GE + 4x 10G? */ +bool lx2160a_serdes1_is_100g_4x10g(void); + +/* + * Keep lanes 0-3 as the 100GE port and change the lanes 4-7 + * into 4x 10G ports (dpmac.3-6). + * + * It refuses unless SD1 is in the 2x 100GE state + */ +int lx2160a_serdes1_100g_4x10g(void); + void lx2160a_serdes_apply_hwconfig(void); #endif /* __LX2160A_SERDES_SPLIT_H */ -- 2.43.0
