From patchwork Tue Nov 29 21:37:21 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vadim Fedorenko X-Patchwork-Id: 13059248 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 6C615C4321E for ; Tue, 29 Nov 2022 21:46:23 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236742AbiK2VqW (ORCPT ); Tue, 29 Nov 2022 16:46:22 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33054 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236480AbiK2VqS (ORCPT ); Tue, 29 Nov 2022 16:46:18 -0500 X-Greylist: delayed 486 seconds by postgrey-1.37 at lindbergh.monkeyblade.net; Tue, 29 Nov 2022 13:46:14 PST Received: from novek.ru (unknown [213.148.174.62]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6A0F32B26E; Tue, 29 Nov 2022 13:46:14 -0800 (PST) Received: from nat1.ooonet.ru (gw.zelenaya.net [91.207.137.40]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by novek.ru (Postfix) with ESMTPSA id 9A255504F77; Wed, 30 Nov 2022 00:33:44 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 novek.ru 9A255504F77 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=novek.ru; s=mail; t=1669757628; bh=rl2oD8De2g6JuveaqQb0mOi0dI1h/XlPYWrM4DF4e0U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=AHq+uKwpNNUye6WWt+8jkX4KUAaft98LXNaL+mXgEgXaI6g72Xf2fzHEPm0gW8SYU w4lPUvdxpGIK/MWhoIkENpa9JvlUlz5yrKQjCPAOW3fmilmKyM9m8vIUUwmLZtqLcU bVt562Zll3HSmNT26FVGFImfT4UMaVF85XY/XHIg= From: Vadim Fedorenko To: Jakub Kicinski , Jiri Pirko , Arkadiusz Kubalewski , Jonathan Lemon , Paolo Abeni Cc: netdev@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-clk@vger.kernel.org, Michal Michalik Subject: [RFC PATCH v4 1/4] dpll: add dpll_attr/dpll_pin_attr helper classes Date: Wed, 30 Nov 2022 00:37:21 +0300 Message-Id: <20221129213724.10119-2-vfedorenko@novek.ru> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20221129213724.10119-1-vfedorenko@novek.ru> References: <20221129213724.10119-1-vfedorenko@novek.ru> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-State: RFC From: Arkadiusz Kubalewski Classes designed for easy exchange of dpll configuration values between dpll_netlink, dpll_core and drivers implementing dpll subsystem. ``dpll_attr`` is designed to store/pass/validate attributes related to dpll device. ``dpll_pin_attr`` designed for same reason but for dpll_pin object related values. All possible attributes for dpll objects are stored in hermetic class with access only with API functions. Each attribute is validated on corresponding set function. If value was not set before, the call to get attribute value either returns error or unspecified value, depending on the attribute. The one might also check validaity of any attribute. Co-developed-by: Michal Michalik Signed-off-by: Michal Michalik Signed-off-by: Arkadiusz Kubalewski --- drivers/dpll/dpll_attr.c | 278 +++++++++++++++++++++ drivers/dpll/dpll_pin_attr.c | 456 +++++++++++++++++++++++++++++++++++ include/linux/dpll_attr.h | 433 +++++++++++++++++++++++++++++++++ 3 files changed, 1167 insertions(+) create mode 100644 drivers/dpll/dpll_attr.c create mode 100644 drivers/dpll/dpll_pin_attr.c create mode 100644 include/linux/dpll_attr.h diff --git a/drivers/dpll/dpll_attr.c b/drivers/dpll/dpll_attr.c new file mode 100644 index 000000000000..9cf957978ff5 --- /dev/null +++ b/drivers/dpll/dpll_attr.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dpll_attr.c - dpll attributes handling helper class. + * + * Copyright (c) 2022, Intel Corporation. + */ + +#include +#include +#include + +struct dpll_attr { + unsigned long valid_mask; + enum dpll_lock_status lock_status; + s32 temp; + u32 source_pin_idx; + enum dpll_mode mode; + unsigned long mode_supported_mask; + unsigned int netifindex; +}; + +static const int MAX_BITS = BITS_PER_TYPE(unsigned long); + +struct dpll_attr *dpll_attr_alloc(void) +{ + return kzalloc(sizeof(struct dpll_attr), GFP_KERNEL); +} +EXPORT_SYMBOL_GPL(dpll_attr_alloc); + +void dpll_attr_free(struct dpll_attr *attr) +{ + kfree(attr); +} +EXPORT_SYMBOL_GPL(dpll_attr_free); + +void dpll_attr_clear(struct dpll_attr *attr) +{ + memset(attr, 0, sizeof(*attr)); +} +EXPORT_SYMBOL_GPL(dpll_attr_clear); + +bool dpll_attr_valid(enum dplla attr_id, const struct dpll_attr *attr) +{ + if (!attr) + return false; + if (attr_id > 0 && attr_id < BITS_PER_LONG) + return test_bit(attr_id, &attr->valid_mask); + + return false; +} +EXPORT_SYMBOL_GPL(dpll_attr_valid); + +int +dpll_attr_copy(struct dpll_attr *dst, const struct dpll_attr *src) +{ + if (!src || !dst) + return -EFAULT; + memcpy(dst, src, sizeof(*dst)); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_attr_copy); + +static inline bool dpll_lock_status_valid(enum dpll_lock_status status) +{ + if (status >= DPLL_LOCK_STATUS_UNSPEC && + status <= DPLL_LOCK_STATUS_MAX) + return true; + + return false; +} + +int dpll_attr_lock_status_set(struct dpll_attr *attr, + enum dpll_lock_status status) +{ + if (!attr) + return -EFAULT; + if (!dpll_lock_status_valid(status)) + return -EINVAL; + + attr->lock_status = status; + set_bit(DPLLA_LOCK_STATUS, &attr->valid_mask); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_attr_lock_status_set); + +enum dpll_lock_status dpll_attr_lock_status_get(const struct dpll_attr *attr) +{ + if (!dpll_attr_valid(DPLLA_LOCK_STATUS, attr)) + return DPLL_LOCK_STATUS_UNSPEC; + + return attr->lock_status; +} +EXPORT_SYMBOL_GPL(dpll_attr_lock_status_get); + +int dpll_attr_temp_set(struct dpll_attr *attr, s32 temp) +{ + if (!attr) + return -EFAULT; + + attr->temp = temp; + set_bit(DPLLA_TEMP, &attr->valid_mask); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_attr_temp_set); + +int dpll_attr_temp_get(const struct dpll_attr *attr, s32 *temp) +{ + if (!attr || !temp) + return -EFAULT; + if (!dpll_attr_valid(DPLLA_TEMP, attr)) + return -EINVAL; + + *temp = attr->temp; + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_attr_temp_get); + +int dpll_attr_source_idx_set(struct dpll_attr *attr, u32 source_idx) +{ + if (!attr) + return -EFAULT; + + attr->source_pin_idx = source_idx; + set_bit(DPLLA_SOURCE_PIN_IDX, &attr->valid_mask); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_attr_source_idx_set); + +int dpll_attr_source_idx_get(const struct dpll_attr *attr, u32 *source_idx) +{ + if (!attr || !source_idx) + return -EFAULT; + if (!dpll_attr_valid(DPLLA_SOURCE_PIN_IDX, attr)) + return -EINVAL; + + *source_idx = attr->source_pin_idx; + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_attr_source_idx_get); + +static inline bool dpll_mode_valid(enum dpll_mode mode) +{ + if (mode >= DPLL_MODE_UNSPEC && + mode <= DPLL_MODE_MAX) + return true; + + return false; +} + +int dpll_attr_mode_set(struct dpll_attr *attr, enum dpll_mode mode) +{ + if (!attr) + return -EFAULT; + if (!dpll_mode_valid(mode)) + return -EINVAL; + + attr->mode = mode; + set_bit(DPLLA_MODE, &attr->valid_mask); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_attr_mode_set); + +enum dpll_mode dpll_attr_mode_get(const struct dpll_attr *attr) +{ + if (!attr || !dpll_attr_valid(DPLLA_MODE, attr)) + return DPLL_MODE_UNSPEC; + + return attr->mode; +} +EXPORT_SYMBOL_GPL(dpll_attr_mode_get); + +int dpll_attr_mode_supported_set(struct dpll_attr *attr, enum dpll_mode mode) +{ + if (!attr) + return -EFAULT; + if (!dpll_mode_valid(mode)) + return -EINVAL; + + set_bit(mode, &attr->mode_supported_mask); + set_bit(DPLLA_MODE_SUPPORTED, &attr->valid_mask); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_attr_mode_supported_set); + +bool dpll_attr_mode_supported(const struct dpll_attr *attr, + enum dpll_mode mode) +{ + if (!dpll_mode_valid(mode)) + return false; + if (!dpll_attr_valid(DPLLA_MODE_SUPPORTED, attr)) + return false; + + return test_bit(mode, &attr->mode_supported_mask); +} +EXPORT_SYMBOL_GPL(dpll_attr_mode_supported); + +int dpll_attr_netifindex_set(struct dpll_attr *attr, unsigned int netifindex) +{ + if (!attr) + return -EFAULT; + + attr->netifindex = netifindex; + set_bit(DPLLA_NETIFINDEX, &attr->valid_mask); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_attr_netifindex_set); + +int dpll_attr_netifindex_get(const struct dpll_attr *attr, + unsigned int *netifindex) +{ + if (!dpll_attr_valid(DPLLA_NETIFINDEX, attr)) + return -EINVAL; + + *netifindex = attr->netifindex; + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_attr_netifindex_get); + +static bool dpll_attr_changed(const enum dplla attr_id, + struct dpll_attr *new, + struct dpll_attr *old) +{ + if (dpll_attr_valid(attr_id, new)) { + if (dpll_attr_valid(attr_id, old)) { + switch (attr_id) { + case DPLLA_MODE: + if (new->mode != old->mode) + return true; + break; + case DPLLA_SOURCE_PIN_IDX: + if (new->source_pin_idx != old->source_pin_idx) + return true; + break; + default: + return false; + } + } else { + return true; + } + } + + return false; +} + +int dpll_attr_delta(struct dpll_attr *delta, struct dpll_attr *new, + struct dpll_attr *old) +{ + int ret = -EINVAL; + + if (!delta || !new || !old) + return -EFAULT; + + dpll_attr_clear(delta); + + if (dpll_attr_changed(DPLLA_MODE, new, old)) { + ret = dpll_attr_mode_set(delta, new->mode); + if (ret) + return ret; + } + if (dpll_attr_changed(DPLLA_SOURCE_PIN_IDX, new, old)) { + ret = dpll_attr_source_idx_set(delta, new->source_pin_idx); + if (ret) + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_attr_delta); diff --git a/drivers/dpll/dpll_pin_attr.c b/drivers/dpll/dpll_pin_attr.c new file mode 100644 index 000000000000..bf57476228af --- /dev/null +++ b/drivers/dpll/dpll_pin_attr.c @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dpll_pin_attr.c - Pin attribute handling helper class. + * + * Copyright (c) 2022, Intel Corporation. + */ + +#include +#include +#include + +struct dpll_pin_attr { + unsigned long valid_mask; + enum dpll_pin_type type; + unsigned long types_supported_mask; + enum dpll_pin_signal_type signal_type; + unsigned long signal_types_supported_mask; + u32 custom_freq; + unsigned long state_mask; + unsigned long state_supported_mask; + u32 prio; + unsigned int netifindex; +}; + +static const int MAX_BITS = BITS_PER_TYPE(unsigned long); + +struct dpll_pin_attr *dpll_pin_attr_alloc(void) +{ + return kzalloc(sizeof(struct dpll_pin_attr), GFP_KERNEL); +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_alloc); + +void dpll_pin_attr_free(struct dpll_pin_attr *attr) +{ + kfree(attr); +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_free); + +void dpll_pin_attr_clear(struct dpll_pin_attr *attr) +{ + memset(attr, 0, sizeof(*attr)); +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_clear); + +bool dpll_pin_attr_valid(enum dplla attr_id, const struct dpll_pin_attr *attr) +{ + if (!attr) + return false; + if (attr_id > 0 && attr_id < BITS_PER_LONG) + return test_bit(attr_id, &attr->valid_mask); + + return false; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_valid); + +int +dpll_pin_attr_copy(struct dpll_pin_attr *dst, const struct dpll_pin_attr *src) +{ + if (!src || !dst) + return -EFAULT; + memcpy(dst, src, sizeof(*dst)); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_copy); + +static inline bool dpll_pin_type_valid(enum dpll_pin_type type) +{ + if (type >= DPLL_PIN_TYPE_UNSPEC && type <= DPLL_PIN_TYPE_MAX) + return true; + + return false; +} + +int dpll_pin_attr_type_set(struct dpll_pin_attr *attr, enum dpll_pin_type type) +{ + if (!attr) + return -EFAULT; + if (!dpll_pin_type_valid(type)) + return -EINVAL; + + attr->type = type; + set_bit(DPLLA_PIN_TYPE, &attr->valid_mask); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_type_set); + +enum dpll_pin_type dpll_pin_attr_type_get(const struct dpll_pin_attr *attr) +{ + if (!dpll_pin_attr_valid(DPLLA_PIN_TYPE, attr)) + return DPLL_PIN_TYPE_UNSPEC; + + return attr->type; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_type_get); + +int dpll_pin_attr_type_supported_set(struct dpll_pin_attr *attr, + enum dpll_pin_type type) +{ + if (!attr) + return -EFAULT; + if (!dpll_pin_type_valid(type)) + return -EINVAL; + + set_bit(type, &attr->types_supported_mask); + set_bit(DPLLA_PIN_TYPE_SUPPORTED, &attr->valid_mask); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_type_supported_set); + +bool dpll_pin_attr_type_supported(const struct dpll_pin_attr *attr, + enum dpll_pin_type type) +{ + if (!dpll_pin_type_valid(type)) + return false; + if (!dpll_pin_attr_valid(DPLLA_PIN_TYPE_SUPPORTED, attr)) + return false; + + return test_bit(type, &attr->types_supported_mask); +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_type_supported); + +static inline bool dpll_pin_signal_type_valid(enum dpll_pin_signal_type type) +{ + if (type >= DPLL_PIN_SIGNAL_TYPE_UNSPEC && + type <= DPLL_PIN_SIGNAL_TYPE_MAX) + return true; + + return false; +} + +int dpll_pin_attr_signal_type_set(struct dpll_pin_attr *attr, + enum dpll_pin_signal_type type) +{ + if (!attr) + return -EFAULT; + if (!dpll_pin_signal_type_valid(type)) + return -EINVAL; + + attr->signal_type = type; + set_bit(DPLLA_PIN_SIGNAL_TYPE, &attr->valid_mask); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_signal_type_set); + +enum dpll_pin_signal_type +dpll_pin_attr_signal_type_get(const struct dpll_pin_attr *attr) +{ + if (!dpll_pin_attr_valid(DPLLA_PIN_SIGNAL_TYPE, attr)) + return DPLL_PIN_SIGNAL_TYPE_UNSPEC; + + return attr->signal_type; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_signal_type_get); + +int dpll_pin_attr_signal_type_supported_set(struct dpll_pin_attr *attr, + enum dpll_pin_signal_type type) +{ + if (!attr) + return -EFAULT; + if (!dpll_pin_signal_type_valid(type)) + return -EINVAL; + + set_bit(type, &attr->signal_types_supported_mask); + set_bit(DPLLA_PIN_SIGNAL_TYPE_SUPPORTED, &attr->valid_mask); + + return 0; + +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_signal_type_supported_set); + +bool dpll_pin_attr_signal_type_supported(const struct dpll_pin_attr *attr, + enum dpll_pin_signal_type type) +{ + if (!dpll_pin_signal_type_valid(type)) + return false; + if (!dpll_pin_attr_valid(DPLLA_PIN_SIGNAL_TYPE_SUPPORTED, attr)) + return false; + + return test_bit(type, &attr->signal_types_supported_mask); +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_signal_type_supported); + +int dpll_pin_attr_custom_freq_set(struct dpll_pin_attr *attr, u32 freq) +{ + if (!attr) + return -EFAULT; + + attr->custom_freq = freq; + set_bit(DPLLA_PIN_CUSTOM_FREQ, &attr->valid_mask); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_custom_freq_set); + +int dpll_pin_attr_custom_freq_get(const struct dpll_pin_attr *attr, u32 *freq) +{ + if (!attr || !freq) + return -EFAULT; + if (!test_bit(DPLLA_PIN_CUSTOM_FREQ, &attr->valid_mask)) + return -EINVAL; + + *freq = attr->custom_freq; + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_custom_freq_get); + +static inline bool dpll_pin_state_valid(enum dpll_pin_state state) +{ + if (state >= DPLL_PIN_STATE_UNSPEC && + state <= DPLL_PIN_STATE_MAX) + return true; + + return false; +} + +int dpll_pin_attr_state_set(struct dpll_pin_attr *attr, + enum dpll_pin_state state) +{ + if (!attr) + return -EFAULT; + if (!dpll_pin_state_valid(state)) + return -EINVAL; + if (state == DPLL_PIN_STATE_CONNECTED) { + if (test_bit(DPLL_PIN_STATE_DISCONNECTED, &attr->state_mask)) + return -EINVAL; + } else if (state == DPLL_PIN_STATE_DISCONNECTED) { + if (test_bit(DPLL_PIN_STATE_CONNECTED, &attr->state_mask)) + return -EINVAL; + } + + set_bit(state, &attr->state_mask); + set_bit(DPLLA_PIN_STATE, &attr->valid_mask); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_state_set); + +bool dpll_pin_attr_state_enabled(const struct dpll_pin_attr *attr, + enum dpll_pin_state state) +{ + if (!dpll_pin_state_valid(state)) + return false; + if (!dpll_pin_attr_valid(DPLLA_PIN_STATE, attr)) + return false; + + return test_bit(state, &attr->state_mask); +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_state_enabled); + +int dpll_pin_attr_state_supported_set(struct dpll_pin_attr *attr, + enum dpll_pin_state state) +{ + if (!attr) + return -EFAULT; + if (!dpll_pin_state_valid(state)) + return -EINVAL; + + set_bit(state, &attr->state_supported_mask); + set_bit(DPLLA_PIN_STATE_SUPPORTED, &attr->valid_mask); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_state_supported_set); + +bool dpll_pin_attr_state_supported(const struct dpll_pin_attr *attr, + enum dpll_pin_state state) +{ + if (!dpll_pin_state_valid(state)) + return false; + if (!dpll_pin_attr_valid(DPLLA_PIN_STATE_SUPPORTED, attr)) + return false; + + return test_bit(state, &attr->state_supported_mask); +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_state_supported); + +int dpll_pin_attr_prio_set(struct dpll_pin_attr *attr, u32 prio) +{ + if (!attr) + return -EFAULT; + if (prio > PIN_PRIO_LOWEST) + return -EINVAL; + + attr->prio = prio; + set_bit(DPLLA_PIN_PRIO, &attr->valid_mask); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_prio_set); + +int dpll_pin_attr_prio_get(const struct dpll_pin_attr *attr, u32 *prio) +{ + if (!attr || !prio) + return -EFAULT; + if (!dpll_pin_attr_valid(DPLLA_PIN_PRIO, attr)) + return -EINVAL; + + *prio = attr->prio; + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_prio_get); + +int dpll_pin_attr_netifindex_set(struct dpll_pin_attr *attr, unsigned int netifindex) +{ + if (!attr) + return -EFAULT; + + attr->netifindex = netifindex; + set_bit(DPLLA_PIN_NETIFINDEX, &attr->valid_mask); + + return 0; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_netifindex_set); + +int dpll_pin_attr_netifindex_get(const struct dpll_pin_attr *attr, + unsigned int *netifindex) +{ + if (!dpll_pin_attr_valid(DPLLA_PIN_NETIFINDEX, attr)) + return -EINVAL; + + *netifindex = attr->netifindex; + return true; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_netifindex_get); + +static bool dpll_pin_attr_changed(const enum dplla attr_id, + struct dpll_pin_attr *new, + struct dpll_pin_attr *old) +{ + if (dpll_pin_attr_valid(attr_id, new)) { + if (dpll_pin_attr_valid(attr_id, old)) { + switch (attr_id) { + case DPLLA_PIN_TYPE: + if (new->type != old->type) + return true; + break; + case DPLLA_PIN_SIGNAL_TYPE: + if (new->signal_type != old->signal_type) + return true; + break; + case DPLLA_PIN_CUSTOM_FREQ: + if (new->custom_freq != old->custom_freq) + return true; + break; + case DPLLA_PIN_STATE: + if (new->state_mask != old->state_mask) + return true; + break; + case DPLLA_PIN_PRIO: + if (new->prio != old->prio) + return true; + break; + default: + return false; + } + } else { + return true; + } + } + + return false; +} + +int dpll_pin_attr_delta(struct dpll_pin_attr *delta, struct dpll_pin_attr *new, + struct dpll_pin_attr *old) +{ + int ret = -EINVAL; + + if (!delta || !new || !old) + return -EFAULT; + + dpll_pin_attr_clear(delta); + + if (dpll_pin_attr_changed(DPLLA_PIN_TYPE, new, old)) { + ret = dpll_pin_attr_type_set(delta, new->type); + if (ret) + return ret; + } + if (dpll_pin_attr_changed(DPLLA_PIN_SIGNAL_TYPE, new, old)) { + ret = dpll_pin_attr_signal_type_set(delta, new->signal_type); + if (ret) + return ret; + } + if (dpll_pin_attr_changed(DPLLA_PIN_CUSTOM_FREQ, new, old)) { + ret = dpll_pin_attr_custom_freq_set(delta, new->custom_freq); + if (ret) + return ret; + } + if (dpll_pin_attr_changed(DPLLA_PIN_STATE, new, old)) { + enum dpll_pin_state i; + + for (i = DPLL_PIN_STATE_UNSPEC + 1; + i <= DPLL_PIN_STATE_MAX; i++) + if (test_bit(i, &new->state_mask)) { + ret = dpll_pin_attr_state_set(delta, i); + if (ret) + return ret; + } + } + if (dpll_pin_attr_changed(DPLLA_PIN_PRIO, new, old)) { + ret = dpll_pin_attr_prio_set(delta, new->prio); + if (ret) + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_delta); + +int dpll_pin_attr_prep_common(struct dpll_pin_attr *common, + const struct dpll_pin_attr *reference) +{ + if (!common || !reference) + return -EFAULT; + dpll_pin_attr_clear(common); + if (dpll_pin_attr_valid(DPLLA_PIN_TYPE, reference)) { + common->type = reference->type; + set_bit(DPLLA_PIN_TYPE, &common->valid_mask); + } + if (dpll_pin_attr_valid(DPLLA_PIN_SIGNAL_TYPE, reference)) { + common->signal_type = reference->signal_type; + set_bit(DPLLA_PIN_SIGNAL_TYPE, &common->valid_mask); + } + if (dpll_pin_attr_valid(DPLLA_PIN_CUSTOM_FREQ, reference)) { + common->custom_freq = reference->custom_freq; + set_bit(DPLLA_PIN_CUSTOM_FREQ, &common->valid_mask); + } + if (dpll_pin_attr_valid(DPLLA_PIN_STATE, reference)) { + common->state_mask = reference->state_mask; + set_bit(DPLLA_PIN_STATE, &common->valid_mask); + } + return common->valid_mask ? PIN_ATTR_CHANGE : 0; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_prep_common); + + +int dpll_pin_attr_prep_exclusive(struct dpll_pin_attr *exclusive, + const struct dpll_pin_attr *reference) +{ + if (!exclusive || !reference) + return -EFAULT; + dpll_pin_attr_clear(exclusive); + if (dpll_pin_attr_valid(DPLLA_PIN_PRIO, reference)) { + exclusive->prio = reference->prio; + set_bit(DPLLA_PIN_PRIO, &exclusive->valid_mask); + } + return exclusive->valid_mask ? PIN_ATTR_CHANGE : 0; +} +EXPORT_SYMBOL_GPL(dpll_pin_attr_prep_exclusive); + diff --git a/include/linux/dpll_attr.h b/include/linux/dpll_attr.h new file mode 100644 index 000000000000..fe5e2188ca0b --- /dev/null +++ b/include/linux/dpll_attr.h @@ -0,0 +1,433 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * dpll_attr.h - Header for attribute handling helper class. + * + * Copyright (c) 2022, Intel Corporation. + */ + +#ifndef __DPLL_ATTR_H__ +#define __DPLL_ATTR_H__ + +#include + +struct dpll_attr; +struct dpll_pin_attr; + +#define PIN_PRIO_HIGHEST 0 +#define PIN_PRIO_LOWEST 0xff +#define PIN_ATTR_CHANGE 1 + +/** + * dpll_attr_alloc - allocate a dpll attributes struct + * + * Return: pointer if succeeded, NULL otherwise. + */ +struct dpll_attr *dpll_attr_alloc(void); + +/** + * dpll_attr_free - frees a dpll attributes struct + * @attr: structure with dpll attributes + */ +void dpll_attr_free(struct dpll_attr *attr); + +/** + * dpll_attr_clear - clears a dpll attributes struct + * @attr: structure with dpll attributes + */ +void dpll_attr_clear(struct dpll_attr *attr); + +/** + * dpll_attr_valid - checks if a attribute is valid + * @attr_id: attribute to be checked + * @attr: structure with dpll attributes + * + * Checks if the attribute has been set before and if stored value is valid. + * Return: true if valid, false otherwise. + */ +bool dpll_attr_valid(enum dplla attr_id, const struct dpll_attr *attr); + +/** + * dpll_attr_copy - create a copy of the dpll attributes structure + * @dst: destination structure with dpll attributes + * @src: source structure with dpll attributes + * + * Memory needs to be allocated before calling this function. + * Return: 0 if succeeds, error code otherwise. + */ +int +dpll_attr_copy(struct dpll_attr *dst, const struct dpll_attr *src); + +/** + * dpll_attr_lock_status_set - set the lock status in the attributes + * @attr: structure with dpll attributes + * @status: dpll lock status to be set + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_attr_lock_status_set(struct dpll_attr *attr, + enum dpll_lock_status status); + +/** + * dpll_attr_lock_status_get - get the lock status from the attributes + * @attr: structure with dpll attributes + * + * Return: dpll lock status + */ +enum dpll_lock_status +dpll_attr_lock_status_get(const struct dpll_attr *attr); + +/** + * dpll_attr_temp_set - set the temperature in the attributes + * @attr: structure with dpll attributes + * @temp: temperature to be set + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_attr_temp_set(struct dpll_attr *attr, s32 temp); + +/** + * dpll_attr_temp_get - get the temperature from the attributes + * @attr: structure with dpll attributes + * @temp: temperature (out) + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_attr_temp_get(const struct dpll_attr *attr, s32 *temp); + +/** + * dpll_attr_source_idx_set - set the source id in the attributes + * @attr: structure with dpll attributes + * @source_idx: source id to be set + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_attr_source_idx_set(struct dpll_attr *attr, u32 source_idx); + +/** + * dpll_attr_source_idx_get - get the source id from the attributes + * @attr: structure with dpll attributes + * @source_idx: source id (out) + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_attr_source_idx_get(const struct dpll_attr *attr, u32 *source_idx); + +/** + * dpll_attr_mode_set - set the dpll mode in the attributes + * @attr: structure with dpll attributes + * @mode: dpll mode to be set + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_attr_mode_set(struct dpll_attr *attr, enum dpll_mode mode); + +/** + * dpll_attr_mode_get - get the dpll mode from the attributes + * @attr: structure with dpll attributes + * + * Return: dpll mode. + */ +enum dpll_mode dpll_attr_mode_get(const struct dpll_attr *attr); + +/** + * dpll_attr_mode_set - set the dpll supported mode in the attributes + * @attr: structure with dpll attributes + * @mode: dpll mode to be set in supported modes + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_attr_mode_supported_set(struct dpll_attr *attr, enum dpll_mode mode); + +/** + * dpll_attr_mode_supported - check if the dpll mode is supported + * @attr: structure with dpll attributes + * @mode: dpll mode to be checked + * + * Return: true if mode supported, false otherwise. + */ +bool dpll_attr_mode_supported(const struct dpll_attr *attr, + enum dpll_mode mode); + +/** + * dpll_attr_netifindex_set - set the netifindex in the attributes + * @attr: structure with dpll attributes + * @netifindex: parameter to be set in attributes + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_attr_netifindex_set(struct dpll_attr *attr, unsigned int netifindex); + +/** + * dpll_attr_netifindex_get - get the netifindex from the attributes + * @attr: structure with dpll attributes + * @netifindex: retrieved parameter + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_attr_netifindex_get(const struct dpll_attr *attr, + unsigned int *netifindex); + +/** + * dpll_attr_delta - calculate the difference between two dpll attribute sets + * @delta: structure with delta of dpll attributes + * @new: structure with new dpll attributes + * @old: structure with old dpll attributes + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_attr_delta(struct dpll_attr *delta, struct dpll_attr *new, + struct dpll_attr *old); + +/** + * dpll_pin_attr_alloc - allocate a dpll pin attributes struct + * + * Return: pointer if succeeded, NULL otherwise. + */ +struct dpll_pin_attr *dpll_pin_attr_alloc(void); + +/** + * dpll_pin_attr_free - frees a dpll pin attributes struct + * @attr: structure with dpll pin attributes + */ +void dpll_pin_attr_free(struct dpll_pin_attr *attr); + +/** + * dpll_pin_attr_clear - clears a dpll pin attributes struct + * @attr: structure with dpll attributes + */ +void dpll_pin_attr_clear(struct dpll_pin_attr *attr); + +/** + * dpll_pin_attr_valid - checks if a pin attribute is valid + * @attr_id: attribute to be checked + * @attr: structure with dpll pin attributes + * + * Checks if the attribute has been set before and if stored value is valid. + * Return: true if valid, false otherwise. + */ +bool dpll_pin_attr_valid(enum dplla attr_id, const struct dpll_pin_attr *attr); + +/** + * dpll_pin_attr_copy - create a copy of the dpll pin attributes structure + * @dst: destination structure with dpll pin attributes + * @src: source structure with dpll pin attributes + * + * Memory needs to be allocated before calling this function. + * Return: 0 if succeeds, error code otherwise. + */ +int +dpll_pin_attr_copy(struct dpll_pin_attr *dst, const struct dpll_pin_attr *src); + +/** + * dpll_pin_attr_type_set - set the pin type in the attributes + * @attr: structure with dpll pin attributes + * @type: parameter to be set in attributes + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_attr_type_set(struct dpll_pin_attr *attr, enum dpll_pin_type type); + +/** + * dpll_pin_attr_type_get - get the pin type from the attributes + * @attr: structure with dpll pin attributes + * + * Return: dpll pin type. + */ +enum dpll_pin_type dpll_pin_attr_type_get(const struct dpll_pin_attr *attr); + +/** + * dpll_pin_attr_type_supported_set - set the dpll pin supported type in the + * attributes + * @attr: structure with dpll attributes + * @type: pin type to be set in supported modes + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_attr_type_supported_set(struct dpll_pin_attr *attr, + enum dpll_pin_type type); + +/** + * dpll_pin_attr_type_supported - check if the pin type is supported + * @attr: structure with dpll attributes + * @type: dpll mode to be checked + * + * Return: true if type supported, false otherwise. + */ +bool dpll_pin_attr_type_supported(const struct dpll_pin_attr *attr, + enum dpll_pin_type type); + +/** + * dpll_pin_attr_signal_type_set - set the pin signal type in the attributes + * @attr: structure with dpll attributes + * @type: pin signal type to be set in supported modes + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_attr_signal_type_set(struct dpll_pin_attr *attr, + enum dpll_pin_signal_type type); + +/** + * dpll_pin_attr_signal_type_get - get the pin signal type from the attributes + * @attr: structure with dpll pin attributes + * + * Return: pin signal type. + */ +enum dpll_pin_signal_type +dpll_pin_attr_signal_type_get(const struct dpll_pin_attr *attr); + +/** + * dpll_pin_attr_signal_type_supported_set - set the dpll pin supported signal + * type in the attributes + * @attr: structure with dpll attributes + * @type: pin signal type to be set in supported types + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_attr_signal_type_supported_set(struct dpll_pin_attr *attr, + enum dpll_pin_signal_type type); + +/** + * dpll_pin_attr_signal_type_supported - check if the pin signal type is + * supported + * @attr: structure with dpll attributes + * @type: pin signal type to be checked + * + * Return: true if type supported, false otherwise. + */ +bool dpll_pin_attr_signal_type_supported(const struct dpll_pin_attr *attr, + enum dpll_pin_signal_type type); + +/** + * dpll_pin_attr_custom_freq_set - set the custom frequency in the attributes + * @attr: structure with dpll pin attributes + * @freq: parameter to be set in attributes + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_attr_custom_freq_set(struct dpll_pin_attr *attr, u32 freq); + +/** + * dpll_pin_attr_custom_freq_get - get the pin type from the attributes + * @attr: structure with dpll pin attributes + * @freq: parameter to be retrieved + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_attr_custom_freq_get(const struct dpll_pin_attr *attr, u32 *freq); + +/** + * dpll_pin_attr_state_set - set the pin state the attributes + * @attr: structure with dpll pin attributes + * @state: parameter to be set in attributes + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_attr_state_set(struct dpll_pin_attr *attr, + enum dpll_pin_state state); + +/** + * dpll_pin_attr_state_enabled - check if state is enabled + * @attr: structure with dpll pin attributes + * @state: parameter to be checked in attributes + * + * Return: true if enabled, false otherwise. + */ +bool dpll_pin_attr_state_enabled(const struct dpll_pin_attr *attr, + enum dpll_pin_state state); + +/** + * dpll_pin_attr_state_supported_set - set the supported pin state in the + * attributes + * @attr: structure with dpll attributes + * @state: pin state to be set in supported types + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_attr_state_supported_set(struct dpll_pin_attr *attr, + enum dpll_pin_state state); + +/** + * dpll_pin_attr_state_supported - check if the pin state is supported + * @attr: structure with dpll attributes + * @state: pin signal type to be checked + * + * Return: true if state supported, false otherwise. + */ +bool dpll_pin_attr_state_supported(const struct dpll_pin_attr *attr, + enum dpll_pin_state state); + +/** + * dpll_pin_attr_prio_set - set the pin priority in the attributes + * @attr: structure with dpll pin attributes + * @prio: parameter to be set in attributes + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_attr_prio_set(struct dpll_pin_attr *attr, u32 prio); + +/** + * dpll_pin_attr_prio_get - get the pin priority from the attributes + * @attr: structure with dpll pin attributes + * @prio: parameter to be retrieved + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_attr_prio_get(const struct dpll_pin_attr *attr, u32 *prio); + +/** + * dpll_pin_attr_netifindex_set - set the pin netifindex in the attributes + * @attr: structure with dpll pin attributes + * @netifindex: parameter to be set in attributes + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_attr_netifindex_set(struct dpll_pin_attr *attr, + unsigned int netifindex); + +/** + * dpll_pin_attr_netifindex_get - get the pin netifindex from the attributes + * @attr: structure with dpll pin attributes + * @netifindex: parameter to be retrieved + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_attr_netifindex_get(const struct dpll_pin_attr *attr, + unsigned int *netifindex); + +/** + * dpll_attr_delta - calculate the difference between two dpll pin attribute sets + * @delta: structure with delta of dpll pin attributes + * @new: structure with new dpll pin attributes + * @old: structure with old dpll pin attributes + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_attr_delta(struct dpll_pin_attr *delta, struct dpll_pin_attr *new, + struct dpll_pin_attr *old); + +/** + * dpll_pin_attr_prep_common - calculate the common dpll pin attributes + * @common: structure with common dpll pin attributes + * @reference: referenced structure with dpll pin attributes + * + * Some of the pin attributes applies to all DPLLs and other are exclusive. + * This function calculates if any of the common pin attributes are set. + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_attr_prep_common(struct dpll_pin_attr *common, + const struct dpll_pin_attr *reference); + +/** + * dpll_pin_attr_prep_exclusive - calculate the exclusive dpll pin attributes + * @exclusive: structure with common dpll pin attributes + * @reference: referenced structure with dpll pin attributes + * + * Some of the pin attributes applies to all DPLLs and other are exclusive. + * This function calculates if any of the exclusive pin attributes are set. + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_attr_prep_exclusive(struct dpll_pin_attr *exclusive, + const struct dpll_pin_attr *reference); + + +#endif /* __DPLL_ATTR_H__ */ From patchwork Tue Nov 29 21:37:22 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vadim Fedorenko X-Patchwork-Id: 13059249 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 549D5C4708A for ; Tue, 29 Nov 2022 21:46:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236480AbiK2VqX (ORCPT ); Tue, 29 Nov 2022 16:46:23 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33056 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236611AbiK2VqT (ORCPT ); Tue, 29 Nov 2022 16:46:19 -0500 Received: from novek.ru (unknown [213.148.174.62]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 862536B39E; Tue, 29 Nov 2022 13:46:14 -0800 (PST) Received: from nat1.ooonet.ru (gw.zelenaya.net [91.207.137.40]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by novek.ru (Postfix) with ESMTPSA id 312B5504F7C; Wed, 30 Nov 2022 00:33:48 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 novek.ru 312B5504F7C DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=novek.ru; s=mail; t=1669757633; bh=Px73Ak0KCieM/jVMfdpjGngqllWKBi36oQGAjLjzzXo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=A19Nco04pe+KGWfDTsKMD31Fso197WvQo82Nix8tJisRGm1N4wqy4RSvq8sQb38Mj tRcSPowb9GG2ZoEBnZKVTDvBVtA8O87IdzL9Z3yDWi7w1zEVmvqjyHO3OhwhDzVjSw +x7fGH+zGx4XlDUCQrtZqQ7pu5JmqkLrO2emDJfk= From: Vadim Fedorenko To: Jakub Kicinski , Jiri Pirko , Arkadiusz Kubalewski , Jonathan Lemon , Paolo Abeni Cc: netdev@vger.kernel.org, Vadim Fedorenko , linux-arm-kernel@lists.infradead.org, linux-clk@vger.kernel.org, Milena Olech , Michal Michalik Subject: [RFC PATCH v4 2/4] dpll: Add DPLL framework base functions Date: Wed, 30 Nov 2022 00:37:22 +0300 Message-Id: <20221129213724.10119-3-vfedorenko@novek.ru> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20221129213724.10119-1-vfedorenko@novek.ru> References: <20221129213724.10119-1-vfedorenko@novek.ru> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-State: RFC From: Vadim Fedorenko DPLL framework is used to represent and configure DPLL devices in systems. Each device that has DPLL and can configure sources and outputs can use this framework. Netlink interface is used to provide configuration data and to receive notification messages about changes in the configuration or status of DPLL device. Inputs and outputs of the DPLL device are represented as special objects which could be dynamically added to and removed from DPLL device. Co-developed-by: Milena Olech Signed-off-by: Milena Olech Co-developed-by: Michal Michalik Signed-off-by: Michal Michalik Co-developed-by: Arkadiusz Kubalewski Signed-off-by: Arkadiusz Kubalewski Signed-off-by: Vadim Fedorenko --- MAINTAINERS | 8 + drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/dpll/Kconfig | 7 + drivers/dpll/Makefile | 11 + drivers/dpll/dpll_core.c | 760 ++++++++++++++++++++++++++++ drivers/dpll/dpll_core.h | 176 +++++++ drivers/dpll/dpll_netlink.c | 963 ++++++++++++++++++++++++++++++++++++ drivers/dpll/dpll_netlink.h | 24 + include/linux/dpll.h | 261 ++++++++++ include/uapi/linux/dpll.h | 263 ++++++++++ 11 files changed, 2476 insertions(+) create mode 100644 drivers/dpll/Kconfig create mode 100644 drivers/dpll/Makefile create mode 100644 drivers/dpll/dpll_core.c create mode 100644 drivers/dpll/dpll_core.h create mode 100644 drivers/dpll/dpll_netlink.c create mode 100644 drivers/dpll/dpll_netlink.h create mode 100644 include/linux/dpll.h create mode 100644 include/uapi/linux/dpll.h diff --git a/MAINTAINERS b/MAINTAINERS index 61fe86968111..79b76cca9620 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6343,6 +6343,14 @@ F: Documentation/networking/device_drivers/ethernet/freescale/dpaa2/switch-drive F: drivers/net/ethernet/freescale/dpaa2/dpaa2-switch* F: drivers/net/ethernet/freescale/dpaa2/dpsw* +DPLL CLOCK SUBSYSTEM +M: Vadim Fedorenko +L: netdev@vger.kernel.org +S: Maintained +F: drivers/dpll/* +F: include/net/dpll.h +F: include/uapi/linux/dpll.h + DRBD DRIVER M: Philipp Reisner M: Lars Ellenberg diff --git a/drivers/Kconfig b/drivers/Kconfig index 19ee995bd0ae..a3e00294a995 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -239,4 +239,6 @@ source "drivers/peci/Kconfig" source "drivers/hte/Kconfig" +source "drivers/dpll/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index bdf1c66141c9..7cbee58bc692 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -189,3 +189,4 @@ obj-$(CONFIG_COUNTER) += counter/ obj-$(CONFIG_MOST) += most/ obj-$(CONFIG_PECI) += peci/ obj-$(CONFIG_HTE) += hte/ +obj-$(CONFIG_DPLL) += dpll/ diff --git a/drivers/dpll/Kconfig b/drivers/dpll/Kconfig new file mode 100644 index 000000000000..a4cae73f20d3 --- /dev/null +++ b/drivers/dpll/Kconfig @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Generic DPLL drivers configuration +# + +config DPLL + bool diff --git a/drivers/dpll/Makefile b/drivers/dpll/Makefile new file mode 100644 index 000000000000..3391deb08014 --- /dev/null +++ b/drivers/dpll/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for DPLL drivers. +# + +obj-$(CONFIG_DPLL) += dpll_sys.o +dpll_sys-y += dpll_attr.o +dpll_sys-y += dpll_core.o +dpll_sys-y += dpll_netlink.o +dpll_sys-y += dpll_pin_attr.o + diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c new file mode 100644 index 000000000000..013ac4150583 --- /dev/null +++ b/drivers/dpll/dpll_core.c @@ -0,0 +1,760 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dpll_core.c - Generic DPLL Management class support. + * + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include + +#include "dpll_core.h" + +/** + * struct dpll_pin - structure for a dpll pin + * @idx: unique id number for each pin + * @parent_pin: parent pin + * @type: type of the pin + * @ops: operations this &dpll_pin supports + * @priv: pointer to private information of owner + * @ref_dplls: array of registered dplls + * @description: name to distinguish the pin + */ +struct dpll_pin { + u32 idx; + struct dpll_pin *parent_pin; + enum dpll_pin_type type; + struct dpll_pin_ops *ops; + void *priv; + struct xarray ref_dplls; + char description[PIN_DESC_LEN]; +}; + +/** + * struct dpll_device - structure for a DPLL device + * @id: unique id number for each device + * @dev: struct device for this dpll device + * @parent: parent device + * @ops: operations this &dpll_device supports + * @lock: mutex to serialize operations + * @type: type of a dpll + * @priv: pointer to private information of owner + * @pins: list of pointers to pins registered with this dpll + * @cookie: unique identifier (cookie) of a dpll + * @dev_driver_idx: provided by driver for + */ +struct dpll_device { + u32 id; + struct device dev; + struct device *parent; + struct dpll_device_ops *ops; + struct mutex lock; + enum dpll_type type; + void *priv; + struct xarray pins; + u8 cookie[DPLL_COOKIE_LEN]; + u8 dev_driver_idx; +}; + +static DEFINE_MUTEX(dpll_device_xa_lock); + +static DEFINE_XARRAY_FLAGS(dpll_device_xa, XA_FLAGS_ALLOC); +#define DPLL_REGISTERED XA_MARK_1 +#define PIN_REGISTERED XA_MARK_1 + +#define ASSERT_DPLL_REGISTERED(d) \ + WARN_ON_ONCE(!xa_get_mark(&dpll_device_xa, (d)->id, DPLL_REGISTERED)) +#define ASSERT_DPLL_NOT_REGISTERED(d) \ + WARN_ON_ONCE(xa_get_mark(&dpll_device_xa, (d)->id, DPLL_REGISTERED)) + +struct pin_ref_dpll { + struct dpll_device *dpll; + struct dpll_pin_ops *ops; + void *priv; +}; + +struct dpll_device *dpll_device_get_by_id(int id) +{ + struct dpll_device *dpll = NULL; + + if (xa_get_mark(&dpll_device_xa, id, DPLL_REGISTERED)) + dpll = xa_load(&dpll_device_xa, id); + + return dpll; +} + +struct dpll_device *dpll_device_get_by_name(const char *name) +{ + struct dpll_device *dpll, *ret = NULL; + unsigned long index; + + mutex_lock(&dpll_device_xa_lock); + xa_for_each_marked(&dpll_device_xa, index, dpll, DPLL_REGISTERED) { + if (!strcmp(dev_name(&dpll->dev), name)) { + ret = dpll; + break; + } + } + mutex_unlock(&dpll_device_xa_lock); + + return ret; +} + +struct dpll_device *dpll_device_get_by_cookie(u8 cookie[DPLL_COOKIE_LEN], + enum dpll_type type, u8 idx) +{ + struct dpll_device *dpll, *ret = NULL; + unsigned long index; + + mutex_lock(&dpll_device_xa_lock); + xa_for_each_marked(&dpll_device_xa, index, dpll, DPLL_REGISTERED) { + if (!memcmp(dpll->cookie, cookie, DPLL_COOKIE_LEN)) { + if (dpll->type == type) { + if (dpll->dev_driver_idx == idx) { + ret = dpll; + break; + } + } + } + } + mutex_unlock(&dpll_device_xa_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_device_get_by_cookie); + +static void dpll_device_release(struct device *dev) +{ + struct dpll_device *dpll; + + dpll = to_dpll_device(dev); + + dpll_device_unregister(dpll); + dpll_device_free(dpll); +} + +static struct class dpll_class = { + .name = "dpll", + .dev_release = dpll_device_release, +}; + +static const char *dpll_type_name[__DPLL_TYPE_MAX] = { + [DPLL_TYPE_UNSPEC] = "", + [DPLL_TYPE_PPS] = "PPS", + [DPLL_TYPE_EEC] = "EEC", +}; + +static const char *dpll_type_str(enum dpll_type type) +{ + if (type >= DPLL_TYPE_UNSPEC && type <= DPLL_TYPE_MAX) + return dpll_type_name[type]; + else + return ""; +} + +struct dpll_device +*dpll_device_alloc(struct dpll_device_ops *ops, enum dpll_type type, + const u8 cookie[DPLL_COOKIE_LEN], u8 idx, + void *priv, struct device *parent) +{ + struct dpll_device *dpll; + int ret; + + dpll = kzalloc(sizeof(*dpll), GFP_KERNEL); + if (!dpll) + return ERR_PTR(-ENOMEM); + + mutex_init(&dpll->lock); + dpll->ops = ops; + dpll->dev.class = &dpll_class; + dpll->parent = parent; + dpll->type = type; + dpll->dev_driver_idx = idx; + memcpy(dpll->cookie, cookie, sizeof(dpll->cookie)); + + mutex_lock(&dpll_device_xa_lock); + ret = xa_alloc(&dpll_device_xa, &dpll->id, dpll, + xa_limit_16b, GFP_KERNEL); + if (ret) + goto error; + dev_set_name(&dpll->dev, "dpll-%s-%s-%s%d", dev_driver_string(parent), + dev_name(parent), type ? dpll_type_str(type) : "", idx); + dpll->priv = priv; + xa_init_flags(&dpll->pins, XA_FLAGS_ALLOC); + mutex_unlock(&dpll_device_xa_lock); + dpll_notify_device_create(dpll); + + return dpll; + +error: + mutex_unlock(&dpll_device_xa_lock); + kfree(dpll); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(dpll_device_alloc); + +void dpll_device_free(struct dpll_device *dpll) +{ + WARN_ON_ONCE(!dpll); + WARN_ON_ONCE(!xa_empty(&dpll->pins)); + xa_destroy(&dpll->pins); + mutex_destroy(&dpll->lock); + kfree(dpll); +} +EXPORT_SYMBOL_GPL(dpll_device_free); + +void dpll_device_register(struct dpll_device *dpll) +{ + ASSERT_DPLL_NOT_REGISTERED(dpll); + + mutex_lock(&dpll_device_xa_lock); + xa_set_mark(&dpll_device_xa, dpll->id, DPLL_REGISTERED); + mutex_unlock(&dpll_device_xa_lock); +} +EXPORT_SYMBOL_GPL(dpll_device_register); + +void dpll_device_unregister(struct dpll_device *dpll) +{ + ASSERT_DPLL_REGISTERED(dpll); + + mutex_lock(&dpll_device_xa_lock); + xa_erase(&dpll_device_xa, dpll->id); + dpll_notify_device_delete(dpll); + mutex_unlock(&dpll_device_xa_lock); +} +EXPORT_SYMBOL_GPL(dpll_device_unregister); + +u32 dpll_id(struct dpll_device *dpll) +{ + return dpll->id; +} +EXPORT_SYMBOL_GPL(dpll_id); + +u32 dpll_pin_idx(struct dpll_device *dpll, struct dpll_pin *pin) +{ + struct dpll_pin *pos; + unsigned long index; + + xa_for_each_marked(&dpll->pins, index, pos, PIN_REGISTERED) { + if (pos == pin) + return pin->idx; + } + + return PIN_IDX_INVALID; +} +EXPORT_SYMBOL_GPL(dpll_pin_idx); + +const char *dpll_dev_name(struct dpll_device *dpll) +{ + return dev_name(&dpll->dev); +} +EXPORT_SYMBOL_GPL(dpll_dev_name); + +struct dpll_pin *dpll_pin_alloc(const char *description, size_t desc_len) +{ + struct dpll_pin *pin = kzalloc(sizeof(struct dpll_pin), GFP_KERNEL); + + if (!pin) + return ERR_PTR(-ENOMEM); + if (desc_len > PIN_DESC_LEN) + return ERR_PTR(-EINVAL); + + strncpy(pin->description, description, PIN_DESC_LEN); + if (desc_len == PIN_DESC_LEN) + pin->description[PIN_DESC_LEN - 1] = '\0'; + xa_init_flags(&pin->ref_dplls, XA_FLAGS_ALLOC); + + return pin; +} +EXPORT_SYMBOL_GPL(dpll_pin_alloc); + +static int dpll_alloc_pin_on_xa(struct xarray *pins, struct dpll_pin *pin) +{ + struct dpll_pin *pos; + unsigned long index; + int ret; + + xa_for_each(pins, index, pos) { + if (pos == pin || + !strncmp(pos->description, pin->description, PIN_DESC_LEN)) + return -EEXIST; + } + + ret = xa_alloc(pins, &pin->idx, pin, xa_limit_16b, GFP_KERNEL); + if (!ret) + xa_set_mark(pins, pin->idx, PIN_REGISTERED); + + return ret; +} + +static int pin_ref_dpll_add(struct dpll_pin *pin, struct dpll_device *dpll, + struct dpll_pin_ops *ops, void *priv) +{ + struct pin_ref_dpll *ref, *pos; + unsigned long index; + u32 idx; + + ref = kzalloc(sizeof(struct pin_ref_dpll), GFP_KERNEL); + if (!ref) + return -ENOMEM; + ref->dpll = dpll; + ref->ops = ops; + ref->priv = priv; + if (!xa_empty(&pin->ref_dplls)) { + xa_for_each(&pin->ref_dplls, index, pos) { + if (pos->dpll == ref->dpll) + return -EEXIST; + } + } + + return xa_alloc(&pin->ref_dplls, &idx, ref, xa_limit_16b, GFP_KERNEL); +} + +static void pin_ref_dpll_del(struct dpll_pin *pin, struct dpll_device *dpll) +{ + struct pin_ref_dpll *pos; + unsigned long index; + + xa_for_each(&pin->ref_dplls, index, pos) { + if (pos->dpll == dpll) { + WARN_ON_ONCE(pos != xa_erase(&pin->ref_dplls, index)); + break; + } + } +} + +static int pin_deregister_from_xa(struct xarray *xa_pins, struct dpll_pin *pin) +{ + struct dpll_pin *pos; + unsigned long index; + + xa_for_each(xa_pins, index, pos) { + if (pos == pin) { + WARN_ON_ONCE(pos != xa_erase(xa_pins, index)); + return 0; + } + } + + return -ENXIO; +} + +int dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv) +{ + int ret; + + if (!pin || !ops) + return -EINVAL; + + mutex_lock(&dpll->lock); + ret = dpll_alloc_pin_on_xa(&dpll->pins, pin); + if (!ret) { + ret = pin_ref_dpll_add(pin, dpll, ops, priv); + if (ret) + pin_deregister_from_xa(&dpll->pins, pin); + } + mutex_unlock(&dpll->lock); + if (!ret) + dpll_notify_device_change(dpll, DPLL_CHANGE_PIN_ADD, pin); + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_pin_register); + +int +dpll_shared_pin_register(struct dpll_device *dpll_pin_owner, + struct dpll_device *dpll, u32 pin_idx, + struct dpll_pin_ops *ops, void *priv) +{ + struct dpll_pin *pin; + int ret; + + mutex_lock(&dpll_pin_owner->lock); + pin = dpll_pin_get_by_idx(dpll_pin_owner, pin_idx); + if (!pin) { + ret = -EINVAL; + goto unlock; + } + ret = dpll_pin_register(dpll, pin, ops, priv); +unlock: + mutex_unlock(&dpll_pin_owner->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_shared_pin_register); + +int dpll_pin_deregister(struct dpll_device *dpll, struct dpll_pin *pin) +{ + int ret = 0; + + if (xa_empty(&dpll->pins)) + return -ENOENT; + + mutex_lock(&dpll->lock); + ret = pin_deregister_from_xa(&dpll->pins, pin); + if (!ret) + pin_ref_dpll_del(pin, dpll); + mutex_unlock(&dpll->lock); + if (!ret) + dpll_notify_device_change(dpll, DPLL_CHANGE_PIN_DEL, pin); + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_pin_deregister); + +void dpll_pin_free(struct dpll_pin *pin) +{ + if (!xa_empty(&pin->ref_dplls)) + return; + + xa_destroy(&pin->ref_dplls); + kfree(pin); +} +EXPORT_SYMBOL_GPL(dpll_pin_free); + +int dpll_muxed_pin_register(struct dpll_device *dpll, + struct dpll_pin *parent_pin, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv) +{ + int ret; + + if (!parent_pin || !pin) + return -EINVAL; + + mutex_lock(&dpll->lock); + ret = dpll_alloc_pin_on_xa(&dpll->pins, pin); + if (!ret) + ret = pin_ref_dpll_add(pin, dpll, ops, priv); + if (!ret) + pin->parent_pin = parent_pin; + mutex_unlock(&dpll->lock); + if (!ret) + dpll_notify_device_change(dpll, DPLL_CHANGE_PIN_ADD, pin); + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_muxed_pin_register); + +struct dpll_pin +*dpll_pin_get_by_description(struct dpll_device *dpll, const char *description) +{ + struct dpll_pin *pos, *pin = NULL; + unsigned long index; + + mutex_lock(&dpll->lock); + xa_for_each(&dpll->pins, index, pos) { + if (!strncmp(pos->description, description, PIN_DESC_LEN)) { + pin = pos; + break; + } + } + mutex_unlock(&dpll->lock); + + return pin; +} +EXPORT_SYMBOL_GPL(dpll_pin_get_by_description); + +static struct dpll_pin +*dpll_pin_get_by_idx_from_xa(struct xarray *xa_pins, int idx) +{ + struct dpll_pin *pos; + unsigned long index; + + xa_for_each_marked(xa_pins, index, pos, PIN_REGISTERED) { + if (pos->idx == idx) + return pos; + } + + return NULL; +} + +struct dpll_pin *dpll_pin_get_by_idx(struct dpll_device *dpll, int idx) +{ + return dpll_pin_get_by_idx_from_xa(&dpll->pins, idx); +} +EXPORT_SYMBOL_GPL(dpll_pin_get_by_idx); + +struct dpll_pin *dpll_pin_first(struct dpll_device *dpll, unsigned long *index) +{ + *index = 0; + + return xa_find(&dpll->pins, index, LONG_MAX, PIN_REGISTERED); +} + +struct dpll_pin *dpll_pin_next(struct dpll_device *dpll, unsigned long *index) +{ + return xa_find_after(&dpll->pins, index, LONG_MAX, PIN_REGISTERED); +} + +struct dpll_device *dpll_first(unsigned long *index) +{ + *index = 0; + + return xa_find(&dpll_device_xa, index, LONG_MAX, DPLL_REGISTERED); +} + +struct dpll_device *dpll_next(unsigned long *index) +{ + return xa_find_after(&dpll_device_xa, index, LONG_MAX, DPLL_REGISTERED); +} + +static int +dpll_notify_pin_change_attr(struct dpll_device *dpll, struct dpll_pin *pin, + const struct dpll_pin_attr *attr) +{ + enum dpll_event_change change; + int ret = 0; + + if (dpll_pin_attr_valid(DPLLA_PIN_TYPE, attr)) { + change = DPLL_CHANGE_PIN_TYPE; + ret = dpll_notify_device_change(dpll, change, pin); + } + if (!ret && dpll_pin_attr_valid(DPLLA_PIN_SIGNAL_TYPE, attr)) { + change = DPLL_CHANGE_PIN_SIGNAL_TYPE; + ret = dpll_notify_device_change(dpll, change, pin); + } + if (!ret && dpll_pin_attr_valid(DPLLA_PIN_CUSTOM_FREQ, attr)) { + change = DPLL_CHANGE_PIN_CUSTOM_FREQ; + ret = dpll_notify_device_change(dpll, change, pin); + } + if (!ret && dpll_pin_attr_valid(DPLLA_PIN_STATE, attr)) { + change = DPLL_CHANGE_PIN_STATE; + ret = dpll_notify_device_change(dpll, change, pin); + } + if (!ret && dpll_pin_attr_valid(DPLLA_PIN_PRIO, attr)) { + change = DPLL_CHANGE_PIN_PRIO; + ret = dpll_notify_device_change(dpll, change, pin); + } + + return ret; +} + +static int dpll_notify_device_change_attr(struct dpll_device *dpll, + const struct dpll_attr *attr) +{ + int ret = 0; + + if (dpll_attr_valid(DPLLA_MODE, attr)) + ret = dpll_notify_device_change(dpll, DPLL_CHANGE_MODE, NULL); + if (!ret && dpll_attr_valid(DPLLA_SOURCE_PIN_IDX, attr)) + ret = dpll_notify_device_change(dpll, DPLL_CHANGE_SOURCE_PIN, + NULL); + return ret; +} + +static struct pin_ref_dpll +*dpll_pin_find_ref(struct dpll_device *dpll, struct dpll_pin *pin) +{ + struct pin_ref_dpll *ref; + unsigned long index; + + xa_for_each(&pin->ref_dplls, index, ref) { + if (ref->dpll != dpll) + continue; + else + return ref; + } + + return NULL; +} + +static int +dpll_pin_set_attr_single_ref(struct dpll_device *dpll, struct dpll_pin *pin, + const struct dpll_pin_attr *attr) +{ + struct pin_ref_dpll *ref = dpll_pin_find_ref(dpll, pin); + int ret; + + mutex_lock(&ref->dpll->lock); + ret = ref->ops->set(ref->dpll, pin, attr); + if (!ret) + dpll_notify_pin_change_attr(dpll, pin, attr); + mutex_unlock(&ref->dpll->lock); + + return ret; +} + +static int +dpll_pin_set_attr_all_refs(struct dpll_pin *pin, + const struct dpll_pin_attr *attr) +{ + struct pin_ref_dpll *ref; + unsigned long index; + int ret; + + xa_for_each(&pin->ref_dplls, index, ref) { + if (!ref->dpll) + return -EFAULT; + if (!ref || !ref->ops || !ref->ops->set) + return -EOPNOTSUPP; + mutex_lock(&ref->dpll->lock); + ret = ref->ops->set(ref->dpll, pin, attr); + mutex_unlock(&ref->dpll->lock); + if (!ret) + dpll_notify_pin_change_attr(ref->dpll, pin, attr); + } + + return ret; +} + +int dpll_pin_set_attr(struct dpll_device *dpll, struct dpll_pin *pin, + const struct dpll_pin_attr *attr) +{ + struct dpll_pin_attr *tmp_attr; + int ret; + + tmp_attr = dpll_pin_attr_alloc(); + if (!tmp_attr) + return -ENOMEM; + ret = dpll_pin_attr_prep_common(tmp_attr, attr); + if (ret < 0) + goto tmp_free; + if (ret == PIN_ATTR_CHANGE) { + ret = dpll_pin_set_attr_all_refs(pin, tmp_attr); + if (ret) + goto tmp_free; + } + + ret = dpll_pin_attr_prep_exclusive(tmp_attr, attr); + if (ret < 0) + goto tmp_free; + if (ret == PIN_ATTR_CHANGE) + ret = dpll_pin_set_attr_single_ref(dpll, pin, tmp_attr); + +tmp_free: + dpll_pin_attr_free(tmp_attr); + return ret; +} + +int dpll_pin_get_attr(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin_attr *attr) +{ + struct pin_ref_dpll *ref = dpll_pin_find_ref(dpll, pin); + int ret; + + if (!ref) + return -ENODEV; + if (!ref->ops || !ref->ops->get) + return -EOPNOTSUPP; + + ret = ref->ops->get(dpll, pin, attr); + if (ret) + return -EAGAIN; + + return ret; +} + +const char *dpll_pin_get_description(struct dpll_pin *pin) +{ + return pin->description; +} + +struct dpll_pin *dpll_pin_get_parent(struct dpll_pin *pin) +{ + return pin->parent_pin; +} + +int dpll_set_attr(struct dpll_device *dpll, const struct dpll_attr *attr) +{ + int ret; + + if (dpll_attr_valid(DPLLA_SOURCE_PIN_IDX, attr)) { + struct pin_ref_dpll *ref; + struct dpll_pin *pin; + u32 source_idx; + + ret = dpll_attr_source_idx_get(attr, &source_idx); + if (ret) + return -EINVAL; + pin = dpll_pin_get_by_idx(dpll, source_idx); + if (!pin) + return -ENXIO; + ref = dpll_pin_find_ref(dpll, pin); + if (!ref || !ref->ops) + return -EFAULT; + if (!ref->ops->select) + return -ENODEV; + dpll_lock(ref->dpll); + ret = ref->ops->select(ref->dpll, pin); + dpll_unlock(ref->dpll); + if (ret) + return -EINVAL; + dpll_notify_device_change_attr(dpll, attr); + } + + if (dpll_attr_valid(DPLLA_MODE, attr)) { + dpll_lock(dpll); + ret = dpll->ops->set(dpll, attr); + dpll_unlock(dpll); + if (ret) + return -EINVAL; + } + dpll_notify_device_change_attr(dpll, attr); + + return ret; +} + +int dpll_get_attr(struct dpll_device *dpll, struct dpll_attr *attr) +{ + if (!dpll) + return -ENODEV; + if (!dpll->ops || !dpll->ops->get) + return -EOPNOTSUPP; + if (dpll->ops->get(dpll, attr)) + return -EAGAIN; + + return 0; +} + +void dpll_lock(struct dpll_device *dpll) +{ + mutex_lock(&dpll->lock); +} + +void dpll_unlock(struct dpll_device *dpll) +{ + mutex_unlock(&dpll->lock); +} + +void *dpll_priv(struct dpll_device *dpll) +{ + return dpll->priv; +} +EXPORT_SYMBOL_GPL(dpll_priv); + +void *dpll_pin_priv(struct dpll_device *dpll, struct dpll_pin *pin) +{ + struct pin_ref_dpll *ref = dpll_pin_find_ref(dpll, pin); + + if (!ref) + return NULL; + + return ref->priv; +} +EXPORT_SYMBOL_GPL(dpll_pin_priv); + +static int __init dpll_init(void) +{ + int ret; + + ret = dpll_netlink_init(); + if (ret) + goto error; + + ret = class_register(&dpll_class); + if (ret) + goto unregister_netlink; + + return 0; + +unregister_netlink: + dpll_netlink_finish(); +error: + mutex_destroy(&dpll_device_xa_lock); + return ret; +} +subsys_initcall(dpll_init); diff --git a/drivers/dpll/dpll_core.h b/drivers/dpll/dpll_core.h new file mode 100644 index 000000000000..9cefecdfc47b --- /dev/null +++ b/drivers/dpll/dpll_core.h @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates + */ + +#ifndef __DPLL_CORE_H__ +#define __DPLL_CORE_H__ + +#include + +#include "dpll_netlink.h" + +#define to_dpll_device(_dev) \ + container_of(_dev, struct dpll_device, dev) + +#define for_each_pin_on_dpll(dpll, pin, i) \ + for (pin = dpll_pin_first(dpll, &i); pin != NULL; \ + pin = dpll_pin_next(dpll, &i)) + +#define for_each_dpll(dpll, i) \ + for (dpll = dpll_first(&i); dpll != NULL; \ + dpll = dpll_next(&i)) + + +/** + * dpll_device_get_by_id - find dpll device by it's id + * @id: dpll id + * + * Return: dpll_device struct if found, NULL otherwise. + */ +struct dpll_device *dpll_device_get_by_id(int id); + +/** + * dpll_device_get_by_name - find dpll device by it's id + * @name: dpll name + * + * Return: dpll_device struct if found, NULL otherwise. + */ +struct dpll_device *dpll_device_get_by_name(const char *name); + +/** + * dpll_pin_first - get first registered pin + * @dpll: registered dpll pointer + * @index: found pin index (out) + * + * Return: dpll_pin struct if found, NULL otherwise. + */ +struct dpll_pin *dpll_pin_first(struct dpll_device *dpll, unsigned long *index); + +/** + * dpll_pin_next - get next registered pin to the relative pin + * @dpll: registered dpll pointer + * @index: relative pin index (in and out) + * + * Return: dpll_pin struct if found, NULL otherwise. + */ +struct dpll_pin *dpll_pin_next(struct dpll_device *dpll, unsigned long *index); + +/** + * dpll_first - get first registered dpll device + * @index: found dpll index (out) + * + * Return: dpll_device struct if found, NULL otherwise. + */ +struct dpll_device *dpll_first(unsigned long *index); + +/** + * dpll_pin_next - get next registered dpll device to the relative pin + * @index: relative dpll index (in and out) + * + * Return: dpll_pin struct if found, NULL otherwise. + */ +struct dpll_device *dpll_next(unsigned long *index); + +/** + * dpll_device_unregister - unregister dpll device + * @dpll: registered dpll pointer + * + * Note: It does not free the memory + */ +void dpll_device_unregister(struct dpll_device *dpll); + +/** + * dpll_id - return dpll id + * @dpll: registered dpll pointer + * + * Return: dpll id. + */ +u32 dpll_id(struct dpll_device *dpll); + +/** + * dpll_pin_idx - return dpll name + * @dpll: registered dpll pointer + * + * Return: dpll name. + */ +const char *dpll_dev_name(struct dpll_device *dpll); + +/** + * dpll_lock - locks the dpll using internal mutex + * @dpll: registered dpll pointer + */ +void dpll_lock(struct dpll_device *dpll); + +/** + * dpll_unlock - unlocks the dpll using internal mutex + * @dpll: registered dpll pointer + */ +void dpll_unlock(struct dpll_device *dpll); + +/** + * dpll_set_attr - handler for dpll subsystem: dpll set attributes + * @dpll: registered dpll pointer + * @attr: dpll attributes + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_set_attr(struct dpll_device *dpll, const struct dpll_attr *attr); + +/** + * dpll_get_attr - handler for dpll subsystem: dpll get attributes + * @dpll: registered dpll pointer + * @attr: dpll attributes + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_get_attr(struct dpll_device *dpll, struct dpll_attr *attr); + +/** + * dpll_pin_idx - return dpll id + * @dpll: registered dpll pointer + * @pin: registered pin pointer + * + * Return: dpll id. + */ +u32 dpll_pin_idx(struct dpll_device *dpll, struct dpll_pin *pin); + +/** + * dpll_pin_get_attr - handler for dpll subsystem: dpll pin get attributes + * @dpll: registered dpll pointer + * @pin: registered pin pointer + * @attr: dpll pin attributes + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_get_attr(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin_attr *attr); + +/** + * dpll_pin_get_description - provide pin's description string + * @pin: registered pin pointer + * + * Return: pointer to a description string. + */ +const char *dpll_pin_get_description(struct dpll_pin *pin); + +/** + * dpll_pin_get_parent - provide pin's parent pin if available + * @pin: registered pin pointer + * + * Return: pointer to aparent if found, NULL otherwise. + */ +struct dpll_pin *dpll_pin_get_parent(struct dpll_pin *pin); + +/** + * dpll_pin_set_attr - handler for dpll subsystem: dpll pin get attributes + * @dpll: registered dpll pointer + * @pin: registered pin pointer + * @attr: dpll pin attributes + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_pin_set_attr(struct dpll_device *dpll, struct dpll_pin *pin, + const struct dpll_pin_attr *attr); + +#endif diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c new file mode 100644 index 000000000000..9a1f682a42ac --- /dev/null +++ b/drivers/dpll/dpll_netlink.c @@ -0,0 +1,963 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generic netlink for DPLL management framework + * + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates + * + */ +#include +#include +#include +#include "dpll_core.h" + +#include + +static const struct genl_multicast_group dpll_mcgrps[] = { + { .name = DPLL_MONITOR_GROUP_NAME, }, +}; + +static const struct nla_policy dpll_cmd_device_get_policy[] = { + [DPLLA_ID] = { .type = NLA_U32 }, + [DPLLA_NAME] = { .type = NLA_STRING, + .len = DPLL_NAME_LEN }, + [DPLLA_DUMP_FILTER] = { .type = NLA_U32 }, + [DPLLA_NETIFINDEX] = { .type = NLA_U32 }, +}; + +static const struct nla_policy dpll_cmd_device_set_policy[] = { + [DPLLA_ID] = { .type = NLA_U32 }, + [DPLLA_NAME] = { .type = NLA_STRING, + .len = DPLL_NAME_LEN }, + [DPLLA_MODE] = { .type = NLA_U32 }, + [DPLLA_SOURCE_PIN_IDX] = { .type = NLA_U32 }, +}; + +static const struct nla_policy dpll_cmd_pin_set_policy[] = { + [DPLLA_ID] = { .type = NLA_U32 }, + [DPLLA_PIN_IDX] = { .type = NLA_U32 }, + [DPLLA_PIN_TYPE] = { .type = NLA_U32 }, + [DPLLA_PIN_SIGNAL_TYPE] = { .type = NLA_U32 }, + [DPLLA_PIN_CUSTOM_FREQ] = { .type = NLA_U32 }, + [DPLLA_PIN_STATE] = { .type = NLA_U32 }, + [DPLLA_PIN_PRIO] = { .type = NLA_U32 }, +}; + +struct dpll_param { + struct netlink_callback *cb; + struct sk_buff *msg; + struct dpll_device *dpll; + struct dpll_pin *pin; + enum dpll_event_change change_type; +}; + +struct dpll_dump_ctx { + int dump_filter; +}; + +typedef int (*cb_t)(struct dpll_param *); + +static struct genl_family dpll_gnl_family; + +static struct dpll_dump_ctx *dpll_dump_context(struct netlink_callback *cb) +{ + return (struct dpll_dump_ctx *)cb->ctx; +} + +static int dpll_msg_add_id(struct sk_buff *msg, u32 id) +{ + if (nla_put_u32(msg, DPLLA_ID, id)) + return -EMSGSIZE; + + return 0; +} + +static int dpll_msg_add_name(struct sk_buff *msg, const char *name) +{ + if (nla_put_string(msg, DPLLA_NAME, name)) + return -EMSGSIZE; + + return 0; +} + +static int __dpll_msg_add_mode(struct sk_buff *msg, enum dplla msg_type, + enum dpll_mode mode) +{ + if (nla_put_s32(msg, msg_type, mode)) + return -EMSGSIZE; + + return 0; +} + +static int dpll_msg_add_mode(struct sk_buff *msg, const struct dpll_attr *attr) +{ + enum dpll_mode m = dpll_attr_mode_get(attr); + + if (m == DPLL_MODE_UNSPEC) + return 0; + + return __dpll_msg_add_mode(msg, DPLLA_MODE, m); +} + +static int dpll_msg_add_modes_supported(struct sk_buff *msg, + const struct dpll_attr *attr) +{ + enum dpll_mode i; + int ret = 0; + + for (i = DPLL_MODE_UNSPEC + 1; i <= DPLL_MODE_MAX; i++) { + if (dpll_attr_mode_supported(attr, i)) { + ret = __dpll_msg_add_mode(msg, DPLLA_MODE_SUPPORTED, i); + if (ret) + return -EMSGSIZE; + } + } + + return ret; +} + +static int dpll_msg_add_source_pin(struct sk_buff *msg, struct dpll_attr *attr) +{ + u32 source_idx; + + if (dpll_attr_source_idx_get(attr, &source_idx)) + return 0; + if (nla_put_u32(msg, DPLLA_SOURCE_PIN_IDX, source_idx)) + return -EMSGSIZE; + + return 0; +} + +static int dpll_msg_add_netifindex(struct sk_buff *msg, struct dpll_attr *attr) +{ + unsigned int netifindex; // TODO: Should be u32? + + if (dpll_attr_netifindex_get(attr, &netifindex)) + return 0; + if (nla_put_u32(msg, DPLLA_NETIFINDEX, netifindex)) + return -EMSGSIZE; + + return 0; +} + +static int dpll_msg_add_lock_status(struct sk_buff *msg, struct dpll_attr *attr) +{ + enum dpll_lock_status s = dpll_attr_lock_status_get(attr); + + if (s == DPLL_LOCK_STATUS_UNSPEC) + return 0; + if (nla_put_s32(msg, DPLLA_LOCK_STATUS, s)) + return -EMSGSIZE; + + return 0; +} + +static int dpll_msg_add_temp(struct sk_buff *msg, struct dpll_attr *attr) +{ + s32 temp; + + if (dpll_attr_temp_get(attr, &temp)) + return 0; + if (nla_put_u32(msg, DPLLA_TEMP, temp)) + return -EMSGSIZE; + + return 0; +} + +static int dpll_msg_add_pin_idx(struct sk_buff *msg, u32 pin_idx) +{ + if (nla_put_u32(msg, DPLLA_PIN_IDX, pin_idx)) + return -EMSGSIZE; + + return 0; +} + +static int dpll_msg_add_pin_description(struct sk_buff *msg, + const char *description) +{ + if (nla_put_string(msg, DPLLA_PIN_DESCRIPTION, description)) + return -EMSGSIZE; + + return 0; +} + +static int dpll_msg_add_pin_parent_idx(struct sk_buff *msg, u32 parent_idx) +{ + if (nla_put_u32(msg, DPLLA_PIN_PARENT_IDX, parent_idx)) + return -EMSGSIZE; + + return 0; +} + +static int __dpll_msg_add_pin_type(struct sk_buff *msg, enum dplla attr, + enum dpll_pin_type type) +{ + if (nla_put_s32(msg, attr, type)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_pin_type(struct sk_buff *msg, const struct dpll_pin_attr *attr) +{ + enum dpll_pin_type t = dpll_pin_attr_type_get(attr); + + if (t == DPLL_PIN_TYPE_UNSPEC) + return 0; + + return __dpll_msg_add_pin_type(msg, DPLLA_PIN_TYPE, t); +} + +static int dpll_msg_add_pin_types_supported(struct sk_buff *msg, + const struct dpll_pin_attr *attr) +{ + enum dpll_pin_type i; + int ret; + + for (i = DPLL_PIN_TYPE_UNSPEC + 1; i <= DPLL_PIN_TYPE_MAX; i++) { + if (dpll_pin_attr_type_supported(attr, i)) { + ret = __dpll_msg_add_pin_type(msg, + DPLLA_PIN_TYPE_SUPPORTED, + i); + if (ret) + return ret; + } + } + + return 0; +} + +static int __dpll_msg_add_pin_signal_type(struct sk_buff *msg, + enum dplla attr, + enum dpll_pin_signal_type type) +{ + if (nla_put_s32(msg, attr, type)) + return -EMSGSIZE; + + return 0; +} + +static int dpll_msg_add_pin_signal_type(struct sk_buff *msg, + const struct dpll_pin_attr *attr) +{ + enum dpll_pin_signal_type t = dpll_pin_attr_signal_type_get(attr); + + if (t == DPLL_PIN_SIGNAL_TYPE_UNSPEC) + return 0; + + return __dpll_msg_add_pin_signal_type(msg, DPLLA_PIN_SIGNAL_TYPE, t); +} + +static int +dpll_msg_add_pin_signal_types_supported(struct sk_buff *msg, + const struct dpll_pin_attr *attr) +{ + const enum dplla da = DPLLA_PIN_SIGNAL_TYPE_SUPPORTED; + enum dpll_pin_signal_type i; + int ret; + + for (i = DPLL_PIN_SIGNAL_TYPE_UNSPEC + 1; + i <= DPLL_PIN_SIGNAL_TYPE_MAX; i++) { + if (dpll_pin_attr_signal_type_supported(attr, i)) { + ret = __dpll_msg_add_pin_signal_type(msg, da, i); + if (ret) + return ret; + } + } + + return 0; +} + +static int dpll_msg_add_pin_custom_freq(struct sk_buff *msg, + const struct dpll_pin_attr *attr) +{ + u32 freq; + + if (dpll_pin_attr_custom_freq_get(attr, &freq)) + return 0; + if (nla_put_u32(msg, DPLLA_PIN_CUSTOM_FREQ, freq)) + return -EMSGSIZE; + + return 0; +} + +static int dpll_msg_add_pin_states(struct sk_buff *msg, + const struct dpll_pin_attr *attr) +{ + enum dpll_pin_state i; + + for (i = DPLL_PIN_STATE_UNSPEC + 1; i <= DPLL_PIN_STATE_MAX; i++) + if (dpll_pin_attr_state_enabled(attr, i)) + if (nla_put_s32(msg, DPLLA_PIN_STATE, i)) + return -EMSGSIZE; + + return 0; +} + +static int dpll_msg_add_pin_states_supported(struct sk_buff *msg, + const struct dpll_pin_attr *attr) +{ + enum dpll_pin_state i; + + for (i = DPLL_PIN_STATE_UNSPEC + 1; i <= DPLL_PIN_STATE_MAX; i++) + if (dpll_pin_attr_state_supported(attr, i)) + if (nla_put_s32(msg, DPLLA_PIN_STATE_SUPPORTED, i)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_pin_prio(struct sk_buff *msg, const struct dpll_pin_attr *attr) +{ + u32 prio; + + if (dpll_pin_attr_prio_get(attr, &prio)) + return 0; + if (nla_put_u32(msg, DPLLA_PIN_PRIO, prio)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_pin_netifindex(struct sk_buff *msg, const struct dpll_pin_attr *attr) +{ + unsigned int netifindex; // TODO: Should be u32? + + if (dpll_pin_attr_netifindex_get(attr, &netifindex)) + return 0; + if (nla_put_u32(msg, DPLLA_PIN_NETIFINDEX, netifindex)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_event_change_type(struct sk_buff *msg, + enum dpll_event_change event) +{ + if (nla_put_s32(msg, DPLLA_CHANGE_TYPE, event)) + return -EMSGSIZE; + + return 0; +} + +static int +__dpll_cmd_device_dump_one(struct sk_buff *msg, struct dpll_device *dpll) +{ + int ret = dpll_msg_add_id(msg, dpll_id(dpll)); + + if (ret) + return ret; + ret = dpll_msg_add_name(msg, dpll_dev_name(dpll)); + + return ret; +} + +static int +__dpll_cmd_pin_dump_one(struct sk_buff *msg, struct dpll_device *dpll, + struct dpll_pin *pin) +{ + struct dpll_pin_attr *attr = dpll_pin_attr_alloc(); + struct dpll_pin *parent = NULL; + int ret; + + if (!attr) + return -ENOMEM; + ret = dpll_msg_add_pin_idx(msg, dpll_pin_idx(dpll, pin)); + if (ret) + goto out; + ret = dpll_msg_add_pin_description(msg, dpll_pin_get_description(pin)); + if (ret) + goto out; + parent = dpll_pin_get_parent(pin); + if (parent) { + ret = dpll_msg_add_pin_parent_idx(msg, dpll_pin_idx(dpll, + parent)); + if (ret) + goto out; + } + ret = dpll_pin_get_attr(dpll, pin, attr); + if (ret) + goto out; + ret = dpll_msg_add_pin_type(msg, attr); + if (ret) + goto out; + ret = dpll_msg_add_pin_types_supported(msg, attr); + if (ret) + goto out; + ret = dpll_msg_add_pin_signal_type(msg, attr); + if (ret) + goto out; + ret = dpll_msg_add_pin_signal_types_supported(msg, attr); + if (ret) + goto out; + ret = dpll_msg_add_pin_custom_freq(msg, attr); + if (ret) + goto out; + ret = dpll_msg_add_pin_states(msg, attr); + if (ret) + goto out; + ret = dpll_msg_add_pin_states_supported(msg, attr); + if (ret) + goto out; + ret = dpll_msg_add_pin_prio(msg, attr); + if (ret) + goto out; + ret = dpll_msg_add_pin_netifindex(msg, attr); + if (ret) + goto out; +out: + dpll_pin_attr_free(attr); + + return ret; +} + +static int __dpll_cmd_dump_pins(struct sk_buff *msg, struct dpll_device *dpll) +{ + struct dpll_pin *pin; + struct nlattr *attr; + unsigned long i; + int ret = 0; + + for_each_pin_on_dpll(dpll, pin, i) { + attr = nla_nest_start(msg, DPLLA_PIN); + if (!attr) { + ret = -EMSGSIZE; + goto nest_cancel; + } + ret = __dpll_cmd_pin_dump_one(msg, dpll, pin); + if (ret) + goto nest_cancel; + nla_nest_end(msg, attr); + } + + return ret; + +nest_cancel: + nla_nest_cancel(msg, attr); + return ret; +} + +static int +__dpll_cmd_dump_status(struct sk_buff *msg, struct dpll_device *dpll) +{ + struct dpll_attr *attr = dpll_attr_alloc(); + int ret = dpll_get_attr(dpll, attr); + + if (ret) + return -EAGAIN; + if (dpll_msg_add_source_pin(msg, attr)) + return -EMSGSIZE; + if (dpll_msg_add_temp(msg, attr)) + return -EMSGSIZE; + if (dpll_msg_add_lock_status(msg, attr)) + return -EMSGSIZE; + if (dpll_msg_add_mode(msg, attr)) + return -EMSGSIZE; + if (dpll_msg_add_modes_supported(msg, attr)) + return -EMSGSIZE; + if (dpll_msg_add_netifindex(msg, attr)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_device_dump_one(struct dpll_device *dpll, struct sk_buff *msg, + int dump_filter) +{ + int ret; + + dpll_lock(dpll); + ret = __dpll_cmd_device_dump_one(msg, dpll); + if (ret) + goto out_unlock; + + if (dump_filter & DPLL_DUMP_FILTER_STATUS) { + ret = __dpll_cmd_dump_status(msg, dpll); + if (ret) + goto out_unlock; + } + if (dump_filter & DPLL_DUMP_FILTER_PINS) + ret = __dpll_cmd_dump_pins(msg, dpll); + dpll_unlock(dpll); + + return ret; +out_unlock: + dpll_unlock(dpll); + return ret; +} + +static enum dpll_pin_type dpll_msg_read_pin_type(struct nlattr *a) +{ + return nla_get_s32(a); +} + +static enum dpll_pin_signal_type dpll_msg_read_pin_sig_type(struct nlattr *a) +{ + return nla_get_s32(a); +} + +static u32 dpll_msg_read_pin_custom_freq(struct nlattr *a) +{ + return nla_get_u32(a); +} + +static enum dpll_pin_state dpll_msg_read_pin_state(struct nlattr *a) +{ + return nla_get_s32(a); +} + +static u32 dpll_msg_read_pin_prio(struct nlattr *a) +{ + return nla_get_u32(a); +} + +static u32 dpll_msg_read_dump_filter(struct nlattr *a) +{ + return nla_get_u32(a); +} + +static int +dpll_pin_attr_from_nlattr(struct dpll_pin_attr *pa, struct genl_info *info) +{ + enum dpll_pin_signal_type st; + enum dpll_pin_state state; + enum dpll_pin_type t; + struct nlattr *a; + int rem, ret = 0; + u32 prio, freq; + + nla_for_each_attr(a, genlmsg_data(info->genlhdr), + genlmsg_len(info->genlhdr), rem) { + switch (nla_type(a)) { + case DPLLA_PIN_TYPE: + t = dpll_msg_read_pin_type(a); + ret = dpll_pin_attr_type_set(pa, t); + if (ret) + return ret; + break; + case DPLLA_PIN_SIGNAL_TYPE: + st = dpll_msg_read_pin_sig_type(a); + ret = dpll_pin_attr_signal_type_set(pa, st); + if (ret) + return ret; + break; + case DPLLA_PIN_CUSTOM_FREQ: + freq = dpll_msg_read_pin_custom_freq(a); + ret = dpll_pin_attr_custom_freq_set(pa, freq); + if (ret) + return ret; + break; + case DPLLA_PIN_STATE: + state = dpll_msg_read_pin_state(a); + ret = dpll_pin_attr_state_set(pa, state); + if (ret) + return ret; + break; + case DPLLA_PIN_PRIO: + prio = dpll_msg_read_pin_prio(a); + ret = dpll_pin_attr_prio_set(pa, prio); + if (ret) + return ret; + break; + default: + break; + } + } + + return ret; +} + +static int dpll_cmd_pin_set(struct sk_buff *skb, struct genl_info *info) +{ + struct dpll_pin_attr *old = NULL, *new = NULL, *delta = NULL; + struct dpll_device *dpll = info->user_ptr[0]; + struct nlattr **attrs = info->attrs; + struct dpll_pin *pin; + int ret, pin_id; + + if (!attrs[DPLLA_PIN_IDX]) + return -EINVAL; + pin_id = nla_get_u32(attrs[DPLLA_PIN_IDX]); + old = dpll_pin_attr_alloc(); + new = dpll_pin_attr_alloc(); + delta = dpll_pin_attr_alloc(); + if (!old || !new || !delta) { + ret = -ENOMEM; + goto mem_free; + } + dpll_lock(dpll); + pin = dpll_pin_get_by_idx(dpll, pin_id); + if (!pin) { + ret = -ENODEV; + goto mem_free_unlock; + } + ret = dpll_pin_get_attr(dpll, pin, old); + if (ret) + goto mem_free_unlock; + ret = dpll_pin_attr_from_nlattr(new, info); + if (ret) + goto mem_free_unlock; + ret = dpll_pin_attr_delta(delta, new, old); + dpll_unlock(dpll); + if (!ret) + ret = dpll_pin_set_attr(dpll, pin, delta); + else + ret = -EINVAL; + + dpll_pin_attr_free(delta); + dpll_pin_attr_free(new); + dpll_pin_attr_free(old); + + return ret; + +mem_free_unlock: + dpll_unlock(dpll); +mem_free: + dpll_pin_attr_free(delta); + dpll_pin_attr_free(new); + dpll_pin_attr_free(old); + return ret; +} + +enum dpll_mode dpll_msg_read_mode(struct nlattr *a) +{ + return nla_get_s32(a); +} + +u32 dpll_msg_read_source_pin_id(struct nlattr *a) +{ + return nla_get_u32(a); +} + +static int +dpll_attr_from_nlattr(struct dpll_attr *dpll, struct genl_info *info) +{ + enum dpll_mode m; + struct nlattr *a; + int rem, ret = 0; + u32 source_pin; + + nla_for_each_attr(a, genlmsg_data(info->genlhdr), + genlmsg_len(info->genlhdr), rem) { + switch (nla_type(a)) { + case DPLLA_MODE: + m = dpll_msg_read_mode(a); + + ret = dpll_attr_mode_set(dpll, m); + if (ret) + return ret; + break; + case DPLLA_SOURCE_PIN_IDX: + source_pin = dpll_msg_read_source_pin_id(a); + + ret = dpll_attr_source_idx_set(dpll, source_pin); + if (ret) + return ret; + break; + default: + break; + } + } + + return ret; +} + +static int dpll_cmd_device_set(struct sk_buff *skb, struct genl_info *info) +{ + struct dpll_attr *old = NULL, *new = NULL, *delta = NULL; + struct dpll_device *dpll = info->user_ptr[0]; + int ret; + + old = dpll_attr_alloc(); + new = dpll_attr_alloc(); + delta = dpll_attr_alloc(); + if (!old || !new || !delta) { + ret = -ENOMEM; + goto mem_free; + } + dpll_lock(dpll); + ret = dpll_get_attr(dpll, old); + dpll_unlock(dpll); + if (!ret) { + dpll_attr_from_nlattr(new, info); + ret = dpll_attr_delta(delta, new, old); + if (!ret) + ret = dpll_set_attr(dpll, delta); + } + +mem_free: + dpll_attr_free(old); + dpll_attr_free(new); + dpll_attr_free(delta); + + return ret; +} + +static int +dpll_cmd_device_dump(struct sk_buff *skb, struct netlink_callback *cb) +{ + struct dpll_dump_ctx *ctx = dpll_dump_context(cb); + struct dpll_device *dpll; + struct nlattr *hdr; + unsigned long i; + int ret; + + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, + &dpll_gnl_family, 0, DPLL_CMD_DEVICE_GET); + if (!hdr) + return -EMSGSIZE; + + for_each_dpll(dpll, i) { + ret = dpll_device_dump_one(dpll, skb, ctx->dump_filter); + if (ret) + break; + } + + if (ret) + genlmsg_cancel(skb, hdr); + else + genlmsg_end(skb, hdr); + + return ret; +} + +static int dpll_cmd_device_get(struct sk_buff *skb, struct genl_info *info) +{ + struct dpll_device *dpll = info->user_ptr[0]; + struct nlattr **attrs = info->attrs; + struct sk_buff *msg; + int dump_filter = 0; + struct nlattr *hdr; + int ret; + + if (attrs[DPLLA_DUMP_FILTER]) + dump_filter = + dpll_msg_read_dump_filter(attrs[DPLLA_DUMP_FILTER]); + + msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + hdr = genlmsg_put_reply(msg, info, &dpll_gnl_family, 0, + DPLL_CMD_DEVICE_GET); + if (!hdr) + return -EMSGSIZE; + + ret = dpll_device_dump_one(dpll, msg, dump_filter); + if (ret) + goto out_free_msg; + genlmsg_end(msg, hdr); + + return genlmsg_reply(msg, info); + +out_free_msg: + nlmsg_free(msg); + return ret; + +} + +static int dpll_cmd_device_get_start(struct netlink_callback *cb) +{ + const struct genl_dumpit_info *info = genl_dumpit_info(cb); + struct dpll_dump_ctx *ctx = dpll_dump_context(cb); + struct nlattr *attr = info->attrs[DPLLA_DUMP_FILTER]; + + if (attr) + ctx->dump_filter = dpll_msg_read_dump_filter(attr); + else + ctx->dump_filter = 0; + + return 0; +} + +static int dpll_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info) +{ + struct dpll_device *dpll_id = NULL, *dpll_name = NULL; + + if (!info->attrs[DPLLA_ID] && + !info->attrs[DPLLA_NAME]) + return -EINVAL; + + if (info->attrs[DPLLA_ID]) { + u32 id = nla_get_u32(info->attrs[DPLLA_ID]); + + dpll_id = dpll_device_get_by_id(id); + if (!dpll_id) + return -ENODEV; + info->user_ptr[0] = dpll_id; + } + if (info->attrs[DPLLA_NAME]) { + const char *name = nla_data(info->attrs[DPLLA_NAME]); + + dpll_name = dpll_device_get_by_name(name); + if (!dpll_name) + return -ENODEV; + + if (dpll_id && dpll_name != dpll_id) + return -EINVAL; + info->user_ptr[0] = dpll_name; + } + + return 0; +} + +static const struct genl_ops dpll_ops[] = { + { + .cmd = DPLL_CMD_DEVICE_GET, + .flags = GENL_UNS_ADMIN_PERM, + .start = dpll_cmd_device_get_start, + .dumpit = dpll_cmd_device_dump, + .doit = dpll_cmd_device_get, + .policy = dpll_cmd_device_get_policy, + .maxattr = ARRAY_SIZE(dpll_cmd_device_get_policy) - 1, + }, + { + .cmd = DPLL_CMD_DEVICE_SET, + .flags = GENL_UNS_ADMIN_PERM, + .doit = dpll_cmd_device_set, + .policy = dpll_cmd_device_set_policy, + .maxattr = ARRAY_SIZE(dpll_cmd_device_set_policy) - 1, + }, + { + .cmd = DPLL_CMD_PIN_SET, + .flags = GENL_UNS_ADMIN_PERM, + .doit = dpll_cmd_pin_set, + .policy = dpll_cmd_pin_set_policy, + .maxattr = ARRAY_SIZE(dpll_cmd_pin_set_policy) - 1, + }, +}; + +static struct genl_family dpll_family __ro_after_init = { + .hdrsize = 0, + .name = DPLL_FAMILY_NAME, + .version = DPLL_VERSION, + .ops = dpll_ops, + .n_ops = ARRAY_SIZE(dpll_ops), + .mcgrps = dpll_mcgrps, + .n_mcgrps = ARRAY_SIZE(dpll_mcgrps), + .pre_doit = dpll_pre_doit, + .parallel_ops = true, +}; + +static int dpll_event_device_id(struct dpll_param *p) +{ + int ret = dpll_msg_add_id(p->msg, dpll_id(p->dpll)); + + if (ret) + return ret; + ret = dpll_msg_add_name(p->msg, dpll_dev_name(p->dpll)); + + return ret; +} + +static int dpll_event_device_change(struct dpll_param *p) +{ + int ret = dpll_msg_add_id(p->msg, dpll_id(p->dpll)); + + if (ret) + return ret; + ret = dpll_msg_add_event_change_type(p->msg, p->change_type); + if (ret) + return ret; + switch (p->change_type) { + case DPLL_CHANGE_PIN_ADD: + case DPLL_CHANGE_PIN_DEL: + case DPLL_CHANGE_PIN_TYPE: + case DPLL_CHANGE_PIN_SIGNAL_TYPE: + case DPLL_CHANGE_PIN_STATE: + case DPLL_CHANGE_PIN_PRIO: + ret = dpll_msg_add_pin_idx(p->msg, dpll_pin_idx(p->dpll, p->pin)); + break; + default: + break; + } + + return ret; +} + +static const cb_t event_cb[] = { + [DPLL_EVENT_DEVICE_CREATE] = dpll_event_device_id, + [DPLL_EVENT_DEVICE_DELETE] = dpll_event_device_id, + [DPLL_EVENT_DEVICE_CHANGE] = dpll_event_device_change, +}; + +/* + * Generic netlink DPLL event encoding + */ +static int dpll_send_event(enum dpll_event event, struct dpll_param *p) +{ + struct sk_buff *msg; + int ret = -EMSGSIZE; + void *hdr; + + msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + p->msg = msg; + + hdr = genlmsg_put(msg, 0, 0, &dpll_family, 0, event); + if (!hdr) + goto out_free_msg; + + ret = event_cb[event](p); + if (ret) + goto out_cancel_msg; + + genlmsg_end(msg, hdr); + + genlmsg_multicast(&dpll_family, msg, 0, 0, GFP_KERNEL); + + return 0; + +out_cancel_msg: + genlmsg_cancel(msg, hdr); +out_free_msg: + nlmsg_free(msg); + + return ret; +} + +int dpll_notify_device_create(struct dpll_device *dpll) +{ + struct dpll_param p = { .dpll = dpll }; + + return dpll_send_event(DPLL_EVENT_DEVICE_CREATE, &p); +} + +int dpll_notify_device_delete(struct dpll_device *dpll) +{ + struct dpll_param p = { .dpll = dpll }; + + return dpll_send_event(DPLL_EVENT_DEVICE_DELETE, &p); +} + +int dpll_notify_device_change(struct dpll_device *dpll, + enum dpll_event_change event, + struct dpll_pin *pin) +{ + struct dpll_param p = { .dpll = dpll, + .change_type = event, + .pin = pin }; + + return dpll_send_event(DPLL_EVENT_DEVICE_CHANGE, &p); +} +EXPORT_SYMBOL_GPL(dpll_notify_device_change); + +int __init dpll_netlink_init(void) +{ + return genl_register_family(&dpll_family); +} + +void dpll_netlink_finish(void) +{ + genl_unregister_family(&dpll_family); +} + +void __exit dpll_netlink_fini(void) +{ + dpll_netlink_finish(); +} diff --git a/drivers/dpll/dpll_netlink.h b/drivers/dpll/dpll_netlink.h new file mode 100644 index 000000000000..8e50b2493027 --- /dev/null +++ b/drivers/dpll/dpll_netlink.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates + */ + +/** + * dpll_notify_device_create - notify that the device has been created + * @dpll: registered dpll pointer + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_notify_device_create(struct dpll_device *dpll); + + +/** + * dpll_notify_device_delete - notify that the device has been deleted + * @dpll: registered dpll pointer + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_notify_device_delete(struct dpll_device *dpll); + +int __init dpll_netlink_init(void); +void dpll_netlink_finish(void); diff --git a/include/linux/dpll.h b/include/linux/dpll.h new file mode 100644 index 000000000000..a9cbf260624d --- /dev/null +++ b/include/linux/dpll.h @@ -0,0 +1,261 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates + */ + +#ifndef __DPLL_H__ +#define __DPLL_H__ + +#include +#include +#include + +struct dpll_device; +struct dpll_pin; + +#define DPLL_COOKIE_LEN 10 +#define PIN_IDX_INVALID ((u32)ULONG_MAX) +struct dpll_device_ops { + int (*get)(struct dpll_device *dpll, struct dpll_attr *attr); + int (*set)(struct dpll_device *dpll, const struct dpll_attr *attr); +}; + +struct dpll_pin_ops { + int (*get)(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin_attr *attr); + int (*set)(struct dpll_device *dpll, struct dpll_pin *pin, + const struct dpll_pin_attr *attr); + int (*select)(struct dpll_device *dpll, struct dpll_pin *pin); +}; + +enum dpll_type { + DPLL_TYPE_UNSPEC, + DPLL_TYPE_PPS, + DPLL_TYPE_EEC, + + __DPLL_TYPE_MAX +}; +#define DPLL_TYPE_MAX (__DPLL_TYPE_MAX - 1) + +/** + * dpll_device_alloc - allocate memory for a new dpll_device object + * @ops: pointer to dpll operations structure + * @type: type of a dpll being allocated + * @cookie: a system unique number for a device + * @dev_driver_idx: index of dpll device on parent device + * @priv: private data of a registerer + * @parent: device structure of a module registering dpll device + * + * Allocate memory for a new dpll and initialize it with its type, name, + * callbacks and private data pointer. + * + * Name is generated based on: cookie, type and dev_driver_idx. + * Finding allocated and registered dpll device is also possible with + * the: cookie, type and dev_driver_idx. This way dpll device can be + * shared by multiple instances of a device driver. + * + * Returns: + * * pointer to initialized dpll - success + * * NULL - memory allocation fail + */ +struct dpll_device +*dpll_device_alloc(struct dpll_device_ops *ops, enum dpll_type type, + const u8 cookie[DPLL_COOKIE_LEN], u8 dev_driver_idx, + void *priv, struct device *parent); + +/** + * dpll_device_register - registers allocated dpll + * @dpll: pointer to dpll + * + * Register the dpll on the dpll subsystem, make it available for netlink + * API users. + */ +void dpll_device_register(struct dpll_device *dpll); + +/** + * dpll_device_unregister - unregister registered dpll + * @dpll: pointer to dpll + * + * Unregister the dpll from the subsystem, make it unavailable for netlink + * API users. + */ +void dpll_device_unregister(struct dpll_device *dpll); + +/** + * dpll_device_free - free dpll memory + * @dpll: pointer to dpll + * + * Free memory allocated with ``dpll_device_alloc(..)`` + */ +void dpll_device_free(struct dpll_device *dpll); + +/** + * dpll_priv - get private data + * @dpll: pointer to dpll + * + * Obtain private data pointer passed to dpll subsystem when allocating + * device with ``dpll_device_alloc(..)`` + */ +void *dpll_priv(struct dpll_device *dpll); + +/** + * dpll_pin_priv - get private data + * @dpll: pointer to dpll + * + * Obtain private pin data pointer passed to dpll subsystem when pin + * was registered with dpll. + */ +void *dpll_pin_priv(struct dpll_device *dpll, struct dpll_pin *pin); + +/** + * dpll_pin_idx - get pin idx + * @dpll: pointer to dpll + * @pin: pointer to a pin + * + * Obtain pin index of given pin on given dpll. + * + * Return: PIN_IDX_INVALID - if failed to find pin, otherwise pin index + */ +u32 dpll_pin_idx(struct dpll_device *dpll, struct dpll_pin *pin); + + +/** + * dpll_shared_pin_register - share a pin between dpll devices + * @dpll_pin_owner: a dpll already registered with a pin + * @dpll: a dpll being registered with a pin + * @pin_idx: index of a pin on dpll device (@dpll_pin_owner) + * that is being registered on new dpll (@dpll) + * @ops: struct with pin ops callbacks + * @priv: private data pointer passed when calling callback ops + * + * Register a pin already registered with different dpll device. + * Allow to share a single pin within multiple dpll instances. + * + * Returns: + * * 0 - success + * * negative - failure + */ +int +dpll_shared_pin_register(struct dpll_device *dpll_pin_owner, + struct dpll_device *dpll, u32 pin_idx, + struct dpll_pin_ops *ops, void *priv); + +/** + * dpll_pin_alloc - allocate memory for a new dpll_pin object + * @description: pointer to string description of a pin with max length + * equal to PIN_DESC_LEN + * @desc_len: number of chars in description + * + * Allocate memory for a new pin and initialize its resources. + * + * Returns: + * * pointer to initialized pin - success + * * NULL - memory allocation fail + */ +struct dpll_pin *dpll_pin_alloc(const char *description, size_t desc_len); + + +/** + * dpll_pin_register - register pin with a dpll device + * @dpll: pointer to dpll object to register pin with + * @pin: pointer to allocated pin object being registered with dpll + * @ops: struct with pin ops callbacks + * @priv: private data pointer passed when calling callback ops + * + * Register previously allocated pin object with a dpll device. + * + * Return: + * * 0 - if pin was registered with a parent pin, + * * -ENOMEM - failed to allocate memory, + * * -EEXIST - pin already registered with this dpll, + * * -EBUSY - couldn't allocate id for a pin. + */ +int dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv); + +/** + * dpll_pin_deregister - deregister pin from a dpll device + * @dpll: pointer to dpll object to deregister pin from + * @pin: pointer to allocated pin object being deregistered from dpll + * + * Deregister previously registered pin object from a dpll device. + * + * Return: + * * 0 - pin was successfully deregistered from this dpll device, + * * -ENXIO - given pin was not registered with this dpll device, + * * -EINVAL - pin pointer is not valid. + */ +int dpll_pin_deregister(struct dpll_device *dpll, struct dpll_pin *pin); + +/** + * dpll_pin_free - free memory allocated for a pin + * @pin: pointer to allocated pin object being freed + * + * Shared pins must be deregistered from all dpll devices before freeing them, + * otherwise the memory won't be freed. + */ +void dpll_pin_free(struct dpll_pin *pin); + +/** + * dpll_muxed_pin_register - register a pin to a muxed-type pin + * @parent_pin: pointer to object to register pin with + * @pin: pointer to allocated pin object being deregistered from dpll + * @ops: struct with pin ops callbacks + * @priv: private data pointer passed when calling callback ops* + * + * In case of multiplexed pins, allows registring them under a single + * parent pin. + * + * Return: + * * 0 - if pin was registered with a parent pin, + * * -ENOMEM - failed to allocate memory, + * * -EEXIST - pin already registered with this parent pin, + * * -EBUSY - couldn't assign id for a pin. + */ +int dpll_muxed_pin_register(struct dpll_device *dpll, + struct dpll_pin *parent_pin, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv); +/** + * dpll_device_get_by_cookie - find a dpll by its cookie + * @cookie: cookie of dpll to search for, as given by driver on + * ``dpll_device_alloc`` + * @type: type of dpll, as given by driver on ``dpll_device_alloc`` + * @idx: index of dpll, as given by driver on ``dpll_device_alloc`` + * + * Allows multiple driver instances using one physical DPLL to find + * and share already registered DPLL device. + * + * Return: pointer if device was found, NULL otherwise. + */ +struct dpll_device *dpll_device_get_by_cookie(u8 cookie[DPLL_COOKIE_LEN], + enum dpll_type type, u8 idx); + +/** + * dpll_pin_get_by_description - find a pin by its description + * @dpll: dpll device pointer + * @description: string description of pin + * + * Allows multiple driver instances using one physical DPLL to find + * and share pin already registered with existing dpll device. + * + * Return: pointer if pin was found, NULL otherwise. + */ +struct dpll_pin *dpll_pin_get_by_description(struct dpll_device *dpll, + const char *description); + +/** + * dpll_pin_get_by_idx - find a pin by its index + * @dpll: dpll device pointer + * @idx: index of pin + * + * Allows multiple driver instances using one physical DPLL to find + * and share pin already registered with existing dpll device. + * + * Return: pointer if pin was found, NULL otherwise. + */ +struct dpll_pin *dpll_pin_get_by_idx(struct dpll_device *dpll, int idx); + +int dpll_notify_device_change(struct dpll_device *dpll, + enum dpll_event_change event, + struct dpll_pin *pin); +#endif diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h new file mode 100644 index 000000000000..16278618d59d --- /dev/null +++ b/include/uapi/linux/dpll.h @@ -0,0 +1,263 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_DPLL_H +#define _UAPI_LINUX_DPLL_H + +#define DPLL_NAME_LEN 32 +#define DPLL_DESC_LEN 20 +#define PIN_DESC_LEN 20 + +/* Adding event notification support elements */ +#define DPLL_FAMILY_NAME "dpll" +#define DPLL_VERSION 0x01 +#define DPLL_MONITOR_GROUP_NAME "monitor" + +#define DPLL_DUMP_FILTER_PINS 1 +#define DPLL_DUMP_FILTER_STATUS 2 + +/* dplla - Attributes of dpll generic netlink family + * + * @DPLLA_UNSPEC - invalid attribute + * @DPLLA_ID - ID of a dpll device (unsigned int) + * @DPLLA_NAME - human-readable name (char array of DPLL_NAME_LENGTH size) + * @DPLLA_MODE - working mode of dpll (enum dpll_mode) + * @DPLLA_MODE_SUPPORTED - list of supported working modes (enum dpll_mode) + * @DPLLA_SOURCE_PIN_ID - ID of source pin selected to drive dpll + * (unsigned int) + * @DPLLA_LOCK_STATUS - dpll's lock status (enum dpll_lock_status) + * @DPLLA_TEMP - dpll's temperature (signed int - Celsius degrees) + * @DPLLA_DUMP_FILTER - filter bitmask (int, sum of DPLL_DUMP_FILTER_* defines) + * @DPLLA_NETIFINDEX - related network interface index + * @DPLLA_PIN - nested attribute, each contains single pin attributes + * @DPLLA_PIN_IDX - index of a pin on dpll (unsigned int) + * @DPLLA_PIN_DESCRIPTION - human-readable pin description provided by driver + * (char array of PIN_DESC_LEN size) + * @DPLLA_PIN_TYPE - current type of a pin (enum dpll_pin_type) + * @DPLLA_PIN_TYPE_SUPPORTED - pin types supported (enum dpll_pin_type) + * @DPLLA_PIN_SIGNAL_TYPE - current type of a signal + * (enum dpll_pin_signal_type) + * @DPLLA_PIN_SIGNAL_TYPE_SUPPORTED - pin signal types supported + * (enum dpll_pin_signal_type) + * @DPLLA_PIN_CUSTOM_FREQ - freq value for DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ + * (unsigned int) + * @DPLLA_PIN_STATE - state of pin's capabilities (enum dpll_pin_state) + * @DPLLA_PIN_STATE_SUPPORTED - available pin's capabilities + * (enum dpll_pin_state) + * @DPLLA_PIN_PRIO - priority of a pin on dpll (unsigned int) + * @DPLLA_PIN_PARENT_IDX - if of a parent pin (unsigned int) + * @DPLLA_CHANGE_TYPE - type of device change event + * (enum dpll_change_type) + * @DPLLA_PIN_NETIFINDEX - related network interface index for the pin + **/ +enum dplla { + DPLLA_UNSPEC, + DPLLA_ID, + DPLLA_NAME, + DPLLA_MODE, + DPLLA_MODE_SUPPORTED, + DPLLA_SOURCE_PIN_IDX, + DPLLA_LOCK_STATUS, + DPLLA_TEMP, + DPLLA_DUMP_FILTER, + DPLLA_NETIFINDEX, + DPLLA_PIN, + DPLLA_PIN_IDX, + DPLLA_PIN_DESCRIPTION, + DPLLA_PIN_TYPE, + DPLLA_PIN_TYPE_SUPPORTED, + DPLLA_PIN_SIGNAL_TYPE, + DPLLA_PIN_SIGNAL_TYPE_SUPPORTED, + DPLLA_PIN_CUSTOM_FREQ, + DPLLA_PIN_STATE, + DPLLA_PIN_STATE_SUPPORTED, + DPLLA_PIN_PRIO, + DPLLA_PIN_PARENT_IDX, + DPLLA_CHANGE_TYPE, + DPLLA_PIN_NETIFINDEX, + __DPLLA_MAX, +}; + +#define DPLLA_MAX (__DPLLA_MAX - 1) + +/* dpll_lock_status - DPLL status provides information of device status + * + * @DPLL_LOCK_STATUS_UNSPEC - unspecified value + * @DPLL_LOCK_STATUS_UNLOCKED - dpll was not yet locked to any valid (or is in + * DPLL_MODE_FREERUN/DPLL_MODE_NCO modes) + * @DPLL_LOCK_STATUS_CALIBRATING - dpll is trying to lock to a valid signal + * @DPLL_LOCK_STATUS_LOCKED - dpll is locked + * @DPLL_LOCK_STATUS_HOLDOVER - dpll is in holdover state - lost a valid lock + * or was forced by DPLL_MODE_HOLDOVER mode) + **/ +enum dpll_lock_status { + DPLL_LOCK_STATUS_UNSPEC, + DPLL_LOCK_STATUS_UNLOCKED, + DPLL_LOCK_STATUS_CALIBRATING, + DPLL_LOCK_STATUS_LOCKED, + DPLL_LOCK_STATUS_HOLDOVER, + + __DPLL_LOCK_STATUS_MAX, +}; + +#define DPLL_LOCK_STATUS_MAX (__DPLL_LOCK_STATUS_MAX - 1) + +/* dpll_pin_type - signal types + * + * @DPLL_PIN_TYPE_UNSPEC - unspecified value + * @DPLL_PIN_TYPE_MUX - mux type pin, aggregates selectable pins + * @DPLL_PIN_TYPE_EXT - external source + * @DPLL_PIN_TYPE_SYNCE_ETH_PORT - ethernet port PHY's recovered clock + * @DPLL_PIN_TYPE_INT_OSCILLATOR - device internal oscillator + * @DPLL_PIN_TYPE_GNSS - GNSS recovered clock + **/ +enum dpll_pin_type { + DPLL_PIN_TYPE_UNSPEC, + DPLL_PIN_TYPE_MUX, + DPLL_PIN_TYPE_EXT, + DPLL_PIN_TYPE_SYNCE_ETH_PORT, + DPLL_PIN_TYPE_INT_OSCILLATOR, + DPLL_PIN_TYPE_GNSS, + + __DPLL_PIN_TYPE_MAX, +}; + +#define DPLL_PIN_TYPE_MAX (__DPLL_PIN_TYPE_MAX - 1) + +/* dpll_pin_signal_type - signal types + * + * @DPLL_PIN_SIGNAL_TYPE_UNSPEC - unspecified value + * @DPLL_PIN_SIGNAL_TYPE_1_PPS - a 1Hz signal + * @DPLL_PIN_SIGNAL_TYPE_10_MHZ - a 10 MHz signal + * @DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ - custom frequency signal, value defined + * with pin's DPLLA_PIN_SIGNAL_TYPE_CUSTOM_FREQ attribute + **/ +enum dpll_pin_signal_type { + DPLL_PIN_SIGNAL_TYPE_UNSPEC, + DPLL_PIN_SIGNAL_TYPE_1_PPS, + DPLL_PIN_SIGNAL_TYPE_10_MHZ, + DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ, + + __DPLL_PIN_SIGNAL_TYPE_MAX, +}; + +#define DPLL_PIN_SIGNAL_TYPE_MAX (__DPLL_PIN_SIGNAL_TYPE_MAX - 1) + +/* dpll_pin_state - available pin states + * + * @DPLL_PIN_STATE_UNSPEC - unspecified value + * @DPLL_PIN_STATE_CONNECTED - pin connected + * @DPLL_PIN_STATE_DISCONNECTED - pin disconnected + * @DPLL_PIN_STATE_SOURCE - pin used as an input pin + * @DPLL_PIN_STATE_OUTPUT - pin used as an output pin + **/ +enum dpll_pin_state { + DPLL_PIN_STATE_UNSPEC, + DPLL_PIN_STATE_CONNECTED, + DPLL_PIN_STATE_DISCONNECTED, + DPLL_PIN_STATE_SOURCE, + DPLL_PIN_STATE_OUTPUT, + + __DPLL_PIN_STATE_MAX, +}; + +#define DPLL_PIN_STATE_MAX (__DPLL_PIN_STATE_MAX - 1) + +/** + * dpll_event - Events of dpll generic netlink family + * + * @DPLL_EVENT_UNSPEC - invalid event type + * @DPLL_EVENT_DEVICE_CREATE - dpll device created + * @DPLL_EVENT_DEVICE_DELETE - dpll device deleted + * @DPLL_EVENT_DEVICE_CHANGE - attribute of dpll device or pin changed + **/ +enum dpll_event { + DPLL_EVENT_UNSPEC, + DPLL_EVENT_DEVICE_CREATE, + DPLL_EVENT_DEVICE_DELETE, + DPLL_EVENT_DEVICE_CHANGE, + + __DPLL_EVENT_MAX, +}; + +#define DPLL_EVENT_MAX (__DPLL_EVENT_MAX - 1) + +/** + * dpll_change_type - values of events in case of device change event + * (DPLL_EVENT_DEVICE_CHANGE) + * + * @DPLL_CHANGE_UNSPEC - invalid event type + * @DPLL_CHANGE_MODE - mode changed + * @DPLL_CHANGE_LOCK_STATUS - lock status changed + * @DPLL_CHANGE_SOURCE_PIN - source pin changed, + * @DPLL_CHANGE_TEMP - temperature changed + * @DPLL_CHANGE_PIN_ADD - source pin added, + * @DPLL_CHANGE_PIN_DEL - source pin deleted, + * @DPLL_CHANGE_PIN_TYPE - pin type cahnged, + * @DPLL_CHANGE_PIN_SIGNAL_TYPE pin signal type changed + * @DPLL_CHANGE_PIN_CUSTOM_FREQ custom frequency changed + * @DPLL_CHANGE_PIN_STATE - pin state changed + * @DPLL_CHANGE_PIN_PRIO - pin prio changed + **/ +enum dpll_event_change { + DPLL_CHANGE_UNSPEC, + DPLL_CHANGE_MODE, + DPLL_CHANGE_LOCK_STATUS, + DPLL_CHANGE_SOURCE_PIN, + DPLL_CHANGE_TEMP, + DPLL_CHANGE_PIN_ADD, + DPLL_CHANGE_PIN_DEL, + DPLL_CHANGE_PIN_TYPE, + DPLL_CHANGE_PIN_SIGNAL_TYPE, + DPLL_CHANGE_PIN_CUSTOM_FREQ, + DPLL_CHANGE_PIN_STATE, + DPLL_CHANGE_PIN_PRIO, + + __DPLL_CHANGE_MAX, +}; + +#define DPLL_CHANGE_MAX (__DPLL_CHANGE_MAX - 1) + +/** + * dpll_cmd - Commands supported by the dpll generic netlink family + * + * @DPLL_CMD_UNSPEC - invalid message type + * @DPLL_CMD_DEVICE_GET - Get list of dpll devices (dump) or attributes of + * single dpll device and it's pins + * @DPLL_CMD_DEVICE_SET - Set attributes for a dpll + * @DPLL_CMD_PIN_SET - Set attributes for a pin + **/ +enum dpll_cmd { + DPLL_CMD_UNSPEC, + DPLL_CMD_DEVICE_GET, + DPLL_CMD_DEVICE_SET, + DPLL_CMD_PIN_SET, + + __DPLL_CMD_MAX, +}; + +#define DPLL_CMD_MAX (__DPLL_CMD_MAX - 1) + +/** + * dpll_mode - Working-modes a dpll can support. Modes differentiate how + * dpll selects one of its sources to syntonize with a source. + * + * @DPLL_MODE_UNSPEC - invalid + * @DPLL_MODE_FORCED - source can be only selected by sending a request to dpll + * @DPLL_MODE_AUTOMATIC - highest prio, valid source, auto selected by dpll + * @DPLL_MODE_HOLDOVER - dpll forced into holdover mode + * @DPLL_MODE_FREERUN - dpll driven on system clk, no holdover available + * @DPLL_MODE_NCO - dpll driven by Numerically Controlled Oscillator + **/ +enum dpll_mode { + DPLL_MODE_UNSPEC, + DPLL_MODE_FORCED, + DPLL_MODE_AUTOMATIC, + DPLL_MODE_HOLDOVER, + DPLL_MODE_FREERUN, + DPLL_MODE_NCO, + + __DPLL_MODE_MAX, +}; + +#define DPLL_MODE_MAX (__DPLL_MODE_MAX - 1) + +#endif /* _UAPI_LINUX_DPLL_H */ From patchwork Tue Nov 29 21:37:23 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vadim Fedorenko X-Patchwork-Id: 13059251 X-Patchwork-Delegate: kuba@kernel.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8511EC433FE for ; Tue, 29 Nov 2022 21:46:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236832AbiK2Vqs (ORCPT ); Tue, 29 Nov 2022 16:46:48 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33310 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236671AbiK2Vqq (ORCPT ); Tue, 29 Nov 2022 16:46:46 -0500 Received: from novek.ru (unknown [213.148.174.62]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 8BE386C701; Tue, 29 Nov 2022 13:46:44 -0800 (PST) Received: from nat1.ooonet.ru (gw.zelenaya.net [91.207.137.40]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by novek.ru (Postfix) with ESMTPSA id A405B504F7D; Wed, 30 Nov 2022 00:33:53 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 novek.ru A405B504F7D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=novek.ru; s=mail; t=1669757636; bh=jxC5mb/B563MYzjyvSKFbgKSDLCEdK3pzyR9nUdt0G8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=DET4Nkq4PmScypnwvF5IFjbz4erXktgCgNOltyJex6PNoErMpgqYjneHvBMIMNPqE HQvMkfaCL/xuko2nbfskLrYoJ6Rpq2S/kRe+Tf2Yq5gCntYfv0x83JSDTdUyRfm7Ou LmBpw3wa4OzeIQ3FDVMUbizUtY0Pyfa/HbBuWxMs= From: Vadim Fedorenko To: Jakub Kicinski , Jiri Pirko , Arkadiusz Kubalewski , Jonathan Lemon , Paolo Abeni Cc: netdev@vger.kernel.org, Vadim Fedorenko , linux-arm-kernel@lists.infradead.org, linux-clk@vger.kernel.org Subject: [RFC PATCH v4 3/4] dpll: documentation on DPLL subsystem interface Date: Wed, 30 Nov 2022 00:37:23 +0300 Message-Id: <20221129213724.10119-4-vfedorenko@novek.ru> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20221129213724.10119-1-vfedorenko@novek.ru> References: <20221129213724.10119-1-vfedorenko@novek.ru> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-Delegate: kuba@kernel.org X-Patchwork-State: RFC From: Vadim Fedorenko Add documentation explaining common netlink interface to configure DPLL devices and monitoring events. Common way to implement DPLL device in a driver is also covered. Co-developed-by: Arkadiusz Kubalewski Signed-off-by: Arkadiusz Kubalewski Signed-off-by: Vadim Fedorenko --- Documentation/networking/dpll.rst | 271 +++++++++++++++++++++++++++++ Documentation/networking/index.rst | 1 + 2 files changed, 272 insertions(+) create mode 100644 Documentation/networking/dpll.rst diff --git a/Documentation/networking/dpll.rst b/Documentation/networking/dpll.rst new file mode 100644 index 000000000000..58401e2b70a7 --- /dev/null +++ b/Documentation/networking/dpll.rst @@ -0,0 +1,271 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=============================== +The Linux kernel DPLL subsystem +=============================== + + +The main purpose of DPLL subsystem is to provide general interface +to configure devices that use any kind of Digital PLL and could use +different sources of signal to synchronize to as well as different +types of outputs. +The main interface is NETLINK_GENERIC based protocol with an event +monitoring multicast group defined. + + +Pin object +========== +A pin is amorphic object which represents either input and output, it +could be internal component of the device, as well as externaly +connected. +The number of pins per dpll vary, but usually multiple pins shall be +provided for a single dpll device. +Direction of a pin and it's capabilities are provided to the user in +response for netlink dump request messages. +Pin can be shared by multiple dpll devices. Where configuration on one +pin can alter multiple dplls (i.e. DPLL_PIN_SGINAL_TYPE, DPLL_PIN_TYPE, +DPLL_PIN_STATE), or just one pin-dpll pair (i.e. DPLL_PIN_PRIO). +Pin can be also a MUX type, where one or more pins are attached to +a parent pin. The parent pin is the one directly connected to the dpll, +which may be used by dplls in DPLL_MODE_AUTOMATIC selection mode, where +only pins directly connected to the dpll are capable of automatic +source pin selection. In such case, pins are dumped with +DPLLA_PIN_PARENT_IDX, and are able to be selected by the userspace with +netlink request. + +Configuration commands group +============================ + +Configuration commands are used to get or dump information about +registered DPLL devices (and pins), as well as set configuration of +device or pins. As DPLL device could not be abstract and reflects real +hardware, there is no way to add new DPLL device via netlink from user +space and each device should be registered by it's driver. + +List of command with possible attributes +======================================== + +All constants identifying command types use ``DPLL_CMD_`` prefix and +suffix according to command purpose. All attributes use ``DPLLA_`` +prefix and suffix according to attribute purpose: + + ============================ ======================================= + ``DEVICE_GET`` userspace to get device info + ``ID`` attr internal dpll device index + ``NAME`` attr dpll device name + ``MODE`` attr selection mode + ``MODE_SUPPORTED`` attr available selection modes + ``SOURCE_PIN_IDX`` attr index of currently selected source + ``LOCK_STATUS`` attr internal frequency-lock status + ``TEMP`` attr device temperature information + ``NETIFINDEX`` attr dpll owner Linux netdevice index + ``DEVICE_SET`` userspace to set dpll device + configuration + ``ID`` attr internal dpll device index + ``MODE`` attr selection mode to configure + ``PIN_IDX`` attr index of source pin to select as + active source + ``PIN_SET`` userspace to set pins configuration + ``ID`` attr internal dpll device index + ``PIN_IDX`` attr index of a pin to configure + ``PIN_TYPE`` attr type configuration value for + selected pin + ``PIN_SIGNAL_TYPE`` attr signal type configuration value + for selected pin + ``PIN_CUSTOM_FREQ`` attr signal custom frequency to be set + ``PIN_STATE`` attr pin state to be set + ``PIN_PRIO`` attr pin priority to be set + +Netlink dump requests +===================== +The ``DEVICE_GET`` command is capable of dump type netlink requests. +In such case the userspace shall provide ``DUMP_FILTER`` attribute +value to filter the response as required. +If filter is not provided only name and id of available dpll(s) is +provided. If the request also contains ``ID`` attribute, only selected +dpll device shall be dumped. + +Possible response message attributes for netlink requests depending on +the value of ``DPLLA_DUMP_FILTER`` attribute: + + =============================== ==================================== + ``DPLL_DUMP_FILTER_PINS`` value of ``DUMP_FILTER`` attribute + ``PIN`` attr nested type contain single pin + attributes + ``PIN_IDX`` attr index of dumped pin + ``PIN_DESCRIPTION`` description of a pin provided by + driver + ``PIN_TYPE`` attr value of pin type + ``PIN_TYPE_SUPPORTED`` attr value of supported pin type + ``PIN_SIGNAL_TYPE`` attr value of pin signal type + ``PIN_SIGNAL_TYPE_SUPPORTED`` attr value of supported pin signal + type + ``PIN_CUSTOM_FREQ`` attr value of pin custom frequency + ``PIN_STATE`` attr value of pin state + ``PIN_STATE_SUPPORTED`` attr value of supported pin state + ``PIN_PRIO`` attr value of pin prio + ``PIN_PARENT_IDX`` attr value of pin patent index + ``PIN_NETIFINDEX`` attr value of netdevice assocaiated + with the pin + ``DPLL_DUMP_FILTER_STATUS`` value of ``DUMP_FILTER`` attribute + ``ID`` attr internal dpll device index + ``NAME`` attr dpll device name + ``MODE`` attr selection mode + ``MODE_SUPPORTED`` attr available selection modes + ``SOURCE_PIN_IDX`` attr index of currently selected + source + ``LOCK_STATUS`` attr internal frequency-lock status + ``TEMP`` attr device temperature information + ``NETIFINDEX`` attr dpll owner Linux netdevice index + + +The pre-defined enums +===================== + +All the enums use the ``DPLL_`` prefix. + +Values for ``PIN_TYPE`` and ``PIN_TYPE_SUPPORTED`` attributes: + + ============================ ======================================== + ``PIN_TYPE_MUX`` MUX type pin, connected pins shall + have their own types + ``PIN_TYPE_EXT`` External pin + ``PIN_TYPE_SYNCE_ETH_PORT`` SyncE on Ethernet port + ``PIN_TYPE_INT_OSCILLATOR`` Internal Oscillator (i.e. Holdover + with Atomic Clock as a Source) + ``PIN_TYPE_GNSS`` GNSS 1PPS source + +Values for ``PIN_SIGNAL_TYPE`` and ``PIN_SIGNAL_TYPE_SUPPORTED`` +attributes: + + =============================== =================================== + ``PIN_SIGNAL_TYPE_1_PPS`` 1 Hz frequency + ``PIN_SIGNAL_TYPE_10_MHZ`` 10 MHz frequency + ``PIN_SIGNAL_TYPE_CUSTOM_FREQ`` Frequency value provided in attr + ``PIN_CUSTOM_FREQ`` + +Values for ``LOCK_STATUS`` attribute: + + ============================= ====================================== + ``LOCK_STATUS_UNLOCKED`` DPLL is in freerun, not locked to any + source pin + ``LOCK_STATUS_CALIBRATING`` DPLL device calibrates to lock to the + source pin signal + ``LOCK_STATUS_LOCKED`` DPLL device is locked to the source + pin frequency + ``LOCK_STATUS_HOLDOVER`` DPLL device lost a lock, using its + frequency holdover capabilities + +Values for ``PIN_STATE`` and ``PIN_STATE_SUPPORTED`` attributes: + +============================= ============================ + ``PIN_STATE_CONNECTED`` Pin connected to a dpll + ``PIN_STATE_DISCONNECTED`` Pin disconnected from dpll + ``PIN_STATE_SOURCE`` Source pin + ``PIN_STATE_OUTPUT`` Output pin + +Possible DPLL source selection mode values: + + =================== ================================================ + ``MODE_FORCED`` source pin is force-selected by + ``DPLL_CMD_DEVICE_SET`` with given value of + ``DPLLA_SOURCE_PIN_IDX`` attribute + ``MODE_AUTOMATIC`` source pin ise auto selected according to + configured pin priorities and source signal + validity + ``MODE_HOLDOVER`` force holdover mode of DPLL + ``MODE_FREERUN`` DPLL is driven by supplied system clock without + holdover capabilities + ``MODE_NCO`` similar to FREERUN, with possibility to + numerically control frequency offset + +Notifications +================ + +DPLL device can provide notifications regarding status changes of the +device, i.e. lock status changes, source/output type changes or alarms. +This is the multicast group that is used to notify user-space apps via +netlink socket: + +Notifications messages: + + ========================= ========================================== + ``EVENT_DEVICE_CREATE`` event value new DPLL device was created + ``ID`` attr dpll device index + ``NAME`` attr dpll device name + ``EVENT_DEVICE_DELETE`` event value DPLL device was deleted + ``ID`` attr dpll device index + ``EVENT_DEVICE_CHANGE`` event value DPLL device attribute has changed + ``ID`` attr dpll device index + ``CHANGE_TYPE`` attr the reason for change with values of + ``enum dpll_event_change`` + +Device change event reasons, values of ``CHANGE_TYPE`` attribute: + + =========================== ========================================= + ``CHANGE_MODE`` DPLL selection mode has changed + ``CHANGE_LOCK_STATUS`` DPLL lock status has changed + ``CHANGE_SOURCE_PIN`` DPLL source pin has changed + ``CHANGE_TEMP`` DPLL temperature has changed + ``CHANGE_PIN_ADD`` pin added to DPLL + ``CHANGE_PIN_DEL`` pin removed from DPLL + ``CHANGE_PIN_TYPE`` pin type has chaned + ``CHANGE_PIN_SIGNAL_TYPE`` pin signal type has changed + ``CHANGE_PIN_CUSTOM_FREQ`` pin custom frequency value has changed + ``CHANGE_PIN_STATE`` pin state has changed + ``CHANGE_PIN_PRIO`` pin prio has changed + + +Device driver implementation +============================ + +For device to operate as DPLL subsystem device, it should implement +set of operations and register device via ``dpll_device_alloc`` and +``dpll_device_register`` provide the operations set, unique device +cookie, type of dpll (PPS/EEC), and pointers to parent device and +its private data for calling back the ops. + +The pins are allocated separately with ``dpll_pin_alloc``, which +requires providing pin description and its length. + +Once DPLL device is created, allocated pin can be registered with it +with 2 different methods, always providing implemented pin callbacks, +and private data pointer for calling them: +``dpll_pin_register`` - simple registration with a dpll device. +``dpll_muxed_pin_register`` - register pin with another MUX type pin. + +It is also possible to register pin already registered with different +DPLL device by calling ``dpll_shared_pin_register`` - in this case +changes requested on a single pin would affect all DPLLs which were +registered with that pin. + +For different instances of a device driver requiring to find already +registered DPLL (i.e. to connect its pins to id) +use ``dpll_device_get_by_cookie`` providing the same cookie, type of +dpll and index of the DPLL device of such type, same as given on +original device allocation. + +The name od DPLL device is generated based on registerer device struct +pointer, DPLL type and an index received from registerer device driver. +Name is in format: ``dpll-%s-%s-%s%d`` witch arguments: +``dev_driver_string(parent)`` - syscall on parent device +``dev_name(parent)`` - syscall on parent device +``type ? dpll_type_str(type) : ""`` - DPLL type converted to string +``idx`` - registerer given index + +Notifications of adding or removing DPLL devices are created within +subsystem itself. +Notifications about configurations changes are also invoked when +requested change was successfully accepted by device driver with +corresponding set command. +Although the interface provides device drivers with +``dpll_notify_device_change``, so notifications or alarms can be +requested by device driver if needed, as different ways of confirmation +could be used. All the interfaces for notification messages could be +found in ````, constants and enums are placed in +```` to be consistent with user-space. + +There is no strict requirement to implement all the operations for +each device, every operation handler is checked for existence and +ENOTSUPP is returned in case of absence of specific handler. + diff --git a/Documentation/networking/index.rst b/Documentation/networking/index.rst index 4f2d1f682a18..d50d98939942 100644 --- a/Documentation/networking/index.rst +++ b/Documentation/networking/index.rst @@ -16,6 +16,7 @@ Contents: device_drivers/index dsa/index devlink/index + dpll caif/index ethtool-netlink ieee802154 From patchwork Tue Nov 29 21:37:24 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vadim Fedorenko X-Patchwork-Id: 13059250 X-Patchwork-Delegate: kuba@kernel.org Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3911AC4167B for ; Tue, 29 Nov 2022 21:46:49 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236789AbiK2Vqr (ORCPT ); Tue, 29 Nov 2022 16:46:47 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33308 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236611AbiK2Vqp (ORCPT ); Tue, 29 Nov 2022 16:46:45 -0500 Received: from novek.ru (unknown [213.148.174.62]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 8BDE56B3B9; Tue, 29 Nov 2022 13:46:44 -0800 (PST) Received: from nat1.ooonet.ru (gw.zelenaya.net [91.207.137.40]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by novek.ru (Postfix) with ESMTPSA id 2DF63504F87; Wed, 30 Nov 2022 00:33:56 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 novek.ru 2DF63504F87 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=novek.ru; s=mail; t=1669757638; bh=fcSY/EdhUZvRyDuXygfRL0ws7XmTP433EmDuD6s0mdM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QMRAnYGw6ynzeBwyyAVm/Bf0UIub+2eMMiauK4hcZKxr/huxkJgippPlZTldfGbcB 5jpHOi4mtkr5OHaIEvoB009hhfTw4cUr1VvuHP6As2MJVOCupUlHxHOFEl3f26Dv+J hYK3y4RQ7oU2bfEAvOJb+EQSpwOigBy6SzK6KavM= From: Vadim Fedorenko To: Jakub Kicinski , Jiri Pirko , Arkadiusz Kubalewski , Jonathan Lemon , Paolo Abeni Cc: netdev@vger.kernel.org, Vadim Fedorenko , linux-arm-kernel@lists.infradead.org, linux-clk@vger.kernel.org Subject: [RFC PATCH v4 4/4] ptp_ocp: implement DPLL ops Date: Wed, 30 Nov 2022 00:37:24 +0300 Message-Id: <20221129213724.10119-5-vfedorenko@novek.ru> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20221129213724.10119-1-vfedorenko@novek.ru> References: <20221129213724.10119-1-vfedorenko@novek.ru> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-Delegate: kuba@kernel.org X-Patchwork-State: RFC From: Vadim Fedorenko Implement basic DPLL operations in ptp_ocp driver as the simplest example of using new subsystem. Signed-off-by: Vadim Fedorenko --- drivers/ptp/Kconfig | 1 + drivers/ptp/ptp_ocp.c | 123 +++++++++++++++++++++++++++++------------- 2 files changed, 87 insertions(+), 37 deletions(-) diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig index fe4971b65c64..8c4cfabc1bfa 100644 --- a/drivers/ptp/Kconfig +++ b/drivers/ptp/Kconfig @@ -177,6 +177,7 @@ config PTP_1588_CLOCK_OCP depends on COMMON_CLK select NET_DEVLINK select CRC16 + select DPLL help This driver adds support for an OpenCompute time card. diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 154d58cbd9ce..605853ac4a12 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #define PCI_VENDOR_ID_FACEBOOK 0x1d9b #define PCI_DEVICE_ID_FACEBOOK_TIMECARD 0x0400 @@ -353,6 +355,7 @@ struct ptp_ocp { struct ptp_ocp_signal signal[4]; struct ptp_ocp_sma_connector sma[4]; const struct ocp_sma_op *sma_op; + struct dpll_device *dpll; }; #define OCP_REQ_TIMESTAMP BIT(0) @@ -835,18 +838,19 @@ static DEFINE_IDR(ptp_ocp_idr); struct ocp_selector { const char *name; int value; + int dpll_type; }; static const struct ocp_selector ptp_ocp_clock[] = { - { .name = "NONE", .value = 0 }, - { .name = "TOD", .value = 1 }, - { .name = "IRIG", .value = 2 }, - { .name = "PPS", .value = 3 }, - { .name = "PTP", .value = 4 }, - { .name = "RTC", .value = 5 }, - { .name = "DCF", .value = 6 }, - { .name = "REGS", .value = 0xfe }, - { .name = "EXT", .value = 0xff }, + { .name = "NONE", .value = 0, .dpll_type = 0 }, + { .name = "TOD", .value = 1, .dpll_type = 0 }, + { .name = "IRIG", .value = 2, .dpll_type = 0 }, + { .name = "PPS", .value = 3, .dpll_type = 0 }, + { .name = "PTP", .value = 4, .dpll_type = 0 }, + { .name = "RTC", .value = 5, .dpll_type = 0 }, + { .name = "DCF", .value = 6, .dpll_type = 0 }, + { .name = "REGS", .value = 0xfe, .dpll_type = 0 }, + { .name = "EXT", .value = 0xff, .dpll_type = 0 }, { } }; @@ -855,37 +859,37 @@ static const struct ocp_selector ptp_ocp_clock[] = { #define SMA_SELECT_MASK GENMASK(14, 0) static const struct ocp_selector ptp_ocp_sma_in[] = { - { .name = "10Mhz", .value = 0x0000 }, - { .name = "PPS1", .value = 0x0001 }, - { .name = "PPS2", .value = 0x0002 }, - { .name = "TS1", .value = 0x0004 }, - { .name = "TS2", .value = 0x0008 }, - { .name = "IRIG", .value = 0x0010 }, - { .name = "DCF", .value = 0x0020 }, - { .name = "TS3", .value = 0x0040 }, - { .name = "TS4", .value = 0x0080 }, - { .name = "FREQ1", .value = 0x0100 }, - { .name = "FREQ2", .value = 0x0200 }, - { .name = "FREQ3", .value = 0x0400 }, - { .name = "FREQ4", .value = 0x0800 }, - { .name = "None", .value = SMA_DISABLE }, + { .name = "10Mhz", .value = 0x0000, .dpll_type = DPLL_PIN_SIGNAL_TYPE_10_MHZ }, + { .name = "PPS1", .value = 0x0001, .dpll_type = DPLL_PIN_SIGNAL_TYPE_1_PPS }, + { .name = "PPS2", .value = 0x0002, .dpll_type = DPLL_PIN_SIGNAL_TYPE_1_PPS }, + { .name = "TS1", .value = 0x0004, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "TS2", .value = 0x0008, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "IRIG", .value = 0x0010, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "DCF", .value = 0x0020, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "TS3", .value = 0x0040, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "TS4", .value = 0x0080, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "FREQ1", .value = 0x0100, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "FREQ2", .value = 0x0200, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "FREQ3", .value = 0x0400, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "FREQ4", .value = 0x0800, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "None", .value = SMA_DISABLE, .dpll_type = 0 }, { } }; static const struct ocp_selector ptp_ocp_sma_out[] = { - { .name = "10Mhz", .value = 0x0000 }, - { .name = "PHC", .value = 0x0001 }, - { .name = "MAC", .value = 0x0002 }, - { .name = "GNSS1", .value = 0x0004 }, - { .name = "GNSS2", .value = 0x0008 }, - { .name = "IRIG", .value = 0x0010 }, - { .name = "DCF", .value = 0x0020 }, - { .name = "GEN1", .value = 0x0040 }, - { .name = "GEN2", .value = 0x0080 }, - { .name = "GEN3", .value = 0x0100 }, - { .name = "GEN4", .value = 0x0200 }, - { .name = "GND", .value = 0x2000 }, - { .name = "VCC", .value = 0x4000 }, + { .name = "10Mhz", .value = 0x0000, .dpll_type = DPLL_PIN_SIGNAL_TYPE_10_MHZ }, + { .name = "PHC", .value = 0x0001, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "MAC", .value = 0x0002, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "GNSS1", .value = 0x0004, .dpll_type = DPLL_PIN_SIGNAL_TYPE_1_PPS }, + { .name = "GNSS2", .value = 0x0008, .dpll_type = DPLL_PIN_SIGNAL_TYPE_1_PPS }, + { .name = "IRIG", .value = 0x0010, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "DCF", .value = 0x0020, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "GEN1", .value = 0x0040, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "GEN2", .value = 0x0080, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "GEN3", .value = 0x0100, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "GEN4", .value = 0x0200, .dpll_type = DPLL_PIN_SIGNAL_TYPE_CUSTOM_FREQ }, + { .name = "GND", .value = 0x2000, .dpll_type = 0 }, + { .name = "VCC", .value = 0x4000, .dpll_type = 0 }, { } }; @@ -4175,12 +4179,41 @@ ptp_ocp_detach(struct ptp_ocp *bp) device_unregister(&bp->dev); } +static int ptp_ocp_dpll_get_attr(struct dpll_device *dpll, struct dpll_attr *attr) +{ + struct ptp_ocp *bp = (struct ptp_ocp *)dpll_priv(dpll); + int sync; + + sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC; + dpll_attr_lock_status_set(attr, sync ? DPLL_LOCK_STATUS_LOCKED : DPLL_LOCK_STATUS_UNLOCKED); + + return 0; +} + +static int ptp_ocp_dpll_pin_get_attr(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin_attr *attr) +{ + dpll_pin_attr_type_set(attr, DPLL_PIN_TYPE_EXT); + return 0; +} + +static struct dpll_device_ops dpll_ops = { + .get = ptp_ocp_dpll_get_attr, +}; + +static struct dpll_pin_ops dpll_pin_ops = { + .get = ptp_ocp_dpll_pin_get_attr, +}; + static int ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id) { + const u8 dpll_cookie[DPLL_COOKIE_LEN] = { "OCP" }; + char pin_desc[PIN_DESC_LEN]; struct devlink *devlink; + struct dpll_pin *pin; struct ptp_ocp *bp; - int err; + int err, i; devlink = devlink_alloc(&ptp_ocp_devlink_ops, sizeof(*bp), &pdev->dev); if (!devlink) { @@ -4230,6 +4263,20 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id) ptp_ocp_info(bp); devlink_register(devlink); + + bp->dpll = dpll_device_alloc(&dpll_ops, DPLL_TYPE_PPS, dpll_cookie, pdev->bus->number, bp, &pdev->dev); + if (!bp->dpll) { + dev_err(&pdev->dev, "dpll_device_alloc failed\n"); + goto out; + } + dpll_device_register(bp->dpll); + + for (i = 0; i < 4; i++) { + snprintf(pin_desc, PIN_DESC_LEN, "sma%d", i + 1); + pin = dpll_pin_alloc(pin_desc, PIN_DESC_LEN); + dpll_pin_register(bp->dpll, pin, &dpll_pin_ops, bp); + } + return 0; out: @@ -4247,6 +4294,8 @@ ptp_ocp_remove(struct pci_dev *pdev) struct ptp_ocp *bp = pci_get_drvdata(pdev); struct devlink *devlink = priv_to_devlink(bp); + dpll_device_unregister(bp->dpll); + dpll_device_free(bp->dpll); devlink_unregister(devlink); ptp_ocp_detach(bp); pci_disable_device(pdev);