]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
OPP: Add dev_pm_opp_set_config() and friends
authorViresh Kumar <viresh.kumar@linaro.org>
Wed, 25 May 2022 09:53:16 +0000 (15:23 +0530)
committerViresh Kumar <viresh.kumar@linaro.org>
Fri, 8 Jul 2022 05:57:32 +0000 (11:27 +0530)
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>
drivers/opp/core.c
drivers/opp/opp.h
include/linux/pm_opp.h

index 4e4593957ec54c73d7a18fd16706f3c2dfb78b1b..7ab20c3b91ed6cb5188a48b6d5137cd59b00f603 100644 (file)
 #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.
index 9e1cfcb0ea98417522647ee15aa70fb5c75f634c..d652f0cc84f1b8a62dbf159d0f218f7cb64ae30e 100644 (file)
@@ -28,6 +28,27 @@ extern struct mutex opp_table_lock;
 
 extern struct list_head opp_tables, lazy_opp_tables;
 
+/* OPP Config flags */
+#define OPP_CONFIG_CLK                 BIT(0)
+#define OPP_CONFIG_REGULATOR           BIT(1)
+#define OPP_CONFIG_REGULATOR_HELPER    BIT(2)
+#define OPP_CONFIG_PROP_NAME           BIT(3)
+#define OPP_CONFIG_SUPPORTED_HW                BIT(4)
+#define OPP_CONFIG_GENPD               BIT(5)
+
+/**
+ * struct opp_config_data - data for set config operations
+ * @opp_table: OPP table
+ * @flags: OPP config flags
+ *
+ * This structure stores the OPP config information for each OPP table
+ * configuration by the callers.
+ */
+struct opp_config_data {
+       struct opp_table *opp_table;
+       unsigned int flags;
+};
+
 /*
  * Internal data structure organization with the OPP layer library is as
  * follows:
index 4c490865d5746c7a8b450e1eb295bd785d232cfb..a08f9481efb32443ed0f4a146cd7742b01ba451b 100644 (file)
@@ -90,6 +90,32 @@ struct dev_pm_set_opp_data {
        struct device *dev;
 };
 
+/**
+ * struct dev_pm_opp_config - Device OPP configuration values
+ * @clk_names: Clk names, NULL terminated array, max 1 clock for now.
+ * @prop_name: Name to postfix to properties.
+ * @set_opp: Custom set OPP helper.
+ * @supported_hw: Array of hierarchy of versions to match.
+ * @supported_hw_count: Number of elements in the array.
+ * @regulator_names: Array of pointers to the names of the regulator, NULL terminated.
+ * @genpd_names: Null terminated array of pointers containing names of genpd to
+ *              attach.
+ * @virt_devs: Pointer to return the array of virtual devices.
+ *
+ * This structure contains platform specific OPP configurations for the device.
+ */
+struct dev_pm_opp_config {
+       /* NULL terminated */
+       const char * const *clk_names;
+       const char *prop_name;
+       int (*set_opp)(struct dev_pm_set_opp_data *data);
+       const unsigned int *supported_hw;
+       unsigned int supported_hw_count;
+       const char * const *regulator_names;
+       const char * const *genpd_names;
+       struct device ***virt_devs;
+};
+
 #if defined(CONFIG_PM_OPP)
 
 struct opp_table *dev_pm_opp_get_opp_table(struct device *dev);
@@ -154,6 +180,10 @@ int dev_pm_opp_disable(struct device *dev, unsigned long freq);
 int dev_pm_opp_register_notifier(struct device *dev, struct notifier_block *nb);
 int dev_pm_opp_unregister_notifier(struct device *dev, struct notifier_block *nb);
 
+int dev_pm_opp_set_config(struct device *dev, struct dev_pm_opp_config *config);
+int devm_pm_opp_set_config(struct device *dev, struct dev_pm_opp_config *config);
+void dev_pm_opp_clear_config(int token);
+
 struct opp_table *dev_pm_opp_set_supported_hw(struct device *dev, const u32 *versions, unsigned int count);
 void dev_pm_opp_put_supported_hw(struct opp_table *opp_table);
 int devm_pm_opp_set_supported_hw(struct device *dev, const u32 *versions, unsigned int count);
@@ -418,6 +448,18 @@ static inline int devm_pm_opp_attach_genpd(struct device *dev,
        return -EOPNOTSUPP;
 }
 
+static inline int dev_pm_opp_set_config(struct device *dev, struct dev_pm_opp_config *config)
+{
+       return -EOPNOTSUPP;
+}
+
+static inline int devm_pm_opp_set_config(struct device *dev, struct dev_pm_opp_config *config)
+{
+       return -EOPNOTSUPP;
+}
+
+static inline void dev_pm_opp_clear_config(int token) {}
+
 static inline struct dev_pm_opp *dev_pm_opp_xlate_required_opp(struct opp_table *src_table,
                                struct opp_table *dst_table, struct dev_pm_opp *src_opp)
 {