summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2026-03-18 05:05:14 +0300
committerJakub Kicinski <kuba@kernel.org>2026-03-18 05:05:14 +0300
commiteb9744e2c5d06e7007f47dd5853f7c50be189176 (patch)
tree66b56e2d420508c79f2b33e73bac6c78fd3d8d55
parentbb8539e0e60916ef3ed4a92eb2f3cfd8e34061ef (diff)
parentacee049a6af2a7b4baabd28f16fb53628ea6e7a5 (diff)
downloadlinux-eb9744e2c5d06e7007f47dd5853f7c50be189176.tar.xz
Merge branch 'dpll-zl3073x-refactor-state-management'
Ivan Vecera says: ==================== dpll: zl3073x: refactor state management This series refactors the zl3073x DPLL driver to centralize hardware state management behind dedicated per-module state interfaces, replacing scattered direct register accesses in dpll.c with cached state and proper accessor functions. The driver already uses a fetch/get/set pattern for ref, out, and synth modules. This series extends and refines that pattern: First, struct_group() is applied to the existing ref, out, and synth structures to partition fields into cfg (mutable configuration), inv (invariants set at init), and stat (read-only status) groups. This enables group-level memcmp for short-circuit checks and bulk copies in state_set, and adds invariant validation guards. A ref_state_update() helper is extracted to encapsulate the per-reference monitor status register read, keeping direct register access behind the ref module interface. A new zl3073x_chan module is introduced following the same pattern, caching the DPLL channel mode_refsel register with inline getters and setters. The refsel_mode and forced_ref fields are removed from struct zl3073x_dpll in favor of the cached channel state. The chan module is then extended with cached mon_status and refsel_status registers, converting lock_status_get and selected_ref_get from direct HW reads to cached state lookups refreshed by the periodic worker. Reference priority registers are cached in the chan cfg group, removing the ad-hoc ref_prio_get/set functions and the redundant pin->selectable flag, which is now derived from the cached priority. The selected_ref_set function is inlined into input_pin_state_on_dpll_set, unifying all mode paths through a single chan_state_set commit point. Finally, selected_ref_get is dropped entirely since the refsel_status register provides the selected reference regardless of mode, and connected_ref_get is simplified to a direct refsel_state check. ==================== Link: https://patch.msgid.link/20260315174224.399074-1-ivecera@redhat.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-rw-r--r--drivers/dpll/zl3073x/Makefile4
-rw-r--r--drivers/dpll/zl3073x/chan.c165
-rw-r--r--drivers/dpll/zl3073x/chan.h179
-rw-r--r--drivers/dpll/zl3073x/core.c36
-rw-r--r--drivers/dpll/zl3073x/core.h14
-rw-r--r--drivers/dpll/zl3073x/dpll.c490
-rw-r--r--drivers/dpll/zl3073x/dpll.h4
-rw-r--r--drivers/dpll/zl3073x/out.c27
-rw-r--r--drivers/dpll/zl3073x/out.h21
-rw-r--r--drivers/dpll/zl3073x/ref.c58
-rw-r--r--drivers/dpll/zl3073x/ref.h33
-rw-r--r--drivers/dpll/zl3073x/regs.h12
-rw-r--r--drivers/dpll/zl3073x/synth.h16
13 files changed, 627 insertions, 432 deletions
diff --git a/drivers/dpll/zl3073x/Makefile b/drivers/dpll/zl3073x/Makefile
index bd324c7fe710..906ec3fbcc20 100644
--- a/drivers/dpll/zl3073x/Makefile
+++ b/drivers/dpll/zl3073x/Makefile
@@ -1,8 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_ZL3073X) += zl3073x.o
-zl3073x-objs := core.o devlink.o dpll.o flash.o fw.o \
- out.o prop.o ref.o synth.o
+zl3073x-objs := chan.o core.o devlink.o dpll.o \
+ flash.o fw.o out.o prop.o ref.o synth.o
obj-$(CONFIG_ZL3073X_I2C) += zl3073x_i2c.o
zl3073x_i2c-objs := i2c.o
diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
new file mode 100644
index 000000000000..2f48ca239149
--- /dev/null
+++ b/drivers/dpll/zl3073x/chan.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/cleanup.h>
+#include <linux/dev_printk.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include "chan.h"
+#include "core.h"
+
+/**
+ * zl3073x_chan_state_update - update DPLL channel status from HW
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: DPLL channel index
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
+{
+ struct zl3073x_chan *chan = &zldev->chan[index];
+ int rc;
+
+ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(index),
+ &chan->mon_status);
+ if (rc)
+ return rc;
+
+ return zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
+ &chan->refsel_status);
+}
+
+/**
+ * zl3073x_chan_state_fetch - fetch DPLL channel state from hardware
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: DPLL channel index to fetch state for
+ *
+ * Reads the mode_refsel register and reference priority registers for
+ * the given DPLL channel and stores the raw values for later use.
+ *
+ * Return: 0 on success, <0 on error
+ */
+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_MODE_REFSEL(index),
+ &chan->mode_refsel);
+ if (rc)
+ return rc;
+
+ dev_dbg(zldev->dev, "DPLL%u mode: %u, ref: %u\n", index,
+ zl3073x_chan_mode_get(chan), zl3073x_chan_ref_get(chan));
+
+ rc = zl3073x_chan_state_update(zldev, index);
+ if (rc)
+ return rc;
+
+ 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),
+ zl3073x_chan_is_ho_ready(chan) ? 1 : 0,
+ zl3073x_chan_refsel_state_get(chan),
+ zl3073x_chan_refsel_ref_get(chan));
+
+ guard(mutex)(&zldev->multiop_lock);
+
+ /* Read DPLL configuration from mailbox */
+ rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD,
+ ZL_REG_DPLL_MB_MASK, BIT(index));
+ 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),
+ &chan->ref_prio[i]);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * zl3073x_chan_state_get - get current DPLL channel state
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: DPLL channel index to get state for
+ *
+ * Return: pointer to given DPLL channel state
+ */
+const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,
+ u8 index)
+{
+ return &zldev->chan[index];
+}
+
+/**
+ * zl3073x_chan_state_set - commit DPLL channel state changes to hardware
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: DPLL channel index to set state for
+ * @chan: desired channel state
+ *
+ * Skips the HW write if the configuration is unchanged, and otherwise
+ * writes only the changed registers to hardware. The mode_refsel register
+ * is written directly, while the reference priority registers are written
+ * via the DPLL mailbox interface.
+ *
+ * Return: 0 on success, <0 on HW error
+ */
+int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
+ const struct zl3073x_chan *chan)
+{
+ struct zl3073x_chan *dchan = &zldev->chan[index];
+ int rc, i;
+
+ /* Skip HW write if configuration hasn't changed */
+ if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg)))
+ return 0;
+
+ /* Direct register write for mode_refsel */
+ if (dchan->mode_refsel != chan->mode_refsel) {
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
+ chan->mode_refsel);
+ if (rc)
+ return rc;
+ 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;
+ return 0;
+ }
+
+ 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(index));
+ 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]) {
+ rc = zl3073x_write_u8(zldev,
+ ZL_REG_DPLL_REF_PRIO(i),
+ chan->ref_prio[i]);
+ if (rc)
+ return rc;
+ }
+ }
+
+ /* Commit DPLL configuration */
+ rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_WR,
+ ZL_REG_DPLL_MB_MASK, BIT(index));
+ if (rc)
+ return rc;
+
+ /* After successful write store new state */
+ dchan->cfg = chan->cfg;
+
+ return 0;
+}
diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h
new file mode 100644
index 000000000000..e0f02d343208
--- /dev/null
+++ b/drivers/dpll/zl3073x/chan.h
@@ -0,0 +1,179 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef _ZL3073X_CHAN_H
+#define _ZL3073X_CHAN_H
+
+#include <linux/bitfield.h>
+#include <linux/stddef.h>
+#include <linux/types.h>
+
+#include "regs.h"
+
+struct zl3073x_dev;
+
+/**
+ * struct zl3073x_chan - DPLL channel state
+ * @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
+ * @refsel_status: reference selection status register value
+ */
+struct zl3073x_chan {
+ struct_group(cfg,
+ u8 mode_refsel;
+ u8 ref_prio[ZL3073X_NUM_REFS / 2];
+ );
+ struct_group(stat,
+ u8 mon_status;
+ u8 refsel_status;
+ );
+};
+
+int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index);
+const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,
+ u8 index);
+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);
+
+/**
+ * zl3073x_chan_mode_get - get DPLL channel operating mode
+ * @chan: pointer to channel state
+ *
+ * Return: reference selection mode of the given DPLL channel
+ */
+static inline u8 zl3073x_chan_mode_get(const struct zl3073x_chan *chan)
+{
+ return FIELD_GET(ZL_DPLL_MODE_REFSEL_MODE, chan->mode_refsel);
+}
+
+/**
+ * zl3073x_chan_ref_get - get manually selected reference
+ * @chan: pointer to channel state
+ *
+ * Return: reference selected in forced reference lock mode
+ */
+static inline u8 zl3073x_chan_ref_get(const struct zl3073x_chan *chan)
+{
+ return FIELD_GET(ZL_DPLL_MODE_REFSEL_REF, chan->mode_refsel);
+}
+
+/**
+ * zl3073x_chan_mode_set - set DPLL channel operating mode
+ * @chan: pointer to channel state
+ * @mode: mode to set
+ */
+static inline void zl3073x_chan_mode_set(struct zl3073x_chan *chan, u8 mode)
+{
+ chan->mode_refsel &= ~ZL_DPLL_MODE_REFSEL_MODE;
+ chan->mode_refsel |= FIELD_PREP(ZL_DPLL_MODE_REFSEL_MODE, mode);
+}
+
+/**
+ * zl3073x_chan_ref_set - set manually selected reference
+ * @chan: pointer to channel state
+ * @ref: reference to set
+ */
+static inline void zl3073x_chan_ref_set(struct zl3073x_chan *chan, u8 ref)
+{
+ chan->mode_refsel &= ~ZL_DPLL_MODE_REFSEL_REF;
+ chan->mode_refsel |= FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref);
+}
+
+/**
+ * zl3073x_chan_ref_prio_get - get reference priority
+ * @chan: pointer to channel state
+ * @ref: input reference index
+ *
+ * Return: priority of the given reference <0, 15>
+ */
+static inline u8
+zl3073x_chan_ref_prio_get(const struct zl3073x_chan *chan, u8 ref)
+{
+ u8 val = chan->ref_prio[ref / 2];
+
+ if (!(ref & 1))
+ return FIELD_GET(ZL_DPLL_REF_PRIO_REF_P, val);
+ else
+ return FIELD_GET(ZL_DPLL_REF_PRIO_REF_N, val);
+}
+
+/**
+ * zl3073x_chan_ref_prio_set - set reference priority
+ * @chan: pointer to channel state
+ * @ref: input reference index
+ * @prio: priority to set
+ */
+static inline void
+zl3073x_chan_ref_prio_set(struct zl3073x_chan *chan, u8 ref, u8 prio)
+{
+ u8 *val = &chan->ref_prio[ref / 2];
+
+ if (!(ref & 1)) {
+ *val &= ~ZL_DPLL_REF_PRIO_REF_P;
+ *val |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_P, prio);
+ } else {
+ *val &= ~ZL_DPLL_REF_PRIO_REF_N;
+ *val |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_N, prio);
+ }
+}
+
+/**
+ * zl3073x_chan_ref_is_selectable - check if reference is selectable
+ * @chan: pointer to channel state
+ * @ref: input reference index
+ *
+ * Return: true if the reference priority is not NONE, false otherwise
+ */
+static inline bool
+zl3073x_chan_ref_is_selectable(const struct zl3073x_chan *chan, u8 ref)
+{
+ return zl3073x_chan_ref_prio_get(chan, ref) != ZL_DPLL_REF_PRIO_NONE;
+}
+
+/**
+ * zl3073x_chan_lock_state_get - get DPLL channel lock state
+ * @chan: pointer to channel state
+ *
+ * Return: lock state of the given DPLL channel
+ */
+static inline u8 zl3073x_chan_lock_state_get(const struct zl3073x_chan *chan)
+{
+ return FIELD_GET(ZL_DPLL_MON_STATUS_STATE, chan->mon_status);
+}
+
+/**
+ * zl3073x_chan_is_ho_ready - check if holdover is ready
+ * @chan: pointer to channel state
+ *
+ * Return: true if holdover is ready, false otherwise
+ */
+static inline bool zl3073x_chan_is_ho_ready(const struct zl3073x_chan *chan)
+{
+ return !!FIELD_GET(ZL_DPLL_MON_STATUS_HO_READY, chan->mon_status);
+}
+
+/**
+ * zl3073x_chan_refsel_state_get - get reference selection state
+ * @chan: pointer to channel state
+ *
+ * Return: reference selection state of the given DPLL channel
+ */
+static inline u8 zl3073x_chan_refsel_state_get(const struct zl3073x_chan *chan)
+{
+ return FIELD_GET(ZL_DPLL_REFSEL_STATUS_STATE, chan->refsel_status);
+}
+
+/**
+ * zl3073x_chan_refsel_ref_get - get currently selected reference in auto mode
+ * @chan: pointer to channel state
+ *
+ * Return: reference selected by the DPLL in automatic mode
+ */
+static inline u8 zl3073x_chan_refsel_ref_get(const struct zl3073x_chan *chan)
+{
+ return FIELD_GET(ZL_DPLL_REFSEL_STATUS_REFSEL, chan->refsel_status);
+}
+
+#endif /* _ZL3073X_CHAN_H */
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 10e036ccf08f..6363002d48d4 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -539,17 +539,26 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev)
}
}
+ for (i = 0; i < zldev->info->num_channels; i++) {
+ rc = zl3073x_chan_state_fetch(zldev, i);
+ if (rc) {
+ dev_err(zldev->dev,
+ "Failed to fetch channel state: %pe\n",
+ ERR_PTR(rc));
+ return rc;
+ }
+ }
+
return rc;
}
static void
-zl3073x_dev_ref_status_update(struct zl3073x_dev *zldev)
+zl3073x_dev_ref_states_update(struct zl3073x_dev *zldev)
{
int i, rc;
for (i = 0; i < ZL3073X_NUM_REFS; i++) {
- rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(i),
- &zldev->ref[i].mon_status);
+ rc = zl3073x_ref_state_update(zldev, i);
if (rc)
dev_warn(zldev->dev,
"Failed to get REF%u status: %pe\n", i,
@@ -557,6 +566,20 @@ zl3073x_dev_ref_status_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
* @zldev: pointer to zl3073x_dev structure
@@ -679,8 +702,11 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
struct zl3073x_dpll *zldpll;
int rc;
- /* Update input references status */
- zl3073x_dev_ref_status_update(zldev);
+ /* 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);
diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
index b6f22ee1c0bd..99440620407d 100644
--- a/drivers/dpll/zl3073x/core.h
+++ b/drivers/dpll/zl3073x/core.h
@@ -9,6 +9,7 @@
#include <linux/mutex.h>
#include <linux/types.h>
+#include "chan.h"
#include "out.h"
#include "ref.h"
#include "regs.h"
@@ -18,17 +19,6 @@ struct device;
struct regmap;
struct zl3073x_dpll;
-/*
- * Hardware limits for ZL3073x chip family
- */
-#define ZL3073X_MAX_CHANNELS 5
-#define ZL3073X_NUM_REFS 10
-#define ZL3073X_NUM_OUTS 10
-#define ZL3073X_NUM_SYNTHS 5
-#define ZL3073X_NUM_INPUT_PINS ZL3073X_NUM_REFS
-#define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2)
-#define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \
- ZL3073X_NUM_OUTPUT_PINS)
enum zl3073x_flags {
ZL3073X_FLAG_REF_PHASE_COMP_32_BIT,
@@ -61,6 +51,7 @@ struct zl3073x_chip_info {
* @ref: array of input references' invariants
* @out: array of outs' invariants
* @synth: array of synths' invariants
+ * @chan: array of DPLL channels' state
* @dplls: list of DPLLs
* @kworker: thread for periodic work
* @work: periodic work
@@ -77,6 +68,7 @@ struct zl3073x_dev {
struct zl3073x_ref ref[ZL3073X_NUM_REFS];
struct zl3073x_out out[ZL3073X_NUM_OUTS];
struct zl3073x_synth synth[ZL3073X_NUM_SYNTHS];
+ struct zl3073x_chan chan[ZL3073X_MAX_CHANNELS];
/* DPLL channels */
struct list_head dplls;
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index c201c974a7f9..a29f606318f6 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -34,7 +34,6 @@
* @dir: pin direction
* @id: pin id
* @prio: pin priority <0, 14>
- * @selectable: pin is selectable in automatic mode
* @esync_control: embedded sync is controllable
* @phase_gran: phase adjustment granularity
* @pin_state: last saved pin state
@@ -50,7 +49,6 @@ struct zl3073x_dpll_pin {
enum dpll_pin_direction dir;
u8 id;
u8 prio;
- bool selectable;
bool esync_control;
s32 phase_gran;
enum dpll_pin_state pin_state;
@@ -246,155 +244,26 @@ zl3073x_dpll_input_pin_frequency_set(const struct dpll_pin *dpll_pin,
}
/**
- * 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.
+ * Looks for currently connected reference the DPLL is locked to.
*
- * Return: 0 on success, <0 on error
+ * Return: reference index if locked, ZL3073X_DPLL_REF_NONE otherwise
*/
-static int
-zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref)
+static u8
+zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll)
{
- 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;
+ const struct zl3073x_chan *chan = zl3073x_chan_state_get(zldpll->dev,
+ zldpll->id);
+ u8 state;
- /* If the monitor indicates an error nothing is connected */
- if (ZL3073X_DPLL_REF_IS_VALID(*ref) &&
- !zl3073x_dev_ref_is_status_ok(zldev, *ref))
- *ref = ZL3073X_DPLL_REF_NONE;
+ /* A reference is connected only when the DPLL is locked to it */
+ state = zl3073x_chan_refsel_state_get(chan);
+ if (state == ZL_DPLL_REFSEL_STATUS_STATE_LOCK)
+ return zl3073x_chan_refsel_ref_get(chan);
- return 0;
+ return ZL3073X_DPLL_REF_NONE;
}
static int
@@ -410,12 +279,9 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
const struct zl3073x_ref *ref;
u8 conn_id, ref_id;
s64 ref_phase;
- int rc;
/* Get currently connected reference */
- rc = zl3073x_dpll_connected_ref_get(zldpll, &conn_id);
- if (rc)
- return rc;
+ conn_id = zl3073x_dpll_connected_ref_get(zldpll);
/* Report phase offset only for currently connected pin if the phase
* monitor feature is disabled and only if the input pin signal is
@@ -453,7 +319,7 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
*phase_offset = ref_phase * DPLL_PHASE_OFFSET_DIVIDER;
- return rc;
+ return 0;
}
static int
@@ -517,98 +383,6 @@ zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
}
/**
- * 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
@@ -624,17 +398,14 @@ zl3073x_dpll_ref_state_get(struct zl3073x_dpll_pin *pin,
{
struct zl3073x_dpll *zldpll = pin->dpll;
struct zl3073x_dev *zldev = zldpll->dev;
- u8 ref, ref_conn;
- int rc;
+ const struct zl3073x_chan *chan;
+ u8 ref;
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
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) {
+ /* Check if the pin reference is connected */
+ if (ref == zl3073x_dpll_connected_ref_get(zldpll)) {
*state = DPLL_PIN_STATE_CONNECTED;
return 0;
}
@@ -643,8 +414,9 @@ zl3073x_dpll_ref_state_get(struct zl3073x_dpll_pin *pin,
* selectable and its monitor does not report any error then report
* pin as selectable.
*/
- if (zldpll->refsel_mode == ZL_DPLL_MODE_REFSEL_MODE_AUTO &&
- zl3073x_dev_ref_is_status_ok(zldev, ref) && pin->selectable) {
+ if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO &&
+ zl3073x_dev_ref_is_status_ok(zldev, ref) &&
+ zl3073x_chan_ref_is_selectable(chan, ref)) {
*state = DPLL_PIN_STATE_SELECTABLE;
return 0;
}
@@ -678,68 +450,81 @@ 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;
- u8 new_ref;
+ struct zl3073x_chan chan;
+ u8 mode, ref;
int rc;
- switch (zldpll->refsel_mode) {
+ chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ ref = zl3073x_input_pin_ref_get(pin->id);
+ mode = zl3073x_chan_mode_get(&chan);
+
+ switch (mode) {
case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK:
- 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);
+ zl3073x_chan_ref_set(&chan, ref);
} else if (state == DPLL_PIN_STATE_DISCONNECTED) {
- /* No reference */
- new_ref = ZL3073X_DPLL_REF_NONE;
+ /* Choose new mode based on lock status */
+ switch (zldpll->lock_status) {
+ case DPLL_LOCK_STATUS_LOCKED_HO_ACQ:
+ case DPLL_LOCK_STATUS_HOLDOVER:
+ mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER;
+ break;
+ default:
+ mode = ZL_DPLL_MODE_REFSEL_MODE_FREERUN;
+ break;
+ }
+ zl3073x_chan_mode_set(&chan, mode);
} else {
- NL_SET_ERR_MSG_MOD(extack,
- "Invalid pin state for manual mode");
- return -EINVAL;
+ goto invalid_state;
+ }
+ break;
+ case ZL_DPLL_MODE_REFSEL_MODE_FREERUN:
+ case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER:
+ if (state == DPLL_PIN_STATE_CONNECTED) {
+ /* Choose the pin as new selected reference */
+ zl3073x_chan_ref_set(&chan, ref);
+ /* Switch to reflock mode */
+ zl3073x_chan_mode_set(&chan,
+ ZL_DPLL_MODE_REFSEL_MODE_REFLOCK);
+ } else if (state != DPLL_PIN_STATE_DISCONNECTED) {
+ goto invalid_state;
}
-
- 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)
+ if (zl3073x_chan_ref_is_selectable(&chan, ref))
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;
+ zl3073x_chan_ref_prio_set(&chan, ref, pin->prio);
} else if (state == DPLL_PIN_STATE_DISCONNECTED) {
- if (!pin->selectable)
+ if (!zl3073x_chan_ref_is_selectable(&chan, ref))
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;
+ zl3073x_chan_ref_prio_set(&chan, ref,
+ ZL_DPLL_REF_PRIO_NONE);
} else {
- NL_SET_ERR_MSG(extack,
- "Invalid pin state for automatic mode");
- return -EINVAL;
+ goto invalid_state;
}
break;
-
default:
/* In other modes we cannot change input reference */
NL_SET_ERR_MSG(extack,
"Pin state cannot be changed in current mode");
- rc = -EOPNOTSUPP;
- break;
+ return -EOPNOTSUPP;
}
- return rc;
+ /* Commit DPLL channel changes */
+ rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
+ if (rc)
+ return rc;
+
+ return 0;
+invalid_state:
+ NL_SET_ERR_MSG_MOD(extack, "Invalid pin state for this device mode");
+ return -EINVAL;
}
static int
@@ -759,15 +544,21 @@ zl3073x_dpll_input_pin_prio_set(const struct dpll_pin *dpll_pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
u32 prio, struct netlink_ext_ack *extack)
{
+ struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
+ struct zl3073x_chan chan;
+ u8 ref;
int rc;
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);
+ chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ ref = zl3073x_input_pin_ref_get(pin->id);
+ if (zl3073x_chan_ref_is_selectable(&chan, ref)) {
+ zl3073x_chan_ref_prio_set(&chan, ref, prio);
+ rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
if (rc)
return rc;
}
@@ -1091,11 +882,11 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
- struct zl3073x_dev *zldev = zldpll->dev;
- u8 mon_status, state;
- int rc;
+ const struct zl3073x_chan *chan;
- switch (zldpll->refsel_mode) {
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+
+ switch (zl3073x_chan_mode_get(chan)) {
case ZL_DPLL_MODE_REFSEL_MODE_FREERUN:
case ZL_DPLL_MODE_REFSEL_MODE_NCO:
/* In FREERUN and NCO modes the DPLL is always unlocked */
@@ -1106,16 +897,9 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
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) {
+ switch (zl3073x_chan_lock_state_get(chan)) {
case ZL_DPLL_MON_STATUS_STATE_LOCK:
- if (FIELD_GET(ZL_DPLL_MON_STATUS_HO_READY, mon_status))
+ if (zl3073x_chan_is_ho_ready(chan))
*status = DPLL_LOCK_STATUS_LOCKED_HO_ACQ;
else
*status = DPLL_LOCK_STATUS_LOCKED;
@@ -1125,8 +909,9 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
*status = DPLL_LOCK_STATUS_HOLDOVER;
break;
default:
- dev_warn(zldev->dev, "Unknown DPLL monitor status: 0x%02x\n",
- mon_status);
+ dev_warn(zldpll->dev->dev,
+ "Unknown DPLL monitor status: 0x%02x\n",
+ chan->mon_status);
*status = DPLL_LOCK_STATUS_UNLOCKED;
break;
}
@@ -1140,13 +925,16 @@ zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll,
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);
/* 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 (zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_NCO)
+ if (zl3073x_chan_mode_get(chan) != ZL_DPLL_MODE_REFSEL_MODE_NCO)
__set_bit(DPLL_MODE_AUTOMATIC, modes);
__set_bit(DPLL_MODE_MANUAL, modes);
@@ -1159,8 +947,11 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
enum dpll_mode *mode, struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
+ const struct zl3073x_chan *chan;
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
- switch (zldpll->refsel_mode) {
+ switch (zl3073x_chan_mode_get(chan)) {
case ZL_DPLL_MODE_REFSEL_MODE_FREERUN:
case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER:
case ZL_DPLL_MODE_REFSEL_MODE_NCO:
@@ -1239,14 +1030,12 @@ 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;
- u8 hw_mode, mode_refsel, ref;
+ struct zl3073x_chan chan;
+ u8 hw_mode, ref;
int rc;
- rc = zl3073x_dpll_selected_ref_get(zldpll, &ref);
- if (rc) {
- NL_SET_ERR_MSG_MOD(extack, "failed to get selected reference");
- return rc;
- }
+ chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ ref = zl3073x_chan_refsel_ref_get(&chan);
if (mode == DPLL_MODE_MANUAL) {
/* We are switching from automatic to manual mode:
@@ -1269,44 +1058,32 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
* it is selectable after switch to automatic mode
* - switch to automatic mode
*/
- struct zl3073x_dpll_pin *pin;
-
- pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref);
- if (pin && !pin->selectable) {
- /* Restore pin priority in HW */
- rc = zl3073x_dpll_ref_prio_set(pin, pin->prio);
- if (rc) {
- NL_SET_ERR_MSG_MOD(extack,
- "failed to restore pin priority");
- return rc;
+ if (ZL3073X_DPLL_REF_IS_VALID(ref) &&
+ !zl3073x_chan_ref_is_selectable(&chan, ref)) {
+ struct zl3073x_dpll_pin *pin;
+
+ pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref);
+ if (pin) {
+ /* Restore pin priority in HW */
+ zl3073x_chan_ref_prio_set(&chan, ref,
+ pin->prio);
}
-
- pin->selectable = true;
}
hw_mode = ZL_DPLL_MODE_REFSEL_MODE_AUTO;
}
- /* Build mode_refsel value */
- mode_refsel = FIELD_PREP(ZL_DPLL_MODE_REFSEL_MODE, hw_mode);
-
+ zl3073x_chan_mode_set(&chan, hw_mode);
if (ZL3073X_DPLL_REF_IS_VALID(ref))
- mode_refsel |= FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref);
+ zl3073x_chan_ref_set(&chan, ref);
- /* Update dpll_mode_refsel register */
- rc = zl3073x_write_u8(zldpll->dev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id),
- mode_refsel);
+ rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
if (rc) {
NL_SET_ERR_MSG_MOD(extack,
"failed to set reference selection mode");
return rc;
}
- zldpll->refsel_mode = hw_mode;
-
- if (ZL3073X_DPLL_REF_IS_VALID(ref))
- zldpll->forced_ref = ref;
-
return 0;
}
@@ -1447,18 +1224,16 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
pin->phase_gran = props->dpll_props.phase_gran;
if (zl3073x_dpll_is_input_pin(pin)) {
- rc = zl3073x_dpll_ref_prio_get(pin, &pin->prio);
- if (rc)
- goto err_prio_get;
+ const struct zl3073x_chan *chan;
+ u8 ref;
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ ref = zl3073x_input_pin_ref_get(pin->id);
+ pin->prio = zl3073x_chan_ref_prio_get(chan, ref);
- if (pin->prio == ZL_DPLL_REF_PRIO_NONE) {
- /* Clamp prio to max value & mark pin non-selectable */
+ if (pin->prio == ZL_DPLL_REF_PRIO_NONE)
+ /* Clamp prio to max value */
pin->prio = ZL_DPLL_REF_PRIO_MAX;
- pin->selectable = false;
- } else {
- /* Mark pin as selectable */
- pin->selectable = true;
- }
}
/* Create or get existing DPLL pin */
@@ -1487,7 +1262,6 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
err_register:
dpll_pin_put(pin->dpll_pin, &pin->tracker);
-err_prio_get:
pin->dpll_pin = NULL;
err_pin_get:
zl3073x_pin_props_put(props);
@@ -1559,15 +1333,18 @@ 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 (zldpll->refsel_mode == ZL_DPLL_MODE_REFSEL_MODE_NCO)
+ if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO)
return false;
name = "REF";
@@ -1675,21 +1452,8 @@ 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->ops = zl3073x_dpll_device_ops;
if (zldev->info->flags & ZL3073X_FLAG_DIE_TEMP)
zldpll->ops.temp_get = zl3073x_dpll_temp_get;
@@ -1844,8 +1608,10 @@ 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;
+ const struct zl3073x_chan *chan;
struct zl3073x_dpll_pin *pin;
int rc;
+ u8 mode;
zldpll->check_count++;
@@ -1867,8 +1633,10 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
/* 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)
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
+ mode = zl3073x_chan_mode_get(chan);
+ if (mode != ZL_DPLL_MODE_REFSEL_MODE_AUTO &&
+ mode != ZL_DPLL_MODE_REFSEL_MODE_REFLOCK)
return;
/* Update phase offset latch registers for this DPLL if the phase
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index 278a24f357c9..115ee4f67e7a 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -13,8 +13,6 @@
* @list: this DPLL list entry
* @dev: pointer to multi-function parent device
* @id: DPLL index
- * @refsel_mode: reference selection mode
- * @forced_ref: selected reference in forced reference lock mode
* @check_count: periodic check counter
* @phase_monitor: is phase offset monitor enabled
* @ops: DPLL device operations for this instance
@@ -28,8 +26,6 @@ struct zl3073x_dpll {
struct list_head list;
struct zl3073x_dev *dev;
u8 id;
- u8 refsel_mode;
- u8 forced_ref;
u8 check_count;
bool phase_monitor;
struct dpll_device_ops ops;
diff --git a/drivers/dpll/zl3073x/out.c b/drivers/dpll/zl3073x/out.c
index 86829a0c1c02..eb5628aebcee 100644
--- a/drivers/dpll/zl3073x/out.c
+++ b/drivers/dpll/zl3073x/out.c
@@ -106,12 +106,32 @@ const struct zl3073x_out *zl3073x_out_state_get(struct zl3073x_dev *zldev,
return &zldev->out[index];
}
+/**
+ * zl3073x_out_state_set - commit output state changes to hardware
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: output index to set state for
+ * @out: desired output state
+ *
+ * Validates that invariant fields have not been modified, skips the HW
+ * write if the mutable configuration is unchanged, and otherwise writes
+ * only the changed cfg fields to hardware via the mailbox interface.
+ *
+ * Return: 0 on success, -EINVAL if invariants changed, <0 on HW error
+ */
int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_out *out)
{
struct zl3073x_out *dout = &zldev->out[index];
int rc;
+ /* Reject attempts to change invariant fields (set at fetch only) */
+ if (WARN_ON(memcmp(&dout->inv, &out->inv, sizeof(out->inv))))
+ return -EINVAL;
+
+ /* Skip HW write if configuration hasn't changed */
+ if (!memcmp(&dout->cfg, &out->cfg, sizeof(out->cfg)))
+ return 0;
+
guard(mutex)(&zldev->multiop_lock);
/* Read output configuration into mailbox */
@@ -146,12 +166,7 @@ int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index,
return rc;
/* After successful commit store new state */
- dout->div = out->div;
- dout->width = out->width;
- dout->esync_n_period = out->esync_n_period;
- dout->esync_n_width = out->esync_n_width;
- dout->mode = out->mode;
- dout->phase_comp = out->phase_comp;
+ dout->cfg = out->cfg;
return 0;
}
diff --git a/drivers/dpll/zl3073x/out.h b/drivers/dpll/zl3073x/out.h
index 318f9bb8da3a..edf40432bba5 100644
--- a/drivers/dpll/zl3073x/out.h
+++ b/drivers/dpll/zl3073x/out.h
@@ -4,6 +4,7 @@
#define _ZL3073X_OUT_H
#include <linux/bitfield.h>
+#include <linux/stddef.h>
#include <linux/types.h>
#include "regs.h"
@@ -17,17 +18,21 @@ struct zl3073x_dev;
* @esync_n_period: embedded sync or n-pin period (for n-div formats)
* @esync_n_width: embedded sync or n-pin pulse width
* @phase_comp: phase compensation
- * @ctrl: output control
* @mode: output mode
+ * @ctrl: output control
*/
struct zl3073x_out {
- u32 div;
- u32 width;
- u32 esync_n_period;
- u32 esync_n_width;
- s32 phase_comp;
- u8 ctrl;
- u8 mode;
+ struct_group(cfg, /* Config */
+ u32 div;
+ u32 width;
+ u32 esync_n_period;
+ u32 esync_n_width;
+ s32 phase_comp;
+ u8 mode;
+ );
+ struct_group(inv, /* Invariants */
+ u8 ctrl;
+ );
};
int zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index);
diff --git a/drivers/dpll/zl3073x/ref.c b/drivers/dpll/zl3073x/ref.c
index 6b65e6103999..825ac30bcd6f 100644
--- a/drivers/dpll/zl3073x/ref.c
+++ b/drivers/dpll/zl3073x/ref.c
@@ -52,6 +52,21 @@ zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult)
}
/**
+ * zl3073x_ref_state_update - update input reference status from HW
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: input reference index
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_ref_state_update(struct zl3073x_dev *zldev, u8 index)
+{
+ struct zl3073x_ref *ref = &zldev->ref[index];
+
+ return zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(index),
+ &ref->mon_status);
+}
+
+/**
* zl3073x_ref_state_fetch - fetch input reference state from hardware
* @zldev: pointer to zl3073x_dev structure
* @index: input reference index to fetch state for
@@ -73,18 +88,17 @@ int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index)
struct zl3073x_ref *p_ref = ref - 1; /* P-pin counterpart*/
/* Copy the shared items from the P-pin */
- ref->config = p_ref->config;
- ref->esync_n_div = p_ref->esync_n_div;
- ref->freq_base = p_ref->freq_base;
- ref->freq_mult = p_ref->freq_mult;
- ref->freq_ratio_m = p_ref->freq_ratio_m;
- ref->freq_ratio_n = p_ref->freq_ratio_n;
- ref->phase_comp = p_ref->phase_comp;
- ref->sync_ctrl = p_ref->sync_ctrl;
+ ref->cfg = p_ref->cfg;
+ ref->inv = p_ref->inv;
return 0; /* Finish - no non-shared items for now */
}
+ /* Read reference status */
+ rc = zl3073x_ref_state_update(zldev, index);
+ if (rc)
+ return rc;
+
guard(mutex)(&zldev->multiop_lock);
/* Read reference configuration */
@@ -154,12 +168,32 @@ zl3073x_ref_state_get(struct zl3073x_dev *zldev, u8 index)
return &zldev->ref[index];
}
+/**
+ * zl3073x_ref_state_set - commit input reference state changes to hardware
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: input reference index to set state for
+ * @ref: desired reference state
+ *
+ * Validates that invariant fields have not been modified, skips the HW
+ * write if the mutable configuration is unchanged, and otherwise writes
+ * only the changed cfg fields to hardware via the mailbox interface.
+ *
+ * Return: 0 on success, -EINVAL if invariants changed, <0 on HW error
+ */
int zl3073x_ref_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_ref *ref)
{
struct zl3073x_ref *dref = &zldev->ref[index];
int rc;
+ /* Reject attempts to change invariant fields (set at init only) */
+ if (WARN_ON(memcmp(&dref->inv, &ref->inv, sizeof(ref->inv))))
+ return -EINVAL;
+
+ /* Skip HW write if configuration hasn't changed */
+ if (!memcmp(&dref->cfg, &ref->cfg, sizeof(ref->cfg)))
+ return 0;
+
guard(mutex)(&zldev->multiop_lock);
/* Read reference configuration into mailbox */
@@ -207,13 +241,7 @@ int zl3073x_ref_state_set(struct zl3073x_dev *zldev, u8 index,
return rc;
/* After successful commit store new state */
- dref->freq_base = ref->freq_base;
- dref->freq_mult = ref->freq_mult;
- dref->freq_ratio_m = ref->freq_ratio_m;
- dref->freq_ratio_n = ref->freq_ratio_n;
- dref->esync_n_div = ref->esync_n_div;
- dref->sync_ctrl = ref->sync_ctrl;
- dref->phase_comp = ref->phase_comp;
+ dref->cfg = ref->cfg;
return 0;
}
diff --git a/drivers/dpll/zl3073x/ref.h b/drivers/dpll/zl3073x/ref.h
index 0d8618f5ce8d..06d8d4d97ea2 100644
--- a/drivers/dpll/zl3073x/ref.h
+++ b/drivers/dpll/zl3073x/ref.h
@@ -5,6 +5,7 @@
#include <linux/bitfield.h>
#include <linux/math64.h>
+#include <linux/stddef.h>
#include <linux/types.h>
#include "regs.h"
@@ -13,28 +14,34 @@ struct zl3073x_dev;
/**
* struct zl3073x_ref - input reference state
- * @ffo: current fractional frequency offset
* @phase_comp: phase compensation
* @esync_n_div: divisor for embedded sync or n-divided signal formats
* @freq_base: frequency base
* @freq_mult: frequnecy multiplier
* @freq_ratio_m: FEC mode multiplier
* @freq_ratio_n: FEC mode divisor
- * @config: reference config
* @sync_ctrl: reference sync control
+ * @config: reference config
+ * @ffo: current fractional frequency offset
* @mon_status: reference monitor status
*/
struct zl3073x_ref {
- s64 ffo;
- u64 phase_comp;
- u32 esync_n_div;
- u16 freq_base;
- u16 freq_mult;
- u16 freq_ratio_m;
- u16 freq_ratio_n;
- u8 config;
- u8 sync_ctrl;
- u8 mon_status;
+ struct_group(cfg, /* Configuration */
+ u64 phase_comp;
+ u32 esync_n_div;
+ u16 freq_base;
+ u16 freq_mult;
+ u16 freq_ratio_m;
+ u16 freq_ratio_n;
+ u8 sync_ctrl;
+ );
+ struct_group(inv, /* Invariants */
+ u8 config;
+ );
+ struct_group(stat, /* Status */
+ s64 ffo;
+ u8 mon_status;
+ );
};
int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index);
@@ -45,6 +52,8 @@ const struct zl3073x_ref *zl3073x_ref_state_get(struct zl3073x_dev *zldev,
int zl3073x_ref_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_ref *ref);
+int zl3073x_ref_state_update(struct zl3073x_dev *zldev, u8 index);
+
int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult);
/**
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index 19c598daa784..5ae50cb761a9 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -7,6 +7,18 @@
#include <linux/bits.h>
/*
+ * Hardware limits for ZL3073x chip family
+ */
+#define ZL3073X_MAX_CHANNELS 5
+#define ZL3073X_NUM_REFS 10
+#define ZL3073X_NUM_OUTS 10
+#define ZL3073X_NUM_SYNTHS 5
+#define ZL3073X_NUM_INPUT_PINS ZL3073X_NUM_REFS
+#define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2)
+#define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \
+ ZL3073X_NUM_OUTPUT_PINS)
+
+/*
* Register address structure:
* ===========================
* 25 19 18 16 15 7 6 0
diff --git a/drivers/dpll/zl3073x/synth.h b/drivers/dpll/zl3073x/synth.h
index 6c55eb8a888c..89e13ea2e6d9 100644
--- a/drivers/dpll/zl3073x/synth.h
+++ b/drivers/dpll/zl3073x/synth.h
@@ -5,6 +5,7 @@
#include <linux/bitfield.h>
#include <linux/math64.h>
+#include <linux/stddef.h>
#include <linux/types.h>
#include "regs.h"
@@ -20,11 +21,13 @@ struct zl3073x_dev;
* @ctrl: synth control
*/
struct zl3073x_synth {
- u32 freq_mult;
- u16 freq_base;
- u16 freq_m;
- u16 freq_n;
- u8 ctrl;
+ struct_group(inv, /* Invariants */
+ u32 freq_mult;
+ u16 freq_base;
+ u16 freq_m;
+ u16 freq_n;
+ u8 ctrl;
+ );
};
int zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 synth_id);
@@ -32,9 +35,6 @@ int zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 synth_id);
const struct zl3073x_synth *zl3073x_synth_state_get(struct zl3073x_dev *zldev,
u8 synth_id);
-int zl3073x_synth_state_set(struct zl3073x_dev *zldev, u8 synth_id,
- const struct zl3073x_synth *synth);
-
/**
* zl3073x_synth_dpll_get - get DPLL ID the synth is driven by
* @synth: pointer to synth state