Add dpll device level attribute DPLL_A_PHASE_OFFSET_AVG_FACTOR to allow control over a calculation of reported phase offset value. Attribute is present, if the driver provides such capability, otherwise attribute shall not be present. Signed-off-by: Ivan Vecera --- Documentation/driver-api/dpll.rst | 18 +++++++++++++++++- Documentation/netlink/specs/dpll.yaml | 6 ++++++ drivers/dpll/dpll_nl.c | 5 +++-- include/uapi/linux/dpll.h | 1 + 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst index eca72d9b9ed87..be1fc643b645e 100644 --- a/Documentation/driver-api/dpll.rst +++ b/Documentation/driver-api/dpll.rst @@ -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 diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index 5decee61a2c4c..cafb4ec20447e 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -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 diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c index 9f2efaf252688..3c6d570babf89 100644 --- a/drivers/dpll/dpll_nl.c +++ b/drivers/dpll/dpll_nl.c @@ -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, }, { diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index 37b438ce8efc4..ab1725a954d74 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -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) -- 2.49.1 Add new callback operations for a dpll device: - phase_offset_avg_factor_get(...) - to obtain current phase offset averaging factor from dpll device, - phase_offset_avg_factor_set(...) - to set phase offset averaging factor Obtain the factor value using the get callback and provide it to the user if the device driver implement this callback. Execute the set callback upon user requests, if the driver implement it. Signed-off-by: Ivan Vecera v2: * do not require 'set' callback to retrieve current value * always call 'set' callback regardless of current value --- drivers/dpll/dpll_netlink.c | 66 +++++++++++++++++++++++++++++++++---- include/linux/dpll.h | 6 ++++ 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index 0a852011653c4..74c1f0ca95f24 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -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; - - if (info->attrs[DPLL_A_PHASE_OFFSET_MONITOR]) { - struct nlattr *a = info->attrs[DPLL_A_PHASE_OFFSET_MONITOR]; + struct nlattr *a; + int rem, ret; - 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; diff --git a/include/linux/dpll.h b/include/linux/dpll.h index fa1e76920d0ee..25be745bf41f1 100644 --- a/include/linux/dpll.h +++ b/include/linux/dpll.h @@ -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 { -- 2.49.1 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 --- v2: * INIT_WORK moved to zl3073x_dpll_alloc() --- drivers/dpll/zl3073x/core.c | 38 +++++++++++++++++++++--- drivers/dpll/zl3073x/core.h | 15 ++++++++-- drivers/dpll/zl3073x/dpll.c | 58 +++++++++++++++++++++++++++++++++++++ drivers/dpll/zl3073x/dpll.h | 2 ++ 4 files changed, 107 insertions(+), 6 deletions(-) diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c index e96095baac657..092e7027948a4 100644 --- a/drivers/dpll/zl3073x/core.c +++ b/drivers/dpll/zl3073x/core.c @@ -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. */ diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h index 128fb899cafc3..1dca4ddcf2350 100644 --- a/drivers/dpll/zl3073x/core.h +++ b/drivers/dpll/zl3073x/core.h @@ -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 **********************/ diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index 3e42e9e7fd272..93dc93eec79ed 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -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; } diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h index 304910ffc9c07..e8c39b44b356c 100644 --- a/drivers/dpll/zl3073x/dpll.h +++ b/drivers/dpll/zl3073x/dpll.h @@ -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); -- 2.49.1