Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions Documentation/netlink/specs/dpll.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,26 @@ definitions:
name: enable
doc: |
feature shall be enabled
-
type: enum
name: reset-type
doc: |
defines types of resets that can be performed on a dpll device,
valid values for DPLL_A_RESET_TYPE attribute
entries:
-
name: resync
doc: |
force the dpll to re-lock to its currently selected input
reference without affecting inputs, outputs or other
device settings
value: 1
-
name: holdover
doc: |
clear the dpll holdover filter and storage, resetting any
accumulated holdover state
render-max: true

attribute-sets:
-
Expand Down Expand Up @@ -377,6 +397,11 @@ attribute-sets:
doc: Current or desired state of the frequency monitor feature.
If enabled, dpll device shall measure all currently available
inputs for their actual input frequency.
-
name: reset-type
type: u32
enum: reset-type
doc: Type of reset to perform on a dpll device.
-
name: pin
enum-name: dpll_a_pin
Expand Down Expand Up @@ -782,6 +807,19 @@ operations:
doc: Notification about pin configuration being changed
notify: pin-get
mcgrp: monitor
-
name: device-reset
doc: Reset a DPLL device
attribute-set: dpll
flags: [admin-perm]

do:
pre: dpll-pre-doit
post: dpll-post-doit
request:
attributes:
- id
- reset-type

mcast-groups:
list:
Expand Down
28 changes: 28 additions & 0 deletions drivers/dpll/dpll_netlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -1977,6 +1977,34 @@ int dpll_nl_device_set_doit(struct sk_buff *skb, struct genl_info *info)
return dpll_set_from_nlattr(dpll, info);
}

int dpll_nl_device_reset_doit(struct sk_buff *skb, struct genl_info *info)
{
const struct dpll_device_ops *ops;
struct dpll_device *dpll;
enum dpll_reset_type type;
struct nlattr *a;
int ret;

dpll = info->user_ptr[0];
if (GENL_REQ_ATTR_CHECK(info, DPLL_A_RESET_TYPE))
return -EINVAL;

a = info->attrs[DPLL_A_RESET_TYPE];
type = nla_get_u32(a);
ops = dpll_device_ops(dpll);
if (!ops->reset) {
NL_SET_ERR_MSG_ATTR(info->extack, a,
"dpll device does not support reset");
return -EOPNOTSUPP;
}

ret = ops->reset(dpll, dpll_priv(dpll), type, info->extack);
if (!ret)
__dpll_device_change_ntf(dpll);

return ret;
}

int dpll_nl_device_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
{
struct dpll_dump_ctx *ctx = dpll_dump_context(cb);
Expand Down
15 changes: 15 additions & 0 deletions drivers/dpll/dpll_nl.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ static const struct nla_policy dpll_pin_set_nl_policy[DPLL_A_PIN_REFERENCE_SYNC
[DPLL_A_PIN_REFERENCE_SYNC] = NLA_POLICY_NESTED(dpll_reference_sync_nl_policy),
};

/* DPLL_CMD_DEVICE_RESET - do */
static const struct nla_policy dpll_device_reset_nl_policy[DPLL_A_RESET_TYPE + 1] = {
[DPLL_A_ID] = { .type = NLA_U32, },
[DPLL_A_RESET_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 2),
};

/* Ops table for dpll */
static const struct genl_split_ops dpll_nl_ops[] = {
{
Expand Down Expand Up @@ -156,6 +162,15 @@ static const struct genl_split_ops dpll_nl_ops[] = {
.maxattr = DPLL_A_PIN_REFERENCE_SYNC,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = DPLL_CMD_DEVICE_RESET,
.pre_doit = dpll_pre_doit,
.doit = dpll_nl_device_reset_doit,
.post_doit = dpll_post_doit,
.policy = dpll_device_reset_nl_policy,
.maxattr = DPLL_A_RESET_TYPE,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
};

static const struct genl_multicast_group dpll_nl_mcgrps[] = {
Expand Down
1 change: 1 addition & 0 deletions drivers/dpll/dpll_nl.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ int dpll_nl_pin_id_get_doit(struct sk_buff *skb, struct genl_info *info);
int dpll_nl_pin_get_doit(struct sk_buff *skb, struct genl_info *info);
int dpll_nl_pin_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int dpll_nl_pin_set_doit(struct sk_buff *skb, struct genl_info *info);
int dpll_nl_device_reset_doit(struct sk_buff *skb, struct genl_info *info);

enum {
DPLL_NLGRP_MONITOR,
Expand Down
43 changes: 39 additions & 4 deletions drivers/dpll/zl3073x/chan.c
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,12 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
if (rc)
return rc;

/* Read fast lock control register */
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_FAST_LOCK_CTRL,
&chan->fast_lock_ctrl);
if (rc)
return rc;

/* Read reference priority registers */
for (i = 0; i < ARRAY_SIZE(chan->ref_prio); i++) {
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(i),
Expand Down Expand Up @@ -591,11 +597,9 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
dchan->mode_refsel = chan->mode_refsel;
}

/* Mailbox write for ref_prio if changed */
if (!memcmp(dchan->ref_prio, chan->ref_prio, sizeof(chan->ref_prio))) {
dchan->cfg = chan->cfg;
/* Skip mailbox write if no mailbox registers changed */
if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg)))
return 0;
}

guard(mutex)(&zldev->multiop_lock);

Expand All @@ -605,6 +609,13 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
if (rc)
return rc;

if (dchan->fast_lock_ctrl != chan->fast_lock_ctrl) {
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_FAST_LOCK_CTRL,
chan->fast_lock_ctrl);
if (rc)
return rc;
}

/* Update changed ref_prio registers */
for (i = 0; i < ARRAY_SIZE(chan->ref_prio); i++) {
if (dchan->ref_prio[i] != chan->ref_prio[i]) {
Expand All @@ -627,3 +638,27 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,

return 0;
}

/**
* zl3073x_chan_holdover_reset - clear holdover history for a DPLL channel
* @zldev: pointer to zl3073x_dev structure
* @index: DPLL channel index
*
* Clears the holdover filter, holdover storage and ho_ready status
* by setting the self-clearing clear_ho bit in the dpll_cmd register.
*
* Return: 0 on success, <0 on error
*/
int zl3073x_chan_holdover_reset(struct zl3073x_dev *zldev, u8 index)
{
int rc;

rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_CMD(index),
ZL_DPLL_CMD_CLEAR_HO);
if (rc)
return rc;

return zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_CMD(index),
ZL_DPLL_CMD_CLEAR_HO,
ZL_POLL_HO_CLEAR_TIMEOUT_US);
}
39 changes: 39 additions & 0 deletions drivers/dpll/zl3073x/chan.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct zl3073x_dev;
/**
* struct zl3073x_chan - DPLL channel state
* @ctrl: DPLL control register value
* @fast_lock_ctrl: fast lock control register value
* @mode_refsel: mode and reference selection register value
* @ref_prio: reference priority registers (4 bits per ref, P/N packed)
* @mon_status: monitor status register value
Expand All @@ -25,6 +26,7 @@ struct zl3073x_dev;
struct zl3073x_chan {
struct_group(cfg,
u8 ctrl;
u8 fast_lock_ctrl;
u8 mode_refsel;
u8 ref_prio[ZL3073X_NUM_REFS / 2];
);
Expand All @@ -43,6 +45,7 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,

int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);
int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index);
int zl3073x_chan_holdover_reset(struct zl3073x_dev *zldev, u8 index);

int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch);
int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch,
Expand Down Expand Up @@ -265,4 +268,40 @@ static inline u8 zl3073x_chan_refsel_ref_get(const struct zl3073x_chan *chan)
return FIELD_GET(ZL_DPLL_REFSEL_STATUS_REFSEL, chan->refsel_status);
}

/**
* zl3073x_chan_is_fast_lock_enabled - check if fast lock is enabled
* @chan: pointer to channel state
*
* Return: true if fast lock master enable is set, false otherwise
*/
static inline bool
zl3073x_chan_is_fast_lock_enabled(const struct zl3073x_chan *chan)
{
return !!(chan->fast_lock_ctrl & ZL_DPLL_FAST_LOCK_CTRL_MASTER_EN);
}

/**
* zl3073x_chan_fast_lock_set - enable or disable fast lock
* @chan: pointer to channel state
* @enable: true to enable, false to disable
*/
static inline void
zl3073x_chan_fast_lock_set(struct zl3073x_chan *chan, bool enable)
{
FIELD_MODIFY(ZL_DPLL_FAST_LOCK_CTRL_MASTER_EN,
&chan->fast_lock_ctrl, enable);
}

/**
* zl3073x_chan_fast_lock_force_set - enable or disable forced fast lock
* @chan: pointer to channel state
* @enable: true to force fast lock, false to clear
*/
static inline void
zl3073x_chan_fast_lock_force_set(struct zl3073x_chan *chan, bool enable)
{
FIELD_MODIFY(ZL_DPLL_FAST_LOCK_CTRL_FORCE_EN,
&chan->fast_lock_ctrl, enable);
}

#endif /* _ZL3073X_CHAN_H */
27 changes: 13 additions & 14 deletions drivers/dpll/zl3073x/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ static const struct zl3073x_chip_info zl3073x_chip_ids[] = {
ZL_CHIP_INFO(0x1E96, 4, ZL3073X_FLAG_DIE_TEMP),
ZL_CHIP_INFO(0x1E97, 5, ZL3073X_FLAG_DIE_TEMP),
ZL_CHIP_INFO(0x1F60, 2, ZL3073X_FLAG_REF_PHASE_COMP_32),
ZL_CHIP_INFO(0x2E93, 1, ZL3073X_FLAG_DIE_TEMP),
ZL_CHIP_INFO(0x2E94, 2, ZL3073X_FLAG_DIE_TEMP),
ZL_CHIP_INFO(0x2E95, 3, ZL3073X_FLAG_DIE_TEMP),
ZL_CHIP_INFO(0x2E96, 4, ZL3073X_FLAG_DIE_TEMP),
ZL_CHIP_INFO(0x2E97, 5, ZL3073X_FLAG_DIE_TEMP),
ZL_CHIP_INFO(0x3FC4, 2, ZL3073X_FLAG_DIE_TEMP),
ZL_CHIP_INFO(0x2E93, 1, ZL3073X_FLAG_DIE_TEMP | ZL3073X_FLAG_HO_CLEAR),
ZL_CHIP_INFO(0x2E94, 2, ZL3073X_FLAG_DIE_TEMP | ZL3073X_FLAG_HO_CLEAR),
ZL_CHIP_INFO(0x2E95, 3, ZL3073X_FLAG_DIE_TEMP | ZL3073X_FLAG_HO_CLEAR),
ZL_CHIP_INFO(0x2E96, 4, ZL3073X_FLAG_DIE_TEMP | ZL3073X_FLAG_HO_CLEAR),
ZL_CHIP_INFO(0x2E97, 5, ZL3073X_FLAG_DIE_TEMP | ZL3073X_FLAG_HO_CLEAR),
ZL_CHIP_INFO(0x3FC4, 2, ZL3073X_FLAG_DIE_TEMP | ZL3073X_FLAG_HO_CLEAR),
};

#define ZL_RANGE_OFFSET 0x80
Expand Down Expand Up @@ -308,34 +308,33 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val)
}

/**
* zl3073x_poll_zero_u8 - wait for register to be cleared by device
* zl3073x_poll_u8 - wait for register masked value to match expected value
* @zldev: zl3073x device pointer
* @reg: register to poll (has to be 8bit register)
* @mask: bit mask for polling
* @expected: expected value after masking
* @timeout_us: timeout in microseconds
*
* Waits for bits specified by @mask in register @reg value to be cleared
* by the device.
* Waits until (register & @mask) == @expected.
*
* Returns: 0 on success, <0 on error
*/
int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
u8 mask, unsigned int timeout_us)
int zl3073x_poll_u8(struct zl3073x_dev *zldev, unsigned int reg,
u8 mask, u8 expected, unsigned int timeout_us)
{
unsigned int sleep_us = timeout_us / 50;
unsigned int val;

/* Check the register is 8bit */
if (ZL_REG_SIZE(reg) != 1) {
dev_err(zldev->dev, "Invalid reg 0x%04lx size for polling\n",
ZL_REG_ADDR(reg));
return -EINVAL;
}

/* Map the register address to virtual range */
reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;

return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask),
return regmap_read_poll_timeout(zldev->regmap, reg, val,
(val & mask) == expected,
sleep_us, timeout_us);
}

Expand Down
31 changes: 29 additions & 2 deletions drivers/dpll/zl3073x/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ struct zl3073x_dpll;

/* Per-operation poll timeouts */
#define ZL_POLL_DF_READ_TIMEOUT_US (25 * USEC_PER_MSEC)
#define ZL_POLL_FAST_LOCK_TIMEOUT_US (50 * USEC_PER_MSEC)
#define ZL_POLL_FREQ_MEAS_TIMEOUT_US (50 * USEC_PER_MSEC)
#define ZL_POLL_HO_CLEAR_TIMEOUT_US (50 * USEC_PER_MSEC)
#define ZL_POLL_HWREG_TIMEOUT_US (50 * USEC_PER_MSEC)
#define ZL_POLL_MB_TIMEOUT_US (30 * USEC_PER_MSEC)
#define ZL_POLL_PHASE_ERR_TIMEOUT_US (50 * USEC_PER_MSEC)
Expand All @@ -34,12 +36,14 @@ struct zl3073x_dpll;
enum zl3073x_flags {
ZL3073X_FLAG_REF_PHASE_COMP_32_BIT,
ZL3073X_FLAG_DIE_TEMP_BIT,
ZL3073X_FLAG_HO_CLEAR_BIT,
ZL3073X_FLAGS_NBITS /* must be last */
};

#define __ZL3073X_FLAG(name) BIT(ZL3073X_FLAG_ ## name ## _BIT)
#define ZL3073X_FLAG_REF_PHASE_COMP_32 __ZL3073X_FLAG(REF_PHASE_COMP_32)
#define ZL3073X_FLAG_DIE_TEMP __ZL3073X_FLAG(DIE_TEMP)
#define ZL3073X_FLAG_HO_CLEAR __ZL3073X_FLAG(HO_CLEAR)

/**
* struct zl3073x_chip_info - chip variant identification
Expand Down Expand Up @@ -144,8 +148,16 @@ struct zl3073x_hwreg_seq_item {

int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
unsigned int mask_reg, u16 mask_val);
int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
u8 mask, unsigned int timeout_us);
int zl3073x_poll_u8(struct zl3073x_dev *zldev, unsigned int reg,
u8 mask, u8 expected, unsigned int timeout_us);

static inline int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev,
unsigned int reg, u8 mask,
unsigned int timeout_us)
{
return zl3073x_poll_u8(zldev, reg, mask, 0, timeout_us);
}

int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val);
int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val);
int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val);
Expand Down Expand Up @@ -183,6 +195,21 @@ zl3073x_dev_is_ref_phase_comp_32bit(struct zl3073x_dev *zldev)
return zldev->info->flags & ZL3073X_FLAG_REF_PHASE_COMP_32;
}

/**
* zl3073x_dev_is_ho_clear_supported - check if holdover clear is supported
* @zldev: pointer to zl3073x device
*
* The clear_ho command in dpll_cmd register is only available on
* B Series and later chip variants.
*
* Return: true if holdover clear is supported, false otherwise
*/
static inline bool
zl3073x_dev_is_ho_clear_supported(struct zl3073x_dev *zldev)
{
return zldev->info->flags & ZL3073X_FLAG_HO_CLEAR;
}

static inline bool
zl3073x_is_n_pin(u8 id)
{
Expand Down
Loading