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

Reply via email to