18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * V4L2 clock service 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2012-2013, Guennadi Liakhovetski <g.liakhovetski@gmx.de> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/atomic.h> 98c2ecf20Sopenharmony_ci#include <linux/clk.h> 108c2ecf20Sopenharmony_ci#include <linux/device.h> 118c2ecf20Sopenharmony_ci#include <linux/errno.h> 128c2ecf20Sopenharmony_ci#include <linux/list.h> 138c2ecf20Sopenharmony_ci#include <linux/module.h> 148c2ecf20Sopenharmony_ci#include <linux/mutex.h> 158c2ecf20Sopenharmony_ci#include <linux/of.h> 168c2ecf20Sopenharmony_ci#include <linux/slab.h> 178c2ecf20Sopenharmony_ci#include <linux/string.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include <media/v4l2-clk.h> 208c2ecf20Sopenharmony_ci#include <media/v4l2-subdev.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(clk_lock); 238c2ecf20Sopenharmony_cistatic LIST_HEAD(clk_list); 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cistatic struct v4l2_clk *v4l2_clk_find(const char *dev_id) 268c2ecf20Sopenharmony_ci{ 278c2ecf20Sopenharmony_ci struct v4l2_clk *clk; 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci list_for_each_entry(clk, &clk_list, list) 308c2ecf20Sopenharmony_ci if (!strcmp(dev_id, clk->dev_id)) 318c2ecf20Sopenharmony_ci return clk; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci return ERR_PTR(-ENODEV); 348c2ecf20Sopenharmony_ci} 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistruct v4l2_clk *v4l2_clk_get(struct device *dev, const char *id) 378c2ecf20Sopenharmony_ci{ 388c2ecf20Sopenharmony_ci struct v4l2_clk *clk; 398c2ecf20Sopenharmony_ci struct clk *ccf_clk = clk_get(dev, id); 408c2ecf20Sopenharmony_ci char clk_name[V4L2_CLK_NAME_SIZE]; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci if (PTR_ERR(ccf_clk) == -EPROBE_DEFER) 438c2ecf20Sopenharmony_ci return ERR_PTR(-EPROBE_DEFER); 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci if (!IS_ERR_OR_NULL(ccf_clk)) { 468c2ecf20Sopenharmony_ci clk = kzalloc(sizeof(*clk), GFP_KERNEL); 478c2ecf20Sopenharmony_ci if (!clk) { 488c2ecf20Sopenharmony_ci clk_put(ccf_clk); 498c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 508c2ecf20Sopenharmony_ci } 518c2ecf20Sopenharmony_ci clk->clk = ccf_clk; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci return clk; 548c2ecf20Sopenharmony_ci } 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci mutex_lock(&clk_lock); 578c2ecf20Sopenharmony_ci clk = v4l2_clk_find(dev_name(dev)); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci /* if dev_name is not found, try use the OF name to find again */ 608c2ecf20Sopenharmony_ci if (PTR_ERR(clk) == -ENODEV && dev->of_node) { 618c2ecf20Sopenharmony_ci v4l2_clk_name_of(clk_name, sizeof(clk_name), dev->of_node); 628c2ecf20Sopenharmony_ci clk = v4l2_clk_find(clk_name); 638c2ecf20Sopenharmony_ci } 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci if (!IS_ERR(clk)) 668c2ecf20Sopenharmony_ci atomic_inc(&clk->use_count); 678c2ecf20Sopenharmony_ci mutex_unlock(&clk_lock); 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci return clk; 708c2ecf20Sopenharmony_ci} 718c2ecf20Sopenharmony_ciEXPORT_SYMBOL(v4l2_clk_get); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_civoid v4l2_clk_put(struct v4l2_clk *clk) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci struct v4l2_clk *tmp; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci if (IS_ERR(clk)) 788c2ecf20Sopenharmony_ci return; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci if (clk->clk) { 818c2ecf20Sopenharmony_ci clk_put(clk->clk); 828c2ecf20Sopenharmony_ci kfree(clk); 838c2ecf20Sopenharmony_ci return; 848c2ecf20Sopenharmony_ci } 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci mutex_lock(&clk_lock); 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci list_for_each_entry(tmp, &clk_list, list) 898c2ecf20Sopenharmony_ci if (tmp == clk) 908c2ecf20Sopenharmony_ci atomic_dec(&clk->use_count); 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci mutex_unlock(&clk_lock); 938c2ecf20Sopenharmony_ci} 948c2ecf20Sopenharmony_ciEXPORT_SYMBOL(v4l2_clk_put); 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic int v4l2_clk_lock_driver(struct v4l2_clk *clk) 978c2ecf20Sopenharmony_ci{ 988c2ecf20Sopenharmony_ci struct v4l2_clk *tmp; 998c2ecf20Sopenharmony_ci int ret = -ENODEV; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci mutex_lock(&clk_lock); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci list_for_each_entry(tmp, &clk_list, list) 1048c2ecf20Sopenharmony_ci if (tmp == clk) { 1058c2ecf20Sopenharmony_ci ret = !try_module_get(clk->ops->owner); 1068c2ecf20Sopenharmony_ci if (ret) 1078c2ecf20Sopenharmony_ci ret = -EFAULT; 1088c2ecf20Sopenharmony_ci break; 1098c2ecf20Sopenharmony_ci } 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci mutex_unlock(&clk_lock); 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci return ret; 1148c2ecf20Sopenharmony_ci} 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_cistatic void v4l2_clk_unlock_driver(struct v4l2_clk *clk) 1178c2ecf20Sopenharmony_ci{ 1188c2ecf20Sopenharmony_ci module_put(clk->ops->owner); 1198c2ecf20Sopenharmony_ci} 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ciint v4l2_clk_enable(struct v4l2_clk *clk) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci int ret; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci if (clk->clk) 1268c2ecf20Sopenharmony_ci return clk_prepare_enable(clk->clk); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci ret = v4l2_clk_lock_driver(clk); 1298c2ecf20Sopenharmony_ci if (ret < 0) 1308c2ecf20Sopenharmony_ci return ret; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci mutex_lock(&clk->lock); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci if (++clk->enable == 1 && clk->ops->enable) { 1358c2ecf20Sopenharmony_ci ret = clk->ops->enable(clk); 1368c2ecf20Sopenharmony_ci if (ret < 0) 1378c2ecf20Sopenharmony_ci clk->enable--; 1388c2ecf20Sopenharmony_ci } 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci mutex_unlock(&clk->lock); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci return ret; 1438c2ecf20Sopenharmony_ci} 1448c2ecf20Sopenharmony_ciEXPORT_SYMBOL(v4l2_clk_enable); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci/* 1478c2ecf20Sopenharmony_ci * You might Oops if you try to disabled a disabled clock, because then the 1488c2ecf20Sopenharmony_ci * driver isn't locked and could have been unloaded by now, so, don't do that 1498c2ecf20Sopenharmony_ci */ 1508c2ecf20Sopenharmony_civoid v4l2_clk_disable(struct v4l2_clk *clk) 1518c2ecf20Sopenharmony_ci{ 1528c2ecf20Sopenharmony_ci int enable; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci if (clk->clk) 1558c2ecf20Sopenharmony_ci return clk_disable_unprepare(clk->clk); 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci mutex_lock(&clk->lock); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci enable = --clk->enable; 1608c2ecf20Sopenharmony_ci if (WARN(enable < 0, "Unbalanced %s() on %s!\n", __func__, 1618c2ecf20Sopenharmony_ci clk->dev_id)) 1628c2ecf20Sopenharmony_ci clk->enable++; 1638c2ecf20Sopenharmony_ci else if (!enable && clk->ops->disable) 1648c2ecf20Sopenharmony_ci clk->ops->disable(clk); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci mutex_unlock(&clk->lock); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci v4l2_clk_unlock_driver(clk); 1698c2ecf20Sopenharmony_ci} 1708c2ecf20Sopenharmony_ciEXPORT_SYMBOL(v4l2_clk_disable); 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ciunsigned long v4l2_clk_get_rate(struct v4l2_clk *clk) 1738c2ecf20Sopenharmony_ci{ 1748c2ecf20Sopenharmony_ci int ret; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci if (clk->clk) 1778c2ecf20Sopenharmony_ci return clk_get_rate(clk->clk); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci ret = v4l2_clk_lock_driver(clk); 1808c2ecf20Sopenharmony_ci if (ret < 0) 1818c2ecf20Sopenharmony_ci return ret; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci mutex_lock(&clk->lock); 1848c2ecf20Sopenharmony_ci if (!clk->ops->get_rate) 1858c2ecf20Sopenharmony_ci ret = -ENOSYS; 1868c2ecf20Sopenharmony_ci else 1878c2ecf20Sopenharmony_ci ret = clk->ops->get_rate(clk); 1888c2ecf20Sopenharmony_ci mutex_unlock(&clk->lock); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci v4l2_clk_unlock_driver(clk); 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci return ret; 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ciEXPORT_SYMBOL(v4l2_clk_get_rate); 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ciint v4l2_clk_set_rate(struct v4l2_clk *clk, unsigned long rate) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci int ret; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci if (clk->clk) { 2018c2ecf20Sopenharmony_ci long r = clk_round_rate(clk->clk, rate); 2028c2ecf20Sopenharmony_ci if (r < 0) 2038c2ecf20Sopenharmony_ci return r; 2048c2ecf20Sopenharmony_ci return clk_set_rate(clk->clk, r); 2058c2ecf20Sopenharmony_ci } 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci ret = v4l2_clk_lock_driver(clk); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci if (ret < 0) 2108c2ecf20Sopenharmony_ci return ret; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci mutex_lock(&clk->lock); 2138c2ecf20Sopenharmony_ci if (!clk->ops->set_rate) 2148c2ecf20Sopenharmony_ci ret = -ENOSYS; 2158c2ecf20Sopenharmony_ci else 2168c2ecf20Sopenharmony_ci ret = clk->ops->set_rate(clk, rate); 2178c2ecf20Sopenharmony_ci mutex_unlock(&clk->lock); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci v4l2_clk_unlock_driver(clk); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci return ret; 2228c2ecf20Sopenharmony_ci} 2238c2ecf20Sopenharmony_ciEXPORT_SYMBOL(v4l2_clk_set_rate); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_cistruct v4l2_clk *v4l2_clk_register(const struct v4l2_clk_ops *ops, 2268c2ecf20Sopenharmony_ci const char *dev_id, 2278c2ecf20Sopenharmony_ci void *priv) 2288c2ecf20Sopenharmony_ci{ 2298c2ecf20Sopenharmony_ci struct v4l2_clk *clk; 2308c2ecf20Sopenharmony_ci int ret; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci if (!ops || !dev_id) 2338c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci clk = kzalloc(sizeof(struct v4l2_clk), GFP_KERNEL); 2368c2ecf20Sopenharmony_ci if (!clk) 2378c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci clk->dev_id = kstrdup(dev_id, GFP_KERNEL); 2408c2ecf20Sopenharmony_ci if (!clk->dev_id) { 2418c2ecf20Sopenharmony_ci ret = -ENOMEM; 2428c2ecf20Sopenharmony_ci goto ealloc; 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci clk->ops = ops; 2458c2ecf20Sopenharmony_ci clk->priv = priv; 2468c2ecf20Sopenharmony_ci atomic_set(&clk->use_count, 0); 2478c2ecf20Sopenharmony_ci mutex_init(&clk->lock); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci mutex_lock(&clk_lock); 2508c2ecf20Sopenharmony_ci if (!IS_ERR(v4l2_clk_find(dev_id))) { 2518c2ecf20Sopenharmony_ci mutex_unlock(&clk_lock); 2528c2ecf20Sopenharmony_ci ret = -EEXIST; 2538c2ecf20Sopenharmony_ci goto eexist; 2548c2ecf20Sopenharmony_ci } 2558c2ecf20Sopenharmony_ci list_add_tail(&clk->list, &clk_list); 2568c2ecf20Sopenharmony_ci mutex_unlock(&clk_lock); 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci return clk; 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_cieexist: 2618c2ecf20Sopenharmony_ciealloc: 2628c2ecf20Sopenharmony_ci kfree(clk->dev_id); 2638c2ecf20Sopenharmony_ci kfree(clk); 2648c2ecf20Sopenharmony_ci return ERR_PTR(ret); 2658c2ecf20Sopenharmony_ci} 2668c2ecf20Sopenharmony_ciEXPORT_SYMBOL(v4l2_clk_register); 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_civoid v4l2_clk_unregister(struct v4l2_clk *clk) 2698c2ecf20Sopenharmony_ci{ 2708c2ecf20Sopenharmony_ci if (WARN(atomic_read(&clk->use_count), 2718c2ecf20Sopenharmony_ci "%s(): Refusing to unregister ref-counted %s clock!\n", 2728c2ecf20Sopenharmony_ci __func__, clk->dev_id)) 2738c2ecf20Sopenharmony_ci return; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci mutex_lock(&clk_lock); 2768c2ecf20Sopenharmony_ci list_del(&clk->list); 2778c2ecf20Sopenharmony_ci mutex_unlock(&clk_lock); 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci kfree(clk->dev_id); 2808c2ecf20Sopenharmony_ci kfree(clk); 2818c2ecf20Sopenharmony_ci} 2828c2ecf20Sopenharmony_ciEXPORT_SYMBOL(v4l2_clk_unregister); 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_cistruct v4l2_clk_fixed { 2858c2ecf20Sopenharmony_ci unsigned long rate; 2868c2ecf20Sopenharmony_ci struct v4l2_clk_ops ops; 2878c2ecf20Sopenharmony_ci}; 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_cistatic unsigned long fixed_get_rate(struct v4l2_clk *clk) 2908c2ecf20Sopenharmony_ci{ 2918c2ecf20Sopenharmony_ci struct v4l2_clk_fixed *priv = clk->priv; 2928c2ecf20Sopenharmony_ci return priv->rate; 2938c2ecf20Sopenharmony_ci} 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_cistruct v4l2_clk *__v4l2_clk_register_fixed(const char *dev_id, 2968c2ecf20Sopenharmony_ci unsigned long rate, struct module *owner) 2978c2ecf20Sopenharmony_ci{ 2988c2ecf20Sopenharmony_ci struct v4l2_clk *clk; 2998c2ecf20Sopenharmony_ci struct v4l2_clk_fixed *priv = kzalloc(sizeof(*priv), GFP_KERNEL); 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ci if (!priv) 3028c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci priv->rate = rate; 3058c2ecf20Sopenharmony_ci priv->ops.get_rate = fixed_get_rate; 3068c2ecf20Sopenharmony_ci priv->ops.owner = owner; 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci clk = v4l2_clk_register(&priv->ops, dev_id, priv); 3098c2ecf20Sopenharmony_ci if (IS_ERR(clk)) 3108c2ecf20Sopenharmony_ci kfree(priv); 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ci return clk; 3138c2ecf20Sopenharmony_ci} 3148c2ecf20Sopenharmony_ciEXPORT_SYMBOL(__v4l2_clk_register_fixed); 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_civoid v4l2_clk_unregister_fixed(struct v4l2_clk *clk) 3178c2ecf20Sopenharmony_ci{ 3188c2ecf20Sopenharmony_ci kfree(clk->priv); 3198c2ecf20Sopenharmony_ci v4l2_clk_unregister(clk); 3208c2ecf20Sopenharmony_ci} 3218c2ecf20Sopenharmony_ciEXPORT_SYMBOL(v4l2_clk_unregister_fixed); 322