| // SPDX-License-Identifier: GPL-2.0-only | |
| #include <linux/cleanup.h> | |
| #include <linux/delay.h> | |
| #include <linux/dev_printk.h> | |
| #include <linux/ptp_clock_kernel.h> | |
| #include <linux/string.h> | |
| #include <linux/types.h> | |
| #include "chan.h" | |
| #include "core.h" | |
| /** | |
| * zl3073x_chan_state_update - update DPLL channel status from HW | |
| * @zldev: pointer to zl3073x_dev structure | |
| * @index: DPLL channel index | |
| * | |
| * Return: 0 on success, <0 on error | |
| */ | |
| int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) | |
| { | |
| struct zl3073x_chan *chan = &zldev->chan[index]; | |
| 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; | |
| } |