Includes support for NIM QSFP and QSFP+. Signed-off-by: Serhii Iliushyk <sil-...@napatech.com> --- drivers/net/ntnic/meson.build | 16 +- drivers/net/ntnic/nim/i2c_nim.c | 1359 +++++++++++++++++ drivers/net/ntnic/nim/i2c_nim.h | 77 + .../net/ntnic/nim/include/qsfp_registers.h | 58 + drivers/net/ntnic/nim/nim_defines.h | 69 + drivers/net/ntnic/nim/nt_link_speed.c | 115 ++ drivers/net/ntnic/nim/nt_link_speed.h | 16 + 7 files changed, 1707 insertions(+), 3 deletions(-) create mode 100644 drivers/net/ntnic/nim/i2c_nim.c create mode 100644 drivers/net/ntnic/nim/i2c_nim.h create mode 100644 drivers/net/ntnic/nim/include/qsfp_registers.h create mode 100644 drivers/net/ntnic/nim/nim_defines.h create mode 100644 drivers/net/ntnic/nim/nt_link_speed.c create mode 100644 drivers/net/ntnic/nim/nt_link_speed.h
diff --git a/drivers/net/ntnic/meson.build b/drivers/net/ntnic/meson.build index c6e4a9fd5b..a622fa7ed5 100644 --- a/drivers/net/ntnic/meson.build +++ b/drivers/net/ntnic/meson.build @@ -42,6 +42,7 @@ includes = [ include_directories('nthw'), include_directories('nthw/supported'), include_directories('nthw/model'), + include_directories('nim/include'), ] # ntnic_core @@ -61,6 +62,9 @@ if fs.is_dir('nthw/core/nt400dxx') includes += include_directories('nthw/core/nt400dxx') includes += include_directories('nthw/core/nt400dxx/reset') endif +includes += include_directories('adapter') +includes += include_directories('link_mgmt') +includes += include_directories('nim') if fs.is_dir('nim/sfp_sensors') includes += include_directories('nim/sfp_sensors') endif @@ -76,10 +80,14 @@ headers = files('rte_pmd_ntnic.h') # all sources sources = files( + 'adapter/nt4ga_adapter.c', + 'adapter/nt4ga_pci_ta_tg.c', + 'adapter/nt4ga_tfg.c', 'dpdk_mod_reg.c', - 'drivers/net/ntnic/driver/modules/adapter/nt4ga_adapter.c', - 'drivers/net/ntnic/driver/modules/adapter/nt4ga_pci_ta_tg.c', - 'drivers/net/ntnic/driver/modules/adapter/nt4ga_tfg.c', + 'link_mgmt/link_100g/nt4ga_link_100g.c', + 'link_mgmt/nt4ga_link.c', + 'nim/i2c_nim.c', + 'nim/nt_link_speed.c', 'nthw/supported/nthw_fpga_9563_055_039_0000.c', 'nthw/supported/nthw_fpga_instances.c', 'nthw/supported/nthw_fpga_mod_str_map.c', @@ -91,9 +99,11 @@ sources = files( 'nthw/core/nthw_fpga_rst.c', 'nthw/core/nthw_gfg.c', 'nthw/core/nthw_gmf.c', + 'nthw/core/nthw_gpio_phy.c', 'nthw/core/nthw_hif.c', 'nthw/core/nthw_i2cm.c', 'nthw/core/nthw_iic.c', + 'nthw/core/nthw_mac_pcs.c', 'nthw/core/nthw_mac_pcs_xxv.c', 'nthw/core/nthw_pcie3.c', 'nthw/core/nthw_pci_rd_tg.c', diff --git a/drivers/net/ntnic/nim/i2c_nim.c b/drivers/net/ntnic/nim/i2c_nim.c new file mode 100644 index 0000000000..c09f94d515 --- /dev/null +++ b/drivers/net/ntnic/nim/i2c_nim.c @@ -0,0 +1,1359 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2023 Napatech A/S + */ + +#include "nthw_drv.h" +#include "i2c_nim.h" +#include "ntlog.h" +#include "nt_util.h" +#include "ntnic_mod_reg.h" + +#include "qsfp_registers.h" + +#include <assert.h> +#include <string.h> /* memcmp, memset */ + +/* + * Nim functions + */ +#define QSFP_SUP_LEN_INFO_LIN_ADDR 142 /* 5bytes */ +#define QSFP_TRANSMITTER_TYPE_LIN_ADDR 147 /* 1byte */ +#define QSFP_CONTROL_STATUS_LIN_ADDR 86 +#define QSFP_SOFT_TX_ALL_DISABLE_BITS 0x0F +#define QSFP_SPEC_COMPLIANCE_CODES_ADDR 131 /* 8 bytes */ +#define QSFP_EXT_SPEC_COMPLIANCE_CODES_ADDR 192 /* 1 byte */ +#define NIM_READ false +#define NIM_WRITE true +#define NIM_PAGE_SEL_REGISTER 127 +#define NIM_I2C_0XA0 0xA0 /* Basic I2C address */ +#define QSFP_VENDOR_NAME_LIN_ADDR 148 /* 16bytes */ +#define QSFP_VENDOR_PN_LIN_ADDR 168 /* 16bytes */ +#define QSFP_VENDOR_SN_LIN_ADDR 196 /* 16bytes */ +#define QSFP_VENDOR_DATE_LIN_ADDR 212 /* 8bytes */ +#define QSFP_VENDOR_REV_LIN_ADDR 184 /* 2bytes */ +#define QSFP_DMI_OPTION_LIN_ADDR 220 + +#define QSFP_EXTENDED_IDENTIFIER 129 +#define QSFP_POWER_CLASS_BITS_1_4 0xC0 +#define QSFP_POWER_CLASS_BITS_5_7 0x03 + +static bool page_addressing(nt_nim_identifier_t id) +{ + switch (id) { + case NT_NIM_SFP_SFP_PLUS: + return false; + + case NT_NIM_XFP: + return true; + + case NT_NIM_QSFP: + case NT_NIM_QSFP_PLUS: + case NT_NIM_QSFP28: + return true; + + default: + NT_LOG(DBG, ETHDEV, "%s: Unknown NIM identifier %d\n", __func__, id); + return false; + } +} + +nt_nim_identifier_t translate_nimid(const nim_i2c_ctx_t *ctx) +{ + return (nt_nim_identifier_t)ctx->nim_id; +} + +static int nim_read_write_i2c_data(nim_i2c_ctx_p ctx, bool do_write, uint16_t lin_addr, + uint8_t i2c_addr, uint8_t a_reg_addr, uint8_t seq_cnt, + uint8_t *p_data) +{ + /* Divide i2c_addr by 2 because nthw_iic_read/writeData multiplies by 2 */ + const uint8_t i2c_devaddr = i2c_addr / 2U; + (void)lin_addr; /* Unused */ + + if (do_write) { + if (ctx->type == I2C_HWIIC) { + return nthw_iic_write_data(&ctx->hwiic, i2c_devaddr, a_reg_addr, seq_cnt, + p_data); + + } else { + return 0; + } + + } else if (ctx->type == I2C_HWIIC) { + return nthw_iic_read_data(&ctx->hwiic, i2c_devaddr, a_reg_addr, seq_cnt, p_data); + + } else { + return 0; + } +} + +/* + * ------------------------------------------------------------------------------ + * Selects a new page for page addressing. This is only relevant if the NIM + * supports this. Since page switching can take substantial time the current page + * select is read and subsequently only changed if necessary. + * Important: + * XFP Standard 8077, Ver 4.5, Page 61 states that: + * If the host attempts to write a table select value which is not supported in + * a particular module, the table select byte will revert to 01h. + * This can lead to some surprising result that some pages seems to be duplicated. + * ------------------------------------------------------------------------------ + */ + +static int nim_setup_page(nim_i2c_ctx_p ctx, uint8_t page_sel) +{ + uint8_t curr_page_sel; + + /* Read the current page select value */ + if (nim_read_write_i2c_data(ctx, NIM_READ, NIM_PAGE_SEL_REGISTER, NIM_I2C_0XA0, + NIM_PAGE_SEL_REGISTER, sizeof(curr_page_sel), + &curr_page_sel) != 0) { + return -1; + } + + /* Only write new page select value if necessary */ + if (page_sel != curr_page_sel) { + if (nim_read_write_i2c_data(ctx, NIM_WRITE, NIM_PAGE_SEL_REGISTER, NIM_I2C_0XA0, + NIM_PAGE_SEL_REGISTER, sizeof(page_sel), + &page_sel) != 0) { + return -1; + } + } + + return 0; +} + +static int nim_read_write_data_lin(nim_i2c_ctx_p ctx, bool m_page_addressing, uint16_t lin_addr, + uint16_t length, uint8_t *p_data, bool do_write) +{ + uint16_t i; + uint8_t a_reg_addr; /* The actual register address in I2C device */ + uint8_t i2c_addr; + int block_size = 128; /* Equal to size of MSA pages */ + int seq_cnt; + int max_seq_cnt = 1; + int multi_byte = 1; /* One byte per I2C register is default */ + + for (i = 0; i < length;) { + bool use_page_select = false; + + /* + * Find out how much can be read from the current block in case of + * single byte access + */ + if (multi_byte == 1) + max_seq_cnt = block_size - (lin_addr % block_size); + + if (m_page_addressing) { + if (lin_addr >= 128) { /* Only page setup above this address */ + use_page_select = true; + + /* Map to [128..255] of 0xA0 device */ + a_reg_addr = (uint8_t)(block_size + (lin_addr % block_size)); + + } else { + a_reg_addr = (uint8_t)lin_addr; + } + + i2c_addr = NIM_I2C_0XA0;/* Base I2C address */ + + } else if (lin_addr >= 256) { + /* Map to address [0..255] of 0xA2 device */ + a_reg_addr = (uint8_t)(lin_addr - 256); + i2c_addr = NIM_I2C_0XA2; + + } else { + a_reg_addr = (uint8_t)lin_addr; + i2c_addr = NIM_I2C_0XA0;/* Base I2C address */ + } + + /* Now actually do the reading/writing */ + seq_cnt = length - i; /* Number of remaining bytes */ + + if (seq_cnt > max_seq_cnt) + seq_cnt = max_seq_cnt; + + /* + * Read a number of bytes without explicitly specifying a new address. + * This can speed up I2C access since automatic incrementation of the + * I2C device internal address counter can be used. It also allows + * a HW implementation, that can deal with block access. + * Furthermore it also allows for access to data that must be accessed + * as 16bit words reading two bytes at each address eg PHYs. + */ + if (use_page_select) { + if (nim_setup_page(ctx, (uint8_t)((lin_addr / 128) - 1)) != 0) { + NT_LOG(ERR, ETHDEV, + "%s: Cannot set up page for linear address %u\n", __func__, + lin_addr); + return -1; + } + } + + if (nim_read_write_i2c_data(ctx, do_write, lin_addr, i2c_addr, a_reg_addr, + (uint8_t)seq_cnt, p_data) != 0) { + NT_LOG(ERR, ETHDEV, "%s: Call to nim_read_write_i2c_data failed\n", + __func__); + return -1; + } + + p_data += seq_cnt; + i = (uint16_t)(i + seq_cnt); + lin_addr = (uint16_t)(lin_addr + (seq_cnt / multi_byte)); + } + + return 0; +} + +int read_data_lin(nim_i2c_ctx_p ctx, uint16_t lin_addr, uint16_t length, void *data) +{ + /* Wrapper for using Mutex for QSFP TODO */ + return nim_read_write_data_lin(ctx, page_addressing(ctx->nim_id), lin_addr, length, data, + NIM_READ); +} + +/* Read and return a single byte */ +static uint8_t read_byte(nim_i2c_ctx_p ctx, uint16_t addr) +{ + uint8_t data; + read_data_lin(ctx, addr, sizeof(data), &data); + return data; +} + +static int nim_read_id(nim_i2c_ctx_t *ctx) +{ + /* We are only reading the first byte so we don't care about pages here. */ + const bool USE_PAGE_ADDRESSING = false; + + if (nim_read_write_data_lin(ctx, USE_PAGE_ADDRESSING, NIM_IDENTIFIER_ADDR, + sizeof(ctx->nim_id), &ctx->nim_id, NIM_READ) != 0) { + return -1; + } + + return 0; +} + +static int i2c_nim_common_construct(nim_i2c_ctx_p ctx) +{ + ctx->nim_id = 0; + int res; + + if (ctx->type == I2C_HWIIC) + res = nim_read_id(ctx); + + else + res = -1; + + if (res) { + NT_LOG(ERR, PMD, "Can't read NIM id."); + return res; + } + + memset(ctx->vendor_name, 0, sizeof(ctx->vendor_name)); + memset(ctx->prod_no, 0, sizeof(ctx->prod_no)); + memset(ctx->serial_no, 0, sizeof(ctx->serial_no)); + memset(ctx->date, 0, sizeof(ctx->date)); + memset(ctx->rev, 0, sizeof(ctx->rev)); + + ctx->content_valid = false; + memset(ctx->len_info, 0, sizeof(ctx->len_info)); + ctx->pwr_level_req = 0; + ctx->pwr_level_cur = 0; + ctx->avg_pwr = false; + ctx->tx_disable = false; + ctx->lane_idx = -1; + ctx->lane_count = 1; + ctx->options = 0; + return 0; +} + +static int nim_read_vendor_info(nim_i2c_ctx_p ctx, uint16_t addr, uint8_t max_len, char *p_data); + +static void qsfp_read_vendor_info(nim_i2c_ctx_t *ctx) +{ + nim_read_vendor_info(ctx, QSFP_VENDOR_NAME_LIN_ADDR, sizeof(ctx->vendor_name), + ctx->vendor_name); + nim_read_vendor_info(ctx, QSFP_VENDOR_PN_LIN_ADDR, sizeof(ctx->prod_no), ctx->prod_no); + nim_read_vendor_info(ctx, QSFP_VENDOR_SN_LIN_ADDR, sizeof(ctx->serial_no), ctx->serial_no); + nim_read_vendor_info(ctx, QSFP_VENDOR_DATE_LIN_ADDR, sizeof(ctx->date), ctx->date); + nim_read_vendor_info(ctx, QSFP_VENDOR_REV_LIN_ADDR, (uint8_t)(sizeof(ctx->rev) - 2), + ctx->rev); /*OBS Only two bytes*/ +} +static int qsfp_nim_state_build(nim_i2c_ctx_t *ctx, sfp_nim_state_t *state) +{ + int res = 0; /* unused due to no readings from HW */ + + assert(ctx && state); + assert(ctx->nim_id != NT_NIM_UNKNOWN && "Nim is not initialized"); + + (void)memset(state, 0, sizeof(*state)); + + switch (ctx->nim_id) { + case 12U: + state->br = 10U;/* QSFP: 4 x 1G = 4G */ + break; + + case 13U: + state->br = 103U; /* QSFP+: 4 x 10G = 40G */ + break; + + case 17U: + state->br = 255U; /* QSFP28: 4 x 25G = 100G */ + break; + + default: + NT_LOG(INF, PMD, "%s:%d nim_id = %u is not an QSFP/QSFP+/QSFP28 module\n", + __func__, __LINE__, ctx->nim_id); + res = -1; + } + + return res; +} + +int nim_state_build(nim_i2c_ctx_t *ctx, sfp_nim_state_t *state) +{ + if (translate_nimid(ctx) == NT_NIM_SFP_SFP_PLUS) + return -1; + + else + return qsfp_nim_state_build(ctx, state); +} + +const char *nim_id_to_text(uint8_t nim_id) +{ + switch (nim_id) { + case 0x0: + return "UNKNOWN"; + + case 0x1: + return "GBIC"; + + case 0x2: + return "FIXED"; + + case 0x3: + return "SFP/SFP+"; + + case 0x04: + return "300 pin XBI"; + + case 0x05: + return "XEN-PAK"; + + case 0x06: + return "XFP"; + + case 0x07: + return "XFF"; + + case 0x08: + return "XFP-E"; + + case 0x09: + return "XPAK"; + + case 0x0A: + return "X2"; + + case 0x0B: + return "DWDM"; + + case 0x0C: + return "QSFP"; + + case 0x0D: + return "QSFP+"; + + case 0x11: + return "QSFP28"; + + case 0x12: + return "CFP4"; + + default: + return "ILLEGAL!"; + } +} + +/* + * Disable laser for specific lane or all lanes + */ +int nim_qsfp_plus_nim_set_tx_laser_disable(nim_i2c_ctx_p ctx, bool disable, int lane_idx) +{ + uint8_t value; + uint8_t mask; + const bool pg_addr = page_addressing(ctx->nim_id); + + if (lane_idx < 0) /* If no lane is specified then all lanes */ + mask = QSFP_SOFT_TX_ALL_DISABLE_BITS; + + else + mask = (uint8_t)(1U << lane_idx); + + if (nim_read_write_data_lin(ctx, pg_addr, QSFP_CONTROL_STATUS_LIN_ADDR, sizeof(value), + &value, NIM_READ) != 0) { + return -1; + } + + if (disable) + value |= mask; + + else + value &= (uint8_t)(~mask); + + if (nim_read_write_data_lin(ctx, pg_addr, QSFP_CONTROL_STATUS_LIN_ADDR, sizeof(value), + &value, NIM_WRITE) != 0) { + return -1; + } + + return 0; +} + +/* + * Read vendor information at a certain address. Any trailing whitespace is + * removed and a missing string termination in the NIM data is handled. + */ +static int nim_read_vendor_info(nim_i2c_ctx_p ctx, uint16_t addr, uint8_t max_len, char *p_data) +{ + const bool pg_addr = page_addressing(ctx->nim_id); + int i; + /* Subtract "1" from max_len that includes a terminating "0" */ + + if (nim_read_write_data_lin(ctx, pg_addr, addr, (uint8_t)(max_len - 1), (uint8_t *)p_data, + NIM_READ) != 0) { + return -1; + } + + /* Terminate at first found white space */ + for (i = 0; i < max_len - 1; i++) { + if (*p_data == ' ' || *p_data == '\n' || *p_data == '\t' || *p_data == '\v' || + *p_data == '\f' || *p_data == '\r') { + *p_data = '\0'; + return 0; + } + + p_data++; + } + + /* + * Add line termination as the very last character, if it was missing in the + * NIM data + */ + *p_data = '\0'; + return 0; +} + +/* + * Import length info in various units from NIM module data and convert to meters + */ +static void nim_import_len_info(nim_i2c_ctx_p ctx, uint8_t *p_nim_len_info, uint16_t *p_nim_units) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(ctx->len_info); i++) + if (*(p_nim_len_info + i) == 255) { + ctx->len_info[i] = 65535; + + } else { + uint32_t len = *(p_nim_len_info + i) * *(p_nim_units + i); + + if (len > 65535) + ctx->len_info[i] = 65535; + + else + ctx->len_info[i] = (uint16_t)len; + } +} + +static int qsfpplus_read_basic_data(nim_i2c_ctx_t *ctx) +{ + const bool pg_addr = page_addressing(ctx->nim_id); + uint8_t options; + uint8_t value; + uint8_t nim_len_info[5]; + uint16_t nim_units[5] = { 1000, 2, 1, 1, 1 }; /* QSFP MSA units in meters */ + const char *yes_no[2] = { "No", "Yes" }; + (void)yes_no; + NT_LOG(DBG, ETHDEV, "Instance %d: NIM id: %s (%d)\n", ctx->instance, + nim_id_to_text(ctx->nim_id), ctx->nim_id); + + /* Read DMI options */ + if (nim_read_write_data_lin(ctx, pg_addr, QSFP_DMI_OPTION_LIN_ADDR, sizeof(options), + &options, NIM_READ) != 0) { + return -1; + } + + ctx->avg_pwr = options & QSFP_DMI_AVG_PWR_BIT; + NT_LOG(DBG, ETHDEV, "Instance %d: NIM options: (DMI: Yes, AvgPwr: %s)\n", ctx->instance, + yes_no[ctx->avg_pwr]); + + qsfp_read_vendor_info(ctx); + NT_LOG(DBG, PMD, + "Instance %d: NIM info: (Vendor: %s, PN: %s, SN: %s, Date: %s, Rev: %s)\n", + ctx->instance, ctx->vendor_name, ctx->prod_no, ctx->serial_no, ctx->date, ctx->rev); + + if (nim_read_write_data_lin(ctx, pg_addr, QSFP_SUP_LEN_INFO_LIN_ADDR, sizeof(nim_len_info), + nim_len_info, NIM_READ) != 0) { + return -1; + } + + /* + * Returns supported length information in meters for various fibers as 5 indivi- + * dual values: [SM(9um), EBW(50um), MM(50um), MM(62.5um), Copper] + * If no length information is available for a certain entry, the returned value + * will be zero. This will be the case for SFP modules - EBW entry. + * If the MSBit is set the returned value in the lower 31 bits indicates that the + * supported length is greater than this. + */ + + nim_import_len_info(ctx, nim_len_info, nim_units); + + /* Read required power level */ + if (nim_read_write_data_lin(ctx, pg_addr, QSFP_EXTENDED_IDENTIFIER, sizeof(value), &value, + NIM_READ) != 0) { + return -1; + } + + /* + * Get power class according to SFF-8636 Rev 2.7, Table 6-16, Page 43: + * If power class >= 5 setHighPower must be called for the module to be fully + * functional + */ + if ((value & QSFP_POWER_CLASS_BITS_5_7) == 0) { + /* NIM in power class 1 - 4 */ + ctx->pwr_level_req = (uint8_t)(((value & QSFP_POWER_CLASS_BITS_1_4) >> 6) + 1); + + } else { + /* NIM in power class 5 - 7 */ + ctx->pwr_level_req = (uint8_t)((value & QSFP_POWER_CLASS_BITS_5_7) + 4); + } + + return 0; +} + +/* + * Read the vendor product number and from this determine which QSFP DMI options + * that are present. This list also covers QSFP28 modules. + * This function should be used if automatic detection does not work. + */ +static bool qsfpplus_get_qsfp_options_from_pn(nim_i2c_ctx_p ctx) +{ + if (strcmp(ctx->prod_no, "FTL410QE1C") == 0) { + /* FINISAR FTL410QE1C, QSFP+ */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_TX_BIAS) | (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "FTL410QE2C") == 0) { + /* FINISAR FTL410QE2C, QSFP+ */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY); + + } else if (strcmp(ctx->prod_no, "FTL4C1QE1C") == 0) { + /* FINISAR FTL4C1QE1C, QSFP+ */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "AFBR-79E4Z") == 0) { + /* + * AFBR-79E4Z: The digital diagnostic accuracy is not guaranteed so only + * the mandatory temperature sensor is made available (although it will + * also be inaccurate) + */ + /* AVAGO 79E4Z, QSFP+ */ + ctx->options = (1 << NIM_OPTION_TEMP); + + } else if (strcmp(ctx->prod_no, "AFBR-79E4Z-D") == 0) { + /* AVAGO 79E4Z-D, QSFP+ */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "AFBR-79EQDZ") == 0) { + /* AVAGO 79EQDZ, QSFP+ */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "AFBR-79EBRZ") == 0) { + /* + * Avago RxOnly BiDi NIM + * No sensors available not even the normally mandatory temp sensor and this + * is ok since the temp sensor is not mandatory on active optical modules + */ + /* SFF-8436_rev4.1, p67 */ + ctx->options = (1 << NIM_OPTION_RX_ONLY); + + } else if (strcmp(ctx->prod_no, "AFBR-79EBPZ-NU1") == 0) { + /* + * Avago RxTx BiDi NIM + * No sensors available not even the normally mandatory temp sensor and this + * is ok since the temp sensor is not mandatory on active optical modules + */ + ctx->options = 0; + + } else if (strcmp(ctx->prod_no, "AFBR-79EBPZ") == 0) { + /* + * Avago RxTx BiDi NIM + * No sensors available not even the normally mandatory temp sensor and this + * is ok since the temp sensor is not mandatory on active optical modules + */ + ctx->options = 0; + + } else if (strcmp(ctx->prod_no, "AFBR-89CDDZ") == 0) { + /* AVAGO 89CDDZ, QSFP28 */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "AFBR-89BDDZ") == 0) { + /* AVAGO 89BDDZ, QSFP28, BiDi */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "AFBR-89BRDZ") == 0) { + /* + * AVAGO 89BRDZ, QSFP28, BiDi, RxOnly + * but sensors have been set as above except for Tx sensors + */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_RX_ONLY); + /* + * According to mail correspondence AFBR-89BRDZ is a RxOnly version of + * AFBR-89BDDZ with lasers default off. + * The lasers can be turned on however but should probably not because the + * receivers might be degraded, and this is the cause for selling them as RxOnly. + */ + + } else if (strcmp(ctx->prod_no, "SQF1000L4LNGG01P") == 0) { + /* Sumitomo SQF1000L4LNGG01P, QSFP28 */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "SQF1000L4LNGG01B") == 0) { + /* Sumitomo SQF1000L4LNGG01B, QSFP28 */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "SQF1001L4LNGG01P") == 0) { + /* Sumitomo SQF1001L4LNGG01P, QSFP28 */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "SQF1001L4LNGG01B") == 0) { + /* Sumitomo SQF1001L4LNGG01B, QSFP28 */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "SQF1002L4LNGG01B") == 0) { + /* Sumitomo SQF1002L4LNGG01B, QSFP28 */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "FIM37700/171") == 0) { + /* Fujitsu FIM37700/171, QSFP28 */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "FIM37700/172") == 0) { + /* Fujitsu FIM37700/172, QSFP28 */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "TR-FC85S-NVS") == 0) { + /* InnoLight TR-FC85S-NVS, QSFP28 */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "TR-FC13L-NVS") == 0) { + /* InnoLight TR-FC13L-NVS, QSFP28 */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "FTLC9551REPM") == 0) { + /* Finisar FTLC9551REPM, QSFP28 */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else if (strcmp(ctx->prod_no, "FTLC9558REPM") == 0) { + /* Finisar FTLC9558REPM, QSFP28 */ + ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) | + (1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) | + (1 << NIM_OPTION_TX_POWER); + + } else { + /* + * DO NOTE: The temperature sensor is not mandatory on active/passive copper + * and active optical modules + */ + ctx->options = (1 << NIM_OPTION_TEMP); + return false; + } + + return true; +} + +/* + * Try to figure out if a sensor is present by reading its value(s) and its limits. + * This is a highly impirical way that cannot be guaranteed to give the correct + * result but it was a wish not to be dependent on a PN table based solution. + */ +static void qsfpplus_find_qsfp_sensor_option(nim_i2c_ctx_p ctx, + uint16_t value_addr, + uint8_t lane_count, + uint16_t limit_addr, + bool two_compl, + uint32_t sensor_option) +{ + uint8_t data[8]; + int i, j; + int value; + int value_list[4]; + int limit; + int limit_list[4]; + bool present; + + /* Read current value(s) */ + read_data_lin(ctx, value_addr, (uint16_t)(lane_count * 2), data); + + for (j = 0; j < lane_count; j++) { + value = 0; + + for (i = 0; i < 2; i++) { + value = value << 8; + value += data[2 * j + i]; + } + + if (two_compl && value >= 0x8000) + value = value - 0x10000; + + value_list[j] = value; + } + + /* Read limits Warning high/low Alarm high/low 4 values each two bytes */ + read_data_lin(ctx, limit_addr, 8, data); + + for (j = 0; j < 4; j++) { + limit = 0; + + for (i = 0; i < 2; i++) { + limit = limit << 8; + limit += data[2 * j + i]; + } + + if (two_compl && limit >= 0x8000) + limit = limit - 0x10000; + + limit_list[j] = limit; + } + + /* Find out if limits contradicts each other */ + int alarm_high = limit_list[0]; + int alarm_low = limit_list[1]; + int warn_high = limit_list[2]; + int warn_low = limit_list[3]; + + bool alarm_limits = false; /* Are they present - that is both not zero */ + bool warn_limits = false; + bool limit_conflict = false; + + if (alarm_high != 0 || alarm_low != 0) { + alarm_limits = true; + + if (alarm_high <= alarm_low) + limit_conflict = true; + } + + if (warn_high != 0 || warn_low != 0) { + warn_limits = true; + + /* Warning limits must be least restrictive */ + if (warn_high <= warn_low) + limit_conflict = true; + + else if (warn_high > alarm_high || warn_low < alarm_low) + limit_conflict = true; + } + + /* Try to deduce if the sensor is present or not */ + present = false; + + if (limit_conflict) { + present = false; + + } else if (warn_limits || alarm_limits) { + /* Is one or both present and not contradictory */ + present = true; + + } else { + /* + * All limits are zero - look at the sensor value + * If one sensor is non-zero the sensor is set to be present + */ + for (j = 0; j < lane_count; j++) { + if (value_list[j] != 0) { + present = true; + break; + } + } + + /* + * If all limits and values are zero then present will be false here. In this + * case it is assumed that the sensor is not present: + * Experience indicates that for QSFP+ modules RxPwr will be non-zero even with + * no optical input. QSFP28 modules however can easily have RxPwr equal to zero + * with no optical input. + * For all investigated modules it was found that if RxPwr is implemented then + * the limits are also set. This is not always the case with TxBias and TxPwr + * but here the measured values will be non-zero when the laser is on what it + * will be just after initialization since it has no external hardware disable. + */ + } + + if (present) + ctx->options |= (1U << sensor_option); +} + +/* + * Find active QSFP sensors. + */ +static void qsfpplus_get_qsfp_options_from_data(nim_i2c_ctx_p ctx) +{ + ctx->options = 0; + + qsfpplus_find_qsfp_sensor_option(ctx, QSFP_TEMP_LIN_ADDR, 1, QSFP_TEMP_THRESH_LIN_ADDR, + true, NIM_OPTION_TEMP); + + qsfpplus_find_qsfp_sensor_option(ctx, QSFP_VOLT_LIN_ADDR, 1, QSFP_VOLT_THRESH_LIN_ADDR, + false, NIM_OPTION_SUPPLY); + + qsfpplus_find_qsfp_sensor_option(ctx, QSFP_RX_PWR_LIN_ADDR, 4, QSFP_RX_PWR_THRESH_LIN_ADDR, + false, NIM_OPTION_RX_POWER); + + qsfpplus_find_qsfp_sensor_option(ctx, QSFP_TX_PWR_LIN_ADDR, 4, QSFP_TX_PWR_THRESH_LIN_ADDR, + false, NIM_OPTION_TX_POWER); + + qsfpplus_find_qsfp_sensor_option(ctx, QSFP_TX_BIAS_LIN_ADDR, 4, QSFP_BIAS_THRESH_LIN_ADDR, + false, NIM_OPTION_TX_BIAS); +} + +static void qsfp28_find_port_params(nim_i2c_ctx_p ctx) +{ + uint8_t fiber_chan_speed; + + /* Table 6-17 SFF-8636 */ + read_data_lin(ctx, QSFP_SPEC_COMPLIANCE_CODES_ADDR, 1, &fiber_chan_speed); + + if (fiber_chan_speed & (1 << 7)) { + /* SFF-8024, Rev 4.7, Table 4-4 */ + uint8_t extended_specification_compliance_code = 0; + read_data_lin(ctx, QSFP_EXT_SPEC_COMPLIANCE_CODES_ADDR, 1, + &extended_specification_compliance_code); + + switch (extended_specification_compliance_code) { + case 0x02: + ctx->port_type = NT_PORT_TYPE_QSFP28_SR4; + break; + + case 0x03: + ctx->port_type = NT_PORT_TYPE_QSFP28_LR4; + break; + + case 0x0B: + ctx->port_type = NT_PORT_TYPE_QSFP28_CR_CA_L; + break; + + case 0x0C: + ctx->port_type = NT_PORT_TYPE_QSFP28_CR_CA_S; + break; + + case 0x0D: + ctx->port_type = NT_PORT_TYPE_QSFP28_CR_CA_N; + break; + + case 0x25: + ctx->port_type = NT_PORT_TYPE_QSFP28_DR; + break; + + case 0x26: + ctx->port_type = NT_PORT_TYPE_QSFP28_FR; + break; + + case 0x27: + ctx->port_type = NT_PORT_TYPE_QSFP28_LR; + break; + + default: + ctx->port_type = NT_PORT_TYPE_QSFP28; + } + + } else { + ctx->port_type = NT_PORT_TYPE_QSFP28; + } +} + +/* + * If true the user must actively select the desired rate. If false the module + * however can still support several rates without the user is required to select + * one of them. Supported rates must then be deduced from the product number. + * SFF-8636, Rev 2.10a: + * p40: 6.2.7 Rate Select + * p85: A.2 Rate Select + */ +static bool qsfp28_is_rate_selection_enabled(nim_i2c_ctx_p ctx) +{ + const uint8_t ext_rate_select_compl_reg_addr = 141; + const uint8_t options_reg_addr = 195; + const uint8_t enh_options_reg_addr = 221; + + uint8_t rate_select_ena = (read_byte(ctx, options_reg_addr) >> 5) & 0x01; /* bit: 5 */ + + if (rate_select_ena == 0) + return false; + + uint8_t rate_select_type = + (read_byte(ctx, enh_options_reg_addr) >> 2) & 0x03; /* bit 3..2 */ + + if (rate_select_type != 2) { + NT_LOG(DBG, PMD, "NIM has unhandled rate select type (%d)", rate_select_type); + return false; + } + + uint8_t ext_rate_select_ver = + read_byte(ctx, ext_rate_select_compl_reg_addr) & 0x03; /* bit 1..0 */ + + if (ext_rate_select_ver != 0x02) { + NT_LOG(DBG, PMD, "NIM has unhandled extended rate select version (%d)", + ext_rate_select_ver); + return false; + } + + return true; /* When true selectRate() can be used */ +} + +static void qsfp28_set_speed_mask(nim_i2c_ctx_p ctx) +{ + if (ctx->port_type == NT_PORT_TYPE_QSFP28_FR || ctx->port_type == NT_PORT_TYPE_QSFP28_DR || + ctx->port_type == NT_PORT_TYPE_QSFP28_LR) { + if (ctx->lane_idx < 0) + ctx->speed_mask = NT_LINK_SPEED_100G; + + else + /* PAM-4 modules can only run on all lanes together */ + ctx->speed_mask = 0; + + } else { + if (ctx->lane_idx < 0) + ctx->speed_mask = NT_LINK_SPEED_100G; + + else + ctx->speed_mask = NT_LINK_SPEED_25G; + + if (qsfp28_is_rate_selection_enabled(ctx)) { + /* + * It is assumed that if the module supports dual rates then the other rate + * is 10G per lane or 40G for all lanes. + */ + if (ctx->lane_idx < 0) + ctx->speed_mask |= NT_LINK_SPEED_40G; + + else + ctx->speed_mask = NT_LINK_SPEED_10G; + } + } +} + +static void qsfpplus_find_port_params(nim_i2c_ctx_p ctx) +{ + uint8_t device_tech; + read_data_lin(ctx, QSFP_TRANSMITTER_TYPE_LIN_ADDR, sizeof(device_tech), &device_tech); + + switch (device_tech & 0xF0) { + case 0xA0: /* Copper cable unequalized */ + case 0xB0: /* Copper cable passive equalized */ + ctx->port_type = NT_PORT_TYPE_QSFP_PASSIVE_DAC; + break; + + case 0xC0: /* Copper cable, near and far end limiting active equalizers */ + case 0xD0: /* Copper cable, far end limiting active equalizers */ + case 0xE0: /* Copper cable, near end limiting active equalizers */ + case 0xF0: /* Copper cable, linear active equalizers */ + ctx->port_type = NT_PORT_TYPE_QSFP_ACTIVE_DAC; + break; + + default:/* Optical */ + ctx->port_type = NT_PORT_TYPE_QSFP_PLUS; + break; + } +} + +static void qsfpplus_set_speed_mask(nim_i2c_ctx_p ctx) +{ + ctx->speed_mask = (ctx->lane_idx < 0) ? NT_LINK_SPEED_40G : (NT_LINK_SPEED_10G); +} + +static void qsfpplus_construct(nim_i2c_ctx_p ctx, int8_t lane_idx) +{ + assert(lane_idx < 4); + ctx->specific_u.qsfp.qsfp28 = false; + ctx->lane_idx = lane_idx; + ctx->lane_count = 4; +} + +static int qsfpplus_preinit(nim_i2c_ctx_p ctx, int8_t lane_idx) +{ + qsfpplus_construct(ctx, lane_idx); + int res = qsfpplus_read_basic_data(ctx); + + if (!res) { + qsfpplus_find_port_params(ctx); + + /* + * If not on the known modules list try to figure out which sensors that are + * present + */ + if (!qsfpplus_get_qsfp_options_from_pn(ctx)) { + NT_LOG(DBG, NTHW, "NIM options not known in advance - trying to detect"); + qsfpplus_get_qsfp_options_from_data(ctx); + } + + /* + * Read if TX_DISABLE has been implemented + * For passive optical modules this is required while it for copper and active + * optical modules is optional. Under all circumstances register 195.4 will + * indicate, if TX_DISABLE has been implemented in register 86.0-3 + */ + uint8_t value; + read_data_lin(ctx, QSFP_OPTION3_LIN_ADDR, sizeof(value), &value); + + ctx->tx_disable = (value & QSFP_OPTION3_TX_DISABLE_BIT) != 0; + + if (ctx->tx_disable) + ctx->options |= (1 << NIM_OPTION_TX_DISABLE); + + /* + * Previously - considering AFBR-89BRDZ - code tried to establish if a module was + * RxOnly by testing the state of the lasers after reset. Lasers were for this + * module default disabled. + * However that code did not work for GigaLight, GQS-MPO400-SR4C so it was + * decided that this option should not be detected automatically but from PN + */ + ctx->specific_u.qsfp.rx_only = (ctx->options & (1 << NIM_OPTION_RX_ONLY)) != 0; + qsfpplus_set_speed_mask(ctx); + } + + return res; +} + +static void qsfp28_wait_for_ready_after_reset(nim_i2c_ctx_p ctx) +{ + uint8_t data; + bool init_complete_flag_present = false; + + /* + * Revision compliance + * 7: SFF-8636 Rev 2.5, 2.6 and 2.7 + * 8: SFF-8636 Rev 2.8, 2.9 and 2.10 + */ + read_data_lin(ctx, 1, sizeof(ctx->specific_u.qsfp.specific_u.qsfp28.rev_compliance), + &ctx->specific_u.qsfp.specific_u.qsfp28.rev_compliance); + NT_LOG(DBG, NTHW, "NIM RevCompliance = %d", + ctx->specific_u.qsfp.specific_u.qsfp28.rev_compliance); + + /* Wait if lane_idx == -1 (all lanes are used) or lane_idx == 0 (the first lane) */ + if (ctx->lane_idx > 0) + return; + + if (ctx->specific_u.qsfp.specific_u.qsfp28.rev_compliance >= 7) { + /* Check if init complete flag is implemented */ + read_data_lin(ctx, 221, sizeof(data), &data); + init_complete_flag_present = (data & (1 << 4)) != 0; + } + + NT_LOG(DBG, NTHW, "NIM InitCompleteFlagPresent = %d", init_complete_flag_present); + + /* + * If the init complete flag is not present then wait 500ms that together with 500ms + * after reset (in the adapter code) should be enough to read data from upper pages + * that otherwise would not be ready. Especially BiDi modules AFBR-89BDDZ have been + * prone to this when trying to read sensor options using getQsfpOptionsFromData() + * Probably because access to the paged address space is required. + */ + if (!init_complete_flag_present) { + nt_os_wait_usec(500000); + return; + } + + /* Otherwise wait for the init complete flag to be set */ + int count = 0; + + while (true) { + if (count > 10) { /* 1 s timeout */ + NT_LOG(WRN, NTHW, "Timeout waiting for module ready"); + break; + } + + read_data_lin(ctx, 6, sizeof(data), &data); + + if (data & 0x01) { + NT_LOG(DBG, NTHW, "Module ready after %dms", count * 100); + break; + } + + nt_os_wait_usec(100000);/* 100 ms */ + count++; + } +} + +static void qsfp28_get_fec_options(nim_i2c_ctx_p ctx) +{ + const char *const nim_list[] = { + "AFBR-89BDDZ", /* Avago BiDi */ + "AFBR-89BRDZ", /* Avago BiDi, RxOnly */ + "FTLC4352RKPL", /* Finisar QSFP28-LR */ + "FTLC4352RHPL", /* Finisar QSFP28-DR */ + "FTLC4352RJPL", /* Finisar QSFP28-FR */ + "SFBR-89BDDZ-CS4", /* Foxconn, QSFP28 100G/40G BiDi */ + }; + + for (size_t i = 0; i < ARRAY_SIZE(nim_list); i++) { + if (ctx->prod_no == nim_list[i]) { + ctx->options |= (1 << NIM_OPTION_MEDIA_SIDE_FEC); + ctx->specific_u.qsfp.specific_u.qsfp28.media_side_fec_ena = true; + NT_LOG(DBG, NTHW, "Found FEC info via PN list"); + return; + } + } + + /* + * For modules not in the list find FEC info via registers + * Read if the module has controllable FEC + * SFF-8636, Rev 2.10a TABLE 6-28 Equalizer, Emphasis, Amplitude and Timing) + * (Page 03h, Bytes 224-229) + */ + uint8_t data; + uint16_t addr = 227 + 3 * 128; + read_data_lin(ctx, addr, sizeof(data), &data); + + /* Check if the module has FEC support that can be controlled */ + ctx->specific_u.qsfp.specific_u.qsfp28.media_side_fec_ctrl = (data & (1 << 6)) != 0; + ctx->specific_u.qsfp.specific_u.qsfp28.host_side_fec_ctrl = (data & (1 << 7)) != 0; + + if (ctx->specific_u.qsfp.specific_u.qsfp28.media_side_fec_ctrl) + ctx->options |= (1 << NIM_OPTION_MEDIA_SIDE_FEC); + + if (ctx->specific_u.qsfp.specific_u.qsfp28.host_side_fec_ctrl) + ctx->options |= (1 << NIM_OPTION_HOST_SIDE_FEC); +} + +static int qsfp28_preinit(nim_i2c_ctx_p ctx, int8_t lane_idx) +{ + int res = qsfpplus_preinit(ctx, lane_idx); + + if (!res) { + qsfp28_wait_for_ready_after_reset(ctx); + memset(&ctx->specific_u.qsfp.specific_u.qsfp28, 0, + sizeof(ctx->specific_u.qsfp.specific_u.qsfp28)); + ctx->specific_u.qsfp.qsfp28 = true; + qsfp28_find_port_params(ctx); + qsfp28_get_fec_options(ctx); + qsfp28_set_speed_mask(ctx); + } + + return res; +} + +static void qsfp_plus_nim_add_all_sensors(uint8_t m_port_no, nim_i2c_ctx_t *ctx, + struct nim_sensor_group **nim_sensors_ptr, + uint16_t *nim_sensors_cnt) +{ + const struct nim_sensors_ops *nim_sensors_ops = get_nim_sensors_ops(); + + if (nim_sensors_ops == NULL) { + NT_LOG(INF, ETHDEV, "Nim sensors module not included\n"); + return; + } + + struct qsfp_ops *qsfp_ops = get_qsfp_ops(); + + if (qsfp_ops == NULL) { + NT_LOG(INF, ETHDEV, "QSFP module not included\n"); + return; + } + + struct nt_adapter_sensor_description *qsfp_sensor_level0 = + nim_sensors_ops->get_qsfp_sensor_level0(); + + struct nt_adapter_sensor_description *qsfp_sensor_level1 = + nim_sensors_ops->get_qsfp_sensor_level1(); + + struct nim_sensor_group *sensor = NULL; + + if (ctx == NULL || nim_sensors_ptr == NULL) { + NT_LOG(ERR, ETHDEV, "%s: bad argument(s)\n", __func__); + return; + } + + /* + * If the user has not provided a name for the temperature sensor then apply + * one automatically + */ + if (strlen(qsfp_sensor_level0[0].name) == 0) { + if (ctx->specific_u.qsfp.qsfp28) + snprintf(qsfp_sensor_level0[0].name, sizeof(qsfp_sensor_level0[0].name), + "%s", "QSFP28"); + + else + snprintf(qsfp_sensor_level0[0].name, sizeof(qsfp_sensor_level0[0].name), + "%s", "QSFP+"); + } + + /* temperature sensor */ + nim_sensors_ptr[m_port_no] = + allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_PORT, + &qsfp_sensor_level0[0]); + sensor = nim_sensors_ptr[m_port_no]; + sensor->read = qsfp_ops->nim_read_qsfp_temp; + (*nim_sensors_cnt)++; + + /* voltage */ + sensor->next = allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_LEVEL1_PORT, + &qsfp_sensor_level1[0]); + sensor = sensor->next; + sensor->read = qsfp_ops->nim_read_qsfp_voltage; + (*nim_sensors_cnt)++; + + /* bias current sensors */ + sensor->next = allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_LEVEL1_PORT, + &qsfp_sensor_level1[1]); + sensor = sensor->next; + sensor->read = qsfp_ops->nim_read_qsfp_bias_current; + (*nim_sensors_cnt)++; + + for (uint8_t i = 2; i < 5; i++) { + sensor->next = + allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_LEVEL1_PORT, + &qsfp_sensor_level1[i]); + sensor = sensor->next; + sensor->read = NULL; + (*nim_sensors_cnt)++; + } + + /* tx power */ + sensor->next = allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_LEVEL1_PORT, + &qsfp_sensor_level1[5]); + sensor = sensor->next; + sensor->read = qsfp_ops->nim_read_qsfp_tx_power; + (*nim_sensors_cnt)++; + + for (uint8_t i = 6; i < 9; i++) { + sensor->next = + allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_LEVEL1_PORT, + &qsfp_sensor_level1[i]); + sensor = sensor->next; + sensor->read = NULL; + (*nim_sensors_cnt)++; + } + + /* rx power */ + sensor->next = allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_LEVEL1_PORT, + &qsfp_sensor_level1[9]); + sensor = sensor->next; + sensor->read = qsfp_ops->nim_read_qsfp_rx_power; + (*nim_sensors_cnt)++; + + for (uint8_t i = 10; i < 13; i++) { + sensor->next = + allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_LEVEL1_PORT, + &qsfp_sensor_level1[i]); + sensor = sensor->next; + sensor->read = NULL; + (*nim_sensors_cnt)++; + } +} + +struct nim_sensor_group *allocate_nim_sensor_group(uint8_t port, struct nim_i2c_ctx *ctx, + enum nt_sensor_source_e ssrc, + struct nt_adapter_sensor_description *sd) +{ + const struct nim_sensors_ops *nim_sensor_ops = get_nim_sensors_ops(); + + if (nim_sensor_ops == NULL) { + NT_LOG(INF, ETHDEV, "Sensors module not included\n"); + return NULL; + } + + struct nim_sensor_group *sg = malloc(sizeof(struct nim_sensor_group)); + + if (sg == NULL) { + NT_LOG(ERR, ETHDEV, "%s: sensor group is NULL", __func__); + return NULL; + } + + sg->sensor = nim_sensor_ops->allocate_sensor_by_description(port, ssrc, sd); + sg->ctx = ctx; + sg->next = NULL; + return sg; +} + +int construct_and_preinit_nim(nim_i2c_ctx_p ctx, void *extra, uint8_t port, + struct nim_sensor_group **nim_sensors_ptr, uint16_t *nim_sensors_cnt) +{ + int res = i2c_nim_common_construct(ctx); + + switch (translate_nimid(ctx)) { + case NT_NIM_SFP_SFP_PLUS: + break; + + case NT_NIM_QSFP_PLUS: + qsfpplus_preinit(ctx, extra ? *(int8_t *)extra : (int8_t)-1); + qsfp_plus_nim_add_all_sensors(port, ctx, nim_sensors_ptr, nim_sensors_cnt); + break; + + case NT_NIM_QSFP28: + qsfp28_preinit(ctx, extra ? *(int8_t *)extra : (int8_t)-1); + qsfp_plus_nim_add_all_sensors(port, ctx, nim_sensors_ptr, nim_sensors_cnt); + break; + + default: + res = 1; + NT_LOG(ERR, NTHW, "NIM type %s is not supported.\n", nim_id_to_text(ctx->nim_id)); + } + + return res; +} + +static struct sfp_ops *sfp_ops; + +void register_sfp_ops(struct sfp_ops *ops) +{ + sfp_ops = ops; +} + +struct sfp_ops *get_sfp_ops(void) +{ + return sfp_ops; +} + +static struct qsfp_ops *qsfp_ops; + +void register_qsfp_ops(struct qsfp_ops *ops) +{ + qsfp_ops = ops; +} + +struct qsfp_ops *get_qsfp_ops(void) +{ + return qsfp_ops; +} diff --git a/drivers/net/ntnic/nim/i2c_nim.h b/drivers/net/ntnic/nim/i2c_nim.h new file mode 100644 index 0000000000..cbeb3852fd --- /dev/null +++ b/drivers/net/ntnic/nim/i2c_nim.h @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2023 Napatech A/S + */ + +#ifndef I2C_NIM_H_ +#define I2C_NIM_H_ + +#include "nthw_drv.h" +#include "nim_defines.h" +#include "nt_link_speed.h" +#include "ntnic_nim.h" + +#include "ntnic_sensor.h" + +typedef struct sfp_nim_state { + uint8_t br; /* bit rate, units of 100 MBits/sec */ +} sfp_nim_state_t, *sfp_nim_state_p; + +struct nim_sensor_group *allocate_nim_sensor_group(uint8_t port, struct nim_i2c_ctx *ctx, + enum nt_sensor_source_e ssrc, + struct nt_adapter_sensor_description *sd); + +/* + * Utility functions + */ + +nt_nim_identifier_t translate_nimid(const nim_i2c_ctx_t *ctx); + +/* + * Builds an nim state for the port implied by `ctx`, returns zero + * if successful, and non-zero otherwise. SFP and QSFP nims are supported + */ +int nim_state_build(nim_i2c_ctx_t *ctx, sfp_nim_state_t *state); + +/* + * Returns a type name such as "SFP/SFP+" for a given NIM type identifier, + * or the string "ILLEGAL!". + */ +const char *nim_id_to_text(uint8_t nim_id); + +int nim_qsfp_plus_nim_set_tx_laser_disable(nim_i2c_ctx_t *ctx, bool disable, int lane_idx); + +/* + * This function tries to classify NIM based on it's ID and some register reads + * and collects information into ctx structure. The @extra parameter could contain + * the initialization argument for specific type of NIMS. + */ +int construct_and_preinit_nim(nim_i2c_ctx_p ctx, void *extra, uint8_t port, + struct nim_sensor_group **nim_sensors_ptr, + uint16_t *nim_sensors_cnt); + +int read_data_lin(nim_i2c_ctx_p ctx, uint16_t lin_addr, uint16_t length, void *data); + +struct sfp_ops { + void (*nim_read_sfp_temp)(struct nim_sensor_group *sg, nthw_spis_t *t_spi); + void (*nim_read_sfp_voltage)(struct nim_sensor_group *sg, nthw_spis_t *t_spi); + void (*nim_read_sfp_bias_current)(struct nim_sensor_group *sg, nthw_spis_t *t_spi); + void (*nim_read_sfp_tx_power)(struct nim_sensor_group *sg, nthw_spis_t *t_spi); + void (*nim_read_sfp_rx_power)(struct nim_sensor_group *sg, nthw_spis_t *t_spi); +}; + +struct qsfp_ops { + void (*nim_read_qsfp_temp)(struct nim_sensor_group *sg, nthw_spis_t *t_spi); + void (*nim_read_qsfp_voltage)(struct nim_sensor_group *sg, nthw_spis_t *t_spi); + void (*nim_read_qsfp_bias_current)(struct nim_sensor_group *sg, nthw_spis_t *t_spi); + void (*nim_read_qsfp_tx_power)(struct nim_sensor_group *sg, nthw_spis_t *t_spi); + void (*nim_read_qsfp_rx_power)(struct nim_sensor_group *sg, nthw_spis_t *t_spi); +}; + +void register_qsfp_ops(struct qsfp_ops *ops); +struct qsfp_ops *get_qsfp_ops(void); + +void register_sfp_ops(struct sfp_ops *ops); +struct sfp_ops *get_sfp_ops(void); + +#endif /* I2C_NIM_H_ */ diff --git a/drivers/net/ntnic/nim/include/qsfp_registers.h b/drivers/net/ntnic/nim/include/qsfp_registers.h new file mode 100644 index 0000000000..d935b0eb07 --- /dev/null +++ b/drivers/net/ntnic/nim/include/qsfp_registers.h @@ -0,0 +1,58 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2023 Napatech A/S + */ + +#ifndef _QSFP_REGISTERS_H +#define _QSFP_REGISTERS_H + +/* + * QSFP Registers + */ +#define QSFP_INT_STATUS_RX_LOS_ADDR 3 +#define QSFP_TEMP_LIN_ADDR 22 +#define QSFP_VOLT_LIN_ADDR 26 +#define QSFP_RX_PWR_LIN_ADDR 34 /* uint16_t [0..3] */ +#define QSFP_TX_BIAS_LIN_ADDR 42/* uint16_t [0..3] */ +#define QSFP_TX_PWR_LIN_ADDR 50 /* uint16_t [0..3] */ + +#define QSFP_CONTROL_STATUS_LIN_ADDR 86 +#define QSFP_SOFT_TX_ALL_DISABLE_BITS 0x0F + +#define QSFP_EXTENDED_IDENTIFIER 129 +#define QSFP_POWER_CLASS_BITS_1_4 0xC0 +#define QSFP_POWER_CLASS_BITS_5_7 0x03 + +#define QSFP_SUP_LEN_INFO_LIN_ADDR 142 /* 5bytes */ +#define QSFP_TRANSMITTER_TYPE_LIN_ADDR 147 /* 1byte */ +#define QSFP_VENDOR_NAME_LIN_ADDR 148 /* 16bytes */ +#define QSFP_VENDOR_PN_LIN_ADDR 168 /* 16bytes */ +#define QSFP_VENDOR_SN_LIN_ADDR 196 /* 16bytes */ +#define QSFP_VENDOR_DATE_LIN_ADDR 212 /* 8bytes */ +#define QSFP_VENDOR_REV_LIN_ADDR 184 /* 2bytes */ + +#define QSFP_SPEC_COMPLIANCE_CODES_ADDR 131 /* 8 bytes */ +#define QSFP_EXT_SPEC_COMPLIANCE_CODES_ADDR 192 /* 1 byte */ + +#define QSFP_OPTION3_LIN_ADDR 195 +#define QSFP_OPTION3_TX_DISABLE_BIT (1 << 4) + +#define QSFP_DMI_OPTION_LIN_ADDR 220 +#define QSFP_DMI_AVG_PWR_BIT (1 << 3) + +#define QSFP_TEMP_THRESH_LIN_ADDR (128 + (3 * 128)) /* Page 3 */ +/* 8bytes: HighAlarm, LowAlarm, HighWarn, LowWarn each 2 bytes */ + +#define QSFP_VOLT_THRESH_LIN_ADDR (144 + (3 * 128)) /* Page 3 */ +/* 8bytes: HighAlarm, LowAlarm, HighWarn, LowWarn each 2 bytes */ + +#define QSFP_RX_PWR_THRESH_LIN_ADDR (176 + (3 * 128)) /* Page 3 */ +/* 8bytes: HighAlarm, LowAlarm, HighWarn, LowWarn each 2 bytes */ + +#define QSFP_BIAS_THRESH_LIN_ADDR (184 + (3 * 128)) /* Page 3 */ +/* 8bytes: HighAlarm, LowAlarm, HighWarn, LowWarn each 2 bytes */ + +#define QSFP_TX_PWR_THRESH_LIN_ADDR (192 + (3 * 128)) /* Page 3 */ +/* 8bytes: HighAlarm, LowAlarm, HighWarn, LowWarn each 2 bytes */ + +#endif /* _QSFP_REGISTERS_H */ diff --git a/drivers/net/ntnic/nim/nim_defines.h b/drivers/net/ntnic/nim/nim_defines.h new file mode 100644 index 0000000000..4abe115dc9 --- /dev/null +++ b/drivers/net/ntnic/nim/nim_defines.h @@ -0,0 +1,69 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2023 Napatech A/S + */ + +#ifndef NIM_DEFINES_H_ +#define NIM_DEFINES_H_ + +#define NIM_IDENTIFIER_ADDR 0 /* 1 byte */ + +#define SFP_BIT_RATE_ADDR 12 /* 1 byte */ +#define SFP_VENDOR_NAME_ADDR 20 /* 16bytes */ +#define SFP_VENDOR_PN_ADDR 40 /* 16bytes */ +#define SFP_VENDOR_REV_ADDR 56 /* 4bytes */ +#define SFP_VENDOR_SN_ADDR 68 /* 16bytes */ +#define SFP_VENDOR_DATE_ADDR 84 /* 8bytes */ + +#define SFP_CONTROL_STATUS_LIN_ADDR (110U + 256U) /* 0xA2 */ +#define SFP_SOFT_TX_DISABLE_BIT (1U << 6) + +#define QSFP_EXTENDED_IDENTIFIER 129 +#define QSFP_SUP_LEN_INFO_ADDR 142 /* 5bytes */ +#define QSFP_TRANSMITTER_TYPE_ADDR 147 /* 1byte */ +#define QSFP_VENDOR_NAME_ADDR 148 /* 16bytes */ +#define QSFP_VENDOR_PN_ADDR 168 /* 16bytes */ +#define QSFP_VENDOR_REV_ADDR 184/* 2bytes */ +#define QSFP_VENDOR_SN_ADDR 196 /* 16bytes */ +#define QSFP_VENDOR_DATE_ADDR 212 /* 8bytes */ + +/* I2C addresses */ +#define NIM_I2C_0XA0 0xA0 /* Basic I2C address */ +#define NIM_I2C_0XA2 0xA2 /* Diagnostic monitoring */ +#define NIM_I2C_0XAC 0xAC /* Address of integrated PHY */ + +typedef enum { + NIM_OPTION_TEMP = 0, + NIM_OPTION_SUPPLY, + NIM_OPTION_RX_POWER, + NIM_OPTION_TX_BIAS, + NIM_OPTION_TX_POWER, + NIM_OPTION_TX_DISABLE, + /* Indicates that the module should be checked for the two next FEC types */ + NIM_OPTION_FEC, + NIM_OPTION_MEDIA_SIDE_FEC, + NIM_OPTION_HOST_SIDE_FEC, + NIM_OPTION_RX_ONLY +} nim_option_t; + +enum nt_nim_identifier_e { + NT_NIM_UNKNOWN = 0x00, /* Nim type is unknown */ + NT_NIM_GBIC = 0x01, /* Nim type = GBIC */ + NT_NIM_FIXED = 0x02, /* Nim type = FIXED */ + NT_NIM_SFP_SFP_PLUS = 0x03, /* Nim type = SFP/SFP+ */ + NT_NIM_300_PIN_XBI = 0x04, /* Nim type = 300 pin XBI */ + NT_NIM_XEN_PAK = 0x05, /* Nim type = XEN-PAK */ + NT_NIM_XFP = 0x06, /* Nim type = XFP */ + NT_NIM_XFF = 0x07, /* Nim type = XFF */ + NT_NIM_XFP_E = 0x08, /* Nim type = XFP-E */ + NT_NIM_XPAK = 0x09, /* Nim type = XPAK */ + NT_NIM_X2 = 0x0A, /* Nim type = X2 */ + NT_NIM_DWDM = 0x0B, /* Nim type = DWDM */ + NT_NIM_QSFP = 0x0C, /* Nim type = QSFP */ + NT_NIM_QSFP_PLUS = 0x0D,/* Nim type = QSFP+ */ + NT_NIM_QSFP28 = 0x11, /* Nim type = QSFP28 */ + NT_NIM_CFP4 = 0x12, /* Nim type = CFP4 */ +}; +typedef enum nt_nim_identifier_e nt_nim_identifier_t; + +#endif /* NIM_DEFINES_H_ */ diff --git a/drivers/net/ntnic/nim/nt_link_speed.c b/drivers/net/ntnic/nim/nt_link_speed.c new file mode 100644 index 0000000000..3cc5f50e49 --- /dev/null +++ b/drivers/net/ntnic/nim/nt_link_speed.c @@ -0,0 +1,115 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2023 Napatech A/S + */ + +#include <assert.h> +#include <stdbool.h> +#include <string.h> + +#include "nt_link_speed.h" +#include "nt4ga_link.h" + +const char *nt_translate_link_speed(nt_link_speed_t link_speed) +{ + switch (link_speed) { + case NT_LINK_SPEED_UNKNOWN: + return "NotAvail"; + + case NT_LINK_SPEED_10M: + return "10M"; + + case NT_LINK_SPEED_100M: + return "100M"; + + case NT_LINK_SPEED_1G: + return "1G"; + + case NT_LINK_SPEED_10G: + return "10G"; + + case NT_LINK_SPEED_25G: + return "25G"; + + case NT_LINK_SPEED_40G: + return "40G"; + + case NT_LINK_SPEED_50G: + return "50G"; + + case NT_LINK_SPEED_100G: + return "100G"; + + default: + /* DEBUG assert: remind developer that a switch/case entry is needed here.... */ + assert(false); + return "Unhandled"; + } +} + +uint64_t nt_get_link_speed(nt_link_speed_t e_link_speed) +{ + uint64_t n_link_speed = 0ULL; + + switch (e_link_speed) { + case NT_LINK_SPEED_UNKNOWN: + n_link_speed = 0UL; + break; + + case NT_LINK_SPEED_10M: + n_link_speed = (10ULL * 1000ULL * 1000ULL); + break; + + case NT_LINK_SPEED_100M: + n_link_speed = (100ULL * 1000ULL * 1000ULL); + break; + + case NT_LINK_SPEED_1G: + n_link_speed = (1ULL * 1000ULL * 1000ULL * 1000ULL); + break; + + case NT_LINK_SPEED_10G: + n_link_speed = (10ULL * 1000ULL * 1000ULL * 1000ULL); + break; + + case NT_LINK_SPEED_25G: + n_link_speed = (25ULL * 1000ULL * 1000ULL * 1000ULL); + break; + + case NT_LINK_SPEED_40G: + n_link_speed = (40ULL * 1000ULL * 1000ULL * 1000ULL); + break; + + case NT_LINK_SPEED_50G: + n_link_speed = (50ULL * 1000ULL * 1000ULL * 1000ULL); + break; + + case NT_LINK_SPEED_100G: + n_link_speed = (100ULL * 1000ULL * 1000ULL * 1000ULL); + break; + + default: + /* DEBUG assert: remind developer that a switch/case entry is needed here.... */ + assert(false); + n_link_speed = 0UL; + break; + } + + return n_link_speed; +} + +uint64_t nt_get_max_link_speed(uint32_t link_speed_mask) +{ + uint64_t n_link_speed = 0UL; + + for (int i = 0; i < 32; i++) { + if ((1U << i) & link_speed_mask) { + uint64_t link_speed = nt_get_link_speed(1 << i); + + if (link_speed > n_link_speed) + n_link_speed = link_speed; + } + } + + return n_link_speed; +} diff --git a/drivers/net/ntnic/nim/nt_link_speed.h b/drivers/net/ntnic/nim/nt_link_speed.h new file mode 100644 index 0000000000..6e1b682fe8 --- /dev/null +++ b/drivers/net/ntnic/nim/nt_link_speed.h @@ -0,0 +1,16 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2023 Napatech A/S + */ + +#ifndef NT_LINK_SPEED_H_ +#define NT_LINK_SPEED_H_ + +#include <stdint.h> +#include "nt4ga_link.h" + +const char *nt_translate_link_speed(nt_link_speed_t link_speed); +uint64_t nt_get_link_speed(nt_link_speed_t e_link_speed); +uint64_t nt_get_max_link_speed(uint32_t link_speed_mask); + +#endif /* NT_LINK_SPEED_H_ */ -- 2.44.0