[PATCH v10 10/14] dpll: sit9531x: add support to adjust output phase

From: Ali Rouhi

Date: Mon Sep 21 2026 - 16:31:34 EST


From: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>

Shift an output in time against the others driven by the same PLL. The
device has a coarse delay counted in VCO cycles and a three-bit fine field
in fixed thirty-picosecond steps, so a requested offset is split between
the two and what the core reads back is what the registers hold rather
than what was asked for.

Delay only ever advances, so an offset larger than one output period is
folded back into a single period -- for a periodic signal that is the same
phase. The write takes effect in the programming state, which is left
with the loops re-locked even when a write inside it failed.

The device has no per-output phase flush, so realigning the adjusted
output restarts the divider phase of every output that PLL drives. On a
part where outputs are deliberately skewed against each other that is a
visible edge jump on the others, and there is no register that would let
the driver avoid it.

Suggested-by: Ivan Vecera <ivecera@xxxxxxxxxx>
Signed-off-by: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>
Assisted-by: Claude:claude-4-opus [chat]
Signed-off-by: Ali Rouhi <arouhi@xxxxxxxxxx>
---

Notes:
Changes in v10:
Read the programmed delay back from the device: decoded at probe, so a
delay the profile carries can be cleared, and re-read when a request
could not be confirmed.

A rate change whose re-timing failed is reported as the rate change it
was, with the re-timing warned about.

Said in the changelog that the device has no per-output phase flush, so
realigning one output restarts the divider phase of all of them.

Folded the three-way sign handling into one abs() and a remainder, and
skipped the coarse and fine encoding for a zero offset, both as Ivan
Vecera suggested.

drivers/dpll/sit9531x/core.c | 341 ++++++++++++++++++++++++++++++++++-
drivers/dpll/sit9531x/core.h | 14 ++
drivers/dpll/sit9531x/dpll.c | 78 ++++++++
drivers/dpll/sit9531x/prop.c | 19 ++
drivers/dpll/sit9531x/regs.h | 28 +++
5 files changed, 477 insertions(+), 3 deletions(-)

diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c
index 8daf0fbf6772..c8c3cd6a64ba 100644
--- a/drivers/dpll/sit9531x/core.c
+++ b/drivers/dpll/sit9531x/core.c
@@ -1811,6 +1811,69 @@ static int sit9531x_output_divo_write(struct sit9531x_dev *sitdev, u8 out_idx,
return rc;
}

+/**
+ * sit9531x_output_phase_read - read an output's programmed delay back
+ * @sitdev: device pointer
+ * @out_idx: logical output index
+ * @phase_ps: result in picoseconds, always a delay (never an advance)
+ *
+ * The delay the chip holds is part of the profile it loads before probe,
+ * and a rate or phase request that failed after its writes reached the
+ * device leaves the cache describing something else. Decoding the five
+ * PRG_RST_DELAY bytes is the only way to say what the output is really
+ * doing. The registers carry an unsigned delay, so a request that was
+ * made as an advance reads back as the equivalent delay.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int sit9531x_output_phase_read(struct sit9531x_dev *sitdev, u8 out_idx,
+ s32 *phase_ps)
+{
+ const struct sit9531x_chip_info *info = sitdev->info;
+ u8 bytes[5], page, base, slot, fine, i;
+ u64 coarse = 0, fvco, ps;
+ int rc;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (out_idx >= info->num_outputs)
+ return -EINVAL;
+
+ rc = sit9531x_get_fvco(sitdev, sitdev->out[out_idx].pll_idx, &fvco);
+ if (rc)
+ return rc == -ENODATA ? -ENODEV : rc;
+
+ slot = info->clkout_map[out_idx];
+ page = (slot > SIT9531X_PAGE_OUTSYS0_SLOT_MAX) ?
+ SIT9531X_PAGE_OUTSYS1 : SIT9531X_PAGE_OUTSYS0;
+ base = SIT9531X_OUT_PRG_DELAY_BASE +
+ SIT9531X_OUT_PRG_SLOT_STRIDE * (slot % 6);
+
+ for (i = 0; i < ARRAY_SIZE(bytes); i++) {
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG(page, base + i),
+ &bytes[i]);
+ if (rc)
+ return rc;
+ }
+
+ fine = (bytes[0] & SIT9531X_OUT_PRG_FINE_MASK) >>
+ SIT9531X_OUT_PRG_FINE_SHIFT;
+ coarse = (u64)(bytes[0] & SIT9531X_OUT_PRG_COARSE_HI_MASK) << 32;
+ coarse |= (u64)bytes[1] << 24;
+ coarse |= (u64)bytes[2] << 16;
+ coarse |= (u64)bytes[3] << 8;
+ coarse |= bytes[4];
+
+ ps = mul_u64_u64_div_u64(coarse, 1000000000000ULL, fvco);
+ ps += (u64)fine * SIT9531X_OUT_PRG_FINE_STEP_PS;
+
+ *phase_ps = (s32)min_t(u64, ps, S32_MAX);
+
+ return 0;
+}
+
int sit9531x_output_freq_set(struct sit9531x_dev *sitdev, u8 out_idx,
u8 pll_idx, u64 frequency)
{
@@ -1864,7 +1927,41 @@ int sit9531x_output_freq_set(struct sit9531x_dev *sitdev, u8 out_idx,

sitdev->out[out_idx].freq = div64_u64(fvco, divo);

- return 0;
+ /*
+ * The programmed reset delay counts VCO cycles against the output
+ * period in force when it was written, so a rate change silently
+ * re-times a previously requested phase adjust. Re-encode the
+ * cached picosecond request against the new rate.
+ *
+ * Keyed off whether a delay was ever programmed rather than off the
+ * cached value: quantization can leave a whole period in the
+ * registers, which is the same phase and caches as zero, and that
+ * still has to be re-timed when the period changes.
+ */
+ if (sitdev->out[out_idx].phase_armed) {
+ s32 phase_ps = sitdev->out[out_idx].phase_adj;
+ int ph_rc;
+
+ /*
+ * The rate is already programmed and latched at this point.
+ * Failing the request for a re-timing that did not take
+ * would report a frequency set that did not happen, and the
+ * core drops an identical retry because it asks the driver
+ * for the current rate first -- which is the new one. Say
+ * what went wrong and mark the delay for a read-back
+ * instead.
+ */
+ ph_rc = sit9531x_output_phase_adjust_set(sitdev, out_idx,
+ phase_ps);
+ if (ph_rc) {
+ sitdev->out[out_idx].phase_stale = true;
+ dev_warn(sitdev->dev,
+ "out%u: rate changed but the phase adjust was not re-timed (%d)\n",
+ out_idx, ph_rc);
+ }
+ }
+
+ return rc;
}

/*
@@ -1946,14 +2043,227 @@ int sit9531x_output_freq_get(struct sit9531x_dev *sitdev, u8 out_idx,
* base + 3 PROG3 PRG_RST_DELAY[15:8]
* base + 4 PROG2 PRG_RST_DELAY[7:0]
*
- * Outputs 0-5 live on Page 3, outputs 6-11 on Page 4, with each
- * output's block at base = 0x15 + 16 * (out_idx % 6).
+ * Slots 0-5 live on Page 3, slots 6-11 on Page 4, with each slot's
+ * block at base = 0x15 + 16 * (slot % 6); the slot is the physical
+ * output position from clkout_map[], not the logical output index.
*
* The chip only supports unsigned positive delay. A negative phase
* adjustment (advance) is wrapped to (T_out - |phase|) modulo one
* output period, which is identical for a periodic signal.
*/

+int sit9531x_output_phase_adjust_set(struct sit9531x_dev *sitdev,
+ u8 out_idx, s32 phase_ps)
+{
+ const struct sit9531x_chip_info *info = sitdev->info;
+ u64 abs_ps, fvco, coarse = 0, coarse_ps, t_out_ps;
+ s64 phase_norm_ps = 0;
+ u8 page, base, prog6_val, fine = 0;
+ u8 old_bytes[5], new_bytes[5], i;
+ u8 pll_idx, slot;
+ u64 freq;
+ int rc, ret, rb_rc;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (out_idx >= info->num_outputs)
+ return -EINVAL;
+
+ pll_idx = sitdev->out[out_idx].pll_idx;
+ if (pll_idx >= SIT9531X_NUM_PLLS)
+ return -EINVAL;
+
+ freq = sitdev->out[out_idx].freq;
+ if (!freq) {
+ /*
+ * The cache is only seeded by a DT frequency list or an
+ * earlier get/set; a board without supported-frequencies-hz
+ * would otherwise get -EINVAL on every phase request forever.
+ * Read the effective rate back from the divider chain.
+ */
+ rc = sit9531x_output_freq_get(sitdev, out_idx, &freq);
+ if (rc)
+ return rc;
+ if (!freq)
+ return -EINVAL;
+ }
+
+ rc = sit9531x_get_fvco(sitdev, pll_idx, &fvco);
+ if (rc)
+ return rc == -ENODATA ? -ENODEV : rc;
+
+ t_out_ps = div64_u64(1000000000000ULL, freq);
+ if (!t_out_ps)
+ return -EINVAL;
+
+ /*
+ * Convert to unsigned absolute delay. Both signs are folded
+ * modulo one period: positive delays wrap naturally, negative
+ * delays are rendered as T_out - |phase|. abs() is safe here
+ * because the core rejects anything outside the advertised phase
+ * range, which is +/-1 ms. div64_u64_rem() rather than the %
+ * operator: a 64-bit modulo has no compiler helper on 32-bit
+ * targets and leaves the module with an undefined __umoddi3.
+ */
+ abs_ps = abs(phase_ps);
+ div64_u64_rem(abs_ps, t_out_ps, &abs_ps);
+ phase_norm_ps = phase_ps < 0 ? -(s64)abs_ps : (s64)abs_ps;
+ abs_ps = (phase_ps < 0 && abs_ps) ? t_out_ps - abs_ps : abs_ps;
+
+ if (abs_ps) {
+ u64 rem_ps;
+
+ /*
+ * coarse_cycles = abs_ps * Fvco / 1e12 ps/s.
+ * mul_u64_u64_div_u64() avoids overflow when abs_ps approaches
+ * one second of 1 PPS wrap-around.
+ */
+ coarse = mul_u64_u64_div_u64(abs_ps, fvco, 1000000000000ULL);
+ if (coarse >= (1ULL << SIT9531X_OUT_PRG_COARSE_BITS))
+ return -ERANGE;
+
+ /*
+ * Fine delay = round((abs_ps - coarse * vco_period_ps) / 30 ps)
+ */
+ coarse_ps = mul_u64_u64_div_u64(coarse, 1000000000000ULL, fvco);
+ rem_ps = (abs_ps > coarse_ps) ? (abs_ps - coarse_ps) : 0;
+ if (rem_ps) {
+ u64 steps;
+
+ steps = div64_u64(rem_ps +
+ SIT9531X_OUT_PRG_FINE_STEP_PS / 2,
+ SIT9531X_OUT_PRG_FINE_STEP_PS);
+ if (steps > SIT9531X_OUT_PRG_FINE_MAX)
+ steps = SIT9531X_OUT_PRG_FINE_MAX;
+ fine = (u8)steps;
+ }
+ }
+
+ /*
+ * Map logical output index to the chip's physical output slot.
+ * On SiT95317 the eight logical outputs land on chip slots
+ * {0, 3, 4, 5, 7, 8, 9, 11}; on SiT95316 the map is identity.
+ * Page/base must address the slot, not the logical index.
+ */
+ slot = info->clkout_map[out_idx];
+ page = (slot > SIT9531X_PAGE_OUTSYS0_SLOT_MAX) ?
+ SIT9531X_PAGE_OUTSYS1 : SIT9531X_PAGE_OUTSYS0;
+ base = SIT9531X_OUT_PRG_DELAY_BASE +
+ SIT9531X_OUT_PRG_SLOT_STRIDE * (slot % 6);
+
+ /*
+ * The PRG_RST_DELAY bytes live in the output system, so the writes
+ * only take effect when made inside the PRG_CMD programming state and
+ * committed to the NVM shadow, exactly like sit9531x_output_freq_set().
+ */
+ rc = sit9531x_prg_enter(sitdev);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < ARRAY_SIZE(old_bytes); i++) {
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG(page, base + i),
+ &old_bytes[i]);
+ if (rc)
+ goto commit;
+ }
+
+ /* PROG6 RMW: preserve OPSTG_VCASC_BUMP in [7:5] */
+ prog6_val = old_bytes[0] & SIT9531X_OUT_PRG_OPSTG_MASK;
+ prog6_val |= (fine << SIT9531X_OUT_PRG_FINE_SHIFT) &
+ SIT9531X_OUT_PRG_FINE_MASK;
+ prog6_val |= (u8)((coarse >> 32) & SIT9531X_OUT_PRG_COARSE_HI_MASK);
+
+ new_bytes[0] = prog6_val;
+ new_bytes[1] = (u8)((coarse >> 24) & 0xFF);
+ new_bytes[2] = (u8)((coarse >> 16) & 0xFF);
+ new_bytes[3] = (u8)((coarse >> 8) & 0xFF);
+ new_bytes[4] = (u8)(coarse & 0xFF);
+
+ for (i = 0; i < ARRAY_SIZE(new_bytes); i++) {
+ rc = sit9531x_write_u8(sitdev,
+ SIT9531X_REG(page, base + i),
+ new_bytes[i]);
+ if (rc)
+ goto rollback;
+ }
+
+ goto commit;
+
+rollback:
+ rb_rc = 0;
+ for (i = 0; i < ARRAY_SIZE(old_bytes); i++) {
+ ret = sit9531x_write_u8(sitdev,
+ SIT9531X_REG(page, base + i),
+ old_bytes[i]);
+ if (ret && !rb_rc)
+ rb_rc = ret;
+ }
+ if (rb_rc) {
+ dev_err(sitdev->dev,
+ "out%u: phase-adjust rollback failed (%d), the delay registers are part old and part new\n",
+ out_idx, rb_rc);
+ if (!rc)
+ rc = rb_rc;
+ }
+
+commit:
+ /*
+ * Always leave the PRG_CMD state via prg_commit(), even on a
+ * mid-sequence write failure, 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;
+ if (rc)
+ return rc;
+
+ /*
+ * Restart the output divider phase so the freshly programmed delay is
+ * applied against a known edge instead of the divider's arbitrary
+ * running phase.
+ */
+ rc = sit9531x_output_phase_flush(sitdev, pll_idx);
+ if (rc)
+ return rc;
+
+ /*
+ * Cache what the registers realize, and only once every step has
+ * succeeded: the core drops a repeated request with the same value,
+ * so a cache updated by a failed call would make the retry a no-op.
+ *
+ * Quantizing to whole VCO cycles plus 30 ps steps can land a few
+ * picoseconds past the end of the period, which would wrap the
+ * subtraction below; one period is the most a delay can be.
+ */
+ coarse_ps = mul_u64_u64_div_u64(coarse, 1000000000000ULL, fvco);
+ abs_ps = coarse_ps + (u64)fine * SIT9531X_OUT_PRG_FINE_STEP_PS;
+ if (abs_ps > t_out_ps)
+ abs_ps = t_out_ps;
+ if (phase_norm_ps < 0)
+ sitdev->out[out_idx].phase_adj =
+ abs_ps ? -(s32)(t_out_ps - abs_ps) : 0;
+ else
+ /*
+ * The cache is an s32 because that is what the ABI carries.
+ * A delay is bounded by the output period, which on a slow
+ * output is wider than that, so bound the cast. The negative
+ * branch above needs no bound: what it stores is the advance
+ * that was asked for, and that came in as an s32.
+ */
+ sitdev->out[out_idx].phase_adj = (s32)min(abs_ps,
+ (u64)S32_MAX);
+
+ /*
+ * Record that a delay is programmed whatever it quantized to. A
+ * request that lands on a whole period caches as zero, and the rate
+ * change that follows still has to re-time what the registers hold.
+ */
+ sitdev->out[out_idx].phase_armed = true;
+
+ return 0;
+}
+
/*
* sit9531x_clear_notifications - clear all notification registers
*
@@ -2368,12 +2678,37 @@ static int sit9531x_dev_state_fetch(struct sit9531x_dev *sitdev)
}

for (i = 0; i < sitdev->info->num_outputs; i++) {
+ s32 phase_ps;
+
rc = sit9531x_out_state_fetch(sitdev, i);
if (rc) {
dev_err(sitdev->dev,
"Failed to fetch output %u state: %d\n", i, rc);
return rc;
}
+
+ /*
+ * The delay registers are part of the profile the chip loads
+ * before probe, so an output can already carry one. Seeding
+ * the cache from the device is what lets a request of 0 ps
+ * clear it: the core drops a request equal to what the
+ * getter reports, and a cache that started at zero would
+ * make clearing a programmed delay impossible. An output
+ * the configuration does not route has no Fvco to decode
+ * against, which is not an error here.
+ */
+ mutex_lock(&sitdev->multiop_lock);
+ rc = sit9531x_output_phase_read(sitdev, i, &phase_ps);
+ mutex_unlock(&sitdev->multiop_lock);
+ if (!rc) {
+ sitdev->out[i].phase_adj = phase_ps;
+ sitdev->out[i].phase_armed = !!phase_ps;
+ } else if (rc != -ENODEV) {
+ dev_err(sitdev->dev,
+ "Failed to read output %u delay: %d\n",
+ i, rc);
+ return rc;
+ }
}

for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
diff --git a/drivers/dpll/sit9531x/core.h b/drivers/dpll/sit9531x/core.h
index 3b35bf1c4cd5..7848ac9bd6ca 100644
--- a/drivers/dpll/sit9531x/core.h
+++ b/drivers/dpll/sit9531x/core.h
@@ -102,6 +102,13 @@ struct sit9531x_ref {
* @routed: output is mapped to @pll_idx by the initial
* configuration; an unrouted output has no DPLL pin
* @pll_idx: PLL driving this output (0-3)
+ * @phase_stale: the programmed delay may differ from @phase_adj
+ * @phase_armed: a phase adjust has been programmed, so a rate
+ * change has to re-time it even when it quantized
+ * to zero
+ * @phase_adj: phase adjust the delay registers actually realize,
+ * i.e. the last request quantized to whole VCO cycles
+ * plus 30 ps fine steps, in the request's sign
* @label: board label from DT or default
*/
struct sit9531x_out {
@@ -111,6 +118,9 @@ struct sit9531x_out {
bool state_stale;
bool routed;
u8 pll_idx;
+ s32 phase_adj;
+ bool phase_armed;
+ bool phase_stale;
const char *label;
};

@@ -268,6 +278,10 @@ int sit9531x_output_freq_get(struct sit9531x_dev *sitdev, u8 out_idx,
u64 *frequency);

/* ---- Output phase adjust (PRG_RST_DELAY register-based) ---- */
+int sit9531x_output_phase_read(struct sit9531x_dev *sitdev, u8 out_idx,
+ s32 *phase_ps);
+int sit9531x_output_phase_adjust_set(struct sit9531x_dev *sitdev,
+ u8 out_idx, s32 phase_ps);

/* ---- Notification clear ---- */
int sit9531x_clear_notifications(struct sit9531x_dev *sitdev);
diff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c
index c1ab202bf297..af1089f192b6 100644
--- a/drivers/dpll/sit9531x/dpll.c
+++ b/drivers/dpll/sit9531x/dpll.c
@@ -868,12 +868,90 @@ sit9531x_dpll_output_pin_state_on_dpll_set(const struct dpll_pin *pin,
return rc;
}

+/*
+ * sit9531x_dpll_output_pin_phase_adjust_get - read output phase adjustment
+ *
+ * Returns what the delay registers hold, i.e. the value
+ * sit9531x_output_phase_adjust_set() programmed after quantization, read
+ * from the cache unless a failed request left it unconfirmed.
+ */
+static int
+sit9531x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, s32 *phase_adjust,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ int rc;
+
+ mutex_lock(&sitdev->multiop_lock);
+ /*
+ * A request whose writes reached the device but whose commit or
+ * phase flush failed left the cache describing the delay before it.
+ * There is no poll of the delay registers to correct that, so read
+ * them here rather than report a value the output is not using.
+ */
+ if (sitdev->out[dpin->id].phase_stale) {
+ s32 phase_ps;
+
+ rc = sit9531x_output_phase_read(sitdev, dpin->id, &phase_ps);
+ if (rc) {
+ mutex_unlock(&sitdev->multiop_lock);
+ NL_SET_ERR_MSG(extack,
+ "Output delay could not be read back");
+ return rc;
+ }
+ sitdev->out[dpin->id].phase_adj = phase_ps;
+ sitdev->out[dpin->id].phase_armed = !!phase_ps;
+ sitdev->out[dpin->id].phase_stale = false;
+ }
+ *phase_adjust = sit9531x_out_state_get(sitdev, dpin->id)->phase_adj;
+ mutex_unlock(&sitdev->multiop_lock);
+
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_output_pin_phase_adjust_set - set output phase adjustment
+ *
+ * Programs the per-output PRG_RST_DELAY registers for deterministic
+ * phase offset; see sit9531x_output_phase_adjust_set() in core.c.
+ */
+static int
+sit9531x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, s32 phase_adjust,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ int rc;
+
+ mutex_lock(&sitdev->multiop_lock);
+ rc = sit9531x_output_phase_adjust_set(sitdev, dpin->id, phase_adjust);
+ mutex_unlock(&sitdev->multiop_lock);
+
+ if (rc) {
+ NL_SET_ERR_MSG(extack, "Phase adjust failed");
+ return rc;
+ }
+
+ return 0;
+}
+
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,
.frequency_set = sit9531x_dpll_output_pin_frequency_set,
.state_on_dpll_get = sit9531x_dpll_output_pin_state_on_dpll_get,
.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,
};

const struct dpll_pin_ops *
diff --git a/drivers/dpll/sit9531x/prop.c b/drivers/dpll/sit9531x/prop.c
index 8270b8ee91be..82e8cd2266f5 100644
--- a/drivers/dpll/sit9531x/prop.c
+++ b/drivers/dpll/sit9531x/prop.c
@@ -228,6 +228,25 @@ sit9531x_pin_props_get(struct sit9531x_dev *sitdev,
props->dpll_props.capabilities =
DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;
curr_freq = sitdev->out[index].freq;
+
+ /*
+ * Allow phase-adjust over a +/-1 ms window. The subsystem
+ * rejects pin_set(phase-adjust, X) when X falls outside
+ * [min, max], so leaving these at 0 silently blocks every
+ * netlink call. 1 ms is well beyond the DCO dynamic range
+ * but costs nothing. Only outputs get a range: input pins
+ * have no .phase_adjust_set, and advertising one there would
+ * promise userspace something every set would refuse.
+ */
+ /* +/-1 ms, in ps */
+ props->dpll_props.phase_range.min = -1000000000;
+ props->dpll_props.phase_range.max = 1000000000;
+ /*
+ * The fine step is 30 ps, but requests are accepted at 1 ps
+ * resolution and rounded to the nearest achievable delay, so
+ * advertise the request granularity, not the hardware step.
+ */
+ props->dpll_props.phase_gran = 1;
}

/* Generate package label */
diff --git a/drivers/dpll/sit9531x/regs.h b/drivers/dpll/sit9531x/regs.h
index 8ce048e9c8f1..cbce62404c97 100644
--- a/drivers/dpll/sit9531x/regs.h
+++ b/drivers/dpll/sit9531x/regs.h
@@ -194,6 +194,34 @@
#define SIT9531X_DEBUG_UNLOCK_VAL 0xC3
#define SIT9531X_DEBUG_LOCK_VAL 0x00

+/*
+ * Per-output programmable phase delay: 34-bit coarse (in VCO clock
+ * cycles) plus a 3-bit fine field with fixed 30 ps steps. Each output
+ * has a five-byte block PROG6..PROG2:
+ *
+ * base + 0 PROG6 [7:5] OPSTG_VCASC_BUMP (preserve via RMW)
+ * [4:2] PRG_RST_FINE_DELAY[2:0]
+ * [1:0] PRG_RST_DELAY[33:32]
+ * base + 1 PROG5 [7:0] PRG_RST_DELAY[31:24]
+ * base + 2 PROG4 [7:0] PRG_RST_DELAY[23:16]
+ * base + 3 PROG3 [7:0] PRG_RST_DELAY[15:8]
+ * base + 4 PROG2 [7:0] PRG_RST_DELAY[7:0]
+ *
+ * Slots 0-5 are on Page 3, slots 6-11 on Page 4. The block base
+ * within a page is 0x15 + 16 * (slot % 6), where slot is the physical
+ * output slot from clkout_map[], not the logical output index.
+ */
+#define SIT9531X_OUT_PRG_DELAY_BASE 0x15
+#define SIT9531X_OUT_PRG_SLOT_STRIDE 0x10
+/* bits [7:5], preserve */
+#define SIT9531X_OUT_PRG_OPSTG_MASK 0xE0
+#define SIT9531X_OUT_PRG_FINE_SHIFT 2
+#define SIT9531X_OUT_PRG_FINE_MASK 0x1C /* bits [4:2] */
+#define SIT9531X_OUT_PRG_COARSE_HI_MASK 0x03 /* bits [1:0] */
+#define SIT9531X_OUT_PRG_FINE_STEP_PS 30
+#define SIT9531X_OUT_PRG_FINE_MAX 7 /* 3-bit field */
+#define SIT9531X_OUT_PRG_COARSE_BITS 34
+
/*
* 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