summaryrefslogtreecommitdiffstats
path: root/drivers/base
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/base')
-rw-r--r--drivers/base/power/opp/core.c51
-rw-r--r--drivers/base/power/opp/opp.h3
2 files changed, 52 insertions, 2 deletions
diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index dcebd5efb6a1..ccc0d8913fd0 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -855,6 +855,7 @@ static struct opp_table *_allocate_opp_table(struct device *dev)
srcu_init_notifier_head(&opp_table->srcu_head);
INIT_LIST_HEAD(&opp_table->opp_list);
mutex_init(&opp_table->lock);
+ kref_init(&opp_table->kref);
/* Secure the device table modification */
list_add_rcu(&opp_table->node, &opp_tables);
@@ -894,8 +895,36 @@ static void _kfree_device_rcu(struct rcu_head *head)
kfree_rcu(opp_table, rcu_head);
}
-static void _free_opp_table(struct opp_table *opp_table)
+void _get_opp_table_kref(struct opp_table *opp_table)
{
+ kref_get(&opp_table->kref);
+}
+
+struct opp_table *dev_pm_opp_get_opp_table(struct device *dev)
+{
+ struct opp_table *opp_table;
+
+ /* Hold our table modification lock here */
+ mutex_lock(&opp_table_lock);
+
+ opp_table = _find_opp_table(dev);
+ if (!IS_ERR(opp_table)) {
+ _get_opp_table_kref(opp_table);
+ goto unlock;
+ }
+
+ opp_table = _allocate_opp_table(dev);
+
+unlock:
+ mutex_unlock(&opp_table_lock);
+
+ return opp_table;
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_get_opp_table);
+
+static void _opp_table_kref_release_unlocked(struct kref *kref)
+{
+ struct opp_table *opp_table = container_of(kref, struct opp_table, kref);
struct opp_device *opp_dev;
/* Release clk */
@@ -916,6 +945,24 @@ static void _free_opp_table(struct opp_table *opp_table)
_kfree_device_rcu);
}
+static void dev_pm_opp_put_opp_table_unlocked(struct opp_table *opp_table)
+{
+ kref_put(&opp_table->kref, _opp_table_kref_release_unlocked);
+}
+
+static void _opp_table_kref_release(struct kref *kref)
+{
+ _opp_table_kref_release_unlocked(kref);
+ mutex_unlock(&opp_table_lock);
+}
+
+void dev_pm_opp_put_opp_table(struct opp_table *opp_table)
+{
+ kref_put_mutex(&opp_table->kref, _opp_table_kref_release,
+ &opp_table_lock);
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_put_opp_table);
+
/**
* _remove_opp_table() - Removes a OPP table
* @opp_table: OPP table to be removed.
@@ -939,7 +986,7 @@ static void _remove_opp_table(struct opp_table *opp_table)
if (opp_table->set_opp)
return;
- _free_opp_table(opp_table);
+ dev_pm_opp_put_opp_table_unlocked(opp_table);
}
void _opp_free(struct dev_pm_opp *opp)
diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h
index 105243b06373..aae4d8f480ef 100644
--- a/drivers/base/power/opp/opp.h
+++ b/drivers/base/power/opp/opp.h
@@ -131,6 +131,7 @@ enum opp_table_access {
* @rcu_head: RCU callback head used for deferred freeing
* @dev_list: list of devices that share these OPPs
* @opp_list: table of opps
+ * @kref: for reference count of the table.
* @lock: mutex protecting the opp_list.
* @np: struct device_node pointer for opp's DT node.
* @clock_latency_ns_max: Max clock latency in nanoseconds.
@@ -164,6 +165,7 @@ struct opp_table {
struct rcu_head rcu_head;
struct list_head dev_list;
struct list_head opp_list;
+ struct kref kref;
struct mutex lock;
struct device_node *np;
@@ -192,6 +194,7 @@ struct opp_table {
};
/* Routines internal to opp core */
+void _get_opp_table_kref(struct opp_table *opp_table);
struct opp_table *_find_opp_table(struct device *dev);
struct opp_table *_add_opp_table(struct device *dev);
struct opp_device *_add_opp_dev(const struct device *dev, struct opp_table *opp_table);