0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <linux/types.h>
0010 #include <linux/errno.h>
0011 #include <linux/kernel.h>
0012 #include <linux/device.h>
0013 #include <linux/platform_device.h>
0014 #include <linux/reboot.h>
0015 #include <linux/of.h>
0016 #include <linux/slab.h>
0017
0018 #include <asm/smu.h>
0019
0020 #include "windfarm.h"
0021 #include "windfarm_pid.h"
0022
0023 #define VERSION "0.2"
0024
0025 #define DEBUG
0026 #undef LOTSA_DEBUG
0027
0028 #ifdef DEBUG
0029 #define DBG(args...) printk(args)
0030 #else
0031 #define DBG(args...) do { } while(0)
0032 #endif
0033
0034 #ifdef LOTSA_DEBUG
0035 #define DBG_LOTS(args...) printk(args)
0036 #else
0037 #define DBG_LOTS(args...) do { } while(0)
0038 #endif
0039
0040
0041
0042
0043 #undef HACKED_OVERTEMP
0044
0045
0046 #define NR_CHIPS 2
0047 #define NR_CORES 4
0048 #define NR_CPU_FANS 3 * NR_CHIPS
0049
0050
0051 static struct wf_sensor *sens_cpu_temp[NR_CORES];
0052 static struct wf_sensor *sens_cpu_power[NR_CORES];
0053 static struct wf_sensor *hd_temp;
0054 static struct wf_sensor *slots_power;
0055 static struct wf_sensor *u4_temp;
0056
0057 static struct wf_control *cpu_fans[NR_CPU_FANS];
0058 static char *cpu_fan_names[NR_CPU_FANS] = {
0059 "cpu-rear-fan-0",
0060 "cpu-rear-fan-1",
0061 "cpu-front-fan-0",
0062 "cpu-front-fan-1",
0063 "cpu-pump-0",
0064 "cpu-pump-1",
0065 };
0066 static struct wf_control *cpufreq_clamp;
0067
0068
0069 #define CPU_FANS_REQD (NR_CPU_FANS - 2)
0070 #define FIRST_PUMP 4
0071 #define LAST_PUMP 5
0072
0073
0074 #define CPU_TEMP_HIST_SIZE 180
0075
0076
0077 static int cpu_fan_scale[NR_CPU_FANS] = {
0078 100,
0079 100,
0080 97,
0081 97,
0082 100,
0083 100,
0084 };
0085
0086 static struct wf_control *backside_fan;
0087 static struct wf_control *slots_fan;
0088 static struct wf_control *drive_bay_fan;
0089
0090
0091 static struct wf_cpu_pid_state cpu_pid[NR_CORES];
0092 static u32 cpu_thist[CPU_TEMP_HIST_SIZE];
0093 static int cpu_thist_pt;
0094 static s64 cpu_thist_total;
0095 static s32 cpu_all_tmax = 100 << 16;
0096 static int cpu_last_target;
0097 static struct wf_pid_state backside_pid;
0098 static int backside_tick;
0099 static struct wf_pid_state slots_pid;
0100 static bool slots_started;
0101 static struct wf_pid_state drive_bay_pid;
0102 static int drive_bay_tick;
0103
0104 static int nr_cores;
0105 static int have_all_controls;
0106 static int have_all_sensors;
0107 static bool started;
0108
0109 static int failure_state;
0110 #define FAILURE_SENSOR 1
0111 #define FAILURE_FAN 2
0112 #define FAILURE_PERM 4
0113 #define FAILURE_LOW_OVERTEMP 8
0114 #define FAILURE_HIGH_OVERTEMP 16
0115
0116
0117 #define LOW_OVER_AVERAGE 0
0118 #define LOW_OVER_IMMEDIATE (10 << 16)
0119 #define LOW_OVER_CLEAR ((-10) << 16)
0120 #define HIGH_OVER_IMMEDIATE (14 << 16)
0121 #define HIGH_OVER_AVERAGE (10 << 16)
0122 #define HIGH_OVER_IMMEDIATE (14 << 16)
0123
0124
0125
0126 static int create_cpu_loop(int cpu)
0127 {
0128 int chip = cpu / 2;
0129 int core = cpu & 1;
0130 struct smu_sdbp_header *hdr;
0131 struct smu_sdbp_cpupiddata *piddata;
0132 struct wf_cpu_pid_param pid;
0133 struct wf_control *main_fan = cpu_fans[0];
0134 s32 tmax;
0135 int fmin;
0136
0137
0138 hdr = smu_sat_get_sdb_partition(chip, 0xC4 + core, NULL);
0139 if (hdr) {
0140 struct smu_sdbp_fvt *fvt = (struct smu_sdbp_fvt *)&hdr[1];
0141 tmax = fvt->maxtemp << 16;
0142 } else
0143 tmax = 95 << 16;
0144
0145
0146 if (tmax < cpu_all_tmax)
0147 cpu_all_tmax = tmax;
0148
0149 kfree(hdr);
0150
0151
0152 hdr = smu_sat_get_sdb_partition(chip, 0xC8 + core, NULL);
0153 if (hdr == NULL) {
0154 printk(KERN_WARNING"windfarm: can't get CPU PID fan config\n");
0155 return -EINVAL;
0156 }
0157 piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
0158
0159
0160
0161
0162
0163
0164 fmin = (nr_cores > 2) ? 750 : 515;
0165
0166
0167 pid.interval = 1;
0168 pid.history_len = piddata->history_len;
0169 pid.gd = piddata->gd;
0170 pid.gp = piddata->gp;
0171 pid.gr = piddata->gr / piddata->history_len;
0172 pid.pmaxadj = (piddata->max_power << 16) - (piddata->power_adj << 8);
0173 pid.ttarget = tmax - (piddata->target_temp_delta << 16);
0174 pid.tmax = tmax;
0175 pid.min = main_fan->ops->get_min(main_fan);
0176 pid.max = main_fan->ops->get_max(main_fan);
0177 if (pid.min < fmin)
0178 pid.min = fmin;
0179
0180 wf_cpu_pid_init(&cpu_pid[cpu], &pid);
0181
0182 kfree(hdr);
0183
0184 return 0;
0185 }
0186
0187 static void cpu_max_all_fans(void)
0188 {
0189 int i;
0190
0191
0192
0193
0194
0195 if (cpufreq_clamp)
0196 wf_control_set_max(cpufreq_clamp);
0197 for (i = 0; i < NR_CPU_FANS; ++i)
0198 if (cpu_fans[i])
0199 wf_control_set_max(cpu_fans[i]);
0200 }
0201
0202 static int cpu_check_overtemp(s32 temp)
0203 {
0204 int new_state = 0;
0205 s32 t_avg, t_old;
0206
0207
0208 if (temp >= (cpu_all_tmax + LOW_OVER_IMMEDIATE)) {
0209 new_state |= FAILURE_LOW_OVERTEMP;
0210 if ((failure_state & FAILURE_LOW_OVERTEMP) == 0)
0211 printk(KERN_ERR "windfarm: Overtemp due to immediate CPU"
0212 " temperature !\n");
0213 }
0214 if (temp >= (cpu_all_tmax + HIGH_OVER_IMMEDIATE)) {
0215 new_state |= FAILURE_HIGH_OVERTEMP;
0216 if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0)
0217 printk(KERN_ERR "windfarm: Critical overtemp due to"
0218 " immediate CPU temperature !\n");
0219 }
0220
0221
0222
0223
0224 t_old = cpu_thist[cpu_thist_pt];
0225 cpu_thist[cpu_thist_pt] = temp;
0226 cpu_thist_pt = (cpu_thist_pt + 1) % CPU_TEMP_HIST_SIZE;
0227 cpu_thist_total -= t_old;
0228 cpu_thist_total += temp;
0229 t_avg = cpu_thist_total / CPU_TEMP_HIST_SIZE;
0230
0231 DBG_LOTS("t_avg = %d.%03d (out: %d.%03d, in: %d.%03d)\n",
0232 FIX32TOPRINT(t_avg), FIX32TOPRINT(t_old), FIX32TOPRINT(temp));
0233
0234
0235 if (t_avg >= (cpu_all_tmax + LOW_OVER_AVERAGE)) {
0236 new_state |= FAILURE_LOW_OVERTEMP;
0237 if ((failure_state & FAILURE_LOW_OVERTEMP) == 0)
0238 printk(KERN_ERR "windfarm: Overtemp due to average CPU"
0239 " temperature !\n");
0240 }
0241 if (t_avg >= (cpu_all_tmax + HIGH_OVER_AVERAGE)) {
0242 new_state |= FAILURE_HIGH_OVERTEMP;
0243 if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0)
0244 printk(KERN_ERR "windfarm: Critical overtemp due to"
0245 " average CPU temperature !\n");
0246 }
0247
0248
0249
0250
0251
0252 if (new_state) {
0253
0254 if (new_state & FAILURE_HIGH_OVERTEMP)
0255 machine_power_off();
0256 if ((failure_state & new_state) != new_state)
0257 cpu_max_all_fans();
0258 failure_state |= new_state;
0259 } else if ((failure_state & FAILURE_LOW_OVERTEMP) &&
0260 (temp < (cpu_all_tmax + LOW_OVER_CLEAR))) {
0261 printk(KERN_ERR "windfarm: Overtemp condition cleared !\n");
0262 failure_state &= ~FAILURE_LOW_OVERTEMP;
0263 }
0264
0265 return failure_state & (FAILURE_LOW_OVERTEMP | FAILURE_HIGH_OVERTEMP);
0266 }
0267
0268 static void cpu_fans_tick(void)
0269 {
0270 int err, cpu;
0271 s32 greatest_delta = 0;
0272 s32 temp, power, t_max = 0;
0273 int i, t, target = 0;
0274 struct wf_sensor *sr;
0275 struct wf_control *ct;
0276 struct wf_cpu_pid_state *sp;
0277
0278 DBG_LOTS(KERN_DEBUG);
0279 for (cpu = 0; cpu < nr_cores; ++cpu) {
0280
0281 sr = sens_cpu_temp[cpu];
0282 err = sr->ops->get_value(sr, &temp);
0283 if (err) {
0284 DBG("\n");
0285 printk(KERN_WARNING "windfarm: CPU %d temperature "
0286 "sensor error %d\n", cpu, err);
0287 failure_state |= FAILURE_SENSOR;
0288 cpu_max_all_fans();
0289 return;
0290 }
0291
0292
0293 t_max = max(t_max, temp);
0294
0295
0296 sr = sens_cpu_power[cpu];
0297 err = sr->ops->get_value(sr, &power);
0298 if (err) {
0299 DBG("\n");
0300 printk(KERN_WARNING "windfarm: CPU %d power "
0301 "sensor error %d\n", cpu, err);
0302 failure_state |= FAILURE_SENSOR;
0303 cpu_max_all_fans();
0304 return;
0305 }
0306
0307
0308 sp = &cpu_pid[cpu];
0309 t = wf_cpu_pid_run(sp, power, temp);
0310
0311 if (cpu == 0 || sp->last_delta > greatest_delta) {
0312 greatest_delta = sp->last_delta;
0313 target = t;
0314 }
0315 DBG_LOTS("[%d] P=%d.%.3d T=%d.%.3d ",
0316 cpu, FIX32TOPRINT(power), FIX32TOPRINT(temp));
0317 }
0318 DBG_LOTS("fans = %d, t_max = %d.%03d\n", target, FIX32TOPRINT(t_max));
0319
0320
0321 if (target < (cpu_last_target - 20))
0322 target = cpu_last_target - 20;
0323 cpu_last_target = target;
0324 for (cpu = 0; cpu < nr_cores; ++cpu)
0325 cpu_pid[cpu].target = target;
0326
0327
0328 if (cpu_check_overtemp(t_max))
0329 return;
0330
0331
0332 for (i = 0; i < NR_CPU_FANS; ++i) {
0333 ct = cpu_fans[i];
0334 if (ct == NULL)
0335 continue;
0336 err = ct->ops->set_value(ct, target * cpu_fan_scale[i] / 100);
0337 if (err) {
0338 printk(KERN_WARNING "windfarm: fan %s reports "
0339 "error %d\n", ct->name, err);
0340 failure_state |= FAILURE_FAN;
0341 break;
0342 }
0343 }
0344 }
0345
0346
0347 static struct wf_pid_param backside_param = {
0348 .interval = 5,
0349 .history_len = 2,
0350 .gd = 48 << 20,
0351 .gp = 5 << 20,
0352 .gr = 0,
0353 .itarget = 64 << 16,
0354 .additive = 1,
0355 };
0356
0357 static void backside_fan_tick(void)
0358 {
0359 s32 temp;
0360 int speed;
0361 int err;
0362
0363 if (!backside_fan || !u4_temp)
0364 return;
0365 if (!backside_tick) {
0366
0367 printk(KERN_INFO "windfarm: Backside control loop started.\n");
0368 backside_param.min = backside_fan->ops->get_min(backside_fan);
0369 backside_param.max = backside_fan->ops->get_max(backside_fan);
0370 wf_pid_init(&backside_pid, &backside_param);
0371 backside_tick = 1;
0372 }
0373 if (--backside_tick > 0)
0374 return;
0375 backside_tick = backside_pid.param.interval;
0376
0377 err = u4_temp->ops->get_value(u4_temp, &temp);
0378 if (err) {
0379 printk(KERN_WARNING "windfarm: U4 temp sensor error %d\n",
0380 err);
0381 failure_state |= FAILURE_SENSOR;
0382 wf_control_set_max(backside_fan);
0383 return;
0384 }
0385 speed = wf_pid_run(&backside_pid, temp);
0386 DBG_LOTS("backside PID temp=%d.%.3d speed=%d\n",
0387 FIX32TOPRINT(temp), speed);
0388
0389 err = backside_fan->ops->set_value(backside_fan, speed);
0390 if (err) {
0391 printk(KERN_WARNING "windfarm: backside fan error %d\n", err);
0392 failure_state |= FAILURE_FAN;
0393 }
0394 }
0395
0396
0397 static struct wf_pid_param drive_bay_prm = {
0398 .interval = 5,
0399 .history_len = 2,
0400 .gd = 30 << 20,
0401 .gp = 5 << 20,
0402 .gr = 0,
0403 .itarget = 40 << 16,
0404 .additive = 1,
0405 };
0406
0407 static void drive_bay_fan_tick(void)
0408 {
0409 s32 temp;
0410 int speed;
0411 int err;
0412
0413 if (!drive_bay_fan || !hd_temp)
0414 return;
0415 if (!drive_bay_tick) {
0416
0417 printk(KERN_INFO "windfarm: Drive bay control loop started.\n");
0418 drive_bay_prm.min = drive_bay_fan->ops->get_min(drive_bay_fan);
0419 drive_bay_prm.max = drive_bay_fan->ops->get_max(drive_bay_fan);
0420 wf_pid_init(&drive_bay_pid, &drive_bay_prm);
0421 drive_bay_tick = 1;
0422 }
0423 if (--drive_bay_tick > 0)
0424 return;
0425 drive_bay_tick = drive_bay_pid.param.interval;
0426
0427 err = hd_temp->ops->get_value(hd_temp, &temp);
0428 if (err) {
0429 printk(KERN_WARNING "windfarm: drive bay temp sensor "
0430 "error %d\n", err);
0431 failure_state |= FAILURE_SENSOR;
0432 wf_control_set_max(drive_bay_fan);
0433 return;
0434 }
0435 speed = wf_pid_run(&drive_bay_pid, temp);
0436 DBG_LOTS("drive_bay PID temp=%d.%.3d speed=%d\n",
0437 FIX32TOPRINT(temp), speed);
0438
0439 err = drive_bay_fan->ops->set_value(drive_bay_fan, speed);
0440 if (err) {
0441 printk(KERN_WARNING "windfarm: drive bay fan error %d\n", err);
0442 failure_state |= FAILURE_FAN;
0443 }
0444 }
0445
0446
0447
0448 static struct wf_pid_param slots_param = {
0449 .interval = 1,
0450 .history_len = 2,
0451 .gd = 0,
0452 .gp = 0,
0453 .gr = 0x1277952,
0454 .itarget = 0,
0455 .min = 1560,
0456 .max = 3510,
0457 };
0458
0459 static void slots_fan_tick(void)
0460 {
0461 s32 power;
0462 int speed;
0463 int err;
0464
0465 if (!slots_fan || !slots_power)
0466 return;
0467 if (!slots_started) {
0468
0469 printk(KERN_INFO "windfarm: Slots control loop started.\n");
0470 wf_pid_init(&slots_pid, &slots_param);
0471 slots_started = true;
0472 }
0473
0474 err = slots_power->ops->get_value(slots_power, &power);
0475 if (err) {
0476 printk(KERN_WARNING "windfarm: slots power sensor error %d\n",
0477 err);
0478 failure_state |= FAILURE_SENSOR;
0479 wf_control_set_max(slots_fan);
0480 return;
0481 }
0482 speed = wf_pid_run(&slots_pid, power);
0483 DBG_LOTS("slots PID power=%d.%.3d speed=%d\n",
0484 FIX32TOPRINT(power), speed);
0485
0486 err = slots_fan->ops->set_value(slots_fan, speed);
0487 if (err) {
0488 printk(KERN_WARNING "windfarm: slots fan error %d\n", err);
0489 failure_state |= FAILURE_FAN;
0490 }
0491 }
0492
0493 static void set_fail_state(void)
0494 {
0495 int i;
0496
0497 if (cpufreq_clamp)
0498 wf_control_set_max(cpufreq_clamp);
0499 for (i = 0; i < NR_CPU_FANS; ++i)
0500 if (cpu_fans[i])
0501 wf_control_set_max(cpu_fans[i]);
0502 if (backside_fan)
0503 wf_control_set_max(backside_fan);
0504 if (slots_fan)
0505 wf_control_set_max(slots_fan);
0506 if (drive_bay_fan)
0507 wf_control_set_max(drive_bay_fan);
0508 }
0509
0510 static void pm112_tick(void)
0511 {
0512 int i, last_failure;
0513
0514 if (!started) {
0515 started = true;
0516 printk(KERN_INFO "windfarm: CPUs control loops started.\n");
0517 for (i = 0; i < nr_cores; ++i) {
0518 if (create_cpu_loop(i) < 0) {
0519 failure_state = FAILURE_PERM;
0520 set_fail_state();
0521 break;
0522 }
0523 }
0524 DBG_LOTS("cpu_all_tmax=%d.%03d\n", FIX32TOPRINT(cpu_all_tmax));
0525
0526 #ifdef HACKED_OVERTEMP
0527 cpu_all_tmax = 60 << 16;
0528 #endif
0529 }
0530
0531
0532 if (failure_state & FAILURE_PERM)
0533 return;
0534
0535
0536
0537 last_failure = failure_state;
0538 failure_state &= FAILURE_LOW_OVERTEMP;
0539 cpu_fans_tick();
0540 backside_fan_tick();
0541 slots_fan_tick();
0542 drive_bay_fan_tick();
0543
0544 DBG_LOTS("last_failure: 0x%x, failure_state: %x\n",
0545 last_failure, failure_state);
0546
0547
0548 if (failure_state && last_failure == 0 && cpufreq_clamp)
0549 wf_control_set_max(cpufreq_clamp);
0550 if (failure_state == 0 && last_failure && cpufreq_clamp)
0551 wf_control_set_min(cpufreq_clamp);
0552
0553
0554
0555
0556 }
0557
0558 static void pm112_new_control(struct wf_control *ct)
0559 {
0560 int i, max_exhaust;
0561
0562 if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) {
0563 if (wf_get_control(ct) == 0)
0564 cpufreq_clamp = ct;
0565 }
0566
0567 for (i = 0; i < NR_CPU_FANS; ++i) {
0568 if (!strcmp(ct->name, cpu_fan_names[i])) {
0569 if (cpu_fans[i] == NULL && wf_get_control(ct) == 0)
0570 cpu_fans[i] = ct;
0571 break;
0572 }
0573 }
0574 if (i >= NR_CPU_FANS) {
0575
0576 if (!strcmp(ct->name, "backside-fan")) {
0577 if (backside_fan == NULL && wf_get_control(ct) == 0)
0578 backside_fan = ct;
0579 } else if (!strcmp(ct->name, "slots-fan")) {
0580 if (slots_fan == NULL && wf_get_control(ct) == 0)
0581 slots_fan = ct;
0582 } else if (!strcmp(ct->name, "drive-bay-fan")) {
0583 if (drive_bay_fan == NULL && wf_get_control(ct) == 0)
0584 drive_bay_fan = ct;
0585 }
0586 return;
0587 }
0588
0589 for (i = 0; i < CPU_FANS_REQD; ++i)
0590 if (cpu_fans[i] == NULL)
0591 return;
0592
0593
0594 max_exhaust = cpu_fans[0]->ops->get_max(cpu_fans[0]);
0595 for (i = FIRST_PUMP; i <= LAST_PUMP; ++i)
0596 if ((ct = cpu_fans[i]) != NULL)
0597 cpu_fan_scale[i] =
0598 ct->ops->get_max(ct) * 100 / max_exhaust;
0599
0600 have_all_controls = 1;
0601 }
0602
0603 static void pm112_new_sensor(struct wf_sensor *sr)
0604 {
0605 unsigned int i;
0606
0607 if (!strncmp(sr->name, "cpu-temp-", 9)) {
0608 i = sr->name[9] - '0';
0609 if (sr->name[10] == 0 && i < NR_CORES &&
0610 sens_cpu_temp[i] == NULL && wf_get_sensor(sr) == 0)
0611 sens_cpu_temp[i] = sr;
0612
0613 } else if (!strncmp(sr->name, "cpu-power-", 10)) {
0614 i = sr->name[10] - '0';
0615 if (sr->name[11] == 0 && i < NR_CORES &&
0616 sens_cpu_power[i] == NULL && wf_get_sensor(sr) == 0)
0617 sens_cpu_power[i] = sr;
0618 } else if (!strcmp(sr->name, "hd-temp")) {
0619 if (hd_temp == NULL && wf_get_sensor(sr) == 0)
0620 hd_temp = sr;
0621 } else if (!strcmp(sr->name, "slots-power")) {
0622 if (slots_power == NULL && wf_get_sensor(sr) == 0)
0623 slots_power = sr;
0624 } else if (!strcmp(sr->name, "backside-temp")) {
0625 if (u4_temp == NULL && wf_get_sensor(sr) == 0)
0626 u4_temp = sr;
0627 } else
0628 return;
0629
0630
0631 for (i = 0; i < nr_cores; ++i)
0632 if (sens_cpu_temp[i] == NULL || sens_cpu_power[i] == NULL)
0633 return;
0634
0635 have_all_sensors = 1;
0636 }
0637
0638 static int pm112_wf_notify(struct notifier_block *self,
0639 unsigned long event, void *data)
0640 {
0641 switch (event) {
0642 case WF_EVENT_NEW_SENSOR:
0643 pm112_new_sensor(data);
0644 break;
0645 case WF_EVENT_NEW_CONTROL:
0646 pm112_new_control(data);
0647 break;
0648 case WF_EVENT_TICK:
0649 if (have_all_controls && have_all_sensors)
0650 pm112_tick();
0651 }
0652 return 0;
0653 }
0654
0655 static struct notifier_block pm112_events = {
0656 .notifier_call = pm112_wf_notify,
0657 };
0658
0659 static int wf_pm112_probe(struct platform_device *dev)
0660 {
0661 wf_register_client(&pm112_events);
0662 return 0;
0663 }
0664
0665 static int wf_pm112_remove(struct platform_device *dev)
0666 {
0667 wf_unregister_client(&pm112_events);
0668
0669 return 0;
0670 }
0671
0672 static struct platform_driver wf_pm112_driver = {
0673 .probe = wf_pm112_probe,
0674 .remove = wf_pm112_remove,
0675 .driver = {
0676 .name = "windfarm",
0677 },
0678 };
0679
0680 static int __init wf_pm112_init(void)
0681 {
0682 struct device_node *cpu;
0683
0684 if (!of_machine_is_compatible("PowerMac11,2"))
0685 return -ENODEV;
0686
0687
0688 nr_cores = 0;
0689 for_each_node_by_type(cpu, "cpu")
0690 ++nr_cores;
0691
0692 printk(KERN_INFO "windfarm: initializing for dual-core desktop G5\n");
0693
0694 #ifdef MODULE
0695 request_module("windfarm_smu_controls");
0696 request_module("windfarm_smu_sensors");
0697 request_module("windfarm_smu_sat");
0698 request_module("windfarm_lm75_sensor");
0699 request_module("windfarm_max6690_sensor");
0700 request_module("windfarm_cpufreq_clamp");
0701
0702 #endif
0703
0704 platform_driver_register(&wf_pm112_driver);
0705 return 0;
0706 }
0707
0708 static void __exit wf_pm112_exit(void)
0709 {
0710 platform_driver_unregister(&wf_pm112_driver);
0711 }
0712
0713 module_init(wf_pm112_init);
0714 module_exit(wf_pm112_exit);
0715
0716 MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>");
0717 MODULE_DESCRIPTION("Thermal control for PowerMac11,2");
0718 MODULE_LICENSE("GPL");
0719 MODULE_ALIAS("platform:windfarm");