Merge branch 'dpll-zl3073x-add-ptp-clock-support'

Ivan Vecera says:

====================
dpll: zl3073x: add PTP clock support

Add PTP hardware clock support to the zl3073x DPLL driver.

Patch 1 scales the poll sleep interval in zl3073x_poll_zero_u8()
proportionally to the timeout to avoid excessive bus traffic for
the longer PTP-related timeouts.

Patch 2 adds low-level channel operations for ToD read/write/adjust,
output phase step, delta frequency offset write and TIE write as
building blocks for PTP callbacks.

Patch 3 registers a PTP clock device for each DPLL channel with
gettimex64, settime64, adjtime, adjfine, adjphase and getmaxphase
callbacks. Callback availability adapts to the current channel
state - adjfine requires NCO pin connected, adjphase uses TIE write
when tracking a reference, and adjtime selects the appropriate
mechanism automatically. Periodic output support will be added in a
follow-up series.
====================

Link: https://patch.msgid.link/20260814082656.306534-1-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-08-18 09:41:02 -07:00
commit de9c18ad3f
8 changed files with 865 additions and 34 deletions

View File

@ -2,7 +2,7 @@
config ZL3073X
tristate "Microchip Azurite DPLL/PTP/SyncE devices" if COMPILE_TEST
depends on NET
depends on NET && PTP_1588_CLOCK
select DPLL
select NET_DEVLINK
select REGMAP
@ -16,7 +16,7 @@ config ZL3073X
config ZL3073X_I2C
tristate "I2C bus implementation for Microchip Azurite devices"
depends on I2C && NET
depends on I2C && NET && PTP_1588_CLOCK
select REGMAP_I2C
select ZL3073X
help
@ -28,7 +28,7 @@ config ZL3073X_I2C
config ZL3073X_SPI
tristate "SPI bus implementation for Microchip Azurite devices"
depends on NET && SPI
depends on NET && SPI && PTP_1588_CLOCK
select REGMAP_SPI
select ZL3073X
help

View File

@ -3,6 +3,7 @@
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/string.h>
#include <linux/types.h>
@ -162,8 +163,8 @@ int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index)
* @zldev: pointer to zl3073x_dev structure
* @index: DPLL channel index to fetch state for
*
* Reads the mode_refsel register and reference priority registers for
* the given DPLL channel and stores the raw values for later use.
* Reads the mode_refsel, status and reference priority registers for
* the given DPLL channel and stores the values for later use.
*
* Return: 0 on success, <0 on error
*/
@ -234,6 +235,322 @@ const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,
return &zldev->chan[index];
}
/**
* zl3073x_chan_tod_ready_wait - wait for ToD semaphore to clear
* @zldev: pointer to zl3073x device
* @ch: DPLL channel index
*
* Checks the ToD control register semaphore bit. If clear, returns
* immediately. Otherwise polls until the bit is cleared by the device.
*
* Return:
* * 0 - success
* * %-EBUSY - timeout
* * %-EOPNOTSUPP - unknown command detected
* * negative - other error
*/
int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch)
{
unsigned int timeout;
u8 tod_ctrl;
int rc;
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), &tod_ctrl);
if (rc)
return rc;
if (!(tod_ctrl & ZL_DPLL_TOD_CTRL_SEM))
return 0;
switch (FIELD_GET(ZL_DPLL_TOD_CTRL_CMD, tod_ctrl)) {
case ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ:
timeout = ZL_POLL_TOD_WR_TIMEOUT_US;
break;
case ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT:
case ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ:
timeout = ZL_POLL_TOD_RD_TIMEOUT_US;
break;
default:
return -EOPNOTSUPP;
}
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),
ZL_DPLL_TOD_CTRL_SEM, timeout);
return rc == -ETIMEDOUT ? -EBUSY : rc;
}
/**
* zl3073x_chan_tod_ctrl - issue ToD command
* @zldev: pointer to zl3073x device
* @ch: DPLL channel index
* @cmd: ToD command to execute
*
* Writes the semaphore and command to dpll_tod_ctrl. The caller must
* ensure the device is ready (semaphore clear) before calling and
* must wait for completion if needed.
*
* Return: 0 on success, <0 on error
*/
static int zl3073x_chan_tod_ctrl(struct zl3073x_dev *zldev, u8 ch, u8 cmd)
{
return zl3073x_write_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),
ZL_DPLL_TOD_CTRL_SEM | cmd);
}
/**
* zl3073x_chan_tod_read - read ToD registers after issuing a command
* @zldev: pointer to zl3073x device
* @ch: DPLL channel index
* @next_hz: if true, read predicted ToD at next 1 Hz; otherwise read current
* @ts: timespec to store the result
* @sts: optional system timestamp pair for cross-timestamping
*
* Context: Caller must serialize all zl3073x_chan_tod_* calls externally.
* Return: 0 on success, <0 on error
*/
int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch,
bool next_hz, struct timespec64 *ts,
struct ptp_system_timestamp *sts)
{
u32 nsec;
u64 sec;
u8 cmd;
int rc;
if (next_hz)
cmd = ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ;
else
cmd = ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT;
/* Wait for any previous ToD operation to complete */
rc = zl3073x_chan_tod_ready_wait(zldev, ch);
if (rc)
return rc;
ptp_read_system_prets(sts);
rc = zl3073x_chan_tod_ctrl(zldev, ch, cmd);
if (rc)
return rc;
rc = zl3073x_chan_tod_ready_wait(zldev, ch);
if (rc)
return rc;
ptp_read_system_postts(sts);
rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), &sec);
if (rc)
return rc;
/* HW nanoseconds are always in [0, NSEC_PER_SEC) range */
rc = zl3073x_read_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), &nsec);
if (rc)
return rc;
ts->tv_sec = sec;
ts->tv_nsec = nsec;
return 0;
}
/**
* zl3073x_chan_tod_write - write ToD registers and trigger 1 Hz update
* @zldev: pointer to zl3073x device
* @ch: DPLL channel index
* @ts: time to set
*
* Context: Caller must serialize all zl3073x_chan_tod_* calls externally.
* Return: 0 on success, <0 on error
*/
int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch,
struct timespec64 ts)
{
int rc;
/* Wait for any previous ToD operation to complete */
rc = zl3073x_chan_tod_ready_wait(zldev, ch);
if (rc)
return rc;
rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), ts.tv_sec);
if (rc)
return rc;
rc = zl3073x_write_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), ts.tv_nsec);
if (rc)
return rc;
return zl3073x_chan_tod_ctrl(zldev, ch,
ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ);
}
/**
* zl3073x_chan_tod_adjust - atomic ToD read-modify-write with rollover guard
* @zldev: pointer to zl3073x device
* @ch: DPLL channel index
* @delta: time adjustment to apply
*
* Reads the next-Hz ToD and current ToD, then checks whether enough time
* remains before the next 1 Hz rollover to safely complete the write.
* Re-reads if the 1 Hz tick crossed between the two reads or if less
* than 20 ms remains before the next rollover. Applies @delta and writes
* the result back.
*
* Context: Caller must serialize all zl3073x_chan_tod_* calls externally.
* Return: 0 on success, <0 on error
*/
int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch,
struct timespec64 delta)
{
#define ZL_TOD_MAX_RETRIES 20
static const long threshold_ns = 20 * NSEC_PER_MSEC;
struct timespec64 ts_next, ts_cur, diff;
int rc, i;
for (i = 0; i < ZL_TOD_MAX_RETRIES; i++) {
rc = zl3073x_chan_tod_read(zldev, ch, true, &ts_next, NULL);
if (rc)
return rc;
rc = zl3073x_chan_tod_read(zldev, ch, false, &ts_cur, NULL);
if (rc)
return rc;
/* Ensure the 1 Hz tick did not cross between the two reads
* and that enough margin remains to complete the write.
*/
diff = timespec64_sub(ts_next, ts_cur);
if (diff.tv_sec > 0 ||
(!diff.tv_sec && diff.tv_nsec >= threshold_ns))
break;
}
if (i == ZL_TOD_MAX_RETRIES) {
dev_warn(zldev->dev,
"DPLL%u ToD adjust failed to get stable margin\n",
ch);
return -EBUSY;
}
/* Apply delta to the next-Hz ToD */
ts_next = timespec64_add(ts_next, delta);
if (!timespec64_valid_settod(&ts_next))
return -EINVAL;
return zl3073x_chan_tod_write(zldev, ch, ts_next);
#undef ZL_TOD_MAX_RETRIES
}
/**
* zl3073x_chan_df_offset_set - write delta frequency offset to hardware
* @zldev: pointer to zl3073x device
* @ch: DPLL channel index
* @offset: frequency offset in 2^-48 steps
*
* Context: Caller must hold the per-DPLL lock.
* Return: 0 on success, <0 on error
*/
int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset)
{
int rc;
rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_DF_OFFSET(ch), offset);
if (!rc)
zldev->chan[ch].df_offset = offset;
return rc;
}
/**
* zl3073x_chan_tie_write - adjust DPLL phase using TIE write
* @zldev: pointer to zl3073x device
* @ch: DPLL channel index
* @delta_ns: phase adjustment in nanoseconds (must be in (-1s, 1s))
*
* Converts nanoseconds to TIE units (0.01 ps) and writes TIE data
* to the specified channel.
*
* Return: 0 on success, <0 on error
*/
int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns)
{
s64 tie_data;
int rc;
guard(mutex)(&zldev->tie_lock);
/* Wait for any previous TIE operation to complete */
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TIE_CTRL,
ZL_DPLL_TIE_CTRL_OP,
ZL_POLL_TIE_WR_TIMEOUT_US);
if (rc)
return rc;
/* Convert ns to TIE units (0.01 ps = 10^-14 s) */
tie_data = delta_ns * 100000LL;
rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TIE_DATA(ch), tie_data);
if (rc)
return rc;
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL_MASK, BIT(ch));
if (rc)
return rc;
return zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL,
ZL_DPLL_TIE_CTRL_OP_WR);
}
/**
* zl3073x_chan_phase_step - execute one output phase step operation
* @zldev: pointer to zl3073x device
* @ch: DPLL channel index
* @out_mask: bitmask of outputs to step
* @step_cycles: phase step in synthesizer clock cycles
* @tod_step: also step the ToD counter
*
* All masked outputs must use synthesizers of the same frequency since
* the step value is in synthesizer clock cycles.
*
* Return: 0 on success, <0 on error
*/
int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch,
u16 out_mask, s32 step_cycles,
bool tod_step)
{
u8 ctrl;
int rc;
guard(mutex)(&zldev->phase_step_lock);
/* Wait for any previous phase step operation to complete */
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL,
ZL_OUTPUT_PHASE_STEP_CTRL_OP,
ZL_POLL_PHASE_STEP_TIMEOUT_US);
if (rc)
return rc;
rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_STEP_DATA,
step_cycles);
if (rc)
return rc;
rc = zl3073x_write_u16(zldev, ZL_REG_OUTPUT_PHASE_STEP_MASK, out_mask);
if (rc)
return rc;
rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_NUMBER, 1);
if (rc)
return rc;
ctrl = FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_DPLL, ch) |
FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_OP,
ZL_OUTPUT_PHASE_STEP_CTRL_OP_WRITE);
if (tod_step)
ctrl |= ZL_OUTPUT_PHASE_STEP_CTRL_TOD_STEP;
return zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL, ctrl);
}
/**
* zl3073x_chan_state_set - commit DPLL channel state changes to hardware
* @zldev: pointer to zl3073x_dev structure

View File

@ -5,10 +5,12 @@
#include <linux/bitfield.h>
#include <linux/stddef.h>
#include <linux/time64.h>
#include <linux/types.h>
#include "regs.h"
struct ptp_system_timestamp;
struct zl3073x_dev;
/**
@ -42,6 +44,21 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);
int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index);
int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch);
int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch,
bool next_hz, struct timespec64 *ts,
struct ptp_system_timestamp *sts);
int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch,
struct timespec64 ts);
int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch,
struct timespec64 delta);
int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch,
u16 out_mask, s32 step_cycles, bool tod_step);
int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset);
int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns);
/**
* zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference
* @chan: pointer to channel state
@ -200,6 +217,21 @@ 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_mode_supports_tie - check if channel mode supports TIE write
* @chan: pointer to channel state
*
* TIE write is supported in AUTO and REFLOCK modes regardless of lock state.
*
* Return: true if TIE write is supported, false otherwise
*/
static inline bool
zl3073x_chan_mode_supports_tie(const struct zl3073x_chan *chan)
{
return zl3073x_chan_mode_is_auto(chan) ||
zl3073x_chan_mode_is_reflock(chan);
}
/**
* zl3073x_chan_is_ho_ready - check if holdover is ready
* @chan: pointer to channel state

View File

@ -322,7 +322,7 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val)
int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
u8 mask, unsigned int timeout_us)
{
#define ZL_POLL_SLEEP_US 10
unsigned int sleep_us = timeout_us / 50;
unsigned int val;
/* Check the register is 8bit */
@ -336,7 +336,7 @@ int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask),
ZL_POLL_SLEEP_US, timeout_us);
sleep_us, timeout_us);
}
int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
@ -511,6 +511,11 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev)
int rc;
u8 i;
rc = zl3073x_read_u16(zldev, ZL_REG_OUTPUT_STEP_TIME_MASK,
&zldev->out_step_time_mask);
if (rc)
return rc;
for (i = 0; i < ZL3073X_NUM_REFS; i++) {
rc = zl3073x_ref_state_fetch(zldev, i);
if (rc) {
@ -1034,6 +1039,14 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev)
* and/or polls are required to be done atomically.
*/
rc = devm_mutex_init(zldev->dev, &zldev->multiop_lock);
if (rc)
return dev_err_probe(zldev->dev, rc,
"Failed to initialize mutex\n");
rc = devm_mutex_init(zldev->dev, &zldev->phase_step_lock);
if (rc)
return dev_err_probe(zldev->dev, rc,
"Failed to initialize mutex\n");
rc = devm_mutex_init(zldev->dev, &zldev->tie_lock);
if (rc)
return dev_err_probe(zldev->dev, rc,
"Failed to initialize mutex\n");

View File

@ -26,6 +26,10 @@ struct zl3073x_dpll;
#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)
#define ZL_POLL_PHASE_STEP_TIMEOUT_US (3000 * USEC_PER_MSEC)
#define ZL_POLL_TIE_WR_TIMEOUT_US (1000 * USEC_PER_MSEC)
#define ZL_POLL_TOD_RD_TIMEOUT_US (30 * USEC_PER_MSEC)
#define ZL_POLL_TOD_WR_TIMEOUT_US (1000 * USEC_PER_MSEC)
enum zl3073x_flags {
ZL3073X_FLAG_REF_PHASE_COMP_32_BIT,
@ -55,6 +59,8 @@ struct zl3073x_chip_info {
* @regmap: regmap to access device registers
* @info: detected chip info
* @multiop_lock: to serialize multiple register operations
* @tie_lock: to serialize TIE write operations
* @phase_step_lock: to serialize output phase step operations
* @ref: array of input references' invariants
* @out: array of outs' invariants
* @synth: array of synths' invariants
@ -63,6 +69,7 @@ struct zl3073x_chip_info {
* @kworker: thread for periodic work
* @work: periodic work
* @clock_id: clock id of the device
* @out_step_time_mask: output step-time mask (device-global)
* @phase_avg_factor: phase offset measurement averaging factor
* @freq_monitor: is frequency monitor enabled
*/
@ -71,6 +78,8 @@ struct zl3073x_dev {
struct regmap *regmap;
const struct zl3073x_chip_info *info;
struct mutex multiop_lock;
struct mutex tie_lock;
struct mutex phase_step_lock;
/* Invariants */
struct zl3073x_ref ref[ZL3073X_NUM_REFS];
@ -87,6 +96,7 @@ struct zl3073x_dev {
/* Per-chip parameters */
u64 clock_id;
u16 out_step_time_mask;
u8 phase_avg_factor;
bool freq_monitor;
};
@ -308,6 +318,19 @@ zl3073x_dev_out_is_enabled(struct zl3073x_dev *zldev, u8 index)
return zl3073x_synth_is_enabled(synth) && zl3073x_out_is_enabled(out);
}
/**
* zl3073x_dev_out_is_stepped - check if output is in step-time mask
* @zldev: pointer to zl3073x device
* @index: output index
*
* Return: true if output is affected by step-time operations
*/
static inline bool
zl3073x_dev_out_is_stepped(struct zl3073x_dev *zldev, u8 index)
{
return !!(zldev->out_step_time_mask & BIT(index));
}
/**
* zl3073x_dev_out_dpll_get - get DPLL ID the output is driven by
* @zldev: pointer to zl3073x device

View File

@ -2,6 +2,7 @@
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/bug.h>
#include <linux/container_of.h>
#include <linux/dev_printk.h>
@ -13,6 +14,7 @@
#include <linux/netlink.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/slab.h>
#include <linux/sprintf.h>
@ -2313,45 +2315,384 @@ zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev)
return zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_CTRL, 0x01);
}
/* Maximum frequency adjustment: +-1% of nominal in ppb */
#define ZL3073X_DPLL_PTP_MAX_ADJ 10000000
/**
* zl3073x_dpll_alloc - allocate DPLL device
* @zldev: pointer to zl3073x device
* @ch: DPLL channel number
* zl3073x_dpll_ptp_gettimex64 - read current time from ToD counters
* @info: PTP clock info
* @ts: timespec to store current time
* @sts: optional system timestamp pair for cross-timestamping
*
* Allocates DPLL device structure for given DPLL channel.
*
* Return: pointer to DPLL device on success, error pointer on error
* Return: 0 on success, <0 on error
*/
struct zl3073x_dpll *
zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
static int zl3073x_dpll_ptp_gettimex64(struct ptp_clock_info *info,
struct timespec64 *ts,
struct ptp_system_timestamp *sts)
{
struct zl3073x_dpll *zldpll;
struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
ptp_info);
zldpll = kzalloc_obj(*zldpll);
if (!zldpll)
return ERR_PTR(-ENOMEM);
guard(mutex)(&zldpll->lock);
zldpll->dev = zldev;
zldpll->id = ch;
mutex_init(&zldpll->lock);
INIT_LIST_HEAD(&zldpll->pins);
return zldpll;
return zl3073x_chan_tod_read(zldpll->dev, zldpll->id, false, ts, sts);
}
/**
* zl3073x_dpll_free - free DPLL device
* @zldpll: pointer to zl3073x_dpll structure
* zl3073x_dpll_ptp_settime64 - set ToD counters to given time
* @info: PTP clock info
* @ts: timespec with time to set
*
* Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc.
* Return: 0 on success, <0 on error
*/
void
zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
static int zl3073x_dpll_ptp_settime64(struct ptp_clock_info *info,
const struct timespec64 *ts)
{
WARN(zldpll->dpll_dev, "DPLL device is still registered\n");
struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
ptp_info);
mutex_destroy(&zldpll->lock);
kfree(zldpll);
guard(mutex)(&zldpll->lock);
return zl3073x_chan_tod_write(zldpll->dev, zldpll->id, *ts);
}
/**
* zl3073x_dpll_ptp_adjtime_phase_step - adjust sub-second time via phase step
* @zldpll: DPLL channel
* @delta: time adjustment in nanoseconds (must be within (-NSEC_PER_SEC,
* NSEC_PER_SEC))
*
* Uses the output phase step mechanism with tod_step=1 to adjust both
* the output clock phase and the ToD counter simultaneously. This keeps
* outputs and ToD coherent. Only valid for NCO.
*
* Outputs are grouped by synthesizer since the phase step value is in
* synthesizer clock cycles. The first synth group with enabled outputs
* uses tod_step to adjust both outputs and the ToD counter. Remaining
* groups step outputs only. If no synth has enabled outputs, the ToD
* counter is stepped alone using an empty output mask (the FW uses
* the first enabled synth's period for the conversion).
*
* Return:
* * %0 - success (or partial success if a later synth group
* failed after the first was already stepped)
* * %-EOPNOTSUPP - no synths available
* * negative - error
*/
static int zl3073x_dpll_ptp_adjtime_phase_step(struct zl3073x_dpll *zldpll,
s64 delta)
{
u16 synth_mask[ZL3073X_NUM_SYNTHS] = {};
struct zl3073x_dev *zldev = zldpll->dev;
const struct zl3073x_synth *synth;
struct zl3073x_dpll_pin *pin;
u32 first_synth_freq = 0;
bool tod_stepped = false;
s32 step_cycles;
u32 synth_freq;
int rc;
u8 i;
/* Build per-synth output masks from registered output pins */
list_for_each_entry(pin, &zldpll->pins, list) {
u8 out_id, synth_id;
if (zl3073x_dpll_is_input_pin(pin))
continue;
out_id = zl3073x_output_pin_out_get(pin->id);
if (!zl3073x_dev_out_is_stepped(zldev, out_id))
continue;
synth_id = zl3073x_dev_out_synth_get(zldev, out_id);
if (synth_id >= ZL3073X_NUM_SYNTHS) {
dev_warn(zldev->dev, "Unexpected synth id for OUT%u\n",
out_id);
continue;
}
synth_mask[synth_id] |= BIT(out_id);
}
/* Process each synth group */
for (i = 0; i < ZL3073X_NUM_SYNTHS; i++) {
synth = zl3073x_synth_state_get(zldev, i);
if (!zl3073x_synth_is_enabled(synth) ||
zl3073x_synth_dpll_get(synth) != zldpll->id)
continue;
synth_freq = zl3073x_synth_freq_get(synth);
/* Remember first enabled synth freq for ToD-only fallback */
if (!first_synth_freq)
first_synth_freq = synth_freq;
if (!synth_mask[i])
continue;
/* Safe for s32: max synth freq is 750 MHz */
step_cycles = div_s64(delta * synth_freq, NSEC_PER_SEC);
rc = zl3073x_chan_phase_step(zldev, zldpll->id,
synth_mask[i], step_cycles,
!tod_stepped);
if (rc) {
if (tod_stepped) {
dev_warn(zldev->dev,
"Partial phase step failure\n");
return 0;
}
return rc;
}
tod_stepped = true;
}
if (!first_synth_freq)
return -EOPNOTSUPP;
/* No enabled outputs found; step ToD counter only using the
* first enabled synth's period (empty output mask).
*/
if (!tod_stepped) {
step_cycles = div_s64(delta * first_synth_freq, NSEC_PER_SEC);
return zl3073x_chan_phase_step(zldev, zldpll->id, 0,
step_cycles, true);
}
return 0;
}
/**
* zl3073x_dpll_ptp_adjtime - adjust PTP clock time
* @info: PTP clock info
* @delta: time adjustment in nanoseconds
*
* For NCO, large deltas (>= 1 second) are split into a ToD
* read-modify-write for the seconds part and an output phase step for
* the sub-second remainder. Sub-second deltas use phase step directly,
* falling back to ToD read-modify-write if phase step or TIE write
* fails. In AUTO/REFLOCK modes, large deltas are split into ToD
* read-modify-write for seconds and TIE write for the sub-second
* remainder. Sub-second deltas use TIE write directly.
*
* If the seconds part was already committed when the sub-second
* mechanism fails, returns 0 to prevent the PTP servo from retrying
* the full delta and applying seconds again.
*
* Return: 0 on success (or partial success), <0 on error
*/
static int zl3073x_dpll_ptp_adjtime(struct ptp_clock_info *info, s64 delta)
{
struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
ptp_info);
struct zl3073x_dev *zldev = zldpll->dev;
const struct zl3073x_chan *chan;
bool sec_adjusted = false;
struct timespec64 ts;
int rc;
if (!delta)
return 0;
guard(mutex)(&zldpll->lock);
/* Modes without phase step or TIE use plain ToD adjust */
chan = zl3073x_chan_state_get(zldev, zldpll->id);
if (!zl3073x_chan_mode_is_nco(chan) &&
!zl3073x_chan_mode_supports_tie(chan))
return zl3073x_chan_tod_adjust(zldev, zldpll->id,
ns_to_timespec64(delta));
/* Split off seconds via ToD read-modify-write so the sub-second
* remainder can be applied through the output-coherent mechanism
* (phase step or TIE write).
*/
if (delta >= NSEC_PER_SEC || delta <= -NSEC_PER_SEC) {
s32 remainder;
ts.tv_sec = div_s64_rem(delta, NSEC_PER_SEC, &remainder);
ts.tv_nsec = 0;
delta = remainder;
rc = zl3073x_chan_tod_adjust(zldev, zldpll->id, ts);
if (rc)
return rc;
/* No sub-second remainder, done */
if (!delta)
return 0;
/* Wait for the ToD write to be applied at the 1 Hz edge
* before issuing phase step or TIE write, so the pending
* WR_NEXT_1HZ does not overwrite the sub-second adjustment.
*/
rc = zl3073x_chan_tod_ready_wait(zldev, zldpll->id);
if (rc)
return rc;
sec_adjusted = true;
}
/* Apply sub-second delta via phase step (NCO) or TIE write */
if (zl3073x_chan_mode_is_nco(chan)) {
rc = zl3073x_dpll_ptp_adjtime_phase_step(zldpll, delta);
if (!rc)
return 0;
} else {
rc = zl3073x_chan_tie_write(zldev, zldpll->id, delta);
if (!rc)
return 0;
}
/* Phase step or TIE write failed, fall back to ToD adjust */
rc = zl3073x_chan_tod_adjust(zldev, zldpll->id,
ns_to_timespec64(delta));
/* In the unlikely event that both phase step/TIE write and fallback
* ToD adjust fail after seconds were already committed, return
* success to prevent the PTP servo from retrying the full delta and
* applying seconds again. The sub-second residual will self-correct
* in the next servo cycle.
*/
if (rc && sec_adjusted) {
dev_warn(zldev->dev,
"Sub-second adjustment failed after seconds applied\n");
return 0;
}
return rc;
}
/**
* zl3073x_dpll_ptp_adjfine - adjust PTP clock frequency
* @info: PTP clock info
* @scaled_ppm: frequency adjustment in scaled ppm (ppm * 2^16)
*
* Only supported for NCO. Writes the delta frequency offset register.
*
* Return:
* * %0 - success or @scaled_ppm is zero (no-op)
* * %-EOPNOTSUPP - NCO pin is not connected and @scaled_ppm is non-zero
* * negative - other error
*/
static int
zl3073x_dpll_ptp_adjfine(struct ptp_clock_info *info, long scaled_ppm)
{
struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
ptp_info);
const struct zl3073x_chan *chan;
s64 offset;
/* Convert scaled_ppm to df_offset in 2^-48 steps:
* df_offset = -(scaled_ppm * 2^32) / 10^6
*
* Simplify to avoid overflow:
* df_offset = -(scaled_ppm * 2^26) / 5^6
* df_offset = -(scaled_ppm * 67108864) / 15625
*/
offset = -div_s64((s64)scaled_ppm * 67108864LL, 15625);
guard(mutex)(&zldpll->lock);
chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
if (!zl3073x_chan_mode_is_nco(chan))
return scaled_ppm ? -EOPNOTSUPP : 0;
if (offset == chan->df_offset)
return 0;
return zl3073x_chan_df_offset_set(zldpll->dev, zldpll->id, offset);
}
/**
* zl3073x_dpll_ptp_adjphase - adjust PTP clock phase
* @info: PTP clock info
* @delta: phase adjustment in nanoseconds
*
* Only supported in AUTO and REFLOCK modes. Uses TIE write for
* nanosecond resolution phase adjustment.
*
* Return:
* * %0 - success or @delta is zero (no-op)
* * %-EOPNOTSUPP - mode does not support TIE and @delta is non-zero
* * negative - other error
*/
static int zl3073x_dpll_ptp_adjphase(struct ptp_clock_info *info, s32 delta)
{
struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
ptp_info);
struct zl3073x_dev *zldev = zldpll->dev;
const struct zl3073x_chan *chan;
if (!delta)
return 0;
guard(mutex)(&zldpll->lock);
chan = zl3073x_chan_state_get(zldev, zldpll->id);
if (!zl3073x_chan_mode_supports_tie(chan))
return -EOPNOTSUPP;
return zl3073x_chan_tie_write(zldev, zldpll->id, delta);
}
static s32
zl3073x_dpll_ptp_getmaxphase(struct ptp_clock_info *info __always_unused)
{
/* HW limits TIE write to +-1 second. Return the constant HW
* limit and let adjphase handle mode-specific checks.
*/
return NSEC_PER_SEC - 1;
}
static const struct ptp_clock_info zl3073x_dpll_ptp_clock_info = {
.owner = THIS_MODULE,
.max_adj = ZL3073X_DPLL_PTP_MAX_ADJ,
.gettimex64 = zl3073x_dpll_ptp_gettimex64,
.settime64 = zl3073x_dpll_ptp_settime64,
.adjtime = zl3073x_dpll_ptp_adjtime,
.adjfine = zl3073x_dpll_ptp_adjfine,
.adjphase = zl3073x_dpll_ptp_adjphase,
.getmaxphase = zl3073x_dpll_ptp_getmaxphase,
};
/**
* zl3073x_dpll_ptp_register - register PTP clock for a DPLL channel
* @zldpll: DPLL channel to register PTP clock for
*
* Return: 0 on success, <0 on error
*/
static int zl3073x_dpll_ptp_register(struct zl3073x_dpll *zldpll)
{
struct zl3073x_dev *zldev = zldpll->dev;
struct ptp_clock *ptp_clock;
zldpll->ptp_info = zl3073x_dpll_ptp_clock_info;
snprintf(zldpll->ptp_info.name, sizeof(zldpll->ptp_info.name),
"%s-dpll%u", dev_name(zldev->dev), zldpll->id);
ptp_clock = ptp_clock_register(&zldpll->ptp_info, zldev->dev);
if (IS_ERR(ptp_clock)) {
dev_err(zldev->dev, "Failed to register PTP clock for DPLL%u\n",
zldpll->id);
return PTR_ERR(ptp_clock);
}
zldpll->ptp_clock = ptp_clock;
return 0;
}
/**
* zl3073x_dpll_ptp_unregister - unregister PTP clock for a DPLL channel
* @zldpll: DPLL channel to unregister PTP clock for
*/
static void zl3073x_dpll_ptp_unregister(struct zl3073x_dpll *zldpll)
{
if (!IS_ERR_OR_NULL(zldpll->ptp_clock)) {
ptp_clock_unregister(zldpll->ptp_clock);
zldpll->ptp_clock = NULL;
}
}
/**
@ -2436,6 +2777,47 @@ zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll)
return 0;
}
/**
* zl3073x_dpll_alloc - allocate DPLL device
* @zldev: pointer to zl3073x device
* @ch: DPLL channel number
*
* Allocates DPLL device structure for given DPLL channel.
*
* Return: pointer to DPLL device on success, error pointer on error
*/
struct zl3073x_dpll *
zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
{
struct zl3073x_dpll *zldpll;
zldpll = kzalloc_obj(*zldpll);
if (!zldpll)
return ERR_PTR(-ENOMEM);
zldpll->dev = zldev;
zldpll->id = ch;
mutex_init(&zldpll->lock);
INIT_LIST_HEAD(&zldpll->pins);
return zldpll;
}
/**
* zl3073x_dpll_free - free DPLL device
* @zldpll: pointer to zl3073x_dpll structure
*
* Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc.
*/
void
zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
{
WARN(zldpll->dpll_dev, "DPLL device is still registered\n");
mutex_destroy(&zldpll->lock);
kfree(zldpll);
}
/**
* zl3073x_dpll_register - register DPLL device and all its pins
* @zldpll: pointer to zl3073x_dpll structure
@ -2466,6 +2848,13 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll)
return rc;
}
rc = zl3073x_dpll_ptp_register(zldpll);
if (rc) {
zl3073x_dpll_pins_unregister(zldpll);
zl3073x_dpll_device_unregister(zldpll);
return rc;
}
return 0;
}
@ -2479,7 +2868,7 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll)
void
zl3073x_dpll_unregister(struct zl3073x_dpll *zldpll)
{
/* Unregister all pins and dpll */
zl3073x_dpll_ptp_unregister(zldpll);
zl3073x_dpll_pins_unregister(zldpll);
zl3073x_dpll_device_unregister(zldpll);
}

View File

@ -5,6 +5,7 @@
#include <linux/dpll.h>
#include <linux/list.h>
#include <linux/ptp_clock_kernel.h>
#include "core.h"
@ -22,6 +23,8 @@
* @type: DPLL type (PPS or EEC)
* @lock_status: last saved DPLL lock status
* @pins: list of pins
* @ptp_info: PTP clock info
* @ptp_clock: registered PTP clock (or NULL)
*/
struct zl3073x_dpll {
struct list_head list;
@ -36,6 +39,8 @@ struct zl3073x_dpll {
enum dpll_type type;
enum dpll_lock_status lock_status;
struct list_head pins;
struct ptp_clock_info ptp_info;
struct ptp_clock *ptp_clock;
};
struct zl3073x_dpll *zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch);

View File

@ -179,6 +179,20 @@
#define ZL_DPLL_DF_READ_CMD GENMASK(2, 0)
#define ZL_DPLL_DF_READ_CMD_ACC_I 4
#define ZL_REG_DPLL_TIE_CTRL ZL_REG(5, 0x30, 1)
#define ZL_DPLL_TIE_CTRL_OP GENMASK(2, 0)
#define ZL_DPLL_TIE_CTRL_OP_WR 4
#define ZL_REG_DPLL_TIE_CTRL_MASK ZL_REG(5, 0x31, 1)
#define ZL_REG_DPLL_TOD_CTRL(_idx) \
ZL_REG_IDX(_idx, 5, 0x38, 1, ZL3073X_MAX_CHANNELS, 1)
#define ZL_DPLL_TOD_CTRL_SEM BIT(4)
#define ZL_DPLL_TOD_CTRL_CMD GENMASK(3, 0)
#define ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ 1
#define ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT 8
#define ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ 9
#define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1)
#define ZL_DPLL_MEAS_CTRL_EN BIT(0)
#define ZL_DPLL_MEAS_CTRL_AVG_FACTOR GENMASK(7, 4)
@ -193,6 +207,9 @@
/*******************************
* Register Pages 6-7, DPLL Data
*
* Per-channel registers with stride 0x20. Channels 0-3 reside on page 6,
* channel 4 on page 7.
*******************************/
#define ZL_REG_DPLL_DF_OFFSET_03(_idx) \
@ -202,6 +219,24 @@
((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4)
#define ZL_DPLL_DF_OFFSET_UNKNOWN S64_MIN
#define ZL_REG_DPLL_TIE_DATA_03(_idx) \
ZL_REG_IDX(_idx, 6, 0x0C, 6, 4, 0x20)
#define ZL_REG_DPLL_TIE_DATA_4 ZL_REG(7, 0x0C, 6)
#define ZL_REG_DPLL_TIE_DATA(_idx) \
((_idx) < 4 ? ZL_REG_DPLL_TIE_DATA_03(_idx) : ZL_REG_DPLL_TIE_DATA_4)
#define ZL_REG_DPLL_TOD_SEC_03(_idx) \
ZL_REG_IDX(_idx, 6, 0x12, 6, 4, 0x20)
#define ZL_REG_DPLL_TOD_SEC_4 ZL_REG(7, 0x12, 6)
#define ZL_REG_DPLL_TOD_SEC(_idx) \
((_idx) < 4 ? ZL_REG_DPLL_TOD_SEC_03(_idx) : ZL_REG_DPLL_TOD_SEC_4)
#define ZL_REG_DPLL_TOD_NS_03(_idx) \
ZL_REG_IDX(_idx, 6, 0x18, 4, 4, 0x20)
#define ZL_REG_DPLL_TOD_NS_4 ZL_REG(7, 0x18, 4)
#define ZL_REG_DPLL_TOD_NS(_idx) \
((_idx) < 4 ? ZL_REG_DPLL_TOD_NS_03(_idx) : ZL_REG_DPLL_TOD_NS_4)
/***********************************
* Register Page 9, Synth and Output
***********************************/
@ -221,6 +256,23 @@
#define ZL_OUTPUT_CTRL_EN BIT(0)
#define ZL_OUTPUT_CTRL_SYNTH_SEL GENMASK(6, 4)
#define ZL_REG_OUTPUT_STEP_TIME_MASK ZL_REG(9, 0x36, 2)
#define ZL_REG_OUTPUT_PHASE_STEP_CTRL ZL_REG(9, 0x38, 1)
#define ZL_OUTPUT_PHASE_STEP_CTRL_DPLL GENMASK(6, 4)
#define ZL_OUTPUT_PHASE_STEP_CTRL_TOD_STEP BIT(3)
#define ZL_OUTPUT_PHASE_STEP_CTRL_OP GENMASK(1, 0)
#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_NONE 0
#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_RESET 1
#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_READ 2
#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_WRITE 3
#define ZL_REG_OUTPUT_PHASE_STEP_NUMBER ZL_REG(9, 0x39, 1)
#define ZL_REG_OUTPUT_PHASE_STEP_MASK ZL_REG(9, 0x3a, 2)
#define ZL_REG_OUTPUT_PHASE_STEP_DATA ZL_REG(9, 0x3c, 4)
/*******************************
* Register Page 10, Ref Mailbox
*******************************/