From 2bd30b8dc75b823dd7bbef9d7bc68e1fd7ecee5c Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 24 Aug 2026 01:18:14 +0900 Subject: coccinelle: alloc_cast: drop removed allocators - pci_alloc_consistent() and pci_zalloc_consistent() were removed by commit 7968778914e5 ("PCI: Remove the deprecated "pci-dma-compat.h" API") - kmem_alloc() was removed by commit f078d4ea8276 ("xfs: convert kmem_alloc() to kmalloc()") - kmem_zalloc() was removed by commit 10634530f7ba ("xfs: convert kmem_zalloc() to kzalloc()") - kmem_zone_alloc() and kmem_zone_zalloc() were removed by commit bae633a4a283 ("xfs: remove xfs_zone_{alloc,zalloc} helpers") So drop them from the rules. No functional change. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- drivers/dpll/zl3073x/Kconfig | 39 + drivers/dpll/zl3073x/Makefile | 11 + drivers/dpll/zl3073x/chan.c | 629 +++++++++ drivers/dpll/zl3073x/chan.h | 268 ++++ drivers/dpll/zl3073x/core.c | 1071 +++++++++++++++ drivers/dpll/zl3073x/core.h | 449 +++++++ drivers/dpll/zl3073x/devlink.c | 390 ++++++ drivers/dpll/zl3073x/devlink.h | 15 + drivers/dpll/zl3073x/dpll.c | 2874 ++++++++++++++++++++++++++++++++++++++++ drivers/dpll/zl3073x/dpll.h | 55 + drivers/dpll/zl3073x/flash.c | 665 ++++++++++ drivers/dpll/zl3073x/flash.h | 29 + drivers/dpll/zl3073x/fw.c | 419 ++++++ drivers/dpll/zl3073x/fw.h | 52 + drivers/dpll/zl3073x/i2c.c | 61 + drivers/dpll/zl3073x/out.c | 168 +++ drivers/dpll/zl3073x/out.h | 137 ++ drivers/dpll/zl3073x/prop.c | 381 ++++++ drivers/dpll/zl3073x/prop.h | 34 + drivers/dpll/zl3073x/ref.c | 247 ++++ drivers/dpll/zl3073x/ref.h | 191 +++ drivers/dpll/zl3073x/regs.h | 419 ++++++ drivers/dpll/zl3073x/spi.c | 61 + drivers/dpll/zl3073x/synth.c | 87 ++ drivers/dpll/zl3073x/synth.h | 72 + 25 files changed, 8824 insertions(+) create mode 100644 drivers/dpll/zl3073x/Kconfig create mode 100644 drivers/dpll/zl3073x/Makefile create mode 100644 drivers/dpll/zl3073x/chan.c create mode 100644 drivers/dpll/zl3073x/chan.h create mode 100644 drivers/dpll/zl3073x/core.c create mode 100644 drivers/dpll/zl3073x/core.h create mode 100644 drivers/dpll/zl3073x/devlink.c create mode 100644 drivers/dpll/zl3073x/devlink.h create mode 100644 drivers/dpll/zl3073x/dpll.c create mode 100644 drivers/dpll/zl3073x/dpll.h create mode 100644 drivers/dpll/zl3073x/flash.c create mode 100644 drivers/dpll/zl3073x/flash.h create mode 100644 drivers/dpll/zl3073x/fw.c create mode 100644 drivers/dpll/zl3073x/fw.h create mode 100644 drivers/dpll/zl3073x/i2c.c create mode 100644 drivers/dpll/zl3073x/out.c create mode 100644 drivers/dpll/zl3073x/out.h create mode 100644 drivers/dpll/zl3073x/prop.c create mode 100644 drivers/dpll/zl3073x/prop.h create mode 100644 drivers/dpll/zl3073x/ref.c create mode 100644 drivers/dpll/zl3073x/ref.h create mode 100644 drivers/dpll/zl3073x/regs.h create mode 100644 drivers/dpll/zl3073x/spi.c create mode 100644 drivers/dpll/zl3073x/synth.c create mode 100644 drivers/dpll/zl3073x/synth.h (limited to 'drivers/dpll/zl3073x') diff --git a/drivers/dpll/zl3073x/Kconfig b/drivers/dpll/zl3073x/Kconfig new file mode 100644 index 000000000..da1731ace --- /dev/null +++ b/drivers/dpll/zl3073x/Kconfig @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config ZL3073X + tristate "Microchip Azurite DPLL/PTP/SyncE devices" if COMPILE_TEST + depends on NET && PTP_1588_CLOCK + select DPLL + select NET_DEVLINK + select REGMAP + help + This driver supports Microchip Azurite family DPLL/PTP/SyncE + devices that support up to 5 independent DPLL channels, + 10 input pins and up to 20 output pins. + + To compile this driver as a module, choose M here. The module + will be called zl3073x. + +config ZL3073X_I2C + tristate "I2C bus implementation for Microchip Azurite devices" + depends on I2C && NET && PTP_1588_CLOCK + select REGMAP_I2C + select ZL3073X + help + This is I2C bus implementation for Microchip Azurite DPLL/PTP/SyncE + devices. + + To compile this driver as a module, choose M here: the module will + be called zl3073x_i2c. + +config ZL3073X_SPI + tristate "SPI bus implementation for Microchip Azurite devices" + depends on NET && SPI && PTP_1588_CLOCK + select REGMAP_SPI + select ZL3073X + help + This is SPI bus implementation for Microchip Azurite DPLL/PTP/SyncE + devices. + + To compile this driver as a module, choose M here: the module will + be called zl3073x_spi. diff --git a/drivers/dpll/zl3073x/Makefile b/drivers/dpll/zl3073x/Makefile new file mode 100644 index 000000000..906ec3fbc --- /dev/null +++ b/drivers/dpll/zl3073x/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_ZL3073X) += zl3073x.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 + +obj-$(CONFIG_ZL3073X_SPI) += zl3073x_spi.o +zl3073x_spi-objs := spi.o diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c new file mode 100644 index 000000000..ba4d303d4 --- /dev/null +++ b/drivers/dpll/zl3073x/chan.c @@ -0,0 +1,629 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include + +#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]; + u64 val; + int rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(index), + &chan->mon_status); + if (rc) + return rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index), + &chan->refsel_status); + if (rc) + return rc; + + /* Read df_offset only when locked to a reference. In NCO mode + * df_offset was captured at entry by nco_mode_set() - preserve it. + */ + if (!zl3073x_chan_is_locked(chan)) { + if (!zl3073x_chan_mode_is_nco(chan)) + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + return 0; + } + + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index), + ZL_DPLL_DF_READ_SEM, + ZL_POLL_DF_READ_TIMEOUT_US); + if (rc) + return rc; + + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index), + ZL_DPLL_DF_READ_SEM | ZL_DPLL_DF_READ_REF_OFST); + if (rc) + return rc; + + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index), + ZL_DPLL_DF_READ_SEM, + ZL_POLL_DF_READ_TIMEOUT_US); + if (rc) + return rc; + + rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val); + if (rc) + return rc; + + chan->df_offset = sign_extend64(val, 47); + + return 0; +} + +/** + * zl3073x_chan_nco_mode_set - switch DPLL channel to NCO mode + * @zldev: pointer to zl3073x_dev structure + * @index: DPLL channel index + * + * Switches the channel to NCO mode and reads the df_offset + * auto-captured by nco_auto_read directly from the register. + * No DF_READ handshake is needed as nco_auto_read populates + * the register before the mode switch completes. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_chan *chan = &zldev->chan[index]; + u8 prev_mode, df_read; + u64 val; + int rc; + + prev_mode = zl3073x_chan_mode_get(chan); + + /* nco_auto_read captures the tracking offset at NCO entry only + * from reflock, auto or holdover mode. From freerun the captured + * value is not meaningful. + */ + if (prev_mode == ZL_DPLL_MODE_REFSEL_MODE_FREERUN) { + zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO); + + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), + chan->mode_refsel); + if (rc) { + zl3073x_chan_mode_set(chan, prev_mode); + return rc; + } + + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + return 0; + } + + /* Configure df_read for nco_auto_read: + * ref_ofst=0 - reads offset relative to master clock (not input ref) + * cmd=CMD_ACC_I - accumulated I-part covering both locked and + * holdover entry. + * + * No semaphore is set - this only configures what the df_offset + * value represents after the mode switch; nco_auto_read performs + * the actual read automatically. + */ + df_read = FIELD_PREP(ZL_DPLL_DF_READ_REF_OFST, 0) | + FIELD_PREP(ZL_DPLL_DF_READ_CMD, ZL_DPLL_DF_READ_CMD_ACC_I); + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index), df_read); + if (rc) + return rc; + + /* Wait for df_read configuration to take effect before + * triggering nco_auto_read via mode switch. The worst-case + * internal register update time is 25 ms. + */ + fsleep(25000); + + zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO); + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), + chan->mode_refsel); + if (rc) { + zl3073x_chan_mode_set(chan, prev_mode); + return rc; + } + + /* Wait for nco_auto_read to populate df_offset. The worst-case + * internal register update time is 25 ms. + */ + fsleep(25000); + + /* Read df_offset captured by nco_auto_read during mode switch. + * No DF_READ semaphore handshake needed. Mode switch already + * succeeded, so don't propagate a read failure back to userspace. + */ + rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val); + if (rc) { + dev_warn(zldev->dev, + "Failed to read DPLL%u df_offset: %pe\n", + index, ERR_PTR(rc)); + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + } else { + chan->df_offset = sign_extend64(val, 47); + } + + return 0; +} + +/** + * zl3073x_chan_state_fetch - fetch DPLL channel state from hardware + * @zldev: pointer to zl3073x_dev structure + * @index: DPLL channel index to fetch state for + * + * Reads the mode_refsel, status and reference priority registers for + * the given DPLL channel and stores the 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_CTRL(index), &chan->ctrl); + if (rc) + return 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)); + + rc = zl3073x_chan_state_update(zldev, index); + if (rc) + return rc; + + /* If firmware left the channel in NCO mode, mark df_offset as + * unknown - we cannot know whether the preconditions for a valid + * nco_auto_read capture were met. + */ + if (zl3073x_chan_mode_is_nco(chan)) + chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN; + + dev_dbg(zldev->dev, + "DPLL%u lock_state: %u, ho: %u, sel_state: %u, sel_ref: %u\n", + index, zl3073x_chan_lock_state_get(chan), + 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_tod_ready_wait - wait for ToD semaphore to clear + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * + * Checks the ToD control register semaphore bit. If clear, returns + * immediately. Otherwise polls until the bit is cleared by the device. + * + * Return: + * * 0 - success + * * %-EBUSY - timeout + * * %-EOPNOTSUPP - unknown command detected + * * negative - other error + */ +int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch) +{ + unsigned int timeout; + u8 tod_ctrl; + int rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), &tod_ctrl); + if (rc) + return rc; + + if (!(tod_ctrl & ZL_DPLL_TOD_CTRL_SEM)) + return 0; + + switch (FIELD_GET(ZL_DPLL_TOD_CTRL_CMD, tod_ctrl)) { + case ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ: + timeout = ZL_POLL_TOD_WR_TIMEOUT_US; + break; + case ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT: + case ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ: + timeout = ZL_POLL_TOD_RD_TIMEOUT_US; + break; + default: + return -EOPNOTSUPP; + } + + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), + ZL_DPLL_TOD_CTRL_SEM, timeout); + + return rc == -ETIMEDOUT ? -EBUSY : rc; +} + +/** + * zl3073x_chan_tod_ctrl - issue ToD command + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @cmd: ToD command to execute + * + * Writes the semaphore and command to dpll_tod_ctrl. The caller must + * ensure the device is ready (semaphore clear) before calling and + * must wait for completion if needed. + * + * Return: 0 on success, <0 on error + */ +static int zl3073x_chan_tod_ctrl(struct zl3073x_dev *zldev, u8 ch, u8 cmd) +{ + return zl3073x_write_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), + ZL_DPLL_TOD_CTRL_SEM | cmd); +} + +/** + * zl3073x_chan_tod_read - read ToD registers after issuing a command + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @next_hz: if true, read predicted ToD at next 1 Hz; otherwise read current + * @ts: timespec to store the result + * @sts: optional system timestamp pair for cross-timestamping + * + * Context: Caller must serialize all zl3073x_chan_tod_* calls externally. + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch, + bool next_hz, struct timespec64 *ts, + struct ptp_system_timestamp *sts) +{ + u32 nsec; + u64 sec; + u8 cmd; + int rc; + + if (next_hz) + cmd = ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ; + else + cmd = ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT; + + /* Wait for any previous ToD operation to complete */ + rc = zl3073x_chan_tod_ready_wait(zldev, ch); + if (rc) + return rc; + + ptp_read_system_prets(sts); + rc = zl3073x_chan_tod_ctrl(zldev, ch, cmd); + if (rc) + return rc; + + rc = zl3073x_chan_tod_ready_wait(zldev, ch); + if (rc) + return rc; + ptp_read_system_postts(sts); + + rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), &sec); + if (rc) + return rc; + + /* HW nanoseconds are always in [0, NSEC_PER_SEC) range */ + rc = zl3073x_read_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), &nsec); + if (rc) + return rc; + + ts->tv_sec = sec; + ts->tv_nsec = nsec; + + return 0; +} + +/** + * zl3073x_chan_tod_write - write ToD registers and trigger 1 Hz update + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @ts: time to set + * + * Context: Caller must serialize all zl3073x_chan_tod_* calls externally. + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch, + struct timespec64 ts) +{ + int rc; + + /* Wait for any previous ToD operation to complete */ + rc = zl3073x_chan_tod_ready_wait(zldev, ch); + if (rc) + return rc; + + rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), ts.tv_sec); + if (rc) + return rc; + + rc = zl3073x_write_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), ts.tv_nsec); + if (rc) + return rc; + + return zl3073x_chan_tod_ctrl(zldev, ch, + ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ); +} + +/** + * zl3073x_chan_tod_adjust - atomic ToD read-modify-write with rollover guard + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @delta: time adjustment to apply + * + * Reads the next-Hz ToD and current ToD, then checks whether enough time + * remains before the next 1 Hz rollover to safely complete the write. + * Re-reads if the 1 Hz tick crossed between the two reads or if less + * than 20 ms remains before the next rollover. Applies @delta and writes + * the result back. + * + * Context: Caller must serialize all zl3073x_chan_tod_* calls externally. + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch, + struct timespec64 delta) +{ +#define ZL_TOD_MAX_RETRIES 20 + static const long threshold_ns = 20 * NSEC_PER_MSEC; + struct timespec64 ts_next, ts_cur, diff; + int rc, i; + + for (i = 0; i < ZL_TOD_MAX_RETRIES; i++) { + rc = zl3073x_chan_tod_read(zldev, ch, true, &ts_next, NULL); + if (rc) + return rc; + + rc = zl3073x_chan_tod_read(zldev, ch, false, &ts_cur, NULL); + if (rc) + return rc; + + /* Ensure the 1 Hz tick did not cross between the two reads + * and that enough margin remains to complete the write. + */ + diff = timespec64_sub(ts_next, ts_cur); + if (diff.tv_sec > 0 || + (!diff.tv_sec && diff.tv_nsec >= threshold_ns)) + break; + } + if (i == ZL_TOD_MAX_RETRIES) { + dev_warn(zldev->dev, + "DPLL%u ToD adjust failed to get stable margin\n", + ch); + return -EBUSY; + } + + /* Apply delta to the next-Hz ToD */ + ts_next = timespec64_add(ts_next, delta); + if (!timespec64_valid_settod(&ts_next)) + return -EINVAL; + + return zl3073x_chan_tod_write(zldev, ch, ts_next); +#undef ZL_TOD_MAX_RETRIES +} + +/** + * zl3073x_chan_df_offset_set - write delta frequency offset to hardware + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @offset: frequency offset in 2^-48 steps + * + * Context: Caller must hold the per-DPLL lock. + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset) +{ + int rc; + + rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_DF_OFFSET(ch), offset); + if (!rc) + zldev->chan[ch].df_offset = offset; + + return rc; +} + +/** + * zl3073x_chan_tie_write - adjust DPLL phase using TIE write + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @delta_ns: phase adjustment in nanoseconds (must be in (-1s, 1s)) + * + * Converts nanoseconds to TIE units (0.01 ps) and writes TIE data + * to the specified channel. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns) +{ + s64 tie_data; + int rc; + + guard(mutex)(&zldev->tie_lock); + + /* Wait for any previous TIE operation to complete */ + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TIE_CTRL, + ZL_DPLL_TIE_CTRL_OP, + ZL_POLL_TIE_WR_TIMEOUT_US); + if (rc) + return rc; + + /* Convert ns to TIE units (0.01 ps = 10^-14 s) */ + tie_data = delta_ns * 100000LL; + + rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TIE_DATA(ch), tie_data); + if (rc) + return rc; + + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL_MASK, BIT(ch)); + if (rc) + return rc; + + return zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL, + ZL_DPLL_TIE_CTRL_OP_WR); +} + +/** + * zl3073x_chan_phase_step - execute one output phase step operation + * @zldev: pointer to zl3073x device + * @ch: DPLL channel index + * @out_mask: bitmask of outputs to step + * @step_cycles: phase step in synthesizer clock cycles + * @tod_step: also step the ToD counter + * + * All masked outputs must use synthesizers of the same frequency since + * the step value is in synthesizer clock cycles. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch, + u16 out_mask, s32 step_cycles, + bool tod_step) +{ + u8 ctrl; + int rc; + + guard(mutex)(&zldev->phase_step_lock); + + /* Wait for any previous phase step operation to complete */ + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL, + ZL_OUTPUT_PHASE_STEP_CTRL_OP, + ZL_POLL_PHASE_STEP_TIMEOUT_US); + if (rc) + return rc; + + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_STEP_DATA, + step_cycles); + if (rc) + return rc; + + rc = zl3073x_write_u16(zldev, ZL_REG_OUTPUT_PHASE_STEP_MASK, out_mask); + if (rc) + return rc; + + rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_NUMBER, 1); + if (rc) + return rc; + + ctrl = FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_DPLL, ch) | + FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_OP, + ZL_OUTPUT_PHASE_STEP_CTRL_OP_WRITE); + if (tod_step) + ctrl |= ZL_OUTPUT_PHASE_STEP_CTRL_TOD_STEP; + + return zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL, ctrl); +} + +/** + * 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 writes for ctrl and mode_refsel */ + if (dchan->ctrl != chan->ctrl) { + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_CTRL(index), + chan->ctrl); + if (rc) + return rc; + dchan->ctrl = chan->ctrl; + } + + if (dchan->mode_refsel != chan->mode_refsel) { + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), + chan->mode_refsel); + 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 000000000..99c3b7088 --- /dev/null +++ b/drivers/dpll/zl3073x/chan.h @@ -0,0 +1,268 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_CHAN_H +#define _ZL3073X_CHAN_H + +#include +#include +#include +#include + +#include "regs.h" + +struct ptp_system_timestamp; +struct zl3073x_dev; + +/** + * struct zl3073x_chan - DPLL channel state + * @ctrl: DPLL control register value + * @mode_refsel: mode and reference selection register value + * @ref_prio: reference priority registers (4 bits per ref, P/N packed) + * @mon_status: monitor status register value + * @refsel_status: reference selection status register value + * @df_offset: frequency offset vs tracked reference in 2^-48 steps + */ +struct zl3073x_chan { + struct_group(cfg, + u8 ctrl; + u8 mode_refsel; + u8 ref_prio[ZL3073X_NUM_REFS / 2]; + ); + struct_group(stat, + u8 mon_status; + u8 refsel_status; + s64 df_offset; + ); +}; + +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); +int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index); + +int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch); +int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch, + bool next_hz, struct timespec64 *ts, + struct ptp_system_timestamp *sts); +int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch, + struct timespec64 ts); +int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch, + struct timespec64 delta); +int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch, + u16 out_mask, s32 step_cycles, bool tod_step); + +int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset); + +int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns); + +/** + * zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference + * @chan: pointer to channel state + * + * Return: frequency offset in 2^-48 steps + */ +static inline s64 +zl3073x_chan_df_offset_get(const struct zl3073x_chan *chan) +{ + return chan->df_offset; +} + +/** + * 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) +{ + FIELD_MODIFY(ZL_DPLL_MODE_REFSEL_MODE, &chan->mode_refsel, 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) +{ + FIELD_MODIFY(ZL_DPLL_MODE_REFSEL_REF, &chan->mode_refsel, 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)) + FIELD_MODIFY(ZL_DPLL_REF_PRIO_REF_P, val, prio); + else + FIELD_MODIFY(ZL_DPLL_REF_PRIO_REF_N, val, 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_locked - check if channel is locked to a reference + * @chan: pointer to channel state + * + * Return: true if channel is locked, false otherwise + */ +static inline bool zl3073x_chan_is_locked(const struct zl3073x_chan *chan) +{ + u8 lock_state = zl3073x_chan_lock_state_get(chan); + return lock_state == ZL_DPLL_MON_STATUS_STATE_LOCK; +} + +/** + * zl3073x_chan_mode_is_auto - check if channel is in automatic mode + * @chan: pointer to channel state + * + * Return: true if channel is in automatic mode, false otherwise + */ +static inline bool zl3073x_chan_mode_is_auto(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO; +} + +/** + * zl3073x_chan_mode_is_nco - check if channel is in NCO mode + * @chan: pointer to channel state + * + * Return: true if channel is in NCO mode, false otherwise + */ +static inline bool zl3073x_chan_mode_is_nco(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO; +} + +/** + * zl3073x_chan_mode_is_reflock - check if channel is in reflock mode + * @chan: pointer to channel state + * + * Return: true if channel is in reflock mode, false otherwise + */ +static inline bool zl3073x_chan_mode_is_reflock(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_REFLOCK; +} + +/** + * zl3073x_chan_mode_supports_tie - check if channel mode supports TIE write + * @chan: pointer to channel state + * + * TIE write is supported in AUTO and REFLOCK modes regardless of lock state. + * + * Return: true if TIE write is supported, false otherwise + */ +static inline bool +zl3073x_chan_mode_supports_tie(const struct zl3073x_chan *chan) +{ + return zl3073x_chan_mode_is_auto(chan) || + zl3073x_chan_mode_is_reflock(chan); +} + +/** + * 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 new file mode 100644 index 000000000..230df08e2 --- /dev/null +++ b/drivers/dpll/zl3073x/core.c @@ -0,0 +1,1071 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "devlink.h" +#include "dpll.h" +#include "regs.h" + +#define ZL_CHIP_INFO(_id, _nchannels, _flags) \ + { .id = (_id), .num_channels = (_nchannels), .flags = (_flags) } + +static const struct zl3073x_chip_info zl3073x_chip_ids[] = { + ZL_CHIP_INFO(0x0E30, 2, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x0E3B, 3, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x0E93, 1, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x0E94, 2, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x0E95, 3, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x0E96, 4, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x0E97, 5, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x1E93, 1, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x1E94, 2, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x1E95, 3, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x1E96, 4, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x1E97, 5, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x1F60, 2, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x2E93, 1, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x2E94, 2, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x2E95, 3, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x2E96, 4, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x2E97, 5, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x3FC4, 2, ZL3073X_FLAG_DIE_TEMP), +}; + +#define ZL_RANGE_OFFSET 0x80 +#define ZL_PAGE_SIZE 0x80 +#define ZL_NUM_PAGES 256 +#define ZL_PAGE_SEL 0x7F +#define ZL_PAGE_SEL_MASK GENMASK(7, 0) +#define ZL_NUM_REGS (ZL_NUM_PAGES * ZL_PAGE_SIZE) + +/* Regmap range configuration */ +static const struct regmap_range_cfg zl3073x_regmap_range = { + .range_min = ZL_RANGE_OFFSET, + .range_max = ZL_RANGE_OFFSET + ZL_NUM_REGS - 1, + .selector_reg = ZL_PAGE_SEL, + .selector_mask = ZL_PAGE_SEL_MASK, + .selector_shift = 0, + .window_start = 0, + .window_len = ZL_PAGE_SIZE, +}; + +static bool +zl3073x_is_volatile_reg(struct device *dev __maybe_unused, unsigned int reg) +{ + /* Only page selector is non-volatile */ + return reg != ZL_PAGE_SEL; +} + +const struct regmap_config zl3073x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ZL_RANGE_OFFSET + ZL_NUM_REGS - 1, + .ranges = &zl3073x_regmap_range, + .num_ranges = 1, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = zl3073x_is_volatile_reg, +}; +EXPORT_SYMBOL_NS_GPL(zl3073x_regmap_config, "ZL3073X"); + +static bool +zl3073x_check_reg(struct zl3073x_dev *zldev, unsigned int reg, size_t size) +{ + /* Check that multiop lock is held when accessing registers + * from page 10 and above except the page 255 that does not + * need this protection. + */ + if (ZL_REG_PAGE(reg) >= 10 && ZL_REG_PAGE(reg) < 255) + lockdep_assert_held(&zldev->multiop_lock); + + /* Check the index is in valid range for indexed register */ + if (ZL_REG_OFFSET(reg) > ZL_REG_MAX_OFFSET(reg)) { + dev_err(zldev->dev, "Index out of range for reg 0x%04lx\n", + ZL_REG_ADDR(reg)); + return false; + } + /* Check the requested size corresponds to register size */ + if (ZL_REG_SIZE(reg) != size) { + dev_err(zldev->dev, "Invalid size %zu for reg 0x%04lx\n", + size, ZL_REG_ADDR(reg)); + return false; + } + + return true; +} + +static int +zl3073x_read_reg(struct zl3073x_dev *zldev, unsigned int reg, void *val, + size_t size) +{ + int rc; + + if (!zl3073x_check_reg(zldev, reg, size)) + return -EINVAL; + + /* Map the register address to virtual range */ + reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET; + + rc = regmap_bulk_read(zldev->regmap, reg, val, size); + if (rc) { + dev_err(zldev->dev, "Failed to read reg 0x%04x: %pe\n", reg, + ERR_PTR(rc)); + return rc; + } + + return 0; +} + +static int +zl3073x_write_reg(struct zl3073x_dev *zldev, unsigned int reg, const void *val, + size_t size) +{ + int rc; + + if (!zl3073x_check_reg(zldev, reg, size)) + return -EINVAL; + + /* Map the register address to virtual range */ + reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET; + + rc = regmap_bulk_write(zldev->regmap, reg, val, size); + if (rc) { + dev_err(zldev->dev, "Failed to write reg 0x%04x: %pe\n", reg, + ERR_PTR(rc)); + return rc; + } + + return 0; +} + +/** + * zl3073x_read_u8 - read value from 8bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Reads value from given 8bit register. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val) +{ + return zl3073x_read_reg(zldev, reg, val, sizeof(*val)); +} + +/** + * zl3073x_write_u8 - write value to 16bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Writes value into given 8bit register. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_write_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 val) +{ + return zl3073x_write_reg(zldev, reg, &val, sizeof(val)); +} + +/** + * zl3073x_read_u16 - read value from 16bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Reads value from given 16bit register. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val) +{ + int rc; + + rc = zl3073x_read_reg(zldev, reg, val, sizeof(*val)); + if (!rc) + be16_to_cpus(val); + + return rc; +} + +/** + * zl3073x_write_u16 - write value to 16bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Writes value into given 16bit register. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_write_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 val) +{ + cpu_to_be16s(&val); + + return zl3073x_write_reg(zldev, reg, &val, sizeof(val)); +} + +/** + * zl3073x_read_u32 - read value from 32bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Reads value from given 32bit register. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val) +{ + int rc; + + rc = zl3073x_read_reg(zldev, reg, val, sizeof(*val)); + if (!rc) + be32_to_cpus(val); + + return rc; +} + +/** + * zl3073x_write_u32 - write value to 32bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Writes value into given 32bit register. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_write_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 val) +{ + cpu_to_be32s(&val); + + return zl3073x_write_reg(zldev, reg, &val, sizeof(val)); +} + +/** + * zl3073x_read_u48 - read value from 48bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Reads value from given 48bit register. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_read_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 *val) +{ + u8 buf[6]; + int rc; + + rc = zl3073x_read_reg(zldev, reg, buf, sizeof(buf)); + if (!rc) + *val = get_unaligned_be48(buf); + + return rc; +} + +/** + * zl3073x_write_u48 - write value to 48bit register + * @zldev: zl3073x device pointer + * @reg: register to write to + * @val: value to write + * + * Writes value into given 48bit register. + * The value must be from the interval -S48_MIN to U48_MAX. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val) +{ + u8 buf[6]; + + /* Check the value belongs to + * Any value >= S48_MIN has bits 47..63 set. + */ + if (val > GENMASK_ULL(47, 0) && val < GENMASK_ULL(63, 47)) { + dev_err(zldev->dev, "Value 0x%0llx out of range\n", val); + return -EINVAL; + } + + put_unaligned_be48(val, buf); + + return zl3073x_write_reg(zldev, reg, buf, sizeof(buf)); +} + +/** + * zl3073x_poll_zero_u8 - wait for register to be cleared by device + * @zldev: zl3073x device pointer + * @reg: register to poll (has to be 8bit register) + * @mask: bit mask for polling + * @timeout_us: timeout in microseconds + * + * Waits for bits specified by @mask in register @reg value to be cleared + * by the device. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, + u8 mask, unsigned int timeout_us) +{ + unsigned int sleep_us = timeout_us / 50; + unsigned int val; + + /* Check the register is 8bit */ + if (ZL_REG_SIZE(reg) != 1) { + dev_err(zldev->dev, "Invalid reg 0x%04lx size for polling\n", + ZL_REG_ADDR(reg)); + return -EINVAL; + } + + /* Map the register address to virtual range */ + reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET; + + return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask), + sleep_us, timeout_us); +} + +int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, + unsigned int mask_reg, u16 mask_val) +{ + int rc; + + /* Set mask for the operation */ + rc = zl3073x_write_u16(zldev, mask_reg, mask_val); + if (rc) + return rc; + + /* Trigger the operation */ + rc = zl3073x_write_u8(zldev, op_reg, op_val); + if (rc) + return rc; + + /* Wait for the operation to actually finish */ + return zl3073x_poll_zero_u8(zldev, op_reg, op_val, + ZL_POLL_MB_TIMEOUT_US); +} + +/** + * zl3073x_do_hwreg_op - Perform HW register read/write operation + * @zldev: zl3073x device pointer + * @op: operation to perform + * + * Performs requested operation and waits for its completion. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_do_hwreg_op(struct zl3073x_dev *zldev, u8 op) +{ + int rc; + + /* Set requested operation and set pending bit */ + rc = zl3073x_write_u8(zldev, ZL_REG_HWREG_OP, op | ZL_HWREG_OP_PENDING); + if (rc) + return rc; + + /* Poll for completion - pending bit cleared */ + return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP, ZL_HWREG_OP_PENDING, + ZL_POLL_HWREG_TIMEOUT_US); +} + +/** + * zl3073x_read_hwreg - Read HW register + * @zldev: zl3073x device pointer + * @addr: HW register address + * @value: Value of the HW register + * + * Reads HW register value and stores it into @value. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_read_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 *value) +{ + int rc; + + /* Set address to read data from */ + rc = zl3073x_write_u32(zldev, ZL_REG_HWREG_ADDR, addr); + if (rc) + return rc; + + /* Perform the read operation */ + rc = zl3073x_do_hwreg_op(zldev, ZL_HWREG_OP_READ); + if (rc) + return rc; + + /* Read the received data */ + return zl3073x_read_u32(zldev, ZL_REG_HWREG_READ_DATA, value); +} + +/** + * zl3073x_write_hwreg - Write value to HW register + * @zldev: zl3073x device pointer + * @addr: HW registers address + * @value: Value to be written to HW register + * + * Stores the requested value into HW register. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_write_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value) +{ + int rc; + + /* Set address to write data to */ + rc = zl3073x_write_u32(zldev, ZL_REG_HWREG_ADDR, addr); + if (rc) + return rc; + + /* Set data to be written */ + rc = zl3073x_write_u32(zldev, ZL_REG_HWREG_WRITE_DATA, value); + if (rc) + return rc; + + /* Perform the write operation */ + return zl3073x_do_hwreg_op(zldev, ZL_HWREG_OP_WRITE); +} + +/** + * zl3073x_update_hwreg - Update certain bits in HW register + * @zldev: zl3073x device pointer + * @addr: HW register address + * @value: Value to be written into HW register + * @mask: Bitmask indicating bits to be updated + * + * Reads given HW register, updates requested bits specified by value and + * mask and writes result back to HW register. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_update_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value, + u32 mask) +{ + u32 tmp; + int rc; + + rc = zl3073x_read_hwreg(zldev, addr, &tmp); + if (rc) + return rc; + + tmp &= ~mask; + tmp |= value & mask; + + return zl3073x_write_hwreg(zldev, addr, tmp); +} + +/** + * zl3073x_write_hwreg_seq - Write HW registers sequence + * @zldev: pointer to device structure + * @seq: pointer to first sequence item + * @num_items: number of items in sequence + * + * Writes given HW registers sequence. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_write_hwreg_seq(struct zl3073x_dev *zldev, + const struct zl3073x_hwreg_seq_item *seq, + size_t num_items) +{ + int i, rc = 0; + + for (i = 0; i < num_items; i++) { + dev_dbg(zldev->dev, "Write 0x%0x [0x%0x] to 0x%0x", + seq[i].value, seq[i].mask, seq[i].addr); + + if (seq[i].mask == U32_MAX) + /* Write value directly */ + rc = zl3073x_write_hwreg(zldev, seq[i].addr, + seq[i].value); + else + /* Update only bits specified by the mask */ + rc = zl3073x_update_hwreg(zldev, seq[i].addr, + seq[i].value, seq[i].mask); + if (rc) + return rc; + + if (seq->wait) + msleep(seq->wait); + } + + return rc; +} + +static int +zl3073x_dev_state_fetch(struct zl3073x_dev *zldev) +{ + int rc; + u8 i; + + rc = zl3073x_read_u16(zldev, ZL_REG_OUTPUT_STEP_TIME_MASK, + &zldev->out_step_time_mask); + if (rc) + return rc; + + for (i = 0; i < ZL3073X_NUM_REFS; i++) { + rc = zl3073x_ref_state_fetch(zldev, i); + if (rc) { + dev_err(zldev->dev, + "Failed to fetch input state: %pe\n", + ERR_PTR(rc)); + return rc; + } + } + + for (i = 0; i < ZL3073X_NUM_SYNTHS; i++) { + rc = zl3073x_synth_state_fetch(zldev, i); + if (rc) { + dev_err(zldev->dev, + "Failed to fetch synth state: %pe\n", + ERR_PTR(rc)); + return rc; + } + } + + for (i = 0; i < ZL3073X_NUM_OUTS; i++) { + rc = zl3073x_out_state_fetch(zldev, i); + if (rc) { + dev_err(zldev->dev, + "Failed to fetch output state: %pe\n", + ERR_PTR(rc)); + return rc; + } + } + + 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_states_update(struct zl3073x_dev *zldev) +{ + int i, rc; + + for (i = 0; i < ZL3073X_NUM_REFS; i++) { + rc = zl3073x_ref_state_update(zldev, i); + if (rc) + dev_warn(zldev->dev, + "Failed to get REF%u status: %pe\n", i, + ERR_PTR(rc)); + } +} + + + +/** + * zl3073x_ref_phase_offsets_update - update reference phase offsets + * @zldev: pointer to zl3073x_dev structure + * @channel: DPLL channel number or -1 + * + * The function asks device to update phase offsets latch registers with + * the latest measured values. There are 2 sets of latch registers: + * + * 1) Up to 5 DPLL-to-connected-ref registers that contain phase offset + * values between particular DPLL channel and its *connected* input + * reference. + * + * 2) 10 selected-DPLL-to-all-ref registers that contain phase offset values + * between selected DPLL channel and all input references. + * + * If the caller is interested in 2) then it has to pass DPLL channel number + * in @channel parameter. If it is interested only in 1) then it should pass + * @channel parameter with value of -1. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel) +{ + int rc; + + /* Per datasheet we have to wait for 'dpll_ref_phase_err_rqst_rd' + * to be zero to ensure that the measured data are coherent. + */ + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST, + ZL_REF_PHASE_ERR_READ_RQST_RD, + ZL_POLL_PHASE_ERR_TIMEOUT_US); + if (rc) + return rc; + + /* Select DPLL channel if it is specified */ + if (channel != -1) { + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_IDX, channel); + if (rc) + return rc; + } + + /* Request to update phase offsets measurement values */ + rc = zl3073x_write_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST, + ZL_REF_PHASE_ERR_READ_RQST_RD); + if (rc) + return rc; + + /* Wait for finish */ + return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST, + ZL_REF_PHASE_ERR_READ_RQST_RD, + ZL_POLL_PHASE_ERR_TIMEOUT_US); +} + +/** + * zl3073x_ref_freq_meas_latch - latch reference frequency measurements + * @zldev: pointer to zl3073x_dev structure + * @type: measurement type (ZL_REF_FREQ_MEAS_CTRL_*) + * + * The function waits for the previous measurement to finish, selects all + * references and requests a new measurement of the given type. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type) +{ + int rc; + + /* Wait for previous measurement to finish */ + rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, + ZL_REF_FREQ_MEAS_CTRL, + ZL_POLL_FREQ_MEAS_TIMEOUT_US); + if (rc) + return rc; + + /* Select all references for measurement */ + rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_MASK_3_0, + GENMASK(7, 0)); /* REF0P..REF3N */ + if (rc) + return rc; + rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_MASK_4, + GENMASK(1, 0)); /* REF4P..REF4N */ + if (rc) + return rc; + + /* Request measurement */ + rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, type); + if (rc) + return rc; + + /* Wait for finish */ + return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, + ZL_REF_FREQ_MEAS_CTRL, + ZL_POLL_FREQ_MEAS_TIMEOUT_US); +} + +/** + * zl3073x_ref_freq_meas_update - update measured input reference frequencies + * @zldev: pointer to zl3073x_dev structure + * + * The function asks device to latch measured input reference frequencies + * and stores the results in the ref state. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_ref_freq_meas_update(struct zl3073x_dev *zldev) +{ + int i, rc; + + rc = zl3073x_ref_freq_meas_latch(zldev, ZL_REF_FREQ_MEAS_CTRL_REF_FREQ); + if (rc) + return rc; + + /* Read measured frequencies in Hz (unsigned 32-bit, LSB = 1 Hz) */ + for (i = 0; i < ZL3073X_NUM_REFS; i++) { + u32 value; + + rc = zl3073x_read_u32(zldev, ZL_REG_REF_FREQ(i), &value); + if (rc) + return rc; + + zldev->ref[i].meas_freq = value; + } + + return 0; +} + +static void +zl3073x_dev_periodic_work(struct kthread_work *work) +{ + struct zl3073x_dev *zldev = container_of(work, struct zl3073x_dev, + work.work); + struct zl3073x_dpll *zldpll; + int rc; + + /* Update input references' states */ + zl3073x_dev_ref_states_update(zldev); + + /* Update DPLL-to-connected-ref phase offsets registers */ + rc = zl3073x_ref_phase_offsets_update(zldev, -1); + if (rc) + dev_warn(zldev->dev, "Failed to update phase offsets: %pe\n", + ERR_PTR(rc)); + + /* Update measured input reference frequencies if frequency + * monitoring is enabled. + */ + if (READ_ONCE(zldev->freq_monitor)) { + rc = zl3073x_ref_freq_meas_update(zldev); + if (rc) + dev_warn(zldev->dev, + "Failed to update measured frequencies: %pe\n", + ERR_PTR(rc)); + } + + list_for_each_entry(zldpll, &zldev->dplls, list) + zl3073x_dpll_changes_check(zldpll); + + /* Run twice a second */ + kthread_queue_delayed_work(zldev->kworker, &zldev->work, + msecs_to_jiffies(500)); +} + +int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor) +{ + u8 dpll_meas_ctrl, value; + int rc; + + /* Read DPLL phase measurement control register */ + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, &dpll_meas_ctrl); + if (rc) + return rc; + + /* Convert requested factor to register value */ + value = (factor + 1) & 0x0f; + + /* Update phase measurement control register */ + FIELD_MODIFY(ZL_DPLL_MEAS_CTRL_AVG_FACTOR, &dpll_meas_ctrl, value); + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, dpll_meas_ctrl); + if (rc) + return rc; + + /* Save the new factor */ + WRITE_ONCE(zldev->phase_avg_factor, factor); + + return 0; +} + +/** + * zl3073x_dev_phase_meas_setup - setup phase offset measurement + * @zldev: pointer to zl3073x_dev structure + * + * Enable phase offset measurement block, set measurement averaging factor + * and enable DPLL-to-its-ref phase measurement for all DPLLs. + * + * Returns: 0 on success, <0 on error + */ +static int +zl3073x_dev_phase_meas_setup(struct zl3073x_dev *zldev) +{ + struct zl3073x_dpll *zldpll; + u8 dpll_meas_ctrl, mask = 0; + int rc; + + /* Setup phase measurement averaging factor */ + rc = zl3073x_dev_phase_avg_factor_set(zldev, zldev->phase_avg_factor); + if (rc) + return rc; + + /* Read DPLL phase measurement control register */ + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, &dpll_meas_ctrl); + if (rc) + return rc; + + /* Enable DPLL measurement block */ + dpll_meas_ctrl |= ZL_DPLL_MEAS_CTRL_EN; + + /* Update phase measurement control register */ + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, dpll_meas_ctrl); + if (rc) + return rc; + + /* Enable DPLL-to-connected-ref measurement for each channel */ + list_for_each_entry(zldpll, &zldev->dplls, list) + mask |= BIT(zldpll->id); + + return zl3073x_write_u8(zldev, ZL_REG_DPLL_PHASE_ERR_READ_MASK, mask); +} + +/** + * zl3073x_dev_start - Start normal operation + * @zldev: zl3073x device pointer + * @full: perform full initialization + * + * The function starts normal operation, which means registering all DPLLs and + * their pins, and starting monitoring. If full initialization is requested, + * the function additionally initializes the phase offset measurement block and + * fetches hardware-invariant parameters. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_dev_start(struct zl3073x_dev *zldev, bool full) +{ + struct zl3073x_dpll *zldpll; + u8 info; + int rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_INFO, &info); + if (rc) { + dev_err(zldev->dev, "Failed to read device status info\n"); + return rc; + } + + if (!FIELD_GET(ZL_INFO_READY, info)) { + /* The ready bit indicates that the firmware was successfully + * configured and is ready for normal operation. If it is + * cleared then the configuration stored in flash is wrong + * or missing. In this situation the driver will expose + * only devlink interface to give an opportunity to flash + * the correct config. + */ + dev_info(zldev->dev, + "FW not fully ready - missing or corrupted config\n"); + + return 0; + } + + if (full) { + /* Fetch device state */ + rc = zl3073x_dev_state_fetch(zldev); + if (rc) + return rc; + + /* Setup phase offset measurement block */ + rc = zl3073x_dev_phase_meas_setup(zldev); + if (rc) { + dev_err(zldev->dev, + "Failed to setup phase measurement\n"); + return rc; + } + } + + /* Register all DPLLs */ + list_for_each_entry(zldpll, &zldev->dplls, list) { + rc = zl3073x_dpll_register(zldpll); + if (rc) { + dev_err_probe(zldev->dev, rc, + "Failed to register DPLL%u\n", + zldpll->id); + return rc; + } + } + + /* Perform initial firmware fine phase correction */ + rc = zl3073x_dpll_init_fine_phase_adjust(zldev); + if (rc) { + dev_err_probe(zldev->dev, rc, + "Failed to init fine phase correction\n"); + return rc; + } + + /* Start monitoring */ + kthread_queue_delayed_work(zldev->kworker, &zldev->work, 0); + + return 0; +} + +/** + * zl3073x_dev_stop - Stop normal operation + * @zldev: zl3073x device pointer + * + * The function stops the normal operation that mean deregistration of all + * DPLLs and their pins and stop monitoring. + * + * Return: 0 on success, <0 on error + */ +void zl3073x_dev_stop(struct zl3073x_dev *zldev) +{ + struct zl3073x_dpll *zldpll; + + /* Stop monitoring */ + kthread_cancel_delayed_work_sync(&zldev->work); + + /* Unregister all DPLLs */ + list_for_each_entry(zldpll, &zldev->dplls, list) { + if (zldpll->dpll_dev) + zl3073x_dpll_unregister(zldpll); + } +} + +static void zl3073x_dev_dpll_fini(void *ptr) +{ + struct zl3073x_dpll *zldpll, *next; + struct zl3073x_dev *zldev = ptr; + + /* Stop monitoring and unregister DPLLs */ + zl3073x_dev_stop(zldev); + + /* Destroy monitoring thread */ + if (zldev->kworker) { + kthread_destroy_worker(zldev->kworker); + zldev->kworker = NULL; + } + + /* Free all DPLLs */ + list_for_each_entry_safe(zldpll, next, &zldev->dplls, list) { + list_del(&zldpll->list); + zl3073x_dpll_free(zldpll); + } +} + +static int +zl3073x_devm_dpll_init(struct zl3073x_dev *zldev) +{ + struct kthread_worker *kworker; + struct zl3073x_dpll *zldpll; + unsigned int i; + int rc; + + INIT_LIST_HEAD(&zldev->dplls); + + /* Allocate all DPLLs */ + for (i = 0; i < zldev->info->num_channels; i++) { + zldpll = zl3073x_dpll_alloc(zldev, i); + if (IS_ERR(zldpll)) { + dev_err_probe(zldev->dev, PTR_ERR(zldpll), + "Failed to alloc DPLL%u\n", i); + rc = PTR_ERR(zldpll); + goto error; + } + + list_add_tail(&zldpll->list, &zldev->dplls); + } + + /* Initialize monitoring thread */ + kthread_init_delayed_work(&zldev->work, zl3073x_dev_periodic_work); + kworker = kthread_run_worker(0, "zl3073x-%s", dev_name(zldev->dev)); + if (IS_ERR(kworker)) { + rc = PTR_ERR(kworker); + goto error; + } + zldev->kworker = kworker; + + /* Start normal operation */ + rc = zl3073x_dev_start(zldev, true); + if (rc) { + dev_err_probe(zldev->dev, rc, "Failed to start device\n"); + goto error; + } + + /* Add devres action to release DPLL related resources */ + return devm_add_action_or_reset(zldev->dev, zl3073x_dev_dpll_fini, zldev); + +error: + zl3073x_dev_dpll_fini(zldev); + + return rc; +} + +/** + * zl3073x_dev_probe - initialize zl3073x device + * @zldev: pointer to zl3073x device + * + * Common initialization of zl3073x device structure. + * + * Returns: 0 on success, <0 on error + */ +int zl3073x_dev_probe(struct zl3073x_dev *zldev) +{ + u16 id, revision, fw_ver; + unsigned int i; + u32 cfg_ver; + int rc; + + /* Read chip ID */ + rc = zl3073x_read_u16(zldev, ZL_REG_ID, &id); + if (rc) + return rc; + + /* Detect chip variant */ + for (i = 0; i < ARRAY_SIZE(zl3073x_chip_ids); i++) { + if (zl3073x_chip_ids[i].id == id) + break; + } + + if (i == ARRAY_SIZE(zl3073x_chip_ids)) + return dev_err_probe(zldev->dev, -ENODEV, + "Unknown chip ID: 0x%04x\n", id); + + zldev->info = &zl3073x_chip_ids[i]; + + /* Read revision, firmware version and custom config version */ + rc = zl3073x_read_u16(zldev, ZL_REG_REVISION, &revision); + if (rc) + return rc; + rc = zl3073x_read_u16(zldev, ZL_REG_FW_VER, &fw_ver); + if (rc) + return rc; + rc = zl3073x_read_u32(zldev, ZL_REG_CUSTOM_CONFIG_VER, &cfg_ver); + if (rc) + return rc; + + dev_dbg(zldev->dev, "ChipID(%X), ChipRev(%X), FwVer(%u)\n", id, + revision, fw_ver); + dev_dbg(zldev->dev, "Custom config version: %lu.%lu.%lu.%lu\n", + FIELD_GET(GENMASK(31, 24), cfg_ver), + FIELD_GET(GENMASK(23, 16), cfg_ver), + FIELD_GET(GENMASK(15, 8), cfg_ver), + FIELD_GET(GENMASK(7, 0), cfg_ver)); + + /* Generate random clock ID as the device has not such property that + * could be used for this purpose. A user can later change this value + * using devlink. + */ + zldev->clock_id = get_random_u64(); + + /* Default phase offset averaging factor */ + zldev->phase_avg_factor = 2; + + /* Initialize mutex for operations where multiple reads, writes + * and/or polls are required to be done atomically. + */ + rc = devm_mutex_init(zldev->dev, &zldev->multiop_lock); + if (rc) + return dev_err_probe(zldev->dev, rc, + "Failed to initialize mutex\n"); + rc = devm_mutex_init(zldev->dev, &zldev->phase_step_lock); + if (rc) + return dev_err_probe(zldev->dev, rc, + "Failed to initialize mutex\n"); + rc = devm_mutex_init(zldev->dev, &zldev->tie_lock); + if (rc) + return dev_err_probe(zldev->dev, rc, + "Failed to initialize mutex\n"); + + /* Register DPLL channels */ + rc = zl3073x_devm_dpll_init(zldev); + if (rc) + return rc; + + /* Register the devlink instance and parameters */ + rc = zl3073x_devlink_register(zldev); + if (rc) + return dev_err_probe(zldev->dev, rc, + "Failed to register devlink instance\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(zl3073x_dev_probe, "ZL3073X"); + +MODULE_AUTHOR("Ivan Vecera "); +MODULE_DESCRIPTION("Microchip ZL3073x core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h new file mode 100644 index 000000000..67c10e259 --- /dev/null +++ b/drivers/dpll/zl3073x/core.h @@ -0,0 +1,449 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_CORE_H +#define _ZL3073X_CORE_H + +#include +#include +#include +#include +#include +#include + +#include "chan.h" +#include "out.h" +#include "ref.h" +#include "regs.h" +#include "synth.h" + +struct device; +struct regmap; +struct zl3073x_dpll; + +/* Per-operation poll timeouts */ +#define ZL_POLL_DF_READ_TIMEOUT_US (25 * USEC_PER_MSEC) +#define ZL_POLL_FREQ_MEAS_TIMEOUT_US (50 * USEC_PER_MSEC) +#define ZL_POLL_HWREG_TIMEOUT_US (50 * USEC_PER_MSEC) +#define ZL_POLL_MB_TIMEOUT_US (30 * USEC_PER_MSEC) +#define ZL_POLL_PHASE_ERR_TIMEOUT_US (50 * USEC_PER_MSEC) +#define ZL_POLL_PHASE_STEP_TIMEOUT_US (3000 * USEC_PER_MSEC) +#define ZL_POLL_TIE_WR_TIMEOUT_US (1000 * USEC_PER_MSEC) +#define ZL_POLL_TOD_RD_TIMEOUT_US (30 * USEC_PER_MSEC) +#define ZL_POLL_TOD_WR_TIMEOUT_US (1000 * USEC_PER_MSEC) + +enum zl3073x_flags { + ZL3073X_FLAG_REF_PHASE_COMP_32_BIT, + ZL3073X_FLAG_DIE_TEMP_BIT, + ZL3073X_FLAGS_NBITS /* must be last */ +}; + +#define __ZL3073X_FLAG(name) BIT(ZL3073X_FLAG_ ## name ## _BIT) +#define ZL3073X_FLAG_REF_PHASE_COMP_32 __ZL3073X_FLAG(REF_PHASE_COMP_32) +#define ZL3073X_FLAG_DIE_TEMP __ZL3073X_FLAG(DIE_TEMP) + +/** + * struct zl3073x_chip_info - chip variant identification + * @id: chip ID + * @num_channels: number of DPLL channels supported by this variant + * @flags: chip variant flags + */ +struct zl3073x_chip_info { + u16 id; + u8 num_channels; + unsigned long flags; +}; + +/** + * struct zl3073x_dev - zl3073x device + * @dev: pointer to device + * @regmap: regmap to access device registers + * @info: detected chip info + * @multiop_lock: to serialize multiple register operations + * @tie_lock: to serialize TIE write operations + * @phase_step_lock: to serialize output phase step operations + * @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 + * @clock_id: clock id of the device + * @out_step_time_mask: output step-time mask (device-global) + * @phase_avg_factor: phase offset measurement averaging factor + * @freq_monitor: is frequency monitor enabled + */ +struct zl3073x_dev { + struct device *dev; + struct regmap *regmap; + const struct zl3073x_chip_info *info; + struct mutex multiop_lock; + struct mutex tie_lock; + struct mutex phase_step_lock; + + /* Invariants */ + 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; + + /* Monitor */ + struct kthread_worker *kworker; + struct kthread_delayed_work work; + + /* Per-chip parameters */ + u64 clock_id; + u16 out_step_time_mask; + u8 phase_avg_factor; + bool freq_monitor; +}; + +extern const struct regmap_config zl3073x_regmap_config; + +struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev); +int zl3073x_dev_probe(struct zl3073x_dev *zldev); + +int zl3073x_dev_start(struct zl3073x_dev *zldev, bool full); +void zl3073x_dev_stop(struct zl3073x_dev *zldev); + +static inline u8 zl3073x_dev_phase_avg_factor_get(struct zl3073x_dev *zldev) +{ + return READ_ONCE(zldev->phase_avg_factor); +} + +int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor); + +/********************** + * Registers operations + **********************/ + +/** + * struct zl3073x_hwreg_seq_item - HW register write sequence item + * @addr: HW register to be written + * @value: value to be written to HW register + * @mask: bitmask indicating bits to be updated + * @wait: number of ms to wait after register write + */ +struct zl3073x_hwreg_seq_item { + u32 addr; + u32 value; + u32 mask; + u32 wait; +}; + +#define HWREG_SEQ_ITEM(_addr, _value, _mask, _wait) \ +{ \ + .addr = _addr, \ + .value = FIELD_PREP_CONST(_mask, _value), \ + .mask = _mask, \ + .wait = _wait, \ +} + +int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, + unsigned int mask_reg, u16 mask_val); +int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, + u8 mask, unsigned int timeout_us); +int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val); +int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val); +int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val); +int zl3073x_read_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 *val); +int zl3073x_write_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 val); +int zl3073x_write_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 val); +int zl3073x_write_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 val); +int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val); +int zl3073x_read_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 *value); +int zl3073x_write_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value); +int zl3073x_update_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value, + u32 mask); +int zl3073x_write_hwreg_seq(struct zl3073x_dev *zldev, + const struct zl3073x_hwreg_seq_item *seq, + size_t num_items); + +/***************** + * Misc operations + *****************/ + +int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel); + +/** + * zl3073x_dev_is_ref_phase_comp_32bit - check ref phase comp register size + * @zldev: pointer to zl3073x device + * + * Some chip IDs have a 32-bit wide ref_phase_offset_comp register instead + * of the default 48-bit. + * + * Return: true if the register is 32-bit, false if 48-bit + */ +static inline bool +zl3073x_dev_is_ref_phase_comp_32bit(struct zl3073x_dev *zldev) +{ + return zldev->info->flags & ZL3073X_FLAG_REF_PHASE_COMP_32; +} + +static inline bool +zl3073x_is_n_pin(u8 id) +{ + /* P-pins ids are even while N-pins are odd */ + return id & 1; +} + +static inline bool +zl3073x_is_p_pin(u8 id) +{ + return !zl3073x_is_n_pin(id); +} + +/** + * zl3073x_input_pin_ref_get - get reference for given input pin + * @id: input pin id + * + * Return: reference id for the given input pin + */ +static inline u8 +zl3073x_input_pin_ref_get(u8 id) +{ + return id; +} + +/** + * zl3073x_output_pin_out_get - get output for the given output pin + * @id: output pin id + * + * Return: output id for the given output pin + */ +static inline u8 +zl3073x_output_pin_out_get(u8 id) +{ + /* Output pin pair shares the single output */ + return id / 2; +} + +/** + * zl3073x_dev_ref_freq_get - get input reference frequency + * @zldev: pointer to zl3073x device + * @index: input reference index + * + * Return: frequency of given input reference + */ +static inline u32 +zl3073x_dev_ref_freq_get(struct zl3073x_dev *zldev, u8 index) +{ + const struct zl3073x_ref *ref = zl3073x_ref_state_get(zldev, index); + + return zl3073x_ref_freq_get(ref); +} + +/** + * zl3073x_dev_ref_is_diff - check if the given input reference is differential + * @zldev: pointer to zl3073x device + * @index: input reference index + * + * Return: true if reference is differential, false if reference is single-ended + */ +static inline bool +zl3073x_dev_ref_is_diff(struct zl3073x_dev *zldev, u8 index) +{ + const struct zl3073x_ref *ref = zl3073x_ref_state_get(zldev, index); + + return zl3073x_ref_is_diff(ref); +} + +/* + * zl3073x_dev_ref_is_status_ok - check the given input reference status + * @zldev: pointer to zl3073x device + * @index: input reference index + * + * Return: true if the status is ok, false otherwise + */ +static inline bool +zl3073x_dev_ref_is_status_ok(struct zl3073x_dev *zldev, u8 index) +{ + const struct zl3073x_ref *ref = zl3073x_ref_state_get(zldev, index); + + return zl3073x_ref_is_status_ok(ref); +} + +/** + * zl3073x_dev_synth_freq_get - get synth current freq + * @zldev: pointer to zl3073x device + * @index: synth index + * + * Return: frequency of given synthetizer + */ +static inline u32 +zl3073x_dev_synth_freq_get(struct zl3073x_dev *zldev, u8 index) +{ + const struct zl3073x_synth *synth; + + synth = zl3073x_synth_state_get(zldev, index); + return zl3073x_synth_freq_get(synth); +} + +/** + * zl3073x_dev_out_synth_get - get synth connected to given output + * @zldev: pointer to zl3073x device + * @index: output index + * + * Return: index of synth connected to given output. + */ +static inline u8 +zl3073x_dev_out_synth_get(struct zl3073x_dev *zldev, u8 index) +{ + const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index); + + return zl3073x_out_synth_get(out); +} + +/** + * zl3073x_dev_out_is_enabled - check if the given output is enabled + * @zldev: pointer to zl3073x device + * @index: output index + * + * Return: true if the output is enabled, false otherwise + */ +static inline bool +zl3073x_dev_out_is_enabled(struct zl3073x_dev *zldev, u8 index) +{ + const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index); + const struct zl3073x_synth *synth; + u8 synth_id; + + /* Output is enabled only if associated synth is enabled */ + synth_id = zl3073x_out_synth_get(out); + synth = zl3073x_synth_state_get(zldev, synth_id); + + return zl3073x_synth_is_enabled(synth) && zl3073x_out_is_enabled(out); +} + +/** + * zl3073x_dev_out_is_stepped - check if output is in step-time mask + * @zldev: pointer to zl3073x device + * @index: output index + * + * Return: true if output is affected by step-time operations + */ +static inline bool +zl3073x_dev_out_is_stepped(struct zl3073x_dev *zldev, u8 index) +{ + return !!(zldev->out_step_time_mask & BIT(index)); +} + +/** + * zl3073x_dev_out_dpll_get - get DPLL ID the output is driven by + * @zldev: pointer to zl3073x device + * @index: output index + * + * Return: ID of DPLL the given output is driven by + */ +static inline +u8 zl3073x_dev_out_dpll_get(struct zl3073x_dev *zldev, u8 index) +{ + const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index); + const struct zl3073x_synth *synth; + u8 synth_id; + + /* Get synthesizer connected to given output */ + synth_id = zl3073x_out_synth_get(out); + synth = zl3073x_synth_state_get(zldev, synth_id); + + /* Return DPLL that drives the synth */ + return zl3073x_synth_dpll_get(synth); +} + +/** + * zl3073x_dev_output_pin_freq_get - get output pin frequency + * @zldev: pointer to zl3073x device + * @id: output pin id + * + * Computes the output pin frequency based on the synth frequency, output + * divisor, and signal format. For N-div formats, N-pin frequency is + * additionally divided by esync_n_period. + * + * Return: frequency of the given output pin in Hz + */ +static inline u32 +zl3073x_dev_output_pin_freq_get(struct zl3073x_dev *zldev, u8 id) +{ + const struct zl3073x_synth *synth; + const struct zl3073x_out *out; + u8 out_id; + u32 freq; + + out_id = zl3073x_output_pin_out_get(id); + out = zl3073x_out_state_get(zldev, out_id); + synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(out)); + freq = zl3073x_synth_freq_get(synth) / out->div; + + if (zl3073x_out_is_ndiv(out) && zl3073x_is_n_pin(id)) + freq /= out->esync_n_period; + + return freq; +} + +/** + * zl3073x_dev_out_is_diff - check if the given output is differential + * @zldev: pointer to zl3073x device + * @index: output index + * + * Return: true if output is differential, false if output is single-ended + */ +static inline bool +zl3073x_dev_out_is_diff(struct zl3073x_dev *zldev, u8 index) +{ + const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index); + + return zl3073x_out_is_diff(out); +} + +/** + * zl3073x_dev_output_pin_is_enabled - check if the given output pin is enabled + * @zldev: pointer to zl3073x device + * @id: output pin id + * + * Checks if the output of the given output pin is enabled and also that + * its signal format also enables the given pin. + * + * Return: true if output pin is enabled, false if output pin is disabled + */ +static inline bool +zl3073x_dev_output_pin_is_enabled(struct zl3073x_dev *zldev, u8 id) +{ + u8 out_id = zl3073x_output_pin_out_get(id); + const struct zl3073x_out *out; + + out = zl3073x_out_state_get(zldev, out_id); + + /* Check if the output is enabled - call _dev_ helper that + * additionally checks for attached synth enablement. + */ + if (!zl3073x_dev_out_is_enabled(zldev, out_id)) + return false; + + /* Check signal format */ + switch (zl3073x_out_signal_format_get(out)) { + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_DISABLED: + /* Both output pins are disabled by signal format */ + return false; + + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_1P: + /* Output is one single ended P-pin output */ + if (zl3073x_is_n_pin(id)) + return false; + break; + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_1N: + /* Output is one single ended N-pin output */ + if (zl3073x_is_p_pin(id)) + return false; + break; + default: + /* For other format both pins are enabled */ + break; + } + + return true; +} + +#endif /* _ZL3073X_CORE_H */ diff --git a/drivers/dpll/zl3073x/devlink.c b/drivers/dpll/zl3073x/devlink.c new file mode 100644 index 000000000..218b08fd8 --- /dev/null +++ b/drivers/dpll/zl3073x/devlink.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include "core.h" +#include "devlink.h" +#include "dpll.h" +#include "flash.h" +#include "fw.h" +#include "regs.h" + +/** + * zl3073x_devlink_info_get - Devlink device info callback + * @devlink: devlink structure pointer + * @req: devlink request pointer to store information + * @extack: netlink extack pointer to report errors + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dev *zldev = devlink_priv(devlink); + u16 id, revision, fw_ver; + char buf[16]; + u32 cfg_ver; + int rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_ID, &id); + if (rc) + return rc; + + snprintf(buf, sizeof(buf), "%X", id); + rc = devlink_info_version_fixed_put(req, + DEVLINK_INFO_VERSION_GENERIC_ASIC_ID, + buf); + if (rc) + return rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_REVISION, &revision); + if (rc) + return rc; + + snprintf(buf, sizeof(buf), "%X", revision); + rc = devlink_info_version_fixed_put(req, + DEVLINK_INFO_VERSION_GENERIC_ASIC_REV, + buf); + if (rc) + return rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_FW_VER, &fw_ver); + if (rc) + return rc; + + snprintf(buf, sizeof(buf), "%u", fw_ver); + rc = devlink_info_version_running_put(req, + DEVLINK_INFO_VERSION_GENERIC_FW, + buf); + if (rc) + return rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_CUSTOM_CONFIG_VER, &cfg_ver); + if (rc) + return rc; + + /* No custom config version */ + if (cfg_ver == U32_MAX) + return 0; + + snprintf(buf, sizeof(buf), "%lu.%lu.%lu.%lu", + FIELD_GET(GENMASK(31, 24), cfg_ver), + FIELD_GET(GENMASK(23, 16), cfg_ver), + FIELD_GET(GENMASK(15, 8), cfg_ver), + FIELD_GET(GENMASK(7, 0), cfg_ver)); + + return devlink_info_version_running_put(req, "custom_cfg", buf); +} + +static int +zl3073x_devlink_reload_down(struct devlink *devlink, bool netns_change, + enum devlink_reload_action action, + enum devlink_reload_limit limit, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dev *zldev = devlink_priv(devlink); + + if (action != DEVLINK_RELOAD_ACTION_DRIVER_REINIT) + return -EOPNOTSUPP; + + /* Stop normal operation */ + zl3073x_dev_stop(zldev); + + return 0; +} + +static int +zl3073x_devlink_reload_up(struct devlink *devlink, + enum devlink_reload_action action, + enum devlink_reload_limit limit, + u32 *actions_performed, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dev *zldev = devlink_priv(devlink); + union devlink_param_value val; + int rc; + + if (action != DEVLINK_RELOAD_ACTION_DRIVER_REINIT) + return -EOPNOTSUPP; + + rc = devl_param_driverinit_value_get(devlink, + DEVLINK_PARAM_GENERIC_ID_CLOCK_ID, + &val); + if (rc) + return rc; + + if (zldev->clock_id != val.vu64) { + dev_dbg(zldev->dev, + "'clock_id' changed to %016llx\n", val.vu64); + zldev->clock_id = val.vu64; + } + + /* Restart normal operation */ + rc = zl3073x_dev_start(zldev, false); + if (rc) + dev_warn(zldev->dev, "Failed to re-start normal operation\n"); + + *actions_performed = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT); + + return 0; +} + +void zl3073x_devlink_flash_notify(struct zl3073x_dev *zldev, const char *msg, + const char *component, u32 done, u32 total) +{ + struct devlink *devlink = priv_to_devlink(zldev); + + devlink_flash_update_status_notify(devlink, msg, component, done, + total); +} + +/** + * zl3073x_devlink_flash_prepare - Prepare and enter flash mode + * @zldev: zl3073x device pointer + * @zlfw: pointer to loaded firmware + * @extack: netlink extack pointer to report errors + * + * The function stops normal operation and switches the device to flash mode. + * If an error occurs the normal operation is resumed. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_devlink_flash_prepare(struct zl3073x_dev *zldev, + struct zl3073x_fw *zlfw, + struct netlink_ext_ack *extack) +{ + struct zl3073x_fw_component *util; + int rc; + + util = zlfw->component[ZL_FW_COMPONENT_UTIL]; + if (!util) { + zl3073x_devlink_flash_notify(zldev, + "Utility is missing in firmware", + NULL, 0, 0); + return -ENOEXEC; + } + + /* Stop normal operation prior entering flash mode */ + zl3073x_dev_stop(zldev); + + rc = zl3073x_flash_mode_enter(zldev, util->data, util->size, extack); + if (rc) { + zl3073x_devlink_flash_notify(zldev, + "Failed to enter flash mode", + NULL, 0, 0); + + /* Resume normal operation */ + zl3073x_dev_start(zldev, true); + + return rc; + } + + return 0; +} + +/** + * zl3073x_devlink_flash_finish - Leave flash mode and resume normal operation + * @zldev: zl3073x device pointer + * @extack: netlink extack pointer to report errors + * + * The function switches the device back to standard mode and resumes normal + * operation. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_devlink_flash_finish(struct zl3073x_dev *zldev, + struct netlink_ext_ack *extack) +{ + int rc; + + /* Reset device CPU to normal mode */ + zl3073x_flash_mode_leave(zldev, extack); + + /* Resume normal operation */ + rc = zl3073x_dev_start(zldev, true); + if (rc) + zl3073x_devlink_flash_notify(zldev, + "Failed to start normal operation", + NULL, 0, 0); + + return rc; +} + +/** + * zl3073x_devlink_flash_update - Devlink flash update callback + * @devlink: devlink structure pointer + * @params: flashing parameters pointer + * @extack: netlink extack pointer to report errors + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_devlink_flash_update(struct devlink *devlink, + struct devlink_flash_update_params *params, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dev *zldev = devlink_priv(devlink); + struct zl3073x_fw *zlfw; + int rc = 0; + + zlfw = zl3073x_fw_load(zldev, params->fw->data, params->fw->size, + extack); + if (IS_ERR(zlfw)) { + zl3073x_devlink_flash_notify(zldev, "Failed to load firmware", + NULL, 0, 0); + rc = PTR_ERR(zlfw); + goto finish; + } + + /* Stop normal operation and enter flash mode */ + rc = zl3073x_devlink_flash_prepare(zldev, zlfw, extack); + if (rc) + goto finish; + + rc = zl3073x_fw_flash(zldev, zlfw, extack); + if (rc) { + zl3073x_devlink_flash_finish(zldev, extack); + goto finish; + } + + /* Resume normal mode */ + rc = zl3073x_devlink_flash_finish(zldev, extack); + +finish: + if (!IS_ERR(zlfw)) + zl3073x_fw_free(zlfw); + + zl3073x_devlink_flash_notify(zldev, + rc ? "Flashing failed" : "Flashing done", + NULL, 0, 0); + + return rc; +} + +static const struct devlink_ops zl3073x_devlink_ops = { + .info_get = zl3073x_devlink_info_get, + .reload_actions = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT), + .reload_down = zl3073x_devlink_reload_down, + .reload_up = zl3073x_devlink_reload_up, + .flash_update = zl3073x_devlink_flash_update, +}; + +static void +zl3073x_devlink_free(void *ptr) +{ + devlink_free(ptr); +} + +/** + * zl3073x_devm_alloc - allocates zl3073x device structure + * @dev: pointer to device structure + * + * Allocates zl3073x device structure as device resource. + * + * Return: pointer to zl3073x device on success, error pointer on error + */ +struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev) +{ + struct zl3073x_dev *zldev; + struct devlink *devlink; + int rc; + + devlink = devlink_alloc(&zl3073x_devlink_ops, sizeof(*zldev), dev); + if (!devlink) + return ERR_PTR(-ENOMEM); + + /* Add devres action to free devlink device */ + rc = devm_add_action_or_reset(dev, zl3073x_devlink_free, devlink); + if (rc) + return ERR_PTR(rc); + + zldev = devlink_priv(devlink); + zldev->dev = dev; + dev_set_drvdata(zldev->dev, zldev); + + return zldev; +} +EXPORT_SYMBOL_NS_GPL(zl3073x_devm_alloc, "ZL3073X"); + +static int +zl3073x_devlink_param_clock_id_validate(struct devlink *devlink, u32 id, + union devlink_param_value *val, + struct netlink_ext_ack *extack) +{ + if (!val->vu64) { + NL_SET_ERR_MSG_MOD(extack, "'clock_id' must be non-zero"); + return -EINVAL; + } + + return 0; +} + +static const struct devlink_param zl3073x_devlink_params[] = { + DEVLINK_PARAM_GENERIC(CLOCK_ID, BIT(DEVLINK_PARAM_CMODE_DRIVERINIT), + NULL, NULL, + zl3073x_devlink_param_clock_id_validate), +}; + +static void +zl3073x_devlink_unregister(void *ptr) +{ + struct devlink *devlink = priv_to_devlink(ptr); + + devl_lock(devlink); + + /* Unregister devlink params */ + devl_params_unregister(devlink, zl3073x_devlink_params, + ARRAY_SIZE(zl3073x_devlink_params)); + + /* Unregister devlink instance */ + devl_unregister(devlink); + + devl_unlock(devlink); +} + +/** + * zl3073x_devlink_register - register devlink instance and params + * @zldev: zl3073x device to register the devlink for + * + * Register the devlink instance and parameters associated with the device. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_devlink_register(struct zl3073x_dev *zldev) +{ + struct devlink *devlink = priv_to_devlink(zldev); + union devlink_param_value value; + int rc; + + devl_lock(devlink); + + /* Register devlink params */ + rc = devl_params_register(devlink, zl3073x_devlink_params, + ARRAY_SIZE(zl3073x_devlink_params)); + if (rc) { + devl_unlock(devlink); + + return rc; + } + + value.vu64 = zldev->clock_id; + devl_param_driverinit_value_set(devlink, + DEVLINK_PARAM_GENERIC_ID_CLOCK_ID, + &value); + + /* Register devlink instance */ + devl_register(devlink); + + devl_unlock(devlink); + + /* Add devres action to unregister devlink device */ + return devm_add_action_or_reset(zldev->dev, zl3073x_devlink_unregister, + zldev); +} diff --git a/drivers/dpll/zl3073x/devlink.h b/drivers/dpll/zl3073x/devlink.h new file mode 100644 index 000000000..63dfd6fa1 --- /dev/null +++ b/drivers/dpll/zl3073x/devlink.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_DEVLINK_H +#define _ZL3073X_DEVLINK_H + +struct zl3073x_dev; + +struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev); + +int zl3073x_devlink_register(struct zl3073x_dev *zldev); + +void zl3073x_devlink_flash_notify(struct zl3073x_dev *zldev, const char *msg, + const char *component, u32 done, u32 total); + +#endif /* _ZL3073X_DEVLINK_H */ diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c new file mode 100644 index 000000000..f2e137475 --- /dev/null +++ b/drivers/dpll/zl3073x/dpll.c @@ -0,0 +1,2874 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "dpll.h" +#include "prop.h" +#include "regs.h" + +#define ZL3073X_DPLL_REF_NONE ZL3073X_NUM_REFS +#define ZL3073X_DPLL_REF_IS_VALID(_ref) ((_ref) != ZL3073X_DPLL_REF_NONE) + +/** + * struct zl3073x_dpll_pin - DPLL pin + * @list: this DPLL pin list entry + * @dpll: DPLL the pin is registered to + * @dpll_pin: pointer to registered dpll_pin + * @tracker: tracking object for the acquired reference + * @fwnode: firmware node handle + * @label: package label + * @dir: pin direction + * @id: pin id + * @prio: pin priority <0, 14> + * @esync_control: embedded sync is controllable + * @phase_gran: phase adjustment granularity + * @operstate: last saved operational state + * @phase_offset: last saved pin phase offset + * @freq_offset: last saved fractional frequency offset + * @measured_freq: last saved measured frequency + */ +struct zl3073x_dpll_pin { + struct list_head list; + struct zl3073x_dpll *dpll; + struct dpll_pin *dpll_pin; + dpll_tracker tracker; + struct fwnode_handle *fwnode; + char label[8]; + enum dpll_pin_direction dir; + u8 id; + u8 prio; + bool esync_control; + s32 phase_gran; + enum dpll_pin_operstate operstate; + s64 phase_offset; + s64 freq_offset; + u32 measured_freq; +}; + +/* + * Supported esync ranges for input and for output per output pair type + */ +static const struct dpll_pin_frequency esync_freq_ranges[] = { + DPLL_PIN_FREQUENCY_RANGE(0, 1), +}; + +/** + * zl3073x_dpll_is_input_pin - check if the pin is input one + * @pin: pin to check + * + * Return: true if pin is input, false if pin is output. + */ +static bool +zl3073x_dpll_is_input_pin(struct zl3073x_dpll_pin *pin) +{ + return pin->dir == DPLL_PIN_DIRECTION_INPUT; +} + +/** + * zl3073x_dpll_is_nco_pin - check if the pin is a virtual NCO pin + * @pin: pin to check + * + * Return: true if pin is a virtual NCO pin, false otherwise. + */ +static bool +zl3073x_dpll_is_nco_pin(struct zl3073x_dpll_pin *pin) +{ + return pin->id == ZL3073X_NCO_PIN_ID; +} + +/** + * zl3073x_dpll_is_p_pin - check if the pin is P-pin + * @pin: pin to check + * + * Return: true if the pin is P-pin, false if it is N-pin + */ +static bool +zl3073x_dpll_is_p_pin(struct zl3073x_dpll_pin *pin) +{ + return zl3073x_is_p_pin(pin->id); +} + +static int +zl3073x_dpll_pin_direction_get(const struct dpll_pin *dpll_pin, void *pin_priv, + const struct dpll_device *dpll, void *dpll_priv, + enum dpll_pin_direction *direction, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll_pin *pin = pin_priv; + + *direction = pin->dir; + + return 0; +} + +static struct zl3073x_dpll_pin * +zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id) +{ + struct zl3073x_dpll_pin *pin; + + list_for_each_entry(pin, &zldpll->pins, list) { + if (zl3073x_dpll_is_input_pin(pin) && + zl3073x_input_pin_ref_get(pin->id) == ref_id) + return pin; + } + + return NULL; +} + +static struct zl3073x_dpll_pin * +zl3073x_dpll_nco_pin_get(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + + list_for_each_entry(pin, &zldpll->pins, list) { + if (zl3073x_dpll_is_nco_pin(pin)) + return pin; + } + + return NULL; +} + +static int +zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + struct dpll_pin_esync *esync, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + const struct zl3073x_ref *ref; + u8 ref_id; + + guard(mutex)(&zldpll->lock); + + ref_id = zl3073x_input_pin_ref_get(pin->id); + ref = zl3073x_ref_state_get(zldev, ref_id); + + if (!pin->esync_control || zl3073x_ref_freq_get(ref) <= 1) + return -EOPNOTSUPP; + + esync->range = esync_freq_ranges; + esync->range_num = ARRAY_SIZE(esync_freq_ranges); + + switch (zl3073x_ref_sync_mode_get(ref)) { + case ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75: + esync->freq = ref->esync_n_div == ZL_REF_ESYNC_DIV_1HZ ? 1 : 0; + esync->pulse = 25; + break; + default: + esync->freq = 0; + esync->pulse = 0; + break; + } + + return 0; +} + +static int +zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 freq, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_ref ref; + u8 ref_id, sync_mode; + + guard(mutex)(&zldpll->lock); + + ref_id = zl3073x_input_pin_ref_get(pin->id); + ref = *zl3073x_ref_state_get(zldev, ref_id); + + /* Use freq == 0 to disable esync */ + if (!freq) + sync_mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF; + else + sync_mode = ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75; + + zl3073x_ref_sync_mode_set(&ref, sync_mode); + + if (freq) { + /* 1 Hz is only supported frequency now */ + ref.esync_n_div = ZL_REF_ESYNC_DIV_1HZ; + } + + /* Update reference configuration */ + return zl3073x_ref_state_set(zldev, ref_id, &ref); +} + +static int +zl3073x_dpll_input_pin_ref_sync_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_pin *ref_sync_pin, + void *ref_sync_pin_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll_pin *sync_pin = ref_sync_pin_priv; + struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_ref *ref; + u8 ref_id, mode, pair; + + guard(mutex)(&zldpll->lock); + + ref_id = zl3073x_input_pin_ref_get(pin->id); + ref = zl3073x_ref_state_get(zldev, ref_id); + mode = zl3073x_ref_sync_mode_get(ref); + pair = zl3073x_ref_sync_pair_get(ref); + + if (mode == ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR && + pair == zl3073x_input_pin_ref_get(sync_pin->id)) + *state = DPLL_PIN_STATE_CONNECTED; + else + *state = DPLL_PIN_STATE_DISCONNECTED; + + return 0; +} + +static int +zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_pin *ref_sync_pin, + void *ref_sync_pin_priv, + const enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll_pin *sync_pin = ref_sync_pin_priv; + struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + u8 mode, ref_id, sync_ref_id; + struct zl3073x_chan chan; + struct zl3073x_ref ref; + bool sync_ntf = false; + int rc; + + mutex_lock(&zldpll->lock); + + ref_id = zl3073x_input_pin_ref_get(pin->id); + sync_ref_id = zl3073x_input_pin_ref_get(sync_pin->id); + ref = *zl3073x_ref_state_get(zldev, ref_id); + + if (state == DPLL_PIN_STATE_CONNECTED) { + const struct zl3073x_ref *sync_ref; + u32 ref_freq, sync_freq; + + sync_ref = zl3073x_ref_state_get(zldev, sync_ref_id); + ref_freq = zl3073x_ref_freq_get(&ref); + sync_freq = zl3073x_ref_freq_get(sync_ref); + + /* Sync signal must be 8 kHz or less and clock reference + * must be 1 kHz or more and higher than the sync signal. + */ + if (sync_freq > 8000) { + NL_SET_ERR_MSG(extack, + "sync frequency must be 8 kHz or less"); + rc = -EINVAL; + goto unlock; + } + if (ref_freq < 1000) { + NL_SET_ERR_MSG(extack, + "clock frequency must be 1 kHz or more"); + rc = -EINVAL; + goto unlock; + } + if (ref_freq <= sync_freq) { + NL_SET_ERR_MSG(extack, + "clock frequency must be higher than sync frequency"); + rc = -EINVAL; + goto unlock; + } + + zl3073x_ref_sync_pair_set(&ref, sync_ref_id); + mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR; + } else { + mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF; + } + + zl3073x_ref_sync_mode_set(&ref, mode); + + rc = zl3073x_ref_state_set(zldev, ref_id, &ref); + if (rc) + goto unlock; + + /* All code paths accessing per-channel reference priorities are + * serialized by the subsystem dpll_lock, so it is safe to release + * our lock here before iterating over the other channels. + */ + mutex_unlock(&zldpll->lock); + + if (state != DPLL_PIN_STATE_CONNECTED) + return 0; + + /* The datasheet recommends excluding the sync source from automatic + * reference selection by setting its priority to NONE on all DPLL + * channels. This is advisory - the ref sync pair is already + * configured, so a failure here is not fatal. On disconnect the + * priority is left as NONE and the user must explicitly make the + * pin selectable again. + */ + list_for_each_entry(zldpll, &zldev->dplls, list) { + u8 prio; + + mutex_lock(&zldpll->lock); + + chan = *zl3073x_chan_state_get(zldev, zldpll->id); + prio = zl3073x_chan_ref_prio_get(&chan, sync_ref_id); + if (prio == ZL_DPLL_REF_PRIO_NONE) { + mutex_unlock(&zldpll->lock); + continue; /* Ref is already non-selectable */ + } + + zl3073x_chan_ref_prio_set(&chan, sync_ref_id, + ZL_DPLL_REF_PRIO_NONE); + if (zl3073x_chan_state_set(zldev, zldpll->id, &chan)) + dev_warn(zldev->dev, + "Failed to set ref prio on DPLL%u\n", + zldpll->id); + else + sync_ntf = true; + + mutex_unlock(&zldpll->lock); + } + if (sync_ntf) + __dpll_pin_change_ntf(sync_pin->dpll_pin); + + return 0; +unlock: + mutex_unlock(&zldpll->lock); + return rc; +} + +static int +zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv, + const struct dpll_device *dpll, void *dpll_priv, + struct dpll_ffo_param *ffo, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dpll_pin *pin = pin_priv; + + guard(mutex)(&zldpll->lock); + + if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE) + return -ENODATA; + + ffo->ffo = pin->freq_offset; + + return 0; +} + +static int +zl3073x_dpll_input_pin_measured_freq_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 *measured_freq, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dpll_pin *pin = pin_priv; + + guard(mutex)(&zldpll->lock); + + *measured_freq = pin->measured_freq; + *measured_freq *= DPLL_PIN_MEASURED_FREQUENCY_DIVIDER; + + return 0; +} + +static int +zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 *frequency, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dpll_pin *pin = pin_priv; + u8 ref_id; + + guard(mutex)(&zldpll->lock); + + ref_id = zl3073x_input_pin_ref_get(pin->id); + *frequency = zl3073x_dev_ref_freq_get(zldpll->dev, ref_id); + + return 0; +} + +static int +zl3073x_dpll_input_pin_frequency_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 frequency, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_ref ref; + u8 ref_id; + + guard(mutex)(&zldpll->lock); + + /* Get reference state */ + ref_id = zl3073x_input_pin_ref_get(pin->id); + ref = *zl3073x_ref_state_get(zldev, ref_id); + + /* Update frequency */ + zl3073x_ref_freq_set(&ref, frequency); + + /* Commit reference state */ + return zl3073x_ref_state_set(zldev, ref_id, &ref); +} + +/** + * zl3073x_dpll_connected_ref_get - get currently connected reference + * @zldpll: pointer to zl3073x_dpll + * + * Looks for currently connected reference the DPLL is locked to. + * + * Return: reference index if locked, ZL3073X_DPLL_REF_NONE otherwise + */ +static u8 +zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll) +{ + const struct zl3073x_chan *chan = zl3073x_chan_state_get(zldpll->dev, + zldpll->id); + u8 state; + + /* 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 ZL3073X_DPLL_REF_NONE; +} + +static int +zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, s64 *phase_offset, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + const struct zl3073x_ref *ref; + u8 conn_id, ref_id; + s64 ref_phase; + + guard(mutex)(&zldpll->lock); + + /* Get currently connected reference */ + 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 + * present. + */ + ref_id = zl3073x_input_pin_ref_get(pin->id); + ref = zl3073x_ref_state_get(zldev, ref_id); + if ((!zldpll->phase_monitor && ref_id != conn_id) || + !zl3073x_ref_is_status_ok(ref)) { + *phase_offset = 0; + return 0; + } + + ref_phase = pin->phase_offset; + + /* The DPLL being locked to a higher freq than the current ref + * the phase offset is modded to the period of the signal + * the dpll is locked to. + */ + if (ZL3073X_DPLL_REF_IS_VALID(conn_id) && conn_id != ref_id) { + u32 conn_freq, ref_freq; + + /* Get frequency of connected and given ref */ + conn_freq = zl3073x_dev_ref_freq_get(zldev, conn_id); + ref_freq = zl3073x_ref_freq_get(ref); + + if (conn_freq > ref_freq) { + s64 conn_period, div_factor; + + conn_period = div_s64(PSEC_PER_SEC, conn_freq); + div_factor = div64_s64(ref_phase, conn_period); + ref_phase -= conn_period * div_factor; + } + } + + *phase_offset = ref_phase * DPLL_PHASE_OFFSET_DIVIDER; + + return 0; +} + +static int +zl3073x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + s32 *phase_adjust, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + const struct zl3073x_ref *ref; + s64 phase_comp; + u8 ref_id; + + guard(mutex)(&zldpll->lock); + + /* Read reference configuration */ + ref_id = zl3073x_input_pin_ref_get(pin->id); + ref = zl3073x_ref_state_get(zldev, ref_id); + + /* Perform sign extension based on register width */ + if (zl3073x_dev_is_ref_phase_comp_32bit(zldev)) + phase_comp = sign_extend64(ref->phase_comp, 31); + else + phase_comp = sign_extend64(ref->phase_comp, 47); + + /* Reverse two's complement negation applied during set and convert + * to 32bit signed int + */ + *phase_adjust = (s32)-phase_comp; + + return 0; +} + +static int +zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + s32 phase_adjust, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_ref ref; + u8 ref_id; + + guard(mutex)(&zldpll->lock); + + /* Read reference configuration */ + ref_id = zl3073x_input_pin_ref_get(pin->id); + ref = *zl3073x_ref_state_get(zldev, ref_id); + + /* The value in the register is stored as two's complement negation + * of requested value. + */ + ref.phase_comp = -phase_adjust; + + /* Update reference configuration */ + return zl3073x_ref_state_set(zldev, ref_id, &ref); +} + +/** + * zl3073x_dpll_ref_operstate_get - get operational state for input pin + * @pin: pointer to pin + * @operstate: place to store operational state + * + * Returns the actual hardware state of the pin: whether it is actively + * used by the DPLL, has no signal, failed qualification, or is simply + * not in use. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_ref_operstate_get(struct zl3073x_dpll_pin *pin, + enum dpll_pin_operstate *operstate) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_ref *ref; + u8 ref_id; + + lockdep_assert_held(&zldpll->lock); + + ref_id = zl3073x_input_pin_ref_get(pin->id); + + /* Check if this pin is the currently locked reference */ + if (ref_id == zl3073x_dpll_connected_ref_get(zldpll)) { + *operstate = DPLL_PIN_OPERSTATE_ACTIVE; + return 0; + } + + /* Check reference monitor status */ + ref = zl3073x_ref_state_get(zldev, ref_id); + if (ref->mon_status & ZL_REF_MON_STATUS_LOS) + *operstate = DPLL_PIN_OPERSTATE_NO_SIGNAL; + else if (!zl3073x_ref_is_status_ok(ref)) + *operstate = DPLL_PIN_OPERSTATE_QUAL_FAILED; + else + *operstate = DPLL_PIN_OPERSTATE_STANDBY; + + return 0; +} + +static int +zl3073x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dpll_pin *pin = pin_priv; + const struct zl3073x_chan *chan; + u8 mode, ref; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + ref = zl3073x_input_pin_ref_get(pin->id); + mode = zl3073x_chan_mode_get(chan); + + switch (mode) { + case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: + if (ref == zl3073x_chan_ref_get(chan)) + *state = DPLL_PIN_STATE_CONNECTED; + else + *state = DPLL_PIN_STATE_DISCONNECTED; + break; + case ZL_DPLL_MODE_REFSEL_MODE_AUTO: + if (zl3073x_chan_ref_is_selectable(chan, ref)) + *state = DPLL_PIN_STATE_SELECTABLE; + else + *state = DPLL_PIN_STATE_DISCONNECTED; + break; + default: + *state = DPLL_PIN_STATE_DISCONNECTED; + break; + } + + return 0; +} + +static int +zl3073x_dpll_input_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_operstate *operstate, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dpll_pin *pin = pin_priv; + + guard(mutex)(&zldpll->lock); + + return zl3073x_dpll_ref_operstate_get(pin, operstate); +} + +static int +zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_dpll_pin *nco_pin = NULL; + struct zl3073x_chan chan; + u8 mode, ref; + int rc = 0; + + mutex_lock(&zldpll->lock); + + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + ref = zl3073x_input_pin_ref_get(pin->id); + mode = zl3073x_chan_mode_get(&chan); + + switch (mode) { + case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: + if (state == DPLL_PIN_STATE_CONNECTED) { + /* Choose the pin as new selected reference */ + zl3073x_chan_ref_set(&chan, ref); + } else if (state == DPLL_PIN_STATE_DISCONNECTED) { + /* 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 { + goto invalid_state; + } + break; + case ZL_DPLL_MODE_REFSEL_MODE_NCO: + if (state == DPLL_PIN_STATE_CONNECTED) + nco_pin = zl3073x_dpll_nco_pin_get(zldpll); + fallthrough; + case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: + case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER: + if (state == DPLL_PIN_STATE_CONNECTED) { + /* 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; + } + break; + case ZL_DPLL_MODE_REFSEL_MODE_AUTO: + if (state == DPLL_PIN_STATE_SELECTABLE) { + if (zl3073x_chan_ref_is_selectable(&chan, ref)) + goto unlock; /* Pin is already selectable */ + + /* Restore pin priority in HW */ + zl3073x_chan_ref_prio_set(&chan, ref, pin->prio); + } else if (state == DPLL_PIN_STATE_DISCONNECTED) { + if (!zl3073x_chan_ref_is_selectable(&chan, ref)) + goto unlock; /* Pin is already disconnected */ + + /* Set pin priority to none in HW */ + zl3073x_chan_ref_prio_set(&chan, ref, + ZL_DPLL_REF_PRIO_NONE); + } else { + 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; + goto unlock; + } + + /* Commit DPLL channel changes */ + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + goto unlock; + +invalid_state: + NL_SET_ERR_MSG_MOD(extack, "Invalid pin state for this device mode"); + rc = -EINVAL; +unlock: + mutex_unlock(&zldpll->lock); + + /* If leaving NCO mode, notify userspace about the NCO pin + * state change - the periodic worker skips the NCO pin. + */ + if (!rc && nco_pin) + __dpll_pin_change_ntf(nco_pin->dpll_pin); + + return rc; +} + +static int +zl3073x_dpll_input_pin_prio_get(const struct dpll_pin *dpll_pin, void *pin_priv, + const struct dpll_device *dpll, void *dpll_priv, + u32 *prio, struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dpll_pin *pin = pin_priv; + + guard(mutex)(&zldpll->lock); + + *prio = pin->prio; + + return 0; +} + +static int +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; + + guard(mutex)(&zldpll->lock); + + if (prio > ZL_DPLL_REF_PRIO_MAX) + return -EINVAL; + + /* If the pin is selectable then update HW registers */ + 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; + } + + /* Save priority */ + pin->prio = prio; + + return 0; +} + +static int +zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + struct dpll_pin_esync *esync, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + const struct zl3073x_synth *synth; + const struct zl3073x_out *out; + u32 synth_freq, out_freq; + u8 out_id; + + guard(mutex)(&zldpll->lock); + + out_id = zl3073x_output_pin_out_get(pin->id); + out = zl3073x_out_state_get(zldev, out_id); + + /* If N-division is enabled, esync is not supported. The register used + * for N-division is also used for the esync divider so both cannot + * be used. + */ + if (zl3073x_out_is_ndiv(out)) + return -EOPNOTSUPP; + + /* Get attached synth frequency */ + synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(out)); + synth_freq = zl3073x_synth_freq_get(synth); + out_freq = synth_freq / out->div; + + if (!pin->esync_control || out_freq <= 1) + return -EOPNOTSUPP; + + esync->range = esync_freq_ranges; + esync->range_num = ARRAY_SIZE(esync_freq_ranges); + + if (zl3073x_out_clock_type_get(out) != ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC) { + /* No need to read esync data if it is not enabled */ + esync->freq = 0; + esync->pulse = 0; + + return 0; + } + + /* Compute esync frequency */ + esync->freq = out_freq / out->esync_n_period; + + /* By comparing the esync_pulse_width to the half of the pulse width + * the esync pulse percentage can be determined. + * Note that half pulse width is in units of half synth cycles, which + * is why it reduces down to be output_div. + */ + esync->pulse = (50 * out->esync_n_width) / out->div; + + return 0; +} + +static int +zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 freq, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + const struct zl3073x_synth *synth; + struct zl3073x_out out; + u32 synth_freq; + u8 out_id; + + guard(mutex)(&zldpll->lock); + + out_id = zl3073x_output_pin_out_get(pin->id); + out = *zl3073x_out_state_get(zldev, out_id); + + /* If N-division is enabled, esync is not supported. The register used + * for N-division is also used for the esync divider so both cannot + * be used. + */ + if (zl3073x_out_is_ndiv(&out)) + return -EOPNOTSUPP; + + /* Update clock type in output mode */ + if (freq) + zl3073x_out_clock_type_set(&out, + ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC); + else + zl3073x_out_clock_type_set(&out, + ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL); + + /* If esync is being disabled just write mailbox and finish */ + if (!freq) + return zl3073x_out_state_set(zldev, out_id, &out); + + /* Get attached synth frequency */ + synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out)); + synth_freq = zl3073x_synth_freq_get(synth); + + /* Compute and update esync period */ + out.esync_n_period = synth_freq / (u32)freq / out.div; + + /* Half of the period in units of 1/2 synth cycle can be represented by + * the output_div. To get the supported esync pulse width of 25% of the + * period the output_div can just be divided by 2. Note that this + * assumes that output_div is even, otherwise some resolution will be + * lost. + */ + out.esync_n_width = out.div / 2; + + /* Commit output configuration */ + return zl3073x_out_state_set(zldev, out_id, &out); +} + +static int +zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 *frequency, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dpll_pin *pin = pin_priv; + + guard(mutex)(&zldpll->lock); + + *frequency = zl3073x_dev_output_pin_freq_get(zldpll->dev, pin->id); + + return 0; +} + +static int +zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 frequency, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + const struct zl3073x_synth *synth; + u32 new_div, synth_freq; + struct zl3073x_out out; + u8 out_id; + + guard(mutex)(&zldpll->lock); + + out_id = zl3073x_output_pin_out_get(pin->id); + out = *zl3073x_out_state_get(zldev, out_id); + + /* Get attached synth frequency and compute new divisor */ + synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out)); + synth_freq = zl3073x_synth_freq_get(synth); + new_div = synth_freq / (u32)frequency; + + /* Check signal format */ + if (!zl3073x_out_is_ndiv(&out)) { + /* For non N-divided signal formats the frequency is computed + * as division of synth frequency and output divisor. + */ + out.div = new_div; + + /* For 50/50 duty cycle the divisor is equal to width */ + out.width = new_div; + + /* Commit output configuration */ + return zl3073x_out_state_set(zldev, out_id, &out); + } + + if (zl3073x_dpll_is_p_pin(pin)) { + /* We are going to change output frequency for P-pin but + * if the requested frequency is less than current N-pin + * frequency then indicate a failure as we are not able + * to compute N-pin divisor to keep its frequency unchanged. + * + * Update divisor for N-pin to keep N-pin frequency. + */ + out.esync_n_period = (out.esync_n_period * out.div) / new_div; + if (!out.esync_n_period) + return -EINVAL; + + /* Update the output divisor */ + out.div = new_div; + + /* For 50/50 duty cycle the divisor is equal to width */ + out.width = out.div; + } else { + /* We are going to change frequency of N-pin but if + * the requested freq is greater or equal than freq of P-pin + * in the output pair we cannot compute divisor for the N-pin. + * In this case indicate a failure. + * + * Update divisor for N-pin + */ + out.esync_n_period = div64_u64(synth_freq, frequency * out.div); + if (!out.esync_n_period) + return -EINVAL; + } + + /* For 50/50 duty cycle the divisor is equal to width */ + out.esync_n_width = out.esync_n_period; + + /* Commit output configuration */ + return zl3073x_out_state_set(zldev, out_id, &out); +} + +static int +zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + s32 *phase_adjust, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + const struct zl3073x_out *out; + u8 out_id; + + guard(mutex)(&zldpll->lock); + + out_id = zl3073x_output_pin_out_get(pin->id); + out = zl3073x_out_state_get(zldev, out_id); + + /* The value in the register is expressed in half synth clock cycles. */ + *phase_adjust = out->phase_comp * pin->phase_gran; + + return 0; +} + +static int +zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + s32 phase_adjust, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_out out; + u8 out_id; + + guard(mutex)(&zldpll->lock); + + out_id = zl3073x_output_pin_out_get(pin->id); + out = *zl3073x_out_state_get(zldev, out_id); + + /* The value in the register is expressed in half synth clock cycles. */ + out.phase_comp = phase_adjust / pin->phase_gran; + + /* Update output configuration from mailbox */ + return zl3073x_out_state_set(zldev, out_id, &out); +} + +static int +zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + /* If the output pin is registered then it is always connected */ + *state = DPLL_PIN_STATE_CONNECTED; + + return 0; +} + +static int +zl3073x_dpll_nco_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_operstate *operstate, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (zl3073x_chan_mode_is_nco(chan)) + *operstate = DPLL_PIN_OPERSTATE_ACTIVE; + else + *operstate = DPLL_PIN_OPERSTATE_STANDBY; + + return 0; +} + +static int +zl3073x_dpll_nco_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (zl3073x_chan_mode_is_nco(chan)) + *state = DPLL_PIN_STATE_CONNECTED; + else + *state = DPLL_PIN_STATE_DISCONNECTED; + + return 0; +} + +static int +zl3073x_dpll_nco_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll_pin *ref_pin = NULL; + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_chan chan; + u8 ref; + int rc; + + mutex_lock(&zldpll->lock); + + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + + switch (state) { + case DPLL_PIN_STATE_CONNECTED: + if (zl3073x_chan_mode_is_nco(&chan)) { + mutex_unlock(&zldpll->lock); + return 0; + } + if (zl3073x_chan_mode_is_auto(&chan)) { + NL_SET_ERR_MSG(extack, + "NCO pin cannot be connected in automatic mode"); + mutex_unlock(&zldpll->lock); + return -EINVAL; + } + if (zl3073x_chan_mode_is_reflock(&chan)) { + /* Get currently connected pin */ + ref = zl3073x_chan_ref_get(&chan); + ref_pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref); + } + rc = zl3073x_chan_nco_mode_set(zldpll->dev, zldpll->id); + break; + case DPLL_PIN_STATE_DISCONNECTED: + if (!zl3073x_chan_mode_is_nco(&chan)) { + mutex_unlock(&zldpll->lock); + return 0; + } + /* Switch to freerun - holdover averaging was not + * updated during NCO mode. + */ + zl3073x_chan_mode_set(&chan, + ZL_DPLL_MODE_REFSEL_MODE_FREERUN); + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + break; + default: + NL_SET_ERR_MSG(extack, "invalid pin state for NCO pin"); + mutex_unlock(&zldpll->lock); + return -EINVAL; + } + + mutex_unlock(&zldpll->lock); + + if (!rc && ref_pin) + __dpll_pin_change_ntf(ref_pin->dpll_pin); + + return rc; +} + +static int +zl3073x_dpll_nco_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv, + const struct dpll_device *dpll, void *dpll_priv, + struct dpll_ffo_param *ffo, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + s64 df_offset; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (!zl3073x_chan_mode_is_nco(chan)) + return -ENODATA; + + /* Do not report FFO if a failure occurred during switching to NCO. */ + df_offset = zl3073x_chan_df_offset_get(chan); + if (df_offset == ZL_DPLL_DF_OFFSET_UNKNOWN) + return -ENODATA; + + /* dpll_df_offset register has inverted sign per datasheet: + * f_offset = f_nom * (-df_offset) / 2^48 + * NCO pin reports DPLL output offset from nominal, so negate. + * Convert to PPT: ppt = -df * 5^12 / 2^36 + */ + ffo->ffo = -mul_s64_u64_shr(df_offset, 244140625, 36); + + return 0; +} + +static int +zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv, + s32 *temp, struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + u16 val; + int rc; + + guard(mutex)(&zldpll->lock); + + rc = zl3073x_read_u16(zldev, ZL_REG_DIE_TEMP_STATUS, &val); + if (rc) + return rc; + + /* Register value is in units of 0.1 C, convert to millidegrees */ + *temp = (s16)val * 100; + + return 0; +} + +static int +__zl3073x_dpll_lock_status_get(struct zl3073x_dpll *zldpll, + enum dpll_lock_status *status) +{ + const struct zl3073x_chan *chan; + + lockdep_assert_held(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + + switch (zl3073x_chan_mode_get(chan)) { + case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: + case ZL_DPLL_MODE_REFSEL_MODE_NCO: + /* In FREERUN and NCO modes the DPLL is always unlocked */ + *status = DPLL_LOCK_STATUS_UNLOCKED; + + return 0; + default: + break; + } + + switch (zl3073x_chan_lock_state_get(chan)) { + case ZL_DPLL_MON_STATUS_STATE_LOCK: + if (zl3073x_chan_is_ho_ready(chan)) + *status = DPLL_LOCK_STATUS_LOCKED_HO_ACQ; + else + *status = DPLL_LOCK_STATUS_LOCKED; + break; + case ZL_DPLL_MON_STATUS_STATE_HOLDOVER: + case ZL_DPLL_MON_STATUS_STATE_ACQUIRING: + *status = DPLL_LOCK_STATUS_HOLDOVER; + break; + default: + dev_warn(zldpll->dev->dev, + "Unknown DPLL monitor status: 0x%02x\n", + chan->mon_status); + *status = DPLL_LOCK_STATUS_UNLOCKED; + break; + } + + return 0; +} + +static int +zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, + enum dpll_lock_status *status, + enum dpll_lock_status_error *status_error, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + guard(mutex)(&zldpll->lock); + + return __zl3073x_dpll_lock_status_get(zldpll, status); +} + +static int +zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll, + void *dpll_priv, unsigned long *modes, + struct netlink_ext_ack *extack) +{ + __set_bit(DPLL_MODE_AUTOMATIC, modes); + __set_bit(DPLL_MODE_MANUAL, modes); + + return 0; +} + +static int +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; + + guard(mutex)(&zldpll->lock); + + 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: + case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: + /* Use MANUAL for device FREERUN, HOLDOVER, NCO and + * REFLOCK modes + */ + *mode = DPLL_MODE_MANUAL; + break; + case ZL_DPLL_MODE_REFSEL_MODE_AUTO: + /* Use AUTO for device AUTO mode */ + *mode = DPLL_MODE_AUTOMATIC; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int +zl3073x_dpll_phase_offset_avg_factor_get(const struct dpll_device *dpll, + void *dpll_priv, u32 *factor, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + *factor = zl3073x_dev_phase_avg_factor_get(zldpll->dev); + + return 0; +} + +static int +zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll, + void *dpll_priv, u32 factor, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *item, *zldpll = dpll_priv; + int rc; + + if (factor > 15) { + NL_SET_ERR_MSG_FMT(extack, + "Phase offset average factor has to be from range <0,15>"); + return -EINVAL; + } + + rc = zl3073x_dev_phase_avg_factor_set(zldpll->dev, factor); + if (rc) { + NL_SET_ERR_MSG_FMT(extack, + "Failed to set phase offset averaging factor"); + return rc; + } + + /* The averaging factor is common for all DPLL channels so after + * change we have to send a notification for other DPLL devices. + */ + list_for_each_entry(item, &zldpll->dev->dplls, list) { + struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev); + + if (item != zldpll && dpll_dev) + __dpll_device_change_ntf(dpll_dev); + } + + return 0; +} + +static int +zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, + enum dpll_mode mode, struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dpll_pin *nco_pin = NULL; + struct zl3073x_chan chan; + u8 hw_mode, ref; + int rc; + + mutex_lock(&zldpll->lock); + + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + ref = zl3073x_chan_refsel_ref_get(&chan); + + if (mode == DPLL_MODE_MANUAL) { + /* We are switching from automatic to manual mode: + * - if we have a valid reference selected during auto mode then + * we will switch to forced reference lock mode and use this + * reference for selection + * - if NO valid reference is selected, we will switch to forced + * holdover mode or freerun mode, depending on the current + * lock status + */ + if (ZL3073X_DPLL_REF_IS_VALID(ref)) + hw_mode = ZL_DPLL_MODE_REFSEL_MODE_REFLOCK; + else if (zldpll->lock_status == DPLL_LOCK_STATUS_UNLOCKED) + hw_mode = ZL_DPLL_MODE_REFSEL_MODE_FREERUN; + else + hw_mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER; + } else { + if (zl3073x_chan_mode_is_nco(&chan)) + nco_pin = zl3073x_dpll_nco_pin_get(zldpll); + + /* We are switching from manual to automatic mode: + * - if there is a valid reference selected then ensure that + * it is selectable after switch to automatic mode + * - switch to automatic mode + */ + 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); + } + } + + hw_mode = ZL_DPLL_MODE_REFSEL_MODE_AUTO; + } + + zl3073x_chan_mode_set(&chan, hw_mode); + if (ZL3073X_DPLL_REF_IS_VALID(ref)) + zl3073x_chan_ref_set(&chan, ref); + + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + if (rc) { + NL_SET_ERR_MSG_MOD(extack, + "failed to set reference selection mode"); + mutex_unlock(&zldpll->lock); + return rc; + } + + mutex_unlock(&zldpll->lock); + + /* If leaving NCO mode, notify userspace about the NCO pin + * state change - the periodic worker skips the NCO pin. + */ + if (nco_pin) + __dpll_pin_change_ntf(nco_pin->dpll_pin); + + return 0; +} + +static int +zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_feature_state *state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + guard(mutex)(&zldpll->lock); + + if (zldpll->phase_monitor) + *state = DPLL_FEATURE_STATE_ENABLE; + else + *state = DPLL_FEATURE_STATE_DISABLE; + + return 0; +} + +static int +zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_feature_state state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + guard(mutex)(&zldpll->lock); + + zldpll->phase_monitor = (state == DPLL_FEATURE_STATE_ENABLE); + + return 0; +} + +static int +zl3073x_dpll_freq_monitor_get(const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_feature_state *state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + if (READ_ONCE(zldpll->dev->freq_monitor)) + *state = DPLL_FEATURE_STATE_ENABLE; + else + *state = DPLL_FEATURE_STATE_DISABLE; + + return 0; +} + +static int +zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_feature_state state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *item, *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + + WRITE_ONCE(zldev->freq_monitor, state == DPLL_FEATURE_STATE_ENABLE); + + /* The frequency monitoring is common for all DPLL channels so after + * change we have to send a notification for other DPLL devices. + */ + list_for_each_entry(item, &zldev->dplls, list) { + struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev); + + if (item != zldpll && dpll_dev) + __dpll_device_change_ntf(dpll_dev); + } + + return 0; +} + +static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = { + .supported_ffo = BIT(DPLL_FFO_PIN_DEVICE), + .direction_get = zl3073x_dpll_pin_direction_get, + .esync_get = zl3073x_dpll_input_pin_esync_get, + .esync_set = zl3073x_dpll_input_pin_esync_set, + .ffo_get = zl3073x_dpll_input_pin_ffo_get, + .frequency_get = zl3073x_dpll_input_pin_frequency_get, + .frequency_set = zl3073x_dpll_input_pin_frequency_set, + .measured_freq_get = zl3073x_dpll_input_pin_measured_freq_get, + .operstate_on_dpll_get = zl3073x_dpll_input_pin_operstate_on_dpll_get, + .phase_offset_get = zl3073x_dpll_input_pin_phase_offset_get, + .phase_adjust_get = zl3073x_dpll_input_pin_phase_adjust_get, + .phase_adjust_set = zl3073x_dpll_input_pin_phase_adjust_set, + .prio_get = zl3073x_dpll_input_pin_prio_get, + .prio_set = zl3073x_dpll_input_pin_prio_set, + .ref_sync_get = zl3073x_dpll_input_pin_ref_sync_get, + .ref_sync_set = zl3073x_dpll_input_pin_ref_sync_set, + .state_on_dpll_get = zl3073x_dpll_input_pin_state_on_dpll_get, + .state_on_dpll_set = zl3073x_dpll_input_pin_state_on_dpll_set, +}; + +static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = { + .direction_get = zl3073x_dpll_pin_direction_get, + .esync_get = zl3073x_dpll_output_pin_esync_get, + .esync_set = zl3073x_dpll_output_pin_esync_set, + .frequency_get = zl3073x_dpll_output_pin_frequency_get, + .frequency_set = zl3073x_dpll_output_pin_frequency_set, + .phase_adjust_get = zl3073x_dpll_output_pin_phase_adjust_get, + .phase_adjust_set = zl3073x_dpll_output_pin_phase_adjust_set, + .state_on_dpll_get = zl3073x_dpll_output_pin_state_on_dpll_get, +}; + +static const struct dpll_pin_ops zl3073x_dpll_nco_pin_ops = { + .supported_ffo = BIT(DPLL_FFO_PIN_DEVICE), + .direction_get = zl3073x_dpll_pin_direction_get, + .ffo_get = zl3073x_dpll_nco_pin_ffo_get, + .operstate_on_dpll_get = zl3073x_dpll_nco_pin_operstate_on_dpll_get, + .state_on_dpll_get = zl3073x_dpll_nco_pin_state_on_dpll_get, + .state_on_dpll_set = zl3073x_dpll_nco_pin_state_on_dpll_set, +}; + +static const struct dpll_device_ops zl3073x_dpll_device_ops = { + .lock_status_get = zl3073x_dpll_lock_status_get, + .mode_get = zl3073x_dpll_mode_get, + .mode_set = zl3073x_dpll_mode_set, + .phase_offset_avg_factor_get = zl3073x_dpll_phase_offset_avg_factor_get, + .phase_offset_avg_factor_set = zl3073x_dpll_phase_offset_avg_factor_set, + .phase_offset_monitor_get = zl3073x_dpll_phase_offset_monitor_get, + .phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set, + .freq_monitor_get = zl3073x_dpll_freq_monitor_get, + .freq_monitor_set = zl3073x_dpll_freq_monitor_set, + .supported_modes_get = zl3073x_dpll_supported_modes_get, +}; + +/** + * zl3073x_dpll_pin_alloc - allocate DPLL pin + * @zldpll: pointer to zl3073x_dpll + * @dir: pin direction + * @id: pin id + * + * Allocates and initializes zl3073x_dpll_pin structure for given + * pin id and direction. + * + * Return: pointer to allocated structure on success, error pointer on error + */ +static struct zl3073x_dpll_pin * +zl3073x_dpll_pin_alloc(struct zl3073x_dpll *zldpll, enum dpll_pin_direction dir, + u8 id) +{ + struct zl3073x_dpll_pin *pin; + + pin = kzalloc_obj(*pin); + if (!pin) + return ERR_PTR(-ENOMEM); + + pin->dpll = zldpll; + pin->dir = dir; + pin->id = id; + + return pin; +} + +/** + * zl3073x_dpll_pin_free - deallocate DPLL pin + * @pin: pin to free + * + * Deallocates DPLL pin previously allocated by @zl3073x_dpll_pin_alloc. + */ +static void +zl3073x_dpll_pin_free(struct zl3073x_dpll_pin *pin) +{ + WARN(pin->dpll_pin, "DPLL pin is still registered\n"); + + kfree(pin); +} + +/** + * zl3073x_dpll_pin_register - register DPLL pin + * @pin: pointer to DPLL pin + * @index: absolute pin index for registration + * + * Registers given DPLL pin into DPLL sub-system. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_pin_props *props; + const struct dpll_pin_ops *ops; + int rc; + + /* Get pin properties */ + props = zl3073x_pin_props_get(zldpll->dev, pin->dir, pin->id); + if (IS_ERR(props)) + return PTR_ERR(props); + + /* Save package label, fwnode, esync capability and phase adjust + * granularity. + */ + strscpy(pin->label, props->package_label); + pin->fwnode = fwnode_handle_get(props->fwnode); + pin->esync_control = props->esync_control; + pin->phase_gran = props->dpll_props.phase_gran; + + if (zl3073x_dpll_is_input_pin(pin)) { + 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 */ + pin->prio = ZL_DPLL_REF_PRIO_MAX; + } + + /* Create or get existing DPLL pin */ + pin->dpll_pin = dpll_pin_get(zldpll->dev->clock_id, index, THIS_MODULE, + &props->dpll_props, &pin->tracker); + if (IS_ERR(pin->dpll_pin)) { + rc = PTR_ERR(pin->dpll_pin); + goto err_pin_get; + } + dpll_pin_fwnode_set(pin->dpll_pin, props->fwnode); + + if (zl3073x_dpll_is_input_pin(pin)) + ops = &zl3073x_dpll_input_pin_ops; + else + ops = &zl3073x_dpll_output_pin_ops; + + /* Register the pin */ + rc = dpll_pin_register(zldpll->dpll_dev, pin->dpll_pin, ops, pin); + if (rc) + goto err_register; + + /* Free pin properties */ + zl3073x_pin_props_put(props); + + return 0; + +err_register: + dpll_pin_put(pin->dpll_pin, &pin->tracker); +err_pin_get: + pin->dpll_pin = NULL; + fwnode_handle_put(pin->fwnode); + pin->fwnode = NULL; + zl3073x_pin_props_put(props); + + return rc; +} + +/** + * zl3073x_dpll_pin_unregister - unregister DPLL pin + * @pin: pointer to DPLL pin + * + * Unregisters pin previously registered by @zl3073x_dpll_pin_register. + */ +static void +zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + const struct dpll_pin_ops *ops; + + WARN(!pin->dpll_pin, "DPLL pin is not registered\n"); + + if (zl3073x_dpll_is_nco_pin(pin)) + ops = &zl3073x_dpll_nco_pin_ops; + else if (zl3073x_dpll_is_input_pin(pin)) + ops = &zl3073x_dpll_input_pin_ops; + else + ops = &zl3073x_dpll_output_pin_ops; + + /* Unregister the pin */ + dpll_pin_unregister(zldpll->dpll_dev, pin->dpll_pin, ops, pin); + + dpll_pin_put(pin->dpll_pin, &pin->tracker); + pin->dpll_pin = NULL; + + fwnode_handle_put(pin->fwnode); + pin->fwnode = NULL; +} + +/** + * zl3073x_dpll_pins_unregister - unregister all registered DPLL pins + * @zldpll: pointer to zl3073x_dpll structure + * + * Enumerates all DPLL pins registered to given DPLL device and + * unregisters them. + */ +static void +zl3073x_dpll_pins_unregister(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin, *next; + + list_for_each_entry_safe(pin, next, &zldpll->pins, list) { + zl3073x_dpll_pin_unregister(pin); + list_del(&pin->list); + zl3073x_dpll_pin_free(pin); + } +} + +/** + * zl3073x_dpll_pin_is_registrable - check if the pin is registrable + * @zldpll: pointer to zl3073x_dpll structure + * @dir: pin direction + * @index: pin index + * + * Checks if the given pin can be registered to given DPLL. For both + * directions the pin can be registered if it is enabled. In case of + * differential signal type only P-pin is reported as registrable. + * And additionally for the output pin, the pin can be registered only + * if it is connected to synthesizer that is driven by given DPLL. + * + * Return: true if the pin is registrable, false if not + */ +static bool +zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, + enum dpll_pin_direction dir, u8 index) +{ + struct zl3073x_dev *zldev = zldpll->dev; + bool is_diff, is_enabled; + const char *name; + + if (dir == DPLL_PIN_DIRECTION_INPUT) { + u8 ref_id = zl3073x_input_pin_ref_get(index); + const struct zl3073x_ref *ref; + + name = "REF"; + ref = zl3073x_ref_state_get(zldev, ref_id); + is_diff = zl3073x_ref_is_diff(ref); + is_enabled = zl3073x_ref_is_enabled(ref); + } else { + /* Output P&N pair shares single HW output */ + u8 out = zl3073x_output_pin_out_get(index); + + /* Skip the pin if it is connected to different DPLL channel */ + if (zl3073x_dev_out_dpll_get(zldev, out) != zldpll->id) { + dev_dbg(zldev->dev, + "OUT%u is driven by different DPLL\n", out); + + return false; + } + + name = "OUT"; + is_diff = zl3073x_dev_out_is_diff(zldev, out); + is_enabled = zl3073x_dev_output_pin_is_enabled(zldev, index); + } + + /* Skip N-pin if the corresponding input/output is differential */ + if (is_diff && zl3073x_is_n_pin(index)) { + dev_dbg(zldev->dev, "%s%u is differential, skipping N-pin\n", + name, index / 2); + + return false; + } + + /* Skip the pin if it is disabled */ + if (!is_enabled) { + dev_dbg(zldev->dev, "%s%u%c is disabled\n", name, index / 2, + zl3073x_is_p_pin(index) ? 'P' : 'N'); + + return false; + } + + return true; +} + +static const struct dpll_pin_properties zl3073x_dpll_nco_pin_props = { + .type = DPLL_PIN_TYPE_INT_NCO, + .package_label = "NCO", + .capabilities = DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE, +}; + +static int +zl3073x_dpll_nco_pin_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + struct zl3073x_chan chan; + int rc; + + /* Ensure that ctrl bits are configured for NCO operation: + * - nco_auto_read: auto-capture tracking offset on NCO entry + * - tod_step_reset: apply negated ToD step on NCO exit + * - tie_clear: PPS DPLLs re-align outputs on NCO exit + */ + mutex_lock(&zldpll->lock); + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + FIELD_MODIFY(ZL_DPLL_CTRL_NCO_AUTO_READ, &chan.ctrl, 1); + FIELD_MODIFY(ZL_DPLL_CTRL_TOD_STEP_RST, &chan.ctrl, 1); + FIELD_MODIFY(ZL_DPLL_CTRL_TIE_CLEAR, &chan.ctrl, + zldpll->type == DPLL_TYPE_PPS ? 1 : 0); + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + mutex_unlock(&zldpll->lock); + if (rc) + return rc; + + pin = zl3073x_dpll_pin_alloc(zldpll, DPLL_PIN_DIRECTION_INPUT, + ZL3073X_NCO_PIN_ID); + if (IS_ERR(pin)) + return PTR_ERR(pin); + + pin->dpll_pin = dpll_pin_get(zldpll->dev->clock_id, ZL3073X_NCO_PIN_ID, + THIS_MODULE, &zl3073x_dpll_nco_pin_props, + &pin->tracker); + if (IS_ERR(pin->dpll_pin)) { + rc = PTR_ERR(pin->dpll_pin); + goto err_pin_get; + } + + rc = dpll_pin_register(zldpll->dpll_dev, pin->dpll_pin, + &zl3073x_dpll_nco_pin_ops, pin); + if (rc) + goto err_register; + + list_add(&pin->list, &zldpll->pins); + + return 0; + +err_register: + dpll_pin_put(pin->dpll_pin, &pin->tracker); +err_pin_get: + pin->dpll_pin = NULL; + kfree(pin); + + return rc; +} + +/** + * zl3073x_dpll_pins_register - register all registerable DPLL pins + * @zldpll: pointer to zl3073x_dpll structure + * + * Enumerates all possible input/output pins and registers all of them + * that are registrable. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + enum dpll_pin_direction dir; + u8 id, index; + int rc; + + /* Process input pins */ + for (index = 0; index < ZL3073X_NUM_PINS; index++) { + /* First input pins and then output pins */ + if (index < ZL3073X_NUM_INPUT_PINS) { + id = index; + dir = DPLL_PIN_DIRECTION_INPUT; + } else { + id = index - ZL3073X_NUM_INPUT_PINS; + dir = DPLL_PIN_DIRECTION_OUTPUT; + } + + /* Check if the pin registrable to this DPLL */ + if (!zl3073x_dpll_pin_is_registrable(zldpll, dir, id)) + continue; + + pin = zl3073x_dpll_pin_alloc(zldpll, dir, id); + if (IS_ERR(pin)) { + rc = PTR_ERR(pin); + goto error; + } + + rc = zl3073x_dpll_pin_register(pin, index); + if (rc) { + zl3073x_dpll_pin_free(pin); + goto error; + } + + list_add(&pin->list, &zldpll->pins); + } + + /* Register NCO virtual input pin */ + rc = zl3073x_dpll_nco_pin_register(zldpll); + if (rc) + goto error; + + return 0; + +error: + zl3073x_dpll_pins_unregister(zldpll); + + return rc; +} + +/** + * zl3073x_dpll_device_register - register DPLL device + * @zldpll: pointer to zl3073x_dpll structure + * + * Registers given DPLL device into DPLL sub-system. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dev *zldev = zldpll->dev; + int rc; + + zldpll->ops = zl3073x_dpll_device_ops; + if (zldev->info->flags & ZL3073X_FLAG_DIE_TEMP) + zldpll->ops.temp_get = zl3073x_dpll_temp_get; + + zldpll->dpll_dev = dpll_device_get(zldev->clock_id, zldpll->id, + THIS_MODULE, &zldpll->tracker); + if (IS_ERR(zldpll->dpll_dev)) { + rc = PTR_ERR(zldpll->dpll_dev); + zldpll->dpll_dev = NULL; + + return rc; + } + + zldpll->type = zl3073x_prop_dpll_type_get(zldev, zldpll->id); + rc = dpll_device_register(zldpll->dpll_dev, zldpll->type, + &zldpll->ops, zldpll); + if (rc) { + dpll_device_put(zldpll->dpll_dev, &zldpll->tracker); + zldpll->dpll_dev = NULL; + } + + return rc; +} + +/** + * zl3073x_dpll_device_unregister - unregister DPLL device + * @zldpll: pointer to zl3073x_dpll structure + * + * Unregisters given DPLL device from DPLL sub-system previously registered + * by @zl3073x_dpll_device_register. + */ +static void +zl3073x_dpll_device_unregister(struct zl3073x_dpll *zldpll) +{ + struct dpll_device *dpll_dev = READ_ONCE(zldpll->dpll_dev); + + WARN(!dpll_dev, "DPLL device is not registered\n"); + + WRITE_ONCE(zldpll->dpll_dev, NULL); + dpll_device_unregister(dpll_dev, &zldpll->ops, zldpll); + dpll_device_put(dpll_dev, &zldpll->tracker); +} + +/** + * zl3073x_dpll_pin_phase_offset_check - check for pin phase offset change + * @pin: pin to check + * + * Check for the change of DPLL to connected pin phase offset change. + * + * Return: true on phase offset change, false otherwise + */ +static bool +zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + unsigned int reg; + s64 phase_offset; + u8 ref_id; + int rc; + + lockdep_assert_held(&zldpll->lock); + + /* No phase offset if the ref monitor reports signal errors */ + ref_id = zl3073x_input_pin_ref_get(pin->id); + if (!zl3073x_dev_ref_is_status_ok(zldev, ref_id)) + return false; + + /* Select register to read phase offset value depending on pin and + * phase monitor state: + * 1) For connected pin use dpll_phase_err_data register + * 2) For other pins use appropriate ref_phase register if the phase + * monitor feature is enabled. + */ + if (pin->operstate == DPLL_PIN_OPERSTATE_ACTIVE) + reg = ZL_REG_DPLL_PHASE_ERR_DATA(zldpll->id); + else if (zldpll->phase_monitor) + reg = ZL_REG_REF_PHASE(ref_id); + else + return false; + + /* Read measured phase offset value */ + rc = zl3073x_read_u48(zldev, reg, &phase_offset); + if (rc) { + dev_err(zldev->dev, "Failed to read ref phase offset: %pe\n", + ERR_PTR(rc)); + + return false; + } + + /* Convert to ps */ + phase_offset = div_s64(sign_extend64(phase_offset, 47), 100); + + /* Compare with previous value */ + if (phase_offset != pin->phase_offset) { + dev_dbg(zldev->dev, "%s phase offset changed: %lld -> %lld\n", + pin->label, pin->phase_offset, phase_offset); + pin->phase_offset = phase_offset; + + return true; + } + + return false; +} + +/** + * zl3073x_dpll_pin_ffo_check - check for FFO change on active pin + * @pin: pin to check + * + * Return: true on change, false otherwise + */ +static bool +zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_chan *chan; + s64 ffo; + + lockdep_assert_held(&zldpll->lock); + + if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE) + return false; + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (zl3073x_chan_df_offset_get(chan) == ZL_DPLL_DF_OFFSET_UNKNOWN) + return false; + + /* dpll_df_offset register has inverted sign per datasheet: + * f_offset = f_nom * (-df_offset) / 2^48 + * Input pin FFO is pin-vs-DPLL (opposite of DPLL-vs-reference), + * so the two inversions cancel out: ppt = df * 5^12 / 2^36 + */ + ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan), + 244140625, 36); + + if (pin->freq_offset != ffo) { + dev_dbg(zldev->dev, "%s freq offset changed to: %lld\n", + pin->label, ffo); + pin->freq_offset = ffo; + return true; + } + + return false; +} + +/** + * zl3073x_dpll_pin_measured_freq_check - check for pin measured frequency + * change + * @pin: pin to check + * + * Check for the given pin's measured frequency change. + * + * Return: true on measured frequency change, false otherwise + */ +static bool +zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_ref *ref; + u8 ref_id; + u32 freq; + + lockdep_assert_held(&zldpll->lock); + + if (!READ_ONCE(zldpll->dev->freq_monitor)) + return false; + + ref_id = zl3073x_input_pin_ref_get(pin->id); + ref = zl3073x_ref_state_get(zldev, ref_id); + + freq = zl3073x_ref_meas_freq_get(ref); + if (pin->measured_freq != freq) { + dev_dbg(zldev->dev, "%s measured freq changed: %u -> %u\n", + pin->label, pin->measured_freq, freq); + pin->measured_freq = freq; + + return true; + } + + return false; +} + +/** + * zl3073x_dpll_changes_check - check for changes and send notifications + * @zldpll: pointer to zl3073x_dpll structure + * + * Checks for changes on given DPLL device and its registered DPLL pins + * and sends notifications about them. + * + * This function is periodically called from @zl3073x_dev_periodic_work. + */ +void +zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) +{ + DECLARE_BITMAP(changed_pins, ZL3073X_NUM_INPUT_PINS); + struct zl3073x_dev *zldev = zldpll->dev; + enum dpll_lock_status lock_status; + struct device *dev = zldev->dev; + struct zl3073x_dpll_pin *pin; + bool dev_changed = false; + int rc; + + bitmap_zero(changed_pins, ZL3073X_NUM_INPUT_PINS); + + mutex_lock(&zldpll->lock); + + zldpll->check_count++; + + rc = zl3073x_chan_state_update(zldev, zldpll->id); + if (rc) { + dev_err(dev, "Failed to get DPLL%u state: %pe\n", + zldpll->id, ERR_PTR(rc)); + goto unlock; + } + + rc = __zl3073x_dpll_lock_status_get(zldpll, &lock_status); + if (rc) { + dev_err(dev, "Failed to get DPLL%u lock status: %pe\n", + zldpll->id, ERR_PTR(rc)); + goto unlock; + } + + if (zldpll->lock_status != lock_status) { + zldpll->lock_status = lock_status; + dev_changed = true; + } + + /* Update phase offset latch registers for this DPLL if the phase + * offset monitor feature is enabled. + */ + if (zldpll->phase_monitor) { + rc = zl3073x_ref_phase_offsets_update(zldev, zldpll->id); + if (rc) { + dev_err(zldev->dev, + "Failed to update phase offsets: %pe\n", + ERR_PTR(rc)); + goto unlock; + } + } + + list_for_each_entry(pin, &zldpll->pins, list) { + enum dpll_pin_operstate operstate; + + /* Only physical input pins need monitoring */ + if (!zl3073x_dpll_is_input_pin(pin) || + zl3073x_dpll_is_nco_pin(pin)) + continue; + + rc = zl3073x_dpll_ref_operstate_get(pin, &operstate); + if (rc) { + dev_err(dev, + "Failed to get %s on DPLL%u oper state: %pe\n", + pin->label, zldpll->id, ERR_PTR(rc)); + goto unlock; + } + + if (operstate != pin->operstate) { + dev_dbg(dev, "%s oper state changed: %u->%u\n", + pin->label, pin->operstate, operstate); + pin->operstate = operstate; + set_bit(pin->id, changed_pins); + } + + if (zldpll->check_count % 2 == 0) { + if (zl3073x_dpll_pin_phase_offset_check(pin)) + set_bit(pin->id, changed_pins); + + if (zl3073x_dpll_pin_ffo_check(pin)) + set_bit(pin->id, changed_pins); + + if (zl3073x_dpll_pin_measured_freq_check(pin)) + set_bit(pin->id, changed_pins); + } + } + +unlock: + mutex_unlock(&zldpll->lock); + + /* Send notifications outside the lock to avoid ABBA deadlock + * with dpll_lock taken by notification functions. + */ + if (dev_changed) + dpll_device_change_ntf(zldpll->dpll_dev); + + list_for_each_entry(pin, &zldpll->pins, list) { + if (zl3073x_dpll_is_input_pin(pin) && + !zl3073x_dpll_is_nco_pin(pin) && + test_bit(pin->id, changed_pins)) + dpll_pin_change_ntf(pin->dpll_pin); + } +} + +/** + * zl3073x_dpll_init_fine_phase_adjust - do initial fine phase adjustments + * @zldev: pointer to zl3073x device + * + * Performs initial fine phase adjustments needed per datasheet. + * + * Return: 0 on success, <0 on error + */ +int +zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev) +{ + int rc; + + rc = zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_MASK, 0x1f); + if (rc) + return rc; + + rc = zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_INTVL, 0x01); + if (rc) + return rc; + + rc = zl3073x_write_u16(zldev, ZL_REG_SYNTH_PHASE_SHIFT_DATA, 0xffff); + if (rc) + return rc; + + return zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_CTRL, 0x01); +} + +/* Maximum frequency adjustment: +-1% of nominal in ppb */ +#define ZL3073X_DPLL_PTP_MAX_ADJ 10000000 + +/** + * zl3073x_dpll_ptp_gettimex64 - read current time from ToD counters + * @info: PTP clock info + * @ts: timespec to store current time + * @sts: optional system timestamp pair for cross-timestamping + * + * Return: 0 on success, <0 on error + */ +static int zl3073x_dpll_ptp_gettimex64(struct ptp_clock_info *info, + struct timespec64 *ts, + struct ptp_system_timestamp *sts) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + + guard(mutex)(&zldpll->lock); + + return zl3073x_chan_tod_read(zldpll->dev, zldpll->id, false, ts, sts); +} + +/** + * zl3073x_dpll_ptp_settime64 - set ToD counters to given time + * @info: PTP clock info + * @ts: timespec with time to set + * + * Return: 0 on success, <0 on error + */ +static int zl3073x_dpll_ptp_settime64(struct ptp_clock_info *info, + const struct timespec64 *ts) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + + guard(mutex)(&zldpll->lock); + + return zl3073x_chan_tod_write(zldpll->dev, zldpll->id, *ts); +} + +/** + * zl3073x_dpll_ptp_adjtime_phase_step - adjust sub-second time via phase step + * @zldpll: DPLL channel + * @delta: time adjustment in nanoseconds (must be within (-NSEC_PER_SEC, + * NSEC_PER_SEC)) + * + * Uses the output phase step mechanism with tod_step=1 to adjust both + * the output clock phase and the ToD counter simultaneously. This keeps + * outputs and ToD coherent. Only valid for NCO. + * + * Outputs are grouped by synthesizer since the phase step value is in + * synthesizer clock cycles. The first synth group with enabled outputs + * uses tod_step to adjust both outputs and the ToD counter. Remaining + * groups step outputs only. If no synth has enabled outputs, the ToD + * counter is stepped alone using an empty output mask (the FW uses + * the first enabled synth's period for the conversion). + * + * Return: + * * %0 - success (or partial success if a later synth group + * failed after the first was already stepped) + * * %-EOPNOTSUPP - no synths available + * * negative - error + */ +static int zl3073x_dpll_ptp_adjtime_phase_step(struct zl3073x_dpll *zldpll, + s64 delta) +{ + u16 synth_mask[ZL3073X_NUM_SYNTHS] = {}; + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_synth *synth; + struct zl3073x_dpll_pin *pin; + u32 first_synth_freq = 0; + bool tod_stepped = false; + s32 step_cycles; + u32 synth_freq; + int rc; + u8 i; + + /* Build per-synth output masks from registered output pins */ + list_for_each_entry(pin, &zldpll->pins, list) { + u8 out_id, synth_id; + + if (zl3073x_dpll_is_input_pin(pin)) + continue; + + out_id = zl3073x_output_pin_out_get(pin->id); + + if (!zl3073x_dev_out_is_stepped(zldev, out_id)) + continue; + + synth_id = zl3073x_dev_out_synth_get(zldev, out_id); + if (synth_id >= ZL3073X_NUM_SYNTHS) { + dev_warn(zldev->dev, "Unexpected synth id for OUT%u\n", + out_id); + continue; + } + synth_mask[synth_id] |= BIT(out_id); + } + + /* Process each synth group */ + for (i = 0; i < ZL3073X_NUM_SYNTHS; i++) { + synth = zl3073x_synth_state_get(zldev, i); + if (!zl3073x_synth_is_enabled(synth) || + zl3073x_synth_dpll_get(synth) != zldpll->id) + continue; + + synth_freq = zl3073x_synth_freq_get(synth); + + /* Remember first enabled synth freq for ToD-only fallback */ + if (!first_synth_freq) + first_synth_freq = synth_freq; + + if (!synth_mask[i]) + continue; + + /* Safe for s32: max synth freq is 750 MHz */ + step_cycles = div_s64(delta * synth_freq, NSEC_PER_SEC); + + rc = zl3073x_chan_phase_step(zldev, zldpll->id, + synth_mask[i], step_cycles, + !tod_stepped); + if (rc) { + if (tod_stepped) { + dev_warn(zldev->dev, + "Partial phase step failure\n"); + return 0; + } + return rc; + } + tod_stepped = true; + } + + if (!first_synth_freq) + return -EOPNOTSUPP; + + /* No enabled outputs found; step ToD counter only using the + * first enabled synth's period (empty output mask). + */ + if (!tod_stepped) { + step_cycles = div_s64(delta * first_synth_freq, NSEC_PER_SEC); + return zl3073x_chan_phase_step(zldev, zldpll->id, 0, + step_cycles, true); + } + + return 0; +} + +/** + * zl3073x_dpll_ptp_adjtime - adjust PTP clock time + * @info: PTP clock info + * @delta: time adjustment in nanoseconds + * + * For NCO, large deltas (>= 1 second) are split into a ToD + * read-modify-write for the seconds part and an output phase step for + * the sub-second remainder. Sub-second deltas use phase step directly, + * falling back to ToD read-modify-write if phase step or TIE write + * fails. In AUTO/REFLOCK modes, large deltas are split into ToD + * read-modify-write for seconds and TIE write for the sub-second + * remainder. Sub-second deltas use TIE write directly. + * + * If the seconds part was already committed when the sub-second + * mechanism fails, returns 0 to prevent the PTP servo from retrying + * the full delta and applying seconds again. + * + * Return: 0 on success (or partial success), <0 on error + */ +static int zl3073x_dpll_ptp_adjtime(struct ptp_clock_info *info, s64 delta) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_chan *chan; + bool sec_adjusted = false; + struct timespec64 ts; + int rc; + + if (!delta) + return 0; + + guard(mutex)(&zldpll->lock); + + /* Modes without phase step or TIE use plain ToD adjust */ + chan = zl3073x_chan_state_get(zldev, zldpll->id); + if (!zl3073x_chan_mode_is_nco(chan) && + !zl3073x_chan_mode_supports_tie(chan)) + return zl3073x_chan_tod_adjust(zldev, zldpll->id, + ns_to_timespec64(delta)); + + /* Split off seconds via ToD read-modify-write so the sub-second + * remainder can be applied through the output-coherent mechanism + * (phase step or TIE write). + */ + if (delta >= NSEC_PER_SEC || delta <= -NSEC_PER_SEC) { + s32 remainder; + + ts.tv_sec = div_s64_rem(delta, NSEC_PER_SEC, &remainder); + ts.tv_nsec = 0; + delta = remainder; + + rc = zl3073x_chan_tod_adjust(zldev, zldpll->id, ts); + if (rc) + return rc; + + /* No sub-second remainder, done */ + if (!delta) + return 0; + + /* Wait for the ToD write to be applied at the 1 Hz edge + * before issuing phase step or TIE write, so the pending + * WR_NEXT_1HZ does not overwrite the sub-second adjustment. + */ + rc = zl3073x_chan_tod_ready_wait(zldev, zldpll->id); + if (rc) + return rc; + + sec_adjusted = true; + } + + /* Apply sub-second delta via phase step (NCO) or TIE write */ + if (zl3073x_chan_mode_is_nco(chan)) { + rc = zl3073x_dpll_ptp_adjtime_phase_step(zldpll, delta); + if (!rc) + return 0; + } else { + rc = zl3073x_chan_tie_write(zldev, zldpll->id, delta); + if (!rc) + return 0; + } + + /* Phase step or TIE write failed, fall back to ToD adjust */ + rc = zl3073x_chan_tod_adjust(zldev, zldpll->id, + ns_to_timespec64(delta)); + + /* In the unlikely event that both phase step/TIE write and fallback + * ToD adjust fail after seconds were already committed, return + * success to prevent the PTP servo from retrying the full delta and + * applying seconds again. The sub-second residual will self-correct + * in the next servo cycle. + */ + if (rc && sec_adjusted) { + dev_warn(zldev->dev, + "Sub-second adjustment failed after seconds applied\n"); + return 0; + } + + return rc; +} + +/** + * zl3073x_dpll_ptp_adjfine - adjust PTP clock frequency + * @info: PTP clock info + * @scaled_ppm: frequency adjustment in scaled ppm (ppm * 2^16) + * + * Only supported for NCO. Writes the delta frequency offset register. + * + * Return: + * * %0 - success or @scaled_ppm is zero (no-op) + * * %-EOPNOTSUPP - NCO pin is not connected and @scaled_ppm is non-zero + * * negative - other error + */ +static int +zl3073x_dpll_ptp_adjfine(struct ptp_clock_info *info, long scaled_ppm) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + const struct zl3073x_chan *chan; + s64 offset; + + /* Convert scaled_ppm to df_offset in 2^-48 steps: + * df_offset = -(scaled_ppm * 2^32) / 10^6 + * + * Simplify to avoid overflow: + * df_offset = -(scaled_ppm * 2^26) / 5^6 + * df_offset = -(scaled_ppm * 67108864) / 15625 + */ + offset = -div_s64((s64)scaled_ppm * 67108864LL, 15625); + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + if (!zl3073x_chan_mode_is_nco(chan)) + return scaled_ppm ? -EOPNOTSUPP : 0; + if (offset == chan->df_offset) + return 0; + + return zl3073x_chan_df_offset_set(zldpll->dev, zldpll->id, offset); +} + +/** + * zl3073x_dpll_ptp_adjphase - adjust PTP clock phase + * @info: PTP clock info + * @delta: phase adjustment in nanoseconds + * + * Only supported in AUTO and REFLOCK modes. Uses TIE write for + * nanosecond resolution phase adjustment. + * + * Return: + * * %0 - success or @delta is zero (no-op) + * * %-EOPNOTSUPP - mode does not support TIE and @delta is non-zero + * * negative - other error + */ +static int zl3073x_dpll_ptp_adjphase(struct ptp_clock_info *info, s32 delta) +{ + struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll, + ptp_info); + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_chan *chan; + + if (!delta) + return 0; + + guard(mutex)(&zldpll->lock); + + chan = zl3073x_chan_state_get(zldev, zldpll->id); + + if (!zl3073x_chan_mode_supports_tie(chan)) + return -EOPNOTSUPP; + + return zl3073x_chan_tie_write(zldev, zldpll->id, delta); +} + +static s32 +zl3073x_dpll_ptp_getmaxphase(struct ptp_clock_info *info __always_unused) +{ + /* HW limits TIE write to +-1 second. Return the constant HW + * limit and let adjphase handle mode-specific checks. + */ + return NSEC_PER_SEC - 1; +} + +static const struct ptp_clock_info zl3073x_dpll_ptp_clock_info = { + .owner = THIS_MODULE, + .max_adj = ZL3073X_DPLL_PTP_MAX_ADJ, + .gettimex64 = zl3073x_dpll_ptp_gettimex64, + .settime64 = zl3073x_dpll_ptp_settime64, + .adjtime = zl3073x_dpll_ptp_adjtime, + .adjfine = zl3073x_dpll_ptp_adjfine, + .adjphase = zl3073x_dpll_ptp_adjphase, + .getmaxphase = zl3073x_dpll_ptp_getmaxphase, +}; + +/** + * zl3073x_dpll_ptp_register - register PTP clock for a DPLL channel + * @zldpll: DPLL channel to register PTP clock for + * + * Return: 0 on success, <0 on error + */ +static int zl3073x_dpll_ptp_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dev *zldev = zldpll->dev; + struct ptp_clock *ptp_clock; + + zldpll->ptp_info = zl3073x_dpll_ptp_clock_info; + snprintf(zldpll->ptp_info.name, sizeof(zldpll->ptp_info.name), + "%s-dpll%u", dev_name(zldev->dev), zldpll->id); + + ptp_clock = ptp_clock_register(&zldpll->ptp_info, zldev->dev); + if (IS_ERR(ptp_clock)) { + dev_err(zldev->dev, "Failed to register PTP clock for DPLL%u\n", + zldpll->id); + return PTR_ERR(ptp_clock); + } + + zldpll->ptp_clock = ptp_clock; + + return 0; +} + +/** + * zl3073x_dpll_ptp_unregister - unregister PTP clock for a DPLL channel + * @zldpll: DPLL channel to unregister PTP clock for + */ +static void zl3073x_dpll_ptp_unregister(struct zl3073x_dpll *zldpll) +{ + if (!IS_ERR_OR_NULL(zldpll->ptp_clock)) { + ptp_clock_unregister(zldpll->ptp_clock); + zldpll->ptp_clock = NULL; + } +} + +/** + * zl3073x_dpll_ref_sync_pair_register - register ref_sync pairs for a pin + * @pin: pointer to zl3073x_dpll_pin structure + * + * Iterates 'ref-sync-sources' phandles in the pin's firmware node and + * registers each declared pairing. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_ref_sync_pair_register(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dev *zldev = pin->dpll->dev; + struct fwnode_handle *fwnode; + struct dpll_pin *sync_pin; + dpll_tracker tracker; + int n, rc; + + for (n = 0; ; n++) { + /* Get n'th ref-sync source */ + fwnode = fwnode_find_reference(pin->fwnode, "ref-sync-sources", + n); + if (IS_ERR(fwnode)) { + rc = PTR_ERR(fwnode); + break; + } + + /* Find associated dpll pin */ + sync_pin = fwnode_dpll_pin_find(fwnode, &tracker); + fwnode_handle_put(fwnode); + if (!sync_pin) { + dev_warn(zldev->dev, "%s: ref-sync source %d not found", + pin->label, n); + continue; + } + + /* Register new ref-sync pair */ + rc = dpll_pin_ref_sync_pair_add(pin->dpll_pin, sync_pin); + dpll_pin_put(sync_pin, &tracker); + + /* -EBUSY means pairing already exists from another DPLL's + * registration. + */ + if (rc && rc != -EBUSY) { + dev_err(zldev->dev, + "%s: failed to add ref-sync source %d: %pe", + pin->label, n, ERR_PTR(rc)); + break; + } + } + + return rc != -ENOENT ? rc : 0; +} + +/** + * zl3073x_dpll_ref_sync_pairs_register - register ref_sync pairs for a DPLL + * @zldpll: pointer to zl3073x_dpll structure + * + * Iterates all registered input pins of the given DPLL and establishes + * ref_sync pairings declared by 'ref-sync-sources' phandles in the + * device tree. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + int rc; + + list_for_each_entry(pin, &zldpll->pins, list) { + if (!zl3073x_dpll_is_input_pin(pin) || !pin->fwnode) + continue; + + rc = zl3073x_dpll_ref_sync_pair_register(pin); + if (rc) + return rc; + } + + return 0; +} + +/** + * zl3073x_dpll_alloc - allocate DPLL device + * @zldev: pointer to zl3073x device + * @ch: DPLL channel number + * + * Allocates DPLL device structure for given DPLL channel. + * + * Return: pointer to DPLL device on success, error pointer on error + */ +struct zl3073x_dpll * +zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch) +{ + struct zl3073x_dpll *zldpll; + + zldpll = kzalloc_obj(*zldpll); + if (!zldpll) + return ERR_PTR(-ENOMEM); + + zldpll->dev = zldev; + zldpll->id = ch; + mutex_init(&zldpll->lock); + INIT_LIST_HEAD(&zldpll->pins); + + return zldpll; +} + +/** + * zl3073x_dpll_free - free DPLL device + * @zldpll: pointer to zl3073x_dpll structure + * + * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc. + */ +void +zl3073x_dpll_free(struct zl3073x_dpll *zldpll) +{ + WARN(zldpll->dpll_dev, "DPLL device is still registered\n"); + + mutex_destroy(&zldpll->lock); + kfree(zldpll); +} + +/** + * zl3073x_dpll_register - register DPLL device and all its pins + * @zldpll: pointer to zl3073x_dpll structure + * + * Registers given DPLL device and all its pins into DPLL sub-system. + * + * Return: 0 on success, <0 on error + */ +int +zl3073x_dpll_register(struct zl3073x_dpll *zldpll) +{ + int rc; + + rc = zl3073x_dpll_device_register(zldpll); + if (rc) + return rc; + + rc = zl3073x_dpll_pins_register(zldpll); + if (rc) { + zl3073x_dpll_device_unregister(zldpll); + return rc; + } + + rc = zl3073x_dpll_ref_sync_pairs_register(zldpll); + if (rc) { + zl3073x_dpll_pins_unregister(zldpll); + zl3073x_dpll_device_unregister(zldpll); + return rc; + } + + rc = zl3073x_dpll_ptp_register(zldpll); + if (rc) { + zl3073x_dpll_pins_unregister(zldpll); + zl3073x_dpll_device_unregister(zldpll); + return rc; + } + + return 0; +} + +/** + * zl3073x_dpll_unregister - unregister DPLL device and its pins + * @zldpll: pointer to zl3073x_dpll structure + * + * Unregisters given DPLL device and all its pins from DPLL sub-system + * previously registered by @zl3073x_dpll_register. + */ +void +zl3073x_dpll_unregister(struct zl3073x_dpll *zldpll) +{ + zl3073x_dpll_ptp_unregister(zldpll); + zl3073x_dpll_pins_unregister(zldpll); + zl3073x_dpll_device_unregister(zldpll); +} diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h new file mode 100644 index 000000000..993221dc6 --- /dev/null +++ b/drivers/dpll/zl3073x/dpll.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_DPLL_H +#define _ZL3073X_DPLL_H + +#include +#include +#include + +#include "core.h" + +/** + * struct zl3073x_dpll - ZL3073x DPLL sub-device structure + * @list: this DPLL list entry + * @dev: pointer to multi-function parent device + * @id: DPLL index + * @check_count: periodic check counter + * @phase_monitor: is phase offset monitor enabled + * @ops: DPLL device operations for this instance + * @dpll_dev: pointer to registered DPLL device + * @tracker: tracking object for the acquired reference + * @lock: per-DPLL mutex serializing all operations + * @type: DPLL type (PPS or EEC) + * @lock_status: last saved DPLL lock status + * @pins: list of pins + * @ptp_info: PTP clock info + * @ptp_clock: registered PTP clock (or NULL) + */ +struct zl3073x_dpll { + struct list_head list; + struct zl3073x_dev *dev; + u8 id; + u8 check_count; + bool phase_monitor; + struct dpll_device_ops ops; + struct dpll_device *dpll_dev; + dpll_tracker tracker; + struct mutex lock; + enum dpll_type type; + enum dpll_lock_status lock_status; + struct list_head pins; + struct ptp_clock_info ptp_info; + struct ptp_clock *ptp_clock; +}; + +struct zl3073x_dpll *zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch); +void zl3073x_dpll_free(struct zl3073x_dpll *zldpll); + +int zl3073x_dpll_register(struct zl3073x_dpll *zldpll); +void zl3073x_dpll_unregister(struct zl3073x_dpll *zldpll); + +int zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev); +void zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll); + +#endif /* _ZL3073X_DPLL_H */ diff --git a/drivers/dpll/zl3073x/flash.c b/drivers/dpll/zl3073x/flash.c new file mode 100644 index 000000000..f85535c8a --- /dev/null +++ b/drivers/dpll/zl3073x/flash.c @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "devlink.h" +#include "flash.h" + +#define ZL_FLASH_ERR_PFX "FW update failed: " +#define ZL_FLASH_ERR_MSG(_extack, _msg, ...) \ + NL_SET_ERR_MSG_FMT_MOD((_extack), ZL_FLASH_ERR_PFX _msg, \ + ## __VA_ARGS__) + +/** + * zl3073x_flash_download - Download image block to device memory + * @zldev: zl3073x device structure + * @component: name of the component to be downloaded + * @addr: device memory target address + * @data: pointer to data to download + * @size: size of data to download + * @extack: netlink extack pointer to report errors + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_download(struct zl3073x_dev *zldev, const char *component, + u32 addr, const void *data, size_t size, + struct netlink_ext_ack *extack) +{ +#define ZL_CHECK_DELAY 5000 /* Check for interrupt each 5 seconds */ + unsigned long check_time; + const void *ptr, *end; + int rc = 0; + + dev_dbg(zldev->dev, "Downloading %zu bytes to device memory at 0x%0x\n", + size, addr); + + check_time = jiffies + msecs_to_jiffies(ZL_CHECK_DELAY); + + for (ptr = data, end = data + size; ptr < end; ptr += 4, addr += 4) { + /* Write current word to HW memory */ + rc = zl3073x_write_hwreg(zldev, addr, + get_unaligned((u32 *)ptr)); + if (rc) { + ZL_FLASH_ERR_MSG(extack, + "failed to write to memory at 0x%0x", + addr); + return rc; + } + + if (time_is_before_jiffies(check_time)) { + if (signal_pending(current)) { + ZL_FLASH_ERR_MSG(extack, + "Flashing interrupted"); + return -EINTR; + } + + check_time = jiffies + msecs_to_jiffies(ZL_CHECK_DELAY); + } + + /* Report status each 1 kB block */ + if ((ptr - data) % 1024 == 0) + zl3073x_devlink_flash_notify(zldev, "Downloading image", + component, ptr - data, + size); + } + + zl3073x_devlink_flash_notify(zldev, "Downloading image", component, + ptr - data, size); + + dev_dbg(zldev->dev, "%zu bytes downloaded to device memory\n", size); + + return rc; +} + +/** + * zl3073x_flash_error_check - Check for flash utility errors + * @zldev: zl3073x device structure + * @extack: netlink extack pointer to report errors + * + * The function checks for errors detected by the flash utility and + * reports them if any were found. + * + * Return: 0 on success, -EIO when errors are detected + */ +static int +zl3073x_flash_error_check(struct zl3073x_dev *zldev, + struct netlink_ext_ack *extack) +{ + u32 count, cause; + int rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_ERROR_COUNT, &count); + if (rc) + return rc; + else if (!count) + return 0; /* No error */ + + rc = zl3073x_read_u32(zldev, ZL_REG_ERROR_CAUSE, &cause); + if (rc) + return rc; + + /* Report errors */ + ZL_FLASH_ERR_MSG(extack, + "utility error occurred: count=%u cause=0x%x", count, + cause); + + return -EIO; +} + +/** + * zl3073x_flash_wait_ready - Check or wait for utility to be ready to flash + * @zldev: zl3073x device structure + * @timeout_ms: timeout for the waiting + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_wait_ready(struct zl3073x_dev *zldev, unsigned int timeout_ms) +{ +#define ZL_FLASH_POLL_DELAY_MS 100 + unsigned long timeout; + int rc, i; + + dev_dbg(zldev->dev, "Waiting for flashing to be ready\n"); + + timeout = jiffies + msecs_to_jiffies(timeout_ms); + + for (i = 0; time_is_after_jiffies(timeout); i++) { + u8 value; + + /* Check for interrupt each 1s */ + if (i > 9) { + if (signal_pending(current)) + return -EINTR; + i = 0; + } + + rc = zl3073x_read_u8(zldev, ZL_REG_WRITE_FLASH, &value); + if (rc) + return rc; + + value = FIELD_GET(ZL_WRITE_FLASH_OP, value); + + if (value == ZL_WRITE_FLASH_OP_DONE) + return 0; /* Successfully done */ + + msleep(ZL_FLASH_POLL_DELAY_MS); + } + + return -ETIMEDOUT; +} + +/** + * zl3073x_flash_cmd_wait - Perform flash operation and wait for finish + * @zldev: zl3073x device structure + * @operation: operation to perform + * @extack: netlink extack pointer to report errors + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_cmd_wait(struct zl3073x_dev *zldev, u32 operation, + struct netlink_ext_ack *extack) +{ +#define ZL_FLASH_PHASE1_TIMEOUT_MS 60000 /* up to 1 minute */ +#define ZL_FLASH_PHASE2_TIMEOUT_MS 120000 /* up to 2 minutes */ + u8 value; + int rc; + + dev_dbg(zldev->dev, "Sending flash command: 0x%x\n", operation); + + rc = zl3073x_flash_wait_ready(zldev, ZL_FLASH_PHASE1_TIMEOUT_MS); + if (rc) + return rc; + + /* Issue the requested operation */ + rc = zl3073x_read_u8(zldev, ZL_REG_WRITE_FLASH, &value); + if (rc) + return rc; + + FIELD_MODIFY(ZL_WRITE_FLASH_OP, &value, operation); + + rc = zl3073x_write_u8(zldev, ZL_REG_WRITE_FLASH, value); + if (rc) + return rc; + + /* Wait for command completion */ + rc = zl3073x_flash_wait_ready(zldev, ZL_FLASH_PHASE2_TIMEOUT_MS); + if (rc) + return rc; + + return zl3073x_flash_error_check(zldev, extack); +} + +/** + * zl3073x_flash_get_sector_size - Get flash sector size + * @zldev: zl3073x device structure + * @sector_size: sector size returned by the function + * + * The function reads the flash sector size detected by flash utility and + * stores it into @sector_size. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_get_sector_size(struct zl3073x_dev *zldev, size_t *sector_size) +{ + u8 flash_info; + int rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_FLASH_INFO, &flash_info); + if (rc) + return rc; + + switch (FIELD_GET(ZL_FLASH_INFO_SECTOR_SIZE, flash_info)) { + case ZL_FLASH_INFO_SECTOR_4K: + *sector_size = SZ_4K; + break; + case ZL_FLASH_INFO_SECTOR_64K: + *sector_size = SZ_64K; + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +/** + * zl3073x_flash_block - Download and flash memory block + * @zldev: zl3073x device structure + * @component: component name + * @operation: flash operation to perform + * @page: destination flash page + * @addr: device memory address to load data + * @data: pointer to data to be flashed + * @size: size of data + * @extack: netlink extack pointer to report errors + * + * The function downloads the memory block given by the @data pointer and + * the size @size and flashes it into internal memory on flash page @page. + * The internal flash operation performed by the firmware is specified by + * the @operation parameter. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_block(struct zl3073x_dev *zldev, const char *component, + u32 operation, u32 page, u32 addr, const void *data, + size_t size, struct netlink_ext_ack *extack) +{ + int rc; + + /* Download block to device memory */ + rc = zl3073x_flash_download(zldev, component, addr, data, size, extack); + if (rc) + return rc; + + /* Set address to flash from */ + rc = zl3073x_write_u32(zldev, ZL_REG_IMAGE_START_ADDR, addr); + if (rc) + return rc; + + /* Set size of block to flash */ + rc = zl3073x_write_u32(zldev, ZL_REG_IMAGE_SIZE, size); + if (rc) + return rc; + + /* Set destination page to flash */ + rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_WRITE, page); + if (rc) + return rc; + + /* Set filling pattern */ + rc = zl3073x_write_u32(zldev, ZL_REG_FILL_PATTERN, U32_MAX); + if (rc) + return rc; + + zl3073x_devlink_flash_notify(zldev, "Flashing image", component, 0, + size); + + dev_dbg(zldev->dev, "Flashing %zu bytes to page %u\n", size, page); + + /* Execute sectors flash operation */ + rc = zl3073x_flash_cmd_wait(zldev, operation, extack); + if (rc) + return rc; + + zl3073x_devlink_flash_notify(zldev, "Flashing image", component, size, + size); + + return 0; +} + +/** + * zl3073x_flash_sectors - Flash sectors + * @zldev: zl3073x device structure + * @component: component name + * @page: destination flash page + * @addr: device memory address to load data + * @data: pointer to data to be flashed + * @size: size of data + * @extack: netlink extack pointer to report errors + * + * The function flashes given @data with size of @size to the internal flash + * memory block starting from page @page. The function uses sector flash + * method and has to take into account the flash sector size reported by + * flashing utility. Input data are spliced into blocks according this + * sector size and each block is flashed separately. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_flash_sectors(struct zl3073x_dev *zldev, const char *component, + u32 page, u32 addr, const void *data, size_t size, + struct netlink_ext_ack *extack) +{ +#define ZL_FLASH_MAX_BLOCK_SIZE 0x0001E000 +#define ZL_FLASH_PAGE_SIZE 256 + size_t max_block_size, block_size, sector_size; + const void *ptr, *end; + int rc; + + /* Get flash sector size */ + rc = zl3073x_flash_get_sector_size(zldev, §or_size); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "Failed to get flash sector size"); + return rc; + } + + /* Determine max block size depending on sector size */ + max_block_size = ALIGN_DOWN(ZL_FLASH_MAX_BLOCK_SIZE, sector_size); + + for (ptr = data, end = data + size; ptr < end; ptr += block_size) { + char comp_str[32]; + + block_size = min_t(size_t, max_block_size, end - ptr); + + /* Add suffix '-partN' if the requested component size is + * greater than max_block_size. + */ + if (max_block_size < size) + snprintf(comp_str, sizeof(comp_str), "%s-part%zu", + component, (ptr - data) / max_block_size + 1); + else + strscpy(comp_str, component); + + /* Flash the memory block */ + rc = zl3073x_flash_block(zldev, comp_str, + ZL_WRITE_FLASH_OP_SECTORS, page, addr, + ptr, block_size, extack); + if (rc) + goto finish; + + /* Move to next page */ + page += block_size / ZL_FLASH_PAGE_SIZE; + } + +finish: + zl3073x_devlink_flash_notify(zldev, + rc ? "Flashing failed" : "Flashing done", + component, 0, 0); + + return rc; +} + +/** + * zl3073x_flash_page - Flash page + * @zldev: zl3073x device structure + * @component: component name + * @page: destination flash page + * @addr: device memory address to load data + * @data: pointer to data to be flashed + * @size: size of data + * @extack: netlink extack pointer to report errors + * + * The function flashes given @data with size of @size to the internal flash + * memory block starting with page @page. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_flash_page(struct zl3073x_dev *zldev, const char *component, + u32 page, u32 addr, const void *data, size_t size, + struct netlink_ext_ack *extack) +{ + int rc; + + /* Flash the memory block */ + rc = zl3073x_flash_block(zldev, component, ZL_WRITE_FLASH_OP_PAGE, page, + addr, data, size, extack); + + zl3073x_devlink_flash_notify(zldev, + rc ? "Flashing failed" : "Flashing done", + component, 0, 0); + + return rc; +} + +/** + * zl3073x_flash_page_copy - Copy flash page + * @zldev: zl3073x device structure + * @component: component name + * @src_page: source page to copy + * @dst_page: destination page + * @extack: netlink extack pointer to report errors + * + * The function copies one flash page specified by @src_page into the flash + * page specified by @dst_page. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_flash_page_copy(struct zl3073x_dev *zldev, const char *component, + u32 src_page, u32 dst_page, + struct netlink_ext_ack *extack) +{ + int rc; + + /* Set source page to be copied */ + rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_READ, src_page); + if (rc) + return rc; + + /* Set destination page for the copy */ + rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_WRITE, dst_page); + if (rc) + return rc; + + /* Perform copy operation */ + rc = zl3073x_flash_cmd_wait(zldev, ZL_WRITE_FLASH_OP_COPY_PAGE, extack); + if (rc) + ZL_FLASH_ERR_MSG(extack, "Failed to copy page %u to page %u", + src_page, dst_page); + + return rc; +} + +/** + * zl3073x_flash_mode_verify - Check flash utility + * @zldev: zl3073x device structure + * + * Return: 0 if the flash utility is ready, <0 on error + */ +static int +zl3073x_flash_mode_verify(struct zl3073x_dev *zldev) +{ + u8 family, release; + u32 hash; + int rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_FLASH_HASH, &hash); + if (rc) + return rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_FLASH_FAMILY, &family); + if (rc) + return rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_FLASH_RELEASE, &release); + if (rc) + return rc; + + dev_dbg(zldev->dev, + "Flash utility check: hash 0x%08x, fam 0x%02x, rel 0x%02x\n", + hash, family, release); + + /* Return success for correct family */ + return (family == 0x21) ? 0 : -ENODEV; +} + +static int +zl3073x_flash_host_ctrl_enable(struct zl3073x_dev *zldev) +{ + u8 host_ctrl; + int rc; + + /* Enable host control */ + rc = zl3073x_read_u8(zldev, ZL_REG_HOST_CONTROL, &host_ctrl); + if (rc) + return rc; + + host_ctrl |= ZL_HOST_CONTROL_ENABLE; + + return zl3073x_write_u8(zldev, ZL_REG_HOST_CONTROL, host_ctrl); +} + +/** + * zl3073x_flash_mode_enter - Switch the device to flash mode + * @zldev: zl3073x device structure + * @util_ptr: buffer with flash utility + * @util_size: size of buffer with flash utility + * @extack: netlink extack pointer to report errors + * + * The function prepares and switches the device into flash mode. + * + * The procedure: + * 1) Stop device CPU by specific HW register sequence + * 2) Download flash utility to device memory + * 3) Resume device CPU by specific HW register sequence + * 4) Check communication with flash utility + * 5) Enable host control necessary to access flash API + * 6) Check for potential error detected by the utility + * + * The API provided by normal firmware is not available in flash mode + * so the caller has to ensure that this API is not used in this mode. + * + * After performing flash operation the caller should call + * @zl3073x_flash_mode_leave to return back to normal operation. + * + * Return: 0 on success, <0 on error. + */ +int zl3073x_flash_mode_enter(struct zl3073x_dev *zldev, const void *util_ptr, + size_t util_size, struct netlink_ext_ack *extack) +{ + /* Sequence to be written prior utility download */ + static const struct zl3073x_hwreg_seq_item pre_seq[] = { + HWREG_SEQ_ITEM(0x80000400, 1, BIT(0), 0), + HWREG_SEQ_ITEM(0x80206340, 1, BIT(4), 0), + HWREG_SEQ_ITEM(0x10000000, 1, BIT(2), 0), + HWREG_SEQ_ITEM(0x10000024, 0x00000001, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10000020, 0x00000001, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10000000, 1, BIT(10), 1000), + }; + /* Sequence to be written after utility download */ + static const struct zl3073x_hwreg_seq_item post_seq[] = { + HWREG_SEQ_ITEM(0x10400004, 0x000000C0, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10400008, 0x00000000, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10400010, 0x20000000, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10400014, 0x20000004, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10000000, 1, GENMASK(10, 9), 0), + HWREG_SEQ_ITEM(0x10000020, 0x00000000, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10000000, 0, BIT(0), 1000), + }; + int rc; + + zl3073x_devlink_flash_notify(zldev, "Prepare flash mode", "utility", + 0, 0); + + /* Execure pre-load sequence */ + rc = zl3073x_write_hwreg_seq(zldev, pre_seq, ARRAY_SIZE(pre_seq)); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "cannot execute pre-load sequence"); + goto error; + } + + /* Download utility image to device memory */ + rc = zl3073x_flash_download(zldev, "utility", 0x20000000, util_ptr, + util_size, extack); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "cannot download flash utility"); + goto error; + } + + /* Execute post-load sequence */ + rc = zl3073x_write_hwreg_seq(zldev, post_seq, ARRAY_SIZE(post_seq)); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "cannot execute post-load sequence"); + goto error; + } + + /* Check that utility identifies itself correctly */ + rc = zl3073x_flash_mode_verify(zldev); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "flash utility check failed"); + goto error; + } + + /* Enable host control */ + rc = zl3073x_flash_host_ctrl_enable(zldev); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "cannot enable host control"); + goto error; + } + + zl3073x_devlink_flash_notify(zldev, "Flash mode enabled", "utility", + 0, 0); + + return 0; + +error: + zl3073x_flash_mode_leave(zldev, extack); + + return rc; +} + +/** + * zl3073x_flash_mode_leave - Leave flash mode + * @zldev: zl3073x device structure + * @extack: netlink extack pointer to report errors + * + * The function instructs the device to leave the flash mode and + * to return back to normal operation. + * + * The procedure: + * 1) Set reset flag + * 2) Reset the device CPU by specific HW register sequence + * 3) Wait for the device to be ready + * 4) Check the reset flag was cleared + * + * Return: 0 on success, <0 on error + */ +int zl3073x_flash_mode_leave(struct zl3073x_dev *zldev, + struct netlink_ext_ack *extack) +{ + /* Sequence to be written after flash */ + static const struct zl3073x_hwreg_seq_item fw_reset_seq[] = { + HWREG_SEQ_ITEM(0x80000404, 1, BIT(0), 0), + HWREG_SEQ_ITEM(0x80000410, 1, BIT(0), 0), + }; + u8 reset_status; + int rc; + + zl3073x_devlink_flash_notify(zldev, "Leaving flash mode", "utility", + 0, 0); + + /* Read reset status register */ + rc = zl3073x_read_u8(zldev, ZL_REG_RESET_STATUS, &reset_status); + if (rc) + return rc; + + /* Set reset bit */ + reset_status |= ZL_REG_RESET_STATUS_RESET; + + /* Update reset status register */ + rc = zl3073x_write_u8(zldev, ZL_REG_RESET_STATUS, reset_status); + if (rc) + return rc; + + /* We do not check the return value here as the sequence resets + * the device CPU and the last write always return an error. + */ + zl3073x_write_hwreg_seq(zldev, fw_reset_seq, ARRAY_SIZE(fw_reset_seq)); + + /* Wait for the device to be ready */ + msleep(500); + + /* Read again the reset status register */ + rc = zl3073x_read_u8(zldev, ZL_REG_RESET_STATUS, &reset_status); + if (rc) + return rc; + + /* Check the reset bit was cleared */ + if (reset_status & ZL_REG_RESET_STATUS_RESET) { + dev_err(zldev->dev, + "Reset not confirmed after switch to normal mode\n"); + return -EINVAL; + } + + return 0; +} diff --git a/drivers/dpll/zl3073x/flash.h b/drivers/dpll/zl3073x/flash.h new file mode 100644 index 000000000..effe1b16b --- /dev/null +++ b/drivers/dpll/zl3073x/flash.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef __ZL3073X_FLASH_H +#define __ZL3073X_FLASH_H + +#include + +struct netlink_ext_ack; +struct zl3073x_dev; + +int zl3073x_flash_mode_enter(struct zl3073x_dev *zldev, const void *util_ptr, + size_t util_size, struct netlink_ext_ack *extack); + +int zl3073x_flash_mode_leave(struct zl3073x_dev *zldev, + struct netlink_ext_ack *extack); + +int zl3073x_flash_page(struct zl3073x_dev *zldev, const char *component, + u32 page, u32 addr, const void *data, size_t size, + struct netlink_ext_ack *extack); + +int zl3073x_flash_page_copy(struct zl3073x_dev *zldev, const char *component, + u32 src_page, u32 dst_page, + struct netlink_ext_ack *extack); + +int zl3073x_flash_sectors(struct zl3073x_dev *zldev, const char *component, + u32 page, u32 addr, const void *data, size_t size, + struct netlink_ext_ack *extack); + +#endif /* __ZL3073X_FLASH_H */ diff --git a/drivers/dpll/zl3073x/fw.c b/drivers/dpll/zl3073x/fw.c new file mode 100644 index 000000000..3acbb33ed --- /dev/null +++ b/drivers/dpll/zl3073x/fw.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "flash.h" +#include "fw.h" + +#define ZL3073X_FW_ERR_PFX "FW load failed: " +#define ZL3073X_FW_ERR_MSG(_extack, _msg, ...) \ + NL_SET_ERR_MSG_FMT_MOD((_extack), ZL3073X_FW_ERR_PFX _msg, \ + ## __VA_ARGS__) + +enum zl3073x_flash_type { + ZL3073X_FLASH_TYPE_NONE = 0, + ZL3073X_FLASH_TYPE_SECTORS, + ZL3073X_FLASH_TYPE_PAGE, + ZL3073X_FLASH_TYPE_PAGE_AND_COPY, +}; + +struct zl3073x_fw_component_info { + const char *name; + size_t max_size; + enum zl3073x_flash_type flash_type; + u32 load_addr; + u32 dest_page; + u32 copy_page; +}; + +static const struct zl3073x_fw_component_info component_info[] = { + [ZL_FW_COMPONENT_UTIL] = { + .name = "utility", + .max_size = 0x4000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_NONE, + }, + [ZL_FW_COMPONENT_FW1] = { + .name = "firmware1", + .max_size = 0x35000, + .load_addr = 0x20002000, + .flash_type = ZL3073X_FLASH_TYPE_SECTORS, + .dest_page = 0x020, + }, + [ZL_FW_COMPONENT_FW2] = { + .name = "firmware2", + .max_size = 0x0040, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE_AND_COPY, + .dest_page = 0x3e0, + .copy_page = 0x000, + }, + [ZL_FW_COMPONENT_FW3] = { + .name = "firmware3", + .max_size = 0x0248, + .load_addr = 0x20000400, + .flash_type = ZL3073X_FLASH_TYPE_PAGE_AND_COPY, + .dest_page = 0x3e4, + .copy_page = 0x004, + }, + [ZL_FW_COMPONENT_CFG0] = { + .name = "config0", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3d0, + }, + [ZL_FW_COMPONENT_CFG1] = { + .name = "config1", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3c0, + }, + [ZL_FW_COMPONENT_CFG2] = { + .name = "config2", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3b0, + }, + [ZL_FW_COMPONENT_CFG3] = { + .name = "config3", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3a0, + }, + [ZL_FW_COMPONENT_CFG4] = { + .name = "config4", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x390, + }, + [ZL_FW_COMPONENT_CFG5] = { + .name = "config5", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x380, + }, + [ZL_FW_COMPONENT_CFG6] = { + .name = "config6", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x370, + }, +}; + +/* Sanity check */ +static_assert(ARRAY_SIZE(component_info) == ZL_FW_NUM_COMPONENTS); + +/** + * zl3073x_fw_component_alloc - Alloc structure to hold firmware component + * @size: size of buffer to store data + * + * Return: pointer to allocated component structure or NULL on error. + */ +static struct zl3073x_fw_component * +zl3073x_fw_component_alloc(size_t size) +{ + struct zl3073x_fw_component *comp; + + comp = kzalloc_obj(*comp); + if (!comp) + return NULL; + + comp->size = size; + comp->data = kzalloc(size, GFP_KERNEL); + if (!comp->data) { + kfree(comp); + return NULL; + } + + return comp; +} + +/** + * zl3073x_fw_component_free - Free allocated component structure + * @comp: pointer to allocated component + */ +static void +zl3073x_fw_component_free(struct zl3073x_fw_component *comp) +{ + if (comp) + kfree(comp->data); + + kfree(comp); +} + +/** + * zl3073x_fw_component_id_get - Get ID for firmware component name + * @name: input firmware component name + * + * Return: + * - ZL3073X_FW_COMPONENT_* ID for known component name + * - ZL3073X_FW_COMPONENT_INVALID if the given name is unknown + */ +static enum zl3073x_fw_component_id +zl3073x_fw_component_id_get(const char *name) +{ + enum zl3073x_fw_component_id id; + + for (id = 0; id < ZL_FW_NUM_COMPONENTS; id++) + if (!strcasecmp(name, component_info[id].name)) + return id; + + return ZL_FW_COMPONENT_INVALID; +} + +/** + * zl3073x_fw_component_load - Load component from firmware source + * @zldev: zl3073x device structure + * @pcomp: pointer to loaded component + * @psrc: data pointer to load component from + * @psize: remaining bytes in buffer + * @extack: netlink extack pointer to report errors + * + * The function allocates single firmware component and loads the data from + * the buffer specified by @psrc and @psize. Pointer to allocated component + * is stored in output @pcomp. Source data pointer @psrc and remaining bytes + * @psize are updated accordingly. + * + * Return: + * * 1 when component was allocated and loaded + * * 0 when there is no component to load + * * <0 on error + */ +static ssize_t +zl3073x_fw_component_load(struct zl3073x_dev *zldev, + struct zl3073x_fw_component **pcomp, + const char **psrc, size_t *psize, + struct netlink_ext_ack *extack) +{ + const struct zl3073x_fw_component_info *info; + struct zl3073x_fw_component *comp = NULL; + struct device *dev = zldev->dev; + enum zl3073x_fw_component_id id; + char buf[32], name[16]; + u32 count, size, *dest; + int pos, rc; + + /* Fetch image name and size from input */ + strscpy(buf, *psrc, min(sizeof(buf), *psize)); + rc = sscanf(buf, "%15s %u %n", name, &count, &pos); + if (!rc) { + /* No more data */ + return 0; + } else if (rc == 1 || count > U32_MAX / sizeof(u32)) { + ZL3073X_FW_ERR_MSG(extack, "invalid component size"); + return -EINVAL; + } + *psrc += pos; + *psize -= pos; + + dev_dbg(dev, "Firmware component '%s' found\n", name); + + id = zl3073x_fw_component_id_get(name); + if (id == ZL_FW_COMPONENT_INVALID) { + ZL3073X_FW_ERR_MSG(extack, "unknown component type '%s'", name); + return -EINVAL; + } + + info = &component_info[id]; + size = count * sizeof(u32); /* get size in bytes */ + + /* Check image size validity */ + if (size > component_info[id].max_size) { + ZL3073X_FW_ERR_MSG(extack, + "[%s] component is too big (%u bytes)\n", + info->name, size); + return -EINVAL; + } + + dev_dbg(dev, "Indicated component image size: %u bytes\n", size); + + /* Alloc component */ + comp = zl3073x_fw_component_alloc(size); + if (!comp) { + ZL3073X_FW_ERR_MSG(extack, "failed to alloc memory"); + return -ENOMEM; + } + comp->id = id; + + /* Load component data from firmware source */ + for (dest = comp->data; count; count--, dest++) { + strscpy(buf, *psrc, min(sizeof(buf), *psize)); + rc = sscanf(buf, "%x %n", dest, &pos); + if (!rc) + goto err_data; + + *psrc += pos; + *psize -= pos; + } + + *pcomp = comp; + + return 1; + +err_data: + ZL3073X_FW_ERR_MSG(extack, "[%s] invalid or missing data", info->name); + + zl3073x_fw_component_free(comp); + + return -ENODATA; +} + +/** + * zl3073x_fw_free - Free allocated firmware + * @fw: firmware pointer + * + * The function frees existing firmware allocated by @zl3073x_fw_load. + */ +void zl3073x_fw_free(struct zl3073x_fw *fw) +{ + size_t i; + + if (!fw) + return; + + for (i = 0; i < ZL_FW_NUM_COMPONENTS; i++) + zl3073x_fw_component_free(fw->component[i]); + + kfree(fw); +} + +/** + * zl3073x_fw_load - Load all components from source + * @zldev: zl3073x device structure + * @data: source buffer pointer + * @size: size of source buffer + * @extack: netlink extack pointer to report errors + * + * The functions allocate firmware structure and loads all components from + * the given buffer specified by @data and @size. + * + * Return: pointer to firmware on success, error pointer on error + */ +struct zl3073x_fw *zl3073x_fw_load(struct zl3073x_dev *zldev, const char *data, + size_t size, struct netlink_ext_ack *extack) +{ + struct zl3073x_fw_component *comp; + enum zl3073x_fw_component_id id; + struct zl3073x_fw *fw; + ssize_t rc; + + /* Allocate firmware structure */ + fw = kzalloc_obj(*fw); + if (!fw) + return ERR_PTR(-ENOMEM); + + do { + /* Load single component */ + rc = zl3073x_fw_component_load(zldev, &comp, &data, &size, + extack); + if (rc <= 0) + /* Everything was read or error occurred */ + break; + + id = comp->id; + + /* Report error if the given component is present twice + * or more. + */ + if (fw->component[id]) { + ZL3073X_FW_ERR_MSG(extack, + "duplicate component '%s' detected", + component_info[id].name); + zl3073x_fw_component_free(comp); + rc = -EINVAL; + break; + } + + fw->component[id] = comp; + } while (true); + + if (rc) { + /* Free allocated firmware in case of error */ + zl3073x_fw_free(fw); + return ERR_PTR(rc); + } + + return fw; +} + +/** + * zl3073x_fw_component_flash - Flash all components + * @zldev: zl3073x device structure + * @comp: pointer to components array + * @extack: netlink extack pointer to report errors + * + * Return: 0 in case of success or negative number otherwise. + */ +static int +zl3073x_fw_component_flash(struct zl3073x_dev *zldev, + struct zl3073x_fw_component *comp, + struct netlink_ext_ack *extack) +{ + const struct zl3073x_fw_component_info *info; + int rc; + + info = &component_info[comp->id]; + + switch (info->flash_type) { + case ZL3073X_FLASH_TYPE_NONE: + /* Non-flashable component - used for utility */ + return 0; + case ZL3073X_FLASH_TYPE_SECTORS: + rc = zl3073x_flash_sectors(zldev, info->name, info->dest_page, + info->load_addr, comp->data, + comp->size, extack); + break; + case ZL3073X_FLASH_TYPE_PAGE: + rc = zl3073x_flash_page(zldev, info->name, info->dest_page, + info->load_addr, comp->data, comp->size, + extack); + break; + case ZL3073X_FLASH_TYPE_PAGE_AND_COPY: + rc = zl3073x_flash_page(zldev, info->name, info->dest_page, + info->load_addr, comp->data, comp->size, + extack); + if (!rc) + rc = zl3073x_flash_page_copy(zldev, info->name, + info->dest_page, + info->copy_page, extack); + break; + } + if (rc) + ZL3073X_FW_ERR_MSG(extack, "Failed to flash component '%s'", + info->name); + + return rc; +} + +int zl3073x_fw_flash(struct zl3073x_dev *zldev, struct zl3073x_fw *zlfw, + struct netlink_ext_ack *extack) +{ + int i, rc = 0; + + for (i = 0; i < ZL_FW_NUM_COMPONENTS; i++) { + if (!zlfw->component[i]) + continue; /* Component is not present */ + + rc = zl3073x_fw_component_flash(zldev, zlfw->component[i], + extack); + if (rc) + break; + } + + return rc; +} diff --git a/drivers/dpll/zl3073x/fw.h b/drivers/dpll/zl3073x/fw.h new file mode 100644 index 000000000..fcaa89ab0 --- /dev/null +++ b/drivers/dpll/zl3073x/fw.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_FW_H +#define _ZL3073X_FW_H + +/* + * enum zl3073x_fw_component_id - Identifiers for possible flash components + */ +enum zl3073x_fw_component_id { + ZL_FW_COMPONENT_INVALID = -1, + ZL_FW_COMPONENT_UTIL = 0, + ZL_FW_COMPONENT_FW1, + ZL_FW_COMPONENT_FW2, + ZL_FW_COMPONENT_FW3, + ZL_FW_COMPONENT_CFG0, + ZL_FW_COMPONENT_CFG1, + ZL_FW_COMPONENT_CFG2, + ZL_FW_COMPONENT_CFG3, + ZL_FW_COMPONENT_CFG4, + ZL_FW_COMPONENT_CFG5, + ZL_FW_COMPONENT_CFG6, + ZL_FW_NUM_COMPONENTS +}; + +/** + * struct zl3073x_fw_component - Firmware component + * @id: Flash component ID + * @size: Size of the buffer + * @data: Pointer to buffer with component data + */ +struct zl3073x_fw_component { + enum zl3073x_fw_component_id id; + size_t size; + void *data; +}; + +/** + * struct zl3073x_fw - Firmware bundle + * @component: firmware components array + */ +struct zl3073x_fw { + struct zl3073x_fw_component *component[ZL_FW_NUM_COMPONENTS]; +}; + +struct zl3073x_fw *zl3073x_fw_load(struct zl3073x_dev *zldev, const char *data, + size_t size, struct netlink_ext_ack *extack); +void zl3073x_fw_free(struct zl3073x_fw *fw); + +int zl3073x_fw_flash(struct zl3073x_dev *zldev, struct zl3073x_fw *zlfw, + struct netlink_ext_ack *extack); + +#endif /* _ZL3073X_FW_H */ diff --git a/drivers/dpll/zl3073x/i2c.c b/drivers/dpll/zl3073x/i2c.c new file mode 100644 index 000000000..4a23340e2 --- /dev/null +++ b/drivers/dpll/zl3073x/i2c.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include "core.h" + +static int zl3073x_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct zl3073x_dev *zldev; + + zldev = zl3073x_devm_alloc(dev); + if (IS_ERR(zldev)) + return PTR_ERR(zldev); + + zldev->regmap = devm_regmap_init_i2c(client, &zl3073x_regmap_config); + if (IS_ERR(zldev->regmap)) + return dev_err_probe(dev, PTR_ERR(zldev->regmap), + "Failed to initialize regmap\n"); + + return zl3073x_dev_probe(zldev); +} + +static const struct i2c_device_id zl3073x_i2c_id[] = { + { .name = "zl30731" }, + { .name = "zl30732" }, + { .name = "zl30733" }, + { .name = "zl30734" }, + { .name = "zl30735" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, zl3073x_i2c_id); + +static const struct of_device_id zl3073x_i2c_of_match[] = { + { .compatible = "microchip,zl30731" }, + { .compatible = "microchip,zl30732" }, + { .compatible = "microchip,zl30733" }, + { .compatible = "microchip,zl30734" }, + { .compatible = "microchip,zl30735" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, zl3073x_i2c_of_match); + +static struct i2c_driver zl3073x_i2c_driver = { + .driver = { + .name = "zl3073x-i2c", + .of_match_table = zl3073x_i2c_of_match, + }, + .probe = zl3073x_i2c_probe, + .id_table = zl3073x_i2c_id, +}; +module_i2c_driver(zl3073x_i2c_driver); + +MODULE_AUTHOR("Ivan Vecera "); +MODULE_DESCRIPTION("Microchip ZL3073x I2C driver"); +MODULE_IMPORT_NS("ZL3073X"); +MODULE_LICENSE("GPL"); diff --git a/drivers/dpll/zl3073x/out.c b/drivers/dpll/zl3073x/out.c new file mode 100644 index 000000000..410d15b96 --- /dev/null +++ b/drivers/dpll/zl3073x/out.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "out.h" + +/** + * zl3073x_out_state_fetch - fetch output state from hardware + * @zldev: pointer to zl3073x_dev structure + * @index: output index to fetch state for + * + * Function fetches state of the given output from hardware and stores it + * for later use. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_out *out = &zldev->out[index]; + int rc; + + /* Read output configuration */ + rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_CTRL(index), &out->ctrl); + if (rc) + return rc; + + dev_dbg(zldev->dev, "OUT%u is %s and connected to SYNTH%u\n", index, + str_enabled_disabled(zl3073x_out_is_enabled(out)), + zl3073x_out_synth_get(out)); + + guard(mutex)(&zldev->multiop_lock); + + /* Read output configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* Read output mode */ + rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &out->mode); + if (rc) + return rc; + + dev_dbg(zldev->dev, "OUT%u has signal format 0x%02x\n", index, + zl3073x_out_signal_format_get(out)); + + /* Read output divisor */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &out->div); + if (rc) + return rc; + + if (!out->div) { + dev_err(zldev->dev, "Zero divisor for OUT%u got from device\n", + index); + return -EINVAL; + } + + dev_dbg(zldev->dev, "OUT%u divisor: %u\n", index, out->div); + + /* Read output width */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_WIDTH, &out->width); + if (rc) + return rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, + &out->esync_n_period); + if (rc) + return rc; + + if (!out->esync_n_period) { + dev_err(zldev->dev, + "Zero esync divisor for OUT%u got from device\n", + index); + return -EINVAL; + } + + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, + &out->esync_n_width); + if (rc) + return rc; + + return zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, + &out->phase_comp); +} + +/** + * zl3073x_out_state_get - get current output state + * @zldev: pointer to zl3073x_dev structure + * @index: output index to get state for + * + * Return: pointer to given output state + */ +const struct zl3073x_out *zl3073x_out_state_get(struct zl3073x_dev *zldev, + u8 index) +{ + 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 */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* Update mailbox with changed values */ + if (dout->div != out->div) + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_DIV, out->div); + if (!rc && dout->width != out->width) + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_WIDTH, out->width); + if (!rc && dout->esync_n_period != out->esync_n_period) + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, + out->esync_n_period); + if (!rc && dout->esync_n_width != out->esync_n_width) + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, + out->esync_n_width); + if (!rc && dout->mode != out->mode) + rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_MODE, out->mode); + if (!rc && dout->phase_comp != out->phase_comp) + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, + out->phase_comp); + if (rc) + return rc; + + /* Commit output configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, + ZL_REG_OUTPUT_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* After successful commit store new state */ + dout->cfg = out->cfg; + + return 0; +} diff --git a/drivers/dpll/zl3073x/out.h b/drivers/dpll/zl3073x/out.h new file mode 100644 index 000000000..660889c57 --- /dev/null +++ b/drivers/dpll/zl3073x/out.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_OUT_H +#define _ZL3073X_OUT_H + +#include +#include +#include + +#include "regs.h" + +struct zl3073x_dev; + +/** + * struct zl3073x_out - output state + * @div: output divisor + * @width: output pulse width + * @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 + * @mode: output mode + * @ctrl: output control + */ +struct zl3073x_out { + 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); +const struct zl3073x_out *zl3073x_out_state_get(struct zl3073x_dev *zldev, + u8 index); + +int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index, + const struct zl3073x_out *out); + +/** + * zl3073x_out_clock_type_get - get output clock type + * @out: pointer to out state + * + * Return: clock type of given output (ZL_OUTPUT_MODE_CLOCK_TYPE_*) + */ +static inline u8 zl3073x_out_clock_type_get(const struct zl3073x_out *out) +{ + return FIELD_GET(ZL_OUTPUT_MODE_CLOCK_TYPE, out->mode); +} + +/** + * zl3073x_out_clock_type_set - set output clock type + * @out: pointer to out state + * @type: clock type (ZL_OUTPUT_MODE_CLOCK_TYPE_*) + */ +static inline void +zl3073x_out_clock_type_set(struct zl3073x_out *out, u8 type) +{ + FIELD_MODIFY(ZL_OUTPUT_MODE_CLOCK_TYPE, &out->mode, type); +} + +/** + * zl3073x_out_signal_format_get - get output signal format + * @out: pointer to out state + * + * Return: signal format of given output + */ +static inline u8 zl3073x_out_signal_format_get(const struct zl3073x_out *out) +{ + return FIELD_GET(ZL_OUTPUT_MODE_SIGNAL_FORMAT, out->mode); +} + +/** + * zl3073x_out_is_diff - check if the given output is differential + * @out: pointer to out state + * + * Return: true if output is differential, false if output is single-ended + */ +static inline bool zl3073x_out_is_diff(const struct zl3073x_out *out) +{ + switch (zl3073x_out_signal_format_get(out)) { + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_LVDS: + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_DIFF: + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_LOWVCM: + return true; + default: + break; + } + + return false; +} + +/** + * zl3073x_out_is_enabled - check if the given output is enabled + * @out: pointer to out state + * + * Return: true if output is enabled, false if output is disabled + */ +static inline bool zl3073x_out_is_enabled(const struct zl3073x_out *out) +{ + return !!FIELD_GET(ZL_OUTPUT_CTRL_EN, out->ctrl); +} + +/** + * zl3073x_out_is_ndiv - check if the given output is in N-div mode + * @out: pointer to out state + * + * Return: true if output is in N-div mode, false otherwise + */ +static inline bool zl3073x_out_is_ndiv(const struct zl3073x_out *out) +{ + switch (zl3073x_out_signal_format_get(out)) { + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: + return true; + default: + return false; + } +} + +/** + * zl3073x_out_synth_get - get synth connected to given output + * @out: pointer to out state + * + * Return: index of synth connected to given output. + */ +static inline u8 zl3073x_out_synth_get(const struct zl3073x_out *out) +{ + return FIELD_GET(ZL_OUTPUT_CTRL_SYNTH_SEL, out->ctrl); +} + +#endif /* _ZL3073X_OUT_H */ diff --git a/drivers/dpll/zl3073x/prop.c b/drivers/dpll/zl3073x/prop.c new file mode 100644 index 000000000..ac9d41d0f --- /dev/null +++ b/drivers/dpll/zl3073x/prop.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "prop.h" + +/** + * zl3073x_pin_check_freq - verify frequency for given pin + * @zldev: pointer to zl3073x device + * @dir: pin direction + * @id: pin index + * @freq: frequency to check + * + * The function checks the given frequency is valid for the device. For input + * pins it checks that the frequency can be factorized using supported base + * frequencies. For output pins it checks that the frequency divides connected + * synth frequency without remainder. + * + * Return: true if the frequency is valid, false if not. + */ +static bool +zl3073x_pin_check_freq(struct zl3073x_dev *zldev, enum dpll_pin_direction dir, + u8 id, u64 freq) +{ + if (freq > U32_MAX) + goto err_inv_freq; + + if (dir == DPLL_PIN_DIRECTION_INPUT) { + int rc; + + /* Check if the frequency can be factorized */ + rc = zl3073x_ref_freq_factorize(freq, NULL, NULL); + if (rc) + goto err_inv_freq; + } else { + u32 synth_freq; + u8 out, synth; + + /* Get output pin synthesizer */ + out = zl3073x_output_pin_out_get(id); + synth = zl3073x_dev_out_synth_get(zldev, out); + + /* Get synth frequency */ + synth_freq = zl3073x_dev_synth_freq_get(zldev, synth); + + /* Check the frequency divides synth frequency */ + if (synth_freq % (u32)freq) + goto err_inv_freq; + } + + return true; + +err_inv_freq: + dev_warn(zldev->dev, + "Unsupported frequency %llu Hz in firmware node\n", freq); + + return false; +} + +/** + * zl3073x_prop_pin_package_label_set - get package label for the pin + * @zldev: pointer to zl3073x device + * @props: pointer to pin properties + * @dir: pin direction + * @id: pin index + * + * Generates package label string and stores it into pin properties structure. + * + * Possible formats: + * REF - differential input reference + * REFP & REFN - single-ended input reference (P or N pin) + * OUT - differential output + * OUTP & OUTN - single-ended output (P or N pin) + */ +static void +zl3073x_prop_pin_package_label_set(struct zl3073x_dev *zldev, + struct zl3073x_pin_props *props, + enum dpll_pin_direction dir, u8 id) +{ + const char *prefix, *suffix; + bool is_diff; + + if (dir == DPLL_PIN_DIRECTION_INPUT) { + u8 ref; + + prefix = "REF"; + ref = zl3073x_input_pin_ref_get(id); + is_diff = zl3073x_dev_ref_is_diff(zldev, ref); + } else { + u8 out; + + prefix = "OUT"; + out = zl3073x_output_pin_out_get(id); + is_diff = zl3073x_dev_out_is_diff(zldev, out); + } + + if (!is_diff) + suffix = zl3073x_is_p_pin(id) ? "P" : "N"; + else + suffix = ""; /* No suffix for differential one */ + + snprintf(props->package_label, sizeof(props->package_label), "%s%u%s", + prefix, id / 2, suffix); + + /* Set package_label pointer in DPLL core properties to generated + * string. + */ + props->dpll_props.package_label = props->package_label; +} + +/** + * zl3073x_prop_pin_fwnode_get - get fwnode for given pin + * @zldev: pointer to zl3073x device + * @props: pointer to pin properties + * @dir: pin direction + * @id: pin index + * + * Return: 0 on success, -ENOENT if the firmware node does not exist + */ +static int +zl3073x_prop_pin_fwnode_get(struct zl3073x_dev *zldev, + struct zl3073x_pin_props *props, + enum dpll_pin_direction dir, u8 id) +{ + struct fwnode_handle *pins_node, *pin_node; + const char *node_name; + + if (dir == DPLL_PIN_DIRECTION_INPUT) + node_name = "input-pins"; + else + node_name = "output-pins"; + + /* Get node containing input or output pins */ + pins_node = device_get_named_child_node(zldev->dev, node_name); + if (!pins_node) { + dev_dbg(zldev->dev, "'%s' sub-node is missing\n", node_name); + return -ENOENT; + } + + /* Enumerate child pin nodes and find the requested one */ + fwnode_for_each_child_node(pins_node, pin_node) { + u32 reg; + + if (fwnode_property_read_u32(pin_node, "reg", ®)) + continue; + + if (id == reg) + break; + } + + /* Release pin parent node */ + fwnode_handle_put(pins_node); + + /* Save found node */ + props->fwnode = pin_node; + + dev_dbg(zldev->dev, "Firmware node for %s %sfound\n", + props->package_label, pin_node ? "" : "NOT "); + + return pin_node ? 0 : -ENOENT; +} + +/** + * zl3073x_pin_props_get - get pin properties + * @zldev: pointer to zl3073x device + * @dir: pin direction + * @index: pin index + * + * The function looks for firmware node for the given pin if it is provided + * by the system firmware (DT or ACPI), allocates pin properties structure, + * generates package label string according pin type and optionally fetches + * board label, connection type, supported frequencies and esync capability + * from the firmware node if it does exist. + * + * Pointer that is returned by this function should be freed using + * @zl3073x_pin_props_put(). + * + * Return: + * * pointer to allocated pin properties structure on success + * * error pointer in case of error + */ +struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev, + enum dpll_pin_direction dir, + u8 index) +{ + struct dpll_pin_frequency *ranges; + struct zl3073x_pin_props *props; + int i, j, num_freqs = 0, rc; + u64 *freqs = NULL; + const char *type; + u32 curr_freq; + + props = kzalloc_obj(*props); + if (!props) + return ERR_PTR(-ENOMEM); + + /* Set default pin type and capabilities */ + if (dir == DPLL_PIN_DIRECTION_INPUT) { + props->dpll_props.type = DPLL_PIN_TYPE_EXT; + props->dpll_props.capabilities = + DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE | + DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE; + curr_freq = zl3073x_dev_ref_freq_get(zldev, index); + } else { + u8 out, synth; + u32 f; + + props->dpll_props.type = DPLL_PIN_TYPE_GNSS; + + /* The output pin phase adjustment granularity equals half of + * the synth frequency count. + */ + out = zl3073x_output_pin_out_get(index); + synth = zl3073x_dev_out_synth_get(zldev, out); + f = 2 * zl3073x_dev_synth_freq_get(zldev, synth); + props->dpll_props.phase_gran = f ? div_u64(PSEC_PER_SEC, f) : 1; + curr_freq = zl3073x_dev_output_pin_freq_get(zldev, index); + } + + props->dpll_props.phase_range.min = S32_MIN; + props->dpll_props.phase_range.max = S32_MAX; + + zl3073x_prop_pin_package_label_set(zldev, props, dir, index); + + /* Get firmware node for the given pin */ + rc = zl3073x_prop_pin_fwnode_get(zldev, props, dir, index); + if (rc) + goto skip_fwnode_props; + + /* Look for label property and store the value as board label */ + fwnode_property_read_string(props->fwnode, "label", + &props->dpll_props.board_label); + + /* Look for pin type property and translate its value to DPLL + * pin type enum if it is present. + */ + if (!fwnode_property_read_string(props->fwnode, "connection-type", + &type)) { + if (!strcmp(type, "ext")) + props->dpll_props.type = DPLL_PIN_TYPE_EXT; + else if (!strcmp(type, "gnss")) + props->dpll_props.type = DPLL_PIN_TYPE_GNSS; + else if (!strcmp(type, "int")) + props->dpll_props.type = DPLL_PIN_TYPE_INT_OSCILLATOR; + else if (!strcmp(type, "synce")) + props->dpll_props.type = DPLL_PIN_TYPE_SYNCE_ETH_PORT; + else if (!strcmp(type, "mux")) + props->dpll_props.type = DPLL_PIN_TYPE_MUX; + else + dev_warn(zldev->dev, + "Unknown or unsupported pin type '%s'\n", + type); + } + + /* Check if the pin supports embedded sync control */ + props->esync_control = fwnode_property_read_bool(props->fwnode, + "esync-control"); + + /* Read supported frequencies property if it is specified */ + num_freqs = fwnode_property_count_u64(props->fwnode, + "supported-frequencies-hz"); + if (num_freqs <= 0) { + num_freqs = 0; + goto skip_fwnode_props; + } + + /* The firmware node specifies list of supported frequencies while + * DPLL core pin properties requires list of frequency ranges. + * So read the frequency list into temporary array. + */ + freqs = kcalloc(num_freqs, sizeof(*freqs), GFP_KERNEL); + if (!freqs) { + rc = -ENOMEM; + goto err_alloc_freqs; + } + + /* Read frequencies list from firmware node */ + fwnode_property_read_u64_array(props->fwnode, + "supported-frequencies-hz", freqs, + num_freqs); + +skip_fwnode_props: + /* Allocate frequency ranges list - extra slot for current frequency */ + ranges = kzalloc_objs(*ranges, num_freqs + 1); + if (!ranges) { + rc = -ENOMEM; + goto err_alloc_ranges; + } + + /* Start with current frequency at index 0 */ + ranges[0] = (struct dpll_pin_frequency)DPLL_PIN_FREQUENCY(curr_freq); + + /* Add frequencies from firmware node, skipping current frequency + * and filtering out frequencies not representable by device + */ + for (i = 0, j = 1; i < num_freqs; i++) { + struct dpll_pin_frequency freq = DPLL_PIN_FREQUENCY(freqs[i]); + + if (freqs[i] == curr_freq) + continue; + if (zl3073x_pin_check_freq(zldev, dir, index, freqs[i])) { + ranges[j] = freq; + j++; + } + } + + /* Save number of freq ranges and pointer to them into pin properties */ + props->dpll_props.freq_supported = ranges; + props->dpll_props.freq_supported_num = j; + + /* Free temporary array */ + kfree(freqs); + + return props; + +err_alloc_ranges: + kfree(freqs); +err_alloc_freqs: + fwnode_handle_put(props->fwnode); + kfree(props); + + return ERR_PTR(rc); +} + +/** + * zl3073x_pin_props_put - release pin properties + * @props: pin properties to free + * + * The function deallocates given pin properties structure. + */ +void zl3073x_pin_props_put(struct zl3073x_pin_props *props) +{ + /* Free supported frequency ranges list if it is present */ + kfree(props->dpll_props.freq_supported); + + /* Put firmware handle if it is present */ + if (props->fwnode) + fwnode_handle_put(props->fwnode); + + kfree(props); +} + +/** + * zl3073x_prop_dpll_type_get - get DPLL channel type + * @zldev: pointer to zl3073x device + * @index: DPLL channel index + * + * Return: DPLL type for given DPLL channel + */ +enum dpll_type +zl3073x_prop_dpll_type_get(struct zl3073x_dev *zldev, u8 index) +{ + const char *types[ZL3073X_MAX_CHANNELS]; + int count; + + /* Read dpll types property from firmware */ + count = device_property_read_string_array(zldev->dev, "dpll-types", + types, ARRAY_SIZE(types)); + + /* Return default if property or entry for given channel is missing */ + if (index >= count) + return DPLL_TYPE_PPS; + + if (!strcmp(types[index], "pps")) + return DPLL_TYPE_PPS; + else if (!strcmp(types[index], "eec")) + return DPLL_TYPE_EEC; + + dev_info(zldev->dev, "Unknown DPLL type '%s', using default\n", + types[index]); + + return DPLL_TYPE_PPS; /* Default */ +} diff --git a/drivers/dpll/zl3073x/prop.h b/drivers/dpll/zl3073x/prop.h new file mode 100644 index 000000000..721a18f05 --- /dev/null +++ b/drivers/dpll/zl3073x/prop.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_PROP_H +#define _ZL3073X_PROP_H + +#include + +#include "core.h" + +struct fwnode_handle; + +/** + * struct zl3073x_pin_props - pin properties + * @fwnode: pin firmware node + * @dpll_props: DPLL core pin properties + * @package_label: pin package label + * @esync_control: embedded sync support + */ +struct zl3073x_pin_props { + struct fwnode_handle *fwnode; + struct dpll_pin_properties dpll_props; + char package_label[8]; + bool esync_control; +}; + +enum dpll_type zl3073x_prop_dpll_type_get(struct zl3073x_dev *zldev, u8 index); + +struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev, + enum dpll_pin_direction dir, + u8 index); + +void zl3073x_pin_props_put(struct zl3073x_pin_props *props); + +#endif /* _ZL3073X_PROP_H */ diff --git a/drivers/dpll/zl3073x/ref.c b/drivers/dpll/zl3073x/ref.c new file mode 100644 index 000000000..825ac30bc --- /dev/null +++ b/drivers/dpll/zl3073x/ref.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "ref.h" + +/** + * zl3073x_ref_freq_factorize - factorize given frequency + * @freq: input frequency + * @base: base frequency + * @mult: multiplier + * + * Checks if the given frequency can be factorized using one of the + * supported base frequencies. If so the base frequency and multiplier + * are stored into appropriate parameters if they are not NULL. + * + * Return: 0 on success, -EINVAL if the frequency cannot be factorized + */ +int +zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult) +{ + static const u16 base_freqs[] = { + 1, 2, 4, 5, 8, 10, 16, 20, 25, 32, 40, 50, 64, 80, 100, 125, + 128, 160, 200, 250, 256, 320, 400, 500, 625, 640, 800, 1000, + 1250, 1280, 1600, 2000, 2500, 3125, 3200, 4000, 5000, 6250, + 6400, 8000, 10000, 12500, 15625, 16000, 20000, 25000, 31250, + 32000, 40000, 50000, 62500, + }; + u32 div; + int i; + + for (i = 0; i < ARRAY_SIZE(base_freqs); i++) { + div = freq / base_freqs[i]; + + if (div <= U16_MAX && (freq % base_freqs[i]) == 0) { + if (base) + *base = base_freqs[i]; + if (mult) + *mult = div; + + return 0; + } + } + + return -EINVAL; +} + +/** + * 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 + * + * Function fetches state for the given input reference from hardware and + * stores it for later use. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_ref *ref = &zldev->ref[index]; + int rc; + + /* For differential type inputs the N-pin reference shares + * part of the configuration with the P-pin counterpart. + */ + if (zl3073x_is_n_pin(index) && zl3073x_ref_is_diff(ref - 1)) { + struct zl3073x_ref *p_ref = ref - 1; /* P-pin counterpart*/ + + /* Copy the shared items from the P-pin */ + 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 */ + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* Read ref_config register */ + rc = zl3073x_read_u8(zldev, ZL_REG_REF_CONFIG, &ref->config); + if (rc) + return rc; + + /* Read frequency related registers */ + rc = zl3073x_read_u16(zldev, ZL_REG_REF_FREQ_BASE, &ref->freq_base); + if (rc) + return rc; + rc = zl3073x_read_u16(zldev, ZL_REG_REF_FREQ_MULT, &ref->freq_mult); + if (rc) + return rc; + rc = zl3073x_read_u16(zldev, ZL_REG_REF_RATIO_M, &ref->freq_ratio_m); + if (rc) + return rc; + rc = zl3073x_read_u16(zldev, ZL_REG_REF_RATIO_N, &ref->freq_ratio_n); + if (rc) + return rc; + + /* Read eSync and N-div rated registers */ + rc = zl3073x_read_u32(zldev, ZL_REG_REF_ESYNC_DIV, &ref->esync_n_div); + if (rc) + return rc; + rc = zl3073x_read_u8(zldev, ZL_REG_REF_SYNC_CTRL, &ref->sync_ctrl); + if (rc) + return rc; + + /* Read phase compensation register */ + if (zl3073x_dev_is_ref_phase_comp_32bit(zldev)) { + u32 val; + + rc = zl3073x_read_u32(zldev, ZL_REG_REF_PHASE_OFFSET_COMP_32, + &val); + ref->phase_comp = val; + } else { + rc = zl3073x_read_u48(zldev, ZL_REG_REF_PHASE_OFFSET_COMP, + &ref->phase_comp); + } + if (rc) + return rc; + + dev_dbg(zldev->dev, "REF%u is %s and configured as %s\n", index, + str_enabled_disabled(zl3073x_ref_is_enabled(ref)), + zl3073x_ref_is_diff(ref) ? "differential" : "single-ended"); + + return rc; +} + +/** + * zl3073x_ref_state_get - get current input reference state + * @zldev: pointer to zl3073x_dev structure + * @index: input reference index to get state for + * + * Return: pointer to given input reference state + */ +const struct zl3073x_ref * +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 */ + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* Update mailbox with changed values */ + if (dref->freq_base != ref->freq_base) + rc = zl3073x_write_u16(zldev, ZL_REG_REF_FREQ_BASE, + ref->freq_base); + if (!rc && dref->freq_mult != ref->freq_mult) + rc = zl3073x_write_u16(zldev, ZL_REG_REF_FREQ_MULT, + ref->freq_mult); + if (!rc && dref->freq_ratio_m != ref->freq_ratio_m) + rc = zl3073x_write_u16(zldev, ZL_REG_REF_RATIO_M, + ref->freq_ratio_m); + if (!rc && dref->freq_ratio_n != ref->freq_ratio_n) + rc = zl3073x_write_u16(zldev, ZL_REG_REF_RATIO_N, + ref->freq_ratio_n); + if (!rc && dref->esync_n_div != ref->esync_n_div) + rc = zl3073x_write_u32(zldev, ZL_REG_REF_ESYNC_DIV, + ref->esync_n_div); + if (!rc && dref->sync_ctrl != ref->sync_ctrl) + rc = zl3073x_write_u8(zldev, ZL_REG_REF_SYNC_CTRL, + ref->sync_ctrl); + if (!rc && dref->phase_comp != ref->phase_comp) { + if (zl3073x_dev_is_ref_phase_comp_32bit(zldev)) + rc = zl3073x_write_u32(zldev, + ZL_REG_REF_PHASE_OFFSET_COMP_32, + ref->phase_comp); + else + rc = zl3073x_write_u48(zldev, + ZL_REG_REF_PHASE_OFFSET_COMP, + ref->phase_comp); + } + if (rc) + return rc; + + /* Commit reference configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR, + ZL_REG_REF_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* After successful commit store new state */ + dref->cfg = ref->cfg; + + return 0; +} diff --git a/drivers/dpll/zl3073x/ref.h b/drivers/dpll/zl3073x/ref.h new file mode 100644 index 000000000..e140ca3ea --- /dev/null +++ b/drivers/dpll/zl3073x/ref.h @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_REF_H +#define _ZL3073X_REF_H + +#include +#include +#include +#include + +#include "regs.h" + +struct zl3073x_dev; + +/** + * struct zl3073x_ref - input reference state + * @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 + * @sync_ctrl: reference sync control + * @config: reference config + * @meas_freq: measured input frequency in Hz + * @mon_status: reference monitor status + */ +struct zl3073x_ref { + 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 */ + u32 meas_freq; + u8 mon_status; + ); +}; + +int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index); + +const struct zl3073x_ref *zl3073x_ref_state_get(struct zl3073x_dev *zldev, + u8 index); + +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); + +/** + * zl3073x_ref_meas_freq_get - get measured input frequency + * @ref: pointer to ref state + * + * Return: measured input frequency in Hz + */ +static inline u32 +zl3073x_ref_meas_freq_get(const struct zl3073x_ref *ref) +{ + return ref->meas_freq; +} + +/** + * zl3073x_ref_freq_get - get given input reference frequency + * @ref: pointer to ref state + * + * Return: frequency of the given input reference + */ +static inline u32 +zl3073x_ref_freq_get(const struct zl3073x_ref *ref) +{ + return mul_u64_u32_div(ref->freq_base * ref->freq_mult, + ref->freq_ratio_m, ref->freq_ratio_n); +} + +/** + * zl3073x_ref_freq_set - set given input reference frequency + * @ref: pointer to ref state + * @freq: frequency to be set + * + * Return: 0 on success, <0 when frequency cannot be factorized + */ +static inline int +zl3073x_ref_freq_set(struct zl3073x_ref *ref, u32 freq) +{ + u16 base, mult; + int rc; + + rc = zl3073x_ref_freq_factorize(freq, &base, &mult); + if (rc) + return rc; + + ref->freq_base = base; + ref->freq_mult = mult; + ref->freq_ratio_m = 1; + ref->freq_ratio_n = 1; + + return 0; +} + +/** + * zl3073x_ref_sync_mode_get - get sync control mode + * @ref: pointer to ref state + * + * Return: sync control mode (ZL_REF_SYNC_CTRL_MODE_*) + */ +static inline u8 +zl3073x_ref_sync_mode_get(const struct zl3073x_ref *ref) +{ + return FIELD_GET(ZL_REF_SYNC_CTRL_MODE, ref->sync_ctrl); +} + +/** + * zl3073x_ref_sync_mode_set - set sync control mode + * @ref: pointer to ref state + * @mode: sync control mode (ZL_REF_SYNC_CTRL_MODE_*) + */ +static inline void +zl3073x_ref_sync_mode_set(struct zl3073x_ref *ref, u8 mode) +{ + FIELD_MODIFY(ZL_REF_SYNC_CTRL_MODE, &ref->sync_ctrl, mode); +} + +/** + * zl3073x_ref_sync_pair_get - get sync pair reference index + * @ref: pointer to ref state + * + * Return: paired reference index + */ +static inline u8 +zl3073x_ref_sync_pair_get(const struct zl3073x_ref *ref) +{ + return FIELD_GET(ZL_REF_SYNC_CTRL_PAIR, ref->sync_ctrl); +} + +/** + * zl3073x_ref_sync_pair_set - set sync pair reference index + * @ref: pointer to ref state + * @pair: paired reference index + */ +static inline void +zl3073x_ref_sync_pair_set(struct zl3073x_ref *ref, u8 pair) +{ + FIELD_MODIFY(ZL_REF_SYNC_CTRL_PAIR, &ref->sync_ctrl, pair); +} + +/** + * zl3073x_ref_is_diff - check if the given input reference is differential + * @ref: pointer to ref state + * + * Return: true if reference is differential, false if reference is single-ended + */ +static inline bool +zl3073x_ref_is_diff(const struct zl3073x_ref *ref) +{ + return !!FIELD_GET(ZL_REF_CONFIG_DIFF_EN, ref->config); +} + +/** + * zl3073x_ref_is_enabled - check if the given input reference is enabled + * @ref: pointer to ref state + * + * Return: true if input refernce is enabled, false otherwise + */ +static inline bool +zl3073x_ref_is_enabled(const struct zl3073x_ref *ref) +{ + return !!FIELD_GET(ZL_REF_CONFIG_ENABLE, ref->config); +} + +/** + * zl3073x_ref_is_status_ok - check the given input reference status + * @ref: pointer to ref state + * + * Return: true if the status is ok, false otherwise + */ +static inline bool +zl3073x_ref_is_status_ok(const struct zl3073x_ref *ref) +{ + return ref->mon_status == ZL_REF_MON_STATUS_OK; +} + +#endif /* _ZL3073X_REF_H */ diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h new file mode 100644 index 000000000..f3a5e1215 --- /dev/null +++ b/drivers/dpll/zl3073x/regs.h @@ -0,0 +1,419 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_REGS_H +#define _ZL3073X_REGS_H + +#include +#include +#include + +/* + * 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) +#define ZL3073X_NCO_PIN_ID ZL3073X_NUM_PINS + +/* + * Register address structure: + * =========================== + * 25 19 18 16 15 7 6 0 + * +------------------------------------------+ + * | max_offset | size | page | page_offset | + * +------------------------------------------+ + * + * page_offset ... <0x00..0x7F> + * page .......... HW page number + * size .......... register byte size (1, 2, 4 or 6) + * max_offset .... maximal offset for indexed registers + * (for non-indexed regs max_offset == page_offset) + */ + +#define ZL_REG_OFFSET_MASK GENMASK(6, 0) +#define ZL_REG_PAGE_MASK GENMASK(15, 7) +#define ZL_REG_SIZE_MASK GENMASK(18, 16) +#define ZL_REG_MAX_OFFSET_MASK GENMASK(25, 19) +#define ZL_REG_ADDR_MASK GENMASK(15, 0) + +#define ZL_REG_OFFSET(_reg) FIELD_GET(ZL_REG_OFFSET_MASK, _reg) +#define ZL_REG_PAGE(_reg) FIELD_GET(ZL_REG_PAGE_MASK, _reg) +#define ZL_REG_MAX_OFFSET(_reg) FIELD_GET(ZL_REG_MAX_OFFSET_MASK, _reg) +#define ZL_REG_SIZE(_reg) FIELD_GET(ZL_REG_SIZE_MASK, _reg) +#define ZL_REG_ADDR(_reg) FIELD_GET(ZL_REG_ADDR_MASK, _reg) + +/** + * ZL_REG_IDX - define indexed register + * @_idx: index of register to access + * @_page: register page + * @_offset: register offset in page + * @_size: register byte size (1, 2, 4 or 6) + * @_items: number of register indices + * @_stride: stride between items in bytes + * + * All parameters except @_idx should be constant. + */ +#define ZL_REG_IDX(_idx, _page, _offset, _size, _items, _stride) \ + (FIELD_PREP(ZL_REG_OFFSET_MASK, \ + (_offset) + (_idx) * (_stride)) | \ + FIELD_PREP_CONST(ZL_REG_PAGE_MASK, _page) | \ + FIELD_PREP_CONST(ZL_REG_SIZE_MASK, _size) | \ + FIELD_PREP_CONST(ZL_REG_MAX_OFFSET_MASK, \ + (_offset) + ((_items) - 1) * (_stride))) + +/** + * ZL_REG - define simple (non-indexed) register + * @_page: register page + * @_offset: register offset in page + * @_size: register byte size (1, 2, 4 or 6) + * + * All parameters should be constant. + */ +#define ZL_REG(_page, _offset, _size) \ + ZL_REG_IDX(0, _page, _offset, _size, 1, 0) + +/************************** + * Register Page 0, General + **************************/ + +#define ZL_REG_INFO ZL_REG(0, 0x00, 1) +#define ZL_INFO_READY BIT(7) + +#define ZL_REG_ID ZL_REG(0, 0x01, 2) +#define ZL_REG_REVISION ZL_REG(0, 0x03, 2) +#define ZL_REG_FW_VER ZL_REG(0, 0x05, 2) +#define ZL_REG_CUSTOM_CONFIG_VER ZL_REG(0, 0x07, 4) + +#define ZL_REG_RESET_STATUS ZL_REG(0, 0x18, 1) +#define ZL_REG_RESET_STATUS_RESET BIT(0) + +#define ZL_REG_DIE_TEMP_STATUS ZL_REG(0, 0x44, 2) + +/************************* + * Register Page 2, Status + *************************/ + +#define ZL_REG_REF_MON_STATUS(_idx) \ + ZL_REG_IDX(_idx, 2, 0x02, 1, ZL3073X_NUM_REFS, 1) +#define ZL_REF_MON_STATUS_OK 0 +#define ZL_REF_MON_STATUS_LOS BIT(0) +#define ZL_REF_MON_STATUS_SCM BIT(1) +#define ZL_REF_MON_STATUS_CFM BIT(2) +#define ZL_REF_MON_STATUS_GST BIT(3) +#define ZL_REF_MON_STATUS_PFM BIT(4) +#define ZL_REF_MON_STATUS_ESYNC BIT(6) +#define ZL_REF_MON_STATUS_SPLIT_XO BIT(7) + +#define ZL_REG_DPLL_MON_STATUS(_idx) \ + ZL_REG_IDX(_idx, 2, 0x10, 1, ZL3073X_MAX_CHANNELS, 1) +#define ZL_DPLL_MON_STATUS_STATE GENMASK(1, 0) +#define ZL_DPLL_MON_STATUS_STATE_ACQUIRING 0 +#define ZL_DPLL_MON_STATUS_STATE_LOCK 1 +#define ZL_DPLL_MON_STATUS_STATE_HOLDOVER 2 +#define ZL_DPLL_MON_STATUS_HO_READY BIT(2) + +#define ZL_REG_DPLL_REFSEL_STATUS(_idx) \ + ZL_REG_IDX(_idx, 2, 0x30, 1, ZL3073X_MAX_CHANNELS, 1) +#define ZL_DPLL_REFSEL_STATUS_REFSEL GENMASK(3, 0) +#define ZL_DPLL_REFSEL_STATUS_STATE GENMASK(6, 4) +#define ZL_DPLL_REFSEL_STATUS_STATE_LOCK 4 + +#define ZL_REG_REF_FREQ(_idx) \ + ZL_REG_IDX(_idx, 2, 0x44, 4, ZL3073X_NUM_REFS, 4) + +/********************** + * Register Page 4, Ref + **********************/ + +#define ZL_REG_REF_PHASE_ERR_READ_RQST ZL_REG(4, 0x0f, 1) +#define ZL_REF_PHASE_ERR_READ_RQST_RD BIT(0) + +#define ZL_REG_REF_FREQ_MEAS_CTRL ZL_REG(4, 0x1c, 1) +#define ZL_REF_FREQ_MEAS_CTRL GENMASK(1, 0) +#define ZL_REF_FREQ_MEAS_CTRL_REF_FREQ 1 +#define ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF 2 +#define ZL_REF_FREQ_MEAS_CTRL_DPLL_FREQ_OFF 3 + +#define ZL_REG_REF_FREQ_MEAS_MASK_3_0 ZL_REG(4, 0x1d, 1) +#define ZL_REF_FREQ_MEAS_MASK_3_0(_ref) BIT(_ref) + +#define ZL_REG_REF_FREQ_MEAS_MASK_4 ZL_REG(4, 0x1e, 1) +#define ZL_REF_FREQ_MEAS_MASK_4(_ref) BIT((_ref) - 8) + +#define ZL_REG_DPLL_MEAS_REF_FREQ_CTRL ZL_REG(4, 0x1f, 1) +#define ZL_DPLL_MEAS_REF_FREQ_CTRL_EN BIT(0) +#define ZL_DPLL_MEAS_REF_FREQ_CTRL_IDX GENMASK(6, 4) + +#define ZL_REG_REF_PHASE(_idx) \ + ZL_REG_IDX(_idx, 4, 0x20, 6, ZL3073X_NUM_REFS, 6) + +/*********************** + * Register Page 5, DPLL + ***********************/ + +#define ZL_REG_DPLL_MODE_REFSEL(_idx) \ + ZL_REG_IDX(_idx, 5, 0x04, 1, ZL3073X_MAX_CHANNELS, 4) +#define ZL_DPLL_MODE_REFSEL_MODE GENMASK(2, 0) +#define ZL_DPLL_MODE_REFSEL_MODE_FREERUN 0 +#define ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER 1 +#define ZL_DPLL_MODE_REFSEL_MODE_REFLOCK 2 +#define ZL_DPLL_MODE_REFSEL_MODE_AUTO 3 +#define ZL_DPLL_MODE_REFSEL_MODE_NCO 4 +#define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4) + +#define ZL_REG_DPLL_CTRL(_idx) \ + ZL_REG_IDX(_idx, 5, 0x05, 1, ZL3073X_MAX_CHANNELS, 4) +#define ZL_DPLL_CTRL_TIE_CLEAR BIT(0) +#define ZL_DPLL_CTRL_TOD_STEP_RST BIT(2) +#define ZL_DPLL_CTRL_NCO_AUTO_READ BIT(7) + +#define ZL_REG_DPLL_DF_READ(_idx) \ + ZL_REG_IDX(_idx, 5, 0x28, 1, ZL3073X_MAX_CHANNELS, 1) +#define ZL_DPLL_DF_READ_SEM BIT(4) +#define ZL_DPLL_DF_READ_REF_OFST BIT(3) +#define ZL_DPLL_DF_READ_CMD GENMASK(2, 0) +#define ZL_DPLL_DF_READ_CMD_ACC_I 4 + +#define ZL_REG_DPLL_TIE_CTRL ZL_REG(5, 0x30, 1) +#define ZL_DPLL_TIE_CTRL_OP GENMASK(2, 0) +#define ZL_DPLL_TIE_CTRL_OP_WR 4 + +#define ZL_REG_DPLL_TIE_CTRL_MASK ZL_REG(5, 0x31, 1) + +#define ZL_REG_DPLL_TOD_CTRL(_idx) \ + ZL_REG_IDX(_idx, 5, 0x38, 1, ZL3073X_MAX_CHANNELS, 1) +#define ZL_DPLL_TOD_CTRL_SEM BIT(4) +#define ZL_DPLL_TOD_CTRL_CMD GENMASK(3, 0) +#define ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ 1 +#define ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT 8 +#define ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ 9 + +#define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1) +#define ZL_DPLL_MEAS_CTRL_EN BIT(0) +#define ZL_DPLL_MEAS_CTRL_AVG_FACTOR GENMASK(7, 4) + +#define ZL_REG_DPLL_MEAS_IDX ZL_REG(5, 0x51, 1) +#define ZL_DPLL_MEAS_IDX GENMASK(2, 0) + +#define ZL_REG_DPLL_PHASE_ERR_READ_MASK ZL_REG(5, 0x54, 1) + +#define ZL_REG_DPLL_PHASE_ERR_DATA(_idx) \ + ZL_REG_IDX(_idx, 5, 0x55, 6, ZL3073X_MAX_CHANNELS, 6) + +/******************************* + * Register Pages 6-7, DPLL Data + * + * Per-channel registers with stride 0x20. Channels 0-3 reside on page 6, + * channel 4 on page 7. + *******************************/ + +#define ZL_REG_DPLL_DF_OFFSET_03(_idx) \ + ZL_REG_IDX(_idx, 6, 0x00, 6, 4, 0x20) +#define ZL_REG_DPLL_DF_OFFSET_4 ZL_REG(7, 0x00, 6) +#define ZL_REG_DPLL_DF_OFFSET(_idx) \ + ((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4) +#define ZL_DPLL_DF_OFFSET_UNKNOWN S64_MIN + +#define ZL_REG_DPLL_TIE_DATA_03(_idx) \ + ZL_REG_IDX(_idx, 6, 0x0C, 6, 4, 0x20) +#define ZL_REG_DPLL_TIE_DATA_4 ZL_REG(7, 0x0C, 6) +#define ZL_REG_DPLL_TIE_DATA(_idx) \ + ((_idx) < 4 ? ZL_REG_DPLL_TIE_DATA_03(_idx) : ZL_REG_DPLL_TIE_DATA_4) + +#define ZL_REG_DPLL_TOD_SEC_03(_idx) \ + ZL_REG_IDX(_idx, 6, 0x12, 6, 4, 0x20) +#define ZL_REG_DPLL_TOD_SEC_4 ZL_REG(7, 0x12, 6) +#define ZL_REG_DPLL_TOD_SEC(_idx) \ + ((_idx) < 4 ? ZL_REG_DPLL_TOD_SEC_03(_idx) : ZL_REG_DPLL_TOD_SEC_4) + +#define ZL_REG_DPLL_TOD_NS_03(_idx) \ + ZL_REG_IDX(_idx, 6, 0x18, 4, 4, 0x20) +#define ZL_REG_DPLL_TOD_NS_4 ZL_REG(7, 0x18, 4) +#define ZL_REG_DPLL_TOD_NS(_idx) \ + ((_idx) < 4 ? ZL_REG_DPLL_TOD_NS_03(_idx) : ZL_REG_DPLL_TOD_NS_4) + +/*********************************** + * Register Page 9, Synth and Output + ***********************************/ + +#define ZL_REG_SYNTH_CTRL(_idx) \ + ZL_REG_IDX(_idx, 9, 0x00, 1, ZL3073X_NUM_SYNTHS, 1) +#define ZL_SYNTH_CTRL_EN BIT(0) +#define ZL_SYNTH_CTRL_DPLL_SEL GENMASK(6, 4) + +#define ZL_REG_SYNTH_PHASE_SHIFT_CTRL ZL_REG(9, 0x1e, 1) +#define ZL_REG_SYNTH_PHASE_SHIFT_MASK ZL_REG(9, 0x1f, 1) +#define ZL_REG_SYNTH_PHASE_SHIFT_INTVL ZL_REG(9, 0x20, 1) +#define ZL_REG_SYNTH_PHASE_SHIFT_DATA ZL_REG(9, 0x21, 2) + +#define ZL_REG_OUTPUT_CTRL(_idx) \ + ZL_REG_IDX(_idx, 9, 0x28, 1, ZL3073X_NUM_OUTS, 1) +#define ZL_OUTPUT_CTRL_EN BIT(0) +#define ZL_OUTPUT_CTRL_SYNTH_SEL GENMASK(6, 4) + +#define ZL_REG_OUTPUT_STEP_TIME_MASK ZL_REG(9, 0x36, 2) + +#define ZL_REG_OUTPUT_PHASE_STEP_CTRL ZL_REG(9, 0x38, 1) +#define ZL_OUTPUT_PHASE_STEP_CTRL_DPLL GENMASK(6, 4) +#define ZL_OUTPUT_PHASE_STEP_CTRL_TOD_STEP BIT(3) +#define ZL_OUTPUT_PHASE_STEP_CTRL_OP GENMASK(1, 0) +#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_NONE 0 +#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_RESET 1 +#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_READ 2 +#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_WRITE 3 + +#define ZL_REG_OUTPUT_PHASE_STEP_NUMBER ZL_REG(9, 0x39, 1) + +#define ZL_REG_OUTPUT_PHASE_STEP_MASK ZL_REG(9, 0x3a, 2) + +#define ZL_REG_OUTPUT_PHASE_STEP_DATA ZL_REG(9, 0x3c, 4) + +/******************************* + * Register Page 10, Ref Mailbox + *******************************/ + +#define ZL_REG_REF_MB_MASK ZL_REG(10, 0x02, 2) + +#define ZL_REG_REF_MB_SEM ZL_REG(10, 0x04, 1) +#define ZL_REF_MB_SEM_WR BIT(0) +#define ZL_REF_MB_SEM_RD BIT(1) + +#define ZL_REG_REF_FREQ_BASE ZL_REG(10, 0x05, 2) +#define ZL_REG_REF_FREQ_MULT ZL_REG(10, 0x07, 2) +#define ZL_REG_REF_RATIO_M ZL_REG(10, 0x09, 2) +#define ZL_REG_REF_RATIO_N ZL_REG(10, 0x0b, 2) + +#define ZL_REG_REF_CONFIG ZL_REG(10, 0x0d, 1) +#define ZL_REF_CONFIG_ENABLE BIT(0) +#define ZL_REF_CONFIG_DIFF_EN BIT(2) + +#define ZL_REG_REF_PHASE_OFFSET_COMP ZL_REG(10, 0x28, 6) +#define ZL_REG_REF_PHASE_OFFSET_COMP_32 ZL_REG(10, 0x28, 4) + +#define ZL_REG_REF_SYNC_CTRL ZL_REG(10, 0x2e, 1) +#define ZL_REF_SYNC_CTRL_MODE GENMASK(2, 0) +#define ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF 0 +#define ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR 1 +#define ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75 2 +#define ZL_REF_SYNC_CTRL_PAIR GENMASK(7, 4) + +#define ZL_REG_REF_ESYNC_DIV ZL_REG(10, 0x30, 4) +#define ZL_REF_ESYNC_DIV_1HZ 0 + +/******************************** + * Register Page 12, DPLL Mailbox + ********************************/ + +#define ZL_REG_DPLL_MB_MASK ZL_REG(12, 0x02, 2) + +#define ZL_REG_DPLL_MB_SEM ZL_REG(12, 0x04, 1) +#define ZL_DPLL_MB_SEM_WR BIT(0) +#define ZL_DPLL_MB_SEM_RD BIT(1) + +#define ZL_REG_DPLL_REF_PRIO(_idx) \ + ZL_REG_IDX(_idx, 12, 0x52, 1, ZL3073X_NUM_REFS / 2, 1) +#define ZL_DPLL_REF_PRIO_REF_P GENMASK(3, 0) +#define ZL_DPLL_REF_PRIO_REF_N GENMASK(7, 4) +#define ZL_DPLL_REF_PRIO_MAX 14 +#define ZL_DPLL_REF_PRIO_NONE 15 + +/********************************* + * Register Page 13, Synth Mailbox + *********************************/ + +#define ZL_REG_SYNTH_MB_MASK ZL_REG(13, 0x02, 2) + +#define ZL_REG_SYNTH_MB_SEM ZL_REG(13, 0x04, 1) +#define ZL_SYNTH_MB_SEM_WR BIT(0) +#define ZL_SYNTH_MB_SEM_RD BIT(1) + +#define ZL_REG_SYNTH_FREQ_BASE ZL_REG(13, 0x06, 2) +#define ZL_REG_SYNTH_FREQ_MULT ZL_REG(13, 0x08, 4) +#define ZL_REG_SYNTH_FREQ_M ZL_REG(13, 0x0c, 2) +#define ZL_REG_SYNTH_FREQ_N ZL_REG(13, 0x0e, 2) + +/********************************** + * Register Page 14, Output Mailbox + **********************************/ +#define ZL_REG_OUTPUT_MB_MASK ZL_REG(14, 0x02, 2) + +#define ZL_REG_OUTPUT_MB_SEM ZL_REG(14, 0x04, 1) +#define ZL_OUTPUT_MB_SEM_WR BIT(0) +#define ZL_OUTPUT_MB_SEM_RD BIT(1) + +#define ZL_REG_OUTPUT_MODE ZL_REG(14, 0x05, 1) +#define ZL_OUTPUT_MODE_CLOCK_TYPE GENMASK(2, 0) +#define ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL 0 +#define ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC 1 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT GENMASK(7, 4) +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_DISABLED 0 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_LVDS 1 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_DIFF 2 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_LOWVCM 3 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_2 4 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_1P 5 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_1N 6 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_INV 7 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV 12 +#define ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV 15 + +#define ZL_REG_OUTPUT_DIV ZL_REG(14, 0x0c, 4) +#define ZL_REG_OUTPUT_WIDTH ZL_REG(14, 0x10, 4) +#define ZL_REG_OUTPUT_ESYNC_PERIOD ZL_REG(14, 0x14, 4) +#define ZL_REG_OUTPUT_ESYNC_WIDTH ZL_REG(14, 0x18, 4) +#define ZL_REG_OUTPUT_PHASE_COMP ZL_REG(14, 0x20, 4) + +/* + * Register Page 255 - HW registers access + */ +#define ZL_REG_HWREG_OP ZL_REG(0xff, 0x00, 1) +#define ZL_HWREG_OP_WRITE 0x28 +#define ZL_HWREG_OP_READ 0x29 +#define ZL_HWREG_OP_PENDING BIT(1) + +#define ZL_REG_HWREG_ADDR ZL_REG(0xff, 0x04, 4) +#define ZL_REG_HWREG_WRITE_DATA ZL_REG(0xff, 0x08, 4) +#define ZL_REG_HWREG_READ_DATA ZL_REG(0xff, 0x0c, 4) + +/* + * Registers available in flash mode + */ +#define ZL_REG_FLASH_HASH ZL_REG(0, 0x78, 4) +#define ZL_REG_FLASH_FAMILY ZL_REG(0, 0x7c, 1) +#define ZL_REG_FLASH_RELEASE ZL_REG(0, 0x7d, 1) + +#define ZL_REG_HOST_CONTROL ZL_REG(1, 0x02, 1) +#define ZL_HOST_CONTROL_ENABLE BIT(0) + +#define ZL_REG_IMAGE_START_ADDR ZL_REG(1, 0x04, 4) +#define ZL_REG_IMAGE_SIZE ZL_REG(1, 0x08, 4) +#define ZL_REG_FLASH_INDEX_READ ZL_REG(1, 0x0c, 4) +#define ZL_REG_FLASH_INDEX_WRITE ZL_REG(1, 0x10, 4) +#define ZL_REG_FILL_PATTERN ZL_REG(1, 0x14, 4) + +#define ZL_REG_WRITE_FLASH ZL_REG(1, 0x18, 1) +#define ZL_WRITE_FLASH_OP GENMASK(2, 0) +#define ZL_WRITE_FLASH_OP_DONE 0x0 +#define ZL_WRITE_FLASH_OP_SECTORS 0x2 +#define ZL_WRITE_FLASH_OP_PAGE 0x3 +#define ZL_WRITE_FLASH_OP_COPY_PAGE 0x4 + +#define ZL_REG_FLASH_INFO ZL_REG(2, 0x00, 1) +#define ZL_FLASH_INFO_SECTOR_SIZE GENMASK(3, 0) +#define ZL_FLASH_INFO_SECTOR_4K 0 +#define ZL_FLASH_INFO_SECTOR_64K 1 + +#define ZL_REG_ERROR_COUNT ZL_REG(2, 0x04, 4) +#define ZL_REG_ERROR_CAUSE ZL_REG(2, 0x08, 4) + +#define ZL_REG_OP_STATE ZL_REG(2, 0x14, 1) +#define ZL_OP_STATE_NO_COMMAND 0 +#define ZL_OP_STATE_PENDING 1 +#define ZL_OP_STATE_DONE 2 + +#endif /* _ZL3073X_REGS_H */ diff --git a/drivers/dpll/zl3073x/spi.c b/drivers/dpll/zl3073x/spi.c new file mode 100644 index 000000000..f024f42b7 --- /dev/null +++ b/drivers/dpll/zl3073x/spi.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include "core.h" + +static int zl3073x_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct zl3073x_dev *zldev; + + zldev = zl3073x_devm_alloc(dev); + if (IS_ERR(zldev)) + return PTR_ERR(zldev); + + zldev->regmap = devm_regmap_init_spi(spi, &zl3073x_regmap_config); + if (IS_ERR(zldev->regmap)) + return dev_err_probe(dev, PTR_ERR(zldev->regmap), + "Failed to initialize regmap\n"); + + return zl3073x_dev_probe(zldev); +} + +static const struct spi_device_id zl3073x_spi_id[] = { + { "zl30731" }, + { "zl30732" }, + { "zl30733" }, + { "zl30734" }, + { "zl30735" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, zl3073x_spi_id); + +static const struct of_device_id zl3073x_spi_of_match[] = { + { .compatible = "microchip,zl30731" }, + { .compatible = "microchip,zl30732" }, + { .compatible = "microchip,zl30733" }, + { .compatible = "microchip,zl30734" }, + { .compatible = "microchip,zl30735" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, zl3073x_spi_of_match); + +static struct spi_driver zl3073x_spi_driver = { + .driver = { + .name = "zl3073x-spi", + .of_match_table = zl3073x_spi_of_match, + }, + .probe = zl3073x_spi_probe, + .id_table = zl3073x_spi_id, +}; +module_spi_driver(zl3073x_spi_driver); + +MODULE_AUTHOR("Ivan Vecera "); +MODULE_DESCRIPTION("Microchip ZL3073x SPI driver"); +MODULE_IMPORT_NS("ZL3073X"); +MODULE_LICENSE("GPL"); diff --git a/drivers/dpll/zl3073x/synth.c b/drivers/dpll/zl3073x/synth.c new file mode 100644 index 000000000..da839572d --- /dev/null +++ b/drivers/dpll/zl3073x/synth.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "synth.h" + +/** + * zl3073x_synth_state_fetch - fetch synth state from hardware + * @zldev: pointer to zl3073x_dev structure + * @index: synth index to fetch state for + * + * Function fetches state of the given synthesizer from the hardware and + * stores it for later use. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_synth *synth = &zldev->synth[index]; + int rc; + + /* Read synth control register */ + rc = zl3073x_read_u8(zldev, ZL_REG_SYNTH_CTRL(index), &synth->ctrl); + if (rc) + return rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Read synth configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_SYNTH_MB_SEM, ZL_SYNTH_MB_SEM_RD, + ZL_REG_SYNTH_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* The output frequency is determined by the following formula: + * base * multiplier * numerator / denominator + * + * Read registers with these values + */ + rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_BASE, &synth->freq_base); + if (rc) + return rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_SYNTH_FREQ_MULT, &synth->freq_mult); + if (rc) + return rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_M, &synth->freq_m); + if (rc) + return rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_N, &synth->freq_n); + if (rc) + return rc; + + /* Check denominator for zero to avoid div by 0 */ + if (!synth->freq_n) { + dev_err(zldev->dev, + "Zero divisor for SYNTH%u retrieved from device\n", + index); + return -EINVAL; + } + + dev_dbg(zldev->dev, "SYNTH%u frequency: %u Hz\n", index, + zl3073x_synth_freq_get(synth)); + + return rc; +} + +/** + * zl3073x_synth_state_get - get current synth state + * @zldev: pointer to zl3073x_dev structure + * @index: synth index to get state for + * + * Return: pointer to given synth state + */ +const struct zl3073x_synth *zl3073x_synth_state_get(struct zl3073x_dev *zldev, + u8 index) +{ + return &zldev->synth[index]; +} diff --git a/drivers/dpll/zl3073x/synth.h b/drivers/dpll/zl3073x/synth.h new file mode 100644 index 000000000..89e13ea2e --- /dev/null +++ b/drivers/dpll/zl3073x/synth.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_SYNTH_H +#define _ZL3073X_SYNTH_H + +#include +#include +#include +#include + +#include "regs.h" + +struct zl3073x_dev; + +/** + * struct zl3073x_synth - synthesizer state + * @freq_mult: frequency multiplier + * @freq_base: frequency base + * @freq_m: frequency numerator + * @freq_n: frequency denominator + * @ctrl: synth control + */ +struct zl3073x_synth { + 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); + +const struct zl3073x_synth *zl3073x_synth_state_get(struct zl3073x_dev *zldev, + u8 synth_id); + +/** + * zl3073x_synth_dpll_get - get DPLL ID the synth is driven by + * @synth: pointer to synth state + * + * Return: ID of DPLL the given synthetizer is driven by + */ +static inline u8 zl3073x_synth_dpll_get(const struct zl3073x_synth *synth) +{ + return FIELD_GET(ZL_SYNTH_CTRL_DPLL_SEL, synth->ctrl); +} + +/** + * zl3073x_synth_freq_get - get synth current freq + * @synth: pointer to synth state + * + * Return: frequency of given synthetizer + */ +static inline u32 zl3073x_synth_freq_get(const struct zl3073x_synth *synth) +{ + return mul_u64_u32_div(synth->freq_base * synth->freq_m, + synth->freq_mult, synth->freq_n); +} + +/** + * zl3073x_synth_is_enabled - check if the given synth is enabled + * @synth: pointer to synth state + * + * Return: true if synth is enabled, false otherwise + */ +static inline bool zl3073x_synth_is_enabled(const struct zl3073x_synth *synth) +{ + return FIELD_GET(ZL_SYNTH_CTRL_EN, synth->ctrl); +} + +#endif /* _ZL3073X_SYNTH_H */ -- cgit v1.3.1