// 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_device.h>
#include <linux/pm_domain.h>
#include <linux/slab.h>
#include <linux/export.h>
#include <linux/energy_model.h>
#include "opp.h"
/*
* 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;
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) {
_get_opp_table_kref(opp_table);
managed_table = opp_table;
}
break;
}
}
of_node_put(np);
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;
lockdep_assert_held(&opp_table_lock);
mutex_lock(&opp_table->lock);
list_for_each_entry(opp, &opp_table->opp_list, node) {
if (opp->np == opp_np) {
dev_pm_opp_get(opp);
mutex_unlock(&opp_table->lock);
return opp;
}
}
mutex_unlock(&opp_table->lock);
return NULL;
}
static struct device_node *of_parse_required_opp(struct device_node *np,
int index)
{
struct device_node *required_np;
required_np = of_parse_phandle(np, "required-opps", index);
if (unlikely(!required_np)) {
pr_err("%s: Unable to parse required-opps: %pOF, index: %d\n",
__func__, np, index);
}
return required_np;
}
/* 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 opp_table *opp_table;
struct device_node *opp_table_np;
lockdep_assert_held(&opp_table_lock);
opp_table_np = of_get_parent(opp_np);
if (!opp_table_np)
goto err;
/* It is safe to put the node now as all we need now is its address */
of_node_put(opp_table_np);
list_for_each_entry(opp_table, &opp_tables, node) {
if (opp_table_np == opp_table->np) {
_get_opp_table_kref(opp_table);
return opp_table;
}
}
err:
return ERR_PTR(-ENODEV);
}
/* Free resources previously acquired by _opp_table_alloc_required_tables() */
static