]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
dpll: zl3073x: report FFO as DPLL vs input reference offset
authorIvan Vecera <ivecera@redhat.com>
Mon, 11 May 2026 15:58:16 +0000 (17:58 +0200)
committerJakub Kicinski <kuba@kernel.org>
Wed, 13 May 2026 01:48:07 +0000 (18:48 -0700)
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>
drivers/dpll/zl3073x/chan.c
drivers/dpll/zl3073x/chan.h
drivers/dpll/zl3073x/core.c
drivers/dpll/zl3073x/dpll.c
drivers/dpll/zl3073x/ref.h
drivers/dpll/zl3073x/regs.h

index 2f48ca2391494fe4316826b741caba56571a894f..2fe3c3da84bb586bc0087edb0bc037cba803521d 100644 (file)
@@ -18,6 +18,7 @@
 int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
 {
        struct zl3073x_chan *chan = &zldev->chan[index];
+       u64 val;
        int rc;
 
        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)
                return rc;
 
-       return zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
-                              &chan->refsel_status);
+       rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
+                            &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;
 }
 
 /**
index 481da2133202beae76896685c458f771fcf96539..4353809c6912230d0093783c346337319d4eef39 100644 (file)
@@ -17,6 +17,7 @@ struct zl3073x_dev;
  * @ref_prio: reference priority registers (4 bits per ref, P/N packed)
  * @mon_status: monitor 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_group(cfg,
@@ -26,6 +27,7 @@ struct zl3073x_chan {
        struct_group(stat,
                u8      mon_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);
 
+/**
+ * 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
  * @chan: pointer to channel state
index 5f1e70f3e40a0527ecdc033c70aff0638da69478..b3345060490dbb6168405edb557ab1e2e32a7394 100644 (file)
@@ -704,44 +704,6 @@ zl3073x_ref_freq_meas_update(struct zl3073x_dev *zldev)
        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
 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)
                zl3073x_dpll_changes_check(zldpll);
 
index 05e63661bf074b3b7805596a0a6e31907d5be1df..cff85cdb9d0e5fb717b5729ed660f6ff9648f0d1 100644 (file)
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 
+#include <linux/atomic.h>
 #include <linux/bits.h>
 #include <linux/bitfield.h>
 #include <linux/bug.h>
@@ -57,7 +58,7 @@ struct zl3073x_dpll_pin {
        s32                     phase_gran;
        enum dpll_pin_operstate operstate;
        s64                     phase_offset;
-       s64                     freq_offset;
+       atomic64_t              freq_offset;
        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;
 
-       ffo->ffo = pin->freq_offset;
+       if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
+               return -ENODATA;
+
+       ffo->ffo = atomic64_read(&pin->freq_offset);
 
        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 = {
-       .supported_ffo = BIT(DPLL_FFO_PORT_RXTX_RATE),
+       .supported_ffo = BIT(DPLL_FFO_PIN_DEVICE),
        .direction_get = zl3073x_dpll_pin_direction_get,
        .esync_get = zl3073x_dpll_input_pin_esync_get,
        .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
  *
- * Check for the given pin's fractional frequency change.
- *
- * Return: true on fractional frequency offset change, false otherwise
+ * Return: true on change, false otherwise
  */
 static bool
 zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
 {
        struct zl3073x_dpll *zldpll = pin->dpll;
        struct zl3073x_dev *zldev = zldpll->dev;
-       const struct zl3073x_ref *ref;
-       u8 ref_id;
+       const struct zl3073x_chan *chan;
        s64 ffo;
 
-       /* Get reference monitor status */
-       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))
+       if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
                return false;
 
-       /* Compare with previous value */
-       ffo = zl3073x_ref_ffo_get(ref);
-       if (pin->freq_offset != ffo) {
-               dev_dbg(zldev->dev, "%s freq offset changed: %lld -> %lld\n",
-                       pin->label, pin->freq_offset, ffo);
-               pin->freq_offset = ffo;
+       chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+       ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan),
+                             244140625, 36);
 
+       if (atomic64_xchg(&pin->freq_offset, ffo) != ffo) {
+               dev_dbg(zldev->dev, "%s freq offset changed to: %lld\n",
+                       pin->label, ffo);
                return true;
        }
 
index 55e80e4f08734c6213ec41aa9e7c48fba937ba6e..e140ca3ea17dce48cc9ccca5525b1fc99863826c 100644 (file)
@@ -22,7 +22,6 @@ struct zl3073x_dev;
  * @freq_ratio_n: FEC mode divisor
  * @sync_ctrl: reference sync control
  * @config: reference config
- * @ffo: current fractional frequency offset
  * @meas_freq: measured input frequency in Hz
  * @mon_status: reference monitor status
  */
@@ -40,7 +39,6 @@ struct zl3073x_ref {
                u8      config;
        );
        struct_group(stat, /* Status */
-               s64     ffo;
                u32     meas_freq;
                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);
 
-/**
- * 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
  * @ref: pointer to ref state
index 8015808bdf548c7eb2a9045d8cdc3bee72a258fa..9578f000952825fc44d57e9f70b69945b9c6bc51 100644 (file)
 #define ZL_DPLL_MODE_REFSEL_MODE_NCO           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_DPLL_MEAS_CTRL_EN                   BIT(0)
 #define ZL_DPLL_MEAS_CTRL_AVG_FACTOR           GENMASK(7, 4)
 #define ZL_REG_DPLL_PHASE_ERR_DATA(_idx)                               \
        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
  ***********************************/