mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-21 16:29:59 +00:00
![Viresh Kumar](/assets/img/avatar_default.png)
We can not call dev_pm_opp_of_cpumask_remove_table() freely anymore since the latest OPP core updates as that uses reference counting to free resources. There are cases where no static OPPs are added (using DT) for a platform and trying to remove the OPP table may end up decrementing refcount which is already zero and hence generating warnings. Lets track if we were able to add static OPPs or not and then only remove the table based on that. Some reshuffling of code is also done to do that. Reported-by: Niklas Cassel <niklas.cassel@linaro.org> Tested-by: Niklas Cassel <niklas.cassel@linaro.org> Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
388 lines
9.4 KiB
C
388 lines
9.4 KiB
C
/*
|
|
* Copyright (C) 2012 Freescale Semiconductor, Inc.
|
|
*
|
|
* Copyright (C) 2014 Linaro.
|
|
* Viresh Kumar <viresh.kumar@linaro.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/cpu_cooling.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/cpumask.h>
|
|
#include <linux/err.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/pm_opp.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/thermal.h>
|
|
|
|
#include "cpufreq-dt.h"
|
|
|
|
struct private_data {
|
|
struct opp_table *opp_table;
|
|
struct device *cpu_dev;
|
|
struct thermal_cooling_device *cdev;
|
|
const char *reg_name;
|
|
bool have_static_opps;
|
|
};
|
|
|
|
static struct freq_attr *cpufreq_dt_attr[] = {
|
|
&cpufreq_freq_attr_scaling_available_freqs,
|
|
NULL, /* Extra space for boost-attr if required */
|
|
NULL,
|
|
};
|
|
|
|
static int set_target(struct cpufreq_policy *policy, unsigned int index)
|
|
{
|
|
struct private_data *priv = policy->driver_data;
|
|
unsigned long freq = policy->freq_table[index].frequency;
|
|
int ret;
|
|
|
|
ret = dev_pm_opp_set_rate(priv->cpu_dev, freq * 1000);
|
|
|
|
if (!ret) {
|
|
arch_set_freq_scale(policy->related_cpus, freq,
|
|
policy->cpuinfo.max_freq);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* An earlier version of opp-v1 bindings used to name the regulator
|
|
* "cpu0-supply", we still need to handle that for backwards compatibility.
|
|
*/
|
|
static const char *find_supply_name(struct device *dev)
|
|
{
|
|
struct device_node *np;
|
|
struct property *pp;
|
|
int cpu = dev->id;
|
|
const char *name = NULL;
|
|
|
|
np = of_node_get(dev->of_node);
|
|
|
|
/* This must be valid for sure */
|
|
if (WARN_ON(!np))
|
|
return NULL;
|
|
|
|
/* Try "cpu0" for older DTs */
|
|
if (!cpu) {
|
|
pp = of_find_property(np, "cpu0-supply", NULL);
|
|
if (pp) {
|
|
name = "cpu0";
|
|
goto node_put;
|
|
}
|
|
}
|
|
|
|
pp = of_find_property(np, "cpu-supply", NULL);
|
|
if (pp) {
|
|
name = "cpu";
|
|
goto node_put;
|
|
}
|
|
|
|
dev_dbg(dev, "no regulator for cpu%d\n", cpu);
|
|
node_put:
|
|
of_node_put(np);
|
|
return name;
|
|
}
|
|
|
|
static int resources_available(void)
|
|
{
|
|
struct device *cpu_dev;
|
|
struct regulator *cpu_reg;
|
|
struct clk *cpu_clk;
|
|
int ret = 0;
|
|
const char *name;
|
|
|
|
cpu_dev = get_cpu_device(0);
|
|
if (!cpu_dev) {
|
|
pr_err("failed to get cpu0 device\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
cpu_clk = clk_get(cpu_dev, NULL);
|
|
ret = PTR_ERR_OR_ZERO(cpu_clk);
|
|
if (ret) {
|
|
/*
|
|
* If cpu's clk node is present, but clock is not yet
|
|
* registered, we should try defering probe.
|
|
*/
|
|
if (ret == -EPROBE_DEFER)
|
|
dev_dbg(cpu_dev, "clock not ready, retry\n");
|
|
else
|
|
dev_err(cpu_dev, "failed to get clock: %d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
clk_put(cpu_clk);
|
|
|
|
name = find_supply_name(cpu_dev);
|
|
/* Platform doesn't require regulator */
|
|
if (!name)
|
|
return 0;
|
|
|
|
cpu_reg = regulator_get_optional(cpu_dev, name);
|
|
ret = PTR_ERR_OR_ZERO(cpu_reg);
|
|
if (ret) {
|
|
/*
|
|
* If cpu's regulator supply node is present, but regulator is
|
|
* not yet registered, we should try defering probe.
|
|
*/
|
|
if (ret == -EPROBE_DEFER)
|
|
dev_dbg(cpu_dev, "cpu0 regulator not ready, retry\n");
|
|
else
|
|
dev_dbg(cpu_dev, "no regulator for cpu0: %d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
regulator_put(cpu_reg);
|
|
return 0;
|
|
}
|
|
|
|
static int cpufreq_init(struct cpufreq_policy *policy)
|
|
{
|
|
struct cpufreq_frequency_table *freq_table;
|
|
struct opp_table *opp_table = NULL;
|
|
struct private_data *priv;
|
|
struct device *cpu_dev;
|
|
struct clk *cpu_clk;
|
|
unsigned int transition_latency;
|
|
bool fallback = false;
|
|
const char *name;
|
|
int ret;
|
|
|
|
cpu_dev = get_cpu_device(policy->cpu);
|
|
if (!cpu_dev) {
|
|
pr_err("failed to get cpu%d device\n", policy->cpu);
|
|
return -ENODEV;
|
|
}
|
|
|
|
cpu_clk = clk_get(cpu_dev, NULL);
|
|
if (IS_ERR(cpu_clk)) {
|
|
ret = PTR_ERR(cpu_clk);
|
|
dev_err(cpu_dev, "%s: failed to get clk: %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Get OPP-sharing information from "operating-points-v2" bindings */
|
|
ret = dev_pm_opp_of_get_sharing_cpus(cpu_dev, policy->cpus);
|
|
if (ret) {
|
|
if (ret != -ENOENT)
|
|
goto out_put_clk;
|
|
|
|
/*
|
|
* operating-points-v2 not supported, fallback to old method of
|
|
* finding shared-OPPs for backward compatibility if the
|
|
* platform hasn't set sharing CPUs.
|
|
*/
|
|
if (dev_pm_opp_get_sharing_cpus(cpu_dev, policy->cpus))
|
|
fallback = true;
|
|
}
|
|
|
|
/*
|
|
* OPP layer will be taking care of regulators now, but it needs to know
|
|
* the name of the regulator first.
|
|
*/
|
|
name = find_supply_name(cpu_dev);
|
|
if (name) {
|
|
opp_table = dev_pm_opp_set_regulators(cpu_dev, &name, 1);
|
|
if (IS_ERR(opp_table)) {
|
|
ret = PTR_ERR(opp_table);
|
|
dev_err(cpu_dev, "Failed to set regulator for cpu%d: %d\n",
|
|
policy->cpu, ret);
|
|
goto out_put_clk;
|
|
}
|
|
}
|
|
|
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
|
if (!priv) {
|
|
ret = -ENOMEM;
|
|
goto out_put_regulator;
|
|
}
|
|
|
|
priv->reg_name = name;
|
|
priv->opp_table = opp_table;
|
|
|
|
/*
|
|
* Initialize OPP tables for all policy->cpus. They will be shared by
|
|
* all CPUs which have marked their CPUs shared with OPP bindings.
|
|
*
|
|
* For platforms not using operating-points-v2 bindings, we do this
|
|
* before updating policy->cpus. Otherwise, we will end up creating
|
|
* duplicate OPPs for policy->cpus.
|
|
*
|
|
* OPPs might be populated at runtime, don't check for error here
|
|
*/
|
|
if (!dev_pm_opp_of_cpumask_add_table(policy->cpus))
|
|
priv->have_static_opps = true;
|
|
|
|
/*
|
|
* But we need OPP table to function so if it is not there let's
|
|
* give platform code chance to provide it for us.
|
|
*/
|
|
ret = dev_pm_opp_get_opp_count(cpu_dev);
|
|
if (ret <= 0) {
|
|
dev_dbg(cpu_dev, "OPP table is not ready, deferring probe\n");
|
|
ret = -EPROBE_DEFER;
|
|
goto out_free_opp;
|
|
}
|
|
|
|
if (fallback) {
|
|
cpumask_setall(policy->cpus);
|
|
|
|
/*
|
|
* OPP tables are initialized only for policy->cpu, do it for
|
|
* others as well.
|
|
*/
|
|
ret = dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus);
|
|
if (ret)
|
|
dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n",
|
|
__func__, ret);
|
|
}
|
|
|
|
ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
|
|
if (ret) {
|
|
dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
|
|
goto out_free_opp;
|
|
}
|
|
|
|
priv->cpu_dev = cpu_dev;
|
|
policy->driver_data = priv;
|
|
policy->clk = cpu_clk;
|
|
policy->freq_table = freq_table;
|
|
|
|
policy->suspend_freq = dev_pm_opp_get_suspend_opp_freq(cpu_dev) / 1000;
|
|
|
|
/* Support turbo/boost mode */
|
|
if (policy_has_boost_freq(policy)) {
|
|
/* This gets disabled by core on driver unregister */
|
|
ret = cpufreq_enable_boost_support();
|
|
if (ret)
|
|
goto out_free_cpufreq_table;
|
|
cpufreq_dt_attr[1] = &cpufreq_freq_attr_scaling_boost_freqs;
|
|
}
|
|
|
|
transition_latency = dev_pm_opp_get_max_transition_latency(cpu_dev);
|
|
if (!transition_latency)
|
|
transition_latency = CPUFREQ_ETERNAL;
|
|
|
|
policy->cpuinfo.transition_latency = transition_latency;
|
|
policy->dvfs_possible_from_any_cpu = true;
|
|
|
|
return 0;
|
|
|
|
out_free_cpufreq_table:
|
|
dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
|
|
out_free_opp:
|
|
if (priv->have_static_opps)
|
|
dev_pm_opp_of_cpumask_remove_table(policy->cpus);
|
|
kfree(priv);
|
|
out_put_regulator:
|
|
if (name)
|
|
dev_pm_opp_put_regulators(opp_table);
|
|
out_put_clk:
|
|
clk_put(cpu_clk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int cpufreq_exit(struct cpufreq_policy *policy)
|
|
{
|
|
struct private_data *priv = policy->driver_data;
|
|
|
|
cpufreq_cooling_unregister(priv->cdev);
|
|
dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
|
|
if (priv->have_static_opps)
|
|
dev_pm_opp_of_cpumask_remove_table(policy->related_cpus);
|
|
if (priv->reg_name)
|
|
dev_pm_opp_put_regulators(priv->opp_table);
|
|
|
|
clk_put(policy->clk);
|
|
kfree(priv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cpufreq_ready(struct cpufreq_policy *policy)
|
|
{
|
|
struct private_data *priv = policy->driver_data;
|
|
|
|
priv->cdev = of_cpufreq_cooling_register(policy);
|
|
}
|
|
|
|
static struct cpufreq_driver dt_cpufreq_driver = {
|
|
.flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK,
|
|
.verify = cpufreq_generic_frequency_table_verify,
|
|
.target_index = set_target,
|
|
.get = cpufreq_generic_get,
|
|
.init = cpufreq_init,
|
|
.exit = cpufreq_exit,
|
|
.ready = cpufreq_ready,
|
|
.name = "cpufreq-dt",
|
|
.attr = cpufreq_dt_attr,
|
|
.suspend = cpufreq_generic_suspend,
|
|
};
|
|
|
|
static int dt_cpufreq_probe(struct platform_device *pdev)
|
|
{
|
|
struct cpufreq_dt_platform_data *data = dev_get_platdata(&pdev->dev);
|
|
int ret;
|
|
|
|
/*
|
|
* All per-cluster (CPUs sharing clock/voltages) initialization is done
|
|
* from ->init(). In probe(), we just need to make sure that clk and
|
|
* regulators are available. Else defer probe and retry.
|
|
*
|
|
* FIXME: Is checking this only for CPU0 sufficient ?
|
|
*/
|
|
ret = resources_available();
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (data) {
|
|
if (data->have_governor_per_policy)
|
|
dt_cpufreq_driver.flags |= CPUFREQ_HAVE_GOVERNOR_PER_POLICY;
|
|
|
|
dt_cpufreq_driver.resume = data->resume;
|
|
if (data->suspend)
|
|
dt_cpufreq_driver.suspend = data->suspend;
|
|
}
|
|
|
|
ret = cpufreq_register_driver(&dt_cpufreq_driver);
|
|
if (ret)
|
|
dev_err(&pdev->dev, "failed register driver: %d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int dt_cpufreq_remove(struct platform_device *pdev)
|
|
{
|
|
cpufreq_unregister_driver(&dt_cpufreq_driver);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver dt_cpufreq_platdrv = {
|
|
.driver = {
|
|
.name = "cpufreq-dt",
|
|
},
|
|
.probe = dt_cpufreq_probe,
|
|
.remove = dt_cpufreq_remove,
|
|
};
|
|
module_platform_driver(dt_cpufreq_platdrv);
|
|
|
|
MODULE_ALIAS("platform:cpufreq-dt");
|
|
MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.org>");
|
|
MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
|
|
MODULE_DESCRIPTION("Generic cpufreq driver");
|
|
MODULE_LICENSE("GPL");
|