// SPDX-License-Identifier: GPL-2.0-only
/*
* Generic OPP OF helpers
*
* Copyright (C) 2009-2010 Texas Instruments Incorporated.
* Nishanth Menon
* Romit Dasgupta
* Kevin Hilman
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/cpu.h>
#include <linux/errno.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/pm_domain.h>
#include <linux/slab.h>
#include <linux/export.h>
#include <linux/energy_model.h>
#include "opp.h"
/* OPP tables with uninitialized required OPPs, protected by opp_table_lock */
static LIST_HEAD(lazy_opp_tables);
/*
* 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 device_node *np __free(device_node);
np = _opp_of_get_opp_desc_node(dev->of_node, index);
if (!np)
return NULL;
list_for_each_entry(opp_table, &opp_tables, node) {
if (opp_table->np == np) {
/*
* Multiple devices can point to the same OPP table and
* so will have same node-pointer, np.
*
* But the OPPs will be considered as shared only if the
* OPP table contains a "opp-shared" property.
*/
if (opp_table->shared_opp == OPP_TABLE_ACCESS_SHARED)
managed_table = dev_pm_opp_get_opp_table_ref(opp_table);
break;
}
}
return managed_table;
}
/* The caller must call dev_pm_opp_put() after the OPP is used */
static struct dev_pm_opp *_find_opp_of_np(struct opp_table *opp_table,
struct device_node *opp_np)
{
struct dev_pm_opp *opp;
guard(mutex)(&opp_table->lock);
list_for_each_entry(opp, &opp_table->opp_list, node) {
if (opp->np == opp_np)
return dev_pm_opp_get(opp);
}
return NULL;
}
static struct device_node *of_parse_required_opp(struct device_node *np,
int index)
{
return of_parse_phandle(np, "required-opps", index);
}
/* The caller must call dev_pm_opp_put_opp_table() after the table is used */
static struct opp_table *_find_table_of_opp_np(struct device_node *opp_np)
{
struct device_node *opp_table_np __free(device_node);
struct opp_table *opp_table;
opp_table_np = of_get_parent(opp_np);
if (!opp_table_np)
return ERR_PTR(-ENODEV);
guard(mutex)(&opp_table_lock);
list_for_each_entry(opp_table, &opp_tables, node) {
if (opp_table_np == opp_table->np)
return dev_pm_opp_get_opp_table_ref(opp_table);
}
return ERR_PTR(-ENODEV);
}
/* Free resources previously acquired by _opp_table_alloc_required_tables() */
static void _opp_table_free_required_tables(struct opp_table *opp_table)
{
struct opp_table **required_opp_tables = opp_table->required_opp_tables;
int i;
if (!required_opp_tables)
return;
for (i = 0; i < opp_table->required_opp_count; i++) {
if (IS_ERR_OR_NULL(required_opp_tables[i]))
continue;
dev_pm_opp_put_opp_table(required_opp_tables[i]);
}
kfree(required_opp_tables);
opp_table->required_opp_count = 0;
opp_table->required_opp_tables = NULL;
guard(mutex)(&opp_table_lock);
list_del(&opp_table->lazy);
}
/*
* Populate all devices and opp tables which are part of "required-opps" list.
* Checking only the first OPP node should be enough.
*/
static void _opp_table_alloc_required_tables(struct opp_table *opp_table,
struct device *dev,
struct device_node *opp_np)
{
struct opp_table **required_opp_tables;
struct device_node *np __free(device_node);
bool lazy = false;
int count, i, size;
/* Traversing the first OPP node is all we need */
np = of_get_next_available_child(opp_np, NULL);
if (!np) {
dev_warn(dev, "Empty OPP table\n");
return;
}
count = of_count_phandle_with_args(np, "required-opps", NULL);
if (count <= 0)
return;
size = sizeof(*required_opp_tables) + sizeof(*opp_table->required_devs);
required_opp_tables = kcalloc(count, size, GFP_KERNEL);
if (!required_opp_tables)
return;
opp_table->required_opp_tables = required_opp_tables;
opp_table->required_devs = (void *)(required_opp_tables + count);
opp_table->required_opp_count = count;
for (i = 0; i < count; i++) {
struct device_node *required_np __free(device_node);
required_np = of_parse_required_opp(np, i);
if (!required_np) {
_opp_table_free_required_tables(opp_table);
return;
}
required_opp_tables[i] = _find_table_of_opp_np(required_np);
if (IS_ERR(required_opp_tables[i]))
lazy = true;
}
/* Let's do the linking later on */
if (lazy) {
/*
* The OPP table is not held while allocating the table, take it
* now to avoid corruption to the lazy_opp_tables list.
*/
guard(mutex)(&opp_table_lock);
list_add(&opp_table->lazy, &lazy_opp_tables);
}
}
void _of_init_opp_table(struct opp_table *opp_table, struct device *dev,
int index)
{
struct device_node *np __free(device_node), *opp_np;
u32 val;
/*
* Only required for backward compatibility with v1 bindings, but isn't
* harmful for other cases. And so we do it unconditionally.
*/
np = of_node_get(dev->of_node);
if (!np)
return;
if (!of_property_read_u32(np, "clock-latency", &val))
opp_table->clock_latency_ns_max = val;
of_property_read_u32(np, "voltage-tolerance",
&opp_table->voltage_tolerance_v1);
if (of_property_present(np, "#power-domain-cells"))
opp_table->is_genpd = true;
/* Get OPP table node */
opp_np = _opp_of_get_opp_desc_node(np, index);
if (!opp_np)
return;
if
|