Commit fb64207b authored by Rafael J. Wysocki's avatar Rafael J. Wysocki

Merge branch 'opp/linux-next' of...

Merge branch 'opp/linux-next' of git://git.kernel.org/pub/scm/linux/kernel/git/vireshk/pm into pm-opp

Pull operating performance points (OPP) material for 4.20 from Viresh Kumar.

"This contains patches that fix several bugs in the OPP core and
makes it more stable."

* 'opp/linux-next' of git://git.kernel.org/pub/scm/linux/kernel/git/vireshk/pm:
  OPP: Pass OPP table to _of_add_opp_table_v{1|2}()
  OPP: Prevent creating multiple OPP tables for devices sharing OPP nodes
  OPP: Use a single mechanism to free the OPP table
  OPP: Don't remove dynamic OPPs from _dev_pm_opp_remove_table()
  cpufreq: mvebu: Remove OPPs using dev_pm_opp_remove()
  OPP: Create separate kref for static OPPs list
  OPP: Don't take OPP table's kref for static OPPs
  OPP: Parse OPP table's DT properties from _of_init_opp_table()
  OPP: Pass index to _of_init_opp_table()
  OPP: Protect dev_list with opp_table lock
  OPP: Don't try to remove all OPP tables on failure
  OPP: Free OPP table properly on performance state irregularities
parents 17b57b18 5ed4cecd
...@@ -84,9 +84,10 @@ static int __init armada_xp_pmsu_cpufreq_init(void) ...@@ -84,9 +84,10 @@ static int __init armada_xp_pmsu_cpufreq_init(void)
ret = dev_pm_opp_add(cpu_dev, clk_get_rate(clk) / 2, 0); ret = dev_pm_opp_add(cpu_dev, clk_get_rate(clk) / 2, 0);
if (ret) { if (ret) {
dev_pm_opp_remove(cpu_dev, clk_get_rate(clk));
clk_put(clk); clk_put(clk);
dev_err(cpu_dev, "Failed to register OPPs\n"); dev_err(cpu_dev, "Failed to register OPPs\n");
goto opp_register_failed; return ret;
} }
ret = dev_pm_opp_set_sharing_cpus(cpu_dev, ret = dev_pm_opp_set_sharing_cpus(cpu_dev,
...@@ -99,11 +100,5 @@ static int __init armada_xp_pmsu_cpufreq_init(void) ...@@ -99,11 +100,5 @@ static int __init armada_xp_pmsu_cpufreq_init(void)
platform_device_register_simple("cpufreq-dt", -1, NULL, 0); platform_device_register_simple("cpufreq-dt", -1, NULL, 0);
return 0; return 0;
opp_register_failed:
/* As registering has failed remove all the opp for all cpus */
dev_pm_opp_cpumask_remove_table(cpu_possible_mask);
return ret;
} }
device_initcall(armada_xp_pmsu_cpufreq_init); device_initcall(armada_xp_pmsu_cpufreq_init);
...@@ -48,9 +48,14 @@ static struct opp_device *_find_opp_dev(const struct device *dev, ...@@ -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) static struct opp_table *_find_opp_table_unlocked(struct device *dev)
{ {
struct opp_table *opp_table; struct opp_table *opp_table;
bool found;
list_for_each_entry(opp_table, &opp_tables, node) { 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); _get_opp_table_kref(opp_table);
return opp_table; return opp_table;
...@@ -754,8 +759,8 @@ static void _remove_opp_dev(struct opp_device *opp_dev, ...@@ -754,8 +759,8 @@ static void _remove_opp_dev(struct opp_device *opp_dev,
kfree(opp_dev); kfree(opp_dev);
} }
struct opp_device *_add_opp_dev(const struct device *dev, static struct opp_device *_add_opp_dev_unlocked(const struct device *dev,
struct opp_table *opp_table) struct opp_table *opp_table)
{ {
struct opp_device *opp_dev; struct opp_device *opp_dev;
int ret; int ret;
...@@ -766,6 +771,7 @@ struct opp_device *_add_opp_dev(const struct device *dev, ...@@ -766,6 +771,7 @@ struct opp_device *_add_opp_dev(const struct device *dev,
/* Initialize opp-dev */ /* Initialize opp-dev */
opp_dev->dev = dev; opp_dev->dev = dev;
list_add(&opp_dev->node, &opp_table->dev_list); list_add(&opp_dev->node, &opp_table->dev_list);
/* Create debugfs entries for the opp_table */ /* Create debugfs entries for the opp_table */
...@@ -777,7 +783,19 @@ struct opp_device *_add_opp_dev(const struct device *dev, ...@@ -777,7 +783,19 @@ struct opp_device *_add_opp_dev(const struct device *dev,
return opp_dev; return opp_dev;
} }
static struct opp_table *_allocate_opp_table(struct device *dev) struct opp_device *_add_opp_dev(const struct device *dev,
struct opp_table *opp_table)
{
struct opp_device *opp_dev;
mutex_lock(&opp_table->lock);
opp_dev = _add_opp_dev_unlocked(dev, opp_table);
mutex_unlock(&opp_table->lock);
return opp_dev;
}
static struct opp_table *_allocate_opp_table(struct device *dev, int index)
{ {
struct opp_table *opp_table; struct opp_table *opp_table;
struct opp_device *opp_dev; struct opp_device *opp_dev;
...@@ -791,6 +809,7 @@ static struct opp_table *_allocate_opp_table(struct device *dev) ...@@ -791,6 +809,7 @@ static struct opp_table *_allocate_opp_table(struct device *dev)
if (!opp_table) if (!opp_table)
return NULL; return NULL;
mutex_init(&opp_table->lock);
INIT_LIST_HEAD(&opp_table->dev_list); INIT_LIST_HEAD(&opp_table->dev_list);
opp_dev = _add_opp_dev(dev, opp_table); opp_dev = _add_opp_dev(dev, opp_table);
...@@ -799,7 +818,7 @@ static struct opp_table *_allocate_opp_table(struct device *dev) ...@@ -799,7 +818,7 @@ static struct opp_table *_allocate_opp_table(struct device *dev)
return NULL; return NULL;
} }
_of_init_opp_table(opp_table, dev); _of_init_opp_table(opp_table, dev, index);
/* Find clk for the device */ /* Find clk for the device */
opp_table->clk = clk_get(dev, NULL); opp_table->clk = clk_get(dev, NULL);
...@@ -812,7 +831,6 @@ static struct opp_table *_allocate_opp_table(struct device *dev) ...@@ -812,7 +831,6 @@ static struct opp_table *_allocate_opp_table(struct device *dev)
BLOCKING_INIT_NOTIFIER_HEAD(&opp_table->head); BLOCKING_INIT_NOTIFIER_HEAD(&opp_table->head);
INIT_LIST_HEAD(&opp_table->opp_list); INIT_LIST_HEAD(&opp_table->opp_list);
mutex_init(&opp_table->lock);
kref_init(&opp_table->kref); kref_init(&opp_table->kref);
/* Secure the device table modification */ /* Secure the device table modification */
...@@ -825,7 +843,7 @@ void _get_opp_table_kref(struct opp_table *opp_table) ...@@ -825,7 +843,7 @@ void _get_opp_table_kref(struct opp_table *opp_table)
kref_get(&opp_table->kref); 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; struct opp_table *opp_table;
...@@ -836,31 +854,56 @@ struct opp_table *dev_pm_opp_get_opp_table(struct device *dev) ...@@ -836,31 +854,56 @@ struct opp_table *dev_pm_opp_get_opp_table(struct device *dev)
if (!IS_ERR(opp_table)) if (!IS_ERR(opp_table))
goto unlock; goto unlock;
opp_table = _allocate_opp_table(dev); opp_table = _managed_opp(dev, index);
if (opp_table) {
if (!_add_opp_dev_unlocked(dev, opp_table)) {
dev_pm_opp_put_opp_table(opp_table);
opp_table = NULL;
}
goto unlock;
}
opp_table = _allocate_opp_table(dev, index);
unlock: unlock:
mutex_unlock(&opp_table_lock); mutex_unlock(&opp_table_lock);
return opp_table; 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); 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) static void _opp_table_kref_release(struct kref *kref)
{ {
struct opp_table *opp_table = container_of(kref, struct opp_table, kref); struct opp_table *opp_table = container_of(kref, struct opp_table, kref);
struct opp_device *opp_dev; struct opp_device *opp_dev, *temp;
/* Release clk */ /* Release clk */
if (!IS_ERR(opp_table->clk)) if (!IS_ERR(opp_table->clk))
clk_put(opp_table->clk); clk_put(opp_table->clk);
opp_dev = list_first_entry(&opp_table->dev_list, struct opp_device, WARN_ON(!list_empty(&opp_table->opp_list));
node);
_remove_opp_dev(opp_dev, opp_table); list_for_each_entry_safe(opp_dev, temp, &opp_table->dev_list, node) {
/*
* The OPP table is getting removed, drop the performance state
* constraints.
*/
if (opp_table->genpd_performance_state)
dev_pm_genpd_set_performance_state((struct device *)(opp_dev->dev), 0);
/* dev_list must be empty now */ _remove_opp_dev(opp_dev, opp_table);
WARN_ON(!list_empty(&opp_table->dev_list)); }
mutex_destroy(&opp_table->lock); mutex_destroy(&opp_table->lock);
list_del(&opp_table->node); list_del(&opp_table->node);
...@@ -869,6 +912,33 @@ static void _opp_table_kref_release(struct kref *kref) ...@@ -869,6 +912,33 @@ static void _opp_table_kref_release(struct kref *kref)
mutex_unlock(&opp_table_lock); 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) void dev_pm_opp_put_opp_table(struct opp_table *opp_table)
{ {
kref_put_mutex(&opp_table->kref, _opp_table_kref_release, kref_put_mutex(&opp_table->kref, _opp_table_kref_release,
...@@ -896,7 +966,6 @@ static void _opp_kref_release(struct kref *kref) ...@@ -896,7 +966,6 @@ static void _opp_kref_release(struct kref *kref)
kfree(opp); kfree(opp);
mutex_unlock(&opp_table->lock); mutex_unlock(&opp_table->lock);
dev_pm_opp_put_opp_table(opp_table);
} }
void dev_pm_opp_get(struct dev_pm_opp *opp) void dev_pm_opp_get(struct dev_pm_opp *opp)
...@@ -940,11 +1009,15 @@ void dev_pm_opp_remove(struct device *dev, unsigned long freq) ...@@ -940,11 +1009,15 @@ void dev_pm_opp_remove(struct device *dev, unsigned long freq)
if (found) { if (found) {
dev_pm_opp_put(opp); dev_pm_opp_put(opp);
/* Drop the reference taken by dev_pm_opp_add() */
dev_pm_opp_put_opp_table(opp_table);
} else { } else {
dev_warn(dev, "%s: Couldn't find OPP with freq: %lu\n", dev_warn(dev, "%s: Couldn't find OPP with freq: %lu\n",
__func__, freq); __func__, freq);
} }
/* Drop the reference taken by _find_opp_table() */
dev_pm_opp_put_opp_table(opp_table); dev_pm_opp_put_opp_table(opp_table);
} }
EXPORT_SYMBOL_GPL(dev_pm_opp_remove); EXPORT_SYMBOL_GPL(dev_pm_opp_remove);
...@@ -1062,9 +1135,6 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, ...@@ -1062,9 +1135,6 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
new_opp->opp_table = opp_table; new_opp->opp_table = opp_table;
kref_init(&new_opp->kref); 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); ret = opp_debug_create_one(new_opp, opp_table);
if (ret) if (ret)
dev_err(dev, "%s: Failed to register opp to debugfs (%d)\n", dev_err(dev, "%s: Failed to register opp to debugfs (%d)\n",
...@@ -1543,8 +1613,9 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) ...@@ -1543,8 +1613,9 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
return -ENOMEM; return -ENOMEM;
ret = _opp_add_v1(opp_table, dev, freq, u_volt, true); 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; return ret;
} }
EXPORT_SYMBOL_GPL(dev_pm_opp_add); EXPORT_SYMBOL_GPL(dev_pm_opp_add);
...@@ -1707,35 +1778,7 @@ int dev_pm_opp_unregister_notifier(struct device *dev, ...@@ -1707,35 +1778,7 @@ int dev_pm_opp_unregister_notifier(struct device *dev,
} }
EXPORT_SYMBOL(dev_pm_opp_unregister_notifier); EXPORT_SYMBOL(dev_pm_opp_unregister_notifier);
/* void _dev_pm_opp_find_and_remove_table(struct device *dev)
* Free OPPs either created using static entries present in DT or even the
* dynamically added entries based on remove_all param.
*/
void _dev_pm_opp_remove_table(struct opp_table *opp_table, struct device *dev,
bool remove_all)
{
struct dev_pm_opp *opp, *tmp;
/* 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);
}
/*
* The OPP table is getting removed, drop the performance state
* constraints.
*/
if (opp_table->genpd_performance_state)
dev_pm_genpd_set_performance_state(dev, 0);
} else {
_remove_opp_dev(_find_opp_dev(dev, opp_table), opp_table);
}
}
void _dev_pm_opp_find_and_remove_table(struct device *dev, bool remove_all)
{ {
struct opp_table *opp_table; struct opp_table *opp_table;
...@@ -1752,8 +1795,12 @@ void _dev_pm_opp_find_and_remove_table(struct device *dev, bool remove_all) ...@@ -1752,8 +1795,12 @@ void _dev_pm_opp_find_and_remove_table(struct device *dev, bool remove_all)
return; return;
} }
_dev_pm_opp_remove_table(opp_table, dev, remove_all); _put_opp_list_kref(opp_table);
/* Drop reference taken by _find_opp_table() */
dev_pm_opp_put_opp_table(opp_table);
/* Drop reference taken while the OPP table was added */
dev_pm_opp_put_opp_table(opp_table); dev_pm_opp_put_opp_table(opp_table);
} }
...@@ -1766,6 +1813,6 @@ void _dev_pm_opp_find_and_remove_table(struct device *dev, bool remove_all) ...@@ -1766,6 +1813,6 @@ void _dev_pm_opp_find_and_remove_table(struct device *dev, bool remove_all)
*/ */
void dev_pm_opp_remove_table(struct device *dev) 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); EXPORT_SYMBOL_GPL(dev_pm_opp_remove_table);
...@@ -108,7 +108,8 @@ void dev_pm_opp_free_cpufreq_table(struct device *dev, ...@@ -108,7 +108,8 @@ void dev_pm_opp_free_cpufreq_table(struct device *dev,
EXPORT_SYMBOL_GPL(dev_pm_opp_free_cpufreq_table); EXPORT_SYMBOL_GPL(dev_pm_opp_free_cpufreq_table);
#endif /* CONFIG_CPU_FREQ */ #endif /* CONFIG_CPU_FREQ */
void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, bool of) void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask,
int last_cpu)
{ {
struct device *cpu_dev; struct device *cpu_dev;
int cpu; int cpu;
...@@ -116,6 +117,9 @@ void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, bool of) ...@@ -116,6 +117,9 @@ void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, bool of)
WARN_ON(cpumask_empty(cpumask)); WARN_ON(cpumask_empty(cpumask));
for_each_cpu(cpu, cpumask) { for_each_cpu(cpu, cpumask) {
if (cpu == last_cpu)
break;
cpu_dev = get_cpu_device(cpu); cpu_dev = get_cpu_device(cpu);
if (!cpu_dev) { if (!cpu_dev) {
pr_err("%s: failed to get cpu%d device\n", __func__, pr_err("%s: failed to get cpu%d device\n", __func__,
...@@ -123,10 +127,7 @@ void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, bool of) ...@@ -123,10 +127,7 @@ void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, bool of)
continue; continue;
} }
if (of) _dev_pm_opp_find_and_remove_table(cpu_dev);
dev_pm_opp_of_remove_table(cpu_dev);
else
dev_pm_opp_remove_table(cpu_dev);
} }
} }
...@@ -140,7 +141,7 @@ void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, bool of) ...@@ -140,7 +141,7 @@ void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, bool of)
*/ */
void dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask) void dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask)
{ {
_dev_pm_opp_cpumask_remove_table(cpumask, false); _dev_pm_opp_cpumask_remove_table(cpumask, -1);
} }
EXPORT_SYMBOL_GPL(dev_pm_opp_cpumask_remove_table); EXPORT_SYMBOL_GPL(dev_pm_opp_cpumask_remove_table);
...@@ -222,8 +223,10 @@ int dev_pm_opp_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask) ...@@ -222,8 +223,10 @@ int dev_pm_opp_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
cpumask_clear(cpumask); cpumask_clear(cpumask);
if (opp_table->shared_opp == OPP_TABLE_ACCESS_SHARED) { if (opp_table->shared_opp == OPP_TABLE_ACCESS_SHARED) {
mutex_lock(&opp_table->lock);
list_for_each_entry(opp_dev, &opp_table->dev_list, node) list_for_each_entry(opp_dev, &opp_table->dev_list, node)
cpumask_set_cpu(opp_dev->dev->id, cpumask); cpumask_set_cpu(opp_dev->dev->id, cpumask);
mutex_unlock(&opp_table->lock);
} else { } else {
cpumask_set_cpu(cpu_dev->id, cpumask); cpumask_set_cpu(cpu_dev->id, cpumask);
} }
......
...@@ -23,11 +23,32 @@ ...@@ -23,11 +23,32 @@
#include "opp.h" #include "opp.h"
static struct opp_table *_managed_opp(const struct device_node *np) /*
* Returns opp descriptor node for a device node, caller must
* do of_node_put().
*/
static struct device_node *_opp_of_get_opp_desc_node(struct device_node *np,
int index)
{
/* "operating-points-v2" can be an array for power domain providers */
return of_parse_phandle(np, "operating-points-v2", index);
}
/* Returns opp descriptor node for a device, caller must do of_node_put() */
struct device_node *dev_pm_opp_of_get_opp_desc_node(struct device *dev)
{
return _opp_of_get_opp_desc_node(dev->of_node, 0);
}
EXPORT_SYMBOL_GPL(dev_pm_opp_of_get_opp_desc_node);
struct opp_table *_managed_opp(struct device *dev, int index)
{ {
struct opp_table *opp_table, *managed_table = NULL; struct opp_table *opp_table, *managed_table = NULL;
struct device_node *np;
mutex_lock(&opp_table_lock); np = _opp_of_get_opp_desc_node(dev->of_node, index);
if (!np)
return NULL;
list_for_each_entry(opp_table, &opp_tables, node) { list_for_each_entry(opp_table, &opp_tables, node) {
if (opp_table->np == np) { if (opp_table->np == np) {
...@@ -47,29 +68,45 @@ static struct opp_table *_managed_opp(const struct device_node *np) ...@@ -47,29 +68,45 @@ static struct opp_table *_managed_opp(const struct device_node *np)
} }
} }
mutex_unlock(&opp_table_lock); of_node_put(np);
return managed_table; return managed_table;
} }
void _of_init_opp_table(struct opp_table *opp_table, struct device *dev) void _of_init_opp_table(struct opp_table *opp_table, struct device *dev,
int index)
{ {
struct device_node *np; struct device_node *np, *opp_np;
u32 val;
/* /*
* Only required for backward compatibility with v1 bindings, but isn't * Only required for backward compatibility with v1 bindings, but isn't
* harmful for other cases. And so we do it unconditionally. * harmful for other cases. And so we do it unconditionally.
*/ */
np = of_node_get(dev->of_node); np = of_node_get(dev->of_node);
if (np) { if (!np)
u32 val; return;
if (!of_property_read_u32(np, "clock-latency", &val)) if (!of_property_read_u32(np, "clock-latency", &val))
opp_table->clock_latency_ns_max = val; opp_table->clock_latency_ns_max = val;
of_property_read_u32(np, "voltage-tolerance", of_property_read_u32(np, "voltage-tolerance",
&opp_table->voltage_tolerance_v1); &opp_table->voltage_tolerance_v1);
of_node_put(np);
} /* Get OPP table node */
opp_np = _opp_of_get_opp_desc_node(np, index);
of_node_put(np);
if (!opp_np)
return;
if (of_property_read_bool(opp_np, "opp-shared"))
opp_table->shared_opp = OPP_TABLE_ACCESS_SHARED;
else
opp_table->shared_opp = OPP_TABLE_ACCESS_EXCLUSIVE;
opp_table->np = opp_np;
of_node_put(opp_np);
} }
static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table, static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table,
...@@ -245,26 +282,10 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev, ...@@ -245,26 +282,10 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
*/ */
void dev_pm_opp_of_remove_table(struct device *dev) void dev_pm_opp_of_remove_table(struct device *dev)
{ {
_dev_pm_opp_find_and_remove_table(dev, false); _dev_pm_opp_find_and_remove_table(dev);
} }
EXPORT_SYMBOL_GPL(dev_pm_opp_of_remove_table); EXPORT_SYMBOL_GPL(dev_pm_opp_of_remove_table);
/* Returns opp descriptor node for a device node, caller must
* do of_node_put() */
static struct device_node *_opp_of_get_opp_desc_node(struct device_node *np,
int index)
{
/* "operating-points-v2" can be an array for power domain providers */
return of_parse_phandle(np, "operating-points-v2", index);
}
/* Returns opp descriptor node for a device, caller must do of_node_put() */
struct device_node *dev_pm_opp_of_get_opp_desc_node(struct device *dev)
{
return _opp_of_get_opp_desc_node(dev->of_node, 0);
}
EXPORT_SYMBOL_GPL(dev_pm_opp_of_get_opp_desc_node);
/** /**
* _opp_add_static_v2() - Allocate static OPPs (As per 'v2' DT bindings) * _opp_add_static_v2() - Allocate static OPPs (As per 'v2' DT bindings)
* @opp_table: OPP table * @opp_table: OPP table
...@@ -378,43 +399,37 @@ static int _opp_add_static_v2(struct opp_table *opp_table, struct device *dev, ...@@ -378,43 +399,37 @@ static int _opp_add_static_v2(struct opp_table *opp_table, struct device *dev,
} }
/* Initializes OPP tables based on new bindings */ /* Initializes OPP tables based on new bindings */
static int _of_add_opp_table_v2(struct device *dev, struct device_node *opp_np) static int _of_add_opp_table_v2(struct device *dev, struct opp_table *opp_table)
{ {
struct device_node *np; struct device_node *np;
struct opp_table *opp_table; int ret, count = 0, pstate_count = 0;
int ret = 0, count = 0, pstate_count = 0;
struct dev_pm_opp *opp; struct dev_pm_opp *opp;
opp_table = _managed_opp(opp_np); /* OPP table is already initialized for the device */
if (opp_table) { if (opp_table->parsed_static_opps) {
/* OPPs are already managed */ kref_get(&opp_table->list_kref);
if (!_add_opp_dev(dev, opp_table)) return 0;
ret = -ENOMEM;
goto put_opp_table;
} }
opp_table = dev_pm_opp_get_opp_table(dev); kref_init(&opp_table->list_kref);
if (!opp_table)
return -ENOMEM;
/* We have opp-table node now, iterate over it and add OPPs */ /* We have opp-table node now, iterate over it and add OPPs */
for_each_available_child_of_node(opp_np, np) { for_each_available_child_of_node(opp_table->np, np) {
count++; count++;
ret = _opp_add_static_v2(opp_table, dev, np); ret = _opp_add_static_v2(opp_table, dev, np);
if (ret) { if (ret) {
dev_err(dev, "%s: Failed to add OPP, %d\n", __func__, dev_err(dev, "%s: Failed to add OPP, %d\n", __func__,
ret); ret);
_dev_pm_opp_remove_table(opp_table, dev, false);
of_node_put(np); of_node_put(np);
goto put_opp_table; goto put_list_kref;
} }
} }
/* There should be one of more OPP defined */ /* There should be one of more OPP defined */
if (WARN_ON(!count)) { if (WARN_ON(!count)) {
ret = -ENOENT; ret = -ENOENT;
goto put_opp_table; goto put_list_kref;
} }
list_for_each_entry(opp, &opp_table->opp_list, node) list_for_each_entry(opp, &opp_table->opp_list, node)
...@@ -425,28 +440,25 @@ static int _of_add_opp_table_v2(struct device *dev, struct device_node *opp_np) ...@@ -425,28 +440,25 @@ static int _of_add_opp_table_v2(struct device *dev, struct device_node *opp_np)
dev_err(dev, "Not all nodes have performance state set (%d: %d)\n", dev_err(dev, "Not all nodes have performance state set (%d: %d)\n",
count, pstate_count); count, pstate_count);
ret = -ENOENT; ret = -ENOENT;
goto put_opp_table; goto put_list_kref;
} }
if (pstate_count) if (pstate_count)
opp_table->genpd_performance_state = true; opp_table->genpd_performance_state = true;
opp_table->np = opp_np; opp_table->parsed_static_opps = true;
if (of_property_read_bool(opp_np, "opp-shared"))
opp_table->shared_opp = OPP_TABLE_ACCESS_SHARED;
else
opp_table->shared_opp = OPP_TABLE_ACCESS_EXCLUSIVE;
put_opp_table: return 0;
dev_pm_opp_put_opp_table(opp_table);
put_list_kref:
_put_opp_list_kref(opp_table);
return ret; return ret;
} }
/* Initializes OPP tables based on old-deprecated bindings */ /* Initializes OPP tables based on old-deprecated bindings */
static int _of_add_opp_table_v1(struct device *dev) static int _of_add_opp_table_v1(struct device *dev, struct opp_table *opp_table)
{ {
struct opp_table *opp_table;
const struct property *prop; const struct property *prop;
const __be32 *val; const __be32 *val;
int nr, ret = 0; int nr, ret = 0;
...@@ -467,9 +479,7 @@ static int _of_add_opp_table_v1(struct device *dev) ...@@ -467,9 +479,7 @@ static int _of_add_opp_table_v1(struct device *dev)
return -EINVAL; return -EINVAL;
} }
opp_table = dev_pm_opp_get_opp_table(dev); kref_init(&opp_table->list_kref);
if (!opp_table)
return -ENOMEM;
val = prop->value; val = prop->value;
while (nr) { while (nr) {
...@@ -480,13 +490,12 @@ static int _of_add_opp_table_v1(struct device *dev) ...@@ -480,13 +490,12 @@ static int _of_add_opp_table_v1(struct device *dev)
if (ret) { if (ret) {
dev_err(dev, "%s: Failed to add OPP %ld (%d)\n", dev_err(dev, "%s: Failed to add OPP %ld (%d)\n",
__func__, freq, ret); __func__, freq, ret);
_dev_pm_opp_remove_table(opp_table, dev, false); _put_opp_list_kref(opp_table);
break; return ret;
} }
nr -= 2; nr -= 2;
} }
dev_pm_opp_put_opp_table(opp_table);
return ret; return ret;
} }
...@@ -509,24 +518,24 @@ static int _of_add_opp_table_v1(struct device *dev) ...@@ -509,24 +518,24 @@ static int _of_add_opp_table_v1(struct device *dev)
*/ */
int dev_pm_opp_of_add_table(struct device *dev) int dev_pm_opp_of_add_table(struct device *dev)
{ {
struct device_node *opp_np; struct opp_table *opp_table;
int ret; int ret;
opp_table = dev_pm_opp_get_opp_table_indexed(dev, 0);
if (!opp_table)
return -ENOMEM;
/* /*
* OPPs have two version of bindings now. The older one is deprecated, * OPPs have two version of bindings now. Also try the old (v1)
* try for the new binding first. * bindings for backward compatibility with older dtbs.
*/ */
opp_np = dev_pm_opp_of_get_opp_desc_node(dev); if (opp_table->np)
if (!opp_np) { ret = _of_add_opp_table_v2(dev, opp_table);
/* else
* Try old-deprecated bindings for backward compatibility with ret = _of_add_opp_table_v1(dev, opp_table);
* older dtbs.
*/
return _of_add_opp_table_v1(dev);
}
ret = _of_add_opp_table_v2(dev, opp_np); if (ret)
of_node_put(opp_np); dev_pm_opp_put_opp_table(opp_table);
return ret; return ret;
} }
...@@ -553,28 +562,29 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table); ...@@ -553,28 +562,29 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table);
*/ */
int dev_pm_opp_of_add_table_indexed(struct device *dev, int index) int dev_pm_opp_of_add_table_indexed(struct device *dev, int index)
{ {
struct device_node *opp_np; struct opp_table *opp_table;
int ret, count; int ret, count;
again: if (index) {
opp_np = _opp_of_get_opp_desc_node(dev->of_node, index);
if (!opp_np) {
/* /*
* If only one phandle is present, then the same OPP table * If only one phandle is present, then the same OPP table
* applies for all index requests. * applies for all index requests.
*/ */
count = of_count_phandle_with_args(dev->of_node, count = of_count_phandle_with_args(dev->of_node,
"operating-points-v2", NULL); "operating-points-v2", NULL);
if (count == 1 && index) { if (count != 1)
index = 0; return -ENODEV;
goto again;
}
return -ENODEV; index = 0;
} }
ret = _of_add_opp_table_v2(dev, opp_np); opp_table = dev_pm_opp_get_opp_table_indexed(dev, index);
of_node_put(opp_np); if (!opp_table)
return -ENOMEM;
ret = _of_add_opp_table_v2(dev, opp_table);
if (ret)
dev_pm_opp_put_opp_table(opp_table);
return ret; return ret;
} }
...@@ -591,7 +601,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table_indexed); ...@@ -591,7 +601,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table_indexed);
*/ */
void dev_pm_opp_of_cpumask_remove_table(const struct cpumask *cpumask) void dev_pm_opp_of_cpumask_remove_table(const struct cpumask *cpumask)
{ {
_dev_pm_opp_cpumask_remove_table(cpumask, true); _dev_pm_opp_cpumask_remove_table(cpumask, -1);
} }
EXPORT_SYMBOL_GPL(dev_pm_opp_of_cpumask_remove_table); EXPORT_SYMBOL_GPL(dev_pm_opp_of_cpumask_remove_table);
...@@ -626,7 +636,7 @@ int dev_pm_opp_of_cpumask_add_table(const struct cpumask *cpumask) ...@@ -626,7 +636,7 @@ int dev_pm_opp_of_cpumask_add_table(const struct cpumask *cpumask)
__func__, cpu, ret); __func__, cpu, ret);
/* Free all other OPPs */ /* Free all other OPPs */
dev_pm_opp_of_cpumask_remove_table(cpumask); _dev_pm_opp_cpumask_remove_table(cpumask, cpu);
break; break;
} }
} }
......
...@@ -126,9 +126,11 @@ enum opp_table_access { ...@@ -126,9 +126,11 @@ enum opp_table_access {
* @dev_list: list of devices that share these OPPs * @dev_list: list of devices that share these OPPs
* @opp_list: table of opps * @opp_list: table of opps
* @kref: for reference count of the table. * @kref: for reference count of the table.
* @lock: mutex protecting the opp_list. * @list_kref: for reference count of the OPP list.
* @lock: mutex protecting the opp_list and dev_list.
* @np: struct device_node pointer for opp's DT node. * @np: struct device_node pointer for opp's DT node.
* @clock_latency_ns_max: Max clock latency in nanoseconds. * @clock_latency_ns_max: Max clock latency in nanoseconds.
* @parsed_static_opps: True if OPPs are initialized from DT.
* @shared_opp: OPP is shared between multiple devices. * @shared_opp: OPP is shared between multiple devices.
* @suspend_opp: Pointer to OPP to be used during device suspend. * @suspend_opp: Pointer to OPP to be used during device suspend.
* @supported_hw: Array of version number to support. * @supported_hw: Array of version number to support.
...@@ -156,6 +158,7 @@ struct opp_table { ...@@ -156,6 +158,7 @@ struct opp_table {
struct list_head dev_list; struct list_head dev_list;
struct list_head opp_list; struct list_head opp_list;
struct kref kref; struct kref kref;
struct kref list_kref;
struct mutex lock; struct mutex lock;
struct device_node *np; struct device_node *np;
...@@ -164,6 +167,7 @@ struct opp_table { ...@@ -164,6 +167,7 @@ struct opp_table {
/* For backward compatibility with v1 bindings */ /* For backward compatibility with v1 bindings */
unsigned int voltage_tolerance_v1; unsigned int voltage_tolerance_v1;
bool parsed_static_opps;
enum opp_table_access shared_opp; enum opp_table_access shared_opp;
struct dev_pm_opp *suspend_opp; struct dev_pm_opp *suspend_opp;
...@@ -186,23 +190,26 @@ struct opp_table { ...@@ -186,23 +190,26 @@ struct opp_table {
/* Routines internal to opp core */ /* Routines internal to opp core */
void dev_pm_opp_get(struct dev_pm_opp *opp); void dev_pm_opp_get(struct dev_pm_opp *opp);
void _opp_remove_all_static(struct opp_table *opp_table);
void _get_opp_table_kref(struct opp_table *opp_table); void _get_opp_table_kref(struct opp_table *opp_table);
int _get_opp_count(struct opp_table *opp_table); int _get_opp_count(struct opp_table *opp_table);
struct opp_table *_find_opp_table(struct device *dev); struct opp_table *_find_opp_table(struct device *dev);
struct opp_device *_add_opp_dev(const struct device *dev, struct opp_table *opp_table); struct opp_device *_add_opp_dev(const struct device *dev, struct opp_table *opp_table);
void _dev_pm_opp_remove_table(struct opp_table *opp_table, struct device *dev, bool remove_all); void _dev_pm_opp_find_and_remove_table(struct device *dev);
void _dev_pm_opp_find_and_remove_table(struct device *dev, bool remove_all);
struct dev_pm_opp *_opp_allocate(struct opp_table *opp_table); struct dev_pm_opp *_opp_allocate(struct opp_table *opp_table);
void _opp_free(struct dev_pm_opp *opp); void _opp_free(struct dev_pm_opp *opp);
int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table, bool rate_not_available); int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table, bool rate_not_available);
int _opp_add_v1(struct opp_table *opp_table, struct device *dev, unsigned long freq, long u_volt, bool dynamic); int _opp_add_v1(struct opp_table *opp_table, struct device *dev, unsigned long freq, long u_volt, bool dynamic);
void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, bool of); void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, int last_cpu);
struct opp_table *_add_opp_table(struct device *dev); struct opp_table *_add_opp_table(struct device *dev);
void _put_opp_list_kref(struct opp_table *opp_table);
#ifdef CONFIG_OF #ifdef CONFIG_OF
void _of_init_opp_table(struct opp_table *opp_table, struct device *dev); void _of_init_opp_table(struct opp_table *opp_table, struct device *dev, int index);
struct opp_table *_managed_opp(struct device *dev, int index);
#else #else
static inline void _of_init_opp_table(struct opp_table *opp_table, struct device *dev) {} static inline void _of_init_opp_table(struct opp_table *opp_table, struct device *dev, int index) {}
static inline struct opp_table *_managed_opp(struct device *dev, int index) { return NULL; }
#endif #endif
#ifdef CONFIG_DEBUG_FS #ifdef CONFIG_DEBUG_FS
......
...@@ -79,6 +79,7 @@ struct dev_pm_set_opp_data { ...@@ -79,6 +79,7 @@ struct dev_pm_set_opp_data {
#if defined(CONFIG_PM_OPP) #if defined(CONFIG_PM_OPP)
struct opp_table *dev_pm_opp_get_opp_table(struct device *dev); struct opp_table *dev_pm_opp_get_opp_table(struct device *dev);
struct opp_table *dev_pm_opp_get_opp_table_indexed(struct device *dev, int index);
void dev_pm_opp_put_opp_table(struct opp_table *opp_table); void dev_pm_opp_put_opp_table(struct opp_table *opp_table);
unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp); unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp);
...@@ -136,6 +137,11 @@ static inline struct opp_table *dev_pm_opp_get_opp_table(struct device *dev) ...@@ -136,6 +137,11 @@ static inline struct opp_table *dev_pm_opp_get_opp_table(struct device *dev)
return ERR_PTR(-ENOTSUPP); return ERR_PTR(-ENOTSUPP);
} }
static inline struct opp_table *dev_pm_opp_get_opp_table_indexed(struct device *dev, int index)
{
return ERR_PTR(-ENOTSUPP);
}
static inline void dev_pm_opp_put_opp_table(struct opp_table *opp_table) {} static inline void dev_pm_opp_put_opp_table(struct opp_table *opp_table) {}
static inline unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp) static inline unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment