Commit 75722d39 authored by Benjamin Herrenschmidt's avatar Benjamin Herrenschmidt Committed by Paul Mackerras

[PATCH] ppc64: Thermal control for SMU based machines

This adds a new thermal control framework for PowerMac, along with the
implementation for PowerMac8,1, PowerMac8,2 (iMac G5 rev 1 and 2), and
PowerMac9,1 (latest single CPU desktop). In the future, I expect to move
the older G5 thermal control to the new framework as well.
Signed-off-by: default avatarBenjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: default avatarPaul Mackerras <paulus@samba.org>
parent 7d49697e
......@@ -169,6 +169,25 @@ config THERM_PM72
This driver provides thermostat and fan control for the desktop
G5 machines.
config WINDFARM
tristate "New PowerMac thermal control infrastructure"
config WINDFARM_PM81
tristate "Support for thermal management on iMac G5"
depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU
select I2C_PMAC_SMU
help
This driver provides thermal control for the iMacG5
config WINDFARM_PM91
tristate "Support for thermal management on PowerMac9,1"
depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU
select I2C_PMAC_SMU
help
This driver provides thermal control for the PowerMac9,1
which is the recent (SMU based) single CPU desktop G5
config ANSLCD
tristate "Support for ANS LCD display"
depends on ADB_CUDA && PPC_PMAC
......
......@@ -26,3 +26,12 @@ obj-$(CONFIG_ADB_MACIO) += macio-adb.o
obj-$(CONFIG_THERM_PM72) += therm_pm72.o
obj-$(CONFIG_THERM_WINDTUNNEL) += therm_windtunnel.o
obj-$(CONFIG_THERM_ADT746X) += therm_adt746x.o
obj-$(CONFIG_WINDFARM) += windfarm_core.o
obj-$(CONFIG_WINDFARM_PM81) += windfarm_smu_controls.o \
windfarm_smu_sensors.o \
windfarm_lm75_sensor.o windfarm_pid.o \
windfarm_cpufreq_clamp.o windfarm_pm81.o
obj-$(CONFIG_WINDFARM_PM91) += windfarm_smu_controls.o \
windfarm_smu_sensors.o \
windfarm_lm75_sensor.o windfarm_pid.o \
windfarm_cpufreq_clamp.o windfarm_pm91.o
......@@ -590,6 +590,8 @@ static void smu_expose_childs(void *unused)
sprintf(name, "smu-i2c-%02x", *reg);
of_platform_device_create(np, name, &smu->of_dev->dev);
}
if (device_is_compatible(np, "smu-sensors"))
of_platform_device_create(np, "smu-sensors", &smu->of_dev->dev);
}
}
......
/*
* Windfarm PowerMac thermal control.
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*/
#ifndef __WINDFARM_H__
#define __WINDFARM_H__
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/notifier.h>
/* Display a 16.16 fixed point value */
#define FIX32TOPRINT(f) ((f) >> 16),((((f) & 0xffff) * 1000) >> 16)
/*
* Control objects
*/
struct wf_control;
struct wf_control_ops {
int (*set_value)(struct wf_control *ct, s32 val);
int (*get_value)(struct wf_control *ct, s32 *val);
s32 (*get_min)(struct wf_control *ct);
s32 (*get_max)(struct wf_control *ct);
void (*release)(struct wf_control *ct);
struct module *owner;
};
struct wf_control {
struct list_head link;
struct wf_control_ops *ops;
char *name;
int type;
struct kref ref;
};
#define WF_CONTROL_TYPE_GENERIC 0
#define WF_CONTROL_RPM_FAN 1
#define WF_CONTROL_PWM_FAN 2
/* Note about lifetime rules: wf_register_control() will initialize
* the kref and wf_unregister_control will decrement it, thus the
* object creating/disposing a given control shouldn't assume it
* still exists after wf_unregister_control has been called.
* wf_find_control will inc the refcount for you
*/
extern int wf_register_control(struct wf_control *ct);
extern void wf_unregister_control(struct wf_control *ct);
extern struct wf_control * wf_find_control(const char *name);
extern int wf_get_control(struct wf_control *ct);
extern void wf_put_control(struct wf_control *ct);
static inline int wf_control_set_max(struct wf_control *ct)
{
s32 vmax = ct->ops->get_max(ct);
return ct->ops->set_value(ct, vmax);
}
static inline int wf_control_set_min(struct wf_control *ct)
{
s32 vmin = ct->ops->get_min(ct);
return ct->ops->set_value(ct, vmin);
}
/*
* Sensor objects
*/
struct wf_sensor;
struct wf_sensor_ops {
int (*get_value)(struct wf_sensor *sr, s32 *val);
void (*release)(struct wf_sensor *sr);
struct module *owner;
};
struct wf_sensor {
struct list_head link;
struct wf_sensor_ops *ops;
char *name;
struct kref ref;
};
/* Same lifetime rules as controls */
extern int wf_register_sensor(struct wf_sensor *sr);
extern void wf_unregister_sensor(struct wf_sensor *sr);
extern struct wf_sensor * wf_find_sensor(const char *name);
extern int wf_get_sensor(struct wf_sensor *sr);
extern void wf_put_sensor(struct wf_sensor *sr);
/* For use by clients. Note that we are a bit racy here since
* notifier_block doesn't have a module owner field. I may fix
* it one day ...
*
* LOCKING NOTE !
*
* All "events" except WF_EVENT_TICK are called with an internal mutex
* held which will deadlock if you call basically any core routine.
* So don't ! Just take note of the event and do your actual operations
* from the ticker.
*
*/
extern int wf_register_client(struct notifier_block *nb);
extern int wf_unregister_client(struct notifier_block *nb);
/* Overtemp conditions. Those are refcounted */
extern void wf_set_overtemp(void);
extern void wf_clear_overtemp(void);
extern int wf_is_overtemp(void);
#define WF_EVENT_NEW_CONTROL 0 /* param is wf_control * */
#define WF_EVENT_NEW_SENSOR 1 /* param is wf_sensor * */
#define WF_EVENT_OVERTEMP 2 /* no param */
#define WF_EVENT_NORMALTEMP 3 /* overtemp condition cleared */
#define WF_EVENT_TICK 4 /* 1 second tick */
/* Note: If that driver gets more broad use, we could replace the
* simplistic overtemp bits with "environmental conditions". That
* could then be used to also notify of things like fan failure,
* case open, battery conditions, ...
*/
#endif /* __WINDFARM_H__ */
/*
* Windfarm PowerMac thermal control. Core
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*
* This core code tracks the list of sensors & controls, register
* clients, and holds the kernel thread used for control.
*
* TODO:
*
* Add some information about sensor/control type and data format to
* sensors/controls, and have the sysfs attribute stuff be moved
* generically here instead of hard coded in the platform specific
* driver as it us currently
*
* This however requires solving some annoying lifetime issues with
* sysfs which doesn't seem to have lifetime rules for struct attribute,
* I may have to create full features kobjects for every sensor/control
* instead which is a bit of an overkill imho
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/smp_lock.h>
#include <linux/kthread.h>
#include <linux/jiffies.h>
#include <linux/reboot.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include "windfarm.h"
#define VERSION "0.2"
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
static LIST_HEAD(wf_controls);
static LIST_HEAD(wf_sensors);
static DECLARE_MUTEX(wf_lock);
static struct notifier_block *wf_client_list;
static int wf_client_count;
static unsigned int wf_overtemp;
static unsigned int wf_overtemp_counter;
struct task_struct *wf_thread;
/*
* Utilities & tick thread
*/
static inline void wf_notify(int event, void *param)
{
notifier_call_chain(&wf_client_list, event, param);
}
int wf_critical_overtemp(void)
{
static char * critical_overtemp_path = "/sbin/critical_overtemp";
char *argv[] = { critical_overtemp_path, NULL };
static char *envp[] = { "HOME=/",
"TERM=linux",
"PATH=/sbin:/usr/sbin:/bin:/usr/bin",
NULL };
return call_usermodehelper(critical_overtemp_path, argv, envp, 0);
}
EXPORT_SYMBOL_GPL(wf_critical_overtemp);
static int wf_thread_func(void *data)
{
unsigned long next, delay;
next = jiffies;
DBG("wf: thread started\n");
while(!kthread_should_stop()) {
try_to_freeze();
if (time_after_eq(jiffies, next)) {
wf_notify(WF_EVENT_TICK, NULL);
if (wf_overtemp) {
wf_overtemp_counter++;
/* 10 seconds overtemp, notify userland */
if (wf_overtemp_counter > 10)
wf_critical_overtemp();
/* 30 seconds, shutdown */
if (wf_overtemp_counter > 30) {
printk(KERN_ERR "windfarm: Overtemp "
"for more than 30"
" seconds, shutting down\n");
machine_power_off();
}
}
next += HZ;
}
delay = next - jiffies;
if (delay <= HZ)
schedule_timeout_interruptible(delay);
/* there should be no signal, but oh well */
if (signal_pending(current)) {
printk(KERN_WARNING "windfarm: thread got sigl !\n");
break;
}
}
DBG("wf: thread stopped\n");
return 0;
}
static void wf_start_thread(void)
{
wf_thread = kthread_run(wf_thread_func, NULL, "kwindfarm");
if (IS_ERR(wf_thread)) {
printk(KERN_ERR "windfarm: failed to create thread,err %ld\n",
PTR_ERR(wf_thread));
wf_thread = NULL;
}
}
static void wf_stop_thread(void)
{
if (wf_thread)
kthread_stop(wf_thread);
wf_thread = NULL;
}
/*
* Controls
*/
static void wf_control_release(struct kref *kref)
{
struct wf_control *ct = container_of(kref, struct wf_control, ref);
DBG("wf: Deleting control %s\n", ct->name);
if (ct->ops && ct->ops->release)
ct->ops->release(ct);
else
kfree(ct);
}
int wf_register_control(struct wf_control *new_ct)
{
struct wf_control *ct;
down(&wf_lock);
list_for_each_entry(ct, &wf_controls, link) {
if (!strcmp(ct->name, new_ct->name)) {
printk(KERN_WARNING "windfarm: trying to register"
" duplicate control %s\n", ct->name);
up(&wf_lock);
return -EEXIST;
}
}
kref_init(&new_ct->ref);
list_add(&new_ct->link, &wf_controls);
DBG("wf: Registered control %s\n", new_ct->name);
wf_notify(WF_EVENT_NEW_CONTROL, new_ct);
up(&wf_lock);
return 0;
}
EXPORT_SYMBOL_GPL(wf_register_control);
void wf_unregister_control(struct wf_control *ct)
{
down(&wf_lock);
list_del(&ct->link);
up(&wf_lock);
DBG("wf: Unregistered control %s\n", ct->name);
kref_put(&ct->ref, wf_control_release);
}
EXPORT_SYMBOL_GPL(wf_unregister_control);
struct wf_control * wf_find_control(const char *name)
{
struct wf_control *ct;
down(&wf_lock);
list_for_each_entry(ct, &wf_controls, link) {
if (!strcmp(ct->name, name)) {
if (wf_get_control(ct))
ct = NULL;
up(&wf_lock);
return ct;
}
}
up(&wf_lock);
return NULL;
}
EXPORT_SYMBOL_GPL(wf_find_control);
int wf_get_control(struct wf_control *ct)
{
if (!try_module_get(ct->ops->owner))
return -ENODEV;
kref_get(&ct->ref);
return 0;
}
EXPORT_SYMBOL_GPL(wf_get_control);
void wf_put_control(struct wf_control *ct)
{
struct module *mod = ct->ops->owner;
kref_put(&ct->ref, wf_control_release);
module_put(mod);
}
EXPORT_SYMBOL_GPL(wf_put_control);
/*
* Sensors
*/
static void wf_sensor_release(struct kref *kref)
{
struct wf_sensor *sr = container_of(kref, struct wf_sensor, ref);
DBG("wf: Deleting sensor %s\n", sr->name);
if (sr->ops && sr->ops->release)
sr->ops->release(sr);
else
kfree(sr);
}
int wf_register_sensor(struct wf_sensor *new_sr)
{
struct wf_sensor *sr;
down(&wf_lock);
list_for_each_entry(sr, &wf_sensors, link) {
if (!strcmp(sr->name, new_sr->name)) {
printk(KERN_WARNING "windfarm: trying to register"
" duplicate sensor %s\n", sr->name);
up(&wf_lock);
return -EEXIST;
}
}
kref_init(&new_sr->ref);
list_add(&new_sr->link, &wf_sensors);
DBG("wf: Registered sensor %s\n", new_sr->name);
wf_notify(WF_EVENT_NEW_SENSOR, new_sr);
up(&wf_lock);
return 0;
}
EXPORT_SYMBOL_GPL(wf_register_sensor);
void wf_unregister_sensor(struct wf_sensor *sr)
{
down(&wf_lock);
list_del(&sr->link);
up(&wf_lock);
DBG("wf: Unregistered sensor %s\n", sr->name);
wf_put_sensor(sr);
}
EXPORT_SYMBOL_GPL(wf_unregister_sensor);
struct wf_sensor * wf_find_sensor(const char *name)
{
struct wf_sensor *sr;
down(&wf_lock);
list_for_each_entry(sr, &wf_sensors, link) {
if (!strcmp(sr->name, name)) {
if (wf_get_sensor(sr))
sr = NULL;
up(&wf_lock);
return sr;
}
}
up(&wf_lock);
return NULL;
}
EXPORT_SYMBOL_GPL(wf_find_sensor);
int wf_get_sensor(struct wf_sensor *sr)
{
if (!try_module_get(sr->ops->owner))
return -ENODEV;
kref_get(&sr->ref);
return 0;
}
EXPORT_SYMBOL_GPL(wf_get_sensor);
void wf_put_sensor(struct wf_sensor *sr)
{
struct module *mod = sr->ops->owner;
kref_put(&sr->ref, wf_sensor_release);
module_put(mod);
}
EXPORT_SYMBOL_GPL(wf_put_sensor);
/*
* Client & notification
*/
int wf_register_client(struct notifier_block *nb)
{
int rc;
struct wf_control *ct;
struct wf_sensor *sr;
down(&wf_lock);
rc = notifier_chain_register(&wf_client_list, nb);
if (rc != 0)
goto bail;
wf_client_count++;
list_for_each_entry(ct, &wf_controls, link)
wf_notify(WF_EVENT_NEW_CONTROL, ct);
list_for_each_entry(sr, &wf_sensors, link)
wf_notify(WF_EVENT_NEW_SENSOR, sr);
if (wf_client_count == 1)
wf_start_thread();
bail:
up(&wf_lock);
return rc;
}
EXPORT_SYMBOL_GPL(wf_register_client);
int wf_unregister_client(struct notifier_block *nb)
{
down(&wf_lock);
notifier_chain_unregister(&wf_client_list, nb);
wf_client_count++;
if (wf_client_count == 0)
wf_stop_thread();
up(&wf_lock);
return 0;
}
EXPORT_SYMBOL_GPL(wf_unregister_client);
void wf_set_overtemp(void)
{
down(&wf_lock);
wf_overtemp++;
if (wf_overtemp == 1) {
printk(KERN_WARNING "windfarm: Overtemp condition detected !\n");
wf_overtemp_counter = 0;
wf_notify(WF_EVENT_OVERTEMP, NULL);
}
up(&wf_lock);
}
EXPORT_SYMBOL_GPL(wf_set_overtemp);
void wf_clear_overtemp(void)
{
down(&wf_lock);
WARN_ON(wf_overtemp == 0);
if (wf_overtemp == 0) {
up(&wf_lock);
return;
}
wf_overtemp--;
if (wf_overtemp == 0) {
printk(KERN_WARNING "windfarm: Overtemp condition cleared !\n");
wf_notify(WF_EVENT_NORMALTEMP, NULL);
}
up(&wf_lock);
}
EXPORT_SYMBOL_GPL(wf_clear_overtemp);
int wf_is_overtemp(void)
{
return (wf_overtemp != 0);
}
EXPORT_SYMBOL_GPL(wf_is_overtemp);
static struct platform_device wf_platform_device = {
.name = "windfarm",
};
static int __init windfarm_core_init(void)
{
DBG("wf: core loaded\n");
platform_device_register(&wf_platform_device);
return 0;
}
static void __exit windfarm_core_exit(void)
{
BUG_ON(wf_client_count != 0);
DBG("wf: core unloaded\n");
platform_device_unregister(&wf_platform_device);
}
module_init(windfarm_core_init);
module_exit(windfarm_core_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("Core component of PowerMac thermal control");
MODULE_LICENSE("GPL");
#include <linux/config.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/cpufreq.h>
#include "windfarm.h"
#define VERSION "0.3"
static int clamped;
static struct wf_control *clamp_control;
static int clamp_notifier_call(struct notifier_block *self,
unsigned long event, void *data)
{
struct cpufreq_policy *p = data;
unsigned long max_freq;
if (event != CPUFREQ_ADJUST)
return 0;
max_freq = clamped ? (p->cpuinfo.min_freq) : (p->cpuinfo.max_freq);
cpufreq_verify_within_limits(p, 0, max_freq);
return 0;
}
static struct notifier_block clamp_notifier = {
.notifier_call = clamp_notifier_call,
};
static int clamp_set(struct wf_control *ct, s32 value)
{
if (value)
printk(KERN_INFO "windfarm: Clamping CPU frequency to "
"minimum !\n");
else
printk(KERN_INFO "windfarm: CPU frequency unclamped !\n");
clamped = value;
cpufreq_update_policy(0);
return 0;
}
static int clamp_get(struct wf_control *ct, s32 *value)
{
*value = clamped;
return 0;
}
static s32 clamp_min(struct wf_control *ct)
{
return 0;
}
static s32 clamp_max(struct wf_control *ct)
{
return 1;
}
static struct wf_control_ops clamp_ops = {
.set_value = clamp_set,
.get_value = clamp_get,
.get_min = clamp_min,
.get_max = clamp_max,
.owner = THIS_MODULE,
};
static int __init wf_cpufreq_clamp_init(void)
{
struct wf_control *clamp;
clamp = kmalloc(sizeof(struct wf_control), GFP_KERNEL);
if (clamp == NULL)
return -ENOMEM;
cpufreq_register_notifier(&clamp_notifier, CPUFREQ_POLICY_NOTIFIER);
clamp->ops = &clamp_ops;
clamp->name = "cpufreq-clamp";
if (wf_register_control(clamp))
goto fail;
clamp_control = clamp;
return 0;
fail:
kfree(clamp);
return -ENODEV;
}
static void __exit wf_cpufreq_clamp_exit(void)
{
if (clamp_control)
wf_unregister_control(clamp_control);
}
module_init(wf_cpufreq_clamp_init);
module_exit(wf_cpufreq_clamp_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("CPU frequency clamp for PowerMacs thermal control");
MODULE_LICENSE("GPL");
/*
* Windfarm PowerMac thermal control. LM75 sensor
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/sections.h>
#include "windfarm.h"
#define VERSION "0.1"
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
struct wf_lm75_sensor {
int ds1775 : 1;
int inited : 1;
struct i2c_client i2c;
struct wf_sensor sens;
};
#define wf_to_lm75(c) container_of(c, struct wf_lm75_sensor, sens)
#define i2c_to_lm75(c) container_of(c, struct wf_lm75_sensor, i2c)
static int wf_lm75_attach(struct i2c_adapter *adapter);
static int wf_lm75_detach(struct i2c_client *client);
static struct i2c_driver wf_lm75_driver = {
.owner = THIS_MODULE,
.name = "wf_lm75",
.flags = I2C_DF_NOTIFY,
.attach_adapter = wf_lm75_attach,
.detach_client = wf_lm75_detach,
};
static int wf_lm75_get(struct wf_sensor *sr, s32 *value)
{
struct wf_lm75_sensor *lm = wf_to_lm75(sr);
s32 data;
if (lm->i2c.adapter == NULL)
return -ENODEV;
/* Init chip if necessary */
if (!lm->inited) {
u8 cfg_new, cfg = (u8)i2c_smbus_read_byte_data(&lm->i2c, 1);
DBG("wf_lm75: Initializing %s, cfg was: %02x\n",
sr->name, cfg);
/* clear shutdown bit, keep other settings as left by
* the firmware for now
*/
cfg_new = cfg & ~0x01;
i2c_smbus_write_byte_data(&lm->i2c, 1, cfg_new);
lm->inited = 1;
/* If we just powered it up, let's wait 200 ms */
msleep(200);
}
/* Read temperature register */
data = (s32)le16_to_cpu(i2c_smbus_read_word_data(&lm->i2c, 0));
data <<= 8;
*value = data;
return 0;
}
static void wf_lm75_release(struct wf_sensor *sr)
{
struct wf_lm75_sensor *lm = wf_to_lm75(sr);
/* check if client is registered and detach from i2c */
if (lm->i2c.adapter) {
i2c_detach_client(&lm->i2c);
lm->i2c.adapter = NULL;
}
kfree(lm);
}
static struct wf_sensor_ops wf_lm75_ops = {
.get_value = wf_lm75_get,
.release = wf_lm75_release,
.owner = THIS_MODULE,
};
static struct wf_lm75_sensor *wf_lm75_create(struct i2c_adapter *adapter,
u8 addr, int ds1775,
const char *loc)
{
struct wf_lm75_sensor *lm;
DBG("wf_lm75: creating %s device at address 0x%02x\n",
ds1775 ? "ds1775" : "lm75", addr);
lm = kmalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL);
if (lm == NULL)
return NULL;
memset(lm, 0, sizeof(struct wf_lm75_sensor));
/* Usual rant about sensor names not beeing very consistent in
* the device-tree, oh well ...
* Add more entries below as you deal with more setups
*/
if (!strcmp(loc, "Hard drive") || !strcmp(loc, "DRIVE BAY"))
lm->sens.name = "hd-temp";
else
goto fail;
lm->inited = 0;
lm->sens.ops = &wf_lm75_ops;
lm->ds1775 = ds1775;
lm->i2c.addr = (addr >> 1) & 0x7f;
lm->i2c.adapter = adapter;
lm->i2c.driver = &wf_lm75_driver;
strncpy(lm->i2c.name, lm->sens.name, I2C_NAME_SIZE-1);
if (i2c_attach_client(&lm->i2c)) {
printk(KERN_ERR "windfarm: failed to attach %s %s to i2c\n",
ds1775 ? "ds1775" : "lm75", lm->i2c.name);
goto fail;
}
if (wf_register_sensor(&lm->sens)) {
i2c_detach_client(&lm->i2c);
goto fail;
}
return lm;
fail:
kfree(lm);
return NULL;
}
static int wf_lm75_attach(struct i2c_adapter *adapter)
{
u8 bus_id;
struct device_node *smu, *bus, *dev;
/* We currently only deal with LM75's hanging off the SMU
* i2c busses. If we extend that driver to other/older
* machines, we should split this function into SMU-i2c,
* keywest-i2c, PMU-i2c, ...
*/
DBG("wf_lm75: adapter %s detected\n", adapter->name);
if (strncmp(adapter->name, "smu-i2c-", 8) != 0)
return 0;
smu = of_find_node_by_type(NULL, "smu");
if (smu == NULL)
return 0;
/* Look for the bus in the device-tree */
bus_id = (u8)simple_strtoul(adapter->name + 8, NULL, 16);
DBG("wf_lm75: bus ID is %x\n", bus_id);
/* Look for sensors subdir */
for (bus = NULL;
(bus = of_get_next_child(smu, bus)) != NULL;) {
u32 *reg;
if (strcmp(bus->name, "i2c"))
continue;
reg = (u32 *)get_property(bus, "reg", NULL);
if (reg == NULL)
continue;
if (bus_id == *reg)
break;
}
of_node_put(smu);
if (bus == NULL) {
printk(KERN_WARNING "windfarm: SMU i2c bus 0x%x not found"
" in device-tree !\n", bus_id);
return 0;
}
DBG("wf_lm75: bus found, looking for device...\n");
/* Now look for lm75(s) in there */
for (dev = NULL;
(dev = of_get_next_child(bus, dev)) != NULL;) {
const char *loc =
get_property(dev, "hwsensor-location", NULL);
u32 *reg = (u32 *)get_property(dev, "reg", NULL);
DBG(" dev: %s... (loc: %p, reg: %p)\n", dev->name, loc, reg);
if (loc == NULL || reg == NULL)
continue;
/* real lm75 */
if (device_is_compatible(dev, "lm75"))
wf_lm75_create(adapter, *reg, 0, loc);
/* ds1775 (compatible, better resolution */
else if (device_is_compatible(dev, "ds1775"))
wf_lm75_create(adapter, *reg, 1, loc);
}
of_node_put(bus);
return 0;
}
static int wf_lm75_detach(struct i2c_client *client)
{
struct wf_lm75_sensor *lm = i2c_to_lm75(client);
DBG("wf_lm75: i2c detatch called for %s\n", lm->sens.name);
/* Mark client detached */
lm->i2c.adapter = NULL;
/* release sensor */
wf_unregister_sensor(&lm->sens);
return 0;
}
static int __init wf_lm75_sensor_init(void)
{
int rc;
rc = i2c_add_driver(&wf_lm75_driver);
if (rc < 0)
return rc;
return 0;
}
static void __exit wf_lm75_sensor_exit(void)
{
i2c_del_driver(&wf_lm75_driver);
}
module_init(wf_lm75_sensor_init);
module_exit(wf_lm75_sensor_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("LM75 sensor objects for PowerMacs thermal control");
MODULE_LICENSE("GPL");
/*
* Windfarm PowerMac thermal control. Generic PID helpers
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/module.h>
#include "windfarm_pid.h"
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
void wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param)
{
memset(st, 0, sizeof(struct wf_pid_state));
st->param = *param;
st->first = 1;
}
EXPORT_SYMBOL_GPL(wf_pid_init);
s32 wf_pid_run(struct wf_pid_state *st, s32 new_sample)
{
s64 error, integ, deriv;
s32 target;
int i, hlen = st->param.history_len;
/* Calculate error term */
error = new_sample - st->param.itarget;
/* Get samples into our history buffer */
if (st->first) {
for (i = 0; i < hlen; i++) {
st->samples[i] = new_sample;
st->errors[i] = error;
}
st->first = 0;
st->index = 0;
} else {
st->index = (st->index + 1) % hlen;
st->samples[st->index] = new_sample;
st->errors[st->index] = error;
}
/* Calculate integral term */
for (i = 0, integ = 0; i < hlen; i++)
integ += st->errors[(st->index + hlen - i) % hlen];
integ *= st->param.interval;
/* Calculate derivative term */
deriv = st->errors[st->index] -
st->errors[(st->index + hlen - 1) % hlen];
deriv /= st->param.interval;
/* Calculate target */
target = (s32)((integ * (s64)st->param.gr + deriv * (s64)st->param.gd +
error * (s64)st->param.gp) >> 36);
if (st->param.additive)
target += st->target;
target = max(target, st->param.min);
target = min(target, st->param.max);
st->target = target;
return st->target;
}
EXPORT_SYMBOL_GPL(wf_pid_run);
void wf_cpu_pid_init(struct wf_cpu_pid_state *st,
struct wf_cpu_pid_param *param)
{
memset(st, 0, sizeof(struct wf_cpu_pid_state));
st->param = *param;
st->first = 1;
}
EXPORT_SYMBOL_GPL(wf_cpu_pid_init);
s32 wf_cpu_pid_run(struct wf_cpu_pid_state *st, s32 new_power, s32 new_temp)
{
s64 error, integ, deriv, prop;
s32 target, sval, adj;
int i, hlen = st->param.history_len;
/* Calculate error term */
error = st->param.pmaxadj - new_power;
/* Get samples into our history buffer */
if (st->first) {
for (i = 0; i < hlen; i++) {
st->powers[i] = new_power;
st->errors[i] = error;
}
st->temps[0] = st->temps[1] = new_temp;
st->first = 0;
st->index = st->tindex = 0;
} else {
st->index = (st->index + 1) % hlen;
st->powers[st->index] = new_power;
st->errors[st->index] = error;
st->tindex = (st->tindex + 1) % 2;
st->temps[st->tindex] = new_temp;
}
/* Calculate integral term */
for (i = 0, integ = 0; i < hlen; i++)
integ += st->errors[(st->index + hlen - i) % hlen];
integ *= st->param.interval;
integ *= st->param.gr;
sval = st->param.tmax - ((integ >> 20) & 0xffffffff);
adj = min(st->param.ttarget, sval);
DBG("integ: %lx, sval: %lx, adj: %lx\n", integ, sval, adj);
/* Calculate derivative term */
deriv = st->temps[st->tindex] -
st->temps[(st->tindex + 2 - 1) % 2];
deriv /= st->param.interval;
deriv *= st->param.gd;
/* Calculate proportional term */
prop = (new_temp - adj);
prop *= st->param.gp;
DBG("deriv: %lx, prop: %lx\n", deriv, prop);
/* Calculate target */
target = st->target + (s32)((deriv + prop) >> 36);
target = max(target, st->param.min);
target = min(target, st->param.max);
st->target = target;
return st->target;
}
EXPORT_SYMBOL_GPL(wf_cpu_pid_run);
/*
* Windfarm PowerMac thermal control. Generic PID helpers
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*
* This is a pair of generic PID helpers that can be used by
* control loops. One is the basic PID implementation, the
* other one is more specifically tailored to the loops used
* for CPU control with 2 input sample types (temp and power)
*/
/*
* *** Simple PID ***
*/
#define WF_PID_MAX_HISTORY 32
/* This parameter array is passed to the PID algorithm. Currently,
* we don't support changing parameters on the fly as it's not needed
* but could be implemented (with necessary adjustment of the history
* buffer
*/
struct wf_pid_param {
int interval; /* Interval between samples in seconds */
int history_len; /* Size of history buffer */
int additive; /* 1: target relative to previous value */
s32 gd, gp, gr; /* PID gains */
s32 itarget; /* PID input target */
s32 min,max; /* min and max target values */
};
struct wf_pid_state {
int first; /* first run of the loop */
int index; /* index of current sample */
s32 target; /* current target value */
s32 samples[WF_PID_MAX_HISTORY]; /* samples history buffer */
s32 errors[WF_PID_MAX_HISTORY]; /* error history buffer */
struct wf_pid_param param;
};
extern void wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param);
extern s32 wf_pid_run(struct wf_pid_state *st, s32 sample);
/*
* *** CPU PID ***
*/
#define WF_CPU_PID_MAX_HISTORY 32
/* This parameter array is passed to the CPU PID algorithm. Currently,
* we don't support changing parameters on the fly as it's not needed
* but could be implemented (with necessary adjustment of the history
* buffer
*/
struct wf_cpu_pid_param {
int interval; /* Interval between samples in seconds */
int history_len; /* Size of history buffer */
s32 gd, gp, gr; /* PID gains */
s32 pmaxadj; /* PID max power adjust */
s32 ttarget; /* PID input target */
s32 tmax; /* PID input max */
s32 min,max; /* min and max target values */
};
struct wf_cpu_pid_state {
int first; /* first run of the loop */
int index; /* index of current power */
int tindex; /* index of current temp */
s32 target; /* current target value */
s32 powers[WF_PID_MAX_HISTORY]; /* power history buffer */
s32 errors[WF_PID_MAX_HISTORY]; /* error history buffer */
s32 temps[2]; /* temp. history buffer */
struct wf_cpu_pid_param param;
};
extern void wf_cpu_pid_init(struct wf_cpu_pid_state *st,
struct wf_cpu_pid_param *param);
extern s32 wf_cpu_pid_run(struct wf_cpu_pid_state *st, s32 power, s32 temp);
/*
* Windfarm PowerMac thermal control. iMac G5
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*
* The algorithm used is the PID control algorithm, used the same
* way the published Darwin code does, using the same values that
* are present in the Darwin 8.2 snapshot property lists (note however
* that none of the code has been re-used, it's a complete re-implementation
*
* The various control loops found in Darwin config file are:
*
* PowerMac8,1 and PowerMac8,2
* ===========================
*
* System Fans control loop. Different based on models. In addition to the
* usual PID algorithm, the control loop gets 2 additional pairs of linear
* scaling factors (scale/offsets) expressed as 4.12 fixed point values
* signed offset, unsigned scale)
*
* The targets are modified such as:
* - the linked control (second control) gets the target value as-is
* (typically the drive fan)
* - the main control (first control) gets the target value scaled with
* the first pair of factors, and is then modified as below
* - the value of the target of the CPU Fan control loop is retreived,
* scaled with the second pair of factors, and the max of that and
* the scaled target is applied to the main control.
*
* # model_id: 2
* controls : system-fan, drive-bay-fan
* sensors : hd-temp
* PID params : G_d = 0x15400000
* G_p = 0x00200000
* G_r = 0x000002fd
* History = 2 entries
* Input target = 0x3a0000
* Interval = 5s
* linear-factors : offset = 0xff38 scale = 0x0ccd
* offset = 0x0208 scale = 0x07ae
*
* # model_id: 3
* controls : system-fan, drive-bay-fan
* sensors : hd-temp
* PID params : G_d = 0x08e00000
* G_p = 0x00566666
* G_r = 0x0000072b
* History = 2 entries
* Input target = 0x350000
* Interval = 5s
* linear-factors : offset = 0xff38 scale = 0x0ccd
* offset = 0x0000 scale = 0x0000
*
* # model_id: 5
* controls : system-fan
* sensors : hd-temp
* PID params : G_d = 0x15400000
* G_p = 0x00233333
* G_r = 0x000002fd
* History = 2 entries
* Input target = 0x3a0000
* Interval = 5s
* linear-factors : offset = 0x0000 scale = 0x1000
* offset = 0x0091 scale = 0x0bae
*
* CPU Fan control loop. The loop is identical for all models. it
* has an additional pair of scaling factor. This is used to scale the
* systems fan control loop target result (the one before it gets scaled
* by the System Fans control loop itself). Then, the max value of the
* calculated target value and system fan value is sent to the fans
*
* controls : cpu-fan
* sensors : cpu-temp cpu-power
* PID params : From SMU sdb partition
* linear-factors : offset = 0xfb50 scale = 0x1000
*
* CPU Slew control loop. Not implemented. The cpufreq driver in linux is
* completely separate for now, though we could find a way to link it, either
* as a client reacting to overtemp notifications, or directling monitoring
* the CPU temperature
*
* WARNING ! The CPU control loop requires the CPU tmax for the current
* operating point. However, we currently are completely separated from
* the cpufreq driver and thus do not know what the current operating
* point is. Fortunately, we also do not have any hardware supporting anything
* but operating point 0 at the moment, thus we just peek that value directly
* from the SDB partition. If we ever end up with actually slewing the system
* clock and thus changing operating points, we'll have to find a way to
* communicate with the CPU freq driver;
*
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/kmod.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/sections.h>
#include <asm/smu.h>
#include "windfarm.h"
#include "windfarm_pid.h"
#define VERSION "0.4"
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
/* define this to force CPU overtemp to 74 degree, useful for testing
* the overtemp code
*/
#undef HACKED_OVERTEMP
static int wf_smu_mach_model; /* machine model id */
static struct device *wf_smu_dev;
/* Controls & sensors */
static struct wf_sensor *sensor_cpu_power;
static struct wf_sensor *sensor_cpu_temp;
static struct wf_sensor *sensor_hd_temp;
static struct wf_control *fan_cpu_main;
static struct wf_control *fan_hd;
static struct wf_control *fan_system;
static struct wf_control *cpufreq_clamp;
/* Set to kick the control loop into life */
static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started;
/* Failure handling.. could be nicer */
#define FAILURE_FAN 0x01
#define FAILURE_SENSOR 0x02
#define FAILURE_OVERTEMP 0x04
static unsigned int wf_smu_failure_state;
static int wf_smu_readjust, wf_smu_skipping;
/*
* ****** System Fans Control Loop ******
*
*/
/* Parameters for the System Fans control loop. Parameters
* not in this table such as interval, history size, ...
* are common to all versions and thus hard coded for now.
*/
struct wf_smu_sys_fans_param {
int model_id;
s32 itarget;
s32 gd, gp, gr;
s16 offset0;
u16 scale0;
s16 offset1;
u16 scale1;
};
#define WF_SMU_SYS_FANS_INTERVAL 5
#define WF_SMU_SYS_FANS_HISTORY_SIZE 2
/* State data used by the system fans control loop
*/
struct wf_smu_sys_fans_state {
int ticks;
s32 sys_setpoint;
s32 hd_setpoint;
s16 offset0;
u16 scale0;
s16 offset1;
u16 scale1;
struct wf_pid_state pid;
};
/*
* Configs for SMU Sytem Fan control loop
*/
static struct wf_smu_sys_fans_param wf_smu_sys_all_params[] = {
/* Model ID 2 */
{
.model_id = 2,
.itarget = 0x3a0000,
.gd = 0x15400000,
.gp = 0x00200000,
.gr = 0x000002fd,
.offset0 = 0xff38,
.scale0 = 0x0ccd,
.offset1 = 0x0208,
.scale1 = 0x07ae,
},
/* Model ID 3 */
{
.model_id = 2,
.itarget = 0x350000,
.gd = 0x08e00000,
.gp = 0x00566666,
.gr = 0x0000072b,
.offset0 = 0xff38,
.scale0 = 0x0ccd,
.offset1 = 0x0000,
.scale1 = 0x0000,
},
/* Model ID 5 */
{
.model_id = 2,
.itarget = 0x3a0000,
.gd = 0x15400000,
.gp = 0x00233333,
.gr = 0x000002fd,
.offset0 = 0x0000,
.scale0 = 0x1000,
.offset1 = 0x0091,
.scale1 = 0x0bae,
},
};
#define WF_SMU_SYS_FANS_NUM_CONFIGS ARRAY_SIZE(wf_smu_sys_all_params)
static struct wf_smu_sys_fans_state *wf_smu_sys_fans;
/*
* ****** CPU Fans Control Loop ******
*
*/
#define WF_SMU_CPU_FANS_INTERVAL 1
#define WF_SMU_CPU_FANS_MAX_HISTORY 16
#define WF_SMU_CPU_FANS_SIBLING_SCALE 0x00001000
#define WF_SMU_CPU_FANS_SIBLING_OFFSET 0xfffffb50
/* State data used by the cpu fans control loop
*/
struct wf_smu_cpu_fans_state {
int ticks;
s32 cpu_setpoint;
s32 scale;
s32 offset;
struct wf_cpu_pid_state pid;
};
static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans;
/*
* ***** Implementation *****
*
*/
static void wf_smu_create_sys_fans(void)
{
struct wf_smu_sys_fans_param *param = NULL;
struct wf_pid_param pid_param;
int i;
/* First, locate the params for this model */
for (i = 0; i < WF_SMU_SYS_FANS_NUM_CONFIGS; i++)
if (wf_smu_sys_all_params[i].model_id == wf_smu_mach_model) {
param = &wf_smu_sys_all_params[i];
break;
}
/* No params found, put fans to max */
if (param == NULL) {
printk(KERN_WARNING "windfarm: System fan config not found "
"for this machine model, max fan speed\n");
goto fail;
}
/* Alloc & initialize state */
wf_smu_sys_fans = kmalloc(sizeof(struct wf_smu_sys_fans_state),
GFP_KERNEL);
if (wf_smu_sys_fans == NULL) {
printk(KERN_WARNING "windfarm: Memory allocation error"
" max fan speed\n");
goto fail;
}
wf_smu_sys_fans->ticks = 1;
wf_smu_sys_fans->scale0 = param->scale0;
wf_smu_sys_fans->offset0 = param->offset0;
wf_smu_sys_fans->scale1 = param->scale1;
wf_smu_sys_fans->offset1 = param->offset1;
/* Fill PID params */
pid_param.gd = param->gd;
pid_param.gp = param->gp;
pid_param.gr = param->gr;
pid_param.interval = WF_SMU_SYS_FANS_INTERVAL;
pid_param.history_len = WF_SMU_SYS_FANS_HISTORY_SIZE;
pid_param.itarget = param->itarget;
pid_param.min = fan_system->ops->get_min(fan_system);
pid_param.max = fan_system->ops->get_max(fan_system);
if (fan_hd) {
pid_param.min =
max(pid_param.min,fan_hd->ops->get_min(fan_hd));
pid_param.max =
min(pid_param.max,fan_hd->ops->get_max(fan_hd));
}
wf_pid_init(&wf_smu_sys_fans->pid, &pid_param);
DBG("wf: System Fan control initialized.\n");
DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
FIX32TOPRINT(pid_param.itarget), pid_param.min, pid_param.max);
return;
fail:
if (fan_system)
wf_control_set_max(fan_system);
if (fan_hd)
wf_control_set_max(fan_hd);
}
static void wf_smu_sys_fans_tick(struct wf_smu_sys_fans_state *st)
{
s32 new_setpoint, temp, scaled, cputarget;
int rc;
if (--st->ticks != 0) {
if (wf_smu_readjust)
goto readjust;
return;
}
st->ticks = WF_SMU_SYS_FANS_INTERVAL;
rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp);
if (rc) {
printk(KERN_WARNING "windfarm: HD temp sensor error %d\n",
rc);
wf_smu_failure_state |= FAILURE_SENSOR;
return;
}
DBG("wf_smu: System Fans tick ! HD temp: %d.%03d\n",
FIX32TOPRINT(temp));
if (temp > (st->pid.param.itarget + 0x50000))
wf_smu_failure_state |= FAILURE_OVERTEMP;
new_setpoint = wf_pid_run(&st->pid, temp);
DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);
scaled = ((((s64)new_setpoint) * (s64)st->scale0) >> 12) + st->offset0;
DBG("wf_smu: scaled setpoint: %d RPM\n", (int)scaled);
cputarget = wf_smu_cpu_fans ? wf_smu_cpu_fans->pid.target : 0;
cputarget = ((((s64)cputarget) * (s64)st->scale1) >> 12) + st->offset1;
scaled = max(scaled, cputarget);
scaled = max(scaled, st->pid.param.min);
scaled = min(scaled, st->pid.param.max);
DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)scaled);
if (st->sys_setpoint == scaled && new_setpoint == st->hd_setpoint)
return;
st->sys_setpoint = scaled;
st->hd_setpoint = new_setpoint;
readjust:
if (fan_system && wf_smu_failure_state == 0) {
rc = fan_system->ops->set_value(fan_system, st->sys_setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: Sys fan error %d\n",
rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
if (fan_hd && wf_smu_failure_state == 0) {
rc = fan_hd->ops->set_value(fan_hd, st->hd_setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: HD fan error %d\n",
rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
}
static void wf_smu_create_cpu_fans(void)
{
struct wf_cpu_pid_param pid_param;
struct smu_sdbp_header *hdr;
struct smu_sdbp_cpupiddata *piddata;
struct smu_sdbp_fvt *fvt;
s32 tmax, tdelta, maxpow, powadj;
/* First, locate the PID params in SMU SBD */
hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL);
if (hdr == 0) {
printk(KERN_WARNING "windfarm: CPU PID fan config not found "
"max fan speed\n");
goto fail;
}
piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
/* Get the FVT params for operating point 0 (the only supported one
* for now) in order to get tmax
*/
hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);
if (hdr) {
fvt = (struct smu_sdbp_fvt *)&hdr[1];
tmax = ((s32)fvt->maxtemp) << 16;
} else
tmax = 0x5e0000; /* 94 degree default */
/* Alloc & initialize state */
wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state),
GFP_KERNEL);
if (wf_smu_cpu_fans == NULL)
goto fail;
wf_smu_cpu_fans->ticks = 1;
wf_smu_cpu_fans->scale = WF_SMU_CPU_FANS_SIBLING_SCALE;
wf_smu_cpu_fans->offset = WF_SMU_CPU_FANS_SIBLING_OFFSET;
/* Fill PID params */
pid_param.interval = WF_SMU_CPU_FANS_INTERVAL;
pid_param.history_len = piddata->history_len;
if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) {
printk(KERN_WARNING "windfarm: History size overflow on "
"CPU control loop (%d)\n", piddata->history_len);
pid_param.history_len = WF_CPU_PID_MAX_HISTORY;
}
pid_param.gd = piddata->gd;
pid_param.gp = piddata->gp;
pid_param.gr = piddata->gr / pid_param.history_len;
tdelta = ((s32)piddata->target_temp_delta) << 16;
maxpow = ((s32)piddata->max_power) << 16;
powadj = ((s32)piddata->power_adj) << 16;
pid_param.tmax = tmax;
pid_param.ttarget = tmax - tdelta;
pid_param.pmaxadj = maxpow - powadj;
pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main);
pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main);
wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param);
DBG("wf: CPU Fan control initialized.\n");
DBG(" ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n",
FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax),
pid_param.min, pid_param.max);
return;
fail:
printk(KERN_WARNING "windfarm: CPU fan config not found\n"
"for this machine model, max fan speed\n");
if (cpufreq_clamp)
wf_control_set_max(cpufreq_clamp);
if (fan_cpu_main)
wf_control_set_max(fan_cpu_main);
}
static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)
{
s32 new_setpoint, temp, power, systarget;
int rc;
if (--st->ticks != 0) {
if (wf_smu_readjust)
goto readjust;
return;
}
st->ticks = WF_SMU_CPU_FANS_INTERVAL;
rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp);
if (rc) {
printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n",
rc);
wf_smu_failure_state |= FAILURE_SENSOR;
return;
}
rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power);
if (rc) {
printk(KERN_WARNING "windfarm: CPU power sensor error %d\n",
rc);
wf_smu_failure_state |= FAILURE_SENSOR;
return;
}
DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n",
FIX32TOPRINT(temp), FIX32TOPRINT(power));
#ifdef HACKED_OVERTEMP
if (temp > 0x4a0000)
wf_smu_failure_state |= FAILURE_OVERTEMP;
#else
if (temp > st->pid.param.tmax)
wf_smu_failure_state |= FAILURE_OVERTEMP;
#endif
new_setpoint = wf_cpu_pid_run(&st->pid, power, temp);
DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);
systarget = wf_smu_sys_fans ? wf_smu_sys_fans->pid.target : 0;
systarget = ((((s64)systarget) * (s64)st->scale) >> 12)
+ st->offset;
new_setpoint = max(new_setpoint, systarget);
new_setpoint = max(new_setpoint, st->pid.param.min);
new_setpoint = min(new_setpoint, st->pid.param.max);
DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)new_setpoint);
if (st->cpu_setpoint == new_setpoint)
return;
st->cpu_setpoint = new_setpoint;
readjust:
if (fan_cpu_main && wf_smu_failure_state == 0) {
rc = fan_cpu_main->ops->set_value(fan_cpu_main,
st->cpu_setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: CPU main fan"
" error %d\n", rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
}
/*
* ****** Attributes ******
*
*/
#define BUILD_SHOW_FUNC_FIX(name, data) \
static ssize_t show_##name(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
ssize_t r; \
s32 val = 0; \
data->ops->get_value(data, &val); \
r = sprintf(buf, "%d.%03d", FIX32TOPRINT(val)); \
return r; \
} \
static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL);
#define BUILD_SHOW_FUNC_INT(name, data) \
static ssize_t show_##name(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
s32 val = 0; \
data->ops->get_value(data, &val); \
return sprintf(buf, "%d", val); \
} \
static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL);
BUILD_SHOW_FUNC_INT(cpu_fan, fan_cpu_main);
BUILD_SHOW_FUNC_INT(sys_fan, fan_system);
BUILD_SHOW_FUNC_INT(hd_fan, fan_hd);
BUILD_SHOW_FUNC_FIX(cpu_temp, sensor_cpu_temp);
BUILD_SHOW_FUNC_FIX(cpu_power, sensor_cpu_power);
BUILD_SHOW_FUNC_FIX(hd_temp, sensor_hd_temp);
/*
* ****** Setup / Init / Misc ... ******
*
*/
static void wf_smu_tick(void)
{
unsigned int last_failure = wf_smu_failure_state;
unsigned int new_failure;
if (!wf_smu_started) {
DBG("wf: creating control loops !\n");
wf_smu_create_sys_fans();
wf_smu_create_cpu_fans();
wf_smu_started = 1;
}
/* Skipping ticks */
if (wf_smu_skipping && --wf_smu_skipping)
return;
wf_smu_failure_state = 0;
if (wf_smu_sys_fans)
wf_smu_sys_fans_tick(wf_smu_sys_fans);
if (wf_smu_cpu_fans)
wf_smu_cpu_fans_tick(wf_smu_cpu_fans);
wf_smu_readjust = 0;
new_failure = wf_smu_failure_state & ~last_failure;
/* If entering failure mode, clamp cpufreq and ramp all
* fans to full speed.
*/
if (wf_smu_failure_state && !last_failure) {
if (cpufreq_clamp)
wf_control_set_max(cpufreq_clamp);
if (fan_system)
wf_control_set_max(fan_system);
if (fan_cpu_main)
wf_control_set_max(fan_cpu_main);
if (fan_hd)
wf_control_set_max(fan_hd);
}
/* If leaving failure mode, unclamp cpufreq and readjust
* all fans on next iteration
*/
if (!wf_smu_failure_state && last_failure) {
if (cpufreq_clamp)
wf_control_set_min(cpufreq_clamp);
wf_smu_readjust = 1;
}
/* Overtemp condition detected, notify and start skipping a couple
* ticks to let the temperature go down
*/
if (new_failure & FAILURE_OVERTEMP) {
wf_set_overtemp();
wf_smu_skipping = 2;
}
/* We only clear the overtemp condition if overtemp is cleared
* _and_ no other failure is present. Since a sensor error will
* clear the overtemp condition (can't measure temperature) at
* the control loop levels, but we don't want to keep it clear
* here in this case
*/
if (new_failure == 0 && last_failure & FAILURE_OVERTEMP)
wf_clear_overtemp();
}
static void wf_smu_new_control(struct wf_control *ct)
{
if (wf_smu_all_controls_ok)
return;
if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-fan")) {
if (wf_get_control(ct) == 0) {
fan_cpu_main = ct;
device_create_file(wf_smu_dev, &dev_attr_cpu_fan);
}
}
if (fan_system == NULL && !strcmp(ct->name, "system-fan")) {
if (wf_get_control(ct) == 0) {
fan_system = ct;
device_create_file(wf_smu_dev, &dev_attr_sys_fan);
}
}
if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) {
if (wf_get_control(ct) == 0)
cpufreq_clamp = ct;
}
/* Darwin property list says the HD fan is only for model ID
* 0, 1, 2 and 3
*/
if (wf_smu_mach_model > 3) {
if (fan_system && fan_cpu_main && cpufreq_clamp)
wf_smu_all_controls_ok = 1;
return;
}
if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) {
if (wf_get_control(ct) == 0) {
fan_hd = ct;
device_create_file(wf_smu_dev, &dev_attr_hd_fan);
}
}
if (fan_system && fan_hd && fan_cpu_main && cpufreq_clamp)
wf_smu_all_controls_ok = 1;
}
static void wf_smu_new_sensor(struct wf_sensor *sr)
{
if (wf_smu_all_sensors_ok)
return;
if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) {
if (wf_get_sensor(sr) == 0) {
sensor_cpu_power = sr;
device_create_file(wf_smu_dev, &dev_attr_cpu_power);
}
}
if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) {
if (wf_get_sensor(sr) == 0) {
sensor_cpu_temp = sr;
device_create_file(wf_smu_dev, &dev_attr_cpu_temp);
}
}
if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) {
if (wf_get_sensor(sr) == 0) {
sensor_hd_temp = sr;
device_create_file(wf_smu_dev, &dev_attr_hd_temp);
}
}
if (sensor_cpu_power && sensor_cpu_temp && sensor_hd_temp)
wf_smu_all_sensors_ok = 1;
}
static int wf_smu_notify(struct notifier_block *self,
unsigned long event, void *data)
{
switch(event) {
case WF_EVENT_NEW_CONTROL:
DBG("wf: new control %s detected\n",
((struct wf_control *)data)->name);
wf_smu_new_control(data);
wf_smu_readjust = 1;
break;
case WF_EVENT_NEW_SENSOR:
DBG("wf: new sensor %s detected\n",
((struct wf_sensor *)data)->name);
wf_smu_new_sensor(data);
break;
case WF_EVENT_TICK:
if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok)
wf_smu_tick();
}
return 0;
}
static struct notifier_block wf_smu_events = {
.notifier_call = wf_smu_notify,
};
static int wf_init_pm(void)
{
struct smu_sdbp_header *hdr;
hdr = smu_get_sdb_partition(SMU_SDB_SENSORTREE_ID, NULL);
if (hdr != 0) {
struct smu_sdbp_sensortree *st =
(struct smu_sdbp_sensortree *)&hdr[1];
wf_smu_mach_model = st->model_id;
}
printk(KERN_INFO "windfarm: Initializing for iMacG5 model ID %d\n",
wf_smu_mach_model);
return 0;
}
static int wf_smu_probe(struct device *ddev)
{
wf_smu_dev = ddev;
wf_register_client(&wf_smu_events);
return 0;
}
static int wf_smu_remove(struct device *ddev)
{
wf_unregister_client(&wf_smu_events);
/* XXX We don't have yet a guarantee that our callback isn't
* in progress when returning from wf_unregister_client, so
* we add an arbitrary delay. I'll have to fix that in the core
*/
msleep(1000);
/* Release all sensors */
/* One more crappy race: I don't think we have any guarantee here
* that the attribute callback won't race with the sensor beeing
* disposed of, and I'm not 100% certain what best way to deal
* with that except by adding locks all over... I'll do that
* eventually but heh, who ever rmmod this module anyway ?
*/
if (sensor_cpu_power) {
device_remove_file(wf_smu_dev, &dev_attr_cpu_power);
wf_put_sensor(sensor_cpu_power);
}
if (sensor_cpu_temp) {
device_remove_file(wf_smu_dev, &dev_attr_cpu_temp);
wf_put_sensor(sensor_cpu_temp);
}
if (sensor_hd_temp) {
device_remove_file(wf_smu_dev, &dev_attr_hd_temp);
wf_put_sensor(sensor_hd_temp);
}
/* Release all controls */
if (fan_cpu_main) {
device_remove_file(wf_smu_dev, &dev_attr_cpu_fan);
wf_put_control(fan_cpu_main);
}
if (fan_hd) {
device_remove_file(wf_smu_dev, &dev_attr_hd_fan);
wf_put_control(fan_hd);
}
if (fan_system) {
device_remove_file(wf_smu_dev, &dev_attr_sys_fan);
wf_put_control(fan_system);
}
if (cpufreq_clamp)
wf_put_control(cpufreq_clamp);
/* Destroy control loops state structures */
if (wf_smu_sys_fans)
kfree(wf_smu_sys_fans);
if (wf_smu_cpu_fans)
kfree(wf_smu_cpu_fans);
wf_smu_dev = NULL;
return 0;
}
static struct device_driver wf_smu_driver = {
.name = "windfarm",
.bus = &platform_bus_type,
.probe = wf_smu_probe,
.remove = wf_smu_remove,
};
static int __init wf_smu_init(void)
{
int rc = -ENODEV;
if (machine_is_compatible("PowerMac8,1") ||
machine_is_compatible("PowerMac8,2"))
rc = wf_init_pm();
if (rc == 0) {
#ifdef MODULE
request_module("windfarm_smu_controls");
request_module("windfarm_smu_sensors");
request_module("windfarm_lm75_sensor");
#endif /* MODULE */
driver_register(&wf_smu_driver);
}
return rc;
}
static void __exit wf_smu_exit(void)
{
driver_unregister(&wf_smu_driver);
}
module_init(wf_smu_init);
module_exit(wf_smu_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("Thermal control logic for iMac G5");
MODULE_LICENSE("GPL");
/*
* Windfarm PowerMac thermal control. SMU based 1 CPU desktop control loops
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*
* The algorithm used is the PID control algorithm, used the same
* way the published Darwin code does, using the same values that
* are present in the Darwin 8.2 snapshot property lists (note however
* that none of the code has been re-used, it's a complete re-implementation
*
* The various control loops found in Darwin config file are:
*
* PowerMac9,1
* ===========
*
* Has 3 control loops: CPU fans is similar to PowerMac8,1 (though it doesn't
* try to play with other control loops fans). Drive bay is rather basic PID
* with one sensor and one fan. Slots area is a bit different as the Darwin
* driver is supposed to be capable of working in a special "AGP" mode which
* involves the presence of an AGP sensor and an AGP fan (possibly on the
* AGP card itself). I can't deal with that special mode as I don't have
* access to those additional sensor/fans for now (though ultimately, it would
* be possible to add sensor objects for them) so I'm only implementing the
* basic PCI slot control loop
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/kmod.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/sections.h>
#include <asm/smu.h>
#include "windfarm.h"
#include "windfarm_pid.h"
#define VERSION "0.4"
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
/* define this to force CPU overtemp to 74 degree, useful for testing
* the overtemp code
*/
#undef HACKED_OVERTEMP
static struct device *wf_smu_dev;
/* Controls & sensors */
static struct wf_sensor *sensor_cpu_power;
static struct wf_sensor *sensor_cpu_temp;
static struct wf_sensor *sensor_hd_temp;
static struct wf_sensor *sensor_slots_power;
static struct wf_control *fan_cpu_main;
static struct wf_control *fan_cpu_second;
static struct wf_control *fan_cpu_third;
static struct wf_control *fan_hd;
static struct wf_control *fan_slots;
static struct wf_control *cpufreq_clamp;
/* Set to kick the control loop into life */
static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started;
/* Failure handling.. could be nicer */
#define FAILURE_FAN 0x01
#define FAILURE_SENSOR 0x02
#define FAILURE_OVERTEMP 0x04
static unsigned int wf_smu_failure_state;
static int wf_smu_readjust, wf_smu_skipping;
/*
* ****** CPU Fans Control Loop ******
*
*/
#define WF_SMU_CPU_FANS_INTERVAL 1
#define WF_SMU_CPU_FANS_MAX_HISTORY 16
/* State data used by the cpu fans control loop
*/
struct wf_smu_cpu_fans_state {
int ticks;
s32 cpu_setpoint;
struct wf_cpu_pid_state pid;
};
static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans;
/*
* ****** Drive Fan Control Loop ******
*
*/
struct wf_smu_drive_fans_state {
int ticks;
s32 setpoint;
struct wf_pid_state pid;
};
static struct wf_smu_drive_fans_state *wf_smu_drive_fans;
/*
* ****** Slots Fan Control Loop ******
*
*/
struct wf_smu_slots_fans_state {
int ticks;
s32 setpoint;
struct wf_pid_state pid;
};
static struct wf_smu_slots_fans_state *wf_smu_slots_fans;
/*
* ***** Implementation *****
*
*/
static void wf_smu_create_cpu_fans(void)
{
struct wf_cpu_pid_param pid_param;
struct smu_sdbp_header *hdr;
struct smu_sdbp_cpupiddata *piddata;
struct smu_sdbp_fvt *fvt;
s32 tmax, tdelta, maxpow, powadj;
/* First, locate the PID params in SMU SBD */
hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL);
if (hdr == 0) {
printk(KERN_WARNING "windfarm: CPU PID fan config not found "
"max fan speed\n");
goto fail;
}
piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
/* Get the FVT params for operating point 0 (the only supported one
* for now) in order to get tmax
*/
hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);
if (hdr) {
fvt = (struct smu_sdbp_fvt *)&hdr[1];
tmax = ((s32)fvt->maxtemp) << 16;
} else
tmax = 0x5e0000; /* 94 degree default */
/* Alloc & initialize state */
wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state),
GFP_KERNEL);
if (wf_smu_cpu_fans == NULL)
goto fail;
wf_smu_cpu_fans->ticks = 1;
/* Fill PID params */
pid_param.interval = WF_SMU_CPU_FANS_INTERVAL;
pid_param.history_len = piddata->history_len;
if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) {
printk(KERN_WARNING "windfarm: History size overflow on "
"CPU control loop (%d)\n", piddata->history_len);
pid_param.history_len = WF_CPU_PID_MAX_HISTORY;
}
pid_param.gd = piddata->gd;
pid_param.gp = piddata->gp;
pid_param.gr = piddata->gr / pid_param.history_len;
tdelta = ((s32)piddata->target_temp_delta) << 16;
maxpow = ((s32)piddata->max_power) << 16;
powadj = ((s32)piddata->power_adj) << 16;
pid_param.tmax = tmax;
pid_param.ttarget = tmax - tdelta;
pid_param.pmaxadj = maxpow - powadj;
pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main);
pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main);
wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param);
DBG("wf: CPU Fan control initialized.\n");
DBG(" ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n",
FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax),
pid_param.min, pid_param.max);
return;
fail:
printk(KERN_WARNING "windfarm: CPU fan config not found\n"
"for this machine model, max fan speed\n");
if (cpufreq_clamp)
wf_control_set_max(cpufreq_clamp);
if (fan_cpu_main)
wf_control_set_max(fan_cpu_main);
}
static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)
{
s32 new_setpoint, temp, power;
int rc;
if (--st->ticks != 0) {
if (wf_smu_readjust)
goto readjust;
return;
}
st->ticks = WF_SMU_CPU_FANS_INTERVAL;
rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp);
if (rc) {
printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n",
rc);
wf_smu_failure_state |= FAILURE_SENSOR;
return;
}
rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power);
if (rc) {
printk(KERN_WARNING "windfarm: CPU power sensor error %d\n",
rc);
wf_smu_failure_state |= FAILURE_SENSOR;
return;
}
DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n",
FIX32TOPRINT(temp), FIX32TOPRINT(power));
#ifdef HACKED_OVERTEMP
if (temp > 0x4a0000)
wf_smu_failure_state |= FAILURE_OVERTEMP;
#else
if (temp > st->pid.param.tmax)
wf_smu_failure_state |= FAILURE_OVERTEMP;
#endif
new_setpoint = wf_cpu_pid_run(&st->pid, power, temp);
DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);
if (st->cpu_setpoint == new_setpoint)
return;
st->cpu_setpoint = new_setpoint;
readjust:
if (fan_cpu_main && wf_smu_failure_state == 0) {
rc = fan_cpu_main->ops->set_value(fan_cpu_main,
st->cpu_setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: CPU main fan"
" error %d\n", rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
if (fan_cpu_second && wf_smu_failure_state == 0) {
rc = fan_cpu_second->ops->set_value(fan_cpu_second,
st->cpu_setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: CPU second fan"
" error %d\n", rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
if (fan_cpu_third && wf_smu_failure_state == 0) {
rc = fan_cpu_main->ops->set_value(fan_cpu_third,
st->cpu_setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: CPU third fan"
" error %d\n", rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
}
static void wf_smu_create_drive_fans(void)
{
struct wf_pid_param param = {
.interval = 5,
.history_len = 2,
.gd = 0x01e00000,
.gp = 0x00500000,
.gr = 0x00000000,
.itarget = 0x00200000,
};
/* Alloc & initialize state */
wf_smu_drive_fans = kmalloc(sizeof(struct wf_smu_drive_fans_state),
GFP_KERNEL);
if (wf_smu_drive_fans == NULL) {
printk(KERN_WARNING "windfarm: Memory allocation error"
" max fan speed\n");
goto fail;
}
wf_smu_drive_fans->ticks = 1;
/* Fill PID params */
param.additive = (fan_hd->type == WF_CONTROL_RPM_FAN);
param.min = fan_hd->ops->get_min(fan_hd);
param.max = fan_hd->ops->get_max(fan_hd);
wf_pid_init(&wf_smu_drive_fans->pid, &param);
DBG("wf: Drive Fan control initialized.\n");
DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
FIX32TOPRINT(param.itarget), param.min, param.max);
return;
fail:
if (fan_hd)
wf_control_set_max(fan_hd);
}
static void wf_smu_drive_fans_tick(struct wf_smu_drive_fans_state *st)
{
s32 new_setpoint, temp;
int rc;
if (--st->ticks != 0) {
if (wf_smu_readjust)
goto readjust;
return;
}
st->ticks = st->pid.param.interval;
rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp);
if (rc) {
printk(KERN_WARNING "windfarm: HD temp sensor error %d\n",
rc);
wf_smu_failure_state |= FAILURE_SENSOR;
return;
}
DBG("wf_smu: Drive Fans tick ! HD temp: %d.%03d\n",
FIX32TOPRINT(temp));
if (temp > (st->pid.param.itarget + 0x50000))
wf_smu_failure_state |= FAILURE_OVERTEMP;
new_setpoint = wf_pid_run(&st->pid, temp);
DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint);
if (st->setpoint == new_setpoint)
return;
st->setpoint = new_setpoint;
readjust:
if (fan_hd && wf_smu_failure_state == 0) {
rc = fan_hd->ops->set_value(fan_hd, st->setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: HD fan error %d\n",
rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
}
static void wf_smu_create_slots_fans(void)
{
struct wf_pid_param param = {
.interval = 1,
.history_len = 8,
.gd = 0x00000000,
.gp = 0x00000000,
.gr = 0x00020000,
.itarget = 0x00000000
};
/* Alloc & initialize state */
wf_smu_slots_fans = kmalloc(sizeof(struct wf_smu_slots_fans_state),
GFP_KERNEL);
if (wf_smu_slots_fans == NULL) {
printk(KERN_WARNING "windfarm: Memory allocation error"
" max fan speed\n");
goto fail;
}
wf_smu_slots_fans->ticks = 1;
/* Fill PID params */
param.additive = (fan_slots->type == WF_CONTROL_RPM_FAN);
param.min = fan_slots->ops->get_min(fan_slots);
param.max = fan_slots->ops->get_max(fan_slots);
wf_pid_init(&wf_smu_slots_fans->pid, &param);
DBG("wf: Slots Fan control initialized.\n");
DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
FIX32TOPRINT(param.itarget), param.min, param.max);
return;
fail:
if (fan_slots)
wf_control_set_max(fan_slots);
}
static void wf_smu_slots_fans_tick(struct wf_smu_slots_fans_state *st)
{
s32 new_setpoint, power;
int rc;
if (--st->ticks != 0) {
if (wf_smu_readjust)
goto readjust;
return;
}
st->ticks = st->pid.param.interval;
rc = sensor_slots_power->ops->get_value(sensor_slots_power, &power);
if (rc) {
printk(KERN_WARNING "windfarm: Slots power sensor error %d\n",
rc);
wf_smu_failure_state |= FAILURE_SENSOR;
return;
}
DBG("wf_smu: Slots Fans tick ! Slots power: %d.%03d\n",
FIX32TOPRINT(power));
#if 0 /* Check what makes a good overtemp condition */
if (power > (st->pid.param.itarget + 0x50000))
wf_smu_failure_state |= FAILURE_OVERTEMP;
#endif
new_setpoint = wf_pid_run(&st->pid, power);
DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint);
if (st->setpoint == new_setpoint)
return;
st->setpoint = new_setpoint;
readjust:
if (fan_slots && wf_smu_failure_state == 0) {
rc = fan_slots->ops->set_value(fan_slots, st->setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: Slots fan error %d\n",
rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
}
/*
* ****** Attributes ******
*
*/
#define BUILD_SHOW_FUNC_FIX(name, data) \
static ssize_t show_##name(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
ssize_t r; \
s32 val = 0; \
data->ops->get_value(data, &val); \
r = sprintf(buf, "%d.%03d", FIX32TOPRINT(val)); \
return r; \
} \
static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL);
#define BUILD_SHOW_FUNC_INT(name, data) \
static ssize_t show_##name(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
s32 val = 0; \
data->ops->get_value(data, &val); \
return sprintf(buf, "%d", val); \
} \
static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL);
BUILD_SHOW_FUNC_INT(cpu_fan, fan_cpu_main);
BUILD_SHOW_FUNC_INT(hd_fan, fan_hd);
BUILD_SHOW_FUNC_INT(slots_fan, fan_slots);
BUILD_SHOW_FUNC_FIX(cpu_temp, sensor_cpu_temp);
BUILD_SHOW_FUNC_FIX(cpu_power, sensor_cpu_power);
BUILD_SHOW_FUNC_FIX(hd_temp, sensor_hd_temp);
BUILD_SHOW_FUNC_FIX(slots_power, sensor_slots_power);
/*
* ****** Setup / Init / Misc ... ******
*
*/
static void wf_smu_tick(void)
{
unsigned int last_failure = wf_smu_failure_state;
unsigned int new_failure;
if (!wf_smu_started) {
DBG("wf: creating control loops !\n");
wf_smu_create_drive_fans();
wf_smu_create_slots_fans();
wf_smu_create_cpu_fans();
wf_smu_started = 1;
}
/* Skipping ticks */
if (wf_smu_skipping && --wf_smu_skipping)
return;
wf_smu_failure_state = 0;
if (wf_smu_drive_fans)
wf_smu_drive_fans_tick(wf_smu_drive_fans);
if (wf_smu_slots_fans)
wf_smu_slots_fans_tick(wf_smu_slots_fans);
if (wf_smu_cpu_fans)
wf_smu_cpu_fans_tick(wf_smu_cpu_fans);
wf_smu_readjust = 0;
new_failure = wf_smu_failure_state & ~last_failure;
/* If entering failure mode, clamp cpufreq and ramp all
* fans to full speed.
*/
if (wf_smu_failure_state && !last_failure) {
if (cpufreq_clamp)
wf_control_set_max(cpufreq_clamp);
if (fan_cpu_main)
wf_control_set_max(fan_cpu_main);
if (fan_cpu_second)
wf_control_set_max(fan_cpu_second);
if (fan_cpu_third)
wf_control_set_max(fan_cpu_third);
if (fan_hd)
wf_control_set_max(fan_hd);
if (fan_slots)
wf_control_set_max(fan_slots);
}
/* If leaving failure mode, unclamp cpufreq and readjust
* all fans on next iteration
*/
if (!wf_smu_failure_state && last_failure) {
if (cpufreq_clamp)
wf_control_set_min(cpufreq_clamp);
wf_smu_readjust = 1;
}
/* Overtemp condition detected, notify and start skipping a couple
* ticks to let the temperature go down
*/
if (new_failure & FAILURE_OVERTEMP) {
wf_set_overtemp();
wf_smu_skipping = 2;
}
/* We only clear the overtemp condition if overtemp is cleared
* _and_ no other failure is present. Since a sensor error will
* clear the overtemp condition (can't measure temperature) at
* the control loop levels, but we don't want to keep it clear
* here in this case
*/
if (new_failure == 0 && last_failure & FAILURE_OVERTEMP)
wf_clear_overtemp();
}
static void wf_smu_new_control(struct wf_control *ct)
{
if (wf_smu_all_controls_ok)
return;
if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-rear-fan-0")) {
if (wf_get_control(ct) == 0) {
fan_cpu_main = ct;
device_create_file(wf_smu_dev, &dev_attr_cpu_fan);
}
}
if (fan_cpu_second == NULL && !strcmp(ct->name, "cpu-rear-fan-1")) {
if (wf_get_control(ct) == 0)
fan_cpu_second = ct;
}
if (fan_cpu_third == NULL && !strcmp(ct->name, "cpu-front-fan-0")) {
if (wf_get_control(ct) == 0)
fan_cpu_third = ct;
}
if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) {
if (wf_get_control(ct) == 0)
cpufreq_clamp = ct;
}
if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) {
if (wf_get_control(ct) == 0) {
fan_hd = ct;
device_create_file(wf_smu_dev, &dev_attr_hd_fan);
}
}
if (fan_slots == NULL && !strcmp(ct->name, "slots-fan")) {
if (wf_get_control(ct) == 0) {
fan_slots = ct;
device_create_file(wf_smu_dev, &dev_attr_slots_fan);
}
}
if (fan_cpu_main && (fan_cpu_second || fan_cpu_third) && fan_hd &&
fan_slots && cpufreq_clamp)
wf_smu_all_controls_ok = 1;
}
static void wf_smu_new_sensor(struct wf_sensor *sr)
{
if (wf_smu_all_sensors_ok)
return;
if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) {
if (wf_get_sensor(sr) == 0) {
sensor_cpu_power = sr;
device_create_file(wf_smu_dev, &dev_attr_cpu_power);
}
}
if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) {
if (wf_get_sensor(sr) == 0) {
sensor_cpu_temp = sr;
device_create_file(wf_smu_dev, &dev_attr_cpu_temp);
}
}
if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) {
if (wf_get_sensor(sr) == 0) {
sensor_hd_temp = sr;
device_create_file(wf_smu_dev, &dev_attr_hd_temp);
}
}
if (sensor_slots_power == NULL && !strcmp(sr->name, "slots-power")) {
if (wf_get_sensor(sr) == 0) {
sensor_slots_power = sr;
device_create_file(wf_smu_dev, &dev_attr_slots_power);
}
}
if (sensor_cpu_power && sensor_cpu_temp &&
sensor_hd_temp && sensor_slots_power)
wf_smu_all_sensors_ok = 1;
}
static int wf_smu_notify(struct notifier_block *self,
unsigned long event, void *data)
{
switch(event) {
case WF_EVENT_NEW_CONTROL:
DBG("wf: new control %s detected\n",
((struct wf_control *)data)->name);
wf_smu_new_control(data);
wf_smu_readjust = 1;
break;
case WF_EVENT_NEW_SENSOR:
DBG("wf: new sensor %s detected\n",
((struct wf_sensor *)data)->name);
wf_smu_new_sensor(data);
break;
case WF_EVENT_TICK:
if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok)
wf_smu_tick();
}
return 0;
}
static struct notifier_block wf_smu_events = {
.notifier_call = wf_smu_notify,
};
static int wf_init_pm(void)
{
printk(KERN_INFO "windfarm: Initializing for Desktop G5 model\n");
return 0;
}
static int wf_smu_probe(struct device *ddev)
{
wf_smu_dev = ddev;
wf_register_client(&wf_smu_events);
return 0;
}
static int wf_smu_remove(struct device *ddev)
{
wf_unregister_client(&wf_smu_events);
/* XXX We don't have yet a guarantee that our callback isn't
* in progress when returning from wf_unregister_client, so
* we add an arbitrary delay. I'll have to fix that in the core
*/
msleep(1000);
/* Release all sensors */
/* One more crappy race: I don't think we have any guarantee here
* that the attribute callback won't race with the sensor beeing
* disposed of, and I'm not 100% certain what best way to deal
* with that except by adding locks all over... I'll do that
* eventually but heh, who ever rmmod this module anyway ?
*/
if (sensor_cpu_power) {
device_remove_file(wf_smu_dev, &dev_attr_cpu_power);
wf_put_sensor(sensor_cpu_power);
}
if (sensor_cpu_temp) {
device_remove_file(wf_smu_dev, &dev_attr_cpu_temp);
wf_put_sensor(sensor_cpu_temp);
}
if (sensor_hd_temp) {
device_remove_file(wf_smu_dev, &dev_attr_hd_temp);
wf_put_sensor(sensor_hd_temp);
}
if (sensor_slots_power) {
device_remove_file(wf_smu_dev, &dev_attr_slots_power);
wf_put_sensor(sensor_slots_power);
}
/* Release all controls */
if (fan_cpu_main) {
device_remove_file(wf_smu_dev, &dev_attr_cpu_fan);
wf_put_control(fan_cpu_main);
}
if (fan_cpu_second)
wf_put_control(fan_cpu_second);
if (fan_cpu_third)
wf_put_control(fan_cpu_third);
if (fan_hd) {
device_remove_file(wf_smu_dev, &dev_attr_hd_fan);
wf_put_control(fan_hd);
}
if (fan_slots) {
device_remove_file(wf_smu_dev, &dev_attr_slots_fan);
wf_put_control(fan_slots);
}
if (cpufreq_clamp)
wf_put_control(cpufreq_clamp);
/* Destroy control loops state structures */
if (wf_smu_slots_fans)
kfree(wf_smu_cpu_fans);
if (wf_smu_drive_fans)
kfree(wf_smu_cpu_fans);
if (wf_smu_cpu_fans)
kfree(wf_smu_cpu_fans);
wf_smu_dev = NULL;
return 0;
}
static struct device_driver wf_smu_driver = {
.name = "windfarm",
.bus = &platform_bus_type,
.probe = wf_smu_probe,
.remove = wf_smu_remove,
};
static int __init wf_smu_init(void)
{
int rc = -ENODEV;
if (machine_is_compatible("PowerMac9,1"))
rc = wf_init_pm();
if (rc == 0) {
#ifdef MODULE
request_module("windfarm_smu_controls");
request_module("windfarm_smu_sensors");
request_module("windfarm_lm75_sensor");
#endif /* MODULE */
driver_register(&wf_smu_driver);
}
return rc;
}
static void __exit wf_smu_exit(void)
{
driver_unregister(&wf_smu_driver);
}
module_init(wf_smu_init);
module_exit(wf_smu_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("Thermal control logic for PowerMac9,1");
MODULE_LICENSE("GPL");
/*
* Windfarm PowerMac thermal control. SMU based controls
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/sections.h>
#include <asm/smu.h>
#include "windfarm.h"
#define VERSION "0.3"
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
/*
* SMU fans control object
*/
static LIST_HEAD(smu_fans);
struct smu_fan_control {
struct list_head link;
int fan_type; /* 0 = rpm, 1 = pwm */
u32 reg; /* index in SMU */
s32 value; /* current value */
s32 min, max; /* min/max values */
struct wf_control ctrl;
};
#define to_smu_fan(c) container_of(c, struct smu_fan_control, ctrl)
static int smu_set_fan(int pwm, u8 id, u16 value)
{
struct smu_cmd cmd;
u8 buffer[16];
DECLARE_COMPLETION(comp);
int rc;
/* Fill SMU command structure */
cmd.cmd = SMU_CMD_FAN_COMMAND;
cmd.data_len = 14;
cmd.reply_len = 16;
cmd.data_buf = cmd.reply_buf = buffer;
cmd.status = 0;
cmd.done = smu_done_complete;
cmd.misc = &comp;
/* Fill argument buffer */
memset(buffer, 0, 16);
buffer[0] = pwm ? 0x10 : 0x00;
buffer[1] = 0x01 << id;
*((u16 *)&buffer[2 + id * 2]) = value;
rc = smu_queue_cmd(&cmd);
if (rc)
return rc;
wait_for_completion(&comp);
return cmd.status;
}
static void smu_fan_release(struct wf_control *ct)
{
struct smu_fan_control *fct = to_smu_fan(ct);
kfree(fct);
}
static int smu_fan_set(struct wf_control *ct, s32 value)
{
struct smu_fan_control *fct = to_smu_fan(ct);
if (value < fct->min)
value = fct->min;
if (value > fct->max)
value = fct->max;
fct->value = value;
return smu_set_fan(fct->fan_type, fct->reg, value);
}
static int smu_fan_get(struct wf_control *ct, s32 *value)
{
struct smu_fan_control *fct = to_smu_fan(ct);
*value = fct->value; /* todo: read from SMU */
return 0;
}
static s32 smu_fan_min(struct wf_control *ct)
{
struct smu_fan_control *fct = to_smu_fan(ct);
return fct->min;
}
static s32 smu_fan_max(struct wf_control *ct)
{
struct smu_fan_control *fct = to_smu_fan(ct);
return fct->max;
}
static struct wf_control_ops smu_fan_ops = {
.set_value = smu_fan_set,
.get_value = smu_fan_get,
.get_min = smu_fan_min,
.get_max = smu_fan_max,
.release = smu_fan_release,
.owner = THIS_MODULE,
};
static struct smu_fan_control *smu_fan_create(struct device_node *node,
int pwm_fan)
{
struct smu_fan_control *fct;
s32 *v; u32 *reg;
char *l;
fct = kmalloc(sizeof(struct smu_fan_control), GFP_KERNEL);
if (fct == NULL)
return NULL;
fct->ctrl.ops = &smu_fan_ops;
l = (char *)get_property(node, "location", NULL);
if (l == NULL)
goto fail;
fct->fan_type = pwm_fan;
fct->ctrl.type = pwm_fan ? WF_CONTROL_PWM_FAN : WF_CONTROL_RPM_FAN;
/* We use the name & location here the same way we do for SMU sensors,
* see the comment in windfarm_smu_sensors.c. The locations are a bit
* less consistent here between the iMac and the desktop models, but
* that is good enough for our needs for now at least.
*
* One problem though is that Apple seem to be inconsistent with case
* and the kernel doesn't have strcasecmp =P
*/
fct->ctrl.name = NULL;
/* Names used on desktop models */
if (!strcmp(l, "Rear Fan 0") || !strcmp(l, "Rear Fan") ||
!strcmp(l, "Rear fan 0") || !strcmp(l, "Rear fan"))
fct->ctrl.name = "cpu-rear-fan-0";
else if (!strcmp(l, "Rear Fan 1") || !strcmp(l, "Rear fan 1"))
fct->ctrl.name = "cpu-rear-fan-1";
else if (!strcmp(l, "Front Fan 0") || !strcmp(l, "Front Fan") ||
!strcmp(l, "Front fan 0") || !strcmp(l, "Front fan"))
fct->ctrl.name = "cpu-front-fan-0";
else if (!strcmp(l, "Front Fan 1") || !strcmp(l, "Front fan 1"))
fct->ctrl.name = "cpu-front-fan-1";
else if (!strcmp(l, "Slots Fan") || !strcmp(l, "Slots fan"))
fct->ctrl.name = "slots-fan";
else if (!strcmp(l, "Drive Bay") || !strcmp(l, "Drive bay"))
fct->ctrl.name = "drive-bay-fan";
/* Names used on iMac models */
if (!strcmp(l, "System Fan") || !strcmp(l, "System fan"))
fct->ctrl.name = "system-fan";
else if (!strcmp(l, "CPU Fan") || !strcmp(l, "CPU fan"))
fct->ctrl.name = "cpu-fan";
else if (!strcmp(l, "Hard Drive") || !strcmp(l, "Hard drive"))
fct->ctrl.name = "drive-bay-fan";
/* Unrecognized fan, bail out */
if (fct->ctrl.name == NULL)
goto fail;
/* Get min & max values*/
v = (s32 *)get_property(node, "min-value", NULL);
if (v == NULL)
goto fail;
fct->min = *v;
v = (s32 *)get_property(node, "max-value", NULL);
if (v == NULL)
goto fail;
fct->max = *v;
/* Get "reg" value */
reg = (u32 *)get_property(node, "reg", NULL);
if (reg == NULL)
goto fail;
fct->reg = *reg;
if (wf_register_control(&fct->ctrl))
goto fail;
return fct;
fail:
kfree(fct);
return NULL;
}
static int __init smu_controls_init(void)
{
struct device_node *smu, *fans, *fan;
if (!smu_present())
return -ENODEV;
smu = of_find_node_by_type(NULL, "smu");
if (smu == NULL)
return -ENODEV;
/* Look for RPM fans */
for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;)
if (!strcmp(fans->name, "rpm-fans"))
break;
for (fan = NULL;
fans && (fan = of_get_next_child(fans, fan)) != NULL;) {
struct smu_fan_control *fct;
fct = smu_fan_create(fan, 0);
if (fct == NULL) {
printk(KERN_WARNING "windfarm: Failed to create SMU "
"RPM fan %s\n", fan->name);
continue;
}
list_add(&fct->link, &smu_fans);
}
of_node_put(fans);
/* Look for PWM fans */
for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;)
if (!strcmp(fans->name, "pwm-fans"))
break;
for (fan = NULL;
fans && (fan = of_get_next_child(fans, fan)) != NULL;) {
struct smu_fan_control *fct;
fct = smu_fan_create(fan, 1);
if (fct == NULL) {
printk(KERN_WARNING "windfarm: Failed to create SMU "
"PWM fan %s\n", fan->name);
continue;
}
list_add(&fct->link, &smu_fans);
}
of_node_put(fans);
of_node_put(smu);
return 0;
}
static void __exit smu_controls_exit(void)
{
struct smu_fan_control *fct;
while (!list_empty(&smu_fans)) {
fct = list_entry(smu_fans.next, struct smu_fan_control, link);
list_del(&fct->link);
wf_unregister_control(&fct->ctrl);
}
}
module_init(smu_controls_init);
module_exit(smu_controls_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("SMU control objects for PowerMacs thermal control");
MODULE_LICENSE("GPL");
/*
* Windfarm PowerMac thermal control. SMU based sensors
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/sections.h>
#include <asm/smu.h>
#include "windfarm.h"
#define VERSION "0.2"
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
/*
* Various SMU "partitions" calibration objects for which we
* keep pointers here for use by bits & pieces of the driver
*/
static struct smu_sdbp_cpuvcp *cpuvcp;
static int cpuvcp_version;
static struct smu_sdbp_cpudiode *cpudiode;
static struct smu_sdbp_slotspow *slotspow;
static u8 *debugswitches;
/*
* SMU basic sensors objects
*/
static LIST_HEAD(smu_ads);
struct smu_ad_sensor {
struct list_head link;
u32 reg; /* index in SMU */
struct wf_sensor sens;
};
#define to_smu_ads(c) container_of(c, struct smu_ad_sensor, sens)
static void smu_ads_release(struct wf_sensor *sr)
{
struct smu_ad_sensor *ads = to_smu_ads(sr);
kfree(ads);
}
static int smu_read_adc(u8 id, s32 *value)
{
struct smu_simple_cmd cmd;
DECLARE_COMPLETION(comp);
int rc;
rc = smu_queue_simple(&cmd, SMU_CMD_READ_ADC, 1,
smu_done_complete, &comp, id);
if (rc)
return rc;
wait_for_completion(&comp);
if (cmd.cmd.status != 0)
return cmd.cmd.status;
if (cmd.cmd.reply_len != 2) {
printk(KERN_ERR "winfarm: read ADC 0x%x returned %d bytes !\n",
id, cmd.cmd.reply_len);
return -EIO;
}
*value = *((u16 *)cmd.buffer);
return 0;
}
static int smu_cputemp_get(struct wf_sensor *sr, s32 *value)
{
struct smu_ad_sensor *ads = to_smu_ads(sr);
int rc;
s32 val;
s64 scaled;
rc = smu_read_adc(ads->reg, &val);
if (rc) {
printk(KERN_ERR "windfarm: read CPU temp failed, err %d\n",
rc);
return rc;
}
/* Ok, we have to scale & adjust, taking units into account */
scaled = (s64)(((u64)val) * (u64)cpudiode->m_value);
scaled >>= 3;
scaled += ((s64)cpudiode->b_value) << 9;
*value = (s32)(scaled << 1);
return 0;
}
static int smu_cpuamp_get(struct wf_sensor *sr, s32 *value)
{
struct smu_ad_sensor *ads = to_smu_ads(sr);
s32 val, scaled;
int rc;
rc = smu_read_adc(ads->reg, &val);
if (rc) {
printk(KERN_ERR "windfarm: read CPU current failed, err %d\n",
rc);
return rc;
}
/* Ok, we have to scale & adjust, taking units into account */
scaled = (s32)(val * (u32)cpuvcp->curr_scale);
scaled += (s32)cpuvcp->curr_offset;
*value = scaled << 4;
return 0;
}
static int smu_cpuvolt_get(struct wf_sensor *sr, s32 *value)
{
struct smu_ad_sensor *ads = to_smu_ads(sr);
s32 val, scaled;
int rc;
rc = smu_read_adc(ads->reg, &val);
if (rc) {
printk(KERN_ERR "windfarm: read CPU voltage failed, err %d\n",
rc);
return rc;
}
/* Ok, we have to scale & adjust, taking units into account */
scaled = (s32)(val * (u32)cpuvcp->volt_scale);
scaled += (s32)cpuvcp->volt_offset;
*value = scaled << 4;
return 0;
}
static int smu_slotspow_get(struct wf_sensor *sr, s32 *value)
{
struct smu_ad_sensor *ads = to_smu_ads(sr);
s32 val, scaled;
int rc;
rc = smu_read_adc(ads->reg, &val);
if (rc) {
printk(KERN_ERR "windfarm: read slots power failed, err %d\n",
rc);
return rc;
}
/* Ok, we have to scale & adjust, taking units into account */
scaled = (s32)(val * (u32)slotspow->pow_scale);
scaled += (s32)slotspow->pow_offset;
*value = scaled << 4;
return 0;
}
static struct wf_sensor_ops smu_cputemp_ops = {
.get_value = smu_cputemp_get,
.release = smu_ads_release,
.owner = THIS_MODULE,
};
static struct wf_sensor_ops smu_cpuamp_ops = {
.get_value = smu_cpuamp_get,
.release = smu_ads_release,
.owner = THIS_MODULE,
};
static struct wf_sensor_ops smu_cpuvolt_ops = {
.get_value = smu_cpuvolt_get,
.release = smu_ads_release,
.owner = THIS_MODULE,
};
static struct wf_sensor_ops smu_slotspow_ops = {
.get_value = smu_slotspow_get,
.release = smu_ads_release,
.owner = THIS_MODULE,
};
static struct smu_ad_sensor *smu_ads_create(struct device_node *node)
{
struct smu_ad_sensor *ads;
char *c, *l;
u32 *v;
ads = kmalloc(sizeof(struct smu_ad_sensor), GFP_KERNEL);
if (ads == NULL)
return NULL;
c = (char *)get_property(node, "device_type", NULL);
l = (char *)get_property(node, "location", NULL);
if (c == NULL || l == NULL)
goto fail;
/* We currently pick the sensors based on the OF name and location
* properties, while Darwin uses the sensor-id's.
* The problem with the IDs is that they are model specific while it
* looks like apple has been doing a reasonably good job at keeping
* the names and locations consistents so I'll stick with the names
* and locations for now.
*/
if (!strcmp(c, "temp-sensor") &&
!strcmp(l, "CPU T-Diode")) {
ads->sens.ops = &smu_cputemp_ops;
ads->sens.name = "cpu-temp";
} else if (!strcmp(c, "current-sensor") &&
!strcmp(l, "CPU Current")) {
ads->sens.ops = &smu_cpuamp_ops;
ads->sens.name = "cpu-current";
} else if (!strcmp(c, "voltage-sensor") &&
!strcmp(l, "CPU Voltage")) {
ads->sens.ops = &smu_cpuvolt_ops;
ads->sens.name = "cpu-voltage";
} else if (!strcmp(c, "power-sensor") &&
!strcmp(l, "Slots Power")) {
ads->sens.ops = &smu_slotspow_ops;
ads->sens.name = "slots-power";
if (slotspow == NULL) {
DBG("wf: slotspow partition (%02x) not found\n",
SMU_SDB_SLOTSPOW_ID);
goto fail;
}
} else
goto fail;
v = (u32 *)get_property(node, "reg", NULL);
if (v == NULL)
goto fail;
ads->reg = *v;
if (wf_register_sensor(&ads->sens))
goto fail;
return ads;
fail:
kfree(ads);
return NULL;
}
/*
* SMU Power combo sensor object
*/
struct smu_cpu_power_sensor {
struct list_head link;
struct wf_sensor *volts;
struct wf_sensor *amps;
int fake_volts : 1;
int quadratic : 1;
struct wf_sensor sens;
};
#define to_smu_cpu_power(c) container_of(c, struct smu_cpu_power_sensor, sens)
static struct smu_cpu_power_sensor *smu_cpu_power;
static void smu_cpu_power_release(struct wf_sensor *sr)
{
struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
if (pow->volts)
wf_put_sensor(pow->volts);
if (pow->amps)
wf_put_sensor(pow->amps);
kfree(pow);
}
static int smu_cpu_power_get(struct wf_sensor *sr, s32 *value)
{
struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
s32 volts, amps, power;
u64 tmps, tmpa, tmpb;
int rc;
rc = pow->amps->ops->get_value(pow->amps, &amps);
if (rc)
return rc;
if (pow->fake_volts) {
*value = amps * 12 - 0x30000;
return 0;
}
rc = pow->volts->ops->get_value(pow->volts, &volts);
if (rc)
return rc;
power = (s32)((((u64)volts) * ((u64)amps)) >> 16);
if (!pow->quadratic) {
*value = power;
return 0;
}
tmps = (((u64)power) * ((u64)power)) >> 16;
tmpa = ((u64)cpuvcp->power_quads[0]) * tmps;
tmpb = ((u64)cpuvcp->power_quads[1]) * ((u64)power);
*value = (tmpa >> 28) + (tmpb >> 28) + (cpuvcp->power_quads[2] >> 12);
return 0;
}
static struct wf_sensor_ops smu_cpu_power_ops = {
.get_value = smu_cpu_power_get,
.release = smu_cpu_power_release,
.owner = THIS_MODULE,
};
static struct smu_cpu_power_sensor *
smu_cpu_power_create(struct wf_sensor *volts, struct wf_sensor *amps)
{
struct smu_cpu_power_sensor *pow;
pow = kmalloc(sizeof(struct smu_cpu_power_sensor), GFP_KERNEL);
if (pow == NULL)
return NULL;
pow->sens.ops = &smu_cpu_power_ops;
pow->sens.name = "cpu-power";
wf_get_sensor(volts);
pow->volts = volts;
wf_get_sensor(amps);
pow->amps = amps;
/* Some early machines need a faked voltage */
if (debugswitches && ((*debugswitches) & 0x80)) {
printk(KERN_INFO "windfarm: CPU Power sensor using faked"
" voltage !\n");
pow->fake_volts = 1;
} else
pow->fake_volts = 0;
/* Try to use quadratic transforms on PowerMac8,1 and 9,1 for now,
* I yet have to figure out what's up with 8,2 and will have to
* adjust for later, unless we can 100% trust the SDB partition...
*/
if ((machine_is_compatible("PowerMac8,1") ||
machine_is_compatible("PowerMac8,2") ||
machine_is_compatible("PowerMac9,1")) &&
cpuvcp_version >= 2) {
pow->quadratic = 1;
DBG("windfarm: CPU Power using quadratic transform\n");
} else
pow->quadratic = 0;
if (wf_register_sensor(&pow->sens))
goto fail;
return pow;
fail:
kfree(pow);
return NULL;
}
static int smu_fetch_param_partitions(void)
{
struct smu_sdbp_header *hdr;
/* Get CPU voltage/current/power calibration data */
hdr = smu_get_sdb_partition(SMU_SDB_CPUVCP_ID, NULL);
if (hdr == NULL) {
DBG("wf: cpuvcp partition (%02x) not found\n",
SMU_SDB_CPUVCP_ID);
return -ENODEV;
}
cpuvcp = (struct smu_sdbp_cpuvcp *)&hdr[1];
/* Keep version around */
cpuvcp_version = hdr->version;
/* Get CPU diode calibration data */
hdr = smu_get_sdb_partition(SMU_SDB_CPUDIODE_ID, NULL);
if (hdr == NULL) {
DBG("wf: cpudiode partition (%02x) not found\n",
SMU_SDB_CPUDIODE_ID);
return -ENODEV;
}
cpudiode = (struct smu_sdbp_cpudiode *)&hdr[1];
/* Get slots power calibration data if any */
hdr = smu_get_sdb_partition(SMU_SDB_SLOTSPOW_ID, NULL);
if (hdr != NULL)
slotspow = (struct smu_sdbp_slotspow *)&hdr[1];
/* Get debug switches if any */
hdr = smu_get_sdb_partition(SMU_SDB_DEBUG_SWITCHES_ID, NULL);
if (hdr != NULL)
debugswitches = (u8 *)&hdr[1];
return 0;
}
static int __init smu_sensors_init(void)
{
struct device_node *smu, *sensors, *s;
struct smu_ad_sensor *volt_sensor = NULL, *curr_sensor = NULL;
int rc;
if (!smu_present())
return -ENODEV;
/* Get parameters partitions */
rc = smu_fetch_param_partitions();
if (rc)
return rc;
smu = of_find_node_by_type(NULL, "smu");
if (smu == NULL)
return -ENODEV;
/* Look for sensors subdir */
for (sensors = NULL;
(sensors = of_get_next_child(smu, sensors)) != NULL;)
if (!strcmp(sensors->name, "sensors"))
break;
of_node_put(smu);
/* Create basic sensors */
for (s = NULL;
sensors && (s = of_get_next_child(sensors, s)) != NULL;) {
struct smu_ad_sensor *ads;
ads = smu_ads_create(s);
if (ads == NULL)
continue;
list_add(&ads->link, &smu_ads);
/* keep track of cpu voltage & current */
if (!strcmp(ads->sens.name, "cpu-voltage"))
volt_sensor = ads;
else if (!strcmp(ads->sens.name, "cpu-current"))
curr_sensor = ads;
}
of_node_put(sensors);
/* Create CPU power sensor if possible */
if (volt_sensor && curr_sensor)
smu_cpu_power = smu_cpu_power_create(&volt_sensor->sens,
&curr_sensor->sens);
return 0;
}
static void __exit smu_sensors_exit(void)
{
struct smu_ad_sensor *ads;
/* dispose of power sensor */
if (smu_cpu_power)
wf_unregister_sensor(&smu_cpu_power->sens);
/* dispose of basic sensors */
while (!list_empty(&smu_ads)) {
ads = list_entry(smu_ads.next, struct smu_ad_sensor, link);
list_del(&ads->link);
wf_unregister_sensor(&ads->sens);
}
}
module_init(smu_sensors_init);
module_exit(smu_sensors_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control");
MODULE_LICENSE("GPL");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment