[v10,17/21] net/ntnic: add generic NIM and I2C modules

Message ID 20240717133313.3104239-17-sil-plv@napatech.com (mailing list archive)
State Accepted, archived
Delegated to: Ferruh Yigit
Headers
Series [v10,01/21] net/ntnic: add ethdev and makes PMD available |

Checks

Context Check Description
ci/checkpatch success coding style OK

Commit Message

Serhii Iliushyk July 17, 2024, 1:33 p.m. UTC
As the ntnic can support different port speeds, it also needs to support
different NIMs (Network Interface Module). This commit add the generic NIM
support for ntnic, such that the specific modules,
such as QSFP28 can be added later.

The communication with NIMs is in the form of I2C, so support for such a
module is added as well.

Additionally a thread is added to control the NIM stat machines.

Signed-off-by: Serhii Iliushyk <sil-plv@napatech.com>
---
v6
*  Remove unnecessary comments
v10
* Use 8 spaces as indentation in meson
---
 drivers/net/ntnic/adapter/nt4ga_adapter.c     |  34 +++
 drivers/net/ntnic/include/nt4ga_adapter.h     |   3 +
 drivers/net/ntnic/include/nt4ga_link.h        |  13 +
 drivers/net/ntnic/include/ntnic_nim.h         |  61 +++++
 .../link_mgmt/link_100g/nt4ga_link_100g.c     | 214 ++++++++++++++++-
 drivers/net/ntnic/link_mgmt/nt4ga_link.c      |  13 +
 drivers/net/ntnic/meson.build                 |   3 +
 drivers/net/ntnic/nim/i2c_nim.c               | 224 ++++++++++++++++++
 drivers/net/ntnic/nim/i2c_nim.h               |  24 ++
 drivers/net/ntnic/nim/nim_defines.h           |  29 +++
 .../net/ntnic/nthw/core/include/nthw_core.h   |   1 +
 .../net/ntnic/nthw/core/include/nthw_i2cm.h   |  50 ++++
 drivers/net/ntnic/nthw/core/nthw_fpga.c       |   3 +
 drivers/net/ntnic/nthw/core/nthw_i2cm.c       | 192 +++++++++++++++
 drivers/net/ntnic/nthw/nthw_drv.h             |   1 +
 drivers/net/ntnic/ntnic_mod_reg.h             |   7 +
 16 files changed, 871 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/ntnic/include/ntnic_nim.h
 create mode 100644 drivers/net/ntnic/nim/i2c_nim.c
 create mode 100644 drivers/net/ntnic/nim/i2c_nim.h
 create mode 100644 drivers/net/ntnic/nim/nim_defines.h
 create mode 100644 drivers/net/ntnic/nthw/core/include/nthw_i2cm.h
 create mode 100644 drivers/net/ntnic/nthw/core/nthw_i2cm.c
  

Patch

diff --git a/drivers/net/ntnic/adapter/nt4ga_adapter.c b/drivers/net/ntnic/adapter/nt4ga_adapter.c
index 43bb566e33..b704a256c6 100644
--- a/drivers/net/ntnic/adapter/nt4ga_adapter.c
+++ b/drivers/net/ntnic/adapter/nt4ga_adapter.c
@@ -9,6 +9,32 @@ 
 #include "nthw_fpga.h"
 #include "ntnic_mod_reg.h"
 
+/*
+ * Global variables shared by NT adapter types
+ */
+rte_thread_t monitor_tasks[NUM_ADAPTER_MAX];
+volatile int monitor_task_is_running[NUM_ADAPTER_MAX];
+
+/*
+ * Signal-handler to stop all monitor threads
+ */
+static void stop_monitor_tasks(int signum)
+{
+	const size_t N = ARRAY_SIZE(monitor_task_is_running);
+	size_t i;
+
+	/* Stop all monitor tasks */
+	for (i = 0; i < N; i++) {
+		const int is_running = monitor_task_is_running[i];
+		monitor_task_is_running[i] = 0;
+
+		if (signum == -1 && is_running != 0) {
+			rte_thread_join(monitor_tasks[i], NULL);
+			memset(&monitor_tasks[i], 0, sizeof(monitor_tasks[0]));
+		}
+	}
+}
+
 static int nt4ga_adapter_show_info(struct adapter_info_s *p_adapter_info, FILE *pfh)
 {
 	const char *const p_dev_name = p_adapter_info->p_dev_name;
@@ -35,6 +61,9 @@  static int nt4ga_adapter_show_info(struct adapter_info_s *p_adapter_info, FILE *
 		p_fpga_info->n_fpga_ver_id, p_fpga_info->n_fpga_rev_id, p_fpga_info->n_fpga_ident,
 		p_fpga_info->n_fpga_build_time);
 	fprintf(pfh, "%s: FpgaDebugMode=0x%x\n", p_adapter_id_str, p_fpga_info->n_fpga_debug_mode);
+	fprintf(pfh, "%s: Nims=%d PhyPorts=%d PhyQuads=%d RxPorts=%d TxPorts=%d\n",
+		p_adapter_id_str, p_fpga_info->n_nims, p_fpga_info->n_phy_ports,
+		p_fpga_info->n_phy_quads, p_fpga_info->n_rx_ports, p_fpga_info->n_tx_ports);
 	fprintf(pfh, "%s: Hw=0x%02X_rev%d: %s\n", p_adapter_id_str, p_hw_info->hw_platform_id,
 		p_fpga_info->nthw_hw_info.hw_id, p_fpga_info->nthw_hw_info.hw_plat_id_str);
 	fprintf(pfh, "%s: MCU Details:\n", p_adapter_id_str);
@@ -56,6 +85,7 @@  static int nt4ga_adapter_init(struct adapter_info_s *p_adapter_info)
 	 * (nthw_fpga_init())
 	 */
 	int n_phy_ports = -1;
+	int n_nim_ports = -1;
 	int res = -1;
 	nthw_fpga_t *p_fpga = NULL;
 
@@ -122,6 +152,8 @@  static int nt4ga_adapter_init(struct adapter_info_s *p_adapter_info)
 	assert(p_fpga);
 	n_phy_ports = fpga_info->n_phy_ports;
 	assert(n_phy_ports >= 1);
+	n_nim_ports = fpga_info->n_nims;
+	assert(n_nim_ports >= 1);
 
 	{
 		int i;
@@ -171,6 +203,8 @@  static int nt4ga_adapter_deinit(struct adapter_info_s *p_adapter_info)
 	int i;
 	int res = -1;
 
+	stop_monitor_tasks(-1);
+
 	nthw_fpga_shutdown(&p_adapter_info->fpga_info);
 
 	/* Rac rab reset flip flop */
diff --git a/drivers/net/ntnic/include/nt4ga_adapter.h b/drivers/net/ntnic/include/nt4ga_adapter.h
index ed14936b38..4b204742a2 100644
--- a/drivers/net/ntnic/include/nt4ga_adapter.h
+++ b/drivers/net/ntnic/include/nt4ga_adapter.h
@@ -39,5 +39,8 @@  typedef struct adapter_info_s {
 	int n_tx_host_buffers;
 } adapter_info_t;
 
+extern rte_thread_t monitor_tasks[NUM_ADAPTER_MAX];
+extern volatile int monitor_task_is_running[NUM_ADAPTER_MAX];
+
 
 #endif	/* _NT4GA_ADAPTER_H_ */
diff --git a/drivers/net/ntnic/include/nt4ga_link.h b/drivers/net/ntnic/include/nt4ga_link.h
index 849261ce3a..0851057f81 100644
--- a/drivers/net/ntnic/include/nt4ga_link.h
+++ b/drivers/net/ntnic/include/nt4ga_link.h
@@ -7,6 +7,7 @@ 
 #define NT4GA_LINK_H_
 
 #include "ntos_drv.h"
+#include "ntnic_nim.h"
 
 enum nt_link_state_e {
 	NT_LINK_STATE_UNKNOWN = 0,	/* The link state has not been read yet */
@@ -41,6 +42,8 @@  enum nt_link_auto_neg_e {
 
 typedef struct link_state_s {
 	bool link_disabled;
+	bool nim_present;
+	bool lh_nim_absent;
 	bool link_up;
 	enum nt_link_state_e link_state;
 	enum nt_link_state_e link_state_latched;
@@ -73,12 +76,22 @@  typedef struct port_action_s {
 	uint32_t port_lpbk_mode;
 } port_action_t;
 
+typedef struct adapter_100g_s {
+	nim_i2c_ctx_t nim_ctx[NUM_ADAPTER_PORTS_MAX];	/* Should be the first field */
+} adapter_100g_t;
+
+typedef union adapter_var_s {
+	nim_i2c_ctx_t nim_ctx[NUM_ADAPTER_PORTS_MAX];	/* First field in all the adapters type */
+	adapter_100g_t var100g;
+} adapter_var_u;
+
 typedef struct nt4ga_link_s {
 	link_state_t link_state[NUM_ADAPTER_PORTS_MAX];
 	link_info_t link_info[NUM_ADAPTER_PORTS_MAX];
 	port_action_t port_action[NUM_ADAPTER_PORTS_MAX];
 	uint32_t speed_capa;
 	bool variables_initialized;
+	adapter_var_u u;
 } nt4ga_link_t;
 
 #endif	/* NT4GA_LINK_H_ */
diff --git a/drivers/net/ntnic/include/ntnic_nim.h b/drivers/net/ntnic/include/ntnic_nim.h
new file mode 100644
index 0000000000..fd4a915811
--- /dev/null
+++ b/drivers/net/ntnic/include/ntnic_nim.h
@@ -0,0 +1,61 @@ 
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _NTNIC_NIM_H_
+#define _NTNIC_NIM_H_
+
+#include <stdint.h>
+
+typedef enum i2c_type {
+	I2C_HWIIC,
+} i2c_type_e;
+
+enum nt_port_type_e {
+	NT_PORT_TYPE_NOT_AVAILABLE = 0,	/* The NIM/port type is not available (unknown) */
+	NT_PORT_TYPE_NOT_RECOGNISED,	/* The NIM/port type not recognized */
+};
+
+typedef enum nt_port_type_e nt_port_type_t, *nt_port_type_p;
+
+typedef struct nim_i2c_ctx {
+	union {
+		nthw_iic_t hwiic;	/* depends on *Fpga_t, instance number, and cycle time */
+		struct {
+			nthw_i2cm_t *p_nt_i2cm;
+			int mux_channel;
+		} hwagx;
+	};
+	i2c_type_e type;/* 0 = hwiic (xilinx) - 1 =  hwagx (agilex) */
+	uint8_t instance;
+	uint8_t devaddr;
+	uint8_t regaddr;
+	uint8_t nim_id;
+	nt_port_type_t port_type;
+
+	char vendor_name[17];
+	char prod_no[17];
+	char serial_no[17];
+	char date[9];
+	char rev[5];
+	bool avg_pwr;
+	bool content_valid;
+	uint8_t pwr_level_req;
+	uint8_t pwr_level_cur;
+	uint16_t len_info[5];
+	uint32_t speed_mask;	/* Speeds supported by the NIM */
+	int8_t lane_idx;
+	uint8_t lane_count;
+	uint32_t options;
+	bool tx_disable;
+	bool dmi_supp;
+
+} nim_i2c_ctx_t, *nim_i2c_ctx_p;
+
+struct nim_sensor_group {
+	struct nim_i2c_ctx *ctx;
+	struct nim_sensor_group *next;
+};
+
+#endif	/* _NTNIC_NIM_H_ */
diff --git a/drivers/net/ntnic/link_mgmt/link_100g/nt4ga_link_100g.c b/drivers/net/ntnic/link_mgmt/link_100g/nt4ga_link_100g.c
index 36c4bf031f..4a8d28af9c 100644
--- a/drivers/net/ntnic/link_mgmt/link_100g/nt4ga_link_100g.c
+++ b/drivers/net/ntnic/link_mgmt/link_100g/nt4ga_link_100g.c
@@ -7,16 +7,202 @@ 
 
 #include "nt_util.h"
 #include "ntlog.h"
+#include "i2c_nim.h"
 #include "ntnic_mod_reg.h"
 
+/*
+ * Initialize NIM, Code based on nt200e3_2_ptp.cpp: MyPort::createNim()
+ */
+static int _create_nim(adapter_info_t *drv, int port)
+{
+	int res = 0;
+	const uint8_t valid_nim_id = 17U;
+	nim_i2c_ctx_t *nim_ctx;
+	nt4ga_link_t *link_info = &drv->nt4ga_link;
+
+	assert(port >= 0 && port < NUM_ADAPTER_PORTS_MAX);
+	assert(link_info->variables_initialized);
+
+	nim_ctx = &link_info->u.var100g.nim_ctx[port];
+
+	/*
+	 * Wait a little after a module has been inserted before trying to access I2C
+	 * data, otherwise the module will not respond correctly.
+	 */
+	nt_os_wait_usec(1000000);	/* pause 1.0s */
+
+	res = construct_and_preinit_nim(nim_ctx);
+
+	if (res)
+		return res;
+
+	/*
+	 * Does the driver support the NIM module type?
+	 */
+	if (nim_ctx->nim_id != valid_nim_id) {
+		NT_LOG(ERR, NTHW, "%s: The driver does not support the NIM module type %s\n",
+			drv->mp_port_id_str[port], nim_id_to_text(nim_ctx->nim_id));
+		NT_LOG(DBG, NTHW, "%s: The driver supports the NIM module type %s\n",
+			drv->mp_port_id_str[port], nim_id_to_text(valid_nim_id));
+		return -1;
+	}
+
+	return res;
+}
+
+/*
+ * Initialize one 100 Gbps port.
+ * The function shall not assume anything about the state of the adapter
+ * and/or port.
+ */
+static int _port_init(adapter_info_t *drv, int port)
+{
+	int res;
+	nt4ga_link_t *link_info = &drv->nt4ga_link;
+
+	assert(port >= 0 && port < NUM_ADAPTER_PORTS_MAX);
+	assert(link_info->variables_initialized);
+
+	/*
+	 * Phase 1. Pre-state machine (`port init` functions)
+	 * 1.1) nt4ga_adapter::port_init()
+	 */
+
+	/* No adapter set-up here, only state variables */
+
+	/* 1.2) MyPort::init() */
+	link_info->link_info[port].link_speed = NT_LINK_SPEED_100G;
+	link_info->link_info[port].link_duplex = NT_LINK_DUPLEX_FULL;
+	link_info->link_info[port].link_auto_neg = NT_LINK_AUTONEG_OFF;
+	link_info->speed_capa |= NT_LINK_SPEED_100G;
+
+	/* Phase 3. Link state machine steps */
+
+	/* 3.1) Create NIM, ::createNim() */
+	res = _create_nim(drv, port);
+
+	if (res) {
+		NT_LOG(WRN, NTNIC, "%s: NIM initialization failed\n", drv->mp_port_id_str[port]);
+		return res;
+	}
+
+	NT_LOG(DBG, NTNIC, "%s: NIM initialized\n", drv->mp_port_id_str[port]);
+
+	return res;
+}
+
+/*
+ * State machine shared between kernel and userland
+ */
+static int _common_ptp_nim_state_machine(void *data)
+{
+	adapter_info_t *drv = (adapter_info_t *)data;
+	fpga_info_t *fpga_info = &drv->fpga_info;
+	nt4ga_link_t *link_info = &drv->nt4ga_link;
+	nthw_fpga_t *fpga = fpga_info->mp_fpga;
+	const int adapter_no = drv->adapter_no;
+	const int nb_ports = fpga_info->n_phy_ports;
+	uint32_t last_lpbk_mode[NUM_ADAPTER_PORTS_MAX];
+
+	link_state_t *link_state;
+
+	if (!fpga) {
+		NT_LOG(ERR, NTNIC, "%s: fpga is NULL\n", drv->mp_adapter_id_str);
+		goto NT4GA_LINK_100G_MON_EXIT;
+	}
+
+	assert(adapter_no >= 0 && adapter_no < NUM_ADAPTER_MAX);
+	link_state = link_info->link_state;
+
+	monitor_task_is_running[adapter_no] = 1;
+	memset(last_lpbk_mode, 0, sizeof(last_lpbk_mode));
+
+	if (monitor_task_is_running[adapter_no])
+		NT_LOG(DBG, NTNIC, "%s: link state machine running...\n", drv->mp_adapter_id_str);
+
+	while (monitor_task_is_running[adapter_no]) {
+		int i;
+
+		for (i = 0; i < nb_ports; i++) {
+			const bool is_port_disabled = link_info->port_action[i].port_disable;
+			const bool was_port_disabled = link_state[i].link_disabled;
+			const bool disable_port = is_port_disabled && !was_port_disabled;
+			const bool enable_port = !is_port_disabled && was_port_disabled;
+
+			if (!monitor_task_is_running[adapter_no])	/* stop quickly */
+				break;
+
+			/* Has the administrative port state changed? */
+			assert(!(disable_port && enable_port));
+
+			if (disable_port) {
+				memset(&link_state[i], 0, sizeof(link_state[i]));
+				link_info->link_info[i].link_speed = NT_LINK_SPEED_UNKNOWN;
+				link_state[i].link_disabled = true;
+				/* Turn off laser and LED, etc. */
+				(void)_create_nim(drv, i);
+				NT_LOG(DBG, NTNIC, "%s: Port %i is disabled\n",
+					drv->mp_port_id_str[i], i);
+				continue;
+			}
+
+			if (enable_port) {
+				link_state[i].link_disabled = false;
+				NT_LOG(DBG, NTNIC, "%s: Port %i is enabled\n",
+					drv->mp_port_id_str[i], i);
+			}
+
+			if (is_port_disabled)
+				continue;
+
+			if (link_info->port_action[i].port_lpbk_mode != last_lpbk_mode[i]) {
+				/* Loopback mode has changed. Do something */
+				_port_init(drv, i);
+
+				NT_LOG(INF, NTNIC, "%s: Loopback mode changed=%u\n",
+					drv->mp_port_id_str[i],
+					link_info->port_action[i].port_lpbk_mode);
+
+				if (link_info->port_action[i].port_lpbk_mode == 1)
+					link_state[i].link_up = true;
+
+				last_lpbk_mode[i] = link_info->port_action[i].port_lpbk_mode;
+				continue;
+			}
+
+		}	/* end-for */
+
+		if (monitor_task_is_running[adapter_no])
+			nt_os_wait_usec(5 * 100000U);	/* 5 x 0.1s = 0.5s */
+	}
+
+NT4GA_LINK_100G_MON_EXIT:
+
+	NT_LOG(DBG, NTNIC, "%s: Stopped NT4GA 100 Gbps link monitoring thread.\n",
+		drv->mp_adapter_id_str);
+
+	return 0;
+}
+
+/*
+ * Userland NIM state machine
+ */
+static uint32_t nt4ga_link_100g_mon(void *data)
+{
+	(void)_common_ptp_nim_state_machine(data);
+
+	return 0;
+}
+
 /*
  * Initialize all ports
  * The driver calls this function during initialization (of the driver).
  */
 static int nt4ga_link_100g_ports_init(struct adapter_info_s *p_adapter_info, nthw_fpga_t *fpga)
 {
-	(void)fpga;
+	fpga_info_t *fpga_info = &p_adapter_info->fpga_info;
 	const int adapter_no = p_adapter_info->adapter_no;
+	const int nb_ports = fpga_info->n_phy_ports;
 	int res = 0;
 
 	NT_LOG(DBG, NTNIC, "%s: Initializing ports\n", p_adapter_info->mp_adapter_id_str);
@@ -27,12 +213,38 @@  static int nt4ga_link_100g_ports_init(struct adapter_info_s *p_adapter_info, nth
 	assert(adapter_no >= 0 && adapter_no < NUM_ADAPTER_MAX);
 
 	if (res == 0 && !p_adapter_info->nt4ga_link.variables_initialized) {
+		nim_i2c_ctx_t *nim_ctx = p_adapter_info->nt4ga_link.u.var100g.nim_ctx;
+		int i;
+
+		for (i = 0; i < nb_ports; i++) {
+			/* 2 + adapter port number */
+			const uint8_t instance = (uint8_t)(2U + i);
+
+			res = nthw_iic_init(&nim_ctx[i].hwiic, fpga, instance, 8);
+
+			if (res != 0)
+				break;
+
+			nim_ctx[i].instance = instance;
+			nim_ctx[i].devaddr = 0x50;	/* 0xA0 / 2 */
+			nim_ctx[i].regaddr = 0U;
+			nim_ctx[i].type = I2C_HWIIC;
+		}
+
 		if (res == 0) {
 			p_adapter_info->nt4ga_link.speed_capa = NT_LINK_SPEED_100G;
 			p_adapter_info->nt4ga_link.variables_initialized = true;
 		}
 	}
 
+	/* Create state-machine thread */
+	if (res == 0) {
+		if (!monitor_task_is_running[adapter_no]) {
+			res = rte_thread_create(&monitor_tasks[adapter_no], NULL,
+					nt4ga_link_100g_mon, p_adapter_info);
+		}
+	}
+
 	return res;
 }
 
diff --git a/drivers/net/ntnic/link_mgmt/nt4ga_link.c b/drivers/net/ntnic/link_mgmt/nt4ga_link.c
index ad23046aae..bc362776fc 100644
--- a/drivers/net/ntnic/link_mgmt/nt4ga_link.c
+++ b/drivers/net/ntnic/link_mgmt/nt4ga_link.c
@@ -12,6 +12,7 @@ 
 #include "ntnic_mod_reg.h"
 
 #include "nt4ga_link.h"
+#include "i2c_nim.h"
 
 /*
  * port: speed capabilities
@@ -26,6 +27,16 @@  static uint32_t nt4ga_port_get_link_speed_capabilities(struct adapter_info_s *p,
 	return nt_link_speed_capa;
 }
 
+/*
+ * port: nim present
+ */
+static bool nt4ga_port_get_nim_present(struct adapter_info_s *p, int port)
+{
+	nt4ga_link_t *const p_link = &p->nt4ga_link;
+	const bool nim_present = p_link->link_state[port].nim_present;
+	return nim_present;
+}
+
 /*
  * port: link mode
  */
@@ -131,6 +142,8 @@  static uint32_t nt4ga_port_get_loopback_mode(struct adapter_info_s *p, int port)
 
 
 static const struct port_ops ops = {
+	.get_nim_present = nt4ga_port_get_nim_present,
+
 	/*
 	 * port:s link mode
 	 */
diff --git a/drivers/net/ntnic/meson.build b/drivers/net/ntnic/meson.build
index 18eab54a5b..5d044153e3 100644
--- a/drivers/net/ntnic/meson.build
+++ b/drivers/net/ntnic/meson.build
@@ -17,6 +17,7 @@  includes = [
         include_directories('nthw'),
         include_directories('nthw/supported'),
         include_directories('nthw/model'),
+        include_directories('nim/'),
 ]
 
 # all sources
@@ -24,6 +25,7 @@  sources = files(
         'adapter/nt4ga_adapter.c',
         'link_mgmt/link_100g/nt4ga_link_100g.c',
         'link_mgmt/nt4ga_link.c',
+        'nim/i2c_nim.c',
         'nthw/supported/nthw_fpga_9563_055_039_0000.c',
         'nthw/supported/nthw_fpga_instances.c',
         'nthw/supported/nthw_fpga_mod_str_map.c',
@@ -33,6 +35,7 @@  sources = files(
         'nthw/core/nt200a0x/reset/nthw_fpga_rst_nt200a0x.c',
         'nthw/core/nthw_fpga.c',
         'nthw/core/nthw_hif.c',
+        'nthw/core/nthw_i2cm.c',
         'nthw/core/nthw_iic.c',
         'nthw/core/nthw_pcie3.c',
         'nthw/core/nthw_sdc.c',
diff --git a/drivers/net/ntnic/nim/i2c_nim.c b/drivers/net/ntnic/nim/i2c_nim.c
new file mode 100644
index 0000000000..3281058822
--- /dev/null
+++ b/drivers/net/ntnic/nim/i2c_nim.c
@@ -0,0 +1,224 @@ 
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <string.h>
+
+#include "nthw_drv.h"
+#include "i2c_nim.h"
+#include "ntlog.h"
+#include "nt_util.h"
+#include "ntnic_mod_reg.h"
+#include "nim_defines.h"
+
+#define NIM_READ false
+#define NIM_WRITE true
+#define NIM_PAGE_SEL_REGISTER 127
+#define NIM_I2C_0XA0 0xA0	/* Basic I2C address */
+
+static int nim_read_write_i2c_data(nim_i2c_ctx_p ctx, bool do_write, uint16_t lin_addr,
+	uint8_t i2c_addr, uint8_t a_reg_addr, uint8_t seq_cnt,
+	uint8_t *p_data)
+{
+	/* Divide i2c_addr by 2 because nthw_iic_read/writeData multiplies by 2 */
+	const uint8_t i2c_devaddr = i2c_addr / 2U;
+	(void)lin_addr;	/* Unused */
+
+	if (do_write) {
+		if (ctx->type == I2C_HWIIC) {
+			return nthw_iic_write_data(&ctx->hwiic, i2c_devaddr, a_reg_addr, seq_cnt,
+					p_data);
+
+		} else {
+			return 0;
+		}
+
+	} else if (ctx->type == I2C_HWIIC) {
+		return nthw_iic_read_data(&ctx->hwiic, i2c_devaddr, a_reg_addr, seq_cnt, p_data);
+
+	} else {
+		return 0;
+	}
+}
+
+/*
+ * ------------------------------------------------------------------------------
+ * Selects a new page for page addressing. This is only relevant if the NIM
+ * supports this. Since page switching can take substantial time the current page
+ * select is read and subsequently only changed if necessary.
+ * Important:
+ * XFP Standard 8077, Ver 4.5, Page 61 states that:
+ * If the host attempts to write a table select value which is not supported in
+ * a particular module, the table select byte will revert to 01h.
+ * This can lead to some surprising result that some pages seems to be duplicated.
+ * ------------------------------------------------------------------------------
+ */
+
+static int nim_setup_page(nim_i2c_ctx_p ctx, uint8_t page_sel)
+{
+	uint8_t curr_page_sel;
+
+	/* Read the current page select value */
+	if (nim_read_write_i2c_data(ctx, NIM_READ, NIM_PAGE_SEL_REGISTER, NIM_I2C_0XA0,
+			NIM_PAGE_SEL_REGISTER, sizeof(curr_page_sel),
+			&curr_page_sel) != 0) {
+		return -1;
+	}
+
+	/* Only write new page select value if necessary */
+	if (page_sel != curr_page_sel) {
+		if (nim_read_write_i2c_data(ctx, NIM_WRITE, NIM_PAGE_SEL_REGISTER, NIM_I2C_0XA0,
+				NIM_PAGE_SEL_REGISTER, sizeof(page_sel),
+				&page_sel) != 0) {
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static int nim_read_write_data_lin(nim_i2c_ctx_p ctx, bool m_page_addressing, uint16_t lin_addr,
+	uint16_t length, uint8_t *p_data, bool do_write)
+{
+	uint16_t i;
+	uint8_t a_reg_addr;	/* The actual register address in I2C device */
+	uint8_t i2c_addr;
+	int block_size = 128;	/* Equal to size of MSA pages */
+	int seq_cnt;
+	int max_seq_cnt = 1;
+	int multi_byte = 1;	/* One byte per I2C register is default */
+
+	for (i = 0; i < length;) {
+		bool use_page_select = false;
+
+		/*
+		 * Find out how much can be read from the current block in case of
+		 * single byte access
+		 */
+		if (multi_byte == 1)
+			max_seq_cnt = block_size - (lin_addr % block_size);
+
+		if (m_page_addressing) {
+			if (lin_addr >= 128) {	/* Only page setup above this address */
+				use_page_select = true;
+
+				/* Map to [128..255] of 0xA0 device */
+				a_reg_addr = (uint8_t)(block_size + (lin_addr % block_size));
+
+			} else {
+				a_reg_addr = (uint8_t)lin_addr;
+			}
+
+			i2c_addr = NIM_I2C_0XA0;/* Base I2C address */
+
+		} else if (lin_addr >= 256) {
+			/* Map to address [0..255] of 0xA2 device */
+			a_reg_addr = (uint8_t)(lin_addr - 256);
+			i2c_addr = NIM_I2C_0XA2;
+
+		} else {
+			a_reg_addr = (uint8_t)lin_addr;
+			i2c_addr = NIM_I2C_0XA0;/* Base I2C address */
+		}
+
+		/* Now actually do the reading/writing */
+		seq_cnt = length - i;	/* Number of remaining bytes */
+
+		if (seq_cnt > max_seq_cnt)
+			seq_cnt = max_seq_cnt;
+
+		/*
+		 * Read a number of bytes without explicitly specifying a new address.
+		 * This can speed up I2C access since automatic incrementation of the
+		 * I2C device internal address counter can be used. It also allows
+		 * a HW implementation, that can deal with block access.
+		 * Furthermore it also allows for access to data that must be accessed
+		 * as 16bit words reading two bytes at each address eg PHYs.
+		 */
+		if (use_page_select) {
+			if (nim_setup_page(ctx, (uint8_t)((lin_addr / 128) - 1)) != 0) {
+				NT_LOG(ERR, NTNIC,
+					"Cannot set up page for linear address %u\n", lin_addr);
+				return -1;
+			}
+		}
+
+		if (nim_read_write_i2c_data(ctx, do_write, lin_addr, i2c_addr, a_reg_addr,
+				(uint8_t)seq_cnt, p_data) != 0) {
+			NT_LOG(ERR, NTNIC, " Call to nim_read_write_i2c_data failed\n");
+			return -1;
+		}
+
+		p_data += seq_cnt;
+		i = (uint16_t)(i + seq_cnt);
+		lin_addr = (uint16_t)(lin_addr + (seq_cnt / multi_byte));
+	}
+
+	return 0;
+}
+
+static int nim_read_id(nim_i2c_ctx_t *ctx)
+{
+	/* We are only reading the first byte so we don't care about pages here. */
+	const bool USE_PAGE_ADDRESSING = false;
+
+	if (nim_read_write_data_lin(ctx, USE_PAGE_ADDRESSING, NIM_IDENTIFIER_ADDR,
+			sizeof(ctx->nim_id), &ctx->nim_id, NIM_READ) != 0) {
+		return -1;
+	}
+
+	return 0;
+}
+
+static int i2c_nim_common_construct(nim_i2c_ctx_p ctx)
+{
+	ctx->nim_id = 0;
+	int res;
+
+	if (ctx->type == I2C_HWIIC)
+		res = nim_read_id(ctx);
+
+	else
+		res = -1;
+
+	if (res) {
+		NT_LOG(ERR, PMD, "Can't read NIM id.");
+		return res;
+	}
+
+	memset(ctx->vendor_name, 0, sizeof(ctx->vendor_name));
+	memset(ctx->prod_no, 0, sizeof(ctx->prod_no));
+	memset(ctx->serial_no, 0, sizeof(ctx->serial_no));
+	memset(ctx->date, 0, sizeof(ctx->date));
+	memset(ctx->rev, 0, sizeof(ctx->rev));
+
+	ctx->content_valid = false;
+	memset(ctx->len_info, 0, sizeof(ctx->len_info));
+	ctx->pwr_level_req = 0;
+	ctx->pwr_level_cur = 0;
+	ctx->avg_pwr = false;
+	ctx->tx_disable = false;
+	ctx->lane_idx = -1;
+	ctx->lane_count = 1;
+	ctx->options = 0;
+	return 0;
+}
+
+const char *nim_id_to_text(uint8_t nim_id)
+{
+	switch (nim_id) {
+	case 0x0:
+		return "UNKNOWN";
+
+	default:
+		return "ILLEGAL!";
+	}
+}
+
+int construct_and_preinit_nim(nim_i2c_ctx_p ctx)
+{
+	int res = i2c_nim_common_construct(ctx);
+
+	return res;
+}
diff --git a/drivers/net/ntnic/nim/i2c_nim.h b/drivers/net/ntnic/nim/i2c_nim.h
new file mode 100644
index 0000000000..e89ae47835
--- /dev/null
+++ b/drivers/net/ntnic/nim/i2c_nim.h
@@ -0,0 +1,24 @@ 
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef I2C_NIM_H_
+#define I2C_NIM_H_
+
+#include "ntnic_nim.h"
+
+/*
+ * Returns a type name such as "SFP/SFP+" for a given NIM type identifier,
+ * or the string "ILLEGAL!".
+ */
+const char *nim_id_to_text(uint8_t nim_id);
+
+/*
+ * This function tries to classify NIM based on it's ID and some register reads
+ * and collects information into ctx structure. The @extra parameter could contain
+ * the initialization argument for specific type of NIMS.
+ */
+int construct_and_preinit_nim(nim_i2c_ctx_p ctx);
+
+#endif	/* I2C_NIM_H_ */
diff --git a/drivers/net/ntnic/nim/nim_defines.h b/drivers/net/ntnic/nim/nim_defines.h
new file mode 100644
index 0000000000..9ba861bb4f
--- /dev/null
+++ b/drivers/net/ntnic/nim/nim_defines.h
@@ -0,0 +1,29 @@ 
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NIM_DEFINES_H_
+#define NIM_DEFINES_H_
+
+#define NIM_IDENTIFIER_ADDR 0	/* 1 byte */
+
+/* I2C addresses */
+#define NIM_I2C_0XA0 0xA0	/* Basic I2C address */
+#define NIM_I2C_0XA2 0xA2	/* Diagnostic monitoring */
+
+typedef enum {
+	NIM_OPTION_TX_DISABLE,
+	/* Indicates that the module should be checked for the two next FEC types */
+	NIM_OPTION_FEC,
+	NIM_OPTION_MEDIA_SIDE_FEC,
+	NIM_OPTION_HOST_SIDE_FEC,
+	NIM_OPTION_RX_ONLY
+} nim_option_t;
+
+enum nt_nim_identifier_e {
+	NT_NIM_UNKNOWN = 0x00,	/* Nim type is unknown */
+};
+typedef enum nt_nim_identifier_e nt_nim_identifier_t;
+
+#endif	/* NIM_DEFINES_H_ */
diff --git a/drivers/net/ntnic/nthw/core/include/nthw_core.h b/drivers/net/ntnic/nthw/core/include/nthw_core.h
index 5648bd8983..8d9d78b86d 100644
--- a/drivers/net/ntnic/nthw/core/include/nthw_core.h
+++ b/drivers/net/ntnic/nthw/core/include/nthw_core.h
@@ -15,6 +15,7 @@ 
 #include "nthw_hif.h"
 #include "nthw_pcie3.h"
 #include "nthw_iic.h"
+#include "nthw_i2cm.h"
 
 #include "nthw_sdc.h"
 
diff --git a/drivers/net/ntnic/nthw/core/include/nthw_i2cm.h b/drivers/net/ntnic/nthw/core/include/nthw_i2cm.h
new file mode 100644
index 0000000000..6e0ec4cf5e
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/include/nthw_i2cm.h
@@ -0,0 +1,50 @@ 
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef __NTHW_II2CM_H__
+#define __NTHW_II2CM_H__
+
+#include "nthw_fpga_model.h"
+#include "pthread.h"
+
+struct nt_i2cm {
+	nthw_fpga_t *mp_fpga;
+
+	nthw_module_t *m_mod_i2cm;
+
+	int mn_i2c_instance;
+
+	nthw_register_t *mp_reg_prer_low;
+	nthw_field_t *mp_fld_prer_low_prer_low;
+
+	nthw_register_t *mp_reg_prer_high;
+	nthw_field_t *mp_fld_prer_high_prer_high;
+
+	nthw_register_t *mp_reg_ctrl;
+	nthw_field_t *mp_fld_ctrl_ien;
+	nthw_field_t *mp_fld_ctrl_en;
+
+	nthw_register_t *mp_reg_data;
+	nthw_field_t *mp_fld_data_data;
+
+	nthw_register_t *mp_reg_cmd_status;
+	nthw_field_t *mp_fld_cmd_status_cmd_status;
+
+	nthw_register_t *mp_reg_select;
+	nthw_field_t *mp_fld_select_select;
+
+	nthw_register_t *mp_reg_io_exp;
+	nthw_field_t *mp_fld_io_exp_rst;
+	nthw_field_t *mp_fld_io_exp_int_b;
+
+	pthread_mutex_t i2cmmutex;
+};
+
+typedef struct nt_i2cm nthw_i2cm_t;
+
+int nthw_i2cm_read(nthw_i2cm_t *p, uint8_t dev_addr, uint8_t reg_addr, uint8_t *value);
+int nthw_i2cm_write(nthw_i2cm_t *p, uint8_t dev_addr, uint8_t reg_addr, uint8_t value);
+
+#endif	/* __NTHW_II2CM_H__ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_fpga.c b/drivers/net/ntnic/nthw/core/nthw_fpga.c
index 38169176a5..d70205e5e3 100644
--- a/drivers/net/ntnic/nthw/core/nthw_fpga.c
+++ b/drivers/net/ntnic/nthw/core/nthw_fpga.c
@@ -19,12 +19,14 @@  int nthw_fpga_get_param_info(struct fpga_info_s *p_fpga_info, nthw_fpga_t *p_fpg
 {
 	mcu_info_t *p_mcu_info = &p_fpga_info->mcu_info;
 
+	const int n_nims = nthw_fpga_get_product_param(p_fpga, NT_NIMS, -1);
 	const int n_phy_ports = nthw_fpga_get_product_param(p_fpga, NT_PHY_PORTS, -1);
 	const int n_phy_quads = nthw_fpga_get_product_param(p_fpga, NT_PHY_QUADS, -1);
 	const int n_rx_ports = nthw_fpga_get_product_param(p_fpga, NT_RX_PORTS, -1);
 	const int n_tx_ports = nthw_fpga_get_product_param(p_fpga, NT_TX_PORTS, -1);
 	const int n_vf_offset = nthw_fpga_get_product_param(p_fpga, NT_HIF_VF_OFFSET, 4);
 
+	p_fpga_info->n_nims = n_nims;
 	p_fpga_info->n_phy_ports = n_phy_ports;
 	p_fpga_info->n_phy_quads = n_phy_quads;
 	p_fpga_info->n_rx_ports = n_rx_ports;
@@ -236,6 +238,7 @@  int nthw_fpga_init(struct fpga_info_s *p_fpga_info)
 	nthw_fpga_get_param_info(p_fpga_info, p_fpga);
 
 	/* debug: report params */
+	NT_LOG(DBG, NTHW, "%s: NT_NIMS=%d\n", p_adapter_id_str, p_fpga_info->n_nims);
 	NT_LOG(DBG, NTHW, "%s: NT_PHY_PORTS=%d\n", p_adapter_id_str, p_fpga_info->n_phy_ports);
 	NT_LOG(DBG, NTHW, "%s: NT_PHY_QUADS=%d\n", p_adapter_id_str, p_fpga_info->n_phy_quads);
 	NT_LOG(DBG, NTHW, "%s: NT_RX_PORTS=%d\n", p_adapter_id_str, p_fpga_info->n_rx_ports);
diff --git a/drivers/net/ntnic/nthw/core/nthw_i2cm.c b/drivers/net/ntnic/nthw/core/nthw_i2cm.c
new file mode 100644
index 0000000000..b5f8e299ff
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_i2cm.c
@@ -0,0 +1,192 @@ 
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "nt_util.h"
+#include "ntlog.h"
+
+#include "nthw_drv.h"
+#include "nthw_register.h"
+
+#include "nthw_i2cm.h"
+
+#define NT_I2C_CMD_START 0x80
+#define NT_I2C_CMD_STOP 0x40
+#define NT_I2C_CMD_RD 0x20
+#define NT_I2C_CMD_WR 0x10
+#define NT_I2C_CMD_NACK 0x08
+#define NT_I2C_CMD_IRQ_ACK 0x01
+
+#define NT_I2C_STATUS_NACK 0x80
+#define NT_I2C_STATUS_TIP 0x02
+
+#define NT_I2C_TRANSMIT_WR 0x00
+#define NT_I2C_TRANSMIT_RD 0x01
+
+#define NUM_RETRIES 50U
+#define SLEEP_USECS 100U/* 0.1 ms */
+
+
+static bool nthw_i2cm_ready(nthw_i2cm_t *p, bool wait_for_ack)
+{
+	uint32_t flags = NT_I2C_STATUS_TIP | (wait_for_ack ? NT_I2C_STATUS_NACK : 0U);
+
+	for (uint32_t i = 0U; i < NUM_RETRIES; i++) {
+		uint32_t status = nthw_field_get_updated(p->mp_fld_cmd_status_cmd_status);
+		uint32_t ready = (status & flags) == 0U;
+		/* MUST have a short break to avoid time-outs, even if ready == true */
+		nt_os_wait_usec(SLEEP_USECS);
+
+		if (ready)
+			return true;
+	}
+
+	return false;
+}
+
+static int nthw_i2cm_write_internal(nthw_i2cm_t *p, uint8_t value)
+{
+	/* Write data to data register */
+	nthw_field_set_val_flush32(p->mp_fld_data_data, value);
+	nthw_field_set_val_flush32(p->mp_fld_cmd_status_cmd_status,
+		NT_I2C_CMD_WR | NT_I2C_CMD_IRQ_ACK);
+
+	if (!nthw_i2cm_ready(p, true)) {
+		nthw_field_set_val_flush32(p->mp_fld_cmd_status_cmd_status,
+			NT_I2C_CMD_STOP | NT_I2C_CMD_IRQ_ACK);
+		NT_LOG(ERR, NTHW, "%s: Time-out writing data %u", __PRETTY_FUNCTION__, value);
+		return 1;
+	}
+
+	/* Generate stop condition and clear interrupt */
+	nthw_field_set_val_flush32(p->mp_fld_cmd_status_cmd_status,
+		NT_I2C_CMD_STOP | NT_I2C_CMD_IRQ_ACK);
+
+	if (!nthw_i2cm_ready(p, true)) {
+		nthw_field_set_val_flush32(p->mp_fld_cmd_status_cmd_status,
+			NT_I2C_CMD_STOP | NT_I2C_CMD_IRQ_ACK);
+		NT_LOG(ERR, NTHW, "%s: Time-out sending stop condition", __PRETTY_FUNCTION__);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int nthw_i2cm_write_reg_addr_internal(nthw_i2cm_t *p, uint8_t dev_addr, uint8_t reg_addr,
+	bool send_stop)
+{
+	/* Write device address to data register */
+	nthw_field_set_val_flush32(p->mp_fld_data_data,
+		(uint8_t)(dev_addr << 1 | NT_I2C_TRANSMIT_WR));
+
+	/* #Set start condition along with secondary I2C dev_addr */
+	nthw_field_set_val_flush32(p->mp_fld_cmd_status_cmd_status,
+		NT_I2C_CMD_START | NT_I2C_CMD_WR | NT_I2C_CMD_IRQ_ACK);
+
+	if (!nthw_i2cm_ready(p, true)) {
+		nthw_field_set_val_flush32(p->mp_fld_cmd_status_cmd_status,
+			NT_I2C_CMD_STOP | NT_I2C_CMD_IRQ_ACK);
+		NT_LOG(ERR, NTHW, "%s: Time-out writing device address %u, reg_addr=%u",
+			__PRETTY_FUNCTION__, dev_addr, reg_addr);
+		return 1;
+	}
+
+	/* Writing I2C register address */
+	nthw_field_set_val_flush32(p->mp_fld_data_data, reg_addr);
+
+	if (send_stop) {
+		nthw_field_set_val_flush32(p->mp_fld_cmd_status_cmd_status,
+			NT_I2C_CMD_WR | NT_I2C_CMD_IRQ_ACK | NT_I2C_CMD_STOP);
+
+	} else {
+		nthw_field_set_val_flush32(p->mp_fld_cmd_status_cmd_status,
+			NT_I2C_CMD_WR | NT_I2C_CMD_IRQ_ACK);
+	}
+
+	if (!nthw_i2cm_ready(p, true)) {
+		nthw_field_set_val_flush32(p->mp_fld_cmd_status_cmd_status,
+			NT_I2C_CMD_STOP | NT_I2C_CMD_IRQ_ACK);
+		NT_LOG(ERR, NTHW, "%s: Time-out writing register address %u", __PRETTY_FUNCTION__,
+			reg_addr);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int nthw_i2cm_read_internal(nthw_i2cm_t *p, uint8_t dev_addr, uint8_t *value)
+{
+	/* Write I2C device address - with LSBit set to READ */
+
+	nthw_field_set_val_flush32(p->mp_fld_data_data,
+		(uint8_t)(dev_addr << 1 | NT_I2C_TRANSMIT_RD));
+	/* #Send START condition along with secondary I2C dev_addr */
+	nthw_field_set_val_flush32(p->mp_fld_cmd_status_cmd_status,
+		NT_I2C_CMD_START | NT_I2C_CMD_WR | NT_I2C_CMD_IRQ_ACK);
+
+	if (!nthw_i2cm_ready(p, true)) {
+		nthw_field_set_val_flush32(p->mp_fld_cmd_status_cmd_status,
+			NT_I2C_CMD_STOP | NT_I2C_CMD_IRQ_ACK);
+		NT_LOG(ERR, NTHW, "%s: Time-out rewriting device address %u", __PRETTY_FUNCTION__,
+			dev_addr);
+		return 1;
+	}
+
+	nthw_field_set_val_flush32(p->mp_fld_cmd_status_cmd_status,
+		NT_I2C_CMD_RD | NT_I2C_CMD_NACK | NT_I2C_CMD_IRQ_ACK);
+
+	if (!nthw_i2cm_ready(p, false)) {
+		nthw_field_set_val_flush32(p->mp_fld_cmd_status_cmd_status,
+			NT_I2C_CMD_STOP | NT_I2C_CMD_IRQ_ACK);
+		NT_LOG(ERR, NTHW, "%s: Time-out during read operation", __PRETTY_FUNCTION__);
+		return 1;
+	}
+
+	*value = (uint8_t)nthw_field_get_updated(p->mp_fld_data_data);
+
+	/* Generate stop condition and clear interrupt */
+	nthw_field_set_val_flush32(p->mp_fld_cmd_status_cmd_status,
+		NT_I2C_CMD_STOP | NT_I2C_CMD_IRQ_ACK);
+
+	if (!nthw_i2cm_ready(p, false)) {
+		nthw_field_set_val_flush32(p->mp_fld_cmd_status_cmd_status,
+			NT_I2C_CMD_STOP | NT_I2C_CMD_IRQ_ACK);
+		NT_LOG(ERR, NTHW, "%s: Time-out sending stop condition", __PRETTY_FUNCTION__);
+		return 1;
+	}
+
+	return 0;
+}
+
+int nthw_i2cm_read(nthw_i2cm_t *p, uint8_t dev_addr, uint8_t reg_addr, uint8_t *value)
+{
+	int status;
+	status = nthw_i2cm_write_reg_addr_internal(p, dev_addr, reg_addr, false);
+
+	if (status != 0)
+		return status;
+
+	status = nthw_i2cm_read_internal(p, dev_addr, value);
+
+	if (status != 0)
+		return status;
+
+	return 0;
+}
+
+int nthw_i2cm_write(nthw_i2cm_t *p, uint8_t dev_addr, uint8_t reg_addr, uint8_t value)
+{
+	int status;
+	status = nthw_i2cm_write_reg_addr_internal(p, dev_addr, reg_addr, false);
+
+	if (status != 0)
+		return status;
+
+	status = nthw_i2cm_write_internal(p, value);
+
+	if (status != 0)
+		return status;
+
+	return 0;
+}
diff --git a/drivers/net/ntnic/nthw/nthw_drv.h b/drivers/net/ntnic/nthw/nthw_drv.h
index 071618eb6e..de60ae171e 100644
--- a/drivers/net/ntnic/nthw/nthw_drv.h
+++ b/drivers/net/ntnic/nthw/nthw_drv.h
@@ -52,6 +52,7 @@  typedef struct fpga_info_s {
 
 	int n_fpga_debug_mode;
 
+	int n_nims;
 	int n_phy_ports;
 	int n_phy_quads;
 	int n_rx_ports;
diff --git a/drivers/net/ntnic/ntnic_mod_reg.h b/drivers/net/ntnic/ntnic_mod_reg.h
index 68629412b7..3189b04f33 100644
--- a/drivers/net/ntnic/ntnic_mod_reg.h
+++ b/drivers/net/ntnic/ntnic_mod_reg.h
@@ -22,6 +22,8 @@  const struct link_ops_s *get_100g_link_ops(void);
 void link_100g_init(void);
 
 struct port_ops {
+	bool (*get_nim_present)(struct adapter_info_s *p, int port);
+
 	/*
 	 * port:s link mode
 	 */
@@ -60,6 +62,11 @@  struct port_ops {
 
 	uint32_t (*get_link_speed_capabilities)(struct adapter_info_s *p, int port);
 
+	/*
+	 * port: nim capabilities
+	 */
+	nim_i2c_ctx_t (*get_nim_capabilities)(struct adapter_info_s *p, int port);
+
 	/*
 	 * port: tx power
 	 */