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 99CB04411D;
	Fri, 31 May 2024 17:50:36 +0200 (CEST)
Received: from mails.dpdk.org (localhost [127.0.0.1])
	by mails.dpdk.org (Postfix) with ESMTP id 19CBA42DC0;
	Fri, 31 May 2024 17:48:14 +0200 (CEST)
Received: from egress-ip11a.ess.de.barracuda.com
 (egress-ip11a.ess.de.barracuda.com [18.184.203.234])
 by mails.dpdk.org (Postfix) with ESMTP id 0AD1642DD4
 for <dev@dpdk.org>; Fri, 31 May 2024 17:48:02 +0200 (CEST)
Received: from EUR04-VI1-obe.outbound.protection.outlook.com
 (mail-vi1eur04lp2042.outbound.protection.outlook.com [104.47.14.42]) by
 mx-outbound13-64.eu-central-1a.ess.aws.cudaops.com (version=TLSv1.2
 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO);
 Fri, 31 May 2024 15:47:58 +0000
ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none;
 b=ceHJ2fscJssFzZHagH5rAqRW6O58TmTyZlIRYmuIQLn8NbgaJAttlTW6GpvFLgxS5ijoiMFWzJQkRrh0WmEGiR7meyGczuy+fhD5HKXx6cuiyhOMyWKYlmbhxzjD18Er6kWSdrxIIvPE1sxJwf75cHui3PLxlooN5iliauY2lf5y11p4zDw091bmOOYJMoHi0anUOy4F0FX51fZQkj71B+cChAjlQal5IXjFUjO+G7YI/Ign6cNWqtmMX4nLdZcegrQNObxk4IeW/WuuDQYb/o0uOboBdT0lzRtrLqyO8FdC21cS5aZKHkWLVkOShgHt8r9p1T8OrlF4FC7+oEvwdA==
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=m77mgn434vqZLRJf+5+fFiNqFa/3l5mzZBBjhz/Rews=;
 b=TeZdrOTgd0U6l9UDoa4ejEORRSyX0THWLFaqsa2TOye6MFtYxt1aC1pybYVHZhBsiLA0FdVWlV9fCOEjP1cts3ca8CBjSbRYupp2C0Dj8uWD8LoD/aMo/MfCIwN9uB40WotCM4fTKUfSek1OLkyRMuvd98QimPZMoL1DD6hsEYrwZ7bED3fdxz6fowOaaWzw2lb+oEcI7kdy+G3d4K64jQSePVY7XIPEe5YOJsJf1MdS9SgijfCBbCKNnXZBrSpaLYza5r33sgvQBz76Twu5CuZebKuMRmTqHhfVCWzIuIUrBaZt+Ycno0oRfrYekw3SFxz2/riJ/cwmjrj1/jCiMQ==
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=m77mgn434vqZLRJf+5+fFiNqFa/3l5mzZBBjhz/Rews=;
 b=HaBI4rBzPpTdogvUHunErQU3fRdYYXfYu6dwg9JhZltooiowx6YwNB6wqgvjXikZBm8cpZEKoV3veDN0xKSYaF+wQ1RQaz6sZv8DieYePjIN390xGB1FfdswAru0Iu7oiN4DgBf40uIjLa19AmXrxkLvC90ZRRWXtqVP8tl1QVY=
Received: from AS4P251CA0020.EURP251.PROD.OUTLOOK.COM (2603:10a6:20b:5d3::9)
 by AM0P190MB0593.EURP190.PROD.OUTLOOK.COM (2603:10a6:208:193::15) with
 Microsoft SMTP Server (version=TLS1_2,
 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7633.21; Fri, 31 May
 2024 15:47:47 +0000
Received: from AMS1EPF00000047.eurprd04.prod.outlook.com
 (2603:10a6:20b:5d3:cafe::5f) by AS4P251CA0020.outlook.office365.com
 (2603:10a6:20b:5d3::9) with Microsoft SMTP Server (version=TLS1_2,
 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7633.23 via Frontend
 Transport; Fri, 31 May 2024 15:47:47 +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
 AMS1EPF00000047.mail.protection.outlook.com (10.167.16.135) with Microsoft
 SMTP Server id 15.20.7633.15 via Frontend Transport; Fri, 31 May 2024
 15:47:47 +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 v2 16/17] net/ntnic: add link 100G module
Date: Fri, 31 May 2024 17:47:20 +0200
Message-ID: <20240531154731.1856874-16-sil-plv@napatech.com>
X-Mailer: git-send-email 2.44.0
In-Reply-To: <20240531154731.1856874-1-sil-plv@napatech.com>
References: <20240530144929.4127931-1-sil-plv@napatech.com>
 <20240531154731.1856874-1-sil-plv@napatech.com>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-EOPAttributedMessage: 0
X-MS-PublicTrafficType: Email
X-MS-TrafficTypeDiagnostic: AMS1EPF00000047:EE_|AM0P190MB0593:EE_
Content-Type: text/plain
X-MS-Office365-Filtering-Correlation-Id: 7c8d8bc7-b53b-4908-5db1-08dc8189050d
X-MS-Exchange-SenderADCheck: 1
X-MS-Exchange-AntiSpam-Relay: 0
X-Microsoft-Antispam: BCL:0;
 ARA:13230031|376005|1800799015|36860700004|82310400017; 
X-Microsoft-Antispam-Message-Info: =?us-ascii?Q?yRPkCWhzi1HYpOfffeKS1TC1rxuFCP2Gl5gk2rehNJz8dIff04jn1WxVUe/a?=
 =?us-ascii?Q?G4EQV7qjS1I7yezMtMM61NrxiT6VGkv9wMklBxIM2hFAF7Whyevs8dbPEEpT?=
 =?us-ascii?Q?Edp6Sx4sxeNbAUqTjYvFaEOXjNrOYykArw35gqlTOYnRf2nLtWLcdD7r5VQS?=
 =?us-ascii?Q?NoZ+kggm+Ax4+Zretn+vwqNU5wrutvFsofFk8bAqn7ZyjBzgc93ipVDKrjbI?=
 =?us-ascii?Q?XOYbQgCQAC1LXAKKExe1BFr+6iHteSNjRGuXQSw0UgohiiLXq2VvpyKjPi7B?=
 =?us-ascii?Q?3K4A3tQG2vaY8ciYj7qIurb/2LJs6WPcg9KWCV8txnpWp/No70Cpi7UYKsWi?=
 =?us-ascii?Q?9L1KB+uVadtQIbRGvHErRztsiG5g4icbTLKH2olG2KBhjpBZoOavZlgKxyEL?=
 =?us-ascii?Q?bajD3gOLQm2ZSxl7hElhWdCPozVQ0n2dkjmD+kbuE83LM27ZMbh5sxlyhakc?=
 =?us-ascii?Q?5AnVsuJsw5LNvzTojfFIOX14HDtAt4FRxTg7h7+HlYOpRgtMN8brRUreV13O?=
 =?us-ascii?Q?g3pGHHP/c6+Iie8ZJ/4nyE49bU//n0DYHgQAxgCY8VpdUWEyRxNbS4eeuqFF?=
 =?us-ascii?Q?ilbKGWhLcDuBxdugOsVc36FWBhF0KxSgk+nlD/jxESX8Wvt6UtTkfPeCQvo4?=
 =?us-ascii?Q?lIx3IdpqhsWEsMuYZ7jM8sws/VFB5Wtjqy7dzvWrESTXHTI51ya7flc4Ez3D?=
 =?us-ascii?Q?b3D6L8V7Gs4OSQ4ndNY+1G7X5wgAgWgTdRvhi92Zkz3wtdx3WlHRULez8vQQ?=
 =?us-ascii?Q?R4nGk7yc3DiQMMKOb0Kp8OpiFpCzAVJ9VFr2EvCROB922NLUnEcDximsMch7?=
 =?us-ascii?Q?3FxJ04KQopEHTq45AcxrPxIHQylSTdA6zxAZmF5y0XlzFTmO703H4GxwY07j?=
 =?us-ascii?Q?J0s1/viQwGUeG442k/OWVa0hFe4wIMqJo+o9/DdUi8aBk9zDsFDxUNAZk8HX?=
 =?us-ascii?Q?cC3jA+bFcweAOX81Ho/Jd1UWBCf+sQOwTC4C52sLHWCWN8O6iLcc0zXsl8t6?=
 =?us-ascii?Q?3XYhPoRBYM2ACRfRRMJ4BQZ+P+XpPN6N9Q+59a8AJcp/IbbVMXJwP0KB8kJs?=
 =?us-ascii?Q?JO9lk4UwnQKqUS/6aZnY0n/6fsI6XAIOjzhAsM2h5iP/OyTXtQflklMgY31q?=
 =?us-ascii?Q?Rs7JoEao6YCBtWh9NKmoe/cMDg2i7yYhrQAek/lR6yDVCoUj+taNY07/TPAN?=
 =?us-ascii?Q?14rriMnKPJoLt4OciSMuuuFCCJGaSoOaw8bW+4bxpfvFwBfszism2lm78U34?=
 =?us-ascii?Q?Kl4ZEsX2MGgOjo6MnSW6yjT8ZWrTddKwTtn0SSYzP3qrj9zukGHLBGFystnk?=
 =?us-ascii?Q?2Yg1ntRX/KQwLLxOuYJPALKF?=
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)(376005)(1800799015)(36860700004)(82310400017);
 DIR:OUT; SFP:1102; 
X-MS-Exchange-AntiSpam-ExternalHop-MessageData-ChunkCount: 1
X-MS-Exchange-AntiSpam-ExternalHop-MessageData-0: 1Rok3YL+ydzQplOUHcC4YIJk38Y5RmtduB8SE0qSD749PzgAlh/sCPG6+O3n3HYCKnmBWF76oZT1mDc7SBc7kUoBLY+x9MSPlvvFBLjYUjaFKel14Us2XTziJ7+gEMChDJNzlTAVr2qw/ylFeI3z8XafZZBnbULK51GGnMF5Wcj5U+eLekTrfVjaCEV/tcD0MlVOcqmoBl6H5F0SUHkvuZqA6GqKwOdh21VjWYgmy30Dbaz9y5okmso61+y4CcUQHr0L7E+EoicF6hjTmiZS7Eo50ujvRCkaaFSM+Egz2N99s0CnxfCMxV1U+wi81w5btZY3rx5XCbe0tlbzgx8dD6YdYoDJQeXi6kQI5KuxPYMTDV2ZdqYW3aq23NAKqIghle3tYU5sVgDGlRDtNZwq+hI7wSxhMS8gH3sZhDT/O5cjQCxgSVf+O6rdBXqYzdZLV79gUuC5S7J6dxdEQ/slWmfFJJdV39edJBtAkjpsuaWhVilqXy1KBtC98AdFTQ4kMEN9IzGWnLGzcQveQIKZEwY1cVnsY3/JWAJwTeQM7cnogX35/a3wahMMuZDOhgeQETeIqyWQPLdX+nh+oGO86zSojU1CR5Lx67PChalCI9dpavjvKosN8AFGFbLqfVA11woVep6iYP9O/ttrXwJfnw==
X-OriginatorOrg: napatech.com
X-MS-Exchange-CrossTenant-OriginalArrivalTime: 31 May 2024 15:47:47.3782 (UTC)
X-MS-Exchange-CrossTenant-Network-Message-Id: 7c8d8bc7-b53b-4908-5db1-08dc8189050d
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: AMS1EPF00000047.eurprd04.prod.outlook.com
X-MS-Exchange-CrossTenant-AuthAs: Anonymous
X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem
X-MS-Exchange-Transport-CrossTenantHeadersStamped: AM0P190MB0593
X-BESS-ID: 1717170470-303392-12642-39835-2
X-BESS-VER: 2019.1_20240530.1612
X-BESS-Apparent-Source-IP: 104.47.14.42
X-BESS-Parts: H4sIAAAAAAACA4uuVkqtKFGyUioBkjpK+cVKVuZmJoYGQGYGUDQxJcUk0TTV3N
 TYxMzIJNXAKCUt0TTRJMXS1CDVwsw4Tak2FgD7+SeYQgAAAA==
X-BESS-Outbound-Spam-Score: 0.00
X-BESS-Outbound-Spam-Report: Code version 3.2,
 rules version 3.2.2.256627 [from 
 cloudscan23-24.eu-central-1b.ess.aws.cudaops.com]
 Rule breakdown below
 pts rule name              description
 ---- ---------------------- --------------------------------
 0.00 BSF_BESS_OUTBOUND      META: BESS Outbound 
X-BESS-Outbound-Spam-Status: SCORE=0.00 using account:ESS113687 scores of
 KILL_LEVEL=7.0 tests=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

Add ntnic 100G link support.

Signed-off-by: Serhii Iliushyk <sil-plv@napatech.com>
---
v2:
* Fixed WARNING:REPEATED_WORD
---
 drivers/net/ntnic/include/nt4ga_link.h        |   8 +
 .../link_mgmt/link_100g/nt4ga_link_100g.c     | 840 ++++++++++++++++
 .../link_mgmt/link_100g/nt4ga_link_100g.h     |  11 +
 .../net/ntnic/nthw/core/include/nthw_core.h   |   2 +
 .../ntnic/nthw/core/include/nthw_gpio_phy.h   |  50 +
 .../ntnic/nthw/core/include/nthw_mac_pcs.h    | 263 +++++
 drivers/net/ntnic/nthw/core/nthw_gpio_phy.c   | 144 +++
 drivers/net/ntnic/nthw/core/nthw_mac_pcs.c    | 950 ++++++++++++++++++
 8 files changed, 2268 insertions(+)
 create mode 100644 drivers/net/ntnic/link_mgmt/link_100g/nt4ga_link_100g.c
 create mode 100644 drivers/net/ntnic/link_mgmt/link_100g/nt4ga_link_100g.h
 create mode 100644 drivers/net/ntnic/nthw/core/include/nthw_gpio_phy.h
 create mode 100644 drivers/net/ntnic/nthw/core/include/nthw_mac_pcs.h
 create mode 100644 drivers/net/ntnic/nthw/core/nthw_gpio_phy.c
 create mode 100644 drivers/net/ntnic/nthw/core/nthw_mac_pcs.c

diff --git a/drivers/net/ntnic/include/nt4ga_link.h b/drivers/net/ntnic/include/nt4ga_link.h
index 49e1c5d672..1d7565f614 100644
--- a/drivers/net/ntnic/include/nt4ga_link.h
+++ b/drivers/net/ntnic/include/nt4ga_link.h
@@ -115,8 +115,16 @@ 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 */
+	nthw_mac_pcs_t mac_pcs100g[NUM_ADAPTER_PORTS_MAX];
+	nthw_gfg_t gfg[NUM_ADAPTER_PORTS_MAX];
+	nthw_gpio_phy_t gpio_phy[NUM_ADAPTER_PORTS_MAX];
+} adapter_100g_t;
+
 typedef union adapter_var_s {
 	nim_i2c_ctx_t nim_ctx[NUM_ADAPTER_PORTS_MAX];	/* First field in all the adaptors type */
+	adapter_100g_t var100g;
 } adapter_var_u;
 
 typedef struct nt4ga_link_s {
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
new file mode 100644
index 0000000000..a429b96450
--- /dev/null
+++ b/drivers/net/ntnic/link_mgmt/link_100g/nt4ga_link_100g.c
@@ -0,0 +1,840 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "nt_util.h"
+#include "ntlog.h"
+#include "i2c_nim.h"
+#include "nt4ga_adapter.h"
+#include "nt4ga_link_100g.h"
+#include "ntnic_mod_reg.h"
+
+#include <string.h>	/* memcmp, memset */
+
+/*
+ * Prototypes
+ */
+static int _swap_tx_rx_polarity(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs, int port, bool swap);
+static int _reset_rx(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs);
+static int nt4ga_link_100g_ports_init(struct adapter_info_s *p_adapter_info, nthw_fpga_t *fpga);
+
+/*
+ * Structs and types definitions
+ */
+enum link_up_state {
+	RESET,	/* A valid signal is detected by NO local faults. */
+	EXPECT_NO_LF,	/* After that we check NO latched local fault bit before */
+	/* de-asserting Remote fault indication. */
+	WAIT_STABLE_LINK,	/* Now we expect the link is up. */
+	MONITOR_LINK	/* After link-up we monitor link state. */
+};
+
+typedef struct _monitoring_state {
+	/* Fields below are set by monitoring thread */
+	enum link_up_state m_link_up_state;
+	enum nt_link_state_e link_state;
+	enum nt_link_state_e latch_link_state;
+	int m_time_out;
+} _monitoring_state_t, *_monitoring_state_p;
+
+/*
+ * Global variables
+ */
+
+/*
+ * External state, to be set by the network driver.
+ */
+
+/*
+ * Utility functions
+ */
+
+/*
+ * Init 100G link ops variables
+ */
+static struct link_ops_s link_100g_ops = {
+	.link_init = nt4ga_link_100g_ports_init,
+};
+
+static void __attribute__((constructor(65535))) link_100g_init(void)
+{
+	register_100g_link_ops(&link_100g_ops);
+}
+
+static void _set_loopback(struct adapter_info_s *p_adapter_info,
+	nthw_mac_pcs_t *mac_pcs,
+	int intf_no,
+	uint32_t mode,
+	uint32_t last_mode)
+{
+	bool swap_polerity = true;
+
+	switch (mode) {
+	case 1:
+		NT_LOG(INF, ETHDEV, "%s: Applying host loopback\n",
+			p_adapter_info->mp_port_id_str[intf_no]);
+		nthw_mac_pcs_set_fec(mac_pcs, true);
+		nthw_mac_pcs_set_host_loopback(mac_pcs, true);
+		swap_polerity = false;
+		break;
+
+	case 2:
+		NT_LOG(INF, ETHDEV, "%s: Applying line loopback\n",
+			p_adapter_info->mp_port_id_str[intf_no]);
+		nthw_mac_pcs_set_line_loopback(mac_pcs, true);
+		break;
+
+	default:
+		switch (last_mode) {
+		case 1:
+			NT_LOG(INF, ETHDEV, "%s: Removing host loopback\n",
+				p_adapter_info->mp_port_id_str[intf_no]);
+			nthw_mac_pcs_set_host_loopback(mac_pcs, false);
+			break;
+
+		case 2:
+			NT_LOG(INF, ETHDEV, "%s: Removing line loopback\n",
+				p_adapter_info->mp_port_id_str[intf_no]);
+			nthw_mac_pcs_set_line_loopback(mac_pcs, false);
+			break;
+
+		default:
+			/* Do nothing */
+			break;
+		}
+
+		break;
+	}
+
+	if ((p_adapter_info->fpga_info.nthw_hw_info.hw_id == 2 &&
+			p_adapter_info->hw_info.n_nthw_adapter_id == NT_HW_ADAPTER_ID_NT200A01) ||
+		p_adapter_info->hw_info.n_nthw_adapter_id == NT_HW_ADAPTER_ID_NT200A02) {
+		(void)_swap_tx_rx_polarity(p_adapter_info, mac_pcs, intf_no, swap_polerity);
+	}
+
+	/* After changing the loopback the system must be properly reset */
+	_reset_rx(p_adapter_info, mac_pcs);
+
+	nt_os_wait_usec(10000);	/* 10ms - arbitrary choice */
+
+	if (!nthw_mac_pcs_is_rx_path_rst(mac_pcs)) {
+		nthw_mac_pcs_reset_bip_counters(mac_pcs);
+
+		if (!nthw_mac_pcs_get_fec_bypass(mac_pcs))
+			nthw_mac_pcs_reset_fec_counters(mac_pcs);
+	}
+}
+
+/*
+ * Function to retrieve the current state of a link (for one port)
+ */
+static int _link_state_build(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs,
+	nthw_gpio_phy_t *gpio_phy, int port, link_state_t *state,
+	bool is_port_disabled)
+{
+	uint32_t abs;
+	uint32_t phy_link_state;
+	uint32_t lh_abs;
+	uint32_t ll_phy_link_state;
+	uint32_t link_down_cnt;
+	uint32_t nim_interr;
+	uint32_t lh_local_fault;
+	uint32_t lh_remote_fault;
+	uint32_t lh_internal_local_fault;
+	uint32_t lh_received_local_fault;
+
+	memset(state, 0, sizeof(*state));
+	state->link_disabled = is_port_disabled;
+	nthw_mac_pcs_get_link_summary(mac_pcs, &abs, &phy_link_state, &lh_abs, &ll_phy_link_state,
+		&link_down_cnt, &nim_interr, &lh_local_fault,
+		&lh_remote_fault, &lh_internal_local_fault,
+		&lh_received_local_fault);
+
+	assert(port >= 0 && port < NUM_ADAPTER_PORTS_MAX);
+	state->nim_present = nthw_gpio_phy_is_module_present(gpio_phy, (uint8_t)port);
+	state->lh_nim_absent = !state->nim_present;
+	state->link_up = phy_link_state ? true : false;
+
+	{
+		static char lsbuf[NUM_ADAPTER_MAX][NUM_ADAPTER_PORTS_MAX][256];
+		char buf[255];
+		const int adapter_no = drv->adapter_no;
+		snprintf(buf, sizeof(buf),
+			"%s: Port = %d: abs = %u, phy_link_state = %u, lh_abs = %u, "
+			"ll_phy_link_state = %u, "
+			"link_down_cnt = %u, nim_interr = %u, lh_local_fault = %u, lh_remote_fault = "
+			"%u, lh_internal_local_fault = %u, lh_received_local_fault = %u",
+			drv->mp_adapter_id_str, mac_pcs->mn_instance, abs, phy_link_state, lh_abs,
+			ll_phy_link_state, link_down_cnt, nim_interr, lh_local_fault,
+			lh_remote_fault, lh_internal_local_fault, lh_received_local_fault);
+
+		if (strcmp(lsbuf[adapter_no][port], buf) != 0) {
+			snprintf(lsbuf[adapter_no][port], sizeof(lsbuf[adapter_no][port]), "%s",
+				buf);
+			lsbuf[adapter_no][port][sizeof(lsbuf[adapter_no][port]) - 1U] = '\0';
+			NT_LOG(DBG, ETHDEV, "%s\n", lsbuf[adapter_no][port]);
+		}
+	}
+	return 0;
+}
+
+/*
+ * Check whether a NIM module is present
+ */
+static bool _nim_is_present(nthw_gpio_phy_t *gpio_phy, uint8_t if_no)
+{
+	assert(if_no < NUM_ADAPTER_PORTS_MAX);
+
+	return nthw_gpio_phy_is_module_present(gpio_phy, if_no);
+}
+
+/*
+ * Enable RX
+ */
+static int _enable_rx(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs)
+{
+	(void)drv;	/* unused */
+	nthw_mac_pcs_set_rx_enable(mac_pcs, true);
+	return 0;
+}
+
+/*
+ * Enable TX
+ */
+static int _enable_tx(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs)
+{
+	(void)drv;	/* unused */
+	nthw_mac_pcs_set_tx_enable(mac_pcs, true);
+	nthw_mac_pcs_set_tx_sel_host(mac_pcs, true);
+	return 0;
+}
+
+/*
+ * Disable RX
+ */
+static int _disable_rx(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs)
+{
+	(void)drv;	/* unused */
+	nthw_mac_pcs_set_rx_enable(mac_pcs, false);
+	return 0;
+}
+
+/*
+ * Disable TX
+ */
+static int _disable_tx(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs)
+{
+	(void)drv;	/* unused */
+	nthw_mac_pcs_set_tx_enable(mac_pcs, false);
+	nthw_mac_pcs_set_tx_sel_host(mac_pcs, false);
+	return 0;
+}
+
+/*
+ * Reset RX
+ */
+static int _reset_rx(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs)
+{
+	(void)drv;
+
+	nthw_mac_pcs_rx_path_rst(mac_pcs, true);
+	nt_os_wait_usec(10000);	/* 10ms */
+	nthw_mac_pcs_rx_path_rst(mac_pcs, false);
+	nt_os_wait_usec(10000);	/* 10ms */
+
+	return 0;
+}
+
+/*
+ * Reset TX
+ */
+
+/*
+ * Swap tx/rx polarity
+ */
+static int _swap_tx_rx_polarity(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs, int port, bool swap)
+{
+	const bool tx_polarity_swap[2][4] = { { true, true, false, false },
+		{ false, true, false, false }
+	};
+	const bool rx_polarity_swap[2][4] = { { false, true, true, true },
+		{ false, true, true, false }
+	};
+	uint8_t lane;
+
+	(void)drv;
+
+	for (lane = 0U; lane < 4U; lane++) {
+		if (swap) {
+			nthw_mac_pcs_swap_gty_tx_polarity(mac_pcs, lane,
+				tx_polarity_swap[port][lane]);
+			nthw_mac_pcs_swap_gty_rx_polarity(mac_pcs, lane,
+				rx_polarity_swap[port][lane]);
+
+		} else {
+			nthw_mac_pcs_swap_gty_tx_polarity(mac_pcs, lane, false);
+			nthw_mac_pcs_swap_gty_rx_polarity(mac_pcs, lane, false);
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Check link once NIM is installed and link can be expected.
+ */
+static int check_link_state(adapter_info_t *drv, nthw_mac_pcs_t *mac_pcs)
+{
+	bool rst_required;
+	bool ber;
+	bool fec_all_locked;
+
+	rst_required = nthw_mac_pcs_reset_required(mac_pcs);
+
+	ber = nthw_mac_pcs_get_hi_ber(mac_pcs);
+
+	fec_all_locked = nthw_mac_pcs_get_fec_stat_all_am_locked(mac_pcs);
+
+	if (rst_required || ber || !fec_all_locked)
+		_reset_rx(drv, mac_pcs);
+
+	return 0;
+}
+
+/*
+ * Initialize NIM, Code based on nt200e3_2_ptp.cpp: MyPort::createNim()
+ */
+static int _create_nim(adapter_info_t *drv, nthw_fpga_t *fpga, int port, bool enable)
+{
+	int res = 0;
+	const uint8_t valid_nim_id = 17U;
+	nthw_gpio_phy_t *gpio_phy;
+	nim_i2c_ctx_t *nim_ctx;
+	sfp_nim_state_t nim;
+	nt4ga_link_t *link_info = &drv->nt4ga_link;
+	nthw_mac_pcs_t *mac_pcs = &link_info->u.var100g.mac_pcs100g[port];
+
+	(void)fpga;	/* unused */
+	assert(port >= 0 && port < NUM_ADAPTER_PORTS_MAX);
+	assert(link_info->variables_initialized);
+
+	gpio_phy = &link_info->u.var100g.gpio_phy[port];
+	nim_ctx = &link_info->u.var100g.nim_ctx[port];
+
+	/*
+	 * Check NIM is present before doing GPIO PHY reset.
+	 */
+	if (!_nim_is_present(gpio_phy, (uint8_t)port)) {
+		NT_LOG(INF, ETHDEV, "%s: NIM module is absent\n", drv->mp_port_id_str[port]);
+		return 0;
+	}
+
+	if (!enable) {
+		_disable_rx(drv, mac_pcs);
+		_disable_tx(drv, mac_pcs);
+		_reset_rx(drv, mac_pcs);
+	}
+
+	/*
+	 * Perform PHY reset.
+	 */
+	NT_LOG(DBG, ETHDEV, "%s: Performing NIM reset\n", drv->mp_port_id_str[port]);
+	nthw_gpio_phy_set_reset(gpio_phy, (uint8_t)port, true);
+	nt_os_wait_usec(100000);/* pause 0.1s */
+	nthw_gpio_phy_set_reset(gpio_phy, (uint8_t)port, false);
+
+	/*
+	 * 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 */
+
+	if (!_nim_is_present(gpio_phy, (uint8_t)port)) {
+		NT_LOG(DBG, ETHDEV, "%s: NIM module is no longer absent!\n",
+			drv->mp_port_id_str[port]);
+		return -1;
+	}
+
+	res = construct_and_preinit_nim(nim_ctx, NULL, port,
+			((struct adapter_info_s *)drv)->nim_sensors,
+			&((struct adapter_info_s *)drv)->nim_sensors_cnt[port]);
+
+	if (res)
+		return res;
+
+	res = nim_state_build(nim_ctx, &nim);
+
+	if (res)
+		return res;
+
+	NT_LOG(DBG, NTHW, "%s: NIM id = %u (%s), br = %u, vendor = '%s', pn = '%s', sn='%s'\n",
+		drv->mp_port_id_str[port], nim_ctx->nim_id, nim_id_to_text(nim_ctx->nim_id), nim.br,
+		nim_ctx->vendor_name, nim_ctx->prod_no, nim_ctx->serial_no);
+
+	/*
+	 * 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;
+	}
+
+	if (enable) {
+		NT_LOG(DBG, ETHDEV, "%s: De-asserting low power\n", drv->mp_port_id_str[port]);
+		nthw_gpio_phy_set_low_power(gpio_phy, (uint8_t)port, false);
+
+	} else {
+		NT_LOG(DBG, ETHDEV, "%s: Asserting low power\n", drv->mp_port_id_str[port]);
+		nthw_gpio_phy_set_low_power(gpio_phy, (uint8_t)port, true);
+	}
+
+	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, nthw_fpga_t *fpga, int port)
+{
+	int adapter_id;
+	int hw_id;
+	int res;
+	nt4ga_link_t *link_info = &drv->nt4ga_link;
+
+	nthw_mac_pcs_t *mac_pcs;
+	nthw_gfg_t *gfg_mod_p;
+
+	assert(port >= 0 && port < NUM_ADAPTER_PORTS_MAX);
+	assert(link_info->variables_initialized);
+
+	if (fpga && fpga->p_fpga_info) {
+		adapter_id = fpga->p_fpga_info->n_nthw_adapter_id;
+		hw_id = fpga->p_fpga_info->nthw_hw_info.hw_id;
+
+	} else {
+		adapter_id = -1;
+		hw_id = -1;
+	}
+
+	mac_pcs = &link_info->u.var100g.mac_pcs100g[port];
+	gfg_mod_p = &link_info->u.var100g.gfg[port];
+
+	/*
+	 * 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;
+	nthw_mac_pcs_set_led_mode(mac_pcs, NTHW_MAC_PCS_LED_AUTO);
+	nthw_mac_pcs_set_receiver_equalization_mode(mac_pcs, nthw_mac_pcs_receiver_mode_lpm);
+
+	/*
+	 * NT200A01 build 2 HW and NT200A02 that require GTY polarity swap
+	 * if (adapter is `NT200A01 build 2 HW or NT200A02`)
+	 */
+	if (adapter_id == NT_HW_ADAPTER_ID_NT200A02 ||
+		(adapter_id == NT_HW_ADAPTER_ID_NT200A01 && hw_id == 2)) {
+		(void)_swap_tx_rx_polarity(drv, mac_pcs, port, true);
+	}
+
+	nthw_mac_pcs_set_ts_eop(mac_pcs, true);	/* end-of-frame timestamping */
+
+	/* Work in ABSOLUTE timing mode, don't set IFG mode. */
+
+	/* Phase 2. Pre-state machine (`setup` functions) */
+
+	/* 2.1) nt200a0x.cpp:Myport::setup() */
+	NT_LOG(DBG, ETHDEV, "%s: Setting up port %d\n", drv->mp_port_id_str[port], port);
+
+	NT_LOG(DBG, ETHDEV, "%s: Port %d: PHY TX enable\n", drv->mp_port_id_str[port], port);
+	_enable_tx(drv, mac_pcs);
+	_reset_rx(drv, mac_pcs);
+
+	/* 2.2) Nt4gaPort::setup() */
+	if (nthw_gmf_init(NULL, fpga, port) == 0) {
+		nthw_gmf_t gmf;
+
+		if (nthw_gmf_init(&gmf, fpga, port) == 0)
+			nthw_gmf_set_enable(&gmf, true);
+	}
+
+	/* Phase 3. Link state machine steps */
+
+	/* 3.1) Create NIM, ::createNim() */
+	res = _create_nim(drv, fpga, port, true);
+
+	if (res) {
+		NT_LOG(WRN, ETHDEV, "%s: NIM initialization failed\n", drv->mp_port_id_str[port]);
+		return res;
+	}
+
+	NT_LOG(DBG, ETHDEV, "%s: NIM initialized\n", drv->mp_port_id_str[port]);
+
+	/* 3.2) MyPort::nimReady() */
+
+	/* 3.3) MyPort::nimReady100Gb() */
+
+	/* Setting FEC resets the lane counter in one half of the GMF */
+	nthw_mac_pcs_set_fec(mac_pcs, true);
+	NT_LOG(DBG, ETHDEV, "%s: Port %d: HOST FEC enabled\n", drv->mp_port_id_str[port], port);
+
+	if (adapter_id == NT_HW_ADAPTER_ID_NT200A01 && hw_id == 1) {
+		const uint8_t tuning_sr4[2][4][3] = {
+			{ { 8, 15, 8 }, { 8, 15, 9 }, { 7, 15, 9 }, { 6, 15, 8 } },
+			{ { 6, 15, 8 }, { 3, 15, 12 }, { 7, 15, 9 }, { 7, 15, 8 } }
+		};
+
+		uint8_t lane = 0;
+
+		for (lane = 0; lane < 4; lane++) {
+			uint8_t pre, diff, post;
+
+			/* Use short-range tuning values */
+			pre = tuning_sr4[port][lane][0];
+			diff = tuning_sr4[port][lane][1];
+			post = tuning_sr4[port][lane][2];
+
+			nthw_mac_pcs_set_gty_tx_tuning(mac_pcs, lane, pre, diff, post);
+		}
+
+	} else if (adapter_id == NT_HW_ADAPTER_ID_NT200A02 ||
+		(adapter_id == NT_HW_ADAPTER_ID_NT200A01 && hw_id == 2)) {
+		const uint8_t pre = 5;
+		const uint8_t diff = 25;
+		const uint8_t post = 12;
+
+		uint8_t lane = 0;
+
+		for (lane = 0; lane < 4; lane++)
+			nthw_mac_pcs_set_gty_tx_tuning(mac_pcs, lane, pre, diff, post);
+
+	} else {
+		NT_LOG(ERR, ETHDEV, "%s: Unhandled AdapterId/HwId: %02x_hwid%d\n", __func__,
+			adapter_id, hw_id);
+		assert(0);
+	}
+
+	_reset_rx(drv, mac_pcs);
+
+	/*
+	 * 3.4) MyPort::setLinkState()
+	 *
+	 * Compensation = 1640 - dly
+	 * CMAC-core dly 188 ns
+	 * FEC no correction 87 ns
+	 * FEC active correction 211
+	 */
+	if (nthw_mac_pcs_get_fec_valid(mac_pcs))
+		nthw_mac_pcs_set_timestamp_comp_rx(mac_pcs, (1640 - 188 - 211));
+
+	else
+		nthw_mac_pcs_set_timestamp_comp_rx(mac_pcs, (1640 - 188 - 87));
+
+	/* 3.5) uint32_t MyPort::macConfig(nt_link_state_t link_state) */
+	_enable_rx(drv, mac_pcs);
+
+	(void)gfg_mod_p;/* unused; */
+
+	nthw_mac_pcs_set_host_loopback(mac_pcs, false);
+
+	return res;
+}
+
+static void read_nim_sensors(adapter_info_t *drv, int port)
+{
+	if (drv->nim_sensors[port] != NULL) {
+		nthw_spis_t *t_spi = new_sensors_t_spi(drv->fpga_info.mp_fpga);
+
+		if (t_spi) {
+			for (struct nim_sensor_group *ptr = drv->nim_sensors[port]; ptr != NULL;
+				ptr = ptr->next) {
+				if (ptr->read)
+					ptr->read(ptr, t_spi);
+			}
+
+			nthw_spis_delete(t_spi);
+		}
+	}
+}
+
+/*
+ * 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];
+
+	nim_i2c_ctx_t *nim_ctx;
+	link_state_t *link_state;
+	nthw_mac_pcs_t *mac_pcs;
+	nthw_gpio_phy_t *gpio_phy;
+
+	if (!fpga) {
+		NT_LOG(ERR, ETHDEV, "%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);
+	nim_ctx = link_info->u.var100g.nim_ctx;
+	link_state = link_info->link_state;
+	mac_pcs = link_info->u.var100g.mac_pcs100g;
+	gpio_phy = link_info->u.var100g.gpio_phy;
+
+	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, ETHDEV, "%s: link state machine running...\n", drv->mp_adapter_id_str);
+
+	while (monitor_task_is_running[adapter_no]) {
+		int i;
+		static bool reported_link[NUM_ADAPTER_PORTS_MAX] = { false };
+
+		/* Read sensors */
+		if (drv->adapter_sensors != NULL) {
+			nthw_spis_t *t_spi = new_sensors_t_spi(drv->fpga_info.mp_fpga);
+
+			if (t_spi) {
+				for (struct nt_sensor_group *ptr = drv->adapter_sensors;
+					ptr != NULL;
+					ptr = ptr->next) {
+					ptr->read(ptr, t_spi);
+				}
+
+				nthw_spis_delete(t_spi);
+			}
+		}
+
+		for (i = 0; i < nb_ports; i++) {
+			link_state_t new_link_state;
+			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;
+
+			/* Reading NIM sensors */
+			read_nim_sensors(drv, i);
+
+			/* 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;
+				reported_link[i] = false;
+				/* Turn off laser and LED, etc. */
+				(void)_create_nim(drv, fpga, i, false);
+				NT_LOG(DBG, ETHDEV, "%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, ETHDEV, "%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 */
+				if (!_nim_is_present(&gpio_phy[i], (uint8_t)i)) {
+					/*
+					 * If there is no Nim present, we need to initialize the
+					 * port anyway
+					 */
+					_port_init(drv, fpga, i);
+				}
+
+				NT_LOG(INF, ETHDEV, "%s: Loopback mode changed=%u\n",
+					drv->mp_port_id_str[i],
+					link_info->port_action[i].port_lpbk_mode);
+				_set_loopback(drv,
+					&mac_pcs[i],
+					i,
+					link_info->port_action[i].port_lpbk_mode,
+					last_lpbk_mode[i]);
+
+				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;
+			}
+
+			(void)_link_state_build(drv, &mac_pcs[i], &gpio_phy[i], i, &new_link_state,
+				is_port_disabled);
+
+			if (!new_link_state.nim_present) {
+				if (link_state[i].nim_present) {
+					NT_LOG(INF, ETHDEV, "%s: NIM module removed\n",
+						drv->mp_port_id_str[i]);
+				}
+
+				link_state[i] = new_link_state;
+				continue;
+			}
+
+			/* NIM module is present */
+			if (new_link_state.lh_nim_absent || !link_state[i].nim_present) {
+				sfp_nim_state_t new_state;
+
+				NT_LOG(DBG, ETHDEV, "%s: NIM module inserted\n",
+					drv->mp_port_id_str[i]);
+
+				if (_port_init(drv, fpga, i)) {
+					NT_LOG(ERR, ETHDEV,
+						"%s: Failed to initialize NIM module\n",
+						drv->mp_port_id_str[i]);
+					continue;
+				}
+
+				if (nim_state_build(&nim_ctx[i], &new_state)) {
+					NT_LOG(ERR, ETHDEV, "%s: Cannot read basic NIM data\n",
+						drv->mp_port_id_str[i]);
+					continue;
+				}
+
+				assert(new_state.br);	/* Cannot be zero if NIM is present */
+				NT_LOG(DBG, ETHDEV,
+					"%s: NIM id = %u (%s), br = %u, vendor = '%s', pn = '%s', sn='%s'\n",
+					drv->mp_port_id_str[i], nim_ctx->nim_id,
+					nim_id_to_text(nim_ctx->nim_id), (unsigned int)new_state.br,
+					nim_ctx->vendor_name, nim_ctx->prod_no, nim_ctx->serial_no);
+
+				(void)_link_state_build(drv, &mac_pcs[i], &gpio_phy[i], i,
+					&link_state[i], is_port_disabled);
+
+				NT_LOG(DBG, ETHDEV, "%s: NIM module initialized\n",
+					drv->mp_port_id_str[i]);
+				continue;
+			}
+
+			if (reported_link[i] != new_link_state.link_up) {
+				NT_LOG(INF, ETHDEV, "%s: link is %s\n", drv->mp_port_id_str[i],
+					(new_link_state.link_up ? "up" : "down"));
+				link_info->link_info[i].link_speed =
+					(new_link_state.link_up ? NT_LINK_SPEED_100G
+						: NT_LINK_SPEED_UNKNOWN);
+				link_state[i].link_up = new_link_state.link_up;
+				reported_link[i] = new_link_state.link_up;
+			}
+
+			check_link_state(drv, &mac_pcs[i]);
+		}	/* 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, ETHDEV, "%s: Stopped NT4GA 100 Gbps link monitoring thread.\n",
+		drv->mp_adapter_id_str);
+
+	return 0;
+}
+
+/*
+ * Userland NIM state machine
+ */
+static void *nt4ga_link_100g_mon(void *data)
+{
+	(void)_common_ptp_nim_state_machine(data);
+
+	return NULL;
+}
+
+/*
+ * 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)
+{
+	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, ETHDEV, "%s: Initializing ports\n", p_adapter_info->mp_adapter_id_str);
+
+	/*
+	 * Initialize global variables
+	 */
+	assert(adapter_no >= 0 && adapter_no < NUM_ADAPTER_MAX);
+
+	if (res == 0 && !p_adapter_info->nt4ga_link.variables_initialized) {
+		nthw_mac_pcs_t *mac_pcs = p_adapter_info->nt4ga_link.u.var100g.mac_pcs100g;
+		nim_i2c_ctx_t *nim_ctx = p_adapter_info->nt4ga_link.u.var100g.nim_ctx;
+		nthw_gfg_t *gfg_mod = p_adapter_info->nt4ga_link.u.var100g.gfg;
+		nthw_gpio_phy_t *gpio_phy = p_adapter_info->nt4ga_link.u.var100g.gpio_phy;
+		int i;
+
+		for (i = 0; i < nb_ports; i++) {
+			/* 2 + adapter port number */
+			const uint8_t instance = (uint8_t)(2U + i);
+			res = nthw_mac_pcs_init(&mac_pcs[i], fpga, i /* int n_instance */);
+
+			if (res != 0)
+				break;
+
+			res = nthw_iic_init(&nim_ctx[i].hwiic, fpga, instance, 8 /* timing */);
+
+			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;
+			res = nthw_gfg_init(&gfg_mod[i], fpga, 0 /* Only one instance */);
+
+			if (res != 0)
+				break;
+
+			res = nthw_gpio_phy_init(&gpio_phy[i], fpga, 0 /* Only one instance */);
+
+			if (res != 0)
+				break;
+		}
+
+		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 = pthread_create(&monitor_tasks[adapter_no], NULL, nt4ga_link_100g_mon,
+					p_adapter_info);
+		}
+	}
+
+	return res;
+}
diff --git a/drivers/net/ntnic/link_mgmt/link_100g/nt4ga_link_100g.h b/drivers/net/ntnic/link_mgmt/link_100g/nt4ga_link_100g.h
new file mode 100644
index 0000000000..0e6103d9a0
--- /dev/null
+++ b/drivers/net/ntnic/link_mgmt/link_100g/nt4ga_link_100g.h
@@ -0,0 +1,11 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NT4GA_LINK_100G_H_
+#define NT4GA_LINK_100G_H_
+
+#include "nthw_drv.h"
+
+#endif	/* NT4GA_LINK_100G_H_ */
diff --git a/drivers/net/ntnic/nthw/core/include/nthw_core.h b/drivers/net/ntnic/nthw/core/include/nthw_core.h
index b72766cc5c..ba7d85b92a 100644
--- a/drivers/net/ntnic/nthw/core/include/nthw_core.h
+++ b/drivers/net/ntnic/nthw/core/include/nthw_core.h
@@ -21,6 +21,8 @@
 
 #include "nthw_gfg.h"
 #include "nthw_gmf.h"
+#include "nthw_gpio_phy.h"
+#include "nthw_mac_pcs.h"
 #include "nthw_mac_pcs_xxv.h"
 #include "nthw_mac_tfg.h"
 #include "nthw_sdc.h"
diff --git a/drivers/net/ntnic/nthw/core/include/nthw_gpio_phy.h b/drivers/net/ntnic/nthw/core/include/nthw_gpio_phy.h
new file mode 100644
index 0000000000..2cc8b65eab
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/include/nthw_gpio_phy.h
@@ -0,0 +1,50 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NTHW_GPIO_PHY_H_
+#define NTHW_GPIO_PHY_H_
+
+#define GPIO_PHY_INTERFACES (2)
+
+typedef struct {
+	nthw_field_t *cfg_fld_lp_mode;	/* Cfg Low Power Mode */
+	nthw_field_t *cfg_int;	/* Cfg Port Interrupt */
+	nthw_field_t *cfg_reset;/* Cfg Reset */
+	nthw_field_t *cfg_mod_prs;	/* Cfg Module Present */
+	nthw_field_t *cfg_pll_int;	/* Cfg PLL Interrupt */
+	nthw_field_t *cfg_port_rxlos;	/* Emulate Cfg Port RXLOS */
+
+	nthw_field_t *gpio_fld_lp_mode;	/* Gpio Low Power Mode */
+	nthw_field_t *gpio_int;	/* Gpio Port Interrupt */
+	nthw_field_t *gpio_reset;	/* Gpio Reset */
+	nthw_field_t *gpio_mod_prs;	/* Gpio Module Present */
+	nthw_field_t *gpio_pll_int;	/* Gpio PLL Interrupt */
+	nthw_field_t *gpio_port_rxlos;	/* Emulate Gpio Port RXLOS */
+} gpio_phy_fields_t;
+
+struct nthw_gpio_phy {
+	nthw_fpga_t *mp_fpga;
+	nthw_module_t *mp_mod_gpio_phy;
+	int mn_instance;
+
+	/* Registers */
+	nthw_register_t *mp_reg_config;
+	nthw_register_t *mp_reg_gpio;
+
+	/* Fields */
+	gpio_phy_fields_t mpa_fields[GPIO_PHY_INTERFACES];
+};
+
+typedef struct nthw_gpio_phy nthw_gpio_phy_t;
+typedef struct nthw_gpio_phy nthw_gpio_phy;
+
+nthw_gpio_phy_t *nthw_gpio_phy_new(void);
+int nthw_gpio_phy_init(nthw_gpio_phy_t *p, nthw_fpga_t *p_fpga, int n_instance);
+
+bool nthw_gpio_phy_is_module_present(nthw_gpio_phy_t *p, uint8_t if_no);
+void nthw_gpio_phy_set_low_power(nthw_gpio_phy_t *p, uint8_t if_no, bool enable);
+void nthw_gpio_phy_set_reset(nthw_gpio_phy_t *p, uint8_t if_no, bool enable);
+
+#endif	/* NTHW_GPIO_PHY_H_ */
diff --git a/drivers/net/ntnic/nthw/core/include/nthw_mac_pcs.h b/drivers/net/ntnic/nthw/core/include/nthw_mac_pcs.h
new file mode 100644
index 0000000000..9243389bef
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/include/nthw_mac_pcs.h
@@ -0,0 +1,263 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef NTHW_MAC_PCS_H_
+#define NTHW_MAC_PCS_H_
+
+enum nthw_mac_pcs_led_mode_e {
+	NTHW_MAC_PCS_LED_AUTO = 0x00,
+	NTHW_MAC_PCS_LED_ON = 0x01,
+	NTHW_MAC_PCS_LED_OFF = 0x02,
+	NTHW_MAC_PCS_LED_PORTID = 0x03,
+};
+
+#define nthw_mac_pcs_receiver_mode_dfe (0)
+#define nthw_mac_pcs_receiver_mode_lpm (1)
+
+struct nthw_mac_pcs {
+	uint8_t m_port_no;
+
+	nthw_fpga_t *mp_fpga;
+	nthw_module_t *mp_mod_mac_pcs;
+	int mn_instance;
+
+	/* Block lock status */
+	nthw_field_t *mp_fld_block_lock_lock;
+	uint32_t m_fld_block_lock_lock_mask;
+
+	/* Lane lock status */
+	nthw_field_t *mp_fld_vl_demuxed_lock;
+	uint32_t m_fld_vl_demuxed_lock_mask;
+
+	/* GTY_STAT */
+	nthw_field_t *mp_fld_gty_stat_rx_rst_done0;
+	nthw_field_t *mp_fld_gty_stat_rx_rst_done1;
+	nthw_field_t *mp_fld_gty_stat_rx_rst_done2;
+	nthw_field_t *mp_fld_gty_stat_rx_rst_done3;
+	nthw_field_t *mp_fld_gty_stat_tx_rst_done0;
+	nthw_field_t *mp_fld_gty_stat_tx_rst_done1;
+	nthw_field_t *mp_fld_gty_stat_tx_rst_done2;
+	nthw_field_t *mp_fld_gty_stat_tx_rst_done3;
+	uint32_t m_fld_gty_stat_rx_rst_done_mask;
+	uint32_t m_fld_gty_stat_tx_rst_done_mask;
+
+	/* GTY_LOOP */
+	nthw_register_t *mp_reg_gty_loop;
+	nthw_field_t *mp_fld_gty_loop_gt_loop0;
+	nthw_field_t *mp_fld_gty_loop_gt_loop1;
+	nthw_field_t *mp_fld_gty_loop_gt_loop2;
+	nthw_field_t *mp_fld_gty_loop_gt_loop3;
+
+	/* MAC_PCS_CONFIG */
+	nthw_field_t *mp_fld_pcs_config_tx_path_rst;
+	nthw_field_t *mp_fld_pcs_config_rx_path_rst;
+	nthw_field_t *mp_fld_pcs_config_rx_enable;
+	nthw_field_t *mp_fld_pcs_config_rx_force_resync;
+	nthw_field_t *mp_fld_pcs_config_rx_test_pattern;
+	nthw_field_t *mp_fld_pcs_config_tx_enable;
+	nthw_field_t *mp_fld_pcs_config_tx_send_idle;
+	nthw_field_t *mp_fld_pcs_config_tx_send_rfi;
+	nthw_field_t *mp_fld_pcs_config_tx_test_pattern;
+
+	/* STAT PCS */
+	nthw_field_t *mp_fld_stat_pcs_rx_status;
+	nthw_field_t *mp_fld_stat_pcs_rx_aligned;
+	nthw_field_t *mp_fld_stat_pcs_rx_aligned_err;
+	nthw_field_t *mp_fld_stat_pcs_rx_misaligned;
+	nthw_field_t *mp_fld_stat_pcs_rx_internal_local_fault;
+	nthw_field_t *mp_fld_stat_pcs_rx_received_local_fault;
+	nthw_field_t *mp_fld_stat_pcs_rx_local_fault;
+	nthw_field_t *mp_fld_stat_pcs_rx_remote_fault;
+	nthw_field_t *mp_fld_stat_pcs_rx_hi_ber;
+
+	/* STAT_PCS_RX_LATCH */
+	nthw_field_t *mp_fld_stat_pcs_rx_latch_status;
+
+	/* PHYMAC_MISC */
+	nthw_field_t *mp_fld_phymac_misc_tx_sel_host;
+	nthw_field_t *mp_fld_phymac_misc_tx_sel_tfg;
+	nthw_field_t *mp_fld_phymac_misc_tx_sel_rx_loop;
+	nthw_field_t *mp_fld_phymac_misc_ts_eop;
+
+	/* LINK_SUMMARY */
+	nthw_register_t *mp_reg_link_summary;
+	nthw_field_t *mp_fld_link_summary_abs;
+	nthw_field_t *mp_fld_link_summary_nt_phy_link_state;
+	nthw_field_t *mp_fld_link_summary_lh_abs;
+	nthw_field_t *mp_fld_link_summary_ll_nt_phy_link_state;
+	nthw_field_t *mp_fld_link_summary_link_down_cnt;
+	nthw_field_t *mp_fld_link_summary_nim_interr;
+	nthw_field_t *mp_fld_link_summary_lh_local_fault;
+	nthw_field_t *mp_fld_link_summary_lh_remote_fault;
+	nthw_field_t *mp_fld_link_summary_local_fault;
+	nthw_field_t *mp_fld_link_summary_remote_fault;
+
+	/* BIP_ERR */
+	nthw_register_t *mp_reg_bip_err;
+	nthw_field_t *mp_fld_reg_bip_err_bip_err;
+
+	/* FEC_CTRL */
+	nthw_register_t *mp_reg_fec_ctrl;
+	nthw_field_t *mp_field_fec_ctrl_reg_rs_fec_ctrl_in;
+
+	/* FEC_STAT */
+	nthw_register_t *mp_reg_fec_stat;
+	nthw_field_t *mp_field_fec_stat_bypass;
+	nthw_field_t *mp_field_fec_stat_valid;
+	nthw_field_t *mp_field_fec_stat_am_lock0;
+	nthw_field_t *mp_field_fec_stat_am_lock1;
+	nthw_field_t *mp_field_fec_stat_am_lock2;
+	nthw_field_t *mp_field_fec_stat_am_lock3;
+	nthw_field_t *mp_field_fec_stat_fec_lane_algn;
+
+	/* FEC Corrected code word count */
+	nthw_register_t *mp_reg_fec_cw_cnt;
+	nthw_field_t *mp_field_fec_cw_cnt_cw_cnt;
+
+	/* FEC Uncorrected code word count */
+	nthw_register_t *mp_reg_fec_ucw_cnt;
+	nthw_field_t *mp_field_fec_ucw_cnt_ucw_cnt;
+
+	/* GTY_RX_BUF_STAT */
+	nthw_register_t *mp_reg_gty_rx_buf_stat;
+	nthw_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat0;
+	nthw_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat1;
+	nthw_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat2;
+	nthw_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat3;
+	nthw_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat_changed0;
+	nthw_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat_changed1;
+	nthw_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat_changed2;
+	nthw_field_t *mp_field_gty_rx_buf_stat_rx_buf_stat_changed3;
+
+	/* GTY_PRE_CURSOR */
+	nthw_register_t *mp_reg_gty_pre_cursor;
+	nthw_field_t *mp_field_gty_pre_cursor_tx_pre_csr0;
+	nthw_field_t *mp_field_gty_pre_cursor_tx_pre_csr1;
+	nthw_field_t *mp_field_gty_pre_cursor_tx_pre_csr2;
+	nthw_field_t *mp_field_gty_pre_cursor_tx_pre_csr3;
+
+	/* GTY_DIFF_CTL */
+	nthw_register_t *mp_reg_gty_diff_ctl;
+	nthw_field_t *mp_field_gty_gty_diff_ctl_tx_diff_ctl0;
+	nthw_field_t *mp_field_gty_gty_diff_ctl_tx_diff_ctl1;
+	nthw_field_t *mp_field_gty_gty_diff_ctl_tx_diff_ctl2;
+	nthw_field_t *mp_field_gty_gty_diff_ctl_tx_diff_ctl3;
+
+	/* GTY_POST_CURSOR */
+	nthw_register_t *mp_reg_gty_post_cursor;
+	nthw_field_t *mp_field_gty_post_cursor_tx_post_csr0;
+	nthw_field_t *mp_field_gty_post_cursor_tx_post_csr1;
+	nthw_field_t *mp_field_gty_post_cursor_tx_post_csr2;
+	nthw_field_t *mp_field_gty_post_cursor_tx_post_csr3;
+
+	/* GTY_CTL */
+	nthw_register_t *mp_reg_gty_ctl;
+	nthw_register_t *mp_reg_gty_ctl_tx;
+	nthw_field_t *mp_field_gty_ctl_tx_pol0;
+	nthw_field_t *mp_field_gty_ctl_tx_pol1;
+	nthw_field_t *mp_field_gty_ctl_tx_pol2;
+	nthw_field_t *mp_field_gty_ctl_tx_pol3;
+	nthw_field_t *mp_field_gty_ctl_rx_pol0;
+	nthw_field_t *mp_field_gty_ctl_rx_pol1;
+	nthw_field_t *mp_field_gty_ctl_rx_pol2;
+	nthw_field_t *mp_field_gty_ctl_rx_pol3;
+	nthw_field_t *mp_field_gty_ctl_rx_lpm_en0;
+	nthw_field_t *mp_field_gty_ctl_rx_lpm_en1;
+	nthw_field_t *mp_field_gty_ctl_rx_lpm_en2;
+	nthw_field_t *mp_field_gty_ctl_rx_lpm_en3;
+	nthw_field_t *mp_field_gty_ctl_rx_equa_rst0;
+	nthw_field_t *mp_field_gty_ctl_rx_equa_rst1;
+	nthw_field_t *mp_field_gty_ctl_rx_equa_rst2;
+	nthw_field_t *mp_field_gty_ctl_rx_equa_rst3;
+
+	/* DEBOUNCE_CTRL */
+	nthw_register_t *mp_reg_debounce_ctrl;
+	nthw_field_t *mp_field_debounce_ctrl_nt_port_ctrl;
+
+	/* TIMESTAMP_COMP */
+	nthw_register_t *mp_reg_time_stamp_comp;
+	nthw_field_t *mp_field_time_stamp_comp_rx_dly;
+	nthw_field_t *mp_field_time_stamp_comp_tx_dly;
+
+	/* STAT_PCS_RX */
+	nthw_register_t *mp_reg_stat_pcs_rx;
+
+	/* STAT_PCS_RX */
+	nthw_register_t *mp_reg_stat_pcs_rx_latch;
+
+	/* PHYMAC_MISC */
+	nthw_register_t *mp_reg_phymac_misc;
+
+	/* BLOCK_LOCK */
+	nthw_register_t *mp_reg_block_lock;
+};
+
+typedef struct nthw_mac_pcs nthw_mac_pcs_t;
+typedef struct nthw_mac_pcs nthw_mac_pcs;
+
+nthw_mac_pcs_t *nthw_mac_pcs_new(void);
+int nthw_mac_pcs_init(nthw_mac_pcs_t *p, nthw_fpga_t *p_fpga, int n_instance);
+void nthw_mac_pcs_delete(nthw_mac_pcs_t *p);
+
+void nthw_mac_pcs_tx_path_rst(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_rx_path_rst(nthw_mac_pcs_t *p, bool enable);
+bool nthw_mac_pcs_is_rx_path_rst(nthw_mac_pcs_t *p);
+bool nthw_mac_pcs_is_ddr3_calib_done(nthw_mac_pcs_t *p);
+/* wrapper - for ease of use */
+void nthw_mac_pcs_tx_host_enable(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_set_rx_enable(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_set_tx_enable(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_set_tx_sel_host(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_set_tx_sel_tfg(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_set_ts_eop(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_set_host_loopback(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_set_line_loopback(nthw_mac_pcs_t *p, bool enable);
+void nthw_mac_pcs_reset_bip_counters(nthw_mac_pcs_t *p);
+void nthw_mac_pcs_get_status(nthw_mac_pcs_t *p, uint8_t *status);
+bool nthw_mac_pcs_get_hi_ber(nthw_mac_pcs_t *p);
+
+void nthw_mac_pcs_get_link_summary1(nthw_mac_pcs_t *p,
+	uint32_t *p_status,
+	uint32_t *p_status_latch,
+	uint32_t *p_aligned,
+	uint32_t *p_local_fault,
+	uint32_t *p_remote_fault);
+
+void nthw_mac_pcs_get_link_summary(nthw_mac_pcs_t *p,
+	uint32_t *p_abs,
+	uint32_t *p_nt_phy_link_state,
+	uint32_t *p_lh_abs,
+	uint32_t *p_ll_nt_phy_link_state,
+	uint32_t *p_link_down_cnt,
+	uint32_t *p_nim_interr,
+	uint32_t *p_lh_local_fault,
+	uint32_t *p_lh_remote_fault,
+	uint32_t *p_local_fault,
+	uint32_t *p_remote_fault);
+
+bool nthw_mac_pcs_reset_required(nthw_mac_pcs_t *p);
+void nthw_mac_pcs_set_fec(nthw_mac_pcs_t *p, bool enable);
+bool nthw_mac_pcs_get_fec_bypass(nthw_mac_pcs_t *p);
+bool nthw_mac_pcs_get_fec_valid(nthw_mac_pcs_t *p);
+bool nthw_mac_pcs_get_fec_stat_all_am_locked(nthw_mac_pcs_t *p);
+void nthw_mac_pcs_reset_fec_counters(nthw_mac_pcs_t *p);
+void nthw_mac_pcs_set_gty_tx_tuning(nthw_mac_pcs_t *p,
+	uint8_t lane,
+	uint8_t tx_pre_csr,
+	uint8_t tx_diff_ctl,
+	uint8_t tx_post_csr);
+void nthw_mac_pcs_swap_gty_tx_polarity(nthw_mac_pcs_t *p, uint8_t lane, bool swap);
+void nthw_mac_pcs_swap_gty_rx_polarity(nthw_mac_pcs_t *p, uint8_t lane, bool swap);
+void nthw_mac_pcs_set_receiver_equalization_mode(nthw_mac_pcs_t *p, uint8_t mode);
+void nthw_mac_pcs_set_led_mode(nthw_mac_pcs_t *p, uint8_t mode);
+void nthw_mac_pcs_set_timestamp_comp_rx(nthw_mac_pcs_t *p, uint16_t rx_dly);
+void nthw_mac_pcs_set_port_no(nthw_mac_pcs_t *p, uint8_t port_no);
+
+uint32_t nthw_mac_pcs_get_fld_block_lock_lock(nthw_mac_pcs_t *p);
+uint32_t nthw_mac_pcs_get_fld_block_lock_lock_mask(nthw_mac_pcs_t *p);
+uint32_t nthw_mac_pcs_get_fld_lane_lock_lock(nthw_mac_pcs_t *p);
+uint32_t nthw_mac_pcs_get_fld_lane_lock_lock_mask(nthw_mac_pcs_t *p);
+
+#endif	/* NTHW_MAC_PCS_H_ */
diff --git a/drivers/net/ntnic/nthw/core/nthw_gpio_phy.c b/drivers/net/ntnic/nthw/core/nthw_gpio_phy.c
new file mode 100644
index 0000000000..c67977341b
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_gpio_phy.c
@@ -0,0 +1,144 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "ntlog.h"
+
+#include "nthw_drv.h"
+#include "nthw_register.h"
+
+#include "nthw_gpio_phy.h"
+
+int nthw_gpio_phy_init(nthw_gpio_phy_t *p, nthw_fpga_t *p_fpga, int n_instance)
+{
+	nthw_module_t *p_mod = nthw_fpga_query_module(p_fpga, MOD_GPIO_PHY, n_instance);
+
+	if (p == NULL)
+		return p_mod == NULL ? -1 : 0;
+
+	if (p_mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: GPIO_PHY %d: no such instance\n",
+			p_fpga->p_fpga_info->mp_adapter_id_str, n_instance);
+		return -1;
+	}
+
+	p->mp_fpga = p_fpga;
+	p->mn_instance = n_instance;
+	p->mp_mod_gpio_phy = p_mod;
+
+	/* Registers */
+	p->mp_reg_config = nthw_module_get_register(p->mp_mod_gpio_phy, GPIO_PHY_CFG);
+	p->mp_reg_gpio = nthw_module_get_register(p->mp_mod_gpio_phy, GPIO_PHY_GPIO);
+
+	/* PORT-0, config fields */
+	p->mpa_fields[0].cfg_fld_lp_mode =
+		nthw_register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT0_LPMODE);
+	p->mpa_fields[0].cfg_int =
+		nthw_register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT0_INT_B);
+	p->mpa_fields[0].cfg_reset =
+		nthw_register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT0_RESET_B);
+	p->mpa_fields[0].cfg_mod_prs =
+		nthw_register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT0_MODPRS_B);
+
+	/* PORT-0, Non-mandatory fields (query_field) */
+	p->mpa_fields[0].cfg_pll_int =
+		nthw_register_query_field(p->mp_reg_config, GPIO_PHY_CFG_PORT0_PLL_INTR);
+	p->mpa_fields[0].cfg_port_rxlos =
+		nthw_register_query_field(p->mp_reg_config, GPIO_PHY_CFG_E_PORT0_RXLOS);
+
+	/* PORT-1, config fields */
+	p->mpa_fields[1].cfg_fld_lp_mode =
+		nthw_register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT1_LPMODE);
+	p->mpa_fields[1].cfg_int =
+		nthw_register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT1_INT_B);
+	p->mpa_fields[1].cfg_reset =
+		nthw_register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT1_RESET_B);
+	p->mpa_fields[1].cfg_mod_prs =
+		nthw_register_get_field(p->mp_reg_config, GPIO_PHY_CFG_PORT1_MODPRS_B);
+
+	/* PORT-1, Non-mandatory fields (query_field) */
+	p->mpa_fields[1].cfg_pll_int =
+		nthw_register_query_field(p->mp_reg_config, GPIO_PHY_CFG_PORT1_PLL_INTR);
+	p->mpa_fields[1].cfg_port_rxlos =
+		nthw_register_query_field(p->mp_reg_config, GPIO_PHY_CFG_E_PORT1_RXLOS);
+
+	/* PORT-0, gpio fields */
+	p->mpa_fields[0].gpio_fld_lp_mode =
+		nthw_register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT0_LPMODE);
+	p->mpa_fields[0].gpio_int =
+		nthw_register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT0_INT_B);
+	p->mpa_fields[0].gpio_reset =
+		nthw_register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT0_RESET_B);
+	p->mpa_fields[0].gpio_mod_prs =
+		nthw_register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT0_MODPRS_B);
+
+	/* PORT-0, Non-mandatory fields (query_field) */
+	p->mpa_fields[0].gpio_pll_int =
+		nthw_register_query_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT0_PLL_INTR);
+	p->mpa_fields[0].gpio_port_rxlos =
+		nthw_register_query_field(p->mp_reg_gpio, GPIO_PHY_GPIO_E_PORT0_RXLOS);
+
+	/* PORT-1, gpio fields */
+	p->mpa_fields[1].gpio_fld_lp_mode =
+		nthw_register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT1_LPMODE);
+	p->mpa_fields[1].gpio_int =
+		nthw_register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT1_INT_B);
+	p->mpa_fields[1].gpio_reset =
+		nthw_register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT1_RESET_B);
+	p->mpa_fields[1].gpio_mod_prs =
+		nthw_register_get_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT1_MODPRS_B);
+
+	/* PORT-1, Non-mandatory fields (query_field) */
+	p->mpa_fields[1].gpio_pll_int =
+		nthw_register_query_field(p->mp_reg_gpio, GPIO_PHY_GPIO_PORT1_PLL_INTR);
+	p->mpa_fields[1].gpio_port_rxlos =
+		nthw_register_query_field(p->mp_reg_gpio, GPIO_PHY_GPIO_E_PORT1_RXLOS);
+
+	nthw_register_update(p->mp_reg_config);
+
+	return 0;
+}
+
+bool nthw_gpio_phy_is_module_present(nthw_gpio_phy_t *p, uint8_t if_no)
+{
+	if (if_no >= ARRAY_SIZE(p->mpa_fields)) {
+		assert(false);
+		return false;
+	}
+
+	/* NOTE: This is a negated GPIO PIN "MODPRS_B" */
+	return nthw_field_get_updated(p->mpa_fields[if_no].gpio_mod_prs) == 0U ? true : false;
+}
+
+void nthw_gpio_phy_set_low_power(nthw_gpio_phy_t *p, uint8_t if_no, bool enable)
+{
+	if (if_no >= ARRAY_SIZE(p->mpa_fields)) {
+		assert(false);
+		return;
+	}
+
+	if (enable)
+		nthw_field_set_flush(p->mpa_fields[if_no].gpio_fld_lp_mode);
+
+	else
+		nthw_field_clr_flush(p->mpa_fields[if_no].gpio_fld_lp_mode);
+
+	nthw_field_clr_flush(p->mpa_fields[if_no].cfg_fld_lp_mode);	/* enable output */
+}
+
+void nthw_gpio_phy_set_reset(nthw_gpio_phy_t *p, uint8_t if_no, bool enable)
+{
+	if (if_no >= ARRAY_SIZE(p->mpa_fields)) {
+		assert(false);
+		return;
+	}
+
+	if (enable)
+		nthw_field_clr_flush(p->mpa_fields[if_no].gpio_reset);
+
+	else
+		nthw_field_set_flush(p->mpa_fields[if_no].gpio_reset);
+
+	nthw_field_clr_flush(p->mpa_fields[if_no].cfg_reset);	/* enable output */
+}
diff --git a/drivers/net/ntnic/nthw/core/nthw_mac_pcs.c b/drivers/net/ntnic/nthw/core/nthw_mac_pcs.c
new file mode 100644
index 0000000000..1e2c9587b5
--- /dev/null
+++ b/drivers/net/ntnic/nthw/core/nthw_mac_pcs.c
@@ -0,0 +1,950 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "nt_util.h"
+#include "ntlog.h"
+
+#include "nthw_drv.h"
+#include "nthw_register.h"
+
+#include "nthw_mac_pcs.h"
+
+#define NTHW_MAC_PCS_LANES (20)
+
+static const uint8_t c_pcs_lanes = NTHW_MAC_PCS_LANES;
+static const uint8_t c_mac_pcs_receiver_mode_dfe;
+
+nthw_mac_pcs_t *nthw_mac_pcs_new(void)
+{
+	nthw_mac_pcs_t *p = malloc(sizeof(nthw_mac_pcs_t));
+
+	if (p)
+		memset(p, 0, sizeof(nthw_mac_pcs_t));
+
+	return p;
+}
+
+void nthw_mac_pcs_delete(nthw_mac_pcs_t *p)
+{
+	if (p) {
+		memset(p, 0, sizeof(nthw_mac_pcs_t));
+		free(p);
+	}
+}
+
+/*
+ * Parameters:
+ *   p != NULL: init struct pointed to by p
+ *   p == NULL: check fpga module(s) presence (but no struct to init)
+ *
+ * Return value:
+ *  <0: if p == NULL then fpga module(s) is/are not present.
+ *      if p != NULL then fpga module(s) is/are not present, struct undefined
+ * ==0: if p == NULL then fpga module(s) is/are present (no struct to init)
+ *    : if p != NULL then fpga module(s) is/are present and struct initialized
+ */
+int nthw_mac_pcs_init(nthw_mac_pcs_t *p, nthw_fpga_t *p_fpga, int n_instance)
+{
+	nthw_module_t *mod = nthw_fpga_query_module(p_fpga, MOD_MAC_PCS, n_instance);
+
+	if (p == NULL)
+		return mod == NULL ? -1 : 0;
+
+	if (mod == NULL) {
+		NT_LOG(ERR, NTHW, "%s: MAC_PCS %d: no such instance\n",
+			p_fpga->p_fpga_info->mp_adapter_id_str, n_instance);
+		return -1;
+	}
+
+	p->mp_fpga = p_fpga;
+	p->mn_instance = n_instance;
+	p->mp_mod_mac_pcs = mod;
+
+	assert(n_instance >= 0 && n_instance <= 255);
+	nthw_mac_pcs_set_port_no(p, (uint8_t)n_instance);
+
+	{
+		nthw_register_t *p_reg_block_lock, *p_reg_stat_pcs_rx, *p_reg_stat_pcs_rx_latch;
+		nthw_register_t *p_reg_vl_demuxed, *p_reg_gty_stat, *p_reg_pcs_config,
+				*p_reg_phymac_misc;
+		const int product_id = nthw_fpga_get_product_id(p_fpga);
+
+		p_reg_block_lock = nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_BLOCK_LOCK);
+		p->mp_reg_block_lock = p_reg_block_lock;
+		p->mp_fld_block_lock_lock =
+			nthw_register_get_field(p_reg_block_lock, MAC_PCS_BLOCK_LOCK_LOCK);
+
+		p_reg_stat_pcs_rx =
+			nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_STAT_PCS_RX);
+		p->mp_reg_stat_pcs_rx = p_reg_stat_pcs_rx;
+		p->mp_fld_stat_pcs_rx_status =
+			nthw_register_get_field(p_reg_stat_pcs_rx, MAC_PCS_STAT_PCS_RX_STATUS);
+		p->mp_fld_stat_pcs_rx_aligned =
+			nthw_register_get_field(p_reg_stat_pcs_rx, MAC_PCS_STAT_PCS_RX_ALIGNED);
+		p->mp_fld_stat_pcs_rx_aligned_err =
+			nthw_register_get_field(p_reg_stat_pcs_rx,
+				MAC_PCS_STAT_PCS_RX_ALIGNED_ERR);
+		p->mp_fld_stat_pcs_rx_misaligned =
+			nthw_register_get_field(p_reg_stat_pcs_rx, MAC_PCS_STAT_PCS_RX_MISALIGNED);
+		p->mp_fld_stat_pcs_rx_internal_local_fault =
+			nthw_register_get_field(p_reg_stat_pcs_rx,
+				MAC_PCS_STAT_PCS_RX_INTERNAL_LOCAL_FAULT);
+		p->mp_fld_stat_pcs_rx_received_local_fault =
+			nthw_register_get_field(p_reg_stat_pcs_rx,
+				MAC_PCS_STAT_PCS_RX_RECEIVED_LOCAL_FAULT);
+		p->mp_fld_stat_pcs_rx_local_fault =
+			nthw_register_get_field(p_reg_stat_pcs_rx,
+				MAC_PCS_STAT_PCS_RX_LOCAL_FAULT);
+		p->mp_fld_stat_pcs_rx_remote_fault =
+			nthw_register_get_field(p_reg_stat_pcs_rx,
+				MAC_PCS_STAT_PCS_RX_REMOTE_FAULT);
+		p->mp_fld_stat_pcs_rx_hi_ber =
+			nthw_register_get_field(p_reg_stat_pcs_rx, MAC_PCS_STAT_PCS_RX_HI_BER);
+
+		p_reg_stat_pcs_rx_latch =
+			nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_STAT_PCS_RX_LATCH);
+		p->mp_reg_stat_pcs_rx_latch = p_reg_stat_pcs_rx_latch;
+		p->mp_fld_stat_pcs_rx_latch_status =
+			nthw_register_get_field(p_reg_stat_pcs_rx_latch,
+				MAC_PCS_STAT_PCS_RX_LATCH_STATUS);
+
+		p_reg_vl_demuxed = nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_VL_DEMUXED);
+		p->mp_fld_vl_demuxed_lock =
+			nthw_register_get_field(p_reg_vl_demuxed, MAC_PCS_VL_DEMUXED_LOCK);
+
+		p_reg_gty_stat = nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_GTY_STAT);
+		p->mp_fld_gty_stat_tx_rst_done0 =
+			nthw_register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_TX_RST_DONE_0);
+		p->mp_fld_gty_stat_tx_rst_done1 =
+			nthw_register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_TX_RST_DONE_1);
+		p->mp_fld_gty_stat_tx_rst_done2 =
+			nthw_register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_TX_RST_DONE_2);
+		p->mp_fld_gty_stat_tx_rst_done3 =
+			nthw_register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_TX_RST_DONE_3);
+		p->mp_fld_gty_stat_rx_rst_done0 =
+			nthw_register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_RX_RST_DONE_0);
+		p->mp_fld_gty_stat_rx_rst_done1 =
+			nthw_register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_RX_RST_DONE_1);
+		p->mp_fld_gty_stat_rx_rst_done2 =
+			nthw_register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_RX_RST_DONE_2);
+		p->mp_fld_gty_stat_rx_rst_done3 =
+			nthw_register_get_field(p_reg_gty_stat, MAC_PCS_GTY_STAT_RX_RST_DONE_3);
+
+		p->m_fld_block_lock_lock_mask = 0;
+		p->m_fld_vl_demuxed_lock_mask = 0;
+		p->m_fld_gty_stat_tx_rst_done_mask = 0;
+		p->m_fld_gty_stat_rx_rst_done_mask = 0;
+
+		if (product_id == 9563) {
+			/* NT200A01_2X100 implements 20 virtual lanes */
+			p->m_fld_block_lock_lock_mask = (1 << 20) - 1;
+			/* NT200A01_2X100 implements 20 virtual lanes */
+			p->m_fld_vl_demuxed_lock_mask = (1 << 20) - 1;
+			p->m_fld_gty_stat_tx_rst_done_mask =
+				1;	/* NT200A01_2X100 implements 4 GTY */
+			p->m_fld_gty_stat_rx_rst_done_mask =
+				1;	/* NT200A01_2X100 implements 4 GTY */
+
+		} else {
+			/* Remember to add new product_ids */
+			assert(0);
+		}
+
+		p_reg_pcs_config =
+			nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_MAC_PCS_CONFIG);
+		p->mp_fld_pcs_config_tx_path_rst =
+			nthw_register_get_field(p_reg_pcs_config,
+				MAC_PCS_MAC_PCS_CONFIG_TX_PATH_RST);
+		p->mp_fld_pcs_config_rx_path_rst =
+			nthw_register_get_field(p_reg_pcs_config,
+				MAC_PCS_MAC_PCS_CONFIG_RX_PATH_RST);
+		p->mp_fld_pcs_config_rx_enable =
+			nthw_register_get_field(p_reg_pcs_config,
+				MAC_PCS_MAC_PCS_CONFIG_RX_ENABLE);
+		p->mp_fld_pcs_config_rx_force_resync =
+			nthw_register_get_field(p_reg_pcs_config,
+				MAC_PCS_MAC_PCS_CONFIG_RX_FORCE_RESYNC);
+		p->mp_fld_pcs_config_rx_test_pattern =
+			nthw_register_get_field(p_reg_pcs_config,
+				MAC_PCS_MAC_PCS_CONFIG_RX_TEST_PATTERN);
+		p->mp_fld_pcs_config_tx_enable =
+			nthw_register_get_field(p_reg_pcs_config,
+				MAC_PCS_MAC_PCS_CONFIG_TX_ENABLE);
+		p->mp_fld_pcs_config_tx_send_idle =
+			nthw_register_get_field(p_reg_pcs_config,
+				MAC_PCS_MAC_PCS_CONFIG_TX_SEND_IDLE);
+		p->mp_fld_pcs_config_tx_send_rfi =
+			nthw_register_get_field(p_reg_pcs_config,
+				MAC_PCS_MAC_PCS_CONFIG_TX_SEND_RFI);
+		p->mp_fld_pcs_config_tx_test_pattern =
+			nthw_register_get_field(p_reg_pcs_config,
+				MAC_PCS_MAC_PCS_CONFIG_TX_TEST_PATTERN);
+
+		p->mp_reg_gty_loop = nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_GTY_LOOP);
+		p->mp_fld_gty_loop_gt_loop0 =
+			nthw_register_get_field(p->mp_reg_gty_loop, MAC_PCS_GTY_LOOP_GT_LOOP_0);
+		p->mp_fld_gty_loop_gt_loop1 =
+			nthw_register_get_field(p->mp_reg_gty_loop, MAC_PCS_GTY_LOOP_GT_LOOP_1);
+		p->mp_fld_gty_loop_gt_loop2 =
+			nthw_register_get_field(p->mp_reg_gty_loop, MAC_PCS_GTY_LOOP_GT_LOOP_2);
+		p->mp_fld_gty_loop_gt_loop3 =
+			nthw_register_get_field(p->mp_reg_gty_loop, MAC_PCS_GTY_LOOP_GT_LOOP_3);
+
+		p_reg_phymac_misc =
+			nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_PHYMAC_MISC);
+		p->mp_reg_phymac_misc = p_reg_phymac_misc;
+		p->mp_fld_phymac_misc_tx_sel_host =
+			nthw_register_get_field(p_reg_phymac_misc,
+				MAC_PCS_PHYMAC_MISC_TX_SEL_HOST);
+		p->mp_fld_phymac_misc_tx_sel_tfg =
+			nthw_register_get_field(p_reg_phymac_misc, MAC_PCS_PHYMAC_MISC_TX_SEL_TFG);
+		p->mp_fld_phymac_misc_tx_sel_rx_loop =
+			nthw_register_get_field(p_reg_phymac_misc,
+				MAC_PCS_PHYMAC_MISC_TX_SEL_RX_LOOP);
+
+		/* SOP or EOP TIMESTAMP */
+		p->mp_fld_phymac_misc_ts_eop =
+			nthw_register_query_field(p_reg_phymac_misc, MAC_PCS_PHYMAC_MISC_TS_EOP);
+
+		p->mp_reg_link_summary =
+			nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_LINK_SUMMARY);
+		p->mp_fld_link_summary_abs =
+			nthw_register_get_field(p->mp_reg_link_summary, MAC_PCS_LINK_SUMMARY_ABS);
+		p->mp_fld_link_summary_nt_phy_link_state =
+			nthw_register_get_field(p->mp_reg_link_summary,
+				MAC_PCS_LINK_SUMMARY_NT_PHY_LINK_STATE);
+		p->mp_fld_link_summary_lh_abs =
+			nthw_register_get_field(p->mp_reg_link_summary,
+				MAC_PCS_LINK_SUMMARY_LH_ABS);
+		p->mp_fld_link_summary_ll_nt_phy_link_state =
+			nthw_register_get_field(p->mp_reg_link_summary,
+				MAC_PCS_LINK_SUMMARY_LL_PHY_LINK_STATE);
+		p->mp_fld_link_summary_link_down_cnt =
+			nthw_register_get_field(p->mp_reg_link_summary,
+				MAC_PCS_LINK_SUMMARY_LINK_DOWN_CNT);
+		p->mp_fld_link_summary_nim_interr =
+			nthw_register_get_field(p->mp_reg_link_summary,
+				MAC_PCS_LINK_SUMMARY_NIM_INTERR);
+		p->mp_fld_link_summary_lh_local_fault =
+			nthw_register_get_field(p->mp_reg_link_summary,
+				MAC_PCS_LINK_SUMMARY_LH_LOCAL_FAULT);
+		p->mp_fld_link_summary_lh_remote_fault =
+			nthw_register_get_field(p->mp_reg_link_summary,
+				MAC_PCS_LINK_SUMMARY_LH_REMOTE_FAULT);
+		p->mp_fld_link_summary_local_fault =
+			nthw_register_get_field(p->mp_reg_link_summary,
+				MAC_PCS_LINK_SUMMARY_LOCAL_FAULT);
+		p->mp_fld_link_summary_remote_fault =
+			nthw_register_get_field(p->mp_reg_link_summary,
+				MAC_PCS_LINK_SUMMARY_REMOTE_FAULT);
+
+		p->mp_reg_bip_err = nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_BIP_ERR);
+		p->mp_fld_reg_bip_err_bip_err =
+			nthw_register_get_field(p->mp_reg_bip_err, MAC_PCS_BIP_ERR_BIP_ERR);
+
+		p->mp_reg_fec_ctrl = nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_FEC_CTRL);
+		p->mp_field_fec_ctrl_reg_rs_fec_ctrl_in =
+			nthw_register_get_field(p->mp_reg_fec_ctrl,
+				MAC_PCS_FEC_CTRL_RS_FEC_CTRL_IN);
+
+		p->mp_reg_fec_stat = nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_FEC_STAT);
+		p->mp_field_fec_stat_bypass =
+			nthw_register_get_field(p->mp_reg_fec_stat, MAC_PCS_FEC_STAT_BYPASS);
+		p->mp_field_fec_stat_valid =
+			nthw_register_get_field(p->mp_reg_fec_stat, MAC_PCS_FEC_STAT_VALID);
+		p->mp_field_fec_stat_am_lock0 =
+			nthw_register_get_field(p->mp_reg_fec_stat, MAC_PCS_FEC_STAT_AM_LOCK_0);
+		p->mp_field_fec_stat_am_lock1 =
+			nthw_register_get_field(p->mp_reg_fec_stat, MAC_PCS_FEC_STAT_AM_LOCK_1);
+		p->mp_field_fec_stat_am_lock2 =
+			nthw_register_get_field(p->mp_reg_fec_stat, MAC_PCS_FEC_STAT_AM_LOCK_2);
+		p->mp_field_fec_stat_am_lock3 =
+			nthw_register_get_field(p->mp_reg_fec_stat, MAC_PCS_FEC_STAT_AM_LOCK_3);
+		p->mp_field_fec_stat_fec_lane_algn =
+			nthw_register_get_field(p->mp_reg_fec_stat,
+				MAC_PCS_FEC_STAT_FEC_LANE_ALGN);
+
+		p->mp_reg_fec_cw_cnt =
+			nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_FEC_CW_CNT);
+		p->mp_field_fec_cw_cnt_cw_cnt =
+			nthw_register_get_field(p->mp_reg_fec_cw_cnt, MAC_PCS_FEC_CW_CNT_CW_CNT);
+
+		p->mp_reg_fec_ucw_cnt =
+			nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_FEC_UCW_CNT);
+		p->mp_field_fec_ucw_cnt_ucw_cnt =
+			nthw_register_get_field(p->mp_reg_fec_ucw_cnt,
+				MAC_PCS_FEC_UCW_CNT_UCW_CNT);
+
+		/* GTY_RX_BUF_STAT */
+#ifdef RXBUFSTAT
+		p->mp_reg_gty_rx_buf_stat =
+			nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_GTY_RX_BUF_STAT);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat0 =
+			nthw_register_get_field(p->mp_reg_gty_rx_buf_stat,
+				MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_0);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat1 =
+			nthw_register_get_field(p->mp_reg_gty_rx_buf_stat,
+				MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_1);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat2 =
+			nthw_register_get_field(p->mp_reg_gty_rx_buf_stat,
+				MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_2);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat3 =
+			nthw_register_get_field(p->mp_reg_gty_rx_buf_stat,
+				MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_3);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat_changed0 =
+			nthw_register_get_field(p->mp_reg_gty_rx_buf_stat,
+				MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_CHANGED_0);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat_changed1 =
+			nthw_register_get_field(p->mp_reg_gty_rx_buf_stat,
+				MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_CHANGED_1);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat_changed2 =
+			nthw_register_get_field(p->mp_reg_gty_rx_buf_stat,
+				MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_CHANGED_2);
+		p->mp_field_gty_rx_buf_stat_rx_buf_stat_changed3 =
+			nthw_register_get_field(p->mp_reg_gty_rx_buf_stat,
+				MAC_PCS_GTY_RX_BUF_STAT_RX_BUF_STAT_CHANGED_3);
+#endif
+
+		/* GTY_PRE_CURSOR */
+		p->mp_reg_gty_pre_cursor =
+			nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_GTY_PRE_CURSOR);
+		p->mp_field_gty_pre_cursor_tx_pre_csr0 =
+			nthw_register_get_field(p->mp_reg_gty_pre_cursor,
+				MAC_PCS_GTY_PRE_CURSOR_TX_PRE_CSR_0);
+		p->mp_field_gty_pre_cursor_tx_pre_csr1 =
+			nthw_register_get_field(p->mp_reg_gty_pre_cursor,
+				MAC_PCS_GTY_PRE_CURSOR_TX_PRE_CSR_1);
+		p->mp_field_gty_pre_cursor_tx_pre_csr2 =
+			nthw_register_get_field(p->mp_reg_gty_pre_cursor,
+				MAC_PCS_GTY_PRE_CURSOR_TX_PRE_CSR_2);
+		p->mp_field_gty_pre_cursor_tx_pre_csr3 =
+			nthw_register_get_field(p->mp_reg_gty_pre_cursor,
+				MAC_PCS_GTY_PRE_CURSOR_TX_PRE_CSR_3);
+
+		/* GTY_DIFF_CTL */
+		p->mp_reg_gty_diff_ctl =
+			nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_GTY_DIFF_CTL);
+		p->mp_field_gty_gty_diff_ctl_tx_diff_ctl0 =
+			nthw_register_get_field(p->mp_reg_gty_diff_ctl,
+				MAC_PCS_GTY_DIFF_CTL_TX_DIFF_CTL_0);
+		p->mp_field_gty_gty_diff_ctl_tx_diff_ctl1 =
+			nthw_register_get_field(p->mp_reg_gty_diff_ctl,
+				MAC_PCS_GTY_DIFF_CTL_TX_DIFF_CTL_1);
+		p->mp_field_gty_gty_diff_ctl_tx_diff_ctl2 =
+			nthw_register_get_field(p->mp_reg_gty_diff_ctl,
+				MAC_PCS_GTY_DIFF_CTL_TX_DIFF_CTL_2);
+		p->mp_field_gty_gty_diff_ctl_tx_diff_ctl3 =
+			nthw_register_get_field(p->mp_reg_gty_diff_ctl,
+				MAC_PCS_GTY_DIFF_CTL_TX_DIFF_CTL_3);
+
+		/* GTY_POST_CURSOR */
+		p->mp_reg_gty_post_cursor =
+			nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_GTY_POST_CURSOR);
+		p->mp_field_gty_post_cursor_tx_post_csr0 =
+			nthw_register_get_field(p->mp_reg_gty_post_cursor,
+				MAC_PCS_GTY_POST_CURSOR_TX_POST_CSR_0);
+		p->mp_field_gty_post_cursor_tx_post_csr1 =
+			nthw_register_get_field(p->mp_reg_gty_post_cursor,
+				MAC_PCS_GTY_POST_CURSOR_TX_POST_CSR_1);
+		p->mp_field_gty_post_cursor_tx_post_csr2 =
+			nthw_register_get_field(p->mp_reg_gty_post_cursor,
+				MAC_PCS_GTY_POST_CURSOR_TX_POST_CSR_2);
+		p->mp_field_gty_post_cursor_tx_post_csr3 =
+			nthw_register_get_field(p->mp_reg_gty_post_cursor,
+				MAC_PCS_GTY_POST_CURSOR_TX_POST_CSR_3);
+
+		/* GTY_CTL */
+		p->mp_reg_gty_ctl = nthw_module_query_register(p->mp_mod_mac_pcs, MAC_PCS_GTY_CTL);
+
+		if (p->mp_reg_gty_ctl) {
+			p->mp_field_gty_ctl_tx_pol0 =
+				nthw_register_get_field(p->mp_reg_gty_ctl,
+					MAC_PCS_GTY_CTL_TX_POLARITY_0);
+			p->mp_field_gty_ctl_tx_pol1 =
+				nthw_register_get_field(p->mp_reg_gty_ctl,
+					MAC_PCS_GTY_CTL_TX_POLARITY_1);
+			p->mp_field_gty_ctl_tx_pol2 =
+				nthw_register_get_field(p->mp_reg_gty_ctl,
+					MAC_PCS_GTY_CTL_TX_POLARITY_2);
+			p->mp_field_gty_ctl_tx_pol3 =
+				nthw_register_get_field(p->mp_reg_gty_ctl,
+					MAC_PCS_GTY_CTL_TX_POLARITY_3);
+
+		} else {
+			p->mp_reg_gty_ctl =
+				nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_GTY_CTL_RX);
+			p->mp_reg_gty_ctl_tx =
+				nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_GTY_CTL_TX);
+			p->mp_field_gty_ctl_tx_pol0 =
+				nthw_register_get_field(p->mp_reg_gty_ctl_tx,
+					MAC_PCS_GTY_CTL_TX_POLARITY_0);
+			p->mp_field_gty_ctl_tx_pol1 =
+				nthw_register_get_field(p->mp_reg_gty_ctl_tx,
+					MAC_PCS_GTY_CTL_TX_POLARITY_1);
+			p->mp_field_gty_ctl_tx_pol2 =
+				nthw_register_get_field(p->mp_reg_gty_ctl_tx,
+					MAC_PCS_GTY_CTL_TX_POLARITY_2);
+			p->mp_field_gty_ctl_tx_pol3 =
+				nthw_register_get_field(p->mp_reg_gty_ctl_tx,
+					MAC_PCS_GTY_CTL_TX_POLARITY_3);
+		}
+
+		p->mp_field_gty_ctl_rx_pol0 =
+			nthw_register_get_field(p->mp_reg_gty_ctl, MAC_PCS_GTY_CTL_RX_POLARITY_0);
+		p->mp_field_gty_ctl_rx_pol1 =
+			nthw_register_get_field(p->mp_reg_gty_ctl, MAC_PCS_GTY_CTL_RX_POLARITY_1);
+		p->mp_field_gty_ctl_rx_pol2 =
+			nthw_register_get_field(p->mp_reg_gty_ctl, MAC_PCS_GTY_CTL_RX_POLARITY_2);
+		p->mp_field_gty_ctl_rx_pol3 =
+			nthw_register_get_field(p->mp_reg_gty_ctl, MAC_PCS_GTY_CTL_RX_POLARITY_3);
+		p->mp_field_gty_ctl_rx_lpm_en0 =
+			nthw_register_get_field(p->mp_reg_gty_ctl, MAC_PCS_GTY_CTL_RX_LPM_EN_0);
+		p->mp_field_gty_ctl_rx_lpm_en1 =
+			nthw_register_get_field(p->mp_reg_gty_ctl, MAC_PCS_GTY_CTL_RX_LPM_EN_1);
+		p->mp_field_gty_ctl_rx_lpm_en2 =
+			nthw_register_get_field(p->mp_reg_gty_ctl, MAC_PCS_GTY_CTL_RX_LPM_EN_2);
+		p->mp_field_gty_ctl_rx_lpm_en3 =
+			nthw_register_get_field(p->mp_reg_gty_ctl, MAC_PCS_GTY_CTL_RX_LPM_EN_3);
+		p->mp_field_gty_ctl_rx_equa_rst0 =
+			nthw_register_get_field(p->mp_reg_gty_ctl, MAC_PCS_GTY_CTL_RX_EQUA_RST_0);
+		p->mp_field_gty_ctl_rx_equa_rst1 =
+			nthw_register_get_field(p->mp_reg_gty_ctl, MAC_PCS_GTY_CTL_RX_EQUA_RST_1);
+		p->mp_field_gty_ctl_rx_equa_rst2 =
+			nthw_register_get_field(p->mp_reg_gty_ctl, MAC_PCS_GTY_CTL_RX_EQUA_RST_2);
+		p->mp_field_gty_ctl_rx_equa_rst3 =
+			nthw_register_get_field(p->mp_reg_gty_ctl, MAC_PCS_GTY_CTL_RX_EQUA_RST_3);
+
+		/* DEBOUNCE_CTRL */
+		p->mp_reg_debounce_ctrl =
+			nthw_module_get_register(p->mp_mod_mac_pcs, MAC_PCS_DEBOUNCE_CTRL);
+		p->mp_field_debounce_ctrl_nt_port_ctrl =
+			nthw_register_get_field(p->mp_reg_debounce_ctrl,
+				MAC_PCS_DEBOUNCE_CTRL_NT_PORT_CTRL);
+
+		p->mp_reg_time_stamp_comp =
+			nthw_module_query_register(p->mp_mod_mac_pcs, MAC_PCS_TIMESTAMP_COMP);
+
+		if (p->mp_reg_time_stamp_comp) {
+			/* TIMESTAMP_COMP */
+			p->mp_field_time_stamp_comp_rx_dly =
+				nthw_register_get_field(p->mp_reg_time_stamp_comp,
+					MAC_PCS_TIMESTAMP_COMP_RX_DLY);
+			p->mp_field_time_stamp_comp_tx_dly =
+				nthw_register_get_field(p->mp_reg_time_stamp_comp,
+					MAC_PCS_TIMESTAMP_COMP_TX_DLY);
+		}
+	}
+	return 0;
+}
+
+/* wrapper - for ease of use */
+void nthw_mac_pcs_tx_host_enable(nthw_mac_pcs_t *p, bool enable)
+{
+	nthw_mac_pcs_set_tx_sel_host(p, enable);
+	nthw_mac_pcs_set_tx_sel_tfg(p, !enable);
+}
+
+void nthw_mac_pcs_set_rx_enable(nthw_mac_pcs_t *p, bool enable)
+{
+	nthw_field_get_updated(p->mp_fld_pcs_config_rx_enable);
+
+	if (enable)
+		nthw_field_set_flush(p->mp_fld_pcs_config_rx_enable);
+
+	else
+		nthw_field_clr_flush(p->mp_fld_pcs_config_rx_enable);
+}
+
+void nthw_mac_pcs_set_tx_enable(nthw_mac_pcs_t *p, bool enable)
+{
+	nthw_field_get_updated(p->mp_fld_pcs_config_tx_enable);
+
+	if (enable)
+		nthw_field_set_flush(p->mp_fld_pcs_config_tx_enable);
+
+	else
+		nthw_field_clr_flush(p->mp_fld_pcs_config_tx_enable);
+}
+
+void nthw_mac_pcs_set_tx_sel_host(nthw_mac_pcs_t *p, bool enable)
+{
+	nthw_field_get_updated(p->mp_fld_phymac_misc_tx_sel_host);
+
+	if (enable)
+		nthw_field_set_flush(p->mp_fld_phymac_misc_tx_sel_host);
+
+	else
+		nthw_field_clr_flush(p->mp_fld_phymac_misc_tx_sel_host);
+}
+
+void nthw_mac_pcs_set_tx_sel_tfg(nthw_mac_pcs_t *p, bool enable)
+{
+	nthw_field_get_updated(p->mp_fld_phymac_misc_tx_sel_tfg);
+
+	if (enable)
+		nthw_field_set_flush(p->mp_fld_phymac_misc_tx_sel_tfg);
+
+	else
+		nthw_field_clr_flush(p->mp_fld_phymac_misc_tx_sel_tfg);
+}
+
+void nthw_mac_pcs_set_ts_eop(nthw_mac_pcs_t *p, bool enable)
+{
+	if (p->mp_fld_phymac_misc_ts_eop) {
+		nthw_field_get_updated(p->mp_fld_phymac_misc_ts_eop);
+
+		if (enable)
+			nthw_field_set_flush(p->mp_fld_phymac_misc_ts_eop);
+
+		else
+			nthw_field_clr_flush(p->mp_fld_phymac_misc_ts_eop);
+	}
+}
+
+void nthw_mac_pcs_tx_path_rst(nthw_mac_pcs_t *p, bool enable)
+{
+	nthw_field_get_updated(p->mp_fld_pcs_config_tx_path_rst);
+
+	if (enable)
+		nthw_field_set_flush(p->mp_fld_pcs_config_tx_path_rst);
+
+	else
+		nthw_field_clr_flush(p->mp_fld_pcs_config_tx_path_rst);
+}
+
+void nthw_mac_pcs_rx_path_rst(nthw_mac_pcs_t *p, bool enable)
+{
+	nthw_field_get_updated(p->mp_fld_pcs_config_rx_path_rst);
+
+	if (enable)
+		nthw_field_set_flush(p->mp_fld_pcs_config_rx_path_rst);
+
+	else
+		nthw_field_clr_flush(p->mp_fld_pcs_config_rx_path_rst);
+}
+
+bool nthw_mac_pcs_is_rx_path_rst(nthw_mac_pcs_t *p)
+{
+	return nthw_field_get_updated(p->mp_fld_pcs_config_rx_path_rst);
+}
+
+void nthw_mac_pcs_set_host_loopback(nthw_mac_pcs_t *p, bool enable)
+{
+	nthw_register_update(p->mp_reg_gty_loop);
+
+	if (enable) {
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop0, 2);
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop1, 2);
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop2, 2);
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop3, 2);
+
+	} else {
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop0, 0);
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop1, 0);
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop2, 0);
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop3, 0);
+	}
+
+	nthw_register_flush(p->mp_reg_gty_loop, 1);
+}
+
+void nthw_mac_pcs_set_line_loopback(nthw_mac_pcs_t *p, bool enable)
+{
+	nthw_register_update(p->mp_reg_gty_loop);
+
+	if (enable) {
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop0, 4);
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop1, 4);
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop2, 4);
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop3, 4);
+
+	} else {
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop0, 0);
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop1, 0);
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop2, 0);
+		nthw_field_set_val32(p->mp_fld_gty_loop_gt_loop3, 0);
+	}
+
+	nthw_register_flush(p->mp_reg_gty_loop, 1);
+}
+
+void nthw_mac_pcs_reset_bip_counters(nthw_mac_pcs_t *p)
+{
+	uint32_t lane_bit_errors[NTHW_MAC_PCS_LANES];
+	nthw_register_update(p->mp_reg_bip_err);
+	nthw_field_get_val(p->mp_fld_reg_bip_err_bip_err, (uint32_t *)lane_bit_errors,
+		ARRAY_SIZE(lane_bit_errors));
+
+#if defined(DEBUG)
+	{
+		uint8_t lane;
+
+		for (lane = 0; lane < c_pcs_lanes; lane++) {
+			if (lane_bit_errors[lane]) {
+				NT_LOG(DBG, NTHW, "Port %u: pcsLane %2u: BIP8 errors: %u\n",
+					p->m_port_no, lane, lane_bit_errors[lane]);
+			}
+		}
+	}
+#else
+	(void)c_pcs_lanes;	/* unused - kill warning */
+#endif
+}
+
+void nthw_mac_pcs_get_status(nthw_mac_pcs_t *p, uint8_t *status)
+{
+	*status = nthw_field_get_updated(p->mp_fld_stat_pcs_rx_status) & 0x01;
+}
+
+bool nthw_mac_pcs_get_hi_ber(nthw_mac_pcs_t *p)
+{
+	return nthw_field_get_updated(p->mp_fld_stat_pcs_rx_hi_ber);
+}
+
+void nthw_mac_pcs_get_link_summary(nthw_mac_pcs_t *p,
+	uint32_t *p_abs,
+	uint32_t *p_nt_phy_link_state,
+	uint32_t *p_lh_abs,
+	uint32_t *p_ll_nt_phy_link_state,
+	uint32_t *p_link_down_cnt,
+	uint32_t *p_nim_interr,
+	uint32_t *p_lh_local_fault,
+	uint32_t *p_lh_remote_fault,
+	uint32_t *p_local_fault,
+	uint32_t *p_remote_fault)
+{
+	nthw_register_update(p->mp_reg_link_summary);
+
+	if (p_abs)
+		*p_abs = nthw_field_get_val32(p->mp_fld_link_summary_abs);
+
+	if (p_nt_phy_link_state) {
+		*p_nt_phy_link_state =
+			nthw_field_get_val32(p->mp_fld_link_summary_nt_phy_link_state);
+	}
+
+	if (p_lh_abs)
+		*p_lh_abs = nthw_field_get_val32(p->mp_fld_link_summary_lh_abs);
+
+	if (p_ll_nt_phy_link_state) {
+		*p_ll_nt_phy_link_state =
+			nthw_field_get_val32(p->mp_fld_link_summary_ll_nt_phy_link_state);
+	}
+
+	if (p_link_down_cnt)
+		*p_link_down_cnt = nthw_field_get_val32(p->mp_fld_link_summary_link_down_cnt);
+
+	if (p_nim_interr)
+		*p_nim_interr = nthw_field_get_val32(p->mp_fld_link_summary_nim_interr);
+
+	if (p_lh_local_fault)
+		*p_lh_local_fault = nthw_field_get_val32(p->mp_fld_link_summary_lh_local_fault);
+
+	if (p_lh_remote_fault)
+		*p_lh_remote_fault = nthw_field_get_val32(p->mp_fld_link_summary_lh_remote_fault);
+
+	if (p_local_fault)
+		*p_local_fault = nthw_field_get_val32(p->mp_fld_link_summary_local_fault);
+
+	if (p_remote_fault)
+		*p_remote_fault = nthw_field_get_val32(p->mp_fld_link_summary_remote_fault);
+}
+
+/*
+ * Returns true if the lane/block lock bits indicate that a reset is required.
+ * This is the case if Block/Lane lock is not all zero but not all set either.
+ */
+bool nthw_mac_pcs_reset_required(nthw_mac_pcs_t *p)
+{
+	uint32_t block_lock = nthw_mac_pcs_get_fld_block_lock_lock(p);
+	uint32_t lane_lock = nthw_mac_pcs_get_fld_lane_lock_lock(p);
+	uint32_t block_lock_mask = nthw_mac_pcs_get_fld_block_lock_lock_mask(p);
+	uint32_t lane_lock_mask = nthw_mac_pcs_get_fld_lane_lock_lock_mask(p);
+
+	return ((block_lock != 0) && (block_lock != block_lock_mask)) ||
+		((lane_lock != 0) && (lane_lock != lane_lock_mask));
+}
+
+void nthw_mac_pcs_set_fec(nthw_mac_pcs_t *p, bool enable)
+{
+	NT_LOG(DBG, NTHW, "Port %u: Set FEC: %u\n", p->m_port_no, enable);
+
+	nthw_field_get_updated(p->mp_field_fec_ctrl_reg_rs_fec_ctrl_in);
+
+	if (enable)
+		nthw_field_set_val_flush32(p->mp_field_fec_ctrl_reg_rs_fec_ctrl_in, 0);
+
+	else
+		nthw_field_set_val_flush32(p->mp_field_fec_ctrl_reg_rs_fec_ctrl_in, (1 << 5) - 1);
+
+	/* Both Rx and Tx must be reset for new FEC state to become active */
+	nthw_mac_pcs_rx_path_rst(p, true);
+	nthw_mac_pcs_tx_path_rst(p, true);
+	nt_os_wait_usec(10000);	/* 10ms */
+
+	nthw_mac_pcs_rx_path_rst(p, false);
+	nthw_mac_pcs_tx_path_rst(p, false);
+	nt_os_wait_usec(10000);	/* 10ms */
+
+#ifdef DEBUG
+
+	if (enable) {
+		NT_LOG(DBG, NTHW, "Port %u: FEC valid: %u\n", p->m_port_no,
+			nthw_field_get_updated(p->mp_field_fec_stat_valid));
+
+	} else {
+		NT_LOG(DBG, NTHW, "Port %u: FEC bypass: %u\n", p->m_port_no,
+			nthw_field_get_updated(p->mp_field_fec_stat_bypass));
+	}
+
+#endif
+}
+
+bool nthw_mac_pcs_get_fec_bypass(nthw_mac_pcs_t *p)
+{
+	return nthw_field_get_updated(p->mp_field_fec_stat_bypass);
+}
+
+bool nthw_mac_pcs_get_fec_valid(nthw_mac_pcs_t *p)
+{
+	return nthw_field_get_updated(p->mp_field_fec_stat_valid);
+}
+
+bool nthw_mac_pcs_get_fec_stat_all_am_locked(nthw_mac_pcs_t *p)
+{
+	nthw_register_update(p->mp_reg_fec_stat);
+
+	if ((nthw_field_get_val32(p->mp_field_fec_stat_am_lock0)) &&
+		(nthw_field_get_val32(p->mp_field_fec_stat_am_lock1)) &&
+		(nthw_field_get_val32(p->mp_field_fec_stat_am_lock2)) &&
+		(nthw_field_get_val32(p->mp_field_fec_stat_am_lock3))) {
+		return true;
+	}
+
+	return false;
+}
+
+void nthw_mac_pcs_reset_fec_counters(nthw_mac_pcs_t *p)
+{
+	nthw_register_update(p->mp_reg_fec_cw_cnt);
+	nthw_register_update(p->mp_reg_fec_ucw_cnt);
+
+	if (nthw_field_get_val32(p->mp_field_fec_cw_cnt_cw_cnt)) {
+		NT_LOG(DBG, NTHW, "Port %u: FEC_CW_CNT: %u\n", p->m_port_no,
+			nthw_field_get_val32(p->mp_field_fec_cw_cnt_cw_cnt));
+	}
+
+	if (nthw_field_get_val32(p->mp_field_fec_ucw_cnt_ucw_cnt)) {
+		NT_LOG(DBG, NTHW, "Port %u: FEC_UCW_CNT: %u\n", p->m_port_no,
+			nthw_field_get_val32(p->mp_field_fec_ucw_cnt_ucw_cnt));
+	}
+}
+
+void nthw_mac_pcs_set_gty_tx_tuning(nthw_mac_pcs_t *p, uint8_t lane, uint8_t tx_pre_csr,
+	uint8_t tx_diff_ctl, uint8_t tx_post_csr)
+{
+	/* GTY_PRE_CURSOR */
+	nthw_register_update(p->mp_reg_gty_pre_cursor);
+
+	switch (lane) {
+	case 0:
+		nthw_field_set_val_flush32(p->mp_field_gty_pre_cursor_tx_pre_csr0,
+			tx_pre_csr & 0x1F);
+		break;
+
+	case 1:
+		nthw_field_set_val_flush32(p->mp_field_gty_pre_cursor_tx_pre_csr1,
+			tx_pre_csr & 0x1F);
+		break;
+
+	case 2:
+		nthw_field_set_val_flush32(p->mp_field_gty_pre_cursor_tx_pre_csr2,
+			tx_pre_csr & 0x1F);
+		break;
+
+	case 3:
+		nthw_field_set_val_flush32(p->mp_field_gty_pre_cursor_tx_pre_csr3,
+			tx_pre_csr & 0x1F);
+		break;
+	}
+
+	/* GTY_DIFF_CTL */
+	nthw_register_update(p->mp_reg_gty_diff_ctl);
+
+	switch (lane) {
+	case 0:
+		nthw_field_set_val_flush32(p->mp_field_gty_gty_diff_ctl_tx_diff_ctl0,
+			tx_diff_ctl & 0x1F);
+		break;
+
+	case 1:
+		nthw_field_set_val_flush32(p->mp_field_gty_gty_diff_ctl_tx_diff_ctl1,
+			tx_diff_ctl & 0x1F);
+		break;
+
+	case 2:
+		nthw_field_set_val_flush32(p->mp_field_gty_gty_diff_ctl_tx_diff_ctl2,
+			tx_diff_ctl & 0x1F);
+		break;
+
+	case 3:
+		nthw_field_set_val_flush32(p->mp_field_gty_gty_diff_ctl_tx_diff_ctl3,
+			tx_diff_ctl & 0x1F);
+		break;
+	}
+
+	/* GTY_POST_CURSOR */
+	nthw_register_update(p->mp_reg_gty_post_cursor);
+
+	switch (lane) {
+	case 0:
+		nthw_field_set_val_flush32(p->mp_field_gty_post_cursor_tx_post_csr0,
+			tx_post_csr & 0x1F);
+		break;
+
+	case 1:
+		nthw_field_set_val_flush32(p->mp_field_gty_post_cursor_tx_post_csr1,
+			tx_post_csr & 0x1F);
+		break;
+
+	case 2:
+		nthw_field_set_val_flush32(p->mp_field_gty_post_cursor_tx_post_csr2,
+			tx_post_csr & 0x1F);
+		break;
+
+	case 3:
+		nthw_field_set_val_flush32(p->mp_field_gty_post_cursor_tx_post_csr3,
+			tx_post_csr & 0x1F);
+		break;
+	}
+
+	NT_LOG(DBG, NTHW,
+		"Port %u, lane %u: GTY tx_pre_csr: %d, tx_diff_ctl: %d, tx_post_csr: %d\n",
+		p->m_port_no, lane, tx_pre_csr, tx_diff_ctl, tx_post_csr);
+}
+
+/*
+ * Set receiver equalization mode
+ *  0: enable DFE
+ *  1: enable LPM
+ *
+ * See UltraScale Architecture GTY Transceivers www.xilinx.com page 181,
+ * UG578 (v1.1) November 24, 2015
+ */
+void nthw_mac_pcs_set_receiver_equalization_mode(nthw_mac_pcs_t *p, uint8_t mode)
+{
+	nthw_register_update(p->mp_reg_gty_ctl);
+	nthw_field_set_val32(p->mp_field_gty_ctl_rx_lpm_en0, mode & 0x1);
+	nthw_field_set_val32(p->mp_field_gty_ctl_rx_lpm_en1, mode & 0x1);
+	nthw_field_set_val32(p->mp_field_gty_ctl_rx_lpm_en2, mode & 0x1);
+	nthw_field_set_val_flush32(p->mp_field_gty_ctl_rx_lpm_en3, mode & 0x1);
+
+	/* Toggle reset */
+	nthw_field_set_val32(p->mp_field_gty_ctl_rx_equa_rst0, 1);
+	nthw_field_set_val32(p->mp_field_gty_ctl_rx_equa_rst1, 1);
+	nthw_field_set_val32(p->mp_field_gty_ctl_rx_equa_rst2, 1);
+	nthw_field_set_val_flush32(p->mp_field_gty_ctl_rx_equa_rst3, 1);
+
+	nt_os_wait_usec(1000);	/* 1ms */
+
+	nthw_field_set_val32(p->mp_field_gty_ctl_rx_equa_rst0, 0);
+	nthw_field_set_val32(p->mp_field_gty_ctl_rx_equa_rst1, 0);
+	nthw_field_set_val32(p->mp_field_gty_ctl_rx_equa_rst2, 0);
+	nthw_field_set_val_flush32(p->mp_field_gty_ctl_rx_equa_rst3, 0);
+
+	NT_LOG(DBG, NTHW, "Port %u: GTY receiver mode: %s\n", p->m_port_no,
+		(mode == c_mac_pcs_receiver_mode_dfe ? "DFE" : "LPM"));
+}
+
+void nthw_mac_pcs_swap_gty_tx_polarity(nthw_mac_pcs_t *p, uint8_t lane, bool swap)
+{
+	nthw_register_update(p->mp_reg_gty_ctl);
+
+	switch (lane) {
+	case 0:
+		nthw_field_set_val_flush32(p->mp_field_gty_ctl_tx_pol0, swap);
+		break;
+
+	case 1:
+		nthw_field_set_val_flush32(p->mp_field_gty_ctl_tx_pol1, swap);
+		break;
+
+	case 2:
+		nthw_field_set_val_flush32(p->mp_field_gty_ctl_tx_pol2, swap);
+		break;
+
+	case 3:
+		nthw_field_set_val_flush32(p->mp_field_gty_ctl_tx_pol3, swap);
+		break;
+	}
+
+	NT_LOG(DBG, NTHW, "Port %u: set GTY Tx lane (%d) polarity: %d\n", p->m_port_no, lane,
+		swap);
+}
+
+void nthw_mac_pcs_swap_gty_rx_polarity(nthw_mac_pcs_t *p, uint8_t lane, bool swap)
+{
+	nthw_register_update(p->mp_reg_gty_ctl);
+
+	switch (lane) {
+	case 0:
+		nthw_field_set_val_flush32(p->mp_field_gty_ctl_rx_pol0, swap);
+		break;
+
+	case 1:
+		nthw_field_set_val_flush32(p->mp_field_gty_ctl_rx_pol1, swap);
+		break;
+
+	case 2:
+		nthw_field_set_val_flush32(p->mp_field_gty_ctl_rx_pol2, swap);
+		break;
+
+	case 3:
+		nthw_field_set_val_flush32(p->mp_field_gty_ctl_rx_pol3, swap);
+		break;
+	}
+
+	NT_LOG(DBG, NTHW, "Port %u: set GTY Rx lane (%d) polarity: %d\n", p->m_port_no, lane,
+		swap);
+}
+
+void nthw_mac_pcs_set_led_mode(nthw_mac_pcs_t *p, uint8_t mode)
+{
+	nthw_field_get_updated(p->mp_field_debounce_ctrl_nt_port_ctrl);
+	nthw_field_set_val_flush32(p->mp_field_debounce_ctrl_nt_port_ctrl, mode);
+}
+
+void nthw_mac_pcs_set_timestamp_comp_rx(nthw_mac_pcs_t *p, uint16_t rx_dly)
+{
+	if (p->mp_field_time_stamp_comp_rx_dly) {
+		nthw_field_get_updated(p->mp_field_time_stamp_comp_rx_dly);
+		nthw_field_set_val_flush32(p->mp_field_time_stamp_comp_rx_dly, rx_dly);
+	}
+}
+
+void nthw_mac_pcs_set_port_no(nthw_mac_pcs_t *p, uint8_t port_no)
+{
+	p->m_port_no = port_no;
+}
+
+uint32_t nthw_mac_pcs_get_fld_block_lock_lock(nthw_mac_pcs_t *p)
+{
+	return nthw_field_get_updated(p->mp_fld_block_lock_lock);
+}
+
+uint32_t nthw_mac_pcs_get_fld_block_lock_lock_mask(nthw_mac_pcs_t *p)
+{
+	return p->m_fld_block_lock_lock_mask;
+}
+
+uint32_t nthw_mac_pcs_get_fld_lane_lock_lock(nthw_mac_pcs_t *p)
+{
+	return nthw_field_get_updated(p->mp_fld_vl_demuxed_lock);
+}
+
+uint32_t nthw_mac_pcs_get_fld_lane_lock_lock_mask(nthw_mac_pcs_t *p)
+{
+	return p->m_fld_vl_demuxed_lock_mask;
+}
-- 
2.44.0