blob: 99e3bbab9a586094f5e6c3434915ce83ee1fd515 [file] [log] [blame]
/*
* arch/arm/mach-tegra/tegra3_thermal.c
*
* Copyright (C) 2010-2012 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 <linux/module.h>
#include <mach/thermal.h>
#include <mach/edp.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include "clock.h"
#include "cpu-tegra.h"
#include "dvfs.h"
static struct tegra_thermal_data *therm;
static LIST_HEAD(tegra_therm_list);
static DEFINE_MUTEX(tegra_therm_mutex);
static struct balanced_throttle *throttle_list;
static int throttle_list_size;
#ifdef CONFIG_TEGRA_EDP_LIMITS
static long edp_thermal_zone_val;
#endif
#ifdef CONFIG_TEGRA_SKIN_THROTTLE
static int skin_devs_bitmap;
static struct therm_est_subdevice *skin_devs[THERMAL_DEVICE_MAX];
static int skin_devs_count;
#endif
static bool tegra_thermal_suspend;
#ifdef CONFIG_DEBUG_FS
static struct dentry *thermal_debugfs_root;
#endif
static inline long dev2tj(struct tegra_thermal_device *dev,
long dev_temp)
{
return dev_temp + dev->offset;
}
static inline long tj2dev(struct tegra_thermal_device *dev,
long tj_temp)
{
return tj_temp - dev->offset;
}
static int tegra_thermal_get_temp_unlocked(long *tj_temp, bool offsetted)
{
struct tegra_thermal_device *dev = NULL;
int ret = 0;
#if defined(CONFIG_TEGRA_EDP_LIMITS) || defined(CONFIG_TEGRA_THERMAL_THROTTLE)
list_for_each_entry(dev, &tegra_therm_list, node)
if (dev->id == therm->throttle_edp_device_id)
break;
#endif
if (dev) {
dev->get_temp(dev->data, tj_temp);
if (offsetted)
*tj_temp = dev2tj(dev, *tj_temp);
} else {
ret = -1;
}
return ret;
}
#ifdef CONFIG_TEGRA_THERMAL_THROTTLE
static int tegra_thermal_zone_bind(struct thermal_zone_device *thz,
struct thermal_cooling_device *cdevice) {
struct balanced_throttle *bthrot = cdevice->devdata;
struct tegra_thermal_device *device = thz->devdata;
if ((bthrot->id == BALANCED_THROTTLE_ID_TJ) &&
(device->id == therm->throttle_edp_device_id))
return thermal_zone_bind_cooling_device(thz, 0, cdevice);
#ifdef CONFIG_TEGRA_SKIN_THROTTLE
if ((bthrot->id == BALANCED_THROTTLE_ID_SKIN) &&
(device->id == therm->skin_device_id))
return thermal_zone_bind_cooling_device(thz, 0, cdevice);
#endif
return 0;
}
static int tegra_thermal_zone_unbind(struct thermal_zone_device *thz,
struct thermal_cooling_device *cdevice) {
struct balanced_throttle *bthrot = cdevice->devdata;
struct tegra_thermal_device *device = thz->devdata;
if ((bthrot->id == BALANCED_THROTTLE_ID_TJ) &&
(device->id == therm->throttle_edp_device_id))
return thermal_zone_unbind_cooling_device(thz, 0, cdevice);
#ifdef CONFIG_TEGRA_SKIN_THROTTLE
if ((bthrot->id == BALANCED_THROTTLE_ID_SKIN) &&
(device->id == therm->skin_device_id))
return thermal_zone_unbind_cooling_device(thz, 0, cdevice);
#endif
return 0;
}
static int tegra_thermal_zone_get_temp(struct thermal_zone_device *thz,
unsigned long *temp)
{
struct tegra_thermal_device *device = thz->devdata;
if (!tegra_thermal_suspend)
device->get_temp(device->data, temp);
return 0;
}
static int tegra_thermal_zone_get_trip_type(
struct thermal_zone_device *thermal,
int trip,
enum thermal_trip_type *type) {
if (trip != 0)
return -EINVAL;
*type = THERMAL_TRIP_PASSIVE;
return 0;
}
static int tegra_thermal_zone_get_trip_temp(struct thermal_zone_device *thz,
int trip,
unsigned long *temp) {
struct tegra_thermal_device *device = thz->devdata;
if (trip != 0)
return -EINVAL;
if (device->id == therm->throttle_edp_device_id)
*temp = therm->temp_throttle;
#ifdef CONFIG_TEGRA_SKIN_THROTTLE
else if (device->id == therm->skin_device_id)
*temp = therm->temp_throttle_skin;
#endif
else
return -EINVAL;
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
static int tegra_thermal_pm_notify(struct notifier_block *nb,
unsigned long event, void *data)
{
switch (event) {
case PM_SUSPEND_PREPARE:
tegra_thermal_suspend = true;
break;
case PM_POST_SUSPEND:
tegra_thermal_suspend = false;
break;
}
return NOTIFY_OK;
};
static struct notifier_block tegra_thermal_nb = {
.notifier_call = tegra_thermal_pm_notify,
};
static void tegra_thermal_alert_unlocked(void *data)
{
struct tegra_thermal_device *device = data;
long temp_tj;
long lo_limit_throttle_tj, hi_limit_throttle_tj;
long lo_limit_edp_tj = 0, hi_limit_edp_tj = 0;
long temp_low_dev, temp_low_tj;
int lo_limit_tj = 0, hi_limit_tj = 0;
#ifdef CONFIG_TEGRA_EDP_LIMITS
const struct tegra_edp_limits *z;
int zones_sz;
int i;
#endif
#ifdef CONFIG_TEGRA_THERMAL_THROTTLE
if (device->thz) {
if ((!device->thz->passive) && (!tegra_thermal_suspend))
thermal_zone_device_update(device->thz);
}
#endif
/* Convert all temps to tj and then do all work/logic in terms of
tj in order to avoid confusion */
if (tegra_thermal_get_temp_unlocked(&temp_tj, true))
return;
device->get_temp_low(device, &temp_low_dev);
temp_low_tj = dev2tj(device, temp_low_dev);
lo_limit_throttle_tj = temp_low_tj;
hi_limit_throttle_tj = dev2tj(device, therm->temp_shutdown);
#ifdef CONFIG_TEGRA_THERMAL_THROTTLE
hi_limit_throttle_tj = dev2tj(device, therm->temp_throttle);
if (temp_tj > dev2tj(device, therm->temp_throttle)) {
lo_limit_throttle_tj = dev2tj(device, therm->temp_throttle);
hi_limit_throttle_tj = dev2tj(device, therm->temp_shutdown);
}
#endif
#ifdef CONFIG_TEGRA_EDP_LIMITS
tegra_get_cpu_edp_limits(&z, &zones_sz);
/* edp table based off of tdiode measurements */
#define EDP_TEMP_TJ(_index) (z[_index].temperature * 1000 + therm->edp_offset)
if (temp_tj < EDP_TEMP_TJ(0)) {
lo_limit_edp_tj = temp_low_tj;
hi_limit_edp_tj = EDP_TEMP_TJ(0);
} else if (temp_tj >= EDP_TEMP_TJ(zones_sz-1)) {
lo_limit_edp_tj = EDP_TEMP_TJ(zones_sz-1) -
therm->hysteresis_edp;
hi_limit_edp_tj = dev2tj(device, therm->temp_shutdown);
} else {
for (i = 0; (i + 1) < zones_sz; i++) {
if ((temp_tj >= EDP_TEMP_TJ(i)) &&
(temp_tj < EDP_TEMP_TJ(i+1))) {
lo_limit_edp_tj = EDP_TEMP_TJ(i) -
therm->hysteresis_edp;
hi_limit_edp_tj = EDP_TEMP_TJ(i+1);
break;
}
}
}
#undef EDP_TEMP_TJ
#else
lo_limit_edp_tj = temp_low_tj;
hi_limit_edp_tj = dev2tj(device, therm->temp_shutdown);
#endif
/* Get smallest window size */
lo_limit_tj = max(lo_limit_throttle_tj, lo_limit_edp_tj);
hi_limit_tj = min(hi_limit_throttle_tj, hi_limit_edp_tj);
device->set_limits(device->data,
tj2dev(device, lo_limit_tj),
tj2dev(device, hi_limit_tj));
#ifdef CONFIG_TEGRA_EDP_LIMITS
/* inform edp governor */
if (edp_thermal_zone_val != temp_tj) {
long temp_edp = (temp_tj - therm->edp_offset) / 1000;
tegra_edp_update_thermal_zone(temp_edp);
edp_thermal_zone_val = temp_tj;
}
#endif
}
#ifdef CONFIG_TEGRA_THERMAL_THROTTLE
/* Make sure this function remains stateless */
static void tegra_thermal_alert(void *data)
{
mutex_lock(&tegra_therm_mutex);
tegra_thermal_alert_unlocked(data);
mutex_unlock(&tegra_therm_mutex);
}
#endif
#ifdef CONFIG_TEGRA_SKIN_THROTTLE
static void tegra_skin_thermal_alert(void *data)
{
struct tegra_thermal_device *dev = data;
if (!dev->thz->passive)
thermal_zone_device_update(dev->thz);
}
static int tegra_skin_device_register(struct tegra_thermal_device *device)
{
int i;
struct therm_est_subdevice *skin_dev =
kzalloc(sizeof(struct therm_est_subdevice), GFP_KERNEL);
for (i = 0; i < therm->skin_devs_size; i++) {
if (therm->skin_devs[i].id == device->id) {
memcpy(skin_dev->coeffs,
therm->skin_devs[i].coeffs,
sizeof(skin_devs[i]->coeffs));
break;
}
}
skin_dev->dev_data = device->data;
skin_dev->get_temp = device->get_temp;
skin_devs[skin_devs_count++] = skin_dev;
/* Create skin thermal device */
if (skin_devs_count == therm->skin_devs_size) {
struct tegra_thermal_device *thermal_skin_device;
struct therm_estimator *skin_estimator;
skin_estimator = therm_est_register(
skin_devs,
skin_devs_count,
therm->skin_temp_offset,
therm->skin_period);
thermal_skin_device = kzalloc(sizeof(struct tegra_thermal_device),
GFP_KERNEL);
thermal_skin_device->name = "skin_pred";
thermal_skin_device->id = THERMAL_DEVICE_ID_SKIN;
thermal_skin_device->data = skin_estimator;
thermal_skin_device->get_temp =
(int (*)(void *, long *)) therm_est_get_temp;
thermal_skin_device->set_limits =
(int (*)(void *, long, long)) therm_est_set_limits;
thermal_skin_device->set_alert =
(int (*)(void *, void (*)(void *), void *))
therm_est_set_alert;
tegra_thermal_device_register(thermal_skin_device);
}
return 0;
}
#endif
int tegra_thermal_device_register(struct tegra_thermal_device *device)
{
struct tegra_thermal_device *dev;
#ifdef CONFIG_TEGRA_THERMAL_THROTTLE
struct thermal_zone_device *thz;
int t1 = 0, t2 = 0, pdelay = 0;
bool create_thz = false;
#endif
mutex_lock(&tegra_therm_mutex);
list_for_each_entry(dev, &tegra_therm_list, node) {
if (dev->id == device->id) {
mutex_unlock(&tegra_therm_mutex);
return -EINVAL;
}
}
#ifdef CONFIG_TEGRA_THERMAL_THROTTLE
if (device->id == therm->throttle_edp_device_id) {
t1 = therm->tc1;
t2 = therm->tc2;
pdelay = therm->passive_delay;
create_thz = true;
}
#endif
#ifdef CONFIG_TEGRA_SKIN_THROTTLE
if (device->id == therm->skin_device_id) {
t1 = 0;
t2 = 1;
pdelay = 5000;
create_thz = true;
}
#endif
#ifdef CONFIG_TEGRA_THERMAL_THROTTLE
if (create_thz) {
thz = thermal_zone_device_register(
device->name,
1, /* trips */
device,
&tegra_thermal_zone_ops,
t1, /* dT/dt */
t2, /* throttle */
pdelay,
0); /* polling delay */
if (IS_ERR_OR_NULL(thz))
return -ENODEV;
device->thz = thz;
}
#endif
list_add(&device->node, &tegra_therm_list);
mutex_unlock(&tegra_therm_mutex);
if (device->id == therm->shutdown_device_id) {
device->set_shutdown_temp(device->data, therm->temp_shutdown);
}
#ifdef CONFIG_TEGRA_SKIN_THROTTLE
if (device->id == therm->skin_device_id) {
if (create_thz)
device->set_alert(device->data,
tegra_skin_thermal_alert,
device);
device->set_limits(device->data, 0, therm->temp_throttle_skin);
}
#endif
#ifdef CONFIG_TEGRA_THERMAL_THROTTLE
if (device->id == therm->throttle_edp_device_id) {
device->set_alert(device->data, tegra_thermal_alert, device);
/* initialize limits */
tegra_thermal_alert(device);
}
#endif
#ifdef CONFIG_TEGRA_SKIN_THROTTLE
if ((therm->skin_device_id == THERMAL_DEVICE_ID_SKIN) &&
device->id && skin_devs_bitmap)
tegra_skin_device_register(device);
#endif
return 0;
}
/* This needs to be inialized later hand */
static int __init throttle_list_init(void)
{
int i;
for (i = 0; i < throttle_list_size; i++)
if (balanced_throttle_register(&throttle_list[i]))
return -ENODEV;
return 0;
}
late_initcall(throttle_list_init);
int __init tegra_thermal_init(struct tegra_thermal_data *data,
struct balanced_throttle *tlist,
int tlist_size)
{
therm = data;
#ifdef CONFIG_DEBUG_FS
thermal_debugfs_root = debugfs_create_dir("tegra_thermal", 0);
#endif
#ifdef CONFIG_TEGRA_SKIN_THROTTLE
{
int i;
for (i = 0; i < therm->skin_devs_size; i++)
skin_devs_bitmap |= therm->skin_devs[i].id;
}
#endif
throttle_list = tlist;
throttle_list_size = tlist_size;
register_pm_notifier(&tegra_thermal_nb);
return 0;
}
int tegra_thermal_exit(void)
{
#ifdef CONFIG_TEGRA_THERMAL_THROTTLE
struct tegra_thermal_device *dev;
mutex_lock(&tegra_therm_mutex);
list_for_each_entry(dev, &tegra_therm_list, node) {
thermal_zone_device_unregister(dev->thz);
}
mutex_unlock(&tegra_therm_mutex);
#endif
return 0;
}
#ifdef CONFIG_DEBUG_FS
static int tegra_thermal_temp_tj_get(void *data, u64 *val)
{
long temp_tj;
mutex_lock(&tegra_therm_mutex);
if (tegra_thermal_get_temp_unlocked(&temp_tj, false))
temp_tj = -1;
mutex_unlock(&tegra_therm_mutex);
*val = (u64)temp_tj;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(temp_tj_fops,
tegra_thermal_temp_tj_get,
NULL,
"%llu\n");
static int __init temp_tj_debug_init(void)
{
debugfs_create_file("temp_tj", 0644, thermal_debugfs_root,
NULL, &temp_tj_fops);
return 0;
}
late_initcall(temp_tj_debug_init);
#define TEGRA_THERM_DEBUGFS(_name, _device_id, throttle, shutdown) \
static int tegra_thermal_##_name##_set(void *data, u64 val) \
{ \
struct tegra_thermal_device *dev; \
mutex_lock(&tegra_therm_mutex); \
therm->_name = val; \
list_for_each_entry(dev, &tegra_therm_list, node) \
if (dev->id == therm->_device_id) \
break; \
if (dev) { \
if (throttle) \
tegra_thermal_alert_unlocked(dev); \
if (shutdown) \
dev->set_shutdown_temp(dev->data, \
therm->temp_shutdown); \
} \
mutex_unlock(&tegra_therm_mutex); \
return 0; \
} \
static int tegra_thermal_##_name##_get(void *data, u64 *val) \
{ \
*val = (u64)therm->_name; \
return 0; \
} \
DEFINE_SIMPLE_ATTRIBUTE(_name##_fops, \
tegra_thermal_##_name##_get, \
tegra_thermal_##_name##_set, \
"%llu\n"); \
static int __init _name##_debug_init(void) \
{ \
debugfs_create_file(#_name, 0644, thermal_debugfs_root, \
NULL, &_name##_fops); \
return 0; \
} \
late_initcall(_name##_debug_init);
TEGRA_THERM_DEBUGFS(temp_shutdown, shutdown_device_id, false, true);
#ifdef CONFIG_TEGRA_THERMAL_THROTTLE
TEGRA_THERM_DEBUGFS(temp_throttle, throttle_edp_device_id, true, false);
#endif
#ifdef CONFIG_TEGRA_SKIN_THROTTLE
TEGRA_THERM_DEBUGFS(temp_throttle_skin, skin_device_id, false, false);
#endif
#ifdef CONFIG_TEGRA_THERMAL_THROTTLE
#define THERM_DEBUGFS(_name) \
static int tegra_thermal_##_name##_set(void *data, u64 val) \
{ \
struct tegra_thermal_device *dev; \
mutex_lock(&tegra_therm_mutex); \
list_for_each_entry(dev, &tegra_therm_list, node) \
if (dev->id == therm->throttle_edp_device_id) \
break; \
if (dev) \
dev->thz->_name = val; \
mutex_unlock(&tegra_therm_mutex); \
return 0; \
} \
static int tegra_thermal_##_name##_get(void *data, u64 *val) \
{ \
struct tegra_thermal_device *dev; \
mutex_lock(&tegra_therm_mutex); \
list_for_each_entry(dev, &tegra_therm_list, node) \
if (dev->id == therm->throttle_edp_device_id) \
break; \
if (dev) \
*val = (u64)dev->thz->_name; \
mutex_unlock(&tegra_therm_mutex); \
return 0; \
} \
DEFINE_SIMPLE_ATTRIBUTE(_name##_fops, \
tegra_thermal_##_name##_get, \
tegra_thermal_##_name##_set, \
"%llu\n"); \
static int __init _name##_debug_init(void) \
{ \
debugfs_create_file(#_name, 0644, thermal_debugfs_root, \
NULL, &_name##_fops); \
return 0; \
} \
late_initcall(_name##_debug_init);
THERM_DEBUGFS(tc1);
THERM_DEBUGFS(tc2);
THERM_DEBUGFS(passive_delay);
#endif
#endif