diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml new file mode 100644 index 000000000000..61fb7e8b4d43 --- /dev/null +++ b/Documentation/netlink/specs/dpll.yaml @@ -0,0 +1,420 @@ +name: dpll + +doc: DPLL subsystem. + +definitions: + - + type: const + name: temp-divider + value: 10 + - + type: const + name: pin-freq-1-hz + value: 1 + - + type: const + name: pin-freq-10-mhz + value: 10000000 + - + type: enum + name: lock-status + doc: | + Provides information of dpll device lock status, valid values for + DPLL_A_LOCK_STATUS attribute + entries: + - + name: unspec + doc: unspecified value + - + name: unlocked + doc: | + dpll was not yet locked to any valid (or is in one of modes: + DPLL_MODE_FREERUN, DPLL_MODE_NCO) + - + name: calibrating + doc: dpll is trying to lock to a valid signal + - + name: locked + doc: dpll is locked + - + name: holdover + doc: | + dpll is in holdover state - lost a valid lock or was forced by + selecting DPLL_MODE_HOLDOVER mode + render-max: true + - + type: enum + name: pin-type + doc: Enumerates types of a pin, valid values for DPLL_A_PIN_TYPE attribute + entries: + - + name: unspec + doc: unspecified value + - + name: mux + doc: aggregates another layer of selectable pins + - + name: ext + doc: external source + - + name: synce-eth-port + doc: ethernet port PHY's recovered clock + - + name: int-oscillator + doc: device internal oscillator + - + name: gnss + doc: GNSS recovered clock + render-max: true + - + type: enum + name: pin-state + doc: available pin modes + entries: + - + name: unspec + doc: unspecified value + - + name: connected + doc: pin connected + - + name: disconnected + doc: pin disconnected + render-max: true + - + type: enum + name: pin-direction + doc: available pin direction + entries: + - + name: unspec + doc: unspecified value + - + name: source + doc: pin used as a source of a signal + - + name: output + doc: pin used to output the signal + render-max: true + - + type: enum + name: mode + doc: | + working-modes a dpll can support, differentiate if and how dpll selects + one of its sources to syntonize with it + entries: + - + name: unspec + doc: unspecified value + - + name: manual + doc: source can be only selected by sending a request to dpll + - + name: automatic + doc: highest prio, valid source, auto selected by dpll + - + name: holdover + doc: dpll forced into holdover mode + - + name: freerun + doc: dpll driven on system clk, no holdover available + - + name: nco + doc: dpll driven by Numerically Controlled Oscillator + render-max: true + - + type: enum + name: type + doc: type of dpll, valid values for DPLL_A_TYPE attribute + entries: + - + name: unspec + doc: unspecified value + - + name: pps + doc: dpll produces Pulse-Per-Second signal + - + name: eec + doc: dpll drives the Ethernet Equipment Clock + - + type: enum + name: event + doc: events of dpll generic netlink family + entries: + - + name: unspec + doc: invalid event type + - + name: device-create + doc: dpll device created + - + name: device-delete + doc: dpll device deleted + - + name: device-change + doc: | + attribute of dpll device or pin changed, reason is to be found with + an attribute type (DPLL_A_*) received with the event + - + type: flags + name: pin-caps + doc: define capabilities of a pin + entries: + - + name: direction-can-change + - + name: priority-can-change + - + name: state-can-change + + +attribute-sets: + - + name: dpll + enum-name: dplla + attributes: + - + name: dpll + type: nest + value: 1 + - + name: id + type: u32 + - + name: dev-name + type: string + - + name: bus-name + type: string + - + name: mode + type: s32 + enum: mode + - + name: mode-supported + type: s32 + - + name: source-pin-idx + type: u32 + - + name: lock-status + type: s32 + enum: lock-status + - + name: temp + type: s32 + - + name: clock-id + type: u64 + - + name: type + type: s32 + enum: type + - + name: pin + type: nest + nested-attributes: pin + - + name: pin-idx + type: u32 + - + name: pin-description + type: string + - + name: pin-type + type: s32 + enum: pin-type + - + name: pin-direction + type: u8 + enum: pin-direction + - + name: pin-frequency + type: u32 + - + name: pin-frequency-supported + type: u32 + - + name: pin-any-frequency-min + type: u32 + - + name: pin-any-frequency-max + type: u32 + - + name: pin-prio + type: u32 + - + name: pin-state + type: u32 + enum: pin-state + - + name: pin-parent + type: nest + - + name: pin-parent-idx + type: u32 + - + name: pin-rclk-device + type: u32 + - + name: pin-dpll-caps + type: u32 + enum: pin-dpll-caps + enum-as-flags: true + - + name: pin + subset-of: dpll + attributes: + - + name: pin-idx + type: u32 + - + name: pin-description + type: string + - + name: pin-type + type: s32 + enum: pin-type + - + name: pin-direction + type: u8 + enum: pin-direction + - + name: pin-frequency + type: u32 + - + name: pin-frequency-supported + type: u32 + - + name: pin-any-frequency-min + type: u32 + - + name: pin-any-frequency-max + type: u32 + - + name: pin-prio + type: u32 + - + name: pin-state + type: u32 + enum: pin-state + - + name: pin-parent + type: nest + - + name: pin-parent-idx + type: u32 + - + name: pin-rclk-device + type: string + - + name: pin-dpll-caps + type: u32 + enum: pin-dpll-caps + enum-as-flags: true + +operations: + list: + - + name: unspec + doc: unused + + - + name: device-get + doc: | + Get list of DPLL devices (dump) or attributes of a single dpll device + attribute-set: dpll + flags: [ admin-perm ] + + do: + pre: dpll-pre-doit + request: + attributes: + - id + - bus-name + - dev-name + reply: &all_attrs + attributes: + - id + + dump: + pre: dpll-cmd-device-get-start + request: + attributes: + - id + - bus-name + - dev-name + reply: *all_attrs + + - + name: device-set + doc: Set attributes for a DPLL device + attribute-set: dpll + flags: [ admin-perm ] + + do: + pre: dpll-pre-doit + request: + attributes: + - id + - bus-name + - dev-name + - mode + + - + name: pin-get + doc: | + Get list of pins and its attributes. + - dump request without any attributes given - list all the pins in the system + - dump request with target dpll - list all the pins registered with a given dpll device + - do request with target dpll and target pin - single pin attributes + attribute-set: dpll + flags: [ admin-perm ] + + do: + pre: dpll-pin-pre-doit + request: + attributes: + - id + - bus-name + - dev-name + - pin-idx + + dump: + request: + attributes: + - id + - bus-name + - dev-name + reply: + attributes: + - id + - bus-name + - dev-name + - pin-idx + + - + name: pin-set + doc: Set attributes of a target pin + attribute-set: dpll + flags: [ admin-perm ] + + do: + pre: dpll-pin-pre-doit + request: + attributes: + - id + - bus-name + - dev-name + - pin-idx + - pin-frequency + - pin-direction + - pin-prio + - pin-parent-idx + - pin-state + + +mcast-groups: + list: + - + name: monitor diff --git a/MAINTAINERS b/MAINTAINERS index f2bd469ffae5..70fb0424bf27 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6414,6 +6414,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 968bd0a6fd78..453df9e1210d 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -241,4 +241,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..d3926f2a733d --- /dev/null +++ b/drivers/dpll/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for DPLL drivers. +# + +obj-$(CONFIG_DPLL) += dpll_sys.o +dpll_sys-y += dpll_core.o +dpll_sys-y += dpll_netlink.o +dpll_sys-y += dpll_nl.o + diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c new file mode 100644 index 000000000000..8e013331eecb --- /dev/null +++ b/drivers/dpll/dpll_core.c @@ -0,0 +1,548 @@ +// 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" + +DEFINE_MUTEX(dpll_device_xa_lock); +DEFINE_MUTEX(dpll_pin_xa_lock); + +DEFINE_XARRAY_FLAGS(dpll_device_xa, XA_FLAGS_ALLOC); +DEFINE_XARRAY_FLAGS(dpll_pin_xa, XA_FLAGS_ALLOC); + +#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)) + +static struct class dpll_class = { + .name = "dpll", +}; + +/** + * dpll_device_get_by_id - find dpll device by it's id + * @id: id of searched dpll + * + * Return: dpll_device struct if found, NULL otherwise. + */ +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; +} + +/** + * dpll_device_get_by_name - find dpll device by it's id + * @bus_name: bus name of searched dpll + * @dev_name: dev name of searched dpll + * + * Return: dpll_device struct if found, NULL otherwise. + */ +struct dpll_device * +dpll_device_get_by_name(const char *bus_name, const char *device_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_bus_name(&dpll->dev), bus_name) && + !strcmp(dev_name(&dpll->dev), device_name)) { + ret = dpll; + break; + } + } + mutex_unlock(&dpll_device_xa_lock); + + return ret; +} + +struct dpll_device +*dpll_device_alloc(const u64 clock_id, u32 dev_driver_id, struct module *module) +{ + struct dpll_device *dpll; + int ret; + + dpll = kzalloc(sizeof(*dpll), GFP_KERNEL); + if (!dpll) + return ERR_PTR(-ENOMEM); + mutex_init(&dpll->lock); + dpll->dev.class = &dpll_class; + dpll->dev_driver_id = dev_driver_id; + dpll->clock_id = clock_id; + ret = xa_alloc(&dpll_device_xa, &dpll->id, dpll, + xa_limit_16b, GFP_KERNEL); + if (ret) { + kfree(dpll); + mutex_unlock(&dpll_device_xa_lock); + return ERR_PTR(ret); + } + xa_init_flags(&dpll->pins, XA_FLAGS_ALLOC); + mutex_unlock(&dpll_device_xa_lock); + + return dpll; +} + +static int dpll_pin_ref_dpll_add(struct dpll_pin *pin, struct dpll_device *dpll, + struct dpll_pin_ops *ops, void *priv) +{ + struct dpll_pin_ref *ref, *pos; + unsigned long index; + u32 idx; + int ret; + + ref = kzalloc(sizeof(struct dpll_pin_ref), GFP_KERNEL); + if (!ref) + return -ENOMEM; + ref->dpll = dpll; + ref->ops = ops; + ref->priv = priv; + if (!xa_empty(&pin->dpll_refs)) { + xa_for_each(&pin->dpll_refs, index, pos) { + if (pos->dpll == ref->dpll) + return -EEXIST; + } + } + + ret = xa_alloc(&pin->dpll_refs, &idx, ref, xa_limit_16b, GFP_KERNEL); + if (!ret) + refcount_inc(&dpll->refcount); + + return ret; +} + +static void +dpll_pin_ref_dpll_del(struct dpll_pin *pin, struct dpll_device *dpll) +{ + struct dpll_pin_ref *pos; + unsigned long index; + + xa_for_each(&pin->dpll_refs, index, pos) { + if (pos->dpll == dpll) { + if (pos == xa_erase(&pin->dpll_refs, index)) { + refcount_dec(&dpll->refcount); + kfree(pos); + break; + } + } + } +} + +struct dpll_device +*dpll_device_get(u64 clock_id, u32 dev_driver_id, struct module *module) +{ + struct dpll_device *dpll, *ret = NULL; + unsigned long index; + + mutex_lock(&dpll_device_xa_lock); + xa_for_each(&dpll_device_xa, index, dpll) { + if (dpll->clock_id == clock_id && + dpll->dev_driver_id == dev_driver_id && + dpll->module == module) { + ret = dpll; + break; + } + } + if (!ret) + ret = dpll_device_alloc(clock_id, dev_driver_id, module); + mutex_unlock(&dpll_device_xa_lock); + if (!IS_ERR_OR_NULL(ret)) + refcount_inc(&ret->refcount); + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_device_get); + +void dpll_device_free(struct dpll_device *dpll) +{ + WARN_ON_ONCE(!xa_empty(&dpll->pins)); + xa_destroy(&dpll->pins); + mutex_destroy(&dpll->lock); + kfree(dpll); +} + +void dpll_device_put(struct dpll_device *dpll) +{ + if (!dpll) + return; + + if (refcount_dec_and_test(&dpll->refcount) == 0) + dpll_device_free(dpll); + +} +EXPORT_SYMBOL_GPL(dpll_device_put); + +void dpll_device_register(struct dpll_device *dpll, enum dpll_type type, + void *priv, struct device *owner) +{ + mutex_lock(&dpll->lock); + ASSERT_DPLL_NOT_REGISTERED(dpll); + dpll->dev.bus = owner->bus; + dpll->parent = owner; + dpll->type = type; + dev_set_name(&dpll->dev, "%s_%d", dev_name(owner), + dpll->dev_driver_id); + dpll->priv = priv; + xa_set_mark(&dpll_device_xa, dpll->id, DPLL_REGISTERED); + mutex_unlock(&dpll->lock); + dpll_notify_device_create(dpll); +} +EXPORT_SYMBOL_GPL(dpll_device_register); + +/** + * dpll_device_deregister - deregister dpll device + * @dpll: registered dpll pointer + * + * Note: It does not free the memory + */ +void dpll_device_deregister(struct dpll_device *dpll) +{ + ASSERT_DPLL_REGISTERED(dpll); + + mutex_lock(&dpll_device_xa_lock); + xa_erase(&dpll_device_xa, dpll->id); + mutex_unlock(&dpll_device_xa_lock); + dpll_notify_device_delete(dpll); +} +EXPORT_SYMBOL_GPL(dpll_device_deregister); + +void dpll_pin_put(struct dpll_pin *pin) +{ + if (refcount_dec_and_test(&pin->refcount) == 0) { + xa_destroy(&pin->dpll_refs); + xa_destroy(&pin->pin_refs); + mutex_destroy(&pin->lock); + kfree(pin->prop.description); + kfree(pin->rclk_dev_name); + kfree(pin); + } +} +EXPORT_SYMBOL_GPL(dpll_pin_put); + +struct dpll_pin +*dpll_pin_alloc(u64 clock_id, u8 device_drv_id, struct module *module, + const struct dpll_pin_properties *prop) +{ + struct dpll_pin *pin; + int ret; + + pin = kzalloc(sizeof(*pin), GFP_KERNEL); + if (!pin) + return ERR_PTR(-ENOMEM); + mutex_init(&pin->lock); + pin->dev_driver_id = device_drv_id; + pin->clock_id = clock_id; + pin->module = module; + refcount_set(&pin->refcount, 0); + if (WARN_ON(pin->prop.description)) + return ERR_PTR(-EINVAL); + pin->prop.description = kstrdup(pin->prop.description, GFP_KERNEL); + if (!pin->prop.description) + return ERR_PTR(-ENOMEM); + if (WARN_ON(pin->prop.type <= DPLL_PIN_TYPE_UNSPEC || + pin->prop.type > DPLL_PIN_TYPE_MAX)) + return ERR_PTR(-EINVAL); + pin->prop.type = pin->prop.type; + pin->prop.caps_supported = pin->prop.caps_supported; + pin->prop.freq_supported = pin->prop.freq_supported; + pin->prop.any_freq_min = pin->prop.any_freq_min; + pin->prop.any_freq_max = pin->prop.any_freq_max; + xa_init_flags(&pin->pin_refs, XA_FLAGS_ALLOC); + xa_init_flags(&pin->dpll_refs, XA_FLAGS_ALLOC); + ret = xa_alloc(&dpll_pin_xa, &pin->idx, pin, + xa_limit_16b, GFP_KERNEL); + if (ret) { + dpll_pin_put(pin); + return ERR_PTR(ret); + } + + return pin; +} + +struct dpll_pin +*dpll_pin_get(u64 clock_id, u32 device_drv_id, struct module *module, + const struct dpll_pin_properties *prop) +{ + struct dpll_pin *pos, *ret = NULL; + unsigned long index; + + mutex_lock(&dpll_pin_xa_lock); + xa_for_each(&dpll_pin_xa, index, pos) { + if (pos->clock_id == clock_id && + pos->dev_driver_id == device_drv_id && + pos->module == module) { + ret = pos; + break; + } + } + if (!ret) + ret = dpll_pin_alloc(clock_id, device_drv_id, module, prop); + mutex_unlock(&dpll_pin_xa_lock); + if (!IS_ERR_OR_NULL(ret)) + refcount_inc(&ret->refcount); + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_pin_get); + +static int dpll_xa_pin_add(struct xarray *pins, struct dpll_pin *pin) +{ + struct dpll_pin *pos; + unsigned long index; + u32 idx; + + xa_for_each(pins, index, pos) { + if (WARN_ON(pos == pin || + !strcmp(pos->prop.description, + pin->prop.description)) || + pos->dev_driver_id == pin->dev_driver_id) + return -EEXIST; + } + + return xa_alloc(pins, &idx, pin, xa_limit_16b, GFP_KERNEL); +} + +static int dpll_xa_pin_del(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_on_dpll_register(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv, + struct device *rclk_device) +{ + int ret; + + if (WARN_ON(!dpll)) + return -ENODEV; + if (WARN_ON(!pin)) + return -EINVAL; + if (rclk_device) { + pin->rclk_dev_name = kstrdup(dev_name(rclk_device), GFP_KERNEL); + if (!pin->rclk_dev_name) + return -ENOMEM; + } + mutex_lock(&dpll->lock); + ret = dpll_pin_ref_dpll_add(pin, dpll, ops, priv); + if (ret) + goto rclk_free; + ret = dpll_xa_pin_add(&dpll->pins, pin); + if (ret) { + dpll_pin_ref_dpll_del(pin, dpll); + goto rclk_free; + } else { + refcount_inc(&pin->refcount); + xa_set_mark(&dpll_pin_xa, pin->idx, DPLL_PIN_REGISTERED); + dpll_pin_notify(dpll, pin, DPLL_A_PIN_IDX); + } + mutex_unlock(&dpll->lock); + + return ret; +rclk_free: + kfree(pin->rclk_dev_name); + mutex_lock(&dpll->lock); + return ret; +} +EXPORT_SYMBOL_GPL(dpll_pin_on_dpll_register); + +static int dpll_pin_ref_pin_add(struct dpll_pin *pin, struct dpll_pin *parent, + struct dpll_pin_ops *ops, void *priv) +{ + struct dpll_pin_ref *ref, *pos; + unsigned long index; + u32 idx; + int ret; + + ref = kzalloc(sizeof(struct dpll_pin_ref), GFP_KERNEL); + if (!ref) + return -ENOMEM; + ref->pin = parent; + ref->ops = ops; + ref->priv = priv; + if (!xa_empty(&pin->pin_refs)) { + xa_for_each(&pin->pin_refs, index, pos) { + if (pos->pin == ref->pin) + return -EEXIST; + } + } + + ret = xa_alloc(&pin->pin_refs, &idx, ref, xa_limit_16b, GFP_KERNEL); + if (!ret) + refcount_inc(&pin->refcount); + + return ret; +} + +int +dpll_pin_on_pin_register(struct dpll_pin *parent, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv, + struct device *rclk_device) +{ + int ret; + + if (WARN_ON(!pin || !parent)) + return -EINVAL; + if (WARN_ON(parent->prop.type != DPLL_PIN_TYPE_MUX)) + return -EPERM; + + mutex_lock(&pin->lock); + ret = dpll_pin_ref_pin_add(pin, parent, ops, priv); + mutex_unlock(&pin->lock); + if (!ret) { + struct dpll_pin_ref *ref; + unsigned long index; + + xa_for_each(&parent->dpll_refs, index, ref) { + dpll_pin_parent_notify(ref->dpll, pin, parent, + DPLL_A_PIN_IDX); + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_pin_on_pin_register); + +struct dpll_pin *dpll_pin_get_by_idx_from_xa(struct xarray *xa_pins, u32 idx) +{ + struct dpll_pin *pos; + unsigned long index; + + xa_for_each_marked(xa_pins, index, pos, DPLL_PIN_REGISTERED) { + if (pos->idx == idx) + return pos; + } + + return NULL; +} + +/** + * 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, u32 idx) +{ + return dpll_pin_get_by_idx_from_xa(&dpll->pins, idx); +} + +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 = dpll_xa_pin_del(&dpll->pins, pin); + if (!ret) + dpll_pin_ref_dpll_del(pin, dpll); + mutex_unlock(&dpll->lock); + if (!ret) + dpll_pin_notify(dpll, pin, DPLL_A_PIN_IDX); + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_pin_deregister); + +struct dpll_pin_ref +*dpll_pin_find_dpll_ref(const struct dpll_device *dpll, + const struct dpll_pin *pin) +{ + struct dpll_pin_ref *ref; + unsigned long index; + + xa_for_each((struct xarray *)&pin->dpll_refs, index, ref) { + if (ref->dpll != dpll) + continue; + else + return ref; + } + + return NULL; +} + +struct dpll_pin_ref +*dpll_pin_find_pin_ref(const struct dpll_pin *parent, + const struct dpll_pin *pin) +{ + struct dpll_pin_ref *ref; + unsigned long index; + + xa_for_each((struct xarray *)&pin->pin_refs, index, ref) { + if (ref->pin != parent) + continue; + else + return ref; + } + + return NULL; +} + +void *dpll_priv(const struct dpll_device *dpll) +{ + return dpll->priv; +} +EXPORT_SYMBOL_GPL(dpll_priv); + +void *dpll_pin_priv(const struct dpll_device *dpll, const struct dpll_pin *pin) +{ + struct dpll_pin_ref *ref = dpll_pin_find_dpll_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..767173fd90bf --- /dev/null +++ b/drivers/dpll/dpll_core.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates + */ + +#ifndef __DPLL_CORE_H__ +#define __DPLL_CORE_H__ + +#include +#include +#include "dpll_netlink.h" + +#define DPLL_REGISTERED XA_MARK_1 +#define DPLL_PIN_REGISTERED XA_MARK_1 + +/** + * 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 + * @module: module of creator + * @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 + * @clock_id: unique identifier (clock_id) of a dpll + * @dev_driver_idx: provided by driver for + * @mode_supported_mask: mask of supported modes + * @refcount: refcount + **/ +struct dpll_device { + u32 id; + u32 dev_driver_id; + struct device dev; + struct device *parent; + struct module *module; + struct dpll_device_ops *ops; + struct mutex lock; + enum dpll_type type; + void *priv; + struct xarray pins; + u64 clock_id; + unsigned long mode_supported_mask; + refcount_t refcount; +}; + +/** + * struct dpll_pin - structure for a dpll pin + * @idx: unique idx given by alloc on global pin's XA + * @dev_driver_id: id given by dev driver + * @clock_id: clock_id of creator + * @type: type of the pin + * @module: module of creator + * @dpll_refs: hold referencees to dplls that pin is registered with + * @pin_refs: hold references to pins that pin is registered with + * @prop: properties given by registerer + * @rclk_dev_name: holds name of device when pin can recover clock from it + * @refcount: refcount + * @lock: access lock + **/ +struct dpll_pin { + u32 idx; + u32 dev_driver_id; + u64 clock_id; + struct module *module; + struct xarray dpll_refs; + struct xarray pin_refs; + struct dpll_pin_properties prop; + char *rclk_dev_name; + refcount_t refcount; + struct mutex lock; +}; + +struct dpll_pin_ref { + union { + struct dpll_device *dpll; + struct dpll_pin *pin; + }; + struct dpll_pin_ops *ops; + void *priv; +}; + +struct dpll_device *dpll_device_get_by_id(int id); +struct dpll_device *dpll_device_get_by_name(const char *bus_name, + const char *dev_name); +void dpll_device_unregister(struct dpll_device *dpll); +struct dpll_pin *dpll_pin_get_by_idx(struct dpll_device *dpll, u32 idx); +struct dpll_pin_ref +*dpll_pin_find_dpll_ref(const struct dpll_device *dpll, + const struct dpll_pin *pin); +struct dpll_pin_ref +*dpll_pin_find_pin_ref(const struct dpll_pin *parent, + const struct dpll_pin *pin); + +extern struct xarray dpll_device_xa; +extern struct xarray dpll_pin_xa; +extern struct mutex dpll_device_xa_lock; +extern struct mutex dpll_pin_xa_lock; +#endif diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c new file mode 100644 index 000000000000..3c88c3c6d584 --- /dev/null +++ b/drivers/dpll/dpll_netlink.c @@ -0,0 +1,898 @@ +// 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 "dpll_nl.h" +#include + +static int +dpll_msg_add_dev_handle(struct sk_buff *msg, const struct dpll_device *dpll) +{ + if (nla_put_u32(msg, DPLL_A_ID, dpll->id)) + return -EMSGSIZE; + if (nla_put_string(msg, DPLL_A_BUS_NAME, dev_bus_name(&dpll->dev))) + return -EMSGSIZE; + if (nla_put_string(msg, DPLL_A_DEV_NAME, dev_name(&dpll->dev))) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_mode(struct sk_buff *msg, const struct dpll_device *dpll, + struct netlink_ext_ack *extack) +{ + enum dpll_mode mode; + + if (WARN_ON(!dpll->ops->mode_get)) + return -EOPNOTSUPP; + if (dpll->ops->mode_get(dpll, &mode, extack)) + return -EFAULT; + if (nla_put_u8(msg, DPLL_A_MODE, mode)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_source_pin_idx(struct sk_buff *msg, struct dpll_device *dpll, + struct netlink_ext_ack *extack) +{ + u32 source_pin_idx; + + if (WARN_ON(!dpll->ops->source_pin_idx_get)) + return -EOPNOTSUPP; + if (dpll->ops->source_pin_idx_get(dpll, &source_pin_idx, extack)) + return -EFAULT; + if (nla_put_u32(msg, DPLL_A_SOURCE_PIN_IDX, source_pin_idx)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_lock_status(struct sk_buff *msg, struct dpll_device *dpll, + struct netlink_ext_ack *extack) +{ + enum dpll_lock_status status; + + if (WARN_ON(!dpll->ops->lock_status_get)) + return -EOPNOTSUPP; + if (dpll->ops->lock_status_get(dpll, &status, extack)) + return -EFAULT; + if (nla_put_u8(msg, DPLL_A_LOCK_STATUS, status)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_temp(struct sk_buff *msg, struct dpll_device *dpll, + struct netlink_ext_ack *extack) +{ + s32 temp; + + if (!dpll->ops->temp_get) + return -EOPNOTSUPP; + if (dpll->ops->temp_get(dpll, &temp, extack)) + return -EFAULT; + if (nla_put_s32(msg, DPLL_A_TEMP, temp)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_pin_prio(struct sk_buff *msg, const struct dpll_device *dpll, + const struct dpll_pin *pin, struct dpll_pin_ops *ops, + struct netlink_ext_ack *extack) +{ + u32 prio; + + if (!ops->prio_get) + return -EOPNOTSUPP; + if (ops->prio_get(pin, dpll, &prio, extack)) + return -EFAULT; + if (nla_put_u8(msg, DPLL_A_PIN_PRIO, prio)) + return -EMSGSIZE; + + return 0; +} + +static u32 dpll_pin_freq_value[] = { + [DPLL_PIN_FREQ_SUPP_1_HZ] = DPLL_PIN_FREQ_1_HZ, + [DPLL_PIN_FREQ_SUPP_10_MHZ] = DPLL_PIN_FREQ_10_MHZ, +}; + +static int +dpll_msg_add_pin_freq(struct sk_buff *msg, const struct dpll_pin *pin, + struct netlink_ext_ack *extack, bool dump_any_freq) +{ + enum dpll_pin_freq_supp fs; + struct dpll_pin_ref *ref; + unsigned long i; + u32 freq; + + xa_for_each((struct xarray *)&pin->dpll_refs, i, ref) { + if (ref && ref->ops && ref->dpll) + break; + } + + if (!ref->ops->frequency_get) + return -EOPNOTSUPP; + if (ref->ops->frequency_get(pin, ref->dpll, &freq, extack)) + return -EFAULT; + if (nla_put_u32(msg, DPLL_A_PIN_FREQUENCY, freq)) + return -EMSGSIZE; + if (!dump_any_freq) + return 0; + + for (fs = DPLL_PIN_FREQ_SUPP_UNSPEC; fs < DPLL_PIN_FREQ_SUPP_MAX; fs++) + if (test_bit(fs, &pin->prop.freq_supported)) + if (nla_put_u32(msg, DPLL_A_PIN_FREQUENCY_SUPPORTED, + dpll_pin_freq_value[fs])) + return -EMSGSIZE; + if (pin->prop.any_freq_min && pin->prop.any_freq_max) { + if (nla_put_u32(msg, DPLL_A_PIN_ANY_FREQUENCY_MIN, + pin->prop.any_freq_min)) + return -EMSGSIZE; + if (nla_put_u32(msg, DPLL_A_PIN_ANY_FREQUENCY_MAX, + pin->prop.any_freq_max)) + return -EMSGSIZE; + } + + return 0; +} + +static int +dpll_msg_add_pin_parents(struct sk_buff *msg, struct dpll_pin *pin, + struct netlink_ext_ack *extack) +{ + struct dpll_pin_ref *ref = NULL; + enum dpll_pin_state state; + struct nlattr *nest; + unsigned long index; + int ret; + + xa_for_each(&pin->pin_refs, index, ref) { + if (WARN_ON(!ref->ops->state_on_pin_get)) + return -EFAULT; + ret = ref->ops->state_on_pin_get(pin, ref->pin, &state, + extack); + if (ret) + return -EFAULT; + nest = nla_nest_start(msg, DPLL_A_PIN_PARENT); + if (!nest) + return -EMSGSIZE; + if (nla_put_u32(msg, DPLL_A_PIN_IDX, ref->pin->dev_driver_id)) { + ret = -EMSGSIZE; + goto nest_cancel; + } + if (nla_put_u8(msg, DPLL_A_PIN_STATE, state)) { + ret = -EMSGSIZE; + goto nest_cancel; + } + nla_nest_end(msg, nest); + } + + return 0; + +nest_cancel: + nla_nest_cancel(msg, nest); + return ret; +} + +static int +dpll_msg_add_pin_dplls(struct sk_buff *msg, struct dpll_pin *pin, + struct netlink_ext_ack *extack) +{ + enum dpll_pin_state state; + struct dpll_pin_ref *ref; + struct nlattr *attr; + unsigned long index; + int ret; + + xa_for_each(&pin->dpll_refs, index, ref) { + struct dpll_device *dpll = ref->dpll; + struct dpll_pin_ops *ops = ref->ops; + + if (WARN_ON(!ref->ops->state_on_dpll_get)) + return -EFAULT; + ret = ops->state_on_dpll_get(pin, dpll, &state, extack); + if (ret) + return -EFAULT; + attr = nla_nest_start(msg, DPLL_A_DPLL); + if (!attr) + return -EMSGSIZE; + ret = dpll_msg_add_dev_handle(msg, dpll); + if (ret) + goto nest_cancel; + if (nla_put_u8(msg, DPLL_A_PIN_STATE, state)) { + ret = -EMSGSIZE; + goto nest_cancel; + } + ret = dpll_msg_add_pin_prio(msg, dpll, pin, ops, extack); + if (ret && ret != -EOPNOTSUPP) + goto nest_cancel; + nla_nest_end(msg, attr); + } + + return 0; + +nest_cancel: + nla_nest_end(msg, attr); + return ret; +} + +static int +__dpll_cmd_pin_dump_one(struct sk_buff *msg, struct dpll_pin *pin, + struct netlink_ext_ack *extack) +{ + int ret; + + if (nla_put_u32(msg, DPLL_A_PIN_IDX, pin->dev_driver_id)) + return -EMSGSIZE; + if (nla_put_string(msg, DPLL_A_PIN_DESCRIPTION, pin->prop.description)) + return -EMSGSIZE; + if (nla_put_s32(msg, DPLL_A_PIN_TYPE, pin->prop.type)) + return -EMSGSIZE; + ret = dpll_msg_add_pin_freq(msg, pin, extack, true); + if (ret) + return ret; + if (!xa_empty(&pin->pin_refs)) { + ret = dpll_msg_add_pin_parents(msg, pin, extack); + if (ret) + return ret; + } + if (!xa_empty(&pin->dpll_refs)) { + ret = dpll_msg_add_pin_dplls(msg, pin, extack); + if (ret) + return ret; + } + if (pin->rclk_dev_name) + if (nla_put_string(msg, DPLL_A_PIN_RCLK_DEVICE, + pin->rclk_dev_name)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_device_dump_one(struct dpll_device *dpll, struct sk_buff *msg, + struct netlink_ext_ack *extack) +{ + enum dpll_mode mode; + int ret; + + ret = dpll_msg_add_dev_handle(msg, dpll); + return ret; + ret = dpll_msg_add_source_pin_idx(msg, dpll, extack); + if (ret) + return ret; + ret = dpll_msg_add_temp(msg, dpll, extack); + if (ret && ret != -EOPNOTSUPP) + return ret; + ret = dpll_msg_add_lock_status(msg, dpll, extack); + if (ret) + return ret; + ret = dpll_msg_add_mode(msg, dpll, extack); + if (ret) + return ret; + for (mode = DPLL_MODE_UNSPEC + 1; mode <= DPLL_MODE_MAX; mode++) + if (test_bit(mode, &dpll->mode_supported_mask)) + if (nla_put_s32(msg, DPLL_A_MODE_SUPPORTED, mode)) + return -EMSGSIZE; + if (nla_put_64bit(msg, DPLL_A_CLOCK_ID, sizeof(dpll->clock_id), + &dpll->clock_id, 0)) + return -EMSGSIZE; + if (nla_put_s32(msg, DPLL_A_TYPE, dpll->type)) + return -EMSGSIZE; + + return 0; +} + +static bool dpll_pin_is_freq_supported(struct dpll_pin *pin, u32 freq) +{ + enum dpll_pin_freq_supp fs; + + if (freq >= pin->prop.any_freq_min && freq <= pin->prop.any_freq_max) + return true; + for (fs = DPLL_PIN_FREQ_SUPP_UNSPEC; fs < DPLL_PIN_FREQ_SUPP_MAX; fs++) + if (test_bit(fs, &pin->prop.freq_supported)) + if (freq == dpll_pin_freq_value[fs]) + return true; + return false; +} + +static int +dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, + struct netlink_ext_ack *extack) +{ + u32 freq = nla_get_u32(a); + struct dpll_pin_ref *ref; + unsigned long i; + int ret; + + if (!dpll_pin_is_freq_supported(pin, freq)) + return -EINVAL; + + xa_for_each(&pin->dpll_refs, i, ref) { + ret = ref->ops->frequency_set(pin, ref->dpll, freq, extack); + if (ret) + return -EFAULT; + dpll_pin_notify(ref->dpll, pin, DPLL_A_PIN_FREQUENCY); + } + + return 0; +} + +static int +dpll_pin_parent_state_set(struct dpll_device *dpll, struct dpll_pin *pin, + struct nlattr *nested, struct netlink_ext_ack *extack) +{ + enum dpll_pin_state state; + struct dpll_pin_ref *ref; + struct dpll_pin *parent; + struct nlattr *a; + unsigned long i; + u32 parent_idx; + int ret, rem; + + if (!test_bit(DPLL_PIN_CAPS_STATE_CAN_CHANGE, &pin->prop.capabilities)) + return -EOPNOTSUPP; + nla_for_each_nested(a, nested, rem) { + switch (nla_type(a)) { + case DPLL_A_PIN_STATE: + state = nla_get_u8(a); + break; + case DPLL_A_PIN_PARENT_IDX: + parent_idx = nla_get_u32(a); + break; + default: + break; + } + } + xa_for_each(&pin->pin_refs, i, parent) { + if (parent->dev_driver_id == parent_idx) + break; + } + if (!parent) + return -EINVAL; + ref = dpll_pin_find_pin_ref(parent, pin); + if (!ref) + return -EINVAL; + ret = ref->ops->state_on_pin_set(pin, parent, state, extack); + if (ret) + return -EFAULT; + dpll_pin_parent_notify(dpll, pin, parent, DPLL_A_PIN_STATE); + + return 0; +} + +static int +dpll_pin_dpll_set(struct dpll_pin *pin, struct nlattr *nested, + struct netlink_ext_ack *extack) +{ + struct nlattr *a, *dev_attr = NULL, *bus_attr = NULL; + bool state_change = false, prio_change = false; + enum dpll_pin_state state; + struct dpll_pin_ref *ref; + unsigned long i; + int rem; + u8 prio; + + nla_for_each_nested(a, nested, rem) { + switch (nla_type(a)) { + case DPLL_A_DEV_NAME: + dev_attr = a; + break; + case DPLL_A_BUS_NAME: + bus_attr = a; + break; + case DPLL_A_PIN_STATE: + state = nla_get_u8(a); + state_change = true; + break; + case DPLL_A_PIN_PRIO: + prio = nla_get_u8(a); + prio_change = true; + break; + default: + break; + } + } + xa_for_each(&pin->dpll_refs, i, ref) { + if (!nla_strcmp(bus_attr, dev_bus_name(&ref->dpll->dev)) && + !nla_strcmp(dev_attr, dev_name(&ref->dpll->dev))) + break; + } + if (!ref) + return -EINVAL; + + if (state_change) { + if (!test_bit(DPLL_PIN_CAPS_STATE_CAN_CHANGE, + &pin->prop.capabilities)) + return -EOPNOTSUPP; + if (!ref->ops || !ref->ops->state_on_dpll_set) + return -EOPNOTSUPP; + if (ref->ops->state_on_dpll_set(pin, ref->dpll, state, extack)) + return -EINVAL; + dpll_pin_notify(ref->dpll, pin, DPLL_A_PIN_STATE); + } + + if (prio_change) { + if (!test_bit(DPLL_PIN_CAPS_PRIORITY_CAN_CHANGE, + &pin->prop.capabilities)) + return -EOPNOTSUPP; + if (!ref->ops || !ref->ops->prio_set) + return -EOPNOTSUPP; + if (ref->ops->prio_set(pin, ref->dpll, state, extack)) + return -EINVAL; + dpll_pin_notify(ref->dpll, pin, DPLL_A_PIN_PRIO); + } + + return 0; +} + +static int +dpll_pin_direction_set(struct dpll_pin *pin, struct nlattr *a, + struct netlink_ext_ack *extack) +{ + enum dpll_pin_direction direction = nla_get_u8(a); + struct dpll_pin_ref *ref; + unsigned long i; + + if (!test_bit(DPLL_PIN_CAPS_DIRECTION_CAN_CHANGE, + &pin->prop.capabilities)) + return -EINVAL; + + xa_for_each(&pin->dpll_refs, i, ref) { + if (ref->ops->direction_set(pin, ref->dpll, direction, extack)) + return -EFAULT; + dpll_pin_notify(ref->dpll, pin, DPLL_A_PIN_DIRECTION); + } + + return 0; +} + +static int +dpll_pin_set_from_nlattr(struct dpll_device *dpll, + struct dpll_pin *pin, struct genl_info *info) +{ + int rem, ret = -EINVAL; + struct nlattr *a; + + nla_for_each_attr(a, genlmsg_data(info->genlhdr), + genlmsg_len(info->genlhdr), rem) { + switch (nla_type(a)) { + case DPLL_A_PIN_FREQUENCY: + ret = dpll_pin_freq_set(pin, a, info->extack); + if (ret) + return ret; + break; + case DPLL_A_PIN_PARENT: + ret = dpll_pin_parent_state_set(dpll, pin, a, + info->extack); + if (ret) + return ret; + break; + case DPLL_A_DPLL: + ret = dpll_pin_dpll_set(pin, a, info->extack); + if (ret) + return ret; + break; + case DPLL_A_PIN_DIRECTION: + ret = dpll_pin_direction_set(pin, a, info->extack); + if (ret) + return ret; + break; + default: + break; + } + } + + return ret; +} + +int dpll_nl_pin_set_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct dpll_device *dpll = info->user_ptr[0]; + struct dpll_pin *pin = info->user_ptr[1]; + + return dpll_pin_set_from_nlattr(dpll, pin, info); +} + +int dpll_nl_pin_get_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct dpll_pin *pin = info->user_ptr[1]; + struct nlattr *hdr, *nest; + struct sk_buff *msg; + int ret; + + if (!pin) + return -ENODEV; + msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + hdr = genlmsg_put_reply(msg, info, &dpll_nl_family, 0, + DPLL_CMD_DEVICE_GET); + if (!hdr) + return -EMSGSIZE; + nest = nla_nest_start(msg, DPLL_A_PIN); + if (!nest) + return -EMSGSIZE; + ret = __dpll_cmd_pin_dump_one(msg, pin, info->extack); + if (ret) { + nlmsg_free(msg); + return ret; + } + nla_nest_end(msg, nest); + genlmsg_end(msg, hdr); + + return genlmsg_reply(msg, info); +} + +int dpll_nl_pin_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb) +{ + struct nlattr *hdr, *nest; + struct dpll_pin *pin; + unsigned long i; + int ret; + + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, + &dpll_nl_family, 0, DPLL_CMD_DEVICE_GET); + if (!hdr) + return -EMSGSIZE; + + xa_for_each_marked(&dpll_pin_xa, i, pin, DPLL_PIN_REGISTERED) { + nest = nla_nest_start(skb, DPLL_A_PIN); + if (!nest) { + ret = -EMSGSIZE; + break; + } + ret = __dpll_cmd_pin_dump_one(skb, pin, cb->extack); + if (ret) { + nla_nest_cancel(skb, nest); + break; + } + nla_nest_end(skb, nest); + } + + if (ret) + genlmsg_cancel(skb, hdr); + else + genlmsg_end(skb, hdr); + + return ret; +} + +static int +dpll_set_from_nlattr(struct dpll_device *dpll, struct genl_info *info) +{ + struct nlattr *attr; + int rem, ret = 0; + + nla_for_each_attr(attr, genlmsg_data(info->genlhdr), + genlmsg_len(info->genlhdr), rem) { + switch (nla_type(attr)) { + case DPLL_A_MODE: + enum dpll_mode mode = nla_get_u8(attr); + + if (!dpll->ops || !dpll->ops->mode_set) + return -EOPNOTSUPP; + ret = dpll->ops->mode_set(dpll, mode, info->extack); + if (ret) + return ret; + break; + default: + break; + } + } + + return ret; +} + +int dpll_nl_device_set_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct dpll_device *dpll = info->user_ptr[0]; + + return dpll_set_from_nlattr(dpll, info); +} + +int dpll_nl_device_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb) +{ + struct nlattr *hdr, *nest; + struct dpll_device *dpll; + unsigned long i; + int ret; + + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, + &dpll_nl_family, 0, DPLL_CMD_DEVICE_GET); + if (!hdr) + return -EMSGSIZE; + + mutex_lock(&dpll_device_xa_lock); + xa_for_each(&dpll_device_xa, i, dpll) { + nest = nla_nest_start(skb, DPLL_A_DPLL); + mutex_lock(&dpll->lock); + ret = dpll_device_dump_one(dpll, skb, cb->extack); + mutex_unlock(&dpll->lock); + if (ret) { + nla_nest_cancel(skb, nest); + break; + } + nla_nest_end(skb, nest); + } + mutex_unlock(&dpll_device_xa_lock); + if (ret) + genlmsg_cancel(skb, hdr); + else + genlmsg_end(skb, hdr); + + return ret; +} + +int dpll_nl_device_get_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct dpll_device *dpll = info->user_ptr[0]; + struct nlattr *hdr, *nest; + struct sk_buff *msg; + int ret; + + msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + hdr = genlmsg_put_reply(msg, info, &dpll_nl_family, 0, + DPLL_CMD_DEVICE_GET); + if (!hdr) + return -EMSGSIZE; + + nest = nla_nest_start(msg, DPLL_A_DPLL); + mutex_lock(&dpll->lock); + ret = dpll_device_dump_one(dpll, msg, info->extack); + mutex_unlock(&dpll->lock); + if (ret) { + nlmsg_free(msg); + return ret; + } + nla_nest_end(msg, nest); + genlmsg_end(msg, hdr); + + return genlmsg_reply(msg, info); +} + +int dpll_pin_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info) +{ + int ret = dpll_pre_doit(ops, skb, info); + struct dpll_device *dpll; + struct dpll_pin *pin; + + if (ret) + return ret; + dpll = info->user_ptr[0]; + if (!info->attrs[DPLL_A_PIN_IDX]) + return -EINVAL; + pin = dpll_pin_get_by_idx(dpll, + nla_get_u32(info->attrs[DPLL_A_PIN_IDX])); + if (!pin) + return -EINVAL; + info->user_ptr[1] = pin; + + return 0; +} + +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[DPLL_A_ID] && + !(info->attrs[DPLL_A_BUS_NAME] && info->attrs[DPLL_A_DEV_NAME])) + return -EINVAL; + + if (info->attrs[DPLL_A_ID]) { + u32 id = nla_get_u32(info->attrs[DPLL_A_ID]); + + dpll_id = dpll_device_get_by_id(id); + if (!dpll_id) + return -ENODEV; + info->user_ptr[0] = dpll_id; + } + if (info->attrs[DPLL_A_BUS_NAME] && + info->attrs[DPLL_A_DEV_NAME]) { + const char *bus_name = nla_data(info->attrs[DPLL_A_BUS_NAME]); + const char *dev_name = nla_data(info->attrs[DPLL_A_DEV_NAME]); + + dpll_name = dpll_device_get_by_name(bus_name, dev_name); + if (!dpll_name) + return -ENODEV; + + if (dpll_id && dpll_name != dpll_id) + return -EINVAL; + info->user_ptr[0] = dpll_name; + } + + return 0; +} + +static int +dpll_event_device_change(struct sk_buff *msg, struct dpll_device *dpll, + struct dpll_pin *pin, struct dpll_pin *parent, + enum dplla attr) +{ + int ret = dpll_msg_add_dev_handle(msg, dpll); + struct dpll_pin_ref *ref = NULL; + + if (ret) + return ret; + if (pin && nla_put_u32(msg, DPLL_A_PIN_IDX, pin->dev_driver_id)) + return -EMSGSIZE; + + switch (attr) { + case DPLL_A_MODE: + ret = dpll_msg_add_mode(msg, dpll, NULL); + break; + case DPLL_A_SOURCE_PIN_IDX: + ret = dpll_msg_add_source_pin_idx(msg, dpll, NULL); + break; + case DPLL_A_LOCK_STATUS: + ret = dpll_msg_add_lock_status(msg, dpll, NULL); + break; + case DPLL_A_TEMP: + ret = dpll_msg_add_temp(msg, dpll, NULL); + break; + case DPLL_A_PIN_FREQUENCY: + ret = dpll_msg_add_pin_freq(msg, pin, NULL, false); + break; + case DPLL_A_PIN_PRIO: + ref = dpll_pin_find_dpll_ref(dpll, pin); + if (!ref) + return -EFAULT; + ret = dpll_msg_add_pin_prio(msg, dpll, pin, ref->ops, NULL); + break; + case DPLL_A_PIN_STATE: + enum dpll_pin_state state; + + if (parent) { + ref = dpll_pin_find_pin_ref(parent, pin); + if (!ref || !ref->ops || !ref->ops->state_on_pin_get) + return -EOPNOTSUPP; + ret = ref->ops->state_on_pin_get(pin, parent, &state, + NULL); + if (ret) + return ret; + if (nla_put_u32(msg, DPLL_A_PIN_PARENT_IDX, + parent->dev_driver_id)) + return -EMSGSIZE; + } else { + ref = dpll_pin_find_dpll_ref(dpll, pin); + if (!ref || !ref->ops || !ref->ops->state_on_dpll_get) + return -EOPNOTSUPP; + ret = ref->ops->state_on_dpll_get(pin, dpll, &state, + NULL); + if (ret) + return ret; + } + if (nla_put_u8(msg, DPLL_A_PIN_STATE, state)) + return -EMSGSIZE; + break; + default: + break; + } + + return ret; +} + +static int +dpll_send_event_create(enum dpll_event event, struct dpll_device *dpll) +{ + struct sk_buff *msg; + int ret = -EMSGSIZE; + void *hdr; + + msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, 0, 0, &dpll_nl_family, 0, event); + if (!hdr) + goto out_free_msg; + + ret = dpll_msg_add_dev_handle(msg, dpll); + if (ret) + goto out_cancel_msg; + genlmsg_end(msg, hdr); + genlmsg_multicast(&dpll_nl_family, msg, 0, 0, GFP_KERNEL); + + return 0; + +out_cancel_msg: + genlmsg_cancel(msg, hdr); +out_free_msg: + nlmsg_free(msg); + + return ret; +} + +static int +dpll_send_event_change(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin *parent, enum dplla attr) +{ + struct sk_buff *msg; + int ret = -EMSGSIZE; + void *hdr; + + msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, 0, 0, &dpll_nl_family, 0, DPLL_EVENT_DEVICE_CHANGE); + if (!hdr) + goto out_free_msg; + + ret = dpll_event_device_change(msg, dpll, pin, parent, attr); + if (ret) + goto out_cancel_msg; + genlmsg_end(msg, hdr); + genlmsg_multicast(&dpll_nl_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) +{ + return dpll_send_event_create(DPLL_EVENT_DEVICE_CREATE, dpll); +} + +int dpll_notify_device_delete(struct dpll_device *dpll) +{ + return dpll_send_event_create(DPLL_EVENT_DEVICE_DELETE, dpll); +} + +int dpll_device_notify(struct dpll_device *dpll, enum dplla attr) +{ + return dpll_send_event_change(dpll, NULL, NULL, attr); +} +EXPORT_SYMBOL_GPL(dpll_device_notify); + +int dpll_pin_notify(struct dpll_device *dpll, struct dpll_pin *pin, + enum dplla attr) +{ + return dpll_send_event_change(dpll, pin, NULL, attr); +} + +int dpll_pin_parent_notify(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin *parent, enum dplla attr) +{ + return dpll_send_event_change(dpll, pin, parent, attr); +} + +int __init dpll_netlink_init(void) +{ + return genl_register_family(&dpll_nl_family); +} + +void dpll_netlink_finish(void) +{ + genl_unregister_family(&dpll_nl_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..b78a6ba93071 --- /dev/null +++ b/drivers/dpll/dpll_netlink.h @@ -0,0 +1,31 @@ +/* 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 dpll_pin_notify(struct dpll_device *dpll, struct dpll_pin *pin, + enum dplla attr); + +int dpll_pin_parent_notify(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin *parent, enum dplla attr); + + +int __init dpll_netlink_init(void); +void dpll_netlink_finish(void); diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c new file mode 100644 index 000000000000..5ce792f325fb --- /dev/null +++ b/drivers/dpll/dpll_nl.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/dpll.yaml */ +/* YNL-GEN kernel source */ + +#include +#include + +#include "dpll_nl.h" + +#include + +/* DPLL_CMD_DEVICE_GET - do */ +static const struct nla_policy dpll_device_get_do_nl_policy[DPLL_A_BUS_NAME + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, +}; + +/* DPLL_CMD_DEVICE_GET - dump */ +static const struct nla_policy dpll_device_get_dump_nl_policy[DPLL_A_BUS_NAME + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, +}; + +/* DPLL_CMD_DEVICE_SET - do */ +static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_MODE + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_MODE] = NLA_POLICY_MAX(NLA_S32, 5), +}; + +/* DPLL_CMD_PIN_GET - do */ +static const struct nla_policy dpll_pin_get_do_nl_policy[DPLL_A_PIN_IDX + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_PIN_IDX] = { .type = NLA_U32, }, +}; + +/* DPLL_CMD_PIN_GET - dump */ +static const struct nla_policy dpll_pin_get_dump_nl_policy[DPLL_A_BUS_NAME + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, +}; + +/* DPLL_CMD_PIN_SET - do */ +static const struct nla_policy dpll_pin_set_nl_policy[DPLL_A_PIN_PARENT_IDX + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_PIN_IDX] = { .type = NLA_U32, }, + [DPLL_A_PIN_FREQUENCY] = { .type = NLA_U32, }, + [DPLL_A_PIN_DIRECTION] = NLA_POLICY_MAX(NLA_U8, 2), + [DPLL_A_PIN_PRIO] = { .type = NLA_U32, }, + [DPLL_A_PIN_PARENT_IDX] = { .type = NLA_U32, }, + [DPLL_A_PIN_STATE] = NLA_POLICY_MAX(NLA_U32, 2), +}; + +/* Ops table for dpll */ +static const struct genl_split_ops dpll_nl_ops[6] = { + { + .cmd = DPLL_CMD_DEVICE_GET, + .pre_doit = dpll_pre_doit, + .doit = dpll_nl_device_get_doit, + .policy = dpll_device_get_do_nl_policy, + .maxattr = DPLL_A_BUS_NAME, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DPLL_CMD_DEVICE_GET, + .start = dpll_cmd_device_get_start, + .dumpit = dpll_nl_device_get_dumpit, + .policy = dpll_device_get_dump_nl_policy, + .maxattr = DPLL_A_BUS_NAME, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + }, + { + .cmd = DPLL_CMD_DEVICE_SET, + .pre_doit = dpll_pre_doit, + .doit = dpll_nl_device_set_doit, + .policy = dpll_device_set_nl_policy, + .maxattr = DPLL_A_MODE, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DPLL_CMD_PIN_GET, + .pre_doit = dpll_pin_pre_doit, + .doit = dpll_nl_pin_get_doit, + .policy = dpll_pin_get_do_nl_policy, + .maxattr = DPLL_A_PIN_IDX, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DPLL_CMD_PIN_GET, + .dumpit = dpll_nl_pin_get_dumpit, + .policy = dpll_pin_get_dump_nl_policy, + .maxattr = DPLL_A_BUS_NAME, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + }, + { + .cmd = DPLL_CMD_PIN_SET, + .pre_doit = dpll_pin_pre_doit, + .doit = dpll_nl_pin_set_doit, + .policy = dpll_pin_set_nl_policy, + .maxattr = DPLL_A_PIN_PARENT_IDX, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, +}; + +static const struct genl_multicast_group dpll_nl_mcgrps[] = { + [DPLL_NLGRP_MONITOR] = { "monitor", }, +}; + +struct genl_family dpll_nl_family __ro_after_init = { + .name = DPLL_FAMILY_NAME, + .version = DPLL_FAMILY_VERSION, + .netnsok = true, + .parallel_ops = true, + .module = THIS_MODULE, + .split_ops = dpll_nl_ops, + .n_split_ops = ARRAY_SIZE(dpll_nl_ops), + .mcgrps = dpll_nl_mcgrps, + .n_mcgrps = ARRAY_SIZE(dpll_nl_mcgrps), +}; diff --git a/drivers/dpll/dpll_nl.h b/drivers/dpll/dpll_nl.h new file mode 100644 index 000000000000..e7d2d977aaf4 --- /dev/null +++ b/drivers/dpll/dpll_nl.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/dpll.yaml */ +/* YNL-GEN kernel header */ + +#ifndef _LINUX_DPLL_GEN_H +#define _LINUX_DPLL_GEN_H + +#include +#include + +#include + +int dpll_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info); +int dpll_pin_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info); +int dpll_cmd_device_get_start(struct netlink_callback *cb); + +int dpll_nl_device_get_doit(struct sk_buff *skb, struct genl_info *info); +int dpll_nl_device_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb); +int dpll_nl_device_set_doit(struct sk_buff *skb, struct genl_info *info); +int dpll_nl_pin_get_doit(struct sk_buff *skb, struct genl_info *info); +int dpll_nl_pin_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb); +int dpll_nl_pin_set_doit(struct sk_buff *skb, struct genl_info *info); + +enum { + DPLL_NLGRP_MONITOR, +}; + +extern struct genl_family dpll_nl_family; + +#endif /* _LINUX_DPLL_GEN_H */ diff --git a/include/linux/dpll.h b/include/linux/dpll.h new file mode 100644 index 000000000000..fd2b590cc29f --- /dev/null +++ b/include/linux/dpll.h @@ -0,0 +1,251 @@ +/* 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 PIN_IDX_INVALID ((u32)ULONG_MAX) + +struct dpll_device_ops { + int (*mode_get)(const struct dpll_device *dpll, enum dpll_mode *mode, + struct netlink_ext_ack *extack); + int (*mode_set)(const struct dpll_device *dpll, + const enum dpll_mode mode, + struct netlink_ext_ack *extack); + bool (*mode_supported)(const struct dpll_device *dpll, + const enum dpll_mode mode, + struct netlink_ext_ack *extack); + int (*source_pin_idx_get)(const struct dpll_device *dpll, + u32 *pin_idx, + struct netlink_ext_ack *extack); + int (*lock_status_get)(const struct dpll_device *dpll, + enum dpll_lock_status *status, + struct netlink_ext_ack *extack); + int (*temp_get)(const struct dpll_device *dpll, s32 *temp, + struct netlink_ext_ack *extack); +}; + +struct dpll_pin_ops { + int (*frequency_set)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const u32 frequency, + struct netlink_ext_ack *extack); + int (*frequency_get)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + u32 *frequency, struct netlink_ext_ack *extack); + int (*direction_set)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const enum dpll_pin_direction direction, + struct netlink_ext_ack *extack); + int (*direction_get)(const struct dpll_pin *pin, + enum dpll_pin_direction *direction, + struct netlink_ext_ack *extack); + int (*state_on_pin_get)(const struct dpll_pin *pin, + const struct dpll_pin *parent_pin, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack); + int (*state_on_dpll_get)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack); + int (*state_on_pin_set)(const struct dpll_pin *pin, + const struct dpll_pin *parent_pin, + const enum dpll_pin_state state, + struct netlink_ext_ack *extack); + int (*state_on_dpll_set)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const enum dpll_pin_state state, + struct netlink_ext_ack *extack); + int (*prio_get)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + u32 *prio, struct netlink_ext_ack *extack); + int (*prio_set)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const u32 prio, struct netlink_ext_ack *extack); +}; + +struct dpll_pin_properties { + const char *description; + enum dpll_pin_type type; + unsigned long caps_supported; + unsigned long freq_supported; + u32 any_freq_min; + u32 any_freq_max; + unsigned long capabilities; +}; + +enum dpll_pin_freq_supp { + DPLL_PIN_FREQ_SUPP_UNSPEC = 0, + DPLL_PIN_FREQ_SUPP_1_HZ, + DPLL_PIN_FREQ_SUPP_10_MHZ, + + __DPLL_PIN_FREQ_SUPP_MAX, + DPLL_PIN_FREQ_SUPP_MAX = (__DPLL_PIN_FREQ_SUPP_MAX - 1) +}; + +/** + * dpll_device_get - find or create dpll_device object + * @clock_id: a system unique number for a device + * @dev_driver_idx: index of dpll device on parent device + * @module: register module + * + * Returns: + * * pointer to initialized dpll - success + * * NULL - memory allocation fail + */ +struct dpll_device +*dpll_device_get(u64 clock_id, u32 dev_driver_id, struct module *module); + +/** + * dpll_device_put - caller drops reference to the device, free resources + * @dpll: dpll device pointer + * + * If all dpll_device_get callers drops their reference, the dpll device + * resources are freed. + */ +void dpll_device_put(struct dpll_device *dpll); + +/** + * dpll_device_register - register device, make it visible in the subsystem. + * @dpll: reference previously allocated with dpll_device_get + * @type: type of dpll + * @priv: private data of registerer + * @owner: device struct of the owner + * + */ +void dpll_device_register(struct dpll_device *dpll, enum dpll_type type, + void *priv, struct device *owner); + +/** + * dpll_device_deregister - deregister registered dpll + * @dpll: pointer to dpll + * + * Unregister the dpll from the subsystem, make it unavailable for netlink + * API users. + */ +void dpll_device_deregister(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(const 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(const struct dpll_device *dpll, const struct dpll_pin *pin); + +/** + * dpll_pin_get - get reference or create new pin object + * @clock_id: a system unique number of a device + * @dev_driver_idx: index of dpll device on parent device + * @module: register module + * @pin_prop: constant properities of a pin + * + * find existing pin with given clock_id, dev_driver_idx and module, or create new + * and returen its reference. + * + * Returns: + * * pointer to initialized pin - success + * * NULL - memory allocation fail + */ +struct dpll_pin +*dpll_pin_get(u64 clock_id, u32 dev_driver_id, struct module *module, + const struct dpll_pin_properties *pin_prop); + +/** + * 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 + * @rclk_device: pointer to device struct if pin is used for recovery of a clock + * from that device + * + * 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_on_dpll_register(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv, + struct device *rclk_device); + +/** + * 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_pin_on_pin_register - register a pin to a muxed-type pin + * @parent: parent pin pointer + * @pin: pointer to allocated pin object being registered with a parent pin + * @ops: struct with pin ops callbacks + * @priv: private data pointer passed when calling callback ops + * @rclk_device: pointer to device struct if pin is used for recovery of a clock + * from that device + * + * 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, + */ +int dpll_pin_on_pin_register(struct dpll_pin *parent, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv, + struct device *rclk_device); + +/** + * dpll_device_notify - notify on dpll device change + * @dpll: dpll device pointer + * @attr: changed attribute + * + * Broadcast event to the netlink multicast registered listeners. + * + * Return: + * * 0 - success + * * negative - error + */ +int dpll_device_notify(struct dpll_device *dpll, enum dplla attr); + +#endif diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h new file mode 100644 index 000000000000..b2cf3cf25853 --- /dev/null +++ b/include/uapi/linux/dpll.h @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/dpll.yaml */ +/* YNL-GEN uapi header */ + +#ifndef _UAPI_LINUX_DPLL_H +#define _UAPI_LINUX_DPLL_H + +#define DPLL_FAMILY_NAME "dpll" +#define DPLL_FAMILY_VERSION 1 + +#define DPLL_TEMP_DIVIDER 10 +#define DPLL_PIN_FREQ_1_HZ 1 +#define DPLL_PIN_FREQ_10_MHZ 10000000 + +/** + * enum dpll_lock_status - Provides information of dpll device lock status, + * valid values for DPLL_A_LOCK_STATUS attribute + * @DPLL_LOCK_STATUS_UNSPEC: unspecified value + * @DPLL_LOCK_STATUS_UNLOCKED: dpll was not yet locked to any valid (or is in + * one of modes: DPLL_MODE_FREERUN, DPLL_MODE_NCO) + * @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 selecting 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, + DPLL_LOCK_STATUS_MAX = (__DPLL_LOCK_STATUS_MAX - 1) +}; + +/** + * enum dpll_pin_type - Enumerates types of a pin, valid values for + * DPLL_A_PIN_TYPE attribute + * @DPLL_PIN_TYPE_UNSPEC: unspecified value + * @DPLL_PIN_TYPE_MUX: aggregates another layer of 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, + DPLL_PIN_TYPE_MAX = (__DPLL_PIN_TYPE_MAX - 1) +}; + +/** + * enum dpll_pin_state - available pin modes + * @DPLL_PIN_STATE_UNSPEC: unspecified value + * @DPLL_PIN_STATE_CONNECTED: pin connected + * @DPLL_PIN_STATE_DISCONNECTED: pin disconnected + */ +enum dpll_pin_state { + DPLL_PIN_STATE_UNSPEC, + DPLL_PIN_STATE_CONNECTED, + DPLL_PIN_STATE_DISCONNECTED, + + __DPLL_PIN_STATE_MAX, + DPLL_PIN_STATE_MAX = (__DPLL_PIN_STATE_MAX - 1) +}; + +/** + * enum dpll_pin_direction - available pin direction + * @DPLL_PIN_DIRECTION_UNSPEC: unspecified value + * @DPLL_PIN_DIRECTION_SOURCE: pin used as a source of a signal + * @DPLL_PIN_DIRECTION_OUTPUT: pin used to output the signal + */ +enum dpll_pin_direction { + DPLL_PIN_DIRECTION_UNSPEC, + DPLL_PIN_DIRECTION_SOURCE, + DPLL_PIN_DIRECTION_OUTPUT, + + __DPLL_PIN_DIRECTION_MAX, + DPLL_PIN_DIRECTION_MAX = (__DPLL_PIN_DIRECTION_MAX - 1) +}; + +/** + * enum dpll_mode - working-modes a dpll can support, differentiate if and how + * dpll selects one of its sources to syntonize with it + * @DPLL_MODE_UNSPEC: unspecified value + * @DPLL_MODE_MANUAL: 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_MANUAL, + DPLL_MODE_AUTOMATIC, + DPLL_MODE_HOLDOVER, + DPLL_MODE_FREERUN, + DPLL_MODE_NCO, + + __DPLL_MODE_MAX, + DPLL_MODE_MAX = (__DPLL_MODE_MAX - 1) +}; + +/** + * enum dpll_type - type of dpll, valid values for DPLL_A_TYPE attribute + * @DPLL_TYPE_UNSPEC: unspecified value + * @DPLL_TYPE_PPS: dpll produces Pulse-Per-Second signal + * @DPLL_TYPE_EEC: dpll drives the Ethernet Equipment Clock + */ +enum dpll_type { + DPLL_TYPE_UNSPEC, + DPLL_TYPE_PPS, + DPLL_TYPE_EEC, +}; + +/** + * enum 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, reason + * is to be found with an attribute type (DPLL_A_*) received with the event + */ +enum dpll_event { + DPLL_EVENT_UNSPEC, + DPLL_EVENT_DEVICE_CREATE, + DPLL_EVENT_DEVICE_DELETE, + DPLL_EVENT_DEVICE_CHANGE, +}; + +/** + * enum dpll_pin_caps - define capabilities of a pin + */ +enum dpll_pin_caps { + DPLL_PIN_CAPS_DIRECTION_CAN_CHANGE = 1, + DPLL_PIN_CAPS_PRIORITY_CAN_CHANGE = 2, + DPLL_PIN_CAPS_STATE_CAN_CHANGE = 4, +}; + +enum dplla { + DPLL_A_DPLL = 1, + DPLL_A_ID, + DPLL_A_DEV_NAME, + DPLL_A_BUS_NAME, + DPLL_A_MODE, + DPLL_A_MODE_SUPPORTED, + DPLL_A_SOURCE_PIN_IDX, + DPLL_A_LOCK_STATUS, + DPLL_A_TEMP, + DPLL_A_CLOCK_ID, + DPLL_A_TYPE, + DPLL_A_PIN, + DPLL_A_PIN_IDX, + DPLL_A_PIN_DESCRIPTION, + DPLL_A_PIN_TYPE, + DPLL_A_PIN_DIRECTION, + DPLL_A_PIN_FREQUENCY, + DPLL_A_PIN_FREQUENCY_SUPPORTED, + DPLL_A_PIN_ANY_FREQUENCY_MIN, + DPLL_A_PIN_ANY_FREQUENCY_MAX, + DPLL_A_PIN_PRIO, + DPLL_A_PIN_STATE, + DPLL_A_PIN_PARENT, + DPLL_A_PIN_PARENT_IDX, + DPLL_A_PIN_RCLK_DEVICE, + DPLL_A_PIN_DPLL_CAPS, + + __DPLL_A_MAX, + DPLL_A_MAX = (__DPLL_A_MAX - 1) +}; + +enum { + DPLL_CMD_UNSPEC, + DPLL_CMD_DEVICE_GET, + DPLL_CMD_DEVICE_SET, + DPLL_CMD_PIN_GET, + DPLL_CMD_PIN_SET, + + __DPLL_CMD_MAX, + DPLL_CMD_MAX = (__DPLL_CMD_MAX - 1) +}; + +#define DPLL_MCGRP_MONITOR "monitor" + +#endif /* _UAPI_LINUX_DPLL_H */