diff --git a/drivers/net/ntnic/include/nthw_bus.h b/drivers/net/ntnic/include/nthw_bus.h
new file mode 100644
index 0000000000..975cc95e78
--- /dev/null
+++ b/drivers/net/ntnic/include/nthw_bus.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_BUS_H__
+#define __NTHW_BUS_H__
+
+typedef uint8_t rab_bus_id_t;
+
+#endif /* __NTHW_BUS_H__ */
diff --git a/drivers/net/ntnic/meson.build b/drivers/net/ntnic/meson.build
index 99569c2843..65064f44ab 100644
--- a/drivers/net/ntnic/meson.build
+++ b/drivers/net/ntnic/meson.build
@@ -10,13 +10,45 @@ cflags += [
 includes = [
     include_directories('.'),
     include_directories('include'),
+    include_directories('ntlog/include'),
+    include_directories('ntutil/include'),
     include_directories('nthw'),
+    include_directories('nthw/core'),
     include_directories('nthw/supported'),
 ]
 
 # all sources
 sources = files(
+    '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_gpio_phy.c',
+    'nthw/core/nthw_hif.c',
+    'nthw/core/nthw_iic.c',
+    'nthw/core/nthw_mac_pcs.c',
+    'nthw/core/nthw_mac_pcs_xxv.c',
+    'nthw/core/nthw_pci_rd_tg.c',
+    'nthw/core/nthw_pci_ta.c',
+    'nthw/core/nthw_pci_wr_tg.c',
+    'nthw/core/nthw_pcie3.c',
+    'nthw/core/nthw_sdc.c',
+    'nthw/core/nthw_si5340.c',
+    'nthw/core/nthw_spi_v3.c',
+    'nthw/core/nthw_spim.c',
+    'nthw/core/nthw_spis.c',
+    'nthw/core/nthw_tsm.c',
+    'nthw/nthw_fpga_model.c',
+    'nthw/nthw_dbs.c',
+    'nthw/nthw_epp.c',
+    'nthw/nthw_platform.c',
+    'nthw/nthw_rac.c',
+    'nthw/nthw_stat.c',
     'nthw/supported/nthw_fpga_9563_055_024_0000.c',
+    'ntlog/ntlog.c',
+    'ntutil/nt_util.c',
 )
 
 if is_variable('default_cflags')
diff --git a/drivers/net/ntnic/nthw/core/nthw_clock_profiles.c b/drivers/net/ntnic/nthw/core/nthw_clock_profiles.c
new file mode 100644
index 0000000000..efdcc222a8
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_clock_profiles.c
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#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;
diff --git a/drivers/net/ntnic/nthw/core/nthw_clock_profiles.h b/drivers/net/ntnic/nthw/core/nthw_clock_profiles.h
new file mode 100644
index 0000000000..4252f69e92
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_clock_profiles.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_CLOCK_PROFILES_H__
+#define __NTHW_CLOCK_PROFILES_H__
+
+#include <stdint.h>
+
+#include "nthw_helper.h"
+
+#define clk_profile_size_error_msg "size test failed"
+
+typedef struct {
+	unsigned char reg_addr;
+	unsigned char reg_val;
+	unsigned char reg_mask;
+} clk_profile_data_fmt0_t;
+
+typedef struct {
+	uint16_t reg_addr;
+	uint8_t reg_val;
+} clk_profile_data_fmt1_t;
+
+typedef struct {
+	unsigned int reg_addr;
+	unsigned char reg_val;
+} clk_profile_data_fmt2_t;
+
+typedef enum {
+	CLK_PROFILE_DATA_FMT_0,
+	CLK_PROFILE_DATA_FMT_1,
+	CLK_PROFILE_DATA_FMT_2
+} clk_profile_data_fmt_t;
+
+extern const int n_data_si5340_nt200a02_u23_v5;
+extern const  clk_profile_data_fmt2_t *p_data_si5340_nt200a02_u23_v5;
+
+#endif /* __NTHW_CLOCK_PROFILES_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_core.h b/drivers/net/ntnic/nthw/core/nthw_core.h
new file mode 100644
index 0000000000..798a95d5cf
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_core.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_CORE_H__
+#define __NTHW_CORE_H__
+
+#include "nthw_helper.h"
+
+#include "nthw_platform_drv.h"
+#include "nthw_fpga_model.h"
+#include "nthw_hif.h"
+#include "nthw_pcie3.h"
+#include "nthw_pci_rd_tg.h"
+#include "nthw_pci_wr_tg.h"
+#include "nthw_pci_ta.h"
+#include "nthw_iic.h"
+
+#include "nthw_gpio_phy.h"
+#include "nthw_mac_pcs.h"
+#include "nthw_mac_pcs_xxv.h"
+#include "nthw_sdc.h"
+
+#include "nthw_spim.h"
+#include "nthw_spis.h"
+
+#include "nthw_tsm.h"
+
+#include "nthw_si5340.h"
+
+#endif /* __NTHW_CORE_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_fpga.c b/drivers/net/ntnic/nthw/core/nthw_fpga.c
new file mode 100644
index 0000000000..646d45b7eb
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_fpga.c
@@ -0,0 +1,914 @@
+/* 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_fpga.h"
+#include "nthw_fpga_instances.h"
+
+#include "nthw_spi_v3.h"
+
+#include <arpa/inet.h>
+
+int nthw_fpga_get_param_info(struct fpga_info_s *p_fpga_info, nt_fpga_t *p_fpga)
+{
+	const int n_nims = fpga_get_product_param(p_fpga, NT_NIMS, -1);
+	const int n_phy_ports = fpga_get_product_param(p_fpga, NT_PHY_PORTS, -1);
+	const int n_phy_quads = fpga_get_product_param(p_fpga, NT_PHY_QUADS, -1);
+	const int n_rx_ports = fpga_get_product_param(p_fpga, NT_RX_PORTS, -1);
+	const int n_tx_ports = fpga_get_product_param(p_fpga, NT_TX_PORTS, -1);
+
+	p_fpga_info->n_nims = n_nims;
+	p_fpga_info->n_phy_ports = n_phy_ports;
+	p_fpga_info->n_phy_quads = n_phy_quads;
+	p_fpga_info->n_rx_ports = n_rx_ports;
+	p_fpga_info->n_tx_ports = n_tx_ports;
+	p_fpga_info->profile = FPGA_INFO_PROFILE_UNKNOWN;
+
+	/* Check for VSWITCH FPGA */
+	if (fpga_get_product_param(p_fpga, NT_NFV_OVS_PRODUCT, 0) != 0)
+		p_fpga_info->profile = FPGA_INFO_PROFILE_VSWITCH;
+	/* Check for VSWITCH FPGA - legacy */
+	else if (fpga_get_product_param(p_fpga, NT_IOA_PRESENT, 0) != 0)
+		p_fpga_info->profile = FPGA_INFO_PROFILE_VSWITCH;
+
+	else if (fpga_get_product_param(p_fpga, NT_QM_PRESENT, 0) != 0)
+		p_fpga_info->profile = FPGA_INFO_PROFILE_CAPTURE;
+
+	else
+		p_fpga_info->profile = FPGA_INFO_PROFILE_INLINE;
+
+	return 0;
+}
+
+int nthw_fpga_iic_read_byte(nt_fpga_t *p_fpga, const int n_instance_no,
+			   const uint8_t n_dev_addr, const uint8_t n_reg_addr)
+{
+	nthw_iic_t nthw_iic;
+	uint8_t val = 0;
+
+	if (nthw_iic_init(&nthw_iic, p_fpga, n_instance_no, 8) != 0)
+		return -1;
+
+	if (nthw_iic_readbyte(&nthw_iic, n_dev_addr, n_reg_addr, 1, &val) == 0)
+		return val;
+
+	else
+		return -1;
+}
+
+int nthw_fpga_iic_write_byte(nt_fpga_t *p_fpga, const int n_instance_no,
+			    const uint8_t n_dev_addr, const uint8_t n_reg_addr,
+			    uint8_t val)
+{
+	nthw_iic_t nthw_iic;
+
+	if (nthw_iic_init(&nthw_iic, p_fpga, n_instance_no, 8) != 0)
+		return -1;
+
+	if (nthw_iic_writebyte(&nthw_iic, n_dev_addr, n_reg_addr, 1, &val) != 0)
+		return -1;
+
+	return 0;
+}
+
+int nthw_fpga_iic_scan(nt_fpga_t *p_fpga, const int n_instance_no_begin,
+		      const int n_instance_no_end)
+{
+	int i;
+
+	assert(n_instance_no_begin <= n_instance_no_end);
+
+	for (i = n_instance_no_begin; i <= n_instance_no_end; i++) {
+		nthw_iic_t *p_nthw_iic = nthw_iic_new();
+
+		if (p_nthw_iic) {
+			const int rc = nthw_iic_init(p_nthw_iic, p_fpga, i, 8);
+
+			if (rc == 0) {
+				nthw_iic_set_retry_params(p_nthw_iic, -1, 100, 100,
+						       3, 3);
+				nthw_iic_scan(p_nthw_iic);
+			}
+			nthw_iic_delete(p_nthw_iic);
+			p_nthw_iic = NULL;
+		}
+	}
+	return 0;
+}
+
+int nthw_fpga_silabs_detect(nt_fpga_t *p_fpga, const int n_instance_no,
+			   const int n_dev_addr, const int n_page_reg_addr)
+{
+	const char *const p_adapter_id_str _unused =
+		p_fpga->p_fpga_info->mp_adapter_id_str;
+	uint64_t ident = -1;
+	int res = -1;
+
+	nthw_iic_t *p_nthw_iic = nthw_iic_new();
+
+	if (p_nthw_iic) {
+		uint8_t data;
+		uint8_t a_silabs_ident[8];
+
+		nthw_iic_init(p_nthw_iic, p_fpga, n_instance_no, 8);
+
+		data = 0;
+		/* switch to page 0 */
+		nthw_iic_write_data(p_nthw_iic, (uint8_t)n_dev_addr,
+				  (uint8_t)n_page_reg_addr, 1, &data);
+		res = nthw_iic_read_data(p_nthw_iic, (uint8_t)n_dev_addr, 0x00,
+				       sizeof(a_silabs_ident), a_silabs_ident);
+		if (res == 0) {
+			int i;
+
+			for (i = 0; i < (int)sizeof(a_silabs_ident); i++) {
+				ident <<= 8;
+				ident |= a_silabs_ident[i];
+			}
+		}
+		nthw_iic_delete(p_nthw_iic);
+		p_nthw_iic = NULL;
+
+		/* Conclude SiLabs part */
+		if (res == 0) {
+			if (a_silabs_ident[3] == 0x53) {
+				if (a_silabs_ident[2] == 0x40)
+					res = 5340;
+				else if (a_silabs_ident[2] == 0x41)
+					res = 5341;
+			} else if (a_silabs_ident[2] == 38) {
+				res = 5338;
+			} else {
+				res = -1;
+			}
+		}
+	}
+
+	NT_LOG(DBG, NTHW, "%s: %016" PRIX64 ": %d\n", p_adapter_id_str, ident,
+	       res);
+	return res;
+}
+
+/*
+ * Calculate CRC-16-CCITT of passed data
+ * CRC-16-CCITT ^16 + ^12 + ^5 + 1 (0x1021) (X.25, HDLC, XMODEM, Bluetooth,
+ *   SD, many others; known as CRC-CCITT)
+ */
+static uint16_t crc16(uint8_t *buffer, size_t length)
+{
+	uint16_t seed = 0;
+
+	while (length--) {
+		seed = (uint16_t)(seed >> 8 | seed << 8);
+		seed = (uint16_t)(seed ^ *buffer++);
+		seed = (uint16_t)(seed ^ (seed & 0xff) >> 4);
+		seed = (uint16_t)(seed ^ seed << 8 << 4);
+		seed = (uint16_t)(seed ^ (seed & 0xff) << 4 << 1);
+	}
+	return seed;
+}
+
+int nthw_fpga_avr_probe(nt_fpga_t *p_fpga, const int n_instance_no)
+{
+	struct fpga_info_s *p_fpga_info = p_fpga->p_fpga_info;
+	const char *const p_adapter_id_str = p_fpga_info->mp_adapter_id_str;
+	nthw_spi_v3_t *p_avr_spi;
+	int res = -1;
+
+	p_avr_spi = nthw_spi_v3_new();
+	if (p_avr_spi) {
+		struct avr_vpd_info_s {
+			/* avr info */
+			uint32_t n_avr_spi_version;
+			uint8_t n_avr_fw_ver_major;
+			uint8_t n_avr_fw_ver_minor;
+			uint8_t n_avr_fw_ver_micro;
+			uint8_t a_avr_fw_ver_str[50];
+			uint8_t a_avr_fw_plat_id_str[20];
+
+			/* vpdEeprom_t */
+			uint8_t psu_hw_version;
+			uint8_t vpd_pn[GEN2_PN_SIZE];
+			uint8_t vpd_pba[GEN2_PBA_SIZE];
+			uint8_t vpd_sn[GEN2_SN_SIZE];
+			uint8_t vpd_board_name[GEN2_BNAME_SIZE];
+			uint8_t vpd_platform_section[GEN2_PLATFORM_SIZE];
+
+			/* BoardInfo_t aka vpd_platform_section: */
+			uint32_t product_family; /* uint8_t 1: capture, 2: Inline, 3: analysis */
+			uint32_t feature_mask; /* Bit 0: OC192 capable */
+			uint32_t invfeature_mask;
+			uint8_t no_of_macs;
+			uint8_t mac_address[6];
+			uint16_t custom_id;
+			uint8_t user_id[8];
+			/*
+			 * Reserved NT operations to monitor the reprogram count of userId with
+			 * vpduser
+			 */
+			uint16_t user_id_erase_write_count;
+
+			/*
+			 * AVR_OP_SYSINFO: struct version_sysinfo_request_container
+			 * Which version of the sysinfo container to retrieve. Set to zero to fetch
+			 * latest. offset zero of latest always contain an uint8_t version info
+			 */
+			uint8_t sysinfo_container_version;
+
+			/* AVR_OP_SYSINFO: struct AvrLibcVersion */
+			uint32_t sysinfo_avr_libc_version; /* The constant __AVR_LIBC_VERSION__ */
+
+			/* AVR_OP_SYSINFO: struct AvrLibcSignature */
+			uint8_t sysinfo_signature_0; /* The constant SIGNATURE_0 */
+			uint8_t sysinfo_signature_1; /* The constant SIGNATURE_1 */
+			uint8_t sysinfo_signature_2; /* The constant SIGNATURE_2 */
+
+			/* AVR_OP_SYSINFO: struct AvrOs */
+			uint8_t sysinfo_spi_version; /* SPI command layer version */
+			/*
+			 * Hardware revision. Locked to eeprom address zero. Is also available via
+			 * VPD read opcode (prior to v1.4b, this is required)
+			 */
+			uint8_t sysinfo_hw_revision;
+			/*
+			 * Number of ticks/second (Note: Be aware this may become zero if timer
+			 * module is rewritten to a tickles system!)
+			 */
+			uint8_t sysinfo_ticks_per_second;
+			uint32_t sysinfo_uptime; /* Uptime in seconds since last AVR reset */
+			uint8_t sysinfo_osccal; /* OSCCAL value */
+
+			/*
+			 * Meta data concluded/calculated from req/reply
+			 */
+			bool b_feature_mask_valid;
+			bool b_crc16_valid;
+			uint16_t n_crc16_stored;
+			uint16_t n_crc16_calced;
+			uint64_t n_mac_val;
+		};
+
+		struct avr_vpd_info_s avr_vpd_info;
+		struct tx_rx_buf tx_buf;
+		struct tx_rx_buf rx_buf;
+		char rx_data[MAX_AVR_CONTAINER_SIZE];
+		uint32_t u32;
+
+		memset(&avr_vpd_info, 0, sizeof(avr_vpd_info));
+
+		nthw_spi_v3_init(p_avr_spi, p_fpga, n_instance_no);
+
+		/* AVR_OP_SPI_VERSION */
+		tx_buf.size = 0;
+		tx_buf.p_buf = NULL;
+		rx_buf.size = sizeof(u32);
+		rx_buf.p_buf = &u32;
+		u32 = 0;
+		res = nthw_spi_v3_transfer(p_avr_spi, AVR_OP_SPI_VERSION, &tx_buf,
+					   &rx_buf);
+		avr_vpd_info.n_avr_spi_version = u32;
+		NT_LOG(DBG, NTHW, "%s: AVR%d: SPI_VER: %d\n", p_adapter_id_str,
+		       n_instance_no, avr_vpd_info.n_avr_spi_version);
+
+		/* AVR_OP_VERSION */
+		tx_buf.size = 0;
+		tx_buf.p_buf = NULL;
+		rx_buf.size = sizeof(rx_data);
+		rx_buf.p_buf = &rx_data;
+		res = nthw_spi_v3_transfer(p_avr_spi, AVR_OP_VERSION, &tx_buf,
+					   &rx_buf);
+
+		avr_vpd_info.n_avr_fw_ver_major = rx_data[0];
+		avr_vpd_info.n_avr_fw_ver_minor = rx_data[1];
+		avr_vpd_info.n_avr_fw_ver_micro = rx_data[2];
+		NT_LOG(DBG, NTHW, "%s: AVR%d: FW_VER: %c.%c.%c\n",
+		       p_adapter_id_str, n_instance_no, avr_vpd_info.n_avr_fw_ver_major,
+		       avr_vpd_info.n_avr_fw_ver_minor,
+		       avr_vpd_info.n_avr_fw_ver_micro);
+
+		memcpy(avr_vpd_info.a_avr_fw_ver_str, &rx_data[0 + 3],
+		       sizeof(avr_vpd_info.a_avr_fw_ver_str));
+		NT_LOG(DBG, NTHW, "%s: AVR%d: FW_VER_STR: '%.*s'\n",
+		       p_adapter_id_str, n_instance_no,
+		       (int)sizeof(avr_vpd_info.a_avr_fw_ver_str),
+		       avr_vpd_info.a_avr_fw_ver_str);
+
+		memcpy(avr_vpd_info.a_avr_fw_plat_id_str, &rx_data[0 + 3 + 50],
+		       sizeof(avr_vpd_info.a_avr_fw_plat_id_str));
+		NT_LOG(DBG, NTHW, "%s: AVR%d: FW_HW_ID_STR: '%.*s'\n",
+		       p_adapter_id_str, n_instance_no,
+		       (int)sizeof(avr_vpd_info.a_avr_fw_plat_id_str),
+		       avr_vpd_info.a_avr_fw_plat_id_str);
+
+		rte_strscpy(p_fpga_info->nthw_hw_info.hw_plat_id_str,
+			(char *)avr_vpd_info.a_avr_fw_plat_id_str,
+			sizeof(p_fpga_info->nthw_hw_info.hw_plat_id_str));
+		p_fpga_info->nthw_hw_info.hw_plat_id_str
+		[sizeof(p_fpga_info->nthw_hw_info.hw_plat_id_str) - 1] =
+			0;
+
+		/* AVR_OP_SYSINFO_2 */
+		tx_buf.size = 0;
+		tx_buf.p_buf = NULL;
+		rx_buf.size = sizeof(rx_data);
+		rx_buf.p_buf = &rx_data;
+		res = nthw_spi_v3_transfer(p_avr_spi, AVR_OP_SYSINFO_2, &tx_buf,
+					   &rx_buf);
+		if (res == 0 && avr_vpd_info.n_avr_spi_version >= 3 &&
+				rx_buf.size >= 16) {
+			if (rx_buf.size != 16) {
+				NT_LOG(WRN, NTHW,
+				       "%s: AVR%d: SYSINFO2: reply is larger than expected: %04X %04X\n",
+				       p_adapter_id_str, n_instance_no, rx_buf.size,
+				       16);
+			} else {
+				NT_LOG(DBG, NTHW,
+				       "%s: AVR%d: SYSINFO2: OK: res=%d sz=%d\n",
+				       p_adapter_id_str, n_instance_no, res,
+				       rx_buf.size);
+			}
+
+			avr_vpd_info.sysinfo_container_version = rx_data[0];
+			NT_LOG(DBG, NTHW, "%s: AVR%d: SYSINFO_REQ_VER: %d\n",
+			       p_adapter_id_str, n_instance_no,
+			       avr_vpd_info.sysinfo_container_version);
+
+			memcpy(&avr_vpd_info.sysinfo_avr_libc_version,
+			       &rx_data[0 + 1],
+			       sizeof(avr_vpd_info.sysinfo_avr_libc_version));
+			NT_LOG(DBG, NTHW, "%s: AVR%d: LIBC_VER: %d\n",
+			       p_adapter_id_str, n_instance_no,
+			       avr_vpd_info.sysinfo_avr_libc_version);
+
+			avr_vpd_info.sysinfo_signature_0 = rx_data[5];
+			avr_vpd_info.sysinfo_signature_1 = rx_data[6];
+			avr_vpd_info.sysinfo_signature_2 = rx_data[7];
+			NT_LOG(DBG, NTHW,
+			       "%s: AVR%d: SIGNATURE: %02x%02x%02x\n",
+			       p_adapter_id_str, n_instance_no,
+			       avr_vpd_info.sysinfo_signature_0,
+			       avr_vpd_info.sysinfo_signature_1,
+			       avr_vpd_info.sysinfo_signature_2);
+
+			avr_vpd_info.sysinfo_spi_version = rx_data[8];
+			NT_LOG(DBG, NTHW, "%s: AVR%d: SPI_VER: %d\n",
+			       p_adapter_id_str, n_instance_no,
+			       avr_vpd_info.sysinfo_spi_version);
+
+			avr_vpd_info.sysinfo_hw_revision = rx_data[9];
+			NT_LOG(DBG, NTHW, "%s: AVR%d: HW_REV: %d\n",
+			       p_adapter_id_str, n_instance_no,
+			       avr_vpd_info.sysinfo_hw_revision);
+
+			avr_vpd_info.sysinfo_ticks_per_second = rx_data[10];
+			NT_LOG(DBG, NTHW, "%s: AVR%d: TICKS_PER_SEC: %d\n",
+			       p_adapter_id_str, n_instance_no,
+			       avr_vpd_info.sysinfo_ticks_per_second);
+
+			memcpy(&avr_vpd_info.sysinfo_uptime, &rx_data[11],
+			       sizeof(avr_vpd_info.sysinfo_uptime));
+			NT_LOG(DBG, NTHW, "%s: AVR%d: UPTIME: %d\n",
+			       p_adapter_id_str, n_instance_no,
+			       avr_vpd_info.sysinfo_uptime);
+
+			avr_vpd_info.sysinfo_osccal = rx_data[15];
+			NT_LOG(DBG, NTHW, "%s: AVR%d: OSCCAL: %d\n",
+			       p_adapter_id_str, n_instance_no,
+			       avr_vpd_info.sysinfo_osccal);
+
+			{
+				bool b_spi_ver_match _unused =
+					(avr_vpd_info.n_avr_spi_version ==
+					 avr_vpd_info.sysinfo_spi_version);
+				NT_LOG(DBG, NTHW,
+				       "%s: AVR%d: SPI_VER_TST: %s (%d %d)\n",
+				       p_adapter_id_str, n_instance_no,
+				       (b_spi_ver_match ? "OK" : "MISMATCH"),
+				       avr_vpd_info.n_avr_spi_version,
+				       avr_vpd_info.sysinfo_spi_version);
+			}
+			/* SYSINFO2: if response: only populate hw_id not hw_id_emulated */
+			p_fpga_info->nthw_hw_info.hw_id =
+				avr_vpd_info.sysinfo_hw_revision;
+		} else {
+			/* AVR_OP_SYSINFO */
+			tx_buf.size = 0;
+			tx_buf.p_buf = NULL;
+			rx_buf.size = sizeof(rx_data);
+			rx_buf.p_buf = &rx_data;
+			res = nthw_spi_v3_transfer(p_avr_spi, AVR_OP_SYSINFO,
+						   &tx_buf, &rx_buf);
+			if (res == 0 && avr_vpd_info.n_avr_spi_version >= 3 &&
+					rx_buf.size >= 16) {
+				if (rx_buf.size != 16) {
+					NT_LOG(WRN, NTHW,
+					       "%s: AVR%d: SYSINFO: reply is larger than expected: %04X %04X\n",
+					       p_adapter_id_str, n_instance_no,
+					       rx_buf.size, 16);
+				} else {
+					NT_LOG(DBG, NTHW,
+					       "%s: AVR%d: SYSINFO: OK: res=%d sz=%d\n",
+					       p_adapter_id_str, n_instance_no, res,
+					       rx_buf.size);
+				}
+
+				avr_vpd_info.sysinfo_container_version =
+					rx_data[0];
+				NT_LOG(DBG, NTHW,
+				       "%s: AVR%d: SYSINFO_REQ_VER: %d\n",
+				       p_adapter_id_str, n_instance_no,
+				       avr_vpd_info.sysinfo_container_version);
+
+				memcpy(&avr_vpd_info.sysinfo_avr_libc_version,
+				       &rx_data[0 + 1],
+				       sizeof(avr_vpd_info
+					      .sysinfo_avr_libc_version));
+				NT_LOG(DBG, NTHW, "%s: AVR%d: LIBC_VER: %d\n",
+				       p_adapter_id_str, n_instance_no,
+				       avr_vpd_info.sysinfo_avr_libc_version);
+
+				avr_vpd_info.sysinfo_signature_0 = rx_data[5];
+				avr_vpd_info.sysinfo_signature_1 = rx_data[6];
+				avr_vpd_info.sysinfo_signature_2 = rx_data[7];
+				NT_LOG(DBG, NTHW,
+				       "%s: AVR%d: SIGNATURE: %02x%02x%02x\n",
+				       p_adapter_id_str, n_instance_no,
+				       avr_vpd_info.sysinfo_signature_0,
+				       avr_vpd_info.sysinfo_signature_1,
+				       avr_vpd_info.sysinfo_signature_2);
+
+				avr_vpd_info.sysinfo_spi_version = rx_data[8];
+				NT_LOG(DBG, NTHW, "%s: AVR%d: SPI_VER: %d\n",
+				       p_adapter_id_str, n_instance_no,
+				       avr_vpd_info.sysinfo_spi_version);
+
+				avr_vpd_info.sysinfo_hw_revision = rx_data[9];
+				NT_LOG(DBG, NTHW, "%s: AVR%d: HW_REV: %d\n",
+				       p_adapter_id_str, n_instance_no,
+				       avr_vpd_info.sysinfo_hw_revision);
+				NT_LOG(INF, NTHW, "%s: AVR%d: HW_REV: %d\n",
+				       p_adapter_id_str, n_instance_no,
+				       avr_vpd_info.sysinfo_hw_revision);
+
+				avr_vpd_info.sysinfo_ticks_per_second =
+					rx_data[10];
+				NT_LOG(DBG, NTHW,
+				       "%s: AVR%d: TICKS_PER_SEC: %d\n",
+				       p_adapter_id_str, n_instance_no,
+				       avr_vpd_info.sysinfo_ticks_per_second);
+
+				memcpy(&avr_vpd_info.sysinfo_uptime,
+				       &rx_data[11],
+				       sizeof(avr_vpd_info.sysinfo_uptime));
+				NT_LOG(DBG, NTHW, "%s: AVR%d: UPTIME: %d\n",
+				       p_adapter_id_str, n_instance_no,
+				       avr_vpd_info.sysinfo_uptime);
+
+				avr_vpd_info.sysinfo_osccal = rx_data[15];
+				NT_LOG(DBG, NTHW, "%s: AVR%d: OSCCAL: %d\n",
+				       p_adapter_id_str, n_instance_no,
+				       avr_vpd_info.sysinfo_osccal);
+
+				{
+					bool b_spi_ver_match _unused =
+						(avr_vpd_info.n_avr_spi_version ==
+						 avr_vpd_info
+						 .sysinfo_spi_version);
+					NT_LOG(DBG, NTHW,
+					       "%s: AVR%d: SPI_VER_TST: %s (%d %d)\n",
+					       p_adapter_id_str, n_instance_no,
+					       (b_spi_ver_match ? "OK" :
+						"MISMATCH"),
+					       avr_vpd_info.n_avr_spi_version,
+					       avr_vpd_info.sysinfo_spi_version);
+				}
+
+				p_fpga_info->nthw_hw_info.hw_id =
+					avr_vpd_info.sysinfo_hw_revision;
+				p_fpga_info->nthw_hw_info.hw_id_emulated =
+					avr_vpd_info.sysinfo_hw_revision;
+			} else {
+				NT_LOG(ERR, NTHW,
+				       "%s: AVR%d: SYSINFO: NA: res=%d sz=%d\n",
+				       p_adapter_id_str, n_instance_no, res,
+				       rx_buf.size);
+			}
+		}
+
+		/* AVR_OP_VPD_READ */
+		tx_buf.size = 0;
+		tx_buf.p_buf = NULL;
+		rx_buf.size = sizeof(rx_data);
+		rx_buf.p_buf = &rx_data;
+		res = nthw_spi_v3_transfer(p_avr_spi, AVR_OP_VPD_READ, &tx_buf,
+					   &rx_buf);
+		if (res == 0 && avr_vpd_info.n_avr_spi_version >= 3 &&
+				rx_buf.size >= GEN2_VPD_SIZE_TOTAL) {
+			avr_vpd_info.n_crc16_calced =
+				crc16(rx_buf.p_buf, rx_buf.size - 2);
+			memcpy(&avr_vpd_info.n_crc16_stored,
+			       &rx_data[rx_buf.size - 2],
+			       sizeof(avr_vpd_info.n_crc16_stored));
+			NT_LOG(DBG, NTHW, "%s: AVR%d: VPD_CRC: %04X %04X\n",
+			       p_adapter_id_str, n_instance_no,
+			       avr_vpd_info.n_crc16_stored,
+			       avr_vpd_info.n_crc16_calced);
+
+			avr_vpd_info.b_crc16_valid = (avr_vpd_info.n_crc16_stored ==
+						    avr_vpd_info.n_crc16_calced);
+			NT_LOG(DBG, NTHW, "%s: AVR%d: CRC_TST: %s\n",
+			       p_adapter_id_str, n_instance_no,
+			       (avr_vpd_info.b_crc16_valid ? "OK" : "ERROR"));
+
+			if (avr_vpd_info.b_crc16_valid) {
+				memcpy(&avr_vpd_info.psu_hw_version, &rx_data[0],
+				       sizeof(avr_vpd_info.psu_hw_version));
+				NT_LOG(DBG, NTHW, "%s: AVR%d: PSU_HW_VER: %d\n",
+				       p_adapter_id_str, n_instance_no,
+				       avr_vpd_info.psu_hw_version);
+
+				memcpy(&avr_vpd_info.vpd_pn, &rx_data[0 + 1],
+				       sizeof(avr_vpd_info.vpd_pn));
+				NT_LOG(DBG, NTHW, "%s: AVR%d: PN: '%.*s'\n",
+				       p_adapter_id_str, n_instance_no, GEN2_PN_SIZE,
+				       avr_vpd_info.vpd_pn);
+
+				memcpy(&avr_vpd_info.vpd_pba,
+				       &rx_data[0 + 1 + GEN2_PN_SIZE],
+				       sizeof(avr_vpd_info.vpd_pba));
+				NT_LOG(DBG, NTHW, "%s: AVR%d: PBA: '%.*s'\n",
+				       p_adapter_id_str, n_instance_no,
+				       GEN2_PBA_SIZE, avr_vpd_info.vpd_pba);
+
+				memcpy(&avr_vpd_info.vpd_sn,
+				       &rx_data[0 + 1 + GEN2_PN_SIZE +
+						 GEN2_PBA_SIZE],
+				       sizeof(avr_vpd_info.vpd_sn));
+				NT_LOG(DBG, NTHW, "%s: AVR%d: SN: '%.*s'\n",
+				       p_adapter_id_str, n_instance_no, GEN2_SN_SIZE,
+				       avr_vpd_info.vpd_sn);
+
+				memcpy(&avr_vpd_info.vpd_board_name,
+				       &rx_data[0 + 1 + GEN2_PN_SIZE +
+						 GEN2_PBA_SIZE + GEN2_SN_SIZE],
+				       sizeof(avr_vpd_info.vpd_board_name));
+				NT_LOG(DBG, NTHW, "%s: AVR%d: BN: '%.*s'\n",
+				       p_adapter_id_str, n_instance_no,
+				       GEN2_BNAME_SIZE,
+				       avr_vpd_info.vpd_board_name);
+
+				{
+					uint32_t u1;
+					union mac_u {
+						uint8_t a_u8[8];
+						uint16_t a_u16[4];
+						uint32_t a_u32[2];
+						uint64_t a_u64[1];
+					} mac;
+
+					 /* vpd_platform_section */
+					uint8_t *p_vpd_board_info =
+						(uint8_t *)(&rx_data[1 +
+								      GEN2_PN_SIZE +
+								      GEN2_PBA_SIZE +
+								      GEN2_SN_SIZE +
+								      GEN2_BNAME_SIZE]);
+					memcpy(&avr_vpd_info.product_family,
+					       &p_vpd_board_info[0],
+					       sizeof(avr_vpd_info
+						      .product_family));
+					NT_LOG(DBG, NTHW,
+					       "%s: AVR%d: PROD_FAM: %d\n",
+					       p_adapter_id_str, n_instance_no,
+					       avr_vpd_info.product_family);
+
+					memcpy(&avr_vpd_info.feature_mask,
+					       &p_vpd_board_info[0 + 4],
+					       sizeof(avr_vpd_info.feature_mask));
+					NT_LOG(DBG, NTHW,
+					       "%s: AVR%d: FMSK_VAL: 0x%08X\n",
+					       p_adapter_id_str, n_instance_no,
+					       avr_vpd_info.feature_mask);
+
+					memcpy(&avr_vpd_info.invfeature_mask,
+					       &p_vpd_board_info[0 + 4 + 4],
+					       sizeof(avr_vpd_info
+						      .invfeature_mask));
+					NT_LOG(DBG, NTHW,
+					       "%s: AVR%d: FMSK_INV: 0x%08X\n",
+					       p_adapter_id_str, n_instance_no,
+					       avr_vpd_info.invfeature_mask);
+
+					avr_vpd_info.b_feature_mask_valid =
+						(avr_vpd_info.feature_mask ==
+						 ~avr_vpd_info.invfeature_mask);
+					NT_LOG(DBG, NTHW,
+					       "%s: AVR%d: FMSK_TST: %s\n",
+					       p_adapter_id_str, n_instance_no,
+					       (avr_vpd_info.b_feature_mask_valid ?
+						"OK" :
+						"ERROR"));
+
+					memcpy(&avr_vpd_info.no_of_macs,
+					       &p_vpd_board_info[0 + 4 + 4 + 4],
+					       sizeof(avr_vpd_info.no_of_macs));
+					NT_LOG(DBG, NTHW,
+					       "%s: AVR%d: NUM_MACS: %d\n",
+					       p_adapter_id_str, n_instance_no,
+					       avr_vpd_info.no_of_macs);
+
+					memcpy(&avr_vpd_info.mac_address,
+					       &p_vpd_board_info[0 + 4 + 4 + 4 + 1],
+					       sizeof(avr_vpd_info.mac_address));
+					NT_LOG(DBG, NTHW,
+					       "%s: AVR%d: MAC_ADDR: %02x:%02x:%02x:%02x:%02x:%02x\n",
+					       p_adapter_id_str, n_instance_no,
+					       avr_vpd_info.mac_address[0],
+					       avr_vpd_info.mac_address[1],
+					       avr_vpd_info.mac_address[2],
+					       avr_vpd_info.mac_address[3],
+					       avr_vpd_info.mac_address[4],
+					       avr_vpd_info.mac_address[5]);
+
+					mac.a_u64[0] = 0;
+					memcpy(&mac.a_u8[2],
+					       &avr_vpd_info.mac_address,
+					       sizeof(avr_vpd_info.mac_address));
+					u1 = ntohl(mac.a_u32[0]);
+					if (u1 != mac.a_u32[0]) {
+						const uint32_t u0 = ntohl(mac.a_u32[1]);
+						mac.a_u32[0] = u0;
+						mac.a_u32[1] = u1;
+					}
+					avr_vpd_info.n_mac_val = mac.a_u64[0];
+					NT_LOG(DBG, NTHW,
+					       "%s: AVR%d: MAC_U64: %012" PRIX64
+					       "\n",
+					       p_adapter_id_str, n_instance_no,
+					       avr_vpd_info.n_mac_val);
+				}
+			}
+			p_fpga_info->nthw_hw_info.vpd_info.mn_mac_addr_count =
+				avr_vpd_info.no_of_macs;
+			p_fpga_info->nthw_hw_info.vpd_info.mn_mac_addr_value =
+				avr_vpd_info.n_mac_val;
+			memcpy(p_fpga_info->nthw_hw_info.vpd_info.ma_mac_addr_octets,
+			       avr_vpd_info.mac_address,
+			       ARRAY_SIZE(p_fpga_info->nthw_hw_info.vpd_info
+					  .ma_mac_addr_octets));
+		} else {
+			NT_LOG(ERR, NTHW, "%s:%u: res=%d\n", __func__, __LINE__,
+			       res);
+			NT_LOG(ERR, NTHW,
+			       "%s: AVR%d: SYSINFO2: NA: res=%d sz=%d\n",
+			       p_adapter_id_str, n_instance_no, res, rx_buf.size);
+		}
+	}
+
+	return res;
+}
+
+/*
+ * NT50B01, NT200A02, NT200A01-HWbuild2
+ */
+int nthw_fpga_si5340_clock_synth_init_fmt2(nt_fpga_t *p_fpga,
+	const uint8_t n_iic_addr,
+	const clk_profile_data_fmt2_t *p_clk_profile,
+	const int n_clk_profile_rec_cnt)
+{
+	int res;
+	nthw_iic_t *p_nthw_iic = nthw_iic_new();
+	nthw_si5340_t *p_nthw_si5340 = nthw_si5340_new();
+
+	assert(p_nthw_iic);
+	assert(p_nthw_si5340);
+	nthw_iic_init(p_nthw_iic, p_fpga, 0, 8); /* I2C cycle time 125Mhz ~ 8ns */
+
+	nthw_si5340_init(p_nthw_si5340, p_nthw_iic,
+			n_iic_addr); /* Si5340_U23_I2c_Addr_7bit */
+	res = nthw_si5340_config_fmt2(p_nthw_si5340, p_clk_profile,
+				    n_clk_profile_rec_cnt);
+	nthw_si5340_delete(p_nthw_si5340);
+	p_nthw_si5340 = NULL;
+
+	return res;
+}
+
+int nthw_fpga_init(struct fpga_info_s *p_fpga_info)
+{
+	const char *const p_adapter_id_str = p_fpga_info->mp_adapter_id_str;
+
+	nthw_hif_t *p_nthw_hif = NULL;
+	nthw_pcie3_t *p_nthw_pcie3 = NULL;
+	nthw_rac_t *p_nthw_rac = NULL;
+	nthw_tsm_t *p_nthw_tsm = NULL;
+
+	uint64_t n_fpga_ident = 0;
+	nt_fpga_mgr_t *p_fpga_mgr = NULL;
+	nt_fpga_t *p_fpga = NULL;
+
+	char s_fpga_prod_ver_rev_str[32] = { 0 };
+
+	int res = 0;
+
+	assert(p_fpga_info);
+
+	{
+		int n_fpga_type_id, n_fpga_prod_id, n_fpga_ver_id, n_fpga_rev_id;
+		uint64_t n_fpga_ident;
+		uint32_t n_fpga_ident_low, n_fpga_ident_high, n_fpga_build_time;
+
+		nthw_rac_reg_read32(p_fpga_info, 0x0, &n_fpga_ident_low);
+		nthw_rac_reg_read32(p_fpga_info, 0x8, &n_fpga_ident_high);
+		nthw_rac_reg_read32(p_fpga_info, 0x10, &n_fpga_build_time);
+
+		n_fpga_ident = (((uint64_t)n_fpga_ident_high << 32) | n_fpga_ident_low);
+		n_fpga_type_id = FPGAID_TO_PRODUCTTYPE(n_fpga_ident);
+		n_fpga_prod_id = FPGAID_TO_PRODUCTCODE(n_fpga_ident);
+		n_fpga_ver_id = FPGAID_TO_VERSIONCODE(n_fpga_ident);
+		n_fpga_rev_id = FPGAID_TO_REVISIONCODE(n_fpga_ident);
+
+		p_fpga_info->n_fpga_ident = n_fpga_ident;
+		p_fpga_info->n_fpga_type_id = n_fpga_type_id;
+		p_fpga_info->n_fpga_prod_id = n_fpga_prod_id;
+		p_fpga_info->n_fpga_ver_id = n_fpga_ver_id;
+		p_fpga_info->n_fpga_rev_id = n_fpga_rev_id;
+		p_fpga_info->n_fpga_build_time = n_fpga_build_time;
+
+		snprintf(s_fpga_prod_ver_rev_str, sizeof(s_fpga_prod_ver_rev_str),
+			 "%04d-%04d-%02d-%02d", n_fpga_type_id, n_fpga_prod_id,
+			 n_fpga_ver_id, n_fpga_rev_id);
+
+		NT_LOG(INF, NTHW, "%s: FPGA %s (%" PRIX64 ") [%08X]\n",
+		       p_adapter_id_str, s_fpga_prod_ver_rev_str, n_fpga_ident,
+		       n_fpga_build_time);
+	}
+
+	n_fpga_ident = p_fpga_info->n_fpga_ident;
+
+	p_fpga_mgr = fpga_mgr_new();
+	fpga_mgr_init(p_fpga_mgr);
+	fpga_mgr_log_dump(p_fpga_mgr);
+	p_fpga = fpga_mgr_query_fpga(p_fpga_mgr, n_fpga_ident, p_fpga_info);
+	p_fpga_info->mp_fpga = p_fpga;
+	if (p_fpga == NULL) {
+		NT_LOG(ERR, NTHW, "%s: Unsupported FPGA: %s (%08X)\n",
+		       p_adapter_id_str, s_fpga_prod_ver_rev_str,
+		       p_fpga_info->n_fpga_build_time);
+		return -1;
+	}
+
+	if (p_fpga_mgr) {
+		fpga_mgr_delete(p_fpga_mgr);
+		p_fpga_mgr = NULL;
+	}
+
+	/* Read Fpga param info */
+	nthw_fpga_get_param_info(p_fpga_info, p_fpga);
+
+	/* debug: report params */
+	NT_LOG(DBG, NTHW, "%s: NT_NIMS=%d\n", p_adapter_id_str, p_fpga_info->n_nims);
+	NT_LOG(DBG, NTHW, "%s: NT_PHY_PORTS=%d\n", p_adapter_id_str,
+	       p_fpga_info->n_phy_ports);
+	NT_LOG(DBG, NTHW, "%s: NT_PHY_QUADS=%d\n", p_adapter_id_str,
+	       p_fpga_info->n_phy_quads);
+	NT_LOG(DBG, NTHW, "%s: NT_RX_PORTS=%d\n", p_adapter_id_str,
+	       p_fpga_info->n_rx_ports);
+	NT_LOG(DBG, NTHW, "%s: NT_TX_PORTS=%d\n", p_adapter_id_str,
+	       p_fpga_info->n_tx_ports);
+	NT_LOG(DBG, NTHW, "%s: nProfile=%d\n", p_adapter_id_str,
+	       (int)p_fpga_info->profile);
+
+	p_nthw_rac = nthw_rac_new();
+	if (p_nthw_rac == NULL) {
+		NT_LOG(ERR, NTHW,
+		       "%s: Unsupported FPGA: RAC is not found: %s (%08X)\n",
+		       p_adapter_id_str, s_fpga_prod_ver_rev_str,
+		       p_fpga_info->n_fpga_build_time);
+		return -1;
+	}
+
+	nthw_rac_init(p_nthw_rac, p_fpga, p_fpga_info);
+	nthw_rac_rab_flush(p_nthw_rac);
+	p_fpga_info->mp_nthw_rac = p_nthw_rac;
+
+	/* special case: values below 0x100 will disable debug on RAC communication */
+	{
+		const int n_fpga_initial_debug_mode = p_fpga_info->n_fpga_debug_mode;
+
+		fpga_set_debug_mode(p_fpga, n_fpga_initial_debug_mode);
+	}
+
+	switch (p_fpga_info->n_nthw_adapter_id) {
+	case NT_HW_ADAPTER_ID_NT200A01: /* fallthrough */
+	case NT_HW_ADAPTER_ID_NT200A02:
+		res = nthw_fpga_nt200a0x_init(p_fpga_info);
+		break;
+	default:
+		NT_LOG(ERR, NTHW, "%s: Unsupported HW product id: %d\n",
+		       p_adapter_id_str, p_fpga_info->n_nthw_adapter_id);
+		res = -1;
+		break;
+	}
+
+	if (res) {
+		NT_LOG(ERR, NTHW, "%s: status: 0x%08X\n", p_adapter_id_str, res);
+		return res;
+	}
+
+	res = nthw_pcie3_init(NULL, p_fpga, 0); /* Probe for module */
+	if (res == 0) {
+		p_nthw_pcie3 = nthw_pcie3_new();
+		if (p_nthw_pcie3) {
+			res = nthw_pcie3_init(p_nthw_pcie3, p_fpga, 0);
+			if (res == 0) {
+				NT_LOG(DBG, NTHW, "%s: Pcie3 module found\n",
+				       p_adapter_id_str);
+				nthw_pcie3_trigger_sample_time(p_nthw_pcie3);
+			} else {
+				nthw_pcie3_delete(p_nthw_pcie3);
+				p_nthw_pcie3 = NULL;
+			}
+		}
+		p_fpga_info->mp_nthw_pcie3 = p_nthw_pcie3;
+	}
+
+	if (p_nthw_pcie3 == NULL) {
+		p_nthw_hif = nthw_hif_new();
+		if (p_nthw_hif) {
+			res = nthw_hif_init(p_nthw_hif, p_fpga, 0);
+			if (res == 0) {
+				NT_LOG(DBG, NTHW, "%s: Hif module found\n",
+				       p_adapter_id_str);
+				nthw_hif_trigger_sample_time(p_nthw_hif);
+			} else {
+				nthw_hif_delete(p_nthw_hif);
+				p_nthw_hif = NULL;
+			}
+		}
+	}
+	p_fpga_info->mp_nthw_hif = p_nthw_hif;
+
+	p_nthw_tsm = nthw_tsm_new();
+	if (p_nthw_tsm) {
+		nthw_tsm_init(p_nthw_tsm, p_fpga, 0);
+
+		nthw_tsm_set_config_ts_format(p_nthw_tsm,
+					  1); /* 1 = TSM: TS format native */
+
+		/* Timer T0 - stat toggle timer */
+		nthw_tsm_set_timer_t0_enable(p_nthw_tsm, false);
+		nthw_tsm_set_timer_t0_max_count(p_nthw_tsm, 50 * 1000 * 1000); /* ns */
+		nthw_tsm_set_timer_t0_enable(p_nthw_tsm, true);
+
+		/* Timer T1 - keep alive timer */
+		nthw_tsm_set_timer_t1_enable(p_nthw_tsm, false);
+		nthw_tsm_set_timer_t1_max_count(p_nthw_tsm,
+					   100 * 1000 * 1000); /* ns */
+		nthw_tsm_set_timer_t1_enable(p_nthw_tsm, true);
+	}
+	p_fpga_info->mp_nthw_tsm = p_nthw_tsm;
+
+	/* TSM sample triggering: test validation... */
+#if defined(DEBUG) && (1)
+	{
+		uint64_t n_time, n_ts;
+		int i;
+
+		for (i = 0; i < 4; i++) {
+			if (p_nthw_hif)
+				nthw_hif_trigger_sample_time(p_nthw_hif);
+
+			else if (p_nthw_pcie3)
+				nthw_pcie3_trigger_sample_time(p_nthw_pcie3);
+			nthw_tsm_get_time(p_nthw_tsm, &n_time);
+			nthw_tsm_get_ts(p_nthw_tsm, &n_ts);
+
+			NT_LOG(DBG, NTHW,
+			       "%s: TSM time: %016" PRIX64 " %016" PRIX64 "\n",
+			       p_adapter_id_str, n_time, n_ts);
+
+			NT_OS_WAIT_USEC(1000);
+		}
+	}
+#endif
+
+	return res;
+}
+
+int nthw_fpga_shutdown(struct fpga_info_s *p_fpga_info)
+{
+	int res = -1;
+
+	if (p_fpga_info) {
+		if (p_fpga_info && p_fpga_info->mp_nthw_rac)
+			res = nthw_rac_rab_reset(p_fpga_info->mp_nthw_rac);
+	}
+
+	return res;
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_fpga.h b/drivers/net/ntnic/nthw/core/nthw_fpga.h
new file mode 100644
index 0000000000..336d81f337
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_fpga.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_FPGA_H__
+#define __NTHW_FPGA_H__
+
+#include "nthw_drv.h"
+
+#include "nthw_fpga_model.h"
+
+#include "nthw_rac.h"
+#include "nthw_iic.h"
+
+#include "nthw_stat.h"
+
+#include "nthw_fpga_rst.h"
+
+#include "nthw_fpga_nt200a0x.h"
+
+#include "nthw_dbs.h"
+
+int nthw_fpga_init(struct fpga_info_s *p_fpga_info);
+int nthw_fpga_shutdown(struct fpga_info_s *p_fpga_info);
+
+int nthw_fpga_get_param_info(struct fpga_info_s *p_fpga_info, nt_fpga_t *p_fpga);
+
+int nthw_fpga_avr_probe(nt_fpga_t *p_fpga, const int n_instance_no);
+
+int nthw_fpga_iic_scan(nt_fpga_t *p_fpga, const int n_instance_no_begin,
+		       const int n_instance_no_end);
+
+int nthw_fpga_iic_read_byte(nt_fpga_t *p_fpga, const int n_instance_no,
+			    const uint8_t n_dev_addr, const uint8_t n_reg_addr);
+int nthw_fpga_iic_write_byte(nt_fpga_t *p_fpga, const int n_instance_no,
+			     const uint8_t n_dev_addr, const uint8_t n_reg_addr,
+			     uint8_t val);
+
+int nthw_fpga_silabs_detect(nt_fpga_t *p_fpga, const int n_instance_no,
+			    const int n_dev_addr, const int n_page_reg_addr);
+
+int nthw_fpga_si5340_clock_synth_init_fmt2(nt_fpga_t *p_fpga,
+	const uint8_t n_iic_addr,
+	const clk_profile_data_fmt2_t *p_clk_profile,
+	const int n_clk_profile_rec_cnt);
+
+#endif /* __NTHW_FPGA_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_fpga_nt200a0x.c b/drivers/net/ntnic/nthw/core/nthw_fpga_nt200a0x.c
new file mode 100644
index 0000000000..70338fdfd7
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_fpga_nt200a0x.c
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "ntlog.h"
+
+#include "nthw_fpga.h"
+#include "nthw_fpga_nt200a0x.h"
+
+int nthw_fpga_nt200a0x_init(struct fpga_info_s *p_fpga_info)
+{
+	assert(p_fpga_info);
+
+	const char *const p_adapter_id_str = p_fpga_info->mp_adapter_id_str;
+	struct nthw_fpga_rst_nt200a0x rst;
+	int res = -1;
+
+	/* reset common */
+	res = nthw_fpga_rst_nt200a0x_init(p_fpga_info, &rst);
+	if (res) {
+		NT_LOG(ERR, NTHW, "%s: %s: loc=%u: FPGA=%04d res=%d\n",
+		       p_adapter_id_str, __func__, __LINE__,
+		       p_fpga_info->n_fpga_prod_id, res);
+		return res;
+	}
+
+	/* reset specific */
+	switch (p_fpga_info->n_fpga_prod_id) {
+	case 9563:
+		res = nthw_fpga_rst9563_init(p_fpga_info, &rst);
+		break;
+	default:
+		NT_LOG(ERR, NTHW, "%s: Unsupported FPGA product: %04d\n",
+		       p_adapter_id_str, p_fpga_info->n_fpga_prod_id);
+		res = -1;
+		break;
+	}
+	if (res) {
+		NT_LOG(ERR, NTHW, "%s: %s: loc=%u: FPGA=%04d res=%d\n",
+		       p_adapter_id_str, __func__, __LINE__,
+		       p_fpga_info->n_fpga_prod_id, res);
+		return res;
+	}
+
+	return res;
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_fpga_nt200a0x.h b/drivers/net/ntnic/nthw/core/nthw_fpga_nt200a0x.h
new file mode 100644
index 0000000000..ff324bee39
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_fpga_nt200a0x.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_FPGA_NT200A0X_H__
+#define __NTHW_FPGA_NT200A0X_H__
+
+int nthw_fpga_nt200a0x_init(struct fpga_info_s *p_fpga_info);
+
+/* NT200A02: 9563 */
+int nthw_fpga_rst9563_init(struct fpga_info_s *p_fpga_info,
+			  struct nthw_fpga_rst_nt200a0x *const p);
+
+#endif /* __NTHW_FPGA_NT200A0X_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_fpga_rst.c b/drivers/net/ntnic/nthw/core/nthw_fpga_rst.c
new file mode 100644
index 0000000000..66c148bab2
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_fpga_rst.c
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "ntlog.h"
+
+#include "nthw_fpga.h"
+#include "nthw_fpga_nt200a0x.h"
+
+#include "nthw_fpga_rst.h"
diff --git a/drivers/net/ntnic/nthw/core/nthw_fpga_rst.h b/drivers/net/ntnic/nthw/core/nthw_fpga_rst.h
new file mode 100644
index 0000000000..2099c4b677
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_fpga_rst.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_FPGA_RST_H__
+#define __NTHW_FPGA_RST_H__
+
+#include "nthw_drv.h"
+
+#include "nthw_fpga_model.h"
+
+#include "nthw_rac.h"
+#include "nthw_iic.h"
+
+#include "nthw_fpga_rst_nt200a0x.h"
+
+#endif /* __NTHW_FPGA_RST_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_fpga_rst9563.c b/drivers/net/ntnic/nthw/core/nthw_fpga_rst9563.c
new file mode 100644
index 0000000000..077b043c60
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_fpga_rst9563.c
@@ -0,0 +1,241 @@
+/* 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_fpga.h"
+
+#include "nthw_clock_profiles.h"
+
+static int nthw_fpga_rst9563_setup(nt_fpga_t *p_fpga,
+				  struct nthw_fpga_rst_nt200a0x *const p)
+{
+	const char *const p_adapter_id_str = p_fpga->p_fpga_info->mp_adapter_id_str;
+	const int n_fpga_product_id = p_fpga->m_product_id;
+	const int n_fpga_version = p_fpga->m_fpga_version;
+	const int n_fpga_revision = p_fpga->m_fpga_revision;
+
+	nt_module_t *p_mod_rst;
+	nt_register_t *p_curr_reg;
+
+	assert(p);
+	p->mn_fpga_product_id = n_fpga_product_id;
+	p->mn_fpga_version = n_fpga_version;
+	p->mn_fpga_revision = n_fpga_revision;
+
+	NT_LOG(DBG, NTHW, "%s: %s: FPGA reset setup: FPGA %04d-%02d-%02d\n",
+	       p_adapter_id_str, __func__, n_fpga_product_id, n_fpga_version,
+	       n_fpga_revision);
+
+	p_mod_rst = fpga_query_module(p_fpga, MOD_RST9563, 0);
+	if (p_mod_rst == NULL) {
+		NT_LOG(ERR, NTHW, "%s: RST %d: no such instance\n",
+		       p_adapter_id_str, 0);
+		return -1;
+	}
+
+	p_mod_rst = fpga_query_module(p_fpga, MOD_RST9563, 0);
+	if (p_mod_rst == NULL) {
+		NT_LOG(ERR, NTHW, "%s: RST %d: no such instance\n",
+		       p_adapter_id_str, 0);
+		return -1;
+	}
+
+	/* RST register field pointers */
+	p_curr_reg = module_get_register(p_mod_rst, RST9563_RST);
+	p->mp_fld_rst_sys = register_get_field(p_curr_reg, RST9563_RST_SYS);
+	p->mp_fld_rst_sys_mmcm = register_get_field(p_curr_reg, RST9563_RST_SYS_MMCM);
+	p->mp_fld_rst_core_mmcm =
+		register_get_field(p_curr_reg, RST9563_RST_CORE_MMCM);
+	p->mp_fld_rst_rpp = register_get_field(p_curr_reg, RST9563_RST_RPP);
+	p->mp_fld_rst_ddr4 = register_get_field(p_curr_reg, RST9563_RST_DDR4);
+	p->mp_fld_rst_sdc = register_get_field(p_curr_reg, RST9563_RST_SDC);
+	p->mp_fld_rst_phy = register_get_field(p_curr_reg, RST9563_RST_PHY);
+	p->mp_fld_rst_serdes_rx = NULL; /* Field not present on 9563 */
+	p->mp_fld_rst_serdes_tx = NULL; /* Field not present on 9563 */
+	p->mp_fld_rst_serdes_rx_datapath = NULL; /* Field not present on 9563 */
+	p->mp_fld_rst_pcs_rx = NULL; /* Field not present on 9563 */
+	p->mp_fld_rst_mac_rx = register_get_field(p_curr_reg, RST9563_RST_MAC_RX);
+	p->mp_fld_rst_mac_tx = NULL;
+	p->mp_fld_rst_ptp = register_get_field(p_curr_reg, RST9563_RST_PTP);
+	p->mp_fld_rst_ptp = register_get_field(p_curr_reg, RST9563_RST_PTP);
+	p->mp_fld_rst_ts = register_get_field(p_curr_reg, RST9563_RST_TS);
+	p->mp_fld_rst_ptp_mmcm = register_get_field(p_curr_reg, RST9563_RST_PTP_MMCM);
+	p->mp_fld_rst_ts_mmcm = register_get_field(p_curr_reg, RST9563_RST_TS_MMCM);
+	/* referenced in separate function */
+	p->mp_fld_rst_periph = register_get_field(p_curr_reg, RST9563_RST_PERIPH);
+	p->mp_fld_rst_tsm_ref_mmcm =
+		register_query_field(p_curr_reg, RST9563_RST_TSM_REF_MMCM);
+	p->mp_fld_rst_tmc = register_query_field(p_curr_reg, RST9563_RST_TMC);
+
+	if (!p->mp_fld_rst_tsm_ref_mmcm) {
+		NT_LOG(DBG, NTHW, "%s: No RST9563_RST_TSM_REF_MMCM found\n",
+		       p_adapter_id_str);
+	}
+	if (!p->mp_fld_rst_tmc) {
+		NT_LOG(DBG, NTHW, "%s: No RST9563_RST_TMC found\n",
+		       p_adapter_id_str);
+	}
+	register_update(p_curr_reg);
+
+	/* CTRL register field pointers */
+	p_curr_reg = module_get_register(p_mod_rst, RST9563_CTRL);
+	p->mp_fld_ctrl_ts_clk_sel_override =
+		register_get_field(p_curr_reg, RST9563_CTRL_TS_CLKSEL_OVERRIDE);
+	/* Field not present on 9563 */
+	p->mp_fld_ctrl_ts_clk_sel =
+		register_get_field(p_curr_reg, RST9563_CTRL_TS_CLKSEL);
+	p->mp_fld_ctrl_ts_clk_sel_ref = NULL; /* Field not present on 9563 */
+	p->mp_fld_ctrl_ptp_mmcm_clk_sel =
+		register_get_field(p_curr_reg, RST9563_CTRL_PTP_MMCM_CLKSEL);
+	register_update(p_curr_reg);
+
+	/* STAT register field pointers */
+	p_curr_reg = module_get_register(p_mod_rst, RST9563_STAT);
+	p->mp_fld_stat_ddr4_mmcm_locked =
+		register_get_field(p_curr_reg, RST9563_STAT_DDR4_MMCM_LOCKED);
+	p->mp_fld_stat_sys_mmcm_locked =
+		register_get_field(p_curr_reg, RST9563_STAT_SYS_MMCM_LOCKED);
+	p->mp_fld_stat_core_mmcm_locked =
+		register_get_field(p_curr_reg, RST9563_STAT_CORE_MMCM_LOCKED);
+	p->mp_fld_stat_ddr4_pll_locked =
+		register_get_field(p_curr_reg, RST9563_STAT_DDR4_PLL_LOCKED);
+	p->mp_fld_stat_ptp_mmcm_locked =
+		register_get_field(p_curr_reg, RST9563_STAT_PTP_MMCM_LOCKED);
+	p->mp_fld_stat_ts_mmcm_locked =
+		register_get_field(p_curr_reg, RST9563_STAT_TS_MMCM_LOCKED);
+	p->mp_fld_stat_tsm_ref_mmcm_locked = NULL; /* Field not present on 9563 */
+
+	if (!p->mp_fld_stat_tsm_ref_mmcm_locked) {
+		NT_LOG(DBG, NTHW,
+		       "%s: No RST9563_STAT_TSM_REF_MMCM_LOCKED found\n",
+		       p_adapter_id_str);
+	}
+	register_update(p_curr_reg);
+
+	/* STICKY register field pointers */
+	p_curr_reg = module_get_register(p_mod_rst, RST9563_STICKY);
+	p->mp_fld_sticky_ptp_mmcm_unlocked =
+		register_get_field(p_curr_reg, RST9563_STICKY_PTP_MMCM_UNLOCKED);
+	p->mp_fld_sticky_ts_mmcm_unlocked =
+		register_get_field(p_curr_reg, RST9563_STICKY_TS_MMCM_UNLOCKED);
+	p->mp_fld_sticky_ddr4_mmcm_unlocked =
+		register_get_field(p_curr_reg, RST9563_STICKY_DDR4_MMCM_UNLOCKED);
+	p->mp_fld_sticky_ddr4_pll_unlocked =
+		register_get_field(p_curr_reg, RST9563_STICKY_DDR4_PLL_UNLOCKED);
+	p->mp_fld_sticky_core_mmcm_unlocked =
+		register_get_field(p_curr_reg, RST9563_STICKY_CORE_MMCM_UNLOCKED);
+	p->mp_fld_sticky_pci_sys_mmcm_unlocked = NULL; /* Field not present on 9563 */
+	p->mp_fld_sticky_tsm_ref_mmcm_unlocked = NULL; /* Field not present on 9563 */
+
+	if (!p->mp_fld_sticky_tsm_ref_mmcm_unlocked) {
+		NT_LOG(DBG, NTHW,
+		       "%s: No RST9563_STICKY_TSM_REF_MMCM_UNLOCKED found\n",
+		       p_adapter_id_str);
+	}
+	register_update(p_curr_reg);
+
+	/* POWER register field pointers */
+	p_curr_reg = module_get_register(p_mod_rst, RST9563_POWER);
+	p->mp_fld_power_pu_phy = register_get_field(p_curr_reg, RST9563_POWER_PU_PHY);
+	p->mp_fld_power_pu_nseb =
+		register_get_field(p_curr_reg, RST9563_POWER_PU_NSEB);
+	register_update(p_curr_reg);
+
+	return 0;
+}
+
+static int nthw_fpga_rst9563_periph_reset(nt_fpga_t *p_fpga)
+{
+	const char *const _unused p_adapter_id_str =
+		p_fpga->p_fpga_info->mp_adapter_id_str;
+	nt_module_t *p_mod_rst = fpga_query_module(p_fpga, MOD_RST9563, 0);
+
+	if (p_mod_rst) {
+		nt_register_t *p_reg_rst;
+		nt_field_t *p_fld_rst_periph;
+
+		NT_LOG(DBG, NTHW, "%s: PERIPH RST\n", p_adapter_id_str);
+		p_reg_rst = module_get_register(p_mod_rst, RST9563_RST);
+		p_fld_rst_periph = register_get_field(p_reg_rst, RST9563_RST_PERIPH);
+		field_set_flush(p_fld_rst_periph);
+		field_clr_flush(p_fld_rst_periph);
+	} else {
+		return -1;
+	}
+	return 0;
+}
+
+static int
+nthw_fpga_rst9563_clock_synth_init(nt_fpga_t *p_fpga,
+				  const int n_si_labs_clock_synth_model,
+				  const uint8_t n_si_labs_clock_synth_i2c_addr)
+{
+	const char *const p_adapter_id_str = p_fpga->p_fpga_info->mp_adapter_id_str;
+	const int n_fpga_product_id = p_fpga->m_product_id;
+	int res;
+
+	if (n_si_labs_clock_synth_model == 5340) {
+		res = nthw_fpga_si5340_clock_synth_init_fmt2(p_fpga,
+			n_si_labs_clock_synth_i2c_addr,
+			p_data_si5340_nt200a02_u23_v5,
+			n_data_si5340_nt200a02_u23_v5);
+	} else {
+		NT_LOG(ERR, NTHW,
+		       "%s: Fpga %d: Unsupported clock synth model (%d)\n",
+		       p_adapter_id_str, n_fpga_product_id, n_si_labs_clock_synth_model);
+		res = -1;
+	}
+	return res;
+}
+
+int nthw_fpga_rst9563_init(struct fpga_info_s *p_fpga_info,
+			  struct nthw_fpga_rst_nt200a0x *p_rst)
+{
+	assert(p_fpga_info);
+	assert(p_rst);
+
+	const char *const _unused p_adapter_id_str = p_fpga_info->mp_adapter_id_str;
+	int res = -1;
+	int n_si_labs_clock_synth_model;
+	uint8_t n_si_labs_clock_synth_i2c_addr;
+	nt_fpga_t *p_fpga = NULL;
+
+	p_fpga = p_fpga_info->mp_fpga;
+	n_si_labs_clock_synth_model = p_rst->mn_si_labs_clock_synth_model;
+	n_si_labs_clock_synth_i2c_addr = p_rst->mn_si_labs_clock_synth_i2c_addr;
+
+	res = nthw_fpga_rst9563_periph_reset(p_fpga);
+	if (res) {
+		NT_LOG(DBG, NTHW, "%s: ERROR: res=%d [%s:%u]\n", p_adapter_id_str,
+		       res, __func__, __LINE__);
+		return res;
+	}
+
+	res = nthw_fpga_rst9563_clock_synth_init(p_fpga, n_si_labs_clock_synth_model,
+						n_si_labs_clock_synth_i2c_addr);
+	if (res) {
+		NT_LOG(DBG, NTHW, "%s: ERROR: res=%d [%s:%u]\n", p_adapter_id_str,
+		       res, __func__, __LINE__);
+		return res;
+	}
+
+	res = nthw_fpga_rst9563_setup(p_fpga, p_rst);
+	if (res) {
+		NT_LOG(DBG, NTHW, "%s: ERROR: res=%d [%s:%u]\n", p_adapter_id_str,
+		       res, __func__, __LINE__);
+		return res;
+	}
+
+	res = nthw_fpga_rst_nt200a0x_reset(p_fpga, p_rst);
+	if (res) {
+		NT_LOG(DBG, NTHW, "%s: ERROR: res=%d [%s:%u]\n", p_adapter_id_str,
+		       res, __func__, __LINE__);
+		return res;
+	}
+
+	return res;
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_fpga_rst_nt200a0x.c b/drivers/net/ntnic/nthw/core/nthw_fpga_rst_nt200a0x.c
new file mode 100644
index 0000000000..ae63fefb09
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_fpga_rst_nt200a0x.c
@@ -0,0 +1,674 @@
+/* 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_fpga.h"
+
+static const uint8_t si5338_u23_i2c_addr_7bit = 0x70;
+static const uint8_t si5340_u23_i2c_addr_7bit = 0x74;
+
+/*
+ * Wait until DDR4 PLL LOCKED
+ */
+static int nthw_fpga_rst_nt200a0x_wait_ddr4_pll_locked(nt_fpga_t *p_fpga,
+	const struct nthw_fpga_rst_nt200a0x *p)
+{
+	const char *const p_adapter_id_str = p_fpga->p_fpga_info->mp_adapter_id_str;
+	uint32_t locked;
+	uint32_t retrycount = 5;
+	uint32_t timeout = 50000; /* initial timeout must be set to 5 sec. */
+	/* 14: wait until DDR4 PLL LOCKED */
+	NT_LOG(DBG, NTHW, "%s: Waiting for DDR4 PLL to lock\n", p_adapter_id_str);
+	/*
+	 * The following retry count gives a total timeout of 1 * 5 + 5 * 8 = 45sec
+	 * It has been observed that at least 21sec can be necessary
+	 */
+	while (true) {
+		int locked = field_wait_set_any32(p->mp_fld_stat_ddr4_pll_locked,
+						  timeout, 100);
+		if (locked == 0)
+			break;
+		NT_LOG(DBG, NTHW,
+		       "%s: Waiting for DDR4 PLL to lock - timeout\n",
+		       p_adapter_id_str);
+		if (retrycount <= 0) {
+			NT_LOG(ERR, NTHW,
+			       "%s: Waiting for DDR4 PLL to lock failed (%d)\n",
+			       p_adapter_id_str, locked);
+			break;
+		}
+		field_set_flush(p->mp_fld_rst_ddr4); /* Reset DDR PLL */
+		field_clr_flush(p->mp_fld_rst_ddr4); /* Reset DDR PLL */
+		retrycount--;
+		timeout =
+			80000; /* Increase timeout for second attempt to 8 sec. */
+	}
+
+	NT_LOG(DBG, NTHW, "%s: Waiting for DDR4 MMCM to lock\n", p_adapter_id_str);
+	locked = field_wait_set_any32(p->mp_fld_stat_ddr4_mmcm_locked, -1, -1);
+	if (locked != 0) {
+		NT_LOG(ERR, NTHW,
+		       "%s: Waiting for DDR4 MMCM to lock failed (%d)\n",
+		       p_adapter_id_str, locked);
+		return -1;
+	}
+
+	if ((true) && p->mp_fld_stat_tsm_ref_mmcm_locked) {
+		NT_LOG(DBG, NTHW, "%s: Waiting for TSM REF MMCM to lock\n",
+		       p_adapter_id_str);
+		locked = field_wait_set_any32(p->mp_fld_stat_tsm_ref_mmcm_locked, -1,
+					    -1);
+		if (locked != 0) {
+			NT_LOG(ERR, NTHW,
+			       "%s: Waiting for TSM REF MMCM to lock failed (%d)\n",
+			       p_adapter_id_str, locked);
+			return -1;
+		}
+	}
+
+	/* 10: Clear all MMCM/PLL lock sticky bits before testing them */
+	NT_LOG(DBG, NTHW, "%s: Clear sticky MMCM unlock bits\n", p_adapter_id_str);
+	field_update_register(p->mp_fld_sticky_ptp_mmcm_unlocked);
+	/* Clear all sticky bits */
+	field_set_flush(p->mp_fld_sticky_ptp_mmcm_unlocked);
+	field_set_flush(p->mp_fld_sticky_ts_mmcm_unlocked);
+	field_set_flush(p->mp_fld_sticky_ddr4_mmcm_unlocked);
+	field_set_flush(p->mp_fld_sticky_ddr4_pll_unlocked);
+	field_set_flush(p->mp_fld_sticky_core_mmcm_unlocked);
+	if (p->mp_fld_sticky_tsm_ref_mmcm_unlocked)
+		field_set_flush(p->mp_fld_sticky_tsm_ref_mmcm_unlocked);
+	if (p->mp_fld_sticky_pci_sys_mmcm_unlocked)
+		field_set_flush(p->mp_fld_sticky_pci_sys_mmcm_unlocked);
+
+	/* 11: Ensure sticky bits are not unlocked except PTP MMCM and TS MMCM */
+	if (field_get_updated(p->mp_fld_sticky_ddr4_mmcm_unlocked)) {
+		NT_LOG(ERR, NTHW,
+		       "%s: get_sticky_ddr4_mmcm_unlocked() returned true\n",
+		       p_adapter_id_str);
+	}
+
+	if (field_get_updated(p->mp_fld_sticky_ddr4_pll_unlocked)) {
+		NT_LOG(ERR, NTHW,
+		       "%s: get_sticky_ddr4_pll_unlocked() returned true\n",
+		       p_adapter_id_str);
+	}
+
+	return 0;
+}
+
+/*
+ * Wait for SDRAM controller has been calibrated - On some adapters we have seen
+ * calibration time of 2.3 seconds
+ */
+static int
+nthw_fpga_rst_nt200a0x_wait_sdc_calibrated(nt_fpga_t *p_fpga,
+		const struct nthw_fpga_rst_nt200a0x *p)
+{
+	const char *const p_adapter_id_str = p_fpga->p_fpga_info->mp_adapter_id_str;
+	nthw_sdc_t *p_nthw_sdc = NULL;
+	const int n_retry_cnt_max = 5;
+	int n_retry_cnt;
+	int res;
+
+	res = nthw_sdc_init(NULL, p_fpga, 0); /* probe for module */
+	if (res == 0) {
+		p_nthw_sdc = nthw_sdc_new();
+		if (p_nthw_sdc) {
+			res = nthw_sdc_init(p_nthw_sdc, p_fpga, 0);
+			if (res) {
+				NT_LOG(ERR, NTHW,
+				       "%s: SDC init failed: res=%d [%s:%d]\n",
+				       p_adapter_id_str, res, __func__, __LINE__);
+				nthw_sdc_delete(p_nthw_sdc);
+				p_nthw_sdc = NULL;
+				return -1;
+			}
+		} else {
+			nthw_sdc_delete(p_nthw_sdc);
+			p_nthw_sdc = NULL;
+		}
+	} else {
+		NT_LOG(DBG, NTHW, "%s: No SDC found\n", p_adapter_id_str);
+	}
+	n_retry_cnt = 0;
+	res = -1;
+	while ((res != 0) && (n_retry_cnt <= n_retry_cnt_max)) {
+		/* wait until DDR4 PLL LOCKED */
+		res = nthw_fpga_rst_nt200a0x_wait_ddr4_pll_locked(p_fpga, p);
+		if (res == 0) {
+			if (p_nthw_sdc) {
+				/*
+				 * Wait for SDRAM controller has been calibrated
+				 * On some adapters we have seen calibration time of 2.3 seconds
+				 */
+				NT_LOG(DBG, NTHW,
+				       "%s: Waiting for SDRAM to calibrate\n",
+				       p_adapter_id_str);
+				res = nthw_sdc_wait_states(p_nthw_sdc, 10000, 1000);
+				{
+					uint64_t n_result_mask;
+
+					int n_state_code _unused =
+						nthw_sdc_get_states(p_nthw_sdc,
+								  &n_result_mask);
+					NT_LOG(DBG, NTHW,
+					       "%s: SDRAM state=0x%08lX state_code=%d retry=%d code=%d\n",
+					       p_adapter_id_str, n_result_mask,
+					       n_state_code, n_retry_cnt, res);
+				}
+				if (res == 0)
+					break;
+			}
+
+			if (n_retry_cnt >= n_retry_cnt_max) {
+				uint64_t n_result_mask;
+				int n_state_code _unused = nthw_sdc_get_states(p_nthw_sdc,
+									       &n_result_mask);
+
+				NT_LOG(DBG, NTHW,
+				       "%s: SDRAM state=0x%08lX state_code=%d retry=%d code=%d\n",
+				       p_adapter_id_str, n_result_mask, n_state_code,
+				       n_retry_cnt, res);
+				if (res != 0) {
+					NT_LOG(ERR, NTHW,
+					       "%s: Timeout waiting for SDRAM controller calibration\n",
+					       p_adapter_id_str);
+				}
+			}
+		}
+
+		/*
+		 * SDRAM controller is not calibrated with DDR4 ram blocks:
+		 * reset DDR and perform calibration retry
+		 */
+		field_set_flush(p->mp_fld_rst_ddr4); /* Reset DDR PLL */
+		NT_OS_WAIT_USEC(100);
+		field_clr_flush(p->mp_fld_rst_ddr4);
+
+		n_retry_cnt++;
+	}
+	nthw_sdc_delete(p_nthw_sdc);
+
+	return res;
+}
+
+int nthw_fpga_rst_nt200a0x_reset(nt_fpga_t *p_fpga,
+				const struct nthw_fpga_rst_nt200a0x *p)
+{
+	const char *const p_adapter_id_str = p_fpga->p_fpga_info->mp_adapter_id_str;
+	const fpga_info_t *const p_fpga_info = p_fpga->p_fpga_info;
+
+	const int n_fpga_product_id = p->mn_fpga_product_id;
+	const int n_fpga_version = p->mn_fpga_version;
+	const int n_fpga_revision = p->mn_fpga_revision;
+	const int n_nthw_adapter_id = p_fpga_info->n_nthw_adapter_id;
+	const bool b_is_nt200a01 = (n_nthw_adapter_id == NT_HW_ADAPTER_ID_NT200A01);
+	const int n_hw_id = p_fpga_info->nthw_hw_info.hw_id;
+	const uint8_t index = 0;
+	int locked;
+	int res = -1;
+
+	NT_LOG(DBG, NTHW,
+	       "%s: %s: FPGA reset sequence: FPGA %04d-%02d-%02d @ HWId%d\n",
+	       p_adapter_id_str, __func__, n_fpga_product_id, n_fpga_version,
+	       n_fpga_revision, n_hw_id);
+	assert(n_fpga_product_id == p_fpga->m_product_id);
+
+	/*
+	 * Reset all domains / modules except peripherals
+	 * Set default reset values to ensure that all modules are reset correctly
+	 * no matter if nic has been powercycled or ntservice has been reloaded
+	 */
+
+	/*
+	 * Reset to defaults
+	 * 1: Reset all domains
+	 */
+	NT_LOG(DBG, NTHW, "%s: RST defaults\n", p_adapter_id_str);
+
+	field_update_register(p->mp_fld_rst_sys);
+	field_set_flush(p->mp_fld_rst_sys);
+	if (p->mp_fld_rst_tmc)
+		field_set_flush(p->mp_fld_rst_tmc);
+	field_set_flush(p->mp_fld_rst_rpp);
+	field_set_flush(p->mp_fld_rst_ddr4); /* 0x07 3 banks */
+	field_set_flush(p->mp_fld_rst_sdc);
+
+	/* Reset port 0 and 1 in the following registers: */
+	field_set_flush(p->mp_fld_rst_phy); /* 0x03 2 ports */
+	if (p->mp_fld_rst_mac_rx)
+		field_set_flush(p->mp_fld_rst_mac_rx); /* 0x03 2 ports */
+
+	if (p->mp_fld_rst_mac_tx)
+		field_set_flush(p->mp_fld_rst_mac_tx); /* 0x03 2 ports */
+
+	if (p->mp_fld_rst_pcs_rx)
+		field_set_flush(p->mp_fld_rst_pcs_rx); /* 0x03 2 ports */
+
+	if (p->mp_fld_rst_serdes_rx)
+		field_set_flush(p->mp_fld_rst_serdes_rx); /* 0x03 2 ports */
+
+	if (p->mp_fld_rst_serdes_rx_datapath) {
+		field_set_flush(p->mp_fld_rst_serdes_rx_datapath);
+		field_clr_flush(p->mp_fld_rst_serdes_rx);
+	}
+	if (p->mp_fld_rst_serdes_tx)
+		field_set_flush(p->mp_fld_rst_serdes_tx);
+
+	field_set_flush(p->mp_fld_rst_ptp);
+	field_set_flush(p->mp_fld_rst_ts);
+	field_set_flush(p->mp_fld_rst_sys_mmcm);
+	field_set_flush(p->mp_fld_rst_core_mmcm);
+	field_set_flush(p->mp_fld_rst_ptp_mmcm);
+	field_set_flush(p->mp_fld_rst_ts_mmcm);
+
+	if ((true) && p->mp_fld_rst_tsm_ref_mmcm)
+		field_set_flush(p->mp_fld_rst_tsm_ref_mmcm);
+
+	/* Write all changes to register */
+	field_flush_register(p->mp_fld_rst_sys);
+
+	if (b_is_nt200a01 && n_hw_id == 2) { /* Not relevant to NT200A02 */
+		if (p->mp_fld_rst_tsm_ref_mmcm) {
+			field_update_register(p->mp_fld_rst_tsm_ref_mmcm);
+			field_set_flush(p->mp_fld_rst_tsm_ref_mmcm);
+		}
+	}
+
+	/*
+	 * 2: Force use of 50 MHz reference clock for timesync;
+	 * NOTE: From 9508-05-18 this is a 20 MHz clock
+	 */
+	NT_LOG(DBG, NTHW, "%s: Setting TS CLK SEL OVERRIDE\n", p_adapter_id_str);
+	field_update_register(p->mp_fld_ctrl_ts_clk_sel_override);
+	field_set_flush(p->mp_fld_ctrl_ts_clk_sel_override);
+
+	NT_LOG(DBG, NTHW, "%s: Setting TS CLK SEL\n", p_adapter_id_str);
+	field_update_register(p->mp_fld_ctrl_ts_clk_sel);
+	field_set_flush(p->mp_fld_ctrl_ts_clk_sel);
+
+	if (b_is_nt200a01 && n_hw_id == 2) { /* Not relevant to NT200A02 */
+		NT_LOG(DBG, NTHW, "%s: _selecting 20MHz TS CLK SEL REF\n",
+		       p_adapter_id_str);
+		if (p->mp_fld_ctrl_ts_clk_sel_ref) {
+			field_update_register(p->mp_fld_ctrl_ts_clk_sel_ref);
+			field_clr_flush(p->mp_fld_ctrl_ts_clk_sel_ref);
+		}
+	}
+
+	/* 4: De-assert sys reset, CORE and SYS MMCM resets */
+	NT_LOG(DBG, NTHW, "%s: De-asserting SYS, CORE and SYS MMCM resets\n",
+	       p_adapter_id_str);
+	field_update_register(p->mp_fld_rst_sys);
+	field_clr_flush(p->mp_fld_rst_sys);
+	field_clr_flush(p->mp_fld_rst_sys_mmcm);
+	field_clr_flush(p->mp_fld_rst_core_mmcm);
+
+	/* 5: wait until CORE MMCM and SYS MMCM are LOCKED */
+	NT_LOG(DBG, NTHW, "%s: Waiting for SYS MMCM to lock\n", p_adapter_id_str);
+	locked = field_wait_set_any32(p->mp_fld_stat_sys_mmcm_locked, -1, -1);
+	if (locked != 0) {
+		NT_LOG(ERR, NTHW,
+		       "%s: Waiting for SYS MMCM to lock failed (%d)\n",
+		       p_adapter_id_str, locked);
+	}
+
+	NT_LOG(DBG, NTHW, "%s: Waiting for CORE MMCM to lock\n", p_adapter_id_str);
+	locked = field_wait_set_any32(p->mp_fld_stat_core_mmcm_locked, -1, -1);
+	if (locked != 0) {
+		NT_LOG(ERR, NTHW,
+		       "%s: Waiting for CORE MMCM to lock failed (%d)\n",
+		       p_adapter_id_str, locked);
+	}
+
+	/* RAC RAB bus "flip/flip" reset second stage - new impl (ref RMT#37020) */
+	/* RAC/RAB init - SYS/CORE MMCM is locked - pull the remaining RAB buses out of reset */
+	{
+		nthw_rac_t *p_nthw_rac = p_fpga_info->mp_nthw_rac;
+
+		NT_LOG(DBG, NTHW, "%s: De-asserting remaining RAB buses\n",
+		       p_adapter_id_str);
+		nthw_rac_rab_init(p_nthw_rac, 0);
+	}
+
+	if ((true) && p->mp_fld_rst_tsm_ref_mmcm) {
+		NT_LOG(DBG, NTHW, "%s: De-asserting TSM REF MMCM\n",
+		       p_adapter_id_str);
+		field_clr_flush(p->mp_fld_rst_tsm_ref_mmcm);
+		if (p->mp_fld_stat_tsm_ref_mmcm_locked) {
+			NT_LOG(DBG, NTHW,
+			       "%s: Waiting for TSM REF MMCM to lock\n",
+			       p_adapter_id_str);
+			locked = field_wait_set_any32(p->mp_fld_stat_tsm_ref_mmcm_locked,
+						      -1, -1);
+			if (locked != 0) {
+				NT_LOG(ERR, NTHW,
+				       "%s: Waiting for TSM REF MMCM to lock failed (%d)\n",
+				       p_adapter_id_str, locked);
+			}
+		}
+	}
+
+	/*
+	 * 5.2: Having ensured CORE MMCM and SYS MMCM are LOCKED,
+	 * we need to select the alternative 20 MHz reference clock,
+	 * the external TSM reference clock
+	 * on NT200A01 - build 2 HW only (see SSF00024 p.32)
+	 */
+	if (b_is_nt200a01 && n_hw_id == 2) { /* Not relevant to NT200A02 */
+		NT_LOG(DBG, NTHW, "%s: Setting TS CLK SEL REF\n",
+		       p_adapter_id_str);
+		if (p->mp_fld_ctrl_ts_clk_sel_ref)
+			field_set_flush(p->mp_fld_ctrl_ts_clk_sel_ref);
+		if (p->mp_fld_rst_tsm_ref_mmcm) {
+			NT_LOG(DBG, NTHW, "%s: De-asserting TSM REF MMCM\n",
+			       p_adapter_id_str);
+			field_clr_flush(p->mp_fld_rst_tsm_ref_mmcm);
+		}
+		NT_LOG(DBG, NTHW, "%s: Waiting for TSM REF MMCM to lock\n",
+		       p_adapter_id_str);
+		if (p->mp_fld_stat_tsm_ref_mmcm_locked) {
+			locked = field_wait_set_any32(p->mp_fld_stat_tsm_ref_mmcm_locked,
+						      -1, -1);
+			if (locked != 0) {
+				NT_LOG(ERR, NTHW,
+				       "%s: Waiting for TSM REF MMCM to lock failed (%d)\n",
+				       p_adapter_id_str, locked);
+			}
+		}
+	}
+
+	NT_LOG(DBG, NTHW, "%s: De-asserting all PHY resets\n", p_adapter_id_str);
+	field_update_register(p->mp_fld_rst_phy);
+	field_clr_flush(p->mp_fld_rst_phy);
+
+	/* MAC_PCS_XXV 10G/25G: 9530 / 9544 */
+	if (n_fpga_product_id == 9530 || n_fpga_product_id == 9544) {
+		{
+			/* Based on nt200e3_2_ptp.cpp My25GbPhy::resetRx */
+			nthw_mac_pcs_xxv_t *p_nthw_mac_pcs_xxv0 = nthw_mac_pcs_xxv_new();
+
+			assert(p_nthw_mac_pcs_xxv0);
+			nthw_mac_pcs_xxv_init(p_nthw_mac_pcs_xxv0, p_fpga, 0, 1);
+
+			nthw_mac_pcs_xxv_reset_rx_gt_data(p_nthw_mac_pcs_xxv0, true,
+						    index);
+			NT_OS_WAIT_USEC(1000);
+
+			nthw_mac_pcs_xxv_reset_rx_gt_data(p_nthw_mac_pcs_xxv0, false,
+						    index);
+			NT_OS_WAIT_USEC(1000);
+
+			nthw_mac_pcs_xxv_delete(p_nthw_mac_pcs_xxv0);
+		}
+
+		{
+			/* Based on nt200e3_2_ptp.cpp My25GbPhy::resetRx */
+			nthw_mac_pcs_xxv_t *p_nthw_mac_pcs_xxv1 = nthw_mac_pcs_xxv_new();
+
+			assert(p_nthw_mac_pcs_xxv1);
+			nthw_mac_pcs_xxv_init(p_nthw_mac_pcs_xxv1, p_fpga, 1, 1);
+
+			nthw_mac_pcs_xxv_reset_rx_gt_data(p_nthw_mac_pcs_xxv1, true,
+						    index);
+			NT_OS_WAIT_USEC(1000);
+
+			nthw_mac_pcs_xxv_reset_rx_gt_data(p_nthw_mac_pcs_xxv1, false,
+						    index);
+			NT_OS_WAIT_USEC(1000);
+
+			nthw_mac_pcs_xxv_delete(p_nthw_mac_pcs_xxv1);
+		}
+		NT_OS_WAIT_USEC(3000);
+	}
+
+	/*
+	 * 8: De-assert reset for remaining domains/modules resets except
+	 * TS, PTP, PTP_MMCM and TS_MMCM
+	 */
+	NT_LOG(DBG, NTHW, "%s: De-asserting TMC RST\n", p_adapter_id_str);
+	if (p->mp_fld_rst_tmc) {
+		field_update_register(p->mp_fld_rst_tmc);
+		field_clr_flush(p->mp_fld_rst_tmc);
+	}
+
+	NT_LOG(DBG, NTHW, "%s: De-asserting RPP RST\n", p_adapter_id_str);
+	field_update_register(p->mp_fld_rst_rpp);
+	field_clr_flush(p->mp_fld_rst_rpp);
+
+	NT_LOG(DBG, NTHW, "%s: De-asserting DDR4 RST\n", p_adapter_id_str);
+	field_update_register(p->mp_fld_rst_ddr4);
+	field_clr_flush(p->mp_fld_rst_ddr4);
+
+	NT_LOG(DBG, NTHW, "%s: De-asserting SDC RST\n", p_adapter_id_str);
+	field_update_register(p->mp_fld_rst_sdc);
+	field_clr_flush(p->mp_fld_rst_sdc);
+
+	/* NOTE: 9522 implements PHY10G_QPLL reset and lock at this stage in mac_rx_rst() */
+	NT_LOG(DBG, NTHW, "%s: De-asserting MAC RX RST\n", p_adapter_id_str);
+	if (p->mp_fld_rst_mac_rx) {
+		field_update_register(p->mp_fld_rst_mac_rx);
+		field_clr_flush(p->mp_fld_rst_mac_rx);
+	}
+
+	/* await until DDR4 PLL LOCKED and SDRAM controller has been calibrated */
+	res = nthw_fpga_rst_nt200a0x_wait_sdc_calibrated(p_fpga, p);
+	if (res) {
+		NT_LOG(ERR, NTHW,
+		       "%s: nthw_fpga_rst_nt200a0x_wait_sdc_calibrated() returned true\n",
+		       p_adapter_id_str);
+		return -1;
+	}
+
+	if (field_get_updated(p->mp_fld_sticky_core_mmcm_unlocked)) {
+		NT_LOG(ERR, NTHW,
+		       "%s: get_sticky_core_mmcm_unlocked() returned true\n",
+		       p_adapter_id_str);
+		return -1;
+	}
+
+	if (p->mp_fld_sticky_pci_sys_mmcm_unlocked &&
+			field_get_updated(p->mp_fld_sticky_pci_sys_mmcm_unlocked)) {
+		NT_LOG(ERR, NTHW,
+		       "%s: get_sticky_pci_sys_mmcm_unlocked() returned true\n",
+		       p_adapter_id_str);
+		return -1;
+	}
+
+	if (b_is_nt200a01 && n_hw_id == 2) { /* Not relevant to NT200A02 */
+		if (p->mp_fld_sticky_tsm_ref_mmcm_unlocked &&
+				field_get_updated(p->mp_fld_sticky_tsm_ref_mmcm_unlocked)) {
+			NT_LOG(ERR, NTHW,
+			       "%s: get_sticky_tsm_ref_mmcm_unlocked returned true\n",
+			       p_adapter_id_str);
+			return -1;
+		}
+	}
+
+	/*
+	 * Timesync/PTP reset sequence
+	 * De-assert TS_MMCM reset
+	 */
+	NT_LOG(DBG, NTHW, "%s: De-asserting TS MMCM RST\n", p_adapter_id_str);
+	field_clr_flush(p->mp_fld_rst_ts_mmcm);
+
+	/* Wait until TS_MMCM LOCKED (NT_RAB0_REG_P9508_RST9508_STAT_TS_MMCM_LOCKED=1); */
+	NT_LOG(DBG, NTHW, "%s: Waiting for TS MMCM to lock\n", p_adapter_id_str);
+	locked = field_wait_set_any32(p->mp_fld_stat_ts_mmcm_locked, -1, -1);
+	if (locked != 0) {
+		NT_LOG(ERR, NTHW,
+		       "%s: Waiting for TS MMCM to lock failed (%d)\n",
+		       p_adapter_id_str, locked);
+	}
+
+	NT_LOG(DBG, NTHW, "%s: Calling clear_sticky_mmcm_unlock_bits()\n",
+	       p_adapter_id_str);
+	field_update_register(p->mp_fld_sticky_ptp_mmcm_unlocked);
+	/* Clear all sticky bits */
+	field_set_flush(p->mp_fld_sticky_ptp_mmcm_unlocked);
+	field_set_flush(p->mp_fld_sticky_ts_mmcm_unlocked);
+	field_set_flush(p->mp_fld_sticky_ddr4_mmcm_unlocked);
+	field_set_flush(p->mp_fld_sticky_ddr4_pll_unlocked);
+	field_set_flush(p->mp_fld_sticky_core_mmcm_unlocked);
+	if (p->mp_fld_sticky_tsm_ref_mmcm_unlocked)
+		field_set_flush(p->mp_fld_sticky_tsm_ref_mmcm_unlocked);
+	if (p->mp_fld_sticky_pci_sys_mmcm_unlocked)
+		field_set_flush(p->mp_fld_sticky_pci_sys_mmcm_unlocked);
+
+	/* De-assert TS reset bit */
+	NT_LOG(DBG, NTHW, "%s: De-asserting TS RST\n", p_adapter_id_str);
+	field_clr_flush(p->mp_fld_rst_ts);
+
+	if (field_get_updated(p->mp_fld_sticky_ts_mmcm_unlocked)) {
+		NT_LOG(ERR, NTHW,
+		       "%s: get_sticky_ts_mmcm_unlocked() returned true\n",
+		       p_adapter_id_str);
+		return -1;
+	}
+
+	if (field_get_updated(p->mp_fld_sticky_ddr4_mmcm_unlocked)) {
+		NT_LOG(ERR, NTHW,
+		       "%s: get_sticky_ddr4_mmcm_unlocked() returned true\n",
+		       p_adapter_id_str);
+		return -1;
+	}
+
+	if (field_get_updated(p->mp_fld_sticky_ddr4_pll_unlocked)) {
+		NT_LOG(ERR, NTHW,
+		       "%s: get_sticky_ddr4_pll_unlocked() returned true\n",
+		       p_adapter_id_str);
+		return -1;
+	}
+
+	if (field_get_updated(p->mp_fld_sticky_core_mmcm_unlocked)) {
+		NT_LOG(ERR, NTHW,
+		       "%s: get_sticky_core_mmcm_unlocked() returned true\n",
+		       p_adapter_id_str);
+		return -1;
+	}
+
+	if (p->mp_fld_sticky_pci_sys_mmcm_unlocked &&
+			field_get_updated(p->mp_fld_sticky_pci_sys_mmcm_unlocked)) {
+		NT_LOG(ERR, NTHW,
+		       "%s: get_sticky_pci_sys_mmcm_unlocked() returned true\n",
+		       p_adapter_id_str);
+		return -1;
+	}
+
+	if (b_is_nt200a01 && n_hw_id == 2) { /* Not relevant to NT200A02 */
+		if (p->mp_fld_sticky_tsm_ref_mmcm_unlocked &&
+				field_get_updated(p->mp_fld_sticky_tsm_ref_mmcm_unlocked)) {
+			NT_LOG(ERR, NTHW,
+			       "%s: get_sticky_tsm_ref_mmcm_unlocked() returned true\n",
+			       p_adapter_id_str);
+			return -1;
+		}
+	}
+
+	if (false) {
+		/* Deassert PTP_MMCM */
+		NT_LOG(DBG, NTHW, "%s: De-asserting PTP MMCM RST\n",
+		       p_adapter_id_str);
+		field_clr_flush(p->mp_fld_rst_ptp_mmcm);
+
+		if ((b_is_nt200a01 && n_fpga_version >= 9) || !b_is_nt200a01) {
+			/* Wait until PTP_MMCM LOCKED */
+			NT_LOG(DBG, NTHW, "%s: Waiting for PTP MMCM to lock\n",
+			       p_adapter_id_str);
+			locked = field_wait_set_any32(p->mp_fld_stat_ptp_mmcm_locked,
+						    -1, -1);
+			if (locked != 0) {
+				NT_LOG(ERR, NTHW,
+				       "%s: Waiting for PTP MMCM to lock failed (%d)\n",
+				       p_adapter_id_str, locked);
+			}
+		}
+
+		/* Switch PTP MMCM sel to use ptp clk */
+		NT_LOG(DBG, NTHW, "%s: Setting PTP MMCM CLK SEL\n",
+		       p_adapter_id_str);
+		field_set_flush(p->mp_fld_ctrl_ptp_mmcm_clk_sel);
+
+		/* Wait until TS_MMCM LOCKED (NT_RAB0_REG_P9508_RST9508_STAT_TS_MMCM_LOCKED=1); */
+		NT_LOG(DBG, NTHW, "%s: Waiting for TS MMCM to re-lock\n",
+		       p_adapter_id_str);
+		locked = field_wait_set_any32(p->mp_fld_stat_ts_mmcm_locked, -1, -1);
+		if (locked != 0) {
+			NT_LOG(ERR, NTHW,
+			       "%s: Waiting for TS MMCM to re-lock failed (%d)\n",
+			       p_adapter_id_str, locked);
+		}
+	}
+
+	NT_LOG(DBG, NTHW, "%s: De-asserting PTP RST\n", p_adapter_id_str);
+	field_clr_flush(p->mp_fld_rst_ptp);
+
+	/* POWER staging introduced in 9508-05-09 and always for 9512 */
+	if (n_fpga_product_id == 9508 && n_fpga_version <= 5 &&
+			n_fpga_revision <= 8) {
+		NT_LOG(DBG, NTHW, "%s: No power staging\n", p_adapter_id_str);
+	} else {
+		NT_LOG(DBG, NTHW, "%s: Staging power\n", p_adapter_id_str);
+		field_set_flush(p->mp_fld_power_pu_phy); /* PHY power up */
+		field_clr_flush(p->mp_fld_power_pu_nseb); /* NSEB power down */
+	}
+
+	NT_LOG(DBG, NTHW, "%s: %s: END\n", p_adapter_id_str, __func__);
+
+	return 0;
+}
+
+int nthw_fpga_rst_nt200a0x_init(struct fpga_info_s *p_fpga_info,
+			       struct nthw_fpga_rst_nt200a0x *p_rst)
+{
+	assert(p_fpga_info);
+
+	const char *const p_adapter_id_str = p_fpga_info->mp_adapter_id_str;
+	int res = -1;
+	int n_si_labs_clock_synth_model = -1;
+	uint8_t n_si_labs_clock_synth_i2c_addr = 0;
+	nt_fpga_t *p_fpga = NULL;
+
+	p_fpga = p_fpga_info->mp_fpga;
+
+	NT_LOG(DBG, NTHW, "%s: %s: RAB init/reset\n", p_adapter_id_str, __func__);
+	nthw_rac_rab_reset(p_fpga_info->mp_nthw_rac);
+	nthw_rac_rab_setup(p_fpga_info->mp_nthw_rac);
+
+	res = nthw_fpga_avr_probe(p_fpga, 0);
+
+	res = nthw_fpga_iic_scan(p_fpga, 0, 0);
+	res = nthw_fpga_iic_scan(p_fpga, 2, 3);
+
+	/*
+	 * Detect clock synth model
+	 * check for NT200A02/NT200A01 HW-build2 - most commonly seen
+	 */
+	n_si_labs_clock_synth_i2c_addr = si5340_u23_i2c_addr_7bit;
+	n_si_labs_clock_synth_model =
+		nthw_fpga_silabs_detect(p_fpga, 0, n_si_labs_clock_synth_i2c_addr, 1);
+	if (n_si_labs_clock_synth_model == -1) {
+		/* check for old NT200A01 HW-build1 */
+		n_si_labs_clock_synth_i2c_addr = si5338_u23_i2c_addr_7bit;
+		n_si_labs_clock_synth_model =
+			nthw_fpga_silabs_detect(p_fpga, 0,
+						n_si_labs_clock_synth_i2c_addr, 255);
+		if (n_si_labs_clock_synth_model == -1) {
+			NT_LOG(ERR, NTHW,
+			       "%s: Failed to detect clock synth model (%d)\n",
+			       p_adapter_id_str, n_si_labs_clock_synth_model);
+			return -1;
+		}
+	}
+	p_rst->mn_si_labs_clock_synth_model = n_si_labs_clock_synth_model;
+	p_rst->mn_si_labs_clock_synth_i2c_addr = n_si_labs_clock_synth_i2c_addr;
+	p_rst->mn_hw_id = p_fpga_info->nthw_hw_info.hw_id;
+	NT_LOG(DBG, NTHW, "%s: %s: Si%04d @ 0x%02x\n", p_adapter_id_str, __func__,
+	       p_rst->mn_si_labs_clock_synth_model, p_rst->mn_si_labs_clock_synth_i2c_addr);
+
+	return res;
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_fpga_rst_nt200a0x.h b/drivers/net/ntnic/nthw/core/nthw_fpga_rst_nt200a0x.h
new file mode 100644
index 0000000000..1f192f5ecc
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_fpga_rst_nt200a0x.h
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_FPGA_RST_NT200A0X_H__
+#define __NTHW_FPGA_RST_NT200A0X_H__
+
+#include "nthw_drv.h"
+#include "nthw_fpga_model.h"
+
+struct nthw_fpga_rst_nt200a0x {
+	int mn_fpga_product_id;
+	int mn_fpga_version;
+	int mn_fpga_revision;
+
+	int mn_hw_id;
+
+	int mn_si_labs_clock_synth_model;
+	uint8_t mn_si_labs_clock_synth_i2c_addr;
+
+	nt_field_t *mp_fld_rst_sys;
+	nt_field_t *mp_fld_rst_sys_mmcm;
+	nt_field_t *mp_fld_rst_core_mmcm;
+	nt_field_t *mp_fld_rst_rpp;
+	nt_field_t *mp_fld_rst_ddr4;
+	nt_field_t *mp_fld_rst_sdc;
+	nt_field_t *mp_fld_rst_phy;
+	nt_field_t *mp_fld_rst_serdes_rx;
+	nt_field_t *mp_fld_rst_serdes_tx;
+	nt_field_t *mp_fld_rst_serdes_rx_datapath;
+	nt_field_t *mp_fld_rst_pcs_rx;
+	nt_field_t *mp_fld_rst_mac_rx;
+	nt_field_t *mp_fld_rst_mac_tx;
+	nt_field_t *mp_fld_rst_ptp;
+	nt_field_t *mp_fld_rst_ts;
+	nt_field_t *mp_fld_rst_ptp_mmcm;
+	nt_field_t *mp_fld_rst_ts_mmcm;
+	nt_field_t *mp_fld_rst_periph;
+	nt_field_t *mp_fld_rst_tsm_ref_mmcm;
+	nt_field_t *mp_fld_rst_tmc;
+
+	/* CTRL register field pointers */
+	nt_field_t *mp_fld_ctrl_ts_clk_sel_override;
+	nt_field_t *mp_fld_ctrl_ts_clk_sel;
+	nt_field_t *mp_fld_ctrl_ts_clk_sel_ref;
+	nt_field_t *mp_fld_ctrl_ptp_mmcm_clk_sel;
+
+	/* STAT register field pointers */
+	nt_field_t *mp_fld_stat_ddr4_mmcm_locked;
+	nt_field_t *mp_fld_stat_sys_mmcm_locked;
+	nt_field_t *mp_fld_stat_core_mmcm_locked;
+	nt_field_t *mp_fld_stat_ddr4_pll_locked;
+	nt_field_t *mp_fld_stat_ptp_mmcm_locked;
+	nt_field_t *mp_fld_stat_ts_mmcm_locked;
+	nt_field_t *mp_fld_stat_tsm_ref_mmcm_locked;
+
+	/* STICKY register field pointers */
+	nt_field_t *mp_fld_sticky_ptp_mmcm_unlocked;
+	nt_field_t *mp_fld_sticky_ts_mmcm_unlocked;
+	nt_field_t *mp_fld_sticky_ddr4_mmcm_unlocked;
+	nt_field_t *mp_fld_sticky_ddr4_pll_unlocked;
+	nt_field_t *mp_fld_sticky_core_mmcm_unlocked;
+	nt_field_t *mp_fld_sticky_pci_sys_mmcm_unlocked;
+	nt_field_t *mp_fld_sticky_tsm_ref_mmcm_unlocked;
+
+	/* POWER register field pointers */
+	nt_field_t *mp_fld_power_pu_phy;
+	nt_field_t *mp_fld_power_pu_nseb;
+	/*  */
+
+	void (*reset_serdes_rx)(struct nthw_fpga_rst_nt200a0x *p, uint32_t intf_no,
+			      uint32_t rst);
+	void (*pcs_rx_rst)(struct nthw_fpga_rst_nt200a0x *p, uint32_t intf_no,
+			   uint32_t rst);
+	void (*get_serdes_rx_rst)(struct nthw_fpga_rst_nt200a0x *p,
+				  uint32_t intf_no, uint32_t *p_set);
+	void (*get_pcs_rx_rst)(struct nthw_fpga_rst_nt200a0x *p, uint32_t intf_no,
+			       uint32_t *p_set);
+	bool (*is_rst_serdes_rx_datapath_implemented)(struct nthw_fpga_rst_nt200a0x *p);
+};
+
+typedef struct nthw_fpga_rst_nt200a0x nthw_fpga_rst_nt200a0x_t;
+
+int nthw_fpga_rst_nt200a0x_init(struct fpga_info_s *p_fpga_info,
+				struct nthw_fpga_rst_nt200a0x *p_rst);
+int nthw_fpga_rst_nt200a0x_reset(nt_fpga_t *p_fpga,
+				 const struct nthw_fpga_rst_nt200a0x *p);
+
+#endif /* __NTHW_FPGA_RST_NT200A0X_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_gpio_phy.c b/drivers/net/ntnic/nthw/core/nthw_gpio_phy.c
new file mode 100644
index 0000000000..9b536726d0
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_gpio_phy.c
@@ -0,0 +1,271 @@
+/* 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_gpio_phy.h"
+
+nthw_gpio_phy_t *nthw_gpio_phy_new(void)
+{
+	nthw_gpio_phy_t *p = malloc(sizeof(nthw_gpio_phy_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_gpio_phy_t));
+	return p;
+}
+
+void nthw_gpio_phy_delete(nthw_gpio_phy_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_gpio_phy_t));
+		free(p);
+	}
+}
+
+int nthw_gpio_phy_init(nthw_gpio_phy_t *p, nt_fpga_t *p_fpga, int n_instance)
+{
+	nt_module_t *p_mod = fpga_query_module(p_fpga, MOD_GPIO_PHY, n_instance);
+
+	if (p == NULL)
+		return (p_mod == NULL ? -1 : 0);
+
+	if (p_mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: GPIO_PHY %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_gpio_phy = p_mod;
+
+	/* Registers */
+	p->mp_reg_config = module_get_register(p->mp_mod_gpio_phy, GPIO_PHY_CFG);
+	p->mp_reg_gpio = module_get_register(p->mp_mod_gpio_phy, GPIO_PHY_GPIO);
+
+	/* PORT-0, config fields */
+	p->mpa_fields[0].cfg_fld_lp_mode =
+		register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT0_LPMODE);
+	p->mpa_fields[0].cfg_int =
+		register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT0_INT_B);
+	p->mpa_fields[0].cfg_reset =
+		register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT0_RESET_B);
+	p->mpa_fields[0].cfg_mod_prs =
+		register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT0_MODPRS_B);
+
+	/* PORT-0, Non-mandatory fields (queryField) */
+	p->mpa_fields[0].cfg_pll_int =
+		register_query_field(p->mp_reg_config, GPIO_PHY_CFG_PORT0_PLL_INTR);
+	p->mpa_fields[0].cfg_port_rxlos =
+		register_query_field(p->mp_reg_config, GPIO_PHY_CFG_E_PORT0_RXLOS);
+
+	/* PORT-1, config fields */
+	p->mpa_fields[1].cfg_fld_lp_mode =
+		register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT1_LPMODE);
+	p->mpa_fields[1].cfg_int =
+		register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT1_INT_B);
+	p->mpa_fields[1].cfg_reset =
+		register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT1_RESET_B);
+	p->mpa_fields[1].cfg_mod_prs =
+		register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT1_MODPRS_B);
+
+	/* PORT-1, Non-mandatory fields (queryField) */
+	p->mpa_fields[1].cfg_pll_int =
+		register_query_field(p->mp_reg_config, GPIO_PHY_CFG_PORT1_PLL_INTR);
+	p->mpa_fields[1].cfg_port_rxlos =
+		register_query_field(p->mp_reg_config, GPIO_PHY_CFG_E_PORT1_RXLOS);
+
+	/* PORT-0, gpio fields */
+	p->mpa_fields[0].gpio_fld_lp_mode =
+		register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT0_LPMODE);
+	p->mpa_fields[0].gpio_int =
+		register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT0_INT_B);
+	p->mpa_fields[0].gpio_reset =
+		register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT0_RESET_B);
+	p->mpa_fields[0].gpio_mod_prs =
+		register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT0_MODPRS_B);
+
+	/* PORT-0, Non-mandatory fields (queryField) */
+	p->mpa_fields[0].gpio_pll_int =
+		register_query_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT0_PLL_INTR);
+	p->mpa_fields[0].gpio_port_rxlos =
+		register_query_field(p->mp_reg_gpio, GPIO_PHY_GPIO_E_PORT0_RXLOS);
+
+	/* PORT-1, gpio fields */
+	p->mpa_fields[1].gpio_fld_lp_mode =
+		register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT1_LPMODE);
+	p->mpa_fields[1].gpio_int =
+		register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT1_INT_B);
+	p->mpa_fields[1].gpio_reset =
+		register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT1_RESET_B);
+	p->mpa_fields[1].gpio_mod_prs =
+		register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT1_MODPRS_B);
+
+	/* PORT-1, Non-mandatory fields (queryField) */
+	p->mpa_fields[1].gpio_pll_int =
+		register_query_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT1_PLL_INTR);
+	p->mpa_fields[1].gpio_port_rxlos =
+		register_query_field(p->mp_reg_gpio, GPIO_PHY_GPIO_E_PORT1_RXLOS);
+
+	register_update(p->mp_reg_config);
+
+	return 0;
+}
+
+bool nthw_gpio_phy_is_low_power_enabled(nthw_gpio_phy_t *p, uint8_t if_no)
+{
+	if (if_no >= ARRAY_SIZE(p->mpa_fields)) {
+		assert(false);
+		return false;
+	}
+
+	if (field_get_updated(p->mpa_fields[if_no].gpio_fld_lp_mode))
+		return true;
+
+	else
+		return false;
+}
+
+bool nthw_gpio_phy_is_interrupt_set(nthw_gpio_phy_t *p, uint8_t if_no)
+{
+	if (if_no >= ARRAY_SIZE(p->mpa_fields)) {
+		assert(false);
+		return false;
+	}
+
+	/* NOTE: This is a negated GPIO PIN "INT_B" */
+	if (field_get_updated(p->mpa_fields[if_no].gpio_int))
+		return false;
+
+	else
+		return true;
+}
+
+bool nthw_gpio_phy_is_reset(nthw_gpio_phy_t *p, uint8_t if_no)
+{
+	if (if_no >= ARRAY_SIZE(p->mpa_fields)) {
+		assert(false);
+		return false;
+	}
+
+	/* NOTE: This is a negated GPIO PIN "RESET_B" */
+	if (field_get_updated(p->mpa_fields[if_no].gpio_reset))
+		return false;
+
+	else
+		return true;
+}
+
+bool nthw_gpio_phy_is_module_present(nthw_gpio_phy_t *p, uint8_t if_no)
+{
+	if (if_no >= ARRAY_SIZE(p->mpa_fields)) {
+		assert(false);
+		return false;
+	}
+
+	/* NOTE: This is a negated GPIO PIN "MODPRS_B" */
+	return field_get_updated(p->mpa_fields[if_no].gpio_mod_prs) == 0U ? true :
+	       false;
+}
+
+bool nthw_gpio_phy_is_pll_interrupt_set(nthw_gpio_phy_t *p, uint8_t if_no)
+{
+	if (if_no >= ARRAY_SIZE(p->mpa_fields)) {
+		assert(false);
+		return false;
+	}
+
+	/* NOTE: This is a normal GPIO PIN "PLL_INTR" */
+	if (p->mpa_fields[if_no].gpio_pll_int) {
+		if (field_get_updated(p->mpa_fields[if_no].gpio_pll_int))
+			return true;
+
+		else
+			return false;
+	} else {
+		/* this HW doesn't support "PLL_INTR" (INTR from SyncE jitter attenuater) */
+		return false;
+	}
+}
+
+bool nthw_gpio_phy_is_port_rxlos(nthw_gpio_phy_t *p, uint8_t if_no)
+{
+	if (if_no >= ARRAY_SIZE(p->mpa_fields)) {
+		assert(false);
+		return false;
+	}
+
+	if (p->mpa_fields[if_no].gpio_port_rxlos) {
+		if (field_get_updated(p->mpa_fields[if_no].gpio_port_rxlos))
+			return true;
+		else
+			return false;
+	} else {
+		return false;
+	}
+}
+
+void nthw_gpio_phy_set_low_power(nthw_gpio_phy_t *p, uint8_t if_no, bool enable)
+{
+	if (if_no >= ARRAY_SIZE(p->mpa_fields)) {
+		assert(false);
+		return;
+	}
+
+	if (enable)
+		field_set_flush(p->mpa_fields[if_no].gpio_fld_lp_mode);
+
+	else
+		field_clr_flush(p->mpa_fields[if_no].gpio_fld_lp_mode);
+	field_clr_flush(p->mpa_fields[if_no].cfg_fld_lp_mode); /* enable output */
+}
+
+void nthw_gpio_phy_set_reset(nthw_gpio_phy_t *p, uint8_t if_no, bool enable)
+{
+	if (if_no >= ARRAY_SIZE(p->mpa_fields)) {
+		assert(false);
+		return;
+	}
+
+	if (enable)
+		field_clr_flush(p->mpa_fields[if_no].gpio_reset);
+
+	else
+		field_set_flush(p->mpa_fields[if_no].gpio_reset);
+	field_clr_flush(p->mpa_fields[if_no].cfg_reset); /* enable output */
+}
+
+void nthw_gpio_phy_set_port_rxlos(nthw_gpio_phy_t *p, uint8_t if_no, bool enable)
+{
+	if (if_no >= ARRAY_SIZE(p->mpa_fields)) {
+		assert(false);
+		return;
+	}
+
+	if (p->mpa_fields[if_no].gpio_port_rxlos) {
+		if (enable)
+			field_set_flush(p->mpa_fields[if_no].gpio_port_rxlos);
+
+		else
+			field_clr_flush(p->mpa_fields[if_no].gpio_port_rxlos);
+	}
+}
+
+void nthw_gpio_phy_set_cfg_default_values(nthw_gpio_phy_t *p, uint8_t if_no)
+{
+	if (if_no >= ARRAY_SIZE(p->mpa_fields)) {
+		assert(false);
+		return;
+	}
+
+	field_set_flush(p->mpa_fields[if_no].cfg_fld_lp_mode); /* enable input */
+	field_set_flush(p->mpa_fields[if_no].cfg_int); /* enable input */
+	field_set_flush(p->mpa_fields[if_no].cfg_reset); /* enable input */
+	field_set_flush(p->mpa_fields[if_no].cfg_mod_prs); /* enable input */
+	if (p->mpa_fields[if_no].cfg_port_rxlos)
+		field_clr_flush(p->mpa_fields[if_no].cfg_port_rxlos); /* enable output */
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_gpio_phy.h b/drivers/net/ntnic/nthw/core/nthw_gpio_phy.h
new file mode 100644
index 0000000000..1c6185150c
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_gpio_phy.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NTHW_GPIO_PHY_H_
+#define NTHW_GPIO_PHY_H_
+
+#define GPIO_PHY_INTERFACES (2)
+
+typedef struct {
+	nt_field_t *cfg_fld_lp_mode; /* Cfg Low Power Mode */
+	nt_field_t *cfg_int; /* Cfg Port Interrupt */
+	nt_field_t *cfg_reset; /* Cfg Reset */
+	nt_field_t *cfg_mod_prs; /* Cfg Module Present */
+	nt_field_t *cfg_pll_int; /* Cfg PLL Interrupt */
+	nt_field_t *cfg_port_rxlos; /* Emulate Cfg Port RXLOS */
+
+	nt_field_t *gpio_fld_lp_mode; /* Gpio Low Power Mode */
+	nt_field_t *gpio_int; /* Gpio Port Interrupt */
+	nt_field_t *gpio_reset; /* Gpio Reset */
+	nt_field_t *gpio_mod_prs; /* Gpio Module Present */
+	nt_field_t *gpio_pll_int; /* Gpio PLL Interrupt */
+	nt_field_t *gpio_port_rxlos; /* Emulate Gpio Port RXLOS */
+} gpio_phy_fields_t;
+
+struct nthw_gpio_phy {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_gpio_phy;
+	int mn_instance;
+
+	/* Registers */
+	nt_register_t *mp_reg_config;
+	nt_register_t *mp_reg_gpio;
+
+	/* Fields */
+	gpio_phy_fields_t mpa_fields[GPIO_PHY_INTERFACES];
+};
+
+typedef struct nthw_gpio_phy nthw_gpio_phy_t;
+typedef struct nthw_gpio_phy nthw_gpio_phy;
+
+nthw_gpio_phy_t *nthw_gpio_phy_new(void);
+void nthw_gpio_phy_delete(nthw_gpio_phy_t *p);
+int nthw_gpio_phy_init(nthw_gpio_phy_t *p, nt_fpga_t *p_fpga, int n_instance);
+
+bool nthw_gpio_phy_is_low_power_enabled(nthw_gpio_phy_t *p, uint8_t if_no);
+bool nthw_gpio_phy_is_interrupt_set(nthw_gpio_phy_t *p, uint8_t if_no);
+bool nthw_gpio_phy_is_reset(nthw_gpio_phy_t *p, uint8_t if_no);
+bool nthw_gpio_phy_is_module_present(nthw_gpio_phy_t *p, uint8_t if_no);
+bool nthw_gpio_phy_is_pll_interrupt_set(nthw_gpio_phy_t *p, uint8_t if_no);
+bool nthw_gpio_phy_is_port_rxlos(nthw_gpio_phy_t *p, uint8_t if_no);
+void nthw_gpio_phy_set_low_power(nthw_gpio_phy_t *p, uint8_t if_no, bool enable);
+void nthw_gpio_phy_set_reset(nthw_gpio_phy_t *p, uint8_t if_no, bool enable);
+void nthw_gpio_phy_set_port_rxlos(nthw_gpio_phy_t *p, uint8_t if_no, bool enable);
+void nthw_gpio_phy_set_cfg_default_values(nthw_gpio_phy_t *p, uint8_t if_no);
+
+#endif /* NTHW_GPIO_PHY_H_ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_hif.c b/drivers/net/ntnic/nthw/core/nthw_hif.c
new file mode 100644
index 0000000000..7b7a919108
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_hif.c
@@ -0,0 +1,342 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "nt_util.h"
+#include "ntlog.h"
+
+#include "nthw_drv.h"
+#include "nthw_register.h"
+
+#include "nthw_hif.h"
+
+nthw_hif_t *nthw_hif_new(void)
+{
+	nthw_hif_t *p = malloc(sizeof(nthw_hif_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_hif_t));
+	return p;
+}
+
+void nthw_hif_delete(nthw_hif_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_hif_t));
+		free(p);
+	}
+}
+
+int nthw_hif_init(nthw_hif_t *p, nt_fpga_t *p_fpga, int n_instance)
+{
+	const char *const p_adapter_id_str _unused =
+		p_fpga->p_fpga_info->mp_adapter_id_str;
+	nt_module_t *mod = fpga_query_module(p_fpga, MOD_HIF, n_instance);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: HIF %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_hif = mod;
+
+	/* default for (Xilinx-based) products until august 2022: (1e6/4000 = 250 MHz) */
+	p->mn_fpga_param_hif_per_ps =
+		fpga_get_product_param(p->mp_fpga, NT_HIF_PER_PS, 4000);
+	p->mn_fpga_hif_ref_clk_freq =
+		(uint32_t)(1000000000000ULL /
+			   (unsigned int)p->mn_fpga_param_hif_per_ps);
+
+	p->mp_reg_prod_id_lsb = module_get_register(p->mp_mod_hif, HIF_PROD_ID_LSB);
+	p->mp_fld_prod_id_lsb_rev_id =
+		register_get_field(p->mp_reg_prod_id_lsb, HIF_PROD_ID_LSB_REV_ID);
+	p->mp_fld_prod_id_lsb_ver_id =
+		register_get_field(p->mp_reg_prod_id_lsb, HIF_PROD_ID_LSB_VER_ID);
+	p->mp_fld_prod_id_lsb_group_id =
+		register_get_field(p->mp_reg_prod_id_lsb, HIF_PROD_ID_LSB_GROUP_ID);
+
+	p->mp_reg_prod_id_msb = module_get_register(p->mp_mod_hif, HIF_PROD_ID_MSB);
+	p->mp_fld_prod_id_msb_type_id =
+		register_get_field(p->mp_reg_prod_id_msb, HIF_PROD_ID_MSB_TYPE_ID);
+	p->mp_fld_prod_id_msb_build_no =
+		register_get_field(p->mp_reg_prod_id_msb, HIF_PROD_ID_MSB_BUILD_NO);
+
+	p->mp_reg_build_time = module_get_register(p->mp_mod_hif, HIF_BUILD_TIME);
+	p->mp_fld_build_time =
+		register_get_field(p->mp_reg_build_time, HIF_BUILD_TIME_TIME);
+
+	p->mn_fpga_id_prod = field_get_updated(p->mp_fld_prod_id_lsb_group_id);
+	p->mn_fpga_id_ver = field_get_updated(p->mp_fld_prod_id_lsb_ver_id);
+	p->mn_fpga_id_rev = field_get_updated(p->mp_fld_prod_id_lsb_rev_id);
+	p->mn_fpga_id_build_no = field_get_updated(p->mp_fld_prod_id_msb_build_no);
+	p->mn_fpga_id_item = field_get_updated(p->mp_fld_prod_id_msb_type_id);
+
+	NT_LOG(DBG, NTHW, "%s: HIF %d: %s: %d-%d-%d-%d-%d\n", p_adapter_id_str,
+	       p->mn_instance, __func__, p->mn_fpga_id_item, p->mn_fpga_id_prod,
+	       p->mn_fpga_id_ver, p->mn_fpga_id_rev, p->mn_fpga_id_build_no);
+	NT_LOG(DBG, NTHW,
+	       "%s: HIF %d: %s: HIF ref clock: %d Hz (%d ticks/ps)\n",
+	       p_adapter_id_str, p->mn_instance, __func__, p->mn_fpga_hif_ref_clk_freq,
+	       p->mn_fpga_param_hif_per_ps);
+
+	p->mp_reg_build_seed = NULL; /* Reg/Fld not present on HIF */
+	if (p->mp_reg_build_seed)
+		p->mp_fld_build_seed = NULL; /* Reg/Fld not present on HIF */
+	else
+		p->mp_fld_build_seed = NULL;
+
+	p->mp_reg_core_speed = NULL; /* Reg/Fld not present on HIF */
+	if (p->mp_reg_core_speed) {
+		p->mp_fld_core_speed = NULL; /* Reg/Fld not present on HIF */
+		p->mp_fld_ddr3_speed = NULL; /* Reg/Fld not present on HIF */
+	} else {
+		p->mp_reg_core_speed = NULL;
+		p->mp_fld_core_speed = NULL;
+		p->mp_fld_ddr3_speed = NULL;
+	}
+
+	/* Optional registers since: 2018-04-25 */
+	p->mp_reg_int_mask = NULL; /* Reg/Fld not present on HIF */
+	p->mp_reg_int_clr = NULL; /* Reg/Fld not present on HIF */
+	p->mp_reg_int_force = NULL; /* Reg/Fld not present on HIF */
+
+	p->mp_fld_int_mask_timer = NULL;
+	p->mp_fld_int_clr_timer = NULL;
+	p->mp_fld_int_force_timer = NULL;
+
+	p->mp_fld_int_mask_port = NULL;
+	p->mp_fld_int_clr_port = NULL;
+	p->mp_fld_int_force_port = NULL;
+
+	p->mp_fld_int_mask_pps = NULL;
+	p->mp_fld_int_clr_pps = NULL;
+	p->mp_fld_int_force_pps = NULL;
+
+	p->mp_reg_stat_ctrl = module_get_register(p->mp_mod_hif, HIF_STAT_CTRL);
+	p->mp_fld_stat_ctrl_ena =
+		register_get_field(p->mp_reg_stat_ctrl, HIF_STAT_CTRL_STAT_ENA);
+	p->mp_fld_stat_ctrl_req =
+		register_get_field(p->mp_reg_stat_ctrl, HIF_STAT_CTRL_STAT_REQ);
+
+	p->mp_reg_stat_rx = module_get_register(p->mp_mod_hif, HIF_STAT_RX);
+	p->mp_fld_stat_rx_counter =
+		register_get_field(p->mp_reg_stat_rx, HIF_STAT_RX_COUNTER);
+
+	p->mp_reg_stat_tx = module_get_register(p->mp_mod_hif, HIF_STAT_TX);
+	p->mp_fld_stat_tx_counter =
+		register_get_field(p->mp_reg_stat_tx, HIF_STAT_TX_COUNTER);
+
+	p->mp_reg_stat_ref_clk = module_get_register(p->mp_mod_hif, HIF_STAT_REFCLK);
+	p->mp_fld_stat_ref_clk_ref_clk = register_get_field(p->mp_reg_stat_ref_clk,
+				   HIF_STAT_REFCLK_REFCLK250);
+
+	p->mp_reg_status = module_query_register(p->mp_mod_hif, HIF_STATUS);
+	if (p->mp_reg_status) {
+		p->mp_fld_status_tags_in_use =
+			register_query_field(p->mp_reg_status, HIF_STATUS_TAGS_IN_USE);
+		p->mp_fld_status_wr_err =
+			register_query_field(p->mp_reg_status, HIF_STATUS_WR_ERR);
+		p->mp_fld_status_rd_err =
+			register_query_field(p->mp_reg_status, HIF_STATUS_RD_ERR);
+	} else {
+		p->mp_reg_status = module_query_register(p->mp_mod_hif, HIF_STATUS);
+		p->mp_fld_status_tags_in_use =
+			register_query_field(p->mp_reg_status, HIF_STATUS_TAGS_IN_USE);
+		p->mp_fld_status_wr_err = NULL;
+		p->mp_fld_status_rd_err = NULL;
+	}
+
+	p->mp_reg_pci_test0 = module_get_register(p->mp_mod_hif, HIF_TEST0);
+	p->mp_fld_pci_test0 = register_get_field(p->mp_reg_pci_test0, HIF_TEST0_DATA);
+
+	p->mp_reg_pci_test1 = module_get_register(p->mp_mod_hif, HIF_TEST1);
+	p->mp_fld_pci_test1 = register_get_field(p->mp_reg_pci_test1, HIF_TEST1_DATA);
+
+	/* Required to run TSM */
+	p->mp_reg_sample_time = module_get_register(p->mp_mod_hif, HIF_SAMPLE_TIME);
+	if (p->mp_reg_sample_time) {
+		p->mp_fld_sample_time =
+			register_get_field(p->mp_reg_sample_time, HIF_SAMPLE_TIME_SAMPLE_TIME);
+	} else {
+		p->mp_fld_sample_time = NULL;
+	}
+
+	/* We need to optimize PCIe3 TLP-size read-request and extended tag usage */
+	{
+		p->mp_reg_config = module_query_register(p->mp_mod_hif, HIF_CONFIG);
+		if (p->mp_reg_config) {
+			p->mp_fld_max_tlp =
+				register_get_field(p->mp_reg_config, HIF_CONFIG_MAX_TLP);
+			p->mp_fld_max_read =
+				register_get_field(p->mp_reg_config, HIF_CONFIG_MAX_READ);
+			p->mp_fld_ext_tag =
+				register_get_field(p->mp_reg_config, HIF_CONFIG_EXT_TAG);
+		} else {
+			p->mp_fld_max_tlp = NULL;
+			p->mp_fld_max_read = NULL;
+			p->mp_fld_ext_tag = NULL;
+		}
+	}
+
+	return 0;
+}
+
+int nthw_hif_setup_config(nthw_hif_t *p)
+{
+	const char *const p_adapter_id_str _unused =
+		p->mp_fpga->p_fpga_info->mp_adapter_id_str;
+
+	/*
+	 * We need to optimize PCIe3 read-request and extended tag usage
+	 * original check: HW_ADAPTER_ID_NT200A02 HW_ADAPTER_ID_NT100A01 HW_ADAPTER_ID_NT50B01
+	 */
+	if (p->mp_fpga->p_fpga_info->n_nthw_adapter_id != NT_HW_ADAPTER_ID_NT40E3) {
+		if (p->mp_fld_max_read) {
+			/*
+			 * NOTE: On Pandion DELL server, this param was negotiated to 4096
+			 * (index=5), but the server crashed. For now we need to limit this value to
+			 * 512 (index=2)
+			 */
+			const uint32_t n_max_read_req_size =
+				field_get_updated(p->mp_fld_max_read);
+			if (n_max_read_req_size > 2) {
+				field_set_val_flush32(p->mp_fld_max_read, 2);
+				NT_LOG(INF, NTHW,
+				       "%s: %s: PCIe: MaxReadReqsize %d - changed to 2 (512B)\n",
+				       p_adapter_id_str, __func__,
+				       n_max_read_req_size);
+			}
+		}
+
+		if (p->mp_fld_ext_tag)
+			field_set_val_flush32(p->mp_fld_ext_tag, 1);
+
+		if (p->mp_fld_max_tlp && p->mp_fld_max_read && p->mp_fld_ext_tag) {
+			NT_LOG(INF, NTHW,
+			       "%s: %s: PCIe config: MaxTlp = %d, MaxReadReqsize = %d, ExtTagEna = %d\n",
+			       p_adapter_id_str, __func__,
+			       field_get_updated(p->mp_fld_max_tlp),
+			       field_get_updated(p->mp_fld_max_read),
+			       field_get_updated(p->mp_fld_ext_tag));
+		}
+	}
+	return 0;
+}
+
+int nthw_hif_trigger_sample_time(nthw_hif_t *p)
+{
+	field_set_val_flush32(p->mp_fld_sample_time, 0xfee1dead);
+
+	return 0;
+}
+
+int nthw_hif_get_stat(nthw_hif_t *p, uint32_t *p_rx_cnt, uint32_t *p_tx_cnt,
+		    uint32_t *p_ref_clk_cnt, uint32_t *p_tg_unit_size,
+		    uint32_t *p_tg_ref_freq, uint64_t *p_tags_in_use,
+		    uint64_t *p_rd_err, uint64_t *p_wr_err)
+{
+	*p_rx_cnt = field_get_updated(p->mp_fld_stat_rx_counter);
+	*p_tx_cnt = field_get_updated(p->mp_fld_stat_tx_counter);
+
+	*p_ref_clk_cnt = field_get_updated(p->mp_fld_stat_ref_clk_ref_clk);
+
+	*p_tg_unit_size = NTHW_TG_CNT_SIZE;
+	*p_tg_ref_freq = p->mn_fpga_hif_ref_clk_freq;
+
+	*p_tags_in_use = (p->mp_fld_status_tags_in_use ?
+		       field_get_updated(p->mp_fld_status_tags_in_use) :
+		       0);
+
+	*p_rd_err = (p->mp_fld_status_rd_err ? field_get_updated(p->mp_fld_status_rd_err) :
+		   0);
+	*p_wr_err = (p->mp_fld_status_wr_err ? field_get_updated(p->mp_fld_status_wr_err) :
+		   0);
+
+	return 0;
+}
+
+int nthw_hif_get_stat_rate(nthw_hif_t *p, uint64_t *p_pci_rx_rate,
+			uint64_t *p_pci_tx_rate, uint64_t *p_ref_clk_cnt,
+			uint64_t *p_tags_in_use, uint64_t *p_rd_err_cnt,
+			uint64_t *p_wr_err_cnt)
+{
+	uint32_t rx_cnt, tx_cnt, ref_clk_cnt, tg_unit_size, tg_ref_freq;
+	uint64_t n_tags_in_use, n_rd_err, n_wr_err;
+
+	nthw_hif_get_stat(p, &rx_cnt, &tx_cnt, &ref_clk_cnt, &tg_unit_size, &tg_ref_freq,
+			&n_tags_in_use, &n_rd_err, &n_wr_err);
+
+	*p_tags_in_use = n_tags_in_use;
+	if (n_rd_err)
+		(*p_rd_err_cnt)++;
+	if (n_wr_err)
+		(*p_wr_err_cnt)++;
+
+	if (ref_clk_cnt) {
+		uint64_t rx_rate;
+		uint64_t tx_rate;
+
+		*p_ref_clk_cnt = ref_clk_cnt;
+
+		rx_rate = ((uint64_t)rx_cnt * tg_unit_size * tg_ref_freq) /
+			 (uint64_t)ref_clk_cnt;
+		*p_pci_rx_rate = rx_rate;
+
+		tx_rate = ((uint64_t)tx_cnt * tg_unit_size * tg_ref_freq) /
+			 (uint64_t)ref_clk_cnt;
+		*p_pci_tx_rate = tx_rate;
+	} else {
+		*p_pci_rx_rate = 0;
+		*p_pci_tx_rate = 0;
+		*p_ref_clk_cnt = 0;
+	}
+
+	return 0;
+}
+
+int nthw_hif_stat_req_enable(nthw_hif_t *p)
+{
+	field_set_all(p->mp_fld_stat_ctrl_ena);
+	field_set_all(p->mp_fld_stat_ctrl_req);
+	field_flush_register(p->mp_fld_stat_ctrl_req);
+	return 0;
+}
+
+int nthw_hif_stat_req_disable(nthw_hif_t *p)
+{
+	field_clr_all(p->mp_fld_stat_ctrl_ena);
+	field_set_all(p->mp_fld_stat_ctrl_req);
+	field_flush_register(p->mp_fld_stat_ctrl_req);
+	return 0;
+}
+
+int nthw_hif_stat_sample(nthw_hif_t *p, uint64_t *p_rx_rate, uint64_t *p_tx_rate,
+		       uint64_t *p_ref_clk_cnt, uint64_t *p_tags_in_use,
+		       uint64_t *p_rd_err_cnt, uint64_t *p_wr_err_cnt)
+{
+	nthw_hif_stat_req_enable(p);
+	NT_OS_WAIT_USEC(100000);
+	nthw_hif_stat_req_disable(p);
+	nthw_hif_get_stat_rate(p, p_rx_rate, p_tx_rate, p_ref_clk_cnt, p_tags_in_use,
+			    p_rd_err_cnt, p_wr_err_cnt);
+
+	return 0;
+}
+
+int nthw_hif_end_point_counters_sample(nthw_hif_t *p,
+				   struct nthw_hif_end_point_counters *epc)
+{
+	assert(epc);
+
+	/* Get stat rate and maintain rx/tx min/max */
+	nthw_hif_get_stat_rate(p, &epc->cur_tx, &epc->cur_rx, &epc->n_ref_clk_cnt,
+			    &epc->n_tags_in_use, &epc->n_rd_err, &epc->n_wr_err);
+
+	return 0;
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_hif.h b/drivers/net/ntnic/nthw/core/nthw_hif.h
new file mode 100644
index 0000000000..2701e222b3
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_hif.h
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_HIF_H__
+#define __NTHW_HIF_H__
+
+#define NTHW_TG_CNT_SIZE (4ULL)
+
+struct nthw_hif {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_hif;
+	int mn_instance;
+
+	nt_register_t *mp_reg_prod_id_lsb;
+	nt_field_t *mp_fld_prod_id_lsb_rev_id;
+	nt_field_t *mp_fld_prod_id_lsb_ver_id;
+	nt_field_t *mp_fld_prod_id_lsb_group_id;
+
+	nt_register_t *mp_reg_prod_id_msb;
+	nt_field_t *mp_fld_prod_id_msb_type_id;
+	nt_field_t *mp_fld_prod_id_msb_build_no;
+
+	nt_register_t *mp_reg_build_time;
+	nt_field_t *mp_fld_build_time;
+
+	nt_register_t *mp_reg_build_seed;
+	nt_field_t *mp_fld_build_seed;
+
+	nt_register_t *mp_reg_core_speed;
+	nt_field_t *mp_fld_core_speed;
+	nt_field_t *mp_fld_ddr3_speed;
+
+	nt_register_t *mp_reg_int_mask;
+	nt_field_t *mp_fld_int_mask_timer;
+	nt_field_t *mp_fld_int_mask_port;
+	nt_field_t *mp_fld_int_mask_pps;
+
+	nt_register_t *mp_reg_int_clr;
+	nt_field_t *mp_fld_int_clr_timer;
+	nt_field_t *mp_fld_int_clr_port;
+	nt_field_t *mp_fld_int_clr_pps;
+
+	nt_register_t *mp_reg_int_force;
+	nt_field_t *mp_fld_int_force_timer;
+	nt_field_t *mp_fld_int_force_port;
+	nt_field_t *mp_fld_int_force_pps;
+
+	nt_register_t *mp_reg_sample_time;
+	nt_field_t *mp_fld_sample_time;
+
+	nt_register_t *mp_reg_status;
+	nt_field_t *mp_fld_status_tags_in_use;
+	nt_field_t *mp_fld_status_wr_err;
+	nt_field_t *mp_fld_status_rd_err;
+
+	nt_register_t *mp_reg_stat_ctrl;
+	nt_field_t *mp_fld_stat_ctrl_ena;
+	nt_field_t *mp_fld_stat_ctrl_req;
+
+	nt_register_t *mp_reg_stat_rx;
+	nt_field_t *mp_fld_stat_rx_counter;
+
+	nt_register_t *mp_reg_stat_tx;
+	nt_field_t *mp_fld_stat_tx_counter;
+
+	nt_register_t *mp_reg_stat_ref_clk;
+	nt_field_t *mp_fld_stat_ref_clk_ref_clk;
+
+	nt_register_t *mp_reg_pci_test0;
+	nt_field_t *mp_fld_pci_test0;
+
+	nt_register_t *mp_reg_pci_test1;
+	nt_field_t *mp_fld_pci_test1;
+
+	nt_register_t *mp_reg_pci_test2;
+	nt_field_t *mp_fld_pci_test2;
+
+	nt_register_t *mp_reg_pci_test3;
+	nt_field_t *mp_fld_pci_test3;
+
+	nt_register_t *mp_reg_config;
+	nt_field_t *mp_fld_max_tlp;
+	nt_field_t *mp_fld_max_read;
+	nt_field_t *mp_fld_ext_tag;
+
+	int mn_fpga_id_item;
+	int mn_fpga_id_prod;
+	int mn_fpga_id_ver;
+	int mn_fpga_id_rev;
+	int mn_fpga_id_build_no;
+
+	int mn_fpga_param_hif_per_ps;
+	uint32_t mn_fpga_hif_ref_clk_freq;
+};
+
+typedef struct nthw_hif nthw_hif_t;
+typedef struct nthw_hif nthw_hif;
+
+struct nthw_hif_end_point_err_counters {
+	uint32_t n_err_correctable, n_err_non_fatal, n_err_fatal;
+};
+
+struct nthw_hif_end_point_counters {
+	int n_numa_node;
+
+	int n_tg_direction;
+	int n_tg_pkt_size;
+	int n_tg_num_pkts;
+	int n_tg_delay;
+
+	uint64_t cur_rx, cur_tx;
+	uint64_t cur_pci_nt_util, cur_pci_xil_util;
+	uint64_t n_ref_clk_cnt;
+
+	uint64_t n_tags_in_use;
+	uint64_t n_rd_err;
+	uint64_t n_wr_err;
+
+	struct nthw_hif_end_point_err_counters s_rc_ep_pre, s_rc_ep_post, s_rc_ep_delta;
+	struct nthw_hif_end_point_err_counters s_ep_rc_pre, s_ep_rc_post, s_ep_rc_delta;
+
+	int bo_error;
+};
+
+struct nthw_hif_end_points {
+	struct nthw_hif_end_point_counters pri, sla;
+};
+
+nthw_hif_t *nthw_hif_new(void);
+void nthw_hif_delete(nthw_hif_t *p);
+int nthw_hif_init(nthw_hif_t *p, nt_fpga_t *p_fpga, int n_instance);
+
+int nthw_hif_setup_config(nthw_hif_t *p);
+
+int nthw_hif_trigger_sample_time(nthw_hif_t *p);
+
+int nthw_hif_stat_req_enable(nthw_hif_t *p);
+int nthw_hif_stat_req_disable(nthw_hif_t *p);
+int nthw_hif_stat_sample(nthw_hif_t *p, uint64_t *p_rx_rate, uint64_t *p_tx_rate,
+		       uint64_t *p_ref_clk_cnt, uint64_t *p_tags_in_use,
+		       uint64_t *p_rd_err_cnt, uint64_t *p_wr_err_cnt);
+
+int nthw_hif_get_stat(nthw_hif_t *p, uint32_t *p_rx_cnt, uint32_t *p_tx_cnt,
+		    uint32_t *p_ref_clk_cnt, uint32_t *p_tg_unit_size,
+		    uint32_t *p_tg_ref_freq, uint64_t *p_tags_in_use,
+		    uint64_t *p_rd_err, uint64_t *p_wr_err);
+int nthw_hif_get_stat_rate(nthw_hif_t *p, uint64_t *p_pci_rx_rate,
+			uint64_t *p_pci_tx_rate, uint64_t *p_ref_clk_cnt,
+			uint64_t *p_tags_in_use, uint64_t *p_rd_err_cnt,
+			uint64_t *p_wr_err_cnt);
+
+int nthw_hif_end_point_counters_sample(nthw_hif_t *p,
+				   struct nthw_hif_end_point_counters *epc);
+
+#endif /* __NTHW_HIF_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_iic.c b/drivers/net/ntnic/nthw/core/nthw_iic.c
new file mode 100644
index 0000000000..14aee221ce
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_iic.c
@@ -0,0 +1,570 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "nt_util.h"
+#include "ntlog.h"
+
+#include "nthw_drv.h"
+#include "nthw_register.h"
+
+#include "nthw_iic.h"
+
+#define I2C_TRANSMIT_WR (0x00)
+#define I2C_TRANSMIT_RD (0x01)
+
+#define I2C_WAIT_US(x) NT_OS_WAIT_USEC(x)
+
+/*
+ * Minimum timing values for I2C for a Marvel 88E11111 Phy.
+ * This Phy is used in many Trispeed NIMs.
+ * In order to access this Phy, the I2C clock speed is needed to be set to 100KHz.
+ */
+static const uint32_t susta = 4700; /* ns */
+static const uint32_t susto = 4000; /* ns */
+static const uint32_t hdsta = 4000; /* ns */
+static const uint32_t sudat = 250; /* ns */
+static const uint32_t buf = 4700; /* ns */
+static const uint32_t high = 4000; /* ns */
+static const uint32_t low = 4700; /* ns */
+static const uint32_t hddat = 300; /* ns */
+
+static int nthw_iic_reg_control_txfifo_reset(nthw_iic_t *p)
+{
+	field_update_register(p->mp_fld_cr_txfifo_reset);
+
+	field_set_all(p->mp_fld_cr_txfifo_reset);
+	field_flush_register(p->mp_fld_cr_txfifo_reset);
+
+	field_clr_all(p->mp_fld_cr_txfifo_reset);
+	field_flush_register(p->mp_fld_cr_txfifo_reset);
+
+	return 0;
+}
+
+static int nthw_iic_reg_tx_fifo_write(nthw_iic_t *p, uint32_t data, bool start,
+				     bool stop)
+{
+	if (start)
+		field_set_all(p->mp_fld_tx_fifo_start);
+
+	else
+		field_clr_all(p->mp_fld_tx_fifo_start);
+
+	if (stop)
+		field_set_all(p->mp_fld_tx_fifo_stop);
+
+	else
+		field_clr_all(p->mp_fld_tx_fifo_stop);
+
+	field_set_val32(p->mp_fld_tx_fifo_txdata, data);
+
+	register_flush(p->mp_reg_tx_fifo, 1);
+
+	return 0;
+}
+
+static int nthw_iic_reg_read_i2c_rx_fifo(nthw_iic_t *p, uint8_t *p_data)
+{
+	assert(p_data);
+
+	*p_data = (uint8_t)field_get_updated(p->mp_fld_rx_fifo_rxdata);
+
+	return 0;
+}
+
+static int nthw_iic_reg_softr(nthw_iic_t *p)
+{
+	field_update_register(p->mp_fld_cr_en);
+	field_set_val_flush32(p->mp_fld_softr_rkey, 0x0A);
+
+	return 0;
+}
+
+static int nthw_iic_reg_enable(nthw_iic_t *p)
+{
+	field_update_register(p->mp_fld_cr_en);
+	field_set_flush(p->mp_fld_cr_en);
+
+	return 0;
+}
+
+static int nthw_iic_reg_busbusy(nthw_iic_t *p, bool *pb_flag)
+{
+	assert(pb_flag);
+
+	*pb_flag = field_get_updated(p->mp_fld_sr_bb) ? true : false;
+
+	return 0;
+}
+
+static int nthw_iic_reg_rxfifo_empty(nthw_iic_t *p, bool *pb_flag)
+{
+	assert(pb_flag);
+
+	*pb_flag = field_get_updated(p->mp_fld_sr_rxfifo_empty) ? true : false;
+
+	return 0;
+}
+
+/*
+ * nIicCycleTime is the I2C clock cycle time in ns ie 125MHz = 8ns
+ */
+static int nthw_iic_reg_set_timing(nthw_iic_t *p, uint32_t n_iic_cycle_time)
+{
+	uint32_t val;
+
+	val = susta / n_iic_cycle_time;
+	field_set_val_flush(p->mp_fld_tsusta, &val, 1);
+
+	val = susto / n_iic_cycle_time;
+	field_set_val_flush(p->mp_fld_tsusto, &val, 1);
+
+	val = hdsta / n_iic_cycle_time;
+	field_set_val_flush(p->mp_fld_thdsta, &val, 1);
+
+	val = sudat / n_iic_cycle_time;
+	field_set_val_flush(p->mp_fld_tsudat, &val, 1);
+
+	val = buf / n_iic_cycle_time;
+	field_set_val_flush(p->mp_fld_tbuf, &val, 1);
+
+	val = high / n_iic_cycle_time;
+	field_set_val_flush(p->mp_fld_thigh, &val, 1);
+
+	val = low / n_iic_cycle_time;
+	field_set_val_flush(p->mp_fld_tlow, &val, 1);
+
+	val = hddat / n_iic_cycle_time;
+	field_set_val_flush(p->mp_fld_thddat, &val, 1);
+
+	return 0;
+}
+
+nthw_iic_t *nthw_iic_new(void)
+{
+	nthw_iic_t *p = malloc(sizeof(nthw_iic_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_iic_t));
+	return p;
+}
+
+int nthw_iic_init(nthw_iic_t *p, nt_fpga_t *p_fpga, int n_iic_instance,
+		 uint32_t n_iic_cycle_time)
+{
+	const char *const p_adapter_id_str = p_fpga->p_fpga_info->mp_adapter_id_str;
+	nt_module_t *mod = fpga_query_module(p_fpga, MOD_IIC, n_iic_instance);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: I2C %d: no such instance\n",
+		       p_adapter_id_str, n_iic_instance);
+		return -1;
+	}
+
+	p->mp_fpga = p_fpga;
+	p->mn_iic_instance = n_iic_instance;
+
+	p->mn_iic_cycle_time = n_iic_cycle_time;
+
+	nthw_iic_set_retry_params(p, -1, -1, -1, -1, -1);
+
+	p->mp_mod_iic = mod;
+
+	/* I2C is a primary communication channel - turn off debug by default */
+	module_set_debug_mode(p->mp_mod_iic, 0x00);
+
+	p->mp_reg_tsusta = module_get_register(p->mp_mod_iic, IIC_TSUSTA);
+	p->mp_fld_tsusta =
+		register_get_field(p->mp_reg_tsusta, IIC_TSUSTA_TSUSTA_VAL);
+
+	p->mp_reg_tsusto = module_get_register(p->mp_mod_iic, IIC_TSUSTO);
+	p->mp_fld_tsusto =
+		register_get_field(p->mp_reg_tsusto, IIC_TSUSTO_TSUSTO_VAL);
+
+	p->mp_reg_thdsta = module_get_register(p->mp_mod_iic, IIC_THDSTA);
+	p->mp_fld_thdsta =
+		register_get_field(p->mp_reg_thdsta, IIC_THDSTA_THDSTA_VAL);
+
+	p->mp_reg_tsudat = module_get_register(p->mp_mod_iic, IIC_TSUDAT);
+	p->mp_fld_tsudat =
+		register_get_field(p->mp_reg_tsudat, IIC_TSUDAT_TSUDAT_VAL);
+
+	p->mp_reg_tbuf = module_get_register(p->mp_mod_iic, IIC_TBUF);
+	p->mp_fld_tbuf = register_get_field(p->mp_reg_tbuf, IIC_TBUF_TBUF_VAL);
+
+	p->mp_reg_thigh = module_get_register(p->mp_mod_iic, IIC_THIGH);
+	p->mp_fld_thigh = register_get_field(p->mp_reg_thigh, IIC_THIGH_THIGH_VAL);
+
+	p->mp_reg_tlow = module_get_register(p->mp_mod_iic, IIC_TLOW);
+	p->mp_fld_tlow = register_get_field(p->mp_reg_tlow, IIC_TLOW_TLOW_VAL);
+
+	p->mp_reg_thddat = module_get_register(p->mp_mod_iic, IIC_THDDAT);
+	p->mp_fld_thddat =
+		register_get_field(p->mp_reg_thddat, IIC_THDDAT_THDDAT_VAL);
+
+	p->mp_reg_cr = module_get_register(p->mp_mod_iic, IIC_CR);
+	p->mp_fld_cr_en = register_get_field(p->mp_reg_cr, IIC_CR_EN);
+	p->mp_fld_cr_msms = register_get_field(p->mp_reg_cr, IIC_CR_MSMS);
+	p->mp_fld_cr_txfifo_reset =
+		register_get_field(p->mp_reg_cr, IIC_CR_TXFIFO_RESET);
+	p->mp_fld_cr_txak = register_get_field(p->mp_reg_cr, IIC_CR_TXAK);
+
+	p->mp_reg_sr = module_get_register(p->mp_mod_iic, IIC_SR);
+	p->mp_fld_sr_bb = register_get_field(p->mp_reg_sr, IIC_SR_BB);
+	p->mp_fld_sr_rxfifo_full =
+		register_get_field(p->mp_reg_sr, IIC_SR_RXFIFO_FULL);
+	p->mp_fld_sr_rxfifo_empty =
+		register_get_field(p->mp_reg_sr, IIC_SR_RXFIFO_EMPTY);
+	p->mp_fld_sr_txfifo_full =
+		register_get_field(p->mp_reg_sr, IIC_SR_TXFIFO_FULL);
+	p->mp_fld_sr_txfifo_empty =
+		register_get_field(p->mp_reg_sr, IIC_SR_TXFIFO_EMPTY);
+
+	p->mp_reg_tx_fifo = module_get_register(p->mp_mod_iic, IIC_TX_FIFO);
+	p->mp_fld_tx_fifo_txdata =
+		register_get_field(p->mp_reg_tx_fifo, IIC_TX_FIFO_TXDATA);
+	p->mp_fld_tx_fifo_start =
+		register_get_field(p->mp_reg_tx_fifo, IIC_TX_FIFO_START);
+	p->mp_fld_tx_fifo_stop =
+		register_get_field(p->mp_reg_tx_fifo, IIC_TX_FIFO_STOP);
+
+	p->mp_reg_rx_fifo_pirq =
+		module_get_register(p->mp_mod_iic, IIC_RX_FIFO_PIRQ);
+	p->mp_fld_rx_fifo_pirq_cmp_val =
+		register_get_field(p->mp_reg_rx_fifo_pirq, IIC_RX_FIFO_PIRQ_CMP_VAL);
+
+	p->mp_reg_rx_fifo = module_get_register(p->mp_mod_iic, IIC_RX_FIFO);
+	p->mp_fld_rx_fifo_rxdata =
+		register_get_field(p->mp_reg_rx_fifo, IIC_RX_FIFO_RXDATA);
+
+	p->mp_reg_softr = module_get_register(p->mp_mod_iic, IIC_SOFTR);
+	p->mp_fld_softr_rkey = register_get_field(p->mp_reg_softr, IIC_SOFTR_RKEY);
+
+	/*
+	 * Initialize I2C controller by applying soft reset and enable the controller
+	 */
+	nthw_iic_reg_softr(p);
+	/* Enable the controller */
+	nthw_iic_reg_enable(p);
+
+	/* Setup controller timing */
+	if (p->mn_iic_cycle_time) {
+		NT_LOG(DBG, NTHW, "%s: I2C%d: cycletime=%d\n", p_adapter_id_str,
+		       p->mn_iic_instance, p->mn_iic_cycle_time);
+		nthw_iic_reg_set_timing(p, p->mn_iic_cycle_time);
+	}
+
+	/* Reset TX fifo - must be after enable */
+	nthw_iic_reg_control_txfifo_reset(p);
+	nthw_iic_reg_tx_fifo_write(p, 0, 0, 0);
+
+	return 0;
+}
+
+void nthw_iic_delete(nthw_iic_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_iic_t));
+		free(p);
+	}
+}
+
+int nthw_iic_set_retry_params(nthw_iic_t *p, const int n_poll_delay,
+			   const int n_bus_ready_retry, const int n_data_ready_retry,
+			   const int n_read_data_retry, const int n_write_data_retry)
+{
+	p->mn_poll_delay = n_poll_delay >= 0 ? n_poll_delay : 10;
+
+	p->mn_bus_ready_retry = n_bus_ready_retry >= 0 ? n_bus_ready_retry : 1000;
+	p->mn_data_ready_retry = n_data_ready_retry >= 0 ? n_data_ready_retry : 1000;
+
+	p->mn_read_data_retry = n_read_data_retry >= 0 ? n_read_data_retry : 10;
+	p->mn_write_data_retry = n_write_data_retry >= 0 ? n_write_data_retry : 10;
+
+	return 0;
+}
+
+int nthw_iic_read_data(nthw_iic_t *p, uint8_t dev_addr, uint8_t reg_addr,
+		     uint8_t data_len, void *p_void)
+{
+	const char *const p_adapter_id_str = p->mp_fpga->p_fpga_info->mp_adapter_id_str;
+	const int n_debug_mode = module_get_debug_mode(p->mp_mod_iic);
+
+	uint8_t *pb = (uint8_t *)p_void;
+	int retry = (p->mn_read_data_retry >= 0 ? p->mn_read_data_retry : 10);
+
+	if (n_debug_mode == 0xff) {
+		NT_LOG(DBG, NTHW, "%s: adr=0x%2.2x, reg=%d, len=%d\n",
+		       p_adapter_id_str, dev_addr, reg_addr, data_len);
+	}
+
+	while (nthw_iic_readbyte(p, dev_addr, reg_addr, data_len, pb) != 0) {
+		retry--;
+		if (retry <= 0) {
+			NT_LOG(ERR, NTHW,
+			       "%s: I2C%d: Read retry exhausted (dev_addr=%d reg_addr=%d)\n",
+			       p_adapter_id_str, p->mn_iic_instance, dev_addr,
+			       reg_addr);
+			return -1;
+		}
+#if defined(DEBUG)
+		NT_LOG(DBG, NTHW,
+		       "%s: I2C%d: Read retry=%d (dev_addr=%d reg_addr=%d)\n",
+		       p_adapter_id_str, p->mn_iic_instance, retry, dev_addr,
+		       reg_addr);
+#endif
+	}
+
+	if (n_debug_mode == 0xff) {
+		NT_LOG(DBG, NTHW,
+		       "%s: adr=0x%2.2x, reg=%d, len=%d, retries remaining: %d\n",
+		       p_adapter_id_str, dev_addr, reg_addr, data_len, retry);
+	}
+
+	return 0;
+}
+
+int nthw_iic_readbyte(nthw_iic_t *p, uint8_t dev_addr, uint8_t reg_addr,
+		     uint8_t data_len, uint8_t *p_byte)
+{
+	const char *const p_adapter_id_str = p->mp_fpga->p_fpga_info->mp_adapter_id_str;
+
+	uint32_t value;
+	uint32_t i;
+
+	if (nthw_iic_bus_ready(p)) {
+		/* Reset TX fifo */
+		nthw_iic_reg_control_txfifo_reset(p);
+
+		/* Write device address to TX_FIFO and set start bit!! */
+		value = (dev_addr << 1) | I2C_TRANSMIT_WR;
+		nthw_iic_reg_tx_fifo_write(p, value, 1, 0);
+
+		/* Write reg_addr to TX FIFO */
+		nthw_iic_reg_tx_fifo_write(p, reg_addr, 0, 1);
+
+		if (!nthw_iic_bus_ready(p)) {
+			NT_LOG(ERR, NTHW, "%s: error: (%s:%u)\n", p_adapter_id_str,
+			       __func__, __LINE__);
+			return -1;
+		}
+
+		/* Write device address + RD bit to TX_FIFO and set start bit!! */
+		value = (dev_addr << 1) | I2C_TRANSMIT_RD;
+		nthw_iic_reg_tx_fifo_write(p, value, 1, 0);
+
+		/* Write DataLen to TX_FIFO and set stop bit!! */
+		nthw_iic_reg_tx_fifo_write(p, data_len, 0, 1);
+
+		for (i = 0; i < data_len; i++) {
+			/* Wait for RX FIFO not empty */
+			if (!nthw_iic_data_ready(p))
+				return -1;
+
+			/* Read DataLen bytes from RX_FIFO */
+			nthw_iic_reg_read_i2c_rx_fifo(p, p_byte);
+			p_byte++;
+		}
+
+		return 0;
+
+	} else {
+		NT_LOG(ERR, NTHW, "%s: error: (%s:%u)\n", p_adapter_id_str,
+		       __func__, __LINE__);
+		return -1;
+	}
+	return 0;
+}
+
+int nthw_iic_write_data(nthw_iic_t *p, uint8_t dev_addr, uint8_t reg_addr,
+		      uint8_t data_len, void *p_void)
+{
+	const char *const p_adapter_id_str = p->mp_fpga->p_fpga_info->mp_adapter_id_str;
+	int retry = (p->mn_write_data_retry >= 0 ? p->mn_write_data_retry : 10);
+	uint8_t *pb = (uint8_t *)p_void;
+
+	while (nthw_iic_writebyte(p, dev_addr, reg_addr, data_len, pb) != 0) {
+		retry--;
+		if (retry <= 0) {
+			NT_LOG(ERR, NTHW,
+			       "%s: I2C%d: Write retry exhausted (dev_addr=%d reg_addr=%d)\n",
+			       p_adapter_id_str, p->mn_iic_instance, dev_addr,
+			       reg_addr);
+			return -1;
+		}
+#if defined(DEBUG)
+		NT_LOG(DBG, NTHW,
+		       "%s: I2C%d: Write retry=%d (dev_addr=%d reg_addr=%d)\n",
+		       p_adapter_id_str, p->mn_iic_instance, retry, dev_addr,
+		       reg_addr);
+#endif
+	}
+
+	return 0;
+}
+
+int nthw_iic_writebyte(nthw_iic_t *p, uint8_t dev_addr, uint8_t reg_addr,
+		      uint8_t data_len, uint8_t *p_byte)
+{
+	const char *const p_adapter_id_str = p->mp_fpga->p_fpga_info->mp_adapter_id_str;
+	uint32_t value;
+	int count;
+	int i;
+
+	if (data_len == 0)
+		return -1;
+
+	count = data_len - 1;
+	if (nthw_iic_bus_ready(p)) {
+		/* Reset TX fifo */
+		nthw_iic_reg_control_txfifo_reset(p);
+
+		/* Write device address to TX_FIFO and set start bit!! */
+		value = (dev_addr << 1) | I2C_TRANSMIT_WR;
+		nthw_iic_reg_tx_fifo_write(p, value, 1, 0);
+
+		/* Write reg_addr to TX FIFO */
+		nthw_iic_reg_tx_fifo_write(p, reg_addr, 0, 0);
+
+		for (i = 0; i < count; i++) {
+			/* Write data byte to TX fifo and set stop bit */
+			nthw_iic_reg_tx_fifo_write(p, *p_byte, 0, 0);
+			p_byte++;
+		}
+
+		/* Write data byte to TX fifo and set stop bit */
+		nthw_iic_reg_tx_fifo_write(p, *p_byte, 0, 1);
+
+		if (!nthw_iic_bus_ready(p)) {
+			NT_LOG(WRN, NTHW, "%s: warn: !busReady (%s:%u)\n",
+			       p_adapter_id_str, __func__, __LINE__);
+			while (true) {
+				if (nthw_iic_bus_ready(p)) {
+					NT_LOG(DBG, NTHW,
+					       "%s: info: busReady (%s:%u)\n",
+					       p_adapter_id_str, __func__,
+					       __LINE__);
+					break;
+				}
+			}
+		}
+
+		return 0;
+
+	} else {
+		NT_LOG(WRN, NTHW, "%s: (%s:%u)\n", p_adapter_id_str, __func__,
+		       __LINE__);
+		return -1;
+	}
+}
+
+/*
+ * Support function for read/write functions below. Waits for bus ready.
+ */
+bool nthw_iic_bus_ready(nthw_iic_t *p)
+{
+	int count = (p->mn_bus_ready_retry >= 0 ? p->mn_bus_ready_retry : 1000);
+	bool b_bus_busy = true;
+
+	while (true) {
+		nthw_iic_reg_busbusy(p, &b_bus_busy);
+		if (!b_bus_busy)
+			break;
+
+		count--;
+		if (count <= 0)   /* Test for timeout */
+			break;
+
+		if (p->mn_poll_delay != 0)
+			I2C_WAIT_US(p->mn_poll_delay);
+	}
+
+	if (count == 0)
+		return false;
+
+	return true;
+}
+
+/*
+ * Support function for read function. Waits for data ready.
+ */
+bool nthw_iic_data_ready(nthw_iic_t *p)
+{
+	int count = (p->mn_data_ready_retry >= 0 ? p->mn_data_ready_retry : 1000);
+	bool b_rx_fifo_empty = true;
+
+	while (true) {
+		nthw_iic_reg_rxfifo_empty(p, &b_rx_fifo_empty);
+		if (!b_rx_fifo_empty)
+			break;
+
+		count--;
+		if (count <= 0)   /* Test for timeout */
+			break;
+
+		if (p->mn_poll_delay != 0)
+			I2C_WAIT_US(p->mn_poll_delay);
+	}
+
+	if (count == 0)
+		return false;
+
+	return true;
+}
+
+int nthw_iic_scan_dev_addr(nthw_iic_t *p, int n_dev_addr, int n_reg_addr)
+{
+	const char *const p_adapter_id_str _unused =
+		p->mp_fpga->p_fpga_info->mp_adapter_id_str;
+	int res;
+	uint8_t data_val = -1;
+
+	res = nthw_iic_readbyte(p, (uint8_t)n_dev_addr, (uint8_t)n_reg_addr, 1,
+			       &data_val);
+	if (res == 0) {
+		NT_LOG(DBG, NTHW,
+		       "%s: I2C%d: devaddr=0x%02X (%03d) regaddr=%02X val=%02X (%03d) res=%d\n",
+		       p_adapter_id_str, p->mn_iic_instance, n_dev_addr, n_dev_addr,
+		       n_reg_addr, data_val, data_val, res);
+	}
+	return res;
+}
+
+int nthw_iic_scan_find_dev(nthw_iic_t *p, int n_dev_addr_start, bool b_increate)
+{
+	const char *const p_adapter_id_str _unused =
+		p->mp_fpga->p_fpga_info->mp_adapter_id_str;
+
+	int res = 0;
+	int i = 0;
+
+	if (b_increate) {
+		for (i = n_dev_addr_start; i < 128; i++) {
+			res = nthw_iic_scan_dev_addr(p, i, 0x00);
+			if (res == 0)
+				break;
+		}
+	} else {
+		for (i = n_dev_addr_start; i >= 0; i--) {
+			res = nthw_iic_scan_dev_addr(p, i, 0x00);
+			if (res == 0)
+				break;
+		}
+	}
+	NT_LOG(DBG, NTHW, "%s: I2C%d: FOUND: %d\n", p_adapter_id_str,
+	       p->mn_iic_instance, i);
+	return (res == 0 ? i : -1);
+}
+
+int nthw_iic_scan(nthw_iic_t *p)
+{
+	int i;
+
+	for (i = 0; i < 128; i++)
+		(void)nthw_iic_scan_dev_addr(p, i, 0x00);
+	return 0;
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_iic.h b/drivers/net/ntnic/nthw/core/nthw_iic.h
new file mode 100644
index 0000000000..e3bd313c88
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_iic.h
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_IIC_H__
+#define __NTHW_IIC_H__
+
+#include "nthw_fpga_model.h"
+
+struct nthw_iic {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_iic;
+	int mn_iic_instance;
+
+	uint32_t mn_iic_cycle_time;
+	int mn_poll_delay;
+	int mn_bus_ready_retry;
+	int mn_data_ready_retry;
+	int mn_read_data_retry;
+	int mn_write_data_retry;
+
+	nt_register_t *mp_reg_tsusta;
+	nt_field_t *mp_fld_tsusta;
+
+	nt_register_t *mp_reg_tsusto;
+	nt_field_t *mp_fld_tsusto;
+
+	nt_register_t *mp_reg_thdsta;
+	nt_field_t *mp_fld_thdsta;
+
+	nt_register_t *mp_reg_tsudat;
+	nt_field_t *mp_fld_tsudat;
+
+	nt_register_t *mp_reg_tbuf;
+	nt_field_t *mp_fld_tbuf;
+
+	nt_register_t *mp_reg_thigh;
+	nt_field_t *mp_fld_thigh;
+
+	nt_register_t *mp_reg_tlow;
+	nt_field_t *mp_fld_tlow;
+
+	nt_register_t *mp_reg_thddat;
+	nt_field_t *mp_fld_thddat;
+
+	nt_register_t *mp_reg_cr;
+	nt_field_t *mp_fld_cr_en;
+	nt_field_t *mp_fld_cr_msms;
+	nt_field_t *mp_fld_cr_txfifo_reset;
+	nt_field_t *mp_fld_cr_txak;
+
+	nt_register_t *mp_reg_sr;
+	nt_field_t *mp_fld_sr_bb;
+	nt_field_t *mp_fld_sr_rxfifo_full;
+	nt_field_t *mp_fld_sr_rxfifo_empty;
+	nt_field_t *mp_fld_sr_txfifo_full;
+	nt_field_t *mp_fld_sr_txfifo_empty;
+
+	nt_register_t *mp_reg_tx_fifo;
+	nt_field_t *mp_fld_tx_fifo_txdata;
+	nt_field_t *mp_fld_tx_fifo_start;
+	nt_field_t *mp_fld_tx_fifo_stop;
+
+	nt_register_t *mp_reg_rx_fifo_pirq;
+	nt_field_t *mp_fld_rx_fifo_pirq_cmp_val;
+
+	nt_register_t *mp_reg_rx_fifo;
+	nt_field_t *mp_fld_rx_fifo_rxdata;
+
+	nt_register_t *mp_reg_softr;
+	nt_field_t *mp_fld_softr_rkey;
+};
+
+typedef struct nthw_iic nthw_iic_t;
+typedef struct nthw_iic nthw_iic;
+
+nthw_iic_t *nthw_iic_new(void);
+int nthw_iic_init(nthw_iic_t *p, nt_fpga_t *p_fpga, int n_iic_instance,
+		 uint32_t n_iic_cycle_time);
+void nthw_iic_delete(nthw_iic_t *p);
+
+int nthw_iic_set_retry_params(nthw_iic_t *p, const int n_poll_delay,
+			   const int n_bus_ready_retry, const int n_data_ready_retry,
+			   const int n_read_data_retry, const int n_write_data_retry);
+
+int nthw_iic_read_data(nthw_iic_t *p, uint8_t dev_addr, uint8_t reg_addr,
+		     uint8_t data_len, void *p_void);
+int nthw_iic_readbyte(nthw_iic_t *p, uint8_t dev_addr, uint8_t reg_addr,
+		     uint8_t data_len, uint8_t *p_byte);
+int nthw_iic_write_data(nthw_iic_t *p, uint8_t dev_addr, uint8_t reg_addr,
+		      uint8_t data_len, void *p_void);
+int nthw_iic_writebyte(nthw_iic_t *p, uint8_t dev_addr, uint8_t reg_addr,
+		      uint8_t data_len, uint8_t *p_byte);
+bool nthw_iic_bus_ready(nthw_iic_t *p);
+bool nthw_iic_data_ready(nthw_iic_t *p);
+
+int nthw_iic_scan(nthw_iic_t *p);
+int nthw_iic_scan_dev_addr(nthw_iic_t *p, int n_dev_addr, int n_reg_addr);
+int nthw_iic_scan_find_dev(nthw_iic_t *p, int n_dev_addr_start, bool b_increate);
+
+#endif /* __NTHW_IIC_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_mac_pcs.c b/drivers/net/ntnic/nthw/core/nthw_mac_pcs.c
new file mode 100644
index 0000000000..d206c374b4
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_mac_pcs.c
@@ -0,0 +1,1034 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "nt_util.h"
+#include "ntlog.h"
+
+#include "nthw_drv.h"
+#include "nthw_register.h"
+
+#include "nthw_mac_pcs.h"
+
+#define NTHW_MAC_PCS_LANES (20)
+
+static const uint8_t c_pcs_lanes = NTHW_MAC_PCS_LANES;
+static const uint8_t c_mac_pcs_receiver_mode_dfe _unused;
+
+nthw_mac_pcs_t *nthw_mac_pcs_new(void)
+{
+	nthw_mac_pcs_t *p = malloc(sizeof(nthw_mac_pcs_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_mac_pcs_t));
+	return p;
+}
+
+void nthw_mac_pcs_delete(nthw_mac_pcs_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_mac_pcs_t));
+		free(p);
+	}
+}
+
+/*
+ * Parameters:
+ *   p != NULL: init struct pointed to by p
+ *   p == NULL: check fpga module(s) presence (but no struct to init)
+ *
+ * Return value:
+ *  <0: if p == NULL then fpga module(s) is/are not present.
+ *      if p != NULL then fpga module(s) is/are not present, struct undefined
+ * ==0: if p == NULL then fpga module(s) is/are are present (no struct to init)
+ *    : if p != NULL then fpga module(s) is/are present and struct initialized
+ */
+int nthw_mac_pcs_init(nthw_mac_pcs_t *p, nt_fpga_t *p_fpga, int n_instance)
+{
+	nt_module_t *mod = fpga_query_module(p_fpga, MOD_MAC_PCS, n_instance);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: MAC_PCS %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_mac_pcs = mod;
+
+	assert(n_instance >= 0 && n_instance <= 255);
+	nthw_mac_pcs_set_port_no(p, (uint8_t)n_instance);
+
+	{
+		nt_register_t *p_reg_block_lock, *p_reg_stat_pcs_rx,
+			      *p_reg_stat_pcs_rx_latch;
+		nt_register_t *p_reg_vl_demuxed, *p_reg_gty_stat, *p_reg_pcs_config,
+			      *p_reg_phymac_misc;
+		const int product_id = fpga_get_product_id(p_fpga);
+
+		p_reg_block_lock =
+			module_get_register(p->mp_mod_mac_pcs, MAC_PCS_BLOCK_LOCK);
+		p->mp_reg_block_lock = p_reg_block_lock;
+		p->mp_fld_block_lock_lock =
+			register_get_field(p_reg_block_lock, MAC_PCS_BLOCK_LOCK_LOCK);
+
+		p_reg_stat_pcs_rx =
+			module_get_register(p->mp_mod_mac_pcs, MAC_PCS_STAT_PCS_RX);
+		p->mp_reg_stat_pcs_rx = p_reg_stat_pcs_rx;
+		p->mp_fld_stat_pcs_rx_status =
+			register_get_field(p_reg_stat_pcs_rx, MAC_PCS_STAT_PCS_RX_STATUS);
+		p->mp_fld_stat_pcs_rx_aligned =
+			register_get_field(p_reg_stat_pcs_rx, MAC_PCS_STAT_PCS_RX_ALIGNED);
+		p->mp_fld_stat_pcs_rx_aligned_err =
+			register_get_field(p_reg_stat_pcs_rx, MAC_PCS_STAT_PCS_RX_ALIGNED_ERR);
+		p->mp_fld_stat_pcs_rx_misaligned =
+			register_get_field(p_reg_stat_pcs_rx, MAC_PCS_STAT_PCS_RX_MISALIGNED);
+		p->mp_fld_stat_pcs_rx_internal_local_fault =
+			register_get_field(p_reg_stat_pcs_rx,
+					   MAC_PCS_STAT_PCS_RX_INTERNAL_LOCAL_FAULT);
+		p->mp_fld_stat_pcs_rx_received_local_fault =
+			register_get_field(p_reg_stat_pcs_rx,
+					   MAC_PCS_STAT_PCS_RX_RECEIVED_LOCAL_FAULT);
+		p->mp_fld_stat_pcs_rx_local_fault =
+			register_get_field(p_reg_stat_pcs_rx,
+					   MAC_PCS_STAT_PCS_RX_LOCAL_FAULT);
+		p->mp_fld_stat_pcs_rx_remote_fault =
+			register_get_field(p_reg_stat_pcs_rx,
+					   MAC_PCS_STAT_PCS_RX_REMOTE_FAULT);
+		p->mp_fld_stat_pcs_rx_hi_ber =
+			register_get_field(p_reg_stat_pcs_rx,
+					   MAC_PCS_STAT_PCS_RX_HI_BER);
+
+		p_reg_stat_pcs_rx_latch =
+			module_get_register(p->mp_mod_mac_pcs,
+					    MAC_PCS_STAT_PCS_RX_LATCH);
+		p->mp_reg_stat_pcs_rx_latch = p_reg_stat_pcs_rx_latch;
+		p->mp_fld_stat_pcs_rx_latch_status =
+			register_get_field(p_reg_stat_pcs_rx_latch,
+					   MAC_PCS_STAT_PCS_RX_LATCH_STATUS);
+
+		p_reg_vl_demuxed =
+			module_get_register(p->mp_mod_mac_pcs, MAC_PCS_VL_DEMUXED);
+		p->mp_fld_vl_demuxed_lock =
+			register_get_field(p_reg_vl_demuxed, MAC_PCS_VL_DEMUXED_LOCK);
+
+		p_reg_gty_stat =
+			module_get_register(p->mp_mod_mac_pcs, MAC_PCS_GTY_STAT);
+		p->mp_fld_gty_stat_tx_rst_done0 =
+			register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_TX_RST_DONE_0);
+		p->mp_fld_gty_stat_tx_rst_done1 =
+			register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_TX_RST_DONE_1);
+		p->mp_fld_gty_stat_tx_rst_done2 =
+			register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_TX_RST_DONE_2);
+		p->mp_fld_gty_stat_tx_rst_done3 =
+			register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_TX_RST_DONE_3);
+		p->mp_fld_gty_stat_rx_rst_done0 =
+			register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_RX_RST_DONE_0);
+		p->mp_fld_gty_stat_rx_rst_done1 =
+			register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_RX_RST_DONE_1);
+		p->mp_fld_gty_stat_rx_rst_done2 =
+			register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_RX_RST_DONE_2);
+		p->mp_fld_gty_stat_rx_rst_done3 =
+			register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_RX_RST_DONE_3);
+
+		p->m_fld_block_lock_lock_mask = 0;
+		p->m_fld_vl_demuxed_lock_mask = 0;
+		p->m_fld_gty_stat_tx_rst_done_mask = 0;
+		p->m_fld_gty_stat_rx_rst_done_mask = 0;
+
+		if (product_id == 9563) {
+			/* NT200A01_2X100 implements 20 virtual lanes */
+			p->m_fld_block_lock_lock_mask = (1 << 20) - 1;
+			/* NT200A01_2X100 implements 20 virtual lanes */
+			p->m_fld_vl_demuxed_lock_mask = (1 << 20) - 1;
+			p->m_fld_gty_stat_tx_rst_done_mask =
+				1; /* NT200A01_2X100 implements 4 GTY */
+			p->m_fld_gty_stat_rx_rst_done_mask =
+				1; /* NT200A01_2X100 implements 4 GTY */
+		} else {
+			/* Remember to add new productIds */
+			assert(0);
+		}
+
+		p_reg_pcs_config = module_get_register(p->mp_mod_mac_pcs,
+						   MAC_PCS_MAC_PCS_CONFIG);
+		p->mp_fld_pcs_config_tx_path_rst =
+			register_get_field(p_reg_pcs_config, MAC_PCS_MAC_PCS_CONFIG_TX_PATH_RST);
+		p->mp_fld_pcs_config_rx_path_rst =
+			register_get_field(p_reg_pcs_config, MAC_PCS_MAC_PCS_CONFIG_RX_PATH_RST);
+		p->mp_fld_pcs_config_rx_enable =
+			register_get_field(p_reg_pcs_config, MAC_PCS_MAC_PCS_CONFIG_RX_ENABLE);
+		p->mp_fld_pcs_config_rx_force_resync =
+			register_get_field(p_reg_pcs_config,
+					   MAC_PCS_MAC_PCS_CONFIG_RX_FORCE_RESYNC);
+		p->mp_fld_pcs_config_rx_test_pattern =
+			register_get_field(p_reg_pcs_config,
+					   MAC_PCS_MAC_PCS_CONFIG_RX_TEST_PATTERN);
+		p->mp_fld_pcs_config_tx_enable =
+			register_get_field(p_reg_pcs_config,
+					   MAC_PCS_MAC_PCS_CONFIG_TX_ENABLE);
+		p->mp_fld_pcs_config_tx_send_idle =
+			register_get_field(p_reg_pcs_config,
+					   MAC_PCS_MAC_PCS_CONFIG_TX_SEND_IDLE);
+		p->mp_fld_pcs_config_tx_send_rfi =
+			register_get_field(p_reg_pcs_config,
+					   MAC_PCS_MAC_PCS_CONFIG_TX_SEND_RFI);
+		p->mp_fld_pcs_config_tx_test_pattern =
+			register_get_field(p_reg_pcs_config,
+					   MAC_PCS_MAC_PCS_CONFIG_TX_TEST_PATTERN);
+
+		p->mp_reg_gty_loop =
+			module_get_register(p->mp_mod_mac_pcs, MAC_PCS_GTY_LOOP);
+		p->mp_fld_gty_loop_gt_loop0 =
+			register_get_field(p->mp_reg_gty_loop, MAC_PCS_GTY_LOOP_GT_LOOP_0);
+		p->mp_fld_gty_loop_gt_loop1 =
+			register_get_field(p->mp_reg_gty_loop, MAC_PCS_GTY_LOOP_GT_LOOP_1);
+		p->mp_fld_gty_loop_gt_loop2 =
+			register_get_field(p->mp_reg_gty_loop, MAC_PCS_GTY_LOOP_GT_LOOP_2);
+		p->mp_fld_gty_loop_gt_loop3 =
+			register_get_field(p->mp_reg_gty_loop, MAC_PCS_GTY_LOOP_GT_LOOP_3);
+
+		p_reg_phymac_misc =
+			module_get_register(p->mp_mod_mac_pcs, MAC_PCS_PHYMAC_MISC);
+		p->mp_reg_phymac_misc = p_reg_phymac_misc;
+		p->mp_fld_phymac_misc_tx_sel_host =
+			register_get_field(p_reg_phymac_misc, MAC_PCS_PHYMAC_MISC_TX_SEL_HOST);
+		p->mp_fld_phymac_misc_tx_sel_tfg =
+			register_get_field(p_reg_phymac_misc, MAC_PCS_PHYMAC_MISC_TX_SEL_TFG);
+		p->mp_fld_phymac_misc_tx_sel_rx_loop =
+			register_get_field(p_reg_phymac_misc, MAC_PCS_PHYMAC_MISC_TX_SEL_RX_LOOP);
+
+		/* SOP or EOP TIMESTAMP */
+		p->mp_fld_phymac_misc_ts_eop =
+			register_query_field(p_reg_phymac_misc, MAC_PCS_PHYMAC_MISC_TS_EOP);
+
+		p->mp_reg_link_summary =
+			module_get_register(p->mp_mod_mac_pcs, MAC_PCS_LINK_SUMMARY);
+		p->mp_fld_link_summary_abs =
+			register_get_field(p->mp_reg_link_summary, MAC_PCS_LINK_SUMMARY_ABS);
+		p->mp_fld_link_summary_nt_phy_link_state =
+			register_get_field(p->mp_reg_link_summary,
+					   MAC_PCS_LINK_SUMMARY_NT_PHY_LINK_STATE);
+		p->mp_fld_link_summary_lh_abs =
+			register_get_field(p->mp_reg_link_summary, MAC_PCS_LINK_SUMMARY_LH_ABS);
+		p->mp_fld_link_summary_ll_nt_phy_link_state =
+			register_get_field(p->mp_reg_link_summary,
+					   MAC_PCS_LINK_SUMMARY_LL_PHY_LINK_STATE);
+		p->mp_fld_link_summary_link_down_cnt =
+			register_get_field(p->mp_reg_link_summary,
+					   MAC_PCS_LINK_SUMMARY_LINK_DOWN_CNT);
+		p->mp_fld_link_summary_nim_interr =
+			register_get_field(p->mp_reg_link_summary,
+					   MAC_PCS_LINK_SUMMARY_NIM_INTERR);
+		p->mp_fld_link_summary_lh_local_fault =
+			register_get_field(p->mp_reg_link_summary,
+					   MAC_PCS_LINK_SUMMARY_LH_LOCAL_FAULT);
+		p->mp_fld_link_summary_lh_remote_fault =
+			register_get_field(p->mp_reg_link_summary,
+					   MAC_PCS_LINK_SUMMARY_LH_REMOTE_FAULT);
+		p->mp_fld_link_summary_local_fault =
+			register_get_field(p->mp_reg_link_summary,
+					   MAC_PCS_LINK_SUMMARY_LOCAL_FAULT);
+		p->mp_fld_link_summary_remote_fault =
+			register_get_field(p->mp_reg_link_summary,
+					   MAC_PCS_LINK_SUMMARY_REMOTE_FAULT);
+
+		p->mp_reg_bip_err =
+			module_get_register(p->mp_mod_mac_pcs, MAC_PCS_BIP_ERR);
+		p->mp_fld_reg_bip_err_bip_err =
+			register_get_field(p->mp_reg_bip_err, MAC_PCS_BIP_ERR_BIP_ERR);
+
+		p->mp_reg_fec_ctrl =
+			module_get_register(p->mp_mod_mac_pcs, MAC_PCS_FEC_CTRL);
+		p->mp_field_fec_ctrl_reg_rs_fec_ctrl_in =
+			register_get_field(p->mp_reg_fec_ctrl, MAC_PCS_FEC_CTRL_RS_FEC_CTRL_IN);
+
+		p->mp_reg_fec_stat =
+			module_get_register(p->mp_mod_mac_pcs, MAC_PCS_FEC_STAT);
+		p->mp_field_fec_stat_bypass =
+			register_get_field(p->mp_reg_fec_stat, MAC_PCS_FEC_STAT_BYPASS);
+		p->mp_field_fec_stat_valid =
+			register_get_field(p->mp_reg_fec_stat, MAC_PCS_FEC_STAT_VALID);
+		p->mp_field_fec_stat_am_lock0 =
+			register_get_field(p->mp_reg_fec_stat, MAC_PCS_FEC_STAT_AM_LOCK_0);
+		p->mp_field_fec_stat_am_lock1 =
+			register_get_field(p->mp_reg_fec_stat, MAC_PCS_FEC_STAT_AM_LOCK_1);
+		p->mp_field_fec_stat_am_lock2 =
+			register_get_field(p->mp_reg_fec_stat, MAC_PCS_FEC_STAT_AM_LOCK_2);
+		p->mp_field_fec_stat_am_lock3 =
+			register_get_field(p->mp_reg_fec_stat, MAC_PCS_FEC_STAT_AM_LOCK_3);
+		p->mp_field_fec_stat_fec_lane_algn =
+			register_get_field(p->mp_reg_fec_stat, MAC_PCS_FEC_STAT_FEC_LANE_ALGN);
+
+		p->mp_reg_fec_cw_cnt =
+			module_get_register(p->mp_mod_mac_pcs, MAC_PCS_FEC_CW_CNT);
+		p->mp_field_fec_cw_cnt_cw_cnt =
+			register_get_field(p->mp_reg_fec_cw_cnt, MAC_PCS_FEC_CW_CNT_CW_CNT);
+
+		p->mp_reg_fec_ucw_cnt =
+			module_get_register(p->mp_mod_mac_pcs, MAC_PCS_FEC_UCW_CNT);
+		p->mp_field_fec_ucw_cnt_ucw_cnt =
+			register_get_field(p->mp_reg_fec_ucw_cnt, MAC_PCS_FEC_UCW_CNT_UCW_CNT);
+
+		/* GTY_RX_BUF_STAT */
+#ifdef RXBUFSTAT
+		p->mp_reg_gty_rx_buf_stat =
+			module_get_register(p->mp_mod_mac_pcs, MAC_PCS_GTY_RX_BUF_STAT);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat0 =
+			register_get_field(p->mp_reg_gty_rx_buf_stat,
+					   MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_0);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat1 =
+			register_get_field(p->mp_reg_gty_rx_buf_stat,
+					   MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_1);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat2 =
+			register_get_field(p->mp_reg_gty_rx_buf_stat,
+					   MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_2);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat3 =
+			register_get_field(p->mp_reg_gty_rx_buf_stat,
+					   MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_3);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat_changed0 =
+			register_get_field(p->mp_reg_gty_rx_buf_stat,
+					   MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_CHANGED_0);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat_changed1 =
+			register_get_field(p->mp_reg_gty_rx_buf_stat,
+					   MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_CHANGED_1);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat_changed2 =
+			register_get_field(p->mp_reg_gty_rx_buf_stat,
+					   MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_CHANGED_2);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat_changed3 =
+			register_get_field(p->mp_reg_gty_rx_buf_stat,
+					   MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_CHANGED_3);
+#endif
+
+		/* GTY_PRE_CURSOR */
+		p->mp_reg_gty_pre_cursor =
+			module_get_register(p->mp_mod_mac_pcs, MAC_PCS_GTY_PRE_CURSOR);
+		p->mp_field_gty_pre_cursor_tx_pre_csr0 =
+			register_get_field(p->mp_reg_gty_pre_cursor,
+					   MAC_PCS_GTY_PRE_CURSOR_TX_PRE_CSR_0);
+		p->mp_field_gty_pre_cursor_tx_pre_csr1 =
+			register_get_field(p->mp_reg_gty_pre_cursor,
+					   MAC_PCS_GTY_PRE_CURSOR_TX_PRE_CSR_1);
+		p->mp_field_gty_pre_cursor_tx_pre_csr2 =
+			register_get_field(p->mp_reg_gty_pre_cursor,
+					   MAC_PCS_GTY_PRE_CURSOR_TX_PRE_CSR_2);
+		p->mp_field_gty_pre_cursor_tx_pre_csr3 =
+			register_get_field(p->mp_reg_gty_pre_cursor,
+					   MAC_PCS_GTY_PRE_CURSOR_TX_PRE_CSR_3);
+
+		/* GTY_DIFF_CTL */
+		p->mp_reg_gty_diff_ctl =
+			module_get_register(p->mp_mod_mac_pcs, MAC_PCS_GTY_DIFF_CTL);
+		p->mp_field_gty_gty_diff_ctl_tx_diff_ctl0 =
+			register_get_field(p->mp_reg_gty_diff_ctl,
+					   MAC_PCS_GTY_DIFF_CTL_TX_DIFF_CTL_0);
+		p->mp_field_gty_gty_diff_ctl_tx_diff_ctl1 =
+			register_get_field(p->mp_reg_gty_diff_ctl,
+					   MAC_PCS_GTY_DIFF_CTL_TX_DIFF_CTL_1);
+		p->mp_field_gty_gty_diff_ctl_tx_diff_ctl2 =
+			register_get_field(p->mp_reg_gty_diff_ctl,
+					   MAC_PCS_GTY_DIFF_CTL_TX_DIFF_CTL_2);
+		p->mp_field_gty_gty_diff_ctl_tx_diff_ctl3 =
+			register_get_field(p->mp_reg_gty_diff_ctl,
+					   MAC_PCS_GTY_DIFF_CTL_TX_DIFF_CTL_3);
+
+		/* GTY_POST_CURSOR */
+		p->mp_reg_gty_post_cursor =
+			module_get_register(p->mp_mod_mac_pcs,
+					    MAC_PCS_GTY_POST_CURSOR);
+		p->mp_field_gty_post_cursor_tx_post_csr0 =
+			register_get_field(p->mp_reg_gty_post_cursor,
+					   MAC_PCS_GTY_POST_CURSOR_TX_POST_CSR_0);
+		p->mp_field_gty_post_cursor_tx_post_csr1 =
+			register_get_field(p->mp_reg_gty_post_cursor,
+					   MAC_PCS_GTY_POST_CURSOR_TX_POST_CSR_1);
+		p->mp_field_gty_post_cursor_tx_post_csr2 =
+			register_get_field(p->mp_reg_gty_post_cursor,
+					   MAC_PCS_GTY_POST_CURSOR_TX_POST_CSR_2);
+		p->mp_field_gty_post_cursor_tx_post_csr3 =
+			register_get_field(p->mp_reg_gty_post_cursor,
+					   MAC_PCS_GTY_POST_CURSOR_TX_POST_CSR_3);
+
+		/* GTY_CTL */
+		p->mp_reg_gty_ctl =
+			module_query_register(p->mp_mod_mac_pcs, MAC_PCS_GTY_CTL);
+		if (p->mp_reg_gty_ctl) {
+			p->mp_field_gty_ctl_tx_pol0 =
+				register_get_field(p->mp_reg_gty_ctl,
+						   MAC_PCS_GTY_CTL_TX_POLARITY_0);
+			p->mp_field_gty_ctl_tx_pol1 =
+				register_get_field(p->mp_reg_gty_ctl,
+						   MAC_PCS_GTY_CTL_TX_POLARITY_1);
+			p->mp_field_gty_ctl_tx_pol2 =
+				register_get_field(p->mp_reg_gty_ctl,
+						   MAC_PCS_GTY_CTL_TX_POLARITY_2);
+			p->mp_field_gty_ctl_tx_pol3 =
+				register_get_field(p->mp_reg_gty_ctl,
+						   MAC_PCS_GTY_CTL_TX_POLARITY_3);
+		} else {
+			p->mp_reg_gty_ctl =
+				module_get_register(p->mp_mod_mac_pcs,
+						    MAC_PCS_GTY_CTL_RX);
+			p->mp_reg_gty_ctl_tx =
+				module_get_register(p->mp_mod_mac_pcs,
+						    MAC_PCS_GTY_CTL_TX);
+			p->mp_field_gty_ctl_tx_pol0 =
+				register_get_field(p->mp_reg_gty_ctl_tx,
+						   MAC_PCS_GTY_CTL_TX_POLARITY_0);
+			p->mp_field_gty_ctl_tx_pol1 =
+				register_get_field(p->mp_reg_gty_ctl_tx,
+						   MAC_PCS_GTY_CTL_TX_POLARITY_1);
+			p->mp_field_gty_ctl_tx_pol2 =
+				register_get_field(p->mp_reg_gty_ctl_tx,
+						   MAC_PCS_GTY_CTL_TX_POLARITY_2);
+			p->mp_field_gty_ctl_tx_pol3 =
+				register_get_field(p->mp_reg_gty_ctl_tx,
+						   MAC_PCS_GTY_CTL_TX_POLARITY_3);
+		}
+		p->mp_field_gty_ctl_rx_pol0 =
+			register_get_field(p->mp_reg_gty_ctl,
+					   MAC_PCS_GTY_CTL_RX_POLARITY_0);
+		p->mp_field_gty_ctl_rx_pol1 =
+			register_get_field(p->mp_reg_gty_ctl,
+					   MAC_PCS_GTY_CTL_RX_POLARITY_1);
+		p->mp_field_gty_ctl_rx_pol2 =
+			register_get_field(p->mp_reg_gty_ctl,
+					   MAC_PCS_GTY_CTL_RX_POLARITY_2);
+		p->mp_field_gty_ctl_rx_pol3 =
+			register_get_field(p->mp_reg_gty_ctl,
+					   MAC_PCS_GTY_CTL_RX_POLARITY_3);
+		p->mp_field_gty_ctl_rx_lpm_en0 =
+			register_get_field(p->mp_reg_gty_ctl,
+					   MAC_PCS_GTY_CTL_RX_LPM_EN_0);
+		p->mp_field_gty_ctl_rx_lpm_en1 =
+			register_get_field(p->mp_reg_gty_ctl,
+					   MAC_PCS_GTY_CTL_RX_LPM_EN_1);
+		p->mp_field_gty_ctl_rx_lpm_en2 =
+			register_get_field(p->mp_reg_gty_ctl,
+					   MAC_PCS_GTY_CTL_RX_LPM_EN_2);
+		p->mp_field_gty_ctl_rx_lpm_en3 =
+			register_get_field(p->mp_reg_gty_ctl,
+					   MAC_PCS_GTY_CTL_RX_LPM_EN_3);
+		p->mp_field_gty_ctl_rx_equa_rst0 =
+			register_get_field(p->mp_reg_gty_ctl,
+					   MAC_PCS_GTY_CTL_RX_EQUA_RST_0);
+		p->mp_field_gty_ctl_rx_equa_rst1 =
+			register_get_field(p->mp_reg_gty_ctl,
+					   MAC_PCS_GTY_CTL_RX_EQUA_RST_1);
+		p->mp_field_gty_ctl_rx_equa_rst2 =
+			register_get_field(p->mp_reg_gty_ctl,
+					   MAC_PCS_GTY_CTL_RX_EQUA_RST_2);
+		p->mp_field_gty_ctl_rx_equa_rst3 =
+			register_get_field(p->mp_reg_gty_ctl,
+					   MAC_PCS_GTY_CTL_RX_EQUA_RST_3);
+
+		/* DEBOUNCE_CTRL */
+		p->mp_reg_debounce_ctrl =
+			module_get_register(p->mp_mod_mac_pcs,
+					    MAC_PCS_DEBOUNCE_CTRL);
+		p->mp_field_debounce_ctrl_nt_port_ctrl =
+			register_get_field(p->mp_reg_debounce_ctrl,
+					   MAC_PCS_DEBOUNCE_CTRL_NT_PORT_CTRL);
+
+		p->mp_reg_time_stamp_comp =
+			module_query_register(p->mp_mod_mac_pcs,
+					      MAC_PCS_TIMESTAMP_COMP);
+		if (p->mp_reg_time_stamp_comp) {
+			/* TIMESTAMP_COMP */
+			p->mp_field_time_stamp_comp_rx_dly =
+				register_get_field(p->mp_reg_time_stamp_comp,
+						   MAC_PCS_TIMESTAMP_COMP_RX_DLY);
+			p->mp_field_time_stamp_comp_tx_dly =
+				register_get_field(p->mp_reg_time_stamp_comp,
+						   MAC_PCS_TIMESTAMP_COMP_TX_DLY);
+		}
+	}
+	return 0;
+}
+
+/* wrapper - for ease of use */
+void nthw_mac_pcs_tx_host_enable(nthw_mac_pcs_t *p, bool enable)
+{
+	nthw_mac_pcs_set_tx_sel_host(p, enable);
+	nthw_mac_pcs_set_tx_sel_tfg(p, !enable);
+}
+
+void nthw_mac_pcs_set_rx_enable(nthw_mac_pcs_t *p, bool enable)
+{
+	field_get_updated(p->mp_fld_pcs_config_rx_enable);
+	if (enable)
+		field_set_flush(p->mp_fld_pcs_config_rx_enable);
+
+	else
+		field_clr_flush(p->mp_fld_pcs_config_rx_enable);
+}
+
+void nthw_mac_pcs_set_tx_enable(nthw_mac_pcs_t *p, bool enable)
+{
+	field_get_updated(p->mp_fld_pcs_config_tx_enable);
+	if (enable)
+		field_set_flush(p->mp_fld_pcs_config_tx_enable);
+
+	else
+		field_clr_flush(p->mp_fld_pcs_config_tx_enable);
+}
+
+void nthw_mac_pcs_set_tx_sel_host(nthw_mac_pcs_t *p, bool enable)
+{
+	field_get_updated(p->mp_fld_phymac_misc_tx_sel_host);
+	if (enable)
+		field_set_flush(p->mp_fld_phymac_misc_tx_sel_host);
+
+	else
+		field_clr_flush(p->mp_fld_phymac_misc_tx_sel_host);
+}
+
+void nthw_mac_pcs_set_tx_sel_tfg(nthw_mac_pcs_t *p, bool enable)
+{
+	field_get_updated(p->mp_fld_phymac_misc_tx_sel_tfg);
+	if (enable)
+		field_set_flush(p->mp_fld_phymac_misc_tx_sel_tfg);
+
+	else
+		field_clr_flush(p->mp_fld_phymac_misc_tx_sel_tfg);
+}
+
+void nthw_mac_pcs_set_ts_eop(nthw_mac_pcs_t *p, bool enable)
+{
+	if (p->mp_fld_phymac_misc_ts_eop) {
+		field_get_updated(p->mp_fld_phymac_misc_ts_eop);
+		if (enable)
+			field_set_flush(p->mp_fld_phymac_misc_ts_eop);
+
+		else
+			field_clr_flush(p->mp_fld_phymac_misc_ts_eop);
+	}
+}
+
+bool nthw_mac_pcs_is_block_and_lane_lock_locked(nthw_mac_pcs_t *p)
+{
+	uint32_t block_lock;
+	uint32_t lane_lock;
+
+	block_lock = field_get_updated(p->mp_fld_block_lock_lock) &
+		    p->m_fld_block_lock_lock_mask;
+	lane_lock = field_get_updated(p->mp_fld_vl_demuxed_lock) &
+		   p->m_fld_vl_demuxed_lock_mask;
+	if (block_lock == p->m_fld_block_lock_lock_mask &&
+			lane_lock == p->m_fld_vl_demuxed_lock_mask)
+		return true;
+
+	return false;
+}
+
+void nthw_mac_pcs_tx_path_rst(nthw_mac_pcs_t *p, bool enable)
+{
+	field_get_updated(p->mp_fld_pcs_config_tx_path_rst);
+	if (enable)
+		field_set_flush(p->mp_fld_pcs_config_tx_path_rst);
+
+	else
+		field_clr_flush(p->mp_fld_pcs_config_tx_path_rst);
+}
+
+void nthw_mac_pcs_rx_path_rst(nthw_mac_pcs_t *p, bool enable)
+{
+	field_get_updated(p->mp_fld_pcs_config_rx_path_rst);
+	if (enable)
+		field_set_flush(p->mp_fld_pcs_config_rx_path_rst);
+
+	else
+		field_clr_flush(p->mp_fld_pcs_config_rx_path_rst);
+}
+
+bool nthw_mac_pcs_is_rx_path_rst(nthw_mac_pcs_t *p)
+{
+	return field_get_updated(p->mp_fld_pcs_config_rx_path_rst);
+}
+
+void nthw_mac_pcs_tx_send_rfi(nthw_mac_pcs_t *p, bool enable)
+{
+	field_get_updated(p->mp_fld_pcs_config_tx_send_rfi);
+	if (enable)
+		field_set_flush(p->mp_fld_pcs_config_tx_send_rfi);
+
+	else
+		field_clr_flush(p->mp_fld_pcs_config_tx_send_rfi);
+}
+
+void nthw_mac_pcs_rx_force_resync(nthw_mac_pcs_t *p, bool enable)
+{
+	field_get_updated(p->mp_fld_pcs_config_rx_force_resync);
+	if (enable)
+		field_set_flush(p->mp_fld_pcs_config_rx_force_resync);
+
+	else
+		field_clr_flush(p->mp_fld_pcs_config_rx_force_resync);
+}
+
+bool nthw_mac_pcs_is_gt_fsm_rx_reset_done(nthw_mac_pcs_t *p)
+{
+	if ((field_get_updated(p->mp_fld_gty_stat_rx_rst_done0) &
+			p->m_fld_gty_stat_rx_rst_done_mask) == p->m_fld_gty_stat_rx_rst_done_mask &&
+			(field_get_updated(p->mp_fld_gty_stat_rx_rst_done1) &
+			p->m_fld_gty_stat_rx_rst_done_mask) == p->m_fld_gty_stat_rx_rst_done_mask &&
+			(field_get_updated(p->mp_fld_gty_stat_rx_rst_done2) &
+			p->m_fld_gty_stat_rx_rst_done_mask) == p->m_fld_gty_stat_rx_rst_done_mask &&
+			(field_get_updated(p->mp_fld_gty_stat_rx_rst_done3) &
+			p->m_fld_gty_stat_rx_rst_done_mask) == p->m_fld_gty_stat_rx_rst_done_mask)
+		return true;
+
+	else
+		return false;
+}
+
+bool nthw_mac_pcs_is_gt_fsm_tx_reset_done(nthw_mac_pcs_t *p)
+{
+	if ((field_get_updated(p->mp_fld_gty_stat_tx_rst_done0) &
+			p->m_fld_gty_stat_tx_rst_done_mask) == p->m_fld_gty_stat_tx_rst_done_mask &&
+			(field_get_updated(p->mp_fld_gty_stat_tx_rst_done1) &
+			p->m_fld_gty_stat_tx_rst_done_mask) == p->m_fld_gty_stat_tx_rst_done_mask &&
+			(field_get_updated(p->mp_fld_gty_stat_tx_rst_done2) &
+			p->m_fld_gty_stat_tx_rst_done_mask) == p->m_fld_gty_stat_tx_rst_done_mask &&
+			(field_get_updated(p->mp_fld_gty_stat_tx_rst_done3) &
+			p->m_fld_gty_stat_tx_rst_done_mask) == p->m_fld_gty_stat_tx_rst_done_mask)
+		return true;
+
+	else
+		return false;
+}
+
+void nthw_mac_pcs_set_host_loopback(nthw_mac_pcs_t *p, bool enable)
+{
+	register_update(p->mp_reg_gty_loop);
+	if (enable) {
+		field_set_val32(p->mp_fld_gty_loop_gt_loop0, 2);
+		field_set_val32(p->mp_fld_gty_loop_gt_loop1, 2);
+		field_set_val32(p->mp_fld_gty_loop_gt_loop2, 2);
+		field_set_val32(p->mp_fld_gty_loop_gt_loop3, 2);
+	} else {
+		field_set_val32(p->mp_fld_gty_loop_gt_loop0, 0);
+		field_set_val32(p->mp_fld_gty_loop_gt_loop1, 0);
+		field_set_val32(p->mp_fld_gty_loop_gt_loop2, 0);
+		field_set_val32(p->mp_fld_gty_loop_gt_loop3, 0);
+	}
+	register_flush(p->mp_reg_gty_loop, 1);
+}
+
+void nthw_mac_pcs_set_line_loopback(nthw_mac_pcs_t *p, bool enable)
+{
+	register_update(p->mp_reg_gty_loop);
+	if (enable) {
+		field_set_val32(p->mp_fld_gty_loop_gt_loop0, 4);
+		field_set_val32(p->mp_fld_gty_loop_gt_loop1, 4);
+		field_set_val32(p->mp_fld_gty_loop_gt_loop2, 4);
+		field_set_val32(p->mp_fld_gty_loop_gt_loop3, 4);
+	} else {
+		field_set_val32(p->mp_fld_gty_loop_gt_loop0, 0);
+		field_set_val32(p->mp_fld_gty_loop_gt_loop1, 0);
+		field_set_val32(p->mp_fld_gty_loop_gt_loop2, 0);
+		field_set_val32(p->mp_fld_gty_loop_gt_loop3, 0);
+	}
+	register_flush(p->mp_reg_gty_loop, 1);
+}
+
+void nthw_mac_pcs_reset_bip_counters(nthw_mac_pcs_t *p)
+{
+	uint32_t lane_bit_errors[NTHW_MAC_PCS_LANES];
+
+	register_update(p->mp_reg_bip_err);
+	field_get_val(p->mp_fld_reg_bip_err_bip_err, (uint32_t *)lane_bit_errors,
+		     ARRAY_SIZE(lane_bit_errors));
+
+#if defined(DEBUG)
+	{
+		uint8_t lane;
+
+		for (lane = 0; lane < c_pcs_lanes; lane++) {
+			if (lane_bit_errors[lane]) {
+				NT_LOG(DBG, NTHW,
+				       "Port %u: pcsLane %2u: BIP8 errors: %u\n",
+				       p->m_port_no, lane, lane_bit_errors[lane]);
+			}
+		}
+	}
+#else
+	(void)c_pcs_lanes; /* unused - kill warning */
+#endif
+}
+
+void nthw_mac_pcs_get_status(nthw_mac_pcs_t *p, uint8_t *status)
+{
+	*status = field_get_updated(p->mp_fld_stat_pcs_rx_status) & 0x01;
+}
+
+bool nthw_mac_pcs_get_hi_ber(nthw_mac_pcs_t *p)
+{
+	return field_get_updated(p->mp_fld_stat_pcs_rx_hi_ber);
+}
+
+void nthw_mac_pcs_get_link_summary(nthw_mac_pcs_t *p, uint32_t *p_abs,
+	uint32_t *p_nt_phy_link_state, uint32_t *p_lh_abs,
+	uint32_t *p_ll_nt_phy_link_state,
+	uint32_t *p_link_down_cnt, uint32_t *p_nim_interr,
+	uint32_t *p_lh_local_fault,
+	uint32_t *p_lh_remote_fault, uint32_t *p_local_fault,
+	uint32_t *p_remote_fault)
+{
+	register_update(p->mp_reg_link_summary);
+	if (p_abs)
+		*p_abs = field_get_val32(p->mp_fld_link_summary_abs);
+	if (p_nt_phy_link_state) {
+		*p_nt_phy_link_state =
+			field_get_val32(p->mp_fld_link_summary_nt_phy_link_state);
+	}
+	if (p_lh_abs)
+		*p_lh_abs = field_get_val32(p->mp_fld_link_summary_lh_abs);
+	if (p_ll_nt_phy_link_state) {
+		*p_ll_nt_phy_link_state =
+			field_get_val32(p->mp_fld_link_summary_ll_nt_phy_link_state);
+	}
+	if (p_link_down_cnt)
+		*p_link_down_cnt = field_get_val32(p->mp_fld_link_summary_link_down_cnt);
+	if (p_nim_interr)
+		*p_nim_interr = field_get_val32(p->mp_fld_link_summary_nim_interr);
+	if (p_lh_local_fault) {
+		*p_lh_local_fault =
+			field_get_val32(p->mp_fld_link_summary_lh_local_fault);
+	}
+	if (p_lh_remote_fault) {
+		*p_lh_remote_fault =
+			field_get_val32(p->mp_fld_link_summary_lh_remote_fault);
+	}
+	if (p_local_fault)
+		*p_local_fault = field_get_val32(p->mp_fld_link_summary_local_fault);
+	if (p_remote_fault)
+		*p_remote_fault = field_get_val32(p->mp_fld_link_summary_remote_fault);
+}
+
+/*
+ * Returns true if the lane/block lock bits indicate that a reset is required.
+ * This is the case if Block/Lane lock is not all zero but not all set either.
+ */
+bool nthw_mac_pcs_reset_required(nthw_mac_pcs_t *p)
+{
+	uint32_t block_lock = nthw_mac_pcs_get_fld_block_lock_lock(p);
+	uint32_t lane_lock = nthw_mac_pcs_get_fld_lane_lock_lock(p);
+	uint32_t block_lock_mask = nthw_mac_pcs_get_fld_block_lock_lock_mask(p);
+	uint32_t lane_lock_mask = nthw_mac_pcs_get_fld_lane_lock_lock_mask(p);
+
+	return ((block_lock != 0) && (block_lock != block_lock_mask)) ||
+	       ((lane_lock != 0) && (lane_lock != lane_lock_mask));
+}
+
+void nthw_mac_pcs_set_fec(nthw_mac_pcs_t *p, bool enable)
+{
+	NT_LOG(DBG, NTHW, "Port %u: Set FEC: %u\n", p->m_port_no, enable);
+
+	field_get_updated(p->mp_field_fec_ctrl_reg_rs_fec_ctrl_in);
+	if (enable) {
+		field_set_val_flush32(p->mp_field_fec_ctrl_reg_rs_fec_ctrl_in, 0);
+	} else {
+		field_set_val_flush32(p->mp_field_fec_ctrl_reg_rs_fec_ctrl_in,
+				    (1 << 5) - 1);
+	}
+
+	/* Both Rx and Tx must be reset for new FEC state to become active */
+	nthw_mac_pcs_rx_path_rst(p, true);
+	nthw_mac_pcs_tx_path_rst(p, true);
+	NT_OS_WAIT_USEC(10000); /* 10ms */
+
+	nthw_mac_pcs_rx_path_rst(p, false);
+	nthw_mac_pcs_tx_path_rst(p, false);
+	NT_OS_WAIT_USEC(10000); /* 10ms */
+
+#ifdef DEBUG
+	if (enable) {
+		NT_LOG(DBG, NTHW, "Port %u: FEC valid: %u\n", p->m_port_no,
+		       field_get_updated(p->mp_field_fec_stat_valid));
+	} else {
+		NT_LOG(DBG, NTHW, "Port %u: FEC bypass: %u\n", p->m_port_no,
+		       field_get_updated(p->mp_field_fec_stat_bypass));
+	}
+#endif
+}
+
+bool nthw_mac_pcs_get_fec_bypass(nthw_mac_pcs_t *p)
+{
+	return field_get_updated(p->mp_field_fec_stat_bypass);
+}
+
+bool nthw_mac_pcs_get_fec_valid(nthw_mac_pcs_t *p)
+{
+	return field_get_updated(p->mp_field_fec_stat_valid);
+}
+
+bool nthw_mac_pcs_get_fec_aligned(nthw_mac_pcs_t *p)
+{
+	return field_get_updated(p->mp_field_fec_stat_fec_lane_algn);
+}
+
+bool nthw_mac_pcs_get_fec_stat_any_am_locked(nthw_mac_pcs_t *p)
+{
+	register_update(p->mp_reg_fec_stat);
+	if ((field_get_val32(p->mp_field_fec_stat_am_lock0)) ||
+			(field_get_val32(p->mp_field_fec_stat_am_lock1)) ||
+			(field_get_val32(p->mp_field_fec_stat_am_lock2)) ||
+			(field_get_val32(p->mp_field_fec_stat_am_lock3)))
+		return true;
+	return false;
+}
+
+bool nthw_mac_pcs_get_fec_stat_all_am_locked(nthw_mac_pcs_t *p)
+{
+	register_update(p->mp_reg_fec_stat);
+	if ((field_get_val32(p->mp_field_fec_stat_am_lock0)) &&
+			(field_get_val32(p->mp_field_fec_stat_am_lock1)) &&
+			(field_get_val32(p->mp_field_fec_stat_am_lock2)) &&
+			(field_get_val32(p->mp_field_fec_stat_am_lock3)))
+		return true;
+	return false;
+}
+
+void nthw_mac_pcs_dump_fec_stat_fields(nthw_mac_pcs_t *p)
+{
+	register_update(p->mp_reg_fec_stat);
+	NT_LOG(DBG, NTHW,
+	       "Port %u: FEC_STAT VALID: %u, AM_LOCK_0: %u, AM_LOCK_1: %u, AM_LOCK_2: %u, AM_LOCK_0: %u, FEC_LANE_ALGN: %u\n",
+	       p->m_port_no, field_get_val32(p->mp_field_fec_stat_valid),
+	       field_get_val32(p->mp_field_fec_stat_am_lock0),
+	       field_get_val32(p->mp_field_fec_stat_am_lock1),
+	       field_get_val32(p->mp_field_fec_stat_am_lock2),
+	       field_get_val32(p->mp_field_fec_stat_am_lock3),
+	       field_get_val32(p->mp_field_fec_stat_fec_lane_algn));
+}
+
+void nthw_mac_pcs_reset_fec_counters(nthw_mac_pcs_t *p)
+{
+	register_update(p->mp_reg_fec_cw_cnt);
+	register_update(p->mp_reg_fec_ucw_cnt);
+
+	if (field_get_val32(p->mp_field_fec_cw_cnt_cw_cnt)) {
+		NT_LOG(DBG, NTHW, "Port %u: FEC_CW_CNT: %u\n", p->m_port_no,
+		       field_get_val32(p->mp_field_fec_cw_cnt_cw_cnt));
+	}
+	if (field_get_val32(p->mp_field_fec_ucw_cnt_ucw_cnt)) {
+		NT_LOG(DBG, NTHW, "Port %u: FEC_UCW_CNT: %u\n", p->m_port_no,
+		       field_get_val32(p->mp_field_fec_ucw_cnt_ucw_cnt));
+	}
+}
+
+bool nthw_mac_pcs_get_gty_rx_buf_stat_error(nthw_mac_pcs_t *p)
+{
+	register_update(p->mp_reg_gty_rx_buf_stat);
+
+	if (field_get_val32(p->mp_field_gty_rx_buf_stat_rx_buf_stat_changed0) ||
+			field_get_val32(p->mp_field_gty_rx_buf_stat_rx_buf_stat_changed1) ||
+			field_get_val32(p->mp_field_gty_rx_buf_stat_rx_buf_stat_changed2) ||
+			field_get_val32(p->mp_field_gty_rx_buf_stat_rx_buf_stat_changed3)) {
+		if (field_get_val32(p->mp_field_gty_rx_buf_stat_rx_buf_stat0) ||
+				field_get_val32(p->mp_field_gty_rx_buf_stat_rx_buf_stat1) ||
+				field_get_val32(p->mp_field_gty_rx_buf_stat_rx_buf_stat2) ||
+				field_get_val32(p->mp_field_gty_rx_buf_stat_rx_buf_stat3)) {
+			NT_LOG(DBG, NTHW,
+			       "Port %u: GTY RX_BUF_STAT_0: %u, RX_BUF_STAT_1: %u, RX_BUF_STAT_2: %u, RX_BUF_STAT_3: %u\n",
+			       p->m_port_no,
+			       field_get_val32(p->mp_field_gty_rx_buf_stat_rx_buf_stat0),
+			       field_get_val32(p->mp_field_gty_rx_buf_stat_rx_buf_stat1),
+			       field_get_val32(p->mp_field_gty_rx_buf_stat_rx_buf_stat2),
+			       field_get_val32(p->mp_field_gty_rx_buf_stat_rx_buf_stat3));
+
+			return true;
+		}
+	}
+	return false;
+}
+
+void nthw_mac_pcs_set_gty_tx_tuning(nthw_mac_pcs_t *p, uint8_t lane, uint8_t tx_pre_csr,
+			       uint8_t tx_diff_ctl, uint8_t tx_post_csr)
+{
+	/* GTY_PRE_CURSOR */
+	register_update(p->mp_reg_gty_pre_cursor);
+	switch (lane) {
+	case 0:
+		field_set_val_flush32(p->mp_field_gty_pre_cursor_tx_pre_csr0,
+				    tx_pre_csr & 0x1F);
+		break;
+	case 1:
+		field_set_val_flush32(p->mp_field_gty_pre_cursor_tx_pre_csr1,
+				    tx_pre_csr & 0x1F);
+		break;
+	case 2:
+		field_set_val_flush32(p->mp_field_gty_pre_cursor_tx_pre_csr2,
+				    tx_pre_csr & 0x1F);
+		break;
+	case 3:
+		field_set_val_flush32(p->mp_field_gty_pre_cursor_tx_pre_csr3,
+				    tx_pre_csr & 0x1F);
+		break;
+	}
+
+	/* GTY_DIFF_CTL */
+	register_update(p->mp_reg_gty_diff_ctl);
+	switch (lane) {
+	case 0:
+		field_set_val_flush32(p->mp_field_gty_gty_diff_ctl_tx_diff_ctl0,
+				    tx_diff_ctl & 0x1F);
+		break;
+	case 1:
+		field_set_val_flush32(p->mp_field_gty_gty_diff_ctl_tx_diff_ctl1,
+				    tx_diff_ctl & 0x1F);
+		break;
+	case 2:
+		field_set_val_flush32(p->mp_field_gty_gty_diff_ctl_tx_diff_ctl2,
+				    tx_diff_ctl & 0x1F);
+		break;
+	case 3:
+		field_set_val_flush32(p->mp_field_gty_gty_diff_ctl_tx_diff_ctl3,
+				    tx_diff_ctl & 0x1F);
+		break;
+	}
+
+	/* GTY_POST_CURSOR */
+	register_update(p->mp_reg_gty_post_cursor);
+	switch (lane) {
+	case 0:
+		field_set_val_flush32(p->mp_field_gty_post_cursor_tx_post_csr0,
+				    tx_post_csr & 0x1F);
+		break;
+	case 1:
+		field_set_val_flush32(p->mp_field_gty_post_cursor_tx_post_csr1,
+				    tx_post_csr & 0x1F);
+		break;
+	case 2:
+		field_set_val_flush32(p->mp_field_gty_post_cursor_tx_post_csr2,
+				    tx_post_csr & 0x1F);
+		break;
+	case 3:
+		field_set_val_flush32(p->mp_field_gty_post_cursor_tx_post_csr3,
+				    tx_post_csr & 0x1F);
+		break;
+	}
+
+	NT_LOG(DBG, NTHW,
+	       "Port %u, lane %u: GTY txPreCsr: %d, txDiffCtl: %d, txPostCsr: %d\n",
+	       p->m_port_no, lane, tx_pre_csr, tx_diff_ctl, tx_post_csr);
+}
+
+/*
+ * Set receiver equalization mode
+ *  0: enable DFE
+ *  mode 1: enable LPM
+ *
+ * See UltraScale Architecture GTY Transceivers www.xilinx.com page 181,
+ * UG578 (v1.1) November 24, 2015
+ */
+void nthw_mac_pcs_set_receiver_equalization_mode(nthw_mac_pcs_t *p, uint8_t mode)
+{
+	register_update(p->mp_reg_gty_ctl);
+	field_set_val32(p->mp_field_gty_ctl_rx_lpm_en0, mode & 0x1);
+	field_set_val32(p->mp_field_gty_ctl_rx_lpm_en1, mode & 0x1);
+	field_set_val32(p->mp_field_gty_ctl_rx_lpm_en2, mode & 0x1);
+	field_set_val_flush32(p->mp_field_gty_ctl_rx_lpm_en3, mode & 0x1);
+
+	/* Toggle reset */
+	field_set_val32(p->mp_field_gty_ctl_rx_equa_rst0, 1);
+	field_set_val32(p->mp_field_gty_ctl_rx_equa_rst1, 1);
+	field_set_val32(p->mp_field_gty_ctl_rx_equa_rst2, 1);
+	field_set_val_flush32(p->mp_field_gty_ctl_rx_equa_rst3, 1);
+
+	NT_OS_WAIT_USEC(1000); /* 1ms */
+
+	field_set_val32(p->mp_field_gty_ctl_rx_equa_rst0, 0);
+	field_set_val32(p->mp_field_gty_ctl_rx_equa_rst1, 0);
+	field_set_val32(p->mp_field_gty_ctl_rx_equa_rst2, 0);
+	field_set_val_flush32(p->mp_field_gty_ctl_rx_equa_rst3, 0);
+
+	NT_LOG(DBG, NTHW, "Port %u: GTY receiver mode: %s\n", p->m_port_no,
+	       (mode == c_mac_pcs_receiver_mode_dfe ? "DFE" : "LPM"));
+}
+
+void nthw_mac_pcs_swap_gty_tx_polarity(nthw_mac_pcs_t *p, uint8_t lane, bool swap)
+{
+	register_update(p->mp_reg_gty_ctl);
+	switch (lane) {
+	case 0:
+		field_set_val_flush32(p->mp_field_gty_ctl_tx_pol0, swap);
+		break;
+	case 1:
+		field_set_val_flush32(p->mp_field_gty_ctl_tx_pol1, swap);
+		break;
+	case 2:
+		field_set_val_flush32(p->mp_field_gty_ctl_tx_pol2, swap);
+		break;
+	case 3:
+		field_set_val_flush32(p->mp_field_gty_ctl_tx_pol3, swap);
+		break;
+	}
+	NT_LOG(DBG, NTHW, "Port %u: set GTY Tx lane (%d) polarity: %d\n",
+	       p->m_port_no, lane, swap);
+}
+
+void nthw_mac_pcs_swap_gty_rx_polarity(nthw_mac_pcs_t *p, uint8_t lane, bool swap)
+{
+	register_update(p->mp_reg_gty_ctl);
+	switch (lane) {
+	case 0:
+		field_set_val_flush32(p->mp_field_gty_ctl_rx_pol0, swap);
+		break;
+	case 1:
+		field_set_val_flush32(p->mp_field_gty_ctl_rx_pol1, swap);
+		break;
+	case 2:
+		field_set_val_flush32(p->mp_field_gty_ctl_rx_pol2, swap);
+		break;
+	case 3:
+		field_set_val_flush32(p->mp_field_gty_ctl_rx_pol3, swap);
+		break;
+	}
+	NT_LOG(DBG, NTHW, "Port %u: set GTY Rx lane (%d) polarity: %d\n",
+	       p->m_port_no, lane, swap);
+}
+
+void nthw_mac_pcs_set_led_mode(nthw_mac_pcs_t *p, uint8_t mode)
+{
+	field_get_updated(p->mp_field_debounce_ctrl_nt_port_ctrl);
+	field_set_val_flush32(p->mp_field_debounce_ctrl_nt_port_ctrl, mode);
+}
+
+void nthw_mac_pcs_set_timestamp_comp_rx(nthw_mac_pcs_t *p, uint16_t rx_dly)
+{
+	if (p->mp_field_time_stamp_comp_rx_dly) {
+		field_get_updated(p->mp_field_time_stamp_comp_rx_dly);
+		field_set_val_flush32(p->mp_field_time_stamp_comp_rx_dly, rx_dly);
+	}
+}
+
+void nthw_mac_pcs_set_port_no(nthw_mac_pcs_t *p, uint8_t port_no)
+{
+	p->m_port_no = port_no;
+}
+
+uint32_t nthw_mac_pcs_get_fld_block_lock_lock(nthw_mac_pcs_t *p)
+{
+	return field_get_updated(p->mp_fld_block_lock_lock);
+}
+
+uint32_t nthw_mac_pcs_get_fld_block_lock_lock_mask(nthw_mac_pcs_t *p)
+{
+	return p->m_fld_block_lock_lock_mask;
+}
+
+uint32_t nthw_mac_pcs_get_fld_lane_lock_lock(nthw_mac_pcs_t *p)
+{
+	return field_get_updated(p->mp_fld_vl_demuxed_lock);
+}
+
+uint32_t nthw_mac_pcs_get_fld_lane_lock_lock_mask(nthw_mac_pcs_t *p)
+{
+	return p->m_fld_vl_demuxed_lock_mask;
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_mac_pcs.h b/drivers/net/ntnic/nthw/core/nthw_mac_pcs.h
new file mode 100644
index 0000000000..08197f8b9e
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_mac_pcs.h
@@ -0,0 +1,261 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NTHW_MAC_PCS_H_
+#define NTHW_MAC_PCS_H_
+
+enum nthw_mac_pcs_led_mode_e {
+	NTHW_MAC_PCS_LED_AUTO = 0x00,
+	NTHW_MAC_PCS_LED_ON = 0x01,
+	NTHW_MAC_PCS_LED_OFF = 0x02,
+	NTHW_MAC_PCS_LED_PORTID = 0x03,
+};
+
+#define nthw_mac_pcs_receiver_mode_dfe (0)
+#define nthw_mac_pcs_receiver_mode_lpm (1)
+
+struct nthw_mac_pcs {
+	uint8_t m_port_no;
+
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_mac_pcs;
+	int mn_instance;
+
+	/* Block lock status */
+	nt_field_t *mp_fld_block_lock_lock;
+	uint32_t m_fld_block_lock_lock_mask;
+
+	/* Lane lock status */
+	nt_field_t *mp_fld_vl_demuxed_lock;
+	uint32_t m_fld_vl_demuxed_lock_mask;
+
+	/* GTY_STAT */
+	nt_field_t *mp_fld_gty_stat_rx_rst_done0;
+	nt_field_t *mp_fld_gty_stat_rx_rst_done1;
+	nt_field_t *mp_fld_gty_stat_rx_rst_done2;
+	nt_field_t *mp_fld_gty_stat_rx_rst_done3;
+	nt_field_t *mp_fld_gty_stat_tx_rst_done0;
+	nt_field_t *mp_fld_gty_stat_tx_rst_done1;
+	nt_field_t *mp_fld_gty_stat_tx_rst_done2;
+	nt_field_t *mp_fld_gty_stat_tx_rst_done3;
+	uint32_t m_fld_gty_stat_rx_rst_done_mask;
+	uint32_t m_fld_gty_stat_tx_rst_done_mask;
+
+	/* GTY_LOOP */
+	nt_register_t *mp_reg_gty_loop;
+	nt_field_t *mp_fld_gty_loop_gt_loop0;
+	nt_field_t *mp_fld_gty_loop_gt_loop1;
+	nt_field_t *mp_fld_gty_loop_gt_loop2;
+	nt_field_t *mp_fld_gty_loop_gt_loop3;
+
+	/* MAC_PCS_CONFIG */
+	nt_field_t *mp_fld_pcs_config_tx_path_rst;
+	nt_field_t *mp_fld_pcs_config_rx_path_rst;
+	nt_field_t *mp_fld_pcs_config_rx_enable;
+	nt_field_t *mp_fld_pcs_config_rx_force_resync;
+	nt_field_t *mp_fld_pcs_config_rx_test_pattern;
+	nt_field_t *mp_fld_pcs_config_tx_enable;
+	nt_field_t *mp_fld_pcs_config_tx_send_idle;
+	nt_field_t *mp_fld_pcs_config_tx_send_rfi;
+	nt_field_t *mp_fld_pcs_config_tx_test_pattern;
+
+	/* STAT PCS */
+	nt_field_t *mp_fld_stat_pcs_rx_status;
+	nt_field_t *mp_fld_stat_pcs_rx_aligned;
+	nt_field_t *mp_fld_stat_pcs_rx_aligned_err;
+	nt_field_t *mp_fld_stat_pcs_rx_misaligned;
+	nt_field_t *mp_fld_stat_pcs_rx_internal_local_fault;
+	nt_field_t *mp_fld_stat_pcs_rx_received_local_fault;
+	nt_field_t *mp_fld_stat_pcs_rx_local_fault;
+	nt_field_t *mp_fld_stat_pcs_rx_remote_fault;
+	nt_field_t *mp_fld_stat_pcs_rx_hi_ber;
+
+	/* STAT_PCS_RX_LATCH */
+	nt_field_t *mp_fld_stat_pcs_rx_latch_status;
+
+	/* PHYMAC_MISC */
+	nt_field_t *mp_fld_phymac_misc_tx_sel_host;
+	nt_field_t *mp_fld_phymac_misc_tx_sel_tfg;
+	nt_field_t *mp_fld_phymac_misc_tx_sel_rx_loop;
+	nt_field_t *mp_fld_phymac_misc_ts_eop;
+
+	/* LINK_SUMMARY */
+	nt_register_t *mp_reg_link_summary;
+	nt_field_t *mp_fld_link_summary_abs;
+	nt_field_t *mp_fld_link_summary_nt_phy_link_state;
+	nt_field_t *mp_fld_link_summary_lh_abs;
+	nt_field_t *mp_fld_link_summary_ll_nt_phy_link_state;
+	nt_field_t *mp_fld_link_summary_link_down_cnt;
+	nt_field_t *mp_fld_link_summary_nim_interr;
+	nt_field_t *mp_fld_link_summary_lh_local_fault;
+	nt_field_t *mp_fld_link_summary_lh_remote_fault;
+	nt_field_t *mp_fld_link_summary_local_fault;
+	nt_field_t *mp_fld_link_summary_remote_fault;
+
+	/* BIP_ERR */
+	nt_register_t *mp_reg_bip_err;
+	nt_field_t *mp_fld_reg_bip_err_bip_err;
+
+	/* FEC_CTRL */
+	nt_register_t *mp_reg_fec_ctrl;
+	nt_field_t *mp_field_fec_ctrl_reg_rs_fec_ctrl_in;
+
+	/* FEC_STAT */
+	nt_register_t *mp_reg_fec_stat;
+	nt_field_t *mp_field_fec_stat_bypass;
+	nt_field_t *mp_field_fec_stat_valid;
+	nt_field_t *mp_field_fec_stat_am_lock0;
+	nt_field_t *mp_field_fec_stat_am_lock1;
+	nt_field_t *mp_field_fec_stat_am_lock2;
+	nt_field_t *mp_field_fec_stat_am_lock3;
+	nt_field_t *mp_field_fec_stat_fec_lane_algn;
+
+	/* FEC Corrected code word count */
+	nt_register_t *mp_reg_fec_cw_cnt;
+	nt_field_t *mp_field_fec_cw_cnt_cw_cnt;
+
+	/* FEC Uncorrected code word count */
+	nt_register_t *mp_reg_fec_ucw_cnt;
+	nt_field_t *mp_field_fec_ucw_cnt_ucw_cnt;
+
+	/* GTY_RX_BUF_STAT */
+	nt_register_t *mp_reg_gty_rx_buf_stat;
+	nt_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat0;
+	nt_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat1;
+	nt_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat2;
+	nt_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat3;
+	nt_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat_changed0;
+	nt_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat_changed1;
+	nt_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat_changed2;
+	nt_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat_changed3;
+
+	/* GTY_PRE_CURSOR */
+	nt_register_t *mp_reg_gty_pre_cursor;
+	nt_field_t *mp_field_gty_pre_cursor_tx_pre_csr0;
+	nt_field_t *mp_field_gty_pre_cursor_tx_pre_csr1;
+	nt_field_t *mp_field_gty_pre_cursor_tx_pre_csr2;
+	nt_field_t *mp_field_gty_pre_cursor_tx_pre_csr3;
+
+	/* GTY_DIFF_CTL */
+	nt_register_t *mp_reg_gty_diff_ctl;
+	nt_field_t *mp_field_gty_gty_diff_ctl_tx_diff_ctl0;
+	nt_field_t *mp_field_gty_gty_diff_ctl_tx_diff_ctl1;
+	nt_field_t *mp_field_gty_gty_diff_ctl_tx_diff_ctl2;
+	nt_field_t *mp_field_gty_gty_diff_ctl_tx_diff_ctl3;
+
+	/* GTY_POST_CURSOR */
+	nt_register_t *mp_reg_gty_post_cursor;
+	nt_field_t *mp_field_gty_post_cursor_tx_post_csr0;
+	nt_field_t *mp_field_gty_post_cursor_tx_post_csr1;
+	nt_field_t *mp_field_gty_post_cursor_tx_post_csr2;
+	nt_field_t *mp_field_gty_post_cursor_tx_post_csr3;
+
+	/* GTY_CTL */
+	nt_register_t *mp_reg_gty_ctl;
+	nt_register_t *mp_reg_gty_ctl_tx;
+	nt_field_t *mp_field_gty_ctl_tx_pol0;
+	nt_field_t *mp_field_gty_ctl_tx_pol1;
+	nt_field_t *mp_field_gty_ctl_tx_pol2;
+	nt_field_t *mp_field_gty_ctl_tx_pol3;
+	nt_field_t *mp_field_gty_ctl_rx_pol0;
+	nt_field_t *mp_field_gty_ctl_rx_pol1;
+	nt_field_t *mp_field_gty_ctl_rx_pol2;
+	nt_field_t *mp_field_gty_ctl_rx_pol3;
+	nt_field_t *mp_field_gty_ctl_rx_lpm_en0;
+	nt_field_t *mp_field_gty_ctl_rx_lpm_en1;
+	nt_field_t *mp_field_gty_ctl_rx_lpm_en2;
+	nt_field_t *mp_field_gty_ctl_rx_lpm_en3;
+	nt_field_t *mp_field_gty_ctl_rx_equa_rst0;
+	nt_field_t *mp_field_gty_ctl_rx_equa_rst1;
+	nt_field_t *mp_field_gty_ctl_rx_equa_rst2;
+	nt_field_t *mp_field_gty_ctl_rx_equa_rst3;
+
+	/* DEBOUNCE_CTRL */
+	nt_register_t *mp_reg_debounce_ctrl;
+	nt_field_t *mp_field_debounce_ctrl_nt_port_ctrl;
+
+	/* TIMESTAMP_COMP */
+	nt_register_t *mp_reg_time_stamp_comp;
+	nt_field_t *mp_field_time_stamp_comp_rx_dly;
+	nt_field_t *mp_field_time_stamp_comp_tx_dly;
+
+	/* STAT_PCS_RX */
+	nt_register_t *mp_reg_stat_pcs_rx;
+
+	/* STAT_PCS_RX */
+	nt_register_t *mp_reg_stat_pcs_rx_latch;
+
+	/* PHYMAC_MISC */
+	nt_register_t *mp_reg_phymac_misc;
+
+	/* BLOCK_LOCK */
+	nt_register_t *mp_reg_block_lock;
+};
+
+typedef struct nthw_mac_pcs nthw_mac_pcs_t;
+typedef struct nthw_mac_pcs nthw_mac_pcs;
+
+nthw_mac_pcs_t *nthw_mac_pcs_new(void);
+int nthw_mac_pcs_init(nthw_mac_pcs_t *p, nt_fpga_t *p_fpga, int n_instance);
+void nthw_mac_pcs_delete(nthw_mac_pcs_t *p);
+
+bool nthw_mac_pcs_is_block_and_lane_lock_locked(nthw_mac_pcs_t *p);
+bool nthw_mac_pcs_is_gt_fsm_rx_reset_done(nthw_mac_pcs_t *p);
+bool nthw_mac_pcs_is_gt_fsm_tx_reset_done(nthw_mac_pcs_t *p);
+void nthw_mac_pcs_tx_path_rst(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_rx_path_rst(nthw_mac_pcs_t *p, bool enable);
+bool nthw_mac_pcs_is_rx_path_rst(nthw_mac_pcs_t *p);
+void nthw_mac_pcs_rx_force_resync(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_tx_send_rfi(nthw_mac_pcs_t *p, bool enable);
+bool nthw_mac_pcs_is_dd_r3_calib_done(nthw_mac_pcs_t *p);
+void nthw_mac_pcs_tx_host_enable(nthw_mac_pcs_t *p,
+			     bool enable); /* wrapper - for ease of use */
+void nthw_mac_pcs_set_rx_enable(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_set_tx_enable(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_set_tx_sel_host(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_set_tx_sel_tfg(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_set_ts_eop(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_set_host_loopback(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_set_line_loopback(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_reset_bip_counters(nthw_mac_pcs_t *p);
+void nthw_mac_pcs_get_status(nthw_mac_pcs_t *p, uint8_t *status);
+bool nthw_mac_pcs_get_hi_ber(nthw_mac_pcs_t *p);
+
+void nthw_mac_pcs_get_link_summary1(nthw_mac_pcs_t *p, uint32_t *p_status,
+				uint32_t *p_status_latch, uint32_t *p_aligned,
+				uint32_t *p_local_fault, uint32_t *p_remote_fault);
+
+void nthw_mac_pcs_get_link_summary(nthw_mac_pcs_t *p, uint32_t *p_abs,
+			       uint32_t *p_nt_phy_link_state, uint32_t *p_lh_abs,
+			       uint32_t *p_ll_nt_phy_link_state,
+			       uint32_t *p_link_down_cnt, uint32_t *p_nim_interr,
+			       uint32_t *p_lh_local_fault,
+			       uint32_t *p_lh_remote_fault, uint32_t *p_local_fault,
+			       uint32_t *p_remote_fault);
+
+bool nthw_mac_pcs_reset_required(nthw_mac_pcs_t *p);
+void nthw_mac_pcs_set_fec(nthw_mac_pcs_t *p, bool enable);
+bool nthw_mac_pcs_get_fec_bypass(nthw_mac_pcs_t *p);
+bool nthw_mac_pcs_get_fec_valid(nthw_mac_pcs_t *p);
+bool nthw_mac_pcs_get_fec_aligned(nthw_mac_pcs_t *p);
+bool nthw_mac_pcs_get_fec_stat_any_am_locked(nthw_mac_pcs_t *p);
+bool nthw_mac_pcs_get_fec_stat_all_am_locked(nthw_mac_pcs_t *p);
+void nthw_mac_pcs_dump_fec_stat_fields(nthw_mac_pcs_t *p);
+void nthw_mac_pcs_reset_fec_counters(nthw_mac_pcs_t *p);
+bool nthw_mac_pcs_get_gty_rx_buf_stat_error(nthw_mac_pcs_t *p);
+void nthw_mac_pcs_set_gty_tx_tuning(nthw_mac_pcs_t *p, uint8_t lane, uint8_t tx_pre_csr,
+			       uint8_t tx_diff_ctl, uint8_t tx_post_csr);
+void nthw_mac_pcs_swap_gty_tx_polarity(nthw_mac_pcs_t *p, uint8_t lane, bool swap);
+void nthw_mac_pcs_swap_gty_rx_polarity(nthw_mac_pcs_t *p, uint8_t lane, bool swap);
+void nthw_mac_pcs_set_receiver_equalization_mode(nthw_mac_pcs_t *p, uint8_t mode);
+void nthw_mac_pcs_set_led_mode(nthw_mac_pcs_t *p, uint8_t mode);
+void nthw_mac_pcs_set_timestamp_comp_rx(nthw_mac_pcs_t *p, uint16_t rx_dly);
+void nthw_mac_pcs_set_port_no(nthw_mac_pcs_t *p, uint8_t port_no);
+
+uint32_t nthw_mac_pcs_get_fld_block_lock_lock(nthw_mac_pcs_t *p);
+uint32_t nthw_mac_pcs_get_fld_block_lock_lock_mask(nthw_mac_pcs_t *p);
+uint32_t nthw_mac_pcs_get_fld_lane_lock_lock(nthw_mac_pcs_t *p);
+uint32_t nthw_mac_pcs_get_fld_lane_lock_lock_mask(nthw_mac_pcs_t *p);
+
+#endif /* NTHW_MAC_PCS_H_ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_mac_pcs_xxv.c b/drivers/net/ntnic/nthw/core/nthw_mac_pcs_xxv.c
new file mode 100644
index 0000000000..d8e1f0de5d
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_mac_pcs_xxv.c
@@ -0,0 +1,1631 @@
+/* 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_mac_pcs_xxv.h"
+
+static void nthw_mac_pcs_xxv_field_set_or_clr_flush(const nt_field_t *f, bool set)
+{
+	if (f) {
+		field_get_updated(f);
+		if (set)
+			field_set_flush(f);
+		else
+			field_clr_flush(f);
+	}
+}
+
+nthw_mac_pcs_xxv_t *nthw_mac_pcs_xxv_new(void)
+{
+	nthw_mac_pcs_xxv_t *p = malloc(sizeof(nthw_mac_pcs_xxv_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_mac_pcs_xxv_t));
+	return p;
+}
+
+void nthw_mac_pcs_xxv_delete(nthw_mac_pcs_xxv_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_mac_pcs_xxv_t));
+		free(p);
+	}
+}
+
+uint8_t nthw_mac_pcs_xxv_get_port_no(const nthw_mac_pcs_xxv_t *p, uint8_t index)
+{
+	return index == 0 ? (uint8_t)p->mn_instance : index;
+}
+
+void nthw_mac_pcs_xxv_set_port_no(nthw_mac_pcs_xxv_t *p, uint8_t port_no)
+{
+	p->m_port_no = port_no;
+}
+
+void nthw_mac_pcs_xxv_get_link_summary(nthw_mac_pcs_xxv_t *p,
+	uint32_t *p_abs, uint32_t *p_nt_phy_link_state,
+	uint32_t *p_lh_abs, uint32_t *p_ll_nt_phy_link_state, uint32_t *p_link_down_cnt,
+	uint32_t *p_nim_interr, uint32_t *p_lh_local_fault, uint32_t *p_lh_remote_fault,
+	uint32_t *p_lh_internal_local_fault, uint32_t *p_lh_received_local_fault,
+	uint8_t index)
+{
+	struct nthw_mac_pcs_xxv__registers_fields *r =
+			&p->regs[index]; /* register and fields */
+
+	assert(p);
+
+	register_update(r->mp_reg_link_summary);
+	if (p_abs)
+		*p_abs = field_get_val32(r->mp_fld_link_summary_abs);
+	if (p_nt_phy_link_state) {
+		*p_nt_phy_link_state =
+			field_get_val32(r->mp_fld_link_summary_nt_phy_link_state);
+	}
+	if (p_lh_abs)
+		*p_lh_abs = field_get_val32(r->mp_fld_link_summary_lh_abs);
+	if (p_ll_nt_phy_link_state) {
+		*p_ll_nt_phy_link_state =
+			field_get_val32(r->mp_fld_link_summary_ll_nt_phy_link_state);
+	}
+	if (p_link_down_cnt)
+		*p_link_down_cnt = field_get_val32(r->mp_fld_link_summary_link_down_cnt);
+	if (p_nim_interr)
+		*p_nim_interr = field_get_val32(r->mp_fld_link_summary_nim_interr);
+	if (p_lh_local_fault) {
+		*p_lh_local_fault =
+			field_get_val32(r->mp_fld_link_summary_lh_local_fault);
+	}
+	if (p_lh_remote_fault) {
+		*p_lh_remote_fault =
+			field_get_val32(r->mp_fld_link_summary_lh_remote_fault);
+	}
+	if (p_lh_internal_local_fault) {
+		*p_lh_internal_local_fault =
+			field_get_val32(r->mp_fld_link_summary_lh_internal_local_fault);
+	}
+	if (p_lh_received_local_fault) {
+		*p_lh_received_local_fault =
+			field_get_val32(r->mp_fld_link_summary_lh_received_local_fault);
+	}
+}
+
+void nthw_mac_pcs_xxv_set_tx_enable(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_core_conf_tx_enable;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_rx_enable(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_core_conf_rx_enable;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_rx_force_resync(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_core_conf_rx_force_resync;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_reset_rx_gt_data(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_sub_rst_rx_gt_data;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_reset_tx_gt_data(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_sub_rst_tx_gt_data;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_reset_an_lt(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_sub_rst_an_lt;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_reset_speed_ctrl(nthw_mac_pcs_xxv_t *p, bool enable,
+				  uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_sub_rst_speed_ctrl;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_tx_send_rfi(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_core_conf_tx_send_rfi;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_tx_send_lfi(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_core_conf_tx_send_lfi;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_tx_send_lfi_tx_send_rfi(nthw_mac_pcs_xxv_t *p, bool enable_lfi,
+		bool enable_rfi, uint8_t index)
+{
+	register_update(p->regs[index].mp_reg_core_conf);
+	field_set_val32(p->regs[index].mp_fld_core_conf_tx_send_lfi, enable_lfi);
+	field_set_val32(p->regs[index].mp_fld_core_conf_tx_send_rfi, enable_rfi);
+	register_flush(p->regs[index].mp_reg_core_conf, 1);
+}
+
+bool nthw_mac_pcs_xxv_is_dfe_enabled(nthw_mac_pcs_xxv_t *p, uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_gty_ctl_rx_lpm_en;
+
+	/* NOTE: DFE is enabled when LPM is disabled */
+	return !field_get_updated(f);
+}
+
+void nthw_mac_pcs_xxv_set_dfe(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index)
+{
+	const nt_field_t *f = p->regs[index].mp_fld_gty_ctl_rx_lpm_en;
+	const bool set_dfe =
+		!enable; /* NOTE: Enable DFE mode means setting LPM = 0 */
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, set_dfe);
+
+	/* Toggle GTY_CTL_RX->EQUA_RST to 1 and 0 to assert new LPM_EN setting */
+	f = p->regs[index].mp_fld_gty_ctl_rx_equa_rst;
+	field_get_updated(f);
+	field_set_val_flush32(f, 1); /* Assert    GTH SOFT RESET */
+	field_get_updated(f);
+	field_set_val_flush32(f, 0); /* De-Assert GTH SOFT RESET */
+	field_get_updated(f);
+}
+
+void nthw_mac_pcs_xxv_set_rx_gty_polarity(nthw_mac_pcs_xxv_t *p, bool enable,
+				    uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_gty_ctl_rx_polarity;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_tx_gty_polarity(nthw_mac_pcs_xxv_t *p, bool enable,
+				    uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_gty_ctl_tx_polarity;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_tx_gty_inhibit(nthw_mac_pcs_xxv_t *p, bool enable,
+				   uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_gty_ctl_tx_inhibit;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_host_loopback(nthw_mac_pcs_xxv_t *p, bool enable,
+				   uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_gty_loop_gt_loop;
+
+	field_get_updated(f);
+	field_set_val_flush32(f, enable ? 2U : 0U);
+}
+
+void nthw_mac_pcs_xxv_set_line_loopback(nthw_mac_pcs_xxv_t *p, bool enable,
+				   uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_core_conf_line_loopback;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+bool nthw_mac_pcs_xxv_is_user_rx_rst(nthw_mac_pcs_xxv_t *p, uint8_t index)
+{
+	return field_get_updated(p->regs[index].mp_fld_sub_rst_status_user_rx_rst);
+}
+
+bool nthw_mac_pcs_xxv_is_user_tx_rst(nthw_mac_pcs_xxv_t *p, uint8_t index)
+{
+	return field_get_updated(p->regs[index].mp_fld_sub_rst_status_user_tx_rst);
+}
+
+/*
+ * QPLL lock signal.
+ * For cores capable of 10G only, there are only 1 QPLL. For cores capable of
+ * 10G/25G, there are 2 QPLLs.
+ */
+bool nthw_mac_pcs_xxv_is_qpll_lock(nthw_mac_pcs_xxv_t *p, uint8_t index)
+{
+	return (field_get_updated(p->regs[index].mp_fld_sub_rst_status_qpll_lock) ==
+		3);
+}
+
+bool nthw_mac_pcs_xxv_is_sub_rst_ready(nthw_mac_pcs_xxv_t *p, uint8_t index)
+{
+	return (nthw_mac_pcs_xxv_is_qpll_lock(p, index) &&
+		!nthw_mac_pcs_xxv_is_user_rx_rst(p, index) &&
+		!nthw_mac_pcs_xxv_is_user_tx_rst(p, index));
+}
+
+bool nthw_mac_pcs_xxv_is_aneg_enabled(nthw_mac_pcs_xxv_t *p, uint8_t index)
+{
+	return field_get_updated(p->regs[index].mp_fld_aneg_config_enable);
+}
+
+void nthw_mac_pcs_xxv_set_tx_send_idle(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_core_conf_tx_send_idle;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_tx_ins_fcs(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_core_conf_tx_ins_fcs;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+bool nthw_mac_pcs_xxv_get_link_speed10_g(nthw_mac_pcs_xxv_t *p, uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_link_speed10_g;
+
+	return field_get_updated(f) != 0; /* 0 = 25g, 1 = 10g */
+}
+
+void nthw_mac_pcs_xxv_set_link_speed10_g(nthw_mac_pcs_xxv_t *p, bool enable,
+				   uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_link_speed10_g;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_link_speed_toggle(nthw_mac_pcs_xxv_t *p, uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_link_speed_toggle;
+
+	field_get_updated(f);
+	field_set_flush(f);
+}
+
+void nthw_mac_pcs_xxv_set_rs_fec_conf_rs_fec_enable(nthw_mac_pcs_xxv_t *p, bool enable,
+		uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_rs_fec_conf_rs_fec_enable;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_led_mode(nthw_mac_pcs_xxv_t *p, uint8_t mode, uint8_t index)
+{
+	const nt_field_t *const f =
+		p->regs[index].mp_field_debounce_ctrl_nt_port_ctrl;
+
+	field_get_updated(f);
+	field_set_val_flush32(f, mode);
+}
+
+void nthw_mac_pcs_xxv_set_rx_mac_pcs_rst(nthw_mac_pcs_xxv_t *p, bool enable,
+				  uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_sub_rst_rx_mac_pcs;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_tx_mac_pcs_rst(nthw_mac_pcs_xxv_t *p, bool enable,
+				  uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_sub_rst_tx_mac_pcs;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_reset_fec_counters(nthw_mac_pcs_xxv_t *p, uint8_t index)
+{
+	register_update(p->regs[index].mp_reg_rs_fec_ccw);
+	register_update(p->regs[index].mp_reg_rs_fec_ucw);
+
+	if (field_get_val32(p->regs[index].mp_field_reg_rs_fec_ccw_reg_rs_fec_ccw_cnt)) {
+		NT_LOG(DBG, NTHW, "Port %u: FEC_CW_CNT: %u", p->m_port_no,
+		       field_get_val32(p->regs[index].mp_field_reg_rs_fec_ccw_reg_rs_fec_ccw_cnt));
+	}
+	if (field_get_val32(p->regs[index].mp_field_reg_rs_fec_ucw_reg_rs_fec_ucw_cnt)) {
+		NT_LOG(DBG, NTHW, "Port %u: FEC_UCW_CNT: %u", p->m_port_no,
+		       field_get_val32(p->regs[index].mp_field_reg_rs_fec_ucw_reg_rs_fec_ucw_cnt));
+	}
+}
+
+void nthw_mac_pcs_xxv_set_timestamp_comp_rx(nthw_mac_pcs_xxv_t *p, uint16_t rx_dly,
+				      uint8_t index)
+{
+	field_get_updated(p->regs[index].mp_field_timestamp_comp_rx_dly);
+	field_set_val_flush32(p->regs[index].mp_field_timestamp_comp_rx_dly, rx_dly);
+}
+
+void nthw_mac_pcs_xxv_set_timestamp_comp_tx(nthw_mac_pcs_xxv_t *p, uint16_t tx_dly,
+				      uint8_t index)
+{
+	field_get_updated(p->regs[index].mp_field_timestamp_comp_tx_dly);
+	field_set_val_flush32(p->regs[index].mp_field_timestamp_comp_tx_dly, tx_dly);
+}
+
+void nthw_mac_pcs_xxv_set_ts_at_eop(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_core_conf_ts_at_eop;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_gty_diff(nthw_mac_pcs_xxv_t *p, uint8_t value, uint8_t index)
+{
+	field_get_updated(p->regs[index].mp_field_gty_gty_diff_ctl_tx_diff_ctl);
+	field_set_val_flush32(p->regs[index].mp_field_gty_gty_diff_ctl_tx_diff_ctl,
+			    value);
+}
+
+void nthw_mac_pcs_xxv_set_gty_pre(nthw_mac_pcs_xxv_t *p, uint8_t value, uint8_t index)
+{
+	field_get_updated(p->regs[index].mp_field_gty_pre_cursor_tx_pre_csr);
+	field_set_val_flush32(p->regs[index].mp_field_gty_pre_cursor_tx_pre_csr, value);
+}
+
+void nthw_mac_pcs_xxv_set_gty_post(nthw_mac_pcs_xxv_t *p, uint8_t value, uint8_t index)
+{
+	field_get_updated(p->regs[index].mp_field_gty_post_cursor_tx_post_csr);
+	field_set_val_flush32(p->regs[index].mp_field_gty_post_cursor_tx_post_csr,
+			    value);
+}
+
+void nthw_mac_pcs_xxv_set_lt_conf_enable(nthw_mac_pcs_xxv_t *p, bool enable,
+				   uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_lt_conf_enable;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_aneg_config_fec91_request(nthw_mac_pcs_xxv_t *p, bool enable,
+		uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_aneg_config_fec91_request;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_aneg_config_rs_fec_request(nthw_mac_pcs_xxv_t *p, bool enable,
+		uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_aneg_config_rs_fec_request;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_aneg_config_fec74_request(nthw_mac_pcs_xxv_t *p, bool enable,
+		uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_aneg_config_fec74_request;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_aneg_config_enable(nthw_mac_pcs_xxv_t *p, bool enable,
+				       uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_aneg_config_enable;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_aneg_config_bypass(nthw_mac_pcs_xxv_t *p, bool enable,
+				       uint8_t index)
+{
+	const nt_field_t *const f = p->regs[index].mp_fld_aneg_config_bypass;
+
+	nthw_mac_pcs_xxv_field_set_or_clr_flush(f, enable);
+}
+
+void nthw_mac_pcs_xxv_set_dac_mode(nthw_mac_pcs_xxv_t *p, uint8_t dac_mode,
+			      uint8_t index)
+{
+	if (dac_mode == NTHW_MAC_PCS_XXV_DAC_OFF) {
+		nthw_mac_pcs_xxv_reset_an_lt(p, false, index);
+		nthw_mac_pcs_xxv_set_aneg_config_enable(p, false, index);
+		nthw_mac_pcs_xxv_set_aneg_config_bypass(p, true, index);
+		nthw_mac_pcs_xxv_set_lt_conf_enable(p, false, index);
+		nthw_mac_pcs_xxv_set_rx_mac_pcs_rst(p, true, index);
+		nthw_mac_pcs_xxv_set_tx_mac_pcs_rst(p, true, index);
+		nthw_mac_pcs_xxv_reset_rx_gt_data(p, true, index);
+		nthw_mac_pcs_xxv_reset_tx_gt_data(p, true, index);
+		nthw_mac_pcs_xxv_set_rx_mac_pcs_rst(p, false, index);
+		nthw_mac_pcs_xxv_set_tx_mac_pcs_rst(p, false, index);
+		nthw_mac_pcs_xxv_reset_rx_gt_data(p, false, index);
+		nthw_mac_pcs_xxv_reset_tx_gt_data(p, false, index);
+
+		return;
+	}
+	assert(0); /* If you end up here you need to implement other DAC modes */
+}
+
+bool nthw_mac_pcs_xxv_get_ll_rx_fec74_lock(nthw_mac_pcs_xxv_t *p, uint8_t index)
+{
+	return field_get_updated(p->regs[index].mp_fld_link_summary_ll_rx_fec74_lock);
+}
+
+bool nthw_mac_pcs_xxv_get_ll_rx_rsfec_lane_alignment(nthw_mac_pcs_xxv_t *p, uint8_t index)
+{
+	return field_get_updated(p->regs[index].mp_fld_link_summary_ll_rx_rsfec_lane_alignment);
+}
+
+int nthw_mac_pcs_xxv_init(nthw_mac_pcs_xxv_t *p, nt_fpga_t *p_fpga, int n_instance,
+		       int n_channels)
+{
+	nt_module_t *p_mod = fpga_query_module(p_fpga, MOD_MAC_PCS_XXV, n_instance);
+	const char *const p_adapter_id_str = p_fpga->p_fpga_info->mp_adapter_id_str;
+	const bool m_mac8x10_g = false;
+	nt_module_t *module = p_mod;
+	uint64_t n_module_version_packed64 = -1;
+	nt_register_t *r;
+	nt_register_t *(*get_register)(nt_module_t *, uint32_t) =
+		module_get_register;
+	nt_field_t *(*get_field)(const nt_register_t *, uint32_t) =
+		register_get_field;
+	nt_field_t *(*query_field)(const nt_register_t *, uint32_t) =
+		register_query_field;
+	struct nthw_mac_pcs_xxv__registers_fields *rf;
+
+	if (p == NULL)
+		return (p_mod == NULL ? -1 : 0);
+
+	if (p_mod == NULL) {
+		NT_LOG(ERR, NTHW,
+		       "%s: MAC_PCS_XXV instance=%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_mac_pcs_xxv = p_mod;
+
+	memset(p->regs, 0, sizeof(p->regs));
+
+	n_module_version_packed64 = module_get_version_packed64(p->mp_mod_mac_pcs_xxv);
+	switch (n_module_version_packed64) {
+	case (0UL << 32) | 0UL: /* 0.0 */
+	case (0UL << 32) | 1UL: /* 0.1 */
+	case (0UL << 32) | 2UL: /* 0.2 */
+		NT_LOG(DBG, NTHW,
+		       "%s: MAC_PCS_XXV instance=%d: version=0x%08lX\n",
+		       p_adapter_id_str, p->mn_instance, n_module_version_packed64);
+		break;
+	default:
+		NT_LOG(ERR, NTHW,
+		       "%s: MAC_PCS_XXV instance=%d: version=0x%08lX: unsupported module version\n",
+		       p_adapter_id_str, p->mn_instance, n_module_version_packed64);
+		return -1;
+	}
+
+	assert(n_channels == 1 || n_channels == 2 || n_channels == 4);
+
+	/* Register MAC_PCS_XXV_CORE_CONF_0 -- MAC_PCS_XXV_CORE_CONF_3 */
+	if (n_channels < 4) {
+		/* Initialize regs/fields for sub-module/channel 0 */
+		rf = &p->regs[0];
+		r = get_register(module, MAC_PCS_XXV_CORE_CONF_0);
+
+		rf->mp_reg_core_conf = r;
+		rf->mp_fld_core_conf_rx_enable =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_0_RX_ENABLE);
+		rf->mp_fld_core_conf_rx_force_resync =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_0_RX_FORCE_RESYNC);
+		rf->mp_fld_core_conf_tx_enable =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_0_TX_ENABLE);
+		rf->mp_fld_core_conf_tx_ins_fcs =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_0_TX_INS_FCS);
+		rf->mp_fld_core_conf_tx_ign_fcs =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_0_TX_IGN_FCS);
+		rf->mp_fld_core_conf_tx_send_lfi =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_0_TX_SEND_LFI);
+		rf->mp_fld_core_conf_tx_send_rfi =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_0_TX_SEND_RFI);
+		rf->mp_fld_core_conf_tx_send_idle =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_0_TX_SEND_IDLE);
+		rf->mp_fld_core_conf_inline_mode =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_0_INLINE_MODE);
+		rf->mp_fld_core_conf_line_loopback =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_0_LINE_LOOPBACK);
+		rf->mp_fld_core_conf_ts_at_eop =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_0_TS_AT_EOP);
+	}
+
+	if (n_channels >= 2) {
+		/* Initialize regs/fields for sub-module/channel 1 */
+		rf = &p->regs[1];
+		r = get_register(module, MAC_PCS_XXV_CORE_CONF_1);
+
+		rf->mp_reg_core_conf = r;
+		rf->mp_fld_core_conf_rx_enable =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_1_RX_ENABLE);
+		rf->mp_fld_core_conf_rx_force_resync =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_1_RX_FORCE_RESYNC);
+		rf->mp_fld_core_conf_tx_enable =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_1_TX_ENABLE);
+		rf->mp_fld_core_conf_tx_ins_fcs =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_1_TX_INS_FCS);
+		rf->mp_fld_core_conf_tx_ign_fcs =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_1_TX_IGN_FCS);
+		rf->mp_fld_core_conf_tx_send_lfi =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_1_TX_SEND_LFI);
+		rf->mp_fld_core_conf_tx_send_rfi =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_1_TX_SEND_RFI);
+		rf->mp_fld_core_conf_tx_send_idle =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_1_TX_SEND_IDLE);
+		rf->mp_fld_core_conf_inline_mode =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_1_INLINE_MODE);
+		rf->mp_fld_core_conf_line_loopback =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_1_LINE_LOOPBACK);
+		rf->mp_fld_core_conf_ts_at_eop =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_1_TS_AT_EOP);
+	}
+
+	if (n_channels == 4) {
+		/* Initialize regs/fields for sub-module/channel 2 */
+		rf = &p->regs[2];
+		r = get_register(module, MAC_PCS_XXV_CORE_CONF_2);
+
+		rf->mp_reg_core_conf = r;
+		rf->mp_fld_core_conf_rx_enable =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_2_RX_ENABLE);
+		rf->mp_fld_core_conf_rx_force_resync =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_2_RX_FORCE_RESYNC);
+		rf->mp_fld_core_conf_tx_enable =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_2_TX_ENABLE);
+		rf->mp_fld_core_conf_tx_ins_fcs =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_2_TX_INS_FCS);
+		rf->mp_fld_core_conf_tx_ign_fcs =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_2_TX_IGN_FCS);
+		rf->mp_fld_core_conf_tx_send_lfi =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_2_TX_SEND_LFI);
+		rf->mp_fld_core_conf_tx_send_rfi =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_2_TX_SEND_RFI);
+		rf->mp_fld_core_conf_tx_send_idle =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_2_TX_SEND_IDLE);
+		rf->mp_fld_core_conf_inline_mode =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_2_INLINE_MODE);
+		rf->mp_fld_core_conf_line_loopback =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_2_LINE_LOOPBACK);
+		rf->mp_fld_core_conf_ts_at_eop =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_2_TS_AT_EOP);
+
+		/* Initialize regs/fields for sub-module/channel 3 */
+		rf = &p->regs[3];
+		r = get_register(module, MAC_PCS_XXV_CORE_CONF_3);
+
+		rf->mp_reg_core_conf = r;
+		rf->mp_fld_core_conf_rx_enable =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_3_RX_ENABLE);
+		rf->mp_fld_core_conf_rx_force_resync =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_3_RX_FORCE_RESYNC);
+		rf->mp_fld_core_conf_tx_enable =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_3_TX_ENABLE);
+		rf->mp_fld_core_conf_tx_ins_fcs =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_3_TX_INS_FCS);
+		rf->mp_fld_core_conf_tx_ign_fcs =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_3_TX_IGN_FCS);
+		rf->mp_fld_core_conf_tx_send_lfi =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_3_TX_SEND_LFI);
+		rf->mp_fld_core_conf_tx_send_rfi =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_3_TX_SEND_RFI);
+		rf->mp_fld_core_conf_tx_send_idle =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_3_TX_SEND_IDLE);
+		rf->mp_fld_core_conf_inline_mode =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_3_INLINE_MODE);
+		rf->mp_fld_core_conf_line_loopback =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_3_LINE_LOOPBACK);
+		rf->mp_fld_core_conf_ts_at_eop =
+			get_field(r, MAC_PCS_XXV_CORE_CONF_3_TS_AT_EOP);
+	}
+
+	/*
+	 * Registers MAC_PCS_XXV_ANEG_CONFIG_0 -- MAC_PCS_XXV_ANEG_CONFIG_3
+	 * and       MAC_PCS_XXV_ANEG_ABILITY_0 -- MAC_PCS_XXV_ANEG_ABILITY_3
+	 * and       MAC_PCS_XXV_LT_CONF_0 -- MAC_PCS_XXV_LT_CONF_3
+	 */
+	if (!m_mac8x10_g && n_channels < 4) {
+		/* 2 x 10 25 G */
+		/* ANEG_CONFIG */
+		rf = &p->regs[0];
+
+		r = get_register(module, MAC_PCS_XXV_ANEG_CONFIG_0);
+		rf->mp_reg_aneg_config = r;
+		rf->mp_fld_aneg_config_enable =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_ENABLE);
+		rf->mp_fld_aneg_config_bypass =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_BYPASS);
+		rf->mp_fld_aneg_config_restart =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_RESTART);
+		rf->mp_fld_aneg_config_pseudo =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_PSEUDO);
+		rf->mp_fld_aneg_config_nonce_seed =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_NONCE_SEED);
+		rf->mp_fld_aneg_config_remote_fault =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_REMOTE_FAULT);
+		rf->mp_fld_aneg_config_pause =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_PAUSE);
+		rf->mp_fld_aneg_config_asmdir =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_ASMDIR);
+		rf->mp_fld_aneg_config_fec74_request10_g =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_FEC74_REQUEST_10G);
+		rf->mp_fld_aneg_config_hide_fec74 =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_HIDE_FEC74);
+		rf->mp_fld_aneg_config_fec74_request =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_FEC74_REQUEST);
+		rf->mp_fld_aneg_config_fec91_request =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_FEC91_REQUEST);
+		rf->mp_fld_aneg_config_fec91_ability =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_FEC91_ABILITY);
+		rf->mp_fld_aneg_config_rs_fec_request =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_RS_FEC_REQUEST);
+		rf->mp_fld_aneg_config_sw_fec_overwrite =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_SW_FEC_OVERWRITE);
+		rf->mp_fld_aneg_config_sw_speed_overwrite =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_0_SW_SPEED_OVERWRITE);
+
+		/* ANEG_ABILITY */
+		r = get_register(module, MAC_PCS_XXV_ANEG_ABILITY_0);
+		rf->mp_reg_aneg_ability = r;
+
+		rf->mp_fld_aneg_ability25_g_base_cr =
+			query_field(r, MAC_PCS_XXV_ANEG_ABILITY_0_BASE25G_CR);
+		if (!rf->mp_fld_aneg_ability25_g_base_cr) {
+			rf->mp_fld_aneg_ability25_g_base_cr =
+				query_field(r, MAC_PCS_XXV_ANEG_ABILITY_0_25GBASE_CR);
+		}
+
+		rf->mp_fld_aneg_ability25_g_base_crs =
+			query_field(r, MAC_PCS_XXV_ANEG_ABILITY_0_BASE25G_CR_S);
+		if (!rf->mp_fld_aneg_ability25_g_base_crs) {
+			rf->mp_fld_aneg_ability25_g_base_crs =
+				query_field(r, MAC_PCS_XXV_ANEG_ABILITY_0_25GBASE_CR_S);
+		}
+
+		rf->mp_fld_aneg_ability25_g_base_cr1 =
+			query_field(r, MAC_PCS_XXV_ANEG_ABILITY_0_BASE25G_CR1);
+		if (!rf->mp_fld_aneg_ability25_g_base_cr1) {
+			rf->mp_fld_aneg_ability25_g_base_cr1 =
+				query_field(r, MAC_PCS_XXV_ANEG_ABILITY_0_25GBASE_CR1);
+		}
+
+		/* LT_CONF */
+		r = get_register(module, MAC_PCS_XXV_LT_CONF_0);
+		rf->mp_reg_lt_conf = r;
+		rf->mp_fld_lt_conf_enable =
+			get_field(r, MAC_PCS_XXV_LT_CONF_0_ENABLE);
+		rf->mp_fld_lt_conf_restart =
+			get_field(r, MAC_PCS_XXV_LT_CONF_0_RESTART);
+		rf->mp_fld_lt_conf_seed = get_field(r, MAC_PCS_XXV_LT_CONF_0_SEED);
+	}
+
+	if (!m_mac8x10_g && n_channels >= 2) {
+		/* 2 x 10 25 G */
+		/* ANEG_CONFIG */
+
+		/* Initialize regs/fields for sub-module/channel 1 */
+		rf = &p->regs[1];
+
+		r = get_register(module, MAC_PCS_XXV_ANEG_CONFIG_1);
+		rf->mp_reg_aneg_config = r;
+		rf->mp_fld_aneg_config_enable =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_ENABLE);
+		rf->mp_fld_aneg_config_bypass =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_BYPASS);
+		rf->mp_fld_aneg_config_restart =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_RESTART);
+		rf->mp_fld_aneg_config_pseudo =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_PSEUDO);
+		rf->mp_fld_aneg_config_nonce_seed =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_NONCE_SEED);
+		rf->mp_fld_aneg_config_remote_fault =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_REMOTE_FAULT);
+		rf->mp_fld_aneg_config_pause =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_PAUSE);
+		rf->mp_fld_aneg_config_asmdir =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_ASMDIR);
+		rf->mp_fld_aneg_config_fec74_request10_g =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_FEC74_REQUEST_10G);
+		rf->mp_fld_aneg_config_hide_fec74 =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_HIDE_FEC74);
+		rf->mp_fld_aneg_config_fec74_request =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_FEC74_REQUEST);
+		rf->mp_fld_aneg_config_fec91_request =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_FEC91_REQUEST);
+		rf->mp_fld_aneg_config_fec91_ability =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_FEC91_ABILITY);
+		rf->mp_fld_aneg_config_rs_fec_request =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_RS_FEC_REQUEST);
+		rf->mp_fld_aneg_config_sw_fec_overwrite =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_SW_FEC_OVERWRITE);
+		rf->mp_fld_aneg_config_sw_speed_overwrite =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_1_SW_SPEED_OVERWRITE);
+
+		/* ANEG_ABILITY */
+		r = get_register(module, MAC_PCS_XXV_ANEG_ABILITY_1);
+		rf->mp_reg_aneg_ability = r;
+
+		rf->mp_fld_aneg_ability25_g_base_cr =
+			query_field(r, MAC_PCS_XXV_ANEG_ABILITY_1_BASE25G_CR);
+		if (!rf->mp_fld_aneg_ability25_g_base_cr) {
+			rf->mp_fld_aneg_ability25_g_base_cr =
+				get_field(r, MAC_PCS_XXV_ANEG_ABILITY_1_25GBASE_CR);
+		}
+
+		rf->mp_fld_aneg_ability25_g_base_crs =
+			query_field(r, MAC_PCS_XXV_ANEG_ABILITY_1_BASE25G_CR_S);
+		if (!rf->mp_fld_aneg_ability25_g_base_crs) {
+			rf->mp_fld_aneg_ability25_g_base_crs =
+				get_field(r, MAC_PCS_XXV_ANEG_ABILITY_1_25GBASE_CR_S);
+		}
+
+		rf->mp_fld_aneg_ability25_g_base_cr1 =
+			query_field(r, MAC_PCS_XXV_ANEG_ABILITY_1_BASE25G_CR1);
+		if (!rf->mp_fld_aneg_ability25_g_base_cr1) {
+			rf->mp_fld_aneg_ability25_g_base_cr1 =
+				get_field(r, MAC_PCS_XXV_ANEG_ABILITY_1_25GBASE_CR1);
+		}
+
+		/* LT_CONF */
+		r = get_register(module, MAC_PCS_XXV_LT_CONF_1);
+		rf->mp_reg_lt_conf = r;
+		rf->mp_fld_lt_conf_enable =
+			get_field(r, MAC_PCS_XXV_LT_CONF_1_ENABLE);
+		rf->mp_fld_lt_conf_restart =
+			get_field(r, MAC_PCS_XXV_LT_CONF_1_RESTART);
+		rf->mp_fld_lt_conf_seed = get_field(r, MAC_PCS_XXV_LT_CONF_1_SEED);
+	}
+
+	if (!m_mac8x10_g && n_channels == 4) {
+		/* Initialize regs/fields for sub-module/channel 2 */
+		rf = &p->regs[2];
+
+		r = get_register(module, MAC_PCS_XXV_ANEG_CONFIG_2);
+		rf->mp_reg_aneg_config = r;
+		rf->mp_fld_aneg_config_enable =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_ENABLE);
+		rf->mp_fld_aneg_config_bypass =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_BYPASS);
+		rf->mp_fld_aneg_config_restart =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_RESTART);
+		rf->mp_fld_aneg_config_pseudo =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_PSEUDO);
+		rf->mp_fld_aneg_config_nonce_seed =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_NONCE_SEED);
+		rf->mp_fld_aneg_config_remote_fault =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_REMOTE_FAULT);
+		rf->mp_fld_aneg_config_pause =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_PAUSE);
+		rf->mp_fld_aneg_config_asmdir =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_ASMDIR);
+		rf->mp_fld_aneg_config_fec74_request10_g =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_FEC74_REQUEST_10G);
+		rf->mp_fld_aneg_config_hide_fec74 =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_HIDE_FEC74);
+		rf->mp_fld_aneg_config_fec74_request =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_FEC74_REQUEST);
+		rf->mp_fld_aneg_config_fec91_request =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_FEC91_REQUEST);
+		rf->mp_fld_aneg_config_fec91_ability =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_FEC91_ABILITY);
+		rf->mp_fld_aneg_config_rs_fec_request =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_RS_FEC_REQUEST);
+		rf->mp_fld_aneg_config_sw_fec_overwrite =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_SW_FEC_OVERWRITE);
+		rf->mp_fld_aneg_config_sw_speed_overwrite =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_2_SW_SPEED_OVERWRITE);
+
+		/* ANEG_ABILITY */
+		r = get_register(module, MAC_PCS_XXV_ANEG_ABILITY_2);
+		rf->mp_reg_aneg_ability = r;
+		rf->mp_fld_aneg_ability25_g_base_cr =
+			get_field(r, MAC_PCS_XXV_ANEG_ABILITY_2_25GBASE_CR);
+		rf->mp_fld_aneg_ability25_g_base_crs =
+			get_field(r, MAC_PCS_XXV_ANEG_ABILITY_2_25GBASE_CR_S);
+		rf->mp_fld_aneg_ability25_g_base_cr1 =
+			get_field(r, MAC_PCS_XXV_ANEG_ABILITY_2_25GBASE_CR1);
+
+		/* LT_CONF */
+		r = get_register(module, MAC_PCS_XXV_LT_CONF_2);
+		rf->mp_reg_lt_conf = r;
+		rf->mp_fld_lt_conf_enable =
+			get_field(r, MAC_PCS_XXV_LT_CONF_2_ENABLE);
+		rf->mp_fld_lt_conf_restart =
+			get_field(r, MAC_PCS_XXV_LT_CONF_2_RESTART);
+		rf->mp_fld_lt_conf_seed = get_field(r, MAC_PCS_XXV_LT_CONF_2_SEED);
+
+		/* Initialize regs/fields for sub-module/channel 3 */
+		rf = &p->regs[3];
+
+		r = get_register(module, MAC_PCS_XXV_ANEG_CONFIG_3);
+		rf->mp_reg_aneg_config = r;
+		rf->mp_fld_aneg_config_enable =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_ENABLE);
+		rf->mp_fld_aneg_config_bypass =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_BYPASS);
+		rf->mp_fld_aneg_config_restart =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_RESTART);
+		rf->mp_fld_aneg_config_pseudo =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_PSEUDO);
+		rf->mp_fld_aneg_config_nonce_seed =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_NONCE_SEED);
+		rf->mp_fld_aneg_config_remote_fault =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_REMOTE_FAULT);
+		rf->mp_fld_aneg_config_pause =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_PAUSE);
+		rf->mp_fld_aneg_config_asmdir =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_ASMDIR);
+		rf->mp_fld_aneg_config_fec74_request10_g =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_FEC74_REQUEST_10G);
+		rf->mp_fld_aneg_config_hide_fec74 =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_HIDE_FEC74);
+		rf->mp_fld_aneg_config_fec74_request =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_FEC74_REQUEST);
+		rf->mp_fld_aneg_config_fec91_request =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_FEC91_REQUEST);
+		rf->mp_fld_aneg_config_fec91_ability =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_FEC91_ABILITY);
+		rf->mp_fld_aneg_config_rs_fec_request =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_RS_FEC_REQUEST);
+		rf->mp_fld_aneg_config_sw_fec_overwrite =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_SW_FEC_OVERWRITE);
+		rf->mp_fld_aneg_config_sw_speed_overwrite =
+			get_field(r, MAC_PCS_XXV_ANEG_CONFIG_3_SW_SPEED_OVERWRITE);
+
+		/* ANEG_ABILITY */
+		r = get_register(module, MAC_PCS_XXV_ANEG_ABILITY_3);
+		rf->mp_reg_aneg_ability = r;
+		rf->mp_fld_aneg_ability25_g_base_cr =
+			get_field(r, MAC_PCS_XXV_ANEG_ABILITY_3_25GBASE_CR);
+		rf->mp_fld_aneg_ability25_g_base_crs =
+			get_field(r, MAC_PCS_XXV_ANEG_ABILITY_3_25GBASE_CR_S);
+		rf->mp_fld_aneg_ability25_g_base_cr1 =
+			get_field(r, MAC_PCS_XXV_ANEG_ABILITY_3_25GBASE_CR1);
+
+		/* LT_CONF */
+		r = get_register(module, MAC_PCS_XXV_LT_CONF_3);
+		rf->mp_reg_lt_conf = r;
+		rf->mp_fld_lt_conf_enable =
+			get_field(r, MAC_PCS_XXV_LT_CONF_3_ENABLE);
+		rf->mp_fld_lt_conf_restart =
+			get_field(r, MAC_PCS_XXV_LT_CONF_3_RESTART);
+		rf->mp_fld_lt_conf_seed = get_field(r, MAC_PCS_XXV_LT_CONF_3_SEED);
+	}
+
+	/*
+	 * Registers MAC_PCS_XXV_SUB_RST_0 -- MAC_PCS_XXV_SUB_RST_3
+	 * and       MAC_PCS_XXV_SUB_RST_STATUS_0 -- MAC_PCS_XXV_SUB_RST_STATUS_3
+	 */
+	if (n_channels < 4) {
+		/* Initialize regs/fields for sub-module/channel 0 */
+		rf = &p->regs[0];
+		r = get_register(module, MAC_PCS_XXV_SUB_RST_0);
+
+		rf->mp_reg_sub_rst = r;
+		rf->mp_fld_sub_rst_rx_mac_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_0_RX_MAC_PCS);
+		rf->mp_fld_sub_rst_tx_mac_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_0_TX_MAC_PCS);
+		rf->mp_fld_sub_rst_rx_gt_data =
+			get_field(r, MAC_PCS_XXV_SUB_RST_0_RX_GT_DATA);
+		rf->mp_fld_sub_rst_tx_gt_data =
+			get_field(r, MAC_PCS_XXV_SUB_RST_0_TX_GT_DATA);
+		rf->mp_fld_sub_rst_rx_buf =
+			get_field(r, MAC_PCS_XXV_SUB_RST_0_RX_BUF);
+		rf->mp_fld_sub_rst_rx_pma =
+			get_field(r, MAC_PCS_XXV_SUB_RST_0_RX_PMA);
+		rf->mp_fld_sub_rst_tx_pma =
+			get_field(r, MAC_PCS_XXV_SUB_RST_0_TX_PMA);
+		rf->mp_fld_sub_rst_rx_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_0_RX_PCS);
+		rf->mp_fld_sub_rst_tx_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_0_TX_PCS);
+		rf->mp_fld_sub_rst_an_lt = get_field(r, MAC_PCS_XXV_SUB_RST_0_AN_LT);
+		rf->mp_fld_sub_rst_speed_ctrl =
+			query_field(r, MAC_PCS_XXV_SUB_RST_0_SPEED_CTRL);
+
+		r = get_register(module, MAC_PCS_XXV_SUB_RST_STATUS_0);
+		rf->mp_reg_sub_rst_status = r;
+		rf->mp_fld_sub_rst_status_user_rx_rst =
+			get_field(r, MAC_PCS_XXV_SUB_RST_STATUS_0_USER_RX_RST);
+		rf->mp_fld_sub_rst_status_user_tx_rst =
+			get_field(r, MAC_PCS_XXV_SUB_RST_STATUS_0_USER_TX_RST);
+		rf->mp_fld_sub_rst_status_qpll_lock =
+			get_field(r, MAC_PCS_XXV_SUB_RST_STATUS_0_QPLL_LOCK);
+	}
+
+	if (n_channels >= 2) {
+		/* Initialize regs/fields for sub-module/channel 1 */
+		rf = &p->regs[1];
+		r = get_register(module, MAC_PCS_XXV_SUB_RST_1);
+
+		rf->mp_reg_sub_rst = r;
+		rf->mp_fld_sub_rst_rx_mac_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_1_RX_MAC_PCS);
+		rf->mp_fld_sub_rst_tx_mac_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_1_TX_MAC_PCS);
+		rf->mp_fld_sub_rst_rx_gt_data =
+			get_field(r, MAC_PCS_XXV_SUB_RST_1_RX_GT_DATA);
+		rf->mp_fld_sub_rst_tx_gt_data =
+			get_field(r, MAC_PCS_XXV_SUB_RST_1_TX_GT_DATA);
+		rf->mp_fld_sub_rst_rx_buf =
+			get_field(r, MAC_PCS_XXV_SUB_RST_1_RX_BUF);
+		rf->mp_fld_sub_rst_rx_pma =
+			get_field(r, MAC_PCS_XXV_SUB_RST_1_RX_PMA);
+		rf->mp_fld_sub_rst_tx_pma =
+			get_field(r, MAC_PCS_XXV_SUB_RST_1_TX_PMA);
+		rf->mp_fld_sub_rst_rx_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_1_RX_PCS);
+		rf->mp_fld_sub_rst_tx_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_1_TX_PCS);
+		rf->mp_fld_sub_rst_an_lt = get_field(r, MAC_PCS_XXV_SUB_RST_1_AN_LT);
+		rf->mp_fld_sub_rst_speed_ctrl =
+			query_field(r, MAC_PCS_XXV_SUB_RST_1_SPEED_CTRL);
+
+		r = get_register(module, MAC_PCS_XXV_SUB_RST_STATUS_1);
+		rf->mp_reg_sub_rst_status = r;
+		rf->mp_fld_sub_rst_status_user_rx_rst =
+			get_field(r, MAC_PCS_XXV_SUB_RST_STATUS_1_USER_RX_RST);
+		rf->mp_fld_sub_rst_status_user_tx_rst =
+			get_field(r, MAC_PCS_XXV_SUB_RST_STATUS_1_USER_TX_RST);
+		rf->mp_fld_sub_rst_status_qpll_lock =
+			get_field(r, MAC_PCS_XXV_SUB_RST_STATUS_1_QPLL_LOCK);
+	}
+
+	if (n_channels == 4) {
+		/* Initialize regs/fields for sub-module/channel 2 */
+		rf = &p->regs[2];
+		r = get_register(module, MAC_PCS_XXV_SUB_RST_2);
+
+		rf->mp_reg_sub_rst = r;
+		rf->mp_fld_sub_rst_rx_mac_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_2_RX_MAC_PCS);
+		rf->mp_fld_sub_rst_tx_mac_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_2_TX_MAC_PCS);
+		rf->mp_fld_sub_rst_rx_gt_data =
+			get_field(r, MAC_PCS_XXV_SUB_RST_2_RX_GT_DATA);
+		rf->mp_fld_sub_rst_tx_gt_data =
+			get_field(r, MAC_PCS_XXV_SUB_RST_2_TX_GT_DATA);
+		rf->mp_fld_sub_rst_rx_buf =
+			get_field(r, MAC_PCS_XXV_SUB_RST_2_RX_BUF);
+		rf->mp_fld_sub_rst_rx_pma =
+			get_field(r, MAC_PCS_XXV_SUB_RST_2_RX_PMA);
+		rf->mp_fld_sub_rst_tx_pma =
+			get_field(r, MAC_PCS_XXV_SUB_RST_2_TX_PMA);
+		rf->mp_fld_sub_rst_rx_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_2_RX_PCS);
+		rf->mp_fld_sub_rst_tx_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_2_TX_PCS);
+		rf->mp_fld_sub_rst_an_lt = get_field(r, MAC_PCS_XXV_SUB_RST_2_AN_LT);
+		rf->mp_fld_sub_rst_speed_ctrl =
+			query_field(r, MAC_PCS_XXV_SUB_RST_2_SPEED_CTRL);
+
+		r = get_register(module, MAC_PCS_XXV_SUB_RST_STATUS_2);
+		rf->mp_reg_sub_rst_status = r;
+		rf->mp_fld_sub_rst_status_user_rx_rst =
+			get_field(r, MAC_PCS_XXV_SUB_RST_STATUS_2_USER_RX_RST);
+		rf->mp_fld_sub_rst_status_user_tx_rst =
+			get_field(r, MAC_PCS_XXV_SUB_RST_STATUS_2_USER_TX_RST);
+		rf->mp_fld_sub_rst_status_qpll_lock =
+			get_field(r, MAC_PCS_XXV_SUB_RST_STATUS_2_QPLL_LOCK);
+
+		/* Initialize regs/fields for sub-module/channel 3 */
+		rf = &p->regs[3];
+		r = get_register(module, MAC_PCS_XXV_SUB_RST_3);
+
+		rf->mp_reg_sub_rst = r;
+		rf->mp_fld_sub_rst_rx_mac_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_3_RX_MAC_PCS);
+		rf->mp_fld_sub_rst_tx_mac_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_3_TX_MAC_PCS);
+		rf->mp_fld_sub_rst_rx_gt_data =
+			get_field(r, MAC_PCS_XXV_SUB_RST_3_RX_GT_DATA);
+		rf->mp_fld_sub_rst_tx_gt_data =
+			get_field(r, MAC_PCS_XXV_SUB_RST_3_TX_GT_DATA);
+		rf->mp_fld_sub_rst_rx_buf =
+			get_field(r, MAC_PCS_XXV_SUB_RST_3_RX_BUF);
+		rf->mp_fld_sub_rst_rx_pma =
+			get_field(r, MAC_PCS_XXV_SUB_RST_3_RX_PMA);
+		rf->mp_fld_sub_rst_tx_pma =
+			get_field(r, MAC_PCS_XXV_SUB_RST_3_TX_PMA);
+		rf->mp_fld_sub_rst_rx_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_3_RX_PCS);
+		rf->mp_fld_sub_rst_tx_pcs =
+			get_field(r, MAC_PCS_XXV_SUB_RST_3_TX_PCS);
+		rf->mp_fld_sub_rst_an_lt = get_field(r, MAC_PCS_XXV_SUB_RST_3_AN_LT);
+		rf->mp_fld_sub_rst_speed_ctrl =
+			query_field(r, MAC_PCS_XXV_SUB_RST_3_SPEED_CTRL);
+
+		r = get_register(module, MAC_PCS_XXV_SUB_RST_STATUS_3);
+		rf->mp_reg_sub_rst_status = r;
+		rf->mp_fld_sub_rst_status_user_rx_rst =
+			get_field(r, MAC_PCS_XXV_SUB_RST_STATUS_3_USER_RX_RST);
+		rf->mp_fld_sub_rst_status_user_tx_rst =
+			get_field(r, MAC_PCS_XXV_SUB_RST_STATUS_3_USER_TX_RST);
+		rf->mp_fld_sub_rst_status_qpll_lock =
+			get_field(r, MAC_PCS_XXV_SUB_RST_STATUS_3_QPLL_LOCK);
+	}
+
+	/* Registers MAC_PCS_XXV_LINK_SUMMARY_0 -- MAC_PCS_XXV_LINK_SUMMARY_3 */
+	if (n_channels < 4) {
+		/* Initialize regs/fields for sub-module/channel 0 */
+		rf = &p->regs[0];
+		r = get_register(module, MAC_PCS_XXV_LINK_SUMMARY_0);
+
+		rf->mp_reg_link_summary = r;
+		rf->mp_fld_link_summary_nt_phy_link_state =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_NT_PHY_LINK_STATE);
+		rf->mp_fld_link_summary_ll_nt_phy_link_state =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_LL_PHY_LINK_STATE);
+		rf->mp_fld_link_summary_abs =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_ABS);
+		rf->mp_fld_link_summary_lh_abs =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_LH_ABS);
+		rf->mp_fld_link_summary_link_down_cnt =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_LINK_DOWN_CNT);
+		if (!m_mac8x10_g) {
+			rf->mp_fld_link_summary_ll_rx_fec74_lock =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_LL_RX_FEC74_LOCK);
+			rf->mp_fld_link_summary_lh_rx_rsfec_hi_ser =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_LH_RX_RSFEC_HI_SER);
+			rf->mp_fld_link_summary_ll_rx_rsfec_lane_alignment =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_LL_RX_RSFEC_LANE_ALIGNMENT);
+			rf->mp_fld_link_summary_ll_tx_rsfec_lane_alignment =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_LL_TX_RSFEC_LANE_ALIGNMENT);
+			rf->mp_fld_link_summary_lh_rx_pcs_valid_ctrl_code =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_LH_RX_PCS_VALID_CTRL_CODE);
+		}
+		rf->mp_fld_link_summary_ll_rx_block_lock =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_LL_RX_BLOCK_LOCK);
+		rf->mp_fld_link_summary_lh_rx_high_bit_error_rate =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_LH_RX_HIGH_BIT_ERROR_RATE);
+		;
+		rf->mp_fld_link_summary_lh_internal_local_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_LH_INTERNAL_LOCAL_FAULT);
+		rf->mp_fld_link_summary_lh_received_local_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_LH_RECEIVED_LOCAL_FAULT);
+		rf->mp_fld_link_summary_lh_local_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_LH_LOCAL_FAULT);
+		rf->mp_fld_link_summary_lh_remote_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_LH_REMOTE_FAULT);
+		rf->mp_fld_link_summary_nim_interr =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_0_NIM_INTERR);
+	}
+
+	if (n_channels >= 2) {
+		/* Initialize regs/fields for sub-module/channel 1 */
+		rf = &p->regs[1];
+		r = get_register(module, MAC_PCS_XXV_LINK_SUMMARY_1);
+
+		rf->mp_reg_link_summary = r;
+		rf->mp_fld_link_summary_nt_phy_link_state =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_NT_PHY_LINK_STATE);
+		rf->mp_fld_link_summary_ll_nt_phy_link_state =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_LL_PHY_LINK_STATE);
+		rf->mp_fld_link_summary_abs =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_ABS);
+		rf->mp_fld_link_summary_lh_abs =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_LH_ABS);
+		rf->mp_fld_link_summary_link_down_cnt =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_LINK_DOWN_CNT);
+		if (!m_mac8x10_g) {
+			rf->mp_fld_link_summary_ll_rx_fec74_lock =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_LL_RX_FEC74_LOCK);
+			rf->mp_fld_link_summary_lh_rx_rsfec_hi_ser =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_LH_RX_RSFEC_HI_SER);
+			rf->mp_fld_link_summary_ll_rx_rsfec_lane_alignment =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_LL_RX_RSFEC_LANE_ALIGNMENT);
+			rf->mp_fld_link_summary_ll_tx_rsfec_lane_alignment =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_LL_TX_RSFEC_LANE_ALIGNMENT);
+			rf->mp_fld_link_summary_lh_rx_pcs_valid_ctrl_code =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_LH_RX_PCS_VALID_CTRL_CODE);
+		}
+		rf->mp_fld_link_summary_ll_rx_block_lock =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_LL_RX_BLOCK_LOCK);
+		rf->mp_fld_link_summary_lh_rx_high_bit_error_rate =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_LH_RX_HIGH_BIT_ERROR_RATE);
+		;
+		rf->mp_fld_link_summary_lh_internal_local_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_LH_INTERNAL_LOCAL_FAULT);
+		rf->mp_fld_link_summary_lh_received_local_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_LH_RECEIVED_LOCAL_FAULT);
+		rf->mp_fld_link_summary_lh_local_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_LH_LOCAL_FAULT);
+		rf->mp_fld_link_summary_lh_remote_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_LH_REMOTE_FAULT);
+		rf->mp_fld_link_summary_nim_interr =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_1_NIM_INTERR);
+	}
+
+	if (n_channels == 4) {
+		/* Initialize regs/fields for sub-module/channel 2 */
+		rf = &p->regs[2];
+		r = get_register(module, MAC_PCS_XXV_LINK_SUMMARY_2);
+
+		rf->mp_reg_link_summary = r;
+		rf->mp_fld_link_summary_nt_phy_link_state =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_NT_PHY_LINK_STATE);
+		rf->mp_fld_link_summary_ll_nt_phy_link_state =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_LL_PHY_LINK_STATE);
+		rf->mp_fld_link_summary_abs =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_ABS);
+		rf->mp_fld_link_summary_lh_abs =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_LH_ABS);
+		rf->mp_fld_link_summary_link_down_cnt =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_LINK_DOWN_CNT);
+		if (!m_mac8x10_g) {
+			rf->mp_fld_link_summary_ll_rx_fec74_lock =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_LL_RX_FEC74_LOCK);
+			rf->mp_fld_link_summary_lh_rx_rsfec_hi_ser =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_LH_RX_RSFEC_HI_SER);
+			rf->mp_fld_link_summary_ll_rx_rsfec_lane_alignment =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_LL_RX_RSFEC_LANE_ALIGNMENT);
+			rf->mp_fld_link_summary_ll_tx_rsfec_lane_alignment =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_LL_TX_RSFEC_LANE_ALIGNMENT);
+			rf->mp_fld_link_summary_lh_rx_pcs_valid_ctrl_code =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_LH_RX_PCS_VALID_CTRL_CODE);
+		}
+		rf->mp_fld_link_summary_ll_rx_block_lock =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_LL_RX_BLOCK_LOCK);
+		rf->mp_fld_link_summary_lh_rx_high_bit_error_rate =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_LH_RX_HIGH_BIT_ERROR_RATE);
+		;
+		rf->mp_fld_link_summary_lh_internal_local_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_LH_INTERNAL_LOCAL_FAULT);
+		rf->mp_fld_link_summary_lh_received_local_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_LH_RECEIVED_LOCAL_FAULT);
+		rf->mp_fld_link_summary_lh_local_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_LH_LOCAL_FAULT);
+		rf->mp_fld_link_summary_lh_remote_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_LH_REMOTE_FAULT);
+		rf->mp_fld_link_summary_nim_interr =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_2_NIM_INTERR);
+
+		/* Initialize regs/fields for sub-module/channel 3 */
+		rf = &p->regs[3];
+		r = get_register(module, MAC_PCS_XXV_LINK_SUMMARY_3);
+
+		rf->mp_reg_link_summary = r;
+		rf->mp_fld_link_summary_nt_phy_link_state =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_NT_PHY_LINK_STATE);
+		rf->mp_fld_link_summary_ll_nt_phy_link_state =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_LL_PHY_LINK_STATE);
+		rf->mp_fld_link_summary_abs =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_ABS);
+		rf->mp_fld_link_summary_lh_abs =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_LH_ABS);
+		rf->mp_fld_link_summary_link_down_cnt =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_LINK_DOWN_CNT);
+		if (!m_mac8x10_g) {
+			rf->mp_fld_link_summary_ll_rx_fec74_lock =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_LL_RX_FEC74_LOCK);
+			rf->mp_fld_link_summary_lh_rx_rsfec_hi_ser =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_LH_RX_RSFEC_HI_SER);
+			rf->mp_fld_link_summary_ll_rx_rsfec_lane_alignment =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_LL_RX_RSFEC_LANE_ALIGNMENT);
+			rf->mp_fld_link_summary_ll_tx_rsfec_lane_alignment =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_LL_TX_RSFEC_LANE_ALIGNMENT);
+			rf->mp_fld_link_summary_lh_rx_pcs_valid_ctrl_code =
+				get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_LH_RX_PCS_VALID_CTRL_CODE);
+		}
+		rf->mp_fld_link_summary_ll_rx_block_lock =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_LL_RX_BLOCK_LOCK);
+		rf->mp_fld_link_summary_lh_rx_high_bit_error_rate =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_LH_RX_HIGH_BIT_ERROR_RATE);
+		;
+		rf->mp_fld_link_summary_lh_internal_local_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_LH_INTERNAL_LOCAL_FAULT);
+		rf->mp_fld_link_summary_lh_received_local_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_LH_RECEIVED_LOCAL_FAULT);
+		rf->mp_fld_link_summary_lh_local_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_LH_LOCAL_FAULT);
+		rf->mp_fld_link_summary_lh_remote_fault =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_LH_REMOTE_FAULT);
+		rf->mp_fld_link_summary_nim_interr =
+			get_field(r, MAC_PCS_XXV_LINK_SUMMARY_3_NIM_INTERR);
+	}
+
+	/*
+	 *  Registers MAC_PCS_XXV_GTY_LOOP_0 -- MAC_PCS_XXV_GTY_LOOP_3
+	 * and       MAC_PCS_XXV_GTY_CTL_RX_0 -- MAC_PCS_XXV_GTY_CTL_RX_3
+	 * and       MAC_PCS_XXV_GTY_CTL_TX_0 -- MAC_PCS_XXV_GTY_CTL_TX_3
+	 * and       MAC_PCS_XXV_LINK_SPEED_0 -- MAC_PCS_XXV_LINK_SPEED_3
+	 * and       MAC_PCS_XXV_RS_FEC_CONF_0 -- MAC_PCS_XXV_RS_FEC_CONF_0
+	 */
+	if (n_channels < 4) {
+		/* Initialize regs/fields for sub-module/channel 0 */
+		rf = &p->regs[0];
+
+		r = get_register(module, MAC_PCS_XXV_GTY_LOOP_0);
+		rf->mp_reg_gty_loop = r;
+		rf->mp_fld_gty_loop_gt_loop =
+			get_field(r, MAC_PCS_XXV_GTY_LOOP_0_GT_LOOP);
+
+		r = get_register(module, MAC_PCS_XXV_GTY_CTL_RX_0);
+		rf->mp_reg_gty_ctl_rx = r;
+		rf->mp_fld_gty_ctl_rx_polarity =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_RX_0_POLARITY);
+		rf->mp_fld_gty_ctl_rx_lpm_en =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_RX_0_LPM_EN);
+		rf->mp_fld_gty_ctl_rx_equa_rst =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_RX_0_EQUA_RST);
+
+		r = get_register(module, MAC_PCS_XXV_GTY_CTL_TX_0);
+		rf->mp_fld_gty_ctl_tx_polarity =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_TX_0_POLARITY);
+		rf->mp_fld_gty_ctl_tx_inhibit =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_TX_0_INHIBIT);
+
+		if (!m_mac8x10_g) {
+			r = get_register(module, MAC_PCS_XXV_LINK_SPEED_0);
+			rf->mp_reg_link_speed =
+				get_register(module, MAC_PCS_XXV_LINK_SPEED_0);
+
+			rf->mp_fld_link_speed10_g =
+				query_field(r, MAC_PCS_XXV_LINK_SPEED_0_SPEED);
+			if (!rf->mp_fld_link_speed10_g) {
+				rf->mp_fld_link_speed10_g =
+					get_field(r, MAC_PCS_XXV_LINK_SPEED_0_10G);
+			}
+
+			rf->mp_fld_link_speed_toggle =
+				get_field(r, MAC_PCS_XXV_LINK_SPEED_0_TOGGLE);
+
+			r = get_register(module, MAC_PCS_XXV_RS_FEC_CONF_0);
+			rf->mp_reg_rs_fec_conf = r;
+			rf->mp_fld_rs_fec_conf_rs_fec_enable =
+				get_field(r, MAC_PCS_XXV_RS_FEC_CONF_0_RS_FEC_ENABLE);
+
+			r = get_register(module, MAC_PCS_XXV_RS_FEC_CCW_CNT_0);
+			rf->mp_reg_rs_fec_ccw = r;
+			rf->mp_field_reg_rs_fec_ccw_reg_rs_fec_ccw_cnt =
+				get_field(r, MAC_PCS_XXV_RS_FEC_CCW_CNT_0_RS_FEC_CCW_CNT);
+
+			r = get_register(module, MAC_PCS_XXV_RS_FEC_UCW_CNT_0);
+			rf->mp_reg_rs_fec_ucw = r;
+			rf->mp_field_reg_rs_fec_ucw_reg_rs_fec_ucw_cnt =
+				get_field(r, MAC_PCS_XXV_RS_FEC_UCW_CNT_0_RS_FEC_UCW_CNT);
+		}
+	}
+
+	if (n_channels >= 2) {
+		/* Initialize regs/fields for sub-module/channel 1 */
+		rf = &p->regs[1];
+
+		r = get_register(module, MAC_PCS_XXV_GTY_LOOP_1);
+		rf->mp_reg_gty_loop = r;
+		rf->mp_fld_gty_loop_gt_loop =
+			get_field(r, MAC_PCS_XXV_GTY_LOOP_1_GT_LOOP);
+
+		r = get_register(module, MAC_PCS_XXV_GTY_CTL_RX_1);
+		rf->mp_reg_gty_ctl_rx = r;
+		rf->mp_fld_gty_ctl_rx_polarity =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_RX_1_POLARITY);
+		rf->mp_fld_gty_ctl_rx_lpm_en =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_RX_1_LPM_EN);
+		rf->mp_fld_gty_ctl_rx_equa_rst =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_RX_1_EQUA_RST);
+
+		r = get_register(module, MAC_PCS_XXV_GTY_CTL_TX_1);
+		rf->mp_fld_gty_ctl_tx_polarity =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_TX_1_POLARITY);
+		rf->mp_fld_gty_ctl_tx_inhibit =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_TX_1_INHIBIT);
+
+		if (!m_mac8x10_g) {
+			r = get_register(module, MAC_PCS_XXV_LINK_SPEED_1);
+			rf->mp_reg_link_speed =
+				get_register(module, MAC_PCS_XXV_LINK_SPEED_1);
+
+			rf->mp_fld_link_speed10_g =
+				get_field(r, MAC_PCS_XXV_LINK_SPEED_1_SPEED);
+			if (!rf->mp_fld_link_speed10_g) {
+				rf->mp_fld_link_speed10_g =
+					get_field(r, MAC_PCS_XXV_LINK_SPEED_1_10G);
+			}
+			rf->mp_fld_link_speed_toggle =
+				get_field(r, MAC_PCS_XXV_LINK_SPEED_1_TOGGLE);
+
+			r = get_register(module, MAC_PCS_XXV_RS_FEC_CONF_1);
+			rf->mp_reg_rs_fec_conf = r;
+			rf->mp_fld_rs_fec_conf_rs_fec_enable =
+				get_field(r, MAC_PCS_XXV_RS_FEC_CONF_1_RS_FEC_ENABLE);
+
+			r = get_register(module, MAC_PCS_XXV_RS_FEC_CCW_CNT_1);
+			rf->mp_reg_rs_fec_ccw = r;
+			rf->mp_field_reg_rs_fec_ccw_reg_rs_fec_ccw_cnt =
+				get_field(r, MAC_PCS_XXV_RS_FEC_CCW_CNT_1_RS_FEC_CCW_CNT);
+
+			r = get_register(module, MAC_PCS_XXV_RS_FEC_UCW_CNT_1);
+			rf->mp_reg_rs_fec_ucw = r;
+			rf->mp_field_reg_rs_fec_ucw_reg_rs_fec_ucw_cnt =
+				get_field(r, MAC_PCS_XXV_RS_FEC_UCW_CNT_1_RS_FEC_UCW_CNT);
+		}
+	}
+
+	if (n_channels == 4) {
+		/* Initialize regs/fields for sub-module/channel 2 */
+		rf = &p->regs[2];
+
+		r = get_register(module, MAC_PCS_XXV_GTY_LOOP_2);
+		rf->mp_reg_gty_loop = r;
+		rf->mp_fld_gty_loop_gt_loop =
+			get_field(r, MAC_PCS_XXV_GTY_LOOP_2_GT_LOOP);
+
+		r = get_register(module, MAC_PCS_XXV_GTY_CTL_RX_2);
+		rf->mp_reg_gty_ctl_rx = r;
+		rf->mp_fld_gty_ctl_rx_polarity =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_RX_2_POLARITY);
+		rf->mp_fld_gty_ctl_rx_lpm_en =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_RX_2_LPM_EN);
+		rf->mp_fld_gty_ctl_rx_equa_rst =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_RX_2_EQUA_RST);
+
+		r = get_register(module, MAC_PCS_XXV_GTY_CTL_TX_2);
+		rf->mp_fld_gty_ctl_tx_polarity =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_TX_2_POLARITY);
+		rf->mp_fld_gty_ctl_tx_inhibit =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_TX_2_INHIBIT);
+
+		if (!m_mac8x10_g) {
+			r = get_register(module, MAC_PCS_XXV_LINK_SPEED_2);
+			rf->mp_reg_link_speed =
+				get_register(module, MAC_PCS_XXV_LINK_SPEED_2);
+
+			rf->mp_fld_link_speed10_g =
+				get_field(r, MAC_PCS_XXV_LINK_SPEED_2_SPEED);
+			if (!rf->mp_fld_link_speed10_g) {
+				rf->mp_fld_link_speed10_g =
+					get_field(r, MAC_PCS_XXV_LINK_SPEED_2_10G);
+			}
+
+			rf->mp_fld_link_speed_toggle =
+				get_field(r, MAC_PCS_XXV_LINK_SPEED_2_TOGGLE);
+
+			r = get_register(module, MAC_PCS_XXV_RS_FEC_CONF_2);
+			rf->mp_reg_rs_fec_conf = r;
+			rf->mp_fld_rs_fec_conf_rs_fec_enable =
+				get_field(r, MAC_PCS_XXV_RS_FEC_CONF_2_RS_FEC_ENABLE);
+
+			r = get_register(module, MAC_PCS_XXV_RS_FEC_CCW_CNT_2);
+			rf->mp_reg_rs_fec_ccw = r;
+			rf->mp_field_reg_rs_fec_ccw_reg_rs_fec_ccw_cnt =
+				get_field(r, MAC_PCS_XXV_RS_FEC_CCW_CNT_2_RS_FEC_CCW_CNT);
+
+			r = get_register(module, MAC_PCS_XXV_RS_FEC_UCW_CNT_2);
+			rf->mp_reg_rs_fec_ucw = r;
+			rf->mp_field_reg_rs_fec_ucw_reg_rs_fec_ucw_cnt =
+				get_field(r, MAC_PCS_XXV_RS_FEC_UCW_CNT_2_RS_FEC_UCW_CNT);
+		}
+
+		/* Initialize regs/fields for sub-module/channel 3 */
+		rf = &p->regs[3];
+
+		r = get_register(module, MAC_PCS_XXV_GTY_LOOP_3);
+		rf->mp_reg_gty_loop = r;
+		rf->mp_fld_gty_loop_gt_loop =
+			get_field(r, MAC_PCS_XXV_GTY_LOOP_3_GT_LOOP);
+
+		r = get_register(module, MAC_PCS_XXV_GTY_CTL_RX_3);
+		rf->mp_reg_gty_ctl_rx = r;
+		rf->mp_fld_gty_ctl_rx_polarity =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_RX_3_POLARITY);
+		rf->mp_fld_gty_ctl_rx_lpm_en =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_RX_3_LPM_EN);
+		rf->mp_fld_gty_ctl_rx_equa_rst =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_RX_3_EQUA_RST);
+
+		r = get_register(module, MAC_PCS_XXV_GTY_CTL_TX_3);
+		rf->mp_fld_gty_ctl_tx_polarity =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_TX_3_POLARITY);
+		rf->mp_fld_gty_ctl_tx_inhibit =
+			get_field(r, MAC_PCS_XXV_GTY_CTL_TX_3_INHIBIT);
+
+		if (!m_mac8x10_g) {
+			r = get_register(module, MAC_PCS_XXV_LINK_SPEED_3);
+			rf->mp_reg_link_speed =
+				get_register(module, MAC_PCS_XXV_LINK_SPEED_3);
+
+			rf->mp_fld_link_speed10_g =
+				get_field(r, MAC_PCS_XXV_LINK_SPEED_3_SPEED);
+			if (!rf->mp_fld_link_speed10_g) {
+				rf->mp_fld_link_speed10_g =
+					get_field(r, MAC_PCS_XXV_LINK_SPEED_3_10G);
+			}
+			rf->mp_fld_link_speed_toggle =
+				get_field(r, MAC_PCS_XXV_LINK_SPEED_3_TOGGLE);
+
+			r = get_register(module, MAC_PCS_XXV_RS_FEC_CONF_3);
+			rf->mp_reg_rs_fec_conf = r;
+			rf->mp_fld_rs_fec_conf_rs_fec_enable =
+				get_field(r, MAC_PCS_XXV_RS_FEC_CONF_3_RS_FEC_ENABLE);
+
+			r = get_register(module, MAC_PCS_XXV_RS_FEC_CCW_CNT_3);
+			rf->mp_reg_rs_fec_ccw = r;
+			rf->mp_field_reg_rs_fec_ccw_reg_rs_fec_ccw_cnt =
+				get_field(r, MAC_PCS_XXV_RS_FEC_CCW_CNT_3_RS_FEC_CCW_CNT);
+
+			r = get_register(module, MAC_PCS_XXV_RS_FEC_UCW_CNT_3);
+			rf->mp_reg_rs_fec_ucw = r;
+			rf->mp_field_reg_rs_fec_ucw_reg_rs_fec_ucw_cnt =
+				get_field(r, MAC_PCS_XXV_RS_FEC_UCW_CNT_3_RS_FEC_UCW_CNT);
+		}
+	}
+
+	/*
+	 * Registers MAC_PCS_XXV_DEBOUNCE_CTRL_0 -- MAC_PCS_XXV_DEBOUNCE_CTRL_3
+	 * and       MAC_PCS_XXV_TIMESTAMP_COMP_0 -- MAC_PCS_XXV_TIMESTAMP_COMP_3
+	 * and       MAC_PCS_XXV_GTY_PRE_CURSOR_0 -- MAC_PCS_XXV_GTY_PRE_CURSOR_3
+	 * and       MAC_PCS_XXV_GTY_DIFF_CTL_0 -- MAC_PCS_XXV_GTY_DIFF_CTL_0
+	 * and       MAC_PCS_XXV_GTY_POST_CURSOR_0 -- MAC_PCS_XXV_GTY_POST_CURSOR_3
+	 */
+	if (n_channels < 4) {
+		/* Initialize regs/fields for sub-module/channel 0 */
+		rf = &p->regs[0];
+
+		r = get_register(module, MAC_PCS_XXV_DEBOUNCE_CTRL_0);
+
+		rf->mp_reg_debounce_ctrl = r;
+		rf->mp_field_debounce_ctrl_nt_port_ctrl =
+			get_field(r, MAC_PCS_XXV_DEBOUNCE_CTRL_0_NT_PORT_CTRL);
+
+		r = get_register(module, MAC_PCS_XXV_TIMESTAMP_COMP_0);
+		rf->mp_reg_timestamp_comp = r;
+		rf->mp_field_timestamp_comp_rx_dly =
+			get_field(r, MAC_PCS_XXV_TIMESTAMP_COMP_0_RX_DLY);
+		rf->mp_field_timestamp_comp_tx_dly =
+			get_field(r, MAC_PCS_XXV_TIMESTAMP_COMP_0_TX_DLY);
+
+		/* GTY_PRE_CURSOR */
+		r = get_register(p->mp_mod_mac_pcs_xxv,
+				 MAC_PCS_XXV_GTY_PRE_CURSOR_0);
+		rf->mp_reg_gty_pre_cursor = r;
+		rf->mp_field_gty_pre_cursor_tx_pre_csr =
+			get_field(r, MAC_PCS_XXV_GTY_PRE_CURSOR_0_TX_PRE_CSR);
+
+		/* GTY_DIFF_CTL */
+		r = get_register(module, MAC_PCS_XXV_GTY_DIFF_CTL_0);
+		rf->mp_reg_gty_diff_ctl = r;
+		rf->mp_field_gty_gty_diff_ctl_tx_diff_ctl =
+			get_field(r, MAC_PCS_XXV_GTY_DIFF_CTL_0_TX_DIFF_CTL);
+
+		/* GTY_POST_CURSOR */
+		r = get_register(module, MAC_PCS_XXV_GTY_POST_CURSOR_0);
+		rf->mp_reg_gty_post_cursor = r;
+		rf->mp_field_gty_post_cursor_tx_post_csr =
+			get_field(r, MAC_PCS_XXV_GTY_POST_CURSOR_0_TX_POST_CSR);
+	}
+
+	if (n_channels >= 2) {
+		/* Initialize regs/fields for sub-module/channel 1 */
+		rf = &p->regs[1];
+
+		r = get_register(module, MAC_PCS_XXV_DEBOUNCE_CTRL_1);
+
+		rf->mp_reg_debounce_ctrl = r;
+		rf->mp_field_debounce_ctrl_nt_port_ctrl =
+			get_field(r, MAC_PCS_XXV_DEBOUNCE_CTRL_1_NT_PORT_CTRL);
+
+		r = get_register(module, MAC_PCS_XXV_TIMESTAMP_COMP_1);
+		rf->mp_reg_timestamp_comp = r;
+		rf->mp_field_timestamp_comp_rx_dly =
+			get_field(r, MAC_PCS_XXV_TIMESTAMP_COMP_1_RX_DLY);
+		rf->mp_field_timestamp_comp_tx_dly =
+			get_field(r, MAC_PCS_XXV_TIMESTAMP_COMP_1_TX_DLY);
+
+		/* GTY_PRE_CURSOR */
+		r = get_register(p->mp_mod_mac_pcs_xxv,
+				MAC_PCS_XXV_GTY_PRE_CURSOR_1);
+		rf->mp_reg_gty_pre_cursor = r;
+		rf->mp_field_gty_pre_cursor_tx_pre_csr =
+			get_field(r, MAC_PCS_XXV_GTY_PRE_CURSOR_1_TX_PRE_CSR);
+
+		/* GTY_DIFF_CTL */
+		r = get_register(module, MAC_PCS_XXV_GTY_DIFF_CTL_1);
+		rf->mp_reg_gty_diff_ctl = r;
+		rf->mp_field_gty_gty_diff_ctl_tx_diff_ctl =
+			get_field(r, MAC_PCS_XXV_GTY_DIFF_CTL_1_TX_DIFF_CTL);
+
+		/* GTY_POST_CURSOR */
+		r = get_register(module, MAC_PCS_XXV_GTY_POST_CURSOR_1);
+		rf->mp_reg_gty_post_cursor = r;
+		rf->mp_field_gty_post_cursor_tx_post_csr =
+			get_field(r, MAC_PCS_XXV_GTY_POST_CURSOR_1_TX_POST_CSR);
+	}
+
+	if (n_channels == 4) {
+		/* Initialize regs/fields for sub-module/channel 2 */
+		rf = &p->regs[2];
+
+		r = get_register(module, MAC_PCS_XXV_DEBOUNCE_CTRL_2);
+
+		rf->mp_reg_debounce_ctrl = r;
+		rf->mp_field_debounce_ctrl_nt_port_ctrl =
+			get_field(r, MAC_PCS_XXV_DEBOUNCE_CTRL_2_NT_PORT_CTRL);
+
+		r = get_register(module, MAC_PCS_XXV_TIMESTAMP_COMP_2);
+		rf->mp_reg_timestamp_comp = r;
+		rf->mp_field_timestamp_comp_rx_dly =
+			get_field(r, MAC_PCS_XXV_TIMESTAMP_COMP_2_RX_DLY);
+		rf->mp_field_timestamp_comp_tx_dly =
+			get_field(r, MAC_PCS_XXV_TIMESTAMP_COMP_2_TX_DLY);
+
+		/* GTY_PRE_CURSOR */
+		r = get_register(p->mp_mod_mac_pcs_xxv,
+				MAC_PCS_XXV_GTY_PRE_CURSOR_2);
+		rf->mp_reg_gty_pre_cursor = r;
+		rf->mp_field_gty_pre_cursor_tx_pre_csr =
+			get_field(r, MAC_PCS_XXV_GTY_PRE_CURSOR_2_TX_PRE_CSR);
+
+		/* GTY_DIFF_CTL */
+		r = get_register(module, MAC_PCS_XXV_GTY_DIFF_CTL_2);
+		rf->mp_reg_gty_diff_ctl = r;
+		rf->mp_field_gty_gty_diff_ctl_tx_diff_ctl =
+			get_field(r, MAC_PCS_XXV_GTY_DIFF_CTL_2_TX_DIFF_CTL);
+
+		/* GTY_POST_CURSOR */
+		r = get_register(module, MAC_PCS_XXV_GTY_POST_CURSOR_2);
+		rf->mp_reg_gty_post_cursor = r;
+		rf->mp_field_gty_post_cursor_tx_post_csr =
+			get_field(r, MAC_PCS_XXV_GTY_POST_CURSOR_2_TX_POST_CSR);
+
+		/* Initialize regs/fields for sub-module/channel 3 */
+		rf = &p->regs[3];
+
+		r = get_register(module, MAC_PCS_XXV_DEBOUNCE_CTRL_3);
+
+		rf->mp_reg_debounce_ctrl = r;
+		rf->mp_field_debounce_ctrl_nt_port_ctrl =
+			get_field(r, MAC_PCS_XXV_DEBOUNCE_CTRL_3_NT_PORT_CTRL);
+
+		r = get_register(module, MAC_PCS_XXV_TIMESTAMP_COMP_3);
+		rf->mp_reg_timestamp_comp = r;
+		rf->mp_field_timestamp_comp_rx_dly =
+			get_field(r, MAC_PCS_XXV_TIMESTAMP_COMP_3_RX_DLY);
+		rf->mp_field_timestamp_comp_tx_dly =
+			get_field(r, MAC_PCS_XXV_TIMESTAMP_COMP_3_TX_DLY);
+
+		/* GTY_PRE_CURSOR */
+		r = get_register(p->mp_mod_mac_pcs_xxv,
+				MAC_PCS_XXV_GTY_PRE_CURSOR_3);
+		rf->mp_reg_gty_pre_cursor = r;
+		rf->mp_field_gty_pre_cursor_tx_pre_csr =
+			get_field(r, MAC_PCS_XXV_GTY_PRE_CURSOR_3_TX_PRE_CSR);
+
+		/* GTY_DIFF_CTL */
+		r = get_register(module, MAC_PCS_XXV_GTY_DIFF_CTL_3);
+		rf->mp_reg_gty_diff_ctl = r;
+		rf->mp_field_gty_gty_diff_ctl_tx_diff_ctl =
+			get_field(r, MAC_PCS_XXV_GTY_DIFF_CTL_3_TX_DIFF_CTL);
+
+		/* GTY_POST_CURSOR */
+		r = get_register(module, MAC_PCS_XXV_GTY_POST_CURSOR_3);
+		rf->mp_reg_gty_post_cursor = r;
+		rf->mp_field_gty_post_cursor_tx_post_csr =
+			get_field(r, MAC_PCS_XXV_GTY_POST_CURSOR_3_TX_POST_CSR);
+	}
+	return 0;
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_mac_pcs_xxv.h b/drivers/net/ntnic/nthw/core/nthw_mac_pcs_xxv.h
new file mode 100644
index 0000000000..5a38494f7e
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_mac_pcs_xxv.h
@@ -0,0 +1,291 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NTHW_MAC_PCS_XXV_H_
+#define NTHW_MAC_PCS_XXV_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "nthw_fpga_model.h"
+
+enum nthw_mac_pcs_xxv_led_mode_e {
+	NTHW_MAC_PCS_XXV_LED_AUTO = 0x00,
+	NTHW_MAC_PCS_XXV_LED_ON = 0x01,
+	NTHW_MAC_PCS_XXV_LED_OFF = 0x02,
+	NTHW_MAC_PCS_XXV_LED_PORTID = 0x03,
+};
+
+enum nthw_mac_pcs_xxv_dac_mode_e {
+	NTHW_MAC_PCS_XXV_DAC_OFF = 0x00,
+	NTHW_MAC_PCS_XXV_DAC_CA_25G_N = 0x01,
+	NTHW_MAC_PCS_XXV_DAC_CA_25G_S = 0x02,
+	NTHW_MAC_PCS_XXV_DAC_CA_25G_L = 0x03,
+};
+
+struct nthw_mac_pcs_xxv {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_mac_pcs_xxv;
+	int mn_instance;
+
+	uint8_t m_port_no;
+
+#define NTHW_MAC_PCS_XXV_NUM_ELEMS 4
+	struct nthw_mac_pcs_xxv__registers_fields {
+		/* CORE_CONF */
+		nt_register_t *mp_reg_core_conf;
+		nt_field_t *mp_fld_core_conf_rx_enable;
+		nt_field_t *mp_fld_core_conf_rx_force_resync;
+		nt_field_t *mp_fld_core_conf_tx_enable;
+		nt_field_t *mp_fld_core_conf_tx_ins_fcs;
+		nt_field_t *mp_fld_core_conf_tx_ign_fcs;
+		nt_field_t *mp_fld_core_conf_tx_send_lfi;
+		nt_field_t *mp_fld_core_conf_tx_send_rfi;
+		nt_field_t *mp_fld_core_conf_tx_send_idle;
+		nt_field_t *mp_fld_core_conf_inline_mode;
+		nt_field_t *mp_fld_core_conf_line_loopback;
+		nt_field_t *mp_fld_core_conf_ts_at_eop;
+
+		/* ANEG_CONFIG */
+		nt_register_t *mp_reg_aneg_config;
+		nt_field_t *mp_fld_aneg_config_enable;
+		nt_field_t *mp_fld_aneg_config_bypass;
+		nt_field_t *mp_fld_aneg_config_restart;
+		nt_field_t *mp_fld_aneg_config_pseudo;
+		nt_field_t *mp_fld_aneg_config_nonce_seed;
+		nt_field_t *mp_fld_aneg_config_remote_fault;
+		nt_field_t *mp_fld_aneg_config_pause;
+		nt_field_t *mp_fld_aneg_config_asmdir;
+		nt_field_t *mp_fld_aneg_config_fec74_request10_g;
+		nt_field_t *mp_fld_aneg_config_hide_fec74;
+		nt_field_t *mp_fld_aneg_config_fec74_request;
+		nt_field_t *mp_fld_aneg_config_fec91_request;
+		nt_field_t *mp_fld_aneg_config_fec91_ability;
+		nt_field_t *mp_fld_aneg_config_rs_fec_request;
+		nt_field_t *mp_fld_aneg_config_sw_fec_overwrite;
+		nt_field_t *mp_fld_aneg_config_sw_speed_overwrite;
+
+		/* ANEG_ABILITY */
+		nt_register_t *mp_reg_aneg_ability;
+		nt_field_t *mp_fld_aneg_ability25_g_base_cr;
+		nt_field_t *mp_fld_aneg_ability25_g_base_crs;
+		nt_field_t *mp_fld_aneg_ability25_g_base_cr1;
+
+		/* LT_CONF */
+		nt_register_t *mp_reg_lt_conf;
+		nt_field_t *mp_fld_lt_conf_enable;
+		nt_field_t *mp_fld_lt_conf_restart;
+		nt_field_t *mp_fld_lt_conf_seed;
+
+		/* SUB_RST */
+		nt_register_t *mp_reg_sub_rst;
+		nt_field_t *mp_fld_sub_rst_rx_mac_pcs;
+		nt_field_t *mp_fld_sub_rst_tx_mac_pcs;
+		nt_field_t *mp_fld_sub_rst_rx_gt_data;
+		nt_field_t *mp_fld_sub_rst_tx_gt_data;
+		nt_field_t *mp_fld_sub_rst_rx_buf;
+		nt_field_t *mp_fld_sub_rst_rx_pma;
+		nt_field_t *mp_fld_sub_rst_tx_pma;
+		nt_field_t *mp_fld_sub_rst_rx_pcs;
+		nt_field_t *mp_fld_sub_rst_tx_pcs;
+		nt_field_t *mp_fld_sub_rst_an_lt;
+		nt_field_t *mp_fld_sub_rst_speed_ctrl;
+
+		/* SUB_RST_STATUS */
+		nt_register_t *mp_reg_sub_rst_status;
+		nt_field_t *mp_fld_sub_rst_status_user_rx_rst;
+		nt_field_t *mp_fld_sub_rst_status_user_tx_rst;
+		nt_field_t *mp_fld_sub_rst_status_qpll_lock;
+
+		/* LINK_SUMMARY */
+		nt_register_t *mp_reg_link_summary;
+		nt_field_t *mp_fld_link_summary_nt_phy_link_state;
+		nt_field_t *mp_fld_link_summary_ll_nt_phy_link_state;
+		nt_field_t *mp_fld_link_summary_abs;
+		nt_field_t *mp_fld_link_summary_lh_abs;
+		nt_field_t *mp_fld_link_summary_link_down_cnt;
+		/* Begin 2 x 10/25 Gbps only fields: */
+		nt_field_t *mp_fld_link_summary_ll_rx_fec74_lock;
+		nt_field_t *mp_fld_link_summary_lh_rx_rsfec_hi_ser;
+		nt_field_t *mp_fld_link_summary_ll_rx_rsfec_lane_alignment;
+		nt_field_t *mp_fld_link_summary_ll_tx_rsfec_lane_alignment;
+		nt_field_t *mp_fld_link_summary_lh_rx_pcs_valid_ctrl_code;
+		/* End 2 x 10/25 Gbps only fields. */
+		nt_field_t *mp_fld_link_summary_ll_rx_block_lock;
+		nt_field_t *mp_fld_link_summary_lh_rx_high_bit_error_rate;
+		nt_field_t *mp_fld_link_summary_lh_internal_local_fault;
+		nt_field_t *mp_fld_link_summary_lh_received_local_fault;
+		nt_field_t *mp_fld_link_summary_lh_local_fault;
+		nt_field_t *mp_fld_link_summary_lh_remote_fault;
+		nt_field_t *mp_fld_link_summary_lh_tx_local_fault;
+		nt_field_t *mp_fld_link_summary_nim_interr;
+
+		/* GTY_LOOP */
+		nt_register_t *mp_reg_gty_loop;
+		nt_field_t *mp_fld_gty_loop_gt_loop;
+
+		/* GTY_CTL_RX */
+		nt_register_t *mp_reg_gty_ctl_rx;
+		nt_field_t *mp_fld_gty_ctl_rx_polarity;
+		nt_field_t *mp_fld_gty_ctl_rx_lpm_en;
+		nt_field_t *mp_fld_gty_ctl_rx_equa_rst;
+
+		/* GTY_CTL_TX */
+		nt_register_t *mp_reg_gty_ctl_tx;
+		nt_field_t *mp_fld_gty_ctl_tx_polarity;
+		nt_field_t *mp_fld_gty_ctl_tx_inhibit;
+
+		/* LINK_SPEED */
+		nt_register_t *mp_reg_link_speed;
+		nt_field_t *mp_fld_link_speed10_g;
+		nt_field_t *mp_fld_link_speed_toggle;
+
+		/* RS_FEC_CONF */
+		nt_register_t *mp_reg_rs_fec_conf;
+		nt_field_t *mp_fld_rs_fec_conf_rs_fec_enable;
+
+		/* DEBOUNCE_CTRL */
+		nt_register_t *mp_reg_debounce_ctrl;
+		nt_field_t *mp_field_debounce_ctrl_nt_port_ctrl;
+
+		/* FEC_CCW_CNT */
+		nt_register_t *mp_reg_rs_fec_ccw;
+		nt_field_t *mp_field_reg_rs_fec_ccw_reg_rs_fec_ccw_cnt;
+
+		/* FEC_UCW_CNT */
+		nt_register_t *mp_reg_rs_fec_ucw;
+		nt_field_t *mp_field_reg_rs_fec_ucw_reg_rs_fec_ucw_cnt;
+
+		/* TIMESTAMP_COMP */
+		nt_register_t *mp_reg_timestamp_comp;
+		nt_field_t *mp_field_timestamp_comp_rx_dly;
+		nt_field_t *mp_field_timestamp_comp_tx_dly;
+
+		/* GTY_PRE_CURSOR */
+		nt_register_t *mp_reg_gty_pre_cursor;
+		nt_field_t *mp_field_gty_pre_cursor_tx_pre_csr;
+
+		/* GTY_DIFF_CTL */
+		nt_register_t *mp_reg_gty_diff_ctl;
+		nt_field_t *mp_field_gty_gty_diff_ctl_tx_diff_ctl;
+
+		/* GTY_POST_CURSOR */
+		nt_register_t *mp_reg_gty_post_cursor;
+		nt_field_t *mp_field_gty_post_cursor_tx_post_csr;
+	} regs[NTHW_MAC_PCS_XXV_NUM_ELEMS];
+};
+
+typedef struct nthw_mac_pcs_xxv nthw_mac_pcs_xxv_t;
+typedef struct nthw_mac_pcs_xxv nthw_mac_pcs_xxv;
+
+nthw_mac_pcs_xxv_t *nthw_mac_pcs_xxv_new(void);
+void nthw_mac_pcs_xxv_delete(nthw_mac_pcs_xxv_t *p);
+int nthw_mac_pcs_xxv_init(nthw_mac_pcs_xxv_t *p, nt_fpga_t *p_fpga, int n_instance,
+		       int n_channels);
+
+void nthw_mac_pcs_xxv_get_link_summary(nthw_mac_pcs_xxv_t *p,
+	uint32_t *p_abs, uint32_t *p_nt_phy_link_state,
+	uint32_t *p_lh_abs, uint32_t *p_ll_nt_phy_link_state, uint32_t *p_link_down_cnt,
+	uint32_t *p_nim_interr, uint32_t *p_lh_local_fault, uint32_t *p_lh_remote_fault,
+	uint32_t *p_lh_internal_local_fault, uint32_t *p_lh_received_local_fault,
+	uint8_t index);
+
+uint8_t nthw_mac_pcs_xxv_get_port_no(const nthw_mac_pcs_xxv_t *p, uint8_t index);
+
+void nthw_mac_pcs_xxv_set_port_no(nthw_mac_pcs_xxv_t *p, uint8_t port_no);
+
+void nthw_mac_pcs_xxv_set_tx_enable(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index);
+void nthw_mac_pcs_xxv_set_rx_enable(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index);
+void nthw_mac_pcs_xxv_rx_force_resync(nthw_mac_pcs_xxv_t *p, bool enable,
+				 uint8_t index);
+
+void nthw_mac_pcs_xxv_reset_rx_gt_data(nthw_mac_pcs_xxv_t *p, bool enable,
+				 uint8_t index);
+void nthw_mac_pcs_xxv_reset_tx_gt_data(nthw_mac_pcs_xxv_t *p, bool enable,
+				 uint8_t index);
+
+void nthw_mac_pcs_xxv_reset_an_lt(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index);
+void nthw_mac_pcs_xxv_reset_speed_ctrl(nthw_mac_pcs_xxv_t *p, bool enable,
+				  uint8_t index);
+
+void nthw_mac_pcs_xxv_set_tx_send_rfi(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index);
+void nthw_mac_pcs_xxv_set_tx_send_lfi(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index);
+void nthw_mac_pcs_xxv_set_tx_send_lfi_tx_send_rfi(nthw_mac_pcs_xxv_t *p, bool enable_lfi,
+		bool enable_rfi, uint8_t index);
+
+bool nthw_mac_pcs_xxv_is_dfe_enabled(nthw_mac_pcs_xxv_t *p, uint8_t index);
+void nthw_mac_pcs_xxv_set_dfe(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index);
+
+void nthw_mac_pcs_xxv_set_rx_gty_polarity(nthw_mac_pcs_xxv_t *p, bool enable,
+				    uint8_t index);
+void nthw_mac_pcs_xxv_set_tx_gty_polarity(nthw_mac_pcs_xxv_t *p, bool enable,
+				    uint8_t index);
+
+void nthw_mac_pcs_xxv_set_tx_gty_inhibit(nthw_mac_pcs_xxv_t *p, bool enable,
+				   uint8_t index);
+
+void nthw_mac_pcs_xxv_set_host_loopback(nthw_mac_pcs_xxv_t *p, bool enable,
+				   uint8_t index);
+void nthw_mac_pcs_xxv_set_line_loopback(nthw_mac_pcs_xxv_t *p, bool enable,
+				   uint8_t index);
+
+bool nthw_mac_pcs_xxv_is_user_rx_rst(nthw_mac_pcs_xxv_t *p, uint8_t index);
+bool nthw_mac_pcs_xxv_is_user_tx_rst(nthw_mac_pcs_xxv_t *p, uint8_t index);
+
+bool nthw_mac_pcs_xxv_is_qpll_lock(nthw_mac_pcs_xxv_t *p, uint8_t index);
+bool nthw_mac_pcs_xxv_is_sub_rst_ready(nthw_mac_pcs_xxv_t *p, uint8_t index);
+bool nthw_mac_pcs_xxv_is_aneg_enabled(nthw_mac_pcs_xxv_t *p, uint8_t index);
+
+void nthw_mac_pcs_xxv_set_tx_send_idle(nthw_mac_pcs_xxv_t *p, bool enable,
+				 uint8_t index);
+void nthw_mac_pcs_xxv_set_tx_ins_fcs(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index);
+
+bool nthw_mac_pcs_xxv_get_link_speed10_g(nthw_mac_pcs_xxv_t *p, uint8_t index);
+void nthw_mac_pcs_xxv_set_link_speed10_g(nthw_mac_pcs_xxv_t *p, bool enable,
+				   uint8_t index);
+void nthw_mac_pcs_xxv_set_link_speed_toggle(nthw_mac_pcs_xxv_t *p, uint8_t index);
+
+void nthw_mac_pcs_xxv_set_rs_fec_conf_rs_fec_enable(nthw_mac_pcs_xxv_t *p, bool enable,
+		uint8_t index);
+
+void nthw_mac_pcs_xxv_set_led_mode(nthw_mac_pcs_xxv_t *p, uint8_t mode, uint8_t index);
+
+void nthw_mac_pcs_xxv_set_rx_mac_pcs_rst(nthw_mac_pcs_xxv_t *p, bool enable,
+				  uint8_t index);
+void nthw_mac_pcs_xxv_set_tx_mac_pcs_rst(nthw_mac_pcs_xxv_t *p, bool enable,
+				  uint8_t index);
+
+void nthw_mac_pcs_xxv_reset_fec_counters(nthw_mac_pcs_xxv_t *p, uint8_t index);
+
+void nthw_mac_pcs_xxv_set_gty_diff(nthw_mac_pcs_xxv_t *p, uint8_t value, uint8_t index);
+void nthw_mac_pcs_xxv_set_gty_pre(nthw_mac_pcs_xxv_t *p, uint8_t value, uint8_t index);
+void nthw_mac_pcs_xxv_set_gty_post(nthw_mac_pcs_xxv_t *p, uint8_t value, uint8_t index);
+
+void nthw_mac_pcs_xxv_set_ts_at_eop(nthw_mac_pcs_xxv_t *p, bool enable, uint8_t index);
+
+void nthw_mac_pcs_xxv_set_aneg_config_enable(nthw_mac_pcs_xxv_t *p, bool enable,
+				       uint8_t index);
+void nthw_mac_pcs_xxv_set_aneg_config_bypass(nthw_mac_pcs_xxv_t *p, bool enable,
+				       uint8_t index);
+void nthw_mac_pcs_xxv_set_lt_conf_enable(nthw_mac_pcs_xxv_t *p, bool enable,
+				   uint8_t index);
+
+void nthw_mac_pcs_xxv_set_dac_mode(nthw_mac_pcs_xxv_t *p, uint8_t dac_mode,
+			      uint8_t index);
+
+void nthw_mac_pcs_xxv_set_timestamp_comp_rx(nthw_mac_pcs_xxv_t *p, uint16_t rx_dly,
+				      uint8_t index);
+void nthw_mac_pcs_xxv_set_timestamp_comp_tx(nthw_mac_pcs_xxv_t *p, uint16_t tx_dly,
+				      uint8_t index);
+
+void nthw_mac_pcs_xxv_set_aneg_config_fec91_request(nthw_mac_pcs_xxv_t *p, bool enable,
+		uint8_t index);
+void nthw_mac_pcs_xxv_set_aneg_config_rs_fec_request(nthw_mac_pcs_xxv_t *p, bool enable,
+		uint8_t index);
+void nthw_mac_pcs_xxv_set_aneg_config_fec74_request(nthw_mac_pcs_xxv_t *p, bool enable,
+		uint8_t index);
+
+bool nthw_mac_pcs_xxv_get_ll_rx_fec74_lock(nthw_mac_pcs_xxv_t *p, uint8_t index);
+bool nthw_mac_pcs_xxv_get_ll_rx_rsfec_lane_alignment(nthw_mac_pcs_xxv_t *p, uint8_t index);
+
+#endif /* NTHW_MAC_PCS_XXV_H_ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_pci_rd_tg.c b/drivers/net/ntnic/nthw/core/nthw_pci_rd_tg.c
new file mode 100644
index 0000000000..92089d2fa3
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_pci_rd_tg.c
@@ -0,0 +1,121 @@
+/* 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_pci_rd_tg.h"
+
+nthw_pci_rd_tg_t *nthw_pci_rd_tg_new(void)
+{
+	nthw_pci_rd_tg_t *p = malloc(sizeof(nthw_pci_rd_tg_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_pci_rd_tg_t));
+	return p;
+}
+
+void nthw_pci_rd_tg_delete(nthw_pci_rd_tg_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_pci_rd_tg_t));
+		free(p);
+	}
+}
+
+int nthw_pci_rd_tg_init(nthw_pci_rd_tg_t *p, nt_fpga_t *p_fpga, int n_instance)
+{
+	nt_module_t *mod = fpga_query_module(p_fpga, MOD_PCI_RD_TG, n_instance);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: PCI_RD_TG %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_pci_rd_tg = mod;
+
+	p->mn_param_pci_ta_tg_present =
+		fpga_get_product_param(p_fpga, NT_PCI_TA_TG_PRESENT, 1);
+
+	p->mp_reg_pci_rd_tg_rd_data0 =
+		module_get_register(p->mp_mod_pci_rd_tg, PCI_RD_TG_TG_RDDATA0);
+	p->mp_fld_pci_rd_tg_phys_addr_low =
+		register_get_field(p->mp_reg_pci_rd_tg_rd_data0,
+				   PCI_RD_TG_TG_RDDATA0_PHYS_ADDR_LOW);
+
+	p->mp_reg_pci_rd_tg_rd_data1 =
+		module_get_register(p->mp_mod_pci_rd_tg, PCI_RD_TG_TG_RDDATA1);
+	p->mp_fld_pci_rd_tg_phys_addr_high =
+		register_get_field(p->mp_reg_pci_rd_tg_rd_data1,
+				   PCI_RD_TG_TG_RDDATA1_PHYS_ADDR_HIGH);
+
+	p->mp_reg_pci_rd_tg_rd_data2 =
+		module_get_register(p->mp_mod_pci_rd_tg, PCI_RD_TG_TG_RDDATA2);
+	p->mp_fld_pci_rd_tg_req_size =
+		register_get_field(p->mp_reg_pci_rd_tg_rd_data2, PCI_RD_TG_TG_RDDATA2_REQ_SIZE);
+	p->mp_fld_pci_rd_tg_wait =
+		register_get_field(p->mp_reg_pci_rd_tg_rd_data2, PCI_RD_TG_TG_RDDATA2_WAIT);
+	p->mp_fld_pci_rd_tg_wrap =
+		register_get_field(p->mp_reg_pci_rd_tg_rd_data2, PCI_RD_TG_TG_RDDATA2_WRAP);
+	/* optional VF host id */
+	p->mp_fld_pci_rd_tg_req_hid =
+		register_query_field(p->mp_reg_pci_rd_tg_rd_data2, PCI_RD_TG_TG_RDDATA2_REQ_HID);
+
+	p->mp_reg_pci_rd_tg_rd_addr =
+		module_get_register(p->mp_mod_pci_rd_tg, PCI_RD_TG_TG_RDADDR);
+	p->mp_fld_pci_rd_tg_ram_addr =
+		register_get_field(p->mp_reg_pci_rd_tg_rd_addr, PCI_RD_TG_TG_RDADDR_RAM_ADDR);
+
+	p->mp_reg_pci_rd_tg_rd_run =
+		module_get_register(p->mp_mod_pci_rd_tg, PCI_RD_TG_TG_RD_RUN);
+	p->mp_fld_pci_rd_tg_run_iteration =
+		register_get_field(p->mp_reg_pci_rd_tg_rd_run, PCI_RD_TG_TG_RD_RUN_RD_ITERATION);
+
+	p->mp_reg_pci_rd_tg_rd_ctrl =
+		module_get_register(p->mp_mod_pci_rd_tg, PCI_RD_TG_TG_CTRL);
+	p->mp_fld_pci_rd_tg_ctrl_rdy =
+		register_get_field(p->mp_reg_pci_rd_tg_rd_ctrl, PCI_RD_TG_TG_CTRL_TG_RD_RDY);
+
+	return 0;
+}
+
+void nthw_pci_rd_tg_set_phys_addr(nthw_pci_rd_tg_t *p, uint64_t n_phys_addr)
+{
+	field_set_val_flush32(p->mp_fld_pci_rd_tg_phys_addr_low,
+			    (uint32_t)(n_phys_addr & ((1UL << 32) - 1)));
+	field_set_val_flush32(p->mp_fld_pci_rd_tg_phys_addr_high,
+			    (uint32_t)((n_phys_addr >> 32) & ((1UL << 32) - 1)));
+}
+
+void nthw_pci_rd_tg_set_ram_addr(nthw_pci_rd_tg_t *p, int n_ram_addr)
+{
+	field_set_val_flush32(p->mp_fld_pci_rd_tg_ram_addr, n_ram_addr);
+}
+
+void nthw_pci_rd_tg_set_ram_data(nthw_pci_rd_tg_t *p, uint32_t req_size, bool wait,
+			    bool wrap)
+{
+	field_set_val32(p->mp_fld_pci_rd_tg_req_size, req_size);
+	field_set_val32(p->mp_fld_pci_rd_tg_wait, wait);
+	field_set_val32(p->mp_fld_pci_rd_tg_wrap, wrap);
+	field_flush_register(p->mp_fld_pci_rd_tg_wrap);
+}
+
+void nthw_pci_rd_tg_set_run(nthw_pci_rd_tg_t *p, int n_iterations)
+{
+	field_set_val_flush32(p->mp_fld_pci_rd_tg_run_iteration, n_iterations);
+}
+
+uint32_t nthw_pci_rd_tg_get_ctrl_rdy(nthw_pci_rd_tg_t *p)
+{
+	return field_get_updated(p->mp_fld_pci_rd_tg_ctrl_rdy);
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_pci_rd_tg.h b/drivers/net/ntnic/nthw/core/nthw_pci_rd_tg.h
new file mode 100644
index 0000000000..b1c912f0f3
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_pci_rd_tg.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_PCI_RD_TG_H__
+#define __NTHW_PCI_RD_TG_H__
+
+struct nthw_pci_rd_tg {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_pci_rd_tg;
+	int mn_instance;
+
+	int mn_param_pci_ta_tg_present;
+
+	nt_register_t *mp_reg_pci_rd_tg_rd_data0;
+	nt_field_t *mp_fld_pci_rd_tg_phys_addr_low;
+
+	nt_register_t *mp_reg_pci_rd_tg_rd_data1;
+	nt_field_t *mp_fld_pci_rd_tg_phys_addr_high;
+
+	nt_register_t *mp_reg_pci_rd_tg_rd_data2;
+	nt_field_t *mp_fld_pci_rd_tg_req_size;
+	nt_field_t *mp_fld_pci_rd_tg_req_hid;
+	nt_field_t *mp_fld_pci_rd_tg_wait;
+	nt_field_t *mp_fld_pci_rd_tg_wrap;
+
+	nt_register_t *mp_reg_pci_rd_tg_rd_addr;
+	nt_field_t *mp_fld_pci_rd_tg_ram_addr;
+
+	nt_register_t *mp_reg_pci_rd_tg_rd_run;
+	nt_field_t *mp_fld_pci_rd_tg_run_iteration;
+
+	nt_register_t *mp_reg_pci_rd_tg_rd_ctrl;
+	nt_field_t *mp_fld_pci_rd_tg_ctrl_rdy;
+};
+
+typedef struct nthw_pci_rd_tg nthw_pci_rd_tg_t;
+typedef struct nthw_pci_rd_tg nthw_pci_rd_tg;
+
+nthw_pci_rd_tg_t *nthw_pci_rd_tg_new(void);
+void nthw_pci_rd_tg_delete(nthw_pci_rd_tg_t *p);
+int nthw_pci_rd_tg_init(nthw_pci_rd_tg_t *p, nt_fpga_t *p_fpga, int n_instance);
+
+void nthw_pci_rd_tg_set_phys_addr(nthw_pci_rd_tg_t *p, uint64_t n_phys_addr);
+void nthw_pci_rd_tg_set_ram_addr(nthw_pci_rd_tg_t *p, int n_ram_addr);
+void nthw_pci_rd_tg_set_ram_data(nthw_pci_rd_tg_t *p, uint32_t req_size, bool wait,
+			    bool wrap);
+void nthw_pci_rd_tg_set_run(nthw_pci_rd_tg_t *p, int n_iterations);
+uint32_t nthw_pci_rd_tg_get_ctrl_rdy(nthw_pci_rd_tg_t *p);
+
+#endif /* __NTHW_PCI_RD_TG_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_pci_ta.c b/drivers/net/ntnic/nthw/core/nthw_pci_ta.c
new file mode 100644
index 0000000000..17e30a670d
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_pci_ta.c
@@ -0,0 +1,99 @@
+/* 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_pci_ta.h"
+
+nthw_pci_ta_t *nthw_pci_ta_new(void)
+{
+	nthw_pci_ta_t *p = malloc(sizeof(nthw_pci_ta_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_pci_ta_t));
+	return p;
+}
+
+void nthw_pci_ta_delete(nthw_pci_ta_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_pci_ta_t));
+		free(p);
+	}
+}
+
+int nthw_pci_ta_init(nthw_pci_ta_t *p, nt_fpga_t *p_fpga, int n_instance)
+{
+	nt_module_t *mod = fpga_query_module(p_fpga, MOD_PCI_TA, n_instance);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: PCI_TA %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_pci_ta = mod;
+
+	p->mn_param_pci_ta_tg_present =
+		fpga_get_product_param(p_fpga, NT_PCI_TA_TG_PRESENT, 1);
+
+	p->mp_reg_pci_ta_ctrl = module_get_register(p->mp_mod_pci_ta, PCI_TA_CONTROL);
+	p->mp_fld_pci_ta_ctrl_enable =
+		register_get_field(p->mp_reg_pci_ta_ctrl, PCI_TA_CONTROL_ENABLE);
+
+	p->mp_reg_pci_ta_packet_good =
+		module_get_register(p->mp_mod_pci_ta, PCI_TA_PACKET_GOOD);
+	p->mp_fld_pci_ta_packet_good_amount =
+		register_get_field(p->mp_reg_pci_ta_packet_good, PCI_TA_PACKET_GOOD_AMOUNT);
+
+	p->mp_reg_pci_ta_packet_bad =
+		module_get_register(p->mp_mod_pci_ta, PCI_TA_PACKET_BAD);
+	p->mp_fld_pci_ta_packet_bad_amount =
+		register_get_field(p->mp_reg_pci_ta_packet_bad, PCI_TA_PACKET_BAD_AMOUNT);
+
+	p->mp_reg_pci_ta_length_error =
+		module_get_register(p->mp_mod_pci_ta, PCI_TA_LENGTH_ERROR);
+	p->mp_fld_pci_ta_length_error_amount =
+		register_get_field(p->mp_reg_pci_ta_length_error, PCI_TA_LENGTH_ERROR_AMOUNT);
+
+	p->mp_reg_pci_ta_payload_error =
+		module_get_register(p->mp_mod_pci_ta, PCI_TA_PAYLOAD_ERROR);
+	p->mp_fld_pci_ta_payload_error_amount =
+		register_get_field(p->mp_reg_pci_ta_payload_error, PCI_TA_PAYLOAD_ERROR_AMOUNT);
+
+	return 0;
+}
+
+void nthw_pci_ta_set_control_enable(nthw_pci_ta_t *p, uint32_t val)
+{
+	field_set_val_flush32(p->mp_fld_pci_ta_ctrl_enable, val);
+}
+
+void nthw_pci_ta_get_packet_good(nthw_pci_ta_t *p, uint32_t *val)
+{
+	*val = field_get_updated(p->mp_fld_pci_ta_packet_good_amount);
+}
+
+void nthw_pci_ta_get_packet_bad(nthw_pci_ta_t *p, uint32_t *val)
+{
+	*val = field_get_updated(p->mp_fld_pci_ta_packet_bad_amount);
+}
+
+void nthw_pci_ta_get_length_error(nthw_pci_ta_t *p, uint32_t *val)
+{
+	*val = field_get_updated(p->mp_fld_pci_ta_length_error_amount);
+}
+
+void nthw_pci_ta_get_payload_error(nthw_pci_ta_t *p, uint32_t *val)
+{
+	*val = field_get_updated(p->mp_fld_pci_ta_payload_error_amount);
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_pci_ta.h b/drivers/net/ntnic/nthw/core/nthw_pci_ta.h
new file mode 100644
index 0000000000..7968cad9fa
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_pci_ta.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_PCI_TA_H__
+#define __NTHW_PCI_TA_H__
+
+struct nthw_pci_ta {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_pci_ta;
+	int mn_instance;
+
+	int mn_param_pci_ta_tg_present;
+
+	nt_register_t *mp_reg_pci_ta_ctrl;
+	nt_field_t *mp_fld_pci_ta_ctrl_enable;
+	nt_register_t *mp_reg_pci_ta_packet_good;
+	nt_field_t *mp_fld_pci_ta_packet_good_amount;
+	nt_register_t *mp_reg_pci_ta_packet_bad;
+	nt_field_t *mp_fld_pci_ta_packet_bad_amount;
+	nt_register_t *mp_reg_pci_ta_length_error;
+	nt_field_t *mp_fld_pci_ta_length_error_amount;
+	nt_register_t *mp_reg_pci_ta_payload_error;
+	nt_field_t *mp_fld_pci_ta_payload_error_amount;
+};
+
+typedef struct nthw_pci_ta nthw_pci_ta_t;
+typedef struct nthw_pci_ta nthw_pci_ta;
+
+nthw_pci_ta_t *nthw_pci_ta_new(void);
+void nthw_pci_ta_delete(nthw_pci_ta_t *p);
+int nthw_pci_ta_init(nthw_pci_ta_t *p, nt_fpga_t *p_fpga, int n_instance);
+
+void nthw_pci_ta_set_control_enable(nthw_pci_ta_t *p, uint32_t val);
+void nthw_pci_ta_get_packet_good(nthw_pci_ta_t *p, uint32_t *val);
+void nthw_pci_ta_get_packet_bad(nthw_pci_ta_t *p, uint32_t *val);
+void nthw_pci_ta_get_length_error(nthw_pci_ta_t *p, uint32_t *val);
+void nthw_pci_ta_get_payload_error(nthw_pci_ta_t *p, uint32_t *val);
+
+#endif /* __NTHW_PCI_TA_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_pci_wr_tg.c b/drivers/net/ntnic/nthw/core/nthw_pci_wr_tg.c
new file mode 100644
index 0000000000..f830a586b2
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_pci_wr_tg.c
@@ -0,0 +1,127 @@
+/* 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_pci_wr_tg.h"
+
+nthw_pci_wr_tg_t *nthw_pci_wr_tg_new(void)
+{
+	nthw_pci_wr_tg_t *p = malloc(sizeof(nthw_pci_wr_tg_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_pci_wr_tg_t));
+	return p;
+}
+
+void nthw_pci_wr_tg_delete(nthw_pci_wr_tg_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_pci_wr_tg_t));
+		free(p);
+	}
+}
+
+int nthw_pci_wr_tg_init(nthw_pci_wr_tg_t *p, nt_fpga_t *p_fpga, int n_instance)
+{
+	nt_module_t *mod = fpga_query_module(p_fpga, MOD_PCI_WR_TG, n_instance);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: PCI_WR_TG %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_pci_wr_tg = mod;
+
+	p->mn_param_pci_ta_tg_present =
+		fpga_get_product_param(p_fpga, NT_PCI_TA_TG_PRESENT, 1);
+
+	p->mp_reg_pci_wr_tg_data0 =
+		module_get_register(p->mp_mod_pci_wr_tg, PCI_WR_TG_TG_WRDATA0);
+	p->mp_fld_pci_wr_tg_phys_addr_low =
+		register_get_field(p->mp_reg_pci_wr_tg_data0, PCI_WR_TG_TG_WRDATA0_PHYS_ADDR_LOW);
+
+	p->mp_reg_pci_wr_tg_data1 =
+		module_get_register(p->mp_mod_pci_wr_tg, PCI_WR_TG_TG_WRDATA1);
+	p->mp_fld_pci_wr_tg_phys_addr_high =
+		register_get_field(p->mp_reg_pci_wr_tg_data1, PCI_WR_TG_TG_WRDATA1_PHYS_ADDR_HIGH);
+
+	p->mp_reg_pci_wr_tg_data2 =
+		module_get_register(p->mp_mod_pci_wr_tg, PCI_WR_TG_TG_WRDATA2);
+	p->mp_fld_pci_wr_tg_req_size =
+		register_get_field(p->mp_reg_pci_wr_tg_data2, PCI_WR_TG_TG_WRDATA2_REQ_SIZE);
+	p->mp_fld_pci_wr_tg_inc_mode =
+		register_get_field(p->mp_reg_pci_wr_tg_data2, PCI_WR_TG_TG_WRDATA2_INC_MODE);
+	p->mp_fld_pci_wr_tg_wait =
+		register_get_field(p->mp_reg_pci_wr_tg_data2, PCI_WR_TG_TG_WRDATA2_WAIT);
+	p->mp_fld_pci_wr_tg_wrap =
+		register_get_field(p->mp_reg_pci_wr_tg_data2, PCI_WR_TG_TG_WRDATA2_WRAP);
+	/* optional VF host id */
+	p->mp_fld_pci_wr_tg_req_hid =
+		register_query_field(p->mp_reg_pci_wr_tg_data2, PCI_WR_TG_TG_WRDATA2_REQ_HID);
+
+	p->mp_reg_pci_wr_tg_addr =
+		module_get_register(p->mp_mod_pci_wr_tg, PCI_WR_TG_TG_WRADDR);
+	p->mp_fld_pci_wr_tg_ram_addr =
+		register_get_field(p->mp_reg_pci_wr_tg_addr, PCI_WR_TG_TG_WRADDR_RAM_ADDR);
+
+	p->mp_reg_pci_wr_tg_run =
+		module_get_register(p->mp_mod_pci_wr_tg, PCI_WR_TG_TG_WR_RUN);
+	p->mp_fld_pci_wr_tg_run_iteration =
+		register_get_field(p->mp_reg_pci_wr_tg_run, PCI_WR_TG_TG_WR_RUN_WR_ITERATION);
+
+	p->mp_reg_pci_wr_tg_ctrl =
+		module_get_register(p->mp_mod_pci_wr_tg, PCI_WR_TG_TG_CTRL);
+	p->mp_fld_pci_wr_tg_ctrl_rdy =
+		register_get_field(p->mp_reg_pci_wr_tg_ctrl, PCI_WR_TG_TG_CTRL_TG_WR_RDY);
+
+	p->mp_reg_pci_wr_tg_seq =
+		module_get_register(p->mp_mod_pci_wr_tg, PCI_WR_TG_TG_SEQ);
+	p->mp_fld_pci_wr_tg_seq_sequence =
+		register_get_field(p->mp_reg_pci_wr_tg_seq, PCI_WR_TG_TG_SEQ_SEQUENCE);
+
+	return 0;
+}
+
+void nthw_pci_wr_tg_set_phys_addr(nthw_pci_wr_tg_t *p, uint64_t n_phys_addr)
+{
+	field_set_val_flush32(p->mp_fld_pci_wr_tg_phys_addr_low,
+			    (uint32_t)(n_phys_addr & ((1UL << 32) - 1)));
+	field_set_val_flush32(p->mp_fld_pci_wr_tg_phys_addr_high,
+			    (uint32_t)((n_phys_addr >> 32) & ((1UL << 32) - 1)));
+}
+
+void nthw_pci_wr_tg_set_ram_addr(nthw_pci_wr_tg_t *p, int n_ram_addr)
+{
+	field_set_val_flush32(p->mp_fld_pci_wr_tg_ram_addr, n_ram_addr);
+}
+
+void nthw_pci_wr_tg_set_ram_data(nthw_pci_wr_tg_t *p, uint32_t req_size, bool wait,
+			    bool wrap, bool inc)
+{
+	field_set_val32(p->mp_fld_pci_wr_tg_req_size, req_size);
+	field_set_val32(p->mp_fld_pci_wr_tg_wait, wait);
+	field_set_val32(p->mp_fld_pci_wr_tg_wrap, wrap);
+	field_set_val32(p->mp_fld_pci_wr_tg_inc_mode, inc);
+	field_flush_register(p->mp_fld_pci_wr_tg_inc_mode);
+}
+
+void nthw_pci_wr_tg_set_run(nthw_pci_wr_tg_t *p, int n_iterations)
+{
+	field_set_val_flush32(p->mp_fld_pci_wr_tg_run_iteration, n_iterations);
+}
+
+uint32_t nthw_pci_wr_tg_get_ctrl_rdy(nthw_pci_wr_tg_t *p)
+{
+	return field_get_updated(p->mp_fld_pci_wr_tg_ctrl_rdy);
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_pci_wr_tg.h b/drivers/net/ntnic/nthw/core/nthw_pci_wr_tg.h
new file mode 100644
index 0000000000..535b39526e
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_pci_wr_tg.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_PCI_WR_TG_H__
+#define __NTHW_PCI_WR_TG_H__
+
+struct nthw_pci_wr_tg {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_pci_wr_tg;
+	int mn_instance;
+
+	int mn_param_pci_ta_tg_present;
+
+	nt_register_t *mp_reg_pci_wr_tg_data0;
+	nt_field_t *mp_fld_pci_wr_tg_phys_addr_low;
+
+	nt_register_t *mp_reg_pci_wr_tg_data1;
+	nt_field_t *mp_fld_pci_wr_tg_phys_addr_high;
+
+	nt_register_t *mp_reg_pci_wr_tg_data2;
+	nt_field_t *mp_fld_pci_wr_tg_req_size;
+	nt_field_t *mp_fld_pci_wr_tg_req_hid;
+	nt_field_t *mp_fld_pci_wr_tg_inc_mode;
+	nt_field_t *mp_fld_pci_wr_tg_wait;
+	nt_field_t *mp_fld_pci_wr_tg_wrap;
+
+	nt_register_t *mp_reg_pci_wr_tg_addr;
+	nt_field_t *mp_fld_pci_wr_tg_ram_addr;
+
+	nt_register_t *mp_reg_pci_wr_tg_run;
+	nt_field_t *mp_fld_pci_wr_tg_run_iteration;
+
+	nt_register_t *mp_reg_pci_wr_tg_ctrl;
+	nt_field_t *mp_fld_pci_wr_tg_ctrl_rdy;
+
+	nt_register_t *mp_reg_pci_wr_tg_seq;
+	nt_field_t *mp_fld_pci_wr_tg_seq_sequence;
+};
+
+typedef struct nthw_pci_wr_tg nthw_pci_wr_tg_t;
+typedef struct nthw_pci_wr_tg nthw_pci_wr_tg;
+
+nthw_pci_wr_tg_t *nthw_pci_wr_tg_new(void);
+void nthw_pci_wr_tg_delete(nthw_pci_wr_tg_t *p);
+int nthw_pci_wr_tg_init(nthw_pci_wr_tg_t *p, nt_fpga_t *p_fpga, int n_instance);
+
+void nthw_pci_wr_tg_set_phys_addr(nthw_pci_wr_tg_t *p, uint64_t n_phys_addr);
+void nthw_pci_wr_tg_set_ram_addr(nthw_pci_wr_tg_t *p, int n_ram_addr);
+void nthw_pci_wr_tg_set_ram_data(nthw_pci_wr_tg_t *p, uint32_t req_size, bool wait,
+			    bool wrap, bool inc);
+void nthw_pci_wr_tg_set_run(nthw_pci_wr_tg_t *p, int n_iterations);
+uint32_t nthw_pci_wr_tg_get_ctrl_rdy(nthw_pci_wr_tg_t *p);
+
+#endif /* __NTHW_PCI_WR_TG_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_pcie3.c b/drivers/net/ntnic/nthw/core/nthw_pcie3.c
new file mode 100644
index 0000000000..07ad784695
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_pcie3.c
@@ -0,0 +1,274 @@
+/* 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_pcie3.h"
+
+#define NTHW_TG_REF_FREQ (250000000ULL)
+
+nthw_pcie3_t *nthw_pcie3_new(void)
+{
+	nthw_pcie3_t *p = malloc(sizeof(nthw_pcie3_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_pcie3_t));
+	return p;
+}
+
+void nthw_pcie3_delete(nthw_pcie3_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_pcie3_t));
+		free(p);
+	}
+}
+
+int nthw_pcie3_init(nthw_pcie3_t *p, nt_fpga_t *p_fpga, int n_instance)
+{
+	nt_module_t *mod = fpga_query_module(p_fpga, MOD_PCIE3, n_instance);
+
+	if (p == NULL)
+		return (mod == NULL ? -1 : 0);
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: PCIE3 %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_pcie3 = mod;
+
+	/* PCIe3 */
+	p->mp_reg_stat_ctrl = module_get_register(p->mp_mod_pcie3, PCIE3_STAT_CTRL);
+	p->mp_fld_stat_ctrl_ena =
+		register_get_field(p->mp_reg_stat_ctrl, PCIE3_STAT_CTRL_STAT_ENA);
+	p->mp_fld_stat_ctrl_req =
+		register_get_field(p->mp_reg_stat_ctrl, PCIE3_STAT_CTRL_STAT_REQ);
+
+	p->mp_reg_stat_rx = module_get_register(p->mp_mod_pcie3, PCIE3_STAT_RX);
+	p->mp_fld_stat_rx_counter =
+		register_get_field(p->mp_reg_stat_rx, PCIE3_STAT_RX_COUNTER);
+
+	p->mp_reg_stat_tx = module_get_register(p->mp_mod_pcie3, PCIE3_STAT_TX);
+	p->mp_fld_stat_tx_counter =
+		register_get_field(p->mp_reg_stat_tx, PCIE3_STAT_TX_COUNTER);
+
+	p->mp_reg_stat_ref_clk =
+		module_get_register(p->mp_mod_pcie3, PCIE3_STAT_REFCLK);
+	p->mp_fld_stat_ref_clk_ref_clk =
+		register_get_field(p->mp_reg_stat_ref_clk, PCIE3_STAT_REFCLK_REFCLK250);
+
+	p->mp_reg_stat_rq_rdy =
+		module_get_register(p->mp_mod_pcie3, PCIE3_STAT_RQ_RDY);
+	p->mp_fld_stat_rq_rdy_counter =
+		register_get_field(p->mp_reg_stat_rq_rdy, PCIE3_STAT_RQ_RDY_COUNTER);
+
+	p->mp_reg_stat_rq_vld =
+		module_get_register(p->mp_mod_pcie3, PCIE3_STAT_RQ_VLD);
+	p->mp_fld_stat_rq_vld_counter =
+		register_get_field(p->mp_reg_stat_rq_vld, PCIE3_STAT_RQ_VLD_COUNTER);
+
+	p->mp_reg_status0 = module_get_register(p->mp_mod_pcie3, PCIE3_STATUS0);
+	p->mp_fld_status0_tags_in_use =
+		register_get_field(p->mp_reg_status0, PCIE3_STATUS0_TAGS_IN_USE);
+
+	p->mp_reg_rp_to_ep_err =
+		module_get_register(p->mp_mod_pcie3, PCIE3_RP_TO_EP_ERR);
+	p->mp_fld_rp_to_ep_err_cor =
+		register_get_field(p->mp_reg_rp_to_ep_err, PCIE3_RP_TO_EP_ERR_ERR_COR);
+	p->mp_fld_rp_to_ep_err_non_fatal =
+		register_get_field(p->mp_reg_rp_to_ep_err, PCIE3_RP_TO_EP_ERR_ERR_NONFATAL);
+	p->mp_fld_rp_to_ep_err_fatal =
+		register_get_field(p->mp_reg_rp_to_ep_err, PCIE3_RP_TO_EP_ERR_ERR_FATAL);
+
+	p->mp_reg_ep_to_rp_err =
+		module_get_register(p->mp_mod_pcie3, PCIE3_EP_TO_RP_ERR);
+	p->mp_fld_ep_to_rp_err_cor = register_get_field(p->mp_reg_ep_to_rp_err,
+			       PCIE3_EP_TO_RP_ERR_ERR_COR);
+	p->mp_fld_ep_to_rp_err_non_fatal =
+		register_get_field(p->mp_reg_ep_to_rp_err, PCIE3_EP_TO_RP_ERR_ERR_NONFATAL);
+	p->mp_fld_ep_to_rp_err_fatal =
+		register_get_field(p->mp_reg_ep_to_rp_err, PCIE3_EP_TO_RP_ERR_ERR_FATAL);
+
+	p->mp_reg_sample_time =
+		module_get_register(p->mp_mod_pcie3, PCIE3_SAMPLE_TIME);
+	p->mp_fld_sample_time =
+		register_get_field(p->mp_reg_sample_time, PCIE3_SAMPLE_TIME_SAMPLE_TIME);
+
+	p->mp_reg_pci_end_point =
+		module_get_register(p->mp_mod_pcie3, PCIE3_PCI_ENDPOINT);
+	p->mp_fld_pci_end_point_if_id =
+		register_get_field(p->mp_reg_pci_end_point, PCIE3_PCI_ENDPOINT_IF_ID);
+	p->mp_fld_pci_end_point_send_msg =
+		register_get_field(p->mp_reg_pci_end_point, PCIE3_PCI_ENDPOINT_SEND_MSG);
+	p->mp_fld_pci_end_point_get_msg =
+		register_get_field(p->mp_reg_pci_end_point, PCIE3_PCI_ENDPOINT_GET_MSG);
+	p->mp_fld_pci_end_point_dmae_p0_allow_mask =
+		register_get_field(p->mp_reg_pci_end_point, PCIE3_PCI_ENDPOINT_DMA_EP0_ALLOW_MASK);
+	p->mp_fld_pci_end_point_dmae_p1_allow_mask =
+		register_get_field(p->mp_reg_pci_end_point, PCIE3_PCI_ENDPOINT_DMA_EP1_ALLOW_MASK);
+	if (p->mp_reg_pci_end_point)
+		register_update(p->mp_reg_pci_end_point);
+
+	p->mp_reg_pci_test0 = module_get_register(p->mp_mod_pcie3, PCIE3_PCI_TEST0);
+	p->mp_fld_pci_test0 =
+		register_get_field(p->mp_reg_pci_test0, PCIE3_PCI_TEST0_DATA);
+	if (p->mp_reg_pci_test0)
+		register_update(p->mp_reg_pci_test0);
+
+	p->mp_reg_pci_test1 = module_get_register(p->mp_mod_pcie3, PCIE3_PCI_TEST1);
+	p->mp_fld_pci_test1 =
+		register_get_field(p->mp_reg_pci_test1, PCIE3_PCI_TEST1_DATA);
+	if (p->mp_reg_pci_test1)
+		register_update(p->mp_reg_pci_test1);
+
+	p->mp_reg_pci_e3_mark_adr_lsb =
+		module_get_register(p->mp_mod_pcie3, PCIE3_MARKADR_LSB);
+	p->mp_fld_pci_e3_mark_adr_lsb_adr =
+		register_get_field(p->mp_reg_pci_e3_mark_adr_lsb, PCIE3_MARKADR_LSB_ADR);
+	if (p->mp_reg_pci_e3_mark_adr_lsb)
+		register_update(p->mp_reg_pci_e3_mark_adr_lsb);
+
+	p->mp_reg_pci_e3_mark_adr_msb =
+		module_get_register(p->mp_mod_pcie3, PCIE3_MARKADR_MSB);
+	p->mp_fld_pci_e3_mark_adr_msb_adr =
+		register_get_field(p->mp_reg_pci_e3_mark_adr_msb, PCIE3_MARKADR_MSB_ADR);
+	if (p->mp_reg_pci_e3_mark_adr_msb)
+		register_update(p->mp_reg_pci_e3_mark_adr_msb);
+
+	/* Initial setup - disable markerscheme and bifurcation */
+	if (p->mp_fld_pci_end_point_dmae_p0_allow_mask)
+		field_clr_flush(p->mp_fld_pci_end_point_dmae_p0_allow_mask);
+
+	if (p->mp_fld_pci_end_point_dmae_p1_allow_mask)
+		field_clr_flush(p->mp_fld_pci_end_point_dmae_p1_allow_mask);
+
+	if (p->mp_fld_pci_e3_mark_adr_lsb_adr)
+		field_set_val_flush32(p->mp_fld_pci_e3_mark_adr_lsb_adr, 0UL);
+
+	if (p->mp_fld_pci_e3_mark_adr_msb_adr)
+		field_set_val_flush32(p->mp_fld_pci_e3_mark_adr_msb_adr, 0UL);
+
+	if (p->mp_fld_pci_end_point_dmae_p0_allow_mask)
+		field_set_flush(p->mp_fld_pci_end_point_dmae_p0_allow_mask);
+
+	if (p->mp_fld_pci_end_point_dmae_p1_allow_mask)
+		field_clr_flush(p->mp_fld_pci_end_point_dmae_p1_allow_mask);
+	return 0;
+};
+
+int nthw_pcie3_trigger_sample_time(nthw_pcie3_t *p)
+{
+	field_set_val_flush32(p->mp_fld_sample_time, 0xfee1dead);
+
+	return 0;
+}
+
+int nthw_pcie3_stat_req_enable(nthw_pcie3_t *p)
+{
+	field_set_all(p->mp_fld_stat_ctrl_ena);
+	field_set_all(p->mp_fld_stat_ctrl_req);
+	field_flush_register(p->mp_fld_stat_ctrl_req);
+	return 0;
+}
+
+int nthw_pcie3_stat_req_disable(nthw_pcie3_t *p)
+{
+	field_clr_all(p->mp_fld_stat_ctrl_ena);
+	field_set_all(p->mp_fld_stat_ctrl_req);
+	field_flush_register(p->mp_fld_stat_ctrl_req);
+	return 0;
+}
+
+int nthw_pcie3_get_stat(nthw_pcie3_t *p, uint32_t *p_rx_cnt, uint32_t *p_tx_cnt,
+			uint32_t *p_ref_clk_cnt, uint32_t *p_tg_unit_size,
+			uint32_t *p_tg_ref_freq, uint32_t *p_tag_use_cnt,
+			uint32_t *p_rq_rdy_cnt, uint32_t *p_rq_vld_cnt)
+{
+	*p_rx_cnt = field_get_updated(p->mp_fld_stat_rx_counter);
+	*p_tx_cnt = field_get_updated(p->mp_fld_stat_tx_counter);
+
+	*p_ref_clk_cnt = field_get_updated(p->mp_fld_stat_ref_clk_ref_clk);
+
+	*p_tg_unit_size = NTHW_TG_CNT_SIZE;
+	*p_tg_ref_freq = NTHW_TG_REF_FREQ;
+
+	*p_tag_use_cnt = field_get_updated(p->mp_fld_status0_tags_in_use);
+
+	*p_rq_rdy_cnt = field_get_updated(p->mp_fld_stat_rq_rdy_counter);
+	*p_rq_vld_cnt = field_get_updated(p->mp_fld_stat_rq_vld_counter);
+
+	return 0;
+}
+
+int nthw_pcie3_get_stat_rate(nthw_pcie3_t *p, uint64_t *p_pci_rx_rate,
+			  uint64_t *p_pci_tx_rate, uint64_t *p_ref_clk_cnt,
+			  uint64_t *p_tag_use_cnt, uint64_t *p_pci_nt_bus_util,
+			  uint64_t *p_pci_xil_bus_util)
+{
+	uint32_t rx_cnt, tx_cnt, ref_clk_cnt;
+	uint32_t tg_unit_size, tg_ref_freq;
+	uint32_t tag_use_cnt, rq_rdy_cnt, rq_vld_cnt;
+
+	nthw_pcie3_get_stat(p, &rx_cnt, &tx_cnt, &ref_clk_cnt, &tg_unit_size,
+			  &tg_ref_freq, &tag_use_cnt, &rq_rdy_cnt, &rq_vld_cnt);
+
+	if (ref_clk_cnt) {
+		uint64_t nt_bus_util, xil_bus_util;
+		uint64_t rx_rate, tx_rate;
+
+		rx_rate = ((uint64_t)rx_cnt * tg_unit_size * tg_ref_freq) /
+			 (uint64_t)ref_clk_cnt;
+		*p_pci_rx_rate = rx_rate;
+
+		tx_rate = ((uint64_t)tx_cnt * tg_unit_size * tg_ref_freq) /
+			 (uint64_t)ref_clk_cnt;
+		*p_pci_tx_rate = tx_rate;
+
+		*p_ref_clk_cnt = ref_clk_cnt;
+
+		*p_tag_use_cnt = tag_use_cnt;
+
+		nt_bus_util =
+			((uint64_t)rq_vld_cnt * 1000000ULL) / (uint64_t)ref_clk_cnt;
+		*p_pci_nt_bus_util = nt_bus_util;
+		xil_bus_util =
+			((uint64_t)rq_rdy_cnt * 1000000ULL) / (uint64_t)ref_clk_cnt;
+		*p_pci_xil_bus_util = xil_bus_util;
+	} else {
+		*p_ref_clk_cnt = 0;
+		*p_pci_nt_bus_util = 0;
+		*p_pci_xil_bus_util = 0;
+	}
+
+	return 0;
+}
+
+int nthw_pcie3_end_point_counters_sample_pre(nthw_pcie3_t *p,
+					struct nthw_hif_end_point_counters *epc)
+{
+	NT_LOG(DBG, NTHW, "%s:%u: empty function\n", __func__, __LINE__);
+
+	(void)p;
+	(void)epc;
+
+	return 0;
+}
+
+int nthw_pcie3_end_point_counters_sample_post(nthw_pcie3_t *p,
+		struct nthw_hif_end_point_counters *epc)
+{
+	NT_LOG(DBG, NTHW, "%s:%u:\n", __func__, __LINE__);
+	assert(epc);
+	nthw_pcie3_get_stat_rate(p, &epc->cur_tx, &epc->cur_rx, &epc->n_ref_clk_cnt,
+			      &epc->n_tags_in_use, &epc->cur_pci_nt_util,
+			      &epc->cur_pci_xil_util);
+	return 0;
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_pcie3.h b/drivers/net/ntnic/nthw/core/nthw_pcie3.h
new file mode 100644
index 0000000000..beb79a9577
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_pcie3.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_PCIE3_H__
+#define __NTHW_PCIE3_H__
+
+struct nthw_pcie3 {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_pcie3;
+	int mn_instance;
+
+	nt_register_t *mp_reg_stat_ctrl;
+	nt_field_t *mp_fld_stat_ctrl_req;
+	nt_field_t *mp_fld_stat_ctrl_ena;
+
+	nt_register_t *mp_reg_stat_rx;
+	nt_field_t *mp_fld_stat_rx_counter;
+
+	nt_register_t *mp_reg_stat_tx;
+	nt_field_t *mp_fld_stat_tx_counter;
+
+	nt_register_t *mp_reg_stat_rq_rdy;
+	nt_field_t *mp_fld_stat_rq_rdy_counter;
+
+	nt_register_t *mp_reg_stat_rq_vld;
+	nt_field_t *mp_fld_stat_rq_vld_counter;
+
+	nt_register_t *mp_reg_status0;
+	nt_field_t *mp_fld_status0_tags_in_use;
+
+	nt_register_t *mp_reg_stat_ref_clk;
+	nt_field_t *mp_fld_stat_ref_clk_ref_clk;
+
+	nt_register_t *mp_reg_rp_to_ep_err;
+	nt_field_t *mp_fld_rp_to_ep_err_cor;
+	nt_field_t *mp_fld_rp_to_ep_err_non_fatal;
+	nt_field_t *mp_fld_rp_to_ep_err_fatal;
+
+	nt_register_t *mp_reg_ep_to_rp_err;
+	nt_field_t *mp_fld_ep_to_rp_err_cor;
+	nt_field_t *mp_fld_ep_to_rp_err_non_fatal;
+	nt_field_t *mp_fld_ep_to_rp_err_fatal;
+
+	nt_register_t *mp_reg_sample_time;
+	nt_field_t *mp_fld_sample_time;
+
+	nt_register_t *mp_reg_pci_end_point;
+	nt_field_t *mp_fld_pci_end_point_if_id;
+	nt_field_t *mp_fld_pci_end_point_send_msg;
+	nt_field_t *mp_fld_pci_end_point_get_msg;
+	nt_field_t *mp_fld_pci_end_point_dmae_p0_allow_mask;
+	nt_field_t *mp_fld_pci_end_point_dmae_p1_allow_mask;
+
+	nt_register_t *mp_reg_pci_e3_mark_adr_lsb;
+	nt_field_t *mp_fld_pci_e3_mark_adr_lsb_adr;
+
+	nt_register_t *mp_reg_pci_e3_mark_adr_msb;
+	nt_field_t *mp_fld_pci_e3_mark_adr_msb_adr;
+
+	nt_register_t *mp_reg_pci_test0;
+	nt_field_t *mp_fld_pci_test0;
+
+	nt_register_t *mp_reg_pci_test1;
+	nt_field_t *mp_fld_pci_test1;
+
+	nt_register_t *mp_reg_pci_test2;
+	nt_field_t *mp_fld_pci_test2;
+
+	nt_register_t *mp_reg_pci_test3;
+	nt_field_t *mp_fld_pci_test3;
+};
+
+typedef struct nthw_pcie3 nthw_pcie3_t;
+typedef struct nthw_pcie3 nthw_pcie3;
+
+nthw_pcie3_t *nthw_pcie3_new(void);
+void nthw_pcie3_delete(nthw_pcie3_t *p);
+int nthw_pcie3_init(nthw_pcie3_t *p, nt_fpga_t *p_fpga, int n_instance);
+
+int nthw_pcie3_trigger_sample_time(nthw_pcie3_t *p);
+
+int nthw_pcie3_stat_req_enable(nthw_pcie3_t *p);
+int nthw_pcie3_stat_req_disable(nthw_pcie3_t *p);
+
+int nthw_pcie3_get_stat(nthw_pcie3_t *p, uint32_t *p_rx_cnt, uint32_t *p_tx_cnt,
+		      uint32_t *p_ref_clk_cnt, uint32_t *p_tg_unit_size,
+		      uint32_t *p_tg_ref_freq, uint32_t *p_tag_use_cnt,
+		      uint32_t *p_rq_rdy_cnt, uint32_t *p_rq_vld_cnt);
+int nthw_pcie3_get_stat_rate(nthw_pcie3_t *p, uint64_t *p_pci_rx_rate,
+			  uint64_t *p_pci_tx_rate, uint64_t *p_ref_clk_cnt,
+			  uint64_t *p_tag_use_cnt, uint64_t *p_pci_nt_bus_util,
+			  uint64_t *p_pci_xil_bus_util);
+
+int nthw_pcie3_end_point_counters_sample_pre(nthw_pcie3_t *p,
+					struct nthw_hif_end_point_counters *epc);
+int nthw_pcie3_end_point_counters_sample_post(nthw_pcie3_t *p,
+		struct nthw_hif_end_point_counters *epc);
+
+#endif /* __NTHW_PCIE3_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_sdc.c b/drivers/net/ntnic/nthw/core/nthw_sdc.c
new file mode 100644
index 0000000000..0547b92c47
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_sdc.c
@@ -0,0 +1,177 @@
+/* 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_sdc.h"
+
+nthw_sdc_t *nthw_sdc_new(void)
+{
+	nthw_sdc_t *p = malloc(sizeof(nthw_sdc_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_sdc_t));
+	return p;
+}
+
+void nthw_sdc_delete(nthw_sdc_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_sdc_t));
+		free(p);
+	}
+}
+
+int nthw_sdc_init(nthw_sdc_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 *mod = fpga_query_module(p_fpga, MOD_SDC, n_instance);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: SDC %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_sdc = mod;
+
+	{
+		nt_register_t *p_reg;
+
+		p_reg = module_get_register(p->mp_mod_sdc, SDC_CTRL);
+		p->mp_fld_ctrl_init = register_get_field(p_reg, SDC_CTRL_INIT);
+		p->mp_fld_ctrl_run_test =
+			register_get_field(p_reg, SDC_CTRL_RUN_TEST);
+		p->mp_fld_ctrl_stop_client =
+			register_get_field(p_reg, SDC_CTRL_STOP_CLIENT);
+		p->mp_fld_ctrl_test_enable =
+			register_get_field(p_reg, SDC_CTRL_TEST_EN);
+
+		p_reg = module_get_register(p->mp_mod_sdc, SDC_STAT);
+		p->mp_fld_stat_calib = register_get_field(p_reg, SDC_STAT_CALIB);
+		p->mp_fld_stat_cell_cnt_stopped =
+			register_get_field(p_reg, SDC_STAT_CELL_CNT_STOPPED);
+		p->mp_fld_stat_err_found =
+			register_get_field(p_reg, SDC_STAT_ERR_FOUND);
+		p->mp_fld_stat_init_done =
+			register_get_field(p_reg, SDC_STAT_INIT_DONE);
+		p->mp_fld_stat_mmcm_lock =
+			register_get_field(p_reg, SDC_STAT_MMCM_LOCK);
+		p->mp_fld_stat_pll_lock =
+			register_get_field(p_reg, SDC_STAT_PLL_LOCK);
+		p->mp_fld_stat_resetting =
+			register_get_field(p_reg, SDC_STAT_RESETTING);
+
+		p_reg = module_get_register(p->mp_mod_sdc, SDC_CELL_CNT);
+		p->mp_fld_cell_cnt =
+			register_get_field(p_reg, SDC_CELL_CNT_CELL_CNT);
+
+		p_reg = module_get_register(p->mp_mod_sdc, SDC_CELL_CNT_PERIOD);
+		p->mp_fld_cell_cnt_period =
+			register_get_field(p_reg, SDC_CELL_CNT_PERIOD_CELL_CNT_PERIOD);
+
+		p_reg = module_get_register(p->mp_mod_sdc, SDC_FILL_LVL);
+		p->mp_fld_fill_level =
+			register_get_field(p_reg, SDC_FILL_LVL_FILL_LVL);
+
+		p_reg = module_get_register(p->mp_mod_sdc, SDC_MAX_FILL_LVL);
+		p->mp_fld_max_fill_level =
+			register_get_field(p_reg, SDC_MAX_FILL_LVL_MAX_FILL_LVL);
+	}
+	return 0;
+}
+
+int nthw_sdc_get_states(nthw_sdc_t *p, uint64_t *pn_result_mask)
+{
+	int n_err_cnt = 0;
+	uint64_t n_mask = 0;
+	uint32_t val;
+	uint32_t val_mask;
+	int n_val_width;
+
+	if (!p || !pn_result_mask)
+		return -1;
+
+	val = field_get_updated(p->mp_fld_stat_calib);
+	n_val_width = field_get_bit_width(p->mp_fld_stat_calib);
+	val_mask = ((1 << n_val_width) - 1);
+	n_mask = (n_mask << n_val_width) | (val & val_mask);
+	if (val != val_mask)
+		n_err_cnt++;
+
+	val = field_get_updated(p->mp_fld_stat_init_done);
+	n_val_width = field_get_bit_width(p->mp_fld_stat_init_done);
+	val_mask = ((1 << n_val_width) - 1);
+	n_mask = (n_mask << n_val_width) | (val & val_mask);
+	if (val != val_mask)
+		n_err_cnt++;
+
+	val = field_get_updated(p->mp_fld_stat_mmcm_lock);
+	n_val_width = field_get_bit_width(p->mp_fld_stat_mmcm_lock);
+	val_mask = ((1 << n_val_width) - 1);
+	n_mask = (n_mask << n_val_width) | (val & val_mask);
+	if (val != val_mask)
+		n_err_cnt++;
+
+	val = field_get_updated(p->mp_fld_stat_pll_lock);
+	n_val_width = field_get_bit_width(p->mp_fld_stat_pll_lock);
+	val_mask = ((1 << n_val_width) - 1);
+	n_mask = (n_mask << n_val_width) | (val & val_mask);
+	if (val != val_mask)
+		n_err_cnt++;
+
+	val = field_get_updated(p->mp_fld_stat_resetting);
+	n_val_width = field_get_bit_width(p->mp_fld_stat_resetting);
+	val_mask = ((1 << n_val_width) - 1);
+	n_mask = (n_mask << n_val_width) | (val & val_mask);
+	if (val != 0)
+		n_err_cnt++;
+
+	if (pn_result_mask)
+		*pn_result_mask = n_mask;
+
+	return n_err_cnt; /* 0 = all ok */
+}
+
+int nthw_sdc_wait_states(nthw_sdc_t *p, const int n_poll_iterations,
+		       const int n_poll_interval)
+{
+	int res;
+	int n_err_cnt = 0;
+
+	res = field_wait_set_all32(p->mp_fld_stat_calib, n_poll_iterations,
+				 n_poll_interval);
+	if (res)
+		n_err_cnt++;
+
+	res = field_wait_set_all32(p->mp_fld_stat_init_done, n_poll_iterations,
+				 n_poll_interval);
+	if (res)
+		n_err_cnt++;
+
+	res = field_wait_set_all32(p->mp_fld_stat_mmcm_lock, n_poll_iterations,
+				 n_poll_interval);
+	if (res)
+		n_err_cnt++;
+
+	res = field_wait_set_all32(p->mp_fld_stat_pll_lock, n_poll_iterations,
+				 n_poll_interval);
+	if (res)
+		n_err_cnt++;
+
+	res = field_wait_clr_all32(p->mp_fld_stat_resetting, n_poll_iterations,
+				 n_poll_interval);
+	if (res)
+		n_err_cnt++;
+
+	return n_err_cnt; /* 0 = all ok */
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_sdc.h b/drivers/net/ntnic/nthw/core/nthw_sdc.h
new file mode 100644
index 0000000000..e6c08ffbc3
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_sdc.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_SDC_H__
+#define __NTHW_SDC_H__
+
+struct nthw_sdc {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_sdc;
+	int mn_instance;
+
+	nt_field_t *mp_fld_ctrl_init;
+	nt_field_t *mp_fld_ctrl_run_test;
+	nt_field_t *mp_fld_ctrl_stop_client;
+	nt_field_t *mp_fld_ctrl_test_enable;
+
+	nt_field_t *mp_fld_stat_calib;
+	nt_field_t *mp_fld_stat_cell_cnt_stopped;
+	nt_field_t *mp_fld_stat_err_found;
+	nt_field_t *mp_fld_stat_init_done;
+	nt_field_t *mp_fld_stat_mmcm_lock;
+	nt_field_t *mp_fld_stat_pll_lock;
+	nt_field_t *mp_fld_stat_resetting;
+
+	nt_field_t *mp_fld_cell_cnt;
+	nt_field_t *mp_fld_cell_cnt_period;
+	nt_field_t *mp_fld_fill_level;
+	nt_field_t *mp_fld_max_fill_level;
+};
+
+typedef struct nthw_sdc nthw_sdc_t;
+typedef struct nthw_sdc nthw_sdc;
+
+nthw_sdc_t *nthw_sdc_new(void);
+int nthw_sdc_init(nthw_sdc_t *p, nt_fpga_t *p_fpga, int n_instance);
+void nthw_sdc_delete(nthw_sdc_t *p);
+
+int nthw_sdc_wait_states(nthw_sdc_t *p, const int n_poll_iterations,
+		       const int n_poll_interval);
+int nthw_sdc_get_states(nthw_sdc_t *p, uint64_t *pn_result_mask);
+
+#endif /* __NTHW_SDC_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_si5340.c b/drivers/net/ntnic/nthw/core/nthw_si5340.c
new file mode 100644
index 0000000000..3337f1f9e3
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_si5340.c
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ *
+ * This file implements Si5340 clock synthesizer support.
+ * The implementation is generic and must be tailored to a specific use by the
+ * correct initialization data.
+ */
+
+#include "nt_util.h"
+#include "ntlog.h"
+
+#include "nthw_drv.h"
+#include "nthw_register.h"
+
+#include "nthw_si5340.h"
+
+#define SI5340_WAIT_US(x) NT_OS_WAIT_USEC(x)
+
+#define SI5340_LOG_DEBUG(...) NT_LOG(DBG, NTHW, __VA_ARGS__)
+#define SI5340_LOG_INFO(...) NT_LOG(INF, NTHW, __VA_ARGS__)
+#define SI5340_LOG_WARN(...) NT_LOG(WRN, NTHW, __VA_ARGS__)
+#define SI5340_LOG_ERROR(...) NT_LOG(ERR, NTHW, __VA_ARGS__)
+
+#define SI5340_PAGE_REG_ADDR (0x01)
+
+nthw_si5340_t *nthw_si5340_new(void)
+{
+	nthw_si5340_t *p = malloc(sizeof(nthw_si5340_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_si5340_t));
+	return p;
+}
+
+int nthw_si5340_init(nthw_si5340_t *p, nthw_iic_t *p_nthw_iic, uint8_t n_iic_addr)
+{
+	uint8_t data;
+
+	p->mp_nthw_iic = p_nthw_iic;
+	p->mn_iic_addr = n_iic_addr;
+	p->mn_clk_cfg = -1;
+
+	p->m_si5340_page = 0;
+	data = p->m_si5340_page;
+	nthw_iic_write_data(p->mp_nthw_iic, p->mn_iic_addr, SI5340_PAGE_REG_ADDR, 1,
+			  &data);
+
+	return 0;
+}
+
+void nthw_si5340_delete(nthw_si5340_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_si5340_t));
+		free(p);
+	}
+}
+
+/*
+ * Read access (via I2C) to the clock synthesizer IC. The IC is located at I2C
+ * 7bit address 0x74
+ */
+static uint8_t nthw_si5340_read(nthw_si5340_t *p, uint16_t reg_addr)
+{
+	const uint8_t offset_adr = (uint8_t)(reg_addr & 0xff);
+	uint8_t page = (uint8_t)((reg_addr >> 8) & 0xff);
+	uint8_t data;
+
+	/* check if we are on the right page */
+	if (page != p->m_si5340_page) {
+		nthw_iic_write_data(p->mp_nthw_iic, p->mn_iic_addr,
+				  SI5340_PAGE_REG_ADDR, 1, &page);
+		p->m_si5340_page = page;
+	}
+	nthw_iic_read_data(p->mp_nthw_iic, p->mn_iic_addr, offset_adr, 1, &data);
+	return data;
+}
+
+/*
+ * Write access (via I2C) to the clock synthesizer IC. The IC is located at I2C
+ * 7 bit address 0x74
+ */
+static int nthw_si5340_write(nthw_si5340_t *p, uint16_t reg_addr, uint8_t data)
+{
+	const uint8_t offset_adr = (uint8_t)(reg_addr & 0xff);
+	uint8_t page = (uint8_t)((reg_addr >> 8) & 0xff);
+
+	/* check if we are on the right page */
+	if (page != p->m_si5340_page) {
+		nthw_iic_write_data(p->mp_nthw_iic, p->mn_iic_addr,
+				  SI5340_PAGE_REG_ADDR, 1, &page);
+		p->m_si5340_page = page;
+	}
+	nthw_iic_write_data(p->mp_nthw_iic, p->mn_iic_addr, offset_adr, 1, &data);
+
+	return 0;
+}
+
+static int nthw_si5340_cfg(nthw_si5340_t *p, const void *p_data, int data_cnt,
+			  clk_profile_data_fmt_t data_format)
+{
+	const char *const p_adapter_id_str =
+		p->mp_nthw_iic->mp_fpga->p_fpga_info->mp_adapter_id_str;
+	int i;
+	uint16_t addr;
+	uint8_t value;
+	uint8_t ctrl_value;
+
+	NT_LOG(DBG, NTHW, "%s: %s: data_cnt = %d, dataFormat = %d\n",
+	       p_adapter_id_str, __func__, data_cnt, data_format);
+
+	for (i = 0; i < data_cnt; i++) {
+		if (data_format == CLK_PROFILE_DATA_FMT_1) {
+			addr = ((const clk_profile_data_fmt1_t *)p_data)->reg_addr;
+			value = ((const clk_profile_data_fmt1_t *)p_data)->reg_val;
+			p_data = ((const clk_profile_data_fmt1_t *)p_data) + 1;
+		} else if (data_format == CLK_PROFILE_DATA_FMT_2) {
+			addr = (uint16_t)(((const clk_profile_data_fmt2_t *)p_data)
+					  ->reg_addr);
+			value = ((const clk_profile_data_fmt2_t *)p_data)->reg_val;
+			p_data = ((const clk_profile_data_fmt2_t *)p_data) + 1;
+		} else {
+			NT_LOG(ERR, NTHW,
+			       "%s: Unhandled Si5340 data format (%d)\n",
+			       p_adapter_id_str, data_format);
+			return -1;
+		}
+
+		if (addr == 0x0006) {
+			/* Wait 300ms before continuing. See NT200E3-2-PTP_U23_Si5340_adr0_v2.h */
+			NT_OS_WAIT_USEC(300000);
+		}
+
+		nthw_si5340_write(p, addr, value);
+
+		if (addr == 0x001C) {
+			/* skip readback for "soft reset" register */
+			continue;
+		}
+
+		ctrl_value = nthw_si5340_read(p, addr);
+
+		if (ctrl_value != value) {
+			NT_LOG(ERR, NTHW,
+			       "%s: Si5340 configuration readback check failed. (Addr = 0x%04X, Write = 0x%02X, Read = 0x%02X)\n",
+			       p_adapter_id_str, addr, value, ctrl_value);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+int nthw_si5340_config(nthw_si5340_t *p, const void *p_data, int data_cnt,
+		      clk_profile_data_fmt_t data_format)
+{
+	const char *const p_adapter_id_str =
+		p->mp_nthw_iic->mp_fpga->p_fpga_info->mp_adapter_id_str;
+	int i;
+	bool success = false;
+	uint8_t status, sticky;
+	uint8_t design_id[9];
+
+	(void)nthw_si5340_cfg(p, p_data, data_cnt, data_format);
+
+	/* Check if DPLL is locked and SYS is calibrated */
+	for (i = 0; i < 5; i++) {
+		status = nthw_si5340_read(p, 0x0c);
+		sticky = nthw_si5340_read(p, 0x11);
+		nthw_si5340_write(p, 0x11, 0x00);
+
+		if (((status & 0x09) == 0x00) && ((sticky & 0x09) == 0x00)) {
+			success = true;
+			break;
+		}
+		NT_OS_WAIT_USEC(1000000); /* 1 sec */
+	}
+
+	if (!success) {
+		NT_LOG(ERR, NTHW,
+		       "%s: Si5340 configuration failed. (Status = 0x%02X, Sticky = 0x%02X)\n",
+		       p_adapter_id_str, status, sticky);
+		return -1;
+	}
+
+	for (i = 0; i < (int)sizeof(design_id) - 1; i++)
+		design_id[i] = nthw_si5340_read(p, (uint16_t)(0x26B + i));
+	design_id[sizeof(design_id) - 1] = 0;
+
+	(void)design_id; /* Only used in debug mode */
+	NT_LOG(DBG, NTHW, "%s: Si5340.DesignId = %s\n", p_adapter_id_str,
+	       design_id);
+
+	return 0;
+}
+
+int nthw_si5340_config_fmt1(nthw_si5340_t *p, const clk_profile_data_fmt1_t *p_data,
+			  const int data_cnt)
+{
+	return nthw_si5340_config(p, p_data, data_cnt, CLK_PROFILE_DATA_FMT_1);
+}
+
+int nthw_si5340_config_fmt2(nthw_si5340_t *p, const clk_profile_data_fmt2_t *p_data,
+			  const int data_cnt)
+{
+	return nthw_si5340_config(p, p_data, data_cnt, CLK_PROFILE_DATA_FMT_2);
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_si5340.h b/drivers/net/ntnic/nthw/core/nthw_si5340.h
new file mode 100644
index 0000000000..f588b5b825
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_si5340.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_SI5340_H__
+#define __NTHW_SI5340_H__
+
+#include "nthw_clock_profiles.h"
+
+#define SI5340_SUCCESS (0)
+#define SI5340_FAILED (999)
+#define SI5340_TIMEOUT (666)
+
+struct nthw_si5340 {
+	uint8_t mn_iic_addr;
+	nthw_iic_t *mp_nthw_iic;
+	int mn_clk_cfg;
+	uint8_t m_si5340_page;
+};
+
+typedef struct nthw_si5340 nthw_si5340_t;
+
+nthw_si5340_t *nthw_si5340_new(void);
+int nthw_si5340_init(nthw_si5340_t *p, nthw_iic_t *p_nthw_iic, uint8_t n_iic_addr);
+void nthw_si5340_delete(nthw_si5340_t *p);
+
+int nthw_si5340_config(nthw_si5340_t *p, const void *p_data, int data_cnt,
+		      clk_profile_data_fmt_t data_format);
+int nthw_si5340_config_fmt1(nthw_si5340_t *p, const clk_profile_data_fmt1_t *p_data,
+			  const int data_cnt);
+int nthw_si5340_config_fmt2(nthw_si5340_t *p, const clk_profile_data_fmt2_t *p_data,
+			  const int data_cnt);
+
+#endif /* __NTHW_SI5338_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_spi_v3.c b/drivers/net/ntnic/nthw/core/nthw_spi_v3.c
new file mode 100644
index 0000000000..454c9b73b8
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_spi_v3.c
@@ -0,0 +1,380 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "ntlog.h"
+
+#include "nthw_drv.h"
+#include "nthw_fpga.h"
+
+#include "nthw_spi_v3.h"
+
+#include <arpa/inet.h>
+
+#undef SPI_V3_DEBUG_PRINT
+
+nthw_spi_v3_t *nthw_spi_v3_new(void)
+{
+	nthw_spi_v3_t *p = malloc(sizeof(nthw_spi_v3_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_spi_v3_t));
+	return p;
+}
+
+void nthw_spi_v3_delete(nthw_spi_v3_t *p)
+{
+	if (p) {
+		if (p->mp_spim_mod) {
+			nthw_spim_delete(p->mp_spim_mod);
+			p->mp_spim_mod = NULL;
+		}
+
+		if (p->mp_spis_mod) {
+			nthw_spis_delete(p->mp_spis_mod);
+			p->mp_spis_mod = NULL;
+		}
+
+		memset(p, 0, sizeof(nthw_spi_v3_t));
+		free(p);
+	}
+}
+
+int nthw_spi_v3_set_timeout(nthw_spi_v3_t *p, int time_out)
+{
+	p->m_time_out = time_out;
+	return 0;
+}
+
+int nthw_spi_v3_get_version(nthw_spi_v3_t *p)
+{
+	(void)p;
+	return 3;
+}
+
+/*
+ * Wait until Tx data have been sent after they have been placed in the Tx FIFO.
+ */
+static int wait_for_tx_data_sent(nthw_spim_t *p_spim_mod, uint64_t time_out)
+{
+	int result;
+	bool empty;
+	uint64_t start_time;
+	uint64_t cur_time;
+
+	start_time = NT_OS_GET_TIME_MONOTONIC_COUNTER();
+
+	while (true) {
+		NT_OS_WAIT_USEC(1000); /* Every 1ms */
+
+		result = nthw_spim_get_tx_fifo_empty(p_spim_mod, &empty);
+		if (result != 0) {
+			NT_LOG(WRN, NTHW,
+			       "nthw_spim_get_tx_fifo_empty failed\n");
+			return result;
+		}
+
+		if (empty)
+			break;
+
+		cur_time = NT_OS_GET_TIME_MONOTONIC_COUNTER();
+		if ((cur_time - start_time) > time_out) {
+			NT_LOG(WRN, NTHW, "%s: Timed out\n", __func__);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Wait until Rx data have been received.
+ */
+static int wait_for_rx_data_ready(nthw_spis_t *p_spis_mod, uint64_t time_out)
+{
+	int result;
+	bool empty;
+	uint64_t start_time;
+	uint64_t cur_time;
+
+	start_time = NT_OS_GET_TIME_MONOTONIC_COUNTER();
+
+	/* Wait for data to become ready in the Rx FIFO */
+	while (true) {
+		NT_OS_WAIT_USEC(10000); /* Every 10ms */
+
+		result = nthw_spis_get_rx_fifo_empty(p_spis_mod, &empty);
+		if (result != 0) {
+			NT_LOG(WRN, NTHW, "nthw_spis_get_rx_empty failed\n");
+			return result;
+		}
+
+		if (!empty)
+			break;
+
+		cur_time = NT_OS_GET_TIME_MONOTONIC_COUNTER();
+		if ((cur_time - start_time) > time_out) {
+			NT_LOG(WRN, NTHW, "%s: Timed out\n", __func__);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+#ifdef SPI_V3_DEBUG_PRINT
+static void dump_hex(uint8_t *p_data, uint16_t count)
+{
+	int i;
+	int j = 0;
+	char tmp_str[128];
+
+	for (i = 0; i < count; i++) {
+		sprintf(&tmp_str[j * 3], "%02X ", *(p_data++));
+		j++;
+
+		if (j == 16 || (i == count - 1)) {
+			tmp_str[j * 3 - 1] = '\0';
+			NT_LOG(DBG, NTHW, "    %s\n", tmp_str);
+			j = 0;
+		}
+	}
+}
+#endif
+
+int nthw_spi_v3_init(nthw_spi_v3_t *p, nt_fpga_t *p_fpga, int n_instance_no)
+{
+	const char *const p_adapter_id_str = p_fpga->p_fpga_info->mp_adapter_id_str;
+	uint32_t result;
+
+	p->mn_instance_no = n_instance_no;
+
+	nthw_spi_v3_set_timeout(p, 1);
+
+	/* Initialize SPIM module */
+	p->mp_spim_mod = nthw_spim_new();
+
+	result = nthw_spim_init(p->mp_spim_mod, p_fpga, n_instance_no);
+	if (result != 0) {
+		NT_LOG(ERR, NTHW, "%s: nthw_spis_init failed: %d\n",
+		       p_adapter_id_str, result);
+	}
+
+	/* Initialize SPIS module */
+	p->mp_spis_mod = nthw_spis_new();
+
+	result = nthw_spis_init(p->mp_spis_mod, p_fpga, n_instance_no);
+	if (result != 0) {
+		NT_LOG(ERR, NTHW, "%s: nthw_spim_init failed: %d\n",
+		       p_adapter_id_str, result);
+	}
+
+	/* Reset SPIM and SPIS modules */
+	result = nthw_spim_reset(p->mp_spim_mod);
+	if (result != 0) {
+		NT_LOG(ERR, NTHW, "%s: nthw_spim_reset failed: %d\n",
+		       p_adapter_id_str, result);
+	}
+
+	result = nthw_spis_reset(p->mp_spis_mod);
+	if (result != 0) {
+		NT_LOG(ERR, NTHW, "%s: nthw_spis_reset failed: %d\n",
+		       p_adapter_id_str, result);
+	}
+
+	return result;
+}
+
+/*
+ * Send Tx data using the SPIM module and receive any data using the SPIS module.
+ * The data are sent and received being wrapped into a SPI v3 container.
+ */
+int nthw_spi_v3_transfer(nthw_spi_v3_t *p, uint16_t opcode,
+			 struct tx_rx_buf *tx_buf, struct tx_rx_buf *rx_buf)
+{
+	const uint16_t max_payload_rx_size = rx_buf->size;
+	int result = 0;
+
+#pragma pack(push, 1)
+	union {
+		uint32_t raw;
+
+		struct {
+			uint16_t opcode;
+			uint16_t size;
+		};
+	} spi_tx_hdr;
+
+	union {
+		uint32_t raw;
+
+		struct {
+			uint16_t error_code;
+			uint16_t size;
+		};
+	} spi_rx_hdr;
+#pragma pack(pop)
+
+#ifdef SPI_V3_DEBUG_PRINT
+	NT_LOG(DBG, NTHW, "%s:  Started\n", __func__);
+#endif
+
+	/* Disable transmission from Tx FIFO */
+	result = nthw_spim_enable(p->mp_spim_mod, false);
+	if (result != 0) {
+		NT_LOG(WRN, NTHW, "nthw_spim_enable failed\n");
+		return result;
+	}
+
+	/* Enable SPIS module */
+	result = nthw_spis_enable(p->mp_spis_mod, true);
+	if (result != 0) {
+		NT_LOG(WRN, NTHW, "nthw_spis_enable failed\n");
+		return result;
+	}
+
+	/* Put data into Tx FIFO */
+	spi_tx_hdr.opcode = opcode;
+	spi_tx_hdr.size = tx_buf->size;
+
+#ifdef SPI_V3_DEBUG_PRINT
+	NT_LOG(DBG, NTHW, "opcode=0x%04X tx_bufsize=0x%04X rx_bufsize=0x%04X\n",
+	       opcode, tx_buf->size, rx_buf->size);
+
+#endif /* SPI_V3_DEBUG_PRINT */
+
+	result = nthw_spim_write_tx_fifo(p->mp_spim_mod, htonl(spi_tx_hdr.raw));
+	if (result != 0) {
+		NT_LOG(WRN, NTHW, "nthw_spim_write_tx_fifo failed\n");
+		return result;
+	}
+
+	{
+		uint8_t *tx_data = (uint8_t *)tx_buf->p_buf;
+		uint16_t tx_size = tx_buf->size;
+		uint16_t count;
+		uint32_t value;
+
+		while (tx_size > 0) {
+			if (tx_size > 4) {
+				count = 4;
+			} else {
+				count = tx_size;
+				value = 0;
+			}
+
+			memcpy(&value, tx_data, count);
+
+			result = nthw_spim_write_tx_fifo(p->mp_spim_mod,
+							htonl(value));
+			if (result != 0) {
+				NT_LOG(WRN, NTHW,
+				       "nthw_spim_write_tx_fifo failed\n");
+				return result;
+			}
+
+			tx_size = (uint16_t)(tx_size - count);
+			tx_data += count;
+		}
+	}
+
+	/* Enable Tx FIFO */
+	result = nthw_spim_enable(p->mp_spim_mod, true);
+	if (result != 0) {
+		NT_LOG(WRN, NTHW, "nthw_spim_enable failed\n");
+		return result;
+	}
+
+	result = wait_for_tx_data_sent(p->mp_spim_mod, p->m_time_out);
+	if (result != 0)
+		return result;
+
+#ifdef SPI_V3_DEBUG_PRINT
+	NT_LOG(DBG, NTHW, "%s: SPI header and payload data have been sent\n",
+	       __func__);
+#endif
+
+	{
+		/*
+		 * Start receiving data
+		 * The first data to read is the header
+		 */
+		uint16_t rx_size = sizeof(spi_rx_hdr.raw);
+		uint8_t *rx_data = (uint8_t *)rx_buf->p_buf;
+		bool rx_hdr_read = false;
+
+		rx_buf->size = 0;
+
+		while (true) {
+			uint16_t count;
+			uint32_t value;
+
+			if (!rx_hdr_read) { /* Read the header */
+				result = wait_for_rx_data_ready(p->mp_spis_mod,
+								p->m_time_out);
+				if (result != 0)
+					return result;
+
+				result = nthw_spis_read_rx_fifo(p->mp_spis_mod,
+								&spi_rx_hdr.raw);
+				if (result != 0) {
+					NT_LOG(WRN, NTHW,
+					       "nthw_spis_read_rx_fifo failed\n");
+					return result;
+				}
+
+				spi_rx_hdr.raw = ntohl(spi_rx_hdr.raw);
+				rx_size = spi_rx_hdr.size;
+				rx_hdr_read = true; /* Next time read payload */
+
+#ifdef SPI_V3_DEBUG_PRINT
+				NT_LOG(DBG, NTHW,
+				       "  spi_rx_hdr.error_code = 0x%04X, spi_rx_hdr.size = 0x%04X\n",
+				       spi_rx_hdr.error_code, spi_rx_hdr.size);
+#endif
+
+				if (spi_rx_hdr.error_code != 0) {
+					result = -1; /* NT_ERROR_AVR_OPCODE_RETURNED_ERROR; */
+					break;
+				}
+
+				if (rx_size > max_payload_rx_size) {
+					result = 1; /* NT_ERROR_AVR_RX_BUFFER_TOO_SMALL; */
+					break;
+				}
+			} else { /* Read the payload */
+				count = (uint16_t)(rx_size < 4U ? rx_size : 4U);
+
+				if (count == 0)
+					break;
+
+				result = wait_for_rx_data_ready(p->mp_spis_mod,
+							    p->m_time_out);
+				if (result != 0)
+					return result;
+
+				result = nthw_spis_read_rx_fifo(p->mp_spis_mod,
+							       &value);
+				if (result != 0) {
+					NT_LOG(WRN, NTHW,
+					       "nthw_spis_read_rx_fifo failed\n");
+					return result;
+				}
+
+				value = ntohl(value); /* Convert to host endian */
+				memcpy(rx_data, &value, count);
+				rx_buf->size = (uint16_t)(rx_buf->size + count);
+				rx_size = (uint16_t)(rx_size - count);
+				rx_data += count;
+			}
+		}
+	}
+
+#ifdef SPI_V3_DEBUG_PRINT
+	NT_LOG(DBG, NTHW, "  RxData: %d\n", rx_buf->size);
+	dump_hex(rx_buf->p_buf, rx_buf->size);
+	NT_LOG(DBG, NTHW, "%s:  Ended: %d\n", __func__, result);
+#endif
+
+	return result;
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_spi_v3.h b/drivers/net/ntnic/nthw/core/nthw_spi_v3.h
new file mode 100644
index 0000000000..c54379a273
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_spi_v3.h
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NT4GA_SPI_V3__
+#define __NT4GA_SPI_V3__
+
+/* Must include v1.x series. The first v1.0a only had 248 bytes of storage. v2.0x have 255 */
+#define MAX_AVR_CONTAINER_SIZE (248)
+
+enum avr_opcodes {
+	AVR_OP_NOP = 0, /* v2 NOP command */
+	/* version handlers */
+	AVR_OP_VERSION = 1,
+	AVR_OP_SPI_VERSION = 2, /* v2.0+ command Get protocol version */
+	AVR_OP_SYSINFO = 3,
+	/* Ping handlers */
+	AVR_OP_PING = 4,
+	AVR_OP_PING_DELAY = 5,
+	/* i2c handlers */
+	AVR_OP_I2C_READ = 9,
+	AVR_OP_I2C_WRITE = 10,
+	AVR_OP_I2C_RANDOM_READ = 11,
+	/* VPD handlers */
+	AVR_OP_VPD_READ = 19,
+	AVR_OP_VPD_WRITE = 20,
+	/* SENSOR handlers */
+	AVR_OP_SENSOR_FETCH = 28,
+	/* The following command are only relevant to V3 */
+	AVR_OP_SENSOR_MON_CONTROL = 42,
+	AVR_OP_SENSOR_MON_SETUP = 43,
+	/* special version handler */
+	AVR_OP_SYSINFO_2 = 62,
+};
+
+#define GEN2_AVR_IDENT_SIZE (20)
+#define GEN2_AVR_VERSION_SIZE (50)
+
+#define GEN2_PN_SIZE (13)
+#define GEN2_PBA_SIZE (16)
+#define GEN2_SN_SIZE (10)
+#define GEN2_BNAME_SIZE (14)
+#define GEN2_PLATFORM_SIZE (72)
+#define GEN2_VPD_SIZE_TOTAL                                                  \
+	(1 + GEN2_PN_SIZE + GEN2_PBA_SIZE + GEN2_SN_SIZE + GEN2_BNAME_SIZE + \
+	 GEN2_PLATFORM_SIZE + 2)
+
+typedef struct vpd_eeprom_s {
+	uint8_t psu_hw_version; /* Hw revision - MUST NEVER ne overwritten. */
+	/* Vital Product Data: P/N   (13bytes ascii 0-9) */
+	uint8_t vpd_pn[GEN2_PN_SIZE];
+	/* Vital Product Data: PBA   (16bytes ascii 0-9) */
+	uint8_t vpd_pba[GEN2_PBA_SIZE];
+	/* Vital Product Data: S/N   (10bytes ascii 0-9) */
+	uint8_t vpd_sn[GEN2_SN_SIZE];
+	/* Vital Product Data: Board Name (10bytes ascii) (e.g. "ntmainb1e2" or "ntfront20b1") */
+	uint8_t vpd_board_name[GEN2_BNAME_SIZE];
+	/*
+	 * Vital Product Data: Other (72bytes of MAC addresses or other stuff.. (gives up to 12 mac
+	 * addresses)
+	 */
+	uint8_t vpd_platform_section[GEN2_PLATFORM_SIZE];
+	/* CRC16 checksum of all of above. This field is not included in the checksum */
+	uint16_t crc16;
+} vpd_eeprom_t;
+
+typedef struct {
+	uint8_t psu_hw_revision;
+	char board_type[GEN2_BNAME_SIZE + 1];
+	char product_id[GEN2_PN_SIZE + 1];
+	char pba_id[GEN2_PBA_SIZE + 1];
+	char serial_number[GEN2_SN_SIZE + 1];
+	uint8_t product_family;
+	uint32_t feature_mask;
+	uint32_t invfeature_mask;
+	uint8_t no_of_macs;
+	uint8_t mac_address[6];
+	uint16_t custom_id;
+	uint8_t user_id[8];
+} board_info_t;
+
+struct tx_rx_buf {
+	uint16_t size;
+	void *p_buf;
+};
+
+struct nthw__spi__v3 {
+	int m_time_out;
+	int mn_instance_no;
+	nthw_spim_t *mp_spim_mod;
+	nthw_spis_t *mp_spis_mod;
+};
+
+typedef struct nthw__spi__v3 nthw_spi_v3_t;
+typedef struct nthw__spi__v3 nthw_spi_v3;
+
+nthw_spi_v3_t *nthw_spi_v3_new(void);
+int nthw_spi_v3_init(nthw_spi_v3_t *p, nt_fpga_t *p_fpga, int n_instance_no);
+void nthw_spi_v3_delete(nthw_spi_v3_t *p);
+
+int nthw_spi_v3_set_timeout(nthw_spi_v3_t *p, int time_out);
+int nthw_spi_v3_get_version(nthw_spi_v3_t *p);
+int nthw_spi_v3_transfer(nthw_spi_v3_t *p, uint16_t opcode,
+			 struct tx_rx_buf *tx_buf, struct tx_rx_buf *rx_buf);
+
+#endif /* __NT4GA_SPI_V3__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_spim.c b/drivers/net/ntnic/nthw/core/nthw_spim.c
new file mode 100644
index 0000000000..ece7db26e1
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_spim.c
@@ -0,0 +1,117 @@
+/* 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_spim.h"
+
+nthw_spim_t *nthw_spim_new(void)
+{
+	nthw_spim_t *p = malloc(sizeof(nthw_spim_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_spim_t));
+	return p;
+}
+
+int nthw_spim_init(nthw_spim_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 *mod = fpga_query_module(p_fpga, MOD_SPIM, n_instance);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: SPIM %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_spim = mod;
+
+	/* SPIM is a primary communication channel - turn off debug by default */
+	module_set_debug_mode(p->mp_mod_spim, 0x00);
+
+	p->mp_reg_srr = module_get_register(p->mp_mod_spim, SPIM_SRR);
+	p->mp_fld_srr_rst = register_get_field(p->mp_reg_srr, SPIM_SRR_RST);
+
+	p->mp_reg_cr = module_get_register(p->mp_mod_spim, SPIM_CR);
+	p->mp_fld_cr_loop = register_get_field(p->mp_reg_cr, SPIM_CR_LOOP);
+	p->mp_fld_cr_en = register_get_field(p->mp_reg_cr, SPIM_CR_EN);
+	p->mp_fld_cr_txrst = register_get_field(p->mp_reg_cr, SPIM_CR_TXRST);
+	p->mp_fld_cr_rxrst = register_get_field(p->mp_reg_cr, SPIM_CR_RXRST);
+
+	p->mp_reg_sr = module_get_register(p->mp_mod_spim, SPIM_SR);
+	p->mp_fld_sr_done = register_get_field(p->mp_reg_sr, SPIM_SR_DONE);
+	p->mp_fld_sr_txempty = register_get_field(p->mp_reg_sr, SPIM_SR_TXEMPTY);
+	p->mp_fld_sr_rxempty = register_get_field(p->mp_reg_sr, SPIM_SR_RXEMPTY);
+	p->mp_fld_sr_txfull = register_get_field(p->mp_reg_sr, SPIM_SR_TXFULL);
+	p->mp_fld_sr_rxfull = register_get_field(p->mp_reg_sr, SPIM_SR_RXFULL);
+	p->mp_fld_sr_txlvl = register_get_field(p->mp_reg_sr, SPIM_SR_TXLVL);
+	p->mp_fld_sr_rxlvl = register_get_field(p->mp_reg_sr, SPIM_SR_RXLVL);
+
+	p->mp_reg_dtr = module_get_register(p->mp_mod_spim, SPIM_DTR);
+	p->mp_fld_dtr_dtr = register_get_field(p->mp_reg_dtr, SPIM_DTR_DTR);
+
+	p->mp_reg_drr = module_get_register(p->mp_mod_spim, SPIM_DRR);
+	p->mp_fld_drr_drr = register_get_field(p->mp_reg_drr, SPIM_DRR_DRR);
+
+	p->mp_reg_cfg = module_get_register(p->mp_mod_spim, SPIM_CFG);
+	p->mp_fld_cfg_pre = register_get_field(p->mp_reg_cfg, SPIM_CFG_PRE);
+
+	return 0;
+}
+
+void nthw_spim_delete(nthw_spim_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_spim_t));
+		free(p);
+	}
+}
+
+uint32_t nthw_spim_reset(nthw_spim_t *p)
+{
+	register_update(p->mp_reg_srr);
+	field_set_val32(p->mp_fld_srr_rst,
+		       0x0A); /* 0x0A hardcoded value - see doc */
+	register_flush(p->mp_reg_srr, 1);
+
+	return 0;
+}
+
+uint32_t nthw_spim_enable(nthw_spim_t *p, bool b_enable)
+{
+	field_update_register(p->mp_fld_cr_en);
+
+	if (b_enable)
+		field_set_all(p->mp_fld_cr_en);
+
+	else
+		field_clr_all(p->mp_fld_cr_en);
+	field_flush_register(p->mp_fld_cr_en);
+
+	return 0;
+}
+
+uint32_t nthw_spim_write_tx_fifo(nthw_spim_t *p, uint32_t n_data)
+{
+	field_set_val_flush32(p->mp_fld_dtr_dtr, n_data);
+	return 0;
+}
+
+uint32_t nthw_spim_get_tx_fifo_empty(nthw_spim_t *p, bool *pb_empty)
+{
+	assert(pb_empty);
+
+	*pb_empty = field_get_updated(p->mp_fld_sr_txempty) ? true : false;
+
+	return 0;
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_spim.h b/drivers/net/ntnic/nthw/core/nthw_spim.h
new file mode 100644
index 0000000000..713751e563
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_spim.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_SPIM_H__
+#define __NTHW_SPIM_H__
+
+struct nthw_spim {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_spim;
+	int mn_instance;
+
+	nt_register_t *mp_reg_srr;
+	nt_field_t *mp_fld_srr_rst;
+
+	nt_register_t *mp_reg_cr;
+	nt_field_t *mp_fld_cr_loop;
+	nt_field_t *mp_fld_cr_en;
+	nt_field_t *mp_fld_cr_txrst;
+	nt_field_t *mp_fld_cr_rxrst;
+
+	nt_register_t *mp_reg_sr;
+	nt_field_t *mp_fld_sr_done;
+	nt_field_t *mp_fld_sr_txempty;
+	nt_field_t *mp_fld_sr_rxempty;
+	nt_field_t *mp_fld_sr_txfull;
+	nt_field_t *mp_fld_sr_rxfull;
+	nt_field_t *mp_fld_sr_txlvl;
+	nt_field_t *mp_fld_sr_rxlvl;
+
+	nt_register_t *mp_reg_dtr;
+	nt_field_t *mp_fld_dtr_dtr;
+
+	nt_register_t *mp_reg_drr;
+	nt_field_t *mp_fld_drr_drr;
+	nt_register_t *mp_reg_cfg;
+	nt_field_t *mp_fld_cfg_pre;
+};
+
+typedef struct nthw_spim nthw_spim_t;
+typedef struct nthw_spim nthw_spim;
+
+nthw_spim_t *nthw_spim_new(void);
+int nthw_spim_init(nthw_spim_t *p, nt_fpga_t *p_fpga, int n_instance);
+void nthw_spim_delete(nthw_spim_t *p);
+
+uint32_t nthw_spim_reset(nthw_spim_t *p);
+uint32_t nthw_spim_enable(nthw_spim_t *p, bool b_enable);
+uint32_t nthw_spim_get_tx_fifo_empty(nthw_spim_t *p, bool *pb_empty);
+uint32_t nthw_spim_write_tx_fifo(nthw_spim_t *p, uint32_t n_data);
+
+#endif /* __NTHW_SPIM_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_spis.c b/drivers/net/ntnic/nthw/core/nthw_spis.c
new file mode 100644
index 0000000000..8799584194
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_spis.c
@@ -0,0 +1,147 @@
+/* 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_spis.h"
+
+nthw_spis_t *nthw_spis_new(void)
+{
+	nthw_spis_t *p = malloc(sizeof(nthw_spis_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_spis_t));
+	return p;
+}
+
+int nthw_spis_init(nthw_spis_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 *mod = fpga_query_module(p_fpga, MOD_SPIS, n_instance);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: SPIS %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_spis = mod;
+
+	/* SPIS is a primary communication channel - turn off debug by default */
+	module_set_debug_mode(p->mp_mod_spis, 0x00);
+
+	p->mp_reg_srr = module_get_register(p->mp_mod_spis, SPIS_SRR);
+	p->mp_fld_srr_rst = register_get_field(p->mp_reg_srr, SPIS_SRR_RST);
+
+	p->mp_reg_cr = module_get_register(p->mp_mod_spis, SPIS_CR);
+	p->mp_fld_cr_loop = register_get_field(p->mp_reg_cr, SPIS_CR_LOOP);
+	p->mp_fld_cr_en = register_get_field(p->mp_reg_cr, SPIS_CR_EN);
+	p->mp_fld_cr_txrst = register_get_field(p->mp_reg_cr, SPIS_CR_TXRST);
+	p->mp_fld_cr_rxrst = register_get_field(p->mp_reg_cr, SPIS_CR_RXRST);
+	p->mp_fld_cr_debug = register_get_field(p->mp_reg_cr, SPIS_CR_DEBUG);
+
+	p->mp_reg_sr = module_get_register(p->mp_mod_spis, SPIS_SR);
+	p->mp_fld_sr_done = register_get_field(p->mp_reg_sr, SPIS_SR_DONE);
+	p->mp_fld_sr_txempty = register_get_field(p->mp_reg_sr, SPIS_SR_TXEMPTY);
+	p->mp_fld_sr_rxempty = register_get_field(p->mp_reg_sr, SPIS_SR_RXEMPTY);
+	p->mp_fld_sr_txfull = register_get_field(p->mp_reg_sr, SPIS_SR_TXFULL);
+	p->mp_fld_sr_rxfull = register_get_field(p->mp_reg_sr, SPIS_SR_RXFULL);
+	p->mp_fld_sr_txlvl = register_get_field(p->mp_reg_sr, SPIS_SR_TXLVL);
+	p->mp_fld_sr_rxlvl = register_get_field(p->mp_reg_sr, SPIS_SR_RXLVL);
+	p->mp_fld_sr_frame_err =
+		register_get_field(p->mp_reg_sr, SPIS_SR_FRAME_ERR);
+	p->mp_fld_sr_read_err = register_get_field(p->mp_reg_sr, SPIS_SR_READ_ERR);
+	p->mp_fld_sr_write_err =
+		register_get_field(p->mp_reg_sr, SPIS_SR_WRITE_ERR);
+
+	p->mp_reg_dtr = module_get_register(p->mp_mod_spis, SPIS_DTR);
+	p->mp_fld_dtr_dtr = register_get_field(p->mp_reg_dtr, SPIS_DTR_DTR);
+
+	p->mp_reg_drr = module_get_register(p->mp_mod_spis, SPIS_DRR);
+	p->mp_fld_drr_drr = register_get_field(p->mp_reg_drr, SPIS_DRR_DRR);
+
+	p->mp_reg_ram_ctrl = module_get_register(p->mp_mod_spis, SPIS_RAM_CTRL);
+	p->mp_fld_ram_ctrl_adr =
+		register_get_field(p->mp_reg_ram_ctrl, SPIS_RAM_CTRL_ADR);
+	p->mp_fld_ram_ctrl_cnt =
+		register_get_field(p->mp_reg_ram_ctrl, SPIS_RAM_CTRL_CNT);
+
+	p->mp_reg_ram_data = module_get_register(p->mp_mod_spis, SPIS_RAM_DATA);
+	p->mp_fld_ram_data_data =
+		register_get_field(p->mp_reg_ram_data, SPIS_RAM_DATA_DATA);
+
+	return 0;
+}
+
+void nthw_spis_delete(nthw_spis_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_spis_t));
+		free(p);
+	}
+}
+
+uint32_t nthw_spis_reset(nthw_spis_t *p)
+{
+	register_update(p->mp_reg_srr);
+	field_set_val32(p->mp_fld_srr_rst,
+		       0x0A); /* 0x0A hardcoded value - see doc */
+	register_flush(p->mp_reg_srr, 1);
+
+	return 0;
+}
+
+uint32_t nthw_spis_enable(nthw_spis_t *p, bool b_enable)
+{
+	field_update_register(p->mp_fld_cr_en);
+
+	if (b_enable)
+		field_set_all(p->mp_fld_cr_en);
+
+	else
+		field_clr_all(p->mp_fld_cr_en);
+	field_flush_register(p->mp_fld_cr_en);
+
+	return 0;
+}
+
+uint32_t nthw_spis_get_rx_fifo_empty(nthw_spis_t *p, bool *pb_empty)
+{
+	assert(pb_empty);
+
+	*pb_empty = field_get_updated(p->mp_fld_sr_rxempty) ? true : false;
+
+	return 0;
+}
+
+uint32_t nthw_spis_read_rx_fifo(nthw_spis_t *p, uint32_t *p_data)
+{
+	assert(p_data);
+
+	*p_data = field_get_updated(p->mp_fld_drr_drr);
+
+	return 0;
+}
+
+uint32_t nthw_spis_read_sensor(nthw_spis_t *p, uint8_t n_result_idx,
+			      uint32_t *p_sensor_result)
+{
+	assert(p_sensor_result);
+
+	field_set_val32(p->mp_fld_ram_ctrl_adr, n_result_idx);
+	field_set_val32(p->mp_fld_ram_ctrl_cnt, 1);
+	register_flush(p->mp_reg_ram_ctrl, 1);
+
+	*p_sensor_result = field_get_updated(p->mp_fld_ram_data_data);
+
+	return 0;
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_spis.h b/drivers/net/ntnic/nthw/core/nthw_spis.h
new file mode 100644
index 0000000000..2ebe840c9e
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_spis.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_SPIS_H__
+#define __NTHW_SPIS_H__
+
+struct nthw_spis {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_spis;
+	int mn_instance;
+
+	nt_register_t *mp_reg_srr;
+	nt_field_t *mp_fld_srr_rst;
+
+	nt_register_t *mp_reg_cr;
+	nt_field_t *mp_fld_cr_loop;
+	nt_field_t *mp_fld_cr_en;
+	nt_field_t *mp_fld_cr_txrst;
+	nt_field_t *mp_fld_cr_rxrst;
+	nt_field_t *mp_fld_cr_debug;
+
+	nt_register_t *mp_reg_sr;
+	nt_field_t *mp_fld_sr_done;
+	nt_field_t *mp_fld_sr_txempty;
+	nt_field_t *mp_fld_sr_rxempty;
+	nt_field_t *mp_fld_sr_txfull;
+	nt_field_t *mp_fld_sr_rxfull;
+	nt_field_t *mp_fld_sr_txlvl;
+	nt_field_t *mp_fld_sr_rxlvl;
+	nt_field_t *mp_fld_sr_frame_err;
+	nt_field_t *mp_fld_sr_read_err;
+	nt_field_t *mp_fld_sr_write_err;
+
+	nt_register_t *mp_reg_dtr;
+	nt_field_t *mp_fld_dtr_dtr;
+
+	nt_register_t *mp_reg_drr;
+	nt_field_t *mp_fld_drr_drr;
+
+	nt_register_t *mp_reg_ram_ctrl;
+	nt_field_t *mp_fld_ram_ctrl_adr;
+	nt_field_t *mp_fld_ram_ctrl_cnt;
+
+	nt_register_t *mp_reg_ram_data;
+	nt_field_t *mp_fld_ram_data_data;
+};
+
+typedef struct nthw_spis nthw_spis_t;
+typedef struct nthw_spis nthw_spis;
+
+nthw_spis_t *nthw_spis_new(void);
+int nthw_spis_init(nthw_spis_t *p, nt_fpga_t *p_fpga, int n_instance);
+void nthw_spis_delete(nthw_spis_t *p);
+
+uint32_t nthw_spis_reset(nthw_spis_t *p);
+uint32_t nthw_spis_enable(nthw_spis_t *p, bool b_enable);
+uint32_t nthw_spis_get_rx_fifo_empty(nthw_spis_t *p, bool *pb_empty);
+uint32_t nthw_spis_read_rx_fifo(nthw_spis_t *p, uint32_t *p_data);
+uint32_t nthw_spis_read_sensor(nthw_spis_t *p, uint8_t n_result_idx,
+			      uint32_t *p_sensor_result);
+
+#endif /* __NTHW_SPIS_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_tsm.c b/drivers/net/ntnic/nthw/core/nthw_tsm.c
new file mode 100644
index 0000000000..8ea4a4c440
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_tsm.c
@@ -0,0 +1,179 @@
+/* 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_tsm.h"
+
+nthw_tsm_t *nthw_tsm_new(void)
+{
+	nthw_tsm_t *p = malloc(sizeof(nthw_tsm_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_tsm_t));
+	return p;
+}
+
+void nthw_tsm_delete(nthw_tsm_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_tsm_t));
+		free(p);
+	}
+}
+
+int nthw_tsm_init(nthw_tsm_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 *mod = fpga_query_module(p_fpga, MOD_TSM, n_instance);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: TSM %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_tsm = mod;
+
+	{
+		nt_register_t *p_reg;
+
+		p_reg = module_get_register(p->mp_mod_tsm, TSM_CONFIG);
+		p->mp_fld_config_ts_format =
+			register_get_field(p_reg, TSM_CONFIG_TS_FORMAT);
+
+		p_reg = module_get_register(p->mp_mod_tsm, TSM_TIMER_CTRL);
+		p->mp_fld_timer_ctrl_timer_en_t0 =
+			register_get_field(p_reg, TSM_TIMER_CTRL_TIMER_EN_T0);
+		p->mp_fld_timer_ctrl_timer_en_t1 =
+			register_get_field(p_reg, TSM_TIMER_CTRL_TIMER_EN_T1);
+
+		p_reg = module_get_register(p->mp_mod_tsm, TSM_TIMER_T0);
+		p->mp_fld_timer_timer_t0_max_count =
+			register_get_field(p_reg, TSM_TIMER_T0_MAX_COUNT);
+
+		p_reg = module_get_register(p->mp_mod_tsm, TSM_TIMER_T1);
+		p->mp_fld_timer_timer_t1_max_count =
+			register_get_field(p_reg, TSM_TIMER_T1_MAX_COUNT);
+
+		p_reg = module_get_register(p->mp_mod_tsm, TSM_TIME_LO);
+		p->mp_reg_time_lo = module_get_register(p->mp_mod_tsm, TSM_TIME_LO);
+		p->mp_fld_time_lo = register_get_field(p_reg, TSM_TIME_LO_NS);
+
+		p_reg = module_get_register(p->mp_mod_tsm, TSM_TIME_HI);
+		p->mp_reg_time_hi = module_get_register(p->mp_mod_tsm, TSM_TIME_HI);
+		p->mp_fld_time_hi = register_get_field(p_reg, TSM_TIME_HI_SEC);
+
+		p_reg = module_get_register(p->mp_mod_tsm, TSM_TS_LO);
+		p->mp_reg_ts_lo = module_get_register(p->mp_mod_tsm, TSM_TS_LO);
+		p->mp_fld_ts_lo = register_get_field(p_reg, TSM_TS_LO_TIME);
+
+		p_reg = module_get_register(p->mp_mod_tsm, TSM_TS_HI);
+		p->mp_reg_ts_hi = module_get_register(p->mp_mod_tsm, TSM_TS_HI);
+		p->mp_fld_ts_hi = register_get_field(p_reg, TSM_TS_HI_TIME);
+	}
+	return 0;
+}
+
+int nthw_tsm_get_ts(nthw_tsm_t *p, uint64_t *p_ts)
+{
+	uint32_t n_ts_lo, n_ts_hi;
+	uint64_t val;
+
+	if (!p_ts)
+		return -1;
+
+	n_ts_lo = field_get_updated(p->mp_fld_ts_lo);
+	n_ts_hi = field_get_updated(p->mp_fld_ts_hi);
+
+	val = ((((uint64_t)n_ts_hi) << 32UL) | n_ts_lo);
+
+	if (p_ts)
+		*p_ts = val;
+
+	return 0;
+}
+
+int nthw_tsm_get_time(nthw_tsm_t *p, uint64_t *p_time)
+{
+	uint32_t n_time_lo, n_time_hi;
+	uint64_t val;
+
+	if (!p_time)
+		return -1;
+
+	n_time_lo = field_get_updated(p->mp_fld_time_lo);
+	n_time_hi = field_get_updated(p->mp_fld_time_hi);
+
+	val = ((((uint64_t)n_time_hi) << 32UL) | n_time_lo);
+
+	if (p_time)
+		*p_time = val;
+
+	return 0;
+}
+
+int nthw_tsm_set_time(nthw_tsm_t *p, uint64_t n_time)
+{
+	field_set_val_flush32(p->mp_fld_time_lo, (n_time & 0xFFFFFFFF));
+	field_set_val_flush32(p->mp_fld_time_hi,
+			    (uint32_t)((n_time >> 32) & 0xFFFFFFFF));
+	return 0;
+}
+
+int nthw_tsm_set_timer_t0_enable(nthw_tsm_t *p, bool b_enable)
+{
+	field_update_register(p->mp_fld_timer_ctrl_timer_en_t0);
+	if (b_enable)
+		field_set_flush(p->mp_fld_timer_ctrl_timer_en_t0);
+
+	else
+		field_clr_flush(p->mp_fld_timer_ctrl_timer_en_t0);
+	return 0;
+}
+
+int nthw_tsm_set_timer_t0_max_count(nthw_tsm_t *p, uint32_t n_timer_val)
+{
+	/* Timer T0 - stat toggle timer */
+	field_update_register(p->mp_fld_timer_timer_t0_max_count);
+	field_set_val_flush32(p->mp_fld_timer_timer_t0_max_count,
+			    n_timer_val); /* ns (50*1000*1000) */
+	return 0;
+}
+
+int nthw_tsm_set_timer_t1_enable(nthw_tsm_t *p, bool b_enable)
+{
+	field_update_register(p->mp_fld_timer_ctrl_timer_en_t1);
+	if (b_enable)
+		field_set_flush(p->mp_fld_timer_ctrl_timer_en_t1);
+
+	else
+		field_clr_flush(p->mp_fld_timer_ctrl_timer_en_t1);
+	return 0;
+}
+
+int nthw_tsm_set_timer_t1_max_count(nthw_tsm_t *p, uint32_t n_timer_val)
+{
+	/* Timer T1 - keep alive timer */
+	field_update_register(p->mp_fld_timer_timer_t1_max_count);
+	field_set_val_flush32(p->mp_fld_timer_timer_t1_max_count,
+			    n_timer_val); /* ns (100*1000*1000) */
+	return 0;
+}
+
+int nthw_tsm_set_config_ts_format(nthw_tsm_t *p, uint32_t n_val)
+{
+	field_update_register(p->mp_fld_config_ts_format);
+	/* 0x1: Native - 10ns units, start date: 1970-01-01. */
+	field_set_val_flush32(p->mp_fld_config_ts_format, n_val);
+	return 0;
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_tsm.h b/drivers/net/ntnic/nthw/core/nthw_tsm.h
new file mode 100644
index 0000000000..590e04c312
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_tsm.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_TSM_H__
+#define __NTHW_TSM_H__
+
+struct nthw_tsm {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_tsm;
+	int mn_instance;
+
+	nt_field_t *mp_fld_config_ts_format;
+
+	nt_field_t *mp_fld_timer_ctrl_timer_en_t0;
+	nt_field_t *mp_fld_timer_ctrl_timer_en_t1;
+
+	nt_field_t *mp_fld_timer_timer_t0_max_count;
+
+	nt_field_t *mp_fld_timer_timer_t1_max_count;
+
+	nt_register_t *mp_reg_ts_lo;
+	nt_field_t *mp_fld_ts_lo;
+
+	nt_register_t *mp_reg_ts_hi;
+	nt_field_t *mp_fld_ts_hi;
+
+	nt_register_t *mp_reg_time_lo;
+	nt_field_t *mp_fld_time_lo;
+
+	nt_register_t *mp_reg_time_hi;
+	nt_field_t *mp_fld_time_hi;
+};
+
+typedef struct nthw_tsm nthw_tsm_t;
+typedef struct nthw_tsm nthw_tsm;
+
+nthw_tsm_t *nthw_tsm_new(void);
+void nthw_tsm_delete(nthw_tsm_t *p);
+int nthw_tsm_init(nthw_tsm_t *p, nt_fpga_t *p_fpga, int n_instance);
+
+int nthw_tsm_get_ts(nthw_tsm_t *p, uint64_t *p_ts);
+int nthw_tsm_get_time(nthw_tsm_t *p, uint64_t *p_time);
+int nthw_tsm_set_time(nthw_tsm_t *p, uint64_t n_time);
+
+int nthw_tsm_set_timer_t0_enable(nthw_tsm_t *p, bool b_enable);
+int nthw_tsm_set_timer_t0_max_count(nthw_tsm_t *p, uint32_t n_timer_val);
+int nthw_tsm_set_timer_t1_enable(nthw_tsm_t *p, bool b_enable);
+int nthw_tsm_set_timer_t1_max_count(nthw_tsm_t *p, uint32_t n_timer_val);
+
+int nthw_tsm_set_config_ts_format(nthw_tsm_t *p, uint32_t n_val);
+
+#endif /* __NTHW_TSM_H__ */
diff --git a/drivers/net/ntnic/nthw/nthw_dbs.c b/drivers/net/ntnic/nthw/nthw_dbs.c
new file mode 100644
index 0000000000..9fc853da73
--- /dev/null
+++ b/drivers/net/ntnic/nthw/nthw_dbs.c
@@ -0,0 +1,1301 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <errno.h>
+#include "ntlog.h"
+
+#include "nthw_drv.h"
+#include "nthw_register.h"
+
+#include "nthw_dbs.h"
+
+#undef DBS_PRINT_REGS
+
+static void set_shadow_tx_qos_data(nthw_dbs_t *p, uint32_t index, uint32_t enable,
+				uint32_t ir, uint32_t bs);
+static void flush_tx_qos_data(nthw_dbs_t *p, uint32_t index);
+static void set_shadow_tx_qp_data(nthw_dbs_t *p, uint32_t index,
+			       uint32_t virtual_port);
+static void flush_tx_qp_data(nthw_dbs_t *p, uint32_t index);
+static void set_shadow_tx_dr_data(nthw_dbs_t *p, uint32_t index,
+			       uint64_t guest_physical_address, uint32_t host_id,
+			       uint32_t queue_size, uint32_t port,
+			       uint32_t header, uint32_t packed);
+static void flush_tx_dr_data(nthw_dbs_t *p, uint32_t index);
+static void set_shadow_rx_dr_data(nthw_dbs_t *p, uint32_t index,
+			       uint64_t guest_physical_address, uint32_t host_id,
+			       uint32_t queue_size, uint32_t header,
+			       uint32_t packed);
+static void flush_rx_dr_data(nthw_dbs_t *p, uint32_t index);
+static void set_shadow_tx_uw_data(nthw_dbs_t *p, uint32_t index,
+			       uint64_t guest_physical_address, uint32_t host_id,
+			       uint32_t queue_size, uint32_t packed,
+			       uint32_t int_enable, uint32_t vec, uint32_t istk,
+			       uint32_t in_order);
+static void flush_tx_uw_data(nthw_dbs_t *p, uint32_t index);
+static void set_shadow_rx_uw_data(nthw_dbs_t *p, uint32_t index,
+			       uint64_t guest_physical_address, uint32_t host_id,
+			       uint32_t queue_size, uint32_t packed,
+			       uint32_t int_enable, uint32_t vec,
+			       uint32_t istk);
+static void flush_rx_uw_data(nthw_dbs_t *p, uint32_t index);
+static void set_shadow_rx_am_data(nthw_dbs_t *p, uint32_t index,
+			       uint64_t guest_physical_address, uint32_t enable,
+			       uint32_t host_id, uint32_t packed,
+			       uint32_t int_enable);
+static void flush_rx_am_data(nthw_dbs_t *p, uint32_t index);
+static void set_shadow_tx_am_data(nthw_dbs_t *p, uint32_t index,
+			       uint64_t guest_physical_address, uint32_t enable,
+			       uint32_t host_id, uint32_t packed,
+			       uint32_t int_enable);
+static void flush_tx_am_data(nthw_dbs_t *p, uint32_t index);
+
+nthw_dbs_t *nthw_dbs_new(void)
+{
+	nthw_dbs_t *p = malloc(sizeof(nthw_dbs_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_dbs_t));
+	return p;
+}
+
+void nthw_dbs_delete(nthw_dbs_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_dbs_t));
+		free(p);
+	}
+}
+
+int dbs_init(nthw_dbs_t *p, nt_fpga_t *p_fpga, int n_instance)
+{
+	nt_module_t *mod = fpga_query_module(p_fpga, MOD_DBS, n_instance);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: DBS %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_dbs = mod;
+
+	p->mn_param_dbs_present = fpga_get_product_param(p_fpga, NT_DBS_PRESENT, 0);
+	if (p->mn_param_dbs_present == 0) {
+		NT_LOG(WRN, NTHW,
+		       "%s: DBS %d: logical error: module found but not flagged at present\n",
+		       p->mp_fpga->p_fpga_info->mp_adapter_id_str, p->mn_instance);
+	}
+
+	p->mp_reg_rx_control = module_get_register(p->mp_mod_dbs, DBS_RX_CONTROL);
+	p->mp_fld_rx_control_last_queue =
+		register_get_field(p->mp_reg_rx_control, DBS_RX_CONTROL_LQ);
+	p->mp_fld_rx_control_avail_monitor_enable =
+		register_get_field(p->mp_reg_rx_control, DBS_RX_CONTROL_AME);
+	p->mp_fld_rx_control_avail_monitor_scan_speed =
+		register_get_field(p->mp_reg_rx_control, DBS_RX_CONTROL_AMS);
+	p->mp_fld_rx_control_used_write_enable =
+		register_get_field(p->mp_reg_rx_control, DBS_RX_CONTROL_UWE);
+	p->mp_fld_rx_control_used_writer_update_speed =
+		register_get_field(p->mp_reg_rx_control, DBS_RX_CONTROL_UWS);
+	p->mp_fld_rx_control_rx_queues_enable =
+		register_get_field(p->mp_reg_rx_control, DBS_RX_CONTROL_QE);
+
+	p->mp_reg_tx_control = module_get_register(p->mp_mod_dbs, DBS_TX_CONTROL);
+	p->mp_fld_tx_control_last_queue =
+		register_get_field(p->mp_reg_tx_control, DBS_TX_CONTROL_LQ);
+	p->mp_fld_tx_control_avail_monitor_enable =
+		register_get_field(p->mp_reg_tx_control, DBS_TX_CONTROL_AME);
+	p->mp_fld_tx_control_avail_monitor_scan_speed =
+		register_get_field(p->mp_reg_tx_control, DBS_TX_CONTROL_AMS);
+	p->mp_fld_tx_control_used_write_enable =
+		register_get_field(p->mp_reg_tx_control, DBS_TX_CONTROL_UWE);
+	p->mp_fld_tx_control_used_writer_update_speed =
+		register_get_field(p->mp_reg_tx_control, DBS_TX_CONTROL_UWS);
+	p->mp_fld_tx_control_tx_queues_enable =
+		register_get_field(p->mp_reg_tx_control, DBS_TX_CONTROL_QE);
+
+	p->mp_reg_rx_init = module_get_register(p->mp_mod_dbs, DBS_RX_INIT);
+	p->mp_fld_rx_init_init =
+		register_get_field(p->mp_reg_rx_init, DBS_RX_INIT_INIT);
+	p->mp_fld_rx_init_queue =
+		register_get_field(p->mp_reg_rx_init, DBS_RX_INIT_QUEUE);
+	p->mp_fld_rx_init_busy =
+		register_get_field(p->mp_reg_rx_init, DBS_RX_INIT_BUSY);
+
+	p->mp_reg_rx_init_val = module_query_register(p->mp_mod_dbs, DBS_RX_INIT_VAL);
+	if (p->mp_reg_rx_init_val) {
+		p->mp_fld_rx_init_val_idx = register_query_field(p->mp_reg_rx_init_val,
+				       DBS_RX_INIT_VAL_IDX);
+		p->mp_fld_rx_init_val_ptr = register_query_field(p->mp_reg_rx_init_val,
+				       DBS_RX_INIT_VAL_PTR);
+	}
+
+	p->mp_reg_rx_ptr = module_query_register(p->mp_mod_dbs, DBS_RX_PTR);
+	if (p->mp_reg_rx_ptr) {
+		p->mp_fld_rx_ptr_ptr =
+			register_query_field(p->mp_reg_rx_ptr, DBS_RX_PTR_PTR);
+		p->mp_fld_rx_ptr_queue =
+			register_query_field(p->mp_reg_rx_ptr, DBS_RX_PTR_QUEUE);
+		p->mp_fld_rx_ptr_valid =
+			register_query_field(p->mp_reg_rx_ptr, DBS_RX_PTR_VALID);
+	}
+
+	p->mp_reg_tx_init = module_get_register(p->mp_mod_dbs, DBS_TX_INIT);
+	p->mp_fld_tx_init_init =
+		register_get_field(p->mp_reg_tx_init, DBS_TX_INIT_INIT);
+	p->mp_fld_tx_init_queue =
+		register_get_field(p->mp_reg_tx_init, DBS_TX_INIT_QUEUE);
+	p->mp_fld_tx_init_busy =
+		register_get_field(p->mp_reg_tx_init, DBS_TX_INIT_BUSY);
+
+	p->mp_reg_tx_init_val = module_query_register(p->mp_mod_dbs, DBS_TX_INIT_VAL);
+	if (p->mp_reg_tx_init_val) {
+		p->mp_fld_tx_init_val_idx = register_query_field(p->mp_reg_tx_init_val,
+				       DBS_TX_INIT_VAL_IDX);
+		p->mp_fld_tx_init_val_ptr = register_query_field(p->mp_reg_tx_init_val,
+				       DBS_TX_INIT_VAL_PTR);
+	}
+
+	p->mp_reg_tx_ptr = module_query_register(p->mp_mod_dbs, DBS_TX_PTR);
+	if (p->mp_reg_tx_ptr) {
+		p->mp_fld_tx_ptr_ptr =
+			register_query_field(p->mp_reg_tx_ptr, DBS_TX_PTR_PTR);
+		p->mp_fld_tx_ptr_queue =
+			register_query_field(p->mp_reg_tx_ptr, DBS_TX_PTR_QUEUE);
+		p->mp_fld_tx_ptr_valid =
+			register_query_field(p->mp_reg_tx_ptr, DBS_TX_PTR_VALID);
+	}
+
+	p->mp_reg_rx_idle = module_query_register(p->mp_mod_dbs, DBS_RX_IDLE);
+	if (p->mp_reg_rx_idle) {
+		p->mp_fld_rx_idle_idle =
+			register_query_field(p->mp_reg_rx_idle, DBS_RX_IDLE_IDLE);
+		p->mp_fld_rx_idle_queue =
+			register_query_field(p->mp_reg_rx_idle, DBS_RX_IDLE_QUEUE);
+		p->mp_fld_rx_idle_busy =
+			register_query_field(p->mp_reg_rx_idle, DBS_RX_IDLE_BUSY);
+	}
+
+	p->mp_reg_tx_idle = module_query_register(p->mp_mod_dbs, DBS_TX_IDLE);
+	if (p->mp_reg_tx_idle) {
+		p->mp_fld_tx_idle_idle =
+			register_query_field(p->mp_reg_tx_idle, DBS_TX_IDLE_IDLE);
+		p->mp_fld_tx_idle_queue =
+			register_query_field(p->mp_reg_tx_idle, DBS_TX_IDLE_QUEUE);
+		p->mp_fld_tx_idle_busy =
+			register_query_field(p->mp_reg_tx_idle, DBS_TX_IDLE_BUSY);
+	}
+
+	p->mp_reg_rx_avail_monitor_control =
+		module_get_register(p->mp_mod_dbs, DBS_RX_AM_CTRL);
+	p->mp_fld_rx_avail_monitor_control_adr =
+		register_get_field(p->mp_reg_rx_avail_monitor_control, DBS_RX_AM_CTRL_ADR);
+	p->mp_fld_rx_avail_monitor_control_cnt =
+		register_get_field(p->mp_reg_rx_avail_monitor_control, DBS_RX_AM_CTRL_CNT);
+
+	p->mp_reg_rx_avail_monitor_data =
+		module_get_register(p->mp_mod_dbs, DBS_RX_AM_DATA);
+	p->mp_fld_rx_avail_monitor_data_guest_physical_address =
+		register_get_field(p->mp_reg_rx_avail_monitor_data, DBS_RX_AM_DATA_GPA);
+	p->mp_fld_rx_avail_monitor_data_enable =
+		register_get_field(p->mp_reg_rx_avail_monitor_data, DBS_RX_AM_DATA_ENABLE);
+	p->mp_fld_rx_avail_monitor_data_host_id =
+		register_get_field(p->mp_reg_rx_avail_monitor_data, DBS_RX_AM_DATA_HID);
+	p->mp_fld_rx_avail_monitor_data_packed =
+		register_query_field(p->mp_reg_rx_avail_monitor_data, DBS_RX_AM_DATA_PCKED);
+	p->mp_fld_rx_avail_monitor_data_int =
+		register_query_field(p->mp_reg_rx_avail_monitor_data, DBS_RX_AM_DATA_INT);
+
+	p->mp_reg_tx_avail_monitor_control =
+		module_get_register(p->mp_mod_dbs, DBS_TX_AM_CTRL);
+	p->mp_fld_tx_avail_monitor_control_adr =
+		register_get_field(p->mp_reg_tx_avail_monitor_control, DBS_TX_AM_CTRL_ADR);
+	p->mp_fld_tx_avail_monitor_control_cnt =
+		register_get_field(p->mp_reg_tx_avail_monitor_control, DBS_TX_AM_CTRL_CNT);
+
+	p->mp_reg_tx_avail_monitor_data =
+		module_get_register(p->mp_mod_dbs, DBS_TX_AM_DATA);
+	p->mp_fld_tx_avail_monitor_data_guest_physical_address =
+		register_get_field(p->mp_reg_tx_avail_monitor_data, DBS_TX_AM_DATA_GPA);
+	p->mp_fld_tx_avail_monitor_data_enable =
+		register_get_field(p->mp_reg_tx_avail_monitor_data, DBS_TX_AM_DATA_ENABLE);
+	p->mp_fld_tx_avail_monitor_data_host_id =
+		register_get_field(p->mp_reg_tx_avail_monitor_data, DBS_TX_AM_DATA_HID);
+	p->mp_fld_tx_avail_monitor_data_packed =
+		register_query_field(p->mp_reg_tx_avail_monitor_data, DBS_TX_AM_DATA_PCKED);
+	p->mp_fld_tx_avail_monitor_data_int =
+		register_query_field(p->mp_reg_tx_avail_monitor_data, DBS_TX_AM_DATA_INT);
+
+	p->mp_reg_rx_used_writer_control =
+		module_get_register(p->mp_mod_dbs, DBS_RX_UW_CTRL);
+	p->mp_fld_rx_used_writer_control_adr =
+		register_get_field(p->mp_reg_rx_used_writer_control, DBS_RX_UW_CTRL_ADR);
+	p->mp_fld_rx_used_writer_control_cnt =
+		register_get_field(p->mp_reg_rx_used_writer_control, DBS_RX_UW_CTRL_CNT);
+
+	p->mp_reg_rx_used_writer_data =
+		module_get_register(p->mp_mod_dbs, DBS_RX_UW_DATA);
+	p->mp_fld_rx_used_writer_data_guest_physical_address =
+		register_get_field(p->mp_reg_rx_used_writer_data, DBS_RX_UW_DATA_GPA);
+	p->mp_fld_rx_used_writer_data_host_id =
+		register_get_field(p->mp_reg_rx_used_writer_data, DBS_RX_UW_DATA_HID);
+	p->mp_fld_rx_used_writer_data_queue_size =
+		register_get_field(p->mp_reg_rx_used_writer_data, DBS_RX_UW_DATA_QS);
+	p->mp_fld_rx_used_writer_data_packed =
+		register_query_field(p->mp_reg_rx_used_writer_data, DBS_RX_UW_DATA_PCKED);
+	p->mp_fld_rx_used_writer_data_int =
+		register_query_field(p->mp_reg_rx_used_writer_data, DBS_RX_UW_DATA_INT);
+	p->mp_fld_rx_used_writer_data_vec =
+		register_query_field(p->mp_reg_rx_used_writer_data, DBS_RX_UW_DATA_VEC);
+	p->mp_fld_rx_used_writer_data_istk =
+		register_query_field(p->mp_reg_rx_used_writer_data, DBS_RX_UW_DATA_ISTK);
+
+	p->mp_reg_tx_used_writer_control =
+		module_get_register(p->mp_mod_dbs, DBS_TX_UW_CTRL);
+	p->mp_fld_tx_used_writer_control_adr =
+		register_get_field(p->mp_reg_tx_used_writer_control, DBS_TX_UW_CTRL_ADR);
+	p->mp_fld_tx_used_writer_control_cnt =
+		register_get_field(p->mp_reg_tx_used_writer_control, DBS_TX_UW_CTRL_CNT);
+
+	p->mp_reg_tx_used_writer_data =
+		module_get_register(p->mp_mod_dbs, DBS_TX_UW_DATA);
+	p->mp_fld_tx_used_writer_data_guest_physical_address =
+		register_get_field(p->mp_reg_tx_used_writer_data, DBS_TX_UW_DATA_GPA);
+	p->mp_fld_tx_used_writer_data_host_id =
+		register_get_field(p->mp_reg_tx_used_writer_data, DBS_TX_UW_DATA_HID);
+	p->mp_fld_tx_used_writer_data_queue_size =
+		register_get_field(p->mp_reg_tx_used_writer_data, DBS_TX_UW_DATA_QS);
+	p->mp_fld_tx_used_writer_data_packed =
+		register_query_field(p->mp_reg_tx_used_writer_data, DBS_TX_UW_DATA_PCKED);
+	p->mp_fld_tx_used_writer_data_int =
+		register_query_field(p->mp_reg_tx_used_writer_data, DBS_TX_UW_DATA_INT);
+	p->mp_fld_tx_used_writer_data_vec =
+		register_query_field(p->mp_reg_tx_used_writer_data, DBS_TX_UW_DATA_VEC);
+	p->mp_fld_tx_used_writer_data_istk =
+		register_query_field(p->mp_reg_tx_used_writer_data, DBS_TX_UW_DATA_ISTK);
+	p->mp_fld_tx_used_writer_data_in_order =
+		register_query_field(p->mp_reg_tx_used_writer_data, DBS_TX_UW_DATA_INO);
+
+	p->mp_reg_rx_descriptor_reader_control =
+		module_get_register(p->mp_mod_dbs, DBS_RX_DR_CTRL);
+	p->mp_fld_rx_descriptor_reader_control_adr =
+		register_get_field(p->mp_reg_rx_descriptor_reader_control, DBS_RX_DR_CTRL_ADR);
+	p->mp_fld_rx_descriptor_reader_control_cnt =
+		register_get_field(p->mp_reg_rx_descriptor_reader_control, DBS_RX_DR_CTRL_CNT);
+
+	p->mp_reg_rx_descriptor_reader_data =
+		module_get_register(p->mp_mod_dbs, DBS_RX_DR_DATA);
+	p->mp_fld_rx_descriptor_reader_data_guest_physical_address =
+		register_get_field(p->mp_reg_rx_descriptor_reader_data, DBS_RX_DR_DATA_GPA);
+	p->mp_fld_rx_descriptor_reader_data_host_id =
+		register_get_field(p->mp_reg_rx_descriptor_reader_data, DBS_RX_DR_DATA_HID);
+	p->mp_fld_rx_descriptor_reader_data_queue_size =
+		register_get_field(p->mp_reg_rx_descriptor_reader_data, DBS_RX_DR_DATA_QS);
+	p->mp_fld_rx_descriptor_reader_data_header =
+		register_get_field(p->mp_reg_rx_descriptor_reader_data, DBS_RX_DR_DATA_HDR);
+	p->mp_fld_rx_descriptor_reader_data_packed =
+		register_query_field(p->mp_reg_rx_descriptor_reader_data, DBS_RX_DR_DATA_PCKED);
+
+	p->mp_reg_tx_descriptor_reader_control =
+		module_get_register(p->mp_mod_dbs, DBS_TX_DR_CTRL);
+	p->mp_fld_tx_descriptor_reader_control_adr =
+		register_get_field(p->mp_reg_tx_descriptor_reader_control, DBS_TX_DR_CTRL_ADR);
+	p->mp_fld_tx_descriptor_reader_control_cnt =
+		register_get_field(p->mp_reg_tx_descriptor_reader_control, DBS_TX_DR_CTRL_CNT);
+
+	p->mp_reg_tx_descriptor_reader_data =
+		module_get_register(p->mp_mod_dbs, DBS_TX_DR_DATA);
+	p->mp_fld_tx_descriptor_reader_data_guest_physical_address =
+		register_get_field(p->mp_reg_tx_descriptor_reader_data, DBS_TX_DR_DATA_GPA);
+	p->mp_fld_tx_descriptor_reader_data_host_id =
+		register_get_field(p->mp_reg_tx_descriptor_reader_data, DBS_TX_DR_DATA_HID);
+	p->mp_fld_tx_descriptor_reader_data_queue_size =
+		register_get_field(p->mp_reg_tx_descriptor_reader_data, DBS_TX_DR_DATA_QS);
+	p->mp_fld_tx_descriptor_reader_data_header =
+		register_get_field(p->mp_reg_tx_descriptor_reader_data, DBS_TX_DR_DATA_HDR);
+	p->mp_fld_tx_descriptor_reader_data_port =
+		register_get_field(p->mp_reg_tx_descriptor_reader_data, DBS_TX_DR_DATA_PORT);
+	p->mp_fld_tx_descriptor_reader_data_packed =
+		register_query_field(p->mp_reg_tx_descriptor_reader_data, DBS_TX_DR_DATA_PCKED);
+
+	p->mp_reg_tx_queue_property_control =
+		module_get_register(p->mp_mod_dbs, DBS_TX_QP_CTRL);
+	p->mp_fld_tx_queue_property_control_adr =
+		register_get_field(p->mp_reg_tx_queue_property_control, DBS_TX_QP_CTRL_ADR);
+	p->mp_fld_tx_queue_property_control_cnt =
+		register_get_field(p->mp_reg_tx_queue_property_control, DBS_TX_QP_CTRL_CNT);
+
+	p->mp_reg_tx_queue_property_data =
+		module_get_register(p->mp_mod_dbs, DBS_TX_QP_DATA);
+	p->mp_fld_tx_queue_property_data_v_port =
+		register_get_field(p->mp_reg_tx_queue_property_data, DBS_TX_QP_DATA_VPORT);
+
+	/* HW QoS Tx rate limiting policing RFC2697/RFC4111 */
+	p->mp_reg_tx_queue_qos_control =
+		module_query_register(p->mp_mod_dbs, DBS_TX_QOS_CTRL);
+	p->mp_reg_tx_queue_qos_data =
+		module_query_register(p->mp_mod_dbs, DBS_TX_QOS_DATA);
+	if (p->mp_reg_tx_queue_qos_control) {
+		p->mp_reg_tx_queue_qos_control_adr =
+			register_query_field(p->mp_reg_tx_queue_qos_control, DBS_TX_QOS_CTRL_ADR);
+		p->mp_reg_tx_queue_qos_control_cnt =
+			register_query_field(p->mp_reg_tx_queue_qos_control, DBS_TX_QOS_CTRL_CNT);
+
+		if (p->mp_reg_tx_queue_qos_data) {
+			p->mp_reg_tx_queue_qos_data_en =
+				register_query_field(p->mp_reg_tx_queue_qos_data,
+						     DBS_TX_QOS_DATA_EN);
+			p->mp_reg_tx_queue_qos_data_ir =
+				register_query_field(p->mp_reg_tx_queue_qos_data,
+						     DBS_TX_QOS_DATA_IR);
+			p->mp_reg_tx_queue_qos_data_bs =
+				register_query_field(p->mp_reg_tx_queue_qos_data,
+						     DBS_TX_QOS_DATA_BS);
+		}
+	}
+
+	p->mp_reg_tx_queue_qos_rate =
+		module_query_register(p->mp_mod_dbs, DBS_TX_QOS_RATE);
+	if (p->mp_reg_tx_queue_qos_rate) {
+		p->mp_reg_tx_queue_qos_rate_mul =
+			register_query_field(p->mp_reg_tx_queue_qos_rate, DBS_TX_QOS_RATE_MUL);
+		p->mp_reg_tx_queue_qos_rate_div =
+			register_query_field(p->mp_reg_tx_queue_qos_rate, DBS_TX_QOS_RATE_DIV);
+	}
+
+	return 0;
+}
+
+int dbs_reset_rx_control(nthw_dbs_t *p)
+{
+	field_set_val32(p->mp_fld_rx_control_last_queue, 0);
+	field_set_val32(p->mp_fld_rx_control_avail_monitor_enable, 0);
+	field_set_val32(p->mp_fld_rx_control_avail_monitor_scan_speed, 8);
+	field_set_val32(p->mp_fld_rx_control_used_write_enable, 0);
+	field_set_val32(p->mp_fld_rx_control_used_writer_update_speed, 5);
+	field_set_val32(p->mp_fld_rx_control_rx_queues_enable, 0);
+	register_flush(p->mp_reg_rx_control, 1);
+	return 0;
+}
+
+int dbs_reset_tx_control(nthw_dbs_t *p)
+{
+	field_set_val32(p->mp_fld_tx_control_last_queue, 0);
+	field_set_val32(p->mp_fld_tx_control_avail_monitor_enable, 0);
+	field_set_val32(p->mp_fld_tx_control_avail_monitor_scan_speed, 5);
+	field_set_val32(p->mp_fld_tx_control_used_write_enable, 0);
+	field_set_val32(p->mp_fld_tx_control_used_writer_update_speed, 8);
+	field_set_val32(p->mp_fld_tx_control_tx_queues_enable, 0);
+	register_flush(p->mp_reg_tx_control, 1);
+	return 0;
+}
+
+void dbs_reset(nthw_dbs_t *p)
+{
+	uint32_t i;
+
+	NT_LOG(DBG, NTHW, "NthwDbs::%s: resetting DBS", __func__);
+
+	dbs_reset_rx_control(p);
+	dbs_reset_tx_control(p);
+
+	/* Reset RX memory banks and shado */
+	for (i = 0; i < NT_DBS_RX_QUEUES_MAX; ++i) {
+		set_shadow_rx_am_data(p, i, 0, 0, 0, 0, 0);
+		flush_rx_am_data(p, i);
+
+		set_shadow_rx_uw_data(p, i, 0, 0, 0, 0, 0, 0, 0);
+		flush_rx_uw_data(p, i);
+
+		set_shadow_rx_dr_data(p, i, 0, 0, 0, 0, 0);
+		flush_rx_dr_data(p, i);
+	}
+
+	/* Reset TX memory banks and shado */
+	for (i = 0; i < NT_DBS_TX_QUEUES_MAX; ++i) {
+		set_shadow_tx_am_data(p, i, 0, 0, 0, 0, 0);
+		flush_tx_am_data(p, i);
+
+		set_shadow_tx_uw_data(p, i, 0, 0, 0, 0, 0, 0, 0, 0);
+		flush_tx_uw_data(p, i);
+
+		set_shadow_tx_dr_data(p, i, 0, 0, 0, 0, 0, 0);
+		flush_tx_dr_data(p, i);
+
+		set_shadow_tx_qp_data(p, i, 0);
+		flush_tx_qp_data(p, i);
+
+		set_shadow_tx_qos_data(p, i, 0, 0, 0);
+		flush_tx_qos_data(p, i);
+	}
+}
+
+int set_rx_control(nthw_dbs_t *p, uint32_t last_queue,
+		   uint32_t avail_monitor_enable, uint32_t avail_monitor_speed,
+		   uint32_t used_write_enable, uint32_t used_write_speed,
+		   uint32_t rx_queue_enable)
+{
+#ifdef DBS_PRINT_REGS
+	printf("last_queue %u\n", last_queue);
+	printf("avail_monitor_enable %u\n", avail_monitor_enable);
+	printf("avail_monitor_speed %u\n", avail_monitor_speed);
+	printf("used_write_enable %u\n", used_write_enable);
+	printf("used_write_speed %u\n", used_write_speed);
+	printf("rx_queue_enable %u\n", rx_queue_enable);
+#endif
+
+	field_set_val32(p->mp_fld_rx_control_last_queue, last_queue);
+	field_set_val32(p->mp_fld_rx_control_avail_monitor_enable, avail_monitor_enable);
+	field_set_val32(p->mp_fld_rx_control_avail_monitor_scan_speed,
+		       avail_monitor_speed);
+	field_set_val32(p->mp_fld_rx_control_used_write_enable, used_write_enable);
+	field_set_val32(p->mp_fld_rx_control_used_writer_update_speed, used_write_speed);
+	field_set_val32(p->mp_fld_rx_control_rx_queues_enable, rx_queue_enable);
+	register_flush(p->mp_reg_rx_control, 1);
+	return 0;
+}
+
+int nthw_dbs_get_rx_control(nthw_dbs_t *p, uint32_t *last_queue,
+			 uint32_t *avail_monitor_enable,
+			 uint32_t *avail_monitor_speed, uint32_t *used_write_enable,
+			 uint32_t *used_write_speed, uint32_t *rx_queue_enable)
+{
+	*last_queue = field_get_val32(p->mp_fld_rx_control_last_queue);
+	*avail_monitor_enable =
+		field_get_val32(p->mp_fld_rx_control_avail_monitor_enable);
+	*avail_monitor_speed =
+		field_get_val32(p->mp_fld_rx_control_avail_monitor_scan_speed);
+	*used_write_enable = field_get_val32(p->mp_fld_rx_control_used_write_enable);
+	*used_write_speed =
+		field_get_val32(p->mp_fld_rx_control_used_writer_update_speed);
+	*rx_queue_enable = field_get_val32(p->mp_fld_rx_control_rx_queues_enable);
+	return 0;
+}
+
+int set_tx_control(nthw_dbs_t *p, uint32_t last_queue,
+		   uint32_t avail_monitor_enable, uint32_t avail_monitor_speed,
+		   uint32_t used_write_enable, uint32_t used_write_speed,
+		   uint32_t tx_queue_enable)
+{
+#ifdef DBS_PRINT_REGS
+	printf("last_queue %u\n", last_queue);
+	printf("avail_monitor_enable %u\n", avail_monitor_enable);
+	printf("avail_monitor_speed %u\n", avail_monitor_speed);
+	printf("used_write_enable %u\n", used_write_enable);
+	printf("used_write_speed %u\n", used_write_speed);
+#endif
+
+	field_set_val32(p->mp_fld_tx_control_last_queue, last_queue);
+	field_set_val32(p->mp_fld_tx_control_avail_monitor_enable, avail_monitor_enable);
+	field_set_val32(p->mp_fld_tx_control_avail_monitor_scan_speed,
+		       avail_monitor_speed);
+	field_set_val32(p->mp_fld_tx_control_used_write_enable, used_write_enable);
+	field_set_val32(p->mp_fld_tx_control_used_writer_update_speed, used_write_speed);
+	field_set_val32(p->mp_fld_tx_control_tx_queues_enable, tx_queue_enable);
+	register_flush(p->mp_reg_tx_control, 1);
+	return 0;
+}
+
+int nthw_dbs_get_tx_control(nthw_dbs_t *p, uint32_t *last_queue,
+			 uint32_t *avail_monitor_enable,
+			 uint32_t *avail_monitor_speed, uint32_t *used_write_enable,
+			 uint32_t *used_write_speed, uint32_t *tx_queue_enable)
+{
+	*last_queue = field_get_val32(p->mp_fld_tx_control_last_queue);
+	*avail_monitor_enable =
+		field_get_val32(p->mp_fld_tx_control_avail_monitor_enable);
+	*avail_monitor_speed =
+		field_get_val32(p->mp_fld_tx_control_avail_monitor_scan_speed);
+	*used_write_enable = field_get_val32(p->mp_fld_tx_control_used_write_enable);
+	*used_write_speed =
+		field_get_val32(p->mp_fld_tx_control_used_writer_update_speed);
+	*tx_queue_enable = field_get_val32(p->mp_fld_tx_control_tx_queues_enable);
+	return 0;
+}
+
+int set_rx_init(nthw_dbs_t *p, uint32_t start_idx, uint32_t start_ptr,
+		uint32_t init, uint32_t queue)
+{
+	if (p->mp_reg_rx_init_val) {
+		field_set_val32(p->mp_fld_rx_init_val_idx, start_idx);
+		field_set_val32(p->mp_fld_rx_init_val_ptr, start_ptr);
+		register_flush(p->mp_reg_rx_init_val, 1);
+	}
+	field_set_val32(p->mp_fld_rx_init_init, init);
+	field_set_val32(p->mp_fld_rx_init_queue, queue);
+	register_flush(p->mp_reg_rx_init, 1);
+	return 0;
+}
+
+int get_rx_init(nthw_dbs_t *p, uint32_t *init, uint32_t *queue, uint32_t *busy)
+{
+	*init = field_get_val32(p->mp_fld_rx_init_init);
+	*queue = field_get_val32(p->mp_fld_rx_init_queue);
+	*busy = field_get_val32(p->mp_fld_rx_init_busy);
+	return 0;
+}
+
+int set_tx_init(nthw_dbs_t *p, uint32_t start_idx, uint32_t start_ptr,
+		uint32_t init, uint32_t queue)
+{
+	if (p->mp_reg_tx_init_val) {
+		field_set_val32(p->mp_fld_tx_init_val_idx, start_idx);
+		field_set_val32(p->mp_fld_tx_init_val_ptr, start_ptr);
+		register_flush(p->mp_reg_tx_init_val, 1);
+	}
+	field_set_val32(p->mp_fld_tx_init_init, init);
+	field_set_val32(p->mp_fld_tx_init_queue, queue);
+	register_flush(p->mp_reg_tx_init, 1);
+	return 0;
+}
+
+int get_tx_init(nthw_dbs_t *p, uint32_t *init, uint32_t *queue, uint32_t *busy)
+{
+	*init = field_get_val32(p->mp_fld_tx_init_init);
+	*queue = field_get_val32(p->mp_fld_tx_init_queue);
+	*busy = field_get_val32(p->mp_fld_tx_init_busy);
+	return 0;
+}
+
+int set_rx_idle(nthw_dbs_t *p, uint32_t idle, uint32_t queue)
+
+{
+	if (!p->mp_reg_rx_idle)
+		return -ENOTSUP;
+
+	field_set_val32(p->mp_fld_rx_idle_idle, idle);
+	field_set_val32(p->mp_fld_rx_idle_queue, queue);
+	register_flush(p->mp_reg_rx_idle, 1);
+	return 0;
+}
+
+int get_rx_idle(nthw_dbs_t *p, uint32_t *idle, uint32_t *queue, uint32_t *busy)
+{
+	if (!p->mp_reg_rx_idle)
+		return -ENOTSUP;
+
+	*idle = field_get_updated(p->mp_fld_rx_idle_idle);
+	*queue = 0;
+	*busy = field_get_updated(p->mp_fld_rx_idle_busy);
+	return 0;
+}
+
+int set_tx_idle(nthw_dbs_t *p, uint32_t idle, uint32_t queue)
+
+{
+	if (!p->mp_reg_tx_idle)
+		return -ENOTSUP;
+
+	field_set_val32(p->mp_fld_tx_idle_idle, idle);
+	field_set_val32(p->mp_fld_tx_idle_queue, queue);
+	register_flush(p->mp_reg_tx_idle, 1);
+	return 0;
+}
+
+int get_tx_idle(nthw_dbs_t *p, uint32_t *idle, uint32_t *queue, uint32_t *busy)
+{
+	if (!p->mp_reg_tx_idle)
+		return -ENOTSUP;
+
+	*idle = field_get_updated(p->mp_fld_tx_idle_idle);
+	*queue = 0;
+	*busy = field_get_updated(p->mp_fld_tx_idle_busy);
+	return 0;
+}
+
+int set_rx_ptr_queue(nthw_dbs_t *p, uint32_t queue)
+{
+	if (!p->mp_reg_rx_ptr)
+		return -ENOTSUP;
+
+	field_set_val32(p->mp_fld_rx_ptr_queue, queue);
+	register_flush(p->mp_reg_rx_ptr, 1);
+	return 0;
+}
+
+int get_rx_ptr(nthw_dbs_t *p, uint32_t *ptr, uint32_t *queue, uint32_t *valid)
+{
+	if (!p->mp_reg_rx_ptr)
+		return -ENOTSUP;
+
+	*ptr = field_get_updated(p->mp_fld_rx_ptr_ptr);
+	*queue = 0;
+	*valid = field_get_updated(p->mp_fld_rx_ptr_valid);
+	return 0;
+}
+
+int set_tx_ptr_queue(nthw_dbs_t *p, uint32_t queue)
+{
+	if (!p->mp_reg_tx_ptr)
+		return -ENOTSUP;
+
+	field_set_val32(p->mp_fld_tx_ptr_queue, queue);
+	register_flush(p->mp_reg_tx_ptr, 1);
+	return 0;
+}
+
+int get_tx_ptr(nthw_dbs_t *p, uint32_t *ptr, uint32_t *queue, uint32_t *valid)
+{
+	if (!p->mp_reg_tx_ptr)
+		return -ENOTSUP;
+
+	*ptr = field_get_updated(p->mp_fld_tx_ptr_ptr);
+	*queue = 0;
+	*valid = field_get_updated(p->mp_fld_tx_ptr_valid);
+	return 0;
+}
+
+static void set_rx_am_data_index(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val32(p->mp_fld_rx_avail_monitor_control_adr, index);
+	field_set_val32(p->mp_fld_rx_avail_monitor_control_cnt, 1);
+	register_flush(p->mp_reg_rx_avail_monitor_control, 1);
+}
+
+static void
+set_shadow_rx_am_data_guest_physical_address(nthw_dbs_t *p, uint32_t index,
+				       uint64_t guest_physical_address)
+{
+	p->m_rx_am_shadow[index].guest_physical_address = guest_physical_address;
+}
+
+static void nthw_dbs_set_shadow_rx_am_data_enable(nthw_dbs_t *p, uint32_t index,
+		uint32_t enable)
+{
+	p->m_rx_am_shadow[index].enable = enable;
+}
+
+static void set_shadow_rx_am_data_host_id(nthw_dbs_t *p, uint32_t index,
+				     uint32_t host_id)
+{
+	p->m_rx_am_shadow[index].host_id = host_id;
+}
+
+static void set_shadow_rx_am_data_packed(nthw_dbs_t *p, uint32_t index,
+				     uint32_t packed)
+{
+	p->m_rx_am_shadow[index].packed = packed;
+}
+
+static void set_shadow_rx_am_data_int_enable(nthw_dbs_t *p, uint32_t index,
+					uint32_t int_enable)
+{
+	p->m_rx_am_shadow[index].int_enable = int_enable;
+}
+
+static void set_shadow_rx_am_data(nthw_dbs_t *p, uint32_t index,
+			       uint64_t guest_physical_address, uint32_t enable,
+			       uint32_t host_id, uint32_t packed,
+			       uint32_t int_enable)
+{
+	set_shadow_rx_am_data_guest_physical_address(p, index, guest_physical_address);
+	nthw_dbs_set_shadow_rx_am_data_enable(p, index, enable);
+	set_shadow_rx_am_data_host_id(p, index, host_id);
+	set_shadow_rx_am_data_packed(p, index, packed);
+	set_shadow_rx_am_data_int_enable(p, index, int_enable);
+}
+
+static void flush_rx_am_data(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val(p->mp_fld_rx_avail_monitor_data_guest_physical_address,
+		     (uint32_t *)&p->m_rx_am_shadow[index].guest_physical_address,
+		     2);
+	field_set_val32(p->mp_fld_rx_avail_monitor_data_enable,
+		       p->m_rx_am_shadow[index].enable);
+	field_set_val32(p->mp_fld_rx_avail_monitor_data_host_id,
+		       p->m_rx_am_shadow[index].host_id);
+	if (p->mp_fld_rx_avail_monitor_data_packed) {
+		field_set_val32(p->mp_fld_rx_avail_monitor_data_packed,
+			       p->m_rx_am_shadow[index].packed);
+	}
+	if (p->mp_fld_rx_avail_monitor_data_int) {
+		field_set_val32(p->mp_fld_rx_avail_monitor_data_int,
+			       p->m_rx_am_shadow[index].int_enable);
+	}
+
+	set_rx_am_data_index(p, index);
+	register_flush(p->mp_reg_rx_avail_monitor_data, 1);
+}
+
+int set_rx_am_data(nthw_dbs_t *p, uint32_t index, uint64_t guest_physical_address,
+		   uint32_t enable, uint32_t host_id, uint32_t packed,
+		   uint32_t int_enable)
+{
+	if (!p->mp_reg_rx_avail_monitor_data)
+		return -ENOTSUP;
+
+	set_shadow_rx_am_data(p, index, guest_physical_address, enable, host_id,
+			   packed, int_enable);
+	flush_rx_am_data(p, index);
+	return 0;
+}
+
+static void set_tx_am_data_index(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val32(p->mp_fld_tx_avail_monitor_control_adr, index);
+	field_set_val32(p->mp_fld_tx_avail_monitor_control_cnt, 1);
+	register_flush(p->mp_reg_tx_avail_monitor_control, 1);
+}
+
+static void set_shadow_tx_am_data(nthw_dbs_t *p, uint32_t index,
+			       uint64_t guest_physical_address, uint32_t enable,
+			       uint32_t host_id, uint32_t packed,
+			       uint32_t int_enable)
+{
+	p->m_tx_am_shadow[index].guest_physical_address = guest_physical_address;
+	p->m_tx_am_shadow[index].enable = enable;
+	p->m_tx_am_shadow[index].host_id = host_id;
+	p->m_tx_am_shadow[index].packed = packed;
+	p->m_tx_am_shadow[index].int_enable = int_enable;
+}
+
+static void flush_tx_am_data(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val(p->mp_fld_tx_avail_monitor_data_guest_physical_address,
+		     (uint32_t *)&p->m_tx_am_shadow[index].guest_physical_address,
+		     2);
+	field_set_val32(p->mp_fld_tx_avail_monitor_data_enable,
+		       p->m_tx_am_shadow[index].enable);
+	field_set_val32(p->mp_fld_tx_avail_monitor_data_host_id,
+		       p->m_tx_am_shadow[index].host_id);
+	if (p->mp_fld_tx_avail_monitor_data_packed) {
+		field_set_val32(p->mp_fld_tx_avail_monitor_data_packed,
+			       p->m_tx_am_shadow[index].packed);
+	}
+	if (p->mp_fld_tx_avail_monitor_data_int) {
+		field_set_val32(p->mp_fld_tx_avail_monitor_data_int,
+			       p->m_tx_am_shadow[index].int_enable);
+	}
+
+	set_tx_am_data_index(p, index);
+	register_flush(p->mp_reg_tx_avail_monitor_data, 1);
+}
+
+int set_tx_am_data(nthw_dbs_t *p, uint32_t index, uint64_t guest_physical_address,
+		   uint32_t enable, uint32_t host_id, uint32_t packed,
+		   uint32_t int_enable)
+{
+	if (!p->mp_reg_tx_avail_monitor_data)
+		return -ENOTSUP;
+
+	set_shadow_tx_am_data(p, index, guest_physical_address, enable, host_id,
+			   packed, int_enable);
+	flush_tx_am_data(p, index);
+	return 0;
+}
+
+static void set_rx_uw_data_index(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val32(p->mp_fld_rx_used_writer_control_adr, index);
+	field_set_val32(p->mp_fld_rx_used_writer_control_cnt, 1);
+	register_flush(p->mp_reg_rx_used_writer_control, 1);
+}
+
+static void
+set_shadow_rx_uw_data_guest_physical_address(nthw_dbs_t *p, uint32_t index,
+				       uint64_t guest_physical_address)
+{
+	p->m_rx_uw_shadow[index].guest_physical_address = guest_physical_address;
+}
+
+static void set_shadow_rx_uw_data_host_id(nthw_dbs_t *p, uint32_t index,
+				     uint32_t host_id)
+{
+	p->m_rx_uw_shadow[index].host_id = host_id;
+}
+
+static void set_shadow_rx_uw_data_queue_size(nthw_dbs_t *p, uint32_t index,
+					uint32_t queue_size)
+{
+	p->m_rx_uw_shadow[index].queue_size = queue_size;
+}
+
+static void set_shadow_rx_uw_data_packed(nthw_dbs_t *p, uint32_t index,
+				     uint32_t packed)
+{
+	p->m_rx_uw_shadow[index].packed = packed;
+}
+
+static void set_shadow_rx_uw_data_int_enable(nthw_dbs_t *p, uint32_t index,
+					uint32_t int_enable)
+{
+	p->m_rx_uw_shadow[index].int_enable = int_enable;
+}
+
+static void set_shadow_rx_uw_data_vec(nthw_dbs_t *p, uint32_t index, uint32_t vec)
+{
+	p->m_rx_uw_shadow[index].vec = vec;
+}
+
+static void set_shadow_rx_uw_data_istk(nthw_dbs_t *p, uint32_t index, uint32_t istk)
+{
+	p->m_rx_uw_shadow[index].istk = istk;
+}
+
+static void set_shadow_rx_uw_data(nthw_dbs_t *p, uint32_t index,
+			       uint64_t guest_physical_address, uint32_t host_id,
+			       uint32_t queue_size, uint32_t packed,
+			       uint32_t int_enable, uint32_t vec, uint32_t istk)
+{
+	set_shadow_rx_uw_data_guest_physical_address(p, index, guest_physical_address);
+	set_shadow_rx_uw_data_host_id(p, index, host_id);
+	set_shadow_rx_uw_data_queue_size(p, index, queue_size);
+	set_shadow_rx_uw_data_packed(p, index, packed);
+	set_shadow_rx_uw_data_int_enable(p, index, int_enable);
+	set_shadow_rx_uw_data_vec(p, index, vec);
+	set_shadow_rx_uw_data_istk(p, index, istk);
+}
+
+static void flush_rx_uw_data(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val(p->mp_fld_rx_used_writer_data_guest_physical_address,
+		     (uint32_t *)&p->m_rx_uw_shadow[index].guest_physical_address,
+		     2);
+	field_set_val32(p->mp_fld_rx_used_writer_data_host_id,
+		       p->m_rx_uw_shadow[index].host_id);
+	if (module_is_version_newer(p->mp_mod_dbs, 0, 8)) {
+		field_set_val32(p->mp_fld_rx_used_writer_data_queue_size,
+			       (1U << p->m_rx_uw_shadow[index].queue_size) - 1U);
+	} else {
+		field_set_val32(p->mp_fld_rx_used_writer_data_queue_size,
+			       p->m_rx_uw_shadow[index].queue_size);
+	}
+	if (p->mp_fld_rx_used_writer_data_packed) {
+		field_set_val32(p->mp_fld_rx_used_writer_data_packed,
+			       p->m_rx_uw_shadow[index].packed);
+	}
+	if (p->mp_fld_rx_used_writer_data_int) {
+		field_set_val32(p->mp_fld_rx_used_writer_data_int,
+			       p->m_rx_uw_shadow[index].int_enable);
+		field_set_val32(p->mp_fld_rx_used_writer_data_vec,
+			       p->m_rx_uw_shadow[index].vec);
+		field_set_val32(p->mp_fld_rx_used_writer_data_istk,
+			       p->m_rx_uw_shadow[index].istk);
+	}
+
+	set_rx_uw_data_index(p, index);
+	register_flush(p->mp_reg_rx_used_writer_data, 1);
+}
+
+int set_rx_uw_data(nthw_dbs_t *p, uint32_t index, uint64_t guest_physical_address,
+		   uint32_t host_id, uint32_t queue_size, uint32_t packed,
+		   uint32_t int_enable, uint32_t vec, uint32_t istk)
+{
+	if (!p->mp_reg_rx_used_writer_data)
+		return -ENOTSUP;
+
+	set_shadow_rx_uw_data(p, index, guest_physical_address, host_id, queue_size,
+			   packed, int_enable, vec, istk);
+	flush_rx_uw_data(p, index);
+	return 0;
+}
+
+static void set_tx_uw_data_index(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val32(p->mp_fld_tx_used_writer_control_adr, index);
+	field_set_val32(p->mp_fld_tx_used_writer_control_cnt, 1);
+	register_flush(p->mp_reg_tx_used_writer_control, 1);
+}
+
+static void
+set_shadow_tx_uw_data_guest_physical_address(nthw_dbs_t *p, uint32_t index,
+				       uint64_t guest_physical_address)
+{
+	p->m_tx_uw_shadow[index].guest_physical_address = guest_physical_address;
+}
+
+static void set_shadow_tx_uw_data_host_id(nthw_dbs_t *p, uint32_t index,
+				     uint32_t host_id)
+{
+	p->m_tx_uw_shadow[index].host_id = host_id;
+}
+
+static void set_shadow_tx_uw_data_queue_size(nthw_dbs_t *p, uint32_t index,
+					uint32_t queue_size)
+{
+	p->m_tx_uw_shadow[index].queue_size = queue_size;
+}
+
+static void set_shadow_tx_uw_data_packed(nthw_dbs_t *p, uint32_t index,
+				     uint32_t packed)
+{
+	p->m_tx_uw_shadow[index].packed = packed;
+}
+
+static void set_shadow_tx_uw_data_int_enable(nthw_dbs_t *p, uint32_t index,
+					uint32_t int_enable)
+{
+	p->m_tx_uw_shadow[index].int_enable = int_enable;
+}
+
+static void set_shadow_tx_uw_data_vec(nthw_dbs_t *p, uint32_t index, uint32_t vec)
+{
+	p->m_tx_uw_shadow[index].vec = vec;
+}
+
+static void set_shadow_tx_uw_data_istk(nthw_dbs_t *p, uint32_t index, uint32_t istk)
+{
+	p->m_tx_uw_shadow[index].istk = istk;
+}
+
+static void set_shadow_tx_uw_data_in_order(nthw_dbs_t *p, uint32_t index,
+				      uint32_t in_order)
+{
+	p->m_tx_uw_shadow[index].in_order = in_order;
+}
+
+static void set_shadow_tx_uw_data(nthw_dbs_t *p, uint32_t index,
+			       uint64_t guest_physical_address, uint32_t host_id,
+			       uint32_t queue_size, uint32_t packed,
+			       uint32_t int_enable, uint32_t vec, uint32_t istk,
+			       uint32_t in_order)
+{
+	set_shadow_tx_uw_data_guest_physical_address(p, index, guest_physical_address);
+	set_shadow_tx_uw_data_host_id(p, index, host_id);
+	set_shadow_tx_uw_data_queue_size(p, index, queue_size);
+	set_shadow_tx_uw_data_packed(p, index, packed);
+	set_shadow_tx_uw_data_int_enable(p, index, int_enable);
+	set_shadow_tx_uw_data_vec(p, index, vec);
+	set_shadow_tx_uw_data_istk(p, index, istk);
+	set_shadow_tx_uw_data_in_order(p, index, in_order);
+}
+
+static void flush_tx_uw_data(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val(p->mp_fld_tx_used_writer_data_guest_physical_address,
+		     (uint32_t *)&p->m_tx_uw_shadow[index].guest_physical_address,
+		     2);
+	field_set_val32(p->mp_fld_tx_used_writer_data_host_id,
+		       p->m_tx_uw_shadow[index].host_id);
+	if (module_is_version_newer(p->mp_mod_dbs, 0, 8)) {
+		field_set_val32(p->mp_fld_tx_used_writer_data_queue_size,
+			       (1U << p->m_tx_uw_shadow[index].queue_size) - 1U);
+	} else {
+		field_set_val32(p->mp_fld_tx_used_writer_data_queue_size,
+			       p->m_tx_uw_shadow[index].queue_size);
+	}
+	if (p->mp_fld_tx_used_writer_data_packed) {
+		field_set_val32(p->mp_fld_tx_used_writer_data_packed,
+			       p->m_tx_uw_shadow[index].packed);
+	}
+	if (p->mp_fld_tx_used_writer_data_int) {
+		field_set_val32(p->mp_fld_tx_used_writer_data_int,
+			       p->m_tx_uw_shadow[index].int_enable);
+		field_set_val32(p->mp_fld_tx_used_writer_data_vec,
+			       p->m_tx_uw_shadow[index].vec);
+		field_set_val32(p->mp_fld_tx_used_writer_data_istk,
+			       p->m_tx_uw_shadow[index].istk);
+	}
+	if (p->mp_fld_tx_used_writer_data_in_order) {
+		field_set_val32(p->mp_fld_tx_used_writer_data_in_order,
+			       p->m_tx_uw_shadow[index].in_order);
+	}
+
+	set_tx_uw_data_index(p, index);
+	register_flush(p->mp_reg_tx_used_writer_data, 1);
+}
+
+int set_tx_uw_data(nthw_dbs_t *p, uint32_t index, uint64_t guest_physical_address,
+		   uint32_t host_id, uint32_t queue_size, uint32_t packed,
+		   uint32_t int_enable, uint32_t vec, uint32_t istk,
+		   uint32_t in_order)
+{
+	if (!p->mp_reg_tx_used_writer_data)
+		return -ENOTSUP;
+
+	set_shadow_tx_uw_data(p, index, guest_physical_address, host_id, queue_size,
+			   packed, int_enable, vec, istk, in_order);
+	flush_tx_uw_data(p, index);
+	return 0;
+}
+
+static void set_rx_dr_data_index(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val32(p->mp_fld_rx_descriptor_reader_control_adr, index);
+	field_set_val32(p->mp_fld_rx_descriptor_reader_control_cnt, 1);
+	register_flush(p->mp_reg_rx_descriptor_reader_control, 1);
+}
+
+static void
+set_shadow_rx_dr_data_guest_physical_address(nthw_dbs_t *p, uint32_t index,
+				       uint64_t guest_physical_address)
+{
+	p->m_rx_dr_shadow[index].guest_physical_address = guest_physical_address;
+}
+
+static void set_shadow_rx_dr_data_host_id(nthw_dbs_t *p, uint32_t index,
+				     uint32_t host_id)
+{
+	p->m_rx_dr_shadow[index].host_id = host_id;
+}
+
+static void set_shadow_rx_dr_data_queue_size(nthw_dbs_t *p, uint32_t index,
+					uint32_t queue_size)
+{
+	p->m_rx_dr_shadow[index].queue_size = queue_size;
+}
+
+static void set_shadow_rx_dr_data_header(nthw_dbs_t *p, uint32_t index,
+				     uint32_t header)
+{
+	p->m_rx_dr_shadow[index].header = header;
+}
+
+static void set_shadow_rx_dr_data_packed(nthw_dbs_t *p, uint32_t index,
+				     uint32_t packed)
+{
+	p->m_rx_dr_shadow[index].packed = packed;
+}
+
+static void set_shadow_rx_dr_data(nthw_dbs_t *p, uint32_t index,
+			       uint64_t guest_physical_address, uint32_t host_id,
+			       uint32_t queue_size, uint32_t header,
+			       uint32_t packed)
+{
+	set_shadow_rx_dr_data_guest_physical_address(p, index, guest_physical_address);
+	set_shadow_rx_dr_data_host_id(p, index, host_id);
+	set_shadow_rx_dr_data_queue_size(p, index, queue_size);
+	set_shadow_rx_dr_data_header(p, index, header);
+	set_shadow_rx_dr_data_packed(p, index, packed);
+}
+
+static void flush_rx_dr_data(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val(p->mp_fld_rx_descriptor_reader_data_guest_physical_address,
+		     (uint32_t *)&p->m_rx_dr_shadow[index].guest_physical_address,
+		     2);
+	field_set_val32(p->mp_fld_rx_descriptor_reader_data_host_id,
+		       p->m_rx_dr_shadow[index].host_id);
+	if (module_is_version_newer(p->mp_mod_dbs, 0, 8)) {
+		field_set_val32(p->mp_fld_rx_descriptor_reader_data_queue_size,
+			       (1U << p->m_rx_dr_shadow[index].queue_size) - 1U);
+	} else {
+		field_set_val32(p->mp_fld_rx_descriptor_reader_data_queue_size,
+			       p->m_rx_dr_shadow[index].queue_size);
+	}
+	field_set_val32(p->mp_fld_rx_descriptor_reader_data_header,
+		       p->m_rx_dr_shadow[index].header);
+	if (p->mp_fld_rx_descriptor_reader_data_packed) {
+		field_set_val32(p->mp_fld_rx_descriptor_reader_data_packed,
+			       p->m_rx_dr_shadow[index].packed);
+	}
+
+	set_rx_dr_data_index(p, index);
+	register_flush(p->mp_reg_rx_descriptor_reader_data, 1);
+}
+
+int set_rx_dr_data(nthw_dbs_t *p, uint32_t index, uint64_t guest_physical_address,
+		   uint32_t host_id, uint32_t queue_size, uint32_t header,
+		   uint32_t packed)
+{
+	if (!p->mp_reg_rx_descriptor_reader_data)
+		return -ENOTSUP;
+
+	set_shadow_rx_dr_data(p, index, guest_physical_address, host_id, queue_size,
+			   header, packed);
+	flush_rx_dr_data(p, index);
+	return 0;
+}
+
+static void set_tx_dr_data_index(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val32(p->mp_fld_tx_descriptor_reader_control_adr, index);
+	field_set_val32(p->mp_fld_tx_descriptor_reader_control_cnt, 1);
+	register_flush(p->mp_reg_tx_descriptor_reader_control, 1);
+}
+
+static void
+set_shadow_tx_dr_data_guest_physical_address(nthw_dbs_t *p, uint32_t index,
+				       uint64_t guest_physical_address)
+{
+	p->m_tx_dr_shadow[index].guest_physical_address = guest_physical_address;
+}
+
+static void set_shadow_tx_dr_data_host_id(nthw_dbs_t *p, uint32_t index,
+				     uint32_t host_id)
+{
+	p->m_tx_dr_shadow[index].host_id = host_id;
+}
+
+static void set_shadow_tx_dr_data_queue_size(nthw_dbs_t *p, uint32_t index,
+					uint32_t queue_size)
+{
+	p->m_tx_dr_shadow[index].queue_size = queue_size;
+}
+
+static void set_shadow_tx_dr_data_header(nthw_dbs_t *p, uint32_t index,
+				     uint32_t header)
+{
+	p->m_tx_dr_shadow[index].header = header;
+}
+
+static void set_shadow_tx_dr_data_port(nthw_dbs_t *p, uint32_t index, uint32_t port)
+{
+	p->m_tx_dr_shadow[index].port = port;
+}
+
+static void set_shadow_tx_dr_data_packed(nthw_dbs_t *p, uint32_t index,
+				     uint32_t packed)
+{
+	p->m_tx_dr_shadow[index].packed = packed;
+}
+
+static void set_shadow_tx_dr_data(nthw_dbs_t *p, uint32_t index,
+			       uint64_t guest_physical_address, uint32_t host_id,
+			       uint32_t queue_size, uint32_t port,
+			       uint32_t header, uint32_t packed)
+{
+	set_shadow_tx_dr_data_guest_physical_address(p, index, guest_physical_address);
+	set_shadow_tx_dr_data_host_id(p, index, host_id);
+	set_shadow_tx_dr_data_queue_size(p, index, queue_size);
+	set_shadow_tx_dr_data_header(p, index, header);
+	set_shadow_tx_dr_data_port(p, index, port);
+	set_shadow_tx_dr_data_packed(p, index, packed);
+}
+
+static void flush_tx_dr_data(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val(p->mp_fld_tx_descriptor_reader_data_guest_physical_address,
+		     (uint32_t *)&p->m_tx_dr_shadow[index].guest_physical_address,
+		     2);
+	field_set_val32(p->mp_fld_tx_descriptor_reader_data_host_id,
+		       p->m_tx_dr_shadow[index].host_id);
+	if (module_is_version_newer(p->mp_mod_dbs, 0, 8)) {
+		field_set_val32(p->mp_fld_tx_descriptor_reader_data_queue_size,
+			       (1U << p->m_tx_dr_shadow[index].queue_size) - 1U);
+	} else {
+		field_set_val32(p->mp_fld_tx_descriptor_reader_data_queue_size,
+			       p->m_tx_dr_shadow[index].queue_size);
+	}
+	field_set_val32(p->mp_fld_tx_descriptor_reader_data_header,
+		       p->m_tx_dr_shadow[index].header);
+	field_set_val32(p->mp_fld_tx_descriptor_reader_data_port,
+		       p->m_tx_dr_shadow[index].port);
+	if (p->mp_fld_tx_descriptor_reader_data_packed) {
+		field_set_val32(p->mp_fld_tx_descriptor_reader_data_packed,
+			       p->m_tx_dr_shadow[index].packed);
+	}
+
+	set_tx_dr_data_index(p, index);
+	register_flush(p->mp_reg_tx_descriptor_reader_data, 1);
+}
+
+int set_tx_dr_data(nthw_dbs_t *p, uint32_t index, uint64_t guest_physical_address,
+		   uint32_t host_id, uint32_t queue_size, uint32_t port,
+		   uint32_t header, uint32_t packed)
+{
+	if (!p->mp_reg_tx_descriptor_reader_data)
+		return -ENOTSUP;
+
+	set_shadow_tx_dr_data(p, index, guest_physical_address, host_id, queue_size,
+			   port, header, packed);
+	flush_tx_dr_data(p, index);
+	return 0;
+}
+
+static void set_tx_qp_data_index(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val32(p->mp_fld_tx_queue_property_control_adr, index);
+	field_set_val32(p->mp_fld_tx_queue_property_control_cnt, 1);
+	register_flush(p->mp_reg_tx_queue_property_control, 1);
+}
+
+static void set_shadow_tx_qp_data_virtual_port(nthw_dbs_t *p, uint32_t index,
+		uint32_t virtual_port)
+{
+	p->m_tx_qp_shadow[index].virtual_port = virtual_port;
+}
+
+static void set_shadow_tx_qp_data(nthw_dbs_t *p, uint32_t index,
+			       uint32_t virtual_port)
+{
+	set_shadow_tx_qp_data_virtual_port(p, index, virtual_port);
+}
+
+static void flush_tx_qp_data(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val32(p->mp_fld_tx_queue_property_data_v_port,
+		       p->m_tx_qp_shadow[index].virtual_port);
+
+	set_tx_qp_data_index(p, index);
+	register_flush(p->mp_reg_tx_queue_property_data, 1);
+}
+
+int nthw_dbs_set_tx_qp_data(nthw_dbs_t *p, uint32_t index, uint32_t virtual_port)
+{
+	if (!p->mp_reg_tx_queue_property_data)
+		return -ENOTSUP;
+
+	set_shadow_tx_qp_data(p, index, virtual_port);
+	flush_tx_qp_data(p, index);
+	return 0;
+}
+
+static void set_tx_qos_data_index(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val32(p->mp_reg_tx_queue_qos_control_adr, index);
+	field_set_val32(p->mp_reg_tx_queue_qos_control_cnt, 1);
+	register_flush(p->mp_reg_tx_queue_qos_control, 1);
+}
+
+static void set_shadow_tx_qos_data_enable(nthw_dbs_t *p, uint32_t index,
+				      uint32_t enable)
+{
+	p->m_tx_qos_shadow[index].enable = enable;
+}
+
+static void set_shadow_tx_qos_data_ir(nthw_dbs_t *p, uint32_t index, uint32_t ir)
+{
+	p->m_tx_qos_shadow[index].ir = ir;
+}
+
+static void set_shadow_tx_qos_data_bs(nthw_dbs_t *p, uint32_t index, uint32_t bs)
+{
+	p->m_tx_qos_shadow[index].bs = bs;
+}
+
+static void set_shadow_tx_qos_data(nthw_dbs_t *p, uint32_t index, uint32_t enable,
+				uint32_t ir, uint32_t bs)
+{
+	set_shadow_tx_qos_data_enable(p, index, enable);
+	set_shadow_tx_qos_data_ir(p, index, ir);
+	set_shadow_tx_qos_data_bs(p, index, bs);
+}
+
+static void flush_tx_qos_data(nthw_dbs_t *p, uint32_t index)
+{
+	field_set_val32(p->mp_reg_tx_queue_qos_data_en, p->m_tx_qos_shadow[index].enable);
+	field_set_val32(p->mp_reg_tx_queue_qos_data_ir, p->m_tx_qos_shadow[index].ir);
+	field_set_val32(p->mp_reg_tx_queue_qos_data_bs, p->m_tx_qos_shadow[index].bs);
+
+	set_tx_qos_data_index(p, index);
+	register_flush(p->mp_reg_tx_queue_qos_data, 1);
+}
+
+int set_tx_qos_data(nthw_dbs_t *p, uint32_t index, uint32_t enable, uint32_t ir,
+		    uint32_t bs)
+{
+	if (!p->mp_reg_tx_queue_qos_data)
+		return -ENOTSUP;
+
+	set_shadow_tx_qos_data(p, index, enable, ir, bs);
+	flush_tx_qos_data(p, index);
+	return 0;
+}
+
+int set_tx_qos_rate(nthw_dbs_t *p, uint32_t mul, uint32_t div)
+{
+	if (!p->mp_reg_tx_queue_qos_rate)
+		return -ENOTSUP;
+
+	field_set_val32(p->mp_reg_tx_queue_qos_rate_mul, mul);
+	field_set_val32(p->mp_reg_tx_queue_qos_rate_div, div);
+	register_flush(p->mp_reg_tx_queue_qos_rate, 1);
+	return 0;
+}
diff --git a/drivers/net/ntnic/nthw/nthw_dbs.h b/drivers/net/ntnic/nthw/nthw_dbs.h
new file mode 100644
index 0000000000..d5891d7538
--- /dev/null
+++ b/drivers/net/ntnic/nthw/nthw_dbs.h
@@ -0,0 +1,313 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NTHW_DBS_HPP_
+#define NTHW_DBS_HPP_
+
+#define NT_DBS_RX_QUEUES_MAX (128)
+#define NT_DBS_TX_QUEUES_MAX (128)
+
+/*
+ * Struct for implementation of memory bank shadows
+ */
+
+/* DBS_RX_AM_DATA */
+struct nthw_dbs_rx_am_data_s {
+	uint64_t guest_physical_address;
+	uint32_t enable;
+	uint32_t host_id;
+	uint32_t packed;
+	uint32_t int_enable;
+};
+
+/* DBS_TX_AM_DATA */
+struct nthw_dbs_tx_am_data_s {
+	uint64_t guest_physical_address;
+	uint32_t enable;
+	uint32_t host_id;
+	uint32_t packed;
+	uint32_t int_enable;
+};
+
+/* DBS_RX_UW_DATA */
+struct nthw_dbs_rx_uw_data_s {
+	uint64_t guest_physical_address;
+	uint32_t host_id;
+	uint32_t queue_size;
+	uint32_t packed;
+	uint32_t int_enable;
+	uint32_t vec;
+	uint32_t istk;
+};
+
+/* DBS_TX_UW_DATA */
+struct nthw_dbs_tx_uw_data_s {
+	uint64_t guest_physical_address;
+	uint32_t host_id;
+	uint32_t queue_size;
+	uint32_t packed;
+	uint32_t int_enable;
+	uint32_t vec;
+	uint32_t istk;
+	uint32_t in_order;
+};
+
+/* DBS_RX_DR_DATA */
+struct nthw_dbs_rx_dr_data_s {
+	uint64_t guest_physical_address;
+	uint32_t host_id;
+	uint32_t queue_size;
+	uint32_t header;
+	uint32_t packed;
+};
+
+/* DBS_TX_DR_DATA */
+struct nthw_dbs_tx_dr_data_s {
+	uint64_t guest_physical_address;
+	uint32_t host_id;
+	uint32_t queue_size;
+	uint32_t header;
+	uint32_t port;
+	uint32_t packed;
+};
+
+/* DBS_TX_QP_DATA */
+struct nthw_dbs_tx_qp_data_s {
+	uint32_t virtual_port;
+};
+
+struct nthw_dbs_tx_qos_data_s {
+	uint32_t enable;
+	uint32_t ir;
+	uint32_t bs;
+};
+
+struct nthw_dbs_s {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_dbs;
+	int mn_instance;
+
+	int mn_param_dbs_present;
+
+	nt_register_t *mp_reg_rx_control;
+	nt_field_t *mp_fld_rx_control_last_queue;
+	nt_field_t *mp_fld_rx_control_avail_monitor_enable;
+	nt_field_t *mp_fld_rx_control_avail_monitor_scan_speed;
+	nt_field_t *mp_fld_rx_control_used_write_enable;
+	nt_field_t *mp_fld_rx_control_used_writer_update_speed;
+	nt_field_t *mp_fld_rx_control_rx_queues_enable;
+
+	nt_register_t *mp_reg_tx_control;
+	nt_field_t *mp_fld_tx_control_last_queue;
+	nt_field_t *mp_fld_tx_control_avail_monitor_enable;
+	nt_field_t *mp_fld_tx_control_avail_monitor_scan_speed;
+	nt_field_t *mp_fld_tx_control_used_write_enable;
+	nt_field_t *mp_fld_tx_control_used_writer_update_speed;
+	nt_field_t *mp_fld_tx_control_tx_queues_enable;
+
+	nt_register_t *mp_reg_rx_init;
+	nt_field_t *mp_fld_rx_init_init;
+	nt_field_t *mp_fld_rx_init_queue;
+	nt_field_t *mp_fld_rx_init_busy;
+
+	nt_register_t *mp_reg_rx_init_val;
+	nt_field_t *mp_fld_rx_init_val_idx;
+	nt_field_t *mp_fld_rx_init_val_ptr;
+
+	nt_register_t *mp_reg_rx_ptr;
+	nt_field_t *mp_fld_rx_ptr_ptr;
+	nt_field_t *mp_fld_rx_ptr_queue;
+	nt_field_t *mp_fld_rx_ptr_valid;
+
+	nt_register_t *mp_reg_tx_init;
+	nt_field_t *mp_fld_tx_init_init;
+	nt_field_t *mp_fld_tx_init_queue;
+	nt_field_t *mp_fld_tx_init_busy;
+
+	nt_register_t *mp_reg_tx_init_val;
+	nt_field_t *mp_fld_tx_init_val_idx;
+	nt_field_t *mp_fld_tx_init_val_ptr;
+
+	nt_register_t *mp_reg_tx_ptr;
+	nt_field_t *mp_fld_tx_ptr_ptr;
+	nt_field_t *mp_fld_tx_ptr_queue;
+	nt_field_t *mp_fld_tx_ptr_valid;
+
+	nt_register_t *mp_reg_rx_idle;
+	nt_field_t *mp_fld_rx_idle_idle;
+	nt_field_t *mp_fld_rx_idle_queue;
+	nt_field_t *mp_fld_rx_idle_busy;
+
+	nt_register_t *mp_reg_tx_idle;
+	nt_field_t *mp_fld_tx_idle_idle;
+	nt_field_t *mp_fld_tx_idle_queue;
+	nt_field_t *mp_fld_tx_idle_busy;
+
+	nt_register_t *mp_reg_rx_avail_monitor_control;
+	nt_field_t *mp_fld_rx_avail_monitor_control_adr;
+	nt_field_t *mp_fld_rx_avail_monitor_control_cnt;
+
+	nt_register_t *mp_reg_rx_avail_monitor_data;
+	nt_field_t *mp_fld_rx_avail_monitor_data_guest_physical_address;
+	nt_field_t *mp_fld_rx_avail_monitor_data_enable;
+	nt_field_t *mp_fld_rx_avail_monitor_data_host_id;
+	nt_field_t *mp_fld_rx_avail_monitor_data_packed;
+	nt_field_t *mp_fld_rx_avail_monitor_data_int;
+
+	nt_register_t *mp_reg_tx_avail_monitor_control;
+	nt_field_t *mp_fld_tx_avail_monitor_control_adr;
+	nt_field_t *mp_fld_tx_avail_monitor_control_cnt;
+
+	nt_register_t *mp_reg_tx_avail_monitor_data;
+	nt_field_t *mp_fld_tx_avail_monitor_data_guest_physical_address;
+	nt_field_t *mp_fld_tx_avail_monitor_data_enable;
+	nt_field_t *mp_fld_tx_avail_monitor_data_host_id;
+	nt_field_t *mp_fld_tx_avail_monitor_data_packed;
+	nt_field_t *mp_fld_tx_avail_monitor_data_int;
+
+	nt_register_t *mp_reg_rx_used_writer_control;
+	nt_field_t *mp_fld_rx_used_writer_control_adr;
+	nt_field_t *mp_fld_rx_used_writer_control_cnt;
+
+	nt_register_t *mp_reg_rx_used_writer_data;
+	nt_field_t *mp_fld_rx_used_writer_data_guest_physical_address;
+	nt_field_t *mp_fld_rx_used_writer_data_host_id;
+	nt_field_t *mp_fld_rx_used_writer_data_queue_size;
+	nt_field_t *mp_fld_rx_used_writer_data_packed;
+	nt_field_t *mp_fld_rx_used_writer_data_int;
+	nt_field_t *mp_fld_rx_used_writer_data_vec;
+	nt_field_t *mp_fld_rx_used_writer_data_istk;
+
+	nt_register_t *mp_reg_tx_used_writer_control;
+	nt_field_t *mp_fld_tx_used_writer_control_adr;
+	nt_field_t *mp_fld_tx_used_writer_control_cnt;
+
+	nt_register_t *mp_reg_tx_used_writer_data;
+	nt_field_t *mp_fld_tx_used_writer_data_guest_physical_address;
+	nt_field_t *mp_fld_tx_used_writer_data_host_id;
+	nt_field_t *mp_fld_tx_used_writer_data_queue_size;
+	nt_field_t *mp_fld_tx_used_writer_data_packed;
+	nt_field_t *mp_fld_tx_used_writer_data_int;
+	nt_field_t *mp_fld_tx_used_writer_data_vec;
+	nt_field_t *mp_fld_tx_used_writer_data_istk;
+	nt_field_t *mp_fld_tx_used_writer_data_in_order;
+
+	nt_register_t *mp_reg_rx_descriptor_reader_control;
+	nt_field_t *mp_fld_rx_descriptor_reader_control_adr;
+	nt_field_t *mp_fld_rx_descriptor_reader_control_cnt;
+
+	nt_register_t *mp_reg_rx_descriptor_reader_data;
+	nt_field_t *mp_fld_rx_descriptor_reader_data_guest_physical_address;
+	nt_field_t *mp_fld_rx_descriptor_reader_data_host_id;
+	nt_field_t *mp_fld_rx_descriptor_reader_data_queue_size;
+	nt_field_t *mp_fld_rx_descriptor_reader_data_header;
+	nt_field_t *mp_fld_rx_descriptor_reader_data_packed;
+
+	nt_register_t *mp_reg_tx_descriptor_reader_control;
+	nt_field_t *mp_fld_tx_descriptor_reader_control_adr;
+	nt_field_t *mp_fld_tx_descriptor_reader_control_cnt;
+
+	nt_register_t *mp_reg_tx_descriptor_reader_data;
+	nt_field_t *mp_fld_tx_descriptor_reader_data_guest_physical_address;
+	nt_field_t *mp_fld_tx_descriptor_reader_data_host_id;
+	nt_field_t *mp_fld_tx_descriptor_reader_data_queue_size;
+	nt_field_t *mp_fld_tx_descriptor_reader_data_port;
+	nt_field_t *mp_fld_tx_descriptor_reader_data_header;
+	nt_field_t *mp_fld_tx_descriptor_reader_data_packed;
+
+	nt_register_t *mp_reg_tx_queue_property_control;
+	nt_field_t *mp_fld_tx_queue_property_control_adr;
+	nt_field_t *mp_fld_tx_queue_property_control_cnt;
+
+	nt_register_t *mp_reg_tx_queue_property_data;
+	nt_field_t *mp_fld_tx_queue_property_data_v_port;
+
+	nt_register_t *mp_reg_tx_queue_qos_control;
+	nt_field_t *mp_reg_tx_queue_qos_control_adr;
+	nt_field_t *mp_reg_tx_queue_qos_control_cnt;
+
+	nt_register_t *mp_reg_tx_queue_qos_data;
+	nt_field_t *mp_reg_tx_queue_qos_data_en;
+	nt_field_t *mp_reg_tx_queue_qos_data_ir;
+	nt_field_t *mp_reg_tx_queue_qos_data_bs;
+
+	nt_register_t *mp_reg_tx_queue_qos_rate;
+	nt_field_t *mp_reg_tx_queue_qos_rate_mul;
+	nt_field_t *mp_reg_tx_queue_qos_rate_div;
+
+	struct nthw_dbs_rx_am_data_s m_rx_am_shadow[NT_DBS_RX_QUEUES_MAX];
+	struct nthw_dbs_rx_uw_data_s m_rx_uw_shadow[NT_DBS_RX_QUEUES_MAX];
+	struct nthw_dbs_rx_dr_data_s m_rx_dr_shadow[NT_DBS_RX_QUEUES_MAX];
+
+	struct nthw_dbs_tx_am_data_s m_tx_am_shadow[NT_DBS_TX_QUEUES_MAX];
+	struct nthw_dbs_tx_uw_data_s m_tx_uw_shadow[NT_DBS_TX_QUEUES_MAX];
+	struct nthw_dbs_tx_dr_data_s m_tx_dr_shadow[NT_DBS_TX_QUEUES_MAX];
+	struct nthw_dbs_tx_qp_data_s m_tx_qp_shadow[NT_DBS_TX_QUEUES_MAX];
+	struct nthw_dbs_tx_qos_data_s m_tx_qos_shadow[NT_DBS_TX_QUEUES_MAX];
+};
+
+typedef struct nthw_dbs_s nthw_dbs_t;
+
+nthw_dbs_t *nthw_dbs_new(void);
+void nthw_dbs_delete(nthw_dbs_t *p);
+int dbs_init(nthw_dbs_t *p, nt_fpga_t *p_fpga, int n_instance);
+void dbs_reset(nthw_dbs_t *p);
+
+int dbs_reset_rx_control(nthw_dbs_t *p);
+int dbs_reset_tx_control(nthw_dbs_t *p);
+int set_rx_control(nthw_dbs_t *p, uint32_t last_queue,
+		   uint32_t avail_monitor_enable, uint32_t avail_monitor_speed,
+		   uint32_t used_write_enable, uint32_t used_write_speed,
+		   uint32_t rx_queue_enable);
+int nthw_dbs_get_rx_control(nthw_dbs_t *p, uint32_t *last_queue,
+			 uint32_t *avail_monitor_enable,
+			 uint32_t *avail_monitor_speed, uint32_t *used_write_enable,
+			 uint32_t *used_write_speed, uint32_t *rx_queue_enable);
+int set_tx_control(nthw_dbs_t *p, uint32_t last_queue,
+		   uint32_t avail_monitor_enable, uint32_t avail_monitor_speed,
+		   uint32_t used_write_enable, uint32_t used_write_speed,
+		   uint32_t tx_queue_enable);
+int nthw_dbs_get_tx_control(nthw_dbs_t *p, uint32_t *last_queue,
+			 uint32_t *avail_monitor_enable,
+			 uint32_t *avail_monitor_speed, uint32_t *used_write_enable,
+			 uint32_t *used_write_speed, uint32_t *tx_queue_enable);
+int set_rx_init(nthw_dbs_t *p, uint32_t start_idx, uint32_t start_ptr,
+		uint32_t init, uint32_t queue);
+int get_rx_init(nthw_dbs_t *p, uint32_t *init, uint32_t *queue, uint32_t *busy);
+int set_tx_init(nthw_dbs_t *p, uint32_t start_idx, uint32_t start_ptr,
+		uint32_t init, uint32_t queue);
+int get_tx_init(nthw_dbs_t *p, uint32_t *init, uint32_t *queue, uint32_t *busy);
+int set_rx_idle(nthw_dbs_t *p, uint32_t idle, uint32_t queue);
+int get_rx_idle(nthw_dbs_t *p, uint32_t *idle, uint32_t *queue, uint32_t *busy);
+int set_tx_idle(nthw_dbs_t *p, uint32_t idle, uint32_t queue);
+int get_tx_idle(nthw_dbs_t *p, uint32_t *idle, uint32_t *queue, uint32_t *busy);
+int set_rx_ptr_queue(nthw_dbs_t *p, uint32_t queue);
+int get_rx_ptr(nthw_dbs_t *p, uint32_t *ptr, uint32_t *queue, uint32_t *valid);
+int set_tx_ptr_queue(nthw_dbs_t *p, uint32_t queue);
+int get_tx_ptr(nthw_dbs_t *p, uint32_t *ptr, uint32_t *queue, uint32_t *valid);
+int set_rx_am_data(nthw_dbs_t *p, uint32_t index, uint64_t guest_physical_address,
+		   uint32_t enable, uint32_t host_id, uint32_t packed,
+		   uint32_t int_enable);
+int set_tx_am_data(nthw_dbs_t *p, uint32_t index, uint64_t guest_physical_address,
+		   uint32_t enable, uint32_t host_id, uint32_t packed,
+		   uint32_t int_enable);
+int set_rx_uw_data(nthw_dbs_t *p, uint32_t index, uint64_t guest_physical_address,
+		   uint32_t host_id, uint32_t queue_size, uint32_t packed,
+		   uint32_t int_enable, uint32_t vec, uint32_t istk);
+int set_tx_uw_data(nthw_dbs_t *p, uint32_t index, uint64_t guest_physical_address,
+		   uint32_t host_id, uint32_t queue_size, uint32_t packed,
+		   uint32_t int_enable, uint32_t vec, uint32_t istk,
+		   uint32_t in_order);
+int set_rx_dr_data(nthw_dbs_t *p, uint32_t index, uint64_t guest_physical_address,
+		   uint32_t host_id, uint32_t queue_size, uint32_t header,
+		   uint32_t packed);
+int set_tx_dr_data(nthw_dbs_t *p, uint32_t index, uint64_t guest_physical_address,
+		   uint32_t host_id, uint32_t queue_size, uint32_t port,
+		   uint32_t header, uint32_t packed);
+int nthw_dbs_set_tx_qp_data(nthw_dbs_t *p, uint32_t index, uint32_t virtual_port);
+int set_tx_qos_data(nthw_dbs_t *p, uint32_t index, uint32_t enable, uint32_t ir,
+		    uint32_t bs);
+int set_tx_qos_rate(nthw_dbs_t *p, uint32_t mul, uint32_t div);
+
+#endif /* NTHW_DBS_H_ */
diff --git a/drivers/net/ntnic/nthw/nthw_drv.h b/drivers/net/ntnic/nthw/nthw_drv.h
new file mode 100644
index 0000000000..7fdd9bf0e2
--- /dev/null
+++ b/drivers/net/ntnic/nthw/nthw_drv.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_DRV_H__
+#define __NTHW_DRV_H__
+
+#include "nthw_profile.h"
+
+typedef enum nt_meta_port_type_e {
+	PORT_TYPE_PHYSICAL,
+	PORT_TYPE_VIRTUAL,
+	PORT_TYPE_OVERRIDE,
+} nt_meta_port_type_t;
+
+#include "nthw_helper.h"
+#include "nthw_platform_drv.h"
+#include "nthw_fpga_model.h"
+#include "nthw_stat.h"
+#include "nthw_dbs.h"
+#include "nthw_epp.h"
+#include "nthw_core.h"
+
+typedef struct nthwhw_info_s {
+	/* From FW */
+	int hw_id;
+	int hw_id_emulated;
+	char hw_plat_id_str[32];
+
+	struct vpd_info_s {
+		int mn_mac_addr_count;
+		uint64_t mn_mac_addr_value;
+		uint8_t ma_mac_addr_octets[6];
+	} vpd_info;
+} nthw_hw_info_t;
+
+typedef struct fpga_info_s {
+	uint64_t n_fpga_ident;
+
+	int n_fpga_type_id;
+	int n_fpga_prod_id;
+	int n_fpga_ver_id;
+	int n_fpga_rev_id;
+
+	int n_fpga_build_time;
+
+	int n_fpga_debug_mode;
+
+	int n_nims;
+	int n_phy_ports;
+	int n_phy_quads;
+	int n_rx_ports;
+	int n_tx_ports;
+
+	enum fpga_info_profile profile;
+
+	struct nt_fpga_s *mp_fpga;
+
+	struct nthw_rac *mp_nthw_rac;
+	struct nthw_hif *mp_nthw_hif;
+	struct nthw_pcie3 *mp_nthw_pcie3;
+	struct nthw_tsm *mp_nthw_tsm;
+
+	nthw_dbs_t *mp_nthw_dbs;
+	nthw_epp_t *mp_nthw_epp;
+
+	uint8_t *bar0_addr; /* Needed for register read/write */
+	size_t bar0_size;
+
+	int adapter_no; /* Needed for nthw_rac DMA array indexing */
+	uint32_t pciident; /* Needed for nthw_rac DMA memzone_reserve */
+	int numa_node; /* Needed for nthw_rac DMA memzone_reserve */
+
+	char *mp_adapter_id_str; /* Pointer to string literal used in nthw log messages */
+
+	struct nthwhw_info_s nthw_hw_info;
+
+	nthw_adapter_id_t n_nthw_adapter_id;
+
+} fpga_info_t;
+
+#endif /* __NTHW_DRV_H__ */
diff --git a/drivers/net/ntnic/nthw/nthw_epp.c b/drivers/net/ntnic/nthw/nthw_epp.c
new file mode 100644
index 0000000000..fbe3993b25
--- /dev/null
+++ b/drivers/net/ntnic/nthw/nthw_epp.c
@@ -0,0 +1,335 @@
+/* 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_epp.h"
+
+#include <errno.h> /* ENOTSUP */
+
+nthw_epp_t *nthw_epp_new(void)
+{
+	nthw_epp_t *p = malloc(sizeof(nthw_epp_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_epp_t));
+	return p;
+}
+
+void nthw_epp_delete(nthw_epp_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_epp_t));
+		free(p);
+	}
+}
+
+int nthw_epp_present(nt_fpga_t *p_fpga, int n_instance)
+{
+	return nthw_epp_init(NULL, p_fpga, n_instance) == 0;
+}
+
+int nthw_epp_init(nthw_epp_t *p, nt_fpga_t *p_fpga, int n_instance)
+{
+	nt_module_t *mod = fpga_query_module(p_fpga, MOD_EPP, n_instance);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: EPP %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_epp = mod;
+
+	p->mn_epp_categories = fpga_get_product_param(p_fpga, NT_EPP_CATEGORIES, 0);
+
+	p->mp_reg_reciepe_memory_control =
+		module_get_register(p->mp_mod_epp, EPP_RCP_CTRL);
+	p->mp_fld_reciepe_memory_control_adr =
+		register_get_field(p->mp_reg_reciepe_memory_control, EPP_RCP_CTRL_ADR);
+	p->mp_fld_reciepe_memory_control_cnt =
+		register_get_field(p->mp_reg_reciepe_memory_control, EPP_RCP_CTRL_CNT);
+
+	p->mp_reg_reciepe_memory_data =
+		module_get_register(p->mp_mod_epp, EPP_RCP_DATA);
+	p->mp_fld_reciepe_memory_data_tx_mtu_epp_enable =
+		register_get_field(p->mp_reg_reciepe_memory_data, EPP_RCP_DATA_TX_MTU_EPP_EN);
+	p->mp_fld_reciepe_memory_data_queue_mtu_epp_enable =
+		register_get_field(p->mp_reg_reciepe_memory_data, EPP_RCP_DATA_QUEUE_MTU_EPP_EN);
+	p->mp_fld_reciepe_memory_data_size_adjust_tx_port =
+		register_get_field(p->mp_reg_reciepe_memory_data, EPP_RCP_DATA_SIZE_ADJUST_TXP);
+	p->mp_fld_reciepe_memory_data_size_adjust_virtual_port =
+		register_get_field(p->mp_reg_reciepe_memory_data, EPP_RCP_DATA_SIZE_ADJUST_VPORT);
+	p->mp_fld_reciepe_memory_data_fixed18b_l2_mtu =
+		register_get_field(p->mp_reg_reciepe_memory_data, EPP_RCP_DATA_FIXED_18B_L2_MTU);
+	p->mp_fld_reciepe_memory_data_txp_qos_epp_enable =
+		register_get_field(p->mp_reg_reciepe_memory_data, EPP_RCP_DATA_TX_QOS_EPP_EN);
+	p->mp_fld_reciepe_memory_data_queue_qos_epp_enable =
+		register_get_field(p->mp_reg_reciepe_memory_data, EPP_RCP_DATA_QUEUE_QOS_EPP_EN);
+
+	p->mp_reg_txp_port_mtu_control =
+		module_get_register(p->mp_mod_epp, EPP_TXP_MTU_CTRL);
+	p->mp_fld_txp_port_mtu_control_adr =
+		register_get_field(p->mp_reg_txp_port_mtu_control, EPP_TXP_MTU_CTRL_ADR);
+	p->mp_fld_txp_port_mtu_control_cnt =
+		register_get_field(p->mp_reg_txp_port_mtu_control, EPP_TXP_MTU_CTRL_CNT);
+
+	p->mp_reg_txp_port_mtu_data =
+		module_get_register(p->mp_mod_epp, EPP_TXP_MTU_DATA);
+	p->mp_fld_txp_port_mtu_data_max_mtu =
+		register_get_field(p->mp_reg_txp_port_mtu_data, EPP_TXP_MTU_DATA_MAX_MTU);
+
+	p->mp_reg_queue_mtu_control =
+		module_get_register(p->mp_mod_epp, EPP_QUEUE_MTU_CTRL);
+	p->mp_fld_queue_mtu_control_adr =
+		register_get_field(p->mp_reg_queue_mtu_control, EPP_QUEUE_MTU_CTRL_ADR);
+	p->mp_fld_queue_mtu_control_cnt =
+		register_get_field(p->mp_reg_queue_mtu_control, EPP_QUEUE_MTU_CTRL_CNT);
+
+	p->mp_reg_queue_mtu_data =
+		module_get_register(p->mp_mod_epp, EPP_QUEUE_MTU_DATA);
+	p->mp_fld_queue_mtu_data_max_mtu =
+		register_get_field(p->mp_reg_queue_mtu_data, EPP_QUEUE_MTU_DATA_MAX_MTU);
+
+	p->mp_reg_txp_qos_control =
+		module_get_register(p->mp_mod_epp, EPP_TXP_QOS_CTRL);
+	p->mp_fld_txp_qos_control_adr =
+		register_get_field(p->mp_reg_txp_qos_control, EPP_TXP_QOS_CTRL_ADR);
+	p->mp_fld_txp_qos_control_cnt =
+		register_get_field(p->mp_reg_txp_qos_control, EPP_TXP_QOS_CTRL_CNT);
+
+	p->mp_reg_txp_qos_data = module_get_register(p->mp_mod_epp, EPP_TXP_QOS_DATA);
+	p->mp_fld_txp_qos_data_enable =
+		register_get_field(p->mp_reg_txp_qos_data, EPP_TXP_QOS_DATA_EN);
+	p->mp_fld_txp_qos_data_information_rate =
+		register_get_field(p->mp_reg_txp_qos_data, EPP_TXP_QOS_DATA_IR);
+	p->mp_fld_txp_qos_data_information_rate_fractional =
+		register_get_field(p->mp_reg_txp_qos_data, EPP_TXP_QOS_DATA_IR_FRACTION);
+	p->mp_fld_txp_qos_data_burst_size =
+		register_get_field(p->mp_reg_txp_qos_data, EPP_TXP_QOS_DATA_BS);
+
+	p->mp_reg_vport_qos_control =
+		module_get_register(p->mp_mod_epp, EPP_VPORT_QOS_CTRL);
+	p->mp_fld_vport_qos_control_adr =
+		register_get_field(p->mp_reg_vport_qos_control, EPP_VPORT_QOS_CTRL_ADR);
+	p->mp_fld_vport_qos_control_cnt =
+		register_get_field(p->mp_reg_vport_qos_control, EPP_VPORT_QOS_CTRL_CNT);
+
+	p->mp_reg_vport_qos_data =
+		module_get_register(p->mp_mod_epp, EPP_VPORT_QOS_DATA);
+	p->mp_fld_vport_qos_data_enable =
+		register_get_field(p->mp_reg_vport_qos_data, EPP_VPORT_QOS_DATA_EN);
+	p->mp_fld_vport_qos_data_information_rate =
+		register_get_field(p->mp_reg_vport_qos_data, EPP_VPORT_QOS_DATA_IR);
+	p->mp_fld_vport_qos_data_information_rate_fractional =
+		register_get_field(p->mp_reg_vport_qos_data, EPP_VPORT_QOS_DATA_IR_FRACTION);
+	p->mp_fld_vport_qos_data_burst_size =
+		register_get_field(p->mp_reg_vport_qos_data, EPP_VPORT_QOS_DATA_BS);
+
+	p->mp_reg_queue_vport_control =
+		module_get_register(p->mp_mod_epp, EPP_QUEUE_VPORT_CTRL);
+	p->mp_fld_queue_vport_control_adr =
+		register_get_field(p->mp_reg_queue_vport_control, EPP_QUEUE_VPORT_CTRL_ADR);
+	p->mp_fld_queue_vport_control_cnt =
+		register_get_field(p->mp_reg_queue_vport_control, EPP_QUEUE_VPORT_CTRL_CNT);
+
+	p->mp_reg_queue_vport_data =
+		module_get_register(p->mp_mod_epp, EPP_QUEUE_VPORT_DATA);
+	p->mp_fld_queue_vport_data_vport =
+		register_get_field(p->mp_reg_queue_vport_data, EPP_QUEUE_VPORT_DATA_VPORT);
+
+	return 0;
+}
+
+int nthw_epp_setup(nthw_epp_t *p)
+{
+	if (p == NULL)
+		return 0;
+
+	/* Set recieps for 2 first records */
+	field_set_val32(p->mp_fld_reciepe_memory_control_cnt, 1);
+
+	/* Zero all categories */
+	for (int i = 0; i < p->mn_epp_categories; ++i) {
+		field_set_val32(p->mp_fld_reciepe_memory_control_adr, i);
+		register_flush(p->mp_reg_reciepe_memory_control, 1);
+
+		field_set_val32(p->mp_fld_reciepe_memory_data_tx_mtu_epp_enable, 0);
+		field_set_val32(p->mp_fld_reciepe_memory_data_queue_mtu_epp_enable, 0);
+		field_set_val32(p->mp_fld_reciepe_memory_data_size_adjust_tx_port, 0);
+		field_set_val32(p->mp_fld_reciepe_memory_data_size_adjust_virtual_port,
+			       0);
+		field_set_val32(p->mp_fld_reciepe_memory_data_fixed18b_l2_mtu, 0);
+		field_set_val32(p->mp_fld_reciepe_memory_data_txp_qos_epp_enable, 0);
+		field_set_val32(p->mp_fld_reciepe_memory_data_queue_qos_epp_enable, 0);
+		register_flush(p->mp_reg_reciepe_memory_data, 1);
+	}
+
+	for (int i = 0; i < NRECIPE; ++i) {
+		field_set_val32(p->mp_fld_reciepe_memory_control_adr, i);
+		register_flush(p->mp_reg_reciepe_memory_control, 1);
+
+		field_set_val32(p->mp_fld_reciepe_memory_data_tx_mtu_epp_enable, 1);
+		field_set_val32(p->mp_fld_reciepe_memory_data_queue_mtu_epp_enable, 1);
+		field_set_val32(p->mp_fld_reciepe_memory_data_size_adjust_tx_port,
+			       rcp_data_size_adjust_txp[i]);
+		field_set_val32(p->mp_fld_reciepe_memory_data_size_adjust_virtual_port,
+			       rcp_data_size_adjust_vport[i]);
+		field_set_val32(p->mp_fld_reciepe_memory_data_fixed18b_l2_mtu, 1);
+		field_set_val32(p->mp_fld_reciepe_memory_data_txp_qos_epp_enable, 1);
+		field_set_val32(p->mp_fld_reciepe_memory_data_queue_qos_epp_enable, 1);
+		register_flush(p->mp_reg_reciepe_memory_data, 1);
+	}
+	/* phy mtu setup */
+	field_set_val32(p->mp_fld_txp_port_mtu_control_cnt, 1);
+	for (int i = 0; i < 2; ++i) {
+		field_set_val32(p->mp_fld_txp_port_mtu_control_adr, i);
+		register_flush(p->mp_reg_txp_port_mtu_control, 1);
+
+		field_set_val32(p->mp_fld_txp_port_mtu_data_max_mtu, MTUINITVAL);
+		register_flush(p->mp_reg_txp_port_mtu_data, 1);
+	}
+	/* phy QoS setup */
+	field_set_val32(p->mp_fld_txp_qos_control_cnt, 1);
+	for (int i = 0; i < 2; ++i) {
+		field_set_val32(p->mp_fld_txp_qos_control_adr, i);
+		register_flush(p->mp_reg_txp_qos_control, 1);
+
+		field_set_val32(p->mp_fld_txp_qos_data_enable, 0);
+		register_flush(p->mp_reg_txp_qos_data, 1);
+	}
+
+	/* virt mtu setup */
+	field_set_val32(p->mp_fld_queue_mtu_control_cnt, 1);
+	for (int i = 0; i < 128; ++i) {
+		field_set_val32(p->mp_fld_queue_mtu_control_adr, i);
+		register_flush(p->mp_reg_queue_mtu_control, 1);
+
+		field_set_val32(p->mp_fld_queue_mtu_data_max_mtu, MTUINITVAL);
+		register_flush(p->mp_reg_queue_mtu_data, 1);
+	}
+
+	/* virt QoS setup */
+	field_set_val32(p->mp_fld_vport_qos_control_cnt, 1);
+	for (int i = 0; i < 128; ++i) {
+		field_set_val32(p->mp_fld_vport_qos_control_adr, i);
+		register_flush(p->mp_reg_vport_qos_control, 1);
+
+		field_set_val32(p->mp_fld_vport_qos_data_enable, 0);
+		register_flush(p->mp_reg_vport_qos_data, 1);
+	}
+
+	return 0;
+}
+
+/*
+ * Set the MTU registers in context with the current setMTU request.
+ */
+int nthw_epp_set_mtu(nthw_epp_t *p, uint32_t port, uint32_t max_mtu,
+		   nt_meta_port_type_t port_type)
+{
+	if (p == NULL)
+		return 0;
+
+	if (port_type == PORT_TYPE_PHYSICAL) {
+		/* Set the TXP Mtu control register */
+		field_set_val32(p->mp_fld_txp_port_mtu_control_adr, port);
+		field_set_val32(p->mp_fld_txp_port_mtu_control_cnt, 1);
+		register_flush(p->mp_reg_txp_port_mtu_control, 1);
+
+		/* Set the TXP Mtu control register */
+		field_set_val32(p->mp_fld_txp_port_mtu_data_max_mtu, max_mtu);
+		register_flush(p->mp_reg_txp_port_mtu_data, 1);
+	} else if (port_type == PORT_TYPE_VIRTUAL) {
+		/* Set the TXP Mtu control register */
+		field_set_val32(p->mp_fld_queue_mtu_control_adr, port);
+		field_set_val32(p->mp_fld_queue_mtu_control_cnt, 1);
+		register_flush(p->mp_reg_queue_mtu_control, 1);
+
+		/* Set the TXP Mtu control register */
+		field_set_val32(p->mp_fld_queue_mtu_data_max_mtu, max_mtu);
+		register_flush(p->mp_reg_queue_mtu_data, 1);
+	} else {
+		NT_LOG(DBG, NTHW, "NthwEpp::%s - port_type unsupported",
+		       __func__);
+		register_reset(p->mp_reg_queue_mtu_control);
+		register_flush(p->mp_reg_queue_mtu_control, 1);
+		register_reset(p->mp_reg_queue_mtu_data);
+		register_flush(p->mp_reg_queue_mtu_data, 1);
+		register_reset(p->mp_reg_txp_port_mtu_control);
+		register_flush(p->mp_reg_txp_port_mtu_control, 1);
+		register_reset(p->mp_reg_txp_port_mtu_data);
+		register_flush(p->mp_reg_txp_port_mtu_data, 1);
+
+		return -ENOTSUP;
+	}
+
+	return 0;
+}
+
+int nthw_epp_set_txp_qos(nthw_epp_t *p, uint32_t port, uint32_t information_rate,
+		      uint32_t information_rate_fractional, uint32_t burst_size)
+{
+	if (p == NULL)
+		return 0;
+
+	field_set_val32(p->mp_fld_txp_qos_control_adr, port);
+	field_set_val32(p->mp_fld_txp_qos_control_cnt, 1);
+	register_flush(p->mp_reg_txp_qos_control, 1);
+
+	uint32_t enable = ((information_rate | information_rate_fractional |
+			    burst_size) != 0);
+	field_set_val32(p->mp_fld_txp_qos_data_enable, enable);
+	field_set_val32(p->mp_fld_txp_qos_data_information_rate, information_rate);
+	field_set_val32(p->mp_fld_txp_qos_data_information_rate_fractional,
+		       information_rate_fractional);
+	field_set_val32(p->mp_fld_txp_qos_data_burst_size, burst_size);
+	register_flush(p->mp_reg_txp_qos_data, 1);
+
+	return 0;
+}
+
+int nthw_epp_set_vport_qos(nthw_epp_t *p, uint32_t port, uint32_t information_rate,
+			uint32_t information_rate_fractional, uint32_t burst_size)
+{
+	if (p == NULL)
+		return 0;
+
+	field_set_val32(p->mp_fld_vport_qos_control_adr, port);
+	field_set_val32(p->mp_fld_vport_qos_control_cnt, 1);
+	register_flush(p->mp_reg_vport_qos_control, 1);
+
+	uint32_t enable = ((information_rate | information_rate_fractional |
+			    burst_size) != 0);
+	field_set_val32(p->mp_fld_vport_qos_data_enable, enable);
+	field_set_val32(p->mp_fld_vport_qos_data_information_rate, information_rate);
+	field_set_val32(p->mp_fld_vport_qos_data_information_rate_fractional,
+		       information_rate_fractional);
+	field_set_val32(p->mp_fld_vport_qos_data_burst_size, burst_size);
+	register_flush(p->mp_reg_vport_qos_data, 1);
+
+	return 0;
+}
+
+int nthw_epp_set_queue_to_vport(nthw_epp_t *p, uint32_t qid, uint32_t vport)
+{
+	if (p == NULL)
+		return 0;
+
+	field_set_val32(p->mp_fld_queue_vport_control_adr, qid);
+	field_set_val32(p->mp_fld_queue_vport_control_cnt, 1);
+	register_flush(p->mp_reg_queue_vport_control, 1);
+
+	field_set_val32(p->mp_fld_queue_vport_data_vport, vport);
+	register_flush(p->mp_reg_queue_vport_data, 1);
+	return 0;
+}
diff --git a/drivers/net/ntnic/nthw/nthw_epp.h b/drivers/net/ntnic/nthw/nthw_epp.h
new file mode 100644
index 0000000000..b404c9b61a
--- /dev/null
+++ b/drivers/net/ntnic/nthw/nthw_epp.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NTHW_EPP_HPP_
+#define NTHW_EPP_HPP_
+
+/* VXLAN adds extra 50 bytes */
+#define VXLANDATASIZEADJUST 50
+#define VXLANDATASIZEADJUSTIPV6 70
+#define MTUINITVAL 1500
+#define NRECIPE 3
+
+/* List of size adjust values to put in the recipe memory data register at startup */
+static const int rcp_data_size_adjust_txp[NRECIPE] = { 0, VXLANDATASIZEADJUST,
+						   VXLANDATASIZEADJUSTIPV6
+						 };
+static const int rcp_data_size_adjust_vport[NRECIPE] = { 0, VXLANDATASIZEADJUST,
+						     VXLANDATASIZEADJUSTIPV6
+						   };
+
+struct nthw_epp_s {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_epp;
+	int mn_instance;
+	int mn_epp_categories;
+
+	nt_register_t *mp_reg_reciepe_memory_control;
+	nt_field_t *mp_fld_reciepe_memory_control_adr;
+	nt_field_t *mp_fld_reciepe_memory_control_cnt;
+
+	nt_register_t *mp_reg_reciepe_memory_data;
+	nt_field_t *mp_fld_reciepe_memory_data_tx_mtu_epp_enable;
+	nt_field_t *mp_fld_reciepe_memory_data_queue_mtu_epp_enable;
+	nt_field_t *mp_fld_reciepe_memory_data_size_adjust_tx_port;
+	nt_field_t *mp_fld_reciepe_memory_data_size_adjust_virtual_port;
+	nt_field_t *mp_fld_reciepe_memory_data_fixed18b_l2_mtu;
+	nt_field_t *mp_fld_reciepe_memory_data_txp_qos_epp_enable;
+	nt_field_t *mp_fld_reciepe_memory_data_queue_qos_epp_enable;
+
+	nt_register_t *mp_reg_txp_port_mtu_control;
+	nt_field_t *mp_fld_txp_port_mtu_control_adr;
+	nt_field_t *mp_fld_txp_port_mtu_control_cnt;
+
+	nt_register_t *mp_reg_txp_port_mtu_data;
+	nt_field_t *mp_fld_txp_port_mtu_data_max_mtu;
+
+	nt_register_t *mp_reg_queue_mtu_control;
+	nt_field_t *mp_fld_queue_mtu_control_adr;
+	nt_field_t *mp_fld_queue_mtu_control_cnt;
+
+	nt_register_t *mp_reg_queue_mtu_data;
+	nt_field_t *mp_fld_queue_mtu_data_max_mtu;
+
+	nt_register_t *mp_reg_txp_qos_control;
+	nt_field_t *mp_fld_txp_qos_control_adr;
+	nt_field_t *mp_fld_txp_qos_control_cnt;
+
+	nt_register_t *mp_reg_txp_qos_data;
+	nt_field_t *mp_fld_txp_qos_data_enable;
+	nt_field_t *mp_fld_txp_qos_data_information_rate;
+	nt_field_t *mp_fld_txp_qos_data_information_rate_fractional;
+	nt_field_t *mp_fld_txp_qos_data_burst_size;
+
+	nt_register_t *mp_reg_vport_qos_control;
+	nt_field_t *mp_fld_vport_qos_control_adr;
+	nt_field_t *mp_fld_vport_qos_control_cnt;
+
+	nt_register_t *mp_reg_vport_qos_data;
+	nt_field_t *mp_fld_vport_qos_data_enable;
+	nt_field_t *mp_fld_vport_qos_data_information_rate;
+	nt_field_t *mp_fld_vport_qos_data_information_rate_fractional;
+	nt_field_t *mp_fld_vport_qos_data_burst_size;
+
+	nt_register_t *mp_reg_queue_vport_control;
+	nt_field_t *mp_fld_queue_vport_control_adr;
+	nt_field_t *mp_fld_queue_vport_control_cnt;
+
+	nt_register_t *mp_reg_queue_vport_data;
+	nt_field_t *mp_fld_queue_vport_data_vport;
+};
+
+typedef struct nthw_epp_s nthw_epp_t;
+
+nthw_epp_t *nthw_epp_new(void);
+void nthw_epp_delete(nthw_epp_t *p);
+
+int nthw_epp_present(nt_fpga_t *p_fpga, int n_instance);
+int nthw_epp_init(nthw_epp_t *p, nt_fpga_t *p_fpga, int n_instance);
+int nthw_epp_setup(nthw_epp_t *p);
+int nthw_epp_set_mtu(nthw_epp_t *p, uint32_t port, uint32_t max_mtu,
+		   nt_meta_port_type_t port_type);
+int nthw_epp_set_txp_qos(nthw_epp_t *p, uint32_t port, uint32_t information_rate,
+		      uint32_t information_rate_fractional, uint32_t burst_size);
+int nthw_epp_set_vport_qos(nthw_epp_t *p, uint32_t port, uint32_t information_rate,
+			uint32_t information_rate_fractional, uint32_t burst_size);
+int nthw_epp_set_queue_to_vport(nthw_epp_t *p, uint32_t qid, uint32_t vport);
+
+#endif /* NTHW_EPP_HPP_ */
diff --git a/drivers/net/ntnic/nthw/nthw_fpga_model.c b/drivers/net/ntnic/nthw/nthw_fpga_model.c
new file mode 100644
index 0000000000..fca13e0f31
--- /dev/null
+++ b/drivers/net/ntnic/nthw/nthw_fpga_model.c
@@ -0,0 +1,1677 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <time.h> /* ctime */
+
+#include "nthw_drv.h" /* fpga_info_s */
+#include "nthw_register.h"
+#include "nthw_fpga_model.h"
+#include "nthw_rac.h"
+#include "ntlog.h"
+
+#include "nthw_fpga_instances.h"
+#include "nthw_fpga_modules_defs.h"
+
+/* Generated code */
+nt_fpga_prod_init_t *nthw_fpga_instances[] = { &nthw_fpga_9563_055_024_0000,
+					       NULL
+					     };
+
+static const struct {
+	const int a;
+	const char *b;
+} sa_nthw_fpga_mod_map[] = {
+	{ MOD_CAT, "CAT" },
+	{ MOD_CB, "CB" },
+	{ MOD_CCIP, "CCIP" },
+	{ MOD_CFP4_CTRL_GBOX, "CFP4_CTRL_GBOX" },
+	{ MOD_COR, "COR" },
+	{ MOD_CPY, "CPY" },
+	{ MOD_CSU, "CSU" },
+	{ MOD_DBS, "DBS" },
+	{ MOD_DDP, "DDP" },
+	{ MOD_EPP, "EPP" },
+	{ MOD_EQM, "EQM" },
+	{ MOD_FHM, "FHM" },
+	{ MOD_FLM, "FLM" },
+	{ MOD_GFG, "GFG" },
+	{ MOD_GMF, "GMF" },
+	{ MOD_GPIO_PHY, "GPIO_PHY" },
+	{ MOD_GPIO_PHY_PORTS, "GPIO_PHY_PORTS" },
+	{ MOD_GPIO_SFPP, "GPIO_SFPP" },
+	{ MOD_HFU, "HFU" },
+	{ MOD_HIF, "HIF" },
+	{ MOD_HSH, "HSH" },
+	{ MOD_HST, "HST" },
+	{ MOD_ICORE_10G, "ICORE_10G" },
+	{ MOD_IFR, "IFR" },
+	{ MOD_IIC, "IIC" },
+	{ MOD_INS, "INS" },
+	{ MOD_IOA, "IOA" },
+	{ MOD_IPF, "IPF" },
+	{ MOD_KM, "KM" },
+	{ MOD_LAO, "LAO" },
+	{ MOD_MAC, "MAC" },
+	{ MOD_MAC10, "MAC10" },
+	{ MOD_MAC100, "MAC100" },
+	{ MOD_MAC10G, "MAC10G" },
+	{ MOD_MAC1G, "MAC1G" },
+	{ MOD_MAC_PCS, "MAC_PCS" },
+	{ MOD_MAC_PCS_XXV, "MAC_PCS_XXV" },
+	{ MOD_MAC_RX, "MAC_RX" },
+	{ MOD_MAC_TFG, "MAC_TFG" },
+	{ MOD_MAC_TX, "MAC_TX" },
+	{ MOD_MCU, "MCU" },
+	{ MOD_MDG, "MDG" },
+	{ MOD_MSK, "MSK" },
+	{ MOD_NIF, "NIF" },
+	{ MOD_PCIE3, "PCIE3" },
+	{ MOD_PCI_RD_TG, "PCI_RD_TG" },
+	{ MOD_PCI_TA, "PCI_TA" },
+	{ MOD_PCI_WR_TG, "PCI_WR_TG" },
+	{ MOD_PCM_NT100A01_01, "PCM_NT100A01_01" },
+	{ MOD_PCM_NT50B01_01, "PCM_NT50B01_01" },
+	{ MOD_PCS, "PCS" },
+	{ MOD_PCS100, "PCS100" },
+	{ MOD_PDB, "PDB" },
+	{ MOD_PDI, "PDI" },
+	{ MOD_PHY10G, "PHY10G" },
+	{ MOD_PHY3S10G, "PHY3S10G" },
+	{ MOD_PM, "PM" },
+	{ MOD_PRM_NT100A01_01, "PRM_NT100A01_01" },
+	{ MOD_PRM_NT50B01_01, "PRM_NT50B01_01" },
+	{ MOD_PTP1588, "PTP1588" },
+	{ MOD_QM, "QM" },
+	{ MOD_QSL, "QSL" },
+	{ MOD_QSPI, "QSPI" },
+	{ MOD_R2DRP, "R2DRP" },
+	{ MOD_RAC, "RAC" },
+	{ MOD_RBH, "RBH" },
+	{ MOD_RFD, "RFD" },
+	{ MOD_RMC, "RMC" },
+	{ MOD_RNTC, "RNTC" },
+	{ MOD_ROA, "ROA" },
+	{ MOD_RPL, "RPL" },
+	{ MOD_RPP_LR, "RPP_LR" },
+	{ MOD_RST7000, "RST7000" },
+	{ MOD_RST7001, "RST7001" },
+	{ MOD_RST9500, "RST9500" },
+	{ MOD_RST9501, "RST9501" },
+	{ MOD_RST9502, "RST9502" },
+	{ MOD_RST9503, "RST9503" },
+	{ MOD_RST9504, "RST9504" },
+	{ MOD_RST9505, "RST9505" },
+	{ MOD_RST9506, "RST9506" },
+	{ MOD_RST9507, "RST9507" },
+	{ MOD_RST9508, "RST9508" },
+	{ MOD_RST9509, "RST9509" },
+	{ MOD_RST9510, "RST9510" },
+	{ MOD_RST9512, "RST9512" },
+	{ MOD_RST9513, "RST9513" },
+	{ MOD_RST9515, "RST9515" },
+	{ MOD_RST9516, "RST9516" },
+	{ MOD_RST9517, "RST9517" },
+	{ MOD_RST9519, "RST9519" },
+	{ MOD_RST9520, "RST9520" },
+	{ MOD_RST9521, "RST9521" },
+	{ MOD_RST9522, "RST9522" },
+	{ MOD_RST9523, "RST9523" },
+	{ MOD_RST9524, "RST9524" },
+	{ MOD_RST9525, "RST9525" },
+	{ MOD_RST9526, "RST9526" },
+	{ MOD_RST9527, "RST9527" },
+	{ MOD_RST9528, "RST9528" },
+	{ MOD_RST9529, "RST9529" },
+	{ MOD_RST9530, "RST9530" },
+	{ MOD_RST9531, "RST9531" },
+	{ MOD_RST9532, "RST9532" },
+	{ MOD_RST9533, "RST9533" },
+	{ MOD_RST9534, "RST9534" },
+	{ MOD_RST9535, "RST9535" },
+	{ MOD_RST9536, "RST9536" },
+	{ MOD_RST9537, "RST9537" },
+	{ MOD_RST9538, "RST9538" },
+	{ MOD_RST9539, "RST9539" },
+	{ MOD_RST9540, "RST9540" },
+	{ MOD_RST9541, "RST9541" },
+	{ MOD_RST9542, "RST9542" },
+	{ MOD_RST9543, "RST9543" },
+	{ MOD_RST9544, "RST9544" },
+	{ MOD_RST9545, "RST9545" },
+	{ MOD_RST9546, "RST9546" },
+	{ MOD_RST9547, "RST9547" },
+	{ MOD_RST9548, "RST9548" },
+	{ MOD_RST9549, "RST9549" },
+	{ MOD_RST9553, "RST9553" },
+	{ MOD_RST9555, "RST9555" },
+	{ MOD_RST9559, "RST9559" },
+	{ MOD_RST9563, "RST9563" },
+	{ MOD_RTD, "RTD" },
+	{ MOD_RTD_HMP, "RTD_HMP" },
+	{ MOD_RTX, "RTX" },
+	{ MOD_SDC, "SDC" },
+	{ MOD_SLC, "SLC" },
+	{ MOD_SLC_LR, "SLC_LR" },
+	{ MOD_SMM, "SMM" },
+	{ MOD_SMM_RX, "SMM_RX" },
+	{ MOD_SMM_TX, "SMM_TX" },
+	{ MOD_SPIM, "SPIM" },
+	{ MOD_SPIS, "SPIS" },
+	{ MOD_STA, "STA" },
+	{ MOD_TBH, "TBH" },
+	{ MOD_TEMPMON, "TEMPMON" },
+	{ MOD_TINT, "TINT" },
+	{ MOD_TMC, "TMC" },
+	{ MOD_TSM, "TSM" },
+	{ MOD_TX_CPY, "TX_CPY" },
+	{ MOD_TX_CSI, "TX_CSI" },
+	{ MOD_TX_CSO, "TX_CSO" },
+	{ MOD_TX_INS, "TX_INS" },
+	{ MOD_TX_RPL, "TX_RPL" },
+	{ 0L, NULL },
+};
+
+/* NOTE: this needs to be (manually) synced with enum */
+static const char *const a_bus_type[] = {
+	"ERR", /* BUS_TYPE_UNKNOWN, */
+	"BAR", /* BUS_TYPE_BAR, */
+	"PCI", /* BUS_TYPE_PCI, */
+	"CCIP", /* BUS_TYPE_CCIP, */
+	"RAB0", /* BUS_TYPE_RAB0, */
+	"RAB1", /* BUS_TYPE_RAB1, */
+	"RAB2", /* BUS_TYPE_RAB2, */
+	"NMB", /* BUS_TYPE_NMB, */
+	"NDM", /* BUS_TYPE_NDM, */
+};
+
+static const char *get_bus_name(int n_bus_type_id)
+{
+	if (n_bus_type_id >= 1 && n_bus_type_id <= (int)ARRAY_SIZE(a_bus_type))
+		return a_bus_type[n_bus_type_id];
+
+	else
+		return "ERR";
+}
+
+/*
+ * Module name lookup by id from array
+ * Uses naive linear search as performance is not an issue here...
+ */
+static const char *nthw_fpga_mod_id_to_str(uint64_t n_fpga_mod_id)
+{
+	int i;
+
+	for (i = 0; i <= (int)ARRAY_SIZE(sa_nthw_fpga_mod_map); i++) {
+		if ((uint64_t)sa_nthw_fpga_mod_map[i].a == n_fpga_mod_id)
+			break;
+	}
+	return (sa_nthw_fpga_mod_map[i].b ? sa_nthw_fpga_mod_map[i].b :
+		"unknown");
+}
+
+/*
+ * Force C linkage for xxx_addr_bases and xxx_module_versions
+ */
+static int read_data(struct fpga_info_s *p_fpga_info, int n_bus_type_id, uint32_t addr,
+		    uint32_t len, uint32_t *p_data)
+{
+	int rc = -1;
+
+	assert(p_fpga_info);
+	assert(p_data);
+
+	switch (n_bus_type_id) {
+	case BUS_TYPE_BAR:
+	case BUS_TYPE_PCI:
+		assert(len == 1);
+		nthw_rac_reg_read32(p_fpga_info, addr, p_data);
+		rc = 0;
+		break;
+	case BUS_TYPE_RAB0:
+		assert(p_fpga_info->mp_nthw_rac);
+		rc = nthw_rac_rab_read32(p_fpga_info->mp_nthw_rac, addr, 0, len,
+					p_data);
+		break;
+	case BUS_TYPE_RAB1:
+		assert(p_fpga_info->mp_nthw_rac);
+		rc = nthw_rac_rab_read32(p_fpga_info->mp_nthw_rac, addr, 1, len,
+					p_data);
+		break;
+	case BUS_TYPE_RAB2:
+		assert(p_fpga_info->mp_nthw_rac);
+		rc = nthw_rac_rab_read32(p_fpga_info->mp_nthw_rac, addr, 2, len,
+					p_data);
+		break;
+	default:
+		assert(false);
+		return -1;
+	}
+
+	return rc;
+}
+
+static int read_data_tsc(struct fpga_info_s *p_fpga_info, int n_bus_type_id,
+		       uint32_t addr, uint32_t len, uint32_t *p_data,
+		       uint64_t *p_tsc1, uint64_t *p_tsc2)
+{
+	int rc = -1;
+
+	(void)p_tsc1;
+	(void)p_tsc2;
+
+	rc = read_data(p_fpga_info, n_bus_type_id, addr, len, p_data);
+
+	return rc;
+}
+
+static int write_data(struct fpga_info_s *p_fpga_info, int n_bus_type_id,
+		     uint32_t addr, uint32_t len, const uint32_t *p_data)
+{
+	int rc = -1;
+
+	assert(p_fpga_info);
+	assert(p_data);
+
+	switch (n_bus_type_id) {
+	case BUS_TYPE_BAR:
+	case BUS_TYPE_PCI:
+		assert(len == 1);
+		nthw_rac_reg_write32(p_fpga_info, addr, *p_data);
+		rc = 0;
+		break;
+	case BUS_TYPE_RAB0:
+		assert(p_fpga_info->mp_nthw_rac);
+		rc = nthw_rac_rab_write32(p_fpga_info->mp_nthw_rac, addr, 0, len,
+					 p_data);
+		break;
+	case BUS_TYPE_RAB1:
+		assert(p_fpga_info->mp_nthw_rac);
+		rc = nthw_rac_rab_write32(p_fpga_info->mp_nthw_rac, addr, 1, len,
+					 p_data);
+		break;
+	case BUS_TYPE_RAB2:
+		assert(p_fpga_info->mp_nthw_rac);
+		rc = nthw_rac_rab_write32(p_fpga_info->mp_nthw_rac, addr, 2, len,
+					 p_data);
+		break;
+	default:
+		assert(false);
+		return -1;
+	}
+
+	return rc;
+}
+
+/*
+ * FpgaMgr
+ */
+nt_fpga_mgr_t *fpga_mgr_new(void)
+{
+	nt_fpga_mgr_t *p = malloc(sizeof(nt_fpga_mgr_t));
+	return p;
+}
+
+void fpga_mgr_delete(nt_fpga_mgr_t *p)
+{
+	memset(p, 0, sizeof(nt_fpga_mgr_t));
+	free(p);
+}
+
+void fpga_mgr_init(nt_fpga_mgr_t *p)
+{
+	size_t i;
+
+	/* Count fpga instance in array */
+	p->mpa_fpga_prod_init = nthw_fpga_instances;
+	for (i = 0; i < ARRAY_SIZE(nthw_fpga_instances); i++) {
+		if (p->mpa_fpga_prod_init[i] == NULL)
+			break;
+	}
+	p->mn_fpgas = (int)i;
+}
+
+nt_fpga_t *fpga_mgr_query_fpga(nt_fpga_mgr_t *p, uint64_t n_fpga_id,
+			     struct fpga_info_s *p_fpga_info)
+{
+	int i;
+
+	const int n_fpga_prod = FPGAID_TO_PRODUCTCODE(n_fpga_id);
+	const int n_fpga_ver = FPGAID_TO_VERSIONCODE(n_fpga_id);
+	const int n_fpga_rev = FPGAID_TO_REVISIONCODE(n_fpga_id);
+
+	for (i = 0; i < p->mn_fpgas; i++) {
+		nt_fpga_prod_init_t *p_init = p->mpa_fpga_prod_init[i];
+
+		if (p_init->fpga_product_id == n_fpga_prod &&
+				p_init->fpga_version == n_fpga_ver &&
+				p_init->fpga_revision == n_fpga_rev) {
+			{
+				nt_fpga_t *p_fpga = fpga_new();
+
+				fpga_init(p_fpga, p_init, p_fpga_info);
+				return p_fpga;
+			}
+		}
+	}
+
+	NT_LOG(ERR, NTHW,
+	       "FPGA Id 0x%" PRIX64 ": %04d: %d.%d: no match found\n", n_fpga_id,
+	       n_fpga_prod, n_fpga_ver, n_fpga_rev);
+
+	return NULL;
+}
+
+void fpga_mgr_show(nt_fpga_mgr_t *p, FILE *fh_out, int detail_level)
+{
+	int i;
+
+	fprintf(fh_out, "\n"); /* start of records */
+	for (i = 0; i < p->mn_fpgas; i++) {
+		nt_fpga_prod_init_t *p_init = p->mpa_fpga_prod_init[i];
+
+		if (detail_level == 0) {
+			fprintf(fh_out, "%04d-%02d-%02d\n",
+				p_init->fpga_product_id, p_init->fpga_version,
+				p_init->fpga_revision);
+		} else {
+			time_t fpga_build_time = p_init->fpga_build_time;
+
+			fprintf(fh_out, "%04d-%02d-%02d: 0x%08lX: %s\n",
+				p_init->fpga_product_id, p_init->fpga_version,
+				p_init->fpga_revision, fpga_build_time,
+				(fpga_build_time ? ctime(&fpga_build_time) :
+				 "NA\n"));
+		}
+	}
+	fprintf(fh_out, "\n"); /* end of records */
+	fflush(fh_out);
+}
+
+void fpga_mgr_log_dump(nt_fpga_mgr_t *p)
+{
+	int i;
+
+	NT_LOG(DBG, NTHW, "%s: fpgas=%d\n", __func__, p->mn_fpgas);
+	for (i = 0; i < p->mn_fpgas; i++) {
+		nt_fpga_prod_init_t *p_init _unused = p->mpa_fpga_prod_init[i];
+		NT_LOG(DBG, NTHW, "%s: fpga=%d/%d: %04d-%02d-%02d\n", __func__,
+		       i, p->mn_fpgas, p_init->fpga_product_id, p_init->fpga_version,
+		       p_init->fpga_revision);
+	}
+}
+
+/*
+ * Fpga
+ */
+nt_fpga_t *fpga_new(void)
+{
+	nt_fpga_t *p = malloc(sizeof(nt_fpga_t));
+
+	if (p)
+		memset(p, 0, sizeof(nt_fpga_t));
+	return p;
+}
+
+void fpga_delete(nt_fpga_t *p)
+{
+	memset(p, 0, sizeof(nt_fpga_t));
+	free(p);
+}
+
+void fpga_delete_all(nt_fpga_t *p)
+{
+	int i;
+
+	for (i = 0; i < p->mn_modules; i++) {
+		nt_module_t *p_mod = p->mpa_modules[i];
+
+		if (p_mod)
+			module_delete(p_mod);
+	}
+
+	fpga_delete(p);
+}
+
+void fpga_init(nt_fpga_t *p, nt_fpga_prod_init_t *fpga_prod_init,
+	       struct fpga_info_s *p_fpga_info)
+{
+	int i;
+
+	p->p_fpga_info = p_fpga_info;
+	p->mp_init = fpga_prod_init;
+
+	p->m_item_id = fpga_prod_init->fpga_item_id;
+	p->m_product_id = fpga_prod_init->fpga_product_id;
+	p->m_fpga_version = fpga_prod_init->fpga_version;
+	p->m_fpga_revision = fpga_prod_init->fpga_revision;
+	p->m_fpga_patch_no = fpga_prod_init->fpga_patch_no;
+	p->m_fpga_build_no = fpga_prod_init->fpga_build_no;
+	p->m_fpga_build_time = fpga_prod_init->fpga_build_time;
+
+	p->mn_params = fpga_prod_init->nb_prod_params;
+
+	if (p->mn_params) {
+		p->mpa_params = malloc(p->mn_params * sizeof(nt_param_t *));
+		if (p->mpa_params) {
+			memset(p->mpa_params, 0,
+			       (p->mn_params * sizeof(nt_param_t *)));
+			for (i = 0; i < p->mn_params; i++) {
+				nt_param_t *p_param = param_new();
+
+				param_init(p_param, p,
+					   &fpga_prod_init->product_params[i]);
+				p->mpa_params[i] = p_param;
+			}
+		}
+	}
+
+	p->mn_modules = fpga_prod_init->nb_modules;
+
+	if (p->mn_modules) {
+		p->mpa_modules =
+			malloc(fpga_prod_init->nb_modules * sizeof(nt_module_t *));
+		if (p->mpa_modules) {
+			memset(p->mpa_modules, 0,
+			       (p->mn_modules * sizeof(nt_module_t *)));
+			for (i = 0; i < p->mn_modules; i++) {
+				nt_module_t *p_mod = module_new();
+
+				module_init(p_mod, p, &fpga_prod_init->modules[i]);
+				p->mpa_modules[i] = p_mod;
+			}
+		}
+	}
+}
+
+void fpga_set_debug_mode(nt_fpga_t *p, int n_debug_mode)
+{
+	int i;
+
+	p->m_debug_mode = n_debug_mode;
+
+	for (i = 0; i < p->mn_modules; i++) {
+		nt_module_t *p_mod = p->mpa_modules[i];
+
+		if (p_mod)
+			module_set_debug_mode(p_mod, n_debug_mode);
+	}
+}
+
+nt_module_t *fpga_query_module(const nt_fpga_t *p, int id, int instance)
+{
+	int i;
+
+	for (i = 0; i < p->mn_modules; i++) {
+		nt_module_t *p_mod = p->mpa_modules[i];
+
+		if (p_mod->m_mod_id == id && p_mod->m_instance == instance)
+			return p_mod;
+	}
+	return NULL;
+}
+
+bool fpga_query(nt_fpga_t *p, int id, int instance)
+{
+	return (fpga_query_module(p, id, instance) != NULL);
+}
+
+nt_fpga_module_init_t *fpga_lookup_init(nt_fpga_t *p, int id, int instance)
+{
+	int i;
+
+	for (i = 0; i < p->mp_init->nb_modules; i++) {
+		nt_fpga_module_init_t *p_mod_init = &p->mp_init->modules[i];
+
+		if (p_mod_init->id == id && p_mod_init->instance == instance)
+			return p_mod_init;
+	}
+	return NULL;
+}
+
+int fpga_get_product_param(const nt_fpga_t *p, const int n_param_id,
+			 const int n_default_value)
+{
+	int i;
+
+	for (i = 0; i < p->mn_params; i++) {
+		nt_param_t *p_param = p->mpa_params[i];
+
+		if (p_param->param_id == n_param_id)
+			return p_param->param_value;
+	}
+
+	return n_default_value;
+}
+
+int fpga_get_product_id(const nt_fpga_t *p)
+{
+	return p->m_product_id;
+}
+
+int fpga_get_fpga_version(const nt_fpga_t *p)
+{
+	return p->m_fpga_version;
+}
+
+int fpga_get_fpga_revision(const nt_fpga_t *p)
+{
+	return p->m_fpga_revision;
+}
+
+void fpga_log_info(const nt_fpga_t *p _unused)
+{
+	NT_LOG(INF, NTHW, "FPGA: %d-%d-%d-%d-%d-%d (%08X)\n", p->m_item_id,
+	       p->m_product_id, p->m_fpga_version, p->m_fpga_revision,
+	       p->m_fpga_patch_no, p->m_fpga_build_no, p->m_fpga_build_time);
+}
+
+void fpga_dump(const nt_fpga_t *p)
+{
+	NT_LOG(DBG, NTHW, "%s: id=%d ver=%d.%d params=%d modules=%d\n",
+	       __func__, p->m_product_id, p->m_fpga_version, p->m_fpga_revision,
+	       p->mn_params, p->mn_modules);
+	fpga_dump_params(p);
+	fpga_dump_modules(p);
+}
+
+void fpga_dump_params(const nt_fpga_t *p)
+{
+	int i;
+
+	NT_LOG(DBG, NTHW, "%s: params=%d\n", __func__, p->mn_params);
+
+	for (i = 0; i < p->mn_params; i++) {
+		nt_param_t *p_par = p->mpa_params[i];
+
+		param_dump(p_par);
+	}
+}
+
+void fpga_dump_modules(const nt_fpga_t *p)
+{
+	int i;
+
+	NT_LOG(DBG, NTHW, "%s: modules=%d\n", __func__, p->mn_modules);
+
+	for (i = 0; i < p->mn_modules; i++) {
+		nt_module_t *p_mod = p->mpa_modules[i];
+
+		module_dump(p_mod);
+	}
+}
+
+/*
+ * Param
+ */
+nt_param_t *param_new(void)
+{
+	nt_param_t *p = malloc(sizeof(nt_param_t));
+	return p;
+}
+
+void param_delete(nt_param_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nt_param_t));
+		free(p);
+	}
+}
+
+void param_init(nt_param_t *p, nt_fpga_t *p_fpga, nt_fpga_prod_param_t *p_init)
+{
+	p->mp_owner = p_fpga;
+	p->mp_init = p_init;
+
+	p->param_id = p_init->param_id;
+	p->param_value = p_init->param_value;
+}
+
+void param_dump(const nt_param_t *p _unused)
+{
+	NT_LOG(DBG, NTHW, "%s: id=%d value=%d\n", __func__, p->param_id,
+	       p->param_value);
+}
+
+/*
+ * Module
+ */
+nt_module_t *module_new(void)
+{
+	nt_module_t *p = malloc(sizeof(nt_module_t));
+	return p;
+}
+
+void module_delete(nt_module_t *p)
+{
+	int i;
+
+	for (i = 0; i < p->mn_registers; i++) {
+		nt_register_t *p_reg = p->mpa_registers[i];
+
+		if (p_reg)
+			register_delete(p_reg);
+	}
+	memset(p, 0, sizeof(nt_module_t));
+	free(p);
+}
+
+void module_init(nt_module_t *p, nt_fpga_t *p_fpga, nt_fpga_module_init_t *p_init)
+{
+	int i;
+
+	p->mp_owner = p_fpga;
+	p->mp_init = p_init;
+
+	p->m_mod_id = p_init->id;
+	p->m_instance = p_init->instance;
+
+	/* Copy debug mode from owner */
+	if (p->mp_owner)
+		p->m_debug_mode = p->mp_owner->m_debug_mode;
+
+	else
+		p->m_debug_mode = 0;
+
+	p->m_mod_def_id = p_init->def_id;
+	p->m_major_version = p_init->major_version;
+	p->m_minor_version = p_init->minor_version;
+	p->m_bus = p_init->bus_id;
+	p->m_addr_base = p_init->addr_base;
+
+	p->mn_registers = p_init->nb_registers;
+	if (p->mn_registers) {
+		p->mpa_registers =
+			malloc(p->mn_registers * sizeof(nt_register_t *));
+		if (p->mpa_registers) {
+			memset(p->mpa_registers, 0,
+			       (p->mn_registers * sizeof(nt_register_t *)));
+			for (i = 0; i < p->mn_registers; i++) {
+				nt_register_t *p_reg = register_new();
+
+				register_init(p_reg, p, &p_init->registers[i]);
+				p->mpa_registers[i] = p_reg;
+			}
+		}
+	}
+}
+
+void module_init2(nt_module_t *p, nt_fpga_t *p_fpga, int mod_id, int instance,
+		  int debug_mode)
+{
+	nt_fpga_module_init_t *p_init = NULL;
+
+	p_init = fpga_lookup_init(p_fpga, mod_id, instance);
+	module_init(p, p_fpga, p_init);
+
+	/* set debug mode after regulat init... */
+	p->m_debug_mode = debug_mode;
+}
+
+void module_dump(const nt_module_t *p)
+{
+	NT_LOG(DBG, NTHW,
+	       "%s: id=%d inst=%d def=%d ver=%d.%d busid=%d base=0x%X regs=%d\n",
+	       __func__, p->m_mod_id, p->m_instance, p->m_mod_def_id,
+	       p->m_major_version, p->m_minor_version, p->m_bus, p->m_addr_base,
+	       p->mn_registers);
+	module_dump_registers(p);
+}
+
+void module_dump_registers(const nt_module_t *p)
+{
+	int i;
+
+	NT_LOG(DBG, NTHW, "%s: regs=%d\n", __func__, p->mn_registers);
+
+	for (i = 0; i < p->mn_registers; i++) {
+		nt_register_t *p_reg = p->mpa_registers[i];
+
+		register_dump(p_reg);
+	}
+}
+
+int module_get_major_version(const nt_module_t *p)
+{
+	return p->m_major_version;
+}
+
+int module_get_minor_version(const nt_module_t *p)
+{
+	return p->m_minor_version;
+}
+
+uint64_t module_get_version_packed64(const nt_module_t *p)
+{
+	return (((uint64_t)p->m_major_version & 0xFFFFFFFF) << 32) |
+	       (p->m_minor_version & 0xFFFFFFFF);
+}
+
+bool module_is_version_newer(const nt_module_t *p, int major_version,
+			   int minor_version)
+{
+	if (major_version == p->m_major_version)
+		return p->m_minor_version >= minor_version;
+	return p->m_major_version >= major_version;
+}
+
+static nt_register_t *module_lookup_register(nt_module_t *p, uint32_t id)
+{
+	int i;
+	nt_register_t *p_register = NULL;
+
+	for (i = 0; i < p->mn_registers; i++) {
+		if (p->mpa_registers[i]->m_id == id) {
+			p_register = p->mpa_registers[i];
+			break;
+		}
+	}
+	return p_register;
+}
+
+nt_register_t *module_get_register(nt_module_t *p, uint32_t id)
+{
+	nt_register_t *p_register;
+
+	if (p == NULL) {
+		NT_LOG(ERR, NTHW, "Illegal module context for register %d\n",
+		       id);
+		return NULL;
+	}
+
+	p_register = module_lookup_register(p, id);
+	if (!p_register) {
+		NT_LOG(ERR, NTHW, "Register %d not found in module: %s (%d)\n",
+		       id, nthw_fpga_mod_id_to_str(p->m_mod_id), p->m_mod_id);
+	}
+	return p_register;
+}
+
+nt_register_t *module_query_register(nt_module_t *p, uint32_t id)
+{
+	return module_lookup_register(p, id);
+}
+
+int module_get_debug_mode(const nt_module_t *p)
+{
+	return p->m_debug_mode;
+}
+
+void module_set_debug_mode(nt_module_t *p, unsigned int n_debug_mode)
+{
+	int i;
+	nt_register_t *p_register = NULL;
+
+	p->m_debug_mode = n_debug_mode;
+
+	for (i = 0; i < p->mn_registers; i++) {
+		p_register = p->mpa_registers[i];
+		if (p_register)
+			register_set_debug_mode(p_register, n_debug_mode);
+	}
+}
+
+int module_get_bus(const nt_module_t *p)
+{
+	return p->m_bus;
+}
+
+uint32_t module_get_addr_base(const nt_module_t *p)
+{
+	return p->m_addr_base;
+}
+
+void module_unsuppported(const nt_module_t *p)
+{
+	NT_LOG(ERR, NTHW, "Module %d not supported", p->mp_init->id);
+}
+
+/*
+ * Register
+ */
+nt_register_t *register_new(void)
+{
+	nt_register_t *p = malloc(sizeof(nt_register_t));
+	return p;
+}
+
+void register_delete(nt_register_t *p)
+{
+	int i;
+
+	for (i = 0; i < p->mn_fields; i++) {
+		nt_field_t *p_field = p->mpa_fields[i];
+
+		if (p_field)
+			field_delete(p_field);
+	}
+
+	if (p->mp_shadow)
+		free(p->mp_shadow);
+
+	if (p->mp_dirty)
+		free(p->mp_dirty);
+
+	memset(p, 0, sizeof(nt_register_t));
+	free(p);
+}
+
+void register_init(nt_register_t *p, nt_module_t *p_module,
+		   nt_fpga_register_init_t *p_init)
+{
+	int i;
+
+	p->mp_owner = p_module;
+
+	p->m_id = p_init->id;
+	p->mn_bit_width = p_init->bw;
+	p->mn_addr_rel = p_init->addr_rel;
+	p->m_addr = p_module->m_addr_base + p_init->addr_rel;
+	p->m_type = p_init->type;
+	p->m_len =
+		((p_init->bw != (uint16_t)-1) ?
+		 ((p_init->bw + 31) >> 5) :
+		 1); /* Old P200 registers have no bw at register level - default to BW=-1 */
+	p->m_debug_mode = p_module->m_debug_mode;
+
+	p->mn_fields = p_init->nb_fields;
+	if (p->mn_fields) {
+		p->mpa_fields = malloc(p->mn_fields * sizeof(nt_field_t *));
+
+		if (p->mpa_fields) {
+			memset(p->mpa_fields, 0,
+			       (p->mn_fields * sizeof(nt_field_t *)));
+			for (i = 0; i < p->mn_fields; i++) {
+				nt_field_t *p_field = field_new();
+
+				field_init(p_field, p, &p_init->fields[i]);
+				p->mpa_fields[i] = p_field;
+			}
+
+			p->mp_shadow = malloc(p->m_len * sizeof(uint32_t));
+			if (p->mp_shadow) {
+				memset(p->mp_shadow, 0x00,
+				       (p->m_len * sizeof(uint32_t)));
+			}
+
+			p->mp_dirty = malloc(p->m_len * sizeof(bool));
+			if (p->mp_dirty) {
+				memset(p->mp_dirty, 0x00,
+				       (p->m_len * sizeof(bool)));
+			}
+		}
+	}
+}
+
+void register_dump(const nt_register_t *p)
+{
+	NT_LOG(DBG, NTHW,
+	       "%s(id=%d type=%d addr=0x%08X addrrel=0x%08X len=%d bw=%d\n",
+	       __func__, p->m_id, p->m_type, p->m_addr, p->mn_addr_rel, p->m_len,
+	       p->mn_bit_width);
+	register_dump_fields(p);
+}
+
+void register_dump_fields(const nt_register_t *p)
+{
+	int i;
+
+	NT_LOG(DBG, NTHW, "%s(addr=0x%08X fields=%d\n", __func__, p->m_addr,
+	       p->mn_fields);
+	for (i = 0; i < p->mn_fields; i++)
+		field_dump(p->mpa_fields[i]);
+	NT_LOG(DBG, NTHW, "\n");
+}
+
+uint32_t register_get_address(const nt_register_t *p)
+{
+	return p->m_addr;
+}
+
+void register_reset(const nt_register_t *p)
+{
+	int i;
+	nt_field_t *p_field = NULL;
+
+	for (i = 0; i < p->mn_fields; i++) {
+		p_field = p->mpa_fields[i];
+		if (p_field)
+			field_reset(p_field);
+	}
+}
+
+static nt_field_t *register_lookup_field(const nt_register_t *p, uint32_t id)
+{
+	int i;
+	nt_field_t *p_field = NULL;
+
+	if (!p)
+		return NULL;
+
+	for (i = 0; i < p->mn_fields; i++) {
+		if (p->mpa_fields[i]->m_id == id) {
+			p_field = p->mpa_fields[i];
+			break;
+		}
+	}
+	return p_field;
+}
+
+nt_field_t *register_get_field(const nt_register_t *p, uint32_t id)
+{
+	nt_field_t *p_field;
+
+	if (p == NULL) {
+		NT_LOG(ERR, NTHW, "Illegal register context for field %d\n",
+		       id);
+		return NULL;
+	}
+
+	p_field = register_lookup_field(p, id);
+	if (!p_field) {
+		NT_LOG(ERR, NTHW, "Field %d not found in module: %s (%d)\n", id,
+		       nthw_fpga_mod_id_to_str(p->mp_owner->m_mod_id),
+		       p->mp_owner->m_mod_id);
+	}
+	return p_field;
+}
+
+nt_field_t *register_query_field(const nt_register_t *p, uint32_t id)
+{
+	return register_lookup_field(p, id);
+}
+
+int register_get_bit_width(const nt_register_t *p)
+{
+	return p->mn_bit_width;
+}
+
+uint32_t register_get_addr_rel(const nt_register_t *p)
+{
+	return p->mn_addr_rel;
+}
+
+int register_get_debug_mode(const nt_module_t *p)
+{
+	return p->m_debug_mode;
+}
+
+/*
+ * NOTE: do not set debug on fields - as register operation dumps typically are enough
+ */
+void register_set_debug_mode(nt_register_t *p, unsigned int n_debug_mode)
+{
+	int i;
+	nt_field_t *p_field = NULL;
+
+	p->m_debug_mode = n_debug_mode;
+
+	for (i = 0; i < p->mn_fields; i++) {
+		p_field = p->mpa_fields[i];
+		if (p_field)
+			field_set_debug_mode(p_field, n_debug_mode);
+	}
+}
+
+static int register_read_data(const nt_register_t *p)
+{
+	int rc = -1;
+
+	const int n_bus_type_id = module_get_bus(p->mp_owner);
+	const uint32_t addr = p->m_addr;
+	const uint32_t len = p->m_len;
+	uint32_t *const p_data = p->mp_shadow;
+
+	struct fpga_info_s *p_fpga_info = NULL;
+
+	if (p && p->mp_owner && p->mp_owner->mp_owner)
+		p_fpga_info = p->mp_owner->mp_owner->p_fpga_info;
+	assert(p_fpga_info);
+	assert(p_data);
+
+	rc = read_data(p_fpga_info, n_bus_type_id, addr, len, p_data);
+	return rc;
+}
+
+static int register_read_data_tsc(const nt_register_t *p, uint64_t *p_tsc1,
+				uint64_t *p_tsc2)
+{
+	int rc = -1;
+
+	const int n_bus_type_id = module_get_bus(p->mp_owner);
+	const uint32_t addr = p->m_addr;
+	const uint32_t len = p->m_len;
+	uint32_t *const p_data = p->mp_shadow;
+
+	struct fpga_info_s *p_fpga_info = NULL;
+
+	if (p && p->mp_owner && p->mp_owner->mp_owner)
+		p_fpga_info = p->mp_owner->mp_owner->p_fpga_info;
+
+	rc = read_data_tsc(p_fpga_info, n_bus_type_id, addr, len, p_data, p_tsc1, p_tsc2);
+
+	return rc;
+}
+
+static int register_write_data(const nt_register_t *p, uint32_t cnt)
+{
+	int rc = -1;
+
+	const int n_bus_type_id = module_get_bus(p->mp_owner);
+	const uint32_t addr = p->m_addr;
+	const uint32_t len = p->m_len;
+	uint32_t *const p_data = p->mp_shadow;
+
+	struct fpga_info_s *p_fpga_info = NULL;
+
+	if (p && p->mp_owner && p->mp_owner->mp_owner)
+		p_fpga_info = p->mp_owner->mp_owner->p_fpga_info;
+	assert(p_fpga_info);
+	assert(p_data);
+
+	rc = write_data(p_fpga_info, n_bus_type_id, addr, (len * cnt), p_data);
+
+	return rc;
+}
+
+void register_get_val(const nt_register_t *p, uint32_t *p_data, uint32_t len)
+{
+	uint32_t i;
+
+	if (len == (uint32_t)-1 || len > p->m_len)
+		len = p->m_len;
+
+	assert(len <= p->m_len);
+	assert(p_data);
+
+	for (i = 0; i < len; i++)
+		p_data[i] = p->mp_shadow[i];
+}
+
+uint32_t register_get_val32(const nt_register_t *p)
+{
+	uint32_t val = 0;
+
+	register_get_val(p, &val, 1);
+	return val;
+}
+
+void register_update(const nt_register_t *p)
+{
+	if (p && p->m_type != REGISTER_TYPE_WO) {
+		const char *const p_dev_name _unused = "NA";
+		const int n_bus_type_id = module_get_bus(p->mp_owner);
+
+		const char *const p_bus_name _unused = get_bus_name(n_bus_type_id);
+		const uint32_t addr _unused = p->m_addr;
+		const uint32_t len = p->m_len;
+		uint32_t *const p_data = p->mp_shadow;
+
+		register_read_data(p);
+		if (p->m_debug_mode & ON_READ) {
+			uint32_t i = len;
+
+			uint32_t *ptr _unused = p_data;
+			NT_LOG(DBG, NTHW,
+			       "Register::read(Dev: %s, Bus: %s, Addr: 0x%08X, _cnt: %d, Data:",
+			       p_dev_name, p_bus_name, addr, len);
+			while (i--)
+				NT_LOG(DBG, NTHW, " 0x%08X ", *ptr++);
+			NT_LOG(DBG, NTHW, ")\n");
+		}
+	}
+}
+
+uint32_t register_get_val_updated32(const nt_register_t *p)
+{
+	uint32_t val = 0;
+
+	register_update(p);
+	register_get_val(p, &val, 1);
+	return val;
+}
+
+void register_make_dirty(nt_register_t *p)
+{
+	uint32_t i;
+
+	for (i = 0; i < p->m_len; i++)
+		p->mp_dirty[i] = true;
+}
+
+void register_set_val(nt_register_t *p, const uint32_t *p_data, uint32_t len)
+{
+	assert(len <= p->m_len);
+	assert(p_data);
+
+	if (len == (uint32_t)-1 || len > p->m_len)
+		len = p->m_len;
+
+	if (p->mp_shadow != p_data)
+		memcpy(p->mp_shadow, p_data, (len * sizeof(uint32_t)));
+}
+
+void register_set_val_flush(nt_register_t *p, const uint32_t *p_data, uint32_t len)
+{
+	register_set_val(p, p_data, len);
+	register_flush(p, 1);
+}
+
+void register_flush(const nt_register_t *p, uint32_t cnt)
+{
+	int rc;
+
+	if (p->m_type != REGISTER_TYPE_RO) {
+		const char *const p_dev_name = "NA";
+		const int n_bus_type_id = module_get_bus(p->mp_owner);
+		const char *p_bus_name = get_bus_name(n_bus_type_id);
+		const uint32_t addr = p->m_addr;
+		const uint32_t len = p->m_len;
+		uint32_t *const p_data = p->mp_shadow;
+		uint32_t i;
+
+		assert(len * cnt <= 256);
+
+		if (p->m_debug_mode & ON_WRITE) {
+			uint32_t i = len * cnt;
+			uint32_t *ptr = p_data;
+			char *tmp_string =
+				ntlog_helper_str_alloc("Register::write");
+			ntlog_helper_str_add(tmp_string,
+					     "(Dev: %s, Bus: %s, Addr: 0x%08X, _cnt: %d, Data:",
+					     p_dev_name, p_bus_name, addr, i);
+			while (i--) {
+				ntlog_helper_str_add(tmp_string, " 0x%08X",
+						     *ptr++);
+			}
+			ntlog_helper_str_add(tmp_string, ")\n");
+			NT_LOG(DBG, NTHW, "%s", tmp_string);
+			ntlog_helper_str_free(tmp_string);
+		}
+
+		rc = register_write_data(p, cnt);
+
+		if (rc)
+			NT_LOG(ERR, NTHW, "Register write error %d\n", rc);
+
+		for (i = 0; i < cnt; i++)
+			p->mp_dirty[i] = false;
+	}
+}
+
+void register_do_read_trig_ts(const nt_register_t *p, uint64_t *tsc1,
+			   uint64_t *tsc2)
+{
+	register_read_data_tsc(p, tsc1, tsc2);
+}
+
+void register_clr(nt_register_t *p)
+{
+	memset(p->mp_shadow, 0, p->m_len * sizeof(uint32_t));
+	register_make_dirty(p);
+}
+
+void register_set(nt_register_t *p)
+{
+	memset(p->mp_shadow, 0xff, p->m_len * sizeof(uint32_t));
+	register_make_dirty(p);
+}
+
+/*
+ * Field
+ */
+nt_field_t *field_new(void)
+{
+	nt_field_t *p = malloc(sizeof(nt_field_t));
+	return p;
+}
+
+void field_delete(nt_field_t *p)
+{
+	memset(p, 0, sizeof(nt_field_t));
+	free(p);
+}
+
+void field_init(nt_field_t *p, nt_register_t *p_reg,
+		const nt_fpga_field_init_t *p_init)
+{
+	p->mp_owner = p_reg;
+
+	p->m_debug_mode = p_reg->m_debug_mode;
+
+	p->m_id = p_init->id;
+	p->mn_bit_width = p_init->bw;
+	p->mn_bit_pos_low = p_init->low;
+	p->m_reset_val = (uint32_t)p_init->reset_val;
+	p->m_first_word = p_init->low / 32;
+	p->m_first_bit = p_init->low % 32;
+	p->m_front_mask = 0;
+	p->m_body_length = 0;
+	p->mn_words = (p_init->bw + 0x1f) / 0x20;
+	p->m_tail_mask = 0;
+
+	{
+		int bits_remaining = p_init->bw;
+		int front_mask_length = 32 - p->m_first_bit;
+
+		if (front_mask_length > bits_remaining)
+			front_mask_length = bits_remaining;
+		bits_remaining -= front_mask_length;
+
+		p->m_front_mask = (uint32_t)(((1ULL << front_mask_length) - 1)
+					   << p->m_first_bit);
+
+		p->m_body_length = bits_remaining / 32;
+		bits_remaining -= p->m_body_length * 32;
+		p->m_tail_mask = (1 << bits_remaining) - 1;
+
+		if (p->m_debug_mode >= 0x100) {
+			NT_LOG(DBG, NTHW,
+			       "%s: fldid=%08d: [%08d:%08d] %08d/%08d: (%08d,%08d) (0x%08X,%08d,0x%08X)\n",
+			       __func__, p_init->id, p_init->low,
+			       (p_init->low + p_init->bw), p_init->bw,
+			       ((p_init->bw + 31) / 32), p->m_first_word,
+			       p->m_first_bit, p->m_front_mask, p->m_body_length,
+			       p->m_tail_mask);
+		}
+	}
+}
+
+int field_get_debug_mode(const nt_module_t *p)
+{
+	return p->m_debug_mode;
+}
+
+void field_set_debug_mode(nt_field_t *p, unsigned int n_debug_mode)
+{
+	p->m_debug_mode = n_debug_mode;
+}
+
+int field_get_bit_width(const nt_field_t *p)
+{
+	return p->mn_bit_width;
+}
+
+int field_get_bit_pos_low(const nt_field_t *p)
+{
+	return p->mn_bit_pos_low;
+}
+
+int field_get_bit_pos_high(const nt_field_t *p)
+{
+	return p->mn_bit_pos_low + p->mn_bit_width - 1;
+}
+
+uint32_t field_get_mask(const nt_field_t *p)
+{
+	return p->m_front_mask;
+}
+
+void field_reset(const nt_field_t *p)
+{
+	field_set_val32(p, (uint32_t)p->m_reset_val);
+}
+
+uint32_t field_get_val_mask(const nt_field_t *p)
+{
+	return (p->m_front_mask >> p->mn_bit_pos_low);
+}
+
+uint32_t field_get_reset_val(const nt_field_t *p)
+{
+	return (uint32_t)p->m_reset_val;
+}
+
+void field_get_val(const nt_field_t *p, uint32_t *p_data, uint32_t len)
+{
+	uint32_t i;
+	uint32_t data_index = 0;
+	uint32_t shadow_index = p->m_first_word;
+
+	union {
+		uint32_t w32[2];
+		uint64_t w64;
+	} buf;
+
+	(void)len;
+	assert(len == p->mn_words);
+
+	/* handle front */
+	buf.w32[0] = p->mp_owner->mp_shadow[shadow_index++] & p->m_front_mask;
+
+	/* handle body */
+	for (i = 0; i < p->m_body_length; i++) {
+		buf.w32[1] = p->mp_owner->mp_shadow[shadow_index++];
+		buf.w64 = buf.w64 >> (p->m_first_bit);
+		assert(data_index < len);
+		p_data[data_index++] = buf.w32[0];
+		buf.w64 = buf.w64 >> (32 - p->m_first_bit);
+	}
+
+	/* handle tail */
+	if (p->m_tail_mask)
+		buf.w32[1] = p->mp_owner->mp_shadow[shadow_index++] & p->m_tail_mask;
+
+	else
+		buf.w32[1] = 0;
+	buf.w64 = buf.w64 >> (p->m_first_bit);
+	p_data[data_index++] = buf.w32[0];
+	if (data_index < p->mn_words)
+		p_data[data_index++] = buf.w32[1];
+}
+
+void field_set_val(const nt_field_t *p, const uint32_t *p_data, uint32_t len)
+{
+	uint32_t i;
+	uint32_t data_index = 0;
+	uint32_t shadow_index = p->m_first_word;
+
+	union {
+		uint32_t w32[2];
+		uint64_t w64;
+	} buf;
+
+	(void)len;
+	assert(len == p->mn_words);
+
+	/* handle front */
+	buf.w32[0] = 0;
+	buf.w32[1] = p_data[data_index++];
+	buf.w64 = buf.w64 >> (32 - p->m_first_bit);
+	p->mp_owner->mp_shadow[shadow_index] =
+		(p->mp_owner->mp_shadow[shadow_index] & ~p->m_front_mask) |
+		(buf.w32[0] & p->m_front_mask);
+	shadow_index++;
+
+	/* handle body */
+	for (i = 0; i < p->m_body_length; i++) {
+		buf.w64 = buf.w64 >> (p->m_first_bit);
+		assert(data_index < len);
+		buf.w32[1] = p_data[data_index++];
+		buf.w64 = buf.w64 >> (32 - p->m_first_bit);
+		p->mp_owner->mp_shadow[shadow_index++] = buf.w32[0];
+	}
+
+	/* handle tail */
+	if (p->m_tail_mask) {
+		buf.w64 = buf.w64 >> (p->m_first_bit);
+		if (data_index < len)
+			buf.w32[1] = p_data[data_index];
+		buf.w64 = buf.w64 >> (32 - p->m_first_bit);
+		p->mp_owner->mp_shadow[shadow_index] =
+			(p->mp_owner->mp_shadow[shadow_index] & ~p->m_tail_mask) |
+			(buf.w32[0] & p->m_tail_mask);
+	}
+
+	register_make_dirty(p->mp_owner);
+}
+
+void field_set_val_flush(const nt_field_t *p, const uint32_t *p_data, uint32_t len)
+{
+	field_set_val(p, p_data, len);
+	field_flush_register(p);
+}
+
+uint32_t field_get_val32(const nt_field_t *p)
+{
+	uint32_t val;
+
+	field_get_val(p, &val, 1);
+	return val;
+}
+
+uint32_t field_get_updated(const nt_field_t *p)
+{
+	uint32_t val;
+
+	register_update(p->mp_owner);
+	field_get_val(p, &val, 1);
+
+	return val;
+}
+
+void field_read_trig_with_tsc(const nt_field_t *p, uint64_t *tsc1, uint64_t *tsc2)
+{
+	register_do_read_trig_ts(p->mp_owner, tsc1, tsc2);
+}
+
+void field_update_register(const nt_field_t *p)
+{
+	register_update(p->mp_owner);
+}
+
+void field_flush_register(const nt_field_t *p)
+{
+	register_flush(p->mp_owner, 1);
+}
+
+void field_set_val32(const nt_field_t *p, uint32_t val)
+{
+	field_set_val(p, &val, 1);
+}
+
+void field_set_val_flush32(const nt_field_t *p, uint32_t val)
+{
+	field_set_val(p, &val, 1);
+	register_flush(p->mp_owner, 1);
+}
+
+void field_clr_all(const nt_field_t *p)
+{
+	assert(p->m_body_length == 0);
+	field_set_val32(p, 0);
+}
+
+void field_clr_flush(const nt_field_t *p)
+{
+	field_clr_all(p);
+	register_flush(p->mp_owner, 1);
+}
+
+void field_set_all(const nt_field_t *p)
+{
+	assert(p->m_body_length == 0);
+	field_set_val32(p, ~0);
+}
+
+void field_set_flush(const nt_field_t *p)
+{
+	field_set_all(p);
+	register_flush(p->mp_owner, 1);
+}
+
+enum field_match {
+	FIELD_MATCH_CLR_ALL,
+	FIELD_MATCH_SET_ALL,
+	FIELD_MATCH_CLR_ANY,
+	FIELD_MATCH_SET_ANY,
+};
+
+static int field_wait_cond32(const nt_field_t *p, enum field_match e_match,
+			    int n_poll_iterations, int n_poll_interval)
+{
+	const uint32_t n_mask = (1 << p->mn_bit_width) - 1;
+
+	if (n_poll_iterations == -1)
+		n_poll_iterations = 10000;
+	if (n_poll_interval == -1)
+		n_poll_interval = 100; /* usec */
+
+	if (p->m_debug_mode) {
+		const char *const p_cond_name _unused =
+			((e_match == FIELD_MATCH_SET_ALL) ?
+			 "SetAll" :
+			 ((e_match == FIELD_MATCH_CLR_ALL) ?
+			  "ClrAll" :
+			  ((e_match == FIELD_MATCH_CLR_ANY) ?
+			   "ClrAny" :
+			   "SetAny")));
+		const char *const p_dev_name _unused = "NA";
+		const char *const p_bus_name _unused =
+			get_bus_name(module_get_bus(p->mp_owner->mp_owner));
+		uint32_t n_reg_addr _unused = register_get_address(p->mp_owner);
+
+		uint32_t n_reg_mask _unused =
+			(((1 << p->mn_bit_width) - 1) << p->mn_bit_pos_low);
+
+		NT_LOG(DBG, NTHW,
+		       "Register::Field::wait%s32(Dev: %s, Bus: %s, Addr: 0x%08X, Mask: 0x%08X, Iterations: %d, Interval: %d)\n",
+		       p_cond_name, p_dev_name, p_bus_name, n_reg_addr, n_reg_mask,
+		       n_poll_iterations, n_poll_interval);
+	}
+
+	while (true) {
+		uint32_t val = field_get_updated(p);
+
+		if (e_match == FIELD_MATCH_SET_ANY && val != 0) {
+			return 0;
+		} else if (e_match == FIELD_MATCH_SET_ALL && val == n_mask) {
+			return 0;
+		} else if (e_match == FIELD_MATCH_CLR_ALL && val == 0) {
+			return 0;
+		} else if (e_match == FIELD_MATCH_CLR_ANY) {
+			uint32_t mask = field_get_mask(p);
+
+			if (val != mask)
+				return 0;
+		}
+
+		n_poll_iterations--;
+		if (n_poll_iterations <= 0)
+			return -1;
+		NT_OS_WAIT_USEC(n_poll_interval);
+	}
+	return 0;
+}
+
+int field_wait_set_all32(const nt_field_t *p, int n_poll_iterations,
+		       int n_poll_interval)
+{
+	return field_wait_cond32(p, FIELD_MATCH_SET_ALL, n_poll_iterations,
+				n_poll_interval);
+}
+
+int field_wait_clr_all32(const nt_field_t *p, int n_poll_iterations,
+		       int n_poll_interval)
+{
+	return field_wait_cond32(p, FIELD_MATCH_CLR_ALL, n_poll_iterations,
+				n_poll_interval);
+}
+
+int field_wait_set_any32(const nt_field_t *p, int n_poll_iterations,
+		       int n_poll_interval)
+{
+	return field_wait_cond32(p, FIELD_MATCH_SET_ANY, n_poll_iterations,
+				n_poll_interval);
+}
+
+int field_wait_clr_any32(const nt_field_t *p, int n_poll_iterations,
+		       int n_poll_interval)
+{
+	return field_wait_cond32(p, FIELD_MATCH_CLR_ANY, n_poll_iterations,
+				n_poll_interval);
+}
+
+int field_wait_val_mask32(const nt_field_t *p, uint32_t n_wait_cond_value,
+			uint32_t n_wait_cond_mask, int n_poll_iterations,
+			int n_poll_interval)
+{
+	if (n_poll_iterations == -1)
+		n_poll_iterations = 10000;
+	if (n_poll_interval == -1)
+		n_poll_interval = 100;
+
+	while (true) {
+		uint32_t val = field_get_updated(p);
+
+		if (val == (n_wait_cond_value & n_wait_cond_mask))
+			break;
+		n_poll_iterations--;
+		if (n_poll_iterations <= 0)
+			return -1;
+		NT_OS_WAIT_USEC(n_poll_interval);
+	}
+	return 0;
+}
+
+void field_dump(const nt_field_t *p _unused)
+{
+	NT_LOG(DBG, NTHW, "%s: %02d: %02d %02d %02d: %02d: %X\n", __func__,
+	       p->m_id, p->mn_bit_pos_low, (p->mn_bit_pos_low + p->mn_bit_width),
+	       p->mn_bit_width, p->mn_words, p->m_reset_val);
+}
+
+void field_dump_val(const nt_field_t *p)
+{
+	int i;
+	uint32_t buf[32];
+
+	field_get_val(p, buf, p->mn_words);
+	NT_LOG(DBG, NTHW, " @%d:", p->m_first_bit + p->m_first_word * 32);
+	NT_LOG(DBG, NTHW, "%X", buf[p->mn_words - 1]);
+	for (i = p->mn_words - 1; i > 0; i--)
+		NT_LOG(DBG, NTHW, "%08X", buf[i - 1]);
+	NT_LOG(DBG, NTHW, "\n");
+}
+
+void field_dump_init(const nt_fpga_field_init_t *p _unused)
+{
+	NT_LOG(DBG, NTHW, "%s: %02d: %02d %02d %02d: 0x%" PRIX64 "\n", __func__,
+	       p->id, p->low, p->low + p->bw, p->bw, p->reset_val);
+}
+
+/*
+ * nthw fpga model helpers
+ */
+
+nt_fpga_t *nthw_get_fpga(struct fpga_info_s *p_fpga_info, uint64_t n_fpga_ident)
+{
+	nt_fpga_mgr_t *p_fpga_mgr = NULL;
+	nt_fpga_t *p_fpga = NULL;
+	int n_fpga_type_id, n_fpga_prod_id, n_fpga_ver_id, n_fpga_rev_id;
+	char s_fpga_prod_ver_rev_str[32];
+
+	p_fpga_mgr = fpga_mgr_new();
+	fpga_mgr_init(p_fpga_mgr);
+	p_fpga = fpga_mgr_query_fpga(p_fpga_mgr, n_fpga_ident, p_fpga_info);
+
+	n_fpga_type_id = FPGAID_TO_PRODUCTTYPE(n_fpga_ident);
+	n_fpga_prod_id = FPGAID_TO_PRODUCTCODE(n_fpga_ident);
+	n_fpga_ver_id = FPGAID_TO_VERSIONCODE(n_fpga_ident);
+	n_fpga_rev_id = FPGAID_TO_REVISIONCODE(n_fpga_ident);
+
+	snprintf(s_fpga_prod_ver_rev_str, sizeof(s_fpga_prod_ver_rev_str),
+		 "%04d-%04d-%02d-%02d", n_fpga_type_id, n_fpga_prod_id, n_fpga_ver_id,
+		 n_fpga_rev_id);
+
+	if (p_fpga == NULL) {
+		NT_LOG(ERR, NTHW, "%s: no match for FPGA: %s\n", __func__,
+		       s_fpga_prod_ver_rev_str);
+		/* do not return here... */
+	}
+
+	if (p_fpga_mgr) {
+		fpga_mgr_delete(p_fpga_mgr);
+		p_fpga_mgr = NULL;
+	}
+
+	return p_fpga;
+}
+
+nt_module_t *nthw_get_module(nt_fpga_t *p_fpga, int n_mod, int n_instance)
+{
+	nt_module_t *p_mod = fpga_query_module(p_fpga, n_mod, n_instance);
+	return p_mod;
+}
+
+nt_register_t *nthw_get_register(nt_module_t *p_mod, int n_reg)
+{
+	nt_register_t *p_reg = module_get_register(p_mod, n_reg);
+	return p_reg;
+}
+
+nt_field_t *nthw_get_field(nt_register_t *p_reg, int n_fld)
+{
+	nt_field_t *p_fld = register_get_field(p_reg, n_fld);
+	return p_fld;
+}
diff --git a/drivers/net/ntnic/nthw/nthw_fpga_model.h b/drivers/net/ntnic/nthw/nthw_fpga_model.h
new file mode 100644
index 0000000000..b00b7b6cfa
--- /dev/null
+++ b/drivers/net/ntnic/nthw/nthw_fpga_model.h
@@ -0,0 +1,308 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_FPGA_MODEL_H__
+#define __NTHW_FPGA_MODEL_H__
+
+#include <stdbool.h>
+#include <stdio.h>
+#include "fpga_model.h"
+
+#ifndef FPGAID_TO_PRODUCTCODE
+#define FPGAID_TO_PRODUCTTYPE(fpga_id) ((uint16_t)((fpga_id) >> 32) & 0xFF)
+#define FPGAID_TO_PRODUCTCODE(fpga_id) ((uint16_t)((fpga_id) >> 16) & 0xFFFF)
+#define FPGAID_TO_VERSIONCODE(fpga_id) ((uint16_t)((fpga_id) >> 8 & 0xFF))
+#define FPGAID_TO_REVISIONCODE(fpga_id) ((uint16_t)((fpga_id) >> 0 & 0xFF))
+#endif
+
+#define VERSION_PACKED64(_major_, _minor_) \
+	((((uint64_t)(_major_) & 0xFFFFFFFF) << 32) | ((_minor_) & 0xFFFFFFFF))
+
+enum debug_mode { NO_DEBUG, ON_READ, ON_WRITE };
+
+enum nthw_bus_type {
+	NTHW_BUS_UNKNOWN,
+	NTHW_BUS_BAR,
+	NTHW_BUS_PCI,
+	NTHW_BUS_NMB,
+	NTHW_BUS_NDM,
+	NTHW_BUS_RAB0,
+	NTHW_BUS_RAB1,
+	NTHW_BUS_RAB2
+};
+
+struct nt_fpga_s;
+
+struct nt_param_s;
+
+struct nt_module_s;
+
+struct nt_register_s;
+
+struct nt_field_s;
+
+struct nt_fpga_mgr_s {
+	int mn_fpgas;
+	struct nt_fpga_prod_init **mpa_fpga_prod_init;
+};
+
+typedef struct nt_fpga_mgr_s nt_fpga_mgr_t;
+
+struct nt_fpga_s {
+	struct fpga_info_s *p_fpga_info;
+
+	int m_item_id;
+	int m_product_id;
+	int m_fpga_version;
+	int m_fpga_revision;
+	int m_fpga_patch_no;
+	int m_fpga_build_no;
+	uint32_t m_fpga_build_time;
+
+	int mn_params;
+	struct nt_param_s **mpa_params;
+
+	int mn_modules;
+	struct nt_module_s **mpa_modules;
+
+	nt_fpga_prod_init_t *mp_init;
+
+	int m_debug_mode;
+};
+
+typedef struct nt_fpga_s nt_fpga_t;
+
+struct nt_param_s {
+	nt_fpga_t *mp_owner;
+
+	int param_id;
+	int param_value;
+
+	nt_fpga_prod_param_t *mp_init;
+};
+
+typedef struct nt_param_s nt_param_t;
+
+struct nt_module_s {
+	nt_fpga_t *mp_owner;
+
+	int m_mod_id;
+
+	int m_instance;
+
+	int m_mod_def_id;
+	int m_major_version;
+	int m_minor_version;
+
+	int m_bus;
+	uint32_t m_addr_base;
+
+	int m_debug_mode;
+
+	int mn_registers;
+	struct nt_register_s **mpa_registers;
+
+	nt_fpga_module_init_t *mp_init;
+};
+
+typedef struct nt_module_s nt_module_t;
+
+struct nt_register_s {
+	nt_module_t *mp_owner;
+
+	uint32_t m_id;
+
+	uint32_t mn_bit_width;
+	uint32_t mn_addr_rel;
+	uint32_t m_addr;
+	uint32_t m_type;
+	uint32_t m_len;
+
+	int m_debug_mode;
+
+	int mn_fields;
+	struct nt_field_s **mpa_fields;
+
+	uint32_t *mp_shadow;
+	bool *mp_dirty;
+
+	nt_fpga_register_init_t *mp_init;
+};
+
+typedef struct nt_register_s nt_register_t;
+
+struct nt_field_s {
+	nt_register_t *mp_owner;
+
+	uint32_t m_id;
+
+	uint32_t mn_bit_width;
+	uint32_t mn_bit_pos_low;
+	uint32_t m_reset_val;
+	uint32_t m_first_word;
+	uint32_t m_first_bit;
+	uint32_t m_front_mask;
+	uint32_t m_body_length;
+	uint32_t mn_words;
+	uint32_t m_tail_mask;
+
+	int m_debug_mode;
+
+	nt_fpga_field_init_t *mp_init;
+};
+
+typedef struct nt_field_s nt_field_t;
+
+nt_fpga_mgr_t *fpga_mgr_new(void);
+void fpga_mgr_init(nt_fpga_mgr_t *p);
+void fpga_mgr_delete(nt_fpga_mgr_t *p);
+nt_fpga_t *fpga_mgr_query_fpga(nt_fpga_mgr_t *p, uint64_t n_fpga_id,
+			     struct fpga_info_s *p_fpga_info);
+
+void fpga_mgr_log_dump(nt_fpga_mgr_t *p);
+void fpga_mgr_show(nt_fpga_mgr_t *p, FILE *out, int detail_level);
+
+nt_fpga_t *fpga_new(void);
+void fpga_delete(nt_fpga_t *p);
+void fpga_delete_all(nt_fpga_t *p);
+void fpga_init(nt_fpga_t *p, nt_fpga_prod_init_t *fpga_prod_init,
+	       struct fpga_info_s *p_fpga_info);
+
+int fpga_get_product_param(const nt_fpga_t *p, const int n_param_id,
+			 const int default_value);
+int fpga_get_product_id(const nt_fpga_t *p);
+int fpga_get_fpga_version(const nt_fpga_t *p);
+int fpga_get_fpga_revision(const nt_fpga_t *p);
+nt_module_t *fpga_query_module(const nt_fpga_t *p, int id, int instance);
+nt_fpga_module_init_t *fpga_lookup_init(nt_fpga_t *p, int id, int instance);
+bool fpga_query(nt_fpga_t *p, int id, int instance);
+void fpga_set_debug_mode(nt_fpga_t *p, int n_debug_mode);
+
+void fpga_log_info(const nt_fpga_t *p);
+void fpga_dump(const nt_fpga_t *p);
+void fpga_dump_params(const nt_fpga_t *p);
+void fpga_dump_modules(const nt_fpga_t *p);
+
+nt_param_t *param_new(void);
+void param_delete(nt_param_t *p);
+void param_init(nt_param_t *p, nt_fpga_t *p_fpga, nt_fpga_prod_param_t *p_init);
+
+void param_dump(const nt_param_t *p);
+
+nt_module_t *module_new(void);
+void module_delete(nt_module_t *p);
+void module_init(nt_module_t *p, nt_fpga_t *p_fpga,
+		 nt_fpga_module_init_t *p_init);
+void module_init2(nt_module_t *p, nt_fpga_t *p_fpga, int mod_id, int instance,
+		  int debug_mode);
+
+int module_get_major_version(const nt_module_t *p);
+int module_get_minor_version(const nt_module_t *p);
+uint64_t module_get_version_packed64(const nt_module_t *p);
+bool module_is_version_newer(const nt_module_t *p, int major_version,
+			   int minor_version);
+
+int module_get_bus(const nt_module_t *p);
+nt_register_t *module_get_register(nt_module_t *p, uint32_t id);
+nt_register_t *module_query_register(nt_module_t *p, uint32_t id);
+int module_get_debug_mode(const nt_module_t *p);
+void module_set_debug_mode(nt_module_t *p, unsigned int debug_mode);
+uint32_t module_get_addr_base(const nt_module_t *p);
+void module_unsuppported(const nt_module_t *p);
+
+void module_dump(const nt_module_t *p);
+void module_dump_registers(const nt_module_t *p);
+
+nt_register_t *register_new(void);
+void register_delete(nt_register_t *p);
+void register_init(nt_register_t *p, nt_module_t *p_module,
+		   nt_fpga_register_init_t *p_init);
+
+nt_field_t *register_get_field(const nt_register_t *p, uint32_t id);
+nt_field_t *register_query_field(const nt_register_t *p, uint32_t id);
+
+uint32_t register_get_address(const nt_register_t *p);
+uint32_t register_get_addr_rel(const nt_register_t *p);
+int register_get_bit_width(const nt_register_t *p);
+int register_get_debug_mode(const nt_module_t *p);
+void register_set_debug_mode(nt_register_t *p, unsigned int debug_mode);
+
+void register_get_val(const nt_register_t *p, uint32_t *p_data, uint32_t len);
+uint32_t register_get_val32(const nt_register_t *p);
+uint32_t register_get_val_updated32(const nt_register_t *p);
+
+void register_set_val(nt_register_t *p, const uint32_t *p_data, uint32_t len);
+void register_set_val_flush(nt_register_t *p, const uint32_t *p_data,
+			  uint32_t len);
+
+void register_make_dirty(nt_register_t *p);
+void register_update(const nt_register_t *p);
+void register_reset(const nt_register_t *p);
+void register_flush(const nt_register_t *p, uint32_t cnt);
+void register_clr(nt_register_t *p);
+void register_set(nt_register_t *p);
+
+void register_do_read_trig_ts(const nt_register_t *p, uint64_t *tsc1,
+			   uint64_t *tsc2);
+
+void register_dump(const nt_register_t *p);
+void register_dump_fields(const nt_register_t *p);
+
+nt_field_t *field_new(void);
+void field_delete(nt_field_t *p);
+void field_init(nt_field_t *p, nt_register_t *p_reg,
+		const nt_fpga_field_init_t *p_init);
+
+int field_get_debug_mode(const nt_module_t *p);
+void field_set_debug_mode(nt_field_t *p, unsigned int n_debug_mode);
+int field_get_bit_width(const nt_field_t *p);
+int field_get_bit_pos_low(const nt_field_t *p);
+int field_get_bit_pos_high(const nt_field_t *p);
+uint32_t field_get_mask(const nt_field_t *p);
+void field_reset(const nt_field_t *p);
+uint32_t field_get_reset_val(const nt_field_t *p);
+void field_get_val(const nt_field_t *p, uint32_t *p_data, uint32_t len);
+void field_set_val(const nt_field_t *p, const uint32_t *p_data, uint32_t len);
+void field_set_val_flush(const nt_field_t *p, const uint32_t *p_data,
+		       uint32_t len);
+uint32_t field_get_val_mask(const nt_field_t *p);
+uint32_t field_get_val32(const nt_field_t *p);
+uint32_t field_get_updated(const nt_field_t *p);
+void field_read_trig_with_tsc(const nt_field_t *p, uint64_t *tsc1, uint64_t *tsc2);
+void field_update_register(const nt_field_t *p);
+void field_flush_register(const nt_field_t *p);
+void field_set_val32(const nt_field_t *p, uint32_t val);
+void field_set_val_flush32(const nt_field_t *p, uint32_t val);
+void field_clr_all(const nt_field_t *p);
+void field_clr_flush(const nt_field_t *p);
+void field_set_all(const nt_field_t *p);
+void field_set_flush(const nt_field_t *p);
+
+int field_wait_clr_all32(const nt_field_t *p, int n_poll_iterations,
+		       int n_poll_interval);
+int field_wait_set_all32(const nt_field_t *p, int n_poll_iterations,
+		       int n_poll_interval);
+
+int field_wait_clr_any32(const nt_field_t *p, int n_poll_iterations,
+		       int n_poll_interval);
+int field_wait_set_any32(const nt_field_t *p, int n_poll_iterations,
+		       int n_poll_interval);
+
+int field_wait_val_mask32(const nt_field_t *p, uint32_t n_wait_cond_value,
+			uint32_t n_wait_cond_mask, int n_poll_iterations,
+			int n_poll_interval);
+
+void field_dump(const nt_field_t *p);
+void field_dump_val(const nt_field_t *p);
+void field_dump_init(const nt_fpga_field_init_t *p);
+
+/*
+ * nthw helpers
+ */
+nt_fpga_t *nthw_get_fpga(struct fpga_info_s *p_fpga_info, uint64_t n_fpga_ident);
+nt_module_t *nthw_get_module(nt_fpga_t *p_fpga, int n_mod, int n_instance);
+nt_register_t *nthw_get_register(nt_module_t *p_mod, int n_reg);
+nt_field_t *nthw_get_field(nt_register_t *p_reg, int n_fld);
+
+#endif /* __NTHW_FPGA_MODEL_H__ */
diff --git a/drivers/net/ntnic/nthw/nthw_helper.h b/drivers/net/ntnic/nthw/nthw_helper.h
new file mode 100644
index 0000000000..22f6a0d471
--- /dev/null
+++ b/drivers/net/ntnic/nthw/nthw_helper.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_HELPER_H__
+#define __NTHW_HELPER_H__
+
+#include <unistd.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <assert.h>
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+#endif
+
+#endif /* __NTHW_HELPER_H__ */
diff --git a/drivers/net/ntnic/nthw/nthw_platform.c b/drivers/net/ntnic/nthw/nthw_platform.c
new file mode 100644
index 0000000000..203947e03a
--- /dev/null
+++ b/drivers/net/ntnic/nthw/nthw_platform.c
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "nthw_platform_drv.h"
+
+nthw_adapter_id_t nthw_platform_get_nthw_adapter_id(const uint16_t n_pci_device_id)
+{
+	switch (n_pci_device_id) {
+	case NT_HW_PCI_DEVICE_ID_NT40E3:
+		return NT_HW_ADAPTER_ID_NT40E3;
+	case NT_HW_PCI_DEVICE_ID_NT100E3:
+		return NT_HW_ADAPTER_ID_NT100E3;
+	case NT_HW_PCI_DEVICE_ID_NT80E3:
+		return NT_HW_ADAPTER_ID_NT80E3;
+	case NT_HW_PCI_DEVICE_ID_NT40A00:
+		return NT_HW_ADAPTER_ID_NT40E3;
+	case NT_HW_PCI_DEVICE_ID_NT40A01:
+		return NT_HW_ADAPTER_ID_NT40E3;
+	case NT_HW_PCI_DEVICE_ID_NT200E3:
+		return NT_HW_ADAPTER_ID_NT200E3;
+	case NT_HW_PCI_DEVICE_ID_NT200A01:
+		return NT_HW_ADAPTER_ID_NT200A01;
+	case NT_HW_PCI_DEVICE_ID_NT200D01:
+		return NT_HW_ADAPTER_ID_NT200D01;
+	case NT_HW_PCI_DEVICE_ID_NT200A02:
+		return NT_HW_ADAPTER_ID_NT200A02;
+	case NT_HW_PCI_DEVICE_ID_NT50B01:
+		return NT_HW_ADAPTER_ID_NT50B01;
+	case NT_HW_PCI_DEVICE_ID_NT100A01:
+		return NT_HW_ADAPTER_ID_NT100A01;
+	default:
+		return NT_HW_ADAPTER_ID_UNKNOWN;
+	}
+}
diff --git a/drivers/net/ntnic/nthw/nthw_platform_drv.h b/drivers/net/ntnic/nthw/nthw_platform_drv.h
new file mode 100644
index 0000000000..fee2dc4853
--- /dev/null
+++ b/drivers/net/ntnic/nthw/nthw_platform_drv.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_PLATFORM_DRV_H__
+#define __NTHW_PLATFORM_DRV_H__
+
+#include "nthw_helper.h"
+
+#define NT_HW_PCI_VENDOR_ID (0x18f4)
+
+#define NT_HW_PCI_DEVICE_ID_NT40E3 (0x145)
+#define NT_HW_PCI_DEVICE_ID_NT100E3 (0x155)
+#define NT_HW_PCI_DEVICE_ID_NT80E3 (0x165)
+#define NT_HW_PCI_DEVICE_ID_NT40A00 (0x175)
+#define NT_HW_PCI_DEVICE_ID_NT40A01 (0x185)
+#define NT_HW_PCI_DEVICE_ID_NT200E3 (0x195)
+#define NT_HW_PCI_DEVICE_ID_NT200A01 (0x1A5)
+#define NT_HW_PCI_DEVICE_ID_NT200D01 (0x1B5)
+#define NT_HW_PCI_DEVICE_ID_NT200A02 (0x1C5)
+#define NT_HW_PCI_DEVICE_ID_NT50B01 (0x1D5)
+#define NT_HW_PCI_DEVICE_ID_NT100A01 (0x1E5)
+
+enum nthw_adapter_id_e {
+	NT_HW_ADAPTER_ID_UNKNOWN = 0,
+	NT_HW_ADAPTER_ID_NT40E3,
+	NT_HW_ADAPTER_ID_NT40A01 = NT_HW_ADAPTER_ID_NT40E3,
+	NT_HW_ADAPTER_ID_NT50B01,
+	NT_HW_ADAPTER_ID_NT80E3,
+	NT_HW_ADAPTER_ID_NT100E3,
+	NT_HW_ADAPTER_ID_NT100A01,
+	NT_HW_ADAPTER_ID_NT200E3,
+	NT_HW_ADAPTER_ID_NT200A01,
+	NT_HW_ADAPTER_ID_NT200D01,
+	NT_HW_ADAPTER_ID_NT200A02,
+};
+
+typedef enum nthw_adapter_id_e nthw_adapter_id_t;
+
+nthw_adapter_id_t nthw_platform_get_nthw_adapter_id(const uint16_t n_pci_device_id);
+
+#endif /* __NTHW_PLATFORM_DRV_H__ */
diff --git a/drivers/net/ntnic/nthw/nthw_profile.h b/drivers/net/ntnic/nthw/nthw_profile.h
new file mode 100644
index 0000000000..2fcb7b4adf
--- /dev/null
+++ b/drivers/net/ntnic/nthw/nthw_profile.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_PROFILE_H__
+#define __NTHW_PROFILE_H__
+
+enum fpga_info_profile {
+	FPGA_INFO_PROFILE_UNKNOWN = 0,
+	FPGA_INFO_PROFILE_VSWITCH = 1,
+	FPGA_INFO_PROFILE_INLINE = 2,
+	FPGA_INFO_PROFILE_CAPTURE = 3,
+};
+
+#endif /* __NTHW_PROFILE_H__ */
diff --git a/drivers/net/ntnic/nthw/nthw_rac.c b/drivers/net/ntnic/nthw/nthw_rac.c
new file mode 100644
index 0000000000..fc50b1a50b
--- /dev/null
+++ b/drivers/net/ntnic/nthw/nthw_rac.c
@@ -0,0 +1,976 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "nt_util.h"
+#include "ntlog.h"
+
+#include "nthw_drv.h"
+#include "nthw_register.h"
+#include "nthw_rac.h"
+
+#include <pthread.h>
+
+/*
+ * Prevent that RAB echo debug trace ever gets into a release build
+ */
+#if defined(DEBUG)
+#undef RAB_DEBUG_ECHO
+#else
+#undef RAB_DEBUG_ECHO
+#endif /* DEBUG */
+
+#define RAB_DMA_WAIT (1000000)
+#define RAB_DMA_BUF_CNT (0x4000)
+
+#define RAB_READ (0x01)
+#define RAB_WRITE (0x02)
+#define RAB_ECHO (0x08)
+#define RAB_COMPLETION (0x0F)
+
+#define RAB_READ_ECHO (RAB_READ | RAB_ECHO)
+#define RAB_WRITE_ECHO (RAB_WRITE | RAB_ECHO)
+
+#define RAB_OPR_LO (28)
+#define RAB_OPR_HI (31)
+#define RAB_OPR_BW (4)
+
+#define RAB_CNT_LO (20)
+#define RAB_CNT_HI (27)
+#define RAB_CNT_BW (8)
+
+#define RAB_BUSID_LO (16)
+#define RAB_BUSID_HI (19)
+#define RAB_BUSID_BW (4)
+
+#define RAB_ADDR_LO (0)
+#define RAB_ADDR_HI (15)
+#define RAB_ADDR_BW (16)
+
+nthw_rac_t *nthw_rac_new(void)
+{
+	nthw_rac_t *p = malloc(sizeof(nthw_rac_t));
+
+	memset(p, 0, sizeof(nthw_rac_t));
+	return p;
+}
+
+void nthw_rac_delete(nthw_rac_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_rac_t));
+		free(p);
+	}
+}
+
+int nthw_rac_init(nthw_rac_t *p, nt_fpga_t *p_fpga, struct fpga_info_s *p_fpga_info)
+{
+	assert(p_fpga_info);
+
+	const char *const p_adapter_id_str = p_fpga_info->mp_adapter_id_str;
+	nt_module_t *mod = fpga_query_module(p_fpga, MOD_RAC, 0);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: RAC %d: no such instance\n",
+		       p_adapter_id_str, 0);
+		return -1;
+	}
+
+	p->mp_fpga = p_fpga;
+	p->mp_mod_rac = mod;
+
+	{
+		/*
+		 * RAC is a primary communication channel
+		 * turn off debug by default
+		 * except for rac_rab_init
+		 */
+		const int n_debug_mode = module_get_debug_mode(p->mp_mod_rac);
+
+		if (n_debug_mode && n_debug_mode <= 0xff) {
+			module_set_debug_mode(p->mp_mod_rac, 0);
+			register_set_debug_mode(p->mp_reg_rab_init, n_debug_mode);
+		}
+	}
+
+	/* Params */
+	p->mn_param_rac_rab_interfaces =
+		fpga_get_product_param(p->mp_fpga, NT_RAC_RAB_INTERFACES, 3);
+	NT_LOG(DBG, NTHW, "%s: NT_RAC_RAB_INTERFACES=%d\n", p_adapter_id_str,
+	       p->mn_param_rac_rab_interfaces);
+
+	p->mn_param_rac_rab_ob_update =
+		fpga_get_product_param(p->mp_fpga, NT_RAC_RAB_OB_UPDATE, 0);
+	NT_LOG(DBG, NTHW, "%s: NT_RAC_RAB_OB_UPDATE=%d\n", p_adapter_id_str,
+	       p->mn_param_rac_rab_ob_update);
+
+	/* Optional dummy test registers */
+	p->mp_reg_dummy0 = module_query_register(p->mp_mod_rac, RAC_DUMMY0);
+	p->mp_reg_dummy1 = module_query_register(p->mp_mod_rac, RAC_DUMMY1);
+	p->mp_reg_dummy2 = module_query_register(p->mp_mod_rac, RAC_DUMMY2);
+
+	p->mp_reg_rab_init = module_get_register(p->mp_mod_rac, RAC_RAB_INIT);
+	p->mp_fld_rab_init = register_get_field(p->mp_reg_rab_init, RAC_RAB_INIT_RAB);
+	p->mn_fld_rab_init_bw = field_get_bit_width(p->mp_fld_rab_init);
+	p->mn_fld_rab_init_mask = field_get_mask(p->mp_fld_rab_init);
+
+	/* RAC_RAB_INIT_RAB reg/field sanity checks: */
+	assert(p->mn_fld_rab_init_mask == ((1UL << p->mn_fld_rab_init_bw) - 1));
+	assert(p->mn_fld_rab_init_bw == p->mn_param_rac_rab_interfaces);
+
+	p->mp_reg_dbg_ctrl = module_query_register(p->mp_mod_rac, RAC_DBG_CTRL);
+	if (p->mp_reg_dbg_ctrl) {
+		p->mp_fld_dbg_ctrl =
+			register_query_field(p->mp_reg_dbg_ctrl, RAC_DBG_CTRL_C);
+	} else {
+		p->mp_fld_dbg_ctrl = NULL;
+	}
+	p->mp_reg_dbg_data = module_query_register(p->mp_mod_rac, RAC_DBG_DATA);
+	if (p->mp_reg_dbg_data) {
+		p->mp_fld_dbg_data =
+			register_query_field(p->mp_reg_dbg_data, RAC_DBG_DATA_D);
+	} else {
+		p->mp_reg_dbg_data = NULL;
+	}
+	p->mp_reg_rab_ib_data = module_get_register(p->mp_mod_rac, RAC_RAB_IB_DATA);
+	p->mp_fld_rab_ib_data =
+		register_get_field(p->mp_reg_rab_ib_data, RAC_RAB_IB_DATA_D);
+
+	p->mp_reg_rab_ob_data = module_get_register(p->mp_mod_rac, RAC_RAB_OB_DATA);
+	p->mp_fld_rab_ob_data =
+		register_get_field(p->mp_reg_rab_ob_data, RAC_RAB_OB_DATA_D);
+
+	p->mp_reg_rab_buf_free = module_get_register(p->mp_mod_rac, RAC_RAB_BUF_FREE);
+	p->mp_fld_rab_buf_free_ib_free =
+		register_get_field(p->mp_reg_rab_buf_free, RAC_RAB_BUF_FREE_IB_FREE);
+	p->mp_fld_rab_buf_free_ib_ovf =
+		register_get_field(p->mp_reg_rab_buf_free, RAC_RAB_BUF_FREE_IB_OVF);
+	p->mp_fld_rab_buf_free_ob_free =
+		register_get_field(p->mp_reg_rab_buf_free, RAC_RAB_BUF_FREE_OB_FREE);
+	p->mp_fld_rab_buf_free_ob_ovf =
+		register_get_field(p->mp_reg_rab_buf_free, RAC_RAB_BUF_FREE_OB_OVF);
+	p->mp_fld_rab_buf_free_timeout =
+		register_get_field(p->mp_reg_rab_buf_free, RAC_RAB_BUF_FREE_TIMEOUT);
+
+	p->mp_reg_rab_buf_used = module_get_register(p->mp_mod_rac, RAC_RAB_BUF_USED);
+	p->mp_fld_rab_buf_used_ib_used =
+		register_get_field(p->mp_reg_rab_buf_used, RAC_RAB_BUF_USED_IB_USED);
+	p->mp_fld_rab_buf_used_ob_used =
+		register_get_field(p->mp_reg_rab_buf_used, RAC_RAB_BUF_USED_OB_USED);
+	p->mp_fld_rab_buf_used_flush =
+		register_get_field(p->mp_reg_rab_buf_used, RAC_RAB_BUF_USED_FLUSH);
+
+	/*
+	 * RAC_RAB_DMA regs are optional - only found in real NT4GA - not found in 9231/9232 and
+	 * earlier
+	 */
+	p->mp_reg_rab_dma_ib_lo = module_get_register(p->mp_mod_rac, RAC_RAB_DMA_IB_LO);
+	p->mp_fld_rab_dma_ib_lo_phy_addr =
+		register_get_field(p->mp_reg_rab_dma_ib_lo, RAC_RAB_DMA_IB_LO_PHYADDR);
+
+	p->mp_reg_rab_dma_ib_hi = module_get_register(p->mp_mod_rac, RAC_RAB_DMA_IB_HI);
+	p->mp_fld_rab_dma_ib_hi_phy_addr =
+		register_get_field(p->mp_reg_rab_dma_ib_hi, RAC_RAB_DMA_IB_HI_PHYADDR);
+
+	p->mp_reg_rab_dma_ob_lo = module_get_register(p->mp_mod_rac, RAC_RAB_DMA_OB_LO);
+	p->mp_fld_rab_dma_ob_lo_phy_addr =
+		register_get_field(p->mp_reg_rab_dma_ob_lo, RAC_RAB_DMA_OB_LO_PHYADDR);
+
+	p->mp_reg_rab_dma_ob_hi = module_get_register(p->mp_mod_rac, RAC_RAB_DMA_OB_HI);
+	p->mp_fld_rab_dma_ob_hi_phy_addr =
+		register_get_field(p->mp_reg_rab_dma_ob_hi, RAC_RAB_DMA_OB_HI_PHYADDR);
+
+	p->mp_reg_rab_dma_ib_wr = module_get_register(p->mp_mod_rac, RAC_RAB_DMA_IB_WR);
+	p->mp_fld_rab_dma_ib_wr_ptr =
+		register_get_field(p->mp_reg_rab_dma_ib_wr, RAC_RAB_DMA_IB_WR_PTR);
+
+	p->mp_reg_rab_dma_ib_rd = module_get_register(p->mp_mod_rac, RAC_RAB_DMA_IB_RD);
+	p->mp_fld_rab_dma_ib_rd_ptr =
+		register_get_field(p->mp_reg_rab_dma_ib_rd, RAC_RAB_DMA_IB_RD_PTR);
+
+	p->mp_reg_rab_dma_ob_wr = module_get_register(p->mp_mod_rac, RAC_RAB_DMA_OB_WR);
+	p->mp_fld_rab_dma_ob_wr_ptr =
+		register_get_field(p->mp_reg_rab_dma_ob_wr, RAC_RAB_DMA_OB_WR_PTR);
+
+	p->rac_rab_init_addr = register_get_address(p->mp_reg_rab_init);
+	p->rac_rab_ib_data_addr = register_get_address(p->mp_reg_rab_ib_data);
+	p->rac_rab_ob_data_addr = register_get_address(p->mp_reg_rab_ob_data);
+	p->rac_rab_buf_free_addr = register_get_address(p->mp_reg_rab_buf_free);
+	p->rac_rab_buf_used_addr = register_get_address(p->mp_reg_rab_buf_used);
+
+	/*
+	 * RAC_RAB_DMA regs are optional - only found in real NT4GA -
+	 * not found in 9231/9232 and earlier
+	 */
+
+	p->rac_rab_dma_ib_lo_addr = register_get_address(p->mp_reg_rab_dma_ib_lo);
+	p->rac_rab_dma_ib_hi_addr = register_get_address(p->mp_reg_rab_dma_ib_hi);
+	p->rac_rab_dma_ob_lo_addr = register_get_address(p->mp_reg_rab_dma_ob_lo);
+	p->rac_rab_dma_ob_hi_addr = register_get_address(p->mp_reg_rab_dma_ob_hi);
+	p->rac_rab_dma_ib_rd_addr = register_get_address(p->mp_reg_rab_dma_ib_rd);
+	p->rac_rab_dma_ob_wr_addr = register_get_address(p->mp_reg_rab_dma_ob_wr);
+	p->rac_rab_dma_ib_wr_addr = register_get_address(p->mp_reg_rab_dma_ib_wr);
+
+	p->rac_rab_buf_free_ib_free_mask =
+		field_get_mask(p->mp_fld_rab_buf_free_ib_free);
+	p->rac_rab_buf_free_ob_free_mask =
+		field_get_mask(p->mp_fld_rab_buf_free_ob_free);
+	p->rac_rab_buf_used_ib_used_mask =
+		field_get_mask(p->mp_fld_rab_buf_used_ib_used);
+	p->rac_rab_buf_used_ob_used_mask =
+		field_get_mask(p->mp_fld_rab_buf_used_ob_used);
+
+	p->rac_rab_buf_used_flush_mask = field_get_mask(p->mp_fld_rab_buf_used_flush);
+
+	p->rac_rab_buf_used_ob_used_low =
+		field_get_bit_pos_low(p->mp_fld_rab_buf_used_ob_used);
+
+	p->mp_reg_rab_nmb_rd = module_query_register(p->mp_mod_rac, RAC_NMB_RD_ADR);
+	if (p->mp_reg_rab_nmb_rd)
+		p->rac_nmb_rd_adr_addr = register_get_address(p->mp_reg_rab_nmb_rd);
+
+	p->mp_reg_rab_nmb_data = module_query_register(p->mp_mod_rac, RAC_NMB_DATA);
+	if (p->mp_reg_rab_nmb_data)
+		p->rac_nmb_data_addr = register_get_address(p->mp_reg_rab_nmb_data);
+
+	p->mp_reg_rab_nmb_wr = module_query_register(p->mp_mod_rac, RAC_NMB_WR_ADR);
+	if (p->mp_reg_rab_nmb_wr)
+		p->rac_nmb_wr_adr_addr = register_get_address(p->mp_reg_rab_nmb_wr);
+
+	p->mp_reg_rab_nmb_status =
+		module_query_register(p->mp_mod_rac, RAC_NMB_STATUS);
+	if (p->mp_reg_rab_nmb_status) {
+		p->rac_nmb_status_addr =
+			register_get_address(p->mp_reg_rab_nmb_status);
+	}
+
+	p->m_dma = NULL;
+
+	pthread_mutex_init(&p->m_mutex, NULL);
+
+	return 0;
+}
+
+int nthw_rac_get_rab_interface_count(const nthw_rac_t *p)
+{
+	return p->mn_param_rac_rab_interfaces;
+}
+
+static inline int nthw_rac_wait_for_rab_done(const nthw_rac_t *p, uint32_t address,
+		uint32_t word_cnt)
+{
+	const struct fpga_info_s *const p_fpga_info = p->mp_fpga->p_fpga_info;
+	const char *const p_adapter_id_str = p_fpga_info->mp_adapter_id_str;
+	uint32_t used = 0;
+	uint32_t retry;
+
+	for (retry = 0; retry < 100000; retry++) {
+		nthw_rac_reg_read32(p_fpga_info, p->rac_rab_buf_used_addr, &used);
+		used = (used & p->rac_rab_buf_used_ob_used_mask) >>
+		       p->rac_rab_buf_used_ob_used_low;
+		if (used >= word_cnt)
+			break;
+	}
+
+	if (used < word_cnt) {
+		NT_LOG(ERR, NTHW,
+		       "%s: Fail rab bus r/w addr=0x%08X used=%x wordcount=%d\n",
+		       p_adapter_id_str, address, used, word_cnt);
+		return -1;
+	}
+	return 0;
+}
+
+/*
+ * NT_PCI_REG_P9xyz_RAC_RAB_INIT
+ *
+ * Initializes (resets) the programmable registers on the Register Access Busses (RAB).
+ * This initialization must be performed by software as part of the driver load procedure.
+ *
+ * Bit n of this field initializes the programmable registers on RAB interface n.
+ * Software must write one to the bit and then clear the bit again.
+ *
+ * All RAB module registers will be reset to their defaults.
+ * This includes the product specific RESET module (eg RST9xyz)
+ * As a consequence of this behavior the official reset sequence
+ * must be excersised - as all RAB modules will be held in reset.
+ */
+int nthw_rac_rab_init(nthw_rac_t *p, uint32_t n_rab_intf_mask)
+{
+	/*
+	 * Write rac_rab_init
+	 * Perform operation twice - first to get trace of operation -
+	 * second to get things done...
+	 */
+	const struct fpga_info_s *const p_fpga_info = p->mp_fpga->p_fpga_info;
+
+	field_set_val_flush32(p->mp_fld_rab_init, n_rab_intf_mask);
+	nthw_rac_reg_write32(p_fpga_info, p->rac_rab_init_addr, n_rab_intf_mask);
+	return 0;
+}
+
+int nthw_rac_rab_reset(nthw_rac_t *p)
+{
+	const struct fpga_info_s *const p_fpga_info = p->mp_fpga->p_fpga_info;
+
+	const char *const p_adapter_id_str _unused = p_fpga_info->mp_adapter_id_str;
+
+	/* RAC RAB bus "flip/flip" reset */
+	const int n_rac_rab_bus_count = nthw_rac_get_rab_interface_count(p);
+	const int n_rac_rab_bus_mask = (1 << n_rac_rab_bus_count) - 1;
+
+	NT_LOG(DBG, NTHW, "%s: NT_RAC_RAB_INTERFACES=%d (0x%02X)\n",
+	       p_adapter_id_str, n_rac_rab_bus_count, n_rac_rab_bus_mask);
+	assert(n_rac_rab_bus_count);
+	assert(n_rac_rab_bus_mask);
+
+	/* RAC RAB bus "flip/flip" reset first stage - new impl (ref RMT#37020) */
+	nthw_rac_rab_init(p, 0);
+	nthw_rac_rab_init(p, n_rac_rab_bus_mask);
+	nthw_rac_rab_init(p, n_rac_rab_bus_mask & ~0x01);
+
+	return 0;
+}
+
+int nthw_rac_rab_setup(nthw_rac_t *p)
+{
+	int rc = 0;
+
+	const struct fpga_info_s *const p_fpga_info = p->mp_fpga->p_fpga_info;
+	uint32_t n_dma_buf_size = 2L * RAB_DMA_BUF_CNT * sizeof(uint32_t);
+	const size_t align_size = ALIGN_SIZE(n_dma_buf_size);
+	int numa_node = p_fpga_info->numa_node;
+	uint64_t dma_addr;
+	uint32_t buf;
+
+	if (!p->m_dma) {
+		struct nt_dma_s *vfio_dma;
+		/* FPGA needs Page alignment (4K) */
+		vfio_dma = nt_dma_alloc(align_size, 0x1000, numa_node);
+
+		if (vfio_dma == NULL) {
+			NT_LOG(ERR, ETHDEV, "%s: nt_dma_alloc failed\n",
+			       __func__);
+			return -1;
+		}
+		p->m_dma_in_buf = (uint32_t *)vfio_dma->addr;
+		p->m_dma_out_buf = p->m_dma_in_buf + RAB_DMA_BUF_CNT;
+		p->m_dma = vfio_dma;
+	}
+
+	/* Setup DMA on the adapter */
+	dma_addr = p->m_dma->iova;
+	nthw_rac_reg_write32(p_fpga_info, p->rac_rab_dma_ib_lo_addr,
+			   dma_addr & 0xffffffff);
+	nthw_rac_reg_write32(p_fpga_info, p->rac_rab_dma_ib_hi_addr,
+			   (uint32_t)(dma_addr >> 32) & 0xffffffff);
+	dma_addr += RAB_DMA_BUF_CNT * sizeof(uint32_t);
+	nthw_rac_reg_write32(p_fpga_info, p->rac_rab_dma_ob_lo_addr,
+			   dma_addr & 0xffffffff);
+	nthw_rac_reg_write32(p_fpga_info, p->rac_rab_dma_ob_hi_addr,
+			   (uint32_t)(dma_addr >> 32) & 0xffffffff);
+
+	/* Set initial value of internal pointers */
+	nthw_rac_reg_read32(p_fpga_info, p->rac_rab_dma_ib_rd_addr, &buf);
+	p->m_dma_in_ptr_wr = (uint16_t)(buf / sizeof(uint32_t));
+	nthw_rac_reg_read32(p_fpga_info, p->rac_rab_dma_ob_wr_addr, &buf);
+	p->m_dma_out_ptr_rd = (uint16_t)(buf / sizeof(uint32_t));
+	p->m_in_free = RAB_DMA_BUF_CNT;
+
+	return rc;
+}
+
+int nthw_rac_rab_dma_begin(nthw_rac_t *p)
+{
+	const struct fpga_info_s *const p_fpga_info = p->mp_fpga->p_fpga_info;
+	const char *const p_adapter_id_str = p_fpga_info->mp_adapter_id_str;
+
+	pthread_mutex_lock(&p->m_mutex);
+
+	if (p->m_dma_active) {
+		pthread_mutex_unlock(&p->m_mutex);
+		NT_LOG(ERR, NTHW,
+		       "%s: DMA begin requested, but a DMA transaction is already active\n",
+		       p_adapter_id_str);
+		return -1;
+	}
+
+	p->m_dma_active = true;
+
+	return 0;
+}
+
+static void nthw_rac_rab_dma_activate(nthw_rac_t *p)
+{
+	const struct fpga_info_s *const p_fpga_info = p->mp_fpga->p_fpga_info;
+	const uint32_t completion = RAB_COMPLETION << RAB_OPR_LO;
+
+	/* Write completion word */
+	p->m_dma_in_buf[p->m_dma_in_ptr_wr] = completion;
+	p->m_dma_in_ptr_wr =
+		(uint16_t)((p->m_dma_in_ptr_wr + 1) & (RAB_DMA_BUF_CNT - 1));
+
+	/* Clear output completion word */
+	p->m_dma_out_buf[p->m_dma_out_ptr_rd] = 0;
+
+	/* _update DMA pointer and start transfer */
+	nthw_rac_reg_write32(p_fpga_info, p->rac_rab_dma_ib_wr_addr,
+			   (uint32_t)(p->m_dma_in_ptr_wr * sizeof(uint32_t)));
+}
+
+static int nthw_rac_rab_dma_wait(nthw_rac_t *p)
+{
+	const struct fpga_info_s *const p_fpga_info = p->mp_fpga->p_fpga_info;
+	const uint32_t completion = RAB_COMPLETION << RAB_OPR_LO;
+	uint32_t i;
+
+	for (i = 0; i < RAB_DMA_WAIT; i++) {
+		NT_OS_WAIT_USEC_POLL(1);
+		if ((p->m_dma_out_buf[p->m_dma_out_ptr_rd] & completion) ==
+				completion)
+			break;
+	}
+
+	if (i == RAB_DMA_WAIT) {
+		NT_LOG(ERR, NTHW,
+		       "%s: RAB: Unexpected value of completion (0x%08X)\n",
+		       p_fpga_info->mp_adapter_id_str,
+		       p->m_dma_out_buf[p->m_dma_out_ptr_rd]);
+		return -1;
+	}
+
+	p->m_dma_out_ptr_rd =
+		(uint16_t)((p->m_dma_out_ptr_rd + 1) & (RAB_DMA_BUF_CNT - 1));
+	p->m_in_free = RAB_DMA_BUF_CNT;
+
+	return 0;
+}
+
+int nthw_rac_rab_dma_commit(nthw_rac_t *p)
+{
+	int ret;
+
+	if (!p->m_dma_active) {
+		/* Expecting mutex not to be locked! */
+		assert(0); /* alert developer that something is wrong */
+		return -1;
+	}
+
+	nthw_rac_rab_dma_activate(p);
+	ret = nthw_rac_rab_dma_wait(p);
+
+	p->m_dma_active = false;
+
+	pthread_mutex_unlock(&p->m_mutex);
+
+	return ret;
+}
+
+void nthw_rac_reg_read32(const struct fpga_info_s *p_fpga_info, uint32_t reg_addr,
+		       uint32_t *p_data)
+{
+	*p_data = *(volatile uint32_t *)((uint8_t *)p_fpga_info->bar0_addr +
+					reg_addr);
+}
+
+void nthw_rac_reg_write32(const struct fpga_info_s *p_fpga_info, uint32_t reg_addr,
+			uint32_t p_data)
+{
+	*(volatile uint32_t *)((uint8_t *)p_fpga_info->bar0_addr + reg_addr) =
+		p_data;
+}
+
+int nthw_rac_rab_write32_dma(nthw_rac_t *p, uint32_t address, rab_bus_id_t bus_id,
+			    uint32_t word_cnt, const uint32_t *p_data)
+{
+	const struct fpga_info_s *const p_fpga_info = p->mp_fpga->p_fpga_info;
+
+	if (word_cnt == 0 || word_cnt > 256) {
+		NT_LOG(ERR, NTHW,
+		       "%s: Failed rab dma write length check - bus: %d addr: 0x%08X wordcount: %d - inBufFree: 0x%08X\n",
+		       p_fpga_info->mp_adapter_id_str, bus_id, address, word_cnt,
+		       p->m_in_free);
+		assert(0); /* alert developer that something is wrong */
+		return -1;
+	}
+
+	if (p->m_in_free < (word_cnt + 3)) {
+		/*
+		 * No more memory available.
+		 * nthw_rac_rab_dma_commit() needs to be called to start and finish pending
+		 * transfers.
+		 */
+		return -1;
+	}
+
+	p->m_in_free -= (word_cnt + 1);
+
+	/* Write the command word */
+#if defined(RAB_DEBUG_ECHO)
+	p->m_dma_in_buf[p->m_dma_in_ptr_wr] =
+		(RAB_WRITE_ECHO << RAB_OPR_LO) |
+		((word_cnt & ((1 << RAB_CNT_BW) - 1)) << RAB_CNT_LO) |
+		(bus_id << RAB_BUSID_LO) | address;
+	p->m_dma_out_ptr_rd = (uint16_t)((p->m_dma_out_ptr_rd + word_cnt + 1) &
+				     (RAB_DMA_BUF_CNT - 1));
+#else
+	p->m_dma_in_buf[p->m_dma_in_ptr_wr] =
+		(RAB_WRITE << RAB_OPR_LO) |
+		((word_cnt & ((1 << RAB_CNT_BW) - 1)) << RAB_CNT_LO) |
+		(bus_id << RAB_BUSID_LO) | address;
+#endif
+	p->m_dma_in_ptr_wr =
+		(uint16_t)((p->m_dma_in_ptr_wr + 1) & (RAB_DMA_BUF_CNT - 1));
+
+	for (uint32_t i = 0; i < word_cnt; i++) {
+		p->m_dma_in_buf[p->m_dma_in_ptr_wr] = p_data[i];
+		p->m_dma_in_ptr_wr = (uint16_t)((p->m_dma_in_ptr_wr + 1) &
+					    (RAB_DMA_BUF_CNT - 1));
+	}
+
+	return 0;
+}
+
+int nthw_rac_rab_read32_dma(nthw_rac_t *p, uint32_t address, rab_bus_id_t bus_id,
+			   uint32_t word_cnt, struct dma_buf_ptr *buf_ptr)
+{
+	const struct fpga_info_s *const p_fpga_info = p->mp_fpga->p_fpga_info;
+
+	if (word_cnt == 0 || word_cnt > 256) {
+		NT_LOG(ERR, NTHW,
+		       "%s: Failed rab dma read length check - bus: %d addr: 0x%08X wordcount: %d - inBufFree: 0x%08X\n",
+		       p_fpga_info->mp_adapter_id_str, bus_id, address, word_cnt,
+		       p->m_in_free);
+		assert(0); /* alert developer that something is wrong */
+		return -1;
+	}
+
+	if ((word_cnt + 3) > RAB_DMA_BUF_CNT) {
+		NT_LOG(ERR, NTHW,
+		       "%s: Failed rab dma read length check - bus: %d addr: 0x%08X wordcount: %d: 0x%08X",
+		       p_fpga_info->mp_adapter_id_str, bus_id, address, word_cnt);
+		return -1;
+	}
+
+	if (p->m_in_free < 3) {
+		/*
+		 * No more memory available.
+		 * nthw_rac_rab_dma_commit() needs to be called to start and finish pending
+		 * transfers.
+		 */
+		return -1;
+	}
+
+	p->m_in_free -= 1;
+
+	/* Write the command word */
+#if defined(RAB_DEBUG_ECHO)
+	p->m_dma_in_buf[p->m_dma_in_ptr_wr] =
+		(RAB_READ_ECHO << RAB_OPR_LO) |
+		((word_cnt & ((1 << RAB_CNT_BW) - 1)) << RAB_CNT_LO) |
+		(bus_id << RAB_BUSID_LO) | address;
+	p->m_dma_out_ptr_rd =
+		(uint16_t)((p->m_dma_out_ptr_rd + 1) & (RAB_DMA_BUF_CNT - 1));
+#else
+	p->m_dma_in_buf[p->m_dma_in_ptr_wr] =
+		(RAB_READ << RAB_OPR_LO) |
+		((word_cnt & ((1 << RAB_CNT_BW) - 1)) << RAB_CNT_LO) |
+		(bus_id << RAB_BUSID_LO) | address;
+#endif
+	p->m_dma_in_ptr_wr =
+		(uint16_t)((p->m_dma_in_ptr_wr + 1) & (RAB_DMA_BUF_CNT - 1));
+
+	buf_ptr->index = p->m_dma_out_ptr_rd;
+	buf_ptr->size = RAB_DMA_BUF_CNT;
+	buf_ptr->base = p->m_dma_out_buf;
+	p->m_dma_out_ptr_rd = (uint16_t)((p->m_dma_out_ptr_rd + word_cnt) &
+				     (RAB_DMA_BUF_CNT - 1U));
+
+	return 0;
+}
+
+int nthw_rac_rab_write32(nthw_rac_t *p, uint32_t address, rab_bus_id_t bus_id,
+			uint32_t word_cnt, const uint32_t *p_data)
+{
+	const struct fpga_info_s *const p_fpga_info = p->mp_fpga->p_fpga_info;
+	const char *const p_adapter_id_str = p_fpga_info->mp_adapter_id_str;
+	int res = 0;
+	uint32_t rab_oper_wr;
+	uint32_t rab_oper_cmpl;
+	uint32_t rab_echo_oper_cmpl;
+	uint32_t word_cnt_expected;
+	uint32_t buf_used;
+	uint32_t buf_free;
+	uint32_t in_buf_free;
+	uint32_t out_buf_free;
+
+	if (address > (1 << RAB_ADDR_BW)) {
+		NT_LOG(ERR, NTHW,
+		       "%s: RAB: Illegal address: value too large %d - max %d\n",
+		       p_adapter_id_str, address, (1 << RAB_ADDR_BW));
+		return -1;
+	}
+
+	if (bus_id > (1 << RAB_BUSID_BW)) {
+		NT_LOG(ERR, NTHW,
+		       "%s: RAB: Illegal bus id: value too large %d - max %d\n",
+		       p_adapter_id_str, bus_id, (1 << RAB_BUSID_BW));
+		return -1;
+	}
+
+	if (word_cnt == 0) {
+		NT_LOG(ERR, NTHW,
+		       "%s: RAB: Illegal word count: value is zero (%d)\n",
+		       p_adapter_id_str, word_cnt);
+		return -1;
+	}
+
+	if (word_cnt > (1 << RAB_CNT_BW)) {
+		NT_LOG(ERR, NTHW,
+		       "%s: RAB: Illegal word count: value too large %d - max %d\n",
+		       p_adapter_id_str, word_cnt, (1 << RAB_CNT_BW));
+		return -1;
+	}
+
+	pthread_mutex_lock(&p->m_mutex);
+
+	if (p->m_dma_active) {
+		NT_LOG(ERR, NTHW, "%s: RAB: Illegal operation: DMA enabled\n",
+		       p_adapter_id_str);
+		res = -1;
+		goto exit_unlock_res;
+	}
+
+	/* Read buffer free register */
+	nthw_rac_reg_read32(p_fpga_info, p->rac_rab_buf_free_addr, &buf_free);
+
+	in_buf_free = buf_free & p->rac_rab_buf_free_ib_free_mask;
+	out_buf_free = (buf_free & p->rac_rab_buf_free_ob_free_mask) >> 16;
+
+	/* Read buffer used register */
+	nthw_rac_reg_read32(p_fpga_info, p->rac_rab_buf_used_addr, &buf_used);
+
+	buf_used = buf_used & (p->rac_rab_buf_used_ib_used_mask |
+			     p->rac_rab_buf_used_ob_used_mask);
+
+	/*
+	 * Verify that output buffer can hold one completion word,
+	 * input buffer can hold the number of words to be written +
+	 * one write and one completion command
+	 * and that the input and output "used" buffer is 0
+	 */
+	if ((out_buf_free >= 1 && (in_buf_free >= word_cnt + 2)) && buf_used == 0) {
+		uint32_t i;
+
+		word_cnt_expected = 0;
+
+		/* Compose write command */
+#if defined(RAB_DEBUG_ECHO)
+		rab_oper_wr =
+			(RAB_WRITE_ECHO << RAB_OPR_LO) |
+			((word_cnt & ((1 << RAB_CNT_BW) - 1)) << RAB_CNT_LO) |
+			(bus_id << RAB_BUSID_LO) | address;
+		word_cnt_expected += word_cnt + 1;
+#else
+		rab_oper_wr =
+			(RAB_WRITE << RAB_OPR_LO) |
+			((word_cnt & ((1 << RAB_CNT_BW) - 1)) << RAB_CNT_LO) |
+			(bus_id << RAB_BUSID_LO) | address;
+#endif /* RAB_DEBUG_ECHO */
+
+		/* Write command */
+		nthw_rac_reg_write32(p_fpga_info, p->rac_rab_ib_data_addr,
+				   rab_oper_wr);
+
+		/* Write da to input buffer */
+		for (i = 0; i < word_cnt; i++) {
+			nthw_rac_reg_write32(p_fpga_info, p->rac_rab_ib_data_addr,
+					   *p_data);
+			p_data++;
+		}
+
+		/* Compose completion command */
+		rab_oper_cmpl = (RAB_COMPLETION << RAB_OPR_LO);
+		word_cnt_expected++;
+
+		/* Write command */
+		nthw_rac_reg_write32(p_fpga_info, p->rac_rab_ib_data_addr,
+				   rab_oper_cmpl);
+
+		/* Wait until done */
+		if (nthw_rac_wait_for_rab_done(p, address, word_cnt_expected)) {
+			res = -1;
+			goto exit_unlock_res;
+		}
+
+#if defined(RAB_DEBUG_ECHO)
+		{
+			uint32_t rab_echo_oper_wr;
+
+			nthw_rac_reg_read32(p_fpga_info, p->rac_rab_ob_data_addr,
+					  &rab_echo_oper_wr);
+			if (p->mn_param_rac_rab_ob_update) {
+				nthw_rac_reg_write32(p_fpga_info,
+						   p->rac_rab_ob_data_addr, 0);
+			}
+			if (rab_oper_wr != rab_echo_oper_wr) {
+				NT_LOG(ERR, NTHW,
+				       "%s: expected rab read echo oper (0x%08X) - read (0x%08X)\n",
+				       p_adapter_id_str, rab_oper_wr, rab_echo_oper_wr);
+			}
+		}
+
+		{
+			/* Read data from output buffer */
+			uint32_t data;
+
+			for (i = 0; i < word_cnt; i++) {
+				nthw_rac_reg_read32(p_fpga_info,
+						  p->rac_rab_ob_data_addr,
+						  &data);
+				if (p->mn_param_rac_rab_ob_update) {
+					nthw_rac_reg_write32(p_fpga_info,
+							     p->rac_rab_ob_data_addr, 0);
+				}
+			}
+		}
+#endif /* RAB_DEBUG_ECHO */
+
+		/* Read completion from out buffer */
+		nthw_rac_reg_read32(p_fpga_info, p->rac_rab_ob_data_addr,
+				  &rab_echo_oper_cmpl);
+		if (p->mn_param_rac_rab_ob_update) {
+			nthw_rac_reg_write32(p_fpga_info, p->rac_rab_ob_data_addr,
+					   0);
+		}
+		if (rab_echo_oper_cmpl != rab_oper_cmpl) {
+			NT_LOG(ERR, NTHW,
+			       "%s: RAB: Unexpected value of completion (0x%08X)- inBufFree: 0x%08X, outBufFree: 0x%08X, bufUsed: 0x%08X\n",
+			       p_adapter_id_str, rab_echo_oper_cmpl, in_buf_free,
+			       out_buf_free, buf_used);
+			res = -1;
+			goto exit_unlock_res;
+		}
+
+		/* Read buffer free register */
+		nthw_rac_reg_read32(p_fpga_info, p->rac_rab_buf_free_addr,
+				  &buf_free);
+		if (buf_free & 0x80000000) {
+			/* Clear Timeout and overflow bits */
+			nthw_rac_reg_write32(p_fpga_info, p->rac_rab_buf_free_addr,
+					   0x0);
+			NT_LOG(ERR, NTHW,
+			       "%s: RAB: timeout - Access outside register - bus: %d addr: 0x%08X - inBufFree: 0x%08X, outBufFree: 0x%08X, bufUsed: 0x%08X\n",
+			       p_adapter_id_str, bus_id, address, in_buf_free,
+			       out_buf_free, buf_used);
+			res = -1;
+			goto exit_unlock_res;
+		}
+
+		res = 0;
+		goto exit_unlock_res;
+	} else {
+		NT_LOG(ERR, NTHW,
+		       "%s: RAB: Fail rab bus buffer check - bus: %d addr: 0x%08X wordcount: %d - inBufFree: 0x%08X, outBufFree: 0x%08X, bufUsed: 0x%08X\n",
+		       p_adapter_id_str, bus_id, address, word_cnt, in_buf_free,
+		       out_buf_free, buf_used);
+		res = -1;
+		goto exit_unlock_res;
+	}
+
+exit_unlock_res:
+	pthread_mutex_unlock(&p->m_mutex);
+	return res;
+}
+
+int nthw_rac_rab_read32(nthw_rac_t *p, uint32_t address, rab_bus_id_t bus_id,
+		       uint32_t word_cnt, uint32_t *p_data)
+{
+	const struct fpga_info_s *const p_fpga_info = p->mp_fpga->p_fpga_info;
+	const char *const p_adapter_id_str = p_fpga_info->mp_adapter_id_str;
+	int res = 0;
+	uint32_t rab_oper_rd;
+	uint32_t word_cnt_expected;
+	uint32_t buf_used;
+	uint32_t buf_free;
+	uint32_t in_buf_free;
+	uint32_t out_buf_free;
+
+	pthread_mutex_lock(&p->m_mutex);
+
+	if (address > (1 << RAB_ADDR_BW)) {
+		NT_LOG(ERR, NTHW,
+		       "%s: RAB: Illegal address: value too large %d - max %d\n",
+		       p_adapter_id_str, address, (1 << RAB_ADDR_BW));
+		res = -1;
+		goto exit_unlock_res;
+	}
+
+	if (bus_id > (1 << RAB_BUSID_BW)) {
+		NT_LOG(ERR, NTHW,
+		       "%s: RAB: Illegal bus id: value too large %d - max %d\n",
+		       p_adapter_id_str, bus_id, (1 << RAB_BUSID_BW));
+		res = -1;
+		goto exit_unlock_res;
+	}
+
+	if (word_cnt == 0) {
+		NT_LOG(ERR, NTHW,
+		       "%s: RAB: Illegal word count: value is zero (%d)\n",
+		       p_adapter_id_str, word_cnt);
+		res = -1;
+		goto exit_unlock_res;
+	}
+
+	if (word_cnt > (1 << RAB_CNT_BW)) {
+		NT_LOG(ERR, NTHW,
+		       "%s: RAB: Illegal word count: value too large %d - max %d\n",
+		       p_adapter_id_str, word_cnt, (1 << RAB_CNT_BW));
+		res = -1;
+		goto exit_unlock_res;
+	}
+
+	/* Read buffer free register */
+	nthw_rac_reg_read32(p_fpga_info, p->rac_rab_buf_free_addr, &buf_free);
+
+	in_buf_free = buf_free & p->rac_rab_buf_free_ib_free_mask;
+	out_buf_free = (buf_free & p->rac_rab_buf_free_ob_free_mask) >> 16;
+
+	/* Read buffer used register */
+	nthw_rac_reg_read32(p_fpga_info, p->rac_rab_buf_used_addr, &buf_used);
+
+	buf_used = buf_used & (p->rac_rab_buf_used_ib_used_mask |
+			     p->rac_rab_buf_used_ob_used_mask);
+
+	/*
+	 * Verify that output buffer can hold the number of words to be read,
+	 * input buffer can hold one read command
+	 * and that the input and output "used" buffer is 0
+	 */
+	if ((out_buf_free >= word_cnt && in_buf_free >= 1) && buf_used == 0) {
+		word_cnt_expected = word_cnt;
+
+#if defined(RAB_DEBUG_ECHO)
+		rab_oper_rd =
+			(RAB_READ_ECHO << RAB_OPR_LO) |
+			((word_cnt & ((1 << RAB_CNT_BW) - 1)) << RAB_CNT_LO) |
+			(bus_id << RAB_BUSID_LO) | address;
+		word_cnt_expected++;
+#else
+		rab_oper_rd = (RAB_READ << RAB_OPR_LO) | (word_cnt << RAB_CNT_LO) |
+			    (bus_id << RAB_BUSID_LO) | address;
+#endif /* RAB_DEBUG_ECHO */
+
+		nthw_rac_reg_write32(p_fpga_info, p->rac_rab_ib_data_addr,
+				   rab_oper_rd);
+
+		/* Wait until done */
+		if (nthw_rac_wait_for_rab_done(p, address, word_cnt_expected)) {
+			res = -1;
+			goto exit_unlock_res;
+		}
+
+#if defined(RAB_DEBUG_ECHO)
+		uint32_t rab_echo_oper_rd;
+
+		nthw_rac_reg_read32(p_fpga_info, p->rac_rab_ob_data_addr,
+				  &rab_echo_oper_rd);
+		if (p->mn_param_rac_rab_ob_update) {
+			nthw_rac_reg_write32(p_fpga_info, p->rac_rab_ob_data_addr,
+					   0);
+		}
+		if (rab_oper_rd != rab_echo_oper_rd) {
+			NT_LOG(ERR, NTHW,
+			       "%s: RAB: expected rab read echo oper (0x%08X) - read (0x%08X)\n",
+			       p_adapter_id_str, rab_oper_rd, rab_echo_oper_rd);
+		}
+#endif /* RAB_DEBUG_ECHO */
+
+		/* Read data from output buffer */
+		{
+			uint32_t i;
+
+			for (i = 0; i < word_cnt; i++) {
+				nthw_rac_reg_read32(p_fpga_info,
+						  p->rac_rab_ob_data_addr,
+						  p_data);
+				if (p->mn_param_rac_rab_ob_update) {
+					nthw_rac_reg_write32(p_fpga_info,
+							     p->rac_rab_ob_data_addr,
+							     0);
+				}
+				p_data++;
+			}
+		}
+
+		/* Read buffer free register */
+		nthw_rac_reg_read32(p_fpga_info, p->rac_rab_buf_free_addr,
+				  &buf_free);
+		if (buf_free & 0x80000000) {
+			/* Clear Timeout and overflow bits */
+			nthw_rac_reg_write32(p_fpga_info, p->rac_rab_buf_free_addr,
+					   0x0);
+			NT_LOG(ERR, NTHW,
+			       "%s: RAB: timeout - Access outside register - bus: %d addr: 0x%08X - inBufFree: 0x%08X, outBufFree: 0x%08X, bufUsed: 0x%08X\n",
+			       p_adapter_id_str, bus_id, address, in_buf_free,
+			       out_buf_free, buf_used);
+			res = -1;
+			goto exit_unlock_res;
+		}
+
+		res = 0;
+		goto exit_unlock_res;
+	} else {
+		NT_LOG(ERR, NTHW,
+		       "%s: RAB: Fail rab bus buffer check - bus: %d addr: 0x%08X wordcount: %d - inBufFree: 0x%08X, outBufFree: 0x%08X, bufUsed: 0x%08X\n",
+		       p_adapter_id_str, bus_id, address, word_cnt, in_buf_free,
+		       out_buf_free, buf_used);
+		res = -1;
+		goto exit_unlock_res;
+	}
+
+exit_unlock_res:
+	pthread_mutex_unlock(&p->m_mutex);
+	return res;
+}
+
+int nthw_rac_rab_flush(nthw_rac_t *p)
+{
+	const struct fpga_info_s *const p_fpga_info = p->mp_fpga->p_fpga_info;
+	const char *const p_adapter_id_str = p_fpga_info->mp_adapter_id_str;
+	uint32_t data = 0;
+	uint32_t retry;
+	int res = 0;
+
+	pthread_mutex_lock(&p->m_mutex);
+
+	/* Set the flush bit */
+	nthw_rac_reg_write32(p_fpga_info, p->rac_rab_buf_used_addr,
+			   p->rac_rab_buf_used_flush_mask);
+
+	/* Reset BUF FREE register */
+	nthw_rac_reg_write32(p_fpga_info, p->rac_rab_buf_free_addr, 0x0);
+
+	/* Wait until OB_USED and IB_USED are 0 */
+	for (retry = 0; retry < 100000; retry++) {
+		nthw_rac_reg_read32(p_fpga_info, p->rac_rab_buf_used_addr, &data);
+
+		if ((data & 0xFFFFFFFF) == p->rac_rab_buf_used_flush_mask)
+			break;
+	}
+
+	if (data != p->rac_rab_buf_used_flush_mask) {
+		NT_LOG(ERR, NTHW, "%s: RAB: Rab bus flush error.\n",
+		       p_adapter_id_str);
+		res = -1;
+	}
+
+	/* Clear flush bit when done */
+	nthw_rac_reg_write32(p_fpga_info, p->rac_rab_buf_used_addr, 0x0);
+
+	pthread_mutex_unlock(&p->m_mutex);
+	return res;
+}
diff --git a/drivers/net/ntnic/nthw/nthw_rac.h b/drivers/net/ntnic/nthw/nthw_rac.h
new file mode 100644
index 0000000000..737598d95a
--- /dev/null
+++ b/drivers/net/ntnic/nthw/nthw_rac.h
@@ -0,0 +1,161 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_RAC_H__
+#define __NTHW_RAC_H__
+
+#include "nt_util.h"
+#include "nthw_bus.h"
+
+#include <pthread.h>
+
+struct nthw_rac {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_rac;
+
+	pthread_mutex_t m_mutex;
+
+	int mn_param_rac_rab_interfaces;
+	int mn_param_rac_rab_ob_update;
+
+	nt_register_t *mp_reg_dummy0;
+	nt_register_t *mp_reg_dummy1;
+	nt_register_t *mp_reg_dummy2;
+
+	nt_register_t *mp_reg_rab_init;
+	nt_field_t *mp_fld_rab_init;
+
+	int mn_fld_rab_init_bw;
+	uint32_t mn_fld_rab_init_mask;
+
+	nt_register_t *mp_reg_dbg_ctrl;
+	nt_field_t *mp_fld_dbg_ctrl;
+
+	nt_register_t *mp_reg_dbg_data;
+	nt_field_t *mp_fld_dbg_data;
+
+	nt_register_t *mp_reg_rab_ib_data;
+	nt_field_t *mp_fld_rab_ib_data;
+
+	nt_register_t *mp_reg_rab_ob_data;
+	nt_field_t *mp_fld_rab_ob_data;
+
+	nt_register_t *mp_reg_rab_buf_free;
+	nt_field_t *mp_fld_rab_buf_free_ib_free;
+	nt_field_t *mp_fld_rab_buf_free_ib_ovf;
+	nt_field_t *mp_fld_rab_buf_free_ob_free;
+	nt_field_t *mp_fld_rab_buf_free_ob_ovf;
+	nt_field_t *mp_fld_rab_buf_free_timeout;
+
+	nt_register_t *mp_reg_rab_buf_used;
+	nt_field_t *mp_fld_rab_buf_used_ib_used;
+	nt_field_t *mp_fld_rab_buf_used_ob_used;
+	nt_field_t *mp_fld_rab_buf_used_flush;
+
+	nt_register_t *mp_reg_rab_dma_ib_lo;
+	nt_field_t *mp_fld_rab_dma_ib_lo_phy_addr;
+
+	nt_register_t *mp_reg_rab_dma_ib_hi;
+	nt_field_t *mp_fld_rab_dma_ib_hi_phy_addr;
+
+	nt_register_t *mp_reg_rab_dma_ob_hi;
+	nt_field_t *mp_fld_rab_dma_ob_hi_phy_addr;
+
+	nt_register_t *mp_reg_rab_dma_ob_lo;
+	nt_field_t *mp_fld_rab_dma_ob_lo_phy_addr;
+
+	nt_register_t *mp_reg_rab_dma_ib_wr;
+	nt_field_t *mp_fld_rab_dma_ib_wr_ptr;
+
+	nt_register_t *mp_reg_rab_dma_ib_rd;
+	nt_field_t *mp_fld_rab_dma_ib_rd_ptr;
+
+	nt_register_t *mp_reg_rab_dma_ob_wr;
+	nt_field_t *mp_fld_rab_dma_ob_wr_ptr;
+
+	nt_register_t *mp_reg_rab_nmb_rd;
+	nt_register_t *mp_reg_rab_nmb_data;
+	nt_register_t *mp_reg_rab_nmb_wr;
+	nt_register_t *mp_reg_rab_nmb_status;
+
+	uint32_t rac_rab_init_addr;
+	uint32_t rac_rab_ib_data_addr;
+	uint32_t rac_rab_ob_data_addr;
+	uint32_t rac_rab_buf_free_addr;
+	uint32_t rac_rab_buf_used_addr;
+
+	uint32_t rac_rab_dma_ib_lo_addr;
+	uint32_t rac_rab_dma_ib_hi_addr;
+	uint32_t rac_rab_dma_ob_lo_addr;
+	uint32_t rac_rab_dma_ob_hi_addr;
+	uint32_t rac_rab_dma_ib_rd_addr;
+	uint32_t rac_rab_dma_ob_wr_addr;
+	uint32_t rac_rab_dma_ib_wr_addr;
+
+	uint32_t rac_rab_buf_free_ib_free_mask;
+	uint32_t rac_rab_buf_free_ob_free_mask;
+	uint32_t rac_rab_buf_used_ib_used_mask;
+	uint32_t rac_rab_buf_used_ob_used_mask;
+	uint32_t rac_rab_buf_used_flush_mask;
+
+	uint32_t rac_rab_buf_used_ob_used_low;
+
+	uint32_t rac_nmb_rd_adr_addr;
+	uint32_t rac_nmb_data_addr;
+	uint32_t rac_nmb_wr_adr_addr;
+	uint32_t rac_nmb_status_addr;
+
+	bool m_dma_active;
+
+	struct nt_dma_s *m_dma;
+
+	volatile uint32_t *m_dma_in_buf;
+	volatile uint32_t *m_dma_out_buf;
+
+	uint16_t m_dma_out_ptr_rd;
+	uint16_t m_dma_in_ptr_wr;
+	uint32_t m_in_free;
+};
+
+typedef struct nthw_rac nthw_rac_t;
+typedef struct nthw_rac nthw_rac;
+
+struct dma_buf_ptr {
+	uint32_t size;
+	uint32_t index;
+	volatile uint32_t *base;
+};
+
+nthw_rac_t *nthw_rac_new(void);
+void nthw_rac_delete(nthw_rac_t *p);
+int nthw_rac_init(nthw_rac_t *p, nt_fpga_t *p_fpga, struct fpga_info_s *p_fpga_info);
+
+int nthw_rac_get_rab_interface_count(const nthw_rac_t *p);
+
+int nthw_rac_rab_init(nthw_rac_t *p, uint32_t rab_intf_mask);
+
+int nthw_rac_rab_setup(nthw_rac_t *p);
+
+int nthw_rac_rab_reset(nthw_rac_t *p);
+
+int nthw_rac_rab_write32(nthw_rac_t *p, uint32_t address, rab_bus_id_t bus_id,
+			uint32_t word_cnt, const uint32_t *p_data);
+int nthw_rac_rab_write32_dma(nthw_rac_t *p, uint32_t address, rab_bus_id_t bus_id,
+			    uint32_t word_cnt, const uint32_t *p_data);
+int nthw_rac_rab_read32(nthw_rac_t *p, uint32_t address, rab_bus_id_t bus_id,
+		       uint32_t word_cnt, uint32_t *p_data);
+int nthw_rac_rab_read32_dma(nthw_rac_t *p, uint32_t address, rab_bus_id_t bus_id,
+			   uint32_t word_cnt, struct dma_buf_ptr *buf_ptr);
+
+int nthw_rac_rab_flush(nthw_rac_t *p);
+
+int nthw_rac_rab_dma_begin(nthw_rac_t *p);
+int nthw_rac_rab_dma_commit(nthw_rac_t *p);
+
+void nthw_rac_reg_read32(const struct fpga_info_s *p_fpga_info, uint32_t reg_addr,
+		       uint32_t *p_data);
+void nthw_rac_reg_write32(const struct fpga_info_s *p_fpga_info, uint32_t reg_addr,
+			uint32_t p_data);
+
+#endif /* __NTHW_RAC_H__ */
diff --git a/drivers/net/ntnic/nthw/nthw_register.h b/drivers/net/ntnic/nthw/nthw_register.h
index 5cdbd9fc5d..4fe3496b9f 100644
--- a/drivers/net/ntnic/nthw/nthw_register.h
+++ b/drivers/net/ntnic/nthw/nthw_register.h
@@ -10,6 +10,8 @@
 #include <stdbool.h>
 #include <inttypes.h>
 
+#include "nthw_fpga_model.h"
+
 #include "fpga_model.h"
 
 #include "nthw_fpga_modules_defs.h"
diff --git a/drivers/net/ntnic/nthw/nthw_stat.c b/drivers/net/ntnic/nthw/nthw_stat.c
new file mode 100644
index 0000000000..fbecbc2dba
--- /dev/null
+++ b/drivers/net/ntnic/nthw/nthw_stat.c
@@ -0,0 +1,266 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "nt_util.h"
+#include "ntlog.h"
+
+#include "nthw_drv.h"
+#include "nthw_register.h"
+
+#include "nthw_stat.h"
+
+#include <malloc.h>
+
+nthw_stat_t *nthw_stat_new(void)
+{
+	nthw_stat_t *p = malloc(sizeof(nthw_stat_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_stat_t));
+	return p;
+}
+
+void nthw_stat_delete(nthw_stat_t *p)
+{
+	if (p)
+		free(p);
+}
+
+int nthw_stat_init(nthw_stat_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;
+	uint64_t n_module_version_packed64 = -1;
+	nt_module_t *mod = fpga_query_module(p_fpga, MOD_STA, n_instance);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: STAT %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_stat = mod;
+
+	n_module_version_packed64 = module_get_version_packed64(p->mp_mod_stat);
+	NT_LOG(DBG, NTHW, "%s: STAT %d: version=0x%08lX\n", p_adapter_id_str,
+	       p->mn_instance, n_module_version_packed64);
+
+	{
+		nt_register_t *p_reg;
+		/* STA_CFG register */
+		p_reg = module_get_register(p->mp_mod_stat, STA_CFG);
+		p->mp_fld_dma_ena = register_get_field(p_reg, STA_CFG_DMA_ENA);
+		p->mp_fld_cnt_clear = register_get_field(p_reg, STA_CFG_CNT_CLEAR);
+
+		/* CFG: fields NOT available from v. 3 */
+		p->mp_fld_tx_disable =
+			register_query_field(p_reg, STA_CFG_TX_DISABLE);
+		p->mp_fld_cnt_freeze = register_query_field(p_reg, STA_CFG_CNT_FRZ);
+
+		/* STA_STATUS register */
+		p_reg = module_get_register(p->mp_mod_stat, STA_STATUS);
+		p->mp_fld_stat_toggle_missed =
+			register_get_field(p_reg, STA_STATUS_STAT_TOGGLE_MISSED);
+
+		/* HOST_ADR registers */
+		p_reg = module_get_register(p->mp_mod_stat, STA_HOST_ADR_LSB);
+		p->mp_fld_dma_lsb = register_get_field(p_reg, STA_HOST_ADR_LSB_LSB);
+
+		p_reg = module_get_register(p->mp_mod_stat, STA_HOST_ADR_MSB);
+		p->mp_fld_dma_msb = register_get_field(p_reg, STA_HOST_ADR_MSB_MSB);
+	}
+
+	/* Params */
+	p->mb_is_vswitch = p_fpga->p_fpga_info->profile == FPGA_INFO_PROFILE_VSWITCH;
+
+	p->m_nb_nim_ports = fpga_get_product_param(p_fpga, NT_NIMS, 0);
+	p->m_nb_phy_ports = fpga_get_product_param(p_fpga, NT_PHY_PORTS, 0);
+
+	p->m_nb_rx_ports =
+		fpga_get_product_param(p_fpga, NT_STA_RX_PORTS, -1); /* VSWITCH */
+	if (p->m_nb_rx_ports == -1) {
+		p->m_nb_rx_ports = fpga_get_product_param(p_fpga, NT_RX_PORTS,
+							  -1); /* non-VSWITCH */
+		if (p->m_nb_rx_ports == -1) {
+			p->m_nb_rx_ports = fpga_get_product_param(p_fpga,
+								  NT_PORTS,
+								  0); /* non-VSWITCH */
+		}
+	}
+
+	p->m_nb_tx_ports = fpga_get_product_param(p_fpga, NT_TX_PORTS, 0);
+	p->m_rx_port_replicate =
+		fpga_get_product_param(p_fpga, NT_RX_PORT_REPLICATE, 0);
+
+	p->m_nb_color_counters = fpga_get_product_param(p_fpga, NT_STA_COLORS, 64) *
+			      2; /* VSWITCH */
+	if (p->m_nb_color_counters == 0) {
+		p->m_nb_color_counters =
+			fpga_get_product_param(p_fpga, NT_CAT_FUNCS, 0) *
+			2; /* non-VSWITCH */
+	}
+
+	p->m_nb_rx_host_buffers = fpga_get_product_param(p_fpga, NT_QUEUES, 0);
+	p->m_nb_tx_host_buffers = p->m_nb_rx_host_buffers;
+
+	p->m_dbs_present = fpga_get_product_param(p_fpga, NT_DBS_PRESENT, 0);
+
+	p->m_nb_rx_hb_counters =
+		(p->m_nb_rx_host_buffers *
+		 (6 + 2 * (n_module_version_packed64 >= VERSION_PACKED64(0, 6) ?
+			   p->m_dbs_present :
+			   0)));
+
+	p->m_nb_tx_hb_counters = 0;
+
+	p->m_nb_rx_port_counters =
+		42 + 2 * (n_module_version_packed64 >= VERSION_PACKED64(0, 6) ?
+			  p->m_dbs_present :
+			  0);
+	p->m_nb_tx_port_counters = 0;
+
+	p->m_nb_counters =
+		p->m_nb_color_counters + p->m_nb_rx_hb_counters + p->m_nb_tx_hb_counters;
+
+	p->mn_stat_layout_version = 0;
+	if (n_module_version_packed64 >= VERSION_PACKED64(0, 8)) {
+		p->mn_stat_layout_version = 6;
+	} else if (n_module_version_packed64 >= VERSION_PACKED64(0, 6)) {
+		p->mn_stat_layout_version = 5;
+	} else if (n_module_version_packed64 >= VERSION_PACKED64(0, 4)) {
+		p->mn_stat_layout_version = 4;
+	} else if (n_module_version_packed64 >= VERSION_PACKED64(0, 3)) {
+		p->mn_stat_layout_version = 3;
+	} else if (n_module_version_packed64 >= VERSION_PACKED64(0, 2)) {
+		p->mn_stat_layout_version = 2;
+	} else if (n_module_version_packed64 > VERSION_PACKED64(0, 0)) {
+		p->mn_stat_layout_version = 1;
+	} else {
+		p->mn_stat_layout_version = 0;
+		NT_LOG(ERR, NTHW,
+		       "%s: unknown module_version 0x%08lX layout=%d\n",
+		       p_adapter_id_str, n_module_version_packed64,
+		       p->mn_stat_layout_version);
+	}
+	assert(p->mn_stat_layout_version);
+
+	/* STA module 0.2+ adds IPF counters per port (Rx feature) */
+	if (n_module_version_packed64 >= VERSION_PACKED64(0, 2))
+		p->m_nb_rx_port_counters += 6;
+
+	/* STA module 0.3+ adds TX stats */
+	if (n_module_version_packed64 >= VERSION_PACKED64(0, 3) ||
+			p->m_nb_tx_ports >= 1)
+		p->mb_has_tx_stats = true;
+
+	/* STA module 0.3+ adds TX stat counters */
+	if (n_module_version_packed64 >= VERSION_PACKED64(0, 3))
+		p->m_nb_tx_port_counters += 22;
+
+	/* STA module 0.4+ adds TX drop event counter */
+	if (n_module_version_packed64 >= VERSION_PACKED64(0, 4))
+		p->m_nb_tx_port_counters += 1; /* TX drop event counter */
+
+	/*
+	 * STA module 0.6+ adds pkt filter drop octets+pkts, retransmit and
+	 * duplicate counters
+	 */
+	if (n_module_version_packed64 >= VERSION_PACKED64(0, 6)) {
+		p->m_nb_rx_port_counters += 4;
+		p->m_nb_tx_port_counters += 1;
+	}
+
+	if (p->mb_is_vswitch) {
+		p->m_nb_rx_port_counters = 5;
+		p->m_nb_tx_port_counters = 5;
+	}
+
+	p->m_nb_counters += (p->m_nb_rx_ports * p->m_nb_rx_port_counters);
+
+	if (p->mb_has_tx_stats)
+		p->m_nb_counters += (p->m_nb_tx_ports * p->m_nb_tx_port_counters);
+
+	/* Output params (debug) */
+	NT_LOG(DBG, NTHW,
+	       "%s: nims=%d rxports=%d txports=%d rxrepl=%d colors=%d queues=%d\n",
+	       p_adapter_id_str, p->m_nb_nim_ports, p->m_nb_rx_ports, p->m_nb_tx_ports,
+	       p->m_rx_port_replicate, p->m_nb_color_counters, p->m_nb_rx_host_buffers);
+	NT_LOG(DBG, NTHW,
+	       "%s: hbs=%d hbcounters=%d rxcounters=%d txcounters=%d\n",
+	       p_adapter_id_str, p->m_nb_rx_host_buffers, p->m_nb_rx_hb_counters,
+	       p->m_nb_rx_port_counters, p->m_nb_tx_port_counters);
+	NT_LOG(DBG, NTHW, "%s: layout=%d\n", p_adapter_id_str,
+	       p->mn_stat_layout_version);
+	NT_LOG(DBG, NTHW, "%s: counters=%d (0x%X)\n", p_adapter_id_str,
+	       p->m_nb_counters, p->m_nb_counters);
+	NT_LOG(DBG, NTHW, "%s: vswitch=%d\n", p_adapter_id_str, p->mb_is_vswitch);
+
+	/* Init */
+	if (p->mp_fld_tx_disable)
+		field_set_flush(p->mp_fld_tx_disable);
+
+	field_update_register(p->mp_fld_cnt_clear);
+	field_set_flush(p->mp_fld_cnt_clear);
+	field_clr_flush(p->mp_fld_cnt_clear);
+
+	field_update_register(p->mp_fld_stat_toggle_missed);
+	field_set_flush(p->mp_fld_stat_toggle_missed);
+
+	field_update_register(p->mp_fld_dma_ena);
+	field_clr_flush(p->mp_fld_dma_ena);
+	field_update_register(p->mp_fld_dma_ena);
+
+	return 0;
+}
+
+int nthw_stat_set_dma_address(nthw_stat_t *p, uint64_t stat_dma_physical,
+			   uint32_t *p_stat_dma_virtual)
+{
+	assert(p_stat_dma_virtual);
+	p->mp_timestamp = NULL;
+
+	p->m_stat_dma_physical = stat_dma_physical;
+	p->mp_stat_dma_virtual = p_stat_dma_virtual;
+
+	memset(p->mp_stat_dma_virtual, 0, (p->m_nb_counters * sizeof(uint32_t)));
+
+	field_set_val_flush32(p->mp_fld_dma_msb,
+			    (uint32_t)((p->m_stat_dma_physical >> 32) &
+				       0xffffffff));
+	field_set_val_flush32(p->mp_fld_dma_lsb,
+			    (uint32_t)(p->m_stat_dma_physical & 0xffffffff));
+
+	p->mp_timestamp = (uint64_t *)(p->mp_stat_dma_virtual + p->m_nb_counters);
+	NT_LOG(DBG, NTHW,
+	       "%s: statDmaPhysical=%" PRIX64 " p_stat_dma_virtual=%" PRIX64
+	       " mp_timestamp=%" PRIX64 "\n",
+	       __func__, p->m_stat_dma_physical, p->mp_stat_dma_virtual,
+	       p->mp_timestamp);
+	if (p->mb_is_vswitch)
+		*p->mp_timestamp = NT_OS_GET_TIME_NS();
+
+	else
+		*p->mp_timestamp = (uint64_t)(int64_t)-1;
+	return 0;
+}
+
+int nthw_stat_trigger(nthw_stat_t *p)
+{
+	int n_toggle_miss = field_get_updated(p->mp_fld_stat_toggle_missed);
+
+	if (n_toggle_miss)
+		field_set_flush(p->mp_fld_stat_toggle_missed);
+
+	if (p->mp_timestamp)
+		*p->mp_timestamp = -1; /* Clear old ts */
+
+	field_update_register(p->mp_fld_dma_ena);
+	field_set_flush(p->mp_fld_dma_ena);
+
+	return 0;
+}
diff --git a/drivers/net/ntnic/nthw/nthw_stat.h b/drivers/net/ntnic/nthw/nthw_stat.h
new file mode 100644
index 0000000000..7bce7ecd15
--- /dev/null
+++ b/drivers/net/ntnic/nthw/nthw_stat.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_STAT_H__
+#define __NTHW_STAT_H__
+
+struct nthw_stat {
+	nt_fpga_t *mp_fpga;
+	nt_module_t *mp_mod_stat;
+	int mn_instance;
+
+	int mn_stat_layout_version;
+
+	bool mb_is_vswitch;
+	bool mb_has_tx_stats;
+
+	int m_nb_phy_ports;
+	int m_nb_nim_ports;
+
+	int m_nb_rx_ports;
+	int m_nb_tx_ports;
+
+	int m_nb_rx_host_buffers;
+	int m_nb_tx_host_buffers;
+
+	int m_dbs_present;
+
+	int m_rx_port_replicate;
+
+	int m_nb_color_counters;
+
+	int m_nb_rx_hb_counters;
+	int m_nb_tx_hb_counters;
+
+	int m_nb_rx_port_counters;
+	int m_nb_tx_port_counters;
+
+	int m_nb_counters;
+
+	nt_field_t *mp_fld_dma_ena;
+	nt_field_t *mp_fld_cnt_clear;
+
+	nt_field_t *mp_fld_tx_disable;
+
+	nt_field_t *mp_fld_cnt_freeze;
+
+	nt_field_t *mp_fld_stat_toggle_missed;
+
+	nt_field_t *mp_fld_dma_lsb;
+	nt_field_t *mp_fld_dma_msb;
+
+	uint64_t m_stat_dma_physical;
+	uint32_t *mp_stat_dma_virtual;
+
+	uint64_t last_ts;
+
+	uint64_t *mp_timestamp;
+};
+
+typedef struct nthw_stat nthw_stat_t;
+typedef struct nthw_stat nthw_stat;
+
+nthw_stat_t *nthw_stat_new(void);
+int nthw_stat_init(nthw_stat_t *p, nt_fpga_t *p_fpga, int n_instance);
+void nthw_stat_delete(nthw_stat_t *p);
+
+int nthw_stat_set_dma_address(nthw_stat_t *p, uint64_t stat_dma_physical,
+			   uint32_t *p_stat_dma_virtual);
+int nthw_stat_trigger(nthw_stat_t *p);
+
+#endif /* __NTHW_STAT_H__ */
diff --git a/drivers/net/ntnic/ntlog/include/ntlog.h b/drivers/net/ntnic/ntlog/include/ntlog.h
new file mode 100644
index 0000000000..81bc014d66
--- /dev/null
+++ b/drivers/net/ntnic/ntlog/include/ntlog.h
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NTOSS_SYSTEM_NTLOG_H
+#define NTOSS_SYSTEM_NTLOG_H
+
+#include <stdarg.h>
+#include <stdint.h>
+
+#ifndef NT_LOG_MODULE_PREFIX
+
+/* DPDK modules */
+#define NT_LOG_MODULE_EAL 0
+#define NT_LOG_MODULE_MALLOC 1
+#define NT_LOG_MODULE_RING 2
+#define NT_LOG_MODULE_MEMPOOL 3
+#define NT_LOG_MODULE_TIMER 4
+#define NT_LOG_MODULE_PMD 5
+#define NT_LOG_MODULE_HASH 6
+#define NT_LOG_MODULE_LPM 7
+#define NT_LOG_MODULE_KNI 8
+#define NT_LOG_MODULE_ACL 9
+#define NT_LOG_MODULE_POWER 10
+#define NT_LOG_MODULE_METER 11
+#define NT_LOG_MODULE_SCHED 12
+#define NT_LOG_MODULE_PORT 13
+#define NT_LOG_MODULE_TABLE 14
+#define NT_LOG_MODULE_PIPELINE 15
+#define NT_LOG_MODULE_MBUF 16
+#define NT_LOG_MODULE_CRYPTODEV 17
+#define NT_LOG_MODULE_EFD 18
+#define NT_LOG_MODULE_EVENTDEV 19
+#define NT_LOG_MODULE_GSO 20
+#define NT_LOG_MODULE_USER1 24
+#define NT_LOG_MODULE_USER2 25
+#define NT_LOG_MODULE_USER3 26
+#define NT_LOG_MODULE_USER4 27
+#define NT_LOG_MODULE_USER5 28
+#define NT_LOG_MODULE_USER6 29
+#define NT_LOG_MODULE_USER7 30
+#define NT_LOG_MODULE_USER8 31
+
+/* NT modules */
+#define NT_LOG_MODULE_GENERAL 10000 /* Should always be a first (smallest) */
+#define NT_LOG_MODULE_NTHW 10001
+#define NT_LOG_MODULE_FILTER 10002
+#define NT_LOG_MODULE_VDPA 10003
+#define NT_LOG_MODULE_FPGA 10004
+#define NT_LOG_MODULE_NTCONNECT 10005
+#define NT_LOG_MODULE_ETHDEV 10006
+#define NT_LOG_MODULE_END 10007 /* Mark for the range end of NT_LOG */
+
+#define NT_LOG_MODULE_COUNT (NT_LOG_MODULE_END - NT_LOG_MODULE_GENERAL)
+#define NT_LOG_MODULE_INDEX(module) ((module) - NT_LOG_MODULE_GENERAL)
+#define NT_LOG_MODULE_PREFIX(type) NT_LOG_MODULE_##type
+
+#endif
+
+#ifndef NT_LOG_ENABLE
+#define NT_LOG_ENABLE 1
+#endif
+
+#if defined NT_LOG_ENABLE && NT_LOG_ENABLE > 0
+#ifndef NT_LOG_ENABLE_ERR
+#define NT_LOG_ENABLE_ERR 1
+#endif
+#ifndef NT_LOG_ENABLE_WRN
+#define NT_LOG_ENABLE_WRN 1
+#endif
+#ifndef NT_LOG_ENABLE_INF
+#define NT_LOG_ENABLE_INF 1
+#endif
+#ifndef NT_LOG_ENABLE_DBG
+#define NT_LOG_ENABLE_DBG 1
+#endif
+#ifndef NT_LOG_ENABLE_DB1
+#define NT_LOG_ENABLE_DB1 0
+#endif
+#ifndef NT_LOG_ENABLE_DB2
+#define NT_LOG_ENABLE_DB2 0
+#endif
+#endif
+
+#if defined NT_LOG_ENABLE_ERR && NT_LOG_ENABLE_ERR > 0
+#define NT_LOG_NT_LOG_ERR(...) nt_log(__VA_ARGS__)
+#else
+#define NT_LOG_NT_LOG_ERR(...)
+#endif
+
+#if defined NT_LOG_ENABLE_WRN && NT_LOG_ENABLE_WRN > 0
+#define NT_LOG_NT_LOG_WRN(...) nt_log(__VA_ARGS__)
+#else
+#define NT_LOG_NT_LOG_WRN(...)
+#endif
+
+#if defined NT_LOG_ENABLE_INF && NT_LOG_ENABLE_INF > 0
+#define NT_LOG_NT_LOG_INF(...) nt_log(__VA_ARGS__)
+#else
+#define NT_LOG_NT_LOG_INF(...)
+#endif
+
+#if defined NT_LOG_ENABLE_DBG && NT_LOG_ENABLE_DBG > 0
+#define NT_LOG_NT_LOG_DBG(...) nt_log(__VA_ARGS__)
+#else
+#define NT_LOG_NT_LOG_DBG(...)
+#endif
+
+#if defined NT_LOG_ENABLE_DB1 && NT_LOG_ENABLE_DB1 > 0
+#define NT_LOG_NT_LOG_DB1(...) nt_log(__VA_ARGS__)
+#else
+#define NT_LOG_NT_LOG_DB1(...)
+#endif
+
+#if defined NT_LOG_ENABLE_DB2 && NT_LOG_ENABLE_DB2 > 0
+#define NT_LOG_NT_LOG_DB2(...) nt_log(__VA_ARGS__)
+#else
+#define NT_LOG_NT_LOG_DB2(...)
+#endif
+
+#define NT_LOG(level, module, ...)                                          \
+	NT_LOG_NT_LOG_##level(NT_LOG_##level, NT_LOG_MODULE_PREFIX(module), \
+			      #module ": " #level ": " __VA_ARGS__)
+
+enum nt_log_level {
+	NT_LOG_ERR = 0x001,
+	NT_LOG_WRN = 0x002,
+	NT_LOG_INF = 0x004,
+	NT_LOG_DBG = 0x008,
+	NT_LOG_DB1 = 0x010,
+	NT_LOG_DB2 = 0x020,
+};
+
+struct nt_log_impl {
+	int (*init)(void);
+	int (*log)(enum nt_log_level level, uint32_t module, const char *format,
+		   va_list args);
+	int (*is_debug)(uint32_t module);
+};
+
+int nt_log_init(struct nt_log_impl *impl);
+
+int nt_log(enum nt_log_level level, uint32_t module, const char *format, ...);
+
+/* Returns 1 if RTE_DEBUG, 0 if lower log level, -1 if incorrect module */
+int nt_log_is_debug(uint32_t module);
+
+/*
+ * nt log helper functions
+ * to create a string for NT_LOG usage to output a one-liner log
+ * to use when one single function call to NT_LOG is not optimal - that is
+ * you do not know the number of parameters at programming time or it is variable
+ */
+char *ntlog_helper_str_alloc(const char *sinit);
+
+void ntlog_helper_str_reset(char *s, const char *sinit);
+
+void ntlog_helper_str_add(char *s, const char *format, ...);
+
+void ntlog_helper_str_free(char *s);
+
+#endif /* NTOSS_SYSTEM_NTLOG_H */
diff --git a/drivers/net/ntnic/ntlog/ntlog.c b/drivers/net/ntnic/ntlog/ntlog.c
new file mode 100644
index 0000000000..b9109754ac
--- /dev/null
+++ b/drivers/net/ntnic/ntlog/ntlog.c
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "ntlog.h"
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#include <rte_string_fns.h>
+
+#define NTLOG_HELPER_STR_SIZE_MAX (1024)
+
+static struct nt_log_impl *user_impl;
+
+int nt_log_init(struct nt_log_impl *impl)
+{
+	user_impl = impl;
+	return user_impl->init();
+}
+
+static char *last_trailing_eol(char *s)
+{
+	int i = strlen(s) - 1;
+	/* Skip spaces */
+	while (i > 0 && s[i] == ' ')
+		--i;
+	if (s[i] != '\n')
+		return NULL;
+	/*
+	 * Find the last trailing EOL "hello_world\n\n\n"
+	 *                                         ^
+	 */
+	while (i > 1 && s[i] == '\n' && s[i - 1] == '\n')
+		--i;
+	return &s[i];
+}
+
+/* Always terminates the NT_LOG statement with a !!!single!!! EOL. */
+int nt_log(enum nt_log_level level, uint32_t module, const char *format, ...)
+{
+	int rv = -1;
+	va_list args;
+
+	if (user_impl == NULL)
+		return rv;
+
+	char *actual_format = ntlog_helper_str_alloc(format);
+	char *eol = last_trailing_eol(actual_format);
+
+	if (!eol) { /* If log line is not terminated with '\n' we add it. */
+		strncat(actual_format, "\n",
+			NTLOG_HELPER_STR_SIZE_MAX - strlen(actual_format));
+	} else {   /* If multiple trailing EOLs, then keep just one of them. */
+		*(eol + 1) = '\0';
+	}
+
+	va_start(args, format);
+	rv = user_impl->log(level, module, actual_format, args);
+	va_end(args);
+
+	ntlog_helper_str_free(actual_format);
+	return rv;
+}
+
+int nt_log_is_debug(uint32_t module)
+{
+	return user_impl->is_debug(module);
+}
+
+char *ntlog_helper_str_alloc(const char *sinit)
+{
+	char *s = malloc(NTLOG_HELPER_STR_SIZE_MAX);
+
+	if (!s)
+		return NULL;
+	if (sinit)
+		rte_strscpy(s, sinit, NTLOG_HELPER_STR_SIZE_MAX);
+	else
+		s[0] = '\0';
+	return s;
+}
+
+void ntlog_helper_str_reset(char *s, const char *sinit)
+{
+	if (s) {
+		if (sinit)
+			rte_strscpy(s, sinit, NTLOG_HELPER_STR_SIZE_MAX);
+		else
+			s[0] = '\0';
+	}
+}
+
+void ntlog_helper_str_add(char *s, const char *format, ...)
+{
+	if (!s)
+		return;
+	va_list args;
+
+	va_start(args, format);
+	int len = strlen(s);
+
+	vsnprintf(&s[len], (NTLOG_HELPER_STR_SIZE_MAX - 1 - len), format, args);
+	va_end(args);
+}
+
+void ntlog_helper_str_free(char *s)
+{
+	free(s);
+}
diff --git a/drivers/net/ntnic/ntutil/include/nt_util.h b/drivers/net/ntnic/ntutil/include/nt_util.h
new file mode 100644
index 0000000000..3850ccd934
--- /dev/null
+++ b/drivers/net/ntnic/ntutil/include/nt_util.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NTOSS_SYSTEM_NT_UTIL_H
+#define NTOSS_SYSTEM_NT_UTIL_H
+
+#include <rte_cycles.h>
+#include <rte_string_fns.h>
+
+#define _unused __rte_unused
+
+#define PCIIDENT_TO_DOMAIN(pci_ident) \
+	((uint16_t)(((unsigned int)(pci_ident) >> 16) & 0xFFFFU))
+#define PCIIDENT_TO_BUSNR(pci_ident) \
+	((uint8_t)(((unsigned int)(pci_ident) >> 8) & 0xFFU))
+#define PCIIDENT_TO_DEVNR(pci_ident) \
+	((uint8_t)(((unsigned int)(pci_ident) >> 3) & 0x1FU))
+#define PCIIDENT_TO_FUNCNR(pci_ident) \
+	((uint8_t)(((unsigned int)(pci_ident) >> 0) & 0x7U))
+
+#define PCIIDENT_PRINT_STR "%04x:%02x:%02x.%x"
+#define BDF_TO_PCIIDENT(dom, bus, dev, fnc) \
+	(((dom) << 16) | ((bus) << 8) | ((dev) << 3) | (fnc))
+
+/* ALIGN: Align x to a boundary */
+#define ALIGN(x, a)                           \
+	({                                    \
+		__typeof__(x) _a = (a);       \
+		((x) + (_a - 1)) & ~(_a - 1); \
+	})
+
+/* PALIGN: Align pointer p to a boundary */
+#define PALIGN(p, a) ((__typeof__(p))ALIGN((unsigned long)(p), (a)))
+
+/* Allocation size matching minimum alignment of specified size */
+#define ALIGN_SIZE(_size_) (1 << rte_log2_u64(_size_))
+
+#define NT_OS_WAIT_USEC(x)    \
+	rte_delay_us_sleep( \
+		x) /* uses usleep which schedules out the calling thread */
+/* spins in a waiting loop calling pause asm instruction uses RDTSC - precise wait */
+#define NT_OS_WAIT_USEC_POLL(x) \
+	rte_delay_us(        \
+		x)
+
+#define NT_OS_GET_TIME_US() \
+	(rte_get_timer_cycles() / (rte_get_timer_hz() / 1000 / 1000))
+#define NT_OS_GET_TIME_NS() \
+	(rte_get_timer_cycles() * 10 / (rte_get_timer_hz() / 1000 / 1000 / 100))
+#define NT_OS_GET_TIME_MONOTONIC_COUNTER() (rte_get_timer_cycles())
+
+struct nt_dma_s {
+	uint64_t iova;
+	uint64_t addr;
+	uint64_t size;
+};
+
+struct nt_dma_s *nt_dma_alloc(uint64_t size, uint64_t align, int numa);
+void nt_dma_free(struct nt_dma_s *vfio_addr);
+
+struct nt_util_vfio_impl {
+	int (*vfio_dma_map)(int vf_num, void *virt_addr, uint64_t *iova_addr,
+			    uint64_t size);
+	int (*vfio_dma_unmap)(int vf_num, void *virt_addr, uint64_t iova_addr,
+			      uint64_t size);
+};
+
+void nt_util_vfio_init(struct nt_util_vfio_impl *impl);
+
+#endif /* NTOSS_SYSTEM_NT_UTIL_H */
diff --git a/drivers/net/ntnic/ntutil/nt_util.c b/drivers/net/ntnic/ntutil/nt_util.c
new file mode 100644
index 0000000000..8f5812bf8b
--- /dev/null
+++ b/drivers/net/ntnic/ntutil/nt_util.c
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include <rte_malloc.h>
+
+#include "ntlog.h"
+#include "nt_util.h"
+
+static struct nt_util_vfio_impl vfio_cb;
+
+void nt_util_vfio_init(struct nt_util_vfio_impl *impl)
+{
+	vfio_cb = *impl;
+}
+
+struct nt_dma_s *nt_dma_alloc(uint64_t size, uint64_t align, int numa)
+{
+	int res;
+	struct nt_dma_s *vfio_addr;
+
+	vfio_addr = rte_malloc(NULL, sizeof(struct nt_dma_s), 0);
+	if (!vfio_addr) {
+		NT_LOG(ERR, GENERAL, "VFIO rte_malloc failed\n");
+		return NULL;
+	}
+	void *addr = rte_malloc_socket(NULL, size, align, numa);
+
+	if (!addr) {
+		rte_free(vfio_addr);
+		NT_LOG(ERR, GENERAL, "VFIO rte_malloc_socket failed\n");
+		return NULL;
+	}
+	res = vfio_cb.vfio_dma_map(0, addr, &vfio_addr->iova,
+				   ALIGN_SIZE(size));
+	if (res != 0) {
+		rte_free(addr);
+		rte_free(vfio_addr);
+		NT_LOG(ERR, GENERAL, "VFIO nt_dma_map failed\n");
+		return NULL;
+	}
+
+	vfio_addr->addr = (uint64_t)addr;
+	vfio_addr->size = ALIGN_SIZE(size);
+
+	NT_LOG(DBG, GENERAL,
+	       "VFIO DMA alloc addr=%" PRIX64 ", iova=%" PRIX64
+	       ", size=%u, align=0x%X\n",
+	       vfio_addr->addr, vfio_addr->iova, vfio_addr->size, align);
+
+	return vfio_addr;
+}
+
+void nt_dma_free(struct nt_dma_s *vfio_addr)
+{
+	NT_LOG(DBG, GENERAL,
+	       "VFIO DMA free addr=%" PRIX64 ", iova=%" PRIX64 ", size=%u\n",
+	       vfio_addr->addr, vfio_addr->iova, vfio_addr->size);
+
+	int res = vfio_cb.vfio_dma_unmap(0, (void *)(vfio_addr->addr),
+					 vfio_addr->iova, vfio_addr->size);
+	if (res != 0) {
+		NT_LOG(WRN, GENERAL,
+		       "VFIO DMA free FAILED addr=%" PRIX64 ", iova=%" PRIX64
+		       ", size=%u\n",
+		       vfio_addr->addr, vfio_addr->iova, vfio_addr->size);
+	}
+	rte_free((void *)(vfio_addr->addr));
+	rte_free(vfio_addr);
+}
