diff options
| author | Jakub Kicinski <kuba@kernel.org> | 2026-10-16 18:01:27 -0700 |
|---|---|---|
| committer | Jakub Kicinski <kuba@kernel.org> | 2026-10-16 18:01:27 -0700 |
| commit | 3d0ae2a0b7d7ae346019061c7df1dad1ab2e17cd (patch) | |
| tree | b7d966feec07b7ddff5544bb0d1d836613c78bff | |
| parent | f42d67a5044e9a36eb9c2a0967e9492e52a1949b (diff) | |
| parent | 0748e473b65a8c8690e250cdf7cc4f5a93252493 (diff) | |
Merge branch 'dpll-zl3073x-fix-output-pin-frequency-esync-and-sibling-notifications'
Ivan Vecera says:
====================
dpll: zl3073x: fix output pin frequency, esync and sibling notifications
This series fixes several issues in the zl3073x output pin handling:
1. Do not offer 0 Hz as a supported frequency, it leads to a division
by zero.
2. Reject N-divided output frequencies that cannot be set exactly.
3. Reject output frequencies whose divisor is too small for the HW.
4. Update the eSync period and width when the output frequency changes.
5. Notify the sibling pin when a shared output setting changes.
====================
Link: https://patch.msgid.link/20261011061211.448373-1-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
| -rw-r--r-- | drivers/dpll/zl3073x/dpll.c | 237 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/out.h | 36 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/prop.c | 56 |
3 files changed, 264 insertions, 65 deletions
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index f2e137475b40..1cdadb8e46fb 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -123,6 +123,8 @@ zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id) { struct zl3073x_dpll_pin *pin; + lockdep_assert_held(&zldpll->lock); + list_for_each_entry(pin, &zldpll->pins, list) { if (zl3073x_dpll_is_input_pin(pin) && zl3073x_input_pin_ref_get(pin->id) == ref_id) @@ -132,11 +134,42 @@ zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id) return NULL; } +/** + * zl3073x_dpll_output_pin_sibling_get - get the other pin of an output pair + * @pin: output pin whose sibling is sought + * + * Output pin ids are allocated in P/N pairs (P even, N odd) that share a + * single HW output. Looks up the other pin of the pair in the pin list + * of this DPLL. A pin is on the list from just before its registration + * until just after its unregistration, so a registered sibling is always + * found, but the returned pin may also be one that is not (yet or any + * longer) registered. + * + * Return: pointer to sibling pin, or NULL if it is not on the pin list + */ +static struct zl3073x_dpll_pin * +zl3073x_dpll_output_pin_sibling_get(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dpll_pin *sibling; + + lockdep_assert_held(&pin->dpll->lock); + + list_for_each_entry(sibling, &pin->dpll->pins, list) { + if (!zl3073x_dpll_is_input_pin(sibling) && + sibling->id == (pin->id ^ 1)) + return sibling; + } + + return NULL; +} + static struct zl3073x_dpll_pin * zl3073x_dpll_nco_pin_get(struct zl3073x_dpll *zldpll) { struct zl3073x_dpll_pin *pin; + lockdep_assert_held(&zldpll->lock); + list_for_each_entry(pin, &zldpll->pins, list) { if (zl3073x_dpll_is_nco_pin(pin)) return pin; @@ -878,7 +911,7 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, esync->range = esync_freq_ranges; esync->range_num = ARRAY_SIZE(esync_freq_ranges); - if (zl3073x_out_clock_type_get(out) != ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC) { + if (!zl3073x_out_esync_is_enabled(out)) { /* No need to read esync data if it is not enabled */ esync->freq = 0; esync->pulse = 0; @@ -894,7 +927,7 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, * Note that half pulse width is in units of half synth cycles, which * is why it reduces down to be output_div. */ - esync->pulse = (50 * out->esync_n_width) / out->div; + esync->pulse = div_u64(mul_u32_u32(50, out->esync_n_width), out->div); return 0; } @@ -909,12 +942,14 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dev *zldev = zldpll->dev; struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_dpll_pin *sibling = NULL; const struct zl3073x_synth *synth; struct zl3073x_out out; u32 synth_freq; u8 out_id; + int rc; - guard(mutex)(&zldpll->lock); + mutex_lock(&zldpll->lock); out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -923,38 +958,40 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, * for N-division is also used for the esync divider so both cannot * be used. */ - if (zl3073x_out_is_ndiv(&out)) - return -EOPNOTSUPP; - - /* Update clock type in output mode */ - if (freq) - zl3073x_out_clock_type_set(&out, - ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC); - else - zl3073x_out_clock_type_set(&out, - ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL); + if (zl3073x_out_is_ndiv(&out)) { + rc = -EOPNOTSUPP; + goto unlock; + } - /* If esync is being disabled just write mailbox and finish */ - if (!freq) - return zl3073x_out_state_set(zldev, out_id, &out); + if (!freq) { + zl3073x_out_esync_disable(&out); + goto commit; + } /* Get attached synth frequency */ synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out)); synth_freq = zl3073x_synth_freq_get(synth); - /* Compute and update esync period */ - out.esync_n_period = synth_freq / (u32)freq / out.div; + /* Enable 1PPS eSync for this pin frequency */ + zl3073x_out_esync_enable(&out, synth_freq / out.div); +commit: + /* Commit output configuration */ + rc = zl3073x_out_state_set(zldev, out_id, &out); + if (rc) + goto unlock; - /* 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. + /* The clock type, esync period and esync width are all shared by + * both pins of the output pair, so the sibling pin's esync + * configuration changes too and userspace has to be notified. */ - out.esync_n_width = out.div / 2; + sibling = zl3073x_dpll_output_pin_sibling_get(pin); +unlock: + mutex_unlock(&zldpll->lock); - /* Commit output configuration */ - return zl3073x_out_state_set(zldev, out_id, &out); + if (!rc && sibling) + __dpll_pin_change_ntf(sibling->dpll_pin); + + return rc; } static int @@ -984,12 +1021,15 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin, struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dev *zldev = zldpll->dev; struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_dpll_pin *sibling = NULL; const struct zl3073x_synth *synth; u32 new_div, synth_freq; struct zl3073x_out out; + u64 n_period, rem; u8 out_id; + int rc; - guard(mutex)(&zldpll->lock); + mutex_lock(&zldpll->lock); out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -1002,28 +1042,50 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin, /* Check signal format */ if (!zl3073x_out_is_ndiv(&out)) { /* For non N-divided signal formats the frequency is computed - * as division of synth frequency and output divisor. + * as division of synth frequency and output divisor, which + * is shared by both pins of the output pair. */ out.div = new_div; /* For 50/50 duty cycle the divisor is equal to width */ out.width = new_div; + /* The embedded sync period and width are computed relative to + * the output carrier so they have to be adjusted to follow the + * new frequency. + */ + if (zl3073x_out_esync_is_enabled(&out)) { + if (frequency == 1) { + /* eSync cannot work on a 1 Hz carrier */ + zl3073x_out_esync_disable(&out); + } else { + /* Update esync period and width according to + * the new frequency. + */ + zl3073x_out_esync_enable(&out, + synth_freq / out.div); + } + } + /* Commit output configuration */ - return zl3073x_out_state_set(zldev, out_id, &out); + rc = zl3073x_out_state_set(zldev, out_id, &out); + if (rc) + goto unlock; + + /* The other pin's frequency changed too - it has to be + * notified about the change. + */ + sibling = zl3073x_dpll_output_pin_sibling_get(pin); + + goto unlock; } 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,23 +1093,47 @@ 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); + rc = -EINVAL; + goto unlock; + } + 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); + rc = -EINVAL; + goto unlock; } + if (rem != 0) { + NL_SET_ERR_MSG_FMT(extack, + "OUT%uN freq must divide OUT%uP freq", + out_id, out_id); + rc = -EINVAL; + goto unlock; + } + 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; /* Commit output configuration */ - return zl3073x_out_state_set(zldev, out_id, &out); + rc = zl3073x_out_state_set(zldev, out_id, &out); +unlock: + mutex_unlock(&zldpll->lock); + + if (!rc && sibling) + __dpll_pin_change_ntf(sibling->dpll_pin); + + return rc; } static int @@ -1086,10 +1172,12 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dev *zldev = zldpll->dev; struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_dpll_pin *sibling = NULL; struct zl3073x_out out; u8 out_id; + int rc; - guard(mutex)(&zldpll->lock); + mutex_lock(&zldpll->lock); out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -1098,7 +1186,21 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, out.phase_comp = phase_adjust / pin->phase_gran; /* Update output configuration from mailbox */ - return zl3073x_out_state_set(zldev, out_id, &out); + rc = zl3073x_out_state_set(zldev, out_id, &out); + if (rc) + goto unlock; + + /* The phase compensation register is shared by both pins of the + * output pair, so the sibling pin's phase adjustment changes too. + */ + sibling = zl3073x_dpll_output_pin_sibling_get(pin); +unlock: + mutex_unlock(&zldpll->lock); + + if (!rc && sibling) + __dpll_pin_change_ntf(sibling->dpll_pin); + + return rc; } static int @@ -1722,6 +1824,13 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index) else ops = &zl3073x_dpll_output_pin_ops; + /* Add the pin to the list before registering it with the DPLL core so + * that it is findable as a sibling as soon as the core publishes it. + */ + mutex_lock(&zldpll->lock); + list_add(&pin->list, &zldpll->pins); + mutex_unlock(&zldpll->lock); + /* Register the pin */ rc = dpll_pin_register(zldpll->dpll_dev, pin->dpll_pin, ops, pin); if (rc) @@ -1733,6 +1842,9 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index) return 0; err_register: + mutex_lock(&zldpll->lock); + list_del(&pin->list); + mutex_unlock(&zldpll->lock); dpll_pin_put(pin->dpll_pin, &pin->tracker); err_pin_get: pin->dpll_pin = NULL; @@ -1767,6 +1879,13 @@ zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin) /* Unregister the pin */ dpll_pin_unregister(zldpll->dpll_dev, pin->dpll_pin, ops, pin); + /* Remove the pin from the list only after it has been unregistered so + * that a still-registered pin is always findable as a sibling. + */ + mutex_lock(&zldpll->lock); + list_del(&pin->list); + mutex_unlock(&zldpll->lock); + dpll_pin_put(pin->dpll_pin, &pin->tracker); pin->dpll_pin = NULL; @@ -1786,9 +1905,11 @@ zl3073x_dpll_pins_unregister(struct zl3073x_dpll *zldpll) { struct zl3073x_dpll_pin *pin, *next; + /* Unregister each pin before removing it from the list so that a + * still-registered pin is always findable as a sibling. + */ list_for_each_entry_safe(pin, next, &zldpll->pins, list) { zl3073x_dpll_pin_unregister(pin); - list_del(&pin->list); zl3073x_dpll_pin_free(pin); } } @@ -1901,16 +2022,24 @@ zl3073x_dpll_nco_pin_register(struct zl3073x_dpll *zldpll) goto err_pin_get; } + /* Add the pin to the list before registering it with the DPLL core so + * that a registered pin is always on the list. + */ + mutex_lock(&zldpll->lock); + list_add(&pin->list, &zldpll->pins); + mutex_unlock(&zldpll->lock); + rc = dpll_pin_register(zldpll->dpll_dev, pin->dpll_pin, &zl3073x_dpll_nco_pin_ops, pin); if (rc) goto err_register; - list_add(&pin->list, &zldpll->pins); - return 0; err_register: + mutex_lock(&zldpll->lock); + list_del(&pin->list); + mutex_unlock(&zldpll->lock); dpll_pin_put(pin->dpll_pin, &pin->tracker); err_pin_get: pin->dpll_pin = NULL; @@ -1962,8 +2091,6 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll) zl3073x_dpll_pin_free(pin); goto error; } - - list_add(&pin->list, &zldpll->pins); } /* Register NCO virtual input pin */ diff --git a/drivers/dpll/zl3073x/out.h b/drivers/dpll/zl3073x/out.h index 660889c57bff..d2b8b4eb5a76 100644 --- a/drivers/dpll/zl3073x/out.h +++ b/drivers/dpll/zl3073x/out.h @@ -134,4 +134,40 @@ static inline u8 zl3073x_out_synth_get(const struct zl3073x_out *out) return FIELD_GET(ZL_OUTPUT_CTRL_SYNTH_SEL, out->ctrl); } +static inline bool zl3073x_out_esync_is_enabled(const struct zl3073x_out *out) +{ + u8 clock_type = zl3073x_out_clock_type_get(out); + + return clock_type == ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC; +} + +static inline +void zl3073x_out_esync_enable(struct zl3073x_out *out, u32 base_freq) +{ + /* Period is expressed as the number of output divider clock cycles. + * So for 1 Hz the period is equal to base frequency. + */ + out->esync_n_period = base_freq; + + /* Half of the period in units of 1/2 synth cycle can be represented by + * the output_div. To get the supported esync pulse width of 25% of the + * period the output_div can just be divided by 2. Note that this + * assumes that output_div is even, otherwise some resolution will be + * lost. + */ + out->esync_n_width = out->div / 2; + + /* Update the clock type */ + zl3073x_out_clock_type_set(out, ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC); +} + +static inline +void zl3073x_out_esync_disable(struct zl3073x_out *out) +{ + /* Just update the clock type. The eSync period and width are + * ignored for this clock type. + */ + zl3073x_out_clock_type_set(out, ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL); +} + #endif /* _ZL3073X_OUT_H */ diff --git a/drivers/dpll/zl3073x/prop.c b/drivers/dpll/zl3073x/prop.c index ac9d41d0f978..4e006ed5950a 100644 --- a/drivers/dpll/zl3073x/prop.c +++ b/drivers/dpll/zl3073x/prop.c @@ -22,7 +22,8 @@ * The function checks the given frequency is valid for the device. For input * pins it checks that the frequency can be factorized using supported base * frequencies. For output pins it checks that the frequency divides connected - * synth frequency without remainder. + * synth frequency without remainder and that the resulting divisor is not + * below the minimum the driver supports. * * Return: true if the frequency is valid, false if not. */ @@ -30,7 +31,7 @@ static bool zl3073x_pin_check_freq(struct zl3073x_dev *zldev, enum dpll_pin_direction dir, u8 id, u64 freq) { - if (freq > U32_MAX) + if (!freq || freq > U32_MAX) goto err_inv_freq; if (dir == DPLL_PIN_DIRECTION_INPUT) { @@ -41,18 +42,38 @@ zl3073x_pin_check_freq(struct zl3073x_dev *zldev, enum dpll_pin_direction dir, if (rc) goto err_inv_freq; } else { - u32 synth_freq; - u8 out, synth; + u32 synth_freq, div, min_div, rem; + const struct zl3073x_out *out; + u8 out_id, synth; /* Get output pin synthesizer */ - out = zl3073x_output_pin_out_get(id); - synth = zl3073x_dev_out_synth_get(zldev, out); + out_id = zl3073x_output_pin_out_get(id); + synth = zl3073x_dev_out_synth_get(zldev, out_id); /* Get synth frequency */ synth_freq = zl3073x_dev_synth_freq_get(zldev, synth); /* Check the frequency divides synth frequency */ - if (synth_freq % (u32)freq) + div = div_u64_rem(synth_freq, freq, &rem); + if (rem) + goto err_inv_freq; + + /* The driver requires an output divisor of at least 2 - + * below that the hardware bypasses the divider and ignores + * the pulse width and eSync registers the driver programs. + * For the N-pin of an N-divided output the effective divisor + * also includes the N divider (>= 2), so the minimum is 4. + * For N-divided outputs this is only a necessary condition - + * whether the frequency can be set also depends on the output + * divisor shared by both pins, which is checked when the + * frequency is set. + */ + out = zl3073x_out_state_get(zldev, out_id); + if (zl3073x_out_is_ndiv(out) && zl3073x_is_n_pin(id)) + min_div = 4; + else + min_div = 2; + if (div < min_div) goto err_inv_freq; } @@ -295,13 +316,20 @@ skip_fwnode_props: goto err_alloc_ranges; } - /* Start with current frequency at index 0 */ - ranges[0] = (struct dpll_pin_frequency)DPLL_PIN_FREQUENCY(curr_freq); + /* Start with current frequency at index 0. A sub-Hz frequency is + * read back as 0 Hz and cannot be set, so it is not offered. + */ + j = 0; + if (curr_freq) { + struct dpll_pin_frequency freq = DPLL_PIN_FREQUENCY(curr_freq); + + ranges[j++] = freq; + } /* Add frequencies from firmware node, skipping current frequency * and filtering out frequencies not representable by device */ - for (i = 0, j = 1; i < num_freqs; i++) { + for (i = 0; i < num_freqs; i++) { struct dpll_pin_frequency freq = DPLL_PIN_FREQUENCY(freqs[i]); if (freqs[i] == curr_freq) @@ -312,6 +340,14 @@ skip_fwnode_props: } } + /* The DPLL core rejects a non-NULL list without entries, so do + * not publish the array at all when nothing is settable. + */ + if (!j) { + kfree(ranges); + ranges = NULL; + } + /* Save number of freq ranges and pointer to them into pin properties */ props->dpll_props.freq_supported = ranges; props->dpll_props.freq_supported_num = j; |
