linux-loongson/drivers/dpll/zl3073x/dpll.c
Ivan Vecera 904c99ea36 dpll: zl3073x: Add support to get fractional frequency offset
Adds support to get fractional frequency offset for input pins. Implement
the appropriate callback and function that periodicaly performs reference
frequency measurement and notifies DPLL core about changes.

Reviewed-by: Jiri Pirko <jiri@nvidia.com>
Tested-by: Prathosh Satish <prathosh.satish@microchip.com>
Co-developed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20250715144633.149156-6-ivecera@redhat.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
2025-07-17 15:31:55 +02:00

2319 lines
59 KiB
C

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