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 2/6] dpll: zl3073x: add GPO support for output pins
Date: Mon, 28 Sep 2026 20:55:48 +0200 [thread overview]
Message-ID: <20260928185552.1103515-3-ivecera@redhat.com> (raw)
In-Reply-To: <20260928185552.1103515-1-ivecera@redhat.com>
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
next prev parent reply other threads:[~2026-09-28 18:56 UTC|newest]
Thread overview: 7+ 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-28 18:55 ` Ivan Vecera [this message]
2026-09-28 18:55 ` [PATCH net-next 3/6] dpll: zl3073x: allow enabling/disabling output pins 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
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-3-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 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®