* [PATCH iwl-next 0/3] ice: expose TSPLL state on E825 through dpll subsystem
@ 2026-07-28 9:13 Grzegorz Nitka
2026-07-28 9:13 ` [PATCH iwl-next 1/3] ice: monitor TSPLL lock from PTP periodic worker Grzegorz Nitka
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Grzegorz Nitka @ 2026-07-28 9:13 UTC (permalink / raw)
To: intel-wired-lan
Cc: netdev, linux-kernel, richardcochran, andrew+netdev,
przemyslaw.kitszel, anthony.l.nguyen, arkadiusz.kubalewski,
pabeni, kuba, davem, edumazet, Grzegorz Nitka
On E825 devices that own the source timer, the TSPLL can lose lock when
the TCXO or TIME_REF signal is disrupted. This series adds monitoring
and recovery for that condition, then surfaces the lock status and clock
source selection through the dpll subsystem. It also fixes the clock_id
generation for E825 generic DPLL devices so userspace can unambiguously
map each DPLL device to its owning interface.
Patch 1 adds TSPLL lock monitoring to ice_ptp_periodic_work(). Placing it
there ensures recovery runs regardless of whether DPLL init succeeded or
CONFIG_DPLL is enabled. Lock state is cached in pf->ptp.tspll_locked via
WRITE_ONCE()/READ_ONCE() for consumption by the DPLL worker.
Patch 2 registers the TSPLL as a DPLL_TYPE_GENERIC device for E825 owner
PFs, with a fwnode-backed "time_ref" input pin. The pin state_on_dpll_set
callback switches the clock source between TIME_REF and TCXO. Lock status
is read from pf->ptp.tspll_locked; UNLOCKED is reported unconditionally
when the clock source is TCXO to reflect the free-running oscillator state.
Patch 3 changes the clock_id generation for E825 TX-CLK and TSPLL DPLLs.
Previously they used the board-level PCIe DSN, which is identical for
all interfaces sharing the same NAC/quad, so userspace could not tell
them apart. The new scheme derives the clock_id from the permanent port
MAC (with a dedicated tag bit distinguishing TSPLL from TX-CLK), while
other DPLL objects (EEC/PPS and non-E825 paths) keep the board-level
DSN-derived clock_id.
Grzegorz Nitka (3):
ice: monitor TSPLL lock from PTP periodic worker
ice: add TSPLL DPLL device and TIME_REF pin for E825
ice: use per-interface clock_id for E825 generic DPLLs
drivers/net/ethernet/intel/ice/ice_dpll.c | 479 +++++++++++++++++++--
drivers/net/ethernet/intel/ice/ice_dpll.h | 4 +
drivers/net/ethernet/intel/ice/ice_ptp.c | 80 ++++
drivers/net/ethernet/intel/ice/ice_ptp.h | 11 +
drivers/net/ethernet/intel/ice/ice_tspll.c | 122 +++++-
drivers/net/ethernet/intel/ice/ice_tspll.h | 6 +
6 files changed, 657 insertions(+), 45 deletions(-)
base-commit: d621cef13189b54ee67019d170a6d8ec727785aa
--
2.39.3
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH iwl-next 1/3] ice: monitor TSPLL lock from PTP periodic worker
2026-07-28 9:13 [PATCH iwl-next 0/3] ice: expose TSPLL state on E825 through dpll subsystem Grzegorz Nitka
@ 2026-07-28 9:13 ` Grzegorz Nitka
2026-07-28 9:13 ` [PATCH iwl-next 2/3] ice: add TSPLL DPLL device and TIME_REF pin for E825 Grzegorz Nitka
2026-07-28 9:13 ` [PATCH iwl-next 3/3] ice: use per-interface clock_id for E825 generic DPLLs Grzegorz Nitka
2 siblings, 0 replies; 4+ messages in thread
From: Grzegorz Nitka @ 2026-07-28 9:13 UTC (permalink / raw)
To: intel-wired-lan
Cc: netdev, linux-kernel, richardcochran, andrew+netdev,
przemyslaw.kitszel, anthony.l.nguyen, arkadiusz.kubalewski,
pabeni, kuba, davem, edumazet, Grzegorz Nitka, Przemyslaw Korba
On E825 devices that own the source timer, the TSPLL can lose lock when
the TCXO or TIME_REF signal is disrupted. Recovery requires re-enabling
the TSPLL via CGU register writes; without it, the PHC keeps running on
a degraded reference indefinitely.
The DPLL periodic worker (ice_dpll_periodic_work()) would be a natural
home for this monitoring, but placing it there has two problems:
1. ice_dpll_init_e825() sets ICE_FLAG_DPLL only after all initialization
steps succeed. If any earlier step fails, the driver would run
without any TSPLL recovery mechanism.
2. When CONFIG_DPLL=n, the DPLL worker is compiled out and TSPLL
recovery would be silently absent.
Add the monitor to ice_ptp_periodic_work() instead, which always runs on
E825 owner PFs regardless of DPLL init state or config. Introduce two
small helpers, ice_tspll_lost_lock_e825c() and ice_tspll_restart_e825c(),
which encapsulate the CGU register reads/writes required to observe and
recover the TSPLL.
Cache the observed lock state in pf->ptp.tspll_locked using
WRITE_ONCE()/READ_ONCE() so a follow-up change can consume it from the
DPLL periodic worker (for user-space notification via
dpll_device_change_ntf()) and drop the redundant poll+recovery from
that path. Precise synchronization is not required: both workers converge
on the same value within one poll period.
Reviewed-by: Przemyslaw Korba <przemyslaw.korba@intel.com>
Signed-off-by: Grzegorz Nitka <grzegorz.nitka@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp.c | 70 +++++++++++++++++
drivers/net/ethernet/intel/ice/ice_ptp.h | 11 +++
drivers/net/ethernet/intel/ice/ice_tspll.c | 88 +++++++++++++++++++++-
drivers/net/ethernet/intel/ice/ice_tspll.h | 4 +
4 files changed, 172 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 4df3d83b5066..a997be5f7d8f 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -5,8 +5,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",
@@ -2875,6 +2878,67 @@ 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;
+
+ err = ice_tspll_lost_lock_e825c(&pf->hw, &lock_lost);
+ if (err) {
+ 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) {
+ enum ice_clk_src clk_src;
+ const char *src_str = "unknown";
+
+ 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;
+ }
+}
+
static void ice_ptp_periodic_work(struct kthread_work *work)
{
struct ice_ptp *ptp = container_of(work, struct ice_ptp, work.work);
@@ -2884,6 +2948,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);
@@ -3001,6 +3067,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)) {
@@ -3364,6 +3433,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..be8e2da21dd6 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:
@@ -531,6 +531,64 @@ 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;
+ err = ice_write_cgu_reg(hw, ICE_CGU_R23, val);
+
+ return err;
+}
+
/**
* ice_tspll_cfg - Configure the Clock Generation Unit TSPLL
* @hw: Pointer to the HW struct
@@ -577,6 +635,34 @@ 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_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..05917ae51ded 100644
--- a/drivers/net/ethernet/intel/ice/ice_tspll.h
+++ b/drivers/net/ethernet/intel/ice/ice_tspll.h
@@ -32,6 +32,9 @@ 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_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 +42,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_ */
--
2.39.3
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH iwl-next 2/3] ice: add TSPLL DPLL device and TIME_REF pin for E825
2026-07-28 9:13 [PATCH iwl-next 0/3] ice: expose TSPLL state on E825 through dpll subsystem Grzegorz Nitka
2026-07-28 9:13 ` [PATCH iwl-next 1/3] ice: monitor TSPLL lock from PTP periodic worker Grzegorz Nitka
@ 2026-07-28 9:13 ` Grzegorz Nitka
2026-07-28 9:13 ` [PATCH iwl-next 3/3] ice: use per-interface clock_id for E825 generic DPLLs Grzegorz Nitka
2 siblings, 0 replies; 4+ messages in thread
From: Grzegorz Nitka @ 2026-07-28 9:13 UTC (permalink / raw)
To: intel-wired-lan
Cc: netdev, linux-kernel, richardcochran, andrew+netdev,
przemyslaw.kitszel, anthony.l.nguyen, arkadiusz.kubalewski,
pabeni, kuba, davem, edumazet, Grzegorz Nitka, Przemyslaw Korba
This extends the E825 advanced sync-timing support introduced by the
tx-clk series, which added the TXC DPLL device for TX reference clock
control. The TSPLL, the source timer PLL, is now also exposed through
the dpll subsystem so that its lock status and clock source selection
are visible and controllable from userspace.
On E825 devices the TSPLL is the source timer PLL, distinct from the
EEC and PPS DPLLs used on E810. Register it as a DPLL_TYPE_GENERIC
device for owner PFs.
Add struct ice_dplls::tspll_in, a fwnode-backed input pin named
"time_ref". The state_on_dpll_get callback queries ICE_CGU_R23 via
ice_tspll_get_clk_src() and returns CONNECTED when TIME_REF is
selected as clock source, DISCONNECTED otherwise. The state_on_dpll_set
callback switches the source between TIME_REF and TCXO via the new
ice_tspll_set_cfg() helper. Registration is deferred via the dpll
notifier path if the pin is not yet visible in the subsystem at probe
time.
Initialize TSPLL DPLL state from direct clock-source/lock reads so the
first published state reflects hardware and prev_dpll_state matches.
During periodic polling, the DPLL worker consumes
READ_ONCE(pf->ptp.tspll_locked), maintained and recovered by the PTP
periodic worker. When the TSPLL clock source is TCXO (TIME_REF pin not
selected), UNLOCKED is reported unconditionally to reflect the
free-running state of the oscillator regardless of the raw lock bit.
To avoid stale lock-status reads after synchronous source changes, the
set callback now refreshes tspll.dpll_state immediately and emits a DPLL
change notification when the cached state changed.
If a TSPLL reconfiguration is applied but the PLL has not yet
re-acquired lock, treat the internal -EAGAIN result as success so the
PTP periodic worker can complete recovery, while real -EBUSY failures
from reset/SBQ paths still propagate to userspace.
Extend ice_dpll_deinit_txclk_pins() with a "flush" parameter so the
E825 init error path for the TSPLL fwnode pin can tear down TXCLK
pins without flushing pf->dplls.wq. If the flush ran here, notifier
work items queued during earlier init steps would be blocked on
pf->dplls.dpll_init, which is only completed at the unregister_pins
label reached after this teardown. destroy_workqueue() at that label
drains the queued items safely. Existing full-teardown callers pass
flush=true and keep current behavior.
Reviewed-by: Przemyslaw Korba <przemyslaw.korba@intel.com>
Signed-off-by: Grzegorz Nitka <grzegorz.nitka@intel.com>
---
drivers/net/ethernet/intel/ice/ice_dpll.c | 398 +++++++++++++++++++--
drivers/net/ethernet/intel/ice/ice_dpll.h | 4 +
drivers/net/ethernet/intel/ice/ice_ptp.c | 10 +
drivers/net/ethernet/intel/ice/ice_tspll.c | 34 +-
drivers/net/ethernet/intel/ice/ice_tspll.h | 2 +
5 files changed, 409 insertions(+), 39 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.c b/drivers/net/ethernet/intel/ice/ice_dpll.c
index 1ca137f67dd4..fd5636394198 100644
--- a/drivers/net/ethernet/intel/ice/ice_dpll.c
+++ b/drivers/net/ethernet/intel/ice/ice_dpll.c
@@ -24,6 +24,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))
@@ -86,6 +88,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),
@@ -2827,6 +2830,126 @@ static const struct dpll_pin_ops ice_dpll_txclk_ops = {
.direction_get = ice_dpll_input_direction,
};
+static enum dpll_lock_status ice_dpll_tspll_lock_status_get(struct ice_pf *pf,
+ bool use_cached);
+
+/**
+ * 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;
+
+ /* Reconfiguration can transiently drop lock; clear cached lock
+ * before PTP monitor refresh to avoid spurious LOCKED reports.
+ */
+ WRITE_ONCE(pf->ptp.tspll_locked, false);
+
+ new_state = ice_dpll_tspll_lock_status_get(pf, false);
+ 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,
@@ -2928,6 +3051,37 @@ static u64 ice_generate_clock_id(struct ice_pf *pf)
return pci_get_dsn(pf->pdev);
}
+/**
+ * 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.
+ */
+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_pin_ntf - notify pin change including any SW pin wrappers
* @dplls: pointer to dplls struct
@@ -3160,12 +3314,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)
{
@@ -3173,38 +3359,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 */
@@ -3567,6 +3758,7 @@ static void ice_dpll_pin_notify_work(struct work_struct *work)
struct ice_dpll_pin_work,
work);
struct ice_dpll_pin *pin, *parent = w->pin;
+ bool is_tspll_time_ref = false;
bool is_tx_synce_parent = false;
struct ice_pf *pf = parent->pf;
bool is_rclk_parent = false;
@@ -3588,7 +3780,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) {
@@ -3629,6 +3825,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:
@@ -3647,6 +3855,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 */
@@ -3818,12 +4031,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));
@@ -3838,6 +4051,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
@@ -3870,7 +4102,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) {
@@ -4066,6 +4301,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
@@ -4093,8 +4367,25 @@ 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) {
@@ -4288,6 +4579,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;
}
/**
@@ -4778,6 +5070,7 @@ static int ice_dpll_init_info_e825c(struct ice_pf *pf)
{
struct ice_dplls *d = &pf->dplls;
struct ice_dpll *dt = &d->txc;
+ struct ice_dpll *tp = &d->tspll;
int ret = 0;
int i;
@@ -4785,7 +5078,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)
@@ -5064,7 +5361,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)
@@ -5078,6 +5375,8 @@ 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);
@@ -5160,9 +5459,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;
@@ -5173,6 +5493,12 @@ 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:
diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.h b/drivers/net/ethernet/intel/ice/ice_dpll.h
index c59d746a8567..6bdb821bfb3a 100644
--- a/drivers/net/ethernet/intel/ice/ice_dpll.h
+++ b/drivers/net/ethernet/intel/ice/ice_dpll.h
@@ -135,10 +135,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
@@ -179,12 +181,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_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index a997be5f7d8f..cbd9f6455c05 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -2898,6 +2898,7 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
static void ice_ptp_tspll_monitor(struct ice_pf *pf)
{
bool lock_lost;
+ bool dpll_ready;
int err;
if (pf->hw.mac_type != ICE_MAC_GENERIC_3K_E825 ||
@@ -2917,7 +2918,16 @@ static void ice_ptp_tspll_monitor(struct ice_pf *pf)
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");
+ /* Serialize R23 restart against TSPLL userspace reconfig.
+ * Guard with ICE_FLAG_DPLL because init failure may already
+ * have destroyed pf->dplls.lock on the error path.
+ */
+ dpll_ready = test_bit(ICE_FLAG_DPLL, pf->flags);
+ if (dpll_ready)
+ mutex_lock(&pf->dplls.lock);
err = ice_tspll_restart_e825c(&pf->hw);
+ if (dpll_ready)
+ mutex_unlock(&pf->dplls.lock);
if (err)
dev_err_ratelimited(ice_pf_to_dev(pf),
"Failed to restart TimeSync PLL (err: %d).\n",
diff --git a/drivers/net/ethernet/intel/ice/ice_tspll.c b/drivers/net/ethernet/intel/ice/ice_tspll.c
index be8e2da21dd6..378eefe0f9af 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);
@@ -663,6 +664,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);
--
2.39.3
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH iwl-next 3/3] ice: use per-interface clock_id for E825 generic DPLLs
2026-07-28 9:13 [PATCH iwl-next 0/3] ice: expose TSPLL state on E825 through dpll subsystem Grzegorz Nitka
2026-07-28 9:13 ` [PATCH iwl-next 1/3] ice: monitor TSPLL lock from PTP periodic worker Grzegorz Nitka
2026-07-28 9:13 ` [PATCH iwl-next 2/3] ice: add TSPLL DPLL device and TIME_REF pin for E825 Grzegorz Nitka
@ 2026-07-28 9:13 ` Grzegorz Nitka
2 siblings, 0 replies; 4+ messages in thread
From: Grzegorz Nitka @ 2026-07-28 9:13 UTC (permalink / raw)
To: intel-wired-lan
Cc: netdev, linux-kernel, richardcochran, andrew+netdev,
przemyslaw.kitszel, anthony.l.nguyen, arkadiusz.kubalewski,
pabeni, kuba, davem, edumazet, Grzegorz Nitka, Przemyslaw Korba
On E825, the TX-CLK and TSPLL DPLL devices are registered as
DPLL_TYPE_GENERIC. Their clock_id was derived from the board-level
PCIe DSN, which is identical for all interfaces sharing the same
NAC/quad. As a result, userspace (e.g. 'dpll device show') reports
several DPLL devices with the same clock_id and no board or signal
label, making it impossible to unambiguously map a DPLL device to
the interface it belongs to.
Since these DPLLs are per-interface, use the permanent port MAC as
the clock_id basis for E825 generic DPLLs:
* TX-CLK uses the plain MAC-derived value.
* TSPLL on the source-timer owner PF uses the same MAC-derived
value with a dedicated tag bit, so it stays distinct from
TX-CLK while remaining stable per interface.
Other DPLL objects (EEC/PPS and non-E825 paths) keep the board
DSN-derived clock_id. When the permanent MAC is not yet valid, fall
back to the existing board-level clock_id to preserve init behavior.
Reviewed-by: Przemyslaw Korba <przemyslaw.korba@intel.com>
Signed-off-by: Grzegorz Nitka <grzegorz.nitka@intel.com>
---
drivers/net/ethernet/intel/ice/ice_dpll.c | 81 +++++++++++++++++++++--
1 file changed, 76 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.c b/drivers/net/ethernet/intel/ice/ice_dpll.c
index fd5636394198..73aadf068dd5 100644
--- a/drivers/net/ethernet/intel/ice/ice_dpll.c
+++ b/drivers/net/ethernet/intel/ice/ice_dpll.c
@@ -3051,6 +3051,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_tspll_lock_status_get - derive TSPLL state for dpll subsystem
* @pf: board private structure
@@ -3887,10 +3949,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);
@@ -4247,10 +4311,17 @@ 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,
@@ -4535,7 +4606,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,
--
2.39.3
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-28 9:18 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-28 9:13 [PATCH iwl-next 0/3] ice: expose TSPLL state on E825 through dpll subsystem Grzegorz Nitka
2026-07-28 9:13 ` [PATCH iwl-next 1/3] ice: monitor TSPLL lock from PTP periodic worker Grzegorz Nitka
2026-07-28 9:13 ` [PATCH iwl-next 2/3] ice: add TSPLL DPLL device and TIME_REF pin for E825 Grzegorz Nitka
2026-07-28 9:13 ` [PATCH iwl-next 3/3] ice: use per-interface clock_id for E825 generic DPLLs Grzegorz Nitka
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox