]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - drivers/opp/core.c
OPP: Don't remove dynamic OPPs from _dev_pm_opp_remove_table()
[linux.git] / drivers / opp / core.c
index 31ff03dbeb83771be1ba57fde89ea6c32f63c2aa..2319ad4a01776bf564dba2ab2f603c2cb6fc0a55 100644 (file)
@@ -48,9 +48,14 @@ static struct opp_device *_find_opp_dev(const struct device *dev,
 static struct opp_table *_find_opp_table_unlocked(struct device *dev)
 {
        struct opp_table *opp_table;
+       bool found;
 
        list_for_each_entry(opp_table, &opp_tables, node) {
-               if (_find_opp_dev(dev, opp_table)) {
+               mutex_lock(&opp_table->lock);
+               found = !!_find_opp_dev(dev, opp_table);
+               mutex_unlock(&opp_table->lock);
+
+               if (found) {
                        _get_opp_table_kref(opp_table);
 
                        return opp_table;
@@ -766,6 +771,8 @@ struct opp_device *_add_opp_dev(const struct device *dev,
 
        /* Initialize opp-dev */
        opp_dev->dev = dev;
+
+       mutex_lock(&opp_table->lock);
        list_add(&opp_dev->node, &opp_table->dev_list);
 
        /* Create debugfs entries for the opp_table */
@@ -773,11 +780,12 @@ struct opp_device *_add_opp_dev(const struct device *dev,
        if (ret)
                dev_err(dev, "%s: Failed to register opp debugfs (%d)\n",
                        __func__, ret);
+       mutex_unlock(&opp_table->lock);
 
        return opp_dev;
 }
 
-static struct opp_table *_allocate_opp_table(struct device *dev)
+static struct opp_table *_allocate_opp_table(struct device *dev, int index)
 {
        struct opp_table *opp_table;
        struct opp_device *opp_dev;
@@ -791,6 +799,7 @@ static struct opp_table *_allocate_opp_table(struct device *dev)
        if (!opp_table)
                return NULL;
 
+       mutex_init(&opp_table->lock);
        INIT_LIST_HEAD(&opp_table->dev_list);
 
        opp_dev = _add_opp_dev(dev, opp_table);
@@ -799,7 +808,7 @@ static struct opp_table *_allocate_opp_table(struct device *dev)
                return NULL;
        }
 
-       _of_init_opp_table(opp_table, dev);
+       _of_init_opp_table(opp_table, dev, index);
 
        /* Find clk for the device */
        opp_table->clk = clk_get(dev, NULL);
@@ -812,7 +821,6 @@ static struct opp_table *_allocate_opp_table(struct device *dev)
 
        BLOCKING_INIT_NOTIFIER_HEAD(&opp_table->head);
        INIT_LIST_HEAD(&opp_table->opp_list);
-       mutex_init(&opp_table->lock);
        kref_init(&opp_table->kref);
 
        /* Secure the device table modification */
@@ -825,7 +833,7 @@ 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)
+static struct opp_table *_opp_get_opp_table(struct device *dev, int index)
 {
        struct opp_table *opp_table;
 
@@ -836,15 +844,26 @@ struct opp_table *dev_pm_opp_get_opp_table(struct device *dev)
        if (!IS_ERR(opp_table))
                goto unlock;
 
-       opp_table = _allocate_opp_table(dev);
+       opp_table = _allocate_opp_table(dev, index);
 
 unlock:
        mutex_unlock(&opp_table_lock);
 
        return opp_table;
 }
+
+struct opp_table *dev_pm_opp_get_opp_table(struct device *dev)
+{
+       return _opp_get_opp_table(dev, 0);
+}
 EXPORT_SYMBOL_GPL(dev_pm_opp_get_opp_table);
 
+struct opp_table *dev_pm_opp_get_opp_table_indexed(struct device *dev,
+                                                  int index)
+{
+       return _opp_get_opp_table(dev, index);
+}
+
 static void _opp_table_kref_release(struct kref *kref)
 {
        struct opp_table *opp_table = container_of(kref, struct opp_table, kref);
@@ -854,6 +873,10 @@ static void _opp_table_kref_release(struct kref *kref)
        if (!IS_ERR(opp_table->clk))
                clk_put(opp_table->clk);
 
+       /*
+        * No need to take opp_table->lock here as we are guaranteed that no
+        * references to the OPP table are taken at this point.
+        */
        opp_dev = list_first_entry(&opp_table->dev_list, struct opp_device,
                                   node);
 
@@ -869,6 +892,33 @@ static void _opp_table_kref_release(struct kref *kref)
        mutex_unlock(&opp_table_lock);
 }
 
+void _opp_remove_all_static(struct opp_table *opp_table)
+{
+       struct dev_pm_opp *opp, *tmp;
+
+       list_for_each_entry_safe(opp, tmp, &opp_table->opp_list, node) {
+               if (!opp->dynamic)
+                       dev_pm_opp_put(opp);
+       }
+
+       opp_table->parsed_static_opps = false;
+}
+
+static void _opp_table_list_kref_release(struct kref *kref)
+{
+       struct opp_table *opp_table = container_of(kref, struct opp_table,
+                                                  list_kref);
+
+       _opp_remove_all_static(opp_table);
+       mutex_unlock(&opp_table_lock);
+}
+
+void _put_opp_list_kref(struct opp_table *opp_table)
+{
+       kref_put_mutex(&opp_table->list_kref, _opp_table_list_kref_release,
+                      &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,
@@ -896,7 +946,6 @@ static void _opp_kref_release(struct kref *kref)
        kfree(opp);
 
        mutex_unlock(&opp_table->lock);
-       dev_pm_opp_put_opp_table(opp_table);
 }
 
 void dev_pm_opp_get(struct dev_pm_opp *opp)
@@ -940,11 +989,15 @@ void dev_pm_opp_remove(struct device *dev, unsigned long freq)
 
        if (found) {
                dev_pm_opp_put(opp);
+
+               /* Drop the reference taken by dev_pm_opp_add() */
+               dev_pm_opp_put_opp_table(opp_table);
        } else {
                dev_warn(dev, "%s: Couldn't find OPP with freq: %lu\n",
                         __func__, freq);
        }
 
+       /* Drop the reference taken by _find_opp_table() */
        dev_pm_opp_put_opp_table(opp_table);
 }
 EXPORT_SYMBOL_GPL(dev_pm_opp_remove);
@@ -1062,9 +1115,6 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
        new_opp->opp_table = opp_table;
        kref_init(&new_opp->kref);
 
-       /* Get a reference to the OPP table */
-       _get_opp_table_kref(opp_table);
-
        ret = opp_debug_create_one(new_opp, opp_table);
        if (ret)
                dev_err(dev, "%s: Failed to register opp to debugfs (%d)\n",
@@ -1543,8 +1593,9 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
                return -ENOMEM;
 
        ret = _opp_add_v1(opp_table, dev, freq, u_volt, true);
+       if (ret)
+               dev_pm_opp_put_opp_table(opp_table);
 
-       dev_pm_opp_put_opp_table(opp_table);
        return ret;
 }
 EXPORT_SYMBOL_GPL(dev_pm_opp_add);
@@ -1708,21 +1759,17 @@ int dev_pm_opp_unregister_notifier(struct device *dev,
 EXPORT_SYMBOL(dev_pm_opp_unregister_notifier);
 
 /*
- * Free OPPs either created using static entries present in DT or even the
- * dynamically added entries based on remove_all param.
+ * Free OPPs either created using static entries present in DT.
  */
-void _dev_pm_opp_remove_table(struct opp_table *opp_table, struct device *dev,
-                             bool remove_all)
+void _dev_pm_opp_remove_table(struct opp_table *opp_table, struct device *dev)
 {
-       struct dev_pm_opp *opp, *tmp;
+       /* Protect dev_list */
+       mutex_lock(&opp_table->lock);
 
        /* Find if opp_table manages a single device */
        if (list_is_singular(&opp_table->dev_list)) {
                /* Free static OPPs */
-               list_for_each_entry_safe(opp, tmp, &opp_table->opp_list, node) {
-                       if (remove_all || !opp->dynamic)
-                               dev_pm_opp_put(opp);
-               }
+               _put_opp_list_kref(opp_table);
 
                /*
                 * The OPP table is getting removed, drop the performance state
@@ -1731,11 +1778,14 @@ void _dev_pm_opp_remove_table(struct opp_table *opp_table, struct device *dev,
                if (opp_table->genpd_performance_state)
                        dev_pm_genpd_set_performance_state(dev, 0);
        } else {
+               _put_opp_list_kref(opp_table);
                _remove_opp_dev(_find_opp_dev(dev, opp_table), opp_table);
        }
+
+       mutex_unlock(&opp_table->lock);
 }
 
-void _dev_pm_opp_find_and_remove_table(struct device *dev, bool remove_all)
+void _dev_pm_opp_find_and_remove_table(struct device *dev)
 {
        struct opp_table *opp_table;
 
@@ -1752,7 +1802,7 @@ void _dev_pm_opp_find_and_remove_table(struct device *dev, bool remove_all)
                return;
        }
 
-       _dev_pm_opp_remove_table(opp_table, dev, remove_all);
+       _dev_pm_opp_remove_table(opp_table, dev);
 
        dev_pm_opp_put_opp_table(opp_table);
 }
@@ -1766,6 +1816,6 @@ void _dev_pm_opp_find_and_remove_table(struct device *dev, bool remove_all)
  */
 void dev_pm_opp_remove_table(struct device *dev)
 {
-       _dev_pm_opp_find_and_remove_table(dev, true);
+       _dev_pm_opp_find_and_remove_table(dev);
 }
 EXPORT_SYMBOL_GPL(dev_pm_opp_remove_table);