From: Oleg Zadorozhnyi Report the phase difference between a PLL's reference and the PLL itself, which is the loop's own residual error and therefore trends small on a locked device -- that is the measurement, not an artefact of it. The value comes from the on-chip time-to-digital converter, read through the debug window: unlock the window, point it at the converter, then read the trigger register, which latches a fresh sample and returns the previous one. It is read three times per sample for that reason; a single read hands back the sample from the last call, so a repeated measurement would look perfectly steady while saying nothing. The offset keeps the fraction of a picosecond the converter resolves. Only the input the device feeds a PLL has a phase offset against it. For any other pin there is nothing to measure and zero is reported, because the core abandons an entire pin dump on an error from any one pin. That pin reads zero too when no sample was taken: the PLL is not tracking, drives no un-muted output, has no usable VCO rate, or the device moved it off the pin, or the lane lost its signal, between the check and the sample. Signed-off-by: Oleg Zadorozhnyi Assisted-by: LLM Signed-off-by: Ali Rouhi --- drivers/dpll/sit9531x/core.c | 220 +++++++++++++++++++++++++++++++++-- drivers/dpll/sit9531x/core.h | 5 + drivers/dpll/sit9531x/dpll.c | 147 +++++++++++++++++++++++ drivers/dpll/sit9531x/regs.h | 22 ++++ 4 files changed, 385 insertions(+), 9 deletions(-) diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c index 8a8872b18d2c..fbac70787ada 100644 --- a/drivers/dpll/sit9531x/core.c +++ b/drivers/dpll/sit9531x/core.c @@ -922,8 +922,9 @@ static int sit9531x_prio_prg_commit(struct sit9531x_dev *sitdev) return 0; } -/* +/** * sit9531x_input_mon_fetch - read the clock monitor status of every lane + * @sitdev: device pointer * * Four registers carry a nibble per lane. Loss of signal and frequency * drift are what separate an input the device could lock to from one it @@ -932,7 +933,7 @@ static int sit9531x_prio_prg_commit(struct sit9531x_dev *sitdev) * * Caller must hold sitdev->multiop_lock, or run before registration. */ -static int sit9531x_input_mon_fetch(struct sit9531x_dev *sitdev) +int sit9531x_input_mon_fetch(struct sit9531x_dev *sitdev) { static const unsigned int regs[] = { SIT9531X_CLKMON_P_STATUS_01, SIT9531X_CLKMON_P_STATUS_23, @@ -3026,6 +3027,202 @@ int sit9531x_clear_notifications(struct sit9531x_dev *sitdev) return 0; } +/** + * sit9531x_phase_offset_read - read phase difference via TDC + * @sitdev: device pointer + * @pll_idx: PLL index (0-3) + * @phase_offset: phase difference in units of 1/DPLL_PHASE_OFFSET_DIVIDER + * of a picosecond, as the DPLL subsystem carries it + * + * Reads the Time-to-Digital Converter (TDC) code from the PLL page + * registers -- a 35-bit magnitude with a separate sign bit -- then + * converts it using the VCO frequency: phase_diff = tdc_code / fvco. + * + * Caller must hold sitdev->multiop_lock. + * + * Return: 0 on success, -ENODATA when the PLL has no known VCO rate + * (nothing is sampled then), or the register access error + */ +int sit9531x_phase_offset_read(struct sit9531x_dev *sitdev, u8 pll_idx, + s64 *phase_offset) +{ + u8 v, old_write_code, old_read_code; + bool have_old = false; + int rc, ret, i; + s64 tdc_signed; + u64 fvco, mag; + u64 tdc_raw; + bool sign; + + lockdep_assert_held(&sitdev->multiop_lock); + + if (pll_idx >= SIT9531X_NUM_PLLS) + return -EINVAL; + + /* + * Get the VCO rate first. -ENODATA means the PLL has no rate to + * convert against (a divider this board leaves unprogrammed), and + * there is then no reason to open the debug window at all -- which + * also keeps a bus error while it is open from ever being reported + * as that benign case. + */ + rc = sit9531x_get_fvco(sitdev, pll_idx, &fvco); + if (rc) { + if (rc == -ENODATA) + dev_dbg(sitdev->dev, + "PLL%c: Fvco unknown, skip TDC\n", + 'A' + pll_idx); + return rc; + } + + /* Unlock the debug page so the TDC registers are accessible. */ + rc = sit9531x_write_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DEBUG, + SIT9531X_PLL_DEBUG_UNLOCK); + if (rc) + goto relock; + + /* + * Remember the tap selection so it can be put back. The key + * register is re-locked below, but the mux is not part of the key: + * leaving it parked on the TDC with a slow sampling clock selected + * is a state change the caller did not ask for, and the next reader + * of a different tap would have to know to undo it. + */ + rc = sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_WRITE_CODE, + &old_write_code); + if (!rc) + rc = sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_READ_CODE, + &old_read_code); + if (rc) + goto relock; + + have_old = true; + + /* + * Select the debug clock for taps below 200 kHz, then point the + * readback at the TDC. Only the one bit is touched: writing the + * modifier register whole would clear the fields belonging to + * other taps. + */ + rc = sit9531x_update_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_WRITE_CODE, + SIT9531X_DBG_LOW_FREQ_CLK_BIT, + SIT9531X_DBG_LOW_FREQ_CLK_BIT); + if (rc) + goto relock; + rc = sit9531x_write_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_READ_CODE, + SIT9531X_DBG_READ_CODE_TDC); + if (rc) + goto relock; + + /* + * Latch a sample by reading the debug status data register. A + * single read returns the previous latch, so read it three times + * as the documented phase-difference procedure does. + */ + for (i = 0; i < SIT9531X_DBG_STATUS_READS; i++) { + rc = sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_STATUS_DATA, + &v); + if (rc) + goto relock; + } + + tdc_raw = 0; + + rc = sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_DATA_4, &v); + if (rc) + goto relock; + sign = !!(v & BIT(SIT9531X_TDC_SIGN_BIT)); + tdc_raw = (u64)(v & SIT9531X_TDC_MAG_HI_MASK) << 32; + + rc = sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_DATA_3, &v); + if (rc) + goto relock; + tdc_raw |= (u64)v << 24; + + rc = sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_DATA_2, &v); + if (rc) + goto relock; + tdc_raw |= (u64)v << 16; + + rc = sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_DATA_1, &v); + if (rc) + goto relock; + tdc_raw |= (u64)v << 8; + + rc = sit9531x_read_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_DATA_0, &v); + if (rc) + goto relock; + tdc_raw |= v; + + /* + * Apply sign. Per the register map the sign bit is active-high + * for a positive offset: bit set -> +code, bit clear -> -code. + */ + tdc_signed = sign ? (s64)tdc_raw : -(s64)tdc_raw; + + /* + * phase_diff (seconds) = tdc_code / fvco + * phase_diff (ps) = tdc_code * 1e12 / fvco + * + * Scaled by the subsystem's divider before the division rather than + * after it: one TDC code is 1e12 / fvco ps, a fraction of a + * picosecond more often than not (203.45 ps at 4.9152 GHz), and + * truncating to whole picoseconds first would leave the three + * fractional digits the attribute carries always zero. The + * quotient fits u64 for any rate sit9531x_get_fvco() returns: it + * refuses one below the VCO band. + * + * mul_u64_u64_div_u64() keeps the exact Hz denominator; dividing + * by whole MHz instead would lose up to ~40 ppm of scale on a + * fractional-DIVN Fvco. + */ + mag = mul_u64_u64_div_u64(tdc_signed < 0 ? -tdc_signed : tdc_signed, + 1000000000000ULL * DPLL_PHASE_OFFSET_DIVIDER, + fvco); + *phase_offset = tdc_signed < 0 ? -(s64)mag : (s64)mag; + + rc = 0; + +relock: + if (have_old) { + ret = sit9531x_write_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_READ_CODE, + old_read_code); + if (ret && !rc) + rc = ret; + ret = sit9531x_write_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DBG_WRITE_CODE, + old_write_code); + if (ret && !rc) + rc = ret; + } + + /* + * Close the debug window again. The key register opens every debug + * register on this PLL while it holds the unlock value, and this read + * runs on every pin-get of a connected input, so leaving it open + * would mean normal monitoring permanently unlocks the block. + */ + ret = sit9531x_write_pll_u8(sitdev, pll_idx, + SIT9531X_PLL_REG_DEBUG, + SIT9531X_PLL_DEBUG_LOCK); + if (ret && !rc) + rc = ret; + + return rc; +} + /* * sit9531x_ref_state_fetch - read input reference status from hardware * @index: logical input index @@ -3124,8 +3321,13 @@ static int sit9531x_pll_is_active(struct sit9531x_dev *sitdev, u8 pll_idx, return 0; } -/* - * Read the reference the device feeds a PLL. +/** + * sit9531x_chan_routed_ref_read - read the reference the device feeds a PLL + * @sitdev: device pointer + * @pll_idx: PLL index (0-3) + * @ref: result: logical input index, SIT9531X_INTSYNC_PIN_ID for the + * inter-PLL sync net, or SIT9531X_REF_INVALID when the code + * names no registered input * * The active selection names the source the PLL was told to follow. The * input subsystem feeds the PLL that source while it has signal and falls @@ -3140,12 +3342,12 @@ static int sit9531x_pll_is_active(struct sit9531x_dev *sitdev, u8 pll_idx, * free-run the PLL is fed nothing and the bus is not consulted, so the * caller checks the mode first. * - * @ref is the logical input index, SIT9531X_INTSYNC_PIN_ID for the - * inter-PLL sync net, or SIT9531X_REF_INVALID when the code names no - * registered input. Caller must hold sitdev->multiop_lock. + * Caller must hold sitdev->multiop_lock. + * + * Return: 0 on success, <0 on error */ -static int sit9531x_chan_routed_ref_read(struct sit9531x_dev *sitdev, - u8 pll_idx, u8 *ref) +int sit9531x_chan_routed_ref_read(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 *ref) { int rc, lock_rc; u8 v, i; diff --git a/drivers/dpll/sit9531x/core.h b/drivers/dpll/sit9531x/core.h index 19efff5112a3..1ed20354f94d 100644 --- a/drivers/dpll/sit9531x/core.h +++ b/drivers/dpll/sit9531x/core.h @@ -310,6 +310,11 @@ int sit9531x_clear_notifications(struct sit9531x_dev *sitdev); /* ---- INTSYNC (inter-PLL synchronization) ---- */ /* ---- Phase offset (TDC readback) ---- */ +int sit9531x_chan_routed_ref_read(struct sit9531x_dev *sitdev, u8 pll_idx, + u8 *ref); +int sit9531x_input_mon_fetch(struct sit9531x_dev *sitdev); +int sit9531x_phase_offset_read(struct sit9531x_dev *sitdev, u8 pll_idx, + s64 *phase_offset); /* ---- State helpers ---- */ diff --git a/drivers/dpll/sit9531x/dpll.c b/drivers/dpll/sit9531x/dpll.c index 436e4b76a727..aab5a4f0f946 100644 --- a/drivers/dpll/sit9531x/dpll.c +++ b/drivers/dpll/sit9531x/dpll.c @@ -669,6 +669,151 @@ sit9531x_dpll_input_pin_prio_set(const struct dpll_pin *pin, void *pin_priv, return 0; } +/* + * sit9531x_dpll_input_pin_phase_offset_get - phase offset of a reference + * + * What this reports, and what it deliberately does not: + * + * The ABI defines the attribute as the phase difference between the signal + * on a pin and its parent DPLL device, so this is the loop's own residual + * error, sampled with the loop closed. On a locked DPLL it therefore + * trends small -- that is the measurement, not an artefact of it. The + * documentation describes the reported value as one that may be averaged + * over prior measurements, which suits a closed-loop residual and not a + * one-shot open-loop capture; the core publishes whatever this callback + * returns, so the averaging, if any, would be this driver's to do. + * + * The chip can also measure the reference against the local oscillator + * with the outer loop's correction frozen, which is a different quantity + * and the one the documented phase-difference procedure produces. That + * needs the digital loop filter held (and, on the 1PPS PLL, the automatic + * phase- and frequency-lock helpers held off), which leaves the PLL + * undisciplined until it is released. A netlink read must not do that, + * so that measurement is not offered here at all; it belongs to a caller + * that can own the freeze and restore it. + * + * Precondition, which this callback cannot create: the TDC compares + * against a signal the PLL drives, so a PLL driving no output with its + * zero-delay buffer off has nothing to measure. SiTime clock + * engineering confirms this is a property of the hardware, not of + * SiTime's TDC measurement procedure, which satisfies it by mapping a + * spare output and restarting the PLL -- side effects that do not belong + * in a getter, so a reading taken in that state is simply not meaningful. + * + * Non-selected pins and a PLL with no programmed divider report zero + * rather than an error: the DPLL core propagates any error from this + * callback and fails the whole pin dump with it. The core has no per-pin + * "no data" for phase offset, as it has -ENODATA for the fractional + * frequency offset, so it is a value or no callback at all. + */ +static int +sit9531x_dpll_input_pin_phase_offset_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, s64 *phase_offset, + struct netlink_ext_ack *extack) +{ + struct sit9531x_dpll_pin *dpin = pin_priv; + struct sit9531x_dpll *sitdpll = dpll_priv; + struct sit9531x_dev *sitdev = sitdpll->dev; + bool drives = false; + s64 offset; + u8 routed, i; + int rc; + + mutex_lock(&sitdev->multiop_lock); + + /* + * The on-chip TDC is a per-PLL resource that always measures the + * phase difference between the VCO and the PLL's currently + * selected reference; it cannot be pointed at an arbitrary input, + * so an input that is not the active reference reports 0 rather + * than the active reference's value. + * + * The sample needs both: the PLL tracking this pin as the poll last + * saw it (locked, outer loop running, not frozen), and the device + * still feeding it this pin, read now -- the device moves on its + * own, and a cache up to a poll period old could attribute a live + * measurement to the pin the PLL used to be fed. The device is read + * again after the sample for the same reason: it can switch during + * the dozen transfers the sample takes. + * + * The TDC also compares against a signal the PLL drives, so a PLL + * with no routed, driving output has nothing to measure and reports + * 0 like any other pin without a reading. Output state is never + * polled, so a mute or unmute whose read-back failed is read + * through here first, as the output's own getter does, rather than + * taken from a cache that was left describing the state before it. + */ + for (i = 0; i < sitdev->info->num_outputs; i++) { + if (!sitdev->out[i].routed || + sitdev->out[i].pll_idx != sitdpll->id) + continue; + if (sitdev->out[i].state_stale) { + rc = sit9531x_output_state_refresh(sitdev, i); + if (rc) { + mutex_unlock(&sitdev->multiop_lock); + NL_SET_ERR_MSG(extack, + "Output mute state could not be read back"); + return rc; + } + } + if (sitdev->out[i].enabled) + drives = true; + } + + /* + * The lane's signal is read live as well: a loss since the last poll + * makes the device fall back on its own, and a sample taken then + * would be credited to a pin the PLL is no longer on. + */ + rc = sit9531x_input_mon_fetch(sitdev); + if (rc) { + mutex_unlock(&sitdev->multiop_lock); + NL_SET_ERR_MSG(extack, + "Failed to read the input clock monitors"); + return rc; + } + + if (!drives || + !sit9531x_dpll_selection_active(sitdev, sitdpll, dpin->id)) { + mutex_unlock(&sitdev->multiop_lock); + *phase_offset = 0; + return 0; + } + + rc = sit9531x_chan_routed_ref_read(sitdev, sitdpll->id, &routed); + if (!rc && routed == dpin->id) { + rc = sit9531x_phase_offset_read(sitdev, sitdpll->id, &offset); + if (!rc) + rc = sit9531x_chan_routed_ref_read(sitdev, sitdpll->id, + &routed); + if (!rc && routed != dpin->id) + rc = -ENODATA; + } else if (!rc) { + rc = -ENODATA; + } + mutex_unlock(&sitdev->multiop_lock); + + /* + * -ENODATA means no reading: the PLL has no known VCO rate, or the + * device moved the PLL off this pin around the sample. Report 0 so a + * full pin-get dump does not fail over it. Every other errno, + * -ENODEV from a vanished adapter included, is a failure. + */ + if (rc == -ENODATA) { + *phase_offset = 0; + return 0; + } + if (rc) { + NL_SET_ERR_MSG(extack, "TDC phase readback failed"); + return rc; + } + + *phase_offset = offset; + return 0; +} + static const struct dpll_pin_ops sit9531x_dpll_input_pin_ops = { .direction_get = sit9531x_dpll_input_pin_direction_get, .frequency_get = sit9531x_dpll_input_pin_frequency_get, @@ -677,6 +822,7 @@ static const struct dpll_pin_ops sit9531x_dpll_input_pin_ops = { .operstate_on_dpll_get = sit9531x_dpll_input_pin_operstate_on_dpll_get, .prio_get = sit9531x_dpll_input_pin_prio_get, .prio_set = sit9531x_dpll_input_pin_prio_set, + .phase_offset_get = sit9531x_dpll_input_pin_phase_offset_get, }; /* @@ -693,6 +839,7 @@ static const struct dpll_pin_ops sit9531x_dpll_input_pin_norate_ops = { .operstate_on_dpll_get = sit9531x_dpll_input_pin_operstate_on_dpll_get, .prio_get = sit9531x_dpll_input_pin_prio_get, .prio_set = sit9531x_dpll_input_pin_prio_set, + .phase_offset_get = sit9531x_dpll_input_pin_phase_offset_get, }; /* diff --git a/drivers/dpll/sit9531x/regs.h b/drivers/dpll/sit9531x/regs.h index 7c41111dca20..1891679698e2 100644 --- a/drivers/dpll/sit9531x/regs.h +++ b/drivers/dpll/sit9531x/regs.h @@ -284,6 +284,28 @@ #define SIT9531X_PLL_REG_DIVN_NUM 0x32 /* 4 bytes (0x32-0x35) */ #define SIT9531X_PLL_REG_DIVN_DEN 0x38 /* 4 bytes (0x38-0x3B) */ +/* + * Signal pathway debug readback -- PLL page. Dig_Sys_ReadCode selects + * which point of the pathway is tapped, Dig_Sys_WriteCode carries the + * modifiers for that read, Dig_Sys_read7..read0 hold the sampled bytes + * and a read of the debug status data register latches a sample. The + * TDC phase measurement is one tap among several, reached through read + * code 69. + */ +#define SIT9531X_PLL_REG_DBG_READ_CODE 0xB3 +#define SIT9531X_PLL_REG_DBG_WRITE_CODE 0xB4 +#define SIT9531X_DBG_LOW_FREQ_CLK_BIT BIT(7) +#define SIT9531X_PLL_REG_DBG_DATA_0 0xB5 /* [7:0] */ +#define SIT9531X_PLL_REG_DBG_DATA_1 0xB6 /* [15:8] */ +#define SIT9531X_PLL_REG_DBG_DATA_2 0xB7 /* [23:16] */ +#define SIT9531X_PLL_REG_DBG_DATA_3 0xB8 /* [31:24] */ +#define SIT9531X_PLL_REG_DBG_DATA_4 0xB9 /* [34:32] + sign */ + +/* Read code of the TDC phase tap, and the sign bit of its sample */ +#define SIT9531X_DBG_READ_CODE_TDC 69 +#define SIT9531X_TDC_SIGN_BIT 3 +#define SIT9531X_TDC_MAG_HI_MASK GENMASK(2, 0) + /* DIVN is carried as fixed point, in steps of 1e-12 of a whole divider */ #define SIT9531X_DIVN_SCALE 1000000000000ULL -- 2.39.2 (Apple Git-143)