On Wed, Sep 16, 2026 at 11:15:47PM +0200, Vincent Jardin wrote: > lx2160_sd1 40g4x10g, hwconfig=serdes1:40g4x10g. > > XFI LNmGCR0 = 0x00000052 LNmTGCR0 = 0x10000000 > 40GE LNmGCR0 = 0x00010052 LNmTGCR0 = 0x10000000 > ^ PORT_LN0_B > > same PROTO_SEL (0x0a spans XFI and 40GBase-R), same 20-bit width, same > USE_SLOW_PLL, same BY_N_RATE_SEL. > > It is inspired from the 100G+4x25G split, but using PLLS. > > Signed-off-by: Vincent Jardin <[email protected]> > --- > > (no changes since v1) > > board/nxp/lx2160a/serdes_split.c | 191 ++++++++++++++++++++++++++++++- > board/nxp/lx2160a/serdes_split.h | 11 ++ > 2 files changed, 199 insertions(+), 3 deletions(-) > > diff --git a/board/nxp/lx2160a/serdes_split.c > b/board/nxp/lx2160a/serdes_split.c > index 0f30019e20a..f48c882aa22 100644 > --- a/board/nxp/lx2160a/serdes_split.c > +++ b/board/nxp/lx2160a/serdes_split.c > @@ -585,6 +585,154 @@ int lx2160a_serdes1_to_40g(void) > return ret; > } > > +/* > + * 2x 40GE -> 1x 40GE + 4x XFI (10G), keeping the QSFP group intact. > + * > + * XFI lane LNmGCR0 = 0x00000052 LNmTGCR0 = 0x10000000 > + * 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 X10G_PCCE 0x01000000 /* E40Gb kept (lanes 0-3) */ > +#define X10G_PCCC 0x88889999 /* XFI converters for lanes 4-7 > */ > + > +static bool srds1_is_40g_4x10g(unsigned long base) > +{ > + u32 pccc = in_le32((void __iomem *)SRDS_PCCC(base)); > + u32 pcce = in_le32((void __iomem *)SRDS_PCCE(base)); > + > + return PCCE_E40GB_CFG(pcce) != 0 && > + PCCE_E40GA_CFG(pcce) == 0 && > + (pccc & 0xffff) == 0x9999; > +} > + > +bool lx2160a_serdes1_is_40g_4x10g(void) > +{ > + return srds1_is_40g_4x10g(srds_base(FSL_SRDS_1)); > +} > + > +int lx2160a_serdes1_40g_4x10g(void) > +{ > + unsigned long base = srds_base(FSL_SRDS_1); > + int m, master = -1, keep_master = -1, ret = 0; > + > + if (srds1_is_40g_4x10g(base)) { > + debug("lx2160a serdes: SD1 already 1x 40GE + 4x 10G\n"); > + return 0; > + } > + > + if (!srds1_is_40g(base)) { > + printf("lx2160a serdes: SD1 is not in the 2x 40GE state > (PCCC=%08x PCCE=%08x), refusing\n", > + in_le32((void __iomem *)SRDS_PCCC(base)), > + in_le32((void __iomem *)SRDS_PCCE(base))); > + return -EINVAL; > + } > + > + /* > + * Halt. As in the 25G split, only the port master's request can > + * clear, so issue all four and wait on that one. > + */ > + /* 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) > + 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++) { > + 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 in the dpmac.1 group, > 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; > + } > + > + for (m = X10G_FIRST_LANE; m <= X10G_LAST_LANE; m++) > + out_le32((void __iomem *)SRDS_LNmGCR0(base, m), X10G_GCR0); > + > + out_le32((void __iomem *)SRDS_PCCE(base), X10G_PCCE); > + out_le32((void __iomem *)SRDS_PCCC(base), X10G_PCCC); > + > + /* Reset BOTH halves, not just the four lanes whose GCR0 changed */ > + for (m = X10G_FIRST_LANE; m <= X10G_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 >= X10G_FIRST_LANE && m <= X10G_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 40GE 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_40g_4x10g(base)) { > + printf("lx2160a serdes: 4x 10G split 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 40GE (dpmac.2) + 4x 10G > (dpmac.3-6)%s\n", > + ret ? " (with warnings above)" : ""); > + > + return ret; > +} > + > /* > * setenv hwconfig serdes1:100g4x25g > * setenv hwconfig serdes1:2x40g > @@ -598,8 +746,9 @@ int lx2160a_serdes1_to_40g(void) > void lx2160a_serdes_apply_hwconfig(void) > { > if (hwconfig_sub("serdes1", "100g4x25g")) { > - if (hwconfig_sub("serdes1", "2x40g")) > - printf("lx2160a serdes: hwconfig asks for both > serdes1:100g4x25g and serdes1:2x40g, doing the split only\n"); > + 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"); > if (lx2160a_serdes1_is_split()) > return; > > @@ -607,6 +756,23 @@ void lx2160a_serdes_apply_hwconfig(void) > return; > } > > + /* > + * serdes1:40g4x10g is a target MODE, not a second step the user has > + * to sequence by hand: it implies the 40GE conversion first, > + * because 1x 40GE + 4x 10G is reached by splitting one of the two > + * 40GE ports. > + */ > + if (hwconfig_sub("serdes1", "40g4x10g")) { > + if (lx2160a_serdes1_is_40g_4x10g()) > + return; > + > + if (!lx2160a_serdes1_is_40g() && lx2160a_serdes1_to_40g()) > + return; > + > + lx2160a_serdes1_40g_4x10g(); > + return; > + } > + > if (!hwconfig_sub("serdes1", "2x40g")) > return; > > @@ -671,6 +837,22 @@ static int do_lx2160_sd1(struct cmd_tbl *cmdtp, int flag, > return CMD_RET_SUCCESS; > } > > + if (!strcmp(argv[1], "40g4x10g")) { > + if (!IS_ENABLED(CONFIG_ARCH_LX2160A)) { > + printf("no 40GE on SerDes 1 to split on this SoC\n"); > + return CMD_RET_FAILURE; > + } > + if (argc != 2) > + return CMD_RET_USAGE; > + if (lx2160a_serdes1_is_40g_4x10g()) { > + printf("SD1 is already 1x 40GE + 4x 10G, nothing to > do\n"); > + return CMD_RET_SUCCESS; > + } > + if (lx2160a_serdes1_40g_4x10g()) > + return CMD_RET_FAILURE; > + return CMD_RET_SUCCESS; > + } > + > return CMD_RET_USAGE; > } > > @@ -714,7 +896,10 @@ static int do_lx2160_serdes(struct cmd_tbl *cmdtp, int > flag, > " - convert SD1 2x 100GE into 2x 40GE without touching the RCW.\n" > \ > " Must run before the MC starts, and the DPC must carry\n" > \ > " board_info/serdes/follow_hw_pssr: RCWSR still reads 13.\n" > \ > - " Undo with `reset`. Permanent: setenv hwconfig serdes1:2x40g" > + " 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)"
I'll add here a comment that applies to the entire lx2160_sd1 command, not specific to this hunk. I see it as a huge foot gun. If the MC firmware has already booted, you don't want to allow any further SerDes lane splitting/unsplitting, because as you say, the follow_hw_pssr logic in the MC has already run. Combined with the fact that the mcinitcmd env variable is executed by mc_env_boot() before the shell even loads, and by default it boots the MC firmware already, I think this is just asking for problems. You never warn the user that the MC firmware was already loaded. I don't really see a practical use case for the lx2160_sd1 command. Would it work for your board to just reset the system after you setenv hwconfig && saveenv? > #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 36d486d7301..1b65848db2f 100644 > --- a/board/nxp/lx2160a/serdes_split.h > +++ b/board/nxp/lx2160a/serdes_split.h > @@ -34,6 +34,17 @@ bool lx2160a_serdes1_is_40g(void); > */ > int lx2160a_serdes1_to_40g(void); > > +/* Is SerDes 1 already 1x 40GE + 4x 10G? */ > +bool lx2160a_serdes1_is_40g_4x10g(void); > + > +/* > + * Split SerDes 1's dpmac.1 40GE port into 4x 10G (dpmac.3-6), > + * keeping dpmac.2 as one 40GE port. > + * > + * Refuses unless SD1 is in the 2x 40GE state. > + */ > +int lx2160a_serdes1_40g_4x10g(void); > + You don't need to export to serdes_split.h functions which are called exclusively from serdes_split.c. This applies to the vast majority of the functions which are already present here. Also, a general comment regarding code organization: have you considered a structured approach with SerDes configurations (aka array of structures with function pointers), carrying their - name - detection method - method to verify whether the initial conditions are fulfilled - method to switch to it ? As an alternative to this ad-hoc lx2160a_serdes1_is_CONFIG_X() and lx2160a_serdes1_CONFIG_X() organization scheme. > void lx2160a_serdes_apply_hwconfig(void); > > #endif /* __LX2160A_SERDES_SPLIT_H */ > -- > 2.43.0 >
