mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Ali Rouhi <arouhi@sitime.com>
To: "jiri@resnulli.us" <jiri@resnulli.us>
Cc: "vadim.fedorenko@linux.dev" <vadim.fedorenko@linux.dev>,
	"arkadiusz.kubalewski@intel.com" <arkadiusz.kubalewski@intel.com>,
	"ivecera@redhat.com" <ivecera@redhat.com>,
	"kuba@kernel.org" <kuba@kernel.org>,
	"pabeni@redhat.com" <pabeni@redhat.com>,
	"robh@kernel.org" <robh@kernel.org>,
	"krzk+dt@kernel.org" <krzk+dt@kernel.org>,
	"conor+dt@kernel.org" <conor+dt@kernel.org>,
	"cjubran@nvidia.com" <cjubran@nvidia.com>,
	"Oleg.Zadorozhnyi@devoxsoftware.com"
	<Oleg.Zadorozhnyi@devoxsoftware.com>,
	"prabhakar.mahadev-lad.rj@bp.renesas.com"
	<prabhakar.mahadev-lad.rj@bp.renesas.com>,
	"devicetree@vger.kernel.org" <devicetree@vger.kernel.org>,
	"netdev@vger.kernel.org" <netdev@vger.kernel.org>,
	"linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>
Subject: [PATCH net-next v15 12/13] dpll: sit9531x: add support to get phase offset on the connected input pin
Date: Tue, 10 Nov 2026 20:26:16 +0000	[thread overview]
Message-ID: <20261110202607.55744-13-arouhi@sitime.com> (raw)
In-Reply-To: <20261110202607.55744-1-arouhi@sitime.com>

From: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>

Report the phase difference between a PLL's reference and the PLL, read
from the on-chip time-to-digital converter through the debug window as
the documented procedure does. A read latches a sample and returns the
previous one, so each sample is read three times.

Only the input the part feeds the PLL has an offset. Any other pin, a PLL
without a reference, or a PLL that drives no enabled output returns
-ENODATA and the core leaves the attribute out.

Signed-off-by: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>
Signed-off-by: Ali Rouhi <arouhi@sitime.com>
---
 drivers/dpll/sit9531x/core.c | 357 +++++++++++++++++++++++++++++------
 drivers/dpll/sit9531x/core.h |  13 ++
 drivers/dpll/sit9531x/dpll.c | 120 ++++++++++++
 drivers/dpll/sit9531x/regs.h |  19 ++
 4 files changed, 456 insertions(+), 53 deletions(-)

diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c
index 9896d5aa40f5..8f305cc27737 100644
--- a/drivers/dpll/sit9531x/core.c
+++ b/drivers/dpll/sit9531x/core.c
@@ -262,6 +262,28 @@ static int sit9531x_update_pll_close(struct sit9531x_dev *sitdev, u8 pll_idx,
 	return rc;
 }
 
+/*
+ * Restore the debug taps a phase-offset read changed. The PLL page's debug
+ * window must already be open.
+ */
+static int sit9531x_tap_restore(struct sit9531x_dev *sitdev, u8 pll_idx)
+{
+	struct sit9531x_chan *chan = &sitdev->chan[pll_idx];
+	int rc;
+
+	rc = sit9531x_write_pll_close(sitdev, pll_idx,
+				      SIT9531X_PLL_REG_DBG_READ_CODE,
+				      chan->tap_read_code);
+	if (!rc)
+		rc = sit9531x_write_pll_close(sitdev, pll_idx,
+					      SIT9531X_PLL_REG_DBG_WRITE_CODE,
+					      chan->tap_write_code);
+	if (!rc)
+		chan->tap_pending = false;
+
+	return rc;
+}
+
 static void sit9531x_input_get_regs(const struct sit9531x_dev *sitdev,
 				    u8 index,
 				    unsigned int *force_reg,
@@ -785,12 +807,16 @@ static int sit9531x_prio_prg_commit(struct sit9531x_dev *sitdev)
 	return 0;
 }
 
-/*
- * Four registers carry a nibble per lane: loss of signal and the fine and
- * coarse frequency-drift flags. Caller must hold sitdev->multiop_lock, or run
- * before registration.
+/**
+ * sit9531x_input_mon_fetch - read the clock monitor status of every lane
+ * @sitdev: device pointer
+ *
+ * One nibble per lane: loss of signal, fine and coarse frequency drift.
+ * Caller must hold sitdev->multiop_lock, or run before registration.
+ *
+ * Return: 0 on success, <0 on error
  */
-static int sit9531x_input_mon_fetch(struct sit9531x_dev *sitdev)
+int sit9531x_input_mon_fetch(struct sit9531x_dev *sitdev)
 {
 	static const unsigned int regs[] = {
 		SIT9531X_CLKMON_P_STATUS_01, SIT9531X_CLKMON_P_STATUS_23,
@@ -2559,6 +2585,171 @@ static int sit9531x_clear_notifications(struct sit9531x_dev *sitdev)
 	return 0;
 }
 
+/**
+ * sit9531x_phase_offset_read - read phase difference via TDC
+ * @sitdev: device pointer
+ * @pll_idx: PLL index (0-3)
+ * @phase_offset: phase difference in 1/DPLL_PHASE_OFFSET_DIVIDER ps
+ *
+ * The TDC code is a 35-bit magnitude with a separate sign bit;
+ * phase_diff = tdc_code / fvco. Caller must hold sitdev->multiop_lock.
+ *
+ * Return: 0 on success, -ENODATA if Fvco is unknown, <0 on other errors
+ */
+int sit9531x_phase_offset_read(struct sit9531x_dev *sitdev, u8 pll_idx,
+			       s64 *phase_offset)
+{
+	u8 v, old_write_code, old_read_code;
+	struct sit9531x_chan *chan;
+	bool have_old = false;
+	int rc, ret, i;
+	s64 tdc_signed;
+	u64 fvco, mag;
+	u64 tdc_raw;
+	bool sign;
+
+	lockdep_assert_held(&sitdev->multiop_lock);
+
+	if (pll_idx >= SIT9531X_NUM_PLLS)
+		return -EINVAL;
+	chan = &sitdev->chan[pll_idx];
+
+	/*
+	 * Read Fvco first; without it there is no reason to open the debug
+	 * window.
+	 */
+	rc = sit9531x_get_fvco(sitdev, pll_idx, &fvco);
+	if (rc) {
+		if (rc == -ENODATA)
+			dev_dbg(sitdev->dev,
+				"PLL%c: Fvco unknown, skip TDC\n",
+				'A' + pll_idx);
+		return rc;
+	}
+
+	rc = sit9531x_write_pll_u8(sitdev, pll_idx,
+				   SIT9531X_PLL_REG_DEBUG,
+				   SIT9531X_PLL_DEBUG_UNLOCK);
+	if (rc)
+		goto relock;
+
+	/* Restore pending taps before saving the selection */
+	if (chan->tap_pending) {
+		rc = sit9531x_tap_restore(sitdev, pll_idx);
+		if (rc)
+			goto relock;
+	}
+
+	/* Save the tap selection; the debug key does not cover the mux. */
+	rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+				  SIT9531X_PLL_REG_DBG_WRITE_CODE,
+				  &old_write_code);
+	if (!rc)
+		rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+					  SIT9531X_PLL_REG_DBG_READ_CODE,
+					  &old_read_code);
+	if (rc)
+		goto relock;
+
+	have_old = true;
+
+	/*
+	 * Enable the debug clock for taps below 200 kHz, then select the TDC
+	 * tap. The modifier register also holds fields of other taps.
+	 */
+	rc = sit9531x_update_pll_u8(sitdev, pll_idx,
+				    SIT9531X_PLL_REG_DBG_WRITE_CODE,
+				    SIT9531X_DBG_LOW_FREQ_CLK_BIT,
+				    SIT9531X_DBG_LOW_FREQ_CLK_BIT);
+	if (rc)
+		goto relock;
+	rc = sit9531x_write_pll_u8(sitdev, pll_idx,
+				   SIT9531X_PLL_REG_DBG_READ_CODE,
+				   SIT9531X_DBG_READ_CODE_TDC);
+	if (rc)
+		goto relock;
+
+	/*
+	 * A status data read latches a sample and returns the previous one;
+	 * read three times as the documented procedure does.
+	 */
+	for (i = 0; i < SIT9531X_DBG_STATUS_READS; i++) {
+		rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+					  SIT9531X_PLL_REG_DBG_STATUS_DATA,
+					  &v);
+		if (rc)
+			goto relock;
+	}
+
+	tdc_raw = 0;
+
+	rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+				  SIT9531X_PLL_REG_DBG_DATA_4, &v);
+	if (rc)
+		goto relock;
+	sign = !!(v & BIT(SIT9531X_TDC_SIGN_BIT));
+	tdc_raw = (u64)(v & SIT9531X_TDC_MAG_HI_MASK) << 32;
+
+	rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+				  SIT9531X_PLL_REG_DBG_DATA_3, &v);
+	if (rc)
+		goto relock;
+	tdc_raw |= (u64)v << 24;
+
+	rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+				  SIT9531X_PLL_REG_DBG_DATA_2, &v);
+	if (rc)
+		goto relock;
+	tdc_raw |= (u64)v << 16;
+
+	rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+				  SIT9531X_PLL_REG_DBG_DATA_1, &v);
+	if (rc)
+		goto relock;
+	tdc_raw |= (u64)v << 8;
+
+	rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+				  SIT9531X_PLL_REG_DBG_DATA_0, &v);
+	if (rc)
+		goto relock;
+	tdc_raw |= v;
+
+	/* Per the register map the sign bit set means a positive offset. */
+	tdc_signed = sign ? (s64)tdc_raw : -(s64)tdc_raw;
+
+	/*
+	 * phase_diff (ps) = tdc_code * 1e12 / fvco. Scale by the subsystem's
+	 * divider before dividing to keep the fractional digits; one code is
+	 * 203.45 ps at 4.9152 GHz.
+	 */
+	mag = mul_u64_u64_div_u64(tdc_signed < 0 ? -tdc_signed : tdc_signed,
+				  1000000000000ULL * DPLL_PHASE_OFFSET_DIVIDER,
+				  fvco);
+	*phase_offset = tdc_signed < 0 ? -(s64)mag : (s64)mag;
+
+	rc = 0;
+
+relock:
+	if (have_old) {
+		chan->tap_read_code = old_read_code;
+		chan->tap_write_code = old_write_code;
+		ret = sit9531x_tap_restore(sitdev, pll_idx);
+		if (ret)
+			chan->tap_pending = true;
+		if (ret && !rc)
+			rc = ret;
+	}
+
+	ret = sit9531x_write_pll_close(sitdev, pll_idx,
+				       SIT9531X_PLL_REG_DEBUG,
+				       SIT9531X_PLL_DEBUG_LOCK);
+	chan->key_pending = !!ret;
+	if (ret && !rc)
+		rc = ret;
+
+	return rc;
+}
+
 /* Read the lane's receiver state from the Page 0x02 force and state bits */
 static int sit9531x_ref_state_fetch(struct sit9531x_dev *sitdev, u8 index)
 {
@@ -2689,13 +2880,20 @@ static int sit9531x_chan_select_fetch(struct sit9531x_dev *sitdev, u8 pll_idx)
 	return 0;
 }
 
-/*
- * Read the reference the device feeds a PLL from the debug status bus; the
- * active selection does not change on a fallback. The caller rules out free-run
- * first and must hold sitdev->multiop_lock.
+/**
+ * sit9531x_chan_routed_ref_read - read the reference the device feeds a PLL
+ * @sitdev: device pointer
+ * @pll_idx: PLL index (0-3)
+ * @ref: logical input index, SIT9531X_INTSYNC_PIN_ID, or SIT9531X_REF_INVALID
+ *
+ * The device falls back between listed sources without changing the active
+ * selection, so read the source from the debug status bus. The caller rules
+ * out free-run first and must hold multiop_lock.
+ *
+ * Return: 0 on success, <0 on error
  */
-static int sit9531x_chan_routed_ref_read(struct sit9531x_dev *sitdev,
-					 u8 pll_idx, u8 *ref)
+int sit9531x_chan_routed_ref_read(struct sit9531x_dev *sitdev, u8 pll_idx,
+				  u8 *ref)
 {
 	int rc, lock_rc;
 	u8 v, i;
@@ -3189,10 +3387,32 @@ static void sit9531x_table_back_retry(struct sit9531x_dev *sitdev, u8 pll_idx)
 		 'A' + pll_idx);
 }
 
+/*
+ * Restore the debug taps and close the debug window after a failed phase-offset
+ * read. Caller must hold sitdev->multiop_lock.
+ */
+static void sit9531x_tap_back_retry(struct sit9531x_dev *sitdev, u8 pll_idx)
+{
+	struct sit9531x_chan *chan = &sitdev->chan[pll_idx];
+
+	if (!chan->tap_pending && !chan->key_pending)
+		return;
+
+	if (chan->tap_pending &&
+	    !sit9531x_write_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_DEBUG,
+				   SIT9531X_PLL_DEBUG_UNLOCK))
+		sit9531x_tap_restore(sitdev, pll_idx);
+
+	chan->key_pending = !!sit9531x_write_pll_close(sitdev, pll_idx,
+						     SIT9531X_PLL_REG_DEBUG,
+						     SIT9531X_PLL_DEBUG_LOCK);
+}
+
 /*
  * Rewrite the priority table when a listed source returns to a PLL without a
- * reference; the latch restarts acquisition. Caller must hold
- * sitdev->multiop_lock.
+ * reference; the write latches the active selection again. Each latch restarts
+ * acquisition, so the table is written once per change of the best live source.
+ * Caller must hold sitdev->multiop_lock.
  */
 static void sit9531x_reselect(struct sit9531x_dev *sitdev, u8 pll_idx)
 {
@@ -3273,6 +3493,7 @@ sit9531x_dev_chan_states_update(struct sit9531x_dev *sitdev)
 	for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
 		sit9531x_table_back_retry(sitdev, i);
 		sit9531x_ho_release_retry(sitdev, i);
+		sit9531x_tap_back_retry(sitdev, i);
 		/*
 		 * A flush pulse sent into open loops may be lost when they
 		 * re-lock.
@@ -3525,55 +3746,30 @@ static void sit9531x_pin_nodes_check(struct sit9531x_dev *sitdev)
 	fwnode_handle_put(pins);
 }
 
-static int sit9531x_dev_start(struct sit9531x_dev *sitdev)
+/* Retry the pending debug tap restore and debug window close */
+static void sit9531x_tap_pending_retry(struct sit9531x_dev *sitdev)
 {
-	struct sit9531x_dpll *sitdpll;
-	int rc;
-
-	rc = sit9531x_dev_state_fetch(sitdev);
-	if (rc)
-		return rc;
+	u8 i;
 
 	mutex_lock(&sitdev->multiop_lock);
-	sit9531x_eeprom_state_report(sitdev);
-	sit9531x_pll_states_report(sitdev);
-	mutex_unlock(&sitdev->multiop_lock);
-
-	sit9531x_pin_nodes_check(sitdev);
-
-	list_for_each_entry(sitdpll, &sitdev->dplls, list) {
-		rc = sit9531x_dpll_register(sitdpll);
-		if (rc) {
-			dev_err_probe(sitdev->dev, rc,
-				      "Failed to register DPLL%u\n",
-				      sitdpll->id);
-			goto err_unregister;
-		}
+	for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
+		sit9531x_tap_back_retry(sitdev, i);
+		if (sitdev->chan[i].tap_pending || sitdev->chan[i].key_pending)
+			dev_warn(sitdev->dev,
+				 "PLL%c left with its debug taps moved or window open\n",
+				 'A' + i);
 	}
-
-	kthread_queue_delayed_work(sitdev->kworker, &sitdev->work, 0);
-
-	return 0;
-
-err_unregister:
-	list_for_each_entry_continue_reverse(sitdpll, &sitdev->dplls, list)
-		sit9531x_dpll_unregister(sitdpll);
-
-	return rc;
+	mutex_unlock(&sitdev->multiop_lock);
 }
 
-static void sit9531x_dev_stop(struct sit9531x_dev *sitdev)
+/*
+ * Retry every pending write once more and report what is still pending. Call
+ * with the DPLLs unregistered.
+ */
+static void sit9531x_dev_pending_retry(struct sit9531x_dev *sitdev)
 {
-	struct sit9531x_dpll *sitdpll;
 	u8 i;
 
-	kthread_cancel_delayed_work_sync(&sitdev->work);
-
-	list_for_each_entry(sitdpll, &sitdev->dplls, list) {
-		if (sitdpll->dpll_dev)
-			sit9531x_dpll_unregister(sitdpll);
-	}
-
 	/*
 	 * Write back tables first: forced holdover stays set while a table
 	 * write-back is pending.
@@ -3637,6 +3833,61 @@ static void sit9531x_dev_stop(struct sit9531x_dev *sitdev)
 				 'A' + i);
 	}
 	mutex_unlock(&sitdev->multiop_lock);
+
+	sit9531x_tap_pending_retry(sitdev);
+}
+
+static int sit9531x_dev_start(struct sit9531x_dev *sitdev)
+{
+	struct sit9531x_dpll *sitdpll;
+	int rc;
+
+	rc = sit9531x_dev_state_fetch(sitdev);
+	if (rc)
+		return rc;
+
+	mutex_lock(&sitdev->multiop_lock);
+	sit9531x_eeprom_state_report(sitdev);
+	sit9531x_pll_states_report(sitdev);
+	mutex_unlock(&sitdev->multiop_lock);
+
+	sit9531x_pin_nodes_check(sitdev);
+
+	list_for_each_entry(sitdpll, &sitdev->dplls, list) {
+		rc = sit9531x_dpll_register(sitdpll);
+		if (rc) {
+			dev_err_probe(sitdev->dev, rc,
+				      "Failed to register DPLL%u\n",
+				      sitdpll->id);
+			goto err_unregister;
+		}
+	}
+
+	kthread_queue_delayed_work(sitdev->kworker, &sitdev->work, 0);
+
+	return 0;
+
+err_unregister:
+	list_for_each_entry_continue_reverse(sitdpll, &sitdev->dplls, list)
+		sit9531x_dpll_unregister(sitdpll);
+
+	sit9531x_dev_pending_retry(sitdev);
+
+	return rc;
+}
+
+static void sit9531x_dev_stop(struct sit9531x_dev *sitdev)
+{
+	struct sit9531x_dpll *sitdpll;
+
+	kthread_cancel_delayed_work_sync(&sitdev->work);
+
+	list_for_each_entry(sitdpll, &sitdev->dplls, list) {
+		if (sitdpll->dpll_dev)
+			sit9531x_dpll_unregister(sitdpll);
+	}
+
+	sit9531x_dev_pending_retry(sitdev);
 }
 
 static struct sit9531x_dpll_pin *
diff --git a/drivers/dpll/sit9531x/core.h b/drivers/dpll/sit9531x/core.h
index bbf5026f945d..8718c463c560 100644
--- a/drivers/dpll/sit9531x/core.h
+++ b/drivers/dpll/sit9531x/core.h
@@ -165,6 +165,10 @@ struct sit9531x_out {
  *   stays in forced holdover
  * @table_back: table reported and to be rewritten while @table_pending
  * @tap_pending: debug taps not restored after a phase-offset read
+ * @tap_write_code: tap selection to restore while @tap_pending
+ * @tap_read_code: readback selection to restore while @tap_pending
+ * @key_pending: debug key not locked again after a phase-offset read or tap
+ *   retry
  * @prio_srcs: cached priority table, a source code per slot
  * @prio_last: first slot of each source plus one, 0 if absent
  * @prio_mask: bit per canonical source in the priority table read back
@@ -194,6 +198,9 @@ struct sit9531x_chan {
 	bool		table_pending;
 	u8		table_back[SIT9531X_PRIO_MAX_SLOTS];
 	bool		tap_pending;
+	u8		tap_write_code;
+	u8		tap_read_code;
+	bool		key_pending;
 	u8		prio_srcs[SIT9531X_PRIO_MAX_SLOTS];
 	u8		prio_last[SIT9531X_PRIO_NUM_SRC];
 	u16		prio_mask;
@@ -311,6 +318,12 @@ int sit9531x_output_phase_read(struct sit9531x_dev *sitdev, u8 out_idx,
 int sit9531x_output_phase_adjust_set(struct sit9531x_dev *sitdev,
 				     u8 out_idx, s32 phase_ps);
 
+int sit9531x_chan_routed_ref_read(struct sit9531x_dev *sitdev, u8 pll_idx,
+				  u8 *ref);
+int sit9531x_input_mon_fetch(struct sit9531x_dev *sitdev);
+int sit9531x_phase_offset_read(struct sit9531x_dev *sitdev, u8 pll_idx,
+			       s64 *phase_offset);
+
 static inline u8 sit9531x_pll_page(u8 pll_idx)
 {
 	return SIT9531X_PAGE_PLLA + pll_idx;
diff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c
index e6271ebd47ac..ba4ac5e7d365 100644
--- a/drivers/dpll/sit9531x/dpll.c
+++ b/drivers/dpll/sit9531x/dpll.c
@@ -488,6 +488,122 @@ sit9531x_dpll_input_pin_prio_set(const struct dpll_pin *pin, void *pin_priv,
 	return 0;
 }
 
+/*
+ * Report the loop's residual phase error, sampled with the loop closed. The TDC
+ * compares against a signal the PLL drives, so a PLL driving no output returns
+ * -ENODATA.
+ */
+static int
+sit9531x_dpll_input_pin_phase_offset_get(const struct dpll_pin *pin,
+					 void *pin_priv,
+					 const struct dpll_device *dpll,
+					 void *dpll_priv, s64 *phase_offset,
+					 struct netlink_ext_ack *extack)
+{
+	struct sit9531x_dpll_pin *dpin = pin_priv;
+	struct sit9531x_dpll *sitdpll = dpll_priv;
+	struct sit9531x_dev *sitdev = sitdpll->dev;
+	bool drives = false;
+	u8 routed, i;
+	s64 offset;
+	int rc;
+
+	mutex_lock(&sitdev->multiop_lock);
+
+	/*
+	 * Check the cache first: the core calls this for every pin of every
+	 * DPLL in a dump.
+	 */
+	if (!sit9531x_dpll_selection_active(sitdev, sitdpll, dpin->id)) {
+		mutex_unlock(&sitdev->multiop_lock);
+		return -ENODATA;
+	}
+
+	/*
+	 * The device can change the routed reference, so read it before and
+	 * after the sample. A PLL with no routed, enabled output has no
+	 * measurement.
+	 */
+	for (i = 0; i < sitdev->info->num_outputs; i++) {
+		if (!sitdev->out[i].routed ||
+		    sitdev->out[i].pll_idx != sitdpll->id)
+			continue;
+		if (sitdev->out[i].state_stale) {
+			rc = sit9531x_output_state_refresh(sitdev, i);
+			if (rc) {
+				mutex_unlock(&sitdev->multiop_lock);
+				NL_SET_ERR_MSG(extack,
+					       "Output mute state could not be read back");
+				return rc;
+			}
+		}
+		if (sitdev->out[i].enabled)
+			drives = true;
+	}
+
+	if (!drives) {
+		mutex_unlock(&sitdev->multiop_lock);
+		return -ENODATA;
+	}
+
+	/*
+	 * Read the monitors live: on loss of signal the device falls back
+	 * without a request.
+	 */
+	rc = sit9531x_input_mon_fetch(sitdev);
+	if (rc) {
+		mutex_unlock(&sitdev->multiop_lock);
+		NL_SET_ERR_MSG(extack,
+			       "Failed to read the input clock monitors");
+		return rc;
+	}
+
+	if (!sit9531x_dpll_selection_active(sitdev, sitdpll, dpin->id)) {
+		mutex_unlock(&sitdev->multiop_lock);
+		return -ENODATA;
+	}
+
+	rc = sit9531x_chan_routed_ref_read(sitdev, sitdpll->id, &routed);
+	if (!rc && routed == dpin->id) {
+		rc = sit9531x_phase_offset_read(sitdev, sitdpll->id, &offset);
+		if (!rc)
+			rc = sit9531x_chan_routed_ref_read(sitdev, sitdpll->id,
+							   &routed);
+		if (!rc && routed != dpin->id)
+			rc = -ENODATA;
+	} else if (!rc) {
+		rc = -ENODATA;
+	}
+
+	/*
+	 * A reference lost during the sample can leave the routed reference
+	 * unchanged while the PLL is in holdover; read the monitors again.
+	 */
+	if (!rc) {
+		rc = sit9531x_input_mon_fetch(sitdev);
+		if (rc) {
+			mutex_unlock(&sitdev->multiop_lock);
+			NL_SET_ERR_MSG(extack,
+				       "Failed to read the input clock monitors");
+			return rc;
+		}
+		if (dpin->id < sitdev->info->num_inputs &&
+		    sit9531x_ref_state_get(sitdev, dpin->id)->los)
+			rc = -ENODATA;
+	}
+	mutex_unlock(&sitdev->multiop_lock);
+
+	if (rc == -ENODATA)
+		return rc;
+	if (rc) {
+		NL_SET_ERR_MSG(extack, "TDC phase readback failed");
+		return rc;
+	}
+
+	*phase_offset = offset;
+	return 0;
+}
+
 static const struct dpll_pin_ops sit9531x_dpll_input_pin_ops = {
 	.direction_get		= sit9531x_dpll_input_pin_direction_get,
 	.frequency_get		= sit9531x_dpll_input_pin_frequency_get,
@@ -496,6 +612,7 @@ static const struct dpll_pin_ops sit9531x_dpll_input_pin_ops = {
 	.operstate_on_dpll_get	= sit9531x_dpll_input_pin_operstate_on_dpll_get,
 	.prio_get		= sit9531x_dpll_input_pin_prio_get,
 	.prio_set		= sit9531x_dpll_input_pin_prio_set,
+	.phase_offset_get	= sit9531x_dpll_input_pin_phase_offset_get,
 };
 
 /*
@@ -509,6 +626,7 @@ static const struct dpll_pin_ops sit9531x_dpll_input_pin_norate_ops = {
 	.operstate_on_dpll_get	= sit9531x_dpll_input_pin_operstate_on_dpll_get,
 	.prio_get		= sit9531x_dpll_input_pin_prio_get,
 	.prio_set		= sit9531x_dpll_input_pin_prio_set,
+	.phase_offset_get	= sit9531x_dpll_input_pin_phase_offset_get,
 };
 
 /*
@@ -520,12 +638,14 @@ static const struct dpll_pin_ops sit9531x_dpll_input_pin_manual_ops = {
 	.frequency_get		= sit9531x_dpll_input_pin_frequency_get,
 	.state_on_dpll_get	= sit9531x_dpll_input_pin_state_on_dpll_get,
 	.operstate_on_dpll_get	= sit9531x_dpll_input_pin_operstate_on_dpll_get,
+	.phase_offset_get	= sit9531x_dpll_input_pin_phase_offset_get,
 };
 
 static const struct dpll_pin_ops sit9531x_dpll_input_pin_norate_manual_ops = {
 	.direction_get		= sit9531x_dpll_input_pin_direction_get,
 	.state_on_dpll_get	= sit9531x_dpll_input_pin_state_on_dpll_get,
 	.operstate_on_dpll_get	= sit9531x_dpll_input_pin_operstate_on_dpll_get,
+	.phase_offset_get	= sit9531x_dpll_input_pin_phase_offset_get,
 };
 
 /*
diff --git a/drivers/dpll/sit9531x/regs.h b/drivers/dpll/sit9531x/regs.h
index 8c76609b99c5..d6c6b4a6a6bb 100644
--- a/drivers/dpll/sit9531x/regs.h
+++ b/drivers/dpll/sit9531x/regs.h
@@ -229,6 +229,25 @@
 #define SIT9531X_PLL_REG_DIVN_NUM		0x32  /* 4 bytes (0x32-0x35) */
 #define SIT9531X_PLL_REG_DIVN_DEN		0x38  /* 4 bytes (0x38-0x3B) */
 
+/*
+ * Signal pathway debug readback: Dig_Sys_ReadCode selects the tap,
+ * Dig_Sys_WriteCode its modifiers; a read of the debug status data register
+ * latches a sample into Dig_Sys_read7..read0.
+ */
+#define SIT9531X_PLL_REG_DBG_READ_CODE	0xB3
+#define SIT9531X_PLL_REG_DBG_WRITE_CODE	0xB4
+#define SIT9531X_DBG_LOW_FREQ_CLK_BIT	BIT(7)
+#define SIT9531X_PLL_REG_DBG_DATA_0		0xB5  /* [7:0] */
+#define SIT9531X_PLL_REG_DBG_DATA_1		0xB6  /* [15:8] */
+#define SIT9531X_PLL_REG_DBG_DATA_2		0xB7  /* [23:16] */
+#define SIT9531X_PLL_REG_DBG_DATA_3		0xB8  /* [31:24] */
+#define SIT9531X_PLL_REG_DBG_DATA_4		0xB9  /* [34:32] + sign */
+
+/* Read code of the TDC phase tap, and the sign bit of its sample */
+#define SIT9531X_DBG_READ_CODE_TDC		69
+#define SIT9531X_TDC_SIGN_BIT		3
+#define SIT9531X_TDC_MAG_HI_MASK	GENMASK(2, 0)
+
 /* DIVN is carried as fixed point, in steps of 1e-12 of a whole divider */
 #define SIT9531X_DIVN_SCALE		1000000000000ULL
 
-- 
2.39.2 (Apple Git-143)


  parent reply	other threads:[~2026-11-10 20:26 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-10 20:26 [PATCH net-next v15 00/13] dpll: add SiTime SiT9531x DPLL clock driver Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 01/13] dpll: omit pin phase attributes the driver has no value for Ali Rouhi
2026-11-11 15:35   ` Ivan Vecera
2026-11-10 20:26 ` [PATCH net-next v15 02/13] dt-bindings: vendor-prefixes: add SiTime Corporation Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 04/13] dpll: add basic SiTime SiT9531x support Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 03/13] dt-bindings: dpll: add SiTime SiT95316 clock generator Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 06/13] dpll: sit9531x: register DPLL devices and pins Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 05/13] dpll: sit9531x: read DPLL types and pin properties from system firmware Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 07/13] dpll: sit9531x: implement input pin state on a DPLL Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 08/13] dpll: sit9531x: add support to get and set priority on input pins Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 09/13] dpll: sit9531x: add support to get and set frequency on pins Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 10/13] dpll: sit9531x: implement output pin state on a DPLL Ali Rouhi
2026-11-10 20:26 ` Ali Rouhi [this message]
2026-11-10 20:26 ` [PATCH net-next v15 11/13] dpll: sit9531x: add support to adjust output phase Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 13/13] dpll: sit9531x: model the inter-PLL sync line as a pair of pins Ali Rouhi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261110202607.55744-13-arouhi@sitime.com \
    --to=arouhi@sitime.com \
    --cc=Oleg.Zadorozhnyi@devoxsoftware.com \
    --cc=arkadiusz.kubalewski@intel.com \
    --cc=cjubran@nvidia.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=ivecera@redhat.com \
    --cc=jiri@resnulli.us \
    --cc=krzk+dt@kernel.org \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=prabhakar.mahadev-lad.rj@bp.renesas.com \
    --cc=robh@kernel.org \
    --cc=vadim.fedorenko@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®