From: Ali Rouhi <arouhi@sitime.com>
To: "jiri@resnulli.us" <jiri@resnulli.us>
Cc: "vadim.fedorenko@linux.dev" <vadim.fedorenko@linux.dev>,
"arkadiusz.kubalewski@intel.com" <arkadiusz.kubalewski@intel.com>,
"ivecera@redhat.com" <ivecera@redhat.com>,
"kuba@kernel.org" <kuba@kernel.org>,
"pabeni@redhat.com" <pabeni@redhat.com>,
"robh@kernel.org" <robh@kernel.org>,
"krzk+dt@kernel.org" <krzk+dt@kernel.org>,
"conor+dt@kernel.org" <conor+dt@kernel.org>,
"cjubran@nvidia.com" <cjubran@nvidia.com>,
"Oleg.Zadorozhnyi@devoxsoftware.com"
<Oleg.Zadorozhnyi@devoxsoftware.com>,
"prabhakar.mahadev-lad.rj@bp.renesas.com"
<prabhakar.mahadev-lad.rj@bp.renesas.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 v15 07/13] dpll: sit9531x: implement input pin state on a DPLL
Date: Tue, 10 Nov 2026 20:26:13 +0000 [thread overview]
Message-ID: <20261110202607.55744-8-arouhi@sitime.com> (raw)
In-Reply-To: <20261110202607.55744-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 PLL picks its reference from a priority table: an input
in the table is SELECTABLE, one absent is DISCONNECTED, and setting the
state adds or removes it. CONNECTED is refused, as the driver does not
program manual input select.
A table write uses the documented procedure: force holdover, write
the slots, latch with a small update, release. The pin reported active
is the one the part feeds the PLL while the PLL tracks it and the lane
has signal.
A PLL in holdover after losing its reference is re-selected when a
listed source returns.
Signed-off-by: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>
Signed-off-by: Ali Rouhi <arouhi@sitime.com>
---
drivers/dpll/sit9531x/core.c | 1122 ++++++++++++++++++++++++++++++++--
drivers/dpll/sit9531x/core.h | 65 +-
drivers/dpll/sit9531x/dpll.c | 257 +++++++-
drivers/dpll/sit9531x/regs.h | 38 ++
4 files changed, 1432 insertions(+), 50 deletions(-)
diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c
index 209671e7917e..af6d7918958d 100644
--- a/drivers/dpll/sit9531x/core.c
+++ b/drivers/dpll/sit9531x/core.c
@@ -61,10 +61,21 @@ static const struct sit9531x_chip_info sit9531x_chip_ids[] = {
* One 256-register page is visible at a time, selected by offset 0xFF on every
* page. The map covers that window, uncached; the accessors select the page.
*/
+/*
+ * Each read of the debug status data register advances the status bus latch;
+ * marking it precious stops a debugfs dump from reading it.
+ */
+static bool sit9531x_precious_reg(struct device *dev __maybe_unused,
+ unsigned int reg)
+{
+ return reg == SIT9531X_PLL_REG_DBG_STATUS_DATA;
+}
+
static const struct regmap_config sit9531x_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = SIT9531X_PAGE_SEL,
+ .precious_reg = sit9531x_precious_reg,
.cache_type = REGCACHE_NONE,
};
@@ -206,6 +217,34 @@ int sit9531x_update_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,
return rc;
}
+#define SIT9531X_CLOSE_TRIES 3
+
+/* Write a register, retrying on failure */
+static int sit9531x_write_close(struct sit9531x_dev *sitdev, unsigned int reg,
+ u8 val)
+{
+ u8 attempt;
+ int rc;
+
+ for (attempt = 0; attempt < SIT9531X_CLOSE_TRIES; attempt++) {
+ if (attempt)
+ usleep_range(1000, 2000);
+ rc = sit9531x_write_u8(sitdev, reg, val);
+ if (!rc)
+ break;
+ }
+
+ return rc;
+}
+
+static int sit9531x_write_pll_close(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 offset, u8 val)
+{
+ return sit9531x_write_close(sitdev,
+ SIT9531X_REG(sit9531x_pll_page(pll_idx),
+ offset), val);
+}
+
static void sit9531x_input_get_regs(const struct sit9531x_dev *sitdev,
u8 index,
unsigned int *force_reg,
@@ -223,6 +262,72 @@ static void sit9531x_input_get_regs(const struct sit9531x_dev *sitdev,
}
}
+/* 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;
+
+ ref->enabled = false;
+ ref->off_pending = rc != 0;
+
+ return rc;
+}
+
+/*
+ * Release the force so the lane uses the loaded configuration. 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;
+ ref->off_pending = false;
+
+ return 0;
+}
+
/*
* Output Hi-Z control: each slot has three force/state pairs on Page 0x03,
* for the differential path and the CMOS OutP and OutN pads. A mute writes
@@ -359,7 +464,7 @@ 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 something other than this driver forced it
*
* Caller must hold sitdev->multiop_lock.
*/
@@ -381,20 +486,460 @@ static u8 sit9531x_prio_slot_get(u8 val, u8 slot)
return val >> SIT9531X_PRIO_HI_SHIFT;
}
+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);
+}
+
+/*
+ * Latch a priority table with a small change update, as in the documented
+ * input_priority_sel() procedure.
+ */
+static int sit9531x_prio_prg_commit(struct sit9531x_dev *sitdev)
+{
+ int rc;
+
+ rc = sit9531x_write_close(sitdev, SIT9531X_REG_GLOBAL_UPDATE,
+ SIT9531X_SMALL_UPDATE_CMD);
+ if (rc)
+ return rc;
+
+ usleep_range(1000, 2000);
+
+ return 0;
+}
+
+/*
+ * Four registers carry a nibble per lane: loss of signal and the fine and
+ * coarse frequency-drift flags. 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 < SIT9531X_MAX_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;
+}
+
+/*
+ * Check if the device can lock to @src now; a differential pair is watched
+ * through its P lane.
+ */
+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;
+}
+
+/*
+ * Map an N-lane source of a differential pair to its P lane, the lane its DPLL
+ * pin represents.
+ */
+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;
+}
+
+/*
+ * @input_idx is in hardware encoding (see sit9531x_input_hw_src()). 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 the hardware table: a listed source gets
+ * the first slot it occupies.
+ */
+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);
+ }
+
+ 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;
+}
+
+/* Order the members by configured priority */
+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;
+
+ 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 the cached membership mask, per-slot copy and first-slot array of a
+ * PLL's priority table.
+ */
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 };
+ u8 slot, src, src_canon;
u16 mask = 0;
- u8 slot;
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;
+ }
+
+ for (src = 0; src < SIT9531X_PRIO_NUM_SRC; src++)
+ chan->prio_last[src] = first[src];
+
+ chan->prio_mask = mask;
+}
+
+#define SIT9531X_HO_CLEAR_TRIES 3
+
+static int sit9531x_prio_table_read(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 *srcs);
+static int sit9531x_chan_state_fetch(struct sit9531x_dev *sitdev, u8 pll_idx);
+
+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;
+}
+
+/*
+ * Pick the active selection to latch; after a latch the PLL uses that source
+ * until it loses signal.
+ */
+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;
+
+ 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;
+
+ for (i = 0; i < SIT9531X_PRIO_MAX_SLOTS; i++)
+ if (sit9531x_prio_src_usable(srcs[i]))
+ return srcs[i];
+
+ return srcs[0];
+}
+
+static int sit9531x_prio_table_commit(struct sit9531x_dev *sitdev, u8 pll_idx,
+ const u8 *srcs)
+{
+ u8 val, last_old, slot, attempt, written = 0, restored = 0;
+ u8 now[SIT9531X_PRIO_MAX_SLOTS], back[SIT9531X_PRIO_MAX_SLOTS];
+ struct sit9531x_chan *chan = &sitdev->chan[pll_idx];
+ int rc = 0, prg_rc, ho_rc = 0;
+ bool empty, ho_foreign, pending = false;
+ bool pending_before = chan->table_pending;
+ u16 reg;
+
+ empty = true;
+ for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++)
+ if (sit9531x_prio_src_usable(srcs[slot]))
+ empty = false;
+
+ memcpy(back, chan->table_pending ? chan->table_back : chan->prio_srcs,
+ sizeof(back));
+
+ /*
+ * Treat a forced holdover as set by this driver only when the table is
+ * empty or a release is pending; leave any other in place.
+ */
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_HO_CTRL,
+ &val);
+ if (rc)
+ return rc;
- if (sit9531x_prio_src_usable(src))
- mask |= BIT(src);
+ ho_foreign = (val & BIT(SIT9531X_PLL_HO_FORCE_BIT)) &&
+ chan->prio_mask && !chan->ho_release_pending;
+
+ 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);
+
+ 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]);
+
+ written = slot + 2;
+ rc = sit9531x_write_u8(sitdev, reg, val);
+ if (rc)
+ goto rollback;
+ }
+
+ /*
+ * The last register holds slot 10 in [7:4] and the device's active
+ * selection in [3:0], addressed as the slot past the last.
+ */
+ 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);
+
+ 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) {
+ for (slot = 0; slot < written; slot += 2) {
+ const u8 *prev = &chan->prio_srcs[slot];
+ u8 old;
+
+ if (slot + 1 < SIT9531X_PRIO_MAX_SLOTS) {
+ old = sit9531x_prio_slot_set(0, slot, prev[0]);
+ old = sit9531x_prio_slot_set(old, slot + 1,
+ prev[1]);
+ } else {
+ old = last_old;
+ }
+ if (sit9531x_write_u8(sitdev,
+ sit9531x_prio_reg(pll_idx, slot),
+ old))
+ break;
+
+ restored = slot + 2;
+ }
+ pending = restored < written;
+ written = restored;
+ }
+
+ /*
+ * Latch unconditionally: the PLL uses the register contents only after
+ * a latch.
+ */
+ prg_rc = sit9531x_prio_prg_commit(sitdev);
+ if (prg_rc && !rc) {
+ pending = true;
+ rc = prg_rc;
+ }
+
+ if (!rc) {
+ sit9531x_prio_mask_build(sitdev, pll_idx, srcs);
+ memcpy(chan->seen_srcs, srcs, sizeof(chan->seen_srcs));
+ chan->seen_valid = true;
+ chan->table_pending = false;
+ } else if (pending || pending_before) {
+ sit9531x_prio_mask_build(sitdev, pll_idx, back);
+ memcpy(chan->table_back, back, sizeof(chan->table_back));
+ chan->table_pending = 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;
}
- sitdev->chan[pll_idx].prio_mask = mask;
+release:
+ /*
+ * An empty table keeps the forced holdover; the PLL uses the source in
+ * the selection nibble, which still names the old one.
+ */
+ if (!chan->prio_mask) {
+ dev_dbg(sitdev->dev,
+ "PLL%c: no source listed, holdover kept\n",
+ 'A' + pll_idx);
+ chan->ho_release_pending = false;
+ goto out;
+ }
+
+ if (ho_foreign) {
+ dev_dbg(sitdev->dev,
+ "PLL%c: holdover was forced before the write, left in place\n",
+ 'A' + pll_idx);
+ goto out;
+ }
+
+ if (chan->table_pending) {
+ dev_dbg(sitdev->dev,
+ "PLL%c: priority table restore pending, holdover kept\n",
+ 'A' + pll_idx);
+ chan->ho_release_pending = true;
+ goto out;
+ }
+
+ 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;
+
+out:
+ /* The core reads the pin getters as soon as the setter returns. */
+ if (sit9531x_chan_state_fetch(sitdev, pll_idx))
+ chan->stale = true;
+
+ return rc;
}
/* Caller must hold sitdev->multiop_lock. */
@@ -416,6 +961,128 @@ 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; skip an unchanged table, since
+ * each write forces holdover.
+ */
+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);
+}
+
+/*
+ * Check if the device holds the table the configured priorities give for
+ * @members.
+ */
+static bool sit9531x_prio_table_held(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u16 members)
+{
+ u8 srcs[SIT9531X_PRIO_MAX_SLOTS];
+
+ sit9531x_prio_table_build(sitdev, pll_idx, members, srcs);
+
+ return !memcmp(srcs, sitdev->chan[pll_idx].prio_srcs, sizeof(srcs));
+}
+
+/*
+ * @input_idx is in hardware encoding (0-11, see sit9531x_input_hw_src()). A
+ * source not in the table keeps the priority for when it is connected. Caller
+ * must hold sitdev->multiop_lock.
+ */
+int sit9531x_input_prio_set(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx, u8 prio)
+{
+ struct sit9531x_chan *chan;
+ u16 old_known;
+ u8 old_prio;
+ 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;
+
+ rc = sit9531x_prio_table_apply(sitdev, pll_idx, chan->prio_mask);
+ if (rc && !sit9531x_prio_table_held(sitdev, pll_idx, chan->prio_mask)) {
+ chan->cfg_prio[input_idx] = old_prio;
+ chan->cfg_known = old_known;
+ }
+
+ return rc;
+}
+
+/*
+ * Removing the last source leaves an empty table, which keeps the PLL in
+ * holdover. Caller must hold sitdev->multiop_lock.
+ */
+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));
+}
+
+/*
+ * A source the PLL never listed gets the priority of the last slot. Caller must
+ * hold sitdev->multiop_lock.
+ */
+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 */
@@ -577,6 +1244,7 @@ static int sit9531x_chan_select_fetch(struct sit9531x_dev *sitdev, u8 pll_idx)
if (rc)
return rc;
chan->manual_input = false;
+ chan->manual_ref = SIT9531X_REF_INVALID;
if (!(cfg & SIT9531X_PLL_CONFIG1F_MANUAL_SEL))
return 0;
@@ -597,6 +1265,9 @@ static int sit9531x_chan_select_fetch(struct sit9531x_dev *sitdev, u8 pll_idx)
chan->manual_input = true;
if (sel & SIT9531X_MAN_IN_SEL_FROM_REG) {
sel = FIELD_GET(SIT9531X_MAN_IN_SEL_MASK, sel);
+ chan->manual_ref =
+ sit9531x_hw_src_input(sit9531x_prio_src_canon(sitdev,
+ sel));
dev_info(sitdev->dev,
"PLL%c: the profile selects its reference from the register, input code %u; reported as manual\n",
'A' + pll_idx, sel);
@@ -609,17 +1280,65 @@ static int sit9531x_chan_select_fetch(struct sit9531x_dev *sitdev, u8 pll_idx)
return 0;
}
+/*
+ * Read the reference the device feeds a PLL from the debug status bus; the
+ * active selection does not change on a fallback. The caller rules out free-run
+ * first and 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)
+ goto relock;
+
+ 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;
+ }
+
+ /*
+ * Canonicalize 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_rc = sit9531x_write_pll_close(sitdev, pll_idx,
+ SIT9531X_PLL_REG_DEBUG,
+ SIT9531X_PLL_DEBUG_LOCK);
+
+ return rc ? rc : lock_rc;
+}
+
/*
* Read the PLL's run, lock and holdover state, mode, selection and priority
* table.
*/
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;
+ u8 status, outer_lol, inner_lol, ho_freeze, pll_status_1;
struct sit9531x_chan *chan = &sitdev->chan[pll_idx];
u8 srcs[SIT9531X_PRIO_MAX_SLOTS];
- u8 pll_status_1;
- bool active;
+ u8 routed = SIT9531X_REF_INVALID;
+ bool active, tracking;
int rc;
/*
@@ -640,21 +1359,6 @@ static int sit9531x_chan_state_fetch(struct sit9531x_dev *sitdev, u8 pll_idx)
if (rc)
return rc;
- /*
- * The active selection is CLK_ACTIVESEL_PLL, the low nibble of the last
- * register of the PLL's Page 1 priority block. 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)
@@ -665,23 +1369,48 @@ static int sit9531x_chan_state_fetch(struct sit9531x_dev *sitdev, u8 pll_idx)
if (rc)
return rc;
+ /*
+ * Read the routed reference only while the PLL tracks one; in free-run
+ * it has none.
+ */
+ 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)
return rc;
- rc = sit9531x_prio_table_read(sitdev, pll_idx, srcs);
- if (rc)
- return rc;
+ /* Keep the cached table while its write-back is pending */
+ if (!chan->table_pending) {
+ rc = sit9531x_prio_table_read(sitdev, pll_idx, srcs);
+ if (rc)
+ return rc;
- sit9531x_prio_mask_build(sitdev, pll_idx, srcs);
+ sit9531x_prio_mask_build(sitdev, pll_idx, srcs);
+
+ /*
+ * A table that differs from the last one written was changed
+ * outside the driver; take it as 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;
+ if (chan->manual_input && routed != SIT9531X_REF_INVALID)
+ chan->manual_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);
@@ -792,11 +1521,39 @@ static int sit9531x_ref_pll_mask_fetch(struct sit9531x_dev *sitdev)
return 0;
}
+/*
+ * Mark the input each manual-select PLL uses; its table need not list it.
+ * Caller must hold sitdev->multiop_lock.
+ */
+static void sit9531x_manual_claims_add(struct sit9531x_dev *sitdev)
+{
+ const struct sit9531x_chan *chan;
+ u8 pll_idx;
+
+ for (pll_idx = 0; pll_idx < SIT9531X_NUM_PLLS; pll_idx++) {
+ chan = &sitdev->chan[pll_idx];
+ if (chan->manual_input &&
+ chan->manual_ref < sitdev->info->num_inputs)
+ sitdev->ref[chan->manual_ref].pll_mask |= BIT(pll_idx);
+ }
+}
+
+/* Arm the one-shot re-select of every PLL; see sit9531x_reselect(). */
+static void sit9531x_reselect_arm(struct sit9531x_dev *sitdev)
+{
+ u8 i;
+
+ for (i = 0; i < SIT9531X_NUM_PLLS; i++)
+ sitdev->chan[i].reselect_armed = true;
+}
+
static int sit9531x_dev_state_fetch(struct sit9531x_dev *sitdev)
{
int rc;
u8 i;
+ sit9531x_reselect_arm(sitdev);
+
/* Detect SE/DE configuration before any per-lane access */
for (i = 0; i < sitdev->info->num_inputs / 2; i++) {
rc = sit9531x_input_mode_fetch(sitdev, i);
@@ -816,6 +1573,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;
+ }
+
mutex_lock(&sitdev->multiop_lock);
rc = sit9531x_ref_pll_mask_fetch(sitdev);
mutex_unlock(&sitdev->multiop_lock);
@@ -851,34 +1615,211 @@ static int sit9531x_dev_state_fetch(struct sit9531x_dev *sitdev)
return rc;
}
+ /* Needs the select registers and samples read above */
+ mutex_lock(&sitdev->multiop_lock);
+ sit9531x_manual_claims_add(sitdev);
+ mutex_unlock(&sitdev->multiop_lock);
+
return 0;
}
-static void sit9531x_dev_ref_states_update(struct sit9531x_dev *sitdev)
+/*
+ * Read each receiver's enable and every lane's clock monitor; return true if
+ * any read succeeds.
+ */
+static bool
+sit9531x_dev_ref_states_update(struct sit9531x_dev *sitdev)
{
+ bool answered = false;
int i, rc;
for (i = 0; i < sitdev->info->num_inputs; i++) {
rc = sit9531x_ref_state_fetch(sitdev, i);
if (rc)
- dev_warn(sitdev->dev,
- "Failed to get REF%u status: %d\n", i, rc);
+ dev_warn_ratelimited(sitdev->dev,
+ "Failed to get REF%u status: %d\n",
+ i, rc);
+ else
+ answered = true;
}
+
+ rc = sit9531x_input_mon_fetch(sitdev);
+ if (rc)
+ dev_warn_ratelimited(sitdev->dev,
+ "Failed to read the input clock monitors: %d\n",
+ rc);
+ else
+ answered = true;
+
+ return answered;
}
-static void sit9531x_dev_chan_states_update(struct sit9531x_dev *sitdev)
+/*
+ * Retry turning off a receiver unless a PLL has claimed the input since. Caller
+ * must hold sitdev->multiop_lock.
+ */
+static void sit9531x_input_off_retry(struct sit9531x_dev *sitdev)
{
+ u8 i;
+
+ for (i = 0; i < sitdev->info->num_inputs; i++) {
+ struct sit9531x_ref *ref = &sitdev->ref[i];
+
+ if (!ref->off_pending)
+ continue;
+ if (ref->pll_mask)
+ ref->off_pending = false;
+ else
+ sit9531x_input_disable(sitdev, i);
+ }
+}
+
+/*
+ * Rebuild pll_mask from the cached priority tables and the manual-select
+ * inputs. 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);
+ }
+ }
+
+ sit9531x_manual_claims_add(sitdev);
+}
+
+/*
+ * Retry a pending forced-holdover release; a pending table write-back releases
+ * it instead. 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 || chan->table_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);
+}
+
+/*
+ * Write back the priority table after a failed write. Caller must hold
+ * sitdev->multiop_lock.
+ */
+static void sit9531x_table_back_retry(struct sit9531x_dev *sitdev, u8 pll_idx)
+{
+ u8 back[SIT9531X_PRIO_MAX_SLOTS];
+
+ if (!sitdev->chan[pll_idx].table_pending)
+ return;
+
+ memcpy(back, sitdev->chan[pll_idx].table_back, sizeof(back));
+ if (sit9531x_prio_table_commit(sitdev, pll_idx, back))
+ return;
+
+ dev_info(sitdev->dev, "PLL%c: priority table written back\n",
+ 'A' + pll_idx);
+}
+
+/*
+ * Rewrite the priority table when a listed source returns to a PLL without a
+ * reference; the latch restarts acquisition. Caller must hold
+ * sitdev->multiop_lock.
+ */
+static void sit9531x_reselect(struct sit9531x_dev *sitdev, u8 pll_idx)
+{
+ struct sit9531x_chan *chan = &sitdev->chan[pll_idx];
+ u8 top;
+ int rc;
+
+ /* A PLL on manual input select uses no priority table */
+ if (chan->manual_input || !chan->prio_mask || chan->mode ||
+ chan->ho_release_pending || chan->table_pending)
+ return;
+
+ top = sit9531x_prio_top_live(sitdev, chan->prio_srcs);
+ if (chan->reselect_armed) {
+ if (top == SIT9531X_PRIO_SRC_NONE || !chan->ho_freeze) {
+ chan->reselect_armed = false;
+ chan->live_top = top;
+ return;
+ }
+ } else {
+ if (top == chan->live_top)
+ return;
+ /*
+ * Tracking is locked and not frozen; the two bits are read
+ * independently.
+ */
+ if (top == SIT9531X_PRIO_SRC_NONE ||
+ (chan->locked && !chan->ho_freeze)) {
+ chan->live_top = top;
+ return;
+ }
+ }
+
+ rc = sit9531x_prio_table_commit(sitdev, pll_idx, chan->prio_srcs);
+ if (rc) {
+ dev_warn_ratelimited(sitdev->dev,
+ "PLL%c: failed to re-select a returned source: %d\n",
+ 'A' + pll_idx, rc);
+ return;
+ }
+
+ chan->reselect_armed = false;
+ chan->live_top = top;
+ dev_info(sitdev->dev, "PLL%c: re-selecting, a source is back\n",
+ 'A' + pll_idx);
+}
+
+/*
+ * Retry pending writes, then read each PLL's state; return true if any state
+ * read succeeds.
+ */
+static bool
+sit9531x_dev_chan_states_update(struct sit9531x_dev *sitdev)
+{
+ bool answered = false;
int i, rc;
+ sit9531x_input_off_retry(sitdev);
for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
+ sit9531x_table_back_retry(sitdev, i);
+ sit9531x_ho_release_retry(sitdev, i);
rc = sit9531x_chan_state_fetch(sitdev, i);
if (rc) {
sitdev->chan[i].stale = true;
- dev_warn(sitdev->dev,
- "Failed to get PLL%c state: %d\n",
- 'A' + i, rc);
+ dev_warn_ratelimited(sitdev->dev,
+ "Failed to get PLL%c state: %d\n",
+ 'A' + i, rc);
+ continue;
}
+ answered = true;
+ sit9531x_reselect(sitdev, i);
}
+
+ sit9531x_ref_pll_mask_rebuild(sitdev);
+
+ return answered;
}
static void sit9531x_dev_periodic_work(struct kthread_work *work)
@@ -886,6 +1827,7 @@ static void sit9531x_dev_periodic_work(struct kthread_work *work)
struct sit9531x_dev *sitdev = container_of(work, struct sit9531x_dev,
work.work);
struct sit9531x_dpll *sitdpll;
+ bool answered;
int rc;
/*
@@ -893,8 +1835,8 @@ static void sit9531x_dev_periodic_work(struct kthread_work *work)
* take DPLL-subsystem locks, which nest outside it.
*/
mutex_lock(&sitdev->multiop_lock);
- sit9531x_dev_ref_states_update(sitdev);
- sit9531x_dev_chan_states_update(sitdev);
+ answered = sit9531x_dev_ref_states_update(sitdev);
+ answered |= sit9531x_dev_chan_states_update(sitdev);
mutex_unlock(&sitdev->multiop_lock);
list_for_each_entry(sitdpll, &sitdev->dplls, list)
@@ -912,8 +1854,14 @@ static void sit9531x_dev_periodic_work(struct kthread_work *work)
"Failed to clear notifications: %d\n",
rc);
+ if (answered || !rc)
+ sitdev->poll_fails = 0;
+ else if (sitdev->poll_fails < SIT9531X_POLL_STRETCH_MAX)
+ sitdev->poll_fails++;
+
kthread_queue_delayed_work(sitdev->kworker, &sitdev->work,
- msecs_to_jiffies(SIT9531X_STATUS_POLL_MS));
+ msecs_to_jiffies(SIT9531X_STATUS_POLL_MS <<
+ sitdev->poll_fails));
}
/* Queue an immediate poll tick; polling continues as a fallback. */
@@ -1046,6 +1994,63 @@ static void sit9531x_pll_states_report(struct sit9531x_dev *sitdev)
}
}
+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 differential pair is not a standalone input. */
+ if (sit9531x_input_is_n(index) &&
+ sitdev->ref[index].sig_mode == SIT9531X_MODE_DE)
+ return false;
+
+ return true;
+}
+
+/* Warn about a pin node whose reg names no registrable pin */
+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);
+}
+
static int sit9531x_dev_start(struct sit9531x_dev *sitdev)
{
struct sit9531x_dpll *sitdpll;
@@ -1060,6 +2065,8 @@ static 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) {
@@ -1084,6 +2091,7 @@ static int sit9531x_dev_start(struct sit9531x_dev *sitdev)
static void sit9531x_dev_stop(struct sit9531x_dev *sitdev)
{
struct sit9531x_dpll *sitdpll;
+ u8 i;
kthread_cancel_delayed_work_sync(&sitdev->work);
@@ -1091,6 +2099,30 @@ static void sit9531x_dev_stop(struct sit9531x_dev *sitdev)
if (sitdpll->dpll_dev)
sit9531x_dpll_unregister(sitdpll);
}
+
+ /*
+ * Write back tables first: forced holdover stays set while a table
+ * write-back is pending.
+ */
+ mutex_lock(&sitdev->multiop_lock);
+ for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
+ sit9531x_table_back_retry(sitdev, i);
+ if (sitdev->chan[i].table_pending)
+ dev_warn(sitdev->dev,
+ "PLL%c priority table left as written, holdover kept\n",
+ 'A' + i);
+ }
+ mutex_unlock(&sitdev->multiop_lock);
+
+ mutex_lock(&sitdev->multiop_lock);
+ for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
+ sit9531x_ho_release_retry(sitdev, i);
+ if (sitdev->chan[i].ho_release_pending &&
+ !sitdev->chan[i].table_pending)
+ dev_warn(sitdev->dev,
+ "PLL%c left in forced holdover\n", 'A' + i);
+ }
+ mutex_unlock(&sitdev->multiop_lock);
}
static struct sit9531x_dpll_pin *
@@ -1209,10 +2241,15 @@ static bool sit9531x_dpll_pin_is_registrable(struct sit9531x_dpll *sitdpll,
enum dpll_pin_direction dir,
u8 index)
{
+ 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);
}
static int sit9531x_dpll_pins_register(struct sit9531x_dpll *sitdpll)
@@ -1600,6 +2637,9 @@ static int sit9531x_resume(struct device *dev)
/* After power loss the part is on page 0, not the cached page */
sit9531x_page_cache_drop(sitdev);
+ mutex_lock(&sitdev->multiop_lock);
+ sit9531x_reselect_arm(sitdev);
+ mutex_unlock(&sitdev->multiop_lock);
if (sitdev->irq > 0)
enable_irq(sitdev->irq);
kthread_queue_delayed_work(sitdev->kworker, &sitdev->work, 0);
diff --git a/drivers/dpll/sit9531x/core.h b/drivers/dpll/sit9531x/core.h
index 9e873432df3d..c2fa6a17be43 100644
--- a/drivers/dpll/sit9531x/core.h
+++ b/drivers/dpll/sit9531x/core.h
@@ -41,7 +41,7 @@
*/
#define SIT9531X_POLL_STRETCH_MAX 3
-/* 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 */
@@ -79,14 +79,20 @@ enum sit9531x_signal_mode {
* struct sit9531x_ref - input reference state
* @freq: configured frequency in Hz
* @enabled: input receiver is on
+ * @off_pending: receiver gate-off to retry while no PLL claims the input
* @pll_mask: PLLs this input feeds (bit 0 = PLLA)
+ * @los: clock monitor reports loss of signal
+ * @qual_fail: clock monitor reports frequency drift while the input has signal
* @sig_mode: signal mode of the input pair, read from CLKINx_INPUT_MODE at
* probe
*/
struct sit9531x_ref {
u64 freq;
bool enabled;
+ bool off_pending;
u8 pll_mask;
+ bool los;
+ bool qual_fail;
enum sit9531x_signal_mode sig_mode;
};
@@ -100,6 +106,8 @@ struct sit9531x_ref {
* @routed: mapped to @pll_idx by the initial configuration; an unrouted
* output has no pin
* @pll_idx: PLL driving this output (0-3)
+ * @retime_pending: re-timed delay not yet applied; the poll applies @retime_ps
+ * @retime_ps: delay to apply while @retime_pending
*/
struct sit9531x_out {
u64 freq;
@@ -108,6 +116,8 @@ struct sit9531x_out {
bool part_muted;
bool routed;
u8 pll_idx;
+ bool retime_pending;
+ s32 retime_ps;
};
/*
@@ -118,14 +128,30 @@ struct sit9531x_out {
* @stale: last state read failed; the fields hold the previous sample and
* are not reported
* @mode: 0 = sync (outer loop enabled), 1 = free-run
- * @selected_ref: logical index of the selected reference (INTSYNC:
- * SIT9531X_INTSYNC_PIN_ID), or SIT9531X_REF_INVALID for a reserved code
+ * @routed_ref: input the device routes to the PLL, as a logical index
+ * (INTSYNC: SIT9531X_INTSYNC_PIN_ID); SIT9531X_REF_INVALID if none
* @inner_lol: inner loop loss of lock
* @ho_freeze: holdover freeze active
* @ho_valid: holdover memory holds a valid estimate
* @manual_input: PLL uses its manual input select (pins or
* GPIO_INPUT_FUNC_CTRL5..8), which table writes do not change; reported
* as MANUAL
+ * @manual_ref: logical input of a manual PLL, SIT9531X_REF_INVALID if
+ * unknown; its receiver stays claimed for the PLL
+ * @ho_release_pending: forced holdover not yet released
+ * @live_top: highest-priority listed source with signal at the last poll,
+ * or SIT9531X_PRIO_SRC_NONE; a change while the PLL tracks no source
+ * rewrites the table
+ * @reselect_armed: no poll since probe or resume; the first poll rewrites
+ * the table of a PLL in holdover while a listed source has signal
+ * @flush_pending: output divider realignment flush not yet triggered after
+ * a rate or delay commit
+ * @table_pending: priority table partly written or not latched; the PLL
+ * stays in forced holdover
+ * @table_back: table reported and to be rewritten while @table_pending
+ * @tap_pending: debug taps not restored after a phase-offset read
+ * @prio_srcs: cached priority table, a source code per slot
+ * @prio_last: first slot of each source plus one, 0 if absent
* @prio_mask: bit per canonical source in the priority table read back
* from hardware
* @cfg_prio: configured priority of each canonical source, listed or not;
@@ -140,11 +166,21 @@ struct sit9531x_chan {
bool locked;
bool stale;
u8 mode;
- u8 selected_ref;
+ u8 routed_ref;
bool inner_lol;
bool ho_freeze;
bool ho_valid;
bool manual_input;
+ u8 manual_ref;
+ bool ho_release_pending;
+ u8 live_top;
+ bool reselect_armed;
+ bool flush_pending;
+ bool table_pending;
+ u8 table_back[SIT9531X_PRIO_MAX_SLOTS];
+ bool tap_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;
@@ -176,6 +212,7 @@ struct sit9531x_chan {
* @irq: optional INTRB IRQ, 0 if not wired
* @intsync_src: PLL sourcing INTSYNC, or -1
* @irq_ack_fails: consecutive failed INTRB acknowledgements
+ * @loops_open: output loops not yet re-locked
*/
struct sit9531x_dev {
struct device *dev;
@@ -206,6 +243,8 @@ struct sit9531x_dev {
int irq;
s8 intsync_src;
+
+ bool loops_open;
};
int sit9531x_read_u8(struct sit9531x_dev *sitdev, unsigned int reg,
@@ -219,6 +258,18 @@ int sit9531x_write_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,
int sit9531x_update_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,
u8 offset, u8 mask, u8 val);
+int sit9531x_input_disable(struct sit9531x_dev *sitdev, u8 index);
+int sit9531x_input_enable(struct sit9531x_dev *sitdev, u8 index);
+
+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);
+
static inline u8 sit9531x_pll_page(u8 pll_idx)
{
return SIT9531X_PAGE_PLLA + pll_idx;
@@ -287,4 +338,10 @@ sit9531x_chan_state_get(const struct sit9531x_dev *sitdev, u8 pll_idx)
return &sitdev->chan[pll_idx];
}
+static inline bool sit9531x_chan_tracking(const struct sit9531x_chan *chan)
+{
+ return chan->locked && !chan->inner_lol && !chan->mode &&
+ !chan->ho_freeze && !chan->stale;
+}
+
#endif /* _SIT9531X_CORE_H */
diff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c
index 6d676fabf13e..56bdec4bcf67 100644
--- a/drivers/dpll/sit9531x/dpll.c
+++ b/drivers/dpll/sit9531x/dpll.c
@@ -34,10 +34,23 @@ sit9531x_dpll_is_xo_pin(const struct sit9531x_dpll_pin *pin)
pin->id == SIT9531X_MAX_INPUTS;
}
+/*
+ * A frozen PLL with an empty table is in forced holdover; report holdover only
+ * while its holdover memory is valid.
+ */
+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;
+}
+
/*
* Report the lock status from the last poll: outer LOL (page 0 reg 0x06), PLL
- * mode (PLL page reg 0x31), inner LOL (reg 0x92), holdover freeze (reg 0x0A)
- * and holdover-valid (PLL page reg 0x06).
+ * mode (PLL page reg 0x31), inner LOL (reg 0x92), holdover freeze (reg 0x0A),
+ * holdover-valid (PLL page reg 0x06) and priority table.
*/
static int
sit9531x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
@@ -71,7 +84,7 @@ sit9531x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
* The core reports the error detail only with an unlocked or
* holdover status.
*/
- *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) {
/*
@@ -84,8 +97,8 @@ sit9531x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
* Test freeze before lock: a frozen PLL tracks no reference
* whatever its outer loss-of-lock bit reads.
*/
- *status = DPLL_LOCK_STATUS_HOLDOVER;
- } else if (chan->locked) {
+ *status = sit9531x_dpll_frozen_status(chan);
+ } else if (sit9531x_chan_tracking(chan)) {
if (chan->ho_valid)
*status = DPLL_LOCK_STATUS_LOCKED_HO_ACQ;
else
@@ -155,6 +168,120 @@ const struct dpll_device_ops sit9531x_dpll_device_ops = {
.supported_modes_get = sit9531x_dpll_supported_modes_get,
};
+/*
+ * Pin-state contract. An input is SELECTABLE while listed in this PLL's
+ * priority table and DISCONNECTED otherwise; CONNECTED is refused because the
+ * device selects by priority. Operstate is ACTIVE when the device feeds the PLL
+ * this input (chan->routed_ref), the PLL is tracking and the lane has signal;
+ * NO_SIGNAL on loss of signal; QUAL_FAILED on frequency drift; STANDBY
+ * otherwise. The routed reference is used because on loss of signal the device
+ * falls back to another listed source and leaves the selection naming the lost
+ * one. On manual input select, the routed input is CONNECTED. The XO pin is
+ * always CONNECTED.
+ */
+
+/* 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->manual_input) {
+ if (chan->routed_ref == pin_id && !chan->stale)
+ *state = DPLL_PIN_STATE_CONNECTED;
+ else
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+ return;
+ }
+
+ if (chan->prio_mask & BIT(sit9531x_input_hw_src(pin_id)))
+ *state = DPLL_PIN_STATE_SELECTABLE;
+ else
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+}
+
+/* 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 || !sit9531x_chan_tracking(chan))
+ return false;
+
+ /*
+ * Do not report a lane without signal as locked, whatever the lock bit
+ * says; the PLL is about to switch away from it.
+ */
+ if (pin_id < sitdev->info->num_inputs &&
+ sit9531x_ref_state_get(sitdev, pin_id)->los)
+ return false;
+
+ return true;
+}
+
+/* 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,
@@ -167,8 +294,123 @@ sit9531x_dpll_input_pin_direction_get(const struct dpll_pin *pin,
return 0;
}
+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;
+}
+
+/*
+ * The input receiver is shared by all DPLLs and is switched off when
+ * ref->pll_mask becomes empty.
+ */
+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:
+ if (!sit9531x_input_prio_present(sitdev, sitdpll->id, hw_src) &&
+ !ref->off_pending) {
+ rc = 0;
+ break;
+ }
+ rc = sit9531x_input_prio_remove(sitdev, sitdpll->id, hw_src);
+ if (rc && sit9531x_input_prio_present(sitdev, sitdpll->id,
+ hw_src))
+ break;
+ ref->pll_mask &= ~pll_bit;
+ 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:
+ NL_SET_ERR_MSG(extack,
+ "Device selects its reference by priority; use selectable");
+ rc = -EOPNOTSUPP;
+ break;
+ case DPLL_PIN_STATE_SELECTABLE:
+ /*
+ * The receiver can be off even when this PLL already lists the
+ * input.
+ */
+ 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)) {
+ if (enabled_here && !ref->pll_mask)
+ sit9531x_input_disable(sitdev, dpin->id);
+ break;
+ }
+ ref->pll_mask |= pll_bit;
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&sitdev->multiop_lock);
+
+ 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,
+};
+
+/*
+ * For a PLL on manual input select: the select pins or register choose the
+ * reference, so the pin has no state setter and no priority.
+ */
+static const struct dpll_pin_ops sit9531x_dpll_input_pin_manual_ops = {
+ .direction_get = sit9531x_dpll_input_pin_direction_get,
+ .state_on_dpll_get = sit9531x_dpll_input_pin_state_on_dpll_get,
+ .operstate_on_dpll_get = sit9531x_dpll_input_pin_operstate_on_dpll_get,
};
/*
@@ -223,10 +465,15 @@ static const struct dpll_pin_ops sit9531x_dpll_output_pin_ops = {
const struct dpll_pin_ops *
sit9531x_dpll_pin_ops_get(const struct sit9531x_dpll_pin *pin)
{
+ const struct sit9531x_dev *sitdev = pin->dpll->dev;
+
if (!sit9531x_dpll_is_input_pin(pin))
return &sit9531x_dpll_output_pin_ops;
if (sit9531x_dpll_is_xo_pin(pin))
return &sit9531x_dpll_xo_pin_ops;
+ /* Read once at probe, before any pin is registered. */
+ if (sit9531x_chan_state_get(sitdev, pin->dpll->id)->manual_input)
+ return &sit9531x_dpll_input_pin_manual_ops;
return &sit9531x_dpll_input_pin_ops;
}
diff --git a/drivers/dpll/sit9531x/regs.h b/drivers/dpll/sit9531x/regs.h
index 832d0360a398..39d836f6b563 100644
--- a/drivers/dpll/sit9531x/regs.h
+++ b/drivers/dpll/sit9531x/regs.h
@@ -96,6 +96,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)
@@ -183,6 +187,29 @@
#define SIT9531X_PLL_STATUS_OUTER_DIS BIT(5)
+/*
+ * Writing the key unlocks the page's debug registers; writing zero locks them.
+ */
+#define SIT9531X_PLL_REG_DEBUG 0xBD
+#define SIT9531X_PLL_DEBUG_UNLOCK 0xC3
+#define SIT9531X_PLL_DEBUG_LOCK 0x00
+
+/*
+ * The address register selects the block the data register reports. A data read
+ * returns what the previous read latched; the documented procedures read it
+ * three times.
+ */
+#define SIT9531X_PLL_REG_DBG_STATUS_ADDR 0xD1
+#define SIT9531X_PLL_REG_DBG_STATUS_DATA 0xD0
+#define SIT9531X_DBG_STATUS_READS 3
+
+/*
+ * Low nibble: the source the input subsystem routes to the PLL, 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: manual active select, no priority arbitration. The
* register is GUI generated; the driver only reads it.
@@ -213,6 +240,17 @@
#define SIT9531X_PLL_REG_STATUS_1 0x06
#define SIT9531X_PLL_STATUS_1_HO_VALID BIT(2)
+/*
+ * Clock monitor status, one nibble per input lane; the lower-numbered input of
+ * each register is in the low nibble.
+ */
+#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-11-10 20:26 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 20:26 [PATCH net-next v15 00/13] dpll: add SiTime SiT9531x DPLL clock driver Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 01/13] dpll: omit pin phase attributes the driver has no value for Ali Rouhi
2026-11-11 15:35 ` Ivan Vecera
2026-11-10 20:26 ` [PATCH net-next v15 02/13] dt-bindings: vendor-prefixes: add SiTime Corporation Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 04/13] dpll: add basic SiTime SiT9531x support Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 03/13] dt-bindings: dpll: add SiTime SiT95316 clock generator Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 05/13] dpll: sit9531x: read DPLL types and pin properties from system firmware Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 06/13] dpll: sit9531x: register DPLL devices and pins Ali Rouhi
2026-11-10 20:26 ` Ali Rouhi [this message]
2026-11-10 20:26 ` [PATCH net-next v15 09/13] dpll: sit9531x: add support to get and set frequency on pins Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 08/13] dpll: sit9531x: add support to get and set priority on input pins Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 10/13] dpll: sit9531x: implement output pin state on a DPLL Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 11/13] dpll: sit9531x: add support to adjust output phase Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 12/13] dpll: sit9531x: add support to get phase offset on the connected input pin Ali Rouhi
2026-11-10 20:26 ` [PATCH net-next v15 13/13] dpll: sit9531x: model the inter-PLL sync line 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=20261110202607.55744-8-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=prabhakar.mahadev-lad.rj@bp.renesas.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®