[PATCH 06/23] net: hpe: sbl: add link state management
From: Doug Ledford
Date: Fri Oct 30 2026 - 17:04:31 EST
Add the link state machine: bring-up, auto-negotiation and the NIC
specific link ops. This ties the PML and SerDes layers together and
is what the NIC driver calls to take a link up or down.
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 (22), LONG_LINE_COMMENT (2)
- CHECK: BRACES (22), UNNECESSARY_PARENTHESES (8), COMPARISON_TO_NULL
(1)
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_link.c | 1739 ++++++++++++++++++++++++
drivers/net/ethernet/hpe/sbl/sbl_link_an.c | 1010 ++++++++++++++
drivers/net/ethernet/hpe/sbl/sbl_link_an_nic.c | 111 ++
drivers/net/ethernet/hpe/sbl/sbl_link_nic.c | 196 +++
4 files changed, 3056 insertions(+)
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_link.c b/drivers/net/ethernet/hpe/sbl/sbl_link.c
new file mode 100644
index 000000000000..706fa9ba9676
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_link.c
@@ -0,0 +1,1739 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2019-2026 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/delay.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"
+
+/* detect the presence of our link partner */
+static int sbl_base_link_lp_detect(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ int err = 0;
+
+ /* already detected - probably from autoneg */
+ if (link->lp_detected)
+ return 0;
+
+ sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_LP_DET);
+
+ /* currently we have only one way to do this */
+ if (link->blattr.options & SBL_OPT_SERDES_LPD) {
+ err = sbl_serdes_lp_detect(sbl, port_num);
+ if (!err)
+ link->lp_detected = true;
+ }
+
+ sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_LP_DET);
+
+ return err;
+}
+
+/* Check/recover SBL FW */
+static int sbl_base_link_check_fix_fw(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ int serdes;
+ int err;
+
+ for (serdes = 0; serdes < sbl->switch_info->num_serdes; ++serdes) {
+ err = sbl_validate_serdes_fw_crc(sbl, port_num, serdes);
+ if (err) {
+ /* Any lane with corrputed FW will cause all lanes for
+ * the port to be reloaded.
+ */
+ goto reload_fw;
+ }
+ }
+ return 0;
+
+ reload_fw:
+ err = sbl_serdes_firmware_flash_safe(sbl, port_num, false);
+ if (err) {
+ /* All we can do is report failure here */
+ sbl_dev_err(sbl->dev, "%d: check/fix: fw flash failed [%d]\n",
+ port_num, err);
+ link->sstate = SBL_SERDES_STATUS_ERROR;
+ }
+ return err;
+}
+
+static int sbl_link_get_mode_electrical(struct sbl_inst *sbl, int port_num)
+{
+ sbl_dev_dbg(sbl->dev, "bl %d: elec get mode", port_num);
+
+ return sbl_link_autoneg(sbl, port_num);
+}
+
+/* For now we will just use the configured speed */
+static int sbl_link_get_mode_optical(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+
+ sbl_dev_dbg(sbl->dev, "bl %d: optical link - fixing speed to config value (%s)\n",
+ port_num, sbl_link_mode_str(link->blattr.link_mode));
+ link->link_mode = link->blattr.link_mode;
+
+ return 0;
+}
+
+/* determine the link mode (speed) */
+static int sbl_base_link_get_mode(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ int err = 0;
+
+ /* determine required mode */
+ if (link->loopback_mode == SBL_LOOPBACK_MODE_LOCAL) {
+ /* directly use the mode specified - no media check */
+ link->link_mode = link->blattr.link_mode;
+ return 0;
+ }
+
+ switch (link->mattr.media) {
+
+ case SBL_LINK_MEDIA_ELECTRICAL:
+ err = sbl_link_get_mode_electrical(sbl, port_num);
+ break;
+
+ case SBL_LINK_MEDIA_OPTICAL:
+ err = sbl_link_get_mode_optical(sbl, port_num);
+ break;
+
+ default:
+ sbl_dev_err(sbl->dev, "bl %d: bad media to determine mode", port_num);
+ err = -ENOMEDIUM;
+ break;
+ }
+ if (err)
+ return err;
+
+ /* ensure the media supports the required mode */
+ if (!sbl_media_check_mode_supported(sbl, port_num, link->link_mode)) {
+ sbl_dev_err(sbl->dev, "bl %d: config mode (%s) not supported by media",
+ port_num, sbl_link_mode_str(link->blattr.link_mode));
+ return -EMEDIUMTYPE;
+ } else
+ return 0;
+}
+
+/* return true if get_mode failed because autoneg timed out */
+static bool sbl_base_link_an_timed_out(struct sbl_inst *sbl, int port_num, int err)
+{
+ struct sbl_link *link = sbl->link + port_num;
+
+ if (err != -ETIME)
+ return false;
+
+ if (link->mattr.media != SBL_LINK_MEDIA_ELECTRICAL)
+ return false;
+
+ if (link->blattr.pec.an_mode == SBL_AN_MODE_OFF)
+ return false;
+
+ return true;
+}
+
+static int sbl_link_fault_monitor_start(struct sbl_inst *sbl, int port_num)
+{
+ int err;
+ u32 base = SBL_PML_BASE(port_num);
+ u64 val64;
+ u64 err_flags;
+
+ val64 = sbl_read64(sbl, base | SBL_PML_CFG_LLR_SM_OFFSET);
+ if (SBL_PML_CFG_LLR_SM_REPLAY_CT_MAX_GET(val64) < SBL_LLR_REPLAY_CT_MAX_UNLIMITED)
+ err_flags = SBL_PML_FAULT_ERR_FLAGS;
+ else
+ err_flags = SBL_PML_REC_FAULT_ERR_FLAGS;
+
+ /* make sure we have not already had an error */
+ if (sbl_pml_err_flgs_test(sbl, port_num, err_flags)) {
+ sbl_dev_err(sbl->dev, "fm %d: start - errors already present\n",
+ port_num);
+ err = -ENETDOWN;
+ goto out;
+ }
+
+ sbl_pml_recovery_enable(sbl, port_num);
+
+ err = sbl_pml_install_intr_handler(sbl, port_num, err_flags);
+ if (err) {
+ sbl_dev_err(sbl->dev, "fm %d: intr install failed [%d]\n", port_num, err);
+ goto out;
+ }
+
+ err = sbl_pml_enable_intr_handler(sbl, port_num, err_flags);
+ if (err) {
+ sbl_dev_err(sbl->dev, "fm %d: intr enable failed [%d]\n", port_num, err);
+ goto out_uninstall;
+ return err;
+ }
+
+ if (sbl_pml_err_flgs_test(sbl, port_num, err_flags)) {
+ sbl_dev_err(sbl->dev, "fm %d: link down during start\n", port_num);
+ err = -ENETDOWN;
+ goto out_disable;
+ } else {
+ /* all up and running */
+ sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_FAULT_MON);
+ }
+ return 0;
+
+out_disable:
+ sbl_pml_disable_intr_handler(sbl, port_num, err_flags);
+out_uninstall:
+ sbl_pml_remove_intr_handler(sbl, port_num);
+out:
+ sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_FAULT_MON);
+
+ return err;
+}
+
+static int sbl_link_fault_monitor_stop(struct sbl_inst *sbl, int port_num)
+{
+ int err;
+ u32 base = SBL_PML_BASE(port_num);
+ u64 val64;
+ u64 err_flags;
+
+ val64 = sbl_read64(sbl, base | SBL_PML_CFG_LLR_SM_OFFSET);
+ if (SBL_PML_CFG_LLR_SM_REPLAY_CT_MAX_GET(val64) < SBL_LLR_REPLAY_CT_MAX_UNLIMITED)
+ err_flags = SBL_PML_FAULT_ERR_FLAGS;
+ else
+ err_flags = SBL_PML_REC_FAULT_ERR_FLAGS;
+
+ err = sbl_pml_disable_intr_handler(sbl, port_num, err_flags);
+ if (err) {
+ sbl_dev_err(sbl->dev, "fm %d: intr disable failed [%d]\n", port_num, err);
+ return err;
+ }
+
+ err = sbl_pml_remove_intr_handler(sbl, port_num);
+ if (err) {
+ sbl_dev_err(sbl->dev, "fm %d: intr remove failed [%d]\n", port_num, err);
+ return err;
+ }
+
+ sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_FAULT_MON);
+ return 0;
+}
+
+static void sbl_base_link_report_err(struct sbl_inst *sbl, char *txt, int port_num, int err)
+{
+ switch (err) {
+ case -ECANCELED:
+ sbl_dev_dbg(sbl->dev, "bl %d: %s cancelled\n", port_num, txt);
+ break;
+ case -ETIMEDOUT:
+ sbl_dev_dbg(sbl->dev, "bl %d: %s timed out\n", port_num, txt);
+ break;
+ default:
+ sbl_dev_err(sbl->dev, "bl %d: %s failed [%d]\n", port_num, txt, err);
+ }
+}
+
+/**
+ * sbl_get_lp_subtype() - Get link partner sub type
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @lp_subtype: Value to get Link partner subtype
+ *
+ * This function reads the versions used (cas(v1/v2))
+ * which refers to link partner sub type. Client driver
+ * uses this information for later process
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int sbl_get_lp_subtype(struct sbl_inst *sbl, int port_num, enum sbl_lp_subtype *lp_subtype)
+{
+ int err;
+ struct sbl_link *link;
+
+ err = sbl_validate_instance(sbl);
+ if (err)
+ return err;
+
+ err = sbl_validate_port_num(sbl, port_num);
+ if (err)
+ return err;
+
+ if (lp_subtype == NULL)
+ return -EINVAL;
+
+ sbl_dev_dbg(sbl->dev, "%d: get lp subtype", port_num);
+
+ link = sbl->link + port_num;
+
+ *lp_subtype = link->lp_subtype;
+
+ return 0;
+}
+EXPORT_SYMBOL(sbl_get_lp_subtype);
+
+/**
+ * sbl_base_link_config() - config base link
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @blattr: Pointer to struct sbl_base_link_attr
+ *
+ * Set base link config based on attributes provided
+ *
+ * Context: Process context, Acquires lock and release link->lock <spin_lock>
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int sbl_base_link_config(struct sbl_inst *sbl, int port_num, struct sbl_base_link_attr *blattr)
+{
+ struct sbl_link *link;
+ int err;
+
+ err = sbl_validate_instance(sbl);
+ if (err)
+ return err;
+
+ err = sbl_validate_port_num(sbl, port_num);
+ if (err)
+ return err;
+
+ sbl_dev_dbg(sbl->dev, "bl %d: config\n", port_num);
+
+ if (!blattr)
+ return -EINVAL;
+
+ if (blattr->magic != SBL_LINK_ATTR_MAGIC)
+ return -EINVAL;
+
+ link = sbl->link + port_num;
+
+ spin_lock(&link->lock);
+
+ if (!(link->blstate & (SBL_BASE_LINK_STATUS_DOWN | SBL_BASE_LINK_STATUS_UNCONFIGURED))) {
+
+ sbl_dev_err(sbl->dev, "bl %d: wrong state (%s) for config", port_num,
+ sbl_link_state_str(link->blstate));
+ spin_unlock(&link->lock);
+ return -EUCLEAN;
+ }
+
+ memcpy(&link->blattr, blattr, sizeof(struct sbl_base_link_attr));
+
+ link->blconfigured = true;
+
+ if (link->blattr.loopback_mode == SBL_LOOPBACK_MODE_LOCAL) {
+ /* we dont need media to go to down */
+ link->blstate = SBL_BASE_LINK_STATUS_DOWN;
+ } else if (link->mconfigured) {
+ /* we need media as well if not loopback */
+ link->blstate = SBL_BASE_LINK_STATUS_DOWN;
+ }
+
+ spin_unlock(&link->lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(sbl_base_link_config);
+
+/**
+ * sbl_base_link_start() - Initialize and bring up links
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * This function performs full sequence to bring up serdes link.
+ * It handles firmware validation, link mode detection, alignment,
+ * SerDes startup and link integrity checks. This process ensures
+ * link partner is detected and all lanes are properly initialized
+ * and active before reporting the link is up
+ *
+ * Context: Process, May sleep. Uses mutex_lock_interruptible
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int sbl_base_link_start(struct sbl_inst *sbl, int port_num)
+{
+ int err;
+ int tmp_err;
+ u32 base = SBL_PML_BASE(port_num);
+ u64 cfg_pcs_reg;
+ u64 sts_pcs_lane_degrade_reg;
+ struct lane_degrade lanes;
+ u32 ucw_adj = 100;
+ u32 ccw_adj = 0;
+ s32 ucw_in = SBL_LINK_FEC_HPE;
+ s32 ccw_in = SBL_LINK_FEC_HPE;
+ struct sbl_link *link;
+
+ err = sbl_validate_instance(sbl);
+ if (err)
+ return err;
+
+ err = sbl_validate_port_num(sbl, port_num);
+ if (err)
+ return err;
+
+ sbl_dev_dbg(sbl->dev, "bl %d: start\n", port_num);
+
+ link = sbl->link + port_num;
+
+ err = mutex_lock_interruptible(&link->busy_mtx);
+ if (err)
+ return -ERESTARTSYS;
+
+ if (link->blstate == SBL_BASE_LINK_STATUS_UP) {
+ /* link came up while waiting for the mutex */
+ mutex_unlock(&link->busy_mtx);
+ return 0;
+ }
+
+ if (link->blstate != SBL_BASE_LINK_STATUS_DOWN) {
+
+ sbl_dev_err(sbl->dev, "bl %d: wrong state (%s) for start", port_num,
+ sbl_link_state_str(link->blstate));
+ mutex_unlock(&link->busy_mtx);
+ return -EUCLEAN;
+ }
+
+ /* only local loopback is possible without media */
+ if (link->blattr.loopback_mode != SBL_LOOPBACK_MODE_LOCAL) {
+ err = sbl_media_validate_config(sbl, port_num);
+ if (err) {
+ sbl_dev_err(sbl->dev, "bl %d: config unsuitable for media type", port_num);
+ mutex_unlock(&link->busy_mtx);
+ return err;
+ }
+ }
+
+ link->blstate = SBL_BASE_LINK_STATUS_STARTING;
+
+ /* check for loopback mode change */
+ if (link->blattr.loopback_mode != link->loopback_mode)
+ link->llr_loop_time = 0;
+ link->loopback_mode = link->blattr.loopback_mode;
+
+ /* setup the timeout for start to complete
+ * (autoneg might modify this later)
+ */
+ sbl_link_init_start_timeout(sbl, port_num);
+
+ /* clear out any residual pcs state */
+ sbl_pml_set_defaults(sbl, port_num);
+
+ /* reset state */
+ link->lp_detected = false;
+
+ /* validate serdes firmwares are (still) uncorrupted, recover them if
+ * needed
+ */
+ err = sbl_base_link_check_fix_fw(sbl, port_num);
+ if (err) {
+ sbl_base_link_report_err(sbl, "ensure_healthly", port_num, err);
+ goto out;
+ }
+
+ /* determine the link mode
+ * this may do autoneg for electrical links
+ */
+ err = sbl_base_link_get_mode(sbl, port_num);
+ if (err) {
+ if (sbl_base_link_an_timed_out(sbl, port_num, err))
+ sbl_dev_dbg(sbl->dev, "bl %d: autoneg timeout", port_num);
+ else
+ sbl_base_link_report_err(sbl, "get_mode", port_num, err);
+ goto out;
+ }
+
+ /* start sending alignment markers for lp to tune against */
+ err = sbl_pml_pcs_am_start(sbl, port_num);
+ if (err) {
+ sbl_dev_err(sbl->dev, "bl %d: am_start failed [%d]\n", port_num, err);
+ goto out;
+ }
+
+ /* wait until we detect the link partner */
+ err = sbl_base_link_lp_detect(sbl, port_num);
+ if (err) {
+ sbl_base_link_report_err(sbl, "lpd", port_num, err);
+ goto out;
+ }
+
+ /* record when we really start to bring up the link */
+ sbl_link_up_begin(sbl, port_num);
+
+ /* start the serdes */
+ err = sbl_serdes_start(sbl, port_num);
+ if (err) {
+ sbl_base_link_report_err(sbl, "serdes_start", port_num, err);
+ goto out;
+ }
+
+ /* start the pml block (pcs,mac,llr) */
+ err = sbl_pml_start(sbl, port_num);
+ if (err) {
+ sbl_base_link_report_err(sbl, "pml_start", port_num, err);
+ goto out_serdes;
+ }
+
+ /* clearing fec values before start */
+ sbl_zero_all_fec_counts(sbl, port_num);
+
+ /* setup thresholds and adjustments for fec monitoring */
+ err = sbl_fec_thresholds_set(sbl, port_num, ucw_in, ccw_in);
+ if (err) {
+ sbl_dev_err(sbl->dev, "%d: setting fec thresholds failed [%d]",
+ port_num, err);
+ goto out_pcs;
+ }
+
+ err = sbl_fec_adjustments_set(sbl, port_num, ucw_adj, ccw_adj);
+ if (err) {
+ sbl_dev_err(sbl->dev, "%d: setting fec adjustments failed [%d]",
+ port_num, err);
+ goto out_pcs;
+ }
+
+ /* start fec checking */
+ err = sbl_fec_up_check(sbl, port_num);
+ if (err) {
+ unsigned long flags;
+
+ sbl_serdes_invalidate_tuning_params(sbl, port_num);
+ sbl_dev_info(sbl->dev, "%d: failed start fec check", port_num);
+
+ /* SSHOTPLAT-5697 forces a maximum effort tune
+ * straight away after FEC up check fails.
+ */
+ sbl_dev_dbg(sbl->dev, "bl %d: forcing a maximum effort tune for next retry", port_num);
+ spin_lock_irqsave(&link->lock, flags);
+ link->blattr.options |= SBL_OPT_DFE_ALWAYS_MAX_EFFORT;
+ spin_unlock_irqrestore(&link->lock, flags);
+
+ goto out_pcs;
+ }
+
+ /* start monitoring for link faults */
+ err = sbl_link_fault_monitor_start(sbl, port_num);
+ if (err) {
+ sbl_dev_err(sbl->dev, "bl %d: link fault detect start failed [%d]\n", port_num, err);
+ goto out_pcs;
+ }
+
+ /* final check the link is still up and all lanes remain active */
+ if (!sbl_pml_pcs_up(sbl, port_num)) {
+ sbl_dev_err(sbl->dev, "bl %d: link failed during startup\n", port_num);
+ err = -ENETDOWN;
+ goto out_fm;
+ }
+
+ cfg_pcs_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET);
+ if (SBL_PML_CFG_PCS_ENABLE_AUTO_LANE_DEGRADE_GET(cfg_pcs_reg)) {
+
+ sts_pcs_lane_degrade_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_LANE_DEGRADE_OFFSET);
+ lanes.tx = SBL_PML_STS_PCS_LANE_DEGRADE_LP_PLS_AVAILABLE_GET(sts_pcs_lane_degrade_reg);
+ lanes.rx = SBL_PML_STS_PCS_LANE_DEGRADE_RX_PLS_AVAILABLE_GET(sts_pcs_lane_degrade_reg);
+
+ if (lanes.tx != SBL_LINK_ALL_LANES || lanes.rx != SBL_LINK_ALL_LANES) {
+ sbl_dev_err(sbl->dev, "bl %d: lane failed during startup - TX: 0x%llx - RX: 0x%llx",
+ port_num, lanes.tx, lanes.rx);
+ err = -ENETDOWN;
+ goto out_fm;
+ }
+ }
+
+ /* success ! */
+ sbl_link_up_record_timespec(sbl, port_num);
+ sbl_link_start_record_timespec(sbl, port_num);
+
+ link->blstate = SBL_BASE_LINK_STATUS_UP;
+ link->blerr = 0;
+
+ sbl_dev_dbg(sbl->dev, "starting timer port_num:%d ", port_num);
+ sbl_fec_monitor_start(sbl, port_num);
+ mutex_unlock(&link->busy_mtx);
+
+ return 0;
+
+ /* failed */
+out_fm:
+ sbl_link_fault_monitor_stop(sbl, port_num);
+out_pcs:
+ if (!sbl_debug_option(sbl, port_num, SBL_DEBUG_INHIBIT_CLEANUP))
+ sbl_pml_link_down(sbl, port_num);
+out_serdes:
+ if (!sbl_debug_option(sbl, port_num, SBL_DEBUG_INHIBIT_CLEANUP))
+ sbl_serdes_stop(sbl, port_num);
+out:
+ if (sbl_debug_option(sbl, port_num, SBL_DEBUG_INHIBIT_CLEANUP) ||
+ sbl_debug_option(sbl, port_num, SBL_DEBUG_INHIBIT_RELOAD_FW))
+ link->reload_serdes_fw = false;
+ else if (sbl_debug_option(sbl, port_num, SBL_DEBUG_FORCE_RELOAD_FW))
+ link->reload_serdes_fw = true;
+
+ if (link->reload_serdes_fw) {
+ sbl_dev_info(sbl->dev, "bl %d: reloading serdes fw\n", port_num);
+ tmp_err = sbl_serdes_firmware_flash_safe(sbl, port_num, true);
+ if (tmp_err) {
+ /* All we can do is report failure here */
+ sbl_dev_err(sbl->dev, "bl %d: fw flash failed [%d]\n",
+ port_num, tmp_err);
+ link->sstate = SBL_SERDES_STATUS_ERROR;
+ } else
+ link->sstate = SBL_SERDES_STATUS_DOWN;
+ link->serr = tmp_err;
+ link->reload_serdes_fw = false;
+ }
+ link->blstate = SBL_BASE_LINK_STATUS_ERROR;
+ link->blerr = err;
+ mutex_unlock(&link->busy_mtx);
+
+ return err;
+}
+EXPORT_SYMBOL(sbl_base_link_start);
+
+/**
+ * sbl_ignore_save_tuning_param() - Config ignore save tuning parameters
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @ignore: If set then ignore save tuning
+ *
+ * This function performs the Load saved tuning params before tuning
+ * when 'ignore' is set false from caller.
+ */
+void sbl_ignore_save_tuning_param(struct sbl_inst *sbl, int port_num, bool ignore)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ unsigned long flags;
+
+ spin_lock_irqsave(&link->lock, flags);
+ if (ignore)
+ link->blattr.options &= ~SBL_OPT_USE_SAVED_PARAMS;
+ else
+ link->blattr.options |= SBL_OPT_USE_SAVED_PARAMS;
+ spin_unlock_irqrestore(&link->lock, flags);
+}
+EXPORT_SYMBOL(sbl_ignore_save_tuning_param);
+
+/**
+ * sbl_enable_opt_lane_degrade() - Enable opt lane degrade
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @enable: if set then enable OPT LANE DEGRADE
+ *
+ * This function performs enable auto lane degrade when
+ * 'enable' is set from the caller
+ */
+void sbl_enable_opt_lane_degrade(struct sbl_inst *sbl, int port_num, bool enable)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ unsigned long flags;
+
+ spin_lock_irqsave(&link->lock, flags);
+ if (enable)
+ link->blattr.options |= SBL_OPT_LANE_DEGRADE;
+ else
+ link->blattr.options &= ~SBL_OPT_LANE_DEGRADE;
+ spin_unlock_irqrestore(&link->lock, flags);
+}
+EXPORT_SYMBOL(sbl_enable_opt_lane_degrade);
+
+/**
+ * sbl_disable_pml_recovery() - Disable PML recovery
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @disable: if set, then disable PML recovery
+ *
+ * This function performs disable PML recovery
+ * when 'disable' is set from the caller
+ */
+void sbl_disable_pml_recovery(struct sbl_inst *sbl, int port_num, bool disable)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ unsigned long flags;
+
+ spin_lock_irqsave(&link->lock, flags);
+ if (disable)
+ link->blattr.options |= SBL_DISABLE_PML_RECOVERY;
+ else
+ link->blattr.options &= ~SBL_DISABLE_PML_RECOVERY;
+ spin_unlock_irqrestore(&link->lock, flags);
+}
+EXPORT_SYMBOL(sbl_disable_pml_recovery);
+
+/**
+ * sbl_set_degraded_flag() - Set degraded flag
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Context: Process context, Acquires lock and releases is_degraded_lock <spin_lock>
+ */
+void sbl_set_degraded_flag(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+
+ spin_lock(&link->is_degraded_lock);
+ link->is_degraded = true;
+ spin_unlock(&link->is_degraded_lock);
+}
+EXPORT_SYMBOL(sbl_set_degraded_flag);
+
+/**
+ * sbl_clear_degraded_flag() - Clear degraded flag
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Context: Process context, Acquires lock and releases is_degraded_lock <spin_lock>
+ */
+void sbl_clear_degraded_flag(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+
+ spin_lock(&link->is_degraded_lock);
+ link->is_degraded = false;
+ spin_unlock(&link->is_degraded_lock);
+}
+EXPORT_SYMBOL(sbl_clear_degraded_flag);
+
+/**
+ * sbl_get_degraded_flag() - Get degraded flag
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Context: Process context, Acquires lock and releases is_degraded_lock <spin_lock>
+ *
+ * Return: status of degraded flag
+ */
+bool sbl_get_degraded_flag(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ bool degraded_flag;
+
+ spin_lock(&link->is_degraded_lock);
+ degraded_flag = link->is_degraded;
+ spin_unlock(&link->is_degraded_lock);
+
+ return degraded_flag;
+}
+EXPORT_SYMBOL(sbl_get_degraded_flag);
+
+/**
+ * sbl_base_link_enable_start() - Enable base link start
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Context: Process context, Acquires lock and releases link->lock <spin_lock>
+ *
+ * Return: 0
+ */
+int sbl_base_link_enable_start(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+
+ sbl_dev_dbg(sbl->dev, "bl %d: enable start\n", port_num);
+
+ spin_lock(&link->lock);
+ link->start_cancelled = false;
+ spin_unlock(&link->lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(sbl_base_link_enable_start);
+
+/**
+ * sbl_base_link_cancel_start() - Cancel base link start
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Context: Process context, Acquires lock and releases link->lock <spin_lock>
+ *
+ * Return: 0
+ */
+int sbl_base_link_cancel_start(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+
+ sbl_dev_dbg(sbl->dev, "bl %d: cancelling start\n", port_num);
+
+ spin_lock(&link->lock);
+ link->start_cancelled = true;
+ complete_all(&link->an_hw_change);
+ spin_unlock(&link->lock);
+ sbl_fec_monitor_stop(sbl, port_num);
+
+ return 0;
+}
+EXPORT_SYMBOL(sbl_base_link_cancel_start);
+
+bool sbl_base_link_start_cancelled(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ bool cancelled;
+
+ spin_lock(&link->lock);
+ cancelled = link->start_cancelled;
+ spin_unlock(&link->lock);
+
+ return cancelled;
+}
+
+/**
+ * sbl_base_link_stop() - Stop base link
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * This function cleanly shuts down previously initialized
+ * SerDes link, link monitoring and stops the PML block.
+ *
+ * Context: Process, May sleep. Uses mutex_lock_interruptible
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int sbl_base_link_stop(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link;
+ int err;
+
+ err = sbl_validate_instance(sbl);
+ if (err)
+ return err;
+
+ err = sbl_validate_port_num(sbl, port_num);
+ if (err)
+ return err;
+
+ sbl_dev_dbg(sbl->dev, "bl %d: stop", port_num);
+
+ link = sbl->link + port_num;
+
+ err = mutex_lock_interruptible(&link->busy_mtx);
+ if (err)
+ return -ERESTARTSYS;
+
+ sbl_fec_monitor_stop(sbl, port_num);
+
+ if (link->blstate == SBL_BASE_LINK_STATUS_DOWN) {
+ /* link went down while waiting for the mutex */
+ mutex_unlock(&link->busy_mtx);
+ return 0;
+ }
+
+ if (link->blstate != SBL_BASE_LINK_STATUS_UP) {
+ sbl_dev_err(sbl->dev, "bl %d: not up (%s) for stop - ignored", port_num,
+ sbl_link_state_str(link->blstate));
+ mutex_unlock(&link->busy_mtx);
+ return -EBUSY;
+ }
+
+ /* if keeping serdes up, don't change state to stopping */
+ if (!sbl_debug_option(sbl, port_num, SBL_DEBUG_KEEP_SERDES_UP))
+ link->blstate = SBL_BASE_LINK_STATUS_STOPPING;
+
+ /* We stop the serdes before stopping the pml to avoid breaking AOC firmware,
+ * really we should stop the pml first. This should be harmless provided
+ * down detection is off.
+ */
+ err = sbl_link_fault_monitor_stop(sbl, port_num);
+ if (err) {
+ sbl_dev_err(sbl->dev, "bl %d: link_fault_monitor_stop failed [%d]", port_num, err);
+ goto out;
+ }
+
+ sbl_pml_recovery_cancel(sbl, port_num);
+
+ err = sbl_serdes_stop(sbl, port_num);
+ if (err) {
+ sbl_dev_err(sbl->dev, "bl %d: serdes_stop failed [%d]", port_num, err);
+ goto out;
+ }
+
+ err = sbl_pml_link_down(sbl, port_num);
+ if (err) {
+ sbl_dev_err(sbl->dev, "bl %d: pml_link_down failed [%d]", port_num, err);
+ goto out;
+ }
+
+out:
+ spin_lock(&link->lock);
+ if (err)
+ link->blstate = SBL_BASE_LINK_STATUS_ERROR;
+ /* if keep serdes up, don't change state to down */
+ else if (!sbl_debug_option(sbl, port_num, SBL_DEBUG_KEEP_SERDES_UP))
+ link->blstate = SBL_BASE_LINK_STATUS_DOWN;
+ link->blerr = err;
+ spin_unlock(&link->lock);
+
+ mutex_unlock(&link->busy_mtx);
+
+ sbl_dev_dbg(sbl->dev, "bl %d: stop done", port_num);
+
+ return link->blerr;
+}
+EXPORT_SYMBOL(sbl_base_link_stop);
+
+/**
+ * sbl_base_link_reset() - Reset base link
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * This function resets the base link by disabling
+ * pml interrupt handlers, resets serdes and PML block.
+ *
+ * Context: Process, May sleep. Uses mutex_lock_interruptible
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int sbl_base_link_reset(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link;
+ int err;
+
+ err = sbl_validate_instance(sbl);
+ if (err)
+ return err;
+
+ err = sbl_validate_port_num(sbl, port_num);
+ if (err)
+ return err;
+
+ sbl_dev_dbg(sbl->dev, "bl %d: reset\n", port_num);
+
+ link = sbl->link + port_num;
+
+ err = mutex_lock_interruptible(&link->busy_mtx);
+ if (err)
+ return -ERESTARTSYS;
+
+ sbl_fec_monitor_stop(sbl, port_num);
+
+ link->blstate = SBL_BASE_LINK_STATUS_RESETTING;
+
+ /* disable and remove any pml intr handlers */
+ if (link->intr_err_flgs) {
+ sbl_pml_disable_intr_handler(sbl, port_num, link->intr_err_flgs);
+ sbl_pml_remove_intr_handler(sbl, port_num);
+ link->intr_err_flgs = 0;
+ }
+
+ sbl_pml_recovery_cancel(sbl, port_num);
+
+ err = sbl_serdes_reset(sbl, port_num);
+ if (err)
+ sbl_dev_err(sbl->dev, "bl %d: reset: serdes_reset failed [%d]", port_num, err);
+
+ err = sbl_pml_link_down(sbl, port_num);
+ if (err)
+ sbl_dev_warn(sbl->dev, "bl %d: reset: pcs_link_down failed [%d]", port_num, err);
+
+ link->blstate = SBL_BASE_LINK_STATUS_UNCONFIGURED;
+ link->blerr = 0;
+ link->blconfigured = false;
+ link->pcs_config = false;
+ link->llr_loop_time = 0;
+ link->start_cancelled = false;
+ WRITE_ONCE(link->link_info, 0);
+ link->lp_subtype = SBL_LP_SUBTYPE_INVALID;
+ /* dont reset media attribute */
+
+ /* reset hw */
+ sbl_pml_set_defaults(sbl, port_num);
+
+ sbl_pml_err_flgs_clear_all(sbl, port_num);
+
+ mutex_unlock(&link->busy_mtx);
+
+ return 0;
+}
+EXPORT_SYMBOL(sbl_base_link_reset);
+
+/**
+ * sbl_base_link_try_start_fail_cleanup() - Clean up resources
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Try to clear any error states that are non-fatal so we can directly
+ * attempt another start up.
+ *
+ * Context: Process, May sleep. Uses mutex_lock_interruptible
+ */
+void sbl_base_link_try_start_fail_cleanup(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link;
+ int err;
+
+ err = sbl_validate_instance(sbl);
+ if (err)
+ return;
+
+ err = sbl_validate_port_num(sbl, port_num);
+ if (err)
+ return;
+
+ sbl_dev_dbg(sbl->dev, "bl %d: try start fail cleanup\n", port_num);
+
+ link = sbl->link + port_num;
+
+ err = mutex_lock_interruptible(&link->busy_mtx);
+ if (err)
+ return;
+
+ /* disable and remove any pml intr handlers */
+ if (link->intr_err_flgs) {
+ sbl_pml_disable_intr_handler(sbl, port_num, link->intr_err_flgs);
+ sbl_pml_remove_intr_handler(sbl, port_num);
+ link->intr_err_flgs = 0;
+ }
+
+ /* when debugging we can leave the hw in its current state */
+ if (sbl_debug_option(sbl, port_num, SBL_DEBUG_INHIBIT_CLEANUP)) {
+ sbl_dev_warn(sbl->dev, "%d: hw cleanup inhibited\n", port_num);
+ mutex_unlock(&link->busy_mtx);
+ return;
+ }
+
+ /* clean up the serdes state
+ * We have to do this before resetting the PML or the optical transceivers can
+ * fail.
+ */
+ switch (link->sstate) {
+ case SBL_SERDES_STATUS_RUNNING:
+ sbl_serdes_stop(sbl, port_num);
+ break;
+ case SBL_SERDES_STATUS_AUTONEG:
+ sbl_an_serdes_stop(sbl, port_num);
+ break;
+ default:
+ break;
+ }
+
+ /* still not down! */
+ if (link->sstate != SBL_SERDES_STATUS_DOWN)
+ sbl_serdes_reset(sbl, port_num);
+
+ /* some errors we can clean up and move to down so
+ * we can try to come up again directly
+ */
+ if (link->blstate == SBL_BASE_LINK_STATUS_ERROR) {
+ switch (link->blerr) {
+
+ case -EADV: /* pcs startup saw high serdes error rate */
+ case -EBADE: /* bad page exchange during auto neg */
+ case -ENETDOWN: /* pcs dropped out after starting */
+ case -ECHRNG: /* serdes eye heights went bad during pcs startup */
+ case -ELNRNG: /* unable to tune properly */
+ sbl_serdes_invalidate_all_tuning_params(sbl);
+ fallthrough;
+ case -ETIME:
+ case -ETIMEDOUT:
+ case -EOVERFLOW:
+ case -ECANCELED:
+ case -ENOSR: /* llr failed to start */
+
+ link->blstate = SBL_BASE_LINK_STATUS_DOWN;
+ link->blerr = 0;
+ link->pcs_config = false;
+ link->llr_loop_time = 0;
+ link->start_cancelled = false;
+
+ sbl_pml_link_down(sbl, port_num);
+ sbl_pml_set_defaults(sbl, port_num);
+ break;
+
+ default:
+ sbl_dev_err(sbl->dev, "%d: start fail cleanup not clearing err %d\n",
+ port_num, link->blerr);
+ break;
+ }
+ }
+
+ mutex_unlock(&link->busy_mtx);
+}
+EXPORT_SYMBOL(sbl_base_link_try_start_fail_cleanup);
+
+/**
+ * sbl_base_link_get_status() - Get link status
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @blstate: link state
+ * @blerr: link err
+ * @sstate: state
+ * @serr: err
+ * @media_type: media type
+ * @link_mode: link mode
+ *
+ * This function gets base link status from params
+ * used
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int sbl_base_link_get_status(struct sbl_inst *sbl, int port_num,
+ int *blstate, int *blerr, int *sstate, int *serr,
+ int *media_type, int *link_mode)
+{
+ struct sbl_link *link;
+ int err;
+
+ err = sbl_validate_instance(sbl);
+ if (err)
+ return err;
+
+ err = sbl_validate_port_num(sbl, port_num);
+ if (err)
+ return err;
+
+ link = sbl->link + port_num;
+
+ if (blstate)
+ *blstate = link->blstate;
+ if (blerr)
+ *blerr = link->blerr;
+ if (sstate)
+ *sstate = link->sstate;
+ if (serr)
+ *serr = link->serr;
+ if (media_type)
+ *media_type = link->mattr.media;
+
+ if (link_mode) {
+ if (link->sstate == SBL_SERDES_STATUS_RUNNING)
+ *link_mode = link->link_mode; /* actual mode */
+ else
+ *link_mode = link->blattr.link_mode; /* target mode */
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(sbl_base_link_get_status);
+
+/**
+ * sbl_base_link_state_str() - Read the state of the pcs and serdes
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @buf: Destination buffer to write the data
+ * @len: Length of data to write
+ *
+ * This function reads the state of pcs and serdes
+ * and return in the string format
+ *
+ * Return: Number of characters written on success
+ */
+char *sbl_base_link_state_str(struct sbl_inst *sbl, int port_num, char *buf, int len)
+{
+ char pcs_buf[SBL_PCS_STATE_STR_LEN];
+ u8 not_idle_map; /* not signalling electrical idle */
+ u8 good_eyes_map;
+ u8 active_map;
+
+ if (!buf || !len)
+ return "";
+
+ sbl_pml_pcs_state_str(sbl, port_num, pcs_buf, SBL_PCS_STATE_STR_LEN);
+
+ if (sbl_port_get_serdes_state_maps(sbl, port_num, &active_map,
+ ¬_idle_map, &good_eyes_map)) {
+ /* only active map unavailable */
+ snprintf(buf, len, "pcs %s, serdes -,-/%x", pcs_buf, active_map);
+ } else
+ snprintf(buf, len, "pcs %s, serdes %x,%x/%x", pcs_buf,
+ not_idle_map, good_eyes_map, active_map);
+
+ return buf;
+}
+EXPORT_SYMBOL(sbl_base_link_state_str);
+
+/**
+ * sbl_link_get_ucw_thresh_hpe() - uncorrected codeword thresholds(hpe)
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * This function reads ucw(hpe) based on link mode
+ *
+ * Return: Threshold value on success, 0 on failure.
+ */
+u64 sbl_link_get_ucw_thresh_hpe(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+
+ /* return a little under 1e-10 for all modes and cable types */
+ switch (link->link_mode) {
+ case SBL_LINK_MODE_BS_200G:
+ return 21;
+ case SBL_LINK_MODE_BJ_100G:
+ case SBL_LINK_MODE_CD_100G:
+ return 11;
+ case SBL_LINK_MODE_CD_50G:
+ return 5;
+ default:
+ sbl_dev_err(sbl->dev, "%d: cannot determine ucw rate", port_num);
+ return 0;
+ }
+}
+EXPORT_SYMBOL(sbl_link_get_ucw_thresh_hpe);
+
+/**
+ * sbl_link_get_ucw_thresh_ieee() - uncorrected codeword thresholds(ieee)
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * This function reads ucw(ieee).
+ *
+ * Return: Threshold value on success, 0 on failure.
+ */
+u64 sbl_link_get_ucw_thresh_ieee(struct sbl_inst *sbl, int port_num)
+{
+ /* currently these are the same as the hpe values */
+ return sbl_link_get_ucw_thresh_hpe(sbl, port_num);
+}
+EXPORT_SYMBOL(sbl_link_get_ucw_thresh_ieee);
+
+/**
+ * sbl_link_get_ccw_thresh_hpe() - corrected codeword thresholds
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * This function reads ccw(hpe) based on link mode
+ *
+ * Return: Threshold value on success, 0 on failure.
+ */
+u64 sbl_link_get_ccw_thresh_hpe(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+
+ /* for electrical links use a threshold of 1.e-5
+ * (we consider loopback links to be electrical)
+ */
+ if ((link->mattr.media == SBL_LINK_MEDIA_ELECTRICAL) ||
+ (link->loopback_mode == SBL_LOOPBACK_MODE_LOCAL)) {
+ switch (link->link_mode) {
+ case SBL_LINK_MODE_BS_200G:
+ return 21250000;
+ case SBL_LINK_MODE_BJ_100G:
+ return 10312500;
+ case SBL_LINK_MODE_CD_100G:
+ return 10625000;
+ case SBL_LINK_MODE_CD_50G:
+ return 5312500;
+ default:
+ sbl_dev_err(sbl->dev, "%d: cannot determine PEC ccw rate", port_num);
+ return 0;
+ }
+
+ }
+
+ /* optical links seem to require a threshold of about 4e-5
+ * (otherwise pcal can sometimes take us over the threshold)
+ */
+ if (link->mattr.media == SBL_LINK_MEDIA_OPTICAL) {
+ switch (link->link_mode) {
+ case SBL_LINK_MODE_BS_200G:
+ return 85000000;
+ case SBL_LINK_MODE_BJ_100G:
+ return 41250000;
+ case SBL_LINK_MODE_CD_100G:
+ return 42500000;
+ case SBL_LINK_MODE_CD_50G:
+ return 21250000;
+ default:
+ sbl_dev_err(sbl->dev, "%d: cannot determine AOC ccw rate", port_num);
+ return 0;
+ }
+ }
+
+ /* dont recognise media */
+ sbl_dev_err(sbl->dev, "%d: cannot determine ccw rate - unrecognised media", port_num);
+ return 0;
+}
+EXPORT_SYMBOL(sbl_link_get_ccw_thresh_hpe);
+
+/**
+ * sbl_link_get_ccw_thresh_ieee() - Get correcred code words threshold
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * This function reads ccw(ieee) based on link mode
+ *
+ * Return: Threshold value on success, 0 on failure.
+ */
+u64 sbl_link_get_ccw_thresh_ieee(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+
+ /* ieee thresholds are about 2e-4
+ * We dont care about media type
+ */
+ switch (link->link_mode) {
+ case SBL_LINK_MODE_BS_200G:
+ return 42500000;
+ case SBL_LINK_MODE_BJ_100G:
+ case SBL_LINK_MODE_CD_100G:
+ return 21250000;
+ case SBL_LINK_MODE_CD_50G:
+ return 10625000;
+ default:
+ sbl_dev_err(sbl->dev, "%d: cannot determine ccw rate", port_num);
+ return 0;
+ }
+}
+EXPORT_SYMBOL(sbl_link_get_ccw_thresh_ieee);
+
+/**
+ * sbl_link_get_stp_ccw_thresh_hpe() - Get Corrected code words(hpe) threshold
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Get Corrected code words(hpe) threshold when STP is used
+ *
+ * Return: Threshold value on success, 0 on failure.
+ */
+u64 sbl_link_get_stp_ccw_thresh_hpe(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+
+ /* for electrical links use a threshold of 1.e-8
+ * (we consider loopback links to be electrical)
+ */
+ if ((link->mattr.media == SBL_LINK_MEDIA_ELECTRICAL) ||
+ (link->loopback_mode == SBL_LOOPBACK_MODE_LOCAL)) {
+ switch (link->link_mode) {
+ case SBL_LINK_MODE_BS_200G:
+ return 2125;
+ case SBL_LINK_MODE_BJ_100G:
+ return 1031;
+ case SBL_LINK_MODE_CD_100G:
+ return 1062;
+ case SBL_LINK_MODE_CD_50G:
+ return 531;
+ default:
+ sbl_dev_err(sbl->dev, "%d: cannot determine PEC stp ccw rate", port_num);
+ return 0;
+ }
+
+ }
+
+ /* optical links seem to require a threshold of about 4e-8
+ * (otherwise pcal can sometimes take us over the threshold)
+ */
+ if (link->mattr.media == SBL_LINK_MEDIA_OPTICAL) {
+ switch (link->link_mode) {
+ case SBL_LINK_MODE_BS_200G:
+ return 8500;
+ case SBL_LINK_MODE_BJ_100G:
+ return 4125;
+ case SBL_LINK_MODE_CD_100G:
+ return 4250;
+ case SBL_LINK_MODE_CD_50G:
+ return 2125;
+ default:
+ sbl_dev_err(sbl->dev, "%d: cannot determine AOC stp ccw rate", port_num);
+ return 0;
+ }
+ }
+
+ /* dont recognise media */
+ sbl_dev_err(sbl->dev, "%d: cannot determine stp ccw rate - unrecognised media", port_num);
+ return 0;
+}
+EXPORT_SYMBOL(sbl_link_get_stp_ccw_thresh_hpe);
+
+/**
+ * sbl_link_get_stp_ccw_thresh_ieee() - Get corrected code word(ieee) threshold
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Get Corrected code words(ieee) threshold when STP is used
+ *
+ * Return: Threshold value on success, 0 on failure.
+ */
+u64 sbl_link_get_stp_ccw_thresh_ieee(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+
+ /* ieee thresholds are about 2e-8
+ * We dont care about media type
+ */
+ switch (link->link_mode) {
+ case SBL_LINK_MODE_BS_200G:
+ return 4250;
+ case SBL_LINK_MODE_BJ_100G:
+ case SBL_LINK_MODE_CD_100G:
+ return 2125;
+ case SBL_LINK_MODE_CD_50G:
+ return 1062;
+ default:
+ sbl_dev_err(sbl->dev, "%d: cannot determine stp ccw rate", port_num);
+ return 0;
+ }
+}
+EXPORT_SYMBOL(sbl_link_get_stp_ccw_thresh_ieee);
+
+/* Link info
+ * lets you see the current state of the link
+ *
+ * Atomic mask updates preserve concurrently changed flags.
+ */
+void sbl_link_info_set(struct sbl_inst *sbl, int port_num, u32 flag)
+{
+ switch (flag) {
+
+ case SBL_LINK_INFO_LP_DET:
+ case SBL_LINK_INFO_PCS_ANEG:
+ case SBL_LINK_INFO_PCS_TX_AM:
+ case SBL_LINK_INFO_PCS_TX_RF:
+ case SBL_LINK_INFO_PCS_ALIGN_EN:
+ case SBL_LINK_INFO_PCS_A_WAIT:
+ case SBL_LINK_INFO_PCS_F_WAIT:
+ case SBL_LINK_INFO_PCS_UP:
+ case SBL_LINK_INFO_MAC_OP:
+ case SBL_LINK_INFO_OS:
+ case SBL_LINK_INFO_LLR_LOOP:
+ case SBL_LINK_INFO_LLR_MEASURE:
+ case SBL_LINK_INFO_LLR_WAIT:
+ case SBL_LINK_INFO_LLR_RUN:
+ case SBL_LINK_INFO_LLR_DISABLED:
+ case SBL_LINK_INFO_FAULT_MON:
+ case SBL_LINK_INFO_LLR_DETECT:
+ set_mask_bits(&sbl->link[port_num].link_info, 0, flag);
+ break;
+
+ default:
+ sbl_dev_dbg(sbl->dev, "bl %d: unrecognised set info flag (%d)\n",
+ port_num, flag);
+ break;
+ }
+}
+
+void sbl_link_info_clear(struct sbl_inst *sbl, int port_num, u32 flag)
+{
+ switch (flag) {
+
+ case SBL_LINK_INFO_LP_DET:
+ case SBL_LINK_INFO_PCS_ANEG:
+ case SBL_LINK_INFO_PCS_TX_AM:
+ case SBL_LINK_INFO_PCS_TX_RF:
+ case SBL_LINK_INFO_PCS_ALIGN_EN:
+ case SBL_LINK_INFO_PCS_A_WAIT:
+ case SBL_LINK_INFO_PCS_F_WAIT:
+ case SBL_LINK_INFO_PCS_UP:
+ case SBL_LINK_INFO_MAC_OP:
+ case SBL_LINK_INFO_OS:
+ case SBL_LINK_INFO_LLR_LOOP:
+ case SBL_LINK_INFO_LLR_MEASURE:
+ case SBL_LINK_INFO_LLR_WAIT:
+ case SBL_LINK_INFO_LLR_RUN:
+ case SBL_LINK_INFO_LLR_DISABLED:
+ case SBL_LINK_INFO_FAULT_MON:
+ case SBL_LINK_INFO_LLR_DETECT:
+ set_mask_bits(&sbl->link[port_num].link_info, flag, 0);
+ break;
+
+ default:
+ sbl_dev_dbg(sbl->dev, "bl %d: unrecognised clear info flag (%d)\n",
+ port_num, flag);
+ break;
+ }
+}
+
+#ifdef CONFIG_SYSFS
+/**
+ * sbl_base_link_sysfs_sprint() - Format link 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
+ *
+ * Context: Process, Acquires lock and release link->lock <spin_lock>
+ *
+ * Return: Number of characters written, at most @size - 1
+ */
+int sbl_base_link_sysfs_sprint(struct sbl_inst *sbl, int port_num, char *buf, size_t size)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ bool mac_tx_op;
+ bool mac_rx_op;
+ u32 mac_ifg_mode;
+ u32 link_info;
+ u64 llr_cfg;
+ u32 llr_mode;
+ u32 llr_down_behavior;
+ u64 llr_sts;
+ int s = 0;
+
+ spin_lock(&link->lock);
+ link_info = READ_ONCE(link->link_info);
+
+ s += scnprintf(buf + s, size - s, "base link state: %s",
+ sbl_link_state_str(link->blstate));
+ if (link->blstate == SBL_BASE_LINK_STATUS_ERROR)
+ s += scnprintf(buf + s, size - s, " [%d]", link->blerr);
+ if (link->blstate == SBL_BASE_LINK_STATUS_STARTING)
+ s += scnprintf(buf + s, size - s, " (%d/%d)",
+ sbl_link_start_elapsed(sbl, port_num),
+ sbl_get_start_timeout(sbl, port_num));
+ if (link->blstate == SBL_BASE_LINK_STATUS_UP)
+ s += scnprintf(buf + s, size - s, " (%lld.%.3ld, %lld.%.3ld)",
+ (long long)link->start_time.tv_sec,
+ link->start_time.tv_nsec / 1000000,
+ (long long)link->up_time.tv_sec,
+ link->up_time.tv_nsec / 1000000);
+ s += scnprintf(buf + s, size - s, "\n");
+
+ if (link_info) {
+ s += scnprintf(buf + s, size - s, "base link info: ");
+ if (link_info & SBL_LINK_INFO_PCS_ANEG)
+ s += scnprintf(buf + s, size - s, "pcs-aneg ");
+ if (link_info & SBL_LINK_INFO_PCS_TX_AM)
+ s += scnprintf(buf + s, size - s, "pcs-tx-am ");
+ if (link_info & SBL_LINK_INFO_PCS_ALIGN_EN)
+ s += scnprintf(buf + s, size - s, "pcs-a-en ");
+ if (link_info & SBL_LINK_INFO_PCS_A_WAIT)
+ s += scnprintf(buf + s, size - s, "pcs-a-wait ");
+ if (link_info & SBL_LINK_INFO_PCS_TX_RF)
+ s += scnprintf(buf + s, size - s, "pcs-tx-rf ");
+ if (link_info & SBL_LINK_INFO_PCS_F_WAIT)
+ s += scnprintf(buf + s, size - s, "pcs-f-wait ");
+ if (link_info & SBL_LINK_INFO_PCS_UP)
+ s += scnprintf(buf + s, size - s, "pcs-up ");
+ if (link_info & SBL_LINK_INFO_LP_DET)
+ s += scnprintf(buf + s, size - s, "lp-det ");
+ if (link_info & SBL_LINK_INFO_MAC_OP)
+ s += scnprintf(buf + s, size - s, "mac ");
+ if (link_info & SBL_LINK_INFO_OS)
+ s += scnprintf(buf + s, size - s, "os ");
+ if (link_info & SBL_LINK_INFO_LLR_LOOP)
+ s += scnprintf(buf + s, size - s, "llr-loop-en ");
+ if (link_info & SBL_LINK_INFO_LLR_DETECT)
+ s += scnprintf(buf + s, size - s, "llr-detect ");
+ if (link_info & SBL_LINK_INFO_LLR_MEASURE)
+ s += scnprintf(buf + s, size - s, "llr-measure ");
+ if (link_info & SBL_LINK_INFO_LLR_WAIT)
+ s += scnprintf(buf + s, size - s, "llr-wait ");
+ if (link_info & SBL_LINK_INFO_LLR_RUN)
+ s += scnprintf(buf + s, size - s, "llr ");
+ if (link_info & SBL_LINK_INFO_LLR_DISABLED)
+ s += scnprintf(buf + s, size - s, "llr-dis ");
+ if (link_info & SBL_LINK_INFO_FAULT_MON)
+ s += scnprintf(buf + s, size - s, "fm ");
+ s += scnprintf(buf + s, size - s, "\n");
+ }
+
+ if (link->blconfigured) {
+
+ if (link->blattr.config_target == SBL_BASE_LINK_CONFIG_PEC) {
+ s += scnprintf(buf + s, size - s, "base link: an: mode %s",
+ sbl_an_mode_str(link->blattr.pec.an_mode));
+ if (link_info & SBL_LINK_INFO_PCS_ANEG) {
+ if (link->blattr.pec.an_mode == SBL_AN_MODE_FIXED)
+ s += scnprintf(buf + s, size - s, "(%s)",
+ sbl_link_mode_str(link->blattr.link_mode));
+ s += scnprintf(buf + s, size - s, ", tries %d, nonce %x, state %s",
+ link->an_try_count, link->an_nonce,
+ sbl_an_get_sm_state(sbl, port_num));
+ }
+ if ((link->blattr.pec.an_mode != SBL_AN_MODE_OFF) && (link->an_rx_count))
+ s += scnprintf(buf + s, size - s, ", received %d",
+ link->an_rx_count);
+ if (link->an_100cr4_fixup_applied)
+ s += scnprintf(buf + s, size - s, ", 100cr4-fixup");
+ if (link->an_options & AN_OPT_LLR)
+ s += scnprintf(buf + s, size - s, ", llr");
+
+ s += scnprintf(buf + s, size - s, "\n");
+ }
+ sbl_pml_mac_hw_status(sbl, port_num, &mac_tx_op, &mac_rx_op, &mac_ifg_mode, NULL);
+ if (mac_tx_op || mac_rx_op) {
+ s += scnprintf(buf + s, size - s, "base link: mac: tx %d, rx %d, ifg %s\n",
+ mac_tx_op, mac_rx_op, sbl_ifg_config_str(link->ifg_config));
+ }
+
+ llr_cfg = sbl_read64(sbl, SBL_PML_BASE(port_num) | SBL_PML_CFG_LLR_OFFSET);
+ switch (SBL_PML_CFG_LLR_LLR_MODE_GET(llr_cfg)) {
+ case 0:
+ llr_mode = SBL_LLR_MODE_OFF;
+ break;
+ case 1:
+ llr_mode = SBL_LLR_MODE_MONITOR;
+ break;
+ case 2:
+ llr_mode = SBL_LLR_MODE_ON;
+ break;
+ default:
+ llr_mode = SBL_LLR_MODE_INVALID;
+ }
+ switch (SBL_PML_CFG_LLR_LINK_DOWN_BEHAVIOR_GET(llr_cfg)) {
+ case 0:
+ llr_down_behavior = SBL_LLR_LINK_DOWN_DISCARD;
+ break;
+ case 1:
+ llr_down_behavior = SBL_LLR_LINK_DOWN_BLOCK;
+ break;
+ case 2:
+ llr_down_behavior = SBL_LLR_LINK_DOWN_BEST_EFFORT;
+ break;
+ default:
+ llr_down_behavior = SBL_LLR_LINK_DOWN_INVALID;
+ }
+ llr_sts = sbl_read64(sbl, SBL_PML_BASE(port_num) |
+ SBL_PML_STS_LLR_MAX_USAGE_OFFSET);
+ s += scnprintf(buf + s, size - s,
+ "base link: llr: mode %s, down %s, loop %lld %s %s\n",
+ sbl_llr_mode_str(llr_mode),
+ sbl_llr_down_behaviour_str(llr_down_behavior),
+ link->llr_loop_time,
+ SBL_PML_STS_LLR_MAX_USAGE_BUFF_SPC_STARVED_GET(llr_sts) ?
+ "buf starved" : "",
+ SBL_PML_STS_LLR_MAX_USAGE_SEQ_STARVED_GET(llr_sts) ?
+ "seq starved" : "");
+
+ if (link->blattr.options & SBL_DISABLE_PML_RECOVERY)
+ s += scnprintf(buf + s, size - s, "base link: pml rec: disabled\n");
+ else
+ s += scnprintf(buf + s, size - s,
+ "base link: pml rec: enabled, to %u, rl %u/%u\n",
+ link->blattr.pml_recovery.timeout,
+ link->blattr.pml_recovery.rl_max_duration,
+ link->blattr.pml_recovery.rl_window_size);
+ }
+
+ spin_unlock(&link->lock);
+
+ s += sbl_debug_sysfs_sprint(sbl, port_num, buf + s, size - s);
+
+ return s;
+}
+EXPORT_SYMBOL(sbl_base_link_sysfs_sprint);
+#endif
+
+/**
+ * sbl_base_link_dump_attr() - Format all link attributes 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
+ *
+ * Return: Number of characters written, at most @size - 1
+ */
+int sbl_base_link_dump_attr(struct sbl_inst *sbl, int port_num, char *buf, size_t size)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ struct sbl_base_link_attr *attr = &link->blattr;
+ int s = 0;
+
+ s += scnprintf(buf + s, size - s, "%d: base link attributes:\n", port_num);
+
+ if (!link->blconfigured) {
+ s += scnprintf(buf + s, size - s, "not configured\n");
+ return s;
+ }
+
+ s += scnprintf(buf + s, size - s, "options 0x%x", attr->options);
+ if (attr->options) {
+ s += scnprintf(buf + s, size - s, " -");
+ if (attr->options & SBL_OPT_FABRIC_LINK)
+ s += scnprintf(buf + s, size - s, " fabric");
+ if (attr->options & SBL_OPT_SERDES_LPD)
+ s += scnprintf(buf + s, size - s, " serdes-lpd");
+ if (attr->options & SBL_OPT_DFE_SAVE_PARAMS)
+ s += scnprintf(buf + s, size - s, " save-params");
+ if (attr->options & SBL_OPT_USE_SAVED_PARAMS)
+ s += scnprintf(buf + s, size - s, " use-params");
+ if (attr->options & SBL_OPT_RESET_CLEAR_PARAMS)
+ s += scnprintf(buf + s, size - s, " reset-clear-params");
+ if (attr->options & SBL_OPT_ENABLE_PCAL)
+ s += scnprintf(buf + s, size - s, " pcal");
+ if (attr->options & SBL_OPT_DFE_ALWAYS_MAX_EFFORT)
+ s += scnprintf(buf + s, size - s, " max-effort");
+ if (attr->options & SBL_OPT_DFE_ALWAYS_MED_EFFORT)
+ s += scnprintf(buf + s, size - s, " med-effort");
+ if (attr->options & SBL_OPT_DFE_ALWAYS_MIN_EFFORT)
+ s += scnprintf(buf + s, size - s, " min-effort");
+ if (attr->options & SBL_OPT_AUTONEG_TIMEOUT_IEEE)
+ s += scnprintf(buf + s, size - s, " an-timeout-ieee");
+ if (attr->options & SBL_OPT_AUTONEG_TIMEOUT_SSHOT)
+ s += scnprintf(buf + s, size - s, " an-timeout-sshot");
+ if (attr->options & SBL_OPT_AUTONEG_100CR4_FIXUP)
+ s += scnprintf(buf + s, size - s, " an-100cr4-fixup");
+ if (attr->options & SBL_OPT_RELOAD_FW_ON_TIMEOUT)
+ s += scnprintf(buf + s, size - s, " timeout-reload-fw");
+ if (attr->options & SBL_OPT_ALLOW_MEDIA_BAD_MODE)
+ s += scnprintf(buf + s, size - s, " allow-media-bad-mode");
+ if (attr->options & SBL_OPT_ALLOW_MEDIA_BAD_LEN)
+ s += scnprintf(buf + s, size - s, " allow-media-bad-len");
+ if (attr->options & SBL_OPT_ENABLE_ETHER_LLR)
+ s += scnprintf(buf + s, size - s, " enable-ether-llr");
+ if (attr->options & SBL_OPT_ENABLE_IFG_HPC_WITH_LLR)
+ s += scnprintf(buf + s, size - s, " enable-ifg-hpc-with-llr");
+ if (attr->options & SBL_OPT_ENABLE_IFG_CONFIG)
+ s += scnprintf(buf + s, size - s, " enable-ifg-config");
+ if (attr->options & SBL_OPT_DISABLE_AN_LLR)
+ s += scnprintf(buf + s, size - s, " disable-an-llr");
+ if (attr->options & SBL_OPT_LANE_DEGRADE)
+ s += scnprintf(buf + s, size - s, " enable-lane-degrade");
+ if (attr->options & SBL_DISABLE_PML_RECOVERY)
+ s += scnprintf(buf + s, size - s, " disable-pml-recovery");
+ }
+ s += scnprintf(buf + s, size - s, "\n");
+ s += scnprintf(buf + s, size - s, "start_timeout %d\n", attr->start_timeout);
+ if (attr->config_target == SBL_BASE_LINK_CONFIG_PEC) {
+ s += scnprintf(buf + s, size - s, "config_target PEC\n");
+ s += scnprintf(buf + s, size - s, "an_mode %d (%s)\n",
+ attr->pec.an_mode,
+ sbl_an_mode_str(attr->pec.an_mode));
+ s += scnprintf(buf + s, size - s, "an_retry_timeout %d\n",
+ attr->pec.an_retry_timeout);
+ s += scnprintf(buf + s, size - s, "an_max_retry %d\n", attr->pec.an_max_retry);
+ }
+ if (attr->config_target == SBL_BASE_LINK_CONFIG_AOC) {
+ s += scnprintf(buf + s, size - s, "config_target AOC\n");
+ s += scnprintf(buf + s, size - s, "optical_lock_delay %d\n",
+ attr->aoc.optical_lock_delay);
+ s += scnprintf(buf + s, size - s, "optical_lock_interval %d\n",
+ attr->aoc.optical_lock_interval);
+ }
+ s += scnprintf(buf + s, size - s, "lpd_timeout %d\n", attr->lpd_timeout);
+ s += scnprintf(buf + s, size - s, "lpd_poll_interval %d\n", attr->lpd_poll_interval);
+ s += scnprintf(buf + s, size - s, "link_mode %d (%s)\n",
+ attr->link_mode, sbl_link_mode_str(attr->link_mode));
+ s += scnprintf(buf + s, size - s, "loopback_mode %d (%s)\n",
+ attr->loopback_mode,
+ sbl_loopback_mode_str(attr->loopback_mode));
+ s += scnprintf(buf + s, size - s, "link_partner %d\n", attr->link_partner);
+ s += scnprintf(buf + s, size - s, "tuning_pattern %d\n", attr->tuning_pattern);
+ s += scnprintf(buf + s, size - s, "precoding %d\n", attr->precoding);
+ s += scnprintf(buf + s, size - s, "dfe_pre_delay %d\n", attr->dfe_pre_delay);
+ s += scnprintf(buf + s, size - s, "dfe_timeout %d\n", attr->dfe_timeout);
+ s += scnprintf(buf + s, size - s, "dfe_poll_interval %d\n", attr->dfe_poll_interval);
+ s += scnprintf(buf + s, size - s, "pcal_eyecheck_holdoff %d\n",
+ attr->pcal_eyecheck_holdoff);
+ s += scnprintf(buf + s, size - s, "nrz_min_eye_height 0x%x\n", attr->nrz_min_eye_height);
+ s += scnprintf(buf + s, size - s, "nrz_max_eye_height 0x%x\n", attr->nrz_max_eye_height);
+ s += scnprintf(buf + s, size - s, "pam4_min_eye_height 0x%x\n", attr->pam4_min_eye_height);
+ s += scnprintf(buf + s, size - s, "pam4_max_eye_height 0x%x\n", attr->pam4_max_eye_height);
+ s += scnprintf(buf + s, size - s, "fec_mode %d\n", attr->fec_mode);
+ s += scnprintf(buf + s, size - s, "enable_autodegrade %d\n", attr->enable_autodegrade);
+ s += scnprintf(buf + s, size - s, "llr_mode %d (%s)\n",
+ attr->llr_mode, sbl_llr_mode_str(attr->llr_mode));
+ s += scnprintf(buf + s, size - s, "ifg_config %d (%s)\n",
+ attr->ifg_config, sbl_ifg_config_str(attr->ifg_config));
+ s += scnprintf(buf + s, size - s, "pml_recovery.timeout %u\n", attr->pml_recovery.timeout);
+ s += scnprintf(buf + s, size - s, "pml_recovery.rl_max_duration %u\n",
+ attr->pml_recovery.rl_max_duration);
+ s += scnprintf(buf + s, size - s, "pml_recovery.rl_window_size %u\n",
+ attr->pml_recovery.rl_window_size);
+
+ return s;
+}
+EXPORT_SYMBOL(sbl_base_link_dump_attr);
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_link_an.c b/drivers/net/ethernet/hpe/sbl/sbl_link_an.c
new file mode 100644
index 000000000000..aecadbceb593
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_link_an.c
@@ -0,0 +1,1010 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2019-2025 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/random.h>
+#include <linux/delay.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_an.h>
+
+#include <linux/hpe/sbl/sbl_pml.h>
+
+#include "sbl_pml_fn.h"
+#include "sbl_link.h"
+#include "sbl_serdes.h"
+#include "sbl_constants.h"
+#include "sbl_serdes_map.h"
+#include "sbl_internal.h"
+
+/* for debug purposes we can add an extra null message page for fabric links */
+#define SBL_SEND_EXTRA_FABRIC_NULL_PAGE 0
+
+/* set the reset state */
+static void sbl_an_pml_an_reset(struct sbl_inst *sbl, int port_num, u64 reset_state)
+{
+ u32 base = SBL_PML_BASE(port_num);
+ u64 cfg_pcs_autoneg_reg;
+
+ /* reset */
+ cfg_pcs_autoneg_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET);
+ cfg_pcs_autoneg_reg = SBL_PML_CFG_PCS_AUTONEG_RESET_UPDATE(cfg_pcs_autoneg_reg, reset_state);
+ sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET,
+ cfg_pcs_autoneg_reg);
+ sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET);
+}
+
+static void sbl_an_send_base_page(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ u32 base = SBL_PML_BASE(port_num);
+
+ /* first page is the base page (whole reg) */
+ sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_BASE_PAGE_OFFSET, link->an_tx_page[0]);
+ sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_BASE_PAGE_OFFSET);
+
+ sbl_dev_dbg(sbl->dev, "an %d: tx base page: 0x%llx", port_num, link->an_tx_page[0]);
+
+ /* start autoneg by taking an sm out of reset*/
+ sbl_an_pml_an_reset(sbl, port_num, 0);
+}
+
+static u32 sbl_an_get_nonce(void)
+{
+ u16 buf;
+
+ while (1) {
+ get_random_bytes(&buf, 2);
+ buf &= 0x1f;
+ if (buf)
+ return buf;
+ }
+}
+
+static bool sbl_an_base_is_complete(struct sbl_inst *sbl, int port_num)
+{
+ u32 base = SBL_PML_BASE(port_num);
+ u64 sts_autoneg_base_reg;
+
+ sts_autoneg_base_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_OFFSET);
+
+ return SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_COMPLETE_GET(sts_autoneg_base_reg);
+}
+
+static bool sbl_an_base_is_page_recv(struct sbl_inst *sbl, int port_num)
+{
+ u32 base = SBL_PML_BASE(port_num);
+ u64 sts_autoneg_base_reg;
+ bool val;
+
+ sts_autoneg_base_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_OFFSET);
+
+ val = (SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_LP_ABILITY_GET(sts_autoneg_base_reg)) &&
+ (SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_PAGE_RECEIVED_GET(sts_autoneg_base_reg));
+
+ return val;
+}
+
+static bool sbl_an_is_base_page(struct sbl_inst *sbl, int port_num)
+{
+ u32 base = SBL_PML_BASE(port_num);
+ u64 sts_autoneg_base_reg;
+ u64 state;
+ bool val;
+
+ sts_autoneg_base_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_OFFSET);
+
+ state = SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_STATE_GET(sts_autoneg_base_reg);
+
+ val = (state == SBL_PML_AUTONEG_STATE_COMPLETE_ACK) ||
+ (state == SBL_PML_AUTONEG_STATE_AN_GOOD_CHECK);
+
+ val = val && (SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_PAGE_RECEIVED_GET(sts_autoneg_base_reg)) &&
+ (SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_BASE_PAGE_GET(sts_autoneg_base_reg)) &&
+ (SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_LP_ABILITY_GET(sts_autoneg_base_reg));
+
+ return val;
+}
+
+/* Setup the pages */
+static int sbl_an_setup_tx_pages(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+
+ sbl_dev_dbg(sbl->dev, "an %d: run setup tx pages", port_num);
+
+ /* init to known non-zero value so it's easy to tell used pages from init pages */
+ memset(link->an_tx_page, 0xAA, sizeof(link->an_tx_page));
+
+ /* build the base page */
+
+ link->an_tx_page[0] = 0;
+ link->an_tx_page[0] |= AN_CW_S_802_3;
+
+ if (link->blattr.pec.an_mode == SBL_AN_MODE_FIXED) {
+
+ switch (link->blattr.link_mode) {
+
+ case SBL_LINK_MODE_BS_200G:
+ link->an_tx_page[0] |= (AN_CW_A_200GBASE_CR4) << AN_CW_A_BASE_BIT;
+ break;
+
+ case SBL_LINK_MODE_BJ_100G:
+ link->an_tx_page[0] |= (AN_CW_A_100GBASE_CR4) << AN_CW_A_BASE_BIT;
+ break;
+
+ case SBL_LINK_MODE_CD_100G:
+ link->an_tx_page[0] |= (AN_CW_A_100GBASE_CR2) << AN_CW_A_BASE_BIT;
+ break;
+
+ case SBL_LINK_MODE_CD_50G:
+ link->an_tx_page[0] |= (AN_CW_A_50GBASE_CR) << AN_CW_A_BASE_BIT;
+ break;
+
+ default:
+ sbl_dev_err(sbl->dev, "an %d: bad an blattr mode", port_num);
+ return -EINVAL;
+ }
+ } else {
+ /* all modes we support */
+ link->an_tx_page[0] |= (AN_CW_A_200GBASE_CR4) << AN_CW_A_BASE_BIT;
+ link->an_tx_page[0] |= (AN_CW_A_100GBASE_CR4) << AN_CW_A_BASE_BIT;
+ link->an_tx_page[0] |= (AN_CW_A_100GBASE_CR2) << AN_CW_A_BASE_BIT;
+ link->an_tx_page[0] |= (AN_CW_A_50GBASE_CR) << AN_CW_A_BASE_BIT;
+ }
+
+ /* Not sure what to set here - both Arista and Mellanox say zero so for
+ * now we will do the same and set nothing
+ */
+
+ /* TODO: Enhance PAUSE support. Currently only symmetric PAUSE is advertised.
+ * Should support: asymmetric PAUSE modes, flow control negotiation per standard,
+ * and link partner capability reporting.
+ */
+ link->an_tx_page[0] |= (AN_CW_C_SYMMETRIC) << AN_CW_C_BASE_BIT;
+
+ sbl_dev_dbg(sbl->dev, "an %d: bp = 0x%llx", port_num, link->an_tx_page[0]);
+
+ /* check for disabled next pages */
+ if (sbl_debug_option(sbl, port_num, SBL_DEBUG_DISABLE_AN_NEXT_PAGES)) {
+ sbl_dev_dbg(sbl->dev, "an %d: next pages disabled", port_num);
+ link->an_tx_count = 1;
+ return 0;
+ }
+
+ /* build HPE OUI next pages */
+
+ /* have next page (set in previous page) */
+ link->an_tx_page[0] |= AN_CW_NP_MASK;
+
+ /* OUI message page */
+ link->an_tx_page[1] = 0ULL;
+ link->an_tx_page[1] |= AN_NP_MP_MASK;
+ link->an_tx_page[1] |= AN_NP_CODE_OUI_EXTENDED_MSG;
+ link->an_tx_page[1] |= AN_NP_OUI_HPE;
+
+ sbl_dev_dbg(sbl->dev, "an %d: np mp = 0x%llx", port_num, link->an_tx_page[1]);
+
+ /* have next page (set in previous page) */
+ link->an_tx_page[1] |= AN_CW_NP_MASK;
+
+ /* unformatted page with OUI message page */
+ link->an_tx_page[2] = 0ULL;
+ link->an_tx_page[2] |= AN_NP_OUI_VER_0_1;
+ /* LLR options here */
+ if (link->blattr.llr_mode == SBL_LLR_MODE_AUTO) {
+ if (!(link->blattr.options & SBL_OPT_DISABLE_AN_LLR)) {
+ link->an_tx_page[2] |= (AN_OPT_LLR << AN_OPT_BASE_BIT);
+ if (link->blattr.options & SBL_OPT_ENABLE_ETHER_LLR)
+ link->an_tx_page[2] |= (AN_OPT_ETHER_LLR << AN_OPT_BASE_BIT);
+
+ if (link->blattr.options & SBL_OPT_ENABLE_IFG_HPC_WITH_LLR)
+ link->an_tx_page[2] |= (AN_OPT_HPC_WITH_LLR << AN_OPT_BASE_BIT);
+ /* TODO: add IPV4 option */
+ }
+ }
+ sbl_an_version_read(sbl, port_num);
+
+ sbl_dev_dbg(sbl->dev, "an %d: np ufp = 0x%llx", port_num, link->an_tx_page[2]);
+
+ link->an_tx_count = SBL_AN_MAX_TX_PAGES;
+
+ return 0;
+}
+
+static int sbl_an_pml_setup(struct sbl_inst *sbl, int port_num)
+{
+ u32 base = SBL_PML_BASE(port_num);
+ u64 cfg_pcs_autoneg_reg;
+ u64 cfg_pcs_reg;
+ u64 cfg_rx_pcs_reg;
+ u64 val64;
+
+ /* disable pcs autoneg
+ * as pcs is also disabled this will reset the pcs-serdes CDC logic
+ */
+ cfg_pcs_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET);
+ cfg_pcs_reg = SBL_PML_CFG_PCS_ENABLE_AUTO_NEG_UPDATE(cfg_pcs_reg, 0ULL);
+ sbl_write64(sbl, base | SBL_PML_CFG_PCS_OFFSET, cfg_pcs_reg);
+ sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET);
+
+ /* configure an */
+ cfg_pcs_autoneg_reg = SBL_PML_CFG_PCS_AUTONEG_RX_LANE_SET(sbl->switch_info->ports[port_num].rx_an_swizzle) |
+ SBL_PML_CFG_PCS_AUTONEG_TX_LANE_SET(sbl->switch_info->ports[port_num].tx_an_swizzle) |
+ SBL_PML_CFG_PCS_AUTONEG_RESET_SET(1ULL) | /* start held in reset */
+ SBL_PML_CFG_PCS_AUTONEG_RESTART_SET(0ULL) | /* not used - always zero */
+ SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_LOADED_SET(0ULL);
+ sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET,
+ cfg_pcs_autoneg_reg);
+ sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET);
+
+ /* reset alignment locking */
+ cfg_rx_pcs_reg = sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET);
+ cfg_rx_pcs_reg = SBL_PML_CFG_RX_PCS_ACTIVE_LANES_UPDATE(cfg_rx_pcs_reg, 0ULL);
+ cfg_rx_pcs_reg = SBL_PML_CFG_RX_PCS_ENABLE_LOCK_UPDATE(cfg_rx_pcs_reg, 0ULL);
+ sbl_write64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET, cfg_rx_pcs_reg);
+ sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET);
+
+ /* config an timers */
+ val64 = SBL_PML_CFG_PCS_AUTONEG_TIMERS_DFLT;
+ /* disable fault timeout by setting it to its max value */
+ val64 = SBL_PML_CFG_PCS_AUTONEG_TIMERS_LINK_FAIL_INHIBIT_TIMER_MAX_UPDATE(val64, 0xffffffffULL);
+
+ sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_TIMERS_OFFSET, val64);
+
+ /* clear err flags */
+ sbl_pml_err_flgs_clear_all(sbl, port_num);
+
+ /* enable autoneg
+ * this will take CDC logic out of reset
+ */
+ cfg_pcs_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET);
+ cfg_pcs_reg = SBL_PML_CFG_PCS_ENABLE_AUTO_NEG_UPDATE(cfg_pcs_reg, 1ULL);
+ sbl_write64(sbl, base | SBL_PML_CFG_PCS_OFFSET, cfg_pcs_reg);
+
+ /* an reset is still asserted, it will be deasserted after base page is loaded */
+
+ sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_PCS_ANEG);
+
+ return 0;
+}
+
+/* perform page exchange
+ *
+ * Rosetta has a hw bug whereby some error flags cannot be cleared by the normal method
+ * as the an state machine will continually reassert them. (The failed, complete and
+ * next page received err flags) so instead we disable intrs for these flags when they
+ * are received and reset them in wait prepare.
+ *
+ * For next pages we would like to do:
+ *
+ * wait_prepare()
+ * send_next_page()
+ * wait_for_completion()
+ *
+ * However this sequence will cause the err flags to be immediately set in the wait
+ * prepare and the intr fire.
+ *
+ * The best we can do is
+ *
+ * preempt_disable()
+ * send_next_page()
+ * wait_prepare()
+ * preempt_enable()
+ * wait_for_completion()
+ *
+ * But we can still miss intrs
+ * There would seem to be nothing we can do about this
+ *
+ * 26 Feb 2021 - added SM exchange to work around rosetta interrupt bug
+ *
+ */
+static int sbl_an_exchange(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ u32 base = SBL_PML_BASE(port_num);
+ unsigned long timeout = msecs_to_jiffies(1000 * link->blattr.pec.an_retry_timeout);
+ unsigned long remaining_jiffies;
+ u64 sts_autoneg_base_reg;
+ int err;
+
+ sbl_dev_dbg(sbl->dev, "an %d: exchange start", port_num);
+
+ if (sbl_base_link_start_cancelled(sbl, port_num))
+ return -ECANCELED;
+ if (sbl_start_timeout(sbl, port_num))
+ return -ETIMEDOUT;
+
+ if (!sbl->is_hw)
+ return 0;
+
+ /* clear any previous pages */
+ memset(link->an_rx_page, 0, SBL_AN_MAX_RX_PAGES * sizeof(u64));
+ link->an_rx_count = 0;
+
+ /* update the nonce */
+ link->an_nonce = sbl_an_get_nonce();
+ link->an_tx_page[0] &= ~AN_CW_T_MASK;
+ link->an_tx_page[0] |= ((link->an_nonce << AN_CW_T_BASE_BIT) & AN_CW_T_MASK);
+
+ /* base page exchange */
+
+ err = sbl_an_hw_wait_prepare(sbl, port_num);
+ if (err)
+ return err;
+
+ sbl_an_send_base_page(sbl, port_num);
+
+ remaining_jiffies = wait_for_completion_timeout(&link->an_hw_change, timeout);
+ if (sbl_base_link_start_cancelled(sbl, port_num))
+ return -ECANCELED;
+ if (remaining_jiffies == 0) {
+ sbl_dev_dbg(sbl->dev, "an %d: base page exchange timeout (nonce %.2x, sm_state %s)",
+ port_num, link->an_nonce, sbl_an_get_sm_state(sbl, port_num));
+ sbl_an_dump_state(sbl, port_num);
+
+ /* check we have not missed the err flg */
+ if (sbl_an_base_is_complete(sbl, port_num) || sbl_an_base_is_page_recv(sbl, port_num))
+ sbl_dev_err(sbl->dev, "an %d: MISSED A BASE PAGE!!!!", port_num);
+
+ return -ETIME;
+ }
+
+ /* check it is a base page */
+ if (!sbl_an_is_base_page(sbl, port_num)) {
+ sbl_dev_err(sbl->dev, "an %d: missing base page indication", port_num);
+ sbl_an_dump_state(sbl, port_num);
+ return -EBADE;
+ }
+
+ /* copy page to return buffer */
+ sts_autoneg_base_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_OFFSET);
+ link->an_rx_page[0] = SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_LP_BASE_PAGE_GET(sts_autoneg_base_reg);
+ link->an_rx_count = 1;
+
+ /* check if an is complete i.e. no one has more to send */
+ if (sbl_an_base_is_complete(sbl, port_num))
+ goto out_success;
+
+ /* since not complete, we must have received a page - check this */
+ if (!sbl_an_base_is_page_recv(sbl, port_num)) {
+ sbl_dev_err(sbl->dev, "an %d: expected page received indication", port_num);
+ sbl_an_dump_state(sbl, port_num);
+ return -EBADE;
+ }
+
+ /* next page exchange */
+ err = sbl_an_page_exchange(sbl, port_num, remaining_jiffies);
+
+ if (err)
+ return err;
+
+out_success:
+
+ /* dump the final state on the way out */
+ sbl_an_dump_state(sbl, port_num);
+
+ sbl_dev_dbg(sbl->dev, "an %d: exchange complete", port_num);
+
+ return 0;
+}
+
+/* save away the LLR options here */
+static void sbl_an_save_llr_options(struct sbl_link *link, int x)
+{
+ if (link->blattr.llr_mode == SBL_LLR_MODE_AUTO) {
+ if (!(link->blattr.options & SBL_OPT_DISABLE_AN_LLR)) {
+ if (link->an_rx_page[x] & (AN_OPT_LLR << AN_OPT_BASE_BIT))
+ link->an_options |= AN_OPT_LLR;
+ if (link->an_rx_page[x] & (AN_OPT_ETHER_LLR << AN_OPT_BASE_BIT)) {
+ if (link->blattr.options & SBL_OPT_ENABLE_ETHER_LLR)
+ link->an_options |= AN_OPT_ETHER_LLR;
+ }
+ if (link->an_rx_page[x] & (AN_OPT_HPC_WITH_LLR << AN_OPT_BASE_BIT)) {
+ if (link->blattr.options & SBL_OPT_ENABLE_IFG_HPC_WITH_LLR)
+ link->an_options |= AN_OPT_HPC_WITH_LLR;
+ }
+ /* TODO: add IPV4 option */
+ }
+ }
+
+}
+
+static int sbl_an_ability_match(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ u32 fec_ability;
+ u32 tech_ability;
+ u32 pause_ability;
+ u64 last_msg_code = 0;
+ bool expect_hpe = false;
+ int x = 0;
+
+ sbl_dev_dbg(sbl->dev, "an %d: rx count = %d", port_num, link->an_rx_count);
+
+ if (!sbl->is_hw) {
+ /* Netsim/Z1: No AN happening, so just force the response data to the
+ * tx data to ensure capability match
+ */
+ link->an_rx_page[0] = link->an_tx_page[0];
+ }
+ /* abilities */
+ tech_ability = (link->an_tx_page[0] & link->an_rx_page[0] & AN_CW_A_MASK)
+ >> AN_CW_A_BASE_BIT;
+ fec_ability = (link->an_tx_page[0] & link->an_rx_page[0] & AN_CW_F_MASK)
+ >> AN_CW_F_BASE_BIT;
+ pause_ability = (link->an_tx_page[0] & link->an_rx_page[0] & AN_CW_C_MASK)
+ >> AN_CW_C_BASE_BIT;
+ sbl_dev_dbg(sbl->dev, "an %d: tech 0x%x, fec 0x%x, pause %d", port_num,
+ tech_ability, fec_ability, pause_ability);
+
+ /* FEC mode (we only do RS) */
+ if (fec_ability & AN_CW_F_25G_RS_REQ) {
+ /* ignore for now - set in config */
+ } else if (fec_ability & AN_CW_F_25G_BASER_REQ) {
+ sbl_dev_warn(sbl->dev, "an %d: cannot do fec mode baser",
+ port_num);
+ } else {
+ sbl_dev_dbg(sbl->dev, "an %d: no matching fec mode (partner fec 0x%x)",
+ port_num, fec_ability);
+ }
+
+ /* TODO pause */
+
+ /* link mode (speed) */
+ if (tech_ability & AN_CW_A_200GBASE_CR4) {
+ link->link_mode = SBL_LINK_MODE_BS_200G;
+ } else if (tech_ability & AN_CW_A_100GBASE_CR4) {
+ link->link_mode = SBL_LINK_MODE_BJ_100G;
+ } else if (tech_ability & AN_CW_A_100GBASE_CR2) {
+ link->link_mode = SBL_LINK_MODE_CD_100G;
+ } else if (tech_ability & AN_CW_A_50GBASE_KR) {
+ link->link_mode = SBL_LINK_MODE_CD_50G;
+ } else {
+ sbl_dev_err(sbl->dev, "an %d: no matching mode (partner ability 0x%x)",
+ port_num, tech_ability);
+ return -ENOENT;
+ }
+
+ /* look at next page(s) */
+ link->lp_subtype = SBL_LP_SUBTYPE_UNKNOWN;
+ for (x = 1; x < link->an_rx_count; ++x) {
+
+ /* do message page */
+ if (link->an_rx_page[x] & AN_NP_MP_MASK) {
+ last_msg_code = link->an_rx_page[x] & AN_NP_MSG_MASK;
+ sbl_dev_dbg(sbl->dev, "an %d: msg_code = %llu", port_num, last_msg_code);
+ switch (last_msg_code) {
+ case AN_NP_CODE_NULL_MSG:
+ case AN_NP_CODE_OUI_MSG:
+ /* nothing to do */
+ break;
+ case AN_NP_CODE_OUI_EXTENDED_MSG:
+ switch (link->an_rx_page[x] & AN_NP_OUI_MASK) {
+ case AN_NP_OUI_HPE:
+ /* have HPE OUI message - expect an unformatted page next */
+ expect_hpe = true;
+ break;
+ default:
+ sbl_dev_dbg(sbl->dev, "an %d: unknown OUI = 0x%llX", port_num, (link->an_rx_page[x] & AN_NP_OUI_MASK) >> AN_NP_OUI_BASE_BIT);
+ expect_hpe = false;
+ break;
+ }
+ break;
+ default:
+ sbl_dev_dbg(sbl->dev, "an %d: unknown msg_code = 0x%llX", port_num, last_msg_code);
+ break;
+ }
+ /* go on to next page */
+ continue;
+ }
+
+ /* do unformatted page */
+ switch (last_msg_code) {
+ case AN_NP_CODE_OUI_EXTENDED_MSG:
+ if (!expect_hpe)
+ break;
+
+ /* got a HPE OUI message - check the unformatted page and set options */
+ switch (link->an_rx_page[x] & AN_NP_OUI_VER_MASK) {
+ case AN_NP_OUI_VER_0_1:
+ sbl_an_save_llr_options(link, x);
+ /* save away cassini version here */
+ link->lp_subtype = ((link->an_rx_page[x] >> AN_LP_SUBTYPE_BASE_BIT) & AN_LP_SUBTYPE_MASK);
+ sbl_dev_dbg(sbl->dev, "an %d: HPE an_options = 0x%X, lp_subtype = %d", port_num, link->an_options, link->lp_subtype);
+ break;
+ default:
+ sbl_dev_dbg(sbl->dev, "an %d: unknown OUI version 0x%llX", port_num, link->an_rx_page[x] & AN_NP_OUI_VER_MASK);
+ break;
+ }
+ expect_hpe = false;
+ break;
+ default:
+ sbl_dev_dbg(sbl->dev, "an %d: unknown ufp = 0x%llX", port_num, link->an_rx_page[x]);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static void sbl_an_update_timeout(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ unsigned int new_timeout = 0;
+
+ if (link->blattr.options & SBL_OPT_AUTONEG_TIMEOUT_SSHOT) {
+ /* use slingshot timeouts - which are longer than the IEEE defaults */
+ switch (link->link_mode) {
+ case SBL_LINK_MODE_BS_200G:
+ new_timeout = SBL_LINK_AUTONEG_TIMEOUT_SSHOT_200;
+ break;
+ case SBL_LINK_MODE_BJ_100G:
+ case SBL_LINK_MODE_CD_100G:
+ new_timeout = SBL_LINK_AUTONEG_TIMEOUT_SSHOT_100;
+ break;
+ case SBL_LINK_MODE_CD_50G:
+ new_timeout = SBL_LINK_AUTONEG_TIMEOUT_SSHOT_50;
+ break;
+ default:
+ sbl_dev_warn(sbl->dev, "an %d: bad mode in update_timeout",
+ port_num);
+ }
+ }
+
+ if (link->blattr.options & SBL_OPT_AUTONEG_TIMEOUT_IEEE) {
+ /* use IEEE timeouts */
+ switch (link->link_mode) {
+ case SBL_LINK_MODE_BS_200G:
+ new_timeout = SBL_LINK_AUTONEG_TIMEOUT_IEEE_200;
+ break;
+ case SBL_LINK_MODE_BJ_100G:
+ case SBL_LINK_MODE_CD_100G:
+ new_timeout = SBL_LINK_AUTONEG_TIMEOUT_IEEE_100;
+ break;
+ case SBL_LINK_MODE_CD_50G:
+ new_timeout = SBL_LINK_AUTONEG_TIMEOUT_IEEE_50;
+ break;
+ default:
+ sbl_dev_warn(sbl->dev, "an %d: bad mode in update_timeout",
+ port_num);
+ }
+ }
+
+ if (new_timeout) {
+ sbl_dev_dbg(sbl->dev, "an %d: update start timeout to %d ms",
+ port_num, new_timeout);
+ link->an_timeout_active = true;
+ sbl_link_update_start_timeout(sbl, port_num, new_timeout);
+ }
+}
+
+/* If there is no common mode between this port and our link partner
+ * and if the lp advertises 100KR4 and we advertise 100CR4 then add 100CR4
+ * to the lp's abilities.
+ *
+ * This is necessary because recent Mellanox software no longer advertises
+ * 100CR even though it seems to be able to tune in this mode. Adding 100CR4
+ * to the lp abilities means that we will resolve 100CR4 and start using this
+ * mode.
+ */
+#define SBL_AN_KR4_BIT (1ULL << (AN_CW_A_BASE_BIT + AN_CW_A_100GBASE_KR4_BIT))
+#define SBL_AN_CR4_BIT (1ULL << (AN_CW_A_BASE_BIT + AN_CW_A_100GBASE_CR4_BIT))
+
+static bool sbl_an_100cr4_fixup(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+
+ /* check no match */
+ if (link->an_tx_page[0] & link->an_rx_page[0] & AN_CW_A_MASK)
+ return false;
+
+ /* check we want cr4 */
+ if (!(link->an_tx_page[0] & SBL_AN_CR4_BIT))
+ return false;
+
+ /* check lp wants kr4 */
+ if (!(link->an_rx_page[0] & SBL_AN_KR4_BIT))
+ return false;
+
+ /* add cr4 to lp abilities */
+ link->an_rx_page[0] |= SBL_AN_CR4_BIT;
+
+ /* fixup applied */
+ sbl_dev_info(sbl->dev, "an %d: 100cr4 fixup applied", port_num);
+ return true;
+}
+
+int sbl_link_autoneg(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link;
+ u32 base = SBL_PML_BASE(port_num);
+ u32 random;
+ u64 cfg_pcs_reg;
+ int err;
+
+ err = sbl_validate_instance(sbl);
+ if (err)
+ return err;
+
+ err = sbl_validate_port_num(sbl, port_num);
+ if (err)
+ return err;
+
+ /* set starting point */
+ link = sbl->link + port_num;
+ link->an_options = 0;
+ link->link_mode = link->blattr.link_mode;
+
+ switch (link->blattr.pec.an_mode) {
+
+ case SBL_AN_MODE_OFF:
+ sbl_dev_dbg(sbl->dev, "an %d: AN off", port_num);
+ /* nothing more to do here */
+ return 0;
+
+ case SBL_AN_MODE_ON:
+ case SBL_AN_MODE_FIXED:
+ /* ok */
+ break;
+
+ default:
+ sbl_dev_err(sbl->dev, "an %d: invalid AN mode (%s)", port_num,
+ sbl_an_mode_str(link->blattr.pec.an_mode));
+ return -EINVAL;
+ }
+
+ /* we can never be in loopback mode as the nonce will be the same! */
+ if (link->blattr.loopback_mode != SBL_LOOPBACK_MODE_OFF) {
+ sbl_dev_err(sbl->dev, "an %d: cannot autoneg in loopback mode", port_num);
+ return -ENOTUNIQ;
+ }
+
+ /* pcs must not be enabled and autoneg must be off */
+ cfg_pcs_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET);
+ if (SBL_PML_CFG_PCS_PCS_ENABLE_GET(cfg_pcs_reg)) {
+ sbl_dev_err(sbl->dev, "an %d: pcs is enabled", port_num);
+ err = -EBUSY;
+ goto out;
+ }
+ if (SBL_PML_CFG_PCS_ENABLE_AUTO_NEG_GET(cfg_pcs_reg)) {
+ sbl_dev_err(sbl->dev, "an %d: autoneg already enabled", port_num);
+ err = -EBUSY;
+ goto out;
+ }
+
+ /* sort out the data to send/receive */
+ err = sbl_an_setup_tx_pages(sbl, port_num);
+ if (err) {
+ sbl_dev_err(sbl->dev, "an %d, page setup failed [%d]", port_num, err);
+ goto out;
+ }
+
+ /* setup */
+ err = sbl_pml_install_intr_handler(sbl, port_num, SBL_AUTONEG_ERR_FLGS);
+ if (err)
+ goto out;
+ link->an_100cr4_fixup_applied = false;
+
+ /* try to negotiate */
+ link->an_try_count = 0;
+ while (1) {
+
+ link->an_try_count++;
+
+ /* keep trying until up timeout expires or cancelled */
+ if (sbl_start_timeout(sbl, port_num)) {
+ err = -ETIMEDOUT;
+ break;
+ }
+ if (sbl_base_link_start_cancelled(sbl, port_num)) {
+ err = -ECANCELED;
+ break;
+ }
+
+ /* setup the serdes and pml */
+ err = sbl_an_serdes_start(sbl, port_num);
+ if (err)
+ break;
+ usleep_range(1000, 2000);
+
+ err = sbl_an_pml_setup(sbl, port_num);
+ if (err)
+ break;
+
+ /* try to exchange pages */
+ err = sbl_an_exchange(sbl, port_num);
+ if (err == -EPROTO) {
+ /* we have received an unexpected base page
+ * The lp must have restarted autoneg and we should too
+ *
+ * However we know that some Mellanox cards will do this if there is no
+ * ability match - particularly only supporting 100KR4 not 100CR4
+ * See if we can try to fix this up
+ */
+ if (link->blattr.options & SBL_OPT_AUTONEG_100CR4_FIXUP) {
+ if (sbl_an_100cr4_fixup(sbl, port_num)) {
+ /* fixup applied */
+ link->an_100cr4_fixup_applied = true;
+ /* carry on to ability match */
+ } else {
+ /* fixup failed - retry exchange */
+ sbl_an_serdes_stop(sbl, port_num);
+ continue;
+ }
+ } else {
+ sbl_an_serdes_stop(sbl, port_num);
+ continue;
+ }
+ } else if (err == -ETIME) {
+ /* we have timed out */
+ if (link->an_try_count < link->blattr.pec.an_max_retry) {
+ /* retry */
+ sbl_an_serdes_stop(sbl, port_num);
+ /* Random delay of 1-5 before retry */
+ get_random_bytes(&random, sizeof(random));
+ random = 1u + (random % 5u);
+ msleep(random);
+ continue;
+ } else {
+ /* give up */
+ break;
+ }
+ } else if (err) {
+ /* other error */
+ sbl_dev_dbg(sbl->dev, "an %d: exchange failed [%d] (sm_state %s)",
+ port_num, err, sbl_an_get_sm_state(sbl, port_num));
+ break;
+ }
+
+ /* pages have been exchanged - try to resolve the mode etc */
+ err = sbl_an_ability_match(sbl, port_num);
+ if (err) {
+ /* no match - try again (in case they change) */
+ sbl_an_serdes_stop(sbl, port_num);
+ continue;
+ }
+
+ /* see if we need to update the start timeout */
+ sbl_an_update_timeout(sbl, port_num);
+
+ /* we know the lp is there */
+ link->lp_detected = true;
+ break;
+ }
+
+ /* cleanup */
+ if (link->sstate == SBL_SERDES_STATUS_AUTONEG)
+ sbl_an_serdes_stop(sbl, port_num);
+ sbl_pml_disable_intr_handler(sbl, port_num, SBL_AUTONEG_ERR_FLGS);
+ sbl_pml_remove_intr_handler(sbl, port_num);
+
+out:
+ sbl_pml_err_flgs_clear_all(sbl, port_num);
+ sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_ANEG);
+
+ return err;
+}
+
+void sbl_an_send_next_page(struct sbl_inst *sbl, int port_num)
+{
+ u32 base = SBL_PML_BASE(port_num);
+ u64 cfg_pcs_autoneg_reg;
+
+ cfg_pcs_autoneg_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET);
+ cfg_pcs_autoneg_reg = SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_LOADED_UPDATE(cfg_pcs_autoneg_reg, 1ULL);
+ sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET, cfg_pcs_autoneg_reg);
+}
+
+bool sbl_an_next_is_complete(struct sbl_inst *sbl, int port_num)
+{
+ u32 base = SBL_PML_BASE(port_num);
+ u64 sts_autoneg_next_reg;
+
+ sts_autoneg_next_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_OFFSET);
+
+ return SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_COMPLETE_GET(sts_autoneg_next_reg);
+}
+
+bool sbl_an_is_next_page(struct sbl_inst *sbl, int port_num)
+{
+ u32 base = SBL_PML_BASE(port_num);
+ u64 sts_autoneg_next_reg;
+ u64 state;
+ bool value;
+
+ sts_autoneg_next_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_OFFSET);
+
+ state = SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_STATE_GET(sts_autoneg_next_reg);
+
+ value = (state == SBL_PML_AUTONEG_STATE_COMPLETE_ACK) ||
+ (state == SBL_PML_AUTONEG_STATE_AN_GOOD_CHECK);
+
+ value = value && (SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_LP_ABILITY_GET(sts_autoneg_next_reg)) &&
+ (!SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_BASE_PAGE_GET(sts_autoneg_next_reg));
+
+ return value;
+}
+
+void sbl_an_setup_next_page(struct sbl_inst *sbl, int port_num, int page_idx)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ u32 base = SBL_PML_BASE(port_num);
+
+ sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_OFFSET,
+ link->an_tx_page[page_idx]);
+ sbl_dev_dbg(sbl->dev, "an %d: tx next page: 0x%llx", port_num,
+ link->an_tx_page[page_idx]);
+ sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_OFFSET);
+}
+
+void sbl_an_setup_null_page(struct sbl_inst *sbl, int port_num)
+{
+ u32 base = SBL_PML_BASE(port_num);
+ u64 null_page;
+
+ /* Construct a null message page as in 802.3-2015: 28.2.3.4.1 & annex 28C */
+ null_page = sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_OFFSET);
+ null_page ^= AN_NP_T_MASK; /* toggle bit 11 */
+ null_page &= ~AN_NP_NP_MASK; /* clear next page bit */
+ null_page |= AN_NP_MP_MASK; /* set this is msg page */
+ null_page &= ~AN_NP_MSG_MASK; /* clear msg code */
+ null_page |= AN_NP_CODE_NULL_MSG; /* set null msg code */
+ null_page &= ~AN_NP_UCF_MASK; /* clear unformatted code field code */
+ sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_OFFSET, null_page);
+ sbl_dev_dbg(sbl->dev, "an %d: tx null next page: 0x%llx", port_num,
+ null_page);
+ sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_OFFSET);
+}
+
+/* setup to detect complete or page received error flags become set */
+int sbl_an_hw_wait_prepare(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ unsigned long flags;
+ int err;
+
+ spin_lock_irqsave(&link->lock, flags);
+ if (link->start_cancelled) {
+ spin_unlock_irqrestore(&link->lock, flags);
+ return -ECANCELED;
+ }
+ reinit_completion(&link->an_hw_change);
+ spin_unlock_irqrestore(&link->lock, flags);
+
+ sbl_pml_err_flgs_clear(sbl, port_num, SBL_AUTONEG_ERR_FLGS);
+
+ err = sbl_pml_enable_intr_handler(sbl, port_num, SBL_AUTONEG_ERR_FLGS);
+ if (err) {
+ sbl_dev_err(sbl->dev, "an %d: intr enable failed [%d]", port_num, err);
+ return err;
+ }
+
+ return 0;
+}
+
+const char *sbl_an_get_sm_state(struct sbl_inst *sbl, int port_num)
+{
+ u32 base = SBL_PML_BASE(port_num);
+ u64 sts_pcs_autoneg_reg;
+ u64 sm_state;
+
+ sts_pcs_autoneg_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_OFFSET);
+ sm_state = SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_STATE_GET(sts_pcs_autoneg_reg);
+
+ return sbl_an_state_str(sm_state);
+}
+
+/**
+ * sbl_get_an_pages() - Get an pages
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @count: number of pages
+ * @pages: Pointer to max rx pages
+ *
+ * Get number of an pages 'count' and rx pages 'pages'
+ * by reading 'an_rx_count' and 'an_rx_page'
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int sbl_get_an_pages(struct sbl_inst *sbl, int port_num, int *count, u64 *pages)
+{
+ struct sbl_link *link;
+ int i;
+ int err;
+
+ err = sbl_validate_instance(sbl);
+ if (err)
+ return err;
+
+ err = sbl_validate_port_num(sbl, port_num);
+ if (err)
+ return err;
+
+ if (!count || !pages)
+ return -EINVAL;
+
+ link = sbl->link + port_num;
+
+ /* check we did autoneg successfully */
+ if ((link->blattr.pec.an_mode != SBL_AN_MODE_ON) &&
+ (link->blattr.pec.an_mode != SBL_AN_MODE_FIXED)) {
+ sbl_dev_info(sbl->dev, "%d: not autoneg mode - no pages to get", port_num);
+ return -ENODATA;
+ }
+ if (!link->an_rx_count) {
+ sbl_dev_info(sbl->dev, "%d: no an pages to get", port_num);
+ return -ENODATA;
+ }
+
+ if (link->an_rx_count > *count) {
+ /* insufficient space
+ * update size to say how much we need and return
+ */
+ *count = link->an_rx_count;
+ return -ENOSPC;
+ }
+
+ for (i = 0; i < link->an_rx_count; ++i)
+ *pages++ = link->an_rx_page[i];
+ *count = link->an_rx_count;
+
+ return 0;
+}
+EXPORT_SYMBOL(sbl_get_an_pages);
+
+void sbl_an_dump_state(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ u32 base = SBL_PML_BASE(port_num);
+ u64 val64;
+ int i;
+
+ val64 = sbl_read64(sbl, base | SBL_PML_ERR_FLG_OFFSET);
+ sbl_dev_dbg(sbl->dev, "an %d: dump: err flgs 0x%llx", port_num, val64);
+
+ val64 = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET);
+ sbl_dev_dbg(sbl->dev, "an %d: dump: cfg pcs 0x%llx", port_num, val64);
+
+ val64 = sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET);
+ sbl_dev_dbg(sbl->dev, "an %d: dump: cfg pcs an 0x%llx", port_num, val64);
+
+ val64 = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_OFFSET);
+ sbl_dev_dbg(sbl->dev, "an %d: dump: sts an base pg 0x%llx", port_num, val64);
+
+ sbl_dev_dbg(sbl->dev, "%d base, complete (%lld), state (%lld) (%s), received (%lld) , base page (%lld), lp (%lld), lp_base (%lld) ",
+ port_num,
+ SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_COMPLETE_GET(val64),
+ SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_STATE_GET(val64),
+ sbl_an_get_sm_state(sbl, port_num),
+ SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_PAGE_RECEIVED_GET(val64),
+ SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_BASE_PAGE_GET(val64),
+ SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_LP_ABILITY_GET(val64),
+ SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_LP_BASE_PAGE_GET(val64));
+
+ val64 = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_OFFSET);
+
+ sbl_dev_dbg(sbl->dev, "%d next, complete (%lld), state (%lld) (%s), received (%lld) , base page (%lld), lp (%lld), lp_next (%lld) ",
+ port_num,
+ SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_COMPLETE_GET(val64),
+ SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_STATE_GET(val64),
+ sbl_an_get_sm_state(sbl, port_num),
+ SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_PAGE_RECEIVED_GET(val64),
+ SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_BASE_PAGE_GET(val64),
+ SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_LP_ABILITY_GET(val64),
+ SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_LP_NEXT_PAGE_GET(val64));
+
+ sbl_dev_dbg(sbl->dev, "an %d: dump: sts an next pg 0x%llx", port_num, val64);
+
+ for (i = 0; i < link->an_tx_count; ++i) {
+ sbl_dev_dbg(sbl->dev, "an %d: dump: tx pg %d: 0x%llx", port_num,
+ i, link->an_tx_page[i]);
+ }
+ for (i = 0; i < link->an_rx_count; ++i) {
+ sbl_dev_dbg(sbl->dev, "an %d: dump: rx pg %d: 0x%llx", port_num,
+ i, link->an_rx_page[i]);
+ }
+}
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_link_an_nic.c b/drivers/net/ethernet/hpe/sbl/sbl_link_an_nic.c
new file mode 100644
index 000000000000..e9536283c47a
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_link_an_nic.c
@@ -0,0 +1,111 @@
+// 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/random.h>
+#include <linux/delay.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_an.h>
+
+#include <linux/hpe/sbl/sbl_pml.h>
+
+#include "sbl_pml_fn.h"
+#include "sbl_link.h"
+#include "sbl_serdes.h"
+#include "sbl_constants.h"
+#include "sbl_serdes_map.h"
+#include "sbl_internal.h"
+
+int sbl_an_page_exchange(struct sbl_inst *sbl, int port_num, unsigned long remaining_jiffies)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ int xcng_count = 1;
+ u64 sts_autoneg_next_reg;
+ u32 base = SBL_PML_BASE(port_num);
+ int err;
+
+ /* -- Interrupt based next page exchange sequence -- */
+ do {
+ /* Too many Rx pages ?*/
+ if (xcng_count >= SBL_AN_MAX_RX_PAGES) {
+ sbl_dev_err_ratelimited(sbl->dev, "an %d: rx next page: too many pages %d",
+ port_num, xcng_count);
+ sbl_an_dump_state(sbl, port_num);
+ return -EPROTO;
+ }
+
+ sbl_dev_dbg(sbl->dev, "an %d: next page %d: start", port_num, xcng_count);
+
+ if (sbl_base_link_start_cancelled(sbl, port_num))
+ return -ECANCELED;
+ if (sbl_start_timeout(sbl, port_num))
+ return -ETIMEDOUT;
+
+ /* setup the next page (real or null) */
+ if (xcng_count < link->an_tx_count)
+ sbl_an_setup_next_page(sbl, port_num, xcng_count);
+ else
+ sbl_an_setup_null_page(sbl, port_num);
+
+ /* setup and enable interrupt */
+ err = sbl_an_hw_wait_prepare(sbl, port_num);
+ if (err)
+ return err;
+
+ /* send the next page */
+ sbl_an_send_next_page(sbl, port_num);
+
+ /* wait for next page interrupt */
+ remaining_jiffies = wait_for_completion_timeout(&link->an_hw_change, remaining_jiffies);
+ if (sbl_base_link_start_cancelled(sbl, port_num))
+ return -ECANCELED;
+ if (!remaining_jiffies) {
+ sbl_dev_err(sbl->dev, "an %d: next page %d: exchange timeout", port_num, xcng_count);
+ sbl_an_dump_state(sbl, port_num);
+ return -ETIME;
+ }
+
+ /* check if received page is a next page */
+ if (!sbl_an_is_next_page(sbl, port_num)) {
+ sbl_dev_dbg_ratelimited(sbl->dev, "an %d: next page %d: missing next page indication, resend next-page",
+ port_num, xcng_count);
+ sbl_an_dump_state(sbl, port_num);
+ continue;
+
+ }
+
+ /* put the received page in the buffer if indicated */
+ if (link->an_rx_page[xcng_count - 1] & AN_NP_NP_MASK) {
+ sts_autoneg_next_reg = sbl_read64(sbl, base |
+ SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_OFFSET);
+ link->an_rx_page[xcng_count] =
+ SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_LP_NEXT_PAGE_GET(sts_autoneg_next_reg);
+ sbl_dev_dbg(sbl->dev, "an %d: rx next page: 0x%llx",
+ port_num, link->an_rx_page[xcng_count]);
+ link->an_rx_count++;
+ }
+
+ /* go to the additional next page */
+ xcng_count++;
+
+ /* check to see if the exchange is done */
+ } while (!sbl_an_next_is_complete(sbl, port_num));
+
+ return 0;
+}
+
+void sbl_an_version_read(struct sbl_inst *sbl, int port_num)
+{
+ struct sbl_link *link = sbl->link + port_num;
+
+ /* cassini version here */
+ /* TODO: Implement dynamic version detection from HW device tree or runtime detection.
+ * Currently hardcoded to CASSINI_V1. Should query actual hardware version
+ * during autonegotiation setup to properly report link partner capability.
+ */
+ link->an_tx_page[2] |= ((SBL_LP_SUBTYPE_CASSINI_V1 & AN_LP_SUBTYPE_MASK) << AN_LP_SUBTYPE_BASE_BIT);
+}
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_link_nic.c b/drivers/net/ethernet/hpe/sbl/sbl_link_nic.c
new file mode 100644
index 000000000000..2c0be275d019
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_link_nic.c
@@ -0,0 +1,196 @@
+// 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/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"
+
+/**
+ * sbl_link_get_fec_thresholds() - Get fec thresholds
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @ucw_bad: Pointer to an int where output is stored(ucw threshold value)
+ * @ccw_bad: Pointer to an int where output is stored(ccw threshold value)
+ * @fecl_warn: Pointer to an int where output is stored(warn threshold value)
+ *
+ * Get threshold values based on link mode used.
+ * @ucw_bad, @ucw_bad and @fecl_warn holds the values and
+ * returns through provided pointers to the caller.
+ *
+ * Return the failure and warning thresholds for fec metrics
+ * monitoring
+ *
+ * The ccw values used here corresponds to a BER of 1.e-4
+ * The ucw values correspond to a little less than 1e-10
+ * The single lane fec warning threshold is the ccw rate divided by
+ * the number of fec lanes in use for the mode.
+ *
+ *
+ * 200G LINK (BS) (CD)
+ * 212500000000 bits/second over a 4-lane link @ 200G
+ * 39062500 cw/s (5440 bits/codeword)
+ *
+ * (Post)Pre-FEC BER
+ * 21250000.000000 (un)corrected cw/s => 1e-04 BER (1 bad bit per codeword)
+ * 2125000.000000 (un)corrected cw/s => 1e-05 BER
+ * 212500.000000 (un)corrected cw/s => 1e-06 BER
+ * 21250.000000 (un)corrected cw/s => 1e-07 BER
+ * 2125.000000 (un)corrected cw/s => 1e-08 BER
+ * 212.500000 (un)corrected cw/s => 1e-09 BER
+ * 21.250000 (un)corrected cw/s => 1e-10 BER
+ * 2.125000 (un)corrected cw/s => 1e-11 BER
+ * 0.212500 (un)corrected cw/s => 1e-12 BER
+ * 0.021250 (un)corrected cw/s => 1e-13 BER
+ * 0.002125 (un)corrected cw/s => 1e-13 BER
+ *
+ * 100G LINK (BJ)
+ * 103125000000 bits/second over a 4-lane link @ 100G
+ * 19531250 cw/s (5280 bits/codeword)
+ *
+ * (Post) Pre-FEC BER
+ * 1031250.000000 (un)corrected cw/s => 1e-05 BER (1 bad bit per codeword)
+ * 103125.000000 (un)corrected cw/s => 1e-06 BER
+ * 10312.500000 (un)corrected cw/s => 1e-07 BER
+ * 1031.250000 (un)corrected cw/s => 1e-08 BER
+ * 103.125000 (un)corrected cw/s => 1e-09 BER
+ * 10.312500 (un)corrected cw/s => 1e-10 BER
+ * 1.031250 (un)corrected cw/s => 1e-11 BER
+ * 0.103125 (un)corrected cw/s => 1e-12 BER
+ *
+ * Cassini uses the old
+ * sbl_link_get_fec_thresholds()
+ * Rosetta uses the newer
+ * sbl_link_get_ucw_thresh_ieee()
+ * sbl_link_get_ucw_thresh_hpe()
+ * sbl_link_get_ccw_thresh_ieee()
+ * sbl_link_get_ccw_thresh_hpe()
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int sbl_link_get_fec_thresholds(struct sbl_inst *sbl, int port_num,
+ int *ucw_bad, int *ccw_bad, int *fecl_warn)
+{
+ struct sbl_link *link = sbl->link + port_num;
+ int ucw;
+ int ccw;
+ int warn;
+
+ if (link->sstate != SBL_SERDES_STATUS_RUNNING)
+ return -ENAVAIL;
+
+ switch (link->link_mode) {
+ case SBL_LINK_MODE_BS_200G:
+ ucw = 21;
+ ccw = 21250000;
+ warn = ccw / 8;
+ break;
+ case SBL_LINK_MODE_BJ_100G:
+ ucw = 11;
+ ccw = 10312500;
+ warn = ccw / 8;
+ break;
+ case SBL_LINK_MODE_CD_100G:
+ ucw = 11;
+ ccw = 10625000;
+ warn = ccw / 4;
+ break;
+ case SBL_LINK_MODE_CD_50G:
+ ucw = 5;
+ ccw = 5312500;
+ warn = ccw / 2;
+ break;
+ default:
+ sbl_dev_err(sbl->dev, "%d: bad mode for fec thresh", port_num);
+ return -ENAVAIL;
+ }
+
+ if (ucw_bad)
+ *ucw_bad = ucw;
+ if (ccw_bad)
+ *ccw_bad = ccw;
+ if (fecl_warn)
+ *fecl_warn = warn;
+
+ return 0;
+}
+EXPORT_SYMBOL(sbl_link_get_fec_thresholds);
+
+#ifdef CONFIG_SYSFS
+/**
+ * sbl_base_link_llr_sysfs_sprint() - Report LLR on/off 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 checks the state info flags of LLR.
+ * Based on this flag, it formats the string indicating
+ * whether LLR is on/off and writes it into provided buffer
+ *
+ * Context: Process context, Acquires lock and release link->lock <spin_lock>
+ *
+ * Return: Number of characters written, at most @size - 1
+ */
+int sbl_base_link_llr_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 (READ_ONCE(link->link_info) & SBL_LINK_INFO_LLR_RUN)
+ s += scnprintf(buf + s, size - s, "on");
+ else
+ s += scnprintf(buf + s, size - s, "off");
+
+ spin_unlock(&link->lock);
+
+ return s;
+}
+EXPORT_SYMBOL(sbl_base_link_llr_sysfs_sprint);
+
+/**
+ * sbl_base_link_loopback_sysfs_sprint() - Report loopback mode 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 gets the loopback mode status and
+ * formats the string into the provided buffer
+ *
+ * Context: Process context, Acquires lock and release link->lock <spin_lock>
+ *
+ * Return: Number of characters written, at most @size - 1
+ */
+int sbl_base_link_loopback_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);
+
+ s += scnprintf(buf + s, size - s, sbl_loopback_mode_str(link->loopback_mode));
+
+ spin_unlock(&link->lock);
+
+ return s;
+}
+EXPORT_SYMBOL(sbl_base_link_loopback_sysfs_sprint);
+#endif
--
2.56.0