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

From: Ali Rouhi

Date: Wed Sep 02 2026 - 17:55:55 EST


From: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>

Embedded sync marks a one-pulse-per-second boundary inside a higher-rate
output by widening one pulse. 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.

Only outputs the firmware describes as esync-controllable offer it; on the
rest the operations are refused rather than silently reprogramming an
output whose board wiring does not expect it. The pulse width is fixed at
half the period, which is what the device produces, and the core is told
the supported range rather than left to guess.

Signed-off-by: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>
Assisted-by: Claude:claude-4-opus [chat]
Signed-off-by: Ali Rouhi <arouhi@xxxxxxxxxx>
---
drivers/dpll/sit9531x/core.c | 51 +++++++++++++++++++
drivers/dpll/sit9531x/dpll.c | 99 ++++++++++++++++++++++++++++++++++++
drivers/dpll/sit9531x/regs.h | 7 +++
3 files changed, 157 insertions(+)

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);
+
+ /*
+ * 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;
+}
+
/*
* 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),
+};
+
+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;
+
+ 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;
+ }
+
+ 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;
+}
+
static const struct dpll_pin_ops sit9531x_dpll_output_pin_ops = {
.direction_get = sit9531x_dpll_output_pin_direction_get,
.frequency_get = sit9531x_dpll_output_pin_frequency_get,
@@ -793,6 +890,8 @@ static const struct dpll_pin_ops sit9531x_dpll_output_pin_ops = {
.state_on_dpll_set = sit9531x_dpll_output_pin_state_on_dpll_set,
.phase_adjust_get = sit9531x_dpll_output_pin_phase_adjust_get,
.phase_adjust_set = sit9531x_dpll_output_pin_phase_adjust_set,
+ .esync_get = sit9531x_dpll_output_pin_esync_get,
+ .esync_set = sit9531x_dpll_output_pin_esync_set,
};

const struct dpll_pin_ops *
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
+
/*
* On-demand phase-flush fired from a register rather than a GPIO pin.
* DIVO_PHASE_SEL_REG selects the in-register trigger source and
--
2.43.0