/** * 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
*/ staticconststruct 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.
*/ staticbool
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
*/ staticbool
zl3073x_dpll_is_p_pin(struct zl3073x_dpll_pin *pin)
{ return zl3073x_is_p_pin(pin->id);
}
/** * 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
*/ staticint
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;
/* 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);
/* 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;
}
/* 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;
}
/** * 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
*/ staticint
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
*/ staticint
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;
} elseif (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;
}
/* 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
*/ staticint
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;
}
/* 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;
/* 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;
/** * 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
*/ staticint
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;
/* 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
*/ staticint
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);
}
/** * 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
*/ staticint
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 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;
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;
} elseif (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;
}
/* 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;
/* 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;
/* 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;
}
/* 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;
/* 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;
/* 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;
}
/* 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;
/* 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;
/* 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;
/* Write the requested value into the compensation register */
rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, phase_adjust); if (rc) return 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;
}
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;
}
/** * 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
*/ staticbool
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; constchar *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) returnfalse;
/* 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);
/* 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);
returnfalse;
}
/* 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');
returnfalse;
}
returntrue;
}
/** * 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
*/ staticint
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;
/** * 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
*/ staticbool
zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
{ struct zl3073x_dpll *zldpll = pin->dpll; struct zl3073x_dev *zldev = zldpll->dev; unsignedint 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);
} elseif (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));
returnfalse;
}
if (status != ZL_REF_MON_STATUS_OK) returnfalse;
reg = ZL_REG_REF_PHASE(ref);
} else { /* The pin is not connected or phase monitor disabled */ returnfalse;
}
/* 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));
returnfalse;
}
/* 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;
returntrue;
}
returnfalse;
}
/** * 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
*/ staticbool
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));
returnfalse;
}
/* Do not report ffo changes if the reference monitor report errors */ if (status != ZL_REF_MON_STATUS_OK) returnfalse;
/* 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;
returntrue;
}
returnfalse;
}
/** * 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;
}
}
/* 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);
/** * 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);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.