diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.c b/drivers/net/ethernet/intel/ice/ice_dpll.c index 85a74cd6ea1f..0109fc34307c 100644 --- a/drivers/net/ethernet/intel/ice/ice_dpll.c +++ b/drivers/net/ethernet/intel/ice/ice_dpll.c @@ -22,6 +22,8 @@ #define E825_RCLK_PARENT_0_PIN_IDX 0 #define E825_RCLK_PARENT_1_PIN_IDX 1 +#define E825_DPLL_TSPLL_BASE_IDX 0 +#define E825_DPLL_TXCLK_BASE_IDX 1 #define ICE_DPLL_PIN_SW_INPUT_ABS(in_idx) \ (ICE_DPLL_SW_PIN_INPUT_BASE_SFP + (in_idx)) @@ -84,6 +86,7 @@ static const char * const ice_dpll_sw_pin_sma[] = { "SMA1", "SMA2" }; static const char * const ice_dpll_sw_pin_ufl[] = { "U.FL1", "U.FL2" }; static const char * const ice_dpll_ext_eref_pin = "EXT_EREF0"; static const char * const ice_dpll_fwnode_ext_synce = "clk_ref_synce"; +static const char * const ice_dpll_fwnode_time_ref = "time_ref"; static const struct dpll_pin_frequency ice_esync_range[] = { DPLL_PIN_FREQUENCY_RANGE(0, DPLL_PIN_FREQUENCY_1_HZ), @@ -2814,6 +2817,160 @@ static const struct dpll_pin_ops ice_dpll_txclk_ops = { .direction_get = ice_dpll_input_direction, }; +/** + * ice_dpll_tspll_lock_status_get - derive TSPLL state for dpll subsystem + * @pf: board private structure + * @use_cached: if true, read lock state from pf->ptp.tspll_locked (maintained + * by the PTP periodic worker); if false, query hardware directly + * + * If TIME_REF is not selected, TSPLL is treated as unlocked from the dpll + * subsystem perspective, regardless of raw lock bit. + * + * Return: TSPLL lock status + */ +static enum dpll_lock_status +ice_dpll_tspll_lock_status_get(struct ice_pf *pf, bool use_cached) +{ + enum ice_clk_src clk_src; + bool locked; + + if (ice_tspll_get_clk_src(&pf->hw, &clk_src) || + clk_src != ICE_CLK_SRC_TIME_REF) + return DPLL_LOCK_STATUS_UNLOCKED; + + if (use_cached) { + locked = READ_ONCE(pf->ptp.tspll_locked); + } else { + bool lock_lost; + + if (ice_tspll_lost_lock_e825c(&pf->hw, &lock_lost)) + return DPLL_LOCK_STATUS_UNLOCKED; + locked = !lock_lost; + } + return locked ? DPLL_LOCK_STATUS_LOCKED : DPLL_LOCK_STATUS_UNLOCKED; +} + +/** + * ice_dpll_tspll_state_on_dpll_get - get TIME_REF pin state on TSPLL DPLL + * @pin: pointer to a pin + * @pin_priv: private data pointer passed on pin registration + * @dpll: registered dpll pointer + * @dpll_priv: private data pointer passed on dpll registration + * @state: on success holds pin state on parent dpll + * @extack: error reporting + * + * Dpll subsystem callback. Returns CONNECTED if the TSPLL is using + * TIME_REF as its clock source, DISCONNECTED otherwise. + * + * Return: + * * 0 - success + * * negative - failed to read clock source + */ +static int +ice_dpll_tspll_state_on_dpll_get(const struct dpll_pin *pin, void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct ice_dpll_pin *p = pin_priv; + enum ice_clk_src clk_src; + int err; + + if (ice_dpll_is_reset(p->pf, extack)) + return -EBUSY; + + err = ice_tspll_get_clk_src(&p->pf->hw, &clk_src); + if (err) + return err; + + *state = clk_src == ICE_CLK_SRC_TIME_REF ? DPLL_PIN_STATE_CONNECTED : + DPLL_PIN_STATE_DISCONNECTED; + return 0; +} + +/** + * ice_dpll_tspll_state_on_dpll_set - set TIME_REF pin state on TSPLL DPLL + * @pin: pointer to a pin + * @pin_priv: private data pointer passed on pin registration + * @dpll: registered dpll pointer + * @dpll_priv: private data pointer passed on dpll registration + * @state: requested state of the pin + * @extack: error reporting + * + * Dpll subsystem callback. Enables (CONNECTED) or disables (DISCONNECTED) + * the TIME_REF signal as the TSPLL clock source. Selects TCXO when disabled. + * + * Return: + * * 0 - success + * * negative - error + */ +static int +ice_dpll_tspll_state_on_dpll_set(const struct dpll_pin *pin, void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct ice_dpll_pin *p = pin_priv; + struct ice_pf *pf = p->pf; + enum ice_clk_src clk_src; + bool changed = false; + struct ice_dpll *tp; + int ret; + + if (ice_dpll_is_reset(pf, extack)) + return -EBUSY; + + if (state != DPLL_PIN_STATE_CONNECTED && + state != DPLL_PIN_STATE_DISCONNECTED) { + NL_SET_ERR_MSG(extack, + "unsupported pin state for TSPLL reference clock"); + return -EINVAL; + } + + clk_src = (state == DPLL_PIN_STATE_CONNECTED) ? ICE_CLK_SRC_TIME_REF : + ICE_CLK_SRC_TCXO; + tp = &pf->dplls.tspll; + /* Serialize multi-register TSPLL reconfiguration with restart path. */ + mutex_lock(&pf->dplls.lock); + ret = ice_tspll_set_cfg(&pf->hw, ICE_TSPLL_FREQ_156_250, clk_src); + if (!ret || ret == -EAGAIN) { + enum dpll_lock_status new_state; + + new_state = ice_dpll_tspll_lock_status_get(pf, false); + + /* Sync the cached lock state to what we just observed from HW + * so ice_dpll_periodic_work_e825() (cached path) does not emit + * a spurious UNLOCKED notification between now and the next + * PTP monitor tick. + */ + WRITE_ONCE(pf->ptp.tspll_locked, + new_state == DPLL_LOCK_STATUS_LOCKED); + + if (tp->prev_dpll_state != new_state) { + tp->dpll_state = new_state; + tp->prev_dpll_state = new_state; + changed = true; + } + } + mutex_unlock(&pf->dplls.lock); + if (changed) + __dpll_device_change_ntf(tp->dpll); + /* TSPLL reconfiguration may complete before lock is reacquired. + * PTP periodic monitoring tracks and restores the final lock state. + */ + if (ret == -EAGAIN) + ret = 0; + + return ret; +} + +static const struct dpll_pin_ops ice_dpll_tspll_in_ops = { + .state_on_dpll_set = ice_dpll_tspll_state_on_dpll_set, + .state_on_dpll_get = ice_dpll_tspll_state_on_dpll_get, + .direction_get = ice_dpll_input_direction, +}; + static const struct dpll_pin_ops ice_dpll_pin_sma_ops = { .state_on_dpll_set = ice_dpll_sma_pin_state_set, .state_on_dpll_get = ice_dpll_sw_pin_state_get, @@ -2902,6 +3059,68 @@ static u64 ice_generate_clock_id(struct ice_pf *pf) return pci_get_dsn(pf->pdev); } +/** + * ice_generate_dpll_clock_id - generate clock_id for a specific dpll device + * @pf: board private structure + * @d: dpll device context + * @type: dpll type being registered + * + * For E825 generic DPLLs, use per-interface permanent MAC as the clock_id + * basis so userspace can unambiguously map DPLL devices to interfaces. + * TX-CLK keeps plain MAC-derived ID, while TSPLL uses the same basis with + * a dedicated tag bit to remain distinct on source-timer owner PFs. + * Other DPLL objects keep board-level DSN-derived clock_id. + * + * Return: generated clock id for a dpll device + */ +static u64 ice_generate_dpll_clock_id(struct ice_pf *pf, struct ice_dpll *d, + enum dpll_type type) +{ + struct ice_hw *hw = &pf->hw; + u64 mac_clock_id; + + if (hw->mac_type == ICE_MAC_GENERIC_3K_E825 && + type == DPLL_TYPE_GENERIC && + hw->port_info && + is_valid_ether_addr(hw->port_info->mac.perm_addr)) { + mac_clock_id = ether_addr_to_u64(hw->port_info->mac.perm_addr); + + if (d->dpll_idx >= E825_DPLL_TXCLK_BASE_IDX) + return mac_clock_id; + + if (d->dpll_idx == E825_DPLL_TSPLL_BASE_IDX) + return mac_clock_id | BIT_ULL(63); + } + + return pf->dplls.clock_id; +} + +/** + * ice_dpll_is_own_dpll_clock_id - check if clock_id belongs to this pf's DPLLs + * @pf: board private structure + * @clock_id: clock_id from a DPLL notification + * + * Match info->src_clock_id from a DPLL pin notification against any DPLL + * device this PF has registered. Used to suppress self-notifications + * generated as a side effect of our own dpll_pin_register() and + * dpll_pin_unregister() calls on the fwnode-backed SYNCE and TIME_REF pins, + * whose DPLLs (TXC and TSPLL) use MAC-derived clock_ids on E825. + * + * Return: true if clock_id matches one of this PF's registered DPLL devices. + */ +static bool ice_dpll_is_own_dpll_clock_id(struct ice_pf *pf, u64 clock_id) +{ + if (clock_id == pf->dplls.clock_id) + return true; + if (pf->hw.mac_type != ICE_MAC_GENERIC_3K_E825) + return false; + if (clock_id == ice_generate_dpll_clock_id(pf, &pf->dplls.txc, + DPLL_TYPE_GENERIC)) + return true; + return clock_id == ice_generate_dpll_clock_id(pf, &pf->dplls.tspll, + DPLL_TYPE_GENERIC); +} + /** * ice_dpll_pin_ntf - notify pin change including any SW pin wrappers * @dplls: pointer to dplls struct @@ -3134,12 +3353,44 @@ ice_dpll_update_state(struct ice_pf *pf, struct ice_dpll *d, bool init) return ret; } +/** + * ice_dpll_periodic_work_e825 - TSPLL DPLL periodic update for E825 + * @pf: board private structure + * + * Publish TSPLL lock status to the dpll subsystem. The PTP periodic worker + * owns TSPLL lock polling and recovery; this function consumes the cached + * result for dpll notifications. + * + * Context: Must be called without pf->dplls.lock held. + */ +static void ice_dpll_periodic_work_e825(struct ice_pf *pf) +{ + struct ice_dpll *tp = &pf->dplls.tspll; + enum dpll_lock_status new_state; + bool changed = false; + + mutex_lock(&pf->dplls.lock); + new_state = ice_dpll_tspll_lock_status_get(pf, true); + + if (tp->prev_dpll_state != new_state) { + tp->dpll_state = new_state; + tp->prev_dpll_state = new_state; + changed = true; + } + mutex_unlock(&pf->dplls.lock); + if (changed) + dpll_device_change_ntf(tp->dpll); +} + /** * ice_dpll_periodic_work - DPLLs periodic worker * @work: pointer to kthread_work structure * - * DPLLs periodic worker is responsible for polling state of dpll. - * Context: Holds pf->dplls.lock + * Periodic worker responsible for polling DPLL state. On E810 devices it + * polls the EEC and PPS DPLLs. On E825 devices, when this PF owns the + * source timer, it publishes TSPLL lock status to the dpll subsystem. + * + * Context: Acquires and releases pf->dplls.lock */ static void ice_dpll_periodic_work(struct kthread_work *work) { @@ -3147,38 +3398,43 @@ static void ice_dpll_periodic_work(struct kthread_work *work) struct ice_pf *pf = container_of(d, struct ice_pf, dplls); struct ice_dpll *de = &pf->dplls.eec; struct ice_dpll *dp = &pf->dplls.pps; - u32 phase_offset_ntf = 0; + u32 ntf_mask = 0; int ret = 0; if (ice_is_reset_in_progress(pf->state)) goto resched; - mutex_lock(&pf->dplls.lock); - d->periodic_counter++; - ret = ice_dpll_update_state(pf, de, false); - if (!ret) - ret = ice_dpll_update_state(pf, dp, false); - if (!ret && dp->phase_offset_monitor_period && - d->periodic_counter % dp->phase_offset_monitor_period == 0) - ret = ice_dpll_pps_update_phase_offsets(pf, &phase_offset_ntf); - if (ret) { - /* EBUSY is expected during reset recovery */ - if (pf->hw.adminq.sq_last_status != LIBIE_AQ_RC_EBUSY) - d->cgu_state_acq_err_num++; - /* stop rescheduling this worker */ - if (d->cgu_state_acq_err_num > - ICE_CGU_STATE_ACQ_ERR_THRESHOLD) { - dev_err(ice_pf_to_dev(pf), - "EEC/PPS DPLLs periodic work disabled\n"); - mutex_unlock(&pf->dplls.lock); - return; + + if (pf->hw.mac_type == ICE_MAC_GENERIC_3K_E825) { + ice_dpll_periodic_work_e825(pf); + } else { + mutex_lock(&pf->dplls.lock); + d->periodic_counter++; + ret = ice_dpll_update_state(pf, de, false); + if (!ret) + ret = ice_dpll_update_state(pf, dp, false); + if (!ret && dp->phase_offset_monitor_period && + d->periodic_counter % dp->phase_offset_monitor_period == 0) + ret = ice_dpll_pps_update_phase_offsets(pf, &ntf_mask); + if (ret) { + /* EBUSY is expected during reset recovery */ + if (pf->hw.adminq.sq_last_status != LIBIE_AQ_RC_EBUSY) + d->cgu_state_acq_err_num++; + /* stop rescheduling this worker */ + if (d->cgu_state_acq_err_num > + ICE_CGU_STATE_ACQ_ERR_THRESHOLD) { + dev_err(ice_pf_to_dev(pf), + "EEC/PPS DPLLs periodic work disabled\n"); + mutex_unlock(&pf->dplls.lock); + return; + } } + mutex_unlock(&pf->dplls.lock); + ice_dpll_notify_changes(de); + ice_dpll_notify_changes(dp); + if (ntf_mask) + ice_dpll_pins_notify_mask(d, d->inputs, d->num_inputs, + ntf_mask); } - mutex_unlock(&pf->dplls.lock); - ice_dpll_notify_changes(de); - ice_dpll_notify_changes(dp); - if (phase_offset_ntf) - ice_dpll_pins_notify_mask(d, d->inputs, d->num_inputs, - phase_offset_ntf); resched: /* Run twice a second or reschedule if update failed */ @@ -3536,6 +3792,7 @@ static void ice_dpll_pin_notify_work(struct work_struct *work) work); struct ice_dpll_pin *pin, *parent = w->pin; bool is_tx_synce_parent = false; + bool is_tspll_time_ref = false; struct ice_pf *pf = parent->pf; bool is_rclk_parent = false; int ret; @@ -3556,7 +3813,11 @@ static void ice_dpll_pin_notify_work(struct work_struct *work) is_tx_synce_parent = ice_dpll_fwnode_eq(parent->fwnode, pf->dplls.txclks[E825_EXT_SYNCE_PIN_IDX].fwnode); - if (!is_rclk_parent && !is_tx_synce_parent) + + is_tspll_time_ref = + ice_dpll_fwnode_eq(parent->fwnode, pf->dplls.tspll_in.fwnode); + + if (!is_rclk_parent && !is_tx_synce_parent && !is_tspll_time_ref) goto out; switch (w->action) { @@ -3597,6 +3858,18 @@ static void ice_dpll_pin_notify_work(struct work_struct *work) ERR_PTR(ret)); goto drop_parent_ref; } + } else if (is_tspll_time_ref) { + /* Register TIME_REF pin directly to TSPLL DPLL */ + ret = dpll_pin_register(pf->dplls.tspll.dpll, + parent->pin, + &ice_dpll_tspll_in_ops, + parent); + if (ret) { + dev_err(ice_pf_to_dev(pf), + "TSPLL TIME_REF pin register failed: %pe\n", + ERR_PTR(ret)); + goto drop_parent_ref; + } } break; case DPLL_PIN_DELETED: @@ -3615,6 +3888,11 @@ static void ice_dpll_pin_notify_work(struct work_struct *work) pin = &pf->dplls.txclks[E825_EXT_SYNCE_PIN_IDX]; dpll_pin_unregister(pf->dplls.txc.dpll, pin->pin, &ice_dpll_txclk_ops, pin); + } else if (is_tspll_time_ref) { + /* Unregister TIME_REF pin from TSPLL DPLL */ + dpll_pin_unregister(pf->dplls.tspll.dpll, + parent->pin, + &ice_dpll_tspll_in_ops, parent); } drop_parent_ref: /* Drop fwnode pin reference */ @@ -3642,10 +3920,12 @@ static int ice_dpll_pin_notify(struct notifier_block *nb, unsigned long action, if (pin->fwnode != info->fwnode) return NOTIFY_DONE; /* Not this pin */ - /* Ignore notification which are the outcome of internal pin - * registration/unregistration calls - synce pin case. + /* Ignore notifications that are a side effect of internal pin + * registration/unregistration calls. E825 uses per-device + * MAC-derived clock_ids for the TXC and TSPLL generic DPLLs, so + * info->src_clock_id may not equal pf->dplls.clock_id. */ - if (info->src_clock_id == pin->pf->dplls.clock_id) + if (ice_dpll_is_own_dpll_clock_id(pin->pf, info->src_clock_id)) return NOTIFY_DONE; work = kzalloc_obj(*work); @@ -3786,12 +4066,12 @@ ice_dpll_deinit_fwnode_pins(struct ice_pf *pf, struct ice_dpll_pin *pins, destroy_workqueue(pf->dplls.wq); } -static int ice_dpll_deinit_txclk_pins(struct ice_pf *pf) +static int ice_dpll_deinit_txclk_pins(struct ice_pf *pf, bool flush) { struct ice_dpll_pin *synce_pin = &pf->dplls.txclks[E825_EXT_SYNCE_PIN_IDX]; struct ice_dpll *dt = &pf->dplls.txc; - ice_dpll_stop_fwnode_pin_activity(synce_pin, true); + ice_dpll_stop_fwnode_pin_activity(synce_pin, flush); ice_dpll_unregister_pins(dt->dpll, pf->dplls.txclks, &ice_dpll_txclk_ops, ARRAY_SIZE(pf->dplls.txclks)); @@ -3806,6 +4086,25 @@ static int ice_dpll_deinit_txclk_pins(struct ice_pf *pf) return 0; } +/** + * ice_dpll_deinit_tspll_pins - deinitialize TSPLL fwnode pin + * @pf: board private structure + * + * Stop notifier activity and release the TIME_REF fwnode pin, unregistering + * it from the TSPLL DPLL if it was registered. + */ +static void ice_dpll_deinit_tspll_pins(struct ice_pf *pf) +{ + struct ice_dpll_pin *time_ref = &pf->dplls.tspll_in; + struct ice_dpll *tp = &pf->dplls.tspll; + + ice_dpll_stop_fwnode_pin_activity(time_ref, true); + if (!IS_ERR_OR_NULL(time_ref->pin)) + dpll_pin_unregister(tp->dpll, time_ref->pin, + &ice_dpll_tspll_in_ops, time_ref); + ice_dpll_release_fwnode_pin(time_ref); +} + /** * ice_dpll_deinit_pins - deinitialize direct pins * @pf: board private structure @@ -3826,7 +4125,10 @@ static void ice_dpll_deinit_pins(struct ice_pf *pf, bool cgu) ice_dpll_deinit_rclk_pin(pf); if (pf->hw.mac_type == ICE_MAC_GENERIC_3K_E825) { - ice_dpll_deinit_txclk_pins(pf); + if (ice_pf_src_tmr_owned(pf) && + test_bit(ICE_FLAG_PTP_SUPPORTED, pf->flags)) + ice_dpll_deinit_tspll_pins(pf); + ice_dpll_deinit_txclk_pins(pf, true); ice_dpll_deinit_fwnode_pins(pf, pf->dplls.inputs, 0); } if (cgu) { @@ -3968,10 +4270,16 @@ static int ice_dpll_init_txclk_pins(struct ice_pf *pf, int start_idx) { struct ice_dpll_pin *ref_pin = pf->dplls.txclks; struct ice_dpll *txc = &pf->dplls.txc; + u64 clock_id; int ret; + /* EXT_EREF0 is a non-fwnode pin; its clock_id must match the TX-CLK + * DPLL device clock_id (see dpll_pin_register()). + */ + clock_id = ice_generate_dpll_clock_id(pf, txc, DPLL_TYPE_GENERIC); + /* Configure EXT_EREF0 pin */ - ret = ice_dpll_get_pins(pf, ref_pin, start_idx, 1, pf->dplls.clock_id); + ret = ice_dpll_get_pins(pf, ref_pin, start_idx, 1, clock_id); if (ret) return ret; ret = dpll_pin_register(txc->dpll, ref_pin->pin, &ice_dpll_txclk_ops, @@ -4022,6 +4330,45 @@ static int ice_dpll_init_txclk_pins(struct ice_pf *pf, int start_idx) return ret; } +/** + * ice_dpll_init_tspll_pins - init and register TSPLL TIME_REF fwnode pin + * @pf: board private structure + * + * Initialize the fwnode-based TIME_REF input pin for the TSPLL DPLL on E825. + * If the pin is not yet available in the dpll subsystem, registration will + * be deferred via the notifier path. + * + * Return: + * * 0 - success + * * negative - initialization failure reason + */ +static int ice_dpll_init_tspll_pins(struct ice_pf *pf) +{ + struct ice_dpll_pin *time_ref = &pf->dplls.tspll_in; + struct ice_dpll *tp = &pf->dplls.tspll; + int ret; + + time_ref->pf = pf; + ret = ice_dpll_init_fwnode_pin(time_ref, ice_dpll_fwnode_time_ref); + if (ret) + return ret; + + if (IS_ERR_OR_NULL(time_ref->pin)) { + dev_dbg(ice_pf_to_dev(pf), + "TSPLL TIME_REF pin not registered yet\n"); + return 0; + } + + ret = dpll_pin_register(tp->dpll, time_ref->pin, + &ice_dpll_tspll_in_ops, time_ref); + if (ret) { + ice_dpll_stop_fwnode_pin_activity(time_ref, false); + ice_dpll_release_fwnode_pin(time_ref); + } + + return ret; +} + /** * ice_dpll_init_pins_e825 - init pins and register pins with a dplls * @pf: board private structure @@ -4049,8 +4396,24 @@ static int ice_dpll_init_pins_e825(struct ice_pf *pf) goto unregister_pins; ret = ice_dpll_init_txclk_pins(pf, 0); - if (ret) + if (ret) { ice_dpll_deinit_rclk_pin(pf); + goto unregister_pins; + } + + if (ice_pf_src_tmr_owned(pf) && + test_bit(ICE_FLAG_PTP_SUPPORTED, pf->flags)) { + ret = ice_dpll_init_tspll_pins(pf); + if (ret) { + /* Avoid flushing pf->dplls.wq here: notifier work + * items block on pf->dplls.dpll_init which is + * completed by the unregister_pins path below. + * destroy_workqueue() there drains queued items. + */ + ice_dpll_deinit_txclk_pins(pf, false); + ice_dpll_deinit_rclk_pin(pf); + } + } unregister_pins: if (ret) { @@ -4194,7 +4557,7 @@ static int ice_dpll_init_dpll(struct ice_pf *pf, struct ice_dpll *d, bool cgu, enum dpll_type type) { - u64 clock_id = pf->dplls.clock_id; + u64 clock_id = ice_generate_dpll_clock_id(pf, d, type); int ret; d->dpll = dpll_device_get(clock_id, d->dpll_idx, THIS_MODULE, @@ -4237,6 +4600,7 @@ static void ice_dpll_deinit_worker(struct ice_pf *pf) kthread_cancel_delayed_work_sync(&d->work); kthread_destroy_worker(d->kworker); + d->kworker = NULL; } /** @@ -4673,6 +5037,7 @@ static void ice_dpll_deinit_info(struct ice_pf *pf) static int ice_dpll_init_info_e825c(struct ice_pf *pf) { struct ice_dplls *d = &pf->dplls; + struct ice_dpll *tp = &d->tspll; struct ice_dpll *dt = &d->txc; int ret = 0; int i; @@ -4681,7 +5046,11 @@ static int ice_dpll_init_info_e825c(struct ice_pf *pf) d->num_inputs = ICE_SYNCE_CLK_NUM; dt->dpll_state = ice_txclk_lock_status(pf->ptp.port.tx_clk); dt->mode = DPLL_MODE_MANUAL; - dt->dpll_idx = pf->ptp.port.port_num; + dt->dpll_idx = E825_DPLL_TXCLK_BASE_IDX + pf->ptp.port.port_num; + tp->dpll_state = ice_dpll_tspll_lock_status_get(pf, false); + tp->prev_dpll_state = tp->dpll_state; + tp->mode = DPLL_MODE_MANUAL; + tp->dpll_idx = E825_DPLL_TSPLL_BASE_IDX; d->inputs = kzalloc_objs(*d->inputs, d->num_inputs); if (!d->inputs) @@ -4824,7 +5193,8 @@ static int ice_dpll_init_info(struct ice_pf *pf, bool cgu) * resources and unregistering the dpll, pin and all resources used for * handling them. * - * Context: Destroys pf->dplls.lock mutex. Call only if ICE_FLAG_DPLL was set. + * Context: Call only if ICE_FLAG_DPLL was set. pf->dplls.lock lifetime is + * managed by ice_init_features()/ice_deinit_features(). */ void ice_dpll_deinit(struct ice_pf *pf) { @@ -4848,7 +5218,7 @@ void ice_dpll_deinit(struct ice_pf *pf) up_write(&pf->dplls.txclk_notify_rwsem); } - if (cgu) + if (pf->dplls.kworker) ice_dpll_deinit_worker(pf); if (pf->hw.mac_type == ICE_MAC_GENERIC_3K_E825) @@ -4861,9 +5231,10 @@ void ice_dpll_deinit(struct ice_pf *pf) ice_dpll_deinit_dpll(pf, &pf->dplls.eec, cgu); if (!IS_ERR_OR_NULL(pf->dplls.txc.dpll)) ice_dpll_deinit_dpll(pf, &pf->dplls.txc, false); + if (!IS_ERR_OR_NULL(pf->dplls.tspll.dpll)) + ice_dpll_deinit_dpll(pf, &pf->dplls.tspll, false); ice_dpll_deinit_info(pf); - mutex_destroy(&pf->dplls.lock); } /** @@ -4873,8 +5244,6 @@ void ice_dpll_deinit(struct ice_pf *pf) * Set up the device dplls, register them and pins connected within Linux dpll * subsystem. Allow userspace to obtain state of DPLL and handling of DPLL * configuration requests. - * - * Context: Initializes pf->dplls.lock mutex. */ static void ice_dpll_init_e825(struct ice_pf *pf) { @@ -4892,7 +5261,6 @@ static void ice_dpll_init_e825(struct ice_pf *pf) pf->ptp.port.port_num = pf->hw.lane_num; } - mutex_init(&d->lock); /* Initialize the txclk worker and its notification rwsem before any * code path can fail: ice_dpll_deinit() runs unconditionally on * failure and calls cancel_work_sync() / down_write() on these. @@ -4904,9 +5272,30 @@ static void ice_dpll_init_e825(struct ice_pf *pf) err = ice_dpll_init_info_e825c(pf); if (err) goto err_exit; + if (ice_pf_src_tmr_owned(pf) && + test_bit(ICE_FLAG_PTP_SUPPORTED, pf->flags)) { + err = ice_dpll_init_dpll(pf, &pf->dplls.tspll, false, + DPLL_TYPE_GENERIC); + if (err) + goto deinit_info; + /* Start the periodic worker before registering fwnode pins. The + * fwnode notifier path (ice_dpll_pin_notify_work()) blocks on + * pf->dplls.dpll_init, which is only signalled on the success + * return below. Starting the worker before any fwnode notifier + * is registered means a subsequent init failure cannot leave + * notifier work items queued on pf->dplls.wq, so the deinit + * flush_workqueue() paths in ice_dpll_deinit_pins() cannot + * deadlock. The E825 periodic worker only touches + * pf->dplls.tspll (already registered above) and pf->ptp state, + * so it is safe to run before pins. + */ + err = ice_dpll_init_worker(pf); + if (err) + goto deinit_tspll; + } err = ice_dpll_init_dpll(pf, &pf->dplls.txc, false, DPLL_TYPE_GENERIC); if (err) - goto deinit_info; + goto deinit_worker; err = ice_dpll_init_pins_e825(pf); if (err) goto deinit_txclk; @@ -4917,10 +5306,15 @@ static void ice_dpll_init_e825(struct ice_pf *pf) deinit_txclk: ice_dpll_deinit_dpll(pf, &pf->dplls.txc, false); +deinit_worker: + if (pf->dplls.kworker) + ice_dpll_deinit_worker(pf); +deinit_tspll: + if (!IS_ERR_OR_NULL(pf->dplls.tspll.dpll)) + ice_dpll_deinit_dpll(pf, &pf->dplls.tspll, false); deinit_info: ice_dpll_deinit_info(pf); err_exit: - mutex_destroy(&d->lock); dev_warn(ice_pf_to_dev(pf), "DPLLs init failure err:%d\n", err); } @@ -4931,16 +5325,12 @@ static void ice_dpll_init_e825(struct ice_pf *pf) * Set up the device dplls, register them and pins connected within Linux dpll * subsystem. Allow userspace to obtain state of DPLL and handling of DPLL * configuration requests. - * - * Context: Initializes pf->dplls.lock mutex. */ static void ice_dpll_init_e810(struct ice_pf *pf) { bool cgu = ice_is_feature_supported(pf, ICE_F_CGU); - struct ice_dplls *d = &pf->dplls; int err = 0; - mutex_init(&d->lock); err = ice_dpll_init_info(pf, cgu); if (err) goto err_exit; @@ -4971,7 +5361,6 @@ static void ice_dpll_init_e810(struct ice_pf *pf) deinit_info: ice_dpll_deinit_info(pf); err_exit: - mutex_destroy(&d->lock); dev_warn(ice_pf_to_dev(pf), "DPLLs init failure err:%d\n", err); } diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.h b/drivers/net/ethernet/intel/ice/ice_dpll.h index 103ba3e49068..bce0bea9a664 100644 --- a/drivers/net/ethernet/intel/ice/ice_dpll.h +++ b/drivers/net/ethernet/intel/ice/ice_dpll.h @@ -133,10 +133,12 @@ struct ice_dpll { * @eec: pointer to EEC dpll dev * @pps: pointer to PPS dpll dev * @txc: pointer to TXC dpll dev + * @tspll: pointer to TSPLL dpll dev (E825 only) * @inputs: input pins pointer * @outputs: output pins pointer * @rclk: recovered pins pointer * @txclks: TX clock reference pins pointer + * @tspll_in: TSPLL TIME_REF fwnode input pin (E825 only) * @num_inputs: number of input pins available on dpll * @num_outputs: number of output pins available on dpll * @cgu_state_acq_err_num: number of errors returned during periodic work @@ -177,12 +179,14 @@ struct ice_dplls { struct ice_dpll eec; struct ice_dpll pps; struct ice_dpll txc; + struct ice_dpll tspll; struct ice_dpll_pin *inputs; struct ice_dpll_pin *outputs; struct ice_dpll_pin sma[ICE_DPLL_PIN_SW_NUM]; struct ice_dpll_pin ufl[ICE_DPLL_PIN_SW_NUM]; struct ice_dpll_pin rclk; struct ice_dpll_pin txclks[ICE_DPLL_TXCLK_NUM_MAX]; + struct ice_dpll_pin tspll_in; u8 num_inputs; u8 num_outputs; u8 sma_data; diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index fe47ec0ba809..707c7431b91a 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -4725,6 +4725,14 @@ static void ice_init_features(struct ice_pf *pf) if (ice_is_safe_mode(pf)) return; + /* pf->dplls.lock guards TSPLL/CGU access shared between the DPLL + * subsystem callbacks and the PTP periodic worker's TSPLL monitor. + * Initialize it before ice_ptp_init() so the PTP kworker never sees + * an uninitialized mutex, and destroy it in ice_deinit_features() + * only after ice_ptp_release() has drained the kworker. + */ + mutex_init(&pf->dplls.lock); + /* initialize DDP driven features */ if (test_bit(ICE_FLAG_PTP_SUPPORTED, pf->flags)) ice_ptp_init(pf); @@ -4769,6 +4777,7 @@ static void ice_deinit_features(struct ice_pf *pf) ice_ptp_release(pf); if (test_bit(ICE_FLAG_DPLL, pf->flags)) ice_dpll_deinit(pf); + mutex_destroy(&pf->dplls.lock); if (pf->eswitch_mode == DEVLINK_ESWITCH_MODE_SWITCHDEV) xa_destroy(&pf->eswitch.reprs); ice_hwmon_exit(pf); diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c index eaec36ab6ae3..e54a98a0bdd0 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c @@ -4,8 +4,11 @@ #include "ice.h" #include "ice_lib.h" #include "ice_trace.h" +#include "ice_tspll.h" #include "ice_txclk.h" +#define ICE_TSPLL_LOG_INTERVAL 120 + static const char ice_pin_names[][64] = { "SDP0", "SDP1", @@ -2849,6 +2852,79 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf) } } +/** + * ice_ptp_tspll_monitor - poll and recover TSPLL lock on E825 owner PFs + * @pf: Board private structure + * + * Called from the PTP periodic worker. On E825 devices that own the source + * timer, poll the TSPLL lock status via CGU registers and trigger a restart + * if the lock has been lost. The result is cached in @pf->ptp.tspll_locked + * so it can be consumed by the DPLL periodic worker via READ_ONCE(). + * + * TSPLL lock is critical for PHC operation and must be monitored regardless + * of whether DPLL init succeeded or CONFIG_DPLL is enabled. Placing the + * monitor here makes recovery independent of the dpll subsystem. + * + * AQ read errors are rate-limited and do not stop monitoring. Lock-lost + * events are logged every 120 retries (~60 s at normal poll rate) to + * surface persistent failures without flooding the log. + */ +static void ice_ptp_tspll_monitor(struct ice_pf *pf) +{ + bool lock_lost; + int err; + + if (pf->hw.mac_type != ICE_MAC_GENERIC_3K_E825 || + !ice_pf_src_tmr_owned(pf)) + return; + + /* Serialize the entire monitor tick against TSPLL userspace reconfig + * (ice_dpll_tspll_state_on_dpll_set()). Both paths read HW state and + * write pf->ptp.tspll_locked; without holding pf->dplls.lock across + * the HW read here, a preempted monitor could observe stale HW state + * and then overwrite an accurate cache update from the DPLL callback. + * pf->dplls.lock is initialized in ice_init_features() before the PTP + * kworker starts and destroyed in ice_deinit_features() only after + * ice_ptp_release() has drained the kworker, so it is always valid. + */ + mutex_lock(&pf->dplls.lock); + err = ice_tspll_lost_lock_e825c(&pf->hw, &lock_lost); + if (err) { + mutex_unlock(&pf->dplls.lock); + dev_err_ratelimited(ice_pf_to_dev(pf), + "Failed reading TimeSync PLL lock status (err: %d). Retrying.\n", + err); + return; + } + + if (lock_lost) { + WRITE_ONCE(pf->ptp.tspll_locked, false); + if (!(pf->ptp.tspll_lock_retries % ICE_TSPLL_LOG_INTERVAL)) + dev_warn(ice_pf_to_dev(pf), + "TimeSync PLL lock lost. Retrying to acquire lock.\n"); + err = ice_tspll_restart_e825c(&pf->hw); + if (err) + dev_err_ratelimited(ice_pf_to_dev(pf), + "Failed to restart TimeSync PLL (err: %d).\n", + err); + pf->ptp.tspll_lock_retries++; + } else { + if (pf->ptp.tspll_lock_retries) { + const char *src_str = "unknown"; + enum ice_clk_src clk_src; + + if (!ice_tspll_get_clk_src(&pf->hw, &clk_src)) + src_str = ice_tspll_clk_src_str(clk_src); + dev_info(ice_pf_to_dev(pf), + "TimeSync PLL lock acquired with %s clock source after %u retries.\n", + src_str, pf->ptp.tspll_lock_retries); + } + WRITE_ONCE(pf->ptp.tspll_locked, true); + pf->ptp.tspll_lock_retries = 0; + } + mutex_unlock(&pf->dplls.lock); +} + static void ice_ptp_periodic_work(struct kthread_work *work) { struct ice_ptp *ptp = container_of(work, struct ice_ptp, work.work); @@ -2858,6 +2934,8 @@ static void ice_ptp_periodic_work(struct kthread_work *work) if (pf->ptp.state != ICE_PTP_READY) return; + ice_ptp_tspll_monitor(pf); + err = ice_ptp_update_cached_phctime(pf); ice_ptp_maybe_trigger_tx_interrupt(pf); @@ -2975,6 +3053,9 @@ static int ice_ptp_rebuild_owner(struct ice_pf *pf) err = ice_tspll_init(hw); if (err) return err; + /* Rebuild reinitialized TSPLL, so reset monitor retry state. */ + WRITE_ONCE(ptp->tspll_locked, true); + ptp->tspll_lock_retries = 0; /* Acquire the global hardware lock */ if (!ice_ptp_lock(hw)) { @@ -3319,6 +3400,7 @@ void ice_ptp_init(struct ice_pf *pf) } ptp->port.port_num = hw->lane_num; + ptp->tspll_locked = true; ice_ptp_init_hw(hw); ice_ptp_init_tx_interrupt_mode(pf); diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.h b/drivers/net/ethernet/intel/ice/ice_ptp.h index c4b0da7ce20e..0e40fef3b4e8 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.h +++ b/drivers/net/ethernet/intel/ice/ice_ptp.h @@ -249,6 +249,15 @@ struct ice_ptp_pin_desc { * @tx_hwtstamp_discarded: number of Tx skbs discarded due to cached PHC time * being too old to correctly extend timestamp * @late_cached_phc_updates: number of times cached PHC update is late + * @tspll_locked: last observed TSPLL lock state on E825 owner PFs. + * Written by the PTP periodic worker after polling the TSPLL and + * intended to be read (without pf->dplls.lock) by the DPLL periodic + * worker in a follow-up change. Access via READ_ONCE()/WRITE_ONCE(); + * precise synchronization is not required because both workers + * converge on the same value within one poll period. + * @tspll_lock_retries: counts consecutive poll cycles in which the TSPLL + * was found unlocked. Reset to zero when lock is re-acquired. Used to + * rate-limit the lock-lost log message (~every 120 retries / ~60 s). */ struct ice_ptp { enum ice_ptp_state state; @@ -274,6 +283,8 @@ struct ice_ptp { u32 tx_hwtstamp_flushed; u32 tx_hwtstamp_discarded; u32 late_cached_phc_updates; + bool tspll_locked; + u32 tspll_lock_retries; }; #define __ptp_port_to_ptp(p) \ diff --git a/drivers/net/ethernet/intel/ice/ice_tspll.c b/drivers/net/ethernet/intel/ice/ice_tspll.c index fd4b58eb9bc0..85bacb7c3a79 100644 --- a/drivers/net/ethernet/intel/ice/ice_tspll.c +++ b/drivers/net/ethernet/intel/ice/ice_tspll.c @@ -129,7 +129,7 @@ static bool ice_tspll_check_params(struct ice_hw *hw, * * Return: specified clock source converted to its string name */ -static const char *ice_tspll_clk_src_str(enum ice_clk_src clk_src) +const char *ice_tspll_clk_src_str(enum ice_clk_src clk_src) { switch (clk_src) { case ICE_CLK_SRC_TCXO: @@ -334,8 +334,9 @@ static int ice_tspll_dis_sticky_bits_e82x(struct ice_hw *hw) * Return: * * %0 - success * * %-EINVAL - input parameters are incorrect - * * %-EBUSY - failed to lock TSPLL - * * %other - CGU read/write failure + * * %-EAGAIN - TSPLL configuration succeeded but lock is not acquired yet + * * %-EBUSY - CGU access is busy (for example during reset) + * * %other - other CGU read/write failures */ static int ice_tspll_cfg_e825c(struct ice_hw *hw, enum ice_tspll_freq clk_freq, enum ice_clk_src clk_src) @@ -467,7 +468,7 @@ static int ice_tspll_cfg_e825c(struct ice_hw *hw, enum ice_tspll_freq clk_freq, if (!(val & ICE_CGU_RO_LOCK_TRUE_LOCK)) { dev_warn(ice_hw_to_dev(hw), "CGU PLL failed to lock\n"); - return -EBUSY; + return -EAGAIN; } err = ice_read_cgu_reg(hw, ICE_CGU_R9, &r9); @@ -531,6 +532,62 @@ int ice_tspll_cfg_pps_out_e825c(struct ice_hw *hw, bool enable) return ice_write_cgu_reg(hw, ICE_CGU_R9, val); } +/** + * ice_tspll_lost_lock_e825c - check if TSPLL lost lock + * @hw: Pointer to the HW struct + * @lost_lock: Output flag for reporting lost lock + * + * Get E825 device TSPLL DPLL lock status. + * + * Return: + * * 0 - OK + * * negative - error + */ +int ice_tspll_lost_lock_e825c(struct ice_hw *hw, bool *lost_lock) +{ + u32 val; + int err; + + err = ice_read_cgu_reg(hw, ICE_CGU_RO_LOCK, &val); + if (err) + return err; + + *lost_lock = !FIELD_GET(ICE_CGU_RO_LOCK_TRUE_LOCK, val); + + return 0; +} + +/** + * ice_tspll_restart_e825c - trigger TSPLL restart + * @hw: Pointer to the HW struct + * + * Re-enable TSPLL for E825 device. + * + * Return: + * * 0 - OK + * * negative - error + */ +int ice_tspll_restart_e825c(struct ice_hw *hw) +{ + u32 val; + int err; + + /* Read the initial values of r23 and disable the PLL */ + err = ice_read_cgu_reg(hw, ICE_CGU_R23, &val); + if (err) + return err; + + val &= ~ICE_CGU_R23_R24_TSPLL_ENABLE; + err = ice_write_cgu_reg(hw, ICE_CGU_R23, val); + if (err) + return err; + + /* Wait at least 1 ms before reenabling PLL */ + usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC); + val |= ICE_CGU_R23_R24_TSPLL_ENABLE; + return ice_write_cgu_reg(hw, ICE_CGU_R23, val); +} + /** * ice_tspll_cfg - Configure the Clock Generation Unit TSPLL * @hw: Pointer to the HW struct @@ -577,6 +634,61 @@ static int ice_tspll_dis_sticky_bits(struct ice_hw *hw) } } +/** + * ice_tspll_get_clk_src - get current TSPLL clock source + * @hw: board private hw structure + * @clk_src: pointer to store clk_src value + * + * Get current TSPLL clock source settings. + * + * Return: + * * 0 - OK + * * negative - error + */ +int ice_tspll_get_clk_src(struct ice_hw *hw, enum ice_clk_src *clk_src) +{ + u32 val; + int err; + + err = (hw->mac_type == ICE_MAC_GENERIC_3K_E825) ? + ice_read_cgu_reg(hw, ICE_CGU_R23, &val) : + ice_read_cgu_reg(hw, ICE_CGU_R24, &val); + if (err) + return err; + + *clk_src = (enum ice_clk_src)FIELD_GET(ICE_CGU_R23_R24_TIME_REF_SEL, + val); + + return 0; +} + +/** + * ice_tspll_set_cfg - configure TS PLL with new settings + * @hw: board private hw structure + * @clk_freq: clock frequency to program + * @clk_src: clock source to select (TIME_REF, or TCXO) + * + * Configure CGU with new clock source and clock frequency settings. + * + * Return: + * * 0 - OK + * * negative - error + */ +int ice_tspll_set_cfg(struct ice_hw *hw, enum ice_tspll_freq clk_freq, + enum ice_clk_src clk_src) +{ + int ret; + + if (!ice_tspll_check_params(hw, clk_freq, clk_src)) + return -EINVAL; + + ret = ice_tspll_dis_sticky_bits(hw); + if (ret) + return ret; + + return ice_tspll_cfg(hw, clk_freq, clk_src); +} + /** * ice_tspll_init - Initialize TSPLL with settings from firmware * @hw: Pointer to the HW structure diff --git a/drivers/net/ethernet/intel/ice/ice_tspll.h b/drivers/net/ethernet/intel/ice/ice_tspll.h index d650867004d1..832c049e686c 100644 --- a/drivers/net/ethernet/intel/ice/ice_tspll.h +++ b/drivers/net/ethernet/intel/ice/ice_tspll.h @@ -32,6 +32,11 @@ struct ice_tspll_params_e82x { #define ICE_TSPLL_FBDIV_INTGR_E825 256 int ice_tspll_cfg_pps_out_e825c(struct ice_hw *hw, bool enable); +int ice_tspll_lost_lock_e825c(struct ice_hw *hw, bool *lost_lock); +int ice_tspll_restart_e825c(struct ice_hw *hw); +int ice_tspll_get_clk_src(struct ice_hw *hw, enum ice_clk_src *clk_src); +int ice_tspll_set_cfg(struct ice_hw *hw, enum ice_tspll_freq clk_freq, + enum ice_clk_src clk_src); int ice_tspll_init(struct ice_hw *hw); int ice_tspll_bypass_mux_active_e825c(struct ice_hw *hw, u8 port, bool *active, enum ice_synce_clk output); @@ -39,4 +44,5 @@ int ice_tspll_cfg_bypass_mux_e825c(struct ice_hw *hw, bool ena, u32 port_num, enum ice_synce_clk output); int ice_tspll_cfg_synce_ethdiv_e825c(struct ice_hw *hw, enum ice_synce_clk output); +const char *ice_tspll_clk_src_str(enum ice_clk_src clk_src); #endif /* _ICE_TSPLL_H_ */