Thread (46 messages) 46 messages, 3 authors, 27d ago

Re: [PATCH net-next v8 11/15] dpll: sit9531x: add support to get and set esync on pins

flat view

From: netdev-bot+sashiko@kernel.org
Date: 2026-09-08 12:45:22
Also in: linux-devicetree, lkml

Thank you for your contribution! Sashiko AI review found 7 potential
issue(s) to consider:

Critical: 0 · High: 2 · Medium: 3 · Low: 2

- [High] Embedded-sync frequency is implemented as the output's carrier
  frequency.
- [High] sit9531x_dpll_output_pin_esync_set() is asymmetric and destroys
  the only record of the pin's previous rate.
- [Medium] sit9531x_dpll_output_pin_esync_get() reports esync state from
  an unsynchronized software cache: it returns dpin->esync_freq and a…
- [Medium] The uAPI duty-cycle percentage constant is written verbatim
  into a hardware register field.
- [Medium] The esync enable path chains three independently committed
  hardware transactions with no rollback: sit9531x_output_freq_set()…
- [Low] sit9531x_dpll_output_pin_esync_get() reports esync->pulse = 50
  unconditionally, including when it reports esync->freq == 0 (embedded…
- [Low] The new PROG0 write is wrapped in its own
  prg_enter()/prg_commit() window even though PROG0 lives in the same…

--

dpll: sit9531x: add support to get and set esync on pins

This adds esync_get and esync_set callbacks to the sit9531x output pin ops
and a new core helper that programs the per-output ODRn_PROG0 pulse control
byte.  Only outputs flagged as esync-control accept the operations, the
rest return -EOPNOTSUPP.

No Link: tags are present in the commit header.

A few questions below.
quoted hunk ↗ jump to hunk
diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c
index be033d7cfe7c..c93578f3b750 100644
--- a/drivers/dpll/sit9531x/core.c
+++ b/drivers/dpll/sit9531x/core.c
@@ -1535,6 +1535,57 @@ int sit9531x_clear_notifications(struct sit9531x_dev *sitdev)
 	return 0;
 }
 
+/*
+ * sit9531x_output_pulse_ctrl_set - program per-output PULSE_CTRL byte
+ * @out_idx:	logical output index (translated to chip slot internally)
+ * @pulse_ctrl:	8-bit PULSE_CTRL value (PROG0)
+ *
+ * Writes ODRn_PROG0 on the output page (Page 3 for slots 0..5,
+ * Page 4 for slots 6..11) at offset 0x1B + 16 * (slot % 6).
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+int sit9531x_output_pulse_ctrl_set(struct sit9531x_dev *sitdev,
+				   u8 out_idx, u8 pulse_ctrl)
+{
+	const struct sit9531x_chip_info *info = sitdev->info;
+	u8 slot, page, reg;
+	int rc, ret;
+
+	lockdep_assert_held(&sitdev->multiop_lock);
+
+	if (out_idx >= info->num_outputs)
+		return -EINVAL;
+
+	slot = info->clkout_map[out_idx];
+	page = (slot > SIT9531X_PAGE_OUTSYS0_SLOT_MAX) ?
+		SIT9531X_PAGE_OUTSYS1 : SIT9531X_PAGE_OUTSYS0;
+	reg = SIT9531X_OUT_PROG0_BASE +
+	      SIT9531X_OUT_PRG_SLOT_STRIDE * (slot % 6);
+
+	/*
+	 * PROG0 lives in the output system, so like the DIVO and
+	 * PRG_RST_DELAY writes it only takes effect inside the PRG_CMD
+	 * programming state committed to the NVM shadow.
+	 */
+	rc = sit9531x_prg_enter(sitdev);
+	if (rc)
+		return rc;
+
+	rc = sit9531x_write_u8(sitdev, SIT9531X_REG(page, reg), pulse_ctrl);
[Severity: Medium]
Is any conversion needed between the value passed in as pulse_ctrl and the
hardware field?  The only caller passes SIT9531X_ESYNC_PULSE_DEFAULT, which
is decimal 50, and the same constant is handed to userspace as esync->pulse
in sit9531x_dpll_output_pin_esync_get().  The uAPI unit for that attribute
is percent:

Documentation/netlink/specs/dpll.yaml
        name: esync-pulse
        type: u32
        doc: |
          A ratio of high to low state of a SYNC signal pulse embedded
          into base clock frequency. Value is in percents.

The new regs.h comment in this patch describes the same register as a pulse
count byte for SYSREF / SYNCB modes and defines no field masks, so it looks
like one constant is being used both as a uAPI percentage and as a raw
register value.  Which of the two is it?

Separately, this is an unmasked full-byte write with no read-modify-write.
The neighbouring PROG6 programming in sit9531x_output_phase_adjust_set()
does preserve the upper bits:

	/* PROG6 RMW: preserve OPSTG_VCASC_BUMP in [7:5] */
	rc = sit9531x_read_u8(sitdev, SIT9531X_REG(page, base), &prog6_val);
	...
	prog6_val &= SIT9531X_OUT_PRG_OPSTG_MASK;

Can writing all eight bits of ODRn_PROG0 clear other fields in that byte
that the initial config blob programmed?
quoted hunk ↗ jump to hunk
+
+	/*
+	 * Always leave the PRG_CMD state via prg_commit(), even if the write
+	 * failed, so the output loops are re-locked rather than stranded
+	 * unlocked; keep the first error.
+	 */
+	ret = sit9531x_prg_commit(sitdev);
+	if (ret && !rc)
+		rc = ret;
+
+	return rc;
+}
[Severity: Low]
This isn't a bug, but the comment above states PROG0 shares the PRG_CMD
programming state with the DIVO and PRG_RST_DELAY writes, so could this
byte be written inside the window sit9531x_output_freq_set() already opens?

As written, one call to sit9531x_dpll_output_pin_esync_set() runs three
separate windows:

  sit9531x_output_freq_set()        -> prg_enter()/prg_commit()
  sit9531x_output_pulse_ctrl_set()  -> prg_enter()/prg_commit()
  sit9531x_output_enable()          -> prg_enter()/prg_commit()

and sit9531x_prg_commit() issues UPDATE_NVM, then LOOP_LOCK, then an
unconditional msleep(100), so that is roughly 300 ms of multiop_lock hold
time plus three unlock/relock cycles of the output loops on a live output.
quoted hunk ↗ jump to hunk
 /*
  * sit9531x_ref_state_fetch - read input reference status from hardware
  * @index:	logical input index
diff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c
index c07fb2256510..76d7d7d21d3d 100644
--- a/drivers/dpll/sit9531x/dpll.c
+++ b/drivers/dpll/sit9531x/dpll.c
@@ -25,6 +25,17 @@
 #define SIT9531X_ESYNC_FREQ_10MHZ	10000000ULL
 #define SIT9531X_ESYNC_PULSE_DEFAULT	50
 
+static const struct dpll_pin_frequency sit9531x_esync_ranges[] = {
+	DPLL_PIN_FREQUENCY(0),
+	DPLL_PIN_FREQUENCY(SIT9531X_ESYNC_FREQ_10MHZ),
+};
[Severity: High]
Is the embedded sync frequency here the marker rate or the carrier rate?

The dpll uAPI describes DPLL_A_PIN_ESYNC_FREQUENCY as a signal embedded
into the pin's separately configured base clock:

Documentation/netlink/specs/dpll.yaml
        name: esync-frequency
        type: u64
        doc: |
          Frequency of Embedded SYNC signal. If provided, the pin is configured
          with a SYNC signal embedded into its base clock frequency.

This range advertises 0 and 10 MHz as the acceptable esync frequencies, and
sit9531x_dpll_output_pin_esync_set() then feeds the accepted 10 MHz into
sit9531x_output_freq_set(), which programs the DIVO output divider, i.e.
the base clock of the pad.  A 1 PPS esync request is rejected with -EINVAL.

For comparison, ice_dpll_output_esync_get() treats 10 MHz as the required
base frequency and reports the marker rate separately:

	if (!(p->flags[0] & ICE_AQC_GET_CGU_OUT_CFG_ESYNC_ABILITY) ||
	    p->freq != DPLL_PIN_FREQUENCY_10_MHZ)
		return -EOPNOTSUPP;
	...
		esync->freq = DPLL_PIN_FREQUENCY_1_HZ;

The driver's own binding example also pairs esync-control with a base rate
that esync_set would overwrite:

Documentation/devicetree/bindings/dpll/sitime,sit95316.yaml
    pin@0 {
        reg = <0>;
        label = "clkout0";
        esync-control;
        supported-frequencies-hz = /bits/ 64 <156250000>;
    };

Enabling esync on that pin changes the pad from 156.25 MHz to 10 MHz.  Is
that intended?

The changelog says:

  "On this device that is the same divider programming as a frequency
  change plus the per-output pulse control, so enabling it sets the output
  to 1 Hz and disabling it restores the requested rate."

That does not seem to match the code: the only accepted non-zero value is
10 MHz, not 1 Hz, and the freq == 0 path only calls
sit9531x_output_disable() rather than restoring a rate.  Could the
changelog be adjusted to describe what the code does?
quoted hunk ↗ jump to hunk
+
+static inline bool
+sit9531x_dpll_esync_pin_supported(const struct sit9531x_dpll_pin *dpin)
+{
+	return dpin->esync_control;
+}
+
 static inline bool sit9531x_dpll_is_input_pin(const struct sit9531x_dpll_pin *pin)
 {
 	return pin->dir == DPLL_PIN_DIRECTION_INPUT;
@@ -785,6 +796,92 @@ sit9531x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *pin,
 	return 0;
 }
 
+static int
+sit9531x_dpll_output_pin_esync_get(const struct dpll_pin *pin,
+				   void *pin_priv,
+				   const struct dpll_device *dpll,
+				   void *dpll_priv,
+				   struct dpll_pin_esync *esync,
+				   struct netlink_ext_ack *extack)
+{
+	struct sit9531x_dpll_pin *dpin = pin_priv;
+
+	if (!sit9531x_dpll_esync_pin_supported(dpin))
+		return -EOPNOTSUPP;
+
+	esync->range = sit9531x_esync_ranges;
+	esync->range_num = ARRAY_SIZE(sit9531x_esync_ranges);
+	esync->pulse = SIT9531X_ESYNC_PULSE_DEFAULT;
+	esync->freq = dpin->esync_freq;
[Severity: Medium]
Can dpin->esync_freq go stale here?  Unlike
sit9531x_dpll_output_pin_frequency_get(), this getter does no DIVO or
PROG0 readback, and dpin->esync_freq is written only by esync_set().

Two writers of the same divider leave it inconsistent:

  sit9531x_dpll_output_pin_frequency_set()
     -> sit9531x_output_freq_set(sitdev, dpin->id, actual_pll, frequency)

  sit9531x_dpll_output_pin_state_on_dpll_set(DISCONNECTED)
     -> sit9531x_output_disable(sitdev, dpin->id)

Neither clears dpin->esync_freq, so after pin_set(esync-frequency=10000000)
followed by pin_set(frequency=25000000) a pin dump still advertises an
embedded sync configuration the device is no longer producing.  Note that
dpll_pin_set_from_nlattr() handles DPLL_A_PIN_FREQUENCY and
DPLL_A_PIN_ESYNC_FREQUENCY independently in message order, so both can
arrive in one request.

Userspace also cannot repair the stale value, because dpll_pin_esync_set()
consults the getter first:

drivers/dpll/dpll_netlink.c:dpll_pin_esync_set() {
	...
	if (freq == esync.freq)
		return 0;
	...
}

so a repeat request for the cached value is acked without the driver
callback running.

Also, sit9531x_dpll_pin_register() copies only the capability flag:

	pin->esync_control = props->esync_control;

so an output that the efuse/NVM blob already programmed for esync reads
back as disabled.  Should the getter read the hardware instead?

[Severity: Low]
Should esync->pulse be reported as 0 when esync->freq is 0?  As written a
pin dump on a never-configured esync-control output reports
DPLL_A_PIN_ESYNC_FREQUENCY = 0 together with DPLL_A_PIN_ESYNC_PULSE = 50,
i.e. a 50 percent pulse ratio for a signal that is not present.  Both
ice_dpll_output_esync_get() and zl3073x_dpll_output_pin_esync_get() zero
both fields in the disabled case:

	} else {
		esync->freq = 0;
		esync->pulse = 0;
	}
quoted hunk ↗ jump to hunk
+
+	return 0;
+}
+
+static int
+sit9531x_dpll_output_pin_esync_set(const struct dpll_pin *pin,
+				   void *pin_priv,
+				   const struct dpll_device *dpll,
+				   void *dpll_priv,
+				   u64 freq,
+				   struct netlink_ext_ack *extack)
+{
+	struct sit9531x_dpll_pin *dpin = pin_priv;
+	struct sit9531x_dpll *sitdpll = dpll_priv;
+	struct sit9531x_dev *sitdev = sitdpll->dev;
+	u8 actual_pll;
+	int rc;
+
+	if (!sit9531x_dpll_esync_pin_supported(dpin)) {
+		NL_SET_ERR_MSG(extack,
+			       "Embedded sync not enabled for this pin");
+		return -EOPNOTSUPP;
+	}
+
+	actual_pll = sitdev->out[dpin->id].pll_idx;
+
+	mutex_lock(&sitdev->multiop_lock);
+
+	/*
+	 * This output is a dedicated embedded-sync pin.
+	 * Treat freq=0 as a request to disable the entire output.
+	 */
+	if (!freq) {
+		rc = sit9531x_output_disable(sitdev, dpin->id);
+		if (!rc)
+			dpin->esync_freq = 0;
+		mutex_unlock(&sitdev->multiop_lock);
+		return rc;
+	}
[Severity: High]
Where is the pin's previous output rate saved so this path can restore it?

The enable path below calls sit9531x_output_freq_set(), which reprograms
DIVO and also replaces the cached rate:

drivers/dpll/sit9531x/core.c:sit9531x_output_freq_set() {
	...
	sitdev->out[out_idx].freq = (u32)div64_u64(fvco, divo);
}

and sit9531x_output_disable() touches only the Hi-Z bits:

	/* Take control (MASK=1) and mute (STATE=0) on both DIFF and SE */

Neither struct sit9531x_dpll_pin nor struct sit9531x_out holds the
pre-esync frequency or the previous PROG0 byte, and
sit9531x_output_pulse_ctrl_set() never reads PROG0 back, so after an
enable/disable cycle the rate userspace originally configured appears to be
gone from both hardware and driver state.

Does a following pin_set(state=connected) then un-mute the pad at 10 MHz
with the esync pulse still armed?

drivers/dpll/sit9531x/dpll.c:sit9531x_dpll_output_pin_state_on_dpll_set() {
	case DPLL_PIN_STATE_CONNECTED:
		rc = sit9531x_output_enable(sitdev, dpin->id);
	...
}

while sit9531x_dpll_output_pin_esync_get() reports esync->freq == 0.
quoted hunk ↗ jump to hunk
+
+	if (freq != SIT9531X_ESYNC_FREQ_10MHZ) {
+		mutex_unlock(&sitdev->multiop_lock);
+		NL_SET_ERR_MSG(extack,
+			       "Only 10 MHz esync frequency is supported");
+		return -EINVAL;
+	}
+
+	rc = sit9531x_output_freq_set(sitdev, dpin->id, actual_pll,
+				      SIT9531X_ESYNC_FREQ_10MHZ);
+	/*
+	 * Program the pulse generator (PROG0 PULSE_CTRL) so the embedded-sync
+	 * pulse is actually emitted; without it the output carries the clock
+	 * but no esync marker.  SIT9531X_ESYNC_PULSE_DEFAULT is the same duty
+	 * the esync_get callback advertises.
+	 */
+	if (!rc)
+		rc = sit9531x_output_pulse_ctrl_set(sitdev, dpin->id,
+						    SIT9531X_ESYNC_PULSE_DEFAULT);
+	if (!rc)
+		rc = sit9531x_output_enable(sitdev, dpin->id);
+
+	mutex_unlock(&sitdev->multiop_lock);
+
+	if (!rc)
+		dpin->esync_freq = SIT9531X_ESYNC_FREQ_10MHZ;
+
+	return rc;
+}
[Severity: Medium]
What happens if the second or third step here fails?

Each of the three calls commits independently: sit9531x_output_freq_set()
writes DIVO and runs prg_commit() with UPDATE_NVM plus LOOP_LOCK, and
sit9531x_output_pulse_ctrl_set() opens its own PRG_CMD window, so this is
not one transaction.  If sit9531x_output_pulse_ctrl_set() or
sit9531x_output_enable() returns an I2C or regmap error, the divider change
stays committed and nothing is unwound.

Since dpin->esync_freq is only assigned when all three steps return 0, the
driver then reports esync as disabled on a pin whose carrier has already
been changed to 10 MHz.  Is that state recoverable through this attribute?
dpll_pin_esync_set() short-circuits on the cached value:

	if (freq == esync.freq)
		return 0;

so a follow-up request for freq == 0 matches the stale cached 0 and never
reaches the driver.

There is also a window between the frequency commit and the PROG0 commit
where an already-enabled output emits the new rate with no marker.
 static const struct dpll_pin_ops sit9531x_dpll_output_pin_ops = {
[ ... ]
quoted hunk ↗ jump to hunk
diff --git a/drivers/dpll/sit9531x/regs.h b/drivers/dpll/sit9531x/regs.h
index 91d33abd15c3..251c98dc7cbe 100644
--- a/drivers/dpll/sit9531x/regs.h
+++ b/drivers/dpll/sit9531x/regs.h
@@ -215,6 +215,13 @@
 #define SIT9531X_OUT_PRG_FINE_MAX		7	/* 3-bit field */
 #define SIT9531X_OUT_PRG_COARSE_BITS		34
 
+/*
+ * Per-output pulse-count control byte used in SYSREF / SYNCB modes.
+ * Slot N within a page sits at 0x1B + 16 * (slot % 6).  Same page
+ * mapping as PRG_RST_DELAY: slots 0-5 on Page 3, slots 6-11 on Page 4.
+ */
+#define SIT9531X_OUT_PROG0_BASE		0x1B
The comment says this byte applies in SYSREF / SYNCB modes.  The new esync
path does not select either mode, so does the write take effect at all in
the mode the output is left in?

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260902214030.20955-1-arouhi%40sitime.com
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help