diff options
| author | Jakub Kicinski <kuba@kernel.org> | 2026-03-18 05:05:14 +0300 |
|---|---|---|
| committer | Jakub Kicinski <kuba@kernel.org> | 2026-03-18 05:05:14 +0300 |
| commit | eb9744e2c5d06e7007f47dd5853f7c50be189176 (patch) | |
| tree | 66b56e2d420508c79f2b33e73bac6c78fd3d8d55 | |
| parent | bb8539e0e60916ef3ed4a92eb2f3cfd8e34061ef (diff) | |
| parent | acee049a6af2a7b4baabd28f16fb53628ea6e7a5 (diff) | |
| download | linux-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/Makefile | 4 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/chan.c | 165 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/chan.h | 179 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/core.c | 36 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/core.h | 14 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/dpll.c | 490 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/dpll.h | 4 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/out.c | 27 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/out.h | 21 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/ref.c | 58 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/ref.h | 33 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/regs.h | 12 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/synth.h | 16 |
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 |
