ARM: tegra: Module for dynamic voltage limits
Sai Gurrappadi [Fri, 8 Nov 2013 01:56:12 +0000 (17:56 -0800)]
Provide userspace with an interface to dynamically adjust
CPU <voltage, temperature> cap. Userspace must respect datasheet-imposed
limits.

Bug 1349095

Change-Id: Ie6d5834d56ad0f1d325bdfbb294c5d9189a8ea2f
Signed-off-by: Sai Gurrappadi <sgurrappadi@nvidia.com>
Reviewed-on: http://git-master/r/327986
Reviewed-by: Diwakar Tundlam <dtundlam@nvidia.com>
Tested-by: Diwakar Tundlam <dtundlam@nvidia.com>

arch/arm/mach-tegra/Kconfig
arch/arm/mach-tegra/Makefile
arch/arm/mach-tegra/board-common.c
arch/arm/mach-tegra/board-common.h
arch/arm/mach-tegra/cpu-tegra.h
arch/arm/mach-tegra/tegra_volt_cap.c [new file with mode: 0644]

index fd6553c..8b357d3 100644 (file)
@@ -593,6 +593,15 @@ config TEGRA_GPU_EDP
          to keep GPU rail current within power supply
          capabilities.
 
+config TEGRA_CPU_VOLT_CAP
+       bool "Allow cpu voltage to be capped based on usage metrics."
+       depends on TEGRA_EDP_LIMITS
+       default n
+       help
+         Provide userspace with an interface to dynamically adjust
+         CPU <voltage, temperature> cap. Userspace must respect
+         datasheet-imposed limits.
+
 config TEGRA_EMC_TO_DDR_CLOCK
        int "EMC to DDR clocks ratio"
        default "2" if ARCH_TEGRA_2x_SOC
index 555bbbc..74ecff1 100644 (file)
@@ -22,6 +22,7 @@ endif
 
 obj-$(CONFIG_PM_SLEEP)                  += tegra-wakeups.o
 
+obj-$(CONFIG_TEGRA_CPU_VOLT_CAP)       += tegra_volt_cap.o
 ifeq ($(CONFIG_ARCH_TEGRA_2x_SOC),y)
 obj-y                                   += common-t2.o
 obj-y                                   += pm-t2.o
index df2bb69..f379203 100644 (file)
@@ -30,6 +30,7 @@
 #include "devices.h"
 #include "clock.h"
 #include "dvfs.h"
+#include "cpu-tegra.h"
 
 extern unsigned long  debug_uart_port_base;
 extern struct clk *debug_uart_clk;
@@ -184,3 +185,8 @@ void tegra_add_tgpu_trips(struct thermal_trip_info *trips, int *num_trips)
 {
        tegra_add_trip_points(trips, num_trips, tegra_dvfs_get_gpu_vts_cdev());
 }
+
+void tegra_add_vc_trips(struct thermal_trip_info *trips, int *num_trips)
+{
+       tegra_add_trip_points(trips, num_trips, tegra_vc_get_cdev());
+}
index 4f4810a..db13f90 100644 (file)
@@ -30,4 +30,6 @@ int tegra_vibrator_init(void);
 void tegra_add_cdev_trips(struct thermal_trip_info *trips, int *num_trips);
 void tegra_add_tj_trips(struct thermal_trip_info *trips, int *num_trips);
 void tegra_add_tgpu_trips(struct thermal_trip_info *trips, int *num_trips);
+void tegra_add_vc_trips(struct thermal_trip_info *trips, int *num_trips);
+
 #endif
index cd11c24..9db09d6 100644 (file)
@@ -74,5 +74,11 @@ static inline int tegra_update_cpu_edp_limits(void)
 static inline int tegra_cpu_reg_mode_force_normal(bool force)
 { return 0; }
 #endif
+#ifdef CONFIG_TEGRA_CPU_VOLT_CAP
+struct tegra_cooling_device *tegra_vc_get_cdev(void);
+#else
+static inline struct tegra_cooling_device *tegra_vc_get_cdev(void)
+{ return NULL; }
+#endif
 
 #endif /* __MACH_TEGRA_CPU_TEGRA_H */
diff --git a/arch/arm/mach-tegra/tegra_volt_cap.c b/arch/arm/mach-tegra/tegra_volt_cap.c
new file mode 100644 (file)
index 0000000..bb9a0d5
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ * arch/arm/mach-tegra/tegra_volt_cap.c
+ *
+ * Copyright (c) 2013, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/kernel.h>
+#include <linux/hrtimer.h>
+#include <linux/module.h>
+#include <mach/edp.h>
+#include "cpu-tegra.h"
+
+#define WATCHDOG_TIMER_RATE            3900000 /* 1hr5min in msecs */
+#define CPU_THERMAL_ZONE_TYPE          "CPU-therm"
+#define ALTERNATE_CPU_TZ_TYPE          "Tdiode_tegra"
+
+/* If the watch dog times out, use this as the voltage cap */
+#define DEFAULT_CPU_VMAX_CAP           1180
+
+static struct timer_list watchdog_timer;
+static struct work_struct reset_cap_work;
+static struct thermal_zone_device *cpu_tz;
+
+static int cpu_vc_temperatures[] = {
+       30, 40, 50, 60, 70, 80, 90, 100, 110, 120
+};
+
+static struct tegra_cooling_device cpu_vc_cdev = {
+       .cdev_type = "tegra_cpu_vc",
+       .trip_temperatures = cpu_vc_temperatures,
+       .trip_temperatures_num = ARRAY_SIZE(cpu_vc_temperatures)
+};
+
+static int cpu_voltage_cap;
+
+struct volt_cap_data {
+       struct tegra_cooling_device *cd;
+       int thermal_idx;
+};
+
+static struct volt_cap_data cpuv_capping_data = {
+       .cd = &cpu_vc_cdev,
+       .thermal_idx = 0,
+};
+
+/* Protects cpu_voltage_cap and serializes calls to update the voltage cap */
+DEFINE_MUTEX(cpu_volt_cap_lock);
+
+static struct kobject *volt_cap_kobj;
+
+static ssize_t tegra_cpu_volt_show(struct kobject *kobj,
+       struct kobj_attribute *attr, char *buf)
+{
+       return scnprintf(buf, PAGE_SIZE, "%d\n", cpu_voltage_cap);
+}
+
+static ssize_t tegra_cpu_volt_store(struct kobject *kobj,
+                                   struct kobj_attribute *attr,
+                                   const char *buf, size_t count)
+{
+       unsigned int val, freq;
+
+       if (kstrtou32(buf, 10, &val))
+               return -EINVAL;
+
+       mutex_lock(&cpu_volt_cap_lock);
+       freq = 0;
+       if (cpu_voltage_cap != val) {
+               if (val)
+                       freq = tegra_edp_find_maxf(val) / 1000;
+               else
+                       freq = tegra_edp_find_maxf(DEFAULT_CPU_VMAX_CAP) / 1000;
+               tegra_cpu_set_volt_cap(freq);
+       }
+       cpu_voltage_cap = val;
+       mod_timer(&watchdog_timer, jiffies +
+                 msecs_to_jiffies(WATCHDOG_TIMER_RATE));
+       mutex_unlock(&cpu_volt_cap_lock);
+
+       return count;
+}
+
+static struct kobj_attribute tegra_cpu_volt =
+       __ATTR(cpu_volt, 0644, tegra_cpu_volt_show, tegra_cpu_volt_store);
+
+static ssize_t watchdog_rate_sec_show(struct kobject *kobj,
+                                     struct kobj_attribute *attr, char *buf)
+{
+       return scnprintf(buf, PAGE_SIZE, "%d\n", WATCHDOG_TIMER_RATE / 1000);
+}
+
+static struct kobj_attribute watchdog_rate =
+       __ATTR_RO(watchdog_rate_sec);
+
+const struct attribute *tegra_volt_cap_attrs[] = {
+       &tegra_cpu_volt.attr,
+       &watchdog_rate.attr,
+       NULL,
+};
+
+static int volt_cap_sysfs_init(void)
+{
+       volt_cap_kobj = kobject_create_and_add("tegra_volt_cap", kernel_kobj);
+
+       if (!volt_cap_kobj) {
+               pr_info("Tegra volt cap kobject create failed\n");
+               return -1;
+       }
+
+       if (sysfs_create_files(volt_cap_kobj, tegra_volt_cap_attrs)) {
+               pr_err("tegra_volt_cap: failed to create sysfs interface\n");
+               return -1;
+       }
+
+       return 0;
+}
+
+static struct thermal_zone_device *get_cpu_tz(void)
+{
+       struct thermal_zone_device *tz;
+       tz = thermal_zone_device_find_by_name(CPU_THERMAL_ZONE_TYPE);
+       if (!tz)
+               tz = thermal_zone_device_find_by_name(ALTERNATE_CPU_TZ_TYPE);
+       return tz;
+}
+
+/* Cooling device limits minimum rail voltage at cold temperature in pll mode */
+static int tegra_vc_get_max_state(struct thermal_cooling_device *cdev,
+                                 unsigned long *max_state)
+{
+       struct volt_cap_data *vcd = (struct volt_cap_data *)cdev->devdata;
+       *max_state = vcd->cd->trip_temperatures_num;
+       return 0;
+}
+
+static int tegra_vc_get_cur_state(struct thermal_cooling_device *cdev,
+                                 unsigned long *cur_state)
+{
+       struct volt_cap_data *vcd = (struct volt_cap_data *)cdev->devdata;
+
+       *cur_state = vcd->thermal_idx;
+       return 0;
+}
+
+static int tegra_vc_set_cur_state(struct thermal_cooling_device *cdev,
+                                 unsigned long cur_state)
+{
+       unsigned long prev_state;
+       unsigned long idx;
+       struct volt_cap_data *vcd = (struct volt_cap_data *)cdev->devdata;
+
+       prev_state = vcd->thermal_idx;
+       vcd->thermal_idx = cur_state;
+       if (prev_state != cur_state && cpu_voltage_cap != 0) {
+               if (!cpu_tz)
+                       cpu_tz = get_cpu_tz();
+               if (!cpu_tz) {
+                       pr_err("tegra_volt_cap: Couldn't find cpu tz\n");
+                       return 0;
+               }
+               idx = cur_state;
+
+               /* Actual trip point being crossed */
+               if (idx)
+                       idx = idx - 1;
+               thermal_generate_netlink_event(cpu_tz,
+                               prev_state < cur_state ? THERMAL_AUX0 :
+                               THERMAL_AUX1,
+                               vcd->cd->trip_temperatures[idx]);
+       }
+       return 0;
+}
+
+static struct thermal_cooling_device_ops tegra_vc_notify_cooling_ops = {
+       .get_max_state = tegra_vc_get_max_state,
+       .get_cur_state = tegra_vc_get_cur_state,
+       .set_cur_state = tegra_vc_set_cur_state,
+};
+
+struct tegra_cooling_device *tegra_vc_get_cdev(void)
+{
+       return &cpu_vc_cdev;
+}
+
+static void reset_cpu_volt_cap(struct work_struct *work)
+{
+       unsigned int freq;
+       mutex_lock(&cpu_volt_cap_lock);
+       cpu_voltage_cap = 0;
+       freq = tegra_edp_find_maxf(DEFAULT_CPU_VMAX_CAP) / 1000;
+       tegra_cpu_set_volt_cap(freq);
+       mutex_unlock(&cpu_volt_cap_lock);
+       pr_warn("tegra_volt_cap:Timeout. Setting default Vmax-vdd_cpu to: %d\n",
+               DEFAULT_CPU_VMAX_CAP);
+}
+
+static void watchdog_timeout(unsigned long data)
+{
+       schedule_work(&reset_cap_work);
+}
+
+static int __init tegra_volt_cap_init(void)
+{
+       struct thermal_cooling_device *tcd;
+
+       volt_cap_sysfs_init();
+
+       /* Register cpu voltage capping related cooling device */
+       tcd = thermal_cooling_device_register("tegra_cpu_vc",
+                                             &cpuv_capping_data,
+                                             &tegra_vc_notify_cooling_ops);
+       if (IS_ERR_OR_NULL(tcd))
+               pr_err("tegra cooling device %s failed to register\n",
+                      "tegra-vc");
+       cpu_tz = get_cpu_tz();
+
+       INIT_WORK(&reset_cap_work, reset_cpu_volt_cap);
+       init_timer(&watchdog_timer);
+       watchdog_timer.function = watchdog_timeout;
+
+       return 0;
+}
+
+MODULE_DESCRIPTION("Tegra voltage capping driver");
+MODULE_LICENSE("GPL");
+module_init(tegra_volt_cap_init);