From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <dev-bounces@dpdk.org>
Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124])
	by inbox.dpdk.org (Postfix) with ESMTP id E75834414A;
	Mon,  3 Jun 2024 18:20:45 +0200 (CEST)
Received: from mails.dpdk.org (localhost [127.0.0.1])
	by mails.dpdk.org (Postfix) with ESMTP id BC24E42EE0;
	Mon,  3 Jun 2024 18:18:59 +0200 (CEST)
Received: from egress-ip42b.ess.de.barracuda.com
 (egress-ip42b.ess.de.barracuda.com [18.185.115.246])
 by mails.dpdk.org (Postfix) with ESMTP id A8BDF42EB9
 for <dev@dpdk.org>; Mon,  3 Jun 2024 18:18:50 +0200 (CEST)
Received: from EUR01-HE1-obe.outbound.protection.outlook.com
 (mail-he1eur01lp2051.outbound.protection.outlook.com [104.47.0.51]) by
 mx-outbound43-249.eu-central-1c.ess.aws.cudaops.com (version=TLSv1.2
 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO);
 Mon, 03 Jun 2024 16:18:48 +0000
ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none;
 b=PQGrid+burfYL0bZ1U0j+jr514LdphPI92EJyrk5DKH4Axq18DtCWYullJSFbTgXnwjTrNJTpFnTcr+RCaVs7F+IuR7N8raYqX/ymhhnP7maGt54JXk2F3aRLn41HnZBik0LLWeOK8QhFCjwBD9TaO5/DUKYA/EFAXC+ECl2c76S858uLhQXfEmtv6/GC9xooOfK+Kqwl48JPP2Ih2xSfY9NvQ6Ps9BCmmvI8pFjqnIQ12wFiswOK3PKBt089TELThnli1D/Mhi1ylZLkX9+rpKqSnEiy6u0PwUHfXQLmMM5VGLF/AvsMr9JU/UPueGhndJ+ImH5hKDMDkQmbsPSKw==
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; 
 s=arcselector9901;
 h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1;
 bh=HHHEW7Vn9FPECNr5OCKiJDlFzzwHghoutaUEYn6ofCU=;
 b=OOzFIpjj6kjukPsLOTLMtFZUslnN5YUtjQSImZbolB6vOP52DcFIKMPfI0r4TMxEZO4GKCxuJFKbJbE2o+j71kramu1lvTIRWR672zZK+5m2uCsk5bglR2q8I0EkrBtVPNpDqRU/nDGBmnldDda39n3H2cp63NOs+pZLGW2jWu39TAJgfYFxVAhTCc7RN32W5iBFhvzeBHsUPlEthYdiGwBxsd9KZ/VXmCKK1uj59kagX11qB/moOUS9dmT31IqPelNr+TR5NiXhXQfKayai18zgpcB7PfM1tYSB7syoKRcsfxM2MeKit8+kG/d8eZHchNem9A/a9N/K5+UbL2k5xw==
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=fail (sender ip is
 178.72.21.4) smtp.rcpttodomain=dpdk.org smtp.mailfrom=napatech.com;
 dmarc=fail (p=reject sp=reject pct=100) action=oreject
 header.from=napatech.com; dkim=none (message not signed); arc=none (0)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=napatech.com;
 s=selector1;
 h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck;
 bh=HHHEW7Vn9FPECNr5OCKiJDlFzzwHghoutaUEYn6ofCU=;
 b=ETof3eKS0rvhslG5vL5nCGl7ftRH3yc6kOAchI8wp986t6kiqbai168VNLuJJsvUAP6zFSRAuwrxxFUGG7ZSYMQjrS2SyNt9Vl0vLca8PLDvjJAhJtLT32PG27WQ1TIizLaSeezEAgNZa0W0x+8IsR97FEKQP4PIzsDCM8DJzBA=
Received: from AM6P191CA0086.EURP191.PROD.OUTLOOK.COM (2603:10a6:209:8a::27)
 by VI1P190MB0639.EURP190.PROD.OUTLOOK.COM (2603:10a6:800:11e::23) with
 Microsoft SMTP Server (version=TLS1_2,
 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7633.27; Mon, 3 Jun
 2024 16:18:44 +0000
Received: from AMS0EPF00000196.eurprd05.prod.outlook.com
 (2603:10a6:209:8a:cafe::86) by AM6P191CA0086.outlook.office365.com
 (2603:10a6:209:8a::27) with Microsoft SMTP Server (version=TLS1_2,
 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7633.29 via Frontend
 Transport; Mon, 3 Jun 2024 16:18:44 +0000
X-MS-Exchange-Authentication-Results: spf=fail (sender IP is 178.72.21.4)
 smtp.mailfrom=napatech.com; dkim=none (message not signed)
 header.d=none;dmarc=fail action=oreject header.from=napatech.com;
Received-SPF: Fail (protection.outlook.com: domain of napatech.com does not
 designate 178.72.21.4 as permitted sender) receiver=protection.outlook.com;
 client-ip=178.72.21.4; helo=localhost.localdomain;
Received: from localhost.localdomain (178.72.21.4) by
 AMS0EPF00000196.mail.protection.outlook.com (10.167.16.217) with Microsoft
 SMTP Server id 15.20.7633.15 via Frontend Transport; Mon, 3 Jun 2024 16:18:44
 +0000
From: Serhii Iliushyk <sil-plv@napatech.com>
To: dev@dpdk.org
Cc: mko-plv@napatech.com, ckm@napatech.com, andrew.rybchenko@oktetlabs.ru,
 ferruh.yigit@amd.com
Subject: [PATCH v3 17/17] net/ntnic: add NIM module
Date: Mon,  3 Jun 2024 18:18:14 +0200
Message-ID: <20240603161822.59552-17-sil-plv@napatech.com>
X-Mailer: git-send-email 2.45.0
In-Reply-To: <20240603161822.59552-1-sil-plv@napatech.com>
References: <20240530144929.4127931-1-sil-plv@napatech.com>
 <20240603161822.59552-1-sil-plv@napatech.com>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-EOPAttributedMessage: 0
X-MS-PublicTrafficType: Email
X-MS-TrafficTypeDiagnostic: AMS0EPF00000196:EE_|VI1P190MB0639:EE_
Content-Type: text/plain
X-MS-Office365-Filtering-Correlation-Id: 7d209e08-4138-4aca-01d6-08dc83e8d70e
X-MS-Exchange-SenderADCheck: 1
X-MS-Exchange-AntiSpam-Relay: 0
X-Microsoft-Antispam: BCL:0;
 ARA:13230031|1800799015|82310400017|36860700004|376005; 
X-Microsoft-Antispam-Message-Info: =?us-ascii?Q?O7XpZuYpMoi7PQF/Ds77KLKGgSfelUscAsGhiL8R+sOOuczxyW5hZjawhp+s?=
 =?us-ascii?Q?1YJsHHytP+NY3PwH8V8MymNrXijsEuUKhwDlHJ2ltRLXGTq4ioGEeQLF2fpw?=
 =?us-ascii?Q?bZvrtLNlW1q/pDyQZzp2e6Ttt8cQr8MUZtGZmZKwcK96ZAmpcgyoiXB64t+c?=
 =?us-ascii?Q?0emHZL+ouf6glX2St5bCbetXYNHlCBazAyF62yMkDh5SwrQG95W+IlEu4oLU?=
 =?us-ascii?Q?hvJxsniDEWJsJMBmf/loHc5ueWFsCds6kbQRWx4UBrT/teGox8cBvlwM7dA9?=
 =?us-ascii?Q?8M7kiZAXHdkKLKNEHtP2kgCeIEPNtHQECp2zxRtsDy7+8rmTKn0Qwus63ak/?=
 =?us-ascii?Q?w6lF9Skwb7wuVOHWhHP/WXKbSlLboSdYYqfz/ney8Vv6WW8Fq2G3Wm2knWrN?=
 =?us-ascii?Q?D8FYrOjZQOzn2EXM2VjA0bcBy0BttqoszmTpo0WgklGd8GrsGtGDZMZWzqd9?=
 =?us-ascii?Q?k+nWXHlG6OytbWJCVDa6zT7c8Yxd/Od9DBzriGgOIhjwmDBp8yn0kBTh82vU?=
 =?us-ascii?Q?ZIF1Z41TjlZShloGs9wWVgWX0FGeYXhn5V/NCq+KulcmVKyKZ0/UmTs0jIvx?=
 =?us-ascii?Q?yN2/Jjo9GufnyMyd6tLjfYyOWAFDa5GTD4DPNhAZKbU+2+B+0VrWvUP6NziG?=
 =?us-ascii?Q?oAkZm6HBmPyFhtrKLO4HCIbMS7mt6I301CNhnHl2SIVqhbFVNHk3+HE2uOGf?=
 =?us-ascii?Q?AhMBgMV4EVrAY956Cb8p+RuXQHkpsmcqyUKfLw4KSiC0mIU50kIo0gOWpF3Z?=
 =?us-ascii?Q?Kce5+tMFzgn/LHcvoVnwta+qT05V4NJ971qBHbQ8ipPbkG0UkEV/c+JdBZJe?=
 =?us-ascii?Q?d4JGZrwcI6cIyQaPNzqmdR/K+ja3MLa3xzB91IpY0Q1NBxnvA9fhlb9TWblM?=
 =?us-ascii?Q?OvqJaQl+heFjfDxwnx8mGef1DjqArBjM+OOJ7rNGhLbqwSV8hLmxLeTpw1ub?=
 =?us-ascii?Q?DEQeGAZDlvkIcaw2O4pLm9Bsh3V9ofpivtEyY/ZtiV8259W16FJ6qsxz6uOQ?=
 =?us-ascii?Q?qIW1bOnmkmKeVR1guv0WgmZtWpaySSsn82hC6A5d/jA1oP/DGPrfYoDf62I1?=
 =?us-ascii?Q?MuohPvJWucZ/TOhq08obB5GMyFge4/NSyGxldZTHM6rf/oud3YGs9Vm7VRit?=
 =?us-ascii?Q?tvCIdTBFV8wNTfNFpyITyYQlRfZcjHLdecWMKhRqVx5celPNsc5+CFH7e5w5?=
 =?us-ascii?Q?S/sndekOG3I3+AQW4hyNpBtUfcSdEAFpo25UX2/mO3MDqpVNU1dijnP+OIz+?=
 =?us-ascii?Q?dQGoV/XTyg9l8vnYRGUZq6FoWFIo8iskDSCnBSkOnsv75pAvb8rVr1Z0aABv?=
 =?us-ascii?Q?qgO9oUokjFbU7rY/qZCSD+P+s+z+WbvbDhQx64ts1TaMCyKAFRFmbyzfOjPS?=
 =?us-ascii?Q?tkq4WZk=3D?=
X-Forefront-Antispam-Report: CIP:178.72.21.4; CTRY:DK; LANG:en; SCL:1; SRV:;
 IPV:NLI; SFV:NSPM; H:localhost.localdomain; PTR:InfoDomainNonexistent;
 CAT:NONE; SFS:(13230031)(1800799015)(82310400017)(36860700004)(376005);
 DIR:OUT; SFP:1102; 
X-MS-Exchange-AntiSpam-ExternalHop-MessageData-ChunkCount: 1
X-MS-Exchange-AntiSpam-ExternalHop-MessageData-0: jbwdtRRFYrG3MXCKnZsFMh/ZtodAigZQ+lQkrfldq+iL0SI4DD6GFGWnhJEzrrr+fRFxXpyVZ7wpi/TE6bVjPZ74NW6bN+wCHBji7lN0uGajsRTFxsrkEumTPzKb1/c7Qm1uiZ0qIJCxZNZKPxryOeBnKCDDQqNbBmJ6n8W1XVzq5ge13OhPpWk/4Ro4JEJ5R0NKen9dp9fIIdd1cIHCFobUEMBs1qIs2uYt/ZDj4KdiZ+xmhEUasLgZXIcVF+dm+eGhc9fKnwXOjn4bkqF9J3DZVtq7L+Z6GiWvYh3+43E9QtgE6s41MZDFEb11PDaTb7hTtiFLU2xMgW8HfRF68vz3ZnmSB6ktRXJrh0fPYnbvDUhaUhNq79Yh7p7DYljtw0f6STsmUSL/SCP4r8ENq+1A02UPNaSs2QYxsCEuO7nSSFRUooX7uZktuJvuDqLq7p+MzTllIh3tBLSw1VU6q7vtauf8S6XxOg7ak7+UY80SlD0ruR+JXa5QAwbE25ZSKjpgN0IePWnfT9dq5ENJS8Zi26tr9wUjHutlgygnpiBBWpOk8P3WPDrGCmnAxoL5aivU0dUskqaImP9HKIkad27D1Pv8EWa7Hx+j7QVpb9NcpW+9YIpSTRss4+jxzF8V5U2oWtQia/F+qBgkP16asw==
X-OriginatorOrg: napatech.com
X-MS-Exchange-CrossTenant-OriginalArrivalTime: 03 Jun 2024 16:18:44.2188 (UTC)
X-MS-Exchange-CrossTenant-Network-Message-Id: 7d209e08-4138-4aca-01d6-08dc83e8d70e
X-MS-Exchange-CrossTenant-Id: c4540d0b-728a-4233-9da5-9ea30c7ec3ed
X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=c4540d0b-728a-4233-9da5-9ea30c7ec3ed; Ip=[178.72.21.4];
 Helo=[localhost.localdomain]
X-MS-Exchange-CrossTenant-AuthSource: AMS0EPF00000196.eurprd05.prod.outlook.com
X-MS-Exchange-CrossTenant-AuthAs: Anonymous
X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem
X-MS-Exchange-Transport-CrossTenantHeadersStamped: VI1P190MB0639
X-BESS-ID: 1717431528-311257-12792-8284-1
X-BESS-VER: 2019.1_20240530.1612
X-BESS-Apparent-Source-IP: 104.47.0.51
X-BESS-Parts: H4sIAAAAAAACA4uuVkqtKFGyUioBkjpK+cVKVqbGphbmQGYGUDQ1NSXNIiU1yd
 Q0xdjQxCjR0sDENDXFMMXAwizZwNjcSKk2FgC0pyowQgAAAA==
X-BESS-Outbound-Spam-Score: 0.50
X-BESS-Outbound-Spam-Report: Code version 3.2,
 rules version 3.2.2.256698 [from 
 cloudscan17-209.eu-central-1b.ess.aws.cudaops.com]
 Rule breakdown below
 pts rule name              description
 ---- ---------------------- --------------------------------
 0.50 BSF_RULE7568M          META: Custom Rule 7568M 
 0.00 BSF_BESS_OUTBOUND      META: BESS Outbound 
X-BESS-Outbound-Spam-Status: SCORE=0.50 using account:ESS113687 scores of
 KILL_LEVEL=7.0 tests=BSF_RULE7568M, BSF_BESS_OUTBOUND
X-BESS-BRTS-Status: 1
X-BeenThere: dev@dpdk.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: DPDK patches and discussions <dev.dpdk.org>
List-Unsubscribe: <https://mails.dpdk.org/options/dev>,
 <mailto:dev-request@dpdk.org?subject=unsubscribe>
List-Archive: <http://mails.dpdk.org/archives/dev/>
List-Post: <mailto:dev@dpdk.org>
List-Help: <mailto:dev-request@dpdk.org?subject=help>
List-Subscribe: <https://mails.dpdk.org/listinfo/dev>,
 <mailto:dev-request@dpdk.org?subject=subscribe>
Errors-To: dev-bounces@dpdk.org

Includes support for NIM QSFP and QSFP+.

Signed-off-by: Serhii Iliushyk <sil-plv@napatech.com>
v3:
* Fix meson file: list of source files
---
 drivers/net/ntnic/meson.build                 |   13 +
 drivers/net/ntnic/nim/i2c_nim.c               | 1359 +++++++++++++++++
 drivers/net/ntnic/nim/i2c_nim.h               |   77 +
 .../net/ntnic/nim/include/qsfp_registers.h    |   58 +
 drivers/net/ntnic/nim/nim_defines.h           |   69 +
 drivers/net/ntnic/nim/nt_link_speed.c         |  115 ++
 drivers/net/ntnic/nim/nt_link_speed.h         |   16 +
 7 files changed, 1707 insertions(+)
 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/include/qsfp_registers.h
 create mode 100644 drivers/net/ntnic/nim/nim_defines.h
 create mode 100644 drivers/net/ntnic/nim/nt_link_speed.c
 create mode 100644 drivers/net/ntnic/nim/nt_link_speed.h

diff --git a/drivers/net/ntnic/meson.build b/drivers/net/ntnic/meson.build
index 74c9e93e72..ae2ff967b1 100644
--- a/drivers/net/ntnic/meson.build
+++ b/drivers/net/ntnic/meson.build
@@ -40,6 +40,7 @@ includes = [
     include_directories('nthw'),
     include_directories('nthw/supported'),
     include_directories('nthw/model'),
+    include_directories('nim/include'),
 ]
 
 # ntnic_core
@@ -59,6 +60,9 @@ if fs.is_dir('nthw/core/nt400dxx')
     includes += include_directories('nthw/core/nt400dxx')
     includes += include_directories('nthw/core/nt400dxx/reset')
 endif
+includes += include_directories('adapter')
+includes += include_directories('link_mgmt')
+includes += include_directories('nim')
 if fs.is_dir('nim/sfp_sensors')
     includes += include_directories('nim/sfp_sensors')
 endif
@@ -74,10 +78,17 @@ headers = files('rte_pmd_ntnic.h')
 
 # all sources
 sources = files(
+    'adapter/nt4ga_adapter.c',
+    'adapter/nt4ga_pci_ta_tg.c',
+    'adapter/nt4ga_tfg.c',
     'dpdk_mod_reg.c',
     'adapter/nt4ga_adapter.c',
     'adapter/nt4ga_pci_ta_tg.c',
     'adapter/nt4ga_tfg.c',
+    'link_mgmt/link_100g/nt4ga_link_100g.c',
+    'link_mgmt/nt4ga_link.c',
+    'nim/i2c_nim.c',
+    'nim/nt_link_speed.c',
     'nthw/supported/nthw_fpga_9563_055_039_0000.c',
     'nthw/supported/nthw_fpga_instances.c',
     'nthw/supported/nthw_fpga_mod_str_map.c',
@@ -89,9 +100,11 @@ sources = files(
     'nthw/core/nthw_fpga_rst.c',
     'nthw/core/nthw_gfg.c',
     'nthw/core/nthw_gmf.c',
+    'nthw/core/nthw_gpio_phy.c',
     'nthw/core/nthw_hif.c',
     'nthw/core/nthw_i2cm.c',
     'nthw/core/nthw_iic.c',
+    'nthw/core/nthw_mac_pcs.c',
     'nthw/core/nthw_mac_pcs_xxv.c',
     'nthw/core/nthw_pcie3.c',
     'nthw/core/nthw_pci_rd_tg.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..c09f94d515
--- /dev/null
+++ b/drivers/net/ntnic/nim/i2c_nim.c
@@ -0,0 +1,1359 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "nthw_drv.h"
+#include "i2c_nim.h"
+#include "ntlog.h"
+#include "nt_util.h"
+#include "ntnic_mod_reg.h"
+
+#include "qsfp_registers.h"
+
+#include <assert.h>
+#include <string.h>	/* memcmp, memset */
+
+/*
+ * Nim functions
+ */
+#define QSFP_SUP_LEN_INFO_LIN_ADDR 142	/* 5bytes */
+#define QSFP_TRANSMITTER_TYPE_LIN_ADDR 147	/* 1byte */
+#define QSFP_CONTROL_STATUS_LIN_ADDR 86
+#define QSFP_SOFT_TX_ALL_DISABLE_BITS 0x0F
+#define QSFP_SPEC_COMPLIANCE_CODES_ADDR 131	/* 8 bytes */
+#define QSFP_EXT_SPEC_COMPLIANCE_CODES_ADDR 192	/* 1 byte */
+#define NIM_READ false
+#define NIM_WRITE true
+#define NIM_PAGE_SEL_REGISTER 127
+#define NIM_I2C_0XA0 0xA0	/* Basic I2C address */
+#define QSFP_VENDOR_NAME_LIN_ADDR 148	/* 16bytes */
+#define QSFP_VENDOR_PN_LIN_ADDR 168	/* 16bytes */
+#define QSFP_VENDOR_SN_LIN_ADDR 196	/* 16bytes */
+#define QSFP_VENDOR_DATE_LIN_ADDR 212	/* 8bytes */
+#define QSFP_VENDOR_REV_LIN_ADDR 184	/* 2bytes */
+#define QSFP_DMI_OPTION_LIN_ADDR 220
+
+#define QSFP_EXTENDED_IDENTIFIER 129
+#define QSFP_POWER_CLASS_BITS_1_4 0xC0
+#define QSFP_POWER_CLASS_BITS_5_7 0x03
+
+static bool page_addressing(nt_nim_identifier_t id)
+{
+	switch (id) {
+	case NT_NIM_SFP_SFP_PLUS:
+		return false;
+
+	case NT_NIM_XFP:
+		return true;
+
+	case NT_NIM_QSFP:
+	case NT_NIM_QSFP_PLUS:
+	case NT_NIM_QSFP28:
+		return true;
+
+	default:
+		NT_LOG(DBG, ETHDEV, "%s: Unknown NIM identifier %d\n", __func__, id);
+		return false;
+	}
+}
+
+nt_nim_identifier_t translate_nimid(const nim_i2c_ctx_t *ctx)
+{
+	return (nt_nim_identifier_t)ctx->nim_id;
+}
+
+static int nim_read_write_i2c_data(nim_i2c_ctx_p ctx, bool do_write, uint16_t lin_addr,
+	uint8_t i2c_addr, uint8_t 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, ETHDEV,
+					"%s: Cannot set up page for linear address %u\n", __func__,
+					lin_addr);
+				return -1;
+			}
+		}
+
+		if (nim_read_write_i2c_data(ctx, do_write, lin_addr, i2c_addr, a_reg_addr,
+				(uint8_t)seq_cnt, p_data) != 0) {
+			NT_LOG(ERR, ETHDEV, "%s: Call to nim_read_write_i2c_data failed\n",
+				__func__);
+			return -1;
+		}
+
+		p_data += seq_cnt;
+		i = (uint16_t)(i + seq_cnt);
+		lin_addr = (uint16_t)(lin_addr + (seq_cnt / multi_byte));
+	}
+
+	return 0;
+}
+
+int read_data_lin(nim_i2c_ctx_p ctx, uint16_t lin_addr, uint16_t length, void *data)
+{
+	/* Wrapper for using Mutex for QSFP TODO */
+	return nim_read_write_data_lin(ctx, page_addressing(ctx->nim_id), lin_addr, length, data,
+			NIM_READ);
+}
+
+/* Read and return a single byte */
+static uint8_t read_byte(nim_i2c_ctx_p ctx, uint16_t addr)
+{
+	uint8_t data;
+	read_data_lin(ctx, addr, sizeof(data), &data);
+	return data;
+}
+
+static int nim_read_id(nim_i2c_ctx_t *ctx)
+{
+	/* We are only reading the first byte so we don't care about pages here. */
+	const bool USE_PAGE_ADDRESSING = false;
+
+	if (nim_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;
+}
+
+static int nim_read_vendor_info(nim_i2c_ctx_p ctx, uint16_t addr, uint8_t max_len, char *p_data);
+
+static void qsfp_read_vendor_info(nim_i2c_ctx_t *ctx)
+{
+	nim_read_vendor_info(ctx, QSFP_VENDOR_NAME_LIN_ADDR, sizeof(ctx->vendor_name),
+		ctx->vendor_name);
+	nim_read_vendor_info(ctx, QSFP_VENDOR_PN_LIN_ADDR, sizeof(ctx->prod_no), ctx->prod_no);
+	nim_read_vendor_info(ctx, QSFP_VENDOR_SN_LIN_ADDR, sizeof(ctx->serial_no), ctx->serial_no);
+	nim_read_vendor_info(ctx, QSFP_VENDOR_DATE_LIN_ADDR, sizeof(ctx->date), ctx->date);
+	nim_read_vendor_info(ctx, QSFP_VENDOR_REV_LIN_ADDR, (uint8_t)(sizeof(ctx->rev) - 2),
+		ctx->rev);	/*OBS Only two bytes*/
+}
+static int qsfp_nim_state_build(nim_i2c_ctx_t *ctx, sfp_nim_state_t *state)
+{
+	int res = 0;	/* unused due to no readings from HW */
+
+	assert(ctx && state);
+	assert(ctx->nim_id != NT_NIM_UNKNOWN && "Nim is not initialized");
+
+	(void)memset(state, 0, sizeof(*state));
+
+	switch (ctx->nim_id) {
+	case 12U:
+		state->br = 10U;/* QSFP: 4 x 1G = 4G */
+		break;
+
+	case 13U:
+		state->br = 103U;	/* QSFP+: 4 x 10G = 40G */
+		break;
+
+	case 17U:
+		state->br = 255U;	/* QSFP28: 4 x 25G = 100G */
+		break;
+
+	default:
+		NT_LOG(INF, PMD, "%s:%d nim_id = %u is not an QSFP/QSFP+/QSFP28 module\n",
+			__func__, __LINE__, ctx->nim_id);
+		res = -1;
+	}
+
+	return res;
+}
+
+int nim_state_build(nim_i2c_ctx_t *ctx, sfp_nim_state_t *state)
+{
+	if (translate_nimid(ctx) == NT_NIM_SFP_SFP_PLUS)
+		return -1;
+
+	else
+		return qsfp_nim_state_build(ctx, state);
+}
+
+const char *nim_id_to_text(uint8_t nim_id)
+{
+	switch (nim_id) {
+	case 0x0:
+		return "UNKNOWN";
+
+	case 0x1:
+		return "GBIC";
+
+	case 0x2:
+		return "FIXED";
+
+	case 0x3:
+		return "SFP/SFP+";
+
+	case 0x04:
+		return "300 pin XBI";
+
+	case 0x05:
+		return "XEN-PAK";
+
+	case 0x06:
+		return "XFP";
+
+	case 0x07:
+		return "XFF";
+
+	case 0x08:
+		return "XFP-E";
+
+	case 0x09:
+		return "XPAK";
+
+	case 0x0A:
+		return "X2";
+
+	case 0x0B:
+		return "DWDM";
+
+	case 0x0C:
+		return "QSFP";
+
+	case 0x0D:
+		return "QSFP+";
+
+	case 0x11:
+		return "QSFP28";
+
+	case 0x12:
+		return "CFP4";
+
+	default:
+		return "ILLEGAL!";
+	}
+}
+
+/*
+ * Disable laser for specific lane or all lanes
+ */
+int nim_qsfp_plus_nim_set_tx_laser_disable(nim_i2c_ctx_p ctx, bool disable, int lane_idx)
+{
+	uint8_t value;
+	uint8_t mask;
+	const bool pg_addr = page_addressing(ctx->nim_id);
+
+	if (lane_idx < 0)	/* If no lane is specified then all lanes */
+		mask = QSFP_SOFT_TX_ALL_DISABLE_BITS;
+
+	else
+		mask = (uint8_t)(1U << lane_idx);
+
+	if (nim_read_write_data_lin(ctx, pg_addr, QSFP_CONTROL_STATUS_LIN_ADDR, sizeof(value),
+			&value, NIM_READ) != 0) {
+		return -1;
+	}
+
+	if (disable)
+		value |= mask;
+
+	else
+		value &= (uint8_t)(~mask);
+
+	if (nim_read_write_data_lin(ctx, pg_addr, QSFP_CONTROL_STATUS_LIN_ADDR, sizeof(value),
+			&value, NIM_WRITE) != 0) {
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * Read vendor information at a certain address. Any trailing whitespace is
+ * removed and a missing string termination in the NIM data is handled.
+ */
+static int nim_read_vendor_info(nim_i2c_ctx_p ctx, uint16_t addr, uint8_t max_len, char *p_data)
+{
+	const bool pg_addr = page_addressing(ctx->nim_id);
+	int i;
+	/* Subtract "1" from max_len that includes a terminating "0" */
+
+	if (nim_read_write_data_lin(ctx, pg_addr, addr, (uint8_t)(max_len - 1), (uint8_t *)p_data,
+			NIM_READ) != 0) {
+		return -1;
+	}
+
+	/* Terminate at first found white space */
+	for (i = 0; i < max_len - 1; i++) {
+		if (*p_data == ' ' || *p_data == '\n' || *p_data == '\t' || *p_data == '\v' ||
+			*p_data == '\f' || *p_data == '\r') {
+			*p_data = '\0';
+			return 0;
+		}
+
+		p_data++;
+	}
+
+	/*
+	 * Add line termination as the very last character, if it was missing in the
+	 * NIM data
+	 */
+	*p_data = '\0';
+	return 0;
+}
+
+/*
+ * Import length info in various units from NIM module data and convert to meters
+ */
+static void nim_import_len_info(nim_i2c_ctx_p ctx, uint8_t *p_nim_len_info, uint16_t *p_nim_units)
+{
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(ctx->len_info); i++)
+		if (*(p_nim_len_info + i) == 255) {
+			ctx->len_info[i] = 65535;
+
+		} else {
+			uint32_t len = *(p_nim_len_info + i) * *(p_nim_units + i);
+
+			if (len > 65535)
+				ctx->len_info[i] = 65535;
+
+			else
+				ctx->len_info[i] = (uint16_t)len;
+		}
+}
+
+static int qsfpplus_read_basic_data(nim_i2c_ctx_t *ctx)
+{
+	const bool pg_addr = page_addressing(ctx->nim_id);
+	uint8_t options;
+	uint8_t value;
+	uint8_t nim_len_info[5];
+	uint16_t nim_units[5] = { 1000, 2, 1, 1, 1 };	/* QSFP MSA units in meters */
+	const char *yes_no[2] = { "No", "Yes" };
+	(void)yes_no;
+	NT_LOG(DBG, ETHDEV, "Instance %d: NIM id: %s (%d)\n", ctx->instance,
+		nim_id_to_text(ctx->nim_id), ctx->nim_id);
+
+	/* Read DMI options */
+	if (nim_read_write_data_lin(ctx, pg_addr, QSFP_DMI_OPTION_LIN_ADDR, sizeof(options),
+			&options, NIM_READ) != 0) {
+		return -1;
+	}
+
+	ctx->avg_pwr = options & QSFP_DMI_AVG_PWR_BIT;
+	NT_LOG(DBG, ETHDEV, "Instance %d: NIM options: (DMI: Yes, AvgPwr: %s)\n", ctx->instance,
+		yes_no[ctx->avg_pwr]);
+
+	qsfp_read_vendor_info(ctx);
+	NT_LOG(DBG, PMD,
+		"Instance %d: NIM info: (Vendor: %s, PN: %s, SN: %s, Date: %s, Rev: %s)\n",
+		ctx->instance, ctx->vendor_name, ctx->prod_no, ctx->serial_no, ctx->date, ctx->rev);
+
+	if (nim_read_write_data_lin(ctx, pg_addr, QSFP_SUP_LEN_INFO_LIN_ADDR, sizeof(nim_len_info),
+			nim_len_info, NIM_READ) != 0) {
+		return -1;
+	}
+
+	/*
+	 * Returns supported length information in meters for various fibers as 5 indivi-
+	 * dual values: [SM(9um), EBW(50um), MM(50um), MM(62.5um), Copper]
+	 * If no length information is available for a certain entry, the returned value
+	 * will be zero. This will be the case for SFP modules - EBW entry.
+	 * If the MSBit is set the returned value in the lower 31 bits indicates that the
+	 * supported length is greater than this.
+	 */
+
+	nim_import_len_info(ctx, nim_len_info, nim_units);
+
+	/* Read required power level */
+	if (nim_read_write_data_lin(ctx, pg_addr, QSFP_EXTENDED_IDENTIFIER, sizeof(value), &value,
+			NIM_READ) != 0) {
+		return -1;
+	}
+
+	/*
+	 * Get power class according to SFF-8636 Rev 2.7, Table 6-16, Page 43:
+	 * If power class >= 5 setHighPower must be called for the module to be fully
+	 * functional
+	 */
+	if ((value & QSFP_POWER_CLASS_BITS_5_7) == 0) {
+		/* NIM in power class 1 - 4 */
+		ctx->pwr_level_req = (uint8_t)(((value & QSFP_POWER_CLASS_BITS_1_4) >> 6) + 1);
+
+	} else {
+		/* NIM in power class 5 - 7 */
+		ctx->pwr_level_req = (uint8_t)((value & QSFP_POWER_CLASS_BITS_5_7) + 4);
+	}
+
+	return 0;
+}
+
+/*
+ * Read the vendor product number and from this determine which QSFP DMI options
+ * that are present. This list also covers QSFP28 modules.
+ * This function should be used if automatic detection does not work.
+ */
+static bool qsfpplus_get_qsfp_options_from_pn(nim_i2c_ctx_p ctx)
+{
+	if (strcmp(ctx->prod_no, "FTL410QE1C") == 0) {
+		/* FINISAR FTL410QE1C, QSFP+ */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_TX_BIAS) | (1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "FTL410QE2C") == 0) {
+		/* FINISAR FTL410QE2C, QSFP+ */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY);
+
+	} else if (strcmp(ctx->prod_no, "FTL4C1QE1C") == 0) {
+		/* FINISAR FTL4C1QE1C, QSFP+ */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "AFBR-79E4Z") == 0) {
+		/*
+		 * AFBR-79E4Z: The digital diagnostic accuracy is not guaranteed so only
+		 * the mandatory temperature sensor is made available (although it will
+		 * also be inaccurate)
+		 */
+		/* AVAGO 79E4Z, QSFP+ */
+		ctx->options = (1 << NIM_OPTION_TEMP);
+
+	} else if (strcmp(ctx->prod_no, "AFBR-79E4Z-D") == 0) {
+		/* AVAGO 79E4Z-D, QSFP+ */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "AFBR-79EQDZ") == 0) {
+		/* AVAGO 79EQDZ, QSFP+ */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "AFBR-79EBRZ") == 0) {
+		/*
+		 * Avago RxOnly BiDi NIM
+		 * No sensors available not even the normally mandatory temp sensor and this
+		 * is ok since the temp sensor is not mandatory on active optical modules
+		 */
+		/* SFF-8436_rev4.1, p67 */
+		ctx->options = (1 << NIM_OPTION_RX_ONLY);
+
+	} else if (strcmp(ctx->prod_no, "AFBR-79EBPZ-NU1") == 0) {
+		/*
+		 * Avago RxTx BiDi NIM
+		 * No sensors available not even the normally mandatory temp sensor and this
+		 * is ok since the temp sensor is not mandatory on active optical modules
+		 */
+		ctx->options = 0;
+
+	} else if (strcmp(ctx->prod_no, "AFBR-79EBPZ") == 0) {
+		/*
+		 * Avago RxTx BiDi NIM
+		 * No sensors available not even the normally mandatory temp sensor and this
+		 * is ok since the temp sensor is not mandatory on active optical modules
+		 */
+		ctx->options = 0;
+
+	} else if (strcmp(ctx->prod_no, "AFBR-89CDDZ") == 0) {
+		/* AVAGO 89CDDZ, QSFP28 */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "AFBR-89BDDZ") == 0) {
+		/* AVAGO 89BDDZ, QSFP28, BiDi */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "AFBR-89BRDZ") == 0) {
+		/*
+		 * AVAGO 89BRDZ, QSFP28, BiDi, RxOnly
+		 * but sensors have been set as above except for Tx sensors
+		 */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_RX_ONLY);
+		/*
+		 * According to mail correspondence AFBR-89BRDZ is a RxOnly version of
+		 * AFBR-89BDDZ with lasers default off.
+		 * The lasers can be turned on however but should probably not because the
+		 * receivers might be degraded, and this is the cause for selling them as RxOnly.
+		 */
+
+	} else if (strcmp(ctx->prod_no, "SQF1000L4LNGG01P") == 0) {
+		/* Sumitomo SQF1000L4LNGG01P, QSFP28 */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "SQF1000L4LNGG01B") == 0) {
+		/* Sumitomo SQF1000L4LNGG01B, QSFP28 */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "SQF1001L4LNGG01P") == 0) {
+		/* Sumitomo SQF1001L4LNGG01P, QSFP28 */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "SQF1001L4LNGG01B") == 0) {
+		/* Sumitomo SQF1001L4LNGG01B, QSFP28 */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "SQF1002L4LNGG01B") == 0) {
+		/* Sumitomo SQF1002L4LNGG01B, QSFP28 */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "FIM37700/171") == 0) {
+		/* Fujitsu FIM37700/171, QSFP28 */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "FIM37700/172") == 0) {
+		/* Fujitsu FIM37700/172, QSFP28 */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "TR-FC85S-NVS") == 0) {
+		/* InnoLight TR-FC85S-NVS, QSFP28 */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "TR-FC13L-NVS") == 0) {
+		/* InnoLight TR-FC13L-NVS, QSFP28 */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "FTLC9551REPM") == 0) {
+		/* Finisar FTLC9551REPM, QSFP28 */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else if (strcmp(ctx->prod_no, "FTLC9558REPM") == 0) {
+		/* Finisar FTLC9558REPM, QSFP28 */
+		ctx->options = (1 << NIM_OPTION_TEMP) | (1 << NIM_OPTION_SUPPLY) |
+			(1 << NIM_OPTION_RX_POWER) | (1 << NIM_OPTION_TX_BIAS) |
+			(1 << NIM_OPTION_TX_POWER);
+
+	} else {
+		/*
+		 * DO NOTE: The temperature sensor is not mandatory on active/passive copper
+		 * and active optical modules
+		 */
+		ctx->options = (1 << NIM_OPTION_TEMP);
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * Try to figure out if a sensor is present by reading its value(s) and its limits.
+ * This is a highly impirical way that cannot be guaranteed to give the correct
+ * result but it was a wish not to be dependent on a PN table based solution.
+ */
+static void qsfpplus_find_qsfp_sensor_option(nim_i2c_ctx_p ctx,
+	uint16_t value_addr,
+	uint8_t lane_count,
+	uint16_t limit_addr,
+	bool two_compl,
+	uint32_t sensor_option)
+{
+	uint8_t data[8];
+	int i, j;
+	int value;
+	int value_list[4];
+	int limit;
+	int limit_list[4];
+	bool present;
+
+	/* Read current value(s) */
+	read_data_lin(ctx, value_addr, (uint16_t)(lane_count * 2), data);
+
+	for (j = 0; j < lane_count; j++) {
+		value = 0;
+
+		for (i = 0; i < 2; i++) {
+			value = value << 8;
+			value += data[2 * j + i];
+		}
+
+		if (two_compl && value >= 0x8000)
+			value = value - 0x10000;
+
+		value_list[j] = value;
+	}
+
+	/* Read limits Warning high/low Alarm high/low 4 values each two bytes */
+	read_data_lin(ctx, limit_addr, 8, data);
+
+	for (j = 0; j < 4; j++) {
+		limit = 0;
+
+		for (i = 0; i < 2; i++) {
+			limit = limit << 8;
+			limit += data[2 * j + i];
+		}
+
+		if (two_compl && limit >= 0x8000)
+			limit = limit - 0x10000;
+
+		limit_list[j] = limit;
+	}
+
+	/* Find out if limits contradicts each other */
+	int alarm_high = limit_list[0];
+	int alarm_low = limit_list[1];
+	int warn_high = limit_list[2];
+	int warn_low = limit_list[3];
+
+	bool alarm_limits = false;	/* Are they present - that is both not zero */
+	bool warn_limits = false;
+	bool limit_conflict = false;
+
+	if (alarm_high != 0 || alarm_low != 0) {
+		alarm_limits = true;
+
+		if (alarm_high <= alarm_low)
+			limit_conflict = true;
+	}
+
+	if (warn_high != 0 || warn_low != 0) {
+		warn_limits = true;
+
+		/* Warning limits must be least restrictive */
+		if (warn_high <= warn_low)
+			limit_conflict = true;
+
+		else if (warn_high > alarm_high || warn_low < alarm_low)
+			limit_conflict = true;
+	}
+
+	/* Try to deduce if the sensor is present or not */
+	present = false;
+
+	if (limit_conflict) {
+		present = false;
+
+	} else if (warn_limits || alarm_limits) {
+		/* Is one or both present and not contradictory */
+		present = true;
+
+	} else {
+		/*
+		 * All limits are zero - look at the sensor value
+		 * If one sensor is non-zero the sensor is set to be present
+		 */
+		for (j = 0; j < lane_count; j++) {
+			if (value_list[j] != 0) {
+				present = true;
+				break;
+			}
+		}
+
+		/*
+		 * If all limits and values are zero then present will be false here. In this
+		 * case it is assumed that the sensor is not present:
+		 * Experience indicates that for QSFP+ modules RxPwr will be non-zero even with
+		 * no optical input. QSFP28 modules however can easily have RxPwr equal to zero
+		 * with no optical input.
+		 * For all investigated modules it was found that if RxPwr is implemented then
+		 * the limits are also set. This is not always the case with TxBias and TxPwr
+		 * but here the measured values will be non-zero when the laser is on what it
+		 * will be just after initialization since it has no external hardware disable.
+		 */
+	}
+
+	if (present)
+		ctx->options |= (1U << sensor_option);
+}
+
+/*
+ * Find active QSFP sensors.
+ */
+static void qsfpplus_get_qsfp_options_from_data(nim_i2c_ctx_p ctx)
+{
+	ctx->options = 0;
+
+	qsfpplus_find_qsfp_sensor_option(ctx, QSFP_TEMP_LIN_ADDR, 1, QSFP_TEMP_THRESH_LIN_ADDR,
+		true, NIM_OPTION_TEMP);
+
+	qsfpplus_find_qsfp_sensor_option(ctx, QSFP_VOLT_LIN_ADDR, 1, QSFP_VOLT_THRESH_LIN_ADDR,
+		false, NIM_OPTION_SUPPLY);
+
+	qsfpplus_find_qsfp_sensor_option(ctx, QSFP_RX_PWR_LIN_ADDR, 4, QSFP_RX_PWR_THRESH_LIN_ADDR,
+		false, NIM_OPTION_RX_POWER);
+
+	qsfpplus_find_qsfp_sensor_option(ctx, QSFP_TX_PWR_LIN_ADDR, 4, QSFP_TX_PWR_THRESH_LIN_ADDR,
+		false, NIM_OPTION_TX_POWER);
+
+	qsfpplus_find_qsfp_sensor_option(ctx, QSFP_TX_BIAS_LIN_ADDR, 4, QSFP_BIAS_THRESH_LIN_ADDR,
+		false, NIM_OPTION_TX_BIAS);
+}
+
+static void qsfp28_find_port_params(nim_i2c_ctx_p ctx)
+{
+	uint8_t fiber_chan_speed;
+
+	/* Table 6-17 SFF-8636 */
+	read_data_lin(ctx, QSFP_SPEC_COMPLIANCE_CODES_ADDR, 1, &fiber_chan_speed);
+
+	if (fiber_chan_speed & (1 << 7)) {
+		/* SFF-8024, Rev 4.7, Table 4-4 */
+		uint8_t extended_specification_compliance_code = 0;
+		read_data_lin(ctx, QSFP_EXT_SPEC_COMPLIANCE_CODES_ADDR, 1,
+			&extended_specification_compliance_code);
+
+		switch (extended_specification_compliance_code) {
+		case 0x02:
+			ctx->port_type = NT_PORT_TYPE_QSFP28_SR4;
+			break;
+
+		case 0x03:
+			ctx->port_type = NT_PORT_TYPE_QSFP28_LR4;
+			break;
+
+		case 0x0B:
+			ctx->port_type = NT_PORT_TYPE_QSFP28_CR_CA_L;
+			break;
+
+		case 0x0C:
+			ctx->port_type = NT_PORT_TYPE_QSFP28_CR_CA_S;
+			break;
+
+		case 0x0D:
+			ctx->port_type = NT_PORT_TYPE_QSFP28_CR_CA_N;
+			break;
+
+		case 0x25:
+			ctx->port_type = NT_PORT_TYPE_QSFP28_DR;
+			break;
+
+		case 0x26:
+			ctx->port_type = NT_PORT_TYPE_QSFP28_FR;
+			break;
+
+		case 0x27:
+			ctx->port_type = NT_PORT_TYPE_QSFP28_LR;
+			break;
+
+		default:
+			ctx->port_type = NT_PORT_TYPE_QSFP28;
+		}
+
+	} else {
+		ctx->port_type = NT_PORT_TYPE_QSFP28;
+	}
+}
+
+/*
+ * If true the user must actively select the desired rate. If false the module
+ * however can still support several rates without the user is required to select
+ * one of them. Supported rates must then be deduced from the product number.
+ * SFF-8636, Rev 2.10a:
+ * p40: 6.2.7 Rate Select
+ * p85: A.2 Rate Select
+ */
+static bool qsfp28_is_rate_selection_enabled(nim_i2c_ctx_p ctx)
+{
+	const uint8_t ext_rate_select_compl_reg_addr = 141;
+	const uint8_t options_reg_addr = 195;
+	const uint8_t enh_options_reg_addr = 221;
+
+	uint8_t rate_select_ena = (read_byte(ctx, options_reg_addr) >> 5) & 0x01;	/* bit: 5 */
+
+	if (rate_select_ena == 0)
+		return false;
+
+	uint8_t rate_select_type =
+		(read_byte(ctx, enh_options_reg_addr) >> 2) & 0x03;	/* bit 3..2 */
+
+	if (rate_select_type != 2) {
+		NT_LOG(DBG, PMD, "NIM has unhandled rate select type (%d)", rate_select_type);
+		return false;
+	}
+
+	uint8_t ext_rate_select_ver =
+		read_byte(ctx, ext_rate_select_compl_reg_addr) & 0x03;	/* bit 1..0 */
+
+	if (ext_rate_select_ver != 0x02) {
+		NT_LOG(DBG, PMD, "NIM has unhandled extended rate select version (%d)",
+			ext_rate_select_ver);
+		return false;
+	}
+
+	return true;	/* When true selectRate() can be used */
+}
+
+static void qsfp28_set_speed_mask(nim_i2c_ctx_p ctx)
+{
+	if (ctx->port_type == NT_PORT_TYPE_QSFP28_FR || ctx->port_type == NT_PORT_TYPE_QSFP28_DR ||
+		ctx->port_type == NT_PORT_TYPE_QSFP28_LR) {
+		if (ctx->lane_idx < 0)
+			ctx->speed_mask = NT_LINK_SPEED_100G;
+
+		else
+			/* PAM-4 modules can only run on all lanes together */
+			ctx->speed_mask = 0;
+
+	} else {
+		if (ctx->lane_idx < 0)
+			ctx->speed_mask = NT_LINK_SPEED_100G;
+
+		else
+			ctx->speed_mask = NT_LINK_SPEED_25G;
+
+		if (qsfp28_is_rate_selection_enabled(ctx)) {
+			/*
+			 * It is assumed that if the module supports dual rates then the other rate
+			 * is 10G per lane or 40G for all lanes.
+			 */
+			if (ctx->lane_idx < 0)
+				ctx->speed_mask |= NT_LINK_SPEED_40G;
+
+			else
+				ctx->speed_mask = NT_LINK_SPEED_10G;
+		}
+	}
+}
+
+static void qsfpplus_find_port_params(nim_i2c_ctx_p ctx)
+{
+	uint8_t device_tech;
+	read_data_lin(ctx, QSFP_TRANSMITTER_TYPE_LIN_ADDR, sizeof(device_tech), &device_tech);
+
+	switch (device_tech & 0xF0) {
+	case 0xA0:	/* Copper cable unequalized */
+	case 0xB0:	/* Copper cable passive equalized */
+		ctx->port_type = NT_PORT_TYPE_QSFP_PASSIVE_DAC;
+		break;
+
+	case 0xC0:	/* Copper cable, near and far end limiting active equalizers */
+	case 0xD0:	/* Copper cable, far end limiting active equalizers */
+	case 0xE0:	/* Copper cable, near end limiting active equalizers */
+	case 0xF0:	/* Copper cable, linear active equalizers */
+		ctx->port_type = NT_PORT_TYPE_QSFP_ACTIVE_DAC;
+		break;
+
+	default:/* Optical */
+		ctx->port_type = NT_PORT_TYPE_QSFP_PLUS;
+		break;
+	}
+}
+
+static void qsfpplus_set_speed_mask(nim_i2c_ctx_p ctx)
+{
+	ctx->speed_mask = (ctx->lane_idx < 0) ? NT_LINK_SPEED_40G : (NT_LINK_SPEED_10G);
+}
+
+static void qsfpplus_construct(nim_i2c_ctx_p ctx, int8_t lane_idx)
+{
+	assert(lane_idx < 4);
+	ctx->specific_u.qsfp.qsfp28 = false;
+	ctx->lane_idx = lane_idx;
+	ctx->lane_count = 4;
+}
+
+static int qsfpplus_preinit(nim_i2c_ctx_p ctx, int8_t lane_idx)
+{
+	qsfpplus_construct(ctx, lane_idx);
+	int res = qsfpplus_read_basic_data(ctx);
+
+	if (!res) {
+		qsfpplus_find_port_params(ctx);
+
+		/*
+		 * If not on the known modules list try to figure out which sensors that are
+		 * present
+		 */
+		if (!qsfpplus_get_qsfp_options_from_pn(ctx)) {
+			NT_LOG(DBG, NTHW, "NIM options not known in advance - trying to detect");
+			qsfpplus_get_qsfp_options_from_data(ctx);
+		}
+
+		/*
+		 * Read if TX_DISABLE has been implemented
+		 * For passive optical modules this is required while it for copper and active
+		 * optical modules is optional. Under all circumstances register 195.4 will
+		 * indicate, if TX_DISABLE has been implemented in register 86.0-3
+		 */
+		uint8_t value;
+		read_data_lin(ctx, QSFP_OPTION3_LIN_ADDR, sizeof(value), &value);
+
+		ctx->tx_disable = (value & QSFP_OPTION3_TX_DISABLE_BIT) != 0;
+
+		if (ctx->tx_disable)
+			ctx->options |= (1 << NIM_OPTION_TX_DISABLE);
+
+		/*
+		 * Previously - considering AFBR-89BRDZ - code tried to establish if a module was
+		 * RxOnly by testing the state of the lasers after reset. Lasers were for this
+		 * module default disabled.
+		 * However that code did not work for GigaLight, GQS-MPO400-SR4C so it was
+		 * decided that this option should not be detected automatically but from PN
+		 */
+		ctx->specific_u.qsfp.rx_only = (ctx->options & (1 << NIM_OPTION_RX_ONLY)) != 0;
+		qsfpplus_set_speed_mask(ctx);
+	}
+
+	return res;
+}
+
+static void qsfp28_wait_for_ready_after_reset(nim_i2c_ctx_p ctx)
+{
+	uint8_t data;
+	bool init_complete_flag_present = false;
+
+	/*
+	 * Revision compliance
+	 * 7: SFF-8636 Rev 2.5, 2.6 and 2.7
+	 * 8: SFF-8636 Rev 2.8, 2.9 and 2.10
+	 */
+	read_data_lin(ctx, 1, sizeof(ctx->specific_u.qsfp.specific_u.qsfp28.rev_compliance),
+		&ctx->specific_u.qsfp.specific_u.qsfp28.rev_compliance);
+	NT_LOG(DBG, NTHW, "NIM RevCompliance = %d",
+		ctx->specific_u.qsfp.specific_u.qsfp28.rev_compliance);
+
+	/* Wait if lane_idx == -1 (all lanes are used) or lane_idx == 0 (the first lane) */
+	if (ctx->lane_idx > 0)
+		return;
+
+	if (ctx->specific_u.qsfp.specific_u.qsfp28.rev_compliance >= 7) {
+		/* Check if init complete flag is implemented */
+		read_data_lin(ctx, 221, sizeof(data), &data);
+		init_complete_flag_present = (data & (1 << 4)) != 0;
+	}
+
+	NT_LOG(DBG, NTHW, "NIM InitCompleteFlagPresent = %d", init_complete_flag_present);
+
+	/*
+	 * If the init complete flag is not present then wait 500ms that together with 500ms
+	 * after reset (in the adapter code) should be enough to read data from upper pages
+	 * that otherwise would not be ready. Especially BiDi modules AFBR-89BDDZ have been
+	 * prone to this when trying to read sensor options using getQsfpOptionsFromData()
+	 * Probably because access to the paged address space is required.
+	 */
+	if (!init_complete_flag_present) {
+		nt_os_wait_usec(500000);
+		return;
+	}
+
+	/* Otherwise wait for the init complete flag to be set */
+	int count = 0;
+
+	while (true) {
+		if (count > 10) {	/* 1 s timeout */
+			NT_LOG(WRN, NTHW, "Timeout waiting for module ready");
+			break;
+		}
+
+		read_data_lin(ctx, 6, sizeof(data), &data);
+
+		if (data & 0x01) {
+			NT_LOG(DBG, NTHW, "Module ready after %dms", count * 100);
+			break;
+		}
+
+		nt_os_wait_usec(100000);/* 100 ms */
+		count++;
+	}
+}
+
+static void qsfp28_get_fec_options(nim_i2c_ctx_p ctx)
+{
+	const char *const nim_list[] = {
+		"AFBR-89BDDZ",	/* Avago BiDi */
+		"AFBR-89BRDZ",	/* Avago BiDi, RxOnly */
+		"FTLC4352RKPL",	/* Finisar QSFP28-LR */
+		"FTLC4352RHPL",	/* Finisar QSFP28-DR */
+		"FTLC4352RJPL",	/* Finisar QSFP28-FR */
+		"SFBR-89BDDZ-CS4",	/* Foxconn, QSFP28 100G/40G BiDi */
+	};
+
+	for (size_t i = 0; i < ARRAY_SIZE(nim_list); i++) {
+		if (ctx->prod_no == nim_list[i]) {
+			ctx->options |= (1 << NIM_OPTION_MEDIA_SIDE_FEC);
+			ctx->specific_u.qsfp.specific_u.qsfp28.media_side_fec_ena = true;
+			NT_LOG(DBG, NTHW, "Found FEC info via PN list");
+			return;
+		}
+	}
+
+	/*
+	 * For modules not in the list find FEC info via registers
+	 * Read if the module has controllable FEC
+	 * SFF-8636, Rev 2.10a TABLE 6-28 Equalizer, Emphasis, Amplitude and Timing)
+	 * (Page 03h, Bytes 224-229)
+	 */
+	uint8_t data;
+	uint16_t addr = 227 + 3 * 128;
+	read_data_lin(ctx, addr, sizeof(data), &data);
+
+	/* Check if the module has FEC support that can be controlled */
+	ctx->specific_u.qsfp.specific_u.qsfp28.media_side_fec_ctrl = (data & (1 << 6)) != 0;
+	ctx->specific_u.qsfp.specific_u.qsfp28.host_side_fec_ctrl = (data & (1 << 7)) != 0;
+
+	if (ctx->specific_u.qsfp.specific_u.qsfp28.media_side_fec_ctrl)
+		ctx->options |= (1 << NIM_OPTION_MEDIA_SIDE_FEC);
+
+	if (ctx->specific_u.qsfp.specific_u.qsfp28.host_side_fec_ctrl)
+		ctx->options |= (1 << NIM_OPTION_HOST_SIDE_FEC);
+}
+
+static int qsfp28_preinit(nim_i2c_ctx_p ctx, int8_t lane_idx)
+{
+	int res = qsfpplus_preinit(ctx, lane_idx);
+
+	if (!res) {
+		qsfp28_wait_for_ready_after_reset(ctx);
+		memset(&ctx->specific_u.qsfp.specific_u.qsfp28, 0,
+			sizeof(ctx->specific_u.qsfp.specific_u.qsfp28));
+		ctx->specific_u.qsfp.qsfp28 = true;
+		qsfp28_find_port_params(ctx);
+		qsfp28_get_fec_options(ctx);
+		qsfp28_set_speed_mask(ctx);
+	}
+
+	return res;
+}
+
+static void qsfp_plus_nim_add_all_sensors(uint8_t m_port_no, nim_i2c_ctx_t *ctx,
+	struct nim_sensor_group **nim_sensors_ptr,
+	uint16_t *nim_sensors_cnt)
+{
+	const struct nim_sensors_ops *nim_sensors_ops = get_nim_sensors_ops();
+
+	if (nim_sensors_ops == NULL) {
+		NT_LOG(INF, ETHDEV, "Nim sensors module not included\n");
+		return;
+	}
+
+	struct qsfp_ops *qsfp_ops = get_qsfp_ops();
+
+	if (qsfp_ops == NULL) {
+		NT_LOG(INF, ETHDEV, "QSFP module not included\n");
+		return;
+	}
+
+	struct nt_adapter_sensor_description *qsfp_sensor_level0 =
+		nim_sensors_ops->get_qsfp_sensor_level0();
+
+	struct nt_adapter_sensor_description *qsfp_sensor_level1 =
+		nim_sensors_ops->get_qsfp_sensor_level1();
+
+	struct nim_sensor_group *sensor = NULL;
+
+	if (ctx == NULL || nim_sensors_ptr == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: bad argument(s)\n", __func__);
+		return;
+	}
+
+	/*
+	 * If the user has not provided a name for the temperature sensor then apply
+	 * one automatically
+	 */
+	if (strlen(qsfp_sensor_level0[0].name) == 0) {
+		if (ctx->specific_u.qsfp.qsfp28)
+			snprintf(qsfp_sensor_level0[0].name, sizeof(qsfp_sensor_level0[0].name),
+				"%s", "QSFP28");
+
+		else
+			snprintf(qsfp_sensor_level0[0].name, sizeof(qsfp_sensor_level0[0].name),
+				"%s", "QSFP+");
+	}
+
+	/* temperature sensor */
+	nim_sensors_ptr[m_port_no] =
+		allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_PORT,
+			&qsfp_sensor_level0[0]);
+	sensor = nim_sensors_ptr[m_port_no];
+	sensor->read = qsfp_ops->nim_read_qsfp_temp;
+	(*nim_sensors_cnt)++;
+
+	/* voltage */
+	sensor->next = allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_LEVEL1_PORT,
+			&qsfp_sensor_level1[0]);
+	sensor = sensor->next;
+	sensor->read = qsfp_ops->nim_read_qsfp_voltage;
+	(*nim_sensors_cnt)++;
+
+	/* bias current sensors */
+	sensor->next = allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_LEVEL1_PORT,
+			&qsfp_sensor_level1[1]);
+	sensor = sensor->next;
+	sensor->read = qsfp_ops->nim_read_qsfp_bias_current;
+	(*nim_sensors_cnt)++;
+
+	for (uint8_t i = 2; i < 5; i++) {
+		sensor->next =
+			allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_LEVEL1_PORT,
+				&qsfp_sensor_level1[i]);
+		sensor = sensor->next;
+		sensor->read = NULL;
+		(*nim_sensors_cnt)++;
+	}
+
+	/* tx power */
+	sensor->next = allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_LEVEL1_PORT,
+			&qsfp_sensor_level1[5]);
+	sensor = sensor->next;
+	sensor->read = qsfp_ops->nim_read_qsfp_tx_power;
+	(*nim_sensors_cnt)++;
+
+	for (uint8_t i = 6; i < 9; i++) {
+		sensor->next =
+			allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_LEVEL1_PORT,
+				&qsfp_sensor_level1[i]);
+		sensor = sensor->next;
+		sensor->read = NULL;
+		(*nim_sensors_cnt)++;
+	}
+
+	/* rx power */
+	sensor->next = allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_LEVEL1_PORT,
+			&qsfp_sensor_level1[9]);
+	sensor = sensor->next;
+	sensor->read = qsfp_ops->nim_read_qsfp_rx_power;
+	(*nim_sensors_cnt)++;
+
+	for (uint8_t i = 10; i < 13; i++) {
+		sensor->next =
+			allocate_nim_sensor_group(m_port_no, ctx, NT_SENSOR_SOURCE_LEVEL1_PORT,
+				&qsfp_sensor_level1[i]);
+		sensor = sensor->next;
+		sensor->read = NULL;
+		(*nim_sensors_cnt)++;
+	}
+}
+
+struct nim_sensor_group *allocate_nim_sensor_group(uint8_t port, struct nim_i2c_ctx *ctx,
+	enum nt_sensor_source_e ssrc,
+	struct nt_adapter_sensor_description *sd)
+{
+	const struct nim_sensors_ops *nim_sensor_ops = get_nim_sensors_ops();
+
+	if (nim_sensor_ops == NULL) {
+		NT_LOG(INF, ETHDEV, "Sensors module not included\n");
+		return NULL;
+	}
+
+	struct nim_sensor_group *sg = malloc(sizeof(struct nim_sensor_group));
+
+	if (sg == NULL) {
+		NT_LOG(ERR, ETHDEV, "%s: sensor group is NULL", __func__);
+		return NULL;
+	}
+
+	sg->sensor = nim_sensor_ops->allocate_sensor_by_description(port, ssrc, sd);
+	sg->ctx = ctx;
+	sg->next = NULL;
+	return sg;
+}
+
+int construct_and_preinit_nim(nim_i2c_ctx_p ctx, void *extra, uint8_t port,
+	struct nim_sensor_group **nim_sensors_ptr, uint16_t *nim_sensors_cnt)
+{
+	int res = i2c_nim_common_construct(ctx);
+
+	switch (translate_nimid(ctx)) {
+	case NT_NIM_SFP_SFP_PLUS:
+		break;
+
+	case NT_NIM_QSFP_PLUS:
+		qsfpplus_preinit(ctx, extra ? *(int8_t *)extra : (int8_t)-1);
+		qsfp_plus_nim_add_all_sensors(port, ctx, nim_sensors_ptr, nim_sensors_cnt);
+		break;
+
+	case NT_NIM_QSFP28:
+		qsfp28_preinit(ctx, extra ? *(int8_t *)extra : (int8_t)-1);
+		qsfp_plus_nim_add_all_sensors(port, ctx, nim_sensors_ptr, nim_sensors_cnt);
+		break;
+
+	default:
+		res = 1;
+		NT_LOG(ERR, NTHW, "NIM type %s is not supported.\n", nim_id_to_text(ctx->nim_id));
+	}
+
+	return res;
+}
+
+static struct sfp_ops *sfp_ops;
+
+void register_sfp_ops(struct sfp_ops *ops)
+{
+	sfp_ops = ops;
+}
+
+struct sfp_ops *get_sfp_ops(void)
+{
+	return sfp_ops;
+}
+
+static struct qsfp_ops *qsfp_ops;
+
+void register_qsfp_ops(struct qsfp_ops *ops)
+{
+	qsfp_ops = ops;
+}
+
+struct qsfp_ops *get_qsfp_ops(void)
+{
+	return qsfp_ops;
+}
diff --git a/drivers/net/ntnic/nim/i2c_nim.h b/drivers/net/ntnic/nim/i2c_nim.h
new file mode 100644
index 0000000000..cbeb3852fd
--- /dev/null
+++ b/drivers/net/ntnic/nim/i2c_nim.h
@@ -0,0 +1,77 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef I2C_NIM_H_
+#define I2C_NIM_H_
+
+#include "nthw_drv.h"
+#include "nim_defines.h"
+#include "nt_link_speed.h"
+#include "ntnic_nim.h"
+
+#include "ntnic_sensor.h"
+
+typedef struct sfp_nim_state {
+	uint8_t br;	/* bit rate, units of 100 MBits/sec */
+} sfp_nim_state_t, *sfp_nim_state_p;
+
+struct nim_sensor_group *allocate_nim_sensor_group(uint8_t port, struct nim_i2c_ctx *ctx,
+	enum nt_sensor_source_e ssrc,
+	struct nt_adapter_sensor_description *sd);
+
+/*
+ * Utility functions
+ */
+
+nt_nim_identifier_t translate_nimid(const nim_i2c_ctx_t *ctx);
+
+/*
+ * Builds an nim state for the port implied by `ctx`, returns zero
+ * if successful, and non-zero otherwise. SFP and QSFP nims are supported
+ */
+int nim_state_build(nim_i2c_ctx_t *ctx, sfp_nim_state_t *state);
+
+/*
+ * Returns a type name such as "SFP/SFP+" for a given NIM type identifier,
+ * or the string "ILLEGAL!".
+ */
+const char *nim_id_to_text(uint8_t nim_id);
+
+int nim_qsfp_plus_nim_set_tx_laser_disable(nim_i2c_ctx_t *ctx, bool disable, int lane_idx);
+
+/*
+ * This function tries to classify NIM based on it's ID and some register reads
+ * and collects information into ctx structure. The @extra parameter could contain
+ * the initialization argument for specific type of NIMS.
+ */
+int construct_and_preinit_nim(nim_i2c_ctx_p ctx, void *extra, uint8_t port,
+	struct nim_sensor_group **nim_sensors_ptr,
+	uint16_t *nim_sensors_cnt);
+
+int read_data_lin(nim_i2c_ctx_p ctx, uint16_t lin_addr, uint16_t length, void *data);
+
+struct sfp_ops {
+	void (*nim_read_sfp_temp)(struct nim_sensor_group *sg, nthw_spis_t *t_spi);
+	void (*nim_read_sfp_voltage)(struct nim_sensor_group *sg, nthw_spis_t *t_spi);
+	void (*nim_read_sfp_bias_current)(struct nim_sensor_group *sg, nthw_spis_t *t_spi);
+	void (*nim_read_sfp_tx_power)(struct nim_sensor_group *sg, nthw_spis_t *t_spi);
+	void (*nim_read_sfp_rx_power)(struct nim_sensor_group *sg, nthw_spis_t *t_spi);
+};
+
+struct qsfp_ops {
+	void (*nim_read_qsfp_temp)(struct nim_sensor_group *sg, nthw_spis_t *t_spi);
+	void (*nim_read_qsfp_voltage)(struct nim_sensor_group *sg, nthw_spis_t *t_spi);
+	void (*nim_read_qsfp_bias_current)(struct nim_sensor_group *sg, nthw_spis_t *t_spi);
+	void (*nim_read_qsfp_tx_power)(struct nim_sensor_group *sg, nthw_spis_t *t_spi);
+	void (*nim_read_qsfp_rx_power)(struct nim_sensor_group *sg, nthw_spis_t *t_spi);
+};
+
+void register_qsfp_ops(struct qsfp_ops *ops);
+struct qsfp_ops *get_qsfp_ops(void);
+
+void register_sfp_ops(struct sfp_ops *ops);
+struct sfp_ops *get_sfp_ops(void);
+
+#endif	/* I2C_NIM_H_ */
diff --git a/drivers/net/ntnic/nim/include/qsfp_registers.h b/drivers/net/ntnic/nim/include/qsfp_registers.h
new file mode 100644
index 0000000000..d935b0eb07
--- /dev/null
+++ b/drivers/net/ntnic/nim/include/qsfp_registers.h
@@ -0,0 +1,58 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _QSFP_REGISTERS_H
+#define _QSFP_REGISTERS_H
+
+/*
+ * QSFP Registers
+ */
+#define QSFP_INT_STATUS_RX_LOS_ADDR 3
+#define QSFP_TEMP_LIN_ADDR 22
+#define QSFP_VOLT_LIN_ADDR 26
+#define QSFP_RX_PWR_LIN_ADDR 34	/* uint16_t [0..3] */
+#define QSFP_TX_BIAS_LIN_ADDR 42/* uint16_t [0..3] */
+#define QSFP_TX_PWR_LIN_ADDR 50	/* uint16_t [0..3] */
+
+#define QSFP_CONTROL_STATUS_LIN_ADDR 86
+#define QSFP_SOFT_TX_ALL_DISABLE_BITS 0x0F
+
+#define QSFP_EXTENDED_IDENTIFIER 129
+#define QSFP_POWER_CLASS_BITS_1_4 0xC0
+#define QSFP_POWER_CLASS_BITS_5_7 0x03
+
+#define QSFP_SUP_LEN_INFO_LIN_ADDR 142	/* 5bytes */
+#define QSFP_TRANSMITTER_TYPE_LIN_ADDR 147	/* 1byte */
+#define QSFP_VENDOR_NAME_LIN_ADDR 148	/* 16bytes */
+#define QSFP_VENDOR_PN_LIN_ADDR 168	/* 16bytes */
+#define QSFP_VENDOR_SN_LIN_ADDR 196	/* 16bytes */
+#define QSFP_VENDOR_DATE_LIN_ADDR 212	/* 8bytes */
+#define QSFP_VENDOR_REV_LIN_ADDR 184	/* 2bytes */
+
+#define QSFP_SPEC_COMPLIANCE_CODES_ADDR 131	/* 8 bytes */
+#define QSFP_EXT_SPEC_COMPLIANCE_CODES_ADDR 192	/* 1 byte */
+
+#define QSFP_OPTION3_LIN_ADDR 195
+#define QSFP_OPTION3_TX_DISABLE_BIT (1 << 4)
+
+#define QSFP_DMI_OPTION_LIN_ADDR 220
+#define QSFP_DMI_AVG_PWR_BIT (1 << 3)
+
+#define QSFP_TEMP_THRESH_LIN_ADDR (128 + (3 * 128))	/* Page 3 */
+/* 8bytes: HighAlarm, LowAlarm, HighWarn, LowWarn each 2 bytes */
+
+#define QSFP_VOLT_THRESH_LIN_ADDR (144 + (3 * 128))	/* Page 3 */
+/* 8bytes: HighAlarm, LowAlarm, HighWarn, LowWarn each 2 bytes */
+
+#define QSFP_RX_PWR_THRESH_LIN_ADDR (176 + (3 * 128))	/* Page 3 */
+/* 8bytes: HighAlarm, LowAlarm, HighWarn, LowWarn each 2 bytes */
+
+#define QSFP_BIAS_THRESH_LIN_ADDR (184 + (3 * 128))	/* Page 3 */
+/* 8bytes: HighAlarm, LowAlarm, HighWarn, LowWarn each 2 bytes */
+
+#define QSFP_TX_PWR_THRESH_LIN_ADDR (192 + (3 * 128))	/* Page 3 */
+/* 8bytes: HighAlarm, LowAlarm, HighWarn, LowWarn each 2 bytes */
+
+#endif	/* _QSFP_REGISTERS_H */
diff --git a/drivers/net/ntnic/nim/nim_defines.h b/drivers/net/ntnic/nim/nim_defines.h
new file mode 100644
index 0000000000..4abe115dc9
--- /dev/null
+++ b/drivers/net/ntnic/nim/nim_defines.h
@@ -0,0 +1,69 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NIM_DEFINES_H_
+#define NIM_DEFINES_H_
+
+#define NIM_IDENTIFIER_ADDR 0	/* 1 byte */
+
+#define SFP_BIT_RATE_ADDR 12	/* 1 byte */
+#define SFP_VENDOR_NAME_ADDR 20	/* 16bytes */
+#define SFP_VENDOR_PN_ADDR 40	/* 16bytes */
+#define SFP_VENDOR_REV_ADDR 56	/* 4bytes */
+#define SFP_VENDOR_SN_ADDR 68	/* 16bytes */
+#define SFP_VENDOR_DATE_ADDR 84	/* 8bytes */
+
+#define SFP_CONTROL_STATUS_LIN_ADDR (110U + 256U)	/* 0xA2 */
+#define SFP_SOFT_TX_DISABLE_BIT (1U << 6)
+
+#define QSFP_EXTENDED_IDENTIFIER 129
+#define QSFP_SUP_LEN_INFO_ADDR 142	/* 5bytes */
+#define QSFP_TRANSMITTER_TYPE_ADDR 147	/* 1byte */
+#define QSFP_VENDOR_NAME_ADDR 148	/* 16bytes */
+#define QSFP_VENDOR_PN_ADDR 168	/* 16bytes */
+#define QSFP_VENDOR_REV_ADDR 184/* 2bytes */
+#define QSFP_VENDOR_SN_ADDR 196	/* 16bytes */
+#define QSFP_VENDOR_DATE_ADDR 212	/* 8bytes */
+
+/* I2C addresses */
+#define NIM_I2C_0XA0 0xA0	/* Basic I2C address */
+#define NIM_I2C_0XA2 0xA2	/* Diagnostic monitoring */
+#define NIM_I2C_0XAC 0xAC	/* Address of integrated PHY */
+
+typedef enum {
+	NIM_OPTION_TEMP = 0,
+	NIM_OPTION_SUPPLY,
+	NIM_OPTION_RX_POWER,
+	NIM_OPTION_TX_BIAS,
+	NIM_OPTION_TX_POWER,
+	NIM_OPTION_TX_DISABLE,
+	/* Indicates that the module should be checked for the two next FEC types */
+	NIM_OPTION_FEC,
+	NIM_OPTION_MEDIA_SIDE_FEC,
+	NIM_OPTION_HOST_SIDE_FEC,
+	NIM_OPTION_RX_ONLY
+} nim_option_t;
+
+enum nt_nim_identifier_e {
+	NT_NIM_UNKNOWN = 0x00,	/* Nim type is unknown */
+	NT_NIM_GBIC = 0x01,	/* Nim type = GBIC */
+	NT_NIM_FIXED = 0x02,	/* Nim type = FIXED */
+	NT_NIM_SFP_SFP_PLUS = 0x03,	/* Nim type = SFP/SFP+ */
+	NT_NIM_300_PIN_XBI = 0x04,	/* Nim type = 300 pin XBI */
+	NT_NIM_XEN_PAK = 0x05,	/* Nim type = XEN-PAK */
+	NT_NIM_XFP = 0x06,	/* Nim type = XFP */
+	NT_NIM_XFF = 0x07,	/* Nim type = XFF */
+	NT_NIM_XFP_E = 0x08,	/* Nim type = XFP-E */
+	NT_NIM_XPAK = 0x09,	/* Nim type = XPAK */
+	NT_NIM_X2 = 0x0A,	/* Nim type = X2 */
+	NT_NIM_DWDM = 0x0B,	/* Nim type = DWDM */
+	NT_NIM_QSFP = 0x0C,	/* Nim type = QSFP */
+	NT_NIM_QSFP_PLUS = 0x0D,/* Nim type = QSFP+ */
+	NT_NIM_QSFP28 = 0x11,	/* Nim type = QSFP28 */
+	NT_NIM_CFP4 = 0x12,	/* Nim type = CFP4 */
+};
+typedef enum nt_nim_identifier_e nt_nim_identifier_t;
+
+#endif	/* NIM_DEFINES_H_ */
diff --git a/drivers/net/ntnic/nim/nt_link_speed.c b/drivers/net/ntnic/nim/nt_link_speed.c
new file mode 100644
index 0000000000..3cc5f50e49
--- /dev/null
+++ b/drivers/net/ntnic/nim/nt_link_speed.c
@@ -0,0 +1,115 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "nt_link_speed.h"
+#include "nt4ga_link.h"
+
+const char *nt_translate_link_speed(nt_link_speed_t link_speed)
+{
+	switch (link_speed) {
+	case NT_LINK_SPEED_UNKNOWN:
+		return "NotAvail";
+
+	case NT_LINK_SPEED_10M:
+		return "10M";
+
+	case NT_LINK_SPEED_100M:
+		return "100M";
+
+	case NT_LINK_SPEED_1G:
+		return "1G";
+
+	case NT_LINK_SPEED_10G:
+		return "10G";
+
+	case NT_LINK_SPEED_25G:
+		return "25G";
+
+	case NT_LINK_SPEED_40G:
+		return "40G";
+
+	case NT_LINK_SPEED_50G:
+		return "50G";
+
+	case NT_LINK_SPEED_100G:
+		return "100G";
+
+	default:
+		/* DEBUG assert: remind developer that a switch/case entry is needed here.... */
+		assert(false);
+		return "Unhandled";
+	}
+}
+
+uint64_t nt_get_link_speed(nt_link_speed_t e_link_speed)
+{
+	uint64_t n_link_speed = 0ULL;
+
+	switch (e_link_speed) {
+	case NT_LINK_SPEED_UNKNOWN:
+		n_link_speed = 0UL;
+		break;
+
+	case NT_LINK_SPEED_10M:
+		n_link_speed = (10ULL * 1000ULL * 1000ULL);
+		break;
+
+	case NT_LINK_SPEED_100M:
+		n_link_speed = (100ULL * 1000ULL * 1000ULL);
+		break;
+
+	case NT_LINK_SPEED_1G:
+		n_link_speed = (1ULL * 1000ULL * 1000ULL * 1000ULL);
+		break;
+
+	case NT_LINK_SPEED_10G:
+		n_link_speed = (10ULL * 1000ULL * 1000ULL * 1000ULL);
+		break;
+
+	case NT_LINK_SPEED_25G:
+		n_link_speed = (25ULL * 1000ULL * 1000ULL * 1000ULL);
+		break;
+
+	case NT_LINK_SPEED_40G:
+		n_link_speed = (40ULL * 1000ULL * 1000ULL * 1000ULL);
+		break;
+
+	case NT_LINK_SPEED_50G:
+		n_link_speed = (50ULL * 1000ULL * 1000ULL * 1000ULL);
+		break;
+
+	case NT_LINK_SPEED_100G:
+		n_link_speed = (100ULL * 1000ULL * 1000ULL * 1000ULL);
+		break;
+
+	default:
+		/* DEBUG assert: remind developer that a switch/case entry is needed here.... */
+		assert(false);
+		n_link_speed = 0UL;
+		break;
+	}
+
+	return n_link_speed;
+}
+
+uint64_t nt_get_max_link_speed(uint32_t link_speed_mask)
+{
+	uint64_t n_link_speed = 0UL;
+
+	for (int i = 0; i < 32; i++) {
+		if ((1U << i) & link_speed_mask) {
+			uint64_t link_speed = nt_get_link_speed(1 << i);
+
+			if (link_speed > n_link_speed)
+				n_link_speed = link_speed;
+		}
+	}
+
+	return n_link_speed;
+}
diff --git a/drivers/net/ntnic/nim/nt_link_speed.h b/drivers/net/ntnic/nim/nt_link_speed.h
new file mode 100644
index 0000000000..6e1b682fe8
--- /dev/null
+++ b/drivers/net/ntnic/nim/nt_link_speed.h
@@ -0,0 +1,16 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NT_LINK_SPEED_H_
+#define NT_LINK_SPEED_H_
+
+#include <stdint.h>
+#include "nt4ga_link.h"
+
+const char *nt_translate_link_speed(nt_link_speed_t link_speed);
+uint64_t nt_get_link_speed(nt_link_speed_t e_link_speed);
+uint64_t nt_get_max_link_speed(uint32_t link_speed_mask);
+
+#endif	/* NT_LINK_SPEED_H_ */
-- 
2.45.0