mirror of
https://github.com/ukui/kernel.git
synced 2026-03-09 10:07:04 -07:00
[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: Benjamin Herrenschmidt <benh@kernel.crashing.org> Signed-off-by: Paul Mackerras <paulus@samba.org>
This commit is contained in:
committed by
Paul Mackerras
parent
7d49697ef9
commit
75722d3992
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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__ */
|
||||
@@ -0,0 +1,426 @@
|
||||
/*
|
||||
* 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");
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#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");
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* 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");
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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);
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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);
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* 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 = ∁
|
||||
|
||||
/* 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");
|
||||
|
||||
@@ -0,0 +1,479 @@
|
||||
/*
|
||||
* 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, &s);
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user