forked from Minki/linux
91466574be
Pull thermal management updates from Zhang Rui: "This time, the biggest change is the work of representing hardware thermal properties in device tree infrastructure. This work includes the introduction of a device tree bindings for describing the hardware thermal behavior and limits, and also a parser to read and interpret the data, and build thermal zones and thermal binding parameters. It also contains three examples on how to use the new representation on sensor devices, using three different drivers to accomplish it. One driver is in thermal subsystem, the TI SoC thermal, and the other two drivers are in hwmon subsystem. Actually, this would be the first step of the complete work because we still need to check other potential drivers to be converted and then validate the proposed API. But the reason why I include it in this pull request is that, first, this change does not hurt any others without using this approach, second, the principle and concept of this change would not break after converting the remaining drivers. BTW, as you can see, there are several points in this change that do not belong to thermal subsystem. Because it has been suggested by Guenter R that in such cases, it is recommended to send the complete series via one single subsystem. Specifics: - representing hardware thermal properties in device tree infrastructure - fix a regression that the imx thermal driver breaks system suspend. - introduce ACPI INT3403 thermal driver to retrieve temperature data from the INT3403 ACPI device object present on some systems. - introduce debug statement for thermal core and step_wise governor. - assorted fixes and cleanups for thermal core, cpu cooling, exynos thrmal, intel powerclamp and imx thermal driver" * 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux: (34 commits) thermal: remove const flag from .ops of imx thermal Thermal: update thermal zone device after setting emul_temp intel_powerclamp: Fix cstate counter detection. thermal: imx: add necessary clk operation Thermal cpu cooling: return error if no valid cpu frequency entry thermal: fix cpu_cooling max_level behavior thermal: rcar-thermal: Enable driver compilation with COMPILE_TEST thermal: debug: add debug statement for core and step_wise thermal: imx_thermal: add module device table drivers: thermal: Mark function as static in x86_pkg_temp_thermal.c thermal:samsung: fix compilation warning thermal: imx: correct suspend/resume flow thermal: exynos: fix error return code Thermal: ACPI INT3403 thermal driver MAINTAINERS: add thermal bindings entry in thermal domain arm: dts: make OMAP4460 bandgap node to belong to OCP arm: dts: make OMAP443x bandgap node to belong to OCP arm: dts: add cooling properties on omap5 cpu node arm: dts: add omap5 thermal data arm: dts: add omap5 CORE thermal data ...
247 lines
6.4 KiB
C
247 lines
6.4 KiB
C
/*
|
|
* Copyright (C) 2012 Freescale Semiconductor, Inc.
|
|
*
|
|
* The OPP code in function cpu0_set_target() is reused from
|
|
* drivers/cpufreq/omap-cpufreq.c
|
|
*
|
|
* 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>
|
|
|
|
static unsigned int transition_latency;
|
|
static unsigned int voltage_tolerance; /* in percentage */
|
|
|
|
static struct device *cpu_dev;
|
|
static struct clk *cpu_clk;
|
|
static struct regulator *cpu_reg;
|
|
static struct cpufreq_frequency_table *freq_table;
|
|
static struct thermal_cooling_device *cdev;
|
|
|
|
static int cpu0_set_target(struct cpufreq_policy *policy, unsigned int index)
|
|
{
|
|
struct dev_pm_opp *opp;
|
|
unsigned long volt = 0, volt_old = 0, tol = 0;
|
|
unsigned int old_freq, new_freq;
|
|
long freq_Hz, freq_exact;
|
|
int ret;
|
|
|
|
freq_Hz = clk_round_rate(cpu_clk, freq_table[index].frequency * 1000);
|
|
if (freq_Hz <= 0)
|
|
freq_Hz = freq_table[index].frequency * 1000;
|
|
|
|
freq_exact = freq_Hz;
|
|
new_freq = freq_Hz / 1000;
|
|
old_freq = clk_get_rate(cpu_clk) / 1000;
|
|
|
|
if (!IS_ERR(cpu_reg)) {
|
|
rcu_read_lock();
|
|
opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_Hz);
|
|
if (IS_ERR(opp)) {
|
|
rcu_read_unlock();
|
|
pr_err("failed to find OPP for %ld\n", freq_Hz);
|
|
return PTR_ERR(opp);
|
|
}
|
|
volt = dev_pm_opp_get_voltage(opp);
|
|
rcu_read_unlock();
|
|
tol = volt * voltage_tolerance / 100;
|
|
volt_old = regulator_get_voltage(cpu_reg);
|
|
}
|
|
|
|
pr_debug("%u MHz, %ld mV --> %u MHz, %ld mV\n",
|
|
old_freq / 1000, volt_old ? volt_old / 1000 : -1,
|
|
new_freq / 1000, volt ? volt / 1000 : -1);
|
|
|
|
/* scaling up? scale voltage before frequency */
|
|
if (!IS_ERR(cpu_reg) && new_freq > old_freq) {
|
|
ret = regulator_set_voltage_tol(cpu_reg, volt, tol);
|
|
if (ret) {
|
|
pr_err("failed to scale voltage up: %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = clk_set_rate(cpu_clk, freq_exact);
|
|
if (ret) {
|
|
pr_err("failed to set clock rate: %d\n", ret);
|
|
if (!IS_ERR(cpu_reg))
|
|
regulator_set_voltage_tol(cpu_reg, volt_old, tol);
|
|
return ret;
|
|
}
|
|
|
|
/* scaling down? scale voltage after frequency */
|
|
if (!IS_ERR(cpu_reg) && new_freq < old_freq) {
|
|
ret = regulator_set_voltage_tol(cpu_reg, volt, tol);
|
|
if (ret) {
|
|
pr_err("failed to scale voltage down: %d\n", ret);
|
|
clk_set_rate(cpu_clk, old_freq * 1000);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int cpu0_cpufreq_init(struct cpufreq_policy *policy)
|
|
{
|
|
policy->clk = cpu_clk;
|
|
return cpufreq_generic_init(policy, freq_table, transition_latency);
|
|
}
|
|
|
|
static struct cpufreq_driver cpu0_cpufreq_driver = {
|
|
.flags = CPUFREQ_STICKY,
|
|
.verify = cpufreq_generic_frequency_table_verify,
|
|
.target_index = cpu0_set_target,
|
|
.get = cpufreq_generic_get,
|
|
.init = cpu0_cpufreq_init,
|
|
.exit = cpufreq_generic_exit,
|
|
.name = "generic_cpu0",
|
|
.attr = cpufreq_generic_attr,
|
|
};
|
|
|
|
static int cpu0_cpufreq_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *np;
|
|
int ret;
|
|
|
|
cpu_dev = get_cpu_device(0);
|
|
if (!cpu_dev) {
|
|
pr_err("failed to get cpu0 device\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
np = of_node_get(cpu_dev->of_node);
|
|
if (!np) {
|
|
pr_err("failed to find cpu0 node\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
cpu_reg = devm_regulator_get_optional(cpu_dev, "cpu0");
|
|
if (IS_ERR(cpu_reg)) {
|
|
/*
|
|
* If cpu0 regulator supply node is present, but regulator is
|
|
* not yet registered, we should try defering probe.
|
|
*/
|
|
if (PTR_ERR(cpu_reg) == -EPROBE_DEFER) {
|
|
dev_err(cpu_dev, "cpu0 regulator not ready, retry\n");
|
|
ret = -EPROBE_DEFER;
|
|
goto out_put_node;
|
|
}
|
|
pr_warn("failed to get cpu0 regulator: %ld\n",
|
|
PTR_ERR(cpu_reg));
|
|
}
|
|
|
|
cpu_clk = devm_clk_get(cpu_dev, NULL);
|
|
if (IS_ERR(cpu_clk)) {
|
|
ret = PTR_ERR(cpu_clk);
|
|
pr_err("failed to get cpu0 clock: %d\n", ret);
|
|
goto out_put_node;
|
|
}
|
|
|
|
ret = of_init_opp_table(cpu_dev);
|
|
if (ret) {
|
|
pr_err("failed to init OPP table: %d\n", ret);
|
|
goto out_put_node;
|
|
}
|
|
|
|
ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
|
|
if (ret) {
|
|
pr_err("failed to init cpufreq table: %d\n", ret);
|
|
goto out_put_node;
|
|
}
|
|
|
|
of_property_read_u32(np, "voltage-tolerance", &voltage_tolerance);
|
|
|
|
if (of_property_read_u32(np, "clock-latency", &transition_latency))
|
|
transition_latency = CPUFREQ_ETERNAL;
|
|
|
|
if (!IS_ERR(cpu_reg)) {
|
|
struct dev_pm_opp *opp;
|
|
unsigned long min_uV, max_uV;
|
|
int i;
|
|
|
|
/*
|
|
* OPP is maintained in order of increasing frequency, and
|
|
* freq_table initialised from OPP is therefore sorted in the
|
|
* same order.
|
|
*/
|
|
for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++)
|
|
;
|
|
rcu_read_lock();
|
|
opp = dev_pm_opp_find_freq_exact(cpu_dev,
|
|
freq_table[0].frequency * 1000, true);
|
|
min_uV = dev_pm_opp_get_voltage(opp);
|
|
opp = dev_pm_opp_find_freq_exact(cpu_dev,
|
|
freq_table[i-1].frequency * 1000, true);
|
|
max_uV = dev_pm_opp_get_voltage(opp);
|
|
rcu_read_unlock();
|
|
ret = regulator_set_voltage_time(cpu_reg, min_uV, max_uV);
|
|
if (ret > 0)
|
|
transition_latency += ret * 1000;
|
|
}
|
|
|
|
ret = cpufreq_register_driver(&cpu0_cpufreq_driver);
|
|
if (ret) {
|
|
pr_err("failed register driver: %d\n", ret);
|
|
goto out_free_table;
|
|
}
|
|
|
|
/*
|
|
* For now, just loading the cooling device;
|
|
* thermal DT code takes care of matching them.
|
|
*/
|
|
if (of_find_property(np, "#cooling-cells", NULL)) {
|
|
cdev = of_cpufreq_cooling_register(np, cpu_present_mask);
|
|
if (IS_ERR(cdev))
|
|
pr_err("running cpufreq without cooling device: %ld\n",
|
|
PTR_ERR(cdev));
|
|
}
|
|
|
|
of_node_put(np);
|
|
return 0;
|
|
|
|
out_free_table:
|
|
dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
|
|
out_put_node:
|
|
of_node_put(np);
|
|
return ret;
|
|
}
|
|
|
|
static int cpu0_cpufreq_remove(struct platform_device *pdev)
|
|
{
|
|
cpufreq_cooling_unregister(cdev);
|
|
cpufreq_unregister_driver(&cpu0_cpufreq_driver);
|
|
dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver cpu0_cpufreq_platdrv = {
|
|
.driver = {
|
|
.name = "cpufreq-cpu0",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
.probe = cpu0_cpufreq_probe,
|
|
.remove = cpu0_cpufreq_remove,
|
|
};
|
|
module_platform_driver(cpu0_cpufreq_platdrv);
|
|
|
|
MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
|
|
MODULE_DESCRIPTION("Generic CPU0 cpufreq driver");
|
|
MODULE_LICENSE("GPL");
|