diff options
| author | Sang-Heon Jeon <ekffu200098@gmail.com> | 2026-08-24 01:18:14 +0900 |
|---|---|---|
| committer | Julia Lawall <Julia.Lawall@inria.fr> | 2026-08-23 22:12:11 +0200 |
| commit | 2bd30b8dc75b823dd7bbef9d7bc68e1fd7ecee5c (patch) | |
| tree | dcc4da1d99f17541c5590ac63c77da4bf372a4ab /drivers/dpll/zl3073x/dpll.c | |
| download | linux-stable-2bd30b8dc75b823dd7bbef9d7bc68e1fd7ecee5c.tar.gz linux-stable-2bd30b8dc75b823dd7bbef9d7bc68e1fd7ecee5c.zip | |
coccinelle: alloc_cast: drop removed allocatorsgrafted
- pci_alloc_consistent() and pci_zalloc_consistent() were removed by
commit 7968778914e5
("PCI: Remove the deprecated "pci-dma-compat.h" API")
- kmem_alloc() was removed by commit f078d4ea8276
("xfs: convert kmem_alloc() to kmalloc()")
- kmem_zalloc() was removed by commit 10634530f7ba
("xfs: convert kmem_zalloc() to kzalloc()")
- kmem_zone_alloc() and kmem_zone_zalloc() were removed by commit
bae633a4a283 ("xfs: remove xfs_zone_{alloc,zalloc} helpers")
So drop them from the rules.
No functional change.
Signed-off-by: Sang-Heon Jeon <ekffu200098@gmail.com>
Signed-off-by: Julia Lawall <Julia.Lawall@inria.fr>
Diffstat (limited to 'drivers/dpll/zl3073x/dpll.c')
| -rw-r--r-- | drivers/dpll/zl3073x/dpll.c | 2874 |
1 files changed, 2874 insertions, 0 deletions
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c new file mode 100644 index 000000000..f2e137475 --- /dev/null +++ b/drivers/dpll/zl3073x/dpll.c @@ -0,0 +1,2874 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/bug.h> +#include <linux/container_of.h> +#include <linux/dev_printk.h> +#include <linux/dpll.h> +#include <linux/err.h> +#include <linux/kthread.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/netlink.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/ptp_clock_kernel.h> +#include <linux/slab.h> +#include <linux/sprintf.h> + +#include "core.h" +#include "dpll.h" +#include "prop.h" +#include "regs.h" + +#define ZL3073X_DPLL_REF_NONE ZL3073X_NUM_REFS +#define ZL3073X_DPLL_REF_IS_VALID(_ref) ((_ref) != ZL3073X_DPLL_REF_NONE) + +/** + * struct zl3073x_dpll_pin - DPLL pin + * @list: this DPLL pin list entry + * @dpll: DPLL the pin is registered to + * @dpll_pin: pointer to registered dpll_pin + * @tracker: tracking object for the acquired reference + * @fwnode: firmware node handle + * @label: package label + * @dir: pin direction + * @id: pin id + * @prio: pin priority <0, 14> + * @esync_control: embedded sync is controllable + * @phase_gran: phase adjustment granularity + * @operstate: last saved operational state + * @phase_offset: last saved pin phase offset + * @freq_offset: last saved fractional frequency offset + * @measured_freq: last saved measured frequency + */ +struct zl3073x_dpll_pin { + struct list_head list; + struct zl3073x_dpll *dpll; + struct dpll_pin *dpll_pin; + dpll_tracker tracker; + struct fwnode_handle *fwnode; + char label[8]; + enum dpll_pin_direction dir; + u8 id; + u8 prio; + bool esync_control; + s32 phase_gran; + enum dpll_pin_operstate operstate; + s64 phase_offset; + s64 freq_offset; + u32 measured_freq; +}; + +/* + * Supported esync ranges for input and for output per output pair type + */ +static const struct dpll_pin_frequency esync_freq_ranges[] = { + DPLL_PIN_FREQUENCY_RANGE(0, 1), +}; + +/** + * zl3073x_dpll_is_input_pin - check if the pin is input one + * @pin: pin to check + * + * Return: true if pin is input, false if pin is output. + */ +static bool +zl3073x_dpll_is_input_pin(struct zl3073x_dpll_pin *pin) +{ + return pin->dir == DPLL_PIN_DIRECTION_INPUT; +} + +/** + * 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 + * + * Return: true if the pin is P-pin, false if it is N-pin + */ +static bool +zl3073x_dpll_is_p_pin(struct zl3073x_dpll_pin *pin) +{ + return zl3073x_is_p_pin(pin->id); +} + +static int +zl3073x_dpll_pin_direction_get(const struct dpll_pin *dpll_pin, void *pin_priv, + const struct dpll_device *dpll, void *dpll_priv, + enum dpll_pin_direction *direction, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll_pin *pin = pin_priv; + + *direction = pin->dir; + + return 0; +} + +static struct zl3073x_dpll_pin * +zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id) +{ + struct zl3073x_dpll_pin *pin; + + list_for_each_entry(pin, &zldpll->pins, list) { + if (zl3073x_dpll_is_input_pin(pin) && + zl3073x_input_pin_ref_get(pin->id) == ref_id) + return pin; + } + + 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, + const struct dpll_device *dpll, + void *dpll_priv, + struct dpll_pin_esync *esync, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + 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); + + if (!pin->esync_control || zl3073x_ref_freq_get(ref) <= 1) + return -EOPNOTSUPP; + + esync->range = esync_freq_ranges; + esync->range_num = ARRAY_SIZE(esync_freq_ranges); + + switch (zl3073x_ref_sync_mode_get(ref)) { + case ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75: + esync->freq = ref->esync_n_div == ZL_REF_ESYNC_DIV_1HZ ? 1 : 0; + esync->pulse = 25; + break; + default: + esync->freq = 0; + esync->pulse = 0; + break; + } + + return 0; +} + +static int +zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 freq, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + 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); + + /* Use freq == 0 to disable esync */ + if (!freq) + sync_mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF; + else + sync_mode = ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75; + + zl3073x_ref_sync_mode_set(&ref, sync_mode); + + if (freq) { + /* 1 Hz is only supported frequency now */ + ref.esync_n_div = ZL_REF_ESYNC_DIV_1HZ; + } + + /* Update reference configuration */ + return zl3073x_ref_state_set(zldev, ref_id, &ref); +} + +static int +zl3073x_dpll_input_pin_ref_sync_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_pin *ref_sync_pin, + void *ref_sync_pin_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll_pin *sync_pin = ref_sync_pin_priv; + struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + 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); + pair = zl3073x_ref_sync_pair_get(ref); + + if (mode == ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR && + pair == zl3073x_input_pin_ref_get(sync_pin->id)) + *state = DPLL_PIN_STATE_CONNECTED; + else + *state = DPLL_PIN_STATE_DISCONNECTED; + + return 0; +} + +static int +zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_pin *ref_sync_pin, + void *ref_sync_pin_priv, + const enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll_pin *sync_pin = ref_sync_pin_priv; + struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + u8 mode, ref_id, sync_ref_id; + struct zl3073x_chan chan; + struct zl3073x_ref ref; + bool sync_ntf = false; + int rc; + + mutex_lock(&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); + + if (state == DPLL_PIN_STATE_CONNECTED) { + const struct zl3073x_ref *sync_ref; + u32 ref_freq, sync_freq; + + sync_ref = zl3073x_ref_state_get(zldev, sync_ref_id); + ref_freq = zl3073x_ref_freq_get(&ref); + sync_freq = zl3073x_ref_freq_get(sync_ref); + + /* Sync signal must be 8 kHz or less and clock reference + * must be 1 kHz or more and higher than the sync signal. + */ + if (sync_freq > 8000) { + NL_SET_ERR_MSG(extack, + "sync frequency must be 8 kHz or less"); + rc = -EINVAL; + goto unlock; + } + if (ref_freq < 1000) { + NL_SET_ERR_MSG(extack, + "clock frequency must be 1 kHz or more"); + rc = -EINVAL; + goto unlock; + } + if (ref_freq <= sync_freq) { + NL_SET_ERR_MSG(extack, + "clock frequency must be higher than sync frequency"); + rc = -EINVAL; + goto unlock; + } + + zl3073x_ref_sync_pair_set(&ref, sync_ref_id); + mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR; + } else { + mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF; + } + + zl3073x_ref_sync_mode_set(&ref, mode); + + rc = zl3073x_ref_state_set(zldev, ref_id, &ref); + if (rc) + goto unlock; + + /* All code paths accessing per-channel reference priorities are + * serialized by the subsystem dpll_lock, so it is safe to release + * our lock here before iterating over the other channels. + */ + mutex_unlock(&zldpll->lock); + + if (state != DPLL_PIN_STATE_CONNECTED) + return 0; + + /* The datasheet recommends excluding the sync source from automatic + * reference selection by setting its priority to NONE on all DPLL + * channels. This is advisory - the ref sync pair is already + * configured, so a failure here is not fatal. On disconnect the + * priority is left as NONE and the user must explicitly make the + * pin selectable again. + */ + list_for_each_entry(zldpll, &zldev->dplls, list) { + u8 prio; + + mutex_lock(&zldpll->lock); + + chan = *zl3073x_chan_state_get(zldev, zldpll->id); + prio = zl3073x_chan_ref_prio_get(&chan, sync_ref_id); + if (prio == ZL_DPLL_REF_PRIO_NONE) { + mutex_unlock(&zldpll->lock); + continue; /* Ref is already non-selectable */ + } + + zl3073x_chan_ref_prio_set(&chan, sync_ref_id, + ZL_DPLL_REF_PRIO_NONE); + if (zl3073x_chan_state_set(zldev, zldpll->id, &chan)) + dev_warn(zldev->dev, + "Failed to set ref prio on DPLL%u\n", + zldpll->id); + else + sync_ntf = true; + + mutex_unlock(&zldpll->lock); + } + if (sync_ntf) + __dpll_pin_change_ntf(sync_pin->dpll_pin); + + return 0; +unlock: + mutex_unlock(&zldpll->lock); + return rc; +} + +static int +zl3073x_dpll_input_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; + struct zl3073x_dpll_pin *pin = pin_priv; + + guard(mutex)(&zldpll->lock); + + if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE) + return -ENODATA; + + ffo->ffo = pin->freq_offset; + + return 0; +} + +static int +zl3073x_dpll_input_pin_measured_freq_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + 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; + + return 0; +} + +static int +zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 *frequency, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + 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); + + return 0; +} + +static int +zl3073x_dpll_input_pin_frequency_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 frequency, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + 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); + + /* Update frequency */ + zl3073x_ref_freq_set(&ref, frequency); + + /* Commit reference state */ + return zl3073x_ref_state_set(zldev, ref_id, &ref); +} + +/** + * zl3073x_dpll_connected_ref_get - get currently connected reference + * @zldpll: pointer to zl3073x_dpll + * + * Looks for currently connected reference the DPLL is locked to. + * + * Return: reference index if locked, ZL3073X_DPLL_REF_NONE otherwise + */ +static u8 +zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll) +{ + const struct zl3073x_chan *chan = zl3073x_chan_state_get(zldpll->dev, + zldpll->id); + u8 state; + + /* A reference is connected only when the DPLL is locked to it */ + state = zl3073x_chan_refsel_state_get(chan); + if (state == ZL_DPLL_REFSEL_STATUS_STATE_LOCK) + return zl3073x_chan_refsel_ref_get(chan); + + return ZL3073X_DPLL_REF_NONE; +} + +static int +zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, s64 *phase_offset, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + const struct zl3073x_ref *ref; + u8 conn_id, ref_id; + s64 ref_phase; + + guard(mutex)(&zldpll->lock); + + /* Get currently connected reference */ + conn_id = zl3073x_dpll_connected_ref_get(zldpll); + + /* Report phase offset only for currently connected pin if the phase + * monitor feature is disabled and only if the input pin signal is + * present. + */ + ref_id = zl3073x_input_pin_ref_get(pin->id); + ref = zl3073x_ref_state_get(zldev, ref_id); + if ((!zldpll->phase_monitor && ref_id != conn_id) || + !zl3073x_ref_is_status_ok(ref)) { + *phase_offset = 0; + return 0; + } + + ref_phase = pin->phase_offset; + + /* The DPLL being locked to a higher freq than the current ref + * the phase offset is modded to the period of the signal + * the dpll is locked to. + */ + if (ZL3073X_DPLL_REF_IS_VALID(conn_id) && conn_id != ref_id) { + u32 conn_freq, ref_freq; + + /* Get frequency of connected and given ref */ + conn_freq = zl3073x_dev_ref_freq_get(zldev, conn_id); + ref_freq = zl3073x_ref_freq_get(ref); + + if (conn_freq > ref_freq) { + s64 conn_period, div_factor; + + conn_period = div_s64(PSEC_PER_SEC, conn_freq); + div_factor = div64_s64(ref_phase, conn_period); + ref_phase -= conn_period * div_factor; + } + } + + *phase_offset = ref_phase * DPLL_PHASE_OFFSET_DIVIDER; + + return 0; +} + +static int +zl3073x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + s32 *phase_adjust, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + const struct zl3073x_ref *ref; + 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); + + /* Perform sign extension based on register width */ + if (zl3073x_dev_is_ref_phase_comp_32bit(zldev)) + phase_comp = sign_extend64(ref->phase_comp, 31); + else + phase_comp = sign_extend64(ref->phase_comp, 47); + + /* Reverse two's complement negation applied during set and convert + * to 32bit signed int + */ + *phase_adjust = (s32)-phase_comp; + + return 0; +} + +static int +zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + s32 phase_adjust, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + 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); + + /* The value in the register is stored as two's complement negation + * of requested value. + */ + ref.phase_comp = -phase_adjust; + + /* Update reference configuration */ + return zl3073x_ref_state_set(zldev, ref_id, &ref); +} + +/** + * zl3073x_dpll_ref_operstate_get - get operational state for input pin + * @pin: pointer to pin + * @operstate: place to store operational state + * + * Returns the actual hardware state of the pin: whether it is actively + * used by the DPLL, has no signal, failed qualification, or is simply + * not in use. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_ref_operstate_get(struct zl3073x_dpll_pin *pin, + enum dpll_pin_operstate *operstate) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + 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 */ + if (ref_id == zl3073x_dpll_connected_ref_get(zldpll)) { + *operstate = DPLL_PIN_OPERSTATE_ACTIVE; + return 0; + } + + /* Check reference monitor status */ + ref = zl3073x_ref_state_get(zldev, ref_id); + if (ref->mon_status & ZL_REF_MON_STATUS_LOS) + *operstate = DPLL_PIN_OPERSTATE_NO_SIGNAL; + else if (!zl3073x_ref_is_status_ok(ref)) + *operstate = DPLL_PIN_OPERSTATE_QUAL_FAILED; + else + *operstate = DPLL_PIN_OPERSTATE_STANDBY; + + return 0; +} + +static int +zl3073x_dpll_input_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; + struct zl3073x_dpll_pin *pin = pin_priv; + 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); + + switch (mode) { + case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: + if (ref == zl3073x_chan_ref_get(chan)) + *state = DPLL_PIN_STATE_CONNECTED; + else + *state = DPLL_PIN_STATE_DISCONNECTED; + break; + case ZL_DPLL_MODE_REFSEL_MODE_AUTO: + if (zl3073x_chan_ref_is_selectable(chan, ref)) + *state = DPLL_PIN_STATE_SELECTABLE; + else + *state = DPLL_PIN_STATE_DISCONNECTED; + break; + default: + *state = DPLL_PIN_STATE_DISCONNECTED; + break; + } + + return 0; +} + +static int +zl3073x_dpll_input_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; + struct zl3073x_dpll_pin *pin = pin_priv; + + guard(mutex)(&zldpll->lock); + + return zl3073x_dpll_ref_operstate_get(pin, operstate); +} + +static int +zl3073x_dpll_input_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 *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 = 0; + + mutex_lock(&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); + + switch (mode) { + case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: + if (state == DPLL_PIN_STATE_CONNECTED) { + /* Choose the pin as new selected reference */ + zl3073x_chan_ref_set(&chan, ref); + } else if (state == DPLL_PIN_STATE_DISCONNECTED) { + /* Choose new mode based on lock status */ + switch (zldpll->lock_status) { + case DPLL_LOCK_STATUS_LOCKED_HO_ACQ: + case DPLL_LOCK_STATUS_HOLDOVER: + mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER; + break; + default: + mode = ZL_DPLL_MODE_REFSEL_MODE_FREERUN; + break; + } + zl3073x_chan_mode_set(&chan, mode); + } else { + 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) { + /* Choose the pin as new selected reference */ + zl3073x_chan_ref_set(&chan, ref); + /* Switch to reflock mode */ + zl3073x_chan_mode_set(&chan, + ZL_DPLL_MODE_REFSEL_MODE_REFLOCK); + } else if (state != DPLL_PIN_STATE_DISCONNECTED) { + goto invalid_state; + } + break; + case ZL_DPLL_MODE_REFSEL_MODE_AUTO: + if (state == DPLL_PIN_STATE_SELECTABLE) { + if (zl3073x_chan_ref_is_selectable(&chan, ref)) + 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)) + goto unlock; /* Pin is already disconnected */ + + /* Set pin priority to none in HW */ + zl3073x_chan_ref_prio_set(&chan, ref, + ZL_DPLL_REF_PRIO_NONE); + } else { + goto invalid_state; + } + break; + default: + /* In other modes we cannot change input reference */ + NL_SET_ERR_MSG(extack, + "Pin state cannot be changed in current mode"); + rc = -EOPNOTSUPP; + goto unlock; + } + + /* Commit DPLL channel changes */ + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + goto unlock; + +invalid_state: + NL_SET_ERR_MSG_MOD(extack, "Invalid pin state for this device mode"); + 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 +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; +} + +static int +zl3073x_dpll_input_pin_prio_set(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; + struct zl3073x_chan chan; + u8 ref; + int rc; + + guard(mutex)(&zldpll->lock); + + if (prio > ZL_DPLL_REF_PRIO_MAX) + return -EINVAL; + + /* If the pin is selectable then update HW registers */ + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + ref = zl3073x_input_pin_ref_get(pin->id); + if (zl3073x_chan_ref_is_selectable(&chan, ref)) { + zl3073x_chan_ref_prio_set(&chan, ref, prio); + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + if (rc) + return rc; + } + + /* Save priority */ + pin->prio = prio; + + return 0; +} + +static int +zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + struct dpll_pin_esync *esync, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + const struct zl3073x_synth *synth; + const struct zl3073x_out *out; + 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); + + /* If N-division is enabled, esync is not supported. The register used + * for N-division is also used for the esync divider so both cannot + * be used. + */ + if (zl3073x_out_is_ndiv(out)) + return -EOPNOTSUPP; + + /* Get attached synth frequency */ + synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(out)); + synth_freq = zl3073x_synth_freq_get(synth); + out_freq = synth_freq / out->div; + + if (!pin->esync_control || out_freq <= 1) + return -EOPNOTSUPP; + + esync->range = esync_freq_ranges; + esync->range_num = ARRAY_SIZE(esync_freq_ranges); + + if (zl3073x_out_clock_type_get(out) != ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC) { + /* No need to read esync data if it is not enabled */ + esync->freq = 0; + esync->pulse = 0; + + return 0; + } + + /* Compute esync frequency */ + esync->freq = out_freq / out->esync_n_period; + + /* By comparing the esync_pulse_width to the half of the pulse width + * the esync pulse percentage can be determined. + * Note that half pulse width is in units of half synth cycles, which + * is why it reduces down to be output_div. + */ + esync->pulse = (50 * out->esync_n_width) / out->div; + + return 0; +} + +static int +zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 freq, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + const struct zl3073x_synth *synth; + struct zl3073x_out out; + 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); + + /* If N-division is enabled, esync is not supported. The register used + * for N-division is also used for the esync divider so both cannot + * be used. + */ + if (zl3073x_out_is_ndiv(&out)) + return -EOPNOTSUPP; + + /* Update clock type in output mode */ + if (freq) + zl3073x_out_clock_type_set(&out, + ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC); + else + zl3073x_out_clock_type_set(&out, + ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL); + + /* If esync is being disabled just write mailbox and finish */ + if (!freq) + return zl3073x_out_state_set(zldev, out_id, &out); + + /* Get attached synth frequency */ + synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out)); + synth_freq = zl3073x_synth_freq_get(synth); + + /* Compute and update esync period */ + out.esync_n_period = synth_freq / (u32)freq / out.div; + + /* Half of the period in units of 1/2 synth cycle can be represented by + * the output_div. To get the supported esync pulse width of 25% of the + * period the output_div can just be divided by 2. Note that this + * assumes that output_div is even, otherwise some resolution will be + * lost. + */ + out.esync_n_width = out.div / 2; + + /* Commit output configuration */ + return zl3073x_out_state_set(zldev, out_id, &out); +} + +static int +zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 *frequency, + struct netlink_ext_ack *extack) +{ + 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; +} + +static int +zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 frequency, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + const struct zl3073x_synth *synth; + u32 new_div, synth_freq; + 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); + + /* Get attached synth frequency and compute new divisor */ + synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out)); + synth_freq = zl3073x_synth_freq_get(synth); + new_div = synth_freq / (u32)frequency; + + /* Check signal format */ + if (!zl3073x_out_is_ndiv(&out)) { + /* For non N-divided signal formats the frequency is computed + * as division of synth frequency and output divisor. + */ + out.div = new_div; + + /* For 50/50 duty cycle the divisor is equal to width */ + out.width = new_div; + + /* Commit output configuration */ + return zl3073x_out_state_set(zldev, out_id, &out); + } + + if (zl3073x_dpll_is_p_pin(pin)) { + /* We are going to change output frequency for P-pin but + * if the requested frequency is less than current N-pin + * frequency then indicate a failure as we are not able + * to compute N-pin divisor to keep its frequency unchanged. + * + * Update divisor for N-pin to keep N-pin frequency. + */ + out.esync_n_period = (out.esync_n_period * out.div) / new_div; + if (!out.esync_n_period) + return -EINVAL; + + /* Update the output divisor */ + out.div = new_div; + + /* For 50/50 duty cycle the divisor is equal to width */ + out.width = out.div; + } else { + /* We are going to change frequency of N-pin but if + * the requested freq is greater or equal than freq of P-pin + * in the output pair we cannot compute divisor for the N-pin. + * In this case indicate a failure. + * + * Update divisor for N-pin + */ + out.esync_n_period = div64_u64(synth_freq, frequency * out.div); + if (!out.esync_n_period) + return -EINVAL; + } + + /* For 50/50 duty cycle the divisor is equal to width */ + out.esync_n_width = out.esync_n_period; + + /* Commit output configuration */ + return zl3073x_out_state_set(zldev, out_id, &out); +} + +static int +zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + s32 *phase_adjust, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + 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); + + /* The value in the register is expressed in half synth clock cycles. */ + *phase_adjust = out->phase_comp * pin->phase_gran; + + return 0; +} + +static int +zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + s32 phase_adjust, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + 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); + + /* The value in the register is expressed in half synth clock cycles. */ + out.phase_comp = phase_adjust / pin->phase_gran; + + /* Update output configuration from mailbox */ + return zl3073x_out_state_set(zldev, out_id, &out); +} + +static int +zl3073x_dpll_output_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) +{ + /* If the output pin is registered then it is always connected */ + *state = DPLL_PIN_STATE_CONNECTED; + + return 0; +} + +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) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + u16 val; + int rc; + + guard(mutex)(&zldpll->lock); + + rc = zl3073x_read_u16(zldev, ZL_REG_DIE_TEMP_STATUS, &val); + if (rc) + return rc; + + /* Register value is in units of 0.1 C, convert to millidegrees */ + *temp = (s16)val * 100; + + return 0; +} + +static int +__zl3073x_dpll_lock_status_get(struct zl3073x_dpll *zldpll, + enum dpll_lock_status *status) +{ + 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)) { + case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: + case ZL_DPLL_MODE_REFSEL_MODE_NCO: + /* In FREERUN and NCO modes the DPLL is always unlocked */ + *status = DPLL_LOCK_STATUS_UNLOCKED; + + return 0; + default: + break; + } + + switch (zl3073x_chan_lock_state_get(chan)) { + case ZL_DPLL_MON_STATUS_STATE_LOCK: + if (zl3073x_chan_is_ho_ready(chan)) + *status = DPLL_LOCK_STATUS_LOCKED_HO_ACQ; + else + *status = DPLL_LOCK_STATUS_LOCKED; + break; + case ZL_DPLL_MON_STATUS_STATE_HOLDOVER: + case ZL_DPLL_MON_STATUS_STATE_ACQUIRING: + *status = DPLL_LOCK_STATUS_HOLDOVER; + break; + default: + dev_warn(zldpll->dev->dev, + "Unknown DPLL monitor status: 0x%02x\n", + chan->mon_status); + *status = DPLL_LOCK_STATUS_UNLOCKED; + break; + } + + return 0; +} + +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) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + guard(mutex)(&zldpll->lock); + + 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; +} + +static int +zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv, + enum dpll_mode *mode, 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); + + switch (zl3073x_chan_mode_get(chan)) { + case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: + case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER: + case ZL_DPLL_MODE_REFSEL_MODE_NCO: + case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: + /* Use MANUAL for device FREERUN, HOLDOVER, NCO and + * REFLOCK modes + */ + *mode = DPLL_MODE_MANUAL; + break; + case ZL_DPLL_MODE_REFSEL_MODE_AUTO: + /* Use AUTO for device AUTO mode */ + *mode = DPLL_MODE_AUTOMATIC; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int +zl3073x_dpll_phase_offset_avg_factor_get(const struct dpll_device *dpll, + void *dpll_priv, u32 *factor, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + *factor = zl3073x_dev_phase_avg_factor_get(zldpll->dev); + + return 0; +} + +static int +zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll, + void *dpll_priv, u32 factor, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *item, *zldpll = dpll_priv; + int rc; + + if (factor > 15) { + NL_SET_ERR_MSG_FMT(extack, + "Phase offset average factor has to be from range <0,15>"); + return -EINVAL; + } + + rc = zl3073x_dev_phase_avg_factor_set(zldpll->dev, factor); + if (rc) { + NL_SET_ERR_MSG_FMT(extack, + "Failed to set phase offset averaging factor"); + return rc; + } + + /* 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); + + if (item != zldpll && dpll_dev) + __dpll_device_change_ntf(dpll_dev); + } + + return 0; +} + +static int +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); + + if (mode == DPLL_MODE_MANUAL) { + /* We are switching from automatic to manual mode: + * - if we have a valid reference selected during auto mode then + * we will switch to forced reference lock mode and use this + * reference for selection + * - if NO valid reference is selected, we will switch to forced + * holdover mode or freerun mode, depending on the current + * lock status + */ + if (ZL3073X_DPLL_REF_IS_VALID(ref)) + hw_mode = ZL_DPLL_MODE_REFSEL_MODE_REFLOCK; + else if (zldpll->lock_status == DPLL_LOCK_STATUS_UNLOCKED) + hw_mode = ZL_DPLL_MODE_REFSEL_MODE_FREERUN; + 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 + * - switch to automatic mode + */ + if (ZL3073X_DPLL_REF_IS_VALID(ref) && + !zl3073x_chan_ref_is_selectable(&chan, ref)) { + struct zl3073x_dpll_pin *pin; + + pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref); + if (pin) { + /* Restore pin priority in HW */ + zl3073x_chan_ref_prio_set(&chan, ref, + pin->prio); + } + } + + hw_mode = ZL_DPLL_MODE_REFSEL_MODE_AUTO; + } + + zl3073x_chan_mode_set(&chan, hw_mode); + if (ZL3073X_DPLL_REF_IS_VALID(ref)) + zl3073x_chan_ref_set(&chan, ref); + + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + 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; +} + +static int +zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_feature_state *state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + guard(mutex)(&zldpll->lock); + + if (zldpll->phase_monitor) + *state = DPLL_FEATURE_STATE_ENABLE; + else + *state = DPLL_FEATURE_STATE_DISABLE; + + return 0; +} + +static int +zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_feature_state state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + guard(mutex)(&zldpll->lock); + + zldpll->phase_monitor = (state == DPLL_FEATURE_STATE_ENABLE); + + return 0; +} + +static int +zl3073x_dpll_freq_monitor_get(const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_feature_state *state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + if (READ_ONCE(zldpll->dev->freq_monitor)) + *state = DPLL_FEATURE_STATE_ENABLE; + else + *state = DPLL_FEATURE_STATE_DISABLE; + + return 0; +} + +static int +zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_feature_state state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *item, *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + + 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, &zldev->dplls, list) { + struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev); + + if (item != zldpll && dpll_dev) + __dpll_device_change_ntf(dpll_dev); + } + + return 0; +} + +static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = { + .supported_ffo = BIT(DPLL_FFO_PIN_DEVICE), + .direction_get = zl3073x_dpll_pin_direction_get, + .esync_get = zl3073x_dpll_input_pin_esync_get, + .esync_set = zl3073x_dpll_input_pin_esync_set, + .ffo_get = zl3073x_dpll_input_pin_ffo_get, + .frequency_get = zl3073x_dpll_input_pin_frequency_get, + .frequency_set = zl3073x_dpll_input_pin_frequency_set, + .measured_freq_get = zl3073x_dpll_input_pin_measured_freq_get, + .operstate_on_dpll_get = zl3073x_dpll_input_pin_operstate_on_dpll_get, + .phase_offset_get = zl3073x_dpll_input_pin_phase_offset_get, + .phase_adjust_get = zl3073x_dpll_input_pin_phase_adjust_get, + .phase_adjust_set = zl3073x_dpll_input_pin_phase_adjust_set, + .prio_get = zl3073x_dpll_input_pin_prio_get, + .prio_set = zl3073x_dpll_input_pin_prio_set, + .ref_sync_get = zl3073x_dpll_input_pin_ref_sync_get, + .ref_sync_set = zl3073x_dpll_input_pin_ref_sync_set, + .state_on_dpll_get = zl3073x_dpll_input_pin_state_on_dpll_get, + .state_on_dpll_set = zl3073x_dpll_input_pin_state_on_dpll_set, +}; + +static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = { + .direction_get = zl3073x_dpll_pin_direction_get, + .esync_get = zl3073x_dpll_output_pin_esync_get, + .esync_set = zl3073x_dpll_output_pin_esync_set, + .frequency_get = zl3073x_dpll_output_pin_frequency_get, + .frequency_set = zl3073x_dpll_output_pin_frequency_set, + .phase_adjust_get = zl3073x_dpll_output_pin_phase_adjust_get, + .phase_adjust_set = zl3073x_dpll_output_pin_phase_adjust_set, + .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, + .mode_set = zl3073x_dpll_mode_set, + .phase_offset_avg_factor_get = zl3073x_dpll_phase_offset_avg_factor_get, + .phase_offset_avg_factor_set = zl3073x_dpll_phase_offset_avg_factor_set, + .phase_offset_monitor_get = zl3073x_dpll_phase_offset_monitor_get, + .phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set, + .freq_monitor_get = zl3073x_dpll_freq_monitor_get, + .freq_monitor_set = zl3073x_dpll_freq_monitor_set, + .supported_modes_get = zl3073x_dpll_supported_modes_get, +}; + +/** + * zl3073x_dpll_pin_alloc - allocate DPLL pin + * @zldpll: pointer to zl3073x_dpll + * @dir: pin direction + * @id: pin id + * + * Allocates and initializes zl3073x_dpll_pin structure for given + * pin id and direction. + * + * Return: pointer to allocated structure on success, error pointer on error + */ +static struct zl3073x_dpll_pin * +zl3073x_dpll_pin_alloc(struct zl3073x_dpll *zldpll, enum dpll_pin_direction dir, + u8 id) +{ + struct zl3073x_dpll_pin *pin; + + pin = kzalloc_obj(*pin); + if (!pin) + return ERR_PTR(-ENOMEM); + + pin->dpll = zldpll; + pin->dir = dir; + pin->id = id; + + return pin; +} + +/** + * zl3073x_dpll_pin_free - deallocate DPLL pin + * @pin: pin to free + * + * Deallocates DPLL pin previously allocated by @zl3073x_dpll_pin_alloc. + */ +static void +zl3073x_dpll_pin_free(struct zl3073x_dpll_pin *pin) +{ + WARN(pin->dpll_pin, "DPLL pin is still registered\n"); + + kfree(pin); +} + +/** + * zl3073x_dpll_pin_register - register DPLL pin + * @pin: pointer to DPLL pin + * @index: absolute pin index for registration + * + * Registers given DPLL pin into DPLL sub-system. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_pin_props *props; + const struct dpll_pin_ops *ops; + int rc; + + /* Get pin properties */ + props = zl3073x_pin_props_get(zldpll->dev, pin->dir, pin->id); + if (IS_ERR(props)) + return PTR_ERR(props); + + /* Save package label, fwnode, esync capability and phase adjust + * granularity. + */ + strscpy(pin->label, props->package_label); + pin->fwnode = fwnode_handle_get(props->fwnode); + pin->esync_control = props->esync_control; + pin->phase_gran = props->dpll_props.phase_gran; + + if (zl3073x_dpll_is_input_pin(pin)) { + const struct zl3073x_chan *chan; + u8 ref; + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + ref = zl3073x_input_pin_ref_get(pin->id); + pin->prio = zl3073x_chan_ref_prio_get(chan, ref); + + if (pin->prio == ZL_DPLL_REF_PRIO_NONE) + /* Clamp prio to max value */ + pin->prio = ZL_DPLL_REF_PRIO_MAX; + } + + /* Create or get existing DPLL pin */ + pin->dpll_pin = dpll_pin_get(zldpll->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); + + if (zl3073x_dpll_is_input_pin(pin)) + ops = &zl3073x_dpll_input_pin_ops; + else + ops = &zl3073x_dpll_output_pin_ops; + + /* Register the pin */ + rc = dpll_pin_register(zldpll->dpll_dev, pin->dpll_pin, ops, pin); + if (rc) + goto err_register; + + /* Free pin properties */ + zl3073x_pin_props_put(props); + + return 0; + +err_register: + dpll_pin_put(pin->dpll_pin, &pin->tracker); +err_pin_get: + pin->dpll_pin = NULL; + fwnode_handle_put(pin->fwnode); + pin->fwnode = NULL; + zl3073x_pin_props_put(props); + + return rc; +} + +/** + * zl3073x_dpll_pin_unregister - unregister DPLL pin + * @pin: pointer to DPLL pin + * + * Unregisters pin previously registered by @zl3073x_dpll_pin_register. + */ +static void +zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + const struct dpll_pin_ops *ops; + + WARN(!pin->dpll_pin, "DPLL pin is not registered\n"); + + 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; + + /* Unregister the pin */ + dpll_pin_unregister(zldpll->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; +} + +/** + * zl3073x_dpll_pins_unregister - unregister all registered DPLL pins + * @zldpll: pointer to zl3073x_dpll structure + * + * Enumerates all DPLL pins registered to given DPLL device and + * unregisters them. + */ +static void +zl3073x_dpll_pins_unregister(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin, *next; + + list_for_each_entry_safe(pin, next, &zldpll->pins, list) { + zl3073x_dpll_pin_unregister(pin); + list_del(&pin->list); + zl3073x_dpll_pin_free(pin); + } +} + +/** + * zl3073x_dpll_pin_is_registrable - check if the pin is registrable + * @zldpll: pointer to zl3073x_dpll structure + * @dir: pin direction + * @index: pin index + * + * Checks if the given pin can be registered to given DPLL. For both + * directions the pin can be registered if it is enabled. In case of + * differential signal type only P-pin is reported as registrable. + * And additionally for the output pin, the pin can be registered only + * if it is connected to synthesizer that is driven by given DPLL. + * + * Return: true if the pin is registrable, false if not + */ +static bool +zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, + enum dpll_pin_direction dir, u8 index) +{ + struct zl3073x_dev *zldev = zldpll->dev; + bool is_diff, is_enabled; + const char *name; + + if (dir == DPLL_PIN_DIRECTION_INPUT) { + u8 ref_id = zl3073x_input_pin_ref_get(index); + const struct zl3073x_ref *ref; + + name = "REF"; + ref = zl3073x_ref_state_get(zldev, ref_id); + is_diff = zl3073x_ref_is_diff(ref); + is_enabled = zl3073x_ref_is_enabled(ref); + } else { + /* Output P&N pair shares single HW output */ + u8 out = zl3073x_output_pin_out_get(index); + + /* Skip the pin if it is connected to different DPLL channel */ + if (zl3073x_dev_out_dpll_get(zldev, out) != zldpll->id) { + dev_dbg(zldev->dev, + "OUT%u is driven by different DPLL\n", out); + + return false; + } + + name = "OUT"; + is_diff = zl3073x_dev_out_is_diff(zldev, out); + is_enabled = zl3073x_dev_output_pin_is_enabled(zldev, index); + } + + /* Skip N-pin if the corresponding input/output is differential */ + if (is_diff && zl3073x_is_n_pin(index)) { + dev_dbg(zldev->dev, "%s%u is differential, skipping N-pin\n", + name, index / 2); + + return false; + } + + /* Skip the pin if it is disabled */ + if (!is_enabled) { + dev_dbg(zldev->dev, "%s%u%c is disabled\n", name, index / 2, + zl3073x_is_p_pin(index) ? 'P' : 'N'); + + return false; + } + + 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 + * + * Enumerates all possible input/output pins and registers all of them + * that are registrable. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + enum dpll_pin_direction dir; + u8 id, index; + int rc; + + /* Process input pins */ + for (index = 0; index < ZL3073X_NUM_PINS; index++) { + /* First input pins and then output pins */ + if (index < ZL3073X_NUM_INPUT_PINS) { + id = index; + dir = DPLL_PIN_DIRECTION_INPUT; + } else { + id = index - ZL3073X_NUM_INPUT_PINS; + dir = DPLL_PIN_DIRECTION_OUTPUT; + } + + /* Check if the pin registrable to this DPLL */ + if (!zl3073x_dpll_pin_is_registrable(zldpll, dir, id)) + continue; + + pin = zl3073x_dpll_pin_alloc(zldpll, dir, id); + if (IS_ERR(pin)) { + rc = PTR_ERR(pin); + goto error; + } + + rc = zl3073x_dpll_pin_register(pin, index); + if (rc) { + zl3073x_dpll_pin_free(pin); + goto error; + } + + 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: + zl3073x_dpll_pins_unregister(zldpll); + + return rc; +} + +/** + * zl3073x_dpll_device_register - register DPLL device + * @zldpll: pointer to zl3073x_dpll structure + * + * Registers given DPLL device into DPLL sub-system. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dev *zldev = zldpll->dev; + int rc; + + zldpll->ops = zl3073x_dpll_device_ops; + if (zldev->info->flags & ZL3073X_FLAG_DIE_TEMP) + zldpll->ops.temp_get = zl3073x_dpll_temp_get; + + zldpll->dpll_dev = dpll_device_get(zldev->clock_id, zldpll->id, + THIS_MODULE, &zldpll->tracker); + if (IS_ERR(zldpll->dpll_dev)) { + rc = PTR_ERR(zldpll->dpll_dev); + zldpll->dpll_dev = NULL; + + return rc; + } + + 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); + zldpll->dpll_dev = NULL; + } + + return rc; +} + +/** + * zl3073x_dpll_device_unregister - unregister DPLL device + * @zldpll: pointer to zl3073x_dpll structure + * + * Unregisters given DPLL device from DPLL sub-system previously registered + * by @zl3073x_dpll_device_register. + */ +static void +zl3073x_dpll_device_unregister(struct zl3073x_dpll *zldpll) +{ + struct dpll_device *dpll_dev = READ_ONCE(zldpll->dpll_dev); + + WARN(!dpll_dev, "DPLL device is not registered\n"); + + WRITE_ONCE(zldpll->dpll_dev, NULL); + dpll_device_unregister(dpll_dev, &zldpll->ops, zldpll); + dpll_device_put(dpll_dev, &zldpll->tracker); +} + +/** + * zl3073x_dpll_pin_phase_offset_check - check for pin phase offset change + * @pin: pin to check + * + * Check for the change of DPLL to connected pin phase offset change. + * + * Return: true on phase offset change, false otherwise + */ +static bool +zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + unsigned int reg; + s64 phase_offset; + 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)) + return false; + + /* Select register to read phase offset value depending on pin and + * phase monitor state: + * 1) For connected pin use dpll_phase_err_data register + * 2) For other pins use appropriate ref_phase register if the phase + * monitor feature is enabled. + */ + if (pin->operstate == DPLL_PIN_OPERSTATE_ACTIVE) + reg = ZL_REG_DPLL_PHASE_ERR_DATA(zldpll->id); + else if (zldpll->phase_monitor) + reg = ZL_REG_REF_PHASE(ref_id); + else + return false; + + /* Read measured phase offset value */ + rc = zl3073x_read_u48(zldev, reg, &phase_offset); + if (rc) { + dev_err(zldev->dev, "Failed to read ref phase offset: %pe\n", + ERR_PTR(rc)); + + return false; + } + + /* Convert to ps */ + phase_offset = div_s64(sign_extend64(phase_offset, 47), 100); + + /* Compare with previous value */ + if (phase_offset != pin->phase_offset) { + dev_dbg(zldev->dev, "%s phase offset changed: %lld -> %lld\n", + pin->label, pin->phase_offset, phase_offset); + pin->phase_offset = phase_offset; + + return true; + } + + return false; +} + +/** + * zl3073x_dpll_pin_ffo_check - check for FFO change on active pin + * @pin: pin to check + * + * Return: true on change, false otherwise + */ +static bool +zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + 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 (pin->freq_offset != ffo) { + dev_dbg(zldev->dev, "%s freq offset changed to: %lld\n", + pin->label, ffo); + pin->freq_offset = ffo; + return true; + } + + return false; +} + +/** + * zl3073x_dpll_pin_measured_freq_check - check for pin measured frequency + * change + * @pin: pin to check + * + * Check for the given pin's measured frequency change. + * + * Return: true on measured frequency change, false otherwise + */ +static bool +zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_ref *ref; + u8 ref_id; + u32 freq; + + lockdep_assert_held(&zldpll->lock); + + if (!READ_ONCE(zldpll->dev->freq_monitor)) + return false; + + ref_id = zl3073x_input_pin_ref_get(pin->id); + ref = zl3073x_ref_state_get(zldev, ref_id); + + freq = zl3073x_ref_meas_freq_get(ref); + if (pin->measured_freq != freq) { + dev_dbg(zldev->dev, "%s measured freq changed: %u -> %u\n", + pin->label, pin->measured_freq, freq); + pin->measured_freq = freq; + + return true; + } + + return false; +} + +/** + * zl3073x_dpll_changes_check - check for changes and send notifications + * @zldpll: pointer to zl3073x_dpll structure + * + * Checks for changes on given DPLL device and its registered DPLL pins + * and sends notifications about them. + * + * This function is periodically called from @zl3073x_dev_periodic_work. + */ +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++; + + 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)); + goto unlock; + } + + if (zldpll->lock_status != lock_status) { + zldpll->lock_status = lock_status; + dev_changed = true; + } + + /* Update phase offset latch registers for this DPLL if the phase + * offset monitor feature is enabled. + */ + if (zldpll->phase_monitor) { + rc = zl3073x_ref_phase_offsets_update(zldev, zldpll->id); + if (rc) { + dev_err(zldev->dev, + "Failed to update phase offsets: %pe\n", + ERR_PTR(rc)); + goto unlock; + } + } + + list_for_each_entry(pin, &zldpll->pins, list) { + enum dpll_pin_operstate operstate; + + /* 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); + if (rc) { + dev_err(dev, + "Failed to get %s on DPLL%u oper state: %pe\n", + pin->label, zldpll->id, ERR_PTR(rc)); + goto unlock; + } + + if (operstate != pin->operstate) { + dev_dbg(dev, "%s oper state changed: %u->%u\n", + pin->label, pin->operstate, operstate); + pin->operstate = operstate; + set_bit(pin->id, changed_pins); + } + + if (zldpll->check_count % 2 == 0) { + if (zl3073x_dpll_pin_phase_offset_check(pin)) + set_bit(pin->id, changed_pins); + + if (zl3073x_dpll_pin_ffo_check(pin)) + set_bit(pin->id, changed_pins); + + if (zl3073x_dpll_pin_measured_freq_check(pin)) + set_bit(pin->id, changed_pins); + } + } + +unlock: + mutex_unlock(&zldpll->lock); + + /* 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); + } +} + +/** + * zl3073x_dpll_init_fine_phase_adjust - do initial fine phase adjustments + * @zldev: pointer to zl3073x device + * + * Performs initial fine phase adjustments needed per datasheet. + * + * Return: 0 on success, <0 on error + */ +int +zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev) +{ + int rc; + + rc = zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_MASK, 0x1f); + if (rc) + return rc; + + rc = zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_INTVL, 0x01); + if (rc) + return rc; + + rc = zl3073x_write_u16(zldev, ZL_REG_SYNTH_PHASE_SHIFT_DATA, 0xffff); + if (rc) + return rc; + + return zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_CTRL, 0x01); +} + +/* Maximum frequency adjustment: +-1% of nominal in ppb */ +#define ZL3073X_DPLL_PTP_MAX_ADJ 10000000 + +/** + * zl3073x_dpll_ptp_gettimex64 - read current time from ToD counters + * @info: PTP clock info + * @ts: timespec to store current time + * @sts: optional system timestamp pair for cross-timestamping + * + * Return: 0 on success, <0 on error + */ +static int zl3073x_dpll_ptp_gettimex64(struct ptp_clock_info *info, + struct timespec64 *ts, + struct ptp_system_timestamp *sts) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + + guard(mutex)(&zldpll->lock); + + return zl3073x_chan_tod_read(zldpll->dev, zldpll->id, false, ts, sts); +} + +/** + * zl3073x_dpll_ptp_settime64 - set ToD counters to given time + * @info: PTP clock info + * @ts: timespec with time to set + * + * Return: 0 on success, <0 on error + */ +static int zl3073x_dpll_ptp_settime64(struct ptp_clock_info *info, + const struct timespec64 *ts) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + + guard(mutex)(&zldpll->lock); + + return zl3073x_chan_tod_write(zldpll->dev, zldpll->id, *ts); +} + +/** + * zl3073x_dpll_ptp_adjtime_phase_step - adjust sub-second time via phase step + * @zldpll: DPLL channel + * @delta: time adjustment in nanoseconds (must be within (-NSEC_PER_SEC, + * NSEC_PER_SEC)) + * + * Uses the output phase step mechanism with tod_step=1 to adjust both + * the output clock phase and the ToD counter simultaneously. This keeps + * outputs and ToD coherent. Only valid for NCO. + * + * Outputs are grouped by synthesizer since the phase step value is in + * synthesizer clock cycles. The first synth group with enabled outputs + * uses tod_step to adjust both outputs and the ToD counter. Remaining + * groups step outputs only. If no synth has enabled outputs, the ToD + * counter is stepped alone using an empty output mask (the FW uses + * the first enabled synth's period for the conversion). + * + * Return: + * * %0 - success (or partial success if a later synth group + * failed after the first was already stepped) + * * %-EOPNOTSUPP - no synths available + * * negative - error + */ +static int zl3073x_dpll_ptp_adjtime_phase_step(struct zl3073x_dpll *zldpll, + s64 delta) +{ + u16 synth_mask[ZL3073X_NUM_SYNTHS] = {}; + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_synth *synth; + struct zl3073x_dpll_pin *pin; + u32 first_synth_freq = 0; + bool tod_stepped = false; + s32 step_cycles; + u32 synth_freq; + int rc; + u8 i; + + /* Build per-synth output masks from registered output pins */ + list_for_each_entry(pin, &zldpll->pins, list) { + u8 out_id, synth_id; + + if (zl3073x_dpll_is_input_pin(pin)) + continue; + + out_id = zl3073x_output_pin_out_get(pin->id); + + if (!zl3073x_dev_out_is_stepped(zldev, out_id)) + continue; + + synth_id = zl3073x_dev_out_synth_get(zldev, out_id); + if (synth_id >= ZL3073X_NUM_SYNTHS) { + dev_warn(zldev->dev, "Unexpected synth id for OUT%u\n", + out_id); + continue; + } + synth_mask[synth_id] |= BIT(out_id); + } + + /* Process each synth group */ + for (i = 0; i < ZL3073X_NUM_SYNTHS; i++) { + synth = zl3073x_synth_state_get(zldev, i); + if (!zl3073x_synth_is_enabled(synth) || + zl3073x_synth_dpll_get(synth) != zldpll->id) + continue; + + synth_freq = zl3073x_synth_freq_get(synth); + + /* Remember first enabled synth freq for ToD-only fallback */ + if (!first_synth_freq) + first_synth_freq = synth_freq; + + if (!synth_mask[i]) + continue; + + /* Safe for s32: max synth freq is 750 MHz */ + step_cycles = div_s64(delta * synth_freq, NSEC_PER_SEC); + + rc = zl3073x_chan_phase_step(zldev, zldpll->id, + synth_mask[i], step_cycles, + !tod_stepped); + if (rc) { + if (tod_stepped) { + dev_warn(zldev->dev, + "Partial phase step failure\n"); + return 0; + } + return rc; + } + tod_stepped = true; + } + + if (!first_synth_freq) + return -EOPNOTSUPP; + + /* No enabled outputs found; step ToD counter only using the + * first enabled synth's period (empty output mask). + */ + if (!tod_stepped) { + step_cycles = div_s64(delta * first_synth_freq, NSEC_PER_SEC); + return zl3073x_chan_phase_step(zldev, zldpll->id, 0, + step_cycles, true); + } + + return 0; +} + +/** + * zl3073x_dpll_ptp_adjtime - adjust PTP clock time + * @info: PTP clock info + * @delta: time adjustment in nanoseconds + * + * For NCO, large deltas (>= 1 second) are split into a ToD + * read-modify-write for the seconds part and an output phase step for + * the sub-second remainder. Sub-second deltas use phase step directly, + * falling back to ToD read-modify-write if phase step or TIE write + * fails. In AUTO/REFLOCK modes, large deltas are split into ToD + * read-modify-write for seconds and TIE write for the sub-second + * remainder. Sub-second deltas use TIE write directly. + * + * If the seconds part was already committed when the sub-second + * mechanism fails, returns 0 to prevent the PTP servo from retrying + * the full delta and applying seconds again. + * + * Return: 0 on success (or partial success), <0 on error + */ +static int zl3073x_dpll_ptp_adjtime(struct ptp_clock_info *info, s64 delta) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_chan *chan; + bool sec_adjusted = false; + struct timespec64 ts; + int rc; + + if (!delta) + return 0; + + guard(mutex)(&zldpll->lock); + + /* Modes without phase step or TIE use plain ToD adjust */ + chan = zl3073x_chan_state_get(zldev, zldpll->id); + if (!zl3073x_chan_mode_is_nco(chan) && + !zl3073x_chan_mode_supports_tie(chan)) + return zl3073x_chan_tod_adjust(zldev, zldpll->id, + ns_to_timespec64(delta)); + + /* Split off seconds via ToD read-modify-write so the sub-second + * remainder can be applied through the output-coherent mechanism + * (phase step or TIE write). + */ + if (delta >= NSEC_PER_SEC || delta <= -NSEC_PER_SEC) { + s32 remainder; + + ts.tv_sec = div_s64_rem(delta, NSEC_PER_SEC, &remainder); + ts.tv_nsec = 0; + delta = remainder; + + rc = zl3073x_chan_tod_adjust(zldev, zldpll->id, ts); + if (rc) + return rc; + + /* No sub-second remainder, done */ + if (!delta) + return 0; + + /* Wait for the ToD write to be applied at the 1 Hz edge + * before issuing phase step or TIE write, so the pending + * WR_NEXT_1HZ does not overwrite the sub-second adjustment. + */ + rc = zl3073x_chan_tod_ready_wait(zldev, zldpll->id); + if (rc) + return rc; + + sec_adjusted = true; + } + + /* Apply sub-second delta via phase step (NCO) or TIE write */ + if (zl3073x_chan_mode_is_nco(chan)) { + rc = zl3073x_dpll_ptp_adjtime_phase_step(zldpll, delta); + if (!rc) + return 0; + } else { + rc = zl3073x_chan_tie_write(zldev, zldpll->id, delta); + if (!rc) + return 0; + } + + /* Phase step or TIE write failed, fall back to ToD adjust */ + rc = zl3073x_chan_tod_adjust(zldev, zldpll->id, + ns_to_timespec64(delta)); + + /* In the unlikely event that both phase step/TIE write and fallback + * ToD adjust fail after seconds were already committed, return + * success to prevent the PTP servo from retrying the full delta and + * applying seconds again. The sub-second residual will self-correct + * in the next servo cycle. + */ + if (rc && sec_adjusted) { + dev_warn(zldev->dev, + "Sub-second adjustment failed after seconds applied\n"); + return 0; + } + + return rc; +} + +/** + * zl3073x_dpll_ptp_adjfine - adjust PTP clock frequency + * @info: PTP clock info + * @scaled_ppm: frequency adjustment in scaled ppm (ppm * 2^16) + * + * Only supported for NCO. Writes the delta frequency offset register. + * + * Return: + * * %0 - success or @scaled_ppm is zero (no-op) + * * %-EOPNOTSUPP - NCO pin is not connected and @scaled_ppm is non-zero + * * negative - other error + */ +static int +zl3073x_dpll_ptp_adjfine(struct ptp_clock_info *info, long scaled_ppm) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + const struct zl3073x_chan *chan; + s64 offset; + + /* Convert scaled_ppm to df_offset in 2^-48 steps: + * df_offset = -(scaled_ppm * 2^32) / 10^6 + * + * Simplify to avoid overflow: + * df_offset = -(scaled_ppm * 2^26) / 5^6 + * df_offset = -(scaled_ppm * 67108864) / 15625 + */ + offset = -div_s64((s64)scaled_ppm * 67108864LL, 15625); + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (!zl3073x_chan_mode_is_nco(chan)) + return scaled_ppm ? -EOPNOTSUPP : 0; + if (offset == chan->df_offset) + return 0; + + return zl3073x_chan_df_offset_set(zldpll->dev, zldpll->id, offset); +} + +/** + * zl3073x_dpll_ptp_adjphase - adjust PTP clock phase + * @info: PTP clock info + * @delta: phase adjustment in nanoseconds + * + * Only supported in AUTO and REFLOCK modes. Uses TIE write for + * nanosecond resolution phase adjustment. + * + * Return: + * * %0 - success or @delta is zero (no-op) + * * %-EOPNOTSUPP - mode does not support TIE and @delta is non-zero + * * negative - other error + */ +static int zl3073x_dpll_ptp_adjphase(struct ptp_clock_info *info, s32 delta) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_chan *chan; + + if (!delta) + return 0; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldev, zldpll->id); + + if (!zl3073x_chan_mode_supports_tie(chan)) + return -EOPNOTSUPP; + + return zl3073x_chan_tie_write(zldev, zldpll->id, delta); +} + +static s32 +zl3073x_dpll_ptp_getmaxphase(struct ptp_clock_info *info __always_unused) +{ + /* HW limits TIE write to +-1 second. Return the constant HW + * limit and let adjphase handle mode-specific checks. + */ + return NSEC_PER_SEC - 1; +} + +static const struct ptp_clock_info zl3073x_dpll_ptp_clock_info = { + .owner = THIS_MODULE, + .max_adj = ZL3073X_DPLL_PTP_MAX_ADJ, + .gettimex64 = zl3073x_dpll_ptp_gettimex64, + .settime64 = zl3073x_dpll_ptp_settime64, + .adjtime = zl3073x_dpll_ptp_adjtime, + .adjfine = zl3073x_dpll_ptp_adjfine, + .adjphase = zl3073x_dpll_ptp_adjphase, + .getmaxphase = zl3073x_dpll_ptp_getmaxphase, +}; + +/** + * zl3073x_dpll_ptp_register - register PTP clock for a DPLL channel + * @zldpll: DPLL channel to register PTP clock for + * + * Return: 0 on success, <0 on error + */ +static int zl3073x_dpll_ptp_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dev *zldev = zldpll->dev; + struct ptp_clock *ptp_clock; + + zldpll->ptp_info = zl3073x_dpll_ptp_clock_info; + snprintf(zldpll->ptp_info.name, sizeof(zldpll->ptp_info.name), + "%s-dpll%u", dev_name(zldev->dev), zldpll->id); + + ptp_clock = ptp_clock_register(&zldpll->ptp_info, zldev->dev); + if (IS_ERR(ptp_clock)) { + dev_err(zldev->dev, "Failed to register PTP clock for DPLL%u\n", + zldpll->id); + return PTR_ERR(ptp_clock); + } + + zldpll->ptp_clock = ptp_clock; + + return 0; +} + +/** + * zl3073x_dpll_ptp_unregister - unregister PTP clock for a DPLL channel + * @zldpll: DPLL channel to unregister PTP clock for + */ +static void zl3073x_dpll_ptp_unregister(struct zl3073x_dpll *zldpll) +{ + if (!IS_ERR_OR_NULL(zldpll->ptp_clock)) { + ptp_clock_unregister(zldpll->ptp_clock); + zldpll->ptp_clock = NULL; + } +} + +/** + * zl3073x_dpll_ref_sync_pair_register - register ref_sync pairs for a pin + * @pin: pointer to zl3073x_dpll_pin structure + * + * Iterates 'ref-sync-sources' phandles in the pin's firmware node and + * registers each declared pairing. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_ref_sync_pair_register(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dev *zldev = pin->dpll->dev; + struct fwnode_handle *fwnode; + struct dpll_pin *sync_pin; + dpll_tracker tracker; + int n, rc; + + for (n = 0; ; n++) { + /* Get n'th ref-sync source */ + fwnode = fwnode_find_reference(pin->fwnode, "ref-sync-sources", + n); + if (IS_ERR(fwnode)) { + rc = PTR_ERR(fwnode); + break; + } + + /* Find associated dpll pin */ + sync_pin = fwnode_dpll_pin_find(fwnode, &tracker); + fwnode_handle_put(fwnode); + if (!sync_pin) { + dev_warn(zldev->dev, "%s: ref-sync source %d not found", + pin->label, n); + continue; + } + + /* Register new ref-sync pair */ + rc = dpll_pin_ref_sync_pair_add(pin->dpll_pin, sync_pin); + dpll_pin_put(sync_pin, &tracker); + + /* -EBUSY means pairing already exists from another DPLL's + * registration. + */ + if (rc && rc != -EBUSY) { + dev_err(zldev->dev, + "%s: failed to add ref-sync source %d: %pe", + pin->label, n, ERR_PTR(rc)); + break; + } + } + + return rc != -ENOENT ? rc : 0; +} + +/** + * zl3073x_dpll_ref_sync_pairs_register - register ref_sync pairs for a DPLL + * @zldpll: pointer to zl3073x_dpll structure + * + * Iterates all registered input pins of the given DPLL and establishes + * ref_sync pairings declared by 'ref-sync-sources' phandles in the + * device tree. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + int rc; + + list_for_each_entry(pin, &zldpll->pins, list) { + if (!zl3073x_dpll_is_input_pin(pin) || !pin->fwnode) + continue; + + rc = zl3073x_dpll_ref_sync_pair_register(pin); + if (rc) + return rc; + } + + return 0; +} + +/** + * zl3073x_dpll_alloc - allocate DPLL device + * @zldev: pointer to zl3073x device + * @ch: DPLL channel number + * + * Allocates DPLL device structure for given DPLL channel. + * + * Return: pointer to DPLL device on success, error pointer on error + */ +struct zl3073x_dpll * +zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch) +{ + struct zl3073x_dpll *zldpll; + + zldpll = kzalloc_obj(*zldpll); + if (!zldpll) + return ERR_PTR(-ENOMEM); + + zldpll->dev = zldev; + zldpll->id = ch; + mutex_init(&zldpll->lock); + INIT_LIST_HEAD(&zldpll->pins); + + return zldpll; +} + +/** + * zl3073x_dpll_free - free DPLL device + * @zldpll: pointer to zl3073x_dpll structure + * + * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc. + */ +void +zl3073x_dpll_free(struct zl3073x_dpll *zldpll) +{ + WARN(zldpll->dpll_dev, "DPLL device is still registered\n"); + + mutex_destroy(&zldpll->lock); + kfree(zldpll); +} + +/** + * zl3073x_dpll_register - register DPLL device and all its pins + * @zldpll: pointer to zl3073x_dpll structure + * + * Registers given DPLL device and all its pins into DPLL sub-system. + * + * Return: 0 on success, <0 on error + */ +int +zl3073x_dpll_register(struct zl3073x_dpll *zldpll) +{ + int rc; + + rc = zl3073x_dpll_device_register(zldpll); + if (rc) + return rc; + + rc = zl3073x_dpll_pins_register(zldpll); + if (rc) { + zl3073x_dpll_device_unregister(zldpll); + return rc; + } + + rc = zl3073x_dpll_ref_sync_pairs_register(zldpll); + if (rc) { + zl3073x_dpll_pins_unregister(zldpll); + zl3073x_dpll_device_unregister(zldpll); + return rc; + } + + rc = zl3073x_dpll_ptp_register(zldpll); + if (rc) { + zl3073x_dpll_pins_unregister(zldpll); + zl3073x_dpll_device_unregister(zldpll); + return rc; + } + + return 0; +} + +/** + * zl3073x_dpll_unregister - unregister DPLL device and its pins + * @zldpll: pointer to zl3073x_dpll structure + * + * Unregisters given DPLL device and all its pins from DPLL sub-system + * previously registered by @zl3073x_dpll_register. + */ +void +zl3073x_dpll_unregister(struct zl3073x_dpll *zldpll) +{ + zl3073x_dpll_ptp_unregister(zldpll); + zl3073x_dpll_pins_unregister(zldpll); + zl3073x_dpll_device_unregister(zldpll); +} |
