linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH net-next 0/5] dpll: zl3073x: Add misc features
@ 2025-07-10 15:38 Ivan Vecera
  2025-07-10 15:38 ` [PATCH net-next 1/5] dpll: zl3073x: Add support to get/set esync on pins Ivan Vecera
                   ` (6 more replies)
  0 siblings, 7 replies; 18+ messages in thread
From: Ivan Vecera @ 2025-07-10 15:38 UTC (permalink / raw)
  To: netdev
  Cc: Arkadiusz Kubalewski, Jiri Pirko, Prathosh Satish,
	David S. Miller, Jakub Kicinski, Paolo Abeni, linux-kernel,
	Michal Schmidt, Petr Oros

Add several new features missing in initial submission:

* Embedded sync for both pin types
* Phase offset reporting for connected input pin
* Selectable phase offset monitoring (aka all inputs phase monitor)
* Phase adjustments for both pin types
* Fractional frequency offset reporting for input pins

Everything was tested on Microchip EVB-LAN9668 EDS2 development board.

Ivan Vecera (5):
  dpll: zl3073x: Add support to get/set esync on pins
  dpll: zl3073x: Add support to get phase offset on connected input pin
  dpll: zl3073x: Implement phase offset monitor feature
  dpll: zl3073x: Add support to adjust phase
  dpll: zl3073x: Add support to get fractional frequency offset

 drivers/dpll/zl3073x/core.c | 171 ++++++++
 drivers/dpll/zl3073x/core.h |  16 +
 drivers/dpll/zl3073x/dpll.c | 805 +++++++++++++++++++++++++++++++++++-
 drivers/dpll/zl3073x/dpll.h |   4 +
 drivers/dpll/zl3073x/regs.h |  55 +++
 5 files changed, 1049 insertions(+), 2 deletions(-)

-- 
2.49.0


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

* [PATCH net-next 1/5] dpll: zl3073x: Add support to get/set esync on pins
  2025-07-10 15:38 [PATCH net-next 0/5] dpll: zl3073x: Add misc features Ivan Vecera
@ 2025-07-10 15:38 ` Ivan Vecera
  2025-07-14 14:13   ` Prathosh.Satish
  2025-07-15 12:52   ` Paolo Abeni
  2025-07-10 15:38 ` [PATCH net-next 2/5] dpll: zl3073x: Add support to get phase offset on connected input pin Ivan Vecera
                   ` (5 subsequent siblings)
  6 siblings, 2 replies; 18+ messages in thread
From: Ivan Vecera @ 2025-07-10 15:38 UTC (permalink / raw)
  To: netdev
  Cc: Prathosh Satish, Arkadiusz Kubalewski, Jiri Pirko,
	David S. Miller, Jakub Kicinski, Paolo Abeni, linux-kernel,
	Michal Schmidt, Petr Oros

Add support to get/set embedded sync for both input and output pins.
The DPLL is able to lock on input reference when the embedded sync
frequency is 1 PPS and pulse width 25%. The esync on outputs are more
versatille and theoretically supports any esync frequency that divides
current output frequency but for now support the same that supported on
input pins (1 PPS & 25% pulse).

Note that for the output pins the esync divisor shares the same register
used for N-divided signal formats. Due to this the esync cannot be
enabled on outputs configured with one of the N-divided signal formats.

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/zl3073x/dpll.c | 343 +++++++++++++++++++++++++++++++++++-
 drivers/dpll/zl3073x/regs.h |  11 ++
 2 files changed, 353 insertions(+), 1 deletion(-)

diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index cb0f1a43c5fbd..2af20532b1ca0 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -34,6 +34,7 @@
  * @id: pin id
  * @prio: pin priority <0, 14>
  * @selectable: pin is selectable in automatic mode
+ * @esync_control: embedded sync is controllable
  * @pin_state: last saved pin state
  */
 struct zl3073x_dpll_pin {
@@ -45,9 +46,17 @@ struct zl3073x_dpll_pin {
 	u8			id;
 	u8			prio;
 	bool			selectable;
+	bool			esync_control;
 	enum dpll_pin_state	pin_state;
 };
 
+/*
+ * Supported esync ranges for input and for output per output pair type
+ */
+static const struct dpll_pin_frequency esync_freq_ranges[] = {
+	DPLL_PIN_FREQUENCY_RANGE(0, 1),
+};
+
 /**
  * zl3073x_dpll_is_input_pin - check if the pin is input one
  * @pin: pin to check
@@ -139,6 +148,126 @@ zl3073x_dpll_input_ref_frequency_get(struct zl3073x_dpll *zldpll, u8 ref_id,
 	return rc;
 }
 
+static int
+zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin,
+				 void *pin_priv,
+				 const struct dpll_device *dpll,
+				 void *dpll_priv,
+				 struct dpll_pin_esync *esync,
+				 struct netlink_ext_ack *extack)
+{
+	struct zl3073x_dpll *zldpll = dpll_priv;
+	struct zl3073x_dev *zldev = zldpll->dev;
+	struct zl3073x_dpll_pin *pin = pin_priv;
+	u8 ref, ref_sync_ctrl, sync_mode;
+	u32 esync_div, ref_freq;
+	int rc;
+
+	/* Get reference frequency */
+	ref = zl3073x_input_pin_ref_get(pin->id);
+	rc = zl3073x_dpll_input_ref_frequency_get(zldpll, pin->id, &ref_freq);
+	if (rc)
+		return rc;
+
+	guard(mutex)(&zldev->multiop_lock);
+
+	/* Read reference configuration into mailbox */
+	rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD,
+			   ZL_REG_REF_MB_MASK, BIT(ref));
+	if (rc)
+		return rc;
+
+	/* Get ref sync mode */
+	rc = zl3073x_read_u8(zldev, ZL_REG_REF_SYNC_CTRL, &ref_sync_ctrl);
+	if (rc)
+		return rc;
+
+	/* Get esync divisor */
+	rc = zl3073x_read_u32(zldev, ZL_REG_REF_ESYNC_DIV, &esync_div);
+	if (rc)
+		return rc;
+
+	sync_mode = FIELD_GET(ZL_REF_SYNC_CTRL_MODE, ref_sync_ctrl);
+
+	switch (sync_mode) {
+	case ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75:
+		esync->freq = (esync_div == ZL_REF_ESYNC_DIV_1HZ) ? 1 : 0;
+		esync->pulse = 25;
+		break;
+	default:
+		esync->freq = 0;
+		esync->pulse = 0;
+		break;
+	}
+
+	/* If the pin supports esync control expose its range but only
+	 * if the current reference frequency is > 1 Hz.
+	 */
+	if (pin->esync_control && ref_freq > 1) {
+		esync->range = esync_freq_ranges;
+		esync->range_num = ARRAY_SIZE(esync_freq_ranges);
+	} else {
+		esync->range = NULL;
+		esync->range_num = 0;
+	}
+
+	return rc;
+}
+
+static int
+zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin,
+				 void *pin_priv,
+				 const struct dpll_device *dpll,
+				 void *dpll_priv, u64 freq,
+				 struct netlink_ext_ack *extack)
+{
+	struct zl3073x_dpll *zldpll = dpll_priv;
+	struct zl3073x_dev *zldev = zldpll->dev;
+	struct zl3073x_dpll_pin *pin = pin_priv;
+	u8 ref, ref_sync_ctrl, sync_mode;
+	int rc;
+
+	guard(mutex)(&zldev->multiop_lock);
+
+	/* Read reference configuration into mailbox */
+	ref = zl3073x_input_pin_ref_get(pin->id);
+	rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD,
+			   ZL_REG_REF_MB_MASK, BIT(ref));
+	if (rc)
+		return rc;
+
+	/* Get ref sync mode */
+	rc = zl3073x_read_u8(zldev, ZL_REG_REF_SYNC_CTRL, &ref_sync_ctrl);
+	if (rc)
+		return rc;
+
+	/* Use freq == 0 to disable esync */
+	if (!freq)
+		sync_mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF;
+	else
+		sync_mode = ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75;
+
+	ref_sync_ctrl &= ~ZL_REF_SYNC_CTRL_MODE;
+	ref_sync_ctrl |= FIELD_PREP(ZL_REF_SYNC_CTRL_MODE, sync_mode);
+
+	/* Update ref sync control register */
+	rc = zl3073x_write_u8(zldev, ZL_REG_REF_SYNC_CTRL, ref_sync_ctrl);
+	if (rc)
+		return rc;
+
+	if (freq) {
+		/* 1 Hz is only supported frequnecy currently */
+		rc = zl3073x_write_u32(zldev, ZL_REG_REF_ESYNC_DIV,
+				       ZL_REF_ESYNC_DIV_1HZ);
+		if (rc)
+			return rc;
+	}
+
+	/* Commit reference configuration */
+	return zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR,
+			     ZL_REG_REF_MB_MASK, BIT(ref));
+}
+
 static int
 zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin,
 				     void *pin_priv,
@@ -640,6 +769,213 @@ zl3073x_dpll_input_pin_prio_set(const struct dpll_pin *dpll_pin, void *pin_priv,
 	return 0;
 }
 
+static int
+zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin,
+				  void *pin_priv,
+				  const struct dpll_device *dpll,
+				  void *dpll_priv,
+				  struct dpll_pin_esync *esync,
+				  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 device *dev = zldev->dev;
+	u32 esync_period, esync_width;
+	u8 clock_type, synth;
+	u8 out, output_mode;
+	u32 output_div;
+	u32 synth_freq;
+	int rc;
+
+	out = zl3073x_output_pin_out_get(pin->id);
+
+	/* If N-division is enabled, esync is not supported. The register used
+	 * for N-division is also used for the esync divider so both cannot
+	 * be used.
+	 */
+	switch (zl3073x_out_signal_format_get(zldev, out)) {
+	case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV:
+	case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV:
+		return -EOPNOTSUPP;
+	default:
+		break;
+	}
+
+	guard(mutex)(&zldev->multiop_lock);
+
+	/* Read output configuration into mailbox */
+	rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD,
+			   ZL_REG_OUTPUT_MB_MASK, BIT(out));
+	if (rc)
+		return rc;
+
+	/* Read output mode */
+	rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode);
+	if (rc)
+		return rc;
+
+	/* Read output divisor */
+	rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div);
+	if (rc)
+		return rc;
+
+	/* Check output divisor for zero */
+	if (!output_div) {
+		dev_err(dev, "Zero divisor for OUTPUT%u got from device\n",
+			out);
+		return -EINVAL;
+	}
+
+	/* Get synth attached to output pin */
+	synth = zl3073x_out_synth_get(zldev, out);
+
+	/* Get synth frequency */
+	synth_freq = zl3073x_synth_freq_get(zldev, synth);
+
+	clock_type = FIELD_GET(ZL_OUTPUT_MODE_CLOCK_TYPE, output_mode);
+	if (clock_type != ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC) {
+		/* No need to read esync data if it is not enabled */
+		esync->freq = 0;
+		esync->pulse = 0;
+
+		goto finish;
+	}
+
+	/* Read esync period */
+	rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, &esync_period);
+	if (rc)
+		return rc;
+
+	/* Check esync divisor for zero */
+	if (!esync_period) {
+		dev_err(dev, "Zero esync divisor for OUTPUT%u got from device\n",
+			out);
+		return -EINVAL;
+	}
+
+	/* Get esync pulse width in units of half synth cycles */
+	rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, &esync_width);
+	if (rc)
+		return rc;
+
+	/* Compute esync frequency */
+	esync->freq = synth_freq / output_div / esync_period;
+
+	/* By comparing the esync_pulse_width to the half of the pulse width
+	 * the esync pulse percentage can be determined.
+	 * Note that half pulse width is in units of half synth cycles, which
+	 * is why it reduces down to be output_div.
+	 */
+	esync->pulse = (50 * esync_width) / output_div;
+
+finish:
+	/* Set supported esync ranges if the pin supports esync control and
+	 * if the output frequency is > 1 Hz.
+	 */
+	if (pin->esync_control && (synth_freq / output_div) > 1) {
+		esync->range = esync_freq_ranges;
+		esync->range_num = ARRAY_SIZE(esync_freq_ranges);
+	} else {
+		esync->range = NULL;
+		esync->range_num = 0;
+	}
+
+	return 0;
+}
+
+static int
+zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
+				  void *pin_priv,
+				  const struct dpll_device *dpll,
+				  void *dpll_priv, u64 freq,
+				  struct netlink_ext_ack *extack)
+{
+	struct zl3073x_dpll *zldpll = dpll_priv;
+	struct zl3073x_dev *zldev = zldpll->dev;
+	struct zl3073x_dpll_pin *pin = pin_priv;
+	u32 esync_period, esync_width, output_div;
+	u8 clock_type, out, output_mode, synth;
+	u32 synth_freq;
+	int rc;
+
+	out = zl3073x_output_pin_out_get(pin->id);
+
+	/* If N-division is enabled, esync is not supported. The register used
+	 * for N-division is also used for the esync divider so both cannot
+	 * be used.
+	 */
+	switch (zl3073x_out_signal_format_get(zldev, out)) {
+	case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV:
+	case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV:
+		return -EOPNOTSUPP;
+	default:
+		break;
+	}
+
+	guard(mutex)(&zldev->multiop_lock);
+
+	/* Read output configuration into mailbox */
+	rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD,
+			   ZL_REG_OUTPUT_MB_MASK, BIT(out));
+	if (rc)
+		return rc;
+
+	/* Read output mode */
+	rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode);
+	if (rc)
+		return rc;
+
+	/* Select clock type */
+	if (freq)
+		clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC;
+	else
+		clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL;
+
+	/* Update clock type in output mode */
+	output_mode &= ~ZL_OUTPUT_MODE_CLOCK_TYPE;
+	output_mode |= FIELD_PREP(ZL_OUTPUT_MODE_CLOCK_TYPE, clock_type);
+	rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_MODE, output_mode);
+	if (rc)
+		return rc;
+
+	/* If esync is being disabled just write mailbox and finish */
+	if (!freq)
+		goto write_mailbox;
+
+	/* Get synth attached to output pin */
+	synth = zl3073x_out_synth_get(zldev, out);
+
+	/* Get synth frequency */
+	synth_freq = zl3073x_synth_freq_get(zldev, synth);
+
+	rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div);
+	if (rc)
+		return rc;
+
+	/* Compute and update esync period */
+	esync_period = synth_freq / (u32)freq / output_div;
+	rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, esync_period);
+	if (rc)
+		return rc;
+
+	/* 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.
+	 */
+	esync_width = output_div / 2;
+	rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, esync_width);
+	if (rc)
+		return rc;
+
+write_mailbox:
+	/* Commit output configuration */
+	return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR,
+			     ZL_REG_OUTPUT_MB_MASK, BIT(out));
+}
+
 static int
 zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin,
 				      void *pin_priv,
@@ -956,6 +1292,8 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
 
 static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
 	.direction_get = zl3073x_dpll_pin_direction_get,
+	.esync_get = zl3073x_dpll_input_pin_esync_get,
+	.esync_set = zl3073x_dpll_input_pin_esync_set,
 	.frequency_get = zl3073x_dpll_input_pin_frequency_get,
 	.frequency_set = zl3073x_dpll_input_pin_frequency_set,
 	.prio_get = zl3073x_dpll_input_pin_prio_get,
@@ -966,6 +1304,8 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
 
 static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
 	.direction_get = zl3073x_dpll_pin_direction_get,
+	.esync_get = zl3073x_dpll_output_pin_esync_get,
+	.esync_set = zl3073x_dpll_output_pin_esync_set,
 	.frequency_get = zl3073x_dpll_output_pin_frequency_get,
 	.frequency_set = zl3073x_dpll_output_pin_frequency_set,
 	.state_on_dpll_get = zl3073x_dpll_output_pin_state_on_dpll_get,
@@ -1040,8 +1380,9 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
 	if (IS_ERR(props))
 		return PTR_ERR(props);
 
-	/* Save package label */
+	/* Save package label & esync capability */
 	strscpy(pin->label, props->package_label);
+	pin->esync_control = props->esync_control;
 
 	if (zl3073x_dpll_is_input_pin(pin)) {
 		rc = zl3073x_dpll_ref_prio_get(pin, &pin->prio);
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index 493c63e729208..64bb43bbc3168 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -146,6 +146,14 @@
 #define ZL_REF_CONFIG_ENABLE			BIT(0)
 #define ZL_REF_CONFIG_DIFF_EN			BIT(2)
 
+#define ZL_REG_REF_SYNC_CTRL			ZL_REG(10, 0x2e, 1)
+#define ZL_REF_SYNC_CTRL_MODE			GENMASK(2, 0)
+#define ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF	0
+#define ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75	2
+
+#define ZL_REG_REF_ESYNC_DIV			ZL_REG(10, 0x30, 4)
+#define ZL_REF_ESYNC_DIV_1HZ			0
+
 /********************************
  * Register Page 12, DPLL Mailbox
  ********************************/
@@ -188,6 +196,9 @@
 #define ZL_OUTPUT_MB_SEM_RD			BIT(1)
 
 #define ZL_REG_OUTPUT_MODE			ZL_REG(14, 0x05, 1)
+#define ZL_OUTPUT_MODE_CLOCK_TYPE		GENMASK(2, 0)
+#define ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL	0
+#define ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC		1
 #define ZL_OUTPUT_MODE_SIGNAL_FORMAT		GENMASK(7, 4)
 #define ZL_OUTPUT_MODE_SIGNAL_FORMAT_DISABLED	0
 #define ZL_OUTPUT_MODE_SIGNAL_FORMAT_LVDS	1
-- 
2.49.0


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

* [PATCH net-next 2/5] dpll: zl3073x: Add support to get phase offset on connected input pin
  2025-07-10 15:38 [PATCH net-next 0/5] dpll: zl3073x: Add misc features Ivan Vecera
  2025-07-10 15:38 ` [PATCH net-next 1/5] dpll: zl3073x: Add support to get/set esync on pins Ivan Vecera
@ 2025-07-10 15:38 ` Ivan Vecera
  2025-07-14 14:14   ` Prathosh.Satish
  2025-07-10 15:38 ` [PATCH net-next 3/5] dpll: zl3073x: Implement phase offset monitor feature Ivan Vecera
                   ` (4 subsequent siblings)
  6 siblings, 1 reply; 18+ messages in thread
From: Ivan Vecera @ 2025-07-10 15:38 UTC (permalink / raw)
  To: netdev
  Cc: Prathosh Satish, Arkadiusz Kubalewski, Jiri Pirko,
	David S. Miller, Jakub Kicinski, Paolo Abeni, linux-kernel,
	Michal Schmidt, Petr Oros

Add support to get phase offset for the connected input pin. Implement
the appropriate callback and function that performs DPLL to connected
reference phase error measurement and notifies DPLL core about changes.

The measurement is performed internally by device on background 40 times
per second but the measured value is read each second and compared with
previous value.

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/zl3073x/core.c |  86 +++++++++++++++++++++++++++++
 drivers/dpll/zl3073x/dpll.c | 105 +++++++++++++++++++++++++++++++++++-
 drivers/dpll/zl3073x/dpll.h |   2 +
 drivers/dpll/zl3073x/regs.h |  16 ++++++
 4 files changed, 208 insertions(+), 1 deletion(-)

diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index f2d58e1a56726..c980c85e7ac51 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -669,12 +669,52 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev)
 	return rc;
 }
 
+/**
+ * zl3073x_ref_phase_offsets_update - update reference phase offsets
+ * @zldev: pointer to zl3073x_dev structure
+ *
+ * Ask device to update phase offsets latch registers with the latest
+ * measured values.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev)
+{
+	int rc;
+
+	/* Per datasheet we have to wait for 'dpll_ref_phase_err_rqst_rd'
+	 * to be zero to ensure that the measured data are coherent.
+	 */
+	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
+				  ZL_REF_PHASE_ERR_READ_RQST_RD);
+	if (rc)
+		return rc;
+
+	/* Request to update phase offsets measurement values */
+	rc = zl3073x_write_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
+			      ZL_REF_PHASE_ERR_READ_RQST_RD);
+	if (rc)
+		return rc;
+
+	/* Wait for finish */
+	return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
+				    ZL_REF_PHASE_ERR_READ_RQST_RD);
+}
+
 static void
 zl3073x_dev_periodic_work(struct kthread_work *work)
 {
 	struct zl3073x_dev *zldev = container_of(work, struct zl3073x_dev,
 						 work.work);
 	struct zl3073x_dpll *zldpll;
+	int rc;
+
+	/* Update DPLL-to-connected-ref phase offsets registers */
+	rc = zl3073x_ref_phase_offsets_update(zldev);
+	if (rc)
+		dev_warn(zldev->dev, "Failed to update phase offsets: %pe\n",
+			 ERR_PTR(rc));
 
 	list_for_each_entry(zldpll, &zldev->dplls, list)
 		zl3073x_dpll_changes_check(zldpll);
@@ -767,6 +807,46 @@ zl3073x_devm_dpll_init(struct zl3073x_dev *zldev, u8 num_dplls)
 	return rc;
 }
 
+/**
+ * zl3073x_dev_phase_meas_setup - setup phase offset measurement
+ * @zldev: pointer to zl3073x_dev structure
+ * @num_channels: number of DPLL channels
+ *
+ * Enable phase offset measurement block, set measurement averaging factor
+ * and enable DPLL-to-its-ref phase measurement for all DPLLs.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+static int
+zl3073x_dev_phase_meas_setup(struct zl3073x_dev *zldev, int num_channels)
+{
+	u8 dpll_meas_ctrl, mask;
+	int i, rc;
+
+	/* Read DPLL phase measurement control register */
+	rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, &dpll_meas_ctrl);
+	if (rc)
+		return rc;
+
+	/* Setup phase measurement averaging factor */
+	dpll_meas_ctrl &= ~ZL_DPLL_MEAS_CTRL_AVG_FACTOR;
+	dpll_meas_ctrl |= FIELD_PREP(ZL_DPLL_MEAS_CTRL_AVG_FACTOR, 3);
+
+	/* Enable DPLL measurement block */
+	dpll_meas_ctrl |= ZL_DPLL_MEAS_CTRL_EN;
+
+	/* Update phase measurement control register */
+	rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, dpll_meas_ctrl);
+	if (rc)
+		return rc;
+
+	/* Enable DPLL-to-connected-ref measurement for each channel */
+	for (i = 0, mask = 0; i < num_channels; i++)
+		mask |= BIT(i);
+
+	return zl3073x_write_u8(zldev, ZL_REG_DPLL_PHASE_ERR_READ_MASK, mask);
+}
+
 /**
  * zl3073x_dev_probe - initialize zl3073x device
  * @zldev: pointer to zl3073x device
@@ -839,6 +919,12 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev,
 	if (rc)
 		return rc;
 
+	/* Setup phase offset measurement block */
+	rc = zl3073x_dev_phase_meas_setup(zldev, chip_info->num_channels);
+	if (rc)
+		return dev_err_probe(zldev->dev, rc,
+				     "Failed to setup phase measurement\n");
+
 	/* Register DPLL channels */
 	rc = zl3073x_devm_dpll_init(zldev, chip_info->num_channels);
 	if (rc)
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 2af20532b1ca0..1a1d4de5b9de5 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -36,6 +36,7 @@
  * @selectable: pin is selectable in automatic mode
  * @esync_control: embedded sync is controllable
  * @pin_state: last saved pin state
+ * @phase_offset: last saved pin phase offset
  */
 struct zl3073x_dpll_pin {
 	struct list_head	list;
@@ -48,6 +49,7 @@ struct zl3073x_dpll_pin {
 	bool			selectable;
 	bool			esync_control;
 	enum dpll_pin_state	pin_state;
+	s64			phase_offset;
 };
 
 /*
@@ -496,6 +498,50 @@ zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref)
 	return 0;
 }
 
+static int
+zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
+					void *pin_priv,
+					const struct dpll_device *dpll,
+					void *dpll_priv, s64 *phase_offset,
+					struct netlink_ext_ack *extack)
+{
+	struct zl3073x_dpll *zldpll = dpll_priv;
+	struct zl3073x_dev *zldev = zldpll->dev;
+	struct zl3073x_dpll_pin *pin = pin_priv;
+	u8 conn_ref, ref, ref_status;
+	int rc;
+
+	/* Get currently connected reference */
+	rc = zl3073x_dpll_connected_ref_get(zldpll, &conn_ref);
+	if (rc)
+		return rc;
+
+	/* Report phase offset only for currently connected pin */
+	ref = zl3073x_input_pin_ref_get(pin->id);
+	if (ref != conn_ref) {
+		*phase_offset = 0;
+
+		return 0;
+	}
+
+	/* Get this pin monitor status */
+	rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref), &ref_status);
+	if (rc)
+		return rc;
+
+	/* Report phase offset only if the input pin signal is present */
+	if (ref_status != ZL_REF_MON_STATUS_OK) {
+		*phase_offset = 0;
+
+		return 0;
+	}
+
+	/* Report the latest measured phase offset for the connected ref */
+	*phase_offset = pin->phase_offset * DPLL_PHASE_OFFSET_DIVIDER;
+
+	return rc;
+}
+
 /**
  * zl3073x_dpll_ref_prio_get - get priority for given input pin
  * @pin: pointer to pin
@@ -1296,6 +1342,7 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
 	.esync_set = zl3073x_dpll_input_pin_esync_set,
 	.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,
 	.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,
@@ -1666,6 +1713,51 @@ zl3073x_dpll_device_unregister(struct zl3073x_dpll *zldpll)
 	zldpll->dpll_dev = NULL;
 }
 
+/**
+ * zl3073x_dpll_pin_phase_offset_check - check for pin phase offset change
+ * @pin: pin to check
+ *
+ * Check for the change of DPLL to connected pin phase offset change.
+ *
+ * Return: true on phase offset change, false otherwise
+ */
+static bool
+zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
+{
+	struct zl3073x_dpll *zldpll = pin->dpll;
+	struct zl3073x_dev *zldev = zldpll->dev;
+	s64 phase_offset;
+	int rc;
+
+	/* Do not check phase offset if the pin is not connected one */
+	if (pin->pin_state != DPLL_PIN_STATE_CONNECTED)
+		return false;
+
+	/* Read DPLL-to-connected-ref phase offset measurement value */
+	rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_PHASE_ERR_DATA(zldpll->id),
+			      &phase_offset);
+	if (rc) {
+		dev_err(zldev->dev, "Failed to read ref phase offset: %pe\n",
+			ERR_PTR(rc));
+
+		return false;
+	}
+
+	/* Convert to ps */
+	phase_offset = div_s64(sign_extend64(phase_offset, 47), 100);
+
+	/* Compare with previous value */
+	if (phase_offset != pin->phase_offset) {
+		dev_dbg(zldev->dev, "%s phase offset changed: %lld -> %lld\n",
+			pin->label, pin->phase_offset, phase_offset);
+		pin->phase_offset = phase_offset;
+
+		return true;
+	}
+
+	return false;
+}
+
 /**
  * zl3073x_dpll_changes_check - check for changes and send notifications
  * @zldpll: pointer to zl3073x_dpll structure
@@ -1684,6 +1776,8 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
 	struct zl3073x_dpll_pin *pin;
 	int rc;
 
+	zldpll->check_count++;
+
 	/* Get current lock status for the DPLL */
 	rc = zl3073x_dpll_lock_status_get(zldpll->dpll_dev, zldpll,
 					  &lock_status, NULL, NULL);
@@ -1708,6 +1802,7 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
 
 	list_for_each_entry(pin, &zldpll->pins, list) {
 		enum dpll_pin_state state;
+		bool pin_changed = false;
 
 		/* Output pins change checks are not necessary because output
 		 * states are constant.
@@ -1727,8 +1822,16 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
 			dev_dbg(dev, "%s state changed: %u->%u\n", pin->label,
 				pin->pin_state, state);
 			pin->pin_state = state;
-			dpll_pin_change_ntf(pin->dpll_pin);
+			pin_changed = true;
 		}
+
+		/* Check for phase offset change once per second */
+		if (zldpll->check_count % 2 == 0)
+			if (zl3073x_dpll_pin_phase_offset_check(pin))
+				pin_changed = true;
+
+		if (pin_changed)
+			dpll_pin_change_ntf(pin->dpll_pin);
 	}
 }
 
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index db7388cc377fd..2e84e56f8c9e1 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -15,6 +15,7 @@
  * @id: DPLL index
  * @refsel_mode: reference selection mode
  * @forced_ref: selected reference in forced reference lock mode
+ * @check_count: periodic check counter
  * @dpll_dev: pointer to registered DPLL device
  * @lock_status: last saved DPLL lock status
  * @pins: list of pins
@@ -25,6 +26,7 @@ struct zl3073x_dpll {
 	u8				id;
 	u8				refsel_mode;
 	u8				forced_ref;
+	u8				check_count;
 	struct dpll_device		*dpll_dev;
 	enum dpll_lock_status		lock_status;
 	struct list_head		pins;
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index 64bb43bbc3168..8dde92e623f76 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -94,6 +94,13 @@
 #define ZL_DPLL_REFSEL_STATUS_STATE		GENMASK(6, 4)
 #define ZL_DPLL_REFSEL_STATUS_STATE_LOCK	4
 
+/**********************
+ * Register Page 4, Ref
+ **********************/
+
+#define ZL_REG_REF_PHASE_ERR_READ_RQST		ZL_REG(4, 0x0f, 1)
+#define ZL_REF_PHASE_ERR_READ_RQST_RD		BIT(0)
+
 /***********************
  * Register Page 5, DPLL
  ***********************/
@@ -108,6 +115,15 @@
 #define ZL_DPLL_MODE_REFSEL_MODE_NCO		4
 #define ZL_DPLL_MODE_REFSEL_REF			GENMASK(7, 4)
 
+#define ZL_REG_DPLL_MEAS_CTRL			ZL_REG(5, 0x50, 1)
+#define ZL_DPLL_MEAS_CTRL_EN			BIT(0)
+#define ZL_DPLL_MEAS_CTRL_AVG_FACTOR		GENMASK(7, 4)
+
+#define ZL_REG_DPLL_PHASE_ERR_READ_MASK		ZL_REG(5, 0x54, 1)
+
+#define ZL_REG_DPLL_PHASE_ERR_DATA(_idx)				\
+	ZL_REG_IDX(_idx, 5, 0x55, 6, ZL3073X_MAX_CHANNELS, 6)
+
 /***********************************
  * Register Page 9, Synth and Output
  ***********************************/
-- 
2.49.0


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

* [PATCH net-next 3/5] dpll: zl3073x: Implement phase offset monitor feature
  2025-07-10 15:38 [PATCH net-next 0/5] dpll: zl3073x: Add misc features Ivan Vecera
  2025-07-10 15:38 ` [PATCH net-next 1/5] dpll: zl3073x: Add support to get/set esync on pins Ivan Vecera
  2025-07-10 15:38 ` [PATCH net-next 2/5] dpll: zl3073x: Add support to get phase offset on connected input pin Ivan Vecera
@ 2025-07-10 15:38 ` Ivan Vecera
  2025-07-14 14:15   ` Prathosh.Satish
  2025-07-15 13:02   ` Paolo Abeni
  2025-07-10 15:38 ` [PATCH net-next 4/5] dpll: zl3073x: Add support to adjust phase Ivan Vecera
                   ` (3 subsequent siblings)
  6 siblings, 2 replies; 18+ messages in thread
From: Ivan Vecera @ 2025-07-10 15:38 UTC (permalink / raw)
  To: netdev
  Cc: Prathosh Satish, Arkadiusz Kubalewski, Jiri Pirko,
	David S. Miller, Jakub Kicinski, Paolo Abeni, linux-kernel,
	Michal Schmidt, Petr Oros

Implement phase offset monitor feature to allow a user to monitor
phase offsets across all available inputs.

The device firmware periodically performs phase offsets measurements for
all available DPLL channels and input references. The driver can ask
the firmware to fill appropriate latch registers with measured values.

There are 2 sets of latch registers for phase offsets reporting:

1) DPLL-to-connected-ref: up to 5 registers that contain values for
   phase offset between particular DPLL channel and its connected input
   reference.
2) selected-DPLL-to-ref: 10  registers that contain values for phase
   offsets between selected DPLL channel and all available input
   references.

Both are filled with single read request so the driver can read
DPLL-to-connected-ref phase offset for all DPLL channels at once.
This was implemented in the previous patch.

To read selected-DPLL-to-ref registers for all DPLLs a separate read
request has to be sent to device firmware for each DPLL channel.

To implement phase offset monitor feature:
* Extend zl3073x_ref_phase_offsets_update() to select given DPLL channel
  in phase offset read request. The caller can set channel==-1 if it
  will not read Type2 registers.
* Use this extended function to update phase offset latch registers
  during zl3073x_dpll_changes_check() call if phase monitor is enabled
* Extend zl3073x_dpll_pin_phase_offset_check() to check phase offset
  changes for all available input references
* Extend zl3073x_dpll_input_pin_phase_offset_get() to report phase
  offset values for all available input references
* Implement phase offset monitor callbacks to enable/disable this
  feature

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/zl3073x/core.c |  28 ++++++--
 drivers/dpll/zl3073x/core.h |   1 +
 drivers/dpll/zl3073x/dpll.c | 126 +++++++++++++++++++++++++++++++++---
 drivers/dpll/zl3073x/dpll.h |   2 +
 drivers/dpll/zl3073x/regs.h |   6 ++
 5 files changed, 149 insertions(+), 14 deletions(-)

diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index c980c85e7ac51..eb62a492b1727 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -672,14 +672,25 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev)
 /**
  * zl3073x_ref_phase_offsets_update - update reference phase offsets
  * @zldev: pointer to zl3073x_dev structure
+ * @channel: DPLL channel number or -1
  *
- * Ask device to update phase offsets latch registers with the latest
- * measured values.
+ * The function asks device to update phase offsets latch registers with
+ * the latest measured values. There are 2 sets of latch registers:
+ *
+ * 1) Up to 5 DPLL-to-connected-ref registers that contain phase offset
+ *    values between particular DPLL channel and its *connected* input
+ *    reference.
+ *
+ * 2) 10 selected-DPLL-to-all-ref registers that contain phase offset values
+ *    between selected DPLL channel and all input references.
+ *
+ * If the caller is interested in 2) then it has to pass DPLL channel number
+ * in @channel parameter. If it is interested only in 1) then it should pass
+ * @channel parameter with value of -1.
  *
  * Return: 0 on success, <0 on error
  */
-static int
-zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev)
+int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
 {
 	int rc;
 
@@ -691,6 +702,13 @@ zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev)
 	if (rc)
 		return rc;
 
+	/* Select DPLL channel if it is specified */
+	if (channel != -1) {
+		rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_IDX, channel);
+		if (rc)
+			return rc;
+	}
+
 	/* Request to update phase offsets measurement values */
 	rc = zl3073x_write_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
 			      ZL_REF_PHASE_ERR_READ_RQST_RD);
@@ -711,7 +729,7 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
 	int rc;
 
 	/* Update DPLL-to-connected-ref phase offsets registers */
-	rc = zl3073x_ref_phase_offsets_update(zldev);
+	rc = zl3073x_ref_phase_offsets_update(zldev, -1);
 	if (rc)
 		dev_warn(zldev->dev, "Failed to update phase offsets: %pe\n",
 			 ERR_PTR(rc));
diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
index 97b1032e392d6..1a5edc4975735 100644
--- a/drivers/dpll/zl3073x/core.h
+++ b/drivers/dpll/zl3073x/core.h
@@ -130,6 +130,7 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val);
  *****************/
 
 int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult);
+int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel);
 
 static inline bool
 zl3073x_is_n_pin(u8 id)
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 1a1d4de5b9de5..198e19f6fb152 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -509,6 +509,7 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
 	struct zl3073x_dev *zldev = zldpll->dev;
 	struct zl3073x_dpll_pin *pin = pin_priv;
 	u8 conn_ref, ref, ref_status;
+	s64 ref_phase;
 	int rc;
 
 	/* Get currently connected reference */
@@ -516,9 +517,11 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
 	if (rc)
 		return rc;
 
-	/* Report phase offset only for currently connected pin */
+	/* Report phase offset only for currently connected pin if the phase
+	 * monitor feature is disabled.
+	 */
 	ref = zl3073x_input_pin_ref_get(pin->id);
-	if (ref != conn_ref) {
+	if (!zldpll->phase_monitor && ref != conn_ref) {
 		*phase_offset = 0;
 
 		return 0;
@@ -536,8 +539,38 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
 		return 0;
 	}
 
-	/* Report the latest measured phase offset for the connected ref */
-	*phase_offset = pin->phase_offset * DPLL_PHASE_OFFSET_DIVIDER;
+	ref_phase = pin->phase_offset;
+
+	/* The DPLL being locked to a higher freq than the current ref
+	 * the phase offset is modded to the period of the signal
+	 * the dpll is locked to.
+	 */
+	if (ZL3073X_DPLL_REF_IS_VALID(conn_ref) && conn_ref != ref) {
+		u32 conn_freq, ref_freq;
+
+		/* Get frequency of connected ref */
+		rc = zl3073x_dpll_input_ref_frequency_get(zldpll, conn_ref,
+							  &conn_freq);
+		if (rc)
+			return rc;
+
+		/* Get frequency of given ref */
+		rc = zl3073x_dpll_input_ref_frequency_get(zldpll, ref,
+							  &ref_freq);
+		if (rc)
+			return rc;
+
+		if (conn_freq > ref_freq) {
+			s64 conn_period;
+			int div_factor;
+
+			conn_period = div_s64(PSEC_PER_SEC, conn_freq);
+			div_factor = div64_s64(ref_phase, conn_period);
+			ref_phase -= conn_period * div_factor;
+		}
+	}
+
+	*phase_offset = ref_phase * DPLL_PHASE_OFFSET_DIVIDER;
 
 	return rc;
 }
@@ -1336,6 +1369,35 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
 	return 0;
 }
 
+static int
+zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll,
+				      void *dpll_priv,
+				      enum dpll_feature_state *state,
+				      struct netlink_ext_ack *extack)
+{
+	struct zl3073x_dpll *zldpll = dpll_priv;
+
+	if (zldpll->phase_monitor)
+		*state = DPLL_FEATURE_STATE_ENABLE;
+	else
+		*state = DPLL_FEATURE_STATE_DISABLE;
+
+	return 0;
+}
+
+static int
+zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll,
+				      void *dpll_priv,
+				      enum dpll_feature_state state,
+				      struct netlink_ext_ack *extack)
+{
+	struct zl3073x_dpll *zldpll = dpll_priv;
+
+	zldpll->phase_monitor = (state == DPLL_FEATURE_STATE_ENABLE);
+
+	return 0;
+}
+
 static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
 	.direction_get = zl3073x_dpll_pin_direction_get,
 	.esync_get = zl3073x_dpll_input_pin_esync_get,
@@ -1361,6 +1423,8 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
 static const struct dpll_device_ops zl3073x_dpll_device_ops = {
 	.lock_status_get = zl3073x_dpll_lock_status_get,
 	.mode_get = zl3073x_dpll_mode_get,
+	.phase_offset_monitor_get = zl3073x_dpll_phase_offset_monitor_get,
+	.phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set,
 };
 
 /**
@@ -1726,16 +1790,47 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
 {
 	struct zl3073x_dpll *zldpll = pin->dpll;
 	struct zl3073x_dev *zldev = zldpll->dev;
+	unsigned int reg;
 	s64 phase_offset;
+	u8 ref;
 	int rc;
 
-	/* Do not check phase offset if the pin is not connected one */
-	if (pin->pin_state != DPLL_PIN_STATE_CONNECTED)
+	ref = zl3073x_input_pin_ref_get(pin->id);
+
+	/* Select register to read phase offset value depending on pin and
+	 * phase monitor state:
+	 * 1) For connected pin use dpll_phase_err_data register
+	 * 2) For other pins use appropriate ref_phase register if the phase
+	 *    monitor feature is enabled and reference monitor does not
+	 *    report signal errors for given input pin
+	 */
+	if (pin->pin_state == DPLL_PIN_STATE_CONNECTED) {
+		reg = ZL_REG_DPLL_PHASE_ERR_DATA(zldpll->id);
+	} else if (zldpll->phase_monitor) {
+		u8 status;
+
+		/* Get reference monitor status */
+		rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref),
+				     &status);
+		if (rc) {
+			dev_err(zldev->dev,
+				"Failed to read %s refmon status: %pe\n",
+				pin->label, ERR_PTR(rc));
+
+			return false;
+		}
+
+		if (status != ZL_REF_MON_STATUS_OK)
+			return false;
+
+		reg = ZL_REG_REF_PHASE(ref);
+	} else {
+		/* The pin is not connected or phase monitor disabled */
 		return false;
+	}
 
-	/* Read DPLL-to-connected-ref phase offset measurement value */
-	rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_PHASE_ERR_DATA(zldpll->id),
-			      &phase_offset);
+	/* Read measured phase offset value */
+	rc = zl3073x_read_u48(zldev, reg, &phase_offset);
 	if (rc) {
 		dev_err(zldev->dev, "Failed to read ref phase offset: %pe\n",
 			ERR_PTR(rc));
@@ -1800,6 +1895,19 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
 	    zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_REFLOCK)
 		return;
 
+	/* Update phase offset latch registers for this DPLL if the phase
+	 * offset monitor feature is enabled.
+	 */
+	if (zldpll->phase_monitor) {
+		rc = zl3073x_ref_phase_offsets_update(zldev, zldpll->id);
+		if (rc) {
+			dev_err(zldev->dev,
+				"Failed to update phase offsets: %pe\n",
+				ERR_PTR(rc));
+			return;
+		}
+	}
+
 	list_for_each_entry(pin, &zldpll->pins, list) {
 		enum dpll_pin_state state;
 		bool pin_changed = false;
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index 2e84e56f8c9e1..304910ffc9c07 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -16,6 +16,7 @@
  * @refsel_mode: reference selection mode
  * @forced_ref: selected reference in forced reference lock mode
  * @check_count: periodic check counter
+ * @phase_monitor: is phase offset monitor enabled
  * @dpll_dev: pointer to registered DPLL device
  * @lock_status: last saved DPLL lock status
  * @pins: list of pins
@@ -27,6 +28,7 @@ struct zl3073x_dpll {
 	u8				refsel_mode;
 	u8				forced_ref;
 	u8				check_count;
+	bool				phase_monitor;
 	struct dpll_device		*dpll_dev;
 	enum dpll_lock_status		lock_status;
 	struct list_head		pins;
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index 8dde92e623f76..9ee2f44a2eec7 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -101,6 +101,9 @@
 #define ZL_REG_REF_PHASE_ERR_READ_RQST		ZL_REG(4, 0x0f, 1)
 #define ZL_REF_PHASE_ERR_READ_RQST_RD		BIT(0)
 
+#define ZL_REG_REF_PHASE(_idx)						\
+	ZL_REG_IDX(_idx, 4, 0x20, 6, ZL3073X_NUM_REFS, 6)
+
 /***********************
  * Register Page 5, DPLL
  ***********************/
@@ -119,6 +122,9 @@
 #define ZL_DPLL_MEAS_CTRL_EN			BIT(0)
 #define ZL_DPLL_MEAS_CTRL_AVG_FACTOR		GENMASK(7, 4)
 
+#define ZL_REG_DPLL_MEAS_IDX			ZL_REG(5, 0x51, 1)
+#define ZL_DPLL_MEAS_IDX			GENMASK(2, 0)
+
 #define ZL_REG_DPLL_PHASE_ERR_READ_MASK		ZL_REG(5, 0x54, 1)
 
 #define ZL_REG_DPLL_PHASE_ERR_DATA(_idx)				\
-- 
2.49.0


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

* [PATCH net-next 4/5] dpll: zl3073x: Add support to adjust phase
  2025-07-10 15:38 [PATCH net-next 0/5] dpll: zl3073x: Add misc features Ivan Vecera
                   ` (2 preceding siblings ...)
  2025-07-10 15:38 ` [PATCH net-next 3/5] dpll: zl3073x: Implement phase offset monitor feature Ivan Vecera
@ 2025-07-10 15:38 ` Ivan Vecera
  2025-07-14 14:15   ` Prathosh.Satish
  2025-07-15 13:12   ` Paolo Abeni
  2025-07-10 15:38 ` [PATCH net-next 5/5] dpll: zl3073x: Add support to get fractional frequency offset Ivan Vecera
                   ` (2 subsequent siblings)
  6 siblings, 2 replies; 18+ messages in thread
From: Ivan Vecera @ 2025-07-10 15:38 UTC (permalink / raw)
  To: netdev
  Cc: Prathosh Satish, Arkadiusz Kubalewski, Jiri Pirko,
	David S. Miller, Jakub Kicinski, Paolo Abeni, linux-kernel,
	Michal Schmidt, Petr Oros

Add 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.

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/zl3073x/dpll.c | 184 ++++++++++++++++++++++++++++++++++++
 drivers/dpll/zl3073x/regs.h |   3 +
 2 files changed, 187 insertions(+)

diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 198e19f6fb152..4e05120c30b9a 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -575,6 +575,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->dev;
+	struct zl3073x_dpll_pin *pin = pin_priv;
+	s64 phase_comp;
+	u8 ref;
+	int rc;
+
+	guard(mutex)(&zldev->multiop_lock);
+
+	/* Read reference configuration */
+	ref = zl3073x_input_pin_ref_get(pin->id);
+	rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD,
+			   ZL_REG_REF_MB_MASK, BIT(ref));
+	if (rc)
+		return rc;
+
+	/* Read current phase offset compensation */
+	rc = zl3073x_read_u48(zldev, ZL_REG_REF_PHASE_OFFSET_COMP, &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->dev;
+	struct zl3073x_dpll_pin *pin = pin_priv;
+	s64 phase_comp;
+	u8 ref;
+	int rc;
+
+	/* The value in the register is stored as two's complement negation
+	 * of requested value.
+	 */
+	phase_comp = (s64) -phase_adjust;
+
+	guard(mutex)(&zldev->multiop_lock);
+
+	/* Read reference configuration */
+	ref = zl3073x_input_pin_ref_get(pin->id);
+	rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD,
+			   ZL_REG_REF_MB_MASK, BIT(ref));
+	if (rc)
+		return rc;
+
+	/* Write the requested value into the compensation register */
+	rc = zl3073x_write_u48(zldev, ZL_REG_REF_PHASE_OFFSET_COMP, phase_comp);
+	if (rc)
+		return rc;
+
+	/* Commit reference configuration */
+	return zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR,
+			     ZL_REG_REF_MB_MASK, BIT(ref));
+}
+
 /**
  * zl3073x_dpll_ref_prio_get - get priority for given input pin
  * @pin: pointer to pin
@@ -1278,6 +1357,107 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
 			     ZL_REG_OUTPUT_MB_MASK, BIT(out));
 }
 
+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->dev;
+	struct zl3073x_dpll_pin *pin = pin_priv;
+	u32 synth_freq;
+	s32 phase_comp;
+	u8 out, synth;
+	int rc;
+
+	out = zl3073x_output_pin_out_get(pin->id);
+	synth = zl3073x_out_synth_get(zldev, out);
+	synth_freq = zl3073x_synth_freq_get(zldev, synth);
+
+	guard(mutex)(&zldev->multiop_lock);
+
+	/* Read output configuration */
+	rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD,
+			   ZL_REG_OUTPUT_MB_MASK, BIT(out));
+	if (rc)
+		return rc;
+
+	/* Read current output phase compensation */
+	rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, &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->dev;
+	struct zl3073x_dpll_pin *pin = pin_priv;
+	int half_synth_cycle;
+	u32 synth_freq;
+	u8 out, synth;
+	int rc;
+
+	/* Get attached synth */
+	out = zl3073x_output_pin_out_get(pin->id);
+	synth = zl3073x_out_synth_get(zldev, out);
+
+	/* 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;
+
+	/* The value in the register is stored as two's complement negation
+	 * of requested value.
+	 */
+	phase_adjust = -phase_adjust;
+
+	guard(mutex)(&zldev->multiop_lock);
+
+	/* Read output configuration */
+	rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD,
+			   ZL_REG_OUTPUT_MB_MASK, BIT(out));
+	if (rc)
+		return rc;
+
+	/* Write the requested value into the compensation register */
+	rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, phase_adjust);
+	if (rc)
+		return rc;
+
+	/* Update output configuration from mailbox */
+	return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR,
+			     ZL_REG_OUTPUT_MB_MASK, BIT(out));
+}
+
 static int
 zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
 					  void *pin_priv,
@@ -1405,6 +1585,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,
@@ -1417,6 +1599,8 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
 	.esync_set = zl3073x_dpll_output_pin_esync_set,
 	.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,
 };
 
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index 9ee2f44a2eec7..a382cd4a109f5 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -168,6 +168,8 @@
 #define ZL_REF_CONFIG_ENABLE			BIT(0)
 #define ZL_REF_CONFIG_DIFF_EN			BIT(2)
 
+#define ZL_REG_REF_PHASE_OFFSET_COMP		ZL_REG(10, 0x28, 6)
+
 #define ZL_REG_REF_SYNC_CTRL			ZL_REG(10, 0x2e, 1)
 #define ZL_REF_SYNC_CTRL_MODE			GENMASK(2, 0)
 #define ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF	0
@@ -237,5 +239,6 @@
 #define ZL_REG_OUTPUT_WIDTH			ZL_REG(14, 0x10, 4)
 #define ZL_REG_OUTPUT_ESYNC_PERIOD		ZL_REG(14, 0x14, 4)
 #define ZL_REG_OUTPUT_ESYNC_WIDTH		ZL_REG(14, 0x18, 4)
+#define ZL_REG_OUTPUT_PHASE_COMP		ZL_REG(14, 0x20, 4)
 
 #endif /* _ZL3073X_REGS_H */
-- 
2.49.0


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

* [PATCH net-next 5/5] dpll: zl3073x: Add support to get fractional frequency offset
  2025-07-10 15:38 [PATCH net-next 0/5] dpll: zl3073x: Add misc features Ivan Vecera
                   ` (3 preceding siblings ...)
  2025-07-10 15:38 ` [PATCH net-next 4/5] dpll: zl3073x: Add support to adjust phase Ivan Vecera
@ 2025-07-10 15:38 ` Ivan Vecera
  2025-07-14 10:55 ` [PATCH net-next 0/5] dpll: zl3073x: Add misc features Jiri Pirko
  2025-07-14 14:11 ` Prathosh.Satish
  6 siblings, 0 replies; 18+ messages in thread
From: Ivan Vecera @ 2025-07-10 15:38 UTC (permalink / raw)
  To: netdev
  Cc: Prathosh Satish, Arkadiusz Kubalewski, Jiri Pirko,
	David S. Miller, Jakub Kicinski, Paolo Abeni, linux-kernel,
	Michal Schmidt, Petr Oros

Adds support to get fractional frequency offset for input pins. Implement
the appropriate callback and function that periodicaly performs reference
frequency measurement and notifies DPLL core about changes.

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/zl3073x/core.c | 67 +++++++++++++++++++++++++++++++++++
 drivers/dpll/zl3073x/core.h | 15 ++++++++
 drivers/dpll/zl3073x/dpll.c | 69 +++++++++++++++++++++++++++++++++++--
 drivers/dpll/zl3073x/regs.h | 19 ++++++++++
 4 files changed, 168 insertions(+), 2 deletions(-)

diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index eb62a492b1727..7ebcfc5ec1f09 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -720,6 +720,66 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
 				    ZL_REF_PHASE_ERR_READ_RQST_RD);
 }
 
+/**
+ * zl3073x_ref_ffo_update - update reference fractional frequency offsets
+ * @zldev: pointer to zl3073x_dev structure
+ *
+ * The function asks device to update fractional frequency offsets latch
+ * registers the latest measured values, reads and stores them into
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_ref_ffo_update(struct zl3073x_dev *zldev)
+{
+	int i, rc;
+
+	/* Per datasheet we have to wait for 'ref_freq_meas_ctrl' to be zero
+	 * to ensure that the measured data are coherent.
+	 */
+	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
+				  ZL_REF_FREQ_MEAS_CTRL);
+	if (rc)
+		return rc;
+
+	/* Select all references for measurement */
+	rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_MASK_3_0,
+			      GENMASK(7, 0)); /* REF0P..REF3N */
+	if (rc)
+		return rc;
+	rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_MASK_4,
+			      GENMASK(1, 0)); /* REF4P..REF4N */
+	if (rc)
+		return rc;
+
+	/* Request frequency offset measurement */
+	rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
+			      ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF);
+	if (rc)
+		return rc;
+
+	/* Wait for finish */
+	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
+				  ZL_REF_FREQ_MEAS_CTRL);
+	if (rc)
+		return rc;
+
+	/* Read DPLL-to-REFx frequency offset measurements */
+	for (i = 0; i < ZL3073X_NUM_REFS; i++) {
+		s32 value;
+
+		/* Read value stored in units of 2^-32 signed */
+		rc = zl3073x_read_u32(zldev, ZL_REG_REF_FREQ(i), &value);
+		if (rc)
+			return rc;
+
+		/* Convert to ppm -> ffo = (10^6 * value) / 2^32 */
+		zldev->ref[i].ffo = mul_s64_u64_shr(value, 1000000, 32);
+	}
+
+	return 0;
+}
+
 static void
 zl3073x_dev_periodic_work(struct kthread_work *work)
 {
@@ -734,6 +794,13 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
 		dev_warn(zldev->dev, "Failed to update phase offsets: %pe\n",
 			 ERR_PTR(rc));
 
+	/* Update references' fractional frequency offsets */
+	rc = zl3073x_ref_ffo_update(zldev);
+	if (rc)
+		dev_warn(zldev->dev,
+			 "Failed to update fractional frequency offsets: %pe\n",
+			 ERR_PTR(rc));
+
 	list_for_each_entry(zldpll, &zldev->dplls, list)
 		zl3073x_dpll_changes_check(zldpll);
 
diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
index 1a5edc4975735..71af2c8001109 100644
--- a/drivers/dpll/zl3073x/core.h
+++ b/drivers/dpll/zl3073x/core.h
@@ -30,10 +30,12 @@ struct zl3073x_dpll;
  * struct zl3073x_ref - input reference invariant info
  * @enabled: input reference is enabled or disabled
  * @diff: true if input reference is differential
+ * @ffo: current fractional frequency offset
  */
 struct zl3073x_ref {
 	bool	enabled;
 	bool	diff;
+	s64	ffo;
 };
 
 /**
@@ -170,6 +172,19 @@ zl3073x_output_pin_out_get(u8 id)
 	return id / 2;
 }
 
+/**
+ * zl3073x_ref_ffo_get - get current fractional frequency offset
+ * @zldev: pointer to zl3073x device
+ * @index: input reference index
+ *
+ * Return: the latest measured fractional frequency offset
+ */
+static inline s64
+zl3073x_ref_ffo_get(struct zl3073x_dev *zldev, u8 index)
+{
+	return zldev->ref[index].ffo;
+}
+
 /**
  * zl3073x_ref_is_diff - check if the given input reference is differential
  * @zldev: pointer to zl3073x device
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 4e05120c30b9a..b061a75aad1a7 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -37,6 +37,7 @@
  * @esync_control: embedded sync is controllable
  * @pin_state: last saved pin state
  * @phase_offset: last saved pin phase offset
+ * @freq_offset: last saved fractional frequency offset
  */
 struct zl3073x_dpll_pin {
 	struct list_head	list;
@@ -50,6 +51,7 @@ struct zl3073x_dpll_pin {
 	bool			esync_control;
 	enum dpll_pin_state	pin_state;
 	s64			phase_offset;
+	s64			freq_offset;
 };
 
 /*
@@ -270,6 +272,18 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin,
 			     ZL_REG_REF_MB_MASK, BIT(ref));
 }
 
+static int
+zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
+			       const struct dpll_device *dpll, void *dpll_priv,
+			       s64 *ffo, struct netlink_ext_ack *extack)
+{
+	struct zl3073x_dpll_pin *pin = pin_priv;
+
+	*ffo = pin->freq_offset;
+
+	return 0;
+}
+
 static int
 zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin,
 				     void *pin_priv,
@@ -1582,6 +1596,7 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
 	.direction_get = zl3073x_dpll_pin_direction_get,
 	.esync_get = zl3073x_dpll_input_pin_esync_get,
 	.esync_set = zl3073x_dpll_input_pin_esync_set,
+	.ffo_get = zl3073x_dpll_input_pin_ffo_get,
 	.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,
@@ -2037,6 +2052,52 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
 	return false;
 }
 
+/**
+ * zl3073x_dpll_pin_ffo_check - check for pin fractional frequency offset change
+ * @pin: pin to check
+ *
+ * Check for the given pin's fractional frequency change.
+ *
+ * Return: true on fractional frequency offset change, false otherwise
+ */
+static bool
+zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
+{
+	struct zl3073x_dpll *zldpll = pin->dpll;
+	struct zl3073x_dev *zldev = zldpll->dev;
+	u8 ref, status;
+	s64 ffo;
+	int rc;
+
+	/* Get reference monitor status */
+	ref = zl3073x_input_pin_ref_get(pin->id);
+	rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref), &status);
+	if (rc) {
+		dev_err(zldev->dev, "Failed to read %s refmon status: %pe\n",
+			pin->label, ERR_PTR(rc));
+
+		return false;
+	}
+
+	/* Do not report ffo changes if the reference monitor report errors */
+	if (status != ZL_REF_MON_STATUS_OK)
+		return false;
+
+	/* Get the latest measured ref's ffo */
+	ffo = zl3073x_ref_ffo_get(zldev, ref);
+
+	/* Compare with previous value */
+	if (pin->freq_offset != ffo) {
+		dev_dbg(zldev->dev, "%s freq offset changed: %lld -> %lld\n",
+			pin->label, pin->freq_offset, ffo);
+		pin->freq_offset = ffo;
+
+		return true;
+	}
+
+	return false;
+}
+
 /**
  * zl3073x_dpll_changes_check - check for changes and send notifications
  * @zldpll: pointer to zl3073x_dpll structure
@@ -2117,11 +2178,15 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
 			pin_changed = true;
 		}
 
-		/* Check for phase offset change once per second */
-		if (zldpll->check_count % 2 == 0)
+		/* Check for phase offset and ffo change once per second */
+		if (zldpll->check_count % 2 == 0) {
 			if (zl3073x_dpll_pin_phase_offset_check(pin))
 				pin_changed = true;
 
+			if (zl3073x_dpll_pin_ffo_check(pin))
+				pin_changed = true;
+		}
+
 		if (pin_changed)
 			dpll_pin_change_ntf(pin->dpll_pin);
 	}
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index a382cd4a109f5..614e33128a5c9 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -94,6 +94,9 @@
 #define ZL_DPLL_REFSEL_STATUS_STATE		GENMASK(6, 4)
 #define ZL_DPLL_REFSEL_STATUS_STATE_LOCK	4
 
+#define ZL_REG_REF_FREQ(_idx)						\
+	ZL_REG_IDX(_idx, 2, 0x44, 4, ZL3073X_NUM_REFS, 4)
+
 /**********************
  * Register Page 4, Ref
  **********************/
@@ -101,6 +104,22 @@
 #define ZL_REG_REF_PHASE_ERR_READ_RQST		ZL_REG(4, 0x0f, 1)
 #define ZL_REF_PHASE_ERR_READ_RQST_RD		BIT(0)
 
+#define ZL_REG_REF_FREQ_MEAS_CTRL		ZL_REG(4, 0x1c, 1)
+#define ZL_REF_FREQ_MEAS_CTRL			GENMASK(1, 0)
+#define ZL_REF_FREQ_MEAS_CTRL_REF_FREQ		1
+#define ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF	2
+#define ZL_REF_FREQ_MEAS_CTRL_DPLL_FREQ_OFF	3
+
+#define ZL_REG_REF_FREQ_MEAS_MASK_3_0		ZL_REG(4, 0x1d, 1)
+#define ZL_REF_FREQ_MEAS_MASK_3_0(_ref)		BIT(_ref)
+
+#define ZL_REG_REF_FREQ_MEAS_MASK_4		ZL_REG(4, 0x1e, 1)
+#define ZL_REF_FREQ_MEAS_MASK_4(_ref)		BIT((_ref) - 8)
+
+#define ZL_REG_DPLL_MEAS_REF_FREQ_CTRL		ZL_REG(4, 0x1f, 1)
+#define ZL_DPLL_MEAS_REF_FREQ_CTRL_EN		BIT(0)
+#define ZL_DPLL_MEAS_REF_FREQ_CTRL_IDX		GENMASK(6, 4)
+
 #define ZL_REG_REF_PHASE(_idx)						\
 	ZL_REG_IDX(_idx, 4, 0x20, 6, ZL3073X_NUM_REFS, 6)
 
-- 
2.49.0


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

* Re: [PATCH net-next 0/5] dpll: zl3073x: Add misc features
  2025-07-10 15:38 [PATCH net-next 0/5] dpll: zl3073x: Add misc features Ivan Vecera
                   ` (4 preceding siblings ...)
  2025-07-10 15:38 ` [PATCH net-next 5/5] dpll: zl3073x: Add support to get fractional frequency offset Ivan Vecera
@ 2025-07-14 10:55 ` Jiri Pirko
  2025-07-14 14:11 ` Prathosh.Satish
  6 siblings, 0 replies; 18+ messages in thread
From: Jiri Pirko @ 2025-07-14 10:55 UTC (permalink / raw)
  To: Ivan Vecera
  Cc: netdev, Arkadiusz Kubalewski, Prathosh Satish, David S. Miller,
	Jakub Kicinski, Paolo Abeni, linux-kernel, Michal Schmidt,
	Petr Oros

Thu, Jul 10, 2025 at 05:38:43PM +0200, ivecera@redhat.com wrote:
>Add several new features missing in initial submission:
>
>* Embedded sync for both pin types
>* Phase offset reporting for connected input pin
>* Selectable phase offset monitoring (aka all inputs phase monitor)
>* Phase adjustments for both pin types
>* Fractional frequency offset reporting for input pins
>
>Everything was tested on Microchip EVB-LAN9668 EDS2 development board.
>
>Ivan Vecera (5):
>  dpll: zl3073x: Add support to get/set esync on pins
>  dpll: zl3073x: Add support to get phase offset on connected input pin
>  dpll: zl3073x: Implement phase offset monitor feature
>  dpll: zl3073x: Add support to adjust phase
>  dpll: zl3073x: Add support to get fractional frequency offset

dpll-side wise:
Reviewed-by: Jiri Pirko <jiri@nvidia.com>

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

* Re: [PATCH net-next 0/5] dpll: zl3073x: Add misc features
  2025-07-10 15:38 [PATCH net-next 0/5] dpll: zl3073x: Add misc features Ivan Vecera
                   ` (5 preceding siblings ...)
  2025-07-14 10:55 ` [PATCH net-next 0/5] dpll: zl3073x: Add misc features Jiri Pirko
@ 2025-07-14 14:11 ` Prathosh.Satish
  6 siblings, 0 replies; 18+ messages in thread
From: Prathosh.Satish @ 2025-07-14 14:11 UTC (permalink / raw)
  To: ivecera, netdev
  Cc: arkadiusz.kubalewski, jiri, davem, kuba, pabeni, linux-kernel,
	mschmidt, poros

On 10/07/2025 16:38, Ivan Vecera wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>
> Add several new features missing in initial submission:
>
> * Embedded sync for both pin types
> * Phase offset reporting for connected input pin
> * Selectable phase offset monitoring (aka all inputs phase monitor)
> * Phase adjustments for both pin types
> * Fractional frequency offset reporting for input pins
>
> Everything was tested on Microchip EVB-LAN9668 EDS2 development board.
>
> Ivan Vecera (5):
>    dpll: zl3073x: Add support to get/set esync on pins
>    dpll: zl3073x: Add support to get phase offset on connected input pin
>    dpll: zl3073x: Implement phase offset monitor feature
>    dpll: zl3073x: Add support to adjust phase
>    dpll: zl3073x: Add support to get fractional frequency offset
>
>   drivers/dpll/zl3073x/core.c | 171 ++++++++
>   drivers/dpll/zl3073x/core.h |  16 +
>   drivers/dpll/zl3073x/dpll.c | 805 +++++++++++++++++++++++++++++++++++-
>   drivers/dpll/zl3073x/dpll.h |   4 +
>   drivers/dpll/zl3073x/regs.h |  55 +++
>   5 files changed, 1049 insertions(+), 2 deletions(-)
>
> --
> 2.49.0
>
Tested-by: Prathosh Satish <prathosh.satish@microchip.com>


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

* Re: [PATCH net-next 1/5] dpll: zl3073x: Add support to get/set esync on pins
  2025-07-10 15:38 ` [PATCH net-next 1/5] dpll: zl3073x: Add support to get/set esync on pins Ivan Vecera
@ 2025-07-14 14:13   ` Prathosh.Satish
  2025-07-15 12:52   ` Paolo Abeni
  1 sibling, 0 replies; 18+ messages in thread
From: Prathosh.Satish @ 2025-07-14 14:13 UTC (permalink / raw)
  To: ivecera, netdev
  Cc: arkadiusz.kubalewski, jiri, davem, kuba, pabeni, linux-kernel,
	mschmidt, poros

On 10/07/2025 16:38, Ivan Vecera wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>
> Add support to get/set embedded sync for both input and output pins.
> The DPLL is able to lock on input reference when the embedded sync
> frequency is 1 PPS and pulse width 25%. The esync on outputs are more
> versatille and theoretically supports any esync frequency that divides
> current output frequency but for now support the same that supported on
> input pins (1 PPS & 25% pulse).
>
> Note that for the output pins the esync divisor shares the same register
> used for N-divided signal formats. Due to this the esync cannot be
> enabled on outputs configured with one of the N-divided signal formats.
>
> 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/zl3073x/dpll.c | 343 +++++++++++++++++++++++++++++++++++-
>   drivers/dpll/zl3073x/regs.h |  11 ++
>   2 files changed, 353 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
> index cb0f1a43c5fbd..2af20532b1ca0 100644
> --- a/drivers/dpll/zl3073x/dpll.c
> +++ b/drivers/dpll/zl3073x/dpll.c
> @@ -34,6 +34,7 @@
>    * @id: pin id
>    * @prio: pin priority <0, 14>
>    * @selectable: pin is selectable in automatic mode
> + * @esync_control: embedded sync is controllable
>    * @pin_state: last saved pin state
>    */
>   struct zl3073x_dpll_pin {
> @@ -45,9 +46,17 @@ struct zl3073x_dpll_pin {
>          u8                      id;
>          u8                      prio;
>          bool                    selectable;
> +       bool                    esync_control;
>          enum dpll_pin_state     pin_state;
>   };
>
> +/*
> + * Supported esync ranges for input and for output per output pair type
> + */
> +static const struct dpll_pin_frequency esync_freq_ranges[] = {
> +       DPLL_PIN_FREQUENCY_RANGE(0, 1),
> +};
> +
>   /**
>    * zl3073x_dpll_is_input_pin - check if the pin is input one
>    * @pin: pin to check
> @@ -139,6 +148,126 @@ zl3073x_dpll_input_ref_frequency_get(struct zl3073x_dpll *zldpll, u8 ref_id,
>          return rc;
>   }
>
> +static int
> +zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin,
> +                                void *pin_priv,
> +                                const struct dpll_device *dpll,
> +                                void *dpll_priv,
> +                                struct dpll_pin_esync *esync,
> +                                struct netlink_ext_ack *extack)
> +{
> +       struct zl3073x_dpll *zldpll = dpll_priv;
> +       struct zl3073x_dev *zldev = zldpll->dev;
> +       struct zl3073x_dpll_pin *pin = pin_priv;
> +       u8 ref, ref_sync_ctrl, sync_mode;
> +       u32 esync_div, ref_freq;
> +       int rc;
> +
> +       /* Get reference frequency */
> +       ref = zl3073x_input_pin_ref_get(pin->id);
> +       rc = zl3073x_dpll_input_ref_frequency_get(zldpll, pin->id, &ref_freq);
> +       if (rc)
> +               return rc;
> +
> +       guard(mutex)(&zldev->multiop_lock);
> +
> +       /* Read reference configuration into mailbox */
> +       rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD,
> +                          ZL_REG_REF_MB_MASK, BIT(ref));
> +       if (rc)
> +               return rc;
> +
> +       /* Get ref sync mode */
> +       rc = zl3073x_read_u8(zldev, ZL_REG_REF_SYNC_CTRL, &ref_sync_ctrl);
> +       if (rc)
> +               return rc;
> +
> +       /* Get esync divisor */
> +       rc = zl3073x_read_u32(zldev, ZL_REG_REF_ESYNC_DIV, &esync_div);
> +       if (rc)
> +               return rc;
> +
> +       sync_mode = FIELD_GET(ZL_REF_SYNC_CTRL_MODE, ref_sync_ctrl);
> +
> +       switch (sync_mode) {
> +       case ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75:
> +               esync->freq = (esync_div == ZL_REF_ESYNC_DIV_1HZ) ? 1 : 0;
> +               esync->pulse = 25;
> +               break;
> +       default:
> +               esync->freq = 0;
> +               esync->pulse = 0;
> +               break;
> +       }
> +
> +       /* If the pin supports esync control expose its range but only
> +        * if the current reference frequency is > 1 Hz.
> +        */
> +       if (pin->esync_control && ref_freq > 1) {
> +               esync->range = esync_freq_ranges;
> +               esync->range_num = ARRAY_SIZE(esync_freq_ranges);
> +       } else {
> +               esync->range = NULL;
> +               esync->range_num = 0;
> +       }
> +
> +       return rc;
> +}
> +
> +static int
> +zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin,
> +                                void *pin_priv,
> +                                const struct dpll_device *dpll,
> +                                void *dpll_priv, u64 freq,
> +                                struct netlink_ext_ack *extack)
> +{
> +       struct zl3073x_dpll *zldpll = dpll_priv;
> +       struct zl3073x_dev *zldev = zldpll->dev;
> +       struct zl3073x_dpll_pin *pin = pin_priv;
> +       u8 ref, ref_sync_ctrl, sync_mode;
> +       int rc;
> +
> +       guard(mutex)(&zldev->multiop_lock);
> +
> +       /* Read reference configuration into mailbox */
> +       ref = zl3073x_input_pin_ref_get(pin->id);
> +       rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD,
> +                          ZL_REG_REF_MB_MASK, BIT(ref));
> +       if (rc)
> +               return rc;
> +
> +       /* Get ref sync mode */
> +       rc = zl3073x_read_u8(zldev, ZL_REG_REF_SYNC_CTRL, &ref_sync_ctrl);
> +       if (rc)
> +               return rc;
> +
> +       /* Use freq == 0 to disable esync */
> +       if (!freq)
> +               sync_mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF;
> +       else
> +               sync_mode = ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75;
> +
> +       ref_sync_ctrl &= ~ZL_REF_SYNC_CTRL_MODE;
> +       ref_sync_ctrl |= FIELD_PREP(ZL_REF_SYNC_CTRL_MODE, sync_mode);
> +
> +       /* Update ref sync control register */
> +       rc = zl3073x_write_u8(zldev, ZL_REG_REF_SYNC_CTRL, ref_sync_ctrl);
> +       if (rc)
> +               return rc;
> +
> +       if (freq) {
> +               /* 1 Hz is only supported frequnecy currently */
> +               rc = zl3073x_write_u32(zldev, ZL_REG_REF_ESYNC_DIV,
> +                                      ZL_REF_ESYNC_DIV_1HZ);
> +               if (rc)
> +                       return rc;
> +       }
> +
> +       /* Commit reference configuration */
> +       return zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR,
> +                            ZL_REG_REF_MB_MASK, BIT(ref));
> +}
> +
>   static int
>   zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin,
>                                       void *pin_priv,
> @@ -640,6 +769,213 @@ zl3073x_dpll_input_pin_prio_set(const struct dpll_pin *dpll_pin, void *pin_priv,
>          return 0;
>   }
>
> +static int
> +zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin,
> +                                 void *pin_priv,
> +                                 const struct dpll_device *dpll,
> +                                 void *dpll_priv,
> +                                 struct dpll_pin_esync *esync,
> +                                 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 device *dev = zldev->dev;
> +       u32 esync_period, esync_width;
> +       u8 clock_type, synth;
> +       u8 out, output_mode;
> +       u32 output_div;
> +       u32 synth_freq;
> +       int rc;
> +
> +       out = zl3073x_output_pin_out_get(pin->id);
> +
> +       /* If N-division is enabled, esync is not supported. The register used
> +        * for N-division is also used for the esync divider so both cannot
> +        * be used.
> +        */
> +       switch (zl3073x_out_signal_format_get(zldev, out)) {
> +       case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV:
> +       case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV:
> +               return -EOPNOTSUPP;
> +       default:
> +               break;
> +       }
> +
> +       guard(mutex)(&zldev->multiop_lock);
> +
> +       /* Read output configuration into mailbox */
> +       rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD,
> +                          ZL_REG_OUTPUT_MB_MASK, BIT(out));
> +       if (rc)
> +               return rc;
> +
> +       /* Read output mode */
> +       rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode);
> +       if (rc)
> +               return rc;
> +
> +       /* Read output divisor */
> +       rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div);
> +       if (rc)
> +               return rc;
> +
> +       /* Check output divisor for zero */
> +       if (!output_div) {
> +               dev_err(dev, "Zero divisor for OUTPUT%u got from device\n",
> +                       out);
> +               return -EINVAL;
> +       }
> +
> +       /* Get synth attached to output pin */
> +       synth = zl3073x_out_synth_get(zldev, out);
> +
> +       /* Get synth frequency */
> +       synth_freq = zl3073x_synth_freq_get(zldev, synth);
> +
> +       clock_type = FIELD_GET(ZL_OUTPUT_MODE_CLOCK_TYPE, output_mode);
> +       if (clock_type != ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC) {
> +               /* No need to read esync data if it is not enabled */
> +               esync->freq = 0;
> +               esync->pulse = 0;
> +
> +               goto finish;
> +       }
> +
> +       /* Read esync period */
> +       rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, &esync_period);
> +       if (rc)
> +               return rc;
> +
> +       /* Check esync divisor for zero */
> +       if (!esync_period) {
> +               dev_err(dev, "Zero esync divisor for OUTPUT%u got from device\n",
> +                       out);
> +               return -EINVAL;
> +       }
> +
> +       /* Get esync pulse width in units of half synth cycles */
> +       rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, &esync_width);
> +       if (rc)
> +               return rc;
> +
> +       /* Compute esync frequency */
> +       esync->freq = synth_freq / output_div / esync_period;
> +
> +       /* By comparing the esync_pulse_width to the half of the pulse width
> +        * the esync pulse percentage can be determined.
> +        * Note that half pulse width is in units of half synth cycles, which
> +        * is why it reduces down to be output_div.
> +        */
> +       esync->pulse = (50 * esync_width) / output_div;
> +
> +finish:
> +       /* Set supported esync ranges if the pin supports esync control and
> +        * if the output frequency is > 1 Hz.
> +        */
> +       if (pin->esync_control && (synth_freq / output_div) > 1) {
> +               esync->range = esync_freq_ranges;
> +               esync->range_num = ARRAY_SIZE(esync_freq_ranges);
> +       } else {
> +               esync->range = NULL;
> +               esync->range_num = 0;
> +       }
> +
> +       return 0;
> +}
> +
> +static int
> +zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
> +                                 void *pin_priv,
> +                                 const struct dpll_device *dpll,
> +                                 void *dpll_priv, u64 freq,
> +                                 struct netlink_ext_ack *extack)
> +{
> +       struct zl3073x_dpll *zldpll = dpll_priv;
> +       struct zl3073x_dev *zldev = zldpll->dev;
> +       struct zl3073x_dpll_pin *pin = pin_priv;
> +       u32 esync_period, esync_width, output_div;
> +       u8 clock_type, out, output_mode, synth;
> +       u32 synth_freq;
> +       int rc;
> +
> +       out = zl3073x_output_pin_out_get(pin->id);
> +
> +       /* If N-division is enabled, esync is not supported. The register used
> +        * for N-division is also used for the esync divider so both cannot
> +        * be used.
> +        */
> +       switch (zl3073x_out_signal_format_get(zldev, out)) {
> +       case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV:
> +       case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV:
> +               return -EOPNOTSUPP;
> +       default:
> +               break;
> +       }
> +
> +       guard(mutex)(&zldev->multiop_lock);
> +
> +       /* Read output configuration into mailbox */
> +       rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD,
> +                          ZL_REG_OUTPUT_MB_MASK, BIT(out));
> +       if (rc)
> +               return rc;
> +
> +       /* Read output mode */
> +       rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode);
> +       if (rc)
> +               return rc;
> +
> +       /* Select clock type */
> +       if (freq)
> +               clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC;
> +       else
> +               clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL;
> +
> +       /* Update clock type in output mode */
> +       output_mode &= ~ZL_OUTPUT_MODE_CLOCK_TYPE;
> +       output_mode |= FIELD_PREP(ZL_OUTPUT_MODE_CLOCK_TYPE, clock_type);
> +       rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_MODE, output_mode);
> +       if (rc)
> +               return rc;
> +
> +       /* If esync is being disabled just write mailbox and finish */
> +       if (!freq)
> +               goto write_mailbox;
> +
> +       /* Get synth attached to output pin */
> +       synth = zl3073x_out_synth_get(zldev, out);
> +
> +       /* Get synth frequency */
> +       synth_freq = zl3073x_synth_freq_get(zldev, synth);
> +
> +       rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div);
> +       if (rc)
> +               return rc;
> +
> +       /* Compute and update esync period */
> +       esync_period = synth_freq / (u32)freq / output_div;
> +       rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, esync_period);
> +       if (rc)
> +               return rc;
> +
> +       /* 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.
> +        */
> +       esync_width = output_div / 2;
> +       rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, esync_width);
> +       if (rc)
> +               return rc;
> +
> +write_mailbox:
> +       /* Commit output configuration */
> +       return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR,
> +                            ZL_REG_OUTPUT_MB_MASK, BIT(out));
> +}
> +
>   static int
>   zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin,
>                                        void *pin_priv,
> @@ -956,6 +1292,8 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
>
>   static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
>          .direction_get = zl3073x_dpll_pin_direction_get,
> +       .esync_get = zl3073x_dpll_input_pin_esync_get,
> +       .esync_set = zl3073x_dpll_input_pin_esync_set,
>          .frequency_get = zl3073x_dpll_input_pin_frequency_get,
>          .frequency_set = zl3073x_dpll_input_pin_frequency_set,
>          .prio_get = zl3073x_dpll_input_pin_prio_get,
> @@ -966,6 +1304,8 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
>
>   static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
>          .direction_get = zl3073x_dpll_pin_direction_get,
> +       .esync_get = zl3073x_dpll_output_pin_esync_get,
> +       .esync_set = zl3073x_dpll_output_pin_esync_set,
>          .frequency_get = zl3073x_dpll_output_pin_frequency_get,
>          .frequency_set = zl3073x_dpll_output_pin_frequency_set,
>          .state_on_dpll_get = zl3073x_dpll_output_pin_state_on_dpll_get,
> @@ -1040,8 +1380,9 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
>          if (IS_ERR(props))
>                  return PTR_ERR(props);
>
> -       /* Save package label */
> +       /* Save package label & esync capability */
>          strscpy(pin->label, props->package_label);
> +       pin->esync_control = props->esync_control;
>
>          if (zl3073x_dpll_is_input_pin(pin)) {
>                  rc = zl3073x_dpll_ref_prio_get(pin, &pin->prio);
> diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
> index 493c63e729208..64bb43bbc3168 100644
> --- a/drivers/dpll/zl3073x/regs.h
> +++ b/drivers/dpll/zl3073x/regs.h
> @@ -146,6 +146,14 @@
>   #define ZL_REF_CONFIG_ENABLE                   BIT(0)
>   #define ZL_REF_CONFIG_DIFF_EN                  BIT(2)
>
> +#define ZL_REG_REF_SYNC_CTRL                   ZL_REG(10, 0x2e, 1)
> +#define ZL_REF_SYNC_CTRL_MODE                  GENMASK(2, 0)
> +#define ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF 0
> +#define ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75        2
> +
> +#define ZL_REG_REF_ESYNC_DIV                   ZL_REG(10, 0x30, 4)
> +#define ZL_REF_ESYNC_DIV_1HZ                   0
> +
>   /********************************
>    * Register Page 12, DPLL Mailbox
>    ********************************/
> @@ -188,6 +196,9 @@
>   #define ZL_OUTPUT_MB_SEM_RD                    BIT(1)
>
>   #define ZL_REG_OUTPUT_MODE                     ZL_REG(14, 0x05, 1)
> +#define ZL_OUTPUT_MODE_CLOCK_TYPE              GENMASK(2, 0)
> +#define ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL       0
> +#define ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC                1
>   #define ZL_OUTPUT_MODE_SIGNAL_FORMAT           GENMASK(7, 4)
>   #define ZL_OUTPUT_MODE_SIGNAL_FORMAT_DISABLED  0
>   #define ZL_OUTPUT_MODE_SIGNAL_FORMAT_LVDS      1
> --
> 2.49.0
>
Tested-by: Prathosh Satish <prathosh.satish@microchip.com>


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

* Re: [PATCH net-next 2/5] dpll: zl3073x: Add support to get phase offset on connected input pin
  2025-07-10 15:38 ` [PATCH net-next 2/5] dpll: zl3073x: Add support to get phase offset on connected input pin Ivan Vecera
@ 2025-07-14 14:14   ` Prathosh.Satish
  0 siblings, 0 replies; 18+ messages in thread
From: Prathosh.Satish @ 2025-07-14 14:14 UTC (permalink / raw)
  To: ivecera, netdev
  Cc: arkadiusz.kubalewski, jiri, davem, kuba, pabeni, linux-kernel,
	mschmidt, poros

On 10/07/2025 16:38, Ivan Vecera wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>
> Add support to get phase offset for the connected input pin. Implement
> the appropriate callback and function that performs DPLL to connected
> reference phase error measurement and notifies DPLL core about changes.
>
> The measurement is performed internally by device on background 40 times
> per second but the measured value is read each second and compared with
> previous value.
>
> 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/zl3073x/core.c |  86 +++++++++++++++++++++++++++++
>   drivers/dpll/zl3073x/dpll.c | 105 +++++++++++++++++++++++++++++++++++-
>   drivers/dpll/zl3073x/dpll.h |   2 +
>   drivers/dpll/zl3073x/regs.h |  16 ++++++
>   4 files changed, 208 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
> index f2d58e1a56726..c980c85e7ac51 100644
> --- a/drivers/dpll/zl3073x/core.c
> +++ b/drivers/dpll/zl3073x/core.c
> @@ -669,12 +669,52 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev)
>          return rc;
>   }
>
> +/**
> + * zl3073x_ref_phase_offsets_update - update reference phase offsets
> + * @zldev: pointer to zl3073x_dev structure
> + *
> + * Ask device to update phase offsets latch registers with the latest
> + * measured values.
> + *
> + * Return: 0 on success, <0 on error
> + */
> +static int
> +zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev)
> +{
> +       int rc;
> +
> +       /* Per datasheet we have to wait for 'dpll_ref_phase_err_rqst_rd'
> +        * to be zero to ensure that the measured data are coherent.
> +        */
> +       rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
> +                                 ZL_REF_PHASE_ERR_READ_RQST_RD);
> +       if (rc)
> +               return rc;
> +
> +       /* Request to update phase offsets measurement values */
> +       rc = zl3073x_write_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
> +                             ZL_REF_PHASE_ERR_READ_RQST_RD);
> +       if (rc)
> +               return rc;
> +
> +       /* Wait for finish */
> +       return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
> +                                   ZL_REF_PHASE_ERR_READ_RQST_RD);
> +}
> +
>   static void
>   zl3073x_dev_periodic_work(struct kthread_work *work)
>   {
>          struct zl3073x_dev *zldev = container_of(work, struct zl3073x_dev,
>                                                   work.work);
>          struct zl3073x_dpll *zldpll;
> +       int rc;
> +
> +       /* Update DPLL-to-connected-ref phase offsets registers */
> +       rc = zl3073x_ref_phase_offsets_update(zldev);
> +       if (rc)
> +               dev_warn(zldev->dev, "Failed to update phase offsets: %pe\n",
> +                        ERR_PTR(rc));
>
>          list_for_each_entry(zldpll, &zldev->dplls, list)
>                  zl3073x_dpll_changes_check(zldpll);
> @@ -767,6 +807,46 @@ zl3073x_devm_dpll_init(struct zl3073x_dev *zldev, u8 num_dplls)
>          return rc;
>   }
>
> +/**
> + * zl3073x_dev_phase_meas_setup - setup phase offset measurement
> + * @zldev: pointer to zl3073x_dev structure
> + * @num_channels: number of DPLL channels
> + *
> + * Enable phase offset measurement block, set measurement averaging factor
> + * and enable DPLL-to-its-ref phase measurement for all DPLLs.
> + *
> + * Returns: 0 on success, <0 on error
> + */
> +static int
> +zl3073x_dev_phase_meas_setup(struct zl3073x_dev *zldev, int num_channels)
> +{
> +       u8 dpll_meas_ctrl, mask;
> +       int i, rc;
> +
> +       /* Read DPLL phase measurement control register */
> +       rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, &dpll_meas_ctrl);
> +       if (rc)
> +               return rc;
> +
> +       /* Setup phase measurement averaging factor */
> +       dpll_meas_ctrl &= ~ZL_DPLL_MEAS_CTRL_AVG_FACTOR;
> +       dpll_meas_ctrl |= FIELD_PREP(ZL_DPLL_MEAS_CTRL_AVG_FACTOR, 3);
> +
> +       /* Enable DPLL measurement block */
> +       dpll_meas_ctrl |= ZL_DPLL_MEAS_CTRL_EN;
> +
> +       /* Update phase measurement control register */
> +       rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, dpll_meas_ctrl);
> +       if (rc)
> +               return rc;
> +
> +       /* Enable DPLL-to-connected-ref measurement for each channel */
> +       for (i = 0, mask = 0; i < num_channels; i++)
> +               mask |= BIT(i);
> +
> +       return zl3073x_write_u8(zldev, ZL_REG_DPLL_PHASE_ERR_READ_MASK, mask);
> +}
> +
>   /**
>    * zl3073x_dev_probe - initialize zl3073x device
>    * @zldev: pointer to zl3073x device
> @@ -839,6 +919,12 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev,
>          if (rc)
>                  return rc;
>
> +       /* Setup phase offset measurement block */
> +       rc = zl3073x_dev_phase_meas_setup(zldev, chip_info->num_channels);
> +       if (rc)
> +               return dev_err_probe(zldev->dev, rc,
> +                                    "Failed to setup phase measurement\n");
> +
>          /* Register DPLL channels */
>          rc = zl3073x_devm_dpll_init(zldev, chip_info->num_channels);
>          if (rc)
> diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
> index 2af20532b1ca0..1a1d4de5b9de5 100644
> --- a/drivers/dpll/zl3073x/dpll.c
> +++ b/drivers/dpll/zl3073x/dpll.c
> @@ -36,6 +36,7 @@
>    * @selectable: pin is selectable in automatic mode
>    * @esync_control: embedded sync is controllable
>    * @pin_state: last saved pin state
> + * @phase_offset: last saved pin phase offset
>    */
>   struct zl3073x_dpll_pin {
>          struct list_head        list;
> @@ -48,6 +49,7 @@ struct zl3073x_dpll_pin {
>          bool                    selectable;
>          bool                    esync_control;
>          enum dpll_pin_state     pin_state;
> +       s64                     phase_offset;
>   };
>
>   /*
> @@ -496,6 +498,50 @@ zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref)
>          return 0;
>   }
>
> +static int
> +zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
> +                                       void *pin_priv,
> +                                       const struct dpll_device *dpll,
> +                                       void *dpll_priv, s64 *phase_offset,
> +                                       struct netlink_ext_ack *extack)
> +{
> +       struct zl3073x_dpll *zldpll = dpll_priv;
> +       struct zl3073x_dev *zldev = zldpll->dev;
> +       struct zl3073x_dpll_pin *pin = pin_priv;
> +       u8 conn_ref, ref, ref_status;
> +       int rc;
> +
> +       /* Get currently connected reference */
> +       rc = zl3073x_dpll_connected_ref_get(zldpll, &conn_ref);
> +       if (rc)
> +               return rc;
> +
> +       /* Report phase offset only for currently connected pin */
> +       ref = zl3073x_input_pin_ref_get(pin->id);
> +       if (ref != conn_ref) {
> +               *phase_offset = 0;
> +
> +               return 0;
> +       }
> +
> +       /* Get this pin monitor status */
> +       rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref), &ref_status);
> +       if (rc)
> +               return rc;
> +
> +       /* Report phase offset only if the input pin signal is present */
> +       if (ref_status != ZL_REF_MON_STATUS_OK) {
> +               *phase_offset = 0;
> +
> +               return 0;
> +       }
> +
> +       /* Report the latest measured phase offset for the connected ref */
> +       *phase_offset = pin->phase_offset * DPLL_PHASE_OFFSET_DIVIDER;
> +
> +       return rc;
> +}
> +
>   /**
>    * zl3073x_dpll_ref_prio_get - get priority for given input pin
>    * @pin: pointer to pin
> @@ -1296,6 +1342,7 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
>          .esync_set = zl3073x_dpll_input_pin_esync_set,
>          .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,
>          .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,
> @@ -1666,6 +1713,51 @@ zl3073x_dpll_device_unregister(struct zl3073x_dpll *zldpll)
>          zldpll->dpll_dev = NULL;
>   }
>
> +/**
> + * zl3073x_dpll_pin_phase_offset_check - check for pin phase offset change
> + * @pin: pin to check
> + *
> + * Check for the change of DPLL to connected pin phase offset change.
> + *
> + * Return: true on phase offset change, false otherwise
> + */
> +static bool
> +zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
> +{
> +       struct zl3073x_dpll *zldpll = pin->dpll;
> +       struct zl3073x_dev *zldev = zldpll->dev;
> +       s64 phase_offset;
> +       int rc;
> +
> +       /* Do not check phase offset if the pin is not connected one */
> +       if (pin->pin_state != DPLL_PIN_STATE_CONNECTED)
> +               return false;
> +
> +       /* Read DPLL-to-connected-ref phase offset measurement value */
> +       rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_PHASE_ERR_DATA(zldpll->id),
> +                             &phase_offset);
> +       if (rc) {
> +               dev_err(zldev->dev, "Failed to read ref phase offset: %pe\n",
> +                       ERR_PTR(rc));
> +
> +               return false;
> +       }
> +
> +       /* Convert to ps */
> +       phase_offset = div_s64(sign_extend64(phase_offset, 47), 100);
> +
> +       /* Compare with previous value */
> +       if (phase_offset != pin->phase_offset) {
> +               dev_dbg(zldev->dev, "%s phase offset changed: %lld -> %lld\n",
> +                       pin->label, pin->phase_offset, phase_offset);
> +               pin->phase_offset = phase_offset;
> +
> +               return true;
> +       }
> +
> +       return false;
> +}
> +
>   /**
>    * zl3073x_dpll_changes_check - check for changes and send notifications
>    * @zldpll: pointer to zl3073x_dpll structure
> @@ -1684,6 +1776,8 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
>          struct zl3073x_dpll_pin *pin;
>          int rc;
>
> +       zldpll->check_count++;
> +
>          /* Get current lock status for the DPLL */
>          rc = zl3073x_dpll_lock_status_get(zldpll->dpll_dev, zldpll,
>                                            &lock_status, NULL, NULL);
> @@ -1708,6 +1802,7 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
>
>          list_for_each_entry(pin, &zldpll->pins, list) {
>                  enum dpll_pin_state state;
> +               bool pin_changed = false;
>
>                  /* Output pins change checks are not necessary because output
>                   * states are constant.
> @@ -1727,8 +1822,16 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
>                          dev_dbg(dev, "%s state changed: %u->%u\n", pin->label,
>                                  pin->pin_state, state);
>                          pin->pin_state = state;
> -                       dpll_pin_change_ntf(pin->dpll_pin);
> +                       pin_changed = true;
>                  }
> +
> +               /* Check for phase offset change once per second */
> +               if (zldpll->check_count % 2 == 0)
> +                       if (zl3073x_dpll_pin_phase_offset_check(pin))
> +                               pin_changed = true;
> +
> +               if (pin_changed)
> +                       dpll_pin_change_ntf(pin->dpll_pin);
>          }
>   }
>
> diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
> index db7388cc377fd..2e84e56f8c9e1 100644
> --- a/drivers/dpll/zl3073x/dpll.h
> +++ b/drivers/dpll/zl3073x/dpll.h
> @@ -15,6 +15,7 @@
>    * @id: DPLL index
>    * @refsel_mode: reference selection mode
>    * @forced_ref: selected reference in forced reference lock mode
> + * @check_count: periodic check counter
>    * @dpll_dev: pointer to registered DPLL device
>    * @lock_status: last saved DPLL lock status
>    * @pins: list of pins
> @@ -25,6 +26,7 @@ struct zl3073x_dpll {
>          u8                              id;
>          u8                              refsel_mode;
>          u8                              forced_ref;
> +       u8                              check_count;
>          struct dpll_device              *dpll_dev;
>          enum dpll_lock_status           lock_status;
>          struct list_head                pins;
> diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
> index 64bb43bbc3168..8dde92e623f76 100644
> --- a/drivers/dpll/zl3073x/regs.h
> +++ b/drivers/dpll/zl3073x/regs.h
> @@ -94,6 +94,13 @@
>   #define ZL_DPLL_REFSEL_STATUS_STATE            GENMASK(6, 4)
>   #define ZL_DPLL_REFSEL_STATUS_STATE_LOCK       4
>
> +/**********************
> + * Register Page 4, Ref
> + **********************/
> +
> +#define ZL_REG_REF_PHASE_ERR_READ_RQST         ZL_REG(4, 0x0f, 1)
> +#define ZL_REF_PHASE_ERR_READ_RQST_RD          BIT(0)
> +
>   /***********************
>    * Register Page 5, DPLL
>    ***********************/
> @@ -108,6 +115,15 @@
>   #define ZL_DPLL_MODE_REFSEL_MODE_NCO           4
>   #define ZL_DPLL_MODE_REFSEL_REF                        GENMASK(7, 4)
>
> +#define ZL_REG_DPLL_MEAS_CTRL                  ZL_REG(5, 0x50, 1)
> +#define ZL_DPLL_MEAS_CTRL_EN                   BIT(0)
> +#define ZL_DPLL_MEAS_CTRL_AVG_FACTOR           GENMASK(7, 4)
> +
> +#define ZL_REG_DPLL_PHASE_ERR_READ_MASK                ZL_REG(5, 0x54, 1)
> +
> +#define ZL_REG_DPLL_PHASE_ERR_DATA(_idx)                               \
> +       ZL_REG_IDX(_idx, 5, 0x55, 6, ZL3073X_MAX_CHANNELS, 6)
> +
>   /***********************************
>    * Register Page 9, Synth and Output
>    ***********************************/
> --
> 2.49.0
>
Tested-by: Prathosh Satish <prathosh.satish@microchip.com>


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

* Re: [PATCH net-next 3/5] dpll: zl3073x: Implement phase offset monitor feature
  2025-07-10 15:38 ` [PATCH net-next 3/5] dpll: zl3073x: Implement phase offset monitor feature Ivan Vecera
@ 2025-07-14 14:15   ` Prathosh.Satish
  2025-07-15 13:02   ` Paolo Abeni
  1 sibling, 0 replies; 18+ messages in thread
From: Prathosh.Satish @ 2025-07-14 14:15 UTC (permalink / raw)
  To: ivecera, netdev
  Cc: arkadiusz.kubalewski, jiri, davem, kuba, pabeni, linux-kernel,
	mschmidt, poros

On 10/07/2025 16:38, Ivan Vecera wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>
> Implement phase offset monitor feature to allow a user to monitor
> phase offsets across all available inputs.
>
> The device firmware periodically performs phase offsets measurements for
> all available DPLL channels and input references. The driver can ask
> the firmware to fill appropriate latch registers with measured values.
>
> There are 2 sets of latch registers for phase offsets reporting:
>
> 1) DPLL-to-connected-ref: up to 5 registers that contain values for
>     phase offset between particular DPLL channel and its connected input
>     reference.
> 2) selected-DPLL-to-ref: 10  registers that contain values for phase
>     offsets between selected DPLL channel and all available input
>     references.
>
> Both are filled with single read request so the driver can read
> DPLL-to-connected-ref phase offset for all DPLL channels at once.
> This was implemented in the previous patch.
>
> To read selected-DPLL-to-ref registers for all DPLLs a separate read
> request has to be sent to device firmware for each DPLL channel.
>
> To implement phase offset monitor feature:
> * Extend zl3073x_ref_phase_offsets_update() to select given DPLL channel
>    in phase offset read request. The caller can set channel==-1 if it
>    will not read Type2 registers.
> * Use this extended function to update phase offset latch registers
>    during zl3073x_dpll_changes_check() call if phase monitor is enabled
> * Extend zl3073x_dpll_pin_phase_offset_check() to check phase offset
>    changes for all available input references
> * Extend zl3073x_dpll_input_pin_phase_offset_get() to report phase
>    offset values for all available input references
> * Implement phase offset monitor callbacks to enable/disable this
>    feature
>
> 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/zl3073x/core.c |  28 ++++++--
>   drivers/dpll/zl3073x/core.h |   1 +
>   drivers/dpll/zl3073x/dpll.c | 126 +++++++++++++++++++++++++++++++++---
>   drivers/dpll/zl3073x/dpll.h |   2 +
>   drivers/dpll/zl3073x/regs.h |   6 ++
>   5 files changed, 149 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
> index c980c85e7ac51..eb62a492b1727 100644
> --- a/drivers/dpll/zl3073x/core.c
> +++ b/drivers/dpll/zl3073x/core.c
> @@ -672,14 +672,25 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev)
>   /**
>    * zl3073x_ref_phase_offsets_update - update reference phase offsets
>    * @zldev: pointer to zl3073x_dev structure
> + * @channel: DPLL channel number or -1
>    *
> - * Ask device to update phase offsets latch registers with the latest
> - * measured values.
> + * The function asks device to update phase offsets latch registers with
> + * the latest measured values. There are 2 sets of latch registers:
> + *
> + * 1) Up to 5 DPLL-to-connected-ref registers that contain phase offset
> + *    values between particular DPLL channel and its *connected* input
> + *    reference.
> + *
> + * 2) 10 selected-DPLL-to-all-ref registers that contain phase offset values
> + *    between selected DPLL channel and all input references.
> + *
> + * If the caller is interested in 2) then it has to pass DPLL channel number
> + * in @channel parameter. If it is interested only in 1) then it should pass
> + * @channel parameter with value of -1.
>    *
>    * Return: 0 on success, <0 on error
>    */
> -static int
> -zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev)
> +int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
>   {
>          int rc;
>
> @@ -691,6 +702,13 @@ zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev)
>          if (rc)
>                  return rc;
>
> +       /* Select DPLL channel if it is specified */
> +       if (channel != -1) {
> +               rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_IDX, channel);
> +               if (rc)
> +                       return rc;
> +       }
> +
>          /* Request to update phase offsets measurement values */
>          rc = zl3073x_write_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
>                                ZL_REF_PHASE_ERR_READ_RQST_RD);
> @@ -711,7 +729,7 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
>          int rc;
>
>          /* Update DPLL-to-connected-ref phase offsets registers */
> -       rc = zl3073x_ref_phase_offsets_update(zldev);
> +       rc = zl3073x_ref_phase_offsets_update(zldev, -1);
>          if (rc)
>                  dev_warn(zldev->dev, "Failed to update phase offsets: %pe\n",
>                           ERR_PTR(rc));
> diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
> index 97b1032e392d6..1a5edc4975735 100644
> --- a/drivers/dpll/zl3073x/core.h
> +++ b/drivers/dpll/zl3073x/core.h
> @@ -130,6 +130,7 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val);
>    *****************/
>
>   int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult);
> +int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel);
>
>   static inline bool
>   zl3073x_is_n_pin(u8 id)
> diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
> index 1a1d4de5b9de5..198e19f6fb152 100644
> --- a/drivers/dpll/zl3073x/dpll.c
> +++ b/drivers/dpll/zl3073x/dpll.c
> @@ -509,6 +509,7 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
>          struct zl3073x_dev *zldev = zldpll->dev;
>          struct zl3073x_dpll_pin *pin = pin_priv;
>          u8 conn_ref, ref, ref_status;
> +       s64 ref_phase;
>          int rc;
>
>          /* Get currently connected reference */
> @@ -516,9 +517,11 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
>          if (rc)
>                  return rc;
>
> -       /* Report phase offset only for currently connected pin */
> +       /* Report phase offset only for currently connected pin if the phase
> +        * monitor feature is disabled.
> +        */
>          ref = zl3073x_input_pin_ref_get(pin->id);
> -       if (ref != conn_ref) {
> +       if (!zldpll->phase_monitor && ref != conn_ref) {
>                  *phase_offset = 0;
>
>                  return 0;
> @@ -536,8 +539,38 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
>                  return 0;
>          }
>
> -       /* Report the latest measured phase offset for the connected ref */
> -       *phase_offset = pin->phase_offset * DPLL_PHASE_OFFSET_DIVIDER;
> +       ref_phase = pin->phase_offset;
> +
> +       /* The DPLL being locked to a higher freq than the current ref
> +        * the phase offset is modded to the period of the signal
> +        * the dpll is locked to.
> +        */
> +       if (ZL3073X_DPLL_REF_IS_VALID(conn_ref) && conn_ref != ref) {
> +               u32 conn_freq, ref_freq;
> +
> +               /* Get frequency of connected ref */
> +               rc = zl3073x_dpll_input_ref_frequency_get(zldpll, conn_ref,
> +                                                         &conn_freq);
> +               if (rc)
> +                       return rc;
> +
> +               /* Get frequency of given ref */
> +               rc = zl3073x_dpll_input_ref_frequency_get(zldpll, ref,
> +                                                         &ref_freq);
> +               if (rc)
> +                       return rc;
> +
> +               if (conn_freq > ref_freq) {
> +                       s64 conn_period;
> +                       int div_factor;
> +
> +                       conn_period = div_s64(PSEC_PER_SEC, conn_freq);
> +                       div_factor = div64_s64(ref_phase, conn_period);
> +                       ref_phase -= conn_period * div_factor;
> +               }
> +       }
> +
> +       *phase_offset = ref_phase * DPLL_PHASE_OFFSET_DIVIDER;
>
>          return rc;
>   }
> @@ -1336,6 +1369,35 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
>          return 0;
>   }
>
> +static int
> +zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll,
> +                                     void *dpll_priv,
> +                                     enum dpll_feature_state *state,
> +                                     struct netlink_ext_ack *extack)
> +{
> +       struct zl3073x_dpll *zldpll = dpll_priv;
> +
> +       if (zldpll->phase_monitor)
> +               *state = DPLL_FEATURE_STATE_ENABLE;
> +       else
> +               *state = DPLL_FEATURE_STATE_DISABLE;
> +
> +       return 0;
> +}
> +
> +static int
> +zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll,
> +                                     void *dpll_priv,
> +                                     enum dpll_feature_state state,
> +                                     struct netlink_ext_ack *extack)
> +{
> +       struct zl3073x_dpll *zldpll = dpll_priv;
> +
> +       zldpll->phase_monitor = (state == DPLL_FEATURE_STATE_ENABLE);
> +
> +       return 0;
> +}
> +
>   static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
>          .direction_get = zl3073x_dpll_pin_direction_get,
>          .esync_get = zl3073x_dpll_input_pin_esync_get,
> @@ -1361,6 +1423,8 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
>   static const struct dpll_device_ops zl3073x_dpll_device_ops = {
>          .lock_status_get = zl3073x_dpll_lock_status_get,
>          .mode_get = zl3073x_dpll_mode_get,
> +       .phase_offset_monitor_get = zl3073x_dpll_phase_offset_monitor_get,
> +       .phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set,
>   };
>
>   /**
> @@ -1726,16 +1790,47 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
>   {
>          struct zl3073x_dpll *zldpll = pin->dpll;
>          struct zl3073x_dev *zldev = zldpll->dev;
> +       unsigned int reg;
>          s64 phase_offset;
> +       u8 ref;
>          int rc;
>
> -       /* Do not check phase offset if the pin is not connected one */
> -       if (pin->pin_state != DPLL_PIN_STATE_CONNECTED)
> +       ref = zl3073x_input_pin_ref_get(pin->id);
> +
> +       /* Select register to read phase offset value depending on pin and
> +        * phase monitor state:
> +        * 1) For connected pin use dpll_phase_err_data register
> +        * 2) For other pins use appropriate ref_phase register if the phase
> +        *    monitor feature is enabled and reference monitor does not
> +        *    report signal errors for given input pin
> +        */
> +       if (pin->pin_state == DPLL_PIN_STATE_CONNECTED) {
> +               reg = ZL_REG_DPLL_PHASE_ERR_DATA(zldpll->id);
> +       } else if (zldpll->phase_monitor) {
> +               u8 status;
> +
> +               /* Get reference monitor status */
> +               rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref),
> +                                    &status);
> +               if (rc) {
> +                       dev_err(zldev->dev,
> +                               "Failed to read %s refmon status: %pe\n",
> +                               pin->label, ERR_PTR(rc));
> +
> +                       return false;
> +               }
> +
> +               if (status != ZL_REF_MON_STATUS_OK)
> +                       return false;
> +
> +               reg = ZL_REG_REF_PHASE(ref);
> +       } else {
> +               /* The pin is not connected or phase monitor disabled */
>                  return false;
> +       }
>
> -       /* Read DPLL-to-connected-ref phase offset measurement value */
> -       rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_PHASE_ERR_DATA(zldpll->id),
> -                             &phase_offset);
> +       /* Read measured phase offset value */
> +       rc = zl3073x_read_u48(zldev, reg, &phase_offset);
>          if (rc) {
>                  dev_err(zldev->dev, "Failed to read ref phase offset: %pe\n",
>                          ERR_PTR(rc));
> @@ -1800,6 +1895,19 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
>              zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_REFLOCK)
>                  return;
>
> +       /* Update phase offset latch registers for this DPLL if the phase
> +        * offset monitor feature is enabled.
> +        */
> +       if (zldpll->phase_monitor) {
> +               rc = zl3073x_ref_phase_offsets_update(zldev, zldpll->id);
> +               if (rc) {
> +                       dev_err(zldev->dev,
> +                               "Failed to update phase offsets: %pe\n",
> +                               ERR_PTR(rc));
> +                       return;
> +               }
> +       }
> +
>          list_for_each_entry(pin, &zldpll->pins, list) {
>                  enum dpll_pin_state state;
>                  bool pin_changed = false;
> diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
> index 2e84e56f8c9e1..304910ffc9c07 100644
> --- a/drivers/dpll/zl3073x/dpll.h
> +++ b/drivers/dpll/zl3073x/dpll.h
> @@ -16,6 +16,7 @@
>    * @refsel_mode: reference selection mode
>    * @forced_ref: selected reference in forced reference lock mode
>    * @check_count: periodic check counter
> + * @phase_monitor: is phase offset monitor enabled
>    * @dpll_dev: pointer to registered DPLL device
>    * @lock_status: last saved DPLL lock status
>    * @pins: list of pins
> @@ -27,6 +28,7 @@ struct zl3073x_dpll {
>          u8                              refsel_mode;
>          u8                              forced_ref;
>          u8                              check_count;
> +       bool                            phase_monitor;
>          struct dpll_device              *dpll_dev;
>          enum dpll_lock_status           lock_status;
>          struct list_head                pins;
> diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
> index 8dde92e623f76..9ee2f44a2eec7 100644
> --- a/drivers/dpll/zl3073x/regs.h
> +++ b/drivers/dpll/zl3073x/regs.h
> @@ -101,6 +101,9 @@
>   #define ZL_REG_REF_PHASE_ERR_READ_RQST         ZL_REG(4, 0x0f, 1)
>   #define ZL_REF_PHASE_ERR_READ_RQST_RD          BIT(0)
>
> +#define ZL_REG_REF_PHASE(_idx)                                         \
> +       ZL_REG_IDX(_idx, 4, 0x20, 6, ZL3073X_NUM_REFS, 6)
> +
>   /***********************
>    * Register Page 5, DPLL
>    ***********************/
> @@ -119,6 +122,9 @@
>   #define ZL_DPLL_MEAS_CTRL_EN                   BIT(0)
>   #define ZL_DPLL_MEAS_CTRL_AVG_FACTOR           GENMASK(7, 4)
>
> +#define ZL_REG_DPLL_MEAS_IDX                   ZL_REG(5, 0x51, 1)
> +#define ZL_DPLL_MEAS_IDX                       GENMASK(2, 0)
> +
>   #define ZL_REG_DPLL_PHASE_ERR_READ_MASK                ZL_REG(5, 0x54, 1)
>
>   #define ZL_REG_DPLL_PHASE_ERR_DATA(_idx)                               \
> --
> 2.49.0
>
Tested-by: Prathosh Satish <prathosh.satish@microchip.com>


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

* Re: [PATCH net-next 4/5] dpll: zl3073x: Add support to adjust phase
  2025-07-10 15:38 ` [PATCH net-next 4/5] dpll: zl3073x: Add support to adjust phase Ivan Vecera
@ 2025-07-14 14:15   ` Prathosh.Satish
  2025-07-15 13:12   ` Paolo Abeni
  1 sibling, 0 replies; 18+ messages in thread
From: Prathosh.Satish @ 2025-07-14 14:15 UTC (permalink / raw)
  To: ivecera, netdev
  Cc: arkadiusz.kubalewski, jiri, davem, kuba, pabeni, linux-kernel,
	mschmidt, poros

On 10/07/2025 16:38, Ivan Vecera wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>
> Add 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.
>
> 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/zl3073x/dpll.c | 184 ++++++++++++++++++++++++++++++++++++
>   drivers/dpll/zl3073x/regs.h |   3 +
>   2 files changed, 187 insertions(+)
>
> diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
> index 198e19f6fb152..4e05120c30b9a 100644
> --- a/drivers/dpll/zl3073x/dpll.c
> +++ b/drivers/dpll/zl3073x/dpll.c
> @@ -575,6 +575,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->dev;
> +       struct zl3073x_dpll_pin *pin = pin_priv;
> +       s64 phase_comp;
> +       u8 ref;
> +       int rc;
> +
> +       guard(mutex)(&zldev->multiop_lock);
> +
> +       /* Read reference configuration */
> +       ref = zl3073x_input_pin_ref_get(pin->id);
> +       rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD,
> +                          ZL_REG_REF_MB_MASK, BIT(ref));
> +       if (rc)
> +               return rc;
> +
> +       /* Read current phase offset compensation */
> +       rc = zl3073x_read_u48(zldev, ZL_REG_REF_PHASE_OFFSET_COMP, &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->dev;
> +       struct zl3073x_dpll_pin *pin = pin_priv;
> +       s64 phase_comp;
> +       u8 ref;
> +       int rc;
> +
> +       /* The value in the register is stored as two's complement negation
> +        * of requested value.
> +        */
> +       phase_comp = (s64) -phase_adjust;
> +
> +       guard(mutex)(&zldev->multiop_lock);
> +
> +       /* Read reference configuration */
> +       ref = zl3073x_input_pin_ref_get(pin->id);
> +       rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD,
> +                          ZL_REG_REF_MB_MASK, BIT(ref));
> +       if (rc)
> +               return rc;
> +
> +       /* Write the requested value into the compensation register */
> +       rc = zl3073x_write_u48(zldev, ZL_REG_REF_PHASE_OFFSET_COMP, phase_comp);
> +       if (rc)
> +               return rc;
> +
> +       /* Commit reference configuration */
> +       return zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR,
> +                            ZL_REG_REF_MB_MASK, BIT(ref));
> +}
> +
>   /**
>    * zl3073x_dpll_ref_prio_get - get priority for given input pin
>    * @pin: pointer to pin
> @@ -1278,6 +1357,107 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
>                               ZL_REG_OUTPUT_MB_MASK, BIT(out));
>   }
>
> +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->dev;
> +       struct zl3073x_dpll_pin *pin = pin_priv;
> +       u32 synth_freq;
> +       s32 phase_comp;
> +       u8 out, synth;
> +       int rc;
> +
> +       out = zl3073x_output_pin_out_get(pin->id);
> +       synth = zl3073x_out_synth_get(zldev, out);
> +       synth_freq = zl3073x_synth_freq_get(zldev, synth);
> +
> +       guard(mutex)(&zldev->multiop_lock);
> +
> +       /* Read output configuration */
> +       rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD,
> +                          ZL_REG_OUTPUT_MB_MASK, BIT(out));
> +       if (rc)
> +               return rc;
> +
> +       /* Read current output phase compensation */
> +       rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, &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->dev;
> +       struct zl3073x_dpll_pin *pin = pin_priv;
> +       int half_synth_cycle;
> +       u32 synth_freq;
> +       u8 out, synth;
> +       int rc;
> +
> +       /* Get attached synth */
> +       out = zl3073x_output_pin_out_get(pin->id);
> +       synth = zl3073x_out_synth_get(zldev, out);
> +
> +       /* 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;
> +
> +       /* The value in the register is stored as two's complement negation
> +        * of requested value.
> +        */
> +       phase_adjust = -phase_adjust;
> +
> +       guard(mutex)(&zldev->multiop_lock);
> +
> +       /* Read output configuration */
> +       rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD,
> +                          ZL_REG_OUTPUT_MB_MASK, BIT(out));
> +       if (rc)
> +               return rc;
> +
> +       /* Write the requested value into the compensation register */
> +       rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, phase_adjust);
> +       if (rc)
> +               return rc;
> +
> +       /* Update output configuration from mailbox */
> +       return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR,
> +                            ZL_REG_OUTPUT_MB_MASK, BIT(out));
> +}
> +
>   static int
>   zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
>                                            void *pin_priv,
> @@ -1405,6 +1585,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,
> @@ -1417,6 +1599,8 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
>          .esync_set = zl3073x_dpll_output_pin_esync_set,
>          .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,
>   };
>
> diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
> index 9ee2f44a2eec7..a382cd4a109f5 100644
> --- a/drivers/dpll/zl3073x/regs.h
> +++ b/drivers/dpll/zl3073x/regs.h
> @@ -168,6 +168,8 @@
>   #define ZL_REF_CONFIG_ENABLE                   BIT(0)
>   #define ZL_REF_CONFIG_DIFF_EN                  BIT(2)
>
> +#define ZL_REG_REF_PHASE_OFFSET_COMP           ZL_REG(10, 0x28, 6)
> +
>   #define ZL_REG_REF_SYNC_CTRL                   ZL_REG(10, 0x2e, 1)
>   #define ZL_REF_SYNC_CTRL_MODE                  GENMASK(2, 0)
>   #define ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF 0
> @@ -237,5 +239,6 @@
>   #define ZL_REG_OUTPUT_WIDTH                    ZL_REG(14, 0x10, 4)
>   #define ZL_REG_OUTPUT_ESYNC_PERIOD             ZL_REG(14, 0x14, 4)
>   #define ZL_REG_OUTPUT_ESYNC_WIDTH              ZL_REG(14, 0x18, 4)
> +#define ZL_REG_OUTPUT_PHASE_COMP               ZL_REG(14, 0x20, 4)
>
>   #endif /* _ZL3073X_REGS_H */
> --
> 2.49.0
>
Tested-by: Prathosh Satish <prathosh.satish@microchip.com>


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

* Re: [PATCH net-next 1/5] dpll: zl3073x: Add support to get/set esync on pins
  2025-07-10 15:38 ` [PATCH net-next 1/5] dpll: zl3073x: Add support to get/set esync on pins Ivan Vecera
  2025-07-14 14:13   ` Prathosh.Satish
@ 2025-07-15 12:52   ` Paolo Abeni
  2025-07-15 14:10     ` Ivan Vecera
  1 sibling, 1 reply; 18+ messages in thread
From: Paolo Abeni @ 2025-07-15 12:52 UTC (permalink / raw)
  To: Ivan Vecera, netdev
  Cc: Prathosh Satish, Arkadiusz Kubalewski, Jiri Pirko,
	David S. Miller, Jakub Kicinski, linux-kernel, Michal Schmidt,
	Petr Oros

On 7/10/25 5:38 PM, Ivan Vecera wrote:
> +static int
> +zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
> +				  void *pin_priv,
> +				  const struct dpll_device *dpll,
> +				  void *dpll_priv, u64 freq,
> +				  struct netlink_ext_ack *extack)
> +{
> +	struct zl3073x_dpll *zldpll = dpll_priv;
> +	struct zl3073x_dev *zldev = zldpll->dev;
> +	struct zl3073x_dpll_pin *pin = pin_priv;
> +	u32 esync_period, esync_width, output_div;
> +	u8 clock_type, out, output_mode, synth;
> +	u32 synth_freq;
> +	int rc;

Minor nit: please respect the reverse christmas tree order above.

> +
> +	out = zl3073x_output_pin_out_get(pin->id);
> +
> +	/* If N-division is enabled, esync is not supported. The register used
> +	 * for N-division is also used for the esync divider so both cannot
> +	 * be used.
> +	 */
> +	switch (zl3073x_out_signal_format_get(zldev, out)) {
> +	case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV:
> +	case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV:
> +		return -EOPNOTSUPP;
> +	default:
> +		break;
> +	}
> +
> +	guard(mutex)(&zldev->multiop_lock);
> +
> +	/* Read output configuration into mailbox */
> +	rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD,
> +			   ZL_REG_OUTPUT_MB_MASK, BIT(out));
> +	if (rc)
> +		return rc;
> +
> +	/* Read output mode */
> +	rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode);
> +	if (rc)
> +		return rc;
> +
> +	/* Select clock type */
> +	if (freq)
> +		clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC;
> +	else
> +		clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL;
> +
> +	/* Update clock type in output mode */
> +	output_mode &= ~ZL_OUTPUT_MODE_CLOCK_TYPE;
> +	output_mode |= FIELD_PREP(ZL_OUTPUT_MODE_CLOCK_TYPE, clock_type);
> +	rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_MODE, output_mode);
> +	if (rc)
> +		return rc;
> +
> +	/* If esync is being disabled just write mailbox and finish */
> +	if (!freq)
> +		goto write_mailbox;
> +
> +	/* Get synth attached to output pin */
> +	synth = zl3073x_out_synth_get(zldev, out);
> +
> +	/* Get synth frequency */
> +	synth_freq = zl3073x_synth_freq_get(zldev, synth);
> +
> +	rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div);
> +	if (rc)
> +		return rc;
> +
> +	/* Compute and update esync period */
> +	esync_period = synth_freq / (u32)freq / output_div;

Here there is no check for output_div != 0, while such check is present
into zl3073x_dpll_output_pin_esync_get(). Either is needed here, too, or
should be dropped from the 'getter'.

/P


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

* Re: [PATCH net-next 3/5] dpll: zl3073x: Implement phase offset monitor feature
  2025-07-10 15:38 ` [PATCH net-next 3/5] dpll: zl3073x: Implement phase offset monitor feature Ivan Vecera
  2025-07-14 14:15   ` Prathosh.Satish
@ 2025-07-15 13:02   ` Paolo Abeni
  2025-07-15 14:18     ` Ivan Vecera
  1 sibling, 1 reply; 18+ messages in thread
From: Paolo Abeni @ 2025-07-15 13:02 UTC (permalink / raw)
  To: Ivan Vecera, netdev
  Cc: Prathosh Satish, Arkadiusz Kubalewski, Jiri Pirko,
	David S. Miller, Jakub Kicinski, linux-kernel, Michal Schmidt,
	Petr Oros

On 7/10/25 5:38 PM, Ivan Vecera wrote:
> @@ -536,8 +539,38 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
>  		return 0;
>  	}
>  
> -	/* Report the latest measured phase offset for the connected ref */
> -	*phase_offset = pin->phase_offset * DPLL_PHASE_OFFSET_DIVIDER;
> +	ref_phase = pin->phase_offset;
> +
> +	/* The DPLL being locked to a higher freq than the current ref
> +	 * the phase offset is modded to the period of the signal
> +	 * the dpll is locked to.
> +	 */
> +	if (ZL3073X_DPLL_REF_IS_VALID(conn_ref) && conn_ref != ref) {
> +		u32 conn_freq, ref_freq;
> +
> +		/* Get frequency of connected ref */
> +		rc = zl3073x_dpll_input_ref_frequency_get(zldpll, conn_ref,
> +							  &conn_freq);
> +		if (rc)
> +			return rc;
> +
> +		/* Get frequency of given ref */
> +		rc = zl3073x_dpll_input_ref_frequency_get(zldpll, ref,
> +							  &ref_freq);
> +		if (rc)
> +			return rc;
> +
> +		if (conn_freq > ref_freq) {
> +			s64 conn_period;
> +			int div_factor;
> +
> +			conn_period = div_s64(PSEC_PER_SEC, conn_freq);
> +			div_factor = div64_s64(ref_phase, conn_period);
> +			ref_phase -= conn_period * div_factor;

It's not obvious to me that the above div64_s64() will yield a 32b value
for every possible arguments/configuration. Possibly a comment would
help (or just use s64 for div_factor).

/P


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

* Re: [PATCH net-next 4/5] dpll: zl3073x: Add support to adjust phase
  2025-07-10 15:38 ` [PATCH net-next 4/5] dpll: zl3073x: Add support to adjust phase Ivan Vecera
  2025-07-14 14:15   ` Prathosh.Satish
@ 2025-07-15 13:12   ` Paolo Abeni
  2025-07-15 14:24     ` Ivan Vecera
  1 sibling, 1 reply; 18+ messages in thread
From: Paolo Abeni @ 2025-07-15 13:12 UTC (permalink / raw)
  To: Ivan Vecera, netdev
  Cc: Prathosh Satish, Arkadiusz Kubalewski, Jiri Pirko,
	David S. Miller, Jakub Kicinski, linux-kernel, Michal Schmidt,
	Petr Oros

On 7/10/25 5:38 PM, Ivan Vecera wrote:
> +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->dev;
> +	struct zl3073x_dpll_pin *pin = pin_priv;
> +	u32 synth_freq;
> +	s32 phase_comp;
> +	u8 out, synth;
> +	int rc;
> +
> +	out = zl3073x_output_pin_out_get(pin->id);
> +	synth = zl3073x_out_synth_get(zldev, out);
> +	synth_freq = zl3073x_synth_freq_get(zldev, synth);
> +
> +	guard(mutex)(&zldev->multiop_lock);
> +
> +	/* Read output configuration */
> +	rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD,
> +			   ZL_REG_OUTPUT_MB_MASK, BIT(out));
> +	if (rc)
> +		return rc;
> +
> +	/* Read current output phase compensation */
> +	rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, &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);

Is 'synth_freq' guaranteed to be != 0 even on extreme conditions?
Possibly a comment or an explicit check could help.

/P


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

* Re: [PATCH net-next 1/5] dpll: zl3073x: Add support to get/set esync on pins
  2025-07-15 12:52   ` Paolo Abeni
@ 2025-07-15 14:10     ` Ivan Vecera
  0 siblings, 0 replies; 18+ messages in thread
From: Ivan Vecera @ 2025-07-15 14:10 UTC (permalink / raw)
  To: Paolo Abeni, netdev
  Cc: Prathosh Satish, Arkadiusz Kubalewski, Jiri Pirko,
	David S. Miller, Jakub Kicinski, linux-kernel, Michal Schmidt,
	Petr Oros



On 15. 07. 25 2:52 odp., Paolo Abeni wrote:
> On 7/10/25 5:38 PM, Ivan Vecera wrote:
>> +static int
>> +zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
>> +				  void *pin_priv,
>> +				  const struct dpll_device *dpll,
>> +				  void *dpll_priv, u64 freq,
>> +				  struct netlink_ext_ack *extack)
>> +{
>> +	struct zl3073x_dpll *zldpll = dpll_priv;
>> +	struct zl3073x_dev *zldev = zldpll->dev;
>> +	struct zl3073x_dpll_pin *pin = pin_priv;
>> +	u32 esync_period, esync_width, output_div;
>> +	u8 clock_type, out, output_mode, synth;
>> +	u32 synth_freq;
>> +	int rc;
> 
> Minor nit: please respect the reverse christmas tree order above.
> 

Will fix

>> +
>> +	out = zl3073x_output_pin_out_get(pin->id);
>> +
>> +	/* If N-division is enabled, esync is not supported. The register used
>> +	 * for N-division is also used for the esync divider so both cannot
>> +	 * be used.
>> +	 */
>> +	switch (zl3073x_out_signal_format_get(zldev, out)) {
>> +	case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV:
>> +	case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV:
>> +		return -EOPNOTSUPP;
>> +	default:
>> +		break;
>> +	}
>> +
>> +	guard(mutex)(&zldev->multiop_lock);
>> +
>> +	/* Read output configuration into mailbox */
>> +	rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD,
>> +			   ZL_REG_OUTPUT_MB_MASK, BIT(out));
>> +	if (rc)
>> +		return rc;
>> +
>> +	/* Read output mode */
>> +	rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode);
>> +	if (rc)
>> +		return rc;
>> +
>> +	/* Select clock type */
>> +	if (freq)
>> +		clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC;
>> +	else
>> +		clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL;
>> +
>> +	/* Update clock type in output mode */
>> +	output_mode &= ~ZL_OUTPUT_MODE_CLOCK_TYPE;
>> +	output_mode |= FIELD_PREP(ZL_OUTPUT_MODE_CLOCK_TYPE, clock_type);
>> +	rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_MODE, output_mode);
>> +	if (rc)
>> +		return rc;
>> +
>> +	/* If esync is being disabled just write mailbox and finish */
>> +	if (!freq)
>> +		goto write_mailbox;
>> +
>> +	/* Get synth attached to output pin */
>> +	synth = zl3073x_out_synth_get(zldev, out);
>> +
>> +	/* Get synth frequency */
>> +	synth_freq = zl3073x_synth_freq_get(zldev, synth);
>> +
>> +	rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div);
>> +	if (rc)
>> +		return rc;
>> +
>> +	/* Compute and update esync period */
>> +	esync_period = synth_freq / (u32)freq / output_div;
> 
> Here there is no check for output_div != 0, while such check is present
> into zl3073x_dpll_output_pin_esync_get(). Either is needed here, too, or
> should be dropped from the 'getter'.

Will add it here...

Thanks Paolo...

I.


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

* Re: [PATCH net-next 3/5] dpll: zl3073x: Implement phase offset monitor feature
  2025-07-15 13:02   ` Paolo Abeni
@ 2025-07-15 14:18     ` Ivan Vecera
  0 siblings, 0 replies; 18+ messages in thread
From: Ivan Vecera @ 2025-07-15 14:18 UTC (permalink / raw)
  To: Paolo Abeni, netdev
  Cc: Prathosh Satish, Arkadiusz Kubalewski, Jiri Pirko,
	David S. Miller, Jakub Kicinski, linux-kernel, Michal Schmidt,
	Petr Oros



On 15. 07. 25 3:02 odp., Paolo Abeni wrote:
> On 7/10/25 5:38 PM, Ivan Vecera wrote:
>> @@ -536,8 +539,38 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
>>   		return 0;
>>   	}
>>   
>> -	/* Report the latest measured phase offset for the connected ref */
>> -	*phase_offset = pin->phase_offset * DPLL_PHASE_OFFSET_DIVIDER;
>> +	ref_phase = pin->phase_offset;
>> +
>> +	/* The DPLL being locked to a higher freq than the current ref
>> +	 * the phase offset is modded to the period of the signal
>> +	 * the dpll is locked to.
>> +	 */
>> +	if (ZL3073X_DPLL_REF_IS_VALID(conn_ref) && conn_ref != ref) {
>> +		u32 conn_freq, ref_freq;
>> +
>> +		/* Get frequency of connected ref */
>> +		rc = zl3073x_dpll_input_ref_frequency_get(zldpll, conn_ref,
>> +							  &conn_freq);
>> +		if (rc)
>> +			return rc;
>> +
>> +		/* Get frequency of given ref */
>> +		rc = zl3073x_dpll_input_ref_frequency_get(zldpll, ref,
>> +							  &ref_freq);
>> +		if (rc)
>> +			return rc;
>> +
>> +		if (conn_freq > ref_freq) {
>> +			s64 conn_period;
>> +			int div_factor;
>> +
>> +			conn_period = div_s64(PSEC_PER_SEC, conn_freq);
>> +			div_factor = div64_s64(ref_phase, conn_period);
>> +			ref_phase -= conn_period * div_factor;
> 
> It's not obvious to me that the above div64_s64() will yield a 32b value
> for every possible arguments/configuration. Possibly a comment would
> help (or just use s64 for div_factor).

Practically it should not happen due to maximal reference frequency but
it will be safer use s64 here also for div_factor..

Will fix in v2.

Thanks,
Ivan


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

* Re: [PATCH net-next 4/5] dpll: zl3073x: Add support to adjust phase
  2025-07-15 13:12   ` Paolo Abeni
@ 2025-07-15 14:24     ` Ivan Vecera
  0 siblings, 0 replies; 18+ messages in thread
From: Ivan Vecera @ 2025-07-15 14:24 UTC (permalink / raw)
  To: Paolo Abeni, netdev
  Cc: Prathosh Satish, Arkadiusz Kubalewski, Jiri Pirko,
	David S. Miller, Jakub Kicinski, linux-kernel, Michal Schmidt,
	Petr Oros

On 15. 07. 25 3:12 odp., Paolo Abeni wrote:
> On 7/10/25 5:38 PM, Ivan Vecera wrote:
>> +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->dev;
>> +	struct zl3073x_dpll_pin *pin = pin_priv;
>> +	u32 synth_freq;
>> +	s32 phase_comp;
>> +	u8 out, synth;
>> +	int rc;
>> +
>> +	out = zl3073x_output_pin_out_get(pin->id);
>> +	synth = zl3073x_out_synth_get(zldev, out);
>> +	synth_freq = zl3073x_synth_freq_get(zldev, synth);
>> +
>> +	guard(mutex)(&zldev->multiop_lock);
>> +
>> +	/* Read output configuration */
>> +	rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD,
>> +			   ZL_REG_OUTPUT_MB_MASK, BIT(out));
>> +	if (rc)
>> +		return rc;
>> +
>> +	/* Read current output phase compensation */
>> +	rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, &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);
> 
> Is 'synth_freq' guaranteed to be != 0 even on extreme conditions?
> Possibly a comment or an explicit check could help.

Under normal conditions (device is working and synth is enabled) this
should not happen but additional check here is reasonable to catch
unusual conditions to avoid division by zero.

Will add it.

Thanks,
Ivan


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

end of thread, other threads:[~2025-07-15 14:24 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-10 15:38 [PATCH net-next 0/5] dpll: zl3073x: Add misc features Ivan Vecera
2025-07-10 15:38 ` [PATCH net-next 1/5] dpll: zl3073x: Add support to get/set esync on pins Ivan Vecera
2025-07-14 14:13   ` Prathosh.Satish
2025-07-15 12:52   ` Paolo Abeni
2025-07-15 14:10     ` Ivan Vecera
2025-07-10 15:38 ` [PATCH net-next 2/5] dpll: zl3073x: Add support to get phase offset on connected input pin Ivan Vecera
2025-07-14 14:14   ` Prathosh.Satish
2025-07-10 15:38 ` [PATCH net-next 3/5] dpll: zl3073x: Implement phase offset monitor feature Ivan Vecera
2025-07-14 14:15   ` Prathosh.Satish
2025-07-15 13:02   ` Paolo Abeni
2025-07-15 14:18     ` Ivan Vecera
2025-07-10 15:38 ` [PATCH net-next 4/5] dpll: zl3073x: Add support to adjust phase Ivan Vecera
2025-07-14 14:15   ` Prathosh.Satish
2025-07-15 13:12   ` Paolo Abeni
2025-07-15 14:24     ` Ivan Vecera
2025-07-10 15:38 ` [PATCH net-next 5/5] dpll: zl3073x: Add support to get fractional frequency offset Ivan Vecera
2025-07-14 10:55 ` [PATCH net-next 0/5] dpll: zl3073x: Add misc features Jiri Pirko
2025-07-14 14:11 ` Prathosh.Satish

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).