All of lore.kernel.org
 help / color / mirror / Atom feed
From: Mykola Kostenok <mko-plv@napatech.com>
To: dev@dpdk.org, mko-plv@napatech.com, thomas@monjalon.net
Cc: ckm@napatech.com
Subject: [PATCH v8 3/8] net/ntnic: adds NT200A02 adapter support
Date: Tue, 29 Aug 2023 10:15:05 +0200	[thread overview]
Message-ID: <20230829081510.944465-3-mko-plv@napatech.com> (raw)
In-Reply-To: <20230829081510.944465-1-mko-plv@napatech.com>

From: Christian Koue Muf <ckm@napatech.com>

The PMD is designed to support multiple different adapters, and this commit
adds support for NT200A02 2x100G. Sensor and NIM code is included.

Signed-off-by: Christian Koue Muf <ckm@napatech.com>
Reviewed-by: Mykola Kostenok <mko-plv@napatech.com>
---
v2:
* Fixed WARNING:TYPO_SPELLING
---
 .../net/ntnic/adapter/common_adapter_defs.h   |   14 +
 drivers/net/ntnic/adapter/nt4ga_adapter.c     |  477 ++++
 drivers/net/ntnic/adapter/nt4ga_adapter.h     |  108 +
 drivers/net/ntnic/adapter/nt4ga_filter.h      |   15 +
 drivers/net/ntnic/adapter/nt4ga_link.c        |  178 ++
 drivers/net/ntnic/adapter/nt4ga_link.h        |  179 ++
 drivers/net/ntnic/adapter/nt4ga_link_100g.c   |  825 +++++++
 drivers/net/ntnic/adapter/nt4ga_link_100g.h   |   12 +
 drivers/net/ntnic/adapter/nt4ga_pci_ta_tg.c   |  598 +++++
 drivers/net/ntnic/adapter/nt4ga_pci_ta_tg.h   |   41 +
 drivers/net/ntnic/adapter/nt4ga_stat.c        |  705 ++++++
 drivers/net/ntnic/adapter/nt4ga_stat.h        |  202 ++
 drivers/net/ntnic/meson.build                 |   24 +
 drivers/net/ntnic/nim/i2c_nim.c               | 1974 +++++++++++++++++
 drivers/net/ntnic/nim/i2c_nim.h               |  122 +
 drivers/net/ntnic/nim/nim_defines.h           |  146 ++
 drivers/net/ntnic/nim/nt_link_speed.c         |  105 +
 drivers/net/ntnic/nim/nt_link_speed.h         |   34 +
 drivers/net/ntnic/nim/qsfp_registers.h        |   57 +
 drivers/net/ntnic/nim/qsfp_sensors.c          |  174 ++
 drivers/net/ntnic/nim/qsfp_sensors.h          |   18 +
 drivers/net/ntnic/nim/sfp_p_registers.h       |  100 +
 drivers/net/ntnic/nim/sfp_sensors.c           |  288 +++
 drivers/net/ntnic/nim/sfp_sensors.h           |   18 +
 .../net/ntnic/nthw/core/nthw_clock_profiles.c |   11 +-
 drivers/net/ntnic/nthw/core/nthw_core.h       |    2 +
 drivers/net/ntnic/nthw/core/nthw_gmf.c        |  290 +++
 drivers/net/ntnic/nthw/core/nthw_gmf.h        |   93 +
 .../nthw/core/nthw_nt200a02_u23_si5340_v5.h   |  344 +++
 drivers/net/ntnic/nthw/core/nthw_rmc.c        |  156 ++
 drivers/net/ntnic/nthw/core/nthw_rmc.h        |   57 +
 .../ntnic/sensors/avr_sensors/avr_sensors.c   |  104 +
 .../ntnic/sensors/avr_sensors/avr_sensors.h   |   22 +
 .../sensors/board_sensors/board_sensors.c     |   48 +
 .../sensors/board_sensors/board_sensors.h     |   18 +
 .../net/ntnic/sensors/board_sensors/tempmon.c |   42 +
 .../net/ntnic/sensors/board_sensors/tempmon.h |   16 +
 .../ntnic/sensors/nim_sensors/nim_sensors.c   |   54 +
 .../ntnic/sensors/nim_sensors/nim_sensors.h   |   19 +
 drivers/net/ntnic/sensors/ntavr/avr_intf.h    |   89 +
 drivers/net/ntnic/sensors/ntavr/ntavr.c       |   78 +
 drivers/net/ntnic/sensors/ntavr/ntavr.h       |   32 +
 drivers/net/ntnic/sensors/sensor_types.h      |  259 +++
 drivers/net/ntnic/sensors/sensors.c           |  273 +++
 drivers/net/ntnic/sensors/sensors.h           |  127 ++
 drivers/net/ntnic/sensors/stream_info.h       |   86 +
 46 files changed, 8632 insertions(+), 2 deletions(-)
 create mode 100644 drivers/net/ntnic/adapter/common_adapter_defs.h
 create mode 100644 drivers/net/ntnic/adapter/nt4ga_adapter.c
 create mode 100644 drivers/net/ntnic/adapter/nt4ga_adapter.h
 create mode 100644 drivers/net/ntnic/adapter/nt4ga_filter.h
 create mode 100644 drivers/net/ntnic/adapter/nt4ga_link.c
 create mode 100644 drivers/net/ntnic/adapter/nt4ga_link.h
 create mode 100644 drivers/net/ntnic/adapter/nt4ga_link_100g.c
 create mode 100644 drivers/net/ntnic/adapter/nt4ga_link_100g.h
 create mode 100644 drivers/net/ntnic/adapter/nt4ga_pci_ta_tg.c
 create mode 100644 drivers/net/ntnic/adapter/nt4ga_pci_ta_tg.h
 create mode 100644 drivers/net/ntnic/adapter/nt4ga_stat.c
 create mode 100644 drivers/net/ntnic/adapter/nt4ga_stat.h
 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/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
 create mode 100644 drivers/net/ntnic/nim/qsfp_registers.h
 create mode 100644 drivers/net/ntnic/nim/qsfp_sensors.c
 create mode 100644 drivers/net/ntnic/nim/qsfp_sensors.h
 create mode 100644 drivers/net/ntnic/nim/sfp_p_registers.h
 create mode 100644 drivers/net/ntnic/nim/sfp_sensors.c
 create mode 100644 drivers/net/ntnic/nim/sfp_sensors.h
 create mode 100644 drivers/net/ntnic/nthw/core/nthw_gmf.c
 create mode 100644 drivers/net/ntnic/nthw/core/nthw_gmf.h
 create mode 100644 drivers/net/ntnic/nthw/core/nthw_nt200a02_u23_si5340_v5.h
 create mode 100644 drivers/net/ntnic/nthw/core/nthw_rmc.c
 create mode 100644 drivers/net/ntnic/nthw/core/nthw_rmc.h
 create mode 100644 drivers/net/ntnic/sensors/avr_sensors/avr_sensors.c
 create mode 100644 drivers/net/ntnic/sensors/avr_sensors/avr_sensors.h
 create mode 100644 drivers/net/ntnic/sensors/board_sensors/board_sensors.c
 create mode 100644 drivers/net/ntnic/sensors/board_sensors/board_sensors.h
 create mode 100644 drivers/net/ntnic/sensors/board_sensors/tempmon.c
 create mode 100644 drivers/net/ntnic/sensors/board_sensors/tempmon.h
 create mode 100644 drivers/net/ntnic/sensors/nim_sensors/nim_sensors.c
 create mode 100644 drivers/net/ntnic/sensors/nim_sensors/nim_sensors.h
 create mode 100644 drivers/net/ntnic/sensors/ntavr/avr_intf.h
 create mode 100644 drivers/net/ntnic/sensors/ntavr/ntavr.c
 create mode 100644 drivers/net/ntnic/sensors/ntavr/ntavr.h
 create mode 100644 drivers/net/ntnic/sensors/sensor_types.h
 create mode 100644 drivers/net/ntnic/sensors/sensors.c
 create mode 100644 drivers/net/ntnic/sensors/sensors.h
 create mode 100644 drivers/net/ntnic/sensors/stream_info.h

diff --git a/drivers/net/ntnic/adapter/common_adapter_defs.h b/drivers/net/ntnic/adapter/common_adapter_defs.h
new file mode 100644
index 0000000000..79167806f1
--- /dev/null
+++ b/drivers/net/ntnic/adapter/common_adapter_defs.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _COMMON_ADAPTER_DEFS_H_
+#define _COMMON_ADAPTER_DEFS_H_
+
+/*
+ * Declarations shared by NT adapter types.
+ */
+#define NUM_ADAPTER_MAX (8)
+#define NUM_ADAPTER_PORTS_MAX (128)
+
+#endif /* _COMMON_ADAPTER_DEFS_H_ */
diff --git a/drivers/net/ntnic/adapter/nt4ga_adapter.c b/drivers/net/ntnic/adapter/nt4ga_adapter.c
new file mode 100644
index 0000000000..259aae2831
--- /dev/null
+++ b/drivers/net/ntnic/adapter/nt4ga_adapter.c
@@ -0,0 +1,477 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "ntlog.h"
+
+#include "nthw_drv.h"
+#include "nthw_fpga.h"
+#include "nt4ga_adapter.h"
+#include "nt4ga_pci_ta_tg.h"
+#include "nt4ga_link_100g.h"
+
+/* Sensors includes */
+#include "board_sensors.h"
+#include "avr_sensors.h"
+
+/*
+ * Global variables shared by NT adapter types
+ */
+pthread_t monitor_tasks[NUM_ADAPTER_MAX];
+volatile int monitor_task_is_running[NUM_ADAPTER_MAX];
+
+/*
+ * Signal-handler to stop all monitor threads
+ */
+static void stop_monitor_tasks(int signum)
+{
+	const size_t n = ARRAY_SIZE(monitor_task_is_running);
+	size_t i;
+
+	/* Stop all monitor tasks */
+	for (i = 0; i < n; i++) {
+		const int is_running = monitor_task_is_running[i];
+
+		monitor_task_is_running[i] = 0;
+		if (signum == -1 && is_running != 0) {
+			void *ret_val = NULL;
+
+			pthread_join(monitor_tasks[i], &ret_val);
+			memset(&monitor_tasks[i], 0, sizeof(monitor_tasks[0]));
+		}
+	}
+}
+
+int nt4ga_adapter_show_info(struct adapter_info_s *p_adapter_info, FILE *pfh)
+{
+	const char *const p_dev_name = p_adapter_info->p_dev_name;
+	const char *const p_adapter_id_str = p_adapter_info->mp_adapter_id_str;
+	fpga_info_t *p_fpga_info = &p_adapter_info->fpga_info;
+	hw_info_t *p_hw_info = &p_adapter_info->hw_info;
+	char a_pci_ident_str[32];
+
+	snprintf(a_pci_ident_str, sizeof(a_pci_ident_str), "" PCIIDENT_PRINT_STR "",
+		PCIIDENT_TO_DOMAIN(p_fpga_info->pciident),
+		PCIIDENT_TO_BUSNR(p_fpga_info->pciident),
+		PCIIDENT_TO_DEVNR(p_fpga_info->pciident),
+		PCIIDENT_TO_FUNCNR(p_fpga_info->pciident));
+
+	fprintf(pfh, "%s: DeviceName: %s\n", p_adapter_id_str,
+		(p_dev_name ? p_dev_name : "NA"));
+	fprintf(pfh, "%s: PCI Details:\n", p_adapter_id_str);
+	fprintf(pfh, "%s: %s: %08X: %04X:%04X %04X:%04X\n", p_adapter_id_str,
+		a_pci_ident_str, p_fpga_info->pciident, p_hw_info->pci_vendor_id,
+		p_hw_info->pci_device_id, p_hw_info->pci_sub_vendor_id,
+		p_hw_info->pci_sub_device_id);
+	fprintf(pfh, "%s: FPGA Details:\n", p_adapter_id_str);
+	fprintf(pfh, "%s: %03d-%04d-%02d-%02d [%016" PRIX64 "] (%08X)\n",
+		p_adapter_id_str, p_fpga_info->n_fpga_type_id, p_fpga_info->n_fpga_prod_id,
+		p_fpga_info->n_fpga_ver_id, p_fpga_info->n_fpga_rev_id,
+		p_fpga_info->n_fpga_ident, p_fpga_info->n_fpga_build_time);
+	fprintf(pfh, "%s: FpgaDebugMode=0x%x\n", p_adapter_id_str,
+		p_fpga_info->n_fpga_debug_mode);
+	fprintf(pfh,
+		"%s: Nims=%d PhyPorts=%d PhyQuads=%d RxPorts=%d TxPorts=%d\n",
+		p_adapter_id_str, p_fpga_info->n_nims, p_fpga_info->n_phy_ports,
+		p_fpga_info->n_phy_quads, p_fpga_info->n_rx_ports, p_fpga_info->n_tx_ports);
+	fprintf(pfh, "%s: Hw=0x%02X_rev%d: %s\n", p_adapter_id_str,
+		p_hw_info->hw_platform_id, p_fpga_info->nthw_hw_info.hw_id,
+		p_fpga_info->nthw_hw_info.hw_plat_id_str);
+
+	nt4ga_stat_dump(p_adapter_info, pfh);
+
+	return 0;
+}
+
+/*
+ * SPI for sensors initialization
+ */
+static nthw_spi_v3_t *new_sensors_s_spi(struct nt_fpga_s *p_fpga)
+{
+	nthw_spi_v3_t *sensors_s_spi = nthw_spi_v3_new();
+
+	if (sensors_s_spi == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: SPI allocation error\n", __func__);
+		return NULL;
+	}
+
+	if (nthw_spi_v3_init(sensors_s_spi, p_fpga, 0)) {
+		NT_LOG(ERR, ETHDEV, "%s: SPI initialization error\n", __func__);
+		nthw_spi_v3_delete(sensors_s_spi);
+		return NULL;
+	}
+
+	return sensors_s_spi;
+}
+
+/*
+ * SPI for sensors reading
+ */
+nthw_spis_t *new_sensors_t_spi(struct nt_fpga_s *p_fpga)
+{
+	nthw_spis_t *sensors_t_spi = nthw_spis_new();
+	/* init SPI for sensor initialization process */
+	if (sensors_t_spi == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: SPI allocation error\n", __func__);
+		return NULL;
+	}
+
+	if (nthw_spis_init(sensors_t_spi, p_fpga, 0)) {
+		NT_LOG(ERR, ETHDEV, "%s: SPI initialization error\n", __func__);
+		nthw_spis_delete(sensors_t_spi);
+		return NULL;
+	}
+
+	return sensors_t_spi;
+}
+
+static void adapter_sensor_setup(hw_info_t *p_hw_info, struct adapter_info_s *adapter)
+{
+	struct nt_fpga_s *p_fpga = adapter->fpga_info.mp_fpga;
+	struct nt_sensor_group *sensors_list_ptr = NULL;
+	nthw_spi_v3_t *sensors_s_spi = new_sensors_s_spi(p_fpga);
+
+	adapter->adapter_sensors_cnt = 0;
+
+	/* FPGA */
+	adapter->adapter_sensors = fpga_temperature_sensor_init(p_hw_info->n_nthw_adapter_id,
+								NT_SENSOR_FPGA_TEMP, p_fpga);
+	sensors_list_ptr = adapter->adapter_sensors;
+	adapter->adapter_sensors_cnt++;
+
+	/* AVR */
+	if (sensors_s_spi) {
+		if (nt_avr_sensor_mon_ctrl(sensors_s_spi,
+					   SENSOR_MON_CTRL_REM_ALL_SENSORS) != 0) {
+			/* stop sensor monitoring */
+			NT_LOG(ERR, ETHDEV,
+			       "Failed to stop AVR sensors monitoring\n");
+		} else {
+			NT_LOG(DBG, ETHDEV, "AVR sensors init started\n");
+
+			sensors_list_ptr->next = avr_sensor_init(sensors_s_spi,
+								 p_hw_info->n_nthw_adapter_id,
+								 "FAN0",
+								 NT_SENSOR_SOURCE_ADAPTER,
+								 NT_SENSOR_TYPE_FAN,
+								 NT_SENSOR_NT200E3_FAN_SPEED,
+								 SENSOR_MON_FAN, 0,
+								 SENSOR_MON_BIG_ENDIAN,
+								 SENSOR_MON_UNSIGNED,
+								 &fan, 0xFFFF);
+			sensors_list_ptr = sensors_list_ptr->next;
+			adapter->adapter_sensors_cnt++;
+
+			sensors_list_ptr->next = avr_sensor_init(sensors_s_spi,
+								 p_hw_info->n_nthw_adapter_id,
+								 "PSU0",
+								 NT_SENSOR_SOURCE_ADAPTER,
+								 NT_SENSOR_TYPE_TEMPERATURE,
+								 NT_SENSOR_NT200E3_PSU0_TEMP,
+								 SENSOR_MON_PSU_EXAR_7724_0, 0x15,
+								 SENSOR_MON_LITTLE_ENDIAN,
+								 SENSOR_MON_UNSIGNED,
+								 &exar7724_tj, 0xFFFF);
+			sensors_list_ptr = sensors_list_ptr->next;
+			adapter->adapter_sensors_cnt++;
+
+			sensors_list_ptr->next = avr_sensor_init(sensors_s_spi,
+								 p_hw_info->n_nthw_adapter_id,
+								 "PSU1",
+								 NT_SENSOR_SOURCE_ADAPTER,
+								 NT_SENSOR_TYPE_TEMPERATURE,
+								 NT_SENSOR_NT200A02_PSU1_TEMP,
+								 SENSOR_MON_MP2886A, 0x8d,
+								 SENSOR_MON_BIG_ENDIAN,
+								 SENSOR_MON_UNSIGNED,
+								 &mp2886a_tj, 0xFFFF);
+			sensors_list_ptr = sensors_list_ptr->next;
+			adapter->adapter_sensors_cnt++;
+
+			sensors_list_ptr->next = avr_sensor_init(sensors_s_spi,
+								 p_hw_info->n_nthw_adapter_id,
+								 "PCB",
+								 NT_SENSOR_SOURCE_ADAPTER,
+								 NT_SENSOR_TYPE_TEMPERATURE,
+								 NT_SENSOR_NT200E3_PCB_TEMP,
+								 SENSOR_MON_DS1775, 0,
+								 SENSOR_MON_LITTLE_ENDIAN,
+								 SENSOR_MON_SIGNED,
+								 &ds1775_t, 0xFFFF);
+			sensors_list_ptr = sensors_list_ptr->next;
+			adapter->adapter_sensors_cnt++;
+
+			NT_LOG(DBG, ETHDEV, "AVR sensors init finished\n");
+
+			if (nt_avr_sensor_mon_ctrl(sensors_s_spi,
+						   SENSOR_MON_CTRL_RUN) != 0) {
+				/* start sensor monitoring */
+				NT_LOG(ERR, ETHDEV,
+				       "Failed to start AVR sensors monitoring\n");
+			} else {
+				NT_LOG(DBG, ETHDEV,
+				       "AVR sensors monitoring starteed\n");
+			}
+		}
+
+		nthw_spi_v3_delete(sensors_s_spi);
+	}
+}
+
+int nt4ga_adapter_init(struct adapter_info_s *p_adapter_info)
+{
+	char *const p_dev_name = malloc(24);
+	char *const p_adapter_id_str = malloc(24);
+	fpga_info_t *fpga_info = &p_adapter_info->fpga_info;
+	hw_info_t *p_hw_info = &p_adapter_info->hw_info;
+
+	/*
+	 * IMPORTANT: Most variables cannot be determined before fpga model is instantiated
+	 * (nthw_fpga_init())
+	 */
+	int n_phy_ports = -1;
+	int n_nim_ports = -1;
+	int res = -1;
+	nt_fpga_t *p_fpga = NULL;
+
+	(void)n_nim_ports; /* currently UNUSED - prevent warning */
+
+	p_hw_info->n_nthw_adapter_id =
+		nthw_platform_get_nthw_adapter_id(p_hw_info->pci_device_id);
+
+	fpga_info->n_nthw_adapter_id = p_hw_info->n_nthw_adapter_id;
+	p_hw_info->hw_product_type = p_hw_info->pci_device_id &
+				   0x000f; /* ref: DN-0060 section 9 */
+	/* ref: DN-0060 section 9 */
+	p_hw_info->hw_platform_id = (p_hw_info->pci_device_id >> 4) & 0x00ff;
+	/* ref: DN-0060 section 9 */
+	p_hw_info->hw_reserved1 = (p_hw_info->pci_device_id >> 12) & 0x000f;
+
+	/* mp_dev_name */
+	p_adapter_info->p_dev_name = p_dev_name;
+	if (p_dev_name) {
+		snprintf(p_dev_name, 24, "" PCIIDENT_PRINT_STR "",
+			 PCIIDENT_TO_DOMAIN(p_adapter_info->fpga_info.pciident),
+			 PCIIDENT_TO_BUSNR(p_adapter_info->fpga_info.pciident),
+			 PCIIDENT_TO_DEVNR(p_adapter_info->fpga_info.pciident),
+			 PCIIDENT_TO_FUNCNR(p_adapter_info->fpga_info.pciident));
+		NT_LOG(DBG, ETHDEV, "%s: (0x%08X)\n", p_dev_name,
+		       p_adapter_info->fpga_info.pciident);
+	}
+
+	/* mp_adapter_id_str */
+	p_adapter_info->mp_adapter_id_str = p_adapter_id_str;
+
+	p_adapter_info->fpga_info.mp_adapter_id_str = p_adapter_id_str;
+
+	if (p_adapter_id_str) {
+		snprintf(p_adapter_id_str, 24, "PCI:" PCIIDENT_PRINT_STR "",
+			 PCIIDENT_TO_DOMAIN(p_adapter_info->fpga_info.pciident),
+			 PCIIDENT_TO_BUSNR(p_adapter_info->fpga_info.pciident),
+			 PCIIDENT_TO_DEVNR(p_adapter_info->fpga_info.pciident),
+			 PCIIDENT_TO_FUNCNR(p_adapter_info->fpga_info.pciident));
+		NT_LOG(DBG, ETHDEV, "%s: %s\n", p_adapter_id_str, p_dev_name);
+	}
+
+	{
+		int i;
+
+		for (i = 0; i < (int)ARRAY_SIZE(p_adapter_info->mp_port_id_str);
+				i++) {
+			char *p = malloc(32);
+
+			if (p) {
+				snprintf(p, 32, "%s:intf_%d",
+					 (p_adapter_id_str ? p_adapter_id_str : "NA"),
+					 i);
+				NT_LOG(DBG, ETHDEV, "%s\n", p);
+			}
+			p_adapter_info->mp_port_id_str[i] = p;
+		}
+	}
+
+	res = nthw_fpga_init(&p_adapter_info->fpga_info);
+	if (res) {
+		NT_LOG(ERR, ETHDEV, "%s: %s: FPGA=%04d res=x%08X [%s:%u]\n",
+		       p_adapter_id_str, p_dev_name, fpga_info->n_fpga_prod_id, res,
+		       __func__, __LINE__);
+		return res;
+	}
+
+	assert(fpga_info);
+	p_fpga = fpga_info->mp_fpga;
+	assert(p_fpga);
+	n_phy_ports = fpga_info->n_phy_ports;
+	assert(n_phy_ports >= 1);
+	n_nim_ports = fpga_info->n_nims;
+	assert(n_nim_ports >= 1);
+
+	/*
+	 * HIF/PCI TA/TG
+	 */
+	{
+		res = nt4ga_pci_ta_tg_init(p_adapter_info);
+		if (res == 0) {
+			nt4ga_pci_ta_tg_measure_throughput_main(p_adapter_info,
+								0, 0,
+								TG_PKT_SIZE,
+								TG_NUM_PACKETS,
+								TG_DELAY);
+		} else {
+			NT_LOG(WRN, ETHDEV,
+			       "%s: PCI TA/TG is not available - skipping\n",
+			       p_adapter_id_str);
+		}
+	}
+
+	adapter_sensor_setup(p_hw_info, p_adapter_info);
+
+	{
+		int i;
+
+		assert(fpga_info->n_fpga_prod_id > 0);
+		for (i = 0; i < NUM_ADAPTER_PORTS_MAX; i++) {
+			/* Disable all ports. Must be enabled later */
+			p_adapter_info->nt4ga_link.port_action[i].port_disable =
+				true;
+		}
+		switch (fpga_info->n_fpga_prod_id) {
+		/* NT200A02: 2x100G */
+		case 9563: /* NT200A02 */
+			res = nt4ga_link_100g_ports_init(p_adapter_info, p_fpga);
+			break;
+		default:
+			NT_LOG(ERR, ETHDEV,
+			       "%s: Unsupported FPGA product: %04d\n", __func__,
+			       fpga_info->n_fpga_prod_id);
+			res = -1;
+			break;
+		}
+
+		if (res) {
+			NT_LOG(ERR, ETHDEV,
+			       "%s: %s: %s: %u: FPGA=%04d res=x%08X\n",
+			       p_adapter_id_str, p_dev_name, __func__, __LINE__,
+			       fpga_info->n_fpga_prod_id, res);
+			return res;
+		}
+	}
+
+	/*
+	 * HostBuffer Systems
+	 */
+	p_adapter_info->n_rx_host_buffers = 0;
+	p_adapter_info->n_tx_host_buffers = 0;
+
+	p_adapter_info->fpga_info.mp_nthw_epp = NULL;
+	if (nthw_epp_present(p_adapter_info->fpga_info.mp_fpga, 0)) {
+		p_adapter_info->fpga_info.mp_nthw_epp = nthw_epp_new();
+		if (p_adapter_info->fpga_info.mp_nthw_epp == NULL) {
+			NT_LOG(ERR, ETHDEV, "%s: Cannot create EPP\n",
+			       p_adapter_id_str);
+			return -1;
+		}
+
+		res = nthw_epp_init(p_adapter_info->fpga_info.mp_nthw_epp,
+				    p_adapter_info->fpga_info.mp_fpga, 0);
+		if (res != 0) {
+			NT_LOG(ERR, ETHDEV, "%s: Cannot initialize EPP\n",
+			       p_adapter_id_str);
+			return res;
+		}
+		NT_LOG(DBG, ETHDEV, "%s: Initialized EPP\n",
+		       p_adapter_id_str);
+
+		res = nthw_epp_setup(p_adapter_info->fpga_info.mp_nthw_epp);
+		if (res != 0) {
+			NT_LOG(ERR, ETHDEV, "%s: Cannot setup EPP\n",
+			       p_adapter_id_str);
+			return res;
+		}
+	}
+
+	/* Nt4ga Stat init/setup */
+	res = nt4ga_stat_init(p_adapter_info);
+	if (res != 0) {
+		NT_LOG(ERR, ETHDEV,
+		       "%s: Cannot initialize the statistics module\n",
+		       p_adapter_id_str);
+		return res;
+	}
+	res = nt4ga_stat_setup(p_adapter_info);
+	if (res != 0) {
+		NT_LOG(ERR, ETHDEV, "%s: Cannot setup the statistics module\n",
+		       p_adapter_id_str);
+		return res;
+	}
+
+	return 0;
+}
+
+int nt4ga_adapter_deinit(struct adapter_info_s *p_adapter_info)
+{
+	fpga_info_t *fpga_info = &p_adapter_info->fpga_info;
+	int i;
+	int res;
+	struct nt_sensor_group *cur_adapter_sensor = NULL;
+	struct nt_sensor_group *next_adapter_sensor = NULL;
+	struct nim_sensor_group *cur_nim_sensor = NULL;
+	struct nim_sensor_group *next_nim_sensor = NULL;
+
+	stop_monitor_tasks(-1);
+
+	nt4ga_stat_stop(p_adapter_info);
+
+	nthw_fpga_shutdown(&p_adapter_info->fpga_info);
+
+	/* Rac rab reset flip flop */
+	res = nthw_rac_rab_reset(fpga_info->mp_nthw_rac);
+
+	/* Free adapter port ident strings */
+	for (i = 0; i < fpga_info->n_phy_ports; i++) {
+		if (p_adapter_info->mp_port_id_str[i]) {
+			free(p_adapter_info->mp_port_id_str[i]);
+			p_adapter_info->mp_port_id_str[i] = NULL;
+		}
+	}
+
+	/* Free adapter ident string */
+	if (p_adapter_info->mp_adapter_id_str) {
+		free(p_adapter_info->mp_adapter_id_str);
+		p_adapter_info->mp_adapter_id_str = NULL;
+	}
+
+	/* Free devname ident string */
+	if (p_adapter_info->p_dev_name) {
+		free(p_adapter_info->p_dev_name);
+		p_adapter_info->p_dev_name = NULL;
+	}
+
+	/* Free adapter sensors */
+	if (p_adapter_info->adapter_sensors != NULL) {
+		do {
+			cur_adapter_sensor = p_adapter_info->adapter_sensors;
+			next_adapter_sensor =
+				p_adapter_info->adapter_sensors->next;
+			p_adapter_info->adapter_sensors = next_adapter_sensor;
+
+			sensor_deinit(cur_adapter_sensor);
+		} while (next_adapter_sensor != NULL);
+	}
+
+	/* Free NIM sensors */
+	for (i = 0; i < fpga_info->n_phy_ports; i++) {
+		if (p_adapter_info->nim_sensors[i] != NULL) {
+			do {
+				cur_nim_sensor = p_adapter_info->nim_sensors[i];
+				next_nim_sensor =
+					p_adapter_info->nim_sensors[i]->next;
+				p_adapter_info->nim_sensors[i] = next_nim_sensor;
+				free(cur_nim_sensor->sensor);
+				free(cur_nim_sensor);
+			} while (next_nim_sensor != NULL);
+		}
+	}
+
+	return res;
+}
diff --git a/drivers/net/ntnic/adapter/nt4ga_adapter.h b/drivers/net/ntnic/adapter/nt4ga_adapter.h
new file mode 100644
index 0000000000..6ae78a3743
--- /dev/null
+++ b/drivers/net/ntnic/adapter/nt4ga_adapter.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _NT4GA_ADAPTER_H_
+#define _NT4GA_ADAPTER_H_
+
+#include "common_adapter_defs.h"
+
+struct adapter_info_s;
+
+/*
+ * DN-0060 section 9
+ */
+typedef struct hw_info_s {
+	/* pciids */
+	uint16_t pci_vendor_id;
+	uint16_t pci_device_id;
+	uint16_t pci_sub_vendor_id;
+	uint16_t pci_sub_device_id;
+	uint16_t pci_class_id;
+
+	/* Derived from pciid */
+	nthw_adapter_id_t n_nthw_adapter_id;
+	int hw_platform_id;
+	int hw_product_type;
+	int hw_reserved1;
+} hw_info_t;
+
+/*
+ * Services provided by the adapter module
+ */
+#include "nt4ga_pci_ta_tg.h"
+#include "nt4ga_filter.h"
+#include "nt4ga_stat.h"
+#include "nt4ga_link.h"
+
+#include "sensors.h"
+#include "i2c_nim.h"
+#include "sensor_types.h"
+
+typedef struct adapter_info_s {
+	struct nt4ga_pci_ta_tg_s nt4ga_pci_ta_tg;
+	struct nt4ga_stat_s nt4ga_stat;
+	struct nt4ga_filter_s nt4ga_filter;
+	struct nt4ga_link_s nt4ga_link;
+
+	struct hw_info_s hw_info;
+	struct fpga_info_s fpga_info;
+
+	uint16_t adapter_sensors_cnt;
+	uint16_t nim_sensors_cnt[NUM_ADAPTER_PORTS_MAX];
+	struct nt_sensor_group *adapter_sensors;
+	struct nim_sensor_group *nim_sensors[NUM_ADAPTER_PORTS_MAX];
+
+	char *mp_port_id_str[NUM_ADAPTER_PORTS_MAX];
+	char *mp_adapter_id_str;
+	char *p_dev_name;
+	volatile bool *pb_shutdown;
+
+	int adapter_no;
+	int n_rx_host_buffers;
+	int n_tx_host_buffers;
+} adapter_info_t;
+
+/*
+ * Monitor task operations.  This structure defines the management hooks for
+ * Napatech network devices.  The following hooks can be defined; unless noted
+ * otherwise, they are optional and can be filled with a null pointer.
+ *
+ * int (*mto_open)(int adapter, int port);
+ *     The function to call when a network device transitions to the up state,
+ *     e.g., `ip link set <interface> up`.
+ *
+ * int (*mto_stop)(int adapter, int port);
+ *     The function to call when a network device transitions to the down state,
+ *     e.g., `ip link set <interface> down`.
+ */
+struct monitor_task_ops {
+	int (*mto_open)(int adapter, int port);
+	int (*mto_stop)(int adapter, int port);
+};
+
+#include <pthread.h>
+#include <signal.h>
+
+/* The file nt4ga_adapter.c defines the next four variables. */
+extern pthread_t monitor_tasks[NUM_ADAPTER_MAX];
+extern volatile int monitor_task_is_running[NUM_ADAPTER_MAX];
+
+/*
+ * Function that sets up signal handler(s) that stop the monitoring tasks.
+ */
+int set_up_signal_handlers_to_stop_monitoring_tasks(void);
+
+int nt4ga_adapter_init(struct adapter_info_s *p_adapter_info);
+int nt4ga_adapter_deinit(struct adapter_info_s *p_adapter_info);
+
+int nt4ga_adapter_status(struct adapter_info_s *p_adapter_info);
+int nt4ga_adapter_transmit_packet(struct adapter_info_s *p_adapter_info,
+				  int n_intf_no, uint8_t *p_pkt, int n_pkt_len);
+
+int nt4ga_adapter_show_info(struct adapter_info_s *p_adapter_info, FILE *pfh);
+
+/* SPI for sensors reading */
+nthw_spis_t *new_sensors_t_spi(struct nt_fpga_s *p_fpga);
+
+#endif /* _NT4GA_ADAPTER_H_ */
diff --git a/drivers/net/ntnic/adapter/nt4ga_filter.h b/drivers/net/ntnic/adapter/nt4ga_filter.h
new file mode 100644
index 0000000000..ad7e7d8c71
--- /dev/null
+++ b/drivers/net/ntnic/adapter/nt4ga_filter.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NT4GA_FILTER_H_
+#define NT4GA_FILTER_H_
+
+typedef struct nt4ga_filter_s {
+	int n_intf_cnt;
+	int n_queues_per_intf_cnt;
+
+	struct flow_nic_dev *mp_flow_device;
+} nt4ga_filter_t;
+
+#endif /* NT4GA_FILTER_H_ */
diff --git a/drivers/net/ntnic/adapter/nt4ga_link.c b/drivers/net/ntnic/adapter/nt4ga_link.c
new file mode 100644
index 0000000000..7fbdb72897
--- /dev/null
+++ b/drivers/net/ntnic/adapter/nt4ga_link.c
@@ -0,0 +1,178 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include "ntlog.h"
+#include "nthw_drv.h"
+#include "nt4ga_adapter.h"
+
+#include "nt4ga_link.h"
+#include "nt_util.h"
+
+/*
+ * port: speed capabilitoes
+ * This is actually an adapter capability mapped onto every port
+ */
+uint32_t nt4ga_port_get_link_speed_capabilities(struct adapter_info_s *p _unused,
+		int port _unused)
+{
+	nt4ga_link_t *const p_link = &p->nt4ga_link;
+	const uint32_t nt_link_speed_capa = p_link->speed_capa;
+	return nt_link_speed_capa;
+}
+
+/*
+ * port: nim present
+ */
+bool nt4ga_port_get_nim_present(struct adapter_info_s *p, int port)
+{
+	nt4ga_link_t *const p_link = &p->nt4ga_link;
+	const bool nim_present = p_link->link_state[port].nim_present;
+	return nim_present;
+}
+
+/*
+ * port: link mode
+ */
+void nt4ga_port_set_adm_state(struct adapter_info_s *p, int port, bool adm_state)
+{
+	nt4ga_link_t *const p_link = &p->nt4ga_link;
+
+	p_link->port_action[port].port_disable = !adm_state;
+}
+
+bool nt4ga_port_get_adm_state(struct adapter_info_s *p, int port)
+{
+	nt4ga_link_t *const p_link = &p->nt4ga_link;
+	const bool adm_state = !p_link->port_action[port].port_disable;
+	return adm_state;
+}
+
+/*
+ * port: link status
+ */
+void nt4ga_port_set_link_status(struct adapter_info_s *p, int port,
+				bool link_status)
+{
+	/* Setting link state/status is (currently) the same as controlling the port adm state */
+	nt4ga_port_set_adm_state(p, port, link_status);
+}
+
+bool nt4ga_port_get_link_status(struct adapter_info_s *p, int port)
+{
+	nt4ga_link_t *const p_link = &p->nt4ga_link;
+	bool status = p_link->link_state[port].link_up;
+	return status;
+}
+
+/*
+ * port: link speed
+ */
+void nt4ga_port_set_link_speed(struct adapter_info_s *p, int port,
+			       nt_link_speed_t speed)
+{
+	nt4ga_link_t *const p_link = &p->nt4ga_link;
+
+	p_link->port_action[port].port_speed = speed;
+	p_link->link_info[port].link_speed = speed;
+}
+
+nt_link_speed_t nt4ga_port_get_link_speed(struct adapter_info_s *p, int port)
+{
+	nt4ga_link_t *const p_link = &p->nt4ga_link;
+	nt_link_speed_t speed = p_link->link_info[port].link_speed;
+	return speed;
+}
+
+/*
+ * port: link autoneg
+ * Currently not fully supported by link code
+ */
+void nt4ga_port_set_link_autoneg(struct adapter_info_s *p _unused,
+				 int port _unused, bool autoneg _unused)
+{
+	nt4ga_link_t *const p_link _unused = &p->nt4ga_link;
+}
+
+bool nt4ga_port_get_link_autoneg(struct adapter_info_s *p _unused,
+				 int port _unused)
+{
+	nt4ga_link_t *const p_link _unused = &p->nt4ga_link;
+	return true;
+}
+
+/*
+ * port: link duplex
+ * Currently not fully supported by link code
+ */
+void nt4ga_port_set_link_duplex(struct adapter_info_s *p, int port,
+				nt_link_duplex_t duplex)
+{
+	nt4ga_link_t *const p_link = &p->nt4ga_link;
+
+	p_link->port_action[port].port_duplex = duplex;
+}
+
+nt_link_duplex_t nt4ga_port_get_link_duplex(struct adapter_info_s *p, int port)
+{
+	nt4ga_link_t *const p_link = &p->nt4ga_link;
+	nt_link_duplex_t duplex = p_link->link_info[port].link_duplex;
+	return duplex;
+}
+
+/*
+ * port: loopback mode
+ */
+void nt4ga_port_set_loopback_mode(struct adapter_info_s *p, int port,
+				  uint32_t mode)
+{
+	nt4ga_link_t *const p_link = &p->nt4ga_link;
+
+	p_link->port_action[port].port_lpbk_mode = mode;
+}
+
+uint32_t nt4ga_port_get_loopback_mode(struct adapter_info_s *p, int port)
+{
+	nt4ga_link_t *const p_link = &p->nt4ga_link;
+
+	return p_link->port_action[port].port_lpbk_mode;
+}
+
+/*
+ * port: nim capabilities
+ */
+nim_i2c_ctx_t nt4ga_port_get_nim_capabilities(struct adapter_info_s *p, int port)
+{
+	nt4ga_link_t *const p_link = &p->nt4ga_link;
+	nim_i2c_ctx_t nim_ctx = p_link->u.var100g.nim_ctx[port];
+	return nim_ctx;
+}
+
+/*
+ * port: tx power
+ */
+int nt4ga_port_tx_power(struct adapter_info_s *p, int port, bool disable)
+{
+	nt4ga_link_t *link_info = &p->nt4ga_link;
+
+	if (link_info->u.nim_ctx[port].port_type == NT_PORT_TYPE_QSFP28_SR4 ||
+			link_info->u.nim_ctx[port].port_type == NT_PORT_TYPE_QSFP28 ||
+			link_info->u.nim_ctx[port].port_type == NT_PORT_TYPE_QSFP28_LR4) {
+		nim_i2c_ctx_t *nim_ctx = &link_info->u.var100g.nim_ctx[port];
+
+		if (!nim_ctx->specific_u.qsfp.rx_only) {
+			if (nim_qsfp_plus_nim_set_tx_laser_disable(nim_ctx, disable,
+							       -1) != 0)
+				return 1;
+		}
+	} else {
+		return -1;
+	}
+	return 0;
+}
diff --git a/drivers/net/ntnic/adapter/nt4ga_link.h b/drivers/net/ntnic/adapter/nt4ga_link.h
new file mode 100644
index 0000000000..2be9f49075
--- /dev/null
+++ b/drivers/net/ntnic/adapter/nt4ga_link.h
@@ -0,0 +1,179 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NT4GA_LINK_H_
+#define NT4GA_LINK_H_
+
+#include "common_adapter_defs.h"
+#include "nthw_drv.h"
+#include "i2c_nim.h"
+#include "nthw_fpga_rst_nt200a0x.h"
+
+/*
+ * Link state.\n
+ * Just after start of ntservice the link state might be unknown since the
+ * monitoring routine is busy reading NIM state and NIM data. This might also
+ * be the case after a NIM is plugged into an interface.
+ * The error state indicates a HW reading error.
+ */
+enum nt_link_state_e {
+	NT_LINK_STATE_UNKNOWN = 0, /* The link state has not been read yet */
+	NT_LINK_STATE_DOWN = 1, /* The link state is DOWN */
+	NT_LINK_STATE_UP = 2, /* The link state is UP */
+	NT_LINK_STATE_ERROR = 3 /* The link state could not be read */
+};
+
+typedef enum nt_link_state_e nt_link_state_t, *nt_link_state_p;
+
+/*
+ * Link duplex mode
+ */
+enum nt_link_duplex_e {
+	NT_LINK_DUPLEX_UNKNOWN = 0,
+	NT_LINK_DUPLEX_HALF = 0x01, /* Half duplex */
+	NT_LINK_DUPLEX_FULL = 0x02, /* Full duplex */
+};
+
+typedef enum nt_link_duplex_e nt_link_duplex_t;
+
+/*
+ * Link loopback mode
+ */
+enum nt_link_loopback_e {
+	NT_LINK_LOOPBACK_OFF = 0,
+	NT_LINK_LOOPBACK_HOST = 0x01, /* Host loopback mode */
+	NT_LINK_LOOPBACK_LINE = 0x02, /* Line loopback mode */
+};
+
+/*
+ * Link MDI mode
+ */
+enum nt_link_mdi_e {
+	NT_LINK_MDI_NA = 0,
+	NT_LINK_MDI_AUTO = 0x01, /* MDI auto */
+	NT_LINK_MDI_MDI = 0x02, /* MDI mode */
+	NT_LINK_MDI_MDIX = 0x04, /* MDIX mode */
+};
+
+typedef enum nt_link_mdi_e nt_link_mdi_t;
+
+/*
+ * Link Auto/Manual mode
+ */
+enum nt_link_auto_neg_e {
+	NT_LINK_AUTONEG_NA = 0,
+	NT_LINK_AUTONEG_MANUAL = 0x01,
+	NT_LINK_AUTONEG_OFF = NT_LINK_AUTONEG_MANUAL, /* Auto negotiation OFF */
+	NT_LINK_AUTONEG_AUTO = 0x02,
+	NT_LINK_AUTONEG_ON = NT_LINK_AUTONEG_AUTO, /* Auto negotiation ON */
+};
+
+typedef enum nt_link_auto_neg_e nt_link_auto_neg_t;
+
+/*
+ * Callback functions to setup mac, pcs and phy
+ */
+typedef struct link_state_s {
+	bool link_disabled;
+	bool nim_present;
+	bool lh_nim_absent;
+	bool link_up;
+	enum nt_link_state_e link_state;
+	enum nt_link_state_e link_state_latched;
+} link_state_t;
+
+typedef struct link_info_s {
+	enum nt_link_speed_e link_speed;
+	enum nt_link_duplex_e link_duplex;
+	enum nt_link_auto_neg_e link_auto_neg;
+} link_info_t;
+
+typedef struct port_action_s {
+	bool port_disable;
+	enum nt_link_speed_e port_speed;
+	enum nt_link_duplex_e port_duplex;
+	uint32_t port_lpbk_mode;
+} port_action_t;
+
+typedef struct adapter_100g_s {
+	nim_i2c_ctx_t
+	nim_ctx[NUM_ADAPTER_PORTS_MAX]; /* Should be the first field */
+	nthw_mac_pcs_t mac_pcs100g[NUM_ADAPTER_PORTS_MAX];
+	nthw_gpio_phy_t gpio_phy[NUM_ADAPTER_PORTS_MAX];
+} adapter_100g_t;
+
+typedef union adapter_var_s {
+	nim_i2c_ctx_t nim_ctx
+	[NUM_ADAPTER_PORTS_MAX]; /* First field in all the adaptors type */
+	adapter_100g_t var100g;
+} adapter_var_u;
+
+typedef struct nt4ga_link_s {
+	link_state_t link_state[NUM_ADAPTER_PORTS_MAX];
+	link_info_t link_info[NUM_ADAPTER_PORTS_MAX];
+	port_action_t port_action[NUM_ADAPTER_PORTS_MAX];
+	uint32_t speed_capa;
+	/* */
+	bool variables_initialized;
+	adapter_var_u u;
+} nt4ga_link_t;
+
+bool nt4ga_port_get_nim_present(struct adapter_info_s *p, int port);
+
+/*
+ * port:s link mode
+ */
+void nt4ga_port_set_adm_state(struct adapter_info_s *p, int port,
+			      bool adm_state);
+bool nt4ga_port_get_adm_state(struct adapter_info_s *p, int port);
+
+/*
+ * port:s link status
+ */
+void nt4ga_port_set_link_status(struct adapter_info_s *p, int port, bool status);
+bool nt4ga_port_get_link_status(struct adapter_info_s *p, int port);
+
+/*
+ * port: link autoneg
+ */
+void nt4ga_port_set_link_autoneg(struct adapter_info_s *p, int port,
+				 bool autoneg);
+bool nt4ga_port_get_link_autoneg(struct adapter_info_s *p, int port);
+
+/*
+ * port: link speed
+ */
+void nt4ga_port_set_link_speed(struct adapter_info_s *p, int port,
+			       nt_link_speed_t speed);
+nt_link_speed_t nt4ga_port_get_link_speed(struct adapter_info_s *p, int port);
+
+/*
+ * port: link duplex
+ */
+void nt4ga_port_set_link_duplex(struct adapter_info_s *p, int port,
+				nt_link_duplex_t duplex);
+nt_link_duplex_t nt4ga_port_get_link_duplex(struct adapter_info_s *p, int port);
+
+/*
+ * port: loopback mode
+ */
+void nt4ga_port_set_loopback_mode(struct adapter_info_s *p, int port,
+				  uint32_t mode);
+uint32_t nt4ga_port_get_loopback_mode(struct adapter_info_s *p, int port);
+
+uint32_t nt4ga_port_get_link_speed_capabilities(struct adapter_info_s *p,
+		int port);
+
+/*
+ * port: nim capabilities
+ */
+nim_i2c_ctx_t nt4ga_port_get_nim_capabilities(struct adapter_info_s *p,
+		int port);
+
+/*
+ * port: tx power
+ */
+int nt4ga_port_tx_power(struct adapter_info_s *p, int port, bool disable);
+
+#endif /* NT4GA_LINK_H_ */
diff --git a/drivers/net/ntnic/adapter/nt4ga_link_100g.c b/drivers/net/ntnic/adapter/nt4ga_link_100g.c
new file mode 100644
index 0000000000..8465b6a341
--- /dev/null
+++ b/drivers/net/ntnic/adapter/nt4ga_link_100g.c
@@ -0,0 +1,825 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "nt_util.h"
+#include "ntlog.h"
+#include "i2c_nim.h"
+#include "nt4ga_adapter.h"
+#include "nt4ga_link_100g.h"
+
+#include <string.h> /* memcmp, memset */
+
+/*
+ * Prototypes
+ */
+static int swap_tx_rx_polarity(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs,
+				int port, bool swap);
+static int reset_rx(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs);
+
+/*
+ * Structs and types definitions
+ */
+enum link_up_state {
+	RESET, /* A valid signal is detected by NO local faults. */
+	EXPECT_NO_LF, /* After that we check NO latched local fault bit before */
+	/* de-asserting Remote fault indication. */
+	WAIT_STABLE_LINK, /* Now we expect the link is up. */
+	MONITOR_LINK /* After link-up we monitor link state. */
+};
+
+typedef struct _monitoring_state {
+	/* Fields below are set by monitoring thread */
+	enum link_up_state m_link_up_state;
+	enum nt_link_state_e link_state;
+	enum nt_link_state_e latch_link_state;
+	int m_time_out;
+} monitoring_state_t, *monitoring_state_p;
+
+/*
+ * Global variables
+ */
+
+/*
+ * External state, to be set by the network driver.
+ */
+
+/*
+ * Utility functions
+ */
+
+static void set_loopback(struct adapter_info_s *p_adapter_info,
+			  nthw_mac_pcs_t *mac_pcs, int intf_no, uint32_t mode,
+			  uint32_t last_mode)
+{
+	bool swap_polerity = true;
+
+	switch (mode) {
+	case 1:
+		NT_LOG(INF, ETHDEV, "%s: Applying host loopback\n",
+		       p_adapter_info->mp_port_id_str[intf_no]);
+		nthw_mac_pcs_set_fec(mac_pcs, true);
+		nthw_mac_pcs_set_host_loopback(mac_pcs, true);
+		swap_polerity = false;
+		break;
+	case 2:
+		NT_LOG(INF, ETHDEV, "%s: Applying line loopback\n",
+		       p_adapter_info->mp_port_id_str[intf_no]);
+		nthw_mac_pcs_set_line_loopback(mac_pcs, true);
+		break;
+	default:
+		switch (last_mode) {
+		case 1:
+			NT_LOG(INF, ETHDEV, "%s: Removing host loopback\n",
+			       p_adapter_info->mp_port_id_str[intf_no]);
+			nthw_mac_pcs_set_host_loopback(mac_pcs, false);
+			break;
+		case 2:
+			NT_LOG(INF, ETHDEV, "%s: Removing line loopback\n",
+			       p_adapter_info->mp_port_id_str[intf_no]);
+			nthw_mac_pcs_set_line_loopback(mac_pcs, false);
+			break;
+		default:
+			/* Do nothing */
+			break;
+		}
+		break;
+	}
+
+	if ((p_adapter_info->fpga_info.nthw_hw_info.hw_id == 2 &&
+			p_adapter_info->hw_info.n_nthw_adapter_id == NT_HW_ADAPTER_ID_NT200A01) ||
+			p_adapter_info->hw_info.n_nthw_adapter_id == NT_HW_ADAPTER_ID_NT200A02) {
+		(void)swap_tx_rx_polarity(p_adapter_info, mac_pcs, intf_no,
+					   swap_polerity);
+	}
+
+	/* After changing the loopback the system must be properly reset */
+	reset_rx(p_adapter_info, mac_pcs);
+
+	NT_OS_WAIT_USEC(10000); /* 10ms - arbitrary choice */
+
+	if (!nthw_mac_pcs_is_rx_path_rst(mac_pcs)) {
+		nthw_mac_pcs_reset_bip_counters(mac_pcs);
+		if (!nthw_mac_pcs_get_fec_bypass(mac_pcs))
+			nthw_mac_pcs_reset_fec_counters(mac_pcs);
+	}
+}
+
+/*
+ * Function to retrieve the current state of a link (for one port)
+ */
+static int link_state_build(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs,
+			     nthw_gpio_phy_t *gpio_phy, int port,
+			     link_state_t *state, bool is_port_disabled)
+{
+	uint32_t abs;
+	uint32_t phy_link_state;
+	uint32_t lh_abs;
+	uint32_t ll_phy_link_state;
+	uint32_t link_down_cnt;
+	uint32_t nim_interr;
+	uint32_t lh_local_fault;
+	uint32_t lh_remote_fault;
+	uint32_t lh_internal_local_fault;
+	uint32_t lh_received_local_fault;
+
+	memset(state, 0, sizeof(*state));
+	state->link_disabled = is_port_disabled;
+	nthw_mac_pcs_get_link_summary(mac_pcs, &abs, &phy_link_state, &lh_abs,
+				  &ll_phy_link_state, &link_down_cnt,
+				  &nim_interr, &lh_local_fault,
+				  &lh_remote_fault, &lh_internal_local_fault,
+				  &lh_received_local_fault);
+
+	assert(port >= 0 && port < NUM_ADAPTER_PORTS_MAX);
+	state->nim_present =
+		nthw_gpio_phy_is_module_present(gpio_phy, (uint8_t)port);
+	state->lh_nim_absent = !state->nim_present;
+	state->link_up = phy_link_state ? true : false;
+
+	{
+		static char lsbuf[NUM_ADAPTER_MAX][NUM_ADAPTER_PORTS_MAX][256];
+		char buf[255];
+		const int adapter_no = drv->adapter_no;
+
+		snprintf(buf, sizeof(buf),
+			 "%s: Port = %d: abs = %u, phy_link_state = %u, lh_abs = %u, "
+			 "ll_phy_link_state = %u, link_down_cnt = %u, nim_interr = %u, "
+			 "lh_local_fault = %u, lh_remote_fault = %u, lh_internal_local_fault = %u, "
+			 "lh_received_local_fault = %u",
+			drv->mp_adapter_id_str, mac_pcs->mn_instance, abs,
+			phy_link_state, lh_abs, ll_phy_link_state,
+			link_down_cnt, nim_interr, lh_local_fault,
+			lh_remote_fault, lh_internal_local_fault,
+			lh_received_local_fault);
+		if (strcmp(lsbuf[adapter_no][port], buf) != 0) {
+			rte_strscpy(lsbuf[adapter_no][port], buf,
+				sizeof(lsbuf[adapter_no][port]) - 1U);
+			lsbuf[adapter_no][port]
+			[sizeof(lsbuf[adapter_no][port]) - 1U] = '\0';
+			NT_LOG(DBG, ETHDEV, "%s\n", lsbuf[adapter_no][port]);
+		}
+	}
+	return 0;
+}
+
+/*
+ * Check whether a NIM module is present
+ */
+static bool nim_is_present(nthw_gpio_phy_t *gpio_phy, uint8_t if_no)
+{
+	assert(if_no < NUM_ADAPTER_PORTS_MAX);
+
+	return nthw_gpio_phy_is_module_present(gpio_phy, if_no);
+}
+
+/*
+ * Enable RX
+ */
+static int enable_rx(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs)
+{
+	(void)drv; /* unused */
+	nthw_mac_pcs_set_rx_enable(mac_pcs, true);
+	return 0;
+}
+
+/*
+ * Enable TX
+ */
+static int enable_tx(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs)
+{
+	(void)drv; /* unused */
+	nthw_mac_pcs_set_tx_enable(mac_pcs, true);
+	nthw_mac_pcs_set_tx_sel_host(mac_pcs, true);
+	return 0;
+}
+
+/*
+ * Disable RX
+ */
+static int disable_rx(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs)
+{
+	(void)drv; /* unused */
+	nthw_mac_pcs_set_rx_enable(mac_pcs, false);
+	return 0;
+}
+
+/*
+ * Disable TX
+ */
+static int disable_tx(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs)
+{
+	(void)drv; /* unused */
+	nthw_mac_pcs_set_tx_enable(mac_pcs, false);
+	nthw_mac_pcs_set_tx_sel_host(mac_pcs, false);
+	return 0;
+}
+
+/*
+ * Reset RX
+ */
+static int reset_rx(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs)
+{
+	(void)drv;
+
+	nthw_mac_pcs_rx_path_rst(mac_pcs, true);
+	NT_OS_WAIT_USEC(10000); /* 10ms */
+	nthw_mac_pcs_rx_path_rst(mac_pcs, false);
+	NT_OS_WAIT_USEC(10000); /* 10ms */
+
+	return 0;
+}
+
+/*
+ * Reset TX
+ */
+
+/*
+ * Swap tx/rx polarity
+ */
+static int swap_tx_rx_polarity(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs,
+				int port, bool swap)
+{
+	const bool tx_polarity_swap[2][4] = { { true, true, false, false },
+		{ false, true, false, false }
+	};
+	const bool rx_polarity_swap[2][4] = { { false, true, true, true },
+		{ false, true, true, false }
+	};
+	uint8_t lane;
+
+	(void)drv;
+	for (lane = 0U; lane < 4U; lane++) {
+		if (swap) {
+			nthw_mac_pcs_swap_gty_tx_polarity(mac_pcs, lane,
+							  tx_polarity_swap[port][lane]);
+			nthw_mac_pcs_swap_gty_rx_polarity(mac_pcs, lane,
+							  rx_polarity_swap[port][lane]);
+		} else {
+			nthw_mac_pcs_swap_gty_tx_polarity(mac_pcs, lane, false);
+			nthw_mac_pcs_swap_gty_rx_polarity(mac_pcs, lane, false);
+		}
+	}
+	return 0;
+}
+
+/*
+ * Check link once NIM is installed and link can be expected.
+ */
+static int check_link_state(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs)
+{
+	bool rst_required;
+	bool ber;
+	bool fec_all_locked;
+
+	rst_required = nthw_mac_pcs_reset_required(mac_pcs);
+
+	ber = nthw_mac_pcs_get_hi_ber(mac_pcs);
+
+	fec_all_locked = nthw_mac_pcs_get_fec_stat_all_am_locked(mac_pcs);
+
+	if (rst_required || ber || !fec_all_locked)
+		reset_rx(drv, mac_pcs);
+
+	return 0;
+}
+
+/*
+ * Initialize NIM, Code based on nt200e3_2_ptp.cpp: MyPort::createNim()
+ */
+static int create_nim(adapter_info_t *drv, nt_fpga_t *fpga, int port,
+		       bool enable)
+{
+	int res = 0;
+	const uint8_t valid_nim_id = 17U;
+	nthw_gpio_phy_t *gpio_phy;
+	nim_i2c_ctx_t *nim_ctx;
+	sfp_nim_state_t nim;
+	nt4ga_link_t *link_info = &drv->nt4ga_link;
+	nthw_mac_pcs_t *mac_pcs = &link_info->u.var100g.mac_pcs100g[port];
+
+	(void)fpga; /* unused */
+	assert(port >= 0 && port < NUM_ADAPTER_PORTS_MAX);
+	assert(link_info->variables_initialized);
+
+	gpio_phy = &link_info->u.var100g.gpio_phy[port];
+	nim_ctx = &link_info->u.var100g.nim_ctx[port];
+
+	/*
+	 * Check NIM is present before doing GPIO PHY reset.
+	 */
+	if (!nim_is_present(gpio_phy, (uint8_t)port)) {
+		NT_LOG(INF, ETHDEV, "%s: NIM module is absent\n",
+		       drv->mp_port_id_str[port]);
+		return 0;
+	}
+
+	if (!enable) {
+		disable_rx(drv, mac_pcs);
+		disable_tx(drv, mac_pcs);
+		reset_rx(drv, mac_pcs);
+	}
+
+	/*
+	 * Perform PHY reset.
+	 */
+	NT_LOG(DBG, ETHDEV, "%s: Performing NIM reset\n",
+	       drv->mp_port_id_str[port]);
+	nthw_gpio_phy_set_reset(gpio_phy, (uint8_t)port, true);
+	NT_OS_WAIT_USEC(100000); /* pause 0.1s */
+	nthw_gpio_phy_set_reset(gpio_phy, (uint8_t)port, false);
+
+	/*
+	 * Wait a little after a module has been inserted before trying to access I2C
+	 * data, otherwise the module will not respond correctly.
+	 */
+	NT_OS_WAIT_USEC(1000000); /* pause 1.0s */
+
+	if (!nim_is_present(gpio_phy, (uint8_t)port)) {
+		NT_LOG(DBG, ETHDEV, "%s: NIM module is no longer absent!\n",
+		       drv->mp_port_id_str[port]);
+		return -1;
+	}
+
+	res = construct_and_preinit_nim(nim_ctx, NULL, port,
+					((struct adapter_info_s *)drv)->nim_sensors,
+					&((struct adapter_info_s *)drv)->nim_sensors_cnt[port]);
+	if (res)
+		return res;
+
+	res = nim_state_build(nim_ctx, &nim);
+	if (res)
+		return res;
+
+	NT_LOG(DBG, NTHW,
+	       "%s: NIM id = %u (%s), br = %u, vendor = '%s', pn = '%s', sn='%s'\n",
+	       drv->mp_port_id_str[port], nim_ctx->nim_id,
+	       nim_id_to_text(nim_ctx->nim_id), nim.br, nim_ctx->vendor_name,
+	       nim_ctx->prod_no, nim_ctx->serial_no);
+
+	/*
+	 * Does the driver support the NIM module type?
+	 */
+	if (nim_ctx->nim_id != valid_nim_id) {
+		NT_LOG(ERR, NTHW,
+		       "%s: The driver does not support the NIM module type %s\n",
+		       drv->mp_port_id_str[port], nim_id_to_text(nim_ctx->nim_id));
+		NT_LOG(DBG, NTHW,
+		       "%s: The driver supports the NIM module type %s\n",
+		       drv->mp_port_id_str[port], nim_id_to_text(valid_nim_id));
+		return -1;
+	}
+
+	if (enable) {
+		NT_LOG(DBG, ETHDEV, "%s: De-asserting low power\n",
+		       drv->mp_port_id_str[port]);
+		nthw_gpio_phy_set_low_power(gpio_phy, (uint8_t)port, false);
+	} else {
+		NT_LOG(DBG, ETHDEV, "%s: Asserting low power\n",
+		       drv->mp_port_id_str[port]);
+		nthw_gpio_phy_set_low_power(gpio_phy, (uint8_t)port, true);
+	}
+
+	return res;
+}
+
+/*
+ * Initialize one 100 Gbps port.
+ * The function shall not assume anything about the state of the adapter
+ * and/or port.
+ */
+static int port_init(adapter_info_t *drv, nt_fpga_t *fpga, int port)
+{
+	int adapter_id;
+	int hw_id;
+	int res;
+	nt4ga_link_t *link_info = &drv->nt4ga_link;
+
+	nthw_mac_pcs_t *mac_pcs;
+
+	assert(port >= 0 && port < NUM_ADAPTER_PORTS_MAX);
+	assert(link_info->variables_initialized);
+
+	if (fpga && fpga->p_fpga_info) {
+		adapter_id = fpga->p_fpga_info->n_nthw_adapter_id;
+		hw_id = fpga->p_fpga_info->nthw_hw_info.hw_id;
+	} else {
+		adapter_id = -1;
+		hw_id = -1;
+	}
+
+	mac_pcs = &link_info->u.var100g.mac_pcs100g[port];
+
+	/*
+	 * Phase 1. Pre-state machine (`port init` functions)
+	 * 1.1) Nt4gaAdapter::portInit()
+	 */
+
+	/* No adapter set-up here, only state variables */
+
+	/* 1.2) MyPort::init() */
+	link_info->link_info[port].link_speed = NT_LINK_SPEED_100G;
+	link_info->link_info[port].link_duplex = NT_LINK_DUPLEX_FULL;
+	link_info->link_info[port].link_auto_neg = NT_LINK_AUTONEG_OFF;
+	link_info->speed_capa |= NT_LINK_SPEED_100G;
+	nthw_mac_pcs_set_led_mode(mac_pcs, NTHW_MAC_PCS_LED_AUTO);
+	nthw_mac_pcs_set_receiver_equalization_mode(mac_pcs,
+					       nthw_mac_pcs_receiver_mode_lpm);
+
+	/*
+	 * NT200A01 build 2 HW and NT200A02 that require GTY polarity swap
+	 * if (adapter is `NT200A01 build 2 HW or NT200A02`)
+	 */
+	if (adapter_id == NT_HW_ADAPTER_ID_NT200A02 ||
+			(adapter_id == NT_HW_ADAPTER_ID_NT200A01 && hw_id == 2))
+		(void)swap_tx_rx_polarity(drv, mac_pcs, port, true);
+
+	nthw_mac_pcs_set_ts_eop(mac_pcs, true); /* end-of-frame timestamping */
+
+	/* Work in ABSOLUTE timing mode, don't set IFG mode. */
+
+	/* Phase 2. Pre-state machine (`setup` functions) */
+
+	/* 2.1) nt200a0x.cpp:Myport::setup() */
+	NT_LOG(DBG, ETHDEV, "%s: Setting up port %d\n", drv->mp_port_id_str[port],
+	       port);
+
+	NT_LOG(DBG, ETHDEV, "%s: Port %d: PHY TX enable\n",
+	       drv->mp_port_id_str[port], port);
+	enable_tx(drv, mac_pcs);
+	reset_rx(drv, mac_pcs);
+
+	/* 2.2) Nt4gaPort::setup() */
+	if (nthw_gmf_init(NULL, fpga, port) == 0) {
+		nthw_gmf_t gmf;
+
+		if (nthw_gmf_init(&gmf, fpga, port) == 0)
+			nthw_gmf_set_enable(&gmf, true);
+	}
+
+	/* Phase 3. Link state machine steps */
+
+	/* 3.1) Create NIM, ::createNim() */
+	res = create_nim(drv, fpga, port, true);
+
+	if (res) {
+		NT_LOG(WRN, ETHDEV, "%s: NIM initialization failed\n",
+		       drv->mp_port_id_str[port]);
+		return res;
+	}
+
+	NT_LOG(DBG, ETHDEV, "%s: NIM initialized\n", drv->mp_port_id_str[port]);
+
+	/* 3.2) MyPort::nimReady() */
+
+	/* 3.3) MyPort::nimReady100Gb() */
+
+	/* Setting FEC resets the lane counter in one half of the GMF */
+	nthw_mac_pcs_set_fec(mac_pcs, true);
+	NT_LOG(DBG, ETHDEV, "%s: Port %d: HOST FEC enabled\n",
+	       drv->mp_port_id_str[port], port);
+
+	if (adapter_id == NT_HW_ADAPTER_ID_NT200A01 && hw_id == 1) {
+		const uint8_t tuning_s_r4[2][4][3] = { { { 8, 15, 8 },
+				{ 8, 15, 9 },
+				{ 7, 15, 9 },
+				{ 6, 15, 8 }
+			},
+			{	{ 6, 15, 8 },
+				{ 3, 15, 12 },
+				{ 7, 15, 9 },
+				{ 7, 15, 8 }
+			}
+		};
+
+		uint8_t lane = 0;
+
+		for (lane = 0; lane < 4; lane++) {
+			uint8_t pre, diff, post;
+
+			/* Use short-range tuning values */
+			pre = tuning_s_r4[port][lane][0];
+			diff = tuning_s_r4[port][lane][1];
+			post = tuning_s_r4[port][lane][2];
+
+			nthw_mac_pcs_set_gty_tx_tuning(mac_pcs, lane, pre, diff,
+						  post);
+		}
+	} else if ((adapter_id == NT_HW_ADAPTER_ID_NT200A02) ||
+			((adapter_id == NT_HW_ADAPTER_ID_NT200A01) &&
+			 (hw_id == 2))) {
+		const uint8_t pre = 5;
+		const uint8_t diff = 25;
+		const uint8_t post = 12;
+
+		uint8_t lane = 0;
+
+		for (lane = 0; lane < 4; lane++) {
+			nthw_mac_pcs_set_gty_tx_tuning(mac_pcs, lane, pre, diff,
+						  post);
+		}
+	} else {
+		NT_LOG(ERR, ETHDEV,
+		       "%s: Unhandled AdapterId/HwId: %02x_hwid%d\n", __func__,
+		       adapter_id, hw_id);
+		assert(0);
+	}
+	reset_rx(drv, mac_pcs);
+
+	/*
+	 * 3.4) MyPort::setLinkState()
+	 *
+	 * Compensation = 1640 - dly
+	 * CMAC-core dly 188 ns
+	 * FEC no correction 87 ns
+	 * FEC active correction 211
+	 */
+	if (nthw_mac_pcs_get_fec_valid(mac_pcs))
+		nthw_mac_pcs_set_timestamp_comp_rx(mac_pcs, (1640 - 188 - 211));
+
+	else
+		nthw_mac_pcs_set_timestamp_comp_rx(mac_pcs, (1640 - 188 - 87));
+
+	/* 3.5) uint32_t MyPort::macConfig(nt_link_state_t link_state) */
+	enable_rx(drv, mac_pcs);
+
+	nthw_mac_pcs_set_host_loopback(mac_pcs, false);
+
+	return res;
+}
+
+/*
+ * State machine shared between kernel and userland
+ */
+static int common_ptp_nim_state_machine(void *data)
+{
+	adapter_info_t *drv = (adapter_info_t *)data;
+	fpga_info_t *fpga_info = &drv->fpga_info;
+	nt4ga_link_t *link_info = &drv->nt4ga_link;
+	nt_fpga_t *fpga = fpga_info->mp_fpga;
+	const int adapter_no = drv->adapter_no;
+	const int nb_ports = fpga_info->n_phy_ports;
+	uint32_t last_lpbk_mode[NUM_ADAPTER_PORTS_MAX];
+
+	nim_i2c_ctx_t *nim_ctx;
+	link_state_t *link_state;
+	nthw_mac_pcs_t *mac_pcs;
+	nthw_gpio_phy_t *gpio_phy;
+
+	if (!fpga) {
+		NT_LOG(ERR, ETHDEV, "%s: fpga is NULL\n", drv->mp_adapter_id_str);
+		goto NT4GA_LINK_100G_MON_EXIT;
+	}
+
+	assert(adapter_no >= 0 && adapter_no < NUM_ADAPTER_MAX);
+	nim_ctx = link_info->u.var100g.nim_ctx;
+	link_state = link_info->link_state;
+	mac_pcs = link_info->u.var100g.mac_pcs100g;
+	gpio_phy = link_info->u.var100g.gpio_phy;
+
+	monitor_task_is_running[adapter_no] = 1;
+	memset(last_lpbk_mode, 0, sizeof(last_lpbk_mode));
+
+	if (monitor_task_is_running[adapter_no]) {
+		NT_LOG(DBG, ETHDEV, "%s: link state machine running...\n",
+		       drv->mp_adapter_id_str);
+	}
+
+	while (monitor_task_is_running[adapter_no]) {
+		int i;
+		static bool reported_link[NUM_ADAPTER_PORTS_MAX] = { false };
+
+		/* Read sensors */
+		if (drv->adapter_sensors != NULL) {
+			nthw_spis_t *t_spi =
+				new_sensors_t_spi(drv->fpga_info.mp_fpga);
+			if (t_spi) {
+				for (struct nt_sensor_group *ptr =
+							drv->adapter_sensors;
+						ptr != NULL; ptr = ptr->next)
+					ptr->read(ptr, t_spi);
+				nthw_spis_delete(t_spi);
+			}
+		}
+
+		for (i = 0; i < nb_ports; i++) {
+			link_state_t new_link_state;
+			const bool is_port_disabled =
+				link_info->port_action[i].port_disable;
+			const bool was_port_disabled =
+				link_state[i].link_disabled;
+			const bool disable_port = is_port_disabled &&
+						  !was_port_disabled;
+			const bool enable_port = !is_port_disabled &&
+						 was_port_disabled;
+
+			if (!monitor_task_is_running[adapter_no])   /* stop quickly */
+				break;
+
+			/* Reading NIM sensors */
+			if (drv->nim_sensors[i] != NULL) {
+				nthw_spis_t *t_spi = new_sensors_t_spi(drv->fpga_info.mp_fpga);
+				if (t_spi) {
+					for (struct nim_sensor_group *ptr =
+								drv->nim_sensors[i];
+							ptr != NULL; ptr = ptr->next)
+						ptr->read(ptr, t_spi);
+					nthw_spis_delete(t_spi);
+				}
+			}
+
+			/* Has the administrative port state changed? */
+			assert(!(disable_port && enable_port));
+			if (disable_port) {
+				memset(&link_state[i], 0,
+				       sizeof(link_state[i]));
+				link_state[i].link_disabled = true;
+				reported_link[i] = false;
+				/* Turn off laser and LED, etc. */
+				(void)create_nim(drv, fpga, i, false);
+				NT_LOG(DBG, ETHDEV, "%s: Port %i is disabled\n",
+				       drv->mp_port_id_str[i], i);
+				continue;
+			}
+
+			if (enable_port) {
+				link_state[i].link_disabled = false;
+				NT_LOG(DBG, ETHDEV, "%s: Port %i is enabled\n",
+				       drv->mp_port_id_str[i], i);
+			}
+
+			if (is_port_disabled)
+				continue;
+
+			if (link_info->port_action[i].port_lpbk_mode !=
+					last_lpbk_mode[i]) {
+				/* Loopback mode has changed. Do something */
+				if (!nim_is_present(&gpio_phy[i],
+						     (uint8_t)i)) {
+					/*
+					 * If there is no Nim present, we need to initialize the
+					 * port anyway
+					 */
+					port_init(drv, fpga, i);
+				}
+				NT_LOG(INF, ETHDEV,
+				       "%s: Loopback mode changed=%u\n",
+				       drv->mp_port_id_str[i],
+				       link_info->port_action[i].port_lpbk_mode);
+				set_loopback(drv, &mac_pcs[i], i,
+					     link_info->port_action[i].port_lpbk_mode,
+					     last_lpbk_mode[i]);
+				if (link_info->port_action[i].port_lpbk_mode ==
+						1)
+					link_state[i].link_up = true;
+				last_lpbk_mode[i] =
+					link_info->port_action[i].port_lpbk_mode;
+				continue;
+			}
+
+			(void)link_state_build(drv, &mac_pcs[i], &gpio_phy[i],
+						i, &new_link_state,
+						is_port_disabled);
+			if (!new_link_state.nim_present) {
+				if (link_state[i].nim_present) {
+					NT_LOG(INF, ETHDEV,
+					       "%s: NIM module removed\n",
+					       drv->mp_port_id_str[i]);
+				}
+				link_state[i] = new_link_state;
+				continue;
+			}
+
+			/* NIM module is present */
+			if (new_link_state.lh_nim_absent ||
+					!link_state[i].nim_present) {
+				sfp_nim_state_t new_state;
+
+				NT_LOG(DBG, ETHDEV, "%s: NIM module inserted\n",
+				       drv->mp_port_id_str[i]);
+
+				if (port_init(drv, fpga, i)) {
+					NT_LOG(ERR, ETHDEV,
+					       "%s: Failed to initialize NIM module\n",
+					       drv->mp_port_id_str[i]);
+					continue;
+				}
+				if (nim_state_build(&nim_ctx[i], &new_state)) {
+					NT_LOG(ERR, ETHDEV,
+					       "%s: Cannot read basic NIM data\n",
+					       drv->mp_port_id_str[i]);
+					continue;
+				}
+				assert(new_state.br); /* Cannot be zero if NIM is present */
+				NT_LOG(DBG, ETHDEV,
+				       "%s: NIM id = %u (%s), br = %u, vendor = '%s', pn = '%s', sn='%s'\n",
+				       drv->mp_port_id_str[i], nim_ctx->nim_id,
+				       nim_id_to_text(nim_ctx->nim_id),
+				       (unsigned int)new_state.br,
+				       nim_ctx->vendor_name, nim_ctx->prod_no,
+				       nim_ctx->serial_no);
+
+				(void)link_state_build(drv, &mac_pcs[i],
+							&gpio_phy[i], i,
+							&link_state[i],
+							is_port_disabled);
+
+				NT_LOG(DBG, ETHDEV,
+				       "%s: NIM module initialized\n",
+				       drv->mp_port_id_str[i]);
+				continue;
+			}
+			if (reported_link[i] != new_link_state.link_up) {
+				NT_LOG(INF, ETHDEV, "%s: link is %s\n",
+				       drv->mp_port_id_str[i],
+				       (new_link_state.link_up ? "up" :
+					"down"));
+				link_state[i].link_up = new_link_state.link_up;
+				reported_link[i] = new_link_state.link_up;
+			}
+			check_link_state(drv, &mac_pcs[i]);
+		} /* end-for */
+		if (monitor_task_is_running[adapter_no])
+			NT_OS_WAIT_USEC(5 * 100000U); /* 5 x 0.1s = 0.5s */
+	}
+
+NT4GA_LINK_100G_MON_EXIT:
+
+	NT_LOG(DBG, ETHDEV,
+	       "%s: Stopped NT4GA 100 Gbps link monitoring thread.\n",
+	       drv->mp_adapter_id_str);
+
+	return 0;
+}
+
+/*
+ * Userland NIM state machine
+ */
+static void *nt4ga_link_100g_mon(void *data)
+{
+	(void)common_ptp_nim_state_machine(data);
+
+	return NULL;
+}
+
+/*
+ * Initialize all ports
+ * The driver calls this function during initialization (of the driver).
+ */
+int nt4ga_link_100g_ports_init(struct adapter_info_s *p_adapter_info,
+			       nt_fpga_t *fpga)
+{
+	fpga_info_t *fpga_info = &p_adapter_info->fpga_info;
+	const int adapter_no = p_adapter_info->adapter_no;
+	const int nb_ports = fpga_info->n_phy_ports;
+	int res = 0;
+
+	NT_LOG(DBG, ETHDEV, "%s: Initializing ports\n",
+	       p_adapter_info->mp_adapter_id_str);
+
+	/*
+	 * Initialize global variables
+	 */
+	assert(adapter_no >= 0 && adapter_no < NUM_ADAPTER_MAX);
+
+	if (res == 0 && !p_adapter_info->nt4ga_link.variables_initialized) {
+		nthw_mac_pcs_t *mac_pcs =
+			p_adapter_info->nt4ga_link.u.var100g.mac_pcs100g;
+		nim_i2c_ctx_t *nim_ctx =
+			p_adapter_info->nt4ga_link.u.var100g.nim_ctx;
+		nthw_gpio_phy_t *gpio_phy =
+			p_adapter_info->nt4ga_link.u.var100g.gpio_phy;
+		int i;
+
+		for (i = 0; i < nb_ports; i++) {
+			const uint8_t instance =
+				(uint8_t)(2U + i); /* 2 + adapter port number */
+			res = nthw_mac_pcs_init(&mac_pcs[i], fpga,
+					      i /* int nInstance */);
+			if (res != 0)
+				break;
+			res = nthw_iic_init(&nim_ctx[i].hwiic, fpga, instance,
+					   8 /* timing */);
+			if (res != 0)
+				break;
+			nim_ctx[i].instance = instance;
+			nim_ctx[i].devaddr = 0x50; /* 0xA0 / 2 */
+			nim_ctx[i].regaddr = 0U;
+			res = nthw_gpio_phy_init(&gpio_phy[i], fpga,
+					       0 /* Only one instance */);
+			if (res != 0)
+				break;
+		}
+		if (res == 0)
+			p_adapter_info->nt4ga_link.variables_initialized = true;
+	}
+
+	/* Create state-machine thread */
+	if (res == 0) {
+		if (!monitor_task_is_running[adapter_no]) {
+			res = pthread_create(&monitor_tasks[adapter_no], NULL,
+					     nt4ga_link_100g_mon, p_adapter_info);
+		}
+	}
+	return res;
+}
diff --git a/drivers/net/ntnic/adapter/nt4ga_link_100g.h b/drivers/net/ntnic/adapter/nt4ga_link_100g.h
new file mode 100644
index 0000000000..803b3454b7
--- /dev/null
+++ b/drivers/net/ntnic/adapter/nt4ga_link_100g.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NT4GA_LINK_100G_H_
+#define NT4GA_LINK_100G_H_
+
+#include "nthw_drv.h"
+
+int nt4ga_link_100g_ports_init(adapter_info_t *p_adapter_info, nt_fpga_t *p_fpga);
+
+#endif /* NT4GA_LINK_100G_H_ */
diff --git a/drivers/net/ntnic/adapter/nt4ga_pci_ta_tg.c b/drivers/net/ntnic/adapter/nt4ga_pci_ta_tg.c
new file mode 100644
index 0000000000..07884e9219
--- /dev/null
+++ b/drivers/net/ntnic/adapter/nt4ga_pci_ta_tg.c
@@ -0,0 +1,598 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "ntlog.h"
+#include "nt_util.h"
+#include "nthw_drv.h"
+#include "nt4ga_adapter.h"
+#include "nt4ga_pci_ta_tg.h"
+#include "nthw_pci_ta.h"
+#include "nthw_pci_rd_tg.h"
+#include "nthw_pci_wr_tg.h"
+
+int nt4ga_pci_ta_tg_init(struct adapter_info_s *p_adapter_info)
+{
+	const char *const p_adapter_id_str = p_adapter_info->mp_adapter_id_str;
+	fpga_info_t *fpga_info = &p_adapter_info->fpga_info;
+	nt_fpga_t *p_fpga = fpga_info->mp_fpga;
+	nt4ga_pci_ta_tg_t *p = &p_adapter_info->nt4ga_pci_ta_tg;
+	int res;
+	int n_err_cnt = 0;
+
+	if (p) {
+		memset(p, 0, sizeof(nt4ga_pci_ta_tg_t));
+	} else {
+		NT_LOG(ERR, NTHW, "%s: %s: null ptr\n", p_adapter_id_str, __func__);
+		return -1;
+	}
+
+	assert(p_fpga);
+
+	p->mp_nthw_pci_rd_tg = nthw_pci_rd_tg_new();
+	assert(p->mp_nthw_pci_rd_tg);
+	res = nthw_pci_rd_tg_init(p->mp_nthw_pci_rd_tg, p_fpga, 0);
+	if (res) {
+		n_err_cnt++;
+		NT_LOG(WRN, NTHW, "%s: module PCI_RD_TG not found\n",
+		       p_adapter_id_str);
+	}
+
+	p->mp_nthw_pci_wr_tg = nthw_pci_wr_tg_new();
+	assert(p->mp_nthw_pci_wr_tg);
+	res = nthw_pci_wr_tg_init(p->mp_nthw_pci_wr_tg, p_fpga, 0);
+	if (res) {
+		n_err_cnt++;
+		NT_LOG(WRN, NTHW, "%s: module PCI_WR_TG not found\n",
+		       p_adapter_id_str);
+	}
+
+	p->mp_nthw_pci_ta = nthw_pci_ta_new();
+	assert(p->mp_nthw_pci_ta);
+	res = nthw_pci_ta_init(p->mp_nthw_pci_ta, p_fpga, 0);
+	if (res) {
+		n_err_cnt++;
+		NT_LOG(WRN, NTHW, "%s: module PCI_TA not found\n",
+		       p_adapter_id_str);
+	}
+
+	return n_err_cnt;
+}
+
+static int nt4ga_pci_ta_tg_ta_write_control_enable(nt4ga_pci_ta_tg_t *p,
+		uint32_t enable)
+{
+	nthw_pci_ta_set_control_enable(p->mp_nthw_pci_ta, enable);
+	return 0;
+}
+
+static int nt4ga_pci_ta_tg_ta_read_length_error(nt4ga_pci_ta_tg_t *p, uint32_t *p_data)
+{
+	nthw_pci_ta_get_length_error(p->mp_nthw_pci_ta, p_data);
+	return 0;
+}
+
+static int nt4ga_pci_ta_tg_ta_read_packet_bad(nt4ga_pci_ta_tg_t *p, uint32_t *p_data)
+{
+	nthw_pci_ta_get_packet_bad(p->mp_nthw_pci_ta, p_data);
+	return 0;
+}
+
+static int nt4ga_pci_ta_tg_ta_read_packet_good(nt4ga_pci_ta_tg_t *p, uint32_t *p_data)
+{
+	nthw_pci_ta_get_packet_good(p->mp_nthw_pci_ta, p_data);
+	return 0;
+}
+
+static int nt4ga_pci_ta_tg_ta_read_payload_error(nt4ga_pci_ta_tg_t *p,
+		uint32_t *p_data)
+{
+	nthw_pci_ta_get_payload_error(p->mp_nthw_pci_ta, p_data);
+	return 0;
+}
+
+static int nt4ga_pci_ta_tg_rd_tg_setup(nt4ga_pci_ta_tg_t *p, uint64_t iova,
+				    int slot_addr, uint32_t req_size, bool wait,
+				    bool wrap)
+{
+	const uint64_t n_phys_addr = (iova + (unsigned long)(slot_addr * req_size));
+
+	nthw_pci_rd_tg_set_ram_addr(p->mp_nthw_pci_rd_tg, slot_addr);
+	nthw_pci_rd_tg_set_phys_addr(p->mp_nthw_pci_rd_tg, n_phys_addr);
+	nthw_pci_rd_tg_set_ram_data(p->mp_nthw_pci_rd_tg, req_size, wait, wrap);
+	return 0;
+}
+
+static int nt4ga_pci_ta_tg_rd_tg_run(nt4ga_pci_ta_tg_t *p, uint32_t num_iterations)
+{
+	nthw_pci_rd_tg_set_run(p->mp_nthw_pci_rd_tg, num_iterations);
+	return 0;
+}
+
+static int nt4ga_pci_ta_tg_rd_tg_wait_ready(nt4ga_pci_ta_tg_t *p)
+{
+	int poll = 0;
+	uint32_t data = 0;
+
+	while (data == 0) {
+		/* NOTE: Deliberately start with a sleep - ensures that the FPGA pipe is empty */
+		NT_OS_WAIT_USEC(1000);
+		data = nthw_pci_rd_tg_get_ctrl_rdy(p->mp_nthw_pci_rd_tg);
+		poll++;
+		if (poll >= 1000) {
+			NT_LOG(ERR, NTHW,
+			       "%s: FAILED waiting PCI RD TG ready: poll=%d\n",
+			       __func__, poll);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static int nt4ga_pci_ta_tg_wr_tg_setup(nt4ga_pci_ta_tg_t *p, uint64_t iova,
+				    int slot_addr, uint32_t req_size, bool wait,
+				    bool wrap, bool inc)
+{
+	const uint64_t n_phys_addr = (iova + (unsigned long)(slot_addr * req_size));
+
+	nthw_pci_wr_tg_set_ram_addr(p->mp_nthw_pci_wr_tg, slot_addr);
+	nthw_pci_wr_tg_set_phys_addr(p->mp_nthw_pci_wr_tg, n_phys_addr);
+	nthw_pci_wr_tg_set_ram_data(p->mp_nthw_pci_wr_tg, req_size, wait, wrap, inc);
+
+	return 0;
+}
+
+static int nt4ga_pci_ta_tg_wr_tg_run(nt4ga_pci_ta_tg_t *p, uint32_t num_iterations)
+{
+	nthw_pci_wr_tg_set_run(p->mp_nthw_pci_wr_tg, num_iterations);
+	return 0;
+}
+
+static int nt4ga_pci_ta_tg_wr_tg_wait_ready(nt4ga_pci_ta_tg_t *p)
+{
+	int poll = 0;
+	uint32_t data = 0;
+
+	while (data == 0) {
+		/* NOTE: Deliberately start with a sleep - ensures that the FPGA pipe is empty */
+		NT_OS_WAIT_USEC(1000);
+		data = nthw_pci_wr_tg_get_ctrl_rdy(p->mp_nthw_pci_wr_tg);
+		poll++;
+		if (poll >= 1000) {
+			NT_LOG(ERR, NTHW,
+			       "%s: FAILED waiting PCI WR TG ready: poll=%d\n",
+			       __func__, poll);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+int nt4ga_pci_ta_tg_measure_throughput_run(struct adapter_info_s *p_adapter_info,
+				      struct nthw_hif_end_point_counters *pri,
+				      struct nthw_hif_end_point_counters *sla)
+{
+	nt4ga_pci_ta_tg_t *p = &p_adapter_info->nt4ga_pci_ta_tg;
+
+	const int delay = pri->n_tg_delay;
+	const int pkt_size = pri->n_tg_pkt_size;
+	const int num_pkts = pri->n_tg_num_pkts;
+	const int n_direction = pri->n_tg_direction;
+	const uint8_t n_numa_node = (uint8_t)pri->n_numa_node;
+	const int dma_buf_size = (4 * 1024 * 1024);
+
+	const size_t align_size = ALIGN_SIZE(dma_buf_size);
+	uint32_t *mem_addr;
+	uint64_t iova;
+
+	int bo_error = 0;
+
+	nthw_hif *p_master_instance = p_adapter_info->fpga_info.mp_nthw_hif;
+	nthw_hif *p_slave_instance = NULL;
+
+	nthw_pcie3 *p_pci_master = p_adapter_info->fpga_info.mp_nthw_pcie3;
+	nthw_pcie3 *p_pci_slave = NULL;
+
+	assert(p_master_instance || p_pci_master);
+
+	struct nt_dma_s *p_dma;
+	/* FPGA needs a Page alignment (4K on Intel) */
+	p_dma = nt_dma_alloc(align_size, 0x1000, n_numa_node);
+	if (p_dma == NULL) {
+		NT_LOG(DBG, ETHDEV, "%s: vfio_dma_alloc failed\n", __func__);
+		return 0;
+	}
+	mem_addr = (uint32_t *)p_dma->addr;
+	iova = p_dma->iova;
+
+	NT_LOG(DBG, NTHW,
+	       "%s: Running HIF bandwidth measurements on NUMA node %d\n",
+	       __func__, n_numa_node);
+
+	bo_error = 0;
+	{
+		int wrap;
+
+		/* Stop any existing running test */
+		bo_error |= nt4ga_pci_ta_tg_rd_tg_run(p, 0);
+		bo_error |= nt4ga_pci_ta_tg_rd_tg_wait_ready(p);
+
+		bo_error |= nt4ga_pci_ta_tg_wr_tg_run(p, 0);
+		bo_error |= nt4ga_pci_ta_tg_wr_tg_wait_ready(p);
+
+		bo_error |= nt4ga_pci_ta_tg_ta_write_control_enable(p, 0);
+
+		/* Prepare the HIF Traffic generator */
+		bo_error |= nt4ga_pci_ta_tg_ta_write_control_enable(p, 1);
+		bo_error |= nt4ga_pci_ta_tg_rd_tg_wait_ready(p);
+		bo_error |= nt4ga_pci_ta_tg_wr_tg_wait_ready(p);
+
+		/*
+		 * Ensure that the hostbuffer memory contain data that can be read -
+		 * For this we will ask the FPGA to write data to it. The last wrap packet
+		 * does not generate any data it only wraps (unlike the PCIe2 TG)
+		 */
+		{
+			int pkt;
+
+			for (pkt = 0; pkt < num_pkts; pkt++) {
+				if (pkt >= (num_pkts - 1))
+					wrap = 1;
+
+				else
+					wrap = 0;
+				bo_error |= nt4ga_pci_ta_tg_wr_tg_setup(p, iova,
+									pkt, pkt_size,
+									0, wrap, 1);
+				bo_error |= nt4ga_pci_ta_tg_rd_tg_setup(p, iova,
+									pkt, pkt_size,
+									0, wrap);
+			}
+		}
+
+		bo_error |= nt4ga_pci_ta_tg_wr_tg_run(p, 1);
+		bo_error |= nt4ga_pci_ta_tg_wr_tg_wait_ready(p);
+
+		/* Start WR TG Write once */
+		bo_error |= nt4ga_pci_ta_tg_wr_tg_run(p, 1);
+		/* Wait until WR TG ready */
+		bo_error |= nt4ga_pci_ta_tg_wr_tg_wait_ready(p);
+
+		/* Verify that we have a packet */
+		{
+			int pkt;
+
+			for (pkt = 0; pkt < num_pkts; pkt++) {
+				uint32_t value = 0;
+				int poll;
+
+				for (poll = 8; poll < pkt_size;
+						poll += 4, value++) {
+					if (*(uint32_t *)((uint8_t *)mem_addr +
+							  (pkt * pkt_size) +
+							  poll) != value) {
+						NT_LOG(ERR, NTHW,
+						       "HIF TG: Prepare failed. Data write failed: #%d.%d:  %016X:%08X\n",
+						       pkt, poll,
+						       *(uint32_t *)((uint8_t *)
+								     mem_addr +
+								     (pkt *
+								      pkt_size) +
+								     poll),
+						       value);
+
+						/*
+						 * Break out of the verification loop on first
+						 * Compare error
+						 */
+						bo_error |= 1;
+						break;
+					}
+				}
+			}
+		}
+
+		switch (n_direction) {
+		case 1: /* Read only test */
+			nt4ga_pci_ta_tg_wr_tg_run(p, 0xffff);
+			break;
+		case 2: /* Write only test */
+			nt4ga_pci_ta_tg_rd_tg_run(p, 0xffff);
+			break;
+		case 3: /* Combined read/write test */
+			nt4ga_pci_ta_tg_wr_tg_run(p, 0xffff);
+			nt4ga_pci_ta_tg_rd_tg_run(p, 0xffff);
+			break;
+		default: /* stop tests */
+			nt4ga_pci_ta_tg_wr_tg_run(p, 0);
+			nt4ga_pci_ta_tg_rd_tg_run(p, 0);
+			break;
+		}
+
+		do {
+			/* prep */
+			if (p_pci_master) {
+				nthw_pcie3_end_point_counters_sample_pre(p_pci_master,
+								    pri);
+			}
+			if (p_pci_slave) {
+				nthw_pcie3_end_point_counters_sample_pre(p_pci_slave,
+								    sla);
+			}
+
+			/* start measure */
+			if (p_master_instance)
+				nthw_hif_stat_req_enable(p_master_instance);
+			if (p_pci_master)
+				nthw_pcie3_stat_req_enable(p_pci_master);
+
+			if (p_slave_instance)
+				nthw_hif_stat_req_enable(p_slave_instance);
+			if (p_pci_slave)
+				nthw_pcie3_stat_req_enable(p_pci_slave);
+
+			/* Wait */
+			NT_OS_WAIT_USEC(delay);
+
+			/* Stop measure */
+			if (p_master_instance)
+				nthw_hif_stat_req_disable(p_master_instance);
+			if (p_pci_master)
+				nthw_pcie3_stat_req_disable(p_pci_master);
+
+			if (p_slave_instance)
+				nthw_hif_stat_req_disable(p_slave_instance);
+			if (p_pci_slave)
+				nthw_pcie3_stat_req_disable(p_pci_slave);
+
+			/* Post process master */
+			if (p_master_instance) {
+				nthw_hif_end_point_counters_sample(p_master_instance,
+							       pri);
+			}
+
+			if (p_pci_master) {
+				nthw_pcie3_end_point_counters_sample_post(p_pci_master,
+								     pri);
+			}
+
+			/* Post process slave */
+			if (p_slave_instance) {
+				nthw_hif_end_point_counters_sample(p_slave_instance,
+							       sla);
+			}
+
+			if (p_pci_slave) {
+				nthw_pcie3_end_point_counters_sample_post(p_pci_slave,
+								     sla);
+			}
+
+			{
+				/* Check for TA transmit errors */
+				uint32_t dw_good_pkts, dw_bad_pkts, dw_bad_length,
+					 dw_bad_payload;
+				nt4ga_pci_ta_tg_ta_read_packet_good(p,
+								 &dw_good_pkts);
+				nt4ga_pci_ta_tg_ta_read_packet_bad(p, &dw_bad_pkts);
+				nt4ga_pci_ta_tg_ta_read_length_error(p,
+								  &dw_bad_length);
+				nt4ga_pci_ta_tg_ta_read_payload_error(p, &dw_bad_payload);
+
+				NT_LOG(DBG, NTHW,
+				       "%s: NUMA node %u: HIF: TA: Good pkts, Bad pkts, Bad length, Bad payload\n",
+				       __func__, n_numa_node);
+				NT_LOG(DBG, NTHW,
+				       "%s: NUMA node %u: HIF: TA: 0x%08x 0x%08x 0x%08x 0x%08x\n",
+				       __func__, n_numa_node, dw_good_pkts,
+				       dw_bad_pkts, dw_bad_length, dw_bad_payload);
+
+				if (dw_bad_pkts | dw_bad_length | dw_bad_payload) {
+					bo_error |= 1;
+					NT_LOG(ERR, NTHW,
+					       "%s: NUMA node %u: HIF: TA: error detected\n",
+					       __func__, n_numa_node);
+					NT_LOG(ERR, NTHW,
+					       "%s: NUMA node %u: HIF: TA: Good packets received: %u\n",
+					       __func__, n_numa_node, dw_good_pkts);
+					NT_LOG(ERR, NTHW,
+					       "%s: NUMA node %u: HIF: TA: Bad packets received : %u\n",
+					       __func__, n_numa_node, dw_bad_pkts);
+					NT_LOG(ERR, NTHW,
+					       "%s: NUMA node %u: HIF: TA: Bad length received  : %u\n",
+					       __func__, n_numa_node,
+					       dw_bad_length);
+					NT_LOG(ERR, NTHW,
+					       "%s: NUMA node %u: HIF: TA: Bad payload received : %u\n",
+					       __func__, n_numa_node,
+					       dw_bad_payload);
+				}
+			}
+
+			if (bo_error != 0)
+				break;
+
+			break; /* for now only loop once */
+
+			/*
+			 * Only do "signalstop" looping if a specific numa node and direction is to
+			 * be tested.
+			 */
+		} while ((bo_error == 0) && (n_numa_node != UINT8_MAX) &&
+				(n_direction != -1));
+
+		/* Stop the test */
+		bo_error |= nt4ga_pci_ta_tg_wr_tg_run(p, 0);
+		bo_error |= nt4ga_pci_ta_tg_wr_tg_wait_ready(p);
+
+		bo_error |= nt4ga_pci_ta_tg_rd_tg_run(p, 0);
+		bo_error |= nt4ga_pci_ta_tg_rd_tg_wait_ready(p);
+
+		bo_error |= nt4ga_pci_ta_tg_ta_write_control_enable(p, 0);
+
+		/* PCIe3 sanity checks */
+		{
+#if defined(DEBUG)
+			int do_loop = 1;
+#else
+			int do_loop = 0;
+#endif
+
+			while (do_loop) {
+				do_loop = 0;
+
+				if (p_master_instance) {
+					nthw_hif_stat_req_enable(p_master_instance);
+					NT_OS_WAIT_USEC(100);
+					nthw_hif_stat_req_disable(p_master_instance);
+				}
+
+				if (do_loop == 0)
+					break;
+
+				NT_LOG(DBG, NTHW,
+				       "%s: WARNING this is wrong - wait again\n",
+				       __func__);
+				NT_OS_WAIT_USEC(200 * 1000);
+			}
+		}
+	}
+
+	/* Stop the test */
+
+	bo_error |= nt4ga_pci_ta_tg_wr_tg_run(p, 0);
+	bo_error |= nt4ga_pci_ta_tg_wr_tg_wait_ready(p);
+
+	bo_error |= nt4ga_pci_ta_tg_rd_tg_run(p, 0);
+	bo_error |= nt4ga_pci_ta_tg_rd_tg_wait_ready(p);
+
+	bo_error |= nt4ga_pci_ta_tg_ta_write_control_enable(p, 0);
+
+	nt_dma_free(p_dma);
+
+	return bo_error;
+}
+
+int nt4ga_pci_ta_tg_measure_throughput_main(struct adapter_info_s *p_adapter_info,
+				       const uint8_t numa_node,
+				       const int direction, const int n_pkt_size,
+				       const int n_batch_count, const int n_delay)
+{
+	/* All numa nodes is indicated by UINT8_MAX */
+	const uint8_t numa_begin = (numa_node == UINT8_MAX ? 0 : numa_node);
+	const uint8_t numa_end = numa_begin;
+
+	/* sanity check direction param */
+	const int dir_begin = (direction <= 0 ? 1 : direction);
+	const int dir_end = (direction <= 0 ? 3 : direction);
+
+	int bo_error = 0;
+	struct nthw_hif_end_points eps;
+
+	if (n_delay == 0)
+		return -1;
+
+	NT_LOG(DBG, NTHW, "HIF adapter throughput:\n");
+
+	/* Only do "signalstop"-looping if a specific numa node is to be tested. */
+	{
+		uint8_t numa;
+
+		for (numa = numa_begin; numa <= numa_end; numa++) {
+			{
+				int by_loop;
+
+				for (by_loop = dir_begin; by_loop <= dir_end;
+						by_loop++) {
+					struct nthw_hif_end_point_counters *pri =
+							&eps.pri;
+					struct nthw_hif_end_point_counters *sla =
+							&eps.sla;
+
+					pri->n_numa_node = numa;
+					pri->n_tg_direction = by_loop;
+					pri->n_tg_pkt_size = (n_pkt_size > 0 ?
+							   n_pkt_size :
+							   TG_PKT_SIZE);
+					pri->n_tg_num_pkts =
+						(n_batch_count > 0 ?
+						 n_batch_count :
+						 TG_NUM_PACKETS);
+					pri->n_tg_delay = (n_delay > 0 ? n_delay :
+							 TG_DELAY);
+					pri->cur_rx = 0;
+					pri->cur_tx = 0;
+					pri->n_ref_clk_cnt = -1;
+					pri->bo_error = 0;
+
+					sla->n_numa_node = numa;
+					sla->n_tg_direction = by_loop;
+					sla->n_tg_pkt_size = (n_pkt_size > 0 ?
+							   n_pkt_size :
+							   TG_PKT_SIZE);
+					sla->n_tg_num_pkts =
+						(n_batch_count > 0 ?
+						 n_batch_count :
+						 TG_NUM_PACKETS);
+					sla->n_tg_delay = (n_delay > 0 ? n_delay :
+							 TG_DELAY);
+					sla->cur_rx = 0;
+					sla->cur_tx = 0;
+					pri->n_ref_clk_cnt = -1;
+					sla->bo_error = 0;
+
+					bo_error +=
+					nt4ga_pci_ta_tg_measure_throughput_run(p_adapter_info,
+									       pri, sla);
+#if defined(DEBUG) && (1)
+					{
+						NT_LOG(DBG, NTHW,
+						       "%s: @ %d: %d %d %d %d: %016lX %016lX : %6ld Mbps %6ld Mbps\n",
+						       __func__, pri->n_numa_node,
+						       pri->n_tg_direction,
+						       pri->n_tg_num_pkts,
+						       pri->n_tg_pkt_size,
+						       pri->n_tg_delay,
+						       pri->cur_rx, pri->cur_tx,
+						       (pri->cur_rx * 8UL /
+							1000000UL),
+						       (pri->cur_tx * 8UL /
+							1000000UL));
+					}
+					{
+						NT_LOG(DBG, NTHW,
+						       "%s: @ %d: %d %d %d %d: %016lX %016lX : %6ld Mbps %6ld Mbps\n",
+						       __func__, sla->n_numa_node,
+						       sla->n_tg_direction,
+						       sla->n_tg_num_pkts,
+						       sla->n_tg_pkt_size,
+						       sla->n_tg_delay,
+						       sla->cur_rx, sla->cur_tx,
+						       (sla->cur_rx * 8UL /
+							1000000UL),
+						       (sla->cur_tx * 8UL /
+							1000000UL));
+					}
+#endif
+
+					if (pri->bo_error != 0 || sla->bo_error != 0)
+						bo_error++;
+					if (bo_error)
+						break;
+				}
+			}
+		}
+	}
+
+	if (bo_error != 0) {
+		NT_LOG(ERR, NTHW, "%s: error during bandwidth measurement\n",
+		       __func__);
+	}
+
+	NT_LOG(DBG, NTHW, "HIF adapter throughput: done\n");
+
+	NT_LOG(DBG, NTHW, "%s: [%s:%u] done\n", __func__, __FILE__, __LINE__);
+
+	return 0;
+}
diff --git a/drivers/net/ntnic/adapter/nt4ga_pci_ta_tg.h b/drivers/net/ntnic/adapter/nt4ga_pci_ta_tg.h
new file mode 100644
index 0000000000..8b46491f77
--- /dev/null
+++ b/drivers/net/ntnic/adapter/nt4ga_pci_ta_tg.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _NT4GA_PCI_TA_TG_H_
+#define _NT4GA_PCI_TA_TG_H_
+
+#include <stdint.h>
+
+#define TA_TG_DBG_SHOW_SUMMARY (1)
+
+#define TG_NUM_PACKETS (8)
+#define TG_PKT_SIZE (2048 * 1)
+#define TG_AREA_SIZE (TG_NUM_PACKETS * TG_PKT_SIZE)
+
+#define TG_DELAY (200000) /* usec */
+
+/* Struct predefinitions */
+struct adapter_info_s;
+struct nthw_hif_end_point_counters;
+
+struct nt4ga_pci_ta_tg_s {
+	struct nthw_pci_rd_tg *mp_nthw_pci_rd_tg;
+	struct nthw_pci_wr_tg *mp_nthw_pci_wr_tg;
+	struct nthw_pci_ta *mp_nthw_pci_ta;
+};
+
+typedef struct nt4ga_pci_ta_tg_s nt4ga_pci_ta_tg_t;
+typedef struct nt4ga_pci_ta_tg_s nt4ga_pci_ta_tg;
+
+int nt4ga_pci_ta_tg_init(struct adapter_info_s *p_adapter_info);
+
+int nt4ga_pci_ta_tg_measure_throughput_run(struct adapter_info_s *p_adapter_info,
+				      struct nthw_hif_end_point_counters *pri,
+				      struct nthw_hif_end_point_counters *sla);
+int nt4ga_pci_ta_tg_measure_throughput_main(struct adapter_info_s *p_adapter_info,
+				       const uint8_t numa_node,
+				       const int direction, const int n_pkt_size,
+				       const int n_batch_count, const int n_delay);
+
+#endif /* _NT4GA_PCI_TA_TG_H_ */
diff --git a/drivers/net/ntnic/adapter/nt4ga_stat.c b/drivers/net/ntnic/adapter/nt4ga_stat.c
new file mode 100644
index 0000000000..b61c73ea12
--- /dev/null
+++ b/drivers/net/ntnic/adapter/nt4ga_stat.c
@@ -0,0 +1,705 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "ntlog.h"
+#include "nt_util.h"
+#include "nthw_drv.h"
+#include "nthw_fpga.h"
+#include "nt4ga_adapter.h"
+
+#define NO_FLAGS 0
+
+/* Inline timestamp format s pcap 32:32 bits. Convert to nsecs */
+static inline uint64_t timestamp2ns(uint64_t ts)
+{
+	return ((ts >> 32) * 1000000000) + (ts & 0xffffffff);
+}
+
+static int nt4ga_stat_collect_cap_v1_stats(nt4ga_stat_t *p_nt4ga_stat,
+				   uint32_t *p_stat_dma_virtual);
+static int nt4ga_stat_collect_virt_v1_stats(nt4ga_stat_t *p_nt4ga_stat,
+				    uint32_t *p_stat_dma_virtual);
+
+int nt4ga_stat_collect(struct adapter_info_s *p_adapter_info _unused,
+		      nt4ga_stat_t *p_nt4ga_stat)
+{
+	nthw_stat_t *p_nthw_stat = p_nt4ga_stat->mp_nthw_stat;
+
+	if (p_nthw_stat->mb_is_vswitch) {
+		/*
+		 * Set all bits in the DMA block timestamp since 9530-42-05 and other Vswitch FPGA
+		 * images may only clear all bits in this memory location. TBV
+		 * Consequently, last_timestamp must be constructed via a system call.
+		 */
+		*p_nthw_stat->mp_timestamp = 0xFFFFFFFF;
+		p_nt4ga_stat->last_timestamp = NT_OS_GET_TIME_NS();
+		nt4ga_stat_collect_virt_v1_stats(p_nt4ga_stat,
+						p_nt4ga_stat->p_stat_dma_virtual);
+	} else {
+		p_nt4ga_stat->last_timestamp =
+			timestamp2ns(*p_nthw_stat->mp_timestamp);
+		nt4ga_stat_collect_cap_v1_stats(p_nt4ga_stat,
+					       p_nt4ga_stat->p_stat_dma_virtual);
+	}
+	return 0;
+}
+
+int nt4ga_stat_init(struct adapter_info_s *p_adapter_info)
+{
+	const char *const p_adapter_id_str = p_adapter_info->mp_adapter_id_str;
+	fpga_info_t *fpga_info = &p_adapter_info->fpga_info;
+	nt_fpga_t *p_fpga = fpga_info->mp_fpga;
+	nt4ga_stat_t *p_nt4ga_stat = &p_adapter_info->nt4ga_stat;
+
+	if (p_nt4ga_stat) {
+		memset(p_nt4ga_stat, 0, sizeof(nt4ga_stat_t));
+	} else {
+		NT_LOG(ERR, ETHDEV, "%s: ERROR (%s:%d)", p_adapter_id_str,
+		       __func__, __LINE__);
+		return -1;
+	}
+
+	{
+		nthw_stat_t *p_nthw_stat = nthw_stat_new();
+		nthw_rmc_t *p_nthw_rmc = nthw_rmc_new();
+
+		if (!p_nthw_stat) {
+			NT_LOG(ERR, ETHDEV, "%s: ERROR (%s:%d)", p_adapter_id_str,
+			       __func__, __LINE__);
+			return -1;
+		}
+
+		if (!p_nthw_rmc) {
+			nthw_stat_delete(p_nthw_stat);
+
+			NT_LOG(ERR, ETHDEV, "%s: ERROR (%s:%d)", p_adapter_id_str,
+			       __func__, __LINE__);
+			return -1;
+		}
+
+		p_nt4ga_stat->mp_nthw_stat = p_nthw_stat;
+		nthw_stat_init(p_nthw_stat, p_fpga, 0);
+
+		p_nt4ga_stat->mp_nthw_rmc = p_nthw_rmc;
+		nthw_rmc_init(p_nthw_rmc, p_fpga, 0);
+
+		p_nt4ga_stat->mn_rx_host_buffers = p_nthw_stat->m_nb_rx_host_buffers;
+		p_nt4ga_stat->mn_tx_host_buffers = p_nthw_stat->m_nb_tx_host_buffers;
+
+		p_nt4ga_stat->mn_rx_ports = p_nthw_stat->m_nb_rx_ports;
+		p_nt4ga_stat->mn_tx_ports = p_nthw_stat->m_nb_tx_ports;
+	}
+
+	return 0;
+}
+
+int nt4ga_stat_setup(struct adapter_info_s *p_adapter_info)
+{
+	const int n_physical_adapter_no _unused = p_adapter_info->adapter_no;
+	nt4ga_stat_t *p_nt4ga_stat = &p_adapter_info->nt4ga_stat;
+	nthw_stat_t *p_nthw_stat = p_nt4ga_stat->mp_nthw_stat;
+	nthw_rmc_t *p_nthw_rmc = p_nt4ga_stat->mp_nthw_rmc;
+
+	if (p_nthw_rmc)
+		nthw_rmc_block(p_nthw_rmc);
+
+	/* Allocate and map memory for fpga statistics */
+	{
+		uint32_t n_stat_size =
+			(uint32_t)(p_nthw_stat->m_nb_counters * sizeof(uint32_t) +
+				   sizeof(p_nthw_stat->mp_timestamp));
+		struct nt_dma_s *p_dma;
+		int numa_node = p_adapter_info->fpga_info.numa_node;
+
+		/* FPGA needs a 16K alignment on Statistics */
+		p_dma = nt_dma_alloc(n_stat_size, 0x4000, numa_node);
+
+		if (!p_dma) {
+			NT_LOG(ERR, ETHDEV, "%s: pDma alloc failed\n",
+			       __func__);
+			return -1;
+		}
+
+		NT_LOG(DBG, ETHDEV, "%s: %x @%d %p %" PRIX64 " %" PRIX64 "\n", __func__,
+		       n_stat_size, numa_node, p_dma->addr, p_dma->iova);
+
+		NT_LOG(DBG, ETHDEV,
+		       "DMA: Physical adapter %02ld, PA = 0x%016" PRIX64
+		       " DMA = 0x%016" PRIX64 " size = 0x%" PRIX64 "\n",
+		       n_physical_adapter_no, p_dma->iova, p_dma->addr, n_stat_size);
+
+		p_nt4ga_stat->p_stat_dma_virtual = (uint32_t *)p_dma->addr;
+		p_nt4ga_stat->n_stat_size = n_stat_size;
+		p_nt4ga_stat->p_stat_dma = p_dma;
+
+		memset(p_nt4ga_stat->p_stat_dma_virtual, 0xaa, n_stat_size);
+		nthw_stat_set_dma_address(p_nthw_stat, p_dma->iova,
+				       p_nt4ga_stat->p_stat_dma_virtual);
+	}
+
+	if (p_nthw_rmc)
+		nthw_rmc_unblock(p_nthw_rmc, false);
+
+	p_nt4ga_stat->mp_stat_structs_color = calloc(p_nthw_stat->m_nb_color_counters,
+						sizeof(struct color_counters));
+	if (!p_nt4ga_stat->mp_stat_structs_color) {
+		NT_LOG(ERR, GENERAL, "Cannot allocate mem (%s:%d).\n", __func__,
+		       __LINE__);
+		return -1;
+	}
+
+	p_nt4ga_stat->mp_stat_structs_hb =
+		calloc(p_nt4ga_stat->mn_rx_host_buffers + p_nt4ga_stat->mn_tx_host_buffers,
+		       sizeof(struct host_buffer_counters));
+	if (!p_nt4ga_stat->mp_stat_structs_hb) {
+		NT_LOG(ERR, GENERAL, "Cannot allocate mem (%s:%d).\n", __func__,
+		       __LINE__);
+		return -1;
+	}
+
+	/*
+	 * Separate memory allocation for VSWITCH and Inline to appropriate port counter structures.
+	 */
+	if (p_nthw_stat->mb_is_vswitch) {
+		p_nt4ga_stat->virt.mp_stat_structs_port_rx =
+			calloc(p_nthw_stat->m_nb_rx_host_buffers,
+			       sizeof(struct port_counters_vswitch_v1));
+		if (!p_nt4ga_stat->virt.mp_stat_structs_port_rx) {
+			NT_LOG(ERR, GENERAL, "Cannot allocate mem (%s:%d).\n",
+			       __func__, __LINE__);
+			return -1;
+		}
+		p_nt4ga_stat->virt.mp_stat_structs_port_tx =
+			calloc(p_nthw_stat->m_nb_tx_host_buffers,
+			       sizeof(struct port_counters_vswitch_v1));
+		if (!p_nt4ga_stat->virt.mp_stat_structs_port_tx) {
+			NT_LOG(ERR, GENERAL, "Cannot allocate mem (%s:%d).\n",
+			       __func__, __LINE__);
+			return -1;
+		}
+		p_nt4ga_stat->flm_stat_ver = 0;
+		p_nt4ga_stat->mp_stat_structs_flm = NULL;
+	} else { /* Inline */
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx =
+			calloc(NUM_ADAPTER_PORTS_MAX,
+			       sizeof(struct port_counters_v2));
+		if (!p_nt4ga_stat->cap.mp_stat_structs_port_rx) {
+			NT_LOG(ERR, GENERAL, "Cannot allocate mem (%s:%d).\n",
+			       __func__, __LINE__);
+			return -1;
+		}
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx =
+			calloc(NUM_ADAPTER_PORTS_MAX,
+			       sizeof(struct port_counters_v2));
+		if (!p_nt4ga_stat->cap.mp_stat_structs_port_tx) {
+			NT_LOG(ERR, GENERAL, "Cannot allocate mem (%s:%d).\n",
+			       __func__, __LINE__);
+			return -1;
+		}
+
+		p_nt4ga_stat->flm_stat_ver = 0;
+
+		p_nt4ga_stat->mp_stat_structs_flm =
+			calloc(1, sizeof(struct flm_counters_v1));
+		if (!p_nt4ga_stat->mp_stat_structs_flm) {
+			NT_LOG(ERR, GENERAL, "Cannot allocate mem (%s:%d).\n",
+			       __func__, __LINE__);
+			return -1;
+		}
+	}
+
+	memset(p_nt4ga_stat->a_stat_structs_color_base, 0,
+	       sizeof(struct color_counters) * NT_MAX_COLOR_FLOW_STATS);
+	p_nt4ga_stat->last_timestamp = 0;
+
+	nthw_stat_trigger(p_nthw_stat);
+
+	return 0;
+}
+
+int nt4ga_stat_stop(struct adapter_info_s *p_adapter_info)
+{
+	nt4ga_stat_t *p_nt4ga_stat = &p_adapter_info->nt4ga_stat;
+
+	if (p_nt4ga_stat->virt.mp_stat_structs_port_rx) {
+		free(p_nt4ga_stat->virt.mp_stat_structs_port_rx);
+		p_nt4ga_stat->virt.mp_stat_structs_port_rx = NULL;
+	}
+	if (p_nt4ga_stat->cap.mp_stat_structs_port_rx) {
+		free(p_nt4ga_stat->cap.mp_stat_structs_port_rx);
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx = NULL;
+	}
+
+	if (p_nt4ga_stat->virt.mp_stat_structs_port_tx) {
+		free(p_nt4ga_stat->virt.mp_stat_structs_port_tx);
+		p_nt4ga_stat->virt.mp_stat_structs_port_tx = NULL;
+	}
+	if (p_nt4ga_stat->cap.mp_stat_structs_port_tx) {
+		free(p_nt4ga_stat->cap.mp_stat_structs_port_tx);
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx = NULL;
+	}
+
+	if (p_nt4ga_stat->mp_stat_structs_color) {
+		free(p_nt4ga_stat->mp_stat_structs_color);
+		p_nt4ga_stat->mp_stat_structs_color = NULL;
+	}
+
+	if (p_nt4ga_stat->mp_stat_structs_hb) {
+		free(p_nt4ga_stat->mp_stat_structs_hb);
+		p_nt4ga_stat->mp_stat_structs_hb = NULL;
+	}
+
+	if (p_nt4ga_stat->mp_stat_structs_flm) {
+		free(p_nt4ga_stat->mp_stat_structs_flm);
+		p_nt4ga_stat->mp_stat_structs_flm = NULL;
+	}
+
+	if (p_nt4ga_stat->p_stat_dma) {
+		nt_dma_free(p_nt4ga_stat->p_stat_dma);
+		p_nt4ga_stat->p_stat_dma = NULL;
+	}
+
+	return 0;
+}
+
+int nt4ga_stat_dump(struct adapter_info_s *p_adapter_info, FILE *pfh)
+{
+	const char *const p_adapter_id_str = p_adapter_info->mp_adapter_id_str;
+	fpga_info_t *fpga_info = &p_adapter_info->fpga_info;
+	nt4ga_stat_t *p_nt4ga_stat = &p_adapter_info->nt4ga_stat;
+	int i;
+
+	for (i = 0; i < fpga_info->n_phy_ports; i++) {
+		fprintf(pfh,
+			"%s: Intf %02d: Rx: %016" PRIX64 " %016" PRIX64
+			" %016" PRIX64 " Tx: %016" PRIX64 " %016" PRIX64
+			" %016" PRIX64 "\n",
+			p_adapter_id_str, i, p_nt4ga_stat->a_port_rx_packets_total[i],
+			p_nt4ga_stat->a_port_rx_octets_total[i],
+			p_nt4ga_stat->a_port_rx_drops_total[i],
+			p_nt4ga_stat->a_port_tx_packets_total[i],
+			p_nt4ga_stat->a_port_tx_octets_total[i],
+			p_nt4ga_stat->a_port_tx_drops_total[i]);
+	}
+
+	return 0;
+}
+
+/* Called with stat mutex locked */
+static int nt4ga_stat_collect_virt_v1_stats(nt4ga_stat_t *p_nt4ga_stat,
+				    uint32_t *p_stat_dma_virtual)
+{
+	nthw_stat_t *p_nthw_stat = p_nt4ga_stat->mp_nthw_stat;
+	const int n_rx_ports = p_nt4ga_stat->mn_rx_ports;
+	const int n_tx_ports = p_nt4ga_stat->mn_tx_ports;
+	int c, h, p;
+
+	if (!p_nthw_stat || !p_nt4ga_stat)
+		return -1;
+
+	if (p_nthw_stat->mn_stat_layout_version != 6) {
+		NT_LOG(ERR, ETHDEV, "HW STA module version not supported");
+		return -1;
+	}
+
+	/* RX ports */
+	for (c = 0; c < p_nthw_stat->m_nb_color_counters / 2; c++) {
+		const unsigned int tcp_flags_bits = 6U;
+		const uint32_t val_mask_dma = 0xffffffffULL >> tcp_flags_bits;
+
+		p_nt4ga_stat->mp_stat_structs_color[c].color_packets +=
+			p_stat_dma_virtual[c * 2] & val_mask_dma;
+		p_nt4ga_stat->mp_stat_structs_color[c].tcp_flags |=
+			(uint8_t)(p_stat_dma_virtual[c * 2] >>
+				  (32 - tcp_flags_bits));
+		p_nt4ga_stat->mp_stat_structs_color[c].color_bytes +=
+			p_stat_dma_virtual[c * 2 + 1];
+	}
+
+	/* Move to Host buffer counters */
+	p_stat_dma_virtual += p_nthw_stat->m_nb_color_counters;
+
+	/* Host buffer counters */
+	for (h = 0; h < p_nthw_stat->m_nb_rx_host_buffers; h++) {
+		p_nt4ga_stat->mp_stat_structs_hb[h].flush_packets +=
+			p_stat_dma_virtual[h * 8];
+		p_nt4ga_stat->mp_stat_structs_hb[h].drop_packets +=
+			p_stat_dma_virtual[h * 8 + 1];
+		p_nt4ga_stat->mp_stat_structs_hb[h].fwd_packets +=
+			p_stat_dma_virtual[h * 8 + 2];
+		p_nt4ga_stat->mp_stat_structs_hb[h].dbs_drop_packets +=
+			p_stat_dma_virtual[h * 8 + 3];
+		p_nt4ga_stat->mp_stat_structs_hb[h].flush_bytes +=
+			p_stat_dma_virtual[h * 8 + 4];
+		p_nt4ga_stat->mp_stat_structs_hb[h].drop_bytes +=
+			p_stat_dma_virtual[h * 8 + 5];
+		p_nt4ga_stat->mp_stat_structs_hb[h].fwd_bytes +=
+			p_stat_dma_virtual[h * 8 + 6];
+		p_nt4ga_stat->mp_stat_structs_hb[h].dbs_drop_bytes +=
+			p_stat_dma_virtual[h * 8 + 7];
+	}
+
+	/* Move to Rx Port counters */
+	p_stat_dma_virtual += p_nthw_stat->m_nb_rx_hb_counters;
+
+	/* RX ports */
+	for (p = 0; p < n_rx_ports; p++) {
+		p_nt4ga_stat->virt.mp_stat_structs_port_rx[p].octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters];
+		p_nt4ga_stat->virt.mp_stat_structs_port_rx[p].pkts +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 1];
+		p_nt4ga_stat->virt.mp_stat_structs_port_rx[p].drop_events +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 2];
+		p_nt4ga_stat->virt.mp_stat_structs_port_rx[p].qos_drop_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 3];
+		p_nt4ga_stat->virt.mp_stat_structs_port_rx[p].qos_drop_pkts +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 4];
+
+		/* Rx totals */
+		p_nt4ga_stat->a_port_rx_octets_total[p] +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters];
+		p_nt4ga_stat->a_port_rx_packets_total[p] +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 1];
+		p_nt4ga_stat->a_port_rx_drops_total[p] +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 2];
+	}
+
+	/* Move to Tx Port counters */
+	p_stat_dma_virtual += n_rx_ports * p_nthw_stat->m_nb_rx_port_counters;
+
+	/* TX ports */
+	for (p = 0; p < n_tx_ports; p++) {
+		p_nt4ga_stat->virt.mp_stat_structs_port_tx[p].octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters];
+		p_nt4ga_stat->virt.mp_stat_structs_port_tx[p].pkts +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 1];
+		p_nt4ga_stat->virt.mp_stat_structs_port_tx[p].drop_events +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 2];
+		p_nt4ga_stat->virt.mp_stat_structs_port_tx[p].qos_drop_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 3];
+		p_nt4ga_stat->virt.mp_stat_structs_port_tx[p].qos_drop_pkts +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 4];
+
+		/* Tx totals */
+		p_nt4ga_stat->a_port_tx_octets_total[p] +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters];
+		p_nt4ga_stat->a_port_tx_packets_total[p] +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 1];
+		p_nt4ga_stat->a_port_tx_drops_total[p] +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 2];
+	}
+
+	return 0;
+}
+
+/* Called with stat mutex locked */
+static int nt4ga_stat_collect_cap_v1_stats(nt4ga_stat_t *p_nt4ga_stat,
+					   uint32_t *p_stat_dma_virtual)
+{
+	nthw_stat_t *p_nthw_stat = p_nt4ga_stat->mp_nthw_stat;
+
+	const int n_rx_ports = p_nt4ga_stat->mn_rx_ports;
+	const int n_tx_ports = p_nt4ga_stat->mn_tx_ports;
+	int c, h, p;
+
+	if (!p_nthw_stat || !p_nt4ga_stat)
+		return -1;
+
+	if (p_nthw_stat->mn_stat_layout_version != 6) {
+		NT_LOG(ERR, ETHDEV, "HW STA module version not supported");
+		return -1;
+	}
+
+	/* RX ports */
+	for (c = 0; c < p_nthw_stat->m_nb_color_counters / 2; c++) {
+		p_nt4ga_stat->mp_stat_structs_color[c].color_packets +=
+			p_stat_dma_virtual[c * 2];
+		p_nt4ga_stat->mp_stat_structs_color[c].color_bytes +=
+			p_stat_dma_virtual[c * 2 + 1];
+	}
+
+	/* Move to Host buffer counters */
+	p_stat_dma_virtual += p_nthw_stat->m_nb_color_counters;
+
+	for (h = 0; h < p_nthw_stat->m_nb_rx_host_buffers; h++) {
+		p_nt4ga_stat->mp_stat_structs_hb[h].flush_packets +=
+			p_stat_dma_virtual[h * 8];
+		p_nt4ga_stat->mp_stat_structs_hb[h].drop_packets +=
+			p_stat_dma_virtual[h * 8 + 1];
+		p_nt4ga_stat->mp_stat_structs_hb[h].fwd_packets +=
+			p_stat_dma_virtual[h * 8 + 2];
+		p_nt4ga_stat->mp_stat_structs_hb[h].dbs_drop_packets +=
+			p_stat_dma_virtual[h * 8 + 3];
+		p_nt4ga_stat->mp_stat_structs_hb[h].flush_bytes +=
+			p_stat_dma_virtual[h * 8 + 4];
+		p_nt4ga_stat->mp_stat_structs_hb[h].drop_bytes +=
+			p_stat_dma_virtual[h * 8 + 5];
+		p_nt4ga_stat->mp_stat_structs_hb[h].fwd_bytes +=
+			p_stat_dma_virtual[h * 8 + 6];
+		p_nt4ga_stat->mp_stat_structs_hb[h].dbs_drop_bytes +=
+			p_stat_dma_virtual[h * 8 + 7];
+	}
+
+	/* Move to Rx Port counters */
+	p_stat_dma_virtual += p_nthw_stat->m_nb_rx_hb_counters;
+
+	/* RX ports */
+	for (p = 0; p < n_rx_ports; p++) {
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 0];
+
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].broadcast_pkts +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 1];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].multicast_pkts +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 2];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].unicast_pkts +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 3];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_alignment +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 4];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_code_violation +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 5];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_crc +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 6];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].undersize_pkts +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 7];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].oversize_pkts +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 8];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].fragments +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 9];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].jabbers_not_truncated +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 10];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].jabbers_truncated +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 11];
+
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_64_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 12];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_65_to_127_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 13];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_128_to_255_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 14];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_256_to_511_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 15];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_512_to_1023_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 16];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p]
+		.pkts_1024_to_1518_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 17];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p]
+		.pkts_1519_to_2047_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 18];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p]
+		.pkts_2048_to_4095_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 19];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p]
+		.pkts_4096_to_8191_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 20];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_8192_to_max_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 21];
+
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].mac_drop_events +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 22];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_lr +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 23];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].duplicate +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 24];
+
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_ip_chksum_error +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 25];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_udp_chksum_error +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 26];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_tcp_chksum_error +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 27];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_giant_undersize +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 28];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_baby_giant +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 29];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_not_isl_vlan_mpls +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 30];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_isl +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 31];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_vlan +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 32];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_isl_vlan +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 33];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_mpls +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 34];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_isl_mpls +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 35];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_vlan_mpls +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 36];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_isl_vlan_mpls +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 37];
+
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_no_filter +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 38];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_dedup_drop +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 39];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_filter_drop +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 40];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_overflow +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 41];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts_dbs_drop +=
+			p_nthw_stat->m_dbs_present ?
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters +
+					  42] :
+			0;
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].octets_no_filter +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 43];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].octets_dedup_drop +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 44];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].octets_filter_drop +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 45];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].octets_overflow +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 46];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].octets_dbs_drop +=
+			p_nthw_stat->m_dbs_present ?
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters +
+					  47] :
+			0;
+
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].ipft_first_hit +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 48];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].ipft_first_not_hit +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 49];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].ipft_mid_hit +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 50];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].ipft_mid_not_hit +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 51];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].ipft_last_hit +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 52];
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].ipft_last_not_hit +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 53];
+
+		/* Rx totals */
+		uint64_t new_drop_events_sum =
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 22] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 38] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 39] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 40] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 41] +
+			(p_nthw_stat->m_dbs_present ?
+			 p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters +
+					   42] :
+			 0);
+
+		uint64_t new_packets_sum =
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 7] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 8] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 9] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 10] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 11] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 12] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 13] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 14] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 15] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 16] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 17] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 18] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 19] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 20] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 21];
+
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].drop_events +=
+			new_drop_events_sum;
+		p_nt4ga_stat->cap.mp_stat_structs_port_rx[p].pkts += new_packets_sum;
+
+		p_nt4ga_stat->a_port_rx_octets_total[p] +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 0];
+		p_nt4ga_stat->a_port_rx_packets_total[p] += new_packets_sum;
+		p_nt4ga_stat->a_port_rx_drops_total[p] += new_drop_events_sum;
+	}
+
+	/* Move to Tx Port counters */
+	p_stat_dma_virtual += n_rx_ports * p_nthw_stat->m_nb_rx_port_counters;
+
+	for (p = 0; p < n_tx_ports; p++) {
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 0];
+
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].broadcast_pkts +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 1];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].multicast_pkts +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 2];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].unicast_pkts +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 3];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].pkts_alignment +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 4];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].pkts_code_violation +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 5];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].pkts_crc +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 6];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].undersize_pkts +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 7];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].oversize_pkts +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 8];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].fragments +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 9];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].jabbers_not_truncated +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 10];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].jabbers_truncated +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 11];
+
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].pkts_64_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 12];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].pkts_65_to_127_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 13];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].pkts_128_to_255_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 14];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].pkts_256_to_511_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 15];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].pkts_512_to_1023_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 16];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p]
+		.pkts_1024_to_1518_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 17];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p]
+		.pkts_1519_to_2047_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 18];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p]
+		.pkts_2048_to_4095_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 19];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p]
+		.pkts_4096_to_8191_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 20];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].pkts_8192_to_max_octets +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 21];
+
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].mac_drop_events +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 22];
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].pkts_lr +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 23];
+
+		/* Tx totals */
+		uint64_t new_drop_events_sum =
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_rx_port_counters + 22];
+
+		uint64_t new_packets_sum =
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 7] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 8] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 9] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 10] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 11] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 12] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 13] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 14] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 15] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 16] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 17] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 18] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 19] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 20] +
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 21];
+
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].drop_events +=
+			new_drop_events_sum;
+		p_nt4ga_stat->cap.mp_stat_structs_port_tx[p].pkts += new_packets_sum;
+
+		p_nt4ga_stat->a_port_tx_octets_total[p] +=
+			p_stat_dma_virtual[p * p_nthw_stat->m_nb_tx_port_counters + 0];
+		p_nt4ga_stat->a_port_tx_packets_total[p] += new_packets_sum;
+		p_nt4ga_stat->a_port_tx_drops_total[p] += new_drop_events_sum;
+	}
+
+	return 0;
+}
diff --git a/drivers/net/ntnic/adapter/nt4ga_stat.h b/drivers/net/ntnic/adapter/nt4ga_stat.h
new file mode 100644
index 0000000000..4a1067200c
--- /dev/null
+++ b/drivers/net/ntnic/adapter/nt4ga_stat.h
@@ -0,0 +1,202 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NT4GA_STAT_H_
+#define NT4GA_STAT_H_
+
+#include "nt_util.h"
+#include "common_adapter_defs.h"
+
+#define NT_MAX_COLOR_FLOW_STATS 0x400
+
+struct color_counters {
+	uint64_t color_packets;
+	uint64_t color_bytes;
+	uint8_t tcp_flags;
+};
+
+struct host_buffer_counters {
+	uint64_t flush_packets;
+	uint64_t drop_packets;
+	uint64_t fwd_packets;
+	uint64_t dbs_drop_packets;
+	uint64_t flush_bytes;
+	uint64_t drop_bytes;
+	uint64_t fwd_bytes;
+	uint64_t dbs_drop_bytes;
+};
+
+struct port_counters_v2 {
+	/* Rx/Tx common port counters */
+	uint64_t drop_events;
+	uint64_t pkts;
+	/* FPGA counters */
+	uint64_t octets;
+	uint64_t broadcast_pkts;
+	uint64_t multicast_pkts;
+	uint64_t unicast_pkts;
+	uint64_t pkts_alignment;
+	uint64_t pkts_code_violation;
+	uint64_t pkts_crc;
+	uint64_t undersize_pkts;
+	uint64_t oversize_pkts;
+	uint64_t fragments;
+	uint64_t jabbers_not_truncated;
+	uint64_t jabbers_truncated;
+	uint64_t pkts_64_octets;
+	uint64_t pkts_65_to_127_octets;
+	uint64_t pkts_128_to_255_octets;
+	uint64_t pkts_256_to_511_octets;
+	uint64_t pkts_512_to_1023_octets;
+	uint64_t pkts_1024_to_1518_octets;
+	uint64_t pkts_1519_to_2047_octets;
+	uint64_t pkts_2048_to_4095_octets;
+	uint64_t pkts_4096_to_8191_octets;
+	uint64_t pkts_8192_to_max_octets;
+	uint64_t mac_drop_events;
+	uint64_t pkts_lr;
+	/* Rx only port counters */
+	uint64_t duplicate;
+	uint64_t pkts_ip_chksum_error;
+	uint64_t pkts_udp_chksum_error;
+	uint64_t pkts_tcp_chksum_error;
+	uint64_t pkts_giant_undersize;
+	uint64_t pkts_baby_giant;
+	uint64_t pkts_not_isl_vlan_mpls;
+	uint64_t pkts_isl;
+	uint64_t pkts_vlan;
+	uint64_t pkts_isl_vlan;
+	uint64_t pkts_mpls;
+	uint64_t pkts_isl_mpls;
+	uint64_t pkts_vlan_mpls;
+	uint64_t pkts_isl_vlan_mpls;
+	uint64_t pkts_no_filter;
+	uint64_t pkts_dedup_drop;
+	uint64_t pkts_filter_drop;
+	uint64_t pkts_overflow;
+	uint64_t pkts_dbs_drop;
+	uint64_t octets_no_filter;
+	uint64_t octets_dedup_drop;
+	uint64_t octets_filter_drop;
+	uint64_t octets_overflow;
+	uint64_t octets_dbs_drop;
+	uint64_t ipft_first_hit;
+	uint64_t ipft_first_not_hit;
+	uint64_t ipft_mid_hit;
+	uint64_t ipft_mid_not_hit;
+	uint64_t ipft_last_hit;
+	uint64_t ipft_last_not_hit;
+};
+
+struct port_counters_vswitch_v1 {
+	/* Rx/Tx common port counters */
+	uint64_t octets;
+	uint64_t pkts;
+	uint64_t drop_events;
+	uint64_t qos_drop_octets;
+	uint64_t qos_drop_pkts;
+};
+
+struct flm_counters_v1 {
+	/* FLM 0.17 */
+	uint64_t current;
+	uint64_t learn_done;
+	uint64_t learn_ignore;
+	uint64_t learn_fail;
+	uint64_t unlearn_done;
+	uint64_t unlearn_ignore;
+	uint64_t auto_unlearn_done;
+	uint64_t auto_unlearn_ignore;
+	uint64_t auto_unlearn_fail;
+	uint64_t timeout_unlearn_done;
+	uint64_t rel_done;
+	uint64_t rel_ignore;
+	/* FLM 0.20 */
+	uint64_t prb_done;
+	uint64_t prb_ignore;
+	uint64_t sta_done;
+	uint64_t inf_done;
+	uint64_t inf_skip;
+	uint64_t pck_hit;
+	uint64_t pck_miss;
+	uint64_t pck_unh;
+	uint64_t pck_dis;
+	uint64_t csh_hit;
+	uint64_t csh_miss;
+	uint64_t csh_unh;
+	uint64_t cuc_start;
+	uint64_t cuc_move;
+};
+
+struct nt4ga_stat_s {
+	nthw_stat_t *mp_nthw_stat;
+	nthw_rmc_t *mp_nthw_rmc;
+	struct nt_dma_s *p_stat_dma;
+	uint32_t *p_stat_dma_virtual;
+	uint32_t n_stat_size;
+
+	uint64_t last_timestamp;
+
+	int mn_rx_host_buffers;
+	int mn_tx_host_buffers;
+
+	int mn_rx_ports;
+	int mn_tx_ports;
+
+	struct color_counters *mp_stat_structs_color;
+	/* For calculating increments between stats polls */
+	struct color_counters a_stat_structs_color_base[NT_MAX_COLOR_FLOW_STATS];
+
+	union {
+		/*Port counters for VSWITCH/inline */
+		struct {
+			struct port_counters_vswitch_v1 *mp_stat_structs_port_rx;
+			struct port_counters_vswitch_v1 *mp_stat_structs_port_tx;
+		} virt;
+		struct {
+			struct port_counters_v2 *mp_stat_structs_port_rx;
+			struct port_counters_v2 *mp_stat_structs_port_tx;
+		} cap;
+	};
+
+	struct host_buffer_counters *mp_stat_structs_hb;
+
+	int flm_stat_ver;
+	struct flm_counters_v1 *mp_stat_structs_flm;
+
+	/* Rx/Tx totals: */
+	uint64_t n_totals_reset_timestamp; /* timestamp for last totals reset */
+
+	uint64_t a_port_rx_octets_total[NUM_ADAPTER_PORTS_MAX];
+	/* Base is for calculating increments between statistics reads */
+	uint64_t a_port_rx_octets_base[NUM_ADAPTER_PORTS_MAX];
+
+	uint64_t a_port_rx_packets_total[NUM_ADAPTER_PORTS_MAX];
+	uint64_t a_port_rx_packets_base[NUM_ADAPTER_PORTS_MAX];
+
+	uint64_t a_port_rx_drops_total[NUM_ADAPTER_PORTS_MAX];
+	uint64_t a_port_rx_drops_base[NUM_ADAPTER_PORTS_MAX];
+
+	uint64_t a_port_tx_octets_total[NUM_ADAPTER_PORTS_MAX];
+	uint64_t a_port_tx_octets_base[NUM_ADAPTER_PORTS_MAX];
+
+	uint64_t a_port_tx_packets_base[NUM_ADAPTER_PORTS_MAX];
+	uint64_t a_port_tx_packets_total[NUM_ADAPTER_PORTS_MAX];
+
+	uint64_t a_port_tx_drops_base[NUM_ADAPTER_PORTS_MAX];
+	uint64_t a_port_tx_drops_total[NUM_ADAPTER_PORTS_MAX];
+};
+
+typedef struct nt4ga_stat_s nt4ga_stat_t;
+
+int nt4ga_stat_init(struct adapter_info_s *p_adapter_info);
+int nt4ga_stat_setup(struct adapter_info_s *p_adapter_info);
+int nt4ga_stat_stop(struct adapter_info_s *p_adapter_info);
+
+int nt4ga_stat_dump(struct adapter_info_s *p_adapter_info, FILE *pfh);
+
+int nt4ga_stat_collect(struct adapter_info_s *p_adapter_info,
+		      nt4ga_stat_t *p_nt4ga_stat);
+
+#endif /* NT4GA_STAT_H_ */
diff --git a/drivers/net/ntnic/meson.build b/drivers/net/ntnic/meson.build
index 428fc7af98..2552b5d68d 100644
--- a/drivers/net/ntnic/meson.build
+++ b/drivers/net/ntnic/meson.build
@@ -10,22 +10,39 @@ endif
 # includes
 includes = [
     include_directories('.'),
+    include_directories('adapter'),
     include_directories('include'),
+    include_directories('nim'),
     include_directories('ntlog/include'),
     include_directories('ntutil/include'),
     include_directories('nthw'),
     include_directories('nthw/core'),
     include_directories('nthw/supported'),
+    include_directories('sensors'),
+    include_directories('sensors/avr_sensors'),
+    include_directories('sensors/board_sensors'),
+    include_directories('sensors/nim_sensors'),
+    include_directories('sensors/ntavr'),
 ]
 
 # all sources
 sources = files(
+    'adapter/nt4ga_adapter.c',
+    'adapter/nt4ga_link.c',
+    'adapter/nt4ga_link_100g.c',
+    'adapter/nt4ga_pci_ta_tg.c',
+    'adapter/nt4ga_stat.c',
+    'nim/i2c_nim.c',
+    'nim/nt_link_speed.c',
+    'nim/qsfp_sensors.c',
+    'nim/sfp_sensors.c',
     'nthw/core/nthw_clock_profiles.c',
     'nthw/core/nthw_fpga.c',
     'nthw/core/nthw_fpga_nt200a0x.c',
     'nthw/core/nthw_fpga_rst.c',
     'nthw/core/nthw_fpga_rst9563.c',
     'nthw/core/nthw_fpga_rst_nt200a0x.c',
+    'nthw/core/nthw_gmf.c',
     'nthw/core/nthw_gpio_phy.c',
     'nthw/core/nthw_hif.c',
     'nthw/core/nthw_iic.c',
@@ -35,6 +52,7 @@ sources = files(
     'nthw/core/nthw_pci_ta.c',
     'nthw/core/nthw_pci_wr_tg.c',
     'nthw/core/nthw_pcie3.c',
+    'nthw/core/nthw_rmc.c',
     'nthw/core/nthw_sdc.c',
     'nthw/core/nthw_si5340.c',
     'nthw/core/nthw_spi_v3.c',
@@ -50,6 +68,12 @@ sources = files(
     'nthw/supported/nthw_fpga_9563_055_024_0000.c',
     'ntlog/ntlog.c',
     'ntutil/nt_util.c',
+    'sensors/avr_sensors/avr_sensors.c',
+    'sensors/board_sensors/board_sensors.c',
+    'sensors/board_sensors/tempmon.c',
+    'sensors/nim_sensors/nim_sensors.c',
+    'sensors/ntavr/ntavr.c',
+    'sensors/sensors.c',
 )
 
 if is_variable('default_cflags')
diff --git a/drivers/net/ntnic/nim/i2c_nim.c b/drivers/net/ntnic/nim/i2c_nim.c
new file mode 100644
index 0000000000..55740e6de6
--- /dev/null
+++ b/drivers/net/ntnic/nim/i2c_nim.c
@@ -0,0 +1,1974 @@
+/* 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 "nim_sensors.h"
+#include "sfp_p_registers.h"
+#include "qsfp_registers.h"
+#include "sfp_sensors.h"
+#include "qsfp_sensors.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 sfp_is_supported_tri_speed_pn(char *prod_no)
+{
+	static const char *const pn_trispeed_list[] = {
+		"FCMJ-8521-3", "FCLF-8521-3", "FCLF8521P2BTL", "EOLT-C12-02A",
+		"AMXP-24RJS",  "ABCU-5710RZ", "ABCU-5740RZ",   "FCLF8522P2BTL",
+	};
+
+	/* Determine if copper SFP is supported 3-speed type */
+	for (size_t i = 0; i < ARRAY_SIZE(pn_trispeed_list); i++)
+		if (strcmp(pn_trispeed_list[i], prod_no) == 0)
+			return true;
+
+	return false;
+}
+
+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 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)
+		return nthw_iic_write_data(&ctx->hwiic, i2c_devaddr, reg_addr,
+					 seq_cnt, p_data);
+	else
+		return nthw_iic_read_data(&ctx->hwiic, i2c_devaddr, reg_addr,
+					seq_cnt, p_data);
+}
+
+/*
+ * ------------------------------------------------------------------------------
+ * 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_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 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 */
+	const int m_port_no = ctx->instance - 2;
+
+	if (lin_addr >= SFP_PHY_LIN_ADDR) {
+		/*
+		 * This represents an address space at I2C address 0xAC for SFP modules
+		 * containing a PHY. (eg 1G Copper SFP). Each register is 16bit and is
+		 * accessed MSByte first and this reading latches the LSByte that is
+		 * subsequently read from the same address.
+		 */
+		multi_byte = 2;
+		max_seq_cnt = 2;
+
+		/* Test for correct multibyte access */
+		if ((length % multi_byte) != 0) {
+			NT_LOG(ERR, ETHDEV,
+			       "Port %d: %s: Uneven length (%d) for address range [0x%X..0x%X].",
+			       m_port_no, __func__, length, SFP_PHY_LIN_ADDR,
+			       SFP_PHY_LIN_ADDR + SFP_PHY_LIN_RNG - 1);
+			return -1;
+		}
+
+		if (lin_addr + (length / 2) >
+				SFP_PHY_LIN_ADDR + SFP_PHY_LIN_RNG) {
+			NT_LOG(ERR, ETHDEV,
+			       "Port %d: %s: Access above address range [0x%X..0x%X].",
+			       m_port_no, __func__, SFP_PHY_LIN_ADDR,
+			       SFP_PHY_LIN_ADDR + SFP_PHY_LIN_RNG - 1);
+			return -1;
+		}
+	} else if (lin_addr + length > 128) {
+		/*
+		 * Page addressing could be relevant since the last byte is outside the
+		 * basic range so check if it is enabled
+		 */
+		if (m_page_addressing) {
+			/* Crossing into the PHY address range is not allowed */
+			if (lin_addr + length > SFP_PHY_LIN_ADDR) {
+				NT_LOG(ERR, ETHDEV,
+				       "Port %d: %s: Access above paged address range [0..0x%X].",
+				       m_port_no, __func__, SFP_PHY_LIN_ADDR);
+				return -1;
+			}
+		} else {
+			/* Access outside 0xA2 address range not allowed */
+			if (lin_addr + length > 512) {
+				NT_LOG(ERR, ETHDEV,
+				       "Port %d: %s: Access above address range [0..511].",
+				       m_port_no, __func__);
+				return -1;
+			}
+		}
+	}
+	/* No missing else here - all devices supports access to address [0..127] */
+
+	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 */
+				reg_addr = (uint8_t)(block_size +
+						    (lin_addr % block_size));
+			} else {
+				reg_addr = (uint8_t)lin_addr;
+			}
+			i2c_addr = nim_i2c_0xa0; /* Base I2C address */
+		} else {
+			if (lin_addr >= SFP_PHY_LIN_ADDR) {
+				/* Map to address [0..31] of 0xAC device */
+				reg_addr = (uint8_t)(lin_addr - SFP_PHY_LIN_ADDR);
+				i2c_addr = nim_i2c_0xac;
+			} else if (lin_addr >= 256) {
+				/* Map to address [0..255] of 0xA2 device */
+				reg_addr = (uint8_t)(lin_addr - 256);
+				i2c_addr = nim_i2c_0xa2;
+			} else {
+				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,
+					    reg_addr, (uint8_t)seq_cnt,
+					    p_data) != 0) {
+			NT_LOG(ERR, ETHDEV,
+			       "%s: Call to NIM_ReadWriteI2cData 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)
+{
+	return nim_nim_read_write_data_lin(ctx, page_addressing(ctx->nim_id),
+				       lin_addr, length, data, NIM_READ);
+}
+
+static int write_data_lin(nim_i2c_ctx_p ctx, uint16_t lin_addr, uint16_t length,
+			void *data)
+{
+	return nim_nim_read_write_data_lin(ctx, page_addressing(ctx->nim_id),
+				       lin_addr, length, data, NIM_WRITE);
+}
+
+/* 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_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 = nim_read_id(ctx);
+
+	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);
+
+#define XSFP_READ_VENDOR_INFO(x)                                             \
+	static void x##sfp_read_vendor_info(nim_i2c_ctx_t *ctx)              \
+	{                                                                    \
+		nim_read_vendor_info(ctx, Q##SFP_VENDOR_NAME_LIN_ADDR,      \
+				      sizeof(ctx->vendor_name),               \
+				      ctx->vendor_name);                      \
+		nim_read_vendor_info(ctx, Q##SFP_VENDOR_PN_LIN_ADDR,        \
+				      sizeof(ctx->prod_no), ctx->prod_no);     \
+		nim_read_vendor_info(ctx, Q##SFP_VENDOR_SN_LIN_ADDR,        \
+				      sizeof(ctx->serial_no), ctx->serial_no); \
+		nim_read_vendor_info(ctx, Q##SFP_VENDOR_DATE_LIN_ADDR,      \
+				      sizeof(ctx->date), ctx->date);         \
+		nim_read_vendor_info(ctx, Q##SFP_VENDOR_REV_LIN_ADDR,       \
+				      (uint8_t)(sizeof(ctx->rev) - 2),       \
+				      ctx->rev); /*OBS Only two bytes*/      \
+	}
+
+XSFP_READ_VENDOR_INFO()
+XSFP_READ_VENDOR_INFO(q)
+
+static int sfp_nim_state_build(nim_i2c_ctx_t *ctx, sfp_nim_state_t *state)
+{
+	int res;
+
+	assert(ctx && state);
+	assert(ctx->nim_id != NT_NIM_UNKNOWN && "Nim is not initialized");
+
+	(void)memset(state, 0, sizeof(*state));
+
+	res = nthw_iic_read_data(&ctx->hwiic, ctx->devaddr, SFP_BIT_RATE_ADDR,
+			       sizeof(state->br), &state->br);
+	return res;
+}
+
+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 sfp_nim_state_build(ctx, state);
+	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!";
+	}
+}
+
+/*
+ * Read and check the validity of the NIM basic data.
+ * This will also preload the cache
+ */
+static void check_content_valid(nim_i2c_ctx_p ctx, uint16_t start_addr)
+{
+	uint32_t sum = 0;
+	uint8_t buf[96];
+
+	read_data_lin(ctx, start_addr, sizeof(buf), &buf[0]);
+
+	for (int i = 0; i < 63; i++)
+		sum += buf[i];
+
+	if ((sum & 0xFF) != buf[63]) {
+		ctx->content_valid = false;
+	} else {
+		sum = 0;
+
+		for (int i = 64; i < 95; i++)
+			sum += buf[i];
+
+		ctx->content_valid = ((sum & 0xFF) == buf[95]);
+	}
+	if (ctx->content_valid)
+		NT_LOG(DBG, NTHW, "NIM content validation passed");
+	else
+		NT_LOG(WRN, NTHW, "NIM content validation failed");
+}
+
+/*
+ * Set/reset Soft Rate__select bits (RS0 & RS1)
+ */
+static void nim_sfp_set_rate_sel_high(nim_i2c_ctx_p ctx, bool rx_rate_high,
+				  bool tx_rate_high)
+{
+	const bool m_page_addressing = page_addressing(ctx->nim_id);
+	uint8_t data;
+
+	nim_nim_read_write_data_lin(ctx, m_page_addressing,
+				SFP_CONTROL_STATUS_LIN_ADDR, sizeof(data),
+				&data, NIM_READ);
+
+	if (rx_rate_high)
+		data |= SFP_SOFT_RATE0_BIT;
+	else
+		data &= (uint8_t)~(SFP_SOFT_RATE0_BIT);
+
+	nim_nim_read_write_data_lin(ctx, m_page_addressing,
+				SFP_CONTROL_STATUS_LIN_ADDR, sizeof(data),
+				&data, NIM_WRITE);
+
+	/* Read the Extended Status/Control and set/reset Soft RS1 bit */
+	nim_nim_read_write_data_lin(ctx, m_page_addressing,
+				SFP_EXT_CTRL_STAT0_LIN_ADDR, sizeof(data),
+				&data, NIM_READ);
+
+	if (tx_rate_high)
+		data |= SFP_SOFT_RATE1_BIT;
+	else
+		data &= (uint8_t)~(SFP_SOFT_RATE1_BIT);
+
+	nim_nim_read_write_data_lin(ctx, m_page_addressing,
+				SFP_EXT_CTRL_STAT0_LIN_ADDR, sizeof(data),
+				&data, NIM_WRITE);
+}
+
+/*
+ * Some NIM modules requires some changes to a rate setting.
+ */
+static int nim_sfp_set_rate_select(nim_i2c_ctx_p ctx, nt_link_speed_t speed)
+{
+	if ((speed & (int)ctx->speed_mask) == 0) {
+		char buf[128];
+
+		NT_LOG(ERR, ETHDEV, "%s - Speed (%s) not within SpeedMask (%s)",
+		       nt_translate_link_speed(speed),
+		       nt_translate_link_speed_mask(ctx->speed_mask, buf,
+						 sizeof(buf)));
+		return -1;
+	}
+
+	if (ctx->specific_u.sfp.dual_rate) {
+		uint64_t req_speed = nt_get_link_speed(speed);
+		uint64_t other_speed =
+			nt_get_link_speed((nt_link_speed_t)(ctx->speed_mask ^ (uint32_t)speed));
+		bool rate_high = req_speed > other_speed;
+		/*
+		 * Do this both for 1/10 and 10/25. For Sfp28 it is not known if
+		 * this is necessary but it is believed not to do any harm.
+		 */
+		nim_sfp_set_rate_sel_high(ctx, rate_high, rate_high);
+	}
+	return 0;
+}
+
+/*
+ * Disable TX laser.
+ */
+int nim_sfp_nim_set_tx_laser_disable(nim_i2c_ctx_p ctx, bool disable)
+{
+	int res;
+	uint8_t value;
+	const bool pg_addr = page_addressing(ctx->nim_id);
+
+	res = nim_nim_read_write_data_lin(ctx, pg_addr, SFP_CONTROL_STATUS_LIN_ADDR,
+				      sizeof(value), &value, NIM_READ);
+	if (res != 0)
+		return res;
+
+	if (disable)
+		value |= SFP_SOFT_TX_DISABLE_BIT;
+	else
+		value &= (uint8_t)~SFP_SOFT_TX_DISABLE_BIT;
+
+	res = nim_nim_read_write_data_lin(ctx, pg_addr, SFP_CONTROL_STATUS_LIN_ADDR,
+				      sizeof(value), &value, NIM_WRITE);
+
+	return res;
+}
+
+/*
+ * 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_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_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 maxLen that includes a terminating "0" */
+
+	if (nim_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] _unused = { "No", "Yes" };
+
+	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_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_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_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;
+}
+
+/*
+ * 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_speed_selection_enabled(nim_i2c_ctx_p ctx)
+{
+	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, NTHW, "NIM has unhandled rate select type (%d)",
+		       rate_select_type);
+		return false;
+	}
+
+	return true; /* When true selectRate() can be used */
+}
+
+/*
+ * Select a speed that is supported for a multi rate module. The possible speed
+ * values must be obtained by setSpeedMask().
+ * Currently rate selection is assumed to be between 40Gb (10GBd) and 100G (25Gbd)
+ * The value in () are the baud rates for PAM-4 and are valid for extended rate
+ * select, version 2.
+ */
+static int qsfp28_set_link_speed(nim_i2c_ctx_p ctx, nt_link_speed_t speed)
+{
+	const uint8_t rx_rate_sel_addr = 87;
+	const uint8_t tx_rate_sel_addr = 88;
+
+	if (ctx->lane_idx < 0) {
+		/*
+		 * All lanes together
+		 * The condition below indicates that the module supports rate selection
+		 */
+		if (ctx->speed_mask == (uint32_t)(NT_LINK_SPEED_40G | NT_LINK_SPEED_100G)) {
+			uint16_t data;
+
+			if (speed == NT_LINK_SPEED_100G) {
+				data = 0xAAAA;
+			} else if (speed == NT_LINK_SPEED_40G) {
+				data = 0x0000;
+			} else {
+				NT_LOG(ERR, NTHW, "Unhandled NIM speed (%s).",
+				       nt_translate_link_speed(speed));
+				return -1;
+			}
+
+			/* Set speed for Rx and Tx on all lanes */
+			write_data_lin(ctx, rx_rate_sel_addr, sizeof(data), &data);
+			write_data_lin(ctx, tx_rate_sel_addr, sizeof(data), &data);
+		} else {
+			/* For ordinary modules only this speed is supported */
+			if (speed != NT_LINK_SPEED_100G) {
+				NT_LOG(ERR, NTHW,
+				       "NIM cannot select this speed (%s).",
+				       nt_translate_link_speed(speed));
+				return -1;
+			}
+		}
+	} else {
+		/*
+		 * Individual lanes
+		 * Currently we do not support QSFP28 modules that support rate selection when
+		 * running on individual lanes but that might change in the future
+		 */
+		if (speed != NT_LINK_SPEED_25G) {
+			NT_LOG(ERR, NTHW,
+			       "NIM cannot select this lane speed (%s).",
+			       nt_translate_link_speed(speed));
+			return -1;
+		}
+	}
+	return 0;
+}
+
+int nim_set_link_speed(nim_i2c_ctx_p ctx, nt_link_speed_t speed)
+{
+	if (translate_nimid(ctx) == NT_NIM_SFP_SFP_PLUS) {
+		return nim_sfp_set_rate_select(ctx, speed);
+	} else if (translate_nimid(ctx) == NT_NIM_QSFP28) {
+		if (qsfp28_is_speed_selection_enabled(ctx))
+			return qsfp28_set_link_speed(ctx, speed);
+
+		return 0; /* NIM picks up the speed automatically */
+	}
+	NT_LOG(ERR, ETHDEV,
+	       "%s nim is not supported for adjustable link speed.",
+	       nim_id_to_text(ctx->nim_id));
+	return -1;
+}
+
+/*
+ * Reads basic vendor and DMI information.
+ */
+static int sfp_read_basic_data(nim_i2c_ctx_p ctx)
+{
+	const char *yes_no[2] _unused = { "No", "Yes" };
+
+	check_content_valid(ctx, 0);
+	NT_LOG(DBG, PMD, "NIM id: %s (%d)", nim_id_to_text(ctx->nim_id),
+	       ctx->nim_id);
+
+	/* Read DMI options */
+	uint8_t options;
+
+	read_data_lin(ctx, SFP_DMI_OPTION_LIN_ADDR, sizeof(options), &options);
+	ctx->avg_pwr = options & SFP_DMI_AVG_PWR_BIT;
+	ctx->dmi_supp = options & SFP_DMI_IMPL_BIT;
+	ctx->specific_u.sfp.ext_cal = options & SFP_DMI_EXT_CAL_BIT;
+	ctx->specific_u.sfp.addr_chg = options & SFP_DMI_ADDR_CHG_BIT;
+
+	if (ctx->dmi_supp) {
+		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);
+	}
+
+	if (ctx->dmi_supp) {
+		NT_LOG(DBG, PMD,
+		       "NIM options: (DMI: %s, AvgPwr: %s, ExtCal: %s, AddrChg: %s)",
+		       yes_no[ctx->dmi_supp], yes_no[ctx->avg_pwr],
+		       yes_no[ctx->specific_u.sfp.ext_cal],
+		       yes_no[ctx->specific_u.sfp.addr_chg]);
+	} else {
+		NT_LOG(DBG, PMD, "NIM options: DMI not supported");
+	}
+	/* Read enhanced options */
+	read_data_lin(ctx, SFP_ENHANCED_OPTIONS_LIN_ADDR, sizeof(options),
+		    &options);
+	ctx->tx_disable = options & SFP_SOFT_TX_DISABLE_IMPL_BIT;
+
+	if (ctx->tx_disable)
+		ctx->options |= (1 << NIM_OPTION_TX_DISABLE);
+
+	sfp_read_vendor_info(ctx);
+
+	uint8_t nim_len_info[5];
+
+	read_data_lin(ctx, SFP_SUP_LEN_INFO_LIN_ADDR, sizeof(nim_len_info),
+		    nim_len_info);
+
+	/*
+	 * 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.
+	 */
+
+	uint16_t nim_units[5] = { 1000, 100, 10, 10,
+				 1
+			       }; /* SFP MSA units in meters */
+	nim_import_len_info(ctx, &nim_len_info[0], &nim_units[0]);
+
+	if (ctx->len_info[0] != 0 || ctx->len_info[1] != 0) {
+		/*
+		 * Make sure that for SFP modules the supported length for SM fibers
+		 * which is given in both km and 100m units is are equal to the greatest
+		 * value.
+		 * The following test will also be valid if NIM_LEN_MAX has been set!
+		 */
+		if (ctx->len_info[1] > ctx->len_info[0])
+			ctx->len_info[0] = ctx->len_info[1];
+
+		ctx->len_info[1] = 0; /* EBW is not supported for SFP */
+	}
+
+	read_data_lin(ctx, SFP_OPTION0_LIN_ADDR, sizeof(options), &options);
+
+	if (options & SFP_POWER_LEVEL2_REQ_BIT)
+		ctx->pwr_level_req = 2;
+	else
+		ctx->pwr_level_req = 1;
+
+	ctx->pwr_level_cur = 1;
+
+	if (ctx->pwr_level_req == 2) {
+		/* Read the current power level status */
+		read_data_lin(ctx, SFP_EXT_CTRL_STAT0_LIN_ADDR, sizeof(options),
+			    &options);
+
+		if (options & SFP_POWER_LEVEL2_GET_BIT)
+			ctx->pwr_level_cur = 2;
+		else
+			ctx->pwr_level_cur = 1;
+	}
+	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 sfp_find_port_params(nim_i2c_ctx_p ctx)
+{
+	uint8_t data;
+	uint16_t bit_rate_nom;
+	uint8_t connector;
+	uint8_t gig_eth_comp;
+	uint8_t dmi_opt;
+	uint8_t fiber_chan_tx_tech;
+	unsigned int len_sm;
+	unsigned int len_mm_50um;
+	unsigned int len_mm_62_5um;
+
+	ctx->specific_u.sfp.sfp28 = false;
+
+	/* gigEthComp: */
+	static const uint8_t eth_1000_b_t = 1 << 3;
+	static const uint8_t eth_1000_b_sx = 1 << 0;
+	static const uint8_t eth_1000_b_lx = 1 << 1;
+
+	/* fiberChanTxTech: */
+	static const uint8_t cu_passive = 1 << 2;
+	static const uint8_t cu_active = 1 << 3;
+
+	/* dmiOpt: */
+	static const uint8_t dd_present = 1 << 6;
+
+	/* connector: */
+	static const uint8_t cu_pig_tail = 0x21;
+
+	ctx->port_type = NT_PORT_TYPE_SFP_NOT_RECOGNISED;
+
+	read_data_lin(ctx, 12, sizeof(data), &data);
+	bit_rate_nom = (uint16_t)(data * 100);
+
+	read_data_lin(ctx, 2, sizeof(connector), &connector);
+	read_data_lin(ctx, 6, sizeof(gig_eth_comp), &gig_eth_comp);
+	read_data_lin(ctx, 92, sizeof(dmi_opt), &dmi_opt);
+	read_data_lin(ctx, 8, sizeof(fiber_chan_tx_tech), &fiber_chan_tx_tech);
+
+	read_data_lin(ctx, 15, sizeof(data), &data);
+	len_sm = (unsigned int)data * 100; /* Unit is 100m */
+
+	read_data_lin(ctx, 16, sizeof(data), &data);
+	len_mm_50um = (unsigned int)data * 10; /* Unit is 10m */
+
+	read_data_lin(ctx, 17, sizeof(data), &data);
+	len_mm_62_5um = (unsigned int)data * 10; /* Unit is 10m */
+
+	/* First find out if it is a SFP or a SFP+ NIM */
+	if (bit_rate_nom == 0) {
+		/*
+		 * A Nominal bit rate of zero indicates that it has not been defined and must
+		 * be deduced from transceiver technology
+		 */
+		ctx->specific_u.sfp.sfpplus = !(gig_eth_comp & eth_1000_b_t);
+	} else if (bit_rate_nom == 25500) {
+		/* SFF-8024 - 4.4 Extended Specification Compliance References */
+		read_data_lin(ctx, 36, sizeof(data), &data);
+
+		if (data == 0x02)
+			ctx->port_type = NT_PORT_TYPE_SFP_28_SR;
+		else if (data == 0x03)
+			ctx->port_type = NT_PORT_TYPE_SFP_28_LR;
+		else if (data == 0x0B)
+			ctx->port_type = NT_PORT_TYPE_SFP_28_CR_CA_L;
+		else if (data == 0x0C)
+			ctx->port_type = NT_PORT_TYPE_SFP_28_CR_CA_S;
+		else if (data == 0x0D)
+			ctx->port_type = NT_PORT_TYPE_SFP_28_CR_CA_N;
+		else
+			ctx->port_type = NT_PORT_TYPE_SFP_28;
+
+		ctx->specific_u.sfp.sfp28 = true;
+		ctx->specific_u.sfp.sfpplus = true;
+
+		/*
+		 * Whitelist of 25G transceivers known to also support 10G.
+		 * There is no way to inquire about this capability.
+		 */
+		if ((strcmp(ctx->prod_no, "TR-PZ85S-N00") == 0) ||
+				(strcmp(ctx->prod_no, "TR-PZ13L-N00") == 0) ||
+				(strcmp(ctx->prod_no, "FTLF8536P4BCV") == 0) ||
+				(strcmp(ctx->prod_no, "FTLF1436P4BCV") == 0)) {
+			ctx->specific_u.sfp.dual_rate = true;
+
+			/* Change the port type for dual rate modules */
+			if (ctx->port_type == NT_PORT_TYPE_SFP_28_SR)
+				ctx->port_type = NT_PORT_TYPE_SFP_28_SR_DR;
+			else if (ctx->port_type == NT_PORT_TYPE_SFP_28_LR)
+				ctx->port_type = NT_PORT_TYPE_SFP_28_LR_DR;
+		}
+
+		return;
+	}
+	ctx->specific_u.sfp.sfpplus = (bit_rate_nom >= 10000);
+	/* Then find sub-types of each */
+	if (ctx->specific_u.sfp.sfpplus) {
+		if (fiber_chan_tx_tech & cu_active) {
+			ctx->port_type = NT_PORT_TYPE_SFP_PLUS_ACTIVE_DAC;
+		} else if (fiber_chan_tx_tech & cu_passive) {
+			if (connector == cu_pig_tail)
+				ctx->port_type =
+					NT_PORT_TYPE_SFP_PLUS_PASSIVE_DAC;
+			else
+				ctx->port_type = NT_PORT_TYPE_SFP_PLUS_CU;
+		} else {
+			ctx->port_type = NT_PORT_TYPE_SFP_PLUS;
+		}
+		if (gig_eth_comp & (eth_1000_b_sx | eth_1000_b_lx)) {
+			ctx->port_type = NT_PORT_TYPE_SFP_PLUS_DUAL_RATE;
+			ctx->specific_u.sfp.dual_rate = true;
+		}
+
+		read_data_lin(ctx, 65, sizeof(data), &data);
+		/* Test hard RATE_SELECT bit */
+		ctx->specific_u.sfp.hw_rate_sel = ((data & (1 << 5)) != 0);
+
+		read_data_lin(ctx, 93, sizeof(data), &data);
+		/* Test soft RATE_SELECT bit */
+		ctx->specific_u.sfp.sw_rate_sel = ((data & (1 << 3)) != 0);
+	} else { /* SFP */
+		/* 100M */
+		if (bit_rate_nom != 0 && bit_rate_nom < 1000) {
+			ctx->port_type = NT_PORT_TYPE_SFP_FX;
+		/* 1G */
+		} else {
+			ctx->specific_u.sfp.cu_type = false;
+			if (gig_eth_comp & eth_1000_b_sx) {
+				ctx->port_type = NT_PORT_TYPE_SFP_SX;
+			} else if (gig_eth_comp & eth_1000_b_lx) {
+				ctx->port_type = NT_PORT_TYPE_SFP_LX;
+			} else if (gig_eth_comp & eth_1000_b_t) {
+				ctx->specific_u.sfp.tri_speed =
+					sfp_is_supported_tri_speed_pn(ctx->prod_no);
+
+				if (ctx->specific_u.sfp.tri_speed) {
+					ctx->port_type =
+						NT_PORT_TYPE_SFP_CU_TRI_SPEED;
+				} else {
+					ctx->port_type = NT_PORT_TYPE_SFP_CU;
+				}
+				ctx->specific_u.sfp.cu_type = true;
+			} else {
+				/*
+				 * Not all modules report their ethernet compliance correctly so use
+				 * length indicators
+				 */
+				if (len_sm > 0)
+					ctx->port_type = NT_PORT_TYPE_SFP_LX;
+				else if ((len_mm_50um > 0) || (len_mm_62_5um > 0))
+					ctx->port_type = NT_PORT_TYPE_SFP_SX;
+			}
+
+			/* Add Diagnostic Data suffix if necessary */
+			if (dmi_opt & dd_present) {
+				if (ctx->port_type == NT_PORT_TYPE_SFP_SX)
+					ctx->port_type = NT_PORT_TYPE_SFP_SX_DD;
+				else if (ctx->port_type == NT_PORT_TYPE_SFP_LX)
+					ctx->port_type = NT_PORT_TYPE_SFP_LX_DD;
+				else if (ctx->port_type == NT_PORT_TYPE_SFP_CU)
+					ctx->port_type = NT_PORT_TYPE_SFP_CU_DD;
+				else if (ctx->port_type ==
+						NT_PORT_TYPE_SFP_CU_TRI_SPEED)
+					ctx->port_type =
+						NT_PORT_TYPE_SFP_CU_TRI_SPEED_DD;
+			}
+		}
+	}
+}
+
+
+static void sfp_set_speed_mask(nim_i2c_ctx_p ctx)
+{
+	if (ctx->specific_u.sfp.sfp28) {
+		ctx->speed_mask = NT_LINK_SPEED_25G; /* Default for SFP28 */
+		if (ctx->specific_u.sfp.dual_rate)
+			ctx->speed_mask |= NT_LINK_SPEED_10G;
+	} else if (ctx->specific_u.sfp.sfpplus) {
+		ctx->speed_mask = NT_LINK_SPEED_10G; /* Default for SFP+ */
+		if (ctx->specific_u.sfp.dual_rate)
+			ctx->speed_mask |= NT_LINK_SPEED_1G;
+		if (ctx->port_type == NT_PORT_TYPE_SFP_PLUS_PASSIVE_DAC)
+			ctx->speed_mask |= NT_LINK_SPEED_1G;
+		if (ctx->port_type == NT_PORT_TYPE_SFP_PLUS_ACTIVE_DAC)
+			ctx->speed_mask |= NT_LINK_SPEED_1G;
+	} else { /* SFP */
+		if (ctx->port_type == NT_PORT_TYPE_SFP_FX) {
+			ctx->speed_mask = NT_LINK_SPEED_100M;
+		} else {
+			ctx->speed_mask = NT_LINK_SPEED_1G; /* Default for SFP */
+			if (ctx->specific_u.sfp.dual_rate ||
+					ctx->specific_u.sfp.tri_speed)
+				ctx->speed_mask |= NT_LINK_SPEED_100M;
+			if (ctx->specific_u.sfp.tri_speed)
+				ctx->speed_mask |= NT_LINK_SPEED_10M;
+		}
+	}
+	if (ctx->port_type == NT_PORT_TYPE_SFP_28_CR_CA_L ||
+			ctx->port_type == NT_PORT_TYPE_SFP_28_CR_CA_S ||
+			ctx->port_type == NT_PORT_TYPE_SFP_28_CR_CA_N) {
+		/* Enable multiple speed setting for SFP28 DAC cables */
+		ctx->speed_mask = (NT_LINK_SPEED_25G | NT_LINK_SPEED_10G |
+				  NT_LINK_SPEED_1G);
+	}
+}
+
+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
+			ctx->speed_mask =
+				0; /* PAM-4 modules can only run on all lanes together */
+	} 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 int sfp_preinit(nim_i2c_ctx_p ctx)
+{
+	int res = sfp_read_basic_data(ctx);
+
+	if (!res) {
+		sfp_find_port_params(ctx);
+		sfp_set_speed_mask(ctx);
+	}
+	return res;
+}
+
+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 sfp_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)
+{
+	struct nim_sensor_group *sensor = NULL;
+	*nim_sensors_cnt = 0;
+
+	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(sfp_sensors_level0[0].name) == 0) {
+		if (ctx->specific_u.sfp.sfp28) {
+			rte_strscpy(sfp_sensors_level0[0].name, "SFP28",
+				sizeof(sfp_sensors_level0[0].name));
+		} else if (ctx->specific_u.sfp.sfpplus) {
+			rte_strscpy(sfp_sensors_level0[0].name, "SFP+",
+				sizeof(sfp_sensors_level0[0].name));
+		} else {
+			rte_strscpy(sfp_sensors_level0[0].name, "SFP",
+				sizeof(sfp_sensors_level0[0].name));
+		}
+	}
+
+	/* allocate temperature sensor */
+	nim_sensors_ptr[m_port_no] = allocate_nim_sensor_group(m_port_no,
+							       ctx,
+							       NT_SENSOR_SOURCE_PORT,
+							       &sfp_sensors_level0[0]);
+	sensor = nim_sensors_ptr[m_port_no];
+	sensor->read = &nim_read_sfp_temp;
+	(*nim_sensors_cnt)++;
+
+	/* voltage */
+	sensor->next = allocate_nim_sensor_group(m_port_no,
+						 ctx,
+						 NT_SENSOR_SOURCE_PORT,
+						 &sfp_sensors_level1[0]);
+	sensor = sensor->next;
+	sensor->read = &nim_read_sfp_voltage;
+	(*nim_sensors_cnt)++;
+
+	/* bias current */
+	sensor->next = allocate_nim_sensor_group(m_port_no,
+						 ctx,
+						 NT_SENSOR_SOURCE_PORT,
+						 &sfp_sensors_level1[1]);
+	sensor = sensor->next;
+	sensor->read = &nim_read_sfp_bias_current;
+	(*nim_sensors_cnt)++;
+
+	/* tx power */
+	sensor->next = allocate_nim_sensor_group(m_port_no,
+						 ctx,
+						 NT_SENSOR_SOURCE_PORT,
+						 &sfp_sensors_level1[2]);
+	sensor = sensor->next;
+	sensor->read = &nim_read_sfp_tx_power;
+	(*nim_sensors_cnt)++;
+
+	/* rx power */
+	sensor->next = allocate_nim_sensor_group(m_port_no,
+						 ctx,
+						 NT_SENSOR_SOURCE_PORT,
+						 &sfp_sensors_level1[3]);
+	sensor = sensor->next;
+	sensor->read = &nim_read_sfp_rx_power;
+	(*nim_sensors_cnt)++;
+}
+
+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)
+{
+	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)
+			rte_strscpy(qsfp_sensor_level0[0].name, "QSFP28",
+				sizeof(qsfp_sensor_level0[0].name));
+		else
+			rte_strscpy(qsfp_sensor_level0[0].name, "QSFP+",
+				sizeof(qsfp_sensor_level0[0].name));
+	}
+
+	/* 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 = &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 = &nim_read_qsfp_voltage;
+	(*nim_sensors_cnt)++;
+
+	/* bias current sensors */
+	for (uint8_t i = 1; 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 = &nim_read_qsfp_bias_current;
+		(*nim_sensors_cnt)++;
+	}
+
+	/* tx power */
+	for (uint8_t i = 5; 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 = &nim_read_qsfp_tx_power;
+		(*nim_sensors_cnt)++;
+	}
+
+	/* rx power */
+	for (uint8_t i = 9; 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 = &nim_read_qsfp_rx_power;
+		(*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)
+{
+	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 = 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:
+		sfp_preinit(ctx);
+		sfp_nim_add_all_sensors(port, ctx, nim_sensors_ptr,
+					nim_sensors_cnt);
+		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;
+}
diff --git a/drivers/net/ntnic/nim/i2c_nim.h b/drivers/net/ntnic/nim/i2c_nim.h
new file mode 100644
index 0000000000..f664e6b7ee
--- /dev/null
+++ b/drivers/net/ntnic/nim/i2c_nim.h
@@ -0,0 +1,122 @@
+/* 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 "sensors.h"
+
+typedef struct sfp_nim_state {
+	uint8_t br; /* bit rate, units of 100 MBits/sec */
+} sfp_nim_state_t, *sfp_nim_state_p;
+
+typedef struct nim_i2c_ctx {
+	nthw_iic_t hwiic; /* depends on *Fpga_t, instance number, and cycle time */
+	uint8_t instance;
+	uint8_t devaddr;
+	uint8_t regaddr;
+	uint8_t nim_id;
+	nt_port_type_t port_type;
+
+	char vendor_name[17];
+	char prod_no[17];
+	char serial_no[17];
+	char date[9];
+	char rev[5];
+	bool avg_pwr;
+	bool content_valid;
+	uint8_t pwr_level_req;
+	uint8_t pwr_level_cur;
+	uint16_t len_info[5];
+	uint32_t speed_mask; /* Speeds supported by the NIM */
+	int8_t lane_idx; /* Is this associated with a single lane or all lanes (-1) */
+	uint8_t lane_count;
+	uint32_t options;
+	bool tx_disable;
+	bool dmi_supp;
+
+	union {
+		struct {
+			bool sfp28;
+			bool sfpplus;
+			bool dual_rate;
+			bool hw_rate_sel;
+			bool sw_rate_sel;
+			bool cu_type;
+			bool tri_speed;
+			bool ext_cal;
+			bool addr_chg;
+		} sfp;
+
+		struct {
+			bool rx_only;
+			bool qsfp28;
+			union {
+				struct {
+					uint8_t rev_compliance;
+					bool media_side_fec_ctrl;
+					bool host_side_fec_ctrl;
+					bool media_side_fec_ena;
+					bool host_side_fec_ena;
+				} qsfp28;
+			} specific_u;
+		} qsfp;
+
+	} specific_u;
+} nim_i2c_ctx_t, *nim_i2c_ctx_p;
+
+struct nim_sensor_group {
+	struct nt_adapter_sensor *sensor;
+	void (*read)(struct nim_sensor_group *sg, nthw_spis_t *t_spi);
+	struct nim_i2c_ctx *ctx;
+	struct nim_sensor_group *next;
+};
+
+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_sfp_nim_set_tx_laser_disable(nim_i2c_ctx_p ctx, bool disable);
+
+int nim_qsfp_plus_nim_set_tx_laser_disable(nim_i2c_ctx_t *ctx, bool disable,
+				       int lane_idx);
+
+int nim_set_link_speed(nim_i2c_ctx_p ctx, nt_link_speed_t speed);
+
+/*
+ * 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);
+
+#endif /* I2C_NIM_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..da3567d073
--- /dev/null
+++ b/drivers/net/ntnic/nim/nim_defines.h
@@ -0,0 +1,146 @@
+/* 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;
+
+/*
+ * Port types
+ * The use of all non-generic XX_NOT_PRESENT is deprecated - use
+ * NT_PORT_TYPE_NIM_NOT_PRESENT instead
+ */
+enum nt_port_type_e {
+	NT_PORT_TYPE_NOT_AVAILABLE =
+		0, /* The NIM/port type is not available (unknown) */
+	NT_PORT_TYPE_NOT_RECOGNISED, /* The NIM/port type not recognized */
+	NT_PORT_TYPE_RJ45, /* RJ45 type */
+	NT_PORT_TYPE_SFP_NOT_PRESENT, /* SFP type but slot is empty */
+	NT_PORT_TYPE_SFP_SX, /* SFP SX */
+	NT_PORT_TYPE_SFP_SX_DD, /* SFP SX digital diagnostic */
+	NT_PORT_TYPE_SFP_LX, /* SFP LX */
+	NT_PORT_TYPE_SFP_LX_DD, /* SFP LX digital diagnostic */
+	NT_PORT_TYPE_SFP_ZX, /* SFP ZX */
+	NT_PORT_TYPE_SFP_ZX_DD, /* SFP ZX digital diagnostic */
+	NT_PORT_TYPE_SFP_CU, /* SFP copper */
+	NT_PORT_TYPE_SFP_CU_DD, /* SFP copper digital diagnostic */
+	NT_PORT_TYPE_SFP_NOT_RECOGNISED, /* SFP unknown */
+	NT_PORT_TYPE_XFP, /* XFP */
+	NT_PORT_TYPE_XPAK, /* XPAK */
+	NT_PORT_TYPE_SFP_CU_TRI_SPEED, /* SFP copper tri-speed */
+	NT_PORT_TYPE_SFP_CU_TRI_SPEED_DD, /* SFP copper tri-speed digital diagnostic */
+	NT_PORT_TYPE_SFP_PLUS, /* SFP+ type */
+	NT_PORT_TYPE_SFP_PLUS_NOT_PRESENT, /* SFP+ type but slot is empty */
+	NT_PORT_TYPE_XFP_NOT_PRESENT, /* XFP type but slot is empty */
+	NT_PORT_TYPE_QSFP_PLUS_NOT_PRESENT, /* QSFP type but slot is empty */
+	NT_PORT_TYPE_QSFP_PLUS, /* QSFP type */
+	NT_PORT_TYPE_SFP_PLUS_PASSIVE_DAC, /* SFP+ Passive DAC */
+	NT_PORT_TYPE_SFP_PLUS_ACTIVE_DAC, /* SFP+ Active DAC */
+	NT_PORT_TYPE_CFP4, /* CFP4 type */
+	NT_PORT_TYPE_CFP4_LR4 = NT_PORT_TYPE_CFP4, /* CFP4 100G, LR4 type */
+	NT_PORT_TYPE_CFP4_NOT_PRESENT, /* CFP4 type but slot is empty */
+	NT_PORT_TYPE_INITIALIZE, /* The port type is not fully established yet */
+	NT_PORT_TYPE_NIM_NOT_PRESENT, /* Generic "Not present" */
+	NT_PORT_TYPE_HCB, /* Test mode: Host Compliance Board */
+	NT_PORT_TYPE_NOT_SUPPORTED, /* The NIM type is not supported in this context */
+	NT_PORT_TYPE_SFP_PLUS_DUAL_RATE, /* SFP+ supports 1G/10G */
+	NT_PORT_TYPE_CFP4_SR4, /* CFP4 100G, SR4 type */
+	NT_PORT_TYPE_QSFP28_NOT_PRESENT, /* QSFP28 type but slot is empty */
+	NT_PORT_TYPE_QSFP28, /* QSFP28 type */
+	NT_PORT_TYPE_QSFP28_SR4, /* QSFP28-SR4 type */
+	NT_PORT_TYPE_QSFP28_LR4, /* QSFP28-LR4 type */
+	/* Deprecated. The port type should not mention speed eg 4x10 or 1x40 */
+	NT_PORT_TYPE_QSFP_PLUS_4X10,
+	/* Deprecated. The port type should not mention speed eg 4x10 or 1x40 */
+	NT_PORT_TYPE_QSFP_PASSIVE_DAC_4X10,
+	NT_PORT_TYPE_QSFP_PASSIVE_DAC =
+		NT_PORT_TYPE_QSFP_PASSIVE_DAC_4X10, /* QSFP passive DAC type */
+	/* Deprecated. The port type should not mention speed eg 4x10 or 1x40 */
+	NT_PORT_TYPE_QSFP_ACTIVE_DAC_4X10,
+	NT_PORT_TYPE_QSFP_ACTIVE_DAC =
+		NT_PORT_TYPE_QSFP_ACTIVE_DAC_4X10, /* QSFP active DAC type */
+	NT_PORT_TYPE_SFP_28, /* SFP28 type */
+	NT_PORT_TYPE_SFP_28_SR, /* SFP28-SR type */
+	NT_PORT_TYPE_SFP_28_LR, /* SFP28-LR type */
+	NT_PORT_TYPE_SFP_28_CR_CA_L, /* SFP28-CR-CA-L type */
+	NT_PORT_TYPE_SFP_28_CR_CA_S, /* SFP28-CR-CA-S type */
+	NT_PORT_TYPE_SFP_28_CR_CA_N, /* SFP28-CR-CA-N type */
+	NT_PORT_TYPE_QSFP28_CR_CA_L, /* QSFP28-CR-CA-L type */
+	NT_PORT_TYPE_QSFP28_CR_CA_S, /* QSFP28-CR-CA-S type */
+	NT_PORT_TYPE_QSFP28_CR_CA_N, /* QSFP28-CR-CA-N type */
+	NT_PORT_TYPE_SFP_28_SR_DR, /* SFP28-SR-DR type */
+	NT_PORT_TYPE_SFP_28_LR_DR, /* SFP28-LR-DR type */
+	NT_PORT_TYPE_SFP_FX, /* SFP FX */
+	NT_PORT_TYPE_SFP_PLUS_CU, /* SFP+ CU type */
+	/* QSFP28-FR type. Uses PAM4 modulation on one lane only */
+	NT_PORT_TYPE_QSFP28_FR,
+	/* QSFP28-DR type. Uses PAM4 modulation on one lane only */
+	NT_PORT_TYPE_QSFP28_DR,
+	/* QSFP28-LR type. Uses PAM4 modulation on one lane only */
+	NT_PORT_TYPE_QSFP28_LR,
+};
+
+typedef enum nt_port_type_e nt_port_type_t, *nt_port_type_p;
+
+#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..35c75f5e56
--- /dev/null
+++ b/drivers/net/ntnic/nim/nt_link_speed.c
@@ -0,0 +1,105 @@
+/* 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"
+
+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;
+}
+
+const char *nt_translate_link_speed_mask(uint32_t link_speed_mask, char *buffer,
+				      uint32_t length)
+{
+	size_t len = 0;
+
+	buffer[0] = 0;
+
+	for (int i = 0; i < 32; i++) {
+		if ((1U << i) & link_speed_mask) {
+			len = strlen(buffer);
+
+			if (len > 0) {
+				if ((length - len - 1) > 2) {
+					strncat(buffer, ", ", length);
+					len = strlen(buffer);
+				}
+			}
+
+			if (len < (length - 1))
+				strncat(buffer, nt_translate_link_speed(1 << i),
+					length);
+		}
+	}
+
+	return buffer;
+}
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..969e3fb867
--- /dev/null
+++ b/drivers/net/ntnic/nim/nt_link_speed.h
@@ -0,0 +1,34 @@
+/* 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>
+
+/*
+ * Link speed.
+ * Note this is a bitmask.
+ */
+enum nt_link_speed_e {
+	NT_LINK_SPEED_UNKNOWN = 0,
+	NT_LINK_SPEED_10M = 0x01, /* 10 Mbps */
+	NT_LINK_SPEED_100M = 0x02, /* 100 Mbps */
+	NT_LINK_SPEED_1G = 0x04, /* 1 Gbps  (Autoneg only) */
+	NT_LINK_SPEED_10G = 0x08, /* 10 Gbps (Autoneg only) */
+	NT_LINK_SPEED_40G = 0x10, /* 40 Gbps (Autoneg only) */
+	NT_LINK_SPEED_100G = 0x20, /* 100 Gbps (Autoneg only) */
+	NT_LINK_SPEED_50G = 0x40, /* 50 Gbps (Autoneg only) */
+	NT_LINK_SPEED_25G = 0x80, /* 25 Gbps (Autoneg only) */
+	NT_LINK_SPEED_END /* always keep this entry as the last in enum */
+};
+
+typedef enum nt_link_speed_e nt_link_speed_t;
+
+const char *nt_translate_link_speed(nt_link_speed_t link_speed);
+const char *nt_translate_link_speed_mask(uint32_t link_speed_mask, char *buffer,
+				      uint32_t length);
+uint64_t nt_get_link_speed(nt_link_speed_t e_link_speed);
+
+#endif /* NT_LINK_SPEED_H_ */
diff --git a/drivers/net/ntnic/nim/qsfp_registers.h b/drivers/net/ntnic/nim/qsfp_registers.h
new file mode 100644
index 0000000000..366dcbf06e
--- /dev/null
+++ b/drivers/net/ntnic/nim/qsfp_registers.h
@@ -0,0 +1,57 @@
+/* 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/qsfp_sensors.c b/drivers/net/ntnic/nim/qsfp_sensors.c
new file mode 100644
index 0000000000..8264f8fb62
--- /dev/null
+++ b/drivers/net/ntnic/nim/qsfp_sensors.c
@@ -0,0 +1,174 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <stdbool.h>
+
+#include "qsfp_sensors.h"
+
+#include "ntlog.h"
+#include "qsfp_registers.h"
+
+static bool qsfp_plus_nim_get_sensor(nim_i2c_ctx_p ctx, uint16_t addr,
+				   nim_option_t nim_option, uint8_t count,
+				   uint16_t *p_lane_values)
+{
+	(void)nim_option;
+
+	read_data_lin(ctx, addr, (uint16_t)(sizeof(uint16_t) * count),
+		    p_lane_values);
+
+	for (int i = 0; i < count; i++) {
+		*p_lane_values = (*p_lane_values); /* Swap to little endian */
+
+#ifdef NIM_DMI_TEST_VALUE
+		if (nim_option == NIM_OPTION_RX_POWER)
+			*p_lane_values = (uint16_t)NIM_DMI_RX_PWR_TEST_VALUE;
+		else
+			*p_lane_values = (uint16_t)NIM_DMI_TEST_VALUE;
+#endif
+
+		p_lane_values++;
+	}
+
+	return true;
+}
+
+/*
+ * Read NIM temperature
+ */
+static bool qsfp_plus_nim_get_temperature(nim_i2c_ctx_p ctx, int16_t *p_value)
+{
+	return qsfp_plus_nim_get_sensor(ctx, QSFP_TEMP_LIN_ADDR, NIM_OPTION_TEMP,
+				      1, (uint16_t *)p_value);
+}
+
+/*
+ * Read NIM supply voltage
+ */
+static bool qsfp_plus_nim_get_supply_voltage(nim_i2c_ctx_p ctx, uint16_t *p_value)
+{
+	return qsfp_plus_nim_get_sensor(ctx, QSFP_VOLT_LIN_ADDR,
+				      NIM_OPTION_SUPPLY, 1, p_value);
+}
+
+/*
+ * Read NIM bias current for four lanes
+ */
+static bool qsfp_plus_nim_get_tx_bias_current(nim_i2c_ctx_p ctx, uint16_t *p_value)
+{
+	return qsfp_plus_nim_get_sensor(ctx, QSFP_TX_BIAS_LIN_ADDR,
+				      NIM_OPTION_TX_BIAS, 4, p_value);
+}
+
+/*
+ * Read NIM TX optical power for four lanes
+ */
+static bool qsfp_plus_nim_get_tx_power(nim_i2c_ctx_p ctx, uint16_t *p_value)
+{
+	return qsfp_plus_nim_get_sensor(ctx, QSFP_TX_PWR_LIN_ADDR,
+				      NIM_OPTION_TX_POWER, 4, p_value);
+}
+
+/*
+ * Read NIM RX optical power for four lanes
+ */
+static bool qsfp_plus_nim_get_rx_power(nim_i2c_ctx_p ctx, uint16_t *p_value)
+{
+	return qsfp_plus_nim_get_sensor(ctx, QSFP_TX_PWR_LIN_ADDR,
+				      NIM_OPTION_RX_POWER, 4, p_value);
+}
+
+void nim_read_qsfp_temp(struct nim_sensor_group *sg, nthw_spis_t *t_spi)
+{
+	int16_t res;
+	(void)t_spi;
+
+	if (sg == NULL || sg->ctx == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: bad argument(s)\n", __func__);
+		return;
+	}
+
+	if (qsfp_plus_nim_get_temperature(sg->ctx, &res))
+		update_sensor_value(sg->sensor, (int)(res * 10 / 256));
+
+	else
+		update_sensor_value(sg->sensor, -1);
+}
+
+void nim_read_qsfp_voltage(struct nim_sensor_group *sg, nthw_spis_t *t_spi)
+{
+	uint16_t res;
+	(void)t_spi;
+
+	if (sg == NULL || sg->ctx == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: bad argument(s)\n", __func__);
+		return;
+	}
+
+	if (qsfp_plus_nim_get_supply_voltage(sg->ctx, &res))
+		update_sensor_value(sg->sensor, (int)((res) / 10));
+
+	else
+		update_sensor_value(sg->sensor, -1);
+}
+
+void nim_read_qsfp_bias_current(struct nim_sensor_group *sg, nthw_spis_t *t_spi)
+{
+	uint16_t temp[4] = { 0 };
+	(void)t_spi;
+
+	if (sg == NULL || sg->ctx == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: bad argument(s)\n", __func__);
+		return;
+	}
+
+	bool res = qsfp_plus_nim_get_tx_bias_current(sg->ctx, temp);
+
+	if (res) {
+		for (uint8_t i = 0; i < sg->ctx->lane_count; i++)
+			update_sensor_value(sg->sensor, (int)temp[i] * 2);
+	} else {
+		update_sensor_value(sg->sensor, -1);
+	}
+}
+
+void nim_read_qsfp_tx_power(struct nim_sensor_group *sg, nthw_spis_t *t_spi)
+{
+	uint16_t temp[4] = { 0 };
+	(void)t_spi;
+
+	if (sg == NULL || sg->ctx == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: bad argument(s)\n", __func__);
+		return;
+	}
+
+	bool res = qsfp_plus_nim_get_tx_power(sg->ctx, temp);
+
+	if (res) {
+		for (uint8_t i = 0; i < sg->ctx->lane_count; i++)
+			update_sensor_value(sg->sensor, (int)temp[i]);
+	} else {
+		update_sensor_value(sg->sensor, -1);
+	}
+}
+
+void nim_read_qsfp_rx_power(struct nim_sensor_group *sg, nthw_spis_t *t_spi)
+{
+	uint16_t temp[4] = { 0 };
+	(void)t_spi;
+
+	if (sg == NULL || sg->ctx == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: bad argument(s)\n", __func__);
+		return;
+	}
+
+	bool res = qsfp_plus_nim_get_rx_power(sg->ctx, temp);
+
+	if (res) {
+		for (uint8_t i = 0; i < sg->ctx->lane_count; i++)
+			update_sensor_value(sg->sensor, (int)temp[i]);
+	} else {
+		update_sensor_value(sg->sensor, -1);
+	}
+}
diff --git a/drivers/net/ntnic/nim/qsfp_sensors.h b/drivers/net/ntnic/nim/qsfp_sensors.h
new file mode 100644
index 0000000000..de64b978cb
--- /dev/null
+++ b/drivers/net/ntnic/nim/qsfp_sensors.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _QSFP_H
+#define _QSFP_H
+
+#include "sensors.h"
+#include "i2c_nim.h"
+
+/* Read functions */
+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);
+
+#endif /* _QSFP_H */
diff --git a/drivers/net/ntnic/nim/sfp_p_registers.h b/drivers/net/ntnic/nim/sfp_p_registers.h
new file mode 100644
index 0000000000..a0fbe2afd7
--- /dev/null
+++ b/drivers/net/ntnic/nim/sfp_p_registers.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _SFP_P_REG_H
+#define _SFP_P_REG_H
+
+/*
+ * SFP/SFP+ Registers
+ */
+#define SFP_GB_ETH_COMP_CODES_LIN_ADDR 6
+#define SFP_GB_ETH_COMP_1000BASET_BIT (1 << 3)
+#define SFP_GB_ETH_COMP_1000BASECX_BIT (1 << 2)
+#define SFP_GB_ETH_COMP_1000BASELX_BIT (1 << 1)
+#define SFP_GB_ETH_COMP_1000BASESX_BIT (1 << 0)
+
+#define SFP_FIBER_CHAN_TRANS_TECH_LIN_ADDR 8
+#define SFP_FIBER_CHAN_TRANS_TECH_ACTIVE_CU_BIT (1 << 3)
+#define SFP_FIBER_CHAN_TRANS_TECH_PASSIVE_CU_BIT (1 << 2)
+
+#define SFP_FIBER_CHAN_TRANS_MEDIA_LIN_ADDR 9
+#define SFP_FIBER_CHAN_TRANS_MEDIA_MM62_BIT (1 << 3)
+#define SFP_FIBER_CHAN_TRANS_MEDIA_MM50_BIT (1 << 2)
+#define SFP_FIBER_CHAN_TRANS_MEDIA_SM_BIT (1 << 0)
+
+#define SFP_CU_LINK_LEN_LIN_ADDR 18 /* 1byte */
+#define SFP_SUP_LEN_INFO_LIN_ADDR 14 /* 5bytes */
+#define SFP_CU_LINK_LEN_LIN_ADDR 18 /* 1byte */
+#define SFP_VENDOR_NAME_LIN_ADDR 20 /* 16bytes */
+#define SFP_VENDOR_PN_LIN_ADDR 40 /* 16bytes */
+#define SFP_VENDOR_REV_LIN_ADDR 56 /* 4bytes */
+#define SFP_VENDOR_SN_LIN_ADDR 68 /* 16bytes */
+#define SFP_VENDOR_DATE_LIN_ADDR 84 /* 8bytes */
+
+/* The following field is only relevant to SFP+ and is marked as reserved for SFP */
+#define SFP_OPTION0_LIN_ADDR 64
+#define SFP_POWER_LEVEL2_REQ_BIT (1 << 1)
+
+#define SFP_DMI_OPTION_LIN_ADDR (92)
+#define SFP_DMI_IMPL_BIT (1 << 6)
+#define SFP_DMI_EXT_CAL_BIT (1 << 4)
+#define SFP_DMI_AVG_PWR_BIT (1 << 3)
+#define SFP_DMI_ADDR_CHG_BIT (1 << 2)
+
+#define SFP_ENHANCED_OPTIONS_LIN_ADDR (93)
+#define SFP_SOFT_TX_FAULT_IMPL_BIT (1 << 5)
+#define SFP_SOFT_TX_DISABLE_IMPL_BIT (1 << 6)
+
+#define SFP_SFF8472_COMPLIANCE_LIN_ADDR 94
+
+#define SFP_TEMP_THRESH_LIN_ADDR (0 + 256)
+/* 8bytes: HighAlarm, LowAlarm, HighWarn, LowWarn each 2 bytes */
+
+#define SFP_VOLT_THRESH_LIN_ADDR (8 + 256)
+/* 8bytes: HighAlarm, LowAlarm, HighWarn, LowWarn each 2 bytes */
+
+#define SFP_TX_BIAS_THRESH_LIN_ADDR (16 + 256)
+/* 8bytes: HighAlarm, LowAlarm, HighWarn, LowWarn each 2 bytes */
+
+#define SFP_TX_PWR_THRESH_LIN_ADDR (24 + 256)
+/* 8bytes: HighAlarm, LowAlarm, HighWarn, LowWarn each 2 bytes */
+
+#define SFP_RX_PWR_THRESH_LIN_ADDR (32 + 256)
+/* 8bytes: HighAlarm, LowAlarm, HighWarn, LowWarn each 2 bytes */
+
+/* Calibration data addresses */
+#define SFP_RX_PWR_COEFF_LIN_ADDR (56 + 256) /* 5 x 32bit float  values */
+
+#define SFP_TX_BIAS_SLOPE_LIN_ADDR (76 + 256)
+#define SFP_TX_BIAS_OFFSET_LIN_ADDR (78 + 256)
+
+#define SFP_TX_PWR_SLOPE_LIN_ADDR (80 + 256)
+#define SFP_TX_PWR_OFFSET_LIN_ADDR (82 + 256)
+
+#define SFP_TEMP_SLOPE_LIN_ADDR (84 + 256)
+#define SFP_TEMP_OFFSET_LIN_ADDR (86 + 256)
+
+#define SFP_VOLT_SLOPE_LIN_ADDR (88 + 256)
+#define SFP_VOLT_OFFSET_LIN_ADDR (90 + 256)
+
+/* Live data */
+#define SFP_TEMP_LIN_ADDR (96 + 256)
+#define SFP_VOLT_LIN_ADDR (98 + 256)
+#define SFP_TX_BIAS_LIN_ADDR (100 + 256)
+#define SFP_TX_PWR_LIN_ADDR (102 + 256)
+#define SFP_RX_PWR_LIN_ADDR (104 + 256)
+
+#define SFP_SOFT_RATE0_BIT (1 << 3)
+#define SFP_TX_FAULT_SET_BIT (1 << 2)
+
+#define SFP_EXT_CTRL_STAT0_LIN_ADDR (118 + 256) /* 0xA2 area */
+#define SFP_SOFT_RATE1_BIT (1 << 3)
+#define SFP_POWER_LEVEL2_GET_BIT (1 << 1) /* For reading the actual level */
+#define SFP_POWER_LEVEL2_SET_BIT (1 << 0) /* For setting the wanted level */
+
+/* PHY addresses */
+#define SFP_PHY_LIN_ADDR (12 * 128)
+#define SFP_PHY_LIN_RNG 32 /* 16bit words */
+
+#endif /* _SFP_P_REG_H */
diff --git a/drivers/net/ntnic/nim/sfp_sensors.c b/drivers/net/ntnic/nim/sfp_sensors.c
new file mode 100644
index 0000000000..766d6feaf3
--- /dev/null
+++ b/drivers/net/ntnic/nim/sfp_sensors.c
@@ -0,0 +1,288 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <arpa/inet.h>
+#include <stdbool.h>
+
+#include "ntlog.h"
+#include "sfp_sensors.h"
+
+#include "sfp_p_registers.h"
+
+/*
+ * Return calibrated data from an SFP module.
+ * It is first investigated if external calibration is to be used and if it is
+ * calibration data is retrieved. The function can only be used when calibration
+ * consists of a slope and offset factor. After function return p_data will point
+ * to 16bit data that can be either signed or unsigned.
+ */
+static bool sfp_nim_get_dmi_data(uint16_t data_addr, uint16_t slope_addr,
+			       uint16_t offset_addr, void *p_value,
+			       bool signed_data, nim_i2c_ctx_p ctx)
+{
+	int32_t value;
+	uint16_t slope = 1;
+	int16_t offset = 0;
+
+	if (!ctx->dmi_supp)
+		return false;
+
+	/* Read data in big endian format */
+	read_data_lin(ctx, data_addr, 2, p_value);
+	*(uint16_t *)p_value =
+		htons(*(uint16_t *)p_value); /* Swap to little endian */
+
+	/*
+	 * Inject test value which can be both signed and unsigned but handle
+	 * here as unsigned
+	 */
+#ifdef NIM_DMI_TEST_VALUE
+	*(uint16_t *)p_value = (uint16_t)NIM_DMI_TEST_VALUE;
+#endif
+
+#if defined(NIM_DMI_TEST_SLOPE) || defined(NIM_DMI_TEST_OFFSET)
+	ctx->specific_u.sfp.ext_cal = true;
+#endif
+
+	if (ctx->specific_u.sfp.ext_cal) {
+		/* External calibration is needed */
+		read_data_lin(ctx, slope_addr, sizeof(slope), &slope);
+		read_data_lin(ctx, offset_addr, sizeof(offset), &offset);
+
+		/* Swap calibration to little endian */
+		slope = htons(slope);
+		offset = htons(offset);
+
+#ifdef NIM_DMI_TEST_SLOPE
+		slope = NIM_DMI_TEST_SLOPE;
+#endif
+
+#ifdef NIM_DMI_TEST_OFFSET
+		offset = NIM_DMI_TEST_OFFSET; /* 0x0140 equals 1.25 */
+#endif
+
+		if (signed_data) {
+			value = *(int16_t *)p_value * slope / 256 + offset;
+
+			if (value > INT16_MAX)
+				value = INT16_MAX;
+			else if (value < INT16_MIN)
+				value = INT16_MIN;
+
+			*(int16_t *)p_value = (int16_t)value;
+		} else {
+			value = *(uint16_t *)p_value * slope / 256 + offset;
+
+			if (value > UINT16_MAX)
+				value = UINT16_MAX;
+			else if (value < 0)
+				value = 0;
+
+			*(uint16_t *)p_value = (uint16_t)value;
+		}
+	}
+
+	return true;
+}
+
+/*
+ * Read NIM temperature
+ */
+static bool sfp_nim_get_temperature(nim_i2c_ctx_p ctx, int16_t *p_value)
+{
+	return sfp_nim_get_dmi_data(SFP_TEMP_LIN_ADDR, SFP_TEMP_SLOPE_LIN_ADDR,
+				  SFP_TEMP_OFFSET_LIN_ADDR, p_value, true, ctx);
+}
+
+/*
+ * Read NIM supply voltage
+ */
+static bool sfp_nim_get_supply_voltage(nim_i2c_ctx_p ctx, uint16_t *p_value)
+{
+	return sfp_nim_get_dmi_data(SFP_VOLT_LIN_ADDR, SFP_VOLT_SLOPE_LIN_ADDR,
+				  SFP_VOLT_OFFSET_LIN_ADDR, p_value, false, ctx);
+}
+
+/*
+ * Read NIM bias current
+ */
+static bool sfp_nim_get_tx_bias_current(nim_i2c_ctx_p ctx, uint16_t *p_value)
+{
+	return sfp_nim_get_dmi_data(SFP_TX_BIAS_LIN_ADDR,
+				  SFP_TX_BIAS_SLOPE_LIN_ADDR,
+				  SFP_TX_BIAS_OFFSET_LIN_ADDR, p_value, false,
+				  ctx);
+}
+
+/*
+ * Read NIM TX optical power
+ */
+static bool sfp_nim_get_tx_power(nim_i2c_ctx_p ctx, uint16_t *p_value)
+{
+	return sfp_nim_get_dmi_data(SFP_TX_PWR_LIN_ADDR,
+				  SFP_TX_PWR_SLOPE_LIN_ADDR,
+				  SFP_TX_PWR_OFFSET_LIN_ADDR, p_value, false,
+				  ctx);
+}
+
+/*
+ * Return the SFP received power in units of 0.1uW from DMI data.
+ * If external calibration is necessary, the calibration data is retrieved and
+ * the calibration is carried out.
+ */
+static bool sfp_nim_get_calibrated_rx_power(nim_i2c_ctx_p ctx, uint16_t addr,
+		uint16_t *p_value)
+{
+	float rx_pwr_cal[5];
+	float power_raised;
+	float rx_power;
+
+	/* Read data in big endian format */
+	read_data_lin(ctx, addr, sizeof(*p_value), p_value);
+	*(uint16_t *)p_value =
+		htons(*(uint16_t *)p_value); /* Swap to little endian */
+
+#ifdef NIM_DMI_RX_PWR_TEST_VALUE
+	*p_value = NIM_DMI_RX_PWR_TEST_VALUE;
+#endif
+
+#ifdef NIM_DMI_RX_PWR_CAL_DATA
+	ctx->specific_u.sfp.ext_cal = true;
+#endif
+
+	if (ctx->specific_u.sfp.ext_cal) {
+		/* Read calibration data in big endian format */
+		read_data_lin(ctx, SFP_RX_PWR_COEFF_LIN_ADDR, sizeof(rx_pwr_cal),
+			    rx_pwr_cal);
+
+		for (int i = 0; i < 5; i++) {
+			uint32_t *p_val = (uint32_t *)&rx_pwr_cal[i];
+			*p_val = ntohl(*p_val); /* 32 bit swap */
+		}
+
+#ifdef NIM_DMI_RX_PWR_CAL_DATA
+		/* Testdata for verification */
+		NIM_DMI_RX_PWR_CAL_DATA
+#endif
+
+		/*
+		 * If SFP module specifies external calibration - use calibration data
+		 * according to the polynomial correction formula
+		 * RxPwrCal = Coeff0 + Coeff1 * RxPwr   + Coeff2 * RxPwr^2 +
+		 *                     Coeff3 * RxPwr^3 + Coeff4 * RxPwr^4
+		 */
+		power_raised = 1.0;
+		rx_power = rx_pwr_cal[4]; /* Coeff0 */
+
+		for (int i = 3; i >= 0; i--) {
+			power_raised *= (float)*p_value;
+			rx_power += rx_pwr_cal[i] * power_raised;
+		}
+
+		/* Check out for out of range */
+		if (rx_power > 65535)
+			return false;
+
+		if (rx_power < 0)
+			*p_value = 0;
+		else
+			*p_value = (uint16_t)rx_power;
+	}
+
+	return true;
+}
+
+/*
+ * Read RX optical power if it exists
+ */
+static bool sfp_nim_get_rx_power(nim_i2c_ctx_p ctx, uint16_t *p_value)
+{
+	return sfp_nim_get_calibrated_rx_power(ctx, SFP_RX_PWR_LIN_ADDR, p_value);
+}
+
+void nim_read_sfp_temp(struct nim_sensor_group *sg, nthw_spis_t *t_spi)
+{
+	int16_t temp;
+	(void)t_spi;
+
+	if (sg == NULL || sg->ctx == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: bad argument(s)\n", __func__);
+		return;
+	}
+
+	if (sfp_nim_get_temperature(sg->ctx, &temp))
+		update_sensor_value(sg->sensor, (int)(temp * 10 / 256));
+
+	else
+		update_sensor_value(sg->sensor, -1);
+}
+
+void nim_read_sfp_voltage(struct nim_sensor_group *sg, nthw_spis_t *t_spi)
+{
+	uint16_t temp;
+	(void)t_spi;
+
+	if (sg == NULL || sg->ctx == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: bad argument(s)\n", __func__);
+		return;
+	}
+
+	if (sfp_nim_get_supply_voltage(sg->ctx, &temp)) {
+		update_sensor_value(sg->sensor,
+				    (int)(temp / 10)); /* Unit: 100uV -> 1mV */
+	} else {
+		update_sensor_value(sg->sensor, -1);
+	}
+}
+
+void nim_read_sfp_bias_current(struct nim_sensor_group *sg, nthw_spis_t *t_spi)
+{
+	uint16_t temp;
+	(void)t_spi;
+
+	if (sg == NULL || sg->ctx == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: bad argument(s)\n", __func__);
+		return;
+	}
+
+	if (sfp_nim_get_tx_bias_current(sg->ctx, &temp))
+		update_sensor_value(sg->sensor, (int)(temp * 2));
+
+	else
+		update_sensor_value(sg->sensor, -1);
+}
+
+void nim_read_sfp_tx_power(struct nim_sensor_group *sg, nthw_spis_t *t_spi)
+{
+	uint16_t temp;
+	(void)t_spi;
+
+	if (sg == NULL || sg->ctx == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: bad argument(s)\n", __func__);
+		return;
+	}
+
+	if (sfp_nim_get_tx_power(sg->ctx, &temp))
+		update_sensor_value(sg->sensor, (int)temp);
+
+	else
+		update_sensor_value(sg->sensor, -1);
+}
+
+void nim_read_sfp_rx_power(struct nim_sensor_group *sg, nthw_spis_t *t_spi)
+{
+	uint16_t temp;
+	(void)t_spi;
+
+	if (sg == NULL || sg->ctx == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: bad argument(s)\n", __func__);
+		return;
+	}
+
+	if (sfp_nim_get_rx_power(sg->ctx, &temp))
+		update_sensor_value(sg->sensor, (int)temp);
+
+	else
+		update_sensor_value(sg->sensor, -1);
+}
diff --git a/drivers/net/ntnic/nim/sfp_sensors.h b/drivers/net/ntnic/nim/sfp_sensors.h
new file mode 100644
index 0000000000..ab56027dc8
--- /dev/null
+++ b/drivers/net/ntnic/nim/sfp_sensors.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _SFP_H
+#define _SFP_H
+
+#include "sensors.h"
+#include "i2c_nim.h"
+
+/* Read functions */
+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);
+
+#endif /* _SFP_H */
diff --git a/drivers/net/ntnic/nthw/core/nthw_clock_profiles.c b/drivers/net/ntnic/nthw/core/nthw_clock_profiles.c
index efdcc222a8..bd7cd2a27c 100644
--- a/drivers/net/ntnic/nthw/core/nthw_clock_profiles.c
+++ b/drivers/net/ntnic/nthw/core/nthw_clock_profiles.c
@@ -5,5 +5,12 @@
 #include "nthw_clock_profiles.h"
 
 /* Clock profile for NT200A02 2x40G, 2x100G */
-const int n_data_si5340_nt200a02_u23_v5;
-const clk_profile_data_fmt2_t *p_data_si5340_nt200a02_u23_v5;
+#define si5340_revd_register_t type_si5340_nt200a02_u23_v5
+#define si5340_revd_registers data_si5340_nt200a02_u23_v5
+#include "nthw_nt200a02_u23_si5340_v5.h"
+const int n_data_si5340_nt200a02_u23_v5 = SI5340_REVD_REG_CONFIG_NUM_REGS;
+const clk_profile_data_fmt2_t *p_data_si5340_nt200a02_u23_v5 =
+	(const clk_profile_data_fmt2_t *)&data_si5340_nt200a02_u23_v5[0];
+#undef si5340_revd_registers
+#undef si5340_revd_register_t
+#undef SI5340_REVD_REG_CONFIG_HEADER /*Disable the include once protection */
diff --git a/drivers/net/ntnic/nthw/core/nthw_core.h b/drivers/net/ntnic/nthw/core/nthw_core.h
index 798a95d5cf..025b6b61cc 100644
--- a/drivers/net/ntnic/nthw/core/nthw_core.h
+++ b/drivers/net/ntnic/nthw/core/nthw_core.h
@@ -16,9 +16,11 @@
 #include "nthw_pci_ta.h"
 #include "nthw_iic.h"
 
+#include "nthw_gmf.h"
 #include "nthw_gpio_phy.h"
 #include "nthw_mac_pcs.h"
 #include "nthw_mac_pcs_xxv.h"
+#include "nthw_rmc.h"
 #include "nthw_sdc.h"
 
 #include "nthw_spim.h"
diff --git a/drivers/net/ntnic/nthw/core/nthw_gmf.c b/drivers/net/ntnic/nthw/core/nthw_gmf.c
new file mode 100644
index 0000000000..fe63c461e5
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_gmf.c
@@ -0,0 +1,290 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <limits.h>
+#include <math.h>
+#include "ntlog.h"
+
+#include "nthw_drv.h"
+#include "nthw_register.h"
+
+#include "nthw_gmf.h"
+
+nthw_gmf_t *nthw_gmf_new(void)
+{
+	nthw_gmf_t *p = malloc(sizeof(nthw_gmf_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_gmf_t));
+	return p;
+}
+
+void nthw_gmf_delete(nthw_gmf_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_gmf_t));
+		free(p);
+	}
+}
+
+int nthw_gmf_init(nthw_gmf_t *p, nt_fpga_t *p_fpga, int n_instance)
+{
+	nt_module_t *mod = fpga_query_module(p_fpga, MOD_GMF, n_instance);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: GMF %d: no such instance\n",
+		       p_fpga->p_fpga_info->mp_adapter_id_str, n_instance);
+		return -1;
+	}
+
+	p->mp_fpga = p_fpga;
+	p->mn_instance = n_instance;
+	p->mp_mod_gmf = mod;
+
+	p->mp_ctrl = module_get_register(p->mp_mod_gmf, GMF_CTRL);
+	p->mp_ctrl_enable = register_get_field(p->mp_ctrl, GMF_CTRL_ENABLE);
+	p->mp_ctrl_ifg_enable = register_get_field(p->mp_ctrl, GMF_CTRL_IFG_ENABLE);
+	p->mp_ctrl_ifg_auto_adjust_enable =
+		register_get_field(p->mp_ctrl, GMF_CTRL_IFG_AUTO_ADJUST_ENABLE);
+
+	p->mp_speed = module_get_register(p->mp_mod_gmf, GMF_SPEED);
+	p->mp_speed_ifg_speed = register_get_field(p->mp_speed, GMF_SPEED_IFG_SPEED);
+
+	p->mp_ifg_clock_delta =
+		module_get_register(p->mp_mod_gmf, GMF_IFG_SET_CLOCK_DELTA);
+	p->mp_ifg_clock_delta_delta =
+		register_get_field(p->mp_ifg_clock_delta, GMF_IFG_SET_CLOCK_DELTA_DELTA);
+
+	p->mp_ifg_max_adjust_slack =
+		module_get_register(p->mp_mod_gmf, GMF_IFG_MAX_ADJUST_SLACK);
+	p->mp_ifg_max_adjust_slack_slack =
+		register_get_field(p->mp_ifg_max_adjust_slack, GMF_IFG_MAX_ADJUST_SLACK_SLACK);
+
+	p->mp_debug_lane_marker =
+		module_get_register(p->mp_mod_gmf, GMF_DEBUG_LANE_MARKER);
+	p->mp_debug_lane_marker_compensation =
+		register_get_field(p->mp_debug_lane_marker, GMF_DEBUG_LANE_MARKER_COMPENSATION);
+
+	p->mp_stat_sticky = module_get_register(p->mp_mod_gmf, GMF_STAT_STICKY);
+	p->mp_stat_sticky_data_underflowed =
+		register_get_field(p->mp_stat_sticky, GMF_STAT_STICKY_DATA_UNDERFLOWED);
+	p->mp_stat_sticky_ifg_adjusted =
+		register_get_field(p->mp_stat_sticky, GMF_STAT_STICKY_IFG_ADJUSTED);
+
+	p->mn_param_gmf_ifg_speed_mul =
+		fpga_get_product_param(p_fpga, NT_GMF_IFG_SPEED_MUL, 1);
+	p->mn_param_gmf_ifg_speed_div =
+		fpga_get_product_param(p_fpga, NT_GMF_IFG_SPEED_DIV, 1);
+
+	p->m_administrative_block = false;
+
+	p->mp_stat_next_pkt = module_query_register(p->mp_mod_gmf, GMF_STAT_NEXT_PKT);
+	if (p->mp_stat_next_pkt) {
+		p->mp_stat_next_pkt_ns =
+			register_query_field(p->mp_stat_next_pkt,
+					     GMF_STAT_NEXT_PKT_NS);
+	} else {
+		p->mp_stat_next_pkt_ns = NULL;
+	}
+	p->mp_stat_max_delayed_pkt =
+		module_query_register(p->mp_mod_gmf, GMF_STAT_MAX_DELAYED_PKT);
+	if (p->mp_stat_max_delayed_pkt) {
+		p->mp_stat_max_delayed_pkt_ns =
+			register_query_field(p->mp_stat_max_delayed_pkt,
+					     GMF_STAT_MAX_DELAYED_PKT_NS);
+	} else {
+		p->mp_stat_max_delayed_pkt_ns = NULL;
+	}
+	p->mp_ctrl_ifg_tx_now_always =
+		register_query_field(p->mp_ctrl, GMF_CTRL_IFG_TX_NOW_ALWAYS);
+	p->mp_ctrl_ifg_tx_on_ts_always =
+		register_query_field(p->mp_ctrl, GMF_CTRL_IFG_TX_ON_TS_ALWAYS);
+
+	p->mp_ctrl_ifg_tx_on_ts_adjust_on_set_clock =
+		register_query_field(p->mp_ctrl, GMF_CTRL_IFG_TX_ON_TS_ADJUST_ON_SET_CLOCK);
+
+	p->mp_ifg_clock_delta_adjust =
+		module_query_register(p->mp_mod_gmf, GMF_IFG_SET_CLOCK_DELTA_ADJUST);
+	if (p->mp_ifg_clock_delta_adjust) {
+		p->mp_ifg_clock_delta_adjust_delta =
+			register_query_field(p->mp_ifg_clock_delta_adjust,
+					     GMF_IFG_SET_CLOCK_DELTA_ADJUST_DELTA);
+	} else {
+		p->mp_ifg_clock_delta_adjust_delta = NULL;
+	}
+	return 0;
+}
+
+void nthw_gmf_set_enable(nthw_gmf_t *p, bool enable)
+{
+	if (!p->m_administrative_block)
+		field_set_val_flush32(p->mp_ctrl_enable, enable ? 1 : 0);
+}
+
+void nthw_gmf_set_ifg_enable(nthw_gmf_t *p, bool enable)
+{
+	field_set_val_flush32(p->mp_ctrl_ifg_enable, enable ? 1 : 0);
+}
+
+void nthw_gmf_set_tx_now_always_enable(nthw_gmf_t *p, bool enable)
+{
+	if (p->mp_ctrl_ifg_tx_now_always)
+		field_set_val_flush32(p->mp_ctrl_ifg_tx_now_always, enable ? 1 : 0);
+}
+
+void nthw_gmf_set_tx_on_ts_always_enable(nthw_gmf_t *p, bool enable)
+{
+	if (p->mp_ctrl_ifg_tx_on_ts_always)
+		field_set_val_flush32(p->mp_ctrl_ifg_tx_on_ts_always, enable ? 1 : 0);
+}
+
+void nthw_gmf_set_tx_on_ts_adjust_on_set_clock(nthw_gmf_t *p, bool enable)
+{
+	if (p->mp_ctrl_ifg_tx_on_ts_adjust_on_set_clock) {
+		field_set_val_flush32(p->mp_ctrl_ifg_tx_on_ts_adjust_on_set_clock,
+				    enable ? 1 : 0);
+	}
+}
+
+void nthw_gmf_set_ifg_auto_adjust_enable(nthw_gmf_t *p, bool enable)
+{
+	field_set_val_flush32(p->mp_ctrl_ifg_auto_adjust_enable, enable);
+}
+
+int nthw_gmf_set_ifg_speed_raw(nthw_gmf_t *p, uint64_t n_speed_val)
+{
+	if (n_speed_val <=
+			(1ULL << (field_get_bit_width(p->mp_speed_ifg_speed) - 1))) {
+		field_set_val(p->mp_speed_ifg_speed, (uint32_t *)&n_speed_val,
+			     (field_get_bit_width(p->mp_speed_ifg_speed) <= 32 ? 1 :
+			      2));
+		field_flush_register(p->mp_speed_ifg_speed);
+		return 0;
+	}
+	return -1;
+}
+
+int nthw_gmf_get_ifg_speed_bit_width(nthw_gmf_t *p)
+{
+	const int n_bit_width = field_get_bit_width(p->mp_speed_ifg_speed);
+
+	assert(n_bit_width >=
+	       22); /* Sanity check: GMF ver 1.2 is bw 22 - GMF ver 1.3 is bw 64 */
+	return n_bit_width;
+}
+
+int nthw_gmf_set_ifg_speed_bits(nthw_gmf_t *p, const uint64_t n_rate_limit_bits,
+			    const uint64_t n_link_speed)
+{
+	const int n_bit_width = (nthw_gmf_get_ifg_speed_bit_width(p) / 2);
+	const double f_adj_rate =
+		((double)((((double)n_rate_limit_bits) / (double)n_link_speed) *
+			  p->mn_param_gmf_ifg_speed_mul) /
+		 p->mn_param_gmf_ifg_speed_div);
+	const double f_speed = ((1UL / f_adj_rate) - 1) * exp2(n_bit_width);
+	uint64_t n_speed_val = (uint64_t)round(f_speed);
+
+	return nthw_gmf_set_ifg_speed_raw(p, n_speed_val);
+}
+
+int nthw_gmf_set_ifg_speed_percent(nthw_gmf_t *p, const double f_rate_limit_percent)
+{
+	uint64_t n_speed_val;
+
+	if (f_rate_limit_percent == 0.0 || f_rate_limit_percent == 100.0) {
+		n_speed_val = 0;
+	} else if (f_rate_limit_percent <= 99) {
+		const int n_bit_width = (nthw_gmf_get_ifg_speed_bit_width(p) / 2);
+		const double f_adj_rate =
+			((double)(f_rate_limit_percent *
+				  (double)p->mn_param_gmf_ifg_speed_mul) /
+			 p->mn_param_gmf_ifg_speed_div / 100);
+		const double f_speed = ((1UL / f_adj_rate) - 1) * exp2(n_bit_width);
+
+		n_speed_val = (uint64_t)f_speed;
+	} else {
+		return -1;
+	}
+
+	return nthw_gmf_set_ifg_speed_raw(p, n_speed_val);
+}
+
+void nthw_gmf_set_delta(nthw_gmf_t *p, uint64_t delta)
+{
+	field_set_val(p->mp_ifg_clock_delta_delta, (uint32_t *)&delta, 2);
+	field_flush_register(p->mp_ifg_clock_delta_delta);
+}
+
+void nthw_gmf_set_delta_adjust(nthw_gmf_t *p, uint64_t delta_adjust)
+{
+	if (p->mp_ifg_clock_delta_adjust) {
+		field_set_val(p->mp_ifg_clock_delta_adjust_delta,
+			     (uint32_t *)&delta_adjust, 2);
+		field_flush_register(p->mp_ifg_clock_delta_adjust_delta);
+	}
+}
+
+void nthw_gmf_set_slack(nthw_gmf_t *p, uint64_t slack)
+{
+	field_set_val(p->mp_ifg_max_adjust_slack_slack, (uint32_t *)&slack, 2);
+	field_flush_register(p->mp_ifg_max_adjust_slack_slack);
+}
+
+void nthw_gmf_set_compensation(nthw_gmf_t *p, uint32_t compensation)
+{
+	field_set_val_flush32(p->mp_debug_lane_marker_compensation, compensation);
+}
+
+uint32_t nthw_gmf_get_status_sticky(nthw_gmf_t *p)
+{
+	uint32_t status = 0;
+
+	register_update(p->mp_stat_sticky);
+
+	if (field_get_val32(p->mp_stat_sticky_data_underflowed))
+		status |= GMF_STATUS_MASK_DATA_UNDERFLOWED;
+	if (field_get_val32(p->mp_stat_sticky_ifg_adjusted))
+		status |= GMF_STATUS_MASK_IFG_ADJUSTED;
+
+	return status;
+}
+
+void nthw_gmf_set_status_sticky(nthw_gmf_t *p, uint32_t status)
+{
+	if (status & GMF_STATUS_MASK_DATA_UNDERFLOWED)
+		field_set_flush(p->mp_stat_sticky_data_underflowed);
+	if (status & GMF_STATUS_MASK_IFG_ADJUSTED)
+		field_set_flush(p->mp_stat_sticky_ifg_adjusted);
+}
+
+uint64_t nthw_gmf_get_stat_next_pkt_ns(nthw_gmf_t *p)
+{
+	uint64_t value = ULONG_MAX;
+
+	if (p->mp_stat_next_pkt) {
+		register_update(p->mp_stat_next_pkt);
+		field_get_val(p->mp_stat_next_pkt_ns, (uint32_t *)&value, 2);
+	}
+	return value;
+}
+
+uint64_t nthw_gmf_get_stat_max_pk_delayedt_ns(nthw_gmf_t *p)
+{
+	uint64_t value = ULONG_MAX;
+
+	if (p->mp_stat_max_delayed_pkt) {
+		register_update(p->mp_stat_max_delayed_pkt);
+		field_get_val(p->mp_stat_max_delayed_pkt_ns, (uint32_t *)&value, 2);
+	}
+	return value;
+}
+
+void nthw_gmf_administrative_block(nthw_gmf_t *p)
+{
+	nthw_gmf_set_enable(p, false);
+	p->m_administrative_block = true;
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_gmf.h b/drivers/net/ntnic/nthw/core/nthw_gmf.h
new file mode 100644
index 0000000000..aec1342be7
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_gmf.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_GMF_H__
+#define __NTHW_GMF_H__
+
+enum gmf_status_mask {
+	GMF_STATUS_MASK_DATA_UNDERFLOWED = 1,
+	GMF_STATUS_MASK_IFG_ADJUSTED
+};
+
+struct nthw_gmf {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_gmf;
+	int mn_instance;
+	/*  */
+
+	nt_register_t *mp_ctrl;
+	nt_field_t *mp_ctrl_enable;
+	nt_field_t *mp_ctrl_ifg_enable;
+	nt_field_t *mp_ctrl_ifg_tx_now_always;
+	nt_field_t *mp_ctrl_ifg_tx_on_ts_always;
+	nt_field_t *mp_ctrl_ifg_tx_on_ts_adjust_on_set_clock;
+	nt_field_t *mp_ctrl_ifg_auto_adjust_enable;
+
+	nt_register_t *mp_speed;
+	nt_field_t *mp_speed_ifg_speed;
+
+	nt_register_t *mp_ifg_clock_delta;
+	nt_field_t *mp_ifg_clock_delta_delta;
+
+	nt_register_t *mp_ifg_clock_delta_adjust;
+	nt_field_t *mp_ifg_clock_delta_adjust_delta;
+
+	nt_register_t *mp_ifg_max_adjust_slack;
+	nt_field_t *mp_ifg_max_adjust_slack_slack;
+
+	nt_register_t *mp_debug_lane_marker;
+	nt_field_t *mp_debug_lane_marker_compensation;
+
+	nt_register_t *mp_stat_sticky;
+	nt_field_t *mp_stat_sticky_data_underflowed;
+	nt_field_t *mp_stat_sticky_ifg_adjusted;
+
+	nt_register_t *mp_stat_next_pkt;
+	nt_field_t *mp_stat_next_pkt_ns;
+
+	nt_register_t *mp_stat_max_delayed_pkt;
+	nt_field_t *mp_stat_max_delayed_pkt_ns;
+
+	int mn_param_gmf_ifg_speed_mul;
+	int mn_param_gmf_ifg_speed_div;
+
+	bool m_administrative_block; /* Used to enforce license expiry */
+};
+
+typedef struct nthw_gmf nthw_gmf_t;
+typedef struct nthw_gmf nthw_gmf;
+
+nthw_gmf_t *nthw_gmf_new(void);
+void nthw_gmf_delete(nthw_gmf_t *p);
+int nthw_gmf_init(nthw_gmf_t *p, nt_fpga_t *p_fpga, int n_instance);
+
+void nthw_gmf_set_enable(nthw_gmf_t *p, bool enable);
+void nthw_gmf_set_ifg_enable(nthw_gmf_t *p, bool enable);
+
+void nthw_gmf_set_tx_now_always_enable(nthw_gmf_t *p, bool enable);
+void nthw_gmf_set_tx_on_ts_always_enable(nthw_gmf_t *p, bool enable);
+void nthw_gmf_set_tx_on_ts_adjust_on_set_clock(nthw_gmf_t *p, bool enable);
+void nthw_gmf_set_ifg_auto_adjust_enable(nthw_gmf_t *p, bool enable);
+
+int nthw_gmf_get_ifg_speed_bit_width(nthw_gmf_t *p);
+
+int nthw_gmf_set_ifg_speed_raw(nthw_gmf_t *p, uint64_t n_speed_val);
+int nthw_gmf_set_ifg_speed_bits(nthw_gmf_t *p, const uint64_t n_rate_limit_bits,
+			    const uint64_t n_link_speed);
+int nthw_gmf_set_ifg_speed_percent(nthw_gmf_t *p, const double f_rate_limit_percent);
+
+void nthw_gmf_set_delta(nthw_gmf_t *p, uint64_t delta);
+void nthw_gmf_set_delta_adjust(nthw_gmf_t *p, uint64_t delta_adjust);
+void nthw_gmf_set_slack(nthw_gmf_t *p, uint64_t slack);
+void nthw_gmf_set_compensation(nthw_gmf_t *p, uint32_t compensation);
+
+uint32_t nthw_gmf_get_status_sticky(nthw_gmf_t *p);
+void nthw_gmf_set_status_sticky(nthw_gmf_t *p, uint32_t status);
+
+uint64_t nthw_gmf_get_stat_next_pkt_ns(nthw_gmf_t *p);
+uint64_t nthw_gmf_get_stat_max_pk_delayedt_ns(nthw_gmf_t *p);
+
+void nthw_gmf_administrative_block(nthw_gmf_t *p); /* Used to enforce license expiry blocking */
+
+#endif /* __NTHW_GMF_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_nt200a02_u23_si5340_v5.h b/drivers/net/ntnic/nthw/core/nthw_nt200a02_u23_si5340_v5.h
new file mode 100644
index 0000000000..f063a1048a
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_nt200a02_u23_si5340_v5.h
@@ -0,0 +1,344 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef SI5340_REVD_REG_CONFIG_HEADER
+#define SI5340_REVD_REG_CONFIG_HEADER
+
+#define SI5340_REVD_REG_CONFIG_NUM_REGS 326
+
+typedef struct {
+	unsigned int address; /* 16-bit register address */
+	unsigned char value; /* 8-bit register data */
+} si5340_revd_register_t;
+
+si5340_revd_register_t const si5340_revd_registers[SI5340_REVD_REG_CONFIG_NUM_REGS] = {
+	{ 0x0B24, 0xC0 },
+	{ 0x0B25, 0x00 },
+	{ 0x0502, 0x01 },
+	{ 0x0505, 0x03 },
+	{ 0x0957, 0x17 },
+	{ 0x0B4E, 0x1A },
+	{ 0x0006, 0x00 },
+	{ 0x0007, 0x00 },
+	{ 0x0008, 0x00 },
+	{ 0x000B, 0x74 },
+	{ 0x0017, 0xF0 },
+	{ 0x0018, 0xFF },
+	{ 0x0021, 0x0F },
+	{ 0x0022, 0x00 },
+	{ 0x002B, 0x0A },
+	{ 0x002C, 0x20 },
+	{ 0x002D, 0x00 },
+	{ 0x002E, 0x00 },
+	{ 0x002F, 0x00 },
+	{ 0x0030, 0x00 },
+	{ 0x0031, 0x00 },
+	{ 0x0032, 0x00 },
+	{ 0x0033, 0x00 },
+	{ 0x0034, 0x00 },
+	{ 0x0035, 0x00 },
+	{ 0x0036, 0x00 },
+	{ 0x0037, 0x00 },
+	{ 0x0038, 0x00 },
+	{ 0x0039, 0x00 },
+	{ 0x003A, 0x00 },
+	{ 0x003B, 0x00 },
+	{ 0x003C, 0x00 },
+	{ 0x003D, 0x00 },
+	{ 0x0041, 0x00 },
+	{ 0x0042, 0x00 },
+	{ 0x0043, 0x00 },
+	{ 0x0044, 0x00 },
+	{ 0x009E, 0x00 },
+	{ 0x0102, 0x01 },
+	{ 0x0112, 0x02 },
+	{ 0x0113, 0x09 },
+	{ 0x0114, 0x3E },
+	{ 0x0115, 0x19 },
+	{ 0x0117, 0x06 },
+	{ 0x0118, 0x09 },
+	{ 0x0119, 0x3E },
+	{ 0x011A, 0x18 },
+	{ 0x0126, 0x06 },
+	{ 0x0127, 0x09 },
+	{ 0x0128, 0x3E },
+	{ 0x0129, 0x18 },
+	{ 0x012B, 0x06 },
+	{ 0x012C, 0x09 },
+	{ 0x012D, 0x3E },
+	{ 0x012E, 0x1A },
+	{ 0x013F, 0x00 },
+	{ 0x0140, 0x00 },
+	{ 0x0141, 0x40 },
+	{ 0x0206, 0x00 },
+	{ 0x0208, 0x00 },
+	{ 0x0209, 0x00 },
+	{ 0x020A, 0x00 },
+	{ 0x020B, 0x00 },
+	{ 0x020C, 0x00 },
+	{ 0x020D, 0x00 },
+	{ 0x020E, 0x00 },
+	{ 0x020F, 0x00 },
+	{ 0x0210, 0x00 },
+	{ 0x0211, 0x00 },
+	{ 0x0212, 0x00 },
+	{ 0x0213, 0x00 },
+	{ 0x0214, 0x00 },
+	{ 0x0215, 0x00 },
+	{ 0x0216, 0x00 },
+	{ 0x0217, 0x00 },
+	{ 0x0218, 0x00 },
+	{ 0x0219, 0x00 },
+	{ 0x021A, 0x00 },
+	{ 0x021B, 0x00 },
+	{ 0x021C, 0x00 },
+	{ 0x021D, 0x00 },
+	{ 0x021E, 0x00 },
+	{ 0x021F, 0x00 },
+	{ 0x0220, 0x00 },
+	{ 0x0221, 0x00 },
+	{ 0x0222, 0x00 },
+	{ 0x0223, 0x00 },
+	{ 0x0224, 0x00 },
+	{ 0x0225, 0x00 },
+	{ 0x0226, 0x00 },
+	{ 0x0227, 0x00 },
+	{ 0x0228, 0x00 },
+	{ 0x0229, 0x00 },
+	{ 0x022A, 0x00 },
+	{ 0x022B, 0x00 },
+	{ 0x022C, 0x00 },
+	{ 0x022D, 0x00 },
+	{ 0x022E, 0x00 },
+	{ 0x022F, 0x00 },
+	{ 0x0235, 0x00 },
+	{ 0x0236, 0x00 },
+	{ 0x0237, 0x00 },
+	{ 0x0238, 0xA6 },
+	{ 0x0239, 0x8B },
+	{ 0x023A, 0x00 },
+	{ 0x023B, 0x00 },
+	{ 0x023C, 0x00 },
+	{ 0x023D, 0x00 },
+	{ 0x023E, 0x80 },
+	{ 0x0250, 0x03 },
+	{ 0x0251, 0x00 },
+	{ 0x0252, 0x00 },
+	{ 0x0253, 0x00 },
+	{ 0x0254, 0x00 },
+	{ 0x0255, 0x00 },
+	{ 0x025C, 0x00 },
+	{ 0x025D, 0x00 },
+	{ 0x025E, 0x00 },
+	{ 0x025F, 0x00 },
+	{ 0x0260, 0x00 },
+	{ 0x0261, 0x00 },
+	{ 0x026B, 0x30 },
+	{ 0x026C, 0x35 },
+	{ 0x026D, 0x00 },
+	{ 0x026E, 0x00 },
+	{ 0x026F, 0x00 },
+	{ 0x0270, 0x00 },
+	{ 0x0271, 0x00 },
+	{ 0x0272, 0x00 },
+	{ 0x0302, 0x00 },
+	{ 0x0303, 0x00 },
+	{ 0x0304, 0x00 },
+	{ 0x0305, 0x00 },
+	{ 0x0306, 0x0D },
+	{ 0x0307, 0x00 },
+	{ 0x0308, 0x00 },
+	{ 0x0309, 0x00 },
+	{ 0x030A, 0x00 },
+	{ 0x030B, 0x80 },
+	{ 0x030C, 0x00 },
+	{ 0x030D, 0x00 },
+	{ 0x030E, 0x00 },
+	{ 0x030F, 0x00 },
+	{ 0x0310, 0x61 },
+	{ 0x0311, 0x08 },
+	{ 0x0312, 0x00 },
+	{ 0x0313, 0x00 },
+	{ 0x0314, 0x00 },
+	{ 0x0315, 0x00 },
+	{ 0x0316, 0x80 },
+	{ 0x0317, 0x00 },
+	{ 0x0318, 0x00 },
+	{ 0x0319, 0x00 },
+	{ 0x031A, 0x00 },
+	{ 0x031B, 0xD0 },
+	{ 0x031C, 0x1A },
+	{ 0x031D, 0x00 },
+	{ 0x031E, 0x00 },
+	{ 0x031F, 0x00 },
+	{ 0x0320, 0x00 },
+	{ 0x0321, 0xA0 },
+	{ 0x0322, 0x00 },
+	{ 0x0323, 0x00 },
+	{ 0x0324, 0x00 },
+	{ 0x0325, 0x00 },
+	{ 0x0326, 0x00 },
+	{ 0x0327, 0x00 },
+	{ 0x0328, 0x00 },
+	{ 0x0329, 0x00 },
+	{ 0x032A, 0x00 },
+	{ 0x032B, 0x00 },
+	{ 0x032C, 0x00 },
+	{ 0x032D, 0x00 },
+	{ 0x0338, 0x00 },
+	{ 0x0339, 0x1F },
+	{ 0x033B, 0x00 },
+	{ 0x033C, 0x00 },
+	{ 0x033D, 0x00 },
+	{ 0x033E, 0x00 },
+	{ 0x033F, 0x00 },
+	{ 0x0340, 0x00 },
+	{ 0x0341, 0x00 },
+	{ 0x0342, 0x00 },
+	{ 0x0343, 0x00 },
+	{ 0x0344, 0x00 },
+	{ 0x0345, 0x00 },
+	{ 0x0346, 0x00 },
+	{ 0x0347, 0x00 },
+	{ 0x0348, 0x00 },
+	{ 0x0349, 0x00 },
+	{ 0x034A, 0x00 },
+	{ 0x034B, 0x00 },
+	{ 0x034C, 0x00 },
+	{ 0x034D, 0x00 },
+	{ 0x034E, 0x00 },
+	{ 0x034F, 0x00 },
+	{ 0x0350, 0x00 },
+	{ 0x0351, 0x00 },
+	{ 0x0352, 0x00 },
+	{ 0x0359, 0x00 },
+	{ 0x035A, 0x00 },
+	{ 0x035B, 0x00 },
+	{ 0x035C, 0x00 },
+	{ 0x035D, 0x00 },
+	{ 0x035E, 0x00 },
+	{ 0x035F, 0x00 },
+	{ 0x0360, 0x00 },
+	{ 0x0802, 0x00 },
+	{ 0x0803, 0x00 },
+	{ 0x0804, 0x00 },
+	{ 0x0805, 0x00 },
+	{ 0x0806, 0x00 },
+	{ 0x0807, 0x00 },
+	{ 0x0808, 0x00 },
+	{ 0x0809, 0x00 },
+	{ 0x080A, 0x00 },
+	{ 0x080B, 0x00 },
+	{ 0x080C, 0x00 },
+	{ 0x080D, 0x00 },
+	{ 0x080E, 0x00 },
+	{ 0x080F, 0x00 },
+	{ 0x0810, 0x00 },
+	{ 0x0811, 0x00 },
+	{ 0x0812, 0x00 },
+	{ 0x0813, 0x00 },
+	{ 0x0814, 0x00 },
+	{ 0x0815, 0x00 },
+	{ 0x0816, 0x00 },
+	{ 0x0817, 0x00 },
+	{ 0x0818, 0x00 },
+	{ 0x0819, 0x00 },
+	{ 0x081A, 0x00 },
+	{ 0x081B, 0x00 },
+	{ 0x081C, 0x00 },
+	{ 0x081D, 0x00 },
+	{ 0x081E, 0x00 },
+	{ 0x081F, 0x00 },
+	{ 0x0820, 0x00 },
+	{ 0x0821, 0x00 },
+	{ 0x0822, 0x00 },
+	{ 0x0823, 0x00 },
+	{ 0x0824, 0x00 },
+	{ 0x0825, 0x00 },
+	{ 0x0826, 0x00 },
+	{ 0x0827, 0x00 },
+	{ 0x0828, 0x00 },
+	{ 0x0829, 0x00 },
+	{ 0x082A, 0x00 },
+	{ 0x082B, 0x00 },
+	{ 0x082C, 0x00 },
+	{ 0x082D, 0x00 },
+	{ 0x082E, 0x00 },
+	{ 0x082F, 0x00 },
+	{ 0x0830, 0x00 },
+	{ 0x0831, 0x00 },
+	{ 0x0832, 0x00 },
+	{ 0x0833, 0x00 },
+	{ 0x0834, 0x00 },
+	{ 0x0835, 0x00 },
+	{ 0x0836, 0x00 },
+	{ 0x0837, 0x00 },
+	{ 0x0838, 0x00 },
+	{ 0x0839, 0x00 },
+	{ 0x083A, 0x00 },
+	{ 0x083B, 0x00 },
+	{ 0x083C, 0x00 },
+	{ 0x083D, 0x00 },
+	{ 0x083E, 0x00 },
+	{ 0x083F, 0x00 },
+	{ 0x0840, 0x00 },
+	{ 0x0841, 0x00 },
+	{ 0x0842, 0x00 },
+	{ 0x0843, 0x00 },
+	{ 0x0844, 0x00 },
+	{ 0x0845, 0x00 },
+	{ 0x0846, 0x00 },
+	{ 0x0847, 0x00 },
+	{ 0x0848, 0x00 },
+	{ 0x0849, 0x00 },
+	{ 0x084A, 0x00 },
+	{ 0x084B, 0x00 },
+	{ 0x084C, 0x00 },
+	{ 0x084D, 0x00 },
+	{ 0x084E, 0x00 },
+	{ 0x084F, 0x00 },
+	{ 0x0850, 0x00 },
+	{ 0x0851, 0x00 },
+	{ 0x0852, 0x00 },
+	{ 0x0853, 0x00 },
+	{ 0x0854, 0x00 },
+	{ 0x0855, 0x00 },
+	{ 0x0856, 0x00 },
+	{ 0x0857, 0x00 },
+	{ 0x0858, 0x00 },
+	{ 0x0859, 0x00 },
+	{ 0x085A, 0x00 },
+	{ 0x085B, 0x00 },
+	{ 0x085C, 0x00 },
+	{ 0x085D, 0x00 },
+	{ 0x085E, 0x00 },
+	{ 0x085F, 0x00 },
+	{ 0x0860, 0x00 },
+	{ 0x0861, 0x00 },
+	{ 0x090E, 0x02 },
+	{ 0x091C, 0x04 },
+	{ 0x0943, 0x00 },
+	{ 0x0949, 0x00 },
+	{ 0x094A, 0x00 },
+	{ 0x094E, 0x49 },
+	{ 0x094F, 0x02 },
+	{ 0x095E, 0x00 },
+	{ 0x0A02, 0x00 },
+	{ 0x0A03, 0x07 },
+	{ 0x0A04, 0x01 },
+	{ 0x0A05, 0x07 },
+	{ 0x0A14, 0x00 },
+	{ 0x0A1A, 0x00 },
+	{ 0x0A20, 0x00 },
+	{ 0x0A26, 0x00 },
+	{ 0x0B44, 0x0F },
+	{ 0x0B4A, 0x08 },
+	{ 0x0B57, 0x0E },
+	{ 0x0B58, 0x01 },
+	{ 0x001C, 0x01 },
+	{ 0x0B24, 0xC3 },
+	{ 0x0B25, 0x02 },
+};
+
+#endif /* SI5340_REVD_REG_CONFIG_HEADER */
diff --git a/drivers/net/ntnic/nthw/core/nthw_rmc.c b/drivers/net/ntnic/nthw/core/nthw_rmc.c
new file mode 100644
index 0000000000..c4c6779ce0
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_rmc.c
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "ntlog.h"
+
+#include "nthw_drv.h"
+#include "nthw_register.h"
+
+#include "nthw_rmc.h"
+
+nthw_rmc_t *nthw_rmc_new(void)
+{
+	nthw_rmc_t *p = malloc(sizeof(nthw_rmc_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_rmc_t));
+	return p;
+}
+
+void nthw_rmc_delete(nthw_rmc_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_rmc_t));
+		free(p);
+	}
+}
+
+int nthw_rmc_init(nthw_rmc_t *p, nt_fpga_t *p_fpga, int n_instance)
+{
+	const char *const p_adapter_id_str = p_fpga->p_fpga_info->mp_adapter_id_str;
+	nt_module_t *p_mod = fpga_query_module(p_fpga, MOD_RMC, n_instance);
+
+	if (p == NULL)
+		return p_mod == NULL ? -1 : 0;
+
+	if (p_mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: RMC %d: no such instance\n",
+		       p_adapter_id_str, n_instance);
+		return -1;
+	}
+
+	p->mp_fpga = p_fpga;
+	p->mn_instance = n_instance;
+	p->mp_mod_rmc = p_mod;
+
+	/* Params */
+	p->mb_is_vswitch = p_fpga->p_fpga_info->profile == FPGA_INFO_PROFILE_VSWITCH;
+	p->mn_ports = fpga_get_product_param(p_fpga, NT_RX_PORTS,
+					     fpga_get_product_param(p_fpga, NT_PORTS, 0));
+	p->mn_nims = fpga_get_product_param(p_fpga, NT_NIMS, 0);
+	p->mb_administrative_block = false;
+
+	NT_LOG(DBG, NTHW, "%s: RMC %d: vswitch=%d\n", p_adapter_id_str,
+	       p->mn_instance, p->mb_is_vswitch);
+
+	p->mp_reg_ctrl = module_get_register(p->mp_mod_rmc, RMC_CTRL);
+
+	p->mp_fld_ctrl_block_stat_drop =
+		register_get_field(p->mp_reg_ctrl, RMC_CTRL_BLOCK_STATT);
+	p->mp_fld_ctrl_block_keep_alive =
+		register_get_field(p->mp_reg_ctrl, RMC_CTRL_BLOCK_KEEPA);
+	p->mp_fld_ctrl_block_mac_port =
+		register_get_field(p->mp_reg_ctrl, RMC_CTRL_BLOCK_MAC_PORT);
+
+	p->mp_reg_status = module_query_register(p->mp_mod_rmc, RMC_STATUS);
+	if (p->mp_reg_status) {
+		p->mp_fld_sf_ram_of =
+			register_get_field(p->mp_reg_status, RMC_STATUS_SF_RAM_OF);
+		p->mp_fld_descr_fifo_of =
+			register_get_field(p->mp_reg_status, RMC_STATUS_DESCR_FIFO_OF);
+	}
+
+	p->mp_reg_dbg = module_query_register(p->mp_mod_rmc, RMC_DBG);
+	if (p->mp_reg_dbg) {
+		p->mp_fld_dbg_merge =
+			register_get_field(p->mp_reg_dbg, RMC_DBG_MERGE);
+	}
+
+	p->mp_reg_mac_if = module_query_register(p->mp_mod_rmc, RMC_MAC_IF);
+	if (p->mp_reg_mac_if) {
+		p->mp_fld_mac_if_err =
+			register_get_field(p->mp_reg_mac_if, RMC_MAC_IF_ERR);
+	}
+	return 0;
+}
+
+uint32_t nthw_rmc_get_mac_block(nthw_rmc_t *p)
+{
+	return field_get_updated(p->mp_fld_ctrl_block_mac_port);
+}
+
+uint32_t nthw_rmc_get_status_sf_ram_of(nthw_rmc_t *p)
+{
+	return (p->mp_reg_status) ? field_get_updated(p->mp_fld_sf_ram_of) :
+	       0xffffffff;
+}
+
+uint32_t nthw_rmc_get_status_descr_fifo_of(nthw_rmc_t *p)
+{
+	return (p->mp_reg_status) ? field_get_updated(p->mp_fld_descr_fifo_of) :
+	       0xffffffff;
+}
+
+uint32_t nthw_rmc_get_dbg_merge(nthw_rmc_t *p)
+{
+	return (p->mp_reg_dbg) ? field_get_updated(p->mp_fld_dbg_merge) : 0xffffffff;
+}
+
+uint32_t nthw_rmc_get_mac_if_err(nthw_rmc_t *p)
+{
+	return (p->mp_reg_mac_if) ? field_get_updated(p->mp_fld_mac_if_err) :
+	       0xffffffff;
+}
+
+void nthw_rmc_set_mac_block(nthw_rmc_t *p, uint32_t mask)
+{
+	field_set_val_flush32(p->mp_fld_ctrl_block_mac_port, mask);
+}
+
+void nthw_rmc_block(nthw_rmc_t *p)
+{
+	/* BLOCK_STATT(0)=1 BLOCK_KEEPA(1)=1 BLOCK_MAC_PORT(8:11)=~0 */
+	if (!p->mb_administrative_block) {
+		field_set_flush(p->mp_fld_ctrl_block_stat_drop);
+		field_set_flush(p->mp_fld_ctrl_block_keep_alive);
+		field_set_flush(p->mp_fld_ctrl_block_mac_port);
+	}
+}
+
+void nthw_rmc_unblock(nthw_rmc_t *p, bool b_is_slave)
+{
+	uint32_t n_block_mask = ~0U << (b_is_slave ? p->mn_nims : p->mn_ports);
+
+	if (p->mb_is_vswitch) {
+		/*
+		 * VSWITCH: NFV: block bits: phy_nim_ports(2) + rtd_ports(4) +
+		 * roa_recirculate_port(1)
+		 */
+		n_block_mask = 1 << (2 + 4); /* block only ROA recirculate */
+	}
+
+	/* BLOCK_STATT(0)=0 BLOCK_KEEPA(1)=0 BLOCK_MAC_PORT(8:11)=0 */
+	if (!p->mb_administrative_block) {
+		field_clr_flush(p->mp_fld_ctrl_block_stat_drop);
+		field_clr_flush(p->mp_fld_ctrl_block_keep_alive);
+		field_set_val_flush32(p->mp_fld_ctrl_block_mac_port, n_block_mask);
+	}
+}
+
+void nthw_rmc_administrative_block(nthw_rmc_t *p)
+{
+	/* block all MAC ports */
+	field_set_flush(p->mp_fld_ctrl_block_mac_port);
+	p->mb_administrative_block = true;
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_rmc.h b/drivers/net/ntnic/nthw/core/nthw_rmc.h
new file mode 100644
index 0000000000..b40f0a0994
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_rmc.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NTHW_RMC_H_
+#define NTHW_RMC_H_
+
+struct nthw_rmc {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_rmc;
+	int mn_instance;
+
+	int mn_ports;
+	int mn_nims;
+	bool mb_is_vswitch;
+
+	bool mb_administrative_block;
+
+	/* RMC CTRL register */
+	nt_register_t *mp_reg_ctrl;
+	nt_field_t *mp_fld_ctrl_block_stat_drop;
+	nt_field_t *mp_fld_ctrl_block_keep_alive;
+	nt_field_t *mp_fld_ctrl_block_mac_port;
+
+	/* RMC Status register */
+	nt_register_t *mp_reg_status;
+	nt_field_t *mp_fld_sf_ram_of;
+	nt_field_t *mp_fld_descr_fifo_of;
+
+	/* RMC DBG register */
+	nt_register_t *mp_reg_dbg;
+	nt_field_t *mp_fld_dbg_merge;
+
+	/* RMC MAC_IF register */
+	nt_register_t *mp_reg_mac_if;
+	nt_field_t *mp_fld_mac_if_err;
+};
+
+typedef struct nthw_rmc nthw_rmc_t;
+typedef struct nthw_rmc nthw_rmc;
+
+nthw_rmc_t *nthw_rmc_new(void);
+void nthw_rmc_delete(nthw_rmc_t *p);
+int nthw_rmc_init(nthw_rmc_t *p, nt_fpga_t *p_fpga, int n_instance);
+
+uint32_t nthw_rmc_get_mac_block(nthw_rmc_t *p);
+void nthw_rmc_set_mac_block(nthw_rmc_t *p, uint32_t mask);
+void nthw_rmc_block(nthw_rmc_t *p);
+void nthw_rmc_unblock(nthw_rmc_t *p, bool b_is_slave);
+void nthw_rmc_administrative_block(nthw_rmc_t *p);
+
+uint32_t nthw_rmc_get_status_sf_ram_of(nthw_rmc_t *p);
+uint32_t nthw_rmc_get_status_descr_fifo_of(nthw_rmc_t *p);
+uint32_t nthw_rmc_get_dbg_merge(nthw_rmc_t *p);
+uint32_t nthw_rmc_get_mac_if_err(nthw_rmc_t *p);
+
+#endif /* NTHW_RMC_H_ */
diff --git a/drivers/net/ntnic/sensors/avr_sensors/avr_sensors.c b/drivers/net/ntnic/sensors/avr_sensors/avr_sensors.c
new file mode 100644
index 0000000000..bf120ccb39
--- /dev/null
+++ b/drivers/net/ntnic/sensors/avr_sensors/avr_sensors.c
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "avr_sensors.h"
+#include "ntlog.h"
+
+#define MAX_ADAPTERS 2
+
+uint8_t s_fpga_indexes[MAX_ADAPTERS] = { 0 }; /* _NTSD_MAX_NUM_ADAPTERS_ */
+static uint8_t get_fpga_idx(unsigned int adapter_no);
+
+/*
+ * This function setups monitoring of AVR sensors
+ */
+static uint8_t _avr_sensor_init(nthw_spi_v3_t *s_spi, uint8_t m_adapter_no,
+				const char *p_name,
+				enum sensor_mon_device avr_dev,
+				uint8_t avr_dev_reg, enum sensor_mon_endian end,
+				enum sensor_mon_sign si, uint16_t mask)
+{
+	uint8_t fpga_idx = get_fpga_idx(m_adapter_no);
+	struct sensor_mon_setup16 avr_sensor_setup;
+
+	/* Setup monitoring in AVR placing results in FPGA */
+	avr_sensor_setup.setup_cnt = 1;
+	avr_sensor_setup.setup_data[0].fpga_idx = fpga_idx;
+	avr_sensor_setup.setup_data[0].device = avr_dev;
+	avr_sensor_setup.setup_data[0].device_register = avr_dev_reg;
+	avr_sensor_setup.setup_data[0].format = (uint16_t)(end | si << 2);
+
+	avr_sensor_setup.setup_data[0].mask = mask;
+	avr_sensor_setup.setup_data[0].pos =
+		0; /* So far for all sensors in table */
+
+	/*
+	 * At first it is the task of ntservice to test limit_low and limit_high on all
+	 * board sensors. Later the test is going to be carried out by the AVR
+	 */
+	if (si == SENSOR_MON_SIGNED) {
+		avr_sensor_setup.setup_data[0].int16.limit_low =
+			SENSOR_MON_INT16_NAN;
+		avr_sensor_setup.setup_data[0].int16.limit_high =
+			SENSOR_MON_INT16_NAN;
+	} else {
+		avr_sensor_setup.setup_data[0].uint16.limit_low =
+			SENSOR_MON_UINT16_NAN;
+		avr_sensor_setup.setup_data[0].uint16.limit_high =
+			SENSOR_MON_UINT16_NAN;
+	}
+
+	int result = nt_avr_sensor_mon_setup(&avr_sensor_setup, s_spi);
+
+	if (result)
+		NT_LOG(ERR, ETHDEV, "%s: sensor initialization error\n", p_name);
+
+	return fpga_idx;
+}
+
+static void avr_read(struct nt_sensor_group *sg, nthw_spis_t *t_spi)
+{
+	uint32_t p_sensor_result;
+
+	if (sg == NULL || sg->sensor == NULL)
+		return;
+
+	sensor_read(t_spi, sg->sensor->fpga_idx, &p_sensor_result);
+	update_sensor_value(sg->sensor, sg->conv_func(p_sensor_result));
+}
+
+struct nt_sensor_group *
+avr_sensor_init(nthw_spi_v3_t *s_spi, uint8_t m_adapter_no, const char *p_name,
+		enum nt_sensor_source_e ssrc, enum nt_sensor_type_e type,
+		unsigned int index, enum sensor_mon_device avr_dev,
+		uint8_t avr_dev_reg, enum sensor_mon_endian end,
+		enum sensor_mon_sign si, int (*conv_func)(uint32_t),
+		uint16_t mask)
+{
+	struct nt_sensor_group *sg = malloc(sizeof(struct nt_sensor_group));
+
+	if (sg == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: sensor group is NULL", __func__);
+		return NULL;
+	}
+	init_sensor_group(sg);
+	sg->sensor = allocate_sensor(m_adapter_no, p_name, ssrc, type, index,
+				     NT_SENSOR_DISABLE_ALARM, si);
+	sg->sensor->fpga_idx = _avr_sensor_init(s_spi, m_adapter_no, p_name, avr_dev,
+					       avr_dev_reg, end, si, mask);
+	sg->read = &avr_read;
+	sg->conv_func = conv_func;
+	sg->monitor = NULL;
+	sg->next = NULL;
+	return sg;
+}
+
+static uint8_t get_fpga_idx(unsigned int adapter_no)
+{
+	uint8_t tmp = s_fpga_indexes[adapter_no];
+
+	s_fpga_indexes[adapter_no] = (uint8_t)(tmp + 1);
+
+	return tmp;
+}
diff --git a/drivers/net/ntnic/sensors/avr_sensors/avr_sensors.h b/drivers/net/ntnic/sensors/avr_sensors/avr_sensors.h
new file mode 100644
index 0000000000..b8c37a12cb
--- /dev/null
+++ b/drivers/net/ntnic/sensors/avr_sensors/avr_sensors.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _AVR_SENSORS_H
+#define _AVR_SENSORS_H
+
+#include <stdint.h>
+
+#include "sensors.h"
+#include "avr_intf.h"
+#include "ntavr.h"
+
+struct nt_sensor_group *
+avr_sensor_init(nthw_spi_v3_t *s_spi, uint8_t m_adapter_no, const char *p_name,
+		enum nt_sensor_source_e ssrc, enum nt_sensor_type_e type,
+		unsigned int index, enum sensor_mon_device avr_dev,
+		uint8_t avr_dev_reg, enum sensor_mon_endian end,
+		enum sensor_mon_sign si, int (*conv_func)(uint32_t),
+		uint16_t mask);
+
+#endif /* _AVR_SENSORS_H */
diff --git a/drivers/net/ntnic/sensors/board_sensors/board_sensors.c b/drivers/net/ntnic/sensors/board_sensors/board_sensors.c
new file mode 100644
index 0000000000..8e52379df8
--- /dev/null
+++ b/drivers/net/ntnic/sensors/board_sensors/board_sensors.c
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <stddef.h>
+#include <math.h>
+
+#include "tempmon.h"
+#include "board_sensors.h"
+#include "ntlog.h"
+
+static void fpga_temperature_sensor_read(struct nt_sensor_group *sg,
+		nthw_spis_t *t_spi)
+{
+	int temp = 0;
+	(void)t_spi;
+	if (sg == NULL || sg->sensor == NULL) {
+		NT_LOG(ERR, ETHDEV, "failed to read FPGA temperature\n");
+		return;
+	}
+	struct nt_fpga_sensor_monitor *temp_monitor = sg->monitor;
+	uint32_t val = field_get_updated(temp_monitor->fields[0]);
+
+	temp = (val * 20159 - 44752896) / 16384;
+
+	update_sensor_value(sg->sensor, temp);
+}
+
+struct nt_sensor_group *fpga_temperature_sensor_init(uint8_t adapter_no,
+		unsigned int sensor_idx,
+		nt_fpga_t *p_fpga)
+{
+	struct nt_sensor_group *sg = malloc(sizeof(struct nt_sensor_group));
+
+	if (sg == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: sensor group is NULL", __func__);
+		return NULL;
+	}
+	init_sensor_group(sg);
+	sg->monitor = tempmon_new();
+	tempmon_init(sg->monitor, p_fpga);
+	sg->sensor =
+		allocate_sensor(adapter_no, "FPGA", NT_SENSOR_SOURCE_ADAPTER,
+				NT_SENSOR_TYPE_TEMPERATURE, sensor_idx,
+				NT_SENSOR_DISABLE_ALARM, SENSOR_MON_UNSIGNED);
+	sg->read = &fpga_temperature_sensor_read;
+	return sg;
+}
diff --git a/drivers/net/ntnic/sensors/board_sensors/board_sensors.h b/drivers/net/ntnic/sensors/board_sensors/board_sensors.h
new file mode 100644
index 0000000000..a7f75b7ae4
--- /dev/null
+++ b/drivers/net/ntnic/sensors/board_sensors/board_sensors.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _BOARD_SENSORS_H
+#define _BOARD_SENSORS_H
+
+#include <stdint.h>
+
+#include "sensors.h"
+
+#include "nthw_fpga_model.h"
+
+struct nt_sensor_group *fpga_temperature_sensor_init(uint8_t adapter_no,
+		unsigned int sensor_idx,
+		nt_fpga_t *p_fpga);
+
+#endif /* _BOARD_SENSORS_H */
diff --git a/drivers/net/ntnic/sensors/board_sensors/tempmon.c b/drivers/net/ntnic/sensors/board_sensors/tempmon.c
new file mode 100644
index 0000000000..2cd3709205
--- /dev/null
+++ b/drivers/net/ntnic/sensors/board_sensors/tempmon.c
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "tempmon.h"
+#include "ntlog.h"
+#include "nthw_register.h"
+
+struct nt_fpga_sensor_monitor *tempmon_new(void)
+{
+	struct nt_fpga_sensor_monitor *temp =
+		malloc(sizeof(struct nt_fpga_sensor_monitor));
+	if (temp == NULL)
+		NT_LOG(ERR, ETHDEV, "%s: monitor is NULL\n", __func__);
+	return temp;
+}
+
+void tempmon_init(struct nt_fpga_sensor_monitor *t, nt_fpga_t *p_fpga)
+{
+	if (t == NULL || p_fpga == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: bad argument(s)\n", __func__);
+		return;
+	}
+	/* fetch initialized module */
+	t->fpga = p_fpga;
+	t->mod = nthw_get_module(t->fpga, MOD_TEMPMON, 0);
+	if (t->mod == NULL)
+		NT_LOG(ERR, ETHDEV, "module is NULL\n");
+	/* fetch register */
+	t->reg = module_get_register(t->mod, TEMPMON_STAT);
+	if (t->reg == NULL)
+		NT_LOG(ERR, ETHDEV, "register is NULL\n");
+	/* fetch fields */
+	t->fields = malloc(sizeof(nt_field_t *));
+	if (t->fields == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: field is NULL", __func__);
+		return;
+	}
+	t->fields[0] = register_get_field(t->reg, TEMPMON_STAT_TEMP);
+	if (t->fields[0] == NULL)
+		NT_LOG(ERR, ETHDEV, "field is NULL\n");
+}
diff --git a/drivers/net/ntnic/sensors/board_sensors/tempmon.h b/drivers/net/ntnic/sensors/board_sensors/tempmon.h
new file mode 100644
index 0000000000..6f2017b714
--- /dev/null
+++ b/drivers/net/ntnic/sensors/board_sensors/tempmon.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _TEMPMON_H
+#define _TEMPMON_H
+
+#include "nthw_fpga_model.h"
+#include <stdlib.h>
+
+#include "sensors.h"
+
+struct nt_fpga_sensor_monitor *tempmon_new(void);
+void tempmon_init(struct nt_fpga_sensor_monitor *t, nt_fpga_t *p_fpga);
+
+#endif /* _TEMPMON_H */
diff --git a/drivers/net/ntnic/sensors/nim_sensors/nim_sensors.c b/drivers/net/ntnic/sensors/nim_sensors/nim_sensors.c
new file mode 100644
index 0000000000..e130855a35
--- /dev/null
+++ b/drivers/net/ntnic/sensors/nim_sensors/nim_sensors.c
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <arpa/inet.h>
+
+#include "nim_sensors.h"
+#include "ntlog.h"
+
+#define TEMP NT_SENSOR_TYPE_TEMPERATURE
+#define VOLT NT_SENSOR_TYPE_VOLTAGE
+#define CURR NT_SENSOR_TYPE_CURRENT
+#define PWR NT_SENSOR_TYPE_POWER
+
+#define SNA NT_SENSOR_SUBTYPE_NA
+#define AVG NT_SENSOR_SUBTYPE_POWER_AVERAGE
+
+#define ENA NT_SENSOR_ENABLE_ALARM
+#define DIA NT_SENSOR_DISABLE_ALARM
+
+/*
+ * Sensors for SFP/SFP+/SFP28. The name of the level 0 temperature sensor is
+ * empty and will then be set automatically
+ */
+struct nt_adapter_sensor_description sfp_sensors_level0[1] = {
+	{ TEMP, SNA, NT_SENSOR_SFP_TEMP, DIA, "" },
+};
+
+struct nt_adapter_sensor_description sfp_sensors_level1[4] = {
+	{ VOLT, SNA, NT_SENSOR_SFP_SUPPLY, DIA, "Supply" },
+	{ CURR, SNA, NT_SENSOR_SFP_TX_BIAS, DIA, "Tx Bias" },
+	{ PWR, AVG, NT_SENSOR_SFP_TX_POWER, DIA, "Tx" },
+	{ PWR, AVG, NT_SENSOR_SFP_RX_POWER, DIA, "Rx" }
+};
+
+struct nt_adapter_sensor_description qsfp_sensor_level0[1] = {
+	{ TEMP, SNA, NT_SENSOR_QSFP_TEMP, DIA, "" },
+};
+
+struct nt_adapter_sensor_description qsfp_sensor_level1[13] = {
+	{ VOLT, SNA, NT_SENSOR_QSFP_SUPPLY, DIA, "Supply" },
+	{ CURR, SNA, NT_SENSOR_QSFP_TX_BIAS1, DIA, "Tx Bias 1" },
+	{ CURR, SNA, NT_SENSOR_QSFP_TX_BIAS2, DIA, "Tx Bias 2" },
+	{ CURR, SNA, NT_SENSOR_QSFP_TX_BIAS3, DIA, "Tx Bias 3" },
+	{ CURR, SNA, NT_SENSOR_QSFP_TX_BIAS4, DIA, "Tx Bias 4" },
+	{ PWR, AVG, NT_SENSOR_QSFP_TX_POWER1, DIA, "Tx 1" },
+	{ PWR, AVG, NT_SENSOR_QSFP_TX_POWER2, DIA, "Tx 2" },
+	{ PWR, AVG, NT_SENSOR_QSFP_TX_POWER3, DIA, "Tx 3" },
+	{ PWR, AVG, NT_SENSOR_QSFP_TX_POWER4, DIA, "Tx 4" },
+	{ PWR, AVG, NT_SENSOR_QSFP_RX_POWER1, DIA, "Rx 1" },
+	{ PWR, AVG, NT_SENSOR_QSFP_RX_POWER2, DIA, "Rx 2" },
+	{ PWR, AVG, NT_SENSOR_QSFP_RX_POWER3, DIA, "Rx 3" },
+	{ PWR, AVG, NT_SENSOR_QSFP_RX_POWER4, DIA, "Rx 4" }
+};
diff --git a/drivers/net/ntnic/sensors/nim_sensors/nim_sensors.h b/drivers/net/ntnic/sensors/nim_sensors/nim_sensors.h
new file mode 100644
index 0000000000..c68c9aa924
--- /dev/null
+++ b/drivers/net/ntnic/sensors/nim_sensors/nim_sensors.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _NIM_SENSORS_H
+#define _NIM_SENSORS_H
+
+#include <stdint.h>
+#include <string.h>
+#include "sensors.h"
+
+#define XFP_TEMP_LIN_ADDR 96
+
+extern struct nt_adapter_sensor_description sfp_sensors_level0[1];
+extern struct nt_adapter_sensor_description sfp_sensors_level1[4];
+extern struct nt_adapter_sensor_description qsfp_sensor_level0[1];
+extern struct nt_adapter_sensor_description qsfp_sensor_level1[13];
+
+#endif /* _NIM_SENSORS_H */
diff --git a/drivers/net/ntnic/sensors/ntavr/avr_intf.h b/drivers/net/ntnic/sensors/ntavr/avr_intf.h
new file mode 100644
index 0000000000..feeec6e13a
--- /dev/null
+++ b/drivers/net/ntnic/sensors/ntavr/avr_intf.h
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _AVR_INTF
+#define _AVR_INTF
+
+#include <stdint.h>
+
+#define SENSOR_MON_UINT16_NAN 0xFFFF /* Most positive number used as NaN */
+#define SENSOR_MON_INT16_NAN \
+	((int16_t)0x8000) /* Most negative number used as NaN */
+
+/*
+ * Specify the nature of the raw data. AVR and ntservice must use this
+ * information when comparing or converting to native format which is little endian
+ */
+enum sensor_mon_endian { SENSOR_MON_LITTLE_ENDIAN, SENSOR_MON_BIG_ENDIAN };
+
+enum sensor_mon_sign {
+	SENSOR_MON_UNSIGNED,
+	SENSOR_MON_SIGNED, /* 2's complement */
+};
+
+/* Define sensor devices */
+enum sensor_mon_device {
+	SENSOR_MON_PSU_EXAR_7724_0 = 0, /* NT40E3, NT100E3 */
+	SENSOR_MON_PSU_EXAR_7724_1, /* NT40E3, NT100E3 */
+	SENSOR_MON_PSU_LTM_4676_0, /* na      NT100E3, page-0 */
+	SENSOR_MON_PSU_LTM_4676_1, /* na      NT100E3, page-0 */
+	SENSOR_MON_INA219_1, /* NT40E3, NT100E3 */
+	SENSOR_MON_INA219_2, /* NT40E3, NT100E3 */
+	SENSOR_MON_MAX6642, /* NT40E3, NT100E3 */
+	SENSOR_MON_DS1775, /* NT40E3, NT100E3 */
+	SENSOR_MON_FAN, /* NT40E3, NT100E3 */
+	SENSOR_MON_AVR, /* NT40E3, NT100E3 */
+	SENSOR_MON_PEX8734, /* na      NT100E3 */
+	SENSOR_MON_RATE_COUNT, /* NT40E3, NT100E3 */
+	SENSOR_MON_PSU_LTM_4676_0_1, /* na      NT100E3, page-1 */
+	SENSOR_MON_PSU_LTM_4676_1_1, /* na      NT100E3, page-1 */
+	SENSOR_MON_MP2886A, /* na,     na,      NT200A02, */
+	SENSOR_MON_PSU_EM2260_1, /*     na,      na,      na,       na, NT200D01^M */
+	SENSOR_MON_PSU_EM2120_2, /*     na,      na,      na,       na, NT200D01^M */
+	SENSOR_MON_MP2886A_PSU_1, /*     na,      na,      na, NT200A02,        na,   NT50B01, */
+	SENSOR_MON_MP8869S_PSU_2, /*     na,      na,      na, NT200A02,        na,   NT50B01, */
+	SENSOR_MON_MP8645PGVT_PSU_3, /*     na,      na,      na, NT200A02,        na,   NT50B01, */
+	SENSOR_MON_MP8645PGVT_PSU_4, /*     na,      na,      na, NT200A02,        na,   NT50B01, */
+	SENSOR_MON_MP8869S_PSU_5, /*     na,      na,      na, NT200A02,        na,   NT50B01, */
+	SENSOR_MON_MP8869S_PSU_6, /*     na,      na,      na, NT200A02,        na,   NT50B01, */
+	SENSOR_MON_DEVICE_COUNT
+};
+
+#pragma pack(1)
+struct sensor_mon_setup_data16 {
+	uint8_t fpga_idx; /* Destination of results */
+	uint8_t device; /* Device to monitor */
+	uint8_t device_register; /* Sensor within device */
+	uint16_t mask; /* Indicates active bits */
+	uint8_t pos; /* Position of first active bit */
+	uint16_t format; /* b0,1:sensor_mon_endian_t endian */
+	/* b2,3:sensor_mon_sign_t   sign */
+	union {
+		struct {
+			int16_t limit_low; /* Signed alarm limit low */
+			int16_t limit_high; /* Signed alarm limit high */
+		} int16;
+
+		struct {
+			uint16_t limit_low; /* Unsigned alarm limit low */
+			uint16_t limit_high; /* Unsigned alarm limit high */
+		} uint16;
+	};
+};
+
+#pragma pack()
+struct sensor_mon_setup16 {
+	uint8_t setup_cnt; /* Number of entries in setup_data */
+	struct sensor_mon_setup_data16 setup_data[40];
+};
+
+/* Define sensor monitoring control */
+enum sensor_mon_control {
+	SENSOR_MON_CTRL_STOP = 0, /* Stop sensor monitoring */
+	SENSOR_MON_CTRL_RUN = 1, /* Start sensor monitoring */
+	SENSOR_MON_CTRL_REM_ALL_SENSORS =
+		2, /* Stop and remove all sensor monitoring setup */
+};
+
+#endif /* _AVR_INTF */
diff --git a/drivers/net/ntnic/sensors/ntavr/ntavr.c b/drivers/net/ntnic/sensors/ntavr/ntavr.c
new file mode 100644
index 0000000000..6d8c3042b1
--- /dev/null
+++ b/drivers/net/ntnic/sensors/ntavr/ntavr.c
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "ntavr.h"
+#include "ntlog.h"
+
+static int txrx(nthw_spi_v3_t *s_spi, enum avr_opcodes opcode, size_t txsz,
+		uint16_t *tx, size_t *rxsz, uint16_t *rx)
+{
+	int res = 1;
+	struct tx_rx_buf m_tx = { .size = (uint16_t)txsz, .p_buf = tx };
+	struct tx_rx_buf m_rx = { .size = (uint16_t)*rxsz, .p_buf = rx };
+
+	res = nthw_spi_v3_transfer(s_spi, opcode, &m_tx, &m_rx);
+	if (res) {
+		NT_LOG(ERR, ETHDEV, "%s transfer failed - %i", __func__, res);
+		return res;
+	}
+
+	if (rxsz != NULL)
+		*rxsz = m_rx.size;
+
+	return res;
+}
+
+uint32_t sensor_read(nthw_spis_t *t_spi, uint8_t fpga_idx,
+		     uint32_t *p_sensor_result)
+{
+	return nthw_spis_read_sensor(t_spi, fpga_idx, p_sensor_result);
+}
+
+int nt_avr_sensor_mon_setup(struct sensor_mon_setup16 *p_setup, nthw_spi_v3_t *s_spi)
+{
+	int error;
+	size_t tx_size;
+	size_t rx_size = 0;
+
+	tx_size = sizeof(struct sensor_mon_setup16) - sizeof(p_setup->setup_data);
+	tx_size += sizeof(p_setup->setup_data[0]) * p_setup->setup_cnt;
+
+	error = txrx(s_spi, AVR_OP_SENSOR_MON_SETUP, tx_size, (uint16_t *)p_setup,
+		     &rx_size, NULL);
+
+	if (error) {
+		NT_LOG(ERR, ETHDEV, "%s failed\n", __func__);
+		return error;
+	}
+
+	if (rx_size != 0) {
+		NT_LOG(ERR, ETHDEV,
+		       "%s: Returned data: Expected size = 0, Actual = %zu",
+		       __func__, rx_size);
+		return 1;
+	}
+	return 0;
+}
+
+int nt_avr_sensor_mon_ctrl(nthw_spi_v3_t *s_spi, enum sensor_mon_control ctrl)
+{
+	int error;
+	size_t rx_size = 0;
+
+	error = txrx(s_spi, AVR_OP_SENSOR_MON_CONTROL, sizeof(ctrl),
+		     (uint16_t *)(&ctrl), &rx_size, NULL);
+
+	if (error != 0)
+		return error;
+
+	if (rx_size != 0) {
+		NT_LOG(ERR, ETHDEV,
+		       "%s: Returned data: Expected size = 0, Actual = %zu",
+		       __func__, rx_size);
+		return 1;
+	}
+
+	return 0;
+}
diff --git a/drivers/net/ntnic/sensors/ntavr/ntavr.h b/drivers/net/ntnic/sensors/ntavr/ntavr.h
new file mode 100644
index 0000000000..b7a7aeb908
--- /dev/null
+++ b/drivers/net/ntnic/sensors/ntavr/ntavr.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _NTAVR_H
+#define _NTAVR_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "avr_intf.h"
+#include "nthw_drv.h"
+#include "nthw_spi_v3.h"
+
+/*
+ * @internal
+ * @brief AVR Device Enum
+ *
+ * Global names for identifying an AVR device for Generation2 adapters
+ */
+enum ntavr_device {
+	NTAVR_MAINBOARD, /* Mainboard AVR device */
+	NTAVR_FRONTBOARD /* Frontboard AVR device */
+};
+
+int nt_avr_sensor_mon_setup(struct sensor_mon_setup16 *p_setup,
+			nthw_spi_v3_t *s_spi);
+int nt_avr_sensor_mon_ctrl(nthw_spi_v3_t *s_spi, enum sensor_mon_control ctrl);
+uint32_t sensor_read(nthw_spis_t *t_spi, uint8_t fpga_idx,
+		     uint32_t *p_sensor_result);
+
+#endif /* _NTAVR_H */
diff --git a/drivers/net/ntnic/sensors/sensor_types.h b/drivers/net/ntnic/sensors/sensor_types.h
new file mode 100644
index 0000000000..bac4e925f9
--- /dev/null
+++ b/drivers/net/ntnic/sensors/sensor_types.h
@@ -0,0 +1,259 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _SENSOR_TYPES_H
+#define _SENSOR_TYPES_H
+
+/*
+ * Sensor types
+ */
+enum nt_sensor_type_e {
+	NT_SENSOR_TYPE_UNKNOWN = 0,
+	NT_SENSOR_TYPE_TEMPERATURE = 1, /* Unit: 0.1 degree Celsius */
+	NT_SENSOR_TYPE_VOLTAGE = 2, /* Unit: 1 mV */
+	NT_SENSOR_TYPE_CURRENT = 3, /* Unit: 1 uA */
+	NT_SENSOR_TYPE_POWER = 4, /* Unit: 0.1 uW */
+	NT_SENSOR_TYPE_FAN = 5, /* Unit: 1 RPM (Revolutions Per Minute) */
+	NT_SENSOR_TYPE_HIGH_POWER = 6, /* Unit: 1 mW */
+	NT_SENSOR_TYPE_NUMBER = 7,
+};
+
+/*
+ * Generic SFP/SFP+/SFP28 sensors
+ *
+ * These sensors should be used instead of all adapter specific SFP sensors
+ * that have been deprecated..
+ */
+enum nt_sensors_sfp {
+	NT_SENSOR_SFP_TEMP,
+	NT_SENSOR_SFP_SUPPLY,
+	NT_SENSOR_SFP_TX_BIAS,
+	NT_SENSOR_SFP_TX_POWER,
+	NT_SENSOR_SFP_RX_POWER,
+};
+
+/*
+ * Generic QSFP/QSFP+/QSFP28 sensors
+ *
+ * These sensors should be used instead of all adapter specific QSFP sensors
+ * that have been deprecated..
+ */
+enum nt_sensors_qsfp {
+	NT_SENSOR_QSFP_TEMP,
+	NT_SENSOR_QSFP_SUPPLY,
+	NT_SENSOR_QSFP_TX_BIAS1,
+	NT_SENSOR_QSFP_TX_BIAS2,
+	NT_SENSOR_QSFP_TX_BIAS3,
+	NT_SENSOR_QSFP_TX_BIAS4,
+	NT_SENSOR_QSFP_TX_POWER1,
+	NT_SENSOR_QSFP_TX_POWER2,
+	NT_SENSOR_QSFP_TX_POWER3,
+	NT_SENSOR_QSFP_TX_POWER4,
+	NT_SENSOR_QSFP_RX_POWER1,
+	NT_SENSOR_QSFP_RX_POWER2,
+	NT_SENSOR_QSFP_RX_POWER3,
+	NT_SENSOR_QSFP_RX_POWER4,
+};
+
+typedef enum nt_sensor_type_e nt_sensor_type_t;
+
+/*
+ * Sensor subtypes
+ */
+enum nt_sensor_sub_type_e {
+	NT_SENSOR_SUBTYPE_NA = 0,
+	/*
+	 * Subtype for NT_SENSOR_TYPE_POWER type on optical modules (optical modulation
+	 * amplitude measured)
+	 */
+	NT_SENSOR_SUBTYPE_POWER_OMA,
+	/* Subtype for NT_SENSOR_TYPE_POWER type on optical modules (average power measured) */
+	NT_SENSOR_SUBTYPE_POWER_AVERAGE,
+	/* Subtype for NT_SENSOR_TYPE_HIGH_POWER type on adapters (total power consumption) */
+	NT_SENSOR_SUBTYPE_POWER_TOTAL
+};
+
+typedef enum nt_sensor_sub_type_e nt_sensor_sub_type_t;
+
+/*
+ * Sensor source
+ */
+enum nt_sensor_source_e {
+	NT_SENSOR_SOURCE_UNKNOWN = 0x00, /* Unknown source */
+	/*
+	 * Sensors located in a port. These are primary sensors - usually NIM temperature. Presence
+	 * depends on adapter and NIM type.
+	 */
+	NT_SENSOR_SOURCE_PORT =
+		0x01,
+	/*
+	 * Level 1 sensors located in a port. These are secondary sensors - usually NIM supply
+	 * voltage, Tx bias and Rx/Tx optical power. Presence depends on adapter and NIM type.
+	 */
+	NT_SENSOR_SOURCE_LEVEL1_PORT =
+		0x02,
+#ifndef DOXYGEN_INTERNAL_ONLY
+	NT_SENSOR_SOURCE_LEVEL2_PORT =
+		0x04, /* Level 2 sensors located in a port */
+#endif
+	NT_SENSOR_SOURCE_ADAPTER = 0x08, /* Sensors mounted on the adapter */
+	NT_SENSOR_SOURCE_LEVEL1_ADAPTER =
+		0x10, /* Level 1 sensors mounted on the adapter */
+#ifndef DOXYGEN_INTERNAL_ONLY
+	NT_SENSOR_SOURCE_LEVEL2_ADAPTER =
+		0x20, /* Level 2 sensors mounted on the adapter */
+#endif
+};
+
+/*
+ * Sensor state
+ */
+enum nt_sensor_state_e {
+	NT_SENSOR_STATE_UNKNOWN = 0, /* Unknown state */
+	NT_SENSOR_STATE_INITIALIZING = 1, /* The sensor is initializing */
+	NT_SENSOR_STATE_NORMAL = 2, /* Sensor values are within range */
+	NT_SENSOR_STATE_ALARM = 3, /* Sensor values are out of range */
+	NT_SENSOR_STATE_NOT_PRESENT =
+		4 /* The sensor is not present, for example, SFP without diagnostics */
+};
+
+typedef enum nt_sensor_state_e nt_sensor_state_t;
+
+/*
+ * Sensor value
+ */
+#define NT_SENSOR_NAN \
+	(0x80000000) /* Indicates that sensor value or sensor limit is not valid (Not a Number) */
+
+/*
+ * Master/Slave
+ */
+enum nt_bonding_type_e {
+	NT_BONDING_UNKNOWN, /* Unknown bonding type */
+	NT_BONDING_MASTER, /* Adapter is master in the bonding */
+	NT_BONDING_SLAVE, /* Adapter is slave in the bonding */
+	NT_BONDING_PEER /* Adapter is bonded, but relationship is symmetric */
+};
+
+enum nt_sensors_e {
+	/* Public sensors (Level 0) */
+	NT_SENSOR_FPGA_TEMP, /* FPGA temperature sensor */
+};
+
+/*
+ * Adapter types
+ */
+enum nt_adapter_type_e {
+	NT_ADAPTER_TYPE_UNKNOWN = 0, /* Unknown adapter type */
+	NT_ADAPTER_TYPE_NT4E, /* NT4E network adapter */
+	NT_ADAPTER_TYPE_NT20E, /* NT20E network adapter */
+	NT_ADAPTER_TYPE_NT4E_STD, /* NT4E-STD network adapter */
+	NT_ADAPTER_TYPE_NT4E_PORT, /* NTPORT4E expansion adapter */
+	NT_ADAPTER_TYPE_NTBPE, /* NTBPE bypass adapter */
+	NT_ADAPTER_TYPE_NT20E2, /* NT20E2 network adapter */
+	NT_ADAPTER_TYPE_RESERVED1, /* Reserved */
+	NT_ADAPTER_TYPE_RESERVED2, /* Reserved */
+	NT_ADAPTER_TYPE_NT40E2_1, /* NT40E2-1 network adapter */
+	NT_ADAPTER_TYPE_NT40E2_4, /* NT40E2-4 network adapter */
+	NT_ADAPTER_TYPE_NT4E2_4T_BP, /* NT4E2-4T-BP bypass network adapter */
+	NT_ADAPTER_TYPE_NT4E2_4_PTP, /* NT4E2-4 PTP network adapter with IEEE1588 */
+	NT_ADAPTER_TYPE_NT20E2_PTP, /* NT20E2 PTP network adapter with IEEE1588 */
+	NT_ADAPTER_TYPE_NT40E3_4_PTP, /* NT40E3 network adapter with IEEE1588 */
+	NT_ADAPTER_TYPE_NT100E3_1_PTP, /* NT100E3 network adapter with IEEE1588 */
+	NT_ADAPTER_TYPE_NT20E3_2_PTP, /* NT20E3 network adapter with IEEE1588 */
+	NT_ADAPTER_TYPE_NT80E3_2_PTP, /* NT80E3 network adapter with IEEE1588 */
+	NT_ADAPTER_TYPE_NT200E3_2, /* NT200E3 network adapter */
+	NT_ADAPTER_TYPE_NT200A01, /* NT200A01 network adapter */
+	NT_ADAPTER_TYPE_NT200A01_2X100 =
+		NT_ADAPTER_TYPE_NT200A01, /* NT200A01 2 x 100 Gbps network adapter */
+	NT_ADAPTER_TYPE_NT40A01_4X1, /* NT40A01_4X1 network adapter with IEEE1588 */
+	NT_ADAPTER_TYPE_NT200A01_2X40, /* NT200A01 2 x 40 Gbps network adapter */
+	NT_ADAPTER_TYPE_NT80E3_2_PTP_8X10, /* NT80E3 8 x 10 Gbps network adapter with IEEE1588 */
+	/*  */
+	NT_ADAPTER_TYPE_INTEL_A10_4X10, /* Intel PAC A10 GX 4 x 10 Gbps network adapter */
+	NT_ADAPTER_TYPE_INTEL_A10_1X40, /* Intel PAC A10 GX 1 x 40 Gbps network adapter */
+	/*  */
+	NT_ADAPTER_TYPE_NT200A01_8X10, /* NT200A01 8 x 10 Gbps network adapter */
+	NT_ADAPTER_TYPE_NT200A02_2X100, /* NT200A02 2 x 100 Gbps network adapter */
+	NT_ADAPTER_TYPE_NT200A02_2X40, /* NT200A02 2 x 40 Gbps network adapter */
+	NT_ADAPTER_TYPE_NT200A01_2X25, /* Deprecated */
+	NT_ADAPTER_TYPE_NT200A01_2X10_25 =
+		NT_ADAPTER_TYPE_NT200A01_2X25, /* NT200A01 2 x 10/25 Gbps network adapter */
+	NT_ADAPTER_TYPE_NT200A02_2X25, /* Deprecated */
+	NT_ADAPTER_TYPE_NT200A02_2X10_25 =
+		NT_ADAPTER_TYPE_NT200A02_2X25, /* NT200A02 2 x 10/25 Gbps network adapter */
+	NT_ADAPTER_TYPE_NT200A02_4X25, /* Deprecated */
+	NT_ADAPTER_TYPE_NT200A02_4X10_25 =
+		NT_ADAPTER_TYPE_NT200A02_4X25, /* NT200A02 4 x 10/25 Gbps network adapter */
+	NT_ADAPTER_TYPE_NT200A02_8X10, /* NT200A02 8 x 10 Gbps network adapter */
+	NT_ADAPTER_TYPE_NT50B01_2X25, /* Deprecated */
+	NT_ADAPTER_TYPE_NT50B01_2X10_25 =
+		NT_ADAPTER_TYPE_NT50B01_2X25, /* NT50B01 2 x 10/25 Gbps network adapter */
+	NT_ADAPTER_TYPE_NT200A02_2X1_10, /* NT200A02 2 x 1/10 Gbps network adapter */
+	NT_ADAPTER_TYPE_NT100A01_4X1_10, /* NT100A01 4 x 1/10 Gbps network adapter */
+	NT_ADAPTER_TYPE_NT100A01_4X10_25, /* NT100A01 4 x 10/25 Gbps network adapter */
+	NT_ADAPTER_TYPE_NT50B01_2X1_10, /* NT50B01 2 x 1/10 Gbps network adapter */
+	NT_ADAPTER_TYPE_NT40A11_4X1_10, /* NT40A11 4 x 1/10 Gbps network adapter */
+#ifndef DOXYGEN_INTERNAL_ONLY
+	NT_ADAPTER_TYPE_ML605 = 10000, /* NT20E2 eval board */
+#endif
+	NT_ADAPTER_TYPE_4GARCH_HAMOA =
+		(1U
+		 << 29), /* Bit to mark to adapters as a 4GArch Hamoa adapter */
+	NT_ADAPTER_TYPE_4GARCH =
+		(1U << 30), /* Bit to mark to adapters as a 4GArch adapter */
+	/* NOTE: do *NOT* add normal adapters after the group bit mark enums */
+};
+
+/* The NT200E3 adapter sensor id's */
+typedef enum nt_sensors_adapter_nt200_e3_e {
+	/* Public sensors (Level 0) */
+	NT_SENSOR_NT200E3_FPGA_TEMP, /* FPGA temperature sensor */
+	NT_SENSOR_NT200E3_FAN_SPEED, /* FAN speed sensor */
+	/* MCU (Micro Controller Unit) temperature sensor located inside enclosure below FAN */
+	NT_SENSOR_NT200E3_MCU_TEMP,
+	NT_SENSOR_NT200E3_PSU0_TEMP, /* Power supply 0 temperature sensor */
+	NT_SENSOR_NT200E3_PSU1_TEMP, /* Power supply 1 temperature sensor */
+	NT_SENSOR_NT200E3_PCB_TEMP, /* PCB temperature sensor */
+
+	/* Diagnostic sensors (Level 1) */
+	/* Total power consumption (calculated value) - does not generate alarms */
+	NT_SENSOR_NT200E3_NT200E3_POWER,
+	/* FPGA power consumption (calculated value) - does not generate alarms */
+	NT_SENSOR_NT200E3_FPGA_POWER,
+	/* DDR4 RAM power consumption (calculated value) - does not generate alarms */
+	NT_SENSOR_NT200E3_DDR4_POWER,
+	/* NIM power consumption (calculated value) - does not generate alarms */
+	NT_SENSOR_NT200E3_NIM_POWER,
+
+	NT_SENSOR_NT200E3_L1_MAX, /* Number of NT200E3 level 0,1 board sensors */
+} nt_sensors_adapter_nt200_e3_t;
+
+/*
+ * The following sensors are deprecated - generic types should be used instead
+ * The NIM temperature sensor must be the one with the lowest sensor_index
+ * (enum value) in order to be shown by the monitoring tool in port mode
+ */
+enum nt_sensors_port_nt200_e3_2_e {
+	/* Public sensors */
+	NT_SENSOR_NT200E3_NIM, /* QSFP28 temperature sensor */
+
+	/* Diagnostic sensors (Level 1) */
+	NT_SENSOR_NT200E3_SUPPLY, /* QSFP28 supply voltage sensor */
+	NT_SENSOR_NT200E3_TX_BIAS1, /* QSFP28 TX bias line 0 current sensor */
+	NT_SENSOR_NT200E3_TX_BIAS2, /* QSFP28 TX bias line 1 current sensor */
+	NT_SENSOR_NT200E3_TX_BIAS3, /* QSFP28 TX bias line 2 current sensor */
+	NT_SENSOR_NT200E3_TX_BIAS4, /* QSFP28 TX bias line 3 current sensor */
+	NT_SENSOR_NT200E3_RX1, /* QSFP28 RX line 0 power sensor */
+	NT_SENSOR_NT200E3_RX2, /* QSFP28 RX line 1 power sensor */
+	NT_SENSOR_NT200E3_RX3, /* QSFP28 RX line 2 power sensor */
+	NT_SENSOR_NT200E3_RX4, /* QSFP28 RX line 3 power sensor */
+	NT_SENSOR_NT200E3_TX1, /* QSFP28 TX line 0 power sensor */
+	NT_SENSOR_NT200E3_TX2, /* QSFP28 TX line 1 power sensor */
+	NT_SENSOR_NT200E3_TX3, /* QSFP28 TX line 2 power sensor */
+	NT_SENSOR_NT200E3_TX4, /* QSFP28 TX line 3 power sensor */
+	NT_SENSOR_NT200E3_PORT_MAX, /* Number of NT200E3 port sensors */
+};
+
+#endif
diff --git a/drivers/net/ntnic/sensors/sensors.c b/drivers/net/ntnic/sensors/sensors.c
new file mode 100644
index 0000000000..2a85843196
--- /dev/null
+++ b/drivers/net/ntnic/sensors/sensors.c
@@ -0,0 +1,273 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "sensors.h"
+#include "ntlog.h"
+
+void sensor_deinit(struct nt_sensor_group *sg)
+{
+	if (sg) {
+		if (sg->sensor)
+			free(sg->sensor);
+		if (sg->monitor)
+			free(sg->monitor);
+		free(sg);
+	}
+}
+
+struct nt_adapter_sensor *
+allocate_sensor(uint8_t adapter_or_port_index, const char *p_name,
+		enum nt_sensor_source_e ssrc, enum nt_sensor_type_e type,
+		unsigned int index, enum nt_sensor_event_alarm_e event_alarm,
+		enum sensor_mon_sign si)
+{
+	struct nt_adapter_sensor *sensor =
+		(struct nt_adapter_sensor *)malloc(sizeof(struct nt_adapter_sensor));
+	if (sensor == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: sensor is NULL", __func__);
+		return NULL;
+	}
+
+	sensor->alarm = event_alarm;
+	sensor->m_enable_alarm = true;
+	sensor->m_intf_no = 0xFF;
+	sensor->m_adapter_no = 0xFF;
+	sensor->si = si;
+
+	sensor->info.source = ssrc;
+	sensor->info.source_index = adapter_or_port_index;
+	sensor->info.sensor_index = index;
+	sensor->info.type = type;
+	sensor->info.sub_type = NT_SENSOR_SUBTYPE_NA;
+	sensor->info.state = NT_SENSOR_STATE_INITIALIZING;
+	sensor->info.value = NT_SENSOR_NAN;
+	sensor->info.value_lowest = NT_SENSOR_NAN;
+	sensor->info.value_highest = NT_SENSOR_NAN;
+	memset(sensor->info.name, 0, NT_INFO_SENSOR_NAME);
+	memcpy(sensor->info.name, p_name,
+	       (strlen(p_name) > NT_INFO_SENSOR_NAME) ? NT_INFO_SENSOR_NAME :
+	       strlen(p_name));
+	sensor->info.name[NT_INFO_SENSOR_NAME] = '\0';
+
+	return sensor;
+}
+
+void update_sensor_value(struct nt_adapter_sensor *sensor, int32_t value)
+{
+	if (sensor == NULL)
+		return;
+	sensor->info.value = value;
+	if (sensor->info.value_highest < value ||
+			(unsigned int)sensor->info.value_highest == NT_SENSOR_NAN)
+		sensor->info.value_highest = value;
+	if (sensor->info.value_lowest > value ||
+			(unsigned int)sensor->info.value_lowest == NT_SENSOR_NAN)
+		sensor->info.value_lowest = value;
+}
+
+struct nt_adapter_sensor *
+allocate_sensor_by_description(uint8_t adapter_or_port_index,
+			       enum nt_sensor_source_e ssrc,
+			       struct nt_adapter_sensor_description *descr)
+{
+	struct nt_adapter_sensor *sensor =
+		(struct nt_adapter_sensor *)malloc(sizeof(struct nt_adapter_sensor));
+	if (sensor == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: sensor is NULL", __func__);
+		return NULL;
+	}
+
+	sensor->alarm = descr->event_alarm;
+	sensor->m_enable_alarm = true;
+	sensor->m_intf_no = 0xFF;
+	sensor->m_adapter_no = 0xFF;
+	sensor->si = SENSOR_MON_UNSIGNED;
+
+	sensor->info.source_index = adapter_or_port_index;
+	sensor->info.source = ssrc;
+	sensor->info.type = descr->type;
+	sensor->info.sensor_index = descr->index;
+	memset(sensor->info.name, 0, NT_INFO_SENSOR_NAME);
+	memcpy(sensor->info.name, descr->name,
+	       (strlen(descr->name) > NT_INFO_SENSOR_NAME) ?
+	       NT_INFO_SENSOR_NAME :
+	       strlen(descr->name));
+	sensor->info.name[NT_INFO_SENSOR_NAME] = '\0';
+
+	return sensor;
+}
+
+void init_sensor_group(struct nt_sensor_group *sg)
+{
+	/* Set all pointers to NULL */
+	sg->sensor = NULL;
+	sg->monitor = NULL;
+	sg->next = NULL;
+	sg->read = NULL;
+	sg->conv_func = NULL;
+}
+
+/* Getters */
+int32_t get_value(struct nt_sensor_group *sg)
+{
+	return sg->sensor->info.value;
+};
+
+int32_t get_lowest(struct nt_sensor_group *sg)
+{
+	return sg->sensor->info.value_lowest;
+};
+
+int32_t get_highest(struct nt_sensor_group *sg)
+{
+	return sg->sensor->info.value_highest;
+};
+
+char *get_name(struct nt_sensor_group *sg)
+{
+	return sg->sensor->info.name;
+};
+
+/* Conversion functions */
+int null_signed(uint32_t p_sensor_result)
+{
+	return (int16_t)p_sensor_result;
+}
+
+int null_unsigned(uint32_t p_sensor_result)
+{
+	return (uint16_t)p_sensor_result;
+}
+
+/*
+ * ******************************************************************************
+ * For EXAR7724: Convert a read Vch value to Napatech internal representation
+ * Doc: Vout = ReadVal * 0.015 (PRESCALE is accounted for)
+ * ******************************************************************************
+ */
+int exar7724_vch(uint32_t p_sensor_result)
+{
+	return p_sensor_result * 15; /* NT unit: 1mV */
+}
+
+/*
+ * ******************************************************************************
+ * For EXAR7724: Convert a read Vin value to Napatech internal representation
+ * Doc: Vout = ReadVal * 0.0125
+ * ******************************************************************************
+ */
+int exar7724_vin(uint32_t p_sensor_result)
+{
+	return (p_sensor_result * 25) / 2; /* NT unit: 1mV */
+}
+
+/*
+ * ******************************************************************************
+ * For EXAR7724: Convert a read Tj value to Napatech internal representation
+ * Doc: Temp (in Kelvin) = (((ReadVal * 10mV) - 600mV) / (2mV/K)) + 300K =
+ *                      = ReadVal * 5K
+ * ******************************************************************************
+ */
+int exar7724_tj(uint32_t p_sensor_result)
+{
+	/*
+	 * A value of 2730 is used instead of 2732 which is more correct but since
+	 * the temperature step is 5 degrees it is more natural to show these steps
+	 */
+	return p_sensor_result * 50 - 2730; /* NT unit: 0.1C */
+}
+
+/*
+ * ******************************************************************************
+ * Conversion function for Linear Tecnology Linear_5s_11s format.
+ * The functions returns Y * 2**N, where N = b[15:11] is a 5-bit two's complement
+ * integer and Y = b[10:0] is an 11-bit two's complement integer.
+ * The multiplier value is used for scaling to Napatech units.
+ * ******************************************************************************
+ */
+static int conv5s_11s(uint16_t value, int multiplier)
+{
+	int n, y;
+
+	y = value & 0x07FF;
+
+	if (value & 0x0400)
+		y -= 0x0800; /* The MSBit is a sign bit */
+
+	n = (value >> 11) & 0x1F;
+
+	if (n & 0x10)
+		n -= 0x20; /* The MSBit is a sign bit */
+
+	y *= multiplier;
+
+	if (n > 0)
+		y *= (1 << n);
+
+	else if (n < 0)
+		y /= (1 << (-n));
+
+	return y;
+}
+
+/*
+ * ******************************************************************************
+ * Temperature conversion from Linear_5s_11s format.
+ * ******************************************************************************
+ */
+int ltm4676_tj(uint32_t p_sensor_result)
+{
+	return (uint16_t)conv5s_11s(p_sensor_result, 10); /* NT unit: 0.1C */
+}
+
+/*
+ * ******************************************************************************
+ * For MP2886a: Convert a read Tj value to Napatech internal representation
+ * ******************************************************************************
+ */
+int mp2886a_tj(uint32_t p_sensor_result)
+{
+	/*
+	 * MPS-2886p: READ_TEMPERATURE (register 0x8Dh)
+	 * READ_TEMPERATURE is a 2-byte, unsigned integer.
+	 */
+	return (uint16_t)p_sensor_result; /* NT unit: 0.1C */
+}
+
+/*
+ * ******************************************************************************
+ * For MAX6642: Convert a read temperature value to Napatech internal representation
+ * ******************************************************************************
+ */
+int max6642_t(uint32_t p_sensor_result)
+{
+	if ((p_sensor_result >> 8) == 0xFF)
+		return NT_SENSOR_NAN;
+
+	/* The six lower bits are not used */
+	return (int)(((p_sensor_result >> 6) * 5) /
+		     2); /* NT unit: 0.25 deg, Native unit: 0.1C */
+}
+
+/*
+ * ******************************************************************************
+ * For DS1775: Convert a read temperature value to Napatech internal representation
+ * ******************************************************************************
+ */
+int ds1775_t(uint32_t p_sensor_result)
+{
+	return (p_sensor_result * 10) /
+	       256; /* NT unit: 0.1 deg, Native unit: 1/256 C */
+}
+
+/*
+ * ******************************************************************************
+ * For FAN: Convert a tick count to RPM
+ * NT unit: RPM, Native unit: 2 ticks/revolution
+ * ******************************************************************************
+ */
+int fan(uint32_t p_sensor_result)
+{
+	return (p_sensor_result * 60U / 4);
+}
diff --git a/drivers/net/ntnic/sensors/sensors.h b/drivers/net/ntnic/sensors/sensors.h
new file mode 100644
index 0000000000..1424b8bc83
--- /dev/null
+++ b/drivers/net/ntnic/sensors/sensors.h
@@ -0,0 +1,127 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _SENSORS_H
+#define _SENSORS_H
+
+#include "sensor_types.h"
+#include "stream_info.h"
+#include "nthw_platform_drv.h"
+#include "nthw_drv.h"
+#include "nthw_spi_v3.h"
+#include "nthw_fpga_model.h"
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <limits.h>
+#include "avr_intf.h"
+
+enum nt_sensor_event_alarm_e {
+	NT_SENSOR_ENABLE_ALARM,
+	NT_SENSOR_LOG_ALARM,
+	NT_SENSOR_DISABLE_ALARM,
+};
+
+/*
+ * Sensor Class types
+ */
+enum nt_sensor_class_e {
+	NT_SENSOR_CLASS_FPGA =
+		0, /* Class for FPGA based sensors e.g FPGA temperature */
+	NT_SENSOR_CLASS_MCU =
+		1, /* Class for MCU based sensors e.g MCU temperature */
+	NT_SENSOR_CLASS_PSU =
+		2, /* Class for PSU based sensors e.g PSU temperature */
+	NT_SENSOR_CLASS_PCB =
+		3, /* Class for PCB based sensors e.g PCB temperature */
+	NT_SENSOR_CLASS_NIM =
+		4, /* Class for NIM based sensors e.g NIM temperature */
+	NT_SENSOR_CLASS_ANY = 5, /* Class for ANY sensors e.g any sensors */
+};
+
+typedef enum nt_sensor_class_e nt_sensor_class_t;
+
+/*
+ * Port of the sensor class
+ */
+struct nt_adapter_sensor {
+	uint8_t m_adapter_no;
+	uint8_t m_intf_no;
+	uint8_t fpga_idx; /* for AVR sensors */
+	enum sensor_mon_sign si;
+	struct nt_info_sensor_s info;
+	enum nt_sensor_event_alarm_e alarm;
+	bool m_enable_alarm;
+};
+
+struct nt_fpga_sensor_monitor {
+	nt_fpga_t *fpga;
+	nt_module_t *mod;
+
+	nt_register_t *reg;
+	nt_field_t **fields;
+	uint8_t fields_num;
+};
+
+/*
+ * Sensor description.
+ * Describe the static behavior of the sensor.
+ */
+struct nt_adapter_sensor_description {
+	enum nt_sensor_type_e type; /* Sensor type. */
+	enum nt_sensor_sub_type_e sub_type; /* Sensor subtype (if any applicable) */
+	unsigned int index; /* Sensor group index. */
+	enum nt_sensor_event_alarm_e event_alarm; /* Enable/Disable event alarm */
+	char name[20]; /* Sensor name. */
+};
+
+struct nt_sensor_group {
+	struct nt_adapter_sensor *sensor;
+	struct nt_fpga_sensor_monitor *monitor;
+	void (*read)(struct nt_sensor_group *sg, nthw_spis_t *t_spi);
+
+	/* conv params are needed to call current conversion functions */
+	int (*conv_func)(uint32_t p_sensor_result);
+	/* i2c interface for NIM sensors */
+
+	struct nt_sensor_group *next;
+};
+
+void init_sensor_group(struct nt_sensor_group *sg);
+
+void update_sensor_value(struct nt_adapter_sensor *sensor, int32_t value);
+
+void sensor_deinit(struct nt_sensor_group *sg);
+
+/* getters */
+int32_t get_value(struct nt_sensor_group *sg);
+int32_t get_lowest(struct nt_sensor_group *sg);
+int32_t get_highest(struct nt_sensor_group *sg);
+char *get_name(struct nt_sensor_group *sg);
+
+struct nt_adapter_sensor *
+allocate_sensor(uint8_t adapter_or_port_index, const char *p_name,
+		enum nt_sensor_source_e ssrc, enum nt_sensor_type_e type,
+		unsigned int index, enum nt_sensor_event_alarm_e event_alarm,
+		enum sensor_mon_sign si);
+struct nt_adapter_sensor *
+allocate_sensor_by_description(uint8_t adapter_or_port_index,
+			       enum nt_sensor_source_e ssrc,
+			       struct nt_adapter_sensor_description *descr);
+
+/* conversion functions */
+int null_signed(uint32_t p_sensor_result);
+int null_unsigned(uint32_t p_sensor_result);
+int exar7724_tj(uint32_t p_sensor_result);
+int max6642_t(uint32_t p_sensor_result);
+int ds1775_t(uint32_t p_sensor_result);
+int ltm4676_tj(uint32_t p_sensor_result);
+int exar7724_vch(uint32_t p_sensor_result);
+int exar7724_vin(uint32_t p_sensor_result);
+int mp2886a_tj(uint32_t p_sensor_result);
+int fan(uint32_t p_sensor_result);
+
+#endif /* _SENSORS_H */
diff --git a/drivers/net/ntnic/sensors/stream_info.h b/drivers/net/ntnic/sensors/stream_info.h
new file mode 100644
index 0000000000..b94231fd8b
--- /dev/null
+++ b/drivers/net/ntnic/sensors/stream_info.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _STREAM_INFO_H
+#define _STREAM_INFO_H
+
+#include "sensor_types.h"
+
+#include <stdint.h>
+
+/*
+ * This structure will return the sensor specific information
+ *
+ * The units used for the fields: value, value_lowest, value_highest, limit_low and
+ * limit_high depend on the type field. See @ref nt_sensor_type_e.
+ *
+ * For the limit_low and limit_high fields the following applies:\n
+ * If the sensor is located in a NIM (Network Interface Module), the limits are read
+ * from the NIM module via the DMI (Diagnostic Monitoring Interface) from the alarm
+ * and warning thresholds section, and the units are changed to internal representation.
+ * Only the alarm thresholds are used and are read only once during initialization.
+ * The limits cannot be changed.
+ *
+ * The value field is updated internally on a regular basis and is also based on a
+ * value read from the NIM which is also changed to internal representation.
+ *
+ * Not all NIM types support DMI data, and its presence must be determined by reading an
+ * option flag. In general, a NIM can read out: temperature, supply voltage,
+ * TX bias, TX optical power and RX optical power but not all NIM types support all
+ * 5 values.
+ *
+ * If external calibration is used (most NIM use internal calibration), both the
+ * current value and the threshold values are subjected to the specified calibration
+ * along with the change to internal calibration.
+ */
+#define NT_INFO_SENSOR_NAME 50
+struct nt_info_sensor_s {
+	enum nt_sensor_source_e
+	source; /* The source of the sensor (port or adapter on which the sensor resides) */
+	/*
+	 * The source index - the adapter number for adapter sensors and port number for port
+	 * sensors
+	 */
+	uint32_t source_index;
+	/*
+	 * The sensor index within the source index (sensor number on the adapter or sensor number
+	 * on the port)
+	 */
+	uint32_t sensor_index;
+	enum nt_sensor_type_e type; /* The sensor type */
+	enum nt_sensor_sub_type_e sub_type; /* The sensor subtype (if applicable) */
+	enum nt_sensor_state_e state; /* The current state (normal or alarm) */
+	int32_t value; /* The current value */
+	int32_t value_lowest; /* The lowest value registered */
+	int32_t value_highest; /* The highest value registered */
+	char name[NT_INFO_SENSOR_NAME + 1]; /* The sensor name */
+	enum nt_adapter_type_e
+	adapter_type; /* The adapter type where the sensor resides */
+};
+
+/* The NT200A02 adapter sensor id's */
+enum nt_sensors_adapter_nt200a02_e {
+	/* Public sensors (Level 0) */
+	NT_SENSOR_NT200A02_FPGA_TEMP, /* FPGA temperature sensor */
+	NT_SENSOR_NT200A02_FAN_SPEED, /* FAN speed sensor */
+
+	NT_SENSOR_NT200A02_MCU_TEMP,
+	NT_SENSOR_NT200A02_PSU0_TEMP, /* Power supply 0 temperature sensor */
+	NT_SENSOR_NT200A02_PSU1_TEMP, /* Power supply 1 temperature sensor */
+	NT_SENSOR_NT200A02_PCB_TEMP, /* PCB temperature sensor */
+
+	/* Diagnostic sensors (Level 1) */
+	/* Total power consumption (calculated value) - does not generate alarms */
+	NT_SENSOR_NT200A02_NT200A02_POWER,
+	/* FPGA power consumption (calculated value) - does not generate alarms */
+	NT_SENSOR_NT200A02_FPGA_POWER,
+	/* DDR4 RAM power consumption (calculated value) - does not generate alarms */
+	NT_SENSOR_NT200A02_DDR4_POWER,
+	/* NIM power consumption (calculated value) - does not generate alarms */
+	NT_SENSOR_NT200A02_NIM_POWER,
+
+	NT_SENSOR_NT200A02_L1_MAX, /* Number of NT200A01 level 0,1 board sensors */
+};
+
+#endif
-- 
2.39.3


  parent reply	other threads:[~2023-08-29  8:15 UTC|newest]

Thread overview: 142+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-08-16 13:25 [PATCH 1/8] net/ntnic: initial commit which adds register defines Mykola Kostenok
2023-08-16 13:25 ` [PATCH 2/8] net/ntnic: adds core registers and fpga functionality Mykola Kostenok
2023-08-16 13:25 ` [PATCH 3/8] net/ntnic: adds NT200A02 adapter support Mykola Kostenok
2023-08-16 13:25 ` [PATCH 4/8] net/ntnic: adds flow related FPGA functionality Mykola Kostenok
2023-08-16 13:25 ` [PATCH 5/8] net/ntnic: adds FPGA abstraction layer Mykola Kostenok
2023-08-16 13:25 ` [PATCH 6/8] net/ntnic: adds flow logic Mykola Kostenok
2023-08-16 13:25 ` [PATCH 7/8] net/ntnic: adds ethdev and makes PMD available Mykola Kostenok
2023-08-16 13:25 ` [PATCH 8/8] net/ntnic: adds socket connection to PMD Mykola Kostenok
2023-08-16 14:46   ` Stephen Hemminger
2023-08-25 13:52     ` Christian Koue Muf
2023-08-16 14:47   ` Stephen Hemminger
2023-08-17 14:43 ` [PATCH v2 1/8] net/ntnic: initial commit which adds register defines Mykola Kostenok
2023-08-17 14:43   ` [PATCH v2 2/8] net/ntnic: adds core registers and fpga functionality Mykola Kostenok
2023-08-17 14:43   ` [PATCH v2 3/8] net/ntnic: adds NT200A02 adapter support Mykola Kostenok
2023-08-17 14:43   ` [PATCH v2 4/8] net/ntnic: adds flow related FPGA functionality Mykola Kostenok
2023-08-17 14:43   ` [PATCH v2 5/8] net/ntnic: adds FPGA abstraction layer Mykola Kostenok
2023-08-17 14:43   ` [PATCH v2 6/8] net/ntnic: adds flow logic Mykola Kostenok
2023-08-17 14:43   ` [PATCH v2 7/8] net/ntnic: adds ethdev and makes PMD available Mykola Kostenok
2023-08-17 14:43   ` [PATCH v2 8/8] net/ntnic: adds socket connection to PMD Mykola Kostenok
2023-08-17 22:08   ` [PATCH v2 1/8] net/ntnic: initial commit which adds register defines Tyler Retzlaff
2023-08-18 11:01     ` Mykola Kostenok
2023-08-18 18:41 ` [PATCH v4 " Mykola Kostenok
2023-08-18 18:41   ` [PATCH v4 2/8] net/ntnic: adds core registers and fpga functionality Mykola Kostenok
2023-08-18 18:41   ` [PATCH v4 3/8] net/ntnic: adds NT200A02 adapter support Mykola Kostenok
2023-08-18 18:41   ` [PATCH v4 4/8] net/ntnic: adds flow related FPGA functionality Mykola Kostenok
2023-08-18 18:41   ` [PATCH v4 5/8] net/ntnic: adds FPGA abstraction layer Mykola Kostenok
2023-08-18 18:41   ` [PATCH v4 6/8] net/ntnic: adds flow logic Mykola Kostenok
2023-08-18 18:41   ` [PATCH v4 7/8] net/ntnic: adds ethdev and makes PMD available Mykola Kostenok
2023-08-18 18:41   ` [PATCH v4 8/8] net/ntnic: adds socket connection to PMD Mykola Kostenok
2023-08-21 11:34 ` [PATCH v5 1/8] net/ntnic: initial commit which adds register defines Mykola Kostenok
2023-08-21 11:34   ` [PATCH v5 2/8] net/ntnic: adds core registers and fpga functionality Mykola Kostenok
2023-08-21 11:34   ` [PATCH v5 3/8] net/ntnic: adds NT200A02 adapter support Mykola Kostenok
2023-08-21 11:34   ` [PATCH v5 4/8] net/ntnic: adds flow related FPGA functionality Mykola Kostenok
2023-08-21 11:34   ` [PATCH v5 5/8] net/ntnic: adds FPGA abstraction layer Mykola Kostenok
2023-08-21 11:34   ` [PATCH v5 6/8] net/ntnic: adds flow logic Mykola Kostenok
2023-08-21 11:34   ` [PATCH v5 7/8] net/ntnic: adds ethdev and makes PMD available Mykola Kostenok
2023-08-21 11:34   ` [PATCH v5 8/8] net/ntnic: adds socket connection to PMD Mykola Kostenok
2023-08-21 13:54 ` [PATCH v6 1/8] net/ntnic: initial commit which adds register defines Mykola Kostenok
2023-08-21 13:54   ` [PATCH v6 2/8] net/ntnic: adds core registers and fpga functionality Mykola Kostenok
2023-08-21 13:54   ` [PATCH v6 3/8] net/ntnic: adds NT200A02 adapter support Mykola Kostenok
2023-08-21 13:54   ` [PATCH v6 4/8] net/ntnic: adds flow related FPGA functionality Mykola Kostenok
2023-08-21 13:54   ` [PATCH v6 5/8] net/ntnic: adds FPGA abstraction layer Mykola Kostenok
2023-08-21 13:54   ` [PATCH v6 6/8] net/ntnic: adds flow logic Mykola Kostenok
2023-08-21 13:54   ` [PATCH v6 7/8] net/ntnic: adds ethdev and makes PMD available Mykola Kostenok
2023-08-21 13:54   ` [PATCH v6 8/8] net/ntnic: adds socket connection to PMD Mykola Kostenok
2023-08-22 15:41 ` [PATCH v7 1/8] net/ntnic: initial commit which adds register defines Mykola Kostenok
2023-08-22 15:41   ` [PATCH v7 2/8] net/ntnic: adds core registers and fpga functionality Mykola Kostenok
2023-08-22 15:41   ` [PATCH v7 3/8] net/ntnic: adds NT200A02 adapter support Mykola Kostenok
2023-08-22 15:41   ` [PATCH v7 4/8] net/ntnic: adds flow related FPGA functionality Mykola Kostenok
2023-08-22 15:41   ` [PATCH v7 5/8] net/ntnic: adds FPGA abstraction layer Mykola Kostenok
2023-08-22 15:41   ` [PATCH v7 6/8] net/ntnic: adds flow logic Mykola Kostenok
2023-08-22 15:41   ` [PATCH v7 7/8] net/ntnic: adds ethdev and makes PMD available Mykola Kostenok
2023-08-29  8:13     ` David Marchand
2023-08-22 15:41   ` [PATCH v7 8/8] net/ntnic: adds socket connection to PMD Mykola Kostenok
2023-08-29  8:15 ` [PATCH v8 1/8] net/ntnic: initial commit which adds register defines Mykola Kostenok
2023-08-29  8:15   ` [PATCH v8 2/8] net/ntnic: adds core registers and fpga functionality Mykola Kostenok
2023-08-29  8:15   ` Mykola Kostenok [this message]
2023-08-29  8:15   ` [PATCH v8 4/8] net/ntnic: adds flow related FPGA functionality Mykola Kostenok
2023-08-29  8:15   ` [PATCH v8 5/8] net/ntnic: adds FPGA abstraction layer Mykola Kostenok
2023-08-29  8:15   ` [PATCH v8 6/8] net/ntnic: adds flow logic Mykola Kostenok
2023-08-29  8:15   ` [PATCH v8 7/8] net/ntnic: adds ethdev and makes PMD available Mykola Kostenok
2023-08-29  8:15   ` [PATCH v8 8/8] net/ntnic: adds socket connection to PMD Mykola Kostenok
2023-08-29 10:17 ` [PATCH v9 1/8] net/ntnic: initial commit which adds register defines Mykola Kostenok
2023-08-29 10:17   ` [PATCH v9 2/8] net/ntnic: adds core registers and fpga functionality Mykola Kostenok
2023-08-29 10:17   ` [PATCH v9 3/8] net/ntnic: adds NT200A02 adapter support Mykola Kostenok
2023-08-29 10:17   ` [PATCH v9 4/8] net/ntnic: adds flow related FPGA functionality Mykola Kostenok
2023-08-29 10:17   ` [PATCH v9 5/8] net/ntnic: adds FPGA abstraction layer Mykola Kostenok
2023-08-29 10:17   ` [PATCH v9 6/8] net/ntnic: adds flow logic Mykola Kostenok
2023-08-29 10:17   ` [PATCH v9 7/8] net/ntnic: adds ethdev and makes PMD available Mykola Kostenok
2023-08-29 10:17   ` [PATCH v9 8/8] net/ntnic: adds socket connection to PMD Mykola Kostenok
2023-08-30 16:51 ` [PATCH v10 1/8] net/ntnic: initial commit which adds register defines Mykola Kostenok
2023-08-30 16:51   ` [PATCH v10 2/8] net/ntnic: adds core registers and fpga functionality Mykola Kostenok
2023-08-30 16:51   ` [PATCH v10 3/8] net/ntnic: adds NT200A02 adapter support Mykola Kostenok
2023-08-30 16:51   ` [PATCH v10 4/8] net/ntnic: adds flow related FPGA functionality Mykola Kostenok
2023-08-30 16:51   ` [PATCH v10 5/8] net/ntnic: adds FPGA abstraction layer Mykola Kostenok
2023-08-30 16:51   ` [PATCH v10 6/8] net/ntnic: adds flow logic Mykola Kostenok
2023-08-30 16:51   ` [PATCH v10 7/8] net/ntnic: adds ethdev and makes PMD available Mykola Kostenok
2023-08-30 16:51   ` [PATCH v10 8/8] net/ntnic: adds socket connection to PMD Mykola Kostenok
2023-08-31 12:23 ` [PATCH v11 1/8] net/ntnic: initial commit which adds register defines Mykola Kostenok
2023-08-31 12:23   ` [PATCH v11 2/8] net/ntnic: adds core registers and fpga functionality Mykola Kostenok
2023-08-31 12:23   ` [PATCH v11 3/8] net/ntnic: adds NT200A02 adapter support Mykola Kostenok
2023-08-31 12:23   ` [PATCH v11 4/8] net/ntnic: adds flow related FPGA functionality Mykola Kostenok
2023-08-31 12:23   ` [PATCH v11 5/8] net/ntnic: adds FPGA abstraction layer Mykola Kostenok
2023-08-31 12:23   ` [PATCH v11 6/8] net/ntnic: adds flow logic Mykola Kostenok
2023-08-31 12:23   ` [PATCH v11 7/8] net/ntnic: adds ethdev and makes PMD available Mykola Kostenok
2023-08-31 12:23   ` [PATCH v11 8/8] net/ntnic: adds socket connection to PMD Mykola Kostenok
2023-08-31 13:51 ` [PATCH v12 1/8] net/ntnic: initial commit which adds register defines Mykola Kostenok
2023-08-31 13:51   ` [PATCH v12 2/8] net/ntnic: adds core registers and fpga functionality Mykola Kostenok
2023-08-31 13:51   ` [PATCH v12 3/8] net/ntnic: adds NT200A02 adapter support Mykola Kostenok
2023-08-31 13:51   ` [PATCH v12 4/8] net/ntnic: adds flow related FPGA functionality Mykola Kostenok
2023-08-31 13:51   ` [PATCH v12 5/8] net/ntnic: adds FPGA abstraction layer Mykola Kostenok
2023-08-31 13:51   ` [PATCH v12 6/8] net/ntnic: adds flow logic Mykola Kostenok
2023-08-31 13:51   ` [PATCH v12 7/8] net/ntnic: adds ethdev and makes PMD available Mykola Kostenok
2023-08-31 13:51   ` [PATCH v12 8/8] net/ntnic: adds socket connection to PMD Mykola Kostenok
2023-09-01 12:18 ` [PATCH v13 1/8] net/ntnic: initial commit which adds register defines Mykola Kostenok
2023-09-01 12:18   ` [PATCH v13 2/8] net/ntnic: adds core registers and fpga functionality Mykola Kostenok
2023-09-01 12:18   ` [PATCH v13 3/8] net/ntnic: adds NT200A02 adapter support Mykola Kostenok
2023-09-01 12:18   ` [PATCH v13 4/8] net/ntnic: adds flow related FPGA functionality Mykola Kostenok
2023-09-01 12:18   ` [PATCH v13 5/8] net/ntnic: adds FPGA abstraction layer Mykola Kostenok
2023-09-01 12:18   ` [PATCH v13 6/8] net/ntnic: adds flow logic Mykola Kostenok
2023-09-01 12:18   ` [PATCH v13 7/8] net/ntnic: adds ethdev and makes PMD available Mykola Kostenok
2023-09-01 12:18   ` [PATCH v13 8/8] net/ntnic: adds socket connection to PMD Mykola Kostenok
2023-09-02 17:26     ` Patrick Robb
2023-09-04 13:53 ` [PATCH v14 1/8] net/ntnic: initial commit which adds register defines Mykola Kostenok
2023-09-04 13:53   ` [PATCH v14 2/8] net/ntnic: adds core registers and fpga functionality Mykola Kostenok
2023-09-04 13:53   ` [PATCH v14 3/8] net/ntnic: adds NT200A02 adapter support Mykola Kostenok
2023-09-04 13:53   ` [PATCH v14 4/8] net/ntnic: adds flow related FPGA functionality Mykola Kostenok
2023-09-04 13:53   ` [PATCH v14 5/8] net/ntnic: adds FPGA abstraction layer Mykola Kostenok
2023-09-04 13:53   ` [PATCH v14 6/8] net/ntnic: adds flow logic Mykola Kostenok
2023-09-04 13:54   ` [PATCH v14 7/8] net/ntnic: adds ethdev and makes PMD available Mykola Kostenok
2023-09-04 13:54   ` [PATCH v14 8/8] net/ntnic: adds socket connection to PMD Mykola Kostenok
2023-09-05 14:54 ` [PATCH v15 1/8] net/ntnic: initial commit which adds register defines Mykola Kostenok
2023-09-05 14:54   ` [PATCH v15 2/8] net/ntnic: adds core registers and fpga functionality Mykola Kostenok
2023-09-05 14:54   ` [PATCH v15 3/8] net/ntnic: adds NT200A02 adapter support Mykola Kostenok
2023-09-05 14:54   ` [PATCH v15 4/8] net/ntnic: adds flow related FPGA functionality Mykola Kostenok
2023-09-05 14:54   ` [PATCH v15 5/8] net/ntnic: adds FPGA abstraction layer Mykola Kostenok
2023-09-05 14:54   ` [PATCH v15 6/8] net/ntnic: adds flow logic Mykola Kostenok
2023-09-05 14:54   ` [PATCH v15 7/8] net/ntnic: adds ethdev and makes PMD available Mykola Kostenok
2023-09-05 14:54   ` [PATCH v15 8/8] net/ntnic: adds socket connection to PMD Mykola Kostenok
2023-09-08 16:07 ` [PATCH v16 1/8] net/ntnic: initial commit which adds register defines Mykola Kostenok
2023-09-08 16:07   ` [PATCH v16 2/8] net/ntnic: adds core registers and fpga functionality Mykola Kostenok
2023-09-08 16:07   ` [PATCH v16 3/8] net/ntnic: adds NT200A02 adapter support Mykola Kostenok
2023-09-08 16:07   ` [PATCH v16 4/8] net/ntnic: adds flow related FPGA functionality Mykola Kostenok
2023-09-08 16:07   ` [PATCH v16 5/8] net/ntnic: adds FPGA abstraction layer Mykola Kostenok
2023-09-08 16:07   ` [PATCH v16 6/8] net/ntnic: adds flow logic Mykola Kostenok
2023-09-08 16:07   ` [PATCH v16 7/8] net/ntnic: adds ethdev and makes PMD available Mykola Kostenok
2023-09-08 16:07   ` [PATCH v16 8/8] net/ntnic: adds socket connection to PMD Mykola Kostenok
2023-09-15 15:54   ` [PATCH v16 1/8] net/ntnic: initial commit which adds register defines Ferruh Yigit
2023-09-15 18:37     ` Morten Brørup
2023-09-18  9:33       ` Ferruh Yigit
2023-09-19  9:06         ` Christian Koue Muf
2023-09-20  9:48           ` Ferruh Yigit
2023-09-20 13:17           ` Thomas Monjalon
2023-09-21 14:04             ` Ferruh Yigit
2023-09-29  9:21               ` Christian Koue Muf
2023-09-29  9:46                 ` Ferruh Yigit
2023-09-29 10:23                   ` Thomas Monjalon
2023-10-09  7:57                     ` Christian Koue Muf
2023-10-09  9:52                       ` Ferruh Yigit
2024-03-29 11:24                       ` Ferruh Yigit
2024-04-03 10:55                         ` Mykola Kostenok
2024-04-04 12:49                           ` Ferruh Yigit

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230829081510.944465-3-mko-plv@napatech.com \
    --to=mko-plv@napatech.com \
    --cc=ckm@napatech.com \
    --cc=dev@dpdk.org \
    --cc=thomas@monjalon.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.