diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /drivers/media/v4l2-core/v4l2-clk.c | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/media/v4l2-core/v4l2-clk.c')
-rw-r--r-- | drivers/media/v4l2-core/v4l2-clk.c | 321 |
1 files changed, 321 insertions, 0 deletions
diff --git a/drivers/media/v4l2-core/v4l2-clk.c b/drivers/media/v4l2-core/v4l2-clk.c new file mode 100644 index 000000000..91274eee6 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-clk.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * V4L2 clock service + * + * Copyright (C) 2012-2013, Guennadi Liakhovetski <g.liakhovetski@gmx.de> + */ + +#include <linux/atomic.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/string.h> + +#include <media/v4l2-clk.h> +#include <media/v4l2-subdev.h> + +static DEFINE_MUTEX(clk_lock); +static LIST_HEAD(clk_list); + +static struct v4l2_clk *v4l2_clk_find(const char *dev_id) +{ + struct v4l2_clk *clk; + + list_for_each_entry(clk, &clk_list, list) + if (!strcmp(dev_id, clk->dev_id)) + return clk; + + return ERR_PTR(-ENODEV); +} + +struct v4l2_clk *v4l2_clk_get(struct device *dev, const char *id) +{ + struct v4l2_clk *clk; + struct clk *ccf_clk = clk_get(dev, id); + char clk_name[V4L2_CLK_NAME_SIZE]; + + if (PTR_ERR(ccf_clk) == -EPROBE_DEFER) + return ERR_PTR(-EPROBE_DEFER); + + if (!IS_ERR_OR_NULL(ccf_clk)) { + clk = kzalloc(sizeof(*clk), GFP_KERNEL); + if (!clk) { + clk_put(ccf_clk); + return ERR_PTR(-ENOMEM); + } + clk->clk = ccf_clk; + + return clk; + } + + mutex_lock(&clk_lock); + clk = v4l2_clk_find(dev_name(dev)); + + /* if dev_name is not found, try use the OF name to find again */ + if (PTR_ERR(clk) == -ENODEV && dev->of_node) { + v4l2_clk_name_of(clk_name, sizeof(clk_name), dev->of_node); + clk = v4l2_clk_find(clk_name); + } + + if (!IS_ERR(clk)) + atomic_inc(&clk->use_count); + mutex_unlock(&clk_lock); + + return clk; +} +EXPORT_SYMBOL(v4l2_clk_get); + +void v4l2_clk_put(struct v4l2_clk *clk) +{ + struct v4l2_clk *tmp; + + if (IS_ERR(clk)) + return; + + if (clk->clk) { + clk_put(clk->clk); + kfree(clk); + return; + } + + mutex_lock(&clk_lock); + + list_for_each_entry(tmp, &clk_list, list) + if (tmp == clk) + atomic_dec(&clk->use_count); + + mutex_unlock(&clk_lock); +} +EXPORT_SYMBOL(v4l2_clk_put); + +static int v4l2_clk_lock_driver(struct v4l2_clk *clk) +{ + struct v4l2_clk *tmp; + int ret = -ENODEV; + + mutex_lock(&clk_lock); + + list_for_each_entry(tmp, &clk_list, list) + if (tmp == clk) { + ret = !try_module_get(clk->ops->owner); + if (ret) + ret = -EFAULT; + break; + } + + mutex_unlock(&clk_lock); + + return ret; +} + +static void v4l2_clk_unlock_driver(struct v4l2_clk *clk) +{ + module_put(clk->ops->owner); +} + +int v4l2_clk_enable(struct v4l2_clk *clk) +{ + int ret; + + if (clk->clk) + return clk_prepare_enable(clk->clk); + + ret = v4l2_clk_lock_driver(clk); + if (ret < 0) + return ret; + + mutex_lock(&clk->lock); + + if (++clk->enable == 1 && clk->ops->enable) { + ret = clk->ops->enable(clk); + if (ret < 0) + clk->enable--; + } + + mutex_unlock(&clk->lock); + + return ret; +} +EXPORT_SYMBOL(v4l2_clk_enable); + +/* + * You might Oops if you try to disabled a disabled clock, because then the + * driver isn't locked and could have been unloaded by now, so, don't do that + */ +void v4l2_clk_disable(struct v4l2_clk *clk) +{ + int enable; + + if (clk->clk) + return clk_disable_unprepare(clk->clk); + + mutex_lock(&clk->lock); + + enable = --clk->enable; + if (WARN(enable < 0, "Unbalanced %s() on %s!\n", __func__, + clk->dev_id)) + clk->enable++; + else if (!enable && clk->ops->disable) + clk->ops->disable(clk); + + mutex_unlock(&clk->lock); + + v4l2_clk_unlock_driver(clk); +} +EXPORT_SYMBOL(v4l2_clk_disable); + +unsigned long v4l2_clk_get_rate(struct v4l2_clk *clk) +{ + int ret; + + if (clk->clk) + return clk_get_rate(clk->clk); + + ret = v4l2_clk_lock_driver(clk); + if (ret < 0) + return ret; + + mutex_lock(&clk->lock); + if (!clk->ops->get_rate) + ret = -ENOSYS; + else + ret = clk->ops->get_rate(clk); + mutex_unlock(&clk->lock); + + v4l2_clk_unlock_driver(clk); + + return ret; +} +EXPORT_SYMBOL(v4l2_clk_get_rate); + +int v4l2_clk_set_rate(struct v4l2_clk *clk, unsigned long rate) +{ + int ret; + + if (clk->clk) { + long r = clk_round_rate(clk->clk, rate); + if (r < 0) + return r; + return clk_set_rate(clk->clk, r); + } + + ret = v4l2_clk_lock_driver(clk); + + if (ret < 0) + return ret; + + mutex_lock(&clk->lock); + if (!clk->ops->set_rate) + ret = -ENOSYS; + else + ret = clk->ops->set_rate(clk, rate); + mutex_unlock(&clk->lock); + + v4l2_clk_unlock_driver(clk); + + return ret; +} +EXPORT_SYMBOL(v4l2_clk_set_rate); + +struct v4l2_clk *v4l2_clk_register(const struct v4l2_clk_ops *ops, + const char *dev_id, + void *priv) +{ + struct v4l2_clk *clk; + int ret; + + if (!ops || !dev_id) + return ERR_PTR(-EINVAL); + + clk = kzalloc(sizeof(struct v4l2_clk), GFP_KERNEL); + if (!clk) + return ERR_PTR(-ENOMEM); + + clk->dev_id = kstrdup(dev_id, GFP_KERNEL); + if (!clk->dev_id) { + ret = -ENOMEM; + goto ealloc; + } + clk->ops = ops; + clk->priv = priv; + atomic_set(&clk->use_count, 0); + mutex_init(&clk->lock); + + mutex_lock(&clk_lock); + if (!IS_ERR(v4l2_clk_find(dev_id))) { + mutex_unlock(&clk_lock); + ret = -EEXIST; + goto eexist; + } + list_add_tail(&clk->list, &clk_list); + mutex_unlock(&clk_lock); + + return clk; + +eexist: +ealloc: + kfree(clk->dev_id); + kfree(clk); + return ERR_PTR(ret); +} +EXPORT_SYMBOL(v4l2_clk_register); + +void v4l2_clk_unregister(struct v4l2_clk *clk) +{ + if (WARN(atomic_read(&clk->use_count), + "%s(): Refusing to unregister ref-counted %s clock!\n", + __func__, clk->dev_id)) + return; + + mutex_lock(&clk_lock); + list_del(&clk->list); + mutex_unlock(&clk_lock); + + kfree(clk->dev_id); + kfree(clk); +} +EXPORT_SYMBOL(v4l2_clk_unregister); + +struct v4l2_clk_fixed { + unsigned long rate; + struct v4l2_clk_ops ops; +}; + +static unsigned long fixed_get_rate(struct v4l2_clk *clk) +{ + struct v4l2_clk_fixed *priv = clk->priv; + return priv->rate; +} + +struct v4l2_clk *__v4l2_clk_register_fixed(const char *dev_id, + unsigned long rate, struct module *owner) +{ + struct v4l2_clk *clk; + struct v4l2_clk_fixed *priv = kzalloc(sizeof(*priv), GFP_KERNEL); + + if (!priv) + return ERR_PTR(-ENOMEM); + + priv->rate = rate; + priv->ops.get_rate = fixed_get_rate; + priv->ops.owner = owner; + + clk = v4l2_clk_register(&priv->ops, dev_id, priv); + if (IS_ERR(clk)) + kfree(priv); + + return clk; +} +EXPORT_SYMBOL(__v4l2_clk_register_fixed); + +void v4l2_clk_unregister_fixed(struct v4l2_clk *clk) +{ + kfree(clk->priv); + v4l2_clk_unregister(clk); +} +EXPORT_SYMBOL(v4l2_clk_unregister_fixed); |