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)"
#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);
+
void lx2160a_serdes_apply_hwconfig(void);
#endif /* __LX2160A_SERDES_SPLIT_H */
--
2.43.0