Merge branch 'dpll-ice-add-generic-dpll-type-and-full-tx-reference-clock-control-for-e825'

Grzegorz Nitka says:

====================
dpll/ice: Add generic DPLL type and full TX reference clock control for E825

NOTE: This series is intentionally submitted on net-next (not
intel-wired-lan) as early feedback of DPLL subsystem changes is
welcomed. In the past possible approaches were discussed in [1].

This series adds TX reference clock support for E825 devices and exposes
TX clock selection and synchronization status via the Linux DPLL
subsystem.

Here is the high-level connection diagram for E825 device:
  +------------------------------------------------------------------+
  |                                                                  |
  |                           +-----------------------------+        |
  |                           |                             |        |
  |                           |         MAC                 |        |
  |                           |+------------+-----+         |        |
  |                           ||RX/1588 |PHC|tspll<----\    |        |
+---+----+                    ||MUX     +---+-^---|    |    |        |
| E | RX >--------------------->              |   >--\ |    |        |
| T |    |    /---------------->              |   >-\| |    |        |
| H |----+    |               |+---------+----^---+ || |    |        |
| 1 | TX <----|----------------+TX MUX   < OCXO   | || |    |        |
|   |PLL |    |               ||         |--------| || |    |        |
+---+----+    |           /----+         <-ext_ref<-||-|----|------ext_ref
| E | RX >----/           |   ||         |--------+ || |    |        |
| T |    |                |   ||         <  SyncE | || |    |        |
| H |----+                |   |+-----------^------+ || |    |        |
| 2 | TX <----------------/   |            | /------||-/    |        |
|   |PLL |                    +------------|-|------||------+        |
+---+----+                              /--/ |      ||               |
| . | RX >---                           |    |      ||               |
| . |    |                   +----------|----|------||--+            |
| . |----+                   |        +-^-+--^+     ||  |            |
|   | TX <---                |        |EEC|PPS|     ||  |            |
|   |PLL |                   |        +-------+     ||  |            |
+---+----+                   |        |       <-CLK0/|  |            |
| E | RX >---                |        |  DPLL |      |  |            |
| T |    |                   |        |       <-CLK1-/  |            |
| H |----+                   |        |       |         |            |
| X | TX <---                |        |       <---SMA---<            |
|   |PLL |                   |        |       |         |            |
+---+----+                   |        |       <---GPS---<            |
  |                          |        |       |         |            |
  |                          |        |       <---...---<            |
  |                          |        |       |         |            |
  |                          |        +-------+         |            |
  |                          | External timing module   |            |
  |                          +--------------------------+            |
  +------------------------------------------------------------------+

E825 hardware contains a dedicated TX clock domain with per-port source
selection behavior that is distinct from PPS handling and from board-level
EEC distribution. TX reference clock selection is device-wide, shared
across ports, and mediated by firmware as part of link bring-up. As a
result, TX clock selection intent may differ from effective hardware
configuration, and software must verify outcome after link-up.

To support this, the series extends the DPLL core and the ice driver
incrementally. The series also introduces DPLL_TYPE_GENERIC as a broad
UAPI class for DPLL instances outside PPS/EEC categories. The intent is
to keep type naming reusable and scalable across different ASIC
topologies while preserving functional discoverability via
driver/device context and pin topology.

This follows netdev discussion guidance that UAPI type naming should avoid
location-specific or vendor-specific taxonomy, because such labels do not
scale across different ASIC designs. The function of a given DPLL instance
is already discoverable from driver/device context and pin topology, and
does not require an additional narrow type identifier in UAPI.

At the same time, a separate DPLL object is still needed for E825 TX clock
control/reporting semantics. Using DPLL_TYPE_GENERIC provides a reusable
class for devices outside PPS/EEC without overfitting UAPI naming to one
topology.

The relevant discussion is in [2].

Series content
- add a new generic DPLL type for devices outside PPS/EEC classes;
- relax DPLL pin registration rules for firmware-described shared pins
  and extend pin notifications with a source identifier;
- allow dynamic state control of SyncE reference pins where hardware
  supports it;
- add CPI infrastructure for PHY-side TX clock control on E825C;
- introduce a TX-clock DPLL device and TX reference clock pins
  (EXT_EREF0 and SYNCE) in the ice driver;
- extend the Restart Auto-Negotiation command to carry a TX reference
  clock index;
- implement hardware-backed TX reference clock switching, post-link
  verification, and TX synchronization reporting.

TXCLK pins report TX reference topology only. Actual synchronization
success is reported via DPLL lock status, updated after hardware
verification: external TX references report LOCKED, while the internal
ENET/TXCO source reports UNLOCKED.

This provides reliable TX reference selection and observability on E825
devices using standard DPLL interfaces, without conflating user intent
with effective hardware behavior.

[1] https://lore.kernel.org/netdev/20250905160333.715c34ac@kernel.org/
[2] https://lore.kernel.org/netdev/20260402230626.3826719-1-grzegorz.nitka@intel.com/
====================

Link: https://patch.msgid.link/20260607183045.1213735-1-grzegorz.nitka@intel.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-06-13 13:24:38 -07:00
commit 0969af4b64
28 changed files with 1517 additions and 65 deletions

View File

@ -138,6 +138,9 @@ definitions:
-
name: eec
doc: dpll drives the Ethernet Equipment Clock
-
name: generic
doc: generic dpll type for devices outside PPS/EEC classes
render-max: true
-
type: enum

View File

@ -11,6 +11,7 @@
#include <linux/device.h>
#include <linux/err.h>
#include <linux/idr.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/string.h>
@ -71,7 +72,8 @@ void dpll_device_notify(struct dpll_device *dpll, unsigned long action)
call_dpll_notifiers(action, &info);
}
void dpll_pin_notify(struct dpll_pin *pin, unsigned long action)
void dpll_pin_notify(struct dpll_pin *pin, u64 src_clock_id,
unsigned long action)
{
struct dpll_pin_notifier_info info = {
.pin = pin,
@ -80,6 +82,7 @@ void dpll_pin_notify(struct dpll_pin *pin, unsigned long action)
.clock_id = pin->clock_id,
.fwnode = pin->fwnode,
.prop = &pin->prop,
.src_clock_id = src_clock_id,
};
call_dpll_notifiers(action, &info);
@ -652,6 +655,7 @@ dpll_pin_alloc(u64 clock_id, u32 pin_idx, struct module *module,
pin->pin_idx = pin_idx;
pin->clock_id = clock_id;
pin->module = module;
strscpy(pin->module_name, module_name(module));
if (WARN_ON(prop->type < DPLL_PIN_TYPE_MUX ||
prop->type > DPLL_PIN_TYPE_MAX)) {
ret = -EINVAL;
@ -847,7 +851,7 @@ __dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin,
if (ret)
goto ref_pin_del;
xa_set_mark(&dpll_pin_xa, pin->id, DPLL_REGISTERED);
dpll_pin_create_ntf(pin);
dpll_pin_create_ntf(pin, dpll->clock_id);
return ret;
@ -884,11 +888,21 @@ dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin,
return -EINVAL;
mutex_lock(&dpll_lock);
if (WARN_ON(!(dpll->module == pin->module &&
dpll->clock_id == pin->clock_id)))
/*
* For pins identified via firmware (pin->fwnode), allow registration
* even if the pin's (module, clock_id) differs from the target DPLL.
* For non-fwnode pins, require a strict (module, clock_id) match.
*/
if (!pin->fwnode &&
WARN_ON_ONCE(dpll->module != pin->module ||
dpll->clock_id != pin->clock_id)) {
ret = -EINVAL;
else
ret = __dpll_pin_register(dpll, pin, ops, priv, NULL);
goto out_unlock;
}
ret = __dpll_pin_register(dpll, pin, ops, priv, NULL);
out_unlock:
mutex_unlock(&dpll_lock);
return ret;
@ -914,11 +928,13 @@ __dpll_pin_unregister(struct dpll_device *dpll, struct dpll_pin *pin,
const struct dpll_pin_ops *ops, void *priv, void *cookie)
{
ASSERT_DPLL_PIN_REGISTERED(pin);
dpll_pin_ref_sync_pair_del(pin->id);
dpll_pin_delete_ntf(pin, dpll->clock_id);
dpll_xa_ref_pin_del(&dpll->pin_refs, pin, ops, priv, cookie);
dpll_xa_ref_dpll_del(&pin->dpll_refs, dpll, ops, priv, cookie);
if (xa_empty(&pin->dpll_refs))
if (xa_empty(&pin->dpll_refs)) {
dpll_pin_ref_sync_pair_del(pin->id);
xa_clear_mark(&dpll_pin_xa, pin->id, DPLL_REGISTERED);
}
}
/**
@ -940,7 +956,6 @@ void dpll_pin_unregister(struct dpll_device *dpll, struct dpll_pin *pin,
return;
mutex_lock(&dpll_lock);
dpll_pin_delete_ntf(pin);
__dpll_pin_unregister(dpll, pin, ops, priv, NULL);
mutex_unlock(&dpll_lock);
}
@ -986,7 +1001,7 @@ int dpll_pin_on_pin_register(struct dpll_pin *parent, struct dpll_pin *pin,
stop = i;
goto dpll_unregister;
}
dpll_pin_create_ntf(pin);
dpll_pin_create_ntf(pin, parent->clock_id);
}
mutex_unlock(&dpll_lock);
@ -995,9 +1010,9 @@ int dpll_pin_on_pin_register(struct dpll_pin *parent, struct dpll_pin *pin,
dpll_unregister:
xa_for_each(&parent->dpll_refs, i, ref)
if (i < stop) {
dpll_pin_delete_ntf(pin, parent->clock_id);
__dpll_pin_unregister(ref->dpll, pin, ops, priv,
parent);
dpll_pin_delete_ntf(pin);
}
dpll_xa_ref_pin_del(&pin->parent_refs, parent, ops, priv, pin);
unlock:
@ -1019,14 +1034,19 @@ EXPORT_SYMBOL_GPL(dpll_pin_on_pin_register);
void dpll_pin_on_pin_unregister(struct dpll_pin *parent, struct dpll_pin *pin,
const struct dpll_pin_ops *ops, void *priv)
{
struct dpll_pin_registration *reg;
struct dpll_pin_ref *ref;
unsigned long i;
mutex_lock(&dpll_lock);
dpll_pin_delete_ntf(pin);
dpll_xa_ref_pin_del(&pin->parent_refs, parent, ops, priv, pin);
xa_for_each(&pin->dpll_refs, i, ref)
xa_for_each(&pin->dpll_refs, i, ref) {
reg = dpll_pin_registration_find(ref, ops, priv, parent);
if (!reg)
continue;
dpll_pin_delete_ntf(pin, parent->clock_id);
__dpll_pin_unregister(ref->dpll, pin, ops, priv, parent);
}
dpll_xa_ref_pin_del(&pin->parent_refs, parent, ops, priv, pin);
mutex_unlock(&dpll_lock);
}
EXPORT_SYMBOL_GPL(dpll_pin_on_pin_unregister);

View File

@ -45,6 +45,7 @@ struct dpll_device {
* @pin_idx: index of a pin given by dev driver
* @clock_id: clock_id of creator
* @module: module of creator
* @module_name: module name of creator
* @fwnode: optional reference to firmware node
* @dpll_refs: hold referencees to dplls pin was registered with
* @parent_refs: hold references to parent pins pin was registered with
@ -59,6 +60,7 @@ struct dpll_pin {
u32 pin_idx;
u64 clock_id;
struct module *module;
char module_name[MODULE_NAME_LEN];
struct fwnode_handle *fwnode;
struct xarray dpll_refs;
struct xarray parent_refs;
@ -98,6 +100,7 @@ extern struct xarray dpll_pin_xa;
extern struct mutex dpll_lock;
void dpll_device_notify(struct dpll_device *dpll, unsigned long action);
void dpll_pin_notify(struct dpll_pin *pin, unsigned long action);
void dpll_pin_notify(struct dpll_pin *pin, u64 src_clock_id,
unsigned long action);
#endif

View File

@ -703,7 +703,7 @@ dpll_cmd_pin_get_one(struct sk_buff *msg, struct dpll_pin *pin,
if (ret)
return ret;
if (nla_put_string(msg, DPLL_A_PIN_MODULE_NAME,
module_name(pin->module)))
pin->module_name))
return -EMSGSIZE;
if (nla_put_64bit(msg, DPLL_A_PIN_CLOCK_ID, sizeof(pin->clock_id),
&pin->clock_id, DPLL_A_PIN_PAD))
@ -915,15 +915,15 @@ dpll_pin_event_send(enum dpll_cmd event, struct dpll_pin *pin)
return ret;
}
int dpll_pin_create_ntf(struct dpll_pin *pin)
int dpll_pin_create_ntf(struct dpll_pin *pin, u64 src_clock_id)
{
dpll_pin_notify(pin, DPLL_PIN_CREATED);
dpll_pin_notify(pin, src_clock_id, DPLL_PIN_CREATED);
return dpll_pin_event_send(DPLL_CMD_PIN_CREATE_NTF, pin);
}
int dpll_pin_delete_ntf(struct dpll_pin *pin)
int dpll_pin_delete_ntf(struct dpll_pin *pin, u64 src_clock_id)
{
dpll_pin_notify(pin, DPLL_PIN_DELETED);
dpll_pin_notify(pin, src_clock_id, DPLL_PIN_DELETED);
return dpll_pin_event_send(DPLL_CMD_PIN_DELETE_NTF, pin);
}
@ -938,7 +938,7 @@ int dpll_pin_delete_ntf(struct dpll_pin *pin)
int __dpll_pin_change_ntf(struct dpll_pin *pin)
{
lockdep_assert_held(&dpll_lock);
dpll_pin_notify(pin, DPLL_PIN_CHANGED);
dpll_pin_notify(pin, pin->clock_id, DPLL_PIN_CHANGED);
return dpll_pin_event_send(DPLL_CMD_PIN_CHANGE_NTF, pin);
}
EXPORT_SYMBOL_GPL(__dpll_pin_change_ntf);
@ -1325,8 +1325,11 @@ dpll_pin_on_pin_state_set(struct dpll_pin *pin, u32 parent_idx,
unsigned long i;
int ret;
/* fwnode pins may not set the capability bit upfront; let the ops
* layer return -EOPNOTSUPP if the operation is unsupported.
*/
if (!(DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE &
pin->prop.capabilities)) {
pin->prop.capabilities) && !pin->fwnode) {
NL_SET_ERR_MSG(extack, "state changing is not allowed");
return -EOPNOTSUPP;
}
@ -1361,8 +1364,11 @@ dpll_pin_state_set(struct dpll_device *dpll, struct dpll_pin *pin,
struct dpll_pin_ref *ref;
int ret;
/* fwnode pins may not set the capability bit upfront; let the ops
* layer return -EOPNOTSUPP if the operation is unsupported.
*/
if (!(DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE &
pin->prop.capabilities)) {
pin->prop.capabilities) && !pin->fwnode) {
NL_SET_ERR_MSG(extack, "state changing is not allowed");
return -EOPNOTSUPP;
}
@ -1650,9 +1656,9 @@ dpll_pin_find(u64 clock_id, struct nlattr *mod_name_attr,
xa_for_each_marked(&dpll_pin_xa, i, pin, DPLL_REGISTERED) {
prop = &pin->prop;
cid_match = clock_id ? pin->clock_id == clock_id : true;
mod_match = mod_name_attr && module_name(pin->module) ?
mod_match = mod_name_attr && pin->module_name[0] ?
!nla_strcmp(mod_name_attr,
module_name(pin->module)) : true;
pin->module_name) : true;
type_match = type ? prop->type == type : true;
board_match = board_label ? (prop->board_label ?
!nla_strcmp(board_label, prop->board_label) : false) :

View File

@ -8,6 +8,6 @@ int dpll_device_create_ntf(struct dpll_device *dpll);
int dpll_device_delete_ntf(struct dpll_device *dpll);
int dpll_pin_create_ntf(struct dpll_pin *pin);
int dpll_pin_create_ntf(struct dpll_pin *pin, u64 src_clock_id);
int dpll_pin_delete_ntf(struct dpll_pin *pin);
int dpll_pin_delete_ntf(struct dpll_pin *pin, u64 src_clock_id);

View File

@ -37,7 +37,7 @@ const struct nla_policy dpll_reference_sync_nl_policy[DPLL_A_PIN_STATE + 1] = {
static const struct nla_policy dpll_device_id_get_nl_policy[DPLL_A_TYPE + 1] = {
[DPLL_A_MODULE_NAME] = { .type = NLA_NUL_STRING, },
[DPLL_A_CLOCK_ID] = { .type = NLA_U64, },
[DPLL_A_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 2),
[DPLL_A_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 3),
};
/* DPLL_CMD_DEVICE_GET - do */

View File

@ -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-$(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

View File

@ -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_ */

View File

@ -62,6 +62,8 @@ static struct ice_adapter *ice_adapter_new(struct pci_dev *pdev)
adapter->index = ice_adapter_index(pdev);
spin_lock_init(&adapter->ptp_gltsyn_time_lock);
spin_lock_init(&adapter->txq_ctx_lock);
for (int i = 0; i < ARRAY_SIZE(adapter->cpi_phy_lock); i++)
mutex_init(&adapter->cpi_phy_lock[i]);
refcount_set(&adapter->refcount, 1);
mutex_init(&adapter->ports.lock);
@ -73,6 +75,8 @@ static struct ice_adapter *ice_adapter_new(struct pci_dev *pdev)
static void ice_adapter_free(struct ice_adapter *adapter)
{
WARN_ON(!list_empty(&adapter->ports.ports));
for (int i = 0; i < ARRAY_SIZE(adapter->cpi_phy_lock); i++)
mutex_destroy(&adapter->cpi_phy_lock[i]);
mutex_destroy(&adapter->ports.lock);
kfree(adapter);

View File

@ -5,9 +5,12 @@
#define _ICE_ADAPTER_H_
#include <linux/types.h>
#include <linux/mutex.h>
#include <linux/spinlock_types.h>
#include <linux/refcount_types.h>
#include "ice_type.h"
struct pci_dev;
struct ice_pf;
@ -31,6 +34,8 @@ struct ice_port_list {
* @ptp_gltsyn_time_lock: Spinlock protecting access to the GLTSYN_TIME
* register of the PTP clock.
* @txq_ctx_lock: Spinlock protecting access to the GLCOMM_QTX_CNTX_CTL register
* @cpi_phy_lock: Per-PHY mutex serializing CPI REQ/ACK transactions.
* Index 0 = PHY0, index 1 = PHY1. Used on E825C devices.
* @ctrl_pf: Control PF of the adapter
* @ports: Ports list
* @index: 64-bit index cached for collision detection on 32bit systems
@ -41,6 +46,8 @@ struct ice_adapter {
spinlock_t ptp_gltsyn_time_lock;
/* For access to GLCOMM_QTX_CNTX_CTL register */
spinlock_t txq_ctx_lock;
/* Serialize CPI REQ/ACK transactions per PHY (E825C only) */
struct mutex cpi_phy_lock[ICE_E825_MAX_PHYS];
struct ice_pf *ctrl_pf;
struct ice_port_list ports;

View File

@ -1169,6 +1169,8 @@ struct ice_aqc_restart_an {
u8 cmd_flags;
#define ICE_AQC_RESTART_AN_LINK_RESTART BIT(1)
#define ICE_AQC_RESTART_AN_LINK_ENABLE BIT(2)
#define ICE_AQC_RESTART_AN_REFCLK_M GENMASK(4, 3)
#define ICE_AQC_RESTART_AN_REFCLK_NOCHANGE 0
u8 reserved2[13];
};

View File

@ -4124,12 +4124,13 @@ int ice_get_link_status(struct ice_port_info *pi, bool *link_up)
* @pi: pointer to the port information structure
* @ena_link: if true: enable link, if false: disable link
* @cd: pointer to command details structure or NULL
* @refclk: the new TX reference clock, 0 if no change
*
* Sets up the link and restarts the Auto-Negotiation over the link.
*/
int
ice_aq_set_link_restart_an(struct ice_port_info *pi, bool ena_link,
struct ice_sq_cd *cd)
struct ice_sq_cd *cd, u8 refclk)
{
struct ice_aqc_restart_an *cmd;
struct libie_aq_desc desc;
@ -4145,6 +4146,8 @@ ice_aq_set_link_restart_an(struct ice_port_info *pi, bool ena_link,
else
cmd->cmd_flags &= ~ICE_AQC_RESTART_AN_LINK_ENABLE;
cmd->cmd_flags |= FIELD_PREP(ICE_AQC_RESTART_AN_REFCLK_M, refclk);
return ice_aq_send_cmd(pi->hw, &desc, NULL, 0, cd);
}

View File

@ -215,7 +215,7 @@ ice_cfg_phy_fec(struct ice_port_info *pi, struct ice_aqc_set_phy_cfg_data *cfg,
enum ice_fec_mode fec);
int
ice_aq_set_link_restart_an(struct ice_port_info *pi, bool ena_link,
struct ice_sq_cd *cd);
struct ice_sq_cd *cd, u8 refclk);
int
ice_aq_set_mac_cfg(struct ice_hw *hw, u16 max_frame_size, struct ice_sq_cd *cd);
int

View File

@ -0,0 +1,362 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (C) 2026 Intel Corporation */
#include "ice_type.h"
#include "ice_common.h"
#include "ice_ptp_hw.h"
#include "ice.h"
#include "ice_cpi.h"
/**
* ice_cpi_get_dest_dev - get destination PHY for given phy index
* @hw: pointer to the HW struct
* @phy: phy index of port the CPI action is taken on
*
* Return: sideband queue destination PHY device.
*/
static enum ice_sbq_dev_id ice_cpi_get_dest_dev(struct ice_hw *hw, u8 phy)
{
u8 curr_phy = hw->lane_num / hw->ptp.ports_per_phy;
/* In the driver, lanes 4..7 are in fact 0..3 on a second PHY.
* On a single complex E825C, PHY 0 is always destination device phy_0
* and PHY 1 is phy_0_peer.
* On dual complex E825C, device phy_0 points to PHY on a current
* complex and phy_0_peer to PHY on a different complex.
*/
if ((!ice_is_dual(hw) && phy) ||
(ice_is_dual(hw) && phy != curr_phy))
return ice_sbq_dev_phy_0_peer;
else
return ice_sbq_dev_phy_0;
}
/**
* ice_cpi_write_phy - Write a CPI port register
* @hw: pointer to the HW struct
* @phy: phy index of port the CPI action is taken on
* @addr: PHY register address
* @val: Value to write
*
* Return:
* * 0 on success
* * other error codes when failed to write to PHY
*/
static int ice_cpi_write_phy(struct ice_hw *hw, u8 phy, u32 addr, u32 val)
{
struct ice_sbq_msg_input msg = {
.dest_dev = ice_cpi_get_dest_dev(hw, phy),
.opcode = ice_sbq_msg_wr_np,
.msg_addr_low = lower_16_bits(addr),
.msg_addr_high = upper_16_bits(addr),
.data = val
};
int err;
err = ice_sbq_rw_reg(hw, &msg, LIBIE_AQ_FLAG_RD);
if (err)
ice_debug(hw, ICE_DBG_PTP,
"Failed to write CPI msg to phy %d, err: %d\n",
phy, err);
return err;
}
/**
* ice_cpi_read_phy - Read a CPI port register
* @hw: pointer to the HW struct
* @phy: phy index of port the CPI action is taken on
* @addr: PHY register address
* @val: storage for register value
*
* Return:
* * 0 on success
* * other error codes when failed to read from PHY
*/
static int ice_cpi_read_phy(struct ice_hw *hw, u8 phy, u32 addr, u32 *val)
{
struct ice_sbq_msg_input msg = {
.dest_dev = ice_cpi_get_dest_dev(hw, phy),
.opcode = ice_sbq_msg_rd,
.msg_addr_low = lower_16_bits(addr),
.msg_addr_high = upper_16_bits(addr)
};
int err;
err = ice_sbq_rw_reg(hw, &msg, LIBIE_AQ_FLAG_RD);
if (err) {
ice_debug(hw, ICE_DBG_PTP,
"Failed to read CPI msg from phy %d, err: %d\n",
phy, err);
return err;
}
*val = msg.data;
return 0;
}
/**
* ice_cpi_wait_req0_ack0 - waits for CPI interface to be available
* @hw: pointer to the HW struct
* @phy: phy index of port the CPI action is taken on
*
* This function checks if CPI interface is ready to use by CPI client.
* It's done by assuring LM.CMD.REQ and PHY.CMD.ACK bit in CPI
* interface registers to be 0.
*
* Return: 0 on success, negative on error
*/
static int ice_cpi_wait_req0_ack0(struct ice_hw *hw, int phy)
{
u32 phy_val;
u32 lm_val;
for (int i = 0; i < CPI_RETRIES_COUNT; i++) {
int err;
/* check if another CPI Client is also accessing CPI */
err = ice_cpi_read_phy(hw, phy, CPI0_LM1_CMD_DATA, &lm_val);
if (err)
return err;
if (FIELD_GET(CPI_LM_CMD_REQ_M, lm_val))
goto retry;
/* check if PHY.ACK is deasserted */
err = ice_cpi_read_phy(hw, phy, CPI0_PHY1_CMD_DATA, &phy_val);
if (err)
return err;
if (!FIELD_GET(CPI_PHY_CMD_ACK_M, phy_val))
/* req0 and ack0 at this point - ready to go */
return 0;
retry:
msleep(CPI_RETRIES_CADENCE_MS);
}
return -ETIMEDOUT;
}
/**
* ice_cpi_wait_ack - Waits for the PHY.ACK bit to be asserted/deasserted
* @hw: pointer to the HW struct
* @phy: phy index of port the CPI action is taken on
* @asserted: desired state of PHY.ACK bit
* @data: pointer to the user data where PHY.data is stored
*
* This function checks if PHY.ACK bit is asserted or deasserted, depending
* on the phase of CPI handshake. If 'asserted' state is required, PHY command
* data is stored in the 'data' storage.
*
* Return: 0 on success, negative on error
*/
static int ice_cpi_wait_ack(struct ice_hw *hw, u8 phy, bool asserted,
u32 *data)
{
u32 phy_val;
for (int i = 0; i < CPI_RETRIES_COUNT; i++) {
int err;
err = ice_cpi_read_phy(hw, phy, CPI0_PHY1_CMD_DATA, &phy_val);
if (err)
return err;
if (asserted && FIELD_GET(CPI_PHY_CMD_ERROR_M, phy_val))
return -EFAULT;
if (asserted && FIELD_GET(CPI_PHY_CMD_ACK_M, phy_val)) {
if (data)
*data = phy_val;
return 0;
}
if (!asserted && !FIELD_GET(CPI_PHY_CMD_ACK_M, phy_val))
return 0;
msleep(CPI_RETRIES_CADENCE_MS);
}
return -ETIMEDOUT;
}
#define ice_cpi_wait_ack0(hw, port) \
ice_cpi_wait_ack(hw, port, false, NULL)
#define ice_cpi_wait_ack1(hw, port, data) \
ice_cpi_wait_ack(hw, port, true, data)
/**
* ice_cpi_req0 - deasserts LM.REQ bit
* @hw: pointer to the HW struct
* @phy: phy index of port the CPI action is taken on
* @data: the command data
*
* Return: 0 on success, negative on CPI write error
*/
static int ice_cpi_req0(struct ice_hw *hw, u8 phy, u32 data)
{
data &= ~CPI_LM_CMD_REQ_M;
return ice_cpi_write_phy(hw, phy, CPI0_LM1_CMD_DATA, data);
}
/**
* ice_cpi_exec_cmd - writes command data to CPI interface
* @hw: pointer to the HW struct
* @phy: phy index of port the CPI action is taken on
* @data: the command data
*
* Return: 0 on success, otherwise negative on error
*/
static int ice_cpi_exec_cmd(struct ice_hw *hw, int phy, u32 data)
{
return ice_cpi_write_phy(hw, phy, CPI0_LM1_CMD_DATA, data);
}
/**
* ice_cpi_phy_lock - get per-PHY lock for CPI transaction serialization
* @hw: pointer to the HW struct
* @phy: PHY index
*
* Return: pointer to PHY mutex, or %NULL when context is unavailable.
*/
static struct mutex *ice_cpi_phy_lock(struct ice_hw *hw, u8 phy)
{
struct ice_pf *pf = hw->back;
if (!pf || !pf->adapter || phy >= ICE_E825_MAX_PHYS)
return NULL;
return &pf->adapter->cpi_phy_lock[phy];
}
/**
* ice_cpi_exec - executes CPI command
* @hw: pointer to the HW struct
* @phy: phy index of port the CPI action is taken on
* @cmd: pointer to the command struct to execute
* @resp: pointer to user allocated CPI response struct
*
* This function executes CPI request with respect to CPI handshake
* mechanism.
*
* Return: 0 on success, otherwise negative on error
*/
int ice_cpi_exec(struct ice_hw *hw, u8 phy,
const struct ice_cpi_cmd *cmd,
struct ice_cpi_resp *resp)
{
struct mutex *cpi_lock; /* serializes CPI transactions per PHY */
u32 phy_cmd, lm_cmd = 0;
int err, err1 = 0;
if (!cmd || !resp)
return -EINVAL;
cpi_lock = ice_cpi_phy_lock(hw, phy);
if (!cpi_lock)
return -EINVAL;
mutex_lock(cpi_lock);
lm_cmd =
FIELD_PREP(CPI_LM_CMD_REQ_M, CPI_LM_CMD_REQ) |
FIELD_PREP(CPI_LM_CMD_GET_SET_M, cmd->set) |
FIELD_PREP(CPI_LM_CMD_OPCODE_M, cmd->opcode) |
FIELD_PREP(CPI_LM_CMD_PORTLANE_M, cmd->port) |
FIELD_PREP(CPI_LM_CMD_DATA_M, cmd->data);
/* 1. Try to acquire the bus, PHY ACK should be low before we begin */
err = ice_cpi_wait_req0_ack0(hw, phy);
if (err)
goto cpi_exec_exit;
/* 2. We start the CPI request */
err = ice_cpi_exec_cmd(hw, phy, lm_cmd);
if (err)
goto cpi_deassert;
/*
* 3. Wait for CPI confirmation, PHY ACK should be asserted and opcode
* echoed in the response
*/
err = ice_cpi_wait_ack1(hw, phy, &phy_cmd);
if (err)
goto cpi_deassert;
if (FIELD_GET(CPI_LM_CMD_OPCODE_M, lm_cmd) !=
FIELD_GET(CPI_PHY_CMD_OPCODE_M, phy_cmd)) {
err = -EFAULT;
goto cpi_deassert;
}
resp->opcode = FIELD_GET(CPI_PHY_CMD_OPCODE_M, phy_cmd);
resp->data = FIELD_GET(CPI_PHY_CMD_DATA_M, phy_cmd);
resp->port = FIELD_GET(CPI_PHY_CMD_PORTLANE_M, phy_cmd);
cpi_deassert:
/* 4. We deassert REQ */
err1 = ice_cpi_req0(hw, phy, lm_cmd);
if (err1)
goto cpi_exec_exit;
/* 5. PHY ACK should be deasserted in response */
err1 = ice_cpi_wait_ack0(hw, phy);
cpi_exec_exit:
if (!err)
err = err1;
mutex_unlock(cpi_lock);
return err;
}
/**
* ice_cpi_set_cmd - execute CPI SET command
* @hw: pointer to the HW struct
* @opcode: CPI command opcode
* @phy: phy index CPI command is applied for
* @port_lane: ephy index CPI command is applied for
* @data: CPI opcode context specific data
*
* Return: 0 on success, negative error code on failure.
*/
static int ice_cpi_set_cmd(struct ice_hw *hw, u8 opcode, u8 phy, u8 port_lane,
u16 data)
{
struct ice_cpi_resp cpi_resp = {0};
struct ice_cpi_cmd cpi_cmd = {
.opcode = opcode,
.set = true,
.port = port_lane,
.data = data,
};
return ice_cpi_exec(hw, phy, &cpi_cmd, &cpi_resp);
}
/**
* ice_cpi_ena_dis_clk_ref - enables/disables Tx reference clock on port
* @hw: pointer to the HW struct
* @phy: phy index of port for which Tx reference clock is enabled/disabled
* @clk: Tx reference clock to enable or disable
* @enable: bool value to enable or disable Tx reference clock
*
* This function executes CPI request to enable or disable specific
* Tx reference clock on given PHY.
*
* Return: 0 on success, negative error code on failure.
*/
int ice_cpi_ena_dis_clk_ref(struct ice_hw *hw, u8 phy,
enum ice_e825c_ref_clk clk, bool enable)
{
u16 val;
val = FIELD_PREP(CPI_OPCODE_PHY_CLK_PHY_SEL_M, phy) |
FIELD_PREP(CPI_OPCODE_PHY_CLK_REF_CTRL_M,
enable ? CPI_OPCODE_PHY_CLK_ENABLE :
CPI_OPCODE_PHY_CLK_DISABLE) |
FIELD_PREP(CPI_OPCODE_PHY_CLK_REF_SEL_M, clk);
return ice_cpi_set_cmd(hw, CPI_OPCODE_PHY_CLK, phy, 0, val);
}

View File

@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2026 Intel Corporation */
#ifndef _ICE_CPI_H_
#define _ICE_CPI_H_
#include "ice_type.h"
#include "ice_ptp_hw.h"
#define CPI0_PHY1_CMD_DATA 0x7FD028
#define CPI0_LM1_CMD_DATA 0x7FD024
#define CPI_RETRIES_COUNT 10
#define CPI_RETRIES_CADENCE_MS 100
/* CPI PHY CMD DATA register (CPI0_PHY1_CMD_DATA) */
#define CPI_PHY_CMD_DATA_M GENMASK(15, 0)
#define CPI_PHY_CMD_OPCODE_M GENMASK(23, 16)
#define CPI_PHY_CMD_PORTLANE_M GENMASK(26, 24)
#define CPI_PHY_CMD_RSVD_M GENMASK(29, 27)
#define CPI_PHY_CMD_ERROR_M BIT(30)
#define CPI_PHY_CMD_ACK_M BIT(31)
/* CPI LM CMD DATA register (CPI0_LM1_CMD_DATA) */
#define CPI_LM_CMD_DATA_M GENMASK(15, 0)
#define CPI_LM_CMD_OPCODE_M GENMASK(23, 16)
#define CPI_LM_CMD_PORTLANE_M GENMASK(26, 24)
#define CPI_LM_CMD_RSVD_M GENMASK(28, 27)
#define CPI_LM_CMD_GET_SET_M BIT(29)
#define CPI_LM_CMD_REQ_M BIT(31)
#define CPI_OPCODE_PHY_CLK 0xF1
#define CPI_OPCODE_PHY_CLK_PHY_SEL_M GENMASK(9, 6)
#define CPI_OPCODE_PHY_CLK_REF_CTRL_M GENMASK(5, 4)
#define CPI_OPCODE_PHY_CLK_DISABLE 1
#define CPI_OPCODE_PHY_CLK_ENABLE 2
#define CPI_OPCODE_PHY_CLK_REF_SEL_M GENMASK(3, 0)
#define CPI_LM_CMD_REQ 1
struct ice_cpi_cmd {
u8 port;
u8 opcode;
u16 data;
bool set;
};
struct ice_cpi_resp {
u8 port;
u8 opcode;
u16 data;
};
int ice_cpi_exec(struct ice_hw *hw, u8 phy,
const struct ice_cpi_cmd *cmd,
struct ice_cpi_resp *resp);
int ice_cpi_ena_dis_clk_ref(struct ice_hw *hw, u8 phy,
enum ice_e825c_ref_clk clk, bool enable);
#endif /* _ICE_CPI_H_ */

View File

@ -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,6 +20,9 @@
#define ICE_DPLL_SW_PIN_INPUT_BASE_QSFP 6
#define ICE_DPLL_SW_PIN_OUTPUT_BASE 0
#define E825_RCLK_PARENT_0_PIN_IDX 0
#define E825_RCLK_PARENT_1_PIN_IDX 1
#define ICE_DPLL_PIN_SW_INPUT_ABS(in_idx) \
(ICE_DPLL_SW_PIN_INPUT_BASE_SFP + (in_idx))
@ -57,6 +61,7 @@
* @ICE_DPLL_PIN_TYPE_OUTPUT: output pin
* @ICE_DPLL_PIN_TYPE_RCLK_INPUT: recovery clock input pin
* @ICE_DPLL_PIN_TYPE_SOFTWARE: software controlled SMA/U.FL pins
* @ICE_DPLL_PIN_TYPE_TXCLK: transmit clock reference input pin
*/
enum ice_dpll_pin_type {
ICE_DPLL_PIN_INVALID,
@ -64,6 +69,7 @@ enum ice_dpll_pin_type {
ICE_DPLL_PIN_TYPE_OUTPUT,
ICE_DPLL_PIN_TYPE_RCLK_INPUT,
ICE_DPLL_PIN_TYPE_SOFTWARE,
ICE_DPLL_PIN_TYPE_TXCLK,
};
static const char * const pin_type_name[] = {
@ -71,10 +77,13 @@ static const char * const pin_type_name[] = {
[ICE_DPLL_PIN_TYPE_OUTPUT] = "output",
[ICE_DPLL_PIN_TYPE_RCLK_INPUT] = "rclk-input",
[ICE_DPLL_PIN_TYPE_SOFTWARE] = "software",
[ICE_DPLL_PIN_TYPE_TXCLK] = "txclk-input",
};
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 struct dpll_pin_frequency ice_esync_range[] = {
DPLL_PIN_FREQUENCY_RANGE(0, DPLL_PIN_FREQUENCY_1_HZ),
@ -2605,12 +2614,206 @@ 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
* @pin_priv: private data pointer passed on pin registration
* @dpll: registered dpll pointer
* @dpll_priv: private data pointer passed on dpll registration
* @state: state to be set on pin
* @extack: error reporting
*
* 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
ice_dpll_txclk_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_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.
*/
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;
}
/**
* ice_dpll_txclk_state_on_dpll_get - get a state of Tx clk reference pin
* @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 pin
* @extack: error reporting
*
* 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,
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;
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;
}
static const struct dpll_pin_ops ice_dpll_rclk_ops = {
.state_on_pin_set = ice_dpll_rclk_state_on_pin_set,
.state_on_pin_get = ice_dpll_rclk_state_on_pin_get,
.direction_get = ice_dpll_input_direction,
};
static const struct dpll_pin_ops ice_dpll_txclk_ops = {
.state_on_dpll_set = ice_dpll_txclk_state_on_dpll_set,
.state_on_dpll_get = ice_dpll_txclk_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,
@ -3135,9 +3338,13 @@ ice_dpll_unregister_pins(struct dpll_device *dpll, struct ice_dpll_pin *pins,
{
int i;
for (i = 0; i < count; i++)
if (!pins[i].hidden)
dpll_pin_unregister(dpll, pins[i].pin, ops, &pins[i]);
for (i = 0; i < count; i++) {
if (pins[i].hidden)
continue;
if (IS_ERR_OR_NULL(pins[i].pin))
continue;
dpll_pin_unregister(dpll, pins[i].pin, ops, &pins[i]);
}
}
/**
@ -3311,19 +3518,42 @@ static bool ice_dpll_is_fwnode_pin(struct ice_dpll_pin *pin)
return !IS_ERR_OR_NULL(pin->fwnode);
}
static bool ice_dpll_fwnode_eq(const struct fwnode_handle *a,
const struct fwnode_handle *b)
{
return a && a == b;
}
static void ice_dpll_pin_notify_work(struct work_struct *work)
{
struct ice_dpll_pin_work *w = container_of(work,
struct ice_dpll_pin_work,
work);
struct ice_dpll_pin *pin, *parent = w->pin;
bool is_tx_synce_parent = false;
struct ice_pf *pf = parent->pf;
bool is_rclk_parent = false;
int ret;
wait_for_completion(&pf->dplls.dpll_init);
if (!test_bit(ICE_FLAG_DPLL, pf->flags))
goto out; /* DPLL initialization failed */
/* Decide which parent we are handling, defensively checking FWNs */
for (int i = 0; i < pf->dplls.rclk.num_parents; i++) {
if (ice_dpll_fwnode_eq(parent->fwnode,
pf->dplls.inputs[i].fwnode)) {
is_rclk_parent = true;
break;
}
}
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)
goto out;
switch (w->action) {
case DPLL_PIN_CREATED:
if (!IS_ERR_OR_NULL(parent->pin)) {
@ -3340,16 +3570,28 @@ static void ice_dpll_pin_notify_work(struct work_struct *work)
goto out;
}
/* Register rclk pin */
pin = &pf->dplls.rclk;
ret = dpll_pin_on_pin_register(parent->pin, pin->pin,
&ice_dpll_rclk_ops, pin);
if (ret) {
dev_err(ice_pf_to_dev(pf),
"Failed to register pin: %pe\n", ERR_PTR(ret));
dpll_pin_put(parent->pin, &parent->tracker);
parent->pin = NULL;
goto out;
if (is_rclk_parent) {
/* Register rclk pin via on-pin relationship */
pin = &pf->dplls.rclk;
ret = dpll_pin_on_pin_register(parent->pin, pin->pin,
&ice_dpll_rclk_ops, pin);
if (ret) {
dev_err(ice_pf_to_dev(pf),
"RCLK pin register failed: %pe\n",
ERR_PTR(ret));
goto drop_parent_ref;
}
} else if (is_tx_synce_parent) {
/* Register TX-CLK SYNCE pin directly to TXC DPLL */
pin = &pf->dplls.txclks[E825_EXT_SYNCE_PIN_IDX];
ret = dpll_pin_register(pf->dplls.txc.dpll, pin->pin,
&ice_dpll_txclk_ops, pin);
if (ret) {
dev_err(ice_pf_to_dev(pf),
"TX SYNCE pin register failed: %pe\n",
ERR_PTR(ret));
goto drop_parent_ref;
}
}
break;
case DPLL_PIN_DELETED:
@ -3358,11 +3600,18 @@ static void ice_dpll_pin_notify_work(struct work_struct *work)
goto out;
}
/* Unregister rclk pin */
pin = &pf->dplls.rclk;
dpll_pin_on_pin_unregister(parent->pin, pin->pin,
&ice_dpll_rclk_ops, pin);
if (is_rclk_parent) {
/* Unregister rclk pin */
pin = &pf->dplls.rclk;
dpll_pin_on_pin_unregister(parent->pin, pin->pin,
&ice_dpll_rclk_ops, pin);
} else if (is_tx_synce_parent) {
/* Unregister TX-CLK SYNCE pin from TXC DPLL */
pin = &pf->dplls.txclks[E825_EXT_SYNCE_PIN_IDX];
dpll_pin_unregister(pf->dplls.txc.dpll, pin->pin,
&ice_dpll_txclk_ops, pin);
}
drop_parent_ref:
/* Drop fwnode pin reference */
dpll_pin_put(parent->pin, &parent->tracker);
parent->pin = NULL;
@ -3388,6 +3637,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.
*/
if (info->src_clock_id == pin->pf->dplls.clock_id)
return NOTIFY_DONE;
work = kzalloc_obj(*work);
if (!work)
return NOTIFY_DONE;
@ -3490,10 +3745,16 @@ ice_dpll_init_rclk_pin(struct ice_pf *pf, int start_idx,
}
static void
ice_dpll_deinit_fwnode_pin(struct ice_dpll_pin *pin)
ice_dpll_stop_fwnode_pin_activity(struct ice_dpll_pin *pin, bool flush)
{
unregister_dpll_notifier(&pin->nb);
flush_workqueue(pin->pf->dplls.wq);
if (flush)
flush_workqueue(pin->pf->dplls.wq);
}
static void
ice_dpll_release_fwnode_pin(struct ice_dpll_pin *pin)
{
if (!IS_ERR_OR_NULL(pin->pin)) {
dpll_pin_put(pin->pin, &pin->tracker);
pin->pin = NULL;
@ -3502,6 +3763,13 @@ ice_dpll_deinit_fwnode_pin(struct ice_dpll_pin *pin)
pin->fwnode = NULL;
}
static void
ice_dpll_deinit_fwnode_pin(struct ice_dpll_pin *pin)
{
ice_dpll_stop_fwnode_pin_activity(pin, true);
ice_dpll_release_fwnode_pin(pin);
}
static void
ice_dpll_deinit_fwnode_pins(struct ice_pf *pf, struct ice_dpll_pin *pins,
int start_idx)
@ -3513,6 +3781,26 @@ 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)
{
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_unregister_pins(dt->dpll, pf->dplls.txclks,
&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;
}
/**
* ice_dpll_deinit_pins - deinitialize direct pins
* @pf: board private structure
@ -3532,8 +3820,10 @@ static void ice_dpll_deinit_pins(struct ice_pf *pf, bool cgu)
struct ice_dpll *dp = &d->pps;
ice_dpll_deinit_rclk_pin(pf);
if (pf->hw.mac_type == ICE_MAC_GENERIC_3K_E825)
if (pf->hw.mac_type == ICE_MAC_GENERIC_3K_E825) {
ice_dpll_deinit_txclk_pins(pf);
ice_dpll_deinit_fwnode_pins(pf, pf->dplls.inputs, 0);
}
if (cgu) {
ice_dpll_unregister_pins(dp->dpll, inputs, &ice_dpll_input_ops,
num_inputs);
@ -3656,6 +3946,11 @@ ice_dpll_init_fwnode_pins(struct ice_pf *pf, struct ice_dpll_pin *pins,
return 0;
error:
/*
* A notifier worker may already be queued and blocked on dpll_init;
* release it so the per-pin flush below does not deadlock.
*/
complete_all(&pf->dplls.dpll_init);
while (i--)
ice_dpll_deinit_fwnode_pin(&pins[start_idx + i]);
@ -3664,6 +3959,64 @@ ice_dpll_init_fwnode_pins(struct ice_pf *pf, struct ice_dpll_pin *pins,
return ret;
}
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;
int ret;
/* Configure EXT_EREF0 pin */
ret = ice_dpll_get_pins(pf, ref_pin, start_idx, 1, pf->dplls.clock_id);
if (ret)
return ret;
ret = dpll_pin_register(txc->dpll, ref_pin->pin, &ice_dpll_txclk_ops,
ref_pin);
if (ret)
goto err_release_ext_eref;
/*
* Configure EXT_SYNCE pin (fwnode-backed).
* The pin may not yet be available; in that case registration
* will be deferred via the notifier path.
*/
ref_pin++;
ret = ice_dpll_init_fwnode_pin(ref_pin, ice_dpll_fwnode_ext_synce);
if (ret)
goto err_unregister_ext_eref;
if (IS_ERR_OR_NULL(ref_pin->pin)) {
dev_dbg(ice_pf_to_dev(pf),
"Tx-clk SYNCE pin not registered yet\n");
return 0;
}
ret = dpll_pin_register(txc->dpll, ref_pin->pin, &ice_dpll_txclk_ops,
ref_pin);
if (ret)
goto err_deinit_synce;
return 0;
err_deinit_synce:
/*
* Avoid deadlock against notifier workers blocked on dpll_init.
* The outer init error path will complete dpll_init and flush the
* shared workqueue before destroying it.
*/
ice_dpll_stop_fwnode_pin_activity(ref_pin, false);
ice_dpll_release_fwnode_pin(ref_pin);
err_unregister_ext_eref:
dpll_pin_unregister(txc->dpll,
pf->dplls.txclks[E825_EXT_EREF_PIN_IDX].pin,
&ice_dpll_txclk_ops,
&pf->dplls.txclks[E825_EXT_EREF_PIN_IDX]);
err_release_ext_eref:
ice_dpll_release_pins(&pf->dplls.txclks[E825_EXT_EREF_PIN_IDX], 1);
return ret;
}
/**
* ice_dpll_init_pins_e825 - init pins and register pins with a dplls
* @pf: board private structure
@ -3686,6 +4039,15 @@ static int ice_dpll_init_pins_e825(struct ice_pf *pf)
ret = ice_dpll_init_rclk_pin(pf, DPLL_PIN_IDX_UNSPEC,
&ice_dpll_rclk_ops);
if (ret)
goto unregister_pins;
ret = ice_dpll_init_txclk_pins(pf, 0);
if (ret)
ice_dpll_deinit_rclk_pin(pf);
unregister_pins:
if (ret) {
/* Inform DPLL notifier works that DPLL init was finished
* unsuccessfully (ICE_DPLL_FLAG not set).
@ -3804,7 +4166,7 @@ static int ice_dpll_init_pins(struct ice_pf *pf, bool cgu)
static void
ice_dpll_deinit_dpll(struct ice_pf *pf, struct ice_dpll *d, bool cgu)
{
if (cgu)
if (cgu || pf->hw.mac_type == ICE_MAC_GENERIC_3K_E825)
dpll_device_unregister(d->dpll, d->ops, d);
dpll_device_put(d->dpll, &d->tracker);
}
@ -3839,12 +4201,13 @@ ice_dpll_init_dpll(struct ice_pf *pf, struct ice_dpll *d, bool cgu,
return ret;
}
d->pf = pf;
if (cgu) {
if (cgu || pf->hw.mac_type == ICE_MAC_GENERIC_3K_E825) {
const struct dpll_device_ops *ops = &ice_dpll_ops;
if (type == DPLL_TYPE_PPS && ice_dpll_is_pps_phase_monitor(pf))
ops = &ice_dpll_pom_ops;
ice_dpll_update_state(pf, d, true);
if (cgu)
ice_dpll_update_state(pf, d, true);
ret = dpll_device_register(d->dpll, type, ops, d);
if (ret) {
dpll_device_put(d->dpll, &d->tracker);
@ -4210,6 +4573,36 @@ static int ice_dpll_init_info_sw_pins(struct ice_pf *pf)
return 0;
}
/**
* ice_dpll_init_info_txclk_pins_e825c - initializes tx-clk pins information
* @pf: board private structure
*
* Init information for tx-clks pin, cache them in pf->dplls.txclks
*
* Return:
* * 0 - success
*/
static int ice_dpll_init_info_txclk_pins_e825c(struct ice_pf *pf)
{
struct ice_dpll_pin *tx_pin;
for (int i = 0; i < ICE_DPLL_TXCLK_NUM_MAX; i++) {
tx_pin = &pf->dplls.txclks[i];
tx_pin->prop.type = DPLL_PIN_TYPE_EXT;
tx_pin->prop.capabilities |=
DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;
tx_pin->pf = pf;
if (i == E825_EXT_EREF_PIN_IDX) {
tx_pin->prop.board_label = ice_dpll_ext_eref_pin;
tx_pin->tx_ref_src = ICE_REF_CLK_EREF0;
} else if (i == E825_EXT_SYNCE_PIN_IDX) {
tx_pin->tx_ref_src = ICE_REF_CLK_SYNCE;
}
}
return 0;
}
/**
* ice_dpll_init_pins_info - init pins info wrapper
* @pf: board private structure
@ -4235,6 +4628,9 @@ ice_dpll_init_pins_info(struct ice_pf *pf, enum ice_dpll_pin_type pin_type)
return ice_dpll_init_info_rclk_pin(pf);
case ICE_DPLL_PIN_TYPE_SOFTWARE:
return ice_dpll_init_info_sw_pins(pf);
case ICE_DPLL_PIN_TYPE_TXCLK:
return ice_dpll_init_info_txclk_pins_e825c(pf);
default:
return -EINVAL;
}
@ -4268,11 +4664,15 @@ static void ice_dpll_deinit_info(struct ice_pf *pf)
static int ice_dpll_init_info_e825c(struct ice_pf *pf)
{
struct ice_dplls *d = &pf->dplls;
struct ice_dpll *dt = &d->txc;
int ret = 0;
int i;
d->clock_id = ice_generate_clock_id(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;
d->inputs = kzalloc_objs(*d->inputs, d->num_inputs);
if (!d->inputs)
@ -4289,6 +4689,11 @@ static int ice_dpll_init_info_e825c(struct ice_pf *pf)
ret = ice_dpll_init_pins_info(pf, ICE_DPLL_PIN_TYPE_RCLK_INPUT);
if (ret)
goto deinit_info;
ret = ice_dpll_init_pins_info(pf, ICE_DPLL_PIN_TYPE_TXCLK);
if (ret)
goto deinit_info;
dev_dbg(ice_pf_to_dev(pf),
"%s - success, inputs: %u, outputs: %u, rclk-parents: %u\n",
__func__, d->num_inputs, d->num_outputs, d->rclk.num_parents);
@ -4412,15 +4817,38 @@ 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);
if (!IS_ERR_OR_NULL(pf->dplls.eec.dpll))
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);
ice_dpll_deinit_info(pf);
mutex_destroy(&pf->dplls.lock);
}
@ -4440,20 +4868,42 @@ static void ice_dpll_init_e825(struct ice_pf *pf)
struct ice_dplls *d = &pf->dplls;
int err;
/* E825 sets ICE_F_PHY_RCLK unconditionally, so DPLL may run without
* PTP. Populate the HW-topology fields the TX-clk path divides by,
* otherwise userspace can trigger div-by-zero in ice_txclk_set_clk().
* When PTP is supported, ice_ptp_init() handles this.
*/
if (!test_bit(ICE_FLAG_PTP_SUPPORTED, pf->flags)) {
ice_ptp_init_hw(&pf->hw);
if (pf->hw.lane_num >= 0)
pf->ptp.port.port_num = pf->hw.lane_num;
}
mutex_init(&d->lock);
/* Initialize the txclk worker and its notification rwsem before any
* code path can fail: ice_dpll_deinit() runs unconditionally on
* failure and calls cancel_work_sync() / down_write() on these.
*/
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);
if (err)
goto err_exit;
err = ice_dpll_init_pins_e825(pf);
err = ice_dpll_init_dpll(pf, &pf->dplls.txc, false, DPLL_TYPE_GENERIC);
if (err)
goto deinit_info;
err = ice_dpll_init_pins_e825(pf);
if (err)
goto deinit_txclk;
set_bit(ICE_FLAG_DPLL, pf->flags);
complete_all(&d->dpll_init);
return;
deinit_txclk:
ice_dpll_deinit_dpll(pf, &pf->dplls.txc, false);
deinit_info:
ice_dpll_deinit_info(pf);
err_exit:

View File

@ -7,6 +7,9 @@
#include "ice.h"
#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)
@ -79,6 +82,7 @@ struct ice_dpll_pin {
u8 ref_sync;
bool active;
bool hidden;
enum ice_e825c_ref_clk tx_ref_src;
};
/** ice_dpll - store info required for DPLL control
@ -124,12 +128,15 @@ 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
* @inputs: input pins pointer
* @outputs: output pins pointer
* @rclk: recovered pins pointer
* @txclks: TX clock reference pins pointer
* @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
@ -138,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;
@ -147,11 +176,13 @@ struct ice_dplls {
struct completion dpll_init;
struct ice_dpll eec;
struct ice_dpll pps;
struct ice_dpll txc;
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];
u8 num_inputs;
u8 num_outputs;
u8 sma_data;
@ -162,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)

View File

@ -3769,7 +3769,8 @@ int ice_set_link(struct ice_vsi *vsi, bool ena)
if (vsi->type != ICE_VSI_PF)
return -EINVAL;
status = ice_aq_set_link_restart_an(pi, ena, NULL);
status = ice_aq_set_link_restart_an(pi, ena, NULL,
ICE_AQC_RESTART_AN_REFCLK_NOCHANGE);
/* if link is owned by manageability, FW will return LIBIE_AQ_RC_EMODE.
* this is not a fatal error, so print a warning message and return

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -265,6 +265,13 @@ struct ice_cgu_pin_desc {
struct dpll_pin_frequency *freq_supp;
};
enum ice_e825c_ref_clk {
ICE_REF_CLK_ENET,
ICE_REF_CLK_SYNCE,
ICE_REF_CLK_EREF0,
ICE_REF_CLK_MAX,
};
#define E810C_QSFP_C827_0_HANDLE 2
#define E810C_QSFP_C827_1_HANDLE 3
@ -376,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
@ -788,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_ */

View File

@ -54,8 +54,9 @@ enum ice_sbq_dev_id {
};
enum ice_sbq_msg_opcode {
ice_sbq_msg_rd = 0x00,
ice_sbq_msg_wr = 0x01
ice_sbq_msg_rd = 0x00,
ice_sbq_msg_wr = 0x01,
ice_sbq_msg_wr_np = 0x02
};
#define ICE_SBQ_MSG_FLAGS 0x40

View File

@ -0,0 +1,354 @@
// 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 perPHY 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);
}

View File

@ -0,0 +1,40 @@
/* 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_ */

View File

@ -893,6 +893,8 @@ struct ice_ptp_hw {
u8 ports_per_phy;
};
#define ICE_E825_MAX_PHYS 2
/* Port hardware description */
struct ice_hw {
u8 __iomem *hw_addr;

View File

@ -233,6 +233,7 @@ struct dpll_pin_notifier_info {
u64 clock_id;
const struct fwnode_handle *fwnode;
const struct dpll_pin_properties *prop;
u64 src_clock_id;
};
#if IS_ENABLED(CONFIG_DPLL)

View File

@ -109,10 +109,12 @@ enum dpll_clock_quality_level {
* enum dpll_type - type of dpll, valid values for DPLL_A_TYPE attribute
* @DPLL_TYPE_PPS: dpll produces Pulse-Per-Second signal
* @DPLL_TYPE_EEC: dpll drives the Ethernet Equipment Clock
* @DPLL_TYPE_GENERIC: generic dpll type for devices outside PPS/EEC classes
*/
enum dpll_type {
DPLL_TYPE_PPS = 1,
DPLL_TYPE_EEC,
DPLL_TYPE_GENERIC,
/* private: */
__DPLL_TYPE_MAX,