drivers: misc: therm_est: add debug sysfs nodes
[linux-2.6.git] / drivers / misc / therm_est.c
1 /*
2  * drivers/misc/therm_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_estimator {
36         long cur_temp;
37         long polling_period;
38         struct workqueue_struct *workqueue;
39         struct delayed_work therm_est_work;
40         long toffset;
41         int ntemp;
42         int ndevs;
43         struct therm_est_subdevice *devs;
44         struct thermal_zone_device *thz;
45         struct thermal_cooling_device *cdev;
46         long trip_temp;
47 };
48
49 static void therm_est_work_func(struct work_struct *work)
50 {
51         int i, j, index, sum = 0;
52         long temp;
53         struct delayed_work *dwork = container_of (work,
54                                         struct delayed_work, work);
55         struct therm_estimator *est = container_of(
56                                         dwork,
57                                         struct therm_estimator,
58                                         therm_est_work);
59
60         for (i = 0; i < est->ndevs; i++) {
61                 if (est->devs[i].get_temp(est->devs[i].dev_data, &temp))
62                         continue;
63                 est->devs[i].hist[(est->ntemp % HIST_LEN)] = temp;
64         }
65
66         for (i = 0; i < est->ndevs; i++) {
67                 for (j = 0; j < HIST_LEN; j++) {
68                         index = (est->ntemp - j + HIST_LEN) % HIST_LEN;
69                         sum += est->devs[i].hist[index] *
70                                 est->devs[i].coeffs[j];
71                 }
72         }
73
74         est->cur_temp = sum / 100 + est->toffset;
75
76         est->ntemp++;
77
78         if (est->cur_temp >= est->trip_temp)
79                 if (est->thz && !est->thz->passive)
80                         thermal_zone_device_update(est->thz);
81
82         queue_delayed_work(est->workqueue, &est->therm_est_work,
83                                 msecs_to_jiffies(est->polling_period));
84 }
85
86 static int therm_est_bind(struct thermal_zone_device *thz,
87                                 struct thermal_cooling_device *cdev)
88 {
89         struct therm_estimator *est = thz->devdata;
90
91         if (cdev == est->cdev)
92                 thermal_zone_bind_cooling_device(thz, 0, cdev);
93
94         return 0;
95 }
96
97 static int therm_est_unbind(struct thermal_zone_device *thz,
98                                 struct thermal_cooling_device *cdev)
99 {
100         struct therm_estimator *est = thz->devdata;
101
102         if (cdev == est->cdev)
103                 thermal_zone_unbind_cooling_device(thz, 0, cdev);
104
105         return 0;
106 }
107
108 static int therm_est_get_trip_type(struct thermal_zone_device *thz,
109                                         int trip,
110                                         enum thermal_trip_type *type)
111 {
112         *type = THERMAL_TRIP_PASSIVE;
113         return 0;
114 }
115
116 static int therm_est_get_trip_temp(struct thermal_zone_device *thz,
117                                         int trip,
118                                         unsigned long *temp)
119 {
120         struct therm_estimator *est = thz->devdata;
121
122         *temp = est->trip_temp;
123
124         return 0;
125 }
126
127 static int therm_est_get_temp(struct thermal_zone_device *thz,
128                                 unsigned long *temp)
129 {
130         struct therm_estimator *est = thz->devdata;
131         *temp = est->cur_temp;
132         return 0;
133 }
134
135 static struct thermal_zone_device_ops therm_est_ops = {
136         .bind = therm_est_bind,
137         .unbind = therm_est_unbind,
138         .get_trip_type = therm_est_get_trip_type,
139         .get_trip_temp = therm_est_get_trip_temp,
140         .get_temp = therm_est_get_temp,
141 };
142
143 static ssize_t show_coeff(struct device *dev,
144                                 struct device_attribute *da,
145                                 char *buf)
146 {
147         struct therm_estimator *est = dev_get_drvdata(dev);
148         ssize_t len, total_len = 0;
149         int i, j;
150         for (i = 0; i < est->ndevs; i++) {
151                 len = snprintf(buf + total_len, PAGE_SIZE, "[%d]", i);
152                 total_len += len;
153                 for (j = 0; j < HIST_LEN; j++) {
154                         len = snprintf(buf + total_len, PAGE_SIZE, " %ld",
155                                         est->devs[i].coeffs[j]);
156                         total_len += len;
157                 }
158                 len = snprintf(buf + total_len, PAGE_SIZE, "\n");
159                 total_len += len;
160         }
161         return strlen(buf);
162 }
163
164 static ssize_t set_coeff(struct device *dev,
165                                 struct device_attribute *da,
166                                 const char *buf, size_t count)
167 {
168         struct therm_estimator *est = dev_get_drvdata(dev);
169         int devid, scount;
170         long coeff[20];
171
172         if (HIST_LEN > 20)
173                 return -EINVAL;
174
175         scount = sscanf(buf, "[%d] %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld " \
176                         "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
177                         &devid,
178                         &coeff[0],
179                         &coeff[1],
180                         &coeff[2],
181                         &coeff[3],
182                         &coeff[4],
183                         &coeff[5],
184                         &coeff[6],
185                         &coeff[7],
186                         &coeff[8],
187                         &coeff[9],
188                         &coeff[10],
189                         &coeff[11],
190                         &coeff[12],
191                         &coeff[13],
192                         &coeff[14],
193                         &coeff[15],
194                         &coeff[16],
195                         &coeff[17],
196                         &coeff[18],
197                         &coeff[19]);
198
199         if (scount != HIST_LEN + 1)
200                 return -1;
201
202         if (devid < 0 || devid >= est->ndevs)
203                 return -EINVAL;
204
205         /* This has obvious locking issues but don't worry about it */
206         memcpy(est->devs[devid].coeffs, coeff, sizeof(long) * HIST_LEN);
207
208         return count;
209 }
210
211 static ssize_t show_offset(struct device *dev,
212                                 struct device_attribute *da,
213                                 char *buf)
214 {
215         struct therm_estimator *est = dev_get_drvdata(dev);
216         snprintf(buf, PAGE_SIZE, "%ld\n", est->toffset);
217         return strlen(buf);
218 }
219
220 static ssize_t set_offset(struct device *dev,
221                                 struct device_attribute *da,
222                                 const char *buf, size_t count)
223 {
224         struct therm_estimator *est = dev_get_drvdata(dev);
225         int offset;
226
227         if (kstrtoint(buf, 0, &offset))
228                 return -EINVAL;
229
230         est->toffset = offset;
231
232         return count;
233 }
234
235 static ssize_t show_temps(struct device *dev,
236                                 struct device_attrbite *da,
237                                 char *buf)
238 {
239         struct therm_estimator *est = dev_get_drvdata(dev);
240         ssize_t total_len = 0;
241         int i, j;
242         int index;
243
244         /* This has obvious locking issues but don't worry about it */
245         for (i = 0; i < est->ndevs; i++) {
246                 total_len += snprintf(buf + total_len, PAGE_SIZE, "[%d]", i);
247                 for (j = 0; j < HIST_LEN; j++) {
248                         index = (est->ntemp - j + HIST_LEN) % HIST_LEN;
249                         total_len += snprintf(buf + total_len,
250                                                 PAGE_SIZE,
251                                                 " %ld",
252                                                 est->devs[i].hist[index]);
253                 }
254                 total_len += snprintf(buf + total_len, PAGE_SIZE, "\n");
255         }
256         return strlen(buf);
257 }
258
259 static struct sensor_device_attribute therm_est_nodes[] = {
260         SENSOR_ATTR(coeff, S_IRUGO | S_IWUSR, show_coeff, set_coeff, 0),
261         SENSOR_ATTR(offset, S_IRUGO | S_IWUSR, show_offset, set_offset, 0),
262         SENSOR_ATTR(temps, S_IRUGO, show_temps, 0, 0),
263 };
264
265 static int __devinit therm_est_probe(struct platform_device *pdev)
266 {
267         int i, j;
268         long temp;
269         struct therm_estimator *est;
270         struct therm_est_subdevice *dev;
271         struct therm_est_data *data;
272
273         est = kzalloc(sizeof(struct therm_estimator), GFP_KERNEL);
274         if (IS_ERR_OR_NULL(est))
275                 return -ENOMEM;
276
277         platform_set_drvdata(pdev, est);
278
279         data = pdev->dev.platform_data;
280
281         est->devs = data->devs;
282         est->ndevs = data->ndevs;
283         est->toffset = data->toffset;
284         est->polling_period = data->polling_period;
285
286         /* initialize history */
287         for (i = 0; i < data->ndevs; i++) {
288                 dev = &est->devs[i];
289
290                 if (dev->get_temp(dev->dev_data, &temp))
291                         goto err;
292
293                 for (j = 0; j < HIST_LEN; j++)
294                         dev->hist[j] = temp;
295         }
296
297         est->workqueue = alloc_workqueue("therm_est",
298                                     WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1);
299         INIT_DELAYED_WORK(&est->therm_est_work, therm_est_work_func);
300
301         queue_delayed_work(est->workqueue,
302                                 &est->therm_est_work,
303                                 msecs_to_jiffies(est->polling_period));
304
305         est->cdev = data->cdev;
306         est->trip_temp = data->trip_temp;
307
308         est->thz = thermal_zone_device_register("therm_est",
309                                         1,
310                                         est,
311                                         &therm_est_ops,
312                                         data->tc1,
313                                         data->tc2,
314                                         data->passive_delay,
315                                         0);
316         if (IS_ERR_OR_NULL(est->thz))
317                 goto err;
318
319         for (i = 0; i < ARRAY_SIZE(therm_est_nodes); i++)
320                 device_create_file(&pdev->dev, &therm_est_nodes[i].dev_attr);
321
322         return 0;
323 err:
324         kfree(est);
325         return -EINVAL;
326 }
327
328 static int __devexit therm_est_remove(struct platform_device *pdev)
329 {
330         return 0;
331 }
332
333 static struct platform_driver therm_est_driver = {
334         .driver = {
335                 .owner = THIS_MODULE,
336                 .name  = "therm_est",
337         },
338         .probe  = therm_est_probe,
339         .remove = __devexit_p(therm_est_remove),
340 };
341
342 static int __init therm_est_driver_init(void)
343 {
344         return platform_driver_register(&therm_est_driver);
345 }
346 module_init(therm_est_driver_init);