From: Ivan Vecera <ivecera@redhat.com>
To: Ali Rouhi <arouhi@sitime.com>, Jiri Pirko <jiri@resnulli.us>
Cc: Vadim Fedorenko <vadim.fedorenko@linux.dev>,
Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Carolina Jubran <cjubran@nvidia.com>,
Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.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: Re: [PATCH net-next v12 10/12] dpll: sit9531x: add support to adjust output phase
Date: Tue, 13 Oct 2026 12:02:01 +0200 [thread overview]
Message-ID: <96179bac-03aa-472a-bffc-51a5252a2089@redhat.com> (raw)
In-Reply-To: <20261009183151.78497-11-arouhi@sitime.com>
On 10/9/26 20:31, Ali Rouhi wrote:
> From: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>
>
> 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. The divider spends two VCO cycles acting on a
> programmed delay before it releases the output, so the registers hold the
> request plus those two cycles and the read-back takes them off again.
>
> The window advertised to the core is one millisecond either way: the
> device holds a delay anywhere within the output period, so on a slow
> output the bound is the signed 32-bit picosecond attribute rather than
> the hardware, and a round figure below it costs nothing and is what
> keeps the subsystem from refusing every request; the granularity is one
> picosecond, because the achievable delays are whole VCO cycles plus
> thirty-picosecond steps and so form no uniform lattice for the core to
> check against.
>
> 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 fold is counted in VCO cycles, since the period is
> exactly the divider's count of them, and the quantizer takes whichever
> of the neighbouring whole cycles with the fine steps lands nearest. An
> advance is held as the complementary delay and reads back as an advance
> only where the period exceeds the advertised window; elsewhere the two
> describe the same edge and the delay form is reported. A delay the
> loaded configuration left beyond the window is reported clamped and is
> not carried into a later rate change. A rate change on an output with a
> programmed delay re-times it inside the rate change's own programming
> window, so there is one sequence and one flush, and a flush that failed
> after the delay was committed is retried by the next request. 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. A PLL the loaded profile builds without the
> phase-flush feature has no flush to fire; realigning its outputs
> restarts the whole PLL, so a phase adjust or a rate change on such a PLL
> is a loss of lock as well as an edge jump on its other outputs.
>
> Suggested-by: Ivan Vecera <ivecera@redhat.com>
> Signed-off-by: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>
> Assisted-by: LLM
> Signed-off-by: Ali Rouhi <arouhi@sitime.com>
> ---
> drivers/dpll/sit9531x/core.c | 662 +++++++++++++++++++++++++++++++++--
> drivers/dpll/sit9531x/core.h | 22 ++
> drivers/dpll/sit9531x/dpll.c | 80 +++++
> drivers/dpll/sit9531x/prop.c | 22 ++
> drivers/dpll/sit9531x/regs.h | 37 ++
> 5 files changed, 798 insertions(+), 25 deletions(-)
>
> diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c
> index 19aeabd3cd4f..8a8872b18d2c 100644
> --- a/drivers/dpll/sit9531x/core.c
> +++ b/drivers/dpll/sit9531x/core.c
> @@ -2264,11 +2264,422 @@ static int sit9531x_output_divo_read(struct sit9531x_dev *sitdev, u8 out_idx,
> return *divo ? 0 : -ENODATA;
> }
>
> +/*
> + * Phase adjust (PRG_RST_DELAY register-based).
> + *
> + * The chip exposes a per-output 34-bit coarse delay measured in VCO
> + * clock periods plus a 3-bit fine delay in fixed 30 ps steps. The
> + * five bytes PROG6..PROG2 hold the field across registers:
> + * base + 0 PROG6 [7:5] OPSTG_VCASC_BUMP (preserved via RMW)
> + * [4:2] PRG_RST_FINE_DELAY
> + * [1:0] PRG_RST_DELAY[33:32]
> + * base + 1 PROG5 PRG_RST_DELAY[31:24]
> + * base + 2 PROG4 PRG_RST_DELAY[23:16]
> + * base + 3 PROG3 PRG_RST_DELAY[15:8]
> + * base + 4 PROG2 PRG_RST_DELAY[7:0]
> + *
> + * 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. Requests are folded
> + * modulo one output period: positive delays wrap naturally and a negative
> + * phase adjustment (advance) is rendered as (T_out - |phase|).
> + */
> +
> +/*
> + * Register of byte @i (PROG6 first) of an output's PRG_RST_DELAY block.
> + *
> + * The logical output index maps 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 and base address the
> + * slot, not the logical index.
> + */
> +static unsigned int sit9531x_output_prg_reg(struct sit9531x_dev *sitdev,
> + u8 out_idx, u8 i)
> +{
> + u8 slot = sitdev->info->clkout_map[out_idx];
> + u8 page = (slot > SIT9531X_PAGE_OUTSYS0_SLOT_MAX) ?
> + SIT9531X_PAGE_OUTSYS1 : SIT9531X_PAGE_OUTSYS0;
> + u8 base = SIT9531X_OUT_PRG_DELAY_BASE +
> + SIT9531X_OUT_PRG_SLOT_STRIDE * (slot % 6);
> +
> + return SIT9531X_REG(page, base + i);
> +}
> +
> +/* Read the PRG_RST_DELAY bytes of an output, PROG6 first. */
> +static int sit9531x_output_phase_bytes_read(struct sit9531x_dev *sitdev,
> + u8 out_idx, u8 *bytes)
> +{
> + int rc;
> + u8 i;
> +
> + for (i = 0; i < SIT9531X_OUT_PRG_BYTES; i++) {
> + rc = sit9531x_read_u8(sitdev,
> + sit9531x_output_prg_reg(sitdev, out_idx, i),
> + &bytes[i]);
> + if (rc)
> + return rc;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Encode a quantized delay into the register bytes. @old_bytes supplies
> + * the PROG6 bits that are not the delay's, which are preserved.
> + *
> + * The register value carries the divider's own settling time: the
> + * quantizer works in the delay the caller asked for, the register wants
> + * that plus the two VCO cycles the divider spends acting on it, and a
> + * request of zero still waits those two. Only the register value carries
> + * them; @coarse stays the requested delay, which is what gets cached.
> + */
> +static void sit9531x_output_phase_bytes_build(const u8 *old_bytes, u64 coarse,
> + u8 fine, u8 *new_bytes)
> +{
> + u64 prg_coarse = coarse + SIT9531X_OUT_PRG_DIVO_CYCLES;
> +
> + /* PROG6 RMW: preserve OPSTG_VCASC_BUMP in [7:5] */
> + new_bytes[0] = old_bytes[0] & SIT9531X_OUT_PRG_OPSTG_MASK;
> + new_bytes[0] |= (fine << SIT9531X_OUT_PRG_FINE_SHIFT) &
> + SIT9531X_OUT_PRG_FINE_MASK;
> + new_bytes[0] |= (u8)((prg_coarse >> 32) &
> + SIT9531X_OUT_PRG_COARSE_HI_MASK);
> + new_bytes[1] = (u8)((prg_coarse >> 24) & 0xFF);
> + new_bytes[2] = (u8)((prg_coarse >> 16) & 0xFF);
> + new_bytes[3] = (u8)((prg_coarse >> 8) & 0xFF);
> + new_bytes[4] = (u8)(prg_coarse & 0xFF);
> +}
> +
> +/*
> + * Write an output's delay bytes. The caller must already be in the
> + * programming state. On a failure every byte is put back, the one whose
> + * write reported the error included, since it may have reached the part.
> + */
> +static int sit9531x_output_phase_bytes_write(struct sit9531x_dev *sitdev,
> + u8 out_idx, const u8 *new_bytes,
> + const u8 *old_bytes)
> +{
> + int rc, ret, rb_rc = 0;
> + u8 i;
> +
> + for (i = 0; i < SIT9531X_OUT_PRG_BYTES; i++) {
> + rc = sit9531x_write_u8(sitdev,
> + sit9531x_output_prg_reg(sitdev, out_idx, i),
> + new_bytes[i]);
> + if (rc)
> + goto rollback;
> + }
> +
> + return 0;
> +
> +rollback:
> + for (i = 0; i < SIT9531X_OUT_PRG_BYTES; i++) {
> + ret = sit9531x_write_u8(sitdev,
> + sit9531x_output_prg_reg(sitdev, out_idx, 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;
> + }
> +
> + return rc;
> +}
> +
> +/*
> + * Error of realizing @abs_ps as @cycles whole VCO cycles plus the fine
> + * steps that come nearest, which are returned through @fine.
> + */
> +static u64 sit9531x_output_phase_quant_err(u64 abs_ps, u64 fvco, u64 cycles,
> + u8 *fine)
> +{
> + u64 cycles_ps, steps = 0;
> +
> + cycles_ps = mul_u64_u64_div_u64(cycles, 1000000000000ULL, fvco);
> + if (abs_ps > cycles_ps)
> + steps = div64_u64(abs_ps - cycles_ps +
> + SIT9531X_OUT_PRG_FINE_STEP_PS / 2,
> + SIT9531X_OUT_PRG_FINE_STEP_PS);
> + *fine = min_t(u64, steps, SIT9531X_OUT_PRG_FINE_MAX);
> +
> + return abs_diff(abs_ps, cycles_ps +
> + (u64)*fine * SIT9531X_OUT_PRG_FINE_STEP_PS);
> +}
> +
> +/*
> + * Quantize a phase request: fold @phase_ps into one output period and
> + * split it into whole VCO cycles (@coarse_out, without the divider's
> + * settling cycles) and fine steps (@fine_out). @phase_adj gets the delay
> + * the two realize, in the request's sign and bounded to the advertised
> + * range, which is what the cache holds. Pure arithmetic against @fvco
> + * and the divider @divo the output runs on, so a caller can encode
> + * before it enters the programming state.
> + */
> +static int sit9531x_output_phase_encode(u64 fvco, u64 divo, s32 phase_ps,
> + u64 *coarse_out, u8 *fine_out,
> + s32 *phase_adj)
> +{
> + u64 abs_ps, cycles, frac_ps, coarse = 0, coarse_ps, t_out_ps;
> + s64 phase_norm_ps = 0;
> + u8 fine = 0;
> +
> + t_out_ps = mul_u64_u64_div_u64(divo, 1000000000000ULL, fvco);
> + if (!t_out_ps)
> + return -EINVAL;
> +
I think you can do this here:
if (!phase_ps) {
*coarse_out = 0;
*fine_out = 0;
*phase_adj = 0;
return 0;
}
and saves CPU cycles consumed by divisions below.
> + /*
> + * 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.
> + *
> + * The period is exactly DIVO VCO cycles, whereas in picoseconds it
> + * is a fraction more often than not: 7812.5 ps at 128 MHz from a
> + * 5.12 GHz VCO, and a fold on the truncated 7812 drops the half
> + * picosecond once per period folded away, so 100 us -- 12800
> + * periods exactly -- would come out as 6400 ps rather than nothing.
> + * Fold the whole VCO cycles of the request modulo DIVO instead, and
> + * carry the sub-cycle part across as it is. 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);
> + cycles = mul_u64_u64_div_u64(abs_ps, fvco, 1000000000000ULL);
> + frac_ps = abs_ps - mul_u64_u64_div_u64(cycles, 1000000000000ULL, fvco);
> + div64_u64_rem(cycles, divo, &cycles);
> + abs_ps = mul_u64_u64_div_u64(cycles, 1000000000000ULL, fvco) + frac_ps;
> + /*
> + * The truncations above can put a request within a picosecond of a
> + * whole period a picosecond past it; that is the same edge as none.
> + */
> + if (abs_ps >= t_out_ps)
> + abs_ps = 0;
> + 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 floor_cycles, err, alt_err;
> + u8 alt_fine;
> +
> + /*
> + * 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.
> + */
> + floor_cycles = mul_u64_u64_div_u64(abs_ps, fvco,
> + 1000000000000ULL);
> +
> + /*
> + * Fine = round((abs_ps - coarse * vco_period_ps) / 30 ps).
> + * The fine field spans 210 ps, more than one VCO cycle in
> + * every band, so the cycle count that floors the request is
> + * not always the nearest encoding: one cycle more can land
> + * closer than any fine code when the remainder sits just
> + * under a cycle, and one cycle fewer with a larger fine code
> + * can land closer or exact -- 210 ps at 5 GHz is seven fine
> + * steps, not a cycle and a ten picosecond remainder. Take
> + * the nearest of the three, the floor on a tie.
> + */
> + coarse = floor_cycles;
> + err = sit9531x_output_phase_quant_err(abs_ps, fvco, coarse,
> + &fine);
> + alt_err = sit9531x_output_phase_quant_err(abs_ps, fvco,
> + floor_cycles + 1,
> + &alt_fine);
> + if (alt_err < err) {
> + coarse = floor_cycles + 1;
> + fine = alt_fine;
> + err = alt_err;
> + }
> + if (floor_cycles) {
> + alt_err = sit9531x_output_phase_quant_err(abs_ps, fvco,
> + floor_cycles - 1,
> + &alt_fine);
> + if (alt_err < err) {
> + coarse = floor_cycles - 1;
> + fine = alt_fine;
> + }
> + }
> +
> + /*
> + * A delay that quantizes to a whole output period or beyond
> + * is the same edge as no delay at all; program none, so the
> + * registers hold no residual past the period and the cache
> + * below describes exactly what they realize.
> + */
> + coarse_ps = mul_u64_u64_div_u64(coarse, 1000000000000ULL, fvco);
> + if (coarse_ps + (u64)fine * SIT9531X_OUT_PRG_FINE_STEP_PS >=
> + t_out_ps) {
> + coarse = 0;
> + fine = 0;
> + }
> +
> + if (coarse + SIT9531X_OUT_PRG_DIVO_CYCLES >=
> + (1ULL << SIT9531X_OUT_PRG_COARSE_BITS))
> + return -ERANGE;
> + }
> +
> + coarse_ps = mul_u64_u64_div_u64(coarse, 1000000000000ULL, fvco);
> + abs_ps = coarse_ps + (u64)fine * SIT9531X_OUT_PRG_FINE_STEP_PS;
> + /*
> + * Quantization can also land a few picoseconds past the end of the
> + * advertised range, which the getter must not report. Bound both
> + * signs to the range; the positive one is also what keeps the cast
> + * to the s32 the ABI carries safe.
> + */
> + if (phase_norm_ps < 0)
> + *phase_adj = abs_ps ?
> + -(s32)min_t(u64, t_out_ps - abs_ps,
> + SIT9531X_OUT_PHASE_ADJ_MAX_PS) : 0;
> + else
> + *phase_adj = (s32)min_t(u64, abs_ps,
> + SIT9531X_OUT_PHASE_ADJ_MAX_PS);
> +
> + *coarse_out = coarse;
> + *fine_out = fine;
> +
> + return 0;
> +}
Thanks,
Ivan
next prev parent reply other threads:[~2026-10-13 10:02 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-09 18:31 [PATCH net-next v12 00/12] dpll: add SiTime SiT9531x DPLL clock driver Ali Rouhi
2026-10-09 18:31 ` [PATCH net-next v12 01/12] dt-bindings: vendor-prefixes: add SiTime Corporation Ali Rouhi
2026-10-09 18:31 ` [PATCH net-next v12 02/12] dt-bindings: dpll: add SiTime SiT95316 clock generator Ali Rouhi
2026-10-09 18:31 ` [PATCH net-next v12 03/12] dpll: add basic SiTime SiT9531x support Ali Rouhi
2026-10-13 9:11 ` Ivan Vecera
2026-10-13 22:36 ` Ali Rouhi
2026-10-09 18:31 ` [PATCH net-next v12 05/12] dpll: sit9531x: register DPLL devices and pins Ali Rouhi
2026-10-13 9:35 ` Ivan Vecera
2026-10-09 18:31 ` [PATCH net-next v12 04/12] dpll: sit9531x: read DPLL types and pin properties from system firmware Ali Rouhi
2026-10-13 9:14 ` Ivan Vecera
2026-10-09 18:31 ` [PATCH net-next v12 06/12] dpll: sit9531x: implement input pin state on a DPLL Ali Rouhi
2026-10-13 9:37 ` Ivan Vecera
2026-10-09 18:31 ` [PATCH net-next v12 07/12] dpll: sit9531x: add support to get and set priority on input pins Ali Rouhi
2026-10-13 9:43 ` Ivan Vecera
2026-10-09 18:31 ` [PATCH net-next v12 08/12] dpll: sit9531x: add support to get and set frequency on pins Ali Rouhi
2026-10-13 9:43 ` Ivan Vecera
2026-10-13 22:36 ` Ali Rouhi
2026-10-09 18:31 ` [PATCH net-next v12 10/12] dpll: sit9531x: add support to adjust output phase Ali Rouhi
2026-10-13 10:02 ` Ivan Vecera [this message]
2026-10-13 22:36 ` Ali Rouhi
2026-10-09 18:31 ` [PATCH net-next v12 09/12] dpll: sit9531x: implement output pin state on a DPLL Ali Rouhi
2026-10-13 9:46 ` Ivan Vecera
2026-10-13 22:36 ` Ali Rouhi
2026-10-09 18:31 ` [PATCH net-next v12 11/12] dpll: sit9531x: add support to get phase offset on the connected input pin Ali Rouhi
2026-10-13 10:10 ` Ivan Vecera
2026-10-09 18:32 ` [PATCH net-next v12 12/12] dpll: sit9531x: model the inter-PLL sync net 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=96179bac-03aa-472a-bffc-51a5252a2089@redhat.com \
--to=ivecera@redhat.com \
--cc=Oleg.Zadorozhnyi@devoxsoftware.com \
--cc=arkadiusz.kubalewski@intel.com \
--cc=arouhi@sitime.com \
--cc=cjubran@nvidia.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--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=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®