Blame view

drivers/macintosh/windfarm_core.c 11 KB
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  /*
   * Windfarm PowerMac thermal control. Core
   *
   * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
   *                    <benh@kernel.crashing.org>
   *
   * Released under the term of the GNU GPL v2.
   *
   * This core code tracks the list of sensors & controls, register
   * clients, and holds the kernel thread used for control.
   *
   * TODO:
   *
   * Add some information about sensor/control type and data format to
   * sensors/controls, and have the sysfs attribute stuff be moved
   * generically here instead of hard coded in the platform specific
   * driver as it us currently
   *
   * This however requires solving some annoying lifetime issues with
   * sysfs which doesn't seem to have lifetime rules for struct attribute,
   * I may have to create full features kobjects for every sensor/control
   * instead which is a bit of an overkill imho
   */
  
  #include <linux/types.h>
  #include <linux/errno.h>
  #include <linux/kernel.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
28
  #include <linux/slab.h>
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
29
30
  #include <linux/init.h>
  #include <linux/spinlock.h>
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
31
32
33
34
35
  #include <linux/kthread.h>
  #include <linux/jiffies.h>
  #include <linux/reboot.h>
  #include <linux/device.h>
  #include <linux/platform_device.h>
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
36
  #include <linux/mutex.h>
7dfb71030   Nigel Cunningham   [PATCH] Add inclu...
37
  #include <linux/freezer.h>
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
38

b55fafc5a   Benjamin Herrenschmidt   [PATCH] powerpc: ...
39
  #include <asm/prom.h>
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
40
41
42
43
44
45
46
47
48
49
50
51
52
53
  #include "windfarm.h"
  
  #define VERSION "0.2"
  
  #undef DEBUG
  
  #ifdef DEBUG
  #define DBG(args...)	printk(args)
  #else
  #define DBG(args...)	do { } while(0)
  #endif
  
  static LIST_HEAD(wf_controls);
  static LIST_HEAD(wf_sensors);
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
54
  static DEFINE_MUTEX(wf_lock);
e041c6834   Alan Stern   [PATCH] Notifier ...
55
  static BLOCKING_NOTIFIER_HEAD(wf_client_list);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
56
57
58
59
  static int wf_client_count;
  static unsigned int wf_overtemp;
  static unsigned int wf_overtemp_counter;
  struct task_struct *wf_thread;
ac171c466   Benjamin Herrenschmidt   [PATCH] powerpc: ...
60
61
62
  static struct platform_device wf_platform_device = {
  	.name	= "windfarm",
  };
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
63
64
65
66
67
68
  /*
   * Utilities & tick thread
   */
  
  static inline void wf_notify(int event, void *param)
  {
e041c6834   Alan Stern   [PATCH] Notifier ...
69
  	blocking_notifier_call_chain(&wf_client_list, event, param);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
70
71
72
73
74
75
76
77
78
79
  }
  
  int wf_critical_overtemp(void)
  {
  	static char * critical_overtemp_path = "/sbin/critical_overtemp";
  	char *argv[] = { critical_overtemp_path, NULL };
  	static char *envp[] = { "HOME=/",
  				"TERM=linux",
  				"PATH=/sbin:/usr/sbin:/bin:/usr/bin",
  				NULL };
86313c488   Jeremy Fitzhardinge   usermodehelper: T...
80
81
  	return call_usermodehelper(critical_overtemp_path,
  				   argv, envp, UMH_WAIT_EXEC);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
82
83
84
85
86
87
88
89
90
91
92
  }
  EXPORT_SYMBOL_GPL(wf_critical_overtemp);
  
  static int wf_thread_func(void *data)
  {
  	unsigned long next, delay;
  
  	next = jiffies;
  
  	DBG("wf: thread started
  ");
831441862   Rafael J. Wysocki   Freezer: make ker...
93
  	set_freezable();
67b60518b   Johannes Berg   [POWERPC] windfar...
94
95
  	while (!kthread_should_stop()) {
  		try_to_freeze();
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
  		if (time_after_eq(jiffies, next)) {
  			wf_notify(WF_EVENT_TICK, NULL);
  			if (wf_overtemp) {
  				wf_overtemp_counter++;
  				/* 10 seconds overtemp, notify userland */
  				if (wf_overtemp_counter > 10)
  					wf_critical_overtemp();
  				/* 30 seconds, shutdown */
  				if (wf_overtemp_counter > 30) {
  					printk(KERN_ERR "windfarm: Overtemp "
  					       "for more than 30"
  					       " seconds, shutting down
  ");
  					machine_power_off();
  				}
  			}
  			next += HZ;
  		}
  
  		delay = next - jiffies;
  		if (delay <= HZ)
  			schedule_timeout_interruptible(delay);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
  	}
  
  	DBG("wf: thread stopped
  ");
  
  	return 0;
  }
  
  static void wf_start_thread(void)
  {
  	wf_thread = kthread_run(wf_thread_func, NULL, "kwindfarm");
  	if (IS_ERR(wf_thread)) {
  		printk(KERN_ERR "windfarm: failed to create thread,err %ld
  ",
  		       PTR_ERR(wf_thread));
  		wf_thread = NULL;
  	}
  }
  
  
  static void wf_stop_thread(void)
  {
  	if (wf_thread)
  		kthread_stop(wf_thread);
  	wf_thread = NULL;
  }
  
  /*
   * Controls
   */
  
  static void wf_control_release(struct kref *kref)
  {
  	struct wf_control *ct = container_of(kref, struct wf_control, ref);
  
  	DBG("wf: Deleting control %s
  ", ct->name);
  
  	if (ct->ops && ct->ops->release)
  		ct->ops->release(ct);
  	else
  		kfree(ct);
  }
ac171c466   Benjamin Herrenschmidt   [PATCH] powerpc: ...
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
  static ssize_t wf_show_control(struct device *dev,
  			       struct device_attribute *attr, char *buf)
  {
  	struct wf_control *ctrl = container_of(attr, struct wf_control, attr);
  	s32 val = 0;
  	int err;
  
  	err = ctrl->ops->get_value(ctrl, &val);
  	if (err < 0)
  		return err;
  	return sprintf(buf, "%d
  ", val);
  }
  
  /* This is really only for debugging... */
  static ssize_t wf_store_control(struct device *dev,
  				struct device_attribute *attr,
  				const char *buf, size_t count)
  {
  	struct wf_control *ctrl = container_of(attr, struct wf_control, attr);
  	int val;
  	int err;
  	char *endp;
  
  	val = simple_strtoul(buf, &endp, 0);
  	while (endp < buf + count && (*endp == ' ' || *endp == '
  '))
  		++endp;
  	if (endp - buf < count)
  		return -EINVAL;
  	err = ctrl->ops->set_value(ctrl, val);
  	if (err < 0)
  		return err;
  	return count;
  }
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
196
197
198
  int wf_register_control(struct wf_control *new_ct)
  {
  	struct wf_control *ct;
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
199
  	mutex_lock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
200
201
202
203
204
  	list_for_each_entry(ct, &wf_controls, link) {
  		if (!strcmp(ct->name, new_ct->name)) {
  			printk(KERN_WARNING "windfarm: trying to register"
  			       " duplicate control %s
  ", ct->name);
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
205
  			mutex_unlock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
206
207
208
209
210
  			return -EEXIST;
  		}
  	}
  	kref_init(&new_ct->ref);
  	list_add(&new_ct->link, &wf_controls);
12765517d   Wolfram Sang   device_attributes...
211
  	sysfs_attr_init(&new_ct->attr.attr);
ac171c466   Benjamin Herrenschmidt   [PATCH] powerpc: ...
212
  	new_ct->attr.attr.name = new_ct->name;
ac171c466   Benjamin Herrenschmidt   [PATCH] powerpc: ...
213
214
215
  	new_ct->attr.attr.mode = 0644;
  	new_ct->attr.show = wf_show_control;
  	new_ct->attr.store = wf_store_control;
ccd308f09   Stephen Rothwell   [POWERPC] Remove ...
216
217
218
219
220
  	if (device_create_file(&wf_platform_device.dev, &new_ct->attr))
  		printk(KERN_WARNING "windfarm: device_create_file failed"
  			" for %s
  ", new_ct->name);
  		/* the subsystem still does useful work without the file */
ac171c466   Benjamin Herrenschmidt   [PATCH] powerpc: ...
221

75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
222
223
224
225
  	DBG("wf: Registered control %s
  ", new_ct->name);
  
  	wf_notify(WF_EVENT_NEW_CONTROL, new_ct);
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
226
  	mutex_unlock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
227
228
229
230
231
232
233
  
  	return 0;
  }
  EXPORT_SYMBOL_GPL(wf_register_control);
  
  void wf_unregister_control(struct wf_control *ct)
  {
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
234
  	mutex_lock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
235
  	list_del(&ct->link);
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
236
  	mutex_unlock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
237
238
239
240
241
242
243
244
245
246
247
  
  	DBG("wf: Unregistered control %s
  ", ct->name);
  
  	kref_put(&ct->ref, wf_control_release);
  }
  EXPORT_SYMBOL_GPL(wf_unregister_control);
  
  struct wf_control * wf_find_control(const char *name)
  {
  	struct wf_control *ct;
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
248
  	mutex_lock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
249
250
251
252
  	list_for_each_entry(ct, &wf_controls, link) {
  		if (!strcmp(ct->name, name)) {
  			if (wf_get_control(ct))
  				ct = NULL;
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
253
  			mutex_unlock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
254
255
256
  			return ct;
  		}
  	}
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
257
  	mutex_unlock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
  	return NULL;
  }
  EXPORT_SYMBOL_GPL(wf_find_control);
  
  int wf_get_control(struct wf_control *ct)
  {
  	if (!try_module_get(ct->ops->owner))
  		return -ENODEV;
  	kref_get(&ct->ref);
  	return 0;
  }
  EXPORT_SYMBOL_GPL(wf_get_control);
  
  void wf_put_control(struct wf_control *ct)
  {
  	struct module *mod = ct->ops->owner;
  	kref_put(&ct->ref, wf_control_release);
  	module_put(mod);
  }
  EXPORT_SYMBOL_GPL(wf_put_control);
  
  
  /*
   * Sensors
   */
  
  
  static void wf_sensor_release(struct kref *kref)
  {
  	struct wf_sensor *sr = container_of(kref, struct wf_sensor, ref);
  
  	DBG("wf: Deleting sensor %s
  ", sr->name);
  
  	if (sr->ops && sr->ops->release)
  		sr->ops->release(sr);
  	else
  		kfree(sr);
  }
ac171c466   Benjamin Herrenschmidt   [PATCH] powerpc: ...
297
298
299
300
301
302
303
304
305
306
307
308
309
  static ssize_t wf_show_sensor(struct device *dev,
  			      struct device_attribute *attr, char *buf)
  {
  	struct wf_sensor *sens = container_of(attr, struct wf_sensor, attr);
  	s32 val = 0;
  	int err;
  
  	err = sens->ops->get_value(sens, &val);
  	if (err < 0)
  		return err;
  	return sprintf(buf, "%d.%03d
  ", FIX32TOPRINT(val));
  }
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
310
311
312
  int wf_register_sensor(struct wf_sensor *new_sr)
  {
  	struct wf_sensor *sr;
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
313
  	mutex_lock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
314
315
316
317
318
  	list_for_each_entry(sr, &wf_sensors, link) {
  		if (!strcmp(sr->name, new_sr->name)) {
  			printk(KERN_WARNING "windfarm: trying to register"
  			       " duplicate sensor %s
  ", sr->name);
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
319
  			mutex_unlock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
320
321
322
323
324
  			return -EEXIST;
  		}
  	}
  	kref_init(&new_sr->ref);
  	list_add(&new_sr->link, &wf_sensors);
b35c74dab   Johannes Berg   sysfs: windfarm: ...
325
  	sysfs_attr_init(&new_sr->attr.attr);
ac171c466   Benjamin Herrenschmidt   [PATCH] powerpc: ...
326
  	new_sr->attr.attr.name = new_sr->name;
ac171c466   Benjamin Herrenschmidt   [PATCH] powerpc: ...
327
328
329
  	new_sr->attr.attr.mode = 0444;
  	new_sr->attr.show = wf_show_sensor;
  	new_sr->attr.store = NULL;
ccd308f09   Stephen Rothwell   [POWERPC] Remove ...
330
331
332
333
334
  	if (device_create_file(&wf_platform_device.dev, &new_sr->attr))
  		printk(KERN_WARNING "windfarm: device_create_file failed"
  			" for %s
  ", new_sr->name);
  		/* the subsystem still does useful work without the file */
ac171c466   Benjamin Herrenschmidt   [PATCH] powerpc: ...
335

75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
336
337
338
339
  	DBG("wf: Registered sensor %s
  ", new_sr->name);
  
  	wf_notify(WF_EVENT_NEW_SENSOR, new_sr);
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
340
  	mutex_unlock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
341
342
343
344
345
346
347
  
  	return 0;
  }
  EXPORT_SYMBOL_GPL(wf_register_sensor);
  
  void wf_unregister_sensor(struct wf_sensor *sr)
  {
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
348
  	mutex_lock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
349
  	list_del(&sr->link);
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
350
  	mutex_unlock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
351
352
353
354
355
356
357
358
359
360
361
  
  	DBG("wf: Unregistered sensor %s
  ", sr->name);
  
  	wf_put_sensor(sr);
  }
  EXPORT_SYMBOL_GPL(wf_unregister_sensor);
  
  struct wf_sensor * wf_find_sensor(const char *name)
  {
  	struct wf_sensor *sr;
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
362
  	mutex_lock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
363
364
365
366
  	list_for_each_entry(sr, &wf_sensors, link) {
  		if (!strcmp(sr->name, name)) {
  			if (wf_get_sensor(sr))
  				sr = NULL;
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
367
  			mutex_unlock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
368
369
370
  			return sr;
  		}
  	}
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
371
  	mutex_unlock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
  	return NULL;
  }
  EXPORT_SYMBOL_GPL(wf_find_sensor);
  
  int wf_get_sensor(struct wf_sensor *sr)
  {
  	if (!try_module_get(sr->ops->owner))
  		return -ENODEV;
  	kref_get(&sr->ref);
  	return 0;
  }
  EXPORT_SYMBOL_GPL(wf_get_sensor);
  
  void wf_put_sensor(struct wf_sensor *sr)
  {
  	struct module *mod = sr->ops->owner;
  	kref_put(&sr->ref, wf_sensor_release);
  	module_put(mod);
  }
  EXPORT_SYMBOL_GPL(wf_put_sensor);
  
  
  /*
   * Client & notification
   */
  
  int wf_register_client(struct notifier_block *nb)
  {
  	int rc;
  	struct wf_control *ct;
  	struct wf_sensor *sr;
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
403
  	mutex_lock(&wf_lock);
e041c6834   Alan Stern   [PATCH] Notifier ...
404
  	rc = blocking_notifier_chain_register(&wf_client_list, nb);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
405
406
407
408
409
410
411
412
413
414
  	if (rc != 0)
  		goto bail;
  	wf_client_count++;
  	list_for_each_entry(ct, &wf_controls, link)
  		wf_notify(WF_EVENT_NEW_CONTROL, ct);
  	list_for_each_entry(sr, &wf_sensors, link)
  		wf_notify(WF_EVENT_NEW_SENSOR, sr);
  	if (wf_client_count == 1)
  		wf_start_thread();
   bail:
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
415
  	mutex_unlock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
416
417
418
419
420
421
  	return rc;
  }
  EXPORT_SYMBOL_GPL(wf_register_client);
  
  int wf_unregister_client(struct notifier_block *nb)
  {
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
422
  	mutex_lock(&wf_lock);
e041c6834   Alan Stern   [PATCH] Notifier ...
423
  	blocking_notifier_chain_unregister(&wf_client_list, nb);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
424
425
426
  	wf_client_count++;
  	if (wf_client_count == 0)
  		wf_stop_thread();
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
427
  	mutex_unlock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
428
429
430
431
432
433
434
  
  	return 0;
  }
  EXPORT_SYMBOL_GPL(wf_unregister_client);
  
  void wf_set_overtemp(void)
  {
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
435
  	mutex_lock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
436
437
438
439
440
441
442
  	wf_overtemp++;
  	if (wf_overtemp == 1) {
  		printk(KERN_WARNING "windfarm: Overtemp condition detected !
  ");
  		wf_overtemp_counter = 0;
  		wf_notify(WF_EVENT_OVERTEMP, NULL);
  	}
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
443
  	mutex_unlock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
444
445
446
447
448
  }
  EXPORT_SYMBOL_GPL(wf_set_overtemp);
  
  void wf_clear_overtemp(void)
  {
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
449
  	mutex_lock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
450
451
  	WARN_ON(wf_overtemp == 0);
  	if (wf_overtemp == 0) {
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
452
  		mutex_unlock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
453
454
455
456
457
458
459
460
  		return;
  	}
  	wf_overtemp--;
  	if (wf_overtemp == 0) {
  		printk(KERN_WARNING "windfarm: Overtemp condition cleared !
  ");
  		wf_notify(WF_EVENT_NORMALTEMP, NULL);
  	}
837e9594f   Ingo Molnar   [PATCH] sem2mutex...
461
  	mutex_unlock(&wf_lock);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
462
463
464
465
466
467
468
469
  }
  EXPORT_SYMBOL_GPL(wf_clear_overtemp);
  
  int wf_is_overtemp(void)
  {
  	return (wf_overtemp != 0);
  }
  EXPORT_SYMBOL_GPL(wf_is_overtemp);
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
470
471
472
473
  static int __init windfarm_core_init(void)
  {
  	DBG("wf: core loaded
  ");
b55fafc5a   Benjamin Herrenschmidt   [PATCH] powerpc: ...
474
  	/* Don't register on old machines that use therm_pm72 for now */
71a157e8e   Grant Likely   of: add 'of_' pre...
475
476
477
  	if (of_machine_is_compatible("PowerMac7,2") ||
  	    of_machine_is_compatible("PowerMac7,3") ||
  	    of_machine_is_compatible("RackMac3,1"))
b55fafc5a   Benjamin Herrenschmidt   [PATCH] powerpc: ...
478
  		return -ENODEV;
75722d399   Benjamin Herrenschmidt   [PATCH] ppc64: Th...
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
  	platform_device_register(&wf_platform_device);
  	return 0;
  }
  
  static void __exit windfarm_core_exit(void)
  {
  	BUG_ON(wf_client_count != 0);
  
  	DBG("wf: core unloaded
  ");
  
  	platform_device_unregister(&wf_platform_device);
  }
  
  
  module_init(windfarm_core_init);
  module_exit(windfarm_core_exit);
  
  MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
  MODULE_DESCRIPTION("Core component of PowerMac thermal control");
  MODULE_LICENSE("GPL");