dpll: zl3073x: Allow to configure phase offset averaging factor

The DPLL phase measurement block uses an exponential moving average with
a configurable averaging factor. Measurements are taken at approximately
40 Hz or at the reference frequency, whichever is lower.

Currently, factor=2 is used to prioritize fast response for dynamic
phase changes. For applications needing a stable, precise average phase
offset where rapid changes are unlikely, a higher factor is recommended.

Implement the .phase_offset_avg_factor_get/set callbacks to allow a user
to adjust this factor.

Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>
Link: https://patch.msgid.link/20250927084912.2343597-4-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Ivan Vecera 2025-09-27 10:49:12 +02:00 committed by Jakub Kicinski
parent e28d5a68b6
commit 9363b48376
4 changed files with 107 additions and 6 deletions

View File

@ -956,6 +956,32 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
msecs_to_jiffies(500));
}
int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor)
{
u8 dpll_meas_ctrl, value;
int rc;
/* Read DPLL phase measurement control register */
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, &dpll_meas_ctrl);
if (rc)
return rc;
/* Convert requested factor to register value */
value = (factor + 1) & 0x0f;
/* Update phase measurement control register */
dpll_meas_ctrl &= ~ZL_DPLL_MEAS_CTRL_AVG_FACTOR;
dpll_meas_ctrl |= FIELD_PREP(ZL_DPLL_MEAS_CTRL_AVG_FACTOR, value);
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, dpll_meas_ctrl);
if (rc)
return rc;
/* Save the new factor */
zldev->phase_avg_factor = factor;
return 0;
}
/**
* zl3073x_dev_phase_meas_setup - setup phase offset measurement
* @zldev: pointer to zl3073x_dev structure
@ -972,15 +998,16 @@ zl3073x_dev_phase_meas_setup(struct zl3073x_dev *zldev)
u8 dpll_meas_ctrl, mask = 0;
int rc;
/* Setup phase measurement averaging factor */
rc = zl3073x_dev_phase_avg_factor_set(zldev, zldev->phase_avg_factor);
if (rc)
return rc;
/* Read DPLL phase measurement control register */
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, &dpll_meas_ctrl);
if (rc)
return rc;
/* Setup phase measurement averaging factor */
dpll_meas_ctrl &= ~ZL_DPLL_MEAS_CTRL_AVG_FACTOR;
dpll_meas_ctrl |= FIELD_PREP(ZL_DPLL_MEAS_CTRL_AVG_FACTOR, 3);
/* Enable DPLL measurement block */
dpll_meas_ctrl |= ZL_DPLL_MEAS_CTRL_EN;
@ -1208,6 +1235,9 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev,
*/
zldev->clock_id = get_random_u64();
/* Default phase offset averaging factor */
zldev->phase_avg_factor = 2;
/* Initialize mutex for operations where multiple reads, writes
* and/or polls are required to be done atomically.
*/

View File

@ -68,19 +68,19 @@ struct zl3073x_synth {
* @dev: pointer to device
* @regmap: regmap to access device registers
* @multiop_lock: to serialize multiple register operations
* @clock_id: clock id of the device
* @ref: array of input references' invariants
* @out: array of outs' invariants
* @synth: array of synths' invariants
* @dplls: list of DPLLs
* @kworker: thread for periodic work
* @work: periodic work
* @clock_id: clock id of the device
* @phase_avg_factor: phase offset measurement averaging factor
*/
struct zl3073x_dev {
struct device *dev;
struct regmap *regmap;
struct mutex multiop_lock;
u64 clock_id;
/* Invariants */
struct zl3073x_ref ref[ZL3073X_NUM_REFS];
@ -93,6 +93,10 @@ struct zl3073x_dev {
/* Monitor */
struct kthread_worker *kworker;
struct kthread_delayed_work work;
/* Devlink parameters */
u64 clock_id;
u8 phase_avg_factor;
};
struct zl3073x_chip_info {
@ -115,6 +119,13 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev,
int zl3073x_dev_start(struct zl3073x_dev *zldev, bool full);
void zl3073x_dev_stop(struct zl3073x_dev *zldev);
static inline u8 zl3073x_dev_phase_avg_factor_get(struct zl3073x_dev *zldev)
{
return zldev->phase_avg_factor;
}
int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor);
/**********************
* Registers operations
**********************/

View File

@ -1576,6 +1576,59 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
return 0;
}
static int
zl3073x_dpll_phase_offset_avg_factor_get(const struct dpll_device *dpll,
void *dpll_priv, u32 *factor,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
*factor = zl3073x_dev_phase_avg_factor_get(zldpll->dev);
return 0;
}
static void
zl3073x_dpll_change_work(struct work_struct *work)
{
struct zl3073x_dpll *zldpll;
zldpll = container_of(work, struct zl3073x_dpll, change_work);
dpll_device_change_ntf(zldpll->dpll_dev);
}
static int
zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll,
void *dpll_priv, u32 factor,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *item, *zldpll = dpll_priv;
int rc;
if (factor > 15) {
NL_SET_ERR_MSG_FMT(extack,
"Phase offset average factor has to be from range <0,15>");
return -EINVAL;
}
rc = zl3073x_dev_phase_avg_factor_set(zldpll->dev, factor);
if (rc) {
NL_SET_ERR_MSG_FMT(extack,
"Failed to set phase offset averaging factor");
return rc;
}
/* The averaging factor is common for all DPLL channels so after change
* we have to send a notification for other DPLL devices.
*/
list_for_each_entry(item, &zldpll->dev->dplls, list) {
if (item != zldpll)
schedule_work(&item->change_work);
}
return 0;
}
static int
zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll,
void *dpll_priv,
@ -1635,6 +1688,8 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
static const struct dpll_device_ops zl3073x_dpll_device_ops = {
.lock_status_get = zl3073x_dpll_lock_status_get,
.mode_get = zl3073x_dpll_mode_get,
.phase_offset_avg_factor_get = zl3073x_dpll_phase_offset_avg_factor_get,
.phase_offset_avg_factor_set = zl3073x_dpll_phase_offset_avg_factor_set,
.phase_offset_monitor_get = zl3073x_dpll_phase_offset_monitor_get,
.phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set,
};
@ -1983,6 +2038,8 @@ zl3073x_dpll_device_unregister(struct zl3073x_dpll *zldpll)
{
WARN(!zldpll->dpll_dev, "DPLL device is not registered\n");
cancel_work_sync(&zldpll->change_work);
dpll_device_unregister(zldpll->dpll_dev, &zl3073x_dpll_device_ops,
zldpll);
dpll_device_put(zldpll->dpll_dev);
@ -2258,6 +2315,7 @@ zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
zldpll->dev = zldev;
zldpll->id = ch;
INIT_LIST_HEAD(&zldpll->pins);
INIT_WORK(&zldpll->change_work, zl3073x_dpll_change_work);
return zldpll;
}

View File

@ -20,6 +20,7 @@
* @dpll_dev: pointer to registered DPLL device
* @lock_status: last saved DPLL lock status
* @pins: list of pins
* @change_work: device change notification work
*/
struct zl3073x_dpll {
struct list_head list;
@ -32,6 +33,7 @@ struct zl3073x_dpll {
struct dpll_device *dpll_dev;
enum dpll_lock_status lock_status;
struct list_head pins;
struct work_struct change_work;
};
struct zl3073x_dpll *zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch);