| /* |
| * V4L2 clock service |
| * |
| * Copyright (C) 2012-2013, Guennadi Liakhovetski <g.liakhovetski@gmx.de> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| #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/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); |
| |
| 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 (!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); |