drivers: make tegra-throughput driver always post fps
[linux-2.6.git] / drivers / misc / therm_fan_est.c
1 /*
2  * drivers/misc/therm_fan_est.c
3  *
4  * Copyright (C) 2010-2012 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
35 struct therm_fan_estimator {
36         long cur_temp;
37         long polling_period;
38         struct workqueue_struct *workqueue;
39         struct delayed_work therm_fan_est_work;
40         long toffset;
41         int ntemp;
42         int ndevs;
43         struct therm_fan_est_subdevice *devs;
44         struct thermal_zone_device *thz;
45         int current_trip_index;
46         char *cdev_type;
47         int active_trip_temps[MAX_ACTIVE_STATES];
48         int active_hysteresis[MAX_ACTIVE_STATES];
49         int active_trip_temps_hyst[(MAX_ACTIVE_STATES << 1) + 1];
50 };
51
52
53 static void fan_set_trip_temp_hyst(struct therm_fan_estimator *est, int trip,
54                                                         unsigned long hyst_temp,
55                                                         unsigned long trip_temp)
56 {
57         est->active_hysteresis[trip] = hyst_temp;
58         est->active_trip_temps[trip] = trip_temp;
59         est->active_trip_temps_hyst[(trip << 1)] = trip_temp;
60         est->active_trip_temps_hyst[((trip - 1) << 1) + 1] =
61                                                 trip_temp - hyst_temp;
62 }
63
64 static void therm_fan_est_work_func(struct work_struct *work)
65 {
66         int i, j, index, trip_index, sum = 0;
67         long temp;
68         struct delayed_work *dwork = container_of(work,
69                                         struct delayed_work, work);
70         struct therm_fan_estimator *est = container_of(
71                                         dwork,
72                                         struct therm_fan_estimator,
73                                         therm_fan_est_work);
74
75         for (i = 0; i < est->ndevs; i++) {
76                 if (est->devs[i].get_temp(est->devs[i].dev_data, &temp))
77                         continue;
78                 est->devs[i].hist[(est->ntemp % HIST_LEN)] = temp;
79         }
80
81         for (i = 0; i < est->ndevs; i++) {
82                 for (j = 0; j < HIST_LEN; j++) {
83                         index = (est->ntemp - j + HIST_LEN) % HIST_LEN;
84                         sum += est->devs[i].hist[index] *
85                                 est->devs[i].coeffs[j];
86                 }
87         }
88
89         est->cur_temp = sum / 100 + est->toffset;
90
91         for (trip_index = 0;
92                 trip_index < ((MAX_ACTIVE_STATES << 1) + 1); trip_index++) {
93                 if (est->cur_temp < est->active_trip_temps_hyst[trip_index])
94                         break;
95         }
96
97         if (est->current_trip_index != (trip_index - 1)) {
98                 est->current_trip_index = trip_index - 1;
99                 if (!((trip_index - 1) % 2))
100                         thermal_zone_device_update(est->thz);
101         }
102
103         est->ntemp++;
104
105         queue_delayed_work(est->workqueue, &est->therm_fan_est_work,
106                                 msecs_to_jiffies(est->polling_period));
107 }
108
109 static int therm_fan_est_bind(struct thermal_zone_device *thz,
110                                 struct thermal_cooling_device *cdev)
111 {
112         int i;
113         struct therm_fan_estimator *est = thz->devdata;
114         if (!strcmp(cdev->type, est->cdev_type)) {
115                 for (i = 0; i < MAX_ACTIVE_STATES; i++)
116                         thermal_zone_bind_cooling_device(thz, i, cdev, i, i);
117         }
118
119         return 0;
120 }
121
122 static int therm_fan_est_unbind(struct thermal_zone_device *thz,
123                                 struct thermal_cooling_device *cdev)
124 {
125         int i;
126         struct therm_fan_estimator *est = thz->devdata;
127         if (!strcmp(cdev->type, est->cdev_type)) {
128                 for (i = 0; i < MAX_ACTIVE_STATES; i++)
129                         thermal_zone_unbind_cooling_device(thz, i, cdev);
130         }
131
132         return 0;
133 }
134
135 static int therm_fan_est_get_trip_type(struct thermal_zone_device *thz,
136                                         int trip,
137                                         enum thermal_trip_type *type)
138 {
139         *type = THERMAL_TRIP_ACTIVE;
140         return 0;
141 }
142
143 static int therm_fan_est_get_trip_temp(struct thermal_zone_device *thz,
144                                         int trip,
145                                         unsigned long *temp)
146 {
147         struct therm_fan_estimator *est = thz->devdata;
148
149         *temp = est->active_trip_temps[trip];
150         return 0;
151 }
152
153 static int therm_fan_est_set_trip_temp(struct thermal_zone_device *thz,
154                                         int trip,
155                                         unsigned long temp)
156 {
157         struct therm_fan_estimator *est = thz->devdata;
158
159         /*Need trip 0 to remain as it is*/
160         if (((temp - est->active_hysteresis[trip]) < 0) || (trip <= 0))
161                 return -EINVAL;
162
163         fan_set_trip_temp_hyst(est, trip, est->active_hysteresis[trip], temp);
164         return 0;
165 }
166
167 static int therm_fan_est_get_temp(struct thermal_zone_device *thz,
168                                 unsigned long *temp)
169 {
170         struct therm_fan_estimator *est = thz->devdata;
171
172         *temp = est->cur_temp;
173         return 0;
174 }
175
176 static int therm_fan_est_set_trip_hyst(struct thermal_zone_device *thz,
177                                 int trip, unsigned long hyst_temp)
178 {
179         struct therm_fan_estimator *est = thz->devdata;
180
181         /*Need trip 0 to remain as it is*/
182         if ((est->active_trip_temps[trip] - hyst_temp) < 0 || trip <= 0)
183                 return -EINVAL;
184
185         fan_set_trip_temp_hyst(est, trip,
186                         hyst_temp, est->active_trip_temps[trip]);
187         return 0;
188 }
189
190 static int therm_fan_est_get_trip_hyst(struct thermal_zone_device *thz,
191                                 int trip, unsigned long *temp)
192 {
193         struct therm_fan_estimator *est = thz->devdata;
194
195         *temp = est->active_hysteresis[trip];
196         return 0;
197 }
198
199 static struct thermal_zone_device_ops therm_fan_est_ops = {
200         .bind = therm_fan_est_bind,
201         .unbind = therm_fan_est_unbind,
202         .get_trip_type = therm_fan_est_get_trip_type,
203         .get_trip_temp = therm_fan_est_get_trip_temp,
204         .get_temp = therm_fan_est_get_temp,
205         .set_trip_temp = therm_fan_est_set_trip_temp,
206         .get_trip_hyst = therm_fan_est_get_trip_hyst,
207         .set_trip_hyst = therm_fan_est_set_trip_hyst,
208 };
209
210 static ssize_t show_coeff(struct device *dev,
211                                 struct device_attribute *da,
212                                 char *buf)
213 {
214         struct therm_fan_estimator *est = dev_get_drvdata(dev);
215         ssize_t len, total_len = 0;
216         int i, j;
217
218         for (i = 0; i < est->ndevs; i++) {
219                 len = snprintf(buf + total_len, PAGE_SIZE, "[%d]", i);
220                 total_len += len;
221                 for (j = 0; j < HIST_LEN; j++) {
222                         len = snprintf(buf + total_len, PAGE_SIZE, " %ld",
223                                         est->devs[i].coeffs[j]);
224                         total_len += len;
225                 }
226                 len = snprintf(buf + total_len, PAGE_SIZE, "\n");
227                 total_len += len;
228         }
229         return strlen(buf);
230 }
231
232 static ssize_t set_coeff(struct device *dev,
233                                 struct device_attribute *da,
234                                 const char *buf, size_t count)
235 {
236         struct therm_fan_estimator *est = dev_get_drvdata(dev);
237         int devid, scount;
238         long coeff[20];
239
240         if (HIST_LEN > 20)
241                 return -EINVAL;
242
243         scount = sscanf(buf, "[%d] %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld " \
244                         "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
245                         &devid, &coeff[0], &coeff[1], &coeff[2], &coeff[3],
246                         &coeff[4], &coeff[5], &coeff[6], &coeff[7], &coeff[8],
247                         &coeff[9], &coeff[10], &coeff[11], &coeff[12],
248                         &coeff[13], &coeff[14], &coeff[15], &coeff[16],
249                         &coeff[17], &coeff[18], &coeff[19]);
250
251         if (scount != HIST_LEN + 1)
252                 return -1;
253
254         if (devid < 0 || devid >= est->ndevs)
255                 return -EINVAL;
256
257         /* This has obvious locking issues but don't worry about it */
258         memcpy(est->devs[devid].coeffs, coeff, sizeof(long) * HIST_LEN);
259
260         return count;
261 }
262
263 static ssize_t show_offset(struct device *dev,
264                                 struct device_attribute *da,
265                                 char *buf)
266 {
267         struct therm_fan_estimator *est = dev_get_drvdata(dev);
268
269         snprintf(buf, PAGE_SIZE, "%ld\n", est->toffset);
270         return strlen(buf);
271 }
272
273 static ssize_t set_offset(struct device *dev,
274                                 struct device_attribute *da,
275                                 const char *buf, size_t count)
276 {
277         struct therm_fan_estimator *est = dev_get_drvdata(dev);
278         int offset;
279
280         if (kstrtoint(buf, 0, &offset))
281                 return -EINVAL;
282
283         est->toffset = offset;
284
285         return count;
286 }
287
288 static ssize_t show_temps(struct device *dev,
289                                 struct device_attribute *da,
290                                 char *buf)
291 {
292         struct therm_fan_estimator *est = dev_get_drvdata(dev);
293         ssize_t total_len = 0;
294         int i, j;
295         int index;
296
297         /* This has obvious locking issues but don't worry about it */
298         for (i = 0; i < est->ndevs; i++) {
299                 total_len += snprintf(buf + total_len, PAGE_SIZE, "[%d]", i);
300                 for (j = 0; j < HIST_LEN; j++) {
301                         index = (est->ntemp - j + HIST_LEN) % HIST_LEN;
302                         total_len += snprintf(buf + total_len,
303                                                 PAGE_SIZE,
304                                                 " %ld",
305                                                 est->devs[i].hist[index]);
306                 }
307                 total_len += snprintf(buf + total_len, PAGE_SIZE, "\n");
308         }
309         return strlen(buf);
310 }
311
312 static struct sensor_device_attribute therm_fan_est_nodes[] = {
313         SENSOR_ATTR(coeff, S_IRUGO | S_IWUSR, show_coeff, set_coeff, 0),
314         SENSOR_ATTR(offset, S_IRUGO | S_IWUSR, show_offset, set_offset, 0),
315         SENSOR_ATTR(temps, S_IRUGO, show_temps, 0, 0),
316 };
317
318 static int __devinit therm_fan_est_probe(struct platform_device *pdev)
319 {
320         int i, j;
321         long temp;
322         struct therm_fan_estimator *est;
323         struct therm_fan_est_subdevice *dev;
324         struct therm_fan_est_data *data;
325
326         est = devm_kzalloc(&pdev->dev,
327                                 sizeof(struct therm_fan_estimator), GFP_KERNEL);
328         if (IS_ERR_OR_NULL(est))
329                 return -ENOMEM;
330
331         platform_set_drvdata(pdev, est);
332
333         data = pdev->dev.platform_data;
334
335         est->devs = data->devs;
336         est->ndevs = data->ndevs;
337         est->toffset = data->toffset;
338         est->polling_period = data->polling_period;
339
340         for (i = 0; i < MAX_ACTIVE_STATES; i++) {
341                 est->active_trip_temps[i] = data->active_trip_temps[i];
342                 est->active_hysteresis[i] = data->active_hysteresis[i];
343         }
344
345         est->active_trip_temps_hyst[0] = data->active_trip_temps[0];
346
347         for (i = 1; i < MAX_ACTIVE_STATES; i++)
348                 fan_set_trip_temp_hyst(est, i,
349                         data->active_hysteresis[i], est->active_trip_temps[i]);
350
351         /* initialize history */
352         for (i = 0; i < data->ndevs; i++) {
353                 dev = &est->devs[i];
354
355                 if (dev->get_temp(dev->dev_data, &temp))
356                         return -EINVAL;
357
358                 for (j = 0; j < HIST_LEN; j++)
359                         dev->hist[j] = temp;
360         }
361
362         est->workqueue = alloc_workqueue(dev_name(&pdev->dev),
363                                     WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1);
364         if (!est->workqueue)
365                 return -ENOMEM;
366
367         est->current_trip_index = 0;
368
369         INIT_DELAYED_WORK(&est->therm_fan_est_work, therm_fan_est_work_func);
370
371         queue_delayed_work(est->workqueue,
372                                 &est->therm_fan_est_work,
373                                 msecs_to_jiffies(est->polling_period));
374         est->cdev_type = data->cdev_type;
375         est->thz = thermal_zone_device_register((char *) dev_name(&pdev->dev),
376                                                 10, 0x3FF, est,
377                                                 &therm_fan_est_ops, NULL, 0, 0);
378         if (IS_ERR_OR_NULL(est->thz))
379                 return -EINVAL;
380         for (i = 0; i < ARRAY_SIZE(therm_fan_est_nodes); i++)
381                 device_create_file(&pdev->dev,
382                         &therm_fan_est_nodes[i].dev_attr);
383
384         return 0;
385 }
386
387 static int __devexit therm_fan_est_remove(struct platform_device *pdev)
388 {
389         struct therm_fan_estimator *est = platform_get_drvdata(pdev);
390
391         if (!est)
392                 return -EINVAL;
393
394         cancel_delayed_work(&est->therm_fan_est_work);
395         thermal_zone_device_unregister(est->thz);
396
397         return 0;
398 }
399
400 #if CONFIG_PM
401 static int therm_fan_est_suspend(struct platform_device *pdev,
402                                                         pm_message_t state)
403 {
404         struct therm_fan_estimator *est = platform_get_drvdata(pdev);
405
406         if (!est)
407                 return -EINVAL;
408
409         cancel_delayed_work(&est->therm_fan_est_work);
410
411         return 0;
412 }
413
414 static int therm_fan_est_resume(struct platform_device *pdev)
415 {
416         struct therm_fan_estimator *est = platform_get_drvdata(pdev);
417
418         if (!est)
419                 return -EINVAL;
420
421         queue_delayed_work(est->workqueue,
422                                 &est->therm_fan_est_work,
423                                 msecs_to_jiffies(est->polling_period));
424         return 0;
425 }
426 #endif
427
428 static struct platform_driver therm_fan_est_driver = {
429         .driver = {
430                 .owner = THIS_MODULE,
431                 .name  = "therm-fan-est",
432         },
433         .probe  = therm_fan_est_probe,
434         .remove = __devexit_p(therm_fan_est_remove),
435 #if CONFIG_PM
436         .suspend = therm_fan_est_suspend,
437         .resume = therm_fan_est_resume,
438 #endif
439 };
440
441 module_platform_driver(therm_fan_est_driver);
442
443 MODULE_DESCRIPTION("fan thermal estimator");
444 MODULE_AUTHOR("Anshul Jain <anshulj@nvidia.com>");
445 MODULE_LICENSE("GPL v2");