diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.c b/drivers/net/ethernet/intel/ice/ice_dpll.c index 85a74cd6ea1f..00e3f7de7810 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, @@ -3134,12 +3291,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 +3336,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 +3730,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 +3751,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 +3796,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 +3826,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 */ @@ -3786,12 +4002,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 +4022,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 +4061,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) { @@ -4022,6 +4260,45 @@ err_release_ext_eref: 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 +4326,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) { @@ -4237,6 +4530,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 +4967,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 +4976,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 +5123,8 @@ deinit_info: * 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 +5148,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 +5161,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 +5174,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 +5191,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 +5202,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 +5236,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 +5255,12 @@ err_exit: * 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 +5291,6 @@ deinit_eec: 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 d88835482d3a..2f372dd37b53 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 4b1040d09f70..e54a98a0bdd0 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c @@ -2878,8 +2878,19 @@ static void ice_ptp_tspll_monitor(struct ice_pf *pf) !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); @@ -2911,6 +2922,7 @@ static void ice_ptp_tspll_monitor(struct ice_pf *pf) 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) diff --git a/drivers/net/ethernet/intel/ice/ice_tspll.c b/drivers/net/ethernet/intel/ice/ice_tspll.c index 78752dc1e762..85bacb7c3a79 100644 --- a/drivers/net/ethernet/intel/ice/ice_tspll.c +++ b/drivers/net/ethernet/intel/ice/ice_tspll.c @@ -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); @@ -661,6 +662,33 @@ int ice_tspll_get_clk_src(struct ice_hw *hw, enum ice_clk_src *clk_src) 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 05917ae51ded..832c049e686c 100644 --- a/drivers/net/ethernet/intel/ice/ice_tspll.h +++ b/drivers/net/ethernet/intel/ice/ice_tspll.h @@ -35,6 +35,8 @@ 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);