diff options
| -rw-r--r-- | Documentation/driver-api/dpll.rst | 18 | ||||
| -rw-r--r-- | Documentation/netlink/specs/dpll.yaml | 6 | ||||
| -rw-r--r-- | drivers/dpll/dpll_netlink.c | 66 | ||||
| -rw-r--r-- | drivers/dpll/dpll_nl.c | 5 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/core.c | 38 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/core.h | 15 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/dpll.c | 58 | ||||
| -rw-r--r-- | drivers/dpll/zl3073x/dpll.h | 2 | ||||
| -rw-r--r-- | include/linux/dpll.h | 6 | ||||
| -rw-r--r-- | include/uapi/linux/dpll.h | 1 |
10 files changed, 199 insertions, 16 deletions
diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst index eca72d9b9ed8..be1fc643b645 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 5decee61a2c4..cafb4ec20447 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_netlink.c b/drivers/dpll/dpll_netlink.c index 0a852011653c..74c1f0ca95f2 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -165,6 +165,27 @@ dpll_msg_add_phase_offset_monitor(struct sk_buff *msg, struct dpll_device *dpll, } 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) { @@ -677,6 +698,9 @@ dpll_device_get_one(struct dpll_device *dpll, struct sk_buff *msg, 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; return 0; } @@ -840,6 +864,23 @@ dpll_phase_offset_monitor_set(struct dpll_device *dpll, struct nlattr *a, } 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/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c index 9f2efaf25268..3c6d570babf8 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/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c index e96095baac65..092e7027948a 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 128fb899cafc..1dca4ddcf235 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 3e42e9e7fd27..93dc93eec79e 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -1577,6 +1577,59 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv, } 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, enum dpll_feature_state *state, @@ -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 304910ffc9c0..e8c39b44b356 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); diff --git a/include/linux/dpll.h b/include/linux/dpll.h index fa1e76920d0e..25be745bf41f 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 { diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index 37b438ce8efc..ab1725a954d7 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) |
