diff options
| author | Paolo Abeni <pabeni@redhat.com> | 2026-07-06 14:45:06 +0200 |
|---|---|---|
| committer | Paolo Abeni <pabeni@redhat.com> | 2026-07-06 14:45:07 +0200 |
| commit | 474cff6868129755cf889edf40d7f491729fc588 (patch) | |
| tree | 26c8381fdf0f5c42d10c665fd59134f50def4f0e | |
| parent | 1704cc8640702c93e79921e2dffff3cfa31f0ce5 (diff) | |
| parent | 3553976ffe2f0ccb7f667725816e41c44f0e4729 (diff) | |
| download | linux-474cff6868129755cf889edf40d7f491729fc588.tar.gz linux-474cff6868129755cf889edf40d7f491729fc588.zip | |
Merge branch 'dpll-add-nco-pin-type-and-zl3073x-support'
Ivan Vecera says:
====================
dpll: add NCO pin type and zl3073x support
Add a new DPLL_PIN_TYPE_INT_NCO pin type for virtual pins representing
the NCO mode of a DPLL and implement support for it in the zl3073x driver.
Patch 1 adds a STATE_CONNECTED_OVERRIDE pin capability for pins that
can override input selection in any DPLL mode, with a WARN_ON check
in dpll_pin_register() that STATE_CAN_CHANGE is also set.
Patch 2 adds the new INT_NCO pin type to the DPLL netlink spec and
UAPI header.
Patch 3 replaces the single 2s poll timeout with per-operation timeouts
based on Microchip proprietary source code and own measurement.
Patch 4 adds a per-DPLL serialization mutex taken by all DPLL callbacks
and the periodic worker, establishing a single lock that protects all
per-channel state. The chan_state_update() call is moved under this lock.
Patch 5 adds a virtual NCO input pin to the zl3073x driver that allows
userspace to switch a DPLL channel into NCO mode. The pin reports
connected/active state when the channel is in NCO mode and handles
the hardware-specific details of mode transitions including automatic
df_offset capture and 1PPS phase preservation.
Link: https://lore.kernel.org/netdev/20260531194423.383366-1-ivecera@redhat.com/
====================
Link: https://patch.msgid.link/20260630125536.720717-1-ivecera@redhat.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
| -rw-r--r-- | Documentation/driver-api/dpll.rst | 7 | ||||
| -rw-r--r-- | Documentation/netlink/specs/dpll.yaml | 19 | ||||
| -rw-r--r-- | drivers/dpll/dpll_core.c | 6 | ||||
| -rw-r--r-- | drivers/dpll/dpll_nl.c | 2 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/chan.c | 128 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/chan.h | 48 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/core.c | 48 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/core.h | 12 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/dpll.c | 489 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/dpll.h | 4 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/regs.h | 11 | ||||
| -rw-r--r-- | include/uapi/linux/dpll.h | 8 |
12 files changed, 677 insertions, 105 deletions
diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst index bae14766d4f7..f83150917814 100644 --- a/Documentation/driver-api/dpll.rst +++ b/Documentation/driver-api/dpll.rst @@ -91,6 +91,13 @@ following pin states: - ``DPLL_PIN_STATE_DISCONNECTED`` - the pin shall be not considered as a valid input for automatic selection algorithm +Pins that have the ``DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE`` +capability can additionally be set to ``DPLL_PIN_STATE_CONNECTED`` in +automatic mode, overriding the active input selection. This is useful +for automatic-only DPLL devices where mode cannot be switched to manual. +When such a pin is disconnected, the device returns to automatic input +selection. + The actual hardware status of a pin is reported via the operational state (``DPLL_A_PIN_OPERSTATE``) attribute nested under the parent device: diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index 2bf83f6732ab..cdc8c7b456df 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -165,6 +165,13 @@ definitions: - name: gnss doc: GNSS recovered clock + - + name: int-nco + doc: | + Device internal numerically controlled oscillator. + When connected as a DPLL input, the DPLL enters NCO mode + where the output frequency is adjusted by the host via + the PTP clock interface. render-max: true - type: enum @@ -252,6 +259,12 @@ definitions: - name: state-can-change doc: pin state can be changed + - + name: state-connected-override + doc: | + pin state can be set to connected regardless of current + DPLL device mode, overriding the active input selection. + Requires state-can-change to be set as well. - type: const name: phase-offset-divider @@ -456,6 +469,9 @@ attribute-sets: offset on the media associated with the pin. Inside the pin-parent-device nest it represents the frequency offset between the pin and its parent DPLL device. + For pins of type PIN_TYPE_INT_NCO this represents + the DPLL's current output frequency offset from its + nominal frequency. Value is in PPM (parts per million). This is a lower-precision version of fractional-frequency-offset-ppt. @@ -502,6 +518,9 @@ attribute-sets: offset on the media associated with the pin. Inside the pin-parent-device nest it represents the frequency offset between the pin and its parent DPLL device. + For pins of type PIN_TYPE_INT_NCO this represents + the DPLL's current output frequency offset from its + nominal frequency. Value is in PPT (parts per trillion, 10^-12). This is a higher-precision version of fractional-frequency-offset. diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c index 2e8690cb3c16..bb1e8650c9d5 100644 --- a/drivers/dpll/dpll_core.c +++ b/drivers/dpll/dpll_core.c @@ -884,7 +884,11 @@ dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, WARN_ON(ops->measured_freq_get && (!dpll_device_ops(dpll)->freq_monitor_get || !dpll_device_ops(dpll)->freq_monitor_set)) || - WARN_ON(ops->supported_ffo && !ops->ffo_get)) + WARN_ON(ops->supported_ffo && !ops->ffo_get) || + WARN_ON((pin->prop.capabilities & + DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE) && + !(pin->prop.capabilities & + DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE))) return -EINVAL; mutex_lock(&dpll_lock); diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c index ed3bbe9841ea..b1ba490e72b0 100644 --- a/drivers/dpll/dpll_nl.c +++ b/drivers/dpll/dpll_nl.c @@ -61,7 +61,7 @@ static const struct nla_policy dpll_pin_id_get_nl_policy[DPLL_A_PIN_TYPE + 1] = [DPLL_A_PIN_BOARD_LABEL] = { .type = NLA_NUL_STRING, }, [DPLL_A_PIN_PANEL_LABEL] = { .type = NLA_NUL_STRING, }, [DPLL_A_PIN_PACKAGE_LABEL] = { .type = NLA_NUL_STRING, }, - [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 5), + [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 6), }; /* DPLL_CMD_PIN_GET - do */ diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c index 2fe3c3da84bb..4ec2cf53dad4 100644 --- a/drivers/dpll/zl3073x/chan.c +++ b/drivers/dpll/zl3073x/chan.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #include <linux/cleanup.h> +#include <linux/delay.h> #include <linux/dev_printk.h> #include <linux/string.h> #include <linux/types.h> @@ -31,9 +32,18 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) if (rc) return rc; - /* Read df_offset vs tracked reference */ + /* Read df_offset only when locked to a reference. In NCO mode + * df_offset was captured at entry by nco_mode_set() - preserve it. + */ + if (!zl3073x_chan_is_locked(chan)) { + if (!zl3073x_chan_mode_is_nco(chan)) + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + return 0; + } + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index), - ZL_DPLL_DF_READ_SEM); + ZL_DPLL_DF_READ_SEM, + ZL_POLL_DF_READ_TIMEOUT_US); if (rc) return rc; @@ -43,7 +53,8 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) return rc; rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index), - ZL_DPLL_DF_READ_SEM); + ZL_DPLL_DF_READ_SEM, + ZL_POLL_DF_READ_TIMEOUT_US); if (rc) return rc; @@ -57,6 +68,96 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) } /** + * zl3073x_chan_nco_mode_set - switch DPLL channel to NCO mode + * @zldev: pointer to zl3073x_dev structure + * @index: DPLL channel index + * + * Switches the channel to NCO mode and reads the df_offset + * auto-captured by nco_auto_read directly from the register. + * No DF_READ handshake is needed as nco_auto_read populates + * the register before the mode switch completes. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_chan *chan = &zldev->chan[index]; + u8 prev_mode, df_read; + u64 val; + int rc; + + prev_mode = zl3073x_chan_mode_get(chan); + + /* nco_auto_read captures the tracking offset at NCO entry only + * from reflock, auto or holdover mode. From freerun the captured + * value is not meaningful. + */ + if (prev_mode == ZL_DPLL_MODE_REFSEL_MODE_FREERUN) { + zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO); + + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), + chan->mode_refsel); + if (rc) { + zl3073x_chan_mode_set(chan, prev_mode); + return rc; + } + + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + return 0; + } + + /* Configure df_read for nco_auto_read: + * ref_ofst=0 - reads offset relative to master clock (not input ref) + * cmd=CMD_ACC_I - accumulated I-part covering both locked and + * holdover entry. + * + * No semaphore is set - this only configures what the df_offset + * value represents after the mode switch; nco_auto_read performs + * the actual read automatically. + */ + df_read = FIELD_PREP(ZL_DPLL_DF_READ_REF_OFST, 0) | + FIELD_PREP(ZL_DPLL_DF_READ_CMD, ZL_DPLL_DF_READ_CMD_ACC_I); + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index), df_read); + if (rc) + return rc; + + /* Wait for df_read configuration to take effect before + * triggering nco_auto_read via mode switch. The worst-case + * internal register update time is 25 ms. + */ + fsleep(25000); + + zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO); + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), + chan->mode_refsel); + if (rc) { + zl3073x_chan_mode_set(chan, prev_mode); + return rc; + } + + /* Wait for nco_auto_read to populate df_offset. The worst-case + * internal register update time is 25 ms. + */ + fsleep(25000); + + /* Read df_offset captured by nco_auto_read during mode switch. + * No DF_READ semaphore handshake needed. Mode switch already + * succeeded, so don't propagate a read failure back to userspace. + */ + rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val); + if (rc) { + dev_warn(zldev->dev, + "Failed to read DPLL%u df_offset: %pe\n", + index, ERR_PTR(rc)); + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + } else { + chan->df_offset = sign_extend64(val, 47); + } + + return 0; +} + +/** * zl3073x_chan_state_fetch - fetch DPLL channel state from hardware * @zldev: pointer to zl3073x_dev structure * @index: DPLL channel index to fetch state for @@ -71,6 +172,10 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index) struct zl3073x_chan *chan = &zldev->chan[index]; int rc, i; + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_CTRL(index), &chan->ctrl); + if (rc) + return rc; + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), &chan->mode_refsel); if (rc) @@ -83,6 +188,13 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index) if (rc) return rc; + /* If firmware left the channel in NCO mode, mark df_offset as + * unknown - we cannot know whether the preconditions for a valid + * nco_auto_read capture were met. + */ + if (zl3073x_chan_mode_is_nco(chan)) + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + dev_dbg(zldev->dev, "DPLL%u lock_state: %u, ho: %u, sel_state: %u, sel_ref: %u\n", index, zl3073x_chan_lock_state_get(chan), @@ -145,7 +257,15 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index, if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg))) return 0; - /* Direct register write for mode_refsel */ + /* Direct register writes for ctrl and mode_refsel */ + if (dchan->ctrl != chan->ctrl) { + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_CTRL(index), + chan->ctrl); + if (rc) + return rc; + dchan->ctrl = chan->ctrl; + } + if (dchan->mode_refsel != chan->mode_refsel) { rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), chan->mode_refsel); diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h index 4353809c6912..dc9c6d95bdee 100644 --- a/drivers/dpll/zl3073x/chan.h +++ b/drivers/dpll/zl3073x/chan.h @@ -13,6 +13,7 @@ struct zl3073x_dev; /** * struct zl3073x_chan - DPLL channel state + * @ctrl: DPLL control register value * @mode_refsel: mode and reference selection register value * @ref_prio: reference priority registers (4 bits per ref, P/N packed) * @mon_status: monitor status register value @@ -21,6 +22,7 @@ struct zl3073x_dev; */ struct zl3073x_chan { struct_group(cfg, + u8 ctrl; u8 mode_refsel; u8 ref_prio[ZL3073X_NUM_REFS / 2]; ); @@ -38,6 +40,7 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index, const struct zl3073x_chan *chan); int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index); +int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index); /** * zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference @@ -153,6 +156,51 @@ static inline u8 zl3073x_chan_lock_state_get(const struct zl3073x_chan *chan) } /** + * zl3073x_chan_is_locked - check if channel is locked to a reference + * @chan: pointer to channel state + * + * Return: true if channel is locked, false otherwise + */ +static inline bool zl3073x_chan_is_locked(const struct zl3073x_chan *chan) +{ + u8 lock_state = zl3073x_chan_lock_state_get(chan); + return lock_state == ZL_DPLL_MON_STATUS_STATE_LOCK; +} + +/** + * zl3073x_chan_mode_is_auto - check if channel is in automatic mode + * @chan: pointer to channel state + * + * Return: true if channel is in automatic mode, false otherwise + */ +static inline bool zl3073x_chan_mode_is_auto(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO; +} + +/** + * zl3073x_chan_mode_is_nco - check if channel is in NCO mode + * @chan: pointer to channel state + * + * Return: true if channel is in NCO mode, false otherwise + */ +static inline bool zl3073x_chan_mode_is_nco(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO; +} + +/** + * zl3073x_chan_mode_is_reflock - check if channel is in reflock mode + * @chan: pointer to channel state + * + * Return: true if channel is in reflock mode, false otherwise + */ +static inline bool zl3073x_chan_mode_is_reflock(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_REFLOCK; +} + +/** * zl3073x_chan_is_ho_ready - check if holdover is ready * @chan: pointer to channel state * diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c index 8e6416a4741d..7f5afaaae634 100644 --- a/drivers/dpll/zl3073x/core.c +++ b/drivers/dpll/zl3073x/core.c @@ -311,17 +311,17 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val) * @zldev: zl3073x device pointer * @reg: register to poll (has to be 8bit register) * @mask: bit mask for polling + * @timeout_us: timeout in microseconds * * Waits for bits specified by @mask in register @reg value to be cleared * by the device. * * Returns: 0 on success, <0 on error */ -int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask) +int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, + u8 mask, unsigned int timeout_us) { - /* Register polling sleep & timeout */ -#define ZL_POLL_SLEEP_US 10 -#define ZL_POLL_TIMEOUT_US 2000000 +#define ZL_POLL_SLEEP_US 10 unsigned int val; /* Check the register is 8bit */ @@ -335,7 +335,7 @@ int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask) reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET; return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask), - ZL_POLL_SLEEP_US, ZL_POLL_TIMEOUT_US); + ZL_POLL_SLEEP_US, timeout_us); } int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, @@ -354,7 +354,8 @@ int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, return rc; /* Wait for the operation to actually finish */ - return zl3073x_poll_zero_u8(zldev, op_reg, op_val); + return zl3073x_poll_zero_u8(zldev, op_reg, op_val, + ZL_POLL_MB_TIMEOUT_US); } /** @@ -377,8 +378,8 @@ zl3073x_do_hwreg_op(struct zl3073x_dev *zldev, u8 op) return rc; /* Poll for completion - pending bit cleared */ - return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP, - ZL_HWREG_OP_PENDING); + return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP, ZL_HWREG_OP_PENDING, + ZL_POLL_HWREG_TIMEOUT_US); } /** @@ -566,19 +567,7 @@ zl3073x_dev_ref_states_update(struct zl3073x_dev *zldev) } } -static void -zl3073x_dev_chan_states_update(struct zl3073x_dev *zldev) -{ - int i, rc; - for (i = 0; i < zldev->info->num_channels; i++) { - rc = zl3073x_chan_state_update(zldev, i); - if (rc) - dev_warn(zldev->dev, - "Failed to get DPLL%u state: %pe\n", i, - ERR_PTR(rc)); - } -} /** * zl3073x_ref_phase_offsets_update - update reference phase offsets @@ -609,7 +598,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel) * to be zero to ensure that the measured data are coherent. */ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST, - ZL_REF_PHASE_ERR_READ_RQST_RD); + ZL_REF_PHASE_ERR_READ_RQST_RD, + ZL_POLL_PHASE_ERR_TIMEOUT_US); if (rc) return rc; @@ -628,7 +618,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel) /* Wait for finish */ return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST, - ZL_REF_PHASE_ERR_READ_RQST_RD); + ZL_REF_PHASE_ERR_READ_RQST_RD, + ZL_POLL_PHASE_ERR_TIMEOUT_US); } /** @@ -648,7 +639,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type) /* Wait for previous measurement to finish */ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, - ZL_REF_FREQ_MEAS_CTRL); + ZL_REF_FREQ_MEAS_CTRL, + ZL_POLL_FREQ_MEAS_TIMEOUT_US); if (rc) return rc; @@ -669,7 +661,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type) /* Wait for finish */ return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, - ZL_REF_FREQ_MEAS_CTRL); + ZL_REF_FREQ_MEAS_CTRL, + ZL_POLL_FREQ_MEAS_TIMEOUT_US); } /** @@ -715,9 +708,6 @@ zl3073x_dev_periodic_work(struct kthread_work *work) /* Update input references' states */ zl3073x_dev_ref_states_update(zldev); - /* Update DPLL channels' states */ - zl3073x_dev_chan_states_update(zldev); - /* Update DPLL-to-connected-ref phase offsets registers */ rc = zl3073x_ref_phase_offsets_update(zldev, -1); if (rc) @@ -727,7 +717,7 @@ zl3073x_dev_periodic_work(struct kthread_work *work) /* Update measured input reference frequencies if frequency * monitoring is enabled. */ - if (zldev->freq_monitor) { + if (READ_ONCE(zldev->freq_monitor)) { rc = zl3073x_ref_freq_meas_update(zldev); if (rc) dev_warn(zldev->dev, @@ -763,7 +753,7 @@ int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor) return rc; /* Save the new factor */ - zldev->phase_avg_factor = factor; + WRITE_ONCE(zldev->phase_avg_factor, factor); return 0; } diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h index addba378b0df..78dc208f3eea 100644 --- a/drivers/dpll/zl3073x/core.h +++ b/drivers/dpll/zl3073x/core.h @@ -7,6 +7,7 @@ #include <linux/kthread.h> #include <linux/list.h> #include <linux/mutex.h> +#include <linux/time64.h> #include <linux/types.h> #include "chan.h" @@ -19,6 +20,12 @@ struct device; struct regmap; struct zl3073x_dpll; +/* Per-operation poll timeouts */ +#define ZL_POLL_DF_READ_TIMEOUT_US (25 * USEC_PER_MSEC) +#define ZL_POLL_FREQ_MEAS_TIMEOUT_US (50 * USEC_PER_MSEC) +#define ZL_POLL_HWREG_TIMEOUT_US (50 * USEC_PER_MSEC) +#define ZL_POLL_MB_TIMEOUT_US (30 * USEC_PER_MSEC) +#define ZL_POLL_PHASE_ERR_TIMEOUT_US (50 * USEC_PER_MSEC) enum zl3073x_flags { ZL3073X_FLAG_REF_PHASE_COMP_32_BIT, @@ -94,7 +101,7 @@ void zl3073x_dev_stop(struct zl3073x_dev *zldev); static inline u8 zl3073x_dev_phase_avg_factor_get(struct zl3073x_dev *zldev) { - return zldev->phase_avg_factor; + return READ_ONCE(zldev->phase_avg_factor); } int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor); @@ -127,7 +134,8 @@ struct zl3073x_hwreg_seq_item { int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, unsigned int mask_reg, u16 mask_val); -int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask); +int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, + u8 mask, unsigned int timeout_us); int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val); int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val); int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val); diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index 5e58ded5734d..d91f52b58eae 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -#include <linux/atomic.h> #include <linux/bits.h> #include <linux/bitfield.h> #include <linux/bug.h> @@ -58,7 +57,7 @@ struct zl3073x_dpll_pin { s32 phase_gran; enum dpll_pin_operstate operstate; s64 phase_offset; - atomic64_t freq_offset; + s64 freq_offset; u32 measured_freq; }; @@ -82,6 +81,18 @@ zl3073x_dpll_is_input_pin(struct zl3073x_dpll_pin *pin) } /** + * zl3073x_dpll_is_nco_pin - check if the pin is a virtual NCO pin + * @pin: pin to check + * + * Return: true if pin is a virtual NCO pin, false otherwise. + */ +static bool +zl3073x_dpll_is_nco_pin(struct zl3073x_dpll_pin *pin) +{ + return pin->id == ZL3073X_NCO_PIN_ID; +} + +/** * zl3073x_dpll_is_p_pin - check if the pin is P-pin * @pin: pin to check * @@ -120,6 +131,19 @@ zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id) return NULL; } +static struct zl3073x_dpll_pin * +zl3073x_dpll_nco_pin_get(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + + list_for_each_entry(pin, &zldpll->pins, list) { + if (zl3073x_dpll_is_nco_pin(pin)) + return pin; + } + + return NULL; +} + static int zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin, void *pin_priv, @@ -134,6 +158,8 @@ zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin, const struct zl3073x_ref *ref; u8 ref_id; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); ref = zl3073x_ref_state_get(zldev, ref_id); @@ -170,6 +196,8 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin, struct zl3073x_ref ref; u8 ref_id, sync_mode; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -205,6 +233,8 @@ zl3073x_dpll_input_pin_ref_sync_get(const struct dpll_pin *dpll_pin, const struct zl3073x_ref *ref; u8 ref_id, mode, pair; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); ref = zl3073x_ref_state_get(zldev, ref_id); mode = zl3073x_ref_sync_mode_get(ref); @@ -236,6 +266,8 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin, struct zl3073x_ref ref; int rc; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); sync_ref_id = zl3073x_input_pin_ref_get(sync_pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -299,12 +331,15 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv, struct dpll_ffo_param *ffo, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE) return -ENODATA; - ffo->ffo = atomic64_read(&pin->freq_offset); + ffo->ffo = pin->freq_offset; return 0; } @@ -316,8 +351,11 @@ zl3073x_dpll_input_pin_measured_freq_get(const struct dpll_pin *dpll_pin, void *dpll_priv, u64 *measured_freq, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + *measured_freq = pin->measured_freq; *measured_freq *= DPLL_PIN_MEASURED_FREQUENCY_DIVIDER; @@ -335,6 +373,8 @@ zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin, struct zl3073x_dpll_pin *pin = pin_priv; u8 ref_id; + guard(mutex)(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); *frequency = zl3073x_dev_ref_freq_get(zldpll->dev, ref_id); @@ -354,6 +394,8 @@ zl3073x_dpll_input_pin_frequency_set(const struct dpll_pin *dpll_pin, struct zl3073x_ref ref; u8 ref_id; + guard(mutex)(&zldpll->lock); + /* Get reference state */ ref_id = zl3073x_input_pin_ref_get(pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -402,6 +444,8 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin, u8 conn_id, ref_id; s64 ref_phase; + guard(mutex)(&zldpll->lock); + /* Get currently connected reference */ conn_id = zl3073x_dpll_connected_ref_get(zldpll); @@ -459,6 +503,8 @@ zl3073x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, s64 phase_comp; u8 ref_id; + guard(mutex)(&zldpll->lock); + /* Read reference configuration */ ref_id = zl3073x_input_pin_ref_get(pin->id); ref = zl3073x_ref_state_get(zldev, ref_id); @@ -491,6 +537,8 @@ zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, struct zl3073x_ref ref; u8 ref_id; + guard(mutex)(&zldpll->lock); + /* Read reference configuration */ ref_id = zl3073x_input_pin_ref_get(pin->id); ref = *zl3073x_ref_state_get(zldev, ref_id); @@ -524,6 +572,8 @@ zl3073x_dpll_ref_operstate_get(struct zl3073x_dpll_pin *pin, const struct zl3073x_ref *ref; u8 ref_id; + lockdep_assert_held(&zldpll->lock); + ref_id = zl3073x_input_pin_ref_get(pin->id); /* Check if this pin is the currently locked reference */ @@ -557,6 +607,8 @@ zl3073x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, const struct zl3073x_chan *chan; u8 mode, ref; + guard(mutex)(&zldpll->lock); + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); ref = zl3073x_input_pin_ref_get(pin->id); mode = zl3073x_chan_mode_get(chan); @@ -590,8 +642,11 @@ zl3073x_dpll_input_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin, enum dpll_pin_operstate *operstate, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + return zl3073x_dpll_ref_operstate_get(pin, operstate); } @@ -605,9 +660,12 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, { struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_dpll_pin *nco_pin = NULL; struct zl3073x_chan chan; u8 mode, ref; - int rc; + int rc = 0; + + mutex_lock(&zldpll->lock); chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); ref = zl3073x_input_pin_ref_get(pin->id); @@ -634,6 +692,10 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, goto invalid_state; } break; + case ZL_DPLL_MODE_REFSEL_MODE_NCO: + if (state == DPLL_PIN_STATE_CONNECTED) + nco_pin = zl3073x_dpll_nco_pin_get(zldpll); + fallthrough; case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER: if (state == DPLL_PIN_STATE_CONNECTED) { @@ -649,13 +711,13 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, case ZL_DPLL_MODE_REFSEL_MODE_AUTO: if (state == DPLL_PIN_STATE_SELECTABLE) { if (zl3073x_chan_ref_is_selectable(&chan, ref)) - return 0; /* Pin is already selectable */ + goto unlock; /* Pin is already selectable */ /* Restore pin priority in HW */ zl3073x_chan_ref_prio_set(&chan, ref, pin->prio); } else if (state == DPLL_PIN_STATE_DISCONNECTED) { if (!zl3073x_chan_ref_is_selectable(&chan, ref)) - return 0; /* Pin is already disconnected */ + goto unlock; /* Pin is already disconnected */ /* Set pin priority to none in HW */ zl3073x_chan_ref_prio_set(&chan, ref, @@ -668,18 +730,27 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, /* In other modes we cannot change input reference */ NL_SET_ERR_MSG(extack, "Pin state cannot be changed in current mode"); - return -EOPNOTSUPP; + rc = -EOPNOTSUPP; + goto unlock; } /* Commit DPLL channel changes */ rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); - if (rc) - return rc; + goto unlock; - return 0; invalid_state: NL_SET_ERR_MSG_MOD(extack, "Invalid pin state for this device mode"); - return -EINVAL; + rc = -EINVAL; +unlock: + mutex_unlock(&zldpll->lock); + + /* If leaving NCO mode, notify userspace about the NCO pin + * state change - the periodic worker skips the NCO pin. + */ + if (!rc && nco_pin) + __dpll_pin_change_ntf(nco_pin->dpll_pin); + + return rc; } static int @@ -687,8 +758,11 @@ zl3073x_dpll_input_pin_prio_get(const struct dpll_pin *dpll_pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, u32 *prio, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + *prio = pin->prio; return 0; @@ -705,6 +779,8 @@ zl3073x_dpll_input_pin_prio_set(const struct dpll_pin *dpll_pin, void *pin_priv, u8 ref; int rc; + guard(mutex)(&zldpll->lock); + if (prio > ZL_DPLL_REF_PRIO_MAX) return -EINVAL; @@ -740,6 +816,8 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, u32 synth_freq, out_freq; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = zl3073x_out_state_get(zldev, out_id); @@ -797,6 +875,8 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, u32 synth_freq; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -817,7 +897,7 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, /* If esync is being disabled just write mailbox and finish */ if (!freq) - goto write_mailbox; + return zl3073x_out_state_set(zldev, out_id, &out); /* Get attached synth frequency */ synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out)); @@ -834,7 +914,6 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, */ out.esync_n_width = out.div / 2; -write_mailbox: /* Commit output configuration */ return zl3073x_out_state_set(zldev, out_id, &out); } @@ -849,6 +928,8 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin, struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + guard(mutex)(&zldpll->lock); + *frequency = zl3073x_dev_output_pin_freq_get(zldpll->dev, pin->id); return 0; @@ -869,6 +950,8 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin, struct zl3073x_out out; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -942,6 +1025,8 @@ zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, const struct zl3073x_out *out; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = zl3073x_out_state_get(zldev, out_id); @@ -965,6 +1050,8 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, struct zl3073x_out out; u8 out_id; + guard(mutex)(&zldpll->lock); + out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -990,6 +1077,144 @@ zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, } static int +zl3073x_dpll_nco_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_operstate *operstate, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (zl3073x_chan_mode_is_nco(chan)) + *operstate = DPLL_PIN_OPERSTATE_ACTIVE; + else + *operstate = DPLL_PIN_OPERSTATE_STANDBY; + + return 0; +} + +static int +zl3073x_dpll_nco_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (zl3073x_chan_mode_is_nco(chan)) + *state = DPLL_PIN_STATE_CONNECTED; + else + *state = DPLL_PIN_STATE_DISCONNECTED; + + return 0; +} + +static int +zl3073x_dpll_nco_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll_pin *ref_pin = NULL; + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_chan chan; + u8 ref; + int rc; + + mutex_lock(&zldpll->lock); + + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + + switch (state) { + case DPLL_PIN_STATE_CONNECTED: + if (zl3073x_chan_mode_is_nco(&chan)) { + mutex_unlock(&zldpll->lock); + return 0; + } + if (zl3073x_chan_mode_is_auto(&chan)) { + NL_SET_ERR_MSG(extack, + "NCO pin cannot be connected in automatic mode"); + mutex_unlock(&zldpll->lock); + return -EINVAL; + } + if (zl3073x_chan_mode_is_reflock(&chan)) { + /* Get currently connected pin */ + ref = zl3073x_chan_ref_get(&chan); + ref_pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref); + } + rc = zl3073x_chan_nco_mode_set(zldpll->dev, zldpll->id); + break; + case DPLL_PIN_STATE_DISCONNECTED: + if (!zl3073x_chan_mode_is_nco(&chan)) { + mutex_unlock(&zldpll->lock); + return 0; + } + /* Switch to freerun - holdover averaging was not + * updated during NCO mode. + */ + zl3073x_chan_mode_set(&chan, + ZL_DPLL_MODE_REFSEL_MODE_FREERUN); + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + break; + default: + NL_SET_ERR_MSG(extack, "invalid pin state for NCO pin"); + mutex_unlock(&zldpll->lock); + return -EINVAL; + } + + mutex_unlock(&zldpll->lock); + + if (!rc && ref_pin) + __dpll_pin_change_ntf(ref_pin->dpll_pin); + + return rc; +} + +static int +zl3073x_dpll_nco_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv, + const struct dpll_device *dpll, void *dpll_priv, + struct dpll_ffo_param *ffo, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + s64 df_offset; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (!zl3073x_chan_mode_is_nco(chan)) + return -ENODATA; + + /* Do not report FFO if a failure occurred during switching to NCO. */ + df_offset = zl3073x_chan_df_offset_get(chan); + if (df_offset == ZL_DPLL_DF_OFFSET_UNKNOWN) + return -ENODATA; + + /* dpll_df_offset register has inverted sign per datasheet: + * f_offset = f_nom * (-df_offset) / 2^48 + * NCO pin reports DPLL output offset from nominal, so negate. + * Convert to PPT: ppt = -df * 5^12 / 2^36 + */ + ffo->ffo = -mul_s64_u64_shr(df_offset, 244140625, 36); + + return 0; +} + +static int zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv, s32 *temp, struct netlink_ext_ack *extack) { @@ -998,6 +1223,8 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv, u16 val; int rc; + guard(mutex)(&zldpll->lock); + rc = zl3073x_read_u16(zldev, ZL_REG_DIE_TEMP_STATUS, &val); if (rc) return rc; @@ -1009,14 +1236,13 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv, } static int -zl3073x_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) +__zl3073x_dpll_lock_status_get(struct zl3073x_dpll *zldpll, + enum dpll_lock_status *status) { - struct zl3073x_dpll *zldpll = dpll_priv; const struct zl3073x_chan *chan; + lockdep_assert_held(&zldpll->lock); + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); switch (zl3073x_chan_mode_get(chan)) { @@ -1053,23 +1279,24 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, } static int -zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll, - void *dpll_priv, unsigned long *modes, - struct netlink_ext_ack *extack) +zl3073x_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 zl3073x_dpll *zldpll = dpll_priv; - const struct zl3073x_chan *chan; - chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + guard(mutex)(&zldpll->lock); - /* We support switching between automatic and manual mode, except in - * a case where the DPLL channel is configured to run in NCO mode. - * In this case, report only the manual mode to which the NCO is mapped - * as the only supported one. - */ - if (zl3073x_chan_mode_get(chan) != ZL_DPLL_MODE_REFSEL_MODE_NCO) - __set_bit(DPLL_MODE_AUTOMATIC, modes); + return __zl3073x_dpll_lock_status_get(zldpll, status); +} +static int +zl3073x_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); __set_bit(DPLL_MODE_MANUAL, modes); return 0; @@ -1082,6 +1309,8 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv, struct zl3073x_dpll *zldpll = dpll_priv; const struct zl3073x_chan *chan; + guard(mutex)(&zldpll->lock); + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); switch (zl3073x_chan_mode_get(chan)) { @@ -1138,8 +1367,8 @@ zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll, return rc; } - /* The averaging factor is common for all DPLL channels so after change - * we have to send a notification for other DPLL devices. + /* The averaging factor is common for all DPLL channels so after + * change we have to send a notification for other DPLL devices. */ list_for_each_entry(item, &zldpll->dev->dplls, list) { struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev); @@ -1156,10 +1385,13 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, enum dpll_mode mode, struct netlink_ext_ack *extack) { struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dpll_pin *nco_pin = NULL; struct zl3073x_chan chan; u8 hw_mode, ref; int rc; + mutex_lock(&zldpll->lock); + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); ref = zl3073x_chan_refsel_ref_get(&chan); @@ -1179,6 +1411,9 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, else hw_mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER; } else { + if (zl3073x_chan_mode_is_nco(&chan)) + nco_pin = zl3073x_dpll_nco_pin_get(zldpll); + /* We are switching from manual to automatic mode: * - if there is a valid reference selected then ensure that * it is selectable after switch to automatic mode @@ -1207,9 +1442,18 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, if (rc) { NL_SET_ERR_MSG_MOD(extack, "failed to set reference selection mode"); + mutex_unlock(&zldpll->lock); return rc; } + mutex_unlock(&zldpll->lock); + + /* If leaving NCO mode, notify userspace about the NCO pin + * state change - the periodic worker skips the NCO pin. + */ + if (nco_pin) + __dpll_pin_change_ntf(nco_pin->dpll_pin); + return 0; } @@ -1221,6 +1465,8 @@ zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll, { struct zl3073x_dpll *zldpll = dpll_priv; + guard(mutex)(&zldpll->lock); + if (zldpll->phase_monitor) *state = DPLL_FEATURE_STATE_ENABLE; else @@ -1237,6 +1483,8 @@ zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll, { struct zl3073x_dpll *zldpll = dpll_priv; + guard(mutex)(&zldpll->lock); + zldpll->phase_monitor = (state == DPLL_FEATURE_STATE_ENABLE); return 0; @@ -1250,7 +1498,7 @@ zl3073x_dpll_freq_monitor_get(const struct dpll_device *dpll, { struct zl3073x_dpll *zldpll = dpll_priv; - if (zldpll->dev->freq_monitor) + if (READ_ONCE(zldpll->dev->freq_monitor)) *state = DPLL_FEATURE_STATE_ENABLE; else *state = DPLL_FEATURE_STATE_DISABLE; @@ -1265,13 +1513,14 @@ zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll, struct netlink_ext_ack *extack) { struct zl3073x_dpll *item, *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; - zldpll->dev->freq_monitor = (state == DPLL_FEATURE_STATE_ENABLE); + WRITE_ONCE(zldev->freq_monitor, state == DPLL_FEATURE_STATE_ENABLE); /* The frequency monitoring is common for all DPLL channels so after * change we have to send a notification for other DPLL devices. */ - list_for_each_entry(item, &zldpll->dev->dplls, list) { + list_for_each_entry(item, &zldev->dplls, list) { struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev); if (item != zldpll && dpll_dev) @@ -1313,6 +1562,15 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = { .state_on_dpll_get = zl3073x_dpll_output_pin_state_on_dpll_get, }; +static const struct dpll_pin_ops zl3073x_dpll_nco_pin_ops = { + .supported_ffo = BIT(DPLL_FFO_PIN_DEVICE), + .direction_get = zl3073x_dpll_pin_direction_get, + .ffo_get = zl3073x_dpll_nco_pin_ffo_get, + .operstate_on_dpll_get = zl3073x_dpll_nco_pin_operstate_on_dpll_get, + .state_on_dpll_get = zl3073x_dpll_nco_pin_state_on_dpll_get, + .state_on_dpll_set = zl3073x_dpll_nco_pin_state_on_dpll_set, +}; + static const struct dpll_device_ops zl3073x_dpll_device_ops = { .lock_status_get = zl3073x_dpll_lock_status_get, .mode_get = zl3073x_dpll_mode_get, @@ -1460,7 +1718,9 @@ zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin) WARN(!pin->dpll_pin, "DPLL pin is not registered\n"); - if (zl3073x_dpll_is_input_pin(pin)) + if (zl3073x_dpll_is_nco_pin(pin)) + ops = &zl3073x_dpll_nco_pin_ops; + else if (zl3073x_dpll_is_input_pin(pin)) ops = &zl3073x_dpll_input_pin_ops; else ops = &zl3073x_dpll_output_pin_ops; @@ -1513,20 +1773,13 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, enum dpll_pin_direction dir, u8 index) { struct zl3073x_dev *zldev = zldpll->dev; - const struct zl3073x_chan *chan; bool is_diff, is_enabled; const char *name; - chan = zl3073x_chan_state_get(zldev, zldpll->id); - if (dir == DPLL_PIN_DIRECTION_INPUT) { u8 ref_id = zl3073x_input_pin_ref_get(index); const struct zl3073x_ref *ref; - /* Skip the pin if the DPLL is running in NCO mode */ - if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO) - return false; - name = "REF"; ref = zl3073x_ref_state_get(zldev, ref_id); is_diff = zl3073x_ref_is_diff(ref); @@ -1567,6 +1820,66 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, return true; } +static const struct dpll_pin_properties zl3073x_dpll_nco_pin_props = { + .type = DPLL_PIN_TYPE_INT_NCO, + .package_label = "NCO", + .capabilities = DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE, +}; + +static int +zl3073x_dpll_nco_pin_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + struct zl3073x_chan chan; + int rc; + + /* Ensure that ctrl bits are configured for NCO operation: + * - nco_auto_read: auto-capture tracking offset on NCO entry + * - tod_step_reset: apply negated ToD step on NCO exit + * - tie_clear: PPS DPLLs re-align outputs on NCO exit + */ + mutex_lock(&zldpll->lock); + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + FIELD_MODIFY(ZL_DPLL_CTRL_NCO_AUTO_READ, &chan.ctrl, 1); + FIELD_MODIFY(ZL_DPLL_CTRL_TOD_STEP_RST, &chan.ctrl, 1); + FIELD_MODIFY(ZL_DPLL_CTRL_TIE_CLEAR, &chan.ctrl, + zldpll->type == DPLL_TYPE_PPS ? 1 : 0); + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + mutex_unlock(&zldpll->lock); + if (rc) + return rc; + + pin = zl3073x_dpll_pin_alloc(zldpll, DPLL_PIN_DIRECTION_INPUT, + ZL3073X_NCO_PIN_ID); + if (IS_ERR(pin)) + return PTR_ERR(pin); + + pin->dpll_pin = dpll_pin_get(zldpll->dev->clock_id, ZL3073X_NCO_PIN_ID, + THIS_MODULE, &zl3073x_dpll_nco_pin_props, + &pin->tracker); + if (IS_ERR(pin->dpll_pin)) { + rc = PTR_ERR(pin->dpll_pin); + goto err_pin_get; + } + + rc = dpll_pin_register(zldpll->dpll_dev, pin->dpll_pin, + &zl3073x_dpll_nco_pin_ops, pin); + if (rc) + goto err_register; + + list_add(&pin->list, &zldpll->pins); + + return 0; + +err_register: + dpll_pin_put(pin->dpll_pin, &pin->tracker); +err_pin_get: + pin->dpll_pin = NULL; + kfree(pin); + + return rc; +} + /** * zl3073x_dpll_pins_register - register all registerable DPLL pins * @zldpll: pointer to zl3073x_dpll structure @@ -1614,6 +1927,11 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll) list_add(&pin->list, &zldpll->pins); } + /* Register NCO virtual input pin */ + rc = zl3073x_dpll_nco_pin_register(zldpll); + if (rc) + goto error; + return 0; error: @@ -1649,8 +1967,8 @@ zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll) return rc; } - rc = dpll_device_register(zldpll->dpll_dev, - zl3073x_prop_dpll_type_get(zldev, zldpll->id), + zldpll->type = zl3073x_prop_dpll_type_get(zldev, zldpll->id); + rc = dpll_device_register(zldpll->dpll_dev, zldpll->type, &zldpll->ops, zldpll); if (rc) { dpll_device_put(zldpll->dpll_dev, &zldpll->tracker); @@ -1697,6 +2015,8 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin) u8 ref_id; int rc; + lockdep_assert_held(&zldpll->lock); + /* No phase offset if the ref monitor reports signal errors */ ref_id = zl3073x_input_pin_ref_get(pin->id); if (!zl3073x_dev_ref_is_status_ok(zldev, ref_id)) @@ -1753,16 +2073,27 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin) const struct zl3073x_chan *chan; s64 ffo; + lockdep_assert_held(&zldpll->lock); + if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE) return false; chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (zl3073x_chan_df_offset_get(chan) == ZL_DPLL_DF_OFFSET_UNKNOWN) + return false; + + /* dpll_df_offset register has inverted sign per datasheet: + * f_offset = f_nom * (-df_offset) / 2^48 + * Input pin FFO is pin-vs-DPLL (opposite of DPLL-vs-reference), + * so the two inversions cancel out: ppt = df * 5^12 / 2^36 + */ ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan), 244140625, 36); - if (atomic64_xchg(&pin->freq_offset, ffo) != ffo) { + if (pin->freq_offset != ffo) { dev_dbg(zldev->dev, "%s freq offset changed to: %lld\n", pin->label, ffo); + pin->freq_offset = ffo; return true; } @@ -1787,7 +2118,9 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin) u8 ref_id; u32 freq; - if (!zldpll->dev->freq_monitor) + lockdep_assert_held(&zldpll->lock); + + if (!READ_ONCE(zldpll->dev->freq_monitor)) return false; ref_id = zl3073x_input_pin_ref_get(pin->id); @@ -1817,27 +2150,37 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin) void zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) { + DECLARE_BITMAP(changed_pins, ZL3073X_NUM_INPUT_PINS); struct zl3073x_dev *zldev = zldpll->dev; enum dpll_lock_status lock_status; struct device *dev = zldev->dev; struct zl3073x_dpll_pin *pin; + bool dev_changed = false; int rc; + bitmap_zero(changed_pins, ZL3073X_NUM_INPUT_PINS); + + mutex_lock(&zldpll->lock); + zldpll->check_count++; - /* Get current lock status for the DPLL */ - rc = zl3073x_dpll_lock_status_get(zldpll->dpll_dev, zldpll, - &lock_status, NULL, NULL); + rc = zl3073x_chan_state_update(zldev, zldpll->id); + if (rc) { + dev_err(dev, "Failed to get DPLL%u state: %pe\n", + zldpll->id, ERR_PTR(rc)); + goto unlock; + } + + rc = __zl3073x_dpll_lock_status_get(zldpll, &lock_status); if (rc) { dev_err(dev, "Failed to get DPLL%u lock status: %pe\n", zldpll->id, ERR_PTR(rc)); - return; + goto unlock; } - /* If lock status was changed then notify DPLL core */ if (zldpll->lock_status != lock_status) { zldpll->lock_status = lock_status; - dpll_device_change_ntf(zldpll->dpll_dev); + dev_changed = true; } /* Update phase offset latch registers for this DPLL if the phase @@ -1849,18 +2192,16 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) dev_err(zldev->dev, "Failed to update phase offsets: %pe\n", ERR_PTR(rc)); - return; + goto unlock; } } list_for_each_entry(pin, &zldpll->pins, list) { enum dpll_pin_operstate operstate; - bool pin_changed = false; - /* Output pins change checks are not necessary because output - * states are constant. - */ - if (!zl3073x_dpll_is_input_pin(pin)) + /* Only physical input pins need monitoring */ + if (!zl3073x_dpll_is_input_pin(pin) || + zl3073x_dpll_is_nco_pin(pin)) continue; rc = zl3073x_dpll_ref_operstate_get(pin, &operstate); @@ -1868,31 +2209,41 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) dev_err(dev, "Failed to get %s on DPLL%u oper state: %pe\n", pin->label, zldpll->id, ERR_PTR(rc)); - return; + goto unlock; } if (operstate != pin->operstate) { dev_dbg(dev, "%s oper state changed: %u->%u\n", pin->label, pin->operstate, operstate); pin->operstate = operstate; - pin_changed = true; + set_bit(pin->id, changed_pins); } - /* Check for phase offset, ffo, and measured freq change - * once per second. - */ if (zldpll->check_count % 2 == 0) { if (zl3073x_dpll_pin_phase_offset_check(pin)) - pin_changed = true; + set_bit(pin->id, changed_pins); if (zl3073x_dpll_pin_ffo_check(pin)) - pin_changed = true; + set_bit(pin->id, changed_pins); if (zl3073x_dpll_pin_measured_freq_check(pin)) - pin_changed = true; + set_bit(pin->id, changed_pins); } + } + +unlock: + mutex_unlock(&zldpll->lock); - if (pin_changed) + /* Send notifications outside the lock to avoid ABBA deadlock + * with dpll_lock taken by notification functions. + */ + if (dev_changed) + dpll_device_change_ntf(zldpll->dpll_dev); + + list_for_each_entry(pin, &zldpll->pins, list) { + if (zl3073x_dpll_is_input_pin(pin) && + !zl3073x_dpll_is_nco_pin(pin) && + test_bit(pin->id, changed_pins)) dpll_pin_change_ntf(pin->dpll_pin); } } @@ -1949,6 +2300,7 @@ zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch) zldpll->dev = zldev; zldpll->id = ch; + mutex_init(&zldpll->lock); INIT_LIST_HEAD(&zldpll->pins); return zldpll; @@ -1965,6 +2317,7 @@ zl3073x_dpll_free(struct zl3073x_dpll *zldpll) { WARN(zldpll->dpll_dev, "DPLL device is still registered\n"); + mutex_destroy(&zldpll->lock); kfree(zldpll); } diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h index 21adcc18e45e..faebc402ba1b 100644 --- a/drivers/dpll/zl3073x/dpll.h +++ b/drivers/dpll/zl3073x/dpll.h @@ -18,6 +18,8 @@ * @ops: DPLL device operations for this instance * @dpll_dev: pointer to registered DPLL device * @tracker: tracking object for the acquired reference + * @lock: per-DPLL mutex serializing all operations + * @type: DPLL type (PPS or EEC) * @lock_status: last saved DPLL lock status * @pins: list of pins */ @@ -30,6 +32,8 @@ struct zl3073x_dpll { struct dpll_device_ops ops; struct dpll_device *dpll_dev; dpll_tracker tracker; + struct mutex lock; + enum dpll_type type; enum dpll_lock_status lock_status; struct list_head pins; }; diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h index 9578f0009528..b70ead7d4495 100644 --- a/drivers/dpll/zl3073x/regs.h +++ b/drivers/dpll/zl3073x/regs.h @@ -5,6 +5,7 @@ #include <linux/bitfield.h> #include <linux/bits.h> +#include <linux/limits.h> /* * Hardware limits for ZL3073x chip family @@ -17,6 +18,7 @@ #define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2) #define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \ ZL3073X_NUM_OUTPUT_PINS) +#define ZL3073X_NCO_PIN_ID ZL3073X_NUM_PINS /* * Register address structure: @@ -164,10 +166,18 @@ #define ZL_DPLL_MODE_REFSEL_MODE_NCO 4 #define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4) +#define ZL_REG_DPLL_CTRL(_idx) \ + ZL_REG_IDX(_idx, 5, 0x05, 1, ZL3073X_MAX_CHANNELS, 4) +#define ZL_DPLL_CTRL_TIE_CLEAR BIT(0) +#define ZL_DPLL_CTRL_TOD_STEP_RST BIT(2) +#define ZL_DPLL_CTRL_NCO_AUTO_READ BIT(7) + #define ZL_REG_DPLL_DF_READ(_idx) \ ZL_REG_IDX(_idx, 5, 0x28, 1, ZL3073X_MAX_CHANNELS, 1) #define ZL_DPLL_DF_READ_SEM BIT(4) #define ZL_DPLL_DF_READ_REF_OFST BIT(3) +#define ZL_DPLL_DF_READ_CMD GENMASK(2, 0) +#define ZL_DPLL_DF_READ_CMD_ACC_I 4 #define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1) #define ZL_DPLL_MEAS_CTRL_EN BIT(0) @@ -190,6 +200,7 @@ #define ZL_REG_DPLL_DF_OFFSET_4 ZL_REG(7, 0x00, 6) #define ZL_REG_DPLL_DF_OFFSET(_idx) \ ((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4) +#define ZL_DPLL_DF_OFFSET_UNKNOWN S64_MIN /*********************************** * Register Page 9, Synth and Output diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index 55eaa82f5f98..85b898b1db5e 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -129,6 +129,9 @@ enum dpll_type { * @DPLL_PIN_TYPE_SYNCE_ETH_PORT: ethernet port PHY's recovered clock * @DPLL_PIN_TYPE_INT_OSCILLATOR: device internal oscillator * @DPLL_PIN_TYPE_GNSS: GNSS recovered clock + * @DPLL_PIN_TYPE_INT_NCO: Device internal numerically controlled oscillator. + * When connected as a DPLL input, the DPLL enters NCO mode where the output + * frequency is adjusted by the host via the PTP clock interface. */ enum dpll_pin_type { DPLL_PIN_TYPE_MUX = 1, @@ -136,6 +139,7 @@ enum dpll_pin_type { DPLL_PIN_TYPE_SYNCE_ETH_PORT, DPLL_PIN_TYPE_INT_OSCILLATOR, DPLL_PIN_TYPE_GNSS, + DPLL_PIN_TYPE_INT_NCO, /* private: */ __DPLL_PIN_TYPE_MAX, @@ -208,11 +212,15 @@ enum dpll_pin_operstate { * @DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE: pin direction can be changed * @DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE: pin priority can be changed * @DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE: pin state can be changed + * @DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE: pin state can be set to + * connected regardless of current DPLL device mode, overriding the active + * input selection. Requires state-can-change to be set as well. */ enum dpll_pin_capabilities { DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE = 1, DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE = 2, DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE = 4, + DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE = 8, }; #define DPLL_PHASE_OFFSET_DIVIDER 1000 |
