Thread (28 messages) 28 messages, 3 authors, 1d ago

[PATCH 08/23] net: hpe: sbl: add media, FEC and counter support

flat view
WARM1d

From: Doug Ledford <hidden>
Date: 2026-10-30 21:01:03
Also in: linux-kbuild, lkml
Subsystem: networking drivers, the rest · Maintainers: Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds

Add media detection, FEC (forward error correction) configuration and
the link counters.  sbl_platform.h is updated for the media fields
added here.

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 (5)
- CHECK: BRACES (16), UNNECESSARY_PARENTHESES (10)

Co-developed-by: Sandeep Bhoot <redacted>
Signed-off-by: Sandeep Bhoot <redacted>
Co-developed-by: Mamila Santosh <redacted>
Signed-off-by: Mamila Santosh <redacted>
Signed-off-by: Doug Ledford <redacted>
---
 drivers/net/ethernet/hpe/sbl/sbl_counters.c  | 137 ++++
 drivers/net/ethernet/hpe/sbl/sbl_fec.c       | 971 +++++++++++++++++++++++++++
 drivers/net/ethernet/hpe/sbl/sbl_fec_nic.c   |  36 +
 drivers/net/ethernet/hpe/sbl/sbl_media.c     | 482 +++++++++++++
 drivers/net/ethernet/hpe/sbl/sbl_media_nic.c |  47 ++
 5 files changed, 1673 insertions(+)
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_counters.c b/drivers/net/ethernet/hpe/sbl/sbl_counters.c
new file mode 100644
index 000000000000..90feb6436105
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_counters.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2019, 2022 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+
+#include <linux/hpe/sbl/sbl.h>
+
+#include "sbl_internal.h"
+
+/* create and initialise the SBL link's counter array */
+int sbl_link_counters_init(struct sbl_link *link)
+{
+	int i;
+
+	if (!link->counters) {
+		link->counters = kzalloc(sizeof(atomic_t) * SBL_LINK_NUM_COUNTERS,
+					 GFP_KERNEL);
+		if (!link->counters)
+			return -ENOMEM;
+	}
+
+	for (i = 0; i < SBL_LINK_NUM_COUNTERS; ++i)
+		atomic_set(link->counters + i, 0);
+
+	return 0;
+}
+
+/* destroy the SBL link's counter array */
+void sbl_link_counters_term(struct sbl_link *link)
+{
+	kfree(link->counters);
+	link->counters = NULL;
+}
+
+/**
+ * sbl_link_counters_get() - Get a block of SBL link counters
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @counters: Number of counters to read from 'first'
+ * @first: Index of the first counter to read
+ * @count: Number of consecutive counters to read starting from 'first'
+ *
+ * Reads a sequence of counters starting from index 'first'
+ * until the given 'count' and stores them to output
+ * pointed by 'counters'
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int sbl_link_counters_get(struct sbl_inst *sbl, int port_num,
+			  int *counters, u16 first, u16 count)
+{
+	struct sbl_link *link;
+	int i;
+	int err;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return -EINVAL;
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return -EINVAL;
+
+	link = sbl->link + port_num;
+
+	if ((first + count) > SBL_LINK_NUM_COUNTERS)
+		return -EINVAL;
+
+	if (!counters)
+		return -EINVAL;
+
+	for (i = first; i < first + count; ++i)
+		*counters++ = atomic_read(link->counters + i);
+
+	return 0;
+}
+EXPORT_SYMBOL(sbl_link_counters_get);
+
+/**
+ * sbl_link_counters_read() - Read value of a SBL link counter
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @counter: Index at which reads the counter value
+ *
+ * Read link count value based on the 'counter' index
+ *
+ * Return: value of a SBL link counter on success, 0 on failure
+ */
+int sbl_link_counters_read(struct sbl_inst *sbl, int port_num, u16 counter)
+{
+	struct sbl_link *link;
+	int err;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return 0;
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return 0;
+
+	link = sbl->link + port_num;
+
+	if (counter >= SBL_LINK_NUM_COUNTERS)
+		return 0;
+
+	return atomic_read(link->counters + counter);
+}
+EXPORT_SYMBOL(sbl_link_counters_read);
+
+/* Increment a SBL link counter */
+int sbl_link_counters_incr(struct sbl_inst *sbl, int port_num, u16 counter)
+{
+	struct sbl_link *link;
+	int err;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return -EINVAL;
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return -EINVAL;
+
+	link = sbl->link + port_num;
+
+	if (counter >= SBL_LINK_NUM_COUNTERS)
+		return -EINVAL;
+
+	atomic_inc(link->counters + counter);
+
+	return 0;
+}
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_fec.c b/drivers/net/ethernet/hpe/sbl/sbl_fec.c
new file mode 100644
index 000000000000..d458ca5e1269
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_fec.c
@@ -0,0 +1,971 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2024-2025 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_an.h>
+#include <linux/hpe/sbl/sbl_fec.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include <linux/hpe/sbl/sbl_pml.h>
+
+#include "sbl_serdes_map.h"
+#include "sbl_pml_fn.h"
+#include "sbl_link.h"
+#include "sbl_serdes.h"
+#include "sbl_serdes_fn.h"
+#include "sbl_internal.h"
+
+static void sbl_fec_counts_zero(struct sbl_inst *sbl, int port_num,
+				struct sbl_pcs_fec_cntrs *cntrs)
+{
+	memset(cntrs, 0, sizeof(struct sbl_pcs_fec_cntrs));
+}
+
+static u64 sbl_fec_rate_calc(struct sbl_inst *sbl, int port_num,
+			     u64 curr, u64 prev, unsigned long tdiff)
+{
+	if (curr < prev) {
+		sbl_dev_err(sbl->dev, "%d: fec counter went backwards", port_num);
+
+		return 0;
+	} else
+		return (curr - prev) * HZ / tdiff;
+}
+
+static int sbl_fec_rates_update(struct sbl_inst *sbl, int port_num,
+				u32 window)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct fec_data *fec_data = link->fec_data;
+	struct sbl_fec *fec_prmts = fec_data->fec_prmts;
+	struct sbl_pcs_fec_cntrs *temp;
+	unsigned long tdiff;
+	int i;
+	bool discard_rates;
+	enum sbl_fec_discard_type reason;
+	unsigned long irq_flags;
+
+	spin_lock(&fec_prmts->fec_cnt_lock);
+
+	/* make sure the period is sufficient to update rates
+	 * otherwise zero rates so nothing fires
+	 */
+	if (time_is_after_jiffies(fec_prmts->fec_curr_cnts->time + msecs_to_jiffies(window))) {
+		sbl_fec_counts_zero(sbl, port_num, fec_prmts->fec_rates);
+		spin_unlock(&fec_prmts->fec_cnt_lock);
+
+		return -EINPROGRESS;
+	}
+
+	temp = fec_prmts->fec_prev_cnts;
+	fec_prmts->fec_prev_cnts = fec_prmts->fec_curr_cnts;
+	fec_prmts->fec_curr_cnts = temp;
+	sbl_fec_counts_get(sbl, port_num, fec_prmts->fec_curr_cnts);
+
+	spin_lock_irqsave(&link->fec_discard_lock, irq_flags);
+	discard_rates = (link->fec_discard_time >= fec_prmts->fec_prev_cnts->time) &&
+			(link->fec_discard_time < fec_prmts->fec_curr_cnts->time);
+	reason = link->fec_discard_type;
+	spin_unlock_irqrestore(&link->fec_discard_lock, irq_flags);
+
+	if (discard_rates) {
+		sbl_dev_dbg(sbl->dev, "%d: %s - ignoring FEC rates for the current window", port_num,
+			    sbl_fec_discard_str(reason));
+		sbl_fec_counts_zero(sbl, port_num, fec_prmts->fec_rates);
+		spin_unlock(&fec_prmts->fec_cnt_lock);
+		return -EINTR;
+	}
+
+	/* calc time difference  (HZ)
+	 * (unsigned so rollover ok)
+	 */
+	tdiff = fec_prmts->fec_curr_cnts->time - fec_prmts->fec_prev_cnts->time;
+
+	/* calculate rates */
+	fec_prmts->fec_rates->ccw =
+		sbl_fec_rate_calc(sbl, port_num,
+				  fec_prmts->fec_curr_cnts->ccw,
+				  fec_prmts->fec_prev_cnts->ccw, tdiff);
+	fec_prmts->fec_rates->ucw =
+		sbl_fec_rate_calc(sbl, port_num,
+				  fec_prmts->fec_curr_cnts->ucw,
+				  fec_prmts->fec_prev_cnts->ucw, tdiff);
+	fec_prmts->fec_rates->llr_tx_replay =
+		sbl_fec_rate_calc(sbl, port_num,
+				  fec_prmts->fec_curr_cnts->llr_tx_replay,
+				  fec_prmts->fec_prev_cnts->llr_tx_replay, tdiff);
+
+	for (i = 0; i < SBL_PCS_NUM_FECL_CNTRS; ++i)
+		fec_prmts->fec_rates->fecl[i] =
+			sbl_fec_rate_calc(sbl, port_num,
+					  fec_prmts->fec_curr_cnts->fecl[i],
+					  fec_prmts->fec_prev_cnts->fecl[i], tdiff);
+
+	fec_prmts->fec_rates->time = jiffies_to_msecs(tdiff);
+
+	spin_unlock(&fec_prmts->fec_cnt_lock);
+
+	return 0;
+
+}
+
+/* returns true if the corrected code word rate is bad */
+static bool sbl_fec_ccw_rate_bad(struct sbl_inst *sbl, int port_num,
+				 u32 thresh_adj, bool use_stp_thresh)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct fec_data *fec_data = link->fec_data;
+	struct sbl_fec *fec_prmts = fec_data->fec_prmts;
+
+	u64 ccw_bad;
+	bool ignore_err;
+	u64 ccw_hwm;
+
+	sbl_fec_ccw_bad_get(fec_prmts, use_stp_thresh, &ccw_bad, &ccw_hwm);
+
+	/* update high water mark
+	 * we record this even if no test is performed so we can use it for
+	 * debug/calibration
+	 */
+	if (fec_prmts->fec_rates->ccw > ccw_hwm) {
+		spin_lock(&fec_prmts->fec_cw_lock);
+		fec_prmts->fec_ccw_hwm = fec_prmts->fec_rates->ccw;
+		spin_unlock(&fec_prmts->fec_cw_lock);
+	}
+
+	/* apply percentage adjustment */
+	ccw_bad = ccw_bad * thresh_adj / 100;
+
+	if (ccw_bad == 0) {
+		sbl_dev_dbg(sbl->dev, "%d: fec ccw test ignored, threshold is zero", port_num);
+
+		return false;
+	}
+
+	/* check corrected code words */
+	if (fec_prmts->fec_rates->ccw > ccw_bad) {
+		sbl_link_counters_incr(sbl, port_num, fec_ccw_err);
+		ignore_err = sbl_debug_option(sbl, port_num, SBL_DEBUG_IGNORE_HIGH_FEC_CCW);
+
+		sbl_dev_err(sbl->dev, "%d: bad ccw, ccw %lld (>%lld), ucw %lld, (%lld %lld %lld %lld %lld %lld %lld %lld), window %ldms%s\n",
+			    port_num, fec_prmts->fec_rates->ccw, ccw_bad,
+				fec_prmts->fec_rates->ucw,
+				fec_prmts->fec_rates->fecl[0], fec_prmts->fec_rates->fecl[1],
+				fec_prmts->fec_rates->fecl[2], fec_prmts->fec_rates->fecl[3],
+				fec_prmts->fec_rates->fecl[4], fec_prmts->fec_rates->fecl[5],
+				fec_prmts->fec_rates->fecl[6], fec_prmts->fec_rates->fecl[7],
+				fec_prmts->fec_rates->time,
+				ignore_err ? " -ignored" : "");
+		if (ignore_err)
+			return false;
+		else
+			return true;
+	}
+	return false;
+}
+
+/* returns true if the uncorrected code word rate is bad
+ * threshold adjustment is a percentage
+ */
+static bool sbl_fec_ucw_rate_bad(struct sbl_inst *sbl, int port_num,
+				 u32 thresh_adj)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct fec_data *fec_data = link->fec_data;
+	struct sbl_fec *fec_prmts = fec_data->fec_prmts;
+
+	bool ignore_err;
+	u64 ucw_bad;
+	u64 ucw_hwm;
+
+	sbl_fec_ucw_bad_get(fec_prmts, &ucw_bad, &ucw_hwm);
+
+	/* update high water mark
+	 * we record this even if no test is performed so we can use it for
+	 * debug/calibration
+	 */
+	if (fec_prmts->fec_rates->ucw > ucw_hwm) {
+		spin_lock(&fec_prmts->fec_cw_lock);
+		fec_prmts->fec_ucw_hwm = fec_prmts->fec_rates->ucw;
+		spin_unlock(&fec_prmts->fec_cw_lock);
+	}
+
+	/* apply percentage adjustment */
+	ucw_bad = ucw_bad * thresh_adj / 100;
+
+	if (ucw_bad == 0) {
+		sbl_dev_dbg(sbl->dev, "%d: fec ucw test ignored, threshold is zero", port_num);
+		return false;
+	}
+
+	/* check uncorrected code words */
+	if (fec_prmts->fec_rates->ucw > ucw_bad) {
+		sbl_link_counters_incr(sbl, port_num, fec_ucw_err);
+		ignore_err = sbl_debug_option(sbl, port_num, SBL_DEBUG_IGNORE_HIGH_FEC_UCW);
+
+		sbl_dev_err(sbl->dev, "%d: bad ucw, ccw %lld, ucw %lld (>%lld), (%lld %lld %lld %lld %lld %lld %lld %lld), window %ldms%s\n",
+			    port_num, fec_prmts->fec_rates->ccw,
+				    fec_prmts->fec_rates->ucw, ucw_bad,
+				    fec_prmts->fec_rates->fecl[0], fec_prmts->fec_rates->fecl[1],
+				    fec_prmts->fec_rates->fecl[2], fec_prmts->fec_rates->fecl[3],
+				    fec_prmts->fec_rates->fecl[4], fec_prmts->fec_rates->fecl[5],
+				    fec_prmts->fec_rates->fecl[6], fec_prmts->fec_rates->fecl[7],
+				    fec_prmts->fec_rates->time,
+				    ignore_err ? " -ignored" : "");
+		if (ignore_err)
+			return false;
+		else
+			return true;
+	}
+	return false;
+
+}
+
+static bool sbl_fec_txr_rate_bad(struct sbl_inst *sbl, int port_num,
+				 u32 thresh_adj)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct fec_data *fec_data = link->fec_data;
+	struct sbl_fec *fec_prmts = fec_data->fec_prmts;
+
+	u64 llr_tx_replay_bad;
+	u64 llr_tx_replay_hwm;
+	bool ignore_err;
+
+	spin_lock(&fec_prmts->fec_cw_lock);
+	llr_tx_replay_bad = fec_prmts->fec_llr_tx_replay_thresh;
+	llr_tx_replay_hwm = fec_prmts->fec_llr_tx_replay_hwm;
+	spin_unlock(&fec_prmts->fec_cw_lock);
+
+	/* update high water mark
+	 * we record this even if no test is performed so we can use it for
+	 * debug/calibration
+	 */
+	if (fec_prmts->fec_rates->llr_tx_replay > llr_tx_replay_hwm) {
+		spin_lock(&fec_prmts->fec_cw_lock);
+		fec_prmts->fec_llr_tx_replay_hwm = fec_prmts->fec_rates->llr_tx_replay;
+		spin_unlock(&fec_prmts->fec_cw_lock);
+	}
+	if (llr_tx_replay_bad == 0) {
+		sbl_dev_dbg(sbl->dev, "%d: fec llr_tx_replay test ignored, threshold is zero", port_num);
+		return false;
+	}
+
+	/* check llr_tx_replay rate */
+	if (fec_prmts->fec_rates->llr_tx_replay > llr_tx_replay_bad) {
+		sbl_link_counters_incr(sbl, port_num, fec_txr_err);
+		ignore_err = sbl_debug_option(sbl, port_num, SBL_DEBUG_IGNORE_HIGH_FEC_TXR);
+
+		sbl_dev_err(sbl->dev, "%d: bad llr_tx_replay, llr_tx_replay %lld (>%lld), window %ldms%s\n",
+			    port_num,
+				fec_prmts->fec_rates->llr_tx_replay, llr_tx_replay_bad,
+				fec_prmts->fec_rates->time,
+				ignore_err ? " -ignored" : "");
+
+		if (ignore_err)
+			return false;
+		else
+			return true;
+	}
+	return false;
+}
+
+/* print a few warnings (so we can see how things are changing) about high
+ * fec lane error rates
+ */
+static void sbl_fec_rates_warnings(struct sbl_inst *sbl, int port_num,
+				   int *warning_count)
+{
+
+	struct sbl_link *link = sbl->link + port_num;
+	struct fec_data *fec_data = link->fec_data;
+	struct sbl_fec *fec_prmts = fec_data->fec_prmts;
+	int fecl_warn;
+	int i;
+
+	spin_lock(&fec_prmts->fec_cw_lock);
+	fecl_warn = fec_prmts->fecl_warn;
+	spin_unlock(&fec_prmts->fec_cw_lock);
+
+	if (fecl_warn == 0) {
+		dev_dbg(sbl->dev, "%d: fec ccw warn ignored, threshold is zero", port_num);
+
+		return;
+	}
+
+	for (i = 0; i < SBL_PCS_NUM_FECL_CNTRS; ++i) {
+		if (fec_prmts->fec_rates->fecl[i] > fecl_warn) {
+			sbl_link_counters_incr(sbl, port_num, fec_warn);
+
+			if (*warning_count > 0) {
+				dev_warn(sbl->dev,
+					 "sbl %d: warning, ccw %lld, (%lld %lld %lld %lld %lld %lld %lld %lld)%s\n",
+					 port_num, fec_prmts->fec_rates->ccw,
+					 fec_prmts->fec_rates->fecl[0],
+					 fec_prmts->fec_rates->fecl[1],
+					 fec_prmts->fec_rates->fecl[2],
+					 fec_prmts->fec_rates->fecl[3],
+					 fec_prmts->fec_rates->fecl[4],
+					 fec_prmts->fec_rates->fecl[5],
+					 fec_prmts->fec_rates->fecl[6],
+					 fec_prmts->fec_rates->fecl[7],
+					 (*warning_count == 1) ? " - last" : "");
+				--*warning_count;
+			} else {
+				dev_dbg(sbl->dev,
+					"sbl %d: warning, ccw %lld, (%lld %lld %lld %lld %lld %lld %lld %lld)\n",
+					port_num, fec_prmts->fec_rates->ccw,
+					fec_prmts->fec_rates->fecl[0],
+					fec_prmts->fec_rates->fecl[1],
+					fec_prmts->fec_rates->fecl[2],
+					fec_prmts->fec_rates->fecl[3],
+					fec_prmts->fec_rates->fecl[4],
+					fec_prmts->fec_rates->fecl[5],
+					fec_prmts->fec_rates->fecl[6],
+					fec_prmts->fec_rates->fecl[7]);
+			}
+			return;
+		}
+	}
+}
+
+/**
+ * sbl_fec_thresholds_clear() - Clear fec ccw/ucw/warn thresholds to zero
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Clears fec ccw/ucw/warn parameters to zero. This update is performed
+ * while holding a spinlock to ensure safe concurrent access.
+ *
+ * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock>
+ */
+void sbl_fec_thresholds_clear(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct fec_data *fec_data = link->fec_data;
+	struct sbl_fec *fec_prmts = fec_data->fec_prmts;
+
+	sbl_dev_dbg(sbl->dev, "%d: clearing fec thresholds", port_num);
+
+	spin_lock(&fec_prmts->fec_cw_lock);
+	fec_prmts->fec_ucw_thresh = 0;
+	fec_prmts->fec_ccw_thresh = 0;
+	fec_prmts->fec_stp_ccw_thresh = 0;
+	fec_prmts->fecl_warn  = 0;
+	spin_unlock(&fec_prmts->fec_cw_lock);
+}
+EXPORT_SYMBOL(sbl_fec_thresholds_clear);
+
+/**
+ * sbl_fec_thresholds_set() - setup the fec thresholds
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @ucw_in: value for link mode and media
+ * @ccw_in: value for link mode and media
+ *
+ * This function gets ccw/ucw/ccw warning values using arguments
+ * provided and copies it to the fec parameters.
+ *
+ * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock>
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int sbl_fec_thresholds_set(struct sbl_inst *sbl, int port_num,
+			   s32 ucw_in, s32 ccw_in)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct fec_data *fec_data = link->fec_data;
+	struct sbl_fec *fec_prmts = fec_data->fec_prmts;
+
+	int link_mode;
+	u64 ucw;
+	u64 ccw;
+	u64 stp_ccw;
+	u64 fecl_warn  = 0;
+	int err = 0;
+
+	/* Uncorrected codeword threshold */
+	switch (ucw_in) {
+	case SBL_LINK_FEC_INVALID:
+		sbl_dev_err(sbl->dev, "%d: link ucw invalid", port_num);
+		goto fail;
+
+	case SBL_LINK_FEC_OFF:
+		ucw = 0;
+		break;
+
+	case SBL_LINK_FEC_IEEE:
+		ucw = sbl_link_get_ucw_thresh_ieee(sbl, port_num);
+		break;
+
+	case SBL_LINK_FEC_HPE:
+		ucw = sbl_link_get_ucw_thresh_hpe(sbl, port_num);
+		break;
+
+	default:
+
+		if (ucw_in < 0) {
+			sbl_dev_err(sbl->dev, "%d: bad link attr ucw (%d)", port_num,
+				    ucw_in);
+			goto fail;
+		}
+
+		/* explicit number, just use it */
+		ucw = ucw_in;
+		break;
+	}
+
+	/* Corrected codeword threshold */
+	switch (ccw_in) {
+	case SBL_LINK_FEC_INVALID:
+		sbl_dev_err(sbl->dev, "%d: attr ccw invalid", port_num);
+		goto fail;
+
+	case SBL_LINK_FEC_OFF:
+		ccw = 0;
+		stp_ccw = 0;
+		break;
+
+	case SBL_LINK_FEC_IEEE:
+		ccw = sbl_link_get_ccw_thresh_ieee(sbl, port_num);
+		stp_ccw = sbl_link_get_stp_ccw_thresh_ieee(sbl, port_num);
+		break;
+
+	case SBL_LINK_FEC_HPE:
+		ccw = sbl_link_get_ccw_thresh_hpe(sbl, port_num);
+		stp_ccw = sbl_link_get_stp_ccw_thresh_hpe(sbl, port_num);
+		break;
+
+	default:
+
+		if (ccw_in < 0) {
+			sbl_dev_err(sbl->dev, "%d:bad link attr ccw (%d)", port_num,
+				    ccw_in);
+			goto fail;
+		}
+
+		/* explicit number, just use it */
+		ccw = ccw_in;
+		stp_ccw = ccw_in;
+		break;
+	}
+
+	/* ccw warning thresholds */
+	err = sbl_base_link_get_status(sbl, port_num,
+				       NULL, NULL, NULL, NULL, NULL, &link_mode);
+
+	if (err) {
+		sbl_dev_warn(sbl->dev, "%d:thresh, get mode failed [%d]-warnings disabled", port_num, err);
+		fecl_warn = 0;
+	} else {
+
+		switch (link_mode) {
+		case SBL_LINK_MODE_BS_200G:
+			fecl_warn = ccw / 8;
+			break;
+		case SBL_LINK_MODE_BJ_100G:
+			fecl_warn = ccw / 8;
+			break;
+		case SBL_LINK_MODE_CD_100G:
+			fecl_warn = ccw / 4;
+			break;
+		case SBL_LINK_MODE_CD_50G:
+			fecl_warn = ccw / 2;
+			break;
+		default:
+			fecl_warn = 0;
+			break;
+		}
+	}
+
+	sbl_dev_dbg(sbl->dev, "%d: Setting fec thresh ucw %lld, ccw %lld, warn %lld",
+		    port_num, ucw, ccw, fecl_warn);
+
+	spin_lock(&fec_prmts->fec_cw_lock);
+	fec_prmts->fec_ucw_thresh = ucw;
+	fec_prmts->fec_ccw_thresh = ccw;
+	fec_prmts->fec_stp_ccw_thresh = stp_ccw;
+	fec_prmts->fec_llr_tx_replay_thresh = SBL_FEC_LLR_TX_REPLAY_THRESH;
+	fec_prmts->fecl_warn = fecl_warn;
+	spin_unlock(&fec_prmts->fec_cw_lock);
+
+	return 0;
+fail:
+	sbl_dev_warn(sbl->dev, "%d: fec monitoring disabled", port_num);
+	sbl_fec_thresholds_clear(sbl, port_num);
+	return -ENAVAIL;
+}
+EXPORT_SYMBOL(sbl_fec_thresholds_set);
+
+/**
+ * sbl_fec_adjustments_set() - setup the fec threshold adjustments
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @ucw_adj: Value (An adjustment of zero means disable the test)
+ * @ccw_adj: Value (An adjustment of zero means disable the test)
+ *
+ * This function copies provided parameters(ucw adj/ccw adj) from caller
+ * into fec parameters of fec_ucw_down_thresh_adj and fec_ccw_down_thresh_adj
+ *
+ * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock>
+ *
+ * Return: 0 on success
+ */
+int sbl_fec_adjustments_set(struct sbl_inst *sbl, int port_num,
+			    u32 ucw_adj, u32 ccw_adj)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct fec_data *fec_data = link->fec_data;
+	struct sbl_fec *fec_prmts = fec_data->fec_prmts;
+
+	spin_lock(&fec_prmts->fec_cw_lock);
+	fec_prmts->fec_ucw_down_thresh_adj = ucw_adj;
+	fec_prmts->fec_ccw_down_thresh_adj = ccw_adj;
+	spin_unlock(&fec_prmts->fec_cw_lock);
+
+	sbl_dev_dbg(sbl->dev, "%d: Setting fec thresh adjustments ucw_adj %d, ccw_adj %d",
+		    port_num, ucw_adj, ccw_adj);
+
+	return 0;
+}
+EXPORT_SYMBOL(sbl_fec_adjustments_set);
+
+int sbl_fec_up_check(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct fec_data *fec_data = link->fec_data;
+	struct sbl_fec *fec_prmts = fec_data->fec_prmts;
+	bool ucw_err;
+	bool ccw_err	 = false;
+	bool stp_ccw_err = false;
+	u32 ucw_thresh_adj;
+	u32 ccw_thresh_adj;
+	u32 stp_ccw_thresh_adj;
+	int i;
+
+	spin_lock(&fec_prmts->fec_cw_lock);
+	ucw_thresh_adj = fec_prmts->fec_ucw_up_thresh_adj;
+	ccw_thresh_adj = fec_prmts->fec_ccw_up_thresh_adj;
+	stp_ccw_thresh_adj = fec_prmts->fec_stp_ccw_up_thresh_adj;
+	spin_unlock(&fec_prmts->fec_cw_lock);
+
+	/* initial measurement */
+	msleep(SBL_FEC_UP_SETTLE_PERIOD); /* time for fec rates to settle */
+	sbl_fec_counts_get(sbl, port_num, fec_prmts->fec_curr_cnts);
+
+	for (i = 0; i < (link->blattr.options & SBL_OPT_FABRIC_LINK ?
+			 SBL_FEC_UP_COUNT_FABRIC : SBL_FEC_UP_COUNT_EDGE); ++i) {
+
+		msleep(SBL_FEC_UP_WINDOW);
+		sbl_fec_rates_update(sbl, port_num, SBL_FEC_UP_WINDOW);
+
+		ucw_err = sbl_fec_ucw_rate_bad(sbl, port_num, ucw_thresh_adj);
+		if (ucw_err)
+			sbl_dev_err(sbl->dev, "%d: fec up check: ucw fail", port_num);
+
+		if ((link->dfe_tune_count == SBL_DFE_USED_SAVED_PARAMS) && (stp_ccw_thresh_adj > 0))
+			stp_ccw_err = sbl_fec_ccw_rate_bad(sbl, port_num, stp_ccw_thresh_adj, true);
+		else
+			ccw_err = sbl_fec_ccw_rate_bad(sbl, port_num, ccw_thresh_adj, false);
+
+		if (ccw_err)
+			sbl_dev_err(sbl->dev, "%d: fec up check: ccw fail", port_num);
+		else if (stp_ccw_err)
+			sbl_dev_err(sbl->dev, "%d: fec up check: stp ccw fail", port_num);
+
+		if (ucw_err || ccw_err || stp_ccw_err) {
+			sbl_link_counters_incr(sbl, port_num, fec_up_fail);
+			return -EOVERFLOW;
+		}
+	}
+
+	return 0;
+}
+
+void sbl_fec_counts_get(struct sbl_inst *sbl, int port_num,
+			struct sbl_pcs_fec_cntrs *cntrs)
+{
+	cntrs->fecl[0] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_00_ADDR(port_num));
+	cntrs->fecl[1] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_01_ADDR(port_num));
+	cntrs->fecl[2] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_02_ADDR(port_num));
+	cntrs->fecl[3] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_03_ADDR(port_num));
+	cntrs->fecl[4] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_04_ADDR(port_num));
+	cntrs->fecl[5] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_05_ADDR(port_num));
+	cntrs->fecl[6] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_06_ADDR(port_num));
+	cntrs->fecl[7] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_07_ADDR(port_num));
+
+	cntrs->ccw = sbl_read64(sbl, SBL_PCS_CORRECTED_CW_ADDR(port_num));
+	cntrs->ucw = sbl_read64(sbl, SBL_PCS_UNCORRECTED_CW_ADDR(port_num));
+
+	cntrs->llr_tx_replay = sbl_read64(sbl, SBL_LLR_TX_REPLAY_EVENT_ADDR(port_num));
+
+	cntrs->time = jiffies;
+}
+
+void sbl_fec_monitor_start(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	unsigned long flags;
+
+	spin_lock_irqsave(&link->lock, flags);
+	if (!link->start_cancelled) {
+		link->fec_monitor_enabled = true;
+		mod_timer(&link->fec_data->fec_timer,
+			  jiffies + msecs_to_jiffies(SBL_FEC_MON_PERIOD));
+	}
+	spin_unlock_irqrestore(&link->lock, flags);
+}
+
+void sbl_fec_monitor_stop(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	unsigned long flags;
+
+	spin_lock_irqsave(&link->lock, flags);
+	link->fec_monitor_enabled = false;
+	spin_unlock_irqrestore(&link->lock, flags);
+
+	timer_delete_sync(&link->fec_data->fec_timer);
+	cancel_work_sync(&link->fec_data->fec_timer_work);
+}
+
+static void sbl_fec_monitor_rearm(struct sbl_link *link)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&link->lock, flags);
+	if (link->fec_monitor_enabled)
+		mod_timer(&link->fec_data->fec_timer,
+			  jiffies + msecs_to_jiffies(SBL_FEC_MON_PERIOD));
+	spin_unlock_irqrestore(&link->lock, flags);
+}
+
+void sbl_fec_timer_work(struct work_struct *work)
+{
+	u32 ucw_thresh_adj;
+	u32 ccw_thresh_adj;
+	int port_num;
+	unsigned long irq_flags;
+	struct sbl_link *link;
+	enum sbl_link_down_origin down_origin = SBL_LINK_DOWN_ORIGIN_UNKNOWN;
+	struct sbl_fec *fec_prmts;
+	int warning_count = SBL_MAX_FEC_WARNINGS;
+	int err;
+	struct fec_data *fec_data = container_of(work, struct fec_data, fec_timer_work);
+	struct sbl_inst *sbl = fec_data->sbl;
+
+	port_num = fec_data->port_num;
+	link = sbl->link + port_num;
+	fec_prmts = fec_data->fec_prmts;
+
+	if ((link->blstate == SBL_BASE_LINK_STATUS_UP) && (sbl_pml_pcs_aligned(sbl, port_num))) {
+		err = sbl_fec_rates_update(sbl, port_num, SBL_FEC_MON_PERIOD);
+
+		if (!err) {
+			spin_lock_irqsave(&fec_prmts->fec_cw_lock, irq_flags);
+			ucw_thresh_adj = fec_prmts->fec_ucw_down_thresh_adj;
+			ccw_thresh_adj = fec_prmts->fec_ccw_down_thresh_adj;
+			spin_unlock_irqrestore(&fec_prmts->fec_cw_lock, irq_flags);
+
+			if (sbl_fec_ucw_rate_bad(sbl, port_num, ucw_thresh_adj)) {
+				/* take the link down */
+				down_origin = SBL_LINK_DOWN_ORIGIN_UCW;
+				sbl_pml_link_down_async_alert(sbl, port_num, down_origin);
+				return;
+			}
+
+			if (sbl_fec_ccw_rate_bad(sbl, port_num, ccw_thresh_adj, false)) {
+				/* take the link down */
+				down_origin = SBL_LINK_DOWN_ORIGIN_CCW;
+				sbl_pml_link_down_async_alert(sbl, port_num, down_origin);
+				return;
+			}
+
+			if (sbl_fec_txr_rate_bad(sbl, port_num, 0)) {
+				/* take the link down */
+				down_origin = SBL_LINK_DOWN_ORIGIN_LLR_TX_REPLAY;
+				sbl_pml_link_down_async_alert(sbl, port_num, down_origin);
+				return;
+			}
+			sbl_fec_rates_warnings(sbl, port_num, &warning_count);
+
+		}
+
+	}
+	sbl_fec_monitor_rearm(link);
+}
+
+void sbl_zero_all_fec_counts(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct fec_data *fec_data = link->fec_data;
+	struct sbl_fec *fec_prmts = fec_data->fec_prmts;
+
+	spin_lock(&fec_prmts->fec_cnt_lock);
+	memset(fec_prmts->fec_curr_cnts, 0, sizeof(struct sbl_pcs_fec_cntrs));
+	memset(fec_prmts->fec_prev_cnts, 0, sizeof(struct sbl_pcs_fec_cntrs));
+	memset(fec_prmts->fec_rates, 0, sizeof(struct sbl_pcs_fec_cntrs));
+	spin_unlock(&fec_prmts->fec_cnt_lock);
+}
+
+void sbl_fec_timer(struct timer_list *timer)
+{
+	int port_num;
+	struct sbl_link *link;
+	struct fec_data *fec_data = timer_container_of(fec_data, timer,
+						       fec_timer);
+	struct sbl_inst *sbl = fec_data->sbl;
+
+	port_num = fec_data->port_num;
+	link = sbl->link + port_num;
+
+	if (!queue_work(sbl->workq, &(link->fec_data->fec_timer_work)))
+		sbl_dev_warn(sbl->dev, "fec timer work already queued");
+}
+
+#ifdef CONFIG_SYSFS
+/**
+ * sbl_fec_sysfs_sprint() - Format FEC parameters status 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 FEC parameters and writes the result into the
+ * provided buffer typically for use in sysfs output.
+ *
+ * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock>
+ *
+ * Return: Number of characters written, at most @size - 1
+ */
+int sbl_fec_sysfs_sprint(struct sbl_inst *sbl, int port_num, char *buf, size_t size)
+{
+	u64 ucw_thresh;
+	u64 ucw_hwm;
+	u64 ccw_thresh;
+	u64 ccw_hwm;
+	u64 llr_tx_replay_thresh;
+	u64 llr_tx_replay_hwm;
+	u64 fecl_warn;
+	int s = 0;
+	struct sbl_link *link = sbl->link + port_num;
+	struct fec_data *fec_data = link->fec_data;
+	struct sbl_fec *fec_prmts = fec_data->fec_prmts;
+
+	spin_lock(&fec_prmts->fec_cw_lock);
+	if (link->blstate == SBL_BASE_LINK_STATUS_STARTING) {
+		ucw_thresh = fec_prmts->fec_ucw_thresh * fec_prmts->fec_ucw_up_thresh_adj / 100;
+		ccw_thresh = fec_prmts->fec_ccw_thresh * fec_prmts->fec_ccw_up_thresh_adj / 100;
+	} else {
+		ucw_thresh = fec_prmts->fec_ucw_thresh * fec_prmts->fec_ucw_down_thresh_adj / 100;
+		ccw_thresh = fec_prmts->fec_ccw_thresh * fec_prmts->fec_ccw_down_thresh_adj / 100;
+	}
+
+	llr_tx_replay_thresh = fec_prmts->fec_llr_tx_replay_thresh;
+	ucw_hwm = fec_prmts->fec_ucw_hwm;
+	ccw_hwm = fec_prmts->fec_ccw_hwm;
+	llr_tx_replay_hwm = fec_prmts->fec_llr_tx_replay_hwm;
+	fecl_warn = fec_prmts->fecl_warn;
+	spin_unlock(&fec_prmts->fec_cw_lock);
+	if (link->blstate == SBL_BASE_LINK_STATUS_UP) {
+
+		s += scnprintf(buf + s, size - s,
+			       "fec monitor: hwm- ccw %lld/%lld, ucw %lld/%lld",
+			       ccw_hwm, ccw_thresh, ucw_hwm, ucw_thresh);
+		s += scnprintf(buf + s, size - s,
+			       ", llr_tx_replay %lld/%lld, warn %lld",
+			       llr_tx_replay_hwm, llr_tx_replay_thresh, fecl_warn);
+
+		if (ucw_thresh == 0)
+			s += scnprintf(buf + s, size - s, ", ucw-off");
+
+		if (ccw_thresh == 0)
+			s += scnprintf(buf + s, size - s, ", ccw-off");
+
+		s += scnprintf(buf + s, size - s, "\n");
+
+		if (sbl_pml_pcs_aligned(sbl, port_num)) {
+			spin_lock(&fec_prmts->fec_cnt_lock);
+			if (fec_prmts->fec_rates) {
+				s += scnprintf(buf + s, size - s,
+					       "fec monitor: rates- ccw %lld",
+					       fec_prmts->fec_rates->ccw);
+				s += scnprintf(buf + s, size - s, ", ucw %lld/%lld",
+					       fec_prmts->fec_rates->ucw,
+					       fec_prmts->fec_curr_cnts->ucw);
+				s += scnprintf(buf + s, size - s,
+					       ", llr_tx_replay %lld, window %ld",
+					       fec_prmts->fec_rates->llr_tx_replay,
+					       fec_prmts->fec_rates->time);
+				s += scnprintf(buf + s, size - s,
+					       ", (%lld %lld %lld %lld %lld %lld %lld %lld)\n",
+					       fec_prmts->fec_rates->fecl[0],
+					       fec_prmts->fec_rates->fecl[1],
+					       fec_prmts->fec_rates->fecl[2],
+					       fec_prmts->fec_rates->fecl[3],
+					       fec_prmts->fec_rates->fecl[4],
+					       fec_prmts->fec_rates->fecl[5],
+					       fec_prmts->fec_rates->fecl[6],
+					       fec_prmts->fec_rates->fecl[7]);
+			}
+			spin_unlock(&fec_prmts->fec_cnt_lock);
+		}
+	}
+	return s;
+}
+EXPORT_SYMBOL(sbl_fec_sysfs_sprint);
+#endif
+
+/**
+ * sbl_fec_txr_rate_set() - set the llr replay threshold
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @txr_rate: Value - An adjustment of zero means disable the test
+ *
+ * This function sets the LLR (Link-Level Retry) replay threshold
+ * used by the FEC logic. The threshold value is provided by the caller.
+ *
+ * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock>
+ *
+ * Return: 0 on success
+ */
+int sbl_fec_txr_rate_set(struct sbl_inst *sbl, int port_num,
+			 u32 txr_rate)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct fec_data *fec_data = link->fec_data;
+	struct sbl_fec *fec_prmts = fec_data->fec_prmts;
+
+	spin_lock(&fec_prmts->fec_cw_lock);
+	fec_prmts->fec_llr_tx_replay_thresh = txr_rate;
+	spin_unlock(&fec_prmts->fec_cw_lock);
+
+	sbl_dev_dbg(sbl->dev, "%d: Setting txr_rate %d",
+		    port_num, txr_rate);
+
+	return 0;
+}
+EXPORT_SYMBOL(sbl_fec_txr_rate_set);
+
+/**
+ * sbl_fec_modify_adjustments() - Update FEC threshold adjustments
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @ucw_up_adj: Adjusting un-corrected code word thresholds
+ * @ccw_up_adj: Adjusting corrected code word thresholds
+ * @ucw_down_adj: Adjusting un-corrected code word thresholds
+ * @ccw_down_adj: Adjusting corrected code word thresholds
+ * @stp_ccw_up_adj: stp un-corrected code word thresholds
+ *
+ * This function adjusts max/min of uncorrected/corrected
+ * words for tuning/debug/test purposes at various levels.
+ * Used at test/debug/tuning interface.
+ *
+ * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock>
+ */
+void sbl_fec_modify_adjustments(struct sbl_inst *sbl, int port_num,
+				u32 *ucw_up_adj, u32 *ccw_up_adj,
+				u32 *ucw_down_adj, u32 *ccw_down_adj,
+				u32 *stp_ccw_up_adj)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct fec_data *fec_data = link->fec_data;
+	struct sbl_fec *fec_prmts = fec_data->fec_prmts;
+
+	spin_lock(&fec_prmts->fec_cw_lock);
+	if (ucw_up_adj)
+		fec_prmts->fec_ucw_up_thresh_adj = *ucw_up_adj;
+	if (ccw_up_adj)
+		fec_prmts->fec_ccw_up_thresh_adj = *ccw_up_adj;
+	if (ucw_down_adj)
+		fec_prmts->fec_ucw_down_thresh_adj = *ucw_down_adj;
+	if (ccw_down_adj)
+		fec_prmts->fec_ccw_down_thresh_adj = *ccw_down_adj;
+	if (stp_ccw_up_adj)
+		fec_prmts->fec_stp_ccw_up_thresh_adj = *stp_ccw_up_adj;
+	spin_unlock(&fec_prmts->fec_cw_lock);
+}
+EXPORT_SYMBOL(sbl_fec_modify_adjustments);
+
+/**
+ * sbl_fec_dump() - Print FEC info
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock>
+ */
+void sbl_fec_dump(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct fec_data *fec_data = link->fec_data;
+	struct sbl_fec *fec_prmts = fec_data->fec_prmts;
+
+	u64 ucw_thresh;
+	u64 ccw_thresh;
+	u64 stp_ccw_thresh;
+	u32 ucw_up_adj;
+	u32 ccw_up_adj;
+	u32 stp_ccw_up_adj;
+	u32 ucw_down_adj;
+	u32 ccw_down_adj;
+
+	spin_lock(&fec_prmts->fec_cw_lock);
+	ucw_thresh     = fec_prmts->fec_ucw_thresh;
+	ccw_thresh     = fec_prmts->fec_ccw_thresh;
+	stp_ccw_thresh = fec_prmts->fec_stp_ccw_thresh;
+	ucw_up_adj     = fec_prmts->fec_ucw_up_thresh_adj;
+	ccw_up_adj     = fec_prmts->fec_ccw_up_thresh_adj;
+	stp_ccw_up_adj = fec_prmts->fec_stp_ccw_up_thresh_adj;
+	ucw_down_adj   = fec_prmts->fec_ucw_down_thresh_adj;
+	ccw_down_adj   = fec_prmts->fec_ccw_down_thresh_adj;
+	spin_unlock(&fec_prmts->fec_cw_lock);
+
+	sbl_dev_info(sbl->dev, "%d: ucw : thresh %lld", port_num, ucw_thresh);
+	sbl_dev_info(sbl->dev, "%d: ucw up: x%d%%, %lld", port_num,
+		     ucw_up_adj, ucw_thresh * ucw_up_adj / 100);
+	sbl_dev_info(sbl->dev, "%d: ucw down: x%d%%, %lld", port_num,
+		     ucw_down_adj, ucw_thresh * ucw_down_adj / 100);
+
+	sbl_dev_info(sbl->dev, "%d: ccw : thresh %lld", port_num, ccw_thresh);
+	sbl_dev_info(sbl->dev, "%d: stp ccw : thresh %lld", port_num, stp_ccw_thresh);
+	sbl_dev_info(sbl->dev, "%d: ccw up: x%d%%, %lld", port_num,
+		     ccw_up_adj, ccw_thresh * ccw_up_adj / 100);
+	sbl_dev_info(sbl->dev, "%d: stp ccw up: x%d%%, %lld", port_num,
+		     stp_ccw_up_adj, stp_ccw_thresh * stp_ccw_up_adj / 100);
+	sbl_dev_info(sbl->dev, "%d: ccw down: x%d%%, %lld", port_num,
+		     ccw_down_adj, ccw_thresh * ccw_down_adj / 100);
+}
+EXPORT_SYMBOL(sbl_fec_dump);
+
+/**
+ * sbl_fec_hwms_clear() - Clear fec HW management config
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * This function clears the FEC HWM configuration
+ *
+ * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock>
+ */
+void sbl_fec_hwms_clear(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct fec_data *fec_data = link->fec_data;
+	struct sbl_fec *fec_prmts = fec_data->fec_prmts;
+
+	sbl_dev_dbg(sbl->dev, "%d: clearing fec thresholds", port_num);
+
+	spin_lock(&fec_prmts->fec_cw_lock);
+	fec_prmts->fec_ucw_hwm = 0;
+	fec_prmts->fec_ccw_hwm = 0;
+	fec_prmts->fec_llr_tx_replay_hwm = 0;
+	spin_unlock(&fec_prmts->fec_cw_lock);
+}
+EXPORT_SYMBOL(sbl_fec_hwms_clear);
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_fec_nic.c b/drivers/net/ethernet/hpe/sbl/sbl_fec_nic.c
new file mode 100644
index 000000000000..e7a06c8895d7
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_fec_nic.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2025 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_fec.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include <linux/hpe/sbl/sbl_pml.h>
+
+#include "sbl_internal.h"
+
+void sbl_fec_ucw_bad_get(struct sbl_fec *fec_prmts, u64 *ucw_bad, u64 *ucw_hwm)
+{
+	spin_lock(&fec_prmts->fec_cw_lock);
+	*ucw_hwm = fec_prmts->fec_ucw_hwm;
+	*ucw_bad = fec_prmts->fec_ucw_thresh;
+	spin_unlock(&fec_prmts->fec_cw_lock);
+}
+
+void sbl_fec_ccw_bad_get(struct sbl_fec *fec_prmts, bool use_stp_thresh,
+			 u64 *ccw_bad, u64 *ccw_hwm)
+{
+	spin_lock(&fec_prmts->fec_cw_lock);
+	*ccw_hwm = fec_prmts->fec_ccw_hwm;
+	*ccw_bad = use_stp_thresh ? fec_prmts->fec_stp_ccw_thresh :
+		fec_prmts->fec_ccw_thresh;
+	spin_unlock(&fec_prmts->fec_cw_lock);
+}
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_media.c b/drivers/net/ethernet/hpe/sbl/sbl_media.c
new file mode 100644
index 000000000000..ec2b4574565e
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_media.c
@@ -0,0 +1,482 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2019, 2021-2025 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include <linux/hpe/sbl/sbl_pml.h>
+
+#include "sbl_internal.h"
+
+/**
+ * sbl_media_config() - Config media
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @mattr: media attributes
+ *
+ * This function sets the media attributes for the link
+ * and sets the state as configured.
+ *
+ * Context: Process, Acquires locks and releases link->lock <spin_lock>
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int sbl_media_config(struct sbl_inst *sbl, int port_num,
+		     struct sbl_media_attr *mattr)
+{
+	struct sbl_link *link;
+	int err;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return err;
+
+	sbl_dev_dbg(sbl->dev, "media config\n");
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return err;
+
+	if (!mattr)
+		return -EINVAL;
+
+	if (mattr->magic != SBL_MEDIA_ATTR_MAGIC)
+		return -EINVAL;
+
+	link = sbl->link + port_num;
+
+	spin_lock(&link->lock);
+
+	if (!(link->blstate & (SBL_BASE_LINK_STATUS_UNCONFIGURED | SBL_BASE_LINK_STATUS_DOWN))) {
+
+		sbl_dev_err(sbl->dev, "%d: wrong state (%s) to configure media\n", port_num,
+			    sbl_link_state_str(link->blstate));
+		spin_unlock(&link->lock);
+		return -EUCLEAN;
+	}
+
+	memcpy(&link->mattr, mattr, sizeof(struct sbl_media_attr));
+	link->mconfigured = true;
+
+	if (link->blconfigured)
+		link->blstate = SBL_BASE_LINK_STATUS_DOWN;
+
+	/* the media might have changed so invalidate the llr loop time */
+	link->llr_loop_time = 0;
+
+	spin_unlock(&link->lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(sbl_media_config);
+
+/**
+ * sbl_media_unconfig() - Unconfig media
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Clear media attributes and set base link status to unconfigure.
+ *
+ * Context: Process, Acquires Lock and releases link->lock <spin_lock>
+ *
+ * Return: negative error code on failure
+ */
+int sbl_media_unconfig(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link;
+	int err = 0;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return err;
+
+	sbl_dev_dbg(sbl->dev, "media unconfig\n");
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return err;
+
+	link = sbl->link + port_num;
+
+	spin_lock(&link->lock);
+
+	if (!(link->blstate & (SBL_BASE_LINK_STATUS_UNCONFIGURED |
+			SBL_BASE_LINK_STATUS_DOWN |
+			SBL_BASE_LINK_STATUS_STOPPING))) {
+
+		sbl_dev_err(sbl->dev, "%d: wrong state (%s) to unconfigure media\n", port_num,
+			    sbl_link_state_str(link->blstate));
+		spin_unlock(&link->lock);
+		return -EUCLEAN;
+	}
+
+	memset(&link->mattr, 0, sizeof(struct sbl_media_attr));
+	link->mconfigured = false;
+
+	link->blstate = SBL_BASE_LINK_STATUS_UNCONFIGURED;
+
+	spin_unlock(&link->lock);
+
+	return err;
+}
+EXPORT_SYMBOL(sbl_media_unconfig);
+
+bool sbl_media_check_mode_supported(struct sbl_inst *sbl, int port_num, u32 link_mode)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u32 info_mode_bit = 0;
+	u32 media_info;
+	bool allow_bad_mode;
+	unsigned long flags;
+
+	spin_lock_irqsave(&link->lock, flags);
+	media_info = link->mattr.info;
+	allow_bad_mode = link->blattr.options & SBL_OPT_ALLOW_MEDIA_BAD_MODE;
+	spin_unlock_irqrestore(&link->lock, flags);
+
+	if (!media_info) {
+		/* We have no info about media
+		 * assume its OK so we can carry on
+		 * if its not we will fail later anyway
+		 */
+		sbl_dev_warn(sbl->dev, "%d: no media info - assuming link mode OK", port_num);
+		return true;
+	}
+
+	switch (link_mode) {
+	case SBL_LINK_MODE_BS_200G:
+		info_mode_bit = SBL_MEDIA_INFO_SUPPORTS_BS_200G;
+		break;
+	case SBL_LINK_MODE_BJ_100G:
+		info_mode_bit = SBL_MEDIA_INFO_SUPPORTS_BJ_100G;
+		break;
+	case SBL_LINK_MODE_CD_100G:
+		info_mode_bit = SBL_MEDIA_INFO_SUPPORTS_CD_100G;
+		break;
+	case SBL_LINK_MODE_CD_50G:
+		info_mode_bit = SBL_MEDIA_INFO_SUPPORTS_CD_50G;
+		break;
+	default:
+		/* do nothing here - below will catch invalid link mode */
+		break;
+	}
+
+	if (media_info & info_mode_bit)
+		return true;
+
+	if (allow_bad_mode ||
+	    sbl_debug_option(sbl, port_num, SBL_DEBUG_ALLOW_MEDIA_BAD_MODE)) {
+		sbl_dev_warn(sbl->dev, "%d: link mode (%s) not supported by media - ignored",
+			     port_num, sbl_link_mode_str(link_mode));
+		return true;
+	}
+	sbl_dev_err(sbl->dev, "%d: link mode (%s) not supported by media",
+		    port_num, sbl_link_mode_str(link_mode));
+	return false;
+}
+
+/**
+ * sbl_media_check_headshell_reset_supported() - Check headshell based on media info
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Return: True if reset is supported else false
+ */
+bool sbl_media_check_headshell_reset_supported(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	if (!link->mattr.info) {
+		/* We have no info about media */
+		sbl_dev_dbg(sbl->dev, "%d: no media info available to check headshell reset supported bit", port_num);
+		return false;
+	}
+
+	if (link->mattr.info & SBL_MEDIA_INFO_SUPPORTS_HEADSHELL_RESET)
+		return true;
+
+	return false;
+}
+EXPORT_SYMBOL(sbl_media_check_headshell_reset_supported);
+
+static bool sbl_media_check_len_supported(struct sbl_inst *sbl, int port_num, u64 len)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	switch (len) {
+	case SBL_LINK_LEN_BACKPLANE:
+	case SBL_LINK_LEN_000_300:
+	case SBL_LINK_LEN_000_400:
+	case SBL_LINK_LEN_000_750:
+	case SBL_LINK_LEN_000_800:
+	case SBL_LINK_LEN_001_000:
+	case SBL_LINK_LEN_001_100:
+	case SBL_LINK_LEN_001_150:
+	case SBL_LINK_LEN_001_200:
+	case SBL_LINK_LEN_001_400:
+	case SBL_LINK_LEN_001_420:
+	case SBL_LINK_LEN_001_500:
+	case SBL_LINK_LEN_001_600:
+	case SBL_LINK_LEN_001_640:
+	case SBL_LINK_LEN_001_700:
+	case SBL_LINK_LEN_001_800:
+	case SBL_LINK_LEN_001_900:
+	case SBL_LINK_LEN_001_910:
+	case SBL_LINK_LEN_002_000:
+	case SBL_LINK_LEN_002_100:
+	case SBL_LINK_LEN_002_130:
+	case SBL_LINK_LEN_002_200:
+	case SBL_LINK_LEN_002_300:
+	case SBL_LINK_LEN_002_390:
+	case SBL_LINK_LEN_002_400:
+	case SBL_LINK_LEN_002_500:
+	case SBL_LINK_LEN_002_600:
+	case SBL_LINK_LEN_002_620:
+	case SBL_LINK_LEN_002_700:
+	case SBL_LINK_LEN_002_800:
+	case SBL_LINK_LEN_002_900:
+	case SBL_LINK_LEN_002_990:
+	case SBL_LINK_LEN_003_000:
+	case SBL_LINK_LEN_004_000:
+	case SBL_LINK_LEN_005_000:
+	case SBL_LINK_LEN_006_000:
+	case SBL_LINK_LEN_007_000:
+	case SBL_LINK_LEN_008_000:
+	case SBL_LINK_LEN_010_000:
+	case SBL_LINK_LEN_014_000:
+	case SBL_LINK_LEN_015_000:
+	case SBL_LINK_LEN_019_000:
+	case SBL_LINK_LEN_025_000:
+	case SBL_LINK_LEN_030_000:
+	case SBL_LINK_LEN_035_000:
+	case SBL_LINK_LEN_050_000:
+	case SBL_LINK_LEN_075_000:
+	case SBL_LINK_LEN_100_000:
+		return true;
+	default:
+		if ((link->blattr.options & SBL_OPT_ALLOW_MEDIA_BAD_LEN) ||
+		    sbl_debug_option(sbl, port_num, SBL_DEBUG_ALLOW_MEDIA_BAD_LEN)) {
+			sbl_dev_warn(sbl->dev, "%d: unsupported media len (%lld) - ignored", port_num, len);
+			return true;
+		}
+		sbl_dev_err(sbl->dev, "%d: unsupported media len (%lld)", port_num, len);
+		return false;
+	}
+}
+
+/* return valid length in cm or 0 */
+u32 sbl_media_get_len_cm(struct sbl_inst *sbl, int port_num, u64 len)
+{
+	if (sbl_media_check_len_supported(sbl, port_num, len)) {
+		switch (len) {
+		case SBL_LINK_LEN_BACKPLANE: return 25;
+		case SBL_LINK_LEN_000_300:   return 30;
+		case SBL_LINK_LEN_000_400:   return 40;
+		case SBL_LINK_LEN_000_750:   return 75;
+		case SBL_LINK_LEN_000_800:   return 80;
+		case SBL_LINK_LEN_001_000:   return 100;
+		case SBL_LINK_LEN_001_100:   return 110;
+		case SBL_LINK_LEN_001_150:   return 115;
+		case SBL_LINK_LEN_001_200:   return 120;
+		case SBL_LINK_LEN_001_400:   return 140;
+		case SBL_LINK_LEN_001_420:   return 142;
+		case SBL_LINK_LEN_001_500:   return 150;
+		case SBL_LINK_LEN_001_600:   return 160;
+		case SBL_LINK_LEN_001_640:   return 164;
+		case SBL_LINK_LEN_001_700:   return 170;
+		case SBL_LINK_LEN_001_800:   return 180;
+		case SBL_LINK_LEN_001_900:   return 190;
+		case SBL_LINK_LEN_001_910:   return 191;
+		case SBL_LINK_LEN_002_000:   return 200;
+		case SBL_LINK_LEN_002_100:   return 210;
+		case SBL_LINK_LEN_002_130:   return 213;
+		case SBL_LINK_LEN_002_200:   return 220;
+		case SBL_LINK_LEN_002_300:   return 230;
+		case SBL_LINK_LEN_002_390:   return 239;
+		case SBL_LINK_LEN_002_400:   return 240;
+		case SBL_LINK_LEN_002_500:   return 250;
+		case SBL_LINK_LEN_002_600:   return 260;
+		case SBL_LINK_LEN_002_620:   return 262;
+		case SBL_LINK_LEN_002_700:   return 270;
+		case SBL_LINK_LEN_002_800:   return 280;
+		case SBL_LINK_LEN_002_900:   return 290;
+		case SBL_LINK_LEN_002_990:   return 299;
+		case SBL_LINK_LEN_003_000:   return 300;
+		case SBL_LINK_LEN_004_000:   return 400;
+		case SBL_LINK_LEN_005_000:   return 500;
+		case SBL_LINK_LEN_006_000:   return 600;
+		case SBL_LINK_LEN_007_000:   return 700;
+		case SBL_LINK_LEN_008_000:   return 800;
+		case SBL_LINK_LEN_010_000:   return 1000;
+		case SBL_LINK_LEN_014_000:   return 1400;
+		case SBL_LINK_LEN_015_000:   return 1500;
+		case SBL_LINK_LEN_019_000:   return 1900;
+		case SBL_LINK_LEN_025_000:   return 2500;
+		case SBL_LINK_LEN_030_000:   return 3000;
+		case SBL_LINK_LEN_035_000:   return 3500;
+		case SBL_LINK_LEN_050_000:   return 5000;
+		case SBL_LINK_LEN_075_000:   return 7500;
+		case SBL_LINK_LEN_100_000:   return 10000;
+		}
+	}
+
+	return 0;
+}
+
+/* calc loop time based on cable length */
+int sbl_media_calc_loop_time(struct sbl_inst *sbl, int port_num, u64 *calc_loop_time)
+{
+	struct sbl_link *link;
+	u32 len;
+	int err;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return err;
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return err;
+
+	if (!calc_loop_time)
+		return -EINVAL;
+
+	link = sbl->link + port_num;
+	*calc_loop_time = 0;
+
+	if ((link->blattr.loopback_mode == SBL_LOOPBACK_MODE_OFF) ||
+	    (link->blattr.loopback_mode == SBL_LOOPBACK_MODE_INVALID)) {
+		len = sbl_media_get_len_cm(sbl, port_num, link->mattr.len);
+		if (!len)
+			return -EBADRQC;
+	} else
+		len = 0;
+
+	sbl_dev_dbg(sbl->dev, "%d: len = %d, lb = %s",
+		    port_num, len, sbl_loopback_mode_str(link->blattr.loopback_mode));
+
+	if (link->mattr.media == SBL_LINK_MEDIA_ELECTRICAL)
+		*calc_loop_time = 2 * len * SBL_MEDIA_NS_PER_M / 100;
+	else
+		*calc_loop_time = (2 * len * SBL_MEDIA_OPTICAL_NS_PER_M / 100) +
+			(4 * SBL_MEDIA_OPTICAL_TRANCEIVER_DELAY);
+	*calc_loop_time += 2 * (SBL_ASIC_TX_DELAY + SBL_ASIC_RX_DELAY);
+
+	sbl_dev_dbg(sbl->dev, "%d: calc loop time %lld cm %lld ns",
+		    port_num, link->mattr.len, *calc_loop_time);
+
+	return 0;
+}
+
+/**
+ * sbl_media_get_loop_time_ns() - Get loop time in ns
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @measurement_only: set to false when reading
+ *
+ * This function returns available loop time. If not and
+ * 'measurement_only' is true, returns 0 with a warning.
+ * Otherwise attempts to calculate the loop time.
+ *
+ * Return: calculate loop time on success, 0 on failure
+ */
+u64 sbl_media_get_loop_time_ns(struct sbl_inst *sbl, int port_num,
+			       bool measurement_only)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u64 calc_loop_time;
+	int err;
+
+	if (!link->mconfigured)
+		return 0;
+
+	if (link->llr_loop_time)
+		return link->llr_loop_time;
+
+	if (measurement_only) {
+		sbl_dev_warn(sbl->dev, "%d: measured loop time not found", port_num);
+		return 0;
+	}
+
+	if (!sbl_media_check_len_supported(sbl, port_num, link->mattr.len))
+		return 0;
+
+	err = sbl_media_calc_loop_time(sbl, port_num, &calc_loop_time);
+	if (err)
+		return 0;
+
+	return calc_loop_time;
+}
+EXPORT_SYMBOL(sbl_media_get_loop_time_ns);
+
+int sbl_media_validate_config(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	if (!(link->mconfigured && link->blconfigured))
+		return -EUCLEAN;
+
+	if ((link->mattr.media == SBL_LINK_MEDIA_ELECTRICAL) &&
+	    (link->blattr.config_target == SBL_BASE_LINK_CONFIG_PEC))
+		return 0;
+
+	if ((link->mattr.media == SBL_LINK_MEDIA_OPTICAL) &&
+	    (link->blattr.config_target == SBL_BASE_LINK_CONFIG_AOC))
+		return 0;
+
+	return -EMEDIUMTYPE;
+}
+
+#ifdef CONFIG_SYSFS
+/**
+ * sbl_media_sysfs_sprint() - Format media 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 state of media configuration and info.
+ * Based on that it will format the string and write into the
+ * provided buffer.
+ *
+ * Return: Number of characters written, at most @size - 1
+ */
+int sbl_media_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->mconfigured) {
+		s += scnprintf(buf + s, size - s, "media: %s, %s, (%s), info ",
+			       sbl_link_media_str(link->mattr.media),
+			       sbl_link_len_str(link->mattr.len),
+			       sbl_link_vendor_str(link->mattr.vendor));
+		if (link->mattr.info) {
+			if (link->mattr.info & SBL_MEDIA_INFO_SUPPORTS_BS_200G)
+				s += scnprintf(buf + s, size - s, "200 ");
+			if (link->mattr.info & SBL_MEDIA_INFO_SUPPORTS_BJ_100G)
+				s += scnprintf(buf + s, size - s, "100bj ");
+			if (link->mattr.info & SBL_MEDIA_INFO_SUPPORTS_CD_100G)
+				s += scnprintf(buf + s, size - s, "100cd ");
+			if (link->mattr.info & SBL_MEDIA_INFO_SUPPORTS_CD_50G)
+				s += scnprintf(buf + s, size - s, "50 ");
+			if (link->mattr.info & SBL_MEDIA_INFO_ANALOG)
+				s += scnprintf(buf + s, size - s, "analog ");
+			if (link->mattr.info & SBL_MEDIA_INFO_DIGITAL)
+				s += scnprintf(buf + s, size - s, "digital ");
+		} else
+			s += scnprintf(buf + s, size - s, "none");
+		s += scnprintf(buf + s, size - s, "\n");
+	}
+	spin_unlock(&link->lock);
+
+	return s;
+}
+EXPORT_SYMBOL(sbl_media_sysfs_sprint);
+#endif
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_media_nic.c b/drivers/net/ethernet/hpe/sbl/sbl_media_nic.c
new file mode 100644
index 000000000000..dc462e17dd72
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_media_nic.c
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2025 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include <linux/hpe/sbl/sbl_pml.h>
+
+#include "sbl_internal.h"
+
+#ifdef CONFIG_SYSFS
+
+/**
+ * sbl_media_type_sysfs_sprint() - Report media type 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 reads the physical type of media used based on mconfigured
+ * and writes a formatted description of the type into the provided buffer
+ *
+ * Return: Number of characters written, at most @size - 1
+ */
+int sbl_media_type_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->mconfigured)
+		s += scnprintf(buf + s, size - s, "%s", sbl_link_media_str(link->mattr.media));
+	else
+		s += scnprintf(buf + s, size - s, "NA");
+
+	spin_unlock(&link->lock);
+
+	return s;
+}
+EXPORT_SYMBOL(sbl_media_type_sysfs_sprint);
+#endif
-- 
2.56.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help