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

From: Doug Ledford

Date: Fri Oct 30 2026 - 17:07:38 EST


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 <sandeep.bhoot@xxxxxxx>
Signed-off-by: Sandeep Bhoot <sandeep.bhoot@xxxxxxx>
Co-developed-by: Mamila Santosh <santosh-kumar.mamila@xxxxxxx>
Signed-off-by: Mamila Santosh <santosh-kumar.mamila@xxxxxxx>
Signed-off-by: Doug Ledford <doug.ledford@xxxxxxx>
---
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