Merge branch 'pm-opp'
* pm-opp: PM / Domains: Propagate performance state updates PM / Domains: Factorize dev_pm_genpd_set_performance_state() PM / Domains: Save OPP table pointer in genpd OPP: Don't return 0 on error from of_get_required_opp_performance_state() OPP: Add dev_pm_opp_xlate_performance_state() helper OPP: Improve _find_table_of_opp_np() PM / Domains: Make genpd performance states orthogonal to the idlestates OPP: Fix missing debugfs supply directory for OPPs OPP: Use opp_table->regulators to verify no regulator case OPP: Remove of_dev_pm_opp_find_required_opp() OPP: Rename and relocate of_genpd_opp_to_performance_state() OPP: Configure all required OPPs OPP: Add dev_pm_opp_{set|put}_genpd_virt_dev() helper PM / Domains: Add genpd_opp_to_performance_state() OPP: Populate OPPs from "required-opps" property OPP: Populate required opp tables from "required-opps" property OPP: Separate out custom OPP handler specific code OPP: Identify and mark genpd OPP tables PM / Domains: Rename genpd virtual devices as virt_dev
This commit is contained in:
commit
6f049e7c87
|
@ -239,6 +239,127 @@ static void genpd_update_accounting(struct generic_pm_domain *genpd)
|
|||
static inline void genpd_update_accounting(struct generic_pm_domain *genpd) {}
|
||||
#endif
|
||||
|
||||
static int _genpd_reeval_performance_state(struct generic_pm_domain *genpd,
|
||||
unsigned int state)
|
||||
{
|
||||
struct generic_pm_domain_data *pd_data;
|
||||
struct pm_domain_data *pdd;
|
||||
struct gpd_link *link;
|
||||
|
||||
/* New requested state is same as Max requested state */
|
||||
if (state == genpd->performance_state)
|
||||
return state;
|
||||
|
||||
/* New requested state is higher than Max requested state */
|
||||
if (state > genpd->performance_state)
|
||||
return state;
|
||||
|
||||
/* Traverse all devices within the domain */
|
||||
list_for_each_entry(pdd, &genpd->dev_list, list_node) {
|
||||
pd_data = to_gpd_data(pdd);
|
||||
|
||||
if (pd_data->performance_state > state)
|
||||
state = pd_data->performance_state;
|
||||
}
|
||||
|
||||
/*
|
||||
* Traverse all sub-domains within the domain. This can be
|
||||
* done without any additional locking as the link->performance_state
|
||||
* field is protected by the master genpd->lock, which is already taken.
|
||||
*
|
||||
* Also note that link->performance_state (subdomain's performance state
|
||||
* requirement to master domain) is different from
|
||||
* link->slave->performance_state (current performance state requirement
|
||||
* of the devices/sub-domains of the subdomain) and so can have a
|
||||
* different value.
|
||||
*
|
||||
* Note that we also take vote from powered-off sub-domains into account
|
||||
* as the same is done for devices right now.
|
||||
*/
|
||||
list_for_each_entry(link, &genpd->master_links, master_node) {
|
||||
if (link->performance_state > state)
|
||||
state = link->performance_state;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static int _genpd_set_performance_state(struct generic_pm_domain *genpd,
|
||||
unsigned int state, int depth)
|
||||
{
|
||||
struct generic_pm_domain *master;
|
||||
struct gpd_link *link;
|
||||
int master_state, ret;
|
||||
|
||||
if (state == genpd->performance_state)
|
||||
return 0;
|
||||
|
||||
/* Propagate to masters of genpd */
|
||||
list_for_each_entry(link, &genpd->slave_links, slave_node) {
|
||||
master = link->master;
|
||||
|
||||
if (!master->set_performance_state)
|
||||
continue;
|
||||
|
||||
/* Find master's performance state */
|
||||
ret = dev_pm_opp_xlate_performance_state(genpd->opp_table,
|
||||
master->opp_table,
|
||||
state);
|
||||
if (unlikely(ret < 0))
|
||||
goto err;
|
||||
|
||||
master_state = ret;
|
||||
|
||||
genpd_lock_nested(master, depth + 1);
|
||||
|
||||
link->prev_performance_state = link->performance_state;
|
||||
link->performance_state = master_state;
|
||||
master_state = _genpd_reeval_performance_state(master,
|
||||
master_state);
|
||||
ret = _genpd_set_performance_state(master, master_state, depth + 1);
|
||||
if (ret)
|
||||
link->performance_state = link->prev_performance_state;
|
||||
|
||||
genpd_unlock(master);
|
||||
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = genpd->set_performance_state(genpd, state);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
genpd->performance_state = state;
|
||||
return 0;
|
||||
|
||||
err:
|
||||
/* Encountered an error, lets rollback */
|
||||
list_for_each_entry_continue_reverse(link, &genpd->slave_links,
|
||||
slave_node) {
|
||||
master = link->master;
|
||||
|
||||
if (!master->set_performance_state)
|
||||
continue;
|
||||
|
||||
genpd_lock_nested(master, depth + 1);
|
||||
|
||||
master_state = link->prev_performance_state;
|
||||
link->performance_state = master_state;
|
||||
|
||||
master_state = _genpd_reeval_performance_state(master,
|
||||
master_state);
|
||||
if (_genpd_set_performance_state(master, master_state, depth + 1)) {
|
||||
pr_err("%s: Failed to roll back to %d performance state\n",
|
||||
master->name, master_state);
|
||||
}
|
||||
|
||||
genpd_unlock(master);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* dev_pm_genpd_set_performance_state- Set performance state of device's power
|
||||
* domain.
|
||||
|
@ -257,10 +378,9 @@ static inline void genpd_update_accounting(struct generic_pm_domain *genpd) {}
|
|||
int dev_pm_genpd_set_performance_state(struct device *dev, unsigned int state)
|
||||
{
|
||||
struct generic_pm_domain *genpd;
|
||||
struct generic_pm_domain_data *gpd_data, *pd_data;
|
||||
struct pm_domain_data *pdd;
|
||||
struct generic_pm_domain_data *gpd_data;
|
||||
unsigned int prev;
|
||||
int ret = 0;
|
||||
int ret;
|
||||
|
||||
genpd = dev_to_genpd(dev);
|
||||
if (IS_ERR(genpd))
|
||||
|
@ -281,47 +401,11 @@ int dev_pm_genpd_set_performance_state(struct device *dev, unsigned int state)
|
|||
prev = gpd_data->performance_state;
|
||||
gpd_data->performance_state = state;
|
||||
|
||||
/* New requested state is same as Max requested state */
|
||||
if (state == genpd->performance_state)
|
||||
goto unlock;
|
||||
state = _genpd_reeval_performance_state(genpd, state);
|
||||
ret = _genpd_set_performance_state(genpd, state, 0);
|
||||
if (ret)
|
||||
gpd_data->performance_state = prev;
|
||||
|
||||
/* New requested state is higher than Max requested state */
|
||||
if (state > genpd->performance_state)
|
||||
goto update_state;
|
||||
|
||||
/* Traverse all devices within the domain */
|
||||
list_for_each_entry(pdd, &genpd->dev_list, list_node) {
|
||||
pd_data = to_gpd_data(pdd);
|
||||
|
||||
if (pd_data->performance_state > state)
|
||||
state = pd_data->performance_state;
|
||||
}
|
||||
|
||||
if (state == genpd->performance_state)
|
||||
goto unlock;
|
||||
|
||||
/*
|
||||
* We aren't propagating performance state changes of a subdomain to its
|
||||
* masters as we don't have hardware that needs it. Over that, the
|
||||
* performance states of subdomain and its masters may not have
|
||||
* one-to-one mapping and would require additional information. We can
|
||||
* get back to this once we have hardware that needs it. For that
|
||||
* reason, we don't have to consider performance state of the subdomains
|
||||
* of genpd here.
|
||||
*/
|
||||
|
||||
update_state:
|
||||
if (genpd_status_on(genpd)) {
|
||||
ret = genpd->set_performance_state(genpd, state);
|
||||
if (ret) {
|
||||
gpd_data->performance_state = prev;
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
|
||||
genpd->performance_state = state;
|
||||
|
||||
unlock:
|
||||
genpd_unlock(genpd);
|
||||
|
||||
return ret;
|
||||
|
@ -347,15 +431,6 @@ static int _genpd_power_on(struct generic_pm_domain *genpd, bool timed)
|
|||
return ret;
|
||||
|
||||
elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start));
|
||||
|
||||
if (unlikely(genpd->set_performance_state)) {
|
||||
ret = genpd->set_performance_state(genpd, genpd->performance_state);
|
||||
if (ret) {
|
||||
pr_warn("%s: Failed to set performance state %d (%d)\n",
|
||||
genpd->name, genpd->performance_state, ret);
|
||||
}
|
||||
}
|
||||
|
||||
if (elapsed_ns <= genpd->states[state_idx].power_on_latency_ns)
|
||||
return ret;
|
||||
|
||||
|
@ -1907,12 +1982,21 @@ int of_genpd_add_provider_simple(struct device_node *np,
|
|||
ret);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/*
|
||||
* Save table for faster processing while setting performance
|
||||
* state.
|
||||
*/
|
||||
genpd->opp_table = dev_pm_opp_get_opp_table(&genpd->dev);
|
||||
WARN_ON(!genpd->opp_table);
|
||||
}
|
||||
|
||||
ret = genpd_add_provider(np, genpd_xlate_simple, genpd);
|
||||
if (ret) {
|
||||
if (genpd->set_performance_state)
|
||||
if (genpd->set_performance_state) {
|
||||
dev_pm_opp_put_opp_table(genpd->opp_table);
|
||||
dev_pm_opp_of_remove_table(&genpd->dev);
|
||||
}
|
||||
|
||||
goto unlock;
|
||||
}
|
||||
|
@ -1965,6 +2049,13 @@ int of_genpd_add_provider_onecell(struct device_node *np,
|
|||
i, ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Save table for faster processing while setting
|
||||
* performance state.
|
||||
*/
|
||||
genpd->opp_table = dev_pm_opp_get_opp_table_indexed(&genpd->dev, i);
|
||||
WARN_ON(!genpd->opp_table);
|
||||
}
|
||||
|
||||
genpd->provider = &np->fwnode;
|
||||
|
@ -1989,8 +2080,10 @@ int of_genpd_add_provider_onecell(struct device_node *np,
|
|||
genpd->provider = NULL;
|
||||
genpd->has_provider = false;
|
||||
|
||||
if (genpd->set_performance_state)
|
||||
if (genpd->set_performance_state) {
|
||||
dev_pm_opp_put_opp_table(genpd->opp_table);
|
||||
dev_pm_opp_of_remove_table(&genpd->dev);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&gpd_list_lock);
|
||||
|
@ -2024,6 +2117,7 @@ void of_genpd_del_provider(struct device_node *np)
|
|||
if (!gpd->set_performance_state)
|
||||
continue;
|
||||
|
||||
dev_pm_opp_put_opp_table(gpd->opp_table);
|
||||
dev_pm_opp_of_remove_table(&gpd->dev);
|
||||
}
|
||||
}
|
||||
|
@ -2338,7 +2432,7 @@ EXPORT_SYMBOL_GPL(genpd_dev_pm_attach);
|
|||
struct device *genpd_dev_pm_attach_by_id(struct device *dev,
|
||||
unsigned int index)
|
||||
{
|
||||
struct device *genpd_dev;
|
||||
struct device *virt_dev;
|
||||
int num_domains;
|
||||
int ret;
|
||||
|
||||
|
@ -2352,31 +2446,31 @@ struct device *genpd_dev_pm_attach_by_id(struct device *dev,
|
|||
return NULL;
|
||||
|
||||
/* Allocate and register device on the genpd bus. */
|
||||
genpd_dev = kzalloc(sizeof(*genpd_dev), GFP_KERNEL);
|
||||
if (!genpd_dev)
|
||||
virt_dev = kzalloc(sizeof(*virt_dev), GFP_KERNEL);
|
||||
if (!virt_dev)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
dev_set_name(genpd_dev, "genpd:%u:%s", index, dev_name(dev));
|
||||
genpd_dev->bus = &genpd_bus_type;
|
||||
genpd_dev->release = genpd_release_dev;
|
||||
dev_set_name(virt_dev, "genpd:%u:%s", index, dev_name(dev));
|
||||
virt_dev->bus = &genpd_bus_type;
|
||||
virt_dev->release = genpd_release_dev;
|
||||
|
||||
ret = device_register(genpd_dev);
|
||||
ret = device_register(virt_dev);
|
||||
if (ret) {
|
||||
kfree(genpd_dev);
|
||||
kfree(virt_dev);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
/* Try to attach the device to the PM domain at the specified index. */
|
||||
ret = __genpd_dev_pm_attach(genpd_dev, dev->of_node, index, false);
|
||||
ret = __genpd_dev_pm_attach(virt_dev, dev->of_node, index, false);
|
||||
if (ret < 1) {
|
||||
device_unregister(genpd_dev);
|
||||
device_unregister(virt_dev);
|
||||
return ret ? ERR_PTR(ret) : NULL;
|
||||
}
|
||||
|
||||
pm_runtime_enable(genpd_dev);
|
||||
genpd_queue_power_off_work(dev_to_genpd(genpd_dev));
|
||||
pm_runtime_enable(virt_dev);
|
||||
genpd_queue_power_off_work(dev_to_genpd(virt_dev));
|
||||
|
||||
return genpd_dev;
|
||||
return virt_dev;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(genpd_dev_pm_attach_by_id);
|
||||
|
||||
|
@ -2521,52 +2615,36 @@ int of_genpd_parse_idle_states(struct device_node *dn,
|
|||
EXPORT_SYMBOL_GPL(of_genpd_parse_idle_states);
|
||||
|
||||
/**
|
||||
* of_genpd_opp_to_performance_state- Gets performance state of device's
|
||||
* power domain corresponding to a DT node's "required-opps" property.
|
||||
* pm_genpd_opp_to_performance_state - Gets performance state of the genpd from its OPP node.
|
||||
*
|
||||
* @dev: Device for which the performance-state needs to be found.
|
||||
* @np: DT node where the "required-opps" property is present. This can be
|
||||
* the device node itself (if it doesn't have an OPP table) or a node
|
||||
* within the OPP table of a device (if device has an OPP table).
|
||||
* @genpd_dev: Genpd's device for which the performance-state needs to be found.
|
||||
* @opp: struct dev_pm_opp of the OPP for which we need to find performance
|
||||
* state.
|
||||
*
|
||||
* Returns performance state corresponding to the "required-opps" property of
|
||||
* a DT node. This calls platform specific genpd->opp_to_performance_state()
|
||||
* callback to translate power domain OPP to performance state.
|
||||
* Returns performance state encoded in the OPP of the genpd. This calls
|
||||
* platform specific genpd->opp_to_performance_state() callback to translate
|
||||
* power domain OPP to performance state.
|
||||
*
|
||||
* Returns performance state on success and 0 on failure.
|
||||
*/
|
||||
unsigned int of_genpd_opp_to_performance_state(struct device *dev,
|
||||
struct device_node *np)
|
||||
unsigned int pm_genpd_opp_to_performance_state(struct device *genpd_dev,
|
||||
struct dev_pm_opp *opp)
|
||||
{
|
||||
struct generic_pm_domain *genpd;
|
||||
struct dev_pm_opp *opp;
|
||||
int state = 0;
|
||||
struct generic_pm_domain *genpd = NULL;
|
||||
int state;
|
||||
|
||||
genpd = dev_to_genpd(dev);
|
||||
if (IS_ERR(genpd))
|
||||
return 0;
|
||||
genpd = container_of(genpd_dev, struct generic_pm_domain, dev);
|
||||
|
||||
if (unlikely(!genpd->set_performance_state))
|
||||
if (unlikely(!genpd->opp_to_performance_state))
|
||||
return 0;
|
||||
|
||||
genpd_lock(genpd);
|
||||
|
||||
opp = of_dev_pm_opp_find_required_opp(&genpd->dev, np);
|
||||
if (IS_ERR(opp)) {
|
||||
dev_err(dev, "Failed to find required OPP: %ld\n",
|
||||
PTR_ERR(opp));
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
state = genpd->opp_to_performance_state(genpd, opp);
|
||||
dev_pm_opp_put(opp);
|
||||
|
||||
unlock:
|
||||
genpd_unlock(genpd);
|
||||
|
||||
return state;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_genpd_opp_to_performance_state);
|
||||
EXPORT_SYMBOL_GPL(pm_genpd_opp_to_performance_state);
|
||||
|
||||
static int __init genpd_bus_init(void)
|
||||
{
|
||||
|
|
|
@ -196,12 +196,12 @@ unsigned long dev_pm_opp_get_max_volt_latency(struct device *dev)
|
|||
if (IS_ERR(opp_table))
|
||||
return 0;
|
||||
|
||||
count = opp_table->regulator_count;
|
||||
|
||||
/* Regulator may not be required for the device */
|
||||
if (!count)
|
||||
if (!opp_table->regulators)
|
||||
goto put_opp_table;
|
||||
|
||||
count = opp_table->regulator_count;
|
||||
|
||||
uV = kmalloc_array(count, sizeof(*uV), GFP_KERNEL);
|
||||
if (!uV)
|
||||
goto put_opp_table;
|
||||
|
@ -548,44 +548,6 @@ _generic_set_opp_clk_only(struct device *dev, struct clk *clk,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static inline int
|
||||
_generic_set_opp_domain(struct device *dev, struct clk *clk,
|
||||
unsigned long old_freq, unsigned long freq,
|
||||
unsigned int old_pstate, unsigned int new_pstate)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Scaling up? Scale domain performance state before frequency */
|
||||
if (freq > old_freq) {
|
||||
ret = dev_pm_genpd_set_performance_state(dev, new_pstate);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = _generic_set_opp_clk_only(dev, clk, old_freq, freq);
|
||||
if (ret)
|
||||
goto restore_domain_state;
|
||||
|
||||
/* Scaling down? Scale domain performance state after frequency */
|
||||
if (freq < old_freq) {
|
||||
ret = dev_pm_genpd_set_performance_state(dev, new_pstate);
|
||||
if (ret)
|
||||
goto restore_freq;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
restore_freq:
|
||||
if (_generic_set_opp_clk_only(dev, clk, freq, old_freq))
|
||||
dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n",
|
||||
__func__, old_freq);
|
||||
restore_domain_state:
|
||||
if (freq > old_freq)
|
||||
dev_pm_genpd_set_performance_state(dev, old_pstate);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _generic_set_opp_regulator(const struct opp_table *opp_table,
|
||||
struct device *dev,
|
||||
unsigned long old_freq,
|
||||
|
@ -635,6 +597,84 @@ static int _generic_set_opp_regulator(const struct opp_table *opp_table,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int _set_opp_custom(const struct opp_table *opp_table,
|
||||
struct device *dev, unsigned long old_freq,
|
||||
unsigned long freq,
|
||||
struct dev_pm_opp_supply *old_supply,
|
||||
struct dev_pm_opp_supply *new_supply)
|
||||
{
|
||||
struct dev_pm_set_opp_data *data;
|
||||
int size;
|
||||
|
||||
data = opp_table->set_opp_data;
|
||||
data->regulators = opp_table->regulators;
|
||||
data->regulator_count = opp_table->regulator_count;
|
||||
data->clk = opp_table->clk;
|
||||
data->dev = dev;
|
||||
|
||||
data->old_opp.rate = old_freq;
|
||||
size = sizeof(*old_supply) * opp_table->regulator_count;
|
||||
if (IS_ERR(old_supply))
|
||||
memset(data->old_opp.supplies, 0, size);
|
||||
else
|
||||
memcpy(data->old_opp.supplies, old_supply, size);
|
||||
|
||||
data->new_opp.rate = freq;
|
||||
memcpy(data->new_opp.supplies, new_supply, size);
|
||||
|
||||
return opp_table->set_opp(data);
|
||||
}
|
||||
|
||||
/* This is only called for PM domain for now */
|
||||
static int _set_required_opps(struct device *dev,
|
||||
struct opp_table *opp_table,
|
||||
struct dev_pm_opp *opp)
|
||||
{
|
||||
struct opp_table **required_opp_tables = opp_table->required_opp_tables;
|
||||
struct device **genpd_virt_devs = opp_table->genpd_virt_devs;
|
||||
unsigned int pstate;
|
||||
int i, ret = 0;
|
||||
|
||||
if (!required_opp_tables)
|
||||
return 0;
|
||||
|
||||
/* Single genpd case */
|
||||
if (!genpd_virt_devs) {
|
||||
pstate = opp->required_opps[0]->pstate;
|
||||
ret = dev_pm_genpd_set_performance_state(dev, pstate);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to set performance state of %s: %d (%d)\n",
|
||||
dev_name(dev), pstate, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Multiple genpd case */
|
||||
|
||||
/*
|
||||
* Acquire genpd_virt_dev_lock to make sure we don't use a genpd_dev
|
||||
* after it is freed from another thread.
|
||||
*/
|
||||
mutex_lock(&opp_table->genpd_virt_dev_lock);
|
||||
|
||||
for (i = 0; i < opp_table->required_opp_count; i++) {
|
||||
pstate = opp->required_opps[i]->pstate;
|
||||
|
||||
if (!genpd_virt_devs[i])
|
||||
continue;
|
||||
|
||||
ret = dev_pm_genpd_set_performance_state(genpd_virt_devs[i], pstate);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to set performance rate of %s: %d (%d)\n",
|
||||
dev_name(genpd_virt_devs[i]), pstate, ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&opp_table->genpd_virt_dev_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* dev_pm_opp_set_rate() - Configure new OPP based on frequency
|
||||
* @dev: device for which we do this operation
|
||||
|
@ -649,7 +689,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
|
|||
unsigned long freq, old_freq;
|
||||
struct dev_pm_opp *old_opp, *opp;
|
||||
struct clk *clk;
|
||||
int ret, size;
|
||||
int ret;
|
||||
|
||||
if (unlikely(!target_freq)) {
|
||||
dev_err(dev, "%s: Invalid target frequency %lu\n", __func__,
|
||||
|
@ -702,44 +742,34 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
|
|||
dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n", __func__,
|
||||
old_freq, freq);
|
||||
|
||||
/* Only frequency scaling */
|
||||
if (!opp_table->regulators) {
|
||||
/*
|
||||
* We don't support devices with both regulator and
|
||||
* domain performance-state for now.
|
||||
*/
|
||||
if (opp_table->genpd_performance_state)
|
||||
ret = _generic_set_opp_domain(dev, clk, old_freq, freq,
|
||||
IS_ERR(old_opp) ? 0 : old_opp->pstate,
|
||||
opp->pstate);
|
||||
else
|
||||
ret = _generic_set_opp_clk_only(dev, clk, old_freq, freq);
|
||||
} else if (!opp_table->set_opp) {
|
||||
/* Scaling up? Configure required OPPs before frequency */
|
||||
if (freq > old_freq) {
|
||||
ret = _set_required_opps(dev, opp_table, opp);
|
||||
if (ret)
|
||||
goto put_opp;
|
||||
}
|
||||
|
||||
if (opp_table->set_opp) {
|
||||
ret = _set_opp_custom(opp_table, dev, old_freq, freq,
|
||||
IS_ERR(old_opp) ? NULL : old_opp->supplies,
|
||||
opp->supplies);
|
||||
} else if (opp_table->regulators) {
|
||||
ret = _generic_set_opp_regulator(opp_table, dev, old_freq, freq,
|
||||
IS_ERR(old_opp) ? NULL : old_opp->supplies,
|
||||
opp->supplies);
|
||||
} else {
|
||||
struct dev_pm_set_opp_data *data;
|
||||
|
||||
data = opp_table->set_opp_data;
|
||||
data->regulators = opp_table->regulators;
|
||||
data->regulator_count = opp_table->regulator_count;
|
||||
data->clk = clk;
|
||||
data->dev = dev;
|
||||
|
||||
data->old_opp.rate = old_freq;
|
||||
size = sizeof(*opp->supplies) * opp_table->regulator_count;
|
||||
if (IS_ERR(old_opp))
|
||||
memset(data->old_opp.supplies, 0, size);
|
||||
else
|
||||
memcpy(data->old_opp.supplies, old_opp->supplies, size);
|
||||
|
||||
data->new_opp.rate = freq;
|
||||
memcpy(data->new_opp.supplies, opp->supplies, size);
|
||||
|
||||
ret = opp_table->set_opp(data);
|
||||
/* Only frequency scaling */
|
||||
ret = _generic_set_opp_clk_only(dev, clk, old_freq, freq);
|
||||
}
|
||||
|
||||
/* Scaling down? Configure required OPPs after frequency */
|
||||
if (!ret && freq < old_freq) {
|
||||
ret = _set_required_opps(dev, opp_table, opp);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to set required opps: %d\n", ret);
|
||||
}
|
||||
|
||||
put_opp:
|
||||
dev_pm_opp_put(opp);
|
||||
put_old_opp:
|
||||
if (!IS_ERR(old_opp))
|
||||
|
@ -810,8 +840,12 @@ static struct opp_table *_allocate_opp_table(struct device *dev, int index)
|
|||
return NULL;
|
||||
|
||||
mutex_init(&opp_table->lock);
|
||||
mutex_init(&opp_table->genpd_virt_dev_lock);
|
||||
INIT_LIST_HEAD(&opp_table->dev_list);
|
||||
|
||||
/* Mark regulator count uninitialized */
|
||||
opp_table->regulator_count = -1;
|
||||
|
||||
opp_dev = _add_opp_dev(dev, opp_table);
|
||||
if (!opp_dev) {
|
||||
kfree(opp_table);
|
||||
|
@ -888,6 +922,8 @@ static void _opp_table_kref_release(struct kref *kref)
|
|||
struct opp_table *opp_table = container_of(kref, struct opp_table, kref);
|
||||
struct opp_device *opp_dev, *temp;
|
||||
|
||||
_of_clear_opp_table(opp_table);
|
||||
|
||||
/* Release clk */
|
||||
if (!IS_ERR(opp_table->clk))
|
||||
clk_put(opp_table->clk);
|
||||
|
@ -905,6 +941,7 @@ static void _opp_table_kref_release(struct kref *kref)
|
|||
_remove_opp_dev(opp_dev, opp_table);
|
||||
}
|
||||
|
||||
mutex_destroy(&opp_table->genpd_virt_dev_lock);
|
||||
mutex_destroy(&opp_table->lock);
|
||||
list_del(&opp_table->node);
|
||||
kfree(opp_table);
|
||||
|
@ -961,6 +998,7 @@ static void _opp_kref_release(struct kref *kref)
|
|||
* frequency/voltage list.
|
||||
*/
|
||||
blocking_notifier_call_chain(&opp_table->head, OPP_EVENT_REMOVE, opp);
|
||||
_of_opp_free_required_opps(opp_table, opp);
|
||||
opp_debug_remove_one(opp);
|
||||
list_del(&opp->node);
|
||||
kfree(opp);
|
||||
|
@ -1028,7 +1066,7 @@ struct dev_pm_opp *_opp_allocate(struct opp_table *table)
|
|||
int count, supply_size;
|
||||
|
||||
/* Allocate space for at least one supply */
|
||||
count = table->regulator_count ? table->regulator_count : 1;
|
||||
count = table->regulator_count > 0 ? table->regulator_count : 1;
|
||||
supply_size = sizeof(*opp->supplies) * count;
|
||||
|
||||
/* allocate new OPP node and supplies structures */
|
||||
|
@ -1049,6 +1087,9 @@ static bool _opp_supported_by_regulators(struct dev_pm_opp *opp,
|
|||
struct regulator *reg;
|
||||
int i;
|
||||
|
||||
if (!opp_table->regulators)
|
||||
return true;
|
||||
|
||||
for (i = 0; i < opp_table->regulator_count; i++) {
|
||||
reg = opp_table->regulators[i];
|
||||
|
||||
|
@ -1333,7 +1374,7 @@ static int _allocate_set_opp_data(struct opp_table *opp_table)
|
|||
struct dev_pm_set_opp_data *data;
|
||||
int len, count = opp_table->regulator_count;
|
||||
|
||||
if (WARN_ON(!count))
|
||||
if (WARN_ON(!opp_table->regulators))
|
||||
return -EINVAL;
|
||||
|
||||
/* space for set_opp_data */
|
||||
|
@ -1430,7 +1471,7 @@ struct opp_table *dev_pm_opp_set_regulators(struct device *dev,
|
|||
|
||||
kfree(opp_table->regulators);
|
||||
opp_table->regulators = NULL;
|
||||
opp_table->regulator_count = 0;
|
||||
opp_table->regulator_count = -1;
|
||||
err:
|
||||
dev_pm_opp_put_opp_table(opp_table);
|
||||
|
||||
|
@ -1459,7 +1500,7 @@ void dev_pm_opp_put_regulators(struct opp_table *opp_table)
|
|||
|
||||
kfree(opp_table->regulators);
|
||||
opp_table->regulators = NULL;
|
||||
opp_table->regulator_count = 0;
|
||||
opp_table->regulator_count = -1;
|
||||
|
||||
put_opp_table:
|
||||
dev_pm_opp_put_opp_table(opp_table);
|
||||
|
@ -1586,6 +1627,155 @@ void dev_pm_opp_unregister_set_opp_helper(struct opp_table *opp_table)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(dev_pm_opp_unregister_set_opp_helper);
|
||||
|
||||
/**
|
||||
* dev_pm_opp_set_genpd_virt_dev - Set virtual genpd device for an index
|
||||
* @dev: Consumer device for which the genpd device is getting set.
|
||||
* @virt_dev: virtual genpd device.
|
||||
* @index: index.
|
||||
*
|
||||
* Multiple generic power domains for a device are supported with the help of
|
||||
* virtual genpd devices, which are created for each consumer device - genpd
|
||||
* pair. These are the device structures which are attached to the power domain
|
||||
* and are required by the OPP core to set the performance state of the genpd.
|
||||
*
|
||||
* This helper will normally be called by the consumer driver of the device
|
||||
* "dev", as only that has details of the genpd devices.
|
||||
*
|
||||
* This helper needs to be called once for each of those virtual devices, but
|
||||
* only if multiple domains are available for a device. Otherwise the original
|
||||
* device structure will be used instead by the OPP core.
|
||||
*/
|
||||
struct opp_table *dev_pm_opp_set_genpd_virt_dev(struct device *dev,
|
||||
struct device *virt_dev,
|
||||
int index)
|
||||
{
|
||||
struct opp_table *opp_table;
|
||||
|
||||
opp_table = dev_pm_opp_get_opp_table(dev);
|
||||
if (!opp_table)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
mutex_lock(&opp_table->genpd_virt_dev_lock);
|
||||
|
||||
if (unlikely(!opp_table->genpd_virt_devs ||
|
||||
index >= opp_table->required_opp_count ||
|
||||
opp_table->genpd_virt_devs[index])) {
|
||||
|
||||
dev_err(dev, "Invalid request to set required device\n");
|
||||
dev_pm_opp_put_opp_table(opp_table);
|
||||
mutex_unlock(&opp_table->genpd_virt_dev_lock);
|
||||
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
opp_table->genpd_virt_devs[index] = virt_dev;
|
||||
mutex_unlock(&opp_table->genpd_virt_dev_lock);
|
||||
|
||||
return opp_table;
|
||||
}
|
||||
|
||||
/**
|
||||
* dev_pm_opp_put_genpd_virt_dev() - Releases resources blocked for genpd device.
|
||||
* @opp_table: OPP table returned by dev_pm_opp_set_genpd_virt_dev().
|
||||
* @virt_dev: virtual genpd device.
|
||||
*
|
||||
* This releases the resource previously acquired with a call to
|
||||
* dev_pm_opp_set_genpd_virt_dev(). The consumer driver shall call this helper
|
||||
* if it doesn't want OPP core to update performance state of a power domain
|
||||
* anymore.
|
||||
*/
|
||||
void dev_pm_opp_put_genpd_virt_dev(struct opp_table *opp_table,
|
||||
struct device *virt_dev)
|
||||
{
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Acquire genpd_virt_dev_lock to make sure virt_dev isn't getting
|
||||
* used in parallel.
|
||||
*/
|
||||
mutex_lock(&opp_table->genpd_virt_dev_lock);
|
||||
|
||||
for (i = 0; i < opp_table->required_opp_count; i++) {
|
||||
if (opp_table->genpd_virt_devs[i] != virt_dev)
|
||||
continue;
|
||||
|
||||
opp_table->genpd_virt_devs[i] = NULL;
|
||||
dev_pm_opp_put_opp_table(opp_table);
|
||||
|
||||
/* Drop the vote */
|
||||
dev_pm_genpd_set_performance_state(virt_dev, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&opp_table->genpd_virt_dev_lock);
|
||||
|
||||
if (unlikely(i == opp_table->required_opp_count))
|
||||
dev_err(virt_dev, "Failed to find required device entry\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* dev_pm_opp_xlate_performance_state() - Find required OPP's pstate for src_table.
|
||||
* @src_table: OPP table which has dst_table as one of its required OPP table.
|
||||
* @dst_table: Required OPP table of the src_table.
|
||||
* @pstate: Current performance state of the src_table.
|
||||
*
|
||||
* This Returns pstate of the OPP (present in @dst_table) pointed out by the
|
||||
* "required-opps" property of the OPP (present in @src_table) which has
|
||||
* performance state set to @pstate.
|
||||
*
|
||||
* Return: Zero or positive performance state on success, otherwise negative
|
||||
* value on errors.
|
||||
*/
|
||||
int dev_pm_opp_xlate_performance_state(struct opp_table *src_table,
|
||||
struct opp_table *dst_table,
|
||||
unsigned int pstate)
|
||||
{
|
||||
struct dev_pm_opp *opp;
|
||||
int dest_pstate = -EINVAL;
|
||||
int i;
|
||||
|
||||
if (!pstate)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Normally the src_table will have the "required_opps" property set to
|
||||
* point to one of the OPPs in the dst_table, but in some cases the
|
||||
* genpd and its master have one to one mapping of performance states
|
||||
* and so none of them have the "required-opps" property set. Return the
|
||||
* pstate of the src_table as it is in such cases.
|
||||
*/
|
||||
if (!src_table->required_opp_count)
|
||||
return pstate;
|
||||
|
||||
for (i = 0; i < src_table->required_opp_count; i++) {
|
||||
if (src_table->required_opp_tables[i]->np == dst_table->np)
|
||||
break;
|
||||
}
|
||||
|
||||
if (unlikely(i == src_table->required_opp_count)) {
|
||||
pr_err("%s: Couldn't find matching OPP table (%p: %p)\n",
|
||||
__func__, src_table, dst_table);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&src_table->lock);
|
||||
|
||||
list_for_each_entry(opp, &src_table->opp_list, node) {
|
||||
if (opp->pstate == pstate) {
|
||||
dest_pstate = opp->required_opps[i]->pstate;
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
|
||||
pr_err("%s: Couldn't find matching OPP (%p: %p)\n", __func__, src_table,
|
||||
dst_table);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&src_table->lock);
|
||||
|
||||
return dest_pstate;
|
||||
}
|
||||
|
||||
/**
|
||||
* dev_pm_opp_add() - Add an OPP table from a table definitions
|
||||
* @dev: device for which we do this operation
|
||||
|
@ -1612,6 +1802,9 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
|
|||
if (!opp_table)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Fix regulator count for dynamic OPPs */
|
||||
opp_table->regulator_count = 1;
|
||||
|
||||
ret = _opp_add_v1(opp_table, dev, freq, u_volt, true);
|
||||
if (ret)
|
||||
dev_pm_opp_put_opp_table(opp_table);
|
||||
|
|
341
drivers/opp/of.c
341
drivers/opp/of.c
|
@ -73,6 +73,167 @@ struct opp_table *_managed_opp(struct device *dev, int index)
|
|||
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 void _opp_table_free_required_tables(struct opp_table *opp_table)
|
||||
{
|
||||
struct opp_table **required_opp_tables = opp_table->required_opp_tables;
|
||||
struct device **genpd_virt_devs = opp_table->genpd_virt_devs;
|
||||
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]))
|
||||
break;
|
||||
|
||||
dev_pm_opp_put_opp_table(required_opp_tables[i]);
|
||||
}
|
||||
|
||||
kfree(required_opp_tables);
|
||||
kfree(genpd_virt_devs);
|
||||
|
||||
opp_table->required_opp_count = 0;
|
||||
opp_table->genpd_virt_devs = NULL;
|
||||
opp_table->required_opp_tables = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 **genpd_virt_devs = NULL;
|
||||
struct device_node *required_np, *np;
|
||||
int count, i;
|
||||
|
||||
/* Traversing the first OPP node is all we need */
|
||||
np = of_get_next_available_child(opp_np, NULL);
|
||||
if (!np) {
|
||||
dev_err(dev, "Empty OPP table\n");
|
||||
return;
|
||||
}
|
||||
|
||||
count = of_count_phandle_with_args(np, "required-opps", NULL);
|
||||
if (!count)
|
||||
goto put_np;
|
||||
|
||||
if (count > 1) {
|
||||
genpd_virt_devs = kcalloc(count, sizeof(*genpd_virt_devs),
|
||||
GFP_KERNEL);
|
||||
if (!genpd_virt_devs)
|
||||
goto put_np;
|
||||
}
|
||||
|
||||
required_opp_tables = kcalloc(count, sizeof(*required_opp_tables),
|
||||
GFP_KERNEL);
|
||||
if (!required_opp_tables) {
|
||||
kfree(genpd_virt_devs);
|
||||
goto put_np;
|
||||
}
|
||||
|
||||
opp_table->genpd_virt_devs = genpd_virt_devs;
|
||||
opp_table->required_opp_tables = required_opp_tables;
|
||||
opp_table->required_opp_count = count;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
required_np = of_parse_required_opp(np, i);
|
||||
if (!required_np)
|
||||
goto free_required_tables;
|
||||
|
||||
required_opp_tables[i] = _find_table_of_opp_np(required_np);
|
||||
of_node_put(required_np);
|
||||
|
||||
if (IS_ERR(required_opp_tables[i]))
|
||||
goto free_required_tables;
|
||||
|
||||
/*
|
||||
* We only support genpd's OPPs in the "required-opps" for now,
|
||||
* as we don't know how much about other cases. Error out if the
|
||||
* required OPP doesn't belong to a genpd.
|
||||
*/
|
||||
if (!required_opp_tables[i]->is_genpd) {
|
||||
dev_err(dev, "required-opp doesn't belong to genpd: %pOF\n",
|
||||
required_np);
|
||||
goto free_required_tables;
|
||||
}
|
||||
}
|
||||
|
||||
goto put_np;
|
||||
|
||||
free_required_tables:
|
||||
_opp_table_free_required_tables(opp_table);
|
||||
put_np:
|
||||
of_node_put(np);
|
||||
}
|
||||
|
||||
void _of_init_opp_table(struct opp_table *opp_table, struct device *dev,
|
||||
int index)
|
||||
{
|
||||
|
@ -92,6 +253,9 @@ void _of_init_opp_table(struct opp_table *opp_table, struct device *dev,
|
|||
of_property_read_u32(np, "voltage-tolerance",
|
||||
&opp_table->voltage_tolerance_v1);
|
||||
|
||||
if (of_find_property(np, "#power-domain-cells", NULL))
|
||||
opp_table->is_genpd = true;
|
||||
|
||||
/* Get OPP table node */
|
||||
opp_np = _opp_of_get_opp_desc_node(np, index);
|
||||
of_node_put(np);
|
||||
|
@ -106,9 +270,86 @@ void _of_init_opp_table(struct opp_table *opp_table, struct device *dev,
|
|||
|
||||
opp_table->np = opp_np;
|
||||
|
||||
_opp_table_alloc_required_tables(opp_table, dev, opp_np);
|
||||
of_node_put(opp_np);
|
||||
}
|
||||
|
||||
void _of_clear_opp_table(struct opp_table *opp_table)
|
||||
{
|
||||
_opp_table_free_required_tables(opp_table);
|
||||
}
|
||||
|
||||
/*
|
||||
* Release all resources previously acquired with a call to
|
||||
* _of_opp_alloc_required_opps().
|
||||
*/
|
||||
void _of_opp_free_required_opps(struct opp_table *opp_table,
|
||||
struct dev_pm_opp *opp)
|
||||
{
|
||||
struct dev_pm_opp **required_opps = opp->required_opps;
|
||||
int i;
|
||||
|
||||
if (!required_opps)
|
||||
return;
|
||||
|
||||
for (i = 0; i < opp_table->required_opp_count; i++) {
|
||||
if (!required_opps[i])
|
||||
break;
|
||||
|
||||
/* Put the reference back */
|
||||
dev_pm_opp_put(required_opps[i]);
|
||||
}
|
||||
|
||||
kfree(required_opps);
|
||||
opp->required_opps = NULL;
|
||||
}
|
||||
|
||||
/* Populate all required OPPs which are part of "required-opps" list */
|
||||
static int _of_opp_alloc_required_opps(struct opp_table *opp_table,
|
||||
struct dev_pm_opp *opp)
|
||||
{
|
||||
struct dev_pm_opp **required_opps;
|
||||
struct opp_table *required_table;
|
||||
struct device_node *np;
|
||||
int i, ret, count = opp_table->required_opp_count;
|
||||
|
||||
if (!count)
|
||||
return 0;
|
||||
|
||||
required_opps = kcalloc(count, sizeof(*required_opps), GFP_KERNEL);
|
||||
if (!required_opps)
|
||||
return -ENOMEM;
|
||||
|
||||
opp->required_opps = required_opps;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
required_table = opp_table->required_opp_tables[i];
|
||||
|
||||
np = of_parse_required_opp(opp->np, i);
|
||||
if (unlikely(!np)) {
|
||||
ret = -ENODEV;
|
||||
goto free_required_opps;
|
||||
}
|
||||
|
||||
required_opps[i] = _find_opp_of_np(required_table, np);
|
||||
of_node_put(np);
|
||||
|
||||
if (!required_opps[i]) {
|
||||
pr_err("%s: Unable to find required OPP node: %pOF (%d)\n",
|
||||
__func__, opp->np, i);
|
||||
ret = -ENODEV;
|
||||
goto free_required_opps;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
free_required_opps:
|
||||
_of_opp_free_required_opps(opp_table, opp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table,
|
||||
struct device_node *np)
|
||||
{
|
||||
|
@ -150,12 +391,10 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
|
|||
struct opp_table *opp_table)
|
||||
{
|
||||
u32 *microvolt, *microamp = NULL;
|
||||
int supplies, vcount, icount, ret, i, j;
|
||||
int supplies = opp_table->regulator_count, vcount, icount, ret, i, j;
|
||||
struct property *prop = NULL;
|
||||
char name[NAME_MAX];
|
||||
|
||||
supplies = opp_table->regulator_count ? opp_table->regulator_count : 1;
|
||||
|
||||
/* Search for "opp-microvolt-<name>" */
|
||||
if (opp_table->prop_name) {
|
||||
snprintf(name, sizeof(name), "opp-microvolt-%s",
|
||||
|
@ -170,7 +409,13 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
|
|||
|
||||
/* Missing property isn't a problem, but an invalid entry is */
|
||||
if (!prop) {
|
||||
if (!opp_table->regulator_count)
|
||||
if (unlikely(supplies == -1)) {
|
||||
/* Initialize regulator_count */
|
||||
opp_table->regulator_count = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!supplies)
|
||||
return 0;
|
||||
|
||||
dev_err(dev, "%s: opp-microvolt missing although OPP managing regulators\n",
|
||||
|
@ -179,6 +424,14 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
|
|||
}
|
||||
}
|
||||
|
||||
if (unlikely(supplies == -1)) {
|
||||
/* Initialize regulator_count */
|
||||
supplies = opp_table->regulator_count = 1;
|
||||
} else if (unlikely(!supplies)) {
|
||||
dev_err(dev, "%s: opp-microvolt wasn't expected\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
vcount = of_property_count_u32_elems(opp->np, name);
|
||||
if (vcount < 0) {
|
||||
dev_err(dev, "%s: Invalid %s property (%d)\n",
|
||||
|
@ -326,8 +579,7 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table,
|
|||
ret = of_property_read_u64(np, "opp-hz", &rate);
|
||||
if (ret < 0) {
|
||||
/* "opp-hz" is optional for devices like power domains. */
|
||||
if (!of_find_property(dev->of_node, "#power-domain-cells",
|
||||
NULL)) {
|
||||
if (!opp_table->is_genpd) {
|
||||
dev_err(dev, "%s: opp-hz not found\n", __func__);
|
||||
goto free_opp;
|
||||
}
|
||||
|
@ -354,21 +606,26 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table,
|
|||
new_opp->dynamic = false;
|
||||
new_opp->available = true;
|
||||
|
||||
ret = _of_opp_alloc_required_opps(opp_table, new_opp);
|
||||
if (ret)
|
||||
goto free_opp;
|
||||
|
||||
if (!of_property_read_u32(np, "clock-latency-ns", &val))
|
||||
new_opp->clock_latency_ns = val;
|
||||
|
||||
new_opp->pstate = of_genpd_opp_to_performance_state(dev, np);
|
||||
|
||||
ret = opp_parse_supplies(new_opp, dev, opp_table);
|
||||
if (ret)
|
||||
goto free_opp;
|
||||
goto free_required_opps;
|
||||
|
||||
if (opp_table->is_genpd)
|
||||
new_opp->pstate = pm_genpd_opp_to_performance_state(dev, new_opp);
|
||||
|
||||
ret = _opp_add(dev, new_opp, opp_table, rate_not_available);
|
||||
if (ret) {
|
||||
/* Don't return error for duplicate OPPs */
|
||||
if (ret == -EBUSY)
|
||||
ret = 0;
|
||||
goto free_opp;
|
||||
goto free_required_opps;
|
||||
}
|
||||
|
||||
/* OPP to select on device suspend */
|
||||
|
@ -398,6 +655,8 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table,
|
|||
blocking_notifier_call_chain(&opp_table->head, OPP_EVENT_ADD, new_opp);
|
||||
return new_opp;
|
||||
|
||||
free_required_opps:
|
||||
_of_opp_free_required_opps(opp_table, new_opp);
|
||||
free_opp:
|
||||
_opp_free(new_opp);
|
||||
|
||||
|
@ -727,58 +986,48 @@ int dev_pm_opp_of_get_sharing_cpus(struct device *cpu_dev,
|
|||
EXPORT_SYMBOL_GPL(dev_pm_opp_of_get_sharing_cpus);
|
||||
|
||||
/**
|
||||
* of_dev_pm_opp_find_required_opp() - Search for required OPP.
|
||||
* @dev: The device whose OPP node is referenced by the 'np' DT node.
|
||||
* of_get_required_opp_performance_state() - Search for required OPP and return its performance state.
|
||||
* @np: Node that contains the "required-opps" property.
|
||||
* @index: Index of the phandle to parse.
|
||||
*
|
||||
* Returns the OPP of the device 'dev', whose phandle is present in the "np"
|
||||
* node. Although the "required-opps" property supports having multiple
|
||||
* phandles, this helper routine only parses the very first phandle in the list.
|
||||
* Returns the performance state of the OPP pointed out by the "required-opps"
|
||||
* property at @index in @np.
|
||||
*
|
||||
* Return: Matching opp, else returns ERR_PTR in case of error and should be
|
||||
* handled using IS_ERR.
|
||||
*
|
||||
* The callers are required to call dev_pm_opp_put() for the returned OPP after
|
||||
* use.
|
||||
* Return: Zero or positive performance state on success, otherwise negative
|
||||
* value on errors.
|
||||
*/
|
||||
struct dev_pm_opp *of_dev_pm_opp_find_required_opp(struct device *dev,
|
||||
struct device_node *np)
|
||||
int of_get_required_opp_performance_state(struct device_node *np, int index)
|
||||
{
|
||||
struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ENODEV);
|
||||
struct dev_pm_opp *opp;
|
||||
struct device_node *required_np;
|
||||
struct opp_table *opp_table;
|
||||
int pstate = -EINVAL;
|
||||
|
||||
opp_table = _find_opp_table(dev);
|
||||
if (IS_ERR(opp_table))
|
||||
return ERR_CAST(opp_table);
|
||||
required_np = of_parse_required_opp(np, index);
|
||||
if (!required_np)
|
||||
return -EINVAL;
|
||||
|
||||
required_np = of_parse_phandle(np, "required-opps", 0);
|
||||
if (unlikely(!required_np)) {
|
||||
dev_err(dev, "Unable to parse required-opps\n");
|
||||
goto put_opp_table;
|
||||
opp_table = _find_table_of_opp_np(required_np);
|
||||
if (IS_ERR(opp_table)) {
|
||||
pr_err("%s: Failed to find required OPP table %pOF: %ld\n",
|
||||
__func__, np, PTR_ERR(opp_table));
|
||||
goto put_required_np;
|
||||
}
|
||||
|
||||
mutex_lock(&opp_table->lock);
|
||||
|
||||
list_for_each_entry(temp_opp, &opp_table->opp_list, node) {
|
||||
if (temp_opp->available && temp_opp->np == required_np) {
|
||||
opp = temp_opp;
|
||||
|
||||
/* Increment the reference count of OPP */
|
||||
dev_pm_opp_get(opp);
|
||||
break;
|
||||
}
|
||||
opp = _find_opp_of_np(opp_table, required_np);
|
||||
if (opp) {
|
||||
pstate = opp->pstate;
|
||||
dev_pm_opp_put(opp);
|
||||
}
|
||||
|
||||
mutex_unlock(&opp_table->lock);
|
||||
|
||||
of_node_put(required_np);
|
||||
put_opp_table:
|
||||
dev_pm_opp_put_opp_table(opp_table);
|
||||
|
||||
return opp;
|
||||
put_required_np:
|
||||
of_node_put(required_np);
|
||||
|
||||
return pstate;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_dev_pm_opp_find_required_opp);
|
||||
EXPORT_SYMBOL_GPL(of_get_required_opp_performance_state);
|
||||
|
||||
/**
|
||||
* dev_pm_opp_get_of_node() - Gets the DT node corresponding to an opp
|
||||
|
|
|
@ -63,6 +63,7 @@ extern struct list_head opp_tables;
|
|||
* @supplies: Power supplies voltage/current values
|
||||
* @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's
|
||||
* frequency from any other OPP's frequency.
|
||||
* @required_opps: List of OPPs that are required by this OPP.
|
||||
* @opp_table: points back to the opp_table struct this opp belongs to
|
||||
* @np: OPP's device node.
|
||||
* @dentry: debugfs dentry pointer (per opp)
|
||||
|
@ -84,6 +85,7 @@ struct dev_pm_opp {
|
|||
|
||||
unsigned long clock_latency_ns;
|
||||
|
||||
struct dev_pm_opp **required_opps;
|
||||
struct opp_table *opp_table;
|
||||
|
||||
struct device_node *np;
|
||||
|
@ -133,13 +135,21 @@ enum opp_table_access {
|
|||
* @parsed_static_opps: True if OPPs are initialized from DT.
|
||||
* @shared_opp: OPP is shared between multiple devices.
|
||||
* @suspend_opp: Pointer to OPP to be used during device suspend.
|
||||
* @genpd_virt_dev_lock: Mutex protecting the genpd virtual device pointers.
|
||||
* @genpd_virt_devs: List of virtual devices for multiple genpd support.
|
||||
* @required_opp_tables: List of device OPP tables that are required by OPPs in
|
||||
* this table.
|
||||
* @required_opp_count: Number of required devices.
|
||||
* @supported_hw: Array of version number to support.
|
||||
* @supported_hw_count: Number of elements in supported_hw array.
|
||||
* @prop_name: A name to postfix to many DT properties, while parsing them.
|
||||
* @clk: Device's clock handle
|
||||
* @regulators: Supply regulators
|
||||
* @regulator_count: Number of power supply regulators
|
||||
* @regulator_count: Number of power supply regulators. Its value can be -1
|
||||
* (uninitialized), 0 (no opp-microvolt property) or > 0 (has opp-microvolt
|
||||
* property).
|
||||
* @genpd_performance_state: Device's power domain support performance state.
|
||||
* @is_genpd: Marks if the OPP table belongs to a genpd.
|
||||
* @set_opp: Platform specific set_opp callback
|
||||
* @set_opp_data: Data to be passed to set_opp callback
|
||||
* @dentry: debugfs dentry pointer of the real device directory (not links).
|
||||
|
@ -171,13 +181,19 @@ struct opp_table {
|
|||
enum opp_table_access shared_opp;
|
||||
struct dev_pm_opp *suspend_opp;
|
||||
|
||||
struct mutex genpd_virt_dev_lock;
|
||||
struct device **genpd_virt_devs;
|
||||
struct opp_table **required_opp_tables;
|
||||
unsigned int required_opp_count;
|
||||
|
||||
unsigned int *supported_hw;
|
||||
unsigned int supported_hw_count;
|
||||
const char *prop_name;
|
||||
struct clk *clk;
|
||||
struct regulator **regulators;
|
||||
unsigned int regulator_count;
|
||||
int regulator_count;
|
||||
bool genpd_performance_state;
|
||||
bool is_genpd;
|
||||
|
||||
int (*set_opp)(struct dev_pm_set_opp_data *data);
|
||||
struct dev_pm_set_opp_data *set_opp_data;
|
||||
|
@ -206,10 +222,16 @@ void _put_opp_list_kref(struct opp_table *opp_table);
|
|||
|
||||
#ifdef CONFIG_OF
|
||||
void _of_init_opp_table(struct opp_table *opp_table, struct device *dev, int index);
|
||||
void _of_clear_opp_table(struct opp_table *opp_table);
|
||||
struct opp_table *_managed_opp(struct device *dev, int index);
|
||||
void _of_opp_free_required_opps(struct opp_table *opp_table,
|
||||
struct dev_pm_opp *opp);
|
||||
#else
|
||||
static inline void _of_init_opp_table(struct opp_table *opp_table, struct device *dev, int index) {}
|
||||
static inline void _of_clear_opp_table(struct opp_table *opp_table) {}
|
||||
static inline struct opp_table *_managed_opp(struct device *dev, int index) { return NULL; }
|
||||
static inline void _of_opp_free_required_opps(struct opp_table *opp_table,
|
||||
struct dev_pm_opp *opp) {}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
|
|
|
@ -73,6 +73,7 @@ struct genpd_power_state {
|
|||
|
||||
struct genpd_lock_ops;
|
||||
struct dev_pm_opp;
|
||||
struct opp_table;
|
||||
|
||||
struct generic_pm_domain {
|
||||
struct device dev;
|
||||
|
@ -94,6 +95,7 @@ struct generic_pm_domain {
|
|||
unsigned int performance_state; /* Aggregated max performance state */
|
||||
int (*power_off)(struct generic_pm_domain *domain);
|
||||
int (*power_on)(struct generic_pm_domain *domain);
|
||||
struct opp_table *opp_table; /* OPP table of the genpd */
|
||||
unsigned int (*opp_to_performance_state)(struct generic_pm_domain *genpd,
|
||||
struct dev_pm_opp *opp);
|
||||
int (*set_performance_state)(struct generic_pm_domain *genpd,
|
||||
|
@ -134,6 +136,10 @@ struct gpd_link {
|
|||
struct list_head master_node;
|
||||
struct generic_pm_domain *slave;
|
||||
struct list_head slave_node;
|
||||
|
||||
/* Sub-domain's per-master domain performance state */
|
||||
unsigned int performance_state;
|
||||
unsigned int prev_performance_state;
|
||||
};
|
||||
|
||||
struct gpd_timing_data {
|
||||
|
@ -258,8 +264,8 @@ int of_genpd_add_subdomain(struct of_phandle_args *parent,
|
|||
struct generic_pm_domain *of_genpd_remove_last(struct device_node *np);
|
||||
int of_genpd_parse_idle_states(struct device_node *dn,
|
||||
struct genpd_power_state **states, int *n);
|
||||
unsigned int of_genpd_opp_to_performance_state(struct device *dev,
|
||||
struct device_node *np);
|
||||
unsigned int pm_genpd_opp_to_performance_state(struct device *genpd_dev,
|
||||
struct dev_pm_opp *opp);
|
||||
|
||||
int genpd_dev_pm_attach(struct device *dev);
|
||||
struct device *genpd_dev_pm_attach_by_id(struct device *dev,
|
||||
|
@ -300,8 +306,8 @@ static inline int of_genpd_parse_idle_states(struct device_node *dn,
|
|||
}
|
||||
|
||||
static inline unsigned int
|
||||
of_genpd_opp_to_performance_state(struct device *dev,
|
||||
struct device_node *np)
|
||||
pm_genpd_opp_to_performance_state(struct device *genpd_dev,
|
||||
struct dev_pm_opp *opp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -126,6 +126,9 @@ struct opp_table *dev_pm_opp_set_clkname(struct device *dev, const char * name);
|
|||
void dev_pm_opp_put_clkname(struct opp_table *opp_table);
|
||||
struct opp_table *dev_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data));
|
||||
void dev_pm_opp_unregister_set_opp_helper(struct opp_table *opp_table);
|
||||
struct opp_table *dev_pm_opp_set_genpd_virt_dev(struct device *dev, struct device *virt_dev, int index);
|
||||
void dev_pm_opp_put_genpd_virt_dev(struct opp_table *opp_table, struct device *virt_dev);
|
||||
int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, struct opp_table *dst_table, unsigned int pstate);
|
||||
int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq);
|
||||
int dev_pm_opp_set_sharing_cpus(struct device *cpu_dev, const struct cpumask *cpumask);
|
||||
int dev_pm_opp_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask);
|
||||
|
@ -272,6 +275,18 @@ static inline struct opp_table *dev_pm_opp_set_clkname(struct device *dev, const
|
|||
|
||||
static inline void dev_pm_opp_put_clkname(struct opp_table *opp_table) {}
|
||||
|
||||
static inline struct opp_table *dev_pm_opp_set_genpd_virt_dev(struct device *dev, struct device *virt_dev, int index)
|
||||
{
|
||||
return ERR_PTR(-ENOTSUPP);
|
||||
}
|
||||
|
||||
static inline void dev_pm_opp_put_genpd_virt_dev(struct opp_table *opp_table, struct device *virt_dev) {}
|
||||
|
||||
static inline int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, struct opp_table *dst_table, unsigned int pstate)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static inline int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
|
@ -305,8 +320,8 @@ int dev_pm_opp_of_cpumask_add_table(const struct cpumask *cpumask);
|
|||
void dev_pm_opp_of_cpumask_remove_table(const struct cpumask *cpumask);
|
||||
int dev_pm_opp_of_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask);
|
||||
struct device_node *dev_pm_opp_of_get_opp_desc_node(struct device *dev);
|
||||
struct dev_pm_opp *of_dev_pm_opp_find_required_opp(struct device *dev, struct device_node *np);
|
||||
struct device_node *dev_pm_opp_get_of_node(struct dev_pm_opp *opp);
|
||||
int of_get_required_opp_performance_state(struct device_node *np, int index);
|
||||
#else
|
||||
static inline int dev_pm_opp_of_add_table(struct device *dev)
|
||||
{
|
||||
|
@ -341,14 +356,14 @@ static inline struct device_node *dev_pm_opp_of_get_opp_desc_node(struct device
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static inline struct dev_pm_opp *of_dev_pm_opp_find_required_opp(struct device *dev, struct device_node *np)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
static inline struct device_node *dev_pm_opp_get_of_node(struct dev_pm_opp *opp)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
static inline int of_get_required_opp_performance_state(struct device_node *np, int index)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __LINUX_OPP_H__ */
|
||||
|
|
Loading…
Reference in New Issue