forked from mirrors/linux
		
	[PATCH] ppc64: Thermal control for SMU based machines
This adds a new thermal control framework for PowerMac, along with the implementation for PowerMac8,1, PowerMac8,2 (iMac G5 rev 1 and 2), and PowerMac9,1 (latest single CPU desktop). In the future, I expect to move the older G5 thermal control to the new framework as well. Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org> Signed-off-by: Paul Mackerras <paulus@samba.org>
This commit is contained in:
		
							parent
							
								
									7d49697ef9
								
							
						
					
					
						commit
						75722d3992
					
				
					 13 changed files with 3638 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -169,6 +169,25 @@ config THERM_PM72
 | 
			
		|||
	  This driver provides thermostat and fan control for the desktop
 | 
			
		||||
	  G5 machines. 
 | 
			
		||||
 | 
			
		||||
config WINDFARM
 | 
			
		||||
	tristate "New PowerMac thermal control infrastructure"
 | 
			
		||||
 | 
			
		||||
config WINDFARM_PM81
 | 
			
		||||
	tristate "Support for thermal management on iMac G5"
 | 
			
		||||
	depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU
 | 
			
		||||
	select I2C_PMAC_SMU
 | 
			
		||||
	help
 | 
			
		||||
	  This driver provides thermal control for the iMacG5
 | 
			
		||||
 | 
			
		||||
config WINDFARM_PM91
 | 
			
		||||
	tristate "Support for thermal management on PowerMac9,1"
 | 
			
		||||
	depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU
 | 
			
		||||
	select I2C_PMAC_SMU
 | 
			
		||||
	help
 | 
			
		||||
	  This driver provides thermal control for the PowerMac9,1
 | 
			
		||||
          which is the recent (SMU based) single CPU desktop G5
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
config ANSLCD
 | 
			
		||||
	tristate "Support for ANS LCD display"
 | 
			
		||||
	depends on ADB_CUDA && PPC_PMAC
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,3 +26,12 @@ obj-$(CONFIG_ADB_MACIO)		+= macio-adb.o
 | 
			
		|||
obj-$(CONFIG_THERM_PM72)	+= therm_pm72.o
 | 
			
		||||
obj-$(CONFIG_THERM_WINDTUNNEL)	+= therm_windtunnel.o
 | 
			
		||||
obj-$(CONFIG_THERM_ADT746X)	+= therm_adt746x.o
 | 
			
		||||
obj-$(CONFIG_WINDFARM)	        += windfarm_core.o
 | 
			
		||||
obj-$(CONFIG_WINDFARM_PM81)     += windfarm_smu_controls.o \
 | 
			
		||||
				   windfarm_smu_sensors.o \
 | 
			
		||||
				   windfarm_lm75_sensor.o windfarm_pid.o \
 | 
			
		||||
				   windfarm_cpufreq_clamp.o windfarm_pm81.o
 | 
			
		||||
obj-$(CONFIG_WINDFARM_PM91)     += windfarm_smu_controls.o \
 | 
			
		||||
				   windfarm_smu_sensors.o \
 | 
			
		||||
				   windfarm_lm75_sensor.o windfarm_pid.o \
 | 
			
		||||
				   windfarm_cpufreq_clamp.o windfarm_pm91.o
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -590,6 +590,8 @@ static void smu_expose_childs(void *unused)
 | 
			
		|||
			sprintf(name, "smu-i2c-%02x", *reg);
 | 
			
		||||
			of_platform_device_create(np, name, &smu->of_dev->dev);
 | 
			
		||||
		}
 | 
			
		||||
		if (device_is_compatible(np, "smu-sensors"))
 | 
			
		||||
			of_platform_device_create(np, "smu-sensors", &smu->of_dev->dev);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										131
									
								
								drivers/macintosh/windfarm.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								drivers/macintosh/windfarm.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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__ */
 | 
			
		||||
							
								
								
									
										426
									
								
								drivers/macintosh/windfarm_core.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										426
									
								
								drivers/macintosh/windfarm_core.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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");
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										105
									
								
								drivers/macintosh/windfarm_cpufreq_clamp.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								drivers/macintosh/windfarm_cpufreq_clamp.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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");
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										263
									
								
								drivers/macintosh/windfarm_lm75_sensor.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								drivers/macintosh/windfarm_lm75_sensor.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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");
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										145
									
								
								drivers/macintosh/windfarm_pid.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								drivers/macintosh/windfarm_pid.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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);
 | 
			
		||||
							
								
								
									
										84
									
								
								drivers/macintosh/windfarm_pid.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								drivers/macintosh/windfarm_pid.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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);
 | 
			
		||||
							
								
								
									
										879
									
								
								drivers/macintosh/windfarm_pm81.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										879
									
								
								drivers/macintosh/windfarm_pm81.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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");
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										814
									
								
								drivers/macintosh/windfarm_pm91.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										814
									
								
								drivers/macintosh/windfarm_pm91.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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, ¶m);
 | 
			
		||||
 | 
			
		||||
	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, ¶m);
 | 
			
		||||
 | 
			
		||||
	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");
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										282
									
								
								drivers/macintosh/windfarm_smu_controls.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								drivers/macintosh/windfarm_smu_controls.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,282 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Windfarm PowerMac thermal control. SMU based controls
 | 
			
		||||
 *
 | 
			
		||||
 * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
 | 
			
		||||
 *                    <benh@kernel.crashing.org>
 | 
			
		||||
 *
 | 
			
		||||
 * Released under the term of the GNU GPL v2.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <linux/types.h>
 | 
			
		||||
#include <linux/errno.h>
 | 
			
		||||
#include <linux/kernel.h>
 | 
			
		||||
#include <linux/delay.h>
 | 
			
		||||
#include <linux/slab.h>
 | 
			
		||||
#include <linux/init.h>
 | 
			
		||||
#include <linux/wait.h>
 | 
			
		||||
#include <asm/prom.h>
 | 
			
		||||
#include <asm/machdep.h>
 | 
			
		||||
#include <asm/io.h>
 | 
			
		||||
#include <asm/system.h>
 | 
			
		||||
#include <asm/sections.h>
 | 
			
		||||
#include <asm/smu.h>
 | 
			
		||||
 | 
			
		||||
#include "windfarm.h"
 | 
			
		||||
 | 
			
		||||
#define VERSION "0.3"
 | 
			
		||||
 | 
			
		||||
#undef DEBUG
 | 
			
		||||
 | 
			
		||||
#ifdef DEBUG
 | 
			
		||||
#define DBG(args...)	printk(args)
 | 
			
		||||
#else
 | 
			
		||||
#define DBG(args...)	do { } while(0)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * SMU fans control object
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
static LIST_HEAD(smu_fans);
 | 
			
		||||
 | 
			
		||||
struct smu_fan_control {
 | 
			
		||||
	struct list_head	link;
 | 
			
		||||
	int    			fan_type;	/* 0 = rpm, 1 = pwm */
 | 
			
		||||
	u32			reg;		/* index in SMU */
 | 
			
		||||
	s32			value;		/* current value */
 | 
			
		||||
	s32			min, max;	/* min/max values */
 | 
			
		||||
	struct wf_control	ctrl;
 | 
			
		||||
};
 | 
			
		||||
#define to_smu_fan(c) container_of(c, struct smu_fan_control, ctrl)
 | 
			
		||||
 | 
			
		||||
static int smu_set_fan(int pwm, u8 id, u16 value)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_cmd cmd;
 | 
			
		||||
	u8 buffer[16];
 | 
			
		||||
	DECLARE_COMPLETION(comp);
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* Fill SMU command structure */
 | 
			
		||||
	cmd.cmd = SMU_CMD_FAN_COMMAND;
 | 
			
		||||
	cmd.data_len = 14;
 | 
			
		||||
	cmd.reply_len = 16;
 | 
			
		||||
	cmd.data_buf = cmd.reply_buf = buffer;
 | 
			
		||||
	cmd.status = 0;
 | 
			
		||||
	cmd.done = smu_done_complete;
 | 
			
		||||
	cmd.misc = ∁
 | 
			
		||||
 | 
			
		||||
	/* Fill argument buffer */
 | 
			
		||||
	memset(buffer, 0, 16);
 | 
			
		||||
	buffer[0] = pwm ? 0x10 : 0x00;
 | 
			
		||||
	buffer[1] = 0x01 << id;
 | 
			
		||||
	*((u16 *)&buffer[2 + id * 2]) = value;
 | 
			
		||||
 | 
			
		||||
	rc = smu_queue_cmd(&cmd);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
	wait_for_completion(&comp);
 | 
			
		||||
	return cmd.status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void smu_fan_release(struct wf_control *ct)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_fan_control *fct = to_smu_fan(ct);
 | 
			
		||||
 | 
			
		||||
	kfree(fct);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int smu_fan_set(struct wf_control *ct, s32 value)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_fan_control *fct = to_smu_fan(ct);
 | 
			
		||||
 | 
			
		||||
	if (value < fct->min)
 | 
			
		||||
		value = fct->min;
 | 
			
		||||
	if (value > fct->max)
 | 
			
		||||
		value = fct->max;
 | 
			
		||||
	fct->value = value;
 | 
			
		||||
 | 
			
		||||
	return smu_set_fan(fct->fan_type, fct->reg, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int smu_fan_get(struct wf_control *ct, s32 *value)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_fan_control *fct = to_smu_fan(ct);
 | 
			
		||||
	*value = fct->value; /* todo: read from SMU */
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static s32 smu_fan_min(struct wf_control *ct)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_fan_control *fct = to_smu_fan(ct);
 | 
			
		||||
	return fct->min;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static s32 smu_fan_max(struct wf_control *ct)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_fan_control *fct = to_smu_fan(ct);
 | 
			
		||||
	return fct->max;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct wf_control_ops smu_fan_ops = {
 | 
			
		||||
	.set_value	= smu_fan_set,
 | 
			
		||||
	.get_value	= smu_fan_get,
 | 
			
		||||
	.get_min	= smu_fan_min,
 | 
			
		||||
	.get_max	= smu_fan_max,
 | 
			
		||||
	.release	= smu_fan_release,
 | 
			
		||||
	.owner		= THIS_MODULE,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct smu_fan_control *smu_fan_create(struct device_node *node,
 | 
			
		||||
					      int pwm_fan)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_fan_control *fct;
 | 
			
		||||
	s32 *v; u32 *reg;
 | 
			
		||||
	char *l;
 | 
			
		||||
 | 
			
		||||
	fct = kmalloc(sizeof(struct smu_fan_control), GFP_KERNEL);
 | 
			
		||||
	if (fct == NULL)
 | 
			
		||||
		return NULL;
 | 
			
		||||
	fct->ctrl.ops = &smu_fan_ops;
 | 
			
		||||
	l = (char *)get_property(node, "location", NULL);
 | 
			
		||||
	if (l == NULL)
 | 
			
		||||
		goto fail;
 | 
			
		||||
 | 
			
		||||
	fct->fan_type = pwm_fan;
 | 
			
		||||
	fct->ctrl.type = pwm_fan ? WF_CONTROL_PWM_FAN : WF_CONTROL_RPM_FAN;
 | 
			
		||||
 | 
			
		||||
	/* We use the name & location here the same way we do for SMU sensors,
 | 
			
		||||
	 * see the comment in windfarm_smu_sensors.c. The locations are a bit
 | 
			
		||||
	 * less consistent here between the iMac and the desktop models, but
 | 
			
		||||
	 * that is good enough for our needs for now at least.
 | 
			
		||||
	 *
 | 
			
		||||
	 * One problem though is that Apple seem to be inconsistent with case
 | 
			
		||||
	 * and the kernel doesn't have strcasecmp =P
 | 
			
		||||
	 */
 | 
			
		||||
 | 
			
		||||
	fct->ctrl.name = NULL;
 | 
			
		||||
 | 
			
		||||
	/* Names used on desktop models */
 | 
			
		||||
	if (!strcmp(l, "Rear Fan 0") || !strcmp(l, "Rear Fan") ||
 | 
			
		||||
	    !strcmp(l, "Rear fan 0") || !strcmp(l, "Rear fan"))
 | 
			
		||||
		fct->ctrl.name = "cpu-rear-fan-0";
 | 
			
		||||
	else if (!strcmp(l, "Rear Fan 1") || !strcmp(l, "Rear fan 1"))
 | 
			
		||||
		fct->ctrl.name = "cpu-rear-fan-1";
 | 
			
		||||
	else if (!strcmp(l, "Front Fan 0") || !strcmp(l, "Front Fan") ||
 | 
			
		||||
		 !strcmp(l, "Front fan 0") || !strcmp(l, "Front fan"))
 | 
			
		||||
		fct->ctrl.name = "cpu-front-fan-0";
 | 
			
		||||
	else if (!strcmp(l, "Front Fan 1") || !strcmp(l, "Front fan 1"))
 | 
			
		||||
		fct->ctrl.name = "cpu-front-fan-1";
 | 
			
		||||
	else if (!strcmp(l, "Slots Fan") || !strcmp(l, "Slots fan"))
 | 
			
		||||
		fct->ctrl.name = "slots-fan";
 | 
			
		||||
	else if (!strcmp(l, "Drive Bay") || !strcmp(l, "Drive bay"))
 | 
			
		||||
		fct->ctrl.name = "drive-bay-fan";
 | 
			
		||||
 | 
			
		||||
	/* Names used on iMac models */
 | 
			
		||||
	if (!strcmp(l, "System Fan") || !strcmp(l, "System fan"))
 | 
			
		||||
		fct->ctrl.name = "system-fan";
 | 
			
		||||
	else if (!strcmp(l, "CPU Fan") || !strcmp(l, "CPU fan"))
 | 
			
		||||
		fct->ctrl.name = "cpu-fan";
 | 
			
		||||
	else if (!strcmp(l, "Hard Drive") || !strcmp(l, "Hard drive"))
 | 
			
		||||
		fct->ctrl.name = "drive-bay-fan";
 | 
			
		||||
 | 
			
		||||
	/* Unrecognized fan, bail out */
 | 
			
		||||
	if (fct->ctrl.name == NULL)
 | 
			
		||||
		goto fail;
 | 
			
		||||
 | 
			
		||||
	/* Get min & max values*/
 | 
			
		||||
	v = (s32 *)get_property(node, "min-value", NULL);
 | 
			
		||||
	if (v == NULL)
 | 
			
		||||
		goto fail;
 | 
			
		||||
	fct->min = *v;
 | 
			
		||||
	v = (s32 *)get_property(node, "max-value", NULL);
 | 
			
		||||
	if (v == NULL)
 | 
			
		||||
		goto fail;
 | 
			
		||||
	fct->max = *v;
 | 
			
		||||
 | 
			
		||||
	/* Get "reg" value */
 | 
			
		||||
	reg = (u32 *)get_property(node, "reg", NULL);
 | 
			
		||||
	if (reg == NULL)
 | 
			
		||||
		goto fail;
 | 
			
		||||
	fct->reg = *reg;
 | 
			
		||||
 | 
			
		||||
	if (wf_register_control(&fct->ctrl))
 | 
			
		||||
		goto fail;
 | 
			
		||||
 | 
			
		||||
	return fct;
 | 
			
		||||
 fail:
 | 
			
		||||
	kfree(fct);
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static int __init smu_controls_init(void)
 | 
			
		||||
{
 | 
			
		||||
	struct device_node *smu, *fans, *fan;
 | 
			
		||||
 | 
			
		||||
	if (!smu_present())
 | 
			
		||||
		return -ENODEV;
 | 
			
		||||
 | 
			
		||||
	smu = of_find_node_by_type(NULL, "smu");
 | 
			
		||||
	if (smu == NULL)
 | 
			
		||||
		return -ENODEV;
 | 
			
		||||
 | 
			
		||||
	/* Look for RPM fans */
 | 
			
		||||
	for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;)
 | 
			
		||||
		if (!strcmp(fans->name, "rpm-fans"))
 | 
			
		||||
			break;
 | 
			
		||||
	for (fan = NULL;
 | 
			
		||||
	     fans && (fan = of_get_next_child(fans, fan)) != NULL;) {
 | 
			
		||||
		struct smu_fan_control *fct;
 | 
			
		||||
 | 
			
		||||
		fct = smu_fan_create(fan, 0);
 | 
			
		||||
		if (fct == NULL) {
 | 
			
		||||
			printk(KERN_WARNING "windfarm: Failed to create SMU "
 | 
			
		||||
			       "RPM fan %s\n", fan->name);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		list_add(&fct->link, &smu_fans);
 | 
			
		||||
	}
 | 
			
		||||
	of_node_put(fans);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	/* Look for PWM fans */
 | 
			
		||||
	for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;)
 | 
			
		||||
		if (!strcmp(fans->name, "pwm-fans"))
 | 
			
		||||
			break;
 | 
			
		||||
	for (fan = NULL;
 | 
			
		||||
	     fans && (fan = of_get_next_child(fans, fan)) != NULL;) {
 | 
			
		||||
		struct smu_fan_control *fct;
 | 
			
		||||
 | 
			
		||||
		fct = smu_fan_create(fan, 1);
 | 
			
		||||
		if (fct == NULL) {
 | 
			
		||||
			printk(KERN_WARNING "windfarm: Failed to create SMU "
 | 
			
		||||
			       "PWM fan %s\n", fan->name);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		list_add(&fct->link, &smu_fans);
 | 
			
		||||
	}
 | 
			
		||||
	of_node_put(fans);
 | 
			
		||||
	of_node_put(smu);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void __exit smu_controls_exit(void)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_fan_control *fct;
 | 
			
		||||
 | 
			
		||||
	while (!list_empty(&smu_fans)) {
 | 
			
		||||
		fct = list_entry(smu_fans.next, struct smu_fan_control, link);
 | 
			
		||||
		list_del(&fct->link);
 | 
			
		||||
		wf_unregister_control(&fct->ctrl);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
module_init(smu_controls_init);
 | 
			
		||||
module_exit(smu_controls_exit);
 | 
			
		||||
 | 
			
		||||
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
 | 
			
		||||
MODULE_DESCRIPTION("SMU control objects for PowerMacs thermal control");
 | 
			
		||||
MODULE_LICENSE("GPL");
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										479
									
								
								drivers/macintosh/windfarm_smu_sensors.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										479
									
								
								drivers/macintosh/windfarm_smu_sensors.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,479 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Windfarm PowerMac thermal control. SMU based sensors
 | 
			
		||||
 *
 | 
			
		||||
 * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
 | 
			
		||||
 *                    <benh@kernel.crashing.org>
 | 
			
		||||
 *
 | 
			
		||||
 * Released under the term of the GNU GPL v2.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <linux/types.h>
 | 
			
		||||
#include <linux/errno.h>
 | 
			
		||||
#include <linux/kernel.h>
 | 
			
		||||
#include <linux/delay.h>
 | 
			
		||||
#include <linux/slab.h>
 | 
			
		||||
#include <linux/init.h>
 | 
			
		||||
#include <linux/wait.h>
 | 
			
		||||
#include <asm/prom.h>
 | 
			
		||||
#include <asm/machdep.h>
 | 
			
		||||
#include <asm/io.h>
 | 
			
		||||
#include <asm/system.h>
 | 
			
		||||
#include <asm/sections.h>
 | 
			
		||||
#include <asm/smu.h>
 | 
			
		||||
 | 
			
		||||
#include "windfarm.h"
 | 
			
		||||
 | 
			
		||||
#define VERSION "0.2"
 | 
			
		||||
 | 
			
		||||
#undef DEBUG
 | 
			
		||||
 | 
			
		||||
#ifdef DEBUG
 | 
			
		||||
#define DBG(args...)	printk(args)
 | 
			
		||||
#else
 | 
			
		||||
#define DBG(args...)	do { } while(0)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Various SMU "partitions" calibration objects for which we
 | 
			
		||||
 * keep pointers here for use by bits & pieces of the driver
 | 
			
		||||
 */
 | 
			
		||||
static struct smu_sdbp_cpuvcp *cpuvcp;
 | 
			
		||||
static int  cpuvcp_version;
 | 
			
		||||
static struct smu_sdbp_cpudiode *cpudiode;
 | 
			
		||||
static struct smu_sdbp_slotspow *slotspow;
 | 
			
		||||
static u8 *debugswitches;
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * SMU basic sensors objects
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
static LIST_HEAD(smu_ads);
 | 
			
		||||
 | 
			
		||||
struct smu_ad_sensor {
 | 
			
		||||
	struct list_head	link;
 | 
			
		||||
	u32			reg;		/* index in SMU */
 | 
			
		||||
	struct wf_sensor	sens;
 | 
			
		||||
};
 | 
			
		||||
#define to_smu_ads(c) container_of(c, struct smu_ad_sensor, sens)
 | 
			
		||||
 | 
			
		||||
static void smu_ads_release(struct wf_sensor *sr)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_ad_sensor *ads = to_smu_ads(sr);
 | 
			
		||||
 | 
			
		||||
	kfree(ads);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int smu_read_adc(u8 id, s32 *value)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_simple_cmd	cmd;
 | 
			
		||||
	DECLARE_COMPLETION(comp);
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	rc = smu_queue_simple(&cmd, SMU_CMD_READ_ADC, 1,
 | 
			
		||||
			      smu_done_complete, &comp, id);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
	wait_for_completion(&comp);
 | 
			
		||||
	if (cmd.cmd.status != 0)
 | 
			
		||||
		return cmd.cmd.status;
 | 
			
		||||
	if (cmd.cmd.reply_len != 2) {
 | 
			
		||||
		printk(KERN_ERR "winfarm: read ADC 0x%x returned %d bytes !\n",
 | 
			
		||||
		       id, cmd.cmd.reply_len);
 | 
			
		||||
		return -EIO;
 | 
			
		||||
	}
 | 
			
		||||
	*value = *((u16 *)cmd.buffer);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int smu_cputemp_get(struct wf_sensor *sr, s32 *value)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_ad_sensor *ads = to_smu_ads(sr);
 | 
			
		||||
	int rc;
 | 
			
		||||
	s32 val;
 | 
			
		||||
	s64 scaled;
 | 
			
		||||
 | 
			
		||||
	rc = smu_read_adc(ads->reg, &val);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		printk(KERN_ERR "windfarm: read CPU temp failed, err %d\n",
 | 
			
		||||
		       rc);
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Ok, we have to scale & adjust, taking units into account */
 | 
			
		||||
	scaled = (s64)(((u64)val) * (u64)cpudiode->m_value);
 | 
			
		||||
	scaled >>= 3;
 | 
			
		||||
	scaled += ((s64)cpudiode->b_value) << 9;
 | 
			
		||||
	*value = (s32)(scaled << 1);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int smu_cpuamp_get(struct wf_sensor *sr, s32 *value)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_ad_sensor *ads = to_smu_ads(sr);
 | 
			
		||||
	s32 val, scaled;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	rc = smu_read_adc(ads->reg, &val);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		printk(KERN_ERR "windfarm: read CPU current failed, err %d\n",
 | 
			
		||||
		       rc);
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Ok, we have to scale & adjust, taking units into account */
 | 
			
		||||
	scaled = (s32)(val * (u32)cpuvcp->curr_scale);
 | 
			
		||||
	scaled += (s32)cpuvcp->curr_offset;
 | 
			
		||||
	*value = scaled << 4;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int smu_cpuvolt_get(struct wf_sensor *sr, s32 *value)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_ad_sensor *ads = to_smu_ads(sr);
 | 
			
		||||
	s32 val, scaled;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	rc = smu_read_adc(ads->reg, &val);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		printk(KERN_ERR "windfarm: read CPU voltage failed, err %d\n",
 | 
			
		||||
		       rc);
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Ok, we have to scale & adjust, taking units into account */
 | 
			
		||||
	scaled = (s32)(val * (u32)cpuvcp->volt_scale);
 | 
			
		||||
	scaled += (s32)cpuvcp->volt_offset;
 | 
			
		||||
	*value = scaled << 4;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int smu_slotspow_get(struct wf_sensor *sr, s32 *value)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_ad_sensor *ads = to_smu_ads(sr);
 | 
			
		||||
	s32 val, scaled;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	rc = smu_read_adc(ads->reg, &val);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		printk(KERN_ERR "windfarm: read slots power failed, err %d\n",
 | 
			
		||||
		       rc);
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Ok, we have to scale & adjust, taking units into account */
 | 
			
		||||
	scaled = (s32)(val * (u32)slotspow->pow_scale);
 | 
			
		||||
	scaled += (s32)slotspow->pow_offset;
 | 
			
		||||
	*value = scaled << 4;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static struct wf_sensor_ops smu_cputemp_ops = {
 | 
			
		||||
	.get_value	= smu_cputemp_get,
 | 
			
		||||
	.release	= smu_ads_release,
 | 
			
		||||
	.owner		= THIS_MODULE,
 | 
			
		||||
};
 | 
			
		||||
static struct wf_sensor_ops smu_cpuamp_ops = {
 | 
			
		||||
	.get_value	= smu_cpuamp_get,
 | 
			
		||||
	.release	= smu_ads_release,
 | 
			
		||||
	.owner		= THIS_MODULE,
 | 
			
		||||
};
 | 
			
		||||
static struct wf_sensor_ops smu_cpuvolt_ops = {
 | 
			
		||||
	.get_value	= smu_cpuvolt_get,
 | 
			
		||||
	.release	= smu_ads_release,
 | 
			
		||||
	.owner		= THIS_MODULE,
 | 
			
		||||
};
 | 
			
		||||
static struct wf_sensor_ops smu_slotspow_ops = {
 | 
			
		||||
	.get_value	= smu_slotspow_get,
 | 
			
		||||
	.release	= smu_ads_release,
 | 
			
		||||
	.owner		= THIS_MODULE,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static struct smu_ad_sensor *smu_ads_create(struct device_node *node)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_ad_sensor *ads;
 | 
			
		||||
	char *c, *l;
 | 
			
		||||
	u32 *v;
 | 
			
		||||
 | 
			
		||||
	ads = kmalloc(sizeof(struct smu_ad_sensor), GFP_KERNEL);
 | 
			
		||||
	if (ads == NULL)
 | 
			
		||||
		return NULL;
 | 
			
		||||
	c = (char *)get_property(node, "device_type", NULL);
 | 
			
		||||
	l = (char *)get_property(node, "location", NULL);
 | 
			
		||||
	if (c == NULL || l == NULL)
 | 
			
		||||
		goto fail;
 | 
			
		||||
 | 
			
		||||
	/* We currently pick the sensors based on the OF name and location
 | 
			
		||||
	 * properties, while Darwin uses the sensor-id's.
 | 
			
		||||
	 * The problem with the IDs is that they are model specific while it
 | 
			
		||||
	 * looks like apple has been doing a reasonably good job at keeping
 | 
			
		||||
	 * the names and locations consistents so I'll stick with the names
 | 
			
		||||
	 * and locations for now.
 | 
			
		||||
	 */
 | 
			
		||||
	if (!strcmp(c, "temp-sensor") &&
 | 
			
		||||
	    !strcmp(l, "CPU T-Diode")) {
 | 
			
		||||
		ads->sens.ops = &smu_cputemp_ops;
 | 
			
		||||
		ads->sens.name = "cpu-temp";
 | 
			
		||||
	} else if (!strcmp(c, "current-sensor") &&
 | 
			
		||||
		   !strcmp(l, "CPU Current")) {
 | 
			
		||||
		ads->sens.ops = &smu_cpuamp_ops;
 | 
			
		||||
		ads->sens.name = "cpu-current";
 | 
			
		||||
	} else if (!strcmp(c, "voltage-sensor") &&
 | 
			
		||||
		   !strcmp(l, "CPU Voltage")) {
 | 
			
		||||
		ads->sens.ops = &smu_cpuvolt_ops;
 | 
			
		||||
		ads->sens.name = "cpu-voltage";
 | 
			
		||||
	} else if (!strcmp(c, "power-sensor") &&
 | 
			
		||||
		   !strcmp(l, "Slots Power")) {
 | 
			
		||||
		ads->sens.ops = &smu_slotspow_ops;
 | 
			
		||||
		ads->sens.name = "slots-power";
 | 
			
		||||
		if (slotspow == NULL) {
 | 
			
		||||
			DBG("wf: slotspow partition (%02x) not found\n",
 | 
			
		||||
			    SMU_SDB_SLOTSPOW_ID);
 | 
			
		||||
			goto fail;
 | 
			
		||||
		}
 | 
			
		||||
	} else
 | 
			
		||||
		goto fail;
 | 
			
		||||
 | 
			
		||||
	v = (u32 *)get_property(node, "reg", NULL);
 | 
			
		||||
	if (v == NULL)
 | 
			
		||||
		goto fail;
 | 
			
		||||
	ads->reg = *v;
 | 
			
		||||
 | 
			
		||||
	if (wf_register_sensor(&ads->sens))
 | 
			
		||||
		goto fail;
 | 
			
		||||
	return ads;
 | 
			
		||||
 fail:
 | 
			
		||||
	kfree(ads);
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * SMU Power combo sensor object
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
struct smu_cpu_power_sensor {
 | 
			
		||||
	struct list_head	link;
 | 
			
		||||
	struct wf_sensor	*volts;
 | 
			
		||||
	struct wf_sensor	*amps;
 | 
			
		||||
	int			fake_volts : 1;
 | 
			
		||||
	int			quadratic : 1;
 | 
			
		||||
	struct wf_sensor	sens;
 | 
			
		||||
};
 | 
			
		||||
#define to_smu_cpu_power(c) container_of(c, struct smu_cpu_power_sensor, sens)
 | 
			
		||||
 | 
			
		||||
static struct smu_cpu_power_sensor *smu_cpu_power;
 | 
			
		||||
 | 
			
		||||
static void smu_cpu_power_release(struct wf_sensor *sr)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
 | 
			
		||||
 | 
			
		||||
	if (pow->volts)
 | 
			
		||||
		wf_put_sensor(pow->volts);
 | 
			
		||||
	if (pow->amps)
 | 
			
		||||
		wf_put_sensor(pow->amps);
 | 
			
		||||
	kfree(pow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int smu_cpu_power_get(struct wf_sensor *sr, s32 *value)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
 | 
			
		||||
	s32 volts, amps, power;
 | 
			
		||||
	u64 tmps, tmpa, tmpb;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	rc = pow->amps->ops->get_value(pow->amps, &s);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	if (pow->fake_volts) {
 | 
			
		||||
		*value = amps * 12 - 0x30000;
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rc = pow->volts->ops->get_value(pow->volts, &volts);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	power = (s32)((((u64)volts) * ((u64)amps)) >> 16);
 | 
			
		||||
	if (!pow->quadratic) {
 | 
			
		||||
		*value = power;
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
	tmps = (((u64)power) * ((u64)power)) >> 16;
 | 
			
		||||
	tmpa = ((u64)cpuvcp->power_quads[0]) * tmps;
 | 
			
		||||
	tmpb = ((u64)cpuvcp->power_quads[1]) * ((u64)power);
 | 
			
		||||
	*value = (tmpa >> 28) + (tmpb >> 28) + (cpuvcp->power_quads[2] >> 12);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct wf_sensor_ops smu_cpu_power_ops = {
 | 
			
		||||
	.get_value	= smu_cpu_power_get,
 | 
			
		||||
	.release	= smu_cpu_power_release,
 | 
			
		||||
	.owner		= THIS_MODULE,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static struct smu_cpu_power_sensor *
 | 
			
		||||
smu_cpu_power_create(struct wf_sensor *volts, struct wf_sensor *amps)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_cpu_power_sensor *pow;
 | 
			
		||||
 | 
			
		||||
	pow = kmalloc(sizeof(struct smu_cpu_power_sensor), GFP_KERNEL);
 | 
			
		||||
	if (pow == NULL)
 | 
			
		||||
		return NULL;
 | 
			
		||||
	pow->sens.ops = &smu_cpu_power_ops;
 | 
			
		||||
	pow->sens.name = "cpu-power";
 | 
			
		||||
 | 
			
		||||
	wf_get_sensor(volts);
 | 
			
		||||
	pow->volts = volts;
 | 
			
		||||
	wf_get_sensor(amps);
 | 
			
		||||
	pow->amps = amps;
 | 
			
		||||
 | 
			
		||||
	/* Some early machines need a faked voltage */
 | 
			
		||||
	if (debugswitches && ((*debugswitches) & 0x80)) {
 | 
			
		||||
		printk(KERN_INFO "windfarm: CPU Power sensor using faked"
 | 
			
		||||
		       " voltage !\n");
 | 
			
		||||
		pow->fake_volts = 1;
 | 
			
		||||
	} else
 | 
			
		||||
		pow->fake_volts = 0;
 | 
			
		||||
 | 
			
		||||
	/* Try to use quadratic transforms on PowerMac8,1 and 9,1 for now,
 | 
			
		||||
	 * I yet have to figure out what's up with 8,2 and will have to
 | 
			
		||||
	 * adjust for later, unless we can 100% trust the SDB partition...
 | 
			
		||||
	 */
 | 
			
		||||
	if ((machine_is_compatible("PowerMac8,1") ||
 | 
			
		||||
	     machine_is_compatible("PowerMac8,2") ||
 | 
			
		||||
	     machine_is_compatible("PowerMac9,1")) &&
 | 
			
		||||
	    cpuvcp_version >= 2) {
 | 
			
		||||
		pow->quadratic = 1;
 | 
			
		||||
		DBG("windfarm: CPU Power using quadratic transform\n");
 | 
			
		||||
	} else
 | 
			
		||||
		pow->quadratic = 0;
 | 
			
		||||
 | 
			
		||||
	if (wf_register_sensor(&pow->sens))
 | 
			
		||||
		goto fail;
 | 
			
		||||
	return pow;
 | 
			
		||||
 fail:
 | 
			
		||||
	kfree(pow);
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int smu_fetch_param_partitions(void)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_sdbp_header *hdr;
 | 
			
		||||
 | 
			
		||||
	/* Get CPU voltage/current/power calibration data */
 | 
			
		||||
	hdr = smu_get_sdb_partition(SMU_SDB_CPUVCP_ID, NULL);
 | 
			
		||||
	if (hdr == NULL) {
 | 
			
		||||
		DBG("wf: cpuvcp partition (%02x) not found\n",
 | 
			
		||||
		    SMU_SDB_CPUVCP_ID);
 | 
			
		||||
		return -ENODEV;
 | 
			
		||||
	}
 | 
			
		||||
	cpuvcp = (struct smu_sdbp_cpuvcp *)&hdr[1];
 | 
			
		||||
	/* Keep version around */
 | 
			
		||||
	cpuvcp_version = hdr->version;
 | 
			
		||||
 | 
			
		||||
	/* Get CPU diode calibration data */
 | 
			
		||||
	hdr = smu_get_sdb_partition(SMU_SDB_CPUDIODE_ID, NULL);
 | 
			
		||||
	if (hdr == NULL) {
 | 
			
		||||
		DBG("wf: cpudiode partition (%02x) not found\n",
 | 
			
		||||
		    SMU_SDB_CPUDIODE_ID);
 | 
			
		||||
		return -ENODEV;
 | 
			
		||||
	}
 | 
			
		||||
	cpudiode = (struct smu_sdbp_cpudiode *)&hdr[1];
 | 
			
		||||
 | 
			
		||||
	/* Get slots power calibration data if any */
 | 
			
		||||
	hdr = smu_get_sdb_partition(SMU_SDB_SLOTSPOW_ID, NULL);
 | 
			
		||||
	if (hdr != NULL)
 | 
			
		||||
		slotspow = (struct smu_sdbp_slotspow *)&hdr[1];
 | 
			
		||||
 | 
			
		||||
	/* Get debug switches if any */
 | 
			
		||||
	hdr = smu_get_sdb_partition(SMU_SDB_DEBUG_SWITCHES_ID, NULL);
 | 
			
		||||
	if (hdr != NULL)
 | 
			
		||||
		debugswitches = (u8 *)&hdr[1];
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int __init smu_sensors_init(void)
 | 
			
		||||
{
 | 
			
		||||
	struct device_node *smu, *sensors, *s;
 | 
			
		||||
	struct smu_ad_sensor *volt_sensor = NULL, *curr_sensor = NULL;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	if (!smu_present())
 | 
			
		||||
		return -ENODEV;
 | 
			
		||||
 | 
			
		||||
	/* Get parameters partitions */
 | 
			
		||||
	rc = smu_fetch_param_partitions();
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	smu = of_find_node_by_type(NULL, "smu");
 | 
			
		||||
	if (smu == NULL)
 | 
			
		||||
		return -ENODEV;
 | 
			
		||||
 | 
			
		||||
	/* Look for sensors subdir */
 | 
			
		||||
	for (sensors = NULL;
 | 
			
		||||
	     (sensors = of_get_next_child(smu, sensors)) != NULL;)
 | 
			
		||||
		if (!strcmp(sensors->name, "sensors"))
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
	of_node_put(smu);
 | 
			
		||||
 | 
			
		||||
	/* Create basic sensors */
 | 
			
		||||
	for (s = NULL;
 | 
			
		||||
	     sensors && (s = of_get_next_child(sensors, s)) != NULL;) {
 | 
			
		||||
		struct smu_ad_sensor *ads;
 | 
			
		||||
 | 
			
		||||
		ads = smu_ads_create(s);
 | 
			
		||||
		if (ads == NULL)
 | 
			
		||||
			continue;
 | 
			
		||||
		list_add(&ads->link, &smu_ads);
 | 
			
		||||
		/* keep track of cpu voltage & current */
 | 
			
		||||
		if (!strcmp(ads->sens.name, "cpu-voltage"))
 | 
			
		||||
			volt_sensor = ads;
 | 
			
		||||
		else if (!strcmp(ads->sens.name, "cpu-current"))
 | 
			
		||||
			curr_sensor = ads;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	of_node_put(sensors);
 | 
			
		||||
 | 
			
		||||
	/* Create CPU power sensor if possible */
 | 
			
		||||
	if (volt_sensor && curr_sensor)
 | 
			
		||||
		smu_cpu_power = smu_cpu_power_create(&volt_sensor->sens,
 | 
			
		||||
						     &curr_sensor->sens);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void __exit smu_sensors_exit(void)
 | 
			
		||||
{
 | 
			
		||||
	struct smu_ad_sensor *ads;
 | 
			
		||||
 | 
			
		||||
	/* dispose of power sensor */
 | 
			
		||||
	if (smu_cpu_power)
 | 
			
		||||
		wf_unregister_sensor(&smu_cpu_power->sens);
 | 
			
		||||
 | 
			
		||||
	/* dispose of basic sensors */
 | 
			
		||||
	while (!list_empty(&smu_ads)) {
 | 
			
		||||
		ads = list_entry(smu_ads.next, struct smu_ad_sensor, link);
 | 
			
		||||
		list_del(&ads->link);
 | 
			
		||||
		wf_unregister_sensor(&ads->sens);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
module_init(smu_sensors_init);
 | 
			
		||||
module_exit(smu_sensors_exit);
 | 
			
		||||
 | 
			
		||||
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
 | 
			
		||||
MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control");
 | 
			
		||||
MODULE_LICENSE("GPL");
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in a new issue