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