mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 04:34:03 +02:00
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:
commit
de9c18ad3f
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*******************************/
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user