162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Windfarm PowerMac thermal control. SMU based 1 CPU desktop control loops 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. 662306a36Sopenharmony_ci * <benh@kernel.crashing.org> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * The algorithm used is the PID control algorithm, used the same 962306a36Sopenharmony_ci * way the published Darwin code does, using the same values that 1062306a36Sopenharmony_ci * are present in the Darwin 8.2 snapshot property lists (note however 1162306a36Sopenharmony_ci * that none of the code has been re-used, it's a complete re-implementation 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * The various control loops found in Darwin config file are: 1462306a36Sopenharmony_ci * 1562306a36Sopenharmony_ci * PowerMac9,1 1662306a36Sopenharmony_ci * =========== 1762306a36Sopenharmony_ci * 1862306a36Sopenharmony_ci * Has 3 control loops: CPU fans is similar to PowerMac8,1 (though it doesn't 1962306a36Sopenharmony_ci * try to play with other control loops fans). Drive bay is rather basic PID 2062306a36Sopenharmony_ci * with one sensor and one fan. Slots area is a bit different as the Darwin 2162306a36Sopenharmony_ci * driver is supposed to be capable of working in a special "AGP" mode which 2262306a36Sopenharmony_ci * involves the presence of an AGP sensor and an AGP fan (possibly on the 2362306a36Sopenharmony_ci * AGP card itself). I can't deal with that special mode as I don't have 2462306a36Sopenharmony_ci * access to those additional sensor/fans for now (though ultimately, it would 2562306a36Sopenharmony_ci * be possible to add sensor objects for them) so I'm only implementing the 2662306a36Sopenharmony_ci * basic PCI slot control loop 2762306a36Sopenharmony_ci */ 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#include <linux/types.h> 3062306a36Sopenharmony_ci#include <linux/errno.h> 3162306a36Sopenharmony_ci#include <linux/kernel.h> 3262306a36Sopenharmony_ci#include <linux/delay.h> 3362306a36Sopenharmony_ci#include <linux/slab.h> 3462306a36Sopenharmony_ci#include <linux/init.h> 3562306a36Sopenharmony_ci#include <linux/spinlock.h> 3662306a36Sopenharmony_ci#include <linux/wait.h> 3762306a36Sopenharmony_ci#include <linux/kmod.h> 3862306a36Sopenharmony_ci#include <linux/device.h> 3962306a36Sopenharmony_ci#include <linux/platform_device.h> 4062306a36Sopenharmony_ci#include <linux/of.h> 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#include <asm/machdep.h> 4362306a36Sopenharmony_ci#include <asm/io.h> 4462306a36Sopenharmony_ci#include <asm/sections.h> 4562306a36Sopenharmony_ci#include <asm/smu.h> 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci#include "windfarm.h" 4862306a36Sopenharmony_ci#include "windfarm_pid.h" 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci#define VERSION "0.4" 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci#undef DEBUG 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci#ifdef DEBUG 5562306a36Sopenharmony_ci#define DBG(args...) printk(args) 5662306a36Sopenharmony_ci#else 5762306a36Sopenharmony_ci#define DBG(args...) do { } while(0) 5862306a36Sopenharmony_ci#endif 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci/* define this to force CPU overtemp to 74 degree, useful for testing 6162306a36Sopenharmony_ci * the overtemp code 6262306a36Sopenharmony_ci */ 6362306a36Sopenharmony_ci#undef HACKED_OVERTEMP 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci/* Controls & sensors */ 6662306a36Sopenharmony_cistatic struct wf_sensor *sensor_cpu_power; 6762306a36Sopenharmony_cistatic struct wf_sensor *sensor_cpu_temp; 6862306a36Sopenharmony_cistatic struct wf_sensor *sensor_hd_temp; 6962306a36Sopenharmony_cistatic struct wf_sensor *sensor_slots_power; 7062306a36Sopenharmony_cistatic struct wf_control *fan_cpu_main; 7162306a36Sopenharmony_cistatic struct wf_control *fan_cpu_second; 7262306a36Sopenharmony_cistatic struct wf_control *fan_cpu_third; 7362306a36Sopenharmony_cistatic struct wf_control *fan_hd; 7462306a36Sopenharmony_cistatic struct wf_control *fan_slots; 7562306a36Sopenharmony_cistatic struct wf_control *cpufreq_clamp; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci/* Set to kick the control loop into life */ 7862306a36Sopenharmony_cistatic int wf_smu_all_controls_ok, wf_smu_all_sensors_ok; 7962306a36Sopenharmony_cistatic bool wf_smu_started; 8062306a36Sopenharmony_cistatic bool wf_smu_overtemp; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci/* Failure handling.. could be nicer */ 8362306a36Sopenharmony_ci#define FAILURE_FAN 0x01 8462306a36Sopenharmony_ci#define FAILURE_SENSOR 0x02 8562306a36Sopenharmony_ci#define FAILURE_OVERTEMP 0x04 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic unsigned int wf_smu_failure_state; 8862306a36Sopenharmony_cistatic int wf_smu_readjust, wf_smu_skipping; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci/* 9162306a36Sopenharmony_ci * ****** CPU Fans Control Loop ****** 9262306a36Sopenharmony_ci * 9362306a36Sopenharmony_ci */ 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci#define WF_SMU_CPU_FANS_INTERVAL 1 9762306a36Sopenharmony_ci#define WF_SMU_CPU_FANS_MAX_HISTORY 16 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci/* State data used by the cpu fans control loop 10062306a36Sopenharmony_ci */ 10162306a36Sopenharmony_cistruct wf_smu_cpu_fans_state { 10262306a36Sopenharmony_ci int ticks; 10362306a36Sopenharmony_ci s32 cpu_setpoint; 10462306a36Sopenharmony_ci struct wf_cpu_pid_state pid; 10562306a36Sopenharmony_ci}; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_cistatic struct wf_smu_cpu_fans_state *wf_smu_cpu_fans; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci/* 11262306a36Sopenharmony_ci * ****** Drive Fan Control Loop ****** 11362306a36Sopenharmony_ci * 11462306a36Sopenharmony_ci */ 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_cistruct wf_smu_drive_fans_state { 11762306a36Sopenharmony_ci int ticks; 11862306a36Sopenharmony_ci s32 setpoint; 11962306a36Sopenharmony_ci struct wf_pid_state pid; 12062306a36Sopenharmony_ci}; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic struct wf_smu_drive_fans_state *wf_smu_drive_fans; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci/* 12562306a36Sopenharmony_ci * ****** Slots Fan Control Loop ****** 12662306a36Sopenharmony_ci * 12762306a36Sopenharmony_ci */ 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistruct wf_smu_slots_fans_state { 13062306a36Sopenharmony_ci int ticks; 13162306a36Sopenharmony_ci s32 setpoint; 13262306a36Sopenharmony_ci struct wf_pid_state pid; 13362306a36Sopenharmony_ci}; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_cistatic struct wf_smu_slots_fans_state *wf_smu_slots_fans; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci/* 13862306a36Sopenharmony_ci * ***** Implementation ***** 13962306a36Sopenharmony_ci * 14062306a36Sopenharmony_ci */ 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic void wf_smu_create_cpu_fans(void) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci struct wf_cpu_pid_param pid_param; 14662306a36Sopenharmony_ci const struct smu_sdbp_header *hdr; 14762306a36Sopenharmony_ci struct smu_sdbp_cpupiddata *piddata; 14862306a36Sopenharmony_ci struct smu_sdbp_fvt *fvt; 14962306a36Sopenharmony_ci s32 tmax, tdelta, maxpow, powadj; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci /* First, locate the PID params in SMU SBD */ 15262306a36Sopenharmony_ci hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL); 15362306a36Sopenharmony_ci if (!hdr) { 15462306a36Sopenharmony_ci printk(KERN_WARNING "windfarm: CPU PID fan config not found " 15562306a36Sopenharmony_ci "max fan speed\n"); 15662306a36Sopenharmony_ci goto fail; 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci piddata = (struct smu_sdbp_cpupiddata *)&hdr[1]; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci /* Get the FVT params for operating point 0 (the only supported one 16162306a36Sopenharmony_ci * for now) in order to get tmax 16262306a36Sopenharmony_ci */ 16362306a36Sopenharmony_ci hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL); 16462306a36Sopenharmony_ci if (hdr) { 16562306a36Sopenharmony_ci fvt = (struct smu_sdbp_fvt *)&hdr[1]; 16662306a36Sopenharmony_ci tmax = ((s32)fvt->maxtemp) << 16; 16762306a36Sopenharmony_ci } else 16862306a36Sopenharmony_ci tmax = 0x5e0000; /* 94 degree default */ 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci /* Alloc & initialize state */ 17162306a36Sopenharmony_ci wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state), 17262306a36Sopenharmony_ci GFP_KERNEL); 17362306a36Sopenharmony_ci if (wf_smu_cpu_fans == NULL) 17462306a36Sopenharmony_ci goto fail; 17562306a36Sopenharmony_ci wf_smu_cpu_fans->ticks = 1; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci /* Fill PID params */ 17862306a36Sopenharmony_ci pid_param.interval = WF_SMU_CPU_FANS_INTERVAL; 17962306a36Sopenharmony_ci pid_param.history_len = piddata->history_len; 18062306a36Sopenharmony_ci if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) { 18162306a36Sopenharmony_ci printk(KERN_WARNING "windfarm: History size overflow on " 18262306a36Sopenharmony_ci "CPU control loop (%d)\n", piddata->history_len); 18362306a36Sopenharmony_ci pid_param.history_len = WF_CPU_PID_MAX_HISTORY; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci pid_param.gd = piddata->gd; 18662306a36Sopenharmony_ci pid_param.gp = piddata->gp; 18762306a36Sopenharmony_ci pid_param.gr = piddata->gr / pid_param.history_len; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci tdelta = ((s32)piddata->target_temp_delta) << 16; 19062306a36Sopenharmony_ci maxpow = ((s32)piddata->max_power) << 16; 19162306a36Sopenharmony_ci powadj = ((s32)piddata->power_adj) << 16; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci pid_param.tmax = tmax; 19462306a36Sopenharmony_ci pid_param.ttarget = tmax - tdelta; 19562306a36Sopenharmony_ci pid_param.pmaxadj = maxpow - powadj; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci pid_param.min = wf_control_get_min(fan_cpu_main); 19862306a36Sopenharmony_ci pid_param.max = wf_control_get_max(fan_cpu_main); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci DBG("wf: CPU Fan control initialized.\n"); 20362306a36Sopenharmony_ci DBG(" ttarget=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n", 20462306a36Sopenharmony_ci FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax), 20562306a36Sopenharmony_ci pid_param.min, pid_param.max); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci return; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci fail: 21062306a36Sopenharmony_ci printk(KERN_WARNING "windfarm: CPU fan config not found\n" 21162306a36Sopenharmony_ci "for this machine model, max fan speed\n"); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci if (cpufreq_clamp) 21462306a36Sopenharmony_ci wf_control_set_max(cpufreq_clamp); 21562306a36Sopenharmony_ci if (fan_cpu_main) 21662306a36Sopenharmony_ci wf_control_set_max(fan_cpu_main); 21762306a36Sopenharmony_ci} 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_cistatic void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st) 22062306a36Sopenharmony_ci{ 22162306a36Sopenharmony_ci s32 new_setpoint, temp, power; 22262306a36Sopenharmony_ci int rc; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci if (--st->ticks != 0) { 22562306a36Sopenharmony_ci if (wf_smu_readjust) 22662306a36Sopenharmony_ci goto readjust; 22762306a36Sopenharmony_ci return; 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci st->ticks = WF_SMU_CPU_FANS_INTERVAL; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci rc = wf_sensor_get(sensor_cpu_temp, &temp); 23262306a36Sopenharmony_ci if (rc) { 23362306a36Sopenharmony_ci printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n", 23462306a36Sopenharmony_ci rc); 23562306a36Sopenharmony_ci wf_smu_failure_state |= FAILURE_SENSOR; 23662306a36Sopenharmony_ci return; 23762306a36Sopenharmony_ci } 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci rc = wf_sensor_get(sensor_cpu_power, &power); 24062306a36Sopenharmony_ci if (rc) { 24162306a36Sopenharmony_ci printk(KERN_WARNING "windfarm: CPU power sensor error %d\n", 24262306a36Sopenharmony_ci rc); 24362306a36Sopenharmony_ci wf_smu_failure_state |= FAILURE_SENSOR; 24462306a36Sopenharmony_ci return; 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n", 24862306a36Sopenharmony_ci FIX32TOPRINT(temp), FIX32TOPRINT(power)); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci#ifdef HACKED_OVERTEMP 25162306a36Sopenharmony_ci if (temp > 0x4a0000) 25262306a36Sopenharmony_ci wf_smu_failure_state |= FAILURE_OVERTEMP; 25362306a36Sopenharmony_ci#else 25462306a36Sopenharmony_ci if (temp > st->pid.param.tmax) 25562306a36Sopenharmony_ci wf_smu_failure_state |= FAILURE_OVERTEMP; 25662306a36Sopenharmony_ci#endif 25762306a36Sopenharmony_ci new_setpoint = wf_cpu_pid_run(&st->pid, power, temp); 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint); 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci if (st->cpu_setpoint == new_setpoint) 26262306a36Sopenharmony_ci return; 26362306a36Sopenharmony_ci st->cpu_setpoint = new_setpoint; 26462306a36Sopenharmony_ci readjust: 26562306a36Sopenharmony_ci if (fan_cpu_main && wf_smu_failure_state == 0) { 26662306a36Sopenharmony_ci rc = wf_control_set(fan_cpu_main, st->cpu_setpoint); 26762306a36Sopenharmony_ci if (rc) { 26862306a36Sopenharmony_ci printk(KERN_WARNING "windfarm: CPU main fan" 26962306a36Sopenharmony_ci " error %d\n", rc); 27062306a36Sopenharmony_ci wf_smu_failure_state |= FAILURE_FAN; 27162306a36Sopenharmony_ci } 27262306a36Sopenharmony_ci } 27362306a36Sopenharmony_ci if (fan_cpu_second && wf_smu_failure_state == 0) { 27462306a36Sopenharmony_ci rc = wf_control_set(fan_cpu_second, st->cpu_setpoint); 27562306a36Sopenharmony_ci if (rc) { 27662306a36Sopenharmony_ci printk(KERN_WARNING "windfarm: CPU second fan" 27762306a36Sopenharmony_ci " error %d\n", rc); 27862306a36Sopenharmony_ci wf_smu_failure_state |= FAILURE_FAN; 27962306a36Sopenharmony_ci } 28062306a36Sopenharmony_ci } 28162306a36Sopenharmony_ci if (fan_cpu_third && wf_smu_failure_state == 0) { 28262306a36Sopenharmony_ci rc = wf_control_set(fan_cpu_third, st->cpu_setpoint); 28362306a36Sopenharmony_ci if (rc) { 28462306a36Sopenharmony_ci printk(KERN_WARNING "windfarm: CPU third fan" 28562306a36Sopenharmony_ci " error %d\n", rc); 28662306a36Sopenharmony_ci wf_smu_failure_state |= FAILURE_FAN; 28762306a36Sopenharmony_ci } 28862306a36Sopenharmony_ci } 28962306a36Sopenharmony_ci} 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_cistatic void wf_smu_create_drive_fans(void) 29262306a36Sopenharmony_ci{ 29362306a36Sopenharmony_ci struct wf_pid_param param = { 29462306a36Sopenharmony_ci .interval = 5, 29562306a36Sopenharmony_ci .history_len = 2, 29662306a36Sopenharmony_ci .gd = 0x01e00000, 29762306a36Sopenharmony_ci .gp = 0x00500000, 29862306a36Sopenharmony_ci .gr = 0x00000000, 29962306a36Sopenharmony_ci .itarget = 0x00200000, 30062306a36Sopenharmony_ci }; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci /* Alloc & initialize state */ 30362306a36Sopenharmony_ci wf_smu_drive_fans = kmalloc(sizeof(struct wf_smu_drive_fans_state), 30462306a36Sopenharmony_ci GFP_KERNEL); 30562306a36Sopenharmony_ci if (wf_smu_drive_fans == NULL) { 30662306a36Sopenharmony_ci printk(KERN_WARNING "windfarm: Memory allocation error" 30762306a36Sopenharmony_ci " max fan speed\n"); 30862306a36Sopenharmony_ci goto fail; 30962306a36Sopenharmony_ci } 31062306a36Sopenharmony_ci wf_smu_drive_fans->ticks = 1; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci /* Fill PID params */ 31362306a36Sopenharmony_ci param.additive = (fan_hd->type == WF_CONTROL_RPM_FAN); 31462306a36Sopenharmony_ci param.min = wf_control_get_min(fan_hd); 31562306a36Sopenharmony_ci param.max = wf_control_get_max(fan_hd); 31662306a36Sopenharmony_ci wf_pid_init(&wf_smu_drive_fans->pid, ¶m); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci DBG("wf: Drive Fan control initialized.\n"); 31962306a36Sopenharmony_ci DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n", 32062306a36Sopenharmony_ci FIX32TOPRINT(param.itarget), param.min, param.max); 32162306a36Sopenharmony_ci return; 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci fail: 32462306a36Sopenharmony_ci if (fan_hd) 32562306a36Sopenharmony_ci wf_control_set_max(fan_hd); 32662306a36Sopenharmony_ci} 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_cistatic void wf_smu_drive_fans_tick(struct wf_smu_drive_fans_state *st) 32962306a36Sopenharmony_ci{ 33062306a36Sopenharmony_ci s32 new_setpoint, temp; 33162306a36Sopenharmony_ci int rc; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci if (--st->ticks != 0) { 33462306a36Sopenharmony_ci if (wf_smu_readjust) 33562306a36Sopenharmony_ci goto readjust; 33662306a36Sopenharmony_ci return; 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci st->ticks = st->pid.param.interval; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci rc = wf_sensor_get(sensor_hd_temp, &temp); 34162306a36Sopenharmony_ci if (rc) { 34262306a36Sopenharmony_ci printk(KERN_WARNING "windfarm: HD temp sensor error %d\n", 34362306a36Sopenharmony_ci rc); 34462306a36Sopenharmony_ci wf_smu_failure_state |= FAILURE_SENSOR; 34562306a36Sopenharmony_ci return; 34662306a36Sopenharmony_ci } 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci DBG("wf_smu: Drive Fans tick ! HD temp: %d.%03d\n", 34962306a36Sopenharmony_ci FIX32TOPRINT(temp)); 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci if (temp > (st->pid.param.itarget + 0x50000)) 35262306a36Sopenharmony_ci wf_smu_failure_state |= FAILURE_OVERTEMP; 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci new_setpoint = wf_pid_run(&st->pid, temp); 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint); 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci if (st->setpoint == new_setpoint) 35962306a36Sopenharmony_ci return; 36062306a36Sopenharmony_ci st->setpoint = new_setpoint; 36162306a36Sopenharmony_ci readjust: 36262306a36Sopenharmony_ci if (fan_hd && wf_smu_failure_state == 0) { 36362306a36Sopenharmony_ci rc = wf_control_set(fan_hd, st->setpoint); 36462306a36Sopenharmony_ci if (rc) { 36562306a36Sopenharmony_ci printk(KERN_WARNING "windfarm: HD fan error %d\n", 36662306a36Sopenharmony_ci rc); 36762306a36Sopenharmony_ci wf_smu_failure_state |= FAILURE_FAN; 36862306a36Sopenharmony_ci } 36962306a36Sopenharmony_ci } 37062306a36Sopenharmony_ci} 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_cistatic void wf_smu_create_slots_fans(void) 37362306a36Sopenharmony_ci{ 37462306a36Sopenharmony_ci struct wf_pid_param param = { 37562306a36Sopenharmony_ci .interval = 1, 37662306a36Sopenharmony_ci .history_len = 8, 37762306a36Sopenharmony_ci .gd = 0x00000000, 37862306a36Sopenharmony_ci .gp = 0x00000000, 37962306a36Sopenharmony_ci .gr = 0x00020000, 38062306a36Sopenharmony_ci .itarget = 0x00000000 38162306a36Sopenharmony_ci }; 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci /* Alloc & initialize state */ 38462306a36Sopenharmony_ci wf_smu_slots_fans = kmalloc(sizeof(struct wf_smu_slots_fans_state), 38562306a36Sopenharmony_ci GFP_KERNEL); 38662306a36Sopenharmony_ci if (wf_smu_slots_fans == NULL) { 38762306a36Sopenharmony_ci printk(KERN_WARNING "windfarm: Memory allocation error" 38862306a36Sopenharmony_ci " max fan speed\n"); 38962306a36Sopenharmony_ci goto fail; 39062306a36Sopenharmony_ci } 39162306a36Sopenharmony_ci wf_smu_slots_fans->ticks = 1; 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci /* Fill PID params */ 39462306a36Sopenharmony_ci param.additive = (fan_slots->type == WF_CONTROL_RPM_FAN); 39562306a36Sopenharmony_ci param.min = wf_control_get_min(fan_slots); 39662306a36Sopenharmony_ci param.max = wf_control_get_max(fan_slots); 39762306a36Sopenharmony_ci wf_pid_init(&wf_smu_slots_fans->pid, ¶m); 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci DBG("wf: Slots Fan control initialized.\n"); 40062306a36Sopenharmony_ci DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n", 40162306a36Sopenharmony_ci FIX32TOPRINT(param.itarget), param.min, param.max); 40262306a36Sopenharmony_ci return; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci fail: 40562306a36Sopenharmony_ci if (fan_slots) 40662306a36Sopenharmony_ci wf_control_set_max(fan_slots); 40762306a36Sopenharmony_ci} 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_cistatic void wf_smu_slots_fans_tick(struct wf_smu_slots_fans_state *st) 41062306a36Sopenharmony_ci{ 41162306a36Sopenharmony_ci s32 new_setpoint, power; 41262306a36Sopenharmony_ci int rc; 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci if (--st->ticks != 0) { 41562306a36Sopenharmony_ci if (wf_smu_readjust) 41662306a36Sopenharmony_ci goto readjust; 41762306a36Sopenharmony_ci return; 41862306a36Sopenharmony_ci } 41962306a36Sopenharmony_ci st->ticks = st->pid.param.interval; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci rc = wf_sensor_get(sensor_slots_power, &power); 42262306a36Sopenharmony_ci if (rc) { 42362306a36Sopenharmony_ci printk(KERN_WARNING "windfarm: Slots power sensor error %d\n", 42462306a36Sopenharmony_ci rc); 42562306a36Sopenharmony_ci wf_smu_failure_state |= FAILURE_SENSOR; 42662306a36Sopenharmony_ci return; 42762306a36Sopenharmony_ci } 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci DBG("wf_smu: Slots Fans tick ! Slots power: %d.%03d\n", 43062306a36Sopenharmony_ci FIX32TOPRINT(power)); 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci#if 0 /* Check what makes a good overtemp condition */ 43362306a36Sopenharmony_ci if (power > (st->pid.param.itarget + 0x50000)) 43462306a36Sopenharmony_ci wf_smu_failure_state |= FAILURE_OVERTEMP; 43562306a36Sopenharmony_ci#endif 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci new_setpoint = wf_pid_run(&st->pid, power); 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint); 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci if (st->setpoint == new_setpoint) 44262306a36Sopenharmony_ci return; 44362306a36Sopenharmony_ci st->setpoint = new_setpoint; 44462306a36Sopenharmony_ci readjust: 44562306a36Sopenharmony_ci if (fan_slots && wf_smu_failure_state == 0) { 44662306a36Sopenharmony_ci rc = wf_control_set(fan_slots, st->setpoint); 44762306a36Sopenharmony_ci if (rc) { 44862306a36Sopenharmony_ci printk(KERN_WARNING "windfarm: Slots fan error %d\n", 44962306a36Sopenharmony_ci rc); 45062306a36Sopenharmony_ci wf_smu_failure_state |= FAILURE_FAN; 45162306a36Sopenharmony_ci } 45262306a36Sopenharmony_ci } 45362306a36Sopenharmony_ci} 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci/* 45762306a36Sopenharmony_ci * ****** Setup / Init / Misc ... ****** 45862306a36Sopenharmony_ci * 45962306a36Sopenharmony_ci */ 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_cistatic void wf_smu_tick(void) 46262306a36Sopenharmony_ci{ 46362306a36Sopenharmony_ci unsigned int last_failure = wf_smu_failure_state; 46462306a36Sopenharmony_ci unsigned int new_failure; 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci if (!wf_smu_started) { 46762306a36Sopenharmony_ci DBG("wf: creating control loops !\n"); 46862306a36Sopenharmony_ci wf_smu_create_drive_fans(); 46962306a36Sopenharmony_ci wf_smu_create_slots_fans(); 47062306a36Sopenharmony_ci wf_smu_create_cpu_fans(); 47162306a36Sopenharmony_ci wf_smu_started = true; 47262306a36Sopenharmony_ci } 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci /* Skipping ticks */ 47562306a36Sopenharmony_ci if (wf_smu_skipping && --wf_smu_skipping) 47662306a36Sopenharmony_ci return; 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci wf_smu_failure_state = 0; 47962306a36Sopenharmony_ci if (wf_smu_drive_fans) 48062306a36Sopenharmony_ci wf_smu_drive_fans_tick(wf_smu_drive_fans); 48162306a36Sopenharmony_ci if (wf_smu_slots_fans) 48262306a36Sopenharmony_ci wf_smu_slots_fans_tick(wf_smu_slots_fans); 48362306a36Sopenharmony_ci if (wf_smu_cpu_fans) 48462306a36Sopenharmony_ci wf_smu_cpu_fans_tick(wf_smu_cpu_fans); 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci wf_smu_readjust = 0; 48762306a36Sopenharmony_ci new_failure = wf_smu_failure_state & ~last_failure; 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci /* If entering failure mode, clamp cpufreq and ramp all 49062306a36Sopenharmony_ci * fans to full speed. 49162306a36Sopenharmony_ci */ 49262306a36Sopenharmony_ci if (wf_smu_failure_state && !last_failure) { 49362306a36Sopenharmony_ci if (cpufreq_clamp) 49462306a36Sopenharmony_ci wf_control_set_max(cpufreq_clamp); 49562306a36Sopenharmony_ci if (fan_cpu_main) 49662306a36Sopenharmony_ci wf_control_set_max(fan_cpu_main); 49762306a36Sopenharmony_ci if (fan_cpu_second) 49862306a36Sopenharmony_ci wf_control_set_max(fan_cpu_second); 49962306a36Sopenharmony_ci if (fan_cpu_third) 50062306a36Sopenharmony_ci wf_control_set_max(fan_cpu_third); 50162306a36Sopenharmony_ci if (fan_hd) 50262306a36Sopenharmony_ci wf_control_set_max(fan_hd); 50362306a36Sopenharmony_ci if (fan_slots) 50462306a36Sopenharmony_ci wf_control_set_max(fan_slots); 50562306a36Sopenharmony_ci } 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci /* If leaving failure mode, unclamp cpufreq and readjust 50862306a36Sopenharmony_ci * all fans on next iteration 50962306a36Sopenharmony_ci */ 51062306a36Sopenharmony_ci if (!wf_smu_failure_state && last_failure) { 51162306a36Sopenharmony_ci if (cpufreq_clamp) 51262306a36Sopenharmony_ci wf_control_set_min(cpufreq_clamp); 51362306a36Sopenharmony_ci wf_smu_readjust = 1; 51462306a36Sopenharmony_ci } 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci /* Overtemp condition detected, notify and start skipping a couple 51762306a36Sopenharmony_ci * ticks to let the temperature go down 51862306a36Sopenharmony_ci */ 51962306a36Sopenharmony_ci if (new_failure & FAILURE_OVERTEMP) { 52062306a36Sopenharmony_ci wf_set_overtemp(); 52162306a36Sopenharmony_ci wf_smu_skipping = 2; 52262306a36Sopenharmony_ci wf_smu_overtemp = true; 52362306a36Sopenharmony_ci } 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci /* We only clear the overtemp condition if overtemp is cleared 52662306a36Sopenharmony_ci * _and_ no other failure is present. Since a sensor error will 52762306a36Sopenharmony_ci * clear the overtemp condition (can't measure temperature) at 52862306a36Sopenharmony_ci * the control loop levels, but we don't want to keep it clear 52962306a36Sopenharmony_ci * here in this case 53062306a36Sopenharmony_ci */ 53162306a36Sopenharmony_ci if (!wf_smu_failure_state && wf_smu_overtemp) { 53262306a36Sopenharmony_ci wf_clear_overtemp(); 53362306a36Sopenharmony_ci wf_smu_overtemp = false; 53462306a36Sopenharmony_ci } 53562306a36Sopenharmony_ci} 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_cistatic void wf_smu_new_control(struct wf_control *ct) 53962306a36Sopenharmony_ci{ 54062306a36Sopenharmony_ci if (wf_smu_all_controls_ok) 54162306a36Sopenharmony_ci return; 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-rear-fan-0")) { 54462306a36Sopenharmony_ci if (wf_get_control(ct) == 0) 54562306a36Sopenharmony_ci fan_cpu_main = ct; 54662306a36Sopenharmony_ci } 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci if (fan_cpu_second == NULL && !strcmp(ct->name, "cpu-rear-fan-1")) { 54962306a36Sopenharmony_ci if (wf_get_control(ct) == 0) 55062306a36Sopenharmony_ci fan_cpu_second = ct; 55162306a36Sopenharmony_ci } 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci if (fan_cpu_third == NULL && !strcmp(ct->name, "cpu-front-fan-0")) { 55462306a36Sopenharmony_ci if (wf_get_control(ct) == 0) 55562306a36Sopenharmony_ci fan_cpu_third = ct; 55662306a36Sopenharmony_ci } 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) { 55962306a36Sopenharmony_ci if (wf_get_control(ct) == 0) 56062306a36Sopenharmony_ci cpufreq_clamp = ct; 56162306a36Sopenharmony_ci } 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) { 56462306a36Sopenharmony_ci if (wf_get_control(ct) == 0) 56562306a36Sopenharmony_ci fan_hd = ct; 56662306a36Sopenharmony_ci } 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci if (fan_slots == NULL && !strcmp(ct->name, "slots-fan")) { 56962306a36Sopenharmony_ci if (wf_get_control(ct) == 0) 57062306a36Sopenharmony_ci fan_slots = ct; 57162306a36Sopenharmony_ci } 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci if (fan_cpu_main && (fan_cpu_second || fan_cpu_third) && fan_hd && 57462306a36Sopenharmony_ci fan_slots && cpufreq_clamp) 57562306a36Sopenharmony_ci wf_smu_all_controls_ok = 1; 57662306a36Sopenharmony_ci} 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_cistatic void wf_smu_new_sensor(struct wf_sensor *sr) 57962306a36Sopenharmony_ci{ 58062306a36Sopenharmony_ci if (wf_smu_all_sensors_ok) 58162306a36Sopenharmony_ci return; 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) { 58462306a36Sopenharmony_ci if (wf_get_sensor(sr) == 0) 58562306a36Sopenharmony_ci sensor_cpu_power = sr; 58662306a36Sopenharmony_ci } 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) { 58962306a36Sopenharmony_ci if (wf_get_sensor(sr) == 0) 59062306a36Sopenharmony_ci sensor_cpu_temp = sr; 59162306a36Sopenharmony_ci } 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) { 59462306a36Sopenharmony_ci if (wf_get_sensor(sr) == 0) 59562306a36Sopenharmony_ci sensor_hd_temp = sr; 59662306a36Sopenharmony_ci } 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci if (sensor_slots_power == NULL && !strcmp(sr->name, "slots-power")) { 59962306a36Sopenharmony_ci if (wf_get_sensor(sr) == 0) 60062306a36Sopenharmony_ci sensor_slots_power = sr; 60162306a36Sopenharmony_ci } 60262306a36Sopenharmony_ci 60362306a36Sopenharmony_ci if (sensor_cpu_power && sensor_cpu_temp && 60462306a36Sopenharmony_ci sensor_hd_temp && sensor_slots_power) 60562306a36Sopenharmony_ci wf_smu_all_sensors_ok = 1; 60662306a36Sopenharmony_ci} 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_cistatic int wf_smu_notify(struct notifier_block *self, 61062306a36Sopenharmony_ci unsigned long event, void *data) 61162306a36Sopenharmony_ci{ 61262306a36Sopenharmony_ci switch(event) { 61362306a36Sopenharmony_ci case WF_EVENT_NEW_CONTROL: 61462306a36Sopenharmony_ci DBG("wf: new control %s detected\n", 61562306a36Sopenharmony_ci ((struct wf_control *)data)->name); 61662306a36Sopenharmony_ci wf_smu_new_control(data); 61762306a36Sopenharmony_ci wf_smu_readjust = 1; 61862306a36Sopenharmony_ci break; 61962306a36Sopenharmony_ci case WF_EVENT_NEW_SENSOR: 62062306a36Sopenharmony_ci DBG("wf: new sensor %s detected\n", 62162306a36Sopenharmony_ci ((struct wf_sensor *)data)->name); 62262306a36Sopenharmony_ci wf_smu_new_sensor(data); 62362306a36Sopenharmony_ci break; 62462306a36Sopenharmony_ci case WF_EVENT_TICK: 62562306a36Sopenharmony_ci if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok) 62662306a36Sopenharmony_ci wf_smu_tick(); 62762306a36Sopenharmony_ci } 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_ci return 0; 63062306a36Sopenharmony_ci} 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_cistatic struct notifier_block wf_smu_events = { 63362306a36Sopenharmony_ci .notifier_call = wf_smu_notify, 63462306a36Sopenharmony_ci}; 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_cistatic int wf_init_pm(void) 63762306a36Sopenharmony_ci{ 63862306a36Sopenharmony_ci printk(KERN_INFO "windfarm: Initializing for Desktop G5 model\n"); 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_ci return 0; 64162306a36Sopenharmony_ci} 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_cistatic int wf_smu_probe(struct platform_device *ddev) 64462306a36Sopenharmony_ci{ 64562306a36Sopenharmony_ci wf_register_client(&wf_smu_events); 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_ci return 0; 64862306a36Sopenharmony_ci} 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_cistatic int wf_smu_remove(struct platform_device *ddev) 65162306a36Sopenharmony_ci{ 65262306a36Sopenharmony_ci wf_unregister_client(&wf_smu_events); 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci /* XXX We don't have yet a guarantee that our callback isn't 65562306a36Sopenharmony_ci * in progress when returning from wf_unregister_client, so 65662306a36Sopenharmony_ci * we add an arbitrary delay. I'll have to fix that in the core 65762306a36Sopenharmony_ci */ 65862306a36Sopenharmony_ci msleep(1000); 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_ci /* Release all sensors */ 66162306a36Sopenharmony_ci /* One more crappy race: I don't think we have any guarantee here 66262306a36Sopenharmony_ci * that the attribute callback won't race with the sensor beeing 66362306a36Sopenharmony_ci * disposed of, and I'm not 100% certain what best way to deal 66462306a36Sopenharmony_ci * with that except by adding locks all over... I'll do that 66562306a36Sopenharmony_ci * eventually but heh, who ever rmmod this module anyway ? 66662306a36Sopenharmony_ci */ 66762306a36Sopenharmony_ci if (sensor_cpu_power) 66862306a36Sopenharmony_ci wf_put_sensor(sensor_cpu_power); 66962306a36Sopenharmony_ci if (sensor_cpu_temp) 67062306a36Sopenharmony_ci wf_put_sensor(sensor_cpu_temp); 67162306a36Sopenharmony_ci if (sensor_hd_temp) 67262306a36Sopenharmony_ci wf_put_sensor(sensor_hd_temp); 67362306a36Sopenharmony_ci if (sensor_slots_power) 67462306a36Sopenharmony_ci wf_put_sensor(sensor_slots_power); 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci /* Release all controls */ 67762306a36Sopenharmony_ci if (fan_cpu_main) 67862306a36Sopenharmony_ci wf_put_control(fan_cpu_main); 67962306a36Sopenharmony_ci if (fan_cpu_second) 68062306a36Sopenharmony_ci wf_put_control(fan_cpu_second); 68162306a36Sopenharmony_ci if (fan_cpu_third) 68262306a36Sopenharmony_ci wf_put_control(fan_cpu_third); 68362306a36Sopenharmony_ci if (fan_hd) 68462306a36Sopenharmony_ci wf_put_control(fan_hd); 68562306a36Sopenharmony_ci if (fan_slots) 68662306a36Sopenharmony_ci wf_put_control(fan_slots); 68762306a36Sopenharmony_ci if (cpufreq_clamp) 68862306a36Sopenharmony_ci wf_put_control(cpufreq_clamp); 68962306a36Sopenharmony_ci 69062306a36Sopenharmony_ci /* Destroy control loops state structures */ 69162306a36Sopenharmony_ci kfree(wf_smu_slots_fans); 69262306a36Sopenharmony_ci kfree(wf_smu_drive_fans); 69362306a36Sopenharmony_ci kfree(wf_smu_cpu_fans); 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_ci return 0; 69662306a36Sopenharmony_ci} 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_cistatic struct platform_driver wf_smu_driver = { 69962306a36Sopenharmony_ci .probe = wf_smu_probe, 70062306a36Sopenharmony_ci .remove = wf_smu_remove, 70162306a36Sopenharmony_ci .driver = { 70262306a36Sopenharmony_ci .name = "windfarm", 70362306a36Sopenharmony_ci }, 70462306a36Sopenharmony_ci}; 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_cistatic int __init wf_smu_init(void) 70862306a36Sopenharmony_ci{ 70962306a36Sopenharmony_ci int rc = -ENODEV; 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci if (of_machine_is_compatible("PowerMac9,1")) 71262306a36Sopenharmony_ci rc = wf_init_pm(); 71362306a36Sopenharmony_ci 71462306a36Sopenharmony_ci if (rc == 0) { 71562306a36Sopenharmony_ci#ifdef MODULE 71662306a36Sopenharmony_ci request_module("windfarm_smu_controls"); 71762306a36Sopenharmony_ci request_module("windfarm_smu_sensors"); 71862306a36Sopenharmony_ci request_module("windfarm_lm75_sensor"); 71962306a36Sopenharmony_ci request_module("windfarm_cpufreq_clamp"); 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_ci#endif /* MODULE */ 72262306a36Sopenharmony_ci platform_driver_register(&wf_smu_driver); 72362306a36Sopenharmony_ci } 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ci return rc; 72662306a36Sopenharmony_ci} 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_cistatic void __exit wf_smu_exit(void) 72962306a36Sopenharmony_ci{ 73062306a36Sopenharmony_ci 73162306a36Sopenharmony_ci platform_driver_unregister(&wf_smu_driver); 73262306a36Sopenharmony_ci} 73362306a36Sopenharmony_ci 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_cimodule_init(wf_smu_init); 73662306a36Sopenharmony_cimodule_exit(wf_smu_exit); 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ciMODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); 73962306a36Sopenharmony_ciMODULE_DESCRIPTION("Thermal control logic for PowerMac9,1"); 74062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ciMODULE_ALIAS("platform:windfarm"); 743