diff options
author | Marcel Ziswiler <marcel.ziswiler@toradex.com> | 2013-01-07 16:15:44 +0100 |
---|---|---|
committer | Marcel Ziswiler <marcel.ziswiler@toradex.com> | 2013-01-07 16:15:44 +0100 |
commit | d68e547b448141f905346f5fa28e0bc8161a9445 (patch) | |
tree | 0a835471e26e8e413277c1670ae9d93ac79bd6c8 /arch/arm/mach-tegra/board-colibri_t20.c | |
parent | 7cea39b9b7c6c82f74b5e624a534c4c2a3b5e317 (diff) |
colibri_t20: integrate thermal throttling
As our hardware only allows triggering an interrupt on over-temperature
shutdown we first use it to catch entering throttle limit and only then
set it up to catch an actual over-temperature shutdown.
While throttling we setup a workqueue to catch leaving it again.
We support the same sysfs interface as on NVIDIA Ventana:
root@colibri-t20:~# cat /sys/kernel/debug/thermal/shutdown
115000
root@colibri-t20:~# cat /sys/kernel/debug/thermal/throttle
90000
As follows some output of before and during a throttling run:
root@colibri-t20:~# tegrastats
RAM 159/345MB (lfb 36x4MB) Carveout 11/128MB (lfb 116MB) GART 0/32MB
(lfb 32MB) IRAM 4/255kB(lfb 248kB) cpu [51%,23%]@216 EMC 666 AVP 72 VDE
240 EDP limit -1 Temperatures CPU 48.7 Board 43.2
root@colibri-t20:~# cat /sys/devices/system/cpu/cpu0/cpufreq/throttle
0
root@colibri-t20:~# cat /sys/bus/i2c/devices/4-004c/temp2_os
90000
root@colibri-t20:~# cat /sys/kernel/debug/thermal/shutdown
115000
root@colibri-t20:~# cat /sys/kernel/debug/thermal/throttle
90000
root@colibri-t20:~# tegrastats
RAM 66/345MB (lfb 5x32kB) Carveout 75/128MB (lfb 53MB) GART 8/32MB
(lfb 24MB) IRAM 20/255kB(lfb 232kB) cpu [85%,83%]@912 EMC 666 AVP 216
VDE 240 EDP limit -1 Temperatures CPU 90.1 Board 77.9
root@colibri-t20:~# cat /sys/devices/system/cpu/cpu0/cpufreq/throttle
1
root@colibri-t20:~# cat /sys/bus/i2c/devices/4-004c/temp2_os
115000
Diffstat (limited to 'arch/arm/mach-tegra/board-colibri_t20.c')
-rw-r--r-- | arch/arm/mach-tegra/board-colibri_t20.c | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/board-colibri_t20.c b/arch/arm/mach-tegra/board-colibri_t20.c index 444b98468c7d..ca44ab0641fa 100644 --- a/arch/arm/mach-tegra/board-colibri_t20.c +++ b/arch/arm/mach-tegra/board-colibri_t20.c @@ -20,6 +20,7 @@ #include <linux/clk.h> #include <linux/colibri_usb.h> +#include <linux/debugfs.h> #include <linux/delay.h> #include <linux/dma-mapping.h> #include <linux/gpio_keys.h> @@ -35,6 +36,7 @@ #include <linux/mfd/tps6586x.h> #include <linux/platform_data/tegra_usb.h> #include <linux/platform_device.h> +#include <linux/reboot.h> #include <linux/serial_8250.h> #include <linux/slab.h> #include <linux/spi/spi.h> @@ -56,6 +58,7 @@ #include "board-colibri_t20.h" #include "board.h" #include "clock.h" +#include "cpu-tegra.h" #include "devices.h" #include "gpio-names.h" //move to board-colibri_t20-power.c? @@ -338,8 +341,11 @@ static struct tegra_i2c_platform_data colibri_t20_i2c2_platform_data = { /* PWR_I2C: power I2C to PMIC and temperature sensor */ +static void lm95245_probe_callback(struct device *dev); + static struct lm95245_platform_data colibri_t20_lm95245_pdata = { .enable_os_pin = true, + .probe_callback = lm95245_probe_callback, }; static struct i2c_board_info colibri_t20_i2c_bus4_board_info[] __initdata = { @@ -683,6 +689,160 @@ static void __init colibri_t20_register_spidev(void) #define colibri_t20_register_spidev() do {} while (0) #endif /* CONFIG_SPI_TEGRA && CONFIG_SPI_SPIDEV */ +/* Thermal throttling + Note: As our hardware only allows triggering an interrupt on + over-temperature shutdown we first use it to catch entering throttle + and only then set it up to catch an actual over-temperature shutdown. + While throttling we setup a workqueue to catch leaving it again. */ + +static int colibri_t20_shutdown_temp = 115000; +static int colibri_t20_throttle_hysteresis = 3000; +static int colibri_t20_throttle_temp = 90000; +static struct device *lm95245_device = NULL; +static int thermd_alert_irq_disabled = 0; +struct workqueue_struct *thermd_alert_workqueue; +struct work_struct thermd_alert_work; + +/* Over-temperature shutdown OS pin GPIO interrupt handler */ +static irqreturn_t thermd_alert_irq(int irq, void *data) +{ + disable_irq_nosync(irq); + thermd_alert_irq_disabled = 1; + queue_work(thermd_alert_workqueue, &thermd_alert_work); + + return IRQ_HANDLED; +} + +/* Gets both entered by THERMD_ALERT GPIO interrupt as well as re-scheduled + while throttling. */ +static void thermd_alert_work_func(struct work_struct *work) +{ + int temp = 0; + + lm95245_thermal_get_temp(lm95245_device, &temp); + + if (temp > colibri_t20_shutdown_temp) { + /* First check for hardware over-temperature condition mandating + immediate shutdown */ + pr_err("over-temperature condition %d degC reached, initiating " + "immediate shutdown", temp); + kernel_power_off(); + } else if (temp < colibri_t20_throttle_temp - colibri_t20_throttle_hysteresis) { + /* Make sure throttling gets disabled again */ + if (tegra_is_throttling()) { + tegra_throttling_enable(false); + lm95245_thermal_set_limit(lm95245_device, colibri_t20_throttle_temp); + } + } else if (temp < colibri_t20_throttle_temp) { + /* Regular operation but keep re-scheduling to catch leaving + below throttle again */ + if (tegra_is_throttling()) { + msleep(100); + queue_work(thermd_alert_workqueue, &thermd_alert_work); + } + } else if (temp >= colibri_t20_throttle_temp) { + /* Make sure throttling gets enabled */ + if (!tegra_is_throttling()) { + tegra_throttling_enable(true); + lm95245_thermal_set_limit(lm95245_device, colibri_t20_shutdown_temp); + } + /* And re-schedule again */ + msleep(100); + queue_work(thermd_alert_workqueue, &thermd_alert_work); + } + + /* Avoid unbalanced enable for IRQ 367 */ + if (thermd_alert_irq_disabled) { + thermd_alert_irq_disabled = 0; + enable_irq(TEGRA_GPIO_TO_IRQ(THERMD_ALERT)); + } +} + +static void colibri_t20_thermd_alert_init(void) +{ + gpio_request(THERMD_ALERT, "THERMD_ALERT"); + gpio_direction_input(THERMD_ALERT); + + thermd_alert_workqueue = create_singlethread_workqueue("THERMD_ALERT"); + + INIT_WORK(&thermd_alert_work, thermd_alert_work_func); + + if (request_irq(TEGRA_GPIO_TO_IRQ(THERMD_ALERT), thermd_alert_irq, + IRQF_TRIGGER_LOW, "THERMD_ALERT", NULL)) + pr_err("%s: unable to register THERMD_ALERT interrupt\n", __func__); +} + +static void lm95245_probe_callback(struct device *dev) +{ + lm95245_device = dev; + if (lm95245_device) lm95245_thermal_set_limit(lm95245_device, colibri_t20_throttle_temp); +} + +#ifdef CONFIG_DEBUG_FS +static int colibri_t20_thermal_get_throttle_temp(void *data, u64 *val) +{ + *val = (u64)colibri_t20_throttle_temp; + return 0; +} + +static int colibri_t20_thermal_set_throttle_temp(void *data, u64 val) +{ + colibri_t20_throttle_temp = val; + if (!tegra_is_throttling() && lm95245_device) + lm95245_thermal_set_limit(lm95245_device, colibri_t20_throttle_temp); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(throttle_fops, + colibri_t20_thermal_get_throttle_temp, + colibri_t20_thermal_set_throttle_temp, + "%llu\n"); + +static int colibri_t20_thermal_get_shutdown_temp(void *data, u64 *val) +{ + *val = (u64)colibri_t20_shutdown_temp; + return 0; +} + +static int colibri_t20_thermal_set_shutdown_temp(void *data, u64 val) +{ + colibri_t20_shutdown_temp = val; + if (tegra_is_throttling() && lm95245_device) + lm95245_thermal_set_limit(lm95245_device, colibri_t20_shutdown_temp); + + /* Carefull as we can only actively monitor one temperatur limit and + assumption is throttling is lower than shutdown one. */ + if (colibri_t20_shutdown_temp < colibri_t20_throttle_temp) + colibri_t20_thermal_set_throttle_temp(NULL, colibri_t20_shutdown_temp); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(shutdown_fops, + colibri_t20_thermal_get_shutdown_temp, + colibri_t20_thermal_set_shutdown_temp, + "%llu\n"); + +static int __init colibri_t20_thermal_debug_init(void) +{ + struct dentry *thermal_debugfs_root; + + thermal_debugfs_root = debugfs_create_dir("thermal", 0); + + if (!debugfs_create_file("throttle", 0644, thermal_debugfs_root, + NULL, &throttle_fops)) + return -ENOMEM; + + if (!debugfs_create_file("shutdown", 0644, thermal_debugfs_root, + NULL, &shutdown_fops)) + return -ENOMEM; + + return 0; +} +late_initcall(colibri_t20_thermal_debug_init); +#endif /* CONFIG_DEBUG_FS */ + /* UART */ static struct platform_device *colibri_t20_uart_devices[] __initdata = { @@ -1102,6 +1262,7 @@ static void __init tegra_colibri_t20_init(void) { tegra_clk_init_from_table(colibri_t20_clk_init_table); colibri_t20_pinmux_init(); + colibri_t20_thermd_alert_init(); colibri_t20_i2c_init(); colibri_t20_uart_init(); // |