[PATCH net-next v9 06/15] dpll: sit9531x: implement input pin state on a DPLL
From: Ali Rouhi
Date: Mon Sep 14 2026 - 20:06:23 EST
From: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>
Report and set whether an input feeds a given PLL. The device selects
its reference itself from a priority table, so membership of that table is
what "connected" means here: an input in the table is selectable, the one
the PLL has locked to is connected, and an input absent from the table is
disconnected. Setting the state therefore adds the input to the table or
removes it, and the table is read back rather than remembered.
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.
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.
Signed-off-by: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>
Assisted-by: Claude:claude-4-opus [chat]
Signed-off-by: Ali Rouhi <arouhi@xxxxxxxxxx>
---
drivers/dpll/sit9531x/core.c | 501 ++++++++++++++++++++++++++++++++++-
drivers/dpll/sit9531x/core.h | 2 +
drivers/dpll/sit9531x/dpll.c | 171 ++++++++++++
drivers/dpll/sit9531x/regs.h | 4 +
4 files changed, 669 insertions(+), 9 deletions(-)
diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c
index 6dfb1b31d491..25418485e975 100644
--- a/drivers/dpll/sit9531x/core.c
+++ b/drivers/dpll/sit9531x/core.c
@@ -185,6 +185,93 @@ 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: the force and state bits only mean something
+ * together, so a half-programmed override is not left behind on a
+ * transient bus error.
+ *
+ * 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;
+
+ if (rc)
+ return rc;
+
+ ref->enabled = false;
+
+ return 0;
+}
+
+/*
+ * 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)
*
@@ -347,6 +434,92 @@ 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;
+}
+
+/*
+ * 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));
+}
+
/*
* 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
@@ -365,6 +538,90 @@ static void sit9531x_prio_mask_build(struct sit9531x_dev *sitdev, u8 pll_idx,
sitdev->chan[pll_idx].prio_mask = mask;
}
+/* Attempts to release a forced holdover before reporting it stuck. */
+#define SIT9531X_HO_CLEAR_TRIES 3
+
+/*
+ * sit9531x_prio_table_commit - write a full priority table for a PLL
+ * @srcs: array of SIT9531X_PRIO_MAX_SLOTS source codes, slot 0 first
+ *
+ * Programs all priority slots (nibble-packed, two per register) for
+ * the PLL using the same holdover / small-update sequence as
+ * sit9531x_input_prio_set(). Caller must hold sitdev->multiop_lock.
+ */
+static int sit9531x_prio_table_commit(struct sit9531x_dev *sitdev, u8 pll_idx,
+ const u8 *srcs)
+{
+ u8 val, slot, attempt;
+ int rc, prg_rc, ho_rc = 0;
+ u16 reg;
+
+ rc = sit9531x_update_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_HO_CTRL,
+ BIT(SIT9531X_PLL_HO_FORCE_BIT),
+ BIT(SIT9531X_PLL_HO_FORCE_BIT));
+ if (rc)
+ return rc;
+
+ usleep_range(10000, 12000);
+
+ for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) {
+ reg = sit9531x_prio_reg(pll_idx, slot);
+
+ rc = sit9531x_read_u8(sitdev, reg, &val);
+ if (rc)
+ goto commit;
+
+ val = sit9531x_prio_slot_set(val, slot, srcs[slot]);
+
+ rc = sit9531x_write_u8(sitdev, reg, val);
+ if (rc)
+ goto commit;
+ }
+
+commit:
+ /*
+ * Latch unconditionally: slots written before a failed write are in
+ * the table regardless, so the latch keeps hardware and the mask
+ * rebuild below consistent with what was actually written.
+ */
+ prg_rc = sit9531x_prio_prg_commit(sitdev);
+ if (prg_rc && !rc)
+ rc = prg_rc;
+
+ /*
+ * Refresh the mask from the table just written so a get that follows
+ * a set does not have to wait for the next poll. Slots written
+ * before a failed write are in the table too, so this is closer to
+ * the truth than the pre-write mask either way, and the poll read-back
+ * corrects whatever a partial write left behind.
+ */
+ sit9531x_prio_mask_build(sitdev, pll_idx, srcs);
+
+ /*
+ * Release the forced holdover. Nothing else in the driver clears
+ * this bit, so a PLL left with it set reports holdover until the
+ * next table write on the same PLL happens to clear it, which may
+ * never come. Retry before giving up, and say so if it stays set.
+ */
+ 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;
+ }
+
+ return rc;
+}
+
/*
* sit9531x_prio_table_read - read a PLL's priority-table source codes
* @srcs: output array of SIT9531X_PRIO_MAX_SLOTS source codes
@@ -389,6 +646,208 @@ static int sit9531x_prio_table_read(struct sit9531x_dev *sitdev, u8 pll_idx,
return 0;
}
+/*
+ * sit9531x_input_prio_set - move an input to a priority slot
+ * @input_idx: input source in hardware encoding (0-11, see
+ * sit9531x_input_hw_src())
+ * @prio: priority slot position (0 = highest)
+ *
+ * Reads the PLL's table, takes the source out of wherever it sits and
+ * reinserts it at @prio, shifting the entries in between. The rest keep
+ * their relative order: a priority change asks about one input, so the
+ * fallbacks configured behind it have to survive it.
+ *
+ * The table is what makes a source eligible for this PLL, so this only
+ * ever reorders sources already in it. A source that is absent is
+ * disconnected on this PLL, and inserting it here would make it a
+ * selection candidate again behind the caller's back; that is a connect,
+ * and it belongs to the pin's state setter.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ *
+ * Return: 0 on success, -EINVAL if the source is not in the table,
+ * <0 on error
+ */
+int sit9531x_input_prio_set(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx, u8 prio)
+{
+ u8 orig_srcs[SIT9531X_PRIO_MAX_SLOTS];
+ u8 srcs[SIT9531X_PRIO_MAX_SLOTS];
+ u8 slot, from;
+ 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;
+ if (prio >= SIT9531X_PRIO_MAX_SLOTS)
+ return -EINVAL;
+
+ rc = sit9531x_prio_table_read(sitdev, pll_idx, srcs);
+ if (rc)
+ return rc;
+
+ memcpy(orig_srcs, srcs, sizeof(srcs));
+
+ for (from = 0; from < SIT9531X_PRIO_MAX_SLOTS; from++)
+ if (sit9531x_prio_src_canon(sitdev, srcs[from]) == input_idx)
+ break;
+
+ if (from == SIT9531X_PRIO_MAX_SLOTS)
+ return -EINVAL;
+
+ if (from == prio)
+ return 0;
+
+ if (from > prio) {
+ /* Moving up: push the entries in between down one slot. */
+ for (slot = from; slot > prio; slot--)
+ srcs[slot] = srcs[slot - 1];
+ } else {
+ for (slot = from; slot < prio; slot++)
+ srcs[slot] = srcs[slot + 1];
+ }
+
+ srcs[prio] = input_idx;
+ if (!memcmp(srcs, orig_srcs, sizeof(srcs)))
+ return 0;
+
+ return sit9531x_prio_table_commit(sitdev, pll_idx, srcs);
+}
+
+/*
+ * sit9531x_input_prio_remove - drop an input from a PLL's priority table
+ * @input_idx: input source in hardware encoding
+ *
+ * Rewrites the priority table with the source removed: the remaining
+ * sources are compacted toward the highest-priority slots and the freed
+ * tail slots are backfilled with the lowest-priority remaining source,
+ * since the 4-bit slot encoding has no "empty" value. This makes a
+ * disconnected input ineligible for automatic reference selection, not
+ * just gated at the input buffer.
+ *
+ * Removing a source that is absent is what the caller asked for already,
+ * so it succeeds without touching the table. Removing the only source
+ * would leave the table empty, which the device does not accept; that
+ * fails with -EBUSY rather than reporting a success the hardware never
+ * carried out.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ *
+ * Return: 0 on success, -EBUSY if the source is the only entry, <0 on
+ * error
+ */
+int sit9531x_input_prio_remove(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx)
+{
+ u8 srcs[SIT9531X_PRIO_MAX_SLOTS];
+ u8 kept[SIT9531X_PRIO_MAX_SLOTS];
+ u8 slot, count = 0;
+ bool found = false;
+ 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);
+
+ rc = sit9531x_prio_table_read(sitdev, pll_idx, srcs);
+ if (rc)
+ return rc;
+
+ for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) {
+ if (sit9531x_prio_src_canon(sitdev, srcs[slot]) == input_idx)
+ found = true;
+ else
+ kept[count++] = srcs[slot];
+ }
+
+ if (!found)
+ return 0;
+
+ if (count == 0)
+ return -EBUSY;
+
+ /* Backfill freed tail slots with the lowest-priority remaining src */
+ while (count < SIT9531X_PRIO_MAX_SLOTS) {
+ kept[count] = kept[count - 1];
+ count++;
+ }
+
+ return sit9531x_prio_table_commit(sitdev, pll_idx, kept);
+}
+
+/*
+ * sit9531x_input_prio_add - make an input eligible in a PLL's table
+ * @input_idx: input source in hardware encoding
+ *
+ * Ensures the source appears in the priority table so it can be picked
+ * by automatic reference selection again after a disconnect. If the
+ * source is already listed the table is left untouched; otherwise it
+ * replaces a duplicate at the tail. A table that contains only unique
+ * sources cannot accept a new one without evicting another, so that
+ * case fails with -ENOSPC.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+int sit9531x_input_prio_add(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 input_idx)
+{
+ u8 srcs[SIT9531X_PRIO_MAX_SLOTS];
+ u8 seen[SIT9531X_PRIO_NUM_SRC] = { 0 };
+ u8 replace = SIT9531X_PRIO_MAX_SLOTS;
+ u8 slot, src, src_canon;
+ 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;
+
+ rc = sit9531x_prio_table_read(sitdev, pll_idx, srcs);
+ if (rc)
+ return rc;
+
+ for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++)
+ if (sit9531x_prio_src_canon(sitdev, srcs[slot]) == input_idx)
+ return 0;
+
+ for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) {
+ src = srcs[slot] & SIT9531X_PRIO_NIBBLE_MASK;
+ src_canon = sit9531x_prio_src_canon(sitdev, src);
+ if (src_canon >= SIT9531X_PRIO_NUM_SRC)
+ continue;
+
+ seen[src_canon]++;
+ }
+
+ for (slot = SIT9531X_PRIO_MAX_SLOTS; slot-- > 0;) {
+ src = srcs[slot] & SIT9531X_PRIO_NIBBLE_MASK;
+ src_canon = sit9531x_prio_src_canon(sitdev, src);
+ if (src_canon >= SIT9531X_PRIO_NUM_SRC)
+ continue;
+
+ if (seen[src_canon] > 1) {
+ replace = slot;
+ break;
+ }
+ }
+
+ if (replace == SIT9531X_PRIO_MAX_SLOTS)
+ return -ENOSPC;
+
+ srcs[replace] = input_idx;
+
+ return sit9531x_prio_table_commit(sitdev, pll_idx, srcs);
+}
+
/* XO doubler register */
#define SIT9531X_REG_XO2_GENERIC SIT9531X_REG(0x00, 0x2D)
#define SIT9531X_XO_DOUBLER_ENB_BIT 7 /* inverted: 0 = enabled */
@@ -1308,14 +1767,38 @@ static void sit9531x_dpll_pins_unregister(struct sit9531x_dpll *sitdpll)
}
}
+/*
+ * 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;
+}
+
/*
* sit9531x_dpll_pin_is_registrable - check if a pin should be registered
* @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().
+ * For output pins: the pin is registrable if this DPLL is routed to it,
+ * whether or not it is currently driving.
*
* Return: true if pin should be registered, false otherwise
*/
@@ -1323,15 +1806,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 5ead3811e14b..f628bd5b7a97 100644
--- a/drivers/dpll/sit9531x/core.h
+++ b/drivers/dpll/sit9531x/core.h
@@ -231,6 +231,8 @@ 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_get(struct sit9531x_dev *sitdev, u8 pll_idx,
diff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c
index b4984335e6bf..ab9aa7db1839 100644
--- a/drivers/dpll/sit9531x/dpll.c
+++ b/drivers/dpll/sit9531x/dpll.c
@@ -288,6 +288,41 @@ 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;
+ bool active_input;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ chan = sit9531x_chan_state_get(sitdev, sitdpll->id);
+ active_input = !chan->mode && chan->locked && !chan->inner_lol &&
+ chan->selected_ref == pin_id;
+
+ if (!(chan->prio_mask & BIT(sit9531x_input_hw_src(pin_id))))
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+ else if (active_input)
+ *state = DPLL_PIN_STATE_CONNECTED;
+ else
+ *state = DPLL_PIN_STATE_SELECTABLE;
+}
+
static int
sit9531x_dpll_input_pin_direction_get(const struct dpll_pin *pin,
void *pin_priv,
@@ -300,8 +335,144 @@ 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);
+ 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;
+ if (rc)
+ break;
+ if (ref->pll_mask)
+ rc = 0; /* another DPLL still uses this input */
+ else
+ rc = sit9531x_input_disable(sitdev, dpin->id);
+ 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_set()).
+ * 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:
+ if (!(ref->pll_mask & pll_bit)) {
+ rc = sit9531x_input_enable(sitdev, dpin->id);
+ if (rc)
+ break;
+ }
+ rc = sit9531x_input_prio_add(sitdev, sitdpll->id, hw_src);
+ if (rc && !sit9531x_input_prio_present(sitdev, sitdpll->id,
+ hw_src)) {
+ if (!(ref->pll_mask & pll_bit) && !ref->pll_mask)
+ 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 == -EBUSY)
+ NL_SET_ERR_MSG(extack,
+ "Only source left in the priority table; it cannot be emptied");
+ else 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,
};
/*
diff --git a/drivers/dpll/sit9531x/regs.h b/drivers/dpll/sit9531x/regs.h
index a892948da38d..da61a65ac2d2 100644
--- a/drivers/dpll/sit9531x/regs.h
+++ b/drivers/dpll/sit9531x/regs.h
@@ -135,6 +135,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)
--
2.39.2 (Apple Git-143)