aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorAndrey Konovalov <andrey.konovalov@linaro.org>2012-07-16 20:16:44 +0400
committerAndrey Konovalov <andrey.konovalov@linaro.org>2012-07-16 20:16:44 +0400
commit2006e5d3e4284868403b8f7f53843ffc6b75f047 (patch)
tree35a36c0e52ced14f9d6d096c14d2aacfcfede7ed /drivers
parent1f7aafe286ebf8f21dc662d27ec0b1294cbfdb85 (diff)
parentb286f1ce2b4a48ae25ee14b30c9da4da378fc5ad (diff)
Automatically merging tracking-thermal_exynos4_imx6 into merge-linux-linaro-core-tracking
Conflicting files:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/cpufreq/Kconfig10
-rw-r--r--drivers/cpufreq/Makefile2
-rw-r--r--drivers/cpufreq/clk-reg-cpufreq.c289
-rw-r--r--drivers/hwmon/Kconfig10
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/thermal/Kconfig26
-rw-r--r--drivers/thermal/Makefile5
-rw-r--r--drivers/thermal/cpu_cooling.c571
-rw-r--r--drivers/thermal/exynos4_thermal.c (renamed from drivers/hwmon/exynos4_tmu.c)482
-rw-r--r--drivers/thermal/imx6q_thermal.c541
-rw-r--r--drivers/thermal/thermal_sys.c62
11 files changed, 1881 insertions, 118 deletions
diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig
index e24a2a1b6666..95470f15e13b 100644
--- a/drivers/cpufreq/Kconfig
+++ b/drivers/cpufreq/Kconfig
@@ -179,6 +179,16 @@ config CPU_FREQ_GOV_CONSERVATIVE
If in doubt, say N.
+config CLK_REG_CPUFREQ_DRIVER
+ tristate "Generic cpufreq driver using clk and regulator APIs"
+ depends on HAVE_CLK && OF && REGULATOR
+ select CPU_FREQ_TABLE
+ help
+ This adds generic CPUFreq driver based on clk and regulator APIs.
+ It assumes all cores of the CPU share the same clock and voltage.
+
+ If in doubt, say N.
+
menu "x86 CPU frequency scaling drivers"
depends on X86
source "drivers/cpufreq/Kconfig.x86"
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 9531fc2eda22..451a2d2796a7 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -13,6 +13,8 @@ obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE) += cpufreq_conservative.o
# CPUfreq cross-arch helpers
obj-$(CONFIG_CPU_FREQ_TABLE) += freq_table.o
+obj-$(CONFIG_CLK_REG_CPUFREQ_DRIVER) += clk-reg-cpufreq.o
+
##################################################################################
# x86 drivers.
# Link order matters. K8 is preferred to ACPI because of firmware bugs in early
diff --git a/drivers/cpufreq/clk-reg-cpufreq.c b/drivers/cpufreq/clk-reg-cpufreq.c
new file mode 100644
index 000000000000..c30d2c5993ed
--- /dev/null
+++ b/drivers/cpufreq/clk-reg-cpufreq.c
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2011 Freescale Semiconductor, Inc.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/cpufreq.h>
+#include <linux/clk.h>
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+
+static u32 *cpu_freqs; /* Hz */
+static u32 *cpu_volts; /* uV */
+static u32 trans_latency; /* ns */
+static int cpu_op_nr;
+static unsigned int cur_index;
+
+static struct clk *cpu_clk;
+static struct regulator *cpu_reg;
+static struct cpufreq_frequency_table *freq_table;
+
+static int set_cpu_freq(unsigned long freq, int index, int higher)
+{
+ int ret = 0;
+
+ if (higher && cpu_reg) {
+ ret = regulator_set_voltage(cpu_reg,
+ cpu_volts[index * 2], cpu_volts[index * 2 + 1]);
+ if (ret) {
+ pr_err("set cpu voltage failed!\n");
+ return ret;
+ }
+ }
+
+ ret = clk_set_rate(cpu_clk, freq);
+ if (ret) {
+ if (cpu_reg)
+ regulator_set_voltage(cpu_reg, cpu_volts[cur_index * 2],
+ cpu_volts[cur_index * 2 + 1]);
+ pr_err("cannot set CPU clock rate\n");
+ return ret;
+ }
+
+ if (!higher && cpu_reg) {
+ ret = regulator_set_voltage(cpu_reg,
+ cpu_volts[index * 2], cpu_volts[index * 2 + 1]);
+ if (ret)
+ pr_warn("set cpu voltage failed, might run on"
+ " higher voltage!\n");
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int clk_reg_verify_speed(struct cpufreq_policy *policy)
+{
+ return cpufreq_frequency_table_verify(policy, freq_table);
+}
+
+static unsigned int clk_reg_get_speed(unsigned int cpu)
+{
+ return clk_get_rate(cpu_clk) / 1000;
+}
+
+static int clk_reg_set_target(struct cpufreq_policy *policy,
+ unsigned int target_freq, unsigned int relation)
+{
+ struct cpufreq_freqs freqs;
+ unsigned long freq_Hz;
+ int cpu;
+ int ret = 0;
+ unsigned int index;
+
+ cpufreq_frequency_table_target(policy, freq_table,
+ target_freq, relation, &index);
+ freq_Hz = clk_round_rate(cpu_clk, cpu_freqs[index]);
+ freq_Hz = freq_Hz ? freq_Hz : cpu_freqs[index];
+ freqs.old = clk_get_rate(cpu_clk) / 1000;
+ freqs.new = freq_Hz / 1000;
+ freqs.flags = 0;
+
+ if (freqs.old == freqs.new)
+ return 0;
+
+ for_each_possible_cpu(cpu) {
+ freqs.cpu = cpu;
+ cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
+ }
+
+ ret = set_cpu_freq(freq_Hz, index, (freqs.new > freqs.old));
+ if (ret)
+ freqs.new = clk_get_rate(cpu_clk) / 1000;
+ else
+ cur_index = index;
+
+ for_each_possible_cpu(cpu) {
+ freqs.cpu = cpu;
+ cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
+ }
+
+ return ret;
+}
+
+static int clk_reg_cpufreq_init(struct cpufreq_policy *policy)
+{
+ int ret;
+
+ if (policy->cpu >= num_possible_cpus())
+ return -EINVAL;
+
+ policy->cur = clk_get_rate(cpu_clk) / 1000;
+ policy->shared_type = CPUFREQ_SHARED_TYPE_ANY;
+ cpumask_setall(policy->cpus);
+ policy->cpuinfo.transition_latency = trans_latency;
+
+ ret = cpufreq_frequency_table_cpuinfo(policy, freq_table);
+
+ if (ret < 0) {
+ pr_err("invalid frequency table for cpu %d\n",
+ policy->cpu);
+ return ret;
+ }
+
+ cpufreq_frequency_table_get_attr(freq_table, policy->cpu);
+ cpufreq_frequency_table_target(policy, freq_table, policy->cur,
+ CPUFREQ_RELATION_H, &cur_index);
+ return 0;
+}
+
+static int clk_reg_cpufreq_exit(struct cpufreq_policy *policy)
+{
+ cpufreq_frequency_table_put_attr(policy->cpu);
+ return 0;
+}
+
+static struct cpufreq_driver clk_reg_cpufreq_driver = {
+ .flags = CPUFREQ_STICKY,
+ .verify = clk_reg_verify_speed,
+ .target = clk_reg_set_target,
+ .get = clk_reg_get_speed,
+ .init = clk_reg_cpufreq_init,
+ .exit = clk_reg_cpufreq_exit,
+ .name = "clk-reg",
+};
+
+
+static u32 max_freq = UINT_MAX / 1000; /* kHz */
+module_param(max_freq, uint, 0);
+MODULE_PARM_DESC(max_freq, "max cpu frequency in unit of kHz");
+
+static int __devinit clk_reg_cpufreq_driver_init(void)
+{
+ struct device_node *cpu0;
+ const struct property *pp;
+ int i, ret;
+
+ cpu0 = of_find_node_by_path("/cpus/cpu@0");
+ if (!cpu0)
+ return -ENODEV;
+
+ pp = of_find_property(cpu0, "cpu-freqs", NULL);
+ if (!pp) {
+ ret = -ENODEV;
+ goto put_node;
+ }
+ cpu_op_nr = pp->length / sizeof(u32);
+ if (!cpu_op_nr) {
+ ret = -ENODEV;
+ goto put_node;
+ }
+ ret = -ENOMEM;
+ cpu_freqs = kzalloc(sizeof(*cpu_freqs) * cpu_op_nr, GFP_KERNEL);
+ if (!cpu_freqs)
+ goto put_node;
+ of_property_read_u32_array(cpu0, "cpu-freqs", cpu_freqs, cpu_op_nr);
+
+ pp = of_find_property(cpu0, "cpu-volts", NULL);
+ if (pp) {
+ if (cpu_op_nr * 2 == pp->length / sizeof(u32)) {
+ cpu_volts = kzalloc(sizeof(*cpu_volts) * cpu_op_nr * 2,
+ GFP_KERNEL);
+ if (!cpu_volts)
+ goto free_cpu_freqs;
+ of_property_read_u32_array(cpu0, "cpu-volts",
+ cpu_volts, cpu_op_nr * 2);
+ } else
+ pr_warn("invalid cpu_volts!\n");
+ }
+
+ if (of_property_read_u32(cpu0, "trans-latency", &trans_latency))
+ trans_latency = CPUFREQ_ETERNAL;
+
+ cpu_clk = clk_get(NULL, "cpu");
+ if (IS_ERR(cpu_clk)) {
+ pr_err("failed to get cpu clock\n");
+ ret = PTR_ERR(cpu_clk);
+ goto free_cpu_volts;
+ }
+
+ if (cpu_volts) {
+ cpu_reg = regulator_get(NULL, "cpu");
+ if (IS_ERR(cpu_reg)) {
+ pr_warn("regulator cpu get failed.\n");
+ cpu_reg = NULL;
+ }
+ }
+
+ freq_table = kmalloc(sizeof(struct cpufreq_frequency_table)
+ * (cpu_op_nr + 1), GFP_KERNEL);
+ if (!freq_table) {
+ ret = -ENOMEM;
+ goto reg_put;
+ }
+
+ for (i = 0; i < cpu_op_nr; i++) {
+ freq_table[i].index = i;
+ if (cpu_freqs[i] > max_freq * 1000) {
+ freq_table[i].frequency = CPUFREQ_ENTRY_INVALID;
+ continue;
+ }
+
+ if (cpu_reg) {
+ ret = regulator_is_supported_voltage(cpu_reg,
+ cpu_volts[i * 2], cpu_volts[i * 2 + 1]);
+ if (ret <= 0) {
+ freq_table[i].frequency = CPUFREQ_ENTRY_INVALID;
+ continue;
+ }
+ }
+ freq_table[i].frequency = cpu_freqs[i] / 1000;
+ }
+
+ freq_table[i].index = i;
+ freq_table[i].frequency = CPUFREQ_TABLE_END;
+
+ ret = cpufreq_register_driver(&clk_reg_cpufreq_driver);
+ if (ret)
+ goto free_freq_table;
+
+ of_node_put(cpu0);
+
+ return 0;
+
+free_freq_table:
+ kfree(freq_table);
+reg_put:
+ if (cpu_reg)
+ regulator_put(cpu_reg);
+ clk_put(cpu_clk);
+free_cpu_volts:
+ kfree(cpu_volts);
+free_cpu_freqs:
+ kfree(cpu_freqs);
+put_node:
+ of_node_put(cpu0);
+
+ return ret;
+}
+
+static void clk_reg_cpufreq_driver_exit(void)
+{
+ cpufreq_unregister_driver(&clk_reg_cpufreq_driver);
+ kfree(cpu_freqs);
+ kfree(cpu_volts);
+ clk_put(cpu_clk);
+ if (cpu_reg)
+ regulator_put(cpu_reg);
+ kfree(freq_table);
+}
+
+module_init(clk_reg_cpufreq_driver_init);
+module_exit(clk_reg_cpufreq_driver_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor Inc. Richard Zhao <richard.zhao@freescale.com>");
+MODULE_DESCRIPTION("Generic CPUFreq driver based on clk and regulator APIs");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 6f1d167cb1ea..2c7ef3932838 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -314,16 +314,6 @@ config SENSORS_DS1621
This driver can also be built as a module. If so, the module
will be called ds1621.
-config SENSORS_EXYNOS4_TMU
- tristate "Temperature sensor on Samsung EXYNOS4"
- depends on ARCH_EXYNOS4
- help
- If you say yes here you get support for TMU (Thermal Management
- Unit) on SAMSUNG EXYNOS4 series of SoC.
-
- This driver can also be built as a module. If so, the module
- will be called exynos4-tmu.
-
config SENSORS_I5K_AMB
tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets"
depends on PCI && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index e1eeac13b851..0544fa830fb6 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -48,7 +48,6 @@ obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o
obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o
obj-$(CONFIG_SENSORS_EMC6W201) += emc6w201.o
-obj-$(CONFIG_SENSORS_EXYNOS4_TMU) += exynos4_tmu.o
obj-$(CONFIG_SENSORS_F71805F) += f71805f.o
obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o
obj-$(CONFIG_SENSORS_F75375S) += f75375s.o
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 514a691abea0..57f897629998 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -19,6 +19,23 @@ config THERMAL_HWMON
depends on HWMON=y || HWMON=THERMAL
default y
+config CPU_THERMAL
+ bool "generic cpu cooling support"
+ depends on THERMAL && CPU_FREQ && HOTPLUG_CPU
+ help
+ This implements the generic cpu cooling mechanism through frequency
+ reduction, cpu hotplug and any other ways of reducing temperature. An
+ ACPI version of this already exists(drivers/acpi/processor_thermal.c).
+ This will be useful for platforms using the generic thermal interface
+ and not the ACPI interface.
+ If you want this support, you should say Y or M here.
+
+config IMX6Q_THERMAL
+ bool "IMX6Q Thermal interface support"
+ depends on THERMAL && CPU_THERMAL
+ help
+ Adds thermal management for IMX6Q.
+
config SPEAR_THERMAL
bool "SPEAr thermal sensor driver"
depends on THERMAL
@@ -26,3 +43,12 @@ config SPEAR_THERMAL
help
Enable this to plug the SPEAr thermal sensor driver into the Linux
thermal framework
+
+config SENSORS_EXYNOS4_TMU
+ tristate "Temperature sensor on Samsung EXYNOS4"
+ depends on ARCH_EXYNOS4 && THERMAL
+ help
+ If you say yes here you get support for TMU (Thermal Managment
+ Unit) on SAMSUNG EXYNOS4 series of SoC.
+ This driver can also be built as a module. If so, the module
+ will be called exynos4-tmu
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index a9fff0bf4b14..69c6fd6e5335 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -3,4 +3,7 @@
#
obj-$(CONFIG_THERMAL) += thermal_sys.o
-obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o \ No newline at end of file
+obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
+obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
+obj-$(CONFIG_SENSORS_EXYNOS4_TMU) += exynos4_thermal.o
+obj-$(CONFIG_IMX6Q_THERMAL) += imx6q_thermal.o
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
new file mode 100644
index 000000000000..429f4fde8f32
--- /dev/null
+++ b/drivers/thermal/cpu_cooling.c
@@ -0,0 +1,571 @@
+/*
+ * linux/drivers/thermal/cpu_cooling.c
+ *
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd(http://www.samsung.com)
+ * Copyright (C) 2011 Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/cpu_cooling.h>
+
+struct cpufreq_cooling_device {
+ int id;
+ struct thermal_cooling_device *cool_dev;
+ struct freq_clip_table *tab_ptr;
+ unsigned int tab_size;
+ unsigned int cpufreq_cur_state;
+ unsigned int cpufreq_max_state;
+ const struct cpumask *allowed_cpus;
+ enum thermal_trip_type type;
+ struct list_head node;
+};
+
+static LIST_HEAD(cooling_cpufreq_list);
+static DEFINE_MUTEX(cooling_cpufreq_lock);
+static DEFINE_IDR(cpufreq_idr);
+static DEFINE_PER_CPU(unsigned int, max_policy_freq);
+static struct freq_clip_table *notify_table;
+static int notify_state;
+static struct cpufreq_cooling_device *notify_dev;
+
+struct hotplug_cooling_device {
+ int id;
+ struct thermal_cooling_device *cool_dev;
+ unsigned int hotplug_state;
+ const struct cpumask *allowed_cpus;
+ struct list_head node;
+};
+
+static LIST_HEAD(cooling_cpuhotplug_list);
+static DEFINE_MUTEX(cooling_cpuhotplug_lock);
+static DEFINE_IDR(cpuhotplug_idr);
+
+static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
+
+static int get_idr(struct idr *idr, struct mutex *lock, int *id)
+{
+ int err;
+again:
+ if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
+ return -ENOMEM;
+
+ if (lock)
+ mutex_lock(lock);
+ err = idr_get_new(idr, NULL, id);
+ if (lock)
+ mutex_unlock(lock);
+ if (unlikely(err == -EAGAIN))
+ goto again;
+ else if (unlikely(err))
+ return err;
+
+ *id = *id & MAX_ID_MASK;
+ return 0;
+}
+
+static void release_idr(struct idr *idr, struct mutex *lock, int id)
+{
+ if (lock)
+ mutex_lock(lock);
+ idr_remove(idr, id);
+ if (lock)
+ mutex_unlock(lock);
+}
+
+int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
+{
+ int ret = 0;
+
+ switch (list) {
+ case CPUFREQ_COOLING_TYPE:
+ case CPUHOTPLUG_COOLING_TYPE:
+ ret = blocking_notifier_chain_register(
+ &cputherm_state_notifier_list, nb);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+EXPORT_SYMBOL(cputherm_register_notifier);
+
+int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
+{
+ int ret = 0;
+
+ switch (list) {
+ case CPUFREQ_COOLING_TYPE:
+ case CPUHOTPLUG_COOLING_TYPE:
+ ret = blocking_notifier_chain_unregister(
+ &cputherm_state_notifier_list, nb);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+EXPORT_SYMBOL(cputherm_unregister_notifier);
+
+/*Below codes defines functions to be used for cpufreq as cooling device*/
+static bool is_cpufreq_valid(int cpu)
+{
+ struct cpufreq_policy policy;
+ return !cpufreq_get_policy(&policy, cpu) ? true : false;
+}
+
+static int get_freq_state_count(struct cpufreq_cooling_device *cpufreq_dev)
+{
+ struct cpufreq_frequency_table *table;
+ int i = 0, cpu, max_count = 0;
+
+ if (cpufreq_dev->type != THERMAL_TRIP_PASSIVE)
+ return cpufreq_dev->cpufreq_max_state;
+
+ for_each_cpu(cpu, cpufreq_dev->allowed_cpus) {
+ table = cpufreq_frequency_get_table(cpu);
+ i = 0;
+ if (table)
+ while (table[i].frequency != CPUFREQ_TABLE_END)
+ i++;
+ if (i > max_count)
+ max_count = i;
+ }
+ return max_count;
+}
+
+static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
+ unsigned long cooling_state)
+{
+ unsigned int event, cpuid;
+ struct freq_clip_table *th_table;
+
+ if (cooling_state > get_freq_state_count(cpufreq_device))
+ return -EINVAL;
+
+ cpufreq_device->cpufreq_cur_state = cooling_state;
+
+ /*cpufreq thermal notifier uses this cpufreq device pointer*/
+ notify_state = cooling_state;
+ notify_dev = cpufreq_device;
+
+ if (notify_state > 0 && cpufreq_device->type != THERMAL_TRIP_PASSIVE) {
+ th_table = &(cpufreq_device->tab_ptr[cooling_state - 1]);
+ memcpy(notify_table, th_table, sizeof(struct freq_clip_table));
+ event = CPUFREQ_COOLING_TYPE;
+ blocking_notifier_call_chain(&cputherm_state_notifier_list,
+ event, notify_table);
+ }
+
+ for_each_cpu(cpuid, cpufreq_device->allowed_cpus) {
+ if (is_cpufreq_valid(cpuid))
+ cpufreq_update_policy(cpuid);
+ }
+
+ notify_state = -1;
+ notify_dev = NULL;
+
+ return 0;
+}
+
+static int cpufreq_thermal_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct cpufreq_policy *policy = data;
+ unsigned long max_freq = 0;
+ struct cpufreq_frequency_table *table;
+ int ret;
+
+ if ((event != CPUFREQ_ADJUST) || (notify_state == -1))
+ return 0;
+
+ if (notify_state > 0) {
+ if (notify_dev->type == THERMAL_TRIP_PASSIVE) {
+ table = cpufreq_frequency_get_table(policy->cpu);
+ ret = get_freq_state_count(notify_dev);
+ if (notify_state < ret)
+ max_freq = table[notify_state - 1].frequency;
+ } else {
+ max_freq = notify_table->freq_clip_max;
+ }
+
+ if (per_cpu(max_policy_freq, policy->cpu) == 0)
+ per_cpu(max_policy_freq, policy->cpu) = policy->max;
+ } else {
+ if (per_cpu(max_policy_freq, policy->cpu) != 0) {
+ max_freq = per_cpu(max_policy_freq, policy->cpu);
+ per_cpu(max_policy_freq, policy->cpu) = 0;
+ } else {
+ max_freq = policy->max;
+ }
+ }
+
+ /* Never exceed user_policy.max*/
+ if (max_freq > policy->user_policy.max)
+ max_freq = policy->user_policy.max;
+
+ if (policy->max != max_freq)
+ cpufreq_verify_within_limits(policy, 0, max_freq);
+
+ return 0;
+}
+
+/*
+ * cpufreq cooling device callback functions
+ */
+static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ int ret = -EINVAL;
+ struct cpufreq_cooling_device *cpufreq_device;
+
+ mutex_lock(&cooling_cpufreq_lock);
+ list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
+ if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
+ *state = get_freq_state_count(cpufreq_device);
+ ret = 0;
+ break;
+ }
+ }
+ mutex_unlock(&cooling_cpufreq_lock);
+ return ret;
+}
+
+static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ int ret = -EINVAL;
+ struct cpufreq_cooling_device *cpufreq_device;
+
+ mutex_lock(&cooling_cpufreq_lock);
+ list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
+ if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
+ *state = cpufreq_device->cpufreq_cur_state;
+ ret = 0;
+ break;
+ }
+ }
+ mutex_unlock(&cooling_cpufreq_lock);
+ return ret;
+}
+
+/*This cooling may be as PASSIVE/ACTIVE type*/
+static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ int ret = -EINVAL;
+ struct cpufreq_cooling_device *cpufreq_device;
+
+ mutex_lock(&cooling_cpufreq_lock);
+ list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
+ if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
+ ret = 0;
+ break;
+ }
+ }
+ mutex_unlock(&cooling_cpufreq_lock);
+
+ if (!ret)
+ ret = cpufreq_apply_cooling(cpufreq_device, state);
+
+ return ret;
+}
+
+/* bind cpufreq callbacks to cpufreq cooling device */
+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,
+};
+
+static struct notifier_block thermal_cpufreq_notifier_block = {
+ .notifier_call = cpufreq_thermal_notifier,
+};
+
+struct thermal_cooling_device *cpufreq_cooling_register(
+ struct freq_clip_table *tab_ptr, unsigned int tab_size,
+ const struct cpumask *mask_val, enum thermal_trip_type type)
+{
+ struct thermal_cooling_device *cool_dev;
+ struct cpufreq_cooling_device *cpufreq_dev = NULL;
+ unsigned int cpufreq_dev_count = 0;
+ char dev_name[THERMAL_NAME_LENGTH];
+ int ret = 0, id = 0, i;
+
+ if ((tab_ptr == NULL || tab_size == 0) && type != THERMAL_TRIP_PASSIVE)
+ return ERR_PTR(-EINVAL);
+
+ if ((tab_ptr != NULL || tab_size != 0) && type == THERMAL_TRIP_PASSIVE)
+ return ERR_PTR(-EINVAL);
+
+ list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node)
+ cpufreq_dev_count++;
+
+ cpufreq_dev =
+ kzalloc(sizeof(struct cpufreq_cooling_device), GFP_KERNEL);
+
+ if (!cpufreq_dev)
+ return ERR_PTR(-ENOMEM);
+
+ if (cpufreq_dev_count == 0) {
+ notify_table = kzalloc(sizeof(struct freq_clip_table),
+ GFP_KERNEL);
+ if (!notify_table) {
+ kfree(cpufreq_dev);
+ return ERR_PTR(-ENOMEM);
+ }
+ }
+
+ cpufreq_dev->tab_ptr = tab_ptr;
+ cpufreq_dev->tab_size = tab_size;
+ cpufreq_dev->allowed_cpus = mask_val;
+ cpufreq_dev->type = type;
+ cpufreq_dev->cpufreq_max_state = 0;
+
+ if (type != THERMAL_TRIP_PASSIVE)
+ cpufreq_dev->cpufreq_max_state = tab_size;
+
+ /* Initialize all the tab_ptr->mask_val to the passed mask_val */
+ for (i = 0; i < tab_size; i++)
+ ((struct freq_clip_table *)&tab_ptr[i])->mask_val = mask_val;
+
+ ret = get_idr(&cpufreq_idr, &cooling_cpufreq_lock, &cpufreq_dev->id);
+ if (ret) {
+ kfree(cpufreq_dev);
+ return ERR_PTR(-EINVAL);
+ }
+
+ sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
+
+ cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
+ &cpufreq_cooling_ops);
+ if (!cool_dev) {
+ release_idr(&cpufreq_idr, &cooling_cpufreq_lock,
+ cpufreq_dev->id);
+ kfree(cpufreq_dev);
+ return ERR_PTR(-EINVAL);
+ }
+ cpufreq_dev->id = id;
+ cpufreq_dev->cool_dev = cool_dev;
+ mutex_lock(&cooling_cpufreq_lock);
+ list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list);
+ mutex_unlock(&cooling_cpufreq_lock);
+
+ /*Register the notifier for first cpufreq cooling device*/
+ if (cpufreq_dev_count == 0)
+ cpufreq_register_notifier(&thermal_cpufreq_notifier_block,
+ CPUFREQ_POLICY_NOTIFIER);
+ return cool_dev;
+}
+EXPORT_SYMBOL(cpufreq_cooling_register);
+
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+ struct cpufreq_cooling_device *cpufreq_dev = NULL;
+ unsigned int cpufreq_dev_count = 0;
+
+ mutex_lock(&cooling_cpufreq_lock);
+ list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) {
+ if (cpufreq_dev && cpufreq_dev->cool_dev == cdev)
+ break;
+ cpufreq_dev_count++;
+ }
+
+ if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) {
+ mutex_unlock(&cooling_cpufreq_lock);
+ return;
+ }
+
+ list_del(&cpufreq_dev->node);
+ mutex_unlock(&cooling_cpufreq_lock);
+
+ /*Unregister the notifier for the last cpufreq cooling device*/
+ if (cpufreq_dev_count == 1) {
+ cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block,
+ CPUFREQ_POLICY_NOTIFIER);
+ kfree(notify_table);
+ }
+
+ thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
+ release_idr(&cpufreq_idr, &cooling_cpufreq_lock, cpufreq_dev->id);
+ kfree(cpufreq_dev);
+}
+EXPORT_SYMBOL(cpufreq_cooling_unregister);
+
+/*
+ * cpu hotplug cooling device callback functions
+ */
+static int cpuhotplug_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ int ret = -EINVAL;
+ struct hotplug_cooling_device *hotplug_dev;
+
+ /*
+ * This cooling device may be of type ACTIVE, so state field can
+ * be 0 or 1
+ */
+ mutex_lock(&cooling_cpuhotplug_lock);
+ list_for_each_entry(hotplug_dev, &cooling_cpuhotplug_list, node) {
+ if (hotplug_dev && hotplug_dev->cool_dev == cdev) {
+ *state = 1;
+ ret = 0;
+ break;
+ }
+ }
+ mutex_unlock(&cooling_cpuhotplug_lock);
+ return ret;
+}
+
+static int cpuhotplug_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ int ret = -EINVAL;
+ struct hotplug_cooling_device *hotplug_dev;
+
+ mutex_lock(&cooling_cpuhotplug_lock);
+ list_for_each_entry(hotplug_dev, &cooling_cpuhotplug_list, node) {
+ if (hotplug_dev && hotplug_dev->cool_dev == cdev) {
+ *state = hotplug_dev->hotplug_state;
+ ret = 0;
+ break;
+ }
+ }
+ mutex_unlock(&cooling_cpuhotplug_lock);
+ return ret;
+}
+
+/*This cooling may be as ACTIVE type*/
+static int cpuhotplug_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ int cpuid, this_cpu = smp_processor_id();
+ struct hotplug_cooling_device *hotplug_dev;
+
+ mutex_lock(&cooling_cpuhotplug_lock);
+ list_for_each_entry(hotplug_dev, &cooling_cpuhotplug_list, node)
+ if (hotplug_dev && hotplug_dev->cool_dev == cdev)
+ break;
+
+ mutex_unlock(&cooling_cpuhotplug_lock);
+ if (!hotplug_dev || hotplug_dev->cool_dev != cdev)
+ return -EINVAL;
+
+ if (hotplug_dev->hotplug_state == state)
+ return 0;
+
+ /*
+ * This cooling device may be of type ACTIVE, so state field can
+ * be 0 or 1
+ */
+ if (state == 1) {
+ for_each_cpu(cpuid, hotplug_dev->allowed_cpus) {
+ if (cpu_online(cpuid) && (cpuid != this_cpu))
+ cpu_down(cpuid);
+ }
+ } else if (state == 0) {
+ for_each_cpu(cpuid, hotplug_dev->allowed_cpus) {
+ if (!cpu_online(cpuid) && (cpuid != this_cpu))
+ cpu_up(cpuid);
+ }
+ } else {
+ return -EINVAL;
+ }
+
+ hotplug_dev->hotplug_state = state;
+
+ return 0;
+}
+/* bind hotplug callbacks to cpu hotplug cooling device */
+static struct thermal_cooling_device_ops cpuhotplug_cooling_ops = {
+ .get_max_state = cpuhotplug_get_max_state,
+ .get_cur_state = cpuhotplug_get_cur_state,
+ .set_cur_state = cpuhotplug_set_cur_state,
+};
+
+struct thermal_cooling_device *cpuhotplug_cooling_register(
+ const struct cpumask *mask_val)
+{
+ struct thermal_cooling_device *cool_dev;
+ struct hotplug_cooling_device *hotplug_dev;
+ int ret = 0;
+ char dev_name[THERMAL_NAME_LENGTH];
+
+ hotplug_dev =
+ kzalloc(sizeof(struct hotplug_cooling_device), GFP_KERNEL);
+
+ if (!hotplug_dev)
+ return ERR_PTR(-ENOMEM);
+
+ ret = get_idr(&cpuhotplug_idr, &cooling_cpuhotplug_lock,
+ &hotplug_dev->id);
+ if (ret) {
+ kfree(hotplug_dev);
+ return ERR_PTR(-EINVAL);
+ }
+
+ sprintf(dev_name, "cpu-hotplug-%u", hotplug_dev->id);
+
+ hotplug_dev->hotplug_state = 0;
+ hotplug_dev->allowed_cpus = mask_val;
+ cool_dev = thermal_cooling_device_register(dev_name, hotplug_dev,
+ &cpuhotplug_cooling_ops);
+ if (!cool_dev) {
+ release_idr(&cpuhotplug_idr, &cooling_cpuhotplug_lock,
+ hotplug_dev->id);
+ kfree(hotplug_dev);
+ return ERR_PTR(-EINVAL);
+ }
+
+ hotplug_dev->cool_dev = cool_dev;
+ mutex_lock(&cooling_cpuhotplug_lock);
+ list_add_tail(&hotplug_dev->node, &cooling_cpuhotplug_list);
+ mutex_unlock(&cooling_cpuhotplug_lock);
+
+ return cool_dev;
+}
+EXPORT_SYMBOL(cpuhotplug_cooling_register);
+
+void cpuhotplug_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+ struct hotplug_cooling_device *hotplug_dev = NULL;
+
+ mutex_lock(&cooling_cpuhotplug_lock);
+ list_for_each_entry(hotplug_dev, &cooling_cpuhotplug_list, node)
+ if (hotplug_dev && hotplug_dev->cool_dev == cdev)
+ break;
+
+ if (!hotplug_dev || hotplug_dev->cool_dev != cdev) {
+ mutex_unlock(&cooling_cpuhotplug_lock);
+ return;
+ }
+
+ list_del(&hotplug_dev->node);
+ mutex_unlock(&cooling_cpuhotplug_lock);
+ thermal_cooling_device_unregister(hotplug_dev->cool_dev);
+ release_idr(&cpuhotplug_idr, &cooling_cpuhotplug_lock,
+ hotplug_dev->id);
+ kfree(hotplug_dev);
+}
+EXPORT_SYMBOL(cpuhotplug_cooling_unregister);
diff --git a/drivers/hwmon/exynos4_tmu.c b/drivers/thermal/exynos4_thermal.c
index f2359a0093bd..a74c455c1568 100644
--- a/drivers/hwmon/exynos4_tmu.c
+++ b/drivers/thermal/exynos4_thermal.c
@@ -1,8 +1,9 @@
/*
- * exynos4_tmu.c - Samsung EXYNOS4 TMU (Thermal Management Unit)
+ * exynos4_thermal.c - Samsung EXYNOS4 TMU (Thermal Management Unit)
*
* Copyright (C) 2011 Samsung Electronics
* Donggeun Kim <dg77.kim@samsung.com>
+ * Amit Daniel Kachhap <amit.kachhap@linaro.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,11 +33,11 @@
#include <linux/kobject.h>
#include <linux/io.h>
#include <linux/mutex.h>
-
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
-
+#include <linux/err.h>
#include <linux/platform_data/exynos4_tmu.h>
+#include <linux/thermal.h>
+#include <linux/cpufreq.h>
+#include <linux/cpu_cooling.h>
#define EXYNOS4_TMU_REG_TRIMINFO 0x0
#define EXYNOS4_TMU_REG_CONTROL 0x20
@@ -68,9 +69,24 @@
#define EXYNOS4_TMU_TRIG_LEVEL3_MASK 0x1000
#define EXYNOS4_TMU_INTCLEAR_VAL 0x1111
+#define SENSOR_NAME_LEN 16
+#define MAX_TRIP_COUNT 8
+#define MAX_COOLING_DEVICE 4
+
+#define ACTIVE_INTERVAL 500
+#define IDLE_INTERVAL 10000
+
+/* CPU Zone information */
+#define PANIC_ZONE 4
+#define WARN_ZONE 3
+#define MONITOR_ZONE 2
+#define SAFE_ZONE 1
+
+#define GET_ZONE(trip) (trip + 2)
+#define GET_TRIP(zone) (zone - 2)
+
struct exynos4_tmu_data {
struct exynos4_tmu_platform_data *pdata;
- struct device *hwmon_dev;
struct resource *mem;
void __iomem *base;
int irq;
@@ -80,6 +96,296 @@ struct exynos4_tmu_data {
u8 temp_error1, temp_error2;
};
+struct thermal_trip_point_conf {
+ int trip_val[MAX_TRIP_COUNT];
+ int trip_count;
+};
+
+struct thermal_cooling_conf {
+ struct freq_clip_table freq_data[MAX_TRIP_COUNT];
+ int freq_clip_count;
+};
+
+struct thermal_sensor_conf {
+ char name[SENSOR_NAME_LEN];
+ int (*read_temperature)(void *data);
+ struct thermal_trip_point_conf trip_data;
+ struct thermal_cooling_conf cooling_data;
+ void *private_data;
+};
+
+struct exynos4_thermal_zone {
+ enum thermal_device_mode mode;
+ struct thermal_zone_device *therm_dev;
+ struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE];
+ unsigned int cool_dev_size;
+ struct platform_device *exynos4_dev;
+ struct thermal_sensor_conf *sensor_conf;
+};
+
+static struct exynos4_thermal_zone *th_zone;
+static void exynos4_unregister_thermal(void);
+static int exynos4_register_thermal(struct thermal_sensor_conf *sensor_conf);
+
+/* Get mode callback functions for thermal zone */
+static int exynos4_get_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode *mode)
+{
+ if (th_zone)
+ *mode = th_zone->mode;
+ return 0;
+}
+
+/* Set mode callback functions for thermal zone */
+static int exynos4_set_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode mode)
+{
+ if (!th_zone->therm_dev) {
+ pr_notice("thermal zone not registered\n");
+ return 0;
+ }
+
+ mutex_lock(&th_zone->therm_dev->lock);
+
+ if (mode == THERMAL_DEVICE_ENABLED)
+ th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
+ else
+ th_zone->therm_dev->polling_delay = 0;
+
+ mutex_unlock(&th_zone->therm_dev->lock);
+
+ th_zone->mode = mode;
+ thermal_zone_device_update(th_zone->therm_dev);
+ pr_info("thermal polling set for duration=%d msec\n",
+ th_zone->therm_dev->polling_delay);
+ return 0;
+}
+
+/*
+ * This function may be called from interrupt based temperature sensor
+ * when threshold is changed.
+ */
+static void exynos4_report_trigger(void)
+{
+ unsigned int i;
+ char data[2];
+ char *envp[] = { data, NULL };
+
+ if (!th_zone || !th_zone->therm_dev)
+ return;
+
+ thermal_zone_device_update(th_zone->therm_dev);
+
+ mutex_lock(&th_zone->therm_dev->lock);
+ /* Find the level for which trip happened */
+ for (i = 0; i < th_zone->sensor_conf->trip_data.trip_count; i++) {
+ if (th_zone->therm_dev->last_temperature <
+ th_zone->sensor_conf->trip_data.trip_val[i] * 1000)
+ break;
+ }
+
+ if (th_zone->mode == THERMAL_DEVICE_ENABLED) {
+ if (i > 0)
+ th_zone->therm_dev->polling_delay = ACTIVE_INTERVAL;
+ else
+ th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
+ }
+
+ sprintf(data, "%u", i);
+ kobject_uevent_env(&th_zone->therm_dev->device.kobj, KOBJ_CHANGE, envp);
+ mutex_unlock(&th_zone->therm_dev->lock);
+}
+
+/* Get trip type callback functions for thermal zone */
+static int exynos4_get_trip_type(struct thermal_zone_device *thermal, int trip,
+ enum thermal_trip_type *type)
+{
+ switch (GET_ZONE(trip)) {
+ case MONITOR_ZONE:
+ case WARN_ZONE:
+ *type = THERMAL_TRIP_STATE_INSTANCE;
+ break;
+ case PANIC_ZONE:
+ *type = THERMAL_TRIP_CRITICAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/* Get trip temperature callback functions for thermal zone */
+static int exynos4_get_trip_temp(struct thermal_zone_device *thermal, int trip,
+ unsigned long *temp)
+{
+ if (trip < 0 || trip > 2)
+ return -EINVAL;
+
+ *temp = th_zone->sensor_conf->trip_data.trip_val[trip];
+ /* convert the temperature into millicelsius */
+ *temp = *temp * 1000;
+
+ return 0;
+}
+
+/* Get critical temperature callback functions for thermal zone */
+static int exynos4_get_crit_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ int ret = 0;
+ /* Panic zone */
+ ret = exynos4_get_trip_temp(thermal, GET_TRIP(PANIC_ZONE), temp);
+ return ret;
+}
+
+/* Bind callback functions for thermal zone */
+static int exynos4_bind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ int ret = 0;
+
+ /* if the cooling device is the one from exynos4 bind it */
+ if (cdev != th_zone->cool_dev[0])
+ return 0;
+
+ if (thermal_zone_bind_cooling_device(thermal, 0, cdev)) {
+ pr_err("error binding cooling dev inst 0\n");
+ return -EINVAL;
+ }
+ if (thermal_zone_bind_cooling_device(thermal, 1, cdev)) {
+ pr_err("error binding cooling dev inst 1\n");
+ ret = -EINVAL;
+ goto error_bind1;
+ }
+
+ return ret;
+error_bind1:
+ thermal_zone_unbind_cooling_device(thermal, 0, cdev);
+ return ret;
+}
+
+/* Unbind callback functions for thermal zone */
+static int exynos4_unbind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ int ret = 0;
+
+ if (cdev != th_zone->cool_dev[0])
+ return 0;
+
+ if (thermal_zone_unbind_cooling_device(thermal, 0, cdev)) {
+ pr_err("error unbinding cooling dev inst 0\n");
+ ret = -EINVAL;
+ }
+ if (thermal_zone_unbind_cooling_device(thermal, 1, cdev)) {
+ pr_err("error unbinding cooling dev inst 1\n");
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+/* Get temperature callback functions for thermal zone */
+static int exynos4_get_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ void *data;
+
+ if (!th_zone->sensor_conf) {
+ pr_info("Temperature sensor not initialised\n");
+ return -EINVAL;
+ }
+ data = th_zone->sensor_conf->private_data;
+ *temp = th_zone->sensor_conf->read_temperature(data);
+ /* convert the temperature into millicelsius */
+ *temp = *temp * 1000;
+ return 0;
+}
+
+/* Operation callback functions for thermal zone */
+static struct thermal_zone_device_ops exynos4_dev_ops = {
+ .bind = exynos4_bind,
+ .unbind = exynos4_unbind,
+ .get_temp = exynos4_get_temp,
+ .get_mode = exynos4_get_mode,
+ .set_mode = exynos4_set_mode,
+ .get_trip_type = exynos4_get_trip_type,
+ .get_trip_temp = exynos4_get_trip_temp,
+ .get_crit_temp = exynos4_get_crit_temp,
+};
+
+/* Register with the in-kernel thermal management */
+static int exynos4_register_thermal(struct thermal_sensor_conf *sensor_conf)
+{
+ int ret, count, tab_size;
+ struct freq_clip_table *tab_ptr;
+
+ if (!sensor_conf || !sensor_conf->read_temperature) {
+ pr_err("Temperature sensor not initialised\n");
+ return -EINVAL;
+ }
+
+ th_zone = kzalloc(sizeof(struct exynos4_thermal_zone), GFP_KERNEL);
+ if (!th_zone) {
+ ret = -ENOMEM;
+ goto err_unregister;
+ }
+
+ th_zone->sensor_conf = sensor_conf;
+
+ tab_ptr = (struct freq_clip_table *)sensor_conf->cooling_data.freq_data;
+ tab_size = sensor_conf->cooling_data.freq_clip_count;
+
+ /* Register the cpufreq cooling device */
+ th_zone->cool_dev_size = 1;
+ count = 0;
+ th_zone->cool_dev[count] = cpufreq_cooling_register(
+ (struct freq_clip_table *)&(tab_ptr[count]),
+ tab_size, cpumask_of(0), THERMAL_TRIP_STATE_INSTANCE);
+
+ if (IS_ERR(th_zone->cool_dev[count])) {
+ pr_err("Failed to register cpufreq cooling device\n");
+ ret = -EINVAL;
+ th_zone->cool_dev_size = 0;
+ goto err_unregister;
+ }
+
+ th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name,
+ 3, NULL, &exynos4_dev_ops, 0, 0, 0, IDLE_INTERVAL);
+
+ if (IS_ERR(th_zone->therm_dev)) {
+ pr_err("Failed to register thermal zone device\n");
+ ret = -EINVAL;
+ goto err_unregister;
+ }
+ th_zone->mode = THERMAL_DEVICE_ENABLED;
+
+ pr_info("Exynos: Kernel Thermal management registered\n");
+
+ return 0;
+
+err_unregister:
+ exynos4_unregister_thermal();
+ return ret;
+}
+
+/* Un-Register with the in-kernel thermal management */
+static void exynos4_unregister_thermal(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < th_zone->cool_dev_size; i++) {
+ if (th_zone && th_zone->cool_dev[i])
+ cpufreq_cooling_unregister(th_zone->cool_dev[i]);
+ }
+
+ if (th_zone && th_zone->therm_dev)
+ thermal_zone_device_unregister(th_zone->therm_dev);
+
+ kfree(th_zone);
+
+ pr_info("Exynos: Kernel Thermal management unregistered\n");
+}
+
/*
* TMU treats temperature as a mapped temperature code.
* The temperature is converted differently depending on the calibration type.
@@ -246,12 +552,10 @@ static void exynos4_tmu_work(struct work_struct *work)
writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR);
- kobject_uevent(&data->hwmon_dev->kobj, KOBJ_CHANGE);
-
- enable_irq(data->irq);
-
clk_disable(data->clk);
mutex_unlock(&data->lock);
+ exynos4_report_trigger();
+ enable_irq(data->irq);
}
static irqreturn_t exynos4_tmu_irq(int irq, void *id)
@@ -264,92 +568,62 @@ static irqreturn_t exynos4_tmu_irq(int irq, void *id)
return IRQ_HANDLED;
}
-static ssize_t exynos4_tmu_show_name(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- return sprintf(buf, "exynos4-tmu\n");
-}
-
-static ssize_t exynos4_tmu_show_temp(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct exynos4_tmu_data *data = dev_get_drvdata(dev);
- int ret;
-
- ret = exynos4_tmu_read(data);
- if (ret < 0)
- return ret;
-
- /* convert from degree Celsius to millidegree Celsius */
- return sprintf(buf, "%d\n", ret * 1000);
-}
-
-static ssize_t exynos4_tmu_show_alarm(struct device *dev,
- struct device_attribute *devattr, char *buf)
-{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct exynos4_tmu_data *data = dev_get_drvdata(dev);
- struct exynos4_tmu_platform_data *pdata = data->pdata;
- int temp;
- unsigned int trigger_level;
-
- temp = exynos4_tmu_read(data);
- if (temp < 0)
- return temp;
+static struct thermal_sensor_conf exynos4_sensor_conf = {
+ .name = "exynos4-therm",
+ .read_temperature = (int (*)(void *))exynos4_tmu_read,
+};
- trigger_level = pdata->threshold + pdata->trigger_levels[attr->index];
+#if defined(CONFIG_CPU_EXYNOS4210)
+static struct exynos4_tmu_platform_data exynos4_default_tmu_data = {
+ .threshold = 80,
+ .trigger_levels[0] = 2,
+ .trigger_levels[1] = 5,
+ .trigger_levels[2] = 20,
+ .trigger_levels[3] = 30,
+ .trigger_level0_en = 1,
+ .trigger_level1_en = 1,
+ .trigger_level2_en = 1,
+ .trigger_level3_en = 1,
+ .gain = 15,
+ .reference_voltage = 7,
+ .cal_type = TYPE_ONE_POINT_TRIMMING,
+ .freq_tab[0] = {
+ .freq_clip_max = 800 * 1000,
+ },
+ .freq_tab[1] = {
+ .freq_clip_max = 200 * 1000,
+ },
+ .freq_tab_count = 2,
+};
+#define EXYNOS4210_TMU_DRV_DATA ((kernel_ulong_t)&exynos4_default_tmu_data)
+#else
+#define EXYNOS4210_TMU_DRV_DATA ((kernel_ulong_t)NULL)
+#endif
- return sprintf(buf, "%d\n", !!(temp > trigger_level));
-}
+static struct platform_device_id exynos4_tmu_driver_ids[] = {
+ {
+ .name = "exynos4-tmu",
+ .driver_data = EXYNOS4210_TMU_DRV_DATA,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, exynos4_tmu_driver_ids);
-static ssize_t exynos4_tmu_show_level(struct device *dev,
- struct device_attribute *devattr, char *buf)
+static inline struct exynos4_tmu_platform_data *exynos4_get_driver_data(
+ struct platform_device *pdev)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct exynos4_tmu_data *data = dev_get_drvdata(dev);
- struct exynos4_tmu_platform_data *pdata = data->pdata;
- unsigned int temp = pdata->threshold +
- pdata->trigger_levels[attr->index];
-
- return sprintf(buf, "%u\n", temp * 1000);
+ return (struct exynos4_tmu_platform_data *)
+ platform_get_device_id(pdev)->driver_data;
}
-static DEVICE_ATTR(name, S_IRUGO, exynos4_tmu_show_name, NULL);
-static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, exynos4_tmu_show_temp, NULL, 0);
-
-static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO,
- exynos4_tmu_show_alarm, NULL, 1);
-static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO,
- exynos4_tmu_show_alarm, NULL, 2);
-static SENSOR_DEVICE_ATTR(temp1_emergency_alarm, S_IRUGO,
- exynos4_tmu_show_alarm, NULL, 3);
-
-static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, exynos4_tmu_show_level, NULL, 1);
-static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, exynos4_tmu_show_level, NULL, 2);
-static SENSOR_DEVICE_ATTR(temp1_emergency, S_IRUGO,
- exynos4_tmu_show_level, NULL, 3);
-
-static struct attribute *exynos4_tmu_attributes[] = {
- &dev_attr_name.attr,
- &sensor_dev_attr_temp1_input.dev_attr.attr,
- &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp1_emergency_alarm.dev_attr.attr,
- &sensor_dev_attr_temp1_max.dev_attr.attr,
- &sensor_dev_attr_temp1_crit.dev_attr.attr,
- &sensor_dev_attr_temp1_emergency.dev_attr.attr,
- NULL,
-};
-
-static const struct attribute_group exynos4_tmu_attr_group = {
- .attrs = exynos4_tmu_attributes,
-};
-
static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
{
struct exynos4_tmu_data *data;
struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data;
- int ret;
+ int ret, i;
+
+ if (!pdata)
+ pdata = exynos4_get_driver_data(pdev);
if (!pdata) {
dev_err(&pdev->dev, "No platform init data supplied.\n");
@@ -418,25 +692,27 @@ static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
goto err_clk;
}
- ret = sysfs_create_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
- if (ret) {
- dev_err(&pdev->dev, "Failed to create sysfs group\n");
- goto err_clk;
- }
+ exynos4_tmu_control(pdev, true);
- data->hwmon_dev = hwmon_device_register(&pdev->dev);
- if (IS_ERR(data->hwmon_dev)) {
- ret = PTR_ERR(data->hwmon_dev);
- dev_err(&pdev->dev, "Failed to register hwmon device\n");
- goto err_create_group;
- }
+ /*Register the sensor with thermal management interface*/
+ (&exynos4_sensor_conf)->private_data = data;
+ exynos4_sensor_conf.trip_data.trip_count = 3;
+ for (i = 0; i < exynos4_sensor_conf.trip_data.trip_count; i++)
+ exynos4_sensor_conf.trip_data.trip_val[i] =
+ pdata->threshold + pdata->trigger_levels[i + 1];
- exynos4_tmu_control(pdev, true);
+ exynos4_sensor_conf.cooling_data.freq_clip_count =
+ pdata->freq_tab_count;
+ for (i = 0; i < pdata->freq_tab_count; i++)
+ exynos4_sensor_conf.cooling_data.freq_data[i].freq_clip_max =
+ pdata->freq_tab[i].freq_clip_max;
+ ret = exynos4_register_thermal(&exynos4_sensor_conf);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register thermal interface\n");
+ goto err_clk;
+ }
return 0;
-
-err_create_group:
- sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
err_clk:
platform_set_drvdata(pdev, NULL);
clk_put(data->clk);
@@ -458,8 +734,7 @@ static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
exynos4_tmu_control(pdev, false);
- hwmon_device_unregister(data->hwmon_dev);
- sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
+ exynos4_unregister_thermal();
clk_put(data->clk);
@@ -504,6 +779,7 @@ static struct platform_driver exynos4_tmu_driver = {
.remove = __devexit_p(exynos4_tmu_remove),
.suspend = exynos4_tmu_suspend,
.resume = exynos4_tmu_resume,
+ .id_table = exynos4_tmu_driver_ids,
};
module_platform_driver(exynos4_tmu_driver);
diff --git a/drivers/thermal/imx6q_thermal.c b/drivers/thermal/imx6q_thermal.c
new file mode 100644
index 000000000000..76b7ba11bd19
--- /dev/null
+++ b/drivers/thermal/imx6q_thermal.c
@@ -0,0 +1,541 @@
+/*
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ * Copyright 2012 Linaro Ltd.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/* i.MX6Q Thermal Implementation */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/thermal.h>
+#include <linux/io.h>
+#include <linux/syscalls.h>
+#include <linux/cpufreq.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/smp.h>
+#include <linux/cpu_cooling.h>
+#include <linux/platform_device.h>
+
+/* register define of anatop */
+#define HW_ANADIG_ANA_MISC0 0x00000150
+#define HW_ANADIG_ANA_MISC0_SET 0x00000154
+#define HW_ANADIG_ANA_MISC0_CLR 0x00000158
+#define HW_ANADIG_ANA_MISC0_TOG 0x0000015c
+#define BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF 0x00000008
+
+#define HW_ANADIG_TEMPSENSE0 0x00000180
+#define HW_ANADIG_TEMPSENSE0_SET 0x00000184
+#define HW_ANADIG_TEMPSENSE0_CLR 0x00000188
+#define HW_ANADIG_TEMPSENSE0_TOG 0x0000018c
+
+#define BP_ANADIG_TEMPSENSE0_TEMP_VALUE 8
+#define BM_ANADIG_TEMPSENSE0_TEMP_VALUE 0x000FFF00
+#define BM_ANADIG_TEMPSENSE0_FINISHED 0x00000004
+#define BM_ANADIG_TEMPSENSE0_MEASURE_TEMP 0x00000002
+#define BM_ANADIG_TEMPSENSE0_POWER_DOWN 0x00000001
+
+#define HW_ANADIG_TEMPSENSE1 0x00000190
+#define HW_ANADIG_TEMPSENSE1_SET 0x00000194
+#define HW_ANADIG_TEMPSENSE1_CLR 0x00000198
+#define BP_ANADIG_TEMPSENSE1_MEASURE_FREQ 0
+#define BM_ANADIG_TEMPSENSE1_MEASURE_FREQ 0x0000FFFF
+
+#define HW_OCOTP_ANA1 0x000004E0
+
+#define IMX6Q_THERMAL_POLLING_FREQUENCY_MS 1000
+
+#define IMX6Q_THERMAL_STATE_TRP_PTS 3
+/* assumption: always one critical trip point */
+#define IMX6Q_THERMAL_TOTAL_TRP_PTS (IMX6Q_THERMAL_STATE_TRP_PTS + 1)
+
+struct th_sys_trip_point {
+ u32 temp; /* in celcius */
+ enum thermal_trip_type type;
+};
+
+struct imx6q_sensor_data {
+ int c1, c2;
+ char name[16];
+ u8 meas_delay; /* in milliseconds */
+ u32 noise_margin; /* in millicelcius */
+ int last_temp; /* in millicelcius */
+ /*
+ * When noise filtering, if consecutive measurements are each higher
+ * up to consec_high_limit times, assume changing temperature readings
+ * to be valid and not noise.
+ */
+ u32 consec_high_limit;
+};
+
+struct imx6q_thermal_data {
+ struct th_sys_trip_point trp_pts[IMX6Q_THERMAL_TOTAL_TRP_PTS];
+ struct freq_clip_table freq_tab[IMX6Q_THERMAL_STATE_TRP_PTS];
+};
+
+struct imx6q_thermal_zone {
+ struct thermal_zone_device *therm_dev;
+ struct thermal_cooling_device *cool_dev;
+ struct imx6q_sensor_data *sensor_data;
+ struct imx6q_thermal_data *thermal_data;
+ struct thermal_zone_device_ops dev_ops;
+};
+
+static struct imx6q_sensor_data g_sensor_data __initdata = {
+ .name = "imx6q-temp_sens",
+ .meas_delay = 1, /* in milliseconds */
+ .noise_margin = 3000,
+ .consec_high_limit = 3,
+};
+
+/*
+ * This data defines the various trip points that will trigger action
+ * when crossed.
+ */
+static struct imx6q_thermal_data g_thermal_data __initdata = {
+ .trp_pts[0] = {
+ .temp = 85000,
+ .type = THERMAL_TRIP_STATE_INSTANCE,
+ },
+ .freq_tab[0] = {
+ .freq_clip_max = 800 * 1000,
+ },
+ .trp_pts[1] = {
+ .temp = 90000,
+ .type = THERMAL_TRIP_STATE_INSTANCE,
+ },
+ .freq_tab[1] = {
+ .freq_clip_max = 400 * 1000,
+ },
+ .trp_pts[2] = {
+ .temp = 95000,
+ .type = THERMAL_TRIP_STATE_INSTANCE,
+ },
+ .freq_tab[2] = {
+ .freq_clip_max = 200 * 1000,
+ },
+ .trp_pts[3] = {
+ .temp = 100000,
+ .type = THERMAL_TRIP_CRITICAL,
+ },
+};
+
+static int th_sys_get_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp);
+
+static struct imx6q_thermal_zone *th_zone;
+static void __iomem *anatop_base, *ocotp_base;
+
+static inline int imx6q_get_temp(int *temp, struct imx6q_sensor_data *sd)
+{
+ unsigned int n_meas;
+ unsigned int reg;
+
+ do {
+ /*
+ * For now we only using single measure. Every time we measure
+ * the temperature, we will power on/down the anadig module
+ */
+ writel_relaxed(BM_ANADIG_TEMPSENSE0_POWER_DOWN,
+ anatop_base + HW_ANADIG_TEMPSENSE0_CLR);
+
+ writel_relaxed(BM_ANADIG_TEMPSENSE0_MEASURE_TEMP,
+ anatop_base + HW_ANADIG_TEMPSENSE0_SET);
+
+ /*
+ * According to imx6q temp sensor designers,
+ * it may require up to ~17us to complete
+ * a measurement. But this timing isn't checked on every part
+ * and specified in the datasheet, so we check the
+ * 'finished' status bit to be sure of completion.
+ */
+ msleep(sd->meas_delay);
+
+ reg = readl_relaxed(anatop_base + HW_ANADIG_TEMPSENSE0);
+
+ writel_relaxed(BM_ANADIG_TEMPSENSE0_MEASURE_TEMP,
+ anatop_base + HW_ANADIG_TEMPSENSE0_CLR);
+
+ writel_relaxed(BM_ANADIG_TEMPSENSE0_POWER_DOWN,
+ anatop_base + HW_ANADIG_TEMPSENSE0_SET);
+
+ } while (!(reg & BM_ANADIG_TEMPSENSE0_FINISHED) &&
+ (reg & BM_ANADIG_TEMPSENSE0_TEMP_VALUE));
+
+ n_meas = (reg & BM_ANADIG_TEMPSENSE0_TEMP_VALUE)
+ >> BP_ANADIG_TEMPSENSE0_TEMP_VALUE;
+
+ /* See imx6q_thermal_process_fuse_data for forumla derivation. */
+ *temp = sd->c2 + (sd->c1 * n_meas);
+
+ pr_debug("Temperature: %d\n", *temp / 1000);
+
+ return 0;
+}
+
+static int th_sys_get_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ int i, total = 0, tmp = 0;
+ const u8 loop = 5;
+ u32 consec_high = 0;
+
+ struct imx6q_sensor_data *sd;
+
+ sd = ((struct imx6q_thermal_zone *)thermal->devdata)->sensor_data;
+
+ /*
+ * Measure and handle noise
+ *
+ * While the imx6q temperature sensor is designed to minimize being
+ * affected by system noise, it's safest to run sanity checks and
+ * perform any necessary filitering.
+ */
+ for (i = 0; (sd->noise_margin) && (i < loop); i++) {
+ imx6q_get_temp(&tmp, sd);
+
+ if ((abs(tmp - sd->last_temp) <= sd->noise_margin) ||
+ (consec_high >= sd->consec_high_limit)) {
+ sd->last_temp = tmp;
+ i = 0;
+ break;
+ }
+ if (tmp > sd->last_temp)
+ consec_high++;
+
+ /*
+ * ignore first measurement as the previous measurement was
+ * a long time ago.
+ */
+ if (i)
+ total += tmp;
+
+ sd->last_temp = tmp;
+ }
+
+ if (sd->noise_margin && i)
+ tmp = total / (loop - 1);
+
+ /*
+ * The thermal framework code stores temperature in unsigned long. Also,
+ * it has references to "millicelcius" which limits the lowest
+ * temperature possible (compared to Kelvin).
+ */
+ if (tmp > 0)
+ *temp = tmp;
+ else
+ *temp = 0;
+
+ return 0;
+}
+
+static int th_sys_get_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode *mode)
+{
+ *mode = THERMAL_DEVICE_ENABLED;
+ return 0;
+}
+
+static int th_sys_get_trip_type(struct thermal_zone_device *thermal, int trip,
+ enum thermal_trip_type *type)
+{
+ struct imx6q_thermal_data *p;
+
+ if (trip >= thermal->trips)
+ return -EINVAL;
+
+ p = ((struct imx6q_thermal_zone *)thermal->devdata)->thermal_data;
+
+ *type = p->trp_pts[trip].type;
+
+ return 0;
+}
+
+static int th_sys_get_trip_temp(struct thermal_zone_device *thermal, int trip,
+ unsigned long *temp)
+{
+ struct imx6q_thermal_data *p;
+
+ if (trip >= thermal->trips)
+ return -EINVAL;
+
+ p = ((struct imx6q_thermal_zone *)thermal->devdata)->thermal_data;
+
+ *temp = p->trp_pts[trip].temp;
+
+ return 0;
+}
+
+static int th_sys_get_crit_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ int i;
+ struct imx6q_thermal_data *p;
+
+ p = ((struct imx6q_thermal_zone *)thermal->devdata)->thermal_data;
+
+ for (i = thermal->trips ; i > 0 ; i--) {
+ if (p->trp_pts[i-1].type == THERMAL_TRIP_CRITICAL) {
+ *temp = p->trp_pts[i-1].temp;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int th_sys_bind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ int i;
+ struct thermal_cooling_device *cd;
+
+ cd = ((struct imx6q_thermal_zone *)thermal->devdata)->cool_dev;
+
+ /* if the cooling device is the one from imx6 bind it */
+ if (cdev != cd)
+ return 0;
+
+ for (i = 0; i < IMX6Q_THERMAL_STATE_TRP_PTS; i++) {
+ if (thermal_zone_bind_cooling_device(thermal, i, cdev)) {
+ pr_err("error binding cooling dev\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int th_sys_unbind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ int i;
+
+ struct thermal_cooling_device *cd;
+
+ cd = ((struct imx6q_thermal_zone *)thermal->devdata)->cool_dev;
+
+ if (cdev != cd)
+ return 0;
+
+ for (i = 0; i < IMX6Q_THERMAL_STATE_TRP_PTS; i++) {
+ if (thermal_zone_unbind_cooling_device(thermal, i, cdev)) {
+ pr_err("error unbinding cooling dev\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static struct thermal_zone_device_ops g_dev_ops __initdata = {
+ .bind = th_sys_bind,
+ .unbind = th_sys_unbind,
+ .get_temp = th_sys_get_temp,
+ .get_mode = th_sys_get_mode,
+ .get_trip_type = th_sys_get_trip_type,
+ .get_trip_temp = th_sys_get_trip_temp,
+ .get_crit_temp = th_sys_get_crit_temp,
+};
+
+static int imx6q_thermal_process_fuse_data(unsigned int fuse_data,
+ struct imx6q_sensor_data *sd)
+{
+ int t1, t2, n1, n2;
+
+ if (fuse_data == 0 || fuse_data == 0xffffffff)
+ return -EINVAL;
+
+ /*
+ * Fuse data layout:
+ * [31:20] sensor value @ 25C
+ * [19:8] sensor value of hot
+ * [7:0] hot temperature value
+ */
+ n1 = fuse_data >> 20;
+ n2 = (fuse_data & 0xfff00) >> 8;
+ t2 = fuse_data & 0xff;
+ t1 = 25; /* t1 always 25C */
+
+ pr_debug(" -- temperature sensor calibration data --\n");
+ pr_debug("HW_OCOTP_ANA1: %x\n", fuse_data);
+ pr_debug("n1: %d\nn2: %d\nt1: %d\nt2: %d\n", n1, n2, t1, t2);
+
+ /*
+ * Derived from linear interpolation,
+ * Tmeas = T2 + (Nmeas - N2) * (T1 - T2) / (N1 - N2)
+ * We want to reduce this down to the minimum computation necessary
+ * for each temperature read. Also, we want Tmeas in millicelcius
+ * and we don't want to lose precision from integer division. So...
+ * milli_Tmeas = 1000 * T2 + 1000 * (Nmeas - N2) * (T1 - T2) / (N1 - N2)
+ * Let constant c1 = 1000 * (T1 - T2) / (N1 - N2)
+ * milli_Tmeas = (1000 * T2) + c1 * (Nmeas - N2)
+ * milli_Tmeas = (1000 * T2) + (c1 * Nmeas) - (c1 * N2)
+ * Let constant c2 = (1000 * T2) - (c1 * N2)
+ * milli_Tmeas = c2 + (c1 * Nmeas)
+ */
+ sd->c1 = (1000 * (t1 - t2)) / (n1 - n2);
+ sd->c2 = (1000 * t2) - (sd->c1 * n2);
+
+ pr_debug("c1: %i\n", sd->c1);
+ pr_debug("c2: %i\n", sd->c2);
+
+ return 0;
+}
+
+static void __exit imx6q_thermal_exit(void)
+{
+ if (th_zone && th_zone->therm_dev)
+ thermal_zone_device_unregister(th_zone->therm_dev);
+
+ if (th_zone && th_zone->cool_dev)
+ cpufreq_cooling_unregister(th_zone->cool_dev);
+
+ kfree(th_zone->sensor_data);
+ kfree(th_zone->thermal_data);
+ kfree(th_zone);
+
+ if (ocotp_base)
+ iounmap(ocotp_base);
+
+ if (anatop_base)
+ iounmap(anatop_base);
+
+ pr_info("i.MX6Q: Kernel Thermal management unregistered\n");
+}
+
+static int __init imx6q_thermal_init(void)
+{
+ struct device_node *np_ocotp, *np_anatop;
+ unsigned int fuse_data;
+ int ret;
+
+ np_ocotp = of_find_compatible_node(NULL, NULL, "fsl,imx6q-ocotp");
+ np_anatop = of_find_compatible_node(NULL, NULL, "fsl,imx6q-anatop");
+
+ if (!(np_ocotp && np_anatop))
+ return -ENXIO; /* not a compatible platform */
+
+ ocotp_base = of_iomap(np_ocotp, 0);
+
+ if (!ocotp_base) {
+ pr_err("Could not retrieve ocotp-base\n");
+ ret = -ENXIO;
+ goto err_unregister;
+ }
+
+ anatop_base = of_iomap(np_anatop, 0);
+
+ if (!anatop_base) {
+ pr_err("Could not retrieve anantop-base\n");
+ ret = -ENXIO;
+ goto err_unregister;
+ }
+
+ fuse_data = readl_relaxed(ocotp_base + HW_OCOTP_ANA1);
+
+ th_zone = kzalloc(sizeof(struct imx6q_thermal_zone), GFP_KERNEL);
+ if (!th_zone) {
+ ret = -ENOMEM;
+ goto err_unregister;
+ }
+
+ th_zone->dev_ops = g_dev_ops;
+
+ th_zone->thermal_data =
+ kzalloc(sizeof(struct imx6q_thermal_data), GFP_KERNEL);
+
+ if (!th_zone->thermal_data) {
+ ret = -ENOMEM;
+ goto err_unregister;
+ }
+
+ memcpy(th_zone->thermal_data, &g_thermal_data,
+ sizeof(struct imx6q_thermal_data));
+
+ th_zone->sensor_data =
+ kzalloc(sizeof(struct imx6q_sensor_data), GFP_KERNEL);
+
+ if (!th_zone->sensor_data) {
+ ret = -ENOMEM;
+ goto err_unregister;
+ }
+
+ memcpy(th_zone->sensor_data, &g_sensor_data,
+ sizeof(struct imx6q_sensor_data));
+
+ ret = imx6q_thermal_process_fuse_data(fuse_data, th_zone->sensor_data);
+
+ if (ret) {
+ pr_err("Invalid temperature calibration data.\n");
+ goto err_unregister;
+ }
+
+ if (!th_zone->sensor_data->meas_delay)
+ th_zone->sensor_data->meas_delay = 1;
+
+ /* Make sure sensor is in known good state for measurements */
+ writel_relaxed(BM_ANADIG_TEMPSENSE0_POWER_DOWN,
+ anatop_base + HW_ANADIG_TEMPSENSE0_CLR);
+
+ writel_relaxed(BM_ANADIG_TEMPSENSE0_MEASURE_TEMP,
+ anatop_base + HW_ANADIG_TEMPSENSE0_CLR);
+
+ writel_relaxed(BM_ANADIG_TEMPSENSE1_MEASURE_FREQ,
+ anatop_base + HW_ANADIG_TEMPSENSE1_CLR);
+
+ writel_relaxed(BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF,
+ anatop_base + HW_ANADIG_ANA_MISC0_SET);
+
+ writel_relaxed(BM_ANADIG_TEMPSENSE0_POWER_DOWN,
+ anatop_base + HW_ANADIG_TEMPSENSE0_SET);
+
+ th_zone->cool_dev = cpufreq_cooling_register(
+ (struct freq_clip_table *)th_zone->thermal_data->freq_tab,
+ IMX6Q_THERMAL_STATE_TRP_PTS, cpumask_of(0),
+ THERMAL_TRIP_STATE_INSTANCE);
+
+ if (IS_ERR(th_zone->cool_dev)) {
+ pr_err("Failed to register cpufreq cooling device\n");
+ ret = -EINVAL;
+ goto err_unregister;
+ }
+
+ th_zone->therm_dev = thermal_zone_device_register(
+ th_zone->sensor_data->name, IMX6Q_THERMAL_TOTAL_TRP_PTS,
+ th_zone, &th_zone->dev_ops, 0, 0, 0,
+ IMX6Q_THERMAL_POLLING_FREQUENCY_MS);
+
+ if (IS_ERR(th_zone->therm_dev)) {
+ pr_err("Failed to register thermal zone device\n");
+ ret = -EINVAL;
+ goto err_unregister;
+ }
+
+ return 0;
+
+err_unregister:
+ imx6q_thermal_exit();
+ return ret;
+}
+
+module_init(imx6q_thermal_init);
+module_exit(imx6q_thermal_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor");
+MODULE_DESCRIPTION("i.MX6Q SoC thermal driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx6q-thermal");
diff --git a/drivers/thermal/thermal_sys.c b/drivers/thermal/thermal_sys.c
index 022bacb71a7e..4ae93fb049e1 100644
--- a/drivers/thermal/thermal_sys.c
+++ b/drivers/thermal/thermal_sys.c
@@ -190,6 +190,8 @@ trip_point_type_show(struct device *dev, struct device_attribute *attr,
return sprintf(buf, "passive\n");
case THERMAL_TRIP_ACTIVE:
return sprintf(buf, "active\n");
+ case THERMAL_TRIP_STATE_INSTANCE:
+ return sprintf(buf, "state-instance\n");
default:
return sprintf(buf, "unknown\n");
}
@@ -1013,10 +1015,10 @@ EXPORT_SYMBOL(thermal_cooling_device_unregister);
void thermal_zone_device_update(struct thermal_zone_device *tz)
{
- int count, ret = 0;
- long temp, trip_temp;
+ int count, ret = 0, inst_id;
+ long temp, trip_temp, max_state, last_trip_change = 0;
enum thermal_trip_type trip_type;
- struct thermal_cooling_device_instance *instance;
+ struct thermal_cooling_device_instance *instance, *state_instance;
struct thermal_cooling_device *cdev;
mutex_lock(&tz->lock);
@@ -1063,6 +1065,43 @@ void thermal_zone_device_update(struct thermal_zone_device *tz)
cdev->ops->set_cur_state(cdev, 0);
}
break;
+ case THERMAL_TRIP_STATE_INSTANCE:
+ list_for_each_entry(instance, &tz->cooling_devices,
+ node) {
+ if (instance->trip != count)
+ continue;
+
+ if (temp <= last_trip_change)
+ continue;
+
+ inst_id = 0;
+ /*
+ *For this instance how many instance of same
+ *cooling device occured before
+ */
+
+ list_for_each_entry(state_instance,
+ &tz->cooling_devices, node) {
+ if (instance->cdev ==
+ state_instance->cdev)
+ inst_id++;
+ if (state_instance->trip == count)
+ break;
+ }
+
+ cdev = instance->cdev;
+ cdev->ops->get_max_state(cdev, &max_state);
+
+ if ((temp >= trip_temp) &&
+ (inst_id <= max_state))
+ cdev->ops->set_cur_state(cdev, inst_id);
+ else if ((temp < trip_temp) &&
+ (--inst_id <= max_state))
+ cdev->ops->set_cur_state(cdev, inst_id);
+
+ last_trip_change = trip_temp;
+ }
+ break;
case THERMAL_TRIP_PASSIVE:
if (temp >= trip_temp || tz->passive)
thermal_zone_device_passive(tz, temp,
@@ -1117,6 +1156,7 @@ struct thermal_zone_device *thermal_zone_device_register(char *type,
int result;
int count;
int passive = 0;
+ long first_trip_temp, trip_temp;
if (strlen(type) >= THERMAL_NAME_LENGTH)
return ERR_PTR(-EINVAL);
@@ -1175,6 +1215,7 @@ struct thermal_zone_device *thermal_zone_device_register(char *type,
goto unregister;
}
+ first_trip_temp = 0;
for (count = 0; count < trips; count++) {
result = device_create_file(&tz->device,
&trip_point_attrs[count * 2]);
@@ -1187,6 +1228,21 @@ struct thermal_zone_device *thermal_zone_device_register(char *type,
tz->ops->get_trip_type(tz, count, &trip_type);
if (trip_type == THERMAL_TRIP_PASSIVE)
passive = 1;
+ /*
+ * For THERMAL_TRIP_STATE_INSTANCE trips, thermal zone should
+ * be in ascending order.
+ */
+ if (trip_type == THERMAL_TRIP_STATE_INSTANCE) {
+ tz->ops->get_trip_temp(tz, count, &trip_temp);
+ if (first_trip_temp == 0)
+ first_trip_temp = trip_temp;
+ else if (first_trip_temp < trip_temp)
+ first_trip_temp = trip_temp;
+ else if (first_trip_temp > trip_temp) {
+ pr_warn("Zone trip points should be in ascending order\n");
+ goto unregister;
+ }
+ }
}
if (!passive)