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 ...@@ -169,6 +169,25 @@ config THERM_PM72
This driver provides thermostat and fan control for the desktop This driver provides thermostat and fan control for the desktop
G5 machines. 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 config ANSLCD
tristate "Support for ANS LCD display" tristate "Support for ANS LCD display"
depends on ADB_CUDA && PPC_PMAC depends on ADB_CUDA && PPC_PMAC
......
...@@ -26,3 +26,12 @@ obj-$(CONFIG_ADB_MACIO) += macio-adb.o ...@@ -26,3 +26,12 @@ obj-$(CONFIG_ADB_MACIO) += macio-adb.o
obj-$(CONFIG_THERM_PM72) += therm_pm72.o obj-$(CONFIG_THERM_PM72) += therm_pm72.o
obj-$(CONFIG_THERM_WINDTUNNEL) += therm_windtunnel.o obj-$(CONFIG_THERM_WINDTUNNEL) += therm_windtunnel.o
obj-$(CONFIG_THERM_ADT746X) += therm_adt746x.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) ...@@ -590,6 +590,8 @@ static void smu_expose_childs(void *unused)
sprintf(name, "smu-i2c-%02x", *reg); sprintf(name, "smu-i2c-%02x", *reg);
of_platform_device_create(np, name, &smu->of_dev->dev); 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);
This diff is collapsed.
This diff is collapsed.
/*
* 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");
This diff is collapsed.
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