* [PATCH net v4 0/5] dpll: zl3073x: fix output pin frequency, esync and sibling notifications
@ 2026-10-09 19:25 Ivan Vecera
2026-10-09 19:25 ` [PATCH net v4 1/5] dpll: zl3073x: do not offer 0 Hz as a supported pin frequency Ivan Vecera
` (5 more replies)
0 siblings, 6 replies; 11+ messages in thread
From: Ivan Vecera @ 2026-10-09 19:25 UTC (permalink / raw)
To: netdev
Cc: Min Li, Vadim Fedorenko, Arkadiusz Kubalewski, Jiri Pirko,
Jakub Kicinski, Prathosh Satish, Paolo Abeni, linux-kernel
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 output frequencies whose divisor is too small for the HW.
3. Update the eSync period and width when the output frequency changes.
4. Reject N-divided output frequencies that cannot be set exactly.
5. Notify the sibling pin when a shared output setting changes.
Changes since v3:
- New patch 1 to fix a division by zero reported by Sashiko.
- Patch 2: state that the check is necessary but not sufficient for
N-divided outputs.
- Patch 3: report the eSync frequency and pulse from the registers again
instead of a fixed 1 Hz / 25%; compute the pulse in 64 bits.
- Patch 4: keep the P-pin quotient in 64 bits and reject values above
U32_MAX; check esync_n_period < 2 first; fix comments and extack.
- Patch 5: fix the sibling lookup kernel-doc and the commit message.
Changes since v2:
- Added patches 2 and 4.
- Patch 3: simplify zl3073x_out_esync_is_enabled().
- Patch 5: add/remove pins from zldpll->pins around dpll_pin_register()/
dpll_pin_unregister() so a registered sibling is always found.
Changes since v1:
- Patch 3: pass the actual output carrier to zl3073x_out_esync_enable(),
drop a redundant "? true : false", fix a comment typo.
- Patch 5: commit message wording.
v3: https://lore.kernel.org/netdev/20261006153116.347497-1-ivecera@redhat.com/
v2: https://lore.kernel.org/netdev/20261002074520.30048-1-ivecera@redhat.com/
v1: https://lore.kernel.org/netdev/20261001080648.1424172-1-ivecera@redhat.com/
Ivan Vecera (5):
dpll: zl3073x: do not offer 0 Hz as a supported pin frequency
dpll: zl3073x: reject output frequencies with too small divisor
dpll: zl3073x: make embedded sync follow the output carrier frequency
dpll: zl3073x: reject inexact frequencies for N-divided outputs
dpll: zl3073x: notify sibling pin when shared output config changes
drivers/dpll/zl3073x/dpll.c | 237 +++++++++++++++++++++++++++---------
drivers/dpll/zl3073x/out.h | 36 ++++++
drivers/dpll/zl3073x/prop.c | 56 +++++++--
3 files changed, 264 insertions(+), 65 deletions(-)
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH net v4 1/5] dpll: zl3073x: do not offer 0 Hz as a supported pin frequency
2026-10-09 19:25 [PATCH net v4 0/5] dpll: zl3073x: fix output pin frequency, esync and sibling notifications Ivan Vecera
@ 2026-10-09 19:25 ` Ivan Vecera
2026-10-10 19:27 ` netdev-bot+sashiko
2026-10-09 19:25 ` [PATCH net v4 2/5] dpll: zl3073x: reject output frequencies with too small divisor Ivan Vecera
` (4 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: Ivan Vecera @ 2026-10-09 19:25 UTC (permalink / raw)
To: netdev
Cc: Petr Oros, Min Li, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Jakub Kicinski, Prathosh Satish, Paolo Abeni,
linux-kernel
zl3073x_pin_props_get() always adds the current pin frequency to the
supported list without checking it. A sub-Hz frequency reads back as
0 Hz, so 0 Hz can then be requested and the output frequency set
callback divides by zero.
Do not add a 0 Hz current frequency. If the list ends up empty, do not
publish it at all, as the DPLL core rejects an empty non-NULL list.
Fixes: 85a9aaac4a38 ("dpll: zl3073x: Include current frequency in supported frequencies list")
Reviewed-by: Petr Oros <poros@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/zl3073x/prop.c | 21 ++++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/drivers/dpll/zl3073x/prop.c b/drivers/dpll/zl3073x/prop.c
index ac9d41d0f978..18a1bf310332 100644
--- a/drivers/dpll/zl3073x/prop.c
+++ b/drivers/dpll/zl3073x/prop.c
@@ -295,13 +295,20 @@ struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
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 +319,14 @@ struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
}
}
+ /* 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;
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH net v4 2/5] dpll: zl3073x: reject output frequencies with too small divisor
2026-10-09 19:25 [PATCH net v4 0/5] dpll: zl3073x: fix output pin frequency, esync and sibling notifications Ivan Vecera
2026-10-09 19:25 ` [PATCH net v4 1/5] dpll: zl3073x: do not offer 0 Hz as a supported pin frequency Ivan Vecera
@ 2026-10-09 19:25 ` Ivan Vecera
2026-10-10 19:27 ` netdev-bot+sashiko
2026-10-09 19:25 ` [PATCH net v4 3/5] dpll: zl3073x: make embedded sync follow the output carrier frequency Ivan Vecera
` (3 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: Ivan Vecera @ 2026-10-09 19:25 UTC (permalink / raw)
To: netdev
Cc: Petr Oros, Min Li, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Jakub Kicinski, Prathosh Satish, Paolo Abeni,
linux-kernel
The output divisor has to be at least 2: a divisor of 1 bypasses the
divider and the hardware then ignores the pulse width and eSync
registers. For the N-pin of an N-divided output the N divider (>= 2)
applies on top, so its effective divisor has to be at least 4.
zl3073x_pin_check_freq() only checked that the frequency divides the
synth frequency. Require also the minimum divisor and reject 0 Hz.
For N-divided outputs this is necessary but not sufficient, the rest
is checked by the frequency set callback.
Fixes: a99a9f0ebdaa ("dpll: zl3073x: Read DPLL types and pin properties from system firmware")
Reviewed-by: Petr Oros <poros@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/zl3073x/prop.c | 35 ++++++++++++++++++++++++++++-------
1 file changed, 28 insertions(+), 7 deletions(-)
diff --git a/drivers/dpll/zl3073x/prop.c b/drivers/dpll/zl3073x/prop.c
index 18a1bf310332..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;
}
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH net v4 3/5] dpll: zl3073x: make embedded sync follow the output carrier frequency
2026-10-09 19:25 [PATCH net v4 0/5] dpll: zl3073x: fix output pin frequency, esync and sibling notifications Ivan Vecera
2026-10-09 19:25 ` [PATCH net v4 1/5] dpll: zl3073x: do not offer 0 Hz as a supported pin frequency Ivan Vecera
2026-10-09 19:25 ` [PATCH net v4 2/5] dpll: zl3073x: reject output frequencies with too small divisor Ivan Vecera
@ 2026-10-09 19:25 ` Ivan Vecera
2026-10-09 19:25 ` [PATCH net v4 4/5] dpll: zl3073x: reject inexact frequencies for N-divided outputs Ivan Vecera
` (2 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Ivan Vecera @ 2026-10-09 19:25 UTC (permalink / raw)
To: netdev
Cc: Petr Oros, Min Li, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Jakub Kicinski, Prathosh Satish, Paolo Abeni,
linux-kernel
The eSync period and width are relative to the output carrier, but
changing the output frequency did not update them, so eSync ran at
a wrong frequency and duty cycle. For a 1 Hz carrier esync_get()
returns -EOPNOTSUPP, so eSync left enabled could not be disabled.
Factor out zl3073x_out_esync_{is_enabled,enable,disable}() helpers
and use them in frequency_set() to recompute the eSync period and
width for the new carrier, or to disable eSync for a 1 Hz carrier.
Also compute the reported pulse width in 64 bits, as
50 * esync_n_width overflows u32 for low carrier frequencies.
Fixes: ce26d7ca50a5 ("dpll: zl3073x: Add support to get/set frequency on pins")
Fixes: 634ca2cb06d2 ("dpll: zl3073x: Add support to get/set esync on pins")
Reviewed-by: Petr Oros <poros@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/zl3073x/dpll.c | 46 +++++++++++++++++++------------------
drivers/dpll/zl3073x/out.h | 36 +++++++++++++++++++++++++++++
2 files changed, 60 insertions(+), 22 deletions(-)
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index f2e137475b40..8bac680394d7 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -878,7 +878,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 +894,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;
}
@@ -926,32 +926,17 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
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 esync is being disabled just write mailbox and finish */
- if (!freq)
+ if (!freq) {
+ zl3073x_out_esync_disable(&out);
return zl3073x_out_state_set(zldev, out_id, &out);
+ }
/* 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;
-
- /* 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;
+ /* Enable 1PPS eSync for this pin frequency */
+ zl3073x_out_esync_enable(&out, synth_freq / out.div);
/* Commit output configuration */
return zl3073x_out_state_set(zldev, out_id, &out);
@@ -1009,6 +994,23 @@ 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 = 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);
}
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 */
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH net v4 4/5] dpll: zl3073x: reject inexact frequencies for N-divided outputs
2026-10-09 19:25 [PATCH net v4 0/5] dpll: zl3073x: fix output pin frequency, esync and sibling notifications Ivan Vecera
` (2 preceding siblings ...)
2026-10-09 19:25 ` [PATCH net v4 3/5] dpll: zl3073x: make embedded sync follow the output carrier frequency Ivan Vecera
@ 2026-10-09 19:25 ` Ivan Vecera
2026-10-10 19:27 ` netdev-bot+sashiko
2026-10-09 19:25 ` [PATCH net v4 5/5] dpll: zl3073x: notify sibling pin when shared output config changes Ivan Vecera
2026-10-09 19:29 ` [PATCH net v4 0/5] dpll: zl3073x: fix output pin frequency, esync and sibling notifications netdev-bot+sinfo
5 siblings, 1 reply; 11+ messages in thread
From: Ivan Vecera @ 2026-10-09 19:25 UTC (permalink / raw)
To: netdev
Cc: Petr Oros, Min Li, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Jakub Kicinski, Prathosh Satish, Paolo Abeni,
linux-kernel
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.
Fixes: ce26d7ca50a5 ("dpll: zl3073x: Add support to get/set frequency on pins")
Reviewed-by: Petr Oros <poros@redhat.com>
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 8bac680394d7..6d9a6d21d30b 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -972,6 +972,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);
@@ -1016,16 +1017,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;
@@ -1033,17 +1029,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
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH net v4 5/5] dpll: zl3073x: notify sibling pin when shared output config changes
2026-10-09 19:25 [PATCH net v4 0/5] dpll: zl3073x: fix output pin frequency, esync and sibling notifications Ivan Vecera
` (3 preceding siblings ...)
2026-10-09 19:25 ` [PATCH net v4 4/5] dpll: zl3073x: reject inexact frequencies for N-divided outputs Ivan Vecera
@ 2026-10-09 19:25 ` Ivan Vecera
2026-10-10 19:27 ` netdev-bot+sashiko
2026-10-09 19:29 ` [PATCH net v4 0/5] dpll: zl3073x: fix output pin frequency, esync and sibling notifications netdev-bot+sinfo
5 siblings, 1 reply; 11+ messages in thread
From: Ivan Vecera @ 2026-10-09 19:25 UTC (permalink / raw)
To: netdev
Cc: Petr Oros, Min Li, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Jakub Kicinski, Prathosh Satish, Paolo Abeni,
linux-kernel
The P-pin and N-pin of an output share the divisor, clock type, eSync
and phase compensation registers (outside N-divided mode). Changing
them through one pin changes the other pin too, but only the requested
pin gets a change notification.
Add zl3073x_dpll_output_pin_sibling_get() and notify the sibling from
frequency_set(), esync_set() and phase_adjust_set(). The notification
re-enters the pin get callbacks, so it has to be sent after
zldpll->lock is released and the callbacks switch from guard(mutex)
to explicit unlocking.
To always find a registered sibling, add a pin to zldpll->pins before
dpll_pin_register() and remove it only after dpll_pin_unregister(),
both under zldpll->lock. A pin that is on the list but not registered
gets no netlink notification, as dpll_pin_event_send() skips such
pins, and the only in-tree DPLL notifier ignores DPLL_PIN_CHANGED.
Fixes: 634ca2cb06d2 ("dpll: zl3073x: Add support to get/set esync on pins")
Fixes: ce26d7ca50a5 ("dpll: zl3073x: Add support to get/set frequency on pins")
Fixes: 6287262f761e ("dpll: zl3073x: Add support to adjust phase")
Reviewed-by: Petr Oros <poros@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/zl3073x/dpll.c | 155 +++++++++++++++++++++++++++++++-----
1 file changed, 135 insertions(+), 20 deletions(-)
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 6d9a6d21d30b..1d61fe5fe586 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;
@@ -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,12 +958,14 @@ 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;
+ if (zl3073x_out_is_ndiv(&out)) {
+ rc = -EOPNOTSUPP;
+ goto unlock;
+ }
if (!freq) {
zl3073x_out_esync_disable(&out);
- return zl3073x_out_state_set(zldev, out_id, &out);
+ goto commit;
}
/* Get attached synth frequency */
@@ -937,9 +974,24 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
/* Enable 1PPS eSync for this pin frequency */
zl3073x_out_esync_enable(&out, synth_freq / out.div);
-
+commit:
/* 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 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.
+ */
+ 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
@@ -969,13 +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);
@@ -988,7 +1042,8 @@ 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;
@@ -1013,7 +1068,16 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
}
/* 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)) {
@@ -1039,19 +1103,22 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
NL_SET_ERR_MSG_FMT(extack,
"OUT%uN freq must be at most half of OUT%uP freq",
out_id, out_id);
- return -EINVAL;
+ 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);
- return -EINVAL;
+ 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);
- return -EINVAL;
+ rc = -EINVAL;
+ goto unlock;
}
out.esync_n_period = n_period;
@@ -1059,7 +1126,14 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
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
@@ -1098,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);
@@ -1110,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
@@ -1734,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)
@@ -1745,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;
@@ -1779,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;
@@ -1798,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);
}
}
@@ -1913,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 the list reflects the DPLL registration state.
+ */
+ 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;
@@ -1974,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 */
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net v4 0/5] dpll: zl3073x: fix output pin frequency, esync and sibling notifications
2026-10-09 19:25 [PATCH net v4 0/5] dpll: zl3073x: fix output pin frequency, esync and sibling notifications Ivan Vecera
` (4 preceding siblings ...)
2026-10-09 19:25 ` [PATCH net v4 5/5] dpll: zl3073x: notify sibling pin when shared output config changes Ivan Vecera
@ 2026-10-09 19:29 ` netdev-bot+sinfo
5 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sinfo @ 2026-10-09 19:29 UTC (permalink / raw)
To: Ivan Vecera
Cc: netdev, Min Li, Vadim Fedorenko, Arkadiusz Kubalewski,
Jiri Pirko, Jakub Kicinski, Prathosh Satish, Paolo Abeni,
linux-kernel
Hi!
This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:
- How the issue was discovered, e.g. hit in production, hit during
development, syzbot report, manual code inspection, LLM or static
analysis tool scan.
- Whether the issue was actually triggered, or is only theoretical
(e.g. found by code inspection). If it was triggered please include
the symptoms, like the stack trace or error messages.
Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.
The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net v4 1/5] dpll: zl3073x: do not offer 0 Hz as a supported pin frequency
2026-10-09 19:25 ` [PATCH net v4 1/5] dpll: zl3073x: do not offer 0 Hz as a supported pin frequency Ivan Vecera
@ 2026-10-10 19:27 ` netdev-bot+sashiko
0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 19:27 UTC (permalink / raw)
To: ivecera
Cc: netdev, poros, min.li, vadim.fedorenko, arkadiusz.kubalewski,
jiri, kuba, Prathosh.Satish, pabeni, linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] At this commit, the subject says 0 Hz is no longer offered,
but zl3073x_pin_props_get() (drivers/dpll/zl3073x/prop.c) only filters…
--
dpll: zl3073x: do not offer 0 Hz as a supported pin frequency
This patch stops zl3073x_pin_props_get() from putting the current pin
frequency into the supported list when it reads back as 0 Hz. If the
list ends up empty, it is freed and not published, because the DPLL
core rejects an empty non-NULL list.
> diff --git a/drivers/dpll/zl3073x/prop.c b/drivers/dpll/zl3073x/prop.c
> index ac9d41d0f978e..18a1bf3103326 100644
> --- a/drivers/dpll/zl3073x/prop.c
> +++ b/drivers/dpll/zl3073x/prop.c
> @@ -295,13 +295,20 @@ struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
> 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)
[Severity: Medium]
Can 0 Hz still end up offered through the firmware list?
The subject says 0 Hz is no longer offered. However, only a 0 Hz current
frequency is filtered.
Suppose the "supported-frequencies-hz" property has a 0 entry and
curr_freq is nonzero. That entry is not skipped by the
freqs[i] == curr_freq check. It goes on to zl3073x_pin_check_freq(),
which has no zero check at this commit:
drivers/dpll/zl3073x/prop.c:zl3073x_pin_check_freq() {
if (freq > U32_MAX)
goto err_inv_freq;
...
/* Check the frequency divides synth frequency */
if (synth_freq % (u32)freq)
goto err_inv_freq;
...
}
On an output pin, does this divide by zero during probe?
On an input pin, zl3073x_ref_freq_factorize(0, ...) succeeds on the first
base frequency of 1. Here 0 / 1 = 0 <= U16_MAX and 0 % 1 == 0, so 0 Hz is
published as supported. It could then be programmed with base 1 and
multiplier 0.
The dpll-pin.yaml binding sets no minimum for supported-frequencies-hz,
so the schema does not reject 0.
A later patch in this series, "dpll: zl3073x: reject output frequencies
with too small divisor", changes the first check in
zl3073x_pin_check_freq() to:
if (!freq || freq > U32_MAX)
goto err_inv_freq;
That fixes this by the end of the series. At this intermediate commit,
though, the 0 Hz case is still reachable from firmware.
Would it make sense to move the !freq check into this patch? Bisection
would then not land on a kernel that can still divide by zero here.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009192556.272263-1-ivecera%40redhat.com
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net v4 2/5] dpll: zl3073x: reject output frequencies with too small divisor
2026-10-09 19:25 ` [PATCH net v4 2/5] dpll: zl3073x: reject output frequencies with too small divisor Ivan Vecera
@ 2026-10-10 19:27 ` netdev-bot+sashiko
0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 19:27 UTC (permalink / raw)
To: ivecera
Cc: netdev, poros, min.li, vadim.fedorenko, arkadiusz.kubalewski,
jiri, kuba, Prathosh.Satish, pabeni, linux-kernel
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] Kept only because requires_validation=true.
- [Low] The commit message does not say that the new `!freq` check fixes
a division by zero that firmware can trigger at probe time.
--
dpll: zl3073x: reject output frequencies with too small divisor
This patch makes zl3073x_pin_check_freq() reject output frequencies from
firmware when the resulting divisor is below 2. For the N-pin of an
N-divided output the minimum is 4. The patch also rejects 0 Hz for both
input and output pins.
> diff --git a/drivers/dpll/zl3073x/prop.c b/drivers/dpll/zl3073x/prop.c
> index 18a1bf3103326..4e006ed5950ad 100644
> --- a/drivers/dpll/zl3073x/prop.c
> +++ b/drivers/dpll/zl3073x/prop.c
[ ... ]
> @@ -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;
[Severity: Low]
Should the commit message say that this check also fixes a divide error at
probe time?
Before this patch, the output path did:
if (synth_freq % (u32)freq)
zl3073x_pin_props_get() only skips a supported-frequencies-hz entry when it
equals curr_freq:
if (freqs[i] == curr_freq)
continue;
if (zl3073x_pin_check_freq(zldev, dir, index, freqs[i])) {
The dpll-pin.yaml binding doesn't set a minimum value. So an output pin
entry of 0 in firmware would reach the modulo and oops during probe.
The same check also changes how input pins behave. Before,
zl3073x_ref_freq_factorize(0, ...) succeeded, because 0 / 1 <= U16_MAX and
0 % 1 == 0. That meant 0 Hz was advertised as a supported input frequency.
With this patch it is dropped.
The commit message only says "reject 0 Hz". Could it also describe the
crash fix and the change for input pins?
> @@ -41,18 +42,38 @@ zl3073x_pin_check_freq(struct zl3073x_dev *zldev, enum dpll_pin_direction dir,
[ ... ]
> /* 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.
> + */
[Severity: Medium]
Is the "checked when the frequency is set" part true at this commit? The
commit message makes the same claim:
"For N-divided outputs this is necessary but not sufficient, the rest
is checked by the frequency set callback."
In zl3073x_dpll_output_pin_frequency_set() in drivers/dpll/zl3073x/dpll.c,
both N-divided branches only reject an N period of zero:
if (zl3073x_dpll_is_p_pin(pin)) {
...
out.esync_n_period = (out.esync_n_period * out.div) / new_div;
if (!out.esync_n_period)
return -EINVAL;
...
} else {
...
out.esync_n_period = div64_u64(synth_freq, frequency * out.div);
if (!out.esync_n_period)
return -EINVAL;
}
Neither branch requires an N divider of at least 2, and neither checks for
a remainder.
For example, take a 1 GHz synth with out.div == 4:
- Requesting 250 MHz on the N-pin passes zl3073x_pin_check_freq(), since
div 4 >= min_div 4. The setter then programs an N divider of 1.
- Requesting 100 MHz on the N-pin gives esync_n_period == 2, so the pin
runs at 125 MHz instead of 100 MHz.
Neither request returns an error.
A later patch in this series, "dpll: zl3073x: reject inexact frequencies
for N-divided outputs", appears to add these checks. It uses
div64_u64_rem() and rejects n_period < 2, n_period > U32_MAX and a non-zero
remainder.
Could the series be reordered so the setter checks land first?
Alternatively, could the comment and commit message here be changed to
match what the code does at this point?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009192556.272263-1-ivecera%40redhat.com
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net v4 4/5] dpll: zl3073x: reject inexact frequencies for N-divided outputs
2026-10-09 19:25 ` [PATCH net v4 4/5] dpll: zl3073x: reject inexact frequencies for N-divided outputs Ivan Vecera
@ 2026-10-10 19:27 ` netdev-bot+sashiko
0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 19:27 UTC (permalink / raw)
To: ivecera
Cc: netdev, poros, min.li, vadim.fedorenko, arkadiusz.kubalewski,
jiri, kuba, Prathosh.Satish, pabeni, linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 0 (1)
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] After this patch, zl3073x_dpll_output_pin_frequency_set()
rejects an N-pin frequency that the driver itself advertises as…
Pre-existing issues:
- [Low] The three NL_SET_ERR_MSG_FMT() diagnostics this patch adds to
zl3073x_dpll_output_pin_frequency_set() never reach userspace.
--
dpll: zl3073x: reject inexact frequencies for N-divided outputs
This patch changes zl3073x_dpll_output_pin_frequency_set() for N-divided
outputs. The esync_n_period computation now uses a 64-bit product, and the
request is rejected when the resulting period is below 2, does not fit
into 32 bits, or the division leaves a remainder.
> diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
> index 8bac680394d76..6d9a6d21d30b7 100644
> --- a/drivers/dpll/zl3073x/dpll.c
> +++ b/drivers/dpll/zl3073x/dpll.c
[ ... ]
> @@ -1033,17 +1029,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);
[Severity: Low]
This isn't a bug introduced by this patch, but will userspace ever see
these three new extack messages?
The only caller, dpll_pin_freq_set() in drivers/dpll/dpll_netlink.c,
always writes its own message when the driver callback fails:
drivers/dpll/dpll_netlink.c:dpll_pin_freq_set() {
...
ret = ops->frequency_set(pin, dpll_pin_on_dpll_priv(dpll, pin),
dpll, dpll_priv(dpll), freq, extack);
if (ret) {
NL_SET_ERR_MSG_FMT(extack,
"frequency set failed for dpll_id:%u",
dpll->id);
return ret;
}
...
}
NL_SET_ERR_MSG_FMT() unconditionally replaces extack->_msg, so the
"OUT%uN freq must ..." strings from zl3073x get replaced by the generic
core message. ice_dpll_pin_freq_set() loses its messages the same way.
Would it make sense to switch the core to NL_SET_ERR_MSG_WEAK(), or to
keep a message the driver has already set?
> + 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;
[Severity: Medium]
Can this reject an N-pin frequency that the driver itself lists as
supported?
The hardware N-pin frequency is synth_freq / (div * esync_n_period), and
that does not have to be a whole number of Hz. The getter rounds it down:
drivers/dpll/zl3073x/core.h:zl3073x_dev_output_pin_freq_get() {
...
freq = zl3073x_synth_freq_get(synth) / out->div;
if (zl3073x_out_is_ndiv(out) && zl3073x_is_n_pin(id))
freq /= out->esync_n_period;
...
}
zl3073x_pin_props_get() then adds this rounded value to freq_supported.
Unlike the firmware-listed frequencies, it does not go through
zl3073x_pin_check_freq():
if (curr_freq) {
struct dpll_pin_frequency freq = DPLL_PIN_FREQUENCY(curr_freq);
ranges[j++] = freq;
}
For example, take a 600 MHz synth with div=60 and esync_n_period=7. The
real N-pin frequency is 1428571.43 Hz, and it is advertised as 1428571 Hz.
Say userspace moves the N-pin to another supported value, such as a
firmware-listed 1 MHz, and then tries to set it back to 1428571 Hz.
dpll_pin_is_freq_supported() accepts the value, and then this function
computes:
n_period = 1428571 * 60 = 85714260
div64_u64_rem(600000000, 85714260, &rem) -> 7, rem = 180
The new rem != 0 check returns -EINVAL here. The old truncating division
gave n_period = 7, which restored the original hardware setting.
The comment in zl3073x_pin_props_get() says a frequency that "cannot be
set ... is not offered", which suggests the current frequency added there
is expected to be settable. Should the advertised current frequency be
skipped or checked for inexact N-divided outputs, or should the setter
accept the truncated value?
> + 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;
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009192556.272263-1-ivecera%40redhat.com
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH net v4 5/5] dpll: zl3073x: notify sibling pin when shared output config changes
2026-10-09 19:25 ` [PATCH net v4 5/5] dpll: zl3073x: notify sibling pin when shared output config changes Ivan Vecera
@ 2026-10-10 19:27 ` netdev-bot+sashiko
0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 19:27 UTC (permalink / raw)
To: ivecera
Cc: netdev, poros, min.li, vadim.fedorenko, arkadiusz.kubalewski,
jiri, kuba, Prathosh.Satish, pabeni, linux-kernel
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] Pins that are on the list but not registered get passed to
__dpll_pin_change_ntf(), and the new NCO comment is wrong.
--
dpll: zl3073x: notify sibling pin when shared output config changes
This patch adds zl3073x_dpll_output_pin_sibling_get(). It sends a change
notification for the sibling P/N pin from frequency_set(), esync_set() and
phase_adjust_set() after zldpll->lock is dropped. Pins are now added to
zldpll->pins before dpll_pin_register() and removed after
dpll_pin_unregister(), so a registered sibling can always be found.
> diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
> index 6d9a6d21d30b7..1d61fe5fe5862 100644
> --- a/drivers/dpll/zl3073x/dpll.c
> +++ b/drivers/dpll/zl3073x/dpll.c
[ ... ]
> @@ -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
> + */
[ ... ]
> @@ -937,9 +974,24 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
>
> /* Enable 1PPS eSync for this pin frequency */
> zl3073x_out_esync_enable(&out, synth_freq / out.div);
> -
> +commit:
> /* 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 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.
> + */
> + sibling = zl3073x_dpll_output_pin_sibling_get(pin);
> +unlock:
> + mutex_unlock(&zldpll->lock);
> +
> + if (!rc && sibling)
> + __dpll_pin_change_ntf(sibling->dpll_pin);
[Severity: Low]
The kernel-doc for __dpll_pin_change_ntf() in drivers/dpll/dpll_netlink.c
says its argument is "@pin: registered pin pointer". The kernel-doc for
zl3073x_dpll_output_pin_sibling_get() above says the returned pin "may
also be one that is not (yet or any longer) registered".
Should the sibling's registration state be checked before it is passed to
__dpll_pin_change_ntf() here? The same question applies to the matching
calls in zl3073x_dpll_output_pin_frequency_set() and
zl3073x_dpll_output_pin_phase_adjust_set().
__dpll_pin_change_ntf() runs the notifier chain before the
dpll_pin_available() check in dpll_pin_event_send():
int __dpll_pin_change_ntf(struct dpll_pin *pin)
{
lockdep_assert_held(&dpll_lock);
dpll_pin_notify(pin, pin->clock_id, DPLL_PIN_CHANGED);
return dpll_pin_event_send(DPLL_CMD_PIN_CHANGE_NTF, pin);
}
This means a notifier subscriber can get DPLL_PIN_CHANGED for a pin
before it got DPLL_PIN_CREATED, or after it got DPLL_PIN_DELETED.
It looks like this can happen during probe:
- zl3073x_dpll_device_register() runs before zl3073x_dpll_pins_register().
- The P pin (even id) is registered before its N sibling.
- A userspace PIN_SET on the registered P pin holds dpll_lock.
- That PIN_SET can find N on the list after list_add(), while
dpll_pin_register(N) is still waiting for dpll_lock.
list_add() now runs before dpll_pin_register() for every pin type. So the
existing callers of zl3073x_dpll_nco_pin_get() and
zl3073x_dpll_pin_get_by_ref() can also get a pin that is not registered
yet and pass it to __dpll_pin_change_ntf(). Before this patch, that could
only happen during teardown, between dpll_pin_unregister() and list_del().
As the commit message says, no netlink message is sent for such pins, and
ice_dpll_pin_notify() ignores DPLL_PIN_CHANGED. So nothing visibly breaks
today.
Would a per-pin registered flag be cleaner? It could be set and cleared
under zldpll->lock around dpll_pin_register() and dpll_pin_unregister(),
and checked before notifying. Another option is to relax the core
kernel-doc and have dpll_pin_notify() check registration.
Separately, the new comment in zl3073x_dpll_nco_pin_register() says:
/* Add the pin to the list before registering it with the DPLL core so
* that the list reflects the DPLL registration state.
*/
Is this accurate? With the early list_add(), the list holds every
registered pin and also pins that are not registered yet.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009192556.272263-1-ivecera%40redhat.com
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-10-10 19:27 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-09 19:25 [PATCH net v4 0/5] dpll: zl3073x: fix output pin frequency, esync and sibling notifications Ivan Vecera
2026-10-09 19:25 ` [PATCH net v4 1/5] dpll: zl3073x: do not offer 0 Hz as a supported pin frequency Ivan Vecera
2026-10-10 19:27 ` netdev-bot+sashiko
2026-10-09 19:25 ` [PATCH net v4 2/5] dpll: zl3073x: reject output frequencies with too small divisor Ivan Vecera
2026-10-10 19:27 ` netdev-bot+sashiko
2026-10-09 19:25 ` [PATCH net v4 3/5] dpll: zl3073x: make embedded sync follow the output carrier frequency Ivan Vecera
2026-10-09 19:25 ` [PATCH net v4 4/5] dpll: zl3073x: reject inexact frequencies for N-divided outputs Ivan Vecera
2026-10-10 19:27 ` netdev-bot+sashiko
2026-10-09 19:25 ` [PATCH net v4 5/5] dpll: zl3073x: notify sibling pin when shared output config changes Ivan Vecera
2026-10-10 19:27 ` netdev-bot+sashiko
2026-10-09 19:29 ` [PATCH net v4 0/5] dpll: zl3073x: fix output pin frequency, esync and sibling notifications netdev-bot+sinfo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®