Commit 9363b483 authored by Ivan Vecera's avatar Ivan Vecera Committed by Jakub Kicinski
Browse files

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: default avatarIvan Vecera <ivecera@redhat.com>
Reviewed-by: default avatarVadim Fedorenko <vadim.fedorenko@linux.dev>
Link: https://patch.msgid.link/20250927084912.2343597-4-ivecera@redhat.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent e28d5a68
Loading
Loading
Loading
Loading
+34 −4
Original line number Diff line number Diff line
@@ -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.
	 */
+13 −2
Original line number Diff line number Diff line
@@ -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
 **********************/
+58 −0
Original line number Diff line number Diff line
@@ -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;
}
+2 −0
Original line number Diff line number Diff line
@@ -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);