7dc5b18ff7
In case of multiple kernel module instances using the same dpll device: if only one registers dpll device, then only that one can register directly connected pins with a dpll device. When unregistered parent is responsible for determining if the muxed pin can be registered with it or not, the drivers need to be loaded in serialized order to work correctly - first the driver instance which registers the direct pins needs to be loaded, then the other instances could register muxed type pins. Allow registration of a pin with a parent even if the parent was not yet registered, thus allow ability for unserialized driver instance load order. Do not WARN_ON notification for unregistered pin, which can be invoked for described case, instead just return error. Fixes:9431063ad3
("dpll: core: Add DPLL framework base functions") Fixes:9d71b54b65
("dpll: netlink: Add DPLL framework base functions") Reviewed-by: Jan Glaza <jan.glaza@intel.com> Reviewed-by: Jiri Pirko <jiri@nvidia.com> Signed-off-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com> Signed-off-by: David S. Miller <davem@davemloft.net>
847 lines
20 KiB
C
847 lines
20 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* dpll_core.c - DPLL subsystem kernel-space interface implementation.
|
|
*
|
|
* Copyright (c) 2023 Meta Platforms, Inc. and affiliates
|
|
* Copyright (c) 2023 Intel Corporation.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
|
|
#include "dpll_core.h"
|
|
#include "dpll_netlink.h"
|
|
|
|
/* Mutex lock to protect DPLL subsystem devices and pins */
|
|
DEFINE_MUTEX(dpll_lock);
|
|
|
|
DEFINE_XARRAY_FLAGS(dpll_device_xa, XA_FLAGS_ALLOC);
|
|
DEFINE_XARRAY_FLAGS(dpll_pin_xa, XA_FLAGS_ALLOC);
|
|
|
|
static u32 dpll_device_xa_id;
|
|
static u32 dpll_pin_xa_id;
|
|
|
|
#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 dpll_device_registration {
|
|
struct list_head list;
|
|
const struct dpll_device_ops *ops;
|
|
void *priv;
|
|
};
|
|
|
|
struct dpll_pin_registration {
|
|
struct list_head list;
|
|
const struct dpll_pin_ops *ops;
|
|
void *priv;
|
|
};
|
|
|
|
struct dpll_device *dpll_device_get_by_id(int id)
|
|
{
|
|
if (xa_get_mark(&dpll_device_xa, id, DPLL_REGISTERED))
|
|
return xa_load(&dpll_device_xa, id);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct dpll_pin_registration *
|
|
dpll_pin_registration_find(struct dpll_pin_ref *ref,
|
|
const struct dpll_pin_ops *ops, void *priv)
|
|
{
|
|
struct dpll_pin_registration *reg;
|
|
|
|
list_for_each_entry(reg, &ref->registration_list, list) {
|
|
if (reg->ops == ops && reg->priv == priv)
|
|
return reg;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
dpll_xa_ref_pin_add(struct xarray *xa_pins, struct dpll_pin *pin,
|
|
const struct dpll_pin_ops *ops, void *priv)
|
|
{
|
|
struct dpll_pin_registration *reg;
|
|
struct dpll_pin_ref *ref;
|
|
bool ref_exists = false;
|
|
unsigned long i;
|
|
int ret;
|
|
|
|
xa_for_each(xa_pins, i, ref) {
|
|
if (ref->pin != pin)
|
|
continue;
|
|
reg = dpll_pin_registration_find(ref, ops, priv);
|
|
if (reg) {
|
|
refcount_inc(&ref->refcount);
|
|
return 0;
|
|
}
|
|
ref_exists = true;
|
|
break;
|
|
}
|
|
|
|
if (!ref_exists) {
|
|
ref = kzalloc(sizeof(*ref), GFP_KERNEL);
|
|
if (!ref)
|
|
return -ENOMEM;
|
|
ref->pin = pin;
|
|
INIT_LIST_HEAD(&ref->registration_list);
|
|
ret = xa_insert(xa_pins, pin->pin_idx, ref, GFP_KERNEL);
|
|
if (ret) {
|
|
kfree(ref);
|
|
return ret;
|
|
}
|
|
refcount_set(&ref->refcount, 1);
|
|
}
|
|
|
|
reg = kzalloc(sizeof(*reg), GFP_KERNEL);
|
|
if (!reg) {
|
|
if (!ref_exists) {
|
|
xa_erase(xa_pins, pin->pin_idx);
|
|
kfree(ref);
|
|
}
|
|
return -ENOMEM;
|
|
}
|
|
reg->ops = ops;
|
|
reg->priv = priv;
|
|
if (ref_exists)
|
|
refcount_inc(&ref->refcount);
|
|
list_add_tail(®->list, &ref->registration_list);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dpll_xa_ref_pin_del(struct xarray *xa_pins, struct dpll_pin *pin,
|
|
const struct dpll_pin_ops *ops, void *priv)
|
|
{
|
|
struct dpll_pin_registration *reg;
|
|
struct dpll_pin_ref *ref;
|
|
unsigned long i;
|
|
|
|
xa_for_each(xa_pins, i, ref) {
|
|
if (ref->pin != pin)
|
|
continue;
|
|
reg = dpll_pin_registration_find(ref, ops, priv);
|
|
if (WARN_ON(!reg))
|
|
return -EINVAL;
|
|
if (refcount_dec_and_test(&ref->refcount)) {
|
|
list_del(®->list);
|
|
kfree(reg);
|
|
xa_erase(xa_pins, i);
|
|
WARN_ON(!list_empty(&ref->registration_list));
|
|
kfree(ref);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int
|
|
dpll_xa_ref_dpll_add(struct xarray *xa_dplls, struct dpll_device *dpll,
|
|
const struct dpll_pin_ops *ops, void *priv)
|
|
{
|
|
struct dpll_pin_registration *reg;
|
|
struct dpll_pin_ref *ref;
|
|
bool ref_exists = false;
|
|
unsigned long i;
|
|
int ret;
|
|
|
|
xa_for_each(xa_dplls, i, ref) {
|
|
if (ref->dpll != dpll)
|
|
continue;
|
|
reg = dpll_pin_registration_find(ref, ops, priv);
|
|
if (reg) {
|
|
refcount_inc(&ref->refcount);
|
|
return 0;
|
|
}
|
|
ref_exists = true;
|
|
break;
|
|
}
|
|
|
|
if (!ref_exists) {
|
|
ref = kzalloc(sizeof(*ref), GFP_KERNEL);
|
|
if (!ref)
|
|
return -ENOMEM;
|
|
ref->dpll = dpll;
|
|
INIT_LIST_HEAD(&ref->registration_list);
|
|
ret = xa_insert(xa_dplls, dpll->id, ref, GFP_KERNEL);
|
|
if (ret) {
|
|
kfree(ref);
|
|
return ret;
|
|
}
|
|
refcount_set(&ref->refcount, 1);
|
|
}
|
|
|
|
reg = kzalloc(sizeof(*reg), GFP_KERNEL);
|
|
if (!reg) {
|
|
if (!ref_exists) {
|
|
xa_erase(xa_dplls, dpll->id);
|
|
kfree(ref);
|
|
}
|
|
return -ENOMEM;
|
|
}
|
|
reg->ops = ops;
|
|
reg->priv = priv;
|
|
if (ref_exists)
|
|
refcount_inc(&ref->refcount);
|
|
list_add_tail(®->list, &ref->registration_list);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
dpll_xa_ref_dpll_del(struct xarray *xa_dplls, struct dpll_device *dpll,
|
|
const struct dpll_pin_ops *ops, void *priv)
|
|
{
|
|
struct dpll_pin_registration *reg;
|
|
struct dpll_pin_ref *ref;
|
|
unsigned long i;
|
|
|
|
xa_for_each(xa_dplls, i, ref) {
|
|
if (ref->dpll != dpll)
|
|
continue;
|
|
reg = dpll_pin_registration_find(ref, ops, priv);
|
|
if (WARN_ON(!reg))
|
|
return;
|
|
if (refcount_dec_and_test(&ref->refcount)) {
|
|
list_del(®->list);
|
|
kfree(reg);
|
|
xa_erase(xa_dplls, i);
|
|
WARN_ON(!list_empty(&ref->registration_list));
|
|
kfree(ref);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
struct dpll_pin_ref *dpll_xa_ref_dpll_first(struct xarray *xa_refs)
|
|
{
|
|
struct dpll_pin_ref *ref;
|
|
unsigned long i = 0;
|
|
|
|
ref = xa_find(xa_refs, &i, ULONG_MAX, XA_PRESENT);
|
|
WARN_ON(!ref);
|
|
return ref;
|
|
}
|
|
|
|
static struct dpll_device *
|
|
dpll_device_alloc(const u64 clock_id, u32 device_idx, struct module *module)
|
|
{
|
|
struct dpll_device *dpll;
|
|
int ret;
|
|
|
|
dpll = kzalloc(sizeof(*dpll), GFP_KERNEL);
|
|
if (!dpll)
|
|
return ERR_PTR(-ENOMEM);
|
|
refcount_set(&dpll->refcount, 1);
|
|
INIT_LIST_HEAD(&dpll->registration_list);
|
|
dpll->device_idx = device_idx;
|
|
dpll->clock_id = clock_id;
|
|
dpll->module = module;
|
|
ret = xa_alloc_cyclic(&dpll_device_xa, &dpll->id, dpll, xa_limit_32b,
|
|
&dpll_device_xa_id, GFP_KERNEL);
|
|
if (ret < 0) {
|
|
kfree(dpll);
|
|
return ERR_PTR(ret);
|
|
}
|
|
xa_init_flags(&dpll->pin_refs, XA_FLAGS_ALLOC);
|
|
|
|
return dpll;
|
|
}
|
|
|
|
/**
|
|
* dpll_device_get - find existing or create new dpll device
|
|
* @clock_id: clock_id of creator
|
|
* @device_idx: idx given by device driver
|
|
* @module: reference to registering module
|
|
*
|
|
* Get existing object of a dpll device, unique for given arguments.
|
|
* Create new if doesn't exist yet.
|
|
*
|
|
* Context: Acquires a lock (dpll_lock)
|
|
* Return:
|
|
* * valid dpll_device struct pointer if succeeded
|
|
* * ERR_PTR(X) - error
|
|
*/
|
|
struct dpll_device *
|
|
dpll_device_get(u64 clock_id, u32 device_idx, struct module *module)
|
|
{
|
|
struct dpll_device *dpll, *ret = NULL;
|
|
unsigned long index;
|
|
|
|
mutex_lock(&dpll_lock);
|
|
xa_for_each(&dpll_device_xa, index, dpll) {
|
|
if (dpll->clock_id == clock_id &&
|
|
dpll->device_idx == device_idx &&
|
|
dpll->module == module) {
|
|
ret = dpll;
|
|
refcount_inc(&ret->refcount);
|
|
break;
|
|
}
|
|
}
|
|
if (!ret)
|
|
ret = dpll_device_alloc(clock_id, device_idx, module);
|
|
mutex_unlock(&dpll_lock);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dpll_device_get);
|
|
|
|
/**
|
|
* dpll_device_put - decrease the refcount and free memory if possible
|
|
* @dpll: dpll_device struct pointer
|
|
*
|
|
* Context: Acquires a lock (dpll_lock)
|
|
* Drop reference for a dpll device, if all references are gone, delete
|
|
* dpll device object.
|
|
*/
|
|
void dpll_device_put(struct dpll_device *dpll)
|
|
{
|
|
mutex_lock(&dpll_lock);
|
|
if (refcount_dec_and_test(&dpll->refcount)) {
|
|
ASSERT_DPLL_NOT_REGISTERED(dpll);
|
|
WARN_ON_ONCE(!xa_empty(&dpll->pin_refs));
|
|
xa_destroy(&dpll->pin_refs);
|
|
xa_erase(&dpll_device_xa, dpll->id);
|
|
WARN_ON(!list_empty(&dpll->registration_list));
|
|
kfree(dpll);
|
|
}
|
|
mutex_unlock(&dpll_lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dpll_device_put);
|
|
|
|
static struct dpll_device_registration *
|
|
dpll_device_registration_find(struct dpll_device *dpll,
|
|
const struct dpll_device_ops *ops, void *priv)
|
|
{
|
|
struct dpll_device_registration *reg;
|
|
|
|
list_for_each_entry(reg, &dpll->registration_list, list) {
|
|
if (reg->ops == ops && reg->priv == priv)
|
|
return reg;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* dpll_device_register - register the dpll device in the subsystem
|
|
* @dpll: pointer to a dpll
|
|
* @type: type of a dpll
|
|
* @ops: ops for a dpll device
|
|
* @priv: pointer to private information of owner
|
|
*
|
|
* Make dpll device available for user space.
|
|
*
|
|
* Context: Acquires a lock (dpll_lock)
|
|
* Return:
|
|
* * 0 on success
|
|
* * negative - error value
|
|
*/
|
|
int dpll_device_register(struct dpll_device *dpll, enum dpll_type type,
|
|
const struct dpll_device_ops *ops, void *priv)
|
|
{
|
|
struct dpll_device_registration *reg;
|
|
bool first_registration = false;
|
|
|
|
if (WARN_ON(!ops))
|
|
return -EINVAL;
|
|
if (WARN_ON(!ops->mode_get))
|
|
return -EINVAL;
|
|
if (WARN_ON(!ops->lock_status_get))
|
|
return -EINVAL;
|
|
if (WARN_ON(type < DPLL_TYPE_PPS || type > DPLL_TYPE_MAX))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&dpll_lock);
|
|
reg = dpll_device_registration_find(dpll, ops, priv);
|
|
if (reg) {
|
|
mutex_unlock(&dpll_lock);
|
|
return -EEXIST;
|
|
}
|
|
|
|
reg = kzalloc(sizeof(*reg), GFP_KERNEL);
|
|
if (!reg) {
|
|
mutex_unlock(&dpll_lock);
|
|
return -ENOMEM;
|
|
}
|
|
reg->ops = ops;
|
|
reg->priv = priv;
|
|
dpll->type = type;
|
|
first_registration = list_empty(&dpll->registration_list);
|
|
list_add_tail(®->list, &dpll->registration_list);
|
|
if (!first_registration) {
|
|
mutex_unlock(&dpll_lock);
|
|
return 0;
|
|
}
|
|
|
|
xa_set_mark(&dpll_device_xa, dpll->id, DPLL_REGISTERED);
|
|
dpll_device_create_ntf(dpll);
|
|
mutex_unlock(&dpll_lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dpll_device_register);
|
|
|
|
/**
|
|
* dpll_device_unregister - unregister dpll device
|
|
* @dpll: registered dpll pointer
|
|
* @ops: ops for a dpll device
|
|
* @priv: pointer to private information of owner
|
|
*
|
|
* Unregister device, make it unavailable for userspace.
|
|
* Note: It does not free the memory
|
|
* Context: Acquires a lock (dpll_lock)
|
|
*/
|
|
void dpll_device_unregister(struct dpll_device *dpll,
|
|
const struct dpll_device_ops *ops, void *priv)
|
|
{
|
|
struct dpll_device_registration *reg;
|
|
|
|
mutex_lock(&dpll_lock);
|
|
ASSERT_DPLL_REGISTERED(dpll);
|
|
dpll_device_delete_ntf(dpll);
|
|
reg = dpll_device_registration_find(dpll, ops, priv);
|
|
if (WARN_ON(!reg)) {
|
|
mutex_unlock(&dpll_lock);
|
|
return;
|
|
}
|
|
list_del(®->list);
|
|
kfree(reg);
|
|
|
|
if (!list_empty(&dpll->registration_list)) {
|
|
mutex_unlock(&dpll_lock);
|
|
return;
|
|
}
|
|
xa_clear_mark(&dpll_device_xa, dpll->id, DPLL_REGISTERED);
|
|
mutex_unlock(&dpll_lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dpll_device_unregister);
|
|
|
|
static void dpll_pin_prop_free(struct dpll_pin_properties *prop)
|
|
{
|
|
kfree(prop->package_label);
|
|
kfree(prop->panel_label);
|
|
kfree(prop->board_label);
|
|
kfree(prop->freq_supported);
|
|
}
|
|
|
|
static int dpll_pin_prop_dup(const struct dpll_pin_properties *src,
|
|
struct dpll_pin_properties *dst)
|
|
{
|
|
memcpy(dst, src, sizeof(*dst));
|
|
if (src->freq_supported && src->freq_supported_num) {
|
|
size_t freq_size = src->freq_supported_num *
|
|
sizeof(*src->freq_supported);
|
|
dst->freq_supported = kmemdup(src->freq_supported,
|
|
freq_size, GFP_KERNEL);
|
|
if (!src->freq_supported)
|
|
return -ENOMEM;
|
|
}
|
|
if (src->board_label) {
|
|
dst->board_label = kstrdup(src->board_label, GFP_KERNEL);
|
|
if (!dst->board_label)
|
|
goto err_board_label;
|
|
}
|
|
if (src->panel_label) {
|
|
dst->panel_label = kstrdup(src->panel_label, GFP_KERNEL);
|
|
if (!dst->panel_label)
|
|
goto err_panel_label;
|
|
}
|
|
if (src->package_label) {
|
|
dst->package_label = kstrdup(src->package_label, GFP_KERNEL);
|
|
if (!dst->package_label)
|
|
goto err_package_label;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_package_label:
|
|
kfree(dst->panel_label);
|
|
err_panel_label:
|
|
kfree(dst->board_label);
|
|
err_board_label:
|
|
kfree(dst->freq_supported);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static struct dpll_pin *
|
|
dpll_pin_alloc(u64 clock_id, u32 pin_idx, 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);
|
|
pin->pin_idx = pin_idx;
|
|
pin->clock_id = clock_id;
|
|
pin->module = module;
|
|
if (WARN_ON(prop->type < DPLL_PIN_TYPE_MUX ||
|
|
prop->type > DPLL_PIN_TYPE_MAX)) {
|
|
ret = -EINVAL;
|
|
goto err_pin_prop;
|
|
}
|
|
ret = dpll_pin_prop_dup(prop, &pin->prop);
|
|
if (ret)
|
|
goto err_pin_prop;
|
|
refcount_set(&pin->refcount, 1);
|
|
xa_init_flags(&pin->dpll_refs, XA_FLAGS_ALLOC);
|
|
xa_init_flags(&pin->parent_refs, XA_FLAGS_ALLOC);
|
|
ret = xa_alloc_cyclic(&dpll_pin_xa, &pin->id, pin, xa_limit_32b,
|
|
&dpll_pin_xa_id, GFP_KERNEL);
|
|
if (ret)
|
|
goto err_xa_alloc;
|
|
return pin;
|
|
err_xa_alloc:
|
|
xa_destroy(&pin->dpll_refs);
|
|
xa_destroy(&pin->parent_refs);
|
|
dpll_pin_prop_free(&pin->prop);
|
|
err_pin_prop:
|
|
kfree(pin);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/**
|
|
* dpll_pin_get - find existing or create new dpll pin
|
|
* @clock_id: clock_id of creator
|
|
* @pin_idx: idx given by dev driver
|
|
* @module: reference to registering module
|
|
* @prop: dpll pin properties
|
|
*
|
|
* Get existing object of a pin (unique for given arguments) or create new
|
|
* if doesn't exist yet.
|
|
*
|
|
* Context: Acquires a lock (dpll_lock)
|
|
* Return:
|
|
* * valid allocated dpll_pin struct pointer if succeeded
|
|
* * ERR_PTR(X) - error
|
|
*/
|
|
struct dpll_pin *
|
|
dpll_pin_get(u64 clock_id, u32 pin_idx, struct module *module,
|
|
const struct dpll_pin_properties *prop)
|
|
{
|
|
struct dpll_pin *pos, *ret = NULL;
|
|
unsigned long i;
|
|
|
|
mutex_lock(&dpll_lock);
|
|
xa_for_each(&dpll_pin_xa, i, pos) {
|
|
if (pos->clock_id == clock_id &&
|
|
pos->pin_idx == pin_idx &&
|
|
pos->module == module) {
|
|
ret = pos;
|
|
refcount_inc(&ret->refcount);
|
|
break;
|
|
}
|
|
}
|
|
if (!ret)
|
|
ret = dpll_pin_alloc(clock_id, pin_idx, module, prop);
|
|
mutex_unlock(&dpll_lock);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dpll_pin_get);
|
|
|
|
/**
|
|
* dpll_pin_put - decrease the refcount and free memory if possible
|
|
* @pin: pointer to a pin to be put
|
|
*
|
|
* Drop reference for a pin, if all references are gone, delete pin object.
|
|
*
|
|
* Context: Acquires a lock (dpll_lock)
|
|
*/
|
|
void dpll_pin_put(struct dpll_pin *pin)
|
|
{
|
|
mutex_lock(&dpll_lock);
|
|
if (refcount_dec_and_test(&pin->refcount)) {
|
|
xa_destroy(&pin->dpll_refs);
|
|
xa_destroy(&pin->parent_refs);
|
|
xa_erase(&dpll_pin_xa, pin->id);
|
|
dpll_pin_prop_free(&pin->prop);
|
|
kfree(pin);
|
|
}
|
|
mutex_unlock(&dpll_lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dpll_pin_put);
|
|
|
|
static int
|
|
__dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin,
|
|
const struct dpll_pin_ops *ops, void *priv)
|
|
{
|
|
int ret;
|
|
|
|
ret = dpll_xa_ref_pin_add(&dpll->pin_refs, pin, ops, priv);
|
|
if (ret)
|
|
return ret;
|
|
ret = dpll_xa_ref_dpll_add(&pin->dpll_refs, dpll, ops, priv);
|
|
if (ret)
|
|
goto ref_pin_del;
|
|
xa_set_mark(&dpll_pin_xa, pin->id, DPLL_REGISTERED);
|
|
dpll_pin_create_ntf(pin);
|
|
|
|
return ret;
|
|
|
|
ref_pin_del:
|
|
dpll_xa_ref_pin_del(&dpll->pin_refs, pin, ops, priv);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* dpll_pin_register - register the dpll pin in the subsystem
|
|
* @dpll: pointer to a dpll
|
|
* @pin: pointer to a dpll pin
|
|
* @ops: ops for a dpll pin ops
|
|
* @priv: pointer to private information of owner
|
|
*
|
|
* Context: Acquires a lock (dpll_lock)
|
|
* Return:
|
|
* * 0 on success
|
|
* * negative - error value
|
|
*/
|
|
int
|
|
dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin,
|
|
const struct dpll_pin_ops *ops, void *priv)
|
|
{
|
|
int ret;
|
|
|
|
if (WARN_ON(!ops) ||
|
|
WARN_ON(!ops->state_on_dpll_get) ||
|
|
WARN_ON(!ops->direction_get))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&dpll_lock);
|
|
if (WARN_ON(!(dpll->module == pin->module &&
|
|
dpll->clock_id == pin->clock_id)))
|
|
ret = -EINVAL;
|
|
else
|
|
ret = __dpll_pin_register(dpll, pin, ops, priv);
|
|
mutex_unlock(&dpll_lock);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dpll_pin_register);
|
|
|
|
static void
|
|
__dpll_pin_unregister(struct dpll_device *dpll, struct dpll_pin *pin,
|
|
const struct dpll_pin_ops *ops, void *priv)
|
|
{
|
|
dpll_xa_ref_pin_del(&dpll->pin_refs, pin, ops, priv);
|
|
dpll_xa_ref_dpll_del(&pin->dpll_refs, dpll, ops, priv);
|
|
if (xa_empty(&pin->dpll_refs))
|
|
xa_clear_mark(&dpll_pin_xa, pin->id, DPLL_REGISTERED);
|
|
}
|
|
|
|
/**
|
|
* dpll_pin_unregister - unregister dpll pin from dpll device
|
|
* @dpll: registered dpll pointer
|
|
* @pin: pointer to a pin
|
|
* @ops: ops for a dpll pin
|
|
* @priv: pointer to private information of owner
|
|
*
|
|
* Note: It does not free the memory
|
|
* Context: Acquires a lock (dpll_lock)
|
|
*/
|
|
void dpll_pin_unregister(struct dpll_device *dpll, struct dpll_pin *pin,
|
|
const struct dpll_pin_ops *ops, void *priv)
|
|
{
|
|
if (WARN_ON(xa_empty(&dpll->pin_refs)))
|
|
return;
|
|
if (WARN_ON(!xa_empty(&pin->parent_refs)))
|
|
return;
|
|
|
|
mutex_lock(&dpll_lock);
|
|
dpll_pin_delete_ntf(pin);
|
|
__dpll_pin_unregister(dpll, pin, ops, priv);
|
|
mutex_unlock(&dpll_lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dpll_pin_unregister);
|
|
|
|
/**
|
|
* dpll_pin_on_pin_register - register a pin with a parent pin
|
|
* @parent: pointer to a parent pin
|
|
* @pin: pointer to a pin
|
|
* @ops: ops for a dpll pin
|
|
* @priv: pointer to private information of owner
|
|
*
|
|
* Register a pin with a parent pin, create references between them and
|
|
* between newly registered pin and dplls connected with a parent pin.
|
|
*
|
|
* Context: Acquires a lock (dpll_lock)
|
|
* Return:
|
|
* * 0 on success
|
|
* * negative - error value
|
|
*/
|
|
int dpll_pin_on_pin_register(struct dpll_pin *parent, struct dpll_pin *pin,
|
|
const struct dpll_pin_ops *ops, void *priv)
|
|
{
|
|
struct dpll_pin_ref *ref;
|
|
unsigned long i, stop;
|
|
int ret;
|
|
|
|
if (WARN_ON(parent->prop.type != DPLL_PIN_TYPE_MUX))
|
|
return -EINVAL;
|
|
|
|
if (WARN_ON(!ops) ||
|
|
WARN_ON(!ops->state_on_pin_get) ||
|
|
WARN_ON(!ops->direction_get))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&dpll_lock);
|
|
ret = dpll_xa_ref_pin_add(&pin->parent_refs, parent, ops, priv);
|
|
if (ret)
|
|
goto unlock;
|
|
refcount_inc(&pin->refcount);
|
|
xa_for_each(&parent->dpll_refs, i, ref) {
|
|
ret = __dpll_pin_register(ref->dpll, pin, ops, priv);
|
|
if (ret) {
|
|
stop = i;
|
|
goto dpll_unregister;
|
|
}
|
|
dpll_pin_create_ntf(pin);
|
|
}
|
|
mutex_unlock(&dpll_lock);
|
|
|
|
return ret;
|
|
|
|
dpll_unregister:
|
|
xa_for_each(&parent->dpll_refs, i, ref)
|
|
if (i < stop) {
|
|
__dpll_pin_unregister(ref->dpll, pin, ops, priv);
|
|
dpll_pin_delete_ntf(pin);
|
|
}
|
|
refcount_dec(&pin->refcount);
|
|
dpll_xa_ref_pin_del(&pin->parent_refs, parent, ops, priv);
|
|
unlock:
|
|
mutex_unlock(&dpll_lock);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dpll_pin_on_pin_register);
|
|
|
|
/**
|
|
* dpll_pin_on_pin_unregister - unregister dpll pin from a parent pin
|
|
* @parent: pointer to a parent pin
|
|
* @pin: pointer to a pin
|
|
* @ops: ops for a dpll pin
|
|
* @priv: pointer to private information of owner
|
|
*
|
|
* Context: Acquires a lock (dpll_lock)
|
|
* Note: It does not free the memory
|
|
*/
|
|
void dpll_pin_on_pin_unregister(struct dpll_pin *parent, struct dpll_pin *pin,
|
|
const struct dpll_pin_ops *ops, void *priv)
|
|
{
|
|
struct dpll_pin_ref *ref;
|
|
unsigned long i;
|
|
|
|
mutex_lock(&dpll_lock);
|
|
dpll_pin_delete_ntf(pin);
|
|
dpll_xa_ref_pin_del(&pin->parent_refs, parent, ops, priv);
|
|
refcount_dec(&pin->refcount);
|
|
xa_for_each(&pin->dpll_refs, i, ref)
|
|
__dpll_pin_unregister(ref->dpll, pin, ops, priv);
|
|
mutex_unlock(&dpll_lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dpll_pin_on_pin_unregister);
|
|
|
|
static struct dpll_device_registration *
|
|
dpll_device_registration_first(struct dpll_device *dpll)
|
|
{
|
|
struct dpll_device_registration *reg;
|
|
|
|
reg = list_first_entry_or_null((struct list_head *)&dpll->registration_list,
|
|
struct dpll_device_registration, list);
|
|
WARN_ON(!reg);
|
|
return reg;
|
|
}
|
|
|
|
void *dpll_priv(struct dpll_device *dpll)
|
|
{
|
|
struct dpll_device_registration *reg;
|
|
|
|
reg = dpll_device_registration_first(dpll);
|
|
return reg->priv;
|
|
}
|
|
|
|
const struct dpll_device_ops *dpll_device_ops(struct dpll_device *dpll)
|
|
{
|
|
struct dpll_device_registration *reg;
|
|
|
|
reg = dpll_device_registration_first(dpll);
|
|
return reg->ops;
|
|
}
|
|
|
|
static struct dpll_pin_registration *
|
|
dpll_pin_registration_first(struct dpll_pin_ref *ref)
|
|
{
|
|
struct dpll_pin_registration *reg;
|
|
|
|
reg = list_first_entry_or_null(&ref->registration_list,
|
|
struct dpll_pin_registration, list);
|
|
WARN_ON(!reg);
|
|
return reg;
|
|
}
|
|
|
|
void *dpll_pin_on_dpll_priv(struct dpll_device *dpll,
|
|
struct dpll_pin *pin)
|
|
{
|
|
struct dpll_pin_registration *reg;
|
|
struct dpll_pin_ref *ref;
|
|
|
|
ref = xa_load(&dpll->pin_refs, pin->pin_idx);
|
|
if (!ref)
|
|
return NULL;
|
|
reg = dpll_pin_registration_first(ref);
|
|
return reg->priv;
|
|
}
|
|
|
|
void *dpll_pin_on_pin_priv(struct dpll_pin *parent,
|
|
struct dpll_pin *pin)
|
|
{
|
|
struct dpll_pin_registration *reg;
|
|
struct dpll_pin_ref *ref;
|
|
|
|
ref = xa_load(&pin->parent_refs, parent->pin_idx);
|
|
if (!ref)
|
|
return NULL;
|
|
reg = dpll_pin_registration_first(ref);
|
|
return reg->priv;
|
|
}
|
|
|
|
const struct dpll_pin_ops *dpll_pin_ops(struct dpll_pin_ref *ref)
|
|
{
|
|
struct dpll_pin_registration *reg;
|
|
|
|
reg = dpll_pin_registration_first(ref);
|
|
return reg->ops;
|
|
}
|
|
|
|
static int __init dpll_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = genl_register_family(&dpll_nl_family);
|
|
if (ret)
|
|
goto error;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
mutex_destroy(&dpll_lock);
|
|
return ret;
|
|
}
|
|
|
|
static void __exit dpll_exit(void)
|
|
{
|
|
genl_unregister_family(&dpll_nl_family);
|
|
mutex_destroy(&dpll_lock);
|
|
}
|
|
|
|
subsys_initcall(dpll_init);
|
|
module_exit(dpll_exit);
|