summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaolo Abeni <pabeni@redhat.com>2026-07-06 14:45:06 +0200
committerPaolo Abeni <pabeni@redhat.com>2026-07-06 14:45:07 +0200
commit474cff6868129755cf889edf40d7f491729fc588 (patch)
tree26c8381fdf0f5c42d10c665fd59134f50def4f0e
parent1704cc8640702c93e79921e2dffff3cfa31f0ce5 (diff)
parent3553976ffe2f0ccb7f667725816e41c44f0e4729 (diff)
downloadlinux-474cff6868129755cf889edf40d7f491729fc588.tar.gz
linux-474cff6868129755cf889edf40d7f491729fc588.zip
Merge branch 'dpll-add-nco-pin-type-and-zl3073x-support'
Ivan Vecera says: ==================== dpll: add NCO pin type and zl3073x support Add a new DPLL_PIN_TYPE_INT_NCO pin type for virtual pins representing the NCO mode of a DPLL and implement support for it in the zl3073x driver. Patch 1 adds a STATE_CONNECTED_OVERRIDE pin capability for pins that can override input selection in any DPLL mode, with a WARN_ON check in dpll_pin_register() that STATE_CAN_CHANGE is also set. Patch 2 adds the new INT_NCO pin type to the DPLL netlink spec and UAPI header. Patch 3 replaces the single 2s poll timeout with per-operation timeouts based on Microchip proprietary source code and own measurement. Patch 4 adds a per-DPLL serialization mutex taken by all DPLL callbacks and the periodic worker, establishing a single lock that protects all per-channel state. The chan_state_update() call is moved under this lock. Patch 5 adds a virtual NCO input pin to the zl3073x driver that allows userspace to switch a DPLL channel into NCO mode. The pin reports connected/active state when the channel is in NCO mode and handles the hardware-specific details of mode transitions including automatic df_offset capture and 1PPS phase preservation. Link: https://lore.kernel.org/netdev/20260531194423.383366-1-ivecera@redhat.com/ ==================== Link: https://patch.msgid.link/20260630125536.720717-1-ivecera@redhat.com Signed-off-by: Paolo Abeni <pabeni@redhat.com>
-rw-r--r--Documentation/driver-api/dpll.rst7
-rw-r--r--Documentation/netlink/specs/dpll.yaml19
-rw-r--r--drivers/dpll/dpll_core.c6
-rw-r--r--drivers/dpll/dpll_nl.c2
-rw-r--r--drivers/dpll/zl3073x/chan.c128
-rw-r--r--drivers/dpll/zl3073x/chan.h48
-rw-r--r--drivers/dpll/zl3073x/core.c48
-rw-r--r--drivers/dpll/zl3073x/core.h12
-rw-r--r--drivers/dpll/zl3073x/dpll.c489
-rw-r--r--drivers/dpll/zl3073x/dpll.h4
-rw-r--r--drivers/dpll/zl3073x/regs.h11
-rw-r--r--include/uapi/linux/dpll.h8
12 files changed, 677 insertions, 105 deletions
diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst
index bae14766d4f7..f83150917814 100644
--- a/Documentation/driver-api/dpll.rst
+++ b/Documentation/driver-api/dpll.rst
@@ -91,6 +91,13 @@ following pin states:
- ``DPLL_PIN_STATE_DISCONNECTED`` - the pin shall be not considered as
a valid input for automatic selection algorithm
+Pins that have the ``DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE``
+capability can additionally be set to ``DPLL_PIN_STATE_CONNECTED`` in
+automatic mode, overriding the active input selection. This is useful
+for automatic-only DPLL devices where mode cannot be switched to manual.
+When such a pin is disconnected, the device returns to automatic input
+selection.
+
The actual hardware status of a pin is reported via the operational
state (``DPLL_A_PIN_OPERSTATE``) attribute nested under the parent
device:
diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml
index 2bf83f6732ab..cdc8c7b456df 100644
--- a/Documentation/netlink/specs/dpll.yaml
+++ b/Documentation/netlink/specs/dpll.yaml
@@ -165,6 +165,13 @@ definitions:
-
name: gnss
doc: GNSS recovered clock
+ -
+ name: int-nco
+ doc: |
+ Device internal numerically controlled oscillator.
+ When connected as a DPLL input, the DPLL enters NCO mode
+ where the output frequency is adjusted by the host via
+ the PTP clock interface.
render-max: true
-
type: enum
@@ -252,6 +259,12 @@ definitions:
-
name: state-can-change
doc: pin state can be changed
+ -
+ name: state-connected-override
+ doc: |
+ pin state can be set to connected regardless of current
+ DPLL device mode, overriding the active input selection.
+ Requires state-can-change to be set as well.
-
type: const
name: phase-offset-divider
@@ -456,6 +469,9 @@ attribute-sets:
offset on the media associated with the pin. Inside
the pin-parent-device nest it represents the frequency
offset between the pin and its parent DPLL device.
+ For pins of type PIN_TYPE_INT_NCO this represents
+ the DPLL's current output frequency offset from its
+ nominal frequency.
Value is in PPM (parts per million).
This is a lower-precision version of
fractional-frequency-offset-ppt.
@@ -502,6 +518,9 @@ attribute-sets:
offset on the media associated with the pin. Inside
the pin-parent-device nest it represents the frequency
offset between the pin and its parent DPLL device.
+ For pins of type PIN_TYPE_INT_NCO this represents
+ the DPLL's current output frequency offset from its
+ nominal frequency.
Value is in PPT (parts per trillion, 10^-12).
This is a higher-precision version of
fractional-frequency-offset.
diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c
index 2e8690cb3c16..bb1e8650c9d5 100644
--- a/drivers/dpll/dpll_core.c
+++ b/drivers/dpll/dpll_core.c
@@ -884,7 +884,11 @@ dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin,
WARN_ON(ops->measured_freq_get &&
(!dpll_device_ops(dpll)->freq_monitor_get ||
!dpll_device_ops(dpll)->freq_monitor_set)) ||
- WARN_ON(ops->supported_ffo && !ops->ffo_get))
+ WARN_ON(ops->supported_ffo && !ops->ffo_get) ||
+ WARN_ON((pin->prop.capabilities &
+ DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE) &&
+ !(pin->prop.capabilities &
+ DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE)))
return -EINVAL;
mutex_lock(&dpll_lock);
diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c
index ed3bbe9841ea..b1ba490e72b0 100644
--- a/drivers/dpll/dpll_nl.c
+++ b/drivers/dpll/dpll_nl.c
@@ -61,7 +61,7 @@ static const struct nla_policy dpll_pin_id_get_nl_policy[DPLL_A_PIN_TYPE + 1] =
[DPLL_A_PIN_BOARD_LABEL] = { .type = NLA_NUL_STRING, },
[DPLL_A_PIN_PANEL_LABEL] = { .type = NLA_NUL_STRING, },
[DPLL_A_PIN_PACKAGE_LABEL] = { .type = NLA_NUL_STRING, },
- [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 5),
+ [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 6),
};
/* DPLL_CMD_PIN_GET - do */
diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
index 2fe3c3da84bb..4ec2cf53dad4 100644
--- a/drivers/dpll/zl3073x/chan.c
+++ b/drivers/dpll/zl3073x/chan.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/cleanup.h>
+#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/string.h>
#include <linux/types.h>
@@ -31,9 +32,18 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
if (rc)
return rc;
- /* Read df_offset vs tracked reference */
+ /* Read df_offset only when locked to a reference. In NCO mode
+ * df_offset was captured at entry by nco_mode_set() - preserve it.
+ */
+ if (!zl3073x_chan_is_locked(chan)) {
+ if (!zl3073x_chan_mode_is_nco(chan))
+ chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
+ return 0;
+ }
+
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
- ZL_DPLL_DF_READ_SEM);
+ ZL_DPLL_DF_READ_SEM,
+ ZL_POLL_DF_READ_TIMEOUT_US);
if (rc)
return rc;
@@ -43,7 +53,8 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
return rc;
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
- ZL_DPLL_DF_READ_SEM);
+ ZL_DPLL_DF_READ_SEM,
+ ZL_POLL_DF_READ_TIMEOUT_US);
if (rc)
return rc;
@@ -57,6 +68,96 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
}
/**
+ * zl3073x_chan_nco_mode_set - switch DPLL channel to NCO mode
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: DPLL channel index
+ *
+ * Switches the channel to NCO mode and reads the df_offset
+ * auto-captured by nco_auto_read directly from the register.
+ * No DF_READ handshake is needed as nco_auto_read populates
+ * the register before the mode switch completes.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index)
+{
+ struct zl3073x_chan *chan = &zldev->chan[index];
+ u8 prev_mode, df_read;
+ u64 val;
+ int rc;
+
+ prev_mode = zl3073x_chan_mode_get(chan);
+
+ /* nco_auto_read captures the tracking offset at NCO entry only
+ * from reflock, auto or holdover mode. From freerun the captured
+ * value is not meaningful.
+ */
+ if (prev_mode == ZL_DPLL_MODE_REFSEL_MODE_FREERUN) {
+ zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO);
+
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
+ chan->mode_refsel);
+ if (rc) {
+ zl3073x_chan_mode_set(chan, prev_mode);
+ return rc;
+ }
+
+ chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
+ return 0;
+ }
+
+ /* Configure df_read for nco_auto_read:
+ * ref_ofst=0 - reads offset relative to master clock (not input ref)
+ * cmd=CMD_ACC_I - accumulated I-part covering both locked and
+ * holdover entry.
+ *
+ * No semaphore is set - this only configures what the df_offset
+ * value represents after the mode switch; nco_auto_read performs
+ * the actual read automatically.
+ */
+ df_read = FIELD_PREP(ZL_DPLL_DF_READ_REF_OFST, 0) |
+ FIELD_PREP(ZL_DPLL_DF_READ_CMD, ZL_DPLL_DF_READ_CMD_ACC_I);
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index), df_read);
+ if (rc)
+ return rc;
+
+ /* Wait for df_read configuration to take effect before
+ * triggering nco_auto_read via mode switch. The worst-case
+ * internal register update time is 25 ms.
+ */
+ fsleep(25000);
+
+ zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO);
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
+ chan->mode_refsel);
+ if (rc) {
+ zl3073x_chan_mode_set(chan, prev_mode);
+ return rc;
+ }
+
+ /* Wait for nco_auto_read to populate df_offset. The worst-case
+ * internal register update time is 25 ms.
+ */
+ fsleep(25000);
+
+ /* Read df_offset captured by nco_auto_read during mode switch.
+ * No DF_READ semaphore handshake needed. Mode switch already
+ * succeeded, so don't propagate a read failure back to userspace.
+ */
+ rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val);
+ if (rc) {
+ dev_warn(zldev->dev,
+ "Failed to read DPLL%u df_offset: %pe\n",
+ index, ERR_PTR(rc));
+ chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
+ } else {
+ chan->df_offset = sign_extend64(val, 47);
+ }
+
+ return 0;
+}
+
+/**
* zl3073x_chan_state_fetch - fetch DPLL channel state from hardware
* @zldev: pointer to zl3073x_dev structure
* @index: DPLL channel index to fetch state for
@@ -71,6 +172,10 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
struct zl3073x_chan *chan = &zldev->chan[index];
int rc, i;
+ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_CTRL(index), &chan->ctrl);
+ if (rc)
+ return rc;
+
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
&chan->mode_refsel);
if (rc)
@@ -83,6 +188,13 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
if (rc)
return rc;
+ /* If firmware left the channel in NCO mode, mark df_offset as
+ * unknown - we cannot know whether the preconditions for a valid
+ * nco_auto_read capture were met.
+ */
+ if (zl3073x_chan_mode_is_nco(chan))
+ chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
+
dev_dbg(zldev->dev,
"DPLL%u lock_state: %u, ho: %u, sel_state: %u, sel_ref: %u\n",
index, zl3073x_chan_lock_state_get(chan),
@@ -145,7 +257,15 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg)))
return 0;
- /* Direct register write for mode_refsel */
+ /* Direct register writes for ctrl and mode_refsel */
+ if (dchan->ctrl != chan->ctrl) {
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_CTRL(index),
+ chan->ctrl);
+ if (rc)
+ return rc;
+ dchan->ctrl = chan->ctrl;
+ }
+
if (dchan->mode_refsel != chan->mode_refsel) {
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
chan->mode_refsel);
diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h
index 4353809c6912..dc9c6d95bdee 100644
--- a/drivers/dpll/zl3073x/chan.h
+++ b/drivers/dpll/zl3073x/chan.h
@@ -13,6 +13,7 @@ struct zl3073x_dev;
/**
* struct zl3073x_chan - DPLL channel state
+ * @ctrl: DPLL 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
@@ -21,6 +22,7 @@ struct zl3073x_dev;
*/
struct zl3073x_chan {
struct_group(cfg,
+ u8 ctrl;
u8 mode_refsel;
u8 ref_prio[ZL3073X_NUM_REFS / 2];
);
@@ -38,6 +40,7 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_chan *chan);
int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);
+int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index);
/**
* zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference
@@ -153,6 +156,51 @@ static inline u8 zl3073x_chan_lock_state_get(const struct zl3073x_chan *chan)
}
/**
+ * zl3073x_chan_is_locked - check if channel is locked to a reference
+ * @chan: pointer to channel state
+ *
+ * Return: true if channel is locked, false otherwise
+ */
+static inline bool zl3073x_chan_is_locked(const struct zl3073x_chan *chan)
+{
+ u8 lock_state = zl3073x_chan_lock_state_get(chan);
+ return lock_state == ZL_DPLL_MON_STATUS_STATE_LOCK;
+}
+
+/**
+ * zl3073x_chan_mode_is_auto - check if channel is in automatic mode
+ * @chan: pointer to channel state
+ *
+ * Return: true if channel is in automatic mode, false otherwise
+ */
+static inline bool zl3073x_chan_mode_is_auto(const struct zl3073x_chan *chan)
+{
+ return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO;
+}
+
+/**
+ * zl3073x_chan_mode_is_nco - check if channel is in NCO mode
+ * @chan: pointer to channel state
+ *
+ * Return: true if channel is in NCO mode, false otherwise
+ */
+static inline bool zl3073x_chan_mode_is_nco(const struct zl3073x_chan *chan)
+{
+ return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO;
+}
+
+/**
+ * zl3073x_chan_mode_is_reflock - check if channel is in reflock mode
+ * @chan: pointer to channel state
+ *
+ * Return: true if channel is in reflock mode, false otherwise
+ */
+static inline bool zl3073x_chan_mode_is_reflock(const struct zl3073x_chan *chan)
+{
+ return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_REFLOCK;
+}
+
+/**
* zl3073x_chan_is_ho_ready - check if holdover is ready
* @chan: pointer to channel state
*
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 8e6416a4741d..7f5afaaae634 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -311,17 +311,17 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val)
* @zldev: zl3073x device pointer
* @reg: register to poll (has to be 8bit register)
* @mask: bit mask for polling
+ * @timeout_us: timeout in microseconds
*
* Waits for bits specified by @mask in register @reg value to be cleared
* by the device.
*
* Returns: 0 on success, <0 on error
*/
-int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask)
+int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
+ u8 mask, unsigned int timeout_us)
{
- /* Register polling sleep & timeout */
-#define ZL_POLL_SLEEP_US 10
-#define ZL_POLL_TIMEOUT_US 2000000
+#define ZL_POLL_SLEEP_US 10
unsigned int val;
/* Check the register is 8bit */
@@ -335,7 +335,7 @@ int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask)
reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask),
- ZL_POLL_SLEEP_US, ZL_POLL_TIMEOUT_US);
+ ZL_POLL_SLEEP_US, timeout_us);
}
int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
@@ -354,7 +354,8 @@ int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
return rc;
/* Wait for the operation to actually finish */
- return zl3073x_poll_zero_u8(zldev, op_reg, op_val);
+ return zl3073x_poll_zero_u8(zldev, op_reg, op_val,
+ ZL_POLL_MB_TIMEOUT_US);
}
/**
@@ -377,8 +378,8 @@ zl3073x_do_hwreg_op(struct zl3073x_dev *zldev, u8 op)
return rc;
/* Poll for completion - pending bit cleared */
- return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP,
- ZL_HWREG_OP_PENDING);
+ return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP, ZL_HWREG_OP_PENDING,
+ ZL_POLL_HWREG_TIMEOUT_US);
}
/**
@@ -566,19 +567,7 @@ zl3073x_dev_ref_states_update(struct zl3073x_dev *zldev)
}
}
-static void
-zl3073x_dev_chan_states_update(struct zl3073x_dev *zldev)
-{
- int i, rc;
- for (i = 0; i < zldev->info->num_channels; i++) {
- rc = zl3073x_chan_state_update(zldev, i);
- if (rc)
- dev_warn(zldev->dev,
- "Failed to get DPLL%u state: %pe\n", i,
- ERR_PTR(rc));
- }
-}
/**
* zl3073x_ref_phase_offsets_update - update reference phase offsets
@@ -609,7 +598,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
* to be zero to ensure that the measured data are coherent.
*/
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
- ZL_REF_PHASE_ERR_READ_RQST_RD);
+ ZL_REF_PHASE_ERR_READ_RQST_RD,
+ ZL_POLL_PHASE_ERR_TIMEOUT_US);
if (rc)
return rc;
@@ -628,7 +618,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
/* Wait for finish */
return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
- ZL_REF_PHASE_ERR_READ_RQST_RD);
+ ZL_REF_PHASE_ERR_READ_RQST_RD,
+ ZL_POLL_PHASE_ERR_TIMEOUT_US);
}
/**
@@ -648,7 +639,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type)
/* Wait for previous measurement to finish */
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
- ZL_REF_FREQ_MEAS_CTRL);
+ ZL_REF_FREQ_MEAS_CTRL,
+ ZL_POLL_FREQ_MEAS_TIMEOUT_US);
if (rc)
return rc;
@@ -669,7 +661,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type)
/* Wait for finish */
return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
- ZL_REF_FREQ_MEAS_CTRL);
+ ZL_REF_FREQ_MEAS_CTRL,
+ ZL_POLL_FREQ_MEAS_TIMEOUT_US);
}
/**
@@ -715,9 +708,6 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
/* Update input references' states */
zl3073x_dev_ref_states_update(zldev);
- /* Update DPLL channels' states */
- zl3073x_dev_chan_states_update(zldev);
-
/* Update DPLL-to-connected-ref phase offsets registers */
rc = zl3073x_ref_phase_offsets_update(zldev, -1);
if (rc)
@@ -727,7 +717,7 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
/* Update measured input reference frequencies if frequency
* monitoring is enabled.
*/
- if (zldev->freq_monitor) {
+ if (READ_ONCE(zldev->freq_monitor)) {
rc = zl3073x_ref_freq_meas_update(zldev);
if (rc)
dev_warn(zldev->dev,
@@ -763,7 +753,7 @@ int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor)
return rc;
/* Save the new factor */
- zldev->phase_avg_factor = factor;
+ WRITE_ONCE(zldev->phase_avg_factor, factor);
return 0;
}
diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
index addba378b0df..78dc208f3eea 100644
--- a/drivers/dpll/zl3073x/core.h
+++ b/drivers/dpll/zl3073x/core.h
@@ -7,6 +7,7 @@
#include <linux/kthread.h>
#include <linux/list.h>
#include <linux/mutex.h>
+#include <linux/time64.h>
#include <linux/types.h>
#include "chan.h"
@@ -19,6 +20,12 @@ struct device;
struct regmap;
struct zl3073x_dpll;
+/* Per-operation poll timeouts */
+#define ZL_POLL_DF_READ_TIMEOUT_US (25 * USEC_PER_MSEC)
+#define ZL_POLL_FREQ_MEAS_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)
enum zl3073x_flags {
ZL3073X_FLAG_REF_PHASE_COMP_32_BIT,
@@ -94,7 +101,7 @@ void zl3073x_dev_stop(struct zl3073x_dev *zldev);
static inline u8 zl3073x_dev_phase_avg_factor_get(struct zl3073x_dev *zldev)
{
- return zldev->phase_avg_factor;
+ return READ_ONCE(zldev->phase_avg_factor);
}
int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor);
@@ -127,7 +134,8 @@ 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);
+int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
+ u8 mask, unsigned int 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);
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 5e58ded5734d..d91f52b58eae 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
-#include <linux/atomic.h>
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/bug.h>
@@ -58,7 +57,7 @@ struct zl3073x_dpll_pin {
s32 phase_gran;
enum dpll_pin_operstate operstate;
s64 phase_offset;
- atomic64_t freq_offset;
+ s64 freq_offset;
u32 measured_freq;
};
@@ -82,6 +81,18 @@ zl3073x_dpll_is_input_pin(struct zl3073x_dpll_pin *pin)
}
/**
+ * 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
*
@@ -120,6 +131,19 @@ zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id)
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,
@@ -134,6 +158,8 @@ zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin,
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);
@@ -170,6 +196,8 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin,
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);
@@ -205,6 +233,8 @@ zl3073x_dpll_input_pin_ref_sync_get(const struct dpll_pin *dpll_pin,
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);
@@ -236,6 +266,8 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin,
struct zl3073x_ref ref;
int rc;
+ guard(mutex)(&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);
@@ -299,12 +331,15 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_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 = atomic64_read(&pin->freq_offset);
+ ffo->ffo = pin->freq_offset;
return 0;
}
@@ -316,8 +351,11 @@ zl3073x_dpll_input_pin_measured_freq_get(const struct dpll_pin *dpll_pin,
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;
@@ -335,6 +373,8 @@ zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin,
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);
@@ -354,6 +394,8 @@ zl3073x_dpll_input_pin_frequency_set(const struct dpll_pin *dpll_pin,
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);
@@ -402,6 +444,8 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
u8 conn_id, ref_id;
s64 ref_phase;
+ guard(mutex)(&zldpll->lock);
+
/* Get currently connected reference */
conn_id = zl3073x_dpll_connected_ref_get(zldpll);
@@ -459,6 +503,8 @@ zl3073x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
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);
@@ -491,6 +537,8 @@ zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
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);
@@ -524,6 +572,8 @@ zl3073x_dpll_ref_operstate_get(struct zl3073x_dpll_pin *pin,
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 */
@@ -557,6 +607,8 @@ zl3073x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
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);
@@ -590,8 +642,11 @@ zl3073x_dpll_input_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin,
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);
}
@@ -605,9 +660,12 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
{
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;
+ int rc = 0;
+
+ mutex_lock(&zldpll->lock);
chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
ref = zl3073x_input_pin_ref_get(pin->id);
@@ -634,6 +692,10 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
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) {
@@ -649,13 +711,13 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
case ZL_DPLL_MODE_REFSEL_MODE_AUTO:
if (state == DPLL_PIN_STATE_SELECTABLE) {
if (zl3073x_chan_ref_is_selectable(&chan, ref))
- return 0; /* Pin is already selectable */
+ 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))
- return 0; /* Pin is already disconnected */
+ goto unlock; /* Pin is already disconnected */
/* Set pin priority to none in HW */
zl3073x_chan_ref_prio_set(&chan, ref,
@@ -668,18 +730,27 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
/* In other modes we cannot change input reference */
NL_SET_ERR_MSG(extack,
"Pin state cannot be changed in current mode");
- return -EOPNOTSUPP;
+ rc = -EOPNOTSUPP;
+ goto unlock;
}
/* Commit DPLL channel changes */
rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
- if (rc)
- return rc;
+ goto unlock;
- return 0;
invalid_state:
NL_SET_ERR_MSG_MOD(extack, "Invalid pin state for this device mode");
- return -EINVAL;
+ 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
@@ -687,8 +758,11 @@ 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;
@@ -705,6 +779,8 @@ zl3073x_dpll_input_pin_prio_set(const struct dpll_pin *dpll_pin, void *pin_priv,
u8 ref;
int rc;
+ guard(mutex)(&zldpll->lock);
+
if (prio > ZL_DPLL_REF_PRIO_MAX)
return -EINVAL;
@@ -740,6 +816,8 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin,
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);
@@ -797,6 +875,8 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
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);
@@ -817,7 +897,7 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
/* If esync is being disabled just write mailbox and finish */
if (!freq)
- goto write_mailbox;
+ return zl3073x_out_state_set(zldev, out_id, &out);
/* Get attached synth frequency */
synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out));
@@ -834,7 +914,6 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
*/
out.esync_n_width = out.div / 2;
-write_mailbox:
/* Commit output configuration */
return zl3073x_out_state_set(zldev, out_id, &out);
}
@@ -849,6 +928,8 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin,
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;
@@ -869,6 +950,8 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
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);
@@ -942,6 +1025,8 @@ zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
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);
@@ -965,6 +1050,8 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
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);
@@ -990,6 +1077,144 @@ zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
}
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)
{
@@ -998,6 +1223,8 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv,
u16 val;
int rc;
+ guard(mutex)(&zldpll->lock);
+
rc = zl3073x_read_u16(zldev, ZL_REG_DIE_TEMP_STATUS, &val);
if (rc)
return rc;
@@ -1009,14 +1236,13 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv,
}
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)
+__zl3073x_dpll_lock_status_get(struct zl3073x_dpll *zldpll,
+ enum dpll_lock_status *status)
{
- struct zl3073x_dpll *zldpll = dpll_priv;
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)) {
@@ -1053,23 +1279,24 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
}
static int
-zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll,
- void *dpll_priv, unsigned long *modes,
- struct netlink_ext_ack *extack)
+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;
- const struct zl3073x_chan *chan;
- chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ guard(mutex)(&zldpll->lock);
- /* We support switching between automatic and manual mode, except in
- * a case where the DPLL channel is configured to run in NCO mode.
- * In this case, report only the manual mode to which the NCO is mapped
- * as the only supported one.
- */
- if (zl3073x_chan_mode_get(chan) != ZL_DPLL_MODE_REFSEL_MODE_NCO)
- __set_bit(DPLL_MODE_AUTOMATIC, modes);
+ 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;
@@ -1082,6 +1309,8 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
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)) {
@@ -1138,8 +1367,8 @@ zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll,
return rc;
}
- /* The averaging factor is common for all DPLL channels so after change
- * we have to send a notification for other DPLL devices.
+ /* 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);
@@ -1156,10 +1385,13 @@ 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);
@@ -1179,6 +1411,9 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
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
@@ -1207,9 +1442,18 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
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;
}
@@ -1221,6 +1465,8 @@ zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll,
{
struct zl3073x_dpll *zldpll = dpll_priv;
+ guard(mutex)(&zldpll->lock);
+
if (zldpll->phase_monitor)
*state = DPLL_FEATURE_STATE_ENABLE;
else
@@ -1237,6 +1483,8 @@ zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll,
{
struct zl3073x_dpll *zldpll = dpll_priv;
+ guard(mutex)(&zldpll->lock);
+
zldpll->phase_monitor = (state == DPLL_FEATURE_STATE_ENABLE);
return 0;
@@ -1250,7 +1498,7 @@ zl3073x_dpll_freq_monitor_get(const struct dpll_device *dpll,
{
struct zl3073x_dpll *zldpll = dpll_priv;
- if (zldpll->dev->freq_monitor)
+ if (READ_ONCE(zldpll->dev->freq_monitor))
*state = DPLL_FEATURE_STATE_ENABLE;
else
*state = DPLL_FEATURE_STATE_DISABLE;
@@ -1265,13 +1513,14 @@ zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *item, *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->dev;
- zldpll->dev->freq_monitor = (state == DPLL_FEATURE_STATE_ENABLE);
+ 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, &zldpll->dev->dplls, list) {
+ list_for_each_entry(item, &zldev->dplls, list) {
struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev);
if (item != zldpll && dpll_dev)
@@ -1313,6 +1562,15 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
.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,
@@ -1460,7 +1718,9 @@ zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin)
WARN(!pin->dpll_pin, "DPLL pin is not registered\n");
- if (zl3073x_dpll_is_input_pin(pin))
+ 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;
@@ -1513,20 +1773,13 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll,
enum dpll_pin_direction dir, u8 index)
{
struct zl3073x_dev *zldev = zldpll->dev;
- const struct zl3073x_chan *chan;
bool is_diff, is_enabled;
const char *name;
- chan = zl3073x_chan_state_get(zldev, zldpll->id);
-
if (dir == DPLL_PIN_DIRECTION_INPUT) {
u8 ref_id = zl3073x_input_pin_ref_get(index);
const struct zl3073x_ref *ref;
- /* Skip the pin if the DPLL is running in NCO mode */
- if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO)
- return false;
-
name = "REF";
ref = zl3073x_ref_state_get(zldev, ref_id);
is_diff = zl3073x_ref_is_diff(ref);
@@ -1567,6 +1820,66 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll,
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
@@ -1614,6 +1927,11 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll)
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:
@@ -1649,8 +1967,8 @@ zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll)
return rc;
}
- rc = dpll_device_register(zldpll->dpll_dev,
- zl3073x_prop_dpll_type_get(zldev, zldpll->id),
+ 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);
@@ -1697,6 +2015,8 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
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))
@@ -1753,16 +2073,27 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
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 (atomic64_xchg(&pin->freq_offset, ffo) != ffo) {
+ 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;
}
@@ -1787,7 +2118,9 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin)
u8 ref_id;
u32 freq;
- if (!zldpll->dev->freq_monitor)
+ lockdep_assert_held(&zldpll->lock);
+
+ if (!READ_ONCE(zldpll->dev->freq_monitor))
return false;
ref_id = zl3073x_input_pin_ref_get(pin->id);
@@ -1817,27 +2150,37 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin)
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++;
- /* Get current lock status for the DPLL */
- rc = zl3073x_dpll_lock_status_get(zldpll->dpll_dev, zldpll,
- &lock_status, NULL, NULL);
+ 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));
- return;
+ goto unlock;
}
- /* 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);
+ dev_changed = true;
}
/* Update phase offset latch registers for this DPLL if the phase
@@ -1849,18 +2192,16 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
dev_err(zldev->dev,
"Failed to update phase offsets: %pe\n",
ERR_PTR(rc));
- return;
+ goto unlock;
}
}
list_for_each_entry(pin, &zldpll->pins, list) {
enum dpll_pin_operstate operstate;
- bool pin_changed = false;
- /* Output pins change checks are not necessary because output
- * states are constant.
- */
- if (!zl3073x_dpll_is_input_pin(pin))
+ /* 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);
@@ -1868,31 +2209,41 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
dev_err(dev,
"Failed to get %s on DPLL%u oper state: %pe\n",
pin->label, zldpll->id, ERR_PTR(rc));
- return;
+ goto unlock;
}
if (operstate != pin->operstate) {
dev_dbg(dev, "%s oper state changed: %u->%u\n",
pin->label, pin->operstate, operstate);
pin->operstate = operstate;
- pin_changed = true;
+ set_bit(pin->id, changed_pins);
}
- /* Check for phase offset, ffo, and measured freq change
- * once per second.
- */
if (zldpll->check_count % 2 == 0) {
if (zl3073x_dpll_pin_phase_offset_check(pin))
- pin_changed = true;
+ set_bit(pin->id, changed_pins);
if (zl3073x_dpll_pin_ffo_check(pin))
- pin_changed = true;
+ set_bit(pin->id, changed_pins);
if (zl3073x_dpll_pin_measured_freq_check(pin))
- pin_changed = true;
+ set_bit(pin->id, changed_pins);
}
+ }
+
+unlock:
+ mutex_unlock(&zldpll->lock);
- if (pin_changed)
+ /* 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);
}
}
@@ -1949,6 +2300,7 @@ zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
zldpll->dev = zldev;
zldpll->id = ch;
+ mutex_init(&zldpll->lock);
INIT_LIST_HEAD(&zldpll->pins);
return zldpll;
@@ -1965,6 +2317,7 @@ zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
{
WARN(zldpll->dpll_dev, "DPLL device is still registered\n");
+ mutex_destroy(&zldpll->lock);
kfree(zldpll);
}
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index 21adcc18e45e..faebc402ba1b 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -18,6 +18,8 @@
* @ops: DPLL device operations for this instance
* @dpll_dev: pointer to registered DPLL device
* @tracker: tracking object for the acquired reference
+ * @lock: per-DPLL mutex serializing all operations
+ * @type: DPLL type (PPS or EEC)
* @lock_status: last saved DPLL lock status
* @pins: list of pins
*/
@@ -30,6 +32,8 @@ struct zl3073x_dpll {
struct dpll_device_ops ops;
struct dpll_device *dpll_dev;
dpll_tracker tracker;
+ struct mutex lock;
+ enum dpll_type type;
enum dpll_lock_status lock_status;
struct list_head pins;
};
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index 9578f0009528..b70ead7d4495 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -5,6 +5,7 @@
#include <linux/bitfield.h>
#include <linux/bits.h>
+#include <linux/limits.h>
/*
* Hardware limits for ZL3073x chip family
@@ -17,6 +18,7 @@
#define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2)
#define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \
ZL3073X_NUM_OUTPUT_PINS)
+#define ZL3073X_NCO_PIN_ID ZL3073X_NUM_PINS
/*
* Register address structure:
@@ -164,10 +166,18 @@
#define ZL_DPLL_MODE_REFSEL_MODE_NCO 4
#define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4)
+#define ZL_REG_DPLL_CTRL(_idx) \
+ ZL_REG_IDX(_idx, 5, 0x05, 1, ZL3073X_MAX_CHANNELS, 4)
+#define ZL_DPLL_CTRL_TIE_CLEAR BIT(0)
+#define ZL_DPLL_CTRL_TOD_STEP_RST BIT(2)
+#define ZL_DPLL_CTRL_NCO_AUTO_READ BIT(7)
+
#define ZL_REG_DPLL_DF_READ(_idx) \
ZL_REG_IDX(_idx, 5, 0x28, 1, ZL3073X_MAX_CHANNELS, 1)
#define ZL_DPLL_DF_READ_SEM BIT(4)
#define ZL_DPLL_DF_READ_REF_OFST BIT(3)
+#define ZL_DPLL_DF_READ_CMD GENMASK(2, 0)
+#define ZL_DPLL_DF_READ_CMD_ACC_I 4
#define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1)
#define ZL_DPLL_MEAS_CTRL_EN BIT(0)
@@ -190,6 +200,7 @@
#define ZL_REG_DPLL_DF_OFFSET_4 ZL_REG(7, 0x00, 6)
#define ZL_REG_DPLL_DF_OFFSET(_idx) \
((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4)
+#define ZL_DPLL_DF_OFFSET_UNKNOWN S64_MIN
/***********************************
* Register Page 9, Synth and Output
diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h
index 55eaa82f5f98..85b898b1db5e 100644
--- a/include/uapi/linux/dpll.h
+++ b/include/uapi/linux/dpll.h
@@ -129,6 +129,9 @@ enum dpll_type {
* @DPLL_PIN_TYPE_SYNCE_ETH_PORT: ethernet port PHY's recovered clock
* @DPLL_PIN_TYPE_INT_OSCILLATOR: device internal oscillator
* @DPLL_PIN_TYPE_GNSS: GNSS recovered clock
+ * @DPLL_PIN_TYPE_INT_NCO: Device internal numerically controlled oscillator.
+ * When connected as a DPLL input, the DPLL enters NCO mode where the output
+ * frequency is adjusted by the host via the PTP clock interface.
*/
enum dpll_pin_type {
DPLL_PIN_TYPE_MUX = 1,
@@ -136,6 +139,7 @@ enum dpll_pin_type {
DPLL_PIN_TYPE_SYNCE_ETH_PORT,
DPLL_PIN_TYPE_INT_OSCILLATOR,
DPLL_PIN_TYPE_GNSS,
+ DPLL_PIN_TYPE_INT_NCO,
/* private: */
__DPLL_PIN_TYPE_MAX,
@@ -208,11 +212,15 @@ enum dpll_pin_operstate {
* @DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE: pin direction can be changed
* @DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE: pin priority can be changed
* @DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE: pin state can be changed
+ * @DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE: pin state can be set to
+ * connected regardless of current DPLL device mode, overriding the active
+ * input selection. Requires state-can-change to be set as well.
*/
enum dpll_pin_capabilities {
DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE = 1,
DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE = 2,
DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE = 4,
+ DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE = 8,
};
#define DPLL_PHASE_OFFSET_DIVIDER 1000