summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>2005-11-07 16:08:17 +1100
committerPaul Mackerras <paulus@samba.org>2005-11-08 11:17:56 +1100
commit75722d3992f57375c0cc029dcceb2334a45ceff1 (patch)
treed3f63b3ea80790c2f29ea435781c1331f17d269e
parent7d49697ef92bd2cf84ab53bd4cea82fefb197fb9 (diff)
[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>
-rw-r--r--drivers/macintosh/Kconfig19
-rw-r--r--drivers/macintosh/Makefile9
-rw-r--r--drivers/macintosh/smu.c2
-rw-r--r--drivers/macintosh/windfarm.h131
-rw-r--r--drivers/macintosh/windfarm_core.c426
-rw-r--r--drivers/macintosh/windfarm_cpufreq_clamp.c105
-rw-r--r--drivers/macintosh/windfarm_lm75_sensor.c263
-rw-r--r--drivers/macintosh/windfarm_pid.c145
-rw-r--r--drivers/macintosh/windfarm_pid.h84
-rw-r--r--drivers/macintosh/windfarm_pm81.c879
-rw-r--r--drivers/macintosh/windfarm_pm91.c814
-rw-r--r--drivers/macintosh/windfarm_smu_controls.c282
-rw-r--r--drivers/macintosh/windfarm_smu_sensors.c479
13 files changed, 3638 insertions, 0 deletions
diff --git a/drivers/macintosh/Kconfig b/drivers/macintosh/Kconfig
index bc3e096d84f7..a0ea44c3e8b1 100644
--- a/drivers/macintosh/Kconfig
+++ b/drivers/macintosh/Kconfig
@@ -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
diff --git a/drivers/macintosh/Makefile b/drivers/macintosh/Makefile
index 236291bd48a4..f4657aa81fb0 100644
--- a/drivers/macintosh/Makefile
+++ b/drivers/macintosh/Makefile
@@ -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
diff --git a/drivers/macintosh/smu.c b/drivers/macintosh/smu.c
index a83c4acf5710..e8378274d710 100644
--- a/drivers/macintosh/smu.c
+++ b/drivers/macintosh/smu.c
@@ -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);
}
}
diff --git a/drivers/macintosh/windfarm.h b/drivers/macintosh/windfarm.h
new file mode 100644
index 000000000000..3f0cb0312ea3
--- /dev/null
+++ b/drivers/macintosh/windfarm.h
@@ -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__ */
diff --git a/drivers/macintosh/windfarm_core.c b/drivers/macintosh/windfarm_core.c
new file mode 100644
index 000000000000..6c2a471ea6c0
--- /dev/null
+++ b/drivers/macintosh/windfarm_core.c
@@ -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");
+
diff --git a/drivers/macintosh/windfarm_cpufreq_clamp.c b/drivers/macintosh/windfarm_cpufreq_clamp.c
new file mode 100644
index 000000000000..607dbaca69c9
--- /dev/null
+++ b/drivers/macintosh/windfarm_cpufreq_clamp.c
@@ -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");
+
diff --git a/drivers/macintosh/windfarm_lm75_sensor.c b/drivers/macintosh/windfarm_lm75_sensor.c
new file mode 100644
index 000000000000..a0a41ad0f2b5
--- /dev/null
+++ b/drivers/macintosh/windfarm_lm75_sensor.c
@@ -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");
+
diff --git a/drivers/macintosh/windfarm_pid.c b/drivers/macintosh/windfarm_pid.c
new file mode 100644
index 000000000000..2e803b368757
--- /dev/null
+++ b/drivers/macintosh/windfarm_pid.c
@@ -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)
+{