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

Reply via email to