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 6f48af601f..1571111bce 100644
--- a/drivers/net/ntnic/meson.build
+++ b/drivers/net/ntnic/meson.build
@@ -16,22 +16,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',
@@ -41,6 +58,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',
@@ -56,6 +74,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
