xref: /aosp_15_r20/external/coreboot/src/vendorcode/cavium/bdk/libbdk-boot/bdk-boot-qlm.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /***********************license start***********************************
2 * Copyright (c) 2003-2017  Cavium Inc. ([email protected]). All rights
3 * reserved.
4 *
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 *   * Redistributions of source code must retain the above copyright
11 *     notice, this list of conditions and the following disclaimer.
12 *
13 *   * Redistributions in binary form must reproduce the above
14 *     copyright notice, this list of conditions and the following
15 *     disclaimer in the documentation and/or other materials provided
16 *     with the distribution.
17 *
18 *   * Neither the name of Cavium Inc. nor the names of
19 *     its contributors may be used to endorse or promote products
20 *     derived from this software without specific prior written
21 *     permission.
22 *
23 * This Software, including technical data, may be subject to U.S. export
24 * control laws, including the U.S. Export Administration Act and its
25 * associated regulations, and may be subject to export or import
26 * regulations in other countries.
27 *
28 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS"
29 * AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR
30 * WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT
31 * TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY
32 * REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT
33 * DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES
34 * OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR
35 * PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT,
36 * QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE  RISK
37 * ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU.
38 ***********************license end**************************************/
39 #include <bdk.h>
40 #include <string.h>
41 #include "libbdk-hal/if/bdk-if.h"
42 #include "libbdk-hal/bdk-qlm.h"
43 #include "libbdk-hal/bdk-utils.h"
44 #include "libbdk-boot/bdk-boot-qlm.h"
45 #include "libbdk-hal/bdk-config.h"
46 #include "libbdk-hal/bdk-twsi.h"
47 
boot_init_qlm_clk(void)48 static void boot_init_qlm_clk(void)
49 {
50     /* Setup reference clocks */
51     for (bdk_node_t n = BDK_NODE_0; n < BDK_NUMA_MAX_NODES; n++)
52     {
53         if (!bdk_numa_exists(n))
54             continue;
55 
56         int num_qlms = bdk_qlm_get_num(n);
57 
58         BDK_TRACE(INIT, "Initializing QLM clocks on Node %d\n", n);
59         for (int qlm = 0; qlm < num_qlms; qlm++)
60         {
61             bdk_qlm_clock_t clk = bdk_config_get_int(BDK_CONFIG_QLM_CLK, n, qlm);
62             if (BDK_QLM_CLK_LAST == clk) /* no entry */
63                 continue;
64 
65             if (clk > BDK_QLM_CLK_LAST)
66             {
67                 bdk_warn("Invalid clock source %d for QLM%d on node %d. Not configuring.\n",
68                          clk, qlm, n);
69                 continue;
70             }
71 
72             if (0 != bdk_qlm_set_clock(n, qlm, clk))
73             {
74                 bdk_error("Error setting clock source %d for QLM%d on node %d. Ignoring.\n",
75                           clk, qlm, n);
76             }
77         }
78     }
79 }
80 
81 /**
82  * Given a node and DLM/QLM, return the possible BGX lanes connected to it. This
83  * is needed to determine which PHY address to use for SFP/SFP+ detection.
84  *
85  * @param node   Node the DLM/QLM is on
86  * @param qlm    DLM/QLM to find the BGX for
87  * @param bgx    Output: The BGX instance number, or -1 on failure
88  * @param bgx_lane_mask
89  *               Output: Which BGX indexes may be connected to this port
90  */
find_bgx(int node,int qlm,int * bgx,int * bgx_lane_mask)91 static void find_bgx(int node, int qlm, int *bgx, int *bgx_lane_mask)
92 {
93     *bgx = -1;
94     *bgx_lane_mask = 0;
95 
96     if (CAVIUM_IS_MODEL(CAVIUM_CN88XX))
97     {
98         switch (qlm)
99         {
100             case 0: /* BGX0 -> QLM0 */
101             case 1: /* BGX1 -> QLM1 */
102                 *bgx = qlm;
103                 *bgx_lane_mask = 0xf;
104                 return;
105             default:
106                 BDK_TRACE(INIT, "N%d.QLM%d: No BGX for this QLM, illegal config\n", node, qlm);
107                 return;
108         }
109     }
110     else if (CAVIUM_IS_MODEL(CAVIUM_CN83XX))
111     {
112         switch (qlm)
113         {
114             case 2: /* BGX0 -> QLM2 */
115                 *bgx = 0;
116                 *bgx_lane_mask = 0xf;
117                 return;
118             case 3: /* BGX1 -> QLM3 */
119                 *bgx = 1;
120                 *bgx_lane_mask = 0xf;
121                 return;
122             case 4: /* BGX3 -> DLM4 */
123                 *bgx = 3;
124                 *bgx_lane_mask = 0x3;
125                 return;
126             case 5: /* BGX2 -> DLM5 */
127                 *bgx = 2;
128                 *bgx_lane_mask = 0x3;
129                 return;
130             case 6: /* BGX2 -> DLM6 */
131                 *bgx = 2;
132                 *bgx_lane_mask = 0xc;
133                 return;
134             default:
135                 BDK_TRACE(INIT, "N%d.QLM%d: No BGX for this QLM, illegal config\n", node, qlm);
136                 return;
137         }
138     }
139     else if (CAVIUM_IS_MODEL(CAVIUM_CN81XX))
140     {
141         switch (qlm)
142         {
143             case 0: /* BGX0 -> DLM0 */
144                 *bgx = 0;
145                 *bgx_lane_mask = 0x3;
146                 return;
147             case 1: /* BGX0 -> DLM1 */
148                 *bgx = 0;
149                 *bgx_lane_mask = 0xc;
150                 return;
151             case 2: /* BGX1 -> DLM2 */
152                 *bgx = 1;
153                 *bgx_lane_mask = 0x3;
154                 return;
155             case 3: /* BGX1 -> DLM3 */
156                 *bgx = 1;
157                 *bgx_lane_mask = 0xc;
158                 return;
159             default:
160                 BDK_TRACE(INIT, "N%d.QLM%d: No BGX for this QLM, illegal config\n", node, qlm);
161                 return;
162         }
163     }
164     else
165         bdk_error("N%d.QLM%d: Unsupported chip, update %s()\n", node, qlm, __func__);
166 }
167 
168 /**
169  * Determine the DLM/QLM mode based on a SFP/SFP+ connected to the port. Note that
170  * the CN8XXX parts can't control mode per lane, so all SFP/SFP+ on a DLM/QLM must
171  * be the same mode. This code is sloppy about finding the BGX PHY for the DLM/QLM
172  * because not all lanes may be used.
173  *
174  * @param node   Node to determine mode for
175  * @param qlm    DLM/QLM the SFP/SFP+ is connected to
176  *
177  * @return QLM mode or -1 on failure
178  */
init_sfp(int node,int qlm)179 static int init_sfp(int node, int qlm)
180 {
181     int mode = BDK_QLM_MODE_XFI_4X1; /* Default to XFI if detection fails */
182     int bgx = -1;
183     int bgx_lane_mask = 0;
184 
185     find_bgx(node, qlm, &bgx, &bgx_lane_mask);
186     if (bgx == -1)
187         return mode;
188 
189     BDK_TRACE(INIT, "N%d.QLM%d: Checking for SFP/SFP+\n", node, qlm);
190 
191     for (int index = 0; index < 4; index++)
192     {
193         /* Skip BGX indexes that aren't applicable */
194         if ((bgx_lane_mask & (1 << index)) == 0)
195             continue;
196         /* Lookup the PHY address for this BGX port */
197         int phy_addr = bdk_config_get_int(BDK_CONFIG_PHY_ADDRESS, node, bgx, index);
198         /* SFP/SFP+ are connected with TWSI, so only check ports with
199            PHYs connected with TWSI */
200         if ((phy_addr & BDK_IF_PHY_TYPE_MASK) != BDK_IF_PHY_TWSI)
201             continue;
202 
203         /* For TWSI:
204             Bits[31:24]: Node ID, 0xff for device node
205             Bits[23:16]: TWSI internal address width in bytes (0-2)
206             Bits[15:12]: 2=TWSI
207             Bits[11:8]: TWSI bus number
208             Bits[7:0]: TWSI address */
209         int n = (phy_addr >> 24) & 0xff;
210         int twsi_ia_width = (phy_addr >> 16) & 0xff;
211         int twsi_bus = (phy_addr >> 8) & 0xf;
212         int twsi_addr = 0x50; /* From SFP spec */
213         if (n == 0xff)
214             n = node;
215 
216         /* Read bytes 0-3 from eeprom. Note read is big endian, so byte 0 is
217            bits 31:24 in the result */
218         int64_t eeprom_00_03 = bdk_twsix_read_ia(n, twsi_bus, twsi_addr, 0, 4, twsi_ia_width);
219         if (eeprom_00_03 == -1)
220         {
221             BDK_TRACE(INIT, "N%d.QLM%d: BGX%d.%d SFP/SFP+ eeprom access failed\n", node, qlm, bgx, index);
222             continue;
223         }
224         int64_t eeprom_04_07 = bdk_twsix_read_ia(n, twsi_bus, twsi_addr, 4, 4, twsi_ia_width);
225         if (eeprom_04_07 == -1)
226         {
227             BDK_TRACE(INIT, "N%d.QLM%d: BGX%d.%d SFP/SFP+ eeprom access failed\n", node, qlm, bgx, index);
228             continue;
229         }
230         int64_t eeprom_08_11 = bdk_twsix_read_ia(n, twsi_bus, twsi_addr, 8, 4, twsi_ia_width);
231         if (eeprom_08_11 == -1)
232         {
233             BDK_TRACE(INIT, "N%d.QLM%d: BGX%d.%d SFP/SFP+ eeprom access failed\n", node, qlm, bgx, index);
234             continue;
235         }
236         int64_t eeprom_12 = bdk_twsix_read_ia(n, twsi_bus, twsi_addr, 12, 1, twsi_ia_width);
237         if (eeprom_12 == -1)
238         {
239             BDK_TRACE(INIT, "N%d.QLM%d: BGX%d.%d SFP/SFP+ eeprom access failed\n", node, qlm, bgx, index);
240             continue;
241         }
242 
243         /* Byte 0: Identifier, should be 0x03 for SFP/SFP+
244                     0x03 = SFP of SFP+
245                     0x0c = QSFP
246                     0x0d = QSFP+ */
247         if (bdk_extract(eeprom_00_03, 24, 8) != 0x03)
248         {
249             /* Byte 0 of eeprom should be 0x03 for SFP/SFP+ */
250             BDK_TRACE(INIT, "N%d.QLM%d: BGX%d.%d SFP/SFP+ not detected\n", node, qlm, bgx, index);
251             continue;
252         }
253         /* Byte 1: Extended Identifier, should be 0x04 */
254         if (bdk_extract(eeprom_00_03, 16, 8) != 0x04)
255         {
256             BDK_TRACE(INIT, "N%d.QLM%d: BGX%d.%d SFP/SFP+ incorrect extended identifier\n", node, qlm, bgx, index);
257             continue;
258         }
259         /* Byte 2: Connector
260                     Value   Description of connector
261                     00h     Unknown or unspecified
262                     01h     SC
263                     02h     Fibre Channel Style 1 copper connector
264                     03h     Fibre Channel Style 2 copper connector
265                     04h     BNC/TNC
266                     05h     Fibre Channel coaxial headers
267                     06h     FiberJack
268                     07h     LC
269                     08h     MT-RJ
270                     09h     MU
271                     0Ah     SG
272                     0Bh     Optical pigtail
273                     0Ch     MPO Parallel Optic
274                     0Dh-1Fh Reserved, Unallocated
275                     20h     HSSDC II
276                     21h     Copper Pigtail
277                     22h     RJ45
278                     23h-7Fh Reserved, Unallocated
279                     80-FFh Vendor specific */
280         bool isOptical = false;
281         switch (bdk_extract(eeprom_00_03, 8, 8))
282         {
283             case 0x01: /* SC - Short channel */
284             case 0x07: /* LC - Long channel */
285             case 0x0B: /* Optical pigtail */
286                 isOptical = true;
287                 break;
288         }
289         BDK_TRACE(INIT, "N%d.QLM%d: SFP/SFP+ eeprom Bytes[0:3] 0x%0llx, Bytes[4:7] 0x%08llx, [8:11] 0x%08llx [12] 0x%02llx\n",
290             node, qlm, eeprom_00_03, eeprom_04_07, eeprom_08_11, eeprom_12);
291         /* Byte 3: Transceiver info first byte. See comments below */
292         /* Byte 3, bits 4-7 correspond to 10G Ethernet speeds */
293         /* 10G Ethernet Compliance Codes
294                 Byte 3[7] 10G BASE-ER (Fiber - Extended Reach)
295                 Byte 3[6] 10G BASE-LRM (Fiber - Long reach multi-mode)
296                 Byte 3[5] 10G BASE-LR (Fiber - Long reach)
297                 Byte 3[4] 10G BASE-SR (Fiber - Short reach) */
298         bool isXFI = bdk_extract(eeprom_00_03, 0, 8) != 0;
299         /* Byte 6, bits 0-7 correspond to Gigabit Ethernet speeds */
300         /* Gigabit Ethernet Compliance Codes
301                 Byte 6[7] BASE-PX
302                 Byte 6[6] BASE-BX10
303                 Byte 6[5] 100BASE-FX
304                 Byte 6[4] 100BASE-LX/LX10 (Fiber)
305                 Byte 6[3] 1000BASE-T (Twisted pair)
306                 Byte 6[2] 1000BASE-CX (Shielded balanced copper)
307                 Byte 6[1] 1000BASE-LX (Fiber)
308                 Byte 6[0] 1000BASE-SX (Fiber) */
309         bool isSGMII = bdk_extract(eeprom_04_07, 8, 8) != 0;
310         /* Byte 12 is the nominal bit rate, units of 100 MBits/sec. */
311         int bit_rate = eeprom_12 * 100;
312         if (bit_rate)
313         {
314             BDK_TRACE(INIT, "N%d.QLM%d: Nominal bit rate %d  MBits/sec\n",
315                 node, qlm, bit_rate);
316             isXFI = (bit_rate >= 10000);
317             isSGMII = (bit_rate <= 2500);
318         }
319 
320         if (isXFI)
321         {
322             mode = BDK_QLM_MODE_XFI_4X1;
323             if (isOptical)
324                 BDK_TRACE(INIT, "N%d.QLM%d: SFP+ selecting XFI Optical\n", node, qlm);
325             else
326                 BDK_TRACE(INIT, "N%d.QLM%d: SFP+ selecting XFI Copper\n", node, qlm);
327         }
328         else if (isSGMII)
329         {
330             mode = BDK_QLM_MODE_SGMII_4X1;
331             if (isOptical)
332             {
333                 /* This should be 1000BASE-X, gigabit over fiber */
334                 BDK_TRACE(INIT, "N%d.QLM%d: SFP selecting SGMII Optical\n", node, qlm);
335             }
336             else /* This should be SGMII, gigabit over copper */
337                 BDK_TRACE(INIT, "N%d.QLM%d: SFP selecting SGMII Copper\n", node, qlm);
338         }
339     }
340     return mode;
341 }
342 
343 /**
344  * Determine the DLM/QLM mode based on a QSFP/QSFP+ connected to
345  * the port. This code is sloppy about finding the BGX PHY for
346  * the DLM/QLM because not all lanes may be used.
347  *
348  * @param node   Node to determine mode for
349  * @param qlm    DLM/QLM the SFP/SFP+ is connected to
350  *
351  * @return QLM mode or -1 on failure
352  */
init_qsfp(int node,int qlm)353 static int init_qsfp(int node, int qlm)
354 {
355     int mode = BDK_QLM_MODE_XLAUI_1X4; /* Default to XLAUI if detection fails */
356     int bgx = -1;
357     int bgx_lane_mask = 0;
358 
359     find_bgx(node, qlm, &bgx, &bgx_lane_mask);
360     if (bgx == -1)
361         return mode;
362 
363     BDK_TRACE(INIT, "N%d.QLM%d: Checking for QSFP/QSFP+\n", node, qlm);
364     int index = 0;
365 
366     /* Lookup the PHY address for this BGX port */
367     int phy_addr = bdk_config_get_int(BDK_CONFIG_PHY_ADDRESS, node, bgx, index);
368     /* QSFP/QSFP+ are connected with TWSI, so only check ports with
369        PHYs connected with TWSI */
370     if ((phy_addr & BDK_IF_PHY_TYPE_MASK) != BDK_IF_PHY_TWSI)
371         return mode;
372 
373     /* For TWSI:
374         Bits[31:24]: Node ID, 0xff for device node
375         Bits[23:16]: TWSI internal address width in bytes (0-2)
376         Bits[15:12]: 2=TWSI
377         Bits[11:8]: TWSI bus number
378         Bits[7:0]: TWSI address */
379     int n = (phy_addr >> 24) & 0xff;
380     int twsi_ia_width = (phy_addr >> 16) & 0xff;
381     int twsi_bus = (phy_addr >> 8) & 0xf;
382     int twsi_addr = 0x50; /* From SFP spec */
383     if (n == 0xff)
384         n = node;
385 
386     /* Byte 0: Identifier, should be 0x03 for SFP/SFP+
387                 0x03 = SFP of SFP+
388                 0x0c = QSFP
389                 0x0d = QSFP+ */
390     int64_t eeprom_00 = bdk_twsix_read_ia(n, twsi_bus, twsi_addr, 0, 1, twsi_ia_width);
391     switch (eeprom_00)
392     {
393         case 0x03:
394             BDK_TRACE(INIT, "N%d.QLM%d: BGX%d QSFP/QSFP+ contains a SFP+\n", node, qlm, bgx);
395             mode = init_sfp(node, qlm);
396             break;
397         case 0x0c:
398         case 0x0d:
399             BDK_TRACE(INIT, "N%d.QLM%d: BGX%d Found a QSFP/QSFP+, assuming 40G\n", node, qlm, bgx);
400             mode = BDK_QLM_MODE_XLAUI_1X4;
401             break;
402         default:
403             BDK_TRACE(INIT, "N%d.QLM%d: BGX%d QSFP/QSFP+ not detected\n", node, qlm, bgx);
404             break;
405     }
406     return mode;
407 }
408 
boot_init_qlm_mode(void)409 static void boot_init_qlm_mode(void)
410 {
411     /* Check if QLM autoconfig is requested */
412     int qlm_auto = bdk_config_get_int(BDK_CONFIG_QLM_AUTO_CONFIG);
413     if (qlm_auto)
414     {
415         /* Auto configuration of QLMs
416          */
417         for (bdk_node_t n = BDK_NODE_0; n < BDK_NUMA_MAX_NODES; n++)
418         {
419             if (bdk_numa_exists(n))
420             {
421                 BDK_TRACE(INIT, "Initializing QLMs on Node %d\n", n);
422                 bdk_qlm_auto_config(n);
423             }
424         }
425     }
426    /*
427     * Check if QLM autoconfig from DIP switch settings is requested
428     */
429     else if (bdk_config_get_int(BDK_CONFIG_QLM_DIP_AUTO_CONFIG))
430     {
431         BDK_TRACE(INIT, "Reading DIP Switch settings for QLM Auto configuration\n");
432 
433         /* Auto configuration of QLMs
434          */
435         for (bdk_node_t n = BDK_NODE_0; n < BDK_NUMA_MAX_NODES; n++)
436         {
437             if (bdk_numa_exists(n))
438             {
439                 BDK_TRACE(INIT, "Initializing QLMs on Node %d\n", n);
440                 if (bdk_qlm_dip_auto_config(n))
441                     bdk_error("QLM Auto configuration failed!\n");
442             }
443         }
444 
445     }
446     else
447     {
448         /* Initialize the QLMs based on configuration file settings
449          */
450 
451         boot_init_qlm_clk();
452 
453         for (bdk_node_t n = BDK_NODE_0; n < BDK_NUMA_MAX_NODES; n++)
454         {
455             if (!bdk_numa_exists(n))
456                 continue;
457 
458             int num_qlms = bdk_qlm_get_num(n);
459 
460             BDK_TRACE(INIT, "Initializing QLMs on Node %d\n", n);
461             for (int qlm = 0; qlm < num_qlms; qlm++)
462             {
463                 const char *cfg_val;
464 
465                 cfg_val = bdk_config_get_str(BDK_CONFIG_QLM_MODE, n, qlm);
466                 if (!cfg_val)
467                     continue;
468 
469                 int mode;
470                 int freq;
471                 /* Check for special token telling us to configure the QLM
472                    based on the SFP/SFP+/QSFP/QSFP+ plugged into the system. */
473                 if ((strcmp(cfg_val, "SFP+") == 0) || (strcmp(cfg_val, "QSFP+") == 0))
474                 {
475                     if (strcmp(cfg_val, "SFP+") == 0)
476                         mode = init_sfp(n, qlm);
477                     else
478                         mode = init_qsfp(n, qlm);
479 
480                     if (mode == BDK_QLM_MODE_SGMII_4X1)
481                         freq = 1250;
482                     else
483                         freq = 10321;
484                 }
485                 else
486                 {
487                     mode = bdk_qlm_cfg_string_to_mode(cfg_val);
488                     freq = bdk_config_get_int(BDK_CONFIG_QLM_FREQ, n, qlm);
489                 }
490                 if (-1 == mode)
491                 {
492                     bdk_error("Invalid QLM mode string '%s' for QLM%d on node %d. "
493                                 "Not configuring.\n", cfg_val, qlm, n);
494                     continue;
495                 }
496                 if (-1 == freq)
497                 {
498                     bdk_error("No frequency setting for QLM%d on node %d. "
499                                 "Not configuring.\n", qlm, n);
500                     continue;
501                 }
502 
503                 bdk_qlm_set_mode(n, qlm, mode, freq, 0);
504             }
505         }
506     }
507 }
508 
509 /**
510  * Configure QLM on all nodes as part of booting
511  */
bdk_boot_qlm(void)512 void bdk_boot_qlm(void)
513 {
514     boot_init_qlm_mode();
515 }
516