// SPDX-License-Identifier: GPL-2.0-only #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 * @label: package label * @dir: pin direction * @id: pin id * @prio: pin priority <0, 14> * @selectable: pin is selectable in automatic mode * @esync_control: embedded sync is controllable * @pin_state: last saved pin state * @phase_offset: last saved pin phase offset * @freq_offset: last saved fractional frequency offset */ struct zl3073x_dpll_pin { struct list_head list; struct zl3073x_dpll *dpll; struct dpll_pin *dpll_pin; char label[8]; enum dpll_pin_direction dir; u8 id; u8 prio; bool selectable; bool esync_control; enum dpll_pin_state pin_state; s64 phase_offset; s64 freq_offset; }; /* * 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_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; } /** * zl3073x_dpll_input_ref_frequency_get - get input reference frequency * @zldpll: pointer to zl3073x_dpll * @ref_id: reference id * @frequency: pointer to variable to store frequency * * Reads frequency of given input reference. * * Return: 0 on success, <0 on error */ static int zl3073x_dpll_input_ref_frequency_get(struct zl3073x_dpll *zldpll, u8 ref_id, u32 *frequency) { struct zl3073x_dev *zldev = zldpll->dev; u16 base, mult, num, denom; int 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(ref_id)); if (rc) return rc; /* Read registers to compute resulting frequency */ rc = zl3073x_read_u16(zldev, ZL_REG_REF_FREQ_BASE, &base); if (rc) return rc; rc = zl3073x_read_u16(zldev, ZL_REG_REF_FREQ_MULT, &mult); if (rc) return rc; rc = zl3073x_read_u16(zldev, ZL_REG_REF_RATIO_M, &num); if (rc) return rc; rc = zl3073x_read_u16(zldev, ZL_REG_REF_RATIO_N, &denom); if (rc) return rc; /* Sanity check that HW has not returned zero denominator */ if (!denom) { dev_err(zldev->dev, "Zero divisor for ref %u frequency got from device\n", ref_id); return -EINVAL; } /* Compute the frequency */ *frequency = mul_u64_u32_div(base * mult, num, denom); return rc; } 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; u8 ref, ref_sync_ctrl, sync_mode; u32 esync_div, ref_freq; int rc; /* Get reference frequency */ ref = zl3073x_input_pin_ref_get(pin->id); rc = zl3073x_dpll_input_ref_frequency_get(zldpll, pin->id, &ref_freq); if (rc) return rc; 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(ref)); if (rc) return rc; /* Get ref sync mode */ rc = zl3073x_read_u8(zldev, ZL_REG_REF_SYNC_CTRL, &ref_sync_ctrl); if (rc) return rc; /* Get esync divisor */ rc = zl3073x_read_u32(zldev, ZL_REG_REF_ESYNC_DIV, &esync_div); if (rc) return rc; sync_mode = FIELD_GET(ZL_REF_SYNC_CTRL_MODE, ref_sync_ctrl); switch (sync_mode) { case ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75: esync->freq = (esync_div == ZL_REF_ESYNC_DIV_1HZ) ? 1 : 0; esync->pulse = 25; break; default: esync->freq = 0; esync->pulse = 0; break; } /* If the pin supports esync control expose its range but only * if the current reference frequency is > 1 Hz. */ if (pin->esync_control && ref_freq > 1) { esync->range = esync_freq_ranges; esync->range_num = ARRAY_SIZE(esync_freq_ranges); } else { esync->range = NULL; esync->range_num = 0; } return rc; } 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; u8 ref, ref_sync_ctrl, sync_mode; int rc; guard(mutex)(&zldev->multiop_lock); /* Read reference configuration into mailbox */ ref = zl3073x_input_pin_ref_get(pin->id); rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, ZL_REG_REF_MB_MASK, BIT(ref)); if (rc) return rc; /* Get ref sync mode */ rc = zl3073x_read_u8(zldev, ZL_REG_REF_SYNC_CTRL, &ref_sync_ctrl); if (rc) return rc; /* 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; ref_sync_ctrl &= ~ZL_REF_SYNC_CTRL_MODE; ref_sync_ctrl |= FIELD_PREP(ZL_REF_SYNC_CTRL_MODE, sync_mode); /* Update ref sync control register */ rc = zl3073x_write_u8(zldev, ZL_REG_REF_SYNC_CTRL, ref_sync_ctrl); if (rc) return rc; if (freq) { /* 1 Hz is only supported frequnecy currently */ rc = zl3073x_write_u32(zldev, ZL_REG_REF_ESYNC_DIV, ZL_REF_ESYNC_DIV_1HZ); if (rc) return rc; } /* Commit reference configuration */ return zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR, ZL_REG_REF_MB_MASK, BIT(ref)); } 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, s64 *ffo, struct netlink_ext_ack *extack) { struct zl3073x_dpll_pin *pin = pin_priv; *ffo = pin->freq_offset; 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; u32 ref_freq; u8 ref; int rc; /* Read and return ref frequency */ ref = zl3073x_input_pin_ref_get(pin->id); rc = zl3073x_dpll_input_ref_frequency_get(zldpll, ref, &ref_freq); if (!rc) *frequency = ref_freq; return rc; } 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; u16 base, mult; u8 ref; int rc; /* Get base frequency and multiplier for the requested frequency */ rc = zl3073x_ref_freq_factorize(frequency, &base, &mult); if (rc) return rc; guard(mutex)(&zldev->multiop_lock); /* Load reference configuration */ ref = zl3073x_input_pin_ref_get(pin->id); rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, ZL_REG_REF_MB_MASK, BIT(ref)); /* Update base frequency, multiplier, numerator & denominator */ rc = zl3073x_write_u16(zldev, ZL_REG_REF_FREQ_BASE, base); if (rc) return rc; rc = zl3073x_write_u16(zldev, ZL_REG_REF_FREQ_MULT, mult); if (rc) return rc; rc = zl3073x_write_u16(zldev, ZL_REG_REF_RATIO_M, 1); if (rc) return rc; rc = zl3073x_write_u16(zldev, ZL_REG_REF_RATIO_N, 1); if (rc) return rc; /* Commit reference configuration */ return zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR, ZL_REG_REF_MB_MASK, BIT(ref)); } /** * zl3073x_dpll_selected_ref_get - get currently selected reference * @zldpll: pointer to zl3073x_dpll * @ref: place to store selected reference * * Check for currently selected reference the DPLL should be locked to * and stores its index to given @ref. * * Return: 0 on success, <0 on error */ static int zl3073x_dpll_selected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref) { struct zl3073x_dev *zldev = zldpll->dev; u8 state, value; int rc; switch (zldpll->refsel_mode) { case ZL_DPLL_MODE_REFSEL_MODE_AUTO: /* For automatic mode read refsel_status register */ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(zldpll->id), &value); if (rc) return rc; /* Extract reference state */ state = FIELD_GET(ZL_DPLL_REFSEL_STATUS_STATE, value); /* Return the reference only if the DPLL is locked to it */ if (state == ZL_DPLL_REFSEL_STATUS_STATE_LOCK) *ref = FIELD_GET(ZL_DPLL_REFSEL_STATUS_REFSEL, value); else *ref = ZL3073X_DPLL_REF_NONE; break; case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: /* For manual mode return stored value */ *ref = zldpll->forced_ref; break; default: /* For other modes like NCO, freerun... there is no input ref */ *ref = ZL3073X_DPLL_REF_NONE; break; } return 0; } /** * zl3073x_dpll_selected_ref_set - select reference in manual mode * @zldpll: pointer to zl3073x_dpll * @ref: input reference to be selected * * Selects the given reference for the DPLL channel it should be * locked to. * * Return: 0 on success, <0 on error */ static int zl3073x_dpll_selected_ref_set(struct zl3073x_dpll *zldpll, u8 ref) { struct zl3073x_dev *zldev = zldpll->dev; u8 mode, mode_refsel; int rc; mode = zldpll->refsel_mode; switch (mode) { case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: /* Manual mode with ref selected */ if (ref == ZL3073X_DPLL_REF_NONE) { switch (zldpll->lock_status) { case DPLL_LOCK_STATUS_LOCKED_HO_ACQ: case DPLL_LOCK_STATUS_HOLDOVER: /* Switch to forced holdover */ mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER; break; default: /* Switch to freerun */ mode = ZL_DPLL_MODE_REFSEL_MODE_FREERUN; break; } /* Keep selected reference */ ref = zldpll->forced_ref; } else if (ref == zldpll->forced_ref) { /* No register update - same mode and same ref */ return 0; } break; case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER: /* Manual mode without no ref */ if (ref == ZL3073X_DPLL_REF_NONE) /* No register update - keep current mode */ return 0; /* Switch to reflock mode and update ref selection */ mode = ZL_DPLL_MODE_REFSEL_MODE_REFLOCK; break; default: /* For other modes like automatic or NCO ref cannot be selected * manually */ return -EOPNOTSUPP; } /* Build mode_refsel value */ mode_refsel = FIELD_PREP(ZL_DPLL_MODE_REFSEL_MODE, mode) | FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref); /* Update dpll_mode_refsel register */ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id), mode_refsel); if (rc) return rc; /* Store new mode and forced reference */ zldpll->refsel_mode = mode; zldpll->forced_ref = ref; return rc; } /** * zl3073x_dpll_connected_ref_get - get currently connected reference * @zldpll: pointer to zl3073x_dpll * @ref: place to store selected reference * * Looks for currently connected the DPLL is locked to and stores its index * to given @ref. * * Return: 0 on success, <0 on error */ static int zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref) { struct zl3073x_dev *zldev = zldpll->dev; int rc; /* Get currently selected input reference */ rc = zl3073x_dpll_selected_ref_get(zldpll, ref); if (rc) return rc; if (ZL3073X_DPLL_REF_IS_VALID(*ref)) { u8 ref_status; /* Read the reference monitor status */ rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(*ref), &ref_status); if (rc) return rc; /* If the monitor indicates an error nothing is connected */ if (ref_status != ZL_REF_MON_STATUS_OK) *ref = ZL3073X_DPLL_REF_NONE; } return 0; } 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; u8 conn_ref, ref, ref_status; s64 ref_phase; int rc; /* Get currently connected reference */ rc = zl3073x_dpll_connected_ref_get(zldpll, &conn_ref); if (rc) return rc; /* Report phase offset only for currently connected pin if the phase * monitor feature is disabled. */ ref = zl3073x_input_pin_ref_get(pin->id); if (!zldpll->phase_monitor && ref != conn_ref) { *phase_offset = 0; return 0; } /* Get this pin monitor status */ rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref), &ref_status); if (rc) return rc; /* Report phase offset only if the input pin signal is present */ if (ref_status != ZL_REF_MON_STATUS_OK) { *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_ref) && conn_ref != ref) { u32 conn_freq, ref_freq; /* Get frequency of connected ref */ rc = zl3073x_dpll_input_ref_frequency_get(zldpll, conn_ref, &conn_freq); if (rc) return rc; /* Get frequency of given ref */ rc = zl3073x_dpll_input_ref_frequency_get(zldpll, ref, &ref_freq); if (rc) return rc; 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 rc; } 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; s64 phase_comp; u8 ref; int rc; guard(mutex)(&zldev->multiop_lock); /* Read reference configuration */ ref = zl3073x_input_pin_ref_get(pin->id); rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, ZL_REG_REF_MB_MASK, BIT(ref)); if (rc) return rc; /* Read current phase offset compensation */ rc = zl3073x_read_u48(zldev, ZL_REG_REF_PHASE_OFFSET_COMP, &phase_comp); if (rc) return rc; /* Perform sign extension for 48bit signed value */ phase_comp = sign_extend64(phase_comp, 47); /* Reverse two's complement negation applied during set and convert * to 32bit signed int */ *phase_adjust = (s32)-phase_comp; return rc; } 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; s64 phase_comp; u8 ref; int rc; /* The value in the register is stored as two's complement negation * of requested value. */ phase_comp = -phase_adjust; guard(mutex)(&zldev->multiop_lock); /* Read reference configuration */ ref = zl3073x_input_pin_ref_get(pin->id); rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, ZL_REG_REF_MB_MASK, BIT(ref)); if (rc) return rc; /* Write the requested value into the compensation register */ rc = zl3073x_write_u48(zldev, ZL_REG_REF_PHASE_OFFSET_COMP, phase_comp); if (rc) return rc; /* Commit reference configuration */ return zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_WR, ZL_REG_REF_MB_MASK, BIT(ref)); } /** * zl3073x_dpll_ref_prio_get - get priority for given input pin * @pin: pointer to pin * @prio: place to store priority * * Reads current priority for the given input pin and stores the value * to @prio. * * Return: 0 on success, <0 on error */ static int zl3073x_dpll_ref_prio_get(struct zl3073x_dpll_pin *pin, u8 *prio) { struct zl3073x_dpll *zldpll = pin->dpll; struct zl3073x_dev *zldev = zldpll->dev; u8 ref, ref_prio; int rc; guard(mutex)(&zldev->multiop_lock); /* Read DPLL configuration */ rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD, ZL_REG_DPLL_MB_MASK, BIT(zldpll->id)); if (rc) return rc; /* Read reference priority - one value for P&N pins (4 bits/pin) */ ref = zl3073x_input_pin_ref_get(pin->id); rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(ref / 2), &ref_prio); if (rc) return rc; /* Select nibble according pin type */ if (zl3073x_dpll_is_p_pin(pin)) *prio = FIELD_GET(ZL_DPLL_REF_PRIO_REF_P, ref_prio); else *prio = FIELD_GET(ZL_DPLL_REF_PRIO_REF_N, ref_prio); return rc; } /** * zl3073x_dpll_ref_prio_set - set priority for given input pin * @pin: pointer to pin * @prio: place to store priority * * Sets priority for the given input pin. * * Return: 0 on success, <0 on error */ static int zl3073x_dpll_ref_prio_set(struct zl3073x_dpll_pin *pin, u8 prio) { struct zl3073x_dpll *zldpll = pin->dpll; struct zl3073x_dev *zldev = zldpll->dev; u8 ref, ref_prio; int rc; guard(mutex)(&zldev->multiop_lock); /* Read DPLL configuration into mailbox */ rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD, ZL_REG_DPLL_MB_MASK, BIT(zldpll->id)); if (rc) return rc; /* Read reference priority - one value shared between P&N pins */ ref = zl3073x_input_pin_ref_get(pin->id); rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(ref / 2), &ref_prio); if (rc) return rc; /* Update nibble according pin type */ if (zl3073x_dpll_is_p_pin(pin)) { ref_prio &= ~ZL_DPLL_REF_PRIO_REF_P; ref_prio |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_P, prio); } else { ref_prio &= ~ZL_DPLL_REF_PRIO_REF_N; ref_prio |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_N, prio); } /* Update reference priority */ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_REF_PRIO(ref / 2), ref_prio); if (rc) return rc; /* Commit configuration */ return zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_WR, ZL_REG_DPLL_MB_MASK, BIT(zldpll->id)); } /** * zl3073x_dpll_ref_state_get - get status for given input pin * @pin: pointer to pin * @state: place to store status * * Checks current status for the given input pin and stores the value * to @state. * * Return: 0 on success, <0 on error */ static int zl3073x_dpll_ref_state_get(struct zl3073x_dpll_pin *pin, enum dpll_pin_state *state) { struct zl3073x_dpll *zldpll = pin->dpll; struct zl3073x_dev *zldev = zldpll->dev; u8 ref, ref_conn, status; int rc; ref = zl3073x_input_pin_ref_get(pin->id); /* Get currently connected reference */ rc = zl3073x_dpll_connected_ref_get(zldpll, &ref_conn); if (rc) return rc; if (ref == ref_conn) { *state = DPLL_PIN_STATE_CONNECTED; return 0; } /* If the DPLL is running in automatic mode and the reference is * selectable and its monitor does not report any error then report * pin as selectable. */ if (zldpll->refsel_mode == ZL_DPLL_MODE_REFSEL_MODE_AUTO && pin->selectable) { /* Read reference monitor status */ rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref), &status); if (rc) return rc; /* If the monitor indicates errors report the reference * as disconnected */ if (status == ZL_REF_MON_STATUS_OK) { *state = DPLL_PIN_STATE_SELECTABLE; return 0; } } /* Otherwise report the pin as disconnected */ *state = DPLL_PIN_STATE_DISCONNECTED; 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_pin *pin = pin_priv; return zl3073x_dpll_ref_state_get(pin, state); } 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; u8 new_ref; int rc; switch (zldpll->refsel_mode) { case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER: if (state == DPLL_PIN_STATE_CONNECTED) { /* Choose the pin as new selected reference */ new_ref = zl3073x_input_pin_ref_get(pin->id); } else if (state == DPLL_PIN_STATE_DISCONNECTED) { /* No reference */ new_ref = ZL3073X_DPLL_REF_NONE; } else { NL_SET_ERR_MSG_MOD(extack, "Invalid pin state for manual mode"); return -EINVAL; } rc = zl3073x_dpll_selected_ref_set(zldpll, new_ref); break; case ZL_DPLL_MODE_REFSEL_MODE_AUTO: if (state == DPLL_PIN_STATE_SELECTABLE) { if (pin->selectable) return 0; /* Pin is already selectable */ /* Restore pin priority in HW */ rc = zl3073x_dpll_ref_prio_set(pin, pin->prio); if (rc) return rc; /* Mark pin as selectable */ pin->selectable = true; } else if (state == DPLL_PIN_STATE_DISCONNECTED) { if (!pin->selectable) return 0; /* Pin is already disconnected */ /* Set pin priority to none in HW */ rc = zl3073x_dpll_ref_prio_set(pin, ZL_DPLL_REF_PRIO_NONE); if (rc) return rc; /* Mark pin as non-selectable */ pin->selectable = false; } else { NL_SET_ERR_MSG(extack, "Invalid pin state for automatic mode"); return -EINVAL; } break; default: /* In other modes we cannot change input reference */ NL_SET_ERR_MSG(extack, "Pin state cannot be changed in current mode"); rc = -EOPNOTSUPP; break; } return 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_pin *pin = pin_priv; *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_pin *pin = pin_priv; int rc; if (prio > ZL_DPLL_REF_PRIO_MAX) return -EINVAL; /* If the pin is selectable then update HW registers */ if (pin->selectable) { rc = zl3073x_dpll_ref_prio_set(pin, prio); 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; struct device *dev = zldev->dev; u32 esync_period, esync_width; u8 clock_type, synth; u8 out, output_mode; u32 output_div; u32 synth_freq; int rc; out = zl3073x_output_pin_out_get(pin->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. */ switch (zl3073x_out_signal_format_get(zldev, out)) { case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: return -EOPNOTSUPP; default: break; } 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(out)); if (rc) return rc; /* Read output mode */ rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode); if (rc) return rc; /* Read output divisor */ rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div); if (rc) return rc; /* Check output divisor for zero */ if (!output_div) { dev_err(dev, "Zero divisor for OUTPUT%u got from device\n", out); return -EINVAL; } /* Get synth attached to output pin */ synth = zl3073x_out_synth_get(zldev, out); /* Get synth frequency */ synth_freq = zl3073x_synth_freq_get(zldev, synth); clock_type = FIELD_GET(ZL_OUTPUT_MODE_CLOCK_TYPE, output_mode); if (clock_type != ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC) { /* No need to read esync data if it is not enabled */ esync->freq = 0; esync->pulse = 0; goto finish; } /* Read esync period */ rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, &esync_period); if (rc) return rc; /* Check esync divisor for zero */ if (!esync_period) { dev_err(dev, "Zero esync divisor for OUTPUT%u got from device\n", out); return -EINVAL; } /* Get esync pulse width in units of half synth cycles */ rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, &esync_width); if (rc) return rc; /* Compute esync frequency */ esync->freq = synth_freq / output_div / esync_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 * esync_width) / output_div; finish: /* Set supported esync ranges if the pin supports esync control and * if the output frequency is > 1 Hz. */ if (pin->esync_control && (synth_freq / output_div) > 1) { esync->range = esync_freq_ranges; esync->range_num = ARRAY_SIZE(esync_freq_ranges); } else { esync->range = NULL; esync->range_num = 0; } 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) { u32 esync_period, esync_width, output_div; struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dev *zldev = zldpll->dev; struct zl3073x_dpll_pin *pin = pin_priv; u8 clock_type, out, output_mode, synth; u32 synth_freq; int rc; out = zl3073x_output_pin_out_get(pin->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. */ switch (zl3073x_out_signal_format_get(zldev, out)) { case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: return -EOPNOTSUPP; default: break; } 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(out)); if (rc) return rc; /* Read output mode */ rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode); if (rc) return rc; /* Select clock type */ if (freq) clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC; else clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL; /* Update clock type in output mode */ output_mode &= ~ZL_OUTPUT_MODE_CLOCK_TYPE; output_mode |= FIELD_PREP(ZL_OUTPUT_MODE_CLOCK_TYPE, clock_type); rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_MODE, output_mode); if (rc) return rc; /* If esync is being disabled just write mailbox and finish */ if (!freq) goto write_mailbox; /* Get synth attached to output pin */ synth = zl3073x_out_synth_get(zldev, out); /* Get synth frequency */ synth_freq = zl3073x_synth_freq_get(zldev, synth); rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div); if (rc) return rc; /* Check output divisor for zero */ if (!output_div) { dev_err(zldev->dev, "Zero divisor for OUTPUT%u got from device\n", out); return -EINVAL; } /* Compute and update esync period */ esync_period = synth_freq / (u32)freq / output_div; rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, esync_period); if (rc) return rc; /* 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. */ esync_width = output_div / 2; rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, esync_width); if (rc) return rc; write_mailbox: /* Commit output configuration */ return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, ZL_REG_OUTPUT_MB_MASK, BIT(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_dev *zldev = zldpll->dev; struct zl3073x_dpll_pin *pin = pin_priv; struct device *dev = zldev->dev; u8 out, signal_format, synth; u32 output_div, synth_freq; int rc; out = zl3073x_output_pin_out_get(pin->id); synth = zl3073x_out_synth_get(zldev, out); synth_freq = zl3073x_synth_freq_get(zldev, synth); 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(out)); if (rc) return rc; rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div); if (rc) return rc; /* Check output divisor for zero */ if (!output_div) { dev_err(dev, "Zero divisor for output %u got from device\n", out); return -EINVAL; } /* Read used signal format for the given output */ signal_format = zl3073x_out_signal_format_get(zldev, out); switch (signal_format) { case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: /* In case of divided format we have to distiguish between * given output pin type. */ if (zl3073x_dpll_is_p_pin(pin)) { /* For P-pin the resulting frequency is computed as * simple division of synth frequency and output * divisor. */ *frequency = synth_freq / output_div; } else { /* For N-pin we have to divide additionally by * divisor stored in esync_period output mailbox * register that is used as N-pin divisor for these * modes. */ u32 ndiv; rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, &ndiv); if (rc) return rc; /* Check N-pin divisor for zero */ if (!ndiv) { dev_err(dev, "Zero N-pin divisor for output %u got from device\n", out); return -EINVAL; } /* Compute final divisor for N-pin */ *frequency = synth_freq / output_div / ndiv; } break; default: /* In other modes the resulting frequency is computed as * division of synth frequency and output divisor. */ *frequency = synth_freq / output_div; break; } return rc; } 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; struct device *dev = zldev->dev; u32 output_n_freq, output_p_freq; u8 out, signal_format, synth; u32 cur_div, new_div, ndiv; u32 synth_freq; int rc; out = zl3073x_output_pin_out_get(pin->id); synth = zl3073x_out_synth_get(zldev, out); synth_freq = zl3073x_synth_freq_get(zldev, synth); new_div = synth_freq / (u32)frequency; /* Get used signal format for the given output */ signal_format = zl3073x_out_signal_format_get(zldev, out); guard(mutex)(&zldev->multiop_lock); /* Load output configuration */ rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, ZL_REG_OUTPUT_MB_MASK, BIT(out)); if (rc) return rc; /* Check signal format */ if (signal_format != ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV && signal_format != ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV) { /* For non N-divided signal formats the frequency is computed * as division of synth frequency and output divisor. */ rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_DIV, new_div); if (rc) return rc; /* For 50/50 duty cycle the divisor is equal to width */ rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_WIDTH, new_div); if (rc) return rc; /* Commit output configuration */ return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, ZL_REG_OUTPUT_MB_MASK, BIT(out)); } /* For N-divided signal format get current divisor */ rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &cur_div); if (rc) return rc; /* Check output divisor for zero */ if (!cur_div) { dev_err(dev, "Zero divisor for output %u got from device\n", out); return -EINVAL; } /* Get N-pin divisor (shares the same register with esync */ rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, &ndiv); if (rc) return rc; /* Check N-pin divisor for zero */ if (!ndiv) { dev_err(dev, "Zero N-pin divisor for output %u got from device\n", out); return -EINVAL; } /* Compute current output frequency for P-pin */ output_p_freq = synth_freq / cur_div; /* Compute current N-pin frequency */ output_n_freq = output_p_freq / ndiv; 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. */ if (frequency <= output_n_freq) return -EINVAL; /* Update the output divisor */ rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_DIV, new_div); if (rc) return rc; /* For 50/50 duty cycle the divisor is equal to width */ rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_WIDTH, new_div); if (rc) return rc; /* Compute new divisor for N-pin */ ndiv = (u32)frequency / output_n_freq; } 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. */ if (output_p_freq <= frequency) return -EINVAL; /* Compute new divisor for N-pin */ ndiv = output_p_freq / (u32)frequency; } /* Update divisor for the N-pin */ rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, ndiv); if (rc) return rc; /* For 50/50 duty cycle the divisor is equal to width */ rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, ndiv); if (rc) return rc; /* Commit output configuration */ return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, ZL_REG_OUTPUT_MB_MASK, BIT(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; u32 synth_freq; s32 phase_comp; u8 out, synth; int rc; out = zl3073x_output_pin_out_get(pin->id); synth = zl3073x_out_synth_get(zldev, out); synth_freq = zl3073x_synth_freq_get(zldev, synth); /* Check synth freq for zero */ if (!synth_freq) { dev_err(zldev->dev, "Got zero synth frequency for output %u\n", out); return -EINVAL; } 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(out)); if (rc) return rc; /* Read current output phase compensation */ rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, &phase_comp); if (rc) return rc; /* Value in register is expressed in half synth clock cycles */ phase_comp *= (int)div_u64(PSEC_PER_SEC, 2 * synth_freq); /* Reverse two's complement negation applied during 'set' */ *phase_adjust = -phase_comp; return rc; } 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; int half_synth_cycle; u32 synth_freq; u8 out, synth; int rc; /* Get attached synth */ out = zl3073x_output_pin_out_get(pin->id); synth = zl3073x_out_synth_get(zldev, out); /* Get synth's frequency */ synth_freq = zl3073x_synth_freq_get(zldev, synth); /* Value in register is expressed in half synth clock cycles so * the given phase adjustment a multiple of half synth clock. */ half_synth_cycle = (int)div_u64(PSEC_PER_SEC, 2 * synth_freq); if ((phase_adjust % half_synth_cycle) != 0) { NL_SET_ERR_MSG_FMT(extack, "Phase adjustment value has to be multiple of %d", half_synth_cycle); return -EINVAL; } phase_adjust /= half_synth_cycle; /* The value in the register is stored as two's complement negation * of requested value. */ phase_adjust = -phase_adjust; 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(out)); if (rc) return rc; /* Write the requested value into the compensation register */ rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, phase_adjust); if (rc) return rc; /* Update output configuration from mailbox */ return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, ZL_REG_OUTPUT_MB_MASK, BIT(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_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; struct zl3073x_dev *zldev = zldpll->dev; u8 mon_status, state; int rc; switch (zldpll->refsel_mode) { 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; } /* Read DPLL monitor status */ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(zldpll->id), &mon_status); if (rc) return rc; state = FIELD_GET(ZL_DPLL_MON_STATUS_STATE, mon_status); switch (state) { case ZL_DPLL_MON_STATUS_STATE_LOCK: if (FIELD_GET(ZL_DPLL_MON_STATUS_HO_READY, mon_status)) *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(zldev->dev, "Unknown DPLL monitor status: 0x%02x\n", mon_status); *status = DPLL_LOCK_STATUS_UNLOCKED; break; } 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; switch (zldpll->refsel_mode) { 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_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 (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; zldpll->phase_monitor = (state == DPLL_FEATURE_STATE_ENABLE); return 0; } static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = { .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, .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, .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_device_ops zl3073x_dpll_device_ops = { .lock_status_get = zl3073x_dpll_lock_status_get, .mode_get = zl3073x_dpll_mode_get, .phase_offset_monitor_get = zl3073x_dpll_phase_offset_monitor_get, .phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set, }; /** * 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(sizeof(*pin), GFP_KERNEL); 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 & esync capability */ strscpy(pin->label, props->package_label); pin->esync_control = props->esync_control; if (zl3073x_dpll_is_input_pin(pin)) { rc = zl3073x_dpll_ref_prio_get(pin, &pin->prio); if (rc) goto err_prio_get; if (pin->prio == ZL_DPLL_REF_PRIO_NONE) { /* Clamp prio to max value & mark pin non-selectable */ pin->prio = ZL_DPLL_REF_PRIO_MAX; pin->selectable = false; } else { /* Mark pin as selectable */ pin->selectable = true; } } /* Create or get existing DPLL pin */ pin->dpll_pin = dpll_pin_get(zldpll->dev->clock_id, index, THIS_MODULE, &props->dpll_props); if (IS_ERR(pin->dpll_pin)) { rc = PTR_ERR(pin->dpll_pin); goto err_pin_get; } 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); err_prio_get: pin->dpll_pin = NULL; err_pin_get: 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_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->dpll_pin = 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 = zl3073x_input_pin_ref_get(index); name = "REF"; /* Skip the pin if the DPLL is running in NCO mode */ if (zldpll->refsel_mode == ZL_DPLL_MODE_REFSEL_MODE_NCO) return false; is_diff = zl3073x_ref_is_diff(zldev, ref); is_enabled = zl3073x_ref_is_enabled(zldev, ref); } else { /* Output P&N pair shares single HW output */ u8 out = zl3073x_output_pin_out_get(index); name = "OUT"; /* Skip the pin if it is connected to different DPLL channel */ if (zl3073x_out_dpll_get(zldev, out) != zldpll->id) { dev_dbg(zldev->dev, "%s%u is driven by different DPLL\n", name, out); return false; } is_diff = zl3073x_out_is_diff(zldev, out); is_enabled = zl3073x_out_is_enabled(zldev, out); } /* 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; } /** * 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) goto error; list_add(&pin->list, &zldpll->pins); } 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; u8 dpll_mode_refsel; int rc; /* Read DPLL mode and forcibly selected reference */ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id), &dpll_mode_refsel); if (rc) return rc; /* Extract mode and selected input reference */ zldpll->refsel_mode = FIELD_GET(ZL_DPLL_MODE_REFSEL_MODE, dpll_mode_refsel); zldpll->forced_ref = FIELD_GET(ZL_DPLL_MODE_REFSEL_REF, dpll_mode_refsel); zldpll->dpll_dev = dpll_device_get(zldev->clock_id, zldpll->id, THIS_MODULE); if (IS_ERR(zldpll->dpll_dev)) { rc = PTR_ERR(zldpll->dpll_dev); zldpll->dpll_dev = NULL; return rc; } rc = dpll_device_register(zldpll->dpll_dev, zl3073x_prop_dpll_type_get(zldev, zldpll->id), &zl3073x_dpll_device_ops, zldpll); if (rc) { dpll_device_put(zldpll->dpll_dev); 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) { WARN(!zldpll->dpll_dev, "DPLL device is not registered\n"); dpll_device_unregister(zldpll->dpll_dev, &zl3073x_dpll_device_ops, zldpll); dpll_device_put(zldpll->dpll_dev); zldpll->dpll_dev = NULL; } /** * 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; int rc; ref = zl3073x_input_pin_ref_get(pin->id); /* 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 and reference monitor does not * report signal errors for given input pin */ if (pin->pin_state == DPLL_PIN_STATE_CONNECTED) { reg = ZL_REG_DPLL_PHASE_ERR_DATA(zldpll->id); } else if (zldpll->phase_monitor) { u8 status; /* Get reference monitor status */ rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref), &status); if (rc) { dev_err(zldev->dev, "Failed to read %s refmon status: %pe\n", pin->label, ERR_PTR(rc)); return false; } if (status != ZL_REF_MON_STATUS_OK) return false; reg = ZL_REG_REF_PHASE(ref); } else { /* The pin is not connected or phase monitor disabled */ 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 pin fractional frequency offset change * @pin: pin to check * * Check for the given pin's fractional frequency change. * * Return: true on fractional frequency offset 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; u8 ref, status; s64 ffo; int rc; /* Get reference monitor status */ ref = zl3073x_input_pin_ref_get(pin->id); rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(ref), &status); if (rc) { dev_err(zldev->dev, "Failed to read %s refmon status: %pe\n", pin->label, ERR_PTR(rc)); return false; } /* Do not report ffo changes if the reference monitor report errors */ if (status != ZL_REF_MON_STATUS_OK) return false; /* Get the latest measured ref's ffo */ ffo = zl3073x_ref_ffo_get(zldev, ref); /* Compare with previous value */ if (pin->freq_offset != ffo) { dev_dbg(zldev->dev, "%s freq offset changed: %lld -> %lld\n", pin->label, pin->freq_offset, ffo); pin->freq_offset = ffo; 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) { struct zl3073x_dev *zldev = zldpll->dev; enum dpll_lock_status lock_status; struct device *dev = zldev->dev; struct zl3073x_dpll_pin *pin; int rc; zldpll->check_count++; /* Get current lock status for the DPLL */ rc = zl3073x_dpll_lock_status_get(zldpll->dpll_dev, zldpll, &lock_status, NULL, NULL); if (rc) { dev_err(dev, "Failed to get DPLL%u lock status: %pe\n", zldpll->id, ERR_PTR(rc)); return; } /* If lock status was changed then notify DPLL core */ if (zldpll->lock_status != lock_status) { zldpll->lock_status = lock_status; dpll_device_change_ntf(zldpll->dpll_dev); } /* Input pin monitoring does make sense only in automatic * or forced reference modes. */ if (zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_AUTO && zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_REFLOCK) return; /* 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)); return; } } list_for_each_entry(pin, &zldpll->pins, list) { enum dpll_pin_state state; bool pin_changed = false; /* Output pins change checks are not necessary because output * states are constant. */ if (!zl3073x_dpll_is_input_pin(pin)) continue; rc = zl3073x_dpll_ref_state_get(pin, &state); if (rc) { dev_err(dev, "Failed to get %s on DPLL%u state: %pe\n", pin->label, zldpll->id, ERR_PTR(rc)); return; } if (state != pin->pin_state) { dev_dbg(dev, "%s state changed: %u->%u\n", pin->label, pin->pin_state, state); pin->pin_state = state; pin_changed = true; } /* Check for phase offset and ffo change once per second */ if (zldpll->check_count % 2 == 0) { if (zl3073x_dpll_pin_phase_offset_check(pin)) pin_changed = true; if (zl3073x_dpll_pin_ffo_check(pin)) pin_changed = true; } if (pin_changed) 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; rc = zl3073x_write_u8(zldev, ZL_REG_SYNTH_PHASE_SHIFT_CTRL, 0x01); if (rc) return rc; return rc; } /** * 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(sizeof(*zldpll), GFP_KERNEL); if (!zldpll) return ERR_PTR(-ENOMEM); zldpll->dev = zldev; zldpll->id = ch; 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"); 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; } 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) { /* Unregister all pins and dpll */ zl3073x_dpll_pins_unregister(zldpll); zl3073x_dpll_device_unregister(zldpll); }