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
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_ */
#include "ice.h"
#include "ice_lib.h"
#include "ice_trace.h"
+#include "ice_txclk.h"
#include <linux/dpll.h>
#include <linux/property.h>
#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
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
*
* 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
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.
+ struct ice_dpll_pin *p = pin_priv;
+ struct ice_pf *pf = p->pf;
+ enum ice_e825c_ref_clk new_clk;
+ int ret = 0;
+
+ 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;
+ }
+
+ /* Check ICE_FLAG_DPLL and queue_work() under pf->dplls.lock.
+ * ice_dpll_deinit() clears the flag under the same lock before
+ * cancel_work_sync() and wq destruction, so a callback arriving
+ * after teardown observes the cleared flag and bails out.
*/
- return -EOPNOTSUPP;
+ mutex_lock(&pf->dplls.lock);
+ if (!test_bit(ICE_FLAG_DPLL, pf->flags)) {
+ ret = -ENODEV;
+ goto unlock;
+ }
+ 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 ret;
}
/**
* @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,
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;
}
&ice_dpll_txclk_ops,
ARRAY_SIZE(pf->dplls.txclks));
ice_dpll_release_pins(&pf->dplls.txclks[E825_EXT_EREF_PIN_IDX], 1);
+ /* ice_dpll_release_pins() puts the pin but does not clear the slot,
+ * unlike ice_dpll_release_fwnode_pin() used for SYNCE below. NULL it
+ * so a late ice_txclk_get_pin() returns NULL rather than a dangling
+ * pointer.
+ */
+ pf->dplls.txclks[E825_EXT_EREF_PIN_IDX].pin = NULL;
ice_dpll_release_fwnode_pin(synce_pin);
return 0;
}
d->clock_id = ice_generate_clock_id(pf);
d->num_inputs = ICE_SYNCE_CLK_NUM;
- dt->dpll_state = DPLL_LOCK_STATUS_UNLOCKED;
+ 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;
{
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);
}
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);
#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)
/** 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
* @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;
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)
#include "ice.h"
#include "ice_lib.h"
#include "ice_trace.h"
+#include "ice_txclk.h"
static const char ice_pin_names[][64] = {
"SDP0",
{ 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);
}
}
mutex_unlock(&pf->dplls.lock);
+
+ if (linkup)
+ ice_txclk_update_and_notify(pf);
}
switch (hw->mac_type) {
&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;
}
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;
* @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;
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 {
* @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
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;
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
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
#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_ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2026 Intel Corporation */
+
+#include "ice.h"
+#include "ice_cpi.h"
+#include "ice_txclk.h"
+
+#define ICE_PHY0 0
+#define ICE_PHY1 1
+
+/**
+ * ice_txclk_get_pin - map TX reference clock to its DPLL pin
+ * @pf: pointer to the PF structure
+ * @ref_clk: TX reference clock selection
+ *
+ * Return the DPLL pin corresponding to a given external TX reference
+ * clock. Only external TX reference clocks (SYNCE and EREF0) are
+ * represented as DPLL pins. The internal ENET (TXCO) clock has no
+ * associated DPLL pin and therefore yields %NULL.
+ *
+ * This helper is used when emitting DPLL pin change notifications
+ * after TX reference clock transitions have been verified.
+ *
+ * Return: Pointer to the corresponding struct dpll_pin, or %NULL if
+ * the TX reference clock has no DPLL pin representation.
+ */
+struct dpll_pin *
+ice_txclk_get_pin(struct ice_pf *pf, enum ice_e825c_ref_clk ref_clk)
+{
+ switch (ref_clk) {
+ case ICE_REF_CLK_SYNCE:
+ return pf->dplls.txclks[E825_EXT_SYNCE_PIN_IDX].pin;
+ case ICE_REF_CLK_EREF0:
+ return pf->dplls.txclks[E825_EXT_EREF_PIN_IDX].pin;
+ case ICE_REF_CLK_ENET:
+ default:
+ return NULL;
+ }
+}
+
+/**
+ * ice_txclk_enable_peer - Enable required TX reference clock on peer PHY
+ * @pf: pointer to the PF structure
+ * @clk: TX reference clock that must be enabled
+ *
+ * Some TX reference clocks on E825-class devices (SyncE and EREF0) must
+ * be enabled on both PHY complexes to allow proper routing:
+ *
+ * - SyncE must be enabled on both PHYs when used by PHY0
+ * - EREF0 must be enabled on both PHYs when used by PHY1
+ *
+ * If the requested clock is not yet enabled on the peer PHY, enable it.
+ * ENET does not require duplication and is ignored.
+ *
+ * Return: 0 on success or negative error code on failure.
+ */
+static int ice_txclk_enable_peer(struct ice_pf *pf, enum ice_e825c_ref_clk clk)
+{
+ struct ice_pf *ctrl_pf = ice_get_ctrl_pf(pf);
+ bool peer_clk_in_use;
+ u8 port_num, phy;
+ int err;
+
+ if (clk == ICE_REF_CLK_ENET)
+ return 0;
+
+ if (IS_ERR_OR_NULL(ctrl_pf)) {
+ dev_err(ice_pf_to_dev(pf),
+ "Can't enable tx-clk on peer: no controlling PF\n");
+ return -EINVAL;
+ }
+
+ port_num = pf->ptp.port.port_num;
+ phy = port_num / pf->hw.ptp.ports_per_phy;
+ peer_clk_in_use = true;
+
+ /* Hold ctrl_pf->dplls.lock across both the peer-usage check and
+ * the enable AQ command so that two PFs racing to enable the same
+ * peer-PHY clock cannot both observe peer_clk_in_use == false and
+ * issue duplicate enables.
+ */
+ mutex_lock(&ctrl_pf->dplls.lock);
+ if (clk == ICE_REF_CLK_SYNCE && phy == ICE_PHY0)
+ peer_clk_in_use = ice_txclk_any_port_uses(ctrl_pf,
+ ICE_PHY1,
+ clk);
+ else if (clk == ICE_REF_CLK_EREF0 && phy == ICE_PHY1)
+ peer_clk_in_use = ice_txclk_any_port_uses(ctrl_pf,
+ ICE_PHY0,
+ clk);
+
+ if ((clk == ICE_REF_CLK_SYNCE && phy == ICE_PHY0 && !peer_clk_in_use) ||
+ (clk == ICE_REF_CLK_EREF0 && phy == ICE_PHY1 && !peer_clk_in_use)) {
+ u8 peer_phy = phy ? ICE_PHY0 : ICE_PHY1;
+
+ err = ice_cpi_ena_dis_clk_ref(&pf->hw, peer_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, peer_phy);
+ return err;
+ }
+ }
+ mutex_unlock(&ctrl_pf->dplls.lock);
+
+ return 0;
+}
+
+#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;
+ }
+
+ if (!test_bit(ICE_FLAG_DPLL, ctrl_pf->flags)) {
+ dev_err(ice_pf_to_dev(pf),
+ "Can't set tx-clk: ctrl PF DPLL not available\n");
+ return -EOPNOTSUPP;
+ }
+
+ 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;
+ }
+
+ /* 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;
+ }
+
+ /* Clear txclk_switch_requested only after the AN restart AQ has been
+ * accepted by FW. Clearing earlier would race with any asynchronous
+ * link-up event: ice_txclk_update_and_notify() would observe the
+ * cleared flag, read the stale SERDES selector, and misinterpret the
+ * not-yet-applied switch as a HW rejection. Only clear if no newer
+ * request has overwritten tx_clk_req while we were dropping locks.
+ */
+ mutex_lock(&pf->dplls.lock);
+ if (pf->ptp.port.tx_clk_req == clk)
+ pf->dplls.txclk_switch_requested = false;
+ mutex_unlock(&pf->dplls.lock);
+
+ return 0;
+}
+
+/**
+ * ice_txclk_update_and_notify - Validate TX reference clock switching
+ * @pf: pointer to PF structure
+ *
+ * After a link-up event, verify whether the previously requested TX reference
+ * clock transition actually succeeded. The SERDES reference selector reflects
+ * the effective hardware choice, which may differ from the requested clock
+ * when Auto-Negotiation or firmware applies additional policy.
+ *
+ * If the hardware-selected clock differs from the requested one, update the
+ * software state accordingly and stop further processing.
+ *
+ * When the switch is successful, update the per‑PHY usage bitmaps so that the
+ * driver knows which reference clock is currently in use by this port.
+ *
+ * This function does not initiate a clock switch; it only validates the result
+ * of a previously triggered transition and performs cleanup of unused clocks.
+ */
+void ice_txclk_update_and_notify(struct ice_pf *pf)
+{
+ struct ice_ptp_port *ptp_port = &pf->ptp.port;
+ struct ice_pf *ctrl_pf = ice_get_ctrl_pf(pf);
+ struct dpll_pin *old_pin = NULL;
+ struct dpll_pin *new_pin = NULL;
+ struct ice_hw *hw = &pf->hw;
+ enum ice_e825c_ref_clk clk;
+ bool notify_dpll = false;
+ int err;
+ u8 phy;
+
+ phy = ptp_port->port_num / hw->ptp.ports_per_phy;
+
+ /* Hold txclk_notify_rwsem for read across the entire critical
+ * region, including the out-of-lock dpll_*_change_ntf() calls
+ * below. ice_dpll_deinit() takes the write side to wait for all
+ * in-flight notifications to complete before freeing pins and the
+ * TXC DPLL device, preventing a use-after-free on rmmod.
+ */
+ down_read(&pf->dplls.txclk_notify_rwsem);
+ mutex_lock(&pf->dplls.lock);
+ /* Bail out if DPLL subsystem is being torn down. ice_dpll_deinit()
+ * clears ICE_FLAG_DPLL before freeing pins and the dpll device, so a
+ * cleared flag under the lock means those objects can no longer be
+ * safely dereferenced.
+ */
+ if (!test_bit(ICE_FLAG_DPLL, pf->flags)) {
+ mutex_unlock(&pf->dplls.lock);
+ goto out;
+ }
+ /* If a switch is still pending, the link-up event preceded the
+ * worker's AN restart. Hardware hasn't applied the new clock yet,
+ * so reading the SERDES selector now would produce a false failure.
+ * Let the worker run first; the link-up that follows the AN restart
+ * will trigger the verification.
+ */
+ if (pf->dplls.txclk_switch_requested) {
+ mutex_unlock(&pf->dplls.lock);
+ goto out;
+ }
+ /* no TX clock change requested */
+ if (pf->ptp.port.tx_clk == pf->ptp.port.tx_clk_req) {
+ mutex_unlock(&pf->dplls.lock);
+ goto out;
+ }
+ /* verify current Tx reference settings */
+ err = ice_get_serdes_ref_sel_e825c(hw,
+ ptp_port->port_num,
+ &clk);
+ if (err) {
+ mutex_unlock(&pf->dplls.lock);
+ goto out;
+ }
+
+ if (clk != pf->ptp.port.tx_clk_req) {
+ dev_warn(ice_pf_to_dev(pf),
+ "Failed to switch tx-clk for phy %d and clk %u (current: %u)\n",
+ phy, pf->ptp.port.tx_clk_req, clk);
+ old_pin = ice_txclk_get_pin(pf, pf->ptp.port.tx_clk_req);
+ new_pin = ice_txclk_get_pin(pf, clk);
+ pf->ptp.port.tx_clk = clk;
+ pf->ptp.port.tx_clk_req = clk;
+ /* Update the reference clock bitmap to match the hardware
+ * clock that was actually accepted, so that
+ * ice_txclk_any_port_uses() reflects reality even on failure.
+ * The map is owned by ctrl_pf; take its lock per documented
+ * order (pf->dplls.lock first, then ctrl_pf->dplls.lock) so
+ * readers on other PFs observe a consistent snapshot.
+ */
+ if (!IS_ERR_OR_NULL(ctrl_pf)) {
+ if (ctrl_pf != pf)
+ mutex_lock(&ctrl_pf->dplls.lock);
+ for (int i = 0; i < ICE_REF_CLK_MAX; i++) {
+ if (clk == i)
+ set_bit(ptp_port->port_num,
+ &ctrl_pf->ptp.tx_refclks[phy][i]);
+ else
+ clear_bit(ptp_port->port_num,
+ &ctrl_pf->ptp.tx_refclks[phy][i]);
+ }
+ if (ctrl_pf != pf)
+ mutex_unlock(&ctrl_pf->dplls.lock);
+ }
+ goto err_notify;
+ }
+
+ old_pin = ice_txclk_get_pin(pf, pf->ptp.port.tx_clk);
+ pf->ptp.port.tx_clk = clk;
+ pf->ptp.port.tx_clk_req = clk;
+
+ if (IS_ERR_OR_NULL(ctrl_pf)) {
+ dev_err(ice_pf_to_dev(pf),
+ "Can't set tx-clk: no controlling PF\n");
+ goto err_notify;
+ }
+
+ /* update Tx reference clock usage map; map is owned by ctrl_pf,
+ * take its lock per documented order so readers on other PFs see
+ * a consistent view.
+ */
+ if (ctrl_pf != pf)
+ mutex_lock(&ctrl_pf->dplls.lock);
+ for (int i = 0; i < ICE_REF_CLK_MAX; i++)
+ if (clk == i)
+ set_bit(ptp_port->port_num,
+ &ctrl_pf->ptp.tx_refclks[phy][i]);
+ else
+ clear_bit(ptp_port->port_num,
+ &ctrl_pf->ptp.tx_refclks[phy][i]);
+ if (ctrl_pf != pf)
+ mutex_unlock(&ctrl_pf->dplls.lock);
+
+err_notify:
+ /* Update TXC DPLL lock status based on effective TX clk, while still
+ * holding the lock to prevent concurrent link-up events from racing
+ * on dpll_state.
+ */
+ if (!IS_ERR_OR_NULL(pf->dplls.txc.dpll)) {
+ enum dpll_lock_status new_lock = ice_txclk_lock_status(clk);
+
+ if (pf->dplls.txc.dpll_state != new_lock) {
+ pf->dplls.txc.dpll_state = new_lock;
+ notify_dpll = true;
+ }
+ }
+ mutex_unlock(&pf->dplls.lock);
+
+ /* Notify TX clk pins state transition */
+ if (old_pin)
+ dpll_pin_change_ntf(old_pin);
+ if (new_pin)
+ dpll_pin_change_ntf(new_pin);
+
+ if (notify_dpll && !IS_ERR_OR_NULL(pf->dplls.txc.dpll))
+ dpll_device_change_ntf(pf->dplls.txc.dpll);
+
+out:
+ up_read(&pf->dplls.txclk_notify_rwsem);
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2026 Intel Corporation */
+
+#ifndef _ICE_TXCLK_H_
+#define _ICE_TXCLK_H_
+
+/**
+ * ice_txclk_any_port_uses - check if any port on a PHY uses this TX refclk
+ * @ctrl_pf: control PF (owner of the shared tx_refclks map)
+ * @phy: PHY index
+ * @clk: TX reference clock
+ *
+ * Return: true if any bit (port) is set for this clock on this PHY
+ */
+static inline bool
+ice_txclk_any_port_uses(const struct ice_pf *ctrl_pf, u8 phy,
+ enum ice_e825c_ref_clk clk)
+{
+ return find_first_bit(&ctrl_pf->ptp.tx_refclks[phy][clk],
+ BITS_PER_LONG) < BITS_PER_LONG;
+}
+
+static inline enum dpll_lock_status
+ice_txclk_lock_status(enum ice_e825c_ref_clk clk)
+{
+ switch (clk) {
+ case ICE_REF_CLK_SYNCE:
+ case ICE_REF_CLK_EREF0:
+ return DPLL_LOCK_STATUS_LOCKED;
+ case ICE_REF_CLK_ENET:
+ default:
+ return DPLL_LOCK_STATUS_UNLOCKED;
+ }
+}
+
+int ice_txclk_set_clk(struct ice_pf *pf, enum ice_e825c_ref_clk clk);
+void ice_txclk_update_and_notify(struct ice_pf *pf);
+struct dpll_pin *ice_txclk_get_pin(struct ice_pf *pf,
+ enum ice_e825c_ref_clk ref_clk);
+#endif /* _ICE_TXCLK_H_ */