diff --git a/COPYING-5.14.0-687.12.1.el9 b/COPYING-5.14.0-687.15.1.el9 similarity index 100% rename from COPYING-5.14.0-687.12.1.el9 rename to COPYING-5.14.0-687.15.1.el9 diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst index 83118c728e..93c191b2d0 100644 --- a/Documentation/driver-api/dpll.rst +++ b/Documentation/driver-api/dpll.rst @@ -250,6 +250,24 @@ in the ``DPLL_A_PIN_PHASE_OFFSET`` attribute. ``DPLL_A_PHASE_OFFSET_MONITOR`` attr state of a feature =============================== ======================== +Frequency monitor +================= + +Some DPLL devices may offer the capability to measure the actual +frequency of all available input pins. The attribute and current feature state +shall be included in the response message of the ``DPLL_CMD_DEVICE_GET`` +command for supported DPLL devices. In such cases, users can also control +the feature using the ``DPLL_CMD_DEVICE_SET`` command by setting the +``enum dpll_feature_state`` values for the attribute. +Once enabled the measured input frequency for each input pin shall be +returned in the ``DPLL_A_PIN_MEASURED_FREQUENCY`` attribute. The value +is in millihertz (mHz), using ``DPLL_PIN_MEASURED_FREQUENCY_DIVIDER`` +as the divider. + + =============================== ======================== + ``DPLL_A_FREQUENCY_MONITOR`` attr state of a feature + =============================== ======================== + Embedded SYNC ============= @@ -411,6 +429,8 @@ according to attribute purpose. ``DPLL_A_PIN_STATE`` attr state of pin on the parent pin ``DPLL_A_PIN_CAPABILITIES`` attr bitmask of pin capabilities + ``DPLL_A_PIN_MEASURED_FREQUENCY`` attr measured frequency of + an input pin in mHz ==================================== ================================== ==================================== ================================= diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index ed58662386..cb5da356bb 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -240,6 +240,20 @@ definitions: integer part of a measured phase offset value. Value of (DPLL_A_PHASE_OFFSET % DPLL_PHASE_OFFSET_DIVIDER) is a fractional part of a measured phase offset value. + - + type: const + name: pin-measured-frequency-divider + value: 1000 + doc: | + pin measured frequency divider allows userspace to calculate + a value of measured input frequency as a fractional value with + three digit decimal precision (millihertz). + Value of (DPLL_A_PIN_MEASURED_FREQUENCY / + DPLL_PIN_MEASURED_FREQUENCY_DIVIDER) is an integer part of + a measured frequency value. + Value of (DPLL_A_PIN_MEASURED_FREQUENCY % + DPLL_PIN_MEASURED_FREQUENCY_DIVIDER) is a fractional part of + a measured frequency value. - type: enum name: feature-state @@ -319,6 +333,13 @@ attribute-sets: name: phase-offset-avg-factor type: u32 doc: Averaging factor applied to calculation of reported phase offset. + - + name: frequency-monitor + type: u32 + enum: feature-state + doc: Current or desired state of the frequency monitor feature. + If enabled, dpll device shall measure all currently available + inputs for their actual input frequency. - name: pin enum-name: dpll_a_pin @@ -456,6 +477,17 @@ attribute-sets: Value is in PPT (parts per trillion, 10^-12). Note: This attribute provides higher resolution than the standard fractional-frequency-offset (which is in PPM). + - + name: measured-frequency + type: u64 + doc: | + The measured frequency of the input pin in millihertz (mHz). + Value of (DPLL_A_PIN_MEASURED_FREQUENCY / + DPLL_PIN_MEASURED_FREQUENCY_DIVIDER) is an integer part (Hz) + of a measured frequency value. + Value of (DPLL_A_PIN_MEASURED_FREQUENCY % + DPLL_PIN_MEASURED_FREQUENCY_DIVIDER) is a fractional part + of a measured frequency value. - name: pin-parent-device @@ -544,6 +576,7 @@ operations: - type - phase-offset-monitor - phase-offset-avg-factor + - frequency-monitor dump: reply: *dev-attrs @@ -563,6 +596,7 @@ operations: - mode - phase-offset-monitor - phase-offset-avg-factor + - frequency-monitor - name: device-create-ntf doc: Notification about device appearing @@ -643,6 +677,7 @@ operations: - esync-frequency-supported - esync-pulse - reference-sync + - measured-frequency dump: request: diff --git a/arch/x86/kernel/kvm.c b/arch/x86/kernel/kvm.c index c8b1db5c91..16d4e504be 100644 --- a/arch/x86/kernel/kvm.c +++ b/arch/x86/kernel/kvm.c @@ -86,6 +86,7 @@ struct kvm_task_sleep_node { struct swait_queue_head wq; u32 token; int cpu; + bool dummy; }; static struct kvm_task_sleep_head { @@ -117,15 +118,26 @@ static bool kvm_async_pf_queue_task(u32 token, struct kvm_task_sleep_node *n) raw_spin_lock(&b->lock); e = _find_apf_task(b, token); if (e) { - /* dummy entry exist -> wake up was delivered ahead of PF */ - hlist_del(&e->link); + struct kvm_task_sleep_node *dummy = NULL; + + /* + * The entry can either be a 'dummy' entry (which is put on the + * list when wake-up happens ahead of APF handling completion) + * or a token from another task which should not be touched. + */ + if (e->dummy) { + hlist_del(&e->link); + dummy = e; + } + raw_spin_unlock(&b->lock); - kfree(e); + kfree(dummy); return false; } n->token = token; n->cpu = smp_processor_id(); + n->dummy = false; init_swait_queue_head(&n->wq); hlist_add_head(&n->link, &b->list); raw_spin_unlock(&b->lock); @@ -228,6 +240,7 @@ again: } dummy->token = token; dummy->cpu = smp_processor_id(); + dummy->dummy = true; init_swait_queue_head(&dummy->wq); hlist_add_head(&dummy->link, &b->list); dummy = NULL; diff --git a/crypto/authenc.c b/crypto/authenc.c index 670bf1a01d..494c0b6db4 100644 --- a/crypto/authenc.c +++ b/crypto/authenc.c @@ -39,7 +39,7 @@ struct authenc_request_ctx { static void authenc_request_complete(struct aead_request *req, int err) { - if (err != -EINPROGRESS) + if (err != -EINPROGRESS && err != -EBUSY) aead_request_complete(req, err); } @@ -109,27 +109,42 @@ out: return err; } -static void authenc_geniv_ahash_done(struct crypto_async_request *areq, int err) +static void authenc_geniv_ahash_finish(struct aead_request *req) { - struct aead_request *req = areq->data; struct crypto_aead *authenc = crypto_aead_reqtfm(req); struct aead_instance *inst = aead_alg_instance(authenc); struct authenc_instance_ctx *ictx = aead_instance_ctx(inst); struct authenc_request_ctx *areq_ctx = aead_request_ctx(req); struct ahash_request *ahreq = (void *)(areq_ctx->tail + ictx->reqoff); - if (err) - goto out; - scatterwalk_map_and_copy(ahreq->result, req->dst, req->assoclen + req->cryptlen, crypto_aead_authsize(authenc), 1); +} -out: +static void authenc_geniv_ahash_done(struct crypto_async_request *areq, int err) +{ + struct aead_request *req = areq->data; + + if (!err) + authenc_geniv_ahash_finish(req); aead_request_complete(req, err); } -static int crypto_authenc_genicv(struct aead_request *req, unsigned int flags) +/* + * Used when the ahash request was invoked in the async callback context + * of the previous skcipher request. Eat any EINPROGRESS notifications. + */ +static void authenc_geniv_ahash_done2(struct crypto_async_request *areq, int err) +{ + struct aead_request *req = areq->data; + + if (!err) + authenc_geniv_ahash_finish(req); + authenc_request_complete(req, err); +} + +static int crypto_authenc_genicv(struct aead_request *req, unsigned int mask) { struct crypto_aead *authenc = crypto_aead_reqtfm(req); struct aead_instance *inst = aead_alg_instance(authenc); @@ -138,6 +153,7 @@ static int crypto_authenc_genicv(struct aead_request *req, unsigned int flags) struct crypto_ahash *auth = ctx->auth; struct authenc_request_ctx *areq_ctx = aead_request_ctx(req); struct ahash_request *ahreq = (void *)(areq_ctx->tail + ictx->reqoff); + unsigned int flags = aead_request_flags(req) & ~mask; u8 *hash = areq_ctx->tail; int err; @@ -148,7 +164,8 @@ static int crypto_authenc_genicv(struct aead_request *req, unsigned int flags) ahash_request_set_crypt(ahreq, req->dst, hash, req->assoclen + req->cryptlen); ahash_request_set_callback(ahreq, flags, - authenc_geniv_ahash_done, req); + mask ? authenc_geniv_ahash_done2 : + authenc_geniv_ahash_done, req); err = crypto_ahash_digest(ahreq); if (err) @@ -165,12 +182,11 @@ static void crypto_authenc_encrypt_done(struct crypto_async_request *req, { struct aead_request *areq = req->data; - if (err) - goto out; - - err = crypto_authenc_genicv(areq, 0); - -out: + if (err) { + aead_request_complete(areq, err); + return; + } + err = crypto_authenc_genicv(areq, CRYPTO_TFM_REQ_MAY_SLEEP); authenc_request_complete(areq, err); } @@ -223,11 +239,18 @@ static int crypto_authenc_encrypt(struct aead_request *req) if (err) return err; - return crypto_authenc_genicv(req, aead_request_flags(req)); + return crypto_authenc_genicv(req, 0); +} + +static void authenc_decrypt_tail_done(struct crypto_async_request *areq, int err) +{ + struct aead_request *req = areq->data; + + authenc_request_complete(req, err); } static int crypto_authenc_decrypt_tail(struct aead_request *req, - unsigned int flags) + unsigned int mask) { struct crypto_aead *authenc = crypto_aead_reqtfm(req); struct aead_instance *inst = aead_alg_instance(authenc); @@ -238,6 +261,7 @@ static int crypto_authenc_decrypt_tail(struct aead_request *req, struct skcipher_request *skreq = (void *)(areq_ctx->tail + ictx->reqoff); unsigned int authsize = crypto_aead_authsize(authenc); + unsigned int flags = aead_request_flags(req) & ~mask; u8 *ihash = ahreq->result + authsize; struct scatterlist *src, *dst; @@ -253,8 +277,10 @@ static int crypto_authenc_decrypt_tail(struct aead_request *req, dst = scatterwalk_ffwd(areq_ctx->dst, req->dst, req->assoclen); skcipher_request_set_tfm(skreq, ctx->enc); - skcipher_request_set_callback(skreq, aead_request_flags(req), - req->base.complete, req->base.data); + skcipher_request_set_callback(skreq, flags, + mask ? authenc_decrypt_tail_done : + req->base.complete, + mask ? req : req->base.data); skcipher_request_set_crypt(skreq, src, dst, req->cryptlen - authsize, req->iv); @@ -266,12 +292,11 @@ static void authenc_verify_ahash_done(struct crypto_async_request *areq, { struct aead_request *req = areq->data; - if (err) - goto out; - - err = crypto_authenc_decrypt_tail(req, 0); - -out: + if (err) { + aead_request_complete(req, err); + return; + } + err = crypto_authenc_decrypt_tail(req, CRYPTO_TFM_REQ_MAY_SLEEP); authenc_request_complete(req, err); } @@ -301,7 +326,7 @@ static int crypto_authenc_decrypt(struct aead_request *req) if (err) return err; - return crypto_authenc_decrypt_tail(req, aead_request_flags(req)); + return crypto_authenc_decrypt_tail(req, 0); } static int crypto_authenc_init_tfm(struct crypto_aead *tfm) diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c index 842f7334db..84e1b637b4 100644 --- a/drivers/dpll/dpll_core.c +++ b/drivers/dpll/dpll_core.c @@ -880,7 +880,10 @@ dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, if (WARN_ON(!ops) || WARN_ON(!ops->state_on_dpll_get) || - WARN_ON(!ops->direction_get)) + WARN_ON(!ops->direction_get) || + WARN_ON(ops->measured_freq_get && + (!dpll_device_ops(dpll)->freq_monitor_get || + !dpll_device_ops(dpll)->freq_monitor_set))) return -EINVAL; mutex_lock(&dpll_lock); diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index 95ae786e98..0ff1658c2d 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -175,6 +175,26 @@ dpll_msg_add_phase_offset_monitor(struct sk_buff *msg, struct dpll_device *dpll, return 0; } +static int +dpll_msg_add_freq_monitor(struct sk_buff *msg, struct dpll_device *dpll, + struct netlink_ext_ack *extack) +{ + const struct dpll_device_ops *ops = dpll_device_ops(dpll); + enum dpll_feature_state state; + int ret; + + if (ops->freq_monitor_set && ops->freq_monitor_get) { + ret = ops->freq_monitor_get(dpll, dpll_priv(dpll), + &state, extack); + if (ret) + return ret; + if (nla_put_u32(msg, DPLL_A_FREQUENCY_MONITOR, state)) + return -EMSGSIZE; + } + + return 0; +} + static int dpll_msg_add_phase_offset_avg_factor(struct sk_buff *msg, struct dpll_device *dpll, @@ -400,6 +420,38 @@ static int dpll_msg_add_ffo(struct sk_buff *msg, struct dpll_pin *pin, ffo); } +static int dpll_msg_add_measured_freq(struct sk_buff *msg, struct dpll_pin *pin, + struct dpll_pin_ref *ref, + struct netlink_ext_ack *extack) +{ + const struct dpll_device_ops *dev_ops = dpll_device_ops(ref->dpll); + const struct dpll_pin_ops *ops = dpll_pin_ops(ref); + struct dpll_device *dpll = ref->dpll; + enum dpll_feature_state state; + u64 measured_freq; + int ret; + + if (!ops->measured_freq_get) + return 0; + ret = dev_ops->freq_monitor_get(dpll, dpll_priv(dpll), + &state, extack); + if (ret) + return ret; + if (state == DPLL_FEATURE_STATE_DISABLE) + return 0; + ret = ops->measured_freq_get(pin, dpll_pin_on_dpll_priv(dpll, pin), + dpll, dpll_priv(dpll), &measured_freq, + extack); + if (ret) + return ret; + if (nla_put_64bit(msg, DPLL_A_PIN_MEASURED_FREQUENCY, + sizeof(measured_freq), &measured_freq, + DPLL_A_PIN_PAD)) + return -EMSGSIZE; + + return 0; +} + static int dpll_msg_add_pin_freq(struct sk_buff *msg, struct dpll_pin *pin, struct dpll_pin_ref *ref, struct netlink_ext_ack *extack) @@ -670,6 +722,9 @@ dpll_cmd_pin_get_one(struct sk_buff *msg, struct dpll_pin *pin, if (ret) return ret; ret = dpll_msg_add_ffo(msg, pin, ref, extack); + if (ret) + return ret; + ret = dpll_msg_add_measured_freq(msg, pin, ref, extack); if (ret) return ret; ret = dpll_msg_add_pin_esync(msg, pin, ref, extack); @@ -722,6 +777,9 @@ dpll_device_get_one(struct dpll_device *dpll, struct sk_buff *msg, if (ret) return ret; ret = dpll_msg_add_phase_offset_avg_factor(msg, dpll, extack); + if (ret) + return ret; + ret = dpll_msg_add_freq_monitor(msg, dpll, extack); if (ret) return ret; @@ -958,6 +1016,32 @@ dpll_phase_offset_avg_factor_set(struct dpll_device *dpll, struct nlattr *a, extack); } +static int +dpll_freq_monitor_set(struct dpll_device *dpll, struct nlattr *a, + struct netlink_ext_ack *extack) +{ + const struct dpll_device_ops *ops = dpll_device_ops(dpll); + enum dpll_feature_state state = nla_get_u32(a), old_state; + int ret; + + if (!(ops->freq_monitor_set && ops->freq_monitor_get)) { + NL_SET_ERR_MSG_ATTR(extack, a, + "dpll device not capable of frequency monitor"); + return -EOPNOTSUPP; + } + ret = ops->freq_monitor_get(dpll, dpll_priv(dpll), &old_state, + extack); + if (ret) { + NL_SET_ERR_MSG(extack, + "unable to get current state of frequency monitor"); + return ret; + } + if (state == old_state) + return 0; + + return ops->freq_monitor_set(dpll, dpll_priv(dpll), state, extack); +} + static int dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, struct netlink_ext_ack *extack) @@ -1888,6 +1972,12 @@ dpll_set_from_nlattr(struct dpll_device *dpll, struct genl_info *info) if (ret) return ret; break; + case DPLL_A_FREQUENCY_MONITOR: + ret = dpll_freq_monitor_set(dpll, a, + info->extack); + if (ret) + return ret; + break; } } diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c index 3fb64aab18..da92aa8f9b 100644 --- a/drivers/dpll/dpll_nl.c +++ b/drivers/dpll/dpll_nl.c @@ -42,11 +42,12 @@ 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_AVG_FACTOR + 1] = { +static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_FREQUENCY_MONITOR + 1] = { [DPLL_A_ID] = { .type = NLA_U32, }, [DPLL_A_MODE] = NLA_POLICY_RANGE(NLA_U32, 1, 2), [DPLL_A_PHASE_OFFSET_MONITOR] = NLA_POLICY_MAX(NLA_U32, 1), [DPLL_A_PHASE_OFFSET_AVG_FACTOR] = { .type = NLA_U32, }, + [DPLL_A_FREQUENCY_MONITOR] = NLA_POLICY_MAX(NLA_U32, 1), }; /* DPLL_CMD_PIN_ID_GET - do */ @@ -114,7 +115,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_AVG_FACTOR, + .maxattr = DPLL_A_FREQUENCY_MONITOR, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { diff --git a/drivers/dpll/zl3073x/Makefile b/drivers/dpll/zl3073x/Makefile index bd324c7fe7..906ec3fbcc 100644 --- a/drivers/dpll/zl3073x/Makefile +++ b/drivers/dpll/zl3073x/Makefile @@ -1,8 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_ZL3073X) += zl3073x.o -zl3073x-objs := core.o devlink.o dpll.o flash.o fw.o \ - out.o prop.o ref.o synth.o +zl3073x-objs := chan.o core.o devlink.o dpll.o \ + flash.o fw.o out.o prop.o ref.o synth.o obj-$(CONFIG_ZL3073X_I2C) += zl3073x_i2c.o zl3073x_i2c-objs := i2c.o diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c new file mode 100644 index 0000000000..2f48ca2391 --- /dev/null +++ b/drivers/dpll/zl3073x/chan.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include + +#include "chan.h" +#include "core.h" + +/** + * zl3073x_chan_state_update - update DPLL channel status from HW + * @zldev: pointer to zl3073x_dev structure + * @index: DPLL channel index + * + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_chan *chan = &zldev->chan[index]; + int rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(index), + &chan->mon_status); + if (rc) + return rc; + + return zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index), + &chan->refsel_status); +} + +/** + * zl3073x_chan_state_fetch - fetch DPLL channel state from hardware + * @zldev: pointer to zl3073x_dev structure + * @index: DPLL channel index to fetch state for + * + * Reads the mode_refsel register and reference priority registers for + * the given DPLL channel and stores the raw values for later use. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_chan *chan = &zldev->chan[index]; + int rc, i; + + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), + &chan->mode_refsel); + if (rc) + return rc; + + dev_dbg(zldev->dev, "DPLL%u mode: %u, ref: %u\n", index, + zl3073x_chan_mode_get(chan), zl3073x_chan_ref_get(chan)); + + rc = zl3073x_chan_state_update(zldev, index); + if (rc) + return rc; + + dev_dbg(zldev->dev, + "DPLL%u lock_state: %u, ho: %u, sel_state: %u, sel_ref: %u\n", + index, zl3073x_chan_lock_state_get(chan), + zl3073x_chan_is_ho_ready(chan) ? 1 : 0, + zl3073x_chan_refsel_state_get(chan), + zl3073x_chan_refsel_ref_get(chan)); + + guard(mutex)(&zldev->multiop_lock); + + /* Read DPLL configuration from mailbox */ + rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD, + ZL_REG_DPLL_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* Read reference priority registers */ + for (i = 0; i < ARRAY_SIZE(chan->ref_prio); i++) { + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(i), + &chan->ref_prio[i]); + if (rc) + return rc; + } + + return 0; +} + +/** + * zl3073x_chan_state_get - get current DPLL channel state + * @zldev: pointer to zl3073x_dev structure + * @index: DPLL channel index to get state for + * + * Return: pointer to given DPLL channel state + */ +const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev, + u8 index) +{ + return &zldev->chan[index]; +} + +/** + * zl3073x_chan_state_set - commit DPLL channel state changes to hardware + * @zldev: pointer to zl3073x_dev structure + * @index: DPLL channel index to set state for + * @chan: desired channel state + * + * Skips the HW write if the configuration is unchanged, and otherwise + * writes only the changed registers to hardware. The mode_refsel register + * is written directly, while the reference priority registers are written + * via the DPLL mailbox interface. + * + * Return: 0 on success, <0 on HW error + */ +int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index, + const struct zl3073x_chan *chan) +{ + struct zl3073x_chan *dchan = &zldev->chan[index]; + int rc, i; + + /* Skip HW write if configuration hasn't changed */ + if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg))) + return 0; + + /* Direct register write for mode_refsel */ + if (dchan->mode_refsel != chan->mode_refsel) { + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index), + chan->mode_refsel); + if (rc) + return rc; + dchan->mode_refsel = chan->mode_refsel; + } + + /* Mailbox write for ref_prio if changed */ + if (!memcmp(dchan->ref_prio, chan->ref_prio, sizeof(chan->ref_prio))) { + dchan->cfg = chan->cfg; + return 0; + } + + guard(mutex)(&zldev->multiop_lock); + + /* Read DPLL configuration into mailbox */ + rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD, + ZL_REG_DPLL_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* Update changed ref_prio registers */ + for (i = 0; i < ARRAY_SIZE(chan->ref_prio); i++) { + if (dchan->ref_prio[i] != chan->ref_prio[i]) { + rc = zl3073x_write_u8(zldev, + ZL_REG_DPLL_REF_PRIO(i), + chan->ref_prio[i]); + if (rc) + return rc; + } + } + + /* Commit DPLL configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_WR, + ZL_REG_DPLL_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* After successful write store new state */ + dchan->cfg = chan->cfg; + + return 0; +} diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h new file mode 100644 index 0000000000..481da21332 --- /dev/null +++ b/drivers/dpll/zl3073x/chan.h @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_CHAN_H +#define _ZL3073X_CHAN_H + +#include +#include +#include + +#include "regs.h" + +struct zl3073x_dev; + +/** + * struct zl3073x_chan - DPLL channel state + * @mode_refsel: mode and reference selection register value + * @ref_prio: reference priority registers (4 bits per ref, P/N packed) + * @mon_status: monitor status register value + * @refsel_status: reference selection status register value + */ +struct zl3073x_chan { + struct_group(cfg, + u8 mode_refsel; + u8 ref_prio[ZL3073X_NUM_REFS / 2]; + ); + struct_group(stat, + u8 mon_status; + u8 refsel_status; + ); +}; + +int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index); +const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev, + u8 index); +int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index, + const struct zl3073x_chan *chan); + +int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index); + +/** + * zl3073x_chan_mode_get - get DPLL channel operating mode + * @chan: pointer to channel state + * + * Return: reference selection mode of the given DPLL channel + */ +static inline u8 zl3073x_chan_mode_get(const struct zl3073x_chan *chan) +{ + return FIELD_GET(ZL_DPLL_MODE_REFSEL_MODE, chan->mode_refsel); +} + +/** + * zl3073x_chan_ref_get - get manually selected reference + * @chan: pointer to channel state + * + * Return: reference selected in forced reference lock mode + */ +static inline u8 zl3073x_chan_ref_get(const struct zl3073x_chan *chan) +{ + return FIELD_GET(ZL_DPLL_MODE_REFSEL_REF, chan->mode_refsel); +} + +/** + * zl3073x_chan_mode_set - set DPLL channel operating mode + * @chan: pointer to channel state + * @mode: mode to set + */ +static inline void zl3073x_chan_mode_set(struct zl3073x_chan *chan, u8 mode) +{ + FIELD_MODIFY(ZL_DPLL_MODE_REFSEL_MODE, &chan->mode_refsel, mode); +} + +/** + * zl3073x_chan_ref_set - set manually selected reference + * @chan: pointer to channel state + * @ref: reference to set + */ +static inline void zl3073x_chan_ref_set(struct zl3073x_chan *chan, u8 ref) +{ + FIELD_MODIFY(ZL_DPLL_MODE_REFSEL_REF, &chan->mode_refsel, ref); +} + +/** + * zl3073x_chan_ref_prio_get - get reference priority + * @chan: pointer to channel state + * @ref: input reference index + * + * Return: priority of the given reference <0, 15> + */ +static inline u8 +zl3073x_chan_ref_prio_get(const struct zl3073x_chan *chan, u8 ref) +{ + u8 val = chan->ref_prio[ref / 2]; + + if (!(ref & 1)) + return FIELD_GET(ZL_DPLL_REF_PRIO_REF_P, val); + else + return FIELD_GET(ZL_DPLL_REF_PRIO_REF_N, val); +} + +/** + * zl3073x_chan_ref_prio_set - set reference priority + * @chan: pointer to channel state + * @ref: input reference index + * @prio: priority to set + */ +static inline void +zl3073x_chan_ref_prio_set(struct zl3073x_chan *chan, u8 ref, u8 prio) +{ + u8 *val = &chan->ref_prio[ref / 2]; + + if (!(ref & 1)) + FIELD_MODIFY(ZL_DPLL_REF_PRIO_REF_P, val, prio); + else + FIELD_MODIFY(ZL_DPLL_REF_PRIO_REF_N, val, prio); +} + +/** + * zl3073x_chan_ref_is_selectable - check if reference is selectable + * @chan: pointer to channel state + * @ref: input reference index + * + * Return: true if the reference priority is not NONE, false otherwise + */ +static inline bool +zl3073x_chan_ref_is_selectable(const struct zl3073x_chan *chan, u8 ref) +{ + return zl3073x_chan_ref_prio_get(chan, ref) != ZL_DPLL_REF_PRIO_NONE; +} + +/** + * zl3073x_chan_lock_state_get - get DPLL channel lock state + * @chan: pointer to channel state + * + * Return: lock state of the given DPLL channel + */ +static inline u8 zl3073x_chan_lock_state_get(const struct zl3073x_chan *chan) +{ + return FIELD_GET(ZL_DPLL_MON_STATUS_STATE, chan->mon_status); +} + +/** + * zl3073x_chan_is_ho_ready - check if holdover is ready + * @chan: pointer to channel state + * + * Return: true if holdover is ready, false otherwise + */ +static inline bool zl3073x_chan_is_ho_ready(const struct zl3073x_chan *chan) +{ + return !!FIELD_GET(ZL_DPLL_MON_STATUS_HO_READY, chan->mon_status); +} + +/** + * zl3073x_chan_refsel_state_get - get reference selection state + * @chan: pointer to channel state + * + * Return: reference selection state of the given DPLL channel + */ +static inline u8 zl3073x_chan_refsel_state_get(const struct zl3073x_chan *chan) +{ + return FIELD_GET(ZL_DPLL_REFSEL_STATUS_STATE, chan->refsel_status); +} + +/** + * zl3073x_chan_refsel_ref_get - get currently selected reference in auto mode + * @chan: pointer to channel state + * + * Return: reference selected by the DPLL in automatic mode + */ +static inline u8 zl3073x_chan_refsel_ref_get(const struct zl3073x_chan *chan) +{ + return FIELD_GET(ZL_DPLL_REFSEL_STATUS_REFSEL, chan->refsel_status); +} + +#endif /* _ZL3073X_CHAN_H */ diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c index 179fff5e7d..024f0a27d2 100644 --- a/drivers/dpll/zl3073x/core.c +++ b/drivers/dpll/zl3073x/core.c @@ -20,78 +20,29 @@ #include "dpll.h" #include "regs.h" -/* Chip IDs for zl30731 */ -static const u16 zl30731_ids[] = { - 0x0E93, - 0x1E93, - 0x2E93, -}; +#define ZL_CHIP_INFO(_id, _nchannels, _flags) \ + { .id = (_id), .num_channels = (_nchannels), .flags = (_flags) } -const struct zl3073x_chip_info zl30731_chip_info = { - .ids = zl30731_ids, - .num_ids = ARRAY_SIZE(zl30731_ids), - .num_channels = 1, +static const struct zl3073x_chip_info zl3073x_chip_ids[] = { + ZL_CHIP_INFO(0x0E30, 2, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x0E93, 1, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x0E94, 2, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x0E95, 3, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x0E96, 4, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x0E97, 5, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x1E93, 1, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x1E94, 2, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x1E95, 3, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x1E96, 4, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x1E97, 5, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x1F60, 2, ZL3073X_FLAG_REF_PHASE_COMP_32), + ZL_CHIP_INFO(0x2E93, 1, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x2E94, 2, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x2E95, 3, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x2E96, 4, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x2E97, 5, ZL3073X_FLAG_DIE_TEMP), + ZL_CHIP_INFO(0x3FC4, 2, ZL3073X_FLAG_DIE_TEMP), }; -EXPORT_SYMBOL_NS_GPL(zl30731_chip_info, ZL3073X); - -/* Chip IDs for zl30732 */ -static const u16 zl30732_ids[] = { - 0x0E30, - 0x0E94, - 0x1E94, - 0x1F60, - 0x2E94, - 0x3FC4, -}; - -const struct zl3073x_chip_info zl30732_chip_info = { - .ids = zl30732_ids, - .num_ids = ARRAY_SIZE(zl30732_ids), - .num_channels = 2, -}; -EXPORT_SYMBOL_NS_GPL(zl30732_chip_info, ZL3073X); - -/* Chip IDs for zl30733 */ -static const u16 zl30733_ids[] = { - 0x0E95, - 0x1E95, - 0x2E95, -}; - -const struct zl3073x_chip_info zl30733_chip_info = { - .ids = zl30733_ids, - .num_ids = ARRAY_SIZE(zl30733_ids), - .num_channels = 3, -}; -EXPORT_SYMBOL_NS_GPL(zl30733_chip_info, ZL3073X); - -/* Chip IDs for zl30734 */ -static const u16 zl30734_ids[] = { - 0x0E96, - 0x1E96, - 0x2E96, -}; - -const struct zl3073x_chip_info zl30734_chip_info = { - .ids = zl30734_ids, - .num_ids = ARRAY_SIZE(zl30734_ids), - .num_channels = 4, -}; -EXPORT_SYMBOL_NS_GPL(zl30734_chip_info, ZL3073X); - -/* Chip IDs for zl30735 */ -static const u16 zl30735_ids[] = { - 0x0E97, - 0x1E97, - 0x2E97, -}; - -const struct zl3073x_chip_info zl30735_chip_info = { - .ids = zl30735_ids, - .num_ids = ARRAY_SIZE(zl30735_ids), - .num_channels = 5, -}; -EXPORT_SYMBOL_NS_GPL(zl30735_chip_info, ZL3073X); #define ZL_RANGE_OFFSET 0x80 #define ZL_PAGE_SIZE 0x80 @@ -588,17 +539,26 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev) } } + for (i = 0; i < zldev->info->num_channels; i++) { + rc = zl3073x_chan_state_fetch(zldev, i); + if (rc) { + dev_err(zldev->dev, + "Failed to fetch channel state: %pe\n", + ERR_PTR(rc)); + return rc; + } + } + return rc; } static void -zl3073x_dev_ref_status_update(struct zl3073x_dev *zldev) +zl3073x_dev_ref_states_update(struct zl3073x_dev *zldev) { int i, rc; for (i = 0; i < ZL3073X_NUM_REFS; i++) { - rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(i), - &zldev->ref[i].mon_status); + rc = zl3073x_ref_state_update(zldev, i); if (rc) dev_warn(zldev->dev, "Failed to get REF%u status: %pe\n", i, @@ -606,6 +566,20 @@ zl3073x_dev_ref_status_update(struct zl3073x_dev *zldev) } } +static void +zl3073x_dev_chan_states_update(struct zl3073x_dev *zldev) +{ + int i, rc; + + for (i = 0; i < zldev->info->num_channels; i++) { + rc = zl3073x_chan_state_update(zldev, i); + if (rc) + dev_warn(zldev->dev, + "Failed to get DPLL%u state: %pe\n", i, + ERR_PTR(rc)); + } +} + /** * zl3073x_ref_phase_offsets_update - update reference phase offsets * @zldev: pointer to zl3073x_dev structure @@ -658,22 +632,21 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel) } /** - * zl3073x_ref_ffo_update - update reference fractional frequency offsets + * zl3073x_ref_freq_meas_latch - latch reference frequency measurements * @zldev: pointer to zl3073x_dev structure + * @type: measurement type (ZL_REF_FREQ_MEAS_CTRL_*) * - * The function asks device to update fractional frequency offsets latch - * registers the latest measured values, reads and stores them into + * The function waits for the previous measurement to finish, selects all + * references and requests a new measurement of the given type. * * Return: 0 on success, <0 on error */ static int -zl3073x_ref_ffo_update(struct zl3073x_dev *zldev) +zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type) { - int i, rc; + int rc; - /* Per datasheet we have to wait for 'ref_freq_meas_ctrl' to be zero - * to ensure that the measured data are coherent. - */ + /* Wait for previous measurement to finish */ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, ZL_REF_FREQ_MEAS_CTRL); if (rc) @@ -689,15 +662,64 @@ zl3073x_ref_ffo_update(struct zl3073x_dev *zldev) if (rc) return rc; - /* Request frequency offset measurement */ - rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, - ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF); + /* Request measurement */ + rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, type); if (rc) return rc; /* Wait for finish */ - rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, - ZL_REF_FREQ_MEAS_CTRL); + return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, + ZL_REF_FREQ_MEAS_CTRL); +} + +/** + * zl3073x_ref_freq_meas_update - update measured input reference frequencies + * @zldev: pointer to zl3073x_dev structure + * + * The function asks device to latch measured input reference frequencies + * and stores the results in the ref state. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_ref_freq_meas_update(struct zl3073x_dev *zldev) +{ + int i, rc; + + rc = zl3073x_ref_freq_meas_latch(zldev, ZL_REF_FREQ_MEAS_CTRL_REF_FREQ); + if (rc) + return rc; + + /* Read measured frequencies in Hz (unsigned 32-bit, LSB = 1 Hz) */ + for (i = 0; i < ZL3073X_NUM_REFS; i++) { + u32 value; + + rc = zl3073x_read_u32(zldev, ZL_REG_REF_FREQ(i), &value); + if (rc) + return rc; + + zldev->ref[i].meas_freq = value; + } + + return 0; +} + +/** + * zl3073x_ref_ffo_update - update reference fractional frequency offsets + * @zldev: pointer to zl3073x_dev structure + * + * The function asks device to latch the latest measured fractional + * frequency offset values, reads and stores them into the ref state. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_ref_ffo_update(struct zl3073x_dev *zldev) +{ + int i, rc; + + rc = zl3073x_ref_freq_meas_latch(zldev, + ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF); if (rc) return rc; @@ -728,8 +750,11 @@ zl3073x_dev_periodic_work(struct kthread_work *work) struct zl3073x_dpll *zldpll; int rc; - /* Update input references status */ - zl3073x_dev_ref_status_update(zldev); + /* Update input references' states */ + zl3073x_dev_ref_states_update(zldev); + + /* Update DPLL channels' states */ + zl3073x_dev_chan_states_update(zldev); /* Update DPLL-to-connected-ref phase offsets registers */ rc = zl3073x_ref_phase_offsets_update(zldev, -1); @@ -737,6 +762,20 @@ zl3073x_dev_periodic_work(struct kthread_work *work) dev_warn(zldev->dev, "Failed to update phase offsets: %pe\n", ERR_PTR(rc)); + /* Update measured input reference frequencies if any DPLL has + * frequency monitoring enabled. + */ + list_for_each_entry(zldpll, &zldev->dplls, list) { + if (zldpll->freq_monitor) { + rc = zl3073x_ref_freq_meas_update(zldev); + if (rc) + dev_warn(zldev->dev, + "Failed to update measured frequencies: %pe\n", + ERR_PTR(rc)); + break; + } + } + /* Update references' fractional frequency offsets */ rc = zl3073x_ref_ffo_update(zldev); if (rc) @@ -766,8 +805,7 @@ int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor) 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); + FIELD_MODIFY(ZL_DPLL_MEAS_CTRL_AVG_FACTOR, &dpll_meas_ctrl, value); rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, dpll_meas_ctrl); if (rc) return rc; @@ -942,7 +980,7 @@ static void zl3073x_dev_dpll_fini(void *ptr) } static int -zl3073x_devm_dpll_init(struct zl3073x_dev *zldev, u8 num_dplls) +zl3073x_devm_dpll_init(struct zl3073x_dev *zldev) { struct kthread_worker *kworker; struct zl3073x_dpll *zldpll; @@ -952,7 +990,7 @@ zl3073x_devm_dpll_init(struct zl3073x_dev *zldev, u8 num_dplls) INIT_LIST_HEAD(&zldev->dplls); /* Allocate all DPLLs */ - for (i = 0; i < num_dplls; i++) { + for (i = 0; i < zldev->info->num_channels; i++) { zldpll = zl3073x_dpll_alloc(zldev, i); if (IS_ERR(zldpll)) { dev_err_probe(zldev->dev, PTR_ERR(zldpll), @@ -994,14 +1032,12 @@ error: /** * zl3073x_dev_probe - initialize zl3073x device * @zldev: pointer to zl3073x device - * @chip_info: chip info based on compatible * * Common initialization of zl3073x device structure. * * Returns: 0 on success, <0 on error */ -int zl3073x_dev_probe(struct zl3073x_dev *zldev, - const struct zl3073x_chip_info *chip_info) +int zl3073x_dev_probe(struct zl3073x_dev *zldev) { u16 id, revision, fw_ver; unsigned int i; @@ -1013,18 +1049,17 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev, if (rc) return rc; - /* Check it matches */ - for (i = 0; i < chip_info->num_ids; i++) { - if (id == chip_info->ids[i]) + /* Detect chip variant */ + for (i = 0; i < ARRAY_SIZE(zl3073x_chip_ids); i++) { + if (zl3073x_chip_ids[i].id == id) break; } - if (i == chip_info->num_ids) { + if (i == ARRAY_SIZE(zl3073x_chip_ids)) return dev_err_probe(zldev->dev, -ENODEV, - "Unknown or non-match chip ID: 0x%0x\n", - id); - } - zldev->chip_id = id; + "Unknown chip ID: 0x%04x\n", id); + + zldev->info = &zl3073x_chip_ids[i]; /* Read revision, firmware version and custom config version */ rc = zl3073x_read_u16(zldev, ZL_REG_REVISION, &revision); @@ -1063,7 +1098,7 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev, "Failed to initialize mutex\n"); /* Register DPLL channels */ - rc = zl3073x_devm_dpll_init(zldev, chip_info->num_channels); + rc = zl3073x_devm_dpll_init(zldev); if (rc) return rc; diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h index fd2af3c62a..9944062040 100644 --- a/drivers/dpll/zl3073x/core.h +++ b/drivers/dpll/zl3073x/core.h @@ -9,6 +9,7 @@ #include #include +#include "chan.h" #include "out.h" #include "ref.h" #include "regs.h" @@ -18,27 +19,39 @@ struct device; struct regmap; struct zl3073x_dpll; -/* - * Hardware limits for ZL3073x chip family + +enum zl3073x_flags { + ZL3073X_FLAG_REF_PHASE_COMP_32_BIT, + ZL3073X_FLAG_DIE_TEMP_BIT, + ZL3073X_FLAGS_NBITS /* must be last */ +}; + +#define __ZL3073X_FLAG(name) BIT(ZL3073X_FLAG_ ## name ## _BIT) +#define ZL3073X_FLAG_REF_PHASE_COMP_32 __ZL3073X_FLAG(REF_PHASE_COMP_32) +#define ZL3073X_FLAG_DIE_TEMP __ZL3073X_FLAG(DIE_TEMP) + +/** + * struct zl3073x_chip_info - chip variant identification + * @id: chip ID + * @num_channels: number of DPLL channels supported by this variant + * @flags: chip variant flags */ -#define ZL3073X_MAX_CHANNELS 5 -#define ZL3073X_NUM_REFS 10 -#define ZL3073X_NUM_OUTS 10 -#define ZL3073X_NUM_SYNTHS 5 -#define ZL3073X_NUM_INPUT_PINS ZL3073X_NUM_REFS -#define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2) -#define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \ - ZL3073X_NUM_OUTPUT_PINS) +struct zl3073x_chip_info { + u16 id; + u8 num_channels; + unsigned long flags; +}; /** * struct zl3073x_dev - zl3073x device * @dev: pointer to device * @regmap: regmap to access device registers + * @info: detected chip info * @multiop_lock: to serialize multiple register operations - * @chip_id: chip ID read from hardware * @ref: array of input references' invariants * @out: array of outs' invariants * @synth: array of synths' invariants + * @chan: array of DPLL channels' state * @dplls: list of DPLLs * @kworker: thread for periodic work * @work: periodic work @@ -46,15 +59,16 @@ struct zl3073x_dpll; * @phase_avg_factor: phase offset measurement averaging factor */ struct zl3073x_dev { - struct device *dev; - struct regmap *regmap; - struct mutex multiop_lock; - u16 chip_id; + struct device *dev; + struct regmap *regmap; + const struct zl3073x_chip_info *info; + struct mutex multiop_lock; /* Invariants */ struct zl3073x_ref ref[ZL3073X_NUM_REFS]; struct zl3073x_out out[ZL3073X_NUM_OUTS]; struct zl3073x_synth synth[ZL3073X_NUM_SYNTHS]; + struct zl3073x_chan chan[ZL3073X_MAX_CHANNELS]; /* DPLL channels */ struct list_head dplls; @@ -68,22 +82,10 @@ struct zl3073x_dev { u8 phase_avg_factor; }; -struct zl3073x_chip_info { - const u16 *ids; - size_t num_ids; - int num_channels; -}; - -extern const struct zl3073x_chip_info zl30731_chip_info; -extern const struct zl3073x_chip_info zl30732_chip_info; -extern const struct zl3073x_chip_info zl30733_chip_info; -extern const struct zl3073x_chip_info zl30734_chip_info; -extern const struct zl3073x_chip_info zl30735_chip_info; extern const struct regmap_config zl3073x_regmap_config; struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev); -int zl3073x_dev_probe(struct zl3073x_dev *zldev, - const struct zl3073x_chip_info *chip_info); +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); @@ -158,18 +160,7 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel); static inline bool zl3073x_dev_is_ref_phase_comp_32bit(struct zl3073x_dev *zldev) { - switch (zldev->chip_id) { - case 0x0E30: - case 0x0E93: - case 0x0E94: - case 0x0E95: - case 0x0E96: - case 0x0E97: - case 0x1F60: - return true; - default: - return false; - } + return zldev->info->flags & ZL3073X_FLAG_REF_PHASE_COMP_32; } static inline bool diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index 8ffbede117..55195dcd21 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -30,32 +31,34 @@ * @dpll: DPLL the pin is registered to * @dpll_pin: pointer to registered dpll_pin * @tracker: tracking object for the acquired reference + * @fwnode: firmware node handle * @label: package label * @dir: pin direction * @id: pin id * @prio: pin priority <0, 14> - * @selectable: pin is selectable in automatic mode * @esync_control: embedded sync is controllable * @phase_gran: phase adjustment granularity * @pin_state: last saved pin state * @phase_offset: last saved pin phase offset * @freq_offset: last saved fractional frequency offset + * @measured_freq: last saved measured frequency */ struct zl3073x_dpll_pin { struct list_head list; struct zl3073x_dpll *dpll; struct dpll_pin *dpll_pin; dpll_tracker tracker; + struct fwnode_handle *fwnode; char label[8]; enum dpll_pin_direction dir; u8 id; u8 prio; - bool selectable; bool esync_control; s32 phase_gran; enum dpll_pin_state pin_state; s64 phase_offset; s64 freq_offset; + u32 measured_freq; }; /* @@ -133,7 +136,13 @@ zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin, ref_id = zl3073x_input_pin_ref_get(pin->id); ref = zl3073x_ref_state_get(zldev, ref_id); - switch (FIELD_GET(ZL_REF_SYNC_CTRL_MODE, ref->sync_ctrl)) { + if (!pin->esync_control || zl3073x_ref_freq_get(ref) <= 1) + return -EOPNOTSUPP; + + esync->range = esync_freq_ranges; + esync->range_num = ARRAY_SIZE(esync_freq_ranges); + + switch (zl3073x_ref_sync_mode_get(ref)) { case ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75: esync->freq = ref->esync_n_div == ZL_REF_ESYNC_DIV_1HZ ? 1 : 0; esync->pulse = 25; @@ -144,17 +153,6 @@ zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin, break; } - /* If the pin supports esync control expose its range but only - * if the current reference frequency is > 1 Hz. - */ - if (pin->esync_control && zl3073x_ref_freq_get(ref) > 1) { - esync->range = esync_freq_ranges; - esync->range_num = ARRAY_SIZE(esync_freq_ranges); - } else { - esync->range = NULL; - esync->range_num = 0; - } - return 0; } @@ -180,8 +178,7 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin, else sync_mode = ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75; - ref.sync_ctrl &= ~ZL_REF_SYNC_CTRL_MODE; - ref.sync_ctrl |= FIELD_PREP(ZL_REF_SYNC_CTRL_MODE, sync_mode); + zl3073x_ref_sync_mode_set(&ref, sync_mode); if (freq) { /* 1 Hz is only supported frequency now */ @@ -192,6 +189,109 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin, return zl3073x_ref_state_set(zldev, ref_id, &ref); } +static int +zl3073x_dpll_input_pin_ref_sync_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_pin *ref_sync_pin, + void *ref_sync_pin_priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll_pin *sync_pin = ref_sync_pin_priv; + struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_ref *ref; + u8 ref_id, mode, pair; + + ref_id = zl3073x_input_pin_ref_get(pin->id); + ref = zl3073x_ref_state_get(zldev, ref_id); + mode = zl3073x_ref_sync_mode_get(ref); + pair = zl3073x_ref_sync_pair_get(ref); + + if (mode == ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR && + pair == zl3073x_input_pin_ref_get(sync_pin->id)) + *state = DPLL_PIN_STATE_CONNECTED; + else + *state = DPLL_PIN_STATE_DISCONNECTED; + + return 0; +} + +static int +zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_pin *ref_sync_pin, + void *ref_sync_pin_priv, + const enum dpll_pin_state state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll_pin *sync_pin = ref_sync_pin_priv; + struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + u8 mode, ref_id, sync_ref_id; + struct zl3073x_chan chan; + struct zl3073x_ref ref; + int rc; + + ref_id = zl3073x_input_pin_ref_get(pin->id); + sync_ref_id = zl3073x_input_pin_ref_get(sync_pin->id); + ref = *zl3073x_ref_state_get(zldev, ref_id); + + if (state == DPLL_PIN_STATE_CONNECTED) { + const struct zl3073x_ref *sync_ref; + u32 ref_freq, sync_freq; + + sync_ref = zl3073x_ref_state_get(zldev, sync_ref_id); + ref_freq = zl3073x_ref_freq_get(&ref); + sync_freq = zl3073x_ref_freq_get(sync_ref); + + /* Sync signal must be 8 kHz or less and clock reference + * must be 1 kHz or more and higher than the sync signal. + */ + if (sync_freq > 8000) { + NL_SET_ERR_MSG(extack, + "sync frequency must be 8 kHz or less"); + return -EINVAL; + } + if (ref_freq < 1000) { + NL_SET_ERR_MSG(extack, + "clock frequency must be 1 kHz or more"); + return -EINVAL; + } + if (ref_freq <= sync_freq) { + NL_SET_ERR_MSG(extack, + "clock frequency must be higher than sync frequency"); + return -EINVAL; + } + + zl3073x_ref_sync_pair_set(&ref, sync_ref_id); + mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR; + } else { + mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF; + } + + zl3073x_ref_sync_mode_set(&ref, mode); + + rc = zl3073x_ref_state_set(zldev, ref_id, &ref); + if (rc) + return rc; + + /* Exclude sync source from automatic reference selection by setting + * its priority to NONE. On disconnect the priority is left as NONE + * and the user must explicitly make the pin selectable again. + */ + if (state == DPLL_PIN_STATE_CONNECTED) { + chan = *zl3073x_chan_state_get(zldev, zldpll->id); + zl3073x_chan_ref_prio_set(&chan, sync_ref_id, + ZL_DPLL_REF_PRIO_NONE); + return zl3073x_chan_state_set(zldev, zldpll->id, &chan); + } + + return 0; +} + static int zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, @@ -204,6 +304,21 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv, return 0; } +static int +zl3073x_dpll_input_pin_measured_freq_get(const struct dpll_pin *dpll_pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 *measured_freq, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll_pin *pin = pin_priv; + + *measured_freq = pin->measured_freq; + *measured_freq *= DPLL_PIN_MEASURED_FREQUENCY_DIVIDER; + + return 0; +} + static int zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin, void *pin_priv, @@ -245,156 +360,27 @@ zl3073x_dpll_input_pin_frequency_set(const struct dpll_pin *dpll_pin, return zl3073x_ref_state_set(zldev, ref_id, &ref); } -/** - * zl3073x_dpll_selected_ref_get - get currently selected reference - * @zldpll: pointer to zl3073x_dpll - * @ref: place to store selected reference - * - * Check for currently selected reference the DPLL should be locked to - * and stores its index to given @ref. - * - * Return: 0 on success, <0 on error - */ -static int -zl3073x_dpll_selected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref) -{ - struct zl3073x_dev *zldev = zldpll->dev; - u8 state, value; - int rc; - - switch (zldpll->refsel_mode) { - case ZL_DPLL_MODE_REFSEL_MODE_AUTO: - /* For automatic mode read refsel_status register */ - rc = zl3073x_read_u8(zldev, - ZL_REG_DPLL_REFSEL_STATUS(zldpll->id), - &value); - if (rc) - return rc; - - /* Extract reference state */ - state = FIELD_GET(ZL_DPLL_REFSEL_STATUS_STATE, value); - - /* Return the reference only if the DPLL is locked to it */ - if (state == ZL_DPLL_REFSEL_STATUS_STATE_LOCK) - *ref = FIELD_GET(ZL_DPLL_REFSEL_STATUS_REFSEL, value); - else - *ref = ZL3073X_DPLL_REF_NONE; - break; - case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: - /* For manual mode return stored value */ - *ref = zldpll->forced_ref; - break; - default: - /* For other modes like NCO, freerun... there is no input ref */ - *ref = ZL3073X_DPLL_REF_NONE; - break; - } - - return 0; -} - -/** - * zl3073x_dpll_selected_ref_set - select reference in manual mode - * @zldpll: pointer to zl3073x_dpll - * @ref: input reference to be selected - * - * Selects the given reference for the DPLL channel it should be - * locked to. - * - * Return: 0 on success, <0 on error - */ -static int -zl3073x_dpll_selected_ref_set(struct zl3073x_dpll *zldpll, u8 ref) -{ - struct zl3073x_dev *zldev = zldpll->dev; - u8 mode, mode_refsel; - int rc; - - mode = zldpll->refsel_mode; - - switch (mode) { - case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: - /* Manual mode with ref selected */ - if (ref == ZL3073X_DPLL_REF_NONE) { - switch (zldpll->lock_status) { - case DPLL_LOCK_STATUS_LOCKED_HO_ACQ: - case DPLL_LOCK_STATUS_HOLDOVER: - /* Switch to forced holdover */ - mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER; - break; - default: - /* Switch to freerun */ - mode = ZL_DPLL_MODE_REFSEL_MODE_FREERUN; - break; - } - /* Keep selected reference */ - ref = zldpll->forced_ref; - } else if (ref == zldpll->forced_ref) { - /* No register update - same mode and same ref */ - return 0; - } - break; - case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: - case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER: - /* Manual mode without no ref */ - if (ref == ZL3073X_DPLL_REF_NONE) - /* No register update - keep current mode */ - return 0; - - /* Switch to reflock mode and update ref selection */ - mode = ZL_DPLL_MODE_REFSEL_MODE_REFLOCK; - break; - default: - /* For other modes like automatic or NCO ref cannot be selected - * manually - */ - return -EOPNOTSUPP; - } - - /* Build mode_refsel value */ - mode_refsel = FIELD_PREP(ZL_DPLL_MODE_REFSEL_MODE, mode) | - FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref); - - /* Update dpll_mode_refsel register */ - rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id), - mode_refsel); - if (rc) - return rc; - - /* Store new mode and forced reference */ - zldpll->refsel_mode = mode; - zldpll->forced_ref = ref; - - return rc; -} - /** * zl3073x_dpll_connected_ref_get - get currently connected reference * @zldpll: pointer to zl3073x_dpll - * @ref: place to store selected reference * - * Looks for currently connected the DPLL is locked to and stores its index - * to given @ref. + * Looks for currently connected reference the DPLL is locked to. * - * Return: 0 on success, <0 on error + * Return: reference index if locked, ZL3073X_DPLL_REF_NONE otherwise */ -static int -zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref) +static u8 +zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll) { - struct zl3073x_dev *zldev = zldpll->dev; - int rc; + const struct zl3073x_chan *chan = zl3073x_chan_state_get(zldpll->dev, + zldpll->id); + u8 state; - /* Get currently selected input reference */ - rc = zl3073x_dpll_selected_ref_get(zldpll, ref); - if (rc) - return rc; + /* A reference is connected only when the DPLL is locked to it */ + state = zl3073x_chan_refsel_state_get(chan); + if (state == ZL_DPLL_REFSEL_STATUS_STATE_LOCK) + return zl3073x_chan_refsel_ref_get(chan); - /* If the monitor indicates an error nothing is connected */ - if (ZL3073X_DPLL_REF_IS_VALID(*ref) && - !zl3073x_dev_ref_is_status_ok(zldev, *ref)) - *ref = ZL3073X_DPLL_REF_NONE; - - return 0; + return ZL3073X_DPLL_REF_NONE; } static int @@ -410,12 +396,9 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin, const struct zl3073x_ref *ref; u8 conn_id, ref_id; s64 ref_phase; - int rc; /* Get currently connected reference */ - rc = zl3073x_dpll_connected_ref_get(zldpll, &conn_id); - if (rc) - return rc; + conn_id = zl3073x_dpll_connected_ref_get(zldpll); /* Report phase offset only for currently connected pin if the phase * monitor feature is disabled and only if the input pin signal is @@ -453,7 +436,7 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin, *phase_offset = ref_phase * DPLL_PHASE_OFFSET_DIVIDER; - return rc; + return 0; } static int @@ -516,98 +499,6 @@ zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, return zl3073x_ref_state_set(zldev, ref_id, &ref); } -/** - * zl3073x_dpll_ref_prio_get - get priority for given input pin - * @pin: pointer to pin - * @prio: place to store priority - * - * Reads current priority for the given input pin and stores the value - * to @prio. - * - * Return: 0 on success, <0 on error - */ -static int -zl3073x_dpll_ref_prio_get(struct zl3073x_dpll_pin *pin, u8 *prio) -{ - struct zl3073x_dpll *zldpll = pin->dpll; - struct zl3073x_dev *zldev = zldpll->dev; - u8 ref, ref_prio; - int rc; - - guard(mutex)(&zldev->multiop_lock); - - /* Read DPLL configuration */ - rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD, - ZL_REG_DPLL_MB_MASK, BIT(zldpll->id)); - if (rc) - return rc; - - /* Read reference priority - one value for P&N pins (4 bits/pin) */ - ref = zl3073x_input_pin_ref_get(pin->id); - rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(ref / 2), - &ref_prio); - if (rc) - return rc; - - /* Select nibble according pin type */ - if (zl3073x_dpll_is_p_pin(pin)) - *prio = FIELD_GET(ZL_DPLL_REF_PRIO_REF_P, ref_prio); - else - *prio = FIELD_GET(ZL_DPLL_REF_PRIO_REF_N, ref_prio); - - return rc; -} - -/** - * zl3073x_dpll_ref_prio_set - set priority for given input pin - * @pin: pointer to pin - * @prio: place to store priority - * - * Sets priority for the given input pin. - * - * Return: 0 on success, <0 on error - */ -static int -zl3073x_dpll_ref_prio_set(struct zl3073x_dpll_pin *pin, u8 prio) -{ - struct zl3073x_dpll *zldpll = pin->dpll; - struct zl3073x_dev *zldev = zldpll->dev; - u8 ref, ref_prio; - int rc; - - guard(mutex)(&zldev->multiop_lock); - - /* Read DPLL configuration into mailbox */ - rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD, - ZL_REG_DPLL_MB_MASK, BIT(zldpll->id)); - if (rc) - return rc; - - /* Read reference priority - one value shared between P&N pins */ - ref = zl3073x_input_pin_ref_get(pin->id); - rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(ref / 2), &ref_prio); - if (rc) - return rc; - - /* Update nibble according pin type */ - if (zl3073x_dpll_is_p_pin(pin)) { - ref_prio &= ~ZL_DPLL_REF_PRIO_REF_P; - ref_prio |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_P, prio); - } else { - ref_prio &= ~ZL_DPLL_REF_PRIO_REF_N; - ref_prio |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_N, prio); - } - - /* Update reference priority */ - rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_REF_PRIO(ref / 2), ref_prio); - if (rc) - return rc; - - /* Commit configuration */ - return zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_WR, - ZL_REG_DPLL_MB_MASK, BIT(zldpll->id)); -} - /** * zl3073x_dpll_ref_state_get - get status for given input pin * @pin: pointer to pin @@ -624,17 +515,14 @@ zl3073x_dpll_ref_state_get(struct zl3073x_dpll_pin *pin, { struct zl3073x_dpll *zldpll = pin->dpll; struct zl3073x_dev *zldev = zldpll->dev; - u8 ref, ref_conn; - int rc; + const struct zl3073x_chan *chan; + u8 ref; + chan = zl3073x_chan_state_get(zldev, zldpll->id); ref = zl3073x_input_pin_ref_get(pin->id); - /* Get currently connected reference */ - rc = zl3073x_dpll_connected_ref_get(zldpll, &ref_conn); - if (rc) - return rc; - - if (ref == ref_conn) { + /* Check if the pin reference is connected */ + if (ref == zl3073x_dpll_connected_ref_get(zldpll)) { *state = DPLL_PIN_STATE_CONNECTED; return 0; } @@ -643,8 +531,9 @@ zl3073x_dpll_ref_state_get(struct zl3073x_dpll_pin *pin, * selectable and its monitor does not report any error then report * pin as selectable. */ - if (zldpll->refsel_mode == ZL_DPLL_MODE_REFSEL_MODE_AUTO && - zl3073x_dev_ref_is_status_ok(zldev, ref) && pin->selectable) { + if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO && + zl3073x_dev_ref_is_status_ok(zldev, ref) && + zl3073x_chan_ref_is_selectable(chan, ref)) { *state = DPLL_PIN_STATE_SELECTABLE; return 0; } @@ -678,68 +567,81 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin, { struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; - u8 new_ref; + struct zl3073x_chan chan; + u8 mode, ref; int rc; - switch (zldpll->refsel_mode) { + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + ref = zl3073x_input_pin_ref_get(pin->id); + mode = zl3073x_chan_mode_get(&chan); + + switch (mode) { case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK: + if (state == DPLL_PIN_STATE_CONNECTED) { + /* Choose the pin as new selected reference */ + zl3073x_chan_ref_set(&chan, ref); + } else if (state == DPLL_PIN_STATE_DISCONNECTED) { + /* Choose new mode based on lock status */ + switch (zldpll->lock_status) { + case DPLL_LOCK_STATUS_LOCKED_HO_ACQ: + case DPLL_LOCK_STATUS_HOLDOVER: + mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER; + break; + default: + mode = ZL_DPLL_MODE_REFSEL_MODE_FREERUN; + break; + } + zl3073x_chan_mode_set(&chan, mode); + } else { + goto invalid_state; + } + break; case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER: if (state == DPLL_PIN_STATE_CONNECTED) { /* Choose the pin as new selected reference */ - new_ref = zl3073x_input_pin_ref_get(pin->id); - } else if (state == DPLL_PIN_STATE_DISCONNECTED) { - /* No reference */ - new_ref = ZL3073X_DPLL_REF_NONE; - } else { - NL_SET_ERR_MSG_MOD(extack, - "Invalid pin state for manual mode"); - return -EINVAL; + zl3073x_chan_ref_set(&chan, ref); + /* Switch to reflock mode */ + zl3073x_chan_mode_set(&chan, + ZL_DPLL_MODE_REFSEL_MODE_REFLOCK); + } else if (state != DPLL_PIN_STATE_DISCONNECTED) { + goto invalid_state; } - - rc = zl3073x_dpll_selected_ref_set(zldpll, new_ref); break; - case ZL_DPLL_MODE_REFSEL_MODE_AUTO: if (state == DPLL_PIN_STATE_SELECTABLE) { - if (pin->selectable) + if (zl3073x_chan_ref_is_selectable(&chan, ref)) return 0; /* Pin is already selectable */ /* Restore pin priority in HW */ - rc = zl3073x_dpll_ref_prio_set(pin, pin->prio); - if (rc) - return rc; - - /* Mark pin as selectable */ - pin->selectable = true; + zl3073x_chan_ref_prio_set(&chan, ref, pin->prio); } else if (state == DPLL_PIN_STATE_DISCONNECTED) { - if (!pin->selectable) + if (!zl3073x_chan_ref_is_selectable(&chan, ref)) return 0; /* Pin is already disconnected */ /* Set pin priority to none in HW */ - rc = zl3073x_dpll_ref_prio_set(pin, - ZL_DPLL_REF_PRIO_NONE); - if (rc) - return rc; - - /* Mark pin as non-selectable */ - pin->selectable = false; + zl3073x_chan_ref_prio_set(&chan, ref, + ZL_DPLL_REF_PRIO_NONE); } else { - NL_SET_ERR_MSG(extack, - "Invalid pin state for automatic mode"); - return -EINVAL; + goto invalid_state; } break; - default: /* In other modes we cannot change input reference */ NL_SET_ERR_MSG(extack, "Pin state cannot be changed in current mode"); - rc = -EOPNOTSUPP; - break; + return -EOPNOTSUPP; } - return rc; + /* Commit DPLL channel changes */ + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); + if (rc) + return rc; + + return 0; +invalid_state: + NL_SET_ERR_MSG_MOD(extack, "Invalid pin state for this device mode"); + return -EINVAL; } static int @@ -759,15 +661,21 @@ zl3073x_dpll_input_pin_prio_set(const struct dpll_pin *dpll_pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, u32 prio, struct netlink_ext_ack *extack) { + struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dpll_pin *pin = pin_priv; + struct zl3073x_chan chan; + u8 ref; int rc; if (prio > ZL_DPLL_REF_PRIO_MAX) return -EINVAL; /* If the pin is selectable then update HW registers */ - if (pin->selectable) { - rc = zl3073x_dpll_ref_prio_set(pin, prio); + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + ref = zl3073x_input_pin_ref_get(pin->id); + if (zl3073x_chan_ref_is_selectable(&chan, ref)) { + zl3073x_chan_ref_prio_set(&chan, ref, prio); + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); if (rc) return rc; } @@ -791,8 +699,8 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, struct zl3073x_dpll_pin *pin = pin_priv; const struct zl3073x_synth *synth; const struct zl3073x_out *out; - u8 clock_type, out_id; - u32 synth_freq; + u32 synth_freq, out_freq; + u8 out_id; out_id = zl3073x_output_pin_out_get(pin->id); out = zl3073x_out_state_get(zldev, out_id); @@ -801,29 +709,30 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, * for N-division is also used for the esync divider so both cannot * be used. */ - switch (zl3073x_out_signal_format_get(out)) { - case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: - case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: + if (zl3073x_out_is_ndiv(out)) return -EOPNOTSUPP; - default: - break; - } /* Get attached synth frequency */ synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(out)); synth_freq = zl3073x_synth_freq_get(synth); + out_freq = synth_freq / out->div; - clock_type = FIELD_GET(ZL_OUTPUT_MODE_CLOCK_TYPE, out->mode); - if (clock_type != ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC) { + if (!pin->esync_control || out_freq <= 1) + return -EOPNOTSUPP; + + esync->range = esync_freq_ranges; + esync->range_num = ARRAY_SIZE(esync_freq_ranges); + + if (zl3073x_out_clock_type_get(out) != ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC) { /* No need to read esync data if it is not enabled */ esync->freq = 0; esync->pulse = 0; - goto finish; + return 0; } /* Compute esync frequency */ - esync->freq = synth_freq / out->div / out->esync_n_period; + esync->freq = out_freq / out->esync_n_period; /* By comparing the esync_pulse_width to the half of the pulse width * the esync pulse percentage can be determined. @@ -832,18 +741,6 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, */ esync->pulse = (50 * out->esync_n_width) / out->div; -finish: - /* Set supported esync ranges if the pin supports esync control and - * if the output frequency is > 1 Hz. - */ - if (pin->esync_control && (synth_freq / out->div) > 1) { - esync->range = esync_freq_ranges; - esync->range_num = ARRAY_SIZE(esync_freq_ranges); - } else { - esync->range = NULL; - esync->range_num = 0; - } - return 0; } @@ -859,8 +756,8 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, struct zl3073x_dpll_pin *pin = pin_priv; const struct zl3073x_synth *synth; struct zl3073x_out out; - u8 clock_type, out_id; u32 synth_freq; + u8 out_id; out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -869,23 +766,16 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, * for N-division is also used for the esync divider so both cannot * be used. */ - switch (zl3073x_out_signal_format_get(&out)) { - case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: - case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: + if (zl3073x_out_is_ndiv(&out)) return -EOPNOTSUPP; - default: - break; - } - - /* Select clock type */ - if (freq) - clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC; - else - clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL; /* Update clock type in output mode */ - out.mode &= ~ZL_OUTPUT_MODE_CLOCK_TYPE; - out.mode |= FIELD_PREP(ZL_OUTPUT_MODE_CLOCK_TYPE, clock_type); + if (freq) + zl3073x_out_clock_type_set(&out, + ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC); + else + zl3073x_out_clock_type_set(&out, + ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL); /* If esync is being disabled just write mailbox and finish */ if (!freq) @@ -937,9 +827,9 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin, struct zl3073x_dev *zldev = zldpll->dev; struct zl3073x_dpll_pin *pin = pin_priv; const struct zl3073x_synth *synth; - u8 out_id, signal_format; u32 new_div, synth_freq; struct zl3073x_out out; + u8 out_id; out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); @@ -949,12 +839,8 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin, synth_freq = zl3073x_synth_freq_get(synth); new_div = synth_freq / (u32)frequency; - /* Get used signal format for the given output */ - signal_format = zl3073x_out_signal_format_get(&out); - /* Check signal format */ - if (signal_format != ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV && - signal_format != ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV) { + if (!zl3073x_out_is_ndiv(&out)) { /* For non N-divided signal formats the frequency is computed * as division of synth frequency and output divisor. */ @@ -1065,6 +951,25 @@ zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin, return 0; } +static int +zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv, + s32 *temp, struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + struct zl3073x_dev *zldev = zldpll->dev; + u16 val; + int rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_DIE_TEMP_STATUS, &val); + if (rc) + return rc; + + /* Register value is in units of 0.1 C, convert to millidegrees */ + *temp = (s16)val * 100; + + return 0; +} + static int zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, enum dpll_lock_status *status, @@ -1072,11 +977,11 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, struct netlink_ext_ack *extack) { struct zl3073x_dpll *zldpll = dpll_priv; - struct zl3073x_dev *zldev = zldpll->dev; - u8 mon_status, state; - int rc; + const struct zl3073x_chan *chan; - switch (zldpll->refsel_mode) { + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + + switch (zl3073x_chan_mode_get(chan)) { case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: case ZL_DPLL_MODE_REFSEL_MODE_NCO: /* In FREERUN and NCO modes the DPLL is always unlocked */ @@ -1087,16 +992,9 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, break; } - /* Read DPLL monitor status */ - rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(zldpll->id), - &mon_status); - if (rc) - return rc; - state = FIELD_GET(ZL_DPLL_MON_STATUS_STATE, mon_status); - - switch (state) { + switch (zl3073x_chan_lock_state_get(chan)) { case ZL_DPLL_MON_STATUS_STATE_LOCK: - if (FIELD_GET(ZL_DPLL_MON_STATUS_HO_READY, mon_status)) + if (zl3073x_chan_is_ho_ready(chan)) *status = DPLL_LOCK_STATUS_LOCKED_HO_ACQ; else *status = DPLL_LOCK_STATUS_LOCKED; @@ -1106,8 +1004,9 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, *status = DPLL_LOCK_STATUS_HOLDOVER; break; default: - dev_warn(zldev->dev, "Unknown DPLL monitor status: 0x%02x\n", - mon_status); + dev_warn(zldpll->dev->dev, + "Unknown DPLL monitor status: 0x%02x\n", + chan->mon_status); *status = DPLL_LOCK_STATUS_UNLOCKED; break; } @@ -1121,13 +1020,16 @@ zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll, struct netlink_ext_ack *extack) { struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; + + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); /* We support switching between automatic and manual mode, except in * a case where the DPLL channel is configured to run in NCO mode. * In this case, report only the manual mode to which the NCO is mapped * as the only supported one. */ - if (zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_NCO) + if (zl3073x_chan_mode_get(chan) != ZL_DPLL_MODE_REFSEL_MODE_NCO) __set_bit(DPLL_MODE_AUTOMATIC, modes); __set_bit(DPLL_MODE_MANUAL, modes); @@ -1140,8 +1042,11 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv, enum dpll_mode *mode, struct netlink_ext_ack *extack) { struct zl3073x_dpll *zldpll = dpll_priv; + const struct zl3073x_chan *chan; - switch (zldpll->refsel_mode) { + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + + switch (zl3073x_chan_mode_get(chan)) { case ZL_DPLL_MODE_REFSEL_MODE_FREERUN: case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER: case ZL_DPLL_MODE_REFSEL_MODE_NCO: @@ -1220,14 +1125,12 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, enum dpll_mode mode, struct netlink_ext_ack *extack) { struct zl3073x_dpll *zldpll = dpll_priv; - u8 hw_mode, mode_refsel, ref; + struct zl3073x_chan chan; + u8 hw_mode, ref; int rc; - rc = zl3073x_dpll_selected_ref_get(zldpll, &ref); - if (rc) { - NL_SET_ERR_MSG_MOD(extack, "failed to get selected reference"); - return rc; - } + chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id); + ref = zl3073x_chan_refsel_ref_get(&chan); if (mode == DPLL_MODE_MANUAL) { /* We are switching from automatic to manual mode: @@ -1250,44 +1153,32 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, * it is selectable after switch to automatic mode * - switch to automatic mode */ - struct zl3073x_dpll_pin *pin; + if (ZL3073X_DPLL_REF_IS_VALID(ref) && + !zl3073x_chan_ref_is_selectable(&chan, ref)) { + struct zl3073x_dpll_pin *pin; - pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref); - if (pin && !pin->selectable) { - /* Restore pin priority in HW */ - rc = zl3073x_dpll_ref_prio_set(pin, pin->prio); - if (rc) { - NL_SET_ERR_MSG_MOD(extack, - "failed to restore pin priority"); - return rc; + pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref); + if (pin) { + /* Restore pin priority in HW */ + zl3073x_chan_ref_prio_set(&chan, ref, + pin->prio); } - - pin->selectable = true; } hw_mode = ZL_DPLL_MODE_REFSEL_MODE_AUTO; } - /* Build mode_refsel value */ - mode_refsel = FIELD_PREP(ZL_DPLL_MODE_REFSEL_MODE, hw_mode); - + zl3073x_chan_mode_set(&chan, hw_mode); if (ZL3073X_DPLL_REF_IS_VALID(ref)) - mode_refsel |= FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref); + zl3073x_chan_ref_set(&chan, ref); - /* Update dpll_mode_refsel register */ - rc = zl3073x_write_u8(zldpll->dev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id), - mode_refsel); + rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan); if (rc) { NL_SET_ERR_MSG_MOD(extack, "failed to set reference selection mode"); return rc; } - zldpll->refsel_mode = hw_mode; - - if (ZL3073X_DPLL_REF_IS_VALID(ref)) - zldpll->forced_ref = ref; - return 0; } @@ -1320,6 +1211,35 @@ zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll, return 0; } +static int +zl3073x_dpll_freq_monitor_get(const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_feature_state *state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + if (zldpll->freq_monitor) + *state = DPLL_FEATURE_STATE_ENABLE; + else + *state = DPLL_FEATURE_STATE_DISABLE; + + return 0; +} + +static int +zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_feature_state state, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + zldpll->freq_monitor = (state == DPLL_FEATURE_STATE_ENABLE); + + return 0; +} + static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = { .direction_get = zl3073x_dpll_pin_direction_get, .esync_get = zl3073x_dpll_input_pin_esync_get, @@ -1327,11 +1247,14 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = { .ffo_get = zl3073x_dpll_input_pin_ffo_get, .frequency_get = zl3073x_dpll_input_pin_frequency_get, .frequency_set = zl3073x_dpll_input_pin_frequency_set, + .measured_freq_get = zl3073x_dpll_input_pin_measured_freq_get, .phase_offset_get = zl3073x_dpll_input_pin_phase_offset_get, .phase_adjust_get = zl3073x_dpll_input_pin_phase_adjust_get, .phase_adjust_set = zl3073x_dpll_input_pin_phase_adjust_set, .prio_get = zl3073x_dpll_input_pin_prio_get, .prio_set = zl3073x_dpll_input_pin_prio_set, + .ref_sync_get = zl3073x_dpll_input_pin_ref_sync_get, + .ref_sync_set = zl3073x_dpll_input_pin_ref_sync_set, .state_on_dpll_get = zl3073x_dpll_input_pin_state_on_dpll_get, .state_on_dpll_set = zl3073x_dpll_input_pin_state_on_dpll_set, }; @@ -1355,6 +1278,8 @@ static const struct dpll_device_ops zl3073x_dpll_device_ops = { .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, + .freq_monitor_get = zl3073x_dpll_freq_monitor_get, + .freq_monitor_set = zl3073x_dpll_freq_monitor_set, .supported_modes_get = zl3073x_dpll_supported_modes_get, }; @@ -1422,24 +1347,25 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index) if (IS_ERR(props)) return PTR_ERR(props); - /* Save package label, esync capability and phase adjust granularity */ + /* Save package label, fwnode, esync capability and phase adjust + * granularity. + */ strscpy(pin->label, props->package_label); + pin->fwnode = fwnode_handle_get(props->fwnode); pin->esync_control = props->esync_control; pin->phase_gran = props->dpll_props.phase_gran; if (zl3073x_dpll_is_input_pin(pin)) { - rc = zl3073x_dpll_ref_prio_get(pin, &pin->prio); - if (rc) - goto err_prio_get; + const struct zl3073x_chan *chan; + u8 ref; - if (pin->prio == ZL_DPLL_REF_PRIO_NONE) { - /* Clamp prio to max value & mark pin non-selectable */ + chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id); + ref = zl3073x_input_pin_ref_get(pin->id); + pin->prio = zl3073x_chan_ref_prio_get(chan, ref); + + if (pin->prio == ZL_DPLL_REF_PRIO_NONE) + /* Clamp prio to max value */ pin->prio = ZL_DPLL_REF_PRIO_MAX; - pin->selectable = false; - } else { - /* Mark pin as selectable */ - pin->selectable = true; - } } /* Create or get existing DPLL pin */ @@ -1468,9 +1394,10 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index) err_register: dpll_pin_put(pin->dpll_pin, &pin->tracker); -err_prio_get: pin->dpll_pin = NULL; err_pin_get: + fwnode_handle_put(pin->fwnode); + pin->fwnode = NULL; zl3073x_pin_props_put(props); return rc; @@ -1500,6 +1427,9 @@ zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin) dpll_pin_put(pin->dpll_pin, &pin->tracker); pin->dpll_pin = NULL; + + fwnode_handle_put(pin->fwnode); + pin->fwnode = NULL; } /** @@ -1540,15 +1470,18 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, enum dpll_pin_direction dir, u8 index) { struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_chan *chan; bool is_diff, is_enabled; const char *name; + chan = zl3073x_chan_state_get(zldev, zldpll->id); + if (dir == DPLL_PIN_DIRECTION_INPUT) { u8 ref_id = zl3073x_input_pin_ref_get(index); const struct zl3073x_ref *ref; /* Skip the pin if the DPLL is running in NCO mode */ - if (zldpll->refsel_mode == ZL_DPLL_MODE_REFSEL_MODE_NCO) + if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO) return false; name = "REF"; @@ -1656,20 +1589,11 @@ static int zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll) { struct zl3073x_dev *zldev = zldpll->dev; - u8 dpll_mode_refsel; int rc; - /* Read DPLL mode and forcibly selected reference */ - rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id), - &dpll_mode_refsel); - if (rc) - return rc; - - /* Extract mode and selected input reference */ - zldpll->refsel_mode = FIELD_GET(ZL_DPLL_MODE_REFSEL_MODE, - dpll_mode_refsel); - zldpll->forced_ref = FIELD_GET(ZL_DPLL_MODE_REFSEL_REF, - dpll_mode_refsel); + zldpll->ops = zl3073x_dpll_device_ops; + if (zldev->info->flags & ZL3073X_FLAG_DIE_TEMP) + zldpll->ops.temp_get = zl3073x_dpll_temp_get; zldpll->dpll_dev = dpll_device_get(zldev->clock_id, zldpll->id, THIS_MODULE, &zldpll->tracker); @@ -1682,7 +1606,7 @@ zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll) rc = dpll_device_register(zldpll->dpll_dev, zl3073x_prop_dpll_type_get(zldev, zldpll->id), - &zl3073x_dpll_device_ops, zldpll); + &zldpll->ops, zldpll); if (rc) { dpll_device_put(zldpll->dpll_dev, &zldpll->tracker); zldpll->dpll_dev = NULL; @@ -1705,8 +1629,7 @@ zl3073x_dpll_device_unregister(struct zl3073x_dpll *zldpll) cancel_work_sync(&zldpll->change_work); - dpll_device_unregister(zldpll->dpll_dev, &zl3073x_dpll_device_ops, - zldpll); + dpll_device_unregister(zldpll->dpll_dev, &zldpll->ops, zldpll); dpll_device_put(zldpll->dpll_dev, &zldpll->tracker); zldpll->dpll_dev = NULL; } @@ -1786,6 +1709,7 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin) struct zl3073x_dev *zldev = zldpll->dev; const struct zl3073x_ref *ref; u8 ref_id; + s64 ffo; /* Get reference monitor status */ ref_id = zl3073x_input_pin_ref_get(pin->id); @@ -1796,10 +1720,47 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin) return false; /* Compare with previous value */ - if (pin->freq_offset != ref->ffo) { + ffo = zl3073x_ref_ffo_get(ref); + if (pin->freq_offset != ffo) { dev_dbg(zldev->dev, "%s freq offset changed: %lld -> %lld\n", - pin->label, pin->freq_offset, ref->ffo); - pin->freq_offset = ref->ffo; + pin->label, pin->freq_offset, ffo); + pin->freq_offset = ffo; + + return true; + } + + return false; +} + +/** + * zl3073x_dpll_pin_measured_freq_check - check for pin measured frequency + * change + * @pin: pin to check + * + * Check for the given pin's measured frequency change. + * + * Return: true on measured frequency change, false otherwise + */ +static bool +zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dpll *zldpll = pin->dpll; + struct zl3073x_dev *zldev = zldpll->dev; + const struct zl3073x_ref *ref; + u8 ref_id; + u32 freq; + + if (!zldpll->freq_monitor) + return false; + + ref_id = zl3073x_input_pin_ref_get(pin->id); + ref = zl3073x_ref_state_get(zldev, ref_id); + + freq = zl3073x_ref_meas_freq_get(ref); + if (pin->measured_freq != freq) { + dev_dbg(zldev->dev, "%s measured freq changed: %u -> %u\n", + pin->label, pin->measured_freq, freq); + pin->measured_freq = freq; return true; } @@ -1822,8 +1783,10 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) struct zl3073x_dev *zldev = zldpll->dev; enum dpll_lock_status lock_status; struct device *dev = zldev->dev; + const struct zl3073x_chan *chan; struct zl3073x_dpll_pin *pin; int rc; + u8 mode; zldpll->check_count++; @@ -1845,8 +1808,10 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) /* Input pin monitoring does make sense only in automatic * or forced reference modes. */ - if (zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_AUTO && - zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_REFLOCK) + chan = zl3073x_chan_state_get(zldev, zldpll->id); + mode = zl3073x_chan_mode_get(chan); + if (mode != ZL_DPLL_MODE_REFSEL_MODE_AUTO && + mode != ZL_DPLL_MODE_REFSEL_MODE_REFLOCK) return; /* Update phase offset latch registers for this DPLL if the phase @@ -1887,13 +1852,18 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll) pin_changed = true; } - /* Check for phase offset and ffo change once per second */ + /* Check for phase offset, ffo, and measured freq change + * once per second. + */ if (zldpll->check_count % 2 == 0) { if (zl3073x_dpll_pin_phase_offset_check(pin)) pin_changed = true; if (zl3073x_dpll_pin_ffo_check(pin)) pin_changed = true; + + if (zl3073x_dpll_pin_measured_freq_check(pin)) + pin_changed = true; } if (pin_changed) @@ -1973,6 +1943,88 @@ zl3073x_dpll_free(struct zl3073x_dpll *zldpll) kfree(zldpll); } +/** + * zl3073x_dpll_ref_sync_pair_register - register ref_sync pairs for a pin + * @pin: pointer to zl3073x_dpll_pin structure + * + * Iterates 'ref-sync-sources' phandles in the pin's firmware node and + * registers each declared pairing. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_ref_sync_pair_register(struct zl3073x_dpll_pin *pin) +{ + struct zl3073x_dev *zldev = pin->dpll->dev; + struct fwnode_handle *fwnode; + struct dpll_pin *sync_pin; + dpll_tracker tracker; + int n, rc; + + for (n = 0; ; n++) { + /* Get n'th ref-sync source */ + fwnode = fwnode_find_reference(pin->fwnode, "ref-sync-sources", + n); + if (IS_ERR(fwnode)) { + rc = PTR_ERR(fwnode); + break; + } + + /* Find associated dpll pin */ + sync_pin = fwnode_dpll_pin_find(fwnode, &tracker); + fwnode_handle_put(fwnode); + if (!sync_pin) { + dev_warn(zldev->dev, "%s: ref-sync source %d not found", + pin->label, n); + continue; + } + + /* Register new ref-sync pair */ + rc = dpll_pin_ref_sync_pair_add(pin->dpll_pin, sync_pin); + dpll_pin_put(sync_pin, &tracker); + + /* -EBUSY means pairing already exists from another DPLL's + * registration. + */ + if (rc && rc != -EBUSY) { + dev_err(zldev->dev, + "%s: failed to add ref-sync source %d: %pe", + pin->label, n, ERR_PTR(rc)); + break; + } + } + + return rc != -ENOENT ? rc : 0; +} + +/** + * zl3073x_dpll_ref_sync_pairs_register - register ref_sync pairs for a DPLL + * @zldpll: pointer to zl3073x_dpll structure + * + * Iterates all registered input pins of the given DPLL and establishes + * ref_sync pairings declared by 'ref-sync-sources' phandles in the + * device tree. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll) +{ + struct zl3073x_dpll_pin *pin; + int rc; + + list_for_each_entry(pin, &zldpll->pins, list) { + if (!zl3073x_dpll_is_input_pin(pin) || !pin->fwnode) + continue; + + rc = zl3073x_dpll_ref_sync_pair_register(pin); + if (rc) + return rc; + } + + return 0; +} + /** * zl3073x_dpll_register - register DPLL device and all its pins * @zldpll: pointer to zl3073x_dpll structure @@ -1996,6 +2048,13 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll) return rc; } + rc = zl3073x_dpll_ref_sync_pairs_register(zldpll); + if (rc) { + zl3073x_dpll_pins_unregister(zldpll); + zl3073x_dpll_device_unregister(zldpll); + return rc; + } + return 0; } diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h index c65c798c37..434c32a7db 100644 --- a/drivers/dpll/zl3073x/dpll.h +++ b/drivers/dpll/zl3073x/dpll.h @@ -13,10 +13,10 @@ * @list: this DPLL list entry * @dev: pointer to multi-function parent device * @id: DPLL index - * @refsel_mode: reference selection mode - * @forced_ref: selected reference in forced reference lock mode * @check_count: periodic check counter * @phase_monitor: is phase offset monitor enabled + * @freq_monitor: is frequency monitor enabled + * @ops: DPLL device operations for this instance * @dpll_dev: pointer to registered DPLL device * @tracker: tracking object for the acquired reference * @lock_status: last saved DPLL lock status @@ -27,10 +27,10 @@ struct zl3073x_dpll { struct list_head list; struct zl3073x_dev *dev; u8 id; - u8 refsel_mode; - u8 forced_ref; u8 check_count; bool phase_monitor; + bool freq_monitor; + struct dpll_device_ops ops; struct dpll_device *dpll_dev; dpll_tracker tracker; enum dpll_lock_status lock_status; diff --git a/drivers/dpll/zl3073x/flash.c b/drivers/dpll/zl3073x/flash.c index e2771dd7f5..fa7ee9e2f3 100644 --- a/drivers/dpll/zl3073x/flash.c +++ b/drivers/dpll/zl3073x/flash.c @@ -194,8 +194,7 @@ zl3073x_flash_cmd_wait(struct zl3073x_dev *zldev, u32 operation, if (rc) return rc; - value &= ~ZL_WRITE_FLASH_OP; - value |= FIELD_PREP(ZL_WRITE_FLASH_OP, operation); + FIELD_MODIFY(ZL_WRITE_FLASH_OP, &value, operation); rc = zl3073x_write_u8(zldev, ZL_REG_WRITE_FLASH, value); if (rc) diff --git a/drivers/dpll/zl3073x/i2c.c b/drivers/dpll/zl3073x/i2c.c index a7ac128008..2f36f2ec94 100644 --- a/drivers/dpll/zl3073x/i2c.c +++ b/drivers/dpll/zl3073x/i2c.c @@ -22,40 +22,25 @@ static int zl3073x_i2c_probe(struct i2c_client *client) return dev_err_probe(dev, PTR_ERR(zldev->regmap), "Failed to initialize regmap\n"); - return zl3073x_dev_probe(zldev, i2c_get_match_data(client)); + return zl3073x_dev_probe(zldev); } static const struct i2c_device_id zl3073x_i2c_id[] = { - { - .name = "zl30731", - .driver_data = (kernel_ulong_t)&zl30731_chip_info, - }, - { - .name = "zl30732", - .driver_data = (kernel_ulong_t)&zl30732_chip_info, - }, - { - .name = "zl30733", - .driver_data = (kernel_ulong_t)&zl30733_chip_info, - }, - { - .name = "zl30734", - .driver_data = (kernel_ulong_t)&zl30734_chip_info, - }, - { - .name = "zl30735", - .driver_data = (kernel_ulong_t)&zl30735_chip_info, - }, + { "zl30731" }, + { "zl30732" }, + { "zl30733" }, + { "zl30734" }, + { "zl30735" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, zl3073x_i2c_id); static const struct of_device_id zl3073x_i2c_of_match[] = { - { .compatible = "microchip,zl30731", .data = &zl30731_chip_info }, - { .compatible = "microchip,zl30732", .data = &zl30732_chip_info }, - { .compatible = "microchip,zl30733", .data = &zl30733_chip_info }, - { .compatible = "microchip,zl30734", .data = &zl30734_chip_info }, - { .compatible = "microchip,zl30735", .data = &zl30735_chip_info }, + { .compatible = "microchip,zl30731" }, + { .compatible = "microchip,zl30732" }, + { .compatible = "microchip,zl30733" }, + { .compatible = "microchip,zl30734" }, + { .compatible = "microchip,zl30735" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, zl3073x_i2c_of_match); diff --git a/drivers/dpll/zl3073x/out.c b/drivers/dpll/zl3073x/out.c index 86829a0c1c..eb5628aebc 100644 --- a/drivers/dpll/zl3073x/out.c +++ b/drivers/dpll/zl3073x/out.c @@ -106,12 +106,32 @@ const struct zl3073x_out *zl3073x_out_state_get(struct zl3073x_dev *zldev, return &zldev->out[index]; } +/** + * zl3073x_out_state_set - commit output state changes to hardware + * @zldev: pointer to zl3073x_dev structure + * @index: output index to set state for + * @out: desired output state + * + * Validates that invariant fields have not been modified, skips the HW + * write if the mutable configuration is unchanged, and otherwise writes + * only the changed cfg fields to hardware via the mailbox interface. + * + * Return: 0 on success, -EINVAL if invariants changed, <0 on HW error + */ int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index, const struct zl3073x_out *out) { struct zl3073x_out *dout = &zldev->out[index]; int rc; + /* Reject attempts to change invariant fields (set at fetch only) */ + if (WARN_ON(memcmp(&dout->inv, &out->inv, sizeof(out->inv)))) + return -EINVAL; + + /* Skip HW write if configuration hasn't changed */ + if (!memcmp(&dout->cfg, &out->cfg, sizeof(out->cfg))) + return 0; + guard(mutex)(&zldev->multiop_lock); /* Read output configuration into mailbox */ @@ -146,12 +166,7 @@ int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index, return rc; /* After successful commit store new state */ - dout->div = out->div; - dout->width = out->width; - dout->esync_n_period = out->esync_n_period; - dout->esync_n_width = out->esync_n_width; - dout->mode = out->mode; - dout->phase_comp = out->phase_comp; + dout->cfg = out->cfg; return 0; } diff --git a/drivers/dpll/zl3073x/out.h b/drivers/dpll/zl3073x/out.h index 318f9bb8da..660889c57b 100644 --- a/drivers/dpll/zl3073x/out.h +++ b/drivers/dpll/zl3073x/out.h @@ -4,6 +4,7 @@ #define _ZL3073X_OUT_H #include +#include #include #include "regs.h" @@ -17,17 +18,21 @@ struct zl3073x_dev; * @esync_n_period: embedded sync or n-pin period (for n-div formats) * @esync_n_width: embedded sync or n-pin pulse width * @phase_comp: phase compensation - * @ctrl: output control * @mode: output mode + * @ctrl: output control */ struct zl3073x_out { - u32 div; - u32 width; - u32 esync_n_period; - u32 esync_n_width; - s32 phase_comp; - u8 ctrl; - u8 mode; + struct_group(cfg, /* Config */ + u32 div; + u32 width; + u32 esync_n_period; + u32 esync_n_width; + s32 phase_comp; + u8 mode; + ); + struct_group(inv, /* Invariants */ + u8 ctrl; + ); }; int zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index); @@ -37,6 +42,28 @@ const struct zl3073x_out *zl3073x_out_state_get(struct zl3073x_dev *zldev, int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index, const struct zl3073x_out *out); +/** + * zl3073x_out_clock_type_get - get output clock type + * @out: pointer to out state + * + * Return: clock type of given output (ZL_OUTPUT_MODE_CLOCK_TYPE_*) + */ +static inline u8 zl3073x_out_clock_type_get(const struct zl3073x_out *out) +{ + return FIELD_GET(ZL_OUTPUT_MODE_CLOCK_TYPE, out->mode); +} + +/** + * zl3073x_out_clock_type_set - set output clock type + * @out: pointer to out state + * @type: clock type (ZL_OUTPUT_MODE_CLOCK_TYPE_*) + */ +static inline void +zl3073x_out_clock_type_set(struct zl3073x_out *out, u8 type) +{ + FIELD_MODIFY(ZL_OUTPUT_MODE_CLOCK_TYPE, &out->mode, type); +} + /** * zl3073x_out_signal_format_get - get output signal format * @out: pointer to out state diff --git a/drivers/dpll/zl3073x/ref.c b/drivers/dpll/zl3073x/ref.c index 6b65e61039..825ac30bcd 100644 --- a/drivers/dpll/zl3073x/ref.c +++ b/drivers/dpll/zl3073x/ref.c @@ -51,6 +51,21 @@ zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult) return -EINVAL; } +/** + * zl3073x_ref_state_update - update input reference status from HW + * @zldev: pointer to zl3073x_dev structure + * @index: input reference index + * + * Return: 0 on success, <0 on error + */ +int zl3073x_ref_state_update(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_ref *ref = &zldev->ref[index]; + + return zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(index), + &ref->mon_status); +} + /** * zl3073x_ref_state_fetch - fetch input reference state from hardware * @zldev: pointer to zl3073x_dev structure @@ -73,18 +88,17 @@ int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index) struct zl3073x_ref *p_ref = ref - 1; /* P-pin counterpart*/ /* Copy the shared items from the P-pin */ - ref->config = p_ref->config; - ref->esync_n_div = p_ref->esync_n_div; - ref->freq_base = p_ref->freq_base; - ref->freq_mult = p_ref->freq_mult; - ref->freq_ratio_m = p_ref->freq_ratio_m; - ref->freq_ratio_n = p_ref->freq_ratio_n; - ref->phase_comp = p_ref->phase_comp; - ref->sync_ctrl = p_ref->sync_ctrl; + ref->cfg = p_ref->cfg; + ref->inv = p_ref->inv; return 0; /* Finish - no non-shared items for now */ } + /* Read reference status */ + rc = zl3073x_ref_state_update(zldev, index); + if (rc) + return rc; + guard(mutex)(&zldev->multiop_lock); /* Read reference configuration */ @@ -154,12 +168,32 @@ zl3073x_ref_state_get(struct zl3073x_dev *zldev, u8 index) return &zldev->ref[index]; } +/** + * zl3073x_ref_state_set - commit input reference state changes to hardware + * @zldev: pointer to zl3073x_dev structure + * @index: input reference index to set state for + * @ref: desired reference state + * + * Validates that invariant fields have not been modified, skips the HW + * write if the mutable configuration is unchanged, and otherwise writes + * only the changed cfg fields to hardware via the mailbox interface. + * + * Return: 0 on success, -EINVAL if invariants changed, <0 on HW error + */ int zl3073x_ref_state_set(struct zl3073x_dev *zldev, u8 index, const struct zl3073x_ref *ref) { struct zl3073x_ref *dref = &zldev->ref[index]; int rc; + /* Reject attempts to change invariant fields (set at init only) */ + if (WARN_ON(memcmp(&dref->inv, &ref->inv, sizeof(ref->inv)))) + return -EINVAL; + + /* Skip HW write if configuration hasn't changed */ + if (!memcmp(&dref->cfg, &ref->cfg, sizeof(ref->cfg))) + return 0; + guard(mutex)(&zldev->multiop_lock); /* Read reference configuration into mailbox */ @@ -207,13 +241,7 @@ int zl3073x_ref_state_set(struct zl3073x_dev *zldev, u8 index, return rc; /* After successful commit store new state */ - dref->freq_base = ref->freq_base; - dref->freq_mult = ref->freq_mult; - dref->freq_ratio_m = ref->freq_ratio_m; - dref->freq_ratio_n = ref->freq_ratio_n; - dref->esync_n_div = ref->esync_n_div; - dref->sync_ctrl = ref->sync_ctrl; - dref->phase_comp = ref->phase_comp; + dref->cfg = ref->cfg; return 0; } diff --git a/drivers/dpll/zl3073x/ref.h b/drivers/dpll/zl3073x/ref.h index 0d8618f5ce..55e80e4f08 100644 --- a/drivers/dpll/zl3073x/ref.h +++ b/drivers/dpll/zl3073x/ref.h @@ -5,6 +5,7 @@ #include #include +#include #include #include "regs.h" @@ -13,28 +14,36 @@ struct zl3073x_dev; /** * struct zl3073x_ref - input reference state - * @ffo: current fractional frequency offset * @phase_comp: phase compensation * @esync_n_div: divisor for embedded sync or n-divided signal formats * @freq_base: frequency base * @freq_mult: frequnecy multiplier * @freq_ratio_m: FEC mode multiplier * @freq_ratio_n: FEC mode divisor - * @config: reference config * @sync_ctrl: reference sync control + * @config: reference config + * @ffo: current fractional frequency offset + * @meas_freq: measured input frequency in Hz * @mon_status: reference monitor status */ struct zl3073x_ref { - s64 ffo; - u64 phase_comp; - u32 esync_n_div; - u16 freq_base; - u16 freq_mult; - u16 freq_ratio_m; - u16 freq_ratio_n; - u8 config; - u8 sync_ctrl; - u8 mon_status; + struct_group(cfg, /* Configuration */ + u64 phase_comp; + u32 esync_n_div; + u16 freq_base; + u16 freq_mult; + u16 freq_ratio_m; + u16 freq_ratio_n; + u8 sync_ctrl; + ); + struct_group(inv, /* Invariants */ + u8 config; + ); + struct_group(stat, /* Status */ + s64 ffo; + u32 meas_freq; + u8 mon_status; + ); }; int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index); @@ -45,6 +54,8 @@ const struct zl3073x_ref *zl3073x_ref_state_get(struct zl3073x_dev *zldev, int zl3073x_ref_state_set(struct zl3073x_dev *zldev, u8 index, const struct zl3073x_ref *ref); +int zl3073x_ref_state_update(struct zl3073x_dev *zldev, u8 index); + int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult); /** @@ -59,6 +70,18 @@ zl3073x_ref_ffo_get(const struct zl3073x_ref *ref) return ref->ffo; } +/** + * zl3073x_ref_meas_freq_get - get measured input frequency + * @ref: pointer to ref state + * + * Return: measured input frequency in Hz + */ +static inline u32 +zl3073x_ref_meas_freq_get(const struct zl3073x_ref *ref) +{ + return ref->meas_freq; +} + /** * zl3073x_ref_freq_get - get given input reference frequency * @ref: pointer to ref state @@ -97,6 +120,52 @@ zl3073x_ref_freq_set(struct zl3073x_ref *ref, u32 freq) return 0; } +/** + * zl3073x_ref_sync_mode_get - get sync control mode + * @ref: pointer to ref state + * + * Return: sync control mode (ZL_REF_SYNC_CTRL_MODE_*) + */ +static inline u8 +zl3073x_ref_sync_mode_get(const struct zl3073x_ref *ref) +{ + return FIELD_GET(ZL_REF_SYNC_CTRL_MODE, ref->sync_ctrl); +} + +/** + * zl3073x_ref_sync_mode_set - set sync control mode + * @ref: pointer to ref state + * @mode: sync control mode (ZL_REF_SYNC_CTRL_MODE_*) + */ +static inline void +zl3073x_ref_sync_mode_set(struct zl3073x_ref *ref, u8 mode) +{ + FIELD_MODIFY(ZL_REF_SYNC_CTRL_MODE, &ref->sync_ctrl, mode); +} + +/** + * zl3073x_ref_sync_pair_get - get sync pair reference index + * @ref: pointer to ref state + * + * Return: paired reference index + */ +static inline u8 +zl3073x_ref_sync_pair_get(const struct zl3073x_ref *ref) +{ + return FIELD_GET(ZL_REF_SYNC_CTRL_PAIR, ref->sync_ctrl); +} + +/** + * zl3073x_ref_sync_pair_set - set sync pair reference index + * @ref: pointer to ref state + * @pair: paired reference index + */ +static inline void +zl3073x_ref_sync_pair_set(struct zl3073x_ref *ref, u8 pair) +{ + FIELD_MODIFY(ZL_REF_SYNC_CTRL_PAIR, &ref->sync_ctrl, pair); +} + /** * zl3073x_ref_is_diff - check if the given input reference is differential * @ref: pointer to ref state diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h index 5573d71884..d425dc6725 100644 --- a/drivers/dpll/zl3073x/regs.h +++ b/drivers/dpll/zl3073x/regs.h @@ -6,6 +6,18 @@ #include #include +/* + * Hardware limits for ZL3073x chip family + */ +#define ZL3073X_MAX_CHANNELS 5 +#define ZL3073X_NUM_REFS 10 +#define ZL3073X_NUM_OUTS 10 +#define ZL3073X_NUM_SYNTHS 5 +#define ZL3073X_NUM_INPUT_PINS ZL3073X_NUM_REFS +#define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2) +#define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \ + ZL3073X_NUM_OUTPUT_PINS) + /* * Register address structure: * =========================== @@ -78,6 +90,8 @@ #define ZL_REG_RESET_STATUS ZL_REG(0, 0x18, 1) #define ZL_REG_RESET_STATUS_RESET BIT(0) +#define ZL_REG_DIE_TEMP_STATUS ZL_REG(0, 0x44, 2) + /************************* * Register Page 2, Status *************************/ @@ -199,7 +213,9 @@ #define ZL_REG_REF_SYNC_CTRL ZL_REG(10, 0x2e, 1) #define ZL_REF_SYNC_CTRL_MODE GENMASK(2, 0) #define ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF 0 +#define ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR 1 #define ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75 2 +#define ZL_REF_SYNC_CTRL_PAIR GENMASK(7, 4) #define ZL_REG_REF_ESYNC_DIV ZL_REG(10, 0x30, 4) #define ZL_REF_ESYNC_DIV_1HZ 0 diff --git a/drivers/dpll/zl3073x/spi.c b/drivers/dpll/zl3073x/spi.c index 00c5e3804c..ad43bf9fd8 100644 --- a/drivers/dpll/zl3073x/spi.c +++ b/drivers/dpll/zl3073x/spi.c @@ -22,40 +22,25 @@ static int zl3073x_spi_probe(struct spi_device *spi) return dev_err_probe(dev, PTR_ERR(zldev->regmap), "Failed to initialize regmap\n"); - return zl3073x_dev_probe(zldev, spi_get_device_match_data(spi)); + return zl3073x_dev_probe(zldev); } static const struct spi_device_id zl3073x_spi_id[] = { - { - .name = "zl30731", - .driver_data = (kernel_ulong_t)&zl30731_chip_info - }, - { - .name = "zl30732", - .driver_data = (kernel_ulong_t)&zl30732_chip_info, - }, - { - .name = "zl30733", - .driver_data = (kernel_ulong_t)&zl30733_chip_info, - }, - { - .name = "zl30734", - .driver_data = (kernel_ulong_t)&zl30734_chip_info, - }, - { - .name = "zl30735", - .driver_data = (kernel_ulong_t)&zl30735_chip_info, - }, + { "zl30731" }, + { "zl30732" }, + { "zl30733" }, + { "zl30734" }, + { "zl30735" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(spi, zl3073x_spi_id); static const struct of_device_id zl3073x_spi_of_match[] = { - { .compatible = "microchip,zl30731", .data = &zl30731_chip_info }, - { .compatible = "microchip,zl30732", .data = &zl30732_chip_info }, - { .compatible = "microchip,zl30733", .data = &zl30733_chip_info }, - { .compatible = "microchip,zl30734", .data = &zl30734_chip_info }, - { .compatible = "microchip,zl30735", .data = &zl30735_chip_info }, + { .compatible = "microchip,zl30731" }, + { .compatible = "microchip,zl30732" }, + { .compatible = "microchip,zl30733" }, + { .compatible = "microchip,zl30734" }, + { .compatible = "microchip,zl30735" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, zl3073x_spi_of_match); diff --git a/drivers/dpll/zl3073x/synth.h b/drivers/dpll/zl3073x/synth.h index 6c55eb8a88..89e13ea2e6 100644 --- a/drivers/dpll/zl3073x/synth.h +++ b/drivers/dpll/zl3073x/synth.h @@ -5,6 +5,7 @@ #include #include +#include #include #include "regs.h" @@ -20,11 +21,13 @@ struct zl3073x_dev; * @ctrl: synth control */ struct zl3073x_synth { - u32 freq_mult; - u16 freq_base; - u16 freq_m; - u16 freq_n; - u8 ctrl; + struct_group(inv, /* Invariants */ + u32 freq_mult; + u16 freq_base; + u16 freq_m; + u16 freq_n; + u8 ctrl; + ); }; int zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 synth_id); @@ -32,9 +35,6 @@ int zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 synth_id); const struct zl3073x_synth *zl3073x_synth_state_get(struct zl3073x_dev *zldev, u8 synth_id); -int zl3073x_synth_state_set(struct zl3073x_dev *zldev, u8 synth_id, - const struct zl3073x_synth *synth); - /** * zl3073x_synth_dpll_get - get DPLL ID the synth is driven by * @synth: pointer to synth state diff --git a/drivers/infiniband/sw/rxe/rxe_srq.c b/drivers/infiniband/sw/rxe/rxe_srq.c index 3661cb627d..6f21928022 100644 --- a/drivers/infiniband/sw/rxe/rxe_srq.c +++ b/drivers/infiniband/sw/rxe/rxe_srq.c @@ -77,9 +77,6 @@ int rxe_srq_from_init(struct rxe_dev *rxe, struct rxe_srq *srq, goto err_free; } - srq->rq.queue = q; - init->attr.max_wr = srq->rq.max_wr; - if (uresp) { if (copy_to_user(&uresp->srq_num, &srq->srq_num, sizeof(uresp->srq_num))) { @@ -88,6 +85,9 @@ int rxe_srq_from_init(struct rxe_dev *rxe, struct rxe_srq *srq, } } + srq->rq.queue = q; + init->attr.max_wr = srq->rq.max_wr; + return 0; err_free: diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 945a0e15e1..0ca0efab41 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -5398,7 +5398,7 @@ static netdev_tx_t bond_xmit_broadcast(struct sk_buff *skb, if (!(bond_slave_is_up(slave) && slave->link == BOND_LINK_UP)) continue; - if (bond_is_last_slave(bond, slave)) { + if (i + 1 == slaves_count) { skb2 = skb; skb_used = true; } else { diff --git a/drivers/net/ethernet/intel/ice/Makefile b/drivers/net/ethernet/intel/ice/Makefile index 0b8cf4eadc..3da38fc62f 100644 --- a/drivers/net/ethernet/intel/ice/Makefile +++ b/drivers/net/ethernet/intel/ice/Makefile @@ -43,7 +43,8 @@ ice-y := ice_main.o \ ice_repr.o \ ice_tc_lib.o \ ice_debugfs.o \ - ice_adapter.o + ice_adapter.o \ + ice_rh.o ice-$(CONFIG_PCI_IOV) += \ ice_sriov.o \ virt/allowlist.o \ diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h index a23ccd4ba0..480db8113f 100644 --- a/drivers/net/ethernet/intel/ice/ice.h +++ b/drivers/net/ethernet/intel/ice/ice.h @@ -837,6 +837,28 @@ static inline void ice_tx_xsk_pool(struct ice_vsi *vsi, u16 qid) WRITE_ONCE(ring->xsk_pool, ice_get_xp_from_qid(vsi, qid)); } +/** + * ice_get_max_txq - return the maximum number of Tx queues for in a PF + * @pf: PF structure + * + * Return: maximum number of Tx queues + */ +static inline int ice_get_max_txq(struct ice_pf *pf) +{ + return min(num_online_cpus(), pf->hw.func_caps.common_cap.num_txq); +} + +/** + * ice_get_max_rxq - return the maximum number of Rx queues for in a PF + * @pf: PF structure + * + * Return: maximum number of Rx queues + */ +static inline int ice_get_max_rxq(struct ice_pf *pf) +{ + return min(num_online_cpus(), pf->hw.func_caps.common_cap.num_rxq); +} + /** * ice_get_main_vsi - Get the PF VSI * @pf: PF instance diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool.c b/drivers/net/ethernet/intel/ice/ice_ethtool.c index 912d8001f5..db6adaa034 100644 --- a/drivers/net/ethernet/intel/ice/ice_ethtool.c +++ b/drivers/net/ethernet/intel/ice/ice_ethtool.c @@ -3744,24 +3744,6 @@ ice_get_ts_info(struct net_device *dev, struct kernel_ethtool_ts_info *info) return 0; } -/** - * ice_get_max_txq - return the maximum number of Tx queues for in a PF - * @pf: PF structure - */ -static int ice_get_max_txq(struct ice_pf *pf) -{ - return min(num_online_cpus(), pf->hw.func_caps.common_cap.num_txq); -} - -/** - * ice_get_max_rxq - return the maximum number of Rx queues for in a PF - * @pf: PF structure - */ -static int ice_get_max_rxq(struct ice_pf *pf) -{ - return min(num_online_cpus(), pf->hw.func_caps.common_cap.num_rxq); -} - /** * ice_get_combined_cnt - return the current number of combined channels * @vsi: PF VSI pointer diff --git a/drivers/net/ethernet/intel/ice/ice_irq.c b/drivers/net/ethernet/intel/ice/ice_irq.c index 30801fd375..4766951d45 100644 --- a/drivers/net/ethernet/intel/ice/ice_irq.c +++ b/drivers/net/ethernet/intel/ice/ice_irq.c @@ -4,6 +4,7 @@ #include "ice.h" #include "ice_lib.h" #include "ice_irq.h" +#include "ice_rh.h" /** * ice_init_irq_tracker - initialize interrupt tracker @@ -106,9 +107,10 @@ static struct ice_irq_entry *ice_get_irq_res(struct ice_pf *pf, #define ICE_RDMA_AEQ_MSIX 1 static int ice_get_default_msix_amount(struct ice_pf *pf) { - return ICE_MIN_LAN_OICR_MSIX + num_online_cpus() + + return ICE_MIN_LAN_OICR_MSIX + ice_rh_get_num_default_rss_queues() + (test_bit(ICE_FLAG_FD_ENA, pf->flags) ? ICE_FDIR_MSIX : 0) + - (ice_is_rdma_ena(pf) ? num_online_cpus() + ICE_RDMA_AEQ_MSIX : 0); + (ice_is_rdma_ena(pf) ? ice_rh_get_num_default_rss_queues() + + ICE_RDMA_AEQ_MSIX : 0); } /** diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c index d668806dd6..72c91bd174 100644 --- a/drivers/net/ethernet/intel/ice/ice_lib.c +++ b/drivers/net/ethernet/intel/ice/ice_lib.c @@ -9,6 +9,7 @@ #include "ice_dcb_lib.h" #include "ice_type.h" #include "ice_vsi_vlan_ops.h" +#include "ice_rh.h" /** * ice_vsi_type_str - maps VSI type enum to string equivalents @@ -159,12 +160,14 @@ static void ice_vsi_set_num_desc(struct ice_vsi *vsi) static u16 ice_get_rxq_count(struct ice_pf *pf) { - return min(ice_get_avail_rxq_count(pf), num_online_cpus()); + return min(ice_get_avail_rxq_count(pf), + ice_rh_get_num_default_rss_queues()); } static u16 ice_get_txq_count(struct ice_pf *pf) { - return min(ice_get_avail_txq_count(pf), num_online_cpus()); + return min(ice_get_avail_txq_count(pf), + ice_rh_get_num_default_rss_queues()); } /** @@ -911,13 +914,15 @@ static void ice_vsi_set_rss_params(struct ice_vsi *vsi) if (vsi->type == ICE_VSI_CHNL) vsi->rss_size = min_t(u16, vsi->num_rxq, max_rss_size); else - vsi->rss_size = min_t(u16, num_online_cpus(), + vsi->rss_size = min_t(u16, + ice_rh_get_num_default_rss_queues(), max_rss_size); vsi->rss_lut_type = ICE_LUT_PF; break; case ICE_VSI_SF: vsi->rss_table_size = ICE_LUT_VSI_SIZE; - vsi->rss_size = min_t(u16, num_online_cpus(), max_rss_size); + vsi->rss_size = min_t(u16, ice_rh_get_num_default_rss_queues(), + max_rss_size); vsi->rss_lut_type = ICE_LUT_VSI; break; case ICE_VSI_VF: @@ -3007,7 +3012,7 @@ ice_vsi_rebuild_set_coalesce(struct ice_vsi *vsi, * ice_vsi_realloc_stat_arrays - Frees unused stat structures or alloc new ones * @vsi: VSI pointer */ -static int +int ice_vsi_realloc_stat_arrays(struct ice_vsi *vsi) { u16 req_txq = vsi->req_txq ? vsi->req_txq : vsi->alloc_txq; diff --git a/drivers/net/ethernet/intel/ice/ice_lib.h b/drivers/net/ethernet/intel/ice/ice_lib.h index 2cb1eb98b9..fce6f633f5 100644 --- a/drivers/net/ethernet/intel/ice/ice_lib.h +++ b/drivers/net/ethernet/intel/ice/ice_lib.h @@ -66,6 +66,7 @@ int ice_ena_vsi(struct ice_vsi *vsi, bool locked); void ice_vsi_decfg(struct ice_vsi *vsi); void ice_dis_vsi(struct ice_vsi *vsi, bool locked); +int ice_vsi_realloc_stat_arrays(struct ice_vsi *vsi); int ice_vsi_rebuild(struct ice_vsi *vsi, u32 vsi_flags); int ice_vsi_cfg(struct ice_vsi *vsi); struct ice_vsi *ice_vsi_alloc(struct ice_pf *pf); diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index 6b1e6eeeda..38356f69bc 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -4730,8 +4730,8 @@ static int ice_cfg_netdev(struct ice_vsi *vsi) struct net_device *netdev; u8 mac_addr[ETH_ALEN]; - netdev = alloc_etherdev_mqs(sizeof(*np), vsi->alloc_txq, - vsi->alloc_rxq); + netdev = alloc_etherdev_mqs(sizeof(*np), ice_get_max_txq(vsi->back), + ice_get_max_rxq(vsi->back)); if (!netdev) return -ENOMEM; diff --git a/drivers/net/ethernet/intel/ice/ice_rh.c b/drivers/net/ethernet/intel/ice/ice_rh.c new file mode 100644 index 0000000000..7660eeef93 --- /dev/null +++ b/drivers/net/ethernet/intel/ice/ice_rh.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include + +#include "ice_rh.h" + +/** + * ice_rh_get_num_default_rss_queues - default number of RSS queues + * + * Default value is the number of physical cores if there are only 1 or 2, or + * divided by 2 if there are more. + * + * This is a renamed copy of netif_get_num_default_rss_queues() from upstream. + */ +int ice_rh_get_num_default_rss_queues(void) +{ + cpumask_var_t cpus; + int cpu, count = 0; + + if (unlikely(is_kdump_kernel() || !zalloc_cpumask_var(&cpus, GFP_KERNEL))) + return 1; + + cpumask_copy(cpus, cpu_online_mask); + for_each_cpu(cpu, cpus) { + ++count; + cpumask_andnot(cpus, cpus, topology_sibling_cpumask(cpu)); + } + free_cpumask_var(cpus); + + return count > 2 ? DIV_ROUND_UP(count, 2) : count; +} diff --git a/drivers/net/ethernet/intel/ice/ice_rh.h b/drivers/net/ethernet/intel/ice/ice_rh.h new file mode 100644 index 0000000000..d88e00a735 --- /dev/null +++ b/drivers/net/ethernet/intel/ice/ice_rh.h @@ -0,0 +1,6 @@ +#ifndef __ICE_RH_H +#define __ICE_RH_H + +int ice_rh_get_num_default_rss_queues(void); + +#endif diff --git a/drivers/net/ethernet/intel/ice/ice_vf_lib.c b/drivers/net/ethernet/intel/ice/ice_vf_lib.c index e53a1e4247..08e3e265c3 100644 --- a/drivers/net/ethernet/intel/ice/ice_vf_lib.c +++ b/drivers/net/ethernet/intel/ice/ice_vf_lib.c @@ -268,6 +268,13 @@ static int ice_vf_reconfig_vsi(struct ice_vf *vf) vsi->flags = ICE_VSI_FLAG_NO_INIT; + vsi->req_txq = vf->num_req_qs; + vsi->req_rxq = vf->num_req_qs; + + err = ice_vsi_realloc_stat_arrays(vsi); + if (err) + return err; + ice_vsi_decfg(vsi); ice_fltr_remove_all(vsi); diff --git a/drivers/net/ethernet/mellanox/mlx4/srq.c b/drivers/net/ethernet/mellanox/mlx4/srq.c index dd890f5d7b..b21eae532a 100644 --- a/drivers/net/ethernet/mellanox/mlx4/srq.c +++ b/drivers/net/ethernet/mellanox/mlx4/srq.c @@ -44,13 +44,14 @@ void mlx4_srq_event(struct mlx4_dev *dev, u32 srqn, int event_type) { struct mlx4_srq_table *srq_table = &mlx4_priv(dev)->srq_table; struct mlx4_srq *srq; + unsigned long flags; - rcu_read_lock(); + spin_lock_irqsave(&srq_table->lock, flags); srq = radix_tree_lookup(&srq_table->tree, srqn & (dev->caps.num_srqs - 1)); - rcu_read_unlock(); - if (srq) - refcount_inc(&srq->refcount); - else { + if (!srq || !refcount_inc_not_zero(&srq->refcount)) + srq = NULL; + spin_unlock_irqrestore(&srq_table->lock, flags); + if (!srq) { mlx4_warn(dev, "Async event for bogus SRQ %08x\n", srqn); return; } @@ -203,8 +204,8 @@ int mlx4_srq_alloc(struct mlx4_dev *dev, u32 pdn, u32 cqn, u16 xrcd, if (err) goto err_radix; - refcount_set(&srq->refcount, 1); init_completion(&srq->free); + atomic_set_release(&srq->refcount.refs, 1); return 0; diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 055a84643d..a64ef3e227 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -3196,6 +3196,7 @@ static int add_adev(struct gdma_dev *gd, const char *name) struct auxiliary_device *adev; struct mana_adev *madev; int ret; + int id; madev = kzalloc(sizeof(*madev), GFP_KERNEL); if (!madev) @@ -3205,7 +3206,8 @@ static int add_adev(struct gdma_dev *gd, const char *name) ret = mana_adev_idx_alloc(); if (ret < 0) goto idx_fail; - adev->id = ret; + id = ret; + adev->id = id; adev->name = name; adev->dev.parent = gd->gdma_context->dev; @@ -3231,7 +3233,7 @@ add_fail: auxiliary_device_uninit(adev); init_fail: - mana_adev_idx_free(adev->id); + mana_adev_idx_free(id); idx_fail: kfree(madev); diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index bd6cbe35da..6cc7d11ad5 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -2354,17 +2354,11 @@ nvme_fc_ctrl_free(struct kref *ref) container_of(ref, struct nvme_fc_ctrl, ref); unsigned long flags; - if (ctrl->ctrl.tagset) - nvme_remove_io_tag_set(&ctrl->ctrl); - /* remove from rport list */ spin_lock_irqsave(&ctrl->rport->lock, flags); list_del(&ctrl->ctrl_list); spin_unlock_irqrestore(&ctrl->rport->lock, flags); - nvme_unquiesce_admin_queue(&ctrl->ctrl); - nvme_remove_admin_tag_set(&ctrl->ctrl); - kfree(ctrl->queues); put_device(ctrl->dev); @@ -3250,13 +3244,20 @@ nvme_fc_delete_ctrl(struct nvme_ctrl *nctrl) { struct nvme_fc_ctrl *ctrl = to_fc_ctrl(nctrl); - cancel_work_sync(&ctrl->ioerr_work); cancel_delayed_work_sync(&ctrl->connect_work); + /* * kill the association on the link side. this will block * waiting for io to terminate */ nvme_fc_delete_association(ctrl); + cancel_work_sync(&ctrl->ioerr_work); + + if (ctrl->ctrl.tagset) + nvme_remove_io_tag_set(&ctrl->ctrl); + + nvme_unquiesce_admin_queue(&ctrl->ctrl); + nvme_remove_admin_tag_set(&ctrl->ctrl); } static void diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index c60424afaf..54d6d29477 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c @@ -6144,10 +6144,12 @@ static void copy_pair_set_active(struct dasd_copy_relation *copy, char *new_busi static int dasd_eckd_copy_pair_swap(struct dasd_device *device, char *prim_busid, char *sec_busid) { + struct dasd_eckd_private *prim_priv, *sec_priv; struct dasd_device *primary, *secondary; struct dasd_copy_relation *copy; struct dasd_block *block; struct gendisk *gdp; + int rc; copy = device->copy; if (!copy) @@ -6163,6 +6165,9 @@ static int dasd_eckd_copy_pair_swap(struct dasd_device *device, char *prim_busid if (!secondary) return DASD_COPYPAIRSWAP_SECONDARY; + prim_priv = primary->private; + sec_priv = secondary->private; + /* * usually the device should be quiesced for swap * for paranoia stop device and requeue requests again @@ -6182,6 +6187,25 @@ static int dasd_eckd_copy_pair_swap(struct dasd_device *device, char *prim_busid /* swap blocklayer device link */ gdp = block->gdp; dasd_add_link_to_gendisk(gdp, secondary); + rc = device_move(disk_to_dev(gdp), &secondary->cdev->dev, DPM_ORDER_NONE); + if (rc) { + dev_err(&primary->cdev->dev, + "copy_pair_swap: moving blockdevice parent %s->%s failed (%d)\n", + dev_name(&primary->cdev->dev), + dev_name(&secondary->cdev->dev), rc); + } + + if (primary->stopped & DASD_STOPPED_QUIESCE) { + dasd_device_set_stop_bits(secondary, DASD_STOPPED_QUIESCE); + dasd_device_remove_stop_bits(primary, DASD_STOPPED_QUIESCE); + } + + /* + * The secondary device never got through format detection, but since it + * is a copy of the primary device, the format is exactly the same; + * therefore, the detected layout can simply be copied. + */ + sec_priv->uses_cdl = prim_priv->uses_cdl; /* re-enable device */ dasd_device_remove_stop_bits(primary, DASD_STOPPED_PPRC); diff --git a/drivers/target/iscsi/iscsi_target_util.c b/drivers/target/iscsi/iscsi_target_util.c index 8fd53fa3d3..d5cd0e2aaf 100644 --- a/drivers/target/iscsi/iscsi_target_util.c +++ b/drivers/target/iscsi/iscsi_target_util.c @@ -857,8 +857,11 @@ void iscsit_dec_conn_usage_count(struct iscsit_conn *conn) spin_lock_bh(&conn->conn_usage_lock); conn->conn_usage_count--; - if (!conn->conn_usage_count && conn->conn_waiting_on_uc) + if (!conn->conn_usage_count && conn->conn_waiting_on_uc) { + spin_unlock_bh(&conn->conn_usage_lock); complete(&conn->conn_waiting_on_uc_comp); + return; + } spin_unlock_bh(&conn->conn_usage_lock); } diff --git a/drivers/xen/sys-hypervisor.c b/drivers/xen/sys-hypervisor.c index feb1d16252..989c9a6e1d 100644 --- a/drivers/xen/sys-hypervisor.c +++ b/drivers/xen/sys-hypervisor.c @@ -364,6 +364,8 @@ static ssize_t buildid_show(struct hyp_sysfs_attr *attr, char *buffer) ret = sprintf(buffer, ""); return ret; } + if (ret > PAGE_SIZE) + return -ENOSPC; buildid = kmalloc(sizeof(*buildid) + ret, GFP_KERNEL); if (!buildid) @@ -371,8 +373,10 @@ static ssize_t buildid_show(struct hyp_sysfs_attr *attr, char *buffer) buildid->len = ret; ret = HYPERVISOR_xen_version(XENVER_build_id, buildid); - if (ret > 0) - ret = sprintf(buffer, "%s", buildid->buf); + if (ret > 0) { + /* Build id is binary, not a string. */ + memcpy(buffer, buildid->buf, ret); + } kfree(buildid); return ret; diff --git a/fs/dlm/lock.c b/fs/dlm/lock.c index fe0cf674b8..7c2eb8168b 100644 --- a/fs/dlm/lock.c +++ b/fs/dlm/lock.c @@ -442,13 +442,16 @@ static int rsb_cmp(struct dlm_rsb *r, const char *name, int nlen) return memcmp(r->res_name, maxname, DLM_RESNAME_MAXLEN); } -int dlm_search_rsb_tree(struct rb_root *tree, char *name, int len, - struct dlm_rsb **r_ret) +int dlm_search_rsb_tree(struct rb_root *tree, char *name, + unsigned int len, struct dlm_rsb **r_ret) { struct rb_node *node = tree->rb_node; struct dlm_rsb *r; int rc; + if (len > DLM_RESNAME_MAXLEN) + return -EINVAL; + while (node) { r = rb_entry(node, struct dlm_rsb, res_hashnode); rc = rsb_cmp(r, name, len); diff --git a/fs/dlm/lock.h b/fs/dlm/lock.h index 456c6ec3ef..8cf81b512f 100644 --- a/fs/dlm/lock.h +++ b/fs/dlm/lock.h @@ -30,8 +30,8 @@ void dlm_adjust_timeouts(struct dlm_ls *ls); int dlm_master_lookup(struct dlm_ls *ls, int nodeid, char *name, int len, unsigned int flags, int *r_nodeid, int *result); -int dlm_search_rsb_tree(struct rb_root *tree, char *name, int len, - struct dlm_rsb **r_ret); +int dlm_search_rsb_tree(struct rb_root *tree, char *name, + unsigned int len, struct dlm_rsb **r_ret); void dlm_recover_purge(struct dlm_ls *ls); void dlm_purge_mstcpy_locks(struct dlm_rsb *r); diff --git a/fs/smb/client/smb2file.c b/fs/smb/client/smb2file.c index fa9726f531..4793981e2b 100644 --- a/fs/smb/client/smb2file.c +++ b/fs/smb/client/smb2file.c @@ -27,10 +27,11 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov) { struct smb2_err_rsp *err = iov->iov_base; struct smb2_symlink_err_rsp *sym = ERR_PTR(-EINVAL); + u8 *end = (u8 *)err + iov->iov_len; u32 len; if (err->ErrorContextCount) { - struct smb2_error_context_rsp *p, *end; + struct smb2_error_context_rsp *p; len = (u32)err->ErrorContextCount * (offsetof(struct smb2_error_context_rsp, ErrorContextData) + @@ -39,8 +40,7 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov) return ERR_PTR(-EINVAL); p = (struct smb2_error_context_rsp *)err->ErrorData; - end = (struct smb2_error_context_rsp *)((u8 *)err + iov->iov_len); - do { + while ((u8 *)p + sizeof(*p) <= end) { if (le32_to_cpu(p->ErrorId) == SMB2_ERROR_ID_DEFAULT) { sym = (struct smb2_symlink_err_rsp *)p->ErrorContextData; break; @@ -50,14 +50,16 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov) len = ALIGN(le32_to_cpu(p->ErrorDataLength), 8); p = (struct smb2_error_context_rsp *)(p->ErrorContextData + len); - } while (p < end); + } } else if (le32_to_cpu(err->ByteCount) >= sizeof(*sym) && iov->iov_len >= SMB2_SYMLINK_STRUCT_SIZE) { sym = (struct smb2_symlink_err_rsp *)err->ErrorData; } - if (!IS_ERR(sym) && (le32_to_cpu(sym->SymLinkErrorTag) != SYMLINK_ERROR_TAG || - le32_to_cpu(sym->ReparseTag) != IO_REPARSE_TAG_SYMLINK)) + if (!IS_ERR(sym) && + ((u8 *)sym + sizeof(*sym) > end || + le32_to_cpu(sym->SymLinkErrorTag) != SYMLINK_ERROR_TAG || + le32_to_cpu(sym->ReparseTag) != IO_REPARSE_TAG_SYMLINK)) sym = ERR_PTR(-EINVAL); return sym; @@ -128,8 +130,10 @@ int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec print_len = le16_to_cpu(sym->PrintNameLength); print_offs = le16_to_cpu(sym->PrintNameOffset); - if (iov->iov_len < SMB2_SYMLINK_STRUCT_SIZE + sub_offs + sub_len || - iov->iov_len < SMB2_SYMLINK_STRUCT_SIZE + print_offs + print_len) + if ((char *)sym->PathBuffer + sub_offs + sub_len > + (char *)iov->iov_base + iov->iov_len || + (char *)sym->PathBuffer + print_offs + print_len > + (char *)iov->iov_base + iov->iov_len) return -EINVAL; return smb2_parse_native_symlink(path, diff --git a/include/linux/dpll.h b/include/linux/dpll.h index 6961f6e95d..7f8fa58662 100644 --- a/include/linux/dpll.h +++ b/include/linux/dpll.h @@ -55,8 +55,12 @@ struct dpll_device_ops { void *dpll_priv, u32 *factor, struct netlink_ext_ack *extack); - RH_KABI_RESERVE(1) - RH_KABI_RESERVE(2) + RH_KABI_USE(1, int (*freq_monitor_set)(const struct dpll_device *dpll, void *dpll_priv, + enum dpll_feature_state state, + struct netlink_ext_ack *extack)) + RH_KABI_USE(2, int (*freq_monitor_get)(const struct dpll_device *dpll, void *dpll_priv, + enum dpll_feature_state *state, + struct netlink_ext_ack *extack)) RH_KABI_RESERVE(3) RH_KABI_RESERVE(4) RH_KABI_RESERVE(5) @@ -141,7 +145,10 @@ struct dpll_pin_ops { enum dpll_pin_state *state, struct netlink_ext_ack *extack); - RH_KABI_RESERVE(1) + RH_KABI_USE(1, int (*measured_freq_get)(const struct dpll_pin *pin, void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 *measured_freq, + struct netlink_ext_ack *extack)) RH_KABI_RESERVE(2) RH_KABI_RESERVE(3) RH_KABI_RESERVE(4) diff --git a/include/linux/gfp.h b/include/linux/gfp.h index 3e4c0c536a..ac836590ba 100644 --- a/include/linux/gfp.h +++ b/include/linux/gfp.h @@ -348,9 +348,15 @@ extern gfp_t gfp_allowed_mask; /* Returns true if the gfp_mask allows use of ALLOC_NO_WATERMARK */ bool gfp_pfmemalloc_allowed(gfp_t gfp_mask); +/* A helper for checking if gfp includes all the specified flags */ +static inline bool gfp_has_flags(gfp_t gfp, gfp_t flags) +{ + return (gfp & flags) == flags; +} + static inline bool gfp_has_io_fs(gfp_t gfp) { - return (gfp & (__GFP_IO | __GFP_FS)) == (__GFP_IO | __GFP_FS); + return gfp_has_flags(gfp, __GFP_IO | __GFP_FS); } /* diff --git a/include/linux/gfp_types.h b/include/linux/gfp_types.h index 6583a58670..359ed69b14 100644 --- a/include/linux/gfp_types.h +++ b/include/linux/gfp_types.h @@ -168,7 +168,8 @@ typedef unsigned int __bitwise gfp_t; * the caller still has to check for failures) while costly requests try to be * not disruptive and back off even without invoking the OOM killer. * The following three modifiers might be used to override some of these - * implicit rules + * implicit rules. Please note that all of them must be used along with + * %__GFP_DIRECT_RECLAIM flag. * * %__GFP_NORETRY: The VM implementation will try only very lightweight * memory direct reclaim to get some memory under memory pressure (thus @@ -199,11 +200,14 @@ typedef unsigned int __bitwise gfp_t; * cannot handle allocation failures. The allocation could block * indefinitely but will never return with failure. Testing for * failure is pointless. + * It _must_ be blockable and used together with __GFP_DIRECT_RECLAIM. + * It should _never_ be used in non-sleepable contexts. * New users should be evaluated carefully (and the flag should be * used only when there is no reasonable failure policy) but it is * definitely preferable to use the flag rather than opencode endless * loop around allocator. - * Using this flag for costly allocations is _highly_ discouraged. + * Allocating pages from the buddy with __GFP_NOFAIL and order > 1 is + * not supported. Please consider using kvmalloc() instead. */ #define __GFP_IO ((__force gfp_t)___GFP_IO) #define __GFP_FS ((__force gfp_t)___GFP_FS) diff --git a/include/net/netfilter/nf_conntrack_core.h b/include/net/netfilter/nf_conntrack_core.h index ff315e3ae7..6ff83618e9 100644 --- a/include/net/netfilter/nf_conntrack_core.h +++ b/include/net/netfilter/nf_conntrack_core.h @@ -84,6 +84,11 @@ void nf_conntrack_lock(spinlock_t *lock); extern spinlock_t nf_conntrack_expect_lock; +static inline void lockdep_nfct_expect_lock_held(void) +{ + lockdep_assert_held(&nf_conntrack_expect_lock); +} + /* ctnetlink code shared by both ctnetlink and nf_conntrack_bpf */ #if (IS_BUILTIN(CONFIG_NF_CONNTRACK) && IS_ENABLED(CONFIG_DEBUG_INFO_BTF)) || \ diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index 603a88cb66..93614ccd8a 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -190,7 +190,8 @@ enum dpll_pin_capabilities { DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE = 4, }; -#define DPLL_PHASE_OFFSET_DIVIDER 1000 +#define DPLL_PHASE_OFFSET_DIVIDER 1000 +#define DPLL_PIN_MEASURED_FREQUENCY_DIVIDER 1000 /** * enum dpll_feature_state - Allow control (enable/disable) and status checking @@ -217,6 +218,7 @@ enum dpll_a { DPLL_A_CLOCK_QUALITY_LEVEL, DPLL_A_PHASE_OFFSET_MONITOR, DPLL_A_PHASE_OFFSET_AVG_FACTOR, + DPLL_A_FREQUENCY_MONITOR, __DPLL_A_MAX, DPLL_A_MAX = (__DPLL_A_MAX - 1) @@ -253,6 +255,7 @@ enum dpll_a_pin { DPLL_A_PIN_REFERENCE_SYNC, DPLL_A_PIN_PHASE_ADJUST_GRAN, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT, + DPLL_A_PIN_MEASURED_FREQUENCY, __DPLL_A_PIN_MAX, DPLL_A_PIN_MAX = (__DPLL_A_PIN_MAX - 1) diff --git a/kernel.sbat b/kernel.sbat index d11bc6926d..9319e0785b 100644 --- a/kernel.sbat +++ b/kernel.sbat @@ -1,3 +1,3 @@ sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md -kernel.rhel,1,Red Hat,kernel-core,5.14.0-687.12.1.el9.x86_64,mailto:secalert@redhat.com -kernel.almalinux,1,AlmaLinux,kernel-core,5.14.0-687.12.1.el9.x86_64,mailto:security@almalinux.org +kernel.rhel,1,Red Hat,kernel-core,5.14.0-687.15.1.el9.x86_64,mailto:secalert@redhat.com +kernel.almalinux,1,AlmaLinux,kernel-core,5.14.0-687.15.1.el9.x86_64,mailto:security@almalinux.org diff --git a/mm/page_alloc.c b/mm/page_alloc.c index 85587366d3..d6607174b2 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -290,6 +290,14 @@ int user_min_free_kbytes = -1; static int watermark_boost_factor __read_mostly = 15000; static int watermark_scale_factor = 10; +/* + * RHEL-ONLY: When set to 1, allows reclaim for __GFP_THISNODE THP allocations, + * restoring the behavior prior to the fix that prevents zone_reclaim_mode-like + * excessive reclaim on a single NUMA node when other nodes have plenty of free + * memory. + */ +static int thp_thisnode_reclaim __read_mostly = 1; + /* movable_zone is the "real" zone pages in ZONE_MOVABLE are taken from */ int movable_zone; EXPORT_SYMBOL(movable_zone); @@ -2915,12 +2923,6 @@ struct page *rmqueue(struct zone *preferred_zone, { struct page *page; - /* - * We most definitely don't want callers attempting to - * allocate greater than order-1 page units with __GFP_NOFAIL. - */ - WARN_ON_ONCE((gfp_flags & __GFP_NOFAIL) && (order > 1)); - if (likely(pcp_allowed_order(order))) { page = rmqueue_pcplist(preferred_zone, zone, order, migratetype, alloc_flags); @@ -4062,7 +4064,8 @@ __alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order, struct alloc_context *ac) { bool can_direct_reclaim = gfp_mask & __GFP_DIRECT_RECLAIM; - bool can_compact = gfp_compaction_allowed(gfp_mask); + bool can_compact = can_direct_reclaim && gfp_compaction_allowed(gfp_mask); + bool nofail = gfp_mask & __GFP_NOFAIL; const bool costly_order = order > PAGE_ALLOC_COSTLY_ORDER; struct page *page = NULL; unsigned int alloc_flags; @@ -4074,6 +4077,27 @@ __alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order, unsigned int cpuset_mems_cookie; unsigned int zonelist_iter_cookie; int reserve_flags; + bool compact_first = false; + bool can_retry_reserves = true; + + if (unlikely(nofail)) { + /* + * We most definitely don't want callers attempting to + * allocate greater than order-1 page units with __GFP_NOFAIL. + */ + WARN_ON_ONCE(order > 1); + /* + * Also we don't support __GFP_NOFAIL without __GFP_DIRECT_RECLAIM, + * otherwise, we may result in lockup. + */ + WARN_ON_ONCE(!can_direct_reclaim); + /* + * PF_MEMALLOC request from this context is rather bizarre + * because we cannot reclaim anything and only can loop waiting + * for somebody to do a work for us. + */ + WARN_ON_ONCE(current->flags & PF_MEMALLOC); + } restart: compaction_retries = 0; @@ -4082,6 +4106,19 @@ restart: cpuset_mems_cookie = read_mems_allowed_begin(); zonelist_iter_cookie = zonelist_iter_begin(); + /* + * For costly allocations, try direct compaction first, as it's likely + * that we have enough base pages and don't need to reclaim. For non- + * movable high-order allocations, do that as well, as compaction will + * try prevent permanent fragmentation by migrating from blocks of the + * same migratetype. + */ + if (can_compact && (costly_order || (order > 0 && + ac->migratetype != MIGRATE_MOVABLE))) { + compact_first = true; + compact_priority = INIT_COMPACT_PRIORITY; + } + /* * The fast path uses conservative alloc_flags to succeed only until * kswapd needs to be woken up, and to avoid the cost of setting up @@ -4113,6 +4150,8 @@ restart: goto nopage; } +retry: + /* Ensure kswapd doesn't accidentally go to sleep as long as we loop */ if (alloc_flags & ALLOC_KSWAPD) wake_all_kswapds(order, gfp_mask, ac); @@ -4124,66 +4163,6 @@ restart: if (page) goto got_pg; - /* - * For costly allocations, try direct compaction first, as it's likely - * that we have enough base pages and don't need to reclaim. For non- - * movable high-order allocations, do that as well, as compaction will - * try prevent permanent fragmentation by migrating from blocks of the - * same migratetype. - * Don't try this for allocations that are allowed to ignore - * watermarks, as the ALLOC_NO_WATERMARKS attempt didn't yet happen. - */ - if (can_direct_reclaim && can_compact && - (costly_order || - (order > 0 && ac->migratetype != MIGRATE_MOVABLE)) - && !gfp_pfmemalloc_allowed(gfp_mask)) { - page = __alloc_pages_direct_compact(gfp_mask, order, - alloc_flags, ac, - INIT_COMPACT_PRIORITY, - &compact_result); - if (page) - goto got_pg; - - /* - * Checks for costly allocations with __GFP_NORETRY, which - * includes some THP page fault allocations - */ - if (costly_order && (gfp_mask & __GFP_NORETRY)) { - /* - * If allocating entire pageblock(s) and compaction - * failed because all zones are below low watermarks - * or is prohibited because it recently failed at this - * order, fail immediately unless the allocator has - * requested compaction and reclaim retry. - * - * Reclaim is - * - potentially very expensive because zones are far - * below their low watermarks or this is part of very - * bursty high order allocations, - * - not guaranteed to help because isolate_freepages() - * may not iterate over freed pages as part of its - * linear scan, and - * - unlikely to make entire pageblocks free on its - * own. - */ - if (compact_result == COMPACT_SKIPPED || - compact_result == COMPACT_DEFERRED) - goto nopage; - - /* - * Looks like reclaim/compaction is worth trying, but - * sync compaction could be very expensive, so keep - * using async compaction. - */ - compact_priority = INIT_COMPACT_PRIORITY; - } - } - -retry: - /* Ensure kswapd doesn't accidentally go to sleep as long as we loop */ - if (alloc_flags & ALLOC_KSWAPD) - wake_all_kswapds(order, gfp_mask, ac); - reserve_flags = __gfp_pfmemalloc_flags(gfp_mask); if (reserve_flags) alloc_flags = gfp_to_alloc_flags_cma(gfp_mask, reserve_flags) | @@ -4198,12 +4177,18 @@ retry: ac->nodemask = NULL; ac->preferred_zoneref = first_zones_zonelist(ac->zonelist, ac->highest_zoneidx, ac->nodemask); - } - /* Attempt with potentially adjusted zonelist and alloc_flags */ - page = get_page_from_freelist(gfp_mask, order, alloc_flags, ac); - if (page) - goto got_pg; + /* + * The first time we adjust anything due to being allowed to + * ignore memory policies or watermarks, retry immediately. This + * allows us to keep the first allocation attempt optimistic so + * it can succeed in a zone that is still above watermarks. + */ + if (can_retry_reserves) { + can_retry_reserves = false; + goto retry; + } + } /* Caller is not willing to reclaim, we can't balance anything */ if (!can_direct_reclaim) @@ -4214,10 +4199,12 @@ retry: goto nopage; /* Try direct reclaim and then allocating */ - page = __alloc_pages_direct_reclaim(gfp_mask, order, alloc_flags, ac, - &did_some_progress); - if (page) - goto got_pg; + if (!compact_first) { + page = __alloc_pages_direct_reclaim(gfp_mask, order, alloc_flags, + ac, &did_some_progress); + if (page) + goto got_pg; + } /* Try direct compaction and then allocating */ page = __alloc_pages_direct_compact(gfp_mask, order, alloc_flags, ac, @@ -4225,6 +4212,44 @@ retry: if (page) goto got_pg; + if (compact_first) { + /* + * THP page faults may attempt local node only first, but are + * then allowed to only compact, not reclaim, see + * alloc_pages_mpol(). + * + * Compaction has failed above and we don't want such THP + * allocations to put reclaim pressure on a single node in a + * situation where other nodes might have plenty of available + * memory. + * + * RHEL-ONLY: vm.thp_thisnode_reclaim can override this to + * restore the pre-fix behavior: allow reclaim for THISNODE THP + * allocations, but still fail immediately when compaction was + * skipped (insufficient free base pages) or deferred (recent + * compaction failures at this order). + */ + if (gfp_has_flags(gfp_mask, __GFP_NORETRY | __GFP_THISNODE)) { + if (!thp_thisnode_reclaim) + goto nopage; + if (compact_result == COMPACT_SKIPPED || + compact_result == COMPACT_DEFERRED) + goto nopage; + } + + /* + * For the initial compaction attempt we have lowered its + * priority. Restore it for further retries, if those are + * allowed. With __GFP_NORETRY there will be a single round of + * reclaim and compaction with the lowered priority. + */ + if (!(gfp_mask & __GFP_NORETRY)) + compact_priority = DEF_COMPACT_PRIORITY; + + compact_first = false; + goto retry; + } + /* Do not loop if specifically requested */ if (gfp_mask & __GFP_NORETRY) goto nopage; @@ -4237,6 +4262,15 @@ retry: !(gfp_mask & __GFP_RETRY_MAYFAIL))) goto nopage; + /* + * Deal with possible cpuset update races or zonelist updates to avoid + * infinite retries. No "goto retry;" can be placed above this check + * unless it can execute just once. + */ + if (check_retry_cpuset(cpuset_mems_cookie, ac) || + check_retry_zonelist(zonelist_iter_cookie)) + goto restart; + if (should_reclaim_retry(gfp_mask, order, ac, alloc_flags, did_some_progress > 0, &no_progress_loops)) goto retry; @@ -4292,29 +4326,15 @@ nopage: * Make sure that __GFP_NOFAIL request doesn't leak out and make sure * we always retry */ - if (gfp_mask & __GFP_NOFAIL) { + if (unlikely(nofail)) { /* - * All existing users of the __GFP_NOFAIL are blockable, so warn - * of any new users that actually require GFP_NOWAIT + * Lacking direct_reclaim we can't do anything to reclaim memory, + * we disregard these unreasonable nofail requests and still + * return NULL */ - if (WARN_ON_ONCE_GFP(!can_direct_reclaim, gfp_mask)) + if (!can_direct_reclaim) goto fail; - /* - * PF_MEMALLOC request from this context is rather bizarre - * because we cannot reclaim anything and only can loop waiting - * for somebody to do a work for us - */ - WARN_ON_ONCE_GFP(current->flags & PF_MEMALLOC, gfp_mask); - - /* - * non failing costly orders are a hard requirement which we - * are not prepared for much so let's warn about these users - * so that we can identify them and convert them to something - * else. - */ - WARN_ON_ONCE_GFP(costly_order, gfp_mask); - /* * Help non-failing allocations by giving some access to memory * reserves normally used for high priority non-blocking @@ -6213,6 +6233,15 @@ static struct ctl_table page_alloc_sysctl_table[] = { .extra1 = SYSCTL_ZERO, .extra2 = SYSCTL_ONE_HUNDRED, }, + { + .procname = "thp_thisnode_reclaim", //RHEL-ONLY + .data = &thp_thisnode_reclaim, + .maxlen = sizeof(thp_thisnode_reclaim), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_ONE, + }, #endif {} }; diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index 400ca6a0b9..4475aa3c19 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -479,6 +479,7 @@ static int ipv6_rpl_srh_rcv(struct sk_buff *skb) struct net *net = dev_net(skb->dev); struct inet6_dev *idev; struct ipv6hdr *oldhdr; + unsigned int chdr_len; struct in6_addr addr; unsigned char *buf; int accept_rpl_seg; @@ -601,8 +602,10 @@ looped_back: skb_pull(skb, ((hdr->hdrlen + 1) << 3)); skb_postpull_rcsum(skb, oldhdr, sizeof(struct ipv6hdr) + ((hdr->hdrlen + 1) << 3)); - if (unlikely(!hdr->segments_left)) { - if (pskb_expand_head(skb, sizeof(struct ipv6hdr) + ((chdr->hdrlen + 1) << 3), 0, + chdr_len = sizeof(struct ipv6hdr) + ((chdr->hdrlen + 1) << 3); + if (unlikely(!hdr->segments_left || + skb_headroom(skb) < chdr_len + skb->mac_len)) { + if (pskb_expand_head(skb, chdr_len + skb->mac_len, 0, GFP_ATOMIC)) { __IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)), IPSTATS_MIB_OUTDISCARDS); kfree_skb(skb); @@ -612,7 +615,7 @@ looped_back: oldhdr = ipv6_hdr(skb); } - skb_push(skb, ((chdr->hdrlen + 1) << 3) + sizeof(struct ipv6hdr)); + skb_push(skb, chdr_len); skb_reset_network_header(skb); skb_mac_header_rebuild(skb); skb_set_transport_header(skb, sizeof(struct ipv6hdr)); diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index 6154a83bb0..ba3cc19ee9 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -603,11 +603,16 @@ ip4ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, if (!skb2) return 0; + /* Remove debris left by IPv6 stack. */ + memset(IPCB(skb2), 0, sizeof(*IPCB(skb2))); + skb_dst_drop(skb2); skb_pull(skb2, offset); skb_reset_network_header(skb2); eiph = ip_hdr(skb2); + if (eiph->version != 4 || eiph->ihl < 5) + goto out; /* Try to guess incoming interface */ rt = ip_route_output_ports(dev_net(skb->dev), &fl4, NULL, eiph->saddr, diff --git a/net/netfilter/nf_conntrack_ecache.c b/net/netfilter/nf_conntrack_ecache.c index 69948e1d69..6526bdcca5 100644 --- a/net/netfilter/nf_conntrack_ecache.c +++ b/net/netfilter/nf_conntrack_ecache.c @@ -237,6 +237,8 @@ void nf_ct_expect_event_report(enum ip_conntrack_expect_events event, struct nf_ct_event_notifier *notify; struct nf_conntrack_ecache *e; + lockdep_nfct_expect_lock_held(); + rcu_read_lock(); notify = rcu_dereference(net->ct.nf_conntrack_event_cb); if (!notify) diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c index ce1ac3187f..29bd82d171 100644 --- a/net/netfilter/nf_conntrack_expect.c +++ b/net/netfilter/nf_conntrack_expect.c @@ -51,6 +51,7 @@ void nf_ct_unlink_expect_report(struct nf_conntrack_expect *exp, struct net *net = nf_ct_exp_net(exp); struct nf_conntrack_net *cnet; + lockdep_nfct_expect_lock_held(); WARN_ON(!master_help); WARN_ON(timer_pending(&exp->timeout)); @@ -118,6 +119,8 @@ nf_ct_exp_equal(const struct nf_conntrack_tuple *tuple, bool nf_ct_remove_expect(struct nf_conntrack_expect *exp) { + lockdep_nfct_expect_lock_held(); + if (del_timer(&exp->timeout)) { nf_ct_unlink_expect(exp); nf_ct_expect_put(exp); @@ -177,6 +180,8 @@ nf_ct_find_expectation(struct net *net, struct nf_conntrack_expect *i, *exp = NULL; unsigned int h; + lockdep_nfct_expect_lock_held(); + if (!cnet->expect_count) return NULL; @@ -442,6 +447,8 @@ static inline int __nf_ct_expect_check(struct nf_conntrack_expect *expect, unsigned int h; int ret = 0; + lockdep_nfct_expect_lock_held(); + if (!master_help) { ret = -ESHUTDOWN; goto out; @@ -498,8 +505,9 @@ int nf_ct_expect_related_report(struct nf_conntrack_expect *expect, nf_ct_expect_insert(expect); - spin_unlock_bh(&nf_conntrack_expect_lock); nf_ct_expect_event_report(IPEXP_NEW, expect, portid, report); + spin_unlock_bh(&nf_conntrack_expect_lock); + return 0; out: spin_unlock_bh(&nf_conntrack_expect_lock); diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index 91151e5df6..e19697d16d 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c @@ -3329,31 +3329,37 @@ static int ctnetlink_get_expect(struct sk_buff *skb, if (err < 0) return err; + skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!skb2) + return -ENOMEM; + + spin_lock_bh(&nf_conntrack_expect_lock); exp = nf_ct_expect_find_get(info->net, &zone, &tuple); - if (!exp) + if (!exp) { + spin_unlock_bh(&nf_conntrack_expect_lock); + kfree_skb(skb2); return -ENOENT; + } if (cda[CTA_EXPECT_ID]) { __be32 id = nla_get_be32(cda[CTA_EXPECT_ID]); if (id != nf_expect_get_id(exp)) { nf_ct_expect_put(exp); + spin_unlock_bh(&nf_conntrack_expect_lock); + kfree_skb(skb2); return -ENOENT; } } - skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); - if (!skb2) { - nf_ct_expect_put(exp); - return -ENOMEM; - } - rcu_read_lock(); err = ctnetlink_exp_fill_info(skb2, NETLINK_CB(skb).portid, info->nlh->nlmsg_seq, IPCTNL_MSG_EXP_NEW, exp); rcu_read_unlock(); nf_ct_expect_put(exp); + spin_unlock_bh(&nf_conntrack_expect_lock); + if (err <= 0) { kfree_skb(skb2); return -ENOMEM; @@ -3403,22 +3409,26 @@ static int ctnetlink_del_expect(struct sk_buff *skb, if (err < 0) return err; + spin_lock_bh(&nf_conntrack_expect_lock); + /* bump usage count to 2 */ exp = nf_ct_expect_find_get(info->net, &zone, &tuple); - if (!exp) + if (!exp) { + spin_unlock_bh(&nf_conntrack_expect_lock); return -ENOENT; + } if (cda[CTA_EXPECT_ID]) { __be32 id = nla_get_be32(cda[CTA_EXPECT_ID]); if (id != nf_expect_get_id(exp)) { nf_ct_expect_put(exp); + spin_unlock_bh(&nf_conntrack_expect_lock); return -ENOENT; } } /* after list removal, usage count == 1 */ - spin_lock_bh(&nf_conntrack_expect_lock); if (del_timer(&exp->timeout)) { nf_ct_unlink_expect_report(exp, NETLINK_CB(skb).portid, nlmsg_report(info->nlh)); diff --git a/net/openvswitch/vport-netdev.c b/net/openvswitch/vport-netdev.c index 3c41d53bb9..18376e10ae 100644 --- a/net/openvswitch/vport-netdev.c +++ b/net/openvswitch/vport-netdev.c @@ -144,11 +144,15 @@ static void vport_netdev_free(struct rcu_head *rcu) void ovs_netdev_detach_dev(struct vport *vport) { ASSERT_RTNL(); - vport->dev->priv_flags &= ~IFF_OVS_DATAPATH; netdev_rx_handler_unregister(vport->dev); netdev_upper_dev_unlink(vport->dev, netdev_master_upper_dev_get(vport->dev)); dev_set_promiscuity(vport->dev, -1); + + /* paired with smp_mb() in netdev_destroy() */ + smp_wmb(); + + vport->dev->priv_flags &= ~IFF_OVS_DATAPATH; } static void netdev_destroy(struct vport *vport) @@ -167,6 +171,9 @@ static void netdev_destroy(struct vport *vport) rtnl_unlock(); } + /* paired with smp_wmb() in ovs_netdev_detach_dev() */ + smp_mb(); + call_rcu(&vport->rcu, vport_netdev_free); } diff --git a/sound/usb/6fire/chip.c b/sound/usb/6fire/chip.c index 5ff78814e6..874f6cd503 100644 --- a/sound/usb/6fire/chip.c +++ b/sound/usb/6fire/chip.c @@ -53,11 +53,6 @@ static void usb6fire_chip_abort(struct sfire_chip *chip) usb6fire_comm_abort(chip); if (chip->control) usb6fire_control_abort(chip); - if (chip->card) { - snd_card_disconnect(chip->card); - snd_card_free_when_closed(chip->card); - chip->card = NULL; - } } } @@ -168,6 +163,7 @@ destroy_chip: static void usb6fire_chip_disconnect(struct usb_interface *intf) { struct sfire_chip *chip; + struct snd_card *card; chip = usb_get_intfdata(intf); if (chip) { /* if !chip, fw upload has been performed */ @@ -178,8 +174,19 @@ static void usb6fire_chip_disconnect(struct usb_interface *intf) chips[chip->regidx] = NULL; } + /* + * Save card pointer before teardown. + * snd_card_free_when_closed() may free card (and + * the embedded chip) immediately, so it must be + * called last and chip must not be accessed after. + */ + card = chip->card; chip->shutdown = true; + if (card) + snd_card_disconnect(card); usb6fire_chip_abort(chip); + if (card) + snd_card_free_when_closed(card); } } }