mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-12-27 12:21:22 -05:00
Merge branch 'dpll-add-phase-offset-averaging-factor'
Ivan Vecera says: ==================== dpll: add phase offset averaging factor For some hardware, the phase shift may result from averaging previous values and the newly measured value. In this case, the averaging is controlled by a configurable averaging factor. Add new device level attribute phase-offset-avg-factor, appropriate callbacks and implement them in zl3073x driver. ==================== Link: https://patch.msgid.link/20250927084912.2343597-1-ivecera@redhat.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
@@ -179,7 +179,23 @@ Phase offset measurement and adjustment
|
||||
Device may provide ability to measure a phase difference between signals
|
||||
on a pin and its parent dpll device. If pin-dpll phase offset measurement
|
||||
is supported, it shall be provided with ``DPLL_A_PIN_PHASE_OFFSET``
|
||||
attribute for each parent dpll device.
|
||||
attribute for each parent dpll device. The reported phase offset may be
|
||||
computed as the average of prior values and the current measurement, using
|
||||
the following formula:
|
||||
|
||||
.. math::
|
||||
curr\_avg = prev\_avg * \frac{2^N-1}{2^N} + new\_val * \frac{1}{2^N}
|
||||
|
||||
where `curr_avg` is the current reported phase offset, `prev_avg` is the
|
||||
previously reported value, `new_val` is the current measurement, and `N` is
|
||||
the averaging factor. Configured averaging factor value is provided with
|
||||
``DPLL_A_PHASE_OFFSET_AVG_FACTOR`` attribute of a device and value change can
|
||||
be requested with the same attribute with ``DPLL_CMD_DEVICE_SET`` command.
|
||||
|
||||
================================== ======================================
|
||||
``DPLL_A_PHASE_OFFSET_AVG_FACTOR`` attr configured value of phase offset
|
||||
averaging factor
|
||||
================================== ======================================
|
||||
|
||||
Device may also provide ability to adjust a signal phase on a pin.
|
||||
If pin phase adjustment is supported, minimal and maximal values that pin
|
||||
|
||||
@@ -315,6 +315,10 @@ attribute-sets:
|
||||
If enabled, dpll device shall monitor and notify all currently
|
||||
available inputs for changes of their phase offset against the
|
||||
dpll device.
|
||||
-
|
||||
name: phase-offset-avg-factor
|
||||
type: u32
|
||||
doc: Averaging factor applied to calculation of reported phase offset.
|
||||
-
|
||||
name: pin
|
||||
enum-name: dpll_a_pin
|
||||
@@ -523,6 +527,7 @@ operations:
|
||||
- clock-id
|
||||
- type
|
||||
- phase-offset-monitor
|
||||
- phase-offset-avg-factor
|
||||
|
||||
dump:
|
||||
reply: *dev-attrs
|
||||
@@ -540,6 +545,7 @@ operations:
|
||||
attributes:
|
||||
- id
|
||||
- phase-offset-monitor
|
||||
- phase-offset-avg-factor
|
||||
-
|
||||
name: device-create-ntf
|
||||
doc: Notification about device appearing
|
||||
|
||||
@@ -164,6 +164,27 @@ dpll_msg_add_phase_offset_monitor(struct sk_buff *msg, struct dpll_device *dpll,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
dpll_msg_add_phase_offset_avg_factor(struct sk_buff *msg,
|
||||
struct dpll_device *dpll,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
const struct dpll_device_ops *ops = dpll_device_ops(dpll);
|
||||
u32 factor;
|
||||
int ret;
|
||||
|
||||
if (ops->phase_offset_avg_factor_get) {
|
||||
ret = ops->phase_offset_avg_factor_get(dpll, dpll_priv(dpll),
|
||||
&factor, extack);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (nla_put_u32(msg, DPLL_A_PHASE_OFFSET_AVG_FACTOR, factor))
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
dpll_msg_add_lock_status(struct sk_buff *msg, struct dpll_device *dpll,
|
||||
struct netlink_ext_ack *extack)
|
||||
@@ -675,6 +696,9 @@ dpll_device_get_one(struct dpll_device *dpll, struct sk_buff *msg,
|
||||
if (nla_put_u32(msg, DPLL_A_TYPE, dpll->type))
|
||||
return -EMSGSIZE;
|
||||
ret = dpll_msg_add_phase_offset_monitor(msg, dpll, extack);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = dpll_msg_add_phase_offset_avg_factor(msg, dpll, extack);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -839,6 +863,23 @@ dpll_phase_offset_monitor_set(struct dpll_device *dpll, struct nlattr *a,
|
||||
extack);
|
||||
}
|
||||
|
||||
static int
|
||||
dpll_phase_offset_avg_factor_set(struct dpll_device *dpll, struct nlattr *a,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
const struct dpll_device_ops *ops = dpll_device_ops(dpll);
|
||||
u32 factor = nla_get_u32(a);
|
||||
|
||||
if (!ops->phase_offset_avg_factor_set) {
|
||||
NL_SET_ERR_MSG_ATTR(extack, a,
|
||||
"device not capable of changing phase offset average factor");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return ops->phase_offset_avg_factor_set(dpll, dpll_priv(dpll), factor,
|
||||
extack);
|
||||
}
|
||||
|
||||
static int
|
||||
dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a,
|
||||
struct netlink_ext_ack *extack)
|
||||
@@ -1736,14 +1777,25 @@ int dpll_nl_device_get_doit(struct sk_buff *skb, struct genl_info *info)
|
||||
static int
|
||||
dpll_set_from_nlattr(struct dpll_device *dpll, struct genl_info *info)
|
||||
{
|
||||
int ret;
|
||||
struct nlattr *a;
|
||||
int rem, ret;
|
||||
|
||||
if (info->attrs[DPLL_A_PHASE_OFFSET_MONITOR]) {
|
||||
struct nlattr *a = info->attrs[DPLL_A_PHASE_OFFSET_MONITOR];
|
||||
|
||||
ret = dpll_phase_offset_monitor_set(dpll, a, info->extack);
|
||||
if (ret)
|
||||
return ret;
|
||||
nla_for_each_attr(a, genlmsg_data(info->genlhdr),
|
||||
genlmsg_len(info->genlhdr), rem) {
|
||||
switch (nla_type(a)) {
|
||||
case DPLL_A_PHASE_OFFSET_MONITOR:
|
||||
ret = dpll_phase_offset_monitor_set(dpll, a,
|
||||
info->extack);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case DPLL_A_PHASE_OFFSET_AVG_FACTOR:
|
||||
ret = dpll_phase_offset_avg_factor_set(dpll, a,
|
||||
info->extack);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -42,9 +42,10 @@ static const struct nla_policy dpll_device_get_nl_policy[DPLL_A_ID + 1] = {
|
||||
};
|
||||
|
||||
/* DPLL_CMD_DEVICE_SET - do */
|
||||
static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_PHASE_OFFSET_MONITOR + 1] = {
|
||||
static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_PHASE_OFFSET_AVG_FACTOR + 1] = {
|
||||
[DPLL_A_ID] = { .type = NLA_U32, },
|
||||
[DPLL_A_PHASE_OFFSET_MONITOR] = NLA_POLICY_MAX(NLA_U32, 1),
|
||||
[DPLL_A_PHASE_OFFSET_AVG_FACTOR] = { .type = NLA_U32, },
|
||||
};
|
||||
|
||||
/* DPLL_CMD_PIN_ID_GET - do */
|
||||
@@ -112,7 +113,7 @@ static const struct genl_split_ops dpll_nl_ops[] = {
|
||||
.doit = dpll_nl_device_set_doit,
|
||||
.post_doit = dpll_post_doit,
|
||||
.policy = dpll_device_set_nl_policy,
|
||||
.maxattr = DPLL_A_PHASE_OFFSET_MONITOR,
|
||||
.maxattr = DPLL_A_PHASE_OFFSET_AVG_FACTOR,
|
||||
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
**********************/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -38,6 +38,12 @@ struct dpll_device_ops {
|
||||
void *dpll_priv,
|
||||
enum dpll_feature_state *state,
|
||||
struct netlink_ext_ack *extack);
|
||||
int (*phase_offset_avg_factor_set)(const struct dpll_device *dpll,
|
||||
void *dpll_priv, u32 factor,
|
||||
struct netlink_ext_ack *extack);
|
||||
int (*phase_offset_avg_factor_get)(const struct dpll_device *dpll,
|
||||
void *dpll_priv, u32 *factor,
|
||||
struct netlink_ext_ack *extack);
|
||||
};
|
||||
|
||||
struct dpll_pin_ops {
|
||||
|
||||
@@ -216,6 +216,7 @@ enum dpll_a {
|
||||
DPLL_A_LOCK_STATUS_ERROR,
|
||||
DPLL_A_CLOCK_QUALITY_LEVEL,
|
||||
DPLL_A_PHASE_OFFSET_MONITOR,
|
||||
DPLL_A_PHASE_OFFSET_AVG_FACTOR,
|
||||
|
||||
__DPLL_A_MAX,
|
||||
DPLL_A_MAX = (__DPLL_A_MAX - 1)
|
||||
|
||||
Reference in New Issue
Block a user