dpll: zl3073x: report FFO as DPLL vs input reference offset

Replace the per-reference frequency offset measurement (which was
redundant with measured-frequency) with a direct read of the DPLL's
delta frequency offset vs its tracked input reference.

The new implementation uses the dpll_df_offset_x register with
ref_ofst=1 via the dpll_df_read_x semaphore mechanism. This
provides 2^-48 resolution (~3.5 fE) and reports the actual
frequency difference between the DPLL and its active input.

Switch supported_ffo from DPLL_FFO_PORT_RXTX_RATE to
DPLL_FFO_PIN_DEVICE so FFO is reported only in the per-parent
context for the active input pin.

Use atomic64_t for freq_offset to prevent torn reads on 32-bit
architectures between the periodic worker and netlink callbacks.

Rewrite ffo_check to compare the cached df_offset converted to PPT
instead of using the old per-reference measurement. Remove the
ref_ffo_update periodic measurement and the ref ffo field since
they are no longer needed.

Changes v3 -> v4:
- Switch to DPLL_FFO_PIN_DEVICE, remove dpll=NULL guard
- Use atomic64_t for freq_offset (torn read on 32-bit)

Reviewed-by: Petr Oros <poros@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260511155816.99936-3-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Ivan Vecera 2026-05-11 17:58:16 +02:00 committed by Jakub Kicinski
parent 9c11fcb2e9
commit 54e65df8cf
6 changed files with 75 additions and 82 deletions

View File

@ -18,6 +18,7 @@
int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
{ {
struct zl3073x_chan *chan = &zldev->chan[index]; struct zl3073x_chan *chan = &zldev->chan[index];
u64 val;
int rc; int rc;
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(index), rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(index),
@ -25,8 +26,34 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
if (rc) if (rc)
return rc; return rc;
return zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index), rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
&chan->refsel_status); &chan->refsel_status);
if (rc)
return rc;
/* Read df_offset vs tracked reference */
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
ZL_DPLL_DF_READ_SEM);
if (rc)
return rc;
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index),
ZL_DPLL_DF_READ_SEM | ZL_DPLL_DF_READ_REF_OFST);
if (rc)
return rc;
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
ZL_DPLL_DF_READ_SEM);
if (rc)
return rc;
rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val);
if (rc)
return rc;
chan->df_offset = sign_extend64(val, 47);
return 0;
} }
/** /**

View File

@ -17,6 +17,7 @@ struct zl3073x_dev;
* @ref_prio: reference priority registers (4 bits per ref, P/N packed) * @ref_prio: reference priority registers (4 bits per ref, P/N packed)
* @mon_status: monitor status register value * @mon_status: monitor status register value
* @refsel_status: reference selection status register value * @refsel_status: reference selection status register value
* @df_offset: frequency offset vs tracked reference in 2^-48 steps
*/ */
struct zl3073x_chan { struct zl3073x_chan {
struct_group(cfg, struct_group(cfg,
@ -26,6 +27,7 @@ struct zl3073x_chan {
struct_group(stat, struct_group(stat,
u8 mon_status; u8 mon_status;
u8 refsel_status; u8 refsel_status;
s64 df_offset;
); );
}; };
@ -37,6 +39,18 @@ 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_state_update(struct zl3073x_dev *zldev, u8 index);
/**
* zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference
* @chan: pointer to channel state
*
* Return: frequency offset in 2^-48 steps
*/
static inline s64
zl3073x_chan_df_offset_get(const struct zl3073x_chan *chan)
{
return chan->df_offset;
}
/** /**
* zl3073x_chan_mode_get - get DPLL channel operating mode * zl3073x_chan_mode_get - get DPLL channel operating mode
* @chan: pointer to channel state * @chan: pointer to channel state

View File

@ -704,44 +704,6 @@ zl3073x_ref_freq_meas_update(struct zl3073x_dev *zldev)
return 0; return 0;
} }
/**
* zl3073x_ref_ffo_update - update reference fractional frequency offsets
* @zldev: pointer to zl3073x_dev structure
*
* The function asks device to latch the latest measured fractional
* frequency offset values, reads and stores them into the ref state.
*
* Return: 0 on success, <0 on error
*/
static int
zl3073x_ref_ffo_update(struct zl3073x_dev *zldev)
{
int i, rc;
rc = zl3073x_ref_freq_meas_latch(zldev,
ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF);
if (rc)
return rc;
/* Read DPLL-to-REFx frequency offset measurements */
for (i = 0; i < ZL3073X_NUM_REFS; i++) {
s32 value;
/* Read value stored in units of 2^-32 signed */
rc = zl3073x_read_u32(zldev, ZL_REG_REF_FREQ(i), &value);
if (rc)
return rc;
/* Convert to ppt
* ffo = (10^12 * value) / 2^32
* ffo = ( 5^12 * value) / 2^20
*/
zldev->ref[i].ffo = mul_s64_u64_shr(value, 244140625, 20);
}
return 0;
}
static void static void
zl3073x_dev_periodic_work(struct kthread_work *work) zl3073x_dev_periodic_work(struct kthread_work *work)
{ {
@ -776,13 +738,6 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
} }
} }
/* Update references' fractional frequency offsets */
rc = zl3073x_ref_ffo_update(zldev);
if (rc)
dev_warn(zldev->dev,
"Failed to update fractional frequency offsets: %pe\n",
ERR_PTR(rc));
list_for_each_entry(zldpll, &zldev->dplls, list) list_for_each_entry(zldpll, &zldev->dplls, list)
zl3073x_dpll_changes_check(zldpll); zl3073x_dpll_changes_check(zldpll);

View File

@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only // SPDX-License-Identifier: GPL-2.0-only
#include <linux/atomic.h>
#include <linux/bits.h> #include <linux/bits.h>
#include <linux/bitfield.h> #include <linux/bitfield.h>
#include <linux/bug.h> #include <linux/bug.h>
@ -57,7 +58,7 @@ struct zl3073x_dpll_pin {
s32 phase_gran; s32 phase_gran;
enum dpll_pin_operstate operstate; enum dpll_pin_operstate operstate;
s64 phase_offset; s64 phase_offset;
s64 freq_offset; atomic64_t freq_offset;
u32 measured_freq; u32 measured_freq;
}; };
@ -300,7 +301,10 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
{ {
struct zl3073x_dpll_pin *pin = pin_priv; struct zl3073x_dpll_pin *pin = pin_priv;
ffo->ffo = pin->freq_offset; if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
return -ENODATA;
ffo->ffo = atomic64_read(&pin->freq_offset);
return 0; return 0;
} }
@ -1275,7 +1279,7 @@ zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll,
} }
static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = { static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
.supported_ffo = BIT(DPLL_FFO_PORT_RXTX_RATE), .supported_ffo = BIT(DPLL_FFO_PIN_DEVICE),
.direction_get = zl3073x_dpll_pin_direction_get, .direction_get = zl3073x_dpll_pin_direction_get,
.esync_get = zl3073x_dpll_input_pin_esync_get, .esync_get = zl3073x_dpll_input_pin_esync_get,
.esync_set = zl3073x_dpll_input_pin_esync_set, .esync_set = zl3073x_dpll_input_pin_esync_set,
@ -1731,37 +1735,29 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
} }
/** /**
* zl3073x_dpll_pin_ffo_check - check for pin fractional frequency offset change * zl3073x_dpll_pin_ffo_check - check for FFO change on active pin
* @pin: pin to check * @pin: pin to check
* *
* Check for the given pin's fractional frequency change. * Return: true on change, false otherwise
*
* Return: true on fractional frequency offset change, false otherwise
*/ */
static bool static bool
zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin) zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
{ {
struct zl3073x_dpll *zldpll = pin->dpll; struct zl3073x_dpll *zldpll = pin->dpll;
struct zl3073x_dev *zldev = zldpll->dev; struct zl3073x_dev *zldev = zldpll->dev;
const struct zl3073x_ref *ref; const struct zl3073x_chan *chan;
u8 ref_id;
s64 ffo; s64 ffo;
/* Get reference monitor status */ if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
ref_id = zl3073x_input_pin_ref_get(pin->id);
ref = zl3073x_ref_state_get(zldev, ref_id);
/* Do not report ffo changes if the reference monitor report errors */
if (!zl3073x_ref_is_status_ok(ref))
return false; return false;
/* Compare with previous value */ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
ffo = zl3073x_ref_ffo_get(ref); ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan),
if (pin->freq_offset != ffo) { 244140625, 36);
dev_dbg(zldev->dev, "%s freq offset changed: %lld -> %lld\n",
pin->label, pin->freq_offset, ffo);
pin->freq_offset = ffo;
if (atomic64_xchg(&pin->freq_offset, ffo) != ffo) {
dev_dbg(zldev->dev, "%s freq offset changed to: %lld\n",
pin->label, ffo);
return true; return true;
} }

View File

@ -22,7 +22,6 @@ struct zl3073x_dev;
* @freq_ratio_n: FEC mode divisor * @freq_ratio_n: FEC mode divisor
* @sync_ctrl: reference sync control * @sync_ctrl: reference sync control
* @config: reference config * @config: reference config
* @ffo: current fractional frequency offset
* @meas_freq: measured input frequency in Hz * @meas_freq: measured input frequency in Hz
* @mon_status: reference monitor status * @mon_status: reference monitor status
*/ */
@ -40,7 +39,6 @@ struct zl3073x_ref {
u8 config; u8 config;
); );
struct_group(stat, /* Status */ struct_group(stat, /* Status */
s64 ffo;
u32 meas_freq; u32 meas_freq;
u8 mon_status; u8 mon_status;
); );
@ -58,18 +56,6 @@ int zl3073x_ref_state_update(struct zl3073x_dev *zldev, u8 index);
int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult); int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult);
/**
* zl3073x_ref_ffo_get - get current fractional frequency offset
* @ref: pointer to ref state
*
* Return: the latest measured fractional frequency offset
*/
static inline s64
zl3073x_ref_ffo_get(const struct zl3073x_ref *ref)
{
return ref->ffo;
}
/** /**
* zl3073x_ref_meas_freq_get - get measured input frequency * zl3073x_ref_meas_freq_get - get measured input frequency
* @ref: pointer to ref state * @ref: pointer to ref state

View File

@ -164,6 +164,11 @@
#define ZL_DPLL_MODE_REFSEL_MODE_NCO 4 #define ZL_DPLL_MODE_REFSEL_MODE_NCO 4
#define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4) #define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4)
#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_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1) #define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1)
#define ZL_DPLL_MEAS_CTRL_EN BIT(0) #define ZL_DPLL_MEAS_CTRL_EN BIT(0)
#define ZL_DPLL_MEAS_CTRL_AVG_FACTOR GENMASK(7, 4) #define ZL_DPLL_MEAS_CTRL_AVG_FACTOR GENMASK(7, 4)
@ -176,6 +181,16 @@
#define ZL_REG_DPLL_PHASE_ERR_DATA(_idx) \ #define ZL_REG_DPLL_PHASE_ERR_DATA(_idx) \
ZL_REG_IDX(_idx, 5, 0x55, 6, ZL3073X_MAX_CHANNELS, 6) ZL_REG_IDX(_idx, 5, 0x55, 6, ZL3073X_MAX_CHANNELS, 6)
/*******************************
* Register Pages 6-7, DPLL Data
*******************************/
#define ZL_REG_DPLL_DF_OFFSET_03(_idx) \
ZL_REG_IDX(_idx, 6, 0x00, 6, 4, 0x20)
#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)
/*********************************** /***********************************
* Register Page 9, Synth and Output * Register Page 9, Synth and Output
***********************************/ ***********************************/