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
>

Reply via email to