drivers: misc: therm_est: add get_trend using dT/dt
[linux-2.6.git] / drivers / misc / therm_est.c
1 /*
2  * drivers/misc/therm_est.c
3  *
4  * Copyright (C) 2010-2013 NVIDIA Corporation.
5  *
6  * This software is licensed under the terms of the GNU General Public
7  * License version 2, as published by the Free Software Foundation, and
8  * may be copied, distributed, and modified under those terms.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  */
16
17 #include <linux/platform_device.h>
18 #include <linux/kernel.h>
19 #include <linux/cpufreq.h>
20 #include <linux/delay.h>
21 #include <linux/mutex.h>
22 #include <linux/init.h>
23 #include <linux/err.h>
24 #include <linux/clk.h>
25 #include <linux/debugfs.h>
26 #include <linux/seq_file.h>
27 #include <linux/uaccess.h>
28 #include <linux/slab.h>
29 #include <linux/syscalls.h>
30 #include <linux/therm_est.h>
31 #include <linux/thermal.h>
32 #include <linux/module.h>
33 #include <linux/hwmon-sysfs.h>
34 #include <linux/suspend.h>
35
36 struct therm_estimator {
37         long cur_temp;
38         long polling_period;
39         struct workqueue_struct *workqueue;
40         struct delayed_work therm_est_work;
41         long toffset;
42         int ntemp;
43         int ndevs;
44         struct therm_est_subdevice *devs;
45         struct thermal_zone_device *thz;
46         char *cdev_type;
47         long trip_temp;
48         int tc1;
49         int tc2;
50 #ifdef CONFIG_PM
51         struct notifier_block pm_nb;
52 #endif
53 };
54
55 static void therm_est_work_func(struct work_struct *work)
56 {
57         int i, j, index, sum = 0;
58         long temp;
59         struct delayed_work *dwork = container_of (work,
60                                         struct delayed_work, work);
61         struct therm_estimator *est = container_of(
62                                         dwork,
63                                         struct therm_estimator,
64                                         therm_est_work);
65
66         for (i = 0; i < est->ndevs; i++) {
67                 if (est->devs[i].get_temp(est->devs[i].dev_data, &temp))
68                         continue;
69                 est->devs[i].hist[(est->ntemp % HIST_LEN)] = temp;
70         }
71
72         for (i = 0; i < est->ndevs; i++) {
73                 for (j = 0; j < HIST_LEN; j++) {
74                         index = (est->ntemp - j + HIST_LEN) % HIST_LEN;
75                         sum += est->devs[i].hist[index] *
76                                 est->devs[i].coeffs[j];
77                 }
78         }
79
80         est->cur_temp = sum / 100 + est->toffset;
81
82         est->ntemp++;
83
84         if (est->cur_temp >= est->trip_temp)
85                 if (est->thz && !est->thz->passive)
86                         thermal_zone_device_update(est->thz);
87
88         queue_delayed_work(est->workqueue, &est->therm_est_work,
89                                 msecs_to_jiffies(est->polling_period));
90 }
91
92 static int therm_est_bind(struct thermal_zone_device *thz,
93                                 struct thermal_cooling_device *cdev)
94 {
95         struct therm_estimator *est = thz->devdata;
96
97         if (!strcmp(cdev->type, est->cdev_type))
98                 thermal_zone_bind_cooling_device(thz, 0, cdev,
99                             THERMAL_NO_LIMIT, THERMAL_NO_LIMIT);
100
101         return 0;
102 }
103
104 static int therm_est_unbind(struct thermal_zone_device *thz,
105                                 struct thermal_cooling_device *cdev)
106 {
107         struct therm_estimator *est = thz->devdata;
108
109         if (!strcmp(cdev->type, est->cdev_type))
110                 thermal_zone_unbind_cooling_device(thz, 0, cdev);
111
112         return 0;
113 }
114
115 static int therm_est_get_trip_type(struct thermal_zone_device *thz,
116                                         int trip,
117                                         enum thermal_trip_type *type)
118 {
119         *type = THERMAL_TRIP_PASSIVE;
120         return 0;
121 }
122
123 static int therm_est_get_trip_temp(struct thermal_zone_device *thz,
124                                         int trip,
125                                         unsigned long *temp)
126 {
127         struct therm_estimator *est = thz->devdata;
128
129         *temp = est->trip_temp;
130
131         return 0;
132 }
133
134 static int therm_est_set_trip_temp(struct thermal_zone_device *thz,
135                                         int trip,
136                                         unsigned long temp)
137 {
138         struct therm_estimator *est = thz->devdata;
139
140         est->trip_temp = temp;
141
142         return 0;
143 }
144
145 static int therm_est_get_temp(struct thermal_zone_device *thz,
146                                 unsigned long *temp)
147 {
148         struct therm_estimator *est = thz->devdata;
149         *temp = est->cur_temp;
150         return 0;
151 }
152
153 static int therm_est_get_trend(struct thermal_zone_device *thz,
154                                 int trip,
155                                 enum thermal_trend *trend)
156 {
157         struct therm_estimator *est = thz->devdata;
158         int new_trend;
159         int cur_temp;
160
161         cur_temp = thz->temperature;
162         new_trend = (est->tc1 * (cur_temp - thz->last_temperature)) +
163                     (est->tc2 * (cur_temp - est->trip_temp));
164
165         if (new_trend > 0)
166                 *trend = THERMAL_TREND_RAISING;
167         else if (new_trend < 0)
168                 *trend = THERMAL_TREND_DROPPING;
169         else
170                 *trend = THERMAL_TREND_STABLE;
171
172         return 0;
173 }
174
175 static struct thermal_zone_device_ops therm_est_ops = {
176         .bind = therm_est_bind,
177         .unbind = therm_est_unbind,
178         .get_trip_type = therm_est_get_trip_type,
179         .get_trip_temp = therm_est_get_trip_temp,
180         .set_trip_temp = therm_est_set_trip_temp,
181         .get_temp = therm_est_get_temp,
182         .get_trend = therm_est_get_trend,
183 };
184
185 static ssize_t show_coeff(struct device *dev,
186                                 struct device_attribute *da,
187                                 char *buf)
188 {
189         struct therm_estimator *est = dev_get_drvdata(dev);
190         ssize_t len, total_len = 0;
191         int i, j;
192         for (i = 0; i < est->ndevs; i++) {
193                 len = snprintf(buf + total_len, PAGE_SIZE, "[%d]", i);
194                 total_len += len;
195                 for (j = 0; j < HIST_LEN; j++) {
196                         len = snprintf(buf + total_len, PAGE_SIZE, " %ld",
197                                         est->devs[i].coeffs[j]);
198                         total_len += len;
199                 }
200                 len = snprintf(buf + total_len, PAGE_SIZE, "\n");
201                 total_len += len;
202         }
203         return strlen(buf);
204 }
205
206 static ssize_t set_coeff(struct device *dev,
207                                 struct device_attribute *da,
208                                 const char *buf, size_t count)
209 {
210         struct therm_estimator *est = dev_get_drvdata(dev);
211         int devid, scount;
212         long coeff[20];
213
214         if (HIST_LEN > 20)
215                 return -EINVAL;
216
217         scount = sscanf(buf, "[%d] %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld " \
218                         "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
219                         &devid,
220                         &coeff[0],
221                         &coeff[1],
222                         &coeff[2],
223                         &coeff[3],
224                         &coeff[4],
225                         &coeff[5],
226                         &coeff[6],
227                         &coeff[7],
228                         &coeff[8],
229                         &coeff[9],
230                         &coeff[10],
231                         &coeff[11],
232                         &coeff[12],
233                         &coeff[13],
234                         &coeff[14],
235                         &coeff[15],
236                         &coeff[16],
237                         &coeff[17],
238                         &coeff[18],
239                         &coeff[19]);
240
241         if (scount != HIST_LEN + 1)
242                 return -1;
243
244         if (devid < 0 || devid >= est->ndevs)
245                 return -EINVAL;
246
247         /* This has obvious locking issues but don't worry about it */
248         memcpy(est->devs[devid].coeffs, coeff, sizeof(long) * HIST_LEN);
249
250         return count;
251 }
252
253 static ssize_t show_offset(struct device *dev,
254                                 struct device_attribute *da,
255                                 char *buf)
256 {
257         struct therm_estimator *est = dev_get_drvdata(dev);
258         snprintf(buf, PAGE_SIZE, "%ld\n", est->toffset);
259         return strlen(buf);
260 }
261
262 static ssize_t set_offset(struct device *dev,
263                                 struct device_attribute *da,
264                                 const char *buf, size_t count)
265 {
266         struct therm_estimator *est = dev_get_drvdata(dev);
267         int offset;
268
269         if (kstrtoint(buf, 0, &offset))
270                 return -EINVAL;
271
272         est->toffset = offset;
273
274         return count;
275 }
276
277 static ssize_t show_temps(struct device *dev,
278                                 struct device_attribute *da,
279                                 char *buf)
280 {
281         struct therm_estimator *est = dev_get_drvdata(dev);
282         ssize_t total_len = 0;
283         int i, j;
284         int index;
285
286         /* This has obvious locking issues but don't worry about it */
287         for (i = 0; i < est->ndevs; i++) {
288                 total_len += snprintf(buf + total_len, PAGE_SIZE, "[%d]", i);
289                 for (j = 0; j < HIST_LEN; j++) {
290                         index = (est->ntemp - j + HIST_LEN) % HIST_LEN;
291                         total_len += snprintf(buf + total_len,
292                                                 PAGE_SIZE,
293                                                 " %ld",
294                                                 est->devs[i].hist[index]);
295                 }
296                 total_len += snprintf(buf + total_len, PAGE_SIZE, "\n");
297         }
298         return strlen(buf);
299 }
300
301 static struct sensor_device_attribute therm_est_nodes[] = {
302         SENSOR_ATTR(coeff, S_IRUGO | S_IWUSR, show_coeff, set_coeff, 0),
303         SENSOR_ATTR(offset, S_IRUGO | S_IWUSR, show_offset, set_offset, 0),
304         SENSOR_ATTR(temps, S_IRUGO, show_temps, 0, 0),
305 };
306
307 static int therm_est_init_history(struct therm_estimator *est)
308 {
309         int i, j;
310         struct therm_est_subdevice *dev;
311         long temp;
312
313         for (i = 0; i < est->ndevs; i++) {
314                 dev = &est->devs[i];
315
316                 if (dev->get_temp(dev->dev_data, &temp))
317                         return -EINVAL;
318
319                 for (j = 0; j < HIST_LEN; j++)
320                         dev->hist[j] = temp;
321         }
322
323         return 0;
324 }
325
326 #ifdef CONFIG_PM
327 static int therm_est_pm_notify(struct notifier_block *nb,
328                                 unsigned long event, void *data)
329 {
330         struct therm_estimator *est = container_of(
331                                         nb,
332                                         struct therm_estimator,
333                                         pm_nb);
334
335         switch (event) {
336         case PM_SUSPEND_PREPARE:
337                 cancel_delayed_work_sync(&est->therm_est_work);
338                 break;
339         case PM_POST_SUSPEND:
340                 therm_est_init_history(est);
341                 queue_delayed_work(est->workqueue,
342                                 &est->therm_est_work,
343                                 msecs_to_jiffies(est->polling_period));
344                 break;
345         }
346
347         return NOTIFY_OK;
348 }
349 #endif
350
351 static int __devinit therm_est_probe(struct platform_device *pdev)
352 {
353         int i;
354         struct therm_estimator *est;
355         struct therm_est_data *data;
356
357         est = kzalloc(sizeof(struct therm_estimator), GFP_KERNEL);
358         if (IS_ERR_OR_NULL(est))
359                 return -ENOMEM;
360
361         platform_set_drvdata(pdev, est);
362
363         data = pdev->dev.platform_data;
364
365         est->devs = data->devs;
366         est->ndevs = data->ndevs;
367         est->toffset = data->toffset;
368         est->polling_period = data->polling_period;
369         est->tc1 = data->tc1;
370         est->tc2 = data->tc2;
371
372         /* initialize history */
373         therm_est_init_history(est);
374
375         est->workqueue = alloc_workqueue(dev_name(&pdev->dev),
376                                     WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1);
377         if (!est->workqueue)
378                 goto err;
379
380         INIT_DELAYED_WORK(&est->therm_est_work, therm_est_work_func);
381
382         queue_delayed_work(est->workqueue,
383                                 &est->therm_est_work,
384                                 msecs_to_jiffies(est->polling_period));
385
386         est->trip_temp = data->trip_temp;
387         est->cdev_type = data->cdev_type;
388
389         est->thz = thermal_zone_device_register(dev_name(&pdev->dev),
390                                         1,
391                                         0x1,
392                                         est,
393                                         &therm_est_ops,
394                                         NULL,
395                                         data->passive_delay,
396                                         0);
397         if (IS_ERR_OR_NULL(est->thz))
398                 goto err;
399
400         for (i = 0; i < ARRAY_SIZE(therm_est_nodes); i++)
401                 device_create_file(&pdev->dev, &therm_est_nodes[i].dev_attr);
402
403 #ifdef CONFIG_PM
404         est->pm_nb.notifier_call = therm_est_pm_notify,
405         register_pm_notifier(&est->pm_nb);
406 #endif
407
408         return 0;
409 err:
410         kfree(est);
411         return -EINVAL;
412 }
413
414 static int __devexit therm_est_remove(struct platform_device *pdev)
415 {
416         return 0;
417 }
418
419 static struct platform_driver therm_est_driver = {
420         .driver = {
421                 .owner = THIS_MODULE,
422                 .name  = "therm_est",
423         },
424         .probe  = therm_est_probe,
425         .remove = __devexit_p(therm_est_remove),
426 };
427
428 static int __init therm_est_driver_init(void)
429 {
430         return platform_driver_register(&therm_est_driver);
431 }
432 module_init(therm_est_driver_init);