diff options
Diffstat (limited to 'drivers/dpll/zl3073x/dpll.c')
-rw-r--r-- | drivers/dpll/zl3073x/dpll.c | 2318 |
1 files changed, 2318 insertions, 0 deletions
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c new file mode 100644 index 000000000000..3e42e9e7fd27 --- /dev/null +++ b/drivers/dpll/zl3073x/dpll.c @@ -0,0 +1,2318 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#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/mod_devicetable.h> +#include <linux/module.h> +#include <linux/netlink.h> +#include <linux/platform_device.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 + * @label: package label + * @dir: pin direction + * @id: pin id + * @prio: pin priority <0, 14> + * @selectable: pin is selectable in automatic mode + * @esync_control: embedded sync is controllable + * @pin_state: last saved pin state + * @phase_offset: last saved pin phase offset + * @freq_offset: last saved fractional frequency offset + */ +struct zl3073x_dpll_pin { + struct list_head list; + struct zl3073x_dpll *dpll; + struct dpll_pin *dpll_pin; + char label[8]; + enum dpll_pin_direction dir; + u8 id; + u8 prio; + bool selectable; + bool esync_control; + enum dpll_pin_state pin_state; + s64 phase_offset; + s64 freq_offset; +}; + +/* + * 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; +} + +/** + * zl3073x_dpll_input_ref_frequency_get - get input reference frequency + * @zldpll: pointer to zl3073x_dpll + * @ref_id: reference id + * @frequency: pointer to variable to store frequency + * + * Reads frequency of given input reference. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_input_ref_frequency_get(struct zl3073x_dpll *zldpll, u8 ref_id, + u32 *frequency) +{ + struct zl3073x_dev *zldev = zldpll->dev; + u16 base, mult, num, denom; + int rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Read reference configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(ref_id)); + if (rc) + return rc; + + /* Read registers to compute resulting frequency */ + rc = zl3073x_read_u16(zldev, ZL_REG_REF_FREQ_BASE, &base); + if (rc) + return rc; + rc = zl3073x_read_u16(zldev, ZL_REG_REF_FREQ_MULT, &mult); + if (rc) + return rc; + rc = zl3073x_read_u16(zldev, ZL_REG_REF_RATIO_M, &num); + if (rc) + return rc; + rc = zl3073x_read_u16(zldev, ZL_REG_REF_RATIO_N, &denom); + if (rc) + return rc; + + /* Sanity check that HW has not returned zero denominator */ + if (!denom) { + dev_err(zldev->dev, + "Zero divisor for ref %u frequency got from device\n", + ref_id); + return -EINVAL; + } + + /* Compute the frequency */ + *frequency = mul_u64_u32_div(base * mult, num, denom); + + return rc; +} + +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; + u8 ref, ref_sync_ctrl, sync_mode; + u32 esync_div, ref_freq; + int rc; + + /* Get reference frequency */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_dpll_input_ref_frequency_get(zldpll, pin->id, &ref_freq); + if (rc) + return rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Read reference configuration into mailbox */ + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(ref)); + if (rc) + return rc; + + /* Get ref sync mode */ + rc = zl3073x_read_u8(zldev, ZL_REG_REF_SYNC_CTRL, &ref_sync_ctrl); + if (rc) + return rc; + + /* Get esync divisor */ + rc = zl3073x_read_u32(zldev, ZL_REG_REF_ESYNC_DIV, &esync_div); + if (rc) + return rc; + + sync_mode = FIELD_GET(ZL_REF_SYNC_CTRL_MODE, ref_sync_ctrl); + + switch (sync_mode) { + case ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75: + esync->freq = (esync_div == ZL_REF_ESYNC_DIV_1HZ) ? 1 : 0; + esync->pulse = 25; + break; + default: + esync->freq = 0; + esync->pulse = 0; + break; + } + + /* If the pin supports esync control expose its range but only + * if the current reference frequency is > 1 Hz. + */ + if (pin->esync_control && ref_freq > 1) { + esync->range = esync_freq_ranges; + esync->range_num = ARRAY_SIZE(esync_freq_ranges); + } else { + esync->range = NULL; + esync->range_num = 0; + } + + return rc; +} + +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; + u8 ref, ref_sync_ctrl, sync_mode; + int rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Read reference configuration into mailbox */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(ref)); + if (rc) + return rc; + + /* Get ref sync mode */ + rc = zl3073x_read_u8(zldev, ZL_REG_REF_SYNC_CTRL, &ref_sync_ctrl); + if (rc) + return rc; + + /* 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; + + ref_sync_ctrl &= ~ZL_REF_SYNC_CTRL_MODE; + ref_sync_ctrl |= FIELD_PREP(ZL_REF_SYNC_CTRL_MODE, sync_mode); + + /* Update ref sync control register */ + rc = zl3073x_write_u8(zldev, ZL_REG_REF_SYNC_CTRL, ref_sync_ctrl); + if (rc) + return rc; + + if (freq) { + /* 1 Hz is only supported frequnecy currently */ + rc = zl3073x_write_u32(zldev, ZL_REG_REF_ESYNC_DIV, + ZL_REF_ESYNC_DIV_1HZ); + if (rc) + return rc; + } + + /* Commit reference configuration */ + return zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR, + ZL_REG_REF_MB_MASK, BIT(ref)); +} + +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, + s64 *ffo, struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll_pin *pin = pin_priv; + + *ffo = pin->freq_offset; + + 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; + u32 ref_freq; + u8 ref; + int rc; + + /* Read and return ref frequency */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_dpll_input_ref_frequency_get(zldpll, ref, &ref_freq); + if (!rc) + *frequency = ref_freq; + + return rc; +} + +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; + u16 base, mult; + u8 ref; + int rc; + + /* Get base frequency and multiplier for the requested frequency */ + rc = zl3073x_ref_freq_factorize(frequency, &base, &mult); + if (rc) + return rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Load reference configuration */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(ref)); + + /* Update base frequency, multiplier, numerator & denominator */ + rc = zl3073x_write_u16(zldev, ZL_REG_REF_FREQ_BASE, base); + if (rc) + return rc; + rc = zl3073x_write_u16(zldev, ZL_REG_REF_FREQ_MULT, mult); + if (rc) + return rc; + rc = zl3073x_write_u16(zldev, ZL_REG_REF_RATIO_M, 1); + if (rc) + return rc; + rc = zl3073x_write_u16(zldev, ZL_REG_REF_RATIO_N, 1); + if (rc) + return rc; + + /* Commit reference configuration */ + return zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR, + ZL_REG_REF_MB_MASK, BIT(ref)); +} + +/** + * zl3073x_dpll_selected_ref_get - get currently selected reference + * @zldpll: pointer to zl3073x_dpll + * @ref: place to store selected reference + * + * Check for currently selected reference the DPLL should be locked to + * and stores its index to given @ref. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_selected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref) +{ + struct zl3073x_dev *zldev = zldpll->dev; + u8 state, value; + int rc; + + switch (zldpll->refsel_mode) { + case ZL_DPLL_MODE_REFSEL_MODE_AUTO: + /* For automatic mode read refsel_status register */ + rc = zl3073x_read_u8(zldev, + ZL_REG_DPLL_REFSEL_STATUS(zldpll->id), + &value); + if (rc) + return rc; + + /* Extract reference state */ + state = FIELD_GET(ZL_DPLL_REFSEL_STATUS_STATE, value); + + /* Return the reference only if the DPLL is locked to it */ + if (state == ZL_DPLL_REFSEL_STATUS_STATE_LOCK) + *ref = FIELD_GET(ZL_DPLL_REFSEL_STATUS_REFSEL, value); + else + *ref = ZL3073X_DPLL_REF_NONE; + break; + case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: + /* For manual mode return stored value */ + *ref = zldpll->forced_ref; + break; + default: + /* For other modes like NCO, freerun... there is no input ref */ + *ref = ZL3073X_DPLL_REF_NONE; + break; + } + + return 0; +} + +/** + * zl3073x_dpll_selected_ref_set - select reference in manual mode + * @zldpll: pointer to zl3073x_dpll + * @ref: input reference to be selected + * + * Selects the given reference for the DPLL channel it should be + * locked to. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_selected_ref_set(struct zl3073x_dpll *zldpll, u8 ref) +{ + struct zl3073x_dev *zldev = zldpll->dev; + u8 mode, mode_refsel; + int rc; + + mode = zldpll->refsel_mode; + + switch (mode) { + case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: + /* Manual mode with ref selected */ + if (ref == ZL3073X_DPLL_REF_NONE) { + switch (zldpll->lock_status) { + case DPLL_LOCK_STATUS_LOCKED_HO_ACQ: + case DPLL_LOCK_STATUS_HOLDOVER: + /* Switch to forced holdover */ + mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER; + break; + default: + /* Switch to freerun */ + mode = ZL_DPLL_MODE_REFSEL_MODE_FREERUN; + break; + } + /* Keep selected reference */ + ref = zldpll->forced_ref; + } else if (ref == zldpll->forced_ref) { + /* No register update - same mode and same ref */ + return 0; + } + break; + case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: + case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER: + /* Manual mode without no ref */ + if (ref == ZL3073X_DPLL_REF_NONE) + /* No register update - keep current mode */ + return 0; + + /* Switch to reflock mode and update ref selection */ + mode = ZL_DPLL_MODE_REFSEL_MODE_REFLOCK; + break; + default: + /* For other modes like automatic or NCO ref cannot be selected + * manually + */ + return -EOPNOTSUPP; + } + + /* Build mode_refsel value */ + mode_refsel = FIELD_PREP(ZL_DPLL_MODE_REFSEL_MODE, mode) | + FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref); + + /* Update dpll_mode_refsel register */ + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id), + mode_refsel); + if (rc) + return rc; + + /* Store new mode and forced reference */ + zldpll->refsel_mode = mode; + zldpll->forced_ref = ref; + + return rc; +} + +/** + * zl3073x_dpll_connected_ref_get - get currently connected reference + * @zldpll: pointer to zl3073x_dpll + * @ref: place to store selected reference + * + * Looks for currently connected the DPLL is locked to and stores its index + * to given @ref. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref) +{ + struct zl3073x_dev *zldev = zldpll->dev; + int rc; + + /* Get currently selected input reference */ + rc = zl3073x_dpll_selected_ref_get(zldpll, ref); + if (rc) + return rc; + + if (ZL3073X_DPLL_REF_IS_VALID(*ref)) { + u8 ref_status; + + /* Read the reference monitor status */ + rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(*ref), + &ref_status); + if (rc) + return rc; + + /* If the monitor indicates an error nothing is connected */ + if (ref_status != ZL_REF_MON_STATUS_OK) + *ref = ZL3073X_DPLL_REF_NONE; + } + + return 0; +} + +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; + u8 conn_ref, ref, ref_status; + s64 ref_phase; + int rc; + + /* Get currently connected reference */ + rc = zl3073x_dpll_connected_ref_get(zldpll, &conn_ref); + if (rc) + return rc; + + /* Report phase offset only for currently connected pin if the phase + * monitor feature is disabled. + */ + ref = zl3073x_input_pin_ref_get(pin->id); + if (!zldpll->phase_monitor && ref != conn_ref) { + *phase_offset = 0; + + return 0; + } + + /* Get this pin monitor status */ + rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref), &ref_status); + if (rc) + return rc; + + /* Report phase offset only if the input pin signal is present */ + if (ref_status != ZL_REF_MON_STATUS_OK) { + *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_ref) && conn_ref != ref) { + u32 conn_freq, ref_freq; + + /* Get frequency of connected ref */ + rc = zl3073x_dpll_input_ref_frequency_get(zldpll, conn_ref, + &conn_freq); + if (rc) + return rc; + + /* Get frequency of given ref */ + rc = zl3073x_dpll_input_ref_frequency_get(zldpll, ref, + &ref_freq); + if (rc) + return rc; + + 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 rc; +} + +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; + s64 phase_comp; + u8 ref; + int rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Read reference configuration */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(ref)); + if (rc) + return rc; + + /* Read current phase offset compensation */ + rc = zl3073x_read_u48(zldev, ZL_REG_REF_PHASE_OFFSET_COMP, &phase_comp); + if (rc) + return rc; + + /* Perform sign extension for 48bit signed value */ + phase_comp = sign_extend64(phase_comp, 47); + + /* Reverse two's complement negation applied during set and convert + * to 32bit signed int + */ + *phase_adjust = (s32)-phase_comp; + + return rc; +} + +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; + s64 phase_comp; + u8 ref; + int rc; + + /* The value in the register is stored as two's complement negation + * of requested value. + */ + phase_comp = -phase_adjust; + + guard(mutex)(&zldev->multiop_lock); + + /* Read reference configuration */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(ref)); + if (rc) + return rc; + + /* Write the requested value into the compensation register */ + rc = zl3073x_write_u48(zldev, ZL_REG_REF_PHASE_OFFSET_COMP, phase_comp); + if (rc) + return rc; + + /* Commit reference configuration */ + return zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR, + ZL_REG_REF_MB_MASK, BIT(ref)); +} + +/** + * zl3073x_dpll_ref_prio_get - get priority for given input pin + * @pin: pointer to pin + * @prio: place to store priority + * + * Reads current priority for the given input pin and stores the value + * to @prio. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_ref_prio_get(struct zl3073x_dpll_pin *pin, u8 *prio) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + u8 ref, ref_prio; + int rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Read DPLL configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD, + ZL_REG_DPLL_MB_MASK, BIT(zldpll->id)); + if (rc) + return rc; + + /* Read reference priority - one value for P&N pins (4 bits/pin) */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(ref / 2), + &ref_prio); + if (rc) + return rc; + + /* Select nibble according pin type */ + if (zl3073x_dpll_is_p_pin(pin)) + *prio = FIELD_GET(ZL_DPLL_REF_PRIO_REF_P, ref_prio); + else + *prio = FIELD_GET(ZL_DPLL_REF_PRIO_REF_N, ref_prio); + + return rc; +} + +/** + * zl3073x_dpll_ref_prio_set - set priority for given input pin + * @pin: pointer to pin + * @prio: place to store priority + * + * Sets priority for the given input pin. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_ref_prio_set(struct zl3073x_dpll_pin *pin, u8 prio) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + u8 ref, ref_prio; + int rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Read DPLL configuration into mailbox */ + rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD, + ZL_REG_DPLL_MB_MASK, BIT(zldpll->id)); + if (rc) + return rc; + + /* Read reference priority - one value shared between P&N pins */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(ref / 2), &ref_prio); + if (rc) + return rc; + + /* Update nibble according pin type */ + if (zl3073x_dpll_is_p_pin(pin)) { + ref_prio &= ~ZL_DPLL_REF_PRIO_REF_P; + ref_prio |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_P, prio); + } else { + ref_prio &= ~ZL_DPLL_REF_PRIO_REF_N; + ref_prio |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_N, prio); + } + + /* Update reference priority */ + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_REF_PRIO(ref / 2), ref_prio); + if (rc) + return rc; + + /* Commit configuration */ + return zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_WR, + ZL_REG_DPLL_MB_MASK, BIT(zldpll->id)); +} + +/** + * zl3073x_dpll_ref_state_get - get status for given input pin + * @pin: pointer to pin + * @state: place to store status + * + * Checks current status for the given input pin and stores the value + * to @state. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_ref_state_get(struct zl3073x_dpll_pin *pin, + enum dpll_pin_state *state) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + u8 ref, ref_conn, status; + int rc; + + ref = zl3073x_input_pin_ref_get(pin->id); + + /* Get currently connected reference */ + rc = zl3073x_dpll_connected_ref_get(zldpll, &ref_conn); + if (rc) + return rc; + + if (ref == ref_conn) { + *state = DPLL_PIN_STATE_CONNECTED; + return 0; + } + + /* If the DPLL is running in automatic mode and the reference is + * selectable and its monitor does not report any error then report + * pin as selectable. + */ + if (zldpll->refsel_mode == ZL_DPLL_MODE_REFSEL_MODE_AUTO && + pin->selectable) { + /* Read reference monitor status */ + rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref), + &status); + if (rc) + return rc; + + /* If the monitor indicates errors report the reference + * as disconnected + */ + if (status == ZL_REF_MON_STATUS_OK) { + *state = DPLL_PIN_STATE_SELECTABLE; + return 0; + } + } + + /* Otherwise report the pin as disconnected */ + *state = DPLL_PIN_STATE_DISCONNECTED; + + 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_pin *pin = pin_priv; + + return zl3073x_dpll_ref_state_get(pin, state); +} + +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; + u8 new_ref; + int rc; + + switch (zldpll->refsel_mode) { + case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: + 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 */ + new_ref = zl3073x_input_pin_ref_get(pin->id); + } else if (state == DPLL_PIN_STATE_DISCONNECTED) { + /* No reference */ + new_ref = ZL3073X_DPLL_REF_NONE; + } else { + NL_SET_ERR_MSG_MOD(extack, + "Invalid pin state for manual mode"); + return -EINVAL; + } + + rc = zl3073x_dpll_selected_ref_set(zldpll, new_ref); + break; + + case ZL_DPLL_MODE_REFSEL_MODE_AUTO: + if (state == DPLL_PIN_STATE_SELECTABLE) { + if (pin->selectable) + return 0; /* Pin is already selectable */ + + /* Restore pin priority in HW */ + rc = zl3073x_dpll_ref_prio_set(pin, pin->prio); + if (rc) + return rc; + + /* Mark pin as selectable */ + pin->selectable = true; + } else if (state == DPLL_PIN_STATE_DISCONNECTED) { + if (!pin->selectable) + return 0; /* Pin is already disconnected */ + + /* Set pin priority to none in HW */ + rc = zl3073x_dpll_ref_prio_set(pin, + ZL_DPLL_REF_PRIO_NONE); + if (rc) + return rc; + + /* Mark pin as non-selectable */ + pin->selectable = false; + } else { + NL_SET_ERR_MSG(extack, + "Invalid pin state for automatic mode"); + return -EINVAL; + } + 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; + break; + } + + 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_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_pin *pin = pin_priv; + int rc; + + if (prio > ZL_DPLL_REF_PRIO_MAX) + return -EINVAL; + + /* If the pin is selectable then update HW registers */ + if (pin->selectable) { + rc = zl3073x_dpll_ref_prio_set(pin, prio); + 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; + struct device *dev = zldev->dev; + u32 esync_period, esync_width; + u8 clock_type, synth; + u8 out, output_mode; + u32 output_div; + u32 synth_freq; + int rc; + + out = zl3073x_output_pin_out_get(pin->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. + */ + switch (zl3073x_out_signal_format_get(zldev, out)) { + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: + return -EOPNOTSUPP; + default: + break; + } + + guard(mutex)(&zldev->multiop_lock); + + /* Read output configuration into mailbox */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); + if (rc) + return rc; + + /* Read output mode */ + rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode); + if (rc) + return rc; + + /* Read output divisor */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div); + if (rc) + return rc; + + /* Check output divisor for zero */ + if (!output_div) { + dev_err(dev, "Zero divisor for OUTPUT%u got from device\n", + out); + return -EINVAL; + } + + /* Get synth attached to output pin */ + synth = zl3073x_out_synth_get(zldev, out); + + /* Get synth frequency */ + synth_freq = zl3073x_synth_freq_get(zldev, synth); + + clock_type = FIELD_GET(ZL_OUTPUT_MODE_CLOCK_TYPE, output_mode); + if (clock_type != ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC) { + /* No need to read esync data if it is not enabled */ + esync->freq = 0; + esync->pulse = 0; + + goto finish; + } + + /* Read esync period */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, &esync_period); + if (rc) + return rc; + + /* Check esync divisor for zero */ + if (!esync_period) { + dev_err(dev, "Zero esync divisor for OUTPUT%u got from device\n", + out); + return -EINVAL; + } + + /* Get esync pulse width in units of half synth cycles */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, &esync_width); + if (rc) + return rc; + + /* Compute esync frequency */ + esync->freq = synth_freq / output_div / esync_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 * esync_width) / output_div; + +finish: + /* Set supported esync ranges if the pin supports esync control and + * if the output frequency is > 1 Hz. + */ + if (pin->esync_control && (synth_freq / output_div) > 1) { + esync->range = esync_freq_ranges; + esync->range_num = ARRAY_SIZE(esync_freq_ranges); + } else { + esync->range = NULL; + esync->range_num = 0; + } + + 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) +{ + u32 esync_period, esync_width, output_div; + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + u8 clock_type, out, output_mode, synth; + u32 synth_freq; + int rc; + + out = zl3073x_output_pin_out_get(pin->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. + */ + switch (zl3073x_out_signal_format_get(zldev, out)) { + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: + return -EOPNOTSUPP; + default: + break; + } + + guard(mutex)(&zldev->multiop_lock); + + /* Read output configuration into mailbox */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); + if (rc) + return rc; + + /* Read output mode */ + rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode); + if (rc) + return rc; + + /* Select clock type */ + if (freq) + clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC; + else + clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL; + + /* Update clock type in output mode */ + output_mode &= ~ZL_OUTPUT_MODE_CLOCK_TYPE; + output_mode |= FIELD_PREP(ZL_OUTPUT_MODE_CLOCK_TYPE, clock_type); + rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_MODE, output_mode); + if (rc) + return rc; + + /* If esync is being disabled just write mailbox and finish */ + if (!freq) + goto write_mailbox; + + /* Get synth attached to output pin */ + synth = zl3073x_out_synth_get(zldev, out); + + /* Get synth frequency */ + synth_freq = zl3073x_synth_freq_get(zldev, synth); + + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div); + if (rc) + return rc; + + /* Check output divisor for zero */ + if (!output_div) { + dev_err(zldev->dev, + "Zero divisor for OUTPUT%u got from device\n", out); + return -EINVAL; + } + + /* Compute and update esync period */ + esync_period = synth_freq / (u32)freq / output_div; + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, esync_period); + if (rc) + return rc; + + /* 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. + */ + esync_width = output_div / 2; + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, esync_width); + if (rc) + return rc; + +write_mailbox: + /* Commit output configuration */ + return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, + ZL_REG_OUTPUT_MB_MASK, BIT(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_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + struct device *dev = zldev->dev; + u8 out, signal_format, synth; + u32 output_div, synth_freq; + int rc; + + out = zl3073x_output_pin_out_get(pin->id); + synth = zl3073x_out_synth_get(zldev, out); + synth_freq = zl3073x_synth_freq_get(zldev, synth); + + guard(mutex)(&zldev->multiop_lock); + + /* Read output configuration into mailbox */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); + if (rc) + return rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div); + if (rc) + return rc; + + /* Check output divisor for zero */ + if (!output_div) { + dev_err(dev, "Zero divisor for output %u got from device\n", + out); + return -EINVAL; + } + + /* Read used signal format for the given output */ + signal_format = zl3073x_out_signal_format_get(zldev, out); + + switch (signal_format) { + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: + /* In case of divided format we have to distiguish between + * given output pin type. + */ + if (zl3073x_dpll_is_p_pin(pin)) { + /* For P-pin the resulting frequency is computed as + * simple division of synth frequency and output + * divisor. + */ + *frequency = synth_freq / output_div; + } else { + /* For N-pin we have to divide additionally by + * divisor stored in esync_period output mailbox + * register that is used as N-pin divisor for these + * modes. + */ + u32 ndiv; + + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, + &ndiv); + if (rc) + return rc; + + /* Check N-pin divisor for zero */ + if (!ndiv) { + dev_err(dev, + "Zero N-pin divisor for output %u got from device\n", + out); + return -EINVAL; + } + + /* Compute final divisor for N-pin */ + *frequency = synth_freq / output_div / ndiv; + } + break; + default: + /* In other modes the resulting frequency is computed as + * division of synth frequency and output divisor. + */ + *frequency = synth_freq / output_div; + break; + } + + return rc; +} + +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; + struct device *dev = zldev->dev; + u32 output_n_freq, output_p_freq; + u8 out, signal_format, synth; + u32 cur_div, new_div, ndiv; + u32 synth_freq; + int rc; + + out = zl3073x_output_pin_out_get(pin->id); + synth = zl3073x_out_synth_get(zldev, out); + synth_freq = zl3073x_synth_freq_get(zldev, synth); + new_div = synth_freq / (u32)frequency; + + /* Get used signal format for the given output */ + signal_format = zl3073x_out_signal_format_get(zldev, out); + + guard(mutex)(&zldev->multiop_lock); + + /* Load output configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); + if (rc) + return rc; + + /* Check signal format */ + if (signal_format != ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV && + signal_format != ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV) { + /* For non N-divided signal formats the frequency is computed + * as division of synth frequency and output divisor. + */ + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_DIV, new_div); + if (rc) + return rc; + + /* For 50/50 duty cycle the divisor is equal to width */ + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_WIDTH, new_div); + if (rc) + return rc; + + /* Commit output configuration */ + return zl3073x_mb_op(zldev, + ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); + } + + /* For N-divided signal format get current divisor */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &cur_div); + if (rc) + return rc; + + /* Check output divisor for zero */ + if (!cur_div) { + dev_err(dev, "Zero divisor for output %u got from device\n", + out); + return -EINVAL; + } + + /* Get N-pin divisor (shares the same register with esync */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, &ndiv); + if (rc) + return rc; + + /* Check N-pin divisor for zero */ + if (!ndiv) { + dev_err(dev, + "Zero N-pin divisor for output %u got from device\n", + out); + return -EINVAL; + } + + /* Compute current output frequency for P-pin */ + output_p_freq = synth_freq / cur_div; + + /* Compute current N-pin frequency */ + output_n_freq = output_p_freq / ndiv; + + 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. + */ + if (frequency <= output_n_freq) + return -EINVAL; + + /* Update the output divisor */ + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_DIV, new_div); + if (rc) + return rc; + + /* For 50/50 duty cycle the divisor is equal to width */ + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_WIDTH, new_div); + if (rc) + return rc; + + /* Compute new divisor for N-pin */ + ndiv = (u32)frequency / output_n_freq; + } 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. + */ + if (output_p_freq <= frequency) + return -EINVAL; + + /* Compute new divisor for N-pin */ + ndiv = output_p_freq / (u32)frequency; + } + + /* Update divisor for the N-pin */ + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, ndiv); + if (rc) + return rc; + + /* For 50/50 duty cycle the divisor is equal to width */ + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, ndiv); + if (rc) + return rc; + + /* Commit output configuration */ + return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, + ZL_REG_OUTPUT_MB_MASK, BIT(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; + u32 synth_freq; + s32 phase_comp; + u8 out, synth; + int rc; + + out = zl3073x_output_pin_out_get(pin->id); + synth = zl3073x_out_synth_get(zldev, out); + synth_freq = zl3073x_synth_freq_get(zldev, synth); + + /* Check synth freq for zero */ + if (!synth_freq) { + dev_err(zldev->dev, "Got zero synth frequency for output %u\n", + out); + return -EINVAL; + } + + guard(mutex)(&zldev->multiop_lock); + + /* Read output configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); + if (rc) + return rc; + + /* Read current output phase compensation */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, &phase_comp); + if (rc) + return rc; + + /* Value in register is expressed in half synth clock cycles */ + phase_comp *= (int)div_u64(PSEC_PER_SEC, 2 * synth_freq); + + /* Reverse two's complement negation applied during 'set' */ + *phase_adjust = -phase_comp; + + return rc; +} + +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; + int half_synth_cycle; + u32 synth_freq; + u8 out, synth; + int rc; + + /* Get attached synth */ + out = zl3073x_output_pin_out_get(pin->id); + synth = zl3073x_out_synth_get(zldev, out); + + /* Get synth's frequency */ + synth_freq = zl3073x_synth_freq_get(zldev, synth); + + /* Value in register is expressed in half synth clock cycles so + * the given phase adjustment a multiple of half synth clock. + */ + half_synth_cycle = (int)div_u64(PSEC_PER_SEC, 2 * synth_freq); + + if ((phase_adjust % half_synth_cycle) != 0) { + NL_SET_ERR_MSG_FMT(extack, + "Phase adjustment value has to be multiple of %d", + half_synth_cycle); + return -EINVAL; + } + phase_adjust /= half_synth_cycle; + + /* The value in the register is stored as two's complement negation + * of requested value. + */ + phase_adjust = -phase_adjust; + + guard(mutex)(&zldev->multiop_lock); + + /* Read output configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(out)); + if (rc) + return rc; + + /* Write the requested value into the compensation register */ + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, phase_adjust); + if (rc) + return rc; + + /* Update output configuration from mailbox */ + return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, + ZL_REG_OUTPUT_MB_MASK, BIT(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_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; + struct zl3073x_dev *zldev = zldpll->dev; + u8 mon_status, state; + int rc; + + switch (zldpll->refsel_mode) { + 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; + } + + /* Read DPLL monitor status */ + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(zldpll->id), + &mon_status); + if (rc) + return rc; + state = FIELD_GET(ZL_DPLL_MON_STATUS_STATE, mon_status); + + switch (state) { + case ZL_DPLL_MON_STATUS_STATE_LOCK: + if (FIELD_GET(ZL_DPLL_MON_STATUS_HO_READY, mon_status)) + *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(zldev->dev, "Unknown DPLL monitor status: 0x%02x\n", + mon_status); + *status = DPLL_LOCK_STATUS_UNLOCKED; + break; + } + + 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; + + switch (zldpll->refsel_mode) { + 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_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 const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = { + .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, + .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, + .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, + .phase_offset_monitor_get = zl3073x_dpll_phase_offset_monitor_get, + .phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set, +}; + +/** + * 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(sizeof(*pin), GFP_KERNEL); + 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 & esync capability */ + strscpy(pin->label, props->package_label); + pin->esync_control = props->esync_control; + + if (zl3073x_dpll_is_input_pin(pin)) { + rc = zl3073x_dpll_ref_prio_get(pin, &pin->prio); + if (rc) + goto err_prio_get; + + if (pin->prio == ZL_DPLL_REF_PRIO_NONE) { + /* Clamp prio to max value & mark pin non-selectable */ + pin->prio = ZL_DPLL_REF_PRIO_MAX; + pin->selectable = false; + } else { + /* Mark pin as selectable */ + pin->selectable = true; + } + } + + /* Create or get existing DPLL pin */ + pin->dpll_pin = dpll_pin_get(zldpll->dev->clock_id, index, THIS_MODULE, + &props->dpll_props); + if (IS_ERR(pin->dpll_pin)) { + rc = PTR_ERR(pin->dpll_pin); + goto err_pin_get; + } + + 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); +err_prio_get: + pin->dpll_pin = NULL; +err_pin_get: + 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->dpll_pin = 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 = zl3073x_input_pin_ref_get(index); + + name = "REF"; + + /* Skip the pin if the DPLL is running in NCO mode */ + if (zldpll->refsel_mode == ZL_DPLL_MODE_REFSEL_MODE_NCO) + return false; + + is_diff = zl3073x_ref_is_diff(zldev, ref); + is_enabled = zl3073x_ref_is_enabled(zldev, ref); + } else { + /* Output P&N pair shares single HW output */ + u8 out = zl3073x_output_pin_out_get(index); + + name = "OUT"; + + /* Skip the pin if it is connected to different DPLL channel */ + if (zl3073x_out_dpll_get(zldev, out) != zldpll->id) { + dev_dbg(zldev->dev, + "%s%u is driven by different DPLL\n", name, + out); + + return false; + } + + is_diff = zl3073x_out_is_diff(zldev, out); + is_enabled = zl3073x_out_is_enabled(zldev, out); + } + + /* 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) + 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; + u8 dpll_mode_refsel; + int rc; + + /* Read DPLL mode and forcibly selected reference */ + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id), + &dpll_mode_refsel); + if (rc) + return rc; + + /* Extract mode and selected input reference */ + zldpll->refsel_mode = FIELD_GET(ZL_DPLL_MODE_REFSEL_MODE, + dpll_mode_refsel); + zldpll->forced_ref = FIELD_GET(ZL_DPLL_MODE_REFSEL_REF, + dpll_mode_refsel); + + zldpll->dpll_dev = dpll_device_get(zldev->clock_id, zldpll->id, + THIS_MODULE); + 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), + &zl3073x_dpll_device_ops, zldpll); + if (rc) { + dpll_device_put(zldpll->dpll_dev); + 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) +{ + WARN(!zldpll->dpll_dev, "DPLL device is not registered\n"); + + dpll_device_unregister(zldpll->dpll_dev, &zl3073x_dpll_device_ops, + zldpll); + dpll_device_put(zldpll->dpll_dev); + zldpll->dpll_dev = NULL; +} + +/** + * 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; + int rc; + + ref = zl3073x_input_pin_ref_get(pin->id); + + /* 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 and reference monitor does not + * report signal errors for given input pin + */ + if (pin->pin_state == DPLL_PIN_STATE_CONNECTED) { + reg = ZL_REG_DPLL_PHASE_ERR_DATA(zldpll->id); + } else if (zldpll->phase_monitor) { + u8 status; + + /* Get reference monitor status */ + rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref), + &status); + if (rc) { + dev_err(zldev->dev, + "Failed to read %s refmon status: %pe\n", + pin->label, ERR_PTR(rc)); + + return false; + } + + if (status != ZL_REF_MON_STATUS_OK) + return false; + + reg = ZL_REG_REF_PHASE(ref); + } else { + /* The pin is not connected or phase monitor disabled */ + 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 pin fractional frequency offset change + * @pin: pin to check + * + * Check for the given pin's fractional frequency change. + * + * Return: true on fractional frequency offset 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; + u8 ref, status; + s64 ffo; + int rc; + + /* Get reference monitor status */ + ref = zl3073x_input_pin_ref_get(pin->id); + rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref), &status); + if (rc) { + dev_err(zldev->dev, "Failed to read %s refmon status: %pe\n", + pin->label, ERR_PTR(rc)); + + return false; + } + + /* Do not report ffo changes if the reference monitor report errors */ + if (status != ZL_REF_MON_STATUS_OK) + return false; + + /* Get the latest measured ref's ffo */ + ffo = zl3073x_ref_ffo_get(zldev, ref); + + /* Compare with previous value */ + if (pin->freq_offset != ffo) { + dev_dbg(zldev->dev, "%s freq offset changed: %lld -> %lld\n", + pin->label, pin->freq_offset, ffo); + pin->freq_offset = ffo; + + 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); + } + + /* Input pin monitoring does make sense only in automatic + * or forced reference modes. + */ + if (zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_AUTO && + zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_REFLOCK) + return; + + /* 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_state state; + 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_state_get(pin, &state); + if (rc) { + dev_err(dev, + "Failed to get %s on DPLL%u state: %pe\n", + pin->label, zldpll->id, ERR_PTR(rc)); + return; + } + + if (state != pin->pin_state) { + dev_dbg(dev, "%s state changed: %u->%u\n", pin->label, + pin->pin_state, state); + pin->pin_state = state; + pin_changed = true; + } + + /* Check for phase offset and ffo 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 (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(sizeof(*zldpll), GFP_KERNEL); + 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_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; + } + + 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); +} |