--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/dev_printk.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include "chan.h"
+#include "core.h"
+
+/**
+ * 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 for the given DPLL channel and stores
+ * the raw value 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;
+
+ 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));
+
+ 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 the mode_refsel register to hardware.
+ *
+ * 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;
+
+ /* Skip HW write if configuration hasn't changed */
+ if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg)))
+ return 0;
+
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
+ chan->mode_refsel);
+ if (rc)
+ return rc;
+
+ /* After successful write store new state */
+ dchan->cfg = chan->cfg;
+
+ return 0;
+}
--- /dev/null
+/* 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
+ */
+struct zl3073x_chan {
+ struct_group(cfg,
+ u8 mode_refsel;
+ );
+};
+
+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);
+
+/**
+ * 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);
+}
+
+#endif /* _ZL3073X_CHAN_H */
zl3073x_dpll_selected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref)
{
struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_chan *chan;
u8 state, value;
int rc;
- switch (zldpll->refsel_mode) {
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
+
+ switch (zl3073x_chan_mode_get(chan)) {
case ZL_DPLL_MODE_REFSEL_MODE_AUTO:
/* For automatic mode read refsel_status register */
rc = zl3073x_read_u8(zldev,
break;
case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK:
/* For manual mode return stored value */
- *ref = zldpll->forced_ref;
+ *ref = zl3073x_chan_ref_get(chan);
break;
default:
/* For other modes like NCO, freerun... there is no input ref */
zl3073x_dpll_selected_ref_set(struct zl3073x_dpll *zldpll, u8 ref)
{
struct zl3073x_dev *zldev = zldpll->dev;
- u8 mode, mode_refsel;
- int rc;
+ struct zl3073x_chan chan;
+ u8 mode;
- mode = zldpll->refsel_mode;
+ chan = *zl3073x_chan_state_get(zldev, zldpll->id);
+ mode = zl3073x_chan_mode_get(&chan);
switch (mode) {
case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK:
break;
}
/* Keep selected reference */
- ref = zldpll->forced_ref;
- } else if (ref == zldpll->forced_ref) {
+ ref = zl3073x_chan_ref_get(&chan);
+ } else if (ref == zl3073x_chan_ref_get(&chan)) {
/* No register update - same mode and same ref */
return 0;
}
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;
+ zl3073x_chan_mode_set(&chan, mode);
+ zl3073x_chan_ref_set(&chan, ref);
- return rc;
+ return zl3073x_chan_state_set(zldev, zldpll->id, &chan);
}
/**
{
struct zl3073x_dpll *zldpll = pin->dpll;
struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_chan *chan;
u8 ref, ref_conn;
int rc;
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
ref = zl3073x_input_pin_ref_get(pin->id);
/* Get currently connected reference */
* selectable and its monitor does not report any error then report
* pin as selectable.
*/
- if (zldpll->refsel_mode == ZL_DPLL_MODE_REFSEL_MODE_AUTO &&
+ if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO &&
zl3073x_dev_ref_is_status_ok(zldev, ref) && pin->selectable) {
*state = DPLL_PIN_STATE_SELECTABLE;
return 0;
{
struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
+ const struct zl3073x_chan *chan;
u8 new_ref;
int rc;
- 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_REFLOCK:
case ZL_DPLL_MODE_REFSEL_MODE_FREERUN:
case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER:
{
struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_chan *chan;
u8 mon_status, state;
int rc;
- switch (zldpll->refsel_mode) {
+ chan = zl3073x_chan_state_get(zldev, 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 */
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);
enum dpll_mode *mode, struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
+ 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_HOLDOVER:
case ZL_DPLL_MODE_REFSEL_MODE_NCO:
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);
hw_mode = ZL_DPLL_MODE_REFSEL_MODE_AUTO;
}
- /* Build mode_refsel value */
- mode_refsel = FIELD_PREP(ZL_DPLL_MODE_REFSEL_MODE, hw_mode);
-
+ chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ 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;
}
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";
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;
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++;
/* 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