]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
dpll: zl3073x: add ref-sync pair support
authorIvan Vecera <ivecera@redhat.com>
Wed, 8 Apr 2026 10:27:16 +0000 (12:27 +0200)
committerJakub Kicinski <kuba@kernel.org>
Sun, 12 Apr 2026 15:27:34 +0000 (08:27 -0700)
Add support for ref-sync pair registration using the 'ref-sync-sources'
phandle property from device tree. A ref-sync pair consists of a clock
reference and a low-frequency sync signal where the DPLL locks to the
clock reference but phase-aligns to the sync reference.

The implementation:
- Stores fwnode handle in zl3073x_dpll_pin during pin registration
- Adds ref_sync_get/set callbacks to read and write the sync control
  mode and pair registers
- Validates ref-sync frequency constraints: sync signal must be 8 kHz
  or less, clock reference must be 1 kHz or more and higher than sync
- Excludes sync source from automatic reference selection by setting
  its priority to NONE on connect; on disconnect the priority is left
  as NONE and the user must explicitly make the pin selectable again
- Iterates ref-sync-sources phandles to register declared pairings
  via dpll_pin_ref_sync_pair_add()

Reviewed-by: Petr Oros <poros@redhat.com>
Reviewed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260408102716.443099-6-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/dpll/zl3073x/dpll.c

index dc649cf103cb4564a7fc681d1d06f4b02f5451c1..c95e93ef3ab04ad810f59b6c4db009ff6e7b5822 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/module.h>
 #include <linux/netlink.h>
 #include <linux/platform_device.h>
+#include <linux/property.h>
 #include <linux/slab.h>
 #include <linux/sprintf.h>
 
@@ -30,6 +31,7 @@
  * @dpll: DPLL the pin is registered to
  * @dpll_pin: pointer to registered dpll_pin
  * @tracker: tracking object for the acquired reference
+ * @fwnode: firmware node handle
  * @label: package label
  * @dir: pin direction
  * @id: pin id
@@ -46,6 +48,7 @@ struct zl3073x_dpll_pin {
        struct zl3073x_dpll     *dpll;
        struct dpll_pin         *dpll_pin;
        dpll_tracker            tracker;
+       struct fwnode_handle    *fwnode;
        char                    label[8];
        enum dpll_pin_direction dir;
        u8                      id;
@@ -186,6 +189,109 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin,
        return zl3073x_ref_state_set(zldev, ref_id, &ref);
 }
 
+static int
+zl3073x_dpll_input_pin_ref_sync_get(const struct dpll_pin *dpll_pin,
+                                   void *pin_priv,
+                                   const struct dpll_pin *ref_sync_pin,
+                                   void *ref_sync_pin_priv,
+                                   enum dpll_pin_state *state,
+                                   struct netlink_ext_ack *extack)
+{
+       struct zl3073x_dpll_pin *sync_pin = ref_sync_pin_priv;
+       struct zl3073x_dpll_pin *pin = pin_priv;
+       struct zl3073x_dpll *zldpll = pin->dpll;
+       struct zl3073x_dev *zldev = zldpll->dev;
+       const struct zl3073x_ref *ref;
+       u8 ref_id, mode, pair;
+
+       ref_id = zl3073x_input_pin_ref_get(pin->id);
+       ref = zl3073x_ref_state_get(zldev, ref_id);
+       mode = zl3073x_ref_sync_mode_get(ref);
+       pair = zl3073x_ref_sync_pair_get(ref);
+
+       if (mode == ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR &&
+           pair == zl3073x_input_pin_ref_get(sync_pin->id))
+               *state = DPLL_PIN_STATE_CONNECTED;
+       else
+               *state = DPLL_PIN_STATE_DISCONNECTED;
+
+       return 0;
+}
+
+static int
+zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin,
+                                   void *pin_priv,
+                                   const struct dpll_pin *ref_sync_pin,
+                                   void *ref_sync_pin_priv,
+                                   const enum dpll_pin_state state,
+                                   struct netlink_ext_ack *extack)
+{
+       struct zl3073x_dpll_pin *sync_pin = ref_sync_pin_priv;
+       struct zl3073x_dpll_pin *pin = pin_priv;
+       struct zl3073x_dpll *zldpll = pin->dpll;
+       struct zl3073x_dev *zldev = zldpll->dev;
+       u8 mode, ref_id, sync_ref_id;
+       struct zl3073x_chan chan;
+       struct zl3073x_ref ref;
+       int rc;
+
+       ref_id = zl3073x_input_pin_ref_get(pin->id);
+       sync_ref_id = zl3073x_input_pin_ref_get(sync_pin->id);
+       ref = *zl3073x_ref_state_get(zldev, ref_id);
+
+       if (state == DPLL_PIN_STATE_CONNECTED) {
+               const struct zl3073x_ref *sync_ref;
+               u32 ref_freq, sync_freq;
+
+               sync_ref = zl3073x_ref_state_get(zldev, sync_ref_id);
+               ref_freq = zl3073x_ref_freq_get(&ref);
+               sync_freq = zl3073x_ref_freq_get(sync_ref);
+
+               /* Sync signal must be 8 kHz or less and clock reference
+                * must be 1 kHz or more and higher than the sync signal.
+                */
+               if (sync_freq > 8000) {
+                       NL_SET_ERR_MSG(extack,
+                                      "sync frequency must be 8 kHz or less");
+                       return -EINVAL;
+               }
+               if (ref_freq < 1000) {
+                       NL_SET_ERR_MSG(extack,
+                                      "clock frequency must be 1 kHz or more");
+                       return -EINVAL;
+               }
+               if (ref_freq <= sync_freq) {
+                       NL_SET_ERR_MSG(extack,
+                                      "clock frequency must be higher than sync frequency");
+                       return -EINVAL;
+               }
+
+               zl3073x_ref_sync_pair_set(&ref, sync_ref_id);
+               mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR;
+       } else {
+               mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF;
+       }
+
+       zl3073x_ref_sync_mode_set(&ref, mode);
+
+       rc = zl3073x_ref_state_set(zldev, ref_id, &ref);
+       if (rc)
+               return rc;
+
+       /* Exclude sync source from automatic reference selection by setting
+        * its priority to NONE. On disconnect the priority is left as NONE
+        * and the user must explicitly make the pin selectable again.
+        */
+       if (state == DPLL_PIN_STATE_CONNECTED) {
+               chan = *zl3073x_chan_state_get(zldev, zldpll->id);
+               zl3073x_chan_ref_prio_set(&chan, sync_ref_id,
+                                         ZL_DPLL_REF_PRIO_NONE);
+               return zl3073x_chan_state_set(zldev, zldpll->id, &chan);
+       }
+
+       return 0;
+}
+
 static int
 zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
                               const struct dpll_device *dpll, void *dpll_priv,
@@ -1147,6 +1253,8 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
        .phase_adjust_set = zl3073x_dpll_input_pin_phase_adjust_set,
        .prio_get = zl3073x_dpll_input_pin_prio_get,
        .prio_set = zl3073x_dpll_input_pin_prio_set,
+       .ref_sync_get = zl3073x_dpll_input_pin_ref_sync_get,
+       .ref_sync_set = zl3073x_dpll_input_pin_ref_sync_set,
        .state_on_dpll_get = zl3073x_dpll_input_pin_state_on_dpll_get,
        .state_on_dpll_set = zl3073x_dpll_input_pin_state_on_dpll_set,
 };
@@ -1239,8 +1347,11 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
        if (IS_ERR(props))
                return PTR_ERR(props);
 
-       /* Save package label, esync capability and phase adjust granularity */
+       /* Save package label, fwnode, esync capability and phase adjust
+        * granularity.
+        */
        strscpy(pin->label, props->package_label);
+       pin->fwnode = fwnode_handle_get(props->fwnode);
        pin->esync_control = props->esync_control;
        pin->phase_gran = props->dpll_props.phase_gran;
 
@@ -1285,6 +1396,8 @@ err_register:
        dpll_pin_put(pin->dpll_pin, &pin->tracker);
        pin->dpll_pin = NULL;
 err_pin_get:
+       fwnode_handle_put(pin->fwnode);
+       pin->fwnode = NULL;
        zl3073x_pin_props_put(props);
 
        return rc;
@@ -1314,6 +1427,9 @@ zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin)
 
        dpll_pin_put(pin->dpll_pin, &pin->tracker);
        pin->dpll_pin = NULL;
+
+       fwnode_handle_put(pin->fwnode);
+       pin->fwnode = NULL;
 }
 
 /**
@@ -1827,6 +1943,88 @@ zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
        kfree(zldpll);
 }
 
+/**
+ * zl3073x_dpll_ref_sync_pair_register - register ref_sync pairs for a pin
+ * @pin: pointer to zl3073x_dpll_pin structure
+ *
+ * Iterates 'ref-sync-sources' phandles in the pin's firmware node and
+ * registers each declared pairing.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_dpll_ref_sync_pair_register(struct zl3073x_dpll_pin *pin)
+{
+       struct zl3073x_dev *zldev = pin->dpll->dev;
+       struct fwnode_handle *fwnode;
+       struct dpll_pin *sync_pin;
+       dpll_tracker tracker;
+       int n, rc;
+
+       for (n = 0; ; n++) {
+               /* Get n'th ref-sync source */
+               fwnode = fwnode_find_reference(pin->fwnode, "ref-sync-sources",
+                                              n);
+               if (IS_ERR(fwnode)) {
+                       rc = PTR_ERR(fwnode);
+                       break;
+               }
+
+               /* Find associated dpll pin */
+               sync_pin = fwnode_dpll_pin_find(fwnode, &tracker);
+               fwnode_handle_put(fwnode);
+               if (!sync_pin) {
+                       dev_warn(zldev->dev, "%s: ref-sync source %d not found",
+                                pin->label, n);
+                       continue;
+               }
+
+               /* Register new ref-sync pair */
+               rc = dpll_pin_ref_sync_pair_add(pin->dpll_pin, sync_pin);
+               dpll_pin_put(sync_pin, &tracker);
+
+               /* -EBUSY means pairing already exists from another DPLL's
+                * registration.
+                */
+               if (rc && rc != -EBUSY) {
+                       dev_err(zldev->dev,
+                               "%s: failed to add ref-sync source %d: %pe",
+                               pin->label, n, ERR_PTR(rc));
+                       break;
+               }
+       }
+
+       return rc != -ENOENT ? rc : 0;
+}
+
+/**
+ * zl3073x_dpll_ref_sync_pairs_register - register ref_sync pairs for a DPLL
+ * @zldpll: pointer to zl3073x_dpll structure
+ *
+ * Iterates all registered input pins of the given DPLL and establishes
+ * ref_sync pairings declared by 'ref-sync-sources' phandles in the
+ * device tree.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll)
+{
+       struct zl3073x_dpll_pin *pin;
+       int rc;
+
+       list_for_each_entry(pin, &zldpll->pins, list) {
+               if (!zl3073x_dpll_is_input_pin(pin) || !pin->fwnode)
+                       continue;
+
+               rc = zl3073x_dpll_ref_sync_pair_register(pin);
+               if (rc)
+                       return rc;
+       }
+
+       return 0;
+}
+
 /**
  * zl3073x_dpll_register - register DPLL device and all its pins
  * @zldpll: pointer to zl3073x_dpll structure
@@ -1850,6 +2048,13 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll)
                return rc;
        }
 
+       rc = zl3073x_dpll_ref_sync_pairs_register(zldpll);
+       if (rc) {
+               zl3073x_dpll_pins_unregister(zldpll);
+               zl3073x_dpll_device_unregister(zldpll);
+               return rc;
+       }
+
        return 0;
 }