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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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
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
196
197
198
199
200
201
202#undef DEBUG
203
204#include <linux/types.h>
205#include <linux/errno.h>
206#include <linux/kernel.h>
207#include <linux/delay.h>
208#include <linux/slab.h>
209#include <linux/init.h>
210#include <linux/spinlock.h>
211#include <linux/wait.h>
212#include <linux/kmod.h>
213#include <linux/device.h>
214#include <linux/platform_device.h>
215#include <asm/prom.h>
216#include <asm/machdep.h>
217#include <asm/io.h>
218#include <asm/sections.h>
219#include <asm/smu.h>
220
221#include "windfarm.h"
222#include "windfarm_pid.h"
223
224#define VERSION "0.3"
225
226static int pm121_mach_model;
227
228
229static struct wf_sensor *sensor_cpu_power;
230static struct wf_sensor *sensor_cpu_temp;
231static struct wf_sensor *sensor_cpu_voltage;
232static struct wf_sensor *sensor_cpu_current;
233static struct wf_sensor *sensor_gpu_temp;
234static struct wf_sensor *sensor_north_bridge_temp;
235static struct wf_sensor *sensor_hard_drive_temp;
236static struct wf_sensor *sensor_optical_drive_temp;
237static struct wf_sensor *sensor_incoming_air_temp;
238
239enum {
240 FAN_CPU,
241 FAN_HD,
242 FAN_OD,
243 CPUFREQ,
244 N_CONTROLS
245};
246static struct wf_control *controls[N_CONTROLS] = {};
247
248
249static int pm121_all_controls_ok, pm121_all_sensors_ok, pm121_started;
250
251enum {
252 FAILURE_FAN = 1 << 0,
253 FAILURE_SENSOR = 1 << 1,
254 FAILURE_OVERTEMP = 1 << 2
255};
256
257
258
259enum {
260 LOOP_GPU,
261
262 LOOP_HD,
263 LOOP_KODIAK,
264 LOOP_OD,
265 N_LOOPS
266};
267
268static const char *loop_names[N_LOOPS] = {
269 "GPU",
270 "HD",
271 "KODIAK",
272 "OD",
273};
274
275#define PM121_NUM_CONFIGS 2
276
277static unsigned int pm121_failure_state;
278static int pm121_readjust, pm121_skipping;
279static s32 average_power;
280
281struct pm121_correction {
282 int offset;
283 int slope;
284};
285
286static struct pm121_correction corrections[N_CONTROLS][PM121_NUM_CONFIGS] = {
287
288 {
289
290 { .offset = -19563152,
291 .slope = 1956315
292 },
293
294 { .offset = -15650652,
295 .slope = 1565065
296 },
297 },
298
299 {
300
301 { .offset = -15650652,
302 .slope = 1565065
303 },
304
305 { .offset = -19563152,
306 .slope = 1956315
307 },
308 },
309
310 {
311
312 { .offset = -25431900,
313 .slope = 2543190
314 },
315
316 { .offset = -15650652,
317 .slope = 1565065
318 },
319 },
320
321};
322
323struct pm121_connection {
324 unsigned int control_id;
325 unsigned int ref_id;
326 struct pm121_correction correction;
327};
328
329static struct pm121_connection pm121_connections[] = {
330
331 { .control_id = FAN_CPU,
332 .ref_id = FAN_OD,
333 { .offset = -32768000,
334 .slope = 65536
335 }
336 },
337
338 { .control_id = FAN_OD,
339 .ref_id = FAN_HD,
340 { .offset = -32768000,
341 .slope = 65536
342 }
343 },
344};
345
346
347static struct pm121_connection *pm121_connection;
348
349
350
351
352
353
354
355
356
357
358
359struct pm121_sys_param {
360
361 int model_id;
362 struct wf_sensor **sensor;
363 s32 gp, itarget;
364 unsigned int control_id;
365};
366
367static struct pm121_sys_param
368pm121_sys_all_params[N_LOOPS][PM121_NUM_CONFIGS] = {
369
370 {
371 { .model_id = 2,
372 .sensor = &sensor_gpu_temp,
373 .gp = 0x002A6666,
374 .itarget = 0x5A0000,
375 .control_id = FAN_HD,
376 },
377 { .model_id = 3,
378 .sensor = &sensor_gpu_temp,
379 .gp = 0x0010CCCC,
380 .itarget = 0x500000,
381 .control_id = FAN_CPU,
382 },
383 },
384
385 {
386 { .model_id = 2,
387 .sensor = &sensor_hard_drive_temp,
388 .gp = 0x002D70A3,
389 .itarget = 0x370000,
390 .control_id = FAN_HD,
391 },
392 { .model_id = 3,
393 .sensor = &sensor_hard_drive_temp,
394 .gp = 0x002170A3,
395 .itarget = 0x370000,
396 .control_id = FAN_HD,
397 },
398 },
399
400 {
401 { .model_id = 2,
402 .sensor = &sensor_north_bridge_temp,
403 .gp = 0x003BD70A,
404 .itarget = 0x550000,
405 .control_id = FAN_OD,
406 },
407 { .model_id = 3,
408 .sensor = &sensor_north_bridge_temp,
409 .gp = 0x0030F5C2,
410 .itarget = 0x550000,
411 .control_id = FAN_HD,
412 },
413 },
414
415 {
416 { .model_id = 2,
417 .sensor = &sensor_optical_drive_temp,
418 .gp = 0x001FAE14,
419 .itarget = 0x320000,
420 .control_id = FAN_OD,
421 },
422 { .model_id = 3,
423 .sensor = &sensor_optical_drive_temp,
424 .gp = 0x001FAE14,
425 .itarget = 0x320000,
426 .control_id = FAN_OD,
427 },
428 },
429};
430
431
432#define PM121_SYS_GD 0x00000000
433#define PM121_SYS_GR 0x00019999
434#define PM121_SYS_HISTORY_SIZE 2
435#define PM121_SYS_INTERVAL 5
436
437
438
439struct pm121_sys_state {
440 int ticks;
441 s32 setpoint;
442 struct wf_pid_state pid;
443};
444
445struct pm121_sys_state *pm121_sys_state[N_LOOPS] = {};
446
447
448
449
450
451
452#define PM121_CPU_INTERVAL 1
453
454
455
456struct pm121_cpu_state {
457 int ticks;
458 s32 setpoint;
459 struct wf_cpu_pid_state pid;
460};
461
462static struct pm121_cpu_state *pm121_cpu_state;
463
464
465
466
467
468
469
470
471
472static s32 pm121_correct(s32 new_setpoint,
473 unsigned int control_id,
474 s32 min)
475{
476 s32 new_min;
477 struct pm121_correction *correction;
478 correction = &corrections[control_id][pm121_mach_model - 2];
479
480 new_min = (average_power * correction->slope) >> 16;
481 new_min += correction->offset;
482 new_min = (new_min >> 16) + min;
483
484 return max3(new_setpoint, new_min, 0);
485}
486
487static s32 pm121_connect(unsigned int control_id, s32 setpoint)
488{
489 s32 new_min, value, new_setpoint;
490
491 if (pm121_connection->control_id == control_id) {
492 controls[control_id]->ops->get_value(controls[control_id],
493 &value);
494 new_min = value * pm121_connection->correction.slope;
495 new_min += pm121_connection->correction.offset;
496 if (new_min > 0) {
497 new_setpoint = max(setpoint, (new_min >> 16));
498 if (new_setpoint != setpoint) {
499 pr_debug("pm121: %s depending on %s, "
500 "corrected from %d to %d RPM\n",
501 controls[control_id]->name,
502 controls[pm121_connection->ref_id]->name,
503 (int) setpoint, (int) new_setpoint);
504 }
505 } else
506 new_setpoint = setpoint;
507 }
508
509 else
510 new_setpoint = setpoint;
511
512 return new_setpoint;
513}
514
515
516static void pm121_create_sys_fans(int loop_id)
517{
518 struct pm121_sys_param *param = NULL;
519 struct wf_pid_param pid_param;
520 struct wf_control *control = NULL;
521 int i;
522
523
524 for (i = 0; i < PM121_NUM_CONFIGS; i++) {
525 if (pm121_sys_all_params[loop_id][i].model_id == pm121_mach_model) {
526 param = &(pm121_sys_all_params[loop_id][i]);
527 break;
528 }
529 }
530
531
532 if (param == NULL) {
533 printk(KERN_WARNING "pm121: %s fan config not found "
534 " for this machine model\n",
535 loop_names[loop_id]);
536 goto fail;
537 }
538
539 control = controls[param->control_id];
540
541
542 pm121_sys_state[loop_id] = kmalloc(sizeof(struct pm121_sys_state),
543 GFP_KERNEL);
544 if (pm121_sys_state[loop_id] == NULL) {
545 printk(KERN_WARNING "pm121: Memory allocation error\n");
546 goto fail;
547 }
548 pm121_sys_state[loop_id]->ticks = 1;
549
550
551 pid_param.gd = PM121_SYS_GD;
552 pid_param.gp = param->gp;
553 pid_param.gr = PM121_SYS_GR;
554 pid_param.interval = PM121_SYS_INTERVAL;
555 pid_param.history_len = PM121_SYS_HISTORY_SIZE;
556 pid_param.itarget = param->itarget;
557 pid_param.min = control->ops->get_min(control);
558 pid_param.max = control->ops->get_max(control);
559
560 wf_pid_init(&pm121_sys_state[loop_id]->pid, &pid_param);
561
562 pr_debug("pm121: %s Fan control loop initialized.\n"
563 " itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
564 loop_names[loop_id], FIX32TOPRINT(pid_param.itarget),
565 pid_param.min, pid_param.max);
566 return;
567
568 fail:
569
570
571 printk(KERN_WARNING "pm121: failed to set up %s loop "
572 "setting \"%s\" to max speed.\n",
573 loop_names[loop_id], control->name);
574
575 if (control)
576 wf_control_set_max(control);
577}
578
579static void pm121_sys_fans_tick(int loop_id)
580{
581 struct pm121_sys_param *param;
582 struct pm121_sys_state *st;
583 struct wf_sensor *sensor;
584 struct wf_control *control;
585 s32 temp, new_setpoint;
586 int rc;
587
588 param = &(pm121_sys_all_params[loop_id][pm121_mach_model-2]);
589 st = pm121_sys_state[loop_id];
590 sensor = *(param->sensor);
591 control = controls[param->control_id];
592
593 if (--st->ticks != 0) {
594 if (pm121_readjust)
595 goto readjust;
596 return;
597 }
598 st->ticks = PM121_SYS_INTERVAL;
599
600 rc = sensor->ops->get_value(sensor, &temp);
601 if (rc) {
602 printk(KERN_WARNING "windfarm: %s sensor error %d\n",
603 sensor->name, rc);
604 pm121_failure_state |= FAILURE_SENSOR;
605 return;
606 }
607
608 pr_debug("pm121: %s Fan tick ! %s: %d.%03d\n",
609 loop_names[loop_id], sensor->name,
610 FIX32TOPRINT(temp));
611
612 new_setpoint = wf_pid_run(&st->pid, temp);
613
614
615 new_setpoint = pm121_correct(new_setpoint,
616 param->control_id,
617 st->pid.param.min);
618
619 new_setpoint = pm121_connect(param->control_id, new_setpoint);
620
621 if (new_setpoint == st->setpoint)
622 return;
623 st->setpoint = new_setpoint;
624 pr_debug("pm121: %s corrected setpoint: %d RPM\n",
625 control->name, (int)new_setpoint);
626 readjust:
627 if (control && pm121_failure_state == 0) {
628 rc = control->ops->set_value(control, st->setpoint);
629 if (rc) {
630 printk(KERN_WARNING "windfarm: %s fan error %d\n",
631 control->name, rc);
632 pm121_failure_state |= FAILURE_FAN;
633 }
634 }
635}
636
637
638
639static void pm121_create_cpu_fans(void)
640{
641 struct wf_cpu_pid_param pid_param;
642 const struct smu_sdbp_header *hdr;
643 struct smu_sdbp_cpupiddata *piddata;
644 struct smu_sdbp_fvt *fvt;
645 struct wf_control *fan_cpu;
646 s32 tmax, tdelta, maxpow, powadj;
647
648 fan_cpu = controls[FAN_CPU];
649
650
651 hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL);
652 if (hdr == 0) {
653 printk(KERN_WARNING "pm121: CPU PID fan config not found.\n");
654 goto fail;
655 }
656 piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
657
658
659
660
661 hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);
662 if (hdr) {
663 fvt = (struct smu_sdbp_fvt *)&hdr[1];
664 tmax = ((s32)fvt->maxtemp) << 16;
665 } else
666 tmax = 0x5e0000;
667
668
669 pm121_cpu_state = kmalloc(sizeof(struct pm121_cpu_state),
670 GFP_KERNEL);
671 if (pm121_cpu_state == NULL)
672 goto fail;
673 pm121_cpu_state->ticks = 1;
674
675
676 pid_param.interval = PM121_CPU_INTERVAL;
677 pid_param.history_len = piddata->history_len;
678 if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) {
679 printk(KERN_WARNING "pm121: History size overflow on "
680 "CPU control loop (%d)\n", piddata->history_len);
681 pid_param.history_len = WF_CPU_PID_MAX_HISTORY;
682 }
683 pid_param.gd = piddata->gd;
684 pid_param.gp = piddata->gp;
685 pid_param.gr = piddata->gr / pid_param.history_len;
686
687 tdelta = ((s32)piddata->target_temp_delta) << 16;
688 maxpow = ((s32)piddata->max_power) << 16;
689 powadj = ((s32)piddata->power_adj) << 16;
690
691 pid_param.tmax = tmax;
692 pid_param.ttarget = tmax - tdelta;
693 pid_param.pmaxadj = maxpow - powadj;
694
695 pid_param.min = fan_cpu->ops->get_min(fan_cpu);
696 pid_param.max = fan_cpu->ops->get_max(fan_cpu);
697
698 wf_cpu_pid_init(&pm121_cpu_state->pid, &pid_param);
699
700 pr_debug("pm121: CPU Fan control initialized.\n");
701 pr_debug(" ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM,\n",
702 FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax),
703 pid_param.min, pid_param.max);
704
705 return;
706
707 fail:
708 printk(KERN_WARNING "pm121: CPU fan config not found, max fan speed\n");
709
710 if (controls[CPUFREQ])
711 wf_control_set_max(controls[CPUFREQ]);
712 if (fan_cpu)
713 wf_control_set_max(fan_cpu);
714}
715
716
717static void pm121_cpu_fans_tick(struct pm121_cpu_state *st)
718{
719 s32 new_setpoint, temp, power;
720 struct wf_control *fan_cpu = NULL;
721 int rc;
722
723 if (--st->ticks != 0) {
724 if (pm121_readjust)
725 goto readjust;
726 return;
727 }
728 st->ticks = PM121_CPU_INTERVAL;
729
730 fan_cpu = controls[FAN_CPU];
731
732 rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp);
733 if (rc) {
734 printk(KERN_WARNING "pm121: CPU temp sensor error %d\n",
735 rc);
736 pm121_failure_state |= FAILURE_SENSOR;
737 return;
738 }
739
740 rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power);
741 if (rc) {
742 printk(KERN_WARNING "pm121: CPU power sensor error %d\n",
743 rc);
744 pm121_failure_state |= FAILURE_SENSOR;
745 return;
746 }
747
748 pr_debug("pm121: CPU Fans tick ! CPU temp: %d.%03d°C, power: %d.%03d\n",
749 FIX32TOPRINT(temp), FIX32TOPRINT(power));
750
751 if (temp > st->pid.param.tmax)
752 pm121_failure_state |= FAILURE_OVERTEMP;
753
754 new_setpoint = wf_cpu_pid_run(&st->pid, power, temp);
755
756
757 new_setpoint = pm121_correct(new_setpoint,
758 FAN_CPU,
759 st->pid.param.min);
760
761
762 new_setpoint = pm121_connect(FAN_CPU, new_setpoint);
763
764 if (st->setpoint == new_setpoint)
765 return;
766 st->setpoint = new_setpoint;
767 pr_debug("pm121: CPU corrected setpoint: %d RPM\n", (int)new_setpoint);
768
769 readjust:
770 if (fan_cpu && pm121_failure_state == 0) {
771 rc = fan_cpu->ops->set_value(fan_cpu, st->setpoint);
772 if (rc) {
773 printk(KERN_WARNING "pm121: %s fan error %d\n",
774 fan_cpu->name, rc);
775 pm121_failure_state |= FAILURE_FAN;
776 }
777 }
778}
779
780
781
782
783
784
785static void pm121_tick(void)
786{
787 unsigned int last_failure = pm121_failure_state;
788 unsigned int new_failure;
789 s32 total_power;
790 int i;
791
792 if (!pm121_started) {
793 pr_debug("pm121: creating control loops !\n");
794 for (i = 0; i < N_LOOPS; i++)
795 pm121_create_sys_fans(i);
796
797 pm121_create_cpu_fans();
798 pm121_started = 1;
799 }
800
801
802 if (pm121_skipping && --pm121_skipping)
803 return;
804
805
806 total_power = 0;
807 for (i = 0; i < pm121_cpu_state->pid.param.history_len; i++)
808 total_power += pm121_cpu_state->pid.powers[i];
809
810 average_power = total_power / pm121_cpu_state->pid.param.history_len;
811
812
813 pm121_failure_state = 0;
814 for (i = 0 ; i < N_LOOPS; i++) {
815 if (pm121_sys_state[i])
816 pm121_sys_fans_tick(i);
817 }
818
819 if (pm121_cpu_state)
820 pm121_cpu_fans_tick(pm121_cpu_state);
821
822 pm121_readjust = 0;
823 new_failure = pm121_failure_state & ~last_failure;
824
825
826
827
828 if (pm121_failure_state && !last_failure) {
829 for (i = 0; i < N_CONTROLS; i++) {
830 if (controls[i])
831 wf_control_set_max(controls[i]);
832 }
833 }
834
835
836
837
838 if (!pm121_failure_state && last_failure) {
839 if (controls[CPUFREQ])
840 wf_control_set_min(controls[CPUFREQ]);
841 pm121_readjust = 1;
842 }
843
844
845
846
847 if (new_failure & FAILURE_OVERTEMP) {
848 wf_set_overtemp();
849 pm121_skipping = 2;
850 }
851
852
853
854
855
856
857
858 if (new_failure == 0 && last_failure & FAILURE_OVERTEMP)
859 wf_clear_overtemp();
860}
861
862
863static struct wf_control* pm121_register_control(struct wf_control *ct,
864 const char *match,
865 unsigned int id)
866{
867 if (controls[id] == NULL && !strcmp(ct->name, match)) {
868 if (wf_get_control(ct) == 0)
869 controls[id] = ct;
870 }
871 return controls[id];
872}
873
874static void pm121_new_control(struct wf_control *ct)
875{
876 int all = 1;
877
878 if (pm121_all_controls_ok)
879 return;
880
881 all = pm121_register_control(ct, "optical-drive-fan", FAN_OD) && all;
882 all = pm121_register_control(ct, "hard-drive-fan", FAN_HD) && all;
883 all = pm121_register_control(ct, "cpu-fan", FAN_CPU) && all;
884 all = pm121_register_control(ct, "cpufreq-clamp", CPUFREQ) && all;
885
886 if (all)
887 pm121_all_controls_ok = 1;
888}
889
890
891
892
893static struct wf_sensor* pm121_register_sensor(struct wf_sensor *sensor,
894 const char *match,
895 struct wf_sensor **var)
896{
897 if (*var == NULL && !strcmp(sensor->name, match)) {
898 if (wf_get_sensor(sensor) == 0)
899 *var = sensor;
900 }
901 return *var;
902}
903
904static void pm121_new_sensor(struct wf_sensor *sr)
905{
906 int all = 1;
907
908 if (pm121_all_sensors_ok)
909 return;
910
911 all = pm121_register_sensor(sr, "cpu-temp",
912 &sensor_cpu_temp) && all;
913 all = pm121_register_sensor(sr, "cpu-current",
914 &sensor_cpu_current) && all;
915 all = pm121_register_sensor(sr, "cpu-voltage",
916 &sensor_cpu_voltage) && all;
917 all = pm121_register_sensor(sr, "cpu-power",
918 &sensor_cpu_power) && all;
919 all = pm121_register_sensor(sr, "hard-drive-temp",
920 &sensor_hard_drive_temp) && all;
921 all = pm121_register_sensor(sr, "optical-drive-temp",
922 &sensor_optical_drive_temp) && all;
923 all = pm121_register_sensor(sr, "incoming-air-temp",
924 &sensor_incoming_air_temp) && all;
925 all = pm121_register_sensor(sr, "north-bridge-temp",
926 &sensor_north_bridge_temp) && all;
927 all = pm121_register_sensor(sr, "gpu-temp",
928 &sensor_gpu_temp) && all;
929
930 if (all)
931 pm121_all_sensors_ok = 1;
932}
933
934
935
936static int pm121_notify(struct notifier_block *self,
937 unsigned long event, void *data)
938{
939 switch (event) {
940 case WF_EVENT_NEW_CONTROL:
941 pr_debug("pm121: new control %s detected\n",
942 ((struct wf_control *)data)->name);
943 pm121_new_control(data);
944 break;
945 case WF_EVENT_NEW_SENSOR:
946 pr_debug("pm121: new sensor %s detected\n",
947 ((struct wf_sensor *)data)->name);
948 pm121_new_sensor(data);
949 break;
950 case WF_EVENT_TICK:
951 if (pm121_all_controls_ok && pm121_all_sensors_ok)
952 pm121_tick();
953 break;
954 }
955
956 return 0;
957}
958
959static struct notifier_block pm121_events = {
960 .notifier_call = pm121_notify,
961};
962
963static int pm121_init_pm(void)
964{
965 const struct smu_sdbp_header *hdr;
966
967 hdr = smu_get_sdb_partition(SMU_SDB_SENSORTREE_ID, NULL);
968 if (hdr != 0) {
969 struct smu_sdbp_sensortree *st =
970 (struct smu_sdbp_sensortree *)&hdr[1];
971 pm121_mach_model = st->model_id;
972 }
973
974 pm121_connection = &pm121_connections[pm121_mach_model - 2];
975
976 printk(KERN_INFO "pm121: Initializing for iMac G5 iSight model ID %d\n",
977 pm121_mach_model);
978
979 return 0;
980}
981
982
983static int pm121_probe(struct platform_device *ddev)
984{
985 wf_register_client(&pm121_events);
986
987 return 0;
988}
989
990static int pm121_remove(struct platform_device *ddev)
991{
992 wf_unregister_client(&pm121_events);
993 return 0;
994}
995
996static struct platform_driver pm121_driver = {
997 .probe = pm121_probe,
998 .remove = pm121_remove,
999 .driver = {
1000 .name = "windfarm",
1001 .bus = &platform_bus_type,
1002 },
1003};
1004
1005
1006static int __init pm121_init(void)
1007{
1008 int rc = -ENODEV;
1009
1010 if (of_machine_is_compatible("PowerMac12,1"))
1011 rc = pm121_init_pm();
1012
1013 if (rc == 0) {
1014 request_module("windfarm_smu_controls");
1015 request_module("windfarm_smu_sensors");
1016 request_module("windfarm_smu_sat");
1017 request_module("windfarm_lm75_sensor");
1018 request_module("windfarm_max6690_sensor");
1019 request_module("windfarm_cpufreq_clamp");
1020 platform_driver_register(&pm121_driver);
1021 }
1022
1023 return rc;
1024}
1025
1026static void __exit pm121_exit(void)
1027{
1028
1029 platform_driver_unregister(&pm121_driver);
1030}
1031
1032
1033module_init(pm121_init);
1034module_exit(pm121_exit);
1035
1036MODULE_AUTHOR("Étienne Bersac <bersace@gmail.com>");
1037MODULE_DESCRIPTION("Thermal control logic for iMac G5 (iSight)");
1038MODULE_LICENSE("GPL");
1039
1040