diff options
author | Viresh Kumar <viresh.kumar@linaro.org> | 2022-05-25 15:23:16 +0530 |
---|---|---|
committer | Viresh Kumar <viresh.kumar@linaro.org> | 2022-07-08 11:27:32 +0530 |
commit | 11b9b663585c4f8b00846089ebbca4d1e3283e86 (patch) | |
tree | d34dcb1302c71e863815cb7a4d0c6d928f1c5d25 /drivers/opp/core.c | |
parent | 87686cc845c3be7dea777f1dbf2de0767007cda8 (diff) |
OPP: Add dev_pm_opp_set_config() and friends
The OPP core already have few configuration specific APIs and it is
getting complex or messy for both the OPP core and its users.
Lets introduce a new set of API which will be used for all kind of
different configurations, and shall eventually be used by all the
existing ones.
The new API, returns a unique token instead of a pointer to the OPP
table, which allows the OPP core to drop the resources selectively later
on.
Tested-by: Dmitry Osipenko <dmitry.osipenko@collabora.com>
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
Diffstat (limited to 'drivers/opp/core.c')
-rw-r--r-- | drivers/opp/core.c | 229 |
1 files changed, 228 insertions, 1 deletions
diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 4e4593957ec5..7ab20c3b91ed 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -13,11 +13,12 @@ #include <linux/clk.h> #include <linux/errno.h> #include <linux/err.h> -#include <linux/slab.h> #include <linux/device.h> #include <linux/export.h> #include <linux/pm_domain.h> #include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/xarray.h> #include "opp.h" @@ -36,6 +37,9 @@ DEFINE_MUTEX(opp_table_lock); /* Flag indicating that opp_tables list is being updated at the moment */ static bool opp_tables_busy; +/* OPP ID allocator */ +static DEFINE_XARRAY_ALLOC1(opp_configs); + static bool _find_opp_dev(const struct device *dev, struct opp_table *opp_table) { struct opp_device *opp_dev; @@ -2624,6 +2628,229 @@ int devm_pm_opp_attach_genpd(struct device *dev, const char * const *names, } EXPORT_SYMBOL_GPL(devm_pm_opp_attach_genpd); +static void _opp_clear_config(struct opp_config_data *data) +{ + if (data->flags & OPP_CONFIG_GENPD) + dev_pm_opp_detach_genpd(data->opp_table); + if (data->flags & OPP_CONFIG_REGULATOR) + dev_pm_opp_put_regulators(data->opp_table); + if (data->flags & OPP_CONFIG_SUPPORTED_HW) + dev_pm_opp_put_supported_hw(data->opp_table); + if (data->flags & OPP_CONFIG_REGULATOR_HELPER) + dev_pm_opp_unregister_set_opp_helper(data->opp_table); + if (data->flags & OPP_CONFIG_PROP_NAME) + dev_pm_opp_put_prop_name(data->opp_table); + if (data->flags & OPP_CONFIG_CLK) + dev_pm_opp_put_clkname(data->opp_table); + + dev_pm_opp_put_opp_table(data->opp_table); + kfree(data); +} + +/** + * dev_pm_opp_set_config() - Set OPP configuration for the device. + * @dev: Device for which configuration is being set. + * @config: OPP configuration. + * + * This allows all device OPP configurations to be performed at once. + * + * This must be called before any OPPs are initialized for the device. This may + * be called multiple times for the same OPP table, for example once for each + * CPU that share the same table. This must be balanced by the same number of + * calls to dev_pm_opp_clear_config() in order to free the OPP table properly. + * + * This returns a token to the caller, which must be passed to + * dev_pm_opp_clear_config() to free the resources later. The value of the + * returned token will be >= 1 for success and negative for errors. The minimum + * value of 1 is chosen here to make it easy for callers to manage the resource. + */ +int dev_pm_opp_set_config(struct device *dev, struct dev_pm_opp_config *config) +{ + struct opp_table *opp_table, *err; + struct opp_config_data *data; + unsigned int id; + int ret; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + opp_table = _add_opp_table(dev, false); + if (IS_ERR(opp_table)) { + kfree(data); + return PTR_ERR(opp_table); + } + + data->opp_table = opp_table; + data->flags = 0; + + /* This should be called before OPPs are initialized */ + if (WARN_ON(!list_empty(&opp_table->opp_list))) { + ret = -EBUSY; + goto err; + } + + /* Configure clocks */ + if (config->clk_names) { + const char * const *temp = config->clk_names; + int count = 0; + + /* Count number of clks */ + while (*temp++) + count++; + + /* + * This is a special case where we have a single clock, whose + * connection id name is NULL, i.e. first two entries are NULL + * in the array. + */ + if (!count && !config->clk_names[1]) + count = 1; + + /* We support only one clock name for now */ + if (count != 1) { + ret = -EINVAL; + goto err; + } + + err = dev_pm_opp_set_clkname(dev, config->clk_names[0]); + if (IS_ERR(err)) { + ret = PTR_ERR(err); + goto err; + } + + data->flags |= OPP_CONFIG_CLK; + } + + /* Configure property names */ + if (config->prop_name) { + err = dev_pm_opp_set_prop_name(dev, config->prop_name); + if (IS_ERR(err)) { + ret = PTR_ERR(err); + goto err; + } + + data->flags |= OPP_CONFIG_PROP_NAME; + } + + /* Configure opp helper */ + if (config->set_opp) { + err = dev_pm_opp_register_set_opp_helper(dev, config->set_opp); + if (IS_ERR(err)) { + ret = PTR_ERR(err); + goto err; + } + + data->flags |= OPP_CONFIG_REGULATOR_HELPER; + } + + /* Configure supported hardware */ + if (config->supported_hw) { + err = dev_pm_opp_set_supported_hw(dev, config->supported_hw, + config->supported_hw_count); + if (IS_ERR(err)) { + ret = PTR_ERR(err); + goto err; + } + + data->flags |= OPP_CONFIG_SUPPORTED_HW; + } + + /* Configure supplies */ + if (config->regulator_names) { + err = dev_pm_opp_set_regulators(dev, config->regulator_names); + if (IS_ERR(err)) { + ret = PTR_ERR(err); + goto err; + } + + data->flags |= OPP_CONFIG_REGULATOR; + } + + /* Attach genpds */ + if (config->genpd_names) { + err = dev_pm_opp_attach_genpd(dev, config->genpd_names, + config->virt_devs); + if (IS_ERR(err)) { + ret = PTR_ERR(err); + goto err; + } + + data->flags |= OPP_CONFIG_GENPD; + } + + ret = xa_alloc(&opp_configs, &id, data, XA_LIMIT(1, INT_MAX), + GFP_KERNEL); + if (ret) + goto err; + + return id; + +err: + _opp_clear_config(data); + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_set_config); + +/** + * dev_pm_opp_clear_config() - Releases resources blocked for OPP configuration. + * @opp_table: OPP table returned from dev_pm_opp_set_config(). + * + * This allows all device OPP configurations to be cleared at once. This must be + * called once for each call made to dev_pm_opp_set_config(), in order to free + * the OPPs properly. + * + * Currently the first call itself ends up freeing all the OPP configurations, + * while the later ones only drop the OPP table reference. This works well for + * now as we would never want to use an half initialized OPP table and want to + * remove the configurations together. + */ +void dev_pm_opp_clear_config(int token) +{ + struct opp_config_data *data; + + /* + * This lets the callers call this unconditionally and keep their code + * simple. + */ + if (unlikely(token <= 0)) + return; + + data = xa_erase(&opp_configs, token); + if (WARN_ON(!data)) + return; + + _opp_clear_config(data); +} +EXPORT_SYMBOL_GPL(dev_pm_opp_clear_config); + +static void devm_pm_opp_config_release(void *token) +{ + dev_pm_opp_clear_config((unsigned long)token); +} + +/** + * devm_pm_opp_set_config() - Set OPP configuration for the device. + * @dev: Device for which configuration is being set. + * @config: OPP configuration. + * + * This allows all device OPP configurations to be performed at once. + * This is a resource-managed variant of dev_pm_opp_set_config(). + * + * Return: 0 on success and errorno otherwise. + */ +int devm_pm_opp_set_config(struct device *dev, struct dev_pm_opp_config *config) +{ + int token = dev_pm_opp_set_config(dev, config); + + if (token < 0) + return token; + + return devm_add_action_or_reset(dev, devm_pm_opp_config_release, + (void *) ((unsigned long) token)); +} +EXPORT_SYMBOL_GPL(devm_pm_opp_set_config); + /** * dev_pm_opp_xlate_required_opp() - Find required OPP for @src_table OPP. * @src_table: OPP table which has @dst_table as one of its required OPP table. |