From: Danylo Vodopianov <dvo-...@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-...@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

Reply via email to