All of lore.kernel.org
 help / color / mirror / Atom feed
From: Serhii Iliushyk <sil-plv@napatech.com>
To: dev@dpdk.org
Cc: mko-plv@napatech.com, sil-plv@napatech.com, ckm@napatech.com,
	stephen@networkplumber.org,
	Danylo Vodopianov <dvo-plv@napatech.com>
Subject: [PATCH v1 25/32] net/ntnic: add SPI v3 support for FPGA
Date: Thu, 20 Feb 2025 23:03:49 +0100	[thread overview]
Message-ID: <20250220220406.3925597-26-sil-plv@napatech.com> (raw)
In-Reply-To: <20250220220406.3925597-1-sil-plv@napatech.com>

From: Danylo Vodopianov <dvo-plv@napatech.com>

- Implement nthw_spi_v3 module with initialization, transfer, and
utility functions.
- Add nthw_spim and nthw_spis modules for SPI primary and secondary
interfaces.
- Include SPI v3 header files and update meson build configuration.
- Implement AVR probe function to retrieve and log AVR information.

Signed-off-by: Danylo Vodopianov <dvo-plv@napatech.com>
---
 drivers/net/ntnic/meson.build                 |   3 +
 .../net/ntnic/nthw/core/include/nthw_fpga.h   |   2 +
 .../net/ntnic/nthw/core/include/nthw_spi_v3.h | 107 +++++
 .../net/ntnic/nthw/core/include/nthw_spim.h   |  58 +++
 .../net/ntnic/nthw/core/include/nthw_spis.h   |  63 +++
 .../nt400dxx/reset/nthw_fpga_rst_nt400dxx.c   |   2 +
 drivers/net/ntnic/nthw/core/nthw_fpga.c       | 444 ++++++++++++++++++
 drivers/net/ntnic/nthw/core/nthw_spi_v3.c     | 358 ++++++++++++++
 drivers/net/ntnic/nthw/core/nthw_spim.c       | 113 +++++
 drivers/net/ntnic/nthw/core/nthw_spis.c       | 121 +++++
 10 files changed, 1271 insertions(+)
 create mode 100644 drivers/net/ntnic/nthw/core/include/nthw_spi_v3.h
 create mode 100644 drivers/net/ntnic/nthw/core/include/nthw_spim.h
 create mode 100644 drivers/net/ntnic/nthw/core/include/nthw_spis.h
 create mode 100644 drivers/net/ntnic/nthw/core/nthw_spi_v3.c
 create mode 100644 drivers/net/ntnic/nthw/core/nthw_spim.c
 create mode 100644 drivers/net/ntnic/nthw/core/nthw_spis.c

diff --git a/drivers/net/ntnic/meson.build b/drivers/net/ntnic/meson.build
index 9885d4efbf..7e326a3e1d 100644
--- a/drivers/net/ntnic/meson.build
+++ b/drivers/net/ntnic/meson.build
@@ -69,6 +69,9 @@ sources = files(
         'nthw/core/nthw_rmc.c',
         'nthw/core/nthw_sdc.c',
         'nthw/core/nthw_si5340.c',
+        'nthw/core/nthw_spim.c',
+        'nthw/core/nthw_spis.c',
+        'nthw/core/nthw_spi_v3.c',
         'nthw/stat/nthw_stat.c',
         'nthw/flow_api/flow_api.c',
         'nthw/flow_api/flow_group.c',
diff --git a/drivers/net/ntnic/nthw/core/include/nthw_fpga.h b/drivers/net/ntnic/nthw/core/include/nthw_fpga.h
index 8b1d548a25..418aea8277 100644
--- a/drivers/net/ntnic/nthw/core/include/nthw_fpga.h
+++ b/drivers/net/ntnic/nthw/core/include/nthw_fpga.h
@@ -18,6 +18,8 @@ int nthw_fpga_shutdown(struct fpga_info_s *p_fpga_info);
 
 int nthw_fpga_get_param_info(struct fpga_info_s *p_fpga_info, nthw_fpga_t *p_fpga);
 
+int nthw_fpga_avr_probe(nthw_fpga_t *p_fpga, const int n_instance_no);
+
 int nthw_fpga_iic_scan(nthw_fpga_t *p_fpga, const int n_instance_no_begin,
 	const int n_instance_no_end);
 
diff --git a/drivers/net/ntnic/nthw/core/include/nthw_spi_v3.h b/drivers/net/ntnic/nthw/core/include/nthw_spi_v3.h
new file mode 100644
index 0000000000..66b1f7f45d
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/include/nthw_spi_v3.h
@@ -0,0 +1,107 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NT4GA_SPI_V3__
+#define __NT4GA_SPI_V3__
+
+#include "nthw_spis.h"
+#include "nthw_spim.h"
+
+/* 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, nthw_fpga_t *p_fpga, int n_instance_no);
+
+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/include/nthw_spim.h b/drivers/net/ntnic/nthw/core/include/nthw_spim.h
new file mode 100644
index 0000000000..70a49ab627
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/include/nthw_spim.h
@@ -0,0 +1,58 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_SPIM_H__
+#define __NTHW_SPIM_H__
+
+#include "nthw_fpga.h"
+
+struct nthw_spim {
+	nthw_fpga_t *mp_fpga;
+	nthw_module_t *mp_mod_spim;
+	int mn_instance;
+
+	nthw_register_t *mp_reg_srr;
+	nthw_field_t *mp_fld_srr_rst;
+
+	nthw_register_t *mp_reg_cr;
+	nthw_field_t *mp_fld_cr_loop;
+	nthw_field_t *mp_fld_cr_en;
+	nthw_field_t *mp_fld_cr_txrst;
+	nthw_field_t *mp_fld_cr_rxrst;
+
+	nthw_register_t *mp_reg_sr;
+	nthw_field_t *mp_fld_sr_done;
+	nthw_field_t *mp_fld_sr_txempty;
+	nthw_field_t *mp_fld_sr_rxempty;
+	nthw_field_t *mp_fld_sr_txfull;
+	nthw_field_t *mp_fld_sr_rxfull;
+	nthw_field_t *mp_fld_sr_txlvl;
+	nthw_field_t *mp_fld_sr_rxlvl;
+
+	nthw_register_t *mp_reg_dtr;
+	nthw_field_t *mp_fld_dtr_dtr;
+
+	nthw_register_t *mp_reg_drr;
+	nthw_field_t *mp_fld_drr_drr;
+
+	nthw_register_t *mp_reg_cfg;
+	nthw_field_t *mp_fld_cfg_pre;
+
+	nthw_register_t *mp_reg_cfg_clk;
+	nthw_field_t *mp_fld_cfg_clk_mode;
+};
+
+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, nthw_fpga_t *p_fpga, int n_instance);
+
+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/include/nthw_spis.h b/drivers/net/ntnic/nthw/core/include/nthw_spis.h
new file mode 100644
index 0000000000..978f239dd0
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/include/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__
+
+#include "nthw_fpga.h"
+
+struct nthw_spis {
+	nthw_fpga_t *mp_fpga;
+	nthw_module_t *mp_mod_spis;
+	int mn_instance;
+
+	nthw_register_t *mp_reg_srr;
+	nthw_field_t *mp_fld_srr_rst;
+
+	nthw_register_t *mp_reg_cr;
+	nthw_field_t *mp_fld_cr_loop;
+	nthw_field_t *mp_fld_cr_en;
+	nthw_field_t *mp_fld_cr_txrst;
+	nthw_field_t *mp_fld_cr_rxrst;
+	nthw_field_t *mp_fld_cr_debug;
+
+	nthw_register_t *mp_reg_sr;
+	nthw_field_t *mp_fld_sr_done;
+	nthw_field_t *mp_fld_sr_txempty;
+	nthw_field_t *mp_fld_sr_rxempty;
+	nthw_field_t *mp_fld_sr_txfull;
+	nthw_field_t *mp_fld_sr_rxfull;
+	nthw_field_t *mp_fld_sr_txlvl;
+	nthw_field_t *mp_fld_sr_rxlvl;
+	nthw_field_t *mp_fld_sr_frame_err;
+	nthw_field_t *mp_fld_sr_read_err;
+	nthw_field_t *mp_fld_sr_write_err;
+
+	nthw_register_t *mp_reg_dtr;
+	nthw_field_t *mp_fld_dtr_dtr;
+
+	nthw_register_t *mp_reg_drr;
+	nthw_field_t *mp_fld_drr_drr;
+
+	nthw_register_t *mp_reg_ram_ctrl;
+	nthw_field_t *mp_fld_ram_ctrl_adr;
+	nthw_field_t *mp_fld_ram_ctrl_cnt;
+
+	nthw_register_t *mp_reg_ram_data;
+	nthw_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, nthw_fpga_t *p_fpga, int n_instance);
+
+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);
+
+#endif	/* __NTHW_SPIS_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nt400dxx/reset/nthw_fpga_rst_nt400dxx.c b/drivers/net/ntnic/nthw/core/nt400dxx/reset/nthw_fpga_rst_nt400dxx.c
index ff29101e61..73feaa4ebd 100644
--- a/drivers/net/ntnic/nthw/core/nt400dxx/reset/nthw_fpga_rst_nt400dxx.c
+++ b/drivers/net/ntnic/nthw/core/nt400dxx/reset/nthw_fpga_rst_nt400dxx.c
@@ -65,6 +65,8 @@ static int nthw_fpga_rst_nt400dxx_init(struct fpga_info_s *p_fpga_info)
 	nthw_prm_nt400dxx_periph_rst(p_fpga_info->mp_nthw_agx.p_prm, 0);
 	nt_os_wait_usec(10000);
 
+	res = nthw_fpga_avr_probe(p_fpga, 0);
+
 	if (res != 0)
 		return res;
 
diff --git a/drivers/net/ntnic/nthw/core/nthw_fpga.c b/drivers/net/ntnic/nthw/core/nthw_fpga.c
index e54a210c9f..3166a2ba51 100644
--- a/drivers/net/ntnic/nthw/core/nthw_fpga.c
+++ b/drivers/net/ntnic/nthw/core/nthw_fpga.c
@@ -14,6 +14,7 @@
 #include "nthw_fpga_mod_str_map.h"
 
 #include "nthw_tsm.h"
+#include "nthw_spi_v3.h"
 
 #include <arpa/inet.h>
 
@@ -151,6 +152,449 @@ int nthw_fpga_silabs_detect(nthw_fpga_t *p_fpga, const int n_instance_no, const
 	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(nthw_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];
+
+			/* vpd_eeprom_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];
+
+			/* board_info_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 user_id 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 */
+			/* The constant __AVR_LIBC_VERSION__ */
+			uint32_t sysinfo_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", 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", 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'", 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'", 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);
+
+		snprintf(p_fpga_info->nthw_hw_info.hw_plat_id_str,
+			sizeof(p_fpga_info->nthw_hw_info.hw_plat_id_str), "%s",
+			(char *)avr_vpd_info.a_avr_fw_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",
+					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",
+					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", 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", 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", 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", 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", 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", 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", 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", p_adapter_id_str,
+				n_instance_no, avr_vpd_info.sysinfo_osccal);
+
+			{
+				bool b_spi_ver_match = (avr_vpd_info.n_avr_spi_version ==
+						avr_vpd_info.sysinfo_spi_version);
+				(void)b_spi_ver_match;
+				NT_LOG(DBG, NTHW, "%s: AVR%d: SPI_VER_TST: %s (%d %d)",
+					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",
+						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",
+						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",
+					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", 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",
+					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", 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", p_adapter_id_str,
+					n_instance_no, avr_vpd_info.sysinfo_hw_revision);
+				NT_LOG(INF, NTHW, "%s: AVR%d: HW_REV: %d", 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",
+					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", 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", p_adapter_id_str,
+					n_instance_no, avr_vpd_info.sysinfo_osccal);
+
+				{
+					bool b_spi_ver_match = (avr_vpd_info.n_avr_spi_version ==
+							avr_vpd_info.sysinfo_spi_version);
+					(void)b_spi_ver_match;
+					NT_LOG(DBG, NTHW, "%s: AVR%d: SPI_VER_TST: %s (%d %d)",
+						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",
+					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", 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", 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", 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'", 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'", 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", 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'", p_adapter_id_str,
+					n_instance_no, GEN2_BNAME_SIZE,
+					avr_vpd_info.vpd_board_name);
+
+				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", 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",
+					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",
+					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", 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", 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",
+					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));
+				{
+					const uint32_t 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 "",
+					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", __func__, __LINE__, res);
+			NT_LOG(ERR, NTHW, "%s: AVR%d: SYSINFO2: NA: res=%d sz=%d",
+				p_adapter_id_str, n_instance_no, res, rx_buf.size);
+		}
+	}
+
+	return res;
+}
+
 /*
  * NT200A02, NT200A01-HWbuild2
  */
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..0b611462a0
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_spi_v3.c
@@ -0,0 +1,358 @@
+/*
+ * 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;
+}
+
+static int nthw_spi_v3_set_timeout(nthw_spi_v3_t *p, int time_out)
+{
+	p->m_time_out = time_out;
+	return 0;
+}
+
+/*
+ * 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");
+			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", __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");
+			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", __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", tmp_str);
+			j = 0;
+		}
+	}
+}
+
+#endif
+
+int nthw_spi_v3_init(nthw_spi_v3_t *p, nthw_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", 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", 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", 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", 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(DBG, NTHW, "Started");
+#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");
+		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");
+		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", 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");
+		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");
+				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");
+		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", __func__);
+#endif
+
+	{
+		/* Start receiving data */
+		uint16_t rx_size =
+			sizeof(spi_rx_hdr.raw);	/* The first data to read is the header */
+		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");
+					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",
+					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");
+					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", rx_buf->size);
+	dump_hex(rx_buf->p_buf, rx_buf->size);
+	NT_LOG(DBG, NTHW, "%s:  Ended: %d", __func__, result);
+#endif
+
+	return result;
+}
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..d30c11d0ff
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_spim.c
@@ -0,0 +1,113 @@
+/*
+ * 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, nthw_fpga_t *p_fpga, int n_instance)
+{
+	const char *const p_adapter_id_str = p_fpga->p_fpga_info->mp_adapter_id_str;
+	nthw_module_t *mod = nthw_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", 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 */
+	nthw_module_set_debug_mode(p->mp_mod_spim, 0x00);
+
+	p->mp_reg_srr = nthw_module_get_register(p->mp_mod_spim, SPIM_SRR);
+	p->mp_fld_srr_rst = nthw_register_get_field(p->mp_reg_srr, SPIM_SRR_RST);
+
+	p->mp_reg_cr = nthw_module_get_register(p->mp_mod_spim, SPIM_CR);
+	p->mp_fld_cr_loop = nthw_register_get_field(p->mp_reg_cr, SPIM_CR_LOOP);
+	p->mp_fld_cr_en = nthw_register_get_field(p->mp_reg_cr, SPIM_CR_EN);
+	p->mp_fld_cr_txrst = nthw_register_get_field(p->mp_reg_cr, SPIM_CR_TXRST);
+	p->mp_fld_cr_rxrst = nthw_register_get_field(p->mp_reg_cr, SPIM_CR_RXRST);
+
+	p->mp_reg_sr = nthw_module_get_register(p->mp_mod_spim, SPIM_SR);
+	p->mp_fld_sr_done = nthw_register_get_field(p->mp_reg_sr, SPIM_SR_DONE);
+	p->mp_fld_sr_txempty = nthw_register_get_field(p->mp_reg_sr, SPIM_SR_TXEMPTY);
+	p->mp_fld_sr_rxempty = nthw_register_get_field(p->mp_reg_sr, SPIM_SR_RXEMPTY);
+	p->mp_fld_sr_txfull = nthw_register_get_field(p->mp_reg_sr, SPIM_SR_TXFULL);
+	p->mp_fld_sr_rxfull = nthw_register_get_field(p->mp_reg_sr, SPIM_SR_RXFULL);
+	p->mp_fld_sr_txlvl = nthw_register_get_field(p->mp_reg_sr, SPIM_SR_TXLVL);
+	p->mp_fld_sr_rxlvl = nthw_register_get_field(p->mp_reg_sr, SPIM_SR_RXLVL);
+
+	p->mp_reg_dtr = nthw_module_get_register(p->mp_mod_spim, SPIM_DTR);
+	p->mp_fld_dtr_dtr = nthw_register_get_field(p->mp_reg_dtr, SPIM_DTR_DTR);
+
+	p->mp_reg_drr = nthw_module_get_register(p->mp_mod_spim, SPIM_DRR);
+	p->mp_fld_drr_drr = nthw_register_get_field(p->mp_reg_drr, SPIM_DRR_DRR);
+
+	p->mp_reg_cfg = nthw_module_get_register(p->mp_mod_spim, SPIM_CFG);
+	p->mp_fld_cfg_pre = nthw_register_get_field(p->mp_reg_cfg, SPIM_CFG_PRE);
+
+	p->mp_reg_cfg_clk = nthw_module_query_register(p->mp_mod_spim, SPIM_CFG_CLK);
+	p->mp_fld_cfg_clk_mode = nthw_register_query_field(p->mp_reg_cfg, SPIM_CFG_CLK_MODE);
+
+	return 0;
+}
+
+uint32_t nthw_spim_reset(nthw_spim_t *p)
+{
+	nthw_register_update(p->mp_reg_srr);
+	nthw_field_set_val32(p->mp_fld_srr_rst, 0x0A);	/* 0x0A hardcoded value - see doc */
+	nthw_register_flush(p->mp_reg_srr, 1);
+
+	return 0;
+}
+
+uint32_t nthw_spim_enable(nthw_spim_t *p, bool b_enable)
+{
+	nthw_field_update_register(p->mp_fld_cr_en);
+
+	if (b_enable)
+		nthw_field_set_all(p->mp_fld_cr_en);
+
+	else
+		nthw_field_clr_all(p->mp_fld_cr_en);
+
+	nthw_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)
+{
+	nthw_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 = nthw_field_get_updated(p->mp_fld_sr_txempty) ? true : false;
+
+	return 0;
+}
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..3c34dec936
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_spis.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_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, nthw_fpga_t *p_fpga, int n_instance)
+{
+	const char *const p_adapter_id_str = p_fpga->p_fpga_info->mp_adapter_id_str;
+	nthw_module_t *mod = nthw_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", 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 */
+	nthw_module_set_debug_mode(p->mp_mod_spis, 0x00);
+
+	p->mp_reg_srr = nthw_module_get_register(p->mp_mod_spis, SPIS_SRR);
+	p->mp_fld_srr_rst = nthw_register_get_field(p->mp_reg_srr, SPIS_SRR_RST);
+
+	p->mp_reg_cr = nthw_module_get_register(p->mp_mod_spis, SPIS_CR);
+	p->mp_fld_cr_loop = nthw_register_get_field(p->mp_reg_cr, SPIS_CR_LOOP);
+	p->mp_fld_cr_en = nthw_register_get_field(p->mp_reg_cr, SPIS_CR_EN);
+	p->mp_fld_cr_txrst = nthw_register_get_field(p->mp_reg_cr, SPIS_CR_TXRST);
+	p->mp_fld_cr_rxrst = nthw_register_get_field(p->mp_reg_cr, SPIS_CR_RXRST);
+	p->mp_fld_cr_debug = nthw_register_get_field(p->mp_reg_cr, SPIS_CR_DEBUG);
+
+	p->mp_reg_sr = nthw_module_get_register(p->mp_mod_spis, SPIS_SR);
+	p->mp_fld_sr_done = nthw_register_get_field(p->mp_reg_sr, SPIS_SR_DONE);
+	p->mp_fld_sr_txempty = nthw_register_get_field(p->mp_reg_sr, SPIS_SR_TXEMPTY);
+	p->mp_fld_sr_rxempty = nthw_register_get_field(p->mp_reg_sr, SPIS_SR_RXEMPTY);
+	p->mp_fld_sr_txfull = nthw_register_get_field(p->mp_reg_sr, SPIS_SR_TXFULL);
+	p->mp_fld_sr_rxfull = nthw_register_get_field(p->mp_reg_sr, SPIS_SR_RXFULL);
+	p->mp_fld_sr_txlvl = nthw_register_get_field(p->mp_reg_sr, SPIS_SR_TXLVL);
+	p->mp_fld_sr_rxlvl = nthw_register_get_field(p->mp_reg_sr, SPIS_SR_RXLVL);
+	p->mp_fld_sr_frame_err = nthw_register_get_field(p->mp_reg_sr, SPIS_SR_FRAME_ERR);
+	p->mp_fld_sr_read_err = nthw_register_get_field(p->mp_reg_sr, SPIS_SR_READ_ERR);
+	p->mp_fld_sr_write_err = nthw_register_get_field(p->mp_reg_sr, SPIS_SR_WRITE_ERR);
+
+	p->mp_reg_dtr = nthw_module_get_register(p->mp_mod_spis, SPIS_DTR);
+	p->mp_fld_dtr_dtr = nthw_register_get_field(p->mp_reg_dtr, SPIS_DTR_DTR);
+
+	p->mp_reg_drr = nthw_module_get_register(p->mp_mod_spis, SPIS_DRR);
+	p->mp_fld_drr_drr = nthw_register_get_field(p->mp_reg_drr, SPIS_DRR_DRR);
+
+	p->mp_reg_ram_ctrl = nthw_module_get_register(p->mp_mod_spis, SPIS_RAM_CTRL);
+	p->mp_fld_ram_ctrl_adr = nthw_register_get_field(p->mp_reg_ram_ctrl, SPIS_RAM_CTRL_ADR);
+	p->mp_fld_ram_ctrl_cnt = nthw_register_get_field(p->mp_reg_ram_ctrl, SPIS_RAM_CTRL_CNT);
+
+	p->mp_reg_ram_data = nthw_module_get_register(p->mp_mod_spis, SPIS_RAM_DATA);
+	p->mp_fld_ram_data_data = nthw_register_get_field(p->mp_reg_ram_data, SPIS_RAM_DATA_DATA);
+
+	return 0;
+}
+
+uint32_t nthw_spis_reset(nthw_spis_t *p)
+{
+	nthw_register_update(p->mp_reg_srr);
+	nthw_field_set_val32(p->mp_fld_srr_rst, 0x0A);	/* 0x0A hardcoded value - see doc */
+	nthw_register_flush(p->mp_reg_srr, 1);
+
+	return 0;
+}
+
+uint32_t nthw_spis_enable(nthw_spis_t *p, bool b_enable)
+{
+	nthw_field_update_register(p->mp_fld_cr_en);
+
+	if (b_enable)
+		nthw_field_set_all(p->mp_fld_cr_en);
+
+	else
+		nthw_field_clr_all(p->mp_fld_cr_en);
+
+	nthw_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 = nthw_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 = nthw_field_get_updated(p->mp_fld_drr_drr);
+
+	return 0;
+}
-- 
2.45.0


  parent reply	other threads:[~2025-02-20 22:07 UTC|newest]

Thread overview: 40+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-02-20 22:03 [PATCH v1 00/32] add new adapter NT400D13 Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 01/32] net/ntnic: add link agx 100g Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 02/32] net/ntnic: add link state machine Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 03/32] net/ntnic: add rpf and gfg init Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 04/32] net/ntnic: add agx setup for port Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 05/32] net/ntnic: add host loopback init Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 06/32] net/ntnic: add line " Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 07/32] net/ntnic: add 100 gbps port init Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 08/32] net/ntnic: add port post init Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 09/32] net/ntnic: add nim low power API Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 10/32] net/ntnic: add link handling API Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 11/32] net/ntnic: add port init to the state machine Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 12/32] net/ntnic: add port disable API Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 13/32] net/ntnic: add minimal initialization new NIC NT400D13 Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 14/32] net/ntnic: add minimal reset FPGA Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 15/32] net/ntnic: add FPGA modules and registers Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 16/32] net/ntnic: add setup for fpga reset Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 17/32] net/ntnic: add default reset setting for NT400D13 Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 18/32] net/ntnic: add DDR calibration to reset stage Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 19/32] net/ntnic: add PHY ftile reset Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 20/32] net/ntnic: add clock init Serhii Iliushyk
2025-03-05 17:03   ` David Marchand
2025-03-07 10:20     ` Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 21/32] net/ntnic: add nt400d13 pcm init Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 22/32] net/ntnic: add HIF clock test Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 23/32] net/ntnic: add nt400d13 PRM module init Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 24/32] net/ntnic: add nt400d13 PRM module reset Serhii Iliushyk
2025-02-20 22:03 ` Serhii Iliushyk [this message]
2025-02-22 19:21   ` [PATCH v1 25/32] net/ntnic: add SPI v3 support for FPGA Stephen Hemminger
2025-02-20 22:03 ` [PATCH v1 26/32] net/ntnic: add i2cm init Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 27/32] net/ntnic: add pca init Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 28/32] net/ntnic: add pcal init Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 29/32] net/ntnic: add reset PHY init Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 30/32] net/ntnic: add igam module init Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 31/32] net/ntnic: init IGAM and config PLL for FPGA Serhii Iliushyk
2025-02-20 22:03 ` [PATCH v1 32/32] net/ntnic: revert untrusted loop bound Serhii Iliushyk
2025-02-20 22:31   ` Stephen Hemminger
2025-02-20 23:49 ` [PATCH v1 00/32] add new adapter NT400D13 Stephen Hemminger
2025-02-22 21:41 ` Stephen Hemminger
2025-04-11 10:36   ` Serhii Iliushyk

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=20250220220406.3925597-26-sil-plv@napatech.com \
    --to=sil-plv@napatech.com \
    --cc=ckm@napatech.com \
    --cc=dev@dpdk.org \
    --cc=dvo-plv@napatech.com \
    --cc=mko-plv@napatech.com \
    --cc=stephen@networkplumber.org \
    /path/to/YOUR_REPLY

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

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