[PATCH 26/28] dpll: zl3073x: Add support to get/set phase adjust on pins

Ivan Vecera
2025-04-07T17:33:52Z

This adds support to get/set phase adjustment for both input and output
pins. The phase adjustment is implemented using reference and output
phase offset compensation registers. For input pins the adjustment value
can be arbitrary number but for outputs the value has to be a multiple
of half synthesizer clock cycles.

Reviewed-by: Michal Schmidt <mschmidt@redhat.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>
---
drivers/dpll/dpll_zl3073x.c | 182 ++++++++++++++++++++++++++++++++++++
1 file changed, 182 insertions(+)

diff --git a/drivers/dpll/dpll_zl3073x.c b/drivers/dpll/dpll_zl3073x.c
index 3b28d229dd4be..b1f38f000899b 100644
--- a/drivers/dpll/dpll_zl3073x.c
+++ b/drivers/dpll/dpll_zl3073x.c
@@ -79,6 +79,7 @@ ZL3073X_REG16_DEF(ref_freq_base, 0x505);
ZL3073X_REG16_DEF(ref_freq_mult, 0x507);
ZL3073X_REG16_DEF(ref_ratio_m, 0x509);
ZL3073X_REG16_DEF(ref_ratio_n, 0x50b);
+ZL3073X_REG48_DEF(ref_phase_compensation, 0x528);

/*
* Register Map Page 12, DPLL Mailbox
@@ -98,6 +99,8 @@ ZL3073X_REG32_DEF(output_width, 0x710);
ZL3073X_REG32_DEF(output_ndiv_period, 0x714);
ZL3073X_REG32_DEF(output_ndiv_width, 0x718);

+ZL3073X_REG32_DEF(output_phase_compensation, 0x720);
+
#define ZL3073X_REF_NONE ZL3073X_NUM_INPUT_PINS
#define ZL3073X_REF_IS_VALID(_ref) ((_ref) != ZL3073X_REF_NONE)

@@ -690,6 +693,85 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
return rc;
}

+static int
+zl3073x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ s32 *phase_adjust,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ s64 phase_comp;
+ u8 ref_id;
+ int rc;
+
+ guard(zl3073x)(zldev);
+
+ /* Get index of the pin */
+ ref_id = zl3073x_dpll_pin_index_get(pin);
+
+ /* Read reference configuration into mailbox */
+ rc = zl3073x_mb_ref_read(zldev, ref_id);
+ if (rc)
+ return rc;
+
+ /* Read current phase offset compensation */
+ rc = zl3073x_read_ref_phase_compensation(zldev, &phase_comp);
+ if (rc)
+ return rc;
+
+ /* Perform sign extension for 48bit signed value */
+ phase_comp = sign_extend64(phase_comp, 47);
+
+ /* Reverse two's complement negation applied during set and convert
+ * to 32bit signed int
+ */
+ *phase_adjust = (s32) -phase_comp;
+
+ return rc;
+}
+
+static int
+zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ s32 phase_adjust,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ s64 phase_comp;
+ u8 ref_id;
+ int rc;
+
+ guard(zl3073x)(zldev);
+
+ /* The value in the register is stored as two's complement negation
+ * of requested value.
+ */
+ phase_comp = (s64) -phase_adjust;
+
+ /* Write the requested value into the compensation register */
+ rc = zl3073x_write_ref_phase_compensation(zldev, phase_comp);
+ if (rc)
+ return rc;
+
+ /* Get index of the pin */
+ ref_id = zl3073x_dpll_pin_index_get(pin);
+
+ /* Update reference configuration from mailbox */
+ rc = zl3073x_mb_ref_write(zldev, ref_id);
+ if (rc)
+ return rc;
+
+ return rc;
+}
+
static int
zl3073x_dpll_ref_prio_get(struct zl3073x_dpll_pin *pin, u8 *prio)
{
@@ -1165,6 +1247,102 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
return zl3073x_mb_output_write(zldev, output);
}

+static int
+zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ s32 *phase_adjust,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ u8 output, synth;
+ u64 synth_freq;
+ s32 phase_comp;
+ int rc;
+
+ guard(zl3073x)(zldev);
+
+ output = zl3073x_dpll_output_pin_output_get(pin);
+ synth = zl3073x_dpll_pin_synth_get(pin);
+ synth_freq = zl3073x_synth_freq_get(zldev, synth);
+
+ /* Read output configuration into mailbox */
+ rc = zl3073x_mb_output_read(zldev, output);
+ if (rc)
+ return rc;
+
+ /* Read current output phase compensation */
+ rc = zl3073x_read_output_phase_compensation(zldev, &phase_comp);
+ if (rc)
+ return rc;
+
+ /* Value in register is expressed in half synth clock cycles */
+ phase_comp *= (int)div_u64(PSEC_PER_SEC, 2 * synth_freq);
+
+ /* Reverse two's complement negation applied during 'set' */
+ *phase_adjust = -phase_comp;
+
+ return rc;
+}
+
+static int
+zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ s32 phase_adjust,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->mfd;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ int half_synth_cycle;
+ u8 output, synth;
+ u64 synth_freq;
+ int phase_comp;
+ int rc;
+
+ /* Get attached synth */
+ synth = zl3073x_dpll_pin_synth_get(pin);
+
+ /* Get synth's frequency */
+ synth_freq = zl3073x_synth_freq_get(zldev, synth);
+
+ /* Value in register is expressed in half synth clock cycles so
+ * the given phase adjustment a multiple of half synth clock.
+ */
+ half_synth_cycle = (int)div_u64(PSEC_PER_SEC, 2 * synth_freq);
+
+ if ((phase_adjust % half_synth_cycle) != 0) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "Phase adjustment value has to be multiple of %d",
+ half_synth_cycle);
+ return -EINVAL;
+ }
+ phase_adjust /= half_synth_cycle;
+
+ guard(zl3073x)(zldev);
+
+ /* The value in the register is stored as two's complement negation
+ * of requested value.
+ */
+ phase_comp = -phase_adjust;
+
+ /* Write the requested value into the compensation register */
+ rc = zl3073x_write_output_phase_compensation(zldev, phase_comp);
+ if (rc)
+ return rc;
+
+ /* Update output configuration from mailbox */
+ output = zl3073x_dpll_output_pin_output_get(pin);
+ rc = zl3073x_mb_output_write(zldev, output);
+
+ return rc;
+}
+
static int
zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
@@ -1243,6 +1421,8 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
.frequency_get = zl3073x_dpll_input_pin_frequency_get,
.frequency_set = zl3073x_dpll_input_pin_frequency_set,
.phase_offset_get = zl3073x_dpll_input_pin_phase_offset_get,
+ .phase_adjust_get = zl3073x_dpll_input_pin_phase_adjust_get,
+ .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,
.state_on_dpll_get = zl3073x_dpll_input_pin_state_on_dpll_get,
@@ -1253,6 +1433,8 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
.direction_get = zl3073x_dpll_pin_direction_get,
.frequency_get = zl3073x_dpll_output_pin_frequency_get,
.frequency_set = zl3073x_dpll_output_pin_frequency_set,
+ .phase_adjust_get = zl3073x_dpll_output_pin_phase_adjust_get,
+ .phase_adjust_set = zl3073x_dpll_output_pin_phase_adjust_set,
.state_on_dpll_get = zl3073x_dpll_output_pin_state_on_dpll_get,
};

--
2.48.1