aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>2005-11-07 00:08:17 -0500
committerPaul Mackerras <paulus@samba.org>2005-11-07 19:17:56 -0500
commit75722d3992f57375c0cc029dcceb2334a45ceff1 (patch)
treed3f63b3ea80790c2f29ea435781c1331f17d269e /drivers
parent7d49697ef92bd2cf84ab53bd4cea82fefb197fb9 (diff)
[PATCH] ppc64: Thermal control for SMU based machines
This adds a new thermal control framework for PowerMac, along with the implementation for PowerMac8,1, PowerMac8,2 (iMac G5 rev 1 and 2), and PowerMac9,1 (latest single CPU desktop). In the future, I expect to move the older G5 thermal control to the new framework as well. Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org> Signed-off-by: Paul Mackerras <paulus@samba.org>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/macintosh/Kconfig19
-rw-r--r--drivers/macintosh/Makefile9
-rw-r--r--drivers/macintosh/smu.c2
-rw-r--r--drivers/macintosh/windfarm.h131
-rw-r--r--drivers/macintosh/windfarm_core.c426
-rw-r--r--drivers/macintosh/windfarm_cpufreq_clamp.c105
-rw-r--r--drivers/macintosh/windfarm_lm75_sensor.c263
-rw-r--r--drivers/macintosh/windfarm_pid.c145
-rw-r--r--drivers/macintosh/windfarm_pid.h84
-rw-r--r--drivers/macintosh/windfarm_pm81.c879
-rw-r--r--drivers/macintosh/windfarm_pm91.c814
-rw-r--r--drivers/macintosh/windfarm_smu_controls.c282
-rw-r--r--drivers/macintosh/windfarm_smu_sensors.c479
13 files changed, 3638 insertions, 0 deletions
diff --git a/drivers/macintosh/Kconfig b/drivers/macintosh/Kconfig
index bc3e096d84f7..a0ea44c3e8b1 100644
--- a/drivers/macintosh/Kconfig
+++ b/drivers/macintosh/Kconfig
@@ -169,6 +169,25 @@ config THERM_PM72
169 This driver provides thermostat and fan control for the desktop 169 This driver provides thermostat and fan control for the desktop
170 G5 machines. 170 G5 machines.
171 171
172config WINDFARM
173 tristate "New PowerMac thermal control infrastructure"
174
175config WINDFARM_PM81
176 tristate "Support for thermal management on iMac G5"
177 depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU
178 select I2C_PMAC_SMU
179 help
180 This driver provides thermal control for the iMacG5
181
182config WINDFARM_PM91
183 tristate "Support for thermal management on PowerMac9,1"
184 depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU
185 select I2C_PMAC_SMU
186 help
187 This driver provides thermal control for the PowerMac9,1
188 which is the recent (SMU based) single CPU desktop G5
189
190
172config ANSLCD 191config ANSLCD
173 tristate "Support for ANS LCD display" 192 tristate "Support for ANS LCD display"
174 depends on ADB_CUDA && PPC_PMAC 193 depends on ADB_CUDA && PPC_PMAC
diff --git a/drivers/macintosh/Makefile b/drivers/macintosh/Makefile
index 236291bd48a4..f4657aa81fb0 100644
--- a/drivers/macintosh/Makefile
+++ b/drivers/macintosh/Makefile
@@ -26,3 +26,12 @@ obj-$(CONFIG_ADB_MACIO) += macio-adb.o
26obj-$(CONFIG_THERM_PM72) += therm_pm72.o 26obj-$(CONFIG_THERM_PM72) += therm_pm72.o
27obj-$(CONFIG_THERM_WINDTUNNEL) += therm_windtunnel.o 27obj-$(CONFIG_THERM_WINDTUNNEL) += therm_windtunnel.o
28obj-$(CONFIG_THERM_ADT746X) += therm_adt746x.o 28obj-$(CONFIG_THERM_ADT746X) += therm_adt746x.o
29obj-$(CONFIG_WINDFARM) += windfarm_core.o
30obj-$(CONFIG_WINDFARM_PM81) += windfarm_smu_controls.o \
31 windfarm_smu_sensors.o \
32 windfarm_lm75_sensor.o windfarm_pid.o \
33 windfarm_cpufreq_clamp.o windfarm_pm81.o
34obj-$(CONFIG_WINDFARM_PM91) += windfarm_smu_controls.o \
35 windfarm_smu_sensors.o \
36 windfarm_lm75_sensor.o windfarm_pid.o \
37 windfarm_cpufreq_clamp.o windfarm_pm91.o
diff --git a/drivers/macintosh/smu.c b/drivers/macintosh/smu.c
index a83c4acf5710..e8378274d710 100644
--- a/drivers/macintosh/smu.c
+++ b/drivers/macintosh/smu.c
@@ -590,6 +590,8 @@ static void smu_expose_childs(void *unused)
590 sprintf(name, "smu-i2c-%02x", *reg); 590 sprintf(name, "smu-i2c-%02x", *reg);
591 of_platform_device_create(np, name, &smu->of_dev->dev); 591 of_platform_device_create(np, name, &smu->of_dev->dev);
592 } 592 }
593 if (device_is_compatible(np, "smu-sensors"))
594 of_platform_device_create(np, "smu-sensors", &smu->of_dev->dev);
593 } 595 }
594 596
595} 597}
diff --git a/drivers/macintosh/windfarm.h b/drivers/macintosh/windfarm.h
new file mode 100644
index 000000000000..3f0cb0312ea3
--- /dev/null
+++ b/drivers/macintosh/windfarm.h
@@ -0,0 +1,131 @@
1/*
2 * Windfarm PowerMac thermal control.
3 *
4 * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
5 * <benh@kernel.crashing.org>
6 *
7 * Released under the term of the GNU GPL v2.
8 */
9
10#ifndef __WINDFARM_H__
11#define __WINDFARM_H__
12
13#include <linux/kref.h>
14#include <linux/list.h>
15#include <linux/module.h>
16#include <linux/notifier.h>
17
18/* Display a 16.16 fixed point value */
19#define FIX32TOPRINT(f) ((f) >> 16),((((f) & 0xffff) * 1000) >> 16)
20
21/*
22 * Control objects
23 */
24
25struct wf_control;
26
27struct wf_control_ops {
28 int (*set_value)(struct wf_control *ct, s32 val);
29 int (*get_value)(struct wf_control *ct, s32 *val);
30 s32 (*get_min)(struct wf_control *ct);
31 s32 (*get_max)(struct wf_control *ct);
32 void (*release)(struct wf_control *ct);
33 struct module *owner;
34};
35
36struct wf_control {
37 struct list_head link;
38 struct wf_control_ops *ops;
39 char *name;
40 int type;
41 struct kref ref;
42};
43
44#define WF_CONTROL_TYPE_GENERIC 0
45#define WF_CONTROL_RPM_FAN 1
46#define WF_CONTROL_PWM_FAN 2
47
48
49/* Note about lifetime rules: wf_register_control() will initialize
50 * the kref and wf_unregister_control will decrement it, thus the
51 * object creating/disposing a given control shouldn't assume it
52 * still exists after wf_unregister_control has been called.
53 * wf_find_control will inc the refcount for you
54 */
55extern int wf_register_control(struct wf_control *ct);
56extern void wf_unregister_control(struct wf_control *ct);
57extern struct wf_control * wf_find_control(const char *name);
58extern int wf_get_control(struct wf_control *ct);
59extern void wf_put_control(struct wf_control *ct);
60
61static inline int wf_control_set_max(struct wf_control *ct)
62{
63 s32 vmax = ct->ops->get_max(ct);
64 return ct->ops->set_value(ct, vmax);
65}
66
67static inline int wf_control_set_min(struct wf_control *ct)
68{
69 s32 vmin = ct->ops->get_min(ct);
70 return ct->ops->set_value(ct, vmin);
71}
72
73/*
74 * Sensor objects
75 */
76
77struct wf_sensor;
78
79struct wf_sensor_ops {
80 int (*get_value)(struct wf_sensor *sr, s32 *val);
81 void (*release)(struct wf_sensor *sr);
82 struct module *owner;
83};
84
85struct wf_sensor {
86 struct list_head link;
87 struct wf_sensor_ops *ops;
88 char *name;
89 struct kref ref;
90};
91
92/* Same lifetime rules as controls */
93extern int wf_register_sensor(struct wf_sensor *sr);
94extern void wf_unregister_sensor(struct wf_sensor *sr);
95extern struct wf_sensor * wf_find_sensor(const char *name);
96extern int wf_get_sensor(struct wf_sensor *sr);
97extern void wf_put_sensor(struct wf_sensor *sr);
98
99/* For use by clients. Note that we are a bit racy here since
100 * notifier_block doesn't have a module owner field. I may fix
101 * it one day ...
102 *
103 * LOCKING NOTE !
104 *
105 * All "events" except WF_EVENT_TICK are called with an internal mutex
106 * held which will deadlock if you call basically any core routine.
107 * So don't ! Just take note of the event and do your actual operations
108 * from the ticker.
109 *
110 */
111extern int wf_register_client(struct notifier_block *nb);
112extern int wf_unregister_client(struct notifier_block *nb);
113
114/* Overtemp conditions. Those are refcounted */
115extern void wf_set_overtemp(void);
116extern void wf_clear_overtemp(void);
117extern int wf_is_overtemp(void);
118
119#define WF_EVENT_NEW_CONTROL 0 /* param is wf_control * */
120#define WF_EVENT_NEW_SENSOR 1 /* param is wf_sensor * */
121#define WF_EVENT_OVERTEMP 2 /* no param */
122#define WF_EVENT_NORMALTEMP 3 /* overtemp condition cleared */
123#define WF_EVENT_TICK 4 /* 1 second tick */
124
125/* Note: If that driver gets more broad use, we could replace the
126 * simplistic overtemp bits with "environmental conditions". That
127 * could then be used to also notify of things like fan failure,
128 * case open, battery conditions, ...
129 */
130
131#endif /* __WINDFARM_H__ */
diff --git a/drivers/macintosh/windfarm_core.c b/drivers/macintosh/windfarm_core.c
new file mode 100644
index 000000000000..6c2a471ea6c0
--- /dev/null
+++ b/drivers/macintosh/windfarm_core.c
@@ -0,0 +1,426 @@
1/*
2 * Windfarm PowerMac thermal control. Core
3 *
4 * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
5 * <benh@kernel.crashing.org>
6 *
7 * Released under the term of the GNU GPL v2.
8 *
9 * This core code tracks the list of sensors & controls, register
10 * clients, and holds the kernel thread used for control.
11 *
12 * TODO:
13 *
14 * Add some information about sensor/control type and data format to
15 * sensors/controls, and have the sysfs attribute stuff be moved
16 * generically here instead of hard coded in the platform specific
17 * driver as it us currently
18 *
19 * This however requires solving some annoying lifetime issues with
20 * sysfs which doesn't seem to have lifetime rules for struct attribute,
21 * I may have to create full features kobjects for every sensor/control
22 * instead which is a bit of an overkill imho
23 */
24
25#include <linux/types.h>
26#include <linux/errno.h>
27#include <linux/kernel.h>
28#include <linux/init.h>
29#include <linux/spinlock.h>
30#include <linux/smp_lock.h>
31#include <linux/kthread.h>
32#include <linux/jiffies.h>
33#include <linux/reboot.h>
34#include <linux/device.h>
35#include <linux/platform_device.h>
36
37#include "windfarm.h"
38
39#define VERSION "0.2"
40
41#undef DEBUG
42
43#ifdef DEBUG
44#define DBG(args...) printk(args)
45#else
46#define DBG(args...) do { } while(0)
47#endif
48
49static LIST_HEAD(wf_controls);
50static LIST_HEAD(wf_sensors);
51static DECLARE_MUTEX(wf_lock);
52static struct notifier_block *wf_client_list;
53static int wf_client_count;
54static unsigned int wf_overtemp;
55static unsigned int wf_overtemp_counter;
56struct task_struct *wf_thread;
57
58/*
59 * Utilities & tick thread
60 */
61
62static inline void wf_notify(int event, void *param)
63{
64 notifier_call_chain(&wf_client_list, event, param);
65}
66
67int wf_critical_overtemp(void)
68{
69 static char * critical_overtemp_path = "/sbin/critical_overtemp";
70 char *argv[] = { critical_overtemp_path, NULL };
71 static char *envp[] = { "HOME=/",
72 "TERM=linux",
73 "PATH=/sbin:/usr/sbin:/bin:/usr/bin",
74 NULL };
75
76 return call_usermodehelper(critical_overtemp_path, argv, envp, 0);
77}
78EXPORT_SYMBOL_GPL(wf_critical_overtemp);
79
80static int wf_thread_func(void *data)
81{
82 unsigned long next, delay;
83
84 next = jiffies;
85
86 DBG("wf: thread started\n");
87
88 while(!kthread_should_stop()) {
89 try_to_freeze();
90
91 if (time_after_eq(jiffies, next)) {
92 wf_notify(WF_EVENT_TICK, NULL);
93 if (wf_overtemp) {
94 wf_overtemp_counter++;
95 /* 10 seconds overtemp, notify userland */
96 if (wf_overtemp_counter > 10)
97 wf_critical_overtemp();
98 /* 30 seconds, shutdown */
99 if (wf_overtemp_counter > 30) {
100 printk(KERN_ERR "windfarm: Overtemp "
101 "for more than 30"
102 " seconds, shutting down\n");
103 machine_power_off();
104 }
105 }
106 next += HZ;
107 }
108
109 delay = next - jiffies;
110 if (delay <= HZ)
111 schedule_timeout_interruptible(delay);
112
113 /* there should be no signal, but oh well */
114 if (signal_pending(current)) {
115 printk(KERN_WARNING "windfarm: thread got sigl !\n");
116 break;
117 }
118 }
119
120 DBG("wf: thread stopped\n");
121
122 return 0;
123}
124
125static void wf_start_thread(void)
126{
127 wf_thread = kthread_run(wf_thread_func, NULL, "kwindfarm");
128 if (IS_ERR(wf_thread)) {
129 printk(KERN_ERR "windfarm: failed to create thread,err %ld\n",
130 PTR_ERR(wf_thread));
131 wf_thread = NULL;
132 }
133}
134
135
136static void wf_stop_thread(void)
137{
138 if (wf_thread)
139 kthread_stop(wf_thread);
140 wf_thread = NULL;
141}
142
143/*
144 * Controls
145 */
146
147static void wf_control_release(struct kref *kref)
148{
149 struct wf_control *ct = container_of(kref, struct wf_control, ref);
150
151 DBG("wf: Deleting control %s\n", ct->name);
152
153 if (ct->ops && ct->ops->release)
154 ct->ops->release(ct);
155 else
156 kfree(ct);
157}
158
159int wf_register_control(struct wf_control *new_ct)
160{
161 struct wf_control *ct;
162
163 down(&wf_lock);
164 list_for_each_entry(ct, &wf_controls, link) {
165 if (!strcmp(ct->name, new_ct->name)) {
166 printk(KERN_WARNING "windfarm: trying to register"
167 " duplicate control %s\n", ct->name);
168 up(&wf_lock);
169 return -EEXIST;
170 }
171 }
172 kref_init(&new_ct->ref);
173 list_add(&new_ct->link, &wf_controls);
174
175 DBG("wf: Registered control %s\n", new_ct->name);
176
177 wf_notify(WF_EVENT_NEW_CONTROL, new_ct);
178 up(&wf_lock);
179
180 return 0;
181}
182EXPORT_SYMBOL_GPL(wf_register_control);
183
184void wf_unregister_control(struct wf_control *ct)
185{
186 down(&wf_lock);
187 list_del(&ct->link);
188 up(&wf_lock);
189
190 DBG("wf: Unregistered control %s\n", ct->name);
191
192 kref_put(&ct->ref, wf_control_release);
193}
194EXPORT_SYMBOL_GPL(wf_unregister_control);
195
196struct wf_control * wf_find_control(const char *name)
197{
198 struct wf_control *ct;
199
200 down(&wf_lock);
201 list_for_each_entry(ct, &wf_controls, link) {
202 if (!strcmp(ct->name, name)) {
203 if (wf_get_control(ct))
204 ct = NULL;
205 up(&wf_lock);
206 return ct;
207 }
208 }
209 up(&wf_lock);
210 return NULL;
211}
212EXPORT_SYMBOL_GPL(wf_find_control);
213
214int wf_get_control(struct wf_control *ct)
215{
216 if (!try_module_get(ct->ops->owner))
217 return -ENODEV;
218 kref_get(&ct->ref);
219 return 0;
220}
221EXPORT_SYMBOL_GPL(wf_get_control);
222
223void wf_put_control(struct wf_control *ct)
224{
225 struct module *mod = ct->ops->owner;
226 kref_put(&ct->ref, wf_control_release);
227 module_put(mod);
228}
229EXPORT_SYMBOL_GPL(wf_put_control);
230
231
232/*
233 * Sensors
234 */
235
236
237static void wf_sensor_release(struct kref *kref)
238{
239 struct wf_sensor *sr = container_of(kref, struct wf_sensor, ref);
240
241 DBG("wf: Deleting sensor %s\n", sr->name);
242
243 if (sr->ops && sr->ops->release)
244 sr->ops->release(sr);
245 else
246 kfree(sr);
247}
248
249int wf_register_sensor(struct wf_sensor *new_sr)
250{
251 struct wf_sensor *sr;
252
253 down(&wf_lock);
254 list_for_each_entry(sr, &wf_sensors, link) {
255 if (!strcmp(sr->name, new_sr->name)) {
256 printk(KERN_WARNING "windfarm: trying to register"
257 " duplicate sensor %s\n", sr->name);
258 up(&wf_lock);
259 return -EEXIST;
260 }
261 }
262 kref_init(&new_sr->ref);
263 list_add(&new_sr->link, &wf_sensors);
264
265 DBG("wf: Registered sensor %s\n", new_sr->name);
266
267 wf_notify(WF_EVENT_NEW_SENSOR, new_sr);
268 up(&wf_lock);
269
270 return 0;
271}
272EXPORT_SYMBOL_GPL(wf_register_sensor);
273
274void wf_unregister_sensor(struct wf_sensor *sr)
275{
276 down(&wf_lock);
277 list_del(&sr->link);
278 up(&wf_lock);
279
280 DBG("wf: Unregistered sensor %s\n", sr->name);
281
282 wf_put_sensor(sr);
283}
284EXPORT_SYMBOL_GPL(wf_unregister_sensor);
285
286struct wf_sensor * wf_find_sensor(const char *name)
287{
288 struct wf_sensor *sr;
289
290 down(&wf_lock);
291 list_for_each_entry(sr, &wf_sensors, link) {
292 if (!strcmp(sr->name, name)) {
293 if (wf_get_sensor(sr))
294 sr = NULL;
295 up(&wf_lock);
296 return sr;
297 }
298 }
299 up(&wf_lock);
300 return NULL;
301}
302EXPORT_SYMBOL_GPL(wf_find_sensor);
303
304int wf_get_sensor(struct wf_sensor *sr)
305{
306 if (!try_module_get(sr->ops->owner))
307 return -ENODEV;
308 kref_get(&sr->ref);
309 return 0;
310}
311EXPORT_SYMBOL_GPL(wf_get_sensor);
312
313void wf_put_sensor(struct wf_sensor *sr)
314{
315 struct module *mod = sr->ops->owner;
316 kref_put(&sr->ref, wf_sensor_release);
317 module_put(mod);
318}
319EXPORT_SYMBOL_GPL(wf_put_sensor);
320
321
322/*
323 * Client & notification
324 */
325
326int wf_register_client(struct notifier_block *nb)
327{
328 int rc;
329 struct wf_control *ct;
330 struct wf_sensor *sr;
331
332 down(&wf_lock);
333 rc = notifier_chain_register(&wf_client_list, nb);
334 if (rc != 0)
335 goto bail;
336 wf_client_count++;
337 list_for_each_entry(ct, &wf_controls, link)
338 wf_notify(WF_EVENT_NEW_CONTROL, ct);
339 list_for_each_entry(sr, &wf_sensors, link)
340 wf_notify(WF_EVENT_NEW_SENSOR, sr);
341 if (wf_client_count == 1)
342 wf_start_thread();
343 bail:
344 up(&wf_lock);
345 return rc;
346}
347EXPORT_SYMBOL_GPL(wf_register_client);
348
349int wf_unregister_client(struct notifier_block *nb)
350{
351 down(&wf_lock);
352 notifier_chain_unregister(&wf_client_list, nb);
353 wf_client_count++;
354 if (wf_client_count == 0)
355 wf_stop_thread();
356 up(&wf_lock);
357
358 return 0;
359}
360EXPORT_SYMBOL_GPL(wf_unregister_client);
361
362void wf_set_overtemp(void)
363{
364 down(&wf_lock);
365 wf_overtemp++;
366 if (wf_overtemp == 1) {
367 printk(KERN_WARNING "windfarm: Overtemp condition detected !\n");
368 wf_overtemp_counter = 0;
369 wf_notify(WF_EVENT_OVERTEMP, NULL);
370 }
371 up(&wf_lock);
372}
373EXPORT_SYMBOL_GPL(wf_set_overtemp);
374
375void wf_clear_overtemp(void)
376{
377 down(&wf_lock);
378 WARN_ON(wf_overtemp == 0);
379 if (wf_overtemp == 0) {
380 up(&wf_lock);
381 return;
382 }
383 wf_overtemp--;
384 if (wf_overtemp == 0) {
385 printk(KERN_WARNING "windfarm: Overtemp condition cleared !\n");
386 wf_notify(WF_EVENT_NORMALTEMP, NULL);
387 }
388 up(&wf_lock);
389}
390EXPORT_SYMBOL_GPL(wf_clear_overtemp);
391
392int wf_is_overtemp(void)
393{
394 return (wf_overtemp != 0);
395}
396EXPORT_SYMBOL_GPL(wf_is_overtemp);
397
398static struct platform_device wf_platform_device = {
399 .name = "windfarm",
400};
401
402static int __init windfarm_core_init(void)
403{
404 DBG("wf: core loaded\n");
405
406 platform_device_register(&wf_platform_device);
407 return 0;
408}
409
410static void __exit windfarm_core_exit(void)
411{
412 BUG_ON(wf_client_count != 0);
413
414 DBG("wf: core unloaded\n");
415
416 platform_device_unregister(&wf_platform_device);
417}
418
419
420module_init(windfarm_core_init);
421module_exit(windfarm_core_exit);
422
423MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
424MODULE_DESCRIPTION("Core component of PowerMac thermal control");
425MODULE_LICENSE("GPL");
426
diff --git a/drivers/macintosh/windfarm_cpufreq_clamp.c b/drivers/macintosh/windfarm_cpufreq_clamp.c
new file mode 100644
index 000000000000..607dbaca69c9
--- /dev/null
+++ b/drivers/macintosh/windfarm_cpufreq_clamp.c
@@ -0,0 +1,105 @@
1#include <linux/config.h>
2#include <linux/types.h>
3#include <linux/errno.h>
4#include <linux/kernel.h>
5#include <linux/delay.h>
6#include <linux/slab.h>
7#include <linux/init.h>
8#include <linux/wait.h>
9#include <linux/cpufreq.h>
10
11#include "windfarm.h"
12
13#define VERSION "0.3"
14
15static int clamped;
16static struct wf_control *clamp_control;
17
18static int clamp_notifier_call(struct notifier_block *self,
19 unsigned long event, void *data)
20{
21 struct cpufreq_policy *p = data;
22 unsigned long max_freq;
23
24 if (event != CPUFREQ_ADJUST)
25 return 0;
26
27 max_freq = clamped ? (p->cpuinfo.min_freq) : (p->cpuinfo.max_freq);
28 cpufreq_verify_within_limits(p, 0, max_freq);
29
30 return 0;
31}
32
33static struct notifier_block clamp_notifier = {
34 .notifier_call = clamp_notifier_call,
35};
36
37static int clamp_set(struct wf_control *ct, s32 value)
38{
39 if (value)
40 printk(KERN_INFO "windfarm: Clamping CPU frequency to "
41 "minimum !\n");
42 else
43 printk(KERN_INFO "windfarm: CPU frequency unclamped !\n");
44 clamped = value;
45 cpufreq_update_policy(0);
46 return 0;
47}
48
49static int clamp_get(struct wf_control *ct, s32 *value)
50{
51 *value = clamped;
52 return 0;
53}
54
55static s32 clamp_min(struct wf_control *ct)
56{
57 return 0;
58}
59
60static s32 clamp_max(struct wf_control *ct)
61{
62 return 1;
63}
64
65static struct wf_control_ops clamp_ops = {
66 .set_value = clamp_set,
67 .get_value = clamp_get,
68 .get_min = clamp_min,
69 .get_max = clamp_max,
70 .owner = THIS_MODULE,
71};
72
73static int __init wf_cpufreq_clamp_init(void)
74{
75 struct wf_control *clamp;
76
77 clamp = kmalloc(sizeof(struct wf_control), GFP_KERNEL);
78 if (clamp == NULL)
79 return -ENOMEM;
80 cpufreq_register_notifier(&clamp_notifier, CPUFREQ_POLICY_NOTIFIER);
81 clamp->ops = &clamp_ops;
82 clamp->name = "cpufreq-clamp";
83 if (wf_register_control(clamp))
84 goto fail;
85 clamp_control = clamp;
86 return 0;
87 fail:
88 kfree(clamp);
89 return -ENODEV;
90}
91
92static void __exit wf_cpufreq_clamp_exit(void)
93{
94 if (clamp_control)
95 wf_unregister_control(clamp_control);
96}
97
98
99module_init(wf_cpufreq_clamp_init);
100module_exit(wf_cpufreq_clamp_exit);
101
102MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
103MODULE_DESCRIPTION("CPU frequency clamp for PowerMacs thermal control");
104MODULE_LICENSE("GPL");
105
diff --git a/drivers/macintosh/windfarm_lm75_sensor.c b/drivers/macintosh/windfarm_lm75_sensor.c
new file mode 100644
index 000000000000..a0a41ad0f2b5
--- /dev/null
+++ b/drivers/macintosh/windfarm_lm75_sensor.c
@@ -0,0 +1,263 @@
1/*
2 * Windfarm PowerMac thermal control. LM75 sensor
3 *
4 * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
5 * <benh@kernel.crashing.org>
6 *
7 * Released under the term of the GNU GPL v2.
8 */
9
10#include <linux/types.h>
11#include <linux/errno.h>
12#include <linux/kernel.h>
13#include <linux/delay.h>
14#include <linux/slab.h>
15#include <linux/init.h>
16#include <linux/wait.h>
17#include <linux/i2c.h>
18#include <linux/i2c-dev.h>
19#include <asm/prom.h>
20#include <asm/machdep.h>
21#include <asm/io.h>
22#include <asm/system.h>
23#include <asm/sections.h>
24
25#include "windfarm.h"
26
27#define VERSION "0.1"
28
29#undef DEBUG
30
31#ifdef DEBUG
32#define DBG(args...) printk(args)
33#else
34#define DBG(args...) do { } while(0)
35#endif
36
37struct wf_lm75_sensor {
38 int ds1775 : 1;
39 int inited : 1;
40 struct i2c_client i2c;
41 struct wf_sensor sens;
42};
43#define wf_to_lm75(c) container_of(c, struct wf_lm75_sensor, sens)
44#define i2c_to_lm75(c) container_of(c, struct wf_lm75_sensor, i2c)
45
46static int wf_lm75_attach(struct i2c_adapter *adapter);
47static int wf_lm75_detach(struct i2c_client *client);
48
49static struct i2c_driver wf_lm75_driver = {
50 .owner = THIS_MODULE,
51 .name = "wf_lm75",
52 .flags = I2C_DF_NOTIFY,
53 .attach_adapter = wf_lm75_attach,
54 .detach_client = wf_lm75_detach,
55};
56
57static int wf_lm75_get(struct wf_sensor *sr, s32 *value)
58{
59 struct wf_lm75_sensor *lm = wf_to_lm75(sr);
60 s32 data;
61
62 if (lm->i2c.adapter == NULL)
63 return -ENODEV;
64
65 /* Init chip if necessary */
66 if (!lm->inited) {
67 u8 cfg_new, cfg = (u8)i2c_smbus_read_byte_data(&lm->i2c, 1);
68
69 DBG("wf_lm75: Initializing %s, cfg was: %02x\n",
70 sr->name, cfg);
71
72 /* clear shutdown bit, keep other settings as left by
73 * the firmware for now
74 */
75 cfg_new = cfg & ~0x01;
76 i2c_smbus_write_byte_data(&lm->i2c, 1, cfg_new);
77 lm->inited = 1;
78
79 /* If we just powered it up, let's wait 200 ms */
80 msleep(200);
81 }
82
83 /* Read temperature register */
84 data = (s32)le16_to_cpu(i2c_smbus_read_word_data(&lm->i2c, 0));
85 data <<= 8;
86 *value = data;
87
88 return 0;
89}
90
91static void wf_lm75_release(struct wf_sensor *sr)
92{
93 struct wf_lm75_sensor *lm = wf_to_lm75(sr);
94
95 /* check if client is registered and detach from i2c */
96 if (lm->i2c.adapter) {
97 i2c_detach_client(&lm->i2c);
98 lm->i2c.adapter = NULL;
99 }
100
101 kfree(lm);
102}
103
104static struct wf_sensor_ops wf_lm75_ops = {
105 .get_value = wf_lm75_get,
106 .release = wf_lm75_release,
107 .owner = THIS_MODULE,
108};
109
110static struct wf_lm75_sensor *wf_lm75_create(struct i2c_adapter *adapter,
111 u8 addr, int ds1775,
112 const char *loc)
113{
114 struct wf_lm75_sensor *lm;
115
116 DBG("wf_lm75: creating %s device at address 0x%02x\n",
117 ds1775 ? "ds1775" : "lm75", addr);
118
119 lm = kmalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL);
120 if (lm == NULL)
121 return NULL;
122 memset(lm, 0, sizeof(struct wf_lm75_sensor));
123
124 /* Usual rant about sensor names not beeing very consistent in
125 * the device-tree, oh well ...
126 * Add more entries below as you deal with more setups
127 */
128 if (!strcmp(loc, "Hard drive") || !strcmp(loc, "DRIVE BAY"))
129 lm->sens.name = "hd-temp";
130 else
131 goto fail;
132
133 lm->inited = 0;
134 lm->sens.ops = &wf_lm75_ops;
135 lm->ds1775 = ds1775;
136 lm->i2c.addr = (addr >> 1) & 0x7f;
137 lm->i2c.adapter = adapter;
138 lm->i2c.driver = &wf_lm75_driver;
139 strncpy(lm->i2c.name, lm->sens.name, I2C_NAME_SIZE-1);
140
141 if (i2c_attach_client(&lm->i2c)) {
142 printk(KERN_ERR "windfarm: failed to attach %s %s to i2c\n",
143 ds1775 ? "ds1775" : "lm75", lm->i2c.name);
144 goto fail;
145 }
146
147 if (wf_register_sensor(&lm->sens)) {
148 i2c_detach_client(&lm->i2c);
149 goto fail;
150 }
151
152 return lm;
153 fail:
154 kfree(lm);
155 return NULL;
156}
157
158static int wf_lm75_attach(struct i2c_adapter *adapter)
159{
160 u8 bus_id;
161 struct device_node *smu, *bus, *dev;
162
163 /* We currently only deal with LM75's hanging off the SMU
164 * i2c busses. If we extend that driver to other/older
165 * machines, we should split this function into SMU-i2c,
166 * keywest-i2c, PMU-i2c, ...
167 */
168
169 DBG("wf_lm75: adapter %s detected\n", adapter->name);
170
171 if (strncmp(adapter->name, "smu-i2c-", 8) != 0)
172 return 0;
173 smu = of_find_node_by_type(NULL, "smu");
174 if (smu == NULL)
175 return 0;
176
177 /* Look for the bus in the device-tree */
178 bus_id = (u8)simple_strtoul(adapter->name + 8, NULL, 16);
179
180 DBG("wf_lm75: bus ID is %x\n", bus_id);
181
182 /* Look for sensors subdir */
183 for (bus = NULL;
184 (bus = of_get_next_child(smu, bus)) != NULL;) {
185 u32 *reg;
186
187 if (strcmp(bus->name, "i2c"))
188 continue;
189 reg = (u32 *)get_property(bus, "reg", NULL);
190 if (reg == NULL)
191 continue;
192 if (bus_id == *reg)
193 break;
194 }
195 of_node_put(smu);
196 if (bus == NULL) {
197 printk(KERN_WARNING "windfarm: SMU i2c bus 0x%x not found"
198 " in device-tree !\n", bus_id);
199 return 0;
200 }
201
202 DBG("wf_lm75: bus found, looking for device...\n");
203
204 /* Now look for lm75(s) in there */
205 for (dev = NULL;
206 (dev = of_get_next_child(bus, dev)) != NULL;) {
207 const char *loc =
208 get_property(dev, "hwsensor-location", NULL);
209 u32 *reg = (u32 *)get_property(dev, "reg", NULL);
210 DBG(" dev: %s... (loc: %p, reg: %p)\n", dev->name, loc, reg);
211 if (loc == NULL || reg == NULL)
212 continue;
213 /* real lm75 */
214 if (device_is_compatible(dev, "lm75"))
215 wf_lm75_create(adapter, *reg, 0, loc);
216 /* ds1775 (compatible, better resolution */
217 else if (device_is_compatible(dev, "ds1775"))
218 wf_lm75_create(adapter, *reg, 1, loc);
219 }
220
221 of_node_put(bus);
222
223 return 0;
224}
225
226static int wf_lm75_detach(struct i2c_client *client)
227{
228 struct wf_lm75_sensor *lm = i2c_to_lm75(client);
229
230 DBG("wf_lm75: i2c detatch called for %s\n", lm->sens.name);
231
232 /* Mark client detached */
233 lm->i2c.adapter = NULL;
234
235 /* release sensor */
236 wf_unregister_sensor(&lm->sens);
237
238 return 0;
239}
240
241static int __init wf_lm75_sensor_init(void)
242{
243 int rc;
244
245 rc = i2c_add_driver(&wf_lm75_driver);
246 if (rc < 0)
247 return rc;
248 return 0;
249}
250
251static void __exit wf_lm75_sensor_exit(void)
252{
253 i2c_del_driver(&wf_lm75_driver);
254}
255
256
257module_init(wf_lm75_sensor_init);
258module_exit(wf_lm75_sensor_exit);
259
260MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
261MODULE_DESCRIPTION("LM75 sensor objects for PowerMacs thermal control");
262MODULE_LICENSE("GPL");
263
diff --git a/drivers/macintosh/windfarm_pid.c b/drivers/macintosh/windfarm_pid.c
new file mode 100644
index 000000000000..2e803b368757
--- /dev/null
+++ b/drivers/macintosh/windfarm_pid.c
@@ -0,0 +1,145 @@
1/*
2 * Windfarm PowerMac thermal control. Generic PID helpers
3 *
4 * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
5 * <benh@kernel.crashing.org>
6 *
7 * Released under the term of the GNU GPL v2.
8 */
9
10#include <linux/types.h>
11#include <linux/errno.h>
12#include <linux/kernel.h>
13#include <linux/string.h>
14#include <linux/module.h>
15
16#include "windfarm_pid.h"
17
18#undef DEBUG
19
20#ifdef DEBUG
21#define DBG(args...) printk(args)
22#else
23#define DBG(args...) do { } while(0)
24#endif
25
26void wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param)
27{
28 memset(st, 0, sizeof(struct wf_pid_state));
29 st->param = *param;
30 st->first = 1;
31}
32EXPORT_SYMBOL_GPL(wf_pid_init);
33
34s32 wf_pid_run(struct wf_pid_state *st, s32 new_sample)
35{
36 s64 error, integ, deriv;
37 s32 target;
38 int i, hlen = st->param.history_len;
39
40 /* Calculate error term */
41 error = new_sample - st->param.itarget;
42
43 /* Get samples into our history buffer */
44 if (st->first) {
45 for (i = 0; i < hlen; i++) {
46 st->samples[i] = new_sample;
47 st->errors[i] = error;
48 }
49 st->first = 0;
50 st->index = 0;
51 } else {
52 st->index = (st->index + 1) % hlen;
53 st->samples[st->index] = new_sample;
54 st->errors[st->index] = error;
55 }
56
57 /* Calculate integral term */
58 for (i = 0, integ = 0; i < hlen; i++)
59 integ += st->errors[(st->index + hlen - i) % hlen];
60 integ *= st->param.interval;
61
62 /* Calculate derivative term */
63 deriv = st->errors[st->index] -
64 st->errors[(st->index + hlen - 1) % hlen];
65 deriv /= st->param.interval;
66
67 /* Calculate target */
68 target = (s32)((integ * (s64)st->param.gr + deriv * (s64)st->param.gd +
69 error * (s64)st->param.gp) >> 36);
70 if (st->param.additive)
71 target += st->target;
72 target = max(target, st->param.min);
73 target = min(target, st->param.max);
74 st->target = target;
75
76 return st->target;
77}
78EXPORT_SYMBOL_GPL(wf_pid_run);
79
80void wf_cpu_pid_init(struct wf_cpu_pid_state *st,
81 struct wf_cpu_pid_param *param)
82{
83 memset(st, 0, sizeof(struct wf_cpu_pid_state));
84 st->param = *param;
85 st->first = 1;
86}
87EXPORT_SYMBOL_GPL(wf_cpu_pid_init);
88
89s32 wf_cpu_pid_run(struct wf_cpu_pid_state *st, s32 new_power, s32 new_temp)
90{
91 s64 error, integ, deriv, prop;
92 s32 target, sval, adj;
93 int i, hlen = st->param.history_len;
94
95 /* Calculate error term */
96 error = st->param.pmaxadj - new_power;
97
98 /* Get samples into our history buffer */
99 if (st->first) {
100 for (i = 0; i < hlen; i++) {
101 st->powers[i] = new_power;
102 st->errors[i] = error;
103 }
104 st->temps[0] = st->temps[1] = new_temp;
105 st->first = 0;
106 st->index = st->tindex = 0;
107 } else {
108 st->index = (st->index + 1) % hlen;
109 st->powers[st->index] = new_power;
110 st->errors[st->index] = error;
111 st->tindex = (st->tindex + 1) % 2;
112 st->temps[st->tindex] = new_temp;
113 }
114
115 /* Calculate integral term */
116 for (i = 0, integ = 0; i < hlen; i++)
117 integ += st->errors[(st->index + hlen - i) % hlen];
118 integ *= st->param.interval;
119 integ *= st->param.gr;
120 sval = st->param.tmax - ((integ >> 20) & 0xffffffff);
121 adj = min(st->param.ttarget, sval);
122
123 DBG("integ: %lx, sval: %lx, adj: %lx\n", integ, sval, adj);
124
125 /* Calculate derivative term */
126 deriv = st->temps[st->tindex] -
127 st->temps[(st->tindex + 2 - 1) % 2];
128 deriv /= st->param.interval;
129 deriv *= st->param.gd;
130
131 /* Calculate proportional term */
132 prop = (new_temp - adj);
133 prop *= st->param.gp;
134
135 DBG("deriv: %lx, prop: %lx\n", deriv, prop);
136
137 /* Calculate target */
138 target = st->target + (s32)((deriv + prop) >> 36);
139 target = max(target, st->param.min);
140 target = min(target, st->param.max);
141 st->target = target;
142
143 return st->target;
144}
145EXPORT_SYMBOL_GPL(wf_cpu_pid_run);
diff --git a/drivers/macintosh/windfarm_pid.h b/drivers/macintosh/windfarm_pid.h
new file mode 100644
index 000000000000..a364c2a2499c
--- /dev/null
+++ b/drivers/macintosh/windfarm_pid.h
@@ -0,0 +1,84 @@
1/*
2 * Windfarm PowerMac thermal control. Generic PID helpers
3 *
4 * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
5 * <benh@kernel.crashing.org>
6 *
7 * Released under the term of the GNU GPL v2.
8 *
9 * This is a pair of generic PID helpers that can be used by
10 * control loops. One is the basic PID implementation, the
11 * other one is more specifically tailored to the loops used
12 * for CPU control with 2 input sample types (temp and power)
13 */
14
15/*
16 * *** Simple PID ***
17 */
18
19#define WF_PID_MAX_HISTORY 32
20
21/* This parameter array is passed to the PID algorithm. Currently,
22 * we don't support changing parameters on the fly as it's not needed
23 * but could be implemented (with necessary adjustment of the history
24 * buffer
25 */
26struct wf_pid_param {
27 int interval; /* Interval between samples in seconds */
28 int history_len; /* Size of history buffer */
29 int additive; /* 1: target relative to previous value */
30 s32 gd, gp, gr; /* PID gains */
31 s32 itarget; /* PID input target */
32 s32 min,max; /* min and max target values */
33};
34
35struct wf_pid_state {
36 int first; /* first run of the loop */
37 int index; /* index of current sample */
38 s32 target; /* current target value */
39 s32 samples[WF_PID_MAX_HISTORY]; /* samples history buffer */
40 s32 errors[WF_PID_MAX_HISTORY]; /* error history buffer */
41
42 struct wf_pid_param param;
43};
44
45extern void wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param);
46extern s32 wf_pid_run(struct wf_pid_state *st, s32 sample);
47
48
49/*
50 * *** CPU PID ***
51 */
52
53#define WF_CPU_PID_MAX_HISTORY 32
54
55/* This parameter array is passed to the CPU PID algorithm. Currently,
56 * we don't support changing parameters on the fly as it's not needed
57 * but could be implemented (with necessary adjustment of the history
58 * buffer
59 */
60struct wf_cpu_pid_param {
61 int interval; /* Interval between samples in seconds */
62 int history_len; /* Size of history buffer */
63 s32 gd, gp, gr; /* PID gains */
64 s32 pmaxadj; /* PID max power adjust */
65 s32 ttarget; /* PID input target */
66 s32 tmax; /* PID input max */
67 s32 min,max; /* min and max target values */
68};
69
70struct wf_cpu_pid_state {
71 int first; /* first run of the loop */
72 int index; /* index of current power */
73 int tindex; /* index of current temp */
74 s32 target; /* current target value */
75 s32 powers[WF_PID_MAX_HISTORY]; /* power history buffer */
76 s32 errors[WF_PID_MAX_HISTORY]; /* error history buffer */
77 s32 temps[2]; /* temp. history buffer */
78
79 struct wf_cpu_pid_param param;
80};
81
82extern void wf_cpu_pid_init(struct wf_cpu_pid_state *st,
83 struct wf_cpu_pid_param *param);
84extern s32 wf_cpu_pid_run(struct wf_cpu_pid_state *st, s32 power, s32 temp);
diff --git a/drivers/macintosh/windfarm_pm81.c b/drivers/macintosh/windfarm_pm81.c
new file mode 100644
index 000000000000..322c74b2687f
--- /dev/null
+++ b/drivers/macintosh/windfarm_pm81.c
@@ -0,0 +1,879 @@
1/*
2 * Windfarm PowerMac thermal control. iMac G5
3 *
4 * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
5 * <benh@kernel.crashing.org>
6 *
7 * Released under the term of the GNU GPL v2.
8 *
9 * The algorithm used is the PID control algorithm, used the same
10 * way the published Darwin code does, using the same values that
11 * are present in the Darwin 8.2 snapshot property lists (note however
12 * that none of the code has been re-used, it's a complete re-implementation
13 *
14 * The various control loops found in Darwin config file are:
15 *
16 * PowerMac8,1 and PowerMac8,2
17 * ===========================
18 *
19 * System Fans control loop. Different based on models. In addition to the
20 * usual PID algorithm, the control loop gets 2 additional pairs of linear
21 * scaling factors (scale/offsets) expressed as 4.12 fixed point values
22 * signed offset, unsigned scale)
23 *
24 * The targets are modified such as:
25 * - the linked control (second control) gets the target value as-is
26 * (typically the drive fan)
27 * - the main control (first control) gets the target value scaled with
28 * the first pair of factors, and is then modified as below
29 * - the value of the target of the CPU Fan control loop is retreived,
30 * scaled with the second pair of factors, and the max of that and
31 * the scaled target is applied to the main control.
32 *
33 * # model_id: 2
34 * controls : system-fan, drive-bay-fan
35 * sensors : hd-temp
36 * PID params : G_d = 0x15400000
37 * G_p = 0x00200000
38 * G_r = 0x000002fd
39 * History = 2 entries
40 * Input target = 0x3a0000
41 * Interval = 5s
42 * linear-factors : offset = 0xff38 scale = 0x0ccd
43 * offset = 0x0208 scale = 0x07ae
44 *
45 * # model_id: 3
46 * controls : system-fan, drive-bay-fan
47 * sensors : hd-temp
48 * PID params : G_d = 0x08e00000
49 * G_p = 0x00566666
50 * G_r = 0x0000072b
51 * History = 2 entries
52 * Input target = 0x350000
53 * Interval = 5s
54 * linear-factors : offset = 0xff38 scale = 0x0ccd
55 * offset = 0x0000 scale = 0x0000
56 *
57 * # model_id: 5
58 * controls : system-fan
59 * sensors : hd-temp
60 * PID params : G_d = 0x15400000
61 * G_p = 0x00233333
62 * G_r = 0x000002fd
63 * History = 2 entries
64 * Input target = 0x3a0000
65 * Interval = 5s
66 * linear-factors : offset = 0x0000 scale = 0x1000
67 * offset = 0x0091 scale = 0x0bae
68 *
69 * CPU Fan control loop. The loop is identical for all models. it
70 * has an additional pair of scaling factor. This is used to scale the
71 * systems fan control loop target result (the one before it gets scaled
72 * by the System Fans control loop itself). Then, the max value of the
73 * calculated target value and system fan value is sent to the fans
74 *
75 * controls : cpu-fan
76 * sensors : cpu-temp cpu-power
77 * PID params : From SMU sdb partition
78 * linear-factors : offset = 0xfb50 scale = 0x1000
79 *
80 * CPU Slew control loop. Not implemented. The cpufreq driver in linux is
81 * completely separate for now, though we could find a way to link it, either
82 * as a client reacting to overtemp notifications, or directling monitoring
83 * the CPU temperature
84 *
85 * WARNING ! The CPU control loop requires the CPU tmax for the current
86 * operating point. However, we currently are completely separated from
87 * the cpufreq driver and thus do not know what the current operating
88 * point is. Fortunately, we also do not have any hardware supporting anything
89 * but operating point 0 at the moment, thus we just peek that value directly
90 * from the SDB partition. If we ever end up with actually slewing the system
91 * clock and thus changing operating points, we'll have to find a way to
92 * communicate with the CPU freq driver;
93 *
94 */
95
96#include <linux/types.h>
97#include <linux/errno.h>
98#include <linux/kernel.h>
99#include <linux/delay.h>
100#include <linux/slab.h>
101#include <linux/init.h>
102#include <linux/spinlock.h>
103#include <linux/wait.h>
104#include <linux/kmod.h>
105#include <linux/device.h>
106#include <linux/platform_device.h>
107#include <asm/prom.h>
108#include <asm/machdep.h>
109#include <asm/io.h>
110#include <asm/system.h>
111#include <asm/sections.h>
112#include <asm/smu.h>
113
114#include "windfarm.h"
115#include "windfarm_pid.h"
116
117#define VERSION "0.4"
118
119#undef DEBUG
120
121#ifdef DEBUG
122#define DBG(args...) printk(args)
123#else
124#define DBG(args...) do { } while(0)
125#endif
126
127/* define this to force CPU overtemp to 74 degree, useful for testing
128 * the overtemp code
129 */
130#undef HACKED_OVERTEMP
131
132static int wf_smu_mach_model; /* machine model id */
133
134static struct device *wf_smu_dev;
135
136/* Controls & sensors */
137static struct wf_sensor *sensor_cpu_power;
138static struct wf_sensor *sensor_cpu_temp;
139static struct wf_sensor *sensor_hd_temp;
140static struct wf_control *fan_cpu_main;
141static struct wf_control *fan_hd;
142static struct wf_control *fan_system;
143static struct wf_control *cpufreq_clamp;
144
145/* Set to kick the control loop into life */
146static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started;
147
148/* Failure handling.. could be nicer */
149#define FAILURE_FAN 0x01
150#define FAILURE_SENSOR 0x02
151#define FAILURE_OVERTEMP 0x04
152
153static unsigned int wf_smu_failure_state;
154static int wf_smu_readjust, wf_smu_skipping;
155
156/*
157 * ****** System Fans Control Loop ******
158 *
159 */
160
161/* Parameters for the System Fans control loop. Parameters
162 * not in this table such as interval, history size, ...
163 * are common to all versions and thus hard coded for now.
164 */
165struct wf_smu_sys_fans_param {
166 int model_id;
167 s32 itarget;
168 s32 gd, gp, gr;
169
170 s16 offset0;
171 u16 scale0;
172 s16 offset1;
173 u16 scale1;
174};
175
176#define WF_SMU_SYS_FANS_INTERVAL 5
177#define WF_SMU_SYS_FANS_HISTORY_SIZE 2
178
179/* State data used by the system fans control loop
180 */
181struct wf_smu_sys_fans_state {
182 int ticks;
183 s32 sys_setpoint;
184 s32 hd_setpoint;
185 s16 offset0;
186 u16 scale0;
187 s16 offset1;
188 u16 scale1;
189 struct wf_pid_state pid;
190};
191
192/*
193 * Configs for SMU Sytem Fan control loop
194 */
195static struct wf_smu_sys_fans_param wf_smu_sys_all_params[] = {
196 /* Model ID 2 */
197 {
198 .model_id = 2,
199 .itarget = 0x3a0000,
200 .gd = 0x15400000,
201 .gp = 0x00200000,
202 .gr = 0x000002fd,
203 .offset0 = 0xff38,
204 .scale0 = 0x0ccd,
205 .offset1 = 0x0208,
206 .scale1 = 0x07ae,
207 },
208 /* Model ID 3 */
209 {
210 .model_id = 2,
211 .itarget = 0x350000,
212 .gd = 0x08e00000,
213 .gp = 0x00566666,
214 .gr = 0x0000072b,
215 .offset0 = 0xff38,
216 .scale0 = 0x0ccd,
217 .offset1 = 0x0000,
218 .scale1 = 0x0000,
219 },
220 /* Model ID 5 */
221 {
222 .model_id = 2,
223 .itarget = 0x3a0000,
224 .gd = 0x15400000,
225 .gp = 0x00233333,
226 .gr = 0x000002fd,
227 .offset0 = 0x0000,
228 .scale0 = 0x1000,
229 .offset1 = 0x0091,
230 .scale1 = 0x0bae,
231 },
232};
233#define WF_SMU_SYS_FANS_NUM_CONFIGS ARRAY_SIZE(wf_smu_sys_all_params)
234
235static struct wf_smu_sys_fans_state *wf_smu_sys_fans;
236
237/*
238 * ****** CPU Fans Control Loop ******
239 *
240 */
241
242
243#define WF_SMU_CPU_FANS_INTERVAL 1
244#define WF_SMU_CPU_FANS_MAX_HISTORY 16
245#define WF_SMU_CPU_FANS_SIBLING_SCALE 0x00001000
246#define WF_SMU_CPU_FANS_SIBLING_OFFSET 0xfffffb50
247
248/* State data used by the cpu fans control loop
249 */
250struct wf_smu_cpu_fans_state {
251 int ticks;
252 s32 cpu_setpoint;
253 s32 scale;
254 s32 offset;
255 struct wf_cpu_pid_state pid;
256};
257
258static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans;
259
260
261
262/*
263 * ***** Implementation *****
264 *
265 */
266
267static void wf_smu_create_sys_fans(void)
268{
269 struct wf_smu_sys_fans_param *param = NULL;
270 struct wf_pid_param pid_param;
271 int i;
272
273 /* First, locate the params for this model */
274 for (i = 0; i < WF_SMU_SYS_FANS_NUM_CONFIGS; i++)
275 if (wf_smu_sys_all_params[i].model_id == wf_smu_mach_model) {
276 param = &wf_smu_sys_all_params[i];
277 break;
278 }
279
280 /* No params found, put fans to max */
281 if (param == NULL) {
282 printk(KERN_WARNING "windfarm: System fan config not found "
283 "for this machine model, max fan speed\n");
284 goto fail;
285 }
286
287 /* Alloc & initialize state */
288 wf_smu_sys_fans = kmalloc(sizeof(struct wf_smu_sys_fans_state),
289 GFP_KERNEL);
290 if (wf_smu_sys_fans == NULL) {
291 printk(KERN_WARNING "windfarm: Memory allocation error"
292 " max fan speed\n");
293 goto fail;
294 }
295 wf_smu_sys_fans->ticks = 1;
296 wf_smu_sys_fans->scale0 = param->scale0;
297 wf_smu_sys_fans->offset0 = param->offset0;
298 wf_smu_sys_fans->scale1 = param->scale1;
299 wf_smu_sys_fans->offset1 = param->offset1;
300
301 /* Fill PID params */
302 pid_param.gd = param->gd;
303 pid_param.gp = param->gp;
304 pid_param.gr = param->gr;
305 pid_param.interval = WF_SMU_SYS_FANS_INTERVAL;
306 pid_param.history_len = WF_SMU_SYS_FANS_HISTORY_SIZE;
307 pid_param.itarget = param->itarget;
308 pid_param.min = fan_system->ops->get_min(fan_system);
309 pid_param.max = fan_system->ops->get_max(fan_system);
310 if (fan_hd) {
311 pid_param.min =
312 max(pid_param.min,fan_hd->ops->get_min(fan_hd));
313 pid_param.max =
314 min(pid_param.max,fan_hd->ops->get_max(fan_hd));
315 }
316 wf_pid_init(&wf_smu_sys_fans->pid, &pid_param);
317
318 DBG("wf: System Fan control initialized.\n");
319 DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
320 FIX32TOPRINT(pid_param.itarget), pid_param.min, pid_param.max);
321 return;
322
323 fail:
324
325 if (fan_system)
326 wf_control_set_max(fan_system);
327 if (fan_hd)
328 wf_control_set_max(fan_hd);
329}
330
331static void wf_smu_sys_fans_tick(struct wf_smu_sys_fans_state *st)
332{
333 s32 new_setpoint, temp, scaled, cputarget;
334 int rc;
335
336 if (--st->ticks != 0) {
337 if (wf_smu_readjust)
338 goto readjust;
339 return;
340 }
341 st->ticks = WF_SMU_SYS_FANS_INTERVAL;
342
343 rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp);
344 if (rc) {
345 printk(KERN_WARNING "windfarm: HD temp sensor error %d\n",
346 rc);
347 wf_smu_failure_state |= FAILURE_SENSOR;
348 return;
349 }
350
351 DBG("wf_smu: System Fans tick ! HD temp: %d.%03d\n",
352 FIX32TOPRINT(temp));
353
354 if (temp > (st->pid.param.itarget + 0x50000))
355 wf_smu_failure_state |= FAILURE_OVERTEMP;
356
357 new_setpoint = wf_pid_run(&st->pid, temp);
358
359 DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);
360
361 scaled = ((((s64)new_setpoint) * (s64)st->scale0) >> 12) + st->offset0;
362
363 DBG("wf_smu: scaled setpoint: %d RPM\n", (int)scaled);
364
365 cputarget = wf_smu_cpu_fans ? wf_smu_cpu_fans->pid.target : 0;
366 cputarget = ((((s64)cputarget) * (s64)st->scale1) >> 12) + st->offset1;
367 scaled = max(scaled, cputarget);
368 scaled = max(scaled, st->pid.param.min);
369 scaled = min(scaled, st->pid.param.max);
370
371 DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)scaled);
372
373 if (st->sys_setpoint == scaled && new_setpoint == st->hd_setpoint)
374 return;
375 st->sys_setpoint = scaled;
376 st->hd_setpoint = new_setpoint;
377 readjust:
378 if (fan_system && wf_smu_failure_state == 0) {
379 rc = fan_system->ops->set_value(fan_system, st->sys_setpoint);
380 if (rc) {
381 printk(KERN_WARNING "windfarm: Sys fan error %d\n",
382 rc);
383 wf_smu_failure_state |= FAILURE_FAN;
384 }
385 }
386 if (fan_hd && wf_smu_failure_state == 0) {
387 rc = fan_hd->ops->set_value(fan_hd, st->hd_setpoint);
388 if (rc) {
389 printk(KERN_WARNING "windfarm: HD fan error %d\n",
390 rc);
391 wf_smu_failure_state |= FAILURE_FAN;
392 }
393 }
394}
395
396static void wf_smu_create_cpu_fans(void)
397{
398 struct wf_cpu_pid_param pid_param;
399 struct smu_sdbp_header *hdr;
400 struct smu_sdbp_cpupiddata *piddata;
401 struct smu_sdbp_fvt *fvt;
402 s32 tmax, tdelta, maxpow, powadj;
403
404 /* First, locate the PID params in SMU SBD */
405 hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL);
406 if (hdr == 0) {
407 printk(KERN_WARNING "windfarm: CPU PID fan config not found "
408 "max fan speed\n");
409 goto fail;
410 }
411 piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
412
413 /* Get the FVT params for operating point 0 (the only supported one
414 * for now) in order to get tmax
415 */
416 hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);
417 if (hdr) {
418 fvt = (struct smu_sdbp_fvt *)&hdr[1];
419 tmax = ((s32)fvt->maxtemp) << 16;
420 } else
421 tmax = 0x5e0000; /* 94 degree default */
422
423 /* Alloc & initialize state */
424 wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state),
425 GFP_KERNEL);
426 if (wf_smu_cpu_fans == NULL)
427 goto fail;
428 wf_smu_cpu_fans->ticks = 1;
429
430 wf_smu_cpu_fans->scale = WF_SMU_CPU_FANS_SIBLING_SCALE;
431 wf_smu_cpu_fans->offset = WF_SMU_CPU_FANS_SIBLING_OFFSET;
432
433 /* Fill PID params */
434 pid_param.interval = WF_SMU_CPU_FANS_INTERVAL;
435 pid_param.history_len = piddata->history_len;
436 if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) {
437 printk(KERN_WARNING "windfarm: History size overflow on "
438 "CPU control loop (%d)\n", piddata->history_len);
439 pid_param.history_len = WF_CPU_PID_MAX_HISTORY;
440 }
441 pid_param.gd = piddata->gd;
442 pid_param.gp = piddata->gp;
443 pid_param.gr = piddata->gr / pid_param.history_len;
444
445 tdelta = ((s32)piddata->target_temp_delta) << 16;
446 maxpow = ((s32)piddata->max_power) << 16;
447 powadj = ((s32)piddata->power_adj) << 16;
448
449 pid_param.tmax = tmax;
450 pid_param.ttarget = tmax - tdelta;
451 pid_param.pmaxadj = maxpow - powadj;
452
453 pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main);
454 pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main);
455
456 wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param);
457
458 DBG("wf: CPU Fan control initialized.\n");
459 DBG(" ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n",
460 FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax),
461 pid_param.min, pid_param.max);
462
463 return;
464
465 fail:
466 printk(KERN_WARNING "windfarm: CPU fan config not found\n"
467 "for this machine model, max fan speed\n");
468
469 if (cpufreq_clamp)
470 wf_control_set_max(cpufreq_clamp);
471 if (fan_cpu_main)
472 wf_control_set_max(fan_cpu_main);
473}
474
475static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)
476{
477 s32 new_setpoint, temp, power, systarget;
478 int rc;
479
480 if (--st->ticks != 0) {
481 if (wf_smu_readjust)
482 goto readjust;
483 return;
484 }
485 st->ticks = WF_SMU_CPU_FANS_INTERVAL;
486
487 rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp);
488 if (rc) {
489 printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n",
490 rc);
491 wf_smu_failure_state |= FAILURE_SENSOR;
492 return;
493 }
494
495 rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power);
496 if (rc) {
497 printk(KERN_WARNING "windfarm: CPU power sensor error %d\n",
498 rc);
499 wf_smu_failure_state |= FAILURE_SENSOR;
500 return;
501 }
502
503 DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n",
504 FIX32TOPRINT(temp), FIX32TOPRINT(power));
505
506#ifdef HACKED_OVERTEMP
507 if (temp > 0x4a0000)
508 wf_smu_failure_state |= FAILURE_OVERTEMP;
509#else
510 if (temp > st->pid.param.tmax)
511 wf_smu_failure_state |= FAILURE_OVERTEMP;
512#endif
513 new_setpoint = wf_cpu_pid_run(&st->pid, power, temp);
514
515 DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);
516
517 systarget = wf_smu_sys_fans ? wf_smu_sys_fans->pid.target : 0;
518 systarget = ((((s64)systarget) * (s64)st->scale) >> 12)
519 + st->offset;
520 new_setpoint = max(new_setpoint, systarget);
521 new_setpoint = max(new_setpoint, st->pid.param.min);
522 new_setpoint = min(new_setpoint, st->pid.param.max);
523
524 DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)new_setpoint);
525
526 if (st->cpu_setpoint == new_setpoint)
527 return;
528 st->cpu_setpoint = new_setpoint;
529 readjust:
530 if (fan_cpu_main && wf_smu_failure_state == 0) {
531 rc = fan_cpu_main->ops->set_value(fan_cpu_main,
532 st->cpu_setpoint);
533 if (rc) {
534 printk(KERN_WARNING "windfarm: CPU main fan"
535 " error %d\n", rc);
536 wf_smu_failure_state |= FAILURE_FAN;
537 }
538 }
539}
540
541
542/*
543 * ****** Attributes ******
544 *
545 */
546
547#define BUILD_SHOW_FUNC_FIX(name, data) \
548static ssize_t show_##name(struct device *dev, \
549 struct device_attribute *attr, \
550 char *buf) \
551{ \
552 ssize_t r; \
553 s32 val = 0; \
554 data->ops->get_value(data, &val); \
555 r = sprintf(buf, "%d.%03d", FIX32TOPRINT(val)); \
556 return r; \
557} \
558static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL);
559
560
561#define BUILD_SHOW_FUNC_INT(name, data) \
562static ssize_t show_##name(struct device *dev, \
563 struct device_attribute *attr, \
564 char *buf) \
565{ \
566 s32 val = 0; \
567 data->ops->get_value(data, &val); \
568 return sprintf(buf, "%d", val); \
569} \
570static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL);
571
572BUILD_SHOW_FUNC_INT(cpu_fan, fan_cpu_main);
573BUILD_SHOW_FUNC_INT(sys_fan, fan_system);
574BUILD_SHOW_FUNC_INT(hd_fan, fan_hd);
575
576BUILD_SHOW_FUNC_FIX(cpu_temp, sensor_cpu_temp);
577BUILD_SHOW_FUNC_FIX(cpu_power, sensor_cpu_power);
578BUILD_SHOW_FUNC_FIX(hd_temp, sensor_hd_temp);
579
580/*
581 * ****** Setup / Init / Misc ... ******
582 *
583 */
584
585static void wf_smu_tick(void)
586{
587 unsigned int last_failure = wf_smu_failure_state;
588 unsigned int new_failure;
589
590 if (!wf_smu_started) {
591 DBG("wf: creating control loops !\n");
592 wf_smu_create_sys_fans();
593 wf_smu_create_cpu_fans();
594 wf_smu_started = 1;
595 }
596
597 /* Skipping ticks */
598 if (wf_smu_skipping && --wf_smu_skipping)
599 return;
600
601 wf_smu_failure_state = 0;
602 if (wf_smu_sys_fans)
603 wf_smu_sys_fans_tick(wf_smu_sys_fans);
604 if (wf_smu_cpu_fans)
605 wf_smu_cpu_fans_tick(wf_smu_cpu_fans);
606
607 wf_smu_readjust = 0;
608 new_failure = wf_smu_failure_state & ~last_failure;
609
610 /* If entering failure mode, clamp cpufreq and ramp all
611 * fans to full speed.
612 */
613 if (wf_smu_failure_state && !last_failure) {
614 if (cpufreq_clamp)
615 wf_control_set_max(cpufreq_clamp);
616 if (fan_system)
617 wf_control_set_max(fan_system);
618 if (fan_cpu_main)
619 wf_control_set_max(fan_cpu_main);
620 if (fan_hd)
621 wf_control_set_max(fan_hd);
622 }
623
624 /* If leaving failure mode, unclamp cpufreq and readjust
625 * all fans on next iteration
626 */
627 if (!wf_smu_failure_state && last_failure) {
628 if (cpufreq_clamp)
629 wf_control_set_min(cpufreq_clamp);
630 wf_smu_readjust = 1;
631 }
632
633 /* Overtemp condition detected, notify and start skipping a couple
634 * ticks to let the temperature go down
635 */
636 if (new_failure & FAILURE_OVERTEMP) {
637 wf_set_overtemp();
638 wf_smu_skipping = 2;
639 }
640
641 /* We only clear the overtemp condition if overtemp is cleared
642 * _and_ no other failure is present. Since a sensor error will
643 * clear the overtemp condition (can't measure temperature) at
644 * the control loop levels, but we don't want to keep it clear
645 * here in this case
646 */
647 if (new_failure == 0 && last_failure & FAILURE_OVERTEMP)
648 wf_clear_overtemp();
649}
650
651static void wf_smu_new_control(struct wf_control *ct)
652{
653 if (wf_smu_all_controls_ok)
654 return;
655
656 if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-fan")) {
657 if (wf_get_control(ct) == 0) {
658 fan_cpu_main = ct;
659 device_create_file(wf_smu_dev, &dev_attr_cpu_fan);
660 }
661 }
662
663 if (fan_system == NULL && !strcmp(ct->name, "system-fan")) {
664 if (wf_get_control(ct) == 0) {
665 fan_system = ct;
666 device_create_file(wf_smu_dev, &dev_attr_sys_fan);
667 }
668 }
669
670 if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) {
671 if (wf_get_control(ct) == 0)
672 cpufreq_clamp = ct;
673 }
674
675 /* Darwin property list says the HD fan is only for model ID
676 * 0, 1, 2 and 3
677 */
678
679 if (wf_smu_mach_model > 3) {
680 if (fan_system && fan_cpu_main && cpufreq_clamp)
681 wf_smu_all_controls_ok = 1;
682 return;
683 }
684
685 if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) {
686 if (wf_get_control(ct) == 0) {
687 fan_hd = ct;
688 device_create_file(wf_smu_dev, &dev_attr_hd_fan);
689 }
690 }
691
692 if (fan_system && fan_hd && fan_cpu_main && cpufreq_clamp)
693 wf_smu_all_controls_ok = 1;
694}
695
696static void wf_smu_new_sensor(struct wf_sensor *sr)
697{
698 if (wf_smu_all_sensors_ok)
699 return;
700
701 if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) {
702 if (wf_get_sensor(sr) == 0) {
703 sensor_cpu_power = sr;
704 device_create_file(wf_smu_dev, &dev_attr_cpu_power);
705 }
706 }
707
708 if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) {
709 if (wf_get_sensor(sr) == 0) {
710 sensor_cpu_temp = sr;
711 device_create_file(wf_smu_dev, &dev_attr_cpu_temp);
712 }
713 }
714
715 if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) {
716 if (wf_get_sensor(sr) == 0) {
717 sensor_hd_temp = sr;
718 device_create_file(wf_smu_dev, &dev_attr_hd_temp);
719 }
720 }
721
722 if (sensor_cpu_power && sensor_cpu_temp && sensor_hd_temp)
723 wf_smu_all_sensors_ok = 1;
724}
725
726
727static int wf_smu_notify(struct notifier_block *self,
728 unsigned long event, void *data)
729{
730 switch(event) {
731 case WF_EVENT_NEW_CONTROL:
732 DBG("wf: new control %s detected\n",
733 ((struct wf_control *)data)->name);
734 wf_smu_new_control(data);
735 wf_smu_readjust = 1;
736 break;
737 case WF_EVENT_NEW_SENSOR:
738 DBG("wf: new sensor %s detected\n",
739 ((struct wf_sensor *)data)->name);
740 wf_smu_new_sensor(data);
741 break;
742 case WF_EVENT_TICK:
743 if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok)
744 wf_smu_tick();
745 }
746
747 return 0;
748}
749
750static struct notifier_block wf_smu_events = {
751 .notifier_call = wf_smu_notify,
752};
753
754static int wf_init_pm(void)
755{
756 struct smu_sdbp_header *hdr;
757
758 hdr = smu_get_sdb_partition(SMU_SDB_SENSORTREE_ID, NULL);
759 if (hdr != 0) {
760 struct smu_sdbp_sensortree *st =
761 (struct smu_sdbp_sensortree *)&hdr[1];
762 wf_smu_mach_model = st->model_id;
763 }
764
765 printk(KERN_INFO "windfarm: Initializing for iMacG5 model ID %d\n",
766 wf_smu_mach_model);
767
768 return 0;
769}
770
771static int wf_smu_probe(struct device *ddev)
772{
773 wf_smu_dev = ddev;
774
775 wf_register_client(&wf_smu_events);
776
777 return 0;
778}
779
780static int wf_smu_remove(struct device *ddev)
781{
782 wf_unregister_client(&wf_smu_events);
783
784 /* XXX We don't have yet a guarantee that our callback isn't
785 * in progress when returning from wf_unregister_client, so
786 * we add an arbitrary delay. I'll have to fix that in the core
787 */
788 msleep(1000);
789
790 /* Release all sensors */
791 /* One more crappy race: I don't think we have any guarantee here
792 * that the attribute callback won't race with the sensor beeing
793 * disposed of, and I'm not 100% certain what best way to deal
794 * with that except by adding locks all over... I'll do that
795 * eventually but heh, who ever rmmod this module anyway ?
796 */
797 if (sensor_cpu_power) {
798 device_remove_file(wf_smu_dev, &dev_attr_cpu_power);
799 wf_put_sensor(sensor_cpu_power);
800 }
801 if (sensor_cpu_temp) {
802 device_remove_file(wf_smu_dev, &dev_attr_cpu_temp);
803 wf_put_sensor(sensor_cpu_temp);
804 }
805 if (sensor_hd_temp) {
806 device_remove_file(wf_smu_dev, &dev_attr_hd_temp);
807 wf_put_sensor(sensor_hd_temp);
808 }
809
810 /* Release all controls */
811 if (fan_cpu_main) {
812 device_remove_file(wf_smu_dev, &dev_attr_cpu_fan);
813 wf_put_control(fan_cpu_main);
814 }
815 if (fan_hd) {
816 device_remove_file(wf_smu_dev, &dev_attr_hd_fan);
817 wf_put_control(fan_hd);
818 }
819 if (fan_system) {
820 device_remove_file(wf_smu_dev, &dev_attr_sys_fan);
821 wf_put_control(fan_system);
822 }
823 if (cpufreq_clamp)
824 wf_put_control(cpufreq_clamp);
825
826 /* Destroy control loops state structures */
827 if (wf_smu_sys_fans)
828 kfree(wf_smu_sys_fans);
829 if (wf_smu_cpu_fans)
830 kfree(wf_smu_cpu_fans);
831
832 wf_smu_dev = NULL;
833
834 return 0;
835}
836
837static struct device_driver wf_smu_driver = {
838 .name = "windfarm",
839 .bus = &platform_bus_type,
840 .probe = wf_smu_probe,
841 .remove = wf_smu_remove,
842};
843
844
845static int __init wf_smu_init(void)
846{
847 int rc = -ENODEV;
848
849 if (machine_is_compatible("PowerMac8,1") ||
850 machine_is_compatible("PowerMac8,2"))
851 rc = wf_init_pm();
852
853 if (rc == 0) {
854#ifdef MODULE
855 request_module("windfarm_smu_controls");
856 request_module("windfarm_smu_sensors");
857 request_module("windfarm_lm75_sensor");
858
859#endif /* MODULE */
860 driver_register(&wf_smu_driver);
861 }
862
863 return rc;
864}
865
866static void __exit wf_smu_exit(void)
867{
868
869 driver_unregister(&wf_smu_driver);
870}
871
872
873module_init(wf_smu_init);
874module_exit(wf_smu_exit);
875
876MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
877MODULE_DESCRIPTION("Thermal control logic for iMac G5");
878MODULE_LICENSE("GPL");
879
diff --git a/drivers/macintosh/windfarm_pm91.c b/drivers/macintosh/windfarm_pm91.c
new file mode 100644
index 000000000000..43243cf7410b
--- /dev/null
+++ b/drivers/macintosh/windfarm_pm91.c
@@ -0,0 +1,814 @@
1/*
2 * Windfarm PowerMac thermal control. SMU based 1 CPU desktop control loops
3 *
4 * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
5 * <benh@kernel.crashing.org>
6 *
7 * Released under the term of the GNU GPL v2.
8 *
9 * The algorithm used is the PID control algorithm, used the same
10 * way the published Darwin code does, using the same values that
11 * are present in the Darwin 8.2 snapshot property lists (note however
12 * that none of the code has been re-used, it's a complete re-implementation
13 *
14 * The various control loops found in Darwin config file are:
15 *
16 * PowerMac9,1
17 * ===========
18 *
19 * Has 3 control loops: CPU fans is similar to PowerMac8,1 (though it doesn't
20 * try to play with other control loops fans). Drive bay is rather basic PID
21 * with one sensor and one fan. Slots area is a bit different as the Darwin
22 * driver is supposed to be capable of working in a special "AGP" mode which
23 * involves the presence of an AGP sensor and an AGP fan (possibly on the
24 * AGP card itself). I can't deal with that special mode as I don't have
25 * access to those additional sensor/fans for now (though ultimately, it would
26 * be possible to add sensor objects for them) so I'm only implementing the
27 * basic PCI slot control loop
28 */
29
30#include <linux/types.h>
31#include <linux/errno.h>
32#include <linux/kernel.h>
33#include <linux/delay.h>
34#include <linux/slab.h>
35#include <linux/init.h>
36#include <linux/spinlock.h>
37#include <linux/wait.h>
38#include <linux/kmod.h>
39#include <linux/device.h>
40#include <linux/platform_device.h>
41#include <asm/prom.h>
42#include <asm/machdep.h>
43#include <asm/io.h>
44#include <asm/system.h>
45#include <asm/sections.h>
46#include <asm/smu.h>
47
48#include "windfarm.h"
49#include "windfarm_pid.h"
50
51#define VERSION "0.4"
52
53#undef DEBUG
54
55#ifdef DEBUG
56#define DBG(args...) printk(args)
57#else
58#define DBG(args...) do { } while(0)
59#endif
60
61/* define this to force CPU overtemp to 74 degree, useful for testing
62 * the overtemp code
63 */
64#undef HACKED_OVERTEMP
65
66static struct device *wf_smu_dev;
67
68/* Controls & sensors */
69static struct wf_sensor *sensor_cpu_power;
70static struct wf_sensor *sensor_cpu_temp;
71static struct wf_sensor *sensor_hd_temp;
72static struct wf_sensor *sensor_slots_power;
73static struct wf_control *fan_cpu_main;
74static struct wf_control *fan_cpu_second;
75static struct wf_control *fan_cpu_third;
76static struct wf_control *fan_hd;
77static struct wf_control *fan_slots;
78static struct wf_control *cpufreq_clamp;
79
80/* Set to kick the control loop into life */
81static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started;
82
83/* Failure handling.. could be nicer */
84#define FAILURE_FAN 0x01
85#define FAILURE_SENSOR 0x02
86#define FAILURE_OVERTEMP 0x04
87
88static unsigned int wf_smu_failure_state;
89static int wf_smu_readjust, wf_smu_skipping;
90
91/*
92 * ****** CPU Fans Control Loop ******
93 *
94 */
95
96
97#define WF_SMU_CPU_FANS_INTERVAL 1
98#define WF_SMU_CPU_FANS_MAX_HISTORY 16
99
100/* State data used by the cpu fans control loop
101 */
102struct wf_smu_cpu_fans_state {
103 int ticks;
104 s32 cpu_setpoint;
105 struct wf_cpu_pid_state pid;
106};
107
108static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans;
109
110
111
112/*
113 * ****** Drive Fan Control Loop ******
114 *
115 */
116
117struct wf_smu_drive_fans_state {
118 int ticks;
119 s32 setpoint;
120 struct wf_pid_state pid;
121};
122
123static struct wf_smu_drive_fans_state *wf_smu_drive_fans;
124
125/*
126 * ****** Slots Fan Control Loop ******
127 *
128 */
129
130struct wf_smu_slots_fans_state {
131 int ticks;
132 s32 setpoint;
133 struct wf_pid_state pid;
134};
135
136static struct wf_smu_slots_fans_state *wf_smu_slots_fans;
137
138/*
139 * ***** Implementation *****
140 *
141 */
142
143
144static void wf_smu_create_cpu_fans(void)
145{
146 struct wf_cpu_pid_param pid_param;
147 struct smu_sdbp_header *hdr;
148 struct smu_sdbp_cpupiddata *piddata;
149 struct smu_sdbp_fvt *fvt;
150 s32 tmax, tdelta, maxpow, powadj;
151
152 /* First, locate the PID params in SMU SBD */
153 hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL);
154 if (hdr == 0) {
155 printk(KERN_WARNING "windfarm: CPU PID fan config not found "
156 "max fan speed\n");
157 goto fail;
158 }
159 piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
160
161 /* Get the FVT params for operating point 0 (the only supported one
162 * for now) in order to get tmax
163 */
164 hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);
165 if (hdr) {
166 fvt = (struct smu_sdbp_fvt *)&hdr[1];
167 tmax = ((s32)fvt->maxtemp) << 16;
168 } else
169 tmax = 0x5e0000; /* 94 degree default */
170
171 /* Alloc & initialize state */
172 wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state),
173 GFP_KERNEL);
174 if (wf_smu_cpu_fans == NULL)
175 goto fail;
176 wf_smu_cpu_fans->ticks = 1;
177
178 /* Fill PID params */
179 pid_param.interval = WF_SMU_CPU_FANS_INTERVAL;
180 pid_param.history_len = piddata->history_len;
181 if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) {
182 printk(KERN_WARNING "windfarm: History size overflow on "
183 "CPU control loop (%d)\n", piddata->history_len);
184 pid_param.history_len = WF_CPU_PID_MAX_HISTORY;
185 }
186 pid_param.gd = piddata->gd;
187 pid_param.gp = piddata->gp;
188 pid_param.gr = piddata->gr / pid_param.history_len;
189
190 tdelta = ((s32)piddata->target_temp_delta) << 16;
191 maxpow = ((s32)piddata->max_power) << 16;
192 powadj = ((s32)piddata->power_adj) << 16;
193
194 pid_param.tmax = tmax;
195 pid_param.ttarget = tmax - tdelta;
196 pid_param.pmaxadj = maxpow - powadj;
197
198 pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main);
199 pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main);
200
201 wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param);
202
203 DBG("wf: CPU Fan control initialized.\n");
204 DBG(" ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n",
205 FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax),
206 pid_param.min, pid_param.max);
207
208 return;
209
210 fail:
211 printk(KERN_WARNING "windfarm: CPU fan config not found\n"
212 "for this machine model, max fan speed\n");
213
214 if (cpufreq_clamp)
215 wf_control_set_max(cpufreq_clamp);
216 if (fan_cpu_main)
217 wf_control_set_max(fan_cpu_main);
218}
219
220static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)
221{
222 s32 new_setpoint, temp, power;
223 int rc;
224
225 if (--st->ticks != 0) {
226 if (wf_smu_readjust)
227 goto readjust;
228 return;
229 }
230 st->ticks = WF_SMU_CPU_FANS_INTERVAL;
231
232 rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp);
233 if (rc) {
234 printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n",
235 rc);
236 wf_smu_failure_state |= FAILURE_SENSOR;
237 return;
238 }
239
240 rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power);
241 if (rc) {
242 printk(KERN_WARNING "windfarm: CPU power sensor error %d\n",
243 rc);
244 wf_smu_failure_state |= FAILURE_SENSOR;
245 return;
246 }
247
248 DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n",
249 FIX32TOPRINT(temp), FIX32TOPRINT(power));
250
251#ifdef HACKED_OVERTEMP
252 if (temp > 0x4a0000)
253 wf_smu_failure_state |= FAILURE_OVERTEMP;
254#else
255 if (temp > st->pid.param.tmax)
256 wf_smu_failure_state |= FAILURE_OVERTEMP;
257#endif
258 new_setpoint = wf_cpu_pid_run(&st->pid, power, temp);
259
260 DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);
261
262 if (st->cpu_setpoint == new_setpoint)
263 return;
264 st->cpu_setpoint = new_setpoint;
265 readjust:
266 if (fan_cpu_main && wf_smu_failure_state == 0) {
267 rc = fan_cpu_main->ops->set_value(fan_cpu_main,
268 st->cpu_setpoint);
269 if (rc) {
270 printk(KERN_WARNING "windfarm: CPU main fan"
271 " error %d\n", rc);
272 wf_smu_failure_state |= FAILURE_FAN;
273 }
274 }
275 if (fan_cpu_second && wf_smu_failure_state == 0) {
276 rc = fan_cpu_second->ops->set_value(fan_cpu_second,
277 st->cpu_setpoint);
278 if (rc) {
279 printk(KERN_WARNING "windfarm: CPU second fan"
280 " error %d\n", rc);
281 wf_smu_failure_state |= FAILURE_FAN;
282 }
283 }
284 if (fan_cpu_third && wf_smu_failure_state == 0) {
285 rc = fan_cpu_main->ops->set_value(fan_cpu_third,
286 st->cpu_setpoint);
287 if (rc) {
288 printk(KERN_WARNING "windfarm: CPU third fan"
289 " error %d\n", rc);
290 wf_smu_failure_state |= FAILURE_FAN;
291 }
292 }
293}
294
295static void wf_smu_create_drive_fans(void)
296{
297 struct wf_pid_param param = {
298 .interval = 5,
299 .history_len = 2,
300 .gd = 0x01e00000,
301 .gp = 0x00500000,
302 .gr = 0x00000000,
303 .itarget = 0x00200000,
304 };
305
306 /* Alloc & initialize state */
307 wf_smu_drive_fans = kmalloc(sizeof(struct wf_smu_drive_fans_state),
308 GFP_KERNEL);
309 if (wf_smu_drive_fans == NULL) {
310 printk(KERN_WARNING "windfarm: Memory allocation error"
311 " max fan speed\n");
312 goto fail;
313 }
314 wf_smu_drive_fans->ticks = 1;
315
316 /* Fill PID params */
317 param.additive = (fan_hd->type == WF_CONTROL_RPM_FAN);
318 param.min = fan_hd->ops->get_min(fan_hd);
319 param.max = fan_hd->ops->get_max(fan_hd);
320 wf_pid_init(&wf_smu_drive_fans->pid, &param);
321
322 DBG("wf: Drive Fan control initialized.\n");
323 DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
324 FIX32TOPRINT(param.itarget), param.min, param.max);
325 return;
326
327 fail:
328 if (fan_hd)
329 wf_control_set_max(fan_hd);
330}
331
332static void wf_smu_drive_fans_tick(struct wf_smu_drive_fans_state *st)
333{
334 s32 new_setpoint, temp;
335 int rc;
336
337 if (--st->ticks != 0) {
338 if (wf_smu_readjust)
339 goto readjust;
340 return;
341 }
342 st->ticks = st->pid.param.interval;
343
344 rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp);
345 if (rc) {
346 printk(KERN_WARNING "windfarm: HD temp sensor error %d\n",
347 rc);
348 wf_smu_failure_state |= FAILURE_SENSOR;
349 return;
350 }
351
352 DBG("wf_smu: Drive Fans tick ! HD temp: %d.%03d\n",
353 FIX32TOPRINT(temp));
354
355 if (temp > (st->pid.param.itarget + 0x50000))
356 wf_smu_failure_state |= FAILURE_OVERTEMP;
357
358 new_setpoint = wf_pid_run(&st->pid, temp);
359
360 DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint);
361
362 if (st->setpoint == new_setpoint)
363 return;
364 st->setpoint = new_setpoint;
365 readjust:
366 if (fan_hd && wf_smu_failure_state == 0) {
367 rc = fan_hd->ops->set_value(fan_hd, st->setpoint);
368 if (rc) {
369 printk(KERN_WARNING "windfarm: HD fan error %d\n",
370 rc);
371 wf_smu_failure_state |= FAILURE_FAN;
372 }
373 }
374}
375
376static void wf_smu_create_slots_fans(void)
377{
378 struct wf_pid_param param = {
379 .interval = 1,
380 .history_len = 8,
381 .gd = 0x00000000,
382 .gp = 0x00000000,
383 .gr = 0x00020000,
384 .itarget = 0x00000000
385 };
386
387 /* Alloc & initialize state */
388 wf_smu_slots_fans = kmalloc(sizeof(struct wf_smu_slots_fans_state),
389 GFP_KERNEL);
390 if (wf_smu_slots_fans == NULL) {
391 printk(KERN_WARNING "windfarm: Memory allocation error"
392 " max fan speed\n");
393 goto fail;
394 }
395 wf_smu_slots_fans->ticks = 1;
396
397 /* Fill PID params */
398 param.additive = (fan_slots->type == WF_CONTROL_RPM_FAN);
399 param.min = fan_slots->ops->get_min(fan_slots);
400 param.max = fan_slots->ops->get_max(fan_slots);
401 wf_pid_init(&wf_smu_slots_fans->pid, &param);
402
403 DBG("wf: Slots Fan control initialized.\n");
404 DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
405 FIX32TOPRINT(param.itarget), param.min, param.max);
406 return;
407
408 fail:
409 if (fan_slots)
410 wf_control_set_max(fan_slots);
411}
412
413static void wf_smu_slots_fans_tick(struct wf_smu_slots_fans_state *st)
414{
415 s32 new_setpoint, power;
416 int rc;
417
418 if (--st->ticks != 0) {
419 if (wf_smu_readjust)
420 goto readjust;
421 return;
422 }
423 st->ticks = st->pid.param.interval;
424
425 rc = sensor_slots_power->ops->get_value(sensor_slots_power, &power);
426 if (rc) {
427 printk(KERN_WARNING "windfarm: Slots power sensor error %d\n",
428 rc);
429 wf_smu_failure_state |= FAILURE_SENSOR;
430 return;
431 }
432
433 DBG("wf_smu: Slots Fans tick ! Slots power: %d.%03d\n",
434 FIX32TOPRINT(power));
435
436#if 0 /* Check what makes a good overtemp condition */
437 if (power > (st->pid.param.itarget + 0x50000))
438 wf_smu_failure_state |= FAILURE_OVERTEMP;
439#endif
440
441 new_setpoint = wf_pid_run(&st->pid, power);
442
443 DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint);
444
445 if (st->setpoint == new_setpoint)
446 return;
447 st->setpoint = new_setpoint;
448 readjust:
449 if (fan_slots && wf_smu_failure_state == 0) {
450 rc = fan_slots->ops->set_value(fan_slots, st->setpoint);
451 if (rc) {
452 printk(KERN_WARNING "windfarm: Slots fan error %d\n",
453 rc);
454 wf_smu_failure_state |= FAILURE_FAN;
455 }
456 }
457}
458
459
460/*
461 * ****** Attributes ******
462 *
463 */
464
465#define BUILD_SHOW_FUNC_FIX(name, data) \
466static ssize_t show_##name(struct device *dev, \
467 struct device_attribute *attr, \
468 char *buf) \
469{ \
470 ssize_t r; \
471 s32 val = 0; \
472 data->ops->get_value(data, &val); \
473 r = sprintf(buf, "%d.%03d", FIX32TOPRINT(val)); \
474 return r; \
475} \
476static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL);
477
478
479#define BUILD_SHOW_FUNC_INT(name, data) \
480static ssize_t show_##name(struct device *dev, \
481 struct device_attribute *attr, \
482 char *buf) \
483{ \
484 s32 val = 0; \
485 data->ops->get_value(data, &val); \
486 return sprintf(buf, "%d", val); \
487} \
488static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL);
489
490BUILD_SHOW_FUNC_INT(cpu_fan, fan_cpu_main);
491BUILD_SHOW_FUNC_INT(hd_fan, fan_hd);
492BUILD_SHOW_FUNC_INT(slots_fan, fan_slots);
493
494BUILD_SHOW_FUNC_FIX(cpu_temp, sensor_cpu_temp);
495BUILD_SHOW_FUNC_FIX(cpu_power, sensor_cpu_power);
496BUILD_SHOW_FUNC_FIX(hd_temp, sensor_hd_temp);
497BUILD_SHOW_FUNC_FIX(slots_power, sensor_slots_power);
498
499/*
500 * ****** Setup / Init / Misc ... ******
501 *
502 */
503
504static void wf_smu_tick(void)
505{
506 unsigned int last_failure = wf_smu_failure_state;
507 unsigned int new_failure;
508
509 if (!wf_smu_started) {
510 DBG("wf: creating control loops !\n");
511 wf_smu_create_drive_fans();
512 wf_smu_create_slots_fans();
513 wf_smu_create_cpu_fans();
514 wf_smu_started = 1;
515 }
516
517 /* Skipping ticks */
518 if (wf_smu_skipping && --wf_smu_skipping)
519 return;
520
521 wf_smu_failure_state = 0;
522 if (wf_smu_drive_fans)
523 wf_smu_drive_fans_tick(wf_smu_drive_fans);
524 if (wf_smu_slots_fans)
525 wf_smu_slots_fans_tick(wf_smu_slots_fans);
526 if (wf_smu_cpu_fans)
527 wf_smu_cpu_fans_tick(wf_smu_cpu_fans);
528
529 wf_smu_readjust = 0;
530 new_failure = wf_smu_failure_state & ~last_failure;
531
532 /* If entering failure mode, clamp cpufreq and ramp all
533 * fans to full speed.
534 */
535 if (wf_smu_failure_state && !last_failure) {
536 if (cpufreq_clamp)
537 wf_control_set_max(cpufreq_clamp);
538 if (fan_cpu_main)
539 wf_control_set_max(fan_cpu_main);
540 if (fan_cpu_second)
541 wf_control_set_max(fan_cpu_second);
542 if (fan_cpu_third)
543 wf_control_set_max(fan_cpu_third);
544 if (fan_hd)
545 wf_control_set_max(fan_hd);
546 if (fan_slots)
547 wf_control_set_max(fan_slots);
548 }
549
550 /* If leaving failure mode, unclamp cpufreq and readjust
551 * all fans on next iteration
552 */
553 if (!wf_smu_failure_state && last_failure) {
554 if (cpufreq_clamp)
555 wf_control_set_min(cpufreq_clamp);
556 wf_smu_readjust = 1;
557 }
558
559 /* Overtemp condition detected, notify and start skipping a couple
560 * ticks to let the temperature go down
561 */
562 if (new_failure & FAILURE_OVERTEMP) {
563 wf_set_overtemp();
564 wf_smu_skipping = 2;
565 }
566
567 /* We only clear the overtemp condition if overtemp is cleared
568 * _and_ no other failure is present. Since a sensor error will
569 * clear the overtemp condition (can't measure temperature) at
570 * the control loop levels, but we don't want to keep it clear
571 * here in this case
572 */
573 if (new_failure == 0 && last_failure & FAILURE_OVERTEMP)
574 wf_clear_overtemp();
575}
576
577
578static void wf_smu_new_control(struct wf_control *ct)
579{
580 if (wf_smu_all_controls_ok)
581 return;
582
583 if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-rear-fan-0")) {
584 if (wf_get_control(ct) == 0) {
585 fan_cpu_main = ct;
586 device_create_file(wf_smu_dev, &dev_attr_cpu_fan);
587 }
588 }
589
590 if (fan_cpu_second == NULL && !strcmp(ct->name, "cpu-rear-fan-1")) {
591 if (wf_get_control(ct) == 0)
592 fan_cpu_second = ct;
593 }
594
595 if (fan_cpu_third == NULL && !strcmp(ct->name, "cpu-front-fan-0")) {
596 if (wf_get_control(ct) == 0)
597 fan_cpu_third = ct;
598 }
599
600 if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) {
601 if (wf_get_control(ct) == 0)
602 cpufreq_clamp = ct;
603 }
604
605 if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) {
606 if (wf_get_control(ct) == 0) {
607 fan_hd = ct;
608 device_create_file(wf_smu_dev, &dev_attr_hd_fan);
609 }
610 }
611
612 if (fan_slots == NULL && !strcmp(ct->name, "slots-fan")) {
613 if (wf_get_control(ct) == 0) {
614 fan_slots = ct;
615 device_create_file(wf_smu_dev, &dev_attr_slots_fan);
616 }
617 }
618
619 if (fan_cpu_main && (fan_cpu_second || fan_cpu_third) && fan_hd &&
620 fan_slots && cpufreq_clamp)
621 wf_smu_all_controls_ok = 1;
622}
623
624static void wf_smu_new_sensor(struct wf_sensor *sr)
625{
626 if (wf_smu_all_sensors_ok)
627 return;
628
629 if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) {
630 if (wf_get_sensor(sr) == 0) {
631 sensor_cpu_power = sr;
632 device_create_file(wf_smu_dev, &dev_attr_cpu_power);
633 }
634 }
635
636 if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) {
637 if (wf_get_sensor(sr) == 0) {
638 sensor_cpu_temp = sr;
639 device_create_file(wf_smu_dev, &dev_attr_cpu_temp);
640 }
641 }
642
643 if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) {
644 if (wf_get_sensor(sr) == 0) {
645 sensor_hd_temp = sr;
646 device_create_file(wf_smu_dev, &dev_attr_hd_temp);
647 }
648 }
649
650 if (sensor_slots_power == NULL && !strcmp(sr->name, "slots-power")) {
651 if (wf_get_sensor(sr) == 0) {
652 sensor_slots_power = sr;
653 device_create_file(wf_smu_dev, &dev_attr_slots_power);
654 }
655 }
656
657 if (sensor_cpu_power && sensor_cpu_temp &&
658 sensor_hd_temp && sensor_slots_power)
659 wf_smu_all_sensors_ok = 1;
660}
661
662
663static int wf_smu_notify(struct notifier_block *self,
664 unsigned long event, void *data)
665{
666 switch(event) {
667 case WF_EVENT_NEW_CONTROL:
668 DBG("wf: new control %s detected\n",
669 ((struct wf_control *)data)->name);
670 wf_smu_new_control(data);
671 wf_smu_readjust = 1;
672 break;
673 case WF_EVENT_NEW_SENSOR:
674 DBG("wf: new sensor %s detected\n",
675 ((struct wf_sensor *)data)->name);
676 wf_smu_new_sensor(data);
677 break;
678 case WF_EVENT_TICK:
679 if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok)
680 wf_smu_tick();
681 }
682
683 return 0;
684}
685
686static struct notifier_block wf_smu_events = {
687 .notifier_call = wf_smu_notify,
688};
689
690static int wf_init_pm(void)
691{
692 printk(KERN_INFO "windfarm: Initializing for Desktop G5 model\n");
693
694 return 0;
695}
696
697static int wf_smu_probe(struct device *ddev)
698{
699 wf_smu_dev = ddev;
700
701 wf_register_client(&wf_smu_events);
702
703 return 0;
704}
705
706static int wf_smu_remove(struct device *ddev)
707{
708 wf_unregister_client(&wf_smu_events);
709
710 /* XXX We don't have yet a guarantee that our callback isn't
711 * in progress when returning from wf_unregister_client, so
712 * we add an arbitrary delay. I'll have to fix that in the core
713 */
714 msleep(1000);
715
716 /* Release all sensors */
717 /* One more crappy race: I don't think we have any guarantee here
718 * that the attribute callback won't race with the sensor beeing
719 * disposed of, and I'm not 100% certain what best way to deal
720 * with that except by adding locks all over... I'll do that
721 * eventually but heh, who ever rmmod this module anyway ?
722 */
723 if (sensor_cpu_power) {
724 device_remove_file(wf_smu_dev, &dev_attr_cpu_power);
725 wf_put_sensor(sensor_cpu_power);
726 }
727 if (sensor_cpu_temp) {
728 device_remove_file(wf_smu_dev, &dev_attr_cpu_temp);
729 wf_put_sensor(sensor_cpu_temp);
730 }
731 if (sensor_hd_temp) {
732 device_remove_file(wf_smu_dev, &dev_attr_hd_temp);
733 wf_put_sensor(sensor_hd_temp);
734 }
735 if (sensor_slots_power) {
736 device_remove_file(wf_smu_dev, &dev_attr_slots_power);
737 wf_put_sensor(sensor_slots_power);
738 }
739
740 /* Release all controls */
741 if (fan_cpu_main) {
742 device_remove_file(wf_smu_dev, &dev_attr_cpu_fan);
743 wf_put_control(fan_cpu_main);
744 }
745 if (fan_cpu_second)
746 wf_put_control(fan_cpu_second);
747 if (fan_cpu_third)
748 wf_put_control(fan_cpu_third);
749 if (fan_hd) {
750 device_remove_file(wf_smu_dev, &dev_attr_hd_fan);
751 wf_put_control(fan_hd);
752 }
753 if (fan_slots) {
754 device_remove_file(wf_smu_dev, &dev_attr_slots_fan);
755 wf_put_control(fan_slots);
756 }
757 if (cpufreq_clamp)
758 wf_put_control(cpufreq_clamp);
759
760 /* Destroy control loops state structures */
761 if (wf_smu_slots_fans)
762 kfree(wf_smu_cpu_fans);
763 if (wf_smu_drive_fans)
764 kfree(wf_smu_cpu_fans);
765 if (wf_smu_cpu_fans)
766 kfree(wf_smu_cpu_fans);
767
768 wf_smu_dev = NULL;
769
770 return 0;
771}
772
773static struct device_driver wf_smu_driver = {
774 .name = "windfarm",
775 .bus = &platform_bus_type,
776 .probe = wf_smu_probe,
777 .remove = wf_smu_remove,
778};
779
780
781static int __init wf_smu_init(void)
782{
783 int rc = -ENODEV;
784
785 if (machine_is_compatible("PowerMac9,1"))
786 rc = wf_init_pm();
787
788 if (rc == 0) {
789#ifdef MODULE
790 request_module("windfarm_smu_controls");
791 request_module("windfarm_smu_sensors");
792 request_module("windfarm_lm75_sensor");
793
794#endif /* MODULE */
795 driver_register(&wf_smu_driver);
796 }
797
798 return rc;
799}
800
801static void __exit wf_smu_exit(void)
802{
803
804 driver_unregister(&wf_smu_driver);
805}
806
807
808module_init(wf_smu_init);
809module_exit(wf_smu_exit);
810
811MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
812MODULE_DESCRIPTION("Thermal control logic for PowerMac9,1");
813MODULE_LICENSE("GPL");
814
diff --git a/drivers/macintosh/windfarm_smu_controls.c b/drivers/macintosh/windfarm_smu_controls.c
new file mode 100644
index 000000000000..2c3158c81ff2
--- /dev/null
+++ b/drivers/macintosh/windfarm_smu_controls.c
@@ -0,0 +1,282 @@
1/*
2 * Windfarm PowerMac thermal control. SMU based controls
3 *
4 * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
5 * <benh@kernel.crashing.org>
6 *
7 * Released under the term of the GNU GPL v2.
8 */
9
10#include <linux/types.h>
11#include <linux/errno.h>
12#include <linux/kernel.h>
13#include <linux/delay.h>
14#include <linux/slab.h>
15#include <linux/init.h>
16#include <linux/wait.h>
17#include <asm/prom.h>
18#include <asm/machdep.h>
19#include <asm/io.h>
20#include <asm/system.h>
21#include <asm/sections.h>
22#include <asm/smu.h>
23
24#include "windfarm.h"
25
26#define VERSION "0.3"
27
28#undef DEBUG
29
30#ifdef DEBUG
31#define DBG(args...) printk(args)
32#else
33#define DBG(args...) do { } while(0)
34#endif
35
36/*
37 * SMU fans control object
38 */
39
40static LIST_HEAD(smu_fans);
41
42struct smu_fan_control {
43 struct list_head link;
44 int fan_type; /* 0 = rpm, 1 = pwm */
45 u32 reg; /* index in SMU */
46 s32 value; /* current value */
47 s32 min, max; /* min/max values */
48 struct wf_control ctrl;
49};
50#define to_smu_fan(c) container_of(c, struct smu_fan_control, ctrl)
51
52static int smu_set_fan(int pwm, u8 id, u16 value)
53{
54 struct smu_cmd cmd;
55 u8 buffer[16];
56 DECLARE_COMPLETION(comp);
57 int rc;
58
59 /* Fill SMU command structure */
60 cmd.cmd = SMU_CMD_FAN_COMMAND;
61 cmd.data_len = 14;
62 cmd.reply_len = 16;
63 cmd.data_buf = cmd.reply_buf = buffer;
64 cmd.status = 0;
65 cmd.done = smu_done_complete;
66 cmd.misc = &comp;
67
68 /* Fill argument buffer */
69 memset(buffer, 0, 16);
70 buffer[0] = pwm ? 0x10 : 0x00;
71 buffer[1] = 0x01 << id;
72 *((u16 *)&buffer[2 + id * 2]) = value;
73
74 rc = smu_queue_cmd(&cmd);
75 if (rc)
76 return rc;
77 wait_for_completion(&comp);
78 return cmd.status;
79}
80
81static void smu_fan_release(struct wf_control *ct)
82{
83 struct smu_fan_control *fct = to_smu_fan(ct);
84
85 kfree(fct);
86}
87
88static int smu_fan_set(struct wf_control *ct, s32 value)
89{
90 struct smu_fan_control *fct = to_smu_fan(ct);
91
92 if (value < fct->min)
93 value = fct->min;
94 if (value > fct->max)
95 value = fct->max;
96 fct->value = value;
97
98 return smu_set_fan(fct->fan_type, fct->reg, value);
99}
100
101static int smu_fan_get(struct wf_control *ct, s32 *value)
102{
103 struct smu_fan_control *fct = to_smu_fan(ct);
104 *value = fct->value; /* todo: read from SMU */
105 return 0;
106}
107
108static s32 smu_fan_min(struct wf_control *ct)
109{
110 struct smu_fan_control *fct = to_smu_fan(ct);
111 return fct->min;
112}
113
114static s32 smu_fan_max(struct wf_control *ct)
115{
116 struct smu_fan_control *fct = to_smu_fan(ct);
117 return fct->max;
118}
119
120static struct wf_control_ops smu_fan_ops = {
121 .set_value = smu_fan_set,
122 .get_value = smu_fan_get,
123 .get_min = smu_fan_min,
124 .get_max = smu_fan_max,
125 .release = smu_fan_release,
126 .owner = THIS_MODULE,
127};
128
129static struct smu_fan_control *smu_fan_create(struct device_node *node,
130 int pwm_fan)
131{
132 struct smu_fan_control *fct;
133 s32 *v; u32 *reg;
134 char *l;
135
136 fct = kmalloc(sizeof(struct smu_fan_control), GFP_KERNEL);
137 if (fct == NULL)
138 return NULL;
139 fct->ctrl.ops = &smu_fan_ops;
140 l = (char *)get_property(node, "location", NULL);
141 if (l == NULL)
142 goto fail;
143
144 fct->fan_type = pwm_fan;
145 fct->ctrl.type = pwm_fan ? WF_CONTROL_PWM_FAN : WF_CONTROL_RPM_FAN;
146
147 /* We use the name & location here the same way we do for SMU sensors,
148 * see the comment in windfarm_smu_sensors.c. The locations are a bit
149 * less consistent here between the iMac and the desktop models, but
150 * that is good enough for our needs for now at least.
151 *
152 * One problem though is that Apple seem to be inconsistent with case
153 * and the kernel doesn't have strcasecmp =P
154 */
155
156 fct->ctrl.name = NULL;
157
158 /* Names used on desktop models */
159 if (!strcmp(l, "Rear Fan 0") || !strcmp(l, "Rear Fan") ||
160 !strcmp(l, "Rear fan 0") || !strcmp(l, "Rear fan"))
161 fct->ctrl.name = "cpu-rear-fan-0";
162 else if (!strcmp(l, "Rear Fan 1") || !strcmp(l, "Rear fan 1"))
163 fct->ctrl.name = "cpu-rear-fan-1";
164 else if (!strcmp(l, "Front Fan 0") || !strcmp(l, "Front Fan") ||
165 !strcmp(l, "Front fan 0") || !strcmp(l, "Front fan"))
166 fct->ctrl.name = "cpu-front-fan-0";
167 else if (!strcmp(l, "Front Fan 1") || !strcmp(l, "Front fan 1"))
168 fct->ctrl.name = "cpu-front-fan-1";
169 else if (!strcmp(l, "Slots Fan") || !strcmp(l, "Slots fan"))
170 fct->ctrl.name = "slots-fan";
171 else if (!strcmp(l, "Drive Bay") || !strcmp(l, "Drive bay"))
172 fct->ctrl.name = "drive-bay-fan";
173
174 /* Names used on iMac models */
175 if (!strcmp(l, "System Fan") || !strcmp(l, "System fan"))
176 fct->ctrl.name = "system-fan";
177 else if (!strcmp(l, "CPU Fan") || !strcmp(l, "CPU fan"))
178 fct->ctrl.name = "cpu-fan";
179 else if (!strcmp(l, "Hard Drive") || !strcmp(l, "Hard drive"))
180 fct->ctrl.name = "drive-bay-fan";
181
182 /* Unrecognized fan, bail out */
183 if (fct->ctrl.name == NULL)
184 goto fail;
185
186 /* Get min & max values*/
187 v = (s32 *)get_property(node, "min-value", NULL);
188 if (v == NULL)
189 goto fail;
190 fct->min = *v;
191 v = (s32 *)get_property(node, "max-value", NULL);
192 if (v == NULL)
193 goto fail;
194 fct->max = *v;
195
196 /* Get "reg" value */
197 reg = (u32 *)get_property(node, "reg", NULL);
198 if (reg == NULL)
199 goto fail;
200 fct->reg = *reg;
201
202 if (wf_register_control(&fct->ctrl))
203 goto fail;
204
205 return fct;
206 fail:
207 kfree(fct);
208 return NULL;
209}
210
211
212static int __init smu_controls_init(void)
213{
214 struct device_node *smu, *fans, *fan;
215
216 if (!smu_present())
217 return -ENODEV;
218
219 smu = of_find_node_by_type(NULL, "smu");
220 if (smu == NULL)
221 return -ENODEV;
222
223 /* Look for RPM fans */
224 for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;)
225 if (!strcmp(fans->name, "rpm-fans"))
226 break;
227 for (fan = NULL;
228 fans && (fan = of_get_next_child(fans, fan)) != NULL;) {
229 struct smu_fan_control *fct;
230
231 fct = smu_fan_create(fan, 0);
232 if (fct == NULL) {
233 printk(KERN_WARNING "windfarm: Failed to create SMU "
234 "RPM fan %s\n", fan->name);
235 continue;
236 }
237 list_add(&fct->link, &smu_fans);
238 }
239 of_node_put(fans);
240
241
242 /* Look for PWM fans */
243 for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;)
244 if (!strcmp(fans->name, "pwm-fans"))
245 break;
246 for (fan = NULL;
247 fans && (fan = of_get_next_child(fans, fan)) != NULL;) {
248 struct smu_fan_control *fct;
249
250 fct = smu_fan_create(fan, 1);
251 if (fct == NULL) {
252 printk(KERN_WARNING "windfarm: Failed to create SMU "
253 "PWM fan %s\n", fan->name);
254 continue;
255 }
256 list_add(&fct->link, &smu_fans);
257 }
258 of_node_put(fans);
259 of_node_put(smu);
260
261 return 0;
262}
263
264static void __exit smu_controls_exit(void)
265{
266 struct smu_fan_control *fct;
267
268 while (!list_empty(&smu_fans)) {
269 fct = list_entry(smu_fans.next, struct smu_fan_control, link);
270 list_del(&fct->link);
271 wf_unregister_control(&fct->ctrl);
272 }
273}
274
275
276module_init(smu_controls_init);
277module_exit(smu_controls_exit);
278
279MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
280MODULE_DESCRIPTION("SMU control objects for PowerMacs thermal control");
281MODULE_LICENSE("GPL");
282
diff --git a/drivers/macintosh/windfarm_smu_sensors.c b/drivers/macintosh/windfarm_smu_sensors.c
new file mode 100644
index 000000000000..b558cc209d49
--- /dev/null
+++ b/drivers/macintosh/windfarm_smu_sensors.c
@@ -0,0 +1,479 @@
1/*
2 * Windfarm PowerMac thermal control. SMU based sensors
3 *
4 * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
5 * <benh@kernel.crashing.org>
6 *
7 * Released under the term of the GNU GPL v2.
8 */
9
10#include <linux/types.h>
11#include <linux/errno.h>
12#include <linux/kernel.h>
13#include <linux/delay.h>
14#include <linux/slab.h>
15#include <linux/init.h>
16#include <linux/wait.h>
17#include <asm/prom.h>
18#include <asm/machdep.h>
19#include <asm/io.h>
20#include <asm/system.h>
21#include <asm/sections.h>
22#include <asm/smu.h>
23
24#include "windfarm.h"
25
26#define VERSION "0.2"
27
28#undef DEBUG
29
30#ifdef DEBUG
31#define DBG(args...) printk(args)
32#else
33#define DBG(args...) do { } while(0)
34#endif
35
36/*
37 * Various SMU "partitions" calibration objects for which we
38 * keep pointers here for use by bits & pieces of the driver
39 */
40static struct smu_sdbp_cpuvcp *cpuvcp;
41static int cpuvcp_version;
42static struct smu_sdbp_cpudiode *cpudiode;
43static struct smu_sdbp_slotspow *slotspow;
44static u8 *debugswitches;
45
46/*
47 * SMU basic sensors objects
48 */
49
50static LIST_HEAD(smu_ads);
51
52struct smu_ad_sensor {
53 struct list_head link;
54 u32 reg; /* index in SMU */
55 struct wf_sensor sens;
56};
57#define to_smu_ads(c) container_of(c, struct smu_ad_sensor, sens)
58
59static void smu_ads_release(struct wf_sensor *sr)
60{
61 struct smu_ad_sensor *ads = to_smu_ads(sr);
62
63 kfree(ads);
64}
65
66static int smu_read_adc(u8 id, s32 *value)
67{
68 struct smu_simple_cmd cmd;
69 DECLARE_COMPLETION(comp);
70 int rc;
71
72 rc = smu_queue_simple(&cmd, SMU_CMD_READ_ADC, 1,
73 smu_done_complete, &comp, id);
74 if (rc)
75 return rc;
76 wait_for_completion(&comp);
77 if (cmd.cmd.status != 0)
78 return cmd.cmd.status;
79 if (cmd.cmd.reply_len != 2) {
80 printk(KERN_ERR "winfarm: read ADC 0x%x returned %d bytes !\n",
81 id, cmd.cmd.reply_len);
82 return -EIO;
83 }
84 *value = *((u16 *)cmd.buffer);
85 return 0;
86}
87
88static int smu_cputemp_get(struct wf_sensor *sr, s32 *value)
89{
90 struct smu_ad_sensor *ads = to_smu_ads(sr);
91 int rc;
92 s32 val;
93 s64 scaled;
94
95 rc = smu_read_adc(ads->reg, &val);
96 if (rc) {
97 printk(KERN_ERR "windfarm: read CPU temp failed, err %d\n",
98 rc);
99 return rc;
100 }
101
102 /* Ok, we have to scale & adjust, taking units into account */
103 scaled = (s64)(((u64)val) * (u64)cpudiode->m_value);
104 scaled >>= 3;
105 scaled += ((s64)cpudiode->b_value) << 9;
106 *value = (s32)(scaled << 1);
107
108 return 0;
109}
110
111static int smu_cpuamp_get(struct wf_sensor *sr, s32 *value)
112{
113 struct smu_ad_sensor *ads = to_smu_ads(sr);
114 s32 val, scaled;
115 int rc;
116
117 rc = smu_read_adc(ads->reg, &val);
118 if (rc) {
119 printk(KERN_ERR "windfarm: read CPU current failed, err %d\n",
120 rc);
121 return rc;
122 }
123
124 /* Ok, we have to scale & adjust, taking units into account */
125 scaled = (s32)(val * (u32)cpuvcp->curr_scale);
126 scaled += (s32)cpuvcp->curr_offset;
127 *value = scaled << 4;
128
129 return 0;
130}
131
132static int smu_cpuvolt_get(struct wf_sensor *sr, s32 *value)
133{
134 struct smu_ad_sensor *ads = to_smu_ads(sr);
135 s32 val, scaled;
136 int rc;
137
138 rc = smu_read_adc(ads->reg, &val);
139 if (rc) {
140 printk(KERN_ERR "windfarm: read CPU voltage failed, err %d\n",
141 rc);
142 return rc;
143 }
144
145 /* Ok, we have to scale & adjust, taking units into account */
146 scaled = (s32)(val * (u32)cpuvcp->volt_scale);
147 scaled += (s32)cpuvcp->volt_offset;
148 *value = scaled << 4;
149
150 return 0;
151}
152
153static int smu_slotspow_get(struct wf_sensor *sr, s32 *value)
154{
155 struct smu_ad_sensor *ads = to_smu_ads(sr);
156 s32 val, scaled;
157 int rc;
158
159 rc = smu_read_adc(ads->reg, &val);
160 if (rc) {
161 printk(KERN_ERR "windfarm: read slots power failed, err %d\n",
162 rc);
163 return rc;
164 }
165
166 /* Ok, we have to scale & adjust, taking units into account */
167 scaled = (s32)(val * (u32)slotspow->pow_scale);
168 scaled += (s32)slotspow->pow_offset;
169 *value = scaled << 4;
170
171 return 0;
172}
173
174
175static struct wf_sensor_ops smu_cputemp_ops = {
176 .get_value = smu_cputemp_get,
177 .release = smu_ads_release,
178 .owner = THIS_MODULE,
179};
180static struct wf_sensor_ops smu_cpuamp_ops = {
181 .get_value = smu_cpuamp_get,
182 .release = smu_ads_release,
183 .owner = THIS_MODULE,
184};
185static struct wf_sensor_ops smu_cpuvolt_ops = {
186 .get_value = smu_cpuvolt_get,
187 .release = smu_ads_release,
188 .owner = THIS_MODULE,
189};
190static struct wf_sensor_ops smu_slotspow_ops = {
191 .get_value = smu_slotspow_get,
192 .release = smu_ads_release,
193 .owner = THIS_MODULE,
194};
195
196
197static struct smu_ad_sensor *smu_ads_create(struct device_node *node)
198{
199 struct smu_ad_sensor *ads;
200 char *c, *l;
201 u32 *v;
202
203 ads = kmalloc(sizeof(struct smu_ad_sensor), GFP_KERNEL);
204 if (ads == NULL)
205 return NULL;
206 c = (char *)get_property(node, "device_type", NULL);
207 l = (char *)get_property(node, "location", NULL);
208 if (c == NULL || l == NULL)
209 goto fail;
210
211 /* We currently pick the sensors based on the OF name and location
212 * properties, while Darwin uses the sensor-id's.
213 * The problem with the IDs is that they are model specific while it
214 * looks like apple has been doing a reasonably good job at keeping
215 * the names and locations consistents so I'll stick with the names
216 * and locations for now.
217 */
218 if (!strcmp(c, "temp-sensor") &&
219 !strcmp(l, "CPU T-Diode")) {
220 ads->sens.ops = &smu_cputemp_ops;
221 ads->sens.name = "cpu-temp";
222 } else if (!strcmp(c, "current-sensor") &&
223 !strcmp(l, "CPU Current")) {
224 ads->sens.ops = &smu_cpuamp_ops;
225 ads->sens.name = "cpu-current";
226 } else if (!strcmp(c, "voltage-sensor") &&
227 !strcmp(l, "CPU Voltage")) {
228 ads->sens.ops = &smu_cpuvolt_ops;
229 ads->sens.name = "cpu-voltage";
230 } else if (!strcmp(c, "power-sensor") &&
231 !strcmp(l, "Slots Power")) {
232 ads->sens.ops = &smu_slotspow_ops;
233 ads->sens.name = "slots-power";
234 if (slotspow == NULL) {
235 DBG("wf: slotspow partition (%02x) not found\n",
236 SMU_SDB_SLOTSPOW_ID);
237 goto fail;
238 }
239 } else
240 goto fail;
241
242 v = (u32 *)get_property(node, "reg", NULL);
243 if (v == NULL)
244 goto fail;
245 ads->reg = *v;
246
247 if (wf_register_sensor(&ads->sens))
248 goto fail;
249 return ads;
250 fail:
251 kfree(ads);
252 return NULL;
253}
254
255/*
256 * SMU Power combo sensor object
257 */
258
259struct smu_cpu_power_sensor {
260 struct list_head link;
261 struct wf_sensor *volts;
262 struct wf_sensor *amps;
263 int fake_volts : 1;
264 int quadratic : 1;
265 struct wf_sensor sens;
266};
267#define to_smu_cpu_power(c) container_of(c, struct smu_cpu_power_sensor, sens)
268
269static struct smu_cpu_power_sensor *smu_cpu_power;
270
271static void smu_cpu_power_release(struct wf_sensor *sr)
272{
273 struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
274
275 if (pow->volts)
276 wf_put_sensor(pow->volts);
277 if (pow->amps)
278 wf_put_sensor(pow->amps);
279 kfree(pow);
280}
281
282static int smu_cpu_power_get(struct wf_sensor *sr, s32 *value)
283{
284 struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
285 s32 volts, amps, power;
286 u64 tmps, tmpa, tmpb;
287 int rc;
288
289 rc = pow->amps->ops->get_value(pow->amps, &amps);
290 if (rc)
291 return rc;
292
293 if (pow->fake_volts) {
294 *value = amps * 12 - 0x30000;
295 return 0;
296 }
297
298 rc = pow->volts->ops->get_value(pow->volts, &volts);
299 if (rc)
300 return rc;
301
302 power = (s32)((((u64)volts) * ((u64)amps)) >> 16);
303 if (!pow->quadratic) {
304 *value = power;
305 return 0;
306 }
307 tmps = (((u64)power) * ((u64)power)) >> 16;
308 tmpa = ((u64)cpuvcp->power_quads[0]) * tmps;
309 tmpb = ((u64)cpuvcp->power_quads[1]) * ((u64)power);
310 *value = (tmpa >> 28) + (tmpb >> 28) + (cpuvcp->power_quads[2] >> 12);
311
312 return 0;
313}
314
315static struct wf_sensor_ops smu_cpu_power_ops = {
316 .get_value = smu_cpu_power_get,
317 .release = smu_cpu_power_release,
318 .owner = THIS_MODULE,
319};
320
321
322static struct smu_cpu_power_sensor *
323smu_cpu_power_create(struct wf_sensor *volts, struct wf_sensor *amps)
324{
325 struct smu_cpu_power_sensor *pow;
326
327 pow = kmalloc(sizeof(struct smu_cpu_power_sensor), GFP_KERNEL);
328 if (pow == NULL)
329 return NULL;
330 pow->sens.ops = &smu_cpu_power_ops;
331 pow->sens.name = "cpu-power";
332
333 wf_get_sensor(volts);
334 pow->volts = volts;
335 wf_get_sensor(amps);
336 pow->amps = amps;
337
338 /* Some early machines need a faked voltage */
339 if (debugswitches && ((*debugswitches) & 0x80)) {
340 printk(KERN_INFO "windfarm: CPU Power sensor using faked"
341 " voltage !\n");
342 pow->fake_volts = 1;
343 } else
344 pow->fake_volts = 0;
345
346 /* Try to use quadratic transforms on PowerMac8,1 and 9,1 for now,
347 * I yet have to figure out what's up with 8,2 and will have to
348 * adjust for later, unless we can 100% trust the SDB partition...
349 */
350 if ((machine_is_compatible("PowerMac8,1") ||
351 machine_is_compatible("PowerMac8,2") ||
352 machine_is_compatible("PowerMac9,1")) &&
353 cpuvcp_version >= 2) {
354 pow->quadratic = 1;
355 DBG("windfarm: CPU Power using quadratic transform\n");
356 } else
357 pow->quadratic = 0;
358
359 if (wf_register_sensor(&pow->sens))
360 goto fail;
361 return pow;
362 fail:
363 kfree(pow);
364 return NULL;
365}
366
367static int smu_fetch_param_partitions(void)
368{
369 struct smu_sdbp_header *hdr;
370
371 /* Get CPU voltage/current/power calibration data */
372 hdr = smu_get_sdb_partition(SMU_SDB_CPUVCP_ID, NULL);
373 if (hdr == NULL) {
374 DBG("wf: cpuvcp partition (%02x) not found\n",
375 SMU_SDB_CPUVCP_ID);
376 return -ENODEV;
377 }
378 cpuvcp = (struct smu_sdbp_cpuvcp *)&hdr[1];
379 /* Keep version around */
380 cpuvcp_version = hdr->version;
381
382 /* Get CPU diode calibration data */
383 hdr = smu_get_sdb_partition(SMU_SDB_CPUDIODE_ID, NULL);
384 if (hdr == NULL) {
385 DBG("wf: cpudiode partition (%02x) not found\n",
386 SMU_SDB_CPUDIODE_ID);
387 return -ENODEV;
388 }
389 cpudiode = (struct smu_sdbp_cpudiode *)&hdr[1];
390
391 /* Get slots power calibration data if any */
392 hdr = smu_get_sdb_partition(SMU_SDB_SLOTSPOW_ID, NULL);
393 if (hdr != NULL)
394 slotspow = (struct smu_sdbp_slotspow *)&hdr[1];
395
396 /* Get debug switches if any */
397 hdr = smu_get_sdb_partition(SMU_SDB_DEBUG_SWITCHES_ID, NULL);
398 if (hdr != NULL)
399 debugswitches = (u8 *)&hdr[1];
400
401 return 0;
402}
403
404static int __init smu_sensors_init(void)
405{
406 struct device_node *smu, *sensors, *s;
407 struct smu_ad_sensor *volt_sensor = NULL, *curr_sensor = NULL;
408 int rc;
409
410 if (!smu_present())
411 return -ENODEV;
412
413 /* Get parameters partitions */
414 rc = smu_fetch_param_partitions();
415 if (rc)
416 return rc;
417
418 smu = of_find_node_by_type(NULL, "smu");
419 if (smu == NULL)
420 return -ENODEV;
421
422 /* Look for sensors subdir */
423 for (sensors = NULL;
424 (sensors = of_get_next_child(smu, sensors)) != NULL;)
425 if (!strcmp(sensors->name, "sensors"))
426 break;
427
428 of_node_put(smu);
429
430 /* Create basic sensors */
431 for (s = NULL;
432 sensors && (s = of_get_next_child(sensors, s)) != NULL;) {
433 struct smu_ad_sensor *ads;
434
435 ads = smu_ads_create(s);
436 if (ads == NULL)
437 continue;
438 list_add(&ads->link, &smu_ads);
439 /* keep track of cpu voltage & current */
440 if (!strcmp(ads->sens.name, "cpu-voltage"))
441 volt_sensor = ads;
442 else if (!strcmp(ads->sens.name, "cpu-current"))
443 curr_sensor = ads;
444 }
445
446 of_node_put(sensors);
447
448 /* Create CPU power sensor if possible */
449 if (volt_sensor && curr_sensor)
450 smu_cpu_power = smu_cpu_power_create(&volt_sensor->sens,
451 &curr_sensor->sens);
452
453 return 0;
454}
455
456static void __exit smu_sensors_exit(void)
457{
458 struct smu_ad_sensor *ads;
459
460 /* dispose of power sensor */
461 if (smu_cpu_power)
462 wf_unregister_sensor(&smu_cpu_power->sens);
463
464 /* dispose of basic sensors */
465 while (!list_empty(&smu_ads)) {
466 ads = list_entry(smu_ads.next, struct smu_ad_sensor, link);
467 list_del(&ads->link);
468 wf_unregister_sensor(&ads->sens);
469 }
470}
471
472
473module_init(smu_sensors_init);
474module_exit(smu_sensors_exit);
475
476MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
477MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control");
478MODULE_LICENSE("GPL");
479