linuxppc-dev.lists.ozlabs.org archive mirror
 help / color / mirror / Atom feed
* Kernel module for iBook G4's fan controller
@ 2003-12-20 13:29 Colin Leroy
  2003-12-20 14:16 ` Colin Leroy
  0 siblings, 1 reply; 3+ messages in thread
From: Colin Leroy @ 2003-12-20 13:29 UTC (permalink / raw)
  To: linuxppc-dev list; +Cc: benh

[-- Attachment #1: Type: text/plain, Size: 334 bytes --]

Hi,

here's a module to allow fine control of the iBook G4's fan controller. The defaults temperature limits are pretty high.

it's also available at http://geekounet.org/powerbook/patches/files/therm_adt7467.c

It's intended to go with the other therm_* modules in drivers/macintosh/ and is for kernel 2.4.

Hope it's fine,
--
Colin

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: therm_adt7467.c --]
[-- Type: text/x-csrc; name="therm_adt7467.c", Size: 9567 bytes --]

/*
 * Device driver for the i2c thermostat found on the iBook G4
 *
 * Copyright (C) 2003 Colin Leroy, Benjamin Herrenschmidt
 *
 * Documentation from
 * http://www.analog.com/UploadedFiles/Data_Sheets/115254175ADT7467_pra.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>

#undef DEBUG

#define TEMP_LOCAL   0x26
#define TEMP_REMOTE1 0x25
#define TEMP_REMOTE2 0x27
#define LIM_LOCAL    0x6a
#define LIM_REMOTE1  0x6b
#define LIM_REMOTE2  0x6c
#define FAN0_SPEED   0x28
#define HYST0        0x6d
#define HYST1        0x6e

#define MANUAL_MODE  0x5c
#define MANUAL_MASK  0xe0
#define AUTO_MASK    0x20

static int limit_decrease = 15;
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_decrease,"i");
MODULE_PARM_DESC(limit_decrease,"Decrease standard maximum temperatures by N °C, default is 15");
MODULE_PARM(fan_speed,"i");
MODULE_PARM_DESC(fan_speed,"Specify fan speed (0-255) when lim < temp < lim+8 (dangerous !), default is automatic");


EXPORT_NO_SYMBOLS;

struct thermostat {
	struct i2c_client	clt;
	u8			cached_temp[3];
	u8			initial_limits[3];
	u8			limits[3];
	int			last_speed;
	int			overriding;
};

static int therm_bus, therm_address;
static struct thermostat* thermostat;
static pid_t monitor_thread_id;
static int monitor_running;
static DECLARE_COMPLETION(monitor_dead);

static int attach_one_thermostat(struct i2c_adapter *adapter, int addr, int busno);
static void write_fan_speed(struct thermostat *th, int speed);

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 0;
	bus_no = simple_strtoul(adapter->name + 6, NULL, 10);
	if (bus_no != therm_bus)
		return 0;	
	return attach_one_thermostat(adapter, therm_address, bus_no);
}

static int
detach_thermostat(struct i2c_client *client)
{
	struct thermostat* th;
	
	if (thermostat == NULL)
		return 0;

	th = thermostat;

	printk(KERN_INFO "adt7467: 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]);
	write_reg(th, LIM_LOCAL,   th->initial_limits[0]);
	write_reg(th, LIM_REMOTE1, th->initial_limits[1]);
	write_reg(th, LIM_REMOTE2, th->initial_limits[2]);
	write_fan_speed(th, -1);

	thermostat = NULL;

	if (monitor_running) {
		(void)kill_proc(monitor_thread_id, SIGTERM, 1);
		wait_for_completion(&monitor_dead);
	}
		
	i2c_detach_client(&th->clt);
	kfree(th);

	return 0;
}


/* What is this supposed to be ? registered ? I hate
 * magic numbers like that ...
 */
#define I2C_DRIVERID_THERMOSTAT (0xDEAD)

static struct i2c_driver thermostat_driver = {  
	name:		"Apple Thermostat",
	id:		I2C_DRIVERID_THERMOSTAT,
	flags:		I2C_DF_NOTIFY,
	attach_adapter:	&attach_thermostat,
	detach_client:	&detach_thermostat,
	command:	NULL,
	inc_use:	NULL,
	dec_use:	NULL 
};

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_fan_speed(struct thermostat *th, int speed)
{
	u8 manual;
	
	if (speed > 0xff) 
		speed = 0xff;
	else if (speed < -1) 
		speed = 0;
	
	if (speed >= 0) {
		manual = read_reg(th, MANUAL_MODE);
		write_reg(th, 0x5c, manual|MANUAL_MASK);
		if (th->last_speed != speed)
			printk(KERN_INFO "adt7467: Setting speed to: %d\n", speed);
		th->last_speed = speed;
		write_reg(th, 0x30, speed);
	} else {
		/* back to automatic */
		manual = read_reg(th, MANUAL_MODE);
		if (th->last_speed != -1)
			printk(KERN_INFO "adt7467: Setting speed to: automatic\n");
		th->last_speed = -1;
		write_reg(th, 0x5c, manual&(~AUTO_MASK));
	}
}

static int monitor_task(void *arg)
{
	struct thermostat* th = arg;
	u8 temps[3];
	u8 lims[3];
	u8 hyst[2];
#ifdef DEBUG
	int mfan_speed;
#endif
	
	lock_kernel();
	daemonize();
	unlock_kernel();
	strcpy(current->comm, "thermostat");
	monitor_running = 1;

	while(1)
	{
		set_task_state(current, TASK_INTERRUPTIBLE);
		schedule_timeout(1*HZ);
		if (signal_pending(current))
			break;

		/* Check status */
		/* remote 1: CPU ?*/
		/* remote 2: GPU ?*/
		temps[0]  = read_reg(th, TEMP_LOCAL);
		temps[1]  = read_reg(th, TEMP_REMOTE1);
		temps[2]  = read_reg(th, TEMP_REMOTE2);
		lims[0]   = read_reg(th, LIM_LOCAL);
		lims[1]   = read_reg(th, LIM_REMOTE1);
		lims[2]   = read_reg(th, LIM_REMOTE2);
		hyst[0]   = read_reg(th, HYST0);
		hyst[1]   = read_reg(th, HYST1);
		
		hyst[0] = (hyst[0] & 0xf0) >> 4;
		hyst[1] = (hyst[1] & 0xf0) >> 4;
		
		if (fan_speed != -1) {
			if (temps[0] > lims[0]
			||  temps[1] > lims[1]
			||  temps[2] > lims[2]) {
				int var = 0;
				var = (temps[0] - lims[0] > var) ? temps[0] - lims[0] : var;
				var = (temps[1] - lims[1] > var) ? temps[1] - lims[1] : var;
				var = (temps[2] - lims[2] > var) ? temps[2] - lims[2] : var;
				if (var > 8) {
					if (th->overriding == 0)
						printk(KERN_INFO "adt7467: Limit exceeded by %d°C, overriding specified fan speed.\n",
							var);
					th->overriding = 1;
					write_fan_speed(th, -1);
				} else {
					if (th->overriding == 1)
						printk(KERN_INFO "adt7467: Limit excess is over, setting speed to specified.\n");					th->overriding = 0;
					write_fan_speed(th, fan_speed);
				}
			} else {
				write_fan_speed(th, 0);
			}
		}
#ifdef DEBUG
		mfan_speed = read_fan_speed(th, FAN0_SPEED);
		/* 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 "adt7467: Temperature infos:"
					 " thermostats: %d,%d,%d °C;"
					 " limits: %d,%d,%d °C;"
					 " fan speed: %d RPM;"
					 " hysteresis: %d,%d\n",
				temps[0], temps[1], temps[2],
				lims[0],  lims[1],  lims[2],
				mfan_speed,hyst[0],  hyst[1]);
		}
#endif		
		th->cached_temp[0] = temps[0];
		th->cached_temp[1] = temps[1];
		th->cached_temp[2] = temps[2];
	}
	monitor_running = 0;
	complete_and_exit(&monitor_dead, 0);
	return 0;
}

static int
attach_one_thermostat(struct i2c_adapter *adapter, int addr, int busno)
{
	struct thermostat* th;
	int rc;

	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.flags = 0;
	th->clt.data = NULL;
	strcpy(th->clt.name, "thermostat");

	rc = read_reg(th, 0);
	if (rc < 0) {
		printk(KERN_ERR "adt7467: Thermostat failed to read config from bus %d !\n",
			busno);
		kfree(th);
		return -ENODEV;
	}
	printk(KERN_INFO "adt7467: ADT7467 initializing\n");

	th->initial_limits[0] = read_reg(th, LIM_LOCAL);
	th->initial_limits[1] = read_reg(th, LIM_REMOTE1);
	th->initial_limits[2] = read_reg(th, LIM_REMOTE2);
	th->limits[0] = th->initial_limits[0] - limit_decrease;
	th->limits[1] = th->initial_limits[1] - limit_decrease;
	th->limits[2] = th->initial_limits[2] - limit_decrease;
	
	printk(KERN_INFO "adt7467: 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]);
	write_reg(th, LIM_LOCAL,   th->limits[0]);
	write_reg(th, LIM_REMOTE1, th->limits[1]);
	write_reg(th, LIM_REMOTE2, th->limits[2]);

	thermostat = th;

	if (i2c_attach_client(&th->clt)) {
		printk("adt7467: Thermostat failed to attach client !\n");
		thermostat = NULL;
		kfree(th);
		return -ENODEV;
	}

	if (fan_speed != -1) {
		write_fan_speed(th, 0);
	} else {
		write_fan_speed(th, -1);
	}
	
	monitor_thread_id = kernel_thread(monitor_task, th,
		SIGCHLD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND);

	return 0;
}

static int __init
thermostat_init(void)
{
	struct device_node* np;
	u32 *prop;
	
	np = find_devices("fan");
	if (!np)
		return -ENODEV;
	if (!device_is_compatible(np, "adt7467"))
		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 "adt7467: Thermostat bus: %d, address: 0x%02x, limit_decrease: %d, fan_speed: %d\n",
		therm_bus, therm_address, limit_decrease, fan_speed);

	return i2c_add_driver(&thermostat_driver);
}

static void __exit
thermostat_exit(void)
{
	i2c_del_driver(&thermostat_driver);
}

module_init(thermostat_init);
module_exit(thermostat_exit);

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: Kernel module for iBook G4's fan controller
  2003-12-20 13:29 Kernel module for iBook G4's fan controller Colin Leroy
@ 2003-12-20 14:16 ` Colin Leroy
  2003-12-20 23:25   ` Benjamin Herrenschmidt
  0 siblings, 1 reply; 3+ messages in thread
From: Colin Leroy @ 2003-12-20 14:16 UTC (permalink / raw)
  To: linuxppc-dev list; +Cc: benh

[-- Attachment #1: Type: text/plain, Size: 193 bytes --]

On 20 Dec 2003 at 14h12, Colin Leroy wrote:

Hi,

> here's a module to allow fine control of the iBook G4's fan controller.

This one's better (hysteresis when forcing fan speed too).
--
Colin

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: therm_adt7467.c --]
[-- Type: text/x-csrc; name="therm_adt7467.c", Size: 9870 bytes --]

/*
 * Device driver for the i2c thermostat found on the iBook G4
 *
 * Copyright (C) 2003 Colin Leroy, Benjamin Herrenschmidt
 *
 * Documentation from
 * http://www.analog.com/UploadedFiles/Data_Sheets/115254175ADT7467_pra.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>

#undef DEBUG

#define TEMP_LOCAL   0x26
#define TEMP_REMOTE1 0x25
#define TEMP_REMOTE2 0x27
#define LIM_LOCAL    0x6a
#define LIM_REMOTE1  0x6b
#define LIM_REMOTE2  0x6c
#define FAN0_SPEED   0x28
#define HYST0        0x6d
#define HYST1        0x6e

#define MANUAL_MODE  0x5c
#define MANUAL_MASK  0xe0
#define AUTO_MASK    0x20
#define FAN_SPD_SET  0x30

static int limit_decrease = 15;
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_decrease,"i");
MODULE_PARM_DESC(limit_decrease,"Decrease standard maximum temperatures by N °C, default is 15");
MODULE_PARM(fan_speed,"i");
MODULE_PARM_DESC(fan_speed,"Specify fan speed (0-255) when lim < temp < lim+8 (dangerous !), default is automatic");


EXPORT_NO_SYMBOLS;

struct thermostat {
	struct i2c_client	clt;
	u8			cached_temp[3];
	u8			initial_limits[3];
	u8			limits[3];
	int			last_speed;
	int			overriding;
};

static int therm_bus, therm_address;
static struct thermostat* thermostat;
static pid_t monitor_thread_id;
static int monitor_running;
static DECLARE_COMPLETION(monitor_dead);

static int attach_one_thermostat(struct i2c_adapter *adapter, int addr, int busno);
static void write_fan_speed(struct thermostat *th, int speed);

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 0;
	bus_no = simple_strtoul(adapter->name + 6, NULL, 10);
	if (bus_no != therm_bus)
		return 0;	
	return attach_one_thermostat(adapter, therm_address, bus_no);
}

static int
detach_thermostat(struct i2c_client *client)
{
	struct thermostat* th;
	
	if (thermostat == NULL)
		return 0;

	th = thermostat;

	printk(KERN_INFO "adt7467: 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]);
	write_reg(th, LIM_LOCAL,   th->initial_limits[0]);
	write_reg(th, LIM_REMOTE1, th->initial_limits[1]);
	write_reg(th, LIM_REMOTE2, th->initial_limits[2]);
	write_fan_speed(th, -1);

	thermostat = NULL;

	if (monitor_running) {
		(void)kill_proc(monitor_thread_id, SIGTERM, 1);
		wait_for_completion(&monitor_dead);
	}
		
	i2c_detach_client(&th->clt);
	kfree(th);

	return 0;
}


/* What is this supposed to be ? registered ? I hate
 * magic numbers like that ...
 */
#define I2C_DRIVERID_THERMOSTAT (0xDEAD)

static struct i2c_driver thermostat_driver = {  
	name:		"Apple Thermostat",
	id:		I2C_DRIVERID_THERMOSTAT,
	flags:		I2C_DF_NOTIFY,
	attach_adapter:	&attach_thermostat,
	detach_client:	&detach_thermostat,
	command:	NULL,
	inc_use:	NULL,
	dec_use:	NULL 
};

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_fan_speed(struct thermostat *th, int speed)
{
	u8 manual;
	
	if (speed > 0xff) 
		speed = 0xff;
	else if (speed < -1) 
		speed = 0;
	
	if (speed >= 0) {
		manual = read_reg(th, MANUAL_MODE);
		write_reg(th, MANUAL_MODE, manual|MANUAL_MASK);
		if (th->last_speed != speed)
			printk(KERN_INFO "adt7467: Setting speed to: %d\n", speed);
		th->last_speed = speed;
		write_reg(th, FAN_SPD_SET, speed);
	} else {
		/* back to automatic */
		manual = read_reg(th, MANUAL_MODE);
		if (th->last_speed != -1)
			printk(KERN_INFO "adt7467: Setting speed to: automatic\n");
		th->last_speed = -1;
		write_reg(th, MANUAL_MODE, manual&(~AUTO_MASK));
	}
}

static int monitor_task(void *arg)
{
	struct thermostat* th = arg;
	u8 temps[3];
	u8 lims[3];
	u8 hyst[2];
#ifdef DEBUG
	int mfan_speed;
#endif
	
	lock_kernel();
	daemonize();
	unlock_kernel();
	strcpy(current->comm, "thermostat");
	monitor_running = 1;

	while(1)
	{
		set_task_state(current, TASK_INTERRUPTIBLE);
		schedule_timeout(1*HZ);
		if (signal_pending(current))
			break;

		/* Check status */
		/* remote 1: CPU ?*/
		/* remote 2: GPU ?*/
		temps[0]  = read_reg(th, TEMP_LOCAL);
		temps[1]  = read_reg(th, TEMP_REMOTE1);
		temps[2]  = read_reg(th, TEMP_REMOTE2);
		lims[0]   = read_reg(th, LIM_LOCAL);
		lims[1]   = read_reg(th, LIM_REMOTE1);
		lims[2]   = read_reg(th, LIM_REMOTE2);
		hyst[0]   = read_reg(th, HYST0);
		hyst[1]   = read_reg(th, HYST1);
		
		hyst[0] = (hyst[0] & 0xf0) >> 4;
		hyst[1] = (hyst[1] & 0xf0) >> 4;
		
		if (fan_speed != -1) {
			if (temps[0] > lims[0]
			||  temps[1] > lims[1]
			||  temps[2] > lims[2]) {
				int var = 0;
				var = (temps[0] - lims[0] > var) ? temps[0] - lims[0] : var;
				var = (temps[1] - lims[1] > var) ? temps[1] - lims[1] : var;
				var = (temps[2] - lims[2] > var) ? temps[2] - lims[2] : var;
				if (var > 8) {
					if (th->overriding == 0)
						printk(KERN_INFO "adt7467: Limit exceeded by %d°C, overriding specified fan speed.\n",
							var);
					th->overriding = 1;
					write_fan_speed(th, -1);
				} else {
					if (th->overriding == 1)
						printk(KERN_INFO "adt7467: Limit excess is over, setting speed to specified.\n");					th->overriding = 0;
					write_fan_speed(th, fan_speed);
				}
			} else {
				int var = 10;
				var = (lims[0] - temps[0] < var) ? lims[0] - temps[0] : var;
				var = (lims[1] - temps[1] < var) ? lims[1] - temps[1] : var;
				var = (lims[2] - temps[2] < var) ? lims[2] - temps[2] : var;
				if (var >= 2) /* pseudo hysteresis */
					write_fan_speed(th, 0);
			}
		}
#ifdef DEBUG
		mfan_speed = read_fan_speed(th, FAN0_SPEED);
		/* 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 "adt7467: Temperature infos:"
					 " thermostats: %d,%d,%d °C;"
					 " limits: %d,%d,%d °C;"
					 " fan speed: %d RPM;"
					 " hysteresis: %d,%d\n",
				temps[0], temps[1], temps[2],
				lims[0],  lims[1],  lims[2],
				mfan_speed,hyst[0],  hyst[1]);
		}
#endif		
		th->cached_temp[0] = temps[0];
		th->cached_temp[1] = temps[1];
		th->cached_temp[2] = temps[2];
	}
	monitor_running = 0;
	complete_and_exit(&monitor_dead, 0);
	return 0;
}

static int
attach_one_thermostat(struct i2c_adapter *adapter, int addr, int busno)
{
	struct thermostat* th;
	int rc;

	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.flags = 0;
	th->clt.data = NULL;
	strcpy(th->clt.name, "thermostat");

	rc = read_reg(th, 0);
	if (rc < 0) {
		printk(KERN_ERR "adt7467: Thermostat failed to read config from bus %d !\n",
			busno);
		kfree(th);
		return -ENODEV;
	}
	printk(KERN_INFO "adt7467: ADT7467 initializing\n");

	th->initial_limits[0] = read_reg(th, LIM_LOCAL);
	th->initial_limits[1] = read_reg(th, LIM_REMOTE1);
	th->initial_limits[2] = read_reg(th, LIM_REMOTE2);
	th->limits[0] = th->initial_limits[0] - limit_decrease;
	th->limits[1] = th->initial_limits[1] - limit_decrease;
	th->limits[2] = th->initial_limits[2] - limit_decrease;
	
	printk(KERN_INFO "adt7467: 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]);
	write_reg(th, LIM_LOCAL,   th->limits[0]);
	write_reg(th, LIM_REMOTE1, th->limits[1]);
	write_reg(th, LIM_REMOTE2, th->limits[2]);

	thermostat = th;

	if (i2c_attach_client(&th->clt)) {
		printk("adt7467: Thermostat failed to attach client !\n");
		thermostat = NULL;
		kfree(th);
		return -ENODEV;
	}

	if (fan_speed != -1) {
		write_fan_speed(th, 0);
	} else {
		write_fan_speed(th, -1);
	}
	
	monitor_thread_id = kernel_thread(monitor_task, th,
		SIGCHLD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND);

	return 0;
}

static int __init
thermostat_init(void)
{
	struct device_node* np;
	u32 *prop;
	
	np = find_devices("fan");
	if (!np)
		return -ENODEV;
	if (!device_is_compatible(np, "adt7467"))
		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 "adt7467: Thermostat bus: %d, address: 0x%02x, limit_decrease: %d, fan_speed: %d\n",
		therm_bus, therm_address, limit_decrease, fan_speed);

	return i2c_add_driver(&thermostat_driver);
}

static void __exit
thermostat_exit(void)
{
	i2c_del_driver(&thermostat_driver);
}

module_init(thermostat_init);
module_exit(thermostat_exit);

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: Kernel module for iBook G4's fan controller
  2003-12-20 14:16 ` Colin Leroy
@ 2003-12-20 23:25   ` Benjamin Herrenschmidt
  0 siblings, 0 replies; 3+ messages in thread
From: Benjamin Herrenschmidt @ 2003-12-20 23:25 UTC (permalink / raw)
  To: Colin Leroy; +Cc: linuxppc-dev list


On Sun, 2003-12-21 at 01:16, Colin Leroy wrote:
> On 20 Dec 2003 at 14h12, Colin Leroy wrote:
>
> Hi,
>
> > here's a module to allow fine control of the iBook G4's fan controller.
>
> This one's better (hysteresis when forcing fan speed too).

Great !

I'll look into this tonight or tomorrow.

Ben.


** Sent via the linuxppc-dev mail list. See http://lists.linuxppc.org/

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2003-12-20 23:25 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2003-12-20 13:29 Kernel module for iBook G4's fan controller Colin Leroy
2003-12-20 14:16 ` Colin Leroy
2003-12-20 23:25   ` Benjamin Herrenschmidt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).