drivers: misc: therm_est: Refactored therm_est
[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
34 struct therm_estimator {
35         long cur_temp;
36         long polling_period;
37         struct workqueue_struct *workqueue;
38         struct delayed_work therm_est_work;
39         long toffset;
40         int ntemp;
41         int ndevs;
42         struct therm_est_subdevice *devs;
43         struct thermal_zone_device *thz;
44         struct thermal_cooling_device *cdev;
45         long trip_temp;
46 };
47
48 static void therm_est_work_func(struct work_struct *work)
49 {
50         int i, j, index, sum = 0;
51         long temp;
52         struct delayed_work *dwork = container_of (work,
53                                         struct delayed_work, work);
54         struct therm_estimator *est = container_of(
55                                         dwork,
56                                         struct therm_estimator,
57                                         therm_est_work);
58
59         for (i = 0; i < est->ndevs; i++) {
60                 if (est->devs[i].get_temp(est->devs[i].dev_data, &temp))
61                         continue;
62                 est->devs[i].hist[(est->ntemp % HIST_LEN)] = temp;
63         }
64
65         for (i = 0; i < est->ndevs; i++) {
66                 for (j = 0; j < HIST_LEN; j++) {
67                         index = (est->ntemp - j + HIST_LEN) % HIST_LEN;
68                         sum += est->devs[i].hist[index] *
69                                 est->devs[i].coeffs[j];
70                 }
71         }
72
73         est->cur_temp = sum / 100 + est->toffset;
74
75         est->ntemp++;
76
77         if (est->cur_temp >= est->trip_temp)
78                 if (est->thz && !est->thz->passive)
79                         thermal_zone_device_update(est->thz);
80
81         queue_delayed_work(est->workqueue, &est->therm_est_work,
82                                 msecs_to_jiffies(est->polling_period));
83 }
84
85 static int therm_est_bind(struct thermal_zone_device *thz,
86                                 struct thermal_cooling_device *cdev)
87 {
88         struct therm_estimator *est = thz->devdata;
89
90         if (cdev == est->cdev)
91                 thermal_zone_bind_cooling_device(thz, 0, cdev);
92
93         return 0;
94 }
95
96 static int therm_est_unbind(struct thermal_zone_device *thz,
97                                 struct thermal_cooling_device *cdev)
98 {
99         struct therm_estimator *est = thz->devdata;
100
101         if (cdev == est->cdev)
102                 thermal_zone_unbind_cooling_device(thz, 0, cdev);
103
104         return 0;
105 }
106
107 static int therm_est_get_trip_type(struct thermal_zone_device *thz,
108                                         int trip,
109                                         enum thermal_trip_type *type)
110 {
111         *type = THERMAL_TRIP_PASSIVE;
112         return 0;
113 }
114
115 static int therm_est_get_trip_temp(struct thermal_zone_device *thz,
116                                         int trip,
117                                         unsigned long *temp)
118 {
119         struct therm_estimator *est = thz->devdata;
120
121         *temp = est->trip_temp;
122
123         return 0;
124 }
125
126 static int therm_est_get_temp(struct thermal_zone_device *thz,
127                                 unsigned long *temp)
128 {
129         struct therm_estimator *est = thz->devdata;
130         *temp = est->cur_temp;
131         return 0;
132 }
133
134 static struct thermal_zone_device_ops therm_est_ops = {
135         .bind = therm_est_bind,
136         .unbind = therm_est_unbind,
137         .get_trip_type = therm_est_get_trip_type,
138         .get_trip_temp = therm_est_get_trip_temp,
139         .get_temp = therm_est_get_temp,
140 };
141
142 static int __devinit therm_est_probe(struct platform_device *pdev)
143 {
144         int i, j;
145         long temp;
146         struct therm_estimator *est;
147         struct therm_est_subdevice *dev;
148         struct therm_est_data *data;
149
150         est = kzalloc(sizeof(struct therm_estimator), GFP_KERNEL);
151         if (IS_ERR_OR_NULL(est))
152                 return -ENOMEM;
153
154         platform_set_drvdata(pdev, est);
155
156         data = pdev->dev.platform_data;
157
158         est->devs = data->devs;
159         est->ndevs = data->ndevs;
160         est->toffset = data->toffset;
161         est->polling_period = data->polling_period;
162
163         /* initialize history */
164         for (i = 0; i < data->ndevs; i++) {
165                 dev = &est->devs[i];
166
167                 if (dev->get_temp(dev->dev_data, &temp))
168                         goto err;
169
170                 for (j = 0; j < HIST_LEN; j++)
171                         dev->hist[j] = temp;
172         }
173
174         est->workqueue = alloc_workqueue("therm_est",
175                                     WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1);
176         INIT_DELAYED_WORK(&est->therm_est_work, therm_est_work_func);
177
178         queue_delayed_work(est->workqueue,
179                                 &est->therm_est_work,
180                                 msecs_to_jiffies(est->polling_period));
181
182         est->cdev = data->cdev;
183         est->trip_temp = data->trip_temp;
184
185         est->thz = thermal_zone_device_register("therm_est",
186                                         1,
187                                         est,
188                                         &therm_est_ops,
189                                         data->tc1,
190                                         data->tc2,
191                                         data->passive_delay,
192                                         0);
193         if (IS_ERR_OR_NULL(est->thz))
194                 goto err;
195
196         return 0;
197 err:
198         kfree(est);
199         return -EINVAL;
200 }
201
202 static int __devexit therm_est_remove(struct platform_device *pdev)
203 {
204         return 0;
205 }
206
207 static struct platform_driver therm_est_driver = {
208         .driver = {
209                 .owner = THIS_MODULE,
210                 .name  = "therm_est",
211         },
212         .probe  = therm_est_probe,
213         .remove = __devexit_p(therm_est_remove),
214 };
215
216 static int __init therm_est_driver_init(void)
217 {
218         return platform_driver_register(&therm_est_driver);
219 }
220 module_init(therm_est_driver_init);