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