diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2005-11-07 00:08:17 -0500 |
---|---|---|
committer | Paul Mackerras <paulus@samba.org> | 2005-11-07 19:17:56 -0500 |
commit | 75722d3992f57375c0cc029dcceb2334a45ceff1 (patch) | |
tree | d3f63b3ea80790c2f29ea435781c1331f17d269e | |
parent | 7d49697ef92bd2cf84ab53bd4cea82fefb197fb9 (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>
-rw-r--r-- | drivers/macintosh/Kconfig | 19 | ||||
-rw-r--r-- | drivers/macintosh/Makefile | 9 | ||||
-rw-r--r-- | drivers/macintosh/smu.c | 2 | ||||
-rw-r--r-- | drivers/macintosh/windfarm.h | 131 | ||||
-rw-r--r-- | drivers/macintosh/windfarm_core.c | 426 | ||||
-rw-r--r-- | drivers/macintosh/windfarm_cpufreq_clamp.c | 105 | ||||
-rw-r--r-- | drivers/macintosh/windfarm_lm75_sensor.c | 263 | ||||
-rw-r--r-- | drivers/macintosh/windfarm_pid.c | 145 | ||||
-rw-r--r-- | drivers/macintosh/windfarm_pid.h | 84 | ||||
-rw-r--r-- | drivers/macintosh/windfarm_pm81.c | 879 | ||||
-rw-r--r-- | drivers/macintosh/windfarm_pm91.c | 814 | ||||
-rw-r--r-- | drivers/macintosh/windfarm_smu_controls.c | 282 | ||||
-rw-r--r-- | drivers/macintosh/windfarm_smu_sensors.c | 479 |
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 | ||
172 | config WINDFARM | ||
173 | tristate "New PowerMac thermal control infrastructure" | ||
174 | |||
175 | config 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 | |||
182 | config 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 | |||
172 | config ANSLCD | 191 | config 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 | |||
26 | obj-$(CONFIG_THERM_PM72) += therm_pm72.o | 26 | obj-$(CONFIG_THERM_PM72) += therm_pm72.o |
27 | obj-$(CONFIG_THERM_WINDTUNNEL) += therm_windtunnel.o | 27 | obj-$(CONFIG_THERM_WINDTUNNEL) += therm_windtunnel.o |
28 | obj-$(CONFIG_THERM_ADT746X) += therm_adt746x.o | 28 | obj-$(CONFIG_THERM_ADT746X) += therm_adt746x.o |
29 | obj-$(CONFIG_WINDFARM) += windfarm_core.o | ||
30 | obj-$(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 | ||
34 | obj-$(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 | |||
25 | struct wf_control; | ||
26 | |||
27 | struct 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 | |||
36 | struct 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 | */ | ||
55 | extern int wf_register_control(struct wf_control *ct); | ||
56 | extern void wf_unregister_control(struct wf_control *ct); | ||
57 | extern struct wf_control * wf_find_control(const char *name); | ||
58 | extern int wf_get_control(struct wf_control *ct); | ||
59 | extern void wf_put_control(struct wf_control *ct); | ||
60 | |||
61 | static 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 | |||
67 | static 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 | |||
77 | struct wf_sensor; | ||
78 | |||
79 | struct 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 | |||
85 | struct 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 */ | ||
93 | extern int wf_register_sensor(struct wf_sensor *sr); | ||
94 | extern void wf_unregister_sensor(struct wf_sensor *sr); | ||
95 | extern struct wf_sensor * wf_find_sensor(const char *name); | ||
96 | extern int wf_get_sensor(struct wf_sensor *sr); | ||
97 | extern 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 | */ | ||
111 | extern int wf_register_client(struct notifier_block *nb); | ||
112 | extern int wf_unregister_client(struct notifier_block *nb); | ||
113 | |||
114 | /* Overtemp conditions. Those are refcounted */ | ||
115 | extern void wf_set_overtemp(void); | ||
116 | extern void wf_clear_overtemp(void); | ||
117 | extern 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 | |||
49 | static LIST_HEAD(wf_controls); | ||
50 | static LIST_HEAD(wf_sensors); | ||
51 | static DECLARE_MUTEX(wf_lock); | ||
52 | static struct notifier_block *wf_client_list; | ||
53 | static int wf_client_count; | ||
54 | static unsigned int wf_overtemp; | ||
55 | static unsigned int wf_overtemp_counter; | ||
56 | struct task_struct *wf_thread; | ||
57 | |||
58 | /* | ||
59 | * Utilities & tick thread | ||
60 | */ | ||
61 | |||
62 | static inline void wf_notify(int event, void *param) | ||
63 | { | ||
64 | notifier_call_chain(&wf_client_list, event, param); | ||
65 | } | ||
66 | |||
67 | int 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 | } | ||
78 | EXPORT_SYMBOL_GPL(wf_critical_overtemp); | ||
79 | |||
80 | static 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 | |||
125 | static 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 | |||
136 | static 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 | |||
147 | static 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 | |||
159 | int 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 | } | ||
182 | EXPORT_SYMBOL_GPL(wf_register_control); | ||
183 | |||
184 | void 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 | } | ||
194 | EXPORT_SYMBOL_GPL(wf_unregister_control); | ||
195 | |||
196 | struct 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 | } | ||
212 | EXPORT_SYMBOL_GPL(wf_find_control); | ||
213 | |||
214 | int 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 | } | ||
221 | EXPORT_SYMBOL_GPL(wf_get_control); | ||
222 | |||
223 | void 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 | } | ||
229 | EXPORT_SYMBOL_GPL(wf_put_control); | ||
230 | |||
231 | |||
232 | /* | ||
233 | * Sensors | ||
234 | */ | ||
235 | |||
236 | |||
237 | static 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 | |||
249 | int 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 | } | ||
272 | EXPORT_SYMBOL_GPL(wf_register_sensor); | ||
273 | |||
274 | void 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 | } | ||
284 | EXPORT_SYMBOL_GPL(wf_unregister_sensor); | ||
285 | |||
286 | struct 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 | } | ||
302 | EXPORT_SYMBOL_GPL(wf_find_sensor); | ||
303 | |||
304 | int 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 | } | ||
311 | EXPORT_SYMBOL_GPL(wf_get_sensor); | ||
312 | |||
313 | void 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 | } | ||
319 | EXPORT_SYMBOL_GPL(wf_put_sensor); | ||
320 | |||
321 | |||
322 | /* | ||
323 | * Client & notification | ||
324 | */ | ||
325 | |||
326 | int 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 | } | ||
347 | EXPORT_SYMBOL_GPL(wf_register_client); | ||
348 | |||
349 | int 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 | } | ||
360 | EXPORT_SYMBOL_GPL(wf_unregister_client); | ||
361 | |||
362 | void 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 | } | ||
373 | EXPORT_SYMBOL_GPL(wf_set_overtemp); | ||
374 | |||
375 | void 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 | } | ||
390 | EXPORT_SYMBOL_GPL(wf_clear_overtemp); | ||
391 | |||
392 | int wf_is_overtemp(void) | ||
393 | { | ||
394 | return (wf_overtemp != 0); | ||
395 | } | ||
396 | EXPORT_SYMBOL_GPL(wf_is_overtemp); | ||
397 | |||
398 | static struct platform_device wf_platform_device = { | ||
399 | .name = "windfarm", | ||
400 | }; | ||
401 | |||
402 | static 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 | |||
410 | static 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 | |||
420 | module_init(windfarm_core_init); | ||
421 | module_exit(windfarm_core_exit); | ||
422 | |||
423 | MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); | ||
424 | MODULE_DESCRIPTION("Core component of PowerMac thermal control"); | ||
425 | MODULE_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 | |||
15 | static int clamped; | ||
16 | static struct wf_control *clamp_control; | ||
17 | |||
18 | static 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 | |||
33 | static struct notifier_block clamp_notifier = { | ||
34 | .notifier_call = clamp_notifier_call, | ||
35 | }; | ||
36 | |||
37 | static 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 | |||
49 | static int clamp_get(struct wf_control *ct, s32 *value) | ||
50 | { | ||
51 | *value = clamped; | ||
52 | return 0; | ||
53 | } | ||
54 | |||
55 | static s32 clamp_min(struct wf_control *ct) | ||
56 | { | ||
57 | return 0; | ||
58 | } | ||
59 | |||
60 | static s32 clamp_max(struct wf_control *ct) | ||
61 | { | ||
62 | return 1; | ||
63 | } | ||
64 | |||
65 | static 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 | |||
73 | static 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 | |||
92 | static void __exit wf_cpufreq_clamp_exit(void) | ||
93 | { | ||
94 | if (clamp_control) | ||
95 | wf_unregister_control(clamp_control); | ||
96 | } | ||
97 | |||
98 | |||
99 | module_init(wf_cpufreq_clamp_init); | ||
100 | module_exit(wf_cpufreq_clamp_exit); | ||
101 | |||
102 | MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); | ||
103 | MODULE_DESCRIPTION("CPU frequency clamp for PowerMacs thermal control"); | ||
104 | MODULE_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 | |||
37 | struct 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 | |||
46 | static int wf_lm75_attach(struct i2c_adapter *adapter); | ||
47 | static int wf_lm75_detach(struct i2c_client *client); | ||
48 | |||
49 | static 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 | |||
57 | static 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 | |||
91 | static 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 | |||
104 | static struct wf_sensor_ops wf_lm75_ops = { | ||
105 | .get_value = wf_lm75_get, | ||
106 | .release = wf_lm75_release, | ||
107 | .owner = THIS_MODULE, | ||
108 | }; | ||
109 | |||
110 | static 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 | |||
158 | static 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 | |||
226 | static 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 | |||
241 | static 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 | |||
251 | static void __exit wf_lm75_sensor_exit(void) | ||
252 | { | ||
253 | i2c_del_driver(&wf_lm75_driver); | ||
254 | } | ||
255 | |||
256 | |||
257 | module_init(wf_lm75_sensor_init); | ||
258 | module_exit(wf_lm75_sensor_exit); | ||
259 | |||
260 | MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); | ||
261 | MODULE_DESCRIPTION("LM75 sensor objects for PowerMacs thermal control"); | ||
262 | MODULE_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 | |||
26 | void 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 | } | ||
32 | EXPORT_SYMBOL_GPL(wf_pid_init); | ||
33 | |||
34 | s32 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 | } | ||
78 | EXPORT_SYMBOL_GPL(wf_pid_run); | ||
79 | |||
80 | void 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 | } | ||
87 | EXPORT_SYMBOL_GPL(wf_cpu_pid_init); | ||
88 | |||
89 | s32 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 | } | ||
145 | EXPORT_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 | */ | ||
26 | struct 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 | |||
35 | struct 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 | |||
45 | extern void wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param); | ||
46 | extern 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 | */ | ||
60 | struct 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 | |||
70 | struct 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 | |||
82 | extern void wf_cpu_pid_init(struct wf_cpu_pid_state *st, | ||
83 | struct wf_cpu_pid_param *param); | ||
84 | extern 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 | |||
132 | static int wf_smu_mach_model; /* machine model id */ | ||
133 | |||
134 | static struct device *wf_smu_dev; | ||
135 | |||
136 | /* Controls & sensors */ | ||
137 | static struct wf_sensor *sensor_cpu_power; | ||
138 | static struct wf_sensor *sensor_cpu_temp; | ||
139 | static struct wf_sensor *sensor_hd_temp; | ||
140 | static struct wf_control *fan_cpu_main; | ||
141 | static struct wf_control *fan_hd; | ||
142 | static struct wf_control *fan_system; | ||
143 | static struct wf_control *cpufreq_clamp; | ||
144 | |||
145 | /* Set to kick the control loop into life */ | ||
146 | static 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 | |||
153 | static unsigned int wf_smu_failure_state; | ||
154 | static 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 | */ | ||
165 | struct 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 | */ | ||
181 | struct 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 | */ | ||
195 | static 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 | |||
235 | static 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 | */ | ||
250 | struct 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 | |||
258 | static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans; | ||
259 | |||
260 | |||
261 | |||
262 | /* | ||
263 | * ***** Implementation ***** | ||
264 | * | ||
265 | */ | ||
266 | |||
267 | static 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 | |||
331 | static 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 | |||
396 | static 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 | |||
475 | static 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) \ | ||
548 | static 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 | } \ | ||
558 | static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL); | ||
559 | |||
560 | |||
561 | #define BUILD_SHOW_FUNC_INT(name, data) \ | ||
562 | static 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 | } \ | ||
570 | static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL); | ||
571 | |||
572 | BUILD_SHOW_FUNC_INT(cpu_fan, fan_cpu_main); | ||
573 | BUILD_SHOW_FUNC_INT(sys_fan, fan_system); | ||
574 | BUILD_SHOW_FUNC_INT(hd_fan, fan_hd); | ||
575 | |||
576 | BUILD_SHOW_FUNC_FIX(cpu_temp, sensor_cpu_temp); | ||
577 | BUILD_SHOW_FUNC_FIX(cpu_power, sensor_cpu_power); | ||
578 | BUILD_SHOW_FUNC_FIX(hd_temp, sensor_hd_temp); | ||
579 | |||
580 | /* | ||
581 | * ****** Setup / Init / Misc ... ****** | ||
582 | * | ||
583 | */ | ||
584 | |||
585 | static 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 | |||
651 | static 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 | |||
696 | static 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 | |||
727 | static 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 | |||
750 | static struct notifier_block wf_smu_events = { | ||
751 | .notifier_call = wf_smu_notify, | ||
752 | }; | ||
753 | |||
754 | static 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 | |||
771 | static 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 | |||
780 | static 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 | |||
837 | static 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 | |||
845 | static 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 | |||
866 | static void __exit wf_smu_exit(void) | ||
867 | { | ||
868 | |||
869 | driver_unregister(&wf_smu_driver); | ||
870 | } | ||
871 | |||
872 | |||
873 | module_init(wf_smu_init); | ||
874 | module_exit(wf_smu_exit); | ||
875 | |||
876 | MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); | ||
877 | MODULE_DESCRIPTION("Thermal control logic for iMac G5"); | ||
878 | MODULE_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 | |||
66 | static struct device *wf_smu_dev; | ||
67 | |||
68 | /* Controls & sensors */ | ||
69 | static struct wf_sensor *sensor_cpu_power; | ||
70 | static struct wf_sensor *sensor_cpu_temp; | ||
71 | static struct wf_sensor *sensor_hd_temp; | ||
72 | static struct wf_sensor *sensor_slots_power; | ||
73 | static struct wf_control *fan_cpu_main; | ||
74 | static struct wf_control *fan_cpu_second; | ||
75 | static struct wf_control *fan_cpu_third; | ||
76 | static struct wf_control *fan_hd; | ||
77 | static struct wf_control *fan_slots; | ||
78 | static struct wf_control *cpufreq_clamp; | ||
79 | |||
80 | /* Set to kick the control loop into life */ | ||
81 | static 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 | |||
88 | static unsigned int wf_smu_failure_state; | ||
89 | static 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 | */ | ||
102 | struct wf_smu_cpu_fans_state { | ||
103 | int ticks; | ||
104 | s32 cpu_setpoint; | ||
105 | struct wf_cpu_pid_state pid; | ||
106 | }; | ||
107 | |||
108 | static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans; | ||
109 | |||
110 | |||
111 | |||
112 | /* | ||
113 | * ****** Drive Fan Control Loop ****** | ||
114 | * | ||
115 | */ | ||
116 | |||
117 | struct wf_smu_drive_fans_state { | ||
118 | int ticks; | ||
119 | s32 setpoint; | ||
120 | struct wf_pid_state pid; | ||
121 | }; | ||
122 | |||
123 | static struct wf_smu_drive_fans_state *wf_smu_drive_fans; | ||
124 | |||
125 | /* | ||
126 | * ****** Slots Fan Control Loop ****** | ||
127 | * | ||
128 | */ | ||
129 | |||
130 | struct wf_smu_slots_fans_state { | ||
131 | int ticks; | ||
132 | s32 setpoint; | ||
133 | struct wf_pid_state pid; | ||
134 | }; | ||
135 | |||
136 | static struct wf_smu_slots_fans_state *wf_smu_slots_fans; | ||
137 | |||
138 | /* | ||
139 | * ***** Implementation ***** | ||
140 | * | ||
141 | */ | ||
142 | |||
143 | |||
144 | static 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 | |||
220 | static 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 | |||
295 | static 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, ¶m); | ||
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 | |||
332 | static 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 | |||
376 | static 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, ¶m); | ||
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 | |||
413 | static 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) \ | ||
466 | static 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 | } \ | ||
476 | static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL); | ||
477 | |||
478 | |||
479 | #define BUILD_SHOW_FUNC_INT(name, data) \ | ||
480 | static 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 | } \ | ||
488 | static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL); | ||
489 | |||
490 | BUILD_SHOW_FUNC_INT(cpu_fan, fan_cpu_main); | ||
491 | BUILD_SHOW_FUNC_INT(hd_fan, fan_hd); | ||
492 | BUILD_SHOW_FUNC_INT(slots_fan, fan_slots); | ||
493 | |||
494 | BUILD_SHOW_FUNC_FIX(cpu_temp, sensor_cpu_temp); | ||
495 | BUILD_SHOW_FUNC_FIX(cpu_power, sensor_cpu_power); | ||
496 | BUILD_SHOW_FUNC_FIX(hd_temp, sensor_hd_temp); | ||
497 | BUILD_SHOW_FUNC_FIX(slots_power, sensor_slots_power); | ||
498 | |||
499 | /* | ||
500 | * ****** Setup / Init / Misc ... ****** | ||
501 | * | ||
502 | */ | ||
503 | |||
504 | static 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 | |||
578 | static 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 | |||
624 | static 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 | |||
663 | static 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 | |||
686 | static struct notifier_block wf_smu_events = { | ||
687 | .notifier_call = wf_smu_notify, | ||
688 | }; | ||
689 | |||
690 | static int wf_init_pm(void) | ||
691 | { | ||
692 | printk(KERN_INFO "windfarm: Initializing for Desktop G5 model\n"); | ||
693 | |||
694 | return 0; | ||
695 | } | ||
696 | |||
697 | static 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 | |||
706 | static 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 | |||
773 | static 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 | |||
781 | static 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 | |||
801 | static void __exit wf_smu_exit(void) | ||
802 | { | ||
803 | |||
804 | driver_unregister(&wf_smu_driver); | ||
805 | } | ||
806 | |||
807 | |||
808 | module_init(wf_smu_init); | ||
809 | module_exit(wf_smu_exit); | ||
810 | |||
811 | MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); | ||
812 | MODULE_DESCRIPTION("Thermal control logic for PowerMac9,1"); | ||
813 | MODULE_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 | |||
40 | static LIST_HEAD(smu_fans); | ||
41 | |||
42 | struct 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 | |||
52 | static 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 = ∁ | ||
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 | |||
81 | static 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 | |||
88 | static 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 | |||
101 | static 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 | |||
108 | static 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 | |||
114 | static 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 | |||
120 | static 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 | |||
129 | static 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 | |||
212 | static 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 | |||
264 | static 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 | |||
276 | module_init(smu_controls_init); | ||
277 | module_exit(smu_controls_exit); | ||
278 | |||
279 | MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); | ||
280 | MODULE_DESCRIPTION("SMU control objects for PowerMacs thermal control"); | ||
281 | MODULE_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 | */ | ||
40 | static struct smu_sdbp_cpuvcp *cpuvcp; | ||
41 | static int cpuvcp_version; | ||
42 | static struct smu_sdbp_cpudiode *cpudiode; | ||
43 | static struct smu_sdbp_slotspow *slotspow; | ||
44 | static u8 *debugswitches; | ||
45 | |||
46 | /* | ||
47 | * SMU basic sensors objects | ||
48 | */ | ||
49 | |||
50 | static LIST_HEAD(smu_ads); | ||
51 | |||
52 | struct 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 | |||
59 | static 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 | |||
66 | static 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 | |||
88 | static 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 | |||
111 | static 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 | |||
132 | static 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 | |||
153 | static 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 | |||
175 | static struct wf_sensor_ops smu_cputemp_ops = { | ||
176 | .get_value = smu_cputemp_get, | ||
177 | .release = smu_ads_release, | ||
178 | .owner = THIS_MODULE, | ||
179 | }; | ||
180 | static struct wf_sensor_ops smu_cpuamp_ops = { | ||
181 | .get_value = smu_cpuamp_get, | ||
182 | .release = smu_ads_release, | ||
183 | .owner = THIS_MODULE, | ||
184 | }; | ||
185 | static struct wf_sensor_ops smu_cpuvolt_ops = { | ||
186 | .get_value = smu_cpuvolt_get, | ||
187 | .release = smu_ads_release, | ||
188 | .owner = THIS_MODULE, | ||
189 | }; | ||
190 | static 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 | |||
197 | static 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 | |||
259 | struct 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 | |||
269 | static struct smu_cpu_power_sensor *smu_cpu_power; | ||
270 | |||
271 | static 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 | |||
282 | static 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, &s); | ||
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 | |||
315 | static 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 | |||
322 | static struct smu_cpu_power_sensor * | ||
323 | smu_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 | |||
367 | static 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 | |||
404 | static 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 | |||
456 | static 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 | |||
473 | module_init(smu_sensors_init); | ||
474 | module_exit(smu_sensors_exit); | ||
475 | |||
476 | MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); | ||
477 | MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control"); | ||
478 | MODULE_LICENSE("GPL"); | ||
479 | |||