Merge branch 'dpll-add-nco-pin-type-and-zl3073x-support'

Ivan Vecera says:

====================
dpll: add NCO pin type and zl3073x support

Add a new DPLL_PIN_TYPE_INT_NCO pin type for virtual pins representing
the NCO mode of a DPLL and implement support for it in the zl3073x driver.

Patch 1 adds a STATE_CONNECTED_OVERRIDE pin capability for pins that
can override input selection in any DPLL mode, with a WARN_ON check
in dpll_pin_register() that STATE_CAN_CHANGE is also set.

Patch 2 adds the new INT_NCO pin type to the DPLL netlink spec and
UAPI header.

Patch 3 replaces the single 2s poll timeout with per-operation timeouts
based on Microchip proprietary source code and own measurement.

Patch 4 adds a per-DPLL serialization mutex taken by all DPLL callbacks
and the periodic worker, establishing a single lock that protects all
per-channel state. The chan_state_update() call is moved under this lock.

Patch 5 adds a virtual NCO input pin to the zl3073x driver that allows
userspace to switch a DPLL channel into NCO mode. The pin reports
connected/active state when the channel is in NCO mode and handles
the hardware-specific details of mode transitions including automatic
df_offset capture and 1PPS phase preservation.

Link: https://lore.kernel.org/netdev/20260531194423.383366-1-ivecera@redhat.com/
====================

Link: https://patch.msgid.link/20260630125536.720717-1-ivecera@redhat.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni 2026-07-06 14:45:06 +02:00
commit 474cff6868
12 changed files with 678 additions and 106 deletions

View File

@ -91,6 +91,13 @@ following pin states:
- ``DPLL_PIN_STATE_DISCONNECTED`` - the pin shall be not considered as
a valid input for automatic selection algorithm
Pins that have the ``DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE``
capability can additionally be set to ``DPLL_PIN_STATE_CONNECTED`` in
automatic mode, overriding the active input selection. This is useful
for automatic-only DPLL devices where mode cannot be switched to manual.
When such a pin is disconnected, the device returns to automatic input
selection.
The actual hardware status of a pin is reported via the operational
state (``DPLL_A_PIN_OPERSTATE``) attribute nested under the parent
device:

View File

@ -165,6 +165,13 @@ definitions:
-
name: gnss
doc: GNSS recovered clock
-
name: int-nco
doc: |
Device internal numerically controlled oscillator.
When connected as a DPLL input, the DPLL enters NCO mode
where the output frequency is adjusted by the host via
the PTP clock interface.
render-max: true
-
type: enum
@ -252,6 +259,12 @@ definitions:
-
name: state-can-change
doc: pin state can be changed
-
name: state-connected-override
doc: |
pin state can be set to connected regardless of current
DPLL device mode, overriding the active input selection.
Requires state-can-change to be set as well.
-
type: const
name: phase-offset-divider
@ -456,6 +469,9 @@ attribute-sets:
offset on the media associated with the pin. Inside
the pin-parent-device nest it represents the frequency
offset between the pin and its parent DPLL device.
For pins of type PIN_TYPE_INT_NCO this represents
the DPLL's current output frequency offset from its
nominal frequency.
Value is in PPM (parts per million).
This is a lower-precision version of
fractional-frequency-offset-ppt.
@ -502,6 +518,9 @@ attribute-sets:
offset on the media associated with the pin. Inside
the pin-parent-device nest it represents the frequency
offset between the pin and its parent DPLL device.
For pins of type PIN_TYPE_INT_NCO this represents
the DPLL's current output frequency offset from its
nominal frequency.
Value is in PPT (parts per trillion, 10^-12).
This is a higher-precision version of
fractional-frequency-offset.

View File

@ -884,7 +884,11 @@ dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin,
WARN_ON(ops->measured_freq_get &&
(!dpll_device_ops(dpll)->freq_monitor_get ||
!dpll_device_ops(dpll)->freq_monitor_set)) ||
WARN_ON(ops->supported_ffo && !ops->ffo_get))
WARN_ON(ops->supported_ffo && !ops->ffo_get) ||
WARN_ON((pin->prop.capabilities &
DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE) &&
!(pin->prop.capabilities &
DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE)))
return -EINVAL;
mutex_lock(&dpll_lock);

View File

@ -61,7 +61,7 @@ static const struct nla_policy dpll_pin_id_get_nl_policy[DPLL_A_PIN_TYPE + 1] =
[DPLL_A_PIN_BOARD_LABEL] = { .type = NLA_NUL_STRING, },
[DPLL_A_PIN_PANEL_LABEL] = { .type = NLA_NUL_STRING, },
[DPLL_A_PIN_PACKAGE_LABEL] = { .type = NLA_NUL_STRING, },
[DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 5),
[DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 6),
};
/* DPLL_CMD_PIN_GET - do */

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/string.h>
#include <linux/types.h>
@ -31,9 +32,18 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
if (rc)
return rc;
/* Read df_offset vs tracked reference */
/* Read df_offset only when locked to a reference. In NCO mode
* df_offset was captured at entry by nco_mode_set() - preserve it.
*/
if (!zl3073x_chan_is_locked(chan)) {
if (!zl3073x_chan_mode_is_nco(chan))
chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
return 0;
}
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
ZL_DPLL_DF_READ_SEM);
ZL_DPLL_DF_READ_SEM,
ZL_POLL_DF_READ_TIMEOUT_US);
if (rc)
return rc;
@ -43,7 +53,8 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
return rc;
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
ZL_DPLL_DF_READ_SEM);
ZL_DPLL_DF_READ_SEM,
ZL_POLL_DF_READ_TIMEOUT_US);
if (rc)
return rc;
@ -56,6 +67,96 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
return 0;
}
/**
* zl3073x_chan_nco_mode_set - switch DPLL channel to NCO mode
* @zldev: pointer to zl3073x_dev structure
* @index: DPLL channel index
*
* Switches the channel to NCO mode and reads the df_offset
* auto-captured by nco_auto_read directly from the register.
* No DF_READ handshake is needed as nco_auto_read populates
* the register before the mode switch completes.
*
* Return: 0 on success, <0 on error
*/
int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index)
{
struct zl3073x_chan *chan = &zldev->chan[index];
u8 prev_mode, df_read;
u64 val;
int rc;
prev_mode = zl3073x_chan_mode_get(chan);
/* nco_auto_read captures the tracking offset at NCO entry only
* from reflock, auto or holdover mode. From freerun the captured
* value is not meaningful.
*/
if (prev_mode == ZL_DPLL_MODE_REFSEL_MODE_FREERUN) {
zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO);
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
chan->mode_refsel);
if (rc) {
zl3073x_chan_mode_set(chan, prev_mode);
return rc;
}
chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
return 0;
}
/* Configure df_read for nco_auto_read:
* ref_ofst=0 - reads offset relative to master clock (not input ref)
* cmd=CMD_ACC_I - accumulated I-part covering both locked and
* holdover entry.
*
* No semaphore is set - this only configures what the df_offset
* value represents after the mode switch; nco_auto_read performs
* the actual read automatically.
*/
df_read = FIELD_PREP(ZL_DPLL_DF_READ_REF_OFST, 0) |
FIELD_PREP(ZL_DPLL_DF_READ_CMD, ZL_DPLL_DF_READ_CMD_ACC_I);
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index), df_read);
if (rc)
return rc;
/* Wait for df_read configuration to take effect before
* triggering nco_auto_read via mode switch. The worst-case
* internal register update time is 25 ms.
*/
fsleep(25000);
zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO);
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
chan->mode_refsel);
if (rc) {
zl3073x_chan_mode_set(chan, prev_mode);
return rc;
}
/* Wait for nco_auto_read to populate df_offset. The worst-case
* internal register update time is 25 ms.
*/
fsleep(25000);
/* Read df_offset captured by nco_auto_read during mode switch.
* No DF_READ semaphore handshake needed. Mode switch already
* succeeded, so don't propagate a read failure back to userspace.
*/
rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val);
if (rc) {
dev_warn(zldev->dev,
"Failed to read DPLL%u df_offset: %pe\n",
index, ERR_PTR(rc));
chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
} else {
chan->df_offset = sign_extend64(val, 47);
}
return 0;
}
/**
* zl3073x_chan_state_fetch - fetch DPLL channel state from hardware
* @zldev: pointer to zl3073x_dev structure
@ -71,6 +172,10 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
struct zl3073x_chan *chan = &zldev->chan[index];
int rc, i;
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_CTRL(index), &chan->ctrl);
if (rc)
return rc;
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
&chan->mode_refsel);
if (rc)
@ -83,6 +188,13 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
if (rc)
return rc;
/* If firmware left the channel in NCO mode, mark df_offset as
* unknown - we cannot know whether the preconditions for a valid
* nco_auto_read capture were met.
*/
if (zl3073x_chan_mode_is_nco(chan))
chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
dev_dbg(zldev->dev,
"DPLL%u lock_state: %u, ho: %u, sel_state: %u, sel_ref: %u\n",
index, zl3073x_chan_lock_state_get(chan),
@ -145,7 +257,15 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg)))
return 0;
/* Direct register write for mode_refsel */
/* Direct register writes for ctrl and mode_refsel */
if (dchan->ctrl != chan->ctrl) {
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_CTRL(index),
chan->ctrl);
if (rc)
return rc;
dchan->ctrl = chan->ctrl;
}
if (dchan->mode_refsel != chan->mode_refsel) {
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
chan->mode_refsel);

View File

@ -13,6 +13,7 @@ struct zl3073x_dev;
/**
* struct zl3073x_chan - DPLL channel state
* @ctrl: DPLL control register value
* @mode_refsel: mode and reference selection register value
* @ref_prio: reference priority registers (4 bits per ref, P/N packed)
* @mon_status: monitor status register value
@ -21,6 +22,7 @@ struct zl3073x_dev;
*/
struct zl3073x_chan {
struct_group(cfg,
u8 ctrl;
u8 mode_refsel;
u8 ref_prio[ZL3073X_NUM_REFS / 2];
);
@ -38,6 +40,7 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_chan *chan);
int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);
int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index);
/**
* zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference
@ -152,6 +155,51 @@ static inline u8 zl3073x_chan_lock_state_get(const struct zl3073x_chan *chan)
return FIELD_GET(ZL_DPLL_MON_STATUS_STATE, chan->mon_status);
}
/**
* zl3073x_chan_is_locked - check if channel is locked to a reference
* @chan: pointer to channel state
*
* Return: true if channel is locked, false otherwise
*/
static inline bool zl3073x_chan_is_locked(const struct zl3073x_chan *chan)
{
u8 lock_state = zl3073x_chan_lock_state_get(chan);
return lock_state == ZL_DPLL_MON_STATUS_STATE_LOCK;
}
/**
* zl3073x_chan_mode_is_auto - check if channel is in automatic mode
* @chan: pointer to channel state
*
* Return: true if channel is in automatic mode, false otherwise
*/
static inline bool zl3073x_chan_mode_is_auto(const struct zl3073x_chan *chan)
{
return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO;
}
/**
* zl3073x_chan_mode_is_nco - check if channel is in NCO mode
* @chan: pointer to channel state
*
* Return: true if channel is in NCO mode, false otherwise
*/
static inline bool zl3073x_chan_mode_is_nco(const struct zl3073x_chan *chan)
{
return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO;
}
/**
* zl3073x_chan_mode_is_reflock - check if channel is in reflock mode
* @chan: pointer to channel state
*
* Return: true if channel is in reflock mode, false otherwise
*/
static inline bool zl3073x_chan_mode_is_reflock(const struct zl3073x_chan *chan)
{
return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_REFLOCK;
}
/**
* zl3073x_chan_is_ho_ready - check if holdover is ready
* @chan: pointer to channel state

View File

@ -311,17 +311,17 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val)
* @zldev: zl3073x device pointer
* @reg: register to poll (has to be 8bit register)
* @mask: bit mask for polling
* @timeout_us: timeout in microseconds
*
* Waits for bits specified by @mask in register @reg value to be cleared
* by the device.
*
* Returns: 0 on success, <0 on error
*/
int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask)
int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
u8 mask, unsigned int timeout_us)
{
/* Register polling sleep & timeout */
#define ZL_POLL_SLEEP_US 10
#define ZL_POLL_TIMEOUT_US 2000000
#define ZL_POLL_SLEEP_US 10
unsigned int val;
/* Check the register is 8bit */
@ -335,7 +335,7 @@ int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask)
reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask),
ZL_POLL_SLEEP_US, ZL_POLL_TIMEOUT_US);
ZL_POLL_SLEEP_US, timeout_us);
}
int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
@ -354,7 +354,8 @@ int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
return rc;
/* Wait for the operation to actually finish */
return zl3073x_poll_zero_u8(zldev, op_reg, op_val);
return zl3073x_poll_zero_u8(zldev, op_reg, op_val,
ZL_POLL_MB_TIMEOUT_US);
}
/**
@ -377,8 +378,8 @@ zl3073x_do_hwreg_op(struct zl3073x_dev *zldev, u8 op)
return rc;
/* Poll for completion - pending bit cleared */
return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP,
ZL_HWREG_OP_PENDING);
return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP, ZL_HWREG_OP_PENDING,
ZL_POLL_HWREG_TIMEOUT_US);
}
/**
@ -566,19 +567,7 @@ zl3073x_dev_ref_states_update(struct zl3073x_dev *zldev)
}
}
static void
zl3073x_dev_chan_states_update(struct zl3073x_dev *zldev)
{
int i, rc;
for (i = 0; i < zldev->info->num_channels; i++) {
rc = zl3073x_chan_state_update(zldev, i);
if (rc)
dev_warn(zldev->dev,
"Failed to get DPLL%u state: %pe\n", i,
ERR_PTR(rc));
}
}
/**
* zl3073x_ref_phase_offsets_update - update reference phase offsets
@ -609,7 +598,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
* to be zero to ensure that the measured data are coherent.
*/
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
ZL_REF_PHASE_ERR_READ_RQST_RD);
ZL_REF_PHASE_ERR_READ_RQST_RD,
ZL_POLL_PHASE_ERR_TIMEOUT_US);
if (rc)
return rc;
@ -628,7 +618,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
/* Wait for finish */
return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
ZL_REF_PHASE_ERR_READ_RQST_RD);
ZL_REF_PHASE_ERR_READ_RQST_RD,
ZL_POLL_PHASE_ERR_TIMEOUT_US);
}
/**
@ -648,7 +639,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type)
/* Wait for previous measurement to finish */
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
ZL_REF_FREQ_MEAS_CTRL);
ZL_REF_FREQ_MEAS_CTRL,
ZL_POLL_FREQ_MEAS_TIMEOUT_US);
if (rc)
return rc;
@ -669,7 +661,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type)
/* Wait for finish */
return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
ZL_REF_FREQ_MEAS_CTRL);
ZL_REF_FREQ_MEAS_CTRL,
ZL_POLL_FREQ_MEAS_TIMEOUT_US);
}
/**
@ -715,9 +708,6 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
/* Update input references' states */
zl3073x_dev_ref_states_update(zldev);
/* Update DPLL channels' states */
zl3073x_dev_chan_states_update(zldev);
/* Update DPLL-to-connected-ref phase offsets registers */
rc = zl3073x_ref_phase_offsets_update(zldev, -1);
if (rc)
@ -727,7 +717,7 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
/* Update measured input reference frequencies if frequency
* monitoring is enabled.
*/
if (zldev->freq_monitor) {
if (READ_ONCE(zldev->freq_monitor)) {
rc = zl3073x_ref_freq_meas_update(zldev);
if (rc)
dev_warn(zldev->dev,
@ -763,7 +753,7 @@ int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor)
return rc;
/* Save the new factor */
zldev->phase_avg_factor = factor;
WRITE_ONCE(zldev->phase_avg_factor, factor);
return 0;
}

View File

@ -7,6 +7,7 @@
#include <linux/kthread.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/time64.h>
#include <linux/types.h>
#include "chan.h"
@ -19,6 +20,12 @@ struct device;
struct regmap;
struct zl3073x_dpll;
/* Per-operation poll timeouts */
#define ZL_POLL_DF_READ_TIMEOUT_US (25 * USEC_PER_MSEC)
#define ZL_POLL_FREQ_MEAS_TIMEOUT_US (50 * USEC_PER_MSEC)
#define ZL_POLL_HWREG_TIMEOUT_US (50 * USEC_PER_MSEC)
#define ZL_POLL_MB_TIMEOUT_US (30 * USEC_PER_MSEC)
#define ZL_POLL_PHASE_ERR_TIMEOUT_US (50 * USEC_PER_MSEC)
enum zl3073x_flags {
ZL3073X_FLAG_REF_PHASE_COMP_32_BIT,
@ -94,7 +101,7 @@ void zl3073x_dev_stop(struct zl3073x_dev *zldev);
static inline u8 zl3073x_dev_phase_avg_factor_get(struct zl3073x_dev *zldev)
{
return zldev->phase_avg_factor;
return READ_ONCE(zldev->phase_avg_factor);
}
int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor);
@ -127,7 +134,8 @@ struct zl3073x_hwreg_seq_item {
int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
unsigned int mask_reg, u16 mask_val);
int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask);
int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
u8 mask, unsigned int timeout_us);
int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val);
int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val);
int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val);

View File

@ -1,6 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/atomic.h>
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/bug.h>
@ -58,7 +57,7 @@ struct zl3073x_dpll_pin {
s32 phase_gran;
enum dpll_pin_operstate operstate;
s64 phase_offset;
atomic64_t freq_offset;
s64 freq_offset;
u32 measured_freq;
};
@ -81,6 +80,18 @@ zl3073x_dpll_is_input_pin(struct zl3073x_dpll_pin *pin)
return pin->dir == DPLL_PIN_DIRECTION_INPUT;
}
/**
* zl3073x_dpll_is_nco_pin - check if the pin is a virtual NCO pin
* @pin: pin to check
*
* Return: true if pin is a virtual NCO pin, false otherwise.
*/
static bool
zl3073x_dpll_is_nco_pin(struct zl3073x_dpll_pin *pin)
{
return pin->id == ZL3073X_NCO_PIN_ID;
}
/**
* zl3073x_dpll_is_p_pin - check if the pin is P-pin
* @pin: pin to check
@ -120,6 +131,19 @@ zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id)
return NULL;
}
static struct zl3073x_dpll_pin *
zl3073x_dpll_nco_pin_get(struct zl3073x_dpll *zldpll)
{
struct zl3073x_dpll_pin *pin;
list_for_each_entry(pin, &zldpll->pins, list) {
if (zl3073x_dpll_is_nco_pin(pin))
return pin;
}
return NULL;
}
static int
zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
@ -134,6 +158,8 @@ zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin,
const struct zl3073x_ref *ref;
u8 ref_id;
guard(mutex)(&zldpll->lock);
ref_id = zl3073x_input_pin_ref_get(pin->id);
ref = zl3073x_ref_state_get(zldev, ref_id);
@ -170,6 +196,8 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin,
struct zl3073x_ref ref;
u8 ref_id, sync_mode;
guard(mutex)(&zldpll->lock);
ref_id = zl3073x_input_pin_ref_get(pin->id);
ref = *zl3073x_ref_state_get(zldev, ref_id);
@ -205,6 +233,8 @@ zl3073x_dpll_input_pin_ref_sync_get(const struct dpll_pin *dpll_pin,
const struct zl3073x_ref *ref;
u8 ref_id, mode, pair;
guard(mutex)(&zldpll->lock);
ref_id = zl3073x_input_pin_ref_get(pin->id);
ref = zl3073x_ref_state_get(zldev, ref_id);
mode = zl3073x_ref_sync_mode_get(ref);
@ -236,6 +266,8 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin,
struct zl3073x_ref ref;
int rc;
guard(mutex)(&zldpll->lock);
ref_id = zl3073x_input_pin_ref_get(pin->id);
sync_ref_id = zl3073x_input_pin_ref_get(sync_pin->id);
ref = *zl3073x_ref_state_get(zldev, ref_id);
@ -299,12 +331,15 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
struct dpll_ffo_param *ffo,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
guard(mutex)(&zldpll->lock);
if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
return -ENODATA;
ffo->ffo = atomic64_read(&pin->freq_offset);
ffo->ffo = pin->freq_offset;
return 0;
}
@ -316,8 +351,11 @@ zl3073x_dpll_input_pin_measured_freq_get(const struct dpll_pin *dpll_pin,
void *dpll_priv, u64 *measured_freq,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
guard(mutex)(&zldpll->lock);
*measured_freq = pin->measured_freq;
*measured_freq *= DPLL_PIN_MEASURED_FREQUENCY_DIVIDER;
@ -335,6 +373,8 @@ zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin,
struct zl3073x_dpll_pin *pin = pin_priv;
u8 ref_id;
guard(mutex)(&zldpll->lock);
ref_id = zl3073x_input_pin_ref_get(pin->id);
*frequency = zl3073x_dev_ref_freq_get(zldpll->dev, ref_id);
@ -354,6 +394,8 @@ zl3073x_dpll_input_pin_frequency_set(const struct dpll_pin *dpll_pin,
struct zl3073x_ref ref;
u8 ref_id;
guard(mutex)(&zldpll->lock);
/* Get reference state */
ref_id = zl3073x_input_pin_ref_get(pin->id);
ref = *zl3073x_ref_state_get(zldev, ref_id);
@ -402,6 +444,8 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
u8 conn_id, ref_id;
s64 ref_phase;
guard(mutex)(&zldpll->lock);
/* Get currently connected reference */
conn_id = zl3073x_dpll_connected_ref_get(zldpll);
@ -459,6 +503,8 @@ zl3073x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
s64 phase_comp;
u8 ref_id;
guard(mutex)(&zldpll->lock);
/* Read reference configuration */
ref_id = zl3073x_input_pin_ref_get(pin->id);
ref = zl3073x_ref_state_get(zldev, ref_id);
@ -491,6 +537,8 @@ zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
struct zl3073x_ref ref;
u8 ref_id;
guard(mutex)(&zldpll->lock);
/* Read reference configuration */
ref_id = zl3073x_input_pin_ref_get(pin->id);
ref = *zl3073x_ref_state_get(zldev, ref_id);
@ -524,6 +572,8 @@ zl3073x_dpll_ref_operstate_get(struct zl3073x_dpll_pin *pin,
const struct zl3073x_ref *ref;
u8 ref_id;
lockdep_assert_held(&zldpll->lock);
ref_id = zl3073x_input_pin_ref_get(pin->id);
/* Check if this pin is the currently locked reference */
@ -557,6 +607,8 @@ zl3073x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
const struct zl3073x_chan *chan;
u8 mode, ref;
guard(mutex)(&zldpll->lock);
chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
ref = zl3073x_input_pin_ref_get(pin->id);
mode = zl3073x_chan_mode_get(chan);
@ -590,8 +642,11 @@ zl3073x_dpll_input_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin,
enum dpll_pin_operstate *operstate,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
guard(mutex)(&zldpll->lock);
return zl3073x_dpll_ref_operstate_get(pin, operstate);
}
@ -605,9 +660,12 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
{
struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
struct zl3073x_dpll_pin *nco_pin = NULL;
struct zl3073x_chan chan;
u8 mode, ref;
int rc;
int rc = 0;
mutex_lock(&zldpll->lock);
chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
ref = zl3073x_input_pin_ref_get(pin->id);
@ -634,6 +692,10 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
goto invalid_state;
}
break;
case ZL_DPLL_MODE_REFSEL_MODE_NCO:
if (state == DPLL_PIN_STATE_CONNECTED)
nco_pin = zl3073x_dpll_nco_pin_get(zldpll);
fallthrough;
case ZL_DPLL_MODE_REFSEL_MODE_FREERUN:
case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER:
if (state == DPLL_PIN_STATE_CONNECTED) {
@ -649,13 +711,13 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
case ZL_DPLL_MODE_REFSEL_MODE_AUTO:
if (state == DPLL_PIN_STATE_SELECTABLE) {
if (zl3073x_chan_ref_is_selectable(&chan, ref))
return 0; /* Pin is already selectable */
goto unlock; /* Pin is already selectable */
/* Restore pin priority in HW */
zl3073x_chan_ref_prio_set(&chan, ref, pin->prio);
} else if (state == DPLL_PIN_STATE_DISCONNECTED) {
if (!zl3073x_chan_ref_is_selectable(&chan, ref))
return 0; /* Pin is already disconnected */
goto unlock; /* Pin is already disconnected */
/* Set pin priority to none in HW */
zl3073x_chan_ref_prio_set(&chan, ref,
@ -668,18 +730,27 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
/* In other modes we cannot change input reference */
NL_SET_ERR_MSG(extack,
"Pin state cannot be changed in current mode");
return -EOPNOTSUPP;
rc = -EOPNOTSUPP;
goto unlock;
}
/* Commit DPLL channel changes */
rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
if (rc)
return rc;
goto unlock;
return 0;
invalid_state:
NL_SET_ERR_MSG_MOD(extack, "Invalid pin state for this device mode");
return -EINVAL;
rc = -EINVAL;
unlock:
mutex_unlock(&zldpll->lock);
/* If leaving NCO mode, notify userspace about the NCO pin
* state change - the periodic worker skips the NCO pin.
*/
if (!rc && nco_pin)
__dpll_pin_change_ntf(nco_pin->dpll_pin);
return rc;
}
static int
@ -687,8 +758,11 @@ zl3073x_dpll_input_pin_prio_get(const struct dpll_pin *dpll_pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
u32 *prio, struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
guard(mutex)(&zldpll->lock);
*prio = pin->prio;
return 0;
@ -705,6 +779,8 @@ zl3073x_dpll_input_pin_prio_set(const struct dpll_pin *dpll_pin, void *pin_priv,
u8 ref;
int rc;
guard(mutex)(&zldpll->lock);
if (prio > ZL_DPLL_REF_PRIO_MAX)
return -EINVAL;
@ -740,6 +816,8 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin,
u32 synth_freq, out_freq;
u8 out_id;
guard(mutex)(&zldpll->lock);
out_id = zl3073x_output_pin_out_get(pin->id);
out = zl3073x_out_state_get(zldev, out_id);
@ -797,6 +875,8 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
u32 synth_freq;
u8 out_id;
guard(mutex)(&zldpll->lock);
out_id = zl3073x_output_pin_out_get(pin->id);
out = *zl3073x_out_state_get(zldev, out_id);
@ -817,7 +897,7 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
/* If esync is being disabled just write mailbox and finish */
if (!freq)
goto write_mailbox;
return zl3073x_out_state_set(zldev, out_id, &out);
/* Get attached synth frequency */
synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out));
@ -834,7 +914,6 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
*/
out.esync_n_width = out.div / 2;
write_mailbox:
/* Commit output configuration */
return zl3073x_out_state_set(zldev, out_id, &out);
}
@ -849,6 +928,8 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin,
struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
guard(mutex)(&zldpll->lock);
*frequency = zl3073x_dev_output_pin_freq_get(zldpll->dev, pin->id);
return 0;
@ -869,6 +950,8 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
struct zl3073x_out out;
u8 out_id;
guard(mutex)(&zldpll->lock);
out_id = zl3073x_output_pin_out_get(pin->id);
out = *zl3073x_out_state_get(zldev, out_id);
@ -942,6 +1025,8 @@ zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
const struct zl3073x_out *out;
u8 out_id;
guard(mutex)(&zldpll->lock);
out_id = zl3073x_output_pin_out_get(pin->id);
out = zl3073x_out_state_get(zldev, out_id);
@ -965,6 +1050,8 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
struct zl3073x_out out;
u8 out_id;
guard(mutex)(&zldpll->lock);
out_id = zl3073x_output_pin_out_get(pin->id);
out = *zl3073x_out_state_get(zldev, out_id);
@ -989,6 +1076,144 @@ zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
return 0;
}
static int
zl3073x_dpll_nco_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
const struct dpll_device *dpll,
void *dpll_priv,
enum dpll_pin_operstate *operstate,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
const struct zl3073x_chan *chan;
guard(mutex)(&zldpll->lock);
chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
if (zl3073x_chan_mode_is_nco(chan))
*operstate = DPLL_PIN_OPERSTATE_ACTIVE;
else
*operstate = DPLL_PIN_OPERSTATE_STANDBY;
return 0;
}
static int
zl3073x_dpll_nco_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
const struct dpll_device *dpll,
void *dpll_priv,
enum dpll_pin_state *state,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
const struct zl3073x_chan *chan;
guard(mutex)(&zldpll->lock);
chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
if (zl3073x_chan_mode_is_nco(chan))
*state = DPLL_PIN_STATE_CONNECTED;
else
*state = DPLL_PIN_STATE_DISCONNECTED;
return 0;
}
static int
zl3073x_dpll_nco_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
void *pin_priv,
const struct dpll_device *dpll,
void *dpll_priv,
enum dpll_pin_state state,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll_pin *ref_pin = NULL;
struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_chan chan;
u8 ref;
int rc;
mutex_lock(&zldpll->lock);
chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
switch (state) {
case DPLL_PIN_STATE_CONNECTED:
if (zl3073x_chan_mode_is_nco(&chan)) {
mutex_unlock(&zldpll->lock);
return 0;
}
if (zl3073x_chan_mode_is_auto(&chan)) {
NL_SET_ERR_MSG(extack,
"NCO pin cannot be connected in automatic mode");
mutex_unlock(&zldpll->lock);
return -EINVAL;
}
if (zl3073x_chan_mode_is_reflock(&chan)) {
/* Get currently connected pin */
ref = zl3073x_chan_ref_get(&chan);
ref_pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref);
}
rc = zl3073x_chan_nco_mode_set(zldpll->dev, zldpll->id);
break;
case DPLL_PIN_STATE_DISCONNECTED:
if (!zl3073x_chan_mode_is_nco(&chan)) {
mutex_unlock(&zldpll->lock);
return 0;
}
/* Switch to freerun - holdover averaging was not
* updated during NCO mode.
*/
zl3073x_chan_mode_set(&chan,
ZL_DPLL_MODE_REFSEL_MODE_FREERUN);
rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
break;
default:
NL_SET_ERR_MSG(extack, "invalid pin state for NCO pin");
mutex_unlock(&zldpll->lock);
return -EINVAL;
}
mutex_unlock(&zldpll->lock);
if (!rc && ref_pin)
__dpll_pin_change_ntf(ref_pin->dpll_pin);
return rc;
}
static int
zl3073x_dpll_nco_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
struct dpll_ffo_param *ffo,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
const struct zl3073x_chan *chan;
s64 df_offset;
guard(mutex)(&zldpll->lock);
chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
if (!zl3073x_chan_mode_is_nco(chan))
return -ENODATA;
/* Do not report FFO if a failure occurred during switching to NCO. */
df_offset = zl3073x_chan_df_offset_get(chan);
if (df_offset == ZL_DPLL_DF_OFFSET_UNKNOWN)
return -ENODATA;
/* dpll_df_offset register has inverted sign per datasheet:
* f_offset = f_nom * (-df_offset) / 2^48
* NCO pin reports DPLL output offset from nominal, so negate.
* Convert to PPT: ppt = -df * 5^12 / 2^36
*/
ffo->ffo = -mul_s64_u64_shr(df_offset, 244140625, 36);
return 0;
}
static int
zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv,
s32 *temp, struct netlink_ext_ack *extack)
@ -998,6 +1223,8 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv,
u16 val;
int rc;
guard(mutex)(&zldpll->lock);
rc = zl3073x_read_u16(zldev, ZL_REG_DIE_TEMP_STATUS, &val);
if (rc)
return rc;
@ -1009,14 +1236,13 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv,
}
static int
zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
enum dpll_lock_status *status,
enum dpll_lock_status_error *status_error,
struct netlink_ext_ack *extack)
__zl3073x_dpll_lock_status_get(struct zl3073x_dpll *zldpll,
enum dpll_lock_status *status)
{
struct zl3073x_dpll *zldpll = dpll_priv;
const struct zl3073x_chan *chan;
lockdep_assert_held(&zldpll->lock);
chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
switch (zl3073x_chan_mode_get(chan)) {
@ -1052,24 +1278,25 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
return 0;
}
static int
zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
enum dpll_lock_status *status,
enum dpll_lock_status_error *status_error,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
guard(mutex)(&zldpll->lock);
return __zl3073x_dpll_lock_status_get(zldpll, status);
}
static int
zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll,
void *dpll_priv, unsigned long *modes,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
const struct zl3073x_chan *chan;
chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
/* We support switching between automatic and manual mode, except in
* a case where the DPLL channel is configured to run in NCO mode.
* In this case, report only the manual mode to which the NCO is mapped
* as the only supported one.
*/
if (zl3073x_chan_mode_get(chan) != ZL_DPLL_MODE_REFSEL_MODE_NCO)
__set_bit(DPLL_MODE_AUTOMATIC, modes);
__set_bit(DPLL_MODE_AUTOMATIC, modes);
__set_bit(DPLL_MODE_MANUAL, modes);
return 0;
@ -1082,6 +1309,8 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
struct zl3073x_dpll *zldpll = dpll_priv;
const struct zl3073x_chan *chan;
guard(mutex)(&zldpll->lock);
chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
switch (zl3073x_chan_mode_get(chan)) {
@ -1138,8 +1367,8 @@ zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll,
return rc;
}
/* The averaging factor is common for all DPLL channels so after change
* we have to send a notification for other DPLL devices.
/* The averaging factor is common for all DPLL channels so after
* change we have to send a notification for other DPLL devices.
*/
list_for_each_entry(item, &zldpll->dev->dplls, list) {
struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev);
@ -1156,10 +1385,13 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
enum dpll_mode mode, struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *nco_pin = NULL;
struct zl3073x_chan chan;
u8 hw_mode, ref;
int rc;
mutex_lock(&zldpll->lock);
chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
ref = zl3073x_chan_refsel_ref_get(&chan);
@ -1179,6 +1411,9 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
else
hw_mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER;
} else {
if (zl3073x_chan_mode_is_nco(&chan))
nco_pin = zl3073x_dpll_nco_pin_get(zldpll);
/* We are switching from manual to automatic mode:
* - if there is a valid reference selected then ensure that
* it is selectable after switch to automatic mode
@ -1207,9 +1442,18 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
if (rc) {
NL_SET_ERR_MSG_MOD(extack,
"failed to set reference selection mode");
mutex_unlock(&zldpll->lock);
return rc;
}
mutex_unlock(&zldpll->lock);
/* If leaving NCO mode, notify userspace about the NCO pin
* state change - the periodic worker skips the NCO pin.
*/
if (nco_pin)
__dpll_pin_change_ntf(nco_pin->dpll_pin);
return 0;
}
@ -1221,6 +1465,8 @@ zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll,
{
struct zl3073x_dpll *zldpll = dpll_priv;
guard(mutex)(&zldpll->lock);
if (zldpll->phase_monitor)
*state = DPLL_FEATURE_STATE_ENABLE;
else
@ -1237,6 +1483,8 @@ zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll,
{
struct zl3073x_dpll *zldpll = dpll_priv;
guard(mutex)(&zldpll->lock);
zldpll->phase_monitor = (state == DPLL_FEATURE_STATE_ENABLE);
return 0;
@ -1250,7 +1498,7 @@ zl3073x_dpll_freq_monitor_get(const struct dpll_device *dpll,
{
struct zl3073x_dpll *zldpll = dpll_priv;
if (zldpll->dev->freq_monitor)
if (READ_ONCE(zldpll->dev->freq_monitor))
*state = DPLL_FEATURE_STATE_ENABLE;
else
*state = DPLL_FEATURE_STATE_DISABLE;
@ -1265,13 +1513,14 @@ zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *item, *zldpll = dpll_priv;
struct zl3073x_dev *zldev = zldpll->dev;
zldpll->dev->freq_monitor = (state == DPLL_FEATURE_STATE_ENABLE);
WRITE_ONCE(zldev->freq_monitor, state == DPLL_FEATURE_STATE_ENABLE);
/* The frequency monitoring is common for all DPLL channels so after
* change we have to send a notification for other DPLL devices.
*/
list_for_each_entry(item, &zldpll->dev->dplls, list) {
list_for_each_entry(item, &zldev->dplls, list) {
struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev);
if (item != zldpll && dpll_dev)
@ -1313,6 +1562,15 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
.state_on_dpll_get = zl3073x_dpll_output_pin_state_on_dpll_get,
};
static const struct dpll_pin_ops zl3073x_dpll_nco_pin_ops = {
.supported_ffo = BIT(DPLL_FFO_PIN_DEVICE),
.direction_get = zl3073x_dpll_pin_direction_get,
.ffo_get = zl3073x_dpll_nco_pin_ffo_get,
.operstate_on_dpll_get = zl3073x_dpll_nco_pin_operstate_on_dpll_get,
.state_on_dpll_get = zl3073x_dpll_nco_pin_state_on_dpll_get,
.state_on_dpll_set = zl3073x_dpll_nco_pin_state_on_dpll_set,
};
static const struct dpll_device_ops zl3073x_dpll_device_ops = {
.lock_status_get = zl3073x_dpll_lock_status_get,
.mode_get = zl3073x_dpll_mode_get,
@ -1460,7 +1718,9 @@ zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin)
WARN(!pin->dpll_pin, "DPLL pin is not registered\n");
if (zl3073x_dpll_is_input_pin(pin))
if (zl3073x_dpll_is_nco_pin(pin))
ops = &zl3073x_dpll_nco_pin_ops;
else if (zl3073x_dpll_is_input_pin(pin))
ops = &zl3073x_dpll_input_pin_ops;
else
ops = &zl3073x_dpll_output_pin_ops;
@ -1513,20 +1773,13 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll,
enum dpll_pin_direction dir, u8 index)
{
struct zl3073x_dev *zldev = zldpll->dev;
const struct zl3073x_chan *chan;
bool is_diff, is_enabled;
const char *name;
chan = zl3073x_chan_state_get(zldev, zldpll->id);
if (dir == DPLL_PIN_DIRECTION_INPUT) {
u8 ref_id = zl3073x_input_pin_ref_get(index);
const struct zl3073x_ref *ref;
/* Skip the pin if the DPLL is running in NCO mode */
if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO)
return false;
name = "REF";
ref = zl3073x_ref_state_get(zldev, ref_id);
is_diff = zl3073x_ref_is_diff(ref);
@ -1567,6 +1820,66 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll,
return true;
}
static const struct dpll_pin_properties zl3073x_dpll_nco_pin_props = {
.type = DPLL_PIN_TYPE_INT_NCO,
.package_label = "NCO",
.capabilities = DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE,
};
static int
zl3073x_dpll_nco_pin_register(struct zl3073x_dpll *zldpll)
{
struct zl3073x_dpll_pin *pin;
struct zl3073x_chan chan;
int rc;
/* Ensure that ctrl bits are configured for NCO operation:
* - nco_auto_read: auto-capture tracking offset on NCO entry
* - tod_step_reset: apply negated ToD step on NCO exit
* - tie_clear: PPS DPLLs re-align outputs on NCO exit
*/
mutex_lock(&zldpll->lock);
chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
FIELD_MODIFY(ZL_DPLL_CTRL_NCO_AUTO_READ, &chan.ctrl, 1);
FIELD_MODIFY(ZL_DPLL_CTRL_TOD_STEP_RST, &chan.ctrl, 1);
FIELD_MODIFY(ZL_DPLL_CTRL_TIE_CLEAR, &chan.ctrl,
zldpll->type == DPLL_TYPE_PPS ? 1 : 0);
rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
mutex_unlock(&zldpll->lock);
if (rc)
return rc;
pin = zl3073x_dpll_pin_alloc(zldpll, DPLL_PIN_DIRECTION_INPUT,
ZL3073X_NCO_PIN_ID);
if (IS_ERR(pin))
return PTR_ERR(pin);
pin->dpll_pin = dpll_pin_get(zldpll->dev->clock_id, ZL3073X_NCO_PIN_ID,
THIS_MODULE, &zl3073x_dpll_nco_pin_props,
&pin->tracker);
if (IS_ERR(pin->dpll_pin)) {
rc = PTR_ERR(pin->dpll_pin);
goto err_pin_get;
}
rc = dpll_pin_register(zldpll->dpll_dev, pin->dpll_pin,
&zl3073x_dpll_nco_pin_ops, pin);
if (rc)
goto err_register;
list_add(&pin->list, &zldpll->pins);
return 0;
err_register:
dpll_pin_put(pin->dpll_pin, &pin->tracker);
err_pin_get:
pin->dpll_pin = NULL;
kfree(pin);
return rc;
}
/**
* zl3073x_dpll_pins_register - register all registerable DPLL pins
* @zldpll: pointer to zl3073x_dpll structure
@ -1614,6 +1927,11 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll)
list_add(&pin->list, &zldpll->pins);
}
/* Register NCO virtual input pin */
rc = zl3073x_dpll_nco_pin_register(zldpll);
if (rc)
goto error;
return 0;
error:
@ -1649,8 +1967,8 @@ zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll)
return rc;
}
rc = dpll_device_register(zldpll->dpll_dev,
zl3073x_prop_dpll_type_get(zldev, zldpll->id),
zldpll->type = zl3073x_prop_dpll_type_get(zldev, zldpll->id);
rc = dpll_device_register(zldpll->dpll_dev, zldpll->type,
&zldpll->ops, zldpll);
if (rc) {
dpll_device_put(zldpll->dpll_dev, &zldpll->tracker);
@ -1697,6 +2015,8 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
u8 ref_id;
int rc;
lockdep_assert_held(&zldpll->lock);
/* No phase offset if the ref monitor reports signal errors */
ref_id = zl3073x_input_pin_ref_get(pin->id);
if (!zl3073x_dev_ref_is_status_ok(zldev, ref_id))
@ -1753,16 +2073,27 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
const struct zl3073x_chan *chan;
s64 ffo;
lockdep_assert_held(&zldpll->lock);
if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
return false;
chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
if (zl3073x_chan_df_offset_get(chan) == ZL_DPLL_DF_OFFSET_UNKNOWN)
return false;
/* dpll_df_offset register has inverted sign per datasheet:
* f_offset = f_nom * (-df_offset) / 2^48
* Input pin FFO is pin-vs-DPLL (opposite of DPLL-vs-reference),
* so the two inversions cancel out: ppt = df * 5^12 / 2^36
*/
ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan),
244140625, 36);
if (atomic64_xchg(&pin->freq_offset, ffo) != ffo) {
if (pin->freq_offset != ffo) {
dev_dbg(zldev->dev, "%s freq offset changed to: %lld\n",
pin->label, ffo);
pin->freq_offset = ffo;
return true;
}
@ -1787,7 +2118,9 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin)
u8 ref_id;
u32 freq;
if (!zldpll->dev->freq_monitor)
lockdep_assert_held(&zldpll->lock);
if (!READ_ONCE(zldpll->dev->freq_monitor))
return false;
ref_id = zl3073x_input_pin_ref_get(pin->id);
@ -1817,27 +2150,37 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin)
void
zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
{
DECLARE_BITMAP(changed_pins, ZL3073X_NUM_INPUT_PINS);
struct zl3073x_dev *zldev = zldpll->dev;
enum dpll_lock_status lock_status;
struct device *dev = zldev->dev;
struct zl3073x_dpll_pin *pin;
bool dev_changed = false;
int rc;
bitmap_zero(changed_pins, ZL3073X_NUM_INPUT_PINS);
mutex_lock(&zldpll->lock);
zldpll->check_count++;
/* Get current lock status for the DPLL */
rc = zl3073x_dpll_lock_status_get(zldpll->dpll_dev, zldpll,
&lock_status, NULL, NULL);
rc = zl3073x_chan_state_update(zldev, zldpll->id);
if (rc) {
dev_err(dev, "Failed to get DPLL%u state: %pe\n",
zldpll->id, ERR_PTR(rc));
goto unlock;
}
rc = __zl3073x_dpll_lock_status_get(zldpll, &lock_status);
if (rc) {
dev_err(dev, "Failed to get DPLL%u lock status: %pe\n",
zldpll->id, ERR_PTR(rc));
return;
goto unlock;
}
/* If lock status was changed then notify DPLL core */
if (zldpll->lock_status != lock_status) {
zldpll->lock_status = lock_status;
dpll_device_change_ntf(zldpll->dpll_dev);
dev_changed = true;
}
/* Update phase offset latch registers for this DPLL if the phase
@ -1849,18 +2192,16 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
dev_err(zldev->dev,
"Failed to update phase offsets: %pe\n",
ERR_PTR(rc));
return;
goto unlock;
}
}
list_for_each_entry(pin, &zldpll->pins, list) {
enum dpll_pin_operstate operstate;
bool pin_changed = false;
/* Output pins change checks are not necessary because output
* states are constant.
*/
if (!zl3073x_dpll_is_input_pin(pin))
/* Only physical input pins need monitoring */
if (!zl3073x_dpll_is_input_pin(pin) ||
zl3073x_dpll_is_nco_pin(pin))
continue;
rc = zl3073x_dpll_ref_operstate_get(pin, &operstate);
@ -1868,31 +2209,41 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
dev_err(dev,
"Failed to get %s on DPLL%u oper state: %pe\n",
pin->label, zldpll->id, ERR_PTR(rc));
return;
goto unlock;
}
if (operstate != pin->operstate) {
dev_dbg(dev, "%s oper state changed: %u->%u\n",
pin->label, pin->operstate, operstate);
pin->operstate = operstate;
pin_changed = true;
set_bit(pin->id, changed_pins);
}
/* Check for phase offset, ffo, and measured freq change
* once per second.
*/
if (zldpll->check_count % 2 == 0) {
if (zl3073x_dpll_pin_phase_offset_check(pin))
pin_changed = true;
set_bit(pin->id, changed_pins);
if (zl3073x_dpll_pin_ffo_check(pin))
pin_changed = true;
set_bit(pin->id, changed_pins);
if (zl3073x_dpll_pin_measured_freq_check(pin))
pin_changed = true;
set_bit(pin->id, changed_pins);
}
}
if (pin_changed)
unlock:
mutex_unlock(&zldpll->lock);
/* Send notifications outside the lock to avoid ABBA deadlock
* with dpll_lock taken by notification functions.
*/
if (dev_changed)
dpll_device_change_ntf(zldpll->dpll_dev);
list_for_each_entry(pin, &zldpll->pins, list) {
if (zl3073x_dpll_is_input_pin(pin) &&
!zl3073x_dpll_is_nco_pin(pin) &&
test_bit(pin->id, changed_pins))
dpll_pin_change_ntf(pin->dpll_pin);
}
}
@ -1949,6 +2300,7 @@ zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
zldpll->dev = zldev;
zldpll->id = ch;
mutex_init(&zldpll->lock);
INIT_LIST_HEAD(&zldpll->pins);
return zldpll;
@ -1965,6 +2317,7 @@ zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
{
WARN(zldpll->dpll_dev, "DPLL device is still registered\n");
mutex_destroy(&zldpll->lock);
kfree(zldpll);
}

View File

@ -18,6 +18,8 @@
* @ops: DPLL device operations for this instance
* @dpll_dev: pointer to registered DPLL device
* @tracker: tracking object for the acquired reference
* @lock: per-DPLL mutex serializing all operations
* @type: DPLL type (PPS or EEC)
* @lock_status: last saved DPLL lock status
* @pins: list of pins
*/
@ -30,6 +32,8 @@ struct zl3073x_dpll {
struct dpll_device_ops ops;
struct dpll_device *dpll_dev;
dpll_tracker tracker;
struct mutex lock;
enum dpll_type type;
enum dpll_lock_status lock_status;
struct list_head pins;
};

View File

@ -5,6 +5,7 @@
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/limits.h>
/*
* Hardware limits for ZL3073x chip family
@ -17,6 +18,7 @@
#define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2)
#define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \
ZL3073X_NUM_OUTPUT_PINS)
#define ZL3073X_NCO_PIN_ID ZL3073X_NUM_PINS
/*
* Register address structure:
@ -164,10 +166,18 @@
#define ZL_DPLL_MODE_REFSEL_MODE_NCO 4
#define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4)
#define ZL_REG_DPLL_CTRL(_idx) \
ZL_REG_IDX(_idx, 5, 0x05, 1, ZL3073X_MAX_CHANNELS, 4)
#define ZL_DPLL_CTRL_TIE_CLEAR BIT(0)
#define ZL_DPLL_CTRL_TOD_STEP_RST BIT(2)
#define ZL_DPLL_CTRL_NCO_AUTO_READ BIT(7)
#define ZL_REG_DPLL_DF_READ(_idx) \
ZL_REG_IDX(_idx, 5, 0x28, 1, ZL3073X_MAX_CHANNELS, 1)
#define ZL_DPLL_DF_READ_SEM BIT(4)
#define ZL_DPLL_DF_READ_REF_OFST BIT(3)
#define ZL_DPLL_DF_READ_CMD GENMASK(2, 0)
#define ZL_DPLL_DF_READ_CMD_ACC_I 4
#define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1)
#define ZL_DPLL_MEAS_CTRL_EN BIT(0)
@ -190,6 +200,7 @@
#define ZL_REG_DPLL_DF_OFFSET_4 ZL_REG(7, 0x00, 6)
#define ZL_REG_DPLL_DF_OFFSET(_idx) \
((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4)
#define ZL_DPLL_DF_OFFSET_UNKNOWN S64_MIN
/***********************************
* Register Page 9, Synth and Output

View File

@ -129,6 +129,9 @@ enum dpll_type {
* @DPLL_PIN_TYPE_SYNCE_ETH_PORT: ethernet port PHY's recovered clock
* @DPLL_PIN_TYPE_INT_OSCILLATOR: device internal oscillator
* @DPLL_PIN_TYPE_GNSS: GNSS recovered clock
* @DPLL_PIN_TYPE_INT_NCO: Device internal numerically controlled oscillator.
* When connected as a DPLL input, the DPLL enters NCO mode where the output
* frequency is adjusted by the host via the PTP clock interface.
*/
enum dpll_pin_type {
DPLL_PIN_TYPE_MUX = 1,
@ -136,6 +139,7 @@ enum dpll_pin_type {
DPLL_PIN_TYPE_SYNCE_ETH_PORT,
DPLL_PIN_TYPE_INT_OSCILLATOR,
DPLL_PIN_TYPE_GNSS,
DPLL_PIN_TYPE_INT_NCO,
/* private: */
__DPLL_PIN_TYPE_MAX,
@ -208,11 +212,15 @@ enum dpll_pin_operstate {
* @DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE: pin direction can be changed
* @DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE: pin priority can be changed
* @DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE: pin state can be changed
* @DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE: pin state can be set to
* connected regardless of current DPLL device mode, overriding the active
* input selection. Requires state-can-change to be set as well.
*/
enum dpll_pin_capabilities {
DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE = 1,
DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE = 2,
DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE = 4,
DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE = 8,
};
#define DPLL_PHASE_OFFSET_DIVIDER 1000