For N-divided outputs the P-pin and N-pin share the output divisor and
the N-pin frequency is synth_freq / (div * esync_n_period). The frequency
set helper computed esync_n_period with a truncating division and only
rejected a zero result, so a frequency that does not divide evenly was
silently rounded. Setting the P-pin could then shift the N-pin frequency
too (e.g. 600 MHz synth, div=60, period=10 gives P=10 MHz, N=1 MHz;
setting P to 2.5 MHz moves N to 1.25 MHz). When setting the P-pin, the
product esync_n_period * div used for the rescale was computed in 32
bits and could wrap, so a wrong N divider was stored for low N-pin
frequencies.
Require esync_n_period >= 2 so the N-pin frequency stays below the P-pin
one, reject a P-pin frequency for which the esync_n_period keeping the
N-pin frequency does not fit into 32 bits, and check the division
remainder and reject the request if it is not exact.
Found by code inspection, not triggered at runtime.
Fixes: ce26d7ca50a5 ("dpll: zl3073x: Add support to get/set frequency on pins")
Reviewed-by: Petr Oros <redacted>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/zl3073x/dpll.c | 46 ++++++++++++++++++++++---------------
1 file changed, 28 insertions(+), 18 deletions(-)
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index f2e137475b40..7a617d9bd671 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -987,6 +987,7 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
const struct zl3073x_synth *synth;
u32 new_div, synth_freq;
struct zl3073x_out out;
+ u64 n_period, rem;
u8 out_id;
guard(mutex)(&zldpll->lock);
@@ -1014,16 +1015,11 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
}
if (zl3073x_dpll_is_p_pin(pin)) {
- /* We are going to change output frequency for P-pin but
- * if the requested frequency is less than current N-pin
- * frequency then indicate a failure as we are not able
- * to compute N-pin divisor to keep its frequency unchanged.
- *
- * Update divisor for N-pin to keep N-pin frequency.
+ /* Keep the N-pin frequency: the new P-pin frequency has to be
+ * a multiple of it, at least twice as high.
*/
- out.esync_n_period = (out.esync_n_period * out.div) / new_div;
- if (!out.esync_n_period)
- return -EINVAL;
+ n_period = mul_u32_u32(out.esync_n_period, out.div);
+ n_period = div64_u64_rem(n_period, new_div, &rem);
/* Update the output divisor */
out.div = new_div;@@ -1031,17 +1027,31 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
/* For 50/50 duty cycle the divisor is equal to width */
out.width = out.div;
} else {
- /* We are going to change frequency of N-pin but if
- * the requested freq is greater or equal than freq of P-pin
- * in the output pair we cannot compute divisor for the N-pin.
- * In this case indicate a failure.
- *
- * Update divisor for N-pin
+ /* The N-pin frequency has to divide the P-pin frequency and
+ * be at most half of it.
*/
- out.esync_n_period = div64_u64(synth_freq, frequency * out.div);
- if (!out.esync_n_period)
- return -EINVAL;
+ n_period = frequency * out.div;
+ n_period = div64_u64_rem(synth_freq, n_period, &rem);
+ }
+ if (n_period < 2) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "OUT%uN freq must be at most half of OUT%uP freq",
+ out_id, out_id);
+ return -EINVAL;
+ }
+ if (n_period > U32_MAX) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "OUT%uN freq is too low for OUT%uP freq",
+ out_id, out_id);
+ return -EINVAL;
+ }
+ if (rem != 0) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "OUT%uN freq must divide OUT%uP freq",
+ out_id, out_id);
+ return -EINVAL;
}
+ out.esync_n_period = n_period;
/* For 50/50 duty cycle the divisor is equal to width */
out.esync_n_width = out.esync_n_period;--
2.56.0