mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-08-29 11:42:36 +00:00

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>
2319 lines
59 KiB
C
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);
|
|
}
|