]> err.no Git - linux-2.6/commitdiff
[PATCH] ppc64: Thermal control for SMU based machines
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>
Mon, 7 Nov 2005 05:08:17 +0000 (16:08 +1100)
committerPaul Mackerras <paulus@samba.org>
Tue, 8 Nov 2005 00:17:56 +0000 (11:17 +1100)
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>
13 files changed:
drivers/macintosh/Kconfig
drivers/macintosh/Makefile
drivers/macintosh/smu.c
drivers/macintosh/windfarm.h [new file with mode: 0644]
drivers/macintosh/windfarm_core.c [new file with mode: 0644]
drivers/macintosh/windfarm_cpufreq_clamp.c [new file with mode: 0644]
drivers/macintosh/windfarm_lm75_sensor.c [new file with mode: 0644]
drivers/macintosh/windfarm_pid.c [new file with mode: 0644]
drivers/macintosh/windfarm_pid.h [new file with mode: 0644]
drivers/macintosh/windfarm_pm81.c [new file with mode: 0644]
drivers/macintosh/windfarm_pm91.c [new file with mode: 0644]
drivers/macintosh/windfarm_smu_controls.c [new file with mode: 0644]
drivers/macintosh/windfarm_smu_sensors.c [new file with mode: 0644]

index bc3e096d84f75a5660e1c98ba9db99d955a1a075..a0ea44c3e8b10ec9ca68689718c3da6423961e3b 100644 (file)
@@ -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
index 236291bd48a4117fe4e7eb12ae8075a9142a2ebd..f4657aa81fb0474d92f1c525af90015db1d5a6c3 100644 (file)
@@ -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
index a83c4acf57100f35a813bf8324047b8f0d2d9168..e8378274d710f02f0d93facd8de2fa050ec6e4f4 100644 (file)
@@ -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 (file)
index 0000000..3f0cb03
--- /dev/null
@@ -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 (file)
index 0000000..6c2a471
--- /dev/null
@@ -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 (file)
index 0000000..607dbac
--- /dev/null
@@ -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 (file)
index 0000000..a0a41ad
--- /dev/null
@@ -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 (file)
index 0000000..2e803b3
--- /dev/null
@@ -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);
diff --git a/drivers/macintosh/windfarm_pid.h b/drivers/macintosh/windfarm_pid.h
new file mode 100644 (file)
index 0000000..a364c2a
--- /dev/null
@@ -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);
diff --git a/drivers/macintosh/windfarm_pm81.c b/drivers/macintosh/windfarm_pm81.c
new file mode 100644 (file)
index 0000000..322c74b
--- /dev/null
@@ -0,0 +1,879 @@
+/*
+ * Windfarm PowerMac thermal control. iMac G5
+ *
+ * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
+ *                    <benh@kernel.crashing.org>
+ *
+ * Released under the term of the GNU GPL v2.
+ *
+ * The algorithm used is the PID control algorithm, used the same
+ * way the published Darwin code does, using the same values that
+ * are present in the Darwin 8.2 snapshot property lists (note however
+ * that none of the code has been re-used, it's a complete re-implementation
+ *
+ * The various control loops found in Darwin config file are:
+ *
+ * PowerMac8,1 and PowerMac8,2
+ * ===========================
+ *
+ * System Fans control loop. Different based on models. In addition to the
+ * usual PID algorithm, the control loop gets 2 additional pairs of linear
+ * scaling factors (scale/offsets) expressed as 4.12 fixed point values
+ * signed offset, unsigned scale)
+ *
+ * The targets are modified such as:
+ *  - the linked control (second control) gets the target value as-is
+ *    (typically the drive fan)
+ *  - the main control (first control) gets the target value scaled with
+ *    the first pair of factors, and is then modified as below
+ *  - the value of the target of the CPU Fan control loop is retreived,
+ *    scaled with the second pair of factors, and the max of that and
+ *    the scaled target is applied to the main control.
+ *
+ * # model_id: 2
+ *   controls       : system-fan, drive-bay-fan
+ *   sensors        : hd-temp
+ *   PID params     : G_d = 0x15400000
+ *                    G_p = 0x00200000
+ *                    G_r = 0x000002fd
+ *                    History = 2 entries
+ *                    Input target = 0x3a0000
+ *                    Interval = 5s
+ *   linear-factors : offset = 0xff38 scale  = 0x0ccd
+ *                    offset = 0x0208 scale  = 0x07ae
+ *
+ * # model_id: 3
+ *   controls       : system-fan, drive-bay-fan
+ *   sensors        : hd-temp
+ *   PID params     : G_d = 0x08e00000
+ *                    G_p = 0x00566666
+ *                    G_r = 0x0000072b
+ *                    History = 2 entries
+ *                    Input target = 0x350000
+ *                    Interval = 5s
+ *   linear-factors : offset = 0xff38 scale  = 0x0ccd
+ *                    offset = 0x0000 scale  = 0x0000
+ *
+ * # model_id: 5
+ *   controls       : system-fan
+ *   sensors        : hd-temp
+ *   PID params     : G_d = 0x15400000
+ *                    G_p = 0x00233333
+ *                    G_r = 0x000002fd
+ *                    History = 2 entries
+ *                    Input target = 0x3a0000
+ *                    Interval = 5s
+ *   linear-factors : offset = 0x0000 scale  = 0x1000
+ *                    offset = 0x0091 scale  = 0x0bae
+ *
+ * CPU Fan control loop. The loop is identical for all models. it
+ * has an additional pair of scaling factor. This is used to scale the
+ * systems fan control loop target result (the one before it gets scaled
+ * by the System Fans control loop itself). Then, the max value of the
+ * calculated target value and system fan value is sent to the fans
+ *
+ *   controls       : cpu-fan
+ *   sensors        : cpu-temp cpu-power
+ *   PID params     : From SMU sdb partition
+ *   linear-factors : offset = 0xfb50 scale  = 0x1000
+ *
+ * CPU Slew control loop. Not implemented. The cpufreq driver in linux is
+ * completely separate for now, though we could find a way to link it, either
+ * as a client reacting to overtemp notifications, or directling monitoring
+ * the CPU temperature
+ *
+ * WARNING ! The CPU control loop requires the CPU tmax for the current
+ * operating point. However, we currently are completely separated from
+ * the cpufreq driver and thus do not know what the current operating
+ * point is. Fortunately, we also do not have any hardware supporting anything
+ * but operating point 0 at the moment, thus we just peek that value directly
+ * from the SDB partition. If we ever end up with actually slewing the system
+ * clock and thus changing operating points, we'll have to find a way to
+ * communicate with the CPU freq driver;
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/kmod.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/sections.h>
+#include <asm/smu.h>
+
+#include "windfarm.h"
+#include "windfarm_pid.h"
+
+#define VERSION "0.4"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(args...)   printk(args)
+#else
+#define DBG(args...)   do { } while(0)
+#endif
+
+/* define this to force CPU overtemp to 74 degree, useful for testing
+ * the overtemp code
+ */
+#undef HACKED_OVERTEMP
+
+static int wf_smu_mach_model;  /* machine model id */
+
+static struct device *wf_smu_dev;
+
+/* Controls & sensors */
+static struct wf_sensor        *sensor_cpu_power;
+static struct wf_sensor        *sensor_cpu_temp;
+static struct wf_sensor        *sensor_hd_temp;
+static struct wf_control *fan_cpu_main;
+static struct wf_control *fan_hd;
+static struct wf_control *fan_system;
+static struct wf_control *cpufreq_clamp;
+
+/* Set to kick the control loop into life */
+static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started;
+
+/* Failure handling.. could be nicer */
+#define FAILURE_FAN            0x01
+#define FAILURE_SENSOR         0x02
+#define FAILURE_OVERTEMP       0x04
+
+static unsigned int wf_smu_failure_state;
+static int wf_smu_readjust, wf_smu_skipping;
+
+/*
+ * ****** System Fans Control Loop ******
+ *
+ */
+
+/* Parameters for the System Fans control loop. Parameters
+ * not in this table such as interval, history size, ...
+ * are common to all versions and thus hard coded for now.
+ */
+struct wf_smu_sys_fans_param {
+       int     model_id;
+       s32     itarget;
+       s32     gd, gp, gr;
+
+       s16     offset0;
+       u16     scale0;
+       s16     offset1;
+       u16     scale1;
+};
+
+#define WF_SMU_SYS_FANS_INTERVAL       5
+#define WF_SMU_SYS_FANS_HISTORY_SIZE   2
+
+/* State data used by the system fans control loop
+ */
+struct wf_smu_sys_fans_state {
+       int                     ticks;
+       s32                     sys_setpoint;
+       s32                     hd_setpoint;
+       s16                     offset0;
+       u16                     scale0;
+       s16                     offset1;
+       u16                     scale1;
+       struct wf_pid_state     pid;
+};
+
+/*
+ * Configs for SMU Sytem Fan control loop
+ */
+static struct wf_smu_sys_fans_param wf_smu_sys_all_params[] = {
+       /* Model ID 2 */
+       {
+               .model_id       = 2,
+               .itarget        = 0x3a0000,
+               .gd             = 0x15400000,
+               .gp             = 0x00200000,
+               .gr             = 0x000002fd,
+               .offset0        = 0xff38,
+               .scale0         = 0x0ccd,
+               .offset1        = 0x0208,
+               .scale1         = 0x07ae,
+       },
+       /* Model ID 3 */
+       {
+               .model_id       = 2,
+               .itarget        = 0x350000,
+               .gd             = 0x08e00000,
+               .gp             = 0x00566666,
+               .gr             = 0x0000072b,
+               .offset0        = 0xff38,
+               .scale0         = 0x0ccd,
+               .offset1        = 0x0000,
+               .scale1         = 0x0000,
+       },
+       /* Model ID 5 */
+       {
+               .model_id       = 2,
+               .itarget        = 0x3a0000,
+               .gd             = 0x15400000,
+               .gp             = 0x00233333,
+               .gr             = 0x000002fd,
+               .offset0        = 0x0000,
+               .scale0         = 0x1000,
+               .offset1        = 0x0091,
+               .scale1         = 0x0bae,
+       },
+};
+#define WF_SMU_SYS_FANS_NUM_CONFIGS ARRAY_SIZE(wf_smu_sys_all_params)
+
+static struct wf_smu_sys_fans_state *wf_smu_sys_fans;
+
+/*
+ * ****** CPU Fans Control Loop ******
+ *
+ */
+
+
+#define WF_SMU_CPU_FANS_INTERVAL       1
+#define WF_SMU_CPU_FANS_MAX_HISTORY    16
+#define WF_SMU_CPU_FANS_SIBLING_SCALE  0x00001000
+#define WF_SMU_CPU_FANS_SIBLING_OFFSET 0xfffffb50
+
+/* State data used by the cpu fans control loop
+ */
+struct wf_smu_cpu_fans_state {
+       int                     ticks;
+       s32                     cpu_setpoint;
+       s32                     scale;
+       s32                     offset;
+       struct wf_cpu_pid_state pid;
+};
+
+static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans;
+
+
+
+/*
+ * ***** Implementation *****
+ *
+ */
+
+static void wf_smu_create_sys_fans(void)
+{
+       struct wf_smu_sys_fans_param *param = NULL;
+       struct wf_pid_param pid_param;
+       int i;
+
+       /* First, locate the params for this model */
+       for (i = 0; i < WF_SMU_SYS_FANS_NUM_CONFIGS; i++)
+               if (wf_smu_sys_all_params[i].model_id == wf_smu_mach_model) {
+                       param = &wf_smu_sys_all_params[i];
+                       break;
+               }
+
+       /* No params found, put fans to max */
+       if (param == NULL) {
+               printk(KERN_WARNING "windfarm: System fan config not found "
+                      "for this machine model, max fan speed\n");
+               goto fail;
+       }
+
+       /* Alloc & initialize state */
+       wf_smu_sys_fans = kmalloc(sizeof(struct wf_smu_sys_fans_state),
+                                 GFP_KERNEL);
+       if (wf_smu_sys_fans == NULL) {
+               printk(KERN_WARNING "windfarm: Memory allocation error"
+                      " max fan speed\n");
+               goto fail;
+       }
+       wf_smu_sys_fans->ticks = 1;
+       wf_smu_sys_fans->scale0 = param->scale0;
+       wf_smu_sys_fans->offset0 = param->offset0;
+       wf_smu_sys_fans->scale1 = param->scale1;
+       wf_smu_sys_fans->offset1 = param->offset1;
+
+       /* Fill PID params */
+       pid_param.gd = param->gd;
+       pid_param.gp = param->gp;
+       pid_param.gr = param->gr;
+       pid_param.interval = WF_SMU_SYS_FANS_INTERVAL;
+       pid_param.history_len = WF_SMU_SYS_FANS_HISTORY_SIZE;
+       pid_param.itarget = param->itarget;
+       pid_param.min = fan_system->ops->get_min(fan_system);
+       pid_param.max = fan_system->ops->get_max(fan_system);
+       if (fan_hd) {
+               pid_param.min =
+                       max(pid_param.min,fan_hd->ops->get_min(fan_hd));
+               pid_param.max =
+                       min(pid_param.max,fan_hd->ops->get_max(fan_hd));
+       }
+       wf_pid_init(&wf_smu_sys_fans->pid, &pid_param);
+
+       DBG("wf: System Fan control initialized.\n");
+       DBG("    itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
+           FIX32TOPRINT(pid_param.itarget), pid_param.min, pid_param.max);
+       return;
+
+ fail:
+
+       if (fan_system)
+               wf_control_set_max(fan_system);
+       if (fan_hd)
+               wf_control_set_max(fan_hd);
+}
+
+static void wf_smu_sys_fans_tick(struct wf_smu_sys_fans_state *st)
+{
+       s32 new_setpoint, temp, scaled, cputarget;
+       int rc;
+
+       if (--st->ticks != 0) {
+               if (wf_smu_readjust)
+                       goto readjust;
+               return;
+       }
+       st->ticks = WF_SMU_SYS_FANS_INTERVAL;
+
+       rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp);
+       if (rc) {
+               printk(KERN_WARNING "windfarm: HD temp sensor error %d\n",
+                      rc);
+               wf_smu_failure_state |= FAILURE_SENSOR;
+               return;
+       }
+
+       DBG("wf_smu: System Fans tick ! HD temp: %d.%03d\n",
+           FIX32TOPRINT(temp));
+
+       if (temp > (st->pid.param.itarget + 0x50000))
+               wf_smu_failure_state |= FAILURE_OVERTEMP;
+
+       new_setpoint = wf_pid_run(&st->pid, temp);
+
+       DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);
+
+       scaled = ((((s64)new_setpoint) * (s64)st->scale0) >> 12) + st->offset0;
+
+       DBG("wf_smu: scaled setpoint: %d RPM\n", (int)scaled);
+
+       cputarget = wf_smu_cpu_fans ? wf_smu_cpu_fans->pid.target : 0;
+       cputarget = ((((s64)cputarget) * (s64)st->scale1) >> 12) + st->offset1;
+       scaled = max(scaled, cputarget);
+       scaled = max(scaled, st->pid.param.min);
+       scaled = min(scaled, st->pid.param.max);
+
+       DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)scaled);
+
+       if (st->sys_setpoint == scaled && new_setpoint == st->hd_setpoint)
+               return;
+       st->sys_setpoint = scaled;
+       st->hd_setpoint = new_setpoint;
+ readjust:
+       if (fan_system && wf_smu_failure_state == 0) {
+               rc = fan_system->ops->set_value(fan_system, st->sys_setpoint);
+               if (rc) {
+                       printk(KERN_WARNING "windfarm: Sys fan error %d\n",
+                              rc);
+                       wf_smu_failure_state |= FAILURE_FAN;
+               }
+       }
+       if (fan_hd && wf_smu_failure_state == 0) {
+               rc = fan_hd->ops->set_value(fan_hd, st->hd_setpoint);
+               if (rc) {
+                       printk(KERN_WARNING "windfarm: HD fan error %d\n",
+                              rc);
+                       wf_smu_failure_state |= FAILURE_FAN;
+               }
+       }
+}
+
+static void wf_smu_create_cpu_fans(void)
+{
+       struct wf_cpu_pid_param pid_param;
+       struct smu_sdbp_header *hdr;
+       struct smu_sdbp_cpupiddata *piddata;
+       struct smu_sdbp_fvt *fvt;
+       s32 tmax, tdelta, maxpow, powadj;
+
+       /* First, locate the PID params in SMU SBD */
+       hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL);
+       if (hdr == 0) {
+               printk(KERN_WARNING "windfarm: CPU PID fan config not found "
+                      "max fan speed\n");
+               goto fail;
+       }
+       piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
+
+       /* Get the FVT params for operating point 0 (the only supported one
+        * for now) in order to get tmax
+        */
+       hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);
+       if (hdr) {
+               fvt = (struct smu_sdbp_fvt *)&hdr[1];
+               tmax = ((s32)fvt->maxtemp) << 16;
+       } else
+               tmax = 0x5e0000; /* 94 degree default */
+
+       /* Alloc & initialize state */
+       wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state),
+                                 GFP_KERNEL);
+       if (wf_smu_cpu_fans == NULL)
+               goto fail;
+               wf_smu_cpu_fans->ticks = 1;
+
+       wf_smu_cpu_fans->scale = WF_SMU_CPU_FANS_SIBLING_SCALE;
+       wf_smu_cpu_fans->offset = WF_SMU_CPU_FANS_SIBLING_OFFSET;
+
+       /* Fill PID params */
+       pid_param.interval = WF_SMU_CPU_FANS_INTERVAL;
+       pid_param.history_len = piddata->history_len;
+       if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) {
+               printk(KERN_WARNING "windfarm: History size overflow on "
+                      "CPU control loop (%d)\n", piddata->history_len);
+               pid_param.history_len = WF_CPU_PID_MAX_HISTORY;
+       }
+       pid_param.gd = piddata->gd;
+       pid_param.gp = piddata->gp;
+       pid_param.gr = piddata->gr / pid_param.history_len;
+
+       tdelta = ((s32)piddata->target_temp_delta) << 16;
+       maxpow = ((s32)piddata->max_power) << 16;
+       powadj = ((s32)piddata->power_adj) << 16;
+
+       pid_param.tmax = tmax;
+       pid_param.ttarget = tmax - tdelta;
+       pid_param.pmaxadj = maxpow - powadj;
+
+       pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main);
+       pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main);
+
+       wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param);
+
+       DBG("wf: CPU Fan control initialized.\n");
+       DBG("    ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n",
+           FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax),
+           pid_param.min, pid_param.max);
+
+       return;
+
+ fail:
+       printk(KERN_WARNING "windfarm: CPU fan config not found\n"
+              "for this machine model, max fan speed\n");
+
+       if (cpufreq_clamp)
+               wf_control_set_max(cpufreq_clamp);
+       if (fan_cpu_main)
+               wf_control_set_max(fan_cpu_main);
+}
+
+static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)
+{
+       s32 new_setpoint, temp, power, systarget;
+       int rc;
+
+       if (--st->ticks != 0) {
+               if (wf_smu_readjust)
+                       goto readjust;
+               return;
+       }
+       st->ticks = WF_SMU_CPU_FANS_INTERVAL;
+
+       rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp);
+       if (rc) {
+               printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n",
+                      rc);
+               wf_smu_failure_state |= FAILURE_SENSOR;
+               return;
+       }
+
+       rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power);
+       if (rc) {
+               printk(KERN_WARNING "windfarm: CPU power sensor error %d\n",
+                      rc);
+               wf_smu_failure_state |= FAILURE_SENSOR;
+               return;
+       }
+
+       DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n",
+           FIX32TOPRINT(temp), FIX32TOPRINT(power));
+
+#ifdef HACKED_OVERTEMP
+       if (temp > 0x4a0000)
+               wf_smu_failure_state |= FAILURE_OVERTEMP;
+#else
+       if (temp > st->pid.param.tmax)
+               wf_smu_failure_state |= FAILURE_OVERTEMP;
+#endif
+       new_setpoint = wf_cpu_pid_run(&st->pid, power, temp);
+
+       DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);
+
+       systarget = wf_smu_sys_fans ? wf_smu_sys_fans->pid.target : 0;
+       systarget = ((((s64)systarget) * (s64)st->scale) >> 12)
+               + st->offset;
+       new_setpoint = max(new_setpoint, systarget);
+       new_setpoint = max(new_setpoint, st->pid.param.min);
+       new_setpoint = min(new_setpoint, st->pid.param.max);
+
+       DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)new_setpoint);
+
+       if (st->cpu_setpoint == new_setpoint)
+               return;
+       st->cpu_setpoint = new_setpoint;
+ readjust:
+       if (fan_cpu_main && wf_smu_failure_state == 0) {
+               rc = fan_cpu_main->ops->set_value(fan_cpu_main,
+                                                 st->cpu_setpoint);
+               if (rc) {
+                       printk(KERN_WARNING "windfarm: CPU main fan"
+                              " error %d\n", rc);
+                       wf_smu_failure_state |= FAILURE_FAN;
+               }
+       }
+}
+
+
+/*
+ * ****** Attributes ******
+ *
+ */
+
+#define BUILD_SHOW_FUNC_FIX(name, data)                                \
+static ssize_t show_##name(struct device *dev,                  \
+                          struct device_attribute *attr,       \
+                          char *buf)                           \
+{                                                              \
+       ssize_t r;                                              \
+       s32 val = 0;                                            \
+       data->ops->get_value(data, &val);                       \
+       r = sprintf(buf, "%d.%03d", FIX32TOPRINT(val));         \
+       return r;                                               \
+}                                                               \
+static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL);
+
+
+#define BUILD_SHOW_FUNC_INT(name, data)                                \
+static ssize_t show_##name(struct device *dev,                  \
+                          struct device_attribute *attr,       \
+                          char *buf)                           \
+{                                                              \
+       s32 val = 0;                                            \
+       data->ops->get_value(data, &val);                       \
+       return sprintf(buf, "%d", val);                         \
+}                                                               \
+static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL);
+
+BUILD_SHOW_FUNC_INT(cpu_fan, fan_cpu_main);
+BUILD_SHOW_FUNC_INT(sys_fan, fan_system);
+BUILD_SHOW_FUNC_INT(hd_fan, fan_hd);
+
+BUILD_SHOW_FUNC_FIX(cpu_temp, sensor_cpu_temp);
+BUILD_SHOW_FUNC_FIX(cpu_power, sensor_cpu_power);
+BUILD_SHOW_FUNC_FIX(hd_temp, sensor_hd_temp);
+
+/*
+ * ****** Setup / Init / Misc ... ******
+ *
+ */
+
+static void wf_smu_tick(void)
+{
+       unsigned int last_failure = wf_smu_failure_state;
+       unsigned int new_failure;
+
+       if (!wf_smu_started) {
+               DBG("wf: creating control loops !\n");
+               wf_smu_create_sys_fans();
+               wf_smu_create_cpu_fans();
+               wf_smu_started = 1;
+       }
+
+       /* Skipping ticks */
+       if (wf_smu_skipping && --wf_smu_skipping)
+               return;
+
+       wf_smu_failure_state = 0;
+       if (wf_smu_sys_fans)
+               wf_smu_sys_fans_tick(wf_smu_sys_fans);
+       if (wf_smu_cpu_fans)
+               wf_smu_cpu_fans_tick(wf_smu_cpu_fans);
+
+       wf_smu_readjust = 0;
+       new_failure = wf_smu_failure_state & ~last_failure;
+
+       /* If entering failure mode, clamp cpufreq and ramp all
+        * fans to full speed.
+        */
+       if (wf_smu_failure_state && !last_failure) {
+               if (cpufreq_clamp)
+                       wf_control_set_max(cpufreq_clamp);
+               if (fan_system)
+                       wf_control_set_max(fan_system);
+               if (fan_cpu_main)
+                       wf_control_set_max(fan_cpu_main);
+               if (fan_hd)
+                       wf_control_set_max(fan_hd);
+       }
+
+       /* If leaving failure mode, unclamp cpufreq and readjust
+        * all fans on next iteration
+        */
+       if (!wf_smu_failure_state && last_failure) {
+               if (cpufreq_clamp)
+                       wf_control_set_min(cpufreq_clamp);
+               wf_smu_readjust = 1;
+       }
+
+       /* Overtemp condition detected, notify and start skipping a couple
+        * ticks to let the temperature go down
+        */
+       if (new_failure & FAILURE_OVERTEMP) {
+               wf_set_overtemp();
+               wf_smu_skipping = 2;
+       }
+
+       /* We only clear the overtemp condition if overtemp is cleared
+        * _and_ no other failure is present. Since a sensor error will
+        * clear the overtemp condition (can't measure temperature) at
+        * the control loop levels, but we don't want to keep it clear
+        * here in this case
+        */
+       if (new_failure == 0 && last_failure & FAILURE_OVERTEMP)
+               wf_clear_overtemp();
+}
+
+static void wf_smu_new_control(struct wf_control *ct)
+{
+       if (wf_smu_all_controls_ok)
+               return;
+
+       if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-fan")) {
+               if (wf_get_control(ct) == 0) {
+                       fan_cpu_main = ct;
+                       device_create_file(wf_smu_dev, &dev_attr_cpu_fan);
+               }
+       }
+
+       if (fan_system == NULL && !strcmp(ct->name, "system-fan")) {
+               if (wf_get_control(ct) == 0) {
+                       fan_system = ct;
+                       device_create_file(wf_smu_dev, &dev_attr_sys_fan);
+               }
+       }
+
+       if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) {
+               if (wf_get_control(ct) == 0)
+                       cpufreq_clamp = ct;
+       }
+
+       /* Darwin property list says the HD fan is only for model ID
+        * 0, 1, 2 and 3
+        */
+
+       if (wf_smu_mach_model > 3) {
+               if (fan_system && fan_cpu_main && cpufreq_clamp)
+                       wf_smu_all_controls_ok = 1;
+               return;
+       }
+
+       if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) {
+               if (wf_get_control(ct) == 0) {
+                       fan_hd = ct;
+                       device_create_file(wf_smu_dev, &dev_attr_hd_fan);
+               }
+       }
+
+       if (fan_system && fan_hd && fan_cpu_main && cpufreq_clamp)
+               wf_smu_all_controls_ok = 1;
+}
+
+static void wf_smu_new_sensor(struct wf_sensor *sr)
+{
+       if (wf_smu_all_sensors_ok)
+               return;
+
+       if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) {
+               if (wf_get_sensor(sr) == 0) {
+                       sensor_cpu_power = sr;
+                       device_create_file(wf_smu_dev, &dev_attr_cpu_power);
+               }
+       }
+
+       if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) {
+               if (wf_get_sensor(sr) == 0) {
+                       sensor_cpu_temp = sr;
+                       device_create_file(wf_smu_dev, &dev_attr_cpu_temp);
+               }
+       }
+
+       if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) {
+               if (wf_get_sensor(sr) == 0) {
+                       sensor_hd_temp = sr;
+                       device_create_file(wf_smu_dev, &dev_attr_hd_temp);
+               }
+       }
+
+       if (sensor_cpu_power && sensor_cpu_temp && sensor_hd_temp)
+               wf_smu_all_sensors_ok = 1;
+}
+
+
+static int wf_smu_notify(struct notifier_block *self,
+                              unsigned long event, void *data)
+{
+       switch(event) {
+       case WF_EVENT_NEW_CONTROL:
+               DBG("wf: new control %s detected\n",
+                   ((struct wf_control *)data)->name);
+               wf_smu_new_control(data);
+               wf_smu_readjust = 1;
+               break;
+       case WF_EVENT_NEW_SENSOR:
+               DBG("wf: new sensor %s detected\n",
+                   ((struct wf_sensor *)data)->name);
+               wf_smu_new_sensor(data);
+               break;
+       case WF_EVENT_TICK:
+               if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok)
+                       wf_smu_tick();
+       }
+
+       return 0;
+}
+
+static struct notifier_block wf_smu_events = {
+       .notifier_call  = wf_smu_notify,
+};
+
+static int wf_init_pm(void)
+{
+       struct smu_sdbp_header *hdr;
+
+       hdr = smu_get_sdb_partition(SMU_SDB_SENSORTREE_ID, NULL);
+       if (hdr != 0) {
+               struct smu_sdbp_sensortree *st =
+                       (struct smu_sdbp_sensortree *)&hdr[1];
+               wf_smu_mach_model = st->model_id;
+       }
+
+       printk(KERN_INFO "windfarm: Initializing for iMacG5 model ID %d\n",
+              wf_smu_mach_model);
+
+       return 0;
+}
+
+static int wf_smu_probe(struct device *ddev)
+{
+       wf_smu_dev = ddev;
+
+       wf_register_client(&wf_smu_events);
+
+       return 0;
+}
+
+static int wf_smu_remove(struct device *ddev)
+{
+       wf_unregister_client(&wf_smu_events);
+
+       /* XXX We don't have yet a guarantee that our callback isn't
+        * in progress when returning from wf_unregister_client, so
+        * we add an arbitrary delay. I'll have to fix that in the core
+        */
+       msleep(1000);
+
+       /* Release all sensors */
+       /* One more crappy race: I don't think we have any guarantee here
+        * that the attribute callback won't race with the sensor beeing
+        * disposed of, and I'm not 100% certain what best way to deal
+        * with that except by adding locks all over... I'll do that
+        * eventually but heh, who ever rmmod this module anyway ?
+        */
+       if (sensor_cpu_power) {
+               device_remove_file(wf_smu_dev, &dev_attr_cpu_power);
+               wf_put_sensor(sensor_cpu_power);
+       }
+       if (sensor_cpu_temp) {
+               device_remove_file(wf_smu_dev, &dev_attr_cpu_temp);
+               wf_put_sensor(sensor_cpu_temp);
+       }
+       if (sensor_hd_temp) {
+               device_remove_file(wf_smu_dev, &dev_attr_hd_temp);
+               wf_put_sensor(sensor_hd_temp);
+       }
+
+       /* Release all controls */
+       if (fan_cpu_main) {
+               device_remove_file(wf_smu_dev, &dev_attr_cpu_fan);
+               wf_put_control(fan_cpu_main);
+       }
+       if (fan_hd) {
+               device_remove_file(wf_smu_dev, &dev_attr_hd_fan);
+               wf_put_control(fan_hd);
+       }
+       if (fan_system) {
+               device_remove_file(wf_smu_dev, &dev_attr_sys_fan);
+               wf_put_control(fan_system);
+       }
+       if (cpufreq_clamp)
+               wf_put_control(cpufreq_clamp);
+
+       /* Destroy control loops state structures */
+       if (wf_smu_sys_fans)
+               kfree(wf_smu_sys_fans);
+       if (wf_smu_cpu_fans)
+               kfree(wf_smu_cpu_fans);
+
+       wf_smu_dev = NULL;
+
+       return 0;
+}
+
+static struct device_driver wf_smu_driver = {
+        .name = "windfarm",
+        .bus = &platform_bus_type,
+        .probe = wf_smu_probe,
+        .remove = wf_smu_remove,
+};
+
+
+static int __init wf_smu_init(void)
+{
+       int rc = -ENODEV;
+
+       if (machine_is_compatible("PowerMac8,1") ||
+           machine_is_compatible("PowerMac8,2"))
+               rc = wf_init_pm();
+
+       if (rc == 0) {
+#ifdef MODULE
+               request_module("windfarm_smu_controls");
+               request_module("windfarm_smu_sensors");
+               request_module("windfarm_lm75_sensor");
+
+#endif /* MODULE */
+               driver_register(&wf_smu_driver);
+       }
+
+       return rc;
+}
+
+static void __exit wf_smu_exit(void)
+{
+
+       driver_unregister(&wf_smu_driver);
+}
+
+
+module_init(wf_smu_init);
+module_exit(wf_smu_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("Thermal control logic for iMac G5");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/macintosh/windfarm_pm91.c b/drivers/macintosh/windfarm_pm91.c
new file mode 100644 (file)
index 0000000..43243cf
--- /dev/null
@@ -0,0 +1,814 @@
+/*
+ * Windfarm PowerMac thermal control. SMU based 1 CPU desktop control loops
+ *
+ * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
+ *                    <benh@kernel.crashing.org>
+ *
+ * Released under the term of the GNU GPL v2.
+ *
+ * The algorithm used is the PID control algorithm, used the same
+ * way the published Darwin code does, using the same values that
+ * are present in the Darwin 8.2 snapshot property lists (note however
+ * that none of the code has been re-used, it's a complete re-implementation
+ *
+ * The various control loops found in Darwin config file are:
+ *
+ * PowerMac9,1
+ * ===========
+ *
+ * Has 3 control loops: CPU fans is similar to PowerMac8,1 (though it doesn't
+ * try to play with other control loops fans). Drive bay is rather basic PID
+ * with one sensor and one fan. Slots area is a bit different as the Darwin
+ * driver is supposed to be capable of working in a special "AGP" mode which
+ * involves the presence of an AGP sensor and an AGP fan (possibly on the
+ * AGP card itself). I can't deal with that special mode as I don't have
+ * access to those additional sensor/fans for now (though ultimately, it would
+ * be possible to add sensor objects for them) so I'm only implementing the
+ * basic PCI slot control loop
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/kmod.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/sections.h>
+#include <asm/smu.h>
+
+#include "windfarm.h"
+#include "windfarm_pid.h"
+
+#define VERSION "0.4"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(args...)   printk(args)
+#else
+#define DBG(args...)   do { } while(0)
+#endif
+
+/* define this to force CPU overtemp to 74 degree, useful for testing
+ * the overtemp code
+ */
+#undef HACKED_OVERTEMP
+
+static struct device *wf_smu_dev;
+
+/* Controls & sensors */
+static struct wf_sensor        *sensor_cpu_power;
+static struct wf_sensor        *sensor_cpu_temp;
+static struct wf_sensor        *sensor_hd_temp;
+static struct wf_sensor        *sensor_slots_power;
+static struct wf_control *fan_cpu_main;
+static struct wf_control *fan_cpu_second;
+static struct wf_control *fan_cpu_third;
+static struct wf_control *fan_hd;
+static struct wf_control *fan_slots;
+static struct wf_control *cpufreq_clamp;
+
+/* Set to kick the control loop into life */
+static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started;
+
+/* Failure handling.. could be nicer */
+#define FAILURE_FAN            0x01
+#define FAILURE_SENSOR         0x02
+#define FAILURE_OVERTEMP       0x04
+
+static unsigned int wf_smu_failure_state;
+static int wf_smu_readjust, wf_smu_skipping;
+
+/*
+ * ****** CPU Fans Control Loop ******
+ *
+ */
+
+
+#define WF_SMU_CPU_FANS_INTERVAL       1
+#define WF_SMU_CPU_FANS_MAX_HISTORY    16
+
+/* State data used by the cpu fans control loop
+ */
+struct wf_smu_cpu_fans_state {
+       int                     ticks;
+       s32                     cpu_setpoint;
+       struct wf_cpu_pid_state pid;
+};
+
+static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans;
+
+
+
+/*
+ * ****** Drive Fan Control Loop ******
+ *
+ */
+
+struct wf_smu_drive_fans_state {
+       int                     ticks;
+       s32                     setpoint;
+       struct wf_pid_state     pid;
+};
+
+static struct wf_smu_drive_fans_state *wf_smu_drive_fans;
+
+/*
+ * ****** Slots Fan Control Loop ******
+ *
+ */
+
+struct wf_smu_slots_fans_state {
+       int                     ticks;
+       s32                     setpoint;
+       struct wf_pid_state     pid;
+};
+
+static struct wf_smu_slots_fans_state *wf_smu_slots_fans;
+
+/*
+ * ***** Implementation *****
+ *
+ */
+
+
+static void wf_smu_create_cpu_fans(void)
+{
+       struct wf_cpu_pid_param pid_param;
+       struct smu_sdbp_header *hdr;
+       struct smu_sdbp_cpupiddata *piddata;
+       struct smu_sdbp_fvt *fvt;
+       s32 tmax, tdelta, maxpow, powadj;
+
+       /* First, locate the PID params in SMU SBD */
+       hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL);
+       if (hdr == 0) {
+               printk(KERN_WARNING "windfarm: CPU PID fan config not found "
+                      "max fan speed\n");
+               goto fail;
+       }
+       piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
+
+       /* Get the FVT params for operating point 0 (the only supported one
+        * for now) in order to get tmax
+        */
+       hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);
+       if (hdr) {
+               fvt = (struct smu_sdbp_fvt *)&hdr[1];
+               tmax = ((s32)fvt->maxtemp) << 16;
+       } else
+               tmax = 0x5e0000; /* 94 degree default */
+
+       /* Alloc & initialize state */
+       wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state),
+                                 GFP_KERNEL);
+       if (wf_smu_cpu_fans == NULL)
+               goto fail;
+               wf_smu_cpu_fans->ticks = 1;
+
+       /* Fill PID params */
+       pid_param.interval = WF_SMU_CPU_FANS_INTERVAL;
+       pid_param.history_len = piddata->history_len;
+       if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) {
+               printk(KERN_WARNING "windfarm: History size overflow on "
+                      "CPU control loop (%d)\n", piddata->history_len);
+               pid_param.history_len = WF_CPU_PID_MAX_HISTORY;
+       }
+       pid_param.gd = piddata->gd;
+       pid_param.gp = piddata->gp;
+       pid_param.gr = piddata->gr / pid_param.history_len;
+
+       tdelta = ((s32)piddata->target_temp_delta) << 16;
+       maxpow = ((s32)piddata->max_power) << 16;
+       powadj = ((s32)piddata->power_adj) << 16;
+
+       pid_param.tmax = tmax;
+       pid_param.ttarget = tmax - tdelta;
+       pid_param.pmaxadj = maxpow - powadj;
+
+       pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main);
+       pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main);
+
+       wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param);
+
+       DBG("wf: CPU Fan control initialized.\n");
+       DBG("    ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n",
+           FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax),
+           pid_param.min, pid_param.max);
+
+       return;
+
+ fail:
+       printk(KERN_WARNING "windfarm: CPU fan config not found\n"
+              "for this machine model, max fan speed\n");
+
+       if (cpufreq_clamp)
+               wf_control_set_max(cpufreq_clamp);
+       if (fan_cpu_main)
+               wf_control_set_max(fan_cpu_main);
+}
+
+static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)
+{
+       s32 new_setpoint, temp, power;
+       int rc;
+
+       if (--st->ticks != 0) {
+               if (wf_smu_readjust)
+                       goto readjust;
+               return;
+       }
+       st->ticks = WF_SMU_CPU_FANS_INTERVAL;
+
+       rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp);
+       if (rc) {
+               printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n",
+                      rc);
+               wf_smu_failure_state |= FAILURE_SENSOR;
+               return;
+       }
+
+       rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power);
+       if (rc) {
+               printk(KERN_WARNING "windfarm: CPU power sensor error %d\n",
+                      rc);
+               wf_smu_failure_state |= FAILURE_SENSOR;
+               return;
+       }
+
+       DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n",
+           FIX32TOPRINT(temp), FIX32TOPRINT(power));
+
+#ifdef HACKED_OVERTEMP
+       if (temp > 0x4a0000)
+               wf_smu_failure_state |= FAILURE_OVERTEMP;
+#else
+       if (temp > st->pid.param.tmax)
+               wf_smu_failure_state |= FAILURE_OVERTEMP;
+#endif
+       new_setpoint = wf_cpu_pid_run(&st->pid, power, temp);
+
+       DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);
+
+       if (st->cpu_setpoint == new_setpoint)
+               return;
+       st->cpu_setpoint = new_setpoint;
+ readjust:
+       if (fan_cpu_main && wf_smu_failure_state == 0) {
+               rc = fan_cpu_main->ops->set_value(fan_cpu_main,
+                                                 st->cpu_setpoint);
+               if (rc) {
+                       printk(KERN_WARNING "windfarm: CPU main fan"
+                              " error %d\n", rc);
+                       wf_smu_failure_state |= FAILURE_FAN;
+               }
+       }
+       if (fan_cpu_second && wf_smu_failure_state == 0) {
+               rc = fan_cpu_second->ops->set_value(fan_cpu_second,
+                                                   st->cpu_setpoint);
+               if (rc) {
+                       printk(KERN_WARNING "windfarm: CPU second fan"
+                              " error %d\n", rc);
+                       wf_smu_failure_state |= FAILURE_FAN;
+               }
+       }
+       if (fan_cpu_third && wf_smu_failure_state == 0) {
+               rc = fan_cpu_main->ops->set_value(fan_cpu_third,
+                                                 st->cpu_setpoint);
+               if (rc) {
+                       printk(KERN_WARNING "windfarm: CPU third fan"
+                              " error %d\n", rc);
+                       wf_smu_failure_state |= FAILURE_FAN;
+               }
+       }
+}
+
+static void wf_smu_create_drive_fans(void)
+{
+       struct wf_pid_param param = {
+               .interval       = 5,
+               .history_len    = 2,
+               .gd             = 0x01e00000,
+               .gp             = 0x00500000,
+               .gr             = 0x00000000,
+               .itarget        = 0x00200000,
+       };
+
+       /* Alloc & initialize state */
+       wf_smu_drive_fans = kmalloc(sizeof(struct wf_smu_drive_fans_state),
+                                       GFP_KERNEL);
+       if (wf_smu_drive_fans == NULL) {
+               printk(KERN_WARNING "windfarm: Memory allocation error"
+                      " max fan speed\n");
+               goto fail;
+       }
+               wf_smu_drive_fans->ticks = 1;
+
+       /* Fill PID params */
+       param.additive = (fan_hd->type == WF_CONTROL_RPM_FAN);
+       param.min = fan_hd->ops->get_min(fan_hd);
+       param.max = fan_hd->ops->get_max(fan_hd);
+       wf_pid_init(&wf_smu_drive_fans->pid, &param);
+
+       DBG("wf: Drive Fan control initialized.\n");
+       DBG("    itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
+           FIX32TOPRINT(param.itarget), param.min, param.max);
+       return;
+
+ fail:
+       if (fan_hd)
+               wf_control_set_max(fan_hd);
+}
+
+static void wf_smu_drive_fans_tick(struct wf_smu_drive_fans_state *st)
+{
+       s32 new_setpoint, temp;
+       int rc;
+
+       if (--st->ticks != 0) {
+               if (wf_smu_readjust)
+                       goto readjust;
+               return;
+       }
+       st->ticks = st->pid.param.interval;
+
+       rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp);
+       if (rc) {
+               printk(KERN_WARNING "windfarm: HD temp sensor error %d\n",
+                      rc);
+               wf_smu_failure_state |= FAILURE_SENSOR;
+               return;
+       }
+
+       DBG("wf_smu: Drive Fans tick ! HD temp: %d.%03d\n",
+           FIX32TOPRINT(temp));
+
+       if (temp > (st->pid.param.itarget + 0x50000))
+               wf_smu_failure_state |= FAILURE_OVERTEMP;
+
+       new_setpoint = wf_pid_run(&st->pid, temp);
+
+       DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint);
+
+       if (st->setpoint == new_setpoint)
+               return;
+       st->setpoint = new_setpoint;
+ readjust:
+       if (fan_hd && wf_smu_failure_state == 0) {
+               rc = fan_hd->ops->set_value(fan_hd, st->setpoint);
+               if (rc) {
+                       printk(KERN_WARNING "windfarm: HD fan error %d\n",
+                              rc);
+                       wf_smu_failure_state |= FAILURE_FAN;
+               }
+       }
+}
+
+static void wf_smu_create_slots_fans(void)
+{
+       struct wf_pid_param param = {
+               .interval       = 1,
+               .history_len    = 8,
+               .gd             = 0x00000000,
+               .gp             = 0x00000000,
+               .gr             = 0x00020000,
+               .itarget        = 0x00000000
+       };
+
+       /* Alloc & initialize state */
+       wf_smu_slots_fans = kmalloc(sizeof(struct wf_smu_slots_fans_state),
+                                       GFP_KERNEL);
+       if (wf_smu_slots_fans == NULL) {
+               printk(KERN_WARNING "windfarm: Memory allocation error"
+                      " max fan speed\n");
+               goto fail;
+       }
+               wf_smu_slots_fans->ticks = 1;
+
+       /* Fill PID params */
+       param.additive = (fan_slots->type == WF_CONTROL_RPM_FAN);
+       param.min = fan_slots->ops->get_min(fan_slots);
+       param.max = fan_slots->ops->get_max(fan_slots);
+       wf_pid_init(&wf_smu_slots_fans->pid, &param);
+
+       DBG("wf: Slots Fan control initialized.\n");
+       DBG("    itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
+           FIX32TOPRINT(param.itarget), param.min, param.max);
+       return;
+
+ fail:
+       if (fan_slots)
+               wf_control_set_max(fan_slots);
+}
+
+static void wf_smu_slots_fans_tick(struct wf_smu_slots_fans_state *st)
+{
+       s32 new_setpoint, power;
+       int rc;
+
+       if (--st->ticks != 0) {
+               if (wf_smu_readjust)
+                       goto readjust;
+               return;
+       }
+       st->ticks = st->pid.param.interval;
+
+       rc = sensor_slots_power->ops->get_value(sensor_slots_power, &power);
+       if (rc) {
+               printk(KERN_WARNING "windfarm: Slots power sensor error %d\n",
+                      rc);
+               wf_smu_failure_state |= FAILURE_SENSOR;
+               return;
+       }
+
+       DBG("wf_smu: Slots Fans tick ! Slots power: %d.%03d\n",
+           FIX32TOPRINT(power));
+
+#if 0 /* Check what makes a good overtemp condition */
+       if (power > (st->pid.param.itarget + 0x50000))
+               wf_smu_failure_state |= FAILURE_OVERTEMP;
+#endif
+
+       new_setpoint = wf_pid_run(&st->pid, power);
+
+       DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint);
+
+       if (st->setpoint == new_setpoint)
+               return;
+       st->setpoint = new_setpoint;
+ readjust:
+       if (fan_slots && wf_smu_failure_state == 0) {
+               rc = fan_slots->ops->set_value(fan_slots, st->setpoint);
+               if (rc) {
+                       printk(KERN_WARNING "windfarm: Slots fan error %d\n",
+                              rc);
+                       wf_smu_failure_state |= FAILURE_FAN;
+               }
+       }
+}
+
+
+/*
+ * ****** Attributes ******
+ *
+ */
+
+#define BUILD_SHOW_FUNC_FIX(name, data)                                \
+static ssize_t show_##name(struct device *dev,                  \
+                          struct device_attribute *attr,       \
+                          char *buf)                           \
+{                                                              \
+       ssize_t r;                                              \
+       s32 val = 0;                                            \
+       data->ops->get_value(data, &val);                       \
+       r = sprintf(buf, "%d.%03d", FIX32TOPRINT(val));         \
+       return r;                                               \
+}                                                               \
+static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL);
+
+
+#define BUILD_SHOW_FUNC_INT(name, data)                                \
+static ssize_t show_##name(struct device *dev,                  \
+                          struct device_attribute *attr,       \
+                          char *buf)                           \
+{                                                              \
+       s32 val = 0;                                            \
+       data->ops->get_value(data, &val);                       \
+       return sprintf(buf, "%d", val);                         \
+}                                                               \
+static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL);
+
+BUILD_SHOW_FUNC_INT(cpu_fan, fan_cpu_main);
+BUILD_SHOW_FUNC_INT(hd_fan, fan_hd);
+BUILD_SHOW_FUNC_INT(slots_fan, fan_slots);
+
+BUILD_SHOW_FUNC_FIX(cpu_temp, sensor_cpu_temp);
+BUILD_SHOW_FUNC_FIX(cpu_power, sensor_cpu_power);
+BUILD_SHOW_FUNC_FIX(hd_temp, sensor_hd_temp);
+BUILD_SHOW_FUNC_FIX(slots_power, sensor_slots_power);
+
+/*
+ * ****** Setup / Init / Misc ... ******
+ *
+ */
+
+static void wf_smu_tick(void)
+{
+       unsigned int last_failure = wf_smu_failure_state;
+       unsigned int new_failure;
+
+       if (!wf_smu_started) {
+               DBG("wf: creating control loops !\n");
+               wf_smu_create_drive_fans();
+               wf_smu_create_slots_fans();
+               wf_smu_create_cpu_fans();
+               wf_smu_started = 1;
+       }
+
+       /* Skipping ticks */
+       if (wf_smu_skipping && --wf_smu_skipping)
+               return;
+
+       wf_smu_failure_state = 0;
+       if (wf_smu_drive_fans)
+               wf_smu_drive_fans_tick(wf_smu_drive_fans);
+       if (wf_smu_slots_fans)
+               wf_smu_slots_fans_tick(wf_smu_slots_fans);
+       if (wf_smu_cpu_fans)
+               wf_smu_cpu_fans_tick(wf_smu_cpu_fans);
+
+       wf_smu_readjust = 0;
+       new_failure = wf_smu_failure_state & ~last_failure;
+
+       /* If entering failure mode, clamp cpufreq and ramp all
+        * fans to full speed.
+        */
+       if (wf_smu_failure_state && !last_failure) {
+               if (cpufreq_clamp)
+                       wf_control_set_max(cpufreq_clamp);
+               if (fan_cpu_main)
+                       wf_control_set_max(fan_cpu_main);
+               if (fan_cpu_second)
+                       wf_control_set_max(fan_cpu_second);
+               if (fan_cpu_third)
+                       wf_control_set_max(fan_cpu_third);
+               if (fan_hd)
+                       wf_control_set_max(fan_hd);
+               if (fan_slots)
+                       wf_control_set_max(fan_slots);
+       }
+
+       /* If leaving failure mode, unclamp cpufreq and readjust
+        * all fans on next iteration
+        */
+       if (!wf_smu_failure_state && last_failure) {
+               if (cpufreq_clamp)
+                       wf_control_set_min(cpufreq_clamp);
+               wf_smu_readjust = 1;
+       }
+
+       /* Overtemp condition detected, notify and start skipping a couple
+        * ticks to let the temperature go down
+        */
+       if (new_failure & FAILURE_OVERTEMP) {
+               wf_set_overtemp();
+               wf_smu_skipping = 2;
+       }
+
+       /* We only clear the overtemp condition if overtemp is cleared
+        * _and_ no other failure is present. Since a sensor error will
+        * clear the overtemp condition (can't measure temperature) at
+        * the control loop levels, but we don't want to keep it clear
+        * here in this case
+        */
+       if (new_failure == 0 && last_failure & FAILURE_OVERTEMP)
+               wf_clear_overtemp();
+}
+
+
+static void wf_smu_new_control(struct wf_control *ct)
+{
+       if (wf_smu_all_controls_ok)
+               return;
+
+       if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-rear-fan-0")) {
+               if (wf_get_control(ct) == 0) {
+                       fan_cpu_main = ct;
+                       device_create_file(wf_smu_dev, &dev_attr_cpu_fan);
+               }
+       }
+
+       if (fan_cpu_second == NULL && !strcmp(ct->name, "cpu-rear-fan-1")) {
+               if (wf_get_control(ct) == 0)
+                       fan_cpu_second = ct;
+       }
+
+       if (fan_cpu_third == NULL && !strcmp(ct->name, "cpu-front-fan-0")) {
+               if (wf_get_control(ct) == 0)
+                       fan_cpu_third = ct;
+       }
+
+       if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) {
+               if (wf_get_control(ct) == 0)
+                       cpufreq_clamp = ct;
+       }
+
+       if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) {
+               if (wf_get_control(ct) == 0) {
+                       fan_hd = ct;
+                       device_create_file(wf_smu_dev, &dev_attr_hd_fan);
+               }
+       }
+
+       if (fan_slots == NULL && !strcmp(ct->name, "slots-fan")) {
+               if (wf_get_control(ct) == 0) {
+                       fan_slots = ct;
+                       device_create_file(wf_smu_dev, &dev_attr_slots_fan);
+               }
+       }
+
+       if (fan_cpu_main && (fan_cpu_second || fan_cpu_third) && fan_hd &&
+           fan_slots && cpufreq_clamp)
+               wf_smu_all_controls_ok = 1;
+}
+
+static void wf_smu_new_sensor(struct wf_sensor *sr)
+{
+       if (wf_smu_all_sensors_ok)
+               return;
+
+       if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) {
+               if (wf_get_sensor(sr) == 0) {
+                       sensor_cpu_power = sr;
+                       device_create_file(wf_smu_dev, &dev_attr_cpu_power);
+               }
+       }
+
+       if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) {
+               if (wf_get_sensor(sr) == 0) {
+                       sensor_cpu_temp = sr;
+                       device_create_file(wf_smu_dev, &dev_attr_cpu_temp);
+               }
+       }
+
+       if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) {
+               if (wf_get_sensor(sr) == 0) {
+                       sensor_hd_temp = sr;
+                       device_create_file(wf_smu_dev, &dev_attr_hd_temp);
+               }
+       }
+
+       if (sensor_slots_power == NULL && !strcmp(sr->name, "slots-power")) {
+               if (wf_get_sensor(sr) == 0) {
+                       sensor_slots_power = sr;
+                       device_create_file(wf_smu_dev, &dev_attr_slots_power);
+               }
+       }
+
+       if (sensor_cpu_power && sensor_cpu_temp &&
+           sensor_hd_temp && sensor_slots_power)
+               wf_smu_all_sensors_ok = 1;
+}
+
+
+static int wf_smu_notify(struct notifier_block *self,
+                              unsigned long event, void *data)
+{
+       switch(event) {
+       case WF_EVENT_NEW_CONTROL:
+               DBG("wf: new control %s detected\n",
+                   ((struct wf_control *)data)->name);
+               wf_smu_new_control(data);
+               wf_smu_readjust = 1;
+               break;
+       case WF_EVENT_NEW_SENSOR:
+               DBG("wf: new sensor %s detected\n",
+                   ((struct wf_sensor *)data)->name);
+               wf_smu_new_sensor(data);
+               break;
+       case WF_EVENT_TICK:
+               if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok)
+                       wf_smu_tick();
+       }
+
+       return 0;
+}
+
+static struct notifier_block wf_smu_events = {
+       .notifier_call  = wf_smu_notify,
+};
+
+static int wf_init_pm(void)
+{
+       printk(KERN_INFO "windfarm: Initializing for Desktop G5 model\n");
+
+       return 0;
+}
+
+static int wf_smu_probe(struct device *ddev)
+{
+       wf_smu_dev = ddev;
+
+       wf_register_client(&wf_smu_events);
+
+       return 0;
+}
+
+static int wf_smu_remove(struct device *ddev)
+{
+       wf_unregister_client(&wf_smu_events);
+
+       /* XXX We don't have yet a guarantee that our callback isn't
+        * in progress when returning from wf_unregister_client, so
+        * we add an arbitrary delay. I'll have to fix that in the core
+        */
+       msleep(1000);
+
+       /* Release all sensors */
+       /* One more crappy race: I don't think we have any guarantee here
+        * that the attribute callback won't race with the sensor beeing
+        * disposed of, and I'm not 100% certain what best way to deal
+        * with that except by adding locks all over... I'll do that
+        * eventually but heh, who ever rmmod this module anyway ?
+        */
+       if (sensor_cpu_power) {
+               device_remove_file(wf_smu_dev, &dev_attr_cpu_power);
+               wf_put_sensor(sensor_cpu_power);
+       }
+       if (sensor_cpu_temp) {
+               device_remove_file(wf_smu_dev, &dev_attr_cpu_temp);
+               wf_put_sensor(sensor_cpu_temp);
+       }
+       if (sensor_hd_temp) {
+               device_remove_file(wf_smu_dev, &dev_attr_hd_temp);
+               wf_put_sensor(sensor_hd_temp);
+       }
+       if (sensor_slots_power) {
+               device_remove_file(wf_smu_dev, &dev_attr_slots_power);
+               wf_put_sensor(sensor_slots_power);
+       }
+
+       /* Release all controls */
+       if (fan_cpu_main) {
+               device_remove_file(wf_smu_dev, &dev_attr_cpu_fan);
+               wf_put_control(fan_cpu_main);
+       }
+       if (fan_cpu_second)
+               wf_put_control(fan_cpu_second);
+       if (fan_cpu_third)
+               wf_put_control(fan_cpu_third);
+       if (fan_hd) {
+               device_remove_file(wf_smu_dev, &dev_attr_hd_fan);
+               wf_put_control(fan_hd);
+       }
+       if (fan_slots) {
+               device_remove_file(wf_smu_dev, &dev_attr_slots_fan);
+               wf_put_control(fan_slots);
+       }
+       if (cpufreq_clamp)
+               wf_put_control(cpufreq_clamp);
+
+       /* Destroy control loops state structures */
+       if (wf_smu_slots_fans)
+               kfree(wf_smu_cpu_fans);
+       if (wf_smu_drive_fans)
+               kfree(wf_smu_cpu_fans);
+       if (wf_smu_cpu_fans)
+               kfree(wf_smu_cpu_fans);
+
+       wf_smu_dev = NULL;
+
+       return 0;
+}
+
+static struct device_driver wf_smu_driver = {
+        .name = "windfarm",
+        .bus = &platform_bus_type,
+        .probe = wf_smu_probe,
+        .remove = wf_smu_remove,
+};
+
+
+static int __init wf_smu_init(void)
+{
+       int rc = -ENODEV;
+
+       if (machine_is_compatible("PowerMac9,1"))
+               rc = wf_init_pm();
+
+       if (rc == 0) {
+#ifdef MODULE
+               request_module("windfarm_smu_controls");
+               request_module("windfarm_smu_sensors");
+               request_module("windfarm_lm75_sensor");
+
+#endif /* MODULE */
+               driver_register(&wf_smu_driver);
+       }
+
+       return rc;
+}
+
+static void __exit wf_smu_exit(void)
+{
+
+       driver_unregister(&wf_smu_driver);
+}
+
+
+module_init(wf_smu_init);
+module_exit(wf_smu_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("Thermal control logic for PowerMac9,1");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/macintosh/windfarm_smu_controls.c b/drivers/macintosh/windfarm_smu_controls.c
new file mode 100644 (file)
index 0000000..2c3158c
--- /dev/null
@@ -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 = &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");
+
diff --git a/drivers/macintosh/windfarm_smu_sensors.c b/drivers/macintosh/windfarm_smu_sensors.c
new file mode 100644 (file)
index 0000000..b558cc2
--- /dev/null
@@ -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, &amps);
+       if (rc)
+               return rc;
+
+       if (pow->fake_volts) {
+               *value = amps * 12 - 0x30000;
+               return 0;
+       }
+
+       rc = pow->volts->ops->get_value(pow->volts, &volts);
+       if (rc)
+               return rc;
+
+       power = (s32)((((u64)volts) * ((u64)amps)) >> 16);
+       if (!pow->quadratic) {
+               *value = power;
+               return 0;
+       }
+       tmps = (((u64)power) * ((u64)power)) >> 16;
+       tmpa = ((u64)cpuvcp->power_quads[0]) * tmps;
+       tmpb = ((u64)cpuvcp->power_quads[1]) * ((u64)power);
+       *value = (tmpa >> 28) + (tmpb >> 28) + (cpuvcp->power_quads[2] >> 12);
+
+       return 0;
+}
+
+static struct wf_sensor_ops smu_cpu_power_ops = {
+       .get_value      = smu_cpu_power_get,
+       .release        = smu_cpu_power_release,
+       .owner          = THIS_MODULE,
+};
+
+
+static struct smu_cpu_power_sensor *
+smu_cpu_power_create(struct wf_sensor *volts, struct wf_sensor *amps)
+{
+       struct smu_cpu_power_sensor *pow;
+
+       pow = kmalloc(sizeof(struct smu_cpu_power_sensor), GFP_KERNEL);
+       if (pow == NULL)
+               return NULL;
+       pow->sens.ops = &smu_cpu_power_ops;
+       pow->sens.name = "cpu-power";
+
+       wf_get_sensor(volts);
+       pow->volts = volts;
+       wf_get_sensor(amps);
+       pow->amps = amps;
+
+       /* Some early machines need a faked voltage */
+       if (debugswitches && ((*debugswitches) & 0x80)) {
+               printk(KERN_INFO "windfarm: CPU Power sensor using faked"
+                      " voltage !\n");
+               pow->fake_volts = 1;
+       } else
+               pow->fake_volts = 0;
+
+       /* Try to use quadratic transforms on PowerMac8,1 and 9,1 for now,
+        * I yet have to figure out what's up with 8,2 and will have to
+        * adjust for later, unless we can 100% trust the SDB partition...
+        */
+       if ((machine_is_compatible("PowerMac8,1") ||
+            machine_is_compatible("PowerMac8,2") ||
+            machine_is_compatible("PowerMac9,1")) &&
+           cpuvcp_version >= 2) {
+               pow->quadratic = 1;
+               DBG("windfarm: CPU Power using quadratic transform\n");
+       } else
+               pow->quadratic = 0;
+
+       if (wf_register_sensor(&pow->sens))
+               goto fail;
+       return pow;
+ fail:
+       kfree(pow);
+       return NULL;
+}
+
+static int smu_fetch_param_partitions(void)
+{
+       struct smu_sdbp_header *hdr;
+
+       /* Get CPU voltage/current/power calibration data */
+       hdr = smu_get_sdb_partition(SMU_SDB_CPUVCP_ID, NULL);
+       if (hdr == NULL) {
+               DBG("wf: cpuvcp partition (%02x) not found\n",
+                   SMU_SDB_CPUVCP_ID);
+               return -ENODEV;
+       }
+       cpuvcp = (struct smu_sdbp_cpuvcp *)&hdr[1];
+       /* Keep version around */
+       cpuvcp_version = hdr->version;
+
+       /* Get CPU diode calibration data */
+       hdr = smu_get_sdb_partition(SMU_SDB_CPUDIODE_ID, NULL);
+       if (hdr == NULL) {
+               DBG("wf: cpudiode partition (%02x) not found\n",
+                   SMU_SDB_CPUDIODE_ID);
+               return -ENODEV;
+       }
+       cpudiode = (struct smu_sdbp_cpudiode *)&hdr[1];
+
+       /* Get slots power calibration data if any */
+       hdr = smu_get_sdb_partition(SMU_SDB_SLOTSPOW_ID, NULL);
+       if (hdr != NULL)
+               slotspow = (struct smu_sdbp_slotspow *)&hdr[1];
+
+       /* Get debug switches if any */
+       hdr = smu_get_sdb_partition(SMU_SDB_DEBUG_SWITCHES_ID, NULL);
+       if (hdr != NULL)
+               debugswitches = (u8 *)&hdr[1];
+
+       return 0;
+}
+
+static int __init smu_sensors_init(void)
+{
+       struct device_node *smu, *sensors, *s;
+       struct smu_ad_sensor *volt_sensor = NULL, *curr_sensor = NULL;
+       int rc;
+
+       if (!smu_present())
+               return -ENODEV;
+
+       /* Get parameters partitions */
+       rc = smu_fetch_param_partitions();
+       if (rc)
+               return rc;
+
+       smu = of_find_node_by_type(NULL, "smu");
+       if (smu == NULL)
+               return -ENODEV;
+
+       /* Look for sensors subdir */
+       for (sensors = NULL;
+            (sensors = of_get_next_child(smu, sensors)) != NULL;)
+               if (!strcmp(sensors->name, "sensors"))
+                       break;
+
+       of_node_put(smu);
+
+       /* Create basic sensors */
+       for (s = NULL;
+            sensors && (s = of_get_next_child(sensors, s)) != NULL;) {
+               struct smu_ad_sensor *ads;
+
+               ads = smu_ads_create(s);
+               if (ads == NULL)
+                       continue;
+               list_add(&ads->link, &smu_ads);
+               /* keep track of cpu voltage & current */
+               if (!strcmp(ads->sens.name, "cpu-voltage"))
+                       volt_sensor = ads;
+               else if (!strcmp(ads->sens.name, "cpu-current"))
+                       curr_sensor = ads;
+       }
+
+       of_node_put(sensors);
+
+       /* Create CPU power sensor if possible */
+       if (volt_sensor && curr_sensor)
+               smu_cpu_power = smu_cpu_power_create(&volt_sensor->sens,
+                                                    &curr_sensor->sens);
+
+       return 0;
+}
+
+static void __exit smu_sensors_exit(void)
+{
+       struct smu_ad_sensor *ads;
+
+       /* dispose of power sensor */
+       if (smu_cpu_power)
+               wf_unregister_sensor(&smu_cpu_power->sens);
+
+       /* dispose of basic sensors */
+       while (!list_empty(&smu_ads)) {
+               ads = list_entry(smu_ads.next, struct smu_ad_sensor, link);
+               list_del(&ads->link);
+               wf_unregister_sensor(&ads->sens);
+       }
+}
+
+
+module_init(smu_sensors_init);
+module_exit(smu_sensors_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control");
+MODULE_LICENSE("GPL");
+