From: Ali Rouhi <arouhi@sitime.com>
To: Jiri Pirko <jiri@resnulli.us>
Cc: Vadim Fedorenko <vadim.fedorenko@linux.dev>,
Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>,
Ivan Vecera <ivecera@redhat.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: [PATCH net-next v12 06/12] dpll: sit9531x: implement input pin state on a DPLL
Date: Fri, 9 Oct 2026 18:31:56 +0000 [thread overview]
Message-ID: <20261009183151.78497-7-arouhi@sitime.com> (raw)
In-Reply-To: <20261009183151.78497-1-arouhi@sitime.com>
From: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>
Report and set whether an input takes part in a PLL's reference
selection. The device selects its reference itself from a priority
table, so membership of that table is what the state means here: an input
in the table is selectable, one absent from it is disconnected. Setting
the state adds the input to the table or removes it, and membership is
read back from the device rather than remembered. CONNECTED would ask
for this input and no other, which the device cannot be told to do, so it
is refused. A physical input's receiver is shared by the four PLLs and is
powered down only when the last of them lets go of it.
What the device does with an input is its operational state. The pin
reported active is the one the device says it feeds the PLL, read from
the PLL's debug status bus, while the PLL is locked to it -- outer loop
running, not frozen -- and its lane has signal. It is not the active
selection: that names the source the driver asked for, and when that
source loses its signal the device falls back to another listed source
on its own and leaves the selection naming the dead one, so after such a
failover the pin the PLL moved to reports active and the one it left
does not. The status bus names what is fed to the PLL, not what the PLL
is locked to, so the lane's monitor has to agree. It is read only while
the PLL tracks a reference, six transfers per such PLL per tick; in
free-run the PLL is fed nothing. The poll reads every lane's clock
monitor: an input whose monitor reports loss of signal is no-signal, one
that reports a frequency drift is qual-failed, any other is standby.
The table is built from a priority the driver keeps per source and PLL,
seeded from the table the device loaded: members in priority order, the
slots past the last one naming no source. The four-bit slot encoding
has two codes for a fifth input pair this part does not have, and one of
them serves as that. A disconnected input keeps its priority for when it
comes back, and a table that would come out as the device already holds
it is not written.
The last register of the table also carries the device's active
selection. After a table write the PLL goes to the source it names, and
leaves it on its own only when that source loses its signal, so every
write names a source the PLL can use. When the priorities put a
different source with signal first, the selection goes to it -- the
highest-priority valid input, which is how the DPLL interface defines
automatic mode. A write that only changes the table below that source
leaves the selection where it is while it is still listed and has
signal, so an edit there does not pull a PLL off a healthy reference;
failing that, it goes to the first listed source that has signal.
Removing the last input is allowed. The selection then has nothing to
name, and left as it is it would keep the PLL following its old source
for as long as that has signal, so the PLL stays in the forced holdover
the write sequence uses -- the one state in which it follows no input.
The next write that lists a source releases it. A holdover the driver
did not force -- set by the loaded configuration or by a tool -- is left
in place by such a write; only the hold the driver itself set for an
empty table is released.
The pins fall into three roles and only the first answers this question,
so the contract for all of them is written above the operations rather
than left to be inferred: inputs and the inter-PLL sync destination take
part in selection; an output and the sync source are driven by the PLL and
report whether they carry a signal; the crystal is fixed and always
connected, since a PLL cannot be told to stop using it.
Probe also warns about a firmware pin node whose reg names no pin the
device can have -- a lane or an output the variant lacks, or the N lane
of a pair the configuration runs differential -- since nodes are looked
up from the pins and such a node would otherwise be dropped silently.
Rewriting the table means forcing the PLL into holdover, waiting for it to
take, writing every slot and releasing holdover again, all under the
device lock -- ten to twenty milliseconds. The device has no way to
change one slot in isolation, and holdover is what keeps the loop from
chasing a table that is momentarily inconsistent, so the sequence is the
cost of doing it correctly. Its error paths leave the device where the
request found it: a failed write is rolled back through the register
that failed, a force that failed is released all the same since the
write may have landed, the hold stays when the table the device holds
afterwards lists no source, and the selection is never picked from a
monitor read that failed. A release that fails after the table was
written and latched is owed, and the poll retries it until it lands.
A hold with no source listed and no valid holdover memory reports the
PLL unlocked, as the interface asks, rather than in holdover.
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 | 1145 ++++++++++++++++++++++++++++++++--
drivers/dpll/sit9531x/core.h | 42 +-
drivers/dpll/sit9531x/dpll.c | 320 +++++++++-
drivers/dpll/sit9531x/regs.h | 41 ++
4 files changed, 1483 insertions(+), 65 deletions(-)
diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c
index d2a863ceeb26..999a6583853d 100644
--- a/drivers/dpll/sit9531x/core.c
+++ b/drivers/dpll/sit9531x/core.c
@@ -239,6 +239,99 @@ static void sit9531x_input_get_regs(const struct sit9531x_dev *sitdev,
}
}
+/*
+ * sit9531x_input_disable - disable an input reference
+ * @index: logical input index (0-N)
+ *
+ * Sets the force mask bit and clears the state bit for the given
+ * input, effectively disabling it. Register selection depends on
+ * the pair's signal mode (SE/DE) and the lane (P/N); the bit within
+ * each register addresses the input pair.
+ *
+ * Both writes are attempted even when the first fails, and the first
+ * error is returned. Neither is rolled back: the force and state bits
+ * only mean something together, so a transient bus error can leave the
+ * force bit asserted over a state bit that was never programmed, and the
+ * error is what says the override is not to be trusted.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+int sit9531x_input_disable(struct sit9531x_dev *sitdev, u8 index)
+{
+ unsigned int force_reg, state_reg;
+ struct sit9531x_ref *ref;
+ u8 pair, val;
+ int rc, ret;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (index >= SIT9531X_MAX_INPUTS)
+ return -EINVAL;
+
+ ref = &sitdev->ref[index];
+ pair = sit9531x_input_pair(index);
+ sit9531x_input_get_regs(sitdev, index, &force_reg, &state_reg);
+
+ rc = sit9531x_read_u8(sitdev, force_reg, &val);
+ if (!rc)
+ rc = sit9531x_write_u8(sitdev, force_reg, val | BIT(pair));
+
+ ret = sit9531x_read_u8(sitdev, state_reg, &val);
+ if (!ret)
+ ret = sit9531x_write_u8(sitdev, state_reg, val & ~BIT(pair));
+ if (ret && !rc)
+ rc = ret;
+
+ /*
+ * Drop the claim even when the pair could not be programmed. The
+ * force bit may be asserted over a state bit that never reached the
+ * device, so the receiver cannot be relied on; leaving the cache
+ * saying it is on makes the next enable skip itself and report a
+ * success the signal does not back. Clearing the force bit again
+ * is harmless, so the worst this costs is one redundant write.
+ */
+ ref->enabled = false;
+
+ return rc;
+}
+
+/*
+ * sit9531x_input_enable - enable an input reference
+ * @index: logical input index (0-N)
+ *
+ * Clears the force mask bit for the given input, returning it to
+ * hardware default (enabled).
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+int sit9531x_input_enable(struct sit9531x_dev *sitdev, u8 index)
+{
+ unsigned int force_reg, state_reg;
+ struct sit9531x_ref *ref;
+ u8 pair, val;
+ int rc;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (index >= SIT9531X_MAX_INPUTS)
+ return -EINVAL;
+
+ ref = &sitdev->ref[index];
+ pair = sit9531x_input_pair(index);
+ sit9531x_input_get_regs(sitdev, index, &force_reg, &state_reg);
+
+ rc = sit9531x_read_u8(sitdev, force_reg, &val);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, force_reg, val & ~BIT(pair));
+ if (rc)
+ return rc;
+
+ ref->enabled = true;
+
+ return 0;
+}
+
/*
* Output enable / disable (Hi-Z control)
*
@@ -406,7 +499,8 @@ static int sit9531x_output_forced_hiz(struct sit9531x_dev *sitdev,
* 1. Force PLL into holdover (PLL page reg 0x6F bit 4)
* 2. Write priority slots on Page 1
* 3. Small change update (Page 0 reg 0x0F bit 1)
- * 4. Release holdover
+ * 4. Release holdover, unless it was forced before step 1 by something
+ * other than this driver
*
* Caller must hold sitdev->multiop_lock.
*/
@@ -433,26 +527,595 @@ static u8 sit9531x_prio_slot_get(u8 val, u8 slot)
return val >> SIT9531X_PRIO_HI_SHIFT;
}
+/* Place source @src in priority slot @slot of a register value. */
+static u8 sit9531x_prio_slot_set(u8 val, u8 slot, u8 src)
+{
+ if (slot & 1)
+ return (val & (SIT9531X_PRIO_NIBBLE_MASK <<
+ SIT9531X_PRIO_HI_SHIFT)) |
+ (src & SIT9531X_PRIO_NIBBLE_MASK);
+
+ return (val & SIT9531X_PRIO_NIBBLE_MASK) |
+ ((src & SIT9531X_PRIO_NIBBLE_MASK) <<
+ SIT9531X_PRIO_HI_SHIFT);
+}
+
+/*
+ * Commit a priority-table programming sequence through the Page-0
+ * programming directive register.
+ *
+ * A small change update is all the table needs. The NVM-bank and
+ * loop-lock directives that the output system issues do not belong
+ * here: the former programs non-volatile storage from the efuse and
+ * the latter only means anything after an escape to the PRG_CMD
+ * state. This matches the documented input_priority_sel() procedure.
+ */
+static int sit9531x_prio_prg_commit(struct sit9531x_dev *sitdev)
+{
+ int rc;
+
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_GLOBAL_UPDATE,
+ SIT9531X_SMALL_UPDATE_CMD);
+ if (rc)
+ return rc;
+
+ usleep_range(1000, 2000);
+
+ return 0;
+}
+
+/*
+ * sit9531x_input_mon_fetch - read the clock monitor status of every lane
+ *
+ * Four registers carry a nibble per lane. Loss of signal and frequency
+ * drift are what separate an input the device could lock to from one it
+ * could not, which the priority commit uses to choose the active
+ * selection and the pins report as their operational state.
+ *
+ * Caller must hold sitdev->multiop_lock, or run before registration.
+ */
+static int sit9531x_input_mon_fetch(struct sit9531x_dev *sitdev)
+{
+ static const unsigned int regs[] = {
+ SIT9531X_CLKMON_P_STATUS_01, SIT9531X_CLKMON_P_STATUS_23,
+ SIT9531X_CLKMON_N_STATUS_01, SIT9531X_CLKMON_N_STATUS_23,
+ };
+ u8 val[ARRAY_SIZE(regs)], i, pair, nib;
+ int rc;
+
+ for (i = 0; i < ARRAY_SIZE(regs); i++) {
+ rc = sit9531x_read_u8(sitdev, regs[i], &val[i]);
+ if (rc)
+ return rc;
+ }
+
+ for (i = 0; i < sitdev->info->num_inputs; i++) {
+ struct sit9531x_ref *ref = &sitdev->ref[i];
+
+ /* Lane 2k is INkP, 2k + 1 INkN; a register holds two pairs. */
+ pair = sit9531x_input_pair(i);
+ nib = val[(sit9531x_input_is_n(i) ? 2 : 0) + pair / 2];
+ nib = (pair & 1) ? nib >> 4 : nib & 0x0F;
+
+ ref->los = !!(nib & SIT9531X_CLKMON_LOSS);
+ ref->qual_fail = !ref->los &&
+ !!(nib & (SIT9531X_CLKMON_FINE_DRIFT |
+ SIT9531X_CLKMON_COARSE_DRIFT));
+ }
+
+ return 0;
+}
+
+/*
+ * Can the device lock to this source now? Only the input lanes have a
+ * monitor; the on-chip oscillator and the inter-PLL net are taken as
+ * present. A differential pair is watched through its P lane, which is
+ * the one its table entries are canonicalised to.
+ */
+static bool sit9531x_prio_src_live(const struct sit9531x_dev *sitdev, u8 src)
+{
+ u8 index = sit9531x_hw_src_input(src);
+
+ if (index >= sitdev->info->num_inputs)
+ return true;
+
+ return !sitdev->ref[index].los;
+}
+
/*
- * Rebuild a PLL's membership mask from the source codes of its priority
- * table. The mask is what the pin state getters test, so it is refreshed
- * from exactly the values the table holds -- here after a write, and once
- * per poll from the read-back in sit9531x_chan_state_fetch().
+ * Fold a source code to the lane a DPLL pin actually represents.
+ *
+ * Differential input pairs expose only the P lane as a DPLL pin. A
+ * priority table entry encoded as an N lane for such a pair must map to
+ * the P-lane source for pin-facing operations (membership, priority slots,
+ * add/remove/set lookups), matching sit9531x_ref_pll_mask_fetch().
+ */
+static u8 sit9531x_prio_src_canon(const struct sit9531x_dev *sitdev, u8 src)
+{
+ u8 index = sit9531x_hw_src_input(src);
+
+ if (index >= sitdev->info->num_inputs)
+ return src;
+
+ if (sit9531x_input_is_n(index) &&
+ sitdev->ref[index].sig_mode == SIT9531X_MODE_DE)
+ return sit9531x_input_hw_src(index - 1);
+
+ return src;
+}
+
+/*
+ * sit9531x_input_prio_present - is a source listed in a PLL's priority table
+ * @input_idx: input source in hardware encoding (see
+ * sit9531x_input_hw_src())
+ *
+ * Answers from the membership mask that every table write and every poll
+ * refreshes, which is what the pin state getters test. The priority slot
+ * cannot answer this: a source that is not in the table reports the lowest
+ * slot, so the slot value alone does not separate absent from last.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+bool sit9531x_input_prio_present(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx)
+{
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (pll_idx >= SIT9531X_NUM_PLLS)
+ return false;
+
+ input_idx = sit9531x_prio_src_canon(sitdev, input_idx);
+ if (input_idx >= SIT9531X_PRIO_NUM_SRC)
+ return false;
+
+ return !!(sitdev->chan[pll_idx].prio_mask & BIT(input_idx));
+}
+
+/*
+ * Take the configured priorities from a table the hardware holds: each
+ * listed source gets the first slot it occupies. A source the table does
+ * not list keeps whatever it had, so a disconnected input comes back with
+ * its old priority.
+ */
+static void sit9531x_prio_cfg_seed(struct sit9531x_dev *sitdev, u8 pll_idx,
+ const u8 *srcs)
+{
+ struct sit9531x_chan *chan = &sitdev->chan[pll_idx];
+ u16 seeded = 0;
+ u8 slot, src;
+
+ for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) {
+ src = srcs[slot] & SIT9531X_PRIO_NIBBLE_MASK;
+ if (!sit9531x_prio_src_usable(src))
+ continue;
+ src = sit9531x_prio_src_canon(sitdev, src);
+ if (seeded & BIT(src))
+ continue;
+ seeded |= BIT(src);
+ chan->cfg_prio[src] = slot;
+ chan->cfg_known |= BIT(src);
+ }
+
+ /*
+ * A source this PLL has never listed gets the lowest slot, the value
+ * it reports and the one it is connected at, so its priority does not
+ * change when its state does.
+ */
+ for (src = 0; src < SIT9531X_PRIO_NUM_SRC; src++) {
+ if (chan->cfg_known & BIT(src))
+ continue;
+ chan->cfg_prio[src] = SIT9531X_PRIO_MAX_SLOTS - 1;
+ chan->cfg_known |= BIT(src);
+ }
+
+ memcpy(chan->seen_srcs, srcs, sizeof(chan->seen_srcs));
+ chan->seen_valid = true;
+}
+
+/*
+ * Build the table for a set of member sources: ordered by configured
+ * priority, ties kept in the order the hardware table has them, the slots
+ * past the last member naming no source. Filling them with the code for
+ * no source rather than with copies of the last member keeps every
+ * source in exactly one slot, so the order the table encodes is the
+ * order the priorities say.
+ */
+static void sit9531x_prio_table_build(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u16 members, u8 *srcs)
+{
+ const struct sit9531x_chan *chan = &sitdev->chan[pll_idx];
+ u8 order[SIT9531X_PRIO_NUM_SRC], n = 0, i, j, src;
+
+ for (src = 0; src < SIT9531X_PRIO_NUM_SRC; src++)
+ if (members & BIT(src))
+ order[n++] = src;
+
+ /* Insertion sort: at most a dozen entries. */
+ for (i = 1; i < n; i++) {
+ u8 cur = order[i];
+
+ for (j = i; j > 0; j--) {
+ u8 prev = order[j - 1];
+ u16 kc, kp;
+
+ kc = (chan->cfg_known & BIT(cur)) ?
+ chan->cfg_prio[cur] : U8_MAX;
+ kp = (chan->cfg_known & BIT(prev)) ?
+ chan->cfg_prio[prev] : U8_MAX;
+ if (kc == kp) {
+ /* Keep the hardware order among equals. */
+ kc = chan->prio_last[cur] ?: U8_MAX;
+ kp = chan->prio_last[prev] ?: U8_MAX;
+ }
+ if (kp <= kc)
+ break;
+ order[j] = prev;
+ }
+ order[j] = cur;
+ }
+
+ for (i = 0; i < SIT9531X_PRIO_MAX_SLOTS; i++)
+ srcs[i] = i < n ? order[i] : SIT9531X_PRIO_SRC_NONE;
+}
+
+/*
+ * Refresh a PLL's cached view of its priority table from the source codes
+ * the table holds -- here after a write, and once per poll from the
+ * read-back in sit9531x_chan_state_fetch().
+ *
+ * The membership mask is what the pin state getters test, the per-slot
+ * copy is what a rewrite compares against, and the first-slot array
+ * orders sources of equal priority, so none of them costs a register read
+ * per pin.
*/
static void sit9531x_prio_mask_build(struct sit9531x_dev *sitdev, u8 pll_idx,
const u8 *srcs)
{
+ struct sit9531x_chan *chan = &sitdev->chan[pll_idx];
+ u8 first[SIT9531X_PRIO_NUM_SRC] = { 0 };
u16 mask = 0;
- u8 slot;
+ u8 slot, src, src_canon;
for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) {
- u8 src = srcs[slot] & SIT9531X_PRIO_NIBBLE_MASK;
+ src = srcs[slot];
+ src &= SIT9531X_PRIO_NIBBLE_MASK;
+ chan->prio_srcs[slot] = src;
+ src_canon = sit9531x_prio_src_canon(sitdev, src);
+ if (!sit9531x_prio_src_usable(src))
+ continue;
+
+ mask |= BIT(src_canon);
+ if (!first[src_canon])
+ first[src_canon] = slot + 1;
+ }
+
+ /*
+ * Assign unconditionally: a source that has left the table has no
+ * slot, and leaving its old one behind would keep reporting it as
+ * listed for as long as the device runs.
+ */
+ for (src = 0; src < SIT9531X_PRIO_NUM_SRC; src++)
+ chan->prio_last[src] = first[src];
+
+ chan->prio_mask = mask;
+}
+
+/* Attempts to release a forced holdover before reporting it stuck. */
+#define SIT9531X_HO_CLEAR_TRIES 3
+
+static int sit9531x_prio_table_read(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 *srcs);
+
+/*
+ * First source in a table that the device could lock to now, compared as
+ * canonical codes, or SIT9531X_PRIO_SRC_NONE when no listed source has a
+ * signal.
+ */
+static u8 sit9531x_prio_top_live(const struct sit9531x_dev *sitdev,
+ const u8 *srcs)
+{
+ u8 i, src;
+
+ for (i = 0; i < SIT9531X_PRIO_MAX_SLOTS; i++) {
+ src = srcs[i] & SIT9531X_PRIO_NIBBLE_MASK;
+ src = sit9531x_prio_src_canon(sitdev, src);
+ if (sit9531x_prio_src_usable(src) &&
+ sit9531x_prio_src_live(sitdev, src))
+ return src;
+ }
+
+ return SIT9531X_PRIO_SRC_NONE;
+}
+
+/*
+ * Choose the active selection for a table about to be latched. After a
+ * table write the PLL goes to the source the selection names; it moves to
+ * another on its own only when that source loses its signal, which is an
+ * event, not a state. So the selection has to name a source the PLL can
+ * use, and it follows the priorities the way the DPLL interface defines
+ * automatic mode -- the highest-priority valid input:
+ *
+ * - When the highest-priority source with signal is not the one the
+ * table held before, the priorities now put another source first, and
+ * the selection goes to it.
+ * - Otherwise the write only reorders sources below it, or removes one
+ * the PLL is not on, and the selection stays where it is while that
+ * source is still listed and has signal: a change further down the
+ * table must not pull a PLL off a healthy reference.
+ * - Otherwise the first listed source with signal; with none alive the
+ * first listed one is as good as any.
+ *
+ * A selection that is still listed but has lost its signal is moved too.
+ * The PLL has then fallen back on its own and the selection still names
+ * the dead source; left alone, the next table write sends the PLL back to
+ * it and it unlocks. The device falls back to the best listed source
+ * that has signal, which is the one chosen here, so moving the selection
+ * there does not move the PLL.
+ *
+ * @old is the table the device holds before this write.
+ */
+static u8 sit9531x_prio_activesel_pick(struct sit9531x_dev *sitdev,
+ const u8 *old, const u8 *srcs, u8 cur)
+{
+ u8 top, i;
+
+ top = sit9531x_prio_top_live(sitdev, srcs);
+ if (top != SIT9531X_PRIO_SRC_NONE &&
+ top != sit9531x_prio_top_live(sitdev, old))
+ return top;
+
+ /*
+ * The table is built from canonical codes, so compare in the same
+ * terms: a differential pair selected through its N-lane code is the
+ * P-lane entry.
+ */
+ cur = sit9531x_prio_src_canon(sitdev, cur & SIT9531X_PRIO_NIBBLE_MASK);
+
+ if (sit9531x_prio_src_usable(cur) &&
+ sit9531x_prio_src_live(sitdev, cur))
+ for (i = 0; i < SIT9531X_PRIO_MAX_SLOTS; i++)
+ if (srcs[i] == cur)
+ return cur;
+
+ if (top != SIT9531X_PRIO_SRC_NONE)
+ return top;
+
+ return srcs[0];
+}
+
+static int sit9531x_prio_table_commit(struct sit9531x_dev *sitdev, u8 pll_idx,
+ const u8 *srcs)
+{
+ struct sit9531x_chan *chan = &sitdev->chan[pll_idx];
+ u8 val, last_old, slot, attempt, written = 0, restored = 0;
+ u8 now[SIT9531X_PRIO_MAX_SLOTS];
+ int rc = 0, prg_rc, ho_rc = 0;
+ bool empty, ho_foreign;
+ u16 reg;
+
+ empty = !sit9531x_prio_src_usable(srcs[0]);
+
+ /*
+ * A forced holdover already in place is this driver's own when the
+ * table lists nothing -- the hold an empty table keeps -- or when a
+ * release is still owed from an earlier write. Any other was put
+ * there by the loaded configuration or by a tool, and a table write
+ * is no reason to lift it: the sequence runs under it and leaves it
+ * as found.
+ */
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_HO_CTRL,
+ &val);
+ if (rc)
+ return rc;
+
+ ho_foreign = (val & BIT(SIT9531X_PLL_HO_FORCE_BIT)) &&
+ chan->prio_mask && !chan->ho_release_pending;
+
+ /*
+ * A write that reports an error may still have reached the part, so
+ * a failure here does not prove the bit stayed clear: nothing is
+ * written to the table, but the release below still runs.
+ */
+ rc = sit9531x_write_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_HO_CTRL,
+ val | BIT(SIT9531X_PLL_HO_FORCE_BIT));
+ if (rc)
+ goto release;
+
+ usleep_range(10000, 12000);
+
+ /*
+ * Two slots share a register, and this writes every slot, so both
+ * nibbles are known for every register but the last -- build those
+ * bytes outright. Reading first would raise the question of what a
+ * read returns between the write and the latch, and the answer does
+ * not matter if nothing is read.
+ */
+ for (slot = 0; slot + 1 < SIT9531X_PRIO_MAX_SLOTS; slot += 2) {
+ reg = sit9531x_prio_reg(pll_idx, slot);
+
+ val = sit9531x_prio_slot_set(0, slot, srcs[slot]);
+ val = sit9531x_prio_slot_set(val, slot + 1, srcs[slot + 1]);
+
+ /*
+ * Counted before the write: a write that reports an error
+ * may still have reached the part, so the rollback has to
+ * cover that register too.
+ */
+ written = slot + 2;
+ rc = sit9531x_write_u8(sitdev, reg, val);
+ if (rc)
+ goto rollback;
+ }
+
+ /*
+ * The last register carries slot 10 in its high nibble and the
+ * device's active selection in its low one; see
+ * sit9531x_prio_activesel_pick() for how the selection is chosen.
+ * It needs the signal state now, not as of the last poll.
+ *
+ * The slot setter picks its nibble by parity, so the selection is
+ * addressed as the slot past the last one. This register has not
+ * been written yet in this sequence, so the read returns what the
+ * device is running with, and that byte is what a rollback puts
+ * back.
+ *
+ * A table naming no source at all is what removing the last one
+ * asks for. There is nothing to point the selection at -- the code
+ * for no source is not one the selection takes -- so the nibble is
+ * left alone and the PLL is kept in holdover below instead.
+ */
+ reg = sit9531x_prio_reg(pll_idx, slot);
+
+ rc = sit9531x_read_u8(sitdev, reg, &last_old);
+ if (rc)
+ goto rollback;
+
+ val = sit9531x_prio_slot_set(last_old, slot, srcs[slot]);
+
+ if (!empty) {
+ u8 sel = sit9531x_prio_slot_get(val, slot + 1);
+
+ /*
+ * Without a fresh read the pick would run on the last
+ * poll's monitor state, up to a poll period old. A source
+ * that lost its signal since would still look live, and a
+ * selection naming a dead source sends the PLL to it and it
+ * unlocks, so no selection is written without the read: the
+ * slots written so far are rolled back and the request
+ * fails.
+ */
+ rc = sit9531x_input_mon_fetch(sitdev);
+ if (rc)
+ goto rollback;
+ sel = sit9531x_prio_activesel_pick(sitdev, chan->prio_srcs,
+ srcs, sel);
+ val = sit9531x_prio_slot_set(val, slot + 1, sel);
+ }
+
+ written = SIT9531X_PRIO_MAX_SLOTS;
+ rc = sit9531x_write_u8(sitdev, reg, val);
+ if (rc)
+ goto rollback;
+
+rollback:
+ if (rc && written) {
+ /*
+ * Put the slots that may have reached the device back the
+ * way they were, the register whose write reported the error
+ * included. Latching a table that is neither the previous
+ * order nor the requested one hands the reference selection
+ * loop a priority list nobody asked for. The cache is the
+ * table as last read, which is what those slots held; the
+ * last register also carries the selection, so it goes back
+ * as it was read above.
+ */
+ for (slot = 0; slot < written; slot += 2) {
+ u8 old;
+
+ if (slot + 1 < SIT9531X_PRIO_MAX_SLOTS) {
+ old = sit9531x_prio_slot_set(0, slot,
+ chan->prio_srcs[slot]);
+ old = sit9531x_prio_slot_set(old, slot + 1,
+ chan->prio_srcs[slot + 1]);
+ } else {
+ old = last_old;
+ }
+ if (sit9531x_write_u8(sitdev,
+ sit9531x_prio_reg(pll_idx, slot),
+ old))
+ break;
- if (sit9531x_prio_src_usable(src))
- mask |= BIT(src);
+ restored = slot + 2;
+ }
+ written = restored;
}
- sitdev->chan[pll_idx].prio_mask = mask;
+ /*
+ * Latch unconditionally: the slots that reached the device are in
+ * the table regardless, so the latch keeps hardware and the cache
+ * refresh below consistent with what was actually written.
+ */
+ prg_rc = sit9531x_prio_prg_commit(sitdev);
+ if (prg_rc && !rc)
+ rc = prg_rc;
+
+ /*
+ * Refresh the cache so a get that follows a set does not have to
+ * wait for the next poll. After a complete write that is the table
+ * just written. After a failure it is whatever the device holds
+ * now -- part request, part restore -- so read it back rather than
+ * piece it together: the membership test decides what a failed
+ * request rolls back, and it must not answer for writes that did not
+ * land. A read-back that fails too leaves the next poll to do it.
+ */
+ if (!rc) {
+ sit9531x_prio_mask_build(sitdev, pll_idx, srcs);
+ memcpy(chan->seen_srcs, srcs, sizeof(chan->seen_srcs));
+ chan->seen_valid = true;
+ } else if (!sit9531x_prio_table_read(sitdev, pll_idx, now)) {
+ sit9531x_prio_mask_build(sitdev, pll_idx, now);
+ memcpy(chan->seen_srcs, now, sizeof(chan->seen_srcs));
+ chan->seen_valid = true;
+ }
+
+release:
+ /*
+ * A table that names no source keeps the PLL in the holdover forced
+ * above: that is the one state in which it follows no input, which
+ * is what disconnecting every input asks for. The selection nibble
+ * alone would not do it -- it still names the old source, and the
+ * PLL keeps following that one for as long as it has signal. The
+ * next table write that lists a source releases it.
+ *
+ * Judged on the table the device holds now, as the refresh above
+ * read it, not on the one requested: a write that failed on a PLL
+ * listing nothing leaves it listing nothing, and releasing the hold
+ * then would hand the PLL back to the source the selection still
+ * names while every pin reports disconnected. An empty table whose
+ * latch failed is the same case from the other side.
+ */
+ if (!chan->prio_mask) {
+ dev_dbg(sitdev->dev,
+ "PLL%c: no source listed, holdover kept\n",
+ 'A' + pll_idx);
+ chan->ho_release_pending = false;
+ return rc;
+ }
+
+ if (ho_foreign) {
+ dev_dbg(sitdev->dev,
+ "PLL%c: holdover was forced before the write, left in place\n",
+ 'A' + pll_idx);
+ return rc;
+ }
+
+ /*
+ * Release the forced holdover. Apart from an empty table, nothing
+ * in the driver keeps this bit set, and a retry of the same request
+ * finds the table already as asked and does not come back here, so
+ * a PLL left with it would report holdover until some unrelated
+ * table write on the same PLL cleared it. Retry before giving up;
+ * if it stays set, say so and leave the release owed to the poll,
+ * which retries it every tick until it lands.
+ */
+ for (attempt = 0; attempt < SIT9531X_HO_CLEAR_TRIES; attempt++) {
+ ho_rc = sit9531x_update_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_HO_CTRL,
+ BIT(SIT9531X_PLL_HO_FORCE_BIT),
+ 0);
+ if (!ho_rc)
+ break;
+ usleep_range(1000, 2000);
+ }
+ if (ho_rc) {
+ dev_err(sitdev->dev, "PLL%c left in forced holdover: %d\n",
+ 'A' + pll_idx, ho_rc);
+ if (!rc)
+ rc = ho_rc;
+ }
+ chan->ho_release_pending = !!ho_rc;
+
+ return rc;
}
/*
@@ -479,6 +1142,155 @@ static int sit9531x_prio_table_read(struct sit9531x_dev *sitdev, u8 pll_idx,
return 0;
}
+/*
+ * Rewrite a PLL's table for a new member set, unless it would come out as
+ * the table already holds: every write forces the PLL into holdover for
+ * the length of the sequence, so one that changes nothing is a
+ * disturbance nobody asked for.
+ */
+static int sit9531x_prio_table_apply(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u16 members)
+{
+ u8 srcs[SIT9531X_PRIO_MAX_SLOTS];
+
+ if (hweight16(members) > SIT9531X_PRIO_MAX_SLOTS)
+ return -ENOSPC;
+
+ sit9531x_prio_table_build(sitdev, pll_idx, members, srcs);
+ if (!memcmp(srcs, sitdev->chan[pll_idx].prio_srcs, sizeof(srcs)))
+ return 0;
+
+ return sit9531x_prio_table_commit(sitdev, pll_idx, srcs);
+}
+
+/*
+ * sit9531x_input_prio_set - set an input's priority on a PLL
+ * @input_idx: input source in hardware encoding (0-11, see
+ * sit9531x_input_hw_src())
+ * @prio: priority, lower is preferred
+ *
+ * Records the priority and, when the source is in the PLL's table,
+ * rebuilds the table from the configured priorities. A source that is
+ * not in the table keeps the priority for when it is connected: that is
+ * the pin's state, and it belongs to the state setter. Other sources
+ * keep theirs either way, so no sibling's priority moves.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ *
+ * Return: 0 on success, -EINVAL for a bad PLL or source, <0 on error
+ */
+int sit9531x_input_prio_set(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx, u8 prio)
+{
+ struct sit9531x_chan *chan;
+ u8 old_prio;
+ u16 old_known;
+ int rc;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (pll_idx >= SIT9531X_NUM_PLLS)
+ return -EINVAL;
+ input_idx = sit9531x_prio_src_canon(sitdev, input_idx);
+ if (input_idx >= SIT9531X_PRIO_NUM_SRC)
+ return -EINVAL;
+
+ chan = &sitdev->chan[pll_idx];
+ old_prio = chan->cfg_prio[input_idx];
+ old_known = chan->cfg_known;
+ chan->cfg_prio[input_idx] = prio;
+ chan->cfg_known |= BIT(input_idx);
+
+ if (!(chan->prio_mask & BIT(input_idx)))
+ return 0;
+
+ /*
+ * The table is built from the configured priority, so it has to be
+ * recorded before the rebuild; a rebuild that failed takes the
+ * record back with it. Left in place, a refused priority would be
+ * reported by the getter, notified by the poll, and written by the
+ * next rebuild for any other reason.
+ */
+ rc = sit9531x_prio_table_apply(sitdev, pll_idx, chan->prio_mask);
+ if (rc) {
+ chan->cfg_prio[input_idx] = old_prio;
+ chan->cfg_known = old_known;
+ }
+
+ return rc;
+}
+
+/*
+ * sit9531x_input_prio_remove - drop an input from a PLL's priority table
+ * @input_idx: input source in hardware encoding
+ *
+ * Rebuilds the table without the source, which makes a disconnected
+ * input ineligible for automatic reference selection, not just gated at
+ * the input buffer. The source keeps its configured priority for when it
+ * comes back. Removing a source that is absent succeeds without touching
+ * the table. Removing the last one leaves a table that names no source;
+ * the commit then keeps the PLL in holdover, which is what disconnecting
+ * every input asks for.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int sit9531x_input_prio_remove(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx)
+{
+ struct sit9531x_chan *chan;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (pll_idx >= SIT9531X_NUM_PLLS)
+ return -EINVAL;
+ input_idx = sit9531x_prio_src_canon(sitdev, input_idx);
+ if (input_idx >= SIT9531X_PRIO_NUM_SRC)
+ return -EINVAL;
+
+ chan = &sitdev->chan[pll_idx];
+ if (!(chan->prio_mask & BIT(input_idx)))
+ return 0;
+
+ return sit9531x_prio_table_apply(sitdev, pll_idx,
+ chan->prio_mask & ~BIT(input_idx));
+}
+
+/*
+ * sit9531x_input_prio_add - make an input eligible in a PLL's table
+ * @input_idx: input source in hardware encoding
+ *
+ * Puts the source back into the table at its configured priority; one the
+ * PLL never listed has the lowest slot (see sit9531x_prio_cfg_seed()). A
+ * source that is already listed leaves the table untouched.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ *
+ * Return: 0 on success, -ENOSPC when the table cannot hold another
+ * source, <0 on error
+ */
+int sit9531x_input_prio_add(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx)
+{
+ struct sit9531x_chan *chan;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (pll_idx >= SIT9531X_NUM_PLLS)
+ return -EINVAL;
+ input_idx = sit9531x_prio_src_canon(sitdev, input_idx);
+ if (input_idx >= SIT9531X_PRIO_NUM_SRC)
+ return -EINVAL;
+
+ chan = &sitdev->chan[pll_idx];
+ if (chan->prio_mask & BIT(input_idx))
+ return 0;
+
+ return sit9531x_prio_table_apply(sitdev, pll_idx,
+ chan->prio_mask | BIT(input_idx));
+}
+
/* XO doubler register */
#define SIT9531X_REG_XO2_GENERIC SIT9531X_REG(0x00, 0x2D)
#define SIT9531X_XO_DOUBLER_ENB_BIT 7 /* inverted: 0 = enabled */
@@ -569,7 +1381,8 @@ int sit9531x_clear_notifications(struct sit9531x_dev *sitdev)
* @index: logical input index
*
* Reads whether the lane's receiver is on, from the Page 0x02 force and
- * state bits.
+ * state bits. Signal status comes from sit9531x_input_mon_fetch(),
+ * which reads every lane's clock monitor in one pass.
*/
static int sit9531x_ref_state_fetch(struct sit9531x_dev *sitdev, u8 index)
{
@@ -661,6 +1474,73 @@ static int sit9531x_pll_is_active(struct sit9531x_dev *sitdev, u8 pll_idx,
return 0;
}
+/*
+ * Read the reference the device feeds a PLL.
+ *
+ * The active selection names the source the PLL was told to follow. The
+ * input subsystem feeds the PLL that source while it has signal and falls
+ * back to another listed source on its own when it does not, without
+ * touching the selection, so after such a fallback the selection no
+ * longer says which reference the PLL runs on. The debug status bus
+ * does: addressed at the routed reference, it returns the source code
+ * the input subsystem currently feeds the PLL.
+ *
+ * This names the source and nothing more. Whether the PLL is locked to
+ * it is read from the outer-loop lock and the lane's clock monitor; in
+ * free-run the PLL is fed nothing and the bus is not consulted, so the
+ * caller checks the mode first.
+ *
+ * @ref is the logical input index, SIT9531X_INTSYNC_PIN_ID for the
+ * inter-PLL sync net, or SIT9531X_REF_INVALID when the code names no
+ * registered input. Caller must hold sitdev->multiop_lock.
+ */
+static int sit9531x_chan_routed_ref_read(struct sit9531x_dev *sitdev,
+ u8 pll_idx, u8 *ref)
+{
+ int rc, lock_rc;
+ u8 v, i;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ rc = sit9531x_write_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_DEBUG,
+ SIT9531X_PLL_DEBUG_UNLOCK);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_write_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DBG_STATUS_ADDR,
+ SIT9531X_DBG_STATUS_ROUTED_SRC);
+ if (rc)
+ goto relock;
+
+ /* The first reads return earlier latches; the last is current. */
+ for (i = 0; i < SIT9531X_DBG_STATUS_READS; i++) {
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DBG_STATUS_DATA,
+ &v);
+ if (rc)
+ goto relock;
+ }
+
+ /*
+ * Canonicalise like the table entries: a differential pair fed
+ * through its N-lane code is the P-lane pin.
+ */
+ v = sit9531x_prio_src_canon(sitdev, v & SIT9531X_DBG_ROUTED_SRC_MASK);
+ *ref = sit9531x_hw_src_input(v);
+
+relock:
+ /*
+ * Lock the debug registers again. A read that failed is reported
+ * ahead of a lock that did; a lock that failed on its own leaves the
+ * page open to a stray write and is reported too.
+ */
+ lock_rc = sit9531x_write_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_DEBUG,
+ SIT9531X_PLL_DEBUG_LOCK);
+
+ return rc ? rc : lock_rc;
+}
+
/*
* sit9531x_chan_state_fetch - read PLL channel status from hardware
*
@@ -669,11 +1549,11 @@ static int sit9531x_pll_is_active(struct sit9531x_dev *sitdev, u8 pll_idx,
*/
static int sit9531x_chan_state_fetch(struct sit9531x_dev *sitdev, u8 pll_idx)
{
- u8 status, outer_lol, input_sel, inner_lol, ho_freeze, activesel_reg;
struct sit9531x_chan *chan = &sitdev->chan[pll_idx];
+ u8 status, outer_lol, inner_lol, ho_freeze, pll_status_1;
u8 srcs[SIT9531X_PRIO_MAX_SLOTS];
- u8 pll_status_1;
- bool active;
+ u8 routed = SIT9531X_REF_INVALID;
+ bool active, tracking;
int rc;
/*
@@ -696,22 +1576,6 @@ static int sit9531x_chan_state_fetch(struct sit9531x_dev *sitdev, u8 pll_idx)
if (rc)
return rc;
- /*
- * Read the input source the PLL has currently selected as its
- * active reference. This lives in the low nibble of the last
- * register of the PLL's page-1 priority block (CLK_ACTIVESEL_PLL),
- * not on the PLL page -- PLL-page 0x29 is a config register.
- */
- activesel_reg = SIT9531X_PRIO_BASE_REG +
- SIT9531X_PRIO_REGS_PER_PLL * pll_idx +
- SIT9531X_PRIO_ACTIVESEL_OFF;
- rc = sit9531x_read_u8(sitdev,
- SIT9531X_REG(SIT9531X_PAGE_PRIOSYS,
- activesel_reg),
- &input_sel);
- if (rc)
- return rc;
-
rc = sit9531x_read_u8(sitdev, SIT9531X_REG_PLL_INNER_LOL_STATUS,
&inner_lol);
if (rc)
@@ -722,6 +1586,21 @@ static int sit9531x_chan_state_fetch(struct sit9531x_dev *sitdev, u8 pll_idx)
if (rc)
return rc;
+ /*
+ * The reference the device feeds the PLL, read only while the PLL
+ * tracks one -- running, locked, outer loop on, not frozen -- which
+ * is the only case the pin state reports it. In free-run the PLL
+ * is fed nothing and the status bus is not consulted.
+ */
+ tracking = active && !(outer_lol & BIT(pll_idx)) &&
+ !(status & SIT9531X_PLL_STATUS_OUTER_DIS) &&
+ !(ho_freeze & BIT(pll_idx));
+ if (tracking) {
+ rc = sit9531x_chan_routed_ref_read(sitdev, pll_idx, &routed);
+ if (rc)
+ return rc;
+ }
+
rc = sit9531x_read_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_STATUS_1,
&pll_status_1);
if (rc)
@@ -739,12 +1618,21 @@ static int sit9531x_chan_state_fetch(struct sit9531x_dev *sitdev, u8 pll_idx)
sit9531x_prio_mask_build(sitdev, pll_idx, srcs);
+ /*
+ * The configured priorities come from the table the first time it
+ * is read, and again whenever it no longer matches what the driver
+ * last wrote: something else -- a profile reload, a direct I2C
+ * tool -- rewrote it, and that table is now the configuration.
+ */
+ if (!chan->seen_valid ||
+ memcmp(srcs, chan->seen_srcs, sizeof(chan->seen_srcs)))
+ sit9531x_prio_cfg_seed(sitdev, pll_idx, srcs);
+
/* STATUS_1_GENERIC reports loss of lock, so invert it. */
chan->active = active;
chan->locked = active && !(outer_lol & BIT(pll_idx));
chan->mode = !!(status & SIT9531X_PLL_STATUS_OUTER_DIS);
- chan->selected_ref =
- sit9531x_hw_src_input(input_sel & SIT9531X_PRIO_NIBBLE_MASK);
+ chan->routed_ref = routed;
chan->inner_lol = !!(inner_lol & BIT(pll_idx));
chan->ho_freeze = !!(ho_freeze & BIT(pll_idx));
chan->ho_valid = !!(pll_status_1 & SIT9531X_PLL_STATUS_1_HO_VALID);
@@ -913,6 +1801,13 @@ static int sit9531x_dev_state_fetch(struct sit9531x_dev *sitdev)
}
}
+ rc = sit9531x_input_mon_fetch(sitdev);
+ if (rc) {
+ dev_err(sitdev->dev,
+ "Failed to read the input clock monitors: %d\n", rc);
+ return rc;
+ }
+
/*
* The priority-table read walks the Page-1 registers, so it runs
* with multiop_lock held like every other multi-register sequence.
@@ -938,14 +1833,22 @@ static int sit9531x_dev_state_fetch(struct sit9531x_dev *sitdev)
}
}
+ /*
+ * The channel fetch walks the priority table and the debug status
+ * bus, multi-register sequences that expect the lock; nothing races
+ * with them yet, but they are serialized the same way as at runtime.
+ */
+ mutex_lock(&sitdev->multiop_lock);
for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
rc = sit9531x_chan_state_fetch(sitdev, i);
- if (rc) {
- dev_err(sitdev->dev,
- "Failed to fetch PLL%c state: %d\n",
- 'A' + i, rc);
- return rc;
- }
+ if (rc)
+ break;
+ }
+ mutex_unlock(&sitdev->multiop_lock);
+ if (rc) {
+ dev_err(sitdev->dev, "Failed to fetch PLL%c state: %d\n",
+ 'A' + i, rc);
+ return rc;
}
return 0;
@@ -961,6 +1864,71 @@ static void sit9531x_dev_ref_states_update(struct sit9531x_dev *sitdev)
dev_warn(sitdev->dev,
"Failed to get REF%u status: %d\n", i, rc);
}
+
+ rc = sit9531x_input_mon_fetch(sitdev);
+ if (rc)
+ dev_warn(sitdev->dev,
+ "Failed to read the input clock monitors: %d\n", rc);
+}
+
+/*
+ * sit9531x_ref_pll_mask_rebuild - re-derive the input receiver refcounts
+ *
+ * ref->pll_mask decides when an input receiver may be powered down, and
+ * the connect and disconnect paths maintain it by hand. A request that
+ * failed part way through leaves it describing a table the device does
+ * not hold, and nothing else corrected it: a later disconnect could then
+ * drop the count to zero and gate an input another PLL is still locked
+ * to. Re-derive every mask from the tables the poll has just read. No
+ * extra bus traffic -- sit9531x_chan_state_fetch() refreshed the masks
+ * this reads immediately before.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+static void sit9531x_ref_pll_mask_rebuild(struct sit9531x_dev *sitdev)
+{
+ u8 pll_idx, src, index;
+
+ for (index = 0; index < sitdev->info->num_inputs; index++)
+ sitdev->ref[index].pll_mask = 0;
+
+ for (pll_idx = 0; pll_idx < SIT9531X_NUM_PLLS; pll_idx++) {
+ u16 mask = sitdev->chan[pll_idx].prio_mask;
+
+ for (src = 0; src < SIT9531X_PRIO_NUM_SRC; src++) {
+ if (!(mask & BIT(src)))
+ continue;
+
+ index = sit9531x_hw_src_input(src);
+ if (index < sitdev->info->num_inputs)
+ sitdev->ref[index].pll_mask |= BIT(pll_idx);
+ }
+ }
+}
+
+/*
+ * Release a forced holdover that a table write owes. The write forces
+ * holdover and releases it at the end; when the release failed, nothing
+ * else in the driver clears the bit -- a retry of the same request finds
+ * the table already as asked and does not run the sequence again -- so
+ * the poll retries it each tick until it lands.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+static void sit9531x_ho_release_retry(struct sit9531x_dev *sitdev, u8 pll_idx)
+{
+ struct sit9531x_chan *chan = &sitdev->chan[pll_idx];
+
+ if (!chan->ho_release_pending)
+ return;
+
+ if (sit9531x_update_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_HO_CTRL,
+ BIT(SIT9531X_PLL_HO_FORCE_BIT), 0))
+ return;
+
+ chan->ho_release_pending = false;
+ dev_info(sitdev->dev, "PLL%c: forced holdover released\n",
+ 'A' + pll_idx);
}
static void sit9531x_dev_chan_states_update(struct sit9531x_dev *sitdev)
@@ -968,12 +1936,15 @@ static void sit9531x_dev_chan_states_update(struct sit9531x_dev *sitdev)
int i, rc;
for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
+ sit9531x_ho_release_retry(sitdev, i);
rc = sit9531x_chan_state_fetch(sitdev, i);
if (rc)
dev_warn(sitdev->dev,
"Failed to get PLL%c state: %d\n",
'A' + i, rc);
}
+
+ sit9531x_ref_pll_mask_rebuild(sitdev);
}
/*
@@ -1265,6 +2236,84 @@ static void sit9531x_pll_states_report(struct sit9531x_dev *sitdev)
}
}
+/*
+ * sit9531x_input_pin_is_registrable - check if an input pin is registrable
+ *
+ * Split out so input-model changes stay local to this helper.
+ *
+ * Return: true if the input pin should be registered, false otherwise
+ */
+static bool sit9531x_input_pin_is_registrable(struct sit9531x_dev *sitdev,
+ u8 index)
+{
+ if (index >= sitdev->info->num_inputs)
+ return false;
+
+ /*
+ * The N lane of a differentially-configured pair is not a
+ * standalone input and is skipped (zl3073x model).
+ */
+ if (sit9531x_input_is_n(index) &&
+ sitdev->ref[index].sig_mode == SIT9531X_MODE_DE)
+ return false;
+
+ return true;
+}
+
+/*
+ * Warn about a pin node in the firmware description whose reg names no
+ * pin this device can have: an input lane or an output the variant does
+ * not have, or the N lane of a pair the configuration runs differential.
+ * Nodes are looked up from the pins (sit9531x_pin_props_get()), so such a
+ * node would otherwise have its label and frequencies dropped without a
+ * word. The binding bounds reg per variant; this catches what reaches
+ * the driver unvalidated, and the pair mode, which only the loaded
+ * configuration decides. An output that exists but that no PLL drives
+ * gets no pin either and is not reported: which outputs a configuration
+ * uses is not a fault in the description.
+ */
+static void sit9531x_pin_nodes_check(struct sit9531x_dev *sitdev)
+{
+ struct fwnode_handle *pins, *node;
+ bool found;
+ u32 reg;
+ u8 i;
+
+ pins = device_get_named_child_node(sitdev->dev, "input-pins");
+ fwnode_for_each_child_node(pins, node) {
+ if (fwnode_property_read_u32(node, "reg", ®))
+ continue;
+ if (reg < sitdev->info->num_inputs &&
+ sit9531x_input_pin_is_registrable(sitdev, reg))
+ continue;
+ dev_warn(sitdev->dev,
+ "input-pins/%pfwP: reg %u is %s, node ignored\n",
+ node, reg,
+ reg < sitdev->info->num_inputs ?
+ "the N lane of a differential pair" :
+ "not an input lane");
+ }
+ fwnode_handle_put(pins);
+
+ pins = device_get_named_child_node(sitdev->dev, "output-pins");
+ fwnode_for_each_child_node(pins, node) {
+ if (fwnode_property_read_u32(node, "reg", ®))
+ continue;
+ found = false;
+ for (i = 0; i < sitdev->info->num_outputs; i++) {
+ if (sitdev->info->clkout_map[i] == reg) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ dev_warn(sitdev->dev,
+ "output-pins/%pfwP: reg %u is not an output of %s, node ignored\n",
+ node, reg, sitdev->info->name);
+ }
+ fwnode_handle_put(pins);
+}
+
/*
* sit9531x_dev_start - start normal operation
*
@@ -1287,6 +2336,8 @@ int sit9531x_dev_start(struct sit9531x_dev *sitdev)
sit9531x_pll_states_report(sitdev);
mutex_unlock(&sitdev->multiop_lock);
+ sit9531x_pin_nodes_check(sitdev);
+
list_for_each_entry(sitdpll, &sitdev->dplls, list) {
rc = sit9531x_dpll_register(sitdpll);
if (rc) {
@@ -1468,9 +2519,9 @@ static void sit9531x_dpll_pins_unregister(struct sit9531x_dpll *sitdpll)
* @dir: pin direction
* @index: pin hardware index
*
- * Only the XO pin has a complete pin-op table in this patch, so only
- * the XO pin is registrable here. Other pin classes are registered
- * once their state callbacks land in the following patches.
+ * For input pins: delegate to sit9531x_input_pin_is_registrable().
+ * A pin class whose state callback the tree does not have yet is not
+ * registrable: the core refuses a pin without one.
*
* Return: true if pin should be registered, false otherwise
*/
@@ -1478,15 +2529,15 @@ static bool sit9531x_dpll_pin_is_registrable(struct sit9531x_dpll *sitdpll,
enum dpll_pin_direction dir,
u8 index)
{
- /*
- * Only the XO pin has a complete pin-op table in this patch.
- * Other pin classes are registered once their state callbacks
- * land in the following patches.
- */
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+
if (dir != DPLL_PIN_DIRECTION_INPUT)
return false;
- return index == SIT9531X_MAX_INPUTS;
+ if (index == SIT9531X_MAX_INPUTS)
+ return true;
+
+ return sit9531x_input_pin_is_registrable(sitdev, index);
}
/*
diff --git a/drivers/dpll/sit9531x/core.h b/drivers/dpll/sit9531x/core.h
index 28fc99f50063..cf4dd5f82d05 100644
--- a/drivers/dpll/sit9531x/core.h
+++ b/drivers/dpll/sit9531x/core.h
@@ -38,7 +38,7 @@
#define SIT9531X_INTSYNC_OUT_PIN_ID SIT9531X_MAX_OUTPUTS
#define SIT9531X_STATUS_POLL_MS 500
-/* selected_ref value when the active source is not a registered input */
+/* routed_ref value when the PLL is fed no registered input */
#define SIT9531X_REF_INVALID 0xFF
/* SiTime IEEE OUI for EUI-64 generation */
@@ -77,6 +77,9 @@ enum sit9531x_signal_mode {
* @freq: configured frequency in Hz
* @enabled: the lane's receiver is on
* @pll_mask: bitmask of PLLs this input feeds (bit 0 = PLLA)
+ * @los: the clock monitor reports loss of signal on the lane
+ * @qual_fail: the clock monitor reports a frequency drift on the
+ * lane while it still has signal
* @sig_mode: signal mode of the pair this lane belongs to
* (detected from CLKINx_INPUT_MODE at probe)
*/
@@ -84,6 +87,8 @@ struct sit9531x_ref {
u64 freq;
bool enabled;
u8 pll_mask;
+ bool los;
+ bool qual_fail;
enum sit9531x_signal_mode sig_mode;
};
@@ -113,15 +118,25 @@ struct sit9531x_out {
* loss-of-lock bit stays clear because nothing drives it
* @locked: PLL is active and its outer loop reports lock
* @mode: 0 = sync (outer loop enabled), 1 = free-run
- * @selected_ref: logical input index of the currently selected
- * reference (the INTSYNC net maps to
- * SIT9531X_INTSYNC_PIN_ID), or SIT9531X_REF_INVALID
- * when the hardware source encoding is reserved
+ * @routed_ref: logical input index of the reference the device
+ * feeds the PLL while it tracks one (the INTSYNC net
+ * maps to SIT9531X_INTSYNC_PIN_ID); SIT9531X_REF_INVALID
+ * when the PLL tracks nothing -- free-run, frozen,
+ * unlocked -- or the source code names no registered
+ * input
* @inner_lol: PLL inner loop loss-of-lock detected
* @ho_freeze: holdover freeze active
* @ho_valid: holdover memory acquired, i.e. the holdover window
* holds a valid estimate to fall back on
- * @prio_mask: bit per hardware source code present in this PLL's
+ * @ho_release_pending: a table write forced holdover and could not release
+ * it; the poll retries the release until it lands
+ * @prio_srcs: cached copy of the priority table, one source code
+ * per slot; refreshed together with @prio_mask, it is
+ * what a rebuilt table is compared against
+ * @prio_last: first slot each source occupies, plus one (0 = the
+ * source is not in the table); refreshed from the same
+ * scan as @prio_mask, so the two never disagree
+ * @prio_mask: bit per canonical source present in this PLL's
* priority table, i.e. the sources it may select. Read
* back from the table by the periodic worker and
* refreshed by every table write, so it tracks the
@@ -141,10 +156,13 @@ struct sit9531x_chan {
bool active;
bool locked;
u8 mode;
- u8 selected_ref;
+ u8 routed_ref;
bool inner_lol;
bool ho_freeze;
bool ho_valid;
+ bool ho_release_pending;
+ u8 prio_srcs[SIT9531X_PRIO_MAX_SLOTS];
+ u8 prio_last[SIT9531X_PRIO_NUM_SRC];
u16 prio_mask;
u8 cfg_prio[SIT9531X_PRIO_NUM_SRC];
u16 cfg_known;
@@ -229,8 +247,18 @@ int sit9531x_update_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,
u8 offset, u8 mask, u8 val);
/* ---- Input enable/disable ---- */
+int sit9531x_input_disable(struct sit9531x_dev *sitdev, u8 index);
+int sit9531x_input_enable(struct sit9531x_dev *sitdev, u8 index);
/* ---- Input priority ---- */
+bool sit9531x_input_prio_present(struct sit9531x_dev *sitdev,
+ u8 pll_idx, u8 input_idx);
+int sit9531x_input_prio_set(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx, u8 prio);
+int sit9531x_input_prio_remove(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx);
+int sit9531x_input_prio_add(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx);
/* ---- Output enable/disable (Hi-Z control) ---- */
diff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c
index e88fa4e29bdf..4c7cae4ce93e 100644
--- a/drivers/dpll/sit9531x/dpll.c
+++ b/drivers/dpll/sit9531x/dpll.c
@@ -34,11 +34,31 @@ sit9531x_dpll_is_xo_pin(const struct sit9531x_dpll_pin *pin)
pin->id == SIT9531X_MAX_INPUTS;
}
+/*
+ * Status of a PLL whose outer loop is frozen in holdover.
+ *
+ * The interface has a PLL report holdover for a hold forced by
+ * disconnecting every input only when it was LOCKED_HO_ACQ before, that
+ * is, when it has a valid holdover estimate to run on; without one it
+ * stays UNLOCKED. Removing the last source from a PLL's table forces
+ * exactly that hold, so a frozen PLL that lists no source is in holdover
+ * only while its memory is valid. A PLL frozen with sources still listed
+ * lost its lock, and is in holdover on the device's say-so.
+ */
+static enum dpll_lock_status
+sit9531x_dpll_frozen_status(const struct sit9531x_chan *chan)
+{
+ if (!chan->prio_mask && !chan->ho_valid)
+ return DPLL_LOCK_STATUS_UNLOCKED;
+
+ return DPLL_LOCK_STATUS_HOLDOVER;
+}
+
/*
* The cached state this reports comes from the outer loss-of-lock byte
* (page 0, reg 0x06), the PLL mode bit (PLL page, reg 0x31), inner LOL
- * (reg 0x92), the holdover freeze byte (reg 0x0A) and the per-PLL
- * holdover-valid bit (PLL page, reg 0x06).
+ * (reg 0x92), the holdover freeze byte (reg 0x0A), the per-PLL
+ * holdover-valid bit (PLL page, reg 0x06) and the priority table.
*/
static int
sit9531x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
@@ -71,7 +91,7 @@ sit9531x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
* under a locked status would never reach userspace. An
* inner loop that is not locked is not a locked PLL.
*/
- *status = chan->ho_freeze ? DPLL_LOCK_STATUS_HOLDOVER :
+ *status = chan->ho_freeze ? sit9531x_dpll_frozen_status(chan) :
DPLL_LOCK_STATUS_UNLOCKED;
} else if (chan->mode) {
/*
@@ -90,7 +110,7 @@ sit9531x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
* counts a frozen PLL as tracking nothing for the same
* reason.
*/
- *status = DPLL_LOCK_STATUS_HOLDOVER;
+ *status = sit9531x_dpll_frozen_status(chan);
} else if (chan->locked) {
/*
* HO_ACQ is locked *and* holdover memory acquired, so it needs
@@ -195,7 +215,8 @@ const struct dpll_device_ops sit9531x_dpll_device_ops = {
* the pin is the operational state. Predicates, all evaluated under
* multiop_lock:
* M source is present in THIS PLL's hardware priority table
- * S chan->selected_ref == this pin's id (the active selection)
+ * S chan->routed_ref == this pin's id (the reference the device
+ * reports feeding the PLL)
* L chan->locked && !chan->mode && !chan->ho_freeze
* (tracking a reference: outer loop running, locked, not frozen)
* N the input lane's clock monitor reports loss of signal
@@ -218,12 +239,16 @@ const struct dpll_device_ops sit9531x_dpll_device_ops = {
* no mode that pins one reference (see "Mode" above)
* other -EINVAL
*
- * The ACTIVE test needs L as well as S because the selection is what the
- * driver last wrote or the device last chose, not proof the loop uses
- * it: a free-running, frozen or unlocked PLL follows nothing. It needs
- * !N because a PLL whose selection names a lane without signal has
- * fallen back to another listed source on its own, and no register
- * says which -- no pin is reported active then. The INTSYNC destination
+ * S is not the active selection the driver writes to the table: when
+ * the selected source loses its signal the device falls back to another
+ * listed source on its own and leaves the selection naming the dead one,
+ * so the reference the device reports feeding the PLL is read instead,
+ * and after such a fallback the pin the PLL moved to is the active one.
+ * The ACTIVE test needs L as well as S because being fed to the PLL is
+ * not proof the loop uses it: a free-running, frozen or unlocked PLL
+ * follows nothing. It needs !N because the device names what it feeds
+ * the PLL, not what the PLL is locked to, and a lane without signal is
+ * not that whatever the lock bit still says. The INTSYNC destination
* has no monitor, so N and Q never hold for it.
*
* M is read from the hardware priority table, not from ref->pll_mask,
@@ -241,6 +266,124 @@ const struct dpll_device_ops sit9531x_dpll_device_ops = {
* FIXED role -- XO pin. Always CONNECTED; it cannot be routed.
*/
+/*
+ * Report a selection-role pin's state on this DPLL. @pin_id is a logical
+ * input index, SIT9531X_INTSYNC_PIN_ID for the INTSYNC destination.
+ *
+ * Membership comes from chan->prio_mask, which is the priority table read
+ * back from the chip -- not a record of what the driver asked for. The
+ * getter runs on every poll for every input pin of every DPLL, so it takes
+ * the mask the worker refreshed rather than rescanning the table over I2C
+ * each time; table writes refresh it too, so a get right after a set does
+ * not report the old membership.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+static void
+sit9531x_dpll_selection_state_get(struct sit9531x_dev *sitdev,
+ const struct sit9531x_dpll *sitdpll,
+ u8 pin_id, enum dpll_pin_state *state)
+{
+ const struct sit9531x_chan *chan;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ chan = sit9531x_chan_state_get(sitdev, sitdpll->id);
+
+ if (chan->prio_mask & BIT(sit9531x_input_hw_src(pin_id)))
+ *state = DPLL_PIN_STATE_SELECTABLE;
+ else
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+}
+
+/*
+ * Is this the reference the PLL is tracking now? See the S && L && !N
+ * predicate in the pin-state contract. This is also what gates the
+ * measurements taken against the active reference.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+static bool
+sit9531x_dpll_selection_active(struct sit9531x_dev *sitdev,
+ const struct sit9531x_dpll *sitdpll, u8 pin_id)
+{
+ const struct sit9531x_chan *chan;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ chan = sit9531x_chan_state_get(sitdev, sitdpll->id);
+
+ if (chan->routed_ref != pin_id || !chan->locked || chan->mode ||
+ chan->ho_freeze)
+ return false;
+
+ /*
+ * Fed to the PLL is not locked to: the device names the source it
+ * routes, and a lane that lost its signal is one the PLL is about
+ * to be moved off, whatever the lock bit still says.
+ */
+ if (pin_id < sitdev->info->num_inputs &&
+ sit9531x_ref_state_get(sitdev, pin_id)->los)
+ return false;
+
+ return true;
+}
+
+/*
+ * Report a selection-role pin's operational state on this DPLL.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+static void
+sit9531x_dpll_selection_operstate_get(struct sit9531x_dev *sitdev,
+ const struct sit9531x_dpll *sitdpll,
+ u8 pin_id,
+ enum dpll_pin_operstate *operstate)
+{
+ const struct sit9531x_ref *ref;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ if (sit9531x_dpll_selection_active(sitdev, sitdpll, pin_id)) {
+ *operstate = DPLL_PIN_OPERSTATE_ACTIVE;
+ return;
+ }
+
+ if (pin_id < sitdev->info->num_inputs) {
+ ref = sit9531x_ref_state_get(sitdev, pin_id);
+ if (ref->los) {
+ *operstate = DPLL_PIN_OPERSTATE_NO_SIGNAL;
+ return;
+ }
+ if (ref->qual_fail) {
+ *operstate = DPLL_PIN_OPERSTATE_QUAL_FAILED;
+ return;
+ }
+ }
+
+ *operstate = DPLL_PIN_OPERSTATE_STANDBY;
+}
+
+static int
+sit9531x_dpll_input_pin_operstate_on_dpll_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_operstate *state,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+
+ mutex_lock(&sitdev->multiop_lock);
+ sit9531x_dpll_selection_operstate_get(sitdev, sitdpll, dpin->id,
+ state);
+ mutex_unlock(&sitdev->multiop_lock);
+
+ return 0;
+}
+
static int
sit9531x_dpll_input_pin_direction_get(const struct dpll_pin *pin,
void *pin_priv,
@@ -253,8 +396,163 @@ sit9531x_dpll_input_pin_direction_get(const struct dpll_pin *pin,
return 0;
}
+/*
+ * sit9531x_dpll_input_pin_state_on_dpll_get - get input pin DPLL state
+ *
+ * Selection role; see the pin-state contract above.
+ */
+static int
+sit9531x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state *state,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+
+ mutex_lock(&sitdev->multiop_lock);
+ sit9531x_dpll_selection_state_get(sitdev, sitdpll, dpin->id, state);
+ mutex_unlock(&sitdev->multiop_lock);
+
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_input_pin_state_on_dpll_set - set input pin DPLL state
+ *
+ * Enables or disables the physical input receiver via Page 0x02
+ * force/state registers (sit9531x_input_disable/enable()) and updates
+ * this DPLL's Page 1 priority table so the state is honoured by the
+ * PLL's automatic reference selection, not just at the input buffer.
+ * Selection role; see the pin-state contract above for the states.
+ *
+ * The priority table is per PLL, so it is always updated for this DPLL.
+ * A single physical input feeds every DPLL, so the hardware receiver is
+ * only cut off once the last DPLL has released it: ref->pll_mask tracks
+ * which DPLLs currently claim the input, and the physical disable
+ * happens on the transition to an empty mask.
+ */
+static int
+sit9531x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state state,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll_pin *dpin = pin_priv;
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ struct sit9531x_ref *ref = &sitdev->ref[dpin->id];
+ u8 hw_src = sit9531x_input_hw_src(dpin->id);
+ u8 pll_bit = BIT(sitdpll->id);
+ bool enabled_here = false;
+ int rc;
+
+ mutex_lock(&sitdev->multiop_lock);
+
+ switch (state) {
+ case DPLL_PIN_STATE_DISCONNECTED:
+ rc = sit9531x_input_prio_remove(sitdev, sitdpll->id, hw_src);
+ /*
+ * The table write, the latch and the holdover release are
+ * three steps behind one return code, so ask the table what
+ * actually happened rather than reading the errno as "no
+ * change". A source that is gone from the table has been
+ * released whatever else failed.
+ */
+ if (rc && sit9531x_input_prio_present(sitdev, sitdpll->id,
+ hw_src))
+ break;
+ ref->pll_mask &= ~pll_bit;
+ /*
+ * The receiver is shared, so the last DPLL to let go turns it
+ * off. That has to happen even when the table rewrite
+ * reported an error, or the input stays powered with nothing
+ * tracking it; the first error is the one returned.
+ */
+ if (!ref->pll_mask) {
+ int off_rc = sit9531x_input_disable(sitdev, dpin->id);
+
+ if (off_rc && !rc)
+ rc = off_rc;
+ }
+ break;
+ case DPLL_PIN_STATE_CONNECTED:
+ /*
+ * CONNECTED asks for this input and no other, which the
+ * device cannot be told to do: it selects by priority and the
+ * manual-active-select path is not wired up (see "Mode").
+ * Refuse instead of quietly behaving like SELECTABLE.
+ */
+ NL_SET_ERR_MSG(extack,
+ "Device selects its reference by priority; use selectable");
+ rc = -EOPNOTSUPP;
+ break;
+ case DPLL_PIN_STATE_SELECTABLE:
+ /*
+ * Gate the receiver on whenever it is off, not only when this
+ * DPLL holds no claim yet. The two are tracked separately --
+ * the claim comes from the priority table, the receiver from
+ * the force bits -- so a PLL that already lists the input can
+ * still find it powered down, and skipping the enable would
+ * report success for a reference that cannot reach the loop.
+ */
+ if (!ref->enabled) {
+ rc = sit9531x_input_enable(sitdev, dpin->id);
+ if (rc)
+ break;
+ enabled_here = true;
+ }
+ rc = sit9531x_input_prio_add(sitdev, sitdpll->id, hw_src);
+ if (rc && !sit9531x_input_prio_present(sitdev, sitdpll->id,
+ hw_src)) {
+ /*
+ * Undo only what this request did. A receiver the
+ * loaded configuration had already turned on is not
+ * this request's to turn off.
+ */
+ if (enabled_here)
+ sit9531x_input_disable(sitdev, dpin->id);
+ break;
+ }
+ /*
+ * Claim the input for this DPLL only once it is both enabled
+ * and present in the priority table. Setting the mask before
+ * prio_add would leak the claim if prio_add failed, keeping the
+ * shared input receiver powered even after every DPLL released
+ * it.
+ */
+ ref->pll_mask |= pll_bit;
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&sitdev->multiop_lock);
+
+ /*
+ * Leave the messages the switch already set in place; only a failure
+ * that came from the hardware path still needs one.
+ */
+ if (rc == -ENOSPC)
+ NL_SET_ERR_MSG(extack,
+ "Priority table is full of unique sources on this PLL");
+ else if (rc && rc != -EOPNOTSUPP && rc != -EINVAL)
+ NL_SET_ERR_MSG(extack, "Failed to set input pin state");
+
+ return rc;
+}
+
static const struct dpll_pin_ops sit9531x_dpll_input_pin_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,
+ .operstate_on_dpll_get = sit9531x_dpll_input_pin_operstate_on_dpll_get,
};
/*
diff --git a/drivers/dpll/sit9531x/regs.h b/drivers/dpll/sit9531x/regs.h
index 9349ec722569..fd1068982cd7 100644
--- a/drivers/dpll/sit9531x/regs.h
+++ b/drivers/dpll/sit9531x/regs.h
@@ -126,6 +126,10 @@
#define SIT9531X_REG_GLOBAL_UPDATE SIT9531X_REG(0x00, 0x0F)
#define SIT9531X_SMALL_UPDATE_CMD 0x02
+/* PLL holdover control (PLL page offset) */
+#define SIT9531X_PLL_REG_HO_CTRL 0x6F
+#define SIT9531X_PLL_HO_FORCE_BIT 4
+
/* One bit per input PAIR (bit 0 = CLKIN0, ..., bit 3 = CLKIN3) */
#define SIT9531X_REG_IN_DE_FORCE SIT9531X_REG(0x02, 0xE8)
#define SIT9531X_REG_IN_DE_STATE SIT9531X_REG(0x02, 0xE9)
@@ -230,6 +234,31 @@
#define SIT9531X_PLL_STATUS_OUTER_DIS BIT(5)
+/*
+ * Debug register -- PLL page. Writing the key unlocks the page's debug
+ * registers for writing; writing zero locks them again.
+ */
+#define SIT9531X_PLL_REG_DEBUG 0xBD
+#define SIT9531X_PLL_DEBUG_UNLOCK 0xC3
+#define SIT9531X_PLL_DEBUG_LOCK 0x00
+
+/*
+ * Debug status bus -- PLL page. The address register selects the block
+ * whose status the data register returns. A read of the data register
+ * returns what the previous read latched, so the documented procedures
+ * read it three times and take the last.
+ */
+#define SIT9531X_PLL_REG_DBG_STATUS_ADDR 0xD1
+#define SIT9531X_PLL_REG_DBG_STATUS_DATA 0xD0
+#define SIT9531X_DBG_STATUS_READS 3
+
+/*
+ * Status of the reference routed to the PLL: the low nibble is the
+ * source the input subsystem feeds it, in the priority-table encoding.
+ */
+#define SIT9531X_DBG_STATUS_ROUTED_SRC 0xB0
+#define SIT9531X_DBG_ROUTED_SRC_MASK GENMASK(3, 0)
+
/*
* PLL_CONFIG1F_PLL: bit 6 puts the PLL in manual active select, where it
* no longer arbitrates by priority. The other bits are GUI-generated
@@ -266,6 +295,18 @@
#define SIT9531X_PLL_REG_STATUS_1 0x06
#define SIT9531X_PLL_STATUS_1_HO_VALID BIT(2)
+/*
+ * Page 0x06 clock monitor status, one nibble per input lane: P lanes in
+ * 0x02 (IN0P low, IN1P high) and 0x06 (IN2P, IN3P), N lanes in 0x92
+ * (IN0N, IN1N) and 0x96 (IN2N, IN3N).
+ */
+#define SIT9531X_CLKMON_P_STATUS_01 SIT9531X_REG(0x06, 0x02)
+#define SIT9531X_CLKMON_P_STATUS_23 SIT9531X_REG(0x06, 0x06)
+#define SIT9531X_CLKMON_N_STATUS_01 SIT9531X_REG(0x06, 0x92)
+#define SIT9531X_CLKMON_N_STATUS_23 SIT9531X_REG(0x06, 0x96)
+#define SIT9531X_CLKMON_FINE_DRIFT BIT(0)
+#define SIT9531X_CLKMON_COARSE_DRIFT BIT(1)
+#define SIT9531X_CLKMON_LOSS BIT(2)
#define SIT9531X_CLKMON_P_NOTIF_01 SIT9531X_REG(0x06, 0x03)
#define SIT9531X_CLKMON_P_NOTIF_23 SIT9531X_REG(0x06, 0x07)
--
2.39.2 (Apple Git-143)
next prev parent reply other threads:[~2026-10-09 18:32 UTC|newest]
Thread overview: 13+ 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-09 18:31 ` [PATCH net-next v12 05/12] dpll: sit9531x: register DPLL devices and pins Ali Rouhi
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-09 18:31 ` Ali Rouhi [this message]
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-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-09 18:31 ` [PATCH net-next v12 10/12] dpll: sit9531x: add support to adjust output phase 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-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-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=20261009183151.78497-7-arouhi@sitime.com \
--to=arouhi@sitime.com \
--cc=Oleg.Zadorozhnyi@devoxsoftware.com \
--cc=arkadiusz.kubalewski@intel.com \
--cc=cjubran@nvidia.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=ivecera@redhat.com \
--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®