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