From: Aaron Williams <awilli...@marvell.com> Import cvmx-helper-sfp.c from 2013 U-Boot. It will be used by the later added drivers to support networking on the MIPS Octeon II / III platforms.
Signed-off-by: Aaron Williams <awilli...@marvell.com> Signed-off-by: Stefan Roese <s...@denx.de> --- arch/mips/mach-octeon/cvmx-helper-sfp.c | 1877 +++++++++++++++++++++++ 1 file changed, 1877 insertions(+) create mode 100644 arch/mips/mach-octeon/cvmx-helper-sfp.c diff --git a/arch/mips/mach-octeon/cvmx-helper-sfp.c b/arch/mips/mach-octeon/cvmx-helper-sfp.c new file mode 100644 index 000000000000..a17ac542fcf8 --- /dev/null +++ b/arch/mips/mach-octeon/cvmx-helper-sfp.c @@ -0,0 +1,1877 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018-2022 Marvell International Ltd. + */ + +#include <errno.h> +#include <i2c.h> +#include <log.h> +#include <malloc.h> +#include <linux/delay.h> +#include <display_options.h> + +#include <mach/cvmx-regs.h> +#include <mach/cvmx-csr.h> +#include <mach/cvmx-bootmem.h> +#include <mach/octeon-model.h> +#include <mach/cvmx-fuse.h> +#include <mach/octeon-feature.h> +#include <mach/cvmx-qlm.h> +#include <mach/octeon_qlm.h> +#include <mach/cvmx-pcie.h> +#include <mach/cvmx-coremask.h> + +#include <mach/cvmx-helper.h> +#include <mach/cvmx-helper-board.h> +#include <mach/cvmx-helper-fdt.h> +#include <mach/cvmx-helper-cfg.h> +#include <mach/cvmx-helper-gpio.h> +#include <mach/cvmx-helper-util.h> + +extern void octeon_i2c_unblock(int bus); + +static struct cvmx_fdt_sfp_info *sfp_list; + +/** + * Local allocator to handle both SE and U-Boot that also zeroes out memory + * + * @param size number of bytes to allocate + * + * @return pointer to allocated memory or NULL if out of memory. + * Alignment is set to 8-bytes. + */ +static void *cvm_sfp_alloc(size_t size) +{ + return calloc(size, 1); +} + +/** + * Free allocated memory. + * + * @param ptr pointer to memory to free + * + * NOTE: This only works in U-Boot since SE does not really have a freeing + * mechanism. In SE the memory is zeroed out and not freed so this + * is a memory leak if errors occur. + */ +static inline void cvm_sfp_free(void *ptr, size_t size) +{ + free(ptr); +} + +/** + * Select a QSFP device before accessing the EEPROM + * + * @param sfp handle for sfp/qsfp connector + * @param enable Set true to select, false to deselect + * + * @return 0 on success or if SFP or no select GPIO, -1 on GPIO error + */ +static int cvmx_qsfp_select(const struct cvmx_fdt_sfp_info *sfp, bool enable) +{ + /* Select is only needed for QSFP modules */ + if (!sfp->is_qsfp) { + debug("%s(%s, %d): not QSFP\n", __func__, sfp->name, enable); + return 0; + } + + if (dm_gpio_is_valid(&sfp->select)) { + /* Note that select is active low */ + return dm_gpio_set_value(&sfp->select, !enable); + } + + debug("%s: select GPIO unknown\n", __func__); + return 0; +} + +static int cvmx_sfp_parse_sfp_buffer(struct cvmx_sfp_mod_info *sfp_info, + const uint8_t *buffer) +{ + u8 csum = 0; + bool csum_good = false; + int i; + + /* Validate the checksum */ + for (i = 0; i < 0x3f; i++) + csum += buffer[i]; + csum_good = csum == buffer[0x3f]; + debug("%s: Lower checksum: 0x%02x, expected: 0x%02x\n", __func__, csum, + buffer[0x3f]); + csum = 0; + for (i = 0x40; i < 0x5f; i++) + csum += buffer[i]; + debug("%s: Upper checksum: 0x%02x, expected: 0x%02x\n", __func__, csum, + buffer[0x5f]); + if (csum != buffer[0x5f] || !csum_good) { + debug("Error: SFP EEPROM checksum information is incorrect\n"); + return -1; + } + + sfp_info->conn_type = buffer[0]; + if (buffer[1] < 1 || buffer[1] > 7) { /* Extended ID */ + debug("Error: Unknown SFP extended identifier 0x%x\n", + buffer[1]); + return -1; + } + if (buffer[1] != 4) { + debug("Module is not SFP/SFP+/SFP28/QSFP+\n"); + return -1; + } + sfp_info->mod_type = buffer[2]; + sfp_info->eth_comp = buffer[3] & 0xf0; + sfp_info->cable_comp = buffer[0x24]; + + /* There are several ways a cable can be marked as active or + * passive. 8.[2-3] specify the SFP+ cable technology. Some + * modules also use 3.[0-1] for Infiniband, though it's + * redundant. + */ + if ((buffer[8] & 0x0C) == 0x08) { + sfp_info->limiting = true; + sfp_info->active_cable = true; + } else if ((buffer[8] & 0xC) == 0x4) { + sfp_info->limiting = false; + sfp_info->active_cable = false; + } + if ((buffer[3] & 3) == 2) { + sfp_info->active_cable = true; + sfp_info->limiting = true; + } + + switch (sfp_info->mod_type) { + case CVMX_SFP_MOD_OPTICAL_LC: + case CVMX_SFP_MOD_OPTICAL_PIGTAIL: + sfp_info->copper_cable = false; + break; + case CVMX_SFP_MOD_COPPER_PIGTAIL: + sfp_info->copper_cable = true; + break; + case CVMX_SFP_MOD_NO_SEP_CONN: + switch (sfp_info->cable_comp) { + case CVMX_SFP_CABLE_100G_25GAUI_C2M_AOC_HIGH_BER: + case CVMX_SFP_CABLE_100G_25GAUI_C2M_AOC_LOW_BER: + case CVMX_SFP_CABLE_100G_25GAUI_C2M_ACC_LOW_BER: + sfp_info->copper_cable = false; + sfp_info->limiting = true; + sfp_info->active_cable = true; + break; + + case CVMX_SFP_CABLE_100G_SR4_25G_SR: + case CVMX_SFP_CABLE_100G_LR4_25G_LR: + case CVMX_SFP_CABLE_100G_ER4_25G_ER: + case CVMX_SFP_CABLE_100G_SR10: + case CVMX_SFP_CABLE_100G_CWDM4_MSA: + case CVMX_SFP_CABLE_100G_PSM4: + case CVMX_SFP_CABLE_100G_CWDM4: + case CVMX_SFP_CABLE_40G_ER4: + case CVMX_SFP_CABLE_4X10G_SR: + case CVMX_SFP_CABLE_G959_1_P1I1_2D1: + case CVMX_SFP_CABLE_G959_1_P1S1_2D2: + case CVMX_SFP_CABLE_G959_1_P1L1_2D2: + case CVMX_SFP_CABLE_100G_CLR4: + case CVMX_SFP_CABLE_100G_2_LAMBDA_DWDM: + case CVMX_SFP_CABLE_40G_SWDM4: + case CVMX_SFP_CABLE_100G_SWDM4: + case CVMX_SFP_CABLE_100G_PAM4_BIDI: + sfp_info->copper_cable = false; + break; + + case CVMX_SFP_CABLE_100G_25GAUI_C2M_ACC_HIGH_BER: + case CVMX_SFP_CABLE_10GBASE_T: + case CVMX_SFP_CABLE_10GBASE_T_SR: + case CVMX_SFP_CABLE_5GBASE_T: + case CVMX_SFP_CABLE_2_5GBASE_T: + sfp_info->copper_cable = true; + sfp_info->limiting = true; + sfp_info->active_cable = true; + break; + + case CVMX_SFP_CABLE_100G_CR4_25G_CR_CA_L: + case CVMX_SFP_CABLE_25G_CR_CA_S: + case CVMX_SFP_CABLE_25G_CR_CA_N: + case CVMX_SFP_CABLE_40G_PSM4: + sfp_info->copper_cable = true; + break; + + default: + switch (sfp_info->eth_comp) { + case CVMX_SFP_CABLE_10GBASE_ER: + case CVMX_SFP_CABLE_10GBASE_LRM: + case CVMX_SFP_CABLE_10GBASE_LR: + case CVMX_SFP_CABLE_10GBASE_SR: + sfp_info->copper_cable = false; + break; + } + break; + } + break; + + case CVMX_SFP_MOD_RJ45: + debug("%s: RJ45 adapter\n", __func__); + sfp_info->copper_cable = true; + sfp_info->active_cable = true; + sfp_info->limiting = true; + break; + case CVMX_SFP_MOD_UNKNOWN: + /* The Avago 1000Base-X to 1000Base-T module reports that it + * is an unknown module type but the Ethernet compliance code + * says it is 1000Base-T. We'll change the reporting to RJ45. + */ + if (buffer[6] & 8) { + debug("RJ45 gigabit module detected\n"); + sfp_info->mod_type = CVMX_SFP_MOD_RJ45; + sfp_info->copper_cable = false; + sfp_info->limiting = true; + sfp_info->active_cable = true; + sfp_info->max_copper_cable_len = buffer[0x12]; + sfp_info->rate = CVMX_SFP_RATE_1G; + } else { + debug("Unknown module type 0x%x\n", sfp_info->mod_type); + } + sfp_info->limiting = true; + break; + case CVMX_SFP_MOD_MXC_2X16: + debug("%s: MXC 2X16\n", __func__); + break; + default: + sfp_info->limiting = true; + break; + } + + if (sfp_info->copper_cable) + sfp_info->max_copper_cable_len = buffer[0x12]; + else + sfp_info->max_50um_om4_cable_length = buffer[0x12] * 10; + + if (buffer[0xe]) + sfp_info->max_single_mode_cable_length = buffer[0xe] * 1000; + else + sfp_info->max_single_mode_cable_length = buffer[0xf] * 100000; + + sfp_info->max_50um_om2_cable_length = buffer[0x10] * 10; + sfp_info->max_62_5um_om1_cable_length = buffer[0x11] * 10; + sfp_info->max_50um_om3_cable_length = buffer[0x13] * 10; + + if (buffer[0xc] == 0xff) { + if (buffer[0x42] >= 255) + sfp_info->rate = CVMX_SFP_RATE_100G; + else if (buffer[0x42] >= 160) + sfp_info->rate = CVMX_SFP_RATE_40G; + else if (buffer[0x42] >= 100) + sfp_info->rate = CVMX_SFP_RATE_25G; + else + sfp_info->rate = CVMX_SFP_RATE_UNKNOWN; + } else if (buffer[0xc] >= 100) { + sfp_info->rate = CVMX_SFP_RATE_10G; + } else if (buffer[0xc] >= 10) { + sfp_info->rate = CVMX_SFP_RATE_1G; + } else { + sfp_info->rate = CVMX_SFP_RATE_UNKNOWN; + } + + if (sfp_info->rate == CVMX_SFP_RATE_UNKNOWN) { + switch (sfp_info->cable_comp) { + case CVMX_SFP_CABLE_100G_SR10: + case CVMX_SFP_CABLE_100G_CWDM4_MSA: + case CVMX_SFP_CABLE_100G_PSM4: + case CVMX_SFP_CABLE_100G_CWDM4: + case CVMX_SFP_CABLE_100G_CLR4: + case CVMX_SFP_CABLE_100G_2_LAMBDA_DWDM: + case CVMX_SFP_CABLE_100G_SWDM4: + case CVMX_SFP_CABLE_100G_PAM4_BIDI: + sfp_info->rate = CVMX_SFP_RATE_100G; + break; + case CVMX_SFP_CABLE_100G_25GAUI_C2M_AOC_HIGH_BER: + case CVMX_SFP_CABLE_100G_SR4_25G_SR: + case CVMX_SFP_CABLE_100G_LR4_25G_LR: + case CVMX_SFP_CABLE_100G_ER4_25G_ER: + case CVMX_SFP_CABLE_100G_25GAUI_C2M_ACC_HIGH_BER: + case CVMX_SFP_CABLE_100G_CR4_25G_CR_CA_L: + case CVMX_SFP_CABLE_25G_CR_CA_S: + case CVMX_SFP_CABLE_25G_CR_CA_N: + case CVMX_SFP_CABLE_100G_25GAUI_C2M_AOC_LOW_BER: + case CVMX_SFP_CABLE_100G_25GAUI_C2M_ACC_LOW_BER: + sfp_info->rate = CVMX_SFP_RATE_25G; + break; + case CVMX_SFP_CABLE_40G_ER4: + case CVMX_SFP_CABLE_4X10G_SR: + case CVMX_SFP_CABLE_40G_PSM4: + case CVMX_SFP_CABLE_40G_SWDM4: + sfp_info->rate = CVMX_SFP_RATE_40G; + break; + case CVMX_SFP_CABLE_G959_1_P1I1_2D1: + case CVMX_SFP_CABLE_G959_1_P1S1_2D2: + case CVMX_SFP_CABLE_G959_1_P1L1_2D2: + case CVMX_SFP_CABLE_10GBASE_T: + case CVMX_SFP_CABLE_10GBASE_T_SR: + case CVMX_SFP_CABLE_5GBASE_T: + case CVMX_SFP_CABLE_2_5GBASE_T: + sfp_info->rate = CVMX_SFP_RATE_10G; + break; + default: + switch (sfp_info->eth_comp) { + case CVMX_SFP_CABLE_10GBASE_ER: + case CVMX_SFP_CABLE_10GBASE_LRM: + case CVMX_SFP_CABLE_10GBASE_LR: + case CVMX_SFP_CABLE_10GBASE_SR: + sfp_info->rate = CVMX_SFP_RATE_10G; + break; + default: + sfp_info->rate = CVMX_SFP_RATE_UNKNOWN; + break; + } + break; + } + } + + if (buffer[0xc] < 0xff) + sfp_info->bitrate_max = buffer[0xc] * 100; + else + sfp_info->bitrate_max = buffer[0x42] * 250; + + if ((buffer[8] & 0xc) == 8) { + if (buffer[0x3c] & 0x4) + sfp_info->limiting = true; + } + + /* Currently we only set this for 25G. FEC is required for CA-S cables + * and for cable lengths >= 5M as of this writing. + */ + if ((sfp_info->rate == CVMX_SFP_RATE_25G && + sfp_info->copper_cable) && + (sfp_info->cable_comp == CVMX_SFP_CABLE_25G_CR_CA_S || + sfp_info->max_copper_cable_len >= 5)) + sfp_info->fec_required = true; + + /* copy strings and vendor info, strings will be automatically NUL + * terminated. + */ + memcpy(sfp_info->vendor_name, &buffer[0x14], 16); + memcpy(sfp_info->vendor_oui, &buffer[0x25], 3); + memcpy(sfp_info->vendor_pn, &buffer[0x28], 16); + memcpy(sfp_info->vendor_rev, &buffer[0x38], 4); + memcpy(sfp_info->vendor_sn, &buffer[0x44], 16); + memcpy(sfp_info->date_code, &buffer[0x54], 8); + + sfp_info->cooled_laser = !!(buffer[0x40] & 4); + sfp_info->internal_cdr = !!(buffer[0x40] & 8); + + if (buffer[0x40] & 0x20) + sfp_info->power_level = 3; + else + sfp_info->power_level = (buffer[0x40] & 2) ? 2 : 1; + + sfp_info->diag_paging = !!(buffer[0x40] & 0x10); + sfp_info->linear_rx_output = !(buffer[0x40] & 1); + sfp_info->los_implemented = !!(buffer[0x41] & 2); + sfp_info->los_inverted = !!(buffer[0x41] & 4); + sfp_info->tx_fault_implemented = !!(buffer[0x41] & 8); + sfp_info->tx_disable_implemented = !!(buffer[0x41] & 0x10); + sfp_info->rate_select_implemented = !!(buffer[0x41] & 0x20); + sfp_info->tuneable_transmitter = !!(buffer[0x41] & 0x40); + sfp_info->rx_decision_threshold_implemented = !!(buffer[0x41] & 0x80); + + sfp_info->diag_monitoring = !!(buffer[0x5c] & 0x40); + sfp_info->diag_rx_power_averaged = !!(buffer[0x5c] & 0x8); + sfp_info->diag_externally_calibrated = !!(buffer[0x5c] & 0x10); + sfp_info->diag_internally_calibrated = !!(buffer[0x5c] & 0x20); + sfp_info->diag_addr_change_required = !!(buffer[0x5c] & 0x4); + sfp_info->diag_soft_rate_select_control = !!(buffer[0x5d] & 2); + sfp_info->diag_app_select_control = !!(buffer[0x5d] & 4); + sfp_info->diag_soft_rate_select_control = !!(buffer[0x5d] & 8); + sfp_info->diag_soft_rx_los_implemented = !!(buffer[0x5d] & 0x10); + sfp_info->diag_soft_tx_fault_implemented = !!(buffer[0x5d] & 0x20); + sfp_info->diag_soft_tx_disable_implemented = !!(buffer[0x5d] & 0x40); + sfp_info->diag_alarm_warning_flags_implemented = + !!(buffer[0x5d] & 0x80); + sfp_info->diag_rev = buffer[0x5e]; + + return 0; +} + +static int cvmx_sfp_parse_qsfp_buffer(struct cvmx_sfp_mod_info *sfp_info, + const uint8_t *buffer) +{ + u8 csum = 0; + bool csum_good = false; + int i; + + /* Validate the checksum */ + for (i = 0x80; i < 0xbf; i++) + csum += buffer[i]; + csum_good = csum == buffer[0xbf]; + debug("%s: Lower checksum: 0x%02x, expected: 0x%02x\n", __func__, csum, + buffer[0xbf]); + csum = 0; + for (i = 0xc0; i < 0xdf; i++) + csum += buffer[i]; + debug("%s: Upper checksum: 0x%02x, expected: 0x%02x\n", __func__, csum, + buffer[0xdf]); + if (csum != buffer[0xdf] || !csum_good) { + debug("Error: SFP EEPROM checksum information is incorrect\n"); + return -1; + } + + sfp_info->conn_type = buffer[0x80]; + sfp_info->mod_type = buffer[0x82]; + sfp_info->eth_comp = buffer[0x83] & 0xf0; + sfp_info->cable_comp = buffer[0xa4]; + + switch (sfp_info->mod_type) { + case CVMX_SFP_MOD_COPPER_PIGTAIL: + case CVMX_SFP_MOD_NO_SEP_CONN: + debug("%s: copper pigtail or no separable cable\n", __func__); + /* There are several ways a cable can be marked as active or + * passive. 8.[2-3] specify the SFP+ cable technology. Some + * modules also use 3.[0-1] for Infiniband, though it's + * redundant. + */ + sfp_info->copper_cable = true; + if ((buffer[0x88] & 0x0C) == 0x08) { + sfp_info->limiting = true; + sfp_info->active_cable = true; + } else if ((buffer[0x88] & 0xC) == 0x4) { + sfp_info->limiting = false; + sfp_info->active_cable = false; + } + if ((buffer[0x83] & 3) == 2) { + sfp_info->active_cable = true; + sfp_info->limiting = true; + } + break; + case CVMX_SFP_MOD_RJ45: + debug("%s: RJ45 adapter\n", __func__); + sfp_info->copper_cable = true; + sfp_info->active_cable = true; + sfp_info->limiting = true; + break; + case CVMX_SFP_MOD_UNKNOWN: + debug("Unknown module type\n"); + /* The Avago 1000Base-X to 1000Base-T module reports that it + * is an unknown module type but the Ethernet compliance code + * says it is 1000Base-T. We'll change the reporting to RJ45. + */ + if (buffer[0x86] & 8) { + sfp_info->mod_type = CVMX_SFP_MOD_RJ45; + sfp_info->copper_cable = false; + sfp_info->limiting = true; + sfp_info->active_cable = true; + sfp_info->max_copper_cable_len = buffer[0x92]; + sfp_info->rate = CVMX_SFP_RATE_1G; + } + fallthrough; + default: + sfp_info->limiting = true; + break; + } + + if (sfp_info->copper_cable) + sfp_info->max_copper_cable_len = buffer[0x92]; + else + sfp_info->max_50um_om4_cable_length = buffer[0x92] * 10; + + debug("%s: copper cable: %d, max copper cable len: %d\n", __func__, + sfp_info->copper_cable, sfp_info->max_copper_cable_len); + if (buffer[0xe]) + sfp_info->max_single_mode_cable_length = buffer[0x8e] * 1000; + else + sfp_info->max_single_mode_cable_length = buffer[0x8f] * 100000; + + sfp_info->max_50um_om2_cable_length = buffer[0x90] * 10; + sfp_info->max_62_5um_om1_cable_length = buffer[0x91] * 10; + sfp_info->max_50um_om3_cable_length = buffer[0x93] * 10; + + if (buffer[0x8c] == 12) { + sfp_info->rate = CVMX_SFP_RATE_1G; + } else if (buffer[0x8c] == 103) { + sfp_info->rate = CVMX_SFP_RATE_10G; + } else if (buffer[0x8c] == 0xff) { + if (buffer[0xc2] == 103) + sfp_info->rate = CVMX_SFP_RATE_100G; + } + + if (buffer[0x8c] < 0xff) + sfp_info->bitrate_max = buffer[0x8c] * 100; + else + sfp_info->bitrate_max = buffer[0xc2] * 250; + + if ((buffer[0x88] & 0xc) == 8) { + if (buffer[0xbc] & 0x4) + sfp_info->limiting = true; + } + + /* Currently we only set this for 25G. FEC is required for CA-S cables + * and for cable lengths >= 5M as of this writing. + */ + /* copy strings and vendor info, strings will be automatically NUL + * terminated. + */ + memcpy(sfp_info->vendor_name, &buffer[0x94], 16); + memcpy(sfp_info->vendor_oui, &buffer[0xa5], 3); + memcpy(sfp_info->vendor_pn, &buffer[0xa8], 16); + memcpy(sfp_info->vendor_rev, &buffer[0xb8], 4); + memcpy(sfp_info->vendor_sn, &buffer[0xc4], 16); + memcpy(sfp_info->date_code, &buffer[0xd4], 8); + + sfp_info->linear_rx_output = !!(buffer[0xc0] & 1); + sfp_info->cooled_laser = !!(buffer[0xc0] & 4); + sfp_info->internal_cdr = !!(buffer[0xc0] & 8); + + if (buffer[0xc0] & 0x20) + sfp_info->power_level = 3; + else + sfp_info->power_level = (buffer[0xc0] & 2) ? 2 : 1; + + sfp_info->diag_paging = !!(buffer[0xc0] & 0x10); + sfp_info->los_implemented = !!(buffer[0xc1] & 2); + sfp_info->los_inverted = !!(buffer[0xc1] & 4); + sfp_info->tx_fault_implemented = !!(buffer[0xc1] & 8); + sfp_info->tx_disable_implemented = !!(buffer[0xc1] & 0x10); + sfp_info->rate_select_implemented = !!(buffer[0xc1] & 0x20); + sfp_info->tuneable_transmitter = !!(buffer[0xc1] & 0x40); + sfp_info->rx_decision_threshold_implemented = !!(buffer[0xc1] & 0x80); + + sfp_info->diag_monitoring = !!(buffer[0xdc] & 0x40); + sfp_info->diag_rx_power_averaged = !!(buffer[0xdc] & 0x8); + sfp_info->diag_externally_calibrated = !!(buffer[0xdc] & 0x10); + sfp_info->diag_internally_calibrated = !!(buffer[0xdc] & 0x20); + sfp_info->diag_addr_change_required = !!(buffer[0xdc] & 0x4); + sfp_info->diag_soft_rate_select_control = !!(buffer[0xdd] & 2); + sfp_info->diag_app_select_control = !!(buffer[0xdd] & 4); + sfp_info->diag_soft_rate_select_control = !!(buffer[0xdd] & 8); + sfp_info->diag_soft_rx_los_implemented = !!(buffer[0xdd] & 0x10); + sfp_info->diag_soft_tx_fault_implemented = !!(buffer[0xdd] & 0x20); + sfp_info->diag_soft_tx_disable_implemented = !!(buffer[0xdd] & 0x40); + sfp_info->diag_alarm_warning_flags_implemented = + !!(buffer[0xdd] & 0x80); + sfp_info->diag_rev = buffer[0xde]; + + return 0; +} + +static bool sfp_verify_checksum(const uint8_t *buffer) +{ + u8 csum = 0; + u8 offset; + bool csum_good = false; + int i; + + switch (buffer[0]) { + case CVMX_SFP_CONN_QSFP: + case CVMX_SFP_CONN_QSFPP: + case CVMX_SFP_CONN_QSFP28: + case CVMX_SFP_CONN_MICRO_QSFP: + case CVMX_SFP_CONN_QSFP_DD: + offset = 0x80; + break; + default: + offset = 0; + break; + } + for (i = offset; i < offset + 0x3f; i++) + csum += buffer[i]; + csum_good = csum == buffer[offset + 0x3f]; + if (!csum_good) { + debug("%s: Lower checksum bad, got 0x%x, expected 0x%x\n", + __func__, csum, buffer[offset + 0x3f]); + return false; + } + csum = 0; + for (i = offset + 0x40; i < offset + 0x5f; i++) + csum += buffer[i]; + if (csum != buffer[offset + 0x5f]) { + debug("%s: Upper checksum bad, got 0x%x, expected 0x%x\n", + __func__, csum, buffer[offset + 0x5f]); + return false; + } + return true; +} + +/** + * Reads and parses SFP/QSFP EEPROM + * + * @param sfp sfp handle to read + * + * @return 0 for success, -1 on error. + */ +int cvmx_sfp_read_i2c_eeprom(struct cvmx_fdt_sfp_info *sfp) +{ + const struct cvmx_fdt_i2c_bus_info *bus = sfp->i2c_bus; + int oct_bus = cvmx_fdt_i2c_get_root_bus(bus); + struct udevice *dev; + u8 buffer[256]; + bool is_qsfp; + int retry; + int err; + + if (!bus) { + debug("%s(%s): Error: i2c bus undefined for eeprom\n", __func__, + sfp->name); + return -1; + } + + is_qsfp = (sfp->sfp_info.conn_type == CVMX_SFP_CONN_QSFP || + sfp->sfp_info.conn_type == CVMX_SFP_CONN_QSFPP || + sfp->sfp_info.conn_type == CVMX_SFP_CONN_QSFP28 || + sfp->sfp_info.conn_type == CVMX_SFP_CONN_MICRO_QSFP) || + sfp->is_qsfp; + + err = cvmx_qsfp_select(sfp, true); + if (err) { + debug("%s: Error selecting SFP/QSFP slot\n", __func__); + return err; + } + + debug("%s: Reading eeprom from i2c address %d:0x%x\n", __func__, + oct_bus, sfp->i2c_eeprom_addr); + for (retry = 0; retry < 3; retry++) { + err = i2c_get_chip(bus->i2c_bus, sfp->i2c_eeprom_addr, 1, &dev); + if (err) { + debug("Cannot find I2C device: %d\n", err); + goto error; + } + + err = dm_i2c_read(dev, 0, buffer, 256); + if (err || !sfp_verify_checksum(buffer)) { + debug("%s: Error %d reading eeprom at 0x%x, bus %d\n", + __func__, err, sfp->i2c_eeprom_addr, oct_bus); + debug("%s: Retry %d\n", __func__, retry + 1); + mdelay(1000); + } else { + break; + } + } + if (err) { + debug("%s: Error reading eeprom from SFP %s\n", __func__, + sfp->name); + return -1; + } +#ifdef DEBUG + print_buffer(0, buffer, 1, 256, 0); +#endif + memset(&sfp->sfp_info, 0, sizeof(struct cvmx_sfp_mod_info)); + + switch (buffer[0]) { + case CVMX_SFP_CONN_SFP: + err = cvmx_sfp_parse_sfp_buffer(&sfp->sfp_info, buffer); + break; + case CVMX_SFP_CONN_QSFP: + case CVMX_SFP_CONN_QSFPP: + case CVMX_SFP_CONN_QSFP28: + case CVMX_SFP_CONN_MICRO_QSFP: + err = cvmx_sfp_parse_qsfp_buffer(&sfp->sfp_info, buffer); + break; + default: + debug("%s: Unknown SFP transceiver type 0x%x\n", __func__, + buffer[0]); + err = -1; + break; + } + +error: + if (is_qsfp) + err |= cvmx_qsfp_select(sfp, false); + + if (!err) { + sfp->valid = true; + sfp->sfp_info.valid = true; + } else { + sfp->valid = false; + sfp->sfp_info.valid = false; + } + + return err; +} + +/** + * Returns the information about a SFP/QSFP device + * + * @param sfp sfp handle + * + * @return sfp_info Pointer sfp mod info data structure + */ +const struct cvmx_sfp_mod_info * +cvmx_phy_get_sfp_mod_info(const struct cvmx_fdt_sfp_info *sfp) +{ + return (sfp) ? &sfp->sfp_info : NULL; +} + +/** + * Function called to check and return the status of the mod_abs pin or + * mod_pres pin for QSFPs. + * + * @param sfp Handle to SFP information. + * @param data User-defined data passed to the function + * + * @return 0 if absent, 1 if present, -1 on error + */ +int cvmx_sfp_check_mod_abs(struct cvmx_fdt_sfp_info *sfp, void *data) +{ + int val; + int err = 0; + int mode; + + if (!dm_gpio_is_valid(&sfp->mod_abs)) { + debug("%s: Error: mod_abs not set for %s\n", __func__, + sfp->name); + return -1; + } + val = dm_gpio_get_value(&sfp->mod_abs); + debug("%s(%s, %p) mod_abs: %d\n", __func__, sfp->name, data, val); + if (val >= 0 && val != sfp->last_mod_abs && sfp->mod_abs_changed) { + err = 0; + if (!val) { + err = cvmx_sfp_read_i2c_eeprom(sfp); + if (err) + debug("%s: Error reading SFP %s EEPROM\n", + __func__, sfp->name); + } + err = sfp->mod_abs_changed(sfp, val, sfp->mod_abs_changed_data); + } + debug("%s(%s (%p)): Last mod_abs: %d, current: %d, changed: %p, rc: %d, next: %p, caller: %p\n", + __func__, sfp->name, sfp, sfp->last_mod_abs, val, + sfp->mod_abs_changed, err, sfp->next_iface_sfp, + __builtin_return_address(0)); + + if (err >= 0) { + sfp->last_mod_abs = val; + mode = cvmx_helper_interface_get_mode(sfp->xiface); + cvmx_sfp_validate_module(sfp, mode); + } else { + debug("%s: mod_abs_changed for %s returned error\n", __func__, + sfp->name); + } + + return err < 0 ? err : val; +} + +/** + * Reads the EEPROMs of all SFP modules. + * + * @return 0 for success + */ +int cvmx_sfp_read_all_modules(void) +{ + struct cvmx_fdt_sfp_info *sfp; + int val; + bool error = false; + int rc; + + for (sfp = sfp_list; sfp; sfp = sfp->next) { + if (dm_gpio_is_valid(&sfp->mod_abs)) { + /* Check if module absent */ + val = dm_gpio_get_value(&sfp->mod_abs); + sfp->last_mod_abs = val; + if (val) + continue; + } + rc = cvmx_sfp_read_i2c_eeprom(sfp); + if (rc) { + debug("%s: Error reading eeprom from SFP %s\n", + __func__, sfp->name); + error = true; + } + } + + return error ? -1 : 0; +} + +/** + * Registers a function to be called to check mod_abs/mod_pres for a SFP/QSFP + * slot. + * + * @param sfp Handle to SFP data structure + * @param check_mod_abs Function to be called or NULL to remove + * @param mod_abs_data User-defined data to be passed to check_mod_abs + * + * @return 0 for success + */ +int cvmx_sfp_register_check_mod_abs(struct cvmx_fdt_sfp_info *sfp, + int (*check_mod_abs)(struct cvmx_fdt_sfp_info *sfp, + void *data), + void *mod_abs_data) +{ + struct cvmx_fdt_sfp_info *nsfp = sfp; /** For walking list */ + + do { + if (nsfp->xiface == sfp->xiface && nsfp->index == sfp->index) { + nsfp->check_mod_abs = check_mod_abs; + nsfp->mod_abs_data = mod_abs_data; + } + nsfp = nsfp->next; + } while (nsfp); + + return 0; +} + +/** + * Registers a function to be called whenever the mod_abs/mod_pres signal + * changes. + * + * @param sfp Handle to SFP data structure + * @param mod_abs_changed Function called whenever mod_abs is changed + * or NULL to remove. + * @param mod_abs_changed_data User-defined data passed to + * mod_abs_changed + * + * @return 0 for success + * + * @NOTE: If multiple SFP slots are linked together, all subsequent slots + * will also be registered for the same handler. + */ +int cvmx_sfp_register_mod_abs_changed(struct cvmx_fdt_sfp_info *sfp, + int (*mod_abs_changed)(struct cvmx_fdt_sfp_info *sfp, + int val, void *data), + void *mod_abs_changed_data) +{ + sfp->mod_abs_changed = mod_abs_changed; + sfp->mod_abs_changed_data = mod_abs_changed_data; + + sfp->last_mod_abs = -2; /* undefined */ + + return 0; +} + +/** + * Function called to check and return the status of the tx_fault pin + * + * @param sfp Handle to SFP information. + * @param data User-defined data passed to the function + * + * @return 0 if signal present, 1 if signal absent, -1 on error + */ +int cvmx_sfp_check_tx_fault(struct cvmx_fdt_sfp_info *sfp, void *data) +{ + int val; + + debug("%s(%s, %p)\n", __func__, sfp->name, data); + if (!dm_gpio_is_valid(&sfp->tx_error)) { + printf("%s: Error: tx_error not set for %s\n", __func__, + sfp->name); + return -1; + } + val = dm_gpio_get_value(&sfp->tx_error); + debug("%s: tx_fault: %d\n", __func__, val); + + return val; +} + +/** + * Function called to check and return the status of the rx_los pin + * + * @param sfp Handle to SFP information. + * @param data User-defined data passed to the function + * + * @return 0 if signal present, 1 if signal absent, -1 on error + */ +int cvmx_sfp_check_rx_los(struct cvmx_fdt_sfp_info *sfp, void *data) +{ + int val; + int err; + + debug("%s(%s, %p)\n", __func__, sfp->name, data); + if (!dm_gpio_is_valid(&sfp->rx_los)) { + printf("%s: Error: rx_los not set for %s\n", __func__, + sfp->name); + return -1; + } + val = dm_gpio_get_value(&sfp->rx_los); + if (val >= 0 && val != sfp->last_rx_los && sfp->rx_los_changed) + err = sfp->rx_los_changed(sfp, val, sfp->rx_los_changed_data); + debug("%s: Last rx_los: %d, current: %d, changed: %p, rc: %d\n", + __func__, sfp->last_rx_los, val, sfp->rx_los_changed, err); + sfp->last_rx_los = val; + + return val; +} + +/** + * Registers a function to be called whenever rx_los changes + * + * @param sfp Handle to SFP data structure + * @param rx_los_changed Function to be called when rx_los changes + * or NULL to remove the function + * @param rx_los_changed_data User-defined data passed to + * rx_los_changed + * + * @return 0 for success + */ +int cvmx_sfp_register_rx_los_changed(struct cvmx_fdt_sfp_info *sfp, + int (*rx_los_changed)(struct cvmx_fdt_sfp_info *sfp, + int val, void *data), + void *rx_los_changed_data) +{ + sfp->rx_los_changed = rx_los_changed; + sfp->rx_los_changed_data = rx_los_changed_data; + sfp->last_rx_los = -2; + + return 0; +} + +/** + * Parses a SFP slot from the device tree + * + * @param sfp SFP handle to store data in + * @param fdt_addr Address of flat device tree + * @param of_offset Node in device tree for SFP slot + * + * @return 0 on success, -1 on error + */ +static int cvmx_sfp_parse_sfp(struct cvmx_fdt_sfp_info *sfp, ofnode node) +{ + struct ofnode_phandle_args phandle; + int err; + + sfp->name = ofnode_get_name(node); + sfp->of_offset = ofnode_to_offset(node); + + err = gpio_request_by_name_nodev(node, "tx_disable", 0, + &sfp->tx_disable, GPIOD_IS_OUT); + if (err) { + printf("%s: tx_disable not found in DT!\n", __func__); + return -ENODEV; + } + dm_gpio_set_value(&sfp->tx_disable, 0); + + err = gpio_request_by_name_nodev(node, "mod_abs", 0, + &sfp->mod_abs, GPIOD_IS_IN); + if (err) { + printf("%s: mod_abs not found in DT!\n", __func__); + return -ENODEV; + } + + err = gpio_request_by_name_nodev(node, "tx_error", 0, + &sfp->tx_error, GPIOD_IS_IN); + if (err) { + printf("%s: tx_error not found in DT!\n", __func__); + return -ENODEV; + } + + err = gpio_request_by_name_nodev(node, "rx_los", 0, + &sfp->rx_los, GPIOD_IS_IN); + if (err) { + printf("%s: rx_los not found in DT!\n", __func__); + return -ENODEV; + } + + err = ofnode_parse_phandle_with_args(node, "eeprom", NULL, 0, 0, + &phandle); + if (!err) { + sfp->i2c_eeprom_addr = ofnode_get_addr(phandle.node); + debug("%s: eeprom address: 0x%x\n", __func__, + sfp->i2c_eeprom_addr); + + debug("%s: Getting eeprom i2c bus for %s\n", __func__, + sfp->name); + sfp->i2c_bus = cvmx_ofnode_get_i2c_bus(ofnode_get_parent(phandle.node)); + } + + err = ofnode_parse_phandle_with_args(node, "diag", NULL, 0, 0, + &phandle); + if (!err) { + sfp->i2c_diag_addr = ofnode_get_addr(phandle.node); + if (!sfp->i2c_bus) + sfp->i2c_bus = cvmx_ofnode_get_i2c_bus(ofnode_get_parent(phandle.node)); + } + + sfp->last_mod_abs = -2; + sfp->last_rx_los = -2; + + if (!sfp->i2c_bus) { + debug("%s(%s): Error: could not get i2c bus from device tree\n", + __func__, sfp->name); + err = -1; + } + + if (err) { + dm_gpio_free(sfp->tx_disable.dev, &sfp->tx_disable); + dm_gpio_free(sfp->mod_abs.dev, &sfp->mod_abs); + dm_gpio_free(sfp->tx_error.dev, &sfp->tx_error); + dm_gpio_free(sfp->rx_los.dev, &sfp->rx_los); + } else { + sfp->valid = true; + } + + return err; +} + +/** + * Parses a QSFP slot from the device tree + * + * @param sfp SFP handle to store data in + * @param fdt_addr Address of flat device tree + * @param of_offset Node in device tree for SFP slot + * + * @return 0 on success, -1 on error + */ +static int cvmx_sfp_parse_qsfp(struct cvmx_fdt_sfp_info *sfp, ofnode node) +{ + struct ofnode_phandle_args phandle; + int err; + + sfp->is_qsfp = true; + sfp->name = ofnode_get_name(node); + sfp->of_offset = ofnode_to_offset(node); + + err = gpio_request_by_name_nodev(node, "lp_mode", 0, + &sfp->lp_mode, GPIOD_IS_OUT); + if (err) { + printf("%s: lp_mode not found in DT!\n", __func__); + return -ENODEV; + } + + err = gpio_request_by_name_nodev(node, "mod_prs", 0, + &sfp->mod_abs, GPIOD_IS_IN); + if (err) { + printf("%s: mod_prs not found in DT!\n", __func__); + return -ENODEV; + } + + err = gpio_request_by_name_nodev(node, "select", 0, + &sfp->select, GPIOD_IS_IN); + if (err) { + printf("%s: select not found in DT!\n", __func__); + return -ENODEV; + } + + err = gpio_request_by_name_nodev(node, "reset", 0, + &sfp->reset, GPIOD_IS_OUT); + if (err) { + printf("%s: reset not found in DT!\n", __func__); + return -ENODEV; + } + + err = gpio_request_by_name_nodev(node, "interrupt", 0, + &sfp->interrupt, GPIOD_IS_IN); + if (err) { + printf("%s: interrupt not found in DT!\n", __func__); + return -ENODEV; + } + + err = ofnode_parse_phandle_with_args(node, "eeprom", NULL, 0, 0, + &phandle); + if (!err) { + sfp->i2c_eeprom_addr = ofnode_get_addr(phandle.node); + sfp->i2c_bus = cvmx_ofnode_get_i2c_bus(ofnode_get_parent(phandle.node)); + } + + err = ofnode_parse_phandle_with_args(node, "diag", NULL, 0, 0, + &phandle); + if (!err) { + sfp->i2c_diag_addr = ofnode_get_addr(phandle.node); + if (!sfp->i2c_bus) + sfp->i2c_bus = cvmx_ofnode_get_i2c_bus(ofnode_get_parent(phandle.node)); + } + + sfp->last_mod_abs = -2; + sfp->last_rx_los = -2; + + if (!sfp->i2c_bus) { + cvmx_printf("%s(%s): Error: could not get i2c bus from device tree\n", + __func__, sfp->name); + err = -1; + } + + if (err) { + dm_gpio_free(sfp->lp_mode.dev, &sfp->lp_mode); + dm_gpio_free(sfp->mod_abs.dev, &sfp->mod_abs); + dm_gpio_free(sfp->select.dev, &sfp->select); + dm_gpio_free(sfp->reset.dev, &sfp->reset); + dm_gpio_free(sfp->interrupt.dev, &sfp->interrupt); + } else { + sfp->valid = true; + } + + return err; +} + +/** + * Parses the device tree for SFP and QSFP slots + * + * @param fdt_addr Address of flat device-tree + * + * @return 0 for success, -1 on error + */ +int cvmx_sfp_parse_device_tree(const void *fdt_addr) +{ + struct cvmx_fdt_sfp_info *sfp, *first_sfp = NULL, *last_sfp = NULL; + ofnode node; + int err = 0; + int reg; + static bool parsed; + + debug("%s(%p): Parsing...\n", __func__, fdt_addr); + if (parsed) { + debug("%s(%p): Already parsed\n", __func__, fdt_addr); + return 0; + } + + ofnode_for_each_compatible_node(node, "ethernet,sfp-slot") { + if (!ofnode_valid(node)) + continue; + + sfp = cvm_sfp_alloc(sizeof(*sfp)); + if (!sfp) + return -1; + + err = cvmx_sfp_parse_sfp(sfp, node); + if (!err) { + if (!sfp_list) + sfp_list = sfp; + if (last_sfp) + last_sfp->next = sfp; + sfp->prev = last_sfp; + last_sfp = sfp; + debug("%s: parsed %s\n", __func__, sfp->name); + } else { + debug("%s: Error parsing SFP at node %s\n", + __func__, ofnode_get_name(node)); + return err; + } + } + + ofnode_for_each_compatible_node(node, "ethernet,qsfp-slot") { + if (!ofnode_valid(node)) + continue; + + sfp = cvm_sfp_alloc(sizeof(*sfp)); + if (!sfp) + return -1; + + err = cvmx_sfp_parse_qsfp(sfp, node); + if (!err) { + if (!sfp_list) + sfp_list = sfp; + if (last_sfp) + last_sfp->next = sfp; + sfp->prev = last_sfp; + last_sfp = sfp; + debug("%s: parsed %s\n", __func__, sfp->name); + } else { + debug("%s: Error parsing QSFP at node %s\n", + __func__, ofnode_get_name(node)); + return err; + } + } + + if (!octeon_has_feature(OCTEON_FEATURE_BGX)) + return 0; + + err = 0; + ofnode_for_each_compatible_node(node, "cavium,octeon-7890-bgx-port") { + int sfp_nodes[4]; + ofnode sfp_ofnodes[4]; + int num_sfp_nodes; + u64 reg_addr; + struct cvmx_xiface xi; + int xiface, index; + cvmx_helper_interface_mode_t mode; + int i; + int rc; + + if (!ofnode_valid(node)) + break; + + num_sfp_nodes = ARRAY_SIZE(sfp_nodes); + rc = cvmx_ofnode_lookup_phandles(node, "sfp-slot", + &num_sfp_nodes, sfp_ofnodes); + if (rc != 0 || num_sfp_nodes < 1) + rc = cvmx_ofnode_lookup_phandles(node, "qsfp-slot", + &num_sfp_nodes, + sfp_ofnodes); + /* If no SFP or QSFP slot found, go to next port */ + if (rc < 0) + continue; + + last_sfp = NULL; + for (i = 0; i < num_sfp_nodes; i++) { + sfp = cvmx_sfp_find_slot_by_fdt_node(ofnode_to_offset(sfp_ofnodes[i])); + debug("%s: Adding sfp %s (%p) to BGX port\n", + __func__, sfp->name, sfp); + if (last_sfp) + last_sfp->next_iface_sfp = sfp; + else + first_sfp = sfp; + last_sfp = sfp; + } + if (!first_sfp) { + debug("%s: Error: could not find SFP slot for BGX port %s\n", + __func__, + fdt_get_name(fdt_addr, sfp_nodes[0], + NULL)); + err = -1; + break; + } + + /* Get the port index */ + reg = ofnode_get_addr(node); + if (reg < 0) { + debug("%s: Error: could not get BGX port reg value\n", + __func__); + err = -1; + break; + } + index = reg; + + /* Get BGX node and address */ + reg_addr = ofnode_get_addr(ofnode_get_parent(node)); + /* Extrace node */ + xi.node = cvmx_csr_addr_to_node(reg_addr); + /* Extract reg address */ + reg_addr = cvmx_csr_addr_strip_node(reg_addr); + if ((reg_addr & 0xFFFFFFFFF0000000) != + 0x00011800E0000000) { + debug("%s: Invalid BGX address 0x%llx\n", + __func__, (unsigned long long)reg_addr); + xi.node = -1; + err = -1; + break; + } + + /* Extract interface from address */ + xi.interface = (reg_addr >> 24) & 0x0F; + /* Convert to xiface */ + xiface = cvmx_helper_node_interface_to_xiface(xi.node, + xi.interface); + debug("%s: Parsed %d SFP slots for interface 0x%x, index %d\n", + __func__, num_sfp_nodes, xiface, index); + + mode = cvmx_helper_interface_get_mode(xiface); + for (sfp = first_sfp; sfp; sfp = sfp->next_iface_sfp) { + sfp->xiface = xiface; + sfp->index = index; + /* Convert to IPD port */ + sfp->ipd_port[0] = + cvmx_helper_get_ipd_port(xiface, index); + debug("%s: sfp %s (%p) xi: 0x%x, index: 0x%x, node: %d, mode: 0x%x, next: %p\n", + __func__, sfp->name, sfp, sfp->xiface, + sfp->index, xi.node, mode, + sfp->next_iface_sfp); + if (mode == CVMX_HELPER_INTERFACE_MODE_XLAUI || + mode == CVMX_HELPER_INTERFACE_MODE_40G_KR4) + for (i = 1; i < 4; i++) + sfp->ipd_port[i] = -1; + else + for (i = 1; i < 4; i++) + sfp->ipd_port[i] = + cvmx_helper_get_ipd_port( + xiface, i); + } + cvmx_helper_cfg_set_sfp_info(xiface, index, first_sfp); + } + + if (!err) { + parsed = true; + cvmx_sfp_read_all_modules(); + } + + return err; +} + +/** + * Given an IPD port number find the corresponding SFP or QSFP slot + * + * @param ipd_port IPD port number to search for + * + * @return pointer to SFP data structure or NULL if not found + */ +struct cvmx_fdt_sfp_info *cvmx_sfp_find_slot_by_port(int ipd_port) +{ + struct cvmx_fdt_sfp_info *sfp = sfp_list; + int i; + + while (sfp) { + for (i = 0; i < 4; i++) + if (sfp->ipd_port[i] == ipd_port) + return sfp; + sfp = sfp->next; + } + return NULL; +} + +/** + * Given a fdt node offset find the corresponding SFP or QSFP slot + * + * @param of_offset flat device tree node offset + * + * @return pointer to SFP data structure or NULL if not found + */ +struct cvmx_fdt_sfp_info *cvmx_sfp_find_slot_by_fdt_node(int of_offset) +{ + struct cvmx_fdt_sfp_info *sfp = sfp_list; + + while (sfp) { + if (sfp->of_offset == of_offset) + return sfp; + sfp = sfp->next; + } + return NULL; +} + +static bool cvmx_sfp_validate_quad(struct cvmx_fdt_sfp_info *sfp, + struct cvmx_phy_gpio_leds *leds) +{ + bool multi_led = leds && (leds->next); + bool error = false; + int mod_abs; + + do { + /* Skip missing modules */ + if (dm_gpio_is_valid(&sfp->mod_abs)) + mod_abs = dm_gpio_get_value(&sfp->mod_abs); + else + mod_abs = 0; + if (!mod_abs) { + if (cvmx_sfp_read_i2c_eeprom(sfp)) { + debug("%s: Error reading eeprom for %s\n", + __func__, sfp->name); + } + if (sfp->sfp_info.rate < CVMX_SFP_RATE_10G) { + cvmx_helper_leds_show_error(leds, true); + error = true; + } else if (sfp->sfp_info.rate >= CVMX_SFP_RATE_10G) { + /* We don't support 10GBase-T modules in + * this mode. + */ + switch (sfp->sfp_info.cable_comp) { + case CVMX_SFP_CABLE_10GBASE_T: + case CVMX_SFP_CABLE_10GBASE_T_SR: + case CVMX_SFP_CABLE_5GBASE_T: + case CVMX_SFP_CABLE_2_5GBASE_T: + cvmx_helper_leds_show_error(leds, true); + error = true; + break; + default: + break; + } + } + } else if (multi_led) { + cvmx_helper_leds_show_error(leds, false); + } + + if (multi_led && leds->next) + leds = leds->next; + sfp = sfp->next_iface_sfp; + } while (sfp); + + if (!multi_led) + cvmx_helper_leds_show_error(leds, error); + + return error; +} + +/** + * Validates if the module is correct for the specified port + * + * @param[in] sfp SFP port to check + * @param xiface interface + * @param index port index + * @param speed link speed, -1 if unknown + * @param mode interface mode + * + * @return true if module is valid, false if invalid + * NOTE: This will also toggle the error LED, if present + */ +bool cvmx_sfp_validate_module(struct cvmx_fdt_sfp_info *sfp, int mode) +{ + const struct cvmx_sfp_mod_info *mod_info = &sfp->sfp_info; + int xiface = sfp->xiface; + int index = sfp->index; + struct cvmx_phy_gpio_leds *leds; + bool error = false; + bool quad_mode = false; + + debug("%s(%s, 0x%x, 0x%x, 0x%x)\n", __func__, sfp->name, xiface, index, + mode); + if (!sfp) { + debug("%s: Error: sfp is NULL\n", __func__); + return false; + } + /* No module is valid */ + leds = cvmx_helper_get_port_phy_leds(xiface, index); + if (!leds) + debug("%s: No leds for 0x%x:0x%x\n", __func__, xiface, index); + + if (mode != CVMX_HELPER_INTERFACE_MODE_XLAUI && + mode != CVMX_HELPER_INTERFACE_MODE_40G_KR4 && !sfp->is_qsfp && + sfp->last_mod_abs && leds) { + cvmx_helper_leds_show_error(leds, false); + debug("%s: %s: last_mod_abs: %d, no error\n", __func__, + sfp->name, sfp->last_mod_abs); + return true; + } + + switch (mode) { + case CVMX_HELPER_INTERFACE_MODE_RGMII: + case CVMX_HELPER_INTERFACE_MODE_GMII: + case CVMX_HELPER_INTERFACE_MODE_SGMII: + case CVMX_HELPER_INTERFACE_MODE_QSGMII: + case CVMX_HELPER_INTERFACE_MODE_AGL: + case CVMX_HELPER_INTERFACE_MODE_SPI: + if ((mod_info->active_cable && + mod_info->rate != CVMX_SFP_RATE_1G) || + mod_info->rate < CVMX_SFP_RATE_1G) + error = true; + break; + case CVMX_HELPER_INTERFACE_MODE_RXAUI: + case CVMX_HELPER_INTERFACE_MODE_XAUI: + case CVMX_HELPER_INTERFACE_MODE_10G_KR: + case CVMX_HELPER_INTERFACE_MODE_XFI: + if ((mod_info->active_cable && + mod_info->rate != CVMX_SFP_RATE_10G) || + mod_info->rate < CVMX_SFP_RATE_10G) + error = true; + break; + case CVMX_HELPER_INTERFACE_MODE_XLAUI: + case CVMX_HELPER_INTERFACE_MODE_40G_KR4: + if (!sfp->is_qsfp) { + quad_mode = true; + error = cvmx_sfp_validate_quad(sfp, leds); + } else { + if ((mod_info->active_cable && + mod_info->rate != CVMX_SFP_RATE_40G) || + mod_info->rate < CVMX_SFP_RATE_25G) + error = true; + } + break; + default: + debug("%s: Unsupported interface mode %d on xiface 0x%x\n", + __func__, mode, xiface); + return false; + } + debug("%s: %s: error: %d\n", __func__, sfp->name, error); + if (leds && !quad_mode) + cvmx_helper_leds_show_error(leds, error); + + return !error; +} + +/** + * Prints information about the SFP module + * + * @param[in] sfp sfp data structure + */ +void cvmx_sfp_print_info(const struct cvmx_fdt_sfp_info *sfp) +{ + const struct cvmx_sfp_mod_info *mi = &sfp->sfp_info; + const char *conn_type; + const char *mod_type; + const char *rate_str; + const char *cable_comp; + + if (!sfp) { + printf("Invalid SFP (NULL)\n"); + return; + } + + /* Please refer to the SFF-8024 and SFF-8472 documents */ + switch (mi->conn_type) { + case CVMX_SFP_CONN_GBIC: + conn_type = "GBIC"; + break; + case CVMX_SFP_CONN_SFP: + conn_type = "SFP/SFP+/SFP28"; + break; + case CVMX_SFP_CONN_QSFP: + conn_type = "QSFP"; + break; + case CVMX_SFP_CONN_QSFPP: + conn_type = "QSFP+"; + break; + case CVMX_SFP_CONN_QSFP28: + conn_type = "QSFP28"; + break; + case CVMX_SFP_CONN_MICRO_QSFP: + conn_type = "Micro QSFP"; + break; + case CVMX_SFP_CONN_QSFP_DD: + conn_type = "QSFP-DD"; + break; + case CVMX_SFP_CONN_SFP_DD: + conn_type = "SFP-DD"; + break; + default: + conn_type = "Unknown"; + break; + } + + switch (mi->mod_type) { + case CVMX_SFP_MOD_UNKNOWN: + mod_type = "Unknown"; + break; + case CVMX_SFP_MOD_OPTICAL_LC: + mod_type = "Optical LC"; + break; + case CVMX_SFP_MOD_MULTIPLE_OPTICAL: + mod_type = "Multiple Optical"; + break; + case CVMX_SFP_MOD_OPTICAL_PIGTAIL: + mod_type = "Optical Pigtail"; + break; + case CVMX_SFP_MOD_COPPER_PIGTAIL: + mod_type = "Copper Pigtail"; + break; + case CVMX_SFP_MOD_RJ45: + mod_type = "Copper RJ45"; + break; + case CVMX_SFP_MOD_NO_SEP_CONN: + mod_type = "No Separable Connector"; + break; + case CVMX_SFP_MOD_MXC_2X16: + mod_type = "MXC 2X16"; + break; + case CVMX_SFP_MOD_CS_OPTICAL: + mod_type = "CS Optical"; + break; + case CVMX_SFP_MOD_MINI_CS_OPTICAL: + mod_type = "Mini CS Optical"; + break; + case CVMX_SFP_MOD_OTHER: + mod_type = "Unknown/Other"; + break; + default: + mod_type = "Undefined"; + break; + } + + switch (mi->cable_comp) { + case CVMX_SFP_CABLE_UNSPEC: + cable_comp = ""; + break; + case CVMX_SFP_CABLE_100G_25GAUI_C2M_AOC_HIGH_BER: + if (mi->rate == CVMX_SFP_RATE_25G) + cable_comp = " 25GAUI-C2M AOC HIGH BER"; + else + cable_comp = " 100G AOC HIGH BER"; + break; + case CVMX_SFP_CABLE_100G_SR4_25G_SR: + if (mi->rate == CVMX_SFP_RATE_25G) + cable_comp = " 25GBASE-SR"; + else + cable_comp = " 100GBASE-SR4"; + break; + case CVMX_SFP_CABLE_100G_LR4_25G_LR: + if (mi->rate == CVMX_SFP_RATE_25G) + cable_comp = " 25GBASE-LR"; + else + cable_comp = " 100GBASE-LR4"; + break; + case CVMX_SFP_CABLE_100G_ER4_25G_ER: + if (mi->rate == CVMX_SFP_RATE_25G) + cable_comp = " 25GBASE-ER"; + else + cable_comp = " 100GBASE-ER4"; + break; + case CVMX_SFP_CABLE_100G_SR10: + cable_comp = " 100GBASE-SR10"; + break; + case CVMX_SFP_CABLE_100G_CWDM4_MSA: + cable_comp = " 100G CWDM4"; + break; + case CVMX_SFP_CABLE_100G_PSM4: + cable_comp = " 100G PSM4 Parallel SMF"; + break; + case CVMX_SFP_CABLE_100G_25GAUI_C2M_ACC_HIGH_BER: + if (mi->rate == CVMX_SFP_RATE_25G) + cable_comp = " 25GAUI C2M ACC"; + else + cable_comp = " 100G ACC"; + break; + case CVMX_SFP_CABLE_100G_CWDM4: + cable_comp = " 100G CWDM4 MSA"; + break; + case CVMX_SFP_CABLE_100G_CR4_25G_CR_CA_L: + if (mi->rate == CVMX_SFP_RATE_25G) + cable_comp = " 25GBASE-CR CA-L"; + else + cable_comp = " 100GBASE-CR4"; + break; + case CVMX_SFP_CABLE_25G_CR_CA_S: + cable_comp = " 25GBase-CR CA-S"; + break; + case CVMX_SFP_CABLE_25G_CR_CA_N: + cable_comp = " 25GBase-CR CA-N"; + break; + case CVMX_SFP_CABLE_40G_ER4: + cable_comp = " 40GBASE-ER4"; + break; + case CVMX_SFP_CABLE_4X10G_SR: + cable_comp = " 4 X 10GBASE-T SR"; + break; + case CVMX_SFP_CABLE_40G_PSM4: + cable_comp = " 40G PSM4 Parallel SMF"; + break; + case CVMX_SFP_CABLE_G959_1_P1I1_2D1: + cable_comp = " G959.1 profile P1I1-2D1"; + break; + case CVMX_SFP_CABLE_G959_1_P1S1_2D2: + cable_comp = " G959.1 profile P1S1-2D2"; + break; + case CVMX_SFP_CABLE_G959_1_P1L1_2D2: + cable_comp = " G959.1 profile P1L1-2D2"; + break; + case CVMX_SFP_CABLE_10GBASE_T: + cable_comp = " 10GBase-T"; + break; + case CVMX_SFP_CABLE_100G_CLR4: + cable_comp = " 10G CLR4"; + break; + case CVMX_SFP_CABLE_100G_25GAUI_C2M_AOC_LOW_BER: + if (mi->rate == CVMX_SFP_RATE_25G) + cable_comp = " 25GAUI C2M AOC LOW BER"; + else + cable_comp = " 100G AOC LOW BER"; + break; + case CVMX_SFP_CABLE_100G_25GAUI_C2M_ACC_LOW_BER: + if (mi->rate == CVMX_SFP_RATE_25G) + cable_comp = " 25GAUI C2M ACC LOW BER"; + else + cable_comp = " 100G ACC LOW BER"; + break; + case CVMX_SFP_CABLE_100G_2_LAMBDA_DWDM: + cable_comp = " 100GE-DWDM2"; + break; + case CVMX_SFP_CABLE_100G_1550NM_WDM: + cable_comp = " 100GE 1550nm WDM"; + break; + case CVMX_SFP_CABLE_10GBASE_T_SR: + cable_comp = " 10GBase-T short reach (30 meters)"; + break; + case CVMX_SFP_CABLE_5GBASE_T: + cable_comp = " 5GBase-T"; + break; + case CVMX_SFP_CABLE_2_5GBASE_T: + cable_comp = " 2.5GBase-T"; + break; + case CVMX_SFP_CABLE_40G_SWDM4: + cable_comp = " 40G SWDM4"; + break; + case CVMX_SFP_CABLE_100G_SWDM4: + cable_comp = " 100G SWDM4"; + break; + case CVMX_SFP_CABLE_100G_PAM4_BIDI: + cable_comp = " 100G SWDM4"; + break; + case CVMX_SFP_CABLE_100G_4WDM_10_FEC_HOST: + cable_comp = " 4WDM-10 MSA (10KM CWDM4) FEC in host"; + break; + case CVMX_SFP_CABLE_100G_4WDM_20_FEC_HOST: + cable_comp = " 4WDM-20 MSA (20KM 100GBASE-LR4) FEC in host"; + break; + case CVMX_SFP_CABLE_100G_4WDM_40_FEC_HOST: + cable_comp = " 4WDM-40 MSA (40KM APD receiver) FEC in host"; + break; + case CVMX_SFP_CABLE_100GBASE_DR_CAUI4_NO_FEC: + cable_comp = " 100GBASE-DR with CAUI-4, no FEC"; + break; + case CVMX_SFP_CABLE_100G_FR_CAUI4_NO_FEC: + cable_comp = " 100G-FR with CAUI-4, no FEC"; + break; + case CVMX_SFP_CABLE_100G_LR_CAUI4_NO_FEC: + cable_comp = " 100G-LR with CAUI-4, no FEC"; + break; + case CVMX_SFP_CABLE_ACTIVE_COPPER_50_100_200GAUI_LOW_BER: + cable_comp = " Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M, BER 10^(-6) or lower"; + break; + case CVMX_SFP_CABLE_ACTIVE_OPTICAL_50_100_200GAUI_LOW_BER: + cable_comp = " Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M, BER 10^(-6) or lower"; + break; + case CVMX_SFP_CABLE_ACTIVE_COPPER_50_100_200GAUI_HI_BER: + cable_comp = " Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 AUI, BER 2.6 * 10^(-4) or lower"; + break; + case CVMX_SFP_CABLE_ACTIVE_OPTICAL_50_100_200GAUI_HI_BER: + cable_comp = " Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 AUI, BER 2.6 10^(-4) or lower"; + break; + case CVMX_SFP_CABLE_50_100_200G_CR: + cable_comp = " 50GBASE-CR, 100G-CR2 or 200G-CR4"; + break; + case CVMX_SFP_CABLE_50_100_200G_SR: + cable_comp = " 50GBASE-SR, 100G-SR2 or 200G-SR4"; + break; + case CVMX_SFP_CABLE_50GBASE_FR_200GBASE_DR4: + cable_comp = " 50GBASE-FR or 200GBASE-DR4"; + break; + case CVMX_SFP_CABLE_200GBASE_FR4: + cable_comp = " 200GBASE-FR4"; + break; + case CVMX_SFP_CABLE_200G_1550NM_PSM4: + cable_comp = " 200G 1550nm PSM4"; + break; + case CVMX_SFP_CABLE_50GBASE_LR: + cable_comp = "50GBASE-LR"; + break; + case CVMX_SFP_CABLE_200GBASE_LR4: + cable_comp = " 200GBASE-LR4"; + break; + case CVMX_SFP_CABLE_64GFC_EA: + cable_comp = " 64GFC EA"; + break; + case CVMX_SFP_CABLE_64GFC_SW: + cable_comp = " 64GFC SW"; + break; + case CVMX_SFP_CABLE_64GFC_LW: + cable_comp = " 64GFC LW"; + break; + case CVMX_SFP_CABLE_128GFC_EA: + cable_comp = " 64GFC EA"; + break; + case CVMX_SFP_CABLE_128GFC_SW: + cable_comp = " 64GFC SW"; + break; + case CVMX_SFP_CABLE_128GFC_LW: + cable_comp = " 64GFC LW"; + break; + default: + cable_comp = " Unknown or Unsupported"; + break; + } + + switch (mi->rate) { + case CVMX_SFP_RATE_UNKNOWN: + default: + rate_str = "Unknown"; + break; + case CVMX_SFP_RATE_1G: + rate_str = "1G"; + break; + case CVMX_SFP_RATE_10G: + switch (mi->cable_comp) { + case CVMX_SFP_CABLE_5GBASE_T: + rate_str = "5G"; + break; + case CVMX_SFP_CABLE_2_5GBASE_T: + rate_str = "2.5G"; + break; + default: + rate_str = "10G"; + break; + } + break; + case CVMX_SFP_RATE_25G: + rate_str = "25G"; + break; + case CVMX_SFP_RATE_40G: + rate_str = "40G"; + break; + case CVMX_SFP_RATE_100G: + rate_str = "100G"; + break; + } + + cvmx_printf("%s %s%s module detected\n", mod_type, conn_type, + cable_comp); + debug("Vendor: %s\n", mi->vendor_name); + debug("Vendor OUI: %02X:%02X:%02X\n", mi->vendor_oui[0], + mi->vendor_oui[1], mi->vendor_oui[2]); + debug("Vendor part number: %s Revision: %s\n", mi->vendor_pn, + mi->vendor_rev); + debug("Manufacturing date code: %s\n", mi->date_code); + debug("Rate: %s\n", rate_str); + if (mi->copper_cable) { + debug("Copper cable type: %s\n", + mi->active_cable ? "Active" : "Passive"); + debug("Cable length: %u meters\n", mi->max_copper_cable_len); + if (mi->rate == CVMX_SFP_RATE_25G) + debug("Forward error correction is%s %s\n", + mi->fec_required ? "" : " not", + (mi->max_copper_cable_len >= 5 || + !mi->fec_required) ? + "required" : + "suggested"); + } else { + bool more = false; + + debug("Supported optical types: "); + if (mi->eth_comp & CVMX_SFP_CABLE_10GBASE_ER) { + more = !!(mi->eth_comp & ~CVMX_SFP_CABLE_10GBASE_ER); + debug("10GBase-ER%s", more ? ", " : ""); + } + if (mi->eth_comp & CVMX_SFP_CABLE_10GBASE_LRM) { + more = !!(mi->eth_comp & ~(CVMX_SFP_CABLE_10GBASE_ER | + CVMX_SFP_CABLE_10GBASE_LRM)); + debug("10GBase-LRM%s", more ? ", " : ""); + } + if (mi->eth_comp & CVMX_SFP_CABLE_10GBASE_LR) { + more = !!(mi->eth_comp & ~(CVMX_SFP_CABLE_10GBASE_ER | + CVMX_SFP_CABLE_10GBASE_LRM | + CVMX_SFP_CABLE_10GBASE_LR)); + debug("10GBase-LR%s", more ? ", " : ""); + } + if (mi->eth_comp & CVMX_SFP_CABLE_10GBASE_SR) + debug("10GBase-SR"); + debug("\nMaximum single mode cable length: %d meters\n", + mi->max_single_mode_cable_length); + debug("Maximum 62.5um OM1 cable length: %d meters\n", + mi->max_62_5um_om1_cable_length); + debug("Maximum 50um OM2 cable length: %d meters\n", + mi->max_50um_om2_cable_length); + debug("Maximum 50um OM3 cable length: %d meters\n", + mi->max_50um_om3_cable_length); + debug("Maximum 50um OM4 cable length: %d meters\n", + mi->max_50um_om4_cable_length); + debug("Laser is%s cooled\n", mi->cooled_laser ? "" : " not"); + } + debug("Limiting is %sabled\n", mi->limiting ? "en" : "dis"); + debug("Power level: %d\n", mi->power_level); + debug("RX LoS is%s implemented and is%s inverted\n", + mi->los_implemented ? "" : " not", + mi->los_inverted ? "" : " not"); + debug("TX fault is%s implemented\n", + mi->tx_fault_implemented ? "" : " not"); + debug("TX disable is%s implemented\n", + mi->tx_disable_implemented ? "" : " not"); + debug("Rate select is%s implemented\n", + mi->rate_select_implemented ? "" : " not"); + debug("RX output is %s\n", + mi->linear_rx_output ? "linear" : "limiting"); + debug("Diagnostic monitoring is%s implemented\n", + mi->diag_monitoring ? "" : " not"); + if (mi->diag_monitoring) { + const char *diag_rev; + + switch (mi->diag_rev) { + case CVMX_SFP_SFF_8472_NO_DIAG: + diag_rev = "none"; + break; + case CVMX_SFP_SFF_8472_REV_9_3: + diag_rev = "9.3"; + break; + case CVMX_SFP_SFF_8472_REV_9_5: + diag_rev = "9.5"; + break; + case CVMX_SFP_SFF_8472_REV_10_2: + diag_rev = "10.2"; + break; + case CVMX_SFP_SFF_8472_REV_10_4: + diag_rev = "10.4"; + break; + case CVMX_SFP_SFF_8472_REV_11_0: + diag_rev = "11.0"; + break; + case CVMX_SFP_SFF_8472_REV_11_3: + diag_rev = "11.3"; + break; + case CVMX_SFP_SFF_8472_REV_11_4: + diag_rev = "11.4"; + break; + case CVMX_SFP_SFF_8472_REV_12_0: + diag_rev = "12.0"; + break; + case CVMX_SFP_SFF_8472_REV_UNALLOCATED: + diag_rev = "Unallocated"; + break; + default: + diag_rev = "Unknown"; + break; + } + debug("Diagnostics revision: %s\n", diag_rev); + debug("Diagnostic address change is%s required\n", + mi->diag_addr_change_required ? "" : " not"); + debug("Diagnostics are%s internally calibrated\n", + mi->diag_internally_calibrated ? "" : " not"); + debug("Diagnostics are%s externally calibrated\n", + mi->diag_externally_calibrated ? "" : " not"); + debug("Receive power measurement type: %s\n", + mi->diag_rx_power_averaged ? "Averaged" : "OMA"); + } +} -- 2.35.1