summaryrefslogtreecommitdiffstats
path: root/kernel/drivers/thermal
diff options
context:
space:
mode:
authorJosé Pekkarinen <jose.pekkarinen@nokia.com>2016-04-11 10:41:07 +0300
committerJosé Pekkarinen <jose.pekkarinen@nokia.com>2016-04-13 08:17:18 +0300
commite09b41010ba33a20a87472ee821fa407a5b8da36 (patch)
treed10dc367189862e7ca5c592f033dc3726e1df4e3 /kernel/drivers/thermal
parentf93b97fd65072de626c074dbe099a1fff05ce060 (diff)
These changes are the raw update to linux-4.4.6-rt14. Kernel sources
are taken from kernel.org, and rt patch from the rt wiki download page. During the rebasing, the following patch collided: Force tick interrupt and get rid of softirq magic(I70131fb85). Collisions have been removed because its logic was found on the source already. Change-Id: I7f57a4081d9deaa0d9ccfc41a6c8daccdee3b769 Signed-off-by: José Pekkarinen <jose.pekkarinen@nokia.com>
Diffstat (limited to 'kernel/drivers/thermal')
-rw-r--r--kernel/drivers/thermal/Kconfig106
-rw-r--r--kernel/drivers/thermal/Makefile9
-rw-r--r--kernel/drivers/thermal/armada_thermal.c8
-rw-r--r--kernel/drivers/thermal/cpu_cooling.c634
-rw-r--r--kernel/drivers/thermal/db8500_cpufreq_cooling.c1
-rw-r--r--kernel/drivers/thermal/db8500_thermal.c9
-rw-r--r--kernel/drivers/thermal/devfreq_cooling.c573
-rw-r--r--kernel/drivers/thermal/dove_thermal.c2
-rw-r--r--kernel/drivers/thermal/fair_share.c43
-rw-r--r--kernel/drivers/thermal/gov_bang_bang.c5
-rw-r--r--kernel/drivers/thermal/hisi_thermal.c420
-rw-r--r--kernel/drivers/thermal/imx_thermal.c105
-rw-r--r--kernel/drivers/thermal/int340x_thermal/int3400_thermal.c2
-rw-r--r--kernel/drivers/thermal/int340x_thermal/int340x_thermal_zone.c10
-rw-r--r--kernel/drivers/thermal/int340x_thermal/int340x_thermal_zone.h8
-rw-r--r--kernel/drivers/thermal/int340x_thermal/processor_thermal_device.c63
-rw-r--r--kernel/drivers/thermal/intel_pch_thermal.c283
-rw-r--r--kernel/drivers/thermal/intel_powerclamp.c12
-rw-r--r--kernel/drivers/thermal/intel_quark_dts_thermal.c472
-rw-r--r--kernel/drivers/thermal/intel_soc_dts_iosf.c478
-rw-r--r--kernel/drivers/thermal/intel_soc_dts_iosf.h62
-rw-r--r--kernel/drivers/thermal/intel_soc_dts_thermal.c430
-rw-r--r--kernel/drivers/thermal/kirkwood_thermal.c2
-rw-r--r--kernel/drivers/thermal/of-thermal.c57
-rw-r--r--kernel/drivers/thermal/power_allocator.c659
-rw-r--r--kernel/drivers/thermal/qcom-spmi-temp-alarm.c309
-rw-r--r--kernel/drivers/thermal/rcar_thermal.c56
-rw-r--r--kernel/drivers/thermal/rockchip_thermal.c357
-rw-r--r--kernel/drivers/thermal/samsung/Kconfig2
-rw-r--r--kernel/drivers/thermal/samsung/exynos_tmu.c273
-rw-r--r--kernel/drivers/thermal/samsung/exynos_tmu.h1
-rw-r--r--kernel/drivers/thermal/spear_thermal.c2
-rw-r--r--kernel/drivers/thermal/st/st_thermal.c7
-rw-r--r--kernel/drivers/thermal/step_wise.c21
-rw-r--r--kernel/drivers/thermal/tegra_soctherm.c4
-rw-r--r--kernel/drivers/thermal/thermal_core.c527
-rw-r--r--kernel/drivers/thermal/thermal_core.h12
-rw-r--r--kernel/drivers/thermal/thermal_hwmon.c10
-rw-r--r--kernel/drivers/thermal/ti-soc-thermal/Kconfig23
-rw-r--r--kernel/drivers/thermal/ti-soc-thermal/Makefile1
-rw-r--r--kernel/drivers/thermal/ti-soc-thermal/omap3-thermal-data.c176
-rw-r--r--kernel/drivers/thermal/ti-soc-thermal/ti-bandgap.c118
-rw-r--r--kernel/drivers/thermal/ti-soc-thermal/ti-bandgap.h11
-rw-r--r--kernel/drivers/thermal/ti-soc-thermal/ti-thermal-common.c15
-rw-r--r--kernel/drivers/thermal/x86_pkg_temp_thermal.c12
45 files changed, 5514 insertions, 876 deletions
diff --git a/kernel/drivers/thermal/Kconfig b/kernel/drivers/thermal/Kconfig
index af40db0df..8cc4ac64a 100644
--- a/kernel/drivers/thermal/Kconfig
+++ b/kernel/drivers/thermal/Kconfig
@@ -42,6 +42,17 @@ config THERMAL_OF
Say 'Y' here if you need to build thermal infrastructure
based on device tree.
+config THERMAL_WRITABLE_TRIPS
+ bool "Enable writable trip points"
+ help
+ This option allows the system integrator to choose whether
+ trip temperatures can be changed from userspace. The
+ writable trips need to be specified when setting up the
+ thermal zone but the choice here takes precedence.
+
+ Say 'Y' here if you would like to allow userspace tools to
+ change trip temperatures.
+
choice
prompt "Default Thermal governor"
default THERMAL_DEFAULT_GOV_STEP_WISE
@@ -71,6 +82,14 @@ config THERMAL_DEFAULT_GOV_USER_SPACE
Select this if you want to let the user space manage the
platform thermals.
+config THERMAL_DEFAULT_GOV_POWER_ALLOCATOR
+ bool "power_allocator"
+ select THERMAL_GOV_POWER_ALLOCATOR
+ help
+ Select this if you want to control temperature based on
+ system and device power allocation. This governor can only
+ operate on cooling devices that implement the power API.
+
endchoice
config THERMAL_GOV_FAIR_SHARE
@@ -99,6 +118,12 @@ config THERMAL_GOV_USER_SPACE
help
Enable this to let the user space manage the platform thermals.
+config THERMAL_GOV_POWER_ALLOCATOR
+ bool "Power allocator thermal governor"
+ help
+ Enable this to manage platform thermals by dynamically
+ allocating and limiting power to devices.
+
config CPU_THERMAL
bool "generic cpu cooling support"
depends on CPU_FREQ
@@ -122,6 +147,20 @@ config CLOCK_THERMAL
device that is configured to use this cooling mechanism will be
controlled to reduce clock frequency whenever temperature is high.
+config DEVFREQ_THERMAL
+ bool "Generic device cooling support"
+ depends on PM_DEVFREQ
+ depends on PM_OPP
+ help
+ This implements the generic devfreq cooling mechanism through
+ frequency reduction for devices using devfreq.
+
+ This will throttle the device by limiting the maximum allowed DVFS
+ frequency corresponding to the cooling level.
+
+ In order to use the power extensions of the cooling device,
+ devfreq should use the simple_ondemand governor.
+
If you want this support, you should say Y here.
config THERMAL_EMULATION
@@ -136,6 +175,14 @@ config THERMAL_EMULATION
because userland can easily disable the thermal policy by simply
flooding this sysfs node with low temperature values.
+config HISI_THERMAL
+ tristate "Hisilicon thermal driver"
+ depends on (ARCH_HISI && CPU_THERMAL && OF) || COMPILE_TEST
+ help
+ Enable this to plug hisilicon's thermal sensor driver into the Linux
+ thermal framework. cpufreq is used as the cooling device to throttle
+ CPUs when the passive trip is crossed.
+
config IMX_THERMAL
tristate "Temperature sensor driver for Freescale i.MX SoCs"
depends on CPU_THERMAL
@@ -149,7 +196,7 @@ config IMX_THERMAL
config SPEAR_THERMAL
bool "SPEAr thermal sensor driver"
- depends on PLAT_SPEAR
+ depends on PLAT_SPEAR || COMPILE_TEST
depends on OF
help
Enable this to plug the SPEAr thermal sensor driver into the Linux
@@ -157,7 +204,7 @@ config SPEAR_THERMAL
config ROCKCHIP_THERMAL
tristate "Rockchip thermal driver"
- depends on ARCH_ROCKCHIP
+ depends on ARCH_ROCKCHIP || COMPILE_TEST
depends on RESET_CONTROLLER
help
Rockchip thermal driver provides support for Temperature sensor
@@ -175,7 +222,7 @@ config RCAR_THERMAL
config KIRKWOOD_THERMAL
tristate "Temperature sensor on Marvell Kirkwood SoCs"
- depends on MACH_KIRKWOOD
+ depends on MACH_KIRKWOOD || COMPILE_TEST
depends on OF
help
Support for the Kirkwood thermal sensor driver into the Linux thermal
@@ -183,7 +230,7 @@ config KIRKWOOD_THERMAL
config DOVE_THERMAL
tristate "Temperature sensor on Marvell Dove SoCs"
- depends on ARCH_DOVE || MACH_DOVE
+ depends on ARCH_DOVE || MACH_DOVE || COMPILE_TEST
depends on OF
help
Support for the Dove thermal sensor driver in the Linux thermal
@@ -201,7 +248,7 @@ config DB8500_THERMAL
config ARMADA_THERMAL
tristate "Armada 370/XP thermal management"
- depends on ARCH_MVEBU
+ depends on ARCH_MVEBU || COMPILE_TEST
depends on OF
help
Enable this option if you want to have support for thermal management
@@ -242,6 +289,7 @@ config X86_PKG_TEMP_THERMAL
tristate "X86 package temperature thermal driver"
depends on X86_THERMAL_VECTOR
select THERMAL_GOV_USER_SPACE
+ select THERMAL_WRITABLE_TRIPS
default m
help
Enable this to register CPU digital sensor for package temperature as
@@ -249,9 +297,21 @@ config X86_PKG_TEMP_THERMAL
two trip points which can be set by user to get notifications via thermal
notification methods.
+config INTEL_SOC_DTS_IOSF_CORE
+ tristate
+ depends on X86
+ select IOSF_MBI
+ help
+ This is becoming a common feature for Intel SoCs to expose the additional
+ digital temperature sensors (DTSs) using side band interface (IOSF). This
+ implements the common set of helper functions to register, get temperature
+ and get/set thresholds on DTSs.
+
config INTEL_SOC_DTS_THERMAL
tristate "Intel SoCs DTS thermal driver"
- depends on X86 && IOSF_MBI
+ depends on X86
+ select INTEL_SOC_DTS_IOSF_CORE
+ select THERMAL_WRITABLE_TRIPS
help
Enable this to register Intel SoCs (e.g. Bay Trail) platform digital
temperature sensor (DTS). These SoCs have two additional DTSs in
@@ -261,12 +321,24 @@ config INTEL_SOC_DTS_THERMAL
notification methods.The other trip is a critical trip point, which
was set by the driver based on the TJ MAX temperature.
+config INTEL_QUARK_DTS_THERMAL
+ tristate "Intel Quark DTS thermal driver"
+ depends on X86_INTEL_QUARK
+ help
+ Enable this to register Intel Quark SoC (e.g. X1000) platform digital
+ temperature sensor (DTS). For X1000 SoC, it has one on-die DTS.
+ The DTS will be registered as a thermal zone. There are two trip points:
+ hot & critical. The critical trip point default value is set by
+ underlying BIOS/Firmware.
+
config INT340X_THERMAL
tristate "ACPI INT340X thermal drivers"
depends on X86 && ACPI
select THERMAL_GOV_USER_SPACE
select ACPI_THERMAL_REL
select ACPI_FAN
+ select INTEL_SOC_DTS_IOSF_CORE
+ select THERMAL_WRITABLE_TRIPS
help
Newer laptops and tablets that use ACPI may have thermal sensors and
other devices with thermal control capabilities outside the core
@@ -285,12 +357,21 @@ config ACPI_THERMAL_REL
tristate
depends on ACPI
+config INTEL_PCH_THERMAL
+ tristate "Intel PCH Thermal Reporting Driver"
+ depends on X86 && PCI
+ help
+ Enable this to support thermal reporting on certain intel PCHs.
+ Thermal reporting device will provide temperature reading,
+ programmable trip points and other information.
+
menu "Texas Instruments thermal drivers"
+depends on ARCH_HAS_BANDGAP || COMPILE_TEST
source "drivers/thermal/ti-soc-thermal/Kconfig"
endmenu
menu "Samsung thermal drivers"
-depends on ARCH_EXYNOS
+depends on ARCH_EXYNOS || COMPILE_TEST
source "drivers/thermal/samsung/Kconfig"
endmenu
@@ -299,4 +380,15 @@ depends on ARCH_STI && OF
source "drivers/thermal/st/Kconfig"
endmenu
+config QCOM_SPMI_TEMP_ALARM
+ tristate "Qualcomm SPMI PMIC Temperature Alarm"
+ depends on OF && SPMI && IIO
+ select REGMAP_SPMI
+ help
+ This enables a thermal sysfs driver for Qualcomm plug-and-play (QPNP)
+ PMIC devices. It shows up in sysfs as a thermal sensor with multiple
+ trip points. The temperature reported by the thermal sensor reflects the
+ real time die temperature if an ADC is present or an estimate of the
+ temperature based upon the over temperature stage value.
+
endif
diff --git a/kernel/drivers/thermal/Makefile b/kernel/drivers/thermal/Makefile
index fa0dc4867..cfae6a654 100644
--- a/kernel/drivers/thermal/Makefile
+++ b/kernel/drivers/thermal/Makefile
@@ -14,6 +14,7 @@ thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += fair_share.o
thermal_sys-$(CONFIG_THERMAL_GOV_BANG_BANG) += gov_bang_bang.o
thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE) += step_wise.o
thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += user_space.o
+thermal_sys-$(CONFIG_THERMAL_GOV_POWER_ALLOCATOR) += power_allocator.o
# cpufreq cooling
thermal_sys-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
@@ -21,7 +22,11 @@ thermal_sys-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
# clock cooling
thermal_sys-$(CONFIG_CLOCK_THERMAL) += clock_cooling.o
+# devfreq cooling
+thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
+
# platform thermal drivers
+obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o
obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
@@ -34,8 +39,12 @@ obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o
obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o
obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o
obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o
+obj-$(CONFIG_INTEL_SOC_DTS_IOSF_CORE) += intel_soc_dts_iosf.o
obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o
+obj-$(CONFIG_INTEL_QUARK_DTS_THERMAL) += intel_quark_dts_thermal.o
obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/
obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/
+obj-$(CONFIG_INTEL_PCH_THERMAL) += intel_pch_thermal.o
obj-$(CONFIG_ST_THERMAL) += st/
obj-$(CONFIG_TEGRA_SOCTHERM) += tegra_soctherm.o
+obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o
diff --git a/kernel/drivers/thermal/armada_thermal.c b/kernel/drivers/thermal/armada_thermal.c
index 01255fd65..ae7532894 100644
--- a/kernel/drivers/thermal/armada_thermal.c
+++ b/kernel/drivers/thermal/armada_thermal.c
@@ -155,7 +155,7 @@ static bool armada_is_valid(struct armada_thermal_priv *priv)
}
static int armada_get_temp(struct thermal_zone_device *thermal,
- unsigned long *temp)
+ int *temp)
{
struct armada_thermal_priv *priv = thermal->devdata;
unsigned long reg;
@@ -224,9 +224,9 @@ static const struct armada_thermal_data armada380_data = {
.is_valid_shift = 10,
.temp_shift = 0,
.temp_mask = 0x3ff,
- .coef_b = 2931108200UL,
- .coef_m = 5000000UL,
- .coef_div = 10502,
+ .coef_b = 1172499100UL,
+ .coef_m = 2000096UL,
+ .coef_div = 4201,
.inverted = true,
};
diff --git a/kernel/drivers/thermal/cpu_cooling.c b/kernel/drivers/thermal/cpu_cooling.c
index f65f0d109..6ceac4f2d 100644
--- a/kernel/drivers/thermal/cpu_cooling.c
+++ b/kernel/drivers/thermal/cpu_cooling.c
@@ -26,10 +26,13 @@
#include <linux/thermal.h>
#include <linux/cpufreq.h>
#include <linux/err.h>
+#include <linux/pm_opp.h>
#include <linux/slab.h>
#include <linux/cpu.h>
#include <linux/cpu_cooling.h>
+#include <trace/events/thermal.h>
+
/*
* Cooling state <-> CPUFreq frequency
*
@@ -45,6 +48,19 @@
*/
/**
+ * struct power_table - frequency to power conversion
+ * @frequency: frequency in KHz
+ * @power: power in mW
+ *
+ * This structure is built when the cooling device registers and helps
+ * in translating frequency to power and viceversa.
+ */
+struct power_table {
+ u32 frequency;
+ u32 power;
+};
+
+/**
* struct cpufreq_cooling_device - data for cooling device with cpufreq
* @id: unique integer value corresponding to each cpufreq_cooling_device
* registered.
@@ -52,12 +68,21 @@
* registered cooling device.
* @cpufreq_state: integer value representing the current state of cpufreq
* cooling devices.
- * @cpufreq_val: integer value representing the absolute value of the clipped
+ * @clipped_freq: integer value representing the absolute value of the clipped
* frequency.
* @max_level: maximum cooling level. One less than total number of valid
* cpufreq frequencies.
* @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
* @node: list_head to link all cpufreq_cooling_device together.
+ * @last_load: load measured by the latest call to cpufreq_get_actual_power()
+ * @time_in_idle: previous reading of the absolute time that this cpu was idle
+ * @time_in_idle_timestamp: wall time of the last invocation of
+ * get_cpu_idle_time_us()
+ * @dyn_power_table: array of struct power_table for frequency to power
+ * conversion, sorted in ascending order.
+ * @dyn_power_table_entries: number of entries in the @dyn_power_table array
+ * @cpu_dev: the first cpu_device from @allowed_cpus that has OPPs registered
+ * @plat_get_static_power: callback to calculate the static power
*
* This structure is required for keeping information of each registered
* cpufreq_cooling_device.
@@ -66,15 +91,25 @@ struct cpufreq_cooling_device {
int id;
struct thermal_cooling_device *cool_dev;
unsigned int cpufreq_state;
- unsigned int cpufreq_val;
+ unsigned int clipped_freq;
unsigned int max_level;
unsigned int *freq_table; /* In descending order */
struct cpumask allowed_cpus;
struct list_head node;
+ u32 last_load;
+ u64 *time_in_idle;
+ u64 *time_in_idle_timestamp;
+ struct power_table *dyn_power_table;
+ int dyn_power_table_entries;
+ struct device *cpu_dev;
+ get_static_t plat_get_static_power;
};
static DEFINE_IDR(cpufreq_idr);
static DEFINE_MUTEX(cooling_cpufreq_lock);
+static unsigned int cpufreq_dev_count;
+
+static DEFINE_MUTEX(cooling_list_lock);
static LIST_HEAD(cpufreq_dev_list);
/**
@@ -153,14 +188,14 @@ unsigned long cpufreq_cooling_get_level(unsigned int cpu, unsigned int freq)
{
struct cpufreq_cooling_device *cpufreq_dev;
- mutex_lock(&cooling_cpufreq_lock);
+ mutex_lock(&cooling_list_lock);
list_for_each_entry(cpufreq_dev, &cpufreq_dev_list, node) {
if (cpumask_test_cpu(cpu, &cpufreq_dev->allowed_cpus)) {
- mutex_unlock(&cooling_cpufreq_lock);
+ mutex_unlock(&cooling_list_lock);
return get_level(cpufreq_dev, freq);
}
}
- mutex_unlock(&cooling_cpufreq_lock);
+ mutex_unlock(&cooling_list_lock);
pr_err("%s: cpu:%d not part of any cooling device\n", __func__, cpu);
return THERMAL_CSTATE_INVALID;
@@ -183,26 +218,255 @@ static int cpufreq_thermal_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
struct cpufreq_policy *policy = data;
- unsigned long max_freq = 0;
+ unsigned long clipped_freq;
struct cpufreq_cooling_device *cpufreq_dev;
if (event != CPUFREQ_ADJUST)
- return 0;
+ return NOTIFY_DONE;
- mutex_lock(&cooling_cpufreq_lock);
+ mutex_lock(&cooling_list_lock);
list_for_each_entry(cpufreq_dev, &cpufreq_dev_list, node) {
- if (!cpumask_test_cpu(policy->cpu,
- &cpufreq_dev->allowed_cpus))
+ if (!cpumask_test_cpu(policy->cpu, &cpufreq_dev->allowed_cpus))
continue;
- max_freq = cpufreq_dev->cpufreq_val;
+ /*
+ * policy->max is the maximum allowed frequency defined by user
+ * and clipped_freq is the maximum that thermal constraints
+ * allow.
+ *
+ * If clipped_freq is lower than policy->max, then we need to
+ * readjust policy->max.
+ *
+ * But, if clipped_freq is greater than policy->max, we don't
+ * need to do anything.
+ */
+ clipped_freq = cpufreq_dev->clipped_freq;
+
+ if (policy->max > clipped_freq)
+ cpufreq_verify_within_limits(policy, 0, clipped_freq);
+ break;
+ }
+ mutex_unlock(&cooling_list_lock);
+
+ return NOTIFY_OK;
+}
- if (policy->max != max_freq)
- cpufreq_verify_within_limits(policy, 0, max_freq);
+/**
+ * build_dyn_power_table() - create a dynamic power to frequency table
+ * @cpufreq_device: the cpufreq cooling device in which to store the table
+ * @capacitance: dynamic power coefficient for these cpus
+ *
+ * Build a dynamic power to frequency table for this cpu and store it
+ * in @cpufreq_device. This table will be used in cpu_power_to_freq() and
+ * cpu_freq_to_power() to convert between power and frequency
+ * efficiently. Power is stored in mW, frequency in KHz. The
+ * resulting table is in ascending order.
+ *
+ * Return: 0 on success, -EINVAL if there are no OPPs for any CPUs,
+ * -ENOMEM if we run out of memory or -EAGAIN if an OPP was
+ * added/enabled while the function was executing.
+ */
+static int build_dyn_power_table(struct cpufreq_cooling_device *cpufreq_device,
+ u32 capacitance)
+{
+ struct power_table *power_table;
+ struct dev_pm_opp *opp;
+ struct device *dev = NULL;
+ int num_opps = 0, cpu, i, ret = 0;
+ unsigned long freq;
+
+ for_each_cpu(cpu, &cpufreq_device->allowed_cpus) {
+ dev = get_cpu_device(cpu);
+ if (!dev) {
+ dev_warn(&cpufreq_device->cool_dev->device,
+ "No cpu device for cpu %d\n", cpu);
+ continue;
+ }
+
+ num_opps = dev_pm_opp_get_opp_count(dev);
+ if (num_opps > 0)
+ break;
+ else if (num_opps < 0)
+ return num_opps;
}
- mutex_unlock(&cooling_cpufreq_lock);
+
+ if (num_opps == 0)
+ return -EINVAL;
+
+ power_table = kcalloc(num_opps, sizeof(*power_table), GFP_KERNEL);
+ if (!power_table)
+ return -ENOMEM;
+
+ rcu_read_lock();
+
+ for (freq = 0, i = 0;
+ opp = dev_pm_opp_find_freq_ceil(dev, &freq), !IS_ERR(opp);
+ freq++, i++) {
+ u32 freq_mhz, voltage_mv;
+ u64 power;
+
+ if (i >= num_opps) {
+ rcu_read_unlock();
+ ret = -EAGAIN;
+ goto free_power_table;
+ }
+
+ freq_mhz = freq / 1000000;
+ voltage_mv = dev_pm_opp_get_voltage(opp) / 1000;
+
+ /*
+ * Do the multiplication with MHz and millivolt so as
+ * to not overflow.
+ */
+ power = (u64)capacitance * freq_mhz * voltage_mv * voltage_mv;
+ do_div(power, 1000000000);
+
+ /* frequency is stored in power_table in KHz */
+ power_table[i].frequency = freq / 1000;
+
+ /* power is stored in mW */
+ power_table[i].power = power;
+ }
+
+ rcu_read_unlock();
+
+ if (i != num_opps) {
+ ret = PTR_ERR(opp);
+ goto free_power_table;
+ }
+
+ cpufreq_device->cpu_dev = dev;
+ cpufreq_device->dyn_power_table = power_table;
+ cpufreq_device->dyn_power_table_entries = i;
return 0;
+
+free_power_table:
+ kfree(power_table);
+
+ return ret;
+}
+
+static u32 cpu_freq_to_power(struct cpufreq_cooling_device *cpufreq_device,
+ u32 freq)
+{
+ int i;
+ struct power_table *pt = cpufreq_device->dyn_power_table;
+
+ for (i = 1; i < cpufreq_device->dyn_power_table_entries; i++)
+ if (freq < pt[i].frequency)
+ break;
+
+ return pt[i - 1].power;
+}
+
+static u32 cpu_power_to_freq(struct cpufreq_cooling_device *cpufreq_device,
+ u32 power)
+{
+ int i;
+ struct power_table *pt = cpufreq_device->dyn_power_table;
+
+ for (i = 1; i < cpufreq_device->dyn_power_table_entries; i++)
+ if (power < pt[i].power)
+ break;
+
+ return pt[i - 1].frequency;
+}
+
+/**
+ * get_load() - get load for a cpu since last updated
+ * @cpufreq_device: &struct cpufreq_cooling_device for this cpu
+ * @cpu: cpu number
+ * @cpu_idx: index of the cpu in cpufreq_device->allowed_cpus
+ *
+ * Return: The average load of cpu @cpu in percentage since this
+ * function was last called.
+ */
+static u32 get_load(struct cpufreq_cooling_device *cpufreq_device, int cpu,
+ int cpu_idx)
+{
+ u32 load;
+ u64 now, now_idle, delta_time, delta_idle;
+
+ now_idle = get_cpu_idle_time(cpu, &now, 0);
+ delta_idle = now_idle - cpufreq_device->time_in_idle[cpu_idx];
+ delta_time = now - cpufreq_device->time_in_idle_timestamp[cpu_idx];
+
+ if (delta_time <= delta_idle)
+ load = 0;
+ else
+ load = div64_u64(100 * (delta_time - delta_idle), delta_time);
+
+ cpufreq_device->time_in_idle[cpu_idx] = now_idle;
+ cpufreq_device->time_in_idle_timestamp[cpu_idx] = now;
+
+ return load;
+}
+
+/**
+ * get_static_power() - calculate the static power consumed by the cpus
+ * @cpufreq_device: struct &cpufreq_cooling_device for this cpu cdev
+ * @tz: thermal zone device in which we're operating
+ * @freq: frequency in KHz
+ * @power: pointer in which to store the calculated static power
+ *
+ * Calculate the static power consumed by the cpus described by
+ * @cpu_actor running at frequency @freq. This function relies on a
+ * platform specific function that should have been provided when the
+ * actor was registered. If it wasn't, the static power is assumed to
+ * be negligible. The calculated static power is stored in @power.
+ *
+ * Return: 0 on success, -E* on failure.
+ */
+static int get_static_power(struct cpufreq_cooling_device *cpufreq_device,
+ struct thermal_zone_device *tz, unsigned long freq,
+ u32 *power)
+{
+ struct dev_pm_opp *opp;
+ unsigned long voltage;
+ struct cpumask *cpumask = &cpufreq_device->allowed_cpus;
+ unsigned long freq_hz = freq * 1000;
+
+ if (!cpufreq_device->plat_get_static_power ||
+ !cpufreq_device->cpu_dev) {
+ *power = 0;
+ return 0;
+ }
+
+ rcu_read_lock();
+
+ opp = dev_pm_opp_find_freq_exact(cpufreq_device->cpu_dev, freq_hz,
+ true);
+ voltage = dev_pm_opp_get_voltage(opp);
+
+ rcu_read_unlock();
+
+ if (voltage == 0) {
+ dev_warn_ratelimited(cpufreq_device->cpu_dev,
+ "Failed to get voltage for frequency %lu: %ld\n",
+ freq_hz, IS_ERR(opp) ? PTR_ERR(opp) : 0);
+ return -EINVAL;
+ }
+
+ return cpufreq_device->plat_get_static_power(cpumask, tz->passive_delay,
+ voltage, power);
+}
+
+/**
+ * get_dynamic_power() - calculate the dynamic power
+ * @cpufreq_device: &cpufreq_cooling_device for this cdev
+ * @freq: current frequency
+ *
+ * Return: the dynamic power consumed by the cpus described by
+ * @cpufreq_device.
+ */
+static u32 get_dynamic_power(struct cpufreq_cooling_device *cpufreq_device,
+ unsigned long freq)
+{
+ u32 raw_cpu_power;
+
+ raw_cpu_power = cpu_freq_to_power(cpufreq_device, freq);
+ return (raw_cpu_power * cpufreq_device->last_load) / 100;
}
/* cpufreq cooling device callback functions are defined below */
@@ -273,15 +537,210 @@ static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
clip_freq = cpufreq_device->freq_table[state];
cpufreq_device->cpufreq_state = state;
- cpufreq_device->cpufreq_val = clip_freq;
+ cpufreq_device->clipped_freq = clip_freq;
cpufreq_update_policy(cpu);
return 0;
}
+/**
+ * cpufreq_get_requested_power() - get the current power
+ * @cdev: &thermal_cooling_device pointer
+ * @tz: a valid thermal zone device pointer
+ * @power: pointer in which to store the resulting power
+ *
+ * Calculate the current power consumption of the cpus in milliwatts
+ * and store it in @power. This function should actually calculate
+ * the requested power, but it's hard to get the frequency that
+ * cpufreq would have assigned if there were no thermal limits.
+ * Instead, we calculate the current power on the assumption that the
+ * immediate future will look like the immediate past.
+ *
+ * We use the current frequency and the average load since this
+ * function was last called. In reality, there could have been
+ * multiple opps since this function was last called and that affects
+ * the load calculation. While it's not perfectly accurate, this
+ * simplification is good enough and works. REVISIT this, as more
+ * complex code may be needed if experiments show that it's not
+ * accurate enough.
+ *
+ * Return: 0 on success, -E* if getting the static power failed.
+ */
+static int cpufreq_get_requested_power(struct thermal_cooling_device *cdev,
+ struct thermal_zone_device *tz,
+ u32 *power)
+{
+ unsigned long freq;
+ int i = 0, cpu, ret;
+ u32 static_power, dynamic_power, total_load = 0;
+ struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
+ u32 *load_cpu = NULL;
+
+ cpu = cpumask_any_and(&cpufreq_device->allowed_cpus, cpu_online_mask);
+
+ /*
+ * All the CPUs are offline, thus the requested power by
+ * the cdev is 0
+ */
+ if (cpu >= nr_cpu_ids) {
+ *power = 0;
+ return 0;
+ }
+
+ freq = cpufreq_quick_get(cpu);
+
+ if (trace_thermal_power_cpu_get_power_enabled()) {
+ u32 ncpus = cpumask_weight(&cpufreq_device->allowed_cpus);
+
+ load_cpu = kcalloc(ncpus, sizeof(*load_cpu), GFP_KERNEL);
+ }
+
+ for_each_cpu(cpu, &cpufreq_device->allowed_cpus) {
+ u32 load;
+
+ if (cpu_online(cpu))
+ load = get_load(cpufreq_device, cpu, i);
+ else
+ load = 0;
+
+ total_load += load;
+ if (trace_thermal_power_cpu_limit_enabled() && load_cpu)
+ load_cpu[i] = load;
+
+ i++;
+ }
+
+ cpufreq_device->last_load = total_load;
+
+ dynamic_power = get_dynamic_power(cpufreq_device, freq);
+ ret = get_static_power(cpufreq_device, tz, freq, &static_power);
+ if (ret) {
+ kfree(load_cpu);
+ return ret;
+ }
+
+ if (load_cpu) {
+ trace_thermal_power_cpu_get_power(
+ &cpufreq_device->allowed_cpus,
+ freq, load_cpu, i, dynamic_power, static_power);
+
+ kfree(load_cpu);
+ }
+
+ *power = static_power + dynamic_power;
+ return 0;
+}
+
+/**
+ * cpufreq_state2power() - convert a cpu cdev state to power consumed
+ * @cdev: &thermal_cooling_device pointer
+ * @tz: a valid thermal zone device pointer
+ * @state: cooling device state to be converted
+ * @power: pointer in which to store the resulting power
+ *
+ * Convert cooling device state @state into power consumption in
+ * milliwatts assuming 100% load. Store the calculated power in
+ * @power.
+ *
+ * Return: 0 on success, -EINVAL if the cooling device state could not
+ * be converted into a frequency or other -E* if there was an error
+ * when calculating the static power.
+ */
+static int cpufreq_state2power(struct thermal_cooling_device *cdev,
+ struct thermal_zone_device *tz,
+ unsigned long state, u32 *power)
+{
+ unsigned int freq, num_cpus;
+ cpumask_t cpumask;
+ u32 static_power, dynamic_power;
+ int ret;
+ struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
+
+ cpumask_and(&cpumask, &cpufreq_device->allowed_cpus, cpu_online_mask);
+ num_cpus = cpumask_weight(&cpumask);
+
+ /* None of our cpus are online, so no power */
+ if (num_cpus == 0) {
+ *power = 0;
+ return 0;
+ }
+
+ freq = cpufreq_device->freq_table[state];
+ if (!freq)
+ return -EINVAL;
+
+ dynamic_power = cpu_freq_to_power(cpufreq_device, freq) * num_cpus;
+ ret = get_static_power(cpufreq_device, tz, freq, &static_power);
+ if (ret)
+ return ret;
+
+ *power = static_power + dynamic_power;
+ return 0;
+}
+
+/**
+ * cpufreq_power2state() - convert power to a cooling device state
+ * @cdev: &thermal_cooling_device pointer
+ * @tz: a valid thermal zone device pointer
+ * @power: power in milliwatts to be converted
+ * @state: pointer in which to store the resulting state
+ *
+ * Calculate a cooling device state for the cpus described by @cdev
+ * that would allow them to consume at most @power mW and store it in
+ * @state. Note that this calculation depends on external factors
+ * such as the cpu load or the current static power. Calling this
+ * function with the same power as input can yield different cooling
+ * device states depending on those external factors.
+ *
+ * Return: 0 on success, -ENODEV if no cpus are online or -EINVAL if
+ * the calculated frequency could not be converted to a valid state.
+ * The latter should not happen unless the frequencies available to
+ * cpufreq have changed since the initialization of the cpu cooling
+ * device.
+ */
+static int cpufreq_power2state(struct thermal_cooling_device *cdev,
+ struct thermal_zone_device *tz, u32 power,
+ unsigned long *state)
+{
+ unsigned int cpu, cur_freq, target_freq;
+ int ret;
+ s32 dyn_power;
+ u32 last_load, normalised_power, static_power;
+ struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
+
+ cpu = cpumask_any_and(&cpufreq_device->allowed_cpus, cpu_online_mask);
+
+ /* None of our cpus are online */
+ if (cpu >= nr_cpu_ids)
+ return -ENODEV;
+
+ cur_freq = cpufreq_quick_get(cpu);
+ ret = get_static_power(cpufreq_device, tz, cur_freq, &static_power);
+ if (ret)
+ return ret;
+
+ dyn_power = power - static_power;
+ dyn_power = dyn_power > 0 ? dyn_power : 0;
+ last_load = cpufreq_device->last_load ?: 1;
+ normalised_power = (dyn_power * 100) / last_load;
+ target_freq = cpu_power_to_freq(cpufreq_device, normalised_power);
+
+ *state = cpufreq_cooling_get_level(cpu, target_freq);
+ if (*state == THERMAL_CSTATE_INVALID) {
+ dev_warn_ratelimited(&cdev->device,
+ "Failed to convert %dKHz for cpu %d into a cdev state\n",
+ target_freq, cpu);
+ return -EINVAL;
+ }
+
+ trace_thermal_power_cpu_limit(&cpufreq_device->allowed_cpus,
+ target_freq, *state, power);
+ return 0;
+}
+
/* Bind cpufreq callbacks to thermal cooling device ops */
-static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
+static struct thermal_cooling_device_ops cpufreq_cooling_ops = {
.get_max_state = cpufreq_get_max_state,
.get_cur_state = cpufreq_get_cur_state,
.set_cur_state = cpufreq_set_cur_state,
@@ -311,6 +770,9 @@ static unsigned int find_next_max(struct cpufreq_frequency_table *table,
* @np: a valid struct device_node to the cooling device device tree node
* @clip_cpus: cpumask of cpus where the frequency constraints will happen.
* Normally this should be same as cpufreq policy->related_cpus.
+ * @capacitance: dynamic power coefficient for these cpus
+ * @plat_static_func: function to calculate the static power consumed by these
+ * cpus (optional)
*
* This interface function registers the cpufreq cooling device with the name
* "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
@@ -322,13 +784,14 @@ static unsigned int find_next_max(struct cpufreq_frequency_table *table,
*/
static struct thermal_cooling_device *
__cpufreq_cooling_register(struct device_node *np,
- const struct cpumask *clip_cpus)
+ const struct cpumask *clip_cpus, u32 capacitance,
+ get_static_t plat_static_func)
{
struct thermal_cooling_device *cool_dev;
struct cpufreq_cooling_device *cpufreq_dev;
char dev_name[THERMAL_NAME_LENGTH];
struct cpufreq_frequency_table *pos, *table;
- unsigned int freq, i;
+ unsigned int freq, i, num_cpus;
int ret;
table = cpufreq_frequency_get_table(cpumask_first(clip_cpus));
@@ -341,6 +804,23 @@ __cpufreq_cooling_register(struct device_node *np,
if (!cpufreq_dev)
return ERR_PTR(-ENOMEM);
+ num_cpus = cpumask_weight(clip_cpus);
+ cpufreq_dev->time_in_idle = kcalloc(num_cpus,
+ sizeof(*cpufreq_dev->time_in_idle),
+ GFP_KERNEL);
+ if (!cpufreq_dev->time_in_idle) {
+ cool_dev = ERR_PTR(-ENOMEM);
+ goto free_cdev;
+ }
+
+ cpufreq_dev->time_in_idle_timestamp =
+ kcalloc(num_cpus, sizeof(*cpufreq_dev->time_in_idle_timestamp),
+ GFP_KERNEL);
+ if (!cpufreq_dev->time_in_idle_timestamp) {
+ cool_dev = ERR_PTR(-ENOMEM);
+ goto free_time_in_idle;
+ }
+
/* Find max levels */
cpufreq_for_each_valid_entry(pos, table)
cpufreq_dev->max_level++;
@@ -349,7 +829,7 @@ __cpufreq_cooling_register(struct device_node *np,
cpufreq_dev->max_level, GFP_KERNEL);
if (!cpufreq_dev->freq_table) {
cool_dev = ERR_PTR(-ENOMEM);
- goto free_cdev;
+ goto free_time_in_idle_timestamp;
}
/* max_level is an index, not a counter */
@@ -357,10 +837,24 @@ __cpufreq_cooling_register(struct device_node *np,
cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
+ if (capacitance) {
+ cpufreq_cooling_ops.get_requested_power =
+ cpufreq_get_requested_power;
+ cpufreq_cooling_ops.state2power = cpufreq_state2power;
+ cpufreq_cooling_ops.power2state = cpufreq_power2state;
+ cpufreq_dev->plat_get_static_power = plat_static_func;
+
+ ret = build_dyn_power_table(cpufreq_dev, capacitance);
+ if (ret) {
+ cool_dev = ERR_PTR(ret);
+ goto free_table;
+ }
+ }
+
ret = get_idr(&cpufreq_idr, &cpufreq_dev->id);
if (ret) {
cool_dev = ERR_PTR(ret);
- goto free_table;
+ goto free_power_table;
}
snprintf(dev_name, sizeof(dev_name), "thermal-cpufreq-%d",
@@ -383,25 +877,33 @@ __cpufreq_cooling_register(struct device_node *np,
pr_debug("%s: freq:%u KHz\n", __func__, freq);
}
- cpufreq_dev->cpufreq_val = cpufreq_dev->freq_table[0];
+ cpufreq_dev->clipped_freq = cpufreq_dev->freq_table[0];
cpufreq_dev->cool_dev = cool_dev;
mutex_lock(&cooling_cpufreq_lock);
+ mutex_lock(&cooling_list_lock);
+ list_add(&cpufreq_dev->node, &cpufreq_dev_list);
+ mutex_unlock(&cooling_list_lock);
+
/* Register the notifier for first cpufreq cooling device */
- if (list_empty(&cpufreq_dev_list))
+ if (!cpufreq_dev_count++)
cpufreq_register_notifier(&thermal_cpufreq_notifier_block,
CPUFREQ_POLICY_NOTIFIER);
- list_add(&cpufreq_dev->node, &cpufreq_dev_list);
-
mutex_unlock(&cooling_cpufreq_lock);
return cool_dev;
remove_idr:
release_idr(&cpufreq_idr, cpufreq_dev->id);
+free_power_table:
+ kfree(cpufreq_dev->dyn_power_table);
free_table:
kfree(cpufreq_dev->freq_table);
+free_time_in_idle_timestamp:
+ kfree(cpufreq_dev->time_in_idle_timestamp);
+free_time_in_idle:
+ kfree(cpufreq_dev->time_in_idle);
free_cdev:
kfree(cpufreq_dev);
@@ -422,7 +924,7 @@ free_cdev:
struct thermal_cooling_device *
cpufreq_cooling_register(const struct cpumask *clip_cpus)
{
- return __cpufreq_cooling_register(NULL, clip_cpus);
+ return __cpufreq_cooling_register(NULL, clip_cpus, 0, NULL);
}
EXPORT_SYMBOL_GPL(cpufreq_cooling_register);
@@ -446,11 +948,78 @@ of_cpufreq_cooling_register(struct device_node *np,
if (!np)
return ERR_PTR(-EINVAL);
- return __cpufreq_cooling_register(np, clip_cpus);
+ return __cpufreq_cooling_register(np, clip_cpus, 0, NULL);
}
EXPORT_SYMBOL_GPL(of_cpufreq_cooling_register);
/**
+ * cpufreq_power_cooling_register() - create cpufreq cooling device with power extensions
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen
+ * @capacitance: dynamic power coefficient for these cpus
+ * @plat_static_func: function to calculate the static power consumed by these
+ * cpus (optional)
+ *
+ * This interface function registers the cpufreq cooling device with
+ * the name "thermal-cpufreq-%x". This api can support multiple
+ * instances of cpufreq cooling devices. Using this function, the
+ * cooling device will implement the power extensions by using a
+ * simple cpu power model. The cpus must have registered their OPPs
+ * using the OPP library.
+ *
+ * An optional @plat_static_func may be provided to calculate the
+ * static power consumed by these cpus. If the platform's static
+ * power consumption is unknown or negligible, make it NULL.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * on failure, it returns a corresponding ERR_PTR().
+ */
+struct thermal_cooling_device *
+cpufreq_power_cooling_register(const struct cpumask *clip_cpus, u32 capacitance,
+ get_static_t plat_static_func)
+{
+ return __cpufreq_cooling_register(NULL, clip_cpus, capacitance,
+ plat_static_func);
+}
+EXPORT_SYMBOL(cpufreq_power_cooling_register);
+
+/**
+ * of_cpufreq_power_cooling_register() - create cpufreq cooling device with power extensions
+ * @np: a valid struct device_node to the cooling device device tree node
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen
+ * @capacitance: dynamic power coefficient for these cpus
+ * @plat_static_func: function to calculate the static power consumed by these
+ * cpus (optional)
+ *
+ * This interface function registers the cpufreq cooling device with
+ * the name "thermal-cpufreq-%x". This api can support multiple
+ * instances of cpufreq cooling devices. Using this API, the cpufreq
+ * cooling device will be linked to the device tree node provided.
+ * Using this function, the cooling device will implement the power
+ * extensions by using a simple cpu power model. The cpus must have
+ * registered their OPPs using the OPP library.
+ *
+ * An optional @plat_static_func may be provided to calculate the
+ * static power consumed by these cpus. If the platform's static
+ * power consumption is unknown or negligible, make it NULL.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * on failure, it returns a corresponding ERR_PTR().
+ */
+struct thermal_cooling_device *
+of_cpufreq_power_cooling_register(struct device_node *np,
+ const struct cpumask *clip_cpus,
+ u32 capacitance,
+ get_static_t plat_static_func)
+{
+ if (!np)
+ return ERR_PTR(-EINVAL);
+
+ return __cpufreq_cooling_register(np, clip_cpus, capacitance,
+ plat_static_func);
+}
+EXPORT_SYMBOL(of_cpufreq_power_cooling_register);
+
+/**
* cpufreq_cooling_unregister - function to remove cpufreq cooling device.
* @cdev: thermal cooling device pointer.
*
@@ -464,17 +1033,24 @@ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
return;
cpufreq_dev = cdev->devdata;
- mutex_lock(&cooling_cpufreq_lock);
- list_del(&cpufreq_dev->node);
/* Unregister the notifier for the last cpufreq cooling device */
- if (list_empty(&cpufreq_dev_list))
+ mutex_lock(&cooling_cpufreq_lock);
+ if (!--cpufreq_dev_count)
cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block,
CPUFREQ_POLICY_NOTIFIER);
+
+ mutex_lock(&cooling_list_lock);
+ list_del(&cpufreq_dev->node);
+ mutex_unlock(&cooling_list_lock);
+
mutex_unlock(&cooling_cpufreq_lock);
thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
release_idr(&cpufreq_idr, cpufreq_dev->id);
+ kfree(cpufreq_dev->dyn_power_table);
+ kfree(cpufreq_dev->time_in_idle_timestamp);
+ kfree(cpufreq_dev->time_in_idle);
kfree(cpufreq_dev->freq_table);
kfree(cpufreq_dev);
}
diff --git a/kernel/drivers/thermal/db8500_cpufreq_cooling.c b/kernel/drivers/thermal/db8500_cpufreq_cooling.c
index 607b62c7e..e58bd0b65 100644
--- a/kernel/drivers/thermal/db8500_cpufreq_cooling.c
+++ b/kernel/drivers/thermal/db8500_cpufreq_cooling.c
@@ -72,6 +72,7 @@ static const struct of_device_id db8500_cpufreq_cooling_match[] = {
{ .compatible = "stericsson,db8500-cpufreq-cooling" },
{},
};
+MODULE_DEVICE_TABLE(of, db8500_cpufreq_cooling_match);
#endif
static struct platform_driver db8500_cpufreq_cooling_driver = {
diff --git a/kernel/drivers/thermal/db8500_thermal.c b/kernel/drivers/thermal/db8500_thermal.c
index 20adfbe27..652acd8fb 100644
--- a/kernel/drivers/thermal/db8500_thermal.c
+++ b/kernel/drivers/thermal/db8500_thermal.c
@@ -76,7 +76,7 @@ static int db8500_cdev_bind(struct thermal_zone_device *thermal,
upper = lower = i > max_state ? max_state : i;
ret = thermal_zone_bind_cooling_device(thermal, i, cdev,
- upper, lower);
+ upper, lower, THERMAL_WEIGHT_DEFAULT);
dev_info(&cdev->device, "%s bind to %d: %d-%s\n", cdev->type,
i, ret, ret ? "fail" : "succeed");
@@ -107,8 +107,7 @@ static int db8500_cdev_unbind(struct thermal_zone_device *thermal,
}
/* Callback to get current temperature */
-static int db8500_sys_get_temp(struct thermal_zone_device *thermal,
- unsigned long *temp)
+static int db8500_sys_get_temp(struct thermal_zone_device *thermal, int *temp)
{
struct db8500_thermal_zone *pzone = thermal->devdata;
@@ -180,7 +179,7 @@ static int db8500_sys_get_trip_type(struct thermal_zone_device *thermal,
/* Callback to get trip point temperature */
static int db8500_sys_get_trip_temp(struct thermal_zone_device *thermal,
- int trip, unsigned long *temp)
+ int trip, int *temp)
{
struct db8500_thermal_zone *pzone = thermal->devdata;
struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
@@ -195,7 +194,7 @@ static int db8500_sys_get_trip_temp(struct thermal_zone_device *thermal,
/* Callback to get critical trip point temperature */
static int db8500_sys_get_crit_temp(struct thermal_zone_device *thermal,
- unsigned long *temp)
+ int *temp)
{
struct db8500_thermal_zone *pzone = thermal->devdata;
struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
diff --git a/kernel/drivers/thermal/devfreq_cooling.c b/kernel/drivers/thermal/devfreq_cooling.c
new file mode 100644
index 000000000..01f0015f8
--- /dev/null
+++ b/kernel/drivers/thermal/devfreq_cooling.c
@@ -0,0 +1,573 @@
+/*
+ * devfreq_cooling: Thermal cooling device implementation for devices using
+ * devfreq
+ *
+ * Copyright (C) 2014-2015 ARM Limited
+ *
+ * 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.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * TODO:
+ * - If OPPs are added or removed after devfreq cooling has
+ * registered, the devfreq cooling won't react to it.
+ */
+
+#include <linux/devfreq.h>
+#include <linux/devfreq_cooling.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/pm_opp.h>
+#include <linux/thermal.h>
+
+#include <trace/events/thermal.h>
+
+static DEFINE_MUTEX(devfreq_lock);
+static DEFINE_IDR(devfreq_idr);
+
+/**
+ * struct devfreq_cooling_device - Devfreq cooling device
+ * @id: unique integer value corresponding to each
+ * devfreq_cooling_device registered.
+ * @cdev: Pointer to associated thermal cooling device.
+ * @devfreq: Pointer to associated devfreq device.
+ * @cooling_state: Current cooling state.
+ * @power_table: Pointer to table with maximum power draw for each
+ * cooling state. State is the index into the table, and
+ * the power is in mW.
+ * @freq_table: Pointer to a table with the frequencies sorted in descending
+ * order. You can index the table by cooling device state
+ * @freq_table_size: Size of the @freq_table and @power_table
+ * @power_ops: Pointer to devfreq_cooling_power, used to generate the
+ * @power_table.
+ */
+struct devfreq_cooling_device {
+ int id;
+ struct thermal_cooling_device *cdev;
+ struct devfreq *devfreq;
+ unsigned long cooling_state;
+ u32 *power_table;
+ u32 *freq_table;
+ size_t freq_table_size;
+ struct devfreq_cooling_power *power_ops;
+};
+
+/**
+ * get_idr - function to get a unique id.
+ * @idr: struct idr * handle used to create a id.
+ * @id: int * value generated by this function.
+ *
+ * This function will populate @id with an unique
+ * id, using the idr API.
+ *
+ * Return: 0 on success, an error code on failure.
+ */
+static int get_idr(struct idr *idr, int *id)
+{
+ int ret;
+
+ mutex_lock(&devfreq_lock);
+ ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL);
+ mutex_unlock(&devfreq_lock);
+ if (unlikely(ret < 0))
+ return ret;
+ *id = ret;
+
+ return 0;
+}
+
+/**
+ * release_idr - function to free the unique id.
+ * @idr: struct idr * handle used for creating the id.
+ * @id: int value representing the unique id.
+ */
+static void release_idr(struct idr *idr, int id)
+{
+ mutex_lock(&devfreq_lock);
+ idr_remove(idr, id);
+ mutex_unlock(&devfreq_lock);
+}
+
+/**
+ * partition_enable_opps() - disable all opps above a given state
+ * @dfc: Pointer to devfreq we are operating on
+ * @cdev_state: cooling device state we're setting
+ *
+ * Go through the OPPs of the device, enabling all OPPs until
+ * @cdev_state and disabling those frequencies above it.
+ */
+static int partition_enable_opps(struct devfreq_cooling_device *dfc,
+ unsigned long cdev_state)
+{
+ int i;
+ struct device *dev = dfc->devfreq->dev.parent;
+
+ for (i = 0; i < dfc->freq_table_size; i++) {
+ struct dev_pm_opp *opp;
+ int ret = 0;
+ unsigned int freq = dfc->freq_table[i];
+ bool want_enable = i >= cdev_state ? true : false;
+
+ rcu_read_lock();
+ opp = dev_pm_opp_find_freq_exact(dev, freq, !want_enable);
+ rcu_read_unlock();
+
+ if (PTR_ERR(opp) == -ERANGE)
+ continue;
+ else if (IS_ERR(opp))
+ return PTR_ERR(opp);
+
+ if (want_enable)
+ ret = dev_pm_opp_enable(dev, freq);
+ else
+ ret = dev_pm_opp_disable(dev, freq);
+
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int devfreq_cooling_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct devfreq_cooling_device *dfc = cdev->devdata;
+
+ *state = dfc->freq_table_size - 1;
+
+ return 0;
+}
+
+static int devfreq_cooling_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct devfreq_cooling_device *dfc = cdev->devdata;
+
+ *state = dfc->cooling_state;
+
+ return 0;
+}
+
+static int devfreq_cooling_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ struct devfreq_cooling_device *dfc = cdev->devdata;
+ struct devfreq *df = dfc->devfreq;
+ struct device *dev = df->dev.parent;
+ int ret;
+
+ if (state == dfc->cooling_state)
+ return 0;
+
+ dev_dbg(dev, "Setting cooling state %lu\n", state);
+
+ if (state >= dfc->freq_table_size)
+ return -EINVAL;
+
+ ret = partition_enable_opps(dfc, state);
+ if (ret)
+ return ret;
+
+ dfc->cooling_state = state;
+
+ return 0;
+}
+
+/**
+ * freq_get_state() - get the cooling state corresponding to a frequency
+ * @dfc: Pointer to devfreq cooling device
+ * @freq: frequency in Hz
+ *
+ * Return: the cooling state associated with the @freq, or
+ * THERMAL_CSTATE_INVALID if it wasn't found.
+ */
+static unsigned long
+freq_get_state(struct devfreq_cooling_device *dfc, unsigned long freq)
+{
+ int i;
+
+ for (i = 0; i < dfc->freq_table_size; i++) {
+ if (dfc->freq_table[i] == freq)
+ return i;
+ }
+
+ return THERMAL_CSTATE_INVALID;
+}
+
+/**
+ * get_static_power() - calculate the static power
+ * @dfc: Pointer to devfreq cooling device
+ * @freq: Frequency in Hz
+ *
+ * Calculate the static power in milliwatts using the supplied
+ * get_static_power(). The current voltage is calculated using the
+ * OPP library. If no get_static_power() was supplied, assume the
+ * static power is negligible.
+ */
+static unsigned long
+get_static_power(struct devfreq_cooling_device *dfc, unsigned long freq)
+{
+ struct devfreq *df = dfc->devfreq;
+ struct device *dev = df->dev.parent;
+ unsigned long voltage;
+ struct dev_pm_opp *opp;
+
+ if (!dfc->power_ops->get_static_power)
+ return 0;
+
+ rcu_read_lock();
+
+ opp = dev_pm_opp_find_freq_exact(dev, freq, true);
+ if (IS_ERR(opp) && (PTR_ERR(opp) == -ERANGE))
+ opp = dev_pm_opp_find_freq_exact(dev, freq, false);
+
+ voltage = dev_pm_opp_get_voltage(opp) / 1000; /* mV */
+
+ rcu_read_unlock();
+
+ if (voltage == 0) {
+ dev_warn_ratelimited(dev,
+ "Failed to get voltage for frequency %lu: %ld\n",
+ freq, IS_ERR(opp) ? PTR_ERR(opp) : 0);
+ return 0;
+ }
+
+ return dfc->power_ops->get_static_power(voltage);
+}
+
+/**
+ * get_dynamic_power - calculate the dynamic power
+ * @dfc: Pointer to devfreq cooling device
+ * @freq: Frequency in Hz
+ * @voltage: Voltage in millivolts
+ *
+ * Calculate the dynamic power in milliwatts consumed by the device at
+ * frequency @freq and voltage @voltage. If the get_dynamic_power()
+ * was supplied as part of the devfreq_cooling_power struct, then that
+ * function is used. Otherwise, a simple power model (Pdyn = Coeff *
+ * Voltage^2 * Frequency) is used.
+ */
+static unsigned long
+get_dynamic_power(struct devfreq_cooling_device *dfc, unsigned long freq,
+ unsigned long voltage)
+{
+ u64 power;
+ u32 freq_mhz;
+ struct devfreq_cooling_power *dfc_power = dfc->power_ops;
+
+ if (dfc_power->get_dynamic_power)
+ return dfc_power->get_dynamic_power(freq, voltage);
+
+ freq_mhz = freq / 1000000;
+ power = (u64)dfc_power->dyn_power_coeff * freq_mhz * voltage * voltage;
+ do_div(power, 1000000000);
+
+ return power;
+}
+
+static int devfreq_cooling_get_requested_power(struct thermal_cooling_device *cdev,
+ struct thermal_zone_device *tz,
+ u32 *power)
+{
+ struct devfreq_cooling_device *dfc = cdev->devdata;
+ struct devfreq *df = dfc->devfreq;
+ struct devfreq_dev_status *status = &df->last_status;
+ unsigned long state;
+ unsigned long freq = status->current_frequency;
+ u32 dyn_power, static_power;
+
+ /* Get dynamic power for state */
+ state = freq_get_state(dfc, freq);
+ if (state == THERMAL_CSTATE_INVALID)
+ return -EAGAIN;
+
+ dyn_power = dfc->power_table[state];
+
+ /* Scale dynamic power for utilization */
+ dyn_power = (dyn_power * status->busy_time) / status->total_time;
+
+ /* Get static power */
+ static_power = get_static_power(dfc, freq);
+
+ trace_thermal_power_devfreq_get_power(cdev, status, freq, dyn_power,
+ static_power);
+
+ *power = dyn_power + static_power;
+
+ return 0;
+}
+
+static int devfreq_cooling_state2power(struct thermal_cooling_device *cdev,
+ struct thermal_zone_device *tz,
+ unsigned long state,
+ u32 *power)
+{
+ struct devfreq_cooling_device *dfc = cdev->devdata;
+ unsigned long freq;
+ u32 static_power;
+
+ if (state < 0 || state >= dfc->freq_table_size)
+ return -EINVAL;
+
+ freq = dfc->freq_table[state];
+ static_power = get_static_power(dfc, freq);
+
+ *power = dfc->power_table[state] + static_power;
+ return 0;
+}
+
+static int devfreq_cooling_power2state(struct thermal_cooling_device *cdev,
+ struct thermal_zone_device *tz,
+ u32 power, unsigned long *state)
+{
+ struct devfreq_cooling_device *dfc = cdev->devdata;
+ struct devfreq *df = dfc->devfreq;
+ struct devfreq_dev_status *status = &df->last_status;
+ unsigned long freq = status->current_frequency;
+ unsigned long busy_time;
+ s32 dyn_power;
+ u32 static_power;
+ int i;
+
+ static_power = get_static_power(dfc, freq);
+
+ dyn_power = power - static_power;
+ dyn_power = dyn_power > 0 ? dyn_power : 0;
+
+ /* Scale dynamic power for utilization */
+ busy_time = status->busy_time ?: 1;
+ dyn_power = (dyn_power * status->total_time) / busy_time;
+
+ /*
+ * Find the first cooling state that is within the power
+ * budget for dynamic power.
+ */
+ for (i = 0; i < dfc->freq_table_size - 1; i++)
+ if (dyn_power >= dfc->power_table[i])
+ break;
+
+ *state = i;
+ trace_thermal_power_devfreq_limit(cdev, freq, *state, power);
+ return 0;
+}
+
+static struct thermal_cooling_device_ops devfreq_cooling_ops = {
+ .get_max_state = devfreq_cooling_get_max_state,
+ .get_cur_state = devfreq_cooling_get_cur_state,
+ .set_cur_state = devfreq_cooling_set_cur_state,
+};
+
+/**
+ * devfreq_cooling_gen_tables() - Generate power and freq tables.
+ * @dfc: Pointer to devfreq cooling device.
+ *
+ * Generate power and frequency tables: the power table hold the
+ * device's maximum power usage at each cooling state (OPP). The
+ * static and dynamic power using the appropriate voltage and
+ * frequency for the state, is acquired from the struct
+ * devfreq_cooling_power, and summed to make the maximum power draw.
+ *
+ * The frequency table holds the frequencies in descending order.
+ * That way its indexed by cooling device state.
+ *
+ * The tables are malloced, and pointers put in dfc. They must be
+ * freed when unregistering the devfreq cooling device.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int devfreq_cooling_gen_tables(struct devfreq_cooling_device *dfc)
+{
+ struct devfreq *df = dfc->devfreq;
+ struct device *dev = df->dev.parent;
+ int ret, num_opps;
+ unsigned long freq;
+ u32 *power_table = NULL;
+ u32 *freq_table;
+ int i;
+
+ num_opps = dev_pm_opp_get_opp_count(dev);
+
+ if (dfc->power_ops) {
+ power_table = kcalloc(num_opps, sizeof(*power_table),
+ GFP_KERNEL);
+ if (!power_table)
+ return -ENOMEM;
+ }
+
+ freq_table = kcalloc(num_opps, sizeof(*freq_table),
+ GFP_KERNEL);
+ if (!freq_table) {
+ ret = -ENOMEM;
+ goto free_power_table;
+ }
+
+ for (i = 0, freq = ULONG_MAX; i < num_opps; i++, freq--) {
+ unsigned long power_dyn, voltage;
+ struct dev_pm_opp *opp;
+
+ rcu_read_lock();
+
+ opp = dev_pm_opp_find_freq_floor(dev, &freq);
+ if (IS_ERR(opp)) {
+ rcu_read_unlock();
+ ret = PTR_ERR(opp);
+ goto free_tables;
+ }
+
+ voltage = dev_pm_opp_get_voltage(opp) / 1000; /* mV */
+
+ rcu_read_unlock();
+
+ if (dfc->power_ops) {
+ power_dyn = get_dynamic_power(dfc, freq, voltage);
+
+ dev_dbg(dev, "Dynamic power table: %lu MHz @ %lu mV: %lu = %lu mW\n",
+ freq / 1000000, voltage, power_dyn, power_dyn);
+
+ power_table[i] = power_dyn;
+ }
+
+ freq_table[i] = freq;
+ }
+
+ if (dfc->power_ops)
+ dfc->power_table = power_table;
+
+ dfc->freq_table = freq_table;
+ dfc->freq_table_size = num_opps;
+
+ return 0;
+
+free_tables:
+ kfree(freq_table);
+free_power_table:
+ kfree(power_table);
+
+ return ret;
+}
+
+/**
+ * of_devfreq_cooling_register_power() - Register devfreq cooling device,
+ * with OF and power information.
+ * @np: Pointer to OF device_node.
+ * @df: Pointer to devfreq device.
+ * @dfc_power: Pointer to devfreq_cooling_power.
+ *
+ * Register a devfreq cooling device. The available OPPs must be
+ * registered on the device.
+ *
+ * If @dfc_power is provided, the cooling device is registered with the
+ * power extensions. For the power extensions to work correctly,
+ * devfreq should use the simple_ondemand governor, other governors
+ * are not currently supported.
+ */
+struct thermal_cooling_device *
+of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,
+ struct devfreq_cooling_power *dfc_power)
+{
+ struct thermal_cooling_device *cdev;
+ struct devfreq_cooling_device *dfc;
+ char dev_name[THERMAL_NAME_LENGTH];
+ int err;
+
+ dfc = kzalloc(sizeof(*dfc), GFP_KERNEL);
+ if (!dfc)
+ return ERR_PTR(-ENOMEM);
+
+ dfc->devfreq = df;
+
+ if (dfc_power) {
+ dfc->power_ops = dfc_power;
+
+ devfreq_cooling_ops.get_requested_power =
+ devfreq_cooling_get_requested_power;
+ devfreq_cooling_ops.state2power = devfreq_cooling_state2power;
+ devfreq_cooling_ops.power2state = devfreq_cooling_power2state;
+ }
+
+ err = devfreq_cooling_gen_tables(dfc);
+ if (err)
+ goto free_dfc;
+
+ err = get_idr(&devfreq_idr, &dfc->id);
+ if (err)
+ goto free_tables;
+
+ snprintf(dev_name, sizeof(dev_name), "thermal-devfreq-%d", dfc->id);
+
+ cdev = thermal_of_cooling_device_register(np, dev_name, dfc,
+ &devfreq_cooling_ops);
+ if (IS_ERR(cdev)) {
+ err = PTR_ERR(cdev);
+ dev_err(df->dev.parent,
+ "Failed to register devfreq cooling device (%d)\n",
+ err);
+ goto release_idr;
+ }
+
+ dfc->cdev = cdev;
+
+ return cdev;
+
+release_idr:
+ release_idr(&devfreq_idr, dfc->id);
+free_tables:
+ kfree(dfc->power_table);
+ kfree(dfc->freq_table);
+free_dfc:
+ kfree(dfc);
+
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(of_devfreq_cooling_register_power);
+
+/**
+ * of_devfreq_cooling_register() - Register devfreq cooling device,
+ * with OF information.
+ * @np: Pointer to OF device_node.
+ * @df: Pointer to devfreq device.
+ */
+struct thermal_cooling_device *
+of_devfreq_cooling_register(struct device_node *np, struct devfreq *df)
+{
+ return of_devfreq_cooling_register_power(np, df, NULL);
+}
+EXPORT_SYMBOL_GPL(of_devfreq_cooling_register);
+
+/**
+ * devfreq_cooling_register() - Register devfreq cooling device.
+ * @df: Pointer to devfreq device.
+ */
+struct thermal_cooling_device *devfreq_cooling_register(struct devfreq *df)
+{
+ return of_devfreq_cooling_register(NULL, df);
+}
+EXPORT_SYMBOL_GPL(devfreq_cooling_register);
+
+/**
+ * devfreq_cooling_unregister() - Unregister devfreq cooling device.
+ * @dfc: Pointer to devfreq cooling device to unregister.
+ */
+void devfreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+ struct devfreq_cooling_device *dfc;
+
+ if (!cdev)
+ return;
+
+ dfc = cdev->devdata;
+
+ thermal_cooling_device_unregister(dfc->cdev);
+ release_idr(&devfreq_idr, dfc->id);
+ kfree(dfc->power_table);
+ kfree(dfc->freq_table);
+
+ kfree(dfc);
+}
+EXPORT_SYMBOL_GPL(devfreq_cooling_unregister);
diff --git a/kernel/drivers/thermal/dove_thermal.c b/kernel/drivers/thermal/dove_thermal.c
index 09f6e304c..a0bc9de42 100644
--- a/kernel/drivers/thermal/dove_thermal.c
+++ b/kernel/drivers/thermal/dove_thermal.c
@@ -93,7 +93,7 @@ static int dove_init_sensor(const struct dove_thermal_priv *priv)
}
static int dove_get_temp(struct thermal_zone_device *thermal,
- unsigned long *temp)
+ int *temp)
{
unsigned long reg;
struct dove_thermal_priv *priv = thermal->devdata;
diff --git a/kernel/drivers/thermal/fair_share.c b/kernel/drivers/thermal/fair_share.c
index 6e0a3fbfa..34fe36504 100644
--- a/kernel/drivers/thermal/fair_share.c
+++ b/kernel/drivers/thermal/fair_share.c
@@ -34,7 +34,7 @@
static int get_trip_level(struct thermal_zone_device *tz)
{
int count = 0;
- unsigned long trip_temp;
+ int trip_temp;
enum thermal_trip_type trip_type;
if (tz->trips == 0 || !tz->ops->get_trip_temp)
@@ -59,17 +59,17 @@ static int get_trip_level(struct thermal_zone_device *tz)
}
static long get_target_state(struct thermal_zone_device *tz,
- struct thermal_cooling_device *cdev, int weight, int level)
+ struct thermal_cooling_device *cdev, int percentage, int level)
{
unsigned long max_state;
cdev->ops->get_max_state(cdev, &max_state);
- return (long)(weight * level * max_state) / (100 * tz->trips);
+ return (long)(percentage * level * max_state) / (100 * tz->trips);
}
/**
- * fair_share_throttle - throttles devices asscciated with the given zone
+ * fair_share_throttle - throttles devices associated with the given zone
* @tz - thermal_zone_device
*
* Throttling Logic: This uses three parameters to calculate the new
@@ -77,7 +77,7 @@ static long get_target_state(struct thermal_zone_device *tz,
*
* Parameters used for Throttling:
* P1. max_state: Maximum throttle state exposed by the cooling device.
- * P2. weight[i]/100:
+ * P2. percentage[i]/100:
* How 'effective' the 'i'th device is, in cooling the given zone.
* P3. cur_trip_level/max_no_of_trips:
* This describes the extent to which the devices should be throttled.
@@ -88,28 +88,33 @@ static long get_target_state(struct thermal_zone_device *tz,
*/
static int fair_share_throttle(struct thermal_zone_device *tz, int trip)
{
- const struct thermal_zone_params *tzp;
- struct thermal_cooling_device *cdev;
struct thermal_instance *instance;
- int i;
+ int total_weight = 0;
+ int total_instance = 0;
int cur_trip_level = get_trip_level(tz);
- if (!tz->tzp || !tz->tzp->tbp)
- return -EINVAL;
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ if (instance->trip != trip)
+ continue;
+
+ total_weight += instance->weight;
+ total_instance++;
+ }
- tzp = tz->tzp;
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ int percentage;
+ struct thermal_cooling_device *cdev = instance->cdev;
- for (i = 0; i < tzp->num_tbps; i++) {
- if (!tzp->tbp[i].cdev)
+ if (instance->trip != trip)
continue;
- cdev = tzp->tbp[i].cdev;
- instance = get_thermal_instance(tz, cdev, trip);
- if (!instance)
- continue;
+ if (!total_weight)
+ percentage = 100 / total_instance;
+ else
+ percentage = (instance->weight * 100) / total_weight;
- instance->target = get_target_state(tz, cdev,
- tzp->tbp[i].weight, cur_trip_level);
+ instance->target = get_target_state(tz, cdev, percentage,
+ cur_trip_level);
instance->cdev->updated = false;
thermal_cdev_update(cdev);
diff --git a/kernel/drivers/thermal/gov_bang_bang.c b/kernel/drivers/thermal/gov_bang_bang.c
index c5dd76b2e..70836c5b8 100644
--- a/kernel/drivers/thermal/gov_bang_bang.c
+++ b/kernel/drivers/thermal/gov_bang_bang.c
@@ -25,14 +25,13 @@
static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
{
- long trip_temp;
- unsigned long trip_hyst;
+ int trip_temp, trip_hyst;
struct thermal_instance *instance;
tz->ops->get_trip_temp(tz, trip, &trip_temp);
tz->ops->get_trip_hyst(tz, trip, &trip_hyst);
- dev_dbg(&tz->device, "Trip%d[temp=%ld]:temp=%d:hyst=%ld\n",
+ dev_dbg(&tz->device, "Trip%d[temp=%d]:temp=%d:hyst=%d\n",
trip, trip_temp, tz->temperature,
trip_hyst);
diff --git a/kernel/drivers/thermal/hisi_thermal.c b/kernel/drivers/thermal/hisi_thermal.c
new file mode 100644
index 000000000..36d07295f
--- /dev/null
+++ b/kernel/drivers/thermal/hisi_thermal.c
@@ -0,0 +1,420 @@
+/*
+ * Hisilicon thermal sensor driver
+ *
+ * Copyright (c) 2014-2015 Hisilicon Limited.
+ * Copyright (c) 2014-2015 Linaro Limited.
+ *
+ * Xinwei Kong <kong.kongxinwei@hisilicon.com>
+ * Leo Yan <leo.yan@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.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/cpufreq.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+
+#include "thermal_core.h"
+
+#define TEMP0_TH (0x4)
+#define TEMP0_RST_TH (0x8)
+#define TEMP0_CFG (0xC)
+#define TEMP0_EN (0x10)
+#define TEMP0_INT_EN (0x14)
+#define TEMP0_INT_CLR (0x18)
+#define TEMP0_RST_MSK (0x1C)
+#define TEMP0_VALUE (0x28)
+
+#define HISI_TEMP_BASE (-60)
+#define HISI_TEMP_RESET (100000)
+
+#define HISI_MAX_SENSORS 4
+
+struct hisi_thermal_sensor {
+ struct hisi_thermal_data *thermal;
+ struct thermal_zone_device *tzd;
+
+ long sensor_temp;
+ uint32_t id;
+ uint32_t thres_temp;
+};
+
+struct hisi_thermal_data {
+ struct mutex thermal_lock; /* protects register data */
+ struct platform_device *pdev;
+ struct clk *clk;
+ struct hisi_thermal_sensor sensors[HISI_MAX_SENSORS];
+
+ int irq, irq_bind_sensor;
+ bool irq_enabled;
+
+ void __iomem *regs;
+};
+
+/* in millicelsius */
+static inline int _step_to_temp(int step)
+{
+ /*
+ * Every step equals (1 * 200) / 255 celsius, and finally
+ * need convert to millicelsius.
+ */
+ return (HISI_TEMP_BASE + (step * 200 / 255)) * 1000;
+}
+
+static inline long _temp_to_step(long temp)
+{
+ return ((temp / 1000 - HISI_TEMP_BASE) * 255 / 200);
+}
+
+static long hisi_thermal_get_sensor_temp(struct hisi_thermal_data *data,
+ struct hisi_thermal_sensor *sensor)
+{
+ long val;
+
+ mutex_lock(&data->thermal_lock);
+
+ /* disable interrupt */
+ writel(0x0, data->regs + TEMP0_INT_EN);
+ writel(0x1, data->regs + TEMP0_INT_CLR);
+
+ /* disable module firstly */
+ writel(0x0, data->regs + TEMP0_EN);
+
+ /* select sensor id */
+ writel((sensor->id << 12), data->regs + TEMP0_CFG);
+
+ /* enable module */
+ writel(0x1, data->regs + TEMP0_EN);
+
+ usleep_range(3000, 5000);
+
+ val = readl(data->regs + TEMP0_VALUE);
+ val = _step_to_temp(val);
+
+ mutex_unlock(&data->thermal_lock);
+
+ return val;
+}
+
+static void hisi_thermal_enable_bind_irq_sensor
+ (struct hisi_thermal_data *data)
+{
+ struct hisi_thermal_sensor *sensor;
+
+ mutex_lock(&data->thermal_lock);
+
+ sensor = &data->sensors[data->irq_bind_sensor];
+
+ /* setting the hdak time */
+ writel(0x0, data->regs + TEMP0_CFG);
+
+ /* disable module firstly */
+ writel(0x0, data->regs + TEMP0_RST_MSK);
+ writel(0x0, data->regs + TEMP0_EN);
+
+ /* select sensor id */
+ writel((sensor->id << 12), data->regs + TEMP0_CFG);
+
+ /* enable for interrupt */
+ writel(_temp_to_step(sensor->thres_temp) | 0x0FFFFFF00,
+ data->regs + TEMP0_TH);
+
+ writel(_temp_to_step(HISI_TEMP_RESET), data->regs + TEMP0_RST_TH);
+
+ /* enable module */
+ writel(0x1, data->regs + TEMP0_RST_MSK);
+ writel(0x1, data->regs + TEMP0_EN);
+
+ writel(0x0, data->regs + TEMP0_INT_CLR);
+ writel(0x1, data->regs + TEMP0_INT_EN);
+
+ usleep_range(3000, 5000);
+
+ mutex_unlock(&data->thermal_lock);
+}
+
+static void hisi_thermal_disable_sensor(struct hisi_thermal_data *data)
+{
+ mutex_lock(&data->thermal_lock);
+
+ /* disable sensor module */
+ writel(0x0, data->regs + TEMP0_INT_EN);
+ writel(0x0, data->regs + TEMP0_RST_MSK);
+ writel(0x0, data->regs + TEMP0_EN);
+
+ mutex_unlock(&data->thermal_lock);
+}
+
+static int hisi_thermal_get_temp(void *_sensor, int *temp)
+{
+ struct hisi_thermal_sensor *sensor = _sensor;
+ struct hisi_thermal_data *data = sensor->thermal;
+
+ int sensor_id = 0, i;
+ long max_temp = 0;
+
+ *temp = hisi_thermal_get_sensor_temp(data, sensor);
+
+ sensor->sensor_temp = *temp;
+
+ for (i = 0; i < HISI_MAX_SENSORS; i++) {
+ if (data->sensors[i].sensor_temp >= max_temp) {
+ max_temp = data->sensors[i].sensor_temp;
+ sensor_id = i;
+ }
+ }
+
+ mutex_lock(&data->thermal_lock);
+ data->irq_bind_sensor = sensor_id;
+ mutex_unlock(&data->thermal_lock);
+
+ dev_dbg(&data->pdev->dev, "id=%d, irq=%d, temp=%d, thres=%d\n",
+ sensor->id, data->irq_enabled, *temp, sensor->thres_temp);
+ /*
+ * Bind irq to sensor for two cases:
+ * Reenable alarm IRQ if temperature below threshold;
+ * if irq has been enabled, always set it;
+ */
+ if (data->irq_enabled) {
+ hisi_thermal_enable_bind_irq_sensor(data);
+ return 0;
+ }
+
+ if (max_temp < sensor->thres_temp) {
+ data->irq_enabled = true;
+ hisi_thermal_enable_bind_irq_sensor(data);
+ enable_irq(data->irq);
+ }
+
+ return 0;
+}
+
+static struct thermal_zone_of_device_ops hisi_of_thermal_ops = {
+ .get_temp = hisi_thermal_get_temp,
+};
+
+static irqreturn_t hisi_thermal_alarm_irq(int irq, void *dev)
+{
+ struct hisi_thermal_data *data = dev;
+
+ disable_irq_nosync(irq);
+ data->irq_enabled = false;
+
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t hisi_thermal_alarm_irq_thread(int irq, void *dev)
+{
+ struct hisi_thermal_data *data = dev;
+ struct hisi_thermal_sensor *sensor;
+ int i;
+
+ mutex_lock(&data->thermal_lock);
+ sensor = &data->sensors[data->irq_bind_sensor];
+
+ dev_crit(&data->pdev->dev, "THERMAL ALARM: T > %d\n",
+ sensor->thres_temp / 1000);
+ mutex_unlock(&data->thermal_lock);
+
+ for (i = 0; i < HISI_MAX_SENSORS; i++)
+ thermal_zone_device_update(data->sensors[i].tzd);
+
+ return IRQ_HANDLED;
+}
+
+static int hisi_thermal_register_sensor(struct platform_device *pdev,
+ struct hisi_thermal_data *data,
+ struct hisi_thermal_sensor *sensor,
+ int index)
+{
+ int ret, i;
+ const struct thermal_trip *trip;
+
+ sensor->id = index;
+ sensor->thermal = data;
+
+ sensor->tzd = thermal_zone_of_sensor_register(&pdev->dev, sensor->id,
+ sensor, &hisi_of_thermal_ops);
+ if (IS_ERR(sensor->tzd)) {
+ ret = PTR_ERR(sensor->tzd);
+ dev_err(&pdev->dev, "failed to register sensor id %d: %d\n",
+ sensor->id, ret);
+ return ret;
+ }
+
+ trip = of_thermal_get_trip_points(sensor->tzd);
+
+ for (i = 0; i < of_thermal_get_ntrips(sensor->tzd); i++) {
+ if (trip[i].type == THERMAL_TRIP_PASSIVE) {
+ sensor->thres_temp = trip[i].temperature;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static const struct of_device_id of_hisi_thermal_match[] = {
+ { .compatible = "hisilicon,tsensor" },
+ { /* end */ }
+};
+MODULE_DEVICE_TABLE(of, of_hisi_thermal_match);
+
+static void hisi_thermal_toggle_sensor(struct hisi_thermal_sensor *sensor,
+ bool on)
+{
+ struct thermal_zone_device *tzd = sensor->tzd;
+
+ tzd->ops->set_mode(tzd,
+ on ? THERMAL_DEVICE_ENABLED : THERMAL_DEVICE_DISABLED);
+}
+
+static int hisi_thermal_probe(struct platform_device *pdev)
+{
+ struct hisi_thermal_data *data;
+ struct resource *res;
+ int i;
+ int ret;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ mutex_init(&data->thermal_lock);
+ data->pdev = pdev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ data->regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(data->regs)) {
+ dev_err(&pdev->dev, "failed to get io address\n");
+ return PTR_ERR(data->regs);
+ }
+
+ data->irq = platform_get_irq(pdev, 0);
+ if (data->irq < 0)
+ return data->irq;
+
+ ret = devm_request_threaded_irq(&pdev->dev, data->irq,
+ hisi_thermal_alarm_irq,
+ hisi_thermal_alarm_irq_thread,
+ 0, "hisi_thermal", data);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, data);
+
+ data->clk = devm_clk_get(&pdev->dev, "thermal_clk");
+ if (IS_ERR(data->clk)) {
+ ret = PTR_ERR(data->clk);
+ if (ret != -EPROBE_DEFER)
+ dev_err(&pdev->dev,
+ "failed to get thermal clk: %d\n", ret);
+ return ret;
+ }
+
+ /* enable clock for thermal */
+ ret = clk_prepare_enable(data->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < HISI_MAX_SENSORS; ++i) {
+ ret = hisi_thermal_register_sensor(pdev, data,
+ &data->sensors[i], i);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to register thermal sensor: %d\n", ret);
+ goto err_get_sensor_data;
+ }
+ }
+
+ hisi_thermal_enable_bind_irq_sensor(data);
+ data->irq_enabled = true;
+
+ for (i = 0; i < HISI_MAX_SENSORS; i++)
+ hisi_thermal_toggle_sensor(&data->sensors[i], true);
+
+ return 0;
+
+err_get_sensor_data:
+ clk_disable_unprepare(data->clk);
+
+ return ret;
+}
+
+static int hisi_thermal_remove(struct platform_device *pdev)
+{
+ struct hisi_thermal_data *data = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < HISI_MAX_SENSORS; i++) {
+ struct hisi_thermal_sensor *sensor = &data->sensors[i];
+
+ hisi_thermal_toggle_sensor(sensor, false);
+ thermal_zone_of_sensor_unregister(&pdev->dev, sensor->tzd);
+ }
+
+ hisi_thermal_disable_sensor(data);
+ clk_disable_unprepare(data->clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int hisi_thermal_suspend(struct device *dev)
+{
+ struct hisi_thermal_data *data = dev_get_drvdata(dev);
+
+ hisi_thermal_disable_sensor(data);
+ data->irq_enabled = false;
+
+ clk_disable_unprepare(data->clk);
+
+ return 0;
+}
+
+static int hisi_thermal_resume(struct device *dev)
+{
+ struct hisi_thermal_data *data = dev_get_drvdata(dev);
+
+ clk_prepare_enable(data->clk);
+
+ data->irq_enabled = true;
+ hisi_thermal_enable_bind_irq_sensor(data);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops,
+ hisi_thermal_suspend, hisi_thermal_resume);
+
+static struct platform_driver hisi_thermal_driver = {
+ .driver = {
+ .name = "hisi_thermal",
+ .pm = &hisi_thermal_pm_ops,
+ .of_match_table = of_hisi_thermal_match,
+ },
+ .probe = hisi_thermal_probe,
+ .remove = hisi_thermal_remove,
+};
+
+module_platform_driver(hisi_thermal_driver);
+
+MODULE_AUTHOR("Xinwei Kong <kong.kongxinwei@hisilicon.com>");
+MODULE_AUTHOR("Leo Yan <leo.yan@linaro.org>");
+MODULE_DESCRIPTION("Hisilicon thermal driver");
+MODULE_LICENSE("GPL v2");
diff --git a/kernel/drivers/thermal/imx_thermal.c b/kernel/drivers/thermal/imx_thermal.c
index 2ccbc0788..c5547bd71 100644
--- a/kernel/drivers/thermal/imx_thermal.c
+++ b/kernel/drivers/thermal/imx_thermal.c
@@ -55,6 +55,7 @@
#define TEMPSENSE2_PANIC_VALUE_SHIFT 16
#define TEMPSENSE2_PANIC_VALUE_MASK 0xfff0000
+#define OCOTP_MEM0 0x0480
#define OCOTP_ANA1 0x04e0
/* The driver supports 1 passive trip point and 1 critical trip point */
@@ -64,12 +65,6 @@ enum imx_thermal_trip {
IMX_TRIP_NUM,
};
-/*
- * It defines the temperature in millicelsius for passive trip point
- * that will trigger cooling action when crossed.
- */
-#define IMX_TEMP_PASSIVE 85000
-
#define IMX_POLLING_DELAY 2000 /* millisecond */
#define IMX_PASSIVE_DELAY 1000
@@ -98,18 +93,20 @@ struct imx_thermal_data {
enum thermal_device_mode mode;
struct regmap *tempmon;
u32 c1, c2; /* See formula in imx_get_sensor_data() */
- unsigned long temp_passive;
- unsigned long temp_critical;
- unsigned long alarm_temp;
- unsigned long last_temp;
+ int temp_passive;
+ int temp_critical;
+ int temp_max;
+ int alarm_temp;
+ int last_temp;
bool irq_enabled;
int irq;
struct clk *thermal_clk;
const struct thermal_soc_data *socdata;
+ const char *temp_grade;
};
static void imx_set_panic_temp(struct imx_thermal_data *data,
- signed long panic_temp)
+ int panic_temp)
{
struct regmap *map = data->tempmon;
int critical_value;
@@ -121,7 +118,7 @@ static void imx_set_panic_temp(struct imx_thermal_data *data,
}
static void imx_set_alarm_temp(struct imx_thermal_data *data,
- signed long alarm_temp)
+ int alarm_temp)
{
struct regmap *map = data->tempmon;
int alarm_value;
@@ -133,7 +130,7 @@ static void imx_set_alarm_temp(struct imx_thermal_data *data,
TEMPSENSE0_ALARM_VALUE_SHIFT);
}
-static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp)
+static int imx_get_temp(struct thermal_zone_device *tz, int *temp)
{
struct imx_thermal_data *data = tz->devdata;
struct regmap *map = data->tempmon;
@@ -189,13 +186,13 @@ static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp)
if (data->alarm_temp == data->temp_critical &&
*temp < data->temp_passive) {
imx_set_alarm_temp(data, data->temp_passive);
- dev_dbg(&tz->device, "thermal alarm off: T < %lu\n",
+ dev_dbg(&tz->device, "thermal alarm off: T < %d\n",
data->alarm_temp / 1000);
}
}
if (*temp != data->last_temp) {
- dev_dbg(&tz->device, "millicelsius: %ld\n", *temp);
+ dev_dbg(&tz->device, "millicelsius: %d\n", *temp);
data->last_temp = *temp;
}
@@ -262,8 +259,7 @@ static int imx_get_trip_type(struct thermal_zone_device *tz, int trip,
return 0;
}
-static int imx_get_crit_temp(struct thermal_zone_device *tz,
- unsigned long *temp)
+static int imx_get_crit_temp(struct thermal_zone_device *tz, int *temp)
{
struct imx_thermal_data *data = tz->devdata;
@@ -272,7 +268,7 @@ static int imx_get_crit_temp(struct thermal_zone_device *tz,
}
static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip,
- unsigned long *temp)
+ int *temp)
{
struct imx_thermal_data *data = tz->devdata;
@@ -282,14 +278,16 @@ static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip,
}
static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip,
- unsigned long temp)
+ int temp)
{
struct imx_thermal_data *data = tz->devdata;
+ /* do not allow changing critical threshold */
if (trip == IMX_TRIP_CRITICAL)
return -EPERM;
- if (temp > IMX_TEMP_PASSIVE)
+ /* do not allow passive to be set higher than critical */
+ if (temp < 0 || temp > data->temp_critical)
return -EINVAL;
data->temp_passive = temp;
@@ -306,7 +304,8 @@ static int imx_bind(struct thermal_zone_device *tz,
ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
THERMAL_NO_LIMIT,
- THERMAL_NO_LIMIT);
+ THERMAL_NO_LIMIT,
+ THERMAL_WEIGHT_DEFAULT);
if (ret) {
dev_err(&tz->device,
"binding zone %s with cdev %s failed:%d\n",
@@ -404,17 +403,39 @@ static int imx_get_sensor_data(struct platform_device *pdev)
data->c1 = temp64;
data->c2 = n1 * data->c1 + 1000 * t1;
- /*
- * Set the default passive cooling trip point,
- * can be changed from userspace.
- */
- data->temp_passive = IMX_TEMP_PASSIVE;
+ /* use OTP for thermal grade */
+ ret = regmap_read(map, OCOTP_MEM0, &val);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to read temp grade: %d\n", ret);
+ return ret;
+ }
+
+ /* The maximum die temp is specified by the Temperature Grade */
+ switch ((val >> 6) & 0x3) {
+ case 0: /* Commercial (0 to 95C) */
+ data->temp_grade = "Commercial";
+ data->temp_max = 95000;
+ break;
+ case 1: /* Extended Commercial (-20 to 105C) */
+ data->temp_grade = "Extended Commercial";
+ data->temp_max = 105000;
+ break;
+ case 2: /* Industrial (-40 to 105C) */
+ data->temp_grade = "Industrial";
+ data->temp_max = 105000;
+ break;
+ case 3: /* Automotive (-40 to 125C) */
+ data->temp_grade = "Automotive";
+ data->temp_max = 125000;
+ break;
+ }
/*
- * The maximum die temperature set to 20 C higher than
- * IMX_TEMP_PASSIVE.
+ * Set the critical trip point at 5C under max
+ * Set the passive trip point at 10C under max (can change via sysfs)
*/
- data->temp_critical = 1000 * 20 + data->temp_passive;
+ data->temp_critical = data->temp_max - (1000 * 5);
+ data->temp_passive = data->temp_max - (1000 * 10);
return 0;
}
@@ -433,7 +454,7 @@ static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev)
{
struct imx_thermal_data *data = dev;
- dev_dbg(&data->tz->device, "THERMAL ALARM: T > %lu\n",
+ dev_dbg(&data->tz->device, "THERMAL ALARM: T > %d\n",
data->alarm_temp / 1000);
thermal_zone_device_update(data->tz);
@@ -487,14 +508,6 @@ static int imx_thermal_probe(struct platform_device *pdev)
if (data->irq < 0)
return data->irq;
- ret = devm_request_threaded_irq(&pdev->dev, data->irq,
- imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
- 0, "imx_thermal", data);
- if (ret < 0) {
- dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
- return ret;
- }
-
platform_set_drvdata(pdev, data);
ret = imx_get_sensor_data(pdev);
@@ -559,6 +572,11 @@ static int imx_thermal_probe(struct platform_device *pdev)
return ret;
}
+ dev_info(&pdev->dev, "%s CPU temperature grade - max:%dC"
+ " critical:%dC passive:%dC\n", data->temp_grade,
+ data->temp_max / 1000, data->temp_critical / 1000,
+ data->temp_passive / 1000);
+
/* Enable measurements at ~ 10 Hz */
regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ);
measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */
@@ -571,6 +589,17 @@ static int imx_thermal_probe(struct platform_device *pdev)
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN);
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP);
+ ret = devm_request_threaded_irq(&pdev->dev, data->irq,
+ imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
+ 0, "imx_thermal", data);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
+ clk_disable_unprepare(data->thermal_clk);
+ thermal_zone_device_unregister(data->tz);
+ cpufreq_cooling_unregister(data->cdev);
+ return ret;
+ }
+
data->irq_enabled = true;
data->mode = THERMAL_DEVICE_ENABLED;
diff --git a/kernel/drivers/thermal/int340x_thermal/int3400_thermal.c b/kernel/drivers/thermal/int340x_thermal/int3400_thermal.c
index 031018e7a..5836e5554 100644
--- a/kernel/drivers/thermal/int340x_thermal/int3400_thermal.c
+++ b/kernel/drivers/thermal/int340x_thermal/int3400_thermal.c
@@ -186,7 +186,7 @@ static int int3400_thermal_run_osc(acpi_handle handle,
}
static int int3400_thermal_get_temp(struct thermal_zone_device *thermal,
- unsigned long *temp)
+ int *temp)
{
*temp = 20 * 1000; /* faked temp sensor with 20C */
return 0;
diff --git a/kernel/drivers/thermal/int340x_thermal/int340x_thermal_zone.c b/kernel/drivers/thermal/int340x_thermal/int340x_thermal_zone.c
index 1e25133d3..b9b2666aa 100644
--- a/kernel/drivers/thermal/int340x_thermal/int340x_thermal_zone.c
+++ b/kernel/drivers/thermal/int340x_thermal/int340x_thermal_zone.c
@@ -20,7 +20,7 @@
#include "int340x_thermal_zone.h"
static int int340x_thermal_get_zone_temp(struct thermal_zone_device *zone,
- unsigned long *temp)
+ int *temp)
{
struct int34x_thermal_zone *d = zone->devdata;
unsigned long long tmp;
@@ -49,7 +49,7 @@ static int int340x_thermal_get_zone_temp(struct thermal_zone_device *zone,
}
static int int340x_thermal_get_trip_temp(struct thermal_zone_device *zone,
- int trip, unsigned long *temp)
+ int trip, int *temp)
{
struct int34x_thermal_zone *d = zone->devdata;
int i;
@@ -114,7 +114,7 @@ static int int340x_thermal_get_trip_type(struct thermal_zone_device *zone,
}
static int int340x_thermal_set_trip_temp(struct thermal_zone_device *zone,
- int trip, unsigned long temp)
+ int trip, int temp)
{
struct int34x_thermal_zone *d = zone->devdata;
acpi_status status;
@@ -136,7 +136,7 @@ static int int340x_thermal_set_trip_temp(struct thermal_zone_device *zone,
static int int340x_thermal_get_trip_hyst(struct thermal_zone_device *zone,
- int trip, unsigned long *temp)
+ int trip, int *temp)
{
struct int34x_thermal_zone *d = zone->devdata;
acpi_status status;
@@ -163,7 +163,7 @@ static struct thermal_zone_device_ops int340x_thermal_zone_ops = {
};
static int int340x_thermal_get_trip_config(acpi_handle handle, char *name,
- unsigned long *temp)
+ int *temp)
{
unsigned long long r;
acpi_status status;
diff --git a/kernel/drivers/thermal/int340x_thermal/int340x_thermal_zone.h b/kernel/drivers/thermal/int340x_thermal/int340x_thermal_zone.h
index 9f38ab72c..aaadf724f 100644
--- a/kernel/drivers/thermal/int340x_thermal/int340x_thermal_zone.h
+++ b/kernel/drivers/thermal/int340x_thermal/int340x_thermal_zone.h
@@ -21,7 +21,7 @@
#define INT340X_THERMAL_MAX_ACT_TRIP_COUNT 10
struct active_trip {
- unsigned long temp;
+ int temp;
int id;
bool valid;
};
@@ -31,11 +31,11 @@ struct int34x_thermal_zone {
struct active_trip act_trips[INT340X_THERMAL_MAX_ACT_TRIP_COUNT];
unsigned long *aux_trips;
int aux_trip_nr;
- unsigned long psv_temp;
+ int psv_temp;
int psv_trip_id;
- unsigned long crt_temp;
+ int crt_temp;
int crt_trip_id;
- unsigned long hot_temp;
+ int hot_temp;
int hot_trip_id;
struct thermal_zone_device *zone;
struct thermal_zone_device_ops *override_ops;
diff --git a/kernel/drivers/thermal/int340x_thermal/processor_thermal_device.c b/kernel/drivers/thermal/int340x_thermal/processor_thermal_device.c
index 5e8d8e91e..ccc0ad02d 100644
--- a/kernel/drivers/thermal/int340x_thermal/processor_thermal_device.c
+++ b/kernel/drivers/thermal/int340x_thermal/processor_thermal_device.c
@@ -16,15 +16,20 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/pci.h>
+#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/acpi.h>
#include <linux/thermal.h>
#include "int340x_thermal_zone.h"
+#include "../intel_soc_dts_iosf.h"
/* Broadwell-U/HSB thermal reporting device */
#define PCI_DEVICE_ID_PROC_BDW_THERMAL 0x1603
#define PCI_DEVICE_ID_PROC_HSB_THERMAL 0x0A03
+/* Skylake thermal reporting device */
+#define PCI_DEVICE_ID_PROC_SKL_THERMAL 0x1903
+
/* Braswell thermal reporting device */
#define PCI_DEVICE_ID_PROC_BSW_THERMAL 0x22DC
@@ -42,6 +47,7 @@ struct proc_thermal_device {
struct acpi_device *adev;
struct power_config power_limits[2];
struct int34x_thermal_zone *int340x_zone;
+ struct intel_soc_dts_sensors *soc_dts;
};
enum proc_thermal_emum_mode_type {
@@ -139,7 +145,7 @@ static int get_tjmax(void)
return -EINVAL;
}
-static int read_temp_msr(unsigned long *temp)
+static int read_temp_msr(int *temp)
{
int cpu;
u32 eax, edx;
@@ -171,7 +177,7 @@ err_ret:
}
static int proc_thermal_get_zone_temp(struct thermal_zone_device *zone,
- unsigned long *temp)
+ int *temp)
{
int ret;
@@ -308,6 +314,18 @@ static int int3401_remove(struct platform_device *pdev)
return 0;
}
+static irqreturn_t proc_thermal_pci_msi_irq(int irq, void *devid)
+{
+ struct proc_thermal_device *proc_priv;
+ struct pci_dev *pdev = devid;
+
+ proc_priv = pci_get_drvdata(pdev);
+
+ intel_soc_dts_iosf_interrupt_handler(proc_priv->soc_dts);
+
+ return IRQ_HANDLED;
+}
+
static int proc_thermal_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *unused)
{
@@ -334,18 +352,57 @@ static int proc_thermal_pci_probe(struct pci_dev *pdev,
pci_set_drvdata(pdev, proc_priv);
proc_thermal_emum_mode = PROC_THERMAL_PCI;
+ if (pdev->device == PCI_DEVICE_ID_PROC_BSW_THERMAL) {
+ /*
+ * Enumerate additional DTS sensors available via IOSF.
+ * But we are not treating as a failure condition, if
+ * there are no aux DTSs enabled or fails. This driver
+ * already exposes sensors, which can be accessed via
+ * ACPI/MSR. So we don't want to fail for auxiliary DTSs.
+ */
+ proc_priv->soc_dts = intel_soc_dts_iosf_init(
+ INTEL_SOC_DTS_INTERRUPT_MSI, 2, 0);
+
+ if (proc_priv->soc_dts && pdev->irq) {
+ ret = pci_enable_msi(pdev);
+ if (!ret) {
+ ret = request_threaded_irq(pdev->irq, NULL,
+ proc_thermal_pci_msi_irq,
+ IRQF_ONESHOT, "proc_thermal",
+ pdev);
+ if (ret) {
+ intel_soc_dts_iosf_exit(
+ proc_priv->soc_dts);
+ pci_disable_msi(pdev);
+ proc_priv->soc_dts = NULL;
+ }
+ }
+ } else
+ dev_err(&pdev->dev, "No auxiliary DTSs enabled\n");
+ }
+
return 0;
}
static void proc_thermal_pci_remove(struct pci_dev *pdev)
{
- proc_thermal_remove(pci_get_drvdata(pdev));
+ struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev);
+
+ if (proc_priv->soc_dts) {
+ intel_soc_dts_iosf_exit(proc_priv->soc_dts);
+ if (pdev->irq) {
+ free_irq(pdev->irq, pdev);
+ pci_disable_msi(pdev);
+ }
+ }
+ proc_thermal_remove(proc_priv);
pci_disable_device(pdev);
}
static const struct pci_device_id proc_thermal_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BDW_THERMAL)},
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_HSB_THERMAL)},
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_SKL_THERMAL)},
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BSW_THERMAL)},
{ 0, },
};
diff --git a/kernel/drivers/thermal/intel_pch_thermal.c b/kernel/drivers/thermal/intel_pch_thermal.c
new file mode 100644
index 000000000..50c7da79b
--- /dev/null
+++ b/kernel/drivers/thermal/intel_pch_thermal.c
@@ -0,0 +1,283 @@
+/* intel_pch_thermal.c - Intel PCH Thermal driver
+ *
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * Authors:
+ * Tushar Dave <tushar.n.dave@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/thermal.h>
+
+/* Intel PCH thermal Device IDs */
+#define PCH_THERMAL_DID_WPT 0x9CA4 /* Wildcat Point */
+
+/* Wildcat Point-LP PCH Thermal registers */
+#define WPT_TEMP 0x0000 /* Temperature */
+#define WPT_TSC 0x04 /* Thermal Sensor Control */
+#define WPT_TSS 0x06 /* Thermal Sensor Status */
+#define WPT_TSEL 0x08 /* Thermal Sensor Enable and Lock */
+#define WPT_TSREL 0x0A /* Thermal Sensor Report Enable and Lock */
+#define WPT_TSMIC 0x0C /* Thermal Sensor SMI Control */
+#define WPT_CTT 0x0010 /* Catastrophic Trip Point */
+#define WPT_TAHV 0x0014 /* Thermal Alert High Value */
+#define WPT_TALV 0x0018 /* Thermal Alert Low Value */
+#define WPT_TL 0x00000040 /* Throttle Value */
+#define WPT_PHL 0x0060 /* PCH Hot Level */
+#define WPT_PHLC 0x62 /* PHL Control */
+#define WPT_TAS 0x80 /* Thermal Alert Status */
+#define WPT_TSPIEN 0x82 /* PCI Interrupt Event Enables */
+#define WPT_TSGPEN 0x84 /* General Purpose Event Enables */
+
+/* Wildcat Point-LP PCH Thermal Register bit definitions */
+#define WPT_TEMP_TSR 0x00ff /* Temp TS Reading */
+#define WPT_TSC_CPDE 0x01 /* Catastrophic Power-Down Enable */
+#define WPT_TSS_TSDSS 0x10 /* Thermal Sensor Dynamic Shutdown Status */
+#define WPT_TSS_GPES 0x08 /* GPE status */
+#define WPT_TSEL_ETS 0x01 /* Enable TS */
+#define WPT_TSEL_PLDB 0x80 /* TSEL Policy Lock-Down Bit */
+#define WPT_TL_TOL 0x000001FF /* T0 Level */
+#define WPT_TL_T1L 0x1ff00000 /* T1 Level */
+#define WPT_TL_TTEN 0x20000000 /* TT Enable */
+
+static char driver_name[] = "Intel PCH thermal driver";
+
+struct pch_thermal_device {
+ void __iomem *hw_base;
+ const struct pch_dev_ops *ops;
+ struct pci_dev *pdev;
+ struct thermal_zone_device *tzd;
+ int crt_trip_id;
+ unsigned long crt_temp;
+ int hot_trip_id;
+ unsigned long hot_temp;
+};
+
+static int pch_wpt_init(struct pch_thermal_device *ptd, int *nr_trips)
+{
+ u8 tsel;
+ u16 trip_temp;
+
+ *nr_trips = 0;
+
+ /* Check if BIOS has already enabled thermal sensor */
+ if (WPT_TSS_TSDSS & readb(ptd->hw_base + WPT_TSS))
+ goto read_trips;
+
+ tsel = readb(ptd->hw_base + WPT_TSEL);
+ /*
+ * When TSEL's Policy Lock-Down bit is 1, TSEL become RO.
+ * If so, thermal sensor cannot enable. Bail out.
+ */
+ if (tsel & WPT_TSEL_PLDB) {
+ dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n");
+ return -ENODEV;
+ }
+
+ writeb(tsel|WPT_TSEL_ETS, ptd->hw_base + WPT_TSEL);
+ if (!(WPT_TSS_TSDSS & readb(ptd->hw_base + WPT_TSS))) {
+ dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n");
+ return -ENODEV;
+ }
+
+read_trips:
+ ptd->crt_trip_id = -1;
+ trip_temp = readw(ptd->hw_base + WPT_CTT);
+ trip_temp &= 0x1FF;
+ if (trip_temp) {
+ /* Resolution of 1/2 degree C and an offset of -50C */
+ ptd->crt_temp = trip_temp * 1000 / 2 - 50000;
+ ptd->crt_trip_id = 0;
+ ++(*nr_trips);
+ }
+
+ ptd->hot_trip_id = -1;
+ trip_temp = readw(ptd->hw_base + WPT_PHL);
+ trip_temp &= 0x1FF;
+ if (trip_temp) {
+ /* Resolution of 1/2 degree C and an offset of -50C */
+ ptd->hot_temp = trip_temp * 1000 / 2 - 50000;
+ ptd->hot_trip_id = *nr_trips;
+ ++(*nr_trips);
+ }
+
+ return 0;
+}
+
+static int pch_wpt_get_temp(struct pch_thermal_device *ptd, int *temp)
+{
+ u8 wpt_temp;
+
+ wpt_temp = WPT_TEMP_TSR & readl(ptd->hw_base + WPT_TEMP);
+
+ /* Resolution of 1/2 degree C and an offset of -50C */
+ *temp = (wpt_temp * 1000 / 2 - 50000);
+
+ return 0;
+}
+
+struct pch_dev_ops {
+ int (*hw_init)(struct pch_thermal_device *ptd, int *nr_trips);
+ int (*get_temp)(struct pch_thermal_device *ptd, int *temp);
+};
+
+
+/* dev ops for Wildcat Point */
+static struct pch_dev_ops pch_dev_ops_wpt = {
+ .hw_init = pch_wpt_init,
+ .get_temp = pch_wpt_get_temp,
+};
+
+static int pch_thermal_get_temp(struct thermal_zone_device *tzd, int *temp)
+{
+ struct pch_thermal_device *ptd = tzd->devdata;
+
+ return ptd->ops->get_temp(ptd, temp);
+}
+
+static int pch_get_trip_type(struct thermal_zone_device *tzd, int trip,
+ enum thermal_trip_type *type)
+{
+ struct pch_thermal_device *ptd = tzd->devdata;
+
+ if (ptd->crt_trip_id == trip)
+ *type = THERMAL_TRIP_CRITICAL;
+ else if (ptd->hot_trip_id == trip)
+ *type = THERMAL_TRIP_HOT;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int pch_get_trip_temp(struct thermal_zone_device *tzd, int trip, int *temp)
+{
+ struct pch_thermal_device *ptd = tzd->devdata;
+
+ if (ptd->crt_trip_id == trip)
+ *temp = ptd->crt_temp;
+ else if (ptd->hot_trip_id == trip)
+ *temp = ptd->hot_temp;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static struct thermal_zone_device_ops tzd_ops = {
+ .get_temp = pch_thermal_get_temp,
+ .get_trip_type = pch_get_trip_type,
+ .get_trip_temp = pch_get_trip_temp,
+};
+
+
+static int intel_pch_thermal_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct pch_thermal_device *ptd;
+ int err;
+ int nr_trips;
+ char *dev_name;
+
+ ptd = devm_kzalloc(&pdev->dev, sizeof(*ptd), GFP_KERNEL);
+ if (!ptd)
+ return -ENOMEM;
+
+ switch (pdev->device) {
+ case PCH_THERMAL_DID_WPT:
+ ptd->ops = &pch_dev_ops_wpt;
+ dev_name = "pch_wildcat_point";
+ break;
+ default:
+ dev_err(&pdev->dev, "unknown pch thermal device\n");
+ return -ENODEV;
+ }
+
+ pci_set_drvdata(pdev, ptd);
+ ptd->pdev = pdev;
+
+ err = pci_enable_device(pdev);
+ if (err) {
+ dev_err(&pdev->dev, "failed to enable pci device\n");
+ return err;
+ }
+
+ err = pci_request_regions(pdev, driver_name);
+ if (err) {
+ dev_err(&pdev->dev, "failed to request pci region\n");
+ goto error_disable;
+ }
+
+ ptd->hw_base = pci_ioremap_bar(pdev, 0);
+ if (!ptd->hw_base) {
+ err = -ENOMEM;
+ dev_err(&pdev->dev, "failed to map mem base\n");
+ goto error_release;
+ }
+
+ err = ptd->ops->hw_init(ptd, &nr_trips);
+ if (err)
+ goto error_cleanup;
+
+ ptd->tzd = thermal_zone_device_register(dev_name, nr_trips, 0, ptd,
+ &tzd_ops, NULL, 0, 0);
+ if (IS_ERR(ptd->tzd)) {
+ dev_err(&pdev->dev, "Failed to register thermal zone %s\n",
+ dev_name);
+ err = PTR_ERR(ptd->tzd);
+ goto error_cleanup;
+ }
+
+ return 0;
+
+error_cleanup:
+ iounmap(ptd->hw_base);
+error_release:
+ pci_release_regions(pdev);
+error_disable:
+ pci_disable_device(pdev);
+ dev_err(&pdev->dev, "pci device failed to probe\n");
+ return err;
+}
+
+static void intel_pch_thermal_remove(struct pci_dev *pdev)
+{
+ struct pch_thermal_device *ptd = pci_get_drvdata(pdev);
+
+ thermal_zone_device_unregister(ptd->tzd);
+ iounmap(ptd->hw_base);
+ pci_set_drvdata(pdev, NULL);
+ pci_release_region(pdev, 0);
+ pci_disable_device(pdev);
+}
+
+static struct pci_device_id intel_pch_thermal_id[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WPT) },
+ { 0, },
+};
+MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id);
+
+static struct pci_driver intel_pch_thermal_driver = {
+ .name = "intel_pch_thermal",
+ .id_table = intel_pch_thermal_id,
+ .probe = intel_pch_thermal_probe,
+ .remove = intel_pch_thermal_remove,
+};
+
+module_pci_driver(intel_pch_thermal_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel PCH Thermal driver");
diff --git a/kernel/drivers/thermal/intel_powerclamp.c b/kernel/drivers/thermal/intel_powerclamp.c
index 725718e97..6c7958825 100644
--- a/kernel/drivers/thermal/intel_powerclamp.c
+++ b/kernel/drivers/thermal/intel_powerclamp.c
@@ -119,7 +119,7 @@ exit:
return ret;
}
-static struct kernel_param_ops duration_ops = {
+static const struct kernel_param_ops duration_ops = {
.set = duration_set,
.get = param_get_int,
};
@@ -167,7 +167,7 @@ exit_win:
return ret;
}
-static struct kernel_param_ops window_size_ops = {
+static const struct kernel_param_ops window_size_ops = {
.set = window_size_set,
.get = param_get_int,
};
@@ -340,7 +340,7 @@ static bool powerclamp_adjust_controls(unsigned int target_ratio,
/* check result for the last window */
msr_now = pkg_state_counter();
- rdtscll(tsc_now);
+ tsc_now = rdtsc();
/* calculate pkg cstate vs tsc ratio */
if (!msr_last || !tsc_last)
@@ -482,7 +482,7 @@ static void poll_pkg_cstate(struct work_struct *dummy)
u64 val64;
msr_now = pkg_state_counter();
- rdtscll(tsc_now);
+ tsc_now = rdtsc();
jiffies_now = jiffies;
/* calculate pkg cstate vs tsc ratio */
@@ -693,10 +693,14 @@ static const struct x86_cpu_id intel_powerclamp_ids[] __initconst = {
{ X86_VENDOR_INTEL, 6, 0x3f},
{ X86_VENDOR_INTEL, 6, 0x45},
{ X86_VENDOR_INTEL, 6, 0x46},
+ { X86_VENDOR_INTEL, 6, 0x47},
{ X86_VENDOR_INTEL, 6, 0x4c},
{ X86_VENDOR_INTEL, 6, 0x4d},
+ { X86_VENDOR_INTEL, 6, 0x4e},
{ X86_VENDOR_INTEL, 6, 0x4f},
{ X86_VENDOR_INTEL, 6, 0x56},
+ { X86_VENDOR_INTEL, 6, 0x57},
+ { X86_VENDOR_INTEL, 6, 0x5e},
{}
};
MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids);
diff --git a/kernel/drivers/thermal/intel_quark_dts_thermal.c b/kernel/drivers/thermal/intel_quark_dts_thermal.c
new file mode 100644
index 000000000..5ed90e6c8
--- /dev/null
+++ b/kernel/drivers/thermal/intel_quark_dts_thermal.c
@@ -0,0 +1,472 @@
+/*
+ * intel_quark_dts_thermal.c
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2015 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * Contact Information:
+ * Ong Boon Leong <boon.leong.ong@intel.com>
+ * Intel Malaysia, Penang
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2015 Intel Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Quark DTS thermal driver is implemented by referencing
+ * intel_soc_dts_thermal.c.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/thermal.h>
+#include <asm/cpu_device_id.h>
+#include <asm/iosf_mbi.h>
+
+#define X86_FAMILY_QUARK 0x5
+#define X86_MODEL_QUARK_X1000 0x9
+
+/* DTS reset is programmed via QRK_MBI_UNIT_SOC */
+#define QRK_DTS_REG_OFFSET_RESET 0x34
+#define QRK_DTS_RESET_BIT BIT(0)
+
+/* DTS enable is programmed via QRK_MBI_UNIT_RMU */
+#define QRK_DTS_REG_OFFSET_ENABLE 0xB0
+#define QRK_DTS_ENABLE_BIT BIT(15)
+
+/* Temperature Register is read via QRK_MBI_UNIT_RMU */
+#define QRK_DTS_REG_OFFSET_TEMP 0xB1
+#define QRK_DTS_MASK_TEMP 0xFF
+#define QRK_DTS_OFFSET_TEMP 0
+#define QRK_DTS_OFFSET_REL_TEMP 16
+#define QRK_DTS_TEMP_BASE 50
+
+/* Programmable Trip Point Register is configured via QRK_MBI_UNIT_RMU */
+#define QRK_DTS_REG_OFFSET_PTPS 0xB2
+#define QRK_DTS_MASK_TP_THRES 0xFF
+#define QRK_DTS_SHIFT_TP 8
+#define QRK_DTS_ID_TP_CRITICAL 0
+#define QRK_DTS_SAFE_TP_THRES 105
+
+/* Thermal Sensor Register Lock */
+#define QRK_DTS_REG_OFFSET_LOCK 0x71
+#define QRK_DTS_LOCK_BIT BIT(5)
+
+/* Quark DTS has 2 trip points: hot & catastrophic */
+#define QRK_MAX_DTS_TRIPS 2
+/* If DTS not locked, all trip points are configurable */
+#define QRK_DTS_WR_MASK_SET 0x3
+/* If DTS locked, all trip points are not configurable */
+#define QRK_DTS_WR_MASK_CLR 0
+
+#define DEFAULT_POLL_DELAY 2000
+
+struct soc_sensor_entry {
+ bool locked;
+ u32 store_ptps;
+ u32 store_dts_enable;
+ enum thermal_device_mode mode;
+ struct thermal_zone_device *tzone;
+};
+
+static struct soc_sensor_entry *soc_dts;
+
+static int polling_delay = DEFAULT_POLL_DELAY;
+module_param(polling_delay, int, 0644);
+MODULE_PARM_DESC(polling_delay,
+ "Polling interval for checking trip points (in milliseconds)");
+
+static DEFINE_MUTEX(dts_update_mutex);
+
+static int soc_dts_enable(struct thermal_zone_device *tzd)
+{
+ u32 out;
+ struct soc_sensor_entry *aux_entry = tzd->devdata;
+ int ret;
+
+ ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+ QRK_DTS_REG_OFFSET_ENABLE, &out);
+ if (ret)
+ return ret;
+
+ if (out & QRK_DTS_ENABLE_BIT) {
+ aux_entry->mode = THERMAL_DEVICE_ENABLED;
+ return 0;
+ }
+
+ if (!aux_entry->locked) {
+ out |= QRK_DTS_ENABLE_BIT;
+ ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_WRITE,
+ QRK_DTS_REG_OFFSET_ENABLE, out);
+ if (ret)
+ return ret;
+
+ aux_entry->mode = THERMAL_DEVICE_ENABLED;
+ } else {
+ aux_entry->mode = THERMAL_DEVICE_DISABLED;
+ pr_info("DTS is locked. Cannot enable DTS\n");
+ ret = -EPERM;
+ }
+
+ return ret;
+}
+
+static int soc_dts_disable(struct thermal_zone_device *tzd)
+{
+ u32 out;
+ struct soc_sensor_entry *aux_entry = tzd->devdata;
+ int ret;
+
+ ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+ QRK_DTS_REG_OFFSET_ENABLE, &out);
+ if (ret)
+ return ret;
+
+ if (!(out & QRK_DTS_ENABLE_BIT)) {
+ aux_entry->mode = THERMAL_DEVICE_DISABLED;
+ return 0;
+ }
+
+ if (!aux_entry->locked) {
+ out &= ~QRK_DTS_ENABLE_BIT;
+ ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_WRITE,
+ QRK_DTS_REG_OFFSET_ENABLE, out);
+
+ if (ret)
+ return ret;
+
+ aux_entry->mode = THERMAL_DEVICE_DISABLED;
+ } else {
+ aux_entry->mode = THERMAL_DEVICE_ENABLED;
+ pr_info("DTS is locked. Cannot disable DTS\n");
+ ret = -EPERM;
+ }
+
+ return ret;
+}
+
+static int _get_trip_temp(int trip, int *temp)
+{
+ int status;
+ u32 out;
+
+ mutex_lock(&dts_update_mutex);
+ status = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+ QRK_DTS_REG_OFFSET_PTPS, &out);
+ mutex_unlock(&dts_update_mutex);
+
+ if (status)
+ return status;
+
+ /*
+ * Thermal Sensor Programmable Trip Point Register has 8-bit
+ * fields for critical (catastrophic) and hot set trip point
+ * thresholds. The threshold value is always offset by its
+ * temperature base (50 degree Celsius).
+ */
+ *temp = (out >> (trip * QRK_DTS_SHIFT_TP)) & QRK_DTS_MASK_TP_THRES;
+ *temp -= QRK_DTS_TEMP_BASE;
+
+ return 0;
+}
+
+static inline int sys_get_trip_temp(struct thermal_zone_device *tzd,
+ int trip, int *temp)
+{
+ return _get_trip_temp(trip, temp);
+}
+
+static inline int sys_get_crit_temp(struct thermal_zone_device *tzd, int *temp)
+{
+ return _get_trip_temp(QRK_DTS_ID_TP_CRITICAL, temp);
+}
+
+static int update_trip_temp(struct soc_sensor_entry *aux_entry,
+ int trip, int temp)
+{
+ u32 out;
+ u32 temp_out;
+ u32 store_ptps;
+ int ret;
+
+ mutex_lock(&dts_update_mutex);
+ if (aux_entry->locked) {
+ ret = -EPERM;
+ goto failed;
+ }
+
+ ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+ QRK_DTS_REG_OFFSET_PTPS, &store_ptps);
+ if (ret)
+ goto failed;
+
+ /*
+ * Protection against unsafe trip point thresdhold value.
+ * As Quark X1000 data-sheet does not provide any recommendation
+ * regarding the safe trip point threshold value to use, we choose
+ * the safe value according to the threshold value set by UEFI BIOS.
+ */
+ if (temp > QRK_DTS_SAFE_TP_THRES)
+ temp = QRK_DTS_SAFE_TP_THRES;
+
+ /*
+ * Thermal Sensor Programmable Trip Point Register has 8-bit
+ * fields for critical (catastrophic) and hot set trip point
+ * thresholds. The threshold value is always offset by its
+ * temperature base (50 degree Celsius).
+ */
+ temp_out = temp + QRK_DTS_TEMP_BASE;
+ out = (store_ptps & ~(QRK_DTS_MASK_TP_THRES <<
+ (trip * QRK_DTS_SHIFT_TP)));
+ out |= (temp_out & QRK_DTS_MASK_TP_THRES) <<
+ (trip * QRK_DTS_SHIFT_TP);
+
+ ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_WRITE,
+ QRK_DTS_REG_OFFSET_PTPS, out);
+
+failed:
+ mutex_unlock(&dts_update_mutex);
+ return ret;
+}
+
+static inline int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip,
+ int temp)
+{
+ return update_trip_temp(tzd->devdata, trip, temp);
+}
+
+static int sys_get_trip_type(struct thermal_zone_device *thermal,
+ int trip, enum thermal_trip_type *type)
+{
+ if (trip)
+ *type = THERMAL_TRIP_HOT;
+ else
+ *type = THERMAL_TRIP_CRITICAL;
+
+ return 0;
+}
+
+static int sys_get_curr_temp(struct thermal_zone_device *tzd,
+ int *temp)
+{
+ u32 out;
+ int ret;
+
+ mutex_lock(&dts_update_mutex);
+ ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+ QRK_DTS_REG_OFFSET_TEMP, &out);
+ mutex_unlock(&dts_update_mutex);
+
+ if (ret)
+ return ret;
+
+ /*
+ * Thermal Sensor Temperature Register has 8-bit field
+ * for temperature value (offset by temperature base
+ * 50 degree Celsius).
+ */
+ out = (out >> QRK_DTS_OFFSET_TEMP) & QRK_DTS_MASK_TEMP;
+ *temp = out - QRK_DTS_TEMP_BASE;
+
+ return 0;
+}
+
+static int sys_get_mode(struct thermal_zone_device *tzd,
+ enum thermal_device_mode *mode)
+{
+ struct soc_sensor_entry *aux_entry = tzd->devdata;
+ *mode = aux_entry->mode;
+ return 0;
+}
+
+static int sys_set_mode(struct thermal_zone_device *tzd,
+ enum thermal_device_mode mode)
+{
+ int ret;
+
+ mutex_lock(&dts_update_mutex);
+ if (mode == THERMAL_DEVICE_ENABLED)
+ ret = soc_dts_enable(tzd);
+ else
+ ret = soc_dts_disable(tzd);
+ mutex_unlock(&dts_update_mutex);
+
+ return ret;
+}
+
+static struct thermal_zone_device_ops tzone_ops = {
+ .get_temp = sys_get_curr_temp,
+ .get_trip_temp = sys_get_trip_temp,
+ .get_trip_type = sys_get_trip_type,
+ .set_trip_temp = sys_set_trip_temp,
+ .get_crit_temp = sys_get_crit_temp,
+ .get_mode = sys_get_mode,
+ .set_mode = sys_set_mode,
+};
+
+static void free_soc_dts(struct soc_sensor_entry *aux_entry)
+{
+ if (aux_entry) {
+ if (!aux_entry->locked) {
+ mutex_lock(&dts_update_mutex);
+ iosf_mbi_write(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_WRITE,
+ QRK_DTS_REG_OFFSET_ENABLE,
+ aux_entry->store_dts_enable);
+
+ iosf_mbi_write(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_WRITE,
+ QRK_DTS_REG_OFFSET_PTPS,
+ aux_entry->store_ptps);
+ mutex_unlock(&dts_update_mutex);
+ }
+ thermal_zone_device_unregister(aux_entry->tzone);
+ kfree(aux_entry);
+ }
+}
+
+static struct soc_sensor_entry *alloc_soc_dts(void)
+{
+ struct soc_sensor_entry *aux_entry;
+ int err;
+ u32 out;
+ int wr_mask;
+
+ aux_entry = kzalloc(sizeof(*aux_entry), GFP_KERNEL);
+ if (!aux_entry) {
+ err = -ENOMEM;
+ return ERR_PTR(-ENOMEM);
+ }
+
+ /* Check if DTS register is locked */
+ err = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+ QRK_DTS_REG_OFFSET_LOCK,
+ &out);
+ if (err)
+ goto err_ret;
+
+ if (out & QRK_DTS_LOCK_BIT) {
+ aux_entry->locked = true;
+ wr_mask = QRK_DTS_WR_MASK_CLR;
+ } else {
+ aux_entry->locked = false;
+ wr_mask = QRK_DTS_WR_MASK_SET;
+ }
+
+ /* Store DTS default state if DTS registers are not locked */
+ if (!aux_entry->locked) {
+ /* Store DTS default enable for restore on exit */
+ err = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+ QRK_DTS_REG_OFFSET_ENABLE,
+ &aux_entry->store_dts_enable);
+ if (err)
+ goto err_ret;
+
+ /* Store DTS default PTPS register for restore on exit */
+ err = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+ QRK_DTS_REG_OFFSET_PTPS,
+ &aux_entry->store_ptps);
+ if (err)
+ goto err_ret;
+ }
+
+ aux_entry->tzone = thermal_zone_device_register("quark_dts",
+ QRK_MAX_DTS_TRIPS,
+ wr_mask,
+ aux_entry, &tzone_ops, NULL, 0, polling_delay);
+ if (IS_ERR(aux_entry->tzone)) {
+ err = PTR_ERR(aux_entry->tzone);
+ goto err_ret;
+ }
+
+ mutex_lock(&dts_update_mutex);
+ err = soc_dts_enable(aux_entry->tzone);
+ mutex_unlock(&dts_update_mutex);
+ if (err)
+ goto err_aux_status;
+
+ return aux_entry;
+
+err_aux_status:
+ thermal_zone_device_unregister(aux_entry->tzone);
+err_ret:
+ kfree(aux_entry);
+ return ERR_PTR(err);
+}
+
+static const struct x86_cpu_id qrk_thermal_ids[] __initconst = {
+ { X86_VENDOR_INTEL, X86_FAMILY_QUARK, X86_MODEL_QUARK_X1000 },
+ {}
+};
+MODULE_DEVICE_TABLE(x86cpu, qrk_thermal_ids);
+
+static int __init intel_quark_thermal_init(void)
+{
+ int err = 0;
+
+ if (!x86_match_cpu(qrk_thermal_ids) || !iosf_mbi_available())
+ return -ENODEV;
+
+ soc_dts = alloc_soc_dts();
+ if (IS_ERR(soc_dts)) {
+ err = PTR_ERR(soc_dts);
+ goto err_free;
+ }
+
+ return 0;
+
+err_free:
+ free_soc_dts(soc_dts);
+ return err;
+}
+
+static void __exit intel_quark_thermal_exit(void)
+{
+ free_soc_dts(soc_dts);
+}
+
+module_init(intel_quark_thermal_init)
+module_exit(intel_quark_thermal_exit)
+
+MODULE_DESCRIPTION("Intel Quark DTS Thermal Driver");
+MODULE_AUTHOR("Ong Boon Leong <boon.leong.ong@intel.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/kernel/drivers/thermal/intel_soc_dts_iosf.c b/kernel/drivers/thermal/intel_soc_dts_iosf.c
new file mode 100644
index 000000000..5841d1d72
--- /dev/null
+++ b/kernel/drivers/thermal/intel_soc_dts_iosf.c
@@ -0,0 +1,478 @@
+/*
+ * intel_soc_dts_iosf.c
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <asm/iosf_mbi.h>
+#include "intel_soc_dts_iosf.h"
+
+#define SOC_DTS_OFFSET_ENABLE 0xB0
+#define SOC_DTS_OFFSET_TEMP 0xB1
+
+#define SOC_DTS_OFFSET_PTPS 0xB2
+#define SOC_DTS_OFFSET_PTTS 0xB3
+#define SOC_DTS_OFFSET_PTTSS 0xB4
+#define SOC_DTS_OFFSET_PTMC 0x80
+#define SOC_DTS_TE_AUX0 0xB5
+#define SOC_DTS_TE_AUX1 0xB6
+
+#define SOC_DTS_AUX0_ENABLE_BIT BIT(0)
+#define SOC_DTS_AUX1_ENABLE_BIT BIT(1)
+#define SOC_DTS_CPU_MODULE0_ENABLE_BIT BIT(16)
+#define SOC_DTS_CPU_MODULE1_ENABLE_BIT BIT(17)
+#define SOC_DTS_TE_SCI_ENABLE BIT(9)
+#define SOC_DTS_TE_SMI_ENABLE BIT(10)
+#define SOC_DTS_TE_MSI_ENABLE BIT(11)
+#define SOC_DTS_TE_APICA_ENABLE BIT(14)
+#define SOC_DTS_PTMC_APIC_DEASSERT_BIT BIT(4)
+
+/* DTS encoding for TJ MAX temperature */
+#define SOC_DTS_TJMAX_ENCODING 0x7F
+
+/* Only 2 out of 4 is allowed for OSPM */
+#define SOC_MAX_DTS_TRIPS 2
+
+/* Mask for two trips in status bits */
+#define SOC_DTS_TRIP_MASK 0x03
+
+/* DTS0 and DTS 1 */
+#define SOC_MAX_DTS_SENSORS 2
+
+static int get_tj_max(u32 *tj_max)
+{
+ u32 eax, edx;
+ u32 val;
+ int err;
+
+ err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
+ if (err)
+ goto err_ret;
+ else {
+ val = (eax >> 16) & 0xff;
+ if (val)
+ *tj_max = val * 1000;
+ else {
+ err = -EINVAL;
+ goto err_ret;
+ }
+ }
+
+ return 0;
+err_ret:
+ *tj_max = 0;
+
+ return err;
+}
+
+static int sys_get_trip_temp(struct thermal_zone_device *tzd, int trip,
+ int *temp)
+{
+ int status;
+ u32 out;
+ struct intel_soc_dts_sensor_entry *dts;
+ struct intel_soc_dts_sensors *sensors;
+
+ dts = tzd->devdata;
+ sensors = dts->sensors;
+ mutex_lock(&sensors->dts_update_lock);
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+ SOC_DTS_OFFSET_PTPS, &out);
+ mutex_unlock(&sensors->dts_update_lock);
+ if (status)
+ return status;
+
+ out = (out >> (trip * 8)) & SOC_DTS_TJMAX_ENCODING;
+ if (!out)
+ *temp = 0;
+ else
+ *temp = sensors->tj_max - out * 1000;
+
+ return 0;
+}
+
+static int update_trip_temp(struct intel_soc_dts_sensor_entry *dts,
+ int thres_index, int temp,
+ enum thermal_trip_type trip_type)
+{
+ int status;
+ u32 temp_out;
+ u32 out;
+ u32 store_ptps;
+ u32 store_ptmc;
+ u32 store_te_out;
+ u32 te_out;
+ u32 int_enable_bit = SOC_DTS_TE_APICA_ENABLE;
+ struct intel_soc_dts_sensors *sensors = dts->sensors;
+
+ if (sensors->intr_type == INTEL_SOC_DTS_INTERRUPT_MSI)
+ int_enable_bit |= SOC_DTS_TE_MSI_ENABLE;
+
+ temp_out = (sensors->tj_max - temp) / 1000;
+
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+ SOC_DTS_OFFSET_PTPS, &store_ptps);
+ if (status)
+ return status;
+
+ out = (store_ptps & ~(0xFF << (thres_index * 8)));
+ out |= (temp_out & 0xFF) << (thres_index * 8);
+ status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+ SOC_DTS_OFFSET_PTPS, out);
+ if (status)
+ return status;
+
+ pr_debug("update_trip_temp PTPS = %x\n", out);
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+ SOC_DTS_OFFSET_PTMC, &out);
+ if (status)
+ goto err_restore_ptps;
+
+ store_ptmc = out;
+
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+ SOC_DTS_TE_AUX0 + thres_index,
+ &te_out);
+ if (status)
+ goto err_restore_ptmc;
+
+ store_te_out = te_out;
+ /* Enable for CPU module 0 and module 1 */
+ out |= (SOC_DTS_CPU_MODULE0_ENABLE_BIT |
+ SOC_DTS_CPU_MODULE1_ENABLE_BIT);
+ if (temp) {
+ if (thres_index)
+ out |= SOC_DTS_AUX1_ENABLE_BIT;
+ else
+ out |= SOC_DTS_AUX0_ENABLE_BIT;
+ te_out |= int_enable_bit;
+ } else {
+ if (thres_index)
+ out &= ~SOC_DTS_AUX1_ENABLE_BIT;
+ else
+ out &= ~SOC_DTS_AUX0_ENABLE_BIT;
+ te_out &= ~int_enable_bit;
+ }
+ status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+ SOC_DTS_OFFSET_PTMC, out);
+ if (status)
+ goto err_restore_te_out;
+
+ status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+ SOC_DTS_TE_AUX0 + thres_index,
+ te_out);
+ if (status)
+ goto err_restore_te_out;
+
+ dts->trip_types[thres_index] = trip_type;
+
+ return 0;
+err_restore_te_out:
+ iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+ SOC_DTS_OFFSET_PTMC, store_te_out);
+err_restore_ptmc:
+ iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+ SOC_DTS_OFFSET_PTMC, store_ptmc);
+err_restore_ptps:
+ iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+ SOC_DTS_OFFSET_PTPS, store_ptps);
+ /* Nothing we can do if restore fails */
+
+ return status;
+}
+
+static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip,
+ int temp)
+{
+ struct intel_soc_dts_sensor_entry *dts = tzd->devdata;
+ struct intel_soc_dts_sensors *sensors = dts->sensors;
+ int status;
+
+ if (temp > sensors->tj_max)
+ return -EINVAL;
+
+ mutex_lock(&sensors->dts_update_lock);
+ status = update_trip_temp(tzd->devdata, trip, temp,
+ dts->trip_types[trip]);
+ mutex_unlock(&sensors->dts_update_lock);
+
+ return status;
+}
+
+static int sys_get_trip_type(struct thermal_zone_device *tzd,
+ int trip, enum thermal_trip_type *type)
+{
+ struct intel_soc_dts_sensor_entry *dts;
+
+ dts = tzd->devdata;
+
+ *type = dts->trip_types[trip];
+
+ return 0;
+}
+
+static int sys_get_curr_temp(struct thermal_zone_device *tzd,
+ int *temp)
+{
+ int status;
+ u32 out;
+ struct intel_soc_dts_sensor_entry *dts;
+ struct intel_soc_dts_sensors *sensors;
+
+ dts = tzd->devdata;
+ sensors = dts->sensors;
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+ SOC_DTS_OFFSET_TEMP, &out);
+ if (status)
+ return status;
+
+ out = (out & dts->temp_mask) >> dts->temp_shift;
+ out -= SOC_DTS_TJMAX_ENCODING;
+ *temp = sensors->tj_max - out * 1000;
+
+ return 0;
+}
+
+static struct thermal_zone_device_ops tzone_ops = {
+ .get_temp = sys_get_curr_temp,
+ .get_trip_temp = sys_get_trip_temp,
+ .get_trip_type = sys_get_trip_type,
+ .set_trip_temp = sys_set_trip_temp,
+};
+
+static int soc_dts_enable(int id)
+{
+ u32 out;
+ int ret;
+
+ ret = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+ SOC_DTS_OFFSET_ENABLE, &out);
+ if (ret)
+ return ret;
+
+ if (!(out & BIT(id))) {
+ out |= BIT(id);
+ ret = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+ SOC_DTS_OFFSET_ENABLE, out);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+static void remove_dts_thermal_zone(struct intel_soc_dts_sensor_entry *dts)
+{
+ if (dts) {
+ iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+ SOC_DTS_OFFSET_ENABLE, dts->store_status);
+ thermal_zone_device_unregister(dts->tzone);
+ }
+}
+
+static int add_dts_thermal_zone(int id, struct intel_soc_dts_sensor_entry *dts,
+ bool notification_support, int trip_cnt,
+ int read_only_trip_cnt)
+{
+ char name[10];
+ int trip_count = 0;
+ int trip_mask = 0;
+ u32 store_ptps;
+ int ret;
+ int i;
+
+ /* Store status to restor on exit */
+ ret = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+ SOC_DTS_OFFSET_ENABLE,
+ &dts->store_status);
+ if (ret)
+ goto err_ret;
+
+ dts->id = id;
+ dts->temp_mask = 0x00FF << (id * 8);
+ dts->temp_shift = id * 8;
+ if (notification_support) {
+ trip_count = min(SOC_MAX_DTS_TRIPS, trip_cnt);
+ trip_mask = BIT(trip_count - read_only_trip_cnt) - 1;
+ }
+
+ /* Check if the writable trip we provide is not used by BIOS */
+ ret = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+ SOC_DTS_OFFSET_PTPS, &store_ptps);
+ if (ret)
+ trip_mask = 0;
+ else {
+ for (i = 0; i < trip_count; ++i) {
+ if (trip_mask & BIT(i))
+ if (store_ptps & (0xff << (i * 8)))
+ trip_mask &= ~BIT(i);
+ }
+ }
+ dts->trip_mask = trip_mask;
+ dts->trip_count = trip_count;
+ snprintf(name, sizeof(name), "soc_dts%d", id);
+ dts->tzone = thermal_zone_device_register(name,
+ trip_count,
+ trip_mask,
+ dts, &tzone_ops,
+ NULL, 0, 0);
+ if (IS_ERR(dts->tzone)) {
+ ret = PTR_ERR(dts->tzone);
+ goto err_ret;
+ }
+
+ ret = soc_dts_enable(id);
+ if (ret)
+ goto err_enable;
+
+ return 0;
+err_enable:
+ thermal_zone_device_unregister(dts->tzone);
+err_ret:
+ return ret;
+}
+
+int intel_soc_dts_iosf_add_read_only_critical_trip(
+ struct intel_soc_dts_sensors *sensors, int critical_offset)
+{
+ int i, j;
+
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+ for (j = 0; j < sensors->soc_dts[i].trip_count; ++j) {
+ if (!(sensors->soc_dts[i].trip_mask & BIT(j))) {
+ return update_trip_temp(&sensors->soc_dts[i], j,
+ sensors->tj_max - critical_offset,
+ THERMAL_TRIP_CRITICAL);
+ }
+ }
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_add_read_only_critical_trip);
+
+void intel_soc_dts_iosf_interrupt_handler(struct intel_soc_dts_sensors *sensors)
+{
+ u32 sticky_out;
+ int status;
+ u32 ptmc_out;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sensors->intr_notify_lock, flags);
+
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+ SOC_DTS_OFFSET_PTMC, &ptmc_out);
+ ptmc_out |= SOC_DTS_PTMC_APIC_DEASSERT_BIT;
+ status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+ SOC_DTS_OFFSET_PTMC, ptmc_out);
+
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+ SOC_DTS_OFFSET_PTTSS, &sticky_out);
+ pr_debug("status %d PTTSS %x\n", status, sticky_out);
+ if (sticky_out & SOC_DTS_TRIP_MASK) {
+ int i;
+ /* reset sticky bit */
+ status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+ SOC_DTS_OFFSET_PTTSS, sticky_out);
+ spin_unlock_irqrestore(&sensors->intr_notify_lock, flags);
+
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+ pr_debug("TZD update for zone %d\n", i);
+ thermal_zone_device_update(sensors->soc_dts[i].tzone);
+ }
+ } else
+ spin_unlock_irqrestore(&sensors->intr_notify_lock, flags);
+}
+EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_interrupt_handler);
+
+struct intel_soc_dts_sensors *intel_soc_dts_iosf_init(
+ enum intel_soc_dts_interrupt_type intr_type, int trip_count,
+ int read_only_trip_count)
+{
+ struct intel_soc_dts_sensors *sensors;
+ bool notification;
+ u32 tj_max;
+ int ret;
+ int i;
+
+ if (!iosf_mbi_available())
+ return ERR_PTR(-ENODEV);
+
+ if (!trip_count || read_only_trip_count > trip_count)
+ return ERR_PTR(-EINVAL);
+
+ if (get_tj_max(&tj_max))
+ return ERR_PTR(-EINVAL);
+
+ sensors = kzalloc(sizeof(*sensors), GFP_KERNEL);
+ if (!sensors)
+ return ERR_PTR(-ENOMEM);
+
+ spin_lock_init(&sensors->intr_notify_lock);
+ mutex_init(&sensors->dts_update_lock);
+ sensors->intr_type = intr_type;
+ sensors->tj_max = tj_max;
+ if (intr_type == INTEL_SOC_DTS_INTERRUPT_NONE)
+ notification = false;
+ else
+ notification = true;
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+ sensors->soc_dts[i].sensors = sensors;
+ ret = add_dts_thermal_zone(i, &sensors->soc_dts[i],
+ notification, trip_count,
+ read_only_trip_count);
+ if (ret)
+ goto err_free;
+ }
+
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+ ret = update_trip_temp(&sensors->soc_dts[i], 0, 0,
+ THERMAL_TRIP_PASSIVE);
+ if (ret)
+ goto err_remove_zone;
+
+ ret = update_trip_temp(&sensors->soc_dts[i], 1, 0,
+ THERMAL_TRIP_PASSIVE);
+ if (ret)
+ goto err_remove_zone;
+ }
+
+ return sensors;
+err_remove_zone:
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i)
+ remove_dts_thermal_zone(&sensors->soc_dts[i]);
+
+err_free:
+ kfree(sensors);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_init);
+
+void intel_soc_dts_iosf_exit(struct intel_soc_dts_sensors *sensors)
+{
+ int i;
+
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+ update_trip_temp(&sensors->soc_dts[i], 0, 0, 0);
+ update_trip_temp(&sensors->soc_dts[i], 1, 0, 0);
+ remove_dts_thermal_zone(&sensors->soc_dts[i]);
+ }
+ kfree(sensors);
+}
+EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_exit);
+
+MODULE_LICENSE("GPL v2");
diff --git a/kernel/drivers/thermal/intel_soc_dts_iosf.h b/kernel/drivers/thermal/intel_soc_dts_iosf.h
new file mode 100644
index 000000000..625e37bf9
--- /dev/null
+++ b/kernel/drivers/thermal/intel_soc_dts_iosf.h
@@ -0,0 +1,62 @@
+/*
+ * intel_soc_dts_iosf.h
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#ifndef _INTEL_SOC_DTS_IOSF_CORE_H
+#define _INTEL_SOC_DTS_IOSF_CORE_H
+
+#include <linux/thermal.h>
+
+/* DTS0 and DTS 1 */
+#define SOC_MAX_DTS_SENSORS 2
+
+enum intel_soc_dts_interrupt_type {
+ INTEL_SOC_DTS_INTERRUPT_NONE,
+ INTEL_SOC_DTS_INTERRUPT_APIC,
+ INTEL_SOC_DTS_INTERRUPT_MSI,
+ INTEL_SOC_DTS_INTERRUPT_SCI,
+ INTEL_SOC_DTS_INTERRUPT_SMI,
+};
+
+struct intel_soc_dts_sensors;
+
+struct intel_soc_dts_sensor_entry {
+ int id;
+ u32 temp_mask;
+ u32 temp_shift;
+ u32 store_status;
+ u32 trip_mask;
+ u32 trip_count;
+ enum thermal_trip_type trip_types[2];
+ struct thermal_zone_device *tzone;
+ struct intel_soc_dts_sensors *sensors;
+};
+
+struct intel_soc_dts_sensors {
+ u32 tj_max;
+ spinlock_t intr_notify_lock;
+ struct mutex dts_update_lock;
+ enum intel_soc_dts_interrupt_type intr_type;
+ struct intel_soc_dts_sensor_entry soc_dts[SOC_MAX_DTS_SENSORS];
+};
+
+struct intel_soc_dts_sensors *intel_soc_dts_iosf_init(
+ enum intel_soc_dts_interrupt_type intr_type, int trip_count,
+ int read_only_trip_count);
+void intel_soc_dts_iosf_exit(struct intel_soc_dts_sensors *sensors);
+void intel_soc_dts_iosf_interrupt_handler(
+ struct intel_soc_dts_sensors *sensors);
+int intel_soc_dts_iosf_add_read_only_critical_trip(
+ struct intel_soc_dts_sensors *sensors, int critical_offset);
+#endif
diff --git a/kernel/drivers/thermal/intel_soc_dts_thermal.c b/kernel/drivers/thermal/intel_soc_dts_thermal.c
index 9013505e4..4ebb31a35 100644
--- a/kernel/drivers/thermal/intel_soc_dts_thermal.c
+++ b/kernel/drivers/thermal/intel_soc_dts_thermal.c
@@ -16,431 +16,54 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
-#include <linux/slab.h>
#include <linux/interrupt.h>
-#include <linux/thermal.h>
#include <asm/cpu_device_id.h>
-#include <asm/iosf_mbi.h>
-
-#define SOC_DTS_OFFSET_ENABLE 0xB0
-#define SOC_DTS_OFFSET_TEMP 0xB1
-
-#define SOC_DTS_OFFSET_PTPS 0xB2
-#define SOC_DTS_OFFSET_PTTS 0xB3
-#define SOC_DTS_OFFSET_PTTSS 0xB4
-#define SOC_DTS_OFFSET_PTMC 0x80
-#define SOC_DTS_TE_AUX0 0xB5
-#define SOC_DTS_TE_AUX1 0xB6
-
-#define SOC_DTS_AUX0_ENABLE_BIT BIT(0)
-#define SOC_DTS_AUX1_ENABLE_BIT BIT(1)
-#define SOC_DTS_CPU_MODULE0_ENABLE_BIT BIT(16)
-#define SOC_DTS_CPU_MODULE1_ENABLE_BIT BIT(17)
-#define SOC_DTS_TE_SCI_ENABLE BIT(9)
-#define SOC_DTS_TE_SMI_ENABLE BIT(10)
-#define SOC_DTS_TE_MSI_ENABLE BIT(11)
-#define SOC_DTS_TE_APICA_ENABLE BIT(14)
-#define SOC_DTS_PTMC_APIC_DEASSERT_BIT BIT(4)
-
-/* DTS encoding for TJ MAX temperature */
-#define SOC_DTS_TJMAX_ENCODING 0x7F
-
-/* IRQ 86 is a fixed APIC interrupt for BYT DTS Aux threshold notifications */
-#define BYT_SOC_DTS_APIC_IRQ 86
-
-/* Only 2 out of 4 is allowed for OSPM */
-#define SOC_MAX_DTS_TRIPS 2
-
-/* Mask for two trips in status bits */
-#define SOC_DTS_TRIP_MASK 0x03
-
-/* DTS0 and DTS 1 */
-#define SOC_MAX_DTS_SENSORS 2
+#include "intel_soc_dts_iosf.h"
#define CRITICAL_OFFSET_FROM_TJ_MAX 5000
-struct soc_sensor_entry {
- int id;
- u32 tj_max;
- u32 temp_mask;
- u32 temp_shift;
- u32 store_status;
- struct thermal_zone_device *tzone;
-};
-
-static struct soc_sensor_entry *soc_dts[SOC_MAX_DTS_SENSORS];
-
static int crit_offset = CRITICAL_OFFSET_FROM_TJ_MAX;
module_param(crit_offset, int, 0644);
MODULE_PARM_DESC(crit_offset,
"Critical Temperature offset from tj max in millidegree Celsius.");
-static DEFINE_MUTEX(aux_update_mutex);
-static spinlock_t intr_notify_lock;
-static int soc_dts_thres_irq;
-
-static int get_tj_max(u32 *tj_max)
-{
- u32 eax, edx;
- u32 val;
- int err;
-
- err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
- if (err)
- goto err_ret;
- else {
- val = (eax >> 16) & 0xff;
- if (val)
- *tj_max = val * 1000;
- else {
- err = -EINVAL;
- goto err_ret;
- }
- }
-
- return 0;
-err_ret:
- *tj_max = 0;
-
- return err;
-}
-
-static int sys_get_trip_temp(struct thermal_zone_device *tzd,
- int trip, unsigned long *temp)
-{
- int status;
- u32 out;
- struct soc_sensor_entry *aux_entry;
-
- aux_entry = tzd->devdata;
-
- if (!trip) {
- /* Just return the critical temp */
- *temp = aux_entry->tj_max - crit_offset;
- return 0;
- }
-
- mutex_lock(&aux_update_mutex);
- status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
- SOC_DTS_OFFSET_PTPS, &out);
- mutex_unlock(&aux_update_mutex);
- if (status)
- return status;
-
- out = (out >> (trip * 8)) & SOC_DTS_TJMAX_ENCODING;
-
- if (!out)
- *temp = 0;
- else
- *temp = aux_entry->tj_max - out * 1000;
-
- return 0;
-}
-
-static int update_trip_temp(struct soc_sensor_entry *aux_entry,
- int thres_index, unsigned long temp)
-{
- int status;
- u32 temp_out;
- u32 out;
- u32 store_ptps;
- u32 store_ptmc;
- u32 store_te_out;
- u32 te_out;
-
- u32 int_enable_bit = SOC_DTS_TE_APICA_ENABLE |
- SOC_DTS_TE_MSI_ENABLE;
-
- temp_out = (aux_entry->tj_max - temp) / 1000;
-
- status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
- SOC_DTS_OFFSET_PTPS, &store_ptps);
- if (status)
- return status;
-
- out = (store_ptps & ~(0xFF << (thres_index * 8)));
- out |= (temp_out & 0xFF) << (thres_index * 8);
- status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
- SOC_DTS_OFFSET_PTPS, out);
- if (status)
- return status;
- pr_debug("update_trip_temp PTPS = %x\n", out);
- status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
- SOC_DTS_OFFSET_PTMC, &out);
- if (status)
- goto err_restore_ptps;
-
- store_ptmc = out;
-
- status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
- SOC_DTS_TE_AUX0 + thres_index,
- &te_out);
- if (status)
- goto err_restore_ptmc;
-
- store_te_out = te_out;
-
- /* Enable for CPU module 0 and module 1 */
- out |= (SOC_DTS_CPU_MODULE0_ENABLE_BIT |
- SOC_DTS_CPU_MODULE1_ENABLE_BIT);
- if (temp) {
- if (thres_index)
- out |= SOC_DTS_AUX1_ENABLE_BIT;
- else
- out |= SOC_DTS_AUX0_ENABLE_BIT;
- te_out |= int_enable_bit;
- } else {
- if (thres_index)
- out &= ~SOC_DTS_AUX1_ENABLE_BIT;
- else
- out &= ~SOC_DTS_AUX0_ENABLE_BIT;
- te_out &= ~int_enable_bit;
- }
- status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
- SOC_DTS_OFFSET_PTMC, out);
- if (status)
- goto err_restore_te_out;
-
- status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
- SOC_DTS_TE_AUX0 + thres_index,
- te_out);
- if (status)
- goto err_restore_te_out;
-
- return 0;
-
-err_restore_te_out:
- iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
- SOC_DTS_OFFSET_PTMC, store_te_out);
-err_restore_ptmc:
- iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
- SOC_DTS_OFFSET_PTMC, store_ptmc);
-err_restore_ptps:
- iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
- SOC_DTS_OFFSET_PTPS, store_ptps);
- /* Nothing we can do if restore fails */
-
- return status;
-}
-
-static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip,
- unsigned long temp)
-{
- struct soc_sensor_entry *aux_entry = tzd->devdata;
- int status;
-
- if (temp > (aux_entry->tj_max - crit_offset))
- return -EINVAL;
-
- mutex_lock(&aux_update_mutex);
- status = update_trip_temp(tzd->devdata, trip, temp);
- mutex_unlock(&aux_update_mutex);
-
- return status;
-}
-
-static int sys_get_trip_type(struct thermal_zone_device *thermal,
- int trip, enum thermal_trip_type *type)
-{
- if (trip)
- *type = THERMAL_TRIP_PASSIVE;
- else
- *type = THERMAL_TRIP_CRITICAL;
-
- return 0;
-}
-
-static int sys_get_curr_temp(struct thermal_zone_device *tzd,
- unsigned long *temp)
-{
- int status;
- u32 out;
- struct soc_sensor_entry *aux_entry;
-
- aux_entry = tzd->devdata;
-
- status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
- SOC_DTS_OFFSET_TEMP, &out);
- if (status)
- return status;
-
- out = (out & aux_entry->temp_mask) >> aux_entry->temp_shift;
- out -= SOC_DTS_TJMAX_ENCODING;
- *temp = aux_entry->tj_max - out * 1000;
-
- return 0;
-}
-
-static struct thermal_zone_device_ops tzone_ops = {
- .get_temp = sys_get_curr_temp,
- .get_trip_temp = sys_get_trip_temp,
- .get_trip_type = sys_get_trip_type,
- .set_trip_temp = sys_set_trip_temp,
-};
-
-static void free_soc_dts(struct soc_sensor_entry *aux_entry)
-{
- if (aux_entry) {
- iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
- SOC_DTS_OFFSET_ENABLE, aux_entry->store_status);
- thermal_zone_device_unregister(aux_entry->tzone);
- kfree(aux_entry);
- }
-}
-
-static int soc_dts_enable(int id)
-{
- u32 out;
- int ret;
-
- ret = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
- SOC_DTS_OFFSET_ENABLE, &out);
- if (ret)
- return ret;
-
- if (!(out & BIT(id))) {
- out |= BIT(id);
- ret = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
- SOC_DTS_OFFSET_ENABLE, out);
- if (ret)
- return ret;
- }
-
- return ret;
-}
-
-static struct soc_sensor_entry *alloc_soc_dts(int id, u32 tj_max,
- bool notification_support)
-{
- struct soc_sensor_entry *aux_entry;
- char name[10];
- int trip_count = 0;
- int trip_mask = 0;
- int err;
-
- aux_entry = kzalloc(sizeof(*aux_entry), GFP_KERNEL);
- if (!aux_entry) {
- err = -ENOMEM;
- return ERR_PTR(-ENOMEM);
- }
-
- /* Store status to restor on exit */
- err = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
- SOC_DTS_OFFSET_ENABLE,
- &aux_entry->store_status);
- if (err)
- goto err_ret;
-
- aux_entry->id = id;
- aux_entry->tj_max = tj_max;
- aux_entry->temp_mask = 0x00FF << (id * 8);
- aux_entry->temp_shift = id * 8;
- if (notification_support) {
- trip_count = SOC_MAX_DTS_TRIPS;
- trip_mask = 0x02;
- }
- snprintf(name, sizeof(name), "soc_dts%d", id);
- aux_entry->tzone = thermal_zone_device_register(name,
- trip_count,
- trip_mask,
- aux_entry, &tzone_ops,
- NULL, 0, 0);
- if (IS_ERR(aux_entry->tzone)) {
- err = PTR_ERR(aux_entry->tzone);
- goto err_ret;
- }
-
- err = soc_dts_enable(id);
- if (err)
- goto err_aux_status;
-
- return aux_entry;
-
-err_aux_status:
- thermal_zone_device_unregister(aux_entry->tzone);
-err_ret:
- kfree(aux_entry);
- return ERR_PTR(err);
-}
-
-static void proc_thermal_interrupt(void)
-{
- u32 sticky_out;
- int status;
- u32 ptmc_out;
- unsigned long flags;
-
- spin_lock_irqsave(&intr_notify_lock, flags);
-
- /* Clear APIC interrupt */
- status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
- SOC_DTS_OFFSET_PTMC, &ptmc_out);
-
- ptmc_out |= SOC_DTS_PTMC_APIC_DEASSERT_BIT;
- status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
- SOC_DTS_OFFSET_PTMC, ptmc_out);
-
- /* Read status here */
- status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
- SOC_DTS_OFFSET_PTTSS, &sticky_out);
- pr_debug("status %d PTTSS %x\n", status, sticky_out);
- if (sticky_out & SOC_DTS_TRIP_MASK) {
- int i;
- /* reset sticky bit */
- status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
- SOC_DTS_OFFSET_PTTSS, sticky_out);
- spin_unlock_irqrestore(&intr_notify_lock, flags);
-
- for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
- pr_debug("TZD update for zone %d\n", i);
- thermal_zone_device_update(soc_dts[i]->tzone);
- }
- } else
- spin_unlock_irqrestore(&intr_notify_lock, flags);
+/* IRQ 86 is a fixed APIC interrupt for BYT DTS Aux threshold notifications */
+#define BYT_SOC_DTS_APIC_IRQ 86
-}
+static int soc_dts_thres_irq;
+static struct intel_soc_dts_sensors *soc_dts;
static irqreturn_t soc_irq_thread_fn(int irq, void *dev_data)
{
- proc_thermal_interrupt();
pr_debug("proc_thermal_interrupt\n");
+ intel_soc_dts_iosf_interrupt_handler(soc_dts);
return IRQ_HANDLED;
}
static const struct x86_cpu_id soc_thermal_ids[] = {
{ X86_VENDOR_INTEL, X86_FAMILY_ANY, 0x37, 0, BYT_SOC_DTS_APIC_IRQ},
- { X86_VENDOR_INTEL, X86_FAMILY_ANY, 0x4c, 0, 0},
{}
};
MODULE_DEVICE_TABLE(x86cpu, soc_thermal_ids);
static int __init intel_soc_thermal_init(void)
{
- u32 tj_max;
int err = 0;
- int i;
const struct x86_cpu_id *match_cpu;
match_cpu = x86_match_cpu(soc_thermal_ids);
if (!match_cpu)
return -ENODEV;
- if (get_tj_max(&tj_max))
- return -EINVAL;
-
- soc_dts_thres_irq = (int)match_cpu->driver_data;
-
- for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
- soc_dts[i] = alloc_soc_dts(i, tj_max,
- soc_dts_thres_irq ? true : false);
- if (IS_ERR(soc_dts[i])) {
- err = PTR_ERR(soc_dts[i]);
- goto err_free;
- }
+ /* Create a zone with 2 trips with marked as read only */
+ soc_dts = intel_soc_dts_iosf_init(INTEL_SOC_DTS_INTERRUPT_APIC, 2, 1);
+ if (IS_ERR(soc_dts)) {
+ err = PTR_ERR(soc_dts);
+ return err;
}
- spin_lock_init(&intr_notify_lock);
+ soc_dts_thres_irq = (int)match_cpu->driver_data;
if (soc_dts_thres_irq) {
err = request_threaded_irq(soc_dts_thres_irq, NULL,
@@ -449,42 +72,31 @@ static int __init intel_soc_thermal_init(void)
"soc_dts", soc_dts);
if (err) {
pr_err("request_threaded_irq ret %d\n", err);
- goto err_free;
+ goto error_irq;
}
}
- for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
- err = update_trip_temp(soc_dts[i], 0, tj_max - crit_offset);
- if (err)
- goto err_trip_temp;
- }
+ err = intel_soc_dts_iosf_add_read_only_critical_trip(soc_dts,
+ crit_offset);
+ if (err)
+ goto error_trips;
return 0;
-err_trip_temp:
- i = SOC_MAX_DTS_SENSORS;
+error_trips:
if (soc_dts_thres_irq)
free_irq(soc_dts_thres_irq, soc_dts);
-err_free:
- while (--i >= 0)
- free_soc_dts(soc_dts[i]);
+error_irq:
+ intel_soc_dts_iosf_exit(soc_dts);
return err;
}
static void __exit intel_soc_thermal_exit(void)
{
- int i;
-
- for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i)
- update_trip_temp(soc_dts[i], 0, 0);
-
if (soc_dts_thres_irq)
free_irq(soc_dts_thres_irq, soc_dts);
-
- for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i)
- free_soc_dts(soc_dts[i]);
-
+ intel_soc_dts_iosf_exit(soc_dts);
}
module_init(intel_soc_thermal_init)
diff --git a/kernel/drivers/thermal/kirkwood_thermal.c b/kernel/drivers/thermal/kirkwood_thermal.c
index 11041fe63..892236621 100644
--- a/kernel/drivers/thermal/kirkwood_thermal.c
+++ b/kernel/drivers/thermal/kirkwood_thermal.c
@@ -33,7 +33,7 @@ struct kirkwood_thermal_priv {
};
static int kirkwood_get_temp(struct thermal_zone_device *thermal,
- unsigned long *temp)
+ int *temp)
{
unsigned long reg;
struct kirkwood_thermal_priv *priv = thermal->devdata;
diff --git a/kernel/drivers/thermal/of-thermal.c b/kernel/drivers/thermal/of-thermal.c
index 668fb1bde..be4eedcb8 100644
--- a/kernel/drivers/thermal/of-thermal.c
+++ b/kernel/drivers/thermal/of-thermal.c
@@ -58,6 +58,8 @@ struct __thermal_bind_params {
* @mode: current thermal zone device mode (enabled/disabled)
* @passive_delay: polling interval while passive cooling is activated
* @polling_delay: zone polling interval
+ * @slope: slope of the temperature adjustment curve
+ * @offset: offset of the temperature adjustment curve
* @ntrips: number of trip points
* @trips: an array of trip points (0..ntrips - 1)
* @num_tbps: number of thermal bind params
@@ -70,6 +72,8 @@ struct __thermal_zone {
enum thermal_device_mode mode;
int passive_delay;
int polling_delay;
+ int slope;
+ int offset;
/* trip data */
int ntrips;
@@ -87,7 +91,7 @@ struct __thermal_zone {
/*** DT thermal zone device callbacks ***/
static int of_thermal_get_temp(struct thermal_zone_device *tz,
- unsigned long *temp)
+ int *temp)
{
struct __thermal_zone *data = tz->devdata;
@@ -173,7 +177,7 @@ EXPORT_SYMBOL_GPL(of_thermal_get_trip_points);
* Return: zero on success, error code otherwise
*/
static int of_thermal_set_emul_temp(struct thermal_zone_device *tz,
- unsigned long temp)
+ int temp)
{
struct __thermal_zone *data = tz->devdata;
@@ -227,7 +231,8 @@ static int of_thermal_bind(struct thermal_zone_device *thermal,
ret = thermal_zone_bind_cooling_device(thermal,
tbp->trip_id, cdev,
tbp->max,
- tbp->min);
+ tbp->min,
+ tbp->usage);
if (ret)
return ret;
}
@@ -306,7 +311,7 @@ static int of_thermal_get_trip_type(struct thermal_zone_device *tz, int trip,
}
static int of_thermal_get_trip_temp(struct thermal_zone_device *tz, int trip,
- unsigned long *temp)
+ int *temp)
{
struct __thermal_zone *data = tz->devdata;
@@ -319,7 +324,7 @@ static int of_thermal_get_trip_temp(struct thermal_zone_device *tz, int trip,
}
static int of_thermal_set_trip_temp(struct thermal_zone_device *tz, int trip,
- unsigned long temp)
+ int temp)
{
struct __thermal_zone *data = tz->devdata;
@@ -333,7 +338,7 @@ static int of_thermal_set_trip_temp(struct thermal_zone_device *tz, int trip,
}
static int of_thermal_get_trip_hyst(struct thermal_zone_device *tz, int trip,
- unsigned long *hyst)
+ int *hyst)
{
struct __thermal_zone *data = tz->devdata;
@@ -346,7 +351,7 @@ static int of_thermal_get_trip_hyst(struct thermal_zone_device *tz, int trip,
}
static int of_thermal_set_trip_hyst(struct thermal_zone_device *tz, int trip,
- unsigned long hyst)
+ int hyst)
{
struct __thermal_zone *data = tz->devdata;
@@ -360,7 +365,7 @@ static int of_thermal_set_trip_hyst(struct thermal_zone_device *tz, int trip,
}
static int of_thermal_get_crit_temp(struct thermal_zone_device *tz,
- unsigned long *temp)
+ int *temp)
{
struct __thermal_zone *data = tz->devdata;
int i;
@@ -581,7 +586,7 @@ static int thermal_of_populate_bind_params(struct device_node *np,
u32 prop;
/* Default weight. Usage is optional */
- __tbp->usage = 0;
+ __tbp->usage = THERMAL_WEIGHT_DEFAULT;
ret = of_property_read_u32(np, "contribution", &prop);
if (ret == 0)
__tbp->usage = prop;
@@ -715,7 +720,7 @@ static int thermal_of_populate_trip(struct device_node *np,
* @np parameter and fills the read data into a __thermal_zone data structure
* and return this pointer.
*
- * TODO: Missing properties to parse: thermal-sensor-names and coefficients
+ * TODO: Missing properties to parse: thermal-sensor-names
*
* Return: On success returns a valid struct __thermal_zone,
* otherwise, it returns a corresponding ERR_PTR(). Caller must
@@ -727,7 +732,7 @@ thermal_of_build_thermal_zone(struct device_node *np)
struct device_node *child = NULL, *gchild;
struct __thermal_zone *tz;
int ret, i;
- u32 prop;
+ u32 prop, coef[2];
if (!np) {
pr_err("no thermal zone np\n");
@@ -752,6 +757,20 @@ thermal_of_build_thermal_zone(struct device_node *np)
}
tz->polling_delay = prop;
+ /*
+ * REVIST: for now, the thermal framework supports only
+ * one sensor per thermal zone. Thus, we are considering
+ * only the first two values as slope and offset.
+ */
+ ret = of_property_read_u32_array(np, "coefficients", coef, 2);
+ if (ret == 0) {
+ tz->slope = coef[0];
+ tz->offset = coef[1];
+ } else {
+ tz->slope = 1;
+ tz->offset = 0;
+ }
+
/* trips */
child = of_get_child_by_name(np, "trips");
@@ -865,6 +884,8 @@ int __init of_parse_thermal_zones(void)
for_each_child_of_node(np, child) {
struct thermal_zone_device *zone;
struct thermal_zone_params *tzp;
+ int i, mask = 0;
+ u32 prop;
/* Check whether child is enabled or not */
if (!of_device_is_available(child))
@@ -891,8 +912,18 @@ int __init of_parse_thermal_zones(void)
/* No hwmon because there might be hwmon drivers registering */
tzp->no_hwmon = true;
+ if (!of_property_read_u32(child, "sustainable-power", &prop))
+ tzp->sustainable_power = prop;
+
+ for (i = 0; i < tz->ntrips; i++)
+ mask |= 1 << i;
+
+ /* these two are left for temperature drivers to use */
+ tzp->slope = tz->slope;
+ tzp->offset = tz->offset;
+
zone = thermal_zone_device_register(child->name, tz->ntrips,
- 0, tz,
+ mask, tz,
ops, tzp,
tz->passive_delay,
tz->polling_delay);
@@ -933,7 +964,7 @@ void of_thermal_destroy_zones(void)
np = of_find_node_by_name(NULL, "thermal-zones");
if (!np) {
- pr_err("unable to find thermal zones\n");
+ pr_debug("unable to find thermal zones\n");
return;
}
diff --git a/kernel/drivers/thermal/power_allocator.c b/kernel/drivers/thermal/power_allocator.c
new file mode 100644
index 000000000..1246aa6fc
--- /dev/null
+++ b/kernel/drivers/thermal/power_allocator.c
@@ -0,0 +1,659 @@
+/*
+ * A power allocator to manage temperature
+ *
+ * Copyright (C) 2014 ARM Ltd.
+ *
+ * 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.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "Power allocator: " fmt
+
+#include <linux/rculist.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/thermal_power_allocator.h>
+
+#include "thermal_core.h"
+
+#define INVALID_TRIP -1
+
+#define FRAC_BITS 10
+#define int_to_frac(x) ((x) << FRAC_BITS)
+#define frac_to_int(x) ((x) >> FRAC_BITS)
+
+/**
+ * mul_frac() - multiply two fixed-point numbers
+ * @x: first multiplicand
+ * @y: second multiplicand
+ *
+ * Return: the result of multiplying two fixed-point numbers. The
+ * result is also a fixed-point number.
+ */
+static inline s64 mul_frac(s64 x, s64 y)
+{
+ return (x * y) >> FRAC_BITS;
+}
+
+/**
+ * div_frac() - divide two fixed-point numbers
+ * @x: the dividend
+ * @y: the divisor
+ *
+ * Return: the result of dividing two fixed-point numbers. The
+ * result is also a fixed-point number.
+ */
+static inline s64 div_frac(s64 x, s64 y)
+{
+ return div_s64(x << FRAC_BITS, y);
+}
+
+/**
+ * struct power_allocator_params - parameters for the power allocator governor
+ * @allocated_tzp: whether we have allocated tzp for this thermal zone and
+ * it needs to be freed on unbind
+ * @err_integral: accumulated error in the PID controller.
+ * @prev_err: error in the previous iteration of the PID controller.
+ * Used to calculate the derivative term.
+ * @trip_switch_on: first passive trip point of the thermal zone. The
+ * governor switches on when this trip point is crossed.
+ * If the thermal zone only has one passive trip point,
+ * @trip_switch_on should be INVALID_TRIP.
+ * @trip_max_desired_temperature: last passive trip point of the thermal
+ * zone. The temperature we are
+ * controlling for.
+ */
+struct power_allocator_params {
+ bool allocated_tzp;
+ s64 err_integral;
+ s32 prev_err;
+ int trip_switch_on;
+ int trip_max_desired_temperature;
+};
+
+/**
+ * estimate_sustainable_power() - Estimate the sustainable power of a thermal zone
+ * @tz: thermal zone we are operating in
+ *
+ * For thermal zones that don't provide a sustainable_power in their
+ * thermal_zone_params, estimate one. Calculate it using the minimum
+ * power of all the cooling devices as that gives a valid value that
+ * can give some degree of functionality. For optimal performance of
+ * this governor, provide a sustainable_power in the thermal zone's
+ * thermal_zone_params.
+ */
+static u32 estimate_sustainable_power(struct thermal_zone_device *tz)
+{
+ u32 sustainable_power = 0;
+ struct thermal_instance *instance;
+ struct power_allocator_params *params = tz->governor_data;
+
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ struct thermal_cooling_device *cdev = instance->cdev;
+ u32 min_power;
+
+ if (instance->trip != params->trip_max_desired_temperature)
+ continue;
+
+ if (power_actor_get_min_power(cdev, tz, &min_power))
+ continue;
+
+ sustainable_power += min_power;
+ }
+
+ return sustainable_power;
+}
+
+/**
+ * estimate_pid_constants() - Estimate the constants for the PID controller
+ * @tz: thermal zone for which to estimate the constants
+ * @sustainable_power: sustainable power for the thermal zone
+ * @trip_switch_on: trip point number for the switch on temperature
+ * @control_temp: target temperature for the power allocator governor
+ * @force: whether to force the update of the constants
+ *
+ * This function is used to update the estimation of the PID
+ * controller constants in struct thermal_zone_parameters.
+ * Sustainable power is provided in case it was estimated. The
+ * estimated sustainable_power should not be stored in the
+ * thermal_zone_parameters so it has to be passed explicitly to this
+ * function.
+ *
+ * If @force is not set, the values in the thermal zone's parameters
+ * are preserved if they are not zero. If @force is set, the values
+ * in thermal zone's parameters are overwritten.
+ */
+static void estimate_pid_constants(struct thermal_zone_device *tz,
+ u32 sustainable_power, int trip_switch_on,
+ int control_temp, bool force)
+{
+ int ret;
+ int switch_on_temp;
+ u32 temperature_threshold;
+
+ ret = tz->ops->get_trip_temp(tz, trip_switch_on, &switch_on_temp);
+ if (ret)
+ switch_on_temp = 0;
+
+ temperature_threshold = control_temp - switch_on_temp;
+ /*
+ * estimate_pid_constants() tries to find appropriate default
+ * values for thermal zones that don't provide them. If a
+ * system integrator has configured a thermal zone with two
+ * passive trip points at the same temperature, that person
+ * hasn't put any effort to set up the thermal zone properly
+ * so just give up.
+ */
+ if (!temperature_threshold)
+ return;
+
+ if (!tz->tzp->k_po || force)
+ tz->tzp->k_po = int_to_frac(sustainable_power) /
+ temperature_threshold;
+
+ if (!tz->tzp->k_pu || force)
+ tz->tzp->k_pu = int_to_frac(2 * sustainable_power) /
+ temperature_threshold;
+
+ if (!tz->tzp->k_i || force)
+ tz->tzp->k_i = int_to_frac(10) / 1000;
+ /*
+ * The default for k_d and integral_cutoff is 0, so we can
+ * leave them as they are.
+ */
+}
+
+/**
+ * pid_controller() - PID controller
+ * @tz: thermal zone we are operating in
+ * @control_temp: the target temperature in millicelsius
+ * @max_allocatable_power: maximum allocatable power for this thermal zone
+ *
+ * This PID controller increases the available power budget so that the
+ * temperature of the thermal zone gets as close as possible to
+ * @control_temp and limits the power if it exceeds it. k_po is the
+ * proportional term when we are overshooting, k_pu is the
+ * proportional term when we are undershooting. integral_cutoff is a
+ * threshold below which we stop accumulating the error. The
+ * accumulated error is only valid if the requested power will make
+ * the system warmer. If the system is mostly idle, there's no point
+ * in accumulating positive error.
+ *
+ * Return: The power budget for the next period.
+ */
+static u32 pid_controller(struct thermal_zone_device *tz,
+ int control_temp,
+ u32 max_allocatable_power)
+{
+ s64 p, i, d, power_range;
+ s32 err, max_power_frac;
+ u32 sustainable_power;
+ struct power_allocator_params *params = tz->governor_data;
+
+ max_power_frac = int_to_frac(max_allocatable_power);
+
+ if (tz->tzp->sustainable_power) {
+ sustainable_power = tz->tzp->sustainable_power;
+ } else {
+ sustainable_power = estimate_sustainable_power(tz);
+ estimate_pid_constants(tz, sustainable_power,
+ params->trip_switch_on, control_temp,
+ true);
+ }
+
+ err = control_temp - tz->temperature;
+ err = int_to_frac(err);
+
+ /* Calculate the proportional term */
+ p = mul_frac(err < 0 ? tz->tzp->k_po : tz->tzp->k_pu, err);
+
+ /*
+ * Calculate the integral term
+ *
+ * if the error is less than cut off allow integration (but
+ * the integral is limited to max power)
+ */
+ i = mul_frac(tz->tzp->k_i, params->err_integral);
+
+ if (err < int_to_frac(tz->tzp->integral_cutoff)) {
+ s64 i_next = i + mul_frac(tz->tzp->k_i, err);
+
+ if (abs(i_next) < max_power_frac) {
+ i = i_next;
+ params->err_integral += err;
+ }
+ }
+
+ /*
+ * Calculate the derivative term
+ *
+ * We do err - prev_err, so with a positive k_d, a decreasing
+ * error (i.e. driving closer to the line) results in less
+ * power being applied, slowing down the controller)
+ */
+ d = mul_frac(tz->tzp->k_d, err - params->prev_err);
+ d = div_frac(d, tz->passive_delay);
+ params->prev_err = err;
+
+ power_range = p + i + d;
+
+ /* feed-forward the known sustainable dissipatable power */
+ power_range = sustainable_power + frac_to_int(power_range);
+
+ power_range = clamp(power_range, (s64)0, (s64)max_allocatable_power);
+
+ trace_thermal_power_allocator_pid(tz, frac_to_int(err),
+ frac_to_int(params->err_integral),
+ frac_to_int(p), frac_to_int(i),
+ frac_to_int(d), power_range);
+
+ return power_range;
+}
+
+/**
+ * divvy_up_power() - divvy the allocated power between the actors
+ * @req_power: each actor's requested power
+ * @max_power: each actor's maximum available power
+ * @num_actors: size of the @req_power, @max_power and @granted_power's array
+ * @total_req_power: sum of @req_power
+ * @power_range: total allocated power
+ * @granted_power: output array: each actor's granted power
+ * @extra_actor_power: an appropriately sized array to be used in the
+ * function as temporary storage of the extra power given
+ * to the actors
+ *
+ * This function divides the total allocated power (@power_range)
+ * fairly between the actors. It first tries to give each actor a
+ * share of the @power_range according to how much power it requested
+ * compared to the rest of the actors. For example, if only one actor
+ * requests power, then it receives all the @power_range. If
+ * three actors each requests 1mW, each receives a third of the
+ * @power_range.
+ *
+ * If any actor received more than their maximum power, then that
+ * surplus is re-divvied among the actors based on how far they are
+ * from their respective maximums.
+ *
+ * Granted power for each actor is written to @granted_power, which
+ * should've been allocated by the calling function.
+ */
+static void divvy_up_power(u32 *req_power, u32 *max_power, int num_actors,
+ u32 total_req_power, u32 power_range,
+ u32 *granted_power, u32 *extra_actor_power)
+{
+ u32 extra_power, capped_extra_power;
+ int i;
+
+ /*
+ * Prevent division by 0 if none of the actors request power.
+ */
+ if (!total_req_power)
+ total_req_power = 1;
+
+ capped_extra_power = 0;
+ extra_power = 0;
+ for (i = 0; i < num_actors; i++) {
+ u64 req_range = req_power[i] * power_range;
+
+ granted_power[i] = DIV_ROUND_CLOSEST_ULL(req_range,
+ total_req_power);
+
+ if (granted_power[i] > max_power[i]) {
+ extra_power += granted_power[i] - max_power[i];
+ granted_power[i] = max_power[i];
+ }
+
+ extra_actor_power[i] = max_power[i] - granted_power[i];
+ capped_extra_power += extra_actor_power[i];
+ }
+
+ if (!extra_power)
+ return;
+
+ /*
+ * Re-divvy the reclaimed extra among actors based on
+ * how far they are from the max
+ */
+ extra_power = min(extra_power, capped_extra_power);
+ if (capped_extra_power > 0)
+ for (i = 0; i < num_actors; i++)
+ granted_power[i] += (extra_actor_power[i] *
+ extra_power) / capped_extra_power;
+}
+
+static int allocate_power(struct thermal_zone_device *tz,
+ int control_temp)
+{
+ struct thermal_instance *instance;
+ struct power_allocator_params *params = tz->governor_data;
+ u32 *req_power, *max_power, *granted_power, *extra_actor_power;
+ u32 *weighted_req_power;
+ u32 total_req_power, max_allocatable_power, total_weighted_req_power;
+ u32 total_granted_power, power_range;
+ int i, num_actors, total_weight, ret = 0;
+ int trip_max_desired_temperature = params->trip_max_desired_temperature;
+
+ mutex_lock(&tz->lock);
+
+ num_actors = 0;
+ total_weight = 0;
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ if ((instance->trip == trip_max_desired_temperature) &&
+ cdev_is_power_actor(instance->cdev)) {
+ num_actors++;
+ total_weight += instance->weight;
+ }
+ }
+
+ if (!num_actors) {
+ ret = -ENODEV;
+ goto unlock;
+ }
+
+ /*
+ * We need to allocate five arrays of the same size:
+ * req_power, max_power, granted_power, extra_actor_power and
+ * weighted_req_power. They are going to be needed until this
+ * function returns. Allocate them all in one go to simplify
+ * the allocation and deallocation logic.
+ */
+ BUILD_BUG_ON(sizeof(*req_power) != sizeof(*max_power));
+ BUILD_BUG_ON(sizeof(*req_power) != sizeof(*granted_power));
+ BUILD_BUG_ON(sizeof(*req_power) != sizeof(*extra_actor_power));
+ BUILD_BUG_ON(sizeof(*req_power) != sizeof(*weighted_req_power));
+ req_power = kcalloc(num_actors * 5, sizeof(*req_power), GFP_KERNEL);
+ if (!req_power) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ max_power = &req_power[num_actors];
+ granted_power = &req_power[2 * num_actors];
+ extra_actor_power = &req_power[3 * num_actors];
+ weighted_req_power = &req_power[4 * num_actors];
+
+ i = 0;
+ total_weighted_req_power = 0;
+ total_req_power = 0;
+ max_allocatable_power = 0;
+
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ int weight;
+ struct thermal_cooling_device *cdev = instance->cdev;
+
+ if (instance->trip != trip_max_desired_temperature)
+ continue;
+
+ if (!cdev_is_power_actor(cdev))
+ continue;
+
+ if (cdev->ops->get_requested_power(cdev, tz, &req_power[i]))
+ continue;
+
+ if (!total_weight)
+ weight = 1 << FRAC_BITS;
+ else
+ weight = instance->weight;
+
+ weighted_req_power[i] = frac_to_int(weight * req_power[i]);
+
+ if (power_actor_get_max_power(cdev, tz, &max_power[i]))
+ continue;
+
+ total_req_power += req_power[i];
+ max_allocatable_power += max_power[i];
+ total_weighted_req_power += weighted_req_power[i];
+
+ i++;
+ }
+
+ power_range = pid_controller(tz, control_temp, max_allocatable_power);
+
+ divvy_up_power(weighted_req_power, max_power, num_actors,
+ total_weighted_req_power, power_range, granted_power,
+ extra_actor_power);
+
+ total_granted_power = 0;
+ i = 0;
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ if (instance->trip != trip_max_desired_temperature)
+ continue;
+
+ if (!cdev_is_power_actor(instance->cdev))
+ continue;
+
+ power_actor_set_power(instance->cdev, instance,
+ granted_power[i]);
+ total_granted_power += granted_power[i];
+
+ i++;
+ }
+
+ trace_thermal_power_allocator(tz, req_power, total_req_power,
+ granted_power, total_granted_power,
+ num_actors, power_range,
+ max_allocatable_power, tz->temperature,
+ control_temp - tz->temperature);
+
+ kfree(req_power);
+unlock:
+ mutex_unlock(&tz->lock);
+
+ return ret;
+}
+
+/**
+ * get_governor_trips() - get the number of the two trip points that are key for this governor
+ * @tz: thermal zone to operate on
+ * @params: pointer to private data for this governor
+ *
+ * The power allocator governor works optimally with two trips points:
+ * a "switch on" trip point and a "maximum desired temperature". These
+ * are defined as the first and last passive trip points.
+ *
+ * If there is only one trip point, then that's considered to be the
+ * "maximum desired temperature" trip point and the governor is always
+ * on. If there are no passive or active trip points, then the
+ * governor won't do anything. In fact, its throttle function
+ * won't be called at all.
+ */
+static void get_governor_trips(struct thermal_zone_device *tz,
+ struct power_allocator_params *params)
+{
+ int i, last_active, last_passive;
+ bool found_first_passive;
+
+ found_first_passive = false;
+ last_active = INVALID_TRIP;
+ last_passive = INVALID_TRIP;
+
+ for (i = 0; i < tz->trips; i++) {
+ enum thermal_trip_type type;
+ int ret;
+
+ ret = tz->ops->get_trip_type(tz, i, &type);
+ if (ret) {
+ dev_warn(&tz->device,
+ "Failed to get trip point %d type: %d\n", i,
+ ret);
+ continue;
+ }
+
+ if (type == THERMAL_TRIP_PASSIVE) {
+ if (!found_first_passive) {
+ params->trip_switch_on = i;
+ found_first_passive = true;
+ } else {
+ last_passive = i;
+ }
+ } else if (type == THERMAL_TRIP_ACTIVE) {
+ last_active = i;
+ } else {
+ break;
+ }
+ }
+
+ if (last_passive != INVALID_TRIP) {
+ params->trip_max_desired_temperature = last_passive;
+ } else if (found_first_passive) {
+ params->trip_max_desired_temperature = params->trip_switch_on;
+ params->trip_switch_on = INVALID_TRIP;
+ } else {
+ params->trip_switch_on = INVALID_TRIP;
+ params->trip_max_desired_temperature = last_active;
+ }
+}
+
+static void reset_pid_controller(struct power_allocator_params *params)
+{
+ params->err_integral = 0;
+ params->prev_err = 0;
+}
+
+static void allow_maximum_power(struct thermal_zone_device *tz)
+{
+ struct thermal_instance *instance;
+ struct power_allocator_params *params = tz->governor_data;
+
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ if ((instance->trip != params->trip_max_desired_temperature) ||
+ (!cdev_is_power_actor(instance->cdev)))
+ continue;
+
+ instance->target = 0;
+ instance->cdev->updated = false;
+ thermal_cdev_update(instance->cdev);
+ }
+}
+
+/**
+ * power_allocator_bind() - bind the power_allocator governor to a thermal zone
+ * @tz: thermal zone to bind it to
+ *
+ * Initialize the PID controller parameters and bind it to the thermal
+ * zone.
+ *
+ * Return: 0 on success, or -ENOMEM if we ran out of memory.
+ */
+static int power_allocator_bind(struct thermal_zone_device *tz)
+{
+ int ret;
+ struct power_allocator_params *params;
+ int control_temp;
+
+ params = kzalloc(sizeof(*params), GFP_KERNEL);
+ if (!params)
+ return -ENOMEM;
+
+ if (!tz->tzp) {
+ tz->tzp = kzalloc(sizeof(*tz->tzp), GFP_KERNEL);
+ if (!tz->tzp) {
+ ret = -ENOMEM;
+ goto free_params;
+ }
+
+ params->allocated_tzp = true;
+ }
+
+ if (!tz->tzp->sustainable_power)
+ dev_warn(&tz->device, "power_allocator: sustainable_power will be estimated\n");
+
+ get_governor_trips(tz, params);
+
+ if (tz->trips > 0) {
+ ret = tz->ops->get_trip_temp(tz,
+ params->trip_max_desired_temperature,
+ &control_temp);
+ if (!ret)
+ estimate_pid_constants(tz, tz->tzp->sustainable_power,
+ params->trip_switch_on,
+ control_temp, false);
+ }
+
+ reset_pid_controller(params);
+
+ tz->governor_data = params;
+
+ return 0;
+
+free_params:
+ kfree(params);
+
+ return ret;
+}
+
+static void power_allocator_unbind(struct thermal_zone_device *tz)
+{
+ struct power_allocator_params *params = tz->governor_data;
+
+ dev_dbg(&tz->device, "Unbinding from thermal zone %d\n", tz->id);
+
+ if (params->allocated_tzp) {
+ kfree(tz->tzp);
+ tz->tzp = NULL;
+ }
+
+ kfree(tz->governor_data);
+ tz->governor_data = NULL;
+}
+
+static int power_allocator_throttle(struct thermal_zone_device *tz, int trip)
+{
+ int ret;
+ int switch_on_temp, control_temp;
+ struct power_allocator_params *params = tz->governor_data;
+
+ /*
+ * We get called for every trip point but we only need to do
+ * our calculations once
+ */
+ if (trip != params->trip_max_desired_temperature)
+ return 0;
+
+ ret = tz->ops->get_trip_temp(tz, params->trip_switch_on,
+ &switch_on_temp);
+ if (!ret && (tz->temperature < switch_on_temp)) {
+ tz->passive = 0;
+ reset_pid_controller(params);
+ allow_maximum_power(tz);
+ return 0;
+ }
+
+ tz->passive = 1;
+
+ ret = tz->ops->get_trip_temp(tz, params->trip_max_desired_temperature,
+ &control_temp);
+ if (ret) {
+ dev_warn(&tz->device,
+ "Failed to get the maximum desired temperature: %d\n",
+ ret);
+ return ret;
+ }
+
+ return allocate_power(tz, control_temp);
+}
+
+static struct thermal_governor thermal_gov_power_allocator = {
+ .name = "power_allocator",
+ .bind_to_tz = power_allocator_bind,
+ .unbind_from_tz = power_allocator_unbind,
+ .throttle = power_allocator_throttle,
+};
+
+int thermal_gov_power_allocator_register(void)
+{
+ return thermal_register_governor(&thermal_gov_power_allocator);
+}
+
+void thermal_gov_power_allocator_unregister(void)
+{
+ thermal_unregister_governor(&thermal_gov_power_allocator);
+}
diff --git a/kernel/drivers/thermal/qcom-spmi-temp-alarm.c b/kernel/drivers/thermal/qcom-spmi-temp-alarm.c
new file mode 100644
index 000000000..b677aada5
--- /dev/null
+++ b/kernel/drivers/thermal/qcom-spmi-temp-alarm.c
@@ -0,0 +1,309 @@
+/*
+ * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/iio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+
+#define QPNP_TM_REG_TYPE 0x04
+#define QPNP_TM_REG_SUBTYPE 0x05
+#define QPNP_TM_REG_STATUS 0x08
+#define QPNP_TM_REG_SHUTDOWN_CTRL1 0x40
+#define QPNP_TM_REG_ALARM_CTRL 0x46
+
+#define QPNP_TM_TYPE 0x09
+#define QPNP_TM_SUBTYPE 0x08
+
+#define STATUS_STAGE_MASK 0x03
+
+#define SHUTDOWN_CTRL1_THRESHOLD_MASK 0x03
+
+#define ALARM_CTRL_FORCE_ENABLE 0x80
+
+/*
+ * Trip point values based on threshold control
+ * 0 = {105 C, 125 C, 145 C}
+ * 1 = {110 C, 130 C, 150 C}
+ * 2 = {115 C, 135 C, 155 C}
+ * 3 = {120 C, 140 C, 160 C}
+*/
+#define TEMP_STAGE_STEP 20000 /* Stage step: 20.000 C */
+#define TEMP_STAGE_HYSTERESIS 2000
+
+#define TEMP_THRESH_MIN 105000 /* Threshold Min: 105 C */
+#define TEMP_THRESH_STEP 5000 /* Threshold step: 5 C */
+
+#define THRESH_MIN 0
+
+/* Temperature in Milli Celsius reported during stage 0 if no ADC is present */
+#define DEFAULT_TEMP 37000
+
+struct qpnp_tm_chip {
+ struct regmap *map;
+ struct thermal_zone_device *tz_dev;
+ long temp;
+ unsigned int thresh;
+ unsigned int stage;
+ unsigned int prev_stage;
+ unsigned int base;
+ struct iio_channel *adc;
+};
+
+static int qpnp_tm_read(struct qpnp_tm_chip *chip, u16 addr, u8 *data)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(chip->map, chip->base + addr, &val);
+ if (ret < 0)
+ return ret;
+
+ *data = val;
+ return 0;
+}
+
+static int qpnp_tm_write(struct qpnp_tm_chip *chip, u16 addr, u8 data)
+{
+ return regmap_write(chip->map, chip->base + addr, data);
+}
+
+/*
+ * This function updates the internal temp value based on the
+ * current thermal stage and threshold as well as the previous stage
+ */
+static int qpnp_tm_update_temp_no_adc(struct qpnp_tm_chip *chip)
+{
+ unsigned int stage;
+ int ret;
+ u8 reg = 0;
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, &reg);
+ if (ret < 0)
+ return ret;
+
+ stage = reg & STATUS_STAGE_MASK;
+
+ if (stage > chip->stage) {
+ /* increasing stage, use lower bound */
+ chip->temp = (stage - 1) * TEMP_STAGE_STEP +
+ chip->thresh * TEMP_THRESH_STEP +
+ TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN;
+ } else if (stage < chip->stage) {
+ /* decreasing stage, use upper bound */
+ chip->temp = stage * TEMP_STAGE_STEP +
+ chip->thresh * TEMP_THRESH_STEP -
+ TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN;
+ }
+
+ chip->stage = stage;
+
+ return 0;
+}
+
+static int qpnp_tm_get_temp(void *data, int *temp)
+{
+ struct qpnp_tm_chip *chip = data;
+ int ret, mili_celsius;
+
+ if (!temp)
+ return -EINVAL;
+
+ if (IS_ERR(chip->adc)) {
+ ret = qpnp_tm_update_temp_no_adc(chip);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = iio_read_channel_processed(chip->adc, &mili_celsius);
+ if (ret < 0)
+ return ret;
+
+ chip->temp = mili_celsius;
+ }
+
+ *temp = chip->temp < 0 ? 0 : chip->temp;
+
+ return 0;
+}
+
+static const struct thermal_zone_of_device_ops qpnp_tm_sensor_ops = {
+ .get_temp = qpnp_tm_get_temp,
+};
+
+static irqreturn_t qpnp_tm_isr(int irq, void *data)
+{
+ struct qpnp_tm_chip *chip = data;
+
+ thermal_zone_device_update(chip->tz_dev);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * This function initializes the internal temp value based on only the
+ * current thermal stage and threshold. Setup threshold control and
+ * disable shutdown override.
+ */
+static int qpnp_tm_init(struct qpnp_tm_chip *chip)
+{
+ int ret;
+ u8 reg;
+
+ chip->thresh = THRESH_MIN;
+ chip->temp = DEFAULT_TEMP;
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, &reg);
+ if (ret < 0)
+ return ret;
+
+ chip->stage = reg & STATUS_STAGE_MASK;
+
+ if (chip->stage)
+ chip->temp = chip->thresh * TEMP_THRESH_STEP +
+ (chip->stage - 1) * TEMP_STAGE_STEP +
+ TEMP_THRESH_MIN;
+
+ /*
+ * Set threshold and disable software override of stage 2 and 3
+ * shutdowns.
+ */
+ reg = chip->thresh & SHUTDOWN_CTRL1_THRESHOLD_MASK;
+ ret = qpnp_tm_write(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, reg);
+ if (ret < 0)
+ return ret;
+
+ /* Enable the thermal alarm PMIC module in always-on mode. */
+ reg = ALARM_CTRL_FORCE_ENABLE;
+ ret = qpnp_tm_write(chip, QPNP_TM_REG_ALARM_CTRL, reg);
+
+ return ret;
+}
+
+static int qpnp_tm_probe(struct platform_device *pdev)
+{
+ struct qpnp_tm_chip *chip;
+ struct device_node *node;
+ u8 type, subtype;
+ u32 res[2];
+ int ret, irq;
+
+ node = pdev->dev.of_node;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ dev_set_drvdata(&pdev->dev, chip);
+
+ chip->map = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!chip->map)
+ return -ENXIO;
+
+ ret = of_property_read_u32_array(node, "reg", res, 2);
+ if (ret < 0)
+ return ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ /* ADC based measurements are optional */
+ chip->adc = iio_channel_get(&pdev->dev, "thermal");
+ if (PTR_ERR(chip->adc) == -EPROBE_DEFER)
+ return PTR_ERR(chip->adc);
+
+ chip->base = res[0];
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_TYPE, &type);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not read type\n");
+ goto fail;
+ }
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_SUBTYPE, &subtype);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not read subtype\n");
+ goto fail;
+ }
+
+ if (type != QPNP_TM_TYPE || subtype != QPNP_TM_SUBTYPE) {
+ dev_err(&pdev->dev, "invalid type 0x%02x or subtype 0x%02x\n",
+ type, subtype);
+ ret = -ENODEV;
+ goto fail;
+ }
+
+ ret = qpnp_tm_init(chip);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "init failed\n");
+ goto fail;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, qpnp_tm_isr,
+ IRQF_ONESHOT, node->name, chip);
+ if (ret < 0)
+ goto fail;
+
+ chip->tz_dev = thermal_zone_of_sensor_register(&pdev->dev, 0, chip,
+ &qpnp_tm_sensor_ops);
+ if (IS_ERR(chip->tz_dev)) {
+ dev_err(&pdev->dev, "failed to register sensor\n");
+ ret = PTR_ERR(chip->tz_dev);
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ if (!IS_ERR(chip->adc))
+ iio_channel_release(chip->adc);
+
+ return ret;
+}
+
+static int qpnp_tm_remove(struct platform_device *pdev)
+{
+ struct qpnp_tm_chip *chip = dev_get_drvdata(&pdev->dev);
+
+ thermal_zone_of_sensor_unregister(&pdev->dev, chip->tz_dev);
+ if (!IS_ERR(chip->adc))
+ iio_channel_release(chip->adc);
+
+ return 0;
+}
+
+static const struct of_device_id qpnp_tm_match_table[] = {
+ { .compatible = "qcom,spmi-temp-alarm" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, qpnp_tm_match_table);
+
+static struct platform_driver qpnp_tm_driver = {
+ .driver = {
+ .name = "spmi-temp-alarm",
+ .of_match_table = qpnp_tm_match_table,
+ },
+ .probe = qpnp_tm_probe,
+ .remove = qpnp_tm_remove,
+};
+module_platform_driver(qpnp_tm_driver);
+
+MODULE_ALIAS("platform:spmi-temp-alarm");
+MODULE_DESCRIPTION("QPNP PMIC Temperature Alarm driver");
+MODULE_LICENSE("GPL v2");
diff --git a/kernel/drivers/thermal/rcar_thermal.c b/kernel/drivers/thermal/rcar_thermal.c
index fe4e76701..13d01edc7 100644
--- a/kernel/drivers/thermal/rcar_thermal.c
+++ b/kernel/drivers/thermal/rcar_thermal.c
@@ -200,8 +200,7 @@ err_out_unlock:
return ret;
}
-static int rcar_thermal_get_temp(struct thermal_zone_device *zone,
- unsigned long *temp)
+static int rcar_thermal_get_temp(struct thermal_zone_device *zone, int *temp)
{
struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
@@ -235,7 +234,7 @@ static int rcar_thermal_get_trip_type(struct thermal_zone_device *zone,
}
static int rcar_thermal_get_trip_temp(struct thermal_zone_device *zone,
- int trip, unsigned long *temp)
+ int trip, int *temp)
{
struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
struct device *dev = rcar_priv_to_dev(priv);
@@ -299,7 +298,7 @@ static void _rcar_thermal_irq_ctrl(struct rcar_thermal_priv *priv, int enable)
static void rcar_thermal_work(struct work_struct *work)
{
struct rcar_thermal_priv *priv;
- unsigned long cctemp, nctemp;
+ int cctemp, nctemp;
priv = container_of(work, struct rcar_thermal_priv, work.work);
@@ -362,6 +361,24 @@ static irqreturn_t rcar_thermal_irq(int irq, void *data)
/*
* platform functions
*/
+static int rcar_thermal_remove(struct platform_device *pdev)
+{
+ struct rcar_thermal_common *common = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+ struct rcar_thermal_priv *priv;
+
+ rcar_thermal_for_each_priv(priv, common) {
+ if (rcar_has_irq_support(priv))
+ rcar_thermal_irq_disable(priv);
+ thermal_zone_device_unregister(priv->zone);
+ }
+
+ pm_runtime_put(dev);
+ pm_runtime_disable(dev);
+
+ return 0;
+}
+
static int rcar_thermal_probe(struct platform_device *pdev)
{
struct rcar_thermal_common *common;
@@ -378,6 +395,8 @@ static int rcar_thermal_probe(struct platform_device *pdev)
if (!common)
return -ENOMEM;
+ platform_set_drvdata(pdev, common);
+
INIT_LIST_HEAD(&common->head);
spin_lock_init(&common->lock);
common->dev = dev;
@@ -455,43 +474,16 @@ static int rcar_thermal_probe(struct platform_device *pdev)
rcar_thermal_common_write(common, ENR, enr_bits);
}
- platform_set_drvdata(pdev, common);
-
dev_info(dev, "%d sensor probed\n", i);
return 0;
error_unregister:
- rcar_thermal_for_each_priv(priv, common) {
- if (rcar_has_irq_support(priv))
- rcar_thermal_irq_disable(priv);
- thermal_zone_device_unregister(priv->zone);
- }
-
- pm_runtime_put(dev);
- pm_runtime_disable(dev);
+ rcar_thermal_remove(pdev);
return ret;
}
-static int rcar_thermal_remove(struct platform_device *pdev)
-{
- struct rcar_thermal_common *common = platform_get_drvdata(pdev);
- struct device *dev = &pdev->dev;
- struct rcar_thermal_priv *priv;
-
- rcar_thermal_for_each_priv(priv, common) {
- if (rcar_has_irq_support(priv))
- rcar_thermal_irq_disable(priv);
- thermal_zone_device_unregister(priv->zone);
- }
-
- pm_runtime_put(dev);
- pm_runtime_disable(dev);
-
- return 0;
-}
-
static const struct of_device_id rcar_thermal_dt_ids[] = {
{ .compatible = "renesas,rcar-thermal", },
{},
diff --git a/kernel/drivers/thermal/rockchip_thermal.c b/kernel/drivers/thermal/rockchip_thermal.c
index cd8f5f93b..e845841ab 100644
--- a/kernel/drivers/thermal/rockchip_thermal.c
+++ b/kernel/drivers/thermal/rockchip_thermal.c
@@ -1,6 +1,9 @@
/*
* Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd
*
+ * Copyright (c) 2015, Fuzhou Rockchip Electronics Co., Ltd
+ * Caesar Wang <wxt@rock-chips.com>
+ *
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
@@ -22,6 +25,7 @@
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/thermal.h>
+#include <linux/pinctrl/consumer.h>
/**
* If the temperature over a period of time High,
@@ -44,17 +48,50 @@ enum tshut_polarity {
};
/**
- * The system has three Temperature Sensors. channel 0 is reserved,
- * channel 1 is for CPU, and channel 2 is for GPU.
+ * The system has two Temperature Sensors.
+ * sensor0 is for CPU, and sensor1 is for GPU.
*/
enum sensor_id {
- SENSOR_CPU = 1,
+ SENSOR_CPU = 0,
SENSOR_GPU,
};
+/**
+* The conversion table has the adc value and temperature.
+* ADC_DECREMENT is the adc value decremnet.(e.g. v2_code_table)
+* ADC_INCREMNET is the adc value incremnet.(e.g. v3_code_table)
+*/
+enum adc_sort_mode {
+ ADC_DECREMENT = 0,
+ ADC_INCREMENT,
+};
+
+/**
+ * The max sensors is two in rockchip SoCs.
+ * Two sensors: CPU and GPU sensor.
+ */
+#define SOC_MAX_SENSORS 2
+
+struct chip_tsadc_table {
+ const struct tsadc_table *id;
+
+ /* the array table size*/
+ unsigned int length;
+
+ /* that analogic mask data */
+ u32 data_mask;
+
+ /* the sort mode is adc value that increment or decrement in table */
+ enum adc_sort_mode mode;
+};
+
struct rockchip_tsadc_chip {
+ /* The sensor id of chip correspond to the ADC channel */
+ int chn_id[SOC_MAX_SENSORS];
+ int chn_num;
+
/* The hardware-controlled tshut property */
- long tshut_temp;
+ int tshut_temp;
enum tshut_mode tshut_mode;
enum tshut_polarity tshut_polarity;
@@ -64,37 +101,40 @@ struct rockchip_tsadc_chip {
void (*control)(void __iomem *reg, bool on);
/* Per-sensor methods */
- int (*get_temp)(int chn, void __iomem *reg, long *temp);
- void (*set_tshut_temp)(int chn, void __iomem *reg, long temp);
+ int (*get_temp)(struct chip_tsadc_table table,
+ int chn, void __iomem *reg, int *temp);
+ void (*set_tshut_temp)(struct chip_tsadc_table table,
+ int chn, void __iomem *reg, int temp);
void (*set_tshut_mode)(int chn, void __iomem *reg, enum tshut_mode m);
+
+ /* Per-table methods */
+ struct chip_tsadc_table table;
};
struct rockchip_thermal_sensor {
struct rockchip_thermal_data *thermal;
struct thermal_zone_device *tzd;
- enum sensor_id id;
+ int id;
};
-#define NUM_SENSORS 2 /* Ignore unused sensor 0 */
-
struct rockchip_thermal_data {
const struct rockchip_tsadc_chip *chip;
struct platform_device *pdev;
struct reset_control *reset;
- struct rockchip_thermal_sensor sensors[NUM_SENSORS];
+ struct rockchip_thermal_sensor sensors[SOC_MAX_SENSORS];
struct clk *clk;
struct clk *pclk;
void __iomem *regs;
- long tshut_temp;
+ int tshut_temp;
enum tshut_mode tshut_mode;
enum tshut_polarity tshut_polarity;
};
-/* TSADC V2 Sensor info define: */
+/* TSADC Sensor info define: */
#define TSADCV2_AUTO_CON 0x04
#define TSADCV2_INT_EN 0x08
#define TSADCV2_INT_PD 0x0c
@@ -106,26 +146,26 @@ struct rockchip_thermal_data {
#define TSADCV2_AUTO_PERIOD_HT 0x6c
#define TSADCV2_AUTO_EN BIT(0)
-#define TSADCV2_AUTO_DISABLE ~BIT(0)
#define TSADCV2_AUTO_SRC_EN(chn) BIT(4 + (chn))
#define TSADCV2_AUTO_TSHUT_POLARITY_HIGH BIT(8)
-#define TSADCV2_AUTO_TSHUT_POLARITY_LOW ~BIT(8)
#define TSADCV2_INT_SRC_EN(chn) BIT(chn)
#define TSADCV2_SHUT_2GPIO_SRC_EN(chn) BIT(4 + (chn))
#define TSADCV2_SHUT_2CRU_SRC_EN(chn) BIT(8 + (chn))
-#define TSADCV2_INT_PD_CLEAR ~BIT(8)
+#define TSADCV2_INT_PD_CLEAR_MASK ~BIT(8)
#define TSADCV2_DATA_MASK 0xfff
+#define TSADCV3_DATA_MASK 0x3ff
+
#define TSADCV2_HIGHT_INT_DEBOUNCE_COUNT 4
#define TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT 4
#define TSADCV2_AUTO_PERIOD_TIME 250 /* msec */
#define TSADCV2_AUTO_PERIOD_HT_TIME 50 /* msec */
struct tsadc_table {
- unsigned long code;
- long temp;
+ u32 code;
+ int temp;
};
static const struct tsadc_table v2_code_table[] = {
@@ -164,24 +204,63 @@ static const struct tsadc_table v2_code_table[] = {
{3452, 115000},
{3437, 120000},
{3421, 125000},
- {0, 125000},
};
-static u32 rk_tsadcv2_temp_to_code(long temp)
+static const struct tsadc_table v3_code_table[] = {
+ {0, -40000},
+ {106, -40000},
+ {108, -35000},
+ {110, -30000},
+ {112, -25000},
+ {114, -20000},
+ {116, -15000},
+ {118, -10000},
+ {120, -5000},
+ {122, 0},
+ {124, 5000},
+ {126, 10000},
+ {128, 15000},
+ {130, 20000},
+ {132, 25000},
+ {134, 30000},
+ {136, 35000},
+ {138, 40000},
+ {140, 45000},
+ {142, 50000},
+ {144, 55000},
+ {146, 60000},
+ {148, 65000},
+ {150, 70000},
+ {152, 75000},
+ {154, 80000},
+ {156, 85000},
+ {158, 90000},
+ {160, 95000},
+ {162, 100000},
+ {163, 105000},
+ {165, 110000},
+ {167, 115000},
+ {169, 120000},
+ {171, 125000},
+ {TSADCV3_DATA_MASK, 125000},
+};
+
+static u32 rk_tsadcv2_temp_to_code(struct chip_tsadc_table table,
+ int temp)
{
int high, low, mid;
low = 0;
- high = ARRAY_SIZE(v2_code_table) - 1;
+ high = table.length - 1;
mid = (high + low) / 2;
- if (temp < v2_code_table[low].temp || temp > v2_code_table[high].temp)
+ if (temp < table.id[low].temp || temp > table.id[high].temp)
return 0;
while (low <= high) {
- if (temp == v2_code_table[mid].temp)
- return v2_code_table[mid].code;
- else if (temp < v2_code_table[mid].temp)
+ if (temp == table.id[mid].temp)
+ return table.id[mid].code;
+ else if (temp < table.id[mid].temp)
high = mid - 1;
else
low = mid + 1;
@@ -191,27 +270,54 @@ static u32 rk_tsadcv2_temp_to_code(long temp)
return 0;
}
-static long rk_tsadcv2_code_to_temp(u32 code)
+static int rk_tsadcv2_code_to_temp(struct chip_tsadc_table table, u32 code,
+ int *temp)
{
- unsigned int low = 0;
- unsigned int high = ARRAY_SIZE(v2_code_table) - 1;
+ unsigned int low = 1;
+ unsigned int high = table.length - 1;
unsigned int mid = (low + high) / 2;
unsigned int num;
unsigned long denom;
- /* Invalid code, return -EAGAIN */
- if (code > TSADCV2_DATA_MASK)
- return -EAGAIN;
-
- while (low <= high && mid) {
- if (code >= v2_code_table[mid].code &&
- code < v2_code_table[mid - 1].code)
- break;
- else if (code < v2_code_table[mid].code)
- low = mid + 1;
- else
- high = mid - 1;
- mid = (low + high) / 2;
+ WARN_ON(table.length < 2);
+
+ switch (table.mode) {
+ case ADC_DECREMENT:
+ code &= table.data_mask;
+ if (code < table.id[high].code)
+ return -EAGAIN; /* Incorrect reading */
+
+ while (low <= high) {
+ if (code >= table.id[mid].code &&
+ code < table.id[mid - 1].code)
+ break;
+ else if (code < table.id[mid].code)
+ low = mid + 1;
+ else
+ high = mid - 1;
+
+ mid = (low + high) / 2;
+ }
+ break;
+ case ADC_INCREMENT:
+ code &= table.data_mask;
+ if (code < table.id[low].code)
+ return -EAGAIN; /* Incorrect reading */
+
+ while (low <= high) {
+ if (code >= table.id[mid - 1].code &&
+ code < table.id[mid].code)
+ break;
+ else if (code > table.id[mid].code)
+ low = mid + 1;
+ else
+ high = mid - 1;
+
+ mid = (low + high) / 2;
+ }
+ break;
+ default:
+ pr_err("Invalid the conversion table\n");
}
/*
@@ -220,31 +326,37 @@ static long rk_tsadcv2_code_to_temp(u32 code)
* temperature between 2 table entries is linear and interpolate
* to produce less granular result.
*/
- num = v2_code_table[mid].temp - v2_code_table[mid - 1].temp;
- num *= v2_code_table[mid - 1].code - code;
- denom = v2_code_table[mid - 1].code - v2_code_table[mid].code;
- return v2_code_table[mid - 1].temp + (num / denom);
+ num = table.id[mid].temp - v2_code_table[mid - 1].temp;
+ num *= abs(table.id[mid - 1].code - code);
+ denom = abs(table.id[mid - 1].code - table.id[mid].code);
+ *temp = table.id[mid - 1].temp + (num / denom);
+
+ return 0;
}
/**
- * rk_tsadcv2_initialize - initialize TASDC Controller
- * (1) Set TSADCV2_AUTO_PERIOD, configure the interleave between
- * every two accessing of TSADC in normal operation.
- * (2) Set TSADCV2_AUTO_PERIOD_HT, configure the interleave between
- * every two accessing of TSADC after the temperature is higher
- * than COM_SHUT or COM_INT.
- * (3) Set TSADCV2_HIGH_INT_DEBOUNCE and TSADC_HIGHT_TSHUT_DEBOUNCE,
- * if the temperature is higher than COMP_INT or COMP_SHUT for
- * "debounce" times, TSADC controller will generate interrupt or TSHUT.
+ * rk_tsadcv2_initialize - initialize TASDC Controller.
+ *
+ * (1) Set TSADC_V2_AUTO_PERIOD:
+ * Configure the interleave between every two accessing of
+ * TSADC in normal operation.
+ *
+ * (2) Set TSADCV2_AUTO_PERIOD_HT:
+ * Configure the interleave between every two accessing of
+ * TSADC after the temperature is higher than COM_SHUT or COM_INT.
+ *
+ * (3) Set TSADCV2_HIGH_INT_DEBOUNCE and TSADC_HIGHT_TSHUT_DEBOUNCE:
+ * If the temperature is higher than COMP_INT or COMP_SHUT for
+ * "debounce" times, TSADC controller will generate interrupt or TSHUT.
*/
static void rk_tsadcv2_initialize(void __iomem *regs,
enum tshut_polarity tshut_polarity)
{
if (tshut_polarity == TSHUT_HIGH_ACTIVE)
- writel_relaxed(0 | (TSADCV2_AUTO_TSHUT_POLARITY_HIGH),
+ writel_relaxed(0U | TSADCV2_AUTO_TSHUT_POLARITY_HIGH,
regs + TSADCV2_AUTO_CON);
else
- writel_relaxed(0 | (TSADCV2_AUTO_TSHUT_POLARITY_LOW),
+ writel_relaxed(0U & ~TSADCV2_AUTO_TSHUT_POLARITY_HIGH,
regs + TSADCV2_AUTO_CON);
writel_relaxed(TSADCV2_AUTO_PERIOD_TIME, regs + TSADCV2_AUTO_PERIOD);
@@ -261,7 +373,7 @@ static void rk_tsadcv2_irq_ack(void __iomem *regs)
u32 val;
val = readl_relaxed(regs + TSADCV2_INT_PD);
- writel_relaxed(val & TSADCV2_INT_PD_CLEAR, regs + TSADCV2_INT_PD);
+ writel_relaxed(val & TSADCV2_INT_PD_CLEAR_MASK, regs + TSADCV2_INT_PD);
}
static void rk_tsadcv2_control(void __iomem *regs, bool enable)
@@ -277,25 +389,22 @@ static void rk_tsadcv2_control(void __iomem *regs, bool enable)
writel_relaxed(val, regs + TSADCV2_AUTO_CON);
}
-static int rk_tsadcv2_get_temp(int chn, void __iomem *regs, long *temp)
+static int rk_tsadcv2_get_temp(struct chip_tsadc_table table,
+ int chn, void __iomem *regs, int *temp)
{
u32 val;
- /* the A/D value of the channel last conversion need some time */
val = readl_relaxed(regs + TSADCV2_DATA(chn));
- if (val == 0)
- return -EAGAIN;
- *temp = rk_tsadcv2_code_to_temp(val);
-
- return 0;
+ return rk_tsadcv2_code_to_temp(table, val, temp);
}
-static void rk_tsadcv2_tshut_temp(int chn, void __iomem *regs, long temp)
+static void rk_tsadcv2_tshut_temp(struct chip_tsadc_table table,
+ int chn, void __iomem *regs, int temp)
{
u32 tshut_value, val;
- tshut_value = rk_tsadcv2_temp_to_code(temp);
+ tshut_value = rk_tsadcv2_temp_to_code(table, temp);
writel_relaxed(tshut_value, regs + TSADCV2_COMP_SHUT(chn));
/* TSHUT will be valid */
@@ -321,6 +430,10 @@ static void rk_tsadcv2_tshut_mode(int chn, void __iomem *regs,
}
static const struct rockchip_tsadc_chip rk3288_tsadc_data = {
+ .chn_id[SENSOR_CPU] = 1, /* cpu sensor is channel 1 */
+ .chn_id[SENSOR_GPU] = 2, /* gpu sensor is channel 2 */
+ .chn_num = 2, /* two channels for tsadc */
+
.tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */
.tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */
.tshut_temp = 95000,
@@ -331,6 +444,37 @@ static const struct rockchip_tsadc_chip rk3288_tsadc_data = {
.get_temp = rk_tsadcv2_get_temp,
.set_tshut_temp = rk_tsadcv2_tshut_temp,
.set_tshut_mode = rk_tsadcv2_tshut_mode,
+
+ .table = {
+ .id = v2_code_table,
+ .length = ARRAY_SIZE(v2_code_table),
+ .data_mask = TSADCV2_DATA_MASK,
+ .mode = ADC_DECREMENT,
+ },
+};
+
+static const struct rockchip_tsadc_chip rk3368_tsadc_data = {
+ .chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */
+ .chn_id[SENSOR_GPU] = 1, /* gpu sensor is channel 1 */
+ .chn_num = 2, /* two channels for tsadc */
+
+ .tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */
+ .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */
+ .tshut_temp = 95000,
+
+ .initialize = rk_tsadcv2_initialize,
+ .irq_ack = rk_tsadcv2_irq_ack,
+ .control = rk_tsadcv2_control,
+ .get_temp = rk_tsadcv2_get_temp,
+ .set_tshut_temp = rk_tsadcv2_tshut_temp,
+ .set_tshut_mode = rk_tsadcv2_tshut_mode,
+
+ .table = {
+ .id = v3_code_table,
+ .length = ARRAY_SIZE(v3_code_table),
+ .data_mask = TSADCV3_DATA_MASK,
+ .mode = ADC_INCREMENT,
+ },
};
static const struct of_device_id of_rockchip_thermal_match[] = {
@@ -338,6 +482,10 @@ static const struct of_device_id of_rockchip_thermal_match[] = {
.compatible = "rockchip,rk3288-tsadc",
.data = (void *)&rk3288_tsadc_data,
},
+ {
+ .compatible = "rockchip,rk3368-tsadc",
+ .data = (void *)&rk3368_tsadc_data,
+ },
{ /* end */ },
};
MODULE_DEVICE_TABLE(of, of_rockchip_thermal_match);
@@ -360,21 +508,22 @@ static irqreturn_t rockchip_thermal_alarm_irq_thread(int irq, void *dev)
thermal->chip->irq_ack(thermal->regs);
- for (i = 0; i < ARRAY_SIZE(thermal->sensors); i++)
+ for (i = 0; i < thermal->chip->chn_num; i++)
thermal_zone_device_update(thermal->sensors[i].tzd);
return IRQ_HANDLED;
}
-static int rockchip_thermal_get_temp(void *_sensor, long *out_temp)
+static int rockchip_thermal_get_temp(void *_sensor, int *out_temp)
{
struct rockchip_thermal_sensor *sensor = _sensor;
struct rockchip_thermal_data *thermal = sensor->thermal;
const struct rockchip_tsadc_chip *tsadc = sensor->thermal->chip;
int retval;
- retval = tsadc->get_temp(sensor->id, thermal->regs, out_temp);
- dev_dbg(&thermal->pdev->dev, "sensor %d - temp: %ld, retval: %d\n",
+ retval = tsadc->get_temp(tsadc->table,
+ sensor->id, thermal->regs, out_temp);
+ dev_dbg(&thermal->pdev->dev, "sensor %d - temp: %d, retval: %d\n",
sensor->id, *out_temp, retval);
return retval;
@@ -392,7 +541,7 @@ static int rockchip_configure_from_dt(struct device *dev,
if (of_property_read_u32(np, "rockchip,hw-tshut-temp", &shut_temp)) {
dev_warn(dev,
- "Missing tshut temp property, using default %ld\n",
+ "Missing tshut temp property, using default %d\n",
thermal->chip->tshut_temp);
thermal->tshut_temp = thermal->chip->tshut_temp;
} else {
@@ -400,7 +549,7 @@ static int rockchip_configure_from_dt(struct device *dev,
}
if (thermal->tshut_temp > INT_MAX) {
- dev_err(dev, "Invalid tshut temperature specified: %ld\n",
+ dev_err(dev, "Invalid tshut temperature specified: %d\n",
thermal->tshut_temp);
return -ERANGE;
}
@@ -445,13 +594,14 @@ static int
rockchip_thermal_register_sensor(struct platform_device *pdev,
struct rockchip_thermal_data *thermal,
struct rockchip_thermal_sensor *sensor,
- enum sensor_id id)
+ int id)
{
const struct rockchip_tsadc_chip *tsadc = thermal->chip;
int error;
tsadc->set_tshut_mode(id, thermal->regs, thermal->tshut_mode);
- tsadc->set_tshut_temp(id, thermal->regs, thermal->tshut_temp);
+ tsadc->set_tshut_temp(tsadc->table, id, thermal->regs,
+ thermal->tshut_temp);
sensor->thermal = thermal;
sensor->id = id;
@@ -484,7 +634,7 @@ static int rockchip_thermal_probe(struct platform_device *pdev)
const struct of_device_id *match;
struct resource *res;
int irq;
- int i;
+ int i, j;
int error;
match = of_match_node(of_rockchip_thermal_match, np);
@@ -559,22 +709,19 @@ static int rockchip_thermal_probe(struct platform_device *pdev)
thermal->chip->initialize(thermal->regs, thermal->tshut_polarity);
- error = rockchip_thermal_register_sensor(pdev, thermal,
- &thermal->sensors[0],
- SENSOR_CPU);
- if (error) {
- dev_err(&pdev->dev,
- "failed to register CPU thermal sensor: %d\n", error);
- goto err_disable_pclk;
- }
-
- error = rockchip_thermal_register_sensor(pdev, thermal,
- &thermal->sensors[1],
- SENSOR_GPU);
- if (error) {
- dev_err(&pdev->dev,
- "failed to register GPU thermal sensor: %d\n", error);
- goto err_unregister_cpu_sensor;
+ for (i = 0; i < thermal->chip->chn_num; i++) {
+ error = rockchip_thermal_register_sensor(pdev, thermal,
+ &thermal->sensors[i],
+ thermal->chip->chn_id[i]);
+ if (error) {
+ dev_err(&pdev->dev,
+ "failed to register sensor[%d] : error = %d\n",
+ i, error);
+ for (j = 0; j < i; j++)
+ thermal_zone_of_sensor_unregister(&pdev->dev,
+ thermal->sensors[j].tzd);
+ goto err_disable_pclk;
+ }
}
error = devm_request_threaded_irq(&pdev->dev, irq, NULL,
@@ -584,22 +731,23 @@ static int rockchip_thermal_probe(struct platform_device *pdev)
if (error) {
dev_err(&pdev->dev,
"failed to request tsadc irq: %d\n", error);
- goto err_unregister_gpu_sensor;
+ goto err_unregister_sensor;
}
thermal->chip->control(thermal->regs, true);
- for (i = 0; i < ARRAY_SIZE(thermal->sensors); i++)
+ for (i = 0; i < thermal->chip->chn_num; i++)
rockchip_thermal_toggle_sensor(&thermal->sensors[i], true);
platform_set_drvdata(pdev, thermal);
return 0;
-err_unregister_gpu_sensor:
- thermal_zone_of_sensor_unregister(&pdev->dev, thermal->sensors[1].tzd);
-err_unregister_cpu_sensor:
- thermal_zone_of_sensor_unregister(&pdev->dev, thermal->sensors[0].tzd);
+err_unregister_sensor:
+ while (i--)
+ thermal_zone_of_sensor_unregister(&pdev->dev,
+ thermal->sensors[i].tzd);
+
err_disable_pclk:
clk_disable_unprepare(thermal->pclk);
err_disable_clk:
@@ -613,7 +761,7 @@ static int rockchip_thermal_remove(struct platform_device *pdev)
struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev);
int i;
- for (i = 0; i < ARRAY_SIZE(thermal->sensors); i++) {
+ for (i = 0; i < thermal->chip->chn_num; i++) {
struct rockchip_thermal_sensor *sensor = &thermal->sensors[i];
rockchip_thermal_toggle_sensor(sensor, false);
@@ -634,7 +782,7 @@ static int __maybe_unused rockchip_thermal_suspend(struct device *dev)
struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev);
int i;
- for (i = 0; i < ARRAY_SIZE(thermal->sensors); i++)
+ for (i = 0; i < thermal->chip->chn_num; i++)
rockchip_thermal_toggle_sensor(&thermal->sensors[i], false);
thermal->chip->control(thermal->regs, false);
@@ -642,6 +790,8 @@ static int __maybe_unused rockchip_thermal_suspend(struct device *dev)
clk_disable(thermal->pclk);
clk_disable(thermal->clk);
+ pinctrl_pm_select_sleep_state(dev);
+
return 0;
}
@@ -664,20 +814,23 @@ static int __maybe_unused rockchip_thermal_resume(struct device *dev)
thermal->chip->initialize(thermal->regs, thermal->tshut_polarity);
- for (i = 0; i < ARRAY_SIZE(thermal->sensors); i++) {
- enum sensor_id id = thermal->sensors[i].id;
+ for (i = 0; i < thermal->chip->chn_num; i++) {
+ int id = thermal->sensors[i].id;
thermal->chip->set_tshut_mode(id, thermal->regs,
thermal->tshut_mode);
- thermal->chip->set_tshut_temp(id, thermal->regs,
+ thermal->chip->set_tshut_temp(thermal->chip->table,
+ id, thermal->regs,
thermal->tshut_temp);
}
thermal->chip->control(thermal->regs, true);
- for (i = 0; i < ARRAY_SIZE(thermal->sensors); i++)
+ for (i = 0; i < thermal->chip->chn_num; i++)
rockchip_thermal_toggle_sensor(&thermal->sensors[i], true);
+ pinctrl_pm_select_default_state(dev);
+
return 0;
}
diff --git a/kernel/drivers/thermal/samsung/Kconfig b/kernel/drivers/thermal/samsung/Kconfig
index c8e35c1a4..e0da3865e 100644
--- a/kernel/drivers/thermal/samsung/Kconfig
+++ b/kernel/drivers/thermal/samsung/Kconfig
@@ -1,6 +1,6 @@
config EXYNOS_THERMAL
tristate "Exynos thermal management unit driver"
- depends on OF
+ depends on THERMAL_OF
help
If you say yes here you get support for the TMU (Thermal Management
Unit) driver for SAMSUNG EXYNOS series of SoCs. This driver initialises
diff --git a/kernel/drivers/thermal/samsung/exynos_tmu.c b/kernel/drivers/thermal/samsung/exynos_tmu.c
index 67098a8a7..fa61eff88 100644
--- a/kernel/drivers/thermal/samsung/exynos_tmu.c
+++ b/kernel/drivers/thermal/samsung/exynos_tmu.c
@@ -97,6 +97,32 @@
#define EXYNOS4412_MUX_ADDR_VALUE 6
#define EXYNOS4412_MUX_ADDR_SHIFT 20
+/* Exynos5433 specific registers */
+#define EXYNOS5433_TMU_REG_CONTROL1 0x024
+#define EXYNOS5433_TMU_SAMPLING_INTERVAL 0x02c
+#define EXYNOS5433_TMU_COUNTER_VALUE0 0x030
+#define EXYNOS5433_TMU_COUNTER_VALUE1 0x034
+#define EXYNOS5433_TMU_REG_CURRENT_TEMP1 0x044
+#define EXYNOS5433_THD_TEMP_RISE3_0 0x050
+#define EXYNOS5433_THD_TEMP_RISE7_4 0x054
+#define EXYNOS5433_THD_TEMP_FALL3_0 0x060
+#define EXYNOS5433_THD_TEMP_FALL7_4 0x064
+#define EXYNOS5433_TMU_REG_INTEN 0x0c0
+#define EXYNOS5433_TMU_REG_INTPEND 0x0c8
+#define EXYNOS5433_TMU_EMUL_CON 0x110
+#define EXYNOS5433_TMU_PD_DET_EN 0x130
+
+#define EXYNOS5433_TRIMINFO_SENSOR_ID_SHIFT 16
+#define EXYNOS5433_TRIMINFO_CALIB_SEL_SHIFT 23
+#define EXYNOS5433_TRIMINFO_SENSOR_ID_MASK \
+ (0xf << EXYNOS5433_TRIMINFO_SENSOR_ID_SHIFT)
+#define EXYNOS5433_TRIMINFO_CALIB_SEL_MASK BIT(23)
+
+#define EXYNOS5433_TRIMINFO_ONE_POINT_TRIMMING 0
+#define EXYNOS5433_TRIMINFO_TWO_POINT_TRIMMING 1
+
+#define EXYNOS5433_PD_DET_EN 1
+
/*exynos5440 specific registers*/
#define EXYNOS5440_TMU_S0_7_TRIM 0x000
#define EXYNOS5440_TMU_S0_7_CTRL 0x020
@@ -181,8 +207,7 @@ struct exynos_tmu_data {
int (*tmu_initialize)(struct platform_device *pdev);
void (*tmu_control)(struct platform_device *pdev, bool on);
int (*tmu_read)(struct exynos_tmu_data *data);
- void (*tmu_set_emulation)(struct exynos_tmu_data *data,
- unsigned long temp);
+ void (*tmu_set_emulation)(struct exynos_tmu_data *data, int temp);
void (*tmu_clear_irqs)(struct exynos_tmu_data *data);
};
@@ -190,7 +215,7 @@ static void exynos_report_trigger(struct exynos_tmu_data *p)
{
char data[10], *envp[] = { data, NULL };
struct thermal_zone_device *tz = p->tzd;
- unsigned long temp;
+ int temp;
unsigned int i;
if (!tz) {
@@ -484,12 +509,107 @@ out:
return ret;
}
+static int exynos5433_tmu_initialize(struct platform_device *pdev)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+ struct exynos_tmu_platform_data *pdata = data->pdata;
+ struct thermal_zone_device *tz = data->tzd;
+ unsigned int status, trim_info;
+ unsigned int rising_threshold = 0, falling_threshold = 0;
+ int temp, temp_hist;
+ int ret = 0, threshold_code, i, sensor_id, cal_type;
+
+ status = readb(data->base + EXYNOS_TMU_REG_STATUS);
+ if (!status) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO);
+ sanitize_temp_error(data, trim_info);
+
+ /* Read the temperature sensor id */
+ sensor_id = (trim_info & EXYNOS5433_TRIMINFO_SENSOR_ID_MASK)
+ >> EXYNOS5433_TRIMINFO_SENSOR_ID_SHIFT;
+ dev_info(&pdev->dev, "Temperature sensor ID: 0x%x\n", sensor_id);
+
+ /* Read the calibration mode */
+ writel(trim_info, data->base + EXYNOS_TMU_REG_TRIMINFO);
+ cal_type = (trim_info & EXYNOS5433_TRIMINFO_CALIB_SEL_MASK)
+ >> EXYNOS5433_TRIMINFO_CALIB_SEL_SHIFT;
+
+ switch (cal_type) {
+ case EXYNOS5433_TRIMINFO_ONE_POINT_TRIMMING:
+ pdata->cal_type = TYPE_ONE_POINT_TRIMMING;
+ break;
+ case EXYNOS5433_TRIMINFO_TWO_POINT_TRIMMING:
+ pdata->cal_type = TYPE_TWO_POINT_TRIMMING;
+ break;
+ default:
+ pdata->cal_type = TYPE_ONE_POINT_TRIMMING;
+ break;
+ }
+
+ dev_info(&pdev->dev, "Calibration type is %d-point calibration\n",
+ cal_type ? 2 : 1);
+
+ /* Write temperature code for rising and falling threshold */
+ for (i = 0; i < of_thermal_get_ntrips(tz); i++) {
+ int rising_reg_offset, falling_reg_offset;
+ int j = 0;
+
+ switch (i) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ rising_reg_offset = EXYNOS5433_THD_TEMP_RISE3_0;
+ falling_reg_offset = EXYNOS5433_THD_TEMP_FALL3_0;
+ j = i;
+ break;
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ rising_reg_offset = EXYNOS5433_THD_TEMP_RISE7_4;
+ falling_reg_offset = EXYNOS5433_THD_TEMP_FALL7_4;
+ j = i - 4;
+ break;
+ default:
+ continue;
+ }
+
+ /* Write temperature code for rising threshold */
+ tz->ops->get_trip_temp(tz, i, &temp);
+ temp /= MCELSIUS;
+ threshold_code = temp_to_code(data, temp);
+
+ rising_threshold = readl(data->base + rising_reg_offset);
+ rising_threshold |= (threshold_code << j * 8);
+ writel(rising_threshold, data->base + rising_reg_offset);
+
+ /* Write temperature code for falling threshold */
+ tz->ops->get_trip_hyst(tz, i, &temp_hist);
+ temp_hist = temp - (temp_hist / MCELSIUS);
+ threshold_code = temp_to_code(data, temp_hist);
+
+ falling_threshold = readl(data->base + falling_reg_offset);
+ falling_threshold &= ~(0xff << j * 8);
+ falling_threshold |= (threshold_code << j * 8);
+ writel(falling_threshold, data->base + falling_reg_offset);
+ }
+
+ data->tmu_clear_irqs(data);
+out:
+ return ret;
+}
+
static int exynos5440_tmu_initialize(struct platform_device *pdev)
{
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
unsigned int trim_info = 0, con, rising_threshold;
- int ret = 0, threshold_code;
- unsigned long crit_temp = 0;
+ int threshold_code;
+ int crit_temp = 0;
/*
* For exynos5440 soc triminfo value is swapped between TMU0 and
@@ -531,7 +651,8 @@ static int exynos5440_tmu_initialize(struct platform_device *pdev)
/* Clear the PMIN in the common TMU register */
if (!data->id)
writel(0, data->base_second + EXYNOS5440_TMU_PMIN);
- return ret;
+
+ return 0;
}
static int exynos7_tmu_initialize(struct platform_device *pdev)
@@ -542,7 +663,7 @@ static int exynos7_tmu_initialize(struct platform_device *pdev)
unsigned int status, trim_info;
unsigned int rising_threshold = 0, falling_threshold = 0;
int ret = 0, threshold_code, i;
- unsigned long temp, temp_hist;
+ int temp, temp_hist;
unsigned int reg_off, bit_off;
status = readb(data->base + EXYNOS_TMU_REG_STATUS);
@@ -643,6 +764,48 @@ static void exynos4210_tmu_control(struct platform_device *pdev, bool on)
writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
}
+static void exynos5433_tmu_control(struct platform_device *pdev, bool on)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+ struct thermal_zone_device *tz = data->tzd;
+ unsigned int con, interrupt_en, pd_det_en;
+
+ con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL));
+
+ if (on) {
+ con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT);
+ interrupt_en =
+ (of_thermal_is_trip_valid(tz, 7)
+ << EXYNOS7_TMU_INTEN_RISE7_SHIFT) |
+ (of_thermal_is_trip_valid(tz, 6)
+ << EXYNOS7_TMU_INTEN_RISE6_SHIFT) |
+ (of_thermal_is_trip_valid(tz, 5)
+ << EXYNOS7_TMU_INTEN_RISE5_SHIFT) |
+ (of_thermal_is_trip_valid(tz, 4)
+ << EXYNOS7_TMU_INTEN_RISE4_SHIFT) |
+ (of_thermal_is_trip_valid(tz, 3)
+ << EXYNOS7_TMU_INTEN_RISE3_SHIFT) |
+ (of_thermal_is_trip_valid(tz, 2)
+ << EXYNOS7_TMU_INTEN_RISE2_SHIFT) |
+ (of_thermal_is_trip_valid(tz, 1)
+ << EXYNOS7_TMU_INTEN_RISE1_SHIFT) |
+ (of_thermal_is_trip_valid(tz, 0)
+ << EXYNOS7_TMU_INTEN_RISE0_SHIFT);
+
+ interrupt_en |=
+ interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT;
+ } else {
+ con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT);
+ interrupt_en = 0; /* Disable all interrupts */
+ }
+
+ pd_det_en = on ? EXYNOS5433_PD_DET_EN : 0;
+
+ writel(pd_det_en, data->base + EXYNOS5433_TMU_PD_DET_EN);
+ writel(interrupt_en, data->base + EXYNOS5433_TMU_REG_INTEN);
+ writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
+}
+
static void exynos5440_tmu_control(struct platform_device *pdev, bool on)
{
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
@@ -713,7 +876,7 @@ static void exynos7_tmu_control(struct platform_device *pdev, bool on)
writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
}
-static int exynos_get_temp(void *p, long *temp)
+static int exynos_get_temp(void *p, int *temp)
{
struct exynos_tmu_data *data = p;
@@ -733,7 +896,7 @@ static int exynos_get_temp(void *p, long *temp)
#ifdef CONFIG_THERMAL_EMULATION
static u32 get_emul_con_reg(struct exynos_tmu_data *data, unsigned int val,
- unsigned long temp)
+ int temp)
{
if (temp) {
temp /= MCELSIUS;
@@ -763,13 +926,15 @@ static u32 get_emul_con_reg(struct exynos_tmu_data *data, unsigned int val,
}
static void exynos4412_tmu_set_emulation(struct exynos_tmu_data *data,
- unsigned long temp)
+ int temp)
{
unsigned int val;
u32 emul_con;
if (data->soc == SOC_ARCH_EXYNOS5260)
emul_con = EXYNOS5260_EMUL_CON;
+ else if (data->soc == SOC_ARCH_EXYNOS5433)
+ emul_con = EXYNOS5433_TMU_EMUL_CON;
else if (data->soc == SOC_ARCH_EXYNOS7)
emul_con = EXYNOS7_TMU_REG_EMUL_CON;
else
@@ -781,7 +946,7 @@ static void exynos4412_tmu_set_emulation(struct exynos_tmu_data *data,
}
static void exynos5440_tmu_set_emulation(struct exynos_tmu_data *data,
- unsigned long temp)
+ int temp)
{
unsigned int val;
@@ -790,7 +955,7 @@ static void exynos5440_tmu_set_emulation(struct exynos_tmu_data *data,
writel(val, data->base + EXYNOS5440_TMU_S0_7_DEBUG);
}
-static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp)
+static int exynos_tmu_set_emulation(void *drv_data, int temp)
{
struct exynos_tmu_data *data = drv_data;
int ret = -EINVAL;
@@ -813,7 +978,7 @@ out:
#else
#define exynos4412_tmu_set_emulation NULL
#define exynos5440_tmu_set_emulation NULL
-static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp)
+static int exynos_tmu_set_emulation(void *drv_data, int temp)
{ return -EINVAL; }
#endif /* CONFIG_THERMAL_EMULATION */
@@ -882,6 +1047,9 @@ static void exynos4210_tmu_clear_irqs(struct exynos_tmu_data *data)
} else if (data->soc == SOC_ARCH_EXYNOS7) {
tmu_intstat = EXYNOS7_TMU_REG_INTPEND;
tmu_intclear = EXYNOS7_TMU_REG_INTPEND;
+ } else if (data->soc == SOC_ARCH_EXYNOS5433) {
+ tmu_intstat = EXYNOS5433_TMU_REG_INTPEND;
+ tmu_intclear = EXYNOS5433_TMU_REG_INTPEND;
} else {
tmu_intstat = EXYNOS_TMU_REG_INTSTAT;
tmu_intclear = EXYNOS_TMU_REG_INTCLEAR;
@@ -926,6 +1094,7 @@ static const struct of_device_id exynos_tmu_match[] = {
{ .compatible = "samsung,exynos5260-tmu", },
{ .compatible = "samsung,exynos5420-tmu", },
{ .compatible = "samsung,exynos5420-tmu-ext-triminfo", },
+ { .compatible = "samsung,exynos5433-tmu", },
{ .compatible = "samsung,exynos5440-tmu", },
{ .compatible = "samsung,exynos7-tmu", },
{ /* sentinel */ },
@@ -949,6 +1118,8 @@ static int exynos_of_get_soc_type(struct device_node *np)
else if (of_device_is_compatible(np,
"samsung,exynos5420-tmu-ext-triminfo"))
return SOC_ARCH_EXYNOS5420_TRIMINFO;
+ else if (of_device_is_compatible(np, "samsung,exynos5433-tmu"))
+ return SOC_ARCH_EXYNOS5433;
else if (of_device_is_compatible(np, "samsung,exynos5440-tmu"))
return SOC_ARCH_EXYNOS5440;
else if (of_device_is_compatible(np, "samsung,exynos7-tmu"))
@@ -998,27 +1169,10 @@ static int exynos_map_dt_data(struct platform_device *pdev)
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
struct exynos_tmu_platform_data *pdata;
struct resource res;
- int ret;
if (!data || !pdev->dev.of_node)
return -ENODEV;
- /*
- * Try enabling the regulator if found
- * TODO: Add regulator as an SOC feature, so that regulator enable
- * is a compulsory call.
- */
- data->regulator = devm_regulator_get(&pdev->dev, "vtmu");
- if (!IS_ERR(data->regulator)) {
- ret = regulator_enable(data->regulator);
- if (ret) {
- dev_err(&pdev->dev, "failed to enable vtmu\n");
- return ret;
- }
- } else {
- dev_info(&pdev->dev, "Regulator node (vtmu) not found\n");
- }
-
data->id = of_alias_get_id(pdev->dev.of_node, "tmuctrl");
if (data->id < 0)
data->id = 0;
@@ -1069,6 +1223,13 @@ static int exynos_map_dt_data(struct platform_device *pdev)
data->tmu_set_emulation = exynos4412_tmu_set_emulation;
data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
break;
+ case SOC_ARCH_EXYNOS5433:
+ data->tmu_initialize = exynos5433_tmu_initialize;
+ data->tmu_control = exynos5433_tmu_control;
+ data->tmu_read = exynos4412_tmu_read;
+ data->tmu_set_emulation = exynos4412_tmu_set_emulation;
+ data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
+ break;
case SOC_ARCH_EXYNOS5440:
data->tmu_initialize = exynos5440_tmu_initialize;
data->tmu_control = exynos5440_tmu_control;
@@ -1118,7 +1279,6 @@ static struct thermal_zone_of_device_ops exynos_sensor_ops = {
static int exynos_tmu_probe(struct platform_device *pdev)
{
- struct exynos_tmu_platform_data *pdata;
struct exynos_tmu_data *data;
int ret;
@@ -1130,18 +1290,26 @@ static int exynos_tmu_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, data);
mutex_init(&data->lock);
- data->tzd = thermal_zone_of_sensor_register(&pdev->dev, 0, data,
- &exynos_sensor_ops);
- if (IS_ERR(data->tzd)) {
- pr_err("thermal: tz: %p ERROR\n", data->tzd);
- return PTR_ERR(data->tzd);
+ /*
+ * Try enabling the regulator if found
+ * TODO: Add regulator as an SOC feature, so that regulator enable
+ * is a compulsory call.
+ */
+ data->regulator = devm_regulator_get(&pdev->dev, "vtmu");
+ if (!IS_ERR(data->regulator)) {
+ ret = regulator_enable(data->regulator);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable vtmu\n");
+ return ret;
+ }
+ } else {
+ dev_info(&pdev->dev, "Regulator node (vtmu) not found\n");
}
+
ret = exynos_map_dt_data(pdev);
if (ret)
goto err_sensor;
- pdata = data->pdata;
-
INIT_WORK(&data->irq_work, exynos_tmu_work);
data->clk = devm_clk_get(&pdev->dev, "tmu_apbif");
@@ -1172,7 +1340,9 @@ static int exynos_tmu_probe(struct platform_device *pdev)
goto err_clk_sec;
}
- if (data->soc == SOC_ARCH_EXYNOS7) {
+ switch (data->soc) {
+ case SOC_ARCH_EXYNOS5433:
+ case SOC_ARCH_EXYNOS7:
data->sclk = devm_clk_get(&pdev->dev, "tmu_sclk");
if (IS_ERR(data->sclk)) {
dev_err(&pdev->dev, "Failed to get sclk\n");
@@ -1184,23 +1354,41 @@ static int exynos_tmu_probe(struct platform_device *pdev)
goto err_clk;
}
}
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * data->tzd must be registered before calling exynos_tmu_initialize(),
+ * requesting irq and calling exynos_tmu_control().
+ */
+ data->tzd = thermal_zone_of_sensor_register(&pdev->dev, 0, data,
+ &exynos_sensor_ops);
+ if (IS_ERR(data->tzd)) {
+ ret = PTR_ERR(data->tzd);
+ dev_err(&pdev->dev, "Failed to register sensor: %d\n", ret);
+ goto err_sclk;
}
ret = exynos_tmu_initialize(pdev);
if (ret) {
dev_err(&pdev->dev, "Failed to initialize TMU\n");
- goto err_sclk;
+ goto err_thermal;
}
ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq,
IRQF_TRIGGER_RISING | IRQF_SHARED, dev_name(&pdev->dev), data);
if (ret) {
dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
- goto err_sclk;
+ goto err_thermal;
}
exynos_tmu_control(pdev, true);
return 0;
+
+err_thermal:
+ thermal_zone_of_sensor_unregister(&pdev->dev, data->tzd);
err_sclk:
clk_disable_unprepare(data->sclk);
err_clk:
@@ -1209,9 +1397,8 @@ err_clk_sec:
if (!IS_ERR(data->clk_sec))
clk_unprepare(data->clk_sec);
err_sensor:
- if (!IS_ERR_OR_NULL(data->regulator))
+ if (!IS_ERR(data->regulator))
regulator_disable(data->regulator);
- thermal_zone_of_sensor_unregister(&pdev->dev, data->tzd);
return ret;
}
diff --git a/kernel/drivers/thermal/samsung/exynos_tmu.h b/kernel/drivers/thermal/samsung/exynos_tmu.h
index 4d71ec6c9..440c7140b 100644
--- a/kernel/drivers/thermal/samsung/exynos_tmu.h
+++ b/kernel/drivers/thermal/samsung/exynos_tmu.h
@@ -33,6 +33,7 @@ enum soc_type {
SOC_ARCH_EXYNOS5260,
SOC_ARCH_EXYNOS5420,
SOC_ARCH_EXYNOS5420_TRIMINFO,
+ SOC_ARCH_EXYNOS5433,
SOC_ARCH_EXYNOS5440,
SOC_ARCH_EXYNOS7,
};
diff --git a/kernel/drivers/thermal/spear_thermal.c b/kernel/drivers/thermal/spear_thermal.c
index bddb71744..534dd9136 100644
--- a/kernel/drivers/thermal/spear_thermal.c
+++ b/kernel/drivers/thermal/spear_thermal.c
@@ -38,7 +38,7 @@ struct spear_thermal_dev {
};
static inline int thermal_get_temp(struct thermal_zone_device *thermal,
- unsigned long *temp)
+ int *temp)
{
struct spear_thermal_dev *stdev = thermal->devdata;
diff --git a/kernel/drivers/thermal/st/st_thermal.c b/kernel/drivers/thermal/st/st_thermal.c
index 76c515dd8..be637e6b0 100644
--- a/kernel/drivers/thermal/st/st_thermal.c
+++ b/kernel/drivers/thermal/st/st_thermal.c
@@ -111,8 +111,7 @@ static int st_thermal_calibration(struct st_thermal_sensor *sensor)
}
/* Callback to get temperature from HW*/
-static int st_thermal_get_temp(struct thermal_zone_device *th,
- unsigned long *temperature)
+static int st_thermal_get_temp(struct thermal_zone_device *th, int *temperature)
{
struct st_thermal_sensor *sensor = th->devdata;
struct device *dev = sensor->dev;
@@ -159,7 +158,7 @@ static int st_thermal_get_trip_type(struct thermal_zone_device *th,
}
static int st_thermal_get_trip_temp(struct thermal_zone_device *th,
- int trip, unsigned long *temp)
+ int trip, int *temp)
{
struct st_thermal_sensor *sensor = th->devdata;
struct device *dev = sensor->dev;
@@ -214,7 +213,7 @@ int st_thermal_register(struct platform_device *pdev,
sensor->ops = sensor->cdata->ops;
- ret = sensor->ops->regmap_init(sensor);
+ ret = (sensor->ops->regmap_init)(sensor);
if (ret)
return ret;
diff --git a/kernel/drivers/thermal/step_wise.c b/kernel/drivers/thermal/step_wise.c
index 5a0f12d08..ea9366ad3 100644
--- a/kernel/drivers/thermal/step_wise.c
+++ b/kernel/drivers/thermal/step_wise.c
@@ -63,6 +63,19 @@ static unsigned long get_target_state(struct thermal_instance *instance,
next_target = instance->target;
dev_dbg(&cdev->device, "cur_state=%ld\n", cur_state);
+ if (!instance->initialized) {
+ if (throttle) {
+ next_target = (cur_state + 1) >= instance->upper ?
+ instance->upper :
+ ((cur_state + 1) < instance->lower ?
+ instance->lower : (cur_state + 1));
+ } else {
+ next_target = THERMAL_NO_TARGET;
+ }
+
+ return next_target;
+ }
+
switch (trend) {
case THERMAL_TREND_RAISING:
if (throttle) {
@@ -113,7 +126,7 @@ static void update_passive_instance(struct thermal_zone_device *tz,
static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
{
- long trip_temp;
+ int trip_temp;
enum thermal_trip_type trip_type;
enum thermal_trend trend;
struct thermal_instance *instance;
@@ -135,7 +148,7 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
trace_thermal_zone_trip(tz, trip, trip_type);
}
- dev_dbg(&tz->device, "Trip%d[type=%d,temp=%ld]:trend=%d,throttle=%d\n",
+ dev_dbg(&tz->device, "Trip%d[type=%d,temp=%d]:trend=%d,throttle=%d\n",
trip, trip_type, trip_temp, trend, throttle);
mutex_lock(&tz->lock);
@@ -149,7 +162,7 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
dev_dbg(&instance->cdev->device, "old_target=%d, target=%d\n",
old_target, (int)instance->target);
- if (old_target == instance->target)
+ if (instance->initialized && old_target == instance->target)
continue;
/* Activate a passive thermal instance */
@@ -161,7 +174,7 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
instance->target == THERMAL_NO_TARGET)
update_passive_instance(tz, trip_type, -1);
-
+ instance->initialized = true;
instance->cdev->updated = false; /* cdev needs update */
}
diff --git a/kernel/drivers/thermal/tegra_soctherm.c b/kernel/drivers/thermal/tegra_soctherm.c
index 9197fc05c..74ea57659 100644
--- a/kernel/drivers/thermal/tegra_soctherm.c
+++ b/kernel/drivers/thermal/tegra_soctherm.c
@@ -293,7 +293,7 @@ static int enable_tsensor(struct tegra_soctherm *tegra,
* H denotes an addition of 0.5 Celsius and N denotes negation
* of the final value.
*/
-static long translate_temp(u16 val)
+static int translate_temp(u16 val)
{
long t;
@@ -306,7 +306,7 @@ static long translate_temp(u16 val)
return t;
}
-static int tegra_thermctl_get_temp(void *data, long *out_temp)
+static int tegra_thermctl_get_temp(void *data, int *out_temp)
{
struct tegra_thermctl_zone *zone = data;
u32 val;
diff --git a/kernel/drivers/thermal/thermal_core.c b/kernel/drivers/thermal/thermal_core.c
index 4108db7e1..ba08b5521 100644
--- a/kernel/drivers/thermal/thermal_core.c
+++ b/kernel/drivers/thermal/thermal_core.c
@@ -37,6 +37,7 @@
#include <linux/of.h>
#include <net/netlink.h>
#include <net/genetlink.h>
+#include <linux/suspend.h>
#define CREATE_TRACE_POINTS
#include <trace/events/thermal.h>
@@ -59,6 +60,8 @@ static LIST_HEAD(thermal_governor_list);
static DEFINE_MUTEX(thermal_list_lock);
static DEFINE_MUTEX(thermal_governor_lock);
+static atomic_t in_suspend;
+
static struct thermal_governor *def_governor;
static struct thermal_governor *__find_governor(const char *name)
@@ -75,6 +78,58 @@ static struct thermal_governor *__find_governor(const char *name)
return NULL;
}
+/**
+ * bind_previous_governor() - bind the previous governor of the thermal zone
+ * @tz: a valid pointer to a struct thermal_zone_device
+ * @failed_gov_name: the name of the governor that failed to register
+ *
+ * Register the previous governor of the thermal zone after a new
+ * governor has failed to be bound.
+ */
+static void bind_previous_governor(struct thermal_zone_device *tz,
+ const char *failed_gov_name)
+{
+ if (tz->governor && tz->governor->bind_to_tz) {
+ if (tz->governor->bind_to_tz(tz)) {
+ dev_err(&tz->device,
+ "governor %s failed to bind and the previous one (%s) failed to bind again, thermal zone %s has no governor\n",
+ failed_gov_name, tz->governor->name, tz->type);
+ tz->governor = NULL;
+ }
+ }
+}
+
+/**
+ * thermal_set_governor() - Switch to another governor
+ * @tz: a valid pointer to a struct thermal_zone_device
+ * @new_gov: pointer to the new governor
+ *
+ * Change the governor of thermal zone @tz.
+ *
+ * Return: 0 on success, an error if the new governor's bind_to_tz() failed.
+ */
+static int thermal_set_governor(struct thermal_zone_device *tz,
+ struct thermal_governor *new_gov)
+{
+ int ret = 0;
+
+ if (tz->governor && tz->governor->unbind_from_tz)
+ tz->governor->unbind_from_tz(tz);
+
+ if (new_gov && new_gov->bind_to_tz) {
+ ret = new_gov->bind_to_tz(tz);
+ if (ret) {
+ bind_previous_governor(tz, new_gov->name);
+
+ return ret;
+ }
+ }
+
+ tz->governor = new_gov;
+
+ return ret;
+}
+
int thermal_register_governor(struct thermal_governor *governor)
{
int err;
@@ -107,8 +162,15 @@ int thermal_register_governor(struct thermal_governor *governor)
name = pos->tzp->governor_name;
- if (!strncasecmp(name, governor->name, THERMAL_NAME_LENGTH))
- pos->governor = governor;
+ if (!strncasecmp(name, governor->name, THERMAL_NAME_LENGTH)) {
+ int ret;
+
+ ret = thermal_set_governor(pos, governor);
+ if (ret)
+ dev_err(&pos->device,
+ "Failed to set governor %s for thermal zone %s: %d\n",
+ governor->name, pos->type, ret);
+ }
}
mutex_unlock(&thermal_list_lock);
@@ -134,7 +196,7 @@ void thermal_unregister_governor(struct thermal_governor *governor)
list_for_each_entry(pos, &thermal_tz_list, node) {
if (!strncasecmp(pos->governor->name, governor->name,
THERMAL_NAME_LENGTH))
- pos->governor = NULL;
+ thermal_set_governor(pos, NULL);
}
mutex_unlock(&thermal_list_lock);
@@ -218,7 +280,8 @@ static void print_bind_err_msg(struct thermal_zone_device *tz,
static void __bind(struct thermal_zone_device *tz, int mask,
struct thermal_cooling_device *cdev,
- unsigned long *limits)
+ unsigned long *limits,
+ unsigned int weight)
{
int i, ret;
@@ -233,7 +296,8 @@ static void __bind(struct thermal_zone_device *tz, int mask,
upper = limits[i * 2 + 1];
}
ret = thermal_zone_bind_cooling_device(tz, i, cdev,
- upper, lower);
+ upper, lower,
+ weight);
if (ret)
print_bind_err_msg(tz, cdev, ret);
}
@@ -280,7 +344,8 @@ static void bind_cdev(struct thermal_cooling_device *cdev)
continue;
tzp->tbp[i].cdev = cdev;
__bind(pos, tzp->tbp[i].trip_mask, cdev,
- tzp->tbp[i].binding_limits);
+ tzp->tbp[i].binding_limits,
+ tzp->tbp[i].weight);
}
}
@@ -319,7 +384,8 @@ static void bind_tz(struct thermal_zone_device *tz)
continue;
tzp->tbp[i].cdev = pos;
__bind(tz, tzp->tbp[i].trip_mask, pos,
- tzp->tbp[i].binding_limits);
+ tzp->tbp[i].binding_limits,
+ tzp->tbp[i].weight);
}
}
exit:
@@ -363,7 +429,7 @@ static void handle_non_critical_trips(struct thermal_zone_device *tz,
static void handle_critical_trips(struct thermal_zone_device *tz,
int trip, enum thermal_trip_type trip_type)
{
- long trip_temp;
+ int trip_temp;
tz->ops->get_trip_temp(tz, trip, &trip_temp);
@@ -402,7 +468,7 @@ static void handle_thermal_trip(struct thermal_zone_device *tz, int trip)
}
/**
- * thermal_zone_get_temp() - returns its the temperature of thermal zone
+ * thermal_zone_get_temp() - returns the temperature of a thermal zone
* @tz: a valid pointer to a struct thermal_zone_device
* @temp: a valid pointer to where to store the resulting temperature.
*
@@ -411,14 +477,12 @@ static void handle_thermal_trip(struct thermal_zone_device *tz, int trip)
*
* Return: On success returns 0, an error code otherwise
*/
-int thermal_zone_get_temp(struct thermal_zone_device *tz, unsigned long *temp)
+int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
{
int ret = -EINVAL;
-#ifdef CONFIG_THERMAL_EMULATION
int count;
- unsigned long crit_temp = -1UL;
+ int crit_temp = INT_MAX;
enum thermal_trip_type type;
-#endif
if (!tz || IS_ERR(tz) || !tz->ops->get_temp)
goto exit;
@@ -426,25 +490,26 @@ int thermal_zone_get_temp(struct thermal_zone_device *tz, unsigned long *temp)
mutex_lock(&tz->lock);
ret = tz->ops->get_temp(tz, temp);
-#ifdef CONFIG_THERMAL_EMULATION
- if (!tz->emul_temperature)
- goto skip_emul;
-
- for (count = 0; count < tz->trips; count++) {
- ret = tz->ops->get_trip_type(tz, count, &type);
- if (!ret && type == THERMAL_TRIP_CRITICAL) {
- ret = tz->ops->get_trip_temp(tz, count, &crit_temp);
- break;
- }
- }
- if (ret)
- goto skip_emul;
+ if (IS_ENABLED(CONFIG_THERMAL_EMULATION) && tz->emul_temperature) {
+ for (count = 0; count < tz->trips; count++) {
+ ret = tz->ops->get_trip_type(tz, count, &type);
+ if (!ret && type == THERMAL_TRIP_CRITICAL) {
+ ret = tz->ops->get_trip_temp(tz, count,
+ &crit_temp);
+ break;
+ }
+ }
- if (*temp < crit_temp)
- *temp = tz->emul_temperature;
-skip_emul:
-#endif
+ /*
+ * Only allow emulating a temperature when the real temperature
+ * is below the critical temperature so that the emulation code
+ * cannot hide critical conditions.
+ */
+ if (!ret && *temp < crit_temp)
+ *temp = tz->emul_temperature;
+ }
+
mutex_unlock(&tz->lock);
exit:
return ret;
@@ -453,8 +518,7 @@ EXPORT_SYMBOL_GPL(thermal_zone_get_temp);
static void update_temperature(struct thermal_zone_device *tz)
{
- long temp;
- int ret;
+ int temp, ret;
ret = thermal_zone_get_temp(tz, &temp);
if (ret) {
@@ -471,14 +535,31 @@ static void update_temperature(struct thermal_zone_device *tz)
mutex_unlock(&tz->lock);
trace_thermal_temperature(tz);
- dev_dbg(&tz->device, "last_temperature=%d, current_temperature=%d\n",
- tz->last_temperature, tz->temperature);
+ if (tz->last_temperature == THERMAL_TEMP_INVALID)
+ dev_dbg(&tz->device, "last_temperature N/A, current_temperature=%d\n",
+ tz->temperature);
+ else
+ dev_dbg(&tz->device, "last_temperature=%d, current_temperature=%d\n",
+ tz->last_temperature, tz->temperature);
+}
+
+static void thermal_zone_device_reset(struct thermal_zone_device *tz)
+{
+ struct thermal_instance *pos;
+
+ tz->temperature = THERMAL_TEMP_INVALID;
+ tz->passive = 0;
+ list_for_each_entry(pos, &tz->thermal_instances, tz_node)
+ pos->initialized = false;
}
void thermal_zone_device_update(struct thermal_zone_device *tz)
{
int count;
+ if (atomic_read(&in_suspend))
+ return;
+
if (!tz->ops->get_temp)
return;
@@ -514,15 +595,14 @@ static ssize_t
temp_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct thermal_zone_device *tz = to_thermal_zone(dev);
- long temperature;
- int ret;
+ int temperature, ret;
ret = thermal_zone_get_temp(tz, &temperature);
if (ret)
return ret;
- return sprintf(buf, "%ld\n", temperature);
+ return sprintf(buf, "%d\n", temperature);
}
static ssize_t
@@ -626,7 +706,7 @@ trip_point_temp_show(struct device *dev, struct device_attribute *attr,
{
struct thermal_zone_device *tz = to_thermal_zone(dev);
int trip, ret;
- long temperature;
+ int temperature;
if (!tz->ops->get_trip_temp)
return -EPERM;
@@ -639,7 +719,7 @@ trip_point_temp_show(struct device *dev, struct device_attribute *attr,
if (ret)
return ret;
- return sprintf(buf, "%ld\n", temperature);
+ return sprintf(buf, "%d\n", temperature);
}
static ssize_t
@@ -648,7 +728,7 @@ trip_point_hyst_store(struct device *dev, struct device_attribute *attr,
{
struct thermal_zone_device *tz = to_thermal_zone(dev);
int trip, ret;
- unsigned long temperature;
+ int temperature;
if (!tz->ops->set_trip_hyst)
return -EPERM;
@@ -656,7 +736,7 @@ trip_point_hyst_store(struct device *dev, struct device_attribute *attr,
if (!sscanf(attr->attr.name, "trip_point_%d_hyst", &trip))
return -EINVAL;
- if (kstrtoul(buf, 10, &temperature))
+ if (kstrtoint(buf, 10, &temperature))
return -EINVAL;
/*
@@ -675,7 +755,7 @@ trip_point_hyst_show(struct device *dev, struct device_attribute *attr,
{
struct thermal_zone_device *tz = to_thermal_zone(dev);
int trip, ret;
- unsigned long temperature;
+ int temperature;
if (!tz->ops->get_trip_hyst)
return -EPERM;
@@ -685,7 +765,7 @@ trip_point_hyst_show(struct device *dev, struct device_attribute *attr,
ret = tz->ops->get_trip_hyst(tz, trip, &temperature);
- return ret ? ret : sprintf(buf, "%ld\n", temperature);
+ return ret ? ret : sprintf(buf, "%d\n", temperature);
}
static ssize_t
@@ -713,7 +793,8 @@ passive_store(struct device *dev, struct device_attribute *attr,
thermal_zone_bind_cooling_device(tz,
THERMAL_TRIPS_NONE, cdev,
THERMAL_NO_LIMIT,
- THERMAL_NO_LIMIT);
+ THERMAL_NO_LIMIT,
+ THERMAL_WEIGHT_DEFAULT);
}
mutex_unlock(&thermal_list_lock);
if (!tz->passive_delay)
@@ -765,8 +846,9 @@ policy_store(struct device *dev, struct device_attribute *attr,
if (!gov)
goto exit;
- tz->governor = gov;
- ret = count;
+ ret = thermal_set_governor(tz, gov);
+ if (!ret)
+ ret = count;
exit:
mutex_unlock(&tz->lock);
@@ -782,7 +864,27 @@ policy_show(struct device *dev, struct device_attribute *devattr, char *buf)
return sprintf(buf, "%s\n", tz->governor->name);
}
-#ifdef CONFIG_THERMAL_EMULATION
+static ssize_t
+available_policies_show(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ struct thermal_governor *pos;
+ ssize_t count = 0;
+ ssize_t size = PAGE_SIZE;
+
+ mutex_lock(&thermal_governor_lock);
+
+ list_for_each_entry(pos, &thermal_governor_list, governor_list) {
+ size = PAGE_SIZE - count;
+ count += scnprintf(buf + count, size, "%s ", pos->name);
+ }
+ count += scnprintf(buf + count, size, "\n");
+
+ mutex_unlock(&thermal_governor_lock);
+
+ return count;
+}
+
static ssize_t
emul_temp_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
@@ -808,13 +910,193 @@ emul_temp_store(struct device *dev, struct device_attribute *attr,
return ret ? ret : count;
}
static DEVICE_ATTR(emul_temp, S_IWUSR, NULL, emul_temp_store);
-#endif/*CONFIG_THERMAL_EMULATION*/
+
+static ssize_t
+sustainable_power_show(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+
+ if (tz->tzp)
+ return sprintf(buf, "%u\n", tz->tzp->sustainable_power);
+ else
+ return -EIO;
+}
+
+static ssize_t
+sustainable_power_store(struct device *dev, struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ u32 sustainable_power;
+
+ if (!tz->tzp)
+ return -EIO;
+
+ if (kstrtou32(buf, 10, &sustainable_power))
+ return -EINVAL;
+
+ tz->tzp->sustainable_power = sustainable_power;
+
+ return count;
+}
+static DEVICE_ATTR(sustainable_power, S_IWUSR | S_IRUGO, sustainable_power_show,
+ sustainable_power_store);
+
+#define create_s32_tzp_attr(name) \
+ static ssize_t \
+ name##_show(struct device *dev, struct device_attribute *devattr, \
+ char *buf) \
+ { \
+ struct thermal_zone_device *tz = to_thermal_zone(dev); \
+ \
+ if (tz->tzp) \
+ return sprintf(buf, "%u\n", tz->tzp->name); \
+ else \
+ return -EIO; \
+ } \
+ \
+ static ssize_t \
+ name##_store(struct device *dev, struct device_attribute *devattr, \
+ const char *buf, size_t count) \
+ { \
+ struct thermal_zone_device *tz = to_thermal_zone(dev); \
+ s32 value; \
+ \
+ if (!tz->tzp) \
+ return -EIO; \
+ \
+ if (kstrtos32(buf, 10, &value)) \
+ return -EINVAL; \
+ \
+ tz->tzp->name = value; \
+ \
+ return count; \
+ } \
+ static DEVICE_ATTR(name, S_IWUSR | S_IRUGO, name##_show, name##_store)
+
+create_s32_tzp_attr(k_po);
+create_s32_tzp_attr(k_pu);
+create_s32_tzp_attr(k_i);
+create_s32_tzp_attr(k_d);
+create_s32_tzp_attr(integral_cutoff);
+create_s32_tzp_attr(slope);
+create_s32_tzp_attr(offset);
+#undef create_s32_tzp_attr
+
+static struct device_attribute *dev_tzp_attrs[] = {
+ &dev_attr_sustainable_power,
+ &dev_attr_k_po,
+ &dev_attr_k_pu,
+ &dev_attr_k_i,
+ &dev_attr_k_d,
+ &dev_attr_integral_cutoff,
+ &dev_attr_slope,
+ &dev_attr_offset,
+};
+
+static int create_tzp_attrs(struct device *dev)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dev_tzp_attrs); i++) {
+ int ret;
+ struct device_attribute *dev_attr = dev_tzp_attrs[i];
+
+ ret = device_create_file(dev, dev_attr);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * power_actor_get_max_power() - get the maximum power that a cdev can consume
+ * @cdev: pointer to &thermal_cooling_device
+ * @tz: a valid thermal zone device pointer
+ * @max_power: pointer in which to store the maximum power
+ *
+ * Calculate the maximum power consumption in milliwats that the
+ * cooling device can currently consume and store it in @max_power.
+ *
+ * Return: 0 on success, -EINVAL if @cdev doesn't support the
+ * power_actor API or -E* on other error.
+ */
+int power_actor_get_max_power(struct thermal_cooling_device *cdev,
+ struct thermal_zone_device *tz, u32 *max_power)
+{
+ if (!cdev_is_power_actor(cdev))
+ return -EINVAL;
+
+ return cdev->ops->state2power(cdev, tz, 0, max_power);
+}
+
+/**
+ * power_actor_get_min_power() - get the mainimum power that a cdev can consume
+ * @cdev: pointer to &thermal_cooling_device
+ * @tz: a valid thermal zone device pointer
+ * @min_power: pointer in which to store the minimum power
+ *
+ * Calculate the minimum power consumption in milliwatts that the
+ * cooling device can currently consume and store it in @min_power.
+ *
+ * Return: 0 on success, -EINVAL if @cdev doesn't support the
+ * power_actor API or -E* on other error.
+ */
+int power_actor_get_min_power(struct thermal_cooling_device *cdev,
+ struct thermal_zone_device *tz, u32 *min_power)
+{
+ unsigned long max_state;
+ int ret;
+
+ if (!cdev_is_power_actor(cdev))
+ return -EINVAL;
+
+ ret = cdev->ops->get_max_state(cdev, &max_state);
+ if (ret)
+ return ret;
+
+ return cdev->ops->state2power(cdev, tz, max_state, min_power);
+}
+
+/**
+ * power_actor_set_power() - limit the maximum power that a cooling device can consume
+ * @cdev: pointer to &thermal_cooling_device
+ * @instance: thermal instance to update
+ * @power: the power in milliwatts
+ *
+ * Set the cooling device to consume at most @power milliwatts.
+ *
+ * Return: 0 on success, -EINVAL if the cooling device does not
+ * implement the power actor API or -E* for other failures.
+ */
+int power_actor_set_power(struct thermal_cooling_device *cdev,
+ struct thermal_instance *instance, u32 power)
+{
+ unsigned long state;
+ int ret;
+
+ if (!cdev_is_power_actor(cdev))
+ return -EINVAL;
+
+ ret = cdev->ops->power2state(cdev, instance->tz, power, &state);
+ if (ret)
+ return ret;
+
+ instance->target = state;
+ cdev->updated = false;
+ thermal_cdev_update(cdev);
+
+ return 0;
+}
static DEVICE_ATTR(type, 0444, type_show, NULL);
static DEVICE_ATTR(temp, 0444, temp_show, NULL);
static DEVICE_ATTR(mode, 0644, mode_show, mode_store);
static DEVICE_ATTR(passive, S_IRUGO | S_IWUSR, passive_show, passive_store);
static DEVICE_ATTR(policy, S_IRUGO | S_IWUSR, policy_show, policy_store);
+static DEVICE_ATTR(available_policies, S_IRUGO, available_policies_show, NULL);
/* sys I/F for cooling device */
#define to_cooling_device(_dev) \
@@ -917,6 +1199,34 @@ static const struct attribute_group *cooling_device_attr_groups[] = {
NULL,
};
+static ssize_t
+thermal_cooling_device_weight_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct thermal_instance *instance;
+
+ instance = container_of(attr, struct thermal_instance, weight_attr);
+
+ return sprintf(buf, "%d\n", instance->weight);
+}
+
+static ssize_t
+thermal_cooling_device_weight_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_instance *instance;
+ int ret, weight;
+
+ ret = kstrtoint(buf, 0, &weight);
+ if (ret)
+ return ret;
+
+ instance = container_of(attr, struct thermal_instance, weight_attr);
+ instance->weight = weight;
+
+ return count;
+}
/* Device management */
/**
@@ -931,6 +1241,9 @@ static const struct attribute_group *cooling_device_attr_groups[] = {
* @lower: the Minimum cooling state can be used for this trip point.
* THERMAL_NO_LIMIT means no lower limit,
* and the cooling device can be in cooling state 0.
+ * @weight: The weight of the cooling device to be bound to the
+ * thermal zone. Use THERMAL_WEIGHT_DEFAULT for the
+ * default value
*
* This interface function bind a thermal cooling device to the certain trip
* point of a thermal zone device.
@@ -941,7 +1254,8 @@ static const struct attribute_group *cooling_device_attr_groups[] = {
int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
int trip,
struct thermal_cooling_device *cdev,
- unsigned long upper, unsigned long lower)
+ unsigned long upper, unsigned long lower,
+ unsigned int weight)
{
struct thermal_instance *dev;
struct thermal_instance *pos;
@@ -986,6 +1300,7 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
dev->upper = upper;
dev->lower = lower;
dev->target = THERMAL_NO_TARGET;
+ dev->weight = weight;
result = get_idr(&tz->idr, &tz->lock, &dev->id);
if (result)
@@ -1006,6 +1321,16 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
if (result)
goto remove_symbol_link;
+ sprintf(dev->weight_attr_name, "cdev%d_weight", dev->id);
+ sysfs_attr_init(&dev->weight_attr.attr);
+ dev->weight_attr.attr.name = dev->weight_attr_name;
+ dev->weight_attr.attr.mode = S_IWUSR | S_IRUGO;
+ dev->weight_attr.show = thermal_cooling_device_weight_show;
+ dev->weight_attr.store = thermal_cooling_device_weight_store;
+ result = device_create_file(&tz->device, &dev->weight_attr);
+ if (result)
+ goto remove_trip_file;
+
mutex_lock(&tz->lock);
mutex_lock(&cdev->lock);
list_for_each_entry(pos, &tz->thermal_instances, tz_node)
@@ -1016,6 +1341,7 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
if (!result) {
list_add_tail(&dev->tz_node, &tz->thermal_instances);
list_add_tail(&dev->cdev_node, &cdev->thermal_instances);
+ atomic_set(&tz->need_update, 1);
}
mutex_unlock(&cdev->lock);
mutex_unlock(&tz->lock);
@@ -1023,6 +1349,8 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
if (!result)
return 0;
+ device_remove_file(&tz->device, &dev->weight_attr);
+remove_trip_file:
device_remove_file(&tz->device, &dev->attr);
remove_symbol_link:
sysfs_remove_link(&tz->device.kobj, dev->name);
@@ -1071,6 +1399,7 @@ int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz,
return -ENODEV;
unbind:
+ device_remove_file(&tz->device, &pos->weight_attr);
device_remove_file(&tz->device, &pos->attr);
sysfs_remove_link(&tz->device.kobj, pos->name);
release_idr(&tz->idr, &tz->lock, pos->id);
@@ -1122,6 +1451,7 @@ __thermal_cooling_device_register(struct device_node *np,
const struct thermal_cooling_device_ops *ops)
{
struct thermal_cooling_device *cdev;
+ struct thermal_zone_device *pos = NULL;
int result;
if (type && strlen(type) >= THERMAL_NAME_LENGTH)
@@ -1166,6 +1496,12 @@ __thermal_cooling_device_register(struct device_node *np,
/* Update binding information for 'this' new cdev */
bind_cdev(cdev);
+ mutex_lock(&thermal_list_lock);
+ list_for_each_entry(pos, &thermal_tz_list, node)
+ if (atomic_cmpxchg(&pos->need_update, 1, 0))
+ thermal_zone_device_update(pos);
+ mutex_unlock(&thermal_list_lock);
+
return cdev;
}
@@ -1377,7 +1713,8 @@ static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
tz->trip_temp_attrs[indx].name;
tz->trip_temp_attrs[indx].attr.attr.mode = S_IRUGO;
tz->trip_temp_attrs[indx].attr.show = trip_point_temp_show;
- if (mask & (1 << indx)) {
+ if (IS_ENABLED(CONFIG_THERMAL_WRITABLE_TRIPS) &&
+ mask & (1 << indx)) {
tz->trip_temp_attrs[indx].attr.attr.mode |= S_IWUSR;
tz->trip_temp_attrs[indx].attr.store =
trip_point_temp_store;
@@ -1454,7 +1791,7 @@ static void remove_trip_attrs(struct thermal_zone_device *tz)
struct thermal_zone_device *thermal_zone_device_register(const char *type,
int trips, int mask, void *devdata,
struct thermal_zone_device_ops *ops,
- const struct thermal_zone_params *tzp,
+ struct thermal_zone_params *tzp,
int passive_delay, int polling_delay)
{
struct thermal_zone_device *tz;
@@ -1462,6 +1799,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
int result;
int count;
int passive = 0;
+ struct thermal_governor *governor;
if (type && strlen(type) >= THERMAL_NAME_LENGTH)
return ERR_PTR(-EINVAL);
@@ -1496,6 +1834,8 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
tz->trips = trips;
tz->passive_delay = passive_delay;
tz->polling_delay = polling_delay;
+ /* A new thermal zone needs to be updated anyway. */
+ atomic_set(&tz->need_update, 1);
dev_set_name(&tz->device, "thermal_zone%d", tz->id);
result = device_register(&tz->device);
@@ -1538,23 +1878,40 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
goto unregister;
}
-#ifdef CONFIG_THERMAL_EMULATION
- result = device_create_file(&tz->device, &dev_attr_emul_temp);
- if (result)
- goto unregister;
-#endif
+ if (IS_ENABLED(CONFIG_THERMAL_EMULATION)) {
+ result = device_create_file(&tz->device, &dev_attr_emul_temp);
+ if (result)
+ goto unregister;
+ }
+
/* Create policy attribute */
result = device_create_file(&tz->device, &dev_attr_policy);
if (result)
goto unregister;
+ /* Add thermal zone params */
+ result = create_tzp_attrs(&tz->device);
+ if (result)
+ goto unregister;
+
+ /* Create available_policies attribute */
+ result = device_create_file(&tz->device, &dev_attr_available_policies);
+ if (result)
+ goto unregister;
+
/* Update 'this' zone's governor information */
mutex_lock(&thermal_governor_lock);
if (tz->tzp)
- tz->governor = __find_governor(tz->tzp->governor_name);
+ governor = __find_governor(tz->tzp->governor_name);
else
- tz->governor = def_governor;
+ governor = def_governor;
+
+ result = thermal_set_governor(tz, governor);
+ if (result) {
+ mutex_unlock(&thermal_governor_lock);
+ goto unregister;
+ }
mutex_unlock(&thermal_governor_lock);
@@ -1573,10 +1930,10 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check);
- if (!tz->ops->get_temp)
- thermal_zone_device_set_polling(tz, 0);
-
- thermal_zone_device_update(tz);
+ thermal_zone_device_reset(tz);
+ /* Update the new thermal zone and mark it as already updated. */
+ if (atomic_cmpxchg(&tz->need_update, 1, 0))
+ thermal_zone_device_update(tz);
return tz;
@@ -1642,8 +1999,9 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz)
if (tz->ops->get_mode)
device_remove_file(&tz->device, &dev_attr_mode);
device_remove_file(&tz->device, &dev_attr_policy);
+ device_remove_file(&tz->device, &dev_attr_available_policies);
remove_trip_attrs(tz);
- tz->governor = NULL;
+ thermal_set_governor(tz, NULL);
thermal_remove_hwmon_sysfs(tz);
release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id);
@@ -1799,7 +2157,11 @@ static int __init thermal_register_governors(void)
if (result)
return result;
- return thermal_gov_user_space_register();
+ result = thermal_gov_user_space_register();
+ if (result)
+ return result;
+
+ return thermal_gov_power_allocator_register();
}
static void thermal_unregister_governors(void)
@@ -1808,8 +2170,39 @@ static void thermal_unregister_governors(void)
thermal_gov_fair_share_unregister();
thermal_gov_bang_bang_unregister();
thermal_gov_user_space_unregister();
+ thermal_gov_power_allocator_unregister();
}
+static int thermal_pm_notify(struct notifier_block *nb,
+ unsigned long mode, void *_unused)
+{
+ struct thermal_zone_device *tz;
+
+ switch (mode) {
+ case PM_HIBERNATION_PREPARE:
+ case PM_RESTORE_PREPARE:
+ case PM_SUSPEND_PREPARE:
+ atomic_set(&in_suspend, 1);
+ break;
+ case PM_POST_HIBERNATION:
+ case PM_POST_RESTORE:
+ case PM_POST_SUSPEND:
+ atomic_set(&in_suspend, 0);
+ list_for_each_entry(tz, &thermal_tz_list, node) {
+ thermal_zone_device_reset(tz);
+ thermal_zone_device_update(tz);
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static struct notifier_block thermal_pm_nb = {
+ .notifier_call = thermal_pm_notify,
+};
+
static int __init thermal_init(void)
{
int result;
@@ -1830,6 +2223,11 @@ static int __init thermal_init(void)
if (result)
goto exit_netlink;
+ result = register_pm_notifier(&thermal_pm_nb);
+ if (result)
+ pr_warn("Thermal: Can not register suspend notifier, return %d\n",
+ result);
+
return 0;
exit_netlink:
@@ -1849,6 +2247,7 @@ error:
static void __exit thermal_exit(void)
{
+ unregister_pm_notifier(&thermal_pm_nb);
of_thermal_destroy_zones();
genetlink_exit();
class_unregister(&thermal_class);
diff --git a/kernel/drivers/thermal/thermal_core.h b/kernel/drivers/thermal/thermal_core.h
index 8e391812e..749d41abf 100644
--- a/kernel/drivers/thermal/thermal_core.h
+++ b/kernel/drivers/thermal/thermal_core.h
@@ -41,13 +41,17 @@ struct thermal_instance {
struct thermal_zone_device *tz;
struct thermal_cooling_device *cdev;
int trip;
+ bool initialized;
unsigned long upper; /* Highest cooling state for this trip point */
unsigned long lower; /* Lowest cooling state for this trip point */
unsigned long target; /* expected cooling state */
char attr_name[THERMAL_NAME_LENGTH];
struct device_attribute attr;
+ char weight_attr_name[THERMAL_NAME_LENGTH];
+ struct device_attribute weight_attr;
struct list_head tz_node; /* node in tz->thermal_instances */
struct list_head cdev_node; /* node in cdev->thermal_instances */
+ unsigned int weight; /* The weight of the cooling device */
};
int thermal_register_governor(struct thermal_governor *);
@@ -85,6 +89,14 @@ static inline int thermal_gov_user_space_register(void) { return 0; }
static inline void thermal_gov_user_space_unregister(void) {}
#endif /* CONFIG_THERMAL_GOV_USER_SPACE */
+#ifdef CONFIG_THERMAL_GOV_POWER_ALLOCATOR
+int thermal_gov_power_allocator_register(void);
+void thermal_gov_power_allocator_unregister(void);
+#else
+static inline int thermal_gov_power_allocator_register(void) { return 0; }
+static inline void thermal_gov_power_allocator_unregister(void) {}
+#endif /* CONFIG_THERMAL_GOV_POWER_ALLOCATOR */
+
/* device tree support */
#ifdef CONFIG_THERMAL_OF
int of_parse_thermal_zones(void);
diff --git a/kernel/drivers/thermal/thermal_hwmon.c b/kernel/drivers/thermal/thermal_hwmon.c
index 1967bee4f..06fd2ed9e 100644
--- a/kernel/drivers/thermal/thermal_hwmon.c
+++ b/kernel/drivers/thermal/thermal_hwmon.c
@@ -69,7 +69,7 @@ static DEVICE_ATTR(name, 0444, name_show, NULL);
static ssize_t
temp_input_show(struct device *dev, struct device_attribute *attr, char *buf)
{
- long temperature;
+ int temperature;
int ret;
struct thermal_hwmon_attr *hwmon_attr
= container_of(attr, struct thermal_hwmon_attr, attr);
@@ -83,7 +83,7 @@ temp_input_show(struct device *dev, struct device_attribute *attr, char *buf)
if (ret)
return ret;
- return sprintf(buf, "%ld\n", temperature);
+ return sprintf(buf, "%d\n", temperature);
}
static ssize_t
@@ -95,14 +95,14 @@ temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf)
= container_of(hwmon_attr, struct thermal_hwmon_temp,
temp_crit);
struct thermal_zone_device *tz = temp->tz;
- long temperature;
+ int temperature;
int ret;
ret = tz->ops->get_trip_temp(tz, 0, &temperature);
if (ret)
return ret;
- return sprintf(buf, "%ld\n", temperature);
+ return sprintf(buf, "%d\n", temperature);
}
@@ -142,7 +142,7 @@ thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon,
static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz)
{
- unsigned long temp;
+ int temp;
return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp);
}
diff --git a/kernel/drivers/thermal/ti-soc-thermal/Kconfig b/kernel/drivers/thermal/ti-soc-thermal/Kconfig
index bd4c7beba..ea8283f08 100644
--- a/kernel/drivers/thermal/ti-soc-thermal/Kconfig
+++ b/kernel/drivers/thermal/ti-soc-thermal/Kconfig
@@ -1,7 +1,5 @@
config TI_SOC_THERMAL
tristate "Texas Instruments SoCs temperature sensor driver"
- depends on THERMAL
- depends on ARCH_HAS_BANDGAP
help
If you say yes here you get support for the Texas Instruments
OMAP4460+ on die bandgap temperature sensor support. The register
@@ -21,10 +19,25 @@ config TI_THERMAL
This includes trip points definitions, extrapolation rules and
CPU cooling device bindings.
+config OMAP3_THERMAL
+ bool "Texas Instruments OMAP3 thermal support"
+ depends on TI_SOC_THERMAL
+ depends on ARCH_OMAP3 || COMPILE_TEST
+ help
+ If you say yes here you get thermal support for the Texas Instruments
+ OMAP3 SoC family. The current chips supported are:
+ - OMAP3430
+
+ OMAP3 chips normally don't need thermal management, and sensors in
+ this generation are not accurate, nor they are very close to
+ the important hotspots.
+
+ Say 'N' here.
+
config OMAP4_THERMAL
bool "Texas Instruments OMAP4 thermal support"
depends on TI_SOC_THERMAL
- depends on ARCH_OMAP4
+ depends on ARCH_OMAP4 || COMPILE_TEST
help
If you say yes here you get thermal support for the Texas Instruments
OMAP4 SoC family. The current chip supported are:
@@ -38,7 +51,7 @@ config OMAP4_THERMAL
config OMAP5_THERMAL
bool "Texas Instruments OMAP5 thermal support"
depends on TI_SOC_THERMAL
- depends on SOC_OMAP5
+ depends on SOC_OMAP5 || COMPILE_TEST
help
If you say yes here you get thermal support for the Texas Instruments
OMAP5 SoC family. The current chip supported are:
@@ -50,7 +63,7 @@ config OMAP5_THERMAL
config DRA752_THERMAL
bool "Texas Instruments DRA752 thermal support"
depends on TI_SOC_THERMAL
- depends on SOC_DRA7XX
+ depends on SOC_DRA7XX || COMPILE_TEST
help
If you say yes here you get thermal support for the Texas Instruments
DRA752 SoC family. The current chip supported are:
diff --git a/kernel/drivers/thermal/ti-soc-thermal/Makefile b/kernel/drivers/thermal/ti-soc-thermal/Makefile
index 1226b2484..0f89bdf03 100644
--- a/kernel/drivers/thermal/ti-soc-thermal/Makefile
+++ b/kernel/drivers/thermal/ti-soc-thermal/Makefile
@@ -2,5 +2,6 @@ obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal.o
ti-soc-thermal-y := ti-bandgap.o
ti-soc-thermal-$(CONFIG_TI_THERMAL) += ti-thermal-common.o
ti-soc-thermal-$(CONFIG_DRA752_THERMAL) += dra752-thermal-data.o
+ti-soc-thermal-$(CONFIG_OMAP3_THERMAL) += omap3-thermal-data.o
ti-soc-thermal-$(CONFIG_OMAP4_THERMAL) += omap4-thermal-data.o
ti-soc-thermal-$(CONFIG_OMAP5_THERMAL) += omap5-thermal-data.o
diff --git a/kernel/drivers/thermal/ti-soc-thermal/omap3-thermal-data.c b/kernel/drivers/thermal/ti-soc-thermal/omap3-thermal-data.c
new file mode 100644
index 000000000..3ee34340e
--- /dev/null
+++ b/kernel/drivers/thermal/ti-soc-thermal/omap3-thermal-data.c
@@ -0,0 +1,176 @@
+/*
+ * OMAP3 thermal driver.
+ *
+ * Copyright (C) 2011-2012 Texas Instruments Inc.
+ * Copyright (C) 2014 Pavel Machek <pavel@ucw.cz>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Note
+ * http://www.ti.com/lit/er/sprz278f/sprz278f.pdf "Advisory
+ * 3.1.1.186 MMC OCP Clock Not Gated When Thermal Sensor Is Used"
+ *
+ * Also TI says:
+ * Just be careful when you try to make thermal policy like decisions
+ * based on this sensor. Placement of the sensor w.r.t the actual logic
+ * generating heat has to be a factor as well. If you are just looking
+ * for an approximation temperature (thermometerish kind), you might be
+ * ok with this. I am not sure we'd find any TI data around this.. just a
+ * heads up.
+ */
+
+#include "ti-thermal.h"
+#include "ti-bandgap.h"
+
+/*
+ * OMAP34XX has one instance of thermal sensor for MPU
+ * need to describe the individual bit fields
+ */
+static struct temp_sensor_registers
+omap34xx_mpu_temp_sensor_registers = {
+ .temp_sensor_ctrl = 0,
+ .bgap_soc_mask = BIT(8),
+ .bgap_eocz_mask = BIT(7),
+ .bgap_dtemp_mask = 0x7f,
+
+ .bgap_mode_ctrl = 0,
+ .mode_ctrl_mask = BIT(9),
+};
+
+/* Thresholds and limits for OMAP34XX MPU temperature sensor */
+static struct temp_sensor_data omap34xx_mpu_temp_sensor_data = {
+ .min_freq = 32768,
+ .max_freq = 32768,
+ .max_temp = 125000,
+ .min_temp = -40000,
+ .hyst_val = 5000,
+};
+
+/*
+ * Temperature values in milli degree celsius
+ */
+static const int
+omap34xx_adc_to_temp[128] = {
+ -40000, -40000, -40000, -40000, -40000, -39000, -38000, -36000,
+ -34000, -32000, -31000, -29000, -28000, -26000, -25000, -24000,
+ -22000, -21000, -19000, -18000, -17000, -15000, -14000, -12000,
+ -11000, -9000, -8000, -7000, -5000, -4000, -2000, -1000, 0000,
+ 1000, 3000, 4000, 5000, 7000, 8000, 10000, 11000, 13000, 14000,
+ 15000, 17000, 18000, 20000, 21000, 22000, 24000, 25000, 27000,
+ 28000, 30000, 31000, 32000, 34000, 35000, 37000, 38000, 39000,
+ 41000, 42000, 44000, 45000, 47000, 48000, 49000, 51000, 52000,
+ 53000, 55000, 56000, 58000, 59000, 60000, 62000, 63000, 65000,
+ 66000, 67000, 69000, 70000, 72000, 73000, 74000, 76000, 77000,
+ 79000, 80000, 81000, 83000, 84000, 85000, 87000, 88000, 89000,
+ 91000, 92000, 94000, 95000, 96000, 98000, 99000, 100000,
+ 102000, 103000, 105000, 106000, 107000, 109000, 110000, 111000,
+ 113000, 114000, 116000, 117000, 118000, 120000, 121000, 122000,
+ 124000, 124000, 125000, 125000, 125000, 125000, 125000
+};
+
+/* OMAP34XX data */
+const struct ti_bandgap_data omap34xx_data = {
+ .features = TI_BANDGAP_FEATURE_CLK_CTRL | TI_BANDGAP_FEATURE_UNRELIABLE,
+ .fclock_name = "ts_fck",
+ .div_ck_name = "ts_fck",
+ .conv_table = omap34xx_adc_to_temp,
+ .adc_start_val = 0,
+ .adc_end_val = 127,
+ .expose_sensor = ti_thermal_expose_sensor,
+ .remove_sensor = ti_thermal_remove_sensor,
+
+ .sensors = {
+ {
+ .registers = &omap34xx_mpu_temp_sensor_registers,
+ .ts_data = &omap34xx_mpu_temp_sensor_data,
+ .domain = "cpu",
+ .slope = 0,
+ .constant = 20000,
+ .slope_pcb = 0,
+ .constant_pcb = 20000,
+ .register_cooling = NULL,
+ .unregister_cooling = NULL,
+ },
+ },
+ .sensor_count = 1,
+};
+
+/*
+ * OMAP36XX has one instance of thermal sensor for MPU
+ * need to describe the individual bit fields
+ */
+static struct temp_sensor_registers
+omap36xx_mpu_temp_sensor_registers = {
+ .temp_sensor_ctrl = 0,
+ .bgap_soc_mask = BIT(9),
+ .bgap_eocz_mask = BIT(8),
+ .bgap_dtemp_mask = 0xFF,
+
+ .bgap_mode_ctrl = 0,
+ .mode_ctrl_mask = BIT(10),
+};
+
+/* Thresholds and limits for OMAP36XX MPU temperature sensor */
+static struct temp_sensor_data omap36xx_mpu_temp_sensor_data = {
+ .min_freq = 32768,
+ .max_freq = 32768,
+ .max_temp = 125000,
+ .min_temp = -40000,
+ .hyst_val = 5000,
+};
+
+/*
+ * Temperature values in milli degree celsius
+ */
+static const int
+omap36xx_adc_to_temp[128] = {
+ -40000, -40000, -40000, -40000, -40000, -40000, -40000, -40000,
+ -40000, -40000, -40000, -40000, -40000, -38000, -35000, -34000,
+ -32000, -30000, -28000, -26000, -24000, -22000, -20000, -18500,
+ -17000, -15000, -13500, -12000, -10000, -8000, -6500, -5000, -3500,
+ -1500, 0, 2000, 3500, 5000, 6500, 8500, 10000, 12000, 13500,
+ 15000, 17000, 19000, 21000, 23000, 25000, 27000, 28500, 30000,
+ 32000, 33500, 35000, 37000, 38500, 40000, 42000, 43500, 45000,
+ 47000, 48500, 50000, 52000, 53500, 55000, 57000, 58500, 60000,
+ 62000, 64000, 66000, 68000, 70000, 71500, 73500, 75000, 77000,
+ 78500, 80000, 82000, 83500, 85000, 87000, 88500, 90000, 92000,
+ 93500, 95000, 97000, 98500, 100000, 102000, 103500, 105000, 107000,
+ 109000, 111000, 113000, 115000, 117000, 118500, 120000, 122000,
+ 123500, 125000, 125000, 125000, 125000, 125000, 125000, 125000,
+ 125000, 125000, 125000, 125000, 125000, 125000, 125000, 125000,
+ 125000, 125000, 125000, 125000, 125000, 125000, 125000
+};
+
+/* OMAP36XX data */
+const struct ti_bandgap_data omap36xx_data = {
+ .features = TI_BANDGAP_FEATURE_CLK_CTRL | TI_BANDGAP_FEATURE_UNRELIABLE,
+ .fclock_name = "ts_fck",
+ .div_ck_name = "ts_fck",
+ .conv_table = omap36xx_adc_to_temp,
+ .adc_start_val = 0,
+ .adc_end_val = 127,
+ .expose_sensor = ti_thermal_expose_sensor,
+ .remove_sensor = ti_thermal_remove_sensor,
+
+ .sensors = {
+ {
+ .registers = &omap36xx_mpu_temp_sensor_registers,
+ .ts_data = &omap36xx_mpu_temp_sensor_data,
+ .domain = "cpu",
+ .slope = 0,
+ .constant = 20000,
+ .slope_pcb = 0,
+ .constant_pcb = 20000,
+ .register_cooling = NULL,
+ .unregister_cooling = NULL,
+ },
+ },
+ .sensor_count = 1,
+};
diff --git a/kernel/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/kernel/drivers/thermal/ti-soc-thermal/ti-bandgap.c
index bc14dc874..1e34a1efc 100644
--- a/kernel/drivers/thermal/ti-soc-thermal/ti-bandgap.c
+++ b/kernel/drivers/thermal/ti-soc-thermal/ti-bandgap.c
@@ -43,6 +43,8 @@
#include "ti-bandgap.h"
+static int ti_bandgap_force_single_read(struct ti_bandgap *bgp, int id);
+
/*** Helper functions to access registers and their bitfields ***/
/**
@@ -103,19 +105,15 @@ do { \
*/
static int ti_bandgap_power(struct ti_bandgap *bgp, bool on)
{
- int i, ret = 0;
+ int i;
- if (!TI_BANDGAP_HAS(bgp, POWER_SWITCH)) {
- ret = -ENOTSUPP;
- goto exit;
- }
+ if (!TI_BANDGAP_HAS(bgp, POWER_SWITCH))
+ return -ENOTSUPP;
for (i = 0; i < bgp->conf->sensor_count; i++)
/* active on 0 */
RMW_BITS(bgp, i, temp_sensor_ctrl, bgap_tempsoff_mask, !on);
-
-exit:
- return ret;
+ return 0;
}
/**
@@ -298,18 +296,13 @@ static
int ti_bandgap_adc_to_mcelsius(struct ti_bandgap *bgp, int adc_val, int *t)
{
const struct ti_bandgap_data *conf = bgp->conf;
- int ret = 0;
/* look up for temperature in the table and return the temperature */
- if (adc_val < conf->adc_start_val || adc_val > conf->adc_end_val) {
- ret = -ERANGE;
- goto exit;
- }
+ if (adc_val < conf->adc_start_val || adc_val > conf->adc_end_val)
+ return -ERANGE;
*t = bgp->conf->conv_table[adc_val - conf->adc_start_val];
-
-exit:
- return ret;
+ return 0;
}
/**
@@ -330,16 +323,14 @@ int ti_bandgap_mcelsius_to_adc(struct ti_bandgap *bgp, long temp, int *adc)
{
const struct ti_bandgap_data *conf = bgp->conf;
const int *conv_table = bgp->conf->conv_table;
- int high, low, mid, ret = 0;
+ int high, low, mid;
low = 0;
high = conf->adc_end_val - conf->adc_start_val;
mid = (high + low) / 2;
- if (temp < conv_table[low] || temp > conv_table[high]) {
- ret = -ERANGE;
- goto exit;
- }
+ if (temp < conv_table[low] || temp > conv_table[high])
+ return -ERANGE;
while (low < high) {
if (temp < conv_table[mid])
@@ -350,9 +341,7 @@ int ti_bandgap_mcelsius_to_adc(struct ti_bandgap *bgp, long temp, int *adc)
}
*adc = conf->adc_start_val + low;
-
-exit:
- return ret;
+ return 0;
}
/**
@@ -378,13 +367,11 @@ int ti_bandgap_add_hyst(struct ti_bandgap *bgp, int adc_val, int hyst_val,
*/
ret = ti_bandgap_adc_to_mcelsius(bgp, adc_val, &temp);
if (ret < 0)
- goto exit;
+ return ret;
temp += hyst_val;
ret = ti_bandgap_mcelsius_to_adc(bgp, temp, sum);
-
-exit:
return ret;
}
@@ -542,22 +529,18 @@ exit:
*/
static inline int ti_bandgap_validate(struct ti_bandgap *bgp, int id)
{
- int ret = 0;
-
if (!bgp || IS_ERR(bgp)) {
pr_err("%s: invalid bandgap pointer\n", __func__);
- ret = -EINVAL;
- goto exit;
+ return -EINVAL;
}
if ((id < 0) || (id >= bgp->conf->sensor_count)) {
dev_err(bgp->dev, "%s: sensor id out of range (%d)\n",
__func__, id);
- ret = -ERANGE;
+ return -ERANGE;
}
-exit:
- return ret;
+ return 0;
}
/**
@@ -585,12 +568,10 @@ static int _ti_bandgap_write_threshold(struct ti_bandgap *bgp, int id, int val,
ret = ti_bandgap_validate(bgp, id);
if (ret)
- goto exit;
+ return ret;
- if (!TI_BANDGAP_HAS(bgp, TALERT)) {
- ret = -ENOTSUPP;
- goto exit;
- }
+ if (!TI_BANDGAP_HAS(bgp, TALERT))
+ return -ENOTSUPP;
ts_data = bgp->conf->sensors[id].ts_data;
tsr = bgp->conf->sensors[id].registers;
@@ -603,17 +584,15 @@ static int _ti_bandgap_write_threshold(struct ti_bandgap *bgp, int id, int val,
}
if (ret)
- goto exit;
+ return ret;
ret = ti_bandgap_mcelsius_to_adc(bgp, val, &adc_val);
if (ret < 0)
- goto exit;
+ return ret;
spin_lock(&bgp->lock);
ret = ti_bandgap_update_alert_threshold(bgp, id, adc_val, hot);
spin_unlock(&bgp->lock);
-
-exit:
return ret;
}
@@ -656,7 +635,7 @@ static int _ti_bandgap_read_threshold(struct ti_bandgap *bgp, int id,
temp = ti_bandgap_readl(bgp, tsr->bgap_threshold);
temp = (temp & mask) >> __ffs(mask);
- ret |= ti_bandgap_adc_to_mcelsius(bgp, temp, &temp);
+ ret = ti_bandgap_adc_to_mcelsius(bgp, temp, &temp);
if (ret) {
dev_err(bgp->dev, "failed to read thot\n");
ret = -EIO;
@@ -926,11 +905,17 @@ int ti_bandgap_read_temperature(struct ti_bandgap *bgp, int id,
if (ret)
return ret;
+ if (!TI_BANDGAP_HAS(bgp, MODE_CONFIG)) {
+ ret = ti_bandgap_force_single_read(bgp, id);
+ if (ret)
+ return ret;
+ }
+
spin_lock(&bgp->lock);
temp = ti_bandgap_read_temp(bgp, id);
spin_unlock(&bgp->lock);
- ret |= ti_bandgap_adc_to_mcelsius(bgp, temp, &temp);
+ ret = ti_bandgap_adc_to_mcelsius(bgp, temp, &temp);
if (ret)
return -EIO;
@@ -991,7 +976,8 @@ void *ti_bandgap_get_sensor_data(struct ti_bandgap *bgp, int id)
static int
ti_bandgap_force_single_read(struct ti_bandgap *bgp, int id)
{
- u32 temp = 0, counter = 1000;
+ u32 counter = 1000;
+ struct temp_sensor_registers *tsr;
/* Select single conversion mode */
if (TI_BANDGAP_HAS(bgp, MODE_CONFIG))
@@ -999,16 +985,27 @@ ti_bandgap_force_single_read(struct ti_bandgap *bgp, int id)
/* Start of Conversion = 1 */
RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 1);
- /* Wait until DTEMP is updated */
- temp = ti_bandgap_read_temp(bgp, id);
- while ((temp == 0) && --counter)
- temp = ti_bandgap_read_temp(bgp, id);
- /* REVISIT: Check correct condition for end of conversion */
+ /* Wait for EOCZ going up */
+ tsr = bgp->conf->sensors[id].registers;
+
+ while (--counter) {
+ if (ti_bandgap_readl(bgp, tsr->temp_sensor_ctrl) &
+ tsr->bgap_eocz_mask)
+ break;
+ }
/* Start of Conversion = 0 */
RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 0);
+ /* Wait for EOCZ going down */
+ counter = 1000;
+ while (--counter) {
+ if (!(ti_bandgap_readl(bgp, tsr->temp_sensor_ctrl) &
+ tsr->bgap_eocz_mask))
+ break;
+ }
+
return 0;
}
@@ -1277,6 +1274,10 @@ int ti_bandgap_probe(struct platform_device *pdev)
}
bgp->dev = &pdev->dev;
+ if (TI_BANDGAP_HAS(bgp, UNRELIABLE))
+ dev_warn(&pdev->dev,
+ "This OMAP thermal sensor is unreliable. You've been warned\n");
+
if (TI_BANDGAP_HAS(bgp, TSHUT)) {
ret = ti_bandgap_tshut_init(bgp, pdev);
if (ret) {
@@ -1294,11 +1295,10 @@ int ti_bandgap_probe(struct platform_device *pdev)
goto free_irqs;
}
- bgp->div_clk = clk_get(NULL, bgp->conf->div_ck_name);
+ bgp->div_clk = clk_get(NULL, bgp->conf->div_ck_name);
ret = IS_ERR(bgp->div_clk);
if (ret) {
- dev_err(&pdev->dev,
- "failed to request div_ts_ck clock ref\n");
+ dev_err(&pdev->dev, "failed to request div_ts_ck clock ref\n");
ret = PTR_ERR(bgp->div_clk);
goto free_irqs;
}
@@ -1583,6 +1583,16 @@ static SIMPLE_DEV_PM_OPS(ti_bandgap_dev_pm_ops, ti_bandgap_suspend,
#endif
static const struct of_device_id of_ti_bandgap_match[] = {
+#ifdef CONFIG_OMAP3_THERMAL
+ {
+ .compatible = "ti,omap34xx-bandgap",
+ .data = (void *)&omap34xx_data,
+ },
+ {
+ .compatible = "ti,omap36xx-bandgap",
+ .data = (void *)&omap36xx_data,
+ },
+#endif
#ifdef CONFIG_OMAP4_THERMAL
{
.compatible = "ti,omap4430-bandgap",
diff --git a/kernel/drivers/thermal/ti-soc-thermal/ti-bandgap.h b/kernel/drivers/thermal/ti-soc-thermal/ti-bandgap.h
index 0c52f7afb..fe0adb898 100644
--- a/kernel/drivers/thermal/ti-soc-thermal/ti-bandgap.h
+++ b/kernel/drivers/thermal/ti-soc-thermal/ti-bandgap.h
@@ -322,6 +322,8 @@ struct ti_temp_sensor {
* has Errata 814
* TI_BANDGAP_FEATURE_ERRATA_813 - used to workaorund when the bandgap device
* has Errata 813
+ * TI_BANDGAP_FEATURE_UNRELIABLE - used when the sensor readings are too
+ * inaccurate.
* TI_BANDGAP_HAS(b, f) - macro to check if a bandgap device is capable of a
* specific feature (above) or not. Return non-zero, if yes.
*/
@@ -337,6 +339,7 @@ struct ti_temp_sensor {
#define TI_BANDGAP_FEATURE_HISTORY_BUFFER BIT(9)
#define TI_BANDGAP_FEATURE_ERRATA_814 BIT(10)
#define TI_BANDGAP_FEATURE_ERRATA_813 BIT(11)
+#define TI_BANDGAP_FEATURE_UNRELIABLE BIT(12)
#define TI_BANDGAP_HAS(b, f) \
((b)->conf->features & TI_BANDGAP_FEATURE_ ## f)
@@ -390,6 +393,14 @@ int ti_bandgap_set_sensor_data(struct ti_bandgap *bgp, int id, void *data);
void *ti_bandgap_get_sensor_data(struct ti_bandgap *bgp, int id);
int ti_bandgap_get_trend(struct ti_bandgap *bgp, int id, int *trend);
+#ifdef CONFIG_OMAP3_THERMAL
+extern const struct ti_bandgap_data omap34xx_data;
+extern const struct ti_bandgap_data omap36xx_data;
+#else
+#define omap34xx_data NULL
+#define omap36xx_data NULL
+#endif
+
#ifdef CONFIG_OMAP4_THERMAL
extern const struct ti_bandgap_data omap4430_data;
extern const struct ti_bandgap_data omap4460_data;
diff --git a/kernel/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/kernel/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
index a38c17564..b213a1222 100644
--- a/kernel/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
+++ b/kernel/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
@@ -75,15 +75,15 @@ static inline int ti_thermal_hotspot_temperature(int t, int s, int c)
}
/* thermal zone ops */
-/* Get temperature callback function for thermal zone*/
-static inline int __ti_thermal_get_temp(void *devdata, long *temp)
+/* Get temperature callback function for thermal zone */
+static inline int __ti_thermal_get_temp(void *devdata, int *temp)
{
struct thermal_zone_device *pcb_tz = NULL;
struct ti_thermal_data *data = devdata;
struct ti_bandgap *bgp;
const struct ti_temp_sensor *s;
int ret, tmp, slope, constant;
- unsigned long pcb_temp;
+ int pcb_temp;
if (!data)
return 0;
@@ -119,7 +119,7 @@ static inline int __ti_thermal_get_temp(void *devdata, long *temp)
}
static inline int ti_thermal_get_temp(struct thermal_zone_device *thermal,
- unsigned long *temp)
+ int *temp)
{
struct ti_thermal_data *data = thermal->devdata;
@@ -146,7 +146,8 @@ static int ti_thermal_bind(struct thermal_zone_device *thermal,
return thermal_zone_bind_cooling_device(thermal, 0, cdev,
/* bind with min and max states defined by cpu_cooling */
THERMAL_NO_LIMIT,
- THERMAL_NO_LIMIT);
+ THERMAL_NO_LIMIT,
+ THERMAL_WEIGHT_DEFAULT);
}
/* Unbind callback functions for thermal zone */
@@ -228,7 +229,7 @@ static int ti_thermal_get_trip_type(struct thermal_zone_device *thermal,
/* Get trip temperature callback functions for thermal zone */
static int ti_thermal_get_trip_temp(struct thermal_zone_device *thermal,
- int trip, unsigned long *temp)
+ int trip, int *temp)
{
if (!ti_thermal_is_valid_trip(trip))
return -EINVAL;
@@ -279,7 +280,7 @@ static int ti_thermal_get_trend(struct thermal_zone_device *thermal,
/* Get critical temperature callback functions for thermal zone */
static int ti_thermal_get_crit_temp(struct thermal_zone_device *thermal,
- unsigned long *temp)
+ int *temp)
{
/* shutdown zone */
return ti_thermal_get_trip_temp(thermal, OMAP_TRIP_NUMBER - 1, temp);
diff --git a/kernel/drivers/thermal/x86_pkg_temp_thermal.c b/kernel/drivers/thermal/x86_pkg_temp_thermal.c
index 9e68706ae..a774f0c8d 100644
--- a/kernel/drivers/thermal/x86_pkg_temp_thermal.c
+++ b/kernel/drivers/thermal/x86_pkg_temp_thermal.c
@@ -69,7 +69,7 @@ struct phy_dev_entry {
struct thermal_zone_device *tzone;
};
-static const struct thermal_zone_params pkg_temp_tz_params = {
+static struct thermal_zone_params pkg_temp_tz_params = {
.no_hwmon = true,
};
@@ -165,7 +165,7 @@ err_ret:
return err;
}
-static int sys_get_curr_temp(struct thermal_zone_device *tzd, unsigned long *temp)
+static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp)
{
u32 eax, edx;
struct phy_dev_entry *phy_dev_entry;
@@ -176,7 +176,7 @@ static int sys_get_curr_temp(struct thermal_zone_device *tzd, unsigned long *tem
if (eax & 0x80000000) {
*temp = phy_dev_entry->tj_max -
((eax >> 16) & 0x7f) * 1000;
- pr_debug("sys_get_curr_temp %ld\n", *temp);
+ pr_debug("sys_get_curr_temp %d\n", *temp);
return 0;
}
@@ -184,7 +184,7 @@ static int sys_get_curr_temp(struct thermal_zone_device *tzd, unsigned long *tem
}
static int sys_get_trip_temp(struct thermal_zone_device *tzd,
- int trip, unsigned long *temp)
+ int trip, int *temp)
{
u32 eax, edx;
struct phy_dev_entry *phy_dev_entry;
@@ -215,13 +215,13 @@ static int sys_get_trip_temp(struct thermal_zone_device *tzd,
*temp = phy_dev_entry->tj_max - thres_reg_value * 1000;
else
*temp = 0;
- pr_debug("sys_get_trip_temp %ld\n", *temp);
+ pr_debug("sys_get_trip_temp %d\n", *temp);
return 0;
}
static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip,
- unsigned long temp)
+ int temp)
{
u32 l, h;
struct phy_dev_entry *phy_dev_entry;