From: Ivan Vecera <ivecera@redhat.com>
To: netdev@vger.kernel.org
Cc: Chris du Quesnay <Chris.duQuesnay@microchip.com>,
Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>,
Jakub Kicinski <kuba@kernel.org>, Jiri Pirko <jiri@resnulli.us>,
Min Li <min.li@microchip.com>, Paolo Abeni <pabeni@redhat.com>,
Petr Oros <poros@redhat.com>,
Richard Cochran <richardcochran@gmail.com>,
Vadim Fedorenko <vadim.fedorenko@linux.dev>,
linux-kernel@vger.kernel.org
Subject: [PATCH net-next 3/6] dpll: zl3073x: allow enabling/disabling output pins
Date: Mon, 28 Sep 2026 20:55:49 +0200 [thread overview]
Message-ID: <20260928185552.1103515-4-ivecera@redhat.com> (raw)
In-Reply-To: <20260928185552.1103515-1-ivecera@redhat.com>
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
next prev parent reply other threads:[~2026-09-28 18:56 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
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-30 6:57 ` netdev-bot+sashiko
2026-09-30 7:49 ` Ivan Vecera
2026-09-30 14:24 ` Ivan Vecera
2026-09-28 18:55 ` [PATCH net-next 2/6] dpll: zl3073x: add GPO support for output pins Ivan Vecera
2026-09-30 6:57 ` netdev-bot+sashiko
2026-09-30 8:28 ` Ivan Vecera
2026-09-28 18:55 ` Ivan Vecera [this message]
2026-09-30 6:57 ` [PATCH net-next 3/6] dpll: zl3073x: allow enabling/disabling " netdev-bot+sashiko
2026-09-30 10:13 ` 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-30 6:57 ` netdev-bot+sashiko
2026-09-30 10:43 ` Ivan Vecera
2026-09-28 18:55 ` [PATCH net-next 6/6] dpll: zl3073x: add PTP periodic output support Ivan Vecera
2026-09-30 6:57 ` netdev-bot+sashiko
2026-10-01 14:24 ` [PATCH net-next 0/6] " Ivan Vecera
2026-10-01 23:51 ` Jakub Kicinski
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260928185552.1103515-4-ivecera@redhat.com \
--to=ivecera@redhat.com \
--cc=Chris.duQuesnay@microchip.com \
--cc=arkadiusz.kubalewski@intel.com \
--cc=jiri@resnulli.us \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=min.li@microchip.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=poros@redhat.com \
--cc=richardcochran@gmail.com \
--cc=vadim.fedorenko@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.