arm: tegra: power: Device agnostic thermal driver
Joshua Primero [Tue, 4 Oct 2011 05:23:53 +0000 (22:23 -0700)]
Added a thermal driver which is agonistic to the device driver
which will make it easier to port thermal devices to android
tegra.

Reviewed-on: http://git-master/r/55883
Reviewed-on: http://git-master/r/59471
(cherry picked from commit b89dbb5a64bcb3124794f644e71658de83a4ab71)

Change-Id: Ib3c7100144ea0a98ac7c15e404d4d9e7737018e5
Signed-off-by: Joshua Primero <jprimero@nvidia.com>
Reviewed-on: http://git-master/r/66548
Reviewed-by: Diwakar Tundlam <dtundlam@nvidia.com>

Rebase-Id: R44a3c17d5434d83357c55e776ff216ba0d97992e

arch/arm/mach-tegra/Makefile
arch/arm/mach-tegra/cpu-tegra.c
arch/arm/mach-tegra/include/mach/thermal.h [new file with mode: 0644]
arch/arm/mach-tegra/tegra3_thermal.c [new file with mode: 0644]

index 94c444e..de0ee04 100644 (file)
@@ -38,6 +38,7 @@ endif
 ifeq ($(CONFIG_TEGRA_THERMAL_THROTTLE),y)
 obj-$(CONFIG_ARCH_TEGRA_3x_SOC)         += tegra3_throttle.o
 endif
+obj-$(CONFIG_ARCH_TEGRA_3x_SOC)         += tegra3_thermal.o
 obj-y                                   += sleep.o
 obj-$(CONFIG_ARCH_TEGRA_2x_SOC)         += sleep-t2.o
 obj-y                                   += mc.o
index 52551b8..0dc33b8 100644 (file)
@@ -35,6 +35,7 @@
 #include <linux/cpu.h>
 
 #include <mach/edp.h>
+#include <mach/thermal.h>
 
 #include "clock.h"
 #include "cpu-tegra.h"
@@ -643,6 +644,10 @@ static int __init tegra_cpufreq_init(void)
 
        suspend_index = table_data->suspend_index;
 
+       ret = tegra_thermal_init();
+       if (ret)
+               return ret;
+
        ret = tegra_throttle_init(&tegra_cpu_lock);
        if (ret)
                return ret;
diff --git a/arch/arm/mach-tegra/include/mach/thermal.h b/arch/arm/mach-tegra/include/mach/thermal.h
new file mode 100644 (file)
index 0000000..0d25c30
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * arch/arm/mach-tegra/thermal.h
+ *
+ * Copyright (C) 2010-2011 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.
+ *
+ */
+
+#ifndef __MACH_THERMAL_H
+#define __MACH_THERMAL_H
+
+struct tegra_thermal_ops {
+       int (*get_temp) (void *, long *);
+       int (*set_limits) (void *, long, long);
+};
+
+struct tegra_thermal {
+       void *data;
+       struct tegra_thermal_ops *ops;
+#ifdef CONFIG_TEGRA_THERMAL_SYSFS
+       struct thermal_zone_device *thz;
+#endif
+};
+
+#ifndef CONFIG_ARCH_TEGRA_2x_SOC
+int tegra_thermal_init(void);
+int tegra_thermal_exit(void);
+
+struct tegra_thermal
+       *tegra_thermal_register(void *data, struct tegra_thermal_ops *ops);
+int tegra_thermal_unregister(struct tegra_thermal *thermal);
+int tegra_thermal_alert(struct tegra_thermal *thermal);
+#else
+static inline int tegra_thermal_init(void)
+{ return 0; }
+static inline int tegra_thermal_exit(void)
+{ return 0; }
+static inline struct tegra_thermal
+       *tegra_thermal_register(void *data, struct tegra_thermal_ops *ops)
+{ return NULL; }
+static inline int tegra_thermal_unregister(struct tegra_thermal *thermal)
+{ return 0; }
+static inline int tegra_thermal_alert(struct tegra_thermal *thermal)
+{ return 0; }
+#endif
+
+#define CELSIUS_TO_MILLICELSIUS(x) ((x)*1000)
+#define MILLICELSIUS_TO_CELSIUS(x) ((x)/1000)
+
+
+
+#endif /* __MACH_THERMAL_H */
diff --git a/arch/arm/mach-tegra/tegra3_thermal.c b/arch/arm/mach-tegra/tegra3_thermal.c
new file mode 100644 (file)
index 0000000..30f8d7d
--- /dev/null
@@ -0,0 +1,286 @@
+/*
+ * arch/arm/mach-tegra/tegra3_thermal.c
+ *
+ * Copyright (C) 2010-2011 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 <linux/kernel.h>
+#include <linux/cpufreq.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/thermal.h>
+#include <mach/thermal.h>
+#include <mach/edp.h>
+#include <linux/slab.h>
+
+
+#include "clock.h"
+#include "cpu-tegra.h"
+#include "dvfs.h"
+
+#define MAX_ZONES (16)
+
+/* Thermal sysfs handles hysteresis */
+#ifndef CONFIG_TEGRA_THERMAL_SYSFS
+#define ALERT_HYSTERESIS_THROTTLE      1
+#endif
+
+#define ALERT_HYSTERESIS_EDP   3
+
+#define THROTTLING_LIMIT       (85000)
+#define MAX_LIMIT              (90000)
+
+u8 thermal_zones[MAX_ZONES];
+int thermal_zones_sz;
+static int edp_thermal_zone_val = -1;
+
+#ifndef CONFIG_TEGRA_THERMAL_SYSFS
+static bool throttle_enb;
+struct mutex mutex;
+#endif
+
+
+int __init tegra_thermal_init()
+{
+       const struct tegra_edp_limits *z;
+       int zones_sz;
+       int i;
+
+#ifndef CONFIG_TEGRA_THERMAL_SYSFS
+       mutex_init(&mutex);
+#endif
+       tegra_get_cpu_edp_limits(&z, &zones_sz);
+       zones_sz = min(zones_sz, MAX_ZONES);
+
+       for (i = 0; i < zones_sz; i++)
+               thermal_zones[i] = z[i].temperature;
+
+       thermal_zones_sz = zones_sz;
+
+       return 0;
+}
+
+
+#ifdef CONFIG_TEGRA_THERMAL_SYSFS
+
+static int tegra_thermal_zone_bind(struct thermal_zone_device *thermal,
+                               struct thermal_cooling_device *cdevice) {
+       /* Support only Thermal Throttling (1 trip) for now */
+       return thermal_zone_bind_cooling_device(thermal, 0, cdevice);
+}
+
+static int tegra_thermal_zone_unbind(struct thermal_zone_device *thermal,
+                               struct thermal_cooling_device *cdevice) {
+       /* Support only Thermal Throttling (1 trip) for now */
+       return thermal_zone_unbind_cooling_device(thermal, 0, cdevice);
+}
+
+static int tegra_thermal_zone_get_temp(struct thermal_zone_device *thz,
+                                               long *temp)
+{
+       struct tegra_thermal *thermal = thz->devdata;
+       thermal->ops->get_temp(thermal->data, temp);
+
+       return 0;
+}
+
+static int tegra_thermal_zone_get_trip_type(
+                       struct thermal_zone_device *thermal,
+                       int trip,
+                       enum thermal_trip_type *type) {
+
+       /* Support only Thermal Throttling (1 trip) for now */
+       if (trip != 0)
+               return -EINVAL;
+
+       *type = THERMAL_TRIP_PASSIVE;
+
+       return 0;
+}
+
+static int tegra_thermal_zone_get_trip_temp(struct thermal_zone_device *thermal,
+                                               int trip,
+                                               long *temp) {
+       /* Support only Thermal Throttling (1 trip) for now */
+       if (trip != 0)
+               return -EINVAL;
+
+       *temp = THROTTLING_LIMIT;
+
+       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
+
+
+
+struct tegra_thermal
+*tegra_thermal_register(void *data, struct tegra_thermal_ops *thermal_ops)
+{
+       long temp_milli;
+       struct tegra_thermal *thermal;
+#ifdef CONFIG_THERMAL_SYSFS
+       struct thermal_zone_device *thz;
+#endif
+
+       thermal = kzalloc(sizeof(struct tegra_thermal), GFP_KERNEL);
+       if (!thermal)
+               return ERR_PTR(-ENOMEM);
+
+       thermal->ops = thermal_ops;
+       thermal->data = data;
+
+#ifdef CONFIG_TEGRA_THERMAL_SYSFS
+       thz = thermal_zone_device_register("nct1008",
+                                       1, /* trips */
+                                       thermal,
+                                       &tegra_thermal_zone_ops,
+                                       2, /* tc1 */
+                                       1, /* tc2 */
+                                       2000, /* passive delay */
+                                       0); /* polling delay */
+
+       if (IS_ERR(thz)) {
+               thz = NULL;
+               kfree(thermal);
+               return ERR_PTR(-ENODEV);
+       }
+
+       thermal->thz = thz;
+#endif
+
+       thermal->ops->get_temp(thermal->data, &temp_milli);
+       tegra_edp_update_thermal_zone(MILLICELSIUS_TO_CELSIUS(temp_milli));
+
+       return thermal;
+}
+
+int tegra_thermal_unregister(struct tegra_thermal *thermal)
+{
+#ifdef CONFIG_TEGRA_THERMAL_SYSFS
+       if (thermal->thz)
+               thermal_zone_device_unregister(thermal->thz);
+#endif
+
+       kfree(thermal);
+
+       return 0;
+}
+
+/* The thermal sysfs handles notifying the throttling
+ * cooling device */
+#ifndef CONFIG_TEGRA_THERMAL_SYSFS
+static void tegra_therm_throttle(bool enable)
+{
+       if (throttle_enb != enable) {
+               mutex_lock(&mutex);
+               tegra_throttling_enable(enable);
+               throttle_enb = enable;
+               mutex_unlock(&mutex);
+       }
+}
+#endif
+
+int tegra_thermal_alert(struct tegra_thermal *thermal)
+{
+       int err;
+       int hysteresis;
+       long temp, tzone1, tzone2;
+       int lo_limit = 0, hi_limit = 0;
+       int nentries = thermal_zones_sz;
+       int i;
+
+       err = thermal->ops->get_temp(thermal->data, &temp);
+       if (err) {
+               pr_err("%s: get temp fail(%d)", __func__, err);
+               return err;
+       }
+
+       hysteresis = ALERT_HYSTERESIS_EDP;
+
+#ifndef CONFIG_TEGRA_THERMAL_SYSFS
+       if (temp >= THROTTLING_LIMIT) {
+               /* start throttling */
+               tegra_therm_throttle(true);
+               hysteresis = ALERT_HYSTERESIS_THROTTLE;
+       } else if (temp <=
+                       (THROTTLING_LIMIT -
+                       ALERT_HYSTERESIS_THROTTLE)) {
+               /* switch off throttling */
+               tegra_therm_throttle(false);
+       }
+#endif
+
+       if (temp < CELSIUS_TO_MILLICELSIUS(thermal_zones[0])) {
+               lo_limit = 0;
+               hi_limit = thermal_zones[0];
+       } else if (temp >=
+                       CELSIUS_TO_MILLICELSIUS(thermal_zones[nentries-1])) {
+               lo_limit = thermal_zones[nentries-1] - hysteresis;
+               hi_limit = MILLICELSIUS_TO_CELSIUS(MAX_LIMIT);
+       } else {
+               for (i = 0; (i + 1) < nentries; i++) {
+                       tzone1 = thermal_zones[i];
+                       tzone2 = thermal_zones[i + 1];
+
+                       if (temp >= CELSIUS_TO_MILLICELSIUS(tzone1) &&
+                           temp < CELSIUS_TO_MILLICELSIUS(tzone2)) {
+                               lo_limit = tzone1 - hysteresis;
+                               hi_limit = tzone2;
+                               break;
+                       }
+               }
+       }
+
+       err = thermal->ops->set_limits(thermal->data, lo_limit, hi_limit);
+
+       if (err)
+               return err;
+
+       /* inform edp governor */
+       if (edp_thermal_zone_val != temp)
+               /*
+                * FIXME: Move this direct tegra_ function call to be called
+                * via a pointer in 'struct nct1008_data' (like 'alarm_fn')
+                */
+               tegra_edp_update_thermal_zone(MILLICELSIUS_TO_CELSIUS(temp));
+
+       edp_thermal_zone_val = temp;
+
+#ifdef CONFIG_TEGRA_THERMAL_SYSFS
+       if (thermal->thz) {
+               if (!thermal->thz->passive)
+                       thermal_zone_device_update(thermal->thz);
+       }
+#endif
+
+       return 0;
+}
+
+int tegra_thermal_exit(void)
+{
+       return 0;
+}