diff options
author | Joshua Primero <jprimero@nvidia.com> | 2011-10-03 22:23:53 -0700 |
---|---|---|
committer | Varun Wadekar <vwadekar@nvidia.com> | 2011-12-08 17:03:16 +0530 |
commit | e93fee7719a78f60a1e9fe48d29cd325b723ee67 (patch) | |
tree | 2d184c51265d2f8d46923d9c5b1afa18fe5e5849 | |
parent | ef10446dfc8eae4f47d032cb71ca8af44281d5a9 (diff) |
arm: tegra: power: Device agnostic thermal driver
Added a thermal driver which is agonistic to the device driver
which will make it easier to port thermal devices to android
tegra.
Reviewed-on: http://git-master/r/55883
Reviewed-on: http://git-master/r/59471
(cherry picked from commit b89dbb5a64bcb3124794f644e71658de83a4ab71)
Change-Id: Ib3c7100144ea0a98ac7c15e404d4d9e7737018e5
Signed-off-by: Joshua Primero <jprimero@nvidia.com>
Reviewed-on: http://git-master/r/66548
Reviewed-by: Diwakar Tundlam <dtundlam@nvidia.com>
-rw-r--r-- | arch/arm/mach-tegra/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/mach-tegra/cpu-tegra.c | 5 | ||||
-rw-r--r-- | arch/arm/mach-tegra/include/mach/thermal.h | 60 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra3_thermal.c | 286 |
4 files changed, 352 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile index 5ef2a5173cd3..7cf036210170 100644 --- a/arch/arm/mach-tegra/Makefile +++ b/arch/arm/mach-tegra/Makefile @@ -86,6 +86,7 @@ ifeq ($(CONFIG_TEGRA_THERMAL_THROTTLE),y) obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_throttle.o obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra3_throttle.o endif +obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra3_thermal.o obj-$(CONFIG_TEGRA_IOVMM) += iovmm.o obj-$(CONFIG_TEGRA_IOVMM_GART) += iovmm-gart.o obj-$(CONFIG_TEGRA_IOVMM_SMMU) += iovmm-smmu.o diff --git a/arch/arm/mach-tegra/cpu-tegra.c b/arch/arm/mach-tegra/cpu-tegra.c index d6793f87cc5f..598c6b0408a9 100644 --- a/arch/arm/mach-tegra/cpu-tegra.c +++ b/arch/arm/mach-tegra/cpu-tegra.c @@ -38,6 +38,7 @@ #include <mach/clk.h> #include <mach/edp.h> +#include <mach/thermal.h> #include "clock.h" #include "cpu-tegra.h" @@ -647,6 +648,10 @@ static int __init tegra_cpufreq_init(void) suspend_index = table_data->suspend_index; + ret = tegra_thermal_init(); + if (ret) + return ret; + ret = tegra_throttle_init(&tegra_cpu_lock); if (ret) return ret; diff --git a/arch/arm/mach-tegra/include/mach/thermal.h b/arch/arm/mach-tegra/include/mach/thermal.h new file mode 100644 index 000000000000..0d25c307f94e --- /dev/null +++ b/arch/arm/mach-tegra/include/mach/thermal.h @@ -0,0 +1,60 @@ +/* + * arch/arm/mach-tegra/thermal.h + * + * Copyright (C) 2010-2011 NVIDIA Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MACH_THERMAL_H +#define __MACH_THERMAL_H + +struct tegra_thermal_ops { + int (*get_temp) (void *, long *); + int (*set_limits) (void *, long, long); +}; + +struct tegra_thermal { + void *data; + struct tegra_thermal_ops *ops; +#ifdef CONFIG_TEGRA_THERMAL_SYSFS + struct thermal_zone_device *thz; +#endif +}; + +#ifndef CONFIG_ARCH_TEGRA_2x_SOC +int tegra_thermal_init(void); +int tegra_thermal_exit(void); + +struct tegra_thermal + *tegra_thermal_register(void *data, struct tegra_thermal_ops *ops); +int tegra_thermal_unregister(struct tegra_thermal *thermal); +int tegra_thermal_alert(struct tegra_thermal *thermal); +#else +static inline int tegra_thermal_init(void) +{ return 0; } +static inline int tegra_thermal_exit(void) +{ return 0; } +static inline struct tegra_thermal + *tegra_thermal_register(void *data, struct tegra_thermal_ops *ops) +{ return NULL; } +static inline int tegra_thermal_unregister(struct tegra_thermal *thermal) +{ return 0; } +static inline int tegra_thermal_alert(struct tegra_thermal *thermal) +{ return 0; } +#endif + +#define CELSIUS_TO_MILLICELSIUS(x) ((x)*1000) +#define MILLICELSIUS_TO_CELSIUS(x) ((x)/1000) + + + +#endif /* __MACH_THERMAL_H */ diff --git a/arch/arm/mach-tegra/tegra3_thermal.c b/arch/arm/mach-tegra/tegra3_thermal.c new file mode 100644 index 000000000000..30f8d7d8d800 --- /dev/null +++ b/arch/arm/mach-tegra/tegra3_thermal.c @@ -0,0 +1,286 @@ +/* + * arch/arm/mach-tegra/tegra3_thermal.c + * + * Copyright (C) 2010-2011 NVIDIA Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/kernel.h> +#include <linux/cpufreq.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> +#include <linux/thermal.h> +#include <mach/thermal.h> +#include <mach/edp.h> +#include <linux/slab.h> + + +#include "clock.h" +#include "cpu-tegra.h" +#include "dvfs.h" + +#define MAX_ZONES (16) + +/* Thermal sysfs handles hysteresis */ +#ifndef CONFIG_TEGRA_THERMAL_SYSFS +#define ALERT_HYSTERESIS_THROTTLE 1 +#endif + +#define ALERT_HYSTERESIS_EDP 3 + +#define THROTTLING_LIMIT (85000) +#define MAX_LIMIT (90000) + +u8 thermal_zones[MAX_ZONES]; +int thermal_zones_sz; +static int edp_thermal_zone_val = -1; + +#ifndef CONFIG_TEGRA_THERMAL_SYSFS +static bool throttle_enb; +struct mutex mutex; +#endif + + +int __init tegra_thermal_init() +{ + const struct tegra_edp_limits *z; + int zones_sz; + int i; + +#ifndef CONFIG_TEGRA_THERMAL_SYSFS + mutex_init(&mutex); +#endif + tegra_get_cpu_edp_limits(&z, &zones_sz); + zones_sz = min(zones_sz, MAX_ZONES); + + for (i = 0; i < zones_sz; i++) + thermal_zones[i] = z[i].temperature; + + thermal_zones_sz = zones_sz; + + return 0; +} + + +#ifdef CONFIG_TEGRA_THERMAL_SYSFS + +static int tegra_thermal_zone_bind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdevice) { + /* Support only Thermal Throttling (1 trip) for now */ + return thermal_zone_bind_cooling_device(thermal, 0, cdevice); +} + +static int tegra_thermal_zone_unbind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdevice) { + /* Support only Thermal Throttling (1 trip) for now */ + return thermal_zone_unbind_cooling_device(thermal, 0, cdevice); +} + +static int tegra_thermal_zone_get_temp(struct thermal_zone_device *thz, + long *temp) +{ + struct tegra_thermal *thermal = thz->devdata; + thermal->ops->get_temp(thermal->data, temp); + + return 0; +} + +static int tegra_thermal_zone_get_trip_type( + struct thermal_zone_device *thermal, + int trip, + enum thermal_trip_type *type) { + + /* Support only Thermal Throttling (1 trip) for now */ + if (trip != 0) + return -EINVAL; + + *type = THERMAL_TRIP_PASSIVE; + + return 0; +} + +static int tegra_thermal_zone_get_trip_temp(struct thermal_zone_device *thermal, + int trip, + long *temp) { + /* Support only Thermal Throttling (1 trip) for now */ + if (trip != 0) + return -EINVAL; + + *temp = THROTTLING_LIMIT; + + return 0; +} + +static struct thermal_zone_device_ops tegra_thermal_zone_ops = { + .bind = tegra_thermal_zone_bind, + .unbind = tegra_thermal_zone_unbind, + .get_temp = tegra_thermal_zone_get_temp, + .get_trip_type = tegra_thermal_zone_get_trip_type, + .get_trip_temp = tegra_thermal_zone_get_trip_temp, +}; +#endif + + + +struct tegra_thermal +*tegra_thermal_register(void *data, struct tegra_thermal_ops *thermal_ops) +{ + long temp_milli; + struct tegra_thermal *thermal; +#ifdef CONFIG_THERMAL_SYSFS + struct thermal_zone_device *thz; +#endif + + thermal = kzalloc(sizeof(struct tegra_thermal), GFP_KERNEL); + if (!thermal) + return ERR_PTR(-ENOMEM); + + thermal->ops = thermal_ops; + thermal->data = data; + +#ifdef CONFIG_TEGRA_THERMAL_SYSFS + thz = thermal_zone_device_register("nct1008", + 1, /* trips */ + thermal, + &tegra_thermal_zone_ops, + 2, /* tc1 */ + 1, /* tc2 */ + 2000, /* passive delay */ + 0); /* polling delay */ + + if (IS_ERR(thz)) { + thz = NULL; + kfree(thermal); + return ERR_PTR(-ENODEV); + } + + thermal->thz = thz; +#endif + + thermal->ops->get_temp(thermal->data, &temp_milli); + tegra_edp_update_thermal_zone(MILLICELSIUS_TO_CELSIUS(temp_milli)); + + return thermal; +} + +int tegra_thermal_unregister(struct tegra_thermal *thermal) +{ +#ifdef CONFIG_TEGRA_THERMAL_SYSFS + if (thermal->thz) + thermal_zone_device_unregister(thermal->thz); +#endif + + kfree(thermal); + + return 0; +} + +/* The thermal sysfs handles notifying the throttling + * cooling device */ +#ifndef CONFIG_TEGRA_THERMAL_SYSFS +static void tegra_therm_throttle(bool enable) +{ + if (throttle_enb != enable) { + mutex_lock(&mutex); + tegra_throttling_enable(enable); + throttle_enb = enable; + mutex_unlock(&mutex); + } +} +#endif + +int tegra_thermal_alert(struct tegra_thermal *thermal) +{ + int err; + int hysteresis; + long temp, tzone1, tzone2; + int lo_limit = 0, hi_limit = 0; + int nentries = thermal_zones_sz; + int i; + + err = thermal->ops->get_temp(thermal->data, &temp); + if (err) { + pr_err("%s: get temp fail(%d)", __func__, err); + return err; + } + + hysteresis = ALERT_HYSTERESIS_EDP; + +#ifndef CONFIG_TEGRA_THERMAL_SYSFS + if (temp >= THROTTLING_LIMIT) { + /* start throttling */ + tegra_therm_throttle(true); + hysteresis = ALERT_HYSTERESIS_THROTTLE; + } else if (temp <= + (THROTTLING_LIMIT - + ALERT_HYSTERESIS_THROTTLE)) { + /* switch off throttling */ + tegra_therm_throttle(false); + } +#endif + + if (temp < CELSIUS_TO_MILLICELSIUS(thermal_zones[0])) { + lo_limit = 0; + hi_limit = thermal_zones[0]; + } else if (temp >= + CELSIUS_TO_MILLICELSIUS(thermal_zones[nentries-1])) { + lo_limit = thermal_zones[nentries-1] - hysteresis; + hi_limit = MILLICELSIUS_TO_CELSIUS(MAX_LIMIT); + } else { + for (i = 0; (i + 1) < nentries; i++) { + tzone1 = thermal_zones[i]; + tzone2 = thermal_zones[i + 1]; + + if (temp >= CELSIUS_TO_MILLICELSIUS(tzone1) && + temp < CELSIUS_TO_MILLICELSIUS(tzone2)) { + lo_limit = tzone1 - hysteresis; + hi_limit = tzone2; + break; + } + } + } + + err = thermal->ops->set_limits(thermal->data, lo_limit, hi_limit); + + if (err) + return err; + + /* inform edp governor */ + if (edp_thermal_zone_val != temp) + /* + * FIXME: Move this direct tegra_ function call to be called + * via a pointer in 'struct nct1008_data' (like 'alarm_fn') + */ + tegra_edp_update_thermal_zone(MILLICELSIUS_TO_CELSIUS(temp)); + + edp_thermal_zone_val = temp; + +#ifdef CONFIG_TEGRA_THERMAL_SYSFS + if (thermal->thz) { + if (!thermal->thz->passive) + thermal_zone_device_update(thermal->thz); + } +#endif + + return 0; +} + +int tegra_thermal_exit(void) +{ + return 0; +} |