ppc32: Add thermal management drivers

Adds thermal management drivers for desktop G5, Windtunnel G4s, and
recent laptops (iBook G4, aluminium 15" and 17" powerbooks)
parent 0aa73732
......@@ -127,6 +127,28 @@ config MAC_EMUMOUSEBTN
If you have an Apple machine with a 1-button mouse, say Y here.
config THERM_WINDTUNNEL
tristate "Support for thermal management on Windtunnel G4s"
depends on I2C && I2C_KEYWEST && !POWER4
help
This driver provides some thermostat and fan control for the desktop
G4 "Windtunnel"
config THERM_ADT7467
tristate "Support for thermal mgmnt on laptops with ADT 7467 chipset"
depends on I2C && I2C_KEYWEST && !POWER4
help
This driver provides some thermostat and fan control for the
iBook G4, and the ATI based aluminium PowerBooks, allowing slighlty
better fan behaviour by default, and some manual control.
config THERM_PM72
tristate "Support for thermal management on PowerMac G5"
depends on I2C && I2C_KEYWEST && POWER4
help
This driver provides thermostat and fan control for the desktop
G5 machines.
config ANSLCD
bool "Support for ANS LCD display"
depends on ADB_CUDA
......
......@@ -22,3 +22,7 @@ obj-$(CONFIG_ADB_MACIISI) += via-maciisi.o
obj-$(CONFIG_ADB_IOP) += adb-iop.o
obj-$(CONFIG_ADB_PMU68K) += via-pmu68k.o
obj-$(CONFIG_ADB_MACIO) += macio-adb.o
obj-$(CONFIG_THERM_PM72) += therm_pm72.o
obj-$(CONFIG_THERM_WINDTUNNEL) += therm_windtunnel.o
obj-$(CONFIG_THERM_ADT7467) += therm_adt7467.o
/*
* Device driver for the i2c thermostat found on the iBook G4, Albook G4
*
* Copyright (C) 2003, 2004 Colin Leroy, Rasmus Rohde, Benjamin Herrenschmidt
*
* Documentation from
* http://www.analog.com/UploadedFiles/Data_Sheets/115254175ADT7467_pra.pdf
* http://www.analog.com/UploadedFiles/Data_Sheets/3686221171167ADT7460_b.pdf
*
*/
#include <linux/config.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/smp_lock.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/of_device.h>
#undef DEBUG
#define CONFIG_REG 0x40
#define MANUAL_MASK 0xe0
#define AUTO_MASK 0x20
static u8 TEMP_REG[3] = {0x26, 0x25, 0x27}; /* local, cpu, gpu */
static u8 LIMIT_REG[3] = {0x6b, 0x6a, 0x6c}; /* local, cpu, gpu */
static u8 MANUAL_MODE[2] = {0x5c, 0x5d};
static u8 REM_CONTROL[2] = {0x00, 0x40};
static u8 FAN_SPEED[2] = {0x28, 0x2a};
static u8 FAN_SPD_SET[2] = {0x30, 0x31};
static u8 default_limits_local[3] = {70, 50, 70}; /* local, cpu, gpu */
static u8 default_limits_chip[3] = {80, 65, 80}; /* local, cpu, gpu */
static int limit_adjust = 0;
static int fan_speed = -1;
MODULE_AUTHOR("Colin Leroy <colin@colino.net>");
MODULE_DESCRIPTION("Driver for ADT7467 thermostat in iBook G4");
MODULE_LICENSE("GPL");
MODULE_PARM(limit_adjust,"i");
MODULE_PARM_DESC(limit_adjust,"Adjust maximum temperatures (50°C cpu, 70°C gpu) by N °C.");
MODULE_PARM(fan_speed,"i");
MODULE_PARM_DESC(fan_speed,"Specify fan speed (0-255) when lim < temp < lim+8 (default 128)");
struct thermostat {
struct i2c_client clt;
u8 cached_temp[3];
u8 initial_limits[3];
u8 limits[3];
int last_speed[2];
int overriding[2];
};
static enum {ADT7460, ADT7467} therm_type;
static int therm_bus, therm_address;
static struct of_device * of_dev;
static struct thermostat* thermostat;
static pid_t monitor_thread_id;
static int monitor_running;
static struct completion monitor_task_compl;
static int attach_one_thermostat(struct i2c_adapter *adapter, int addr, int busno);
static void write_both_fan_speed(struct thermostat *th, int speed);
static void write_fan_speed(struct thermostat *th, int speed, int fan);
static int
write_reg(struct thermostat* th, int reg, u8 data)
{
u8 tmp[2];
int rc;
tmp[0] = reg;
tmp[1] = data;
rc = i2c_master_send(&th->clt, (const char *)tmp, 2);
if (rc < 0)
return rc;
if (rc != 2)
return -ENODEV;
return 0;
}
static int
read_reg(struct thermostat* th, int reg)
{
u8 reg_addr, data;
int rc;
reg_addr = (u8)reg;
rc = i2c_master_send(&th->clt, &reg_addr, 1);
if (rc < 0)
return rc;
if (rc != 1)
return -ENODEV;
rc = i2c_master_recv(&th->clt, (char *)&data, 1);
if (rc < 0)
return rc;
return data;
}
static int
attach_thermostat(struct i2c_adapter *adapter)
{
unsigned long bus_no;
if (strncmp(adapter->name, "uni-n", 5))
return -ENODEV;
bus_no = simple_strtoul(adapter->name + 6, NULL, 10);
if (bus_no != therm_bus)
return -ENODEV;
return attach_one_thermostat(adapter, therm_address, bus_no);
}
static int
detach_thermostat(struct i2c_adapter *adapter)
{
struct thermostat* th;
int i;
if (thermostat == NULL)
return 0;
th = thermostat;
if (monitor_running) {
monitor_running = 0;
wait_for_completion(&monitor_task_compl);
}
printk(KERN_INFO "adt746x: Putting max temperatures back from %d, %d, %d,"
" to %d, %d, %d, (°C)\n",
th->limits[0], th->limits[1], th->limits[2],
th->initial_limits[0], th->initial_limits[1], th->initial_limits[2]);
for (i = 0; i < 3; i++)
write_reg(th, LIMIT_REG[i], th->initial_limits[i]);
write_both_fan_speed(th, -1);
i2c_detach_client(&th->clt);
thermostat = NULL;
kfree(th);
return 0;
}
static struct i2c_driver thermostat_driver = {
.name ="Apple Thermostat ADT7467",
.id =0xDEAD7467,
.flags =I2C_DF_NOTIFY,
.attach_adapter =&attach_thermostat,
.detach_adapter =&detach_thermostat,
};
static int read_fan_speed(struct thermostat *th, u8 addr)
{
u8 tmp[2];
u16 res;
/* should start with low byte */
tmp[1] = read_reg(th, addr);
tmp[0] = read_reg(th, addr + 1);
res = tmp[1] + (tmp[0] << 8);
return (90000*60)/res;
}
static void write_both_fan_speed(struct thermostat *th, int speed)
{
write_fan_speed(th, speed, 0);
if (therm_type == ADT7460)
write_fan_speed(th, speed, 1);
}
static void write_fan_speed(struct thermostat *th, int speed, int fan)
{
u8 manual;
if (speed > 0xff)
speed = 0xff;
else if (speed < -1)
speed = 0;
if (therm_type == ADT7467 && fan == 1)
return;
if (th->last_speed[fan] != speed) {
if (speed == -1)
printk(KERN_INFO "adt746x: Setting speed to: automatic for %s fan.\n",
fan?"GPU":"CPU");
else
printk(KERN_INFO "adt746x: Setting speed to: %d for %s fan.\n",
speed, fan?"GPU":"CPU");
} else
return;
if (speed >= 0) {
manual = read_reg(th, MANUAL_MODE[fan]);
write_reg(th, MANUAL_MODE[fan], manual|MANUAL_MASK);
write_reg(th, FAN_SPD_SET[fan], speed);
} else {
/* back to automatic */
if(therm_type == ADT7460) {
manual = read_reg(th, MANUAL_MODE[fan]) & (~MANUAL_MASK);
write_reg(th, MANUAL_MODE[fan], manual|REM_CONTROL[fan]);
} else {
manual = read_reg(th, MANUAL_MODE[fan]);
write_reg(th, MANUAL_MODE[fan], manual&(~AUTO_MASK));
}
}
th->last_speed[fan] = speed;
}
static int monitor_task(void *arg)
{
struct thermostat* th = arg;
u8 temps[3];
u8 lims[3];
int i;
#ifdef DEBUG
int mfan_speed;
#endif
lock_kernel();
daemonize("kfand");
unlock_kernel();
strcpy(current->comm, "thermostat");
monitor_running = 1;
while(monitor_running)
{
set_task_state(current, TASK_UNINTERRUPTIBLE);
schedule_timeout(2*HZ);
/* Check status */
/* local : chip */
/* remote 1: CPU ?*/
/* remote 2: GPU ?*/
#ifndef DEBUG
if (fan_speed != -1) {
#endif
for (i = 0; i < 3; i++) {
temps[i] = read_reg(th, TEMP_REG[i]);
lims[i] = th->limits[i];
}
#ifndef DEBUG
}
#endif
if (fan_speed != -1) {
int lastvar = 0; /* for iBook */
for (i = 1; i < 3; i++) { /* we don't care about local sensor */
int started = 0;
int fan_number = (therm_type == ADT7460 && i == 2);
int var = temps[i] - lims[i];
if (var > 8) {
if (th->overriding[fan_number] == 0)
printk(KERN_INFO "adt746x: Limit exceeded by %d°C, overriding specified fan speed for %s.\n",
var, fan_number?"GPU":"CPU");
th->overriding[fan_number] = 1;
write_fan_speed(th, 255, fan_number);
started = 1;
} else if ((!th->overriding[fan_number] || var < 6) && var > 0) {
if (th->overriding[fan_number] == 1)
printk(KERN_INFO "adt746x: Limit exceeded by %d°C, setting speed to specified for %s.\n",
var, fan_number?"GPU":"CPU");
th->overriding[fan_number] = 0;
write_fan_speed(th, fan_speed, fan_number);
started = 1;
} else if (var < -1) {
/* don't stop iBook fan if GPU is cold and CPU is not
* so cold (lastvar >= -1) */
if (therm_type == ADT7460 || lastvar < -1 || i == 1) {
if (th->last_speed[fan_number] != 0)
printk(KERN_INFO "adt746x: Stopping %s fan.\n",
fan_number?"GPU":"CPU");
write_fan_speed(th, 0, fan_number);
}
}
lastvar = var;
if (started && therm_type == ADT7467)
break; /* we don't want to re-stop the fan
* if CPU is heating and GPU is not */
}
}
#ifdef DEBUG
mfan_speed = read_fan_speed(th, FAN_SPEED[0]);
/* only one fan in the iBook G4 */
if (temps[0] != th->cached_temp[0]
|| temps[1] != th->cached_temp[1]
|| temps[2] != th->cached_temp[2]) {
printk(KERN_INFO "adt746x: Temperature infos:"
" thermostats: %d,%d,%d °C;"
" limits: %d,%d,%d °C;"
" fan speed: %d RPM\n",
temps[0], temps[1], temps[2],
lims[0], lims[1], lims[2],
mfan_speed);
}
th->cached_temp[0] = temps[0];
th->cached_temp[1] = temps[1];
th->cached_temp[2] = temps[2];
#endif
}
complete_and_exit(&monitor_task_compl, 0);
return 0;
}
static void
set_limit(struct thermostat *th, int i)
{
/* Set CPU limit higher to avoid powerdowns */
th->limits[i] = default_limits_chip[i] + limit_adjust;
write_reg(th, LIMIT_REG[i], th->limits[i]);
/* set our limits to normal */
th->limits[i] = default_limits_local[i] + limit_adjust;
}
static int
attach_one_thermostat(struct i2c_adapter *adapter, int addr, int busno)
{
struct thermostat* th;
int rc;
int i;
if (thermostat)
return 0;
th = (struct thermostat *)kmalloc(sizeof(struct thermostat), GFP_KERNEL);
if (!th)
return -ENOMEM;
memset(th, 0, sizeof(*th));
th->clt.addr = addr;
th->clt.adapter = adapter;
th->clt.driver = &thermostat_driver;
th->clt.id = 0xDEAD7467;
strcpy(th->clt.name, "thermostat");
rc = read_reg(th, 0);
if (rc < 0) {
printk(KERN_ERR "adt746x: Thermostat failed to read config from bus %d !\n",
busno);
kfree(th);
return -ENODEV;
}
/* force manual control to start the fan quieter */
if (fan_speed == -1)
fan_speed=128;
if(therm_type == ADT7460) {
printk(KERN_INFO "adt746x: ADT7460 initializing\n");
/* The 7460 needs to be started explicitly */
write_reg(th, CONFIG_REG, 1);
} else
printk(KERN_INFO "adt746x: ADT7467 initializing\n");
for (i = 0; i < 3; i++) {
th->initial_limits[i] = read_reg(th, LIMIT_REG[i]);
set_limit(th, i);
}
printk(KERN_INFO "adt746x: Lowering max temperatures from %d, %d, %d"
" to %d, %d, %d (°C)\n",
th->initial_limits[0], th->initial_limits[1], th->initial_limits[2],
th->limits[0], th->limits[1], th->limits[2]);
thermostat = th;
if (i2c_attach_client(&th->clt)) {
printk("adt746x: Thermostat failed to attach client !\n");
thermostat = NULL;
kfree(th);
return -ENODEV;
}
/* be sure to really write fan speed the first time */
th->last_speed[0] = -2;
th->last_speed[1] = -2;
if (fan_speed != -1) {
write_both_fan_speed(th, 0);
} else {
write_both_fan_speed(th, -1);
}
init_completion(&monitor_task_compl);
monitor_thread_id = kernel_thread(monitor_task, th,
SIGCHLD | CLONE_KERNEL);
return 0;
}
/*
* Now, unfortunately, sysfs doesn't give us a nice void * we could
* pass around to the attribute functions, so we don't really have
* choice but implement a bunch of them...
*
*/
#define BUILD_SHOW_FUNC_DEG(name, data) \
static ssize_t show_##name(struct device *dev, char *buf) \
{ \
return sprintf(buf, "%d°C\n", data); \
}
#define BUILD_SHOW_FUNC_INT(name, data) \
static ssize_t show_##name(struct device *dev, char *buf) \
{ \
return sprintf(buf, "%d\n", data); \
}
#define BUILD_STORE_FUNC_DEG(name, data) \
static ssize_t store_##name(struct device *dev, const char *buf, size_t n) \
{ \
int val; \
int i; \
val = simple_strtol(buf, NULL, 10); \
printk(KERN_INFO "Adjusting limits by %d°C\n", val); \
limit_adjust = val; \
for (i=0; i < 3; i++) \
set_limit(thermostat, i); \
return n; \
}
#define BUILD_STORE_FUNC_INT(name, data) \
static ssize_t store_##name(struct device *dev, const char *buf, size_t n) \
{ \
u32 val; \
val = simple_strtoul(buf, NULL, 10); \
if (val < 0 || val > 255) \
return -EINVAL; \
printk(KERN_INFO "Setting fan speed to %d\n", val); \
data = val; \
return n; \
}
BUILD_SHOW_FUNC_DEG(cpu_temperature, (read_reg(thermostat, TEMP_REG[1])))
BUILD_SHOW_FUNC_DEG(gpu_temperature, (read_reg(thermostat, TEMP_REG[2])))
BUILD_SHOW_FUNC_DEG(cpu_limit, thermostat->limits[1])
BUILD_SHOW_FUNC_DEG(gpu_limit, thermostat->limits[2])
BUILD_SHOW_FUNC_INT(specified_fan_speed, fan_speed)
BUILD_SHOW_FUNC_INT(cpu_fan_speed, (read_fan_speed(thermostat, FAN_SPEED[0])))
BUILD_SHOW_FUNC_INT(gpu_fan_speed, (read_fan_speed(thermostat, FAN_SPEED[1])))
BUILD_STORE_FUNC_INT(specified_fan_speed,fan_speed)
BUILD_SHOW_FUNC_INT(limit_adjust, limit_adjust)
BUILD_STORE_FUNC_DEG(limit_adjust, thermostat)
static DEVICE_ATTR(cpu_temperature, S_IRUGO,
show_cpu_temperature,NULL);
static DEVICE_ATTR(gpu_temperature, S_IRUGO,
show_gpu_temperature,NULL);
static DEVICE_ATTR(cpu_limit, S_IRUGO,
show_cpu_limit, NULL);
static DEVICE_ATTR(gpu_limit, S_IRUGO,
show_gpu_limit, NULL);
static DEVICE_ATTR(specified_fan_speed, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH,
show_specified_fan_speed,store_specified_fan_speed);
static DEVICE_ATTR(cpu_fan_speed, S_IRUGO,
show_cpu_fan_speed, NULL);
static DEVICE_ATTR(gpu_fan_speed, S_IRUGO,
show_gpu_fan_speed, NULL);
static DEVICE_ATTR(limit_adjust, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH,
show_limit_adjust, store_limit_adjust);
static int __init
thermostat_init(void)
{
struct device_node* np;
u32 *prop;
/* Currently, we only deal with the iBook G4, we will support
* all "2003" powerbooks later on
*/
np = of_find_node_by_name(NULL, "fan");
if (!np)
return -ENODEV;
if (device_is_compatible(np, "adt7460"))
therm_type = ADT7460;
else if (device_is_compatible(np, "adt7467"))
therm_type = ADT7467;
else
return -ENODEV;
prop = (u32 *)get_property(np, "reg", NULL);
if (!prop)
return -ENODEV;
therm_bus = ((*prop) >> 8) & 0x0f;
therm_address = ((*prop) & 0xff) >> 1;
printk(KERN_INFO "adt746x: Thermostat bus: %d, address: 0x%02x, limit_adjust: %d, fan_speed: %d\n",
therm_bus, therm_address, limit_adjust, fan_speed);
of_dev = of_platform_device_create(np, "temperatures");
if (of_dev == NULL) {
printk(KERN_ERR "Can't register temperatures device !\n");
return -ENODEV;
}
device_create_file(&of_dev->dev, &dev_attr_cpu_temperature);
device_create_file(&of_dev->dev, &dev_attr_gpu_temperature);
device_create_file(&of_dev->dev, &dev_attr_cpu_limit);
device_create_file(&of_dev->dev, &dev_attr_gpu_limit);
device_create_file(&of_dev->dev, &dev_attr_limit_adjust);
device_create_file(&of_dev->dev, &dev_attr_specified_fan_speed);
device_create_file(&of_dev->dev, &dev_attr_cpu_fan_speed);
if(therm_type == ADT7460)
device_create_file(&of_dev->dev, &dev_attr_gpu_fan_speed);
#ifndef CONFIG_I2C_KEYWEST
request_module("i2c-keywest");
#endif
return i2c_add_driver(&thermostat_driver);
}
static void __exit
thermostat_exit(void)
{
if (of_dev) {
device_remove_file(&of_dev->dev, &dev_attr_cpu_temperature);
device_remove_file(&of_dev->dev, &dev_attr_gpu_temperature);
device_remove_file(&of_dev->dev, &dev_attr_cpu_limit);
device_remove_file(&of_dev->dev, &dev_attr_gpu_limit);
device_remove_file(&of_dev->dev, &dev_attr_limit_adjust);
device_remove_file(&of_dev->dev, &dev_attr_specified_fan_speed);
device_remove_file(&of_dev->dev, &dev_attr_cpu_fan_speed);
if(therm_type == ADT7460)
device_remove_file(&of_dev->dev, &dev_attr_gpu_fan_speed);
of_device_unregister(of_dev);
}
i2c_del_driver(&thermostat_driver);
}
module_init(thermostat_init);
module_exit(thermostat_exit);
/*
* Device driver for the thermostats & fan controller of the
* Apple G5 "PowerMac7,2" desktop machines.
*
* (c) Copyright IBM Corp. 2003
*
* Maintained by: Benjamin Herrenschmidt
* <benh@kernel.crashing.org>
*
*
* 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 7.0 snapshot property lists.
*
* As far as the CPUs control loops are concerned, I use the
* calibration & PID constants provided by the EEPROM,
* I do _not_ embed any value from the property lists, as the ones
* provided by Darwin 7.0 seem to always have an older version that
* what I've seen on the actual computers.
* It would be interesting to verify that though. Darwin has a
* version code of 1.0.0d11 for all control loops it seems, while
* so far, the machines EEPROMs contain a dataset versioned 1.0.0f
*
* Darwin doesn't provide source to all parts, some missing
* bits like the AppleFCU driver or the actual scale of some
* of the values returned by sensors had to be "guessed" some
* way... or based on what Open Firmware does.
*
* I didn't yet figure out how to get the slots power consumption
* out of the FCU, so that part has not been implemented yet and
* the slots fan is set to a fixed 50% PWM, hoping this value is
* safe enough ...
*
* Note: I have observed strange oscillations of the CPU control
* loop on a dual G5 here. When idle, the CPU exhaust fan tend to
* oscillates slowly (over several minutes) between the minimum
* of 300RPMs and approx. 1000 RPMs. I don't know what is causing
* this, it could be some incorrect constant or an error in the
* way I ported the algorithm, or it could be just normal. I
* don't have full understanding on the way Apple tweaked the PID
* algorithm for the CPU control, it is definitely not a standard
* implementation...
*
* TODO: - Check MPU structure version/signature
* - Add things like /sbin/overtemp for non-critical
* overtemp conditions so userland can take some policy
* decisions, like slewing down CPUs
* - Deal with fan failures
*
* History:
*
* Nov. 13, 2003 : 0.5
* - First release
*
* Nov. 14, 2003 : 0.6
* - Read fan speed from FCU, low level fan routines now deal
* with errors & check fan status, though higher level don't
* do much.
* - Move a bunch of definitions to .h file
*
* Nov. 18, 2003 : 0.7
* - Fix build on ppc64 kernel
* - Move back statics definitions to .c file
* - Avoid calling schedule_timeout with a negative number
*
* Dev. 18, 2003 : 0.8
* - Fix typo when reading back fan speed on 2 CPU machines
*
*/
#include <linux/config.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/smp_lock.h>
#include <linux/wait.h>
#include <linux/reboot.h>
#include <linux/kmod.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 <asm/of_device.h>
#include "therm_pm72.h"
#define VERSION "0.8"
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...)
#endif
/*
* Driver statics
*/
static struct of_device * of_dev;
static struct i2c_adapter * u3_0;
static struct i2c_adapter * u3_1;
static struct i2c_client * fcu;
static struct cpu_pid_state cpu_state[2];
static struct backside_pid_state backside_state;
static struct drives_pid_state drives_state;
static int state;
static int cpu_count;
static pid_t ctrl_task;
static struct completion ctrl_complete;
static int critical_state;
static DECLARE_MUTEX(driver_lock);
/*
* i2c_driver structure to attach to the host i2c controller
*/
static int therm_pm72_attach(struct i2c_adapter *adapter);
static int therm_pm72_detach(struct i2c_adapter *adapter);
static struct i2c_driver therm_pm72_driver =
{
.name = "therm_pm72",
.id = 0xDEADBEEF,
.flags = I2C_DF_NOTIFY,
.attach_adapter = therm_pm72_attach,
.detach_adapter = therm_pm72_detach,
};
static inline void wait_ms(unsigned int ms)
{
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(1 + (ms * HZ + 999) / 1000);
}
/*
* Utility function to create an i2c_client structure and
* attach it to one of u3 adapters
*/
static struct i2c_client *attach_i2c_chip(int id, const char *name)
{
struct i2c_client *clt;
struct i2c_adapter *adap;
if (id & 0x100)
adap = u3_1;
else
adap = u3_0;
if (adap == NULL)
return NULL;
clt = kmalloc(sizeof(struct i2c_client), GFP_KERNEL);
if (clt == NULL)
return NULL;
memset(clt, 0, sizeof(struct i2c_client));
clt->addr = (id >> 1) & 0x7f;
clt->adapter = adap;
clt->driver = &therm_pm72_driver;
clt->id = 0xDEADBEEF;
strncpy(clt->name, name, I2C_NAME_SIZE-1);
if (i2c_attach_client(clt)) {
printk(KERN_ERR "therm_pm72: Failed to attach to i2c ID 0x%x\n", id);
kfree(clt);
return NULL;
}
return clt;
}
/*
* Utility function to get rid of the i2c_client structure
* (will also detach from the adapter hopepfully)
*/
static void detach_i2c_chip(struct i2c_client *clt)
{
i2c_detach_client(clt);
kfree(clt);
}
/*
* Here are the i2c chip access wrappers
*/
static int read_smon_adc(struct i2c_client *chip, int chan)
{
int ctrl;
ctrl = i2c_smbus_read_byte_data(chip, 1);
i2c_smbus_write_byte_data(chip, 1, (ctrl & 0x1f) | (chan << 5));
wait_ms(1);
return le16_to_cpu(i2c_smbus_read_word_data(chip, 4)) >> 6;
}
static int fan_read_reg(int reg, unsigned char *buf, int nb)
{
int tries, nr, nw;
buf[0] = reg;
tries = 0;
for (;;) {
nw = i2c_master_send(fcu, buf, 1);
if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100)
break;
wait_ms(10);
++tries;
}
if (nw <= 0) {
printk(KERN_ERR "Failure writing address to FCU: %d", nw);
return -EIO;
}
tries = 0;
for (;;) {
nr = i2c_master_recv(fcu, buf, nb);
if (nr > 0 || (nr < 0 && nr != ENODEV) || tries >= 100)
break;
wait_ms(10);
++tries;
}
if (nr <= 0)
printk(KERN_ERR "Failure reading data from FCU: %d", nw);
return nr;
}
static int fan_write_reg(int reg, const unsigned char *ptr, int nb)
{
int tries, nw;
unsigned char buf[16];
buf[0] = reg;
memcpy(buf+1, ptr, nb);
++nb;
tries = 0;
for (;;) {
nw = i2c_master_send(fcu, buf, nb);
if (nw > 0 || (nw < 0 && nw != EIO) || tries >= 100)
break;
wait_ms(10);
++tries;
}
if (nw < 0)
printk(KERN_ERR "Failure writing to FCU: %d", nw);
return nw;
}
static int set_rpm_fan(int fan, int rpm)
{
unsigned char buf[2];
int rc;
if (rpm < 300)
rpm = 300;
else if (rpm > 8191)
rpm = 8191;
buf[0] = rpm >> 5;
buf[1] = rpm << 3;
rc = fan_write_reg(0x10 + (fan * 2), buf, 2);
if (rc < 0)
return -EIO;
return 0;
}
static int get_rpm_fan(int fan, int programmed)
{
unsigned char failure;
unsigned char active;
unsigned char buf[2];
int rc, reg_base;
rc = fan_read_reg(0xb, &failure, 1);
if (rc != 1)
return -EIO;
if ((failure & (1 << fan)) != 0)
return -EFAULT;
rc = fan_read_reg(0xd, &active, 1);
if (rc != 1)
return -EIO;
if ((active & (1 << fan)) == 0)
return -ENXIO;
/* Programmed value or real current speed */
reg_base = programmed ? 0x10 : 0x11;
rc = fan_read_reg(reg_base + (fan * 2), buf, 2);
if (rc != 2)
return -EIO;
return (buf[0] << 5) | buf[1] >> 3;
}
static int set_pwm_fan(int fan, int pwm)
{
unsigned char buf[2];
int rc;
if (pwm < 10)
pwm = 10;
else if (pwm > 100)
pwm = 100;
pwm = (pwm * 2559) / 1000;
buf[0] = pwm;
rc = fan_write_reg(0x30 + (fan * 2), buf, 1);
if (rc < 0)
return rc;
return 0;
}
static int get_pwm_fan(int fan)
{
unsigned char failure;
unsigned char active;
unsigned char buf[2];
int rc;
rc = fan_read_reg(0x2b, &failure, 1);
if (rc != 1)
return -EIO;
if ((failure & (1 << fan)) != 0)
return -EFAULT;
rc = fan_read_reg(0x2d, &active, 1);
if (rc != 1)
return -EIO;
if ((active & (1 << fan)) == 0)
return -ENXIO;
/* Programmed value or real current speed */
rc = fan_read_reg(0x30 + (fan * 2), buf, 1);
if (rc != 1)
return -EIO;
return (buf[0] * 1000) / 2559;
}
/*
* Utility routine to read the CPU calibration EEPROM data
* from the device-tree
*/
static int read_eeprom(int cpu, struct mpu_data *out)
{
struct device_node *np;
char nodename[64];
u8 *data;
int len;
/* prom.c routine for finding a node by path is a bit brain dead
* and requires exact @xxx unit numbers. This is a bit ugly but
* will work for these machines
*/
sprintf(nodename, "/u3@0,f8000000/i2c@f8001000/cpuid@a%d", cpu ? 2 : 0);
np = of_find_node_by_path(nodename);
if (np == NULL) {
printk(KERN_ERR "therm_pm72: Failed to retreive cpuid node from device-tree\n");
return -ENODEV;
}
data = (u8 *)get_property(np, "cpuid", &len);
if (data == NULL) {
printk(KERN_ERR "therm_pm72: Failed to retreive cpuid property from device-tree\n");
of_node_put(np);
return -ENODEV;
}
memcpy(out, data, sizeof(struct mpu_data));
of_node_put(np);
return 0;
}
/*
* Now, unfortunately, sysfs doesn't give us a nice void * we could
* pass around to the attribute functions, so we don't really have
* choice but implement a bunch of them...
*
* That sucks a bit, we take the lock because FIX32TOPRINT evaluates
* the input twice... I accept patches :)
*/
#define BUILD_SHOW_FUNC_FIX(name, data) \
static ssize_t show_##name(struct device *dev, char *buf) \
{ \
ssize_t r; \
down(&driver_lock); \
r = sprintf(buf, "%d.%03d", FIX32TOPRINT(data)); \
up(&driver_lock); \
return r; \
}
#define BUILD_SHOW_FUNC_INT(name, data) \
static ssize_t show_##name(struct device *dev, char *buf) \
{ \
return sprintf(buf, "%d", data); \
}
BUILD_SHOW_FUNC_FIX(cpu0_temperature, cpu_state[0].last_temp)
BUILD_SHOW_FUNC_FIX(cpu0_voltage, cpu_state[0].voltage)
BUILD_SHOW_FUNC_FIX(cpu0_current, cpu_state[0].current_a)
BUILD_SHOW_FUNC_INT(cpu0_exhaust_fan_rpm, cpu_state[0].rpm)
BUILD_SHOW_FUNC_INT(cpu0_intake_fan_rpm, cpu_state[0].intake_rpm)
BUILD_SHOW_FUNC_FIX(cpu1_temperature, cpu_state[1].last_temp)
BUILD_SHOW_FUNC_FIX(cpu1_voltage, cpu_state[1].voltage)
BUILD_SHOW_FUNC_FIX(cpu1_current, cpu_state[1].current_a)
BUILD_SHOW_FUNC_INT(cpu1_exhaust_fan_rpm, cpu_state[1].rpm)
BUILD_SHOW_FUNC_INT(cpu1_intake_fan_rpm, cpu_state[1].intake_rpm)
BUILD_SHOW_FUNC_FIX(backside_temperature, backside_state.last_temp)
BUILD_SHOW_FUNC_INT(backside_fan_pwm, backside_state.pwm)
BUILD_SHOW_FUNC_FIX(drives_temperature, drives_state.last_temp)
BUILD_SHOW_FUNC_INT(drives_fan_rpm, drives_state.rpm)
static DEVICE_ATTR(cpu0_temperature,S_IRUGO,show_cpu0_temperature,NULL);
static DEVICE_ATTR(cpu0_voltage,S_IRUGO,show_cpu0_voltage,NULL);
static DEVICE_ATTR(cpu0_current,S_IRUGO,show_cpu0_current,NULL);
static DEVICE_ATTR(cpu0_exhaust_fan_rpm,S_IRUGO,show_cpu0_exhaust_fan_rpm,NULL);
static DEVICE_ATTR(cpu0_intake_fan_rpm,S_IRUGO,show_cpu0_intake_fan_rpm,NULL);
static DEVICE_ATTR(cpu1_temperature,S_IRUGO,show_cpu1_temperature,NULL);
static DEVICE_ATTR(cpu1_voltage,S_IRUGO,show_cpu1_voltage,NULL);
static DEVICE_ATTR(cpu1_current,S_IRUGO,show_cpu1_current,NULL);
static DEVICE_ATTR(cpu1_exhaust_fan_rpm,S_IRUGO,show_cpu1_exhaust_fan_rpm,NULL);
static DEVICE_ATTR(cpu1_intake_fan_rpm,S_IRUGO,show_cpu1_intake_fan_rpm,NULL);
static DEVICE_ATTR(backside_temperature,S_IRUGO,show_backside_temperature,NULL);
static DEVICE_ATTR(backside_fan_pwm,S_IRUGO,show_backside_fan_pwm,NULL);
static DEVICE_ATTR(drives_temperature,S_IRUGO,show_drives_temperature,NULL);
static DEVICE_ATTR(drives_fan_rpm,S_IRUGO,show_drives_fan_rpm,NULL);
/*
* CPUs fans control loop
*/
static void do_monitor_cpu(struct cpu_pid_state *state)
{
s32 temp, voltage, current_a, power, power_target;
s32 integral, derivative, proportional, adj_in_target, sval;
s64 integ_p, deriv_p, prop_p, sum;
int i, intake, rc;
DBG("cpu %d:\n", state->index);
/* Read current fan status */
if (state->index == 0)
rc = get_rpm_fan(CPUA_EXHAUST_FAN_RPM_ID, !RPM_PID_USE_ACTUAL_SPEED);
else
rc = get_rpm_fan(CPUB_EXHAUST_FAN_RPM_ID, !RPM_PID_USE_ACTUAL_SPEED);
if (rc < 0) {
printk(KERN_WARNING "Error %d reading CPU %d exhaust fan !\n",
rc, state->index);
/* XXX What do we do now ? */
} else
state->rpm = rc;
DBG(" current rpm: %d\n", state->rpm);
/* Get some sensor readings and scale it */
temp = read_smon_adc(state->monitor, 1);
voltage = read_smon_adc(state->monitor, 3);
current_a = read_smon_adc(state->monitor, 4);
/* Fixup temperature according to diode calibration
*/
DBG(" temp raw: %04x, m_diode: %04x, b_diode: %04x\n",
temp, state->mpu.mdiode, state->mpu.bdiode);
temp = ((s32)temp * (s32)state->mpu.mdiode + ((s32)state->mpu.bdiode << 12)) >> 2;
state->last_temp = temp;
DBG(" temp: %d.%03d\n", FIX32TOPRINT(temp));
/* Check tmax, increment overtemp if we are there. At tmax+8, we go
* full blown immediately and try to trigger a shutdown
*/
if (temp >= ((state->mpu.tmax + 8) << 16)) {
printk(KERN_WARNING "Warning ! CPU %d temperature way above maximum (%d) !\n",
state->index, temp >> 16);
state->overtemp = CPU_MAX_OVERTEMP;
} else if (temp > (state->mpu.tmax << 16))
state->overtemp++;
else
state->overtemp = 0;
if (state->overtemp >= CPU_MAX_OVERTEMP)
critical_state = 1;
if (state->overtemp > 0) {
state->rpm = state->mpu.rmaxn_exhaust_fan;
state->intake_rpm = intake = state->mpu.rmaxn_intake_fan;
goto do_set_fans;
}
/* Scale other sensor values according to fixed scales
* obtained in Darwin and calculate power from I and V
*/
state->voltage = voltage *= ADC_CPU_VOLTAGE_SCALE;
state->current_a = current_a *= ADC_CPU_CURRENT_SCALE;
power = (((u64)current_a) * ((u64)voltage)) >> 16;
/* Calculate power target value (could be done once for all)
* and convert to a 16.16 fp number
*/
power_target = ((u32)(state->mpu.pmaxh - state->mpu.padjmax)) << 16;
DBG(" current: %d.%03d, voltage: %d.%03d\n",
FIX32TOPRINT(current_a), FIX32TOPRINT(voltage));
DBG(" power: %d.%03d W, target: %d.%03d, error: %d.%03d\n", FIX32TOPRINT(power),
FIX32TOPRINT(power_target), FIX32TOPRINT(power_target - power));
/* Store temperature and power in history array */
state->cur_temp = (state->cur_temp + 1) % CPU_TEMP_HISTORY_SIZE;
state->temp_history[state->cur_temp] = temp;
state->cur_power = (state->cur_power + 1) % state->count_power;
state->power_history[state->cur_power] = power;
state->error_history[state->cur_power] = power_target - power;
/* If first loop, fill the history table */
if (state->first) {
for (i = 0; i < (state->count_power - 1); i++) {
state->cur_power = (state->cur_power + 1) % state->count_power;
state->power_history[state->cur_power] = power;
state->error_history[state->cur_power] = power_target - power;
}
for (i = 0; i < (CPU_TEMP_HISTORY_SIZE - 1); i++) {
state->cur_temp = (state->cur_temp + 1) % CPU_TEMP_HISTORY_SIZE;
state->temp_history[state->cur_temp] = temp;
}
state->first = 0;
}
/* Calculate the integral term normally based on the "power" values */
sum = 0;
integral = 0;
for (i = 0; i < state->count_power; i++)
integral += state->error_history[i];
integral *= CPU_PID_INTERVAL;
DBG(" integral: %08x\n", integral);
/* Calculate the adjusted input (sense value).
* G_r is 12.20
* integ is 16.16
* so the result is 28.36
*
* input target is mpu.ttarget, input max is mpu.tmax
*/
integ_p = ((s64)state->mpu.pid_gr) * (s64)integral;
DBG(" integ_p: %d\n", (int)(deriv_p >> 36));
sval = (state->mpu.tmax << 16) - ((integ_p >> 20) & 0xffffffff);
adj_in_target = (state->mpu.ttarget << 16);
if (adj_in_target > sval)
adj_in_target = sval;
DBG(" adj_in_target: %d.%03d, ttarget: %d\n", FIX32TOPRINT(adj_in_target),
state->mpu.ttarget);
/* Calculate the derivative term */
derivative = state->temp_history[state->cur_temp] -
state->temp_history[(state->cur_temp + CPU_TEMP_HISTORY_SIZE - 1)
% CPU_TEMP_HISTORY_SIZE];
derivative /= CPU_PID_INTERVAL;
deriv_p = ((s64)state->mpu.pid_gd) * (s64)derivative;
DBG(" deriv_p: %d\n", (int)(deriv_p >> 36));
sum += deriv_p;
/* Calculate the proportional term */
proportional = temp - adj_in_target;
prop_p = ((s64)state->mpu.pid_gp) * (s64)proportional;
DBG(" prop_p: %d\n", (int)(prop_p >> 36));
sum += prop_p;
/* Scale sum */
sum >>= 36;
DBG(" sum: %d\n", (int)sum);
state->rpm += (s32)sum;
if (state->rpm < state->mpu.rminn_exhaust_fan)
state->rpm = state->mpu.rminn_exhaust_fan;
if (state->rpm > state->mpu.rmaxn_exhaust_fan)
state->rpm = state->mpu.rmaxn_exhaust_fan;
intake = (state->rpm * CPU_INTAKE_SCALE) >> 16;
if (intake < state->mpu.rminn_intake_fan)
intake = state->mpu.rminn_intake_fan;
if (intake > state->mpu.rmaxn_intake_fan)
intake = state->mpu.rmaxn_intake_fan;
state->intake_rpm = intake;
do_set_fans:
DBG("** CPU %d RPM: %d Ex, %d In, overtemp: %d\n",
state->index, (int)state->rpm, intake, state->overtemp);
/* We should check for errors, shouldn't we ? But then, what
* do we do once the error occurs ? For FCU notified fan
* failures (-EFAULT) we probably want to notify userland
* some way...
*/
if (state->index == 0) {
set_rpm_fan(CPUA_INTAKE_FAN_RPM_ID, intake);
set_rpm_fan(CPUA_EXHAUST_FAN_RPM_ID, state->rpm);
} else {
set_rpm_fan(CPUB_INTAKE_FAN_RPM_ID, intake);
set_rpm_fan(CPUB_EXHAUST_FAN_RPM_ID, state->rpm);
}
}
/*
* Initialize the state structure for one CPU control loop
*/
static int init_cpu_state(struct cpu_pid_state *state, int index)
{
state->index = index;
state->first = 1;
state->rpm = 1000;
state->overtemp = 0;
if (index == 0)
state->monitor = attach_i2c_chip(SUPPLY_MONITOR_ID, "CPU0_monitor");
else if (index == 1)
state->monitor = attach_i2c_chip(SUPPLY_MONITORB_ID, "CPU1_monitor");
if (state->monitor == NULL)
goto fail;
if (read_eeprom(index, &state->mpu))
goto fail;
state->count_power = state->mpu.tguardband;
if (state->count_power > CPU_POWER_HISTORY_SIZE) {
printk(KERN_WARNING "Warning ! too many power history slots\n");
state->count_power = CPU_POWER_HISTORY_SIZE;
}
DBG("CPU %d Using %d power history entries\n", index, state->count_power);
if (index == 0) {
device_create_file(&of_dev->dev, &dev_attr_cpu0_temperature);
device_create_file(&of_dev->dev, &dev_attr_cpu0_voltage);
device_create_file(&of_dev->dev, &dev_attr_cpu0_current);
device_create_file(&of_dev->dev, &dev_attr_cpu0_exhaust_fan_rpm);
device_create_file(&of_dev->dev, &dev_attr_cpu0_intake_fan_rpm);
} else {
device_create_file(&of_dev->dev, &dev_attr_cpu1_temperature);
device_create_file(&of_dev->dev, &dev_attr_cpu1_voltage);
device_create_file(&of_dev->dev, &dev_attr_cpu1_current);
device_create_file(&of_dev->dev, &dev_attr_cpu1_exhaust_fan_rpm);
device_create_file(&of_dev->dev, &dev_attr_cpu1_intake_fan_rpm);
}
return 0;
fail:
if (state->monitor)
detach_i2c_chip(state->monitor);
state->monitor = NULL;
return -ENODEV;
}
/*
* Dispose of the state data for one CPU control loop
*/
static void dispose_cpu_state(struct cpu_pid_state *state)
{
if (state->monitor == NULL)
return;
if (state->index == 0) {
device_remove_file(&of_dev->dev, &dev_attr_cpu0_temperature);
device_remove_file(&of_dev->dev, &dev_attr_cpu0_voltage);
device_remove_file(&of_dev->dev, &dev_attr_cpu0_current);
device_remove_file(&of_dev->dev, &dev_attr_cpu0_exhaust_fan_rpm);
device_remove_file(&of_dev->dev, &dev_attr_cpu0_intake_fan_rpm);
} else {
device_remove_file(&of_dev->dev, &dev_attr_cpu1_temperature);
device_remove_file(&of_dev->dev, &dev_attr_cpu1_voltage);
device_remove_file(&of_dev->dev, &dev_attr_cpu1_current);
device_remove_file(&of_dev->dev, &dev_attr_cpu1_exhaust_fan_rpm);
device_remove_file(&of_dev->dev, &dev_attr_cpu1_intake_fan_rpm);
}
detach_i2c_chip(state->monitor);
state->monitor = NULL;
}
/*
* Motherboard backside & U3 heatsink fan control loop
*/
static void do_monitor_backside(struct backside_pid_state *state)
{
s32 temp, integral, derivative;
s64 integ_p, deriv_p, prop_p, sum;
int i, rc;
if (--state->ticks != 0)
return;
state->ticks = BACKSIDE_PID_INTERVAL;
DBG("backside:\n");
/* Check fan status */
rc = get_pwm_fan(BACKSIDE_FAN_PWM_ID);
if (rc < 0) {
printk(KERN_WARNING "Error %d reading backside fan !\n", rc);
/* XXX What do we do now ? */
} else
state->pwm = rc;
DBG(" current pwm: %d\n", state->pwm);
/* Get some sensor readings */
temp = i2c_smbus_read_byte_data(state->monitor, MAX6690_EXT_TEMP) << 16;
state->last_temp = temp;
DBG(" temp: %d.%03d, target: %d.%03d\n", FIX32TOPRINT(temp),
FIX32TOPRINT(BACKSIDE_PID_INPUT_TARGET));
/* Store temperature and error in history array */
state->cur_sample = (state->cur_sample + 1) % BACKSIDE_PID_HISTORY_SIZE;
state->sample_history[state->cur_sample] = temp;
state->error_history[state->cur_sample] = temp - BACKSIDE_PID_INPUT_TARGET;
/* If first loop, fill the history table */
if (state->first) {
for (i = 0; i < (BACKSIDE_PID_HISTORY_SIZE - 1); i++) {
state->cur_sample = (state->cur_sample + 1) %
BACKSIDE_PID_HISTORY_SIZE;
state->sample_history[state->cur_sample] = temp;
state->error_history[state->cur_sample] =
temp - BACKSIDE_PID_INPUT_TARGET;
}
state->first = 0;
}
/* Calculate the integral term */
sum = 0;
integral = 0;
for (i = 0; i < BACKSIDE_PID_HISTORY_SIZE; i++)
integral += state->error_history[i];
integral *= BACKSIDE_PID_INTERVAL;
DBG(" integral: %08x\n", integral);
integ_p = ((s64)BACKSIDE_PID_G_r) * (s64)integral;
DBG(" integ_p: %d\n", (int)(integ_p >> 36));
sum += integ_p;
/* Calculate the derivative term */
derivative = state->error_history[state->cur_sample] -
state->error_history[(state->cur_sample + BACKSIDE_PID_HISTORY_SIZE - 1)
% BACKSIDE_PID_HISTORY_SIZE];
derivative /= BACKSIDE_PID_INTERVAL;
deriv_p = ((s64)BACKSIDE_PID_G_d) * (s64)derivative;
DBG(" deriv_p: %d\n", (int)(deriv_p >> 36));
sum += deriv_p;
/* Calculate the proportional term */
prop_p = ((s64)BACKSIDE_PID_G_p) * (s64)(state->error_history[state->cur_sample]);
DBG(" prop_p: %d\n", (int)(prop_p >> 36));
sum += prop_p;
/* Scale sum */
sum >>= 36;
DBG(" sum: %d\n", (int)sum);
state->pwm += (s32)sum;
if (state->pwm < BACKSIDE_PID_OUTPUT_MIN)
state->pwm = BACKSIDE_PID_OUTPUT_MIN;
if (state->pwm > BACKSIDE_PID_OUTPUT_MAX)
state->pwm = BACKSIDE_PID_OUTPUT_MAX;
DBG("** BACKSIDE PWM: %d\n", (int)state->pwm);
set_pwm_fan(BACKSIDE_FAN_PWM_ID, state->pwm);
}
/*
* Initialize the state structure for the backside fan control loop
*/
static int init_backside_state(struct backside_pid_state *state)
{
state->ticks = 1;
state->first = 1;
state->pwm = 50;
state->monitor = attach_i2c_chip(BACKSIDE_MAX_ID, "backside_temp");
if (state->monitor == NULL)
return -ENODEV;
device_create_file(&of_dev->dev, &dev_attr_backside_temperature);
device_create_file(&of_dev->dev, &dev_attr_backside_fan_pwm);
return 0;
}
/*
* Dispose of the state data for the backside control loop
*/
static void dispose_backside_state(struct backside_pid_state *state)
{
if (state->monitor == NULL)
return;
device_remove_file(&of_dev->dev, &dev_attr_backside_temperature);
device_remove_file(&of_dev->dev, &dev_attr_backside_fan_pwm);
detach_i2c_chip(state->monitor);
state->monitor = NULL;
}
/*
* Drives bay fan control loop
*/
static void do_monitor_drives(struct drives_pid_state *state)
{
s32 temp, integral, derivative;
s64 integ_p, deriv_p, prop_p, sum;
int i, rc;
if (--state->ticks != 0)
return;
state->ticks = DRIVES_PID_INTERVAL;
DBG("drives:\n");
/* Check fan status */
rc = get_rpm_fan(DRIVES_FAN_RPM_ID, !RPM_PID_USE_ACTUAL_SPEED);
if (rc < 0) {
printk(KERN_WARNING "Error %d reading drives fan !\n", rc);
/* XXX What do we do now ? */
} else
state->rpm = rc;
DBG(" current rpm: %d\n", state->rpm);
/* Get some sensor readings */
temp = le16_to_cpu(i2c_smbus_read_word_data(state->monitor, DS1775_TEMP)) << 8;
state->last_temp = temp;
DBG(" temp: %d.%03d, target: %d.%03d\n", FIX32TOPRINT(temp),
FIX32TOPRINT(DRIVES_PID_INPUT_TARGET));
/* Store temperature and error in history array */
state->cur_sample = (state->cur_sample + 1) % DRIVES_PID_HISTORY_SIZE;
state->sample_history[state->cur_sample] = temp;
state->error_history[state->cur_sample] = temp - DRIVES_PID_INPUT_TARGET;
/* If first loop, fill the history table */
if (state->first) {
for (i = 0; i < (DRIVES_PID_HISTORY_SIZE - 1); i++) {
state->cur_sample = (state->cur_sample + 1) %
DRIVES_PID_HISTORY_SIZE;
state->sample_history[state->cur_sample] = temp;
state->error_history[state->cur_sample] =
temp - DRIVES_PID_INPUT_TARGET;
}
state->first = 0;
}
/* Calculate the integral term */
sum = 0;
integral = 0;
for (i = 0; i < DRIVES_PID_HISTORY_SIZE; i++)
integral += state->error_history[i];
integral *= DRIVES_PID_INTERVAL;
DBG(" integral: %08x\n", integral);
integ_p = ((s64)DRIVES_PID_G_r) * (s64)integral;
DBG(" integ_p: %d\n", (int)(integ_p >> 36));
sum += integ_p;
/* Calculate the derivative term */
derivative = state->error_history[state->cur_sample] -
state->error_history[(state->cur_sample + DRIVES_PID_HISTORY_SIZE - 1)
% DRIVES_PID_HISTORY_SIZE];
derivative /= DRIVES_PID_INTERVAL;
deriv_p = ((s64)DRIVES_PID_G_d) * (s64)derivative;
DBG(" deriv_p: %d\n", (int)(deriv_p >> 36));
sum += deriv_p;
/* Calculate the proportional term */
prop_p = ((s64)DRIVES_PID_G_p) * (s64)(state->error_history[state->cur_sample]);
DBG(" prop_p: %d\n", (int)(prop_p >> 36));
sum += prop_p;
/* Scale sum */
sum >>= 36;
DBG(" sum: %d\n", (int)sum);
state->rpm += (s32)sum;
if (state->rpm < DRIVES_PID_OUTPUT_MIN)
state->rpm = DRIVES_PID_OUTPUT_MIN;
if (state->rpm > DRIVES_PID_OUTPUT_MAX)
state->rpm = DRIVES_PID_OUTPUT_MAX;
DBG("** DRIVES RPM: %d\n", (int)state->rpm);
set_rpm_fan(DRIVES_FAN_RPM_ID, state->rpm);
}
/*
* Initialize the state structure for the drives bay fan control loop
*/
static int init_drives_state(struct drives_pid_state *state)
{
state->ticks = 1;
state->first = 1;
state->rpm = 1000;
state->monitor = attach_i2c_chip(DRIVES_DALLAS_ID, "drives_temp");
if (state->monitor == NULL)
return -ENODEV;
device_create_file(&of_dev->dev, &dev_attr_drives_temperature);
device_create_file(&of_dev->dev, &dev_attr_drives_fan_rpm);
return 0;
}
/*
* Dispose of the state data for the drives control loop
*/
static void dispose_drives_state(struct drives_pid_state *state)
{
if (state->monitor == NULL)
return;
device_remove_file(&of_dev->dev, &dev_attr_drives_temperature);
device_remove_file(&of_dev->dev, &dev_attr_drives_fan_rpm);
detach_i2c_chip(state->monitor);
state->monitor = NULL;
}
static int call_critical_overtemp(void)
{
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);
}
/*
* Here's the kernel thread that calls the various control loops
*/
static int main_control_loop(void *x)
{
daemonize("kfand");
DBG("main_control_loop started\n");
/* Set the PCI fan once for now */
set_pwm_fan(SLOTS_FAN_PWM_ID, SLOTS_FAN_DEFAULT_PWM);
while (state == state_attached) {
unsigned long elapsed, start;
start = jiffies;
down(&driver_lock);
do_monitor_cpu(&cpu_state[0]);
if (cpu_state[1].monitor != NULL)
do_monitor_cpu(&cpu_state[1]);
do_monitor_backside(&backside_state);
do_monitor_drives(&drives_state);
up(&driver_lock);
if (critical_state == 1) {
printk(KERN_WARNING "Temperature control detected a critical condition\n");
printk(KERN_WARNING "Attempting to shut down...\n");
if (call_critical_overtemp()) {
printk(KERN_WARNING "Can't call %s, power off now!\n",
critical_overtemp_path);
machine_power_off();
}
}
if (critical_state > 0)
critical_state++;
if (critical_state > MAX_CRITICAL_STATE) {
printk(KERN_WARNING "Shutdown timed out, power off now !\n");
machine_power_off();
}
// FIXME: Deal with signals
set_current_state(TASK_INTERRUPTIBLE);
elapsed = jiffies - start;
if (elapsed < HZ)
schedule_timeout(HZ - elapsed);
}
DBG("main_control_loop ended\n");
ctrl_task = 0;
complete_and_exit(&ctrl_complete, 0);
}
/*
* Dispose the control loops when tearing down
*/
static void dispose_control_loops(void)
{
dispose_cpu_state(&cpu_state[0]);
dispose_cpu_state(&cpu_state[1]);
dispose_backside_state(&backside_state);
dispose_drives_state(&drives_state);
}
/*
* Create the control loops. U3-0 i2c bus is up, so we can now
* get to the various sensors
*/
static int create_control_loops(void)
{
struct device_node *np;
/* Count CPUs from the device-tree, we don't care how many are
* actually used by Linux
*/
cpu_count = 0;
for (np = NULL; NULL != (np = of_find_node_by_type(np, "cpu"));)
cpu_count++;
DBG("counted %d CPUs in the device-tree\n", cpu_count);
/* Create control loops for everything. If any fail, everything
* fails
*/
if (init_cpu_state(&cpu_state[0], 0))
goto fail;
if (cpu_count > 1 && init_cpu_state(&cpu_state[1], 1))
goto fail;
if (init_backside_state(&backside_state))
goto fail;
if (init_drives_state(&drives_state))
goto fail;
DBG("all control loops up !\n");
return 0;
fail:
DBG("failure creating control loops, disposing\n");
dispose_control_loops();
return -ENODEV;
}
/*
* Start the control loops after everything is up, that is create
* the thread that will make them run
*/
static void start_control_loops(void)
{
init_completion(&ctrl_complete);
ctrl_task = kernel_thread(main_control_loop, NULL, SIGCHLD | CLONE_KERNEL);
}
/*
* Stop the control loops when tearing down
*/
static void stop_control_loops(void)
{
if (ctrl_task != 0)
wait_for_completion(&ctrl_complete);
}
/*
* Attach to the i2c FCU after detecting U3-1 bus
*/
static int attach_fcu(void)
{
fcu = attach_i2c_chip(FAN_CTRLER_ID, "fcu");
if (fcu == NULL)
return -ENODEV;
DBG("FCU attached\n");
return 0;
}
/*
* Detach from the i2c FCU when tearing down
*/
static void detach_fcu(void)
{
if (fcu)
detach_i2c_chip(fcu);
fcu = NULL;
}
/*
* Attach to the i2c controller. We probe the various chips based
* on the device-tree nodes and build everything for the driver to
* run, we then kick the driver monitoring thread
*/
static int therm_pm72_attach(struct i2c_adapter *adapter)
{
down(&driver_lock);
/* Check state */
if (state == state_detached)
state = state_attaching;
if (state != state_attaching) {
up(&driver_lock);
return 0;
}
/* Check if we are looking for one of these */
if (u3_0 == NULL && !strcmp(adapter->name, "u3 0")) {
u3_0 = adapter;
DBG("found U3-0, creating control loops\n");
if (create_control_loops())
u3_0 = NULL;
} else if (u3_1 == NULL && !strcmp(adapter->name, "u3 1")) {
u3_1 = adapter;
DBG("found U3-1, attaching FCU\n");
if (attach_fcu())
u3_1 = NULL;
}
/* We got all we need, start control loops */
if (u3_0 != NULL && u3_1 != NULL) {
DBG("everything up, starting control loops\n");
state = state_attached;
start_control_loops();
}
up(&driver_lock);
return 0;
}
/*
* Called on every adapter when the driver or the i2c controller
* is going away.
*/
static int therm_pm72_detach(struct i2c_adapter *adapter)
{
down(&driver_lock);
if (state != state_detached)
state = state_detaching;
/* Stop control loops if any */
DBG("stopping control loops\n");
up(&driver_lock);
stop_control_loops();
down(&driver_lock);
if (u3_0 != NULL && !strcmp(adapter->name, "u3 0")) {
DBG("lost U3-0, disposing control loops\n");
dispose_control_loops();
u3_0 = NULL;
}
if (u3_1 != NULL && !strcmp(adapter->name, "u3 1")) {
DBG("lost U3-1, detaching FCU\n");
detach_fcu();
u3_1 = NULL;
}
if (u3_0 == NULL && u3_1 == NULL)
state = state_detached;
up(&driver_lock);
return 0;
}
static int fcu_of_probe(struct of_device* dev, const struct of_match *match)
{
int rc;
state = state_detached;
rc = i2c_add_driver(&therm_pm72_driver);
if (rc < 0)
return rc;
return 0;
}
static int fcu_of_remove(struct of_device* dev)
{
i2c_del_driver(&therm_pm72_driver);
return 0;
}
static struct of_match fcu_of_match[] =
{
{
.name = OF_ANY_MATCH,
.type = "fcu",
.compatible = OF_ANY_MATCH
},
{},
};
static struct of_platform_driver fcu_of_platform_driver =
{
.name = "temperature",
.match_table = fcu_of_match,
.probe = fcu_of_probe,
.remove = fcu_of_remove
};
/*
* Check machine type, attach to i2c controller
*/
static int __init therm_pm72_init(void)
{
struct device_node *np;
if (!machine_is_compatible("PowerMac7,2"))
return -ENODEV;
printk(KERN_INFO "PowerMac G5 Thermal control driver %s\n", VERSION);
np = of_find_node_by_type(NULL, "fcu");
if (np == NULL) {
printk(KERN_ERR "Can't find FCU in device-tree !\n");
return -ENODEV;
}
of_dev = of_platform_device_create(np, "temperature");
if (of_dev == NULL) {
printk(KERN_ERR "Can't register FCU platform device !\n");
return -ENODEV;
}
of_register_driver(&fcu_of_platform_driver);
return 0;
}
static void __exit therm_pm72_exit(void)
{
of_unregister_driver(&fcu_of_platform_driver);
if (of_dev)
of_device_unregister(of_dev);
}
module_init(therm_pm72_init);
module_exit(therm_pm72_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("Driver for Apple's PowerMac7,2 G5 thermal control");
MODULE_LICENSE("GPL");
#ifndef __THERM_PMAC_7_2_H__
#define __THERM_PMAC_7_2_H__
typedef unsigned short fu16;
typedef int fs32;
typedef short fs16;
struct mpu_data
{
u8 signature; /* 0x00 - EEPROM sig. */
u8 bytes_used; /* 0x01 - Bytes used in eeprom (160 ?) */
u8 size; /* 0x02 - EEPROM size (256 ?) */
u8 version; /* 0x03 - EEPROM version */
u32 data_revision; /* 0x04 - Dataset revision */
u8 processor_bin_code[3]; /* 0x08 - Processor BIN code */
u8 bin_code_expansion; /* 0x0b - ??? (padding ?) */
u8 processor_num; /* 0x0c - Number of CPUs on this MPU */
u8 input_mul_bus_div; /* 0x0d - Clock input multiplier/bus divider */
u8 reserved1[2]; /* 0x0e - */
u32 input_clk_freq_high; /* 0x10 - Input clock frequency high */
u8 cpu_nb_target_cycles; /* 0x14 - ??? */
u8 cpu_statlat; /* 0x15 - ??? */
u8 cpu_snooplat; /* 0x16 - ??? */
u8 cpu_snoopacc; /* 0x17 - ??? */
u8 nb_paamwin; /* 0x18 - ??? */
u8 nb_statlat; /* 0x19 - ??? */
u8 nb_snooplat; /* 0x1a - ??? */
u8 nb_snoopwin; /* 0x1b - ??? */
u8 api_bus_mode; /* 0x1c - ??? */
u8 reserved2[3]; /* 0x1d - */
u32 input_clk_freq_low; /* 0x20 - Input clock frequency low */
u8 processor_card_slot; /* 0x24 - Processor card slot number */
u8 reserved3[2]; /* 0x25 - */
u8 padjmax; /* 0x27 - Max power adjustment (Not in OF!) */
u8 ttarget; /* 0x28 - Target temperature */
u8 tmax; /* 0x29 - Max temperature */
u8 pmaxh; /* 0x2a - Max power */
u8 tguardband; /* 0x2b - Guardband temp ??? Hist. len in OSX */
fs32 pid_gp; /* 0x2c - PID proportional gain */
fs32 pid_gr; /* 0x30 - PID reset gain */
fs32 pid_gd; /* 0x34 - PID derivative gain */
fu16 voph; /* 0x38 - Vop High */
fu16 vopl; /* 0x3a - Vop Low */
fs16 nactual_die; /* 0x3c - nActual Die */
fs16 nactual_heatsink; /* 0x3e - nActual Heatsink */
fs16 nactual_system; /* 0x40 - nActual System */
u16 calibration_flags; /* 0x42 - Calibration flags */
fu16 mdiode; /* 0x44 - Diode M value (scaling factor) */
fs16 bdiode; /* 0x46 - Diode B value (offset) */
fs32 theta_heat_sink; /* 0x48 - Theta heat sink */
u16 rminn_intake_fan; /* 0x4c - Intake fan min RPM */
u16 rmaxn_intake_fan; /* 0x4e - Intake fan max RPM */
u16 rminn_exhaust_fan; /* 0x50 - Exhaust fan min RPM */
u16 rmaxn_exhaust_fan; /* 0x52 - Exhaust fan max RPM */
u8 processor_part_num[8]; /* 0x54 - Processor part number */
u32 processor_lot_num; /* 0x5c - Processor lot number */
u8 orig_card_sernum[0x10]; /* 0x60 - Card original serial number */
u8 curr_card_sernum[0x10]; /* 0x70 - Card current serial number */
u8 mlb_sernum[0x18]; /* 0x80 - MLB serial number */
u32 checksum1; /* 0x98 - */
u32 checksum2; /* 0x9c - */
}; /* Total size = 0xa0 */
/* Display a 16.16 fixed point value */
#define FIX32TOPRINT(f) ((f) >> 16),((((f) & 0xffff) * 1000) >> 16)
/*
* Maximum number of seconds to be in critical state (after a
* normal shutdown attempt). If the machine isn't down after
* this counter elapses, we force an immediate machine power
* off.
*/
#define MAX_CRITICAL_STATE 30
static char * critical_overtemp_path = "/sbin/critical_overtemp";
/*
* This option is "weird" :) Basically, if you define this to 1
* the control loop for the RPMs fans (not PWMs) will apply the
* correction factor obtained from the PID to the _actual_ RPM
* speed read from the FCU.
* If you define the below constant to 0, then it will be
* applied to the setpoint RPM speed, that is basically the
* speed we proviously "asked" for.
*
* I'm not sure which of these Apple's algorithm is supposed
* to use
*/
#define RPM_PID_USE_ACTUAL_SPEED 1
/*
* i2c IDs. Currently, we hard code those and assume that
* the FCU is on U3 bus 1 while all sensors are on U3 bus
* 0. This appear to be safe enough for this first version
* of the driver, though I would accept any clean patch
* doing a better use of the device-tree without turning the
* while i2c registration mecanism into a racy mess
*/
#define FAN_CTRLER_ID 0x15e
#define SUPPLY_MONITOR_ID 0x58
#define SUPPLY_MONITORB_ID 0x5a
#define DRIVES_DALLAS_ID 0x94
#define BACKSIDE_MAX_ID 0x98
/*
* Some MAX6690 & DS1775 register definitions
*/
#define MAX6690_INT_TEMP 0
#define MAX6690_EXT_TEMP 1
#define DS1775_TEMP 0
/*
* Scaling factors for the AD7417 ADC converters (except
* for the CPU diode which is obtained from the EEPROM).
* Those values are obtained from the property list of
* the darwin driver
*/
#define ADC_12V_CURRENT_SCALE 0x0320 /* _AD2 */
#define ADC_CPU_VOLTAGE_SCALE 0x00a0 /* _AD3 */
#define ADC_CPU_CURRENT_SCALE 0x1f40 /* _AD4 */
/*
* PID factors for the U3/Backside fan control loop
*/
#define BACKSIDE_FAN_PWM_ID 1
#define BACKSIDE_PID_G_d 0x02800000
#define BACKSIDE_PID_G_p 0x00500000
#define BACKSIDE_PID_G_r 0x00000000
#define BACKSIDE_PID_INPUT_TARGET 0x00410000
#define BACKSIDE_PID_INTERVAL 5
#define BACKSIDE_PID_OUTPUT_MAX 100
#define BACKSIDE_PID_OUTPUT_MIN 20
#define BACKSIDE_PID_HISTORY_SIZE 2
struct backside_pid_state
{
int ticks;
struct i2c_client * monitor;
s32 sample_history[BACKSIDE_PID_HISTORY_SIZE];
s32 error_history[BACKSIDE_PID_HISTORY_SIZE];
int cur_sample;
s32 last_temp;
int pwm;
int first;
};
/*
* PID factors for the Drive Bay fan control loop
*/
#define DRIVES_FAN_RPM_ID 2
#define DRIVES_PID_G_d 0x01e00000
#define DRIVES_PID_G_p 0x00500000
#define DRIVES_PID_G_r 0x00000000
#define DRIVES_PID_INPUT_TARGET 0x00280000
#define DRIVES_PID_INTERVAL 5
#define DRIVES_PID_OUTPUT_MAX 4000
#define DRIVES_PID_OUTPUT_MIN 300
#define DRIVES_PID_HISTORY_SIZE 2
struct drives_pid_state
{
int ticks;
struct i2c_client * monitor;
s32 sample_history[BACKSIDE_PID_HISTORY_SIZE];
s32 error_history[BACKSIDE_PID_HISTORY_SIZE];
int cur_sample;
s32 last_temp;
int rpm;
int first;
};
#define SLOTS_FAN_PWM_ID 2
#define SLOTS_FAN_DEFAULT_PWM 50 /* Do better here ! */
/*
* IDs in Darwin for the sensors & fans
*
* CPU A AD7417_TEMP 10 (CPU A ambient temperature)
* CPU A AD7417_AD1 11 (CPU A diode temperature)
* CPU A AD7417_AD2 12 (CPU A 12V current)
* CPU A AD7417_AD3 13 (CPU A voltage)
* CPU A AD7417_AD4 14 (CPU A current)
*
* CPU A FAKE POWER 48 (I_V_inputs: 13, 14)
*
* CPU B AD7417_TEMP 15 (CPU B ambient temperature)
* CPU B AD7417_AD1 16 (CPU B diode temperature)
* CPU B AD7417_AD2 17 (CPU B 12V current)
* CPU B AD7417_AD3 18 (CPU B voltage)
* CPU B AD7417_AD4 19 (CPU B current)
*
* CPU B FAKE POWER 49 (I_V_inputs: 18, 19)
*/
#define CPUA_INTAKE_FAN_RPM_ID 3
#define CPUA_EXHAUST_FAN_RPM_ID 4
#define CPUB_INTAKE_FAN_RPM_ID 5
#define CPUB_EXHAUST_FAN_RPM_ID 6
#define CPU_INTAKE_SCALE 0x0000f852
#define CPU_TEMP_HISTORY_SIZE 2
#define CPU_POWER_HISTORY_SIZE 10
#define CPU_PID_INTERVAL 1
#define CPU_MAX_OVERTEMP 30
struct cpu_pid_state
{
int index;
struct i2c_client * monitor;
struct mpu_data mpu;
int overtemp;
s32 temp_history[CPU_TEMP_HISTORY_SIZE];
int cur_temp;
s32 power_history[CPU_POWER_HISTORY_SIZE];
s32 error_history[CPU_POWER_HISTORY_SIZE];
int cur_power;
int count_power;
int rpm;
int intake_rpm;
s32 voltage;
s32 current_a;
s32 last_temp;
int first;
};
/*
* Driver state
*/
enum {
state_detached,
state_attaching,
state_attached,
state_detaching,
};
#endif /* __THERM_PMAC_7_2_H__ */
/*
* Creation Date: <2003/03/14 20:54:13 samuel>
* Time-stamp: <2003/03/15 18:55:53 samuel>
*
* <therm_windtunnel.c>
*
* The G4 "windtunnel" has a single fan controlled by a
* DS1775 fan controller and an ADM1030 thermostat.
*
* The fan controller is equipped with a temperature sensor
* which measures the case temperature. The ADM censor
* measures the CPU temperature. This driver tunes the
* behavior of the fan. It is based upon empirical observations
* of the 'AppleFan' driver under OSX.
*
* WARNING: This driver has only been testen on Apple's
* 1.25 MHz Dual G4 (March 03). Other machines might have
* a different thermal design. It is tuned for a CPU
* temperatur around 57 C.
*
* Copyright (C) 2003 Samuel Rydh (samuel@ibrium.se)
*
* Loosely based upon 'thermostat.c' written by Benjamin Herrenschmidt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation
*
*/
#include <linux/config.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/workqueue.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/sections.h>
MODULE_AUTHOR("Samuel Rydh <samuel@ibrium.se>");
MODULE_DESCRIPTION("Apple G4 (windtunnel) fan driver");
MODULE_LICENSE("GPL");
#define LOG_TEMP 0 /* continously log temperature */
/* scan 0x48-0x4f (DS1775) and 0x2c-2x2f (ADM1030) */
static unsigned short normal_i2c[] = { 0x49, 0x2c, I2C_CLIENT_END };
static unsigned short normal_i2c_range[] = { 0x48, 0x4f, 0x2c, 0x2f, I2C_CLIENT_END };
static struct work_struct poll_work;
I2C_CLIENT_INSMOD;
#define I2C_DRIVERID_G4FAN 0x9001 /* fixme */
#define THERMOSTAT_CLIENT_ID 1
#define FAN_CLIENT_ID 2
struct temp_range {
u8 high; /* start the fan */
u8 low; /* stop the fan */
};
struct apple_thermal_info {
u8 id; /* implementation ID */
u8 fan_count; /* number of fans */
u8 thermostat_count; /* number of thermostats */
u8 unused[5];
struct temp_range ranges[4]; /* temperature ranges (may be [])*/
};
static int do_detect( struct i2c_adapter *adapter, int addr, int kind);
static struct {
struct i2c_client *thermostat;
struct i2c_client *fan;
int error;
struct timer_list timer;
int overheat_temp; /* 100% fan at this temp */
int overheat_hyst;
int temp;
int casetemp;
int fan_level; /* active fan_table setting */
int downind;
int upind;
int r0, r1, r20, r23, r25; /* saved register */
} x;
static struct {
int temp;
int fan_setting;
} fan_up_table[] = {
{ 0x0000, 11 }, /* min fan */
{ 0x3900, 8 }, /* 57.0 C */
{ 0x3a4a, 7 }, /* 58.3 C */
{ 0x3ad3, 6 }, /* 58.8 C */
{ 0x3b3c, 5 }, /* 59.2 C */
{ 0x3b94, 4 }, /* 59.6 C */
{ 0x3be3, 3 }, /* 58.9 C */
{ 0x3c29, 2 }, /* 59.2 C */
{ 0xffff, 1 } /* on fire */
};
static struct {
int temp;
int fan_setting;
} fan_down_table[] = {
{ 0x3700, 11 }, /* 55.0 C */
{ 0x374a, 6 },
{ 0x3800, 7 }, /* 56.0 C */
{ 0x3900, 8 }, /* 57.0 C */
{ 0x3a4a, 7 }, /* 58.3 C */
{ 0x3ad3, 6 }, /* 58.8 C */
{ 0x3b3c, 5 }, /* 59.2 C */
{ 0x3b94, 4 }, /* 58.9 C */
{ 0x3be3, 3 }, /* 58.9 C */
{ 0x3c29, 2 }, /* 59.2 C */
{ 0xffff, 1 }
};
static int
write_reg( struct i2c_client *cl, int reg, int data, int len )
{
u8 tmp[3];
if( len < 1 || len > 2 || data < 0 )
return -EINVAL;
tmp[0] = reg;
tmp[1] = (len == 1) ? data : (data >> 8);
tmp[2] = data;
len++;
if( i2c_master_send(cl, tmp, len) != len )
return -ENODEV;
return 0;
}
static int
read_reg( struct i2c_client *cl, int reg, int len )
{
u8 buf[2];
if( len != 1 && len != 2 )
return -EINVAL;
buf[0] = reg;
if( i2c_master_send(cl, buf, 1) != 1 )
return -ENODEV;
if( i2c_master_recv(cl, buf, len) != len )
return -ENODEV;
return (len == 2)? ((unsigned int)buf[0] << 8) | buf[1] : buf[0];
}
static void
print_temp( const char *s, int temp )
{
printk("%s%d.%d C", s ? s : "", temp>>8, (temp & 255)*10/256 );
}
static void
tune_fan( int fan_setting )
{
int val = (fan_setting << 3) | 7;
x.fan_level = fan_setting;
//write_reg( x.fan, 0x24, val, 1 );
write_reg( x.fan, 0x25, val, 1 );
write_reg( x.fan, 0x20, 0, 1 );
print_temp("CPU-temp: ", x.temp );
if( x.casetemp )
print_temp(", Case: ", x.casetemp );
printk(" Tuning fan: %d (%02x)\n", fan_setting, val );
}
static void
poll_temp( void *param )
{
int temp = read_reg( x.thermostat, 0, 2 );
int i, level, casetemp;
/* this actually occurs when the computer is loaded */
if( temp < 0 )
goto out;
casetemp = read_reg(x.fan, 0x0b, 1) << 8;
casetemp |= (read_reg(x.fan, 0x06, 1) & 0x7) << 5;
if( LOG_TEMP && x.temp != temp ) {
print_temp("CPU-temp: ", temp );
print_temp(", Case: ", casetemp );
printk(", Fan: %d\n", x.fan_level );
}
x.temp = temp;
x.casetemp = casetemp;
level = -1;
for( i=0; (temp & 0xffff) > fan_down_table[i].temp ; i++ )
;
if( i < x.downind )
level = fan_down_table[i].fan_setting;
x.downind = i;
for( i=0; (temp & 0xfffe) >= fan_up_table[i+1].temp ; i++ )
;
if( x.upind < i )
level = fan_up_table[i].fan_setting;
x.upind = i;
if( level >= 0 )
tune_fan( level );
out:
x.timer.expires = jiffies + 8*HZ;
add_timer( &x.timer );
}
static void
schedule_poll( unsigned long t )
{
schedule_work(&poll_work);
}
/************************************************************************/
/* i2c probing and setup */
/************************************************************************/
static int
do_attach( struct i2c_adapter *adapter )
{
return i2c_probe( adapter, &addr_data, &do_detect );
}
static int
do_detach( struct i2c_client *client )
{
int err;
printk("do_detach: id %d\n", client->id );
if( (err=i2c_detach_client(client)) ) {
printk("failed to detach thermostat client\n");
return err;
}
kfree( client );
return 0;
}
static struct i2c_driver g4fan_driver = {
.name = "Apple G4 Thermostat/Fan",
.id = I2C_DRIVERID_G4FAN,
.flags = I2C_DF_NOTIFY,
.attach_adapter = &do_attach,
.detach_client = &do_detach,
.command = NULL,
};
static int
detect_fan( struct i2c_client *cl )
{
/* check that this is an ADM1030 */
if( read_reg(cl, 0x3d, 1) != 0x30 || read_reg(cl, 0x3e, 1) != 0x41 )
goto out;
printk("ADM1030 fan controller detected at %02x\n", cl->addr );
if( x.fan ) {
x.error |= 2;
goto out;
}
x.fan = cl;
cl->id = FAN_CLIENT_ID;
strncpy( cl->name, "ADM1030 fan controller", sizeof(cl->name) );
if( i2c_attach_client( cl ) )
goto out;
return 0;
out:
if( cl != x.fan )
kfree( cl );
return 0;
}
static int
detect_thermostat( struct i2c_client *cl )
{
int hyst_temp, os_temp, temp;
if( (temp=read_reg(cl, 0, 2)) < 0 )
goto out;
/* temperature sanity check */
if( temp < 0x1600 || temp > 0x3c00 )
goto out;
hyst_temp = read_reg(cl, 2, 2);
os_temp = read_reg(cl, 3, 2);
if( hyst_temp < 0 || os_temp < 0 )
goto out;
printk("DS1775 digital thermometer detected at %02x\n", cl->addr );
print_temp("Temp: ", temp );
print_temp(" Hyst: ", hyst_temp );
print_temp(" OS: ", os_temp );
printk("\n");
if( x.thermostat ) {
x.error |= 1;
goto out;
}
x.temp = temp;
x.thermostat = cl;
x.overheat_temp = os_temp;
x.overheat_hyst = hyst_temp;
cl->id = THERMOSTAT_CLIENT_ID;
strncpy( cl->name, "DS1775 thermostat", sizeof(cl->name) );
if( i2c_attach_client( cl ) )
goto out;
return 0;
out:
kfree( cl );
return 0;
}
static int
do_detect( struct i2c_adapter *adapter, int addr, int kind )
{
struct i2c_client *cl;
if( strncmp(adapter->name, "uni-n", 5) )
return 0;
if( !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA
| I2C_FUNC_SMBUS_WRITE_BYTE) )
return 0;
if( !(cl=kmalloc( sizeof(struct i2c_client), GFP_KERNEL )) )
return -ENOMEM;
memset( cl, 0, sizeof(struct i2c_client) );
cl->addr = addr;
cl->adapter = adapter;
cl->driver = &g4fan_driver;
cl->flags = 0;
if( addr < 0x48 )
return detect_fan( cl );
return detect_thermostat( cl );
}
#define PRINT_REG( r ) printk("reg %02x = %02x\n", r, read_reg(x.fan, r, 1) )
static int __init
g4fan_init( void )
{
struct apple_thermal_info *info;
struct device_node *np;
int ret, val;
np = of_find_node_by_name(NULL, "power-mgt");
if (np == NULL)
return -ENODEV;
info = (struct apple_thermal_info*)get_property(np, "thermal-info", NULL);
of_node_put(np);
if (info == NULL)
return -ENODEV;
/* check for G4 "Windtunnel" SMP */
if( machine_is_compatible("PowerMac3,6") ) {
if( info->id != 3 ) {
printk(KERN_ERR "g4fan: design id %d unknown\n", info->id);
return -ENODEV;
}
} else {
printk(KERN_ERR "g4fan: unsupported machine type\n");
return -ENODEV;
}
if( (ret=i2c_add_driver(&g4fan_driver)) )
return ret;
if( !x.thermostat || !x.fan ) {
i2c_del_driver(&g4fan_driver );
return -ENODEV;
}
/* save registers (if we unload the module) */
x.r0 = read_reg( x.fan, 0x00, 1 );
x.r1 = read_reg( x.fan, 0x01, 1 );
x.r20 = read_reg( x.fan, 0x20, 1 );
x.r23 = read_reg( x.fan, 0x23, 1 );
x.r25 = read_reg( x.fan, 0x25, 1 );
/* improve measurement resolution (convergence time 1.5s) */
if( (val=read_reg( x.thermostat, 1, 1 )) >= 0 ) {
val |= 0x60;
if( write_reg( x.thermostat, 1, val, 1 ) )
printk("Failed writing config register\n");
}
/* disable interrupts and TAC input */
write_reg( x.fan, 0x01, 0x01, 1 );
/* enable filter */
write_reg( x.fan, 0x23, 0x91, 1 );
/* remote temp. controls fan */
write_reg( x.fan, 0x00, 0x95, 1 );
/* The thermostat (which besides measureing temperature controls
* has a THERM output which puts the fan on 100%) is usually
* set to kick in at 80 C (chip default). We reduce this a bit
* to be on the safe side (OSX doesn't)...
*/
if( x.overheat_temp == (80 << 8) ) {
x.overheat_temp = 65 << 8;
x.overheat_hyst = 60 << 8;
write_reg( x.thermostat, 2, x.overheat_hyst, 2 );
write_reg( x.thermostat, 3, x.overheat_temp, 2 );
print_temp("Reducing overheating limit to ", x.overheat_temp );
print_temp(" (Hyst: ", x.overheat_hyst );
printk(")\n");
}
/* set an initial fan setting */
x.upind = x.downind = 1;
tune_fan( fan_up_table[x.upind].fan_setting );
INIT_WORK(&poll_work, poll_temp, NULL);
init_timer( &x.timer );
x.timer.expires = jiffies + 8*HZ;
x.timer.function = schedule_poll;
add_timer( &x.timer );
return 0;
}
static void __exit
g4fan_exit( void )
{
del_timer( &x.timer );
write_reg( x.fan, 0x01, x.r1, 1 );
write_reg( x.fan, 0x20, x.r20, 1 );
write_reg( x.fan, 0x23, x.r23, 1 );
write_reg( x.fan, 0x25, x.r25, 1 );
write_reg( x.fan, 0x00, x.r0, 1 );
i2c_del_driver( &g4fan_driver );
}
module_init(g4fan_init);
module_exit(g4fan_exit);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment