mirror of
https://github.com/torvalds/linux.git
synced 2026-07-28 01:55:51 +02:00
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:
commit
0969af4b64
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) :
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
362
drivers/net/ethernet/intel/ice/ice_cpi.c
Normal file
362
drivers/net/ethernet/intel/ice/ice_cpi.c
Normal 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);
|
||||
}
|
||||
|
||||
58
drivers/net/ethernet/intel/ice/ice_cpi.h
Normal file
58
drivers/net/ethernet/intel/ice/ice_cpi.h
Normal 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_ */
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
354
drivers/net/ethernet/intel/ice/ice_txclk.c
Normal file
354
drivers/net/ethernet/intel/ice/ice_txclk.c
Normal 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 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);
|
||||
}
|
||||
40
drivers/net/ethernet/intel/ice/ice_txclk.h
Normal file
40
drivers/net/ethernet/intel/ice/ice_txclk.h
Normal 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_ */
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user