diff options
author | Andrey Konovalov <andrey.konovalov@linaro.org> | 2012-07-16 20:16:44 +0400 |
---|---|---|
committer | Andrey Konovalov <andrey.konovalov@linaro.org> | 2012-07-16 20:16:44 +0400 |
commit | 2006e5d3e4284868403b8f7f53843ffc6b75f047 (patch) | |
tree | 35a36c0e52ced14f9d6d096c14d2aacfcfede7ed /drivers | |
parent | 1f7aafe286ebf8f21dc662d27ec0b1294cbfdb85 (diff) | |
parent | b286f1ce2b4a48ae25ee14b30c9da4da378fc5ad (diff) |
Automatically merging tracking-thermal_exynos4_imx6 into merge-linux-linaro-core-tracking
Conflicting files:
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/cpufreq/Kconfig | 10 | ||||
-rw-r--r-- | drivers/cpufreq/Makefile | 2 | ||||
-rw-r--r-- | drivers/cpufreq/clk-reg-cpufreq.c | 289 | ||||
-rw-r--r-- | drivers/hwmon/Kconfig | 10 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 1 | ||||
-rw-r--r-- | drivers/thermal/Kconfig | 26 | ||||
-rw-r--r-- | drivers/thermal/Makefile | 5 | ||||
-rw-r--r-- | drivers/thermal/cpu_cooling.c | 571 | ||||
-rw-r--r-- | drivers/thermal/exynos4_thermal.c (renamed from drivers/hwmon/exynos4_tmu.c) | 482 | ||||
-rw-r--r-- | drivers/thermal/imx6q_thermal.c | 541 | ||||
-rw-r--r-- | drivers/thermal/thermal_sys.c | 62 |
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) |