]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
dpll: zl3073x: Implement phase offset monitor feature
authorIvan Vecera <ivecera@redhat.com>
Tue, 15 Jul 2025 14:46:31 +0000 (16:46 +0200)
committerPaolo Abeni <pabeni@redhat.com>
Thu, 17 Jul 2025 13:31:55 +0000 (15:31 +0200)
Implement phase offset monitor feature to allow a user to monitor
phase offsets across all available inputs.

The device firmware periodically performs phase offsets measurements for
all available DPLL channels and input references. The driver can ask
the firmware to fill appropriate latch registers with measured values.

There are 2 sets of latch registers for phase offsets reporting:

1) DPLL-to-connected-ref: up to 5 registers that contain values for
   phase offset between particular DPLL channel and its connected input
   reference.
2) selected-DPLL-to-ref: 10  registers that contain values for phase
   offsets between selected DPLL channel and all available input
   references.

Both are filled with single read request so the driver can read
DPLL-to-connected-ref phase offset for all DPLL channels at once.
This was implemented in the previous patch.

To read selected-DPLL-to-ref registers for all DPLLs a separate read
request has to be sent to device firmware for each DPLL channel.

To implement phase offset monitor feature:
* Extend zl3073x_ref_phase_offsets_update() to select given DPLL channel
  in phase offset read request. The caller can set channel==-1 if it
  will not read Type2 registers.
* Use this extended function to update phase offset latch registers
  during zl3073x_dpll_changes_check() call if phase monitor is enabled
* Extend zl3073x_dpll_pin_phase_offset_check() to check phase offset
  changes for all available input references
* Extend zl3073x_dpll_input_pin_phase_offset_get() to report phase
  offset values for all available input references
* Implement phase offset monitor callbacks to enable/disable this
  feature

Reviewed-by: Jiri Pirko <jiri@nvidia.com>
Tested-by: Prathosh Satish <prathosh.satish@microchip.com>
Co-developed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20250715144633.149156-4-ivecera@redhat.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
drivers/dpll/zl3073x/core.c
drivers/dpll/zl3073x/core.h
drivers/dpll/zl3073x/dpll.c
drivers/dpll/zl3073x/dpll.h
drivers/dpll/zl3073x/regs.h

index c980c85e7ac5171512ad532725e9c92db79fd43e..eb62a492b172773d150a83b331fd643281b1febd 100644 (file)
@@ -672,14 +672,25 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev)
 /**
  * zl3073x_ref_phase_offsets_update - update reference phase offsets
  * @zldev: pointer to zl3073x_dev structure
+ * @channel: DPLL channel number or -1
  *
- * Ask device to update phase offsets latch registers with the latest
- * measured values.
+ * The function asks device to update phase offsets latch registers with
+ * the latest measured values. There are 2 sets of latch registers:
+ *
+ * 1) Up to 5 DPLL-to-connected-ref registers that contain phase offset
+ *    values between particular DPLL channel and its *connected* input
+ *    reference.
+ *
+ * 2) 10 selected-DPLL-to-all-ref registers that contain phase offset values
+ *    between selected DPLL channel and all input references.
+ *
+ * If the caller is interested in 2) then it has to pass DPLL channel number
+ * in @channel parameter. If it is interested only in 1) then it should pass
+ * @channel parameter with value of -1.
  *
  * Return: 0 on success, <0 on error
  */
-static int
-zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev)
+int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
 {
        int rc;
 
@@ -691,6 +702,13 @@ zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev)
        if (rc)
                return rc;
 
+       /* Select DPLL channel if it is specified */
+       if (channel != -1) {
+               rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_IDX, channel);
+               if (rc)
+                       return rc;
+       }
+
        /* Request to update phase offsets measurement values */
        rc = zl3073x_write_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
                              ZL_REF_PHASE_ERR_READ_RQST_RD);
@@ -711,7 +729,7 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
        int rc;
 
        /* Update DPLL-to-connected-ref phase offsets registers */
-       rc = zl3073x_ref_phase_offsets_update(zldev);
+       rc = zl3073x_ref_phase_offsets_update(zldev, -1);
        if (rc)
                dev_warn(zldev->dev, "Failed to update phase offsets: %pe\n",
                         ERR_PTR(rc));
index 97b1032e392d647dc09f3c1013415519e0dd0034..1a5edc4975735156c2372cd2eb4b30f431b9923e 100644 (file)
@@ -130,6 +130,7 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val);
  *****************/
 
 int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult);
+int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel);
 
 static inline bool
 zl3073x_is_n_pin(u8 id)
index 90a99cf91816d9d6b21adfef70c438cb2fedf0e7..11a7c4a58e2572fdf767446514b1877b08fb894d 100644 (file)
@@ -509,6 +509,7 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
        struct zl3073x_dev *zldev = zldpll->dev;
        struct zl3073x_dpll_pin *pin = pin_priv;
        u8 conn_ref, ref, ref_status;
+       s64 ref_phase;
        int rc;
 
        /* Get currently connected reference */
@@ -516,9 +517,11 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
        if (rc)
                return rc;
 
-       /* Report phase offset only for currently connected pin */
+       /* Report phase offset only for currently connected pin if the phase
+        * monitor feature is disabled.
+        */
        ref = zl3073x_input_pin_ref_get(pin->id);
-       if (ref != conn_ref) {
+       if (!zldpll->phase_monitor && ref != conn_ref) {
                *phase_offset = 0;
 
                return 0;
@@ -536,8 +539,37 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
                return 0;
        }
 
-       /* Report the latest measured phase offset for the connected ref */
-       *phase_offset = pin->phase_offset * DPLL_PHASE_OFFSET_DIVIDER;
+       ref_phase = pin->phase_offset;
+
+       /* The DPLL being locked to a higher freq than the current ref
+        * the phase offset is modded to the period of the signal
+        * the dpll is locked to.
+        */
+       if (ZL3073X_DPLL_REF_IS_VALID(conn_ref) && conn_ref != ref) {
+               u32 conn_freq, ref_freq;
+
+               /* Get frequency of connected ref */
+               rc = zl3073x_dpll_input_ref_frequency_get(zldpll, conn_ref,
+                                                         &conn_freq);
+               if (rc)
+                       return rc;
+
+               /* Get frequency of given ref */
+               rc = zl3073x_dpll_input_ref_frequency_get(zldpll, ref,
+                                                         &ref_freq);
+               if (rc)
+                       return rc;
+
+               if (conn_freq > ref_freq) {
+                       s64 conn_period, div_factor;
+
+                       conn_period = div_s64(PSEC_PER_SEC, conn_freq);
+                       div_factor = div64_s64(ref_phase, conn_period);
+                       ref_phase -= conn_period * div_factor;
+               }
+       }
+
+       *phase_offset = ref_phase * DPLL_PHASE_OFFSET_DIVIDER;
 
        return rc;
 }
@@ -1343,6 +1375,35 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
        return 0;
 }
 
+static int
+zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll,
+                                     void *dpll_priv,
+                                     enum dpll_feature_state *state,
+                                     struct netlink_ext_ack *extack)
+{
+       struct zl3073x_dpll *zldpll = dpll_priv;
+
+       if (zldpll->phase_monitor)
+               *state = DPLL_FEATURE_STATE_ENABLE;
+       else
+               *state = DPLL_FEATURE_STATE_DISABLE;
+
+       return 0;
+}
+
+static int
+zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll,
+                                     void *dpll_priv,
+                                     enum dpll_feature_state state,
+                                     struct netlink_ext_ack *extack)
+{
+       struct zl3073x_dpll *zldpll = dpll_priv;
+
+       zldpll->phase_monitor = (state == DPLL_FEATURE_STATE_ENABLE);
+
+       return 0;
+}
+
 static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
        .direction_get = zl3073x_dpll_pin_direction_get,
        .esync_get = zl3073x_dpll_input_pin_esync_get,
@@ -1368,6 +1429,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_monitor_get = zl3073x_dpll_phase_offset_monitor_get,
+       .phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set,
 };
 
 /**
@@ -1733,16 +1796,47 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
 {
        struct zl3073x_dpll *zldpll = pin->dpll;
        struct zl3073x_dev *zldev = zldpll->dev;
+       unsigned int reg;
        s64 phase_offset;
+       u8 ref;
        int rc;
 
-       /* Do not check phase offset if the pin is not connected one */
-       if (pin->pin_state != DPLL_PIN_STATE_CONNECTED)
+       ref = zl3073x_input_pin_ref_get(pin->id);
+
+       /* Select register to read phase offset value depending on pin and
+        * phase monitor state:
+        * 1) For connected pin use dpll_phase_err_data register
+        * 2) For other pins use appropriate ref_phase register if the phase
+        *    monitor feature is enabled and reference monitor does not
+        *    report signal errors for given input pin
+        */
+       if (pin->pin_state == DPLL_PIN_STATE_CONNECTED) {
+               reg = ZL_REG_DPLL_PHASE_ERR_DATA(zldpll->id);
+       } else if (zldpll->phase_monitor) {
+               u8 status;
+
+               /* Get reference monitor status */
+               rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref),
+                                    &status);
+               if (rc) {
+                       dev_err(zldev->dev,
+                               "Failed to read %s refmon status: %pe\n",
+                               pin->label, ERR_PTR(rc));
+
+                       return false;
+               }
+
+               if (status != ZL_REF_MON_STATUS_OK)
+                       return false;
+
+               reg = ZL_REG_REF_PHASE(ref);
+       } else {
+               /* The pin is not connected or phase monitor disabled */
                return false;
+       }
 
-       /* Read DPLL-to-connected-ref phase offset measurement value */
-       rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_PHASE_ERR_DATA(zldpll->id),
-                             &phase_offset);
+       /* Read measured phase offset value */
+       rc = zl3073x_read_u48(zldev, reg, &phase_offset);
        if (rc) {
                dev_err(zldev->dev, "Failed to read ref phase offset: %pe\n",
                        ERR_PTR(rc));
@@ -1807,6 +1901,19 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
            zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_REFLOCK)
                return;
 
+       /* Update phase offset latch registers for this DPLL if the phase
+        * offset monitor feature is enabled.
+        */
+       if (zldpll->phase_monitor) {
+               rc = zl3073x_ref_phase_offsets_update(zldev, zldpll->id);
+               if (rc) {
+                       dev_err(zldev->dev,
+                               "Failed to update phase offsets: %pe\n",
+                               ERR_PTR(rc));
+                       return;
+               }
+       }
+
        list_for_each_entry(pin, &zldpll->pins, list) {
                enum dpll_pin_state state;
                bool pin_changed = false;
index 2e84e56f8c9e1bfc97e308acaf274ee3a2e0fe71..304910ffc9c07d51f8cd5a4ec62220d7cd4cdd35 100644 (file)
@@ -16,6 +16,7 @@
  * @refsel_mode: reference selection mode
  * @forced_ref: selected reference in forced reference lock mode
  * @check_count: periodic check counter
+ * @phase_monitor: is phase offset monitor enabled
  * @dpll_dev: pointer to registered DPLL device
  * @lock_status: last saved DPLL lock status
  * @pins: list of pins
@@ -27,6 +28,7 @@ struct zl3073x_dpll {
        u8                              refsel_mode;
        u8                              forced_ref;
        u8                              check_count;
+       bool                            phase_monitor;
        struct dpll_device              *dpll_dev;
        enum dpll_lock_status           lock_status;
        struct list_head                pins;
index 8dde92e623f768e9848d3ff51acc8466d2355a24..9ee2f44a2eec737f54ce59495482ee7247783eb2 100644 (file)
 #define ZL_REG_REF_PHASE_ERR_READ_RQST         ZL_REG(4, 0x0f, 1)
 #define ZL_REF_PHASE_ERR_READ_RQST_RD          BIT(0)
 
+#define ZL_REG_REF_PHASE(_idx)                                         \
+       ZL_REG_IDX(_idx, 4, 0x20, 6, ZL3073X_NUM_REFS, 6)
+
 /***********************
  * Register Page 5, DPLL
  ***********************/
 #define ZL_DPLL_MEAS_CTRL_EN                   BIT(0)
 #define ZL_DPLL_MEAS_CTRL_AVG_FACTOR           GENMASK(7, 4)
 
+#define ZL_REG_DPLL_MEAS_IDX                   ZL_REG(5, 0x51, 1)
+#define ZL_DPLL_MEAS_IDX                       GENMASK(2, 0)
+
 #define ZL_REG_DPLL_PHASE_ERR_READ_MASK                ZL_REG(5, 0x54, 1)
 
 #define ZL_REG_DPLL_PHASE_ERR_DATA(_idx)                               \