Path: blob/master/drivers/macintosh/windfarm_pm81.c
15109 views
/*1* Windfarm PowerMac thermal control. iMac G52*3* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.4* <[email protected]>5*6* Released under the term of the GNU GPL v2.7*8* The algorithm used is the PID control algorithm, used the same9* way the published Darwin code does, using the same values that10* are present in the Darwin 8.2 snapshot property lists (note however11* that none of the code has been re-used, it's a complete re-implementation12*13* The various control loops found in Darwin config file are:14*15* PowerMac8,1 and PowerMac8,216* ===========================17*18* System Fans control loop. Different based on models. In addition to the19* usual PID algorithm, the control loop gets 2 additional pairs of linear20* scaling factors (scale/offsets) expressed as 4.12 fixed point values21* signed offset, unsigned scale)22*23* The targets are modified such as:24* - the linked control (second control) gets the target value as-is25* (typically the drive fan)26* - the main control (first control) gets the target value scaled with27* the first pair of factors, and is then modified as below28* - the value of the target of the CPU Fan control loop is retrieved,29* scaled with the second pair of factors, and the max of that and30* the scaled target is applied to the main control.31*32* # model_id: 233* controls : system-fan, drive-bay-fan34* sensors : hd-temp35* PID params : G_d = 0x1540000036* G_p = 0x0020000037* G_r = 0x000002fd38* History = 2 entries39* Input target = 0x3a000040* Interval = 5s41* linear-factors : offset = 0xff38 scale = 0x0ccd42* offset = 0x0208 scale = 0x07ae43*44* # model_id: 345* controls : system-fan, drive-bay-fan46* sensors : hd-temp47* PID params : G_d = 0x08e0000048* G_p = 0x0056666649* G_r = 0x0000072b50* History = 2 entries51* Input target = 0x35000052* Interval = 5s53* linear-factors : offset = 0xff38 scale = 0x0ccd54* offset = 0x0000 scale = 0x000055*56* # model_id: 557* controls : system-fan58* sensors : hd-temp59* PID params : G_d = 0x1540000060* G_p = 0x0023333361* G_r = 0x000002fd62* History = 2 entries63* Input target = 0x3a000064* Interval = 5s65* linear-factors : offset = 0x0000 scale = 0x100066* offset = 0x0091 scale = 0x0bae67*68* CPU Fan control loop. The loop is identical for all models. it69* has an additional pair of scaling factor. This is used to scale the70* systems fan control loop target result (the one before it gets scaled71* by the System Fans control loop itself). Then, the max value of the72* calculated target value and system fan value is sent to the fans73*74* controls : cpu-fan75* sensors : cpu-temp cpu-power76* PID params : From SMU sdb partition77* linear-factors : offset = 0xfb50 scale = 0x100078*79* CPU Slew control loop. Not implemented. The cpufreq driver in linux is80* completely separate for now, though we could find a way to link it, either81* as a client reacting to overtemp notifications, or directling monitoring82* the CPU temperature83*84* WARNING ! The CPU control loop requires the CPU tmax for the current85* operating point. However, we currently are completely separated from86* the cpufreq driver and thus do not know what the current operating87* point is. Fortunately, we also do not have any hardware supporting anything88* but operating point 0 at the moment, thus we just peek that value directly89* from the SDB partition. If we ever end up with actually slewing the system90* clock and thus changing operating points, we'll have to find a way to91* communicate with the CPU freq driver;92*93*/9495#include <linux/types.h>96#include <linux/errno.h>97#include <linux/kernel.h>98#include <linux/delay.h>99#include <linux/slab.h>100#include <linux/init.h>101#include <linux/spinlock.h>102#include <linux/wait.h>103#include <linux/kmod.h>104#include <linux/device.h>105#include <linux/platform_device.h>106#include <asm/prom.h>107#include <asm/machdep.h>108#include <asm/io.h>109#include <asm/system.h>110#include <asm/sections.h>111#include <asm/smu.h>112113#include "windfarm.h"114#include "windfarm_pid.h"115116#define VERSION "0.4"117118#undef DEBUG119120#ifdef DEBUG121#define DBG(args...) printk(args)122#else123#define DBG(args...) do { } while(0)124#endif125126/* define this to force CPU overtemp to 74 degree, useful for testing127* the overtemp code128*/129#undef HACKED_OVERTEMP130131static int wf_smu_mach_model; /* machine model id */132133/* Controls & sensors */134static struct wf_sensor *sensor_cpu_power;135static struct wf_sensor *sensor_cpu_temp;136static struct wf_sensor *sensor_hd_temp;137static struct wf_control *fan_cpu_main;138static struct wf_control *fan_hd;139static struct wf_control *fan_system;140static struct wf_control *cpufreq_clamp;141142/* Set to kick the control loop into life */143static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started;144145/* Failure handling.. could be nicer */146#define FAILURE_FAN 0x01147#define FAILURE_SENSOR 0x02148#define FAILURE_OVERTEMP 0x04149150static unsigned int wf_smu_failure_state;151static int wf_smu_readjust, wf_smu_skipping;152153/*154* ****** System Fans Control Loop ******155*156*/157158/* Parameters for the System Fans control loop. Parameters159* not in this table such as interval, history size, ...160* are common to all versions and thus hard coded for now.161*/162struct wf_smu_sys_fans_param {163int model_id;164s32 itarget;165s32 gd, gp, gr;166167s16 offset0;168u16 scale0;169s16 offset1;170u16 scale1;171};172173#define WF_SMU_SYS_FANS_INTERVAL 5174#define WF_SMU_SYS_FANS_HISTORY_SIZE 2175176/* State data used by the system fans control loop177*/178struct wf_smu_sys_fans_state {179int ticks;180s32 sys_setpoint;181s32 hd_setpoint;182s16 offset0;183u16 scale0;184s16 offset1;185u16 scale1;186struct wf_pid_state pid;187};188189/*190* Configs for SMU System Fan control loop191*/192static struct wf_smu_sys_fans_param wf_smu_sys_all_params[] = {193/* Model ID 2 */194{195.model_id = 2,196.itarget = 0x3a0000,197.gd = 0x15400000,198.gp = 0x00200000,199.gr = 0x000002fd,200.offset0 = 0xff38,201.scale0 = 0x0ccd,202.offset1 = 0x0208,203.scale1 = 0x07ae,204},205/* Model ID 3 */206{207.model_id = 3,208.itarget = 0x350000,209.gd = 0x08e00000,210.gp = 0x00566666,211.gr = 0x0000072b,212.offset0 = 0xff38,213.scale0 = 0x0ccd,214.offset1 = 0x0000,215.scale1 = 0x0000,216},217/* Model ID 5 */218{219.model_id = 5,220.itarget = 0x3a0000,221.gd = 0x15400000,222.gp = 0x00233333,223.gr = 0x000002fd,224.offset0 = 0x0000,225.scale0 = 0x1000,226.offset1 = 0x0091,227.scale1 = 0x0bae,228},229};230#define WF_SMU_SYS_FANS_NUM_CONFIGS ARRAY_SIZE(wf_smu_sys_all_params)231232static struct wf_smu_sys_fans_state *wf_smu_sys_fans;233234/*235* ****** CPU Fans Control Loop ******236*237*/238239240#define WF_SMU_CPU_FANS_INTERVAL 1241#define WF_SMU_CPU_FANS_MAX_HISTORY 16242#define WF_SMU_CPU_FANS_SIBLING_SCALE 0x00001000243#define WF_SMU_CPU_FANS_SIBLING_OFFSET 0xfffffb50244245/* State data used by the cpu fans control loop246*/247struct wf_smu_cpu_fans_state {248int ticks;249s32 cpu_setpoint;250s32 scale;251s32 offset;252struct wf_cpu_pid_state pid;253};254255static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans;256257258259/*260* ***** Implementation *****261*262*/263264static void wf_smu_create_sys_fans(void)265{266struct wf_smu_sys_fans_param *param = NULL;267struct wf_pid_param pid_param;268int i;269270/* First, locate the params for this model */271for (i = 0; i < WF_SMU_SYS_FANS_NUM_CONFIGS; i++)272if (wf_smu_sys_all_params[i].model_id == wf_smu_mach_model) {273param = &wf_smu_sys_all_params[i];274break;275}276277/* No params found, put fans to max */278if (param == NULL) {279printk(KERN_WARNING "windfarm: System fan config not found "280"for this machine model, max fan speed\n");281goto fail;282}283284/* Alloc & initialize state */285wf_smu_sys_fans = kmalloc(sizeof(struct wf_smu_sys_fans_state),286GFP_KERNEL);287if (wf_smu_sys_fans == NULL) {288printk(KERN_WARNING "windfarm: Memory allocation error"289" max fan speed\n");290goto fail;291}292wf_smu_sys_fans->ticks = 1;293wf_smu_sys_fans->scale0 = param->scale0;294wf_smu_sys_fans->offset0 = param->offset0;295wf_smu_sys_fans->scale1 = param->scale1;296wf_smu_sys_fans->offset1 = param->offset1;297298/* Fill PID params */299pid_param.gd = param->gd;300pid_param.gp = param->gp;301pid_param.gr = param->gr;302pid_param.interval = WF_SMU_SYS_FANS_INTERVAL;303pid_param.history_len = WF_SMU_SYS_FANS_HISTORY_SIZE;304pid_param.itarget = param->itarget;305pid_param.min = fan_system->ops->get_min(fan_system);306pid_param.max = fan_system->ops->get_max(fan_system);307if (fan_hd) {308pid_param.min =309max(pid_param.min,fan_hd->ops->get_min(fan_hd));310pid_param.max =311min(pid_param.max,fan_hd->ops->get_max(fan_hd));312}313wf_pid_init(&wf_smu_sys_fans->pid, &pid_param);314315DBG("wf: System Fan control initialized.\n");316DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n",317FIX32TOPRINT(pid_param.itarget), pid_param.min, pid_param.max);318return;319320fail:321322if (fan_system)323wf_control_set_max(fan_system);324if (fan_hd)325wf_control_set_max(fan_hd);326}327328static void wf_smu_sys_fans_tick(struct wf_smu_sys_fans_state *st)329{330s32 new_setpoint, temp, scaled, cputarget;331int rc;332333if (--st->ticks != 0) {334if (wf_smu_readjust)335goto readjust;336return;337}338st->ticks = WF_SMU_SYS_FANS_INTERVAL;339340rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp);341if (rc) {342printk(KERN_WARNING "windfarm: HD temp sensor error %d\n",343rc);344wf_smu_failure_state |= FAILURE_SENSOR;345return;346}347348DBG("wf_smu: System Fans tick ! HD temp: %d.%03d\n",349FIX32TOPRINT(temp));350351if (temp > (st->pid.param.itarget + 0x50000))352wf_smu_failure_state |= FAILURE_OVERTEMP;353354new_setpoint = wf_pid_run(&st->pid, temp);355356DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);357358scaled = ((((s64)new_setpoint) * (s64)st->scale0) >> 12) + st->offset0;359360DBG("wf_smu: scaled setpoint: %d RPM\n", (int)scaled);361362cputarget = wf_smu_cpu_fans ? wf_smu_cpu_fans->pid.target : 0;363cputarget = ((((s64)cputarget) * (s64)st->scale1) >> 12) + st->offset1;364scaled = max(scaled, cputarget);365scaled = max(scaled, st->pid.param.min);366scaled = min(scaled, st->pid.param.max);367368DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)scaled);369370if (st->sys_setpoint == scaled && new_setpoint == st->hd_setpoint)371return;372st->sys_setpoint = scaled;373st->hd_setpoint = new_setpoint;374readjust:375if (fan_system && wf_smu_failure_state == 0) {376rc = fan_system->ops->set_value(fan_system, st->sys_setpoint);377if (rc) {378printk(KERN_WARNING "windfarm: Sys fan error %d\n",379rc);380wf_smu_failure_state |= FAILURE_FAN;381}382}383if (fan_hd && wf_smu_failure_state == 0) {384rc = fan_hd->ops->set_value(fan_hd, st->hd_setpoint);385if (rc) {386printk(KERN_WARNING "windfarm: HD fan error %d\n",387rc);388wf_smu_failure_state |= FAILURE_FAN;389}390}391}392393static void wf_smu_create_cpu_fans(void)394{395struct wf_cpu_pid_param pid_param;396const struct smu_sdbp_header *hdr;397struct smu_sdbp_cpupiddata *piddata;398struct smu_sdbp_fvt *fvt;399s32 tmax, tdelta, maxpow, powadj;400401/* First, locate the PID params in SMU SBD */402hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL);403if (hdr == 0) {404printk(KERN_WARNING "windfarm: CPU PID fan config not found "405"max fan speed\n");406goto fail;407}408piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];409410/* Get the FVT params for operating point 0 (the only supported one411* for now) in order to get tmax412*/413hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);414if (hdr) {415fvt = (struct smu_sdbp_fvt *)&hdr[1];416tmax = ((s32)fvt->maxtemp) << 16;417} else418tmax = 0x5e0000; /* 94 degree default */419420/* Alloc & initialize state */421wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state),422GFP_KERNEL);423if (wf_smu_cpu_fans == NULL)424goto fail;425wf_smu_cpu_fans->ticks = 1;426427wf_smu_cpu_fans->scale = WF_SMU_CPU_FANS_SIBLING_SCALE;428wf_smu_cpu_fans->offset = WF_SMU_CPU_FANS_SIBLING_OFFSET;429430/* Fill PID params */431pid_param.interval = WF_SMU_CPU_FANS_INTERVAL;432pid_param.history_len = piddata->history_len;433if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) {434printk(KERN_WARNING "windfarm: History size overflow on "435"CPU control loop (%d)\n", piddata->history_len);436pid_param.history_len = WF_CPU_PID_MAX_HISTORY;437}438pid_param.gd = piddata->gd;439pid_param.gp = piddata->gp;440pid_param.gr = piddata->gr / pid_param.history_len;441442tdelta = ((s32)piddata->target_temp_delta) << 16;443maxpow = ((s32)piddata->max_power) << 16;444powadj = ((s32)piddata->power_adj) << 16;445446pid_param.tmax = tmax;447pid_param.ttarget = tmax - tdelta;448pid_param.pmaxadj = maxpow - powadj;449450pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main);451pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main);452453wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param);454455DBG("wf: CPU Fan control initialized.\n");456DBG(" ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n",457FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax),458pid_param.min, pid_param.max);459460return;461462fail:463printk(KERN_WARNING "windfarm: CPU fan config not found\n"464"for this machine model, max fan speed\n");465466if (cpufreq_clamp)467wf_control_set_max(cpufreq_clamp);468if (fan_cpu_main)469wf_control_set_max(fan_cpu_main);470}471472static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)473{474s32 new_setpoint, temp, power, systarget;475int rc;476477if (--st->ticks != 0) {478if (wf_smu_readjust)479goto readjust;480return;481}482st->ticks = WF_SMU_CPU_FANS_INTERVAL;483484rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp);485if (rc) {486printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n",487rc);488wf_smu_failure_state |= FAILURE_SENSOR;489return;490}491492rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power);493if (rc) {494printk(KERN_WARNING "windfarm: CPU power sensor error %d\n",495rc);496wf_smu_failure_state |= FAILURE_SENSOR;497return;498}499500DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n",501FIX32TOPRINT(temp), FIX32TOPRINT(power));502503#ifdef HACKED_OVERTEMP504if (temp > 0x4a0000)505wf_smu_failure_state |= FAILURE_OVERTEMP;506#else507if (temp > st->pid.param.tmax)508wf_smu_failure_state |= FAILURE_OVERTEMP;509#endif510new_setpoint = wf_cpu_pid_run(&st->pid, power, temp);511512DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);513514systarget = wf_smu_sys_fans ? wf_smu_sys_fans->pid.target : 0;515systarget = ((((s64)systarget) * (s64)st->scale) >> 12)516+ st->offset;517new_setpoint = max(new_setpoint, systarget);518new_setpoint = max(new_setpoint, st->pid.param.min);519new_setpoint = min(new_setpoint, st->pid.param.max);520521DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)new_setpoint);522523if (st->cpu_setpoint == new_setpoint)524return;525st->cpu_setpoint = new_setpoint;526readjust:527if (fan_cpu_main && wf_smu_failure_state == 0) {528rc = fan_cpu_main->ops->set_value(fan_cpu_main,529st->cpu_setpoint);530if (rc) {531printk(KERN_WARNING "windfarm: CPU main fan"532" error %d\n", rc);533wf_smu_failure_state |= FAILURE_FAN;534}535}536}537538/*539* ****** Setup / Init / Misc ... ******540*541*/542543static void wf_smu_tick(void)544{545unsigned int last_failure = wf_smu_failure_state;546unsigned int new_failure;547548if (!wf_smu_started) {549DBG("wf: creating control loops !\n");550wf_smu_create_sys_fans();551wf_smu_create_cpu_fans();552wf_smu_started = 1;553}554555/* Skipping ticks */556if (wf_smu_skipping && --wf_smu_skipping)557return;558559wf_smu_failure_state = 0;560if (wf_smu_sys_fans)561wf_smu_sys_fans_tick(wf_smu_sys_fans);562if (wf_smu_cpu_fans)563wf_smu_cpu_fans_tick(wf_smu_cpu_fans);564565wf_smu_readjust = 0;566new_failure = wf_smu_failure_state & ~last_failure;567568/* If entering failure mode, clamp cpufreq and ramp all569* fans to full speed.570*/571if (wf_smu_failure_state && !last_failure) {572if (cpufreq_clamp)573wf_control_set_max(cpufreq_clamp);574if (fan_system)575wf_control_set_max(fan_system);576if (fan_cpu_main)577wf_control_set_max(fan_cpu_main);578if (fan_hd)579wf_control_set_max(fan_hd);580}581582/* If leaving failure mode, unclamp cpufreq and readjust583* all fans on next iteration584*/585if (!wf_smu_failure_state && last_failure) {586if (cpufreq_clamp)587wf_control_set_min(cpufreq_clamp);588wf_smu_readjust = 1;589}590591/* Overtemp condition detected, notify and start skipping a couple592* ticks to let the temperature go down593*/594if (new_failure & FAILURE_OVERTEMP) {595wf_set_overtemp();596wf_smu_skipping = 2;597}598599/* We only clear the overtemp condition if overtemp is cleared600* _and_ no other failure is present. Since a sensor error will601* clear the overtemp condition (can't measure temperature) at602* the control loop levels, but we don't want to keep it clear603* here in this case604*/605if (new_failure == 0 && last_failure & FAILURE_OVERTEMP)606wf_clear_overtemp();607}608609static void wf_smu_new_control(struct wf_control *ct)610{611if (wf_smu_all_controls_ok)612return;613614if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-fan")) {615if (wf_get_control(ct) == 0)616fan_cpu_main = ct;617}618619if (fan_system == NULL && !strcmp(ct->name, "system-fan")) {620if (wf_get_control(ct) == 0)621fan_system = ct;622}623624if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) {625if (wf_get_control(ct) == 0)626cpufreq_clamp = ct;627}628629/* Darwin property list says the HD fan is only for model ID630* 0, 1, 2 and 3631*/632633if (wf_smu_mach_model > 3) {634if (fan_system && fan_cpu_main && cpufreq_clamp)635wf_smu_all_controls_ok = 1;636return;637}638639if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) {640if (wf_get_control(ct) == 0)641fan_hd = ct;642}643644if (fan_system && fan_hd && fan_cpu_main && cpufreq_clamp)645wf_smu_all_controls_ok = 1;646}647648static void wf_smu_new_sensor(struct wf_sensor *sr)649{650if (wf_smu_all_sensors_ok)651return;652653if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) {654if (wf_get_sensor(sr) == 0)655sensor_cpu_power = sr;656}657658if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) {659if (wf_get_sensor(sr) == 0)660sensor_cpu_temp = sr;661}662663if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) {664if (wf_get_sensor(sr) == 0)665sensor_hd_temp = sr;666}667668if (sensor_cpu_power && sensor_cpu_temp && sensor_hd_temp)669wf_smu_all_sensors_ok = 1;670}671672673static int wf_smu_notify(struct notifier_block *self,674unsigned long event, void *data)675{676switch(event) {677case WF_EVENT_NEW_CONTROL:678DBG("wf: new control %s detected\n",679((struct wf_control *)data)->name);680wf_smu_new_control(data);681wf_smu_readjust = 1;682break;683case WF_EVENT_NEW_SENSOR:684DBG("wf: new sensor %s detected\n",685((struct wf_sensor *)data)->name);686wf_smu_new_sensor(data);687break;688case WF_EVENT_TICK:689if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok)690wf_smu_tick();691}692693return 0;694}695696static struct notifier_block wf_smu_events = {697.notifier_call = wf_smu_notify,698};699700static int wf_init_pm(void)701{702const struct smu_sdbp_header *hdr;703704hdr = smu_get_sdb_partition(SMU_SDB_SENSORTREE_ID, NULL);705if (hdr != 0) {706struct smu_sdbp_sensortree *st =707(struct smu_sdbp_sensortree *)&hdr[1];708wf_smu_mach_model = st->model_id;709}710711printk(KERN_INFO "windfarm: Initializing for iMacG5 model ID %d\n",712wf_smu_mach_model);713714return 0;715}716717static int wf_smu_probe(struct platform_device *ddev)718{719wf_register_client(&wf_smu_events);720721return 0;722}723724static int __devexit wf_smu_remove(struct platform_device *ddev)725{726wf_unregister_client(&wf_smu_events);727728/* XXX We don't have yet a guarantee that our callback isn't729* in progress when returning from wf_unregister_client, so730* we add an arbitrary delay. I'll have to fix that in the core731*/732msleep(1000);733734/* Release all sensors */735/* One more crappy race: I don't think we have any guarantee here736* that the attribute callback won't race with the sensor beeing737* disposed of, and I'm not 100% certain what best way to deal738* with that except by adding locks all over... I'll do that739* eventually but heh, who ever rmmod this module anyway ?740*/741if (sensor_cpu_power)742wf_put_sensor(sensor_cpu_power);743if (sensor_cpu_temp)744wf_put_sensor(sensor_cpu_temp);745if (sensor_hd_temp)746wf_put_sensor(sensor_hd_temp);747748/* Release all controls */749if (fan_cpu_main)750wf_put_control(fan_cpu_main);751if (fan_hd)752wf_put_control(fan_hd);753if (fan_system)754wf_put_control(fan_system);755if (cpufreq_clamp)756wf_put_control(cpufreq_clamp);757758/* Destroy control loops state structures */759kfree(wf_smu_sys_fans);760kfree(wf_smu_cpu_fans);761762return 0;763}764765static struct platform_driver wf_smu_driver = {766.probe = wf_smu_probe,767.remove = __devexit_p(wf_smu_remove),768.driver = {769.name = "windfarm",770.owner = THIS_MODULE,771},772};773774775static int __init wf_smu_init(void)776{777int rc = -ENODEV;778779if (of_machine_is_compatible("PowerMac8,1") ||780of_machine_is_compatible("PowerMac8,2"))781rc = wf_init_pm();782783if (rc == 0) {784#ifdef MODULE785request_module("windfarm_smu_controls");786request_module("windfarm_smu_sensors");787request_module("windfarm_lm75_sensor");788request_module("windfarm_cpufreq_clamp");789790#endif /* MODULE */791platform_driver_register(&wf_smu_driver);792}793794return rc;795}796797static void __exit wf_smu_exit(void)798{799800platform_driver_unregister(&wf_smu_driver);801}802803804module_init(wf_smu_init);805module_exit(wf_smu_exit);806807MODULE_AUTHOR("Benjamin Herrenschmidt <[email protected]>");808MODULE_DESCRIPTION("Thermal control logic for iMac G5");809MODULE_LICENSE("GPL");810MODULE_ALIAS("platform:windfarm");811812813