/* * 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);