mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Doug Ledford <doug.ledford@hpe.com>
To: "Slingshot Open Source Team"
	<slingshot-os-team@groups.ext.hpe.com>,
	"Nathan Chancellor" <nathan@kernel.org>,
	"Nicolas Schier" <nsc@kernel.org>,
	"Richard Cochran" <richardcochran@gmail.com>,
	"Andrew Lunn" <andrew+netdev@lunn.ch>,
	"David S. Miller" <davem@davemloft.net>,
	"Eric Dumazet" <edumazet@kernel.org>,
	"Jakub Kicinski" <kuba@kernel.org>,
	"Paolo Abeni" <pabeni@redhat.com>,
	"Sumit Semwal" <sumit.semwal@linaro.org>,
	"Christian König" <christian.koenig@amd.com>,
	"Guenter Roeck" <linux@roeck-us.net>
Cc: <linux-kernel@vger.kernel.org>, <netdev@vger.kernel.org>,
	<linux-kbuild@vger.kernel.org>,
	Doug Ledford <doug.ledford@hpe.com>,
	"Sandeep Bhoot" <sandeep.bhoot@hpe.com>,
	Mamila Santosh <santosh-kumar.mamila@hpe.com>
Subject: [PATCH 05/23] net: hpe: sbl: add the PML link layer
Date: Fri, 30 Oct 2026 13:59:01 -0700	[thread overview]
Message-ID: <20261030-cxi-netdev-submission-v1-5-8c2c391cc283@hpe.com> (raw)
In-Reply-To: <20261030-cxi-netdev-submission-v1-0-8c2c391cc283@hpe.com>

Add the PML (physical media layer) code: MAC, PCS and LLR (link level
retry) configuration and the SerDes operation hooks.  The *_nic files
hold the parts that only apply to the NIC; the base files are shared
with the switch variant of this block.

checkpatch --strict (100 columns) still reports the following in this
patch. They need coordination with the internal repositories this code
is maintained in and will be fixed in V2:
- WARNING: LONG_LINE (33), LONG_LINE_COMMENT (7), TYPO_SPELLING (2)
- CHECK: BRACES (20), PARENTHESIS_ALIGNMENT (8), UNNECESSARY_PARENTHESES
  (7), OPEN_ENDED_LINE (2)

Co-developed-by: Sandeep Bhoot <sandeep.bhoot@hpe.com>
Signed-off-by: Sandeep Bhoot <sandeep.bhoot@hpe.com>
Co-developed-by: Mamila Santosh <santosh-kumar.mamila@hpe.com>
Signed-off-by: Mamila Santosh <santosh-kumar.mamila@hpe.com>
Signed-off-by: Doug Ledford <doug.ledford@hpe.com>
---
 drivers/net/ethernet/hpe/sbl/sbl_pml.c           |  787 ++++++++++++++++
 drivers/net/ethernet/hpe/sbl/sbl_pml_llr.c       |  736 +++++++++++++++
 drivers/net/ethernet/hpe/sbl/sbl_pml_llr_nic.c   |   34 +
 drivers/net/ethernet/hpe/sbl/sbl_pml_mac.c       |  220 +++++
 drivers/net/ethernet/hpe/sbl/sbl_pml_pcs.c       | 1085 ++++++++++++++++++++++
 drivers/net/ethernet/hpe/sbl/sbl_pml_pcs_nic.c   |   53 ++
 drivers/net/ethernet/hpe/sbl/sbl_pml_serdes_op.c |  145 +++
 7 files changed, 3060 insertions(+)

diff --git a/drivers/net/ethernet/hpe/sbl/sbl_pml.c b/drivers/net/ethernet/hpe/sbl/sbl_pml.c
new file mode 100644
index 000000000000..367c3184fb79
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_pml.c
@@ -0,0 +1,787 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2019-2023 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/jiffies.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+
+#include <linux/hpe/sbl/sbl_kconfig.h>
+#include <linux/hpe/sbl/sbl.h>
+
+#include <linux/hpe/sbl/sbl_pml.h>
+
+#include "sbl_pml_fn.h"
+#include "sbl_link.h"
+#include "sbl_internal.h"
+#include "sbl_serdes_fn.h"
+
+/* Bring-up PML block of a link */
+int sbl_pml_start(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	int err;
+
+	sbl_dev_dbg(sbl->dev, "%d: pml bring-up starting", port_num);
+
+	/* serdes must be up */
+	if (link->sstate != SBL_SERDES_STATUS_RUNNING) {
+		sbl_dev_err(sbl->dev, "%d: pml serdes not running", port_num);
+		err = -ENOLINK;
+		goto out_err;
+	}
+
+	/* pcs must be sending am so link partner can tune */
+	if (!(READ_ONCE(link->link_info) & SBL_LINK_INFO_PCS_TX_AM)) {
+		sbl_dev_err(sbl->dev, "%d: pml pcs not tx am", port_num);
+		err = -ENODATA;
+		goto out_err;
+	}
+
+	/* make sure we have time left */
+	 sbl_start_timeout_ensure_remaining(sbl, port_num,
+					    SBL_PML_MIN_START_TIME);
+
+	/* make sure am locking is enabled */
+	if (!(READ_ONCE(link->link_info) & SBL_LINK_INFO_PCS_ALIGN_EN))
+		sbl_pml_pcs_enable_alignment(sbl, port_num);
+
+	err = sbl_pml_mac_config(sbl, port_num);
+	if (err) {
+		sbl_dev_err(sbl->dev, "%d: pml mac config failed [%d]", port_num, err);
+		goto out_err;
+	}
+
+	sbl_pml_llr_config(sbl, port_num);
+
+	/* keep trying to get the PML block up until
+	 * we succeed, timeout or have a fatal error
+	 */
+	while (true) {
+
+		/* wait for the pcs to come up  */
+		err = sbl_pml_pcs_wait(sbl, port_num);
+		switch (err) {
+		case 0:
+			/* good - carry on with mac */
+			break;
+		case -ECANCELED:
+			sbl_dev_dbg(sbl->dev, "%d: pml pcs wait cancelled", port_num);
+			goto out_err;
+		case -ETIMEDOUT:
+			sbl_dev_dbg(sbl->dev, "%d: pml pcs wait timeout", port_num);
+			goto out_err_invalidate;
+		default:
+			sbl_dev_dbg(sbl->dev, "%d: pml pcs wait failed [%d]", port_num, err);
+			goto out_err_invalidate;
+		}
+
+		sbl_pml_pcs_enable_auto_lane_degrade(sbl, port_num);
+
+		/* start mac */
+		sbl_pml_mac_start(sbl, port_num);
+
+		/* start llr */
+		err = sbl_pml_llr_start(sbl, port_num);
+		switch (err) {
+		case 0:
+			/* all good */
+			goto out_done;
+		case -ECANCELED:
+			sbl_dev_dbg(sbl->dev, "%d: pml llr start cancelled", port_num);
+			goto out_err;
+		case -ETIMEDOUT:
+			sbl_dev_dbg(sbl->dev, "%d: pml llr start timeout", port_num);
+			goto out_err;
+		default:
+			sbl_dev_dbg(sbl->dev, "%d: pml llr start failed [%d]", port_num, err);
+			goto out_err;
+		}
+	};
+
+out_done:
+	/* clear any PML errors which might have been set during startup */
+	sbl_pml_err_flgs_clear_all(sbl, port_num);
+
+	/* pml block is up */
+	sbl_dev_dbg(sbl->dev, "%d: pml bring-up done (up)", port_num);
+	return 0;
+
+out_err_invalidate:
+	if (link->dfe_tune_count == SBL_DFE_USED_SAVED_PARAMS) {
+		/* either the link partner is not starting or the saved params could be bad -
+		 * we cannot (currently) determine which so destroy the saved params
+		 * just in case
+		 */
+		sbl_dev_dbg(sbl->dev, "%d: pml bring-up - invalidating tuning params", port_num);
+		sbl_serdes_invalidate_tuning_params(sbl, port_num);
+	}
+out_err:
+	sbl_pml_mac_stop(sbl, port_num);
+	if (!sbl_debug_option(sbl, port_num, SBL_DEBUG_INHIBIT_CLEANUP))
+		sbl_pml_pcs_disable_alignment(sbl, port_num);
+	sbl_dev_dbg(sbl->dev, "%d: pml bring-up failed [%d]", port_num, err);
+	return err;
+}
+
+/* Take down the PML block of a link */
+int sbl_pml_link_down(struct sbl_inst *sbl, int port_num)
+{
+	sbl_dev_dbg(sbl->dev, "%d: pml bring-down starting\n", port_num);
+
+	sbl_pml_recovery_cancel(sbl, port_num);
+
+	/* stop everything in order */
+	sbl_pml_llr_stop(sbl, port_num);
+	sbl_pml_mac_stop(sbl, port_num);
+	sbl_pml_pcs_stop(sbl, port_num);
+
+	/* clear pml errors */
+	sbl_pml_err_flgs_clear_all(sbl, port_num);
+
+	sbl_dev_dbg(sbl->dev, "%d: pml bring-down done (down)\n", port_num);
+	return 0;
+}
+
+/* function to reset the major PML cfg regs to their default values
+ *
+ * It would seem that the default for the llr down behaviour is
+ * SBL_LLR_LINK_DOWN_BLOCK this should be SBL_LLR_LINK_DOWN_DISCARD;
+ */
+void sbl_pml_set_defaults(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+	u64 llr_link_down_behavior;
+
+	/* pcs */
+	if (link->sstate != SBL_SERDES_STATUS_DOWN) {
+		/* default is to not tx am.
+		 * we should not do this because it can break optical headshells
+		 */
+		sbl_dev_warn(sbl->dev, "%d: pcs reset while serdes is running\n", port_num);
+	}
+
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_OFFSET,
+		    SBL_PML_CFG_PCS_DFLT);
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET,
+		    SBL_PML_CFG_PCS_AUTONEG_DFLT);
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_TIMERS_OFFSET,
+		    SBL_PML_CFG_PCS_AUTONEG_TIMERS_DFLT);
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_BASE_PAGE_OFFSET,
+		    SBL_PML_CFG_PCS_AUTONEG_BASE_PAGE_DFLT);
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_OFFSET,
+		    SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_DFLT);
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_AMS_OFFSET,
+		    SBL_PML_CFG_PCS_AMS_DFLT);
+	sbl_write64(sbl, base | SBL_PML_CFG_TX_PCS_OFFSET,
+		    SBL_PML_CFG_TX_PCS_DFLT);
+	sbl_write64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET,
+		    SBL_PML_CFG_RX_PCS_DFLT);
+
+	sbl_write64(sbl, base | SBL_PML_DBG_PCS_OFFSET, SBL_PML_DBG_PCS_DFLT);
+
+	/* pcs: we need special AMs for 200GHz mode */
+	sbl_write64(sbl, base | SBL_PML_CFG_RX_PCS_AMS_OFFSET,
+		    SBL_PML_CFG_RX_PCS_AMS_SET(SBL_PCS_200_UM_MATCH_MSK,
+					       SBL_PCS_200_CM_MATCH_MSK));
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_CM_OFFSET,
+		    SBL_PCS_200_CM);
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_UM_OFFSET(0), SBL_PCS_200_UM0);
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_UM_OFFSET(1), SBL_PCS_200_UM1);
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_UM_OFFSET(2), SBL_PCS_200_UM2);
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_UM_OFFSET(3), SBL_PCS_200_UM3);
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_UM_OFFSET(4), SBL_PCS_200_UM4);
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_UM_OFFSET(5), SBL_PCS_200_UM5);
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_UM_OFFSET(6), SBL_PCS_200_UM6);
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_UM_OFFSET(7), SBL_PCS_200_UM7);
+
+	/* assert we have destroyed any previous pcs config */
+	link->pcs_config = false;
+
+	/* mac */
+	sbl_write64(sbl, base | SBL_PML_CFG_TX_MAC_OFFSET,
+		    SBL_PML_CFG_TX_MAC_DFLT);
+	sbl_write64(sbl, base | SBL_PML_CFG_RX_MAC_OFFSET,
+		    SBL_PML_CFG_RX_MAC_DFLT);
+
+	/* llr */
+	val64 = SBL_PML_CFG_LLR_DFLT;
+	llr_link_down_behavior = sbl_pml_llr_link_down_behaviour(sbl, port_num);
+	val64 = SBL_PML_CFG_LLR_LINK_DOWN_BEHAVIOR_UPDATE(val64,
+							  llr_link_down_behavior);
+	sbl_write64(sbl, base | SBL_PML_CFG_LLR_OFFSET, val64);
+	sbl_write64(sbl, base | SBL_PML_CFG_LLR_SM_OFFSET,
+		    SBL_PML_CFG_LLR_SM_DFLT);
+
+	/* read to flush everything */
+	sbl_read64(sbl, base | SBL_PML_CFG_LLR_OFFSET);
+}
+
+/* interrupt support
+ *
+ * we only have one handler
+ */
+int sbl_pml_install_intr_handler(struct sbl_inst *sbl, int port_num, u64 err_flags)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	if (link->intr_err_flgs) {
+		sbl_dev_err(sbl->dev, "intr %d: handler already registered\n", port_num);
+		return -EALREADY;
+	}
+	link->intr_err_flgs = err_flags;
+
+	return (*sbl->ops.sbl_pml_install_intr_handler)(sbl->accessor, port_num, err_flags);
+}
+
+int sbl_pml_enable_intr_handler(struct sbl_inst *sbl, int port_num, u64 err_flags)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	if (!link->intr_err_flgs) {
+		sbl_dev_warn(sbl->dev, "intr %d: no handler registered for enable\n", port_num);
+		return 0;
+	}
+
+	if ((err_flags & ~link->intr_err_flgs) || !(err_flags & link->intr_err_flgs)) {
+		sbl_dev_err(sbl->dev, "intr %d: cannot enable flags 0x%llx, 0x%llx\n", port_num,
+			    err_flags, link->intr_err_flgs);
+		return -EINVAL;
+	}
+
+	return (*sbl->ops.sbl_pml_enable_intr_handler)(sbl->accessor, port_num, err_flags);
+}
+
+int sbl_pml_disable_intr_handler(struct sbl_inst *sbl, int port_num, u64 err_flags)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	if (!link->intr_err_flgs) {
+		sbl_dev_warn(sbl->dev, "intr %d: no handler registered for disable\n", port_num);
+		return 0;
+	}
+
+	if ((err_flags & ~link->intr_err_flgs) || !(err_flags & link->intr_err_flgs)) {
+		sbl_dev_err(sbl->dev, "intr %d: cannot disable flags 0x%llx, 0x%llx\n", port_num,
+			    err_flags, link->intr_err_flgs);
+		return -EINVAL;
+	}
+
+	return (*sbl->ops.sbl_pml_disable_intr_handler)(sbl->accessor, port_num, err_flags);
+}
+
+int sbl_pml_remove_intr_handler(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	int err;
+
+	if (!link->intr_err_flgs) {
+		sbl_dev_warn(sbl->dev, "intr %d: no handler registered to remove\n", port_num);
+		return 0;
+	}
+
+	err =  (*sbl->ops.sbl_pml_remove_intr_handler)(sbl->accessor, port_num,
+			link->intr_err_flgs);
+	if (!err)
+		link->intr_err_flgs = 0;
+
+	return err;
+}
+
+/* Error flags
+ *
+ * TODO: inline?
+ */
+bool sbl_pml_err_flgs_test(struct sbl_inst *sbl, int port_num, u64 err_flgs)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	val64 = sbl_read64(sbl, base | SBL_PML_ERR_FLG_OFFSET);
+
+	return val64 & err_flgs;
+}
+
+void sbl_pml_err_flgs_clear(struct sbl_inst *sbl, int port_num, u64 err_flgs)
+{
+	u32 base = SBL_PML_BASE(port_num);
+
+	sbl_write64(sbl, base | SBL_PML_ERR_CLR_OFFSET, err_flgs);
+}
+
+void sbl_pml_err_flgs_clear_all(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 err_flgs;
+
+	err_flgs = sbl_read64(sbl, base | SBL_PML_ERR_FLG_OFFSET);
+	sbl_write64(sbl, base | SBL_PML_ERR_CLR_OFFSET, err_flgs);
+}
+
+static bool sbl_pml_recovery_ignore_down_origin_fault(enum sbl_link_down_origin down_origin)
+{
+	switch (down_origin) {
+	case SBL_LINK_DOWN_ORIGIN_LINK_DOWN:
+	case SBL_LINK_DOWN_ORIGIN_LOCAL_FAULT:
+	case SBL_LINK_DOWN_ORIGIN_REMOTE_FAULT:
+	case SBL_LINK_DOWN_ORIGIN_ALIGN:
+	case SBL_LINK_DOWN_ORIGIN_HISER:
+	case SBL_LINK_DOWN_ORIGIN_LLR_MAX:
+		return true;
+	default:
+		return false;
+	}
+}
+
+void sbl_pml_link_down_async_alert(struct sbl_inst *sbl, int port_num,
+				   enum sbl_link_down_origin down_origin)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+	u64 err_flags;
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_LLR_SM_OFFSET);
+	if (SBL_PML_CFG_LLR_SM_REPLAY_CT_MAX_GET(val64) < SBL_LLR_REPLAY_CT_MAX_UNLIMITED)
+		err_flags = SBL_PML_FAULT_ERR_FLAGS;
+	else
+		err_flags = SBL_PML_REC_FAULT_ERR_FLAGS;
+
+	/* going down or in recovery state, so don't need more intrs */
+	sbl_pml_disable_intr_handler(sbl, port_num, err_flags);
+
+	if (sbl_debug_option(sbl, port_num, SBL_DEBUG_INHIBIT_CLEANUP)) {
+		/* set state to error and signal no cleanup with the error number */
+		link->blstate = SBL_BASE_LINK_STATUS_ERROR;
+		link->blerr = -ECONNABORTED;
+	}
+
+	sbl_async_alert(sbl, port_num, SBL_ASYNC_ALERT_LINK_DOWN,
+			(void *)(uintptr_t)down_origin, 0);
+}
+
+static void sbl_pml_recovery_origin_counter_update(struct sbl_inst *sbl, int port_num,
+						   enum sbl_link_down_origin origin)
+{
+	switch (origin) {
+	case SBL_LINK_DOWN_ORIGIN_LINK_DOWN:
+		sbl_link_counters_incr(sbl, port_num, pml_recovery_origin_bl_ldown);
+		break;
+	case SBL_LINK_DOWN_ORIGIN_LOCAL_FAULT:
+		sbl_link_counters_incr(sbl, port_num, pml_recovery_origin_bl_lfault);
+		break;
+	case SBL_LINK_DOWN_ORIGIN_REMOTE_FAULT:
+		sbl_link_counters_incr(sbl, port_num, pml_recovery_origin_bl_rfault);
+		break;
+	case SBL_LINK_DOWN_ORIGIN_ALIGN:
+		sbl_link_counters_incr(sbl, port_num, pml_recovery_origin_bl_align);
+		break;
+	case SBL_LINK_DOWN_ORIGIN_HISER:
+		sbl_link_counters_incr(sbl, port_num, pml_recovery_origin_bl_hiser);
+		break;
+	case SBL_LINK_DOWN_ORIGIN_LLR_MAX:
+		sbl_link_counters_incr(sbl, port_num, pml_recovery_origin_bl_llr);
+		break;
+	default:
+		sbl_dev_warn(sbl->dev, "%d: pml recovery origin (%d) with no counter",
+			     port_num, origin);
+		break;
+	}
+}
+
+/* PML recovery is limited by the combined amount of time spent in one or more
+ * recovery attempts over a window, rather than by a count. This approximates
+ * bandwidth loss. For example, 60 ms per second in PML recovery corresponds
+ * to a roughly %6 loss of bandwidth.
+ *
+ * After each successful recovery, the duration is subtracted from the
+ * remaining time budgeted for the window. The rate test fails if the remaining
+ * time is insufficient for another attempt.
+ */
+static bool sbl_pml_recovery_rate_test(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u64 window_end = link->pml_recovery.rl_window_start + msecs_to_jiffies(link->blattr.pml_recovery.rl_window_size);
+
+	/* reset if not started or window has elapsed */
+	if (!link->pml_recovery.rl_window_start ||
+	    link->pml_recovery.init_jiffies > window_end) {
+		link->pml_recovery.rl_window_start = link->pml_recovery.init_jiffies;
+		link->pml_recovery.rl_time_remaining = link->blattr.pml_recovery.rl_max_duration;
+		return true;
+	}
+
+	if (link->pml_recovery.rl_time_remaining < SBL_PML_REC_POLL_INTERVAL)
+		return false;
+
+	return true;
+}
+
+static void sbl_pml_recovery_monitor_fallback_timer(struct timer_list *t)
+{
+	struct sbl_pml_recovery *pml_recovery = container_of(t, struct sbl_pml_recovery, timer);
+	struct sbl_inst *sbl;
+	struct sbl_link *link;
+	int port_num;
+	enum sbl_link_down_origin down_origin;
+	u32 elapsed;
+	u32 rl_total_time;
+	unsigned long timeout;
+	unsigned long irq_flags;
+
+	if (!pml_recovery || !pml_recovery->sbl)
+		return;
+
+	spin_lock_irqsave(&pml_recovery->lock, irq_flags);
+	if (!pml_recovery->enabled || !pml_recovery->started) {
+		spin_unlock_irqrestore(&pml_recovery->lock, irq_flags);
+		return;
+	}
+	spin_unlock_irqrestore(&pml_recovery->lock, irq_flags);
+
+	sbl = pml_recovery->sbl;
+	port_num = pml_recovery->port_num;
+	down_origin = pml_recovery->down_origin;
+	link = sbl->link + port_num;
+	timeout = pml_recovery->init_jiffies + msecs_to_jiffies(pml_recovery->timeout);
+	elapsed = jiffies_to_msecs(jiffies - link->pml_recovery.init_jiffies);
+	link->pml_recovery.rl_time_remaining -=
+		jiffies_to_msecs(jiffies -
+				 link->pml_recovery.last_poll_jiffies);
+	link->pml_recovery.last_poll_jiffies  = jiffies;
+
+	if (sbl_pml_recovery_no_faults(sbl, port_num)) {
+		sbl_dev_info(sbl->dev, "%d: PML recovered successfully in %ums", port_num, elapsed);
+		sbl_link_counters_incr(sbl, port_num, pml_recovery_successes);
+		sbl_pml_recovery_origin_counter_update(sbl, port_num, down_origin);
+		sbl_link_counters_incr(sbl, port_num, (elapsed < SBL_PML_REC_HISTOGRAM_MAX) ?
+				      (pml_recovery_histogram_0_9ms + elapsed / 10) :
+				       pml_recovery_histogram_high);
+		goto out;
+	} else if (time_is_before_eq_jiffies(timeout)) {
+		sbl_dev_info(sbl->dev, "%d: PML recovery monitor timed out (%ums)", port_num, elapsed);
+		goto out_fail;
+	} else if (!sbl_pml_recovery_rate_test(sbl, port_num)) {
+		rl_total_time = link->blattr.pml_recovery.rl_max_duration - link->pml_recovery.rl_time_remaining;
+		sbl_dev_err(sbl->dev, "%d: PML recovery rate exceeded (%ums/%ums) after %ums", port_num, rl_total_time,
+			    link->blattr.pml_recovery.rl_window_size, elapsed);
+		sbl_link_counters_incr(sbl, port_num, pml_recovery_rate_exceeded);
+		goto out_fail;
+	}
+
+	/* start timer for next poll */
+	spin_lock_irqsave(&pml_recovery->lock, irq_flags);
+	if (pml_recovery->enabled)
+		mod_timer(&pml_recovery->timer,
+			  jiffies + msecs_to_jiffies(SBL_PML_REC_POLL_INTERVAL));
+	spin_unlock_irqrestore(&pml_recovery->lock, irq_flags);
+	return;
+
+out_fail:
+	sbl_pml_link_down_async_alert(sbl, port_num, down_origin);
+
+out:
+	spin_lock_irqsave(&link->fec_discard_lock, irq_flags);
+	link->fec_discard_time = jiffies;
+	link->fec_discard_type = SBL_FEC_DISCARD_TYPE_PML_REC_END;
+	spin_unlock_irqrestore(&link->fec_discard_lock, irq_flags);
+	spin_lock_irqsave(&pml_recovery->lock, irq_flags);
+	pml_recovery->started = false;
+	spin_unlock_irqrestore(&pml_recovery->lock, irq_flags);
+}
+
+void sbl_pml_recovery_init(struct sbl_pml_recovery *recovery)
+{
+	spin_lock_init(&recovery->lock);
+	recovery->enabled = false;
+	timer_setup(&recovery->timer, sbl_pml_recovery_monitor_fallback_timer,
+		    TIMER_IRQSAFE);
+}
+
+/* Allow recovery once the fault monitor is about to deliver PML faults */
+void sbl_pml_recovery_enable(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_pml_recovery *recovery = &sbl->link[port_num].pml_recovery;
+	unsigned long irq_flags;
+
+	spin_lock_irqsave(&recovery->lock, irq_flags);
+	recovery->enabled = true;
+	spin_unlock_irqrestore(&recovery->lock, irq_flags);
+}
+
+static void sbl_pml_recovery_monitor(struct sbl_inst *sbl, int port_num,
+				     enum sbl_link_down_origin down_origin)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct sbl_pml_recovery *recovery = &link->pml_recovery;
+	u32 rl_total_time;
+	unsigned long irq_flags;
+
+	spin_lock_irqsave(&recovery->lock, irq_flags);
+	if (!recovery->enabled || recovery->started) {
+		spin_unlock_irqrestore(&recovery->lock, irq_flags);
+		return;
+	}
+
+	recovery->started = true;
+	recovery->init_jiffies = jiffies;
+	recovery->last_poll_jiffies = recovery->init_jiffies;
+	recovery->sbl = sbl;
+	recovery->port_num = port_num;
+	recovery->down_origin = down_origin;
+	recovery->timeout = link->blattr.pml_recovery.timeout;
+
+	if (!sbl_pml_recovery_rate_test(sbl, port_num)) {
+		rl_total_time = link->blattr.pml_recovery.rl_max_duration -
+				recovery->rl_time_remaining;
+		recovery->started = false;
+		spin_unlock_irqrestore(&recovery->lock, irq_flags);
+		sbl_dev_err(sbl->dev, "%d: PML recovery rate exceeded (%ums/%ums)", port_num,
+			    rl_total_time, link->blattr.pml_recovery.rl_window_size);
+		sbl_pml_link_down_async_alert(sbl, port_num, down_origin);
+		sbl_link_counters_incr(sbl, port_num, pml_recovery_rate_exceeded);
+		return;
+	}
+	sbl_link_counters_incr(sbl, port_num, pml_recovery_attempts);
+
+	spin_lock(&link->fec_discard_lock);
+	link->fec_discard_time = recovery->init_jiffies;
+	link->fec_discard_type = SBL_FEC_DISCARD_TYPE_PML_REC_START;
+	spin_unlock(&link->fec_discard_lock);
+
+	/* cancel cannot slip between restarting alignment and arming the poll */
+	sbl_pml_pcs_disable_alignment(sbl, port_num);
+	sbl_pml_pcs_enable_alignment(sbl, port_num);
+	mod_timer(&recovery->timer,
+		  jiffies + msecs_to_jiffies(SBL_PML_REC_POLL_INTERVAL));
+	spin_unlock_irqrestore(&recovery->lock, irq_flags);
+
+	sbl_dev_info(sbl->dev, "%d: PML recovery started - %s", port_num,
+		     sbl_down_origin_str(down_origin));
+}
+
+void sbl_pml_recovery_cancel(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct sbl_pml_recovery *recovery = &link->pml_recovery;
+	unsigned long irq_flags;
+	bool was_started;
+	u32 elapsed;
+
+	spin_lock_irqsave(&recovery->lock, irq_flags);
+	recovery->enabled = false;
+	spin_unlock_irqrestore(&recovery->lock, irq_flags);
+
+	/* with the gate closed neither the handler nor the timer can rearm */
+	timer_delete_sync(&recovery->timer);
+
+	spin_lock_irqsave(&recovery->lock, irq_flags);
+	was_started = recovery->started;
+	recovery->started = false;
+	spin_unlock_irqrestore(&recovery->lock, irq_flags);
+
+	if (!was_started)
+		return;
+
+	elapsed = jiffies_to_msecs(jiffies - recovery->init_jiffies);
+	spin_lock_irqsave(&link->fec_discard_lock, irq_flags);
+	link->fec_discard_time = jiffies;
+	link->fec_discard_type = SBL_FEC_DISCARD_TYPE_PML_REC_END;
+	spin_unlock_irqrestore(&link->fec_discard_lock, irq_flags);
+
+	sbl_dev_info(sbl->dev, "%d: PML recovery canceled (%ums)", port_num, elapsed);
+}
+
+/**
+ * sbl_pml_hdlr() - local interrupt handler called by surrounding framework
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @data: Value is NULL in all cases
+ *
+ * This function does the following operations: Reads PML error-flag
+ * registers, extracts PCS-lane degradation information for both TX
+ * and RX paths and issues warning alerts when failure conditions are
+ * met. If auto negotiation error flags are raised, the function disables
+ * further PML interrupts. It also logs critical error when all lanes
+ * are reported down, link faults. It also checks possible case of
+ * down origin and alert the clients.
+ *
+ * Context: Interrupt
+ *
+ * Return: 0 on success
+ */
+int sbl_pml_hdlr(struct sbl_inst *sbl, int port_num, void *data)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u32 base = SBL_PML_BASE(port_num);
+	u64 raised_flgs;
+	u64 sts_pcs_lane_degrade_reg;
+	enum sbl_link_down_origin down_origin = SBL_LINK_DOWN_ORIGIN_UNKNOWN;
+	char pcs_state_str[SBL_PCS_STATE_STR_LEN];
+	u64 val64;
+	struct lane_degrade degrade_data;
+	enum sbl_async_alert_type alert = SBL_ASYNC_ALERT_INVALID;
+	int result;
+	unsigned long irq_flags;
+
+	raised_flgs = sbl_read64(sbl, base | SBL_PML_ERR_FLG_OFFSET) & link->intr_err_flgs;
+
+	if (!raised_flgs)
+		return 0;
+
+	if (sbl_debug_option(sbl, port_num, SBL_DEBUG_TRACE_PML_INT)) {
+		sbl_dev_info(sbl->dev,
+			     "%d: pml hdlr (%lld %lld hs%lld mr%lld ld%lld) in 0x%llx",
+			port_num,
+			SBL_PML_ERR_FLG_AUTONEG_PAGE_RECEIVED_GET(raised_flgs),
+			SBL_PML_ERR_FLG_AUTONEG_COMPLETE_GET(raised_flgs),
+			SBL_PML_ERR_FLG_PCS_HI_SER_GET(raised_flgs),
+			SBL_PML_ERR_FLG_LLR_REPLAY_AT_MAX_GET(raised_flgs),
+			SBL_PML_ERR_FLG_PCS_LINK_DOWN_GET(raised_flgs),
+			link->intr_err_flgs);
+	}
+
+	sts_pcs_lane_degrade_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_LANE_DEGRADE_OFFSET);
+	degrade_data.tx = SBL_PML_STS_PCS_LANE_DEGRADE_LP_PLS_AVAILABLE_GET(sts_pcs_lane_degrade_reg);
+	degrade_data.rx = SBL_PML_STS_PCS_LANE_DEGRADE_RX_PLS_AVAILABLE_GET(sts_pcs_lane_degrade_reg);
+	if (SBL_PML_ERR_FLG_PCS_RX_DEGRADE_GET(raised_flgs) && degrade_data.tx && degrade_data.rx) {
+		spin_lock_irqsave(&link->fec_discard_lock, irq_flags);
+		link->fec_discard_time = jiffies;
+		link->fec_discard_type = SBL_FEC_DISCARD_TYPE_RX_DEGRADE;
+		spin_unlock_irqrestore(&link->fec_discard_lock, irq_flags);
+		sbl_dev_warn(sbl->dev, "%d: RX side Degraded -> TX Lanes Available: 0x%llx - RX Lanes Available: 0x%llx",
+			     port_num, degrade_data.tx, degrade_data.rx);
+		sbl_async_alert(sbl, port_num, SBL_ASYNC_ALERT_RX_DEGRADE,
+				&degrade_data, sizeof(degrade_data));
+	}
+
+	if (SBL_PML_ERR_FLG_PCS_TX_DEGRADE_GET(raised_flgs) && degrade_data.tx && degrade_data.rx) {
+		sbl_dev_warn(sbl->dev, "%d: TX side Degraded -> TX Lanes Available: 0x%llx - RX Lanes Available: 0x%llx",
+			     port_num, degrade_data.tx, degrade_data.rx);
+		sbl_async_alert(sbl, port_num, SBL_ASYNC_ALERT_TX_DEGRADE,
+				&degrade_data, sizeof(degrade_data));
+	}
+
+	result = SBL_PML_ERR_FLG_PCS_TX_DEGRADE_FAILURE_GET(raised_flgs);
+
+	if (result) {
+		alert = SBL_ASYNC_ALERT_TX_DEGRADE_FAILURE;
+		sbl_async_alert(sbl, port_num, alert, NULL, 0);
+	}
+
+	result = SBL_PML_ERR_FLG_PCS_RX_DEGRADE_FAILURE_GET(raised_flgs);
+
+	if (result) {
+		alert = SBL_ASYNC_ALERT_RX_DEGRADE_FAILURE;
+		sbl_async_alert(sbl, port_num, alert, NULL, 0);
+	}
+
+	if (alert != SBL_ASYNC_ALERT_INVALID) {
+		/* if Auto Lane Degrade is enabled, we will print this message regardless of whether
+		 * the link went down because of lane degrade failure or not
+		 */
+		sbl_dev_err(sbl->dev, "%d: pml hdlr - link going down - all lanes down [%d]", port_num, alert);
+		down_origin = SBL_LINK_DOWN_ORIGIN_DEGRADE_FAILURE;
+	}
+
+	/* autoneg err flags */
+	if (raised_flgs & SBL_AUTONEG_ERR_FLGS) {
+		sbl_pml_disable_intr_handler(sbl, port_num, SBL_AUTONEG_ERR_FLGS);
+		complete(&link->an_hw_change);
+	}
+
+	/* link faults */
+	if (SBL_PML_ERR_FLG_PCS_HI_SER_GET(raised_flgs)) {
+		if (sbl_debug_option(sbl, port_num, SBL_DEBUG_IGNORE_HISER)) {
+			sbl_dev_warn(sbl->dev, "%d: pml hdlr - hiser - ignored", port_num);
+		} else {
+			sbl_dev_dbg(sbl->dev, "%d: pml hdlr - hiser", port_num);
+			down_origin = SBL_LINK_DOWN_ORIGIN_HISER;
+		}
+	}
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_LLR_SM_OFFSET);
+	if (SBL_PML_ERR_FLG_LLR_REPLAY_AT_MAX_GET(raised_flgs) &&
+	    SBL_PML_CFG_LLR_SM_REPLAY_CT_MAX_GET(val64) < SBL_LLR_REPLAY_CT_MAX_UNLIMITED) {
+		sbl_dev_dbg(sbl->dev, "%d: pml hdlr - max llr replay", port_num);
+		down_origin = SBL_LINK_DOWN_ORIGIN_LLR_MAX;
+	}
+
+	if (SBL_PML_ERR_FLG_PCS_LINK_DOWN_GET(raised_flgs)) {
+		val64 = sbl_read64(sbl, base | SBL_PML_STS_RX_PCS_OFFSET);
+
+		if (sbl_debug_option(sbl, port_num, SBL_DEBUG_TRACE_PML_INT)) {
+			sbl_dev_info(sbl->dev, "%d: pml hdlr - link down (%s)", port_num,
+				     sbl_pml_pcs_state_str(sbl, port_num, pcs_state_str,
+							   SBL_PCS_STATE_STR_LEN));
+		}
+
+		if (!SBL_PML_STS_RX_PCS_ALIGN_STATUS_GET(val64)) {
+			if (sbl_debug_option(sbl, port_num, SBL_DEBUG_IGNORE_ALIGN)) {
+				sbl_dev_warn(sbl->dev, "%d: pml hdlr - align - ignored", port_num);
+			} else {
+				sbl_dev_dbg(sbl->dev, "%d: pml hdlr - align", port_num);
+				down_origin = SBL_LINK_DOWN_ORIGIN_ALIGN;
+			}
+		} else if (SBL_PML_STS_RX_PCS_LOCAL_FAULT_GET(val64)) {
+			sbl_dev_dbg(sbl->dev, "%d: pml hdlr - local fault", port_num);
+			down_origin = SBL_LINK_DOWN_ORIGIN_LOCAL_FAULT;
+		} else if (SBL_PML_STS_RX_PCS_FAULT_GET(val64)) {
+			if (link->blattr.options & SBL_DISABLE_PML_RECOVERY ||
+			    sbl_debug_option(sbl, port_num, SBL_DEBUG_REMOTE_FAULT_RECOVERY)) {
+				sbl_dev_dbg(sbl->dev, "%d: pml hdlr - remote fault", port_num);
+				down_origin = SBL_LINK_DOWN_ORIGIN_REMOTE_FAULT;
+			} else {
+				sbl_dev_dbg(sbl->dev, "%d: pml hdlr - remote fault - ignored", port_num);
+			}
+		} else {
+			sbl_dev_dbg(sbl->dev, "%d: pml hdlr - link down", port_num);
+			down_origin = SBL_LINK_DOWN_ORIGIN_LINK_DOWN;
+		}
+	}
+
+	if (down_origin != SBL_LINK_DOWN_ORIGIN_UNKNOWN) {
+		if (!(link->blattr.options & SBL_DISABLE_PML_RECOVERY) && !link->is_degraded) {
+			if (sbl_pml_recovery_ignore_down_origin_fault(down_origin)) {
+				sbl_dev_dbg(sbl->dev, "%d: PML recovery, pml hdlr fault(%d) is ignored", port_num, down_origin);
+				sbl_pml_recovery_monitor(sbl, port_num, down_origin);
+
+			} else
+				sbl_pml_link_down_async_alert(sbl, port_num, down_origin);
+		} else {
+			sbl_dev_info(sbl->dev, "%d: PML recovery is disabled", port_num);
+			sbl_pml_link_down_async_alert(sbl, port_num, down_origin);
+		}
+	}
+
+	sbl_write64(sbl, base | SBL_PML_ERR_CLR_OFFSET, raised_flgs);
+	sbl_read64(sbl, base | SBL_PML_ERR_CLR_OFFSET);  /* flush */
+
+	return 0;
+}
+EXPORT_SYMBOL(sbl_pml_hdlr);
+
+/**
+ * sbl_pml_recovery_log_link_down() - Print pcs status when link down
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Print PCS receive path status and eye heights signal when
+ * reporting link down reason
+ */
+void sbl_pml_recovery_log_link_down(struct sbl_inst *sbl, int port_num)
+{
+	if (!sbl_pml_recovery_no_faults(sbl, port_num)) {
+		sbl_pml_recovery_log_pcs_status(sbl, port_num);
+		sbl_log_port_eye_heights(sbl, port_num);
+	}
+}
+EXPORT_SYMBOL(sbl_pml_recovery_log_link_down);
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_pml_llr.c b/drivers/net/ethernet/hpe/sbl/sbl_pml_llr.c
new file mode 100644
index 000000000000..a190f6890b4e
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_pml_llr.c
@@ -0,0 +1,736 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2019-2025 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_an.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include <linux/hpe/sbl/sbl_pml.h>
+
+#include "sbl_pml_fn.h"
+#include "sbl_link.h"
+#include "sbl_internal.h"
+
+static int sbl_pml_llr_ready_wait(struct sbl_inst *sbl, int port_num)
+{
+	int err = -1;
+
+	sbl_dev_dbg(sbl->dev, "%d: LLR ready wait", port_num);
+
+	sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_LLR_WAIT);
+
+	while (sbl_pml_llr_get_state(sbl, port_num) != SBL_PML_LLR_STATE_ADVANCE) {
+
+		if (sbl_start_timeout(sbl, port_num)) {
+			sbl_dev_err(sbl->dev, "%d: LLR ready wait timeout", port_num);
+			err = -ETIMEDOUT;
+			goto out;
+		}
+
+		if (sbl_base_link_start_cancelled(sbl, port_num)) {
+			sbl_dev_err(sbl->dev, "%d: LLR ready wait cancelled", port_num);
+			err = -ECANCELED;
+			goto out;
+		}
+
+		/* should be relatively well synchronised by this point */
+		usleep_range(5000, 6000);
+	}
+
+	err = 0;
+
+out:
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_LLR_WAIT);
+	return err;
+}
+
+/* calculate the loop time
+ *
+ * try to make a number of measurements or timeout/cancelled
+ */
+static int sbl_pml_llr_measure_loop_time_ns(struct sbl_inst *sbl, int port_num,
+					    u64 *llr_loop_time)
+{
+	struct sbl_link *link;
+	u32              base;
+	u64              min_loop_time;
+	u64              max_loop_time;
+	int              x;
+	u64              val64;
+	u64              time64;
+	int              err;
+
+	sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_LLR_MEASURE);
+
+	link = sbl->link + port_num;
+	base = SBL_PML_BASE(port_num);
+
+	min_loop_time  = SBL_PML_LLR_MIN_LOOP_TIME;
+	max_loop_time  = SBL_PML_LLR_MAX_LOOP_TIME;
+	*llr_loop_time = SBL_PML_LLR_MAX_LOOP_TIME;
+	time64         = 0ULL;
+
+	sbl_dev_dbg(sbl->dev, "%d: LLR measure loop time (len = %s)(media = %s)",
+		    port_num, sbl_link_len_str(link->mattr.len),
+		    sbl_link_media_str(link->mattr.media));
+
+	/* calc loop time bounds */
+	if (link->mattr.len != SBL_LINK_LEN_INVALID) {
+		err = sbl_media_calc_loop_time(sbl, port_num, &min_loop_time);
+		if (err) {
+			sbl_dev_err(sbl->dev, "%d: LLR measure calc loop time failed [%d]", port_num, err);
+			goto out;
+		}
+		/* arbitrary range for max */
+		max_loop_time = min_loop_time + 100 /* ns */;
+	}
+	sbl_dev_dbg(sbl->dev, "%d: LLR measure loop time bounds: min = %lldns, max = %lldns",
+		    port_num, min_loop_time, max_loop_time);
+
+	/* measure loop time */
+	for (x = 0; x < SBL_PML_LLR_TIMING_LOOPS; ++x) {
+
+		if (sbl_start_timeout(sbl, port_num)) {
+			sbl_dev_err(sbl->dev, "%d: LLR measure loop time timeout", port_num);
+			err = -ETIMEDOUT;
+			goto out;
+		}
+
+		if (sbl_base_link_start_cancelled(sbl, port_num)) {
+			sbl_dev_err(sbl->dev, "%d: LLR measure loop time cancelled", port_num);
+			err = -ECANCELED;
+			goto out;
+		}
+
+		/* start a measurement */
+		sbl_write64(sbl, base | SBL_PML_STS_LLR_LOOP_TIME_OFFSET, 0ULL);
+		sbl_read64(sbl, base | SBL_PML_STS_LLR_LOOP_TIME_OFFSET);  /* flush */
+		/* give the measurement time to complete */
+		udelay(SBL_PML_LLR_TIMING_MEASURE_DELAY);
+		/* read the measurement */
+		val64 = sbl_read64(sbl, base | SBL_PML_STS_LLR_LOOP_TIME_OFFSET);
+
+		time64 = SBL_PML_STS_LLR_LOOP_TIME_LOOP_TIME_GET(val64); /* ns */
+		sbl_dev_dbg(sbl->dev, "%d: LLR measure time = %lldns (%d)", port_num, time64, x);
+
+		/* if we didn't get a valid time, then wait to retry */
+		if (!time64)
+			msleep(SBL_PML_LLR_TIMING_RETRY_DELAY);
+
+		/* if we did get a valid time, then check it here */
+		else if ((time64 >= min_loop_time) && (time64 <= max_loop_time)) {
+			/* save the fastest time here */
+			if (time64 < *llr_loop_time)
+				*llr_loop_time = time64;
+		}
+	}
+
+	/* check result here */
+	if (*llr_loop_time == SBL_PML_LLR_MAX_LOOP_TIME) {
+		if (sbl_debug_option(sbl, port_num, SBL_DEBUG_ALLOW_LOOP_TIME_FAIL)) {
+			sbl_dev_err(sbl->dev, "%d: LLR measure loop time failed (min = %lld, max = %lld, last = %lld)",
+				    port_num, min_loop_time, max_loop_time, time64);
+			err = -ENODATA;
+			goto out;
+		}
+		/* set to max calculated loop time */
+		*llr_loop_time = max_loop_time;
+	}
+
+	sbl_dev_dbg(sbl->dev, "%d: LLR measure loop time = %lldns", port_num, *llr_loop_time);
+	err = 0;
+
+out:
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_LLR_MEASURE);
+	return err;
+}
+
+/*   calculate the required size of the llr buffer
+ *
+ *   we will use the measured loop time not cable length as we dont
+ *   always have that.
+ *
+ *   TODO: link degrade & mfs
+ */
+static void sbl_pml_llr_calculate_capacity(struct sbl_inst *sbl, int port_num,
+					   u64 *max_data, u64 *max_seq)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	u64 bytes_per_ns = 25;
+	u64 bytes_per_frame = sbl_frame_size(sbl, port_num);
+	u64 cap_data_max;
+	u64 cap_seq_max;
+	u64 calc;
+
+	switch (link->link_mode) {
+	case SBL_LINK_MODE_BS_200G:
+		bytes_per_ns = 25;  /* 25000us */
+		break;
+	case SBL_LINK_MODE_BJ_100G:
+	case SBL_LINK_MODE_CD_100G:
+		bytes_per_ns = 13;  /* 12500us */
+		break;
+	case SBL_LINK_MODE_CD_50G:
+		bytes_per_ns =  7;  /*  6250us */
+		break;
+	}
+
+	sbl_llr_max_data_get(sbl, port_num, &cap_data_max, &cap_seq_max);
+
+	calc = (link->llr_loop_time * bytes_per_ns) + (bytes_per_frame * SBL_PML_LLR_NUM_FRAMES);
+
+	/* units 48 byte quanta */
+	*max_data = DIV_ROUND_UP(calc, 48);
+	if (*max_data > cap_data_max) {
+		sbl_dev_dbg(sbl->dev,
+			    "%d: LLR max data cap 0x%llx out of bounds, setting to 0x%llx",
+			port_num, *max_data, cap_data_max);
+		*max_data = cap_data_max;
+	}
+
+	/* units 32 byte frame size */
+	*max_seq  = DIV_ROUND_UP(calc, 32);
+	if (*max_seq > cap_seq_max) {
+		sbl_dev_dbg(sbl->dev,
+			    "%d: LLR max seq cap 0x%llx out of bounds, setting to 0x%llx",
+			port_num, *max_seq, cap_seq_max);
+		*max_seq = cap_seq_max;
+	}
+
+	sbl_dev_dbg(sbl->dev,
+		    "%d: LLR cap: data = 0x%llx, seq = 0x%llx",
+		port_num, *max_data, *max_seq);
+}
+
+/* enable llr timing measurements */
+static void sbl_pml_llr_enable_loop_timing(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: LLR enable loop timing", port_num);
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_LLR_OFFSET);
+	val64 = SBL_PML_CFG_LLR_ENABLE_LOOP_TIMING_UPDATE(val64, 1ULL);
+	sbl_write64(sbl, base | SBL_PML_CFG_LLR_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_LLR_OFFSET);  /* flush */
+
+	sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_LLR_LOOP);
+}
+
+/* disable llr timing measurements */
+static void sbl_pml_llr_disable_loop_timing(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: LLR disable loop timing", port_num);
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_LLR_OFFSET);
+	val64 = SBL_PML_CFG_LLR_ENABLE_LOOP_TIMING_UPDATE(val64, 0ULL);
+	sbl_write64(sbl, base | SBL_PML_CFG_LLR_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_LLR_OFFSET);  /* flush */
+
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_LLR_LOOP);
+}
+
+/* set LLR mode */
+static void sbl_pml_llr_mode_set(struct sbl_inst *sbl, int port_num, u32 llr_mode)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: LLR mode set (%d)", port_num, llr_mode);
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_LLR_OFFSET);
+	switch (llr_mode) {
+	case SBL_LLR_MODE_OFF:
+		val64 = SBL_PML_CFG_LLR_LLR_MODE_UPDATE(val64, 0ULL);
+		break;
+	case SBL_LLR_MODE_MONITOR:
+		val64 = SBL_PML_CFG_LLR_LLR_MODE_UPDATE(val64, 1ULL);
+		break;
+	case SBL_LLR_MODE_ON:
+		val64 = SBL_PML_CFG_LLR_LLR_MODE_UPDATE(val64, 2ULL);
+		break;
+	default:
+		sbl_dev_dbg(sbl->dev, "%d: LLR mode invalid (%d)", port_num, llr_mode);
+		return;
+	}
+	sbl_write64(sbl, base | SBL_PML_CFG_LLR_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_LLR_OFFSET);  /* flush */
+}
+
+/* LLR detect */
+static int sbl_pml_llr_detect(struct sbl_inst *sbl, int port_num, u32 *llr_mode)
+{
+	u32           base       = SBL_PML_BASE(port_num);
+	u64           val64      = 0;
+	u64           time64     = 0;
+	unsigned long last_jiffy = jiffies + msecs_to_jiffies(SBL_PML_LLR_DETECT_TIMEOUT);
+	int           err        = -1;
+
+	sbl_dev_dbg(sbl->dev, "%d: LLR detect", port_num);
+
+	sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_LLR_DETECT);
+
+	/* OFF unless we detect a loop */
+	*llr_mode = SBL_LLR_MODE_OFF;
+
+	sbl_write64(sbl, (base | SBL_PML_STS_LLR_LOOP_TIME_OFFSET), 0ULL);
+	sbl_read64(sbl, (base | SBL_PML_STS_LLR_LOOP_TIME_OFFSET));  /* flush */
+	do {
+
+		if (sbl_start_timeout(sbl, port_num)) {
+			err = -ETIMEDOUT;
+			goto out;
+		}
+
+		if (sbl_base_link_start_cancelled(sbl, port_num)) {
+			err = -ECANCELED;
+			goto out;
+		}
+
+		msleep(SBL_PML_LLR_DETECT_DELAY);
+
+		val64 = sbl_read64(sbl, (base | SBL_PML_STS_LLR_LOOP_TIME_OFFSET));
+		time64 = SBL_PML_STS_LLR_LOOP_TIME_LOOP_TIME_GET(val64);                 /* ns */
+		sbl_dev_dbg(sbl->dev, "%d: LLR time = %lldns", port_num, time64);
+
+		if (time64) {
+			/* loop detected */
+			if ((time64 < SBL_PML_LLR_MIN_LOOP_TIME) || (time64 > SBL_PML_LLR_MAX_LOOP_TIME)) {
+				sbl_dev_warn(sbl->dev, "%d: LLR time out of bounds (%lldns)", port_num, time64);
+				continue;
+			}
+			*llr_mode = SBL_LLR_MODE_ON;
+			err = 0;
+			goto out;
+		}
+
+	} while (time_before(jiffies, last_jiffy));
+
+out:
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_LLR_DETECT);
+	return err;
+}
+
+/* get LLR mode */
+static int sbl_pml_llr_mode_get(struct sbl_inst *sbl, int port_num, u32 *llr_mode)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	int               err = -1;
+
+	sbl_dev_dbg(sbl->dev, "%d: LLR mode get (%d)", port_num, link->blattr.llr_mode);
+
+	switch (link->blattr.llr_mode) {
+
+	case SBL_LLR_MODE_MONITOR:
+		sbl_dev_dbg(sbl->dev, "%d: LLR mode monitor", port_num);
+		*llr_mode = SBL_LLR_MODE_MONITOR;
+		return 0;
+
+	case SBL_LLR_MODE_ON:
+		sbl_dev_dbg(sbl->dev, "%d: LLR mode on", port_num);
+		goto out_on;
+
+	case SBL_LLR_MODE_AUTO:
+		/* check fabric link here */
+		if (link->blattr.options & SBL_OPT_FABRIC_LINK) {
+			sbl_dev_dbg(sbl->dev, "%d: LLR mode on fabric", port_num);
+			goto out_on;
+		}
+		/* check ether link here */
+		if (!(link->blattr.options & SBL_OPT_ENABLE_ETHER_LLR))
+			goto out_off;
+		/* check AN options here */
+		if (link->an_options & AN_OPT_LLR) {
+			if (link->an_options & AN_OPT_ETHER_LLR) {
+				if (link->an_options & AN_OPT_HPC_WITH_LLR) {
+					link->llr_options |= SBL_PML_LLR_OPTION_HPC_WIH_LLR;
+					link->ifg_config = SBL_IFG_CONFIG_HPC;
+					sbl_dev_dbg(sbl->dev, "%d: LLR mode on with HPC from AN", port_num);
+					goto out_on;
+				}
+				sbl_dev_dbg(sbl->dev, "%d: LLR mode on without HPC from AN", port_num);
+				goto out_on;
+			}
+			goto out_off;
+		}
+		/* do loop detect here */
+		err = sbl_pml_llr_detect(sbl, port_num, &(link->llr_mode));
+		switch (err) {
+		case 0:
+			if (link->llr_mode == SBL_LLR_MODE_OFF)
+				goto out_off;
+			if (link->blattr.options & SBL_OPT_ENABLE_IFG_HPC_WITH_LLR) {
+				link->llr_options |= SBL_PML_LLR_OPTION_HPC_WIH_LLR;
+				link->ifg_config = SBL_IFG_CONFIG_HPC;
+				sbl_dev_dbg(sbl->dev, "%d: LLR mode on with HPC from LOOP", port_num);
+				goto out_on;
+			}
+			sbl_dev_dbg(sbl->dev, "%d: LLR mode on without HPC from LOOP", port_num);
+			goto out_on;
+		case -ECANCELED:
+			sbl_dev_dbg(sbl->dev, "%d: LLR detect cancelled", port_num);
+			goto out_err;
+		case -ETIMEDOUT:
+			sbl_dev_dbg(sbl->dev, "%d: LLR detect timed out", port_num);
+			goto out_err;
+		default:
+			goto out_off;
+		}
+		goto out_off;
+
+	case SBL_LLR_MODE_OFF:
+		goto out_off;
+
+	default:
+		sbl_dev_err(sbl->dev, "%d: LLR invalid mode [%d]", port_num, link->blattr.llr_mode);
+		err = -EBADRQC;
+		goto out_err;
+	}
+
+out_off:
+	sbl_dev_dbg(sbl->dev, "%d: LLR mode off", port_num);
+	*llr_mode = SBL_LLR_MODE_OFF;
+	return 0;
+
+out_on:
+	*llr_mode = SBL_LLR_MODE_ON;
+	return 0;
+
+out_err:
+	*llr_mode = SBL_LLR_MODE_OFF;
+	return err;
+}
+
+/* configure */
+void sbl_pml_llr_config(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: LLR config", port_num);
+
+	/* init to original requested */
+	link->llr_mode = link->blattr.llr_mode;
+
+	/* config */
+	sbl_write64(sbl, base | SBL_PML_STS_LLR_MAX_USAGE_OFFSET, 0ULL);
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_LLR_OFFSET);
+	val64 = SBL_PML_CFG_LLR_LLR_MODE_UPDATE(val64, 0ULL);  /* LLR OFF */
+	val64 = SBL_PML_CFG_LLR_PREAMBLE_SEQ_CHECK_UPDATE(val64, 1ULL);
+	val64 = SBL_PML_CFG_LLR_ACK_NACK_ERR_CHECK_UPDATE(val64, 1ULL);
+	val64 = SBL_PML_CFG_LLR_FILTER_LOSSLESS_WHEN_OFF_UPDATE(val64, 1ULL);
+	val64 = SBL_PML_CFG_LLR_FILTER_CTL_FRAMES_UPDATE(val64, 1ULL);
+	val64 = SBL_PML_CFG_LLR_SIZE_UPDATE(val64, 3ULL);
+	val64 = SBL_PML_CFG_LLR_ENABLE_LOOP_TIMING_UPDATE(val64, 0ULL);  /* loop timing off */
+	val64 = SBL_PML_CFG_LLR_LINK_DOWN_BEHAVIOR_UPDATE(val64,
+				 sbl_pml_llr_link_down_behaviour(sbl,
+								 port_num));
+	val64 = SBL_PML_CFG_LLR_MAC_IF_CREDITS_UPDATE(val64,
+						      SBL_PML_CFG_LLR_MAC_IF_CREDITS_DFLT);
+	sbl_write64(sbl, base | SBL_PML_CFG_LLR_OFFSET, val64);
+
+	sbl_write64(sbl, base | SBL_PML_CFG_LLR_CF_SMAC_OFFSET,
+		    SBL_PML_CFG_LLR_CF_SMAC_DFLT);
+
+	sbl_write64(sbl, base | SBL_PML_CFG_LLR_CF_ETYPE_OFFSET,
+		    SBL_PML_CFG_LLR_CF_ETYPE_DFLT);
+
+	sbl_write64(sbl, base | SBL_PML_CFG_LLR_SM_OFFSET,
+		    SBL_PML_CFG_LLR_SM_DFLT);
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_LLR_CF_RATES_OFFSET);
+	val64 = SBL_PML_CFG_LLR_CF_RATES_LOOP_TIMING_PERIOD_UPDATE(val64, SBL_PML_LLR_TIMING_PERIOD);
+	sbl_write64(sbl, base | SBL_PML_CFG_LLR_CF_RATES_OFFSET, val64);
+
+	sbl_read64(sbl, base | SBL_PML_CFG_LLR_OFFSET);  /* flush */
+}
+
+/* start */
+int sbl_pml_llr_start(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u32 base = SBL_PML_BASE(port_num);
+	u64 llr_max_data;
+	u64 llr_max_seq;
+	u64 val64;
+	u64 recovery_timeout_ns;
+	int err = -1;
+
+	sbl_dev_dbg(sbl->dev, "%d: LLR start", port_num);
+
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_LLR_DISABLED);
+
+	sbl_pml_llr_enable_loop_timing(sbl, port_num);
+
+	/* get mode here */
+	err = sbl_pml_llr_mode_get(sbl, port_num, &(link->llr_mode));
+	if (err) {
+		sbl_dev_err(sbl->dev, "%d: LLR get mode failed [%d]", port_num, err);
+		goto out_err;
+	}
+
+	/* if LLR is OFF then nothing to do here */
+	if (link->llr_mode == SBL_LLR_MODE_OFF)
+		goto out;
+
+	/* set HPC if needed */
+	if (link->llr_options & SBL_PML_LLR_OPTION_HPC_WIH_LLR)
+		sbl_pml_mac_hpc_set(sbl, port_num);
+
+	/* start ordered sets */
+	sbl_pml_pcs_ordered_sets(sbl, port_num, true);
+
+	/* measure llr loop time if we don't have it already */
+	if (!link->llr_loop_time) {
+		err = sbl_pml_llr_measure_loop_time_ns(sbl, port_num, &link->llr_loop_time);
+		if (err) {
+			sbl_dev_err(sbl->dev, "%d: LLR loop measurement failed [%d]", port_num, err);
+			goto out_err;
+		}
+	}
+
+	/* set max replay time from loop-back time */
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_LLR_SM_OFFSET);
+	if (link->blattr.options & SBL_DISABLE_PML_RECOVERY)
+		val64 = SBL_PML_CFG_LLR_SM_REPLAY_CT_MAX_UPDATE(val64, SBL_DFLT_REPLAY_CT_MAX);
+	else
+		val64 = SBL_PML_CFG_LLR_SM_REPLAY_CT_MAX_UPDATE(val64, SBL_LLR_REPLAY_CT_MAX_UNLIMITED);
+	val64 = SBL_PML_CFG_LLR_SM_REPLAY_TIMER_MAX_UPDATE(val64,
+							   3 * link->llr_loop_time + 500);
+	sbl_write64(sbl, base | SBL_PML_CFG_LLR_SM_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_LLR_SM_OFFSET);  /* flush */
+
+	/* capacity configuration */
+	if (link->blattr.options & SBL_OPT_FABRIC_LINK) {
+		/* these can be set to their defaults for fabric links */
+		sbl_write64(sbl, base | SBL_PML_CFG_LLR_CAPACITY_OFFSET,
+			    SBL_PML_CFG_LLR_CAPACITY_DFLT);
+		sbl_read64(sbl, base | SBL_PML_CFG_LLR_CAPACITY_OFFSET);  /* flush */
+	} else {
+		sbl_pml_llr_calculate_capacity(sbl, port_num, &llr_max_data, &llr_max_seq);
+		val64 = SBL_PML_CFG_LLR_CAPACITY_MAX_DATA_SET(llr_max_data) |
+			SBL_PML_CFG_LLR_CAPACITY_MAX_SEQ_SET(llr_max_seq);
+		sbl_write64(sbl, base | SBL_PML_CFG_LLR_CAPACITY_OFFSET, val64);
+		sbl_read64(sbl, base | SBL_PML_CFG_LLR_CAPACITY_OFFSET);  /* flush */
+	}
+
+	/* set max data age timer & link down timer */
+	if (link->blattr.options & SBL_DISABLE_PML_RECOVERY)
+		sbl_write64(sbl, base | SBL_PML_CFG_LLR_TIMEOUTS_OFFSET,
+			    SBL_PML_CFG_LLR_TIMEOUTS_DFLT);
+	else {
+		recovery_timeout_ns = ((u64)link->blattr.pml_recovery.timeout +
+			SBL_PML_REC_LLR_TIMEOUT_OFFSET) * NSEC_PER_MSEC;
+		if (recovery_timeout_ns > U32_MAX) {
+			err = -ERANGE;
+			goto out_err;
+		}
+		val64 = sbl_read64(sbl, base | SBL_PML_CFG_LLR_TIMEOUTS_OFFSET);
+		val64 = SBL_PML_CFG_LLR_TIMEOUTS_DATA_AGE_TIMER_MAX_UPDATE(val64,
+			recovery_timeout_ns);
+		val64 = SBL_PML_CFG_LLR_TIMEOUTS_PCS_LINK_DN_TIMER_MAX_UPDATE(val64,
+			recovery_timeout_ns);
+		sbl_write64(sbl, base | SBL_PML_CFG_LLR_TIMEOUTS_OFFSET, val64);
+	}
+	sbl_read64(sbl, base | SBL_PML_CFG_LLR_TIMEOUTS_OFFSET);  /* flush */
+
+	/* clear all the llr related error flags */
+	sbl_pml_err_flgs_clear(sbl, port_num, SBL_PML_ALL_LLR_ERR_FLGS);
+
+	/* set required mode */
+	sbl_pml_llr_mode_set(sbl, port_num, link->llr_mode);
+
+	/* wait for llr to start */
+	err = sbl_pml_llr_ready_wait(sbl, port_num);
+	if (err) {
+		sbl_dev_err(sbl->dev, "%d: LLR ready wait failed [%d]", port_num, err);
+		goto out_err;
+	}
+
+	/* success */
+	sbl_dev_dbg(sbl->dev, "%d: LLR running", port_num);
+	sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_LLR_RUN);
+
+out:
+	sbl_pml_llr_disable_loop_timing(sbl, port_num);
+	return 0;
+
+out_err:
+	sbl_pml_llr_disable_loop_timing(sbl, port_num);
+	sbl_pml_llr_stop(sbl, port_num);
+	return err;
+}
+
+/**
+ * sbl_pml_llr_get_state() - Get pml llr state
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Return: state value
+ */
+u32 sbl_pml_llr_get_state(struct sbl_inst *sbl, int port_num)
+{
+	u32 base  = SBL_PML_BASE(port_num);
+	u64 val64 = sbl_read64(sbl, base | SBL_PML_STS_LLR_OFFSET);
+
+	switch (SBL_PML_STS_LLR_LLR_STATE_GET(val64)) {
+	case 0: /* OFF */
+		return SBL_PML_LLR_STATE_OFF;
+	case 1: /* INIT */
+		return SBL_PML_LLR_STATE_INIT;
+	case 2: /* ADVANCE */
+		return SBL_PML_LLR_STATE_ADVANCE;
+	case 3: /* HALT */
+		return SBL_PML_LLR_STATE_HALT;
+	case 4: /* REPLAY */
+		return SBL_PML_LLR_STATE_REPLAY;
+	case 5: /* DISCARD */
+		return SBL_PML_LLR_STATE_DISCARD;
+	case 6: /* MONITOR */
+		return SBL_PML_LLR_STATE_MONITOR;
+	default:
+		return SBL_PML_LLR_STATE_UNKNOWN;
+	}
+}
+EXPORT_SYMBOL(sbl_pml_llr_get_state);
+
+/**
+ * sbl_pml_llr_stop() - Stop pml llr functionality
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Configure various pml llr registers to stop functionality
+ */
+void sbl_pml_llr_stop(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: LLR stop", port_num);
+
+	link->llr_mode = SBL_LLR_MODE_OFF;
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_LLR_OFFSET);
+	val64 = SBL_PML_CFG_LLR_LLR_MODE_UPDATE(val64, 0LL);  /* LLR OFF */
+	val64 = SBL_PML_CFG_LLR_FILTER_LOSSLESS_WHEN_OFF_UPDATE(val64, 1ULL);
+	val64 = SBL_PML_CFG_LLR_FILTER_CTL_FRAMES_UPDATE(val64, 0ULL);
+	val64 = SBL_PML_CFG_LLR_ENABLE_LOOP_TIMING_UPDATE(val64, 0ULL);
+	val64 = SBL_PML_CFG_LLR_LINK_DOWN_BEHAVIOR_UPDATE(val64,
+				 sbl_pml_llr_link_down_behaviour(sbl,
+								 port_num));
+	val64 = SBL_PML_CFG_LLR_MAC_IF_CREDITS_UPDATE(val64,
+						      SBL_PML_CFG_LLR_MAC_IF_CREDITS_DFLT);
+	sbl_write64(sbl, base | SBL_PML_CFG_LLR_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_LLR_OFFSET);  /* flush */
+
+	sbl_pml_pcs_ordered_sets(sbl, port_num, false);
+
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_LLR_RUN);
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_LLR_DISABLED);
+}
+EXPORT_SYMBOL(sbl_pml_llr_stop);
+
+/**
+ * sbl_pml_llr_disable() - Disable pml llr
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * This function disables the LLR feature by updating the LLR
+ * mode configuration register. Also updates the link info to reflect
+ * disabled state and clear active status.
+ */
+void sbl_pml_llr_disable(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: LLR disable", port_num);
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_LLR_OFFSET);
+	val64 = SBL_PML_CFG_LLR_LLR_MODE_UPDATE(val64, 0ULL); /* LLR OFF */
+	sbl_write64(sbl, base | SBL_PML_CFG_LLR_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_LLR_OFFSET);  /* flush */
+
+	sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_LLR_DISABLED);
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_LLR_RUN);
+}
+EXPORT_SYMBOL(sbl_pml_llr_disable);
+
+/**
+ * sbl_pml_llr_enable() - Enable pml llr
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Sets the LLR mode based on the link configuration to
+ * enable the LLR feature.
+ */
+void sbl_pml_llr_enable(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	sbl_dev_dbg(sbl->dev, "%d: LLR enable", port_num);
+
+	sbl_pml_llr_mode_set(sbl, port_num, link->llr_mode);
+}
+EXPORT_SYMBOL(sbl_pml_llr_enable);
+
+/**
+ * sbl_pml_llr_check_is_ready() - check llr state
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @timeout_ms: Value
+ *
+ * Check matching LLR state in invervals, if state
+ * matches then return else sleep in intervals untill timeout.
+ *
+ * Context: Sleep in intervals based on condition
+ *
+ * Return: true on success
+ */
+bool sbl_pml_llr_check_is_ready(struct sbl_inst *sbl, int port_num, unsigned int timeout_ms)
+{
+	unsigned long to_jiffy = jiffies + msecs_to_jiffies(timeout_ms);
+
+	sbl_dev_dbg(sbl->dev, "%d: LLR check is ready", port_num);
+
+	do {
+		if (sbl_pml_llr_get_state(sbl, port_num) == SBL_PML_LLR_STATE_ADVANCE)
+			return true;
+		usleep_range(5000, 6000);
+	} while (time_before(jiffies, to_jiffy));
+
+	return false;
+}
+EXPORT_SYMBOL(sbl_pml_llr_check_is_ready);
+
+/* behaviour when the link is down
+ * TODO - revisit with cassini and/or faster restart
+ */
+u64 sbl_pml_llr_link_down_behaviour(struct sbl_inst *sbl,
+				    int port_num)
+{
+	if (sbl_is_fabric_link(sbl, port_num)) {
+		/*   fabric link should really be BLOCK, but only if the pcs
+		 *   can recover quickly. Currently it can't - pcs failure means
+		 *   going right back to tuning the serdes.
+		 */
+		return 0ULL;
+	}
+
+	return 0ULL;
+}
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_pml_llr_nic.c b/drivers/net/ethernet/hpe/sbl/sbl_pml_llr_nic.c
new file mode 100644
index 000000000000..d154a2f25c6e
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_pml_llr_nic.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2025 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_an.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include <linux/hpe/sbl/sbl_pml.h>
+
+#include "sbl_pml_fn.h"
+#include "sbl_link.h"
+#include "sbl_internal.h"
+
+int sbl_frame_size(struct sbl_inst *sbl, int port_num)
+{
+	u64 bytes_per_frame = 9216;
+
+	return bytes_per_frame;
+}
+
+void sbl_llr_max_data_get(struct sbl_inst *sbl, int port_num,
+			  u64 *cap_data_max, u64 *cap_seq_max)
+{
+	*cap_data_max = sbl_llr_edge_cap_data_max_get();
+	*cap_seq_max  = sbl_llr_edge_cap_seq_max_get();
+}
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_pml_mac.c b/drivers/net/ethernet/hpe/sbl/sbl_pml_mac.c
new file mode 100644
index 000000000000..782bfc26451e
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_pml_mac.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2019-2021 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include <linux/hpe/sbl/sbl_pml.h>
+
+#include "sbl_pml_fn.h"
+#include "sbl_link.h"
+#include "sbl_internal.h"
+
+/* get mode */
+static void sbl_pml_mac_get_mode(struct sbl_inst *sbl, int port_num, u64 *ifg_mode, u64 *ifg_adjustment)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	sbl_dev_dbg(sbl->dev, "%d: MAC get mode", port_num);
+
+	/* start with what was requested */
+	link->ifg_config = link->blattr.ifg_config;
+
+	/* check BL options */
+	if (link->blattr.options & SBL_OPT_ENABLE_IFG_CONFIG) {
+		/* set using IFG config */
+		switch (link->ifg_config) {
+		case SBL_IFG_CONFIG_HPC:
+			*ifg_mode       = 0;
+			*ifg_adjustment = 3;
+			break;
+		case SBL_IFG_CONFIG_IEEE_200G:
+			*ifg_mode       = 1;
+			*ifg_adjustment = 0;
+			break;
+		case SBL_IFG_CONFIG_IEEE_100G:
+			*ifg_mode       = 1;
+			*ifg_adjustment = 1;
+			break;
+		case SBL_IFG_CONFIG_IEEE_50G:
+		default:
+			*ifg_mode       = 1;
+			*ifg_adjustment = 2;
+			break;
+		}
+	} else if (link->blattr.options & SBL_OPT_FABRIC_LINK) {
+		/* set fabric link */
+		*ifg_mode       = 0;
+		*ifg_adjustment = 3;
+	} else {
+		/* set ether link */
+		switch (link->link_mode) {
+		case SBL_LINK_MODE_BS_200G:
+			*ifg_mode       = 1;
+			*ifg_adjustment = 0;
+			break;
+		case SBL_LINK_MODE_BJ_100G:
+			*ifg_mode       = 1;
+			*ifg_adjustment = 1;
+			break;
+		case SBL_LINK_MODE_CD_100G:
+			*ifg_mode       = 1;
+			*ifg_adjustment = 1;
+			break;
+		case SBL_LINK_MODE_CD_50G:
+		default:
+			*ifg_mode       = 1;
+			*ifg_adjustment = 2;
+			break;
+		}
+	}
+}
+
+/* configure */
+int sbl_pml_mac_config(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 ifg_mode       = 0;
+	u64 ifg_adjustment = 0;
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: MAC config", port_num);
+
+	sbl_pml_mac_get_mode(sbl, port_num, &ifg_mode, &ifg_adjustment);
+
+	/* tx mac config */
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_TX_MAC_OFFSET);
+	val64 = SBL_PML_CFG_TX_MAC_PCS_CREDITS_UPDATE(val64,
+						      SBL_PML_CFG_TX_MAC_PCS_CREDITS_DFLT);
+	val64 = SBL_PML_CFG_TX_MAC_IFG_MODE_UPDATE(val64, ifg_mode);
+	val64 = SBL_PML_CFG_TX_MAC_IEEE_IFG_ADJUSTMENT_UPDATE(val64, ifg_adjustment);
+	/* Short preamble is always 0 for now. See Rosetta ERRATA-2594 */
+	val64 = SBL_PML_CFG_TX_MAC_SHORT_PREAMBLE_UPDATE(val64, 0ULL);
+	val64 = SBL_PML_CFG_TX_MAC_MAC_OPERATIONAL_UPDATE(val64, 0ULL);
+	sbl_write64(sbl, base | SBL_PML_CFG_TX_MAC_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_TX_MAC_OFFSET);  /* flush */
+
+	/* rx mac config */
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_RX_MAC_OFFSET);
+	val64 = SBL_PML_CFG_RX_MAC_SHORT_PREAMBLE_UPDATE(val64, 0ULL);
+	val64 = SBL_PML_CFG_RX_MAC_FILTER_ILLEGAL_SIZE_UPDATE(val64, 1ULL);
+	val64 = SBL_PML_CFG_RX_MAC_MAC_OPERATIONAL_UPDATE(val64, 0ULL);
+	sbl_write64(sbl, base | SBL_PML_CFG_RX_MAC_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_RX_MAC_OFFSET);  /* flush */
+
+	return 0;
+}
+
+/**
+ * sbl_pml_mac_start() - Start mac
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Configure various MAC registers to start tx/rx mac
+ * and clear if any errors
+ */
+void sbl_pml_mac_start(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: MAC start", port_num);
+
+	/* tx mac start */
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_TX_MAC_OFFSET);
+	val64 = SBL_PML_CFG_TX_MAC_MAC_OPERATIONAL_UPDATE(val64, 1ULL);
+	sbl_write64(sbl, base | SBL_PML_CFG_TX_MAC_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_TX_MAC_OFFSET);  /* flush */
+
+	/* rx mac start */
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_RX_MAC_OFFSET);
+	val64 = SBL_PML_CFG_RX_MAC_MAC_OPERATIONAL_UPDATE(val64, 1ULL);
+	sbl_write64(sbl, base | SBL_PML_CFG_RX_MAC_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_RX_MAC_OFFSET);  /* flush */
+
+	/* clear any errors */
+	val64 = SBL_PML_ERR_FLG_MAC_TX_DP_ERR_SET(1ULL) |
+		SBL_PML_ERR_FLG_MAC_RX_DP_ERR_SET(1ULL);
+	sbl_write64(sbl, base | SBL_PML_ERR_CLR_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_ERR_CLR_OFFSET);  /* flush */
+
+	sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_MAC_OP);
+
+}
+EXPORT_SYMBOL(sbl_pml_mac_start);
+
+/**
+ * sbl_pml_mac_stop() - Stop mac
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Configure various MAC registers to off tx/rx mac
+ */
+void sbl_pml_mac_stop(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: MAC stop", port_num);
+
+	/* rx mac off */
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_RX_MAC_OFFSET);
+	val64 = SBL_PML_CFG_RX_MAC_MAC_OPERATIONAL_UPDATE(val64, 0ULL);
+	sbl_write64(sbl, base | SBL_PML_CFG_RX_MAC_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_RX_MAC_OFFSET);  /* flush */
+
+	/* tx mac off */
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_TX_MAC_OFFSET);
+	val64 = SBL_PML_CFG_TX_MAC_MAC_OPERATIONAL_UPDATE(val64, 0ULL);
+	sbl_write64(sbl, base | SBL_PML_CFG_TX_MAC_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_TX_MAC_OFFSET);  /* flush */
+
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_MAC_OP);
+}
+EXPORT_SYMBOL(sbl_pml_mac_stop);
+
+/* get the current hw status */
+void sbl_pml_mac_hw_status(struct sbl_inst *sbl, int port_num, bool *tx_op, bool *rx_op,
+			   u32 *ifg_mode, u32 *ifg_adjustment)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: MAC status", port_num);
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_TX_MAC_OFFSET);
+	if (tx_op)
+		*tx_op = SBL_PML_CFG_TX_MAC_MAC_OPERATIONAL_GET(val64);
+	if (ifg_mode)
+		*ifg_mode = SBL_PML_CFG_TX_MAC_IFG_MODE_GET(val64);
+	if (ifg_adjustment)
+		*ifg_adjustment = SBL_PML_CFG_TX_MAC_IEEE_IFG_ADJUSTMENT_GET(val64);
+	if (rx_op) {
+		val64 = sbl_read64(sbl, base | SBL_PML_CFG_RX_MAC_OFFSET);
+		*rx_op = SBL_PML_CFG_RX_MAC_MAC_OPERATIONAL_GET(val64);
+	}
+}
+
+/* HPC IFG set */
+void sbl_pml_mac_hpc_set(struct sbl_inst *sbl, int port_num)
+{
+	u32 base  = SBL_PML_BASE(port_num);
+	u64 val64 = 0;
+
+	sbl_dev_dbg(sbl->dev, "%d: MAC HPC set", port_num);
+
+	val64 = sbl_read64(sbl, (base | SBL_PML_CFG_TX_MAC_OFFSET));
+	val64 = SBL_PML_CFG_TX_MAC_IFG_MODE_UPDATE(val64, 0ULL); /* HPC */
+	val64 = SBL_PML_CFG_TX_MAC_IEEE_IFG_ADJUSTMENT_UPDATE(val64, 3ULL); /* no adjustment */
+	sbl_write64(sbl, (base | SBL_PML_CFG_TX_MAC_OFFSET), val64);
+	sbl_read64(sbl, (base | SBL_PML_CFG_TX_MAC_OFFSET));  /* flush */
+}
+
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_pml_pcs.c b/drivers/net/ethernet/hpe/sbl/sbl_pml_pcs.c
new file mode 100644
index 000000000000..81484e177394
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_pml_pcs.c
@@ -0,0 +1,1085 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2019,2022-2025 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+#include <linux/hpe/sbl/sbl_pml.h>
+
+#include "sbl_pml_fn.h"
+#include "sbl_link.h"
+#include "sbl_serdes_map.h"
+#include "sbl_internal.h"
+#include "sbl_serdes_fn.h"
+
+/* Function to find the lanes that data will be arriving on and
+ * setup the correct active lanes to align and active fec lanes.
+ */
+static int sbl_pml_pcs_determine_active_lanes(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct serdes_info *serdes =  sbl->switch_info->ports[port_num].serdes;
+	int i;
+
+	sbl_dev_dbg(sbl->dev, "%d: pcs config lanes", port_num);
+
+	switch (link->link_mode) {
+	case SBL_LINK_MODE_BS_200G:
+		/* 4 lanes of 50Gbps - 8 internal 25Gbps fec lanes
+		 * setup directly
+		 */
+		link->active_rx_lanes  = 0xf;    /* all */
+		link->active_fec_lanes = 0xff;   /* all */
+		break;
+	case SBL_LINK_MODE_BJ_100G:
+		/* 4 lanes of 25Gbps - 4 internal 25Gbps lanes
+		 * setup directly
+		 */
+		link->active_rx_lanes  = 0xf;    /* all */
+		link->active_fec_lanes = 0xf;    /* special case */
+		break;
+	case SBL_LINK_MODE_CD_100G:
+		/* 2 lanes of 50Gbps - 4 internal 25Gbps lanes
+		 * locate incoming lanes 0 and 1
+		 */
+		link->active_rx_lanes  = 0;
+		link->active_fec_lanes = 0;
+		if (link->loopback_mode == SBL_LOOPBACK_MODE_LOCAL) {
+			/* we will receive on the tx lanes we went out on */
+			for (i = 0; i < SBL_SERDES_LANES_PER_PORT; ++i) {
+				if (serdes[i].tx_lane_source < 2) {
+					link->active_rx_lanes  |= 0x1 << i;
+					link->active_fec_lanes |= 0x3 << 2 * i;
+				}
+			}
+		} else {
+			/* we will receive on the rx lanes */
+			for (i = 0; i < SBL_SERDES_LANES_PER_PORT; ++i) {
+				if (serdes[i].rx_lane_source < 2) {
+					link->active_rx_lanes  |= 0x1 << i;
+					link->active_fec_lanes |= 0x3 << 2 * i;
+				}
+			}
+		}
+		break;
+	case SBL_LINK_MODE_CD_50G:
+		/* 1 lanes of 50Gbps - 2 internal 25Gbps lanes
+		 * locate incoming lane 0
+		 */
+		if (link->loopback_mode == SBL_LOOPBACK_MODE_LOCAL) {
+			/* we will receive on the lane 0 we went out on */
+			for (i = 0; i < SBL_SERDES_LANES_PER_PORT; ++i) {
+				if (serdes[i].tx_lane_source == 0) {
+					link->active_rx_lanes  = 0x1 << i;
+					link->active_fec_lanes = 0x3 << 2 * i;
+					break;
+				}
+			}
+		} else {
+			/* we will receive on the rx lane */
+			for (i = 0; i < SBL_SERDES_LANES_PER_PORT; ++i) {
+				if (serdes[i].rx_lane_source == 0) {
+					link->active_rx_lanes  = 0x1 << i;
+					link->active_fec_lanes = 0x3 << 2 * i;
+				}
+			}
+		}
+		break;
+	default:
+		sbl_dev_err(sbl->dev, "%d: pcs bad link_mode (%d)", port_num, link->link_mode);
+		return -EBADRQC;
+	}
+
+	return 0;
+}
+
+/* helper state functions */
+static bool sbl_pml_pcs_locked(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	struct sbl_link *link = sbl->link + port_num;
+	u64 locked_lanes;
+	u64 val64;
+
+	val64 = sbl_read64(sbl, base | SBL_PML_STS_RX_PCS_OFFSET);
+	locked_lanes = SBL_PML_STS_RX_PCS_AM_LOCK_GET(val64);
+
+	return (locked_lanes == link->active_fec_lanes);
+}
+
+static void sbl_pml_pcs_start_lock(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: pml bring-up start am lock\n", port_num);
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET);
+	val64 = SBL_PML_CFG_RX_PCS_ENABLE_LOCK_UPDATE(val64, 1ULL);
+	val64 = SBL_PML_CFG_RX_PCS_ACTIVE_LANES_UPDATE(val64, link->active_rx_lanes);
+	sbl_write64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET);
+}
+
+static void sbl_pml_pcs_stop_lock(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: pml bring-up stop am lock\n", port_num);
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET);
+	val64 = SBL_PML_CFG_RX_PCS_ENABLE_LOCK_UPDATE(val64, 0ULL);
+	val64 = SBL_PML_CFG_RX_PCS_ACTIVE_LANES_UPDATE(val64, 0ULL);
+	sbl_write64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET);
+}
+
+/* wait for the pcs to become aligned */
+static int sbl_pml_pcs_alignment_wait(struct sbl_inst *sbl, int port_num)
+{
+	unsigned long start_jiffy;
+	int elapsed;
+	int err = 0;
+
+	sbl_dev_dbg(sbl->dev, "%d: pml pcs alignment wait\n", port_num);
+
+	sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_PCS_A_WAIT);
+
+	 /*   poll for all lanes to get bitlock
+	  *
+	  *   This might be a very long time as the other end obviously needs to
+	  *   be coming up at the same time. So start eagerly checking and then back off.
+	  *
+	  *   If we are getting a high serdes error rate or bad eyes then we can never
+	  *   meaningfully lock so bail with an error and the serdes will be retuned.
+	  *
+	  *   Occasionally the pcs seems to get stuck and some lanes never come up.
+	  *   Restarting locking seems to clear this.
+	  */
+restart:
+	start_jiffy = jiffies;
+	while (!sbl_pml_pcs_locked(sbl, port_num)) {
+
+		if (sbl_start_timeout(sbl, port_num)) {
+			sbl_dev_dbg(sbl->dev, "%d: pcs lock wait timeout\n",
+				    port_num);
+			err = -ETIMEDOUT;
+			goto out;
+		}
+
+		if (sbl_base_link_start_cancelled(sbl, port_num)) {
+			sbl_dev_dbg(sbl->dev, "%d: pcs lock wait cancelled\n",
+				    port_num);
+			err = -ECANCELED;
+			goto out;
+		}
+
+		elapsed = jiffies_to_msecs(jiffies - start_jiffy);
+		if (elapsed > 1000) {
+
+			/* if we have a high serdes error, we will never align  */
+			if (sbl_pml_pcs_high_serdes_error(sbl, port_num)) {
+				sbl_dev_warn(sbl->dev, "%d: pcs lock high serdes error detected\n", port_num);
+				err = -EADV;
+				goto out;
+			}
+
+			/* Ensure the serdes is still good (eyes stay open)  */
+			err = sbl_port_check_eyes(sbl, port_num);
+			if (err) {
+				sbl_dev_warn(sbl->dev, "%d: pcs lock some eyes have gone bad", port_num);
+				goto out;
+			}
+
+			/* restart locking in case it's locked up */
+			sbl_pml_pcs_stop_lock(sbl, port_num);
+			sbl_pml_pcs_start_lock(sbl, port_num);
+
+			msleep(SBL_PML_PCS_ALIGN_SLOW_POLL_DELAY);
+		} else if (elapsed > 50)
+			msleep(100);
+		else
+			usleep_range(10000, 11000);
+	}
+
+	/* poll for lane alignment */
+	start_jiffy = jiffies;
+
+	while (!sbl_pml_pcs_aligned(sbl, port_num)) {
+
+		if (sbl_start_timeout(sbl, port_num)) {
+			sbl_dev_dbg(sbl->dev, "%d: pcs align wait timeout\n",
+				    port_num);
+			err = -ETIMEDOUT;
+			goto out;
+		}
+
+		if (sbl_base_link_start_cancelled(sbl, port_num)) {
+			sbl_dev_dbg(sbl->dev, "%d: pcs align wait cancelled\n",
+				    port_num);
+			err = -ECANCELED;
+			goto out;
+		}
+
+		/* if we lose lock restart trying to lock again  */
+		if (!sbl_pml_pcs_locked(sbl, port_num)) {
+			sbl_dev_warn(sbl->dev, "%d: pcs align - lost lock\n", port_num);
+			goto restart;
+		}
+
+		elapsed = jiffies_to_msecs(jiffies - start_jiffy);
+		if (elapsed > SBL_PML_PCS_ALIGN_TIMEOUT) {
+
+			/* we should have got alignment by now
+			 * give up, restart locking and try again
+			 */
+			sbl_pml_pcs_stop_lock(sbl, port_num);
+			sbl_pml_pcs_start_lock(sbl, port_num);
+			goto restart;
+		} else if (elapsed > 50)
+			msleep(100);
+		else
+			usleep_range(10000, 11000);
+	}
+
+out:
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_A_WAIT);
+
+	return err;
+}
+
+static bool sbl_pml_pcs_no_faults(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 faults;
+	u64 val64;
+
+	val64 = sbl_read64(sbl, base | SBL_PML_STS_RX_PCS_OFFSET);
+	faults = SBL_PML_STS_RX_PCS_FAULT_GET(val64) | SBL_PML_STS_RX_PCS_LOCAL_FAULT_GET(val64);
+
+	return (faults == 0ULL);
+}
+
+/* wait for pcs faults to clear
+ *
+ * we wait for fault to stay clear as it is momentarily set sometimes
+ */
+static int sbl_pml_pcs_fault_clear_wait(struct sbl_inst *sbl, int port_num)
+{
+	unsigned long start_jiffy = jiffies;
+	int no_fault_count = 0;
+	int elapsed;
+	int err;
+
+	sbl_dev_dbg(sbl->dev, "%d: pml pcs fault clear wait\n", port_num);
+
+	sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_PCS_F_WAIT);
+
+	while (true) {
+
+		/* check for timeout */
+		if (sbl_start_timeout(sbl, port_num)) {
+			sbl_dev_dbg(sbl->dev, "%d: pml fault clear wait timeout\n",
+				    port_num);
+			err = -ETIMEDOUT;
+			goto out;
+		}
+
+		if (sbl_base_link_start_cancelled(sbl, port_num)) {
+			sbl_dev_dbg(sbl->dev, "%d: pml fault clear wait cancelled\n",
+				    port_num);
+			err = -ECANCELED;
+			goto out;
+		}
+
+		/* check we are still aligned */
+		if (!sbl_pml_pcs_aligned(sbl, port_num)) {
+			sbl_dev_dbg(sbl->dev, "%d: pml fault clear wait lost alignment\n",
+				    port_num);
+			err = -ENOLCK;
+			goto out;
+		}
+
+		/* check for no fault
+		 * we need multiple good tests to be sure it is up
+		 */
+		if (sbl_pml_pcs_no_faults(sbl, port_num)) {
+			++no_fault_count;
+			if (no_fault_count == SBL_PML_REQUIRED_NO_FAULT_COUNT) {
+				/* done */
+				err = 0;
+				goto out;
+			} else {
+				/* poll fast and check again */
+				start_jiffy = jiffies;
+			}
+		} else
+			no_fault_count = 0;
+
+		/* wait with backoff */
+		elapsed = jiffies_to_msecs(jiffies - start_jiffy);
+		if (elapsed > 5000)
+			msleep(1000);
+		else if (elapsed > 100)
+			msleep(100);
+		else
+			usleep_range(10000, 11000);
+	}
+
+out:
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_F_WAIT);
+
+	return err;
+}
+
+/* PCS configuration */
+static int sbl_pml_pcs_config(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+	int err;
+	u64 en_lane_degrade_reg;
+
+	sbl_dev_dbg(sbl->dev, "%d: pcs config", port_num);
+
+	/* work out all the lane config and store in link db */
+	err = sbl_pml_pcs_determine_active_lanes(sbl, port_num);
+	if (err) {
+		sbl_dev_dbg(sbl->dev, "%d: pcs config lanes failed [%d]", port_num, err);
+		return err;
+	}
+
+	/* pcs must be off for setting  */
+	sbl_pml_pcs_stop(sbl, port_num);
+
+	/* clear out the previous active lanes and disable lock */
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET);
+	val64 = SBL_PML_CFG_RX_PCS_ACTIVE_LANES_UPDATE(val64, 0ULL);
+	val64 = SBL_PML_CFG_RX_PCS_ENABLE_LOCK_UPDATE(val64, 0ULL);
+	sbl_write64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET);
+
+	/* rapid alignment is broken so disable */
+	val64 = sbl_read64(sbl, base | SBL_PML_DBG_PCS_OFFSET);
+	val64 = SBL_PML_DBG_PCS_ENABLE_RAPID_ALIGNMENT_UPDATE(val64, 0ULL);
+	sbl_write64(sbl, base | SBL_PML_DBG_PCS_OFFSET, val64);
+
+	/* pcs config */
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET);
+	switch (link->link_mode) {
+	case SBL_LINK_MODE_BS_200G:
+		val64 = SBL_PML_CFG_PCS_PCS_MODE_UPDATE(val64, 0ULL);
+		break;
+	case SBL_LINK_MODE_BJ_100G:
+		val64 = SBL_PML_CFG_PCS_PCS_MODE_UPDATE(val64, 1ULL);
+		break;
+	case SBL_LINK_MODE_CD_100G:
+		val64 = SBL_PML_CFG_PCS_PCS_MODE_UPDATE(val64, 2ULL);
+		break;
+	case SBL_LINK_MODE_CD_50G:
+		val64 = SBL_PML_CFG_PCS_PCS_MODE_UPDATE(val64, 3ULL);
+		break;
+	default:
+		sbl_dev_dbg(sbl->dev, "%d: pcs link mode invalid (%d)", port_num, link->link_mode);
+		return -EBADRQC;
+	}
+	val64 = SBL_PML_CFG_PCS_ENABLE_AUTO_NEG_UPDATE(val64, 0ULL);
+	if ((link->blattr.options & SBL_OPT_LANE_DEGRADE) &&
+	    (link->link_mode == SBL_LINK_MODE_BS_200G))
+		val64 = SBL_PML_CFG_PCS_ENABLE_AUTO_LANE_DEGRADE_UPDATE(val64, 1ULL);
+	else
+		val64 = SBL_PML_CFG_PCS_ENABLE_AUTO_LANE_DEGRADE_UPDATE(val64, 0ULL);
+
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_OFFSET, val64);
+
+	/* pcs tx  */
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_TX_PCS_OFFSET);
+	val64 = SBL_PML_CFG_TX_PCS_ENABLE_CTL_OS_UPDATE(val64, 0ULL);
+	val64 = SBL_PML_CFG_TX_PCS_CDC_READY_LEVEL_UPDATE(val64,
+							  SBL_PML_CFG_TX_PCS_CDC_READY_LEVEL_DFLT);
+	val64 = SBL_PML_CFG_TX_PCS_GEARBOX_CREDITS_UPDATE(val64,
+							  SBL_PML_CFG_TX_PCS_GEARBOX_CREDITS_DFLT);
+	val64 = SBL_PML_CFG_TX_PCS_LANE_0_SOURCE_UPDATE(val64,
+		 sbl->switch_info->ports[port_num].serdes[0].tx_lane_source);
+	val64 = SBL_PML_CFG_TX_PCS_LANE_1_SOURCE_UPDATE(val64,
+		 sbl->switch_info->ports[port_num].serdes[1].tx_lane_source);
+	val64 = SBL_PML_CFG_TX_PCS_LANE_2_SOURCE_UPDATE(val64,
+		 sbl->switch_info->ports[port_num].serdes[2].tx_lane_source);
+	val64 = SBL_PML_CFG_TX_PCS_LANE_3_SOURCE_UPDATE(val64,
+		 sbl->switch_info->ports[port_num].serdes[3].tx_lane_source);
+	sbl_write64(sbl, base | SBL_PML_CFG_TX_PCS_OFFSET, val64);
+
+	/* pcs rx */
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET);
+	switch (link->blattr.fec_mode) {
+	case SBL_RS_MODE_OFF:
+		val64 = SBL_PML_CFG_RX_PCS_RS_MODE_UPDATE(val64, 0ULL);
+		break;
+	case SBL_RS_MODE_OFF_SYN:
+		val64 = SBL_PML_CFG_RX_PCS_RS_MODE_UPDATE(val64, 1ULL);
+		break;
+	case SBL_RS_MODE_OFF_CHK:
+		val64 = SBL_PML_CFG_RX_PCS_RS_MODE_UPDATE(val64, 2ULL);
+		break;
+	case SBL_RS_MODE_ON_SYN_MRK:
+		val64 = SBL_PML_CFG_RX_PCS_RS_MODE_UPDATE(val64, 3ULL);
+		break;
+	case SBL_RS_MODE_ON_CHK_SYN_MRK:
+		val64 = SBL_PML_CFG_RX_PCS_RS_MODE_UPDATE(val64, 4ULL);
+		break;
+	case SBL_RS_MODE_ON:
+		val64 = SBL_PML_CFG_RX_PCS_RS_MODE_UPDATE(val64, 5ULL);
+		break;
+	default:
+		sbl_dev_dbg(sbl->dev, "%d: pcs fec mode invalid (%d)", port_num, link->blattr.fec_mode);
+		return -EBADRQC;
+	}
+	val64 = SBL_PML_CFG_RX_PCS_ENABLE_CTL_OS_UPDATE(val64, 0ULL);                   /* ordered sets off */
+	val64 = SBL_PML_CFG_RX_PCS_HEALTH_BAD_SENSITIVITY_UPDATE(val64, 4ULL);          /* less sensitive */
+	val64 = SBL_PML_CFG_RX_PCS_ENABLE_RX_SM_UPDATE(val64,                           /* state machine */
+			(link->blattr.options & SBL_OPT_FABRIC_LINK) ? 0ULL : 1ULL);
+	en_lane_degrade_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET);
+	/* If we disable the following CSR, Auto Lane Degrade is enabled and
+	 * there is a concern regarding handling of short burst errors
+	 */
+	if (SBL_PML_CFG_PCS_ENABLE_AUTO_LANE_DEGRADE_GET(en_lane_degrade_reg))
+		val64 = SBL_PML_CFG_RX_PCS_RESTART_LOCK_ON_BAD_CWS_UPDATE(val64, 0ULL);     /* restart on 3 bad RS codewords - 0 with autodegrade enabled */
+	else
+		val64 = SBL_PML_CFG_RX_PCS_RESTART_LOCK_ON_BAD_CWS_UPDATE(val64, 1ULL);     /* restart on 3 bad RS codewords - 1 with autodegrade disabled */
+	val64 = SBL_PML_CFG_RX_PCS_RESTART_LOCK_ON_BAD_AMS_UPDATE(val64,                /* restart on 5 bad AMs */
+			(link->link_mode == SBL_LINK_MODE_BS_200G) ? 1ULL : 0ULL);
+	val64 = SBL_PML_CFG_RX_PCS_ACTIVE_LANES_UPDATE(val64, link->active_rx_lanes);
+	val64 = SBL_PML_CFG_RX_PCS_ENABLE_LOCK_UPDATE(val64, 0ULL);                     /* pcs disable lock  (really enable alignment process) */
+	sbl_write64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET, val64);
+
+	link->pcs_config = true;
+
+	return 0;
+}
+
+/* Enables auto lane degrading in the hardware.
+ *
+ * This function can also be called if the AM_LOCK is observed on some lanes
+ * but not all and the timeout has expired. This will allow auto lane degrade
+ * feature to be used when not all lanes are available at the initial link
+ * bringup.
+ */
+void sbl_pml_pcs_enable_auto_lane_degrade(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64, en_lane_degrade_reg;
+	unsigned long timeout;
+	int err = 0;
+
+	en_lane_degrade_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET);
+	if (SBL_PML_CFG_PCS_ENABLE_AUTO_LANE_DEGRADE_GET(en_lane_degrade_reg)) {
+		timeout = jiffies + usecs_to_jiffies(HM_TIMEOUT);
+		while (!sbl_pml_rx_pls_available(sbl, port_num)) {
+			if (time_after(jiffies, timeout)) {
+				sbl_dev_dbg(sbl->dev, "%d: timeout for rx pls available\n", port_num);
+				err = -ETIMEDOUT;
+				goto out;
+			}
+		}
+		timeout = jiffies + usecs_to_jiffies(HM_TIMEOUT);
+		while (!sbl_pml_lp_pls_available(sbl, port_num)) {
+			if (time_after(jiffies, timeout)) {
+				sbl_dev_dbg(sbl->dev, "%d: timeout for lp pls available\n", port_num);
+				err = -ETIMEDOUT;
+				goto out;
+			}
+		}
+
+		val64 = sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET);
+		val64 = SBL_PML_CFG_RX_PCS_ALLOW_AUTO_DEGRADE_UPDATE(val64, 1ULL);
+		sbl_write64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET, val64);
+
+		val64 = sbl_read64(sbl, base | SBL_PML_CFG_TX_PCS_OFFSET);
+		val64 = SBL_PML_CFG_TX_PCS_ALLOW_AUTO_DEGRADE_UPDATE(val64, 1ULL);
+		sbl_write64(sbl, base | SBL_PML_CFG_TX_PCS_OFFSET, val64);
+		sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET); /* flush */
+		sbl_dev_dbg(sbl->dev, "auto lane degrade is enabled");
+	}
+out:
+	if (err) {
+		sbl_dev_err(sbl->dev, "%d: auto lane degrade is not enabled", port_num);
+		en_lane_degrade_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET);
+		en_lane_degrade_reg = SBL_PML_CFG_PCS_ENABLE_AUTO_LANE_DEGRADE_UPDATE(en_lane_degrade_reg, 0ULL);
+		sbl_write64(sbl, base | SBL_PML_CFG_PCS_OFFSET, en_lane_degrade_reg);
+
+		val64 = sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET);
+		val64 = SBL_PML_CFG_RX_PCS_RESTART_LOCK_ON_BAD_CWS_UPDATE(val64, 1ULL);
+		sbl_write64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET, val64);
+		sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET); /* flush */
+		sbl_dev_dbg(sbl->dev, "Restart lock on bad CWS set back to 1 since couldn't enable ALD\n");
+	}
+}
+
+bool sbl_pml_rx_pls_available(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+	u64 sts_pcs_lane_degrade_reg;
+
+	sts_pcs_lane_degrade_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_LANE_DEGRADE_OFFSET);
+	val64 = SBL_PML_STS_PCS_LANE_DEGRADE_RX_PLS_AVAILABLE_GET(sts_pcs_lane_degrade_reg);
+
+	return (val64 == MAX_PLS_AVAILABLE);
+}
+
+bool sbl_pml_lp_pls_available(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+	u64 sts_pcs_lane_degrade_reg;
+
+	sts_pcs_lane_degrade_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_LANE_DEGRADE_OFFSET);
+	val64 = SBL_PML_STS_PCS_LANE_DEGRADE_LP_PLS_AVAILABLE_GET(sts_pcs_lane_degrade_reg);
+
+	return (val64 == MAX_PLS_AVAILABLE);
+}
+
+/* PCS start-up */
+void sbl_pml_pcs_start(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+	u32 val32;
+
+	sbl_dev_dbg(sbl->dev, "%d: pcs start", port_num);
+
+	/* we need updated AMs for 200GHz mode */
+	val32 = SBL_PML_CFG_PCS_AMS_DFLT;
+	if (link->link_mode == SBL_LINK_MODE_BS_200G)
+		val32 = SBL_PML_CFG_PCS_AMS_USE_PROGRAMMABLE_AMS_UPDATE(
+				val32, 1ULL);
+	else
+		val32 = SBL_PML_CFG_PCS_AMS_USE_PROGRAMMABLE_AMS_UPDATE(
+				val32, 0ULL);
+	sbl_write32(sbl, base | SBL_PML_CFG_PCS_AMS_OFFSET, val32);
+
+	/* start pcs */
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET);
+	val64 = SBL_PML_CFG_PCS_PCS_ENABLE_UPDATE(val64, 1ULL);
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET); /* flush */
+
+	/* record if we are trying to align */
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET);
+	if (SBL_PML_CFG_RX_PCS_ENABLE_LOCK_GET(val64))
+		sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_PCS_ALIGN_EN);
+	else
+		sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_ALIGN_EN);
+}
+
+void sbl_pml_pcs_enable_alignment(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: pcs enable alignment", port_num);
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET);
+	val64 = SBL_PML_CFG_RX_PCS_ENABLE_LOCK_UPDATE(val64, 1ULL);
+	sbl_write64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET, val64);
+
+	/* alignment process is underway */
+	sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_PCS_ALIGN_EN);
+}
+
+void sbl_pml_pcs_disable_alignment(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: pcs disable alignment", port_num);
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET);
+	val64 = SBL_PML_CFG_RX_PCS_ENABLE_LOCK_UPDATE(val64, 0ULL);
+	sbl_write64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET, val64);
+
+	/* alignment process is stopped */
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_ALIGN_EN);
+}
+
+/* remote fault.
+ *
+ * there is a debug reg we can use to send remote fault to our link partner
+ */
+void sbl_pml_pcs_set_tx_rf(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: pcs set tx rf", port_num);
+
+	val64 = sbl_read64(sbl, base | SBL_PML_DBG_PCS_OFFSET);
+	val64 = SBL_PML_DBG_PCS_FORCE_TX_DATA_UPDATE(val64, 1ULL);
+	val64 = SBL_PML_DBG_PCS_FORCE_TX_DATA_RF_UPDATE(val64, 1ULL);
+	sbl_write64(sbl, base | SBL_PML_DBG_PCS_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_DBG_PCS_OFFSET);
+
+	sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_PCS_TX_RF);
+}
+
+void sbl_pml_pcs_clear_tx_rf(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	sbl_dev_dbg(sbl->dev, "%d: pcs clear tx rf", port_num);
+
+	val64 = sbl_read64(sbl, base | SBL_PML_DBG_PCS_OFFSET);
+	val64 = SBL_PML_DBG_PCS_FORCE_TX_DATA_UPDATE(val64, 0ULL);
+	val64 = SBL_PML_DBG_PCS_FORCE_TX_DATA_RF_UPDATE(val64, 0ULL);
+	sbl_write64(sbl, base | SBL_PML_DBG_PCS_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_DBG_PCS_OFFSET);
+
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_TX_RF);
+}
+
+/*   wait for the pcs to come up
+ *
+ *   i.e for it to be locked and aligned and faults to have cleared
+ *
+ *
+ */
+int sbl_pml_pcs_wait(struct sbl_inst *sbl, int port_num)
+{
+	char pcs_state_str[SBL_PCS_STATE_STR_LEN];
+	int err;
+
+	sbl_dev_dbg(sbl->dev, "%d: pcs wait", port_num);
+
+	/* start locking */
+	sbl_pml_pcs_start_lock(sbl, port_num);
+
+	/* clear forcing remote fault.
+	 * (it will continue to be set until PCS is actually ready)
+	 */
+	sbl_pml_pcs_clear_tx_rf(sbl, port_num);
+
+	/* keep trying to bring the pcs up until we timeout */
+	while (true) {
+
+		/* wait for alignment */
+		err = sbl_pml_pcs_alignment_wait(sbl, port_num);
+		switch (err) {
+		case 0:
+			break;
+
+		case -ETIMEDOUT:
+			/* out of time, dump state and give up  */
+			sbl_dev_err(sbl->dev, "%d: pcs_wait alignment timeout (%s)\n", port_num,
+				    sbl_pml_pcs_state_str(sbl, port_num, pcs_state_str,
+							  SBL_PCS_STATE_STR_LEN));
+			goto out;
+
+		case -ECANCELED:
+			sbl_dev_dbg(sbl->dev, "%d: pcs_wait alignment failed [%d]\n", port_num, err);
+			goto out;
+
+		default:
+			/* something unexpected */
+			sbl_dev_err(sbl->dev, "%d: pcs_wait alignment failed [%d]\n", port_num, err);
+			goto out;
+		}
+
+		/* wait for faults to clear */
+		err = sbl_pml_pcs_fault_clear_wait(sbl, port_num);
+		switch (err) {
+
+		case 0:
+			goto out;
+
+		case -ENOLCK:
+			/* alignment lost - try to align again  */
+			sbl_dev_warn(sbl->dev, "%d: pcs_wait alignment lost - restart (%s)\n", port_num,
+				     sbl_pml_pcs_state_str(sbl, port_num, pcs_state_str,
+							   SBL_PCS_STATE_STR_LEN));
+			continue;
+
+		case -ETIMEDOUT:
+			sbl_dev_err(sbl->dev, "%d: pcs_wait fault clear timeout (%s)\n", port_num,
+				    sbl_pml_pcs_state_str(sbl, port_num, pcs_state_str,
+							  SBL_PCS_STATE_STR_LEN));
+			goto out;
+
+		case -ECANCELED:
+			sbl_dev_dbg(sbl->dev, "%d: pcs_wait fault clear cancelled\n", port_num);
+			goto out;
+
+		default:
+			/* something unexpected */
+			sbl_dev_err(sbl->dev, "%d: pcs_wait fault clear failed [%d]\n", port_num, err);
+			goto out;
+		}
+	}
+
+out:
+	/* drop all stage info */
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_ALIGN_EN);
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_A_WAIT);
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_F_WAIT);
+
+	if (err)
+		return err;
+
+	/* clear all the PCS related error flags */
+	sbl_pml_err_flgs_clear(sbl, port_num, SBL_PML_ALL_PCS_ERR_FLGS);
+
+	/* note pcs is up */
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_TX_AM);
+	sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_PCS_UP);
+
+	return 0;
+}
+
+char *sbl_pml_pcs_state_str(struct sbl_inst *sbl, int port_num, char *buf, int len)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	if (!buf || !len)
+		return "";
+
+	val64 = sbl_read64(sbl, base | SBL_PML_STS_RX_PCS_OFFSET);
+	snprintf(buf, len, "lk %llx/%.2x, a%lld f%lld lf%lld hs%lld",
+		 SBL_PML_STS_RX_PCS_AM_LOCK_GET(val64),
+			link->active_fec_lanes,
+			SBL_PML_STS_RX_PCS_ALIGN_STATUS_GET(val64),
+			SBL_PML_STS_RX_PCS_FAULT_GET(val64),
+			SBL_PML_STS_RX_PCS_LOCAL_FAULT_GET(val64),
+			SBL_PML_STS_RX_PCS_HI_SER_GET(val64));
+	return buf;
+}
+
+/**
+ * sbl_pml_pcs_recovery_enable() - Enable PCS recovery
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Context: Process context, Acquires lock and release
+ * pcs_recovery_lock <spin_lock>
+ */
+void sbl_pml_pcs_recovery_enable(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link;
+
+	link = sbl->link + port_num;
+
+	sbl_dev_dbg(sbl->dev, "%d: PCS recovery enable", port_num);
+
+	spin_lock(&link->pcs_recovery_lock);
+	link->pcs_recovery_flag = true;
+	spin_unlock(&link->pcs_recovery_lock);
+}
+EXPORT_SYMBOL(sbl_pml_pcs_recovery_enable);
+
+/**
+ * sbl_pml_pcs_recovery_disable() - Disable PCS recovery
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Context: Process context, Acquires lock and release
+ * pcs_recovery_lock <spin_lock>
+ */
+void sbl_pml_pcs_recovery_disable(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link;
+
+	link = sbl->link + port_num;
+
+	sbl_dev_dbg(sbl->dev, "%d: PCS recovery disable", port_num);
+
+	spin_lock(&link->pcs_recovery_lock);
+	link->pcs_recovery_flag = false;
+	spin_unlock(&link->pcs_recovery_lock);
+}
+EXPORT_SYMBOL(sbl_pml_pcs_recovery_disable);
+
+/**
+ * sbl_pml_pcs_aligned() - Check pcs align status
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Return: status of pcs align
+ */
+bool sbl_pml_pcs_aligned(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	val64 = sbl_read64(sbl, base | SBL_PML_STS_RX_PCS_OFFSET);
+
+	return (SBL_PML_STS_RX_PCS_ALIGN_STATUS_GET(val64) == 1);
+}
+EXPORT_SYMBOL(sbl_pml_pcs_aligned);
+
+bool sbl_pml_pcs_high_serdes_error(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	val64 = sbl_read64(sbl, base | SBL_PML_STS_RX_PCS_OFFSET);
+	return SBL_PML_STS_RX_PCS_HI_SER_GET(val64);
+}
+
+bool sbl_pml_pcs_up(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	if (!sbl->is_hw)
+		return true;
+
+	val64 = sbl_read64(sbl, base | SBL_PML_STS_RX_PCS_OFFSET);
+
+	return SBL_PML_STS_RX_PCS_ALIGN_STATUS_GET(val64) &&
+			!SBL_PML_STS_RX_PCS_FAULT_GET(val64) &&
+			!SBL_PML_STS_RX_PCS_LOCAL_FAULT_GET(val64) &&
+			!SBL_PML_STS_RX_PCS_HI_SER_GET(val64);
+}
+
+bool sbl_pml_recovery_no_faults(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	struct sbl_link *link = sbl->link + port_num;
+	u64 val64;
+
+	val64 = sbl_read64(sbl, base | SBL_PML_STS_RX_PCS_OFFSET);
+
+	sbl_dev_dbg(sbl->dev, "%d:sbl_pml_recovery fault info val=0x%llx aml=0x%llx fecl:0x%x",
+		    port_num, val64, SBL_PML_STS_RX_PCS_AM_LOCK_GET(val64), link->active_fec_lanes);
+
+	return SBL_PML_STS_RX_PCS_ALIGN_STATUS_GET(val64) &&
+			!SBL_PML_STS_RX_PCS_FAULT_GET(val64) &&
+			!SBL_PML_STS_RX_PCS_LOCAL_FAULT_GET(val64) &&
+			!SBL_PML_STS_RX_PCS_HI_SER_GET(val64) &&
+			(SBL_PML_STS_RX_PCS_AM_LOCK_GET(val64) == link->active_fec_lanes);
+}
+
+void sbl_pml_recovery_log_pcs_status(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	val64 = sbl_read64(sbl, base | SBL_PML_STS_RX_PCS_DESKEW_DEPTHS_OFFSET);
+	sbl_dev_info(sbl->dev, "%d: SBL_PML_STS_RX_PCS_DESKEW_DEPTHS_OFFSET = 0x%llx",
+		     port_num, val64);
+
+	val64 = sbl_read64(sbl, base | SBL_PML_STS_RX_PCS_AM_MATCH_OFFSET);
+	sbl_dev_info(sbl->dev, "%d: SBL_PML_STS_RX_PCS_AM_MATCH_OFFSET = 0x%llx",
+		     port_num, val64);
+
+	val64 = sbl_read64(sbl, base | SBL_PML_STS_RX_PCS_FECL_SOURCES_OFFSET);
+	sbl_dev_info(sbl->dev, "%d: SBL_PML_STS_RX_PCS_FECL_SOURCES_OFFSET = 0x%llx",
+		     port_num, val64);
+}
+
+/* stop the pcs */
+void sbl_pml_pcs_stop(struct sbl_inst *sbl, int port_num)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	struct sbl_link *link = sbl->link + port_num;
+	u64 val64;
+
+	if (sbl_debug_option(sbl, port_num, SBL_DEBUG_KEEP_SERDES_UP)) {
+		sbl_dev_err(sbl->dev, "%d: KEEP SERDES UP", port_num);
+		return;
+	}
+
+	sbl_dev_dbg(sbl->dev, "%d: pml pcs stop\n", port_num);
+
+	if ((link->sstate != SBL_SERDES_STATUS_DOWN) &&
+	    (link->mattr.media == SBL_LINK_MEDIA_OPTICAL)) {
+		sbl_dev_err(sbl->dev, "%d: not stopping pcs because serdes is running",
+			    port_num);
+		return;
+	}
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET);
+	val64 = SBL_PML_CFG_PCS_PCS_ENABLE_UPDATE(val64, 0ULL);
+	sbl_write64(sbl, base | SBL_PML_CFG_PCS_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET); /* flush */
+
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_TX_AM);
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_ALIGN_EN);
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_A_WAIT);
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_F_WAIT);
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_UP);
+}
+
+/* enable/disable pcs sending ordered sets */
+void sbl_pml_pcs_ordered_sets(struct sbl_inst *sbl,
+			      int port_num, int enable)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+	u64 state = (enable) ? 1ULL : 0ULL;
+
+	sbl_dev_dbg(sbl->dev, "%d: pml pcs ordered sets - %s\n", port_num,
+		    (enable) ? "start" : "stop");
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET);
+	val64 = SBL_PML_CFG_RX_PCS_ENABLE_CTL_OS_UPDATE(val64, state);
+	sbl_write64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET, val64);
+
+	val64 = sbl_read64(sbl, base | SBL_PML_CFG_TX_PCS_OFFSET);
+	val64 = SBL_PML_CFG_TX_PCS_ENABLE_CTL_OS_UPDATE(val64, state);
+	sbl_write64(sbl, base | SBL_PML_CFG_TX_PCS_OFFSET, val64);
+
+	/* pci flush */
+	sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET);
+
+	if (enable)
+		sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_OS);
+	else
+		sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_OS);
+}
+
+/*   Start/Stop sending am.
+ *
+ *   Start: configures and starts the PCS and then immediately disables the alignment
+ *   locking process
+ *
+ *   Stop: just switched off the PCS
+ */
+int  sbl_pml_pcs_am_start(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link;
+	int err;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return err;
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return err;
+
+	link = sbl->link + port_num;
+
+	if (!(link->blstate & (SBL_BASE_LINK_STATUS_STARTING | SBL_BASE_LINK_STATUS_STARTING))) {
+		sbl_dev_err(sbl->dev, "pcs_am_start %d: wrong state (%s)\n", port_num,
+			    sbl_link_state_str(link->blstate));
+		return -ENAVAIL;
+	}
+
+	/* configuring will stop the pcs
+	 * this might perturb link partner when tuning so report we do this
+	 */
+	if (READ_ONCE(link->link_info) & SBL_LINK_INFO_PCS_TX_AM)
+		sbl_dev_dbg(sbl->dev, "%d pcs_am_start: tx am restart\n", port_num);
+
+	err = sbl_pml_pcs_config(sbl, port_num);
+	if (err) {
+		sbl_dev_err(sbl->dev, "pcs_config %d: failed [%d]\n", port_num, err);
+		return err;
+	}
+
+	/* send remote fault to the link partner */
+	sbl_pml_pcs_set_tx_rf(sbl, port_num);
+
+	/* start the pcs - which will start it sending AM */
+	sbl_pml_pcs_start(sbl, port_num);
+
+	/* if pcs is running it will be sending AMs */
+	sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_PCS_TX_AM);
+
+	/* but definitely not doing any of this yet */
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_ANEG);
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_A_WAIT);
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_F_WAIT);
+	sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_UP);
+
+	return 0;
+}
+
+#ifdef CONFIG_SYSFS
+/**
+ * sbl_pml_pcs_sysfs_sprint() - Format serdes info into buffer
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @buf: Destination buffer to write the data
+ * @size: Size of @buf in bytes, the output is truncated to fit
+ *
+ * This function checks the pcs_config flag and based on that
+ * it will format serdes info string into buffer.
+ *
+ * Context: Process context, Acquires lock and release
+ * link->lock <spin_lock>
+ *
+ * Return: Number of characters written, at most @size - 1
+ */
+int sbl_pml_pcs_sysfs_sprint(struct sbl_inst *sbl, int port_num, char *buf, size_t size)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	char state_str[SBL_PCS_STATE_STR_LEN];
+	int s = 0;
+
+	spin_lock(&link->lock);
+	if (link->pcs_config) {
+		s += scnprintf(buf + s, size - s, "pcs:  %s (%d %d %d %d) rx %x %s\n",
+				sbl_link_mode_str(link->link_mode),
+				sbl->switch_info->ports[port_num].serdes[0].tx_lane_source,
+				sbl->switch_info->ports[port_num].serdes[1].tx_lane_source,
+				sbl->switch_info->ports[port_num].serdes[2].tx_lane_source,
+				sbl->switch_info->ports[port_num].serdes[3].tx_lane_source,
+				link->active_rx_lanes,
+				sbl_pml_pcs_state_str(sbl, port_num, state_str, SBL_PCS_STATE_STR_LEN));
+	}
+	spin_unlock(&link->lock);
+
+	return s;
+}
+EXPORT_SYMBOL(sbl_pml_pcs_sysfs_sprint);
+
+/**
+ * sbl_pml_pcs_lane_degrade_sysfs_sprint() - Formats PCS ALD,TX/RX lane degrade into buffer
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @buf: Destination buffer to write the data
+ * @size: Size of @buf in bytes, the output is truncated to fit
+ *
+ * This function formats PCS ALD/(TX/RX) lane degrade string into buffer
+ * based on the flag 'pcs_config'.
+ *
+ * Context: Process context, Acquires lock and release
+ * link->lock <spin_lock>
+ *
+ * Return: Number of characters written, at most @size - 1
+ */
+int sbl_pml_pcs_lane_degrade_sysfs_sprint(struct sbl_inst *sbl, int port_num, char *buf, size_t size)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	int s = 0;
+	u32 base = SBL_PML_BASE(port_num);
+	u64 sts_pcs_lane_degrade_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_LANE_DEGRADE_OFFSET);
+	u64 cfg_pcs_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET);
+
+	spin_lock(&link->lock);
+	if (link->pcs_config) {
+		s += scnprintf(buf + s, size - s, "ALD: %lld - TX: 0x%llx - RX: 0x%llx\n",
+				SBL_PML_CFG_PCS_ENABLE_AUTO_LANE_DEGRADE_GET(cfg_pcs_reg),
+				SBL_PML_STS_PCS_LANE_DEGRADE_LP_PLS_AVAILABLE_GET(sts_pcs_lane_degrade_reg),
+				SBL_PML_STS_PCS_LANE_DEGRADE_RX_PLS_AVAILABLE_GET(sts_pcs_lane_degrade_reg));
+	}
+	spin_unlock(&link->lock);
+
+	return s;
+}
+EXPORT_SYMBOL(sbl_pml_pcs_lane_degrade_sysfs_sprint);
+#endif
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_pml_pcs_nic.c b/drivers/net/ethernet/hpe/sbl/sbl_pml_pcs_nic.c
new file mode 100644
index 000000000000..220039733053
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_pml_pcs_nic.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2025 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include <linux/hpe/sbl/sbl_pml.h>
+
+#include "sbl_pml_fn.h"
+#include "sbl_link.h"
+#include "sbl_serdes_map.h"
+#include "sbl_internal.h"
+#include "sbl_serdes_fn.h"
+
+#ifdef CONFIG_SYSFS
+/**
+ * sbl_pml_pcs_speed_sysfs_sprint() - Report PCS mode into buffer
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @buf: Destination buffer to write the data
+ * @size: Size of @buf in bytes, the output is truncated to fit
+ *
+ * Based on PCS config, this function writes the link mode into
+ * the provided buffer
+ *
+ * Context: Process context, Acquires lock and release link->lock <spin_lock>
+ *
+ * Return: Number of characters written, at most @size - 1
+ */
+int sbl_pml_pcs_speed_sysfs_sprint(struct sbl_inst *sbl, int port_num, char *buf, size_t size)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	int s = 0;
+
+	spin_lock(&link->lock);
+	if (link->pcs_config)
+		s += scnprintf(buf + s, size - s, "%s", sbl_link_mode_str(link->link_mode));
+	else
+		s += scnprintf(buf + s, size - s, "NA");
+	spin_unlock(&link->lock);
+
+	return s;
+}
+EXPORT_SYMBOL(sbl_pml_pcs_speed_sysfs_sprint);
+#endif
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_pml_serdes_op.c b/drivers/net/ethernet/hpe/sbl/sbl_pml_serdes_op.c
new file mode 100644
index 000000000000..4ade8b13b398
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_pml_serdes_op.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright 2019-2023,2025 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include <linux/hpe/sbl/sbl_pml.h>
+
+#include "sbl_serdes.h"
+#include "sbl_internal.h"
+
+static inline bool sbl_pml_serdes_op_busy(struct sbl_inst *sbl, int port_num)
+{
+	u64 val64;
+
+	val64 = sbl_read64(sbl, SBL_PML_BASE(port_num) |
+			SBL_PML_SERDES_CORE_INTERRUPT_OFFSET);
+	return SBL_PML_SERDES_CORE_INTERRUPT_DO_CORE_INTERRUPT_GET(val64);
+}
+
+/**
+ * sbl_pml_serdes_op() - Perform a serdes interrupt configuration
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @serdes_sel: Value
+ * @op: Read serdes version
+ * @data: Set to zero
+ * @result: Pass this value to caller
+ * @timeout: Time set used for polling
+ * @flags: Used to set US/MSec delay
+ *
+ * This function validates the instance and port number, picks
+ * polling interval from the provided flags and executes SERDES
+ * core interrupt configuration. It polls the completion or timeout
+ * for this operation and returns success or negative on failure
+ *
+ * Context: May sleep based on access to serdes
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int sbl_pml_serdes_op(struct sbl_inst *sbl, int port_num, u64 serdes_sel,
+		      u64 op, u64 data, u16 *result, int timeout,
+		      unsigned int flags)
+{
+	struct sbl_link *link;
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+	int delay;
+	int poll_interval;
+	unsigned long last_jiffy;
+	int err;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return err;
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return err;
+
+	sbl_dev_dbg(sbl->dev, "serdes op, p%ds%lld, %lld, %lld, %d 0x%x\n",
+		    port_num, serdes_sel, op, data, timeout, flags);
+
+	if (!result)
+		return -EINVAL;
+
+	/* cannot wait infinitely */
+	if (timeout <= 0)
+		return -EINVAL;
+
+	/* a polling interval is mandatory */
+	poll_interval = sbl_flags_get_poll_interval_from_flags(flags);
+	if (!poll_interval)
+		return -EINVAL;
+
+	delay = sbl_flags_get_delay_from_flags(flags);
+
+	link = sbl->link + port_num;
+	err = mutex_lock_interruptible(&link->serdes_mtx);
+	if (err)
+		return -ERESTARTSYS;
+
+	if (sbl_pml_serdes_op_busy(sbl, port_num)) {
+		mutex_unlock(&link->serdes_mtx);
+		return -EBUSY;
+	}
+
+	/* start the operation  */
+	val64 = SBL_PML_SERDES_CORE_INTERRUPT_SET(serdes_sel, 1ULL, op, data);
+	sbl_write64(sbl, base | SBL_PML_SERDES_CORE_INTERRUPT_OFFSET, val64);
+	sbl_read64(sbl, base | SBL_PML_SERDES_CORE_INTERRUPT_OFFSET); /* flush */
+
+	if (delay)
+		udelay(delay);
+
+	/* poll for completion or timeout */
+	last_jiffy = jiffies + msecs_to_jiffies(timeout) + 1;
+	while (sbl_pml_serdes_op_busy(sbl, port_num)) {
+		if (time_is_before_jiffies(last_jiffy)) {
+			mutex_unlock(&link->serdes_mtx);
+			return -ETIMEDOUT;
+		}
+		msleep(poll_interval);
+	}
+
+	/* get the result */
+	val64 = sbl_read64(sbl, base | SBL_PML_SERDES_CORE_INTERRUPT_OFFSET);
+	*result = SBL_PML_SERDES_CORE_INTERRUPT_CORE_INTERRUPT_DATA_GET(val64);
+
+	mutex_unlock(&link->serdes_mtx);
+	return 0;
+}
+EXPORT_SYMBOL(sbl_pml_serdes_op);
+
+/**
+ * sbl_pml_serdes_op_timing() - serdes core interrupt access timings
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @capture: Value to capture the interrupt delay
+ * @clear: Value to clear interrupt delay
+ * @set: Value to set interrupt delay
+ *
+ * Return: 0 on success
+ */
+int sbl_pml_serdes_op_timing(struct sbl_inst *sbl, int port_num, u64 capture,
+			     u64 clear, u64 set)
+{
+	u32 base = SBL_PML_BASE(port_num);
+	u64 val64;
+
+	val64 = SBL_PML_CFG_SERDES_CORE_INTERRUPT_CAPTURE_INTERRUPT_DATA_DELAY_SET(capture) |
+		SBL_PML_CFG_SERDES_CORE_INTERRUPT_CLEAR_INTERRUPT_DELAY_SET(clear) |
+		SBL_PML_CFG_SERDES_CORE_INTERRUPT_SET_INTERRUPT_DELAY_SET(set);
+
+	sbl_write64(sbl, base | SBL_PML_CFG_SERDES_CORE_INTERRUPT_OFFSET,
+		    val64);
+	return 0;
+}
+EXPORT_SYMBOL(sbl_pml_serdes_op_timing);

-- 
2.56.0


  parent reply	other threads:[~2026-10-30 21:00 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-30 20:58 [PATCH 00/23] Subject: [PATCH net-next 00/23] net/hpe: add HPE Slingshot NIC drivers Doug Ledford
2026-10-30 20:58 ` [PATCH 01/23] net: hpe: ss1: add the Cassini headers Doug Ledford
2026-10-31 13:58   ` Andrew Lunn
2026-10-31 19:34     ` Doug Ledford
2026-10-30 20:58 ` [PATCH 02/23] net: hpe: sbl: add the Slingshot Base Link headers Doug Ledford
2026-10-30 20:58 ` [PATCH 03/23] net: hpe: sbl: add the core instance and configuration layer Doug Ledford
2026-10-30 20:59 ` [PATCH 04/23] net: hpe: sbl: add the SerDes layer Doug Ledford
2026-10-30 20:59 ` Doug Ledford [this message]
2026-10-30 20:59 ` [PATCH 06/23] net: hpe: sbl: add link state management Doug Ledford
2026-10-30 20:59 ` [PATCH 07/23] net: hpe: sbl: add the SBM serial bus layer Doug Ledford
2026-10-30 20:59 ` [PATCH 08/23] net: hpe: sbl: add media, FEC and counter support Doug Ledford
2026-10-30 20:59 ` [PATCH 09/23] net: hpe: sl: add the Slingshot Link headers Doug Ledford
2026-10-30 20:59 ` [PATCH 10/23] net: hpe: sl: add the module entry points Doug Ledford
2026-10-30 20:59 ` [PATCH 11/23] net: hpe: sl: add the core link state machines Doug Ledford
2026-10-30 20:59 ` [PATCH 12/23] net: hpe: sl: add the control layer Doug Ledford
2026-10-30 20:59 ` [PATCH 13/23] net: hpe: sl: add media and cable handling Doug Ledford
2026-10-30 20:59 ` [PATCH 14/23] net: hpe: sl: add the sysfs interface Doug Ledford
2026-10-30 20:59 ` [PATCH 15/23] net: hpe: sl: add the NIC binding and logging helpers Doug Ledford
2026-10-30 20:59 ` [PATCH 16/23] net: hpe: ss1: add the Cassini core device layer Doug Ledford
2026-10-30 20:59 ` [PATCH 17/23] net: hpe: ss1: add the datapath and memory translation layers Doug Ledford
2026-10-30 20:59 ` [PATCH 18/23] net: hpe: ss1: add the link layer glue Doug Ledford
2026-10-30 20:59 ` [PATCH 19/23] net: hpe: ss1: add SR-IOV support Doug Ledford
2026-10-30 20:59 ` [PATCH 20/23] net: hpe: ss1: add the CXI core interfaces Doug Ledford
2026-10-30 20:59 ` [PATCH 21/23] net: hpe: ss1: add resource groups, QoS and services Doug Ledford
2026-10-30 20:59 ` [PATCH 22/23] net: hpe: ss1: add the Ethernet personality Doug Ledford
2026-10-30 20:59 ` [PATCH 23/23] net: hpe: wire up the Slingshot NIC drivers Doug Ledford
2026-10-30 22:11 ` [PATCH 00/23] Subject: [PATCH net-next 00/23] net/hpe: add HPE " Jakub Kicinski
2026-10-31 20:17   ` Andrew Lunn
2026-11-02  1:02     ` Doug Ledford
2026-11-02  1:35       ` Andrew Lunn
2026-11-02  3:45         ` Doug Ledford
2026-11-02  3:01       ` Guenter Roeck

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=20261030-cxi-netdev-submission-v1-5-8c2c391cc283@hpe.com \
    --to=doug.ledford@hpe.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=christian.koenig@amd.com \
    --cc=davem@davemloft.net \
    --cc=edumazet@kernel.org \
    --cc=kuba@kernel.org \
    --cc=linux-kbuild@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=nathan@kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=nsc@kernel.org \
    --cc=pabeni@redhat.com \
    --cc=richardcochran@gmail.com \
    --cc=sandeep.bhoot@hpe.com \
    --cc=santosh-kumar.mamila@hpe.com \
    --cc=slingshot-os-team@groups.ext.hpe.com \
    --cc=sumit.semwal@linaro.org \
    /path/to/YOUR_REPLY

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

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

all inboxes | Powered by JetHome®