public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH net-next v3 0/2] dpll: rework fractional frequency offset reporting
@ 2026-05-04 15:53 Ivan Vecera
  2026-05-04 15:53 ` [PATCH net-next v3 1/2] dpll: add fractional frequency offset to pin-parent-device Ivan Vecera
  2026-05-04 15:53 ` [PATCH net-next v3 2/2] dpll: zl3073x: report FFO as DPLL vs input reference offset Ivan Vecera
  0 siblings, 2 replies; 5+ messages in thread
From: Ivan Vecera @ 2026-05-04 15:53 UTC (permalink / raw)
  To: netdev, Jiri Pirko
  Cc: Andrew Lunn, Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
	Eric Dumazet, Jakub Kicinski, Jonathan Corbet, Leon Romanovsky,
	Mark Bloch, Michal Schmidt, Paolo Abeni, Pasi Vaananen, Petr Oros,
	Prathosh Satish, Saeed Mahameed, Shuah Khan, Simon Horman,
	Tariq Toukan, Vadim Fedorenko, linux-doc, linux-kernel,
	linux-rdma

Rework how the fractional frequency offset (FFO) is reported in
the DPLL subsystem.

Both fractional-frequency-offset (PPM) and
fractional-frequency-offset-ppt (PPT) attributes are now present at
the top level of a pin and inside each pin-parent-device nest. They
carry the same measurement at different precisions.

The ffo_get callback distinguishes the two contexts: dpll=NULL for
the top-level call (RX vs TX symbol rate offset) and a valid dpll
pointer for the nested per-parent call (pin vs DPLL offset). This
allows drivers to report a different value per parent DPLL if needed.

Patch 1 adds both attributes to the pin-parent-device subset, updates
the DPLL netlink handling to emit both at each level, updates the YAML
spec and driver-api documentation, and adds NULL guards to mlx5 and
zl3073x drivers.

Patch 2 implements the nested FFO for zl3073x using the
dpll_df_offset_x register with ref_ofst=1, providing 2^-48
resolution. The old per-reference frequency measurement is removed
as it was redundant with measured-frequency.

Changes v2 -> v3:
- Keep both FFO attributes (PPM and PPT) at both levels instead of
  moving PPT under pin-parent-device only (Jiri Pirko)
- Unify attribute documentation to describe semantics at each level

Changes v1 -> v2:
- Minor commit message fixes

Ivan Vecera (2):
  dpll: add fractional frequency offset to pin-parent-device
  dpll: zl3073x: report FFO as DPLL vs input reference offset

 Documentation/driver-api/dpll.rst             | 15 +++++++
 Documentation/netlink/specs/dpll.yaml         | 28 +++++++-----
 drivers/dpll/dpll_netlink.c                   | 23 +++++-----
 drivers/dpll/dpll_nl.c                        |  2 +
 drivers/dpll/zl3073x/chan.c                   | 31 ++++++++++++-
 drivers/dpll/zl3073x/chan.h                   | 14 ++++++
 drivers/dpll/zl3073x/core.c                   | 45 -------------------
 drivers/dpll/zl3073x/dpll.c                   | 34 +++++++-------
 drivers/dpll/zl3073x/ref.h                    | 14 ------
 drivers/dpll/zl3073x/regs.h                   | 15 +++++++
 .../net/ethernet/mellanox/mlx5/core/dpll.c    |  4 ++
 11 files changed, 125 insertions(+), 100 deletions(-)

-- 
2.53.0


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

* [PATCH net-next v3 1/2] dpll: add fractional frequency offset to pin-parent-device
  2026-05-04 15:53 [PATCH net-next v3 0/2] dpll: rework fractional frequency offset reporting Ivan Vecera
@ 2026-05-04 15:53 ` Ivan Vecera
  2026-05-05 11:31   ` Jiri Pirko
  2026-05-04 15:53 ` [PATCH net-next v3 2/2] dpll: zl3073x: report FFO as DPLL vs input reference offset Ivan Vecera
  1 sibling, 1 reply; 5+ messages in thread
From: Ivan Vecera @ 2026-05-04 15:53 UTC (permalink / raw)
  To: netdev, Jiri Pirko
  Cc: Andrew Lunn, Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
	Eric Dumazet, Jakub Kicinski, Jonathan Corbet, Leon Romanovsky,
	Mark Bloch, Michal Schmidt, Paolo Abeni, Pasi Vaananen, Petr Oros,
	Prathosh Satish, Saeed Mahameed, Shuah Khan, Simon Horman,
	Tariq Toukan, Vadim Fedorenko, linux-doc, linux-kernel,
	linux-rdma

Add both fractional-frequency-offset (PPM) and
fractional-frequency-offset-ppt (PPT) attributes to the
pin-parent-device nested attribute set, alongside the existing
top-level pin attributes. Both carry the same measurement at
different precisions.

Distinguish the two contexts in the ffo_get callback by passing
dpll=NULL for the top-level call and a valid dpll pointer for the
nested per-parent call. This allows drivers to report a different
value per parent DPLL if needed. Update mlx5 and zl3073x drivers
to return -ENODATA for the context they do not yet support.

Add documentation for both FFO attributes to dpll.rst.

Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 Documentation/driver-api/dpll.rst             | 15 ++++++++++
 Documentation/netlink/specs/dpll.yaml         | 28 ++++++++++++-------
 drivers/dpll/dpll_netlink.c                   | 23 +++++++--------
 drivers/dpll/dpll_nl.c                        |  2 ++
 drivers/dpll/zl3073x/dpll.c                   |  4 +++
 .../net/ethernet/mellanox/mlx5/core/dpll.c    |  4 +++
 6 files changed, 55 insertions(+), 21 deletions(-)

diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst
index 37eaef785e304..c21aea6b52f6b 100644
--- a/Documentation/driver-api/dpll.rst
+++ b/Documentation/driver-api/dpll.rst
@@ -258,6 +258,21 @@ in the ``DPLL_A_PIN_PHASE_OFFSET`` attribute.
   ``DPLL_A_PHASE_OFFSET_MONITOR`` attr state of a feature
   =============================== ========================
 
+Fractional frequency offset
+===========================
+
+The fractional frequency offset (FFO) is reported through two attributes
+that carry the same measurement at different precisions:
+
+- ``DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET`` in PPM (parts per million)
+- ``DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT`` in PPT (parts per trillion)
+
+Both attributes appear at the top level of a pin and inside each
+``pin-parent-device`` nest. The driver's ``ffo_get`` callback receives
+a NULL ``dpll`` pointer for the top-level context and a valid pointer
+for the per-parent context, allowing it to distinguish the two if
+needed (e.g. report a different measurement per parent DPLL).
+
 Frequency monitor
 =================
 
diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml
index c45de70a47ce6..91a172617b3a9 100644
--- a/Documentation/netlink/specs/dpll.yaml
+++ b/Documentation/netlink/specs/dpll.yaml
@@ -448,12 +448,14 @@ attribute-sets:
         name: fractional-frequency-offset
         type: sint
         doc: |
-          The FFO (Fractional Frequency Offset) between the RX and TX
-          symbol rate on the media associated with the pin:
-          (rx_frequency-tx_frequency)/rx_frequency
+          The FFO (Fractional Frequency Offset) of the pin.
+          At top level this represents the RX vs TX symbol rate
+          offset on the media associated with the pin. Inside
+          the pin-parent-device nest it represents the frequency
+          offset between the pin and its parent DPLL device.
           Value is in PPM (parts per million).
-          This may be implemented for example for pin of type
-          PIN_TYPE_SYNCE_ETH_PORT.
+          This is a lower-precision version of
+          fractional-frequency-offset-ppt.
       -
         name: esync-frequency
         type: u64
@@ -492,12 +494,14 @@ attribute-sets:
         name: fractional-frequency-offset-ppt
         type: sint
         doc: |
-          The FFO (Fractional Frequency Offset) of the pin with respect to
-          the nominal frequency.
-          Value = (frequency_measured - frequency_nominal) / frequency_nominal
+          The FFO (Fractional Frequency Offset) of the pin.
+          At top level this represents the RX vs TX symbol rate
+          offset on the media associated with the pin. Inside
+          the pin-parent-device nest it represents the frequency
+          offset between the pin and its parent DPLL device.
           Value is in PPT (parts per trillion, 10^-12).
-          Note: This attribute provides higher resolution than the standard
-          fractional-frequency-offset (which is in PPM).
+          This is a higher-precision version of
+          fractional-frequency-offset.
       -
         name: measured-frequency
         type: u64
@@ -534,6 +538,10 @@ attribute-sets:
         name: operstate
       -
         name: phase-offset
+      -
+        name: fractional-frequency-offset
+      -
+        name: fractional-frequency-offset-ppt
   -
     name: pin-parent-pin
     subset-of: pin
diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
index 05cf946b4be5e..e1158033ba0a1 100644
--- a/drivers/dpll/dpll_netlink.c
+++ b/drivers/dpll/dpll_netlink.c
@@ -417,30 +417,27 @@ dpll_msg_add_phase_offset(struct sk_buff *msg, struct dpll_pin *pin,
 
 static int dpll_msg_add_ffo(struct sk_buff *msg, struct dpll_pin *pin,
 			    struct dpll_pin_ref *ref,
+			    const struct dpll_device *dpll, void *dpll_priv,
 			    struct netlink_ext_ack *extack)
 {
 	const struct dpll_pin_ops *ops = dpll_pin_ops(ref);
-	struct dpll_device *dpll = ref->dpll;
 	s64 ffo;
 	int ret;
 
 	if (!ops->ffo_get)
 		return 0;
-	ret = ops->ffo_get(pin, dpll_pin_on_dpll_priv(dpll, pin),
-			   dpll, dpll_priv(dpll), &ffo, extack);
+	ret = ops->ffo_get(pin, dpll_pin_on_dpll_priv(ref->dpll, pin),
+			   dpll, dpll_priv, &ffo, extack);
 	if (ret) {
 		if (ret == -ENODATA)
 			return 0;
 		return ret;
 	}
-	/* Put the FFO value in PPM to preserve compatibility with older
-	 * programs.
-	 */
-	ret = nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET,
-			   div_s64(ffo, 1000000));
-	if (ret)
+	if (nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET,
+			 div_s64(ffo, 1000000)))
 		return -EMSGSIZE;
-	return nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT,
+	return nla_put_sint(msg,
+			    DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT,
 			    ffo);
 }
 
@@ -686,6 +683,10 @@ dpll_msg_add_pin_dplls(struct sk_buff *msg, struct dpll_pin *pin,
 		if (ret)
 			goto nest_cancel;
 		ret = dpll_msg_add_phase_offset(msg, pin, ref, extack);
+		if (ret)
+			goto nest_cancel;
+		ret = dpll_msg_add_ffo(msg, pin, ref, ref->dpll,
+				       dpll_priv(ref->dpll), extack);
 		if (ret)
 			goto nest_cancel;
 		nla_nest_end(msg, attr);
@@ -748,7 +749,7 @@ dpll_cmd_pin_get_one(struct sk_buff *msg, struct dpll_pin *pin,
 	ret = dpll_msg_add_pin_phase_adjust(msg, pin, ref, extack);
 	if (ret)
 		return ret;
-	ret = dpll_msg_add_ffo(msg, pin, ref, extack);
+	ret = dpll_msg_add_ffo(msg, pin, ref, NULL, NULL, extack);
 	if (ret)
 		return ret;
 	ret = dpll_msg_add_measured_freq(msg, pin, ref, extack);
diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c
index 58235845fa3d5..b1d9182c7802f 100644
--- a/drivers/dpll/dpll_nl.c
+++ b/drivers/dpll/dpll_nl.c
@@ -19,6 +19,8 @@ const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_OPERSTATE +
 	[DPLL_A_PIN_STATE] = NLA_POLICY_RANGE(NLA_U32, 1, 3),
 	[DPLL_A_PIN_OPERSTATE] = NLA_POLICY_RANGE(NLA_U32, 1, 4),
 	[DPLL_A_PIN_PHASE_OFFSET] = { .type = NLA_S64, },
+	[DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET] = { .type = NLA_SINT, },
+	[DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT] = { .type = NLA_SINT, },
 };
 
 const struct nla_policy dpll_pin_parent_pin_nl_policy[DPLL_A_PIN_STATE + 1] = {
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 6fd718696de0d..f2d430d1a8e7b 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -299,6 +299,10 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
 {
 	struct zl3073x_dpll_pin *pin = pin_priv;
 
+	/* Only rx vs tx symbol rate FFO is supported */
+	if (dpll)
+		return -ENODATA;
+
 	*ffo = pin->freq_offset;
 
 	return 0;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/dpll.c b/drivers/net/ethernet/mellanox/mlx5/core/dpll.c
index bce72e8d1bc31..ef2c58c390efa 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/dpll.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/dpll.c
@@ -306,6 +306,10 @@ static int mlx5_dpll_ffo_get(const struct dpll_pin *pin, void *pin_priv,
 	struct mlx5_dpll *mdpll = pin_priv;
 	int err;
 
+	/* Only rx vs tx symbol rate FFO is supported */
+	if (dpll)
+		return -ENODATA;
+
 	err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status);
 	if (err)
 		return err;
-- 
2.53.0


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

* [PATCH net-next v3 2/2] dpll: zl3073x: report FFO as DPLL vs input reference offset
  2026-05-04 15:53 [PATCH net-next v3 0/2] dpll: rework fractional frequency offset reporting Ivan Vecera
  2026-05-04 15:53 ` [PATCH net-next v3 1/2] dpll: add fractional frequency offset to pin-parent-device Ivan Vecera
@ 2026-05-04 15:53 ` Ivan Vecera
  2026-05-05 14:34   ` Petr Oros
  1 sibling, 1 reply; 5+ messages in thread
From: Ivan Vecera @ 2026-05-04 15:53 UTC (permalink / raw)
  To: netdev, Jiri Pirko
  Cc: Andrew Lunn, Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
	Eric Dumazet, Jakub Kicinski, Jonathan Corbet, Leon Romanovsky,
	Mark Bloch, Michal Schmidt, Paolo Abeni, Pasi Vaananen, Petr Oros,
	Prathosh Satish, Saeed Mahameed, Shuah Khan, Simon Horman,
	Tariq Toukan, Vadim Fedorenko, linux-doc, linux-kernel,
	linux-rdma

Replace the per-reference frequency offset measurement (which was
redundant with measured-frequency) with a direct read of the DPLL's
delta frequency offset vs its tracked input reference.

The new implementation uses the dpll_df_offset_x register with
ref_ofst=1 via the dpll_df_read_x semaphore mechanism. This
provides 2^-48 resolution (~3.5 fE) and reports the actual
frequency difference between the DPLL and its active input.

FFO is now reported only for the active input pin in the nested
(pin vs parent DPLL) context. Top-level FFO returns -ENODATA.

Rewrite ffo_check to compare the cached df_offset converted to PPT
instead of using the old per-reference measurement. Remove the
ref_ffo_update periodic measurement and the ref ffo field since
they are no longer needed.

Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 drivers/dpll/zl3073x/chan.c | 31 +++++++++++++++++++++++--
 drivers/dpll/zl3073x/chan.h | 14 ++++++++++++
 drivers/dpll/zl3073x/core.c | 45 -------------------------------------
 drivers/dpll/zl3073x/dpll.c | 34 ++++++++++++----------------
 drivers/dpll/zl3073x/ref.h  | 14 ------------
 drivers/dpll/zl3073x/regs.h | 15 +++++++++++++
 6 files changed, 72 insertions(+), 81 deletions(-)

diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
index 2f48ca2391494..2fe3c3da84bb5 100644
--- a/drivers/dpll/zl3073x/chan.c
+++ b/drivers/dpll/zl3073x/chan.c
@@ -18,6 +18,7 @@
 int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
 {
 	struct zl3073x_chan *chan = &zldev->chan[index];
+	u64 val;
 	int rc;
 
 	rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(index),
@@ -25,8 +26,34 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
 	if (rc)
 		return rc;
 
-	return zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
-			       &chan->refsel_status);
+	rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
+			     &chan->refsel_status);
+	if (rc)
+		return rc;
+
+	/* Read df_offset vs tracked reference */
+	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
+				  ZL_DPLL_DF_READ_SEM);
+	if (rc)
+		return rc;
+
+	rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index),
+			      ZL_DPLL_DF_READ_SEM | ZL_DPLL_DF_READ_REF_OFST);
+	if (rc)
+		return rc;
+
+	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
+				  ZL_DPLL_DF_READ_SEM);
+	if (rc)
+		return rc;
+
+	rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val);
+	if (rc)
+		return rc;
+
+	chan->df_offset = sign_extend64(val, 47);
+
+	return 0;
 }
 
 /**
diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h
index 481da2133202b..4353809c69122 100644
--- a/drivers/dpll/zl3073x/chan.h
+++ b/drivers/dpll/zl3073x/chan.h
@@ -17,6 +17,7 @@ struct zl3073x_dev;
  * @ref_prio: reference priority registers (4 bits per ref, P/N packed)
  * @mon_status: monitor status register value
  * @refsel_status: reference selection status register value
+ * @df_offset: frequency offset vs tracked reference in 2^-48 steps
  */
 struct zl3073x_chan {
 	struct_group(cfg,
@@ -26,6 +27,7 @@ struct zl3073x_chan {
 	struct_group(stat,
 		u8	mon_status;
 		u8	refsel_status;
+		s64	df_offset;
 	);
 };
 
@@ -37,6 +39,18 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
 
 int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);
 
+/**
+ * zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference
+ * @chan: pointer to channel state
+ *
+ * Return: frequency offset in 2^-48 steps
+ */
+static inline s64
+zl3073x_chan_df_offset_get(const struct zl3073x_chan *chan)
+{
+	return chan->df_offset;
+}
+
 /**
  * zl3073x_chan_mode_get - get DPLL channel operating mode
  * @chan: pointer to channel state
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 5f1e70f3e40a0..b3345060490db 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -704,44 +704,6 @@ zl3073x_ref_freq_meas_update(struct zl3073x_dev *zldev)
 	return 0;
 }
 
-/**
- * zl3073x_ref_ffo_update - update reference fractional frequency offsets
- * @zldev: pointer to zl3073x_dev structure
- *
- * The function asks device to latch the latest measured fractional
- * frequency offset values, reads and stores them into the ref state.
- *
- * Return: 0 on success, <0 on error
- */
-static int
-zl3073x_ref_ffo_update(struct zl3073x_dev *zldev)
-{
-	int i, rc;
-
-	rc = zl3073x_ref_freq_meas_latch(zldev,
-					 ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF);
-	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 ppt
-		 * ffo = (10^12 * value) / 2^32
-		 * ffo = ( 5^12 * value) / 2^20
-		 */
-		zldev->ref[i].ffo = mul_s64_u64_shr(value, 244140625, 20);
-	}
-
-	return 0;
-}
-
 static void
 zl3073x_dev_periodic_work(struct kthread_work *work)
 {
@@ -776,13 +738,6 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
 		}
 	}
 
-	/* 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/dpll.c b/drivers/dpll/zl3073x/dpll.c
index f2d430d1a8e7b..af50cd6200001 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -299,8 +299,12 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
 {
 	struct zl3073x_dpll_pin *pin = pin_priv;
 
-	/* Only rx vs tx symbol rate FFO is supported */
-	if (dpll)
+	/* Only nested FFO (pin vs parent DPLL) is supported */
+	if (!dpll)
+		return -ENODATA;
+
+	/* Report FFO only for the active pin */
+	if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
 		return -ENODATA;
 
 	*ffo = pin->freq_offset;
@@ -1733,37 +1737,27 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
 }
 
 /**
- * zl3073x_dpll_pin_ffo_check - check for pin fractional frequency offset change
+ * zl3073x_dpll_pin_ffo_check - check for FFO change on active pin
  * @pin: pin to check
  *
- * Check for the given pin's fractional frequency change.
- *
- * Return: true on fractional frequency offset change, false otherwise
+ * Return: true on 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;
-	const struct zl3073x_ref *ref;
-	u8 ref_id;
+	const struct zl3073x_chan *chan;
 	s64 ffo;
 
-	/* Get reference monitor status */
-	ref_id = zl3073x_input_pin_ref_get(pin->id);
-	ref = zl3073x_ref_state_get(zldev, ref_id);
-
-	/* Do not report ffo changes if the reference monitor report errors */
-	if (!zl3073x_ref_is_status_ok(ref))
+	if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
 		return false;
 
-	/* Compare with previous value */
-	ffo = zl3073x_ref_ffo_get(ref);
+	chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+	ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan),
+			      244140625, 36);
+
 	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;
 	}
 
diff --git a/drivers/dpll/zl3073x/ref.h b/drivers/dpll/zl3073x/ref.h
index 55e80e4f08734..e140ca3ea17dc 100644
--- a/drivers/dpll/zl3073x/ref.h
+++ b/drivers/dpll/zl3073x/ref.h
@@ -22,7 +22,6 @@ struct zl3073x_dev;
  * @freq_ratio_n: FEC mode divisor
  * @sync_ctrl: reference sync control
  * @config: reference config
- * @ffo: current fractional frequency offset
  * @meas_freq: measured input frequency in Hz
  * @mon_status: reference monitor status
  */
@@ -40,7 +39,6 @@ struct zl3073x_ref {
 		u8	config;
 	);
 	struct_group(stat, /* Status */
-		s64	ffo;
 		u32	meas_freq;
 		u8	mon_status;
 	);
@@ -58,18 +56,6 @@ int zl3073x_ref_state_update(struct zl3073x_dev *zldev, u8 index);
 
 int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult);
 
-/**
- * zl3073x_ref_ffo_get - get current fractional frequency offset
- * @ref: pointer to ref state
- *
- * Return: the latest measured fractional frequency offset
- */
-static inline s64
-zl3073x_ref_ffo_get(const struct zl3073x_ref *ref)
-{
-	return ref->ffo;
-}
-
 /**
  * zl3073x_ref_meas_freq_get - get measured input frequency
  * @ref: pointer to ref state
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index 8015808bdf548..9578f00095282 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -164,6 +164,11 @@
 #define ZL_DPLL_MODE_REFSEL_MODE_NCO		4
 #define ZL_DPLL_MODE_REFSEL_REF			GENMASK(7, 4)
 
+#define ZL_REG_DPLL_DF_READ(_idx)					\
+	ZL_REG_IDX(_idx, 5, 0x28, 1, ZL3073X_MAX_CHANNELS, 1)
+#define ZL_DPLL_DF_READ_SEM			BIT(4)
+#define ZL_DPLL_DF_READ_REF_OFST		BIT(3)
+
 #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)
@@ -176,6 +181,16 @@
 #define ZL_REG_DPLL_PHASE_ERR_DATA(_idx)				\
 	ZL_REG_IDX(_idx, 5, 0x55, 6, ZL3073X_MAX_CHANNELS, 6)
 
+/*******************************
+ * Register Pages 6-7, DPLL Data
+ *******************************/
+
+#define ZL_REG_DPLL_DF_OFFSET_03(_idx)					\
+	ZL_REG_IDX(_idx, 6, 0x00, 6, 4, 0x20)
+#define ZL_REG_DPLL_DF_OFFSET_4		ZL_REG(7, 0x00, 6)
+#define ZL_REG_DPLL_DF_OFFSET(_idx)					\
+	((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4)
+
 /***********************************
  * Register Page 9, Synth and Output
  ***********************************/
-- 
2.53.0


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

* Re: [PATCH net-next v3 1/2] dpll: add fractional frequency offset to pin-parent-device
  2026-05-04 15:53 ` [PATCH net-next v3 1/2] dpll: add fractional frequency offset to pin-parent-device Ivan Vecera
@ 2026-05-05 11:31   ` Jiri Pirko
  0 siblings, 0 replies; 5+ messages in thread
From: Jiri Pirko @ 2026-05-05 11:31 UTC (permalink / raw)
  To: Ivan Vecera
  Cc: netdev, Andrew Lunn, Arkadiusz Kubalewski, David S. Miller,
	Donald Hunter, Eric Dumazet, Jakub Kicinski, Jonathan Corbet,
	Leon Romanovsky, Mark Bloch, Michal Schmidt, Paolo Abeni,
	Pasi Vaananen, Petr Oros, Prathosh Satish, Saeed Mahameed,
	Shuah Khan, Simon Horman, Tariq Toukan, Vadim Fedorenko,
	linux-doc, linux-kernel, linux-rdma

Mon, May 04, 2026 at 05:53:39PM +0200, ivecera@redhat.com wrote:
>Add both fractional-frequency-offset (PPM) and
>fractional-frequency-offset-ppt (PPT) attributes to the
>pin-parent-device nested attribute set, alongside the existing
>top-level pin attributes. Both carry the same measurement at
>different precisions.
>
>Distinguish the two contexts in the ffo_get callback by passing
>dpll=NULL for the top-level call and a valid dpll pointer for the
>nested per-parent call. This allows drivers to report a different
>value per parent DPLL if needed. Update mlx5 and zl3073x drivers
>to return -ENODATA for the context they do not yet support.
>
>Add documentation for both FFO attributes to dpll.rst.
>
>Signed-off-by: Ivan Vecera <ivecera@redhat.com>

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

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

* Re: [PATCH net-next v3 2/2] dpll: zl3073x: report FFO as DPLL vs input reference offset
  2026-05-04 15:53 ` [PATCH net-next v3 2/2] dpll: zl3073x: report FFO as DPLL vs input reference offset Ivan Vecera
@ 2026-05-05 14:34   ` Petr Oros
  0 siblings, 0 replies; 5+ messages in thread
From: Petr Oros @ 2026-05-05 14:34 UTC (permalink / raw)
  To: Ivan Vecera, netdev, Jiri Pirko
  Cc: Andrew Lunn, Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
	Eric Dumazet, Jakub Kicinski, Jonathan Corbet, Leon Romanovsky,
	Mark Bloch, Michal Schmidt, Paolo Abeni, Pasi Vaananen,
	Prathosh Satish, Saeed Mahameed, Shuah Khan, Simon Horman,
	Tariq Toukan, Vadim Fedorenko, linux-doc, linux-kernel,
	linux-rdma


On 5/4/26 17:53, Ivan Vecera wrote:
> Replace the per-reference frequency offset measurement (which was
> redundant with measured-frequency) with a direct read of the DPLL's
> delta frequency offset vs its tracked input reference.
>
> The new implementation uses the dpll_df_offset_x register with
> ref_ofst=1 via the dpll_df_read_x semaphore mechanism. This
> provides 2^-48 resolution (~3.5 fE) and reports the actual
> frequency difference between the DPLL and its active input.
>
> FFO is now reported only for the active input pin in the nested
> (pin vs parent DPLL) context. Top-level FFO returns -ENODATA.
>
> Rewrite ffo_check to compare the cached df_offset converted to PPT
> instead of using the old per-reference measurement. Remove the
> ref_ffo_update periodic measurement and the ref ffo field since
> they are no longer needed.
>
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
> ---
>   drivers/dpll/zl3073x/chan.c | 31 +++++++++++++++++++++++--
>   drivers/dpll/zl3073x/chan.h | 14 ++++++++++++
>   drivers/dpll/zl3073x/core.c | 45 -------------------------------------
>   drivers/dpll/zl3073x/dpll.c | 34 ++++++++++++----------------
>   drivers/dpll/zl3073x/ref.h  | 14 ------------
>   drivers/dpll/zl3073x/regs.h | 15 +++++++++++++
>   6 files changed, 72 insertions(+), 81 deletions(-)
>
> diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
> index 2f48ca2391494..2fe3c3da84bb5 100644
> --- a/drivers/dpll/zl3073x/chan.c
> +++ b/drivers/dpll/zl3073x/chan.c
> @@ -18,6 +18,7 @@
>   int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
>   {
>   	struct zl3073x_chan *chan = &zldev->chan[index];
> +	u64 val;
>   	int rc;
>   
>   	rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(index),
> @@ -25,8 +26,34 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
>   	if (rc)
>   		return rc;
>   
> -	return zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
> -			       &chan->refsel_status);
> +	rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
> +			     &chan->refsel_status);
> +	if (rc)
> +		return rc;
> +
> +	/* Read df_offset vs tracked reference */
> +	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
> +				  ZL_DPLL_DF_READ_SEM);
> +	if (rc)
> +		return rc;
> +
> +	rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index),
> +			      ZL_DPLL_DF_READ_SEM | ZL_DPLL_DF_READ_REF_OFST);
> +	if (rc)
> +		return rc;
> +
> +	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
> +				  ZL_DPLL_DF_READ_SEM);
> +	if (rc)
> +		return rc;
> +
> +	rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val);
> +	if (rc)
> +		return rc;
> +
> +	chan->df_offset = sign_extend64(val, 47);
> +
> +	return 0;
>   }
>   
>   /**
> diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h
> index 481da2133202b..4353809c69122 100644
> --- a/drivers/dpll/zl3073x/chan.h
> +++ b/drivers/dpll/zl3073x/chan.h
> @@ -17,6 +17,7 @@ struct zl3073x_dev;
>    * @ref_prio: reference priority registers (4 bits per ref, P/N packed)
>    * @mon_status: monitor status register value
>    * @refsel_status: reference selection status register value
> + * @df_offset: frequency offset vs tracked reference in 2^-48 steps
>    */
>   struct zl3073x_chan {
>   	struct_group(cfg,
> @@ -26,6 +27,7 @@ struct zl3073x_chan {
>   	struct_group(stat,
>   		u8	mon_status;
>   		u8	refsel_status;
> +		s64	df_offset;
>   	);
>   };
>   
> @@ -37,6 +39,18 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
>   
>   int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);
>   
> +/**
> + * zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference
> + * @chan: pointer to channel state
> + *
> + * Return: frequency offset in 2^-48 steps
> + */
> +static inline s64
> +zl3073x_chan_df_offset_get(const struct zl3073x_chan *chan)
> +{
> +	return chan->df_offset;
> +}
> +
>   /**
>    * zl3073x_chan_mode_get - get DPLL channel operating mode
>    * @chan: pointer to channel state
> diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
> index 5f1e70f3e40a0..b3345060490db 100644
> --- a/drivers/dpll/zl3073x/core.c
> +++ b/drivers/dpll/zl3073x/core.c
> @@ -704,44 +704,6 @@ zl3073x_ref_freq_meas_update(struct zl3073x_dev *zldev)
>   	return 0;
>   }
>   
> -/**
> - * zl3073x_ref_ffo_update - update reference fractional frequency offsets
> - * @zldev: pointer to zl3073x_dev structure
> - *
> - * The function asks device to latch the latest measured fractional
> - * frequency offset values, reads and stores them into the ref state.
> - *
> - * Return: 0 on success, <0 on error
> - */
> -static int
> -zl3073x_ref_ffo_update(struct zl3073x_dev *zldev)
> -{
> -	int i, rc;
> -
> -	rc = zl3073x_ref_freq_meas_latch(zldev,
> -					 ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF);
> -	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 ppt
> -		 * ffo = (10^12 * value) / 2^32
> -		 * ffo = ( 5^12 * value) / 2^20
> -		 */
> -		zldev->ref[i].ffo = mul_s64_u64_shr(value, 244140625, 20);
> -	}
> -
> -	return 0;
> -}
> -
>   static void
>   zl3073x_dev_periodic_work(struct kthread_work *work)
>   {
> @@ -776,13 +738,6 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
>   		}
>   	}
>   
> -	/* 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/dpll.c b/drivers/dpll/zl3073x/dpll.c
> index f2d430d1a8e7b..af50cd6200001 100644
> --- a/drivers/dpll/zl3073x/dpll.c
> +++ b/drivers/dpll/zl3073x/dpll.c
> @@ -299,8 +299,12 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
>   {
>   	struct zl3073x_dpll_pin *pin = pin_priv;
>   
> -	/* Only rx vs tx symbol rate FFO is supported */
> -	if (dpll)
> +	/* Only nested FFO (pin vs parent DPLL) is supported */
> +	if (!dpll)
> +		return -ENODATA;
> +
> +	/* Report FFO only for the active pin */
> +	if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
>   		return -ENODATA;
>   
>   	*ffo = pin->freq_offset;
> @@ -1733,37 +1737,27 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
>   }
>   
>   /**
> - * zl3073x_dpll_pin_ffo_check - check for pin fractional frequency offset change
> + * zl3073x_dpll_pin_ffo_check - check for FFO change on active pin
>    * @pin: pin to check
>    *
> - * Check for the given pin's fractional frequency change.
> - *
> - * Return: true on fractional frequency offset change, false otherwise
> + * Return: true on 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;
> -	const struct zl3073x_ref *ref;
> -	u8 ref_id;
> +	const struct zl3073x_chan *chan;
>   	s64 ffo;
>   
> -	/* Get reference monitor status */
> -	ref_id = zl3073x_input_pin_ref_get(pin->id);
> -	ref = zl3073x_ref_state_get(zldev, ref_id);
> -
> -	/* Do not report ffo changes if the reference monitor report errors */
> -	if (!zl3073x_ref_is_status_ok(ref))
> +	if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
>   		return false;
>   
> -	/* Compare with previous value */
> -	ffo = zl3073x_ref_ffo_get(ref);
> +	chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
> +	ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan),
> +			      244140625, 36);
> +
>   	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;
>   	}
>   
> diff --git a/drivers/dpll/zl3073x/ref.h b/drivers/dpll/zl3073x/ref.h
> index 55e80e4f08734..e140ca3ea17dc 100644
> --- a/drivers/dpll/zl3073x/ref.h
> +++ b/drivers/dpll/zl3073x/ref.h
> @@ -22,7 +22,6 @@ struct zl3073x_dev;
>    * @freq_ratio_n: FEC mode divisor
>    * @sync_ctrl: reference sync control
>    * @config: reference config
> - * @ffo: current fractional frequency offset
>    * @meas_freq: measured input frequency in Hz
>    * @mon_status: reference monitor status
>    */
> @@ -40,7 +39,6 @@ struct zl3073x_ref {
>   		u8	config;
>   	);
>   	struct_group(stat, /* Status */
> -		s64	ffo;
>   		u32	meas_freq;
>   		u8	mon_status;
>   	);
> @@ -58,18 +56,6 @@ int zl3073x_ref_state_update(struct zl3073x_dev *zldev, u8 index);
>   
>   int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult);
>   
> -/**
> - * zl3073x_ref_ffo_get - get current fractional frequency offset
> - * @ref: pointer to ref state
> - *
> - * Return: the latest measured fractional frequency offset
> - */
> -static inline s64
> -zl3073x_ref_ffo_get(const struct zl3073x_ref *ref)
> -{
> -	return ref->ffo;
> -}
> -
>   /**
>    * zl3073x_ref_meas_freq_get - get measured input frequency
>    * @ref: pointer to ref state
> diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
> index 8015808bdf548..9578f00095282 100644
> --- a/drivers/dpll/zl3073x/regs.h
> +++ b/drivers/dpll/zl3073x/regs.h
> @@ -164,6 +164,11 @@
>   #define ZL_DPLL_MODE_REFSEL_MODE_NCO		4
>   #define ZL_DPLL_MODE_REFSEL_REF			GENMASK(7, 4)
>   
> +#define ZL_REG_DPLL_DF_READ(_idx)					\
> +	ZL_REG_IDX(_idx, 5, 0x28, 1, ZL3073X_MAX_CHANNELS, 1)
> +#define ZL_DPLL_DF_READ_SEM			BIT(4)
> +#define ZL_DPLL_DF_READ_REF_OFST		BIT(3)
> +
>   #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)
> @@ -176,6 +181,16 @@
>   #define ZL_REG_DPLL_PHASE_ERR_DATA(_idx)				\
>   	ZL_REG_IDX(_idx, 5, 0x55, 6, ZL3073X_MAX_CHANNELS, 6)
>   
> +/*******************************
> + * Register Pages 6-7, DPLL Data
> + *******************************/
> +
> +#define ZL_REG_DPLL_DF_OFFSET_03(_idx)					\
> +	ZL_REG_IDX(_idx, 6, 0x00, 6, 4, 0x20)
> +#define ZL_REG_DPLL_DF_OFFSET_4		ZL_REG(7, 0x00, 6)
> +#define ZL_REG_DPLL_DF_OFFSET(_idx)					\
> +	((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4)
> +
>   /***********************************
>    * Register Page 9, Synth and Output
>    ***********************************/
Reviewed-by: Petr Oros <poros@redhat.com>


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

end of thread, other threads:[~2026-05-05 14:34 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-04 15:53 [PATCH net-next v3 0/2] dpll: rework fractional frequency offset reporting Ivan Vecera
2026-05-04 15:53 ` [PATCH net-next v3 1/2] dpll: add fractional frequency offset to pin-parent-device Ivan Vecera
2026-05-05 11:31   ` Jiri Pirko
2026-05-04 15:53 ` [PATCH net-next v3 2/2] dpll: zl3073x: report FFO as DPLL vs input reference offset Ivan Vecera
2026-05-05 14:34   ` Petr Oros

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