]> www.infradead.org Git - users/hch/misc.git/commitdiff
dpll: zl3073x: Allow to configure phase offset averaging factor
authorIvan Vecera <ivecera@redhat.com>
Sat, 27 Sep 2025 08:49:12 +0000 (10:49 +0200)
committerJakub Kicinski <kuba@kernel.org>
Tue, 30 Sep 2025 01:57:41 +0000 (18:57 -0700)
The DPLL phase measurement block uses an exponential moving average with
a configurable averaging factor. Measurements are taken at approximately
40 Hz or at the reference frequency, whichever is lower.

Currently, factor=2 is used to prioritize fast response for dynamic
phase changes. For applications needing a stable, precise average phase
offset where rapid changes are unlikely, a higher factor is recommended.

Implement the .phase_offset_avg_factor_get/set callbacks to allow a user
to adjust this factor.

Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>
Link: https://patch.msgid.link/20250927084912.2343597-4-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/dpll/zl3073x/core.c
drivers/dpll/zl3073x/core.h
drivers/dpll/zl3073x/dpll.c
drivers/dpll/zl3073x/dpll.h

index e96095baac6574c379d12739489c4e1c947a75f4..092e7027948a45ec2d2bfd1a402af2bbfc9e850f 100644 (file)
@@ -956,6 +956,32 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
                                   msecs_to_jiffies(500));
 }
 
+int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor)
+{
+       u8 dpll_meas_ctrl, value;
+       int rc;
+
+       /* Read DPLL phase measurement control register */
+       rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, &dpll_meas_ctrl);
+       if (rc)
+               return rc;
+
+       /* Convert requested factor to register value */
+       value = (factor + 1) & 0x0f;
+
+       /* Update phase measurement control register */
+       dpll_meas_ctrl &= ~ZL_DPLL_MEAS_CTRL_AVG_FACTOR;
+       dpll_meas_ctrl |= FIELD_PREP(ZL_DPLL_MEAS_CTRL_AVG_FACTOR, value);
+       rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, dpll_meas_ctrl);
+       if (rc)
+               return rc;
+
+       /* Save the new factor */
+       zldev->phase_avg_factor = factor;
+
+       return 0;
+}
+
 /**
  * zl3073x_dev_phase_meas_setup - setup phase offset measurement
  * @zldev: pointer to zl3073x_dev structure
@@ -972,15 +998,16 @@ zl3073x_dev_phase_meas_setup(struct zl3073x_dev *zldev)
        u8 dpll_meas_ctrl, mask = 0;
        int rc;
 
+       /* Setup phase measurement averaging factor */
+       rc = zl3073x_dev_phase_avg_factor_set(zldev, zldev->phase_avg_factor);
+       if (rc)
+               return rc;
+
        /* Read DPLL phase measurement control register */
        rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, &dpll_meas_ctrl);
        if (rc)
                return rc;
 
-       /* Setup phase measurement averaging factor */
-       dpll_meas_ctrl &= ~ZL_DPLL_MEAS_CTRL_AVG_FACTOR;
-       dpll_meas_ctrl |= FIELD_PREP(ZL_DPLL_MEAS_CTRL_AVG_FACTOR, 3);
-
        /* Enable DPLL measurement block */
        dpll_meas_ctrl |= ZL_DPLL_MEAS_CTRL_EN;
 
@@ -1208,6 +1235,9 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev,
         */
        zldev->clock_id = get_random_u64();
 
+       /* Default phase offset averaging factor */
+       zldev->phase_avg_factor = 2;
+
        /* Initialize mutex for operations where multiple reads, writes
         * and/or polls are required to be done atomically.
         */
index 128fb899cafc3d0d2e10c5889fb6019ec2bf76ec..1dca4ddcf2350cec0a8f9e86c1727403748e3c42 100644 (file)
@@ -68,19 +68,19 @@ struct zl3073x_synth {
  * @dev: pointer to device
  * @regmap: regmap to access device registers
  * @multiop_lock: to serialize multiple register operations
- * @clock_id: clock id of the device
  * @ref: array of input references' invariants
  * @out: array of outs' invariants
  * @synth: array of synths' invariants
  * @dplls: list of DPLLs
  * @kworker: thread for periodic work
  * @work: periodic work
+ * @clock_id: clock id of the device
+ * @phase_avg_factor: phase offset measurement averaging factor
  */
 struct zl3073x_dev {
        struct device           *dev;
        struct regmap           *regmap;
        struct mutex            multiop_lock;
-       u64                     clock_id;
 
        /* Invariants */
        struct zl3073x_ref      ref[ZL3073X_NUM_REFS];
@@ -93,6 +93,10 @@ struct zl3073x_dev {
        /* Monitor */
        struct kthread_worker           *kworker;
        struct kthread_delayed_work     work;
+
+       /* Devlink parameters */
+       u64                     clock_id;
+       u8                      phase_avg_factor;
 };
 
 struct zl3073x_chip_info {
@@ -115,6 +119,13 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev,
 int zl3073x_dev_start(struct zl3073x_dev *zldev, bool full);
 void zl3073x_dev_stop(struct zl3073x_dev *zldev);
 
+static inline u8 zl3073x_dev_phase_avg_factor_get(struct zl3073x_dev *zldev)
+{
+       return zldev->phase_avg_factor;
+}
+
+int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor);
+
 /**********************
  * Registers operations
  **********************/
index 3e42e9e7fd2726bb21fb12a8a75318d7470dd2fa..93dc93eec79edb0ba5b71bfa2598143bfabaa3e8 100644 (file)
@@ -1576,6 +1576,59 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
        return 0;
 }
 
+static int
+zl3073x_dpll_phase_offset_avg_factor_get(const struct dpll_device *dpll,
+                                        void *dpll_priv, u32 *factor,
+                                        struct netlink_ext_ack *extack)
+{
+       struct zl3073x_dpll *zldpll = dpll_priv;
+
+       *factor = zl3073x_dev_phase_avg_factor_get(zldpll->dev);
+
+       return 0;
+}
+
+static void
+zl3073x_dpll_change_work(struct work_struct *work)
+{
+       struct zl3073x_dpll *zldpll;
+
+       zldpll = container_of(work, struct zl3073x_dpll, change_work);
+       dpll_device_change_ntf(zldpll->dpll_dev);
+}
+
+static int
+zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll,
+                                        void *dpll_priv, u32 factor,
+                                        struct netlink_ext_ack *extack)
+{
+       struct zl3073x_dpll *item, *zldpll = dpll_priv;
+       int rc;
+
+       if (factor > 15) {
+               NL_SET_ERR_MSG_FMT(extack,
+                                  "Phase offset average factor has to be from range <0,15>");
+               return -EINVAL;
+       }
+
+       rc = zl3073x_dev_phase_avg_factor_set(zldpll->dev, factor);
+       if (rc) {
+               NL_SET_ERR_MSG_FMT(extack,
+                                  "Failed to set phase offset averaging factor");
+               return rc;
+       }
+
+       /* The averaging factor is common for all DPLL channels so after change
+        * we have to send a notification for other DPLL devices.
+        */
+       list_for_each_entry(item, &zldpll->dev->dplls, list) {
+               if (item != zldpll)
+                       schedule_work(&item->change_work);
+       }
+
+       return 0;
+}
+
 static int
 zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll,
                                      void *dpll_priv,
@@ -1635,6 +1688,8 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
 static const struct dpll_device_ops zl3073x_dpll_device_ops = {
        .lock_status_get = zl3073x_dpll_lock_status_get,
        .mode_get = zl3073x_dpll_mode_get,
+       .phase_offset_avg_factor_get = zl3073x_dpll_phase_offset_avg_factor_get,
+       .phase_offset_avg_factor_set = zl3073x_dpll_phase_offset_avg_factor_set,
        .phase_offset_monitor_get = zl3073x_dpll_phase_offset_monitor_get,
        .phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set,
 };
@@ -1983,6 +2038,8 @@ zl3073x_dpll_device_unregister(struct zl3073x_dpll *zldpll)
 {
        WARN(!zldpll->dpll_dev, "DPLL device is not registered\n");
 
+       cancel_work_sync(&zldpll->change_work);
+
        dpll_device_unregister(zldpll->dpll_dev, &zl3073x_dpll_device_ops,
                               zldpll);
        dpll_device_put(zldpll->dpll_dev);
@@ -2258,6 +2315,7 @@ zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
        zldpll->dev = zldev;
        zldpll->id = ch;
        INIT_LIST_HEAD(&zldpll->pins);
+       INIT_WORK(&zldpll->change_work, zl3073x_dpll_change_work);
 
        return zldpll;
 }
index 304910ffc9c07d51f8cd5a4ec62220d7cd4cdd35..e8c39b44b356cdcc6a67a6e41e729f532f2575a7 100644 (file)
@@ -20,6 +20,7 @@
  * @dpll_dev: pointer to registered DPLL device
  * @lock_status: last saved DPLL lock status
  * @pins: list of pins
+ * @change_work: device change notification work
  */
 struct zl3073x_dpll {
        struct list_head                list;
@@ -32,6 +33,7 @@ struct zl3073x_dpll {
        struct dpll_device              *dpll_dev;
        enum dpll_lock_status           lock_status;
        struct list_head                pins;
+       struct work_struct              change_work;
 };
 
 struct zl3073x_dpll *zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch);