Path: blob/master/drivers/macintosh/windfarm_pm91.c
15109 views
/*1* Windfarm PowerMac thermal control. SMU based 1 CPU desktop control loops2*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* PowerMac9,116* ===========17*18* Has 3 control loops: CPU fans is similar to PowerMac8,1 (though it doesn't19* try to play with other control loops fans). Drive bay is rather basic PID20* with one sensor and one fan. Slots area is a bit different as the Darwin21* driver is supposed to be capable of working in a special "AGP" mode which22* involves the presence of an AGP sensor and an AGP fan (possibly on the23* AGP card itself). I can't deal with that special mode as I don't have24* access to those additional sensor/fans for now (though ultimately, it would25* be possible to add sensor objects for them) so I'm only implementing the26* basic PCI slot control loop27*/2829#include <linux/types.h>30#include <linux/errno.h>31#include <linux/kernel.h>32#include <linux/delay.h>33#include <linux/slab.h>34#include <linux/init.h>35#include <linux/spinlock.h>36#include <linux/wait.h>37#include <linux/kmod.h>38#include <linux/device.h>39#include <linux/platform_device.h>40#include <asm/prom.h>41#include <asm/machdep.h>42#include <asm/io.h>43#include <asm/system.h>44#include <asm/sections.h>45#include <asm/smu.h>4647#include "windfarm.h"48#include "windfarm_pid.h"4950#define VERSION "0.4"5152#undef DEBUG5354#ifdef DEBUG55#define DBG(args...) printk(args)56#else57#define DBG(args...) do { } while(0)58#endif5960/* define this to force CPU overtemp to 74 degree, useful for testing61* the overtemp code62*/63#undef HACKED_OVERTEMP6465/* Controls & sensors */66static struct wf_sensor *sensor_cpu_power;67static struct wf_sensor *sensor_cpu_temp;68static struct wf_sensor *sensor_hd_temp;69static struct wf_sensor *sensor_slots_power;70static struct wf_control *fan_cpu_main;71static struct wf_control *fan_cpu_second;72static struct wf_control *fan_cpu_third;73static struct wf_control *fan_hd;74static struct wf_control *fan_slots;75static struct wf_control *cpufreq_clamp;7677/* Set to kick the control loop into life */78static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started;7980/* Failure handling.. could be nicer */81#define FAILURE_FAN 0x0182#define FAILURE_SENSOR 0x0283#define FAILURE_OVERTEMP 0x048485static unsigned int wf_smu_failure_state;86static int wf_smu_readjust, wf_smu_skipping;8788/*89* ****** CPU Fans Control Loop ******90*91*/929394#define WF_SMU_CPU_FANS_INTERVAL 195#define WF_SMU_CPU_FANS_MAX_HISTORY 169697/* State data used by the cpu fans control loop98*/99struct wf_smu_cpu_fans_state {100int ticks;101s32 cpu_setpoint;102struct wf_cpu_pid_state pid;103};104105static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans;106107108109/*110* ****** Drive Fan Control Loop ******111*112*/113114struct wf_smu_drive_fans_state {115int ticks;116s32 setpoint;117struct wf_pid_state pid;118};119120static struct wf_smu_drive_fans_state *wf_smu_drive_fans;121122/*123* ****** Slots Fan Control Loop ******124*125*/126127struct wf_smu_slots_fans_state {128int ticks;129s32 setpoint;130struct wf_pid_state pid;131};132133static struct wf_smu_slots_fans_state *wf_smu_slots_fans;134135/*136* ***** Implementation *****137*138*/139140141static void wf_smu_create_cpu_fans(void)142{143struct wf_cpu_pid_param pid_param;144const struct smu_sdbp_header *hdr;145struct smu_sdbp_cpupiddata *piddata;146struct smu_sdbp_fvt *fvt;147s32 tmax, tdelta, maxpow, powadj;148149/* First, locate the PID params in SMU SBD */150hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL);151if (hdr == 0) {152printk(KERN_WARNING "windfarm: CPU PID fan config not found "153"max fan speed\n");154goto fail;155}156piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];157158/* Get the FVT params for operating point 0 (the only supported one159* for now) in order to get tmax160*/161hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);162if (hdr) {163fvt = (struct smu_sdbp_fvt *)&hdr[1];164tmax = ((s32)fvt->maxtemp) << 16;165} else166tmax = 0x5e0000; /* 94 degree default */167168/* Alloc & initialize state */169wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state),170GFP_KERNEL);171if (wf_smu_cpu_fans == NULL)172goto fail;173wf_smu_cpu_fans->ticks = 1;174175/* Fill PID params */176pid_param.interval = WF_SMU_CPU_FANS_INTERVAL;177pid_param.history_len = piddata->history_len;178if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) {179printk(KERN_WARNING "windfarm: History size overflow on "180"CPU control loop (%d)\n", piddata->history_len);181pid_param.history_len = WF_CPU_PID_MAX_HISTORY;182}183pid_param.gd = piddata->gd;184pid_param.gp = piddata->gp;185pid_param.gr = piddata->gr / pid_param.history_len;186187tdelta = ((s32)piddata->target_temp_delta) << 16;188maxpow = ((s32)piddata->max_power) << 16;189powadj = ((s32)piddata->power_adj) << 16;190191pid_param.tmax = tmax;192pid_param.ttarget = tmax - tdelta;193pid_param.pmaxadj = maxpow - powadj;194195pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main);196pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main);197198wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param);199200DBG("wf: CPU Fan control initialized.\n");201DBG(" ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n",202FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax),203pid_param.min, pid_param.max);204205return;206207fail:208printk(KERN_WARNING "windfarm: CPU fan config not found\n"209"for this machine model, max fan speed\n");210211if (cpufreq_clamp)212wf_control_set_max(cpufreq_clamp);213if (fan_cpu_main)214wf_control_set_max(fan_cpu_main);215}216217static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)218{219s32 new_setpoint, temp, power;220int rc;221222if (--st->ticks != 0) {223if (wf_smu_readjust)224goto readjust;225return;226}227st->ticks = WF_SMU_CPU_FANS_INTERVAL;228229rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp);230if (rc) {231printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n",232rc);233wf_smu_failure_state |= FAILURE_SENSOR;234return;235}236237rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power);238if (rc) {239printk(KERN_WARNING "windfarm: CPU power sensor error %d\n",240rc);241wf_smu_failure_state |= FAILURE_SENSOR;242return;243}244245DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n",246FIX32TOPRINT(temp), FIX32TOPRINT(power));247248#ifdef HACKED_OVERTEMP249if (temp > 0x4a0000)250wf_smu_failure_state |= FAILURE_OVERTEMP;251#else252if (temp > st->pid.param.tmax)253wf_smu_failure_state |= FAILURE_OVERTEMP;254#endif255new_setpoint = wf_cpu_pid_run(&st->pid, power, temp);256257DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);258259if (st->cpu_setpoint == new_setpoint)260return;261st->cpu_setpoint = new_setpoint;262readjust:263if (fan_cpu_main && wf_smu_failure_state == 0) {264rc = fan_cpu_main->ops->set_value(fan_cpu_main,265st->cpu_setpoint);266if (rc) {267printk(KERN_WARNING "windfarm: CPU main fan"268" error %d\n", rc);269wf_smu_failure_state |= FAILURE_FAN;270}271}272if (fan_cpu_second && wf_smu_failure_state == 0) {273rc = fan_cpu_second->ops->set_value(fan_cpu_second,274st->cpu_setpoint);275if (rc) {276printk(KERN_WARNING "windfarm: CPU second fan"277" error %d\n", rc);278wf_smu_failure_state |= FAILURE_FAN;279}280}281if (fan_cpu_third && wf_smu_failure_state == 0) {282rc = fan_cpu_main->ops->set_value(fan_cpu_third,283st->cpu_setpoint);284if (rc) {285printk(KERN_WARNING "windfarm: CPU third fan"286" error %d\n", rc);287wf_smu_failure_state |= FAILURE_FAN;288}289}290}291292static void wf_smu_create_drive_fans(void)293{294struct wf_pid_param param = {295.interval = 5,296.history_len = 2,297.gd = 0x01e00000,298.gp = 0x00500000,299.gr = 0x00000000,300.itarget = 0x00200000,301};302303/* Alloc & initialize state */304wf_smu_drive_fans = kmalloc(sizeof(struct wf_smu_drive_fans_state),305GFP_KERNEL);306if (wf_smu_drive_fans == NULL) {307printk(KERN_WARNING "windfarm: Memory allocation error"308" max fan speed\n");309goto fail;310}311wf_smu_drive_fans->ticks = 1;312313/* Fill PID params */314param.additive = (fan_hd->type == WF_CONTROL_RPM_FAN);315param.min = fan_hd->ops->get_min(fan_hd);316param.max = fan_hd->ops->get_max(fan_hd);317wf_pid_init(&wf_smu_drive_fans->pid, ¶m);318319DBG("wf: Drive Fan control initialized.\n");320DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n",321FIX32TOPRINT(param.itarget), param.min, param.max);322return;323324fail:325if (fan_hd)326wf_control_set_max(fan_hd);327}328329static void wf_smu_drive_fans_tick(struct wf_smu_drive_fans_state *st)330{331s32 new_setpoint, temp;332int rc;333334if (--st->ticks != 0) {335if (wf_smu_readjust)336goto readjust;337return;338}339st->ticks = st->pid.param.interval;340341rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp);342if (rc) {343printk(KERN_WARNING "windfarm: HD temp sensor error %d\n",344rc);345wf_smu_failure_state |= FAILURE_SENSOR;346return;347}348349DBG("wf_smu: Drive Fans tick ! HD temp: %d.%03d\n",350FIX32TOPRINT(temp));351352if (temp > (st->pid.param.itarget + 0x50000))353wf_smu_failure_state |= FAILURE_OVERTEMP;354355new_setpoint = wf_pid_run(&st->pid, temp);356357DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint);358359if (st->setpoint == new_setpoint)360return;361st->setpoint = new_setpoint;362readjust:363if (fan_hd && wf_smu_failure_state == 0) {364rc = fan_hd->ops->set_value(fan_hd, st->setpoint);365if (rc) {366printk(KERN_WARNING "windfarm: HD fan error %d\n",367rc);368wf_smu_failure_state |= FAILURE_FAN;369}370}371}372373static void wf_smu_create_slots_fans(void)374{375struct wf_pid_param param = {376.interval = 1,377.history_len = 8,378.gd = 0x00000000,379.gp = 0x00000000,380.gr = 0x00020000,381.itarget = 0x00000000382};383384/* Alloc & initialize state */385wf_smu_slots_fans = kmalloc(sizeof(struct wf_smu_slots_fans_state),386GFP_KERNEL);387if (wf_smu_slots_fans == NULL) {388printk(KERN_WARNING "windfarm: Memory allocation error"389" max fan speed\n");390goto fail;391}392wf_smu_slots_fans->ticks = 1;393394/* Fill PID params */395param.additive = (fan_slots->type == WF_CONTROL_RPM_FAN);396param.min = fan_slots->ops->get_min(fan_slots);397param.max = fan_slots->ops->get_max(fan_slots);398wf_pid_init(&wf_smu_slots_fans->pid, ¶m);399400DBG("wf: Slots Fan control initialized.\n");401DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n",402FIX32TOPRINT(param.itarget), param.min, param.max);403return;404405fail:406if (fan_slots)407wf_control_set_max(fan_slots);408}409410static void wf_smu_slots_fans_tick(struct wf_smu_slots_fans_state *st)411{412s32 new_setpoint, power;413int rc;414415if (--st->ticks != 0) {416if (wf_smu_readjust)417goto readjust;418return;419}420st->ticks = st->pid.param.interval;421422rc = sensor_slots_power->ops->get_value(sensor_slots_power, &power);423if (rc) {424printk(KERN_WARNING "windfarm: Slots power sensor error %d\n",425rc);426wf_smu_failure_state |= FAILURE_SENSOR;427return;428}429430DBG("wf_smu: Slots Fans tick ! Slots power: %d.%03d\n",431FIX32TOPRINT(power));432433#if 0 /* Check what makes a good overtemp condition */434if (power > (st->pid.param.itarget + 0x50000))435wf_smu_failure_state |= FAILURE_OVERTEMP;436#endif437438new_setpoint = wf_pid_run(&st->pid, power);439440DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint);441442if (st->setpoint == new_setpoint)443return;444st->setpoint = new_setpoint;445readjust:446if (fan_slots && wf_smu_failure_state == 0) {447rc = fan_slots->ops->set_value(fan_slots, st->setpoint);448if (rc) {449printk(KERN_WARNING "windfarm: Slots fan error %d\n",450rc);451wf_smu_failure_state |= FAILURE_FAN;452}453}454}455456457/*458* ****** Setup / Init / Misc ... ******459*460*/461462static void wf_smu_tick(void)463{464unsigned int last_failure = wf_smu_failure_state;465unsigned int new_failure;466467if (!wf_smu_started) {468DBG("wf: creating control loops !\n");469wf_smu_create_drive_fans();470wf_smu_create_slots_fans();471wf_smu_create_cpu_fans();472wf_smu_started = 1;473}474475/* Skipping ticks */476if (wf_smu_skipping && --wf_smu_skipping)477return;478479wf_smu_failure_state = 0;480if (wf_smu_drive_fans)481wf_smu_drive_fans_tick(wf_smu_drive_fans);482if (wf_smu_slots_fans)483wf_smu_slots_fans_tick(wf_smu_slots_fans);484if (wf_smu_cpu_fans)485wf_smu_cpu_fans_tick(wf_smu_cpu_fans);486487wf_smu_readjust = 0;488new_failure = wf_smu_failure_state & ~last_failure;489490/* If entering failure mode, clamp cpufreq and ramp all491* fans to full speed.492*/493if (wf_smu_failure_state && !last_failure) {494if (cpufreq_clamp)495wf_control_set_max(cpufreq_clamp);496if (fan_cpu_main)497wf_control_set_max(fan_cpu_main);498if (fan_cpu_second)499wf_control_set_max(fan_cpu_second);500if (fan_cpu_third)501wf_control_set_max(fan_cpu_third);502if (fan_hd)503wf_control_set_max(fan_hd);504if (fan_slots)505wf_control_set_max(fan_slots);506}507508/* If leaving failure mode, unclamp cpufreq and readjust509* all fans on next iteration510*/511if (!wf_smu_failure_state && last_failure) {512if (cpufreq_clamp)513wf_control_set_min(cpufreq_clamp);514wf_smu_readjust = 1;515}516517/* Overtemp condition detected, notify and start skipping a couple518* ticks to let the temperature go down519*/520if (new_failure & FAILURE_OVERTEMP) {521wf_set_overtemp();522wf_smu_skipping = 2;523}524525/* We only clear the overtemp condition if overtemp is cleared526* _and_ no other failure is present. Since a sensor error will527* clear the overtemp condition (can't measure temperature) at528* the control loop levels, but we don't want to keep it clear529* here in this case530*/531if (new_failure == 0 && last_failure & FAILURE_OVERTEMP)532wf_clear_overtemp();533}534535536static void wf_smu_new_control(struct wf_control *ct)537{538if (wf_smu_all_controls_ok)539return;540541if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-rear-fan-0")) {542if (wf_get_control(ct) == 0)543fan_cpu_main = ct;544}545546if (fan_cpu_second == NULL && !strcmp(ct->name, "cpu-rear-fan-1")) {547if (wf_get_control(ct) == 0)548fan_cpu_second = ct;549}550551if (fan_cpu_third == NULL && !strcmp(ct->name, "cpu-front-fan-0")) {552if (wf_get_control(ct) == 0)553fan_cpu_third = ct;554}555556if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) {557if (wf_get_control(ct) == 0)558cpufreq_clamp = ct;559}560561if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) {562if (wf_get_control(ct) == 0)563fan_hd = ct;564}565566if (fan_slots == NULL && !strcmp(ct->name, "slots-fan")) {567if (wf_get_control(ct) == 0)568fan_slots = ct;569}570571if (fan_cpu_main && (fan_cpu_second || fan_cpu_third) && fan_hd &&572fan_slots && cpufreq_clamp)573wf_smu_all_controls_ok = 1;574}575576static void wf_smu_new_sensor(struct wf_sensor *sr)577{578if (wf_smu_all_sensors_ok)579return;580581if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) {582if (wf_get_sensor(sr) == 0)583sensor_cpu_power = sr;584}585586if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) {587if (wf_get_sensor(sr) == 0)588sensor_cpu_temp = sr;589}590591if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) {592if (wf_get_sensor(sr) == 0)593sensor_hd_temp = sr;594}595596if (sensor_slots_power == NULL && !strcmp(sr->name, "slots-power")) {597if (wf_get_sensor(sr) == 0)598sensor_slots_power = sr;599}600601if (sensor_cpu_power && sensor_cpu_temp &&602sensor_hd_temp && sensor_slots_power)603wf_smu_all_sensors_ok = 1;604}605606607static int wf_smu_notify(struct notifier_block *self,608unsigned long event, void *data)609{610switch(event) {611case WF_EVENT_NEW_CONTROL:612DBG("wf: new control %s detected\n",613((struct wf_control *)data)->name);614wf_smu_new_control(data);615wf_smu_readjust = 1;616break;617case WF_EVENT_NEW_SENSOR:618DBG("wf: new sensor %s detected\n",619((struct wf_sensor *)data)->name);620wf_smu_new_sensor(data);621break;622case WF_EVENT_TICK:623if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok)624wf_smu_tick();625}626627return 0;628}629630static struct notifier_block wf_smu_events = {631.notifier_call = wf_smu_notify,632};633634static int wf_init_pm(void)635{636printk(KERN_INFO "windfarm: Initializing for Desktop G5 model\n");637638return 0;639}640641static int wf_smu_probe(struct platform_device *ddev)642{643wf_register_client(&wf_smu_events);644645return 0;646}647648static int __devexit wf_smu_remove(struct platform_device *ddev)649{650wf_unregister_client(&wf_smu_events);651652/* XXX We don't have yet a guarantee that our callback isn't653* in progress when returning from wf_unregister_client, so654* we add an arbitrary delay. I'll have to fix that in the core655*/656msleep(1000);657658/* Release all sensors */659/* One more crappy race: I don't think we have any guarantee here660* that the attribute callback won't race with the sensor beeing661* disposed of, and I'm not 100% certain what best way to deal662* with that except by adding locks all over... I'll do that663* eventually but heh, who ever rmmod this module anyway ?664*/665if (sensor_cpu_power)666wf_put_sensor(sensor_cpu_power);667if (sensor_cpu_temp)668wf_put_sensor(sensor_cpu_temp);669if (sensor_hd_temp)670wf_put_sensor(sensor_hd_temp);671if (sensor_slots_power)672wf_put_sensor(sensor_slots_power);673674/* Release all controls */675if (fan_cpu_main)676wf_put_control(fan_cpu_main);677if (fan_cpu_second)678wf_put_control(fan_cpu_second);679if (fan_cpu_third)680wf_put_control(fan_cpu_third);681if (fan_hd)682wf_put_control(fan_hd);683if (fan_slots)684wf_put_control(fan_slots);685if (cpufreq_clamp)686wf_put_control(cpufreq_clamp);687688/* Destroy control loops state structures */689kfree(wf_smu_slots_fans);690kfree(wf_smu_drive_fans);691kfree(wf_smu_cpu_fans);692693return 0;694}695696static struct platform_driver wf_smu_driver = {697.probe = wf_smu_probe,698.remove = __devexit_p(wf_smu_remove),699.driver = {700.name = "windfarm",701.owner = THIS_MODULE,702},703};704705706static int __init wf_smu_init(void)707{708int rc = -ENODEV;709710if (of_machine_is_compatible("PowerMac9,1"))711rc = wf_init_pm();712713if (rc == 0) {714#ifdef MODULE715request_module("windfarm_smu_controls");716request_module("windfarm_smu_sensors");717request_module("windfarm_lm75_sensor");718request_module("windfarm_cpufreq_clamp");719720#endif /* MODULE */721platform_driver_register(&wf_smu_driver);722}723724return rc;725}726727static void __exit wf_smu_exit(void)728{729730platform_driver_unregister(&wf_smu_driver);731}732733734module_init(wf_smu_init);735module_exit(wf_smu_exit);736737MODULE_AUTHOR("Benjamin Herrenschmidt <[email protected]>");738MODULE_DESCRIPTION("Thermal control logic for PowerMac9,1");739MODULE_LICENSE("GPL");740741MODULE_ALIAS("platform:windfarm");742743744