drivers: misc: fan thermal estimator driver
Anshul Jain [Fri, 30 Nov 2012 11:42:42 +0000 (03:42 -0800)]
Thermal estimator driver that estimates temperature based on
a linear formula from other temperature sensors

This estimated thermal point is used to drive fan

Change-Id: Ic6f82473435901514bca47cfda7a453ab64468e0
Signed-off-by: Anshul Jain <anshulj@nvidia.com>
Reviewed-on: http://git-master/r/167700
Reviewed-by: Automatic_Commit_Validation_User
Tested-by: Matt Wagner <mwagner@nvidia.com>
GVS: Gerrit_Virtual_Submit
Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>

drivers/misc/Kconfig
drivers/misc/Makefile
drivers/misc/therm_fan_est.c [new file with mode: 0644]
include/linux/therm_est.h

index c5864b3..ad2468e 100644 (file)
@@ -579,6 +579,15 @@ config TEGRA_THROUGHPUT
        ---help---
        Dev node /dev/tegra-throughput used to set a throughput target.
 
+config FAN_THERM_EST
+       bool "Fan driving temp estimator"
+       ---help---
+       Thermal driver that estimates the fan driving temperature based on
+       other sensors.
+
+       This enables a virtual sensor that polls other registered thermal
+       zone devices for temperature updates.
+
 config BLUEDROID_PM
         tristate "Bluedroid_pm driver support"
         help
index 606c955..294f2e5 100644 (file)
@@ -66,4 +66,5 @@ obj-$(CONFIG_THERM_EST)               += therm_est.o
 obj-$(CONFIG_TEGRA_THROUGHPUT) += tegra-throughput.o
 obj-$(CONFIG_SND_SOC_TEGRA_CS42L73)    += a2220.o
 obj-y                          += tfa9887.o
+obj-$(CONFIG_FAN_THERM_EST)    += therm_fan_est.o
 obj-$(CONFIG_BLUEDROID_PM)      += bluedroid_pm.o
diff --git a/drivers/misc/therm_fan_est.c b/drivers/misc/therm_fan_est.c
new file mode 100644 (file)
index 0000000..3d26137
--- /dev/null
@@ -0,0 +1,355 @@
+/*
+ * drivers/misc/therm_fan_est.c
+ *
+ * Copyright (C) 2010-2012 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/platform_device.h>
+#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/slab.h>
+#include <linux/syscalls.h>
+#include <linux/therm_est.h>
+#include <linux/thermal.h>
+#include <linux/module.h>
+#include <linux/hwmon-sysfs.h>
+
+struct therm_fan_estimator {
+       long cur_temp;
+       long polling_period;
+       struct workqueue_struct *workqueue;
+       struct delayed_work therm_fan_est_work;
+       long toffset;
+       int ntemp;
+       int ndevs;
+       struct therm_fan_est_subdevice *devs;
+       struct thermal_zone_device *thz;
+       int active_trip_temps[MAX_ACTIVE_STATES];
+};
+
+static void therm_fan_est_work_func(struct work_struct *work)
+{
+       int i, j, index, sum = 0;
+       long temp;
+       struct delayed_work *dwork = container_of(work,
+                                       struct delayed_work, work);
+       struct therm_fan_estimator *est = container_of(
+                                       dwork,
+                                       struct therm_fan_estimator,
+                                       therm_fan_est_work);
+
+       for (i = 0; i < est->ndevs; i++) {
+               if (est->devs[i].get_temp(est->devs[i].dev_data, &temp))
+                       continue;
+               est->devs[i].hist[(est->ntemp % HIST_LEN)] = temp;
+       }
+
+       for (i = 0; i < est->ndevs; i++) {
+               for (j = 0; j < HIST_LEN; j++) {
+                       index = (est->ntemp - j + HIST_LEN) % HIST_LEN;
+                       sum += est->devs[i].hist[index] *
+                               est->devs[i].coeffs[j];
+               }
+       }
+
+       est->cur_temp = sum / 100 + est->toffset;
+
+       est->ntemp++;
+
+       if (est->thz)
+               thermal_zone_device_update(est->thz);
+
+       queue_delayed_work(est->workqueue, &est->therm_fan_est_work,
+                               msecs_to_jiffies(est->polling_period));
+}
+
+static int therm_fan_est_bind(struct thermal_zone_device *thz,
+                               struct thermal_cooling_device *cdev)
+{
+       int i;
+
+       if (!strcmp(cdev->type, "pwm-fan")) {
+               for (i = 0; i < MAX_ACTIVE_STATES; i++)
+                       thermal_zone_bind_cooling_device(thz, i, cdev,
+                                                               i + 1,  i + 1);
+       }
+
+       return 0;
+}
+
+static int therm_fan_est_unbind(struct thermal_zone_device *thz,
+                               struct thermal_cooling_device *cdev)
+{
+       int i;
+
+       if (!strcmp(cdev->type, "pwm-fan")) {
+               for (i = 0; i < MAX_ACTIVE_STATES; i++)
+                       thermal_zone_unbind_cooling_device(thz, i, cdev);
+       }
+
+       return 0;
+}
+
+static int therm_fan_est_get_trip_type(struct thermal_zone_device *thz,
+                                       int trip,
+                                       enum thermal_trip_type *type)
+{
+       *type = THERMAL_TRIP_ACTIVE;
+       return 0;
+}
+
+static int therm_fan_est_get_trip_temp(struct thermal_zone_device *thz,
+                                       int trip,
+                                       unsigned long *temp)
+{
+       struct therm_fan_estimator *est = thz->devdata;
+
+       *temp = est->active_trip_temps[trip];
+       return 0;
+}
+
+static int therm_fan_est_set_trip_temp(struct thermal_zone_device *thz,
+                                       int trip,
+                                       unsigned long temp)
+{
+       struct therm_fan_estimator *est = thz->devdata;
+
+       est->active_trip_temps[trip] = temp;
+       return 0;
+}
+
+static int therm_fan_est_get_temp(struct thermal_zone_device *thz,
+                               unsigned long *temp)
+{
+       struct therm_fan_estimator *est = thz->devdata;
+
+       *temp = est->cur_temp;
+       return 0;
+}
+
+static int therm_fan_est_get_trend(struct thermal_zone_device *thz,
+                                       int trip, enum thermal_trend *trend)
+{
+       *trend = THERMAL_TREND_RAISING;
+       return 0;
+}
+
+static struct thermal_zone_device_ops therm_fan_est_ops = {
+       .bind = therm_fan_est_bind,
+       .unbind = therm_fan_est_unbind,
+       .get_trip_type = therm_fan_est_get_trip_type,
+       .get_trip_temp = therm_fan_est_get_trip_temp,
+       .get_temp = therm_fan_est_get_temp,
+       .get_trend = therm_fan_est_get_trend,
+       .set_trip_temp = therm_fan_est_set_trip_temp,
+};
+
+static ssize_t show_coeff(struct device *dev,
+                               struct device_attribute *da,
+                               char *buf)
+{
+       struct therm_fan_estimator *est = dev_get_drvdata(dev);
+       ssize_t len, total_len = 0;
+       int i, j;
+
+       for (i = 0; i < est->ndevs; i++) {
+               len = snprintf(buf + total_len, PAGE_SIZE, "[%d]", i);
+               total_len += len;
+               for (j = 0; j < HIST_LEN; j++) {
+                       len = snprintf(buf + total_len, PAGE_SIZE, " %ld",
+                                       est->devs[i].coeffs[j]);
+                       total_len += len;
+               }
+               len = snprintf(buf + total_len, PAGE_SIZE, "\n");
+               total_len += len;
+       }
+       return strlen(buf);
+}
+
+static ssize_t set_coeff(struct device *dev,
+                               struct device_attribute *da,
+                               const char *buf, size_t count)
+{
+       struct therm_fan_estimator *est = dev_get_drvdata(dev);
+       int devid, scount;
+       long coeff[20];
+
+       if (HIST_LEN > 20)
+               return -EINVAL;
+
+       scount = sscanf(buf, "[%d] %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld " \
+                       "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
+                       &devid, &coeff[0], &coeff[1], &coeff[2], &coeff[3],
+                       &coeff[4], &coeff[5], &coeff[6], &coeff[7], &coeff[8],
+                       &coeff[9], &coeff[10], &coeff[11], &coeff[12],
+                       &coeff[13], &coeff[14], &coeff[15], &coeff[16],
+                       &coeff[17], &coeff[18], &coeff[19]);
+
+       if (scount != HIST_LEN + 1)
+               return -1;
+
+       if (devid < 0 || devid >= est->ndevs)
+               return -EINVAL;
+
+       /* This has obvious locking issues but don't worry about it */
+       memcpy(est->devs[devid].coeffs, coeff, sizeof(long) * HIST_LEN);
+
+       return count;
+}
+
+static ssize_t show_offset(struct device *dev,
+                               struct device_attribute *da,
+                               char *buf)
+{
+       struct therm_fan_estimator *est = dev_get_drvdata(dev);
+
+       snprintf(buf, PAGE_SIZE, "%ld\n", est->toffset);
+       return strlen(buf);
+}
+
+static ssize_t set_offset(struct device *dev,
+                               struct device_attribute *da,
+                               const char *buf, size_t count)
+{
+       struct therm_fan_estimator *est = dev_get_drvdata(dev);
+       int offset;
+
+       if (kstrtoint(buf, 0, &offset))
+               return -EINVAL;
+
+       est->toffset = offset;
+
+       return count;
+}
+
+static ssize_t show_temps(struct device *dev,
+                               struct device_attribute *da,
+                               char *buf)
+{
+       struct therm_fan_estimator *est = dev_get_drvdata(dev);
+       ssize_t total_len = 0;
+       int i, j;
+       int index;
+
+       /* This has obvious locking issues but don't worry about it */
+       for (i = 0; i < est->ndevs; i++) {
+               total_len += snprintf(buf + total_len, PAGE_SIZE, "[%d]", i);
+               for (j = 0; j < HIST_LEN; j++) {
+                       index = (est->ntemp - j + HIST_LEN) % HIST_LEN;
+                       total_len += snprintf(buf + total_len,
+                                               PAGE_SIZE,
+                                               " %ld",
+                                               est->devs[i].hist[index]);
+               }
+               total_len += snprintf(buf + total_len, PAGE_SIZE, "\n");
+       }
+       return strlen(buf);
+}
+
+static struct sensor_device_attribute therm_fan_est_nodes[] = {
+       SENSOR_ATTR(coeff, S_IRUGO | S_IWUSR, show_coeff, set_coeff, 0),
+       SENSOR_ATTR(offset, S_IRUGO | S_IWUSR, show_offset, set_offset, 0),
+       SENSOR_ATTR(temps, S_IRUGO, show_temps, 0, 0),
+};
+
+static int __devinit therm_fan_est_probe(struct platform_device *pdev)
+{
+       int i, j;
+       long temp;
+       struct therm_fan_estimator *est;
+       struct therm_fan_est_subdevice *dev;
+       struct therm_fan_est_data *data;
+
+       est = devm_kzalloc(&pdev->dev,
+                               sizeof(struct therm_fan_estimator), GFP_KERNEL);
+       if (IS_ERR_OR_NULL(est))
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, est);
+
+       data = pdev->dev.platform_data;
+
+       est->devs = data->devs;
+       est->ndevs = data->ndevs;
+       est->toffset = data->toffset;
+       est->polling_period = data->polling_period;
+
+       for (i = 0; i < MAX_ACTIVE_STATES; i++)
+               est->active_trip_temps[i] = data->active_trip_temps[i];
+       /* initialize history */
+       for (i = 0; i < data->ndevs; i++) {
+               dev = &est->devs[i];
+
+               if (dev->get_temp(dev->dev_data, &temp))
+                       return -EINVAL;
+
+               for (j = 0; j < HIST_LEN; j++)
+                       dev->hist[j] = temp;
+       }
+
+       est->workqueue = alloc_workqueue(dev_name(&pdev->dev),
+                                   WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1);
+       if (!est->workqueue)
+               return -ENOMEM;
+
+       INIT_DELAYED_WORK(&est->therm_fan_est_work, therm_fan_est_work_func);
+
+       queue_delayed_work(est->workqueue,
+                               &est->therm_fan_est_work,
+                               msecs_to_jiffies(est->polling_period));
+
+       est->thz = thermal_zone_device_register((char *) dev_name(&pdev->dev),
+                                               10, 0x3FF, est,
+                                               &therm_fan_est_ops, NULL, 0, 0);
+       if (IS_ERR_OR_NULL(est->thz))
+               return -EINVAL;
+
+       for (i = 0; i < ARRAY_SIZE(therm_fan_est_nodes); i++)
+               device_create_file(&pdev->dev,
+                       &therm_fan_est_nodes[i].dev_attr);
+
+       return 0;
+}
+
+static int __devexit therm_fan_est_remove(struct platform_device *pdev)
+{
+       struct therm_fan_estimator *est = platform_get_drvdata(pdev);
+
+       if (!est)
+               return -EINVAL;
+       thermal_zone_device_unregister(est->thz);
+       return 0;
+}
+
+static struct platform_driver therm_fan_est_driver = {
+       .driver = {
+               .owner = THIS_MODULE,
+               .name  = "therm-fan-est",
+       },
+       .probe  = therm_fan_est_probe,
+       .remove = __devexit_p(therm_fan_est_remove),
+};
+
+module_platform_driver(therm_fan_est_driver);
+
+MODULE_DESCRIPTION("fan thermal estimator");
+MODULE_AUTHOR("Anshul Jain <anshulj@nvidia.com>");
+MODULE_LICENSE("GPL v2");
index 0a900ef..ad13a4a 100644 (file)
 #define _LINUX_THERM_EST_H
 
 #include <linux/workqueue.h>
-
 #define HIST_LEN (20)
 
+#define MAX_ACTIVE_STATES 10
+
 struct therm_est_subdevice {
        void *dev_data;
        int (*get_temp)(void *, long *);
@@ -40,4 +41,18 @@ struct therm_est_data {
        struct therm_est_subdevice devs[];
 };
 
+struct therm_fan_est_subdevice {
+       void *dev_data;
+       int (*get_temp)(void *, long *);
+       long coeffs[HIST_LEN];
+       long hist[HIST_LEN];
+};
+
+struct therm_fan_est_data {
+       long toffset;
+       long polling_period;
+       int ndevs;
+       int active_trip_temps[MAX_ACTIVE_STATES];
+       struct therm_fan_est_subdevice devs[];
+};
 #endif /* _LINUX_THERM_EST_H */