mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net-next 0/6] dpll: zl3073x: add PTP periodic output support
@ 2026-09-28 18:55 Ivan Vecera
  2026-09-28 18:55 ` [PATCH net-next 1/6] dpll: zl3073x: notify sibling pin when shared output config changes Ivan Vecera
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Ivan Vecera @ 2026-09-28 18:55 UTC (permalink / raw)
  To: netdev
  Cc: Arkadiusz Kubalewski, Jakub Kicinski, Jiri Pirko, Min Li,
	Paolo Abeni, Petr Oros, Richard Cochran, Vadim Fedorenko,
	linux-kernel

This series extends the zl3073x output pin support and finally adds PTP
periodic output (perout) support on top of it.

Each zl3073x output exposes a P-pin and an N-pin that share a single HW
output and most of its configuration registers. Patch 1 makes a change
requested on one pin notify the sibling pin as well, so userspace on the
sibling is told when its effective frequency, esync or phase adjustment
changed.

Patches 2 and 3 add the plumbing to control an individual output pin:
GPO override registers and a clean stop/restart of an output (patch 2),
and then per-pin enable/disable through the state_on_dpll_get/set
callbacks - differential pins via the output stop condition, CMOS pins
via the GPO override bracketed by a glitch-free stop/restart (patch 3).

Patches 4 and 5 are preparatory: the per-pin 'esync_control' boolean is
replaced by an extensible 'caps' bitmap, and the divisor/N-division
computation is factored out of frequency_set() into a helper that
updates a struct zl3073x_out without committing it.

Patch 6 registers a PTP periodic output pin for each output pin that
supports step-time and declares 1 PPS support in firmware. Any perout
channel can be assigned to any such pin; only 1 PPS is supported.
Enabling a channel programs its pin for 1 Hz and connects it, disabling
disconnects it, reusing the helper and connect/disconnect primitive
introduced earlier in the series.

Ivan Vecera (6):
  dpll: zl3073x: notify sibling pin when shared output config changes
  dpll: zl3073x: add GPO support for output pins
  dpll: zl3073x: allow enabling/disabling output pins
  dpll: zl3073x: consolidate pin capabilities into bitmap
  dpll: zl3073x: factor out output pin frequency helper
  dpll: zl3073x: add PTP periodic output support

 drivers/dpll/zl3073x/core.c | 117 ++++++++
 drivers/dpll/zl3073x/core.h |  30 ++
 drivers/dpll/zl3073x/dpll.c | 539 +++++++++++++++++++++++++++++++-----
 drivers/dpll/zl3073x/dpll.h |   4 +
 drivers/dpll/zl3073x/out.c  |  50 +++-
 drivers/dpll/zl3073x/out.h  | 138 ++++++++-
 drivers/dpll/zl3073x/prop.c |   2 +
 drivers/dpll/zl3073x/prop.h |  21 ++
 drivers/dpll/zl3073x/regs.h |  20 ++
 9 files changed, 834 insertions(+), 87 deletions(-)


base-commit: 014d795c73837ea2339a4ea8e8f82c6e959b845d
-- 
2.55.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH net-next 1/6] dpll: zl3073x: notify sibling pin when shared output config changes
  2026-09-28 18:55 [PATCH net-next 0/6] dpll: zl3073x: add PTP periodic output support Ivan Vecera
@ 2026-09-28 18:55 ` Ivan Vecera
  2026-09-28 18:55 ` [PATCH net-next 2/6] dpll: zl3073x: add GPO support for output pins Ivan Vecera
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Ivan Vecera @ 2026-09-28 18:55 UTC (permalink / raw)
  To: netdev
  Cc: Chris du Quesnay, Arkadiusz Kubalewski, Jakub Kicinski,
	Jiri Pirko, Min Li, Paolo Abeni, Petr Oros, Richard Cochran,
	Vadim Fedorenko, linux-kernel

Each zl3073x output has a P-pin and an N-pin that share a single HW
output and, outside N-pin divide mode, share the output's divisor,
clock type, esync period/width and phase compensation registers.
Changing one of these settings through one pin's dpll_pin therefore
also changes the other (sibling) pin's effective configuration, but
only the pin the change was requested on gets a dpll_pin_change_ntf()
notification - userspace listening on the sibling pin is never told
its frequency, esync configuration or phase adjustment changed.

Add zl3073x_dpll_output_pin_sibling_get() to look up the other pin of
an output pair, and use it in frequency_set() (for the non-N-divided
signal formats, where the output divisor is shared), esync_set() and
phase_adjust_set() to notify the sibling pin, if it is registered,
whenever the shared HW state actually changes.

Tested-by: Chris du Quesnay <Chris.duQuesnay@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 drivers/dpll/zl3073x/dpll.c | 144 ++++++++++++++++++++++++++++--------
 1 file changed, 115 insertions(+), 29 deletions(-)

diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index f2e137475b40ff..2c6de4dab8b4ad 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -132,6 +132,30 @@ zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id)
 	return NULL;
 }
 
+/**
+ * zl3073x_dpll_output_pin_sibling_get - get the other pin of an output pair
+ * @pin: output pin whose sibling is sought
+ *
+ * Output pin ids are allocated in P/N pairs (P even, N odd) that share a
+ * single HW output. Looks up the other pin of the pair, if it is
+ * registered as a dpll_pin on this DPLL.
+ *
+ * Return: pointer to sibling pin, or NULL if it is not registered
+ */
+static struct zl3073x_dpll_pin *
+zl3073x_dpll_output_pin_sibling_get(struct zl3073x_dpll_pin *pin)
+{
+	struct zl3073x_dpll_pin *sibling;
+
+	list_for_each_entry(sibling, &pin->dpll->pins, list) {
+		if (!zl3073x_dpll_is_input_pin(sibling) &&
+		    sibling->id == (pin->id ^ 1))
+			return sibling;
+	}
+
+	return NULL;
+}
+
 static struct zl3073x_dpll_pin *
 zl3073x_dpll_nco_pin_get(struct zl3073x_dpll *zldpll)
 {
@@ -910,11 +934,13 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
 	struct zl3073x_dev *zldev = zldpll->dev;
 	struct zl3073x_dpll_pin *pin = pin_priv;
 	const struct zl3073x_synth *synth;
+	struct zl3073x_dpll_pin *sibling;
 	struct zl3073x_out out;
 	u32 synth_freq;
 	u8 out_id;
+	int rc;
 
-	guard(mutex)(&zldpll->lock);
+	mutex_lock(&zldpll->lock);
 
 	out_id = zl3073x_output_pin_out_get(pin->id);
 	out = *zl3073x_out_state_get(zldev, out_id);
@@ -923,8 +949,10 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
 	 * for N-division is also used for the esync divider so both cannot
 	 * be used.
 	 */
-	if (zl3073x_out_is_ndiv(&out))
-		return -EOPNOTSUPP;
+	if (zl3073x_out_is_ndiv(&out)) {
+		rc = -EOPNOTSUPP;
+		goto unlock;
+	}
 
 	/* Update clock type in output mode */
 	if (freq)
@@ -934,27 +962,44 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
 		zl3073x_out_clock_type_set(&out,
 					   ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL);
 
-	/* If esync is being disabled just write mailbox and finish */
-	if (!freq)
-		return zl3073x_out_state_set(zldev, out_id, &out);
+	if (freq) {
+		/* Get attached synth frequency */
+		synth = zl3073x_synth_state_get(zldev,
+						zl3073x_out_synth_get(&out));
+		synth_freq = zl3073x_synth_freq_get(synth);
 
-	/* Get attached synth frequency */
-	synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out));
-	synth_freq = zl3073x_synth_freq_get(synth);
+		/* Compute and update esync period */
+		out.esync_n_period = synth_freq / (u32)freq / out.div;
 
-	/* Compute and update esync period */
-	out.esync_n_period = synth_freq / (u32)freq / out.div;
+		/* Half of the period in units of 1/2 synth cycle can be
+		 * represented by the output_div. To get the supported esync
+		 * pulse width of 25% of the period the output_div can just
+		 * be divided by 2. Note that this assumes that output_div
+		 * is even, otherwise some resolution will be lost.
+		 */
+		out.esync_n_width = out.div / 2;
+	}
 
-	/* Half of the period in units of 1/2 synth cycle can be represented by
-	 * the output_div. To get the supported esync pulse width of 25% of the
-	 * period the output_div can just be divided by 2. Note that this
-	 * assumes that output_div is even, otherwise some resolution will be
-	 * lost.
+	/* Commit output configuration */
+	rc = zl3073x_out_state_set(zldev, out_id, &out);
+	if (rc)
+		goto unlock;
+
+	/* The clock type, esync period and esync width are all shared by
+	 * both pins of the output pair, so the sibling pin's esync
+	 * configuration changes too and userspace has to be notified.
 	 */
-	out.esync_n_width = out.div / 2;
+	sibling = zl3073x_dpll_output_pin_sibling_get(pin);
 
-	/* Commit output configuration */
-	return zl3073x_out_state_set(zldev, out_id, &out);
+	mutex_unlock(&zldpll->lock);
+
+	if (sibling)
+		__dpll_pin_change_ntf(sibling->dpll_pin);
+
+	return 0;
+unlock:
+	mutex_unlock(&zldpll->lock);
+	return rc;
 }
 
 static int
@@ -984,12 +1029,14 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
 	struct zl3073x_dpll *zldpll = dpll_priv;
 	struct zl3073x_dev *zldev = zldpll->dev;
 	struct zl3073x_dpll_pin *pin = pin_priv;
+	struct zl3073x_dpll_pin *sibling = NULL;
 	const struct zl3073x_synth *synth;
 	u32 new_div, synth_freq;
 	struct zl3073x_out out;
 	u8 out_id;
+	int rc;
 
-	guard(mutex)(&zldpll->lock);
+	mutex_lock(&zldpll->lock);
 
 	out_id = zl3073x_output_pin_out_get(pin->id);
 	out = *zl3073x_out_state_get(zldev, out_id);
@@ -1002,7 +1049,8 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
 	/* Check signal format */
 	if (!zl3073x_out_is_ndiv(&out)) {
 		/* For non N-divided signal formats the frequency is computed
-		 * as division of synth frequency and output divisor.
+		 * as division of synth frequency and output divisor, which
+		 * is shared by both pins of the output pair.
 		 */
 		out.div = new_div;
 
@@ -1010,7 +1058,16 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
 		out.width = new_div;
 
 		/* Commit output configuration */
-		return zl3073x_out_state_set(zldev, out_id, &out);
+		rc = zl3073x_out_state_set(zldev, out_id, &out);
+		if (rc)
+			goto unlock;
+
+		/* The other pin's frequency changed too - it has to be
+		 * notified about the change.
+		 */
+		sibling = zl3073x_dpll_output_pin_sibling_get(pin);
+
+		goto unlock;
 	}
 
 	if (zl3073x_dpll_is_p_pin(pin)) {
@@ -1022,8 +1079,10 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
 		 * Update divisor for N-pin to keep N-pin frequency.
 		 */
 		out.esync_n_period = (out.esync_n_period * out.div) / new_div;
-		if (!out.esync_n_period)
-			return -EINVAL;
+		if (!out.esync_n_period) {
+			rc = -EINVAL;
+			goto unlock;
+		}
 
 		/* Update the output divisor */
 		out.div = new_div;
@@ -1039,15 +1098,24 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
 		 * Update divisor for N-pin
 		 */
 		out.esync_n_period = div64_u64(synth_freq, frequency * out.div);
-		if (!out.esync_n_period)
-			return -EINVAL;
+		if (!out.esync_n_period) {
+			rc = -EINVAL;
+			goto unlock;
+		}
 	}
 
 	/* For 50/50 duty cycle the divisor is equal to width */
 	out.esync_n_width = out.esync_n_period;
 
 	/* Commit output configuration */
-	return zl3073x_out_state_set(zldev, out_id, &out);
+	rc = zl3073x_out_state_set(zldev, out_id, &out);
+unlock:
+	mutex_unlock(&zldpll->lock);
+
+	if (sibling)
+		__dpll_pin_change_ntf(sibling->dpll_pin);
+
+	return rc;
 }
 
 static int
@@ -1086,10 +1154,12 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
 	struct zl3073x_dpll *zldpll = dpll_priv;
 	struct zl3073x_dev *zldev = zldpll->dev;
 	struct zl3073x_dpll_pin *pin = pin_priv;
+	struct zl3073x_dpll_pin *sibling;
 	struct zl3073x_out out;
 	u8 out_id;
+	int rc;
 
-	guard(mutex)(&zldpll->lock);
+	mutex_lock(&zldpll->lock);
 
 	out_id = zl3073x_output_pin_out_get(pin->id);
 	out = *zl3073x_out_state_get(zldev, out_id);
@@ -1098,7 +1168,23 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
 	out.phase_comp = phase_adjust / pin->phase_gran;
 
 	/* Update output configuration from mailbox */
-	return zl3073x_out_state_set(zldev, out_id, &out);
+	rc = zl3073x_out_state_set(zldev, out_id, &out);
+	if (rc) {
+		mutex_unlock(&zldpll->lock);
+		return rc;
+	}
+
+	/* The phase compensation register is shared by both pins of the
+	 * output pair, so the sibling pin's phase adjustment changes too.
+	 */
+	sibling = zl3073x_dpll_output_pin_sibling_get(pin);
+
+	mutex_unlock(&zldpll->lock);
+
+	if (sibling)
+		__dpll_pin_change_ntf(sibling->dpll_pin);
+
+	return 0;
 }
 
 static int
-- 
2.55.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH net-next 2/6] dpll: zl3073x: add GPO support for output pins
  2026-09-28 18:55 [PATCH net-next 0/6] dpll: zl3073x: add PTP periodic output support Ivan Vecera
  2026-09-28 18:55 ` [PATCH net-next 1/6] dpll: zl3073x: notify sibling pin when shared output config changes Ivan Vecera
@ 2026-09-28 18:55 ` Ivan Vecera
  2026-09-28 18:55 ` [PATCH net-next 3/6] dpll: zl3073x: allow enabling/disabling " Ivan Vecera
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Ivan Vecera @ 2026-09-28 18:55 UTC (permalink / raw)
  To: netdev
  Cc: Chris du Quesnay, Arkadiusz Kubalewski, Jakub Kicinski,
	Jiri Pirko, Min Li, Paolo Abeni, Petr Oros, Richard Cochran,
	Vadim Fedorenko, linux-kernel

Add the GPO override registers for CMOS output pins: the per-output
output_gpo_en and output_gpo_config_out_p/out_n mailbox fields and the
direct gpo_out_x bitmask registers selecting the static value of an
overridden pin. zl3073x_dev_gpo_set() sets that value for a given GPO
channel and zl3073x_out_pin_func_get()/_set() report and configure the
function (clock or GPO mode) of an individual output pin.

Add the output_ctrl_x stop bits and zl3073x_out_stop()/_start() to
request a clean, edge-aligned stop or restart of an output. Move ctrl
into the cfg struct_group and let zl3073x_out_state_set() write it
directly, since it is no longer invariant and is not part of the
output mailbox.

Tested-by: Chris du Quesnay <Chris.duQuesnay@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 drivers/dpll/zl3073x/core.c |  39 ++++++++++
 drivers/dpll/zl3073x/core.h |   1 +
 drivers/dpll/zl3073x/out.c  |  50 ++++++++++---
 drivers/dpll/zl3073x/out.h  | 138 +++++++++++++++++++++++++++++++++++-
 drivers/dpll/zl3073x/regs.h |  20 ++++++
 5 files changed, 236 insertions(+), 12 deletions(-)

diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 230df08e27cd19..7386932df0327f 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -628,6 +628,45 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
 				    ZL_POLL_PHASE_ERR_TIMEOUT_US);
 }
 
+/**
+ * zl3073x_dev_gpo_set - set the static value driven by a GPO channel
+ * @zldev: pointer to zl3073x_dev structure
+ * @gpo: GPO channel index (2 * output index for the P-pin, +1 for the
+ *	 N-pin)
+ * @value: value to drive when the channel is GPO-overridden
+ *
+ * The gpo_out_x registers are direct, multi-channel bitmask registers
+ * shared by all outputs, so the read-modify-write is serialized against
+ * concurrent updates to other channels via multiop_lock.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_dev_gpo_set(struct zl3073x_dev *zldev, u8 gpo, bool value)
+{
+	unsigned int reg;
+	u8 bit, val;
+	int rc;
+
+	if (gpo >= ZL3073X_NUM_OUTPUT_PINS)
+		return -EINVAL;
+
+	reg = ZL_REG_GPO_OUT(gpo / 8);
+	bit = gpo % 8;
+
+	guard(mutex)(&zldev->multiop_lock);
+
+	rc = zl3073x_read_u8(zldev, reg, &val);
+	if (rc)
+		return rc;
+
+	if (value)
+		val |= BIT(bit);
+	else
+		val &= ~BIT(bit);
+
+	return zl3073x_write_u8(zldev, reg, val);
+}
+
 /**
  * zl3073x_ref_freq_meas_latch - latch reference frequency measurements
  * @zldev: pointer to zl3073x_dev structure
diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
index 67c10e2595118c..e7064f2958fa5f 100644
--- a/drivers/dpll/zl3073x/core.h
+++ b/drivers/dpll/zl3073x/core.h
@@ -167,6 +167,7 @@ int zl3073x_write_hwreg_seq(struct zl3073x_dev *zldev,
  *****************/
 
 int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel);
+int zl3073x_dev_gpo_set(struct zl3073x_dev *zldev, u8 gpo, bool value);
 
 /**
  * zl3073x_dev_is_ref_phase_comp_32bit - check ref phase comp register size
diff --git a/drivers/dpll/zl3073x/out.c b/drivers/dpll/zl3073x/out.c
index 410d15b96d0bf0..82564045890db0 100644
--- a/drivers/dpll/zl3073x/out.c
+++ b/drivers/dpll/zl3073x/out.c
@@ -85,8 +85,22 @@ int zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index)
 	if (rc)
 		return rc;
 
-	return zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP,
-				&out->phase_comp);
+	rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP,
+			      &out->phase_comp);
+	if (rc)
+		return rc;
+
+	rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_GPO_EN, &out->gpo_en);
+	if (rc)
+		return rc;
+
+	rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_GPO_CONFIG_OUT_P,
+			     &out->gpo_config_p);
+	if (rc)
+		return rc;
+
+	return zl3073x_read_u8(zldev, ZL_REG_OUTPUT_GPO_CONFIG_OUT_N,
+			       &out->gpo_config_n);
 }
 
 /**
@@ -108,11 +122,12 @@ const struct zl3073x_out *zl3073x_out_state_get(struct zl3073x_dev *zldev,
  * @index: output index to set state for
  * @out: desired output state
  *
- * Validates that invariant fields have not been modified, skips the HW
- * write if the mutable configuration is unchanged, and otherwise writes
- * only the changed cfg fields to hardware via the mailbox interface.
+ * Skips the HW write if the configuration is unchanged, writes ctrl
+ * directly to the output_ctrl_x register if it differs (it is not part
+ * of the output mailbox), and otherwise writes only the changed cfg
+ * fields to hardware via the mailbox interface.
  *
- * Return: 0 on success, -EINVAL if invariants changed, <0 on HW error
+ * Return: 0 on success, <0 on HW error
  */
 int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index,
 			  const struct zl3073x_out *out)
@@ -120,11 +135,17 @@ int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index,
 	struct zl3073x_out *dout = &zldev->out[index];
 	int rc;
 
-	/* Reject attempts to change invariant fields (set at fetch only) */
-	if (WARN_ON(memcmp(&dout->inv, &out->inv, sizeof(out->inv))))
-		return -EINVAL;
+	/* ctrl is a direct register, independent of the output mailbox */
+	if (dout->ctrl != out->ctrl) {
+		rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_CTRL(index),
+				      out->ctrl);
+		if (rc)
+			return rc;
+
+		dout->ctrl = out->ctrl;
+	}
 
-	/* Skip HW write if configuration hasn't changed */
+	/* Skip the mailbox commit if nothing else has changed */
 	if (!memcmp(&dout->cfg, &out->cfg, sizeof(out->cfg)))
 		return 0;
 
@@ -152,6 +173,15 @@ int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index,
 	if (!rc && dout->phase_comp != out->phase_comp)
 		rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP,
 				       out->phase_comp);
+	if (!rc && dout->gpo_en != out->gpo_en)
+		rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_GPO_EN,
+				      out->gpo_en);
+	if (!rc && dout->gpo_config_p != out->gpo_config_p)
+		rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_GPO_CONFIG_OUT_P,
+				      out->gpo_config_p);
+	if (!rc && dout->gpo_config_n != out->gpo_config_n)
+		rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_GPO_CONFIG_OUT_N,
+				      out->gpo_config_n);
 	if (rc)
 		return rc;
 
diff --git a/drivers/dpll/zl3073x/out.h b/drivers/dpll/zl3073x/out.h
index 660889c57bffab..66a8432c00dfd0 100644
--- a/drivers/dpll/zl3073x/out.h
+++ b/drivers/dpll/zl3073x/out.h
@@ -19,6 +19,9 @@ struct zl3073x_dev;
  * @esync_n_width: embedded sync or n-pin pulse width
  * @phase_comp: phase compensation
  * @mode: output mode
+ * @gpo_en: GPO override enable for the P-pin and N-pin
+ * @gpo_config_p: GPO mode configuration for the P-pin
+ * @gpo_config_n: GPO mode configuration for the N-pin
  * @ctrl: output control
  */
 struct zl3073x_out {
@@ -29,8 +32,9 @@ struct zl3073x_out {
 		u32	esync_n_width;
 		s32	phase_comp;
 		u8	mode;
-	);
-	struct_group(inv, /* Invariants */
+		u8	gpo_en;
+		u8	gpo_config_p;
+		u8	gpo_config_n;
 		u8	ctrl;
 	);
 };
@@ -106,6 +110,136 @@ static inline bool zl3073x_out_is_enabled(const struct zl3073x_out *out)
 	return !!FIELD_GET(ZL_OUTPUT_CTRL_EN, out->ctrl);
 }
 
+/**
+ * zl3073x_out_is_stopped - check if the given output is stopped
+ * @out: pointer to out state
+ *
+ * Return: true if output clock is stopped, false if it is running
+ */
+static inline bool zl3073x_out_is_stopped(const struct zl3073x_out *out)
+{
+	return !!FIELD_GET(ZL_OUTPUT_CTRL_STOP, out->ctrl);
+}
+
+/**
+ * zl3073x_out_stop - request a clean stop of an output
+ * @out: pointer to out state to update
+ *
+ * Sets the stop and stop_hz bits together, so the output goes high-Z
+ * rather than holding a fixed level once stopped. The stop is
+ * edge-aligned (the device waits for the proper edge before actually
+ * stopping).
+ */
+static inline void zl3073x_out_stop(struct zl3073x_out *out)
+{
+	FIELD_MODIFY(ZL_OUTPUT_CTRL_STOP, &out->ctrl, 1);
+	FIELD_MODIFY(ZL_OUTPUT_CTRL_STOP_HZ, &out->ctrl, 1);
+}
+
+/**
+ * zl3073x_out_start - request a clean restart of a stopped output
+ * @out: pointer to out state to update
+ *
+ * Clears the stop and stop_hz bits together. See zl3073x_out_stop().
+ */
+static inline void zl3073x_out_start(struct zl3073x_out *out)
+{
+	FIELD_MODIFY(ZL_OUTPUT_CTRL_STOP, &out->ctrl, 0);
+	FIELD_MODIFY(ZL_OUTPUT_CTRL_STOP_HZ, &out->ctrl, 0);
+}
+
+#define ZL3073X_OUT_PIN_F_CLOCK		0
+#define ZL3073X_OUT_PIN_F_GPO_CONST	1
+#define ZL3073X_OUT_PIN_F_GPO_STATUS	2
+#define ZL3073X_OUT_PIN_F_GPO_IRQ	3
+#define ZL3073X_OUT_PIN_F_GPO_UNKNOWN	4
+
+/**
+ * zl3073x_out_pin_func_get - get the function of an output pin
+ * @out: pointer to out state
+ * @id: output pin ID (even for P pin, odd for N pin)
+ *
+ * Report the current function of the given output pin. If GPO override is
+ * disabled the pin acts as a clock, otherwise it acts as a GPO with the
+ * mode selected by its GPO config control field.
+ *
+ * Return: one of the ZL3073X_OUT_PIN_F_* function codes
+ */
+static inline u8
+zl3073x_out_pin_func_get(const struct zl3073x_out *out, u8 id)
+{
+	u8 gpo_config;
+	bool gpo_en;
+
+	if (id & 1) {
+		gpo_en = FIELD_GET(ZL_OUTPUT_GPO_EN_OUT_N, out->gpo_en);
+		gpo_config = out->gpo_config_n;
+	} else {
+		gpo_en = FIELD_GET(ZL_OUTPUT_GPO_EN_OUT_P, out->gpo_en);
+		gpo_config = out->gpo_config_p;
+	}
+
+	if (!gpo_en)
+		return ZL3073X_OUT_PIN_F_CLOCK;
+
+	switch (FIELD_GET(ZL_OUTPUT_GPO_CONFIG_CTRL, gpo_config)) {
+	case ZL_OUTPUT_GPO_CONFIG_CTRL_OUTPUT:
+		return ZL3073X_OUT_PIN_F_GPO_CONST;
+	case ZL_OUTPUT_GPO_CONFIG_CTRL_STATUS:
+		return ZL3073X_OUT_PIN_F_GPO_STATUS;
+	case ZL_OUTPUT_GPO_CONFIG_CTRL_IRQ:
+		return ZL3073X_OUT_PIN_F_GPO_IRQ;
+	}
+
+	return ZL3073X_OUT_PIN_F_GPO_UNKNOWN;
+}
+
+/**
+ * zl3073x_out_pin_func_set - set the function of an output pin
+ * @out: pointer to out state to update
+ * @id: output pin ID (even for P pin, odd for N pin)
+ * @func: requested function, one of the ZL3073X_OUT_PIN_F_* codes
+ *
+ * Configure the given output pin as a clock or as a GPO in the requested
+ * mode by updating its GPO enable and GPO config control fields. Unknown
+ * function codes are ignored.
+ */
+static inline void
+zl3073x_out_pin_func_set(struct zl3073x_out *out, u8 id, u8 func)
+{
+	bool gpo_en = true;
+	u8 *gpo_config;
+	int ctrl = -1;
+
+	switch (func) {
+	case ZL3073X_OUT_PIN_F_CLOCK:
+		gpo_en = false;
+		break;
+	case ZL3073X_OUT_PIN_F_GPO_CONST:
+		ctrl = ZL_OUTPUT_GPO_CONFIG_CTRL_OUTPUT;
+		break;
+	case ZL3073X_OUT_PIN_F_GPO_STATUS:
+		ctrl = ZL_OUTPUT_GPO_CONFIG_CTRL_STATUS;
+		break;
+	case ZL3073X_OUT_PIN_F_GPO_IRQ:
+		ctrl = ZL_OUTPUT_GPO_CONFIG_CTRL_IRQ;
+		break;
+	default:
+		return;
+	}
+
+	if (id & 1) {
+		FIELD_MODIFY(ZL_OUTPUT_GPO_EN_OUT_N, &out->gpo_en, gpo_en);
+		gpo_config = &out->gpo_config_n;
+	} else {
+		FIELD_MODIFY(ZL_OUTPUT_GPO_EN_OUT_P, &out->gpo_en, gpo_en);
+		gpo_config = &out->gpo_config_p;
+	}
+
+	if (ctrl != -1)
+		FIELD_MODIFY(ZL_OUTPUT_GPO_CONFIG_CTRL, gpo_config, ctrl);
+}
+
 /**
  * zl3073x_out_is_ndiv - check if the given output is in N-div mode
  * @out: pointer to out state
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index f3a5e1215aa36b..f17a2c78611827 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -94,6 +94,13 @@
 
 #define ZL_REG_DIE_TEMP_STATUS			ZL_REG(0, 0x44, 2)
 
+/*************************
+ * Register Page 1, GPIOs
+ *************************/
+
+#define ZL_REG_GPO_OUT(_idx)						\
+	ZL_REG_IDX(_idx, 1, 0x70, 1, 3, 1)
+
 /*************************
  * Register Page 2, Status
  *************************/
@@ -254,6 +261,8 @@
 #define ZL_REG_OUTPUT_CTRL(_idx)					\
 	ZL_REG_IDX(_idx, 9, 0x28, 1, ZL3073X_NUM_OUTS, 1)
 #define ZL_OUTPUT_CTRL_EN			BIT(0)
+#define ZL_OUTPUT_CTRL_STOP			BIT(1)
+#define ZL_OUTPUT_CTRL_STOP_HZ			BIT(3)
 #define ZL_OUTPUT_CTRL_SYNTH_SEL		GENMASK(6, 4)
 
 #define ZL_REG_OUTPUT_STEP_TIME_MASK		ZL_REG(9, 0x36, 2)
@@ -368,6 +377,17 @@
 #define ZL_REG_OUTPUT_ESYNC_WIDTH		ZL_REG(14, 0x18, 4)
 #define ZL_REG_OUTPUT_PHASE_COMP		ZL_REG(14, 0x20, 4)
 
+#define ZL_REG_OUTPUT_GPO_EN			ZL_REG(14, 0x24, 1)
+#define ZL_OUTPUT_GPO_EN_OUT_P			BIT(0)
+#define ZL_OUTPUT_GPO_EN_OUT_N			BIT(1)
+
+#define ZL_REG_OUTPUT_GPO_CONFIG_OUT_P		ZL_REG(14, 0x27, 1)
+#define ZL_REG_OUTPUT_GPO_CONFIG_OUT_N		ZL_REG(14, 0x2a, 1)
+#define ZL_OUTPUT_GPO_CONFIG_CTRL		GENMASK(2, 0)
+#define ZL_OUTPUT_GPO_CONFIG_CTRL_OUTPUT	1
+#define ZL_OUTPUT_GPO_CONFIG_CTRL_STATUS	3
+#define ZL_OUTPUT_GPO_CONFIG_CTRL_IRQ		4
+
 /*
  * Register Page 255 - HW registers access
  */
-- 
2.55.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH net-next 3/6] dpll: zl3073x: allow enabling/disabling output pins
  2026-09-28 18:55 [PATCH net-next 0/6] dpll: zl3073x: add PTP periodic output support Ivan Vecera
  2026-09-28 18:55 ` [PATCH net-next 1/6] dpll: zl3073x: notify sibling pin when shared output config changes Ivan Vecera
  2026-09-28 18:55 ` [PATCH net-next 2/6] dpll: zl3073x: add GPO support for output pins Ivan Vecera
@ 2026-09-28 18:55 ` Ivan Vecera
  2026-09-28 18:55 ` [PATCH net-next 4/6] dpll: zl3073x: consolidate pin capabilities into bitmap Ivan Vecera
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Ivan Vecera @ 2026-09-28 18:55 UTC (permalink / raw)
  To: netdev
  Cc: Chris du Quesnay, Arkadiusz Kubalewski, Jakub Kicinski,
	Jiri Pirko, Min Li, Paolo Abeni, Petr Oros, Richard Cochran,
	Vadim Fedorenko, linux-kernel

Add support for enabling and disabling individual output pins through
the DPLL subsystem's state_on_dpll_get/set callbacks, at the
granularity of a single P-pin or N-pin rather than the whole output.
zl3073x_dev_output_pin_state_get() reports the pin connection state and
zl3073x_dev_output_pin_state_set() applies the requested one.

Differential pins are toggled directly through the output_ctrl_x::stop
condition. CMOS pins are muted/unmuted via the GPO override, bracketed
by a clean stop/restart of the whole output since the GPO toggle is not
glitch-free. Output pins advertise DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE
to allow this from userspace.

Tested-by: Chris du Quesnay <Chris.duQuesnay@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 drivers/dpll/zl3073x/core.c | 78 +++++++++++++++++++++++++++++++++++++
 drivers/dpll/zl3073x/core.h | 29 ++++++++++++++
 drivers/dpll/zl3073x/dpll.c | 68 +++++++++++++++++++++++++++++++-
 drivers/dpll/zl3073x/prop.c |  2 +
 4 files changed, 175 insertions(+), 2 deletions(-)

diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 7386932df0327f..89cf46111dcb13 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -3,6 +3,7 @@
 #include <linux/array_size.h>
 #include <linux/bitfield.h>
 #include <linux/bits.h>
+#include <linux/delay.h>
 #include <linux/dev_printk.h>
 #include <linux/device.h>
 #include <linux/export.h>
@@ -12,6 +13,7 @@
 #include <linux/regmap.h>
 #include <linux/sprintf.h>
 #include <linux/string_choices.h>
+#include <linux/time64.h>
 #include <linux/unaligned.h>
 #include <net/devlink.h>
 
@@ -667,6 +669,82 @@ int zl3073x_dev_gpo_set(struct zl3073x_dev *zldev, u8 gpo, bool value)
 	return zl3073x_write_u8(zldev, reg, val);
 }
 
+/**
+ * zl3073x_dev_output_pin_state_set - enable or disable the given output pin
+ * @zldev: pointer to zl3073x_dev structure
+ * @id: output pin id
+ * @enable: true to enable the pin, false to disable it
+ *
+ * Differential output pins are enabled/disabled through the clean
+ * output_ctrl_x::stop condition, since they expose only a single logical
+ * pin.
+ *
+ * CMOS output pins are enabled/disabled by muting/unmuting the pin's
+ * driver via a GPO override. The GPO toggle is not glitch-free, so it is
+ * bracketed by a clean stop/restart of the whole output.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_dev_output_pin_state_set(struct zl3073x_dev *zldev, u8 id,
+				     bool enable)
+{
+	u8 out_id = zl3073x_output_pin_out_get(id);
+	struct zl3073x_out out;
+	u32 delay, freq;
+	int rc;
+
+	out = *zl3073x_out_state_get(zldev, out_id);
+
+	if (zl3073x_out_is_diff(&out)) {
+		if (enable)
+			zl3073x_out_start(&out);
+		else
+			zl3073x_out_stop(&out);
+
+		return zl3073x_out_state_set(zldev, out_id, &out);
+	}
+
+	/* Bracket the GPO override toggle below with a clean stop/restart,
+	 * since the toggle itself is not glitch-free.
+	 */
+	zl3073x_out_stop(&out);
+	rc = zl3073x_out_state_set(zldev, out_id, &out);
+	if (rc)
+		return rc;
+
+	/* output_ctrl_x::stop is edge-aligned, so the device can take up
+	 * to half a period to actually reach the stopped state. Wait for
+	 * that long plus 25 ms, to make sure it is really stopped before
+	 * touching the GPO override below.
+	 */
+	delay = 25 * USEC_PER_MSEC;
+	freq = zl3073x_dev_output_pin_freq_get(zldev, id);
+	if (freq)
+		delay += USEC_PER_SEC / 2 / freq;
+	fsleep(delay);
+
+	if (enable) {
+		zl3073x_out_pin_func_set(&out, id, ZL3073X_OUT_PIN_F_CLOCK);
+	} else {
+		rc = zl3073x_dev_gpo_set(zldev, id, false);
+		if (rc)
+			goto restart_output;
+		zl3073x_out_pin_func_set(&out, id, ZL3073X_OUT_PIN_F_GPO_CONST);
+	}
+
+	/* Restart the output regardless of the result below: on failure,
+	 * don't leave the whole output, including the unrelated sibling
+	 * pin, stopped indefinitely.
+	 */
+	rc = zl3073x_out_state_set(zldev, out_id, &out);
+
+restart_output:
+	zl3073x_out_start(&out);
+	rc = zl3073x_out_state_set(zldev, out_id, &out) ? : rc;
+
+	return rc;
+}
+
 /**
  * zl3073x_ref_freq_meas_latch - latch reference frequency measurements
  * @zldev: pointer to zl3073x_dev structure
diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
index e7064f2958fa5f..2b4785ba18b237 100644
--- a/drivers/dpll/zl3073x/core.h
+++ b/drivers/dpll/zl3073x/core.h
@@ -168,6 +168,8 @@ int zl3073x_write_hwreg_seq(struct zl3073x_dev *zldev,
 
 int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel);
 int zl3073x_dev_gpo_set(struct zl3073x_dev *zldev, u8 gpo, bool value);
+int zl3073x_dev_output_pin_state_set(struct zl3073x_dev *zldev, u8 id,
+				     bool enable);
 
 /**
  * zl3073x_dev_is_ref_phase_comp_32bit - check ref phase comp register size
@@ -447,4 +449,31 @@ zl3073x_dev_output_pin_is_enabled(struct zl3073x_dev *zldev, u8 id)
 	return true;
 }
 
+/**
+ * zl3073x_dev_output_pin_state_get - get the given output pin connection state
+ * @zldev: pointer to zl3073x device
+ * @id: output pin id
+ *
+ * Differential outputs are connected when their clock is not stopped.
+ * CMOS ones are connected when they are not GPO-overridden - the pin would
+ * not have been registered at all if its P/N side was not enabled by the
+ * signal format in the first place.
+ *
+ * Return: true if the output pin is connected, false if disconnected
+ */
+static inline bool
+zl3073x_dev_output_pin_state_get(struct zl3073x_dev *zldev, u8 id)
+{
+	u8 out_id = zl3073x_output_pin_out_get(id);
+	const struct zl3073x_out *out;
+
+	out = zl3073x_out_state_get(zldev, out_id);
+
+	if (zl3073x_out_is_stopped(out))
+		return false;
+
+	return zl3073x_out_is_diff(out) ||
+	       zl3073x_out_pin_func_get(out, id) == ZL3073X_OUT_PIN_F_CLOCK;
+}
+
 #endif /* _ZL3073X_CORE_H */
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 2c6de4dab8b4ad..426974b0b5dc5c 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -17,6 +17,7 @@
 #include <linux/ptp_clock_kernel.h>
 #include <linux/slab.h>
 #include <linux/sprintf.h>
+#include <linux/time64.h>
 
 #include "core.h"
 #include "dpll.h"
@@ -1195,12 +1196,74 @@ zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
 					  enum dpll_pin_state *state,
 					  struct netlink_ext_ack *extack)
 {
-	/* If the output pin is registered then it is always connected */
-	*state = DPLL_PIN_STATE_CONNECTED;
+	struct zl3073x_dpll *zldpll = dpll_priv;
+	struct zl3073x_dev *zldev = zldpll->dev;
+	struct zl3073x_dpll_pin *pin = pin_priv;
+
+	guard(mutex)(&zldpll->lock);
+
+	if (zl3073x_dev_output_pin_state_get(zldev, pin->id))
+		*state = DPLL_PIN_STATE_CONNECTED;
+	else
+		*state = DPLL_PIN_STATE_DISCONNECTED;
 
 	return 0;
 }
 
+/**
+ * zl3073x_dpll_output_pin_state_on_dpll_set - enable or disable an output pin
+ * @dpll_pin: registered dpll_pin
+ * @pin_priv: pointer to zl3073x_dpll_pin structure
+ * @dpll: registered dpll_device
+ * @dpll_priv: pointer to zl3073x_dpll structure
+ * @state: requested pin state
+ * @extack: netlink extack pointer
+ *
+ * Differential output pins are enabled/disabled through the clean
+ * output_ctrl_x::stop condition, since they expose only a single
+ * logical pin.
+ *
+ * CMOS output pins are enabled/disabled by muting/unmuting the pin's
+ * driver via a GPO override rather than by changing the output's
+ * signal_format, since a signal_format change is not glitch-free on
+ * this hardware. The GPO toggle itself is not glitch-free either, so
+ * it is bracketed by a clean stop/restart of the whole output.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_dpll_output_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
+					  void *pin_priv,
+					  const struct dpll_device *dpll,
+					  void *dpll_priv,
+					  enum dpll_pin_state state,
+					  struct netlink_ext_ack *extack)
+{
+	struct zl3073x_dpll *zldpll = dpll_priv;
+	struct zl3073x_dev *zldev = zldpll->dev;
+	struct zl3073x_dpll_pin *pin = pin_priv;
+	bool enable;
+	int rc = 0;
+
+	if (state != DPLL_PIN_STATE_CONNECTED &&
+	    state != DPLL_PIN_STATE_DISCONNECTED) {
+		NL_SET_ERR_MSG(extack, "Invalid pin state for output pin");
+		return -EINVAL;
+	}
+
+	guard(mutex)(&zldpll->lock);
+
+	enable = state == DPLL_PIN_STATE_CONNECTED;
+	if (zl3073x_dev_output_pin_state_get(zldev, pin->id) != enable) {
+		rc = zl3073x_dev_output_pin_state_set(zldev, pin->id, enable);
+		if (rc)
+			NL_SET_ERR_MSG(extack,
+				       "Failed to change output pin state");
+	}
+
+	return rc;
+}
+
 static int
 zl3073x_dpll_nco_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin,
 					   void *pin_priv,
@@ -1685,6 +1748,7 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
 	.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,
+	.state_on_dpll_set = zl3073x_dpll_output_pin_state_on_dpll_set,
 };
 
 static const struct dpll_pin_ops zl3073x_dpll_nco_pin_ops = {
diff --git a/drivers/dpll/zl3073x/prop.c b/drivers/dpll/zl3073x/prop.c
index ac9d41d0f978ef..806d1e6622da5a 100644
--- a/drivers/dpll/zl3073x/prop.c
+++ b/drivers/dpll/zl3073x/prop.c
@@ -214,6 +214,8 @@ struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
 		u32 f;
 
 		props->dpll_props.type = DPLL_PIN_TYPE_GNSS;
+		props->dpll_props.capabilities =
+			DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;
 
 		/* The output pin phase adjustment granularity equals half of
 		 * the synth frequency count.
-- 
2.55.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH net-next 4/6] dpll: zl3073x: consolidate pin capabilities into bitmap
  2026-09-28 18:55 [PATCH net-next 0/6] dpll: zl3073x: add PTP periodic output support Ivan Vecera
                   ` (2 preceding siblings ...)
  2026-09-28 18:55 ` [PATCH net-next 3/6] dpll: zl3073x: allow enabling/disabling " Ivan Vecera
@ 2026-09-28 18:55 ` Ivan Vecera
  2026-09-28 18:55 ` [PATCH net-next 5/6] dpll: zl3073x: factor out output pin frequency helper Ivan Vecera
  2026-09-28 18:55 ` [PATCH net-next 6/6] dpll: zl3073x: add PTP periodic output support Ivan Vecera
  5 siblings, 0 replies; 7+ messages in thread
From: Ivan Vecera @ 2026-09-28 18:55 UTC (permalink / raw)
  To: netdev
  Cc: Chris du Quesnay, Arkadiusz Kubalewski, Jakub Kicinski,
	Jiri Pirko, Min Li, Paolo Abeni, Petr Oros, Richard Cochran,
	Vadim Fedorenko, linux-kernel

Replace the per-pin 'esync_control' boolean with a 'caps' bitmap in
struct zl3073x_dpll_pin. The bitmap is built from the pin properties
during pin registration and is easily extensible with further per-pin
capabilities.

No functional change intended.

Tested-by: Chris du Quesnay <Chris.duQuesnay@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 drivers/dpll/zl3073x/dpll.c | 27 +++++++++++++++++++++------
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 426974b0b5dc5c..fcf91aba2988af 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -38,7 +38,7 @@
  * @dir: pin direction
  * @id: pin id
  * @prio: pin priority <0, 14>
- * @esync_control: embedded sync is controllable
+ * @caps: pin capabilities (ZL3073X_DPLL_PIN_CAP_*)
  * @phase_gran: phase adjustment granularity
  * @operstate: last saved operational state
  * @phase_offset: last saved pin phase offset
@@ -55,7 +55,7 @@ struct zl3073x_dpll_pin {
 	enum dpll_pin_direction	dir;
 	u8			id;
 	u8			prio;
-	bool			esync_control;
+	u8			caps;
 	s32			phase_gran;
 	enum dpll_pin_operstate	operstate;
 	s64			phase_offset;
@@ -63,6 +63,17 @@ struct zl3073x_dpll_pin {
 	u32			measured_freq;
 };
 
+/*
+ * DPLL pin capabilities
+ */
+enum zl3073x_dpll_pin_caps {
+	ZL3073X_DPLL_PIN_CAP_ESYNC_BIT,
+	ZL3073X_DPLL_PIN_CAPS_NBITS /* must be last */
+};
+
+#define __ZL3073X_DPLL_PIN_CAP(name)	BIT(ZL3073X_DPLL_PIN_CAP_##name##_BIT)
+#define ZL3073X_DPLL_PIN_CAP_ESYNC	__ZL3073X_DPLL_PIN_CAP(ESYNC)
+
 /*
  * Supported esync ranges for input and for output per output pair type
  */
@@ -189,7 +200,8 @@ zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin,
 	ref_id = zl3073x_input_pin_ref_get(pin->id);
 	ref = zl3073x_ref_state_get(zldev, ref_id);
 
-	if (!pin->esync_control || zl3073x_ref_freq_get(ref) <= 1)
+	if (!(pin->caps & ZL3073X_DPLL_PIN_CAP_ESYNC) ||
+	    zl3073x_ref_freq_get(ref) <= 1)
 		return -EOPNOTSUPP;
 
 	esync->range = esync_freq_ranges;
@@ -897,7 +909,7 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin,
 	synth_freq = zl3073x_synth_freq_get(synth);
 	out_freq = synth_freq / out->div;
 
-	if (!pin->esync_control || out_freq <= 1)
+	if (!(pin->caps & ZL3073X_DPLL_PIN_CAP_ESYNC) || out_freq <= 1)
 		return -EOPNOTSUPP;
 
 	esync->range = esync_freq_ranges;
@@ -1837,14 +1849,17 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
 	if (IS_ERR(props))
 		return PTR_ERR(props);
 
-	/* Save package label, fwnode, esync capability and phase adjust
+	/* Save package label, fwnode, capabilities 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;
 
+	pin->caps = 0;
+	if (props->esync_control)
+		pin->caps |= ZL3073X_DPLL_PIN_CAP_ESYNC;
+
 	if (zl3073x_dpll_is_input_pin(pin)) {
 		const struct zl3073x_chan *chan;
 		u8 ref;
-- 
2.55.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH net-next 5/6] dpll: zl3073x: factor out output pin frequency helper
  2026-09-28 18:55 [PATCH net-next 0/6] dpll: zl3073x: add PTP periodic output support Ivan Vecera
                   ` (3 preceding siblings ...)
  2026-09-28 18:55 ` [PATCH net-next 4/6] dpll: zl3073x: consolidate pin capabilities into bitmap Ivan Vecera
@ 2026-09-28 18:55 ` Ivan Vecera
  2026-09-28 18:55 ` [PATCH net-next 6/6] dpll: zl3073x: add PTP periodic output support Ivan Vecera
  5 siblings, 0 replies; 7+ messages in thread
From: Ivan Vecera @ 2026-09-28 18:55 UTC (permalink / raw)
  To: netdev
  Cc: Chris du Quesnay, Arkadiusz Kubalewski, Jakub Kicinski,
	Jiri Pirko, Min Li, Paolo Abeni, Petr Oros, Richard Cochran,
	Vadim Fedorenko, linux-kernel

Extract the divisor and N-division computation from the output pin
frequency_set callback into zl3073x_dpll_output_pin_freq_set(), which
updates a struct zl3073x_out without committing it to hardware. This
lets the upcoming PTP periodic output support reuse the same P-pin,
N-pin and N-divided frequency handling.

No functional change intended.

Tested-by: Chris du Quesnay <Chris.duQuesnay@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 drivers/dpll/zl3073x/dpll.c | 135 ++++++++++++++++++++----------------
 1 file changed, 74 insertions(+), 61 deletions(-)

diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index fcf91aba2988af..0a36a2acf15b9f 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -1032,96 +1032,109 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin,
 	return 0;
 }
 
+/**
+ * zl3073x_dpll_output_pin_freq_set - compute output config for pin frequency
+ * @pin: output pin to set the frequency for
+ * @out: output state to update, not committed to hardware
+ * @frequency: requested pin frequency in Hz
+ *
+ * Updates the divisor and N-division fields of @out so the given output
+ * pin runs at the requested frequency. For non N-divided formats the
+ * divisor is shared by both pins of the output pair. The caller is
+ * responsible for committing @out with zl3073x_out_state_set().
+ *
+ * Return: 0 on success, -EINVAL if the frequency cannot be represented
+ */
 static int
-zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
-				      void *pin_priv,
-				      const struct dpll_device *dpll,
-				      void *dpll_priv, u64 frequency,
-				      struct netlink_ext_ack *extack)
+zl3073x_dpll_output_pin_freq_set(struct zl3073x_dpll_pin *pin,
+				 struct zl3073x_out *out, u64 frequency)
 {
-	struct zl3073x_dpll *zldpll = dpll_priv;
-	struct zl3073x_dev *zldev = zldpll->dev;
-	struct zl3073x_dpll_pin *pin = pin_priv;
-	struct zl3073x_dpll_pin *sibling = NULL;
-	const struct zl3073x_synth *synth;
+	struct zl3073x_dev *zldev = pin->dpll->dev;
 	u32 new_div, synth_freq;
-	struct zl3073x_out out;
-	u8 out_id;
-	int rc;
+	u8 synth;
 
-	mutex_lock(&zldpll->lock);
-
-	out_id = zl3073x_output_pin_out_get(pin->id);
-	out = *zl3073x_out_state_get(zldev, out_id);
-
-	/* Get attached synth frequency and compute new divisor */
-	synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out));
-	synth_freq = zl3073x_synth_freq_get(synth);
+	synth = zl3073x_out_synth_get(out);
+	synth_freq = zl3073x_dev_synth_freq_get(zldev, synth);
 	new_div = synth_freq / (u32)frequency;
 
-	/* Check signal format */
-	if (!zl3073x_out_is_ndiv(&out)) {
+	if (!zl3073x_out_is_ndiv(out)) {
 		/* For non N-divided signal formats the frequency is computed
 		 * as division of synth frequency and output divisor, which
 		 * is shared by both pins of the output pair.
 		 */
-		out.div = new_div;
+		out->div = new_div;
 
 		/* For 50/50 duty cycle the divisor is equal to width */
-		out.width = new_div;
-
-		/* Commit output configuration */
-		rc = zl3073x_out_state_set(zldev, out_id, &out);
-		if (rc)
-			goto unlock;
+		out->width = new_div;
 
-		/* The other pin's frequency changed too - it has to be
-		 * notified about the change.
-		 */
-		sibling = zl3073x_dpll_output_pin_sibling_get(pin);
-
-		goto unlock;
+		return 0;
 	}
 
 	if (zl3073x_dpll_is_p_pin(pin)) {
-		/* We are going to change output frequency for P-pin but
-		 * if the requested frequency is less than current N-pin
-		 * frequency then indicate a failure as we are not able
-		 * to compute N-pin divisor to keep its frequency unchanged.
-		 *
-		 * Update divisor for N-pin to keep N-pin frequency.
+		/* Changing the P-pin frequency, rescale the N-pin divisor to
+		 * keep the N-pin frequency unchanged. Fail if the requested
+		 * frequency is too low to represent the current N-pin one.
 		 */
-		out.esync_n_period = (out.esync_n_period * out.div) / new_div;
-		if (!out.esync_n_period) {
-			rc = -EINVAL;
-			goto unlock;
-		}
+		out->esync_n_period = out->esync_n_period * out->div / new_div;
+		if (!out->esync_n_period)
+			return -EINVAL;
 
 		/* Update the output divisor */
-		out.div = new_div;
+		out->div = new_div;
 
 		/* For 50/50 duty cycle the divisor is equal to width */
-		out.width = out.div;
+		out->width = new_div;
 	} else {
-		/* We are going to change frequency of N-pin but if
-		 * the requested freq is greater or equal than freq of P-pin
-		 * in the output pair we cannot compute divisor for the N-pin.
-		 * In this case indicate a failure.
-		 *
-		 * Update divisor for N-pin
+		/* Changing the N-pin frequency. Fail if the requested
+		 * frequency is higher than or does not divide the P-pin one.
 		 */
-		out.esync_n_period = div64_u64(synth_freq, frequency * out.div);
-		if (!out.esync_n_period) {
-			rc = -EINVAL;
-			goto unlock;
-		}
+		out->esync_n_period = div64_u64(synth_freq,
+						frequency * out->div);
+		if (!out->esync_n_period)
+			return -EINVAL;
 	}
 
 	/* For 50/50 duty cycle the divisor is equal to width */
-	out.esync_n_width = out.esync_n_period;
+	out->esync_n_width = out->esync_n_period;
+
+	return 0;
+}
+
+static int
+zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
+				      void *pin_priv,
+				      const struct dpll_device *dpll,
+				      void *dpll_priv, u64 frequency,
+				      struct netlink_ext_ack *extack)
+{
+	struct zl3073x_dpll *zldpll = dpll_priv;
+	struct zl3073x_dev *zldev = zldpll->dev;
+	struct zl3073x_dpll_pin *pin = pin_priv;
+	struct zl3073x_dpll_pin *sibling = NULL;
+	struct zl3073x_out out;
+	u8 out_id;
+	int rc;
+
+	mutex_lock(&zldpll->lock);
+
+	out_id = zl3073x_output_pin_out_get(pin->id);
+	out = *zl3073x_out_state_get(zldev, out_id);
+
+	rc = zl3073x_dpll_output_pin_freq_set(pin, &out, frequency);
+	if (rc)
+		goto unlock;
 
 	/* Commit output configuration */
 	rc = zl3073x_out_state_set(zldev, out_id, &out);
+	if (rc)
+		goto unlock;
+
+	/* For non N-divided formats the divisor is shared, so the other
+	 * pin's frequency changed too and has to be notified.
+	 */
+	if (!zl3073x_out_is_ndiv(&out))
+		sibling = zl3073x_dpll_output_pin_sibling_get(pin);
+
 unlock:
 	mutex_unlock(&zldpll->lock);
 
-- 
2.55.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH net-next 6/6] dpll: zl3073x: add PTP periodic output support
  2026-09-28 18:55 [PATCH net-next 0/6] dpll: zl3073x: add PTP periodic output support Ivan Vecera
                   ` (4 preceding siblings ...)
  2026-09-28 18:55 ` [PATCH net-next 5/6] dpll: zl3073x: factor out output pin frequency helper Ivan Vecera
@ 2026-09-28 18:55 ` Ivan Vecera
  5 siblings, 0 replies; 7+ messages in thread
From: Ivan Vecera @ 2026-09-28 18:55 UTC (permalink / raw)
  To: netdev
  Cc: Chris du Quesnay, Arkadiusz Kubalewski, Jakub Kicinski,
	Jiri Pirko, Min Li, Paolo Abeni, Petr Oros, Richard Cochran,
	Vadim Fedorenko, linux-kernel

Register a PTP periodic output pin for each DPLL output pin that
supports step-time and declares 1 PPS (1 Hz) support in firmware.
The pins are named after the output pin (e.g. OUT5, OUT5P, OUT5N) and
any perout channel can be assigned to any of them.

Only 1 PPS is supported. Enabling a channel programs the pin assigned
to it for 1 Hz and connects it; disabling disconnects it, reusing the
output pin frequency helper and the per-pin connect/disconnect
primitive. A 1PPS bit is added to the per-pin capabilities.

Tested-by: Chris du Quesnay <Chris.duQuesnay@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 drivers/dpll/zl3073x/dpll.c | 211 ++++++++++++++++++++++++++++++++++++
 drivers/dpll/zl3073x/dpll.h |   4 +
 drivers/dpll/zl3073x/prop.h |  21 ++++
 3 files changed, 236 insertions(+)

diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 0a36a2acf15b9f..f406d1e72530ff 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -68,11 +68,13 @@ struct zl3073x_dpll_pin {
  */
 enum zl3073x_dpll_pin_caps {
 	ZL3073X_DPLL_PIN_CAP_ESYNC_BIT,
+	ZL3073X_DPLL_PIN_CAP_1PPS_BIT,
 	ZL3073X_DPLL_PIN_CAPS_NBITS /* must be last */
 };
 
 #define __ZL3073X_DPLL_PIN_CAP(name)	BIT(ZL3073X_DPLL_PIN_CAP_##name##_BIT)
 #define ZL3073X_DPLL_PIN_CAP_ESYNC	__ZL3073X_DPLL_PIN_CAP(ESYNC)
+#define ZL3073X_DPLL_PIN_CAP_1PPS	__ZL3073X_DPLL_PIN_CAP(1PPS)
 
 /*
  * Supported esync ranges for input and for output per output pair type
@@ -144,6 +146,19 @@ zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id)
 	return NULL;
 }
 
+static struct zl3073x_dpll_pin *
+zl3073x_dpll_output_pin_get_by_id(struct zl3073x_dpll *zldpll, u8 id)
+{
+	struct zl3073x_dpll_pin *pin;
+
+	list_for_each_entry(pin, &zldpll->pins, list) {
+		if (!zl3073x_dpll_is_input_pin(pin) && pin->id == id)
+			return pin;
+	}
+
+	return NULL;
+}
+
 /**
  * zl3073x_dpll_output_pin_sibling_get - get the other pin of an output pair
  * @pin: output pin whose sibling is sought
@@ -1872,6 +1887,8 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
 	pin->caps = 0;
 	if (props->esync_control)
 		pin->caps |= ZL3073X_DPLL_PIN_CAP_ESYNC;
+	if (zl3073x_props_is_freq_supported(props, 1))
+		pin->caps |= ZL3073X_DPLL_PIN_CAP_1PPS;
 
 	if (zl3073x_dpll_is_input_pin(pin)) {
 		const struct zl3073x_chan *chan;
@@ -2823,6 +2840,159 @@ zl3073x_dpll_ptp_getmaxphase(struct ptp_clock_info *info __always_unused)
 	return NSEC_PER_SEC - 1;
 }
 
+/**
+ * zl3073x_dpll_pin_is_perout_capable - check output pin perout eligibility
+ * @pin: output pin to check
+ *
+ * A registered output pin can be used for periodic output if its output
+ * supports step-time and the pin declares 1 PPS (1 Hz) support in firmware.
+ *
+ * Return: true if the pin can be used for periodic output.
+ */
+static bool
+zl3073x_dpll_pin_is_perout_capable(struct zl3073x_dpll_pin *pin)
+{
+	struct zl3073x_dev *zldev = pin->dpll->dev;
+	u8 out_id;
+
+	/* Periodic output is only available on output pins */
+	if (zl3073x_dpll_is_input_pin(pin) || zl3073x_dpll_is_nco_pin(pin))
+		return false;
+
+	out_id = zl3073x_output_pin_out_get(pin->id);
+
+	return zl3073x_dev_out_is_stepped(zldev, out_id) &&
+	       (pin->caps & ZL3073X_DPLL_PIN_CAP_1PPS);
+}
+
+/**
+ * zl3073x_dpll_perout_enable - enable 1 PPS periodic output on a pin
+ * @pin: output pin to enable periodic output on
+ * @perout: periodic output request
+ *
+ * Programs the pin for 1 PPS (1 Hz) output and connects it.
+ *
+ * Context: Caller must hold pin->dpll->lock.
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_dpll_perout_enable(struct zl3073x_dpll_pin *pin,
+			   struct ptp_perout_request *perout)
+{
+	u8 out_id = zl3073x_output_pin_out_get(pin->id);
+	struct zl3073x_dev *zldev = pin->dpll->dev;
+	struct zl3073x_out out;
+	int rc;
+
+	/* Only 1 PPS (1 Hz) periodic output is supported */
+	if (perout->period.sec != 1 || perout->period.nsec)
+		return -EINVAL;
+
+	out = *zl3073x_out_state_get(zldev, out_id);
+
+	rc = zl3073x_dpll_output_pin_freq_set(pin, &out, 1);
+	if (rc)
+		return rc;
+
+	rc = zl3073x_out_state_set(zldev, out_id, &out);
+	if (rc)
+		return rc;
+
+	if (zl3073x_dev_output_pin_state_get(zldev, pin->id))
+		return 0;
+
+	return zl3073x_dev_output_pin_state_set(zldev, pin->id, true);
+}
+
+/**
+ * zl3073x_dpll_perout_disable - disable periodic output on a pin
+ * @pin: output pin to disable periodic output on
+ *
+ * Context: Caller must hold pin->dpll->lock.
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_dpll_perout_disable(struct zl3073x_dpll_pin *pin)
+{
+	struct zl3073x_dev *zldev = pin->dpll->dev;
+
+	if (!zl3073x_dev_output_pin_state_get(zldev, pin->id))
+		return 0;
+
+	return zl3073x_dev_output_pin_state_set(zldev, pin->id, false);
+}
+
+static int zl3073x_dpll_ptp_verify(struct ptp_clock_info *info,
+				   unsigned int pin_idx,
+				   enum ptp_pin_function func,
+				   unsigned int chan)
+{
+	/* Any perout pin can serve any perout channel, the channel range is
+	 * validated by the PTP core. The requested pin is resolved from the
+	 * channel via ptp_find_pin() in the enable callback.
+	 */
+	switch (func) {
+	case PTP_PF_NONE:
+	case PTP_PF_PEROUT:
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int zl3073x_dpll_ptp_enable(struct ptp_clock_info *info,
+				   struct ptp_clock_request *rq, int on)
+{
+	struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+						   ptp_info);
+	struct zl3073x_dpll_pin *pin = NULL;
+	struct zl3073x_dpll_pin *sibling;
+	int n, pin_idx, rc;
+	u8 id;
+
+	if (rq->type != PTP_CLK_REQ_PEROUT)
+		return -EOPNOTSUPP;
+
+	if (rq->perout.flags)
+		return -EOPNOTSUPP;
+
+	pin_idx = ptp_find_pin(zldpll->ptp_clock, PTP_PF_PEROUT,
+			       rq->perout.index);
+	if (pin_idx < 0)
+		return -EINVAL;
+
+	n = pin_idx;
+	for_each_set_bit(id, zldpll->perout_map, ZL3073X_NUM_OUTPUT_PINS) {
+		if (!n) {
+			pin = zl3073x_dpll_output_pin_get_by_id(zldpll, id);
+			break;
+		}
+		n--;
+	}
+	if (!pin)
+		return -EINVAL;
+
+	mutex_lock(&zldpll->lock);
+	if (on)
+		rc = zl3073x_dpll_perout_enable(pin, &rq->perout);
+	else
+		rc = zl3073x_dpll_perout_disable(pin);
+	mutex_unlock(&zldpll->lock);
+
+	if (rc)
+		return rc;
+
+	/* Notify the affected output pin and, for shared-divisor formats,
+	 * its sibling sharing the same HW output.
+	 */
+	dpll_pin_change_ntf(pin->dpll_pin);
+	sibling = zl3073x_dpll_output_pin_sibling_get(pin);
+	if (sibling)
+		dpll_pin_change_ntf(sibling->dpll_pin);
+
+	return 0;
+}
+
 static const struct ptp_clock_info zl3073x_dpll_ptp_clock_info = {
 	.owner		= THIS_MODULE,
 	.max_adj	= ZL3073X_DPLL_PTP_MAX_ADJ,
@@ -2832,6 +3002,8 @@ static const struct ptp_clock_info zl3073x_dpll_ptp_clock_info = {
 	.adjfine	= zl3073x_dpll_ptp_adjfine,
 	.adjphase	= zl3073x_dpll_ptp_adjphase,
 	.getmaxphase	= zl3073x_dpll_ptp_getmaxphase,
+	.enable		= zl3073x_dpll_ptp_enable,
+	.verify		= zl3073x_dpll_ptp_verify,
 };
 
 /**
@@ -2843,16 +3015,53 @@ static const struct ptp_clock_info zl3073x_dpll_ptp_clock_info = {
 static int zl3073x_dpll_ptp_register(struct zl3073x_dpll *zldpll)
 {
 	struct zl3073x_dev *zldev = zldpll->dev;
+	struct ptp_pin_desc *pin_config;
+	struct zl3073x_dpll_pin *pin;
 	struct ptp_clock *ptp_clock;
+	unsigned int i;
+	u8 id;
 
 	zldpll->ptp_info = zl3073x_dpll_ptp_clock_info;
 	snprintf(zldpll->ptp_info.name, sizeof(zldpll->ptp_info.name),
 		 "%s-dpll%u", dev_name(zldev->dev), zldpll->id);
 
+	/* Count output pins eligible for periodic output */
+	bitmap_zero(zldpll->perout_map, ZL3073X_NUM_OUTPUT_PINS);
+	list_for_each_entry(pin, &zldpll->pins, list)
+		if (zl3073x_dpll_pin_is_perout_capable(pin))
+			set_bit(pin->id, zldpll->perout_map);
+
+	zldpll->ptp_info.n_pins = bitmap_weight(zldpll->perout_map,
+						ZL3073X_NUM_OUTPUT_PINS);
+	zldpll->ptp_info.n_per_out = zldpll->ptp_info.n_pins;
+	if (!zldpll->ptp_info.n_pins)
+		goto no_pins;
+
+	pin_config = kzalloc_objs(*pin_config, zldpll->ptp_info.n_pins);
+	if (!pin_config)
+		return -ENOMEM;
+
+	i = 0;
+	for_each_set_bit(id, zldpll->perout_map, ZL3073X_NUM_OUTPUT_PINS) {
+		pin = zl3073x_dpll_output_pin_get_by_id(zldpll, id);
+		strscpy(pin_config[i].name, pin->label);
+		pin_config[i].index = i;
+		if (zl3073x_dev_output_pin_state_get(zldev, id)) {
+			pin_config[i].func = PTP_PF_PEROUT;
+			pin_config[i].chan = i;
+		}
+		i++;
+	}
+
+	zldpll->ptp_info.pin_config = pin_config;
+
+no_pins:
 	ptp_clock = ptp_clock_register(&zldpll->ptp_info, zldev->dev);
 	if (IS_ERR(ptp_clock)) {
 		dev_err(zldev->dev, "Failed to register PTP clock for DPLL%u\n",
 			zldpll->id);
+		kfree(zldpll->ptp_info.pin_config);
+		zldpll->ptp_info.pin_config = NULL;
 		return PTR_ERR(ptp_clock);
 	}
 
@@ -2871,6 +3080,8 @@ static void zl3073x_dpll_ptp_unregister(struct zl3073x_dpll *zldpll)
 		ptp_clock_unregister(zldpll->ptp_clock);
 		zldpll->ptp_clock = NULL;
 	}
+	kfree(zldpll->ptp_info.pin_config);
+	zldpll->ptp_info.pin_config = NULL;
 }
 
 /**
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index 993221dc63249d..b9ae6d8301031a 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -9,6 +9,8 @@
 
 #include "core.h"
 
+struct zl3073x_dpll_pin;
+
 /**
  * struct zl3073x_dpll - ZL3073x DPLL sub-device structure
  * @list: this DPLL list entry
@@ -25,6 +27,7 @@
  * @pins: list of pins
  * @ptp_info: PTP clock info
  * @ptp_clock: registered PTP clock (or NULL)
+ * @perout_map: bitmap of output pins eligible for periodic output
  */
 struct zl3073x_dpll {
 	struct list_head		list;
@@ -41,6 +44,7 @@ struct zl3073x_dpll {
 	struct list_head		pins;
 	struct ptp_clock_info		ptp_info;
 	struct ptp_clock		*ptp_clock;
+	DECLARE_BITMAP(perout_map, ZL3073X_NUM_OUTPUT_PINS);
 };
 
 struct zl3073x_dpll *zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch);
diff --git a/drivers/dpll/zl3073x/prop.h b/drivers/dpll/zl3073x/prop.h
index 721a18f05938b8..c5f477b56d4ab1 100644
--- a/drivers/dpll/zl3073x/prop.h
+++ b/drivers/dpll/zl3073x/prop.h
@@ -31,4 +31,25 @@ struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
 
 void zl3073x_pin_props_put(struct zl3073x_pin_props *props);
 
+/**
+ * zl3073x_props_is_freq_supported - check if pin supports given frequency
+ * @props: pin properties
+ * @freq: frequency to check in Hz
+ *
+ * Return: true if the frequency is within the pin supported frequency ranges.
+ */
+static inline bool
+zl3073x_props_is_freq_supported(const struct zl3073x_pin_props *props, u64 freq)
+{
+	const struct dpll_pin_frequency *freqs;
+	int i;
+
+	freqs = props->dpll_props.freq_supported;
+	for (i = 0; i < props->dpll_props.freq_supported_num; i++)
+		if (freq >= freqs[i].min && freq <= freqs[i].max)
+			return true;
+
+	return false;
+}
+
 #endif /* _ZL3073X_PROP_H */
-- 
2.55.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-09-28 18:56 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-28 18:55 [PATCH net-next 0/6] dpll: zl3073x: add PTP periodic output support Ivan Vecera
2026-09-28 18:55 ` [PATCH net-next 1/6] dpll: zl3073x: notify sibling pin when shared output config changes Ivan Vecera
2026-09-28 18:55 ` [PATCH net-next 2/6] dpll: zl3073x: add GPO support for output pins Ivan Vecera
2026-09-28 18:55 ` [PATCH net-next 3/6] dpll: zl3073x: allow enabling/disabling " Ivan Vecera
2026-09-28 18:55 ` [PATCH net-next 4/6] dpll: zl3073x: consolidate pin capabilities into bitmap Ivan Vecera
2026-09-28 18:55 ` [PATCH net-next 5/6] dpll: zl3073x: factor out output pin frequency helper Ivan Vecera
2026-09-28 18:55 ` [PATCH net-next 6/6] dpll: zl3073x: add PTP periodic output support Ivan Vecera

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®