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 08/12] dpll: sit9531x: add support to get and set frequency on pins
Date: Tue, 13 Oct 2026 11:43:22 +0200 [thread overview]
Message-ID: <0051c3dd-6abd-4ff1-a125-7cace9fac6fb@redhat.com> (raw)
In-Reply-To: <20261009183151.78497-9-arouhi@sitime.com>
On 10/9/26 20:31, Ali Rouhi wrote:
> From: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>
>
> Both directions in one patch, since they share everything that matters.
>
> An input's frequency is what the board presents, so it is reported from
> the firmware description rather than read back: the chip has no divider on
> an input whose rate it merely qualifies. An input the firmware gives no
> rate for has no frequency attribute at all rather than a zero, because
> the core abandons the whole pin dump on an error from one pin.
>
> An output's frequency is the VCO divided by that output's divider, so it
> is computed from the divider read back from the chip and set by writing
> a new one. The VCO in turn comes from the feedback divider, which is
> why the crystal rate is needed at probe. That rate is trusted only
> inside the band the PLL's VCO runs in: below the low band the divider is
> not programmed and the PLL reports no data, and a request against a rate
> outside the band is refused rather than programmed, since a divider
> computed from a rate the output is not at would be reported as success.
> A divider write only takes effect inside the programming state, and that
> state has to be left with the output loops re-locked whatever happened
> in between, so the exit runs even when a write in the middle failed and
> the first error is the one returned.
>
> Programming a divider costs about a hundred milliseconds under the device
> lock: a dozen or so register transactions, then the settling time the part
> requires after the loop-lock command, which is a property of the hardware
> rather than a conservative guess. The DPLL core holds its own lock across
> the whole callback, so a frequency set on this device delays netlink
> traffic for every DPLL in the system for that long. Splitting the wait
> out would need the ops to complete asynchronously, which the interface
> does not offer; issuing the commit without waiting would let the next
> request program a part that has not settled. A rate change is a
> configuration action, not something a running system does per packet, so
> the cost is paid where it is visible rather than hidden behind a
> completion the caller cannot wait for.
>
> The phase flush that follows a divider write realigns every output fed by
> that PLL, not only the one that changed. The flush is a per-PLL function
> in the device and there is no per-output equivalent, so an output whose
> rate is set while its siblings are running will step their phase too.
>
> The flush's own error paths restore what they changed: a failed source
> select puts the original source back, a sibling is marked parked before
> it is cleared so a failure cannot leave it unparked, and a failed arm
> disarms rather than unparks.
>
> 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 | 805 +++++++++++++++++++++++++++++++++++
> drivers/dpll/sit9531x/core.h | 7 +
> drivers/dpll/sit9531x/dpll.c | 120 ++++++
> drivers/dpll/sit9531x/prop.c | 29 +-
> drivers/dpll/sit9531x/regs.h | 21 +
> 5 files changed, 978 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c
> index 79520640dfc8..ea15830b0efa 100644
> --- a/drivers/dpll/sit9531x/core.c
> +++ b/drivers/dpll/sit9531x/core.c
> @@ -488,6 +488,140 @@ static int sit9531x_output_forced_hiz(struct sit9531x_dev *sitdev,
> muted);
> }
>
> +/* Attempts to re-lock the output loops before reporting them open. */
> +#define SIT9531X_LOOP_LOCK_TRIES 3
> +
> +/*
> + * sit9531x_prg_abort - leave the programming state without committing
> + *
> + * Entering the state is two writes, and the second can fail with the debug
> + * block already unlocked and the part possibly already in PRG_CMD. There
> + * is nothing to commit in that case, but the loops still have to be closed
> + * and the debug key put back, which is otherwise only done by
> + * sit9531x_prg_commit().
> + */
> +static void sit9531x_prg_abort(struct sit9531x_dev *sitdev)
> +{
> + u8 attempt;
> + int rc = -EIO;
> +
> + for (attempt = 0; attempt < SIT9531X_LOOP_LOCK_TRIES; attempt++) {
> + rc = sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN,
> + SIT9531X_LOOP_LOCK);
> + if (!rc)
> + break;
> + usleep_range(1000, 2000);
> + }
> + if (rc)
> + dev_err(sitdev->dev,
> + "output loops left unlocked after a failed entry: %d\n",
> + rc);
> +
> + sit9531x_write_u8(sitdev, SIT9531X_REG_OUTSYS_DEBUG,
> + SIT9531X_DEBUG_LOCK_VAL);
> +}
> +
> +/*
> + * Enter the output-system programming state: unlock the debug
> + * registers on Page 3 and issue the PRG_CMD state command. Register
> + * writes that reconfigure the output system only take effect when
> + * they are made inside this state.
> + */
> +static int sit9531x_prg_enter(struct sit9531x_dev *sitdev)
> +{
> + int rc;
> +
> + rc = sit9531x_write_u8(sitdev, SIT9531X_REG_OUTSYS_DEBUG,
> + SIT9531X_DEBUG_UNLOCK_VAL);
> + if (rc)
> + return rc;
> +
> + rc = sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN,
> + SIT9531X_PRG_CMD_STATE);
> + if (rc) {
> + /*
> + * The debug block is unlocked at this point, and a transfer
> + * that reported an error may still have reached the part --
> + * which would leave the device in PRG_CMD with its output
> + * loops open. Callers skip the commit when the entry
> + * fails, so close both here.
> + */
> + sit9531x_prg_abort(sitdev);
> + return rc;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Commit a programming sequence started by sit9531x_prg_enter():
> + * update the NVM shadow and re-lock the loops. The sleep gives the
> + * hardware its required settling time after the loop-lock command;
> + * it is intentional despite the caller holding multiop_lock, as the
> + * whole NVM + lock sequence must be atomic.
> + */
> +static int sit9531x_prg_commit(struct sit9531x_dev *sitdev)
> +{
> + int rc, rc2 = 0, rc3;
> + u8 attempt;
> +
> + rc = sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN,
> + SIT9531X_UPDATE_NVM);
> +
> + /*
> + * Issue the loop lock even if the update failed: every caller
> + * commits whatever happened after the entry so that the chip never
> + * stays in the PRG_CMD state with its loops open, and returning
> + * early here would defeat that.
> + *
> + * Re-lock the loops. Leaving them open is worse than any other
> + * failure this function can report, and nothing else closes them,
> + * so retry, as the priority commit retries the release of its
> + * forced holdover.
> + */
> + for (attempt = 0; attempt < SIT9531X_LOOP_LOCK_TRIES; attempt++) {
> + rc2 = sit9531x_write_u8(sitdev, SIT9531X_REG_PRG_DIR_GEN,
> + SIT9531X_LOOP_LOCK);
> + if (!rc2)
> + break;
> + usleep_range(1000, 2000);
> + }
> + if (rc2)
> + dev_err(sitdev->dev,
> + "output loops left unlocked after programming: %d\n",
> + rc2);
> +
> + msleep(100);
> +
> + /*
> + * Put the output-system debug block back the way the device powers
> + * up. Its key register unlocks every debug register while it holds
> + * the unlock value, and each programming sequence writes that value
> + * itself, so nothing needs it left unlocked in between.
> + */
> + rc3 = sit9531x_write_u8(sitdev, SIT9531X_REG_OUTSYS_DEBUG,
> + SIT9531X_DEBUG_LOCK_VAL);
> +
> + if (rc)
> + return rc;
> + if (rc2)
> + return rc2;
> +
> + /*
> + * The programming is committed and the loops are locked by now;
> + * only the debug key stayed open, and the next sequence writes it
> + * again. Failing here would make the caller skip what follows a
> + * change that did take effect -- and the core drops an identical
> + * retry, so it would never run.
> + */
> + if (rc3)
> + dev_warn(sitdev->dev,
> + "output debug block left unlocked after programming: %d\n",
> + rc3);
> +
> + return 0;
> +}
> +
> /*
> * Input priority selection
> *
> @@ -1329,6 +1463,11 @@ int sit9531x_input_prio_add(struct sit9531x_dev *sitdev, u8 pll_idx,
> chan->prio_mask | BIT(input_idx));
> }
>
> +/* Per-slot DIVO base register offsets (6 slots per page) */
> +static const u8 clkout_odr_divn_base[] = {
> + 0x14, 0x24, 0x34, 0x44, 0x54, 0x64
> +};
> +
> /* XO doubler register */
> #define SIT9531X_REG_XO2_GENERIC SIT9531X_REG(0x00, 0x2D)
> #define SIT9531X_XO_DOUBLER_ENB_BIT 7 /* inverted: 0 = enabled */
> @@ -1342,6 +1481,672 @@ int sit9531x_input_prio_add(struct sit9531x_dev *sitdev, u8 pll_idx,
> /* The output divider is a 34-bit field */
> #define SIT9531X_DIVO_MAX GENMASK_ULL(33, 0)
>
> +/*
> + * sit9531x_is_xo_doubler_enabled - check if Fref doubler is active
> + *
> + * Register 0x2D bit 7 is active-low: 0 = doubler enabled, 1 = disabled.
> + *
> + * Return: 1 if enabled, 0 if disabled, <0 on error
> + */
> +static int sit9531x_is_xo_doubler_enabled(struct sit9531x_dev *sitdev)
> +{
> + u8 val;
> + int rc;
> +
> + rc = sit9531x_read_u8(sitdev, SIT9531X_REG_XO2_GENERIC, &val);
> + if (rc)
> + return rc;
> +
> + return (~val >> SIT9531X_XO_DOUBLER_ENB_BIT) & 1u;
> +}
> +
> +/*
> + * DIVN as a fixed-point value: int_part plus fracn/fracd, carried with
> + * SIT9531X_DIVN_SCALE steps per unit. The scale keeps a whole DIVN
> + * well inside s64 while resolving far below the parts-per-trillion the
> + * frequency offset is reported in.
> + */
> +static s64 sit9531x_divn_fixed(u32 int_part, s64 fracn, u64 fracd)
> +{
> + s64 whole = (s64)int_part * SIT9531X_DIVN_SCALE;
> + u64 frac;
> +
> + if (!fracd)
> + return whole;
> +
> + frac = mul_u64_u64_div_u64(abs(fracn), SIT9531X_DIVN_SCALE, fracd);
> +
> + return fracn < 0 ? whole - (s64)frac : whole + (s64)frac;
> +}
> +
> +/*
> + * sit9531x_divn_static - read the configured DIVN of a PLL
> + * @sitdev: device pointer
> + * @pll_idx: PLL index (0-3)
> + * @divn: result, fixed point as per sit9531x_divn_fixed()
> + *
> + * Reads PLL page regs 0x30 (integer part), 0x32-0x35 (numerator) and
> + * 0x38-0x3B (denominator). The numerator is a two's complement 32-bit
> + * value, so DIVN can sit below the integer part, and the denominator
> + * register holds the divisor minus one.
> + *
> + * Return: 0 on success, <0 on error
> + */
> +static int sit9531x_divn_static(struct sit9531x_dev *sitdev, u8 pll_idx,
> + s64 *divn)
> +{
> + u32 int_part, fracn_raw = 0, fracd_raw = 0;
> + u64 fracd;
> + s64 fracn;
> + int rc, i;
> + u8 v;
> +
> + rc = sit9531x_read_pll_u8(sitdev, pll_idx,
> + SIT9531X_PLL_REG_DIVN_INT, &v);
> + if (rc)
> + return rc;
> + int_part = v;
> +
> + for (i = 3; i >= 0; i--) {
> + rc = sit9531x_read_pll_u8(sitdev, pll_idx,
> + SIT9531X_PLL_REG_DIVN_NUM + i, &v);
> + if (rc)
> + return rc;
> + fracn_raw = (fracn_raw << 8) | v;
> + }
> +
> + for (i = 3; i >= 0; i--) {
> + rc = sit9531x_read_pll_u8(sitdev, pll_idx,
> + SIT9531X_PLL_REG_DIVN_DEN + i, &v);
> + if (rc)
> + return rc;
> + fracd_raw = (fracd_raw << 8) | v;
> + }
> +
> + /*
> + * NUM/DEN is the fractional part of DIVN, so |NUM| is below DEN by
> + * construction. A pair that says otherwise did not come from a
> + * programmed divider, and handing it on would divide by a
> + * denominator small enough for the quotient to leave u64 -- which
> + * is a divide-error exception on x86, not a value a caller could
> + * reject.
> + */
> + fracn = (s32)fracn_raw;
> + fracd = (u64)fracd_raw + 1;
> + if ((u64)abs(fracn) >= fracd)
> + return -ENODATA;
> +
> + *divn = sit9531x_divn_fixed(int_part, fracn, fracd);
> +
> + return 0;
> +}
> +
> +/* PLLB and PLLD run their VCO in the high band, PLLA and PLLC in the low. */
> +static bool sit9531x_fvco_in_band(u8 pll_idx, u64 fvco)
> +{
> + if (pll_idx == 1 || pll_idx == 3)
> + return fvco >= SIT9531X_FVCO_HIGHBAND_MIN &&
> + fvco <= SIT9531X_FVCO_HIGHBAND_MAX;
> + return fvco >= SIT9531X_FVCO_LOWBAND_MIN &&
> + fvco <= SIT9531X_FVCO_LOWBAND_MAX;
> +}
> +
> +/*
> + * sit9531x_get_fvco - read VCO frequency from chip's DIVN registers
> + *
> + * Fvco = Fref * DIVN, where DIVN comes from sit9531x_divn_static() and
> + * Fref = xtal_freq << doubler. DIVN is the steady-state Fvco/Fref
> + * target programmed by the NVM blob and is authoritative in both
> + * free-run and sync modes.
> + *
> + * Return: 0 with *fvco set on success, -ENODATA when DIVN is not
> + * programmed (dormant PLL), or the register access error. A bus
> + * failure is never folded into the -ENODATA case, so callers can fail
> + * a request instead of acting on a guessed rate.
> + */
> +static int sit9531x_get_fvco(struct sit9531x_dev *sitdev, u8 pll_idx,
> + u64 *fvco)
> +{
> + int doubler, rc;
> + u64 fref;
> + s64 divn;
> +
> + rc = sit9531x_divn_static(sitdev, pll_idx, &divn);
> + if (rc)
> + return rc;
> + if (divn <= 0)
> + return -ENODATA;
> +
> + doubler = sit9531x_is_xo_doubler_enabled(sitdev);
> + if (doubler < 0)
> + return doubler;
> +
> + fref = (u64)sitdev->xtal_freq << doubler;
> +
> + /*
> + * Round to the nearest hertz: DIVN is carried in fixed point, so a
> + * fraction such as 1/6 is already floored once, and flooring the
> + * product again reads an exact 5 GHz VCO as 4999999999 Hz -- which
> + * then refuses every rate the VCO divides exactly.
> + */
> + *fvco = mul_u64_u64_div_u64(2 * fref, (u64)divn, SIT9531X_DIVN_SCALE);
> + *fvco = (*fvco + 1) / 2;
> +
> + /*
> + * No VCO of this family runs below the low band, so a product under
> + * it is not a rate: a DIVN of a cycle or less, say, which the check
> + * above lets through and which a profile that never programmed the
> + * divider can leave behind. Callers scale 34-bit register fields by
> + * 1e12 / Fvco or more, and for a rate far enough below the band that
> + * quotient leaves u64 -- a divide-error exception on x86, not a
> + * value they could reject. Report the unprogrammed divider instead.
> + */
> + if (*fvco < SIT9531X_FVCO_LOWBAND_MIN)
> + return -ENODATA;
> +
> + /*
> + * A rate outside the band the PLL's VCO runs in usually means
> + * Fref * DIVN is not what this PLL runs at -- a PLL fed from another
> + * PLL rather than from the XO, for one. The rate derived from the
> + * registers is still the only estimate there is, so a read reports
> + * what it derives; a set refuses to program a divider against it
> + * (sit9531x_output_divo_calc()), since substituting the band edge
> + * would program against a rate nothing supports and report the
> + * result as exact. Say so once per PLL.
> + */
> + if (!sit9531x_fvco_in_band(pll_idx, *fvco) &&
> + !(sitdev->fvco_band_warned & BIT(pll_idx))) {
> + sitdev->fvco_band_warned |= BIT(pll_idx);
> + dev_warn(sitdev->dev,
> + "PLL%c: Fref * DIVN = %llu Hz is outside its VCO band\n",
> + 'A' + pll_idx, *fvco);
> + }
> +
> + return 0;
> +}
> +
> +/* Latch a change to a PLL page with a small update. */
> +static int sit9531x_pll_small_update(struct sit9531x_dev *sitdev, u8 pll_idx)
> +{
> + return sit9531x_write_pll_u8(sitdev, pll_idx,
> + SIT9531X_PLL_REG_SMALL_UPDATE,
> + SIT9531X_SMALL_UPDATE_CMD);
> +}
> +
> +/*
> + * sit9531x_output_phase_flush - flush the output phase of a PLL
> + *
> + * Fires the chip's on-demand phase-flush (PHFL) so every output divider
> + * of @pll_idx restarts aligned to the PLL phase. Without it a rewritten
> + * DIVO keeps counting from an arbitrary point and the output edge lands
> + * with a persistent offset against the tracked reference (only a power
> + * cycle realigned it).
> + *
> + * The sequence mirrors the documented procedure: arm the on-demand PHFL and
> + * latch it with the PLL-page small-change update, then select the
> + * in-register phase trigger on Page 0 and pulse it. The Page 0 trigger
> + * register is touched read-modify-write so the unrelated OEb bits are
> + * preserved.
> + *
> + * A PLL the loaded configuration builds without the phase-flush feature
> + * (PLL page 0x47 bit 7 clear) has nothing to fire; it is restarted
> + * instead, which restarts its output dividers from the PLL phase. That
> + * is what SiTime's Dely_program_output.py does for such a PLL.
> + *
> + * Caller must hold sitdev->multiop_lock.
> + */
> +static int sit9531x_output_phase_flush(struct sit9531x_dev *sitdev, u8 pll_idx)
> +{
> + u8 ctrl, orig, phfl, armed_mask = 0, parked = 0, i;
> + int rc, ret;
> +
> + rc = sit9531x_read_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_CONFIG47,
> + &phfl);
> + if (rc)
> + return rc;
> + if (!(phfl & SIT9531X_PLL_CONFIG47_PHFL_EN))
> + return sit9531x_write_pll_u8(sitdev, pll_idx,
> + SIT9531X_PLL_REG_DIRECTIVES,
> + SIT9531X_PLL_DIRECTIVE_RESTART);
> +
> + /*
> + * The trigger below is chip-global: every PLL whose on-demand flush
> + * is armed answers it. Note which ones the loaded profile arms --
> + * this one so its setting can be put back, the others so they can
> + * be parked for the pulse and do not realign outputs nobody asked
> + * to move.
> + */
> + for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
> + rc = sit9531x_read_pll_u8(sitdev, i, SIT9531X_PLL_REG_PHFL_CTRL,
> + &phfl);
> + if (rc)
> + return rc;
> + if (phfl & SIT9531X_PLL_PHFL_ON_DEMAND_EN)
> + armed_mask |= BIT(i);
> + }
> +
> + for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
> + if (i == pll_idx || !(armed_mask & BIT(i)))
> + continue;
> + /*
> + * A clear whose write reported an error may still have
> + * landed, latched or not: put it back either way.
> + */
> + parked |= BIT(i);
> + rc = sit9531x_update_pll_u8(sitdev, i,
> + SIT9531X_PLL_REG_PHFL_CTRL,
> + SIT9531X_PLL_PHFL_ON_DEMAND_EN, 0);
> + if (rc)
> + goto unpark;
> + rc = sit9531x_pll_small_update(sitdev, i);
> + if (rc)
> + goto unpark;
> + }
> +
> + /*
> + * Arm the on-demand phase-flush on the PLL page. An arm whose write
> + * failed may have landed too, so it goes through the disarm below.
> + */
> + rc = sit9531x_update_pll_u8(sitdev, pll_idx,
> + SIT9531X_PLL_REG_PHFL_CTRL,
> + SIT9531X_PLL_PHFL_ON_DEMAND_EN,
> + SIT9531X_PLL_PHFL_ON_DEMAND_EN);
> + if (rc)
> + goto disarm;
> +
> + /*
> + * Latch it with the PLL small-change update. Written whole, like
> + * every other issue of this directive: the register is a command
> + * register, and a read-modify-write skips the write entirely when
> + * the bit still reads back set from the previous command.
> + */
> + rc = sit9531x_pll_small_update(sitdev, pll_idx);
> + if (rc)
> + goto disarm;
> +
> + /*
> + * Select the in-register phase trigger, preserving the OEb bits.
> + * Remember the original register value (with the trigger de-asserted)
> + * so the trigger-source select can be restored once the pulse has
> + * fired.
> + */
> + rc = sit9531x_read_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1, &ctrl);
> + if (rc)
> + goto disarm;
> +
> + orig = ctrl & ~SIT9531X_DIVO_PHASE_TRIG;
> + ctrl = orig | SIT9531X_DIVO_PHASE_SEL_REG;
> + rc = sit9531x_write_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1, ctrl);
> + if (rc)
> + goto restore;
> +
> + /*
> + * Pulse the phase trigger. No explicit delay is needed between the
> + * set and clear writes: each I2C transaction takes far longer than
> + * any minimum pulse width.
> + */
> + rc = sit9531x_write_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1,
> + ctrl | SIT9531X_DIVO_PHASE_TRIG);
> +
> +restore:
> + /*
> + * Restore the original trigger-source select. The pulse above has
> + * already latched the flush, so a one-shot flush must not leave the
> + * phase trigger permanently pinned to the in-register source. This
> + * runs even when the pulse write failed, and when the select write
> + * itself reported a failure, since the transfer may have reached
> + * the part all the same; otherwise a failed flush would keep a
> + * hardware trigger source hijacked. The restore error is only
> + * surfaced when it would not mask the earlier failure.
> + */
> + ret = sit9531x_write_u8(sitdev, SIT9531X_REG_GPIO_FUNC_CTRL1, orig);
> + if (ret && !rc)
> + rc = ret;
> +
> +disarm:
> + /*
> + * Put the on-demand flush enable back as the profile had it. One the
> + * profile left clear must not stay armed: a later assertion of the
> + * restored trigger source would re-flush every output divider of this
> + * PLL. One the profile armed -- a board realigning from a GPIO, say
> + * -- must not be switched off by an unrelated rate change.
> + */
> + if (!(armed_mask & BIT(pll_idx))) {
> + ret = sit9531x_update_pll_u8(sitdev, pll_idx,
> + SIT9531X_PLL_REG_PHFL_CTRL,
> + SIT9531X_PLL_PHFL_ON_DEMAND_EN, 0);
> + if (!ret)
> + ret = sit9531x_pll_small_update(sitdev, pll_idx);
> + if (ret && !rc)
> + rc = ret;
> + }
> +
> +unpark:
> + for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
> + if (!(parked & BIT(i)))
> + continue;
> + ret = sit9531x_update_pll_u8(sitdev, i,
> + SIT9531X_PLL_REG_PHFL_CTRL,
> + SIT9531X_PLL_PHFL_ON_DEMAND_EN,
> + SIT9531X_PLL_PHFL_ON_DEMAND_EN);
> + if (!ret)
> + ret = sit9531x_pll_small_update(sitdev, i);
> + if (ret && !rc)
> + rc = ret;
> + }
> +
> + return rc;
> +}
> +
> +/*
> + * sit9531x_output_divo_calc - work out an output's divider and its VCO
> + *
> + * Separated from the write so a caller that programs more than the
> + * divider in one sequence can compute the value before it enters the
> + * programming state.
> + */
> +static int sit9531x_output_divo_calc(struct sit9531x_dev *sitdev, u8 out_idx,
> + u8 pll_idx, u64 frequency, u64 *fvco_out,
> + u64 *divo_out)
> +{
> + const struct sit9531x_chip_info *info = sitdev->info;
> + u64 fvco, divo;
> + int rc;
> +
> + if (out_idx >= info->num_outputs || pll_idx >= SIT9531X_NUM_PLLS)
> + return -EINVAL;
> +
> + if (!frequency)
> + return -EINVAL;
> +
> + /*
> + * The core validates the request against the supported ranges with
> + * the value narrowed to u32 but hands the full u64 down, so a value
> + * like U32_MAX + 1 Hz validates as 1 Hz. Reject anything that does
> + * not fit the narrowed width the validation actually covered.
> + */
> + if (frequency > U32_MAX)
> + return -EINVAL;
> +
> + /*
> + * sit9531x_get_fvco() returns the register-derived rate, so a
> + * frequency get and a frequency set divide the same number. A VCO
> + * that cannot be read fails the request: programming a divider from a
> + * guessed rate would put the output far from what was asked for
> + * while reporting success.
> + */
> + rc = sit9531x_get_fvco(sitdev, pll_idx, &fvco);
> + if (rc)
> + return rc == -ENODATA ? -ENODEV : rc;
> +
> + /*
> + * Fref * DIVN outside the VCO band is not what the PLL runs at, and
> + * sit9531x_get_fvco() has said so. A divider computed against it
> + * would put the output at some other rate while the exactness check
> + * below passes and the read-back, dividing the same number, confirms
> + * the request. Refuse the set instead.
> + */
> + if (!sit9531x_fvco_in_band(pll_idx, fvco)) {
> + dev_dbg(sitdev->dev,
> + "out%u: PLL%c Fvco %llu Hz is outside its VCO band, rate not settable\n",
> + out_idx, 'A' + pll_idx, fvco);
> + return -EINVAL;
> + }
> +
> + /*
> + * Round to nearest rather than down: flooring picks the worse of the
> + * two adjacent dividers whenever the remainder is above half the
> + * request.
> + */
> + divo = div64_u64(fvco + frequency / 2, frequency);
> + if (!divo)
> + return -EINVAL;
> +
> + /* DIVO is a 34-bit field; refuse a wider divider rather than truncate it. */
> + if (divo > SIT9531X_DIVO_MAX)
> + return -EINVAL;
> +
> + /*
> + * The output divider is an integer divider of the VCO, so the only
> + * rates the part can make are Fvco/N. An output pin that lists no
> + * supported frequencies advertises a continuous range, because the
> + * divisors cannot be enumerated ahead of a known Fvco, so a request
> + * for a rate between two of them arrives here. Refuse it: running
> + * the output at the nearest divider instead and reporting success
> + * would leave the pin several percent off what was asked for with
> + * nothing saying so.
> + */
> + if (div64_u64(fvco, divo) != frequency) {
> + dev_dbg(sitdev->dev,
> + "out%u: %llu Hz is not Fvco/N (Fvco=%llu, nearest %llu Hz)\n",
> + out_idx, frequency, fvco, div64_u64(fvco, divo));
> + return -EINVAL;
> + }
> +
> + dev_dbg(sitdev->dev,
> + "out%u: Fvco=%llu freq=%llu DIVO=%llu (effective %llu Hz)\n",
> + out_idx, fvco, frequency, divo, div64_u64(fvco, divo));
> +
> + *fvco_out = fvco;
> + *divo_out = divo;
> +
> + return 0;
> +}
> +
> +/*
> + * sit9531x_output_divo_write - write the five DIVO bytes of an output
> + *
> + * The caller must already be in the programming state. Bytes written
> + * before a failure are put back, so the output keeps the divider it had
> + * rather than a mixture of the two.
> + */
> +static int sit9531x_output_divo_write(struct sit9531x_dev *sitdev, u8 out_idx,
> + u64 divo)
> +{
> + const struct sit9531x_chip_info *info = sitdev->info;
> + u8 slot, page, base_reg, divo_bytes[5], old_bytes[5], msb_old;
> + int rc, j, rb_rc;
> + u8 written = 0;
> +
> + /* Map output index to physical slot */
> + slot = info->clkout_map[out_idx];
> +
> + /* Determine page and per-page slot register */
> + if (slot > SIT9531X_PAGE_OUTSYS0_SLOT_MAX)
> + page = SIT9531X_PAGE_OUTSYS1;
> + else
> + page = SIT9531X_PAGE_OUTSYS0;
> + base_reg = clkout_odr_divn_base[slot % 6];
> +
> + divo_bytes[0] = (divo >> 0) & 0xFF;
> + divo_bytes[1] = (divo >> 8) & 0xFF;
> + divo_bytes[2] = (divo >> 16) & 0xFF;
> + divo_bytes[3] = (divo >> 24) & 0xFF;
> + divo_bytes[4] = (divo >> 32) & 0x03; /* only bits [1:0] */
> +
> + for (j = 0; j < 5; j++) {
> + rc = sit9531x_read_u8(sitdev,
> + SIT9531X_REG(page, base_reg - j),
> + &old_bytes[j]);
> + if (rc)
> + return rc;
> + }
> +
> + msb_old = old_bytes[4];
> + divo_bytes[4] |= msb_old & 0xFC;
> +
> + for (j = 0; j < 5; j++) {
> + rc = sit9531x_write_u8(sitdev,
> + SIT9531X_REG(page, base_reg - j),
> + divo_bytes[j]);
> + if (rc)
> + goto rollback;
> + written++;
> + }
> +
> + return 0;
> +
> +rollback:
> + /*
> + * The byte whose write reported the error may still have reached
> + * the part, so it is put back along with the ones that did.
> + */
> + for (j = 0; j <= written && j < 5; j++) {
> + rb_rc = sit9531x_write_u8(sitdev,
> + SIT9531X_REG(page, base_reg - j),
> + old_bytes[j]);
> + if (rb_rc) {
> + dev_err(sitdev->dev,
> + "out%u: DIVO rollback failed (%d), the divider is part old and part new\n",
> + out_idx, rb_rc);
> + if (!rc)
> + rc = rb_rc;
> + }
> + }
> +
> + return rc;
> +}
> +
> +/*
> + * Read an output's divider back from the device.
> + *
> + * Return: 0 with *divo set, -ENODATA when the divider is not programmed,
> + * or the register access error
> + */
> +static int sit9531x_output_divo_read(struct sit9531x_dev *sitdev, u8 out_idx,
> + u64 *divo)
> +{
> + u8 slot, page, base_reg, v;
> + int rc, j;
> +
> + slot = sitdev->info->clkout_map[out_idx];
> + if (slot > SIT9531X_PAGE_OUTSYS0_SLOT_MAX)
> + page = SIT9531X_PAGE_OUTSYS1;
> + else
> + page = SIT9531X_PAGE_OUTSYS0;
> + base_reg = clkout_odr_divn_base[slot % 6];
> +
> + *divo = 0;
> + for (j = 4; j >= 0; j--) {
> + rc = sit9531x_read_u8(sitdev,
> + SIT9531X_REG(page, base_reg - j), &v);
> + if (rc)
> + return rc;
> + if (j == 4)
> + v &= 0x03;
> + *divo = (*divo << 8) | v;
> + }
> +
> + return *divo ? 0 : -ENODATA;
> +}
> +
> +int sit9531x_output_freq_set(struct sit9531x_dev *sitdev, u8 out_idx,
> + u8 pll_idx, u64 frequency)
> +{
> + u64 fvco, divo;
> + int rc, ret;
> +
> + lockdep_assert_held(&sitdev->multiop_lock);
> +
> + rc = sit9531x_output_divo_calc(sitdev, out_idx, pll_idx, frequency,
> + &fvco, &divo);
> + if (rc)
> + return rc;
> +
> + rc = sit9531x_prg_enter(sitdev);
> + if (rc)
> + return rc;
> +
> + rc = sit9531x_output_divo_write(sitdev, out_idx, divo);
> + /*
> + * Step 4: NVM update + loop lock. Always run prg_commit() so the chip
> + * leaves the PRG_CMD state with the output loops re-locked, even when a
> + * write above failed; keep the first error to return. It also carries
> + * the required post-lock settling sleep.
> + */
> + ret = sit9531x_prg_commit(sitdev);
> + if (ret && !rc)
> + rc = ret;
> + if (rc)
> + return rc;
> +
> + /*
> + * Step 5: flush the PLL's output phase so the new DIVO starts
> + * aligned instead of keeping the arbitrary phase the divider
> + * happened to be at.
> + */
> + /*
> + * The divider is committed by this point, so the part is already
> + * running at the new rate. A flush that fails leaves the output
> + * divider on its old phase, which is a realignment that did not
> + * happen rather than a rate that did not change -- and reporting a
> + * failure would be doubly wrong, because the core asks for the
> + * current rate first and would drop an identical retry.
> + */
> + rc = sit9531x_output_phase_flush(sitdev, pll_idx);
> + if (rc) {
> + dev_warn(sitdev->dev,
> + "out%u: rate changed but the divider phase was not realigned (%d)\n",
> + out_idx, rc);
> + rc = 0;
> + }
> +
> + sitdev->out[out_idx].freq = div64_u64(fvco, divo);
> +
> + return 0;
> +}
> +
> +/*
> + * sit9531x_output_freq_get - read output clock frequency from hardware
> + * @out_idx: output index (0-N for this chip variant)
> + * @frequency: output frequency in Hz
> + *
> + * Reads the 34-bit DIVO divider back from the output system registers
> + * and computes the live output frequency as Fvco / DIVO. This stays
> + * correct even when the divider was reprogrammed behind the driver's
> + * back (e.g. by a direct-I2C userspace tool), where the cached value
> + * would be stale.
> + *
> + * The cached output state is refreshed with the computed value.
> + *
> + * Caller must hold sitdev->multiop_lock.
> + *
> + * Return: 0 on success, -ENODATA when the output divider or VCO rate
> + * is not resolvable, <0 on register access error. -ENODATA and not
> + * -ENODEV, which the I2C layer returns for an adapter that is gone
> + * and which must not read as "no rate to report".
> + */
> +int sit9531x_output_freq_get(struct sit9531x_dev *sitdev, u8 out_idx,
> + u64 *frequency)
> +{
> + const struct sit9531x_chip_info *info = sitdev->info;
> + u64 fvco, divo;
> + u8 pll_idx;
> + int 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 || !sitdev->out[out_idx].routed)
> + return -ENODATA;
> +
> + rc = sit9531x_get_fvco(sitdev, pll_idx, &fvco);
> + if (rc)
> + return rc;
> +
> + rc = sit9531x_output_divo_read(sitdev, out_idx, &divo);
> + if (rc)
> + return rc;
> +
> + *frequency = div64_u64(fvco, divo);
> + sitdev->out[out_idx].freq = *frequency;
> +
> + return 0;
> +}
> +
> /*
> * Phase adjust (PRG_RST_DELAY register-based).
> *
> diff --git a/drivers/dpll/sit9531x/core.h b/drivers/dpll/sit9531x/core.h
> index fa1197bfc81c..006a7fee76ab 100644
> --- a/drivers/dpll/sit9531x/core.h
> +++ b/drivers/dpll/sit9531x/core.h
> @@ -192,6 +192,8 @@ struct sit9531x_chan {
> * synchronization (INTSYNC), or -1 when disabled
> * @irq_ack_fails: consecutive failures to acknowledge the
> * notification latches from the interrupt handler
> + * @fvco_band_warned: bit per PLL whose Fref * DIVN has been reported as
> + * outside its VCO band; written under @multiop_lock
> */
> struct sit9531x_dev {
> struct device *dev;
> @@ -203,6 +205,7 @@ struct sit9531x_dev {
>
> /* Hardware state */
> u8 irq_ack_fails;
> + u8 fvco_band_warned;
> struct sit9531x_ref ref[SIT9531X_MAX_INPUTS + 1]; /* +1 for xtal */
> struct sit9531x_out out[SIT9531X_MAX_OUTPUTS];
> struct sit9531x_chan chan[SIT9531X_NUM_PLLS];
> @@ -265,6 +268,10 @@ int sit9531x_input_prio_add(struct sit9531x_dev *sitdev, u8 pll_idx,
> /* ---- Output enable/disable (Hi-Z control) ---- */
>
> /* ---- Output frequency ---- */
> +int sit9531x_output_freq_set(struct sit9531x_dev *sitdev, u8 out_idx,
> + u8 pll_idx, u64 frequency);
> +int sit9531x_output_freq_get(struct sit9531x_dev *sitdev, u8 out_idx,
> + u64 *frequency);
>
> /* ---- Output phase adjust (PRG_RST_DELAY register-based) ---- */
>
> diff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c
> index 0400444d91a5..aa021b374c45 100644
> --- a/drivers/dpll/sit9531x/dpll.c
> +++ b/drivers/dpll/sit9531x/dpll.c
> @@ -411,6 +411,32 @@ sit9531x_dpll_input_pin_direction_get(const struct dpll_pin *pin,
> return 0;
> }
>
> +/*
> + * sit9531x_dpll_input_pin_frequency_get - read input pin frequency
> + *
> + * Returns the rate the board wired to the input, the first entry of its
> + * supported-frequencies-hz; an input has no frequency setter. An input
> + * the firmware lists no rate for is registered without this callback
> + * (sit9531x_dpll_pin_ops_get()), so its attribute is absent rather than
> + * zero.
> + */
> +static int
> +sit9531x_dpll_input_pin_frequency_get(const struct dpll_pin *pin,
> + void *pin_priv,
> + const struct dpll_device *dpll,
> + void *dpll_priv, u64 *frequency,
> + struct netlink_ext_ack *extack)
> +{
> + struct sit9531x_dpll_pin *dpin = pin_priv;
> + struct sit9531x_dpll *sitdpll = dpll_priv;
> + const struct sit9531x_ref *ref;
> +
> + ref = sit9531x_ref_state_get(sitdpll->dev, dpin->id);
> + *frequency = ref->freq;
> +
> + return 0;
> +}
> +
> /*
> * sit9531x_dpll_input_pin_state_on_dpll_get - get input pin DPLL state
> *
> @@ -644,6 +670,23 @@ sit9531x_dpll_input_pin_prio_set(const struct dpll_pin *pin, void *pin_priv,
> }
>
> 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,
> + .state_on_dpll_get = sit9531x_dpll_input_pin_state_on_dpll_get,
> + .state_on_dpll_set = sit9531x_dpll_input_pin_state_on_dpll_set,
> + .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,
> +};
> +
> +/*
> + * The same without frequency_get, for an input whose firmware node lists
> + * no supported-frequencies-hz (or that has no node at all): there is no
> + * rate to report, and the core fails a whole pin dump on an error from
> + * any one callback, so the attribute is left out rather than reported
> + * as 0 Hz.
> + */
> +static const struct dpll_pin_ops sit9531x_dpll_input_pin_norate_ops = {
> .direction_get = sit9531x_dpll_input_pin_direction_get,
> .state_on_dpll_get = sit9531x_dpll_input_pin_state_on_dpll_get,
> .state_on_dpll_set = sit9531x_dpll_input_pin_state_on_dpll_set,
> @@ -704,6 +747,7 @@ sit9531x_dpll_xo_pin_state_on_dpll_get(const struct dpll_pin *pin,
>
> static const struct dpll_pin_ops sit9531x_dpll_xo_pin_ops = {
> .direction_get = sit9531x_dpll_input_pin_direction_get,
> + .frequency_get = sit9531x_dpll_input_pin_frequency_get,
> .state_on_dpll_get = sit9531x_dpll_xo_pin_state_on_dpll_get,
> };
>
> @@ -719,8 +763,78 @@ sit9531x_dpll_output_pin_direction_get(const struct dpll_pin *pin,
> return 0;
> }
>
> +/*
> + * sit9531x_dpll_output_pin_frequency_get - read output pin frequency
> + *
> + * Reads the DIVO divider back from the chip and computes the live
> + * frequency as Fvco / DIVO. Falls back to the cached value only when
> + * the output is not resolvable through the divider chain (e.g. not
> + * mapped to a PLL), so transport/register errors still surface.
> + */
> +static int
> +sit9531x_dpll_output_pin_frequency_get(const struct dpll_pin *pin,
> + void *pin_priv,
> + const struct dpll_device *dpll,
> + void *dpll_priv, u64 *frequency,
> + 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_freq_get(sitdev, dpin->id, frequency);
> + if (rc == -ENODATA)
> + *frequency = sit9531x_out_state_get(sitdev, dpin->id)->freq;
> + mutex_unlock(&sitdev->multiop_lock);
> +
> + return rc == -ENODATA ? 0 : rc;
> +}
> +
> +/*
> + * sit9531x_dpll_output_pin_frequency_set - set output pin frequency
> + *
> + * computes DIVO = Fvco / frequency and writes the
> + * 34-bit output divider to the output system registers via
> + * sit9531x_output_freq_set().
> + */
> +static int
> +sit9531x_dpll_output_pin_frequency_set(const struct dpll_pin *pin,
> + void *pin_priv,
> + const struct dpll_device *dpll,
> + void *dpll_priv, u64 frequency,
> + 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;
> +
> + /*
> + * Read the PLL that drives this output from its OUT_MAP state
> + * (populated by out_state_fetch from the chip's OUT_MAP registers).
> + * That is the index the output register programming below is keyed
> + * by; the output is registered under the DPLL matching this PLL.
> + */
> + actual_pll = sitdev->out[dpin->id].pll_idx;
> +
> + mutex_lock(&sitdev->multiop_lock);
> + rc = sit9531x_output_freq_set(sitdev, dpin->id, actual_pll,
> + frequency);
> + mutex_unlock(&sitdev->multiop_lock);
> +
> + if (rc)
> + NL_SET_ERR_MSG(extack, "Output frequency set failed");
> +
> + 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,
> + .frequency_set = sit9531x_dpll_output_pin_frequency_set,
> };
>
> const struct dpll_pin_ops *
> @@ -730,6 +844,12 @@ sit9531x_dpll_pin_ops_get(const struct sit9531x_dpll_pin *pin)
> return &sit9531x_dpll_output_pin_ops;
> if (sit9531x_dpll_is_xo_pin(pin))
> return &sit9531x_dpll_xo_pin_ops;
> + /*
> + * Seeded from the firmware node before the pin is registered and
> + * never written afterwards, so register and unregister agree.
> + */
> + if (!sit9531x_ref_state_get(pin->dpll->dev, pin->id)->freq)
> + return &sit9531x_dpll_input_pin_norate_ops;
> return &sit9531x_dpll_input_pin_ops;
> }
>
> diff --git a/drivers/dpll/sit9531x/prop.c b/drivers/dpll/sit9531x/prop.c
> index 934db566c002..8270b8ee91be 100644
> --- a/drivers/dpll/sit9531x/prop.c
> +++ b/drivers/dpll/sit9531x/prop.c
> @@ -295,15 +295,36 @@ sit9531x_pin_props_get(struct sit9531x_dev *sitdev,
> }
>
> /*
> - * Seed an input's runtime ref->freq with the first DT-listed
> - * supported frequency: for an input the board lists the rate that is
> - * physically wired to it first. An output's current rate is what its
> - * divider produces, not an entry of the list it may be set to.
> + * Seed the runtime ref->freq with the first DT-listed supported
> + * frequency: an input's rate is a board fact the device cannot be
> + * asked for, so firmware is the only source. An output is left to
> + * the read-back below, which knows what the divider is actually
> + * doing.
> */
> if (num_freqs > 0 && dir == DPLL_PIN_DIRECTION_INPUT &&
> index != SIT9531X_MAX_INPUTS)
> curr_freq = freqs[0];
>
> + /*
> + * An output's current rate is the one its divider produces, so read
> + * it rather than assume the first entry of a list of the rates the
> + * board supports is the one in force. A rate taken from firmware
> + * that the part is not running would be reported as current and,
> + * worse, used as the output period the phase adjust quantizes
> + * against. An output the configuration does not route has no rate
> + * to read, which is not an error.
> + */
> + if (dir == DPLL_PIN_DIRECTION_OUTPUT &&
> + index < sitdev->info->num_outputs) {
> + u64 hw_freq;
> +
> + mutex_lock(&sitdev->multiop_lock);
> + rc = sit9531x_output_freq_get(sitdev, index, &hw_freq);
> + mutex_unlock(&sitdev->multiop_lock);
> + if (!rc)
> + curr_freq = hw_freq;
> + }
> +
> skip_fwnode_props:
This read-back sits before the skip_fwnode_props label, so an output with
no firmware node, or with a node that has no supported-frequencies-hz.
For such an output curr_freq stays at the zero-initialized
sitdev->out[index].freq, so the current rate is neither cached nor listed
ahead of the 1 Hz to 1 GHz fallback range. Was the read-back meant to
cover outputs without a firmware frequency list as well?
Thanks,
Ivan
next prev parent reply other threads:[~2026-10-13 9:43 UTC|newest]
Thread overview: 22+ 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 02/12] dt-bindings: dpll: add SiTime SiT95316 clock generator 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 03/12] dpll: add basic SiTime SiT9531x support Ali Rouhi
2026-10-13 9:11 ` Ivan Vecera
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 08/12] dpll: sit9531x: add support to get and set frequency on pins Ali Rouhi
2026-10-13 9:43 ` Ivan Vecera [this message]
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 10/12] dpll: sit9531x: add support to adjust output phase Ali Rouhi
2026-10-13 10:02 ` Ivan Vecera
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-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=0051c3dd-6abd-4ff1-a125-7cace9fac6fb@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®