/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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