root/drivers/dpll/zl3073x/dpll.c
// SPDX-License-Identifier: GPL-2.0-only

#include <linux/atomic.h>
#include <linux/bits.h>
#include <linux/bitfield.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/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;
        atomic64_t              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_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 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;

        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;

        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;

        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;
        int rc;

        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");
                        return -EINVAL;
                }
                if (ref_freq < 1000) {
                        NL_SET_ERR_MSG(extack,
                                       "clock frequency must be 1 kHz or more");
                        return -EINVAL;
                }
                if (ref_freq <= sync_freq) {
                        NL_SET_ERR_MSG(extack,
                                       "clock frequency must be higher than sync frequency");
                        return -EINVAL;
                }

                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)
                return rc;

        /* Exclude sync source from automatic reference selection by setting
         * its priority to NONE. On disconnect the priority is left as NONE
         * and the user must explicitly make the pin selectable again.
         */
        if (state == DPLL_PIN_STATE_CONNECTED) {
                chan = *zl3073x_chan_state_get(zldev, zldpll->id);
                zl3073x_chan_ref_prio_set(&chan, sync_ref_id,
                                          ZL_DPLL_REF_PRIO_NONE);
                return zl3073x_chan_state_set(zldev, zldpll->id, &chan);
        }

        return 0;
}

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_pin *pin = pin_priv;

        if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
                return -ENODATA;

        ffo->ffo = atomic64_read(&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_pin *pin = pin_priv;

        *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;

        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;

        /* 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;

        /* 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;

        /* 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;

        /* 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;

        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;

        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_pin *pin = pin_priv;

        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_chan chan;
        u8 mode, ref;
        int rc;

        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_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))
                                return 0; /* 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 */

                        /* 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");
                return -EOPNOTSUPP;
        }

        /* Commit DPLL channel changes */
        rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
        if (rc)
                return rc;

        return 0;
invalid_state:
        NL_SET_ERR_MSG_MOD(extack, "Invalid pin state for this device mode");
        return -EINVAL;
}

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_pin *pin = pin_priv;

        *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;

        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;

        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;

        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)
                goto write_mailbox;

        /* 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;

write_mailbox:
        /* 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;

        *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;

        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;

        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;

        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_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;

        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(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);

        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_supported_modes_get(const struct dpll_device *dpll,
                                 void *dpll_priv, unsigned long *modes,
                                 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);

        /* 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);

        __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;

        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_chan chan;
        u8 hw_mode, ref;
        int rc;

        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 {
                /* 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");
                return rc;
        }

        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;

        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;

        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 (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;

        zldpll->dev->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) {
                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_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_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;
        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);
                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;
}

/**
 * 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);
        }

        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;
        }

        rc = dpll_device_register(zldpll->dpll_dev,
                                  zl3073x_prop_dpll_type_get(zldev, zldpll->id),
                                  &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;

        /* 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;

        if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
                return false;

        chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
        ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan),
                              244140625, 36);

        if (atomic64_xchg(&pin->freq_offset, ffo) != ffo) {
                dev_dbg(zldev->dev, "%s freq offset changed to: %lld\n",
                        pin->label, 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;

        if (!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)
{
        struct zl3073x_dev *zldev = zldpll->dev;
        enum dpll_lock_status lock_status;
        struct device *dev = zldev->dev;
        struct zl3073x_dpll_pin *pin;
        int rc;

        zldpll->check_count++;

        /* Get current lock status for the DPLL */
        rc = zl3073x_dpll_lock_status_get(zldpll->dpll_dev, zldpll,
                                          &lock_status, NULL, NULL);
        if (rc) {
                dev_err(dev, "Failed to get DPLL%u lock status: %pe\n",
                        zldpll->id, ERR_PTR(rc));
                return;
        }

        /* 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);
        }

        /* 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));
                        return;
                }
        }

        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))
                        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));
                        return;
                }

                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;
                }

                /* 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;

                        if (zl3073x_dpll_pin_ffo_check(pin))
                                pin_changed = true;

                        if (zl3073x_dpll_pin_measured_freq_check(pin))
                                pin_changed = true;
                }

                if (pin_changed)
                        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;

        rc = zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_CTRL, 0x01);
        if (rc)
                return rc;

        return rc;
}

/**
 * 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;
        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");

        kfree(zldpll);
}

/**
 * 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_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;
        }

        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)
{
        /* Unregister all pins and dpll */
        zl3073x_dpll_pins_unregister(zldpll);
        zl3073x_dpll_device_unregister(zldpll);
}