The SD1 lane configuration can support many cases, not just the
1x100GE + 4x25G split.

Let's describe each one as an entry of a table.
Then, let's check the serdes1:<mode> of hwconfig.

The master-lane lookup, the halt and the reset of the split become
helpers.

Suggested-by: Vladimir Oltean <[email protected]>
Signed-off-by: Vincent Jardin <[email protected]>

---

Changes in v4:
- New patch

 board/nxp/lx2160a/serdes_split.c | 272 +++++++++++++++++++++----------
 board/nxp/lx2160a/serdes_split.h |  16 +-
 2 files changed, 192 insertions(+), 96 deletions(-)

diff --git a/board/nxp/lx2160a/serdes_split.c b/board/nxp/lx2160a/serdes_split.c
index 1a5c44e7740..11bec35fd22 100644
--- a/board/nxp/lx2160a/serdes_split.c
+++ b/board/nxp/lx2160a/serdes_split.c
@@ -12,6 +12,7 @@
 #include <asm/arch/fsl_serdes.h>
 #include <asm/arch/immap_lsch3.h>
 #include <asm/arch/soc.h>              /* gur_in32() picks the GUR endianness 
*/
+#include <linux/bitmap.h>
 #include <linux/kernel.h>
 #include <linux/string.h>
 #include <linux/delay.h>
@@ -245,16 +246,22 @@ static void print_rcw_view(int sd)
 
 #if defined(CONFIG_ARCH_LX2160A)
 
-/*
- * The split itself.
- *
- * This is NXP's e100g1_split.rcw sequence, moved from the PBI into
- * U-Boot.
- */
-#define SPLIT_FIRST_LANE       4       /* LNE */
-#define SPLIT_LAST_LANE                7       /* LNH */
+struct sd1_mode {
+       const char *name;                       /* hwconfig serdes1:<name> */
+       const char *desc;
+       const char *from;
+       bool (*is_active)(unsigned long base);
+       int (*enter)(unsigned long base);
+};
+
+#define SD1_LNE_LNH            GENMASK(7, 4)
+
+#define SRDS_POLL_US           10000           /* NXP waits ~100 cycles   */
 
 /*
+ * 1x 100GE + 4x 25G. This is NXP's e100g1_split.rcw sequence, adapted
+ * from the PBI sequence to U-Boot.
+ *
  * PROTO_SEL=11010b (25G/50G/100G, shared) + IF_WIDTH=100b (40-bit),
  * PORT_RST_LEFT=0 and PORT_LN0_B=0 so every lane becomes its own
  * single-lane port. Only those last two bits actually change: the
@@ -264,31 +271,24 @@ static void print_rcw_view(int sd)
 #define SPLIT_PCCE             0x00000100      /* E100Ga off, E100Gb on   */
 #define SPLIT_PCCD             0x11110000      /* E25Ga..E25Gd on         */
 
-#define SPLIT_POLL_US          10000           /* NXP waits ~100 cycles   */
-
-static bool srds1_split_state(unsigned long base)
+static bool sd1_is_2x100g(unsigned long base)
 {
        u32 pccd = in_le32((void __iomem *)SRDS_PCCD(base));
        u32 pcce = in_le32((void __iomem *)SRDS_PCCE(base));
 
-       return PCCE_E100GA_CFG(pcce) == 0 &&
-              PCCD_E25G_CFG(pccd, 0) && PCCD_E25G_CFG(pccd, 1) &&
-              PCCD_E25G_CFG(pccd, 2) && PCCD_E25G_CFG(pccd, 3);
-}
-
-bool lx2160a_serdes1_is_split(void)
-{
-       return srds1_split_state(srds_base(FSL_SRDS_1));
+       return PCCE_E100GA_CFG(pcce) != 0 && PCCE_E100GB_CFG(pcce) != 0 &&
+              PCCD_E25G_CFG(pccd, 0) == 0 && PCCD_E25G_CFG(pccd, 1) == 0 &&
+              PCCD_E25G_CFG(pccd, 2) == 0 && PCCD_E25G_CFG(pccd, 3) == 0;
 }
 
-static bool srds1_is_unsplit(unsigned long base)
+static bool sd1_is_100g4x25g(unsigned long base)
 {
        u32 pccd = in_le32((void __iomem *)SRDS_PCCD(base));
        u32 pcce = in_le32((void __iomem *)SRDS_PCCE(base));
 
-       return PCCE_E100GA_CFG(pcce) != 0 && PCCE_E100GB_CFG(pcce) != 0 &&
-              PCCD_E25G_CFG(pccd, 0) == 0 && PCCD_E25G_CFG(pccd, 1) == 0 &&
-              PCCD_E25G_CFG(pccd, 2) == 0 && PCCD_E25G_CFG(pccd, 3) == 0;
+       return PCCE_E100GA_CFG(pcce) == 0 &&
+              PCCD_E25G_CFG(pccd, 0) && PCCD_E25G_CFG(pccd, 1) &&
+              PCCD_E25G_CFG(pccd, 2) && PCCD_E25G_CFG(pccd, 3);
 }
 
 /*
@@ -298,7 +298,7 @@ static bool srds1_is_unsplit(unsigned long base)
  */
 static int poll_clear(unsigned long reg, u32 bit)
 {
-       int us = SPLIT_POLL_US;
+       int us = SRDS_POLL_US;
 
        while (us--) {
                if (!(in_le32((void __iomem *)reg) & bit))
@@ -309,79 +309,82 @@ static int poll_clear(unsigned long reg, u32 bit)
        return -ETIMEDOUT;
 }
 
-int lx2160a_serdes1_split(void)
+static int sd1_port_master(unsigned long base, unsigned long lanes)
 {
-       unsigned long base = srds_base(FSL_SRDS_1);
-       int m, master = -1, ret = 0;
-
-       if (srds1_split_state(base)) {
-               debug("lx2160a serdes: SD1 already split, nothing to do\n");
-               return 0;
-       }
+       int m, master = -1;
 
-       if (!srds1_is_unsplit(base)) {
-               printf("lx2160a serdes: SD1 is in neither the split nor the 
unsplit state (PCCD=%08x PCCE=%08x), refusing\n",
-                      in_le32((void __iomem *)SRDS_PCCD(base)),
-                      in_le32((void __iomem *)SRDS_PCCE(base)));
-               return -EINVAL;
-       }
-
-       /*
-        * Halt. "Halt only has meaning on the master source
-        * clock lane of a port (LNmGCR0[PORT_LN0_B]=0)". Lanes E-H are one
-        * 100G port at this point, so exactly one of them is the master and
-        * the other three requests are ignored. Issue all four anyway
-        * (that is what NXP's sequence does) but only wait on the lane
-        * where the bit can actually clear.
-        */
-       for (m = SPLIT_FIRST_LANE; m <= SPLIT_LAST_LANE; m++) {
+       for_each_set_bit(m, &lanes, SRDS_LANES)
                if (!(in_le32((void __iomem *)SRDS_LNmGCR0(base, m)) &
                      GCR0_PORT_LN0_B))
                        master = m;
+
+       if (master < 0)
+               printf("lx2160a serdes: no master lane among LN%c-LN%c, 
refusing\n",
+                      'A' + (int)__ffs(lanes), 'A' + (int)__fls(lanes));
+
+       return master;
+}
+
+/*
+ * Halt the port made of @lanes.
+ *
+ * "Halt only has meaning on the master source clock lane of a port
+ * (LNmGCR0[PORT_LN0_B]=0)", so the request goes to every lane, as
+ * NXP's sequence does, and only the master is waited on:
+ * it is the one lane where the bit can clear.
+ *
+ *  -EINVAL means nothing was touched
+ *  -ETIMEDOUT is only a warning
+ */
+static int sd1_halt_port(unsigned long base, unsigned long lanes)
+{
+       int m, master = sd1_port_master(base, lanes);
+
+       if (master < 0)
+               return -EINVAL;
+
+       for_each_set_bit(m, &lanes, SRDS_LANES) {
                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 LNE-LNH, 
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: LN%c did not halt within %d us, 
continuing\n",
-                      'A' + master, SPLIT_POLL_US);
-               ret = -ETIMEDOUT;
+                      'A' + master, SRDS_POLL_US);
+               return -ETIMEDOUT;
        }
 
-       /*
-        * Reconfigure while halted: four one-lane ports instead of one
-        * four-lane port. Everything else about the lane is unchanged.
-        */
-       for (m = SPLIT_FIRST_LANE; m <= SPLIT_LAST_LANE; m++)
-               out_le32((void __iomem *)SRDS_LNmGCR0(base, m), SPLIT_GCR0);
+       return 0;
+}
 
-       /* Re-point the protocol converters. */
-       out_le32((void __iomem *)SRDS_PCCE(base), SPLIT_PCCE);
-       out_le32((void __iomem *)SRDS_PCCD(base), SPLIT_PCCD);
+static void sd1_reset_req(unsigned long base, unsigned long lanes, int skip)
+{
+       int m;
 
-       /* Reset. Every lane is its own master now, so all four count. */
-       for (m = SPLIT_FIRST_LANE; m <= SPLIT_LAST_LANE; m++) {
+       for_each_set_bit(m, &lanes, SRDS_LANES) {
+               if (m == skip)
+                       continue;
                out_le32((void __iomem *)SRDS_LNmTRSTCTL(base, m),
                         RSTCTL_RST_REQ);
                out_le32((void __iomem *)SRDS_LNmRRSTCTL(base, m),
                         RSTCTL_RST_REQ);
        }
+}
+
+static int sd1_reset_wait(unsigned long base, unsigned long lanes)
+{
+       int m, ret = 0;
 
-       for (m = SPLIT_FIRST_LANE; m <= SPLIT_LAST_LANE; m++) {
+       for_each_set_bit(m, &lanes, SRDS_LANES) {
                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: LN%c reset did not complete 
within %d us\n",
-                              'A' + m, SPLIT_POLL_US);
+                              'A' + m, SRDS_POLL_US);
                        ret = -ETIMEDOUT;
                        continue;
                }
@@ -395,33 +398,138 @@ int lx2160a_serdes1_split(void)
                }
        }
 
-       if (!srds1_split_state(base)) {
-               printf("lx2160a serdes: split did not take (PCCD=%08x 
PCCE=%08x)\n",
-                      in_le32((void __iomem *)SRDS_PCCD(base)),
-                      in_le32((void __iomem *)SRDS_PCCE(base)));
-               return -EIO;
+       return ret;
+}
+
+static int sd1_enter_100g4x25g(unsigned long base)
+{
+       unsigned long lanes = SD1_LNE_LNH;
+       int m, ret, err;
+
+       ret = sd1_halt_port(base, lanes);
+       if (ret == -EINVAL)
+               return ret;
+
+       /*
+        * Reconfigure while halted: four 1-lane ports instead of one
+        * 4-lane port. Everything else about the lane is unchanged.
+        */
+       for_each_set_bit(m, &lanes, SRDS_LANES)
+               out_le32((void __iomem *)SRDS_LNmGCR0(base, m), SPLIT_GCR0);
+
+       /* Re-point the protocol converters. */
+       out_le32((void __iomem *)SRDS_PCCE(base), SPLIT_PCCE);
+       out_le32((void __iomem *)SRDS_PCCD(base), SPLIT_PCCD);
+
+       /* Reset. Every lane is its own master now, so all four count. */
+       sd1_reset_req(base, lanes, -1);
+       err = sd1_reset_wait(base, lanes);
+
+       return err ? err : ret;
+}
+
+static const struct sd1_mode sd1_modes[] = {
+       {
+               .name = "2x100g",
+               .desc = "2x 100GE",
+               .is_active = sd1_is_2x100g,
+       },
+       {
+               .name = "100g4x25g",
+               .desc = "1x 100GE + 4x 25G",
+               .from = "2x100g",
+               .is_active = sd1_is_100g4x25g,
+               .enter = sd1_enter_100g4x25g,
+       },
+};
+
+static const struct sd1_mode *sd1_mode_find(const char *name)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(sd1_modes); i++)
+               if (!strcmp(sd1_modes[i].name, name))
+                       return &sd1_modes[i];
+
+       return NULL;
+}
+
+static void sd1_print_pcc(unsigned long base)
+{
+       printf("PCCC=%08x PCCD=%08x PCCE=%08x",
+              in_le32((void __iomem *)SRDS_PCCC(base)),
+              in_le32((void __iomem *)SRDS_PCCD(base)),
+              in_le32((void __iomem *)SRDS_PCCE(base)));
+}
+
+/* Put SD1 in @mode, for serdes1:@target */
+static int sd1_enter(const struct sd1_mode *mode,
+                    const struct sd1_mode *target, int depth)
+{
+       unsigned long base = srds_base(FSL_SRDS_1);
+       const struct sd1_mode *from;
+       int ret;
+
+       if (mode->is_active(base))
+               return 0;
+
+       if (!mode->enter) {
+               printf("lx2160a serdes: serdes1:%s needs SD1 to be %s, it is 
not (",
+                      target->name, mode->desc);
+               sd1_print_pcc(base);
+               printf("), refusing\n");
+               return -EINVAL;
+       }
+
+       from = sd1_mode_find(mode->from);
+       if (!from || depth >= ARRAY_SIZE(sd1_modes))
+               return -EINVAL;
+
+       ret = sd1_enter(from, target, depth + 1);
+       if (ret)
+               return ret;
+
+       ret = mode->enter(base);
+       if (!mode->is_active(base)) {
+               printf("lx2160a serdes: SD1 is not %s (", mode->desc);
+               sd1_print_pcc(base);
+               printf("), stopping\n");
+               return ret ? ret : -EIO;
        }
 
-       printf("lx2160a serdes: SD1 100GE.1 split into 4x 25G%s\n",
+       printf("lx2160a serdes: SD1 now %s%s\n", mode->desc,
               ret ? " (with warnings above)" : "");
 
-       return ret;
+       return 0;
 }
 
 /*
- *   setenv hwconfig serdes1:100g4x25g
+ *   setenv hwconfig serdes1:<mode>
+ *
+ * The modes are target states of the same 8 lanes, not steps:
+ * when several are given, the first one of sd1_modes[] is applied and
+ * the others are reported.
  *
- * Idempotent: a board whose PBI already split SD1 skip it too.
+ * Idempotent: a board whose PBI already configured SD1 skips it.
  */
 void lx2160a_serdes_apply_hwconfig(void)
 {
-       if (!hwconfig_sub("serdes1", "100g4x25g"))
-               return;
+       const struct sd1_mode *mode = NULL;
+       int i;
 
-       if (lx2160a_serdes1_is_split())
-               return;
+       for (i = 0; i < ARRAY_SIZE(sd1_modes); i++) {
+               if (!hwconfig_sub("serdes1", sd1_modes[i].name))
+                       continue;
+               if (mode) {
+                       printf("lx2160a serdes: hwconfig also asks for 
serdes1:%s, ignored\n",
+                              sd1_modes[i].name);
+                       continue;
+               }
+               mode = &sd1_modes[i];
+       }
 
-       lx2160a_serdes1_split();
+       if (mode)
+               sd1_enter(mode, mode, 0);
 }
 
 #endif /* CONFIG_ARCH_LX2160A */
diff --git a/board/nxp/lx2160a/serdes_split.h b/board/nxp/lx2160a/serdes_split.h
index 8dce9ddf97a..5a820c8c5d8 100644
--- a/board/nxp/lx2160a/serdes_split.h
+++ b/board/nxp/lx2160a/serdes_split.h
@@ -7,25 +7,13 @@
 #define __LX2160A_SERDES_SPLIT_H
 
 /*
- * The split is LX2160A only: LX2162A's SerDes 1 is four lanes with no
+ * SD1 modes are for LX2160A only: LX2162A's SD 1 is four lanes with no
  * 100GE in its protocol table.
  */
 #if defined(CONFIG_ARCH_LX2160A)
 
-/* Is SerDes 1 already 1x 100G + 4x 25G? */
-bool lx2160a_serdes1_is_split(void);
-
-/*
- * Split SerDes 1's 100GE.1 (lanes LNE-LNH) into 4x 25G.
- *
- * Refuses unless SD1 is in the unsplit state, and must be called before
- * the Management Complex starts: MC latches the live protocol-converter
- * configuration when it boots.
- */
-int lx2160a_serdes1_split(void);
-
 /*
- * Apply `hwconfig=serdes1:100g4x25g` if the board's environment asks for it.
+ * Apply `hwconfig=serdes1:<mode>` if the board's environment asks for it.
  * Boards call this from reset_phy(), before mc_env_boot().
  */
 void lx2160a_serdes_apply_hwconfig(void);
-- 
2.43.0

Reply via email to