ARM: tegra: emc: Add DRAM thermal monitoring code
Alex Waterman [Fri, 19 Apr 2013 17:03:23 +0000 (10:03 -0700)]
For certain DRAM chips we can monitor the DRAM temperature by reading
MR4. When this register reports a particular threshold the refresh
rate and possibly the timing parameters must be updated to ensure
correct operation of the DRAM cell.

This patch provides a simple driver that periodically polls the MR4
register and if particular thresholds are hit will tell the EMC driver
to update the timings/refresh rate.

Right now only a 4x refresh mode is used; however, it is capable of
updating the timings with minimal code addition if necessary. It should
also be possible to lower refresh intervals with this driver if the
system detects that the temp is low and the EMC clock frequency is low.

Change-Id: I813e58bb3d1b3641894d04d8850c194f9a8754bc
Signed-off-by: Alex Waterman <alexw@nvidia.com>
Reviewed-on: http://git-master/r/221129
Reviewed-by: Automatic_Commit_Validation_User
Tested-by: Diwakar Tundlam <dtundlam@nvidia.com>
Reviewed-by: Diwakar Tundlam <dtundlam@nvidia.com>

arch/arm/mach-tegra/Makefile
arch/arm/mach-tegra/tegra_emc_therm.c [new file with mode: 0644]

index 5b0d484..f377128 100644 (file)
@@ -91,6 +91,8 @@ obj-$(CONFIG_PM_GENERIC_DOMAINS)        += pm_domains.o
 obj-y                                   += dvfs.o
 obj-$(CONFIG_ARCH_TEGRA_11x_SOC)        += tegra11_dvfs.o
 obj-$(CONFIG_ARCH_TEGRA_14x_SOC)        += tegra14_dvfs.o
+obj-$(CONFIG_ARCH_TEGRA_14x_SOC)        += tegra_emc_therm.o
+
 obj-y                                   += latency_allowance.o
 obj-y                                   += tegra3_la.o
 obj-y                                   += tegra11x_la.o
diff --git a/arch/arm/mach-tegra/tegra_emc_therm.c b/arch/arm/mach-tegra/tegra_emc_therm.c
new file mode 100644 (file)
index 0000000..a1c8cbb
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+ * arch/arm/mach-tegra/therm-dram.c
+ *
+ * Copyright (C) 2013 NVIDIA Corporation
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/kthread.h>
+#include <linux/seq_file.h>
+#include <linux/thermal.h>
+#include <linux/timer.h>
+#include <linux/atomic.h>
+
+#include "tegra_emc.h"
+
+#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;
+
+       pr_info("[dram-therm] (polling)\n");
+
+       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] Disabling 4x refresh\n");
+               tegra_emc_set_over_temp_state(DRAM_OVER_TEMP_NONE);
+               break;
+       case 4:
+               /*
+                * Temp is high - 4x refresh.
+                */
+               pr_info("[dram-therm] Enabling 4x refresh\n");
+               tegra_emc_set_over_temp_state(DRAM_OVER_TEMP_REFRESH);
+               break;
+       default:
+               pr_info("[dram-therm] Enabling 4x refresh\n");
+               tegra_emc_set_over_temp_state(DRAM_OVER_TEMP_REFRESH);
+               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("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);