/* * Copyright (C) 2013-2014 NVIDIA Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #define TEGRA_DRAM_THERM_MAX_STATE 1 /* In ms - time between taking MR4 samples. */ static unsigned long emc_mr4_sample_interval = 1000; static atomic_t do_poll; static int prev_temp = -1; static struct timer_list emc_mr4_timer; /* * Set to 1 to allow debugfs to control the mr4 read value. */ static int test_mode; static int dram_temp_override; static void emc_mr4_poll(unsigned long nothing) { int dram_temp; if (!test_mode) dram_temp = tegra_emc_get_dram_temperature(); else dram_temp = dram_temp_override; if (prev_temp == dram_temp) goto reset; switch (dram_temp) { case 0: case 1: case 2: case 3: /* * Temp is fine - go back to regular refresh. */ pr_info("[dram-therm] Setting nominal refresh + timings.\n"); tegra_emc_set_over_temp_state(DRAM_OVER_TEMP_NONE); break; case 4: pr_info("[dram-therm] Enabling 2x refresh.\n"); tegra_emc_set_over_temp_state(DRAM_OVER_TEMP_REFRESH_X2); break; case 5: pr_info("[dram-therm] Enabling 4x refresh.\n"); tegra_emc_set_over_temp_state(DRAM_OVER_TEMP_REFRESH_X4); break; case 6: pr_info("[dram-therm] Enabling 4x refresh + derating.\n"); tegra_emc_set_over_temp_state(DRAM_OVER_TEMP_THROTTLE); break; default: WARN(1, "%s: Invalid DRAM temp state %d\n", __func__, dram_temp); break; } prev_temp = dram_temp; reset: if (atomic_read(&do_poll) == 0) return; if (mod_timer(&emc_mr4_timer, jiffies + msecs_to_jiffies(emc_mr4_sample_interval))) pr_err("[dram-therm] Failed to restart timer!!!\n"); } /* * Tell the dram thermal driver to start polling for the DRAM temperature. This * should be invoked when there is reason to believe the DRAM temperature is * high. */ static int tegra_dram_temp_start(void) { int err; pr_info("[dram-therm] Starting DRAM temperature polling.\n"); err = mod_timer(&emc_mr4_timer, jiffies + msecs_to_jiffies(emc_mr4_sample_interval)); if (err) return err; atomic_set(&do_poll, 1); return mod_timer(&emc_mr4_timer, jiffies + msecs_to_jiffies(emc_mr4_sample_interval)); } /* * Stop the DRAM thermal driver from polling for the DRAM temperature. If there * is no reason to expect the DRAM to be very hot then there is no reason to * poll for the DRAM's temperature. */ static void tegra_dram_temp_stop(void) { pr_info("[dram-therm] Stopping DRAM temperature polling.\n"); atomic_set(&do_poll, 0); } static int tegra_dram_cd_max_state(struct thermal_cooling_device *tcd, unsigned long *state) { *state = TEGRA_DRAM_THERM_MAX_STATE; return 0; } static int tegra_dram_cd_cur_state(struct thermal_cooling_device *tcd, unsigned long *state) { *state = (unsigned long)atomic_read(&do_poll); return 0; } static int tegra_dram_cd_set_state(struct thermal_cooling_device *tcd, unsigned long state) { if (state == (unsigned long)atomic_read(&do_poll)) return 0; if (state) tegra_dram_temp_start(); else tegra_dram_temp_stop(); return 0; } /* * Cooling device support. */ static struct thermal_cooling_device_ops emc_dram_cd_ops = { .get_max_state = tegra_dram_cd_max_state, .get_cur_state = tegra_dram_cd_cur_state, .set_cur_state = tegra_dram_cd_set_state, }; #ifdef CONFIG_DEBUG_FS static struct dentry *dram_therm_debugfs; static int __get_sample_interval(void *data, u64 *val) { *val = emc_mr4_sample_interval; return 0; } static int __set_sample_interval(void *data, u64 val) { emc_mr4_sample_interval = (unsigned long) val; return 0; } DEFINE_SIMPLE_ATTRIBUTE(sample_interval_fops, __get_sample_interval, __set_sample_interval, "%llu\n"); static int __get_test_mode(void *data, u64 *val) { *val = test_mode; return 0; } static int __set_test_mode(void *data, u64 val) { test_mode = !!val; return 0; } DEFINE_SIMPLE_ATTRIBUTE(test_mode_fops, __get_test_mode, __set_test_mode, "%llu\n"); static int __get_dram_temp_override(void *data, u64 *val) { *val = dram_temp_override; return 0; } static int __set_dram_temp_override(void *data, u64 val) { dram_temp_override = (unsigned int) val; return 0; } DEFINE_SIMPLE_ATTRIBUTE(dram_temp_override_fops, __get_dram_temp_override, __set_dram_temp_override, "%llu\n"); static int __get_do_poll(void *data, u64 *val) { *val = atomic_read(&do_poll); return 0; } static int __set_do_poll(void *data, u64 val) { atomic_set(&do_poll, (unsigned int)val); /* Explicitly wake up the DRAM monitoring thread. */ if (atomic_read(&do_poll)) tegra_dram_temp_start(); return 0; } DEFINE_SIMPLE_ATTRIBUTE(do_poll_fops, __get_do_poll, __set_do_poll, "%llu\n"); #endif static int __init tegra_dram_therm_init(void) { void *ret; ret = thermal_cooling_device_register("tegra-dram", NULL, &emc_dram_cd_ops); if (IS_ERR(ret)) return PTR_ERR(ret); if (ret == NULL) return -ENODEV; setup_timer(&emc_mr4_timer, emc_mr4_poll, 0); #ifdef CONFIG_DEBUG_FS dram_therm_debugfs = debugfs_create_dir("dram-therm", NULL); if (!dram_therm_debugfs) return -ENOMEM; if (!debugfs_create_file("sample_interval", S_IRUGO | S_IWUSR, dram_therm_debugfs, NULL, &sample_interval_fops)) return -ENOMEM; if (!debugfs_create_file("test_mode", S_IRUGO | S_IWUSR, dram_therm_debugfs, NULL, &test_mode_fops)) return -ENOMEM; if (!debugfs_create_file("dram_temp_override", S_IRUGO | S_IWUSR, dram_therm_debugfs, NULL, &dram_temp_override_fops)) return -ENOMEM; if (!debugfs_create_file("do_poll", S_IRUGO | S_IWUSR, dram_therm_debugfs, NULL, &do_poll_fops)) return -ENOMEM; #endif return 0; } late_initcall(tegra_dram_therm_init);