[PATCH net-next v8 05/15] dpll: sit9531x: register DPLL devices and pins
From: Ali Rouhi
Date: Wed Sep 02 2026 - 18:10:59 EST
From: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>
Register each of the four on-chip PLLs as a DPLL device and the clocks
around them as pins: the inputs, the crystal, and the outputs the loaded
configuration routes to a PLL. An output routed to no PLL gets no pin,
since there would be nothing for the core to report about it.
The callbacks a device needs from the start come with it -- lock status
and the operating mode -- along with the direction of each pin. The rest
of the pin operations follow in later patches.
Both need somewhere to read from. The chip reports its state across
several pages, and a netlink call must not turn into a burst of I2C
transactions, so a kthread worker polls the interesting registers twice a
second into a per-input, per-output and per-PLL cache, and the callbacks
answer from it. The same tick compares the new state against the old and
notifies the core only on a change. Where a board wires INTRB, the
interrupt schedules that tick immediately rather than replacing it: the
poll stays as the fallback, since this device gives no way to be sure an
edge was not missed.
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/Makefile | 2 +-
drivers/dpll/sit9531x/core.c | 1517 +++++++++++++++++++++++++++++++-
drivers/dpll/sit9531x/core.h | 264 +++++-
drivers/dpll/sit9531x/dpll.c | 417 +++++++++
drivers/dpll/sit9531x/dpll.h | 69 ++
drivers/dpll/sit9531x/regs.h | 223 ++++-
6 files changed, 2417 insertions(+), 75 deletions(-)
create mode 100644 drivers/dpll/sit9531x/dpll.c
create mode 100644 drivers/dpll/sit9531x/dpll.h
diff --git a/drivers/dpll/sit9531x/Makefile b/drivers/dpll/sit9531x/Makefile
index 819af61123f5..b97d2656a460 100644
--- a/drivers/dpll/sit9531x/Makefile
+++ b/drivers/dpll/sit9531x/Makefile
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_SIT9531X_DPLL) += sit9531x.o
-sit9531x-y := core.o prop.o
+sit9531x-y := core.o dpll.o prop.o
diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c
index f355b08aebca..1394ea1077d9 100644
--- a/drivers/dpll/sit9531x/core.c
+++ b/drivers/dpll/sit9531x/core.c
@@ -6,9 +6,11 @@
* Author: Ali Rouhi <arouhi@xxxxxxxxxx>
* Author: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>
*
- * I2C probe, paged regmap configuration and register access helpers.
+ * Core I2C probe, regmap configuration, hardware state management,
+ * and periodic work thread.
*/
+#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/delay.h>
@@ -16,13 +18,25 @@
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
+#include <linux/interrupt.h>
#include <linux/kernel.h>
+#include <linux/kthread.h>
#include <linux/module.h>
+#include <linux/property.h>
#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
#include "core.h"
+#include "dpll.h"
+#include "prop.h"
#include "regs.h"
+/* Number of input + output pin positions for pin index allocation */
+#define SIT9531X_NUM_INPUT_PINS (SIT9531X_MAX_INPUTS + 2) /* +xtal +INTSYNC */
+#define SIT9531X_NUM_OUTPUT_PINS (SIT9531X_MAX_OUTPUTS + 1) /* +INTSYNC src */
+#define SIT9531X_NUM_PINS_TOTAL (SIT9531X_NUM_INPUT_PINS + SIT9531X_NUM_OUTPUT_PINS)
+
#define SIT9531X_CHIP(_id, _nin, _nout, _name, _map) \
{ .id = (_id), .num_inputs = (_nin), .num_outputs = (_nout), \
.name = (_name), .clkout_map = (_map) }
@@ -153,76 +167,1487 @@ int sit9531x_update_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,
* Selects the correct Page 0x02 register pair based on the pair's
* signal mode and the lane (P/N) the index refers to.
*/
+static void sit9531x_input_get_regs(const struct sit9531x_dev *sitdev,
+ u8 index,
+ unsigned int *force_reg,
+ unsigned int *state_reg)
+{
+ if (sitdev->ref[index].sig_mode == SIT9531X_MODE_DE) {
+ *force_reg = SIT9531X_REG_IN_DE_FORCE;
+ *state_reg = SIT9531X_REG_IN_DE_STATE;
+ } else if (sit9531x_input_is_n(index)) {
+ *force_reg = SIT9531X_REG_IN_SEN_FORCE;
+ *state_reg = SIT9531X_REG_IN_SEN_STATE;
+ } else {
+ *force_reg = SIT9531X_REG_IN_SEP_FORCE;
+ *state_reg = SIT9531X_REG_IN_SEP_STATE;
+ }
+}
-static int sit9531x_read_variant_id(struct sit9531x_dev *sitdev, u8 *id)
+/*
+ * Output enable / disable (Hi-Z control)
+ *
+ * SiT9531x outputs can be configured as differential (DIFF) or
+ * single-ended (SE) depending on the factory blob. Each output slot
+ * has TWO Hi-Z force/state register pairs on Page 0x03 -- one for the
+ * DIFF path, one for the SE path.
+ *
+ * We write to BOTH pairs so the function mutes the output regardless
+ * of whether it's been configured DIFF or SE on this board.
+ *
+ * slot 0-7 :
+ * DIFF mask=0xF2 state=0xF3 SE mask=0xF8 state=0xF9
+ * slot 8-11:
+ * DIFF mask=0xF4 state=0xF5 SE mask=0xFA state=0xFB
+ *
+ * MASK bit = 1 -> driver takes control of that output's Hi-Z state
+ * STATE bit = 0 -> output is forced to Hi-Z (muted)
+ * STATE bit = 1 -> output is driven (active)
+ *
+ * The output "index" in the driver is logical; the physical slot comes
+ * from info->clkout_map[].
+ */
+
+struct sit9531x_hiz_regs {
+ unsigned int diff_mask;
+ unsigned int diff_state;
+ unsigned int se_mask;
+ unsigned int se_state;
+ u8 bit;
+};
+
+static void sit9531x_output_get_hiz_regs(u8 slot,
+ struct sit9531x_hiz_regs *r)
{
- return sit9531x_read_u8(sitdev, SIT9531X_REG_VARIANT_ID, id);
+ if (slot <= 7) {
+ r->diff_mask = SIT9531X_REG_HIZ_DIFF_07_MASK;
+ r->diff_state = SIT9531X_REG_HIZ_DIFF_07_STATE;
+ r->se_mask = SIT9531X_REG_HIZ_SE_07_MASK;
+ r->se_state = SIT9531X_REG_HIZ_SE_07_STATE;
+ r->bit = slot;
+ } else {
+ r->diff_mask = SIT9531X_REG_HIZ_DIFF_811_MASK;
+ r->diff_state = SIT9531X_REG_HIZ_DIFF_811_STATE;
+ r->se_mask = SIT9531X_REG_HIZ_SE_811_MASK;
+ r->se_state = SIT9531X_REG_HIZ_SE_811_STATE;
+ r->bit = slot - 8;
+ }
}
-static const struct sit9531x_chip_info *sit9531x_match_variant(u8 id)
+/*
+ * Report whether a slot is currently forced into Hi-Z, i.e. the driver
+ * (or the blob) took control of the Hi-Z state (MASK bit set) and drives
+ * it low (STATE bit clear). Either register pair muting the slot counts,
+ * mirroring what sit9531x_output_disable() programs.
+ */
+static int sit9531x_output_forced_hiz(struct sit9531x_dev *sitdev, u8 slot,
+ bool *muted)
{
- unsigned int i;
+ struct sit9531x_hiz_regs r;
+ u8 mask, state;
+ int rc;
- for (i = 0; i < ARRAY_SIZE(sit9531x_chip_ids); i++) {
- if (sit9531x_chip_ids[i].id == id)
- return &sit9531x_chip_ids[i];
+ sit9531x_output_get_hiz_regs(slot, &r);
+
+ rc = sit9531x_read_u8(sitdev, r.diff_mask, &mask);
+ if (rc)
+ return rc;
+ rc = sit9531x_read_u8(sitdev, r.diff_state, &state);
+ if (rc)
+ return rc;
+
+ *muted = (mask & BIT(r.bit)) && !(state & BIT(r.bit));
+ if (*muted)
+ return 0;
+
+ rc = sit9531x_read_u8(sitdev, r.se_mask, &mask);
+ if (rc)
+ return rc;
+ rc = sit9531x_read_u8(sitdev, r.se_state, &state);
+ if (rc)
+ return rc;
+
+ *muted = (mask & BIT(r.bit)) && !(state & BIT(r.bit));
+
+ return 0;
+}
+
+/*
+ * Input priority selection
+ *
+ * The SiT9531x has an 11-slot priority table per PLL on Page 1. Each
+ * register holds two slots nibble-packed: the earlier (even, 2n) slot
+ * in [7:4] and the later (odd, 2n+1) slot in [3:0].
+ *
+ * The procedure:
+ * 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
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+
+/* Page-1 register holding priority slot @slot of @pll_idx. */
+static u16 sit9531x_prio_reg(u8 pll_idx, u8 slot)
+{
+ return SIT9531X_REG(SIT9531X_PAGE_PRIOSYS,
+ SIT9531X_PRIO_BASE_REG +
+ SIT9531X_PRIO_REGS_PER_PLL * pll_idx +
+ slot / SIT9531X_PRIO_SLOTS_PER_REG);
+}
+
+/*
+ * Extract priority slot @slot from its register value. The register
+ * holding slots 2n and 2n+1 keeps the earlier slot in the high nibble
+ * (CLK_SPARE<2n>SEL) and the later one in the low nibble.
+ */
+static u8 sit9531x_prio_slot_get(u8 val, u8 slot)
+{
+ if (slot & 1)
+ return val & SIT9531X_PRIO_NIBBLE_MASK;
+
+ return val >> SIT9531X_PRIO_HI_SHIFT;
+}
+
+/*
+ * 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().
+ */
+static void sit9531x_prio_mask_build(struct sit9531x_dev *sitdev, u8 pll_idx,
+ const u8 *srcs)
+{
+ u16 mask = 0;
+ u8 slot;
+
+ for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++)
+ mask |= BIT(srcs[slot] & SIT9531X_PRIO_NIBBLE_MASK);
+
+ sitdev->chan[pll_idx].prio_mask = mask;
+}
+
+/*
+ * sit9531x_prio_table_read - read a PLL's priority-table source codes
+ * @srcs: output array of SIT9531X_PRIO_MAX_SLOTS source codes
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+static int sit9531x_prio_table_read(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 *srcs)
+{
+ u8 val, slot;
+ int rc;
+
+ for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) {
+ rc = sit9531x_read_u8(sitdev,
+ sit9531x_prio_reg(pll_idx, slot), &val);
+ if (rc)
+ return rc;
+
+ srcs[slot] = sit9531x_prio_slot_get(val, slot);
}
- return NULL;
+ return 0;
}
-int sit9531x_dev_probe(struct sit9531x_dev *sitdev)
+/* XO doubler register */
+#define SIT9531X_REG_XO2_GENERIC SIT9531X_REG(0x00, 0x2D)
+#define SIT9531X_XO_DOUBLER_ENB_BIT 7 /* inverted: 0 = enabled */
+
+/* VCO frequency bands (Hz) */
+#define SIT9531X_FVCO_LOWBAND_MIN 4915200000ULL
+#define SIT9531X_FVCO_LOWBAND_MAX 5898240000ULL
+#define SIT9531X_FVCO_HIGHBAND_MIN 6875000000ULL
+#define SIT9531X_FVCO_HIGHBAND_MAX 7812500000ULL
+
+/*
+ * Phase adjust (PRG_RST_DELAY register-based).
+ *
+ * The chip exposes a per-output 34-bit coarse delay measured in VCO
+ * clock periods plus a 3-bit fine delay in fixed 30 ps steps. The
+ * five bytes PROG6..PROG2 hold the field across registers:
+ * base + 0 PROG6 [7:5] OPSTG_VCASC_BUMP (preserved via RMW)
+ * [4:2] PRG_RST_FINE_DELAY
+ * [1:0] PRG_RST_DELAY[33:32]
+ * base + 1 PROG5 PRG_RST_DELAY[31:24]
+ * base + 2 PROG4 PRG_RST_DELAY[23:16]
+ * base + 3 PROG3 PRG_RST_DELAY[15:8]
+ * base + 4 PROG2 PRG_RST_DELAY[7:0]
+ *
+ * Outputs 0-5 live on Page 3, outputs 6-11 on Page 4, with each
+ * output's block at base = 0x15 + 16 * (out_idx % 6).
+ *
+ * The chip only supports unsigned positive delay. A negative phase
+ * adjustment (advance) is wrapped to (T_out - |phase|) modulo one
+ * output period, which is identical for a periodic signal.
+ */
+
+/*
+ * sit9531x_clear_notifications - clear all notification registers
+ *
+ * Clears all write-1-to-clear notification registers:
+ * - PLL outer LOL notification (Page 0, reg 0x07)
+ * - PLL holdover freeze notification (Page 0, reg 0x0B)
+ * - PLL inner LOL notification (Page 0, reg 0x93)
+ * - Clock monitor XO/PLL notification (Page 0, reg 0x9E)
+ * - Clock input notifications (Page 6, regs 0x03/0x07/0x93/0x97)
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+int sit9531x_clear_notifications(struct sit9531x_dev *sitdev)
{
- struct clk *xtal_clk;
- u8 variant_id;
+ int rc;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ /* Page 0x00 W1C notification registers */
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_OUTER_LOL_NOTIF, 0xFF);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_HO_FREEZE_NOTIF, 0xFF);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_PLL_INNER_LOL_NOTIF, 0xFF);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_REG_CMON_NOTIF, 0xFF);
+ if (rc)
+ return rc;
+
+ /* Page 0x06 clock input monitor notifications */
+ rc = sit9531x_write_u8(sitdev, SIT9531X_CLKMON_P_NOTIF_01, 0xFF);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_CLKMON_P_NOTIF_23, 0xFF);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_CLKMON_N_NOTIF_01, 0xFF);
+ if (rc)
+ return rc;
+ rc = sit9531x_write_u8(sitdev, SIT9531X_CLKMON_N_NOTIF_23, 0xFF);
+ if (rc)
+ return rc;
+
+ dev_dbg(sitdev->dev, "All notification registers cleared\n");
+ return 0;
+}
+
+/*
+ * sit9531x_ref_state_fetch - read input reference status from hardware
+ * @index: logical input index
+ *
+ * Reads LOS and OOF status for the given input lane from the Page 0x06
+ * clock monitor registers. P and N lanes have separate register banks;
+ * each register carries two input pairs nibble-packed (even pair in
+ * bits [3:0], odd pair in [7:4]).
+ */
+static int sit9531x_ref_state_fetch(struct sit9531x_dev *sitdev, u8 index)
+{
+ unsigned int reg, force_reg, state_reg;
+ u8 pair, status, force, state;
+ struct sit9531x_ref *ref;
int rc;
/*
- * Fvco = Fref * (DIVN + frac/2^32) with Fref derived from the XO
- * feeding XIN/XO_CLK, so the rate is needed before anything can be
- * computed from a divider.
+ * The XTAL/XO reference (index SIT9531X_MAX_INPUTS) is the on-chip
+ * oscillator that feeds every PLL. It cannot be routed or deselected,
+ * so its pin is modeled as permanently connected (see
+ * sit9531x_dpll_xo_pin_ops) and its LOS/OOF flags are never consulted.
+ * Only the routable per-lane inputs (0..num_inputs-1) are polled here.
*/
- xtal_clk = devm_clk_get_enabled(sitdev->dev, "xtal");
- if (IS_ERR(xtal_clk))
- return dev_err_probe(sitdev->dev, PTR_ERR(xtal_clk),
- "Failed to get xtal clock\n");
- sitdev->xtal_freq = clk_get_rate(xtal_clk);
- if (!sitdev->xtal_freq)
- return dev_err_probe(sitdev->dev, -EINVAL,
- "xtal clock has no rate\n");
+ if (index >= SIT9531X_MAX_INPUTS)
+ return -EINVAL;
+
+ ref = &sitdev->ref[index];
+ pair = sit9531x_input_pair(index);
+
+ if (sit9531x_input_is_n(index))
+ reg = pair < 2 ? SIT9531X_CLKMON_N_STATUS_01
+ : SIT9531X_CLKMON_N_STATUS_23;
+ else
+ reg = pair < 2 ? SIT9531X_CLKMON_P_STATUS_01
+ : SIT9531X_CLKMON_P_STATUS_23;
+
+ rc = sit9531x_read_u8(sitdev, reg, &status);
+ if (rc)
+ return rc;
+
+ if (pair & 1)
+ status >>= 4;
+
+ ref->los = !!(status & (BIT(SIT9531X_CLKMON_CLK_LOSS) |
+ BIT(SIT9531X_CLKMON_CLK_LOSS_FD)));
+ ref->oof = !!(status & (BIT(SIT9531X_CLKMON_FREQ_FINE) |
+ BIT(SIT9531X_CLKMON_FREQ_COARSE)));
/*
- * Held deasserted, never pulsed: the chip configuration comes from
- * efuse or an NVM blob applied before probe, and a reset would
- * discard it. Must precede the first I2C access, as a board that
- * powers up asserted keeps the chip unreachable until released.
+ * Whether the receiver is on. This has to come from the chip: it
+ * is the loaded configuration that decides, and without reading it
+ * back every input would look disabled until something called
+ * sit9531x_input_enable(). A lane counts as disabled only while
+ * the force bit overrides it to the off state; with the force bit
+ * clear it follows the configuration, which is the enabled case.
*/
- sitdev->reset_gpio = devm_gpiod_get_optional(sitdev->dev, "reset",
- GPIOD_OUT_LOW);
- if (IS_ERR(sitdev->reset_gpio))
- return dev_err_probe(sitdev->dev, PTR_ERR(sitdev->reset_gpio),
- "Failed to request reset gpio\n");
- if (sitdev->reset_gpio)
- fsleep(10000); /* internal boot after release */
+ sit9531x_input_get_regs(sitdev, index, &force_reg, &state_reg);
- rc = sit9531x_read_variant_id(sitdev, &variant_id);
+ rc = sit9531x_read_u8(sitdev, force_reg, &force);
+ if (rc)
+ return rc;
+ rc = sit9531x_read_u8(sitdev, state_reg, &state);
if (rc)
return rc;
- sitdev->info = sit9531x_match_variant(variant_id);
- if (!sitdev->info)
- return dev_err_probe(sitdev->dev, -ENODEV,
- "Unknown variant ID: 0x%02x\n",
- variant_id);
+ ref->enabled = !((force & BIT(pair)) && !(state & BIT(pair)));
- rc = devm_mutex_init(sitdev->dev, &sitdev->multiop_lock);
+ return 0;
+}
+
+/*
+ * sit9531x_input_mode_fetch - detect SE/DE configuration of an input pair
+ * @pair: input pair number (0-3)
+ *
+ * Reads CLKINx_INPUT_MODE and stores the detected signal mode on both
+ * lanes of the pair. A pair with neither SE lane enabled is running
+ * differential.
+ */
+static int sit9531x_input_mode_fetch(struct sit9531x_dev *sitdev, u8 pair)
+{
+ enum sit9531x_signal_mode sig_mode;
+ u8 mode;
+ int rc;
+
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG_IN_MODE(pair), &mode);
if (rc)
- return dev_err_probe(sitdev->dev, rc,
- "Failed to initialize mutex\n");
+ return rc;
+
+ if (mode & (SIT9531X_IN_MODE_SE_P_EN | SIT9531X_IN_MODE_SE_N_EN))
+ sig_mode = SIT9531X_MODE_SE;
+ else
+ sig_mode = SIT9531X_MODE_DE;
+
+ sitdev->ref[pair * 2].sig_mode = sig_mode;
+ sitdev->ref[pair * 2 + 1].sig_mode = sig_mode;
+
+ dev_dbg(sitdev->dev, "CLKIN%u mode reg 0x%02x -> %s\n", pair, mode,
+ sig_mode == SIT9531X_MODE_DE ? "differential" : "single-ended");
+
+ return 0;
+}
+
+/*
+ * sit9531x_chan_state_fetch - read PLL channel status from hardware
+ *
+ * Reads lock status and mode from the PLL status register.
+ */
+/* Read the PLL active-state bit (PLL page reg 0x02 bit 0). */
+static int sit9531x_pll_is_active(struct sit9531x_dev *sitdev, u8 pll_idx,
+ bool *active)
+{
+ u8 v;
+ int rc;
+
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_ACTIVE, &v);
+ if (rc)
+ return rc;
+
+ *active = !!(v & SIT9531X_PLL_ACTIVE_BIT);
+
+ return 0;
+}
+
+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 srcs[SIT9531X_PRIO_MAX_SLOTS];
+ u8 pll_status_1;
+ bool active;
+ int rc;
+
+ /*
+ * Whether the PLL is running at all. The loss-of-lock bit read
+ * below is driven by the PLL itself, so on one the loaded
+ * configuration leaves unused it simply stays clear and would
+ * otherwise read as a lock.
+ */
+ rc = sit9531x_pll_is_active(sitdev, pll_idx, &active);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_STATUS, &status);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG_OUTER_LOL_STATUS,
+ &outer_lol);
+ 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)
+ return rc;
+
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG_HO_FREEZE_STATUS, &ho_freeze);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx, SIT9531X_PLL_REG_STATUS_1,
+ &pll_status_1);
+ if (rc)
+ return rc;
+
+ /*
+ * Which sources this PLL may select. The table is configuration and
+ * changes only through the driver, but reading it back keeps the
+ * membership the pin state getters report tied to the hardware
+ * instead of to a value the driver maintains on the side.
+ */
+ rc = sit9531x_prio_table_read(sitdev, pll_idx, srcs);
+ if (rc)
+ return rc;
+
+ sit9531x_prio_mask_build(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->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);
+
+ return 0;
+}
+
+/*
+ * sit9531x_out_state_fetch - read output status from hardware
+ *
+ * Reads the output PLL association from the PLL page output map
+ * registers into out->routed / out->pll_idx, and the current drive
+ * state from the Hi-Z force bits into out->enabled. The two are
+ * separate: routing decides whether the output gets a DPLL pin at all,
+ * while a muted but routed output keeps its pin and reports
+ * DPLL_PIN_STATE_DISCONNECTED until it is un-muted.
+ */
+static int sit9531x_out_state_fetch(struct sit9531x_dev *sitdev, u8 index)
+{
+ struct sit9531x_out *out = &sitdev->out[index];
+ u8 map_lo, map_hi, slot;
+ int pll_idx;
+ bool muted;
+ int rc;
+
+ slot = sitdev->info->clkout_map[index];
+
+ rc = sit9531x_output_forced_hiz(sitdev, slot, &muted);
+ if (rc)
+ return rc;
+
+ /*
+ * DT board-config override: the per-PLL OUTPUT_ENABLE bitmaps
+ * (0x27/0x28) do not unambiguously express output->PLL routing on
+ * every config (overlaps, and some outputs routed outside that
+ * path). When the board supplies an explicit map, trust it.
+ */
+ if (sitdev->out_pll_map_valid) {
+ u8 m = sitdev->out_pll_map[index];
+
+ if (m < SIT9531X_NUM_PLLS) {
+ out->pll_idx = m;
+ out->routed = true;
+ out->enabled = !muted;
+ } else {
+ out->pll_idx = 0;
+ out->routed = false;
+ out->enabled = false;
+ }
+ return 0;
+ }
+
+ /*
+ * The OUT_MAP_LO/HI bitmaps are indexed by the physical slot the
+ * output occupies on the chip, not by the driver's logical output
+ * index (translated above via the chip-info clkout_map[]: identity
+ * on SiT95316, non-contiguous on SiT95317).
+ *
+ * Determine which PLL drives this output by checking each PLL's
+ * output map registers (0x27 = slots 8-11, 0x28 = slots 0-7).
+ */
+ for (pll_idx = 0; pll_idx < SIT9531X_NUM_PLLS; pll_idx++) {
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_OUT_MAP_LO, &map_lo);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_read_pll_u8(sitdev, pll_idx,
+ SIT9531X_PLL_REG_OUT_MAP_HI, &map_hi);
+ if (rc)
+ return rc;
+
+ if (slot < 8) {
+ if (map_lo & BIT(slot)) {
+ out->pll_idx = pll_idx;
+ out->routed = true;
+ out->enabled = !muted;
+ return 0;
+ }
+ } else {
+ if (map_hi & BIT(slot - 8)) {
+ out->pll_idx = pll_idx;
+ out->routed = true;
+ out->enabled = !muted;
+ return 0;
+ }
+ }
+ }
+
+ /* Output not mapped to any PLL */
+ out->pll_idx = 0;
+ out->routed = false;
+ out->enabled = false;
+
+ return 0;
+}
+
+/*
+ * sit9531x_ref_pll_mask_fetch - seed the input-to-PLL usage masks
+ *
+ * ref->pll_mask is the refcount the disconnect path uses to decide when
+ * an input receiver may be powered down: the physical input is only
+ * disabled once the last DPLL has released it. It therefore has to
+ * start out matching the hardware. Without this pass every mask starts
+ * at zero, and disconnecting an input from one DPLL drops the mask to
+ * zero and disables a receiver the other DPLLs are still locked to.
+ *
+ * An input is counted for a PLL when it appears in that PLL's Page-1
+ * priority table, which is exactly the condition the connect and
+ * disconnect callbacks maintain. Sources that are not physical inputs
+ * (OCXO, INTSYNC) and reserved codes are skipped.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+static int sit9531x_ref_pll_mask_fetch(struct sit9531x_dev *sitdev)
+{
+ u8 srcs[SIT9531X_PRIO_MAX_SLOTS];
+ u8 pll_idx, slot, index;
+ int rc;
+
+ for (pll_idx = 0; pll_idx < SIT9531X_NUM_PLLS; pll_idx++) {
+ rc = sit9531x_prio_table_read(sitdev, pll_idx, srcs);
+ if (rc)
+ return rc;
+
+ for (slot = 0; slot < SIT9531X_PRIO_MAX_SLOTS; slot++) {
+ index = sit9531x_hw_src_input(srcs[slot]);
+ if (index >= sitdev->info->num_inputs)
+ continue;
+
+ /*
+ * On a differentially configured pair only the P lane
+ * has a DPLL pin, so that is the lane the connect and
+ * disconnect callbacks account for. Fold an N-lane
+ * table entry onto its P lane, otherwise the count
+ * would land on a lane nothing ever releases. The
+ * signaling mode is already known here:
+ * sit9531x_input_mode_fetch() runs first.
+ */
+ if (sit9531x_input_is_n(index) &&
+ sitdev->ref[index].sig_mode == SIT9531X_MODE_DE)
+ index--;
- dev_info(sitdev->dev, "%s detected, %u inputs, %u outputs\n",
- sitdev->info->name, sitdev->info->num_inputs,
- sitdev->info->num_outputs);
+ sitdev->ref[index].pll_mask |= BIT(pll_idx);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * sit9531x_dev_state_fetch - read all hardware state at startup
+ *
+ * Called once during probe to populate the initial state cache.
+ */
+static int sit9531x_dev_state_fetch(struct sit9531x_dev *sitdev)
+{
+ int rc;
+ u8 i;
+
+ /* 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);
+ if (rc) {
+ dev_err(sitdev->dev,
+ "Failed to fetch CLKIN%u mode: %d\n", i, rc);
+ return rc;
+ }
+ }
+
+ for (i = 0; i < sitdev->info->num_inputs; i++) {
+ rc = sit9531x_ref_state_fetch(sitdev, i);
+ if (rc) {
+ dev_err(sitdev->dev,
+ "Failed to fetch input %u state: %d\n", i, 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.
+ * Nothing can race with it here -- the DPLLs are not registered and
+ * the monitor is not running yet -- but the page handling stays
+ * serialized the same way as at runtime.
+ */
+ mutex_lock(&sitdev->multiop_lock);
+ rc = sit9531x_ref_pll_mask_fetch(sitdev);
+ mutex_unlock(&sitdev->multiop_lock);
+ if (rc) {
+ dev_err(sitdev->dev,
+ "Failed to fetch input priority tables: %d\n", rc);
+ return rc;
+ }
+
+ for (i = 0; i < sitdev->info->num_outputs; i++) {
+ rc = sit9531x_out_state_fetch(sitdev, i);
+ if (rc) {
+ dev_err(sitdev->dev,
+ "Failed to fetch output %u state: %d\n", i, rc);
+ return rc;
+ }
+ }
+
+ 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;
+ }
+ }
+
+ return 0;
+}
+
+static void sit9531x_dev_ref_states_update(struct sit9531x_dev *sitdev)
+{
+ 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);
+ }
+}
+
+static void sit9531x_dev_chan_states_update(struct sit9531x_dev *sitdev)
+{
+ int i, rc;
+
+ for (i = 0; i < SIT9531X_NUM_PLLS; 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_dev_periodic_work - periodic hardware state polling
+ * @work: kthread_work pointer
+ *
+ * Polls hardware state at SIT9531X_STATUS_POLL_MS intervals.
+ * Updates reference and channel states, then delegates change
+ * detection to sit9531x_dpll_changes_check() for each registered DPLL.
+ */
+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;
+ int rc;
+
+ /*
+ * Update the cached ref[]/chan[] arrays under multiop_lock so the
+ * fetches are serialized against the DPLL callbacks that read
+ * these fields and against the chip's page selector.
+ *
+ * The lock is then dropped before sit9531x_dpll_changes_check(),
+ * which calls dpll_pin_change_ntf() / dpll_device_change_ntf().
+ * Those notification helpers take DPLL-subsystem locks that are
+ * already held when our callbacks are invoked from netlink
+ * context, and nesting multiop_lock around them would invert the
+ * lock order. changes_check() reads the cache published above,
+ * which is already consistent.
+ */
+ mutex_lock(&sitdev->multiop_lock);
+ sit9531x_dev_ref_states_update(sitdev);
+ sit9531x_dev_chan_states_update(sitdev);
+ mutex_unlock(&sitdev->multiop_lock);
+
+ list_for_each_entry(sitdpll, &sitdev->dplls, list)
+ sit9531x_dpll_changes_check(sitdpll);
+
+ /*
+ * Acknowledge the chip's notification latches after the tick has
+ * read and acted on them. Without this, the W1C bits remain set
+ * and -- on boards that wire INTRB -- the line stays asserted,
+ * re-firing the threaded handler back to back. The helper writes
+ * W1C bits across page 0 and page 6 and must run under
+ * multiop_lock to serialize the page selector against userspace
+ * dpll ops. Failure is non-fatal: status was already consumed
+ * for this tick and the next tick re-processes whatever stayed
+ * latched.
+ */
+ mutex_lock(&sitdev->multiop_lock);
+ rc = sit9531x_clear_notifications(sitdev);
+ mutex_unlock(&sitdev->multiop_lock);
+ if (rc)
+ dev_warn_ratelimited(sitdev->dev,
+ "Failed to clear notifications: %d\n",
+ rc);
+
+ /* Run twice a second */
+ kthread_queue_delayed_work(sitdev->kworker, &sitdev->work,
+ msecs_to_jiffies(SIT9531X_STATUS_POLL_MS));
+}
+
+/*
+ * sit9531x_irq_thread_fn - threaded IRQ handler for the chip's INTRB line
+ *
+ * Triggered when the chip asserts INTRB (and only when DT wires up the
+ * client interrupt; absent property == handler never installed). The
+ * action mirrors a periodic-work tick: queue an immediate run so status
+ * registers are read and DPLL changes_check fires without waiting for
+ * the next poll deadline. Polling continues to run as a fallback.
+ */
+static irqreturn_t sit9531x_irq_thread_fn(int irq, void *data)
+{
+ struct sit9531x_dev *sitdev = data;
+ int rc;
+
+ /*
+ * Acknowledge the chip's notification latches from the threaded
+ * handler itself. With IRQF_ONESHOT the line is unmasked on
+ * return, so deferring the W1C clear to the async kworker would
+ * let a still-asserted INTRB re-fire immediately (interrupt storm).
+ * Clear here, then kick the poll worker to read state and run
+ * changes_check.
+ */
+ mutex_lock(&sitdev->multiop_lock);
+ rc = sit9531x_clear_notifications(sitdev);
+ mutex_unlock(&sitdev->multiop_lock);
+ if (rc)
+ dev_warn_ratelimited(sitdev->dev,
+ "IRQ: failed to clear notifications: %d\n",
+ rc);
+
+ kthread_mod_delayed_work(sitdev->kworker, &sitdev->work, 0);
+ return IRQ_HANDLED;
+}
+
+/*
+ * sit9531x_dev_start - start normal operation
+ *
+ * Fetches initial hardware state, registers all DPLL devices and
+ * their pins, and starts the periodic monitoring thread.
+ */
+/*
+ * Report what the device loaded from its EEPROM, and warn if it does not
+ * look like a healthy load.
+ *
+ * A profile that failed to load leaves the part running something other
+ * than what the board was designed around -- dividers, output routing
+ * and priority tables all differ -- while every register still reads
+ * back a plausible value. Naming the profile and saying whether the
+ * load was clean turns that into something visible at startup instead of
+ * something inferred from measurements later.
+ *
+ * This only reports. Boards in this family may have their
+ * configuration pushed over I2C rather than held in an EEPROM, and there
+ * the CRC pair means nothing, so a mismatch is not grounds for refusing
+ * to drive the device.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+static void sit9531x_eeprom_state_report(struct sit9531x_dev *sitdev)
+{
+ u32 rec_crc = 0, cal_crc = 0, prof_id = 0;
+ u8 notif, v;
+ int rc, i;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ /* Profile id: three bytes, least significant first. */
+ for (i = 2; i >= 0; i--) {
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG_PROFILE_ID + i, &v);
+ if (rc)
+ return;
+ prof_id = prof_id << 8 | v;
+ }
+
+ dev_info(sitdev->dev, "profile id %u\n", prof_id);
+
+ /* Both CRCs: four bytes, most significant first. */
+ for (i = 0; i < 4; i++) {
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG_REC_CRC + i, &v);
+ if (rc)
+ return;
+ rec_crc = rec_crc << 8 | v;
+
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG_CAL_CRC + i, &v);
+ if (rc)
+ return;
+ cal_crc = cal_crc << 8 | v;
+ }
+
+ rc = sit9531x_read_u8(sitdev, SIT9531X_REG_EEPROM_NOTIF, ¬if);
+ if (rc)
+ return;
+
+ /*
+ * A clean load leaves the read-done bit set and every defect bit
+ * clear. A zero calculated CRC means nothing was read at all.
+ */
+ if (rec_crc != cal_crc || !cal_crc)
+ dev_warn(sitdev->dev,
+ "EEPROM CRC mismatch: stored %08x, computed %08x\n",
+ rec_crc, cal_crc);
+ else if (notif != SIT9531X_EEPROM_READ_DONE)
+ dev_warn(sitdev->dev,
+ "EEPROM read reported defects (notify %02x)\n",
+ notif);
+ else
+ dev_dbg(sitdev->dev, "EEPROM profile loaded, CRC %08x\n",
+ cal_crc);
+}
+
+/*
+ * Report which PLLs came up, and flag the one case that is a real
+ * inconsistency rather than a configuration choice.
+ *
+ * A PLL the loaded configuration leaves unused never reaches its active
+ * state, which is normal and not worth a warning. A PLL that has
+ * outputs routed to it and is still not active is different: something
+ * that is meant to be generating clocks is not running, and every value
+ * read from it -- lock state, phase, frequency offset -- describes a
+ * stopped loop. Say so once at startup rather than leaving it to be
+ * discovered through measurements that quietly read as zero.
+ *
+ * Caller must hold sitdev->multiop_lock.
+ */
+static void sit9531x_pll_states_report(struct sit9531x_dev *sitdev)
+{
+ unsigned int idx, i;
+ bool routed;
+
+ lockdep_assert_held(&sitdev->multiop_lock);
+
+ for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
+ if (sitdev->chan[i].active) {
+ dev_dbg(sitdev->dev, "PLL%c active\n", 'A' + i);
+ continue;
+ }
+
+ routed = false;
+ for (idx = 0; idx < sitdev->info->num_outputs; idx++) {
+ const struct sit9531x_out *out;
+
+ out = sit9531x_out_state_get(sitdev, idx);
+ if (out->routed && out->pll_idx == i) {
+ routed = true;
+ break;
+ }
+ }
+
+ if (routed)
+ dev_warn(sitdev->dev,
+ "PLL%c drives outputs but is not in its active state\n",
+ 'A' + i);
+ else
+ dev_dbg(sitdev->dev, "PLL%c unused by the loaded configuration\n",
+ 'A' + i);
+ }
+}
+
+int sit9531x_dev_start(struct sit9531x_dev *sitdev)
+{
+ struct sit9531x_dpll *sitdpll;
+ int rc;
+
+ /* Fetch device state */
+ rc = sit9531x_dev_state_fetch(sitdev);
+ if (rc)
+ return rc;
+
+ mutex_lock(&sitdev->multiop_lock);
+ sit9531x_eeprom_state_report(sitdev);
+ sit9531x_pll_states_report(sitdev);
+ mutex_unlock(&sitdev->multiop_lock);
+
+ list_for_each_entry(sitdpll, &sitdev->dplls, list) {
+ rc = sit9531x_dpll_register(sitdpll);
+ if (rc) {
+ dev_err_probe(sitdev->dev, rc,
+ "Failed to register DPLL%u\n",
+ sitdpll->id);
+ goto err_unregister;
+ }
+ }
+
+ kthread_queue_delayed_work(sitdev->kworker, &sitdev->work, 0);
+
+ return 0;
+
+err_unregister:
+ /*
+ * Unregister what did register. The caller frees the list on this
+ * path, so leaving a DPLL registered would hand the subsystem a
+ * pointer to freed memory.
+ */
+ list_for_each_entry_continue_reverse(sitdpll, &sitdev->dplls, list)
+ sit9531x_dpll_unregister(sitdpll);
+
+ return rc;
+}
+
+/*
+ * sit9531x_dev_stop - stop normal operation
+ *
+ * Cancels the monitoring thread and unregisters all DPLL devices
+ * and their pins.
+ */
+void sit9531x_dev_stop(struct sit9531x_dev *sitdev)
+{
+ struct sit9531x_dpll *sitdpll;
+
+ kthread_cancel_delayed_work_sync(&sitdev->work);
+
+ list_for_each_entry(sitdpll, &sitdev->dplls, list) {
+ if (sitdpll->dpll_dev)
+ sit9531x_dpll_unregister(sitdpll);
+ }
+}
+
+static struct sit9531x_dpll_pin *
+sit9531x_dpll_pin_alloc(struct sit9531x_dpll *sitdpll,
+ enum dpll_pin_direction dir, u8 id)
+{
+ struct sit9531x_dpll_pin *pin;
+
+ pin = kzalloc_obj(*pin, GFP_KERNEL);
+ if (!pin)
+ return ERR_PTR(-ENOMEM);
+
+ pin->dpll = sitdpll;
+ pin->dir = dir;
+ pin->id = id;
+
+ return pin;
+}
+
+/*
+ * sit9531x_dpll_pin_register - register a DPLL pin with the subsystem
+ * @index: absolute pin index for clock_id namespace
+ *
+ * Gets pin properties from firmware, creates or gets a dpll_pin,
+ * and registers it with the parent DPLL device.
+ */
+static int sit9531x_dpll_pin_register(struct sit9531x_dpll_pin *pin,
+ u32 index)
+{
+ struct sit9531x_dpll *sitdpll = pin->dpll;
+ struct sit9531x_pin_props *props;
+ const struct dpll_pin_ops *ops;
+ int rc;
+
+ props = sit9531x_pin_props_get(sitdpll->dev, pin->dir, pin->id);
+ if (IS_ERR(props))
+ return PTR_ERR(props);
+
+ strscpy(pin->label, props->package_label, sizeof(pin->label));
+ pin->fwnode = fwnode_handle_get(props->fwnode);
+ pin->esync_control = props->esync_control;
+
+ pin->dpll_pin = dpll_pin_get(sitdpll->dev->clock_id, index,
+ THIS_MODULE, &props->dpll_props,
+ &pin->tracker);
+ if (IS_ERR(pin->dpll_pin)) {
+ rc = PTR_ERR(pin->dpll_pin);
+ goto err_pin_get;
+ }
+ dpll_pin_fwnode_set(pin->dpll_pin, props->fwnode);
+
+ ops = sit9531x_dpll_pin_ops_get(pin);
+
+ rc = dpll_pin_register(sitdpll->dpll_dev, pin->dpll_pin, ops, pin);
+ if (rc)
+ goto err_register;
+
+ sit9531x_pin_props_put(props);
+
+ return 0;
+
+err_register:
+ dpll_pin_put(pin->dpll_pin, &pin->tracker);
+err_pin_get:
+ /* dpll_pin_get() left an ERR_PTR here. */
+ pin->dpll_pin = NULL;
+ fwnode_handle_put(pin->fwnode);
+ pin->fwnode = NULL;
+ sit9531x_pin_props_put(props);
+
+ return rc;
+}
+
+static void sit9531x_dpll_pin_unregister(struct sit9531x_dpll_pin *pin)
+{
+ struct sit9531x_dpll *sitdpll = pin->dpll;
+ const struct dpll_pin_ops *ops;
+
+ ops = sit9531x_dpll_pin_ops_get(pin);
+
+ dpll_pin_unregister(sitdpll->dpll_dev, pin->dpll_pin, ops, pin);
+ dpll_pin_put(pin->dpll_pin, &pin->tracker);
+ pin->dpll_pin = NULL;
+
+ fwnode_handle_put(pin->fwnode);
+ pin->fwnode = NULL;
+}
+
+static void sit9531x_dpll_pins_unregister(struct sit9531x_dpll *sitdpll)
+{
+ struct sit9531x_dpll_pin *pin, *next;
+
+ list_for_each_entry_safe(pin, next, &sitdpll->pins, list) {
+ sit9531x_dpll_pin_unregister(pin);
+ list_del(&pin->list);
+ kfree(pin);
+ }
+}
+
+/*
+ * 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
+ *
+ * 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
+ */
+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) {
+ /* The internal INTSYNC and XO pins are always registrable */
+ if (index == SIT9531X_INTSYNC_PIN_ID ||
+ index == SIT9531X_MAX_INPUTS)
+ return true;
+
+ return sit9531x_input_pin_is_registrable(sitdev, index);
+ }
+
+ /* The internal INTSYNC source pin is always registrable */
+ if (index == SIT9531X_INTSYNC_OUT_PIN_ID)
+ return true;
+
+ /* Output -- check if driven by this DPLL */
+ if (index >= sitdev->info->num_outputs)
+ return false;
+
+ /*
+ * Routing, not the current drive state: an output the initial
+ * configuration muted still gets a pin, so userspace can see it and
+ * un-mute it. Only outputs no PLL drives are left unregistered.
+ */
+ return sitdev->out[index].pll_idx == sitdpll->id &&
+ sitdev->out[index].routed;
+}
+
+/*
+ * sit9531x_dpll_pins_register - register all registrable pins
+ *
+ * Enumerates all possible input and output pins, checks registrability,
+ * and registers each one. Input pins come first, then output pins,
+ * with input pins first, then output pins.
+ */
+static int sit9531x_dpll_pins_register(struct sit9531x_dpll *sitdpll)
+{
+ struct sit9531x_dpll_pin *pin;
+ enum dpll_pin_direction dir;
+ u8 id, index;
+ int rc;
+
+ for (index = 0; index < SIT9531X_NUM_PINS_TOTAL; index++) {
+ if (index < SIT9531X_NUM_INPUT_PINS) {
+ id = index;
+ dir = DPLL_PIN_DIRECTION_INPUT;
+ } else {
+ id = index - SIT9531X_NUM_INPUT_PINS;
+ dir = DPLL_PIN_DIRECTION_OUTPUT;
+ }
+
+ if (!sit9531x_dpll_pin_is_registrable(sitdpll, dir, id))
+ continue;
+
+ pin = sit9531x_dpll_pin_alloc(sitdpll, dir, id);
+ if (IS_ERR(pin)) {
+ rc = PTR_ERR(pin);
+ goto error;
+ }
+
+ rc = sit9531x_dpll_pin_register(pin, index);
+ if (rc) {
+ kfree(pin);
+ goto error;
+ }
+
+ list_add(&pin->list, &sitdpll->pins);
+ }
+
+ return 0;
+
+error:
+ sit9531x_dpll_pins_unregister(sitdpll);
+ return rc;
+}
+
+static int sit9531x_dpll_device_register(struct sit9531x_dpll *sitdpll)
+{
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ int rc;
+
+ sitdpll->ops = sit9531x_dpll_device_ops;
+
+ sitdpll->dpll_dev = dpll_device_get(sitdev->clock_id, sitdpll->id,
+ THIS_MODULE, &sitdpll->tracker);
+ if (IS_ERR(sitdpll->dpll_dev)) {
+ rc = PTR_ERR(sitdpll->dpll_dev);
+ sitdpll->dpll_dev = NULL;
+ return rc;
+ }
+
+ rc = dpll_device_register(sitdpll->dpll_dev,
+ sit9531x_prop_dpll_type_get(sitdev,
+ sitdpll->id),
+ &sitdpll->ops, sitdpll);
+ if (rc) {
+ dpll_device_put(sitdpll->dpll_dev, &sitdpll->tracker);
+ sitdpll->dpll_dev = NULL;
+ }
+
+ return rc;
+}
+
+static void sit9531x_dpll_device_unregister(struct sit9531x_dpll *sitdpll)
+{
+ dpll_device_unregister(sitdpll->dpll_dev, &sitdpll->ops, sitdpll);
+ dpll_device_put(sitdpll->dpll_dev, &sitdpll->tracker);
+ sitdpll->dpll_dev = NULL;
+}
+
+/*
+ * sit9531x_dpll_alloc - allocate a DPLL device structure
+ * @sitdev: parent device
+ * @ch: PLL channel number (0-3)
+ *
+ * Return: pointer to allocated DPLL on success, error pointer on error
+ */
+struct sit9531x_dpll *sit9531x_dpll_alloc(struct sit9531x_dev *sitdev, u8 ch)
+{
+ struct sit9531x_dpll *sitdpll;
+
+ sitdpll = kzalloc_obj(*sitdpll, GFP_KERNEL);
+ if (!sitdpll)
+ return ERR_PTR(-ENOMEM);
+
+ sitdpll->dev = sitdev;
+ sitdpll->id = ch;
+ sitdpll->lock_status = DPLL_LOCK_STATUS_UNLOCKED;
+ INIT_LIST_HEAD(&sitdpll->pins);
+
+ return sitdpll;
+}
+
+/*
+ * sit9531x_dpll_free - deallocate a DPLL device structure
+ * @sitdpll: DPLL to free
+ */
+void sit9531x_dpll_free(struct sit9531x_dpll *sitdpll)
+{
+ kfree(sitdpll);
+}
+
+/*
+ * sit9531x_dpll_register - register DPLL device and all its pins
+ *
+ * Registers the DPLL device with the subsystem and then registers
+ * all input and output pins that are connected to this PLL.
+ */
+int sit9531x_dpll_register(struct sit9531x_dpll *sitdpll)
+{
+ int rc;
+
+ rc = sit9531x_dpll_device_register(sitdpll);
+ if (rc)
+ return rc;
+
+ rc = sit9531x_dpll_pins_register(sitdpll);
+ if (rc) {
+ sit9531x_dpll_device_unregister(sitdpll);
+ return rc;
+ }
+
+ return 0;
+}
+
+/* sit9531x_dpll_unregister - unregister DPLL device and its pins */
+void sit9531x_dpll_unregister(struct sit9531x_dpll *sitdpll)
+{
+ sit9531x_dpll_pins_unregister(sitdpll);
+ sit9531x_dpll_device_unregister(sitdpll);
+}
+
+static void sit9531x_dpll_list_free(struct sit9531x_dev *sitdev)
+{
+ struct sit9531x_dpll *sitdpll, *next;
+
+ list_for_each_entry_safe(sitdpll, next, &sitdev->dplls, list) {
+ list_del(&sitdpll->list);
+ sit9531x_dpll_free(sitdpll);
+ }
+}
+
+/* Runs only once the device is fully started, see the caller. */
+static void sit9531x_dev_dpll_fini(void *ptr)
+{
+ struct sit9531x_dev *sitdev = ptr;
+
+ sit9531x_dev_stop(sitdev);
+ kthread_destroy_worker(sitdev->kworker);
+ sit9531x_dpll_list_free(sitdev);
+}
+
+static int sit9531x_devm_dpll_init(struct sit9531x_dev *sitdev)
+{
+ struct kthread_worker *kworker;
+ struct sit9531x_dpll *sitdpll;
+ unsigned int i;
+ int rc;
+
+ INIT_LIST_HEAD(&sitdev->dplls);
+ kthread_init_delayed_work(&sitdev->work, sit9531x_dev_periodic_work);
+
+ for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
+ sitdpll = sit9531x_dpll_alloc(sitdev, i);
+ if (IS_ERR(sitdpll)) {
+ rc = dev_err_probe(sitdev->dev, PTR_ERR(sitdpll),
+ "Failed to alloc DPLL%u\n", i);
+ goto err_free_dplls;
+ }
+
+ list_add_tail(&sitdpll->list, &sitdev->dplls);
+ }
+
+ kworker = kthread_run_worker(0, "sit9531x-%s", dev_name(sitdev->dev));
+ if (IS_ERR(kworker)) {
+ rc = PTR_ERR(kworker);
+ goto err_free_dplls;
+ }
+ sitdev->kworker = kworker;
+
+ rc = sit9531x_dev_start(sitdev);
+ if (rc) {
+ rc = dev_err_probe(sitdev->dev, rc, "Failed to start device\n");
+ goto err_destroy_worker;
+ }
+
+ /*
+ * Only now is every field the cleanup touches valid, so this is the
+ * first point at which the action may be registered. On failure it
+ * runs the action itself, which is correct here and only here.
+ */
+ return devm_add_action_or_reset(sitdev->dev, sit9531x_dev_dpll_fini,
+ sitdev);
+
+err_destroy_worker:
+ kthread_destroy_worker(sitdev->kworker);
+err_free_dplls:
+ sit9531x_dpll_list_free(sitdev);
+
+ return rc;
+}
+
+/*
+ * sit9531x_read_variant_id - read chip variant ID byte from hardware
+ * @id: output variant ID byte
+ *
+ * Reads the single-byte variant identification register from Page 0
+ * reg 0x02 (95317 = 0x17, 95316 = 0x31). Reg 0x03 holds a separate
+ * revision byte and is intentionally not consumed here.
+ */
+static int sit9531x_read_variant_id(struct sit9531x_dev *sitdev, u8 *id)
+{
+ return sit9531x_read_u8(sitdev, SIT9531X_REG_VARIANT_ID, id);
+}
+
+static const struct sit9531x_chip_info *sit9531x_match_variant(u8 id)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(sit9531x_chip_ids); i++) {
+ if (sit9531x_chip_ids[i].id == id)
+ return &sit9531x_chip_ids[i];
+ }
+
+ return NULL;
+}
+
+/*
+ * sit9531x_derive_clock_id - build EUI-64 clock identifier
+ *
+ * Generates a deterministic 64-bit identifier from the SiTime OUI,
+ * the chip ID, and the I2C address. This provides a stable clock_id
+ * across reboots.
+ *
+ * Return: 64-bit clock identifier
+ */
+static u64 sit9531x_derive_clock_id(struct sit9531x_dev *sitdev)
+{
+ u64 clkid;
+
+ clkid = SIT9531X_OUI << 24;
+ clkid |= (u64)sitdev->info->id << 8;
+ clkid |= (u64)sitdev->client->addr;
+
+ return clkid;
+}
+
+int sit9531x_dev_probe(struct sit9531x_dev *sitdev)
+{
+ struct clk *xtal_clk;
+ u8 variant_id;
+ int rc;
+
+ /*
+ * Fvco = Fref * (DIVN + frac/2^32) with Fref = xtal_freq << doubler,
+ * so every freq_set and phase_adjust path divides by a rate derived
+ * from the XO feeding XIN/XO_CLK.
+ */
+ xtal_clk = devm_clk_get_enabled(sitdev->dev, "xtal");
+ if (IS_ERR(xtal_clk))
+ return dev_err_probe(sitdev->dev, PTR_ERR(xtal_clk),
+ "Failed to get xtal clock\n");
+ sitdev->xtal_freq = clk_get_rate(xtal_clk);
+ if (!sitdev->xtal_freq)
+ return dev_err_probe(sitdev->dev, -EINVAL,
+ "xtal clock has no rate\n");
+
+ /*
+ * Held deasserted, never pulsed: the chip configuration comes from
+ * efuse or an NVM blob applied before probe, and a reset would
+ * discard it. Must precede the first I2C access, as a board that
+ * powers up asserted keeps the chip unreachable until released.
+ */
+ sitdev->reset_gpio = devm_gpiod_get_optional(sitdev->dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(sitdev->reset_gpio))
+ return dev_err_probe(sitdev->dev, PTR_ERR(sitdev->reset_gpio),
+ "Failed to request reset gpio\n");
+ if (sitdev->reset_gpio)
+ fsleep(10000); /* internal boot after release */
+
+ rc = sit9531x_read_variant_id(sitdev, &variant_id);
+ if (rc)
+ return rc;
+
+ sitdev->info = sit9531x_match_variant(variant_id);
+ if (!sitdev->info)
+ return dev_err_probe(sitdev->dev, -ENODEV,
+ "Unknown variant ID: 0x%02x\n", variant_id);
+
+ sitdev->clock_id = sit9531x_derive_clock_id(sitdev);
+ sitdev->intsync_src = -1;
+
+ rc = devm_mutex_init(sitdev->dev, &sitdev->multiop_lock);
+ if (rc)
+ return dev_err_probe(sitdev->dev, rc,
+ "Failed to initialize mutex\n");
+
+ /*
+ * Before the IRQ: the handler reaches sitdev->kworker through
+ * kthread_mod_delayed_work(), so the worker has to exist before an
+ * INTRB assertion can land.
+ */
+ rc = sit9531x_devm_dpll_init(sitdev);
+ if (rc)
+ return rc;
+
+ /* Absent "interrupts" leaves client->irq 0 and the poll in charge. */
+ sitdev->irq = sitdev->client ? sitdev->client->irq : 0;
+ if (sitdev->irq > 0) {
+ rc = devm_request_threaded_irq(sitdev->dev, sitdev->irq,
+ NULL, sit9531x_irq_thread_fn,
+ IRQF_ONESHOT,
+ dev_name(sitdev->dev), sitdev);
+ if (rc)
+ return dev_err_probe(sitdev->dev, rc,
+ "Failed to request IRQ %d\n",
+ sitdev->irq);
+ }
return 0;
}
diff --git a/drivers/dpll/sit9531x/core.h b/drivers/dpll/sit9531x/core.h
index 230b21b9e238..d4159c7ddaf5 100644
--- a/drivers/dpll/sit9531x/core.h
+++ b/drivers/dpll/sit9531x/core.h
@@ -15,6 +15,8 @@
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/types.h>
@@ -25,6 +27,8 @@
#define SIT9531X_MAX_INPUTS 8
#define SIT9531X_NUM_INPUT_PAIRS (SIT9531X_MAX_INPUTS / 2)
#define SIT9531X_MAX_OUTPUTS 12
+/* out_pll_map[] entry meaning "this output is not routed to any PLL" */
+#define SIT9531X_OUT_PLL_UNMAPPED 0xFF
/*
* INTSYNC (the inter-PLL sync net) is modeled as two pins. The
* destination PLL that locks to INTSYNC sees an input pin
@@ -34,6 +38,16 @@
*/
#define SIT9531X_INTSYNC_PIN_ID (SIT9531X_MAX_INPUTS + 1)
#define SIT9531X_INTSYNC_OUT_PIN_ID SIT9531X_MAX_OUTPUTS
+#define SIT9531X_NUM_PINS (SIT9531X_MAX_INPUTS + 2 + SIT9531X_MAX_OUTPUTS + 1)
+#define SIT9531X_STATUS_POLL_MS 500
+
+/* selected_ref value when the active source is not a registered input */
+#define SIT9531X_REF_INVALID 0xFF
+
+/* SiTime IEEE OUI for EUI-64 generation */
+#define SIT9531X_OUI 0x0090C2FFFEULL
+
+struct sit9531x_dpll;
/*
* struct sit9531x_chip_info - chip variant identification
@@ -64,37 +78,94 @@ enum sit9531x_signal_mode {
/*
* struct sit9531x_ref - input reference state
* @freq: configured frequency in Hz
+ * @enabled: reference is enabled for monitoring
+ * @los: loss-of-signal detected
+ * @oof: out-of-frequency detected
+ * @pll_mask: bitmask of PLLs this input feeds (bit 0 = PLLA)
* @label: board label from DT or default
* @sig_mode: signal mode of the pair this lane belongs to
* (detected from CLKINx_INPUT_MODE at probe)
*/
struct sit9531x_ref {
- u32 freq;
- const char *label;
+ u32 freq;
+ bool enabled;
+ bool los;
+ bool oof;
+ u8 pll_mask;
+ const char *label;
enum sit9531x_signal_mode sig_mode;
};
/*
* struct sit9531x_out - output state
- * @freq: configured frequency in Hz
+ * @enabled: output is driving, i.e. not forced into Hi-Z
+ * @routed: output is mapped to @pll_idx by the initial
+ * configuration; an unrouted output has no DPLL pin
+ * @pll_idx: PLL driving this output (0-3)
* @label: board label from DT or default
*/
struct sit9531x_out {
u32 freq;
+ bool enabled;
+ bool routed;
+ u8 pll_idx;
const char *label;
};
+/*
+ * struct sit9531x_chan - per-PLL channel state
+ * @active: PLL has reached its active state; a PLL the loaded
+ * configuration leaves unused never does, and its
+ * loss-of-lock bit stays clear because nothing drives it
+ * @locked: PLL is locked (raw status register bit)
+ * @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
+ * @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
+ * 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
+ * hardware rather than the driver's intent
+ */
+struct sit9531x_chan {
+ bool active;
+ bool locked;
+ u8 mode;
+ u8 selected_ref;
+ bool inner_lol;
+ bool ho_freeze;
+ bool ho_valid;
+ u16 prio_mask;
+};
+
/*
* struct sit9531x_dev - SiT9531x device instance
- * @dev: parent device
- * @client: I2C client
- * @regmap: paged register map
* @info: detected chip variant info
- * @multiop_lock: serializes multi-register sequences
+ * @multiop_lock: mutex for multi-register atomic operations
* @ref: array of input reference states
* @out: array of output states
+ * @chan: array of per-PLL channel states
* @xtal_freq: crystal oscillator frequency in Hz
+ * @kworker: kthread worker for periodic polling
+ * @work: delayed work for periodic state checks
+ * @clock_id: IEEE 1588 EUI-64 clock identifier
* @reset_gpio: optional reset line (DT "reset-gpios"), NULL if absent
+ * @irq: optional INTRB IRQ number (from DT "interrupts" via the
+ * I2C client), 0 if no IRQ is wired
+ * @pll_fvco: optional per-PLL VCO in Hz from DT
+ * "sitime,pll-fvco"; 0 means derive from DIVN
+ * @out_pll_map: optional per-output source PLL (0-3, 0xff =
+ * unmapped) from DT "sitime,output-pll-map"
+ * @out_pll_map_valid: true when out_pll_map[] was populated from DT;
+ * false means use the chip's OUT_MAP registers
+ * @intsync_src: PLL index currently sourcing inter-PLL
+ * synchronization (INTSYNC), or -1 when disabled
*/
struct sit9531x_dev {
struct device *dev;
@@ -107,11 +178,107 @@ struct sit9531x_dev {
/* Hardware state */
struct sit9531x_ref ref[SIT9531X_MAX_INPUTS + 1]; /* +1 for xtal */
struct sit9531x_out out[SIT9531X_MAX_OUTPUTS];
+ struct sit9531x_chan chan[SIT9531X_NUM_PLLS];
u32 xtal_freq;
+ /* DPLL channels */
+ struct list_head dplls;
+
+ /* Monitor */
+ struct kthread_worker *kworker;
+ struct kthread_delayed_work work;
+
+ /* Device identity */
+ u64 clock_id;
+
+ /* Optional DT-described GPIO / IRQ lines */
struct gpio_desc *reset_gpio;
+ int irq;
+
+ /* Optional DT board-config overrides */
+ u64 pll_fvco[SIT9531X_NUM_PLLS];
+ u8 out_pll_map[SIT9531X_MAX_OUTPUTS];
+ bool out_pll_map_valid;
+
+ /* Inter-PLL synchronization state */
+ s8 intsync_src;
+
};
+extern const struct regmap_config sit9531x_regmap_config;
+
+/* ---- Core lifecycle ---- */
+int sit9531x_dev_probe(struct sit9531x_dev *sitdev);
+int sit9531x_dev_start(struct sit9531x_dev *sitdev);
+void sit9531x_dev_stop(struct sit9531x_dev *sitdev);
+
+/* ---- Register access ---- */
+int sit9531x_read_u8(struct sit9531x_dev *sitdev, unsigned int reg,
+ u8 *val);
+int sit9531x_write_u8(struct sit9531x_dev *sitdev, unsigned int reg,
+ u8 val);
+int sit9531x_read_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 offset, u8 *val);
+int sit9531x_write_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx,
+ u8 offset, u8 val);
+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 ---- */
+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,
+ 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) ---- */
+int sit9531x_output_disable(struct sit9531x_dev *sitdev, u8 index);
+int sit9531x_output_enable(struct sit9531x_dev *sitdev, u8 index);
+
+/* ---- Output frequency ---- */
+int sit9531x_output_freq_set(struct sit9531x_dev *sitdev, u8 out_idx,
+ u8 pll_idx, u64 frequency);
+int sit9531x_output_freq_get(struct sit9531x_dev *sitdev, u8 out_idx,
+ u64 *frequency);
+
+/* ---- Output phase adjust (PRG_RST_DELAY register-based) ---- */
+int sit9531x_output_phase_adjust_set(struct sit9531x_dev *sitdev,
+ u8 out_idx, s32 phase_ps);
+
+/* ---- Notification clear ---- */
+int sit9531x_clear_notifications(struct sit9531x_dev *sitdev);
+
+/* ---- INTSYNC (inter-PLL synchronization) ---- */
+int sit9531x_intsync_enable(struct sit9531x_dev *sitdev, u8 src_pll_idx);
+int sit9531x_intsync_disable(struct sit9531x_dev *sitdev, u8 src_pll_idx);
+
+/* ---- Output pulse control ---- */
+int sit9531x_output_pulse_ctrl_set(struct sit9531x_dev *sitdev,
+ u8 out_idx, u8 pulse_ctrl);
+
+/* ---- Phase offset (TDC readback) ---- */
+int sit9531x_pll_ffo_ppt(struct sit9531x_dev *sitdev, u8 pll_idx, s64 *ffo);
+int sit9531x_phase_offset_read(struct sit9531x_dev *sitdev, u8 pll_idx,
+ s64 *phase_ps);
+
+/* ---- State helpers ---- */
+
+/*
+ * sit9531x_pll_page - get register page for PLL index
+ * @pll_idx: PLL index (0 = PLLA, 3 = PLLD)
+ */
+static inline u8 sit9531x_pll_page(u8 pll_idx)
+{
+ return SIT9531X_PAGE_PLLA + pll_idx;
+}
+
/*
* Logical input pins are interleaved: even index = P lane, odd
* index = N lane of pair index/2 (IN0P, IN0N, IN1P, IN1N, ...).
@@ -137,27 +304,78 @@ static inline bool sit9531x_input_is_n(u8 index)
}
/*
- * sit9531x_pll_page - get register page for PLL index
- * @pll_idx: PLL index (0 = PLLA, 3 = PLLD)
+ * sit9531x_input_hw_src - translate logical input index to source encoding
+ * @index: logical input pin index
+ *
+ * The priority table and CLK_ACTIVESEL registers use a non-contiguous
+ * source encoding: 0-3 = CLK0P..CLK3P, 5 = OCXO, 6 = INTSYNC,
+ * 7-10 = CLK0N..CLK3N.
*/
-static inline u8 sit9531x_pll_page(u8 pll_idx)
+static inline u8 sit9531x_input_hw_src(u8 index)
{
- return SIT9531X_PAGE_PLLA + pll_idx;
+ if (index == SIT9531X_MAX_INPUTS)
+ return SIT9531X_PRIO_SRC_OCXO;
+ if (index == SIT9531X_INTSYNC_PIN_ID)
+ return SIT9531X_PRIO_SRC_INTSYNC;
+ if (sit9531x_input_is_n(index))
+ return SIT9531X_PRIO_SRC_N_BASE + sit9531x_input_pair(index);
+ return sit9531x_input_pair(index);
}
-extern const struct regmap_config sit9531x_regmap_config;
+/*
+ * sit9531x_hw_src_input - translate source encoding to logical input index
+ * @src: 4-bit hardware source encoding
+ *
+ * Return: logical input index (INTSYNC maps to SIT9531X_INTSYNC_PIN_ID),
+ * or SIT9531X_REF_INVALID if @src is a reserved value
+ */
+static inline u8 sit9531x_hw_src_input(u8 src)
+{
+ if (src < SIT9531X_NUM_INPUT_PAIRS)
+ return src * 2;
+ if (src == SIT9531X_PRIO_SRC_OCXO)
+ return SIT9531X_MAX_INPUTS;
+ if (src == SIT9531X_PRIO_SRC_INTSYNC)
+ return SIT9531X_INTSYNC_PIN_ID;
+ if (src >= SIT9531X_PRIO_SRC_N_BASE &&
+ src < SIT9531X_PRIO_SRC_N_BASE + SIT9531X_NUM_INPUT_PAIRS)
+ return (src - SIT9531X_PRIO_SRC_N_BASE) * 2 + 1;
+ return SIT9531X_REF_INVALID;
+}
-/* ---- Core lifecycle ---- */
-int sit9531x_dev_probe(struct sit9531x_dev *sitdev);
+/*
+ * sit9531x_ref_state_get - get reference state by index
+ * @index: logical input index
+ *
+ * Return: pointer to the cached input reference state
+ */
+static inline const struct sit9531x_ref *
+sit9531x_ref_state_get(const struct sit9531x_dev *sitdev, u8 index)
+{
+ return &sitdev->ref[index];
+}
-/* ---- Register access ---- */
-int sit9531x_read_u8(struct sit9531x_dev *sitdev, unsigned int reg, u8 *val);
-int sit9531x_write_u8(struct sit9531x_dev *sitdev, unsigned int reg, u8 val);
-int sit9531x_read_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx, u8 offset,
- u8 *val);
-int sit9531x_write_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx, u8 offset,
- u8 val);
-int sit9531x_update_pll_u8(struct sit9531x_dev *sitdev, u8 pll_idx, u8 offset,
- u8 mask, u8 val);
+/*
+ * sit9531x_out_state_get - get output state by index
+ * @index: logical output index
+ *
+ * Return: pointer to the cached output state
+ */
+static inline const struct sit9531x_out *
+sit9531x_out_state_get(const struct sit9531x_dev *sitdev, u8 index)
+{
+ return &sitdev->out[index];
+}
+
+/*
+ * sit9531x_chan_state_get - get channel state by PLL index
+ *
+ * Return: pointer to the cached per-PLL channel state
+ */
+static inline const struct sit9531x_chan *
+sit9531x_chan_state_get(const struct sit9531x_dev *sitdev, u8 pll_idx)
+{
+ return &sitdev->chan[pll_idx];
+}
#endif /* _SIT9531X_CORE_H */
diff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c
new file mode 100644
index 000000000000..821370ddeab0
--- /dev/null
+++ b/drivers/dpll/sit9531x/dpll.c
@@ -0,0 +1,417 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SiTime SiT9531x DPLL subsystem callbacks and registration
+ *
+ * Copyright (C) 2026 SiTime Corp.
+ * Author: Ali Rouhi <arouhi@xxxxxxxxxx>
+ * Author: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>
+ *
+ * DPLL device ops, pin ops (separate input/output), pin registration,
+ * and periodic change detection.
+ */
+
+#include <linux/dpll.h>
+#include <linux/err.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/netlink.h>
+#include <linux/slab.h>
+
+#include "core.h"
+#include "dpll.h"
+#include "prop.h"
+#include "regs.h"
+
+#define SIT9531X_ESYNC_FREQ_10MHZ 10000000ULL
+#define SIT9531X_ESYNC_PULSE_DEFAULT 50
+
+static inline bool sit9531x_dpll_is_input_pin(const struct sit9531x_dpll_pin *pin)
+{
+ return pin->dir == DPLL_PIN_DIRECTION_INPUT;
+}
+
+static inline bool
+sit9531x_dpll_is_xo_pin(const struct sit9531x_dpll_pin *pin)
+{
+ return sit9531x_dpll_is_input_pin(pin) &&
+ pin->id == SIT9531X_MAX_INPUTS;
+}
+
+/*
+ * 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).
+ */
+static int
+sit9531x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
+ enum dpll_lock_status *status,
+ enum dpll_lock_status_error *status_error,
+ struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ const struct sit9531x_chan *chan;
+
+ if (status_error)
+ *status_error = DPLL_LOCK_STATUS_ERROR_NONE;
+
+ chan = sit9531x_chan_state_get(sitdev, sitdpll->id);
+
+ mutex_lock(&sitdev->multiop_lock);
+
+ if (!chan->active) {
+ /*
+ * A PLL the loaded configuration leaves unused never reaches
+ * its active state. Nothing drives its loss-of-lock bit, so
+ * without this it would report a lock it does not have.
+ */
+ *status = DPLL_LOCK_STATUS_UNLOCKED;
+ } else if (chan->mode) {
+ /*
+ * Free-run: the outer loop is disabled, so the PLL tracks no
+ * reference at all and its loss-of-lock bit means nothing.
+ * That is what UNLOCKED describes -- "not yet locked to any
+ * valid input (or was forced by user)".
+ */
+ *status = DPLL_LOCK_STATUS_UNLOCKED;
+ } else if (chan->locked) {
+ /*
+ * HO_ACQ is locked *and* holdover memory acquired, so it needs
+ * the holdover-valid bit rather than following from the lock.
+ */
+ if (chan->ho_valid)
+ *status = DPLL_LOCK_STATUS_LOCKED_HO_ACQ;
+ else
+ *status = DPLL_LOCK_STATUS_LOCKED;
+ } else if (chan->ho_freeze) {
+ *status = DPLL_LOCK_STATUS_HOLDOVER;
+ } else {
+ *status = DPLL_LOCK_STATUS_UNLOCKED;
+ }
+
+ /* Report inner LOL as an error condition */
+ if (status_error && chan->inner_lol)
+ *status_error = DPLL_LOCK_STATUS_ERROR_UNDEFINED;
+
+ mutex_unlock(&sitdev->multiop_lock);
+
+ return 0;
+}
+
+/*
+ * Mode
+ * ====
+ * enum dpll_mode differentiates how a DPLL selects an input: AUTOMATIC
+ * has the device pick the highest-priority one, MANUAL has userspace
+ * request one. The device only implements the former through this
+ * driver, so AUTOMATIC is the only mode advertised.
+ *
+ * Free-run -- the outer loop disabled through PLL page reg 0x31[5] -- is
+ * not a mode in those terms, because no input is selected either way. It
+ * is reported through lock status instead, and reached through the
+ * chip-specific tool rather than over netlink.
+ *
+ * The device could implement real MANUAL: MISCINNER_PLL (PLL page reg
+ * 0x18) bit 5 switches a PLL from priority-based to manual active select,
+ * and GPIO_INPUT_FUNC_CTRL5..8 (page 0, regs 0xE8-0xEB) bit 4 makes the
+ * choice come from the register's own low nibble instead of the GPIO
+ * pins, which pins one reference while the loop keeps running. Wiring
+ * that up would let .state_on_dpll_set() accept CONNECTED; it needs bench
+ * validation first, and reg 0x18 carries GUI-generated configuration in
+ * its other bits, so it is left out until then. Advertising MANUAL and
+ * then refusing the one request MANUAL exists for is the worse of the two
+ * incomplete answers, and after merge it would be ABI.
+ */
+static int
+sit9531x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
+ enum dpll_mode *mode, struct netlink_ext_ack *extack)
+{
+ *mode = DPLL_MODE_AUTOMATIC;
+
+ return 0;
+}
+
+/*
+ * sit9531x_dpll_mode_set - put the PLL in automatic selection mode
+ *
+ * Clears the outer loop disable bit (PLL page reg 0x31[5]) and triggers a
+ * small update via reg 0x0F, so a PLL left free-running by the loaded
+ * configuration or by the chip-specific tool returns to selecting its
+ * reference from the priority table. Any other mode is refused.
+ */
+static int
+sit9531x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
+ enum dpll_mode mode, struct netlink_ext_ack *extack)
+{
+ struct sit9531x_dpll *sitdpll = dpll_priv;
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ int rc;
+
+ if (mode != DPLL_MODE_AUTOMATIC) {
+ NL_SET_ERR_MSG(extack,
+ "Device selects its reference by priority; only automatic mode is supported");
+ return -EOPNOTSUPP;
+ }
+
+ mutex_lock(&sitdev->multiop_lock);
+
+ rc = sit9531x_update_pll_u8(sitdev, sitdpll->id,
+ SIT9531X_PLL_REG_STATUS,
+ SIT9531X_PLL_STATUS_OUTER_DIS, 0);
+ if (rc) {
+ NL_SET_ERR_MSG(extack, "Failed to write PLL mode register");
+ goto unlock;
+ }
+
+ /* Trigger small update to apply without full NVM cycle */
+ rc = sit9531x_write_pll_u8(sitdev, sitdpll->id,
+ SIT9531X_PLL_REG_SMALL_UPDATE,
+ SIT9531X_SMALL_UPDATE_CMD);
+ if (rc) {
+ NL_SET_ERR_MSG(extack, "Failed to trigger small update");
+ goto unlock;
+ }
+
+ /*
+ * Keep the cached mode in step with the register. The periodic
+ * monitor refreshes it too, but the pin state getters and lock status
+ * read this cache and would otherwise keep reporting free-run until
+ * the next poll.
+ */
+ sitdev->chan[sitdpll->id].mode = 0;
+
+unlock:
+ mutex_unlock(&sitdev->multiop_lock);
+
+ return rc;
+}
+
+static int
+sit9531x_dpll_supported_modes_get(const struct dpll_device *dpll,
+ void *dpll_priv, unsigned long *modes,
+ struct netlink_ext_ack *extack)
+{
+ __set_bit(DPLL_MODE_AUTOMATIC, modes);
+
+ return 0;
+}
+
+const struct dpll_device_ops sit9531x_dpll_device_ops = {
+ .lock_status_get = sit9531x_dpll_lock_status_get,
+ .mode_get = sit9531x_dpll_mode_get,
+ .mode_set = sit9531x_dpll_mode_set,
+ .supported_modes_get = sit9531x_dpll_supported_modes_get,
+ /* temp_get not available -- SiT9531x has no on-die temp sensor */
+};
+
+/*
+ * Pin-state contract
+ * ==================
+ * The five pin ops tables below fall into three roles, and only the first
+ * has a selection state machine. Each state_on_dpll callback implements
+ * the rules for its role and nothing else, so the tables cannot drift
+ * apart the way five independent encodings of this did.
+ *
+ * SELECTION role -- physical input pins, INTSYNC destination pin.
+ * Where does this reference sit in this DPLL's selection process?
+ * Predicates, all evaluated under multiop_lock:
+ * M source is present in THIS PLL's hardware priority table
+ * A chan->mode == 0 (outer loop running)
+ * L chan->locked && !chan->inner_lol (only meaningful when A)
+ * S chan->selected_ref == this pin's id
+ * get:
+ * CONNECTED A && L && S && M
+ * SELECTABLE M && !(A && L && S)
+ * DISCONNECTED !M
+ * set:
+ * DISCONNECTED remove from this PLL's table; a physical input also
+ * releases this DPLL's claim and powers the shared
+ * receiver down on the last release
+ * SELECTABLE add to this PLL's table; a physical input powers the
+ * receiver up and takes the claim, in that order
+ * CONNECTED -EOPNOTSUPP -- the device selects by priority and has
+ * no mode that pins one reference (see mode_set())
+ * other -EINVAL
+ *
+ * A is what separates CONNECTED from SELECTABLE: chan->locked is derived
+ * from the outer loss-of-lock bit, which can read clear while the outer
+ * loop is disabled, so a free-running PLL would otherwise claim its
+ * reference as the active input of a loop that is ignoring it.
+ *
+ * M is read from the hardware priority table, not from ref->pll_mask.
+ * pll_mask is only the shared-receiver refcount and says nothing about
+ * one DPLL's eligibility; it also drifts, because .prio_set writes the
+ * table without touching it. Signal quality is reported through the
+ * pin's own attributes rather than by demoting the state, so a source
+ * that is momentarily in LOS stays selectable.
+ *
+ * DRIVE role -- output pins, INTSYNC source pin.
+ * Is this pin or net being driven? Nothing is selected here, so:
+ * CONNECTED pin or net is driven
+ * DISCONNECTED pin is muted (Hi-Z), or this PLL does not drive it
+ * SELECTABLE -EINVAL on set, never reported by get
+ *
+ * FIXED role -- XO pin. Always CONNECTED; it cannot be routed.
+ */
+
+static int
+sit9531x_dpll_input_pin_direction_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_direction *direction,
+ struct netlink_ext_ack *extack)
+{
+ *direction = DPLL_PIN_DIRECTION_INPUT;
+ return 0;
+}
+
+static const struct dpll_pin_ops sit9531x_dpll_input_pin_ops = {
+ .direction_get = sit9531x_dpll_input_pin_direction_get,
+ /*
+ * The measurement compares the PLL's running feedback divider with
+ * its configured one, so it describes the device's own reference
+ * rather than a port rate.
+ */
+ .supported_ffo = BIT(DPLL_FFO_PIN_DEVICE),
+};
+
+/*
+ * INTSYNC pin ops
+ *
+ * INTSYNC is the chip's inter-PLL sync net: one PLL drives it and other
+ * PLLs may lock to it instead of to an external reference. The two
+ * roles are exposed as two separate pins so neither overloads the other:
+ *
+ * - a source (output) pin registered on every DPLL. Connecting it on a
+ * DPLL makes that DPLL drive INTSYNC; only one DPLL may drive it at a
+ * time. It has no priority ops -- driving the net is not a reference
+ * selection.
+ * - a destination (input) pin registered on every DPLL. Connecting it
+ * on a DPLL makes that DPLL eligible to lock to INTSYNC as a
+ * reference, so it carries the priority ops.
+ */
+
+/* ---- INTSYNC source (output) pin ---- */
+
+/* The INTSYNC source pin is an output; its direction_get is defined below. */
+static int
+sit9531x_dpll_output_pin_direction_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_direction *direction,
+ struct netlink_ext_ack *extack);
+
+/* ---- INTSYNC destination (input) pin ---- */
+
+/*
+ * XO (crystal oscillator) pin ops
+ *
+ * The XO is the chip's internal reference oscillator that feeds every
+ * PLL. It is exposed so userspace can see the on-chip reference, but it
+ * cannot be routed or disconnected, so it is reported permanently
+ * connected and offers no state_on_dpll_set / prio ops.
+ */
+
+static int
+sit9531x_dpll_xo_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)
+{
+ *state = DPLL_PIN_STATE_CONNECTED;
+ return 0;
+}
+
+static const struct dpll_pin_ops sit9531x_dpll_xo_pin_ops = {
+ .direction_get = sit9531x_dpll_input_pin_direction_get,
+ .state_on_dpll_get = sit9531x_dpll_xo_pin_state_on_dpll_get,
+};
+
+static int
+sit9531x_dpll_output_pin_direction_get(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_direction *direction,
+ struct netlink_ext_ack *extack)
+{
+ *direction = DPLL_PIN_DIRECTION_OUTPUT;
+ return 0;
+}
+
+static const struct dpll_pin_ops sit9531x_dpll_output_pin_ops = {
+ .direction_get = sit9531x_dpll_output_pin_direction_get,
+};
+
+const struct dpll_pin_ops *
+sit9531x_dpll_pin_ops_get(const struct sit9531x_dpll_pin *pin)
+{
+ 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;
+ return &sit9531x_dpll_input_pin_ops;
+}
+
+/*
+ * sit9531x_dpll_changes_check - check for state changes and notify
+ *
+ * Called from sit9531x_dev_periodic_work(). Compares current hardware
+ * state against cached values and sends netlink notifications on changes.
+ */
+void sit9531x_dpll_changes_check(struct sit9531x_dpll *sitdpll)
+{
+ struct sit9531x_dev *sitdev = sitdpll->dev;
+ enum dpll_lock_status lock_status;
+ struct sit9531x_dpll_pin *pin;
+ int rc;
+
+ rc = sit9531x_dpll_lock_status_get(sitdpll->dpll_dev, sitdpll,
+ &lock_status, NULL, NULL);
+ if (rc) {
+ dev_err(sitdev->dev, "Failed to get DPLL%u lock status: %d\n",
+ sitdpll->id, rc);
+ return;
+ }
+
+ /* If lock status changed, notify DPLL core */
+ if (sitdpll->lock_status != lock_status) {
+ sitdpll->lock_status = lock_status;
+ dpll_device_change_ntf(sitdpll->dpll_dev);
+ }
+
+ list_for_each_entry(pin, &sitdpll->pins, list) {
+ const struct dpll_pin_ops *ops;
+ enum dpll_pin_state state;
+
+ /*
+ * Poll input pins whose state can change autonomously: regular
+ * references and the INTSYNC destination pin. Outputs (incl.
+ * the INTSYNC source) change only through their own set
+ * callback and the XO is permanently connected, so skip those.
+ * Each pin's own state_on_dpll_get resolves to the right getter.
+ */
+ if (!sit9531x_dpll_is_input_pin(pin) ||
+ sit9531x_dpll_is_xo_pin(pin))
+ continue;
+
+ ops = sit9531x_dpll_pin_ops_get(pin);
+ rc = ops->state_on_dpll_get(pin->dpll_pin, pin,
+ sitdpll->dpll_dev, sitdpll,
+ &state, NULL);
+ if (rc)
+ continue;
+
+ if (state != pin->pin_state) {
+ dev_dbg(sitdev->dev, "%s state changed: %u->%u\n",
+ pin->label, pin->pin_state, state);
+ pin->pin_state = state;
+ dpll_pin_change_ntf(pin->dpll_pin);
+ }
+ }
+}
diff --git a/drivers/dpll/sit9531x/dpll.h b/drivers/dpll/sit9531x/dpll.h
new file mode 100644
index 000000000000..e5eef4514bcd
--- /dev/null
+++ b/drivers/dpll/sit9531x/dpll.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SiTime SiT9531x DPLL subsystem interface
+ *
+ * Copyright (C) 2026 SiTime Corp.
+ * Author: Ali Rouhi <arouhi@xxxxxxxxxx>
+ * Author: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@xxxxxxxxxxxxxxxxx>
+ *
+ * DPLL device and pin structures, and function declarations for
+ * the DPLL registration and callback layer.
+ */
+
+#ifndef _SIT9531X_DPLL_H
+#define _SIT9531X_DPLL_H
+
+#include <linux/dpll.h>
+#include <linux/list.h>
+#include <linux/types.h>
+
+struct sit9531x_dev;
+
+/* Per-pin DPLL state. */
+struct sit9531x_dpll_pin {
+ struct list_head list;
+ struct sit9531x_dpll *dpll;
+ struct dpll_pin *dpll_pin;
+ dpll_tracker tracker;
+ struct fwnode_handle *fwnode;
+ char label[8]; /* "IN0", "OUT3" */
+ enum dpll_pin_direction dir;
+ u8 id; /* hardware index */
+ u8 prio;
+ enum dpll_pin_state pin_state;
+ s32 phase_adjust; /* picoseconds */
+ s64 phase_offset; /* picoseconds */
+ bool esync_control;
+ u64 esync_freq; /* 0 == disabled */
+};
+
+/* Per-PLL DPLL device state. */
+struct sit9531x_dpll {
+ struct list_head list;
+ struct sit9531x_dev *dev;
+ struct dpll_device *dpll_dev;
+ dpll_tracker tracker;
+ struct dpll_device_ops ops; /* per-instance copy */
+ struct list_head pins;
+ u8 id; /* 0 = PLLA .. 3 = PLLD */
+ enum dpll_lock_status lock_status;
+};
+
+/* ---- DPLL allocation and registration ---- */
+/*
+ * The callback tables stay with the callbacks; the registration code that
+ * hands them to the subsystem lives next to probe() in core.c.
+ */
+extern const struct dpll_device_ops sit9531x_dpll_device_ops;
+const struct dpll_pin_ops *
+sit9531x_dpll_pin_ops_get(const struct sit9531x_dpll_pin *pin);
+
+struct sit9531x_dpll *sit9531x_dpll_alloc(struct sit9531x_dev *sitdev, u8 ch);
+void sit9531x_dpll_free(struct sit9531x_dpll *sitdpll);
+int sit9531x_dpll_register(struct sit9531x_dpll *sitdpll);
+void sit9531x_dpll_unregister(struct sit9531x_dpll *sitdpll);
+
+/* ---- Periodic change detection ---- */
+void sit9531x_dpll_changes_check(struct sit9531x_dpll *sitdpll);
+
+#endif /* _SIT9531X_DPLL_H */
diff --git a/drivers/dpll/sit9531x/regs.h b/drivers/dpll/sit9531x/regs.h
index 8337f1235472..705176c35ddd 100644
--- a/drivers/dpll/sit9531x/regs.h
+++ b/drivers/dpll/sit9531x/regs.h
@@ -42,10 +42,6 @@
#define SIT9531X_PAGE_PLLD 0x0D
#define SIT9531X_PAGE_PLLD_EXT 0x1D
-/* PLL index to page mapping */
-#define SIT9531X_PLL_PAGE(_idx) \
- (SIT9531X_PAGE_PLLA + (_idx))
-
/*
* VARIANT_ID is a single byte at page 0 reg 0x02 (95317 = 0x17, 95316 = 0x31).
* Reg 0x03 carries an unrelated revision byte and must not be combined into
@@ -53,7 +49,224 @@
*/
#define SIT9531X_REG_VARIANT_ID SIT9531X_REG(0x00, 0x02)
-/* Variant ID values (page 0 reg 0x02) */
+/* DCO trigger pulse timing: minimum 6 ns required by hardware */
+
+#define SIT9531X_REG_HOLDOVER_HISTORY SIT9531X_REG(0x00, 0x58)
+
+/* Page 0 -- PLL inner loop loss-of-lock */
+#define SIT9531X_REG_PLL_INNER_LOL_STATUS SIT9531X_REG(0x00, 0x92)
+#define SIT9531X_REG_PLL_INNER_LOL_NOTIF SIT9531X_REG(0x00, 0x93)
+
+/* Page 0 -- Clock monitor PLL / XO status */
+#define SIT9531X_REG_CMON_NOTIF SIT9531X_REG(0x00, 0x9E)
+
+/* Page 0 -- PLL outer-loop loss-of-lock */
+#define SIT9531X_REG_OUTER_LOL_STATUS SIT9531X_REG(0x00, 0x06)
+#define SIT9531X_REG_OUTER_LOL_NOTIF SIT9531X_REG(0x00, 0x07)
+
+/* Page 0 -- PLL holdover freeze status */
+#define SIT9531X_REG_HO_FREEZE_STATUS SIT9531X_REG(0x00, 0x0A)
+#define SIT9531X_REG_HO_FREEZE_NOTIF SIT9531X_REG(0x00, 0x0B)
+
+/* Page 0 -- INTSYNC (inter-PLL synchronization) global enable */
+#define SIT9531X_REG_INTSYNC_GLOBAL SIT9531X_REG(0x00, 0x40)
+#define SIT9531X_INTSYNC_EN_BIT 6
+
+/*
+ * Priority table: 6 registers per PLL, each holds two priority slots
+ * nibble-packed. The register holding slots 2n and 2n+1 keeps the
+ * earlier slot (CLK_SPARE<2n>SEL_PLL) in [7:4] and the later one in
+ * [3:0].
+ *
+ * Base registers for PLLA: 0x16-0x1B (slots 0-10 plus the
+ * active-reference nibble).
+ * For PLL N: base + 6 * N (e.g. PLLB starts at 0x1C).
+ *
+ * Input source encoding (4-bit value):
+ * 0=IN0P, 1=IN1P, 2=IN2P, 3=IN3P, 4=IN4P,
+ * 5=OCXO, 6=INTSYNC,
+ * 7=IN0N, 8=IN1N, 9=IN2N, 10=IN3N, 11=IN4N
+ */
+#define SIT9531X_PAGE_PRIOSYS 0x01
+#define SIT9531X_PRIO_BASE_REG 0x16
+#define SIT9531X_PRIO_REGS_PER_PLL 6
+#define SIT9531X_PRIO_SLOTS_PER_REG 2
+/*
+ * 11 priority slots, CLK_SPARE0SEL_PLL through CLK_SPARE10SEL_PLL.
+ * The twelfth nibble of the block is not a slot: it is
+ * CLK_ACTIVESEL_PLL, see SIT9531X_PRIO_ACTIVESEL_OFF below.
+ */
+#define SIT9531X_PRIO_MAX_SLOTS 11
+/* Number of source encodings (0-11), unrelated to the slot count */
+#define SIT9531X_PRIO_NUM_SRC 12
+#define SIT9531X_PRIO_NIBBLE_MASK 0x0F
+#define SIT9531X_PRIO_HI_SHIFT 4
+/* Input source encoding values (see table above) */
+#define SIT9531X_PRIO_SRC_OCXO 5
+#define SIT9531X_PRIO_SRC_INTSYNC 6
+#define SIT9531X_PRIO_SRC_N_BASE 7
+/*
+ * The last register of each PLL's priority block holds, in its low
+ * nibble, the input source the PLL has currently selected as its
+ * active reference (CLK_ACTIVESEL_PLL, same 4-bit encoding as above).
+ */
+#define SIT9531X_PRIO_ACTIVESEL_OFF 5
+
+/*
+ * Page 0 -- PRG_Directives_GENERIC_0, the main system's programming
+ * directive register. Every page carries its own copy of this
+ * register at offset 0x0F with the same bit layout:
+ *
+ * bit 6 proceed to loop lock / active state from the PRG_CMD state
+ * bit 4 update the NVM bank from the efuse contents
+ * bit 3 read the efuse into the volatile registers
+ * bit 2 program the efuse
+ * bit 1 small change update (SIT9531X_SMALL_UPDATE_CMD)
+ * bit 0 escape to the PRG_CMD state
+ *
+ * The NVM bank is a volatile shadow, so bits 4 and 1 are both fine in
+ * a runtime path: bit 1 for a change made in the active state, bit 4
+ * to close a PRG_CMD sequence. Only bit 2 writes non-volatile
+ * storage, and the driver never issues it.
+ */
+#define SIT9531X_REG_GLOBAL_UPDATE SIT9531X_REG(0x00, 0x0F)
+#define SIT9531X_SMALL_UPDATE_CMD 0x02
+
+/* 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)
+#define SIT9531X_REG_IN_SEP_FORCE SIT9531X_REG(0x02, 0xEA)
+#define SIT9531X_REG_IN_SEP_STATE SIT9531X_REG(0x02, 0xEB)
+#define SIT9531X_REG_IN_SEN_FORCE SIT9531X_REG(0x02, 0xF2)
+#define SIT9531X_REG_IN_SEN_STATE SIT9531X_REG(0x02, 0xF3)
+
+/*
+ * One register per input pair at 0x1B + 0x10 * pair
+ * (CLKIN0 = 0x1B, CLKIN1 = 0x2B, CLKIN2 = 0x3B, CLKIN3 = 0x4B).
+ * SE_P_EN/SE_N_EN set means the corresponding lane is configured
+ * single-ended; both clear means the pair runs differential.
+ */
+#define SIT9531X_REG_IN_MODE(_pair) \
+ SIT9531X_REG(0x02, 0x1B + 0x10 * (_pair))
+#define SIT9531X_IN_MODE_SE_P_EN BIT(0)
+#define SIT9531X_IN_MODE_SE_N_EN BIT(1)
+
+/* ---- Page 0x03 (Output System) registers -- Hi-Z control ---- */
+#define SIT9531X_REG_HIZ_DIFF_07_MASK SIT9531X_REG(0x03, 0xF2)
+#define SIT9531X_REG_HIZ_DIFF_07_STATE SIT9531X_REG(0x03, 0xF3)
+#define SIT9531X_REG_HIZ_DIFF_811_MASK SIT9531X_REG(0x03, 0xF4)
+#define SIT9531X_REG_HIZ_DIFF_811_STATE SIT9531X_REG(0x03, 0xF5)
+#define SIT9531X_REG_HIZ_SE_07_MASK SIT9531X_REG(0x03, 0xF8)
+#define SIT9531X_REG_HIZ_SE_07_STATE SIT9531X_REG(0x03, 0xF9)
+#define SIT9531X_REG_HIZ_SE_811_MASK SIT9531X_REG(0x03, 0xFA)
+#define SIT9531X_REG_HIZ_SE_811_STATE SIT9531X_REG(0x03, 0xFB)
+
+/*
+ * Output divider registers in Pages 3/4. Each output has a 34-bit
+ * integer divider mapped to 5 bytes (LSB at base reg, MSB at base-4).
+ * Outputs 0-5 are on Page 3, outputs 6-11 are on Page 4.
+ *
+ * The base register for slot N within a page is:
+ * clkout_odr_divn_base[slot] = { 0x14, 0x24, 0x34, 0x44, 0x54, 0x64 }
+ *
+ * Layout: base=LSB, base-1, base-2, base-3, base-4[1:0]=MSB.
+ *
+ * Per-chip clkout_map[] translates output index to slot position.
+ */
+#define SIT9531X_PAGE_OUTSYS0_SLOT_MAX 5 /* slots 0-5 on Page 0x03 */
+
+/* Misc output system registers */
+#define SIT9531X_REG_PRG_DIR_GEN SIT9531X_REG(0x03, 0x0F)
+#define SIT9531X_PRG_CMD_STATE 0x01
+#define SIT9531X_UPDATE_NVM 0x10
+#define SIT9531X_LOOP_LOCK 0x40
+
+/* Debug register (same offset, per-page) */
+#define SIT9531X_REG_OUTSYS_DEBUG SIT9531X_REG(0x03, 0xBD)
+#define SIT9531X_DEBUG_UNLOCK_VAL 0xC3
+
+/*
+ * On-demand phase-flush fired from a register rather than a GPIO pin.
+ * DIVO_PHASE_SEL_REG selects the in-register trigger source and
+ * DIVO_PHASE_TRIG flushes the output phase when pulsed high then low.
+ * The unrelated OEb trigger pair in bits [7:6] must be preserved.
+ */
+#define SIT9531X_REG_GPIO_FUNC_CTRL1 SIT9531X_REG(0x00, 0x65)
+#define SIT9531X_DIVO_PHASE_SEL_REG BIT(5)
+#define SIT9531X_DIVO_PHASE_TRIG BIT(4)
+
+/* ---- PLL page registers (apply to pages 0x0A-0x0D) ---- */
+#define SIT9531X_PLL_REG_SMALL_UPDATE 0x0F
+
+/*
+ * Loop-filter coefficients on PLL_PAGE regs 0x10-0x15 (3 normal +
+ * 3 fast-lock) are GUI/NVM-generated by the timing configurator and must not be
+ * reprogrammed at runtime; the register map flags them as
+ * "GUI generated configuration should not change manually".
+ */
+
+#define SIT9531X_PLL_REG_OUT_MAP_HI 0x27
+#define SIT9531X_PLL_REG_OUT_MAP_LO 0x28
+#define SIT9531X_PLL_REG_STATUS 0x31
+#define SIT9531X_PLL_REG_NVM_UPDATE 0x3F
+
+#define SIT9531X_PLL_REG_ACTIVE 0x02
+#define SIT9531X_PLL_ACTIVE_BIT BIT(0) /* PLL reached active state */
+#define SIT9531X_PLL_REG_ZDB0 0x2B
+#define SIT9531X_PLL_REG_ZDB1 0x1E
+#define SIT9531X_PLL_ZDB_EN_BIT BIT(4) /* zero-delay buffer enabled */
+
+/* PLL STATUS register bits */
+#define SIT9531X_PLL_STATUS_LOCK BIT(0)
+#define SIT9531X_PLL_STATUS_OUTER_DIS BIT(5)
+
+/*
+ * Per-PLL status register. HO_VALID says the holdover window holds a
+ * valid frequency estimate, i.e. holdover memory has been acquired; it is
+ * not the same as HO_FREEZE (page 0, reg 0x0A), which says the PLL has
+ * already switched over to holdover.
+ */
+#define SIT9531X_PLL_REG_STATUS_1 0x06
+#define SIT9531X_PLL_STATUS_1_HO_VALID BIT(2)
+
+/* P-polarity status registers */
+#define SIT9531X_CLKMON_P_STATUS_01 SIT9531X_REG(0x06, 0x02) /* inputs 0,1 */
+#define SIT9531X_CLKMON_P_NOTIF_01 SIT9531X_REG(0x06, 0x03)
+#define SIT9531X_CLKMON_P_STATUS_23 SIT9531X_REG(0x06, 0x06) /* inputs 2,3 */
+#define SIT9531X_CLKMON_P_NOTIF_23 SIT9531X_REG(0x06, 0x07)
+
+/* N-polarity status registers */
+#define SIT9531X_CLKMON_N_STATUS_01 SIT9531X_REG(0x06, 0x92) /* inputs 0,1 */
+#define SIT9531X_CLKMON_N_NOTIF_01 SIT9531X_REG(0x06, 0x93)
+#define SIT9531X_CLKMON_N_STATUS_23 SIT9531X_REG(0x06, 0x96) /* inputs 2,3 */
+#define SIT9531X_CLKMON_N_NOTIF_23 SIT9531X_REG(0x06, 0x97)
+
+/* Per-input bit offsets within clock monitor nibble */
+#define SIT9531X_CLKMON_FREQ_FINE 0 /* bit 0 / bit 4 */
+#define SIT9531X_CLKMON_FREQ_COARSE 1 /* bit 1 / bit 5 */
+#define SIT9531X_CLKMON_CLK_LOSS 2 /* bit 2 / bit 6 */
+#define SIT9531X_CLKMON_CLK_LOSS_FD 3 /* bit 3 / bit 7 */
+
+/* ---- Debug / NVM unlock registers ---- */
+#define SIT9531X_REG_DBG_UNLOCK1 0x24
+#define SIT9531X_REG_DBG_UNLOCK2 0x25
+
+/*
+ * EEPROM profile load, page 0. The device compares the CRC stored in the
+ * EEPROM against the one it computes from what it read; a mismatch means
+ * the profile on the part is not the profile the board expects.
+ * NOTIFY_4 collects the read-done bit and the defect bits, all sticky, so
+ * a healthy load leaves exactly the read-done bit set.
+ */
+#define SIT9531X_REG_REC_CRC SIT9531X_REG(0x00, 0x8A) /* 4 bytes, MSB first */
+#define SIT9531X_REG_CAL_CRC SIT9531X_REG(0x00, 0x8E) /* 4 bytes, MSB first */
+#define SIT9531X_REG_EEPROM_NOTIF SIT9531X_REG(0x00, 0x97)
+#define SIT9531X_EEPROM_READ_DONE BIT(0)
+
+/* Profile identifier the loaded configuration carries, page 1, 24 bits */
+#define SIT9531X_REG_PROFILE_ID SIT9531X_REG(0x01, 0x44) /* 3 bytes, LSB first */
+
+/* ---- Variant ID values (single byte read from SIT9531X_REG_VARIANT_ID) ---- */
#define SIT9531X_VARIANT_ID_95317 0x17
#define SIT9531X_VARIANT_ID_95316 0x31
--
2.43.0