> -----Original Message-----
> From: Loktionov, Aleksandr <[email protected]>
> Sent: Thursday, May 28, 2026 2:22 PM
> To: Nitka, Grzegorz <[email protected]>; [email protected]
> Cc: Vecera, Ivan <[email protected]>; [email protected];
> [email protected]; [email protected]; [email protected]; Kitszel,
> Przemyslaw <[email protected]>; [email protected];
> [email protected]; [email protected]; Kubalewski,
> Arkadiusz <[email protected]>; [email protected];
> [email protected]; [email protected];
> [email protected]; Nguyen, Anthony L
> <[email protected]>; [email protected]; [email protected]
> Subject: RE: [Intel-wired-lan] [PATCH v11 net-next 8/8] ice: implement E825
> TX ref clock control and TXC hardware sync status
> 
> 
> 
> > -----Original Message-----
> > From: Intel-wired-lan <[email protected]> On Behalf
> > Of Grzegorz Nitka
> > Sent: Tuesday, May 26, 2026 11:34 AM
> > To: [email protected]
> > Cc: Vecera, Ivan <[email protected]>; [email protected];
> > [email protected]; [email protected]; [email protected]; Kitszel,
> > Przemyslaw <[email protected]>; [email protected];
> > [email protected]; [email protected]; Kubalewski,
> > Arkadiusz <[email protected]>; [email protected];
> > [email protected]; [email protected];
> > [email protected]; Nguyen, Anthony L
> > <[email protected]>; [email protected];
> [email protected]
> > Subject: [Intel-wired-lan] [PATCH v11 net-next 8/8] ice: implement
> > E825 TX ref clock control and TXC hardware sync status
> >
> > Build on the previously introduced TXC DPLL framework and implement
> > full TX reference clock control and hardware-backed synchronization
> > status reporting for E825 devices.
> >
> > E825 firmware may accept or override TX reference clock requests based
> > on device-wide routing constraints and link conditions. Because the
> > final selection becomes visible only after a link-up event, the driver
> > splits the observation into two complementary signals:
> >
> >   - TXCLK pin state reflects the requested TX reference clock
> >     (pf->ptp.port.tx_clk_req). After a link-up, the value is
> > reconciled
> >     against the SERDES reference selector by
> >     ice_txclk_update_and_notify(); if firmware or auto-negotiation
> >     selected a different clock, tx_clk_req is overwritten so that pin
> >     state converges to the actual hardware selection.
> >
> >   - TXC DPLL lock status reflects hardware synchronization:
> >       * LOCKED   when an external TX reference is in use
> >       * UNLOCKED when falling back to ENET/TXCO, or when a requested
> >         external reference has not (yet) been accepted by hardware.
> >
> > Userspace observing only pin state therefore sees user intent, while
> > lock status is the authoritative indicator of whether the requested
> > clock is actually selected and synchronizing. This matches the DPLL
> > subsystem model where pin state describes topology and device lock
> > status describes signal quality.
> >
> > TX reference selection topology:
> >   - External references (SYNCE, EREF0) are represented as TXCLK pins
> >   - The internal ENET/TXCO clock has no pin representation; when
> >     selected, all TXCLK pins are reported DISCONNECTED
> >
> > With this change, TX reference clocks on E825 devices can be reliably
> > selected, observed via standard DPLL interfaces, and monitored for
> > effective synchronization through TXC DPLL lock status.
> >
> > Reviewed-by: Arkadiusz Kubalewski <[email protected]>
> > Signed-off-by: Grzegorz Nitka <[email protected]>
> > ---
> >  drivers/net/ethernet/intel/ice/Makefile     |   2 +-
> >  drivers/net/ethernet/intel/ice/ice.h        |  12 +
> >  drivers/net/ethernet/intel/ice/ice_dpll.c   | 172 ++++++++++-
> >  drivers/net/ethernet/intel/ice/ice_dpll.h   |  30 +-
> >  drivers/net/ethernet/intel/ice/ice_ptp.c    |  37 ++-
> >  drivers/net/ethernet/intel/ice/ice_ptp.h    |   6 +
> >  drivers/net/ethernet/intel/ice/ice_ptp_hw.c |  37 +++
> > drivers/net/ethernet/intel/ice/ice_ptp_hw.h |  10 +
> > drivers/net/ethernet/intel/ice/ice_txclk.c  | 320 ++++++++++++++++++++
> > drivers/net/ethernet/intel/ice/ice_txclk.h  |  40 +++
> >  10 files changed, 646 insertions(+), 20 deletions(-)  create mode
> > 100644 drivers/net/ethernet/intel/ice/ice_txclk.c
> >  create mode 100644 drivers/net/ethernet/intel/ice/ice_txclk.h
> >
> > diff --git a/drivers/net/ethernet/intel/ice/Makefile
> > b/drivers/net/ethernet/intel/ice/Makefile
> > index 38db476ab2ec..95fd0c49800f 100644
> > --- a/drivers/net/ethernet/intel/ice/Makefile
> > +++ b/drivers/net/ethernet/intel/ice/Makefile
> > @@ -54,7 +54,7 @@ ice-$(CONFIG_PCI_IOV) +=  \
> >     ice_vf_mbx.o            \
> >     ice_vf_vsi_vlan_ops.o   \
> >     ice_vf_lib.o
> > -ice-$(CONFIG_PTP_1588_CLOCK) += ice_ptp.o ice_ptp_hw.o ice_dpll.o
> > ice_tspll.o ice_cpi.o
> > +ice-$(CONFIG_PTP_1588_CLOCK) += ice_ptp.o ice_ptp_hw.o ice_dpll.o
> > +ice_tspll.o ice_cpi.o ice_txclk.o
> >  ice-$(CONFIG_DCB) += ice_dcb.o ice_dcb_nl.o ice_dcb_lib.o
> >  ice-$(CONFIG_RFS_ACCEL) += ice_arfs.o
> >  ice-$(CONFIG_XDP_SOCKETS) += ice_xsk.o
> > diff --git a/drivers/net/ethernet/intel/ice/ice.h
> > b/drivers/net/ethernet/intel/ice/ice.h
> > index 725b130dd3a2..f72bb1aa4067 100644
> > --- a/drivers/net/ethernet/intel/ice/ice.h
> > +++ b/drivers/net/ethernet/intel/ice/ice.h
> > @@ -1155,4 +1155,16 @@ static inline struct ice_hw
> > *ice_get_primary_hw(struct ice_pf *pf)
> >     else
> >             return &pf->adapter->ctrl_pf->hw;
> >  }
> > +
> > +/**
> > + * ice_get_ctrl_pf - Get pointer to Control PF of the adapter
> > + * @pf: pointer to the current PF structure
> > + *
> > + * Return: A pointer to ice_pf structure which is Control PF,
> > + * NULL if it's not initialized yet.
> > + */
> > +static inline struct ice_pf *ice_get_ctrl_pf(struct ice_pf *pf) {
> > +   return !pf->adapter ? NULL : pf->adapter->ctrl_pf; }
> >  #endif /* _ICE_H_ */
> > diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.c
> > b/drivers/net/ethernet/intel/ice/ice_dpll.c
> > index 1b193e0c35ab..fcc70a7add3b 100644
> > --- a/drivers/net/ethernet/intel/ice/ice_dpll.c
> > +++ b/drivers/net/ethernet/intel/ice/ice_dpll.c
> > @@ -4,6 +4,7 @@
> >  #include "ice.h"
> >  #include "ice_lib.h"
> >  #include "ice_trace.h"
> > +#include "ice_txclk.h"
> >  #include <linux/dpll.h>
> >  #include <linux/property.h>
> >
> > @@ -19,8 +20,6 @@
> >  #define ICE_DPLL_SW_PIN_INPUT_BASE_QSFP            6
> >  #define ICE_DPLL_SW_PIN_OUTPUT_BASE                0
> >
> > -#define E825_EXT_EREF_PIN_IDX                      0
> > -#define E825_EXT_SYNCE_PIN_IDX                     1
> >  #define E825_RCLK_PARENT_0_PIN_IDX         0
> >  #define E825_RCLK_PARENT_1_PIN_IDX         1
> >
> > @@ -2615,6 +2614,82 @@ ice_dpll_rclk_state_on_pin_get(const struct
> > dpll_pin *pin, void *pin_priv,
> >     return ret;
> >  }
> >
> > +/**
> > + * ice_dpll_txclk_work - apply a pending TX reference clock change
> > + * @work: work_struct embedded in struct ice_dplls
> > + *
> > + * This worker executes an outstanding TX reference clock switch
> > +request
> > + * that was previously queued via the DPLL TXCLK pin set callback.
> > + *
> > + * The worker performs only the operational part of the switch,
> > issuing
> > + * the necessary firmware commands to request a new TX reference
> > clock
> > + * selection (e.g. triggering an AN restart). It does not verify
> > +whether
> > + * the requested clock was ultimately accepted by the hardware.
> > + *
> > + * Hardware verification, software state reconciliation, pin state
> > + * notification, and TXC DPLL lock-status updates are performed
> > later,
> > + * after link-up, by ice_txclk_update_and_notify().
> > + *
> > + * Context:
> > + *   - Runs in process context on pf->dplls.wq and may sleep.
> > + *   - Serializes access to shared TXCLK state using pf->dplls.lock.
> > + */
> > +static void ice_dpll_txclk_work(struct work_struct *work) {
> > +   struct ice_dplls *dplls =
> > +           container_of(work, struct ice_dplls, txclk_work);
> > +   struct ice_pf *pf = container_of(dplls, struct ice_pf, dplls);
> > +   struct dpll_pin *old_pin = NULL;
> > +   struct dpll_pin *new_pin = NULL;
> > +   enum ice_e825c_ref_clk clk;
> > +   bool do_switch;
> > +   int err;
> > +
> > +   mutex_lock(&pf->dplls.lock);
> > +   do_switch = pf->dplls.txclk_switch_requested;
> > +   clk = pf->ptp.port.tx_clk_req;
> > +   mutex_unlock(&pf->dplls.lock);
> > +
> > +   if (!do_switch)
> > +           return;
> > +
> > +   err = ice_txclk_set_clk(pf, clk);
> > +
> > +   mutex_lock(&pf->dplls.lock);
> > +   /* Only clear the request flag if no newer request arrived
> > while
> > +    * the lock was dropped. Otherwise leave it set so the re-
> > queued
> > +    * worker run picks up the updated tx_clk_req value.
> > +    */
> > +   if (pf->ptp.port.tx_clk_req == clk)
> > +           pf->dplls.txclk_switch_requested = false;
> > +   if (err) {
> > +           /* Roll back the requested clock to match the current
> > hardware
> > +            * state so that ice_txclk_update_and_notify() does not
> > +            * misinterpret a future link-up as a failed switch.
> > Only roll
> > +            * back if no newer request arrived in the meantime;
> > otherwise
> > +            * the re-queued worker run will apply the updated
> > value.
> > +            */
> > +           dev_err(ice_pf_to_dev(pf),
> > +                   "TX clock switch to %u failed, err=%d;
> > reverting\n",
> > +                   clk, err);
> > +           if (pf->ptp.port.tx_clk_req == clk) {
> > +                   /* Capture pins for post-unlock notification so
> > that
> > +                    * userspace observes the requested pin flipping
> > back
> > +                    * to DISCONNECTED and the effective pin to
> > CONNECTED.
> > +                    */
> > +                   new_pin = ice_txclk_get_pin(pf, clk);
> > +                   old_pin = ice_txclk_get_pin(pf, pf-
> > >ptp.port.tx_clk);
> > +                   pf->ptp.port.tx_clk_req = pf->ptp.port.tx_clk;
> > +           }
> > +   }
> > +   mutex_unlock(&pf->dplls.lock);
> > +
> > +   if (old_pin)
> > +           dpll_pin_change_ntf(old_pin);
> > +   if (new_pin)
> > +           dpll_pin_change_ntf(new_pin);
> > +}
> > +
> >  /**
> >   * ice_dpll_txclk_state_on_dpll_set - set a state on TX clk pin
> >   * @pin: pointer to a pin
> > @@ -2626,7 +2701,9 @@ ice_dpll_rclk_state_on_pin_get(const struct
> > dpll_pin *pin, void *pin_priv,
> >   *
> >   * Dpll subsystem callback, set a state of a Tx reference clock pin
> >   *
> > + * Context: Acquires and releases pf->dplls.lock
> >   * Return:
> > + * * 0 - success
> >   * * negative - failure
> >   */
> >  static int
> > @@ -2635,11 +2712,36 @@ ice_dpll_txclk_state_on_dpll_set(const struct
> > dpll_pin *pin, void *pin_priv,
> >                              void *dpll_priv, enum dpll_pin_state
> > state,
> >                              struct netlink_ext_ack *extack)
> >  {
> > -   /*
> > -    * TODO: set HW accordingly to selected TX reference clock.
> > -    * To be added in the follow up patches.
> > -    */
> > -   return -EOPNOTSUPP;
> > +   struct ice_dpll_pin *p = pin_priv;
> > +   struct ice_pf *pf = p->pf;
> > +   enum ice_e825c_ref_clk new_clk;
> > +
> > +   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 TX reference
> > clock");
> > +           return -EINVAL;
> > +   }
> > +
> > +   mutex_lock(&pf->dplls.lock);
> > +   if (state == DPLL_PIN_STATE_DISCONNECTED &&
> > +       p->tx_ref_src != pf->ptp.port.tx_clk_req)
> > +           goto unlock;
> > +
> > +   new_clk = (state == DPLL_PIN_STATE_DISCONNECTED) ?
> > ICE_REF_CLK_ENET :
> > +                   p->tx_ref_src;
> > +   if (new_clk == pf->ptp.port.tx_clk_req)
> > +           goto unlock;
> > +
> > +   pf->ptp.port.tx_clk_req = new_clk;
> > +   pf->dplls.txclk_switch_requested = true;
> > +   queue_work(pf->dplls.wq, &pf->dplls.txclk_work);
> > +unlock:
> > +   mutex_unlock(&pf->dplls.lock);
> > +   return 0;
> >  }
> >
> >  /**
> > @@ -2651,10 +2753,21 @@ ice_dpll_txclk_state_on_dpll_set(const struct
> > dpll_pin *pin, void *pin_priv,
> >   * @state: on success holds pin state on parent pin
> >   * @extack: error reporting
> >   *
> > - * dpll subsystem callback, get a state of a TX clock reference pin.
> > + * TXCLK DPLL pin state is derived and not stored explicitly.
> > + *
> > + * Only external TX reference clocks (SYNCE, EREF0) are modeled
> > + * as DPLL pins. The internal ENET (TXCO) clock has no pin and,
> > + * when selected, all TXCLK pins are reported DISCONNECTED.
> > + *
> > + * During a pending TXCLK switch, the requested pin may be
> > + * reported as CONNECTED before hardware verification.
> > + * Hardware acceptance and synchronization are reported
> > + * exclusively via TXC DPLL lock-status.
> >   *
> > + * Context: Acquires and releases pf->dplls.lock
> >   * Return:
> >   * * 0 - success
> > + * * negative - failure
> >   */
> >  static int
> >  ice_dpll_txclk_state_on_dpll_get(const struct dpll_pin *pin, void
> > *pin_priv, @@ -2663,11 +2776,18 @@
> > ice_dpll_txclk_state_on_dpll_get(const struct dpll_pin *pin, void
> > *pin_priv,
> >                              enum dpll_pin_state *state,
> >                              struct netlink_ext_ack *extack)
> >  {
> > -   /*
> > -    * TODO: query HW status to determine if the TX reference is
> > selected.
> > -    * To be added in the follow up patches.
> > -    */
> > -   *state = DPLL_PIN_STATE_DISCONNECTED;
> > +   struct ice_dpll_pin *p = pin_priv;
> > +   struct ice_pf *pf = p->pf;
> > +
> > +   if (ice_dpll_is_reset(pf, extack))
> > +           return -EBUSY;
> > +
> > +   mutex_lock(&pf->dplls.lock);
> > +   if (pf->ptp.port.tx_clk_req == p->tx_ref_src)
> > +           *state = DPLL_PIN_STATE_CONNECTED;
> > +   else
> > +           *state = DPLL_PIN_STATE_DISCONNECTED;
> > +   mutex_unlock(&pf->dplls.lock);
> >
> >     return 0;
> >  }
> > @@ -4674,10 +4794,30 @@ void ice_dpll_deinit(struct ice_pf *pf)  {
> >     bool cgu = ice_is_feature_supported(pf, ICE_F_CGU);
> >
> > +   /* Clear ICE_FLAG_DPLL under the lock so that any new caller of
> > +    * ice_txclk_update_and_notify() observes the cleared flag and
> > +    * returns early. In-flight callers that already passed the
> > flag
> > +    * check hold txclk_notify_rwsem for read across the out-of-
> > lock
> > +    * dpll_*_change_ntf() calls; the down_write/up_write barrier
> > +    * below waits for them to finish before pins and the TXC DPLL
> > +    * device may be freed.
> > +    */
> > +   mutex_lock(&pf->dplls.lock);
> >     clear_bit(ICE_FLAG_DPLL, pf->flags);
> > +   mutex_unlock(&pf->dplls.lock);
> > +
> > +   /* Wait for in-flight ice_txclk_update_and_notify() readers */
> > +   if (pf->hw.mac_type == ICE_MAC_GENERIC_3K_E825) {
> > +           down_write(&pf->dplls.txclk_notify_rwsem);
> > +           up_write(&pf->dplls.txclk_notify_rwsem);
> > +   }
> > +
> >     if (cgu)
> >             ice_dpll_deinit_worker(pf);
> >
> > +   if (pf->hw.mac_type == ICE_MAC_GENERIC_3K_E825)
> > +           cancel_work_sync(&pf->dplls.txclk_work);
> > +
> >     ice_dpll_deinit_pins(pf, cgu);
> >     if (!IS_ERR_OR_NULL(pf->dplls.pps.dpll))
> >             ice_dpll_deinit_dpll(pf, &pf->dplls.pps, cgu); @@ -
> > 4706,6 +4846,12 @@ static void ice_dpll_init_e825(struct ice_pf *pf)
> >     int err;
> >
> >     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.
> > +    */
> > +   INIT_WORK(&d->txclk_work, ice_dpll_txclk_work);
> > +   init_rwsem(&d->txclk_notify_rwsem);
> >     init_completion(&d->dpll_init);
> >
> >     err = ice_dpll_init_info_e825c(pf);
> > diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.h
> > b/drivers/net/ethernet/intel/ice/ice_dpll.h
> > index 85e38752056d..103ba3e49068 100644
> > --- a/drivers/net/ethernet/intel/ice/ice_dpll.h
> > +++ b/drivers/net/ethernet/intel/ice/ice_dpll.h
> > @@ -8,6 +8,8 @@
> >
> >  #define ICE_DPLL_RCLK_NUM_MAX      4
> >  #define ICE_DPLL_TXCLK_NUM_MAX     2
> > +#define E825_EXT_EREF_PIN_IDX      0
> > +#define E825_EXT_SYNCE_PIN_IDX     1
> >
> >  #define ICE_CGU_R10                        0x28
> >  #define ICE_CGU_R10_SYNCE_CLKO_SEL GENMASK(8, 5)
> > @@ -126,7 +128,8 @@ struct ice_dpll {
> >  /** ice_dplls - store info required for CCU (clock controlling unit)
> >   * @kworker: periodic worker
> >   * @work: periodic work
> > - * @lock: locks access to configuration of a dpll
> > + * @wq: workqueue used to schedule DPLL-related deferred work
> > + * @lock: protects DPLL configuration (see Locking below)
> >   * @eec: pointer to EEC dpll dev
> >   * @pps: pointer to PPS dpll dev
> >   * @txc: pointer to TXC dpll dev
> > @@ -142,6 +145,28 @@ struct ice_dpll {
> >   * @input_phase_adj_max: max phase adjust value for an input pins
> >   * @output_phase_adj_max: max phase adjust value for an output pins
> >   * @periodic_counter: counter of periodic work executions
> > + * @generic: true when generic DPLL ops are used
> > + * @txclk_work: deferred TX reference clock switch worker
> > + * @txclk_switch_requested: a TX ref clock switch is queued in
> > + @txclk_work
> > + * @txclk_notify_rwsem: drains in-flight TXCLK notifications on
> > + teardown
> > + *
> > + * Locking:
> > + *   Acquisition order (top to bottom):
> > + *
> > + *     txclk_notify_rwsem (read)
> > + *       -> pf->dplls.lock
> > + *         -> ctrl_pf->dplls.lock
> > + *
> > + *   - @lock serializes all DPLL state mutations on this PF. When the
> > + *     controlling PF's lock must also be taken (e.g. updating the
> > shared
> > + *     tx_refclks usage map), acquire pf->dplls.lock first, then
> > + *     ctrl_pf->dplls.lock. Skip the second acquire when pf ==
> > ctrl_pf
> > + *     to avoid recursive locking.
> > + *   - @txclk_notify_rwsem is held for read across
> > + *     ice_txclk_update_and_notify(), including the out-of-lock
> > + *     dpll_*_change_ntf() calls. ice_dpll_deinit() takes the write
> > side
> > + *     standalone (not nested under any other lock) to drain in-
> > flight
> > + *     readers before pins and the TXC DPLL device are freed.
> >   */
> >  struct ice_dplls {
> >     struct kthread_worker *kworker;
> > @@ -168,6 +193,9 @@ struct ice_dplls {
> >     s32 output_phase_adj_max;
> >     u32 periodic_counter;
> >     bool generic;
> > +   struct work_struct txclk_work;
> > +   bool txclk_switch_requested;
> > +   struct rw_semaphore txclk_notify_rwsem;
> >  };
> >
> >  #if IS_ENABLED(CONFIG_PTP_1588_CLOCK)
> > diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c
> > b/drivers/net/ethernet/intel/ice/ice_ptp.c
> > index 36df742c326c..380833a24327 100644
> > --- a/drivers/net/ethernet/intel/ice/ice_ptp.c
> > +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
> > @@ -4,6 +4,7 @@
> >  #include "ice.h"
> >  #include "ice_lib.h"
> >  #include "ice_trace.h"
> > +#include "ice_txclk.h"
> >
> >  static const char ice_pin_names[][64] = {
> >     "SDP0",
> > @@ -54,11 +55,6 @@ static const struct ice_ptp_pin_desc
> > ice_pin_desc_dpll[] = {
> >     {  SDP3, {  3, -1 }, { 0, 0 }},
> >  };
> >
> > -static struct ice_pf *ice_get_ctrl_pf(struct ice_pf *pf) -{
> > -   return !pf->adapter ? NULL : pf->adapter->ctrl_pf;
> > -}
> > -
> >  static struct ice_ptp *ice_get_ctrl_ptp(struct ice_pf *pf)  {
> >     struct ice_pf *ctrl_pf = ice_get_ctrl_pf(pf); @@ -1328,6
> > +1324,9 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
> >                     }
> >             }
> >             mutex_unlock(&pf->dplls.lock);
> > +
> > +           if (linkup)
> > +                   ice_txclk_update_and_notify(pf);
> >     }
> >
> >     switch (hw->mac_type) {
> > @@ -3090,6 +3089,21 @@ static int ice_ptp_setup_pf(struct ice_pf *pf)
> >              &pf->adapter->ports.ports);
> >     mutex_unlock(&pf->adapter->ports.lock);
> >
> > +   /* Seed the per-PHY Tx reference clock usage map for this port.
> > +    * Only meaningful on E825 (other MAC types don't expose tx-clk
> > +    * selection). No locking is needed because this runs during
> > +    * ice_ptp_init() before pf->dplls.lock exists and before any
> > +    * link event or DPLL callback can observe the map.
> > +    */
> > +   if (pf->hw.mac_type == ICE_MAC_GENERIC_3K_E825) {
> > +           u8 port_num, phy;
> > +
> > +           port_num = ptp->port.port_num;
> > +           phy = port_num / pf->hw.ptp.ports_per_phy;
> > +           set_bit(port_num,
> > +                   &ctrl_ptp->tx_refclks[phy][pf->ptp.port.tx_clk]);
> > +   }
> > +
> >     return 0;
> >  }
> >
> > @@ -3318,6 +3332,19 @@ void ice_ptp_init(struct ice_pf *pf)
> >                     goto err_exit;
> >     }
> >
> > +   ptp->port.tx_clk = ICE_REF_CLK_ENET;
> > +   ptp->port.tx_clk_req = ICE_REF_CLK_ENET;
> > +   if (hw->mac_type == ICE_MAC_GENERIC_3K_E825) {
> > +           enum ice_e825c_ref_clk tx_ref_clk;
> > +
> > +           err = ice_get_serdes_ref_sel_e825c(hw, ptp-
> > >port.port_num,
> > +                                              &tx_ref_clk);
> > +           if (!err) {
> > +                   ptp->port.tx_clk = tx_ref_clk;
> > +                   ptp->port.tx_clk_req = tx_ref_clk;
> > +           }
> > +   }
> > +
> >     err = ice_ptp_setup_pf(pf);
> >     if (err)
> >             goto err_exit;
> > diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.h
> > b/drivers/net/ethernet/intel/ice/ice_ptp.h
> > index 8c44bd758a4f..c4b0da7ce20e 100644
> > --- a/drivers/net/ethernet/intel/ice/ice_ptp.h
> > +++ b/drivers/net/ethernet/intel/ice/ice_ptp.h
> > @@ -144,6 +144,8 @@ struct ice_ptp_tx {
> >   * @link_up: indicates whether the link is up
> >   * @tx_fifo_busy_cnt: number of times the Tx FIFO was busy
> >   * @port_num: the port number this structure represents
> > + * @tx_clk: currently active Tx reference clock source
> > + * @tx_clk_req: requested Tx reference clock source (new target)
> >   */
> >  struct ice_ptp_port {
> >     struct list_head list_node;
> > @@ -153,6 +155,8 @@ struct ice_ptp_port {
> >     bool link_up;
> >     u8 tx_fifo_busy_cnt;
> >     u8 port_num;
> > +   enum ice_e825c_ref_clk tx_clk;
> > +   enum ice_e825c_ref_clk tx_clk_req;
> >  };
> >
> >  enum ice_ptp_tx_interrupt {
> > @@ -236,6 +240,7 @@ struct ice_ptp_pin_desc {
> >   * @info: structure defining PTP hardware capabilities
> >   * @clock: pointer to registered PTP clock device
> >   * @tstamp_config: hardware timestamping configuration
> > + * @tx_refclks: bitmaps table to store the information about TX
> > + reference clocks
> >   * @reset_time: kernel time after clock stop on reset
> >   * @tx_hwtstamp_good: number of completed Tx timestamp requests
> >   * @tx_hwtstamp_skipped: number of Tx time stamp requests skipped @@
> > -261,6 +266,7 @@ struct ice_ptp {
> >     struct ptp_clock_info info;
> >     struct ptp_clock *clock;
> >     struct kernel_hwtstamp_config tstamp_config;
> > +   unsigned long tx_refclks[ICE_E825_MAX_PHYS][ICE_REF_CLK_MAX];
> >     u64 reset_time;
> >     u64 tx_hwtstamp_good;
> >     u32 tx_hwtstamp_skipped;
> > diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> > b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> > index 2c18e16fe053..8e5f97835954 100644
> > --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> > +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> > @@ -486,6 +486,43 @@ static int ice_read_phy_eth56g(struct ice_hw *hw,
> > u8 port, u32 addr, u32 *val)
> >     return err;
> >  }
> >
> > +/**
> > + * ice_get_serdes_ref_sel_e825c - Read current Tx ref clock source
> > + * @hw: pointer to the HW struct
> > + * @port: port number for which Tx reference clock is read
> > + * @clk: Tx reference clock value (output)
> > + *
> > + * Return: 0 on success, other error codes when failed to read from
> > PHY
> > +*/ int ice_get_serdes_ref_sel_e825c(struct ice_hw *hw, u8 port,
> > +                            enum ice_e825c_ref_clk *clk)
> > +{
> > +   u8 lane = port % hw->ptp.ports_per_phy;
> > +   u32 serdes_rx_nt, serdes_tx_nt;
> > +   u32 val;
> > +   int ret;
> > +
> > +   ret = ice_read_phy_eth56g(hw, port,
> > +                             SERDES_IP_IF_LN_FLXM_GENERAL(lane, 0),
> > +                             &val);
> > +   if (ret)
> > +           return ret;
> > +
> > +   serdes_rx_nt = FIELD_GET(CFG_ICTL_PCS_REF_SEL_RX_NT, val);
> > +   serdes_tx_nt = FIELD_GET(CFG_ICTL_PCS_REF_SEL_TX_NT, val);
> > +
> > +   if (serdes_tx_nt == REF_SEL_NT_SYNCE &&
> > +       serdes_rx_nt == REF_SEL_NT_SYNCE)
> > +           *clk = ICE_REF_CLK_SYNCE;
> > +   else if (serdes_tx_nt == REF_SEL_NT_EREF0 &&
> > +            serdes_rx_nt == REF_SEL_NT_EREF0)
> > +           *clk = ICE_REF_CLK_EREF0;
> > +   else
> > +           *clk = ICE_REF_CLK_ENET;
> > +
> > +   return 0;
> > +}
> > +
> >  /**
> >   * ice_phy_res_address_eth56g - Calculate a PHY port register address
> >   * @hw: pointer to the HW struct
> > diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
> > b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
> > index 10795d023f44..16b1988e993d 100644
> > --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
> > +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
> > @@ -383,6 +383,8 @@ int ice_start_phy_timer_eth56g(struct ice_hw *hw,
> > u8 port);  int ice_phy_cfg_intr_eth56g(struct ice_hw *hw, u8 port,
> > bool ena, u8 threshold);  int ice_phy_cfg_ptp_1step_eth56g(struct
> > ice_hw *hw, u8 port);  int ice_ptp_phy_soft_reset_eth56g(struct ice_hw
> > *hw, u8 port);
> > +int ice_get_serdes_ref_sel_e825c(struct ice_hw *hw, u8 port,
> > +                            enum ice_e825c_ref_clk *clk);
> >
> >  #define ICE_ETH56G_NOMINAL_INCVAL  0x140000000ULL
> >  #define ICE_ETH56G_NOMINAL_PCS_REF_TUS     0x100000000ULL
> > @@ -795,4 +797,12 @@ static inline u64 ice_get_base_incval(struct
> > ice_hw *hw)
> >  #define PHY_PTP_1STEP_PD_DELAY_M   GENMASK(30, 1)
> >  #define PHY_PTP_1STEP_PD_DLY_V_M   BIT(31)
> >
> > +#define SERDES_IP_IF_LN_FLXM_GENERAL(n, m) \
> > +   (0x32B800 + (m) * 0x100000 + (n) * 0x8000)
> > +#define CFG_ICTL_PCS_REF_SEL_RX_NT         GENMASK(9, 6)
> > +#define CFG_ICTL_PCS_REF_SEL_TX_NT         GENMASK(28, 25)
> > +#define REF_SEL_NT_ENET                            0
> > +#define REF_SEL_NT_EREF0                   1
> > +#define REF_SEL_NT_SYNCE                   2
> > +
> >  #endif /* _ICE_PTP_HW_H_ */
> > diff --git a/drivers/net/ethernet/intel/ice/ice_txclk.c
> > b/drivers/net/ethernet/intel/ice/ice_txclk.c
> > new file mode 100644
> > index 000000000000..5230081c53b9
> > --- /dev/null
> > +++ b/drivers/net/ethernet/intel/ice/ice_txclk.c
> > @@ -0,0 +1,320 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Copyright (C) 2026 Intel Corporation */
> 
> ...
> 
> > +#define ICE_REFCLK_USER_TO_AQ_IDX(x) ((x) + 1)
> > +
> > +/**
> > + * ice_txclk_set_clk - Set Tx reference clock
> > + * @pf: pointer to pf structure
> > + * @clk: new Tx clock
> > + *
> > + * Return: 0 on success, negative value otherwise.
> > + */
> > +int ice_txclk_set_clk(struct ice_pf *pf, enum ice_e825c_ref_clk clk)
> > {
> > +   struct ice_pf *ctrl_pf = ice_get_ctrl_pf(pf);
> > +   struct ice_port_info *port_info;
> > +   bool clk_in_use;
> > +   u8 port_num, phy;
> > +   int err;
> > +
> > +   if (pf->ptp.port.tx_clk == clk)
> > +           return 0;
> > +
> > +   if (IS_ERR_OR_NULL(ctrl_pf)) {
> > +           dev_err(ice_pf_to_dev(pf),
> > +                   "Can't set tx-clk: no controlling PF\n");
> > +           return -EINVAL;
> > +   }
> > +
> > +   port_num = pf->ptp.port.port_num;
> > +   phy = port_num / pf->hw.ptp.ports_per_phy;
> > +   port_info = pf->hw.port_info;
> > +
> > +   /* Hold ctrl_pf->dplls.lock across both the usage check and the
> > +    * enable AQ command so that two PFs racing to switch to the
> > same
> > +    * (phy, clk) cannot both observe clk_in_use == false and issue
> > +    * duplicate enables. The tx_refclks bitmap is updated only
> > later
> > +    * by ice_txclk_update_and_notify() after link-up, so without
> > this
> > +    * the check-then-act window is wide open.
> > +    */
> > +   mutex_lock(&ctrl_pf->dplls.lock);
> > +   clk_in_use = ice_txclk_any_port_uses(ctrl_pf, phy, clk);
> > +   if (!clk_in_use) {
> > +           err = ice_cpi_ena_dis_clk_ref(&pf->hw, phy, clk, true);
> > +           if (err) {
> > +                   mutex_unlock(&ctrl_pf->dplls.lock);
> > +                   dev_err(ice_pf_to_dev(pf), "Failed to enable the
> > %u TX clock for the %u PHY\n",
> > +                           clk, phy);
> > +                   return err;
> > +           }
> > +   }
> > +   mutex_unlock(&ctrl_pf->dplls.lock);
> > +
> > +   if (!clk_in_use) {
> > +           err = ice_txclk_enable_peer(pf, clk);
> > +           if (err)
> > +                   return err;
> Local PHY clock was enabled above via CPI but
> enable_peer() failed.  Function returns error without
> disabling the locally-enabled clock.
> Isn't it a problem?
> 

Thanks for that comment. It's not desired situation of course.
But no negative impact on functioning of the device

> > +   }
> > +
> > +   /* We are ready to switch to the new TX clk. */
> > +   err = ice_aq_set_link_restart_an(port_info, true, NULL,
> > +                                    ICE_REFCLK_USER_TO_AQ_IDX(clk));
> > +   if (err)
> > +           dev_err(ice_pf_to_dev(pf),
> > +                   "AN restart AQ command failed with err %d\n",
> > +                   err);
> > +
> > +   return err;
> > +}
> > +
> 
> ...
> 
> > /* _ICE_TXCLK_H_ */
> > --
> > 2.39.3


Reply via email to