ADM1030 Driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi everybody,

Here is the first version of the adm1030 sensor driver I've made. It is
tested on my MSI MegaPC 651 which is SiS chipset based.

The driver is written for the Linux 2.6.4 kernel, if you want to compile
it just add 
obj-m += adm1030.o 
to the drivers/i2c/chips/Makefile. It's ugly, but if you do not see a
lot of issues in the code, I'll prepare a patch for the latest 2.6.5-mm
kernel.

What the driver can do :
	- Read system temperature ( temp_local_in )
	- Read CPU Temperature ( temp_remote_in )
	- Set minimum CPU fan speed ( fan_rpm )
	- Read CPU fan speed (pwm_input)

What is it supposed to do but not verified correctly:
	- temp_local_min, temp_local_range are the values to start the fan and
the range to let the fan active. 
	- temp_remote_min, temp_remote_range are the values to start the fan
and the range to let the fan active. 

Please give me your feedbacks,

Thanks,

Alex.

The code is following inlined :

++++++++++++++++++++++++++ adm1030.c +++++++++++++++++++++++++++++++++++
/*
    adm1030.c - Part of lm_sensors, Linux kernel modules for hardware
             monitoring
    Based on lm75.c
    Copyright (c) 1998, 1999  Frodo Looijaard <frodol at dds.nl>
    Copyright (c) 2004 Alexandre d'Alton <alex at alexdalton.org>

    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; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-sensor.h>
#include "adm1030.h"


/* Addresses to scan */
static unsigned short normal_i2c[] = { I2C_CLIENT_END };
static unsigned short normal_i2c_range[] = { 0x2e, I2C_CLIENT_END };
static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
static unsigned int normal_isa_range[] = { I2C_CLIENT_ISA_END };

/* Insmod parameters */
SENSORS_INSMOD_1(adm1030);


/* Each client has this additional data */
struct adm1030_data {
  struct semaphore	update_lock;
  char			valid;		/* !=0 if following fields are valid */
  unsigned long		last_updated;	/* In jiffies */
  u16			temp_local_in;	/* Register values */
  u16			temp_remote_in;
  u16			fan_rpm;
  u16		        conf1;
  u16			conf2;
  u16			ftac;
  u16			pwm;
  u16                   temp_local_min;
  u16                   temp_local_range;
  u16                   temp_remote_min;
  u16                   temp_remote_range;
};

static int adm1030_attach_adapter(struct i2c_adapter *adapter);
static int adm1030_detect(struct i2c_adapter *adapter, int address, int
kind);
static void adm1030_init_client(struct i2c_client *client);
static int adm1030_detach_client(struct i2c_client *client);
static int adm1030_read_value(struct i2c_client *client, u8 reg);
static int adm1030_write_value(struct i2c_client *client, u8 reg, u16
value);
static struct adm1030_data *adm1030_update_device(struct device *dev);


/* This is the driver that will be inserted */
static struct i2c_driver adm1030_driver = {
	.owner		= THIS_MODULE,
	.name		= "adm1030",
	.id		= I2C_DRIVERID_ADM1030,
	.flags		= I2C_DF_NOTIFY,
	.attach_adapter	= adm1030_attach_adapter,
	.detach_client	= adm1030_detach_client,
};

static int adm1030_id = 0;

#define show(value)	\
static ssize_t show_##value(struct device *dev, char *buf)		\
{									\
	struct adm1030_data *data = adm1030_update_device(dev);		\
	return sprintf(buf, "%d\n", data->value);	\
}
show(temp_local_in);
show(temp_remote_in);
show(conf1);
show(conf2);
show(ftac);
show(pwm);
show(temp_local_min);
show(temp_local_range);
show(temp_remote_min);
show(temp_remote_range);

static ssize_t show_fan_rpm(struct device *dev, char *buf)		
{									
  int fan_speed;
  struct adm1030_data *data = adm1030_update_device(dev);		
  fan_speed = (11250 * 60) / (data->fan_rpm * 8);
  return sprintf(buf, "%d\n", fan_speed);	
}

#define set(value, reg)	\
static ssize_t set_##value(struct device *dev, const char *buf, size_t
count)	\
{								\
	struct i2c_client *client = to_i2c_client(dev);		\
	struct adm1030_data *data = i2c_get_clientdata(client);	\
	int temp = simple_strtoul(buf, NULL, 10);		\
	data->value = temp;			\
	adm1030_write_value(client, reg, data->value);		\
	return count;						\
}

set(conf1, ADM1030_REG_CONF1);
set(conf2, ADM1030_REG_CONF2);
set(ftac, ADM1030_REG_FTAC);
set(pwm, ADM1030_REG_FSP);

void set_temp_min(struct i2c_client *client, int value, int reg)
{
  int val = adm1030_read_value(client, reg);
  val = ( ( ( value >> 2 ) & 0x1f ) << 3 ) | ( val & 0x7 ); 
  adm1030_write_value(client, reg, val);
}
void set_temp_range(struct i2c_client *client, int value, int reg)
{
  int val = adm1030_read_value(client, reg);
  switch (value) {
  case 5: value = 0; break;
  case 10: value = 1; break;
  case 20: value = 2; break;
  case 40: value = 3; break;
  case 80: value = 4; break;
  default: return;
  }
  val = (val&0xf8) | (value & 0x7); 
  adm1030_write_value(client, reg, val);
}

static ssize_t set_temp_local_min(struct device *dev, const char *buf,
size_t
				  count) 
{
  struct i2c_client * client = to_i2c_client(dev);
  int temp = simple_strtoul(buf, NULL, 10);		
  set_temp_min(client, temp, ADM1030_REG_LTR);
  return count;
}
static ssize_t set_temp_local_range(struct device *dev, const char *buf,
size_t
				  count) 
{
  struct i2c_client * client = to_i2c_client(dev);
  int temp = simple_strtoul(buf, NULL, 10);		
  set_temp_range(client, temp, ADM1030_REG_LTR);
  return count;
  
}
static ssize_t set_temp_remote_min(struct device *dev, const char *buf,
size_t
				  count) 
{
  struct i2c_client * client = to_i2c_client(dev);
  int temp = simple_strtoul(buf, NULL, 10);		
  set_temp_min(client, temp, ADM1030_REG_RTR);
  return count;  
}

static ssize_t set_temp_remote_range(struct device *dev, const char
*buf, size_t
				  count) 
{
  struct i2c_client * client = to_i2c_client(dev);
  int temp = simple_strtoul(buf, NULL, 10);		
  set_temp_range(client, temp, ADM1030_REG_RTR);
  return count;
  
}

//static DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max,
set_temp_max);
//static DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, show_temp_hyst,
set_temp_hyst);
static DEVICE_ATTR(conf1, S_IWUSR | S_IRUGO, show_conf1, set_conf1);
static DEVICE_ATTR(conf2, S_IWUSR | S_IRUGO, show_conf2, set_conf2);
static DEVICE_ATTR(ftac, S_IWUSR | S_IRUGO, show_ftac, set_ftac);
static DEVICE_ATTR(pwm, S_IWUSR | S_IRUGO, show_pwm, set_pwm);
static DEVICE_ATTR(temp_local_in, S_IRUGO, show_temp_local_in, NULL);
static DEVICE_ATTR(temp_remote_in, S_IRUGO, show_temp_remote_in, NULL);
static DEVICE_ATTR(fan_rpm, S_IRUGO , show_fan_rpm, NULL);
static DEVICE_ATTR(temp_local_min, S_IRUGO | S_IWUSR, 
		   show_temp_local_min, set_temp_local_min);
static DEVICE_ATTR(temp_local_range, S_IRUGO | S_IWUSR, 
		   show_temp_local_range, set_temp_local_range);
static DEVICE_ATTR(temp_remote_min, S_IRUGO | S_IWUSR, 
		   show_temp_remote_min, set_temp_remote_min);
static DEVICE_ATTR(temp_remote_range, S_IRUGO | S_IWUSR, 
		   show_temp_remote_range, set_temp_remote_range);

static int adm1030_attach_adapter(struct i2c_adapter *adapter)
{
	if (!(adapter->class & I2C_ADAP_CLASS_SMBUS))
		return 0;
	return i2c_detect(adapter, &addr_data, adm1030_detect);
}

/* This function is called by i2c_detect */
static int adm1030_detect(struct i2c_adapter *adapter, int address, int
kind)
{
	int id, co;
	struct i2c_client *new_client;
	struct adm1030_data *data;
	int err = 0;
	const char *name = "";

	/* Make sure we aren't probing the ISA bus!! This is just a safety
check
	   at this moment; i2c_detect really won't call us. */
#ifdef DEBUG
	if (i2c_is_isa_adapter(adapter)) {
		dev_dbg(&adapter->dev,
			"adm1030_detect called for an ISA bus adapter?!?\n");
		goto exit;
	}
#endif

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
				     I2C_FUNC_SMBUS_WORD_DATA))
		goto exit;

	/* OK. For now, we presume we have a valid client. We now create the
	   client structure, even though we cannot fill it completely yet.
	   But it allows us to access lm75_{read,write}_value. */
	if (!(new_client = kmalloc(sizeof(struct i2c_client) +
				   sizeof(struct adm1030_data),
				   GFP_KERNEL))) {
		err = -ENOMEM;
		goto exit;
	}
	memset(new_client, 0x00, sizeof(struct i2c_client) +
				 sizeof(struct adm1030_data));

	data = (struct adm1030_data *) (new_client + 1);
	i2c_set_clientdata(new_client, data);
	new_client->addr = address;
	new_client->adapter = adapter;
	new_client->driver = &adm1030_driver;
	new_client->flags = 0;

	/* Now, we do the remaining detection. It is lousy. */
	if (kind < 0) {
		id = i2c_smbus_read_byte_data(new_client, 0x3d);
		co = i2c_smbus_read_byte_data(new_client, 0x3e);
		
		//		printk("read values@%x : %x %x\n", address, id, co);
		
		if((id != 0x30) && (co != 0x41))
		  goto exit_free;
	}

	/* Determine the chip type - only one kind supported! */
	if (kind <= 0)
		kind = adm1030;

	if (kind == adm1030) {
		name = "adm1030";
	}

	/* Fill in the remaining client fields and put it into the global list
*/
	strlcpy(new_client->name, name, I2C_NAME_SIZE);

	new_client->id = adm1030_id++;
	data->valid = 0;
	init_MUTEX(&data->update_lock);

	/* Tell the I2C layer a new client has arrived */
	if ((err = i2c_attach_client(new_client)))
		goto exit_free;

	/* Initialize the LM75 chip */
	adm1030_init_client(new_client);
	
	/* Register sysfs hooks */
	device_create_file(&new_client->dev, &dev_attr_temp_local_in);
	device_create_file(&new_client->dev, &dev_attr_temp_remote_in);
	device_create_file(&new_client->dev, &dev_attr_fan_rpm);

	/* Following entries are for debugging purpose only, commented
	 * out. These are access to HW configuration registers. 
	 */
	/*
	  device_create_file(&new_client->dev, &dev_attr_conf1);
	  device_create_file(&new_client->dev, &dev_attr_conf2);
	  device_create_file(&new_client->dev, &dev_attr_ftac);
	*/
	device_create_file(&new_client->dev, &dev_attr_pwm);

	device_create_file(&new_client->dev, &dev_attr_temp_local_min);
	device_create_file(&new_client->dev, &dev_attr_temp_local_range);
	device_create_file(&new_client->dev, &dev_attr_temp_remote_min);
	device_create_file(&new_client->dev, &dev_attr_temp_remote_range);

	return 0;

exit_free:
	kfree(new_client);
exit:
	return err;
}

static int adm1030_detach_client(struct i2c_client *client)
{
	i2c_detach_client(client);
	kfree(client);
	return 0;
}

static int adm1030_read_value(struct i2c_client *client, u8 reg)
{
  return i2c_smbus_read_byte_data(client, reg);
}

static int adm1030_write_value(struct i2c_client *client, u8 reg, u16
value)
{
  return i2c_smbus_write_byte_data(client, reg, value);
}

static void adm1030_init_client(struct i2c_client *client)
{
  int read_val;
	/* Initialize the ADM1030 chip (enables fan speed reading )*/
	adm1030_write_value(client, ADM1030_REG_CONF2, 0x3f);

	/* Increase the accuracy on rpm measurements */
	read_val = adm1030_read_value(client, ADM1030_REG_FCH);
	adm1030_write_value(client, ADM1030_REG_FCH, 0xc0 | read_val);

}

static struct adm1030_data *adm1030_update_device(struct device *dev)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct adm1030_data *data = i2c_get_clientdata(client);
	int temp_range;
	static unsigned short values[5] = {5, 10, 20, 40, 80};
	
	down(&data->update_lock);

	if ((jiffies - data->last_updated > HZ + HZ / 2) ||
	    (jiffies < data->last_updated) || !data->valid) {
		dev_dbg(&client->dev, "Starting adm1030 update\n");

		data->temp_local_in = adm1030_read_value(client,
ADM1030_REG_TEMP_INT);
		data->temp_remote_in = adm1030_read_value(client,
ADM1030_REG_TEMP_EXT);
		data->fan_rpm      = adm1030_read_value(client,
							  ADM1030_REG_FAN_SPEED);
		temp_range = adm1030_read_value(client,	ADM1030_REG_LTR);
		data->temp_local_min = ((temp_range >> 3) & 0x1f) * 4;
		data->temp_local_range = (values[temp_range&0x7]);

		temp_range = adm1030_read_value(client,	ADM1030_REG_RTR);
		data->temp_remote_min = ((temp_range >> 3) & 0x1f) * 4;
		data->temp_remote_range = (values[temp_range&0x7]);

		data->conf1 = adm1030_read_value(client, ADM1030_REG_CONF1);
		data->conf2 = adm1030_read_value(client, ADM1030_REG_CONF2);
		data->ftac      = adm1030_read_value(client, ADM1030_REG_FTAC);
		data->pwm      = 0xf & adm1030_read_value(client, ADM1030_REG_FSP);
		data->last_updated = jiffies;
		data->valid = 1;
	}

	up(&data->update_lock);

	return data;
}

static int __init sensors_adm1030_init(void)
{
	return i2c_add_driver(&adm1030_driver);
}

static void __exit sensors_adm1030_exit(void)
{
	i2c_del_driver(&adm1030_driver);
}

MODULE_AUTHOR("Alexandre d'Alton <alex at alexdalton.org>");
MODULE_DESCRIPTION("ADM1030 driver");
MODULE_LICENSE("GPL");

module_init(sensors_adm1030_init);
module_exit(sensors_adm1030_exit);
+++++++++++++++++++++++++ end adm1030.c ++++++++++++++++++++++++++++++++


+++++++++++++++++++++++++++++ adm1030.h ++++++++++++++++++++++++++++++++
/*
    adm1030.h - Part of lm_sensors, Linux kernel modules for hardware
             monitoring
    Copyright (c) 2004 Alexandre d'Alton <alex at alexdalton.org>

    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; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
    This file contains common code for encoding/decoding LM75 type
    temperature readings, which are emulated by many of the chips
    we support.  As the user is unlikely to load more than one driver
    which contains this code, we don't worry about the wasted space.
*/

#include <linux/i2c-sensor.h>

/* Many ADM1030 constants specified below */
/* The ADM1030 registers */
/* Configuration register 1 */
#define ADM1030_REG_CONF1		0x00
#define ADM1030_CONF1_MEN               0x01 /* monitoring enable */
#define ADM1030_CONF1_IEN               0x02 /* INT enable */
#define ADM1030_CONF1_TMODE             0x04 /* Tach/Ain mode */
#define ADM1030_CONF1_PINV              0x08 /* PWM Invert */
#define ADM1030_CONF1_FFE               0x10 /* Fan Fault Enable */
#define ADM1030_CONF1_PMODE             0x60 /* PWM Mode 00 = Remote
						control Fan (Select
						PWM when manual) 11 =
						Fastest Calc speed
						controls fan (Select
						RPM when manual)*/
#define ADM1030_CONF1_AEN               0x80 /* Auto / SW Control */

/* Configuration register 2 */
#define ADM1030_REG_CONF2		0x01
#define ADM1030_CONF2_PEN               0x01 /* PWM1 Enable */
#define ADM1030_CONF2_UN1               0x02 /* Unused */
#define ADM1030_CONF2_TIEN              0x04 /* TACH input Enable  */
#define ADM1030_CONF2_UN2               0x08 /* Unused */
#define ADM1030_CONF2_LTE               0x10 /* Local temp enable */
#define ADM1030_CONF2_RTE               0x20 /* Remote temp enable */
#define ADM1030_CONF2_UN3               0x40 /* Unused */
#define ADM1030_CONF2_SWR               0x80 /* SW Reset */

/* STATUS register 1 */
#define ADM1030_REG_STAT1		0x02
#define ADM1030_STAT1_ASP               0x01 /* Alarm Speed  */
#define ADM1030_STAT1_FF                0x02 /* Fan Fault */
#define ADM1030_STAT1_RTH               0x04 /* Remote Temp High  */
#define ADM1030_STAT1_RTL               0x08 /* Remote Temp Low */
#define ADM1030_STAT1_RTT               0x10 /* Remote Temp Therm */
#define ADM1030_STAT1_RDE               0x20 /* Remote Diode Error */
#define ADM1030_STAT1_LTH               0x40 /* Local Temp High */
#define ADM1030_STAT1_LTL               0x80 /* Loc Temp Low */

/* STATUS register 2 */
#define ADM1030_REG_STAT2		0x03
#define ADM1030_STAT2_UN1               0x3f /* Unused  */
#define ADM1030_STAT2_LTH               0x40 /* Local Temp Therm */
#define ADM1030_STAT2_LTL               0x80 /* Therm Status */

/* Extended Temp register  */
#define ADM1030_REG_EXT 		0x06
#define ADM1030_EXT_ASP                 0x07 /* Remote extended temp
range  */
#define ADM1030_EXT_LTH                 0x38 /* Unused  */
#define ADM1030_EXT_LTL                 0xc0 /* Local extended temp
range */

/* Fan charasteristic register  */
#define ADM1030_REG_FCH 		0x20
#define ADM1030_FCH_FSU                 0x07 /* Fan spin up  */
#define ADM1030_EXT_PWMF                0x38 /* PWM Freq  */
#define ADM1030_EXT_SPR                 0xc0 /* Speed range */

/* Fan Speed register  */
#define ADM1030_REG_FSP 		0x22
#define ADM1030_FSP_NSP                 0x0f /* Fan min speed  */

/* Fan Filter register  */
#define ADM1030_REG_FLT 		0x23
#define ADM1030_FLT_SUD                 0x80 /* Spin up disable  */
#define ADM1030_FLT_RR                  0x60 /* Ramp rate  */
#define ADM1030_FLT_ASR                 0x1c /* ADC Sample rate  */
#define ADM1030_FLT_UN1                 0x02 /* Unused  */
#define ADM1030_FLT_FFE                 0x01 /* Fan Filter enable */

/* Local Temp Tmin / Trange  */
#define ADM1030_REG_LTR 		0x24
/* Remote Temp Tmin / Trange */
#define ADM1030_REG_RTR 		0x25
#define ADM1030_TR_TM                  0xf8 /* Local Temp Tmin  */
#define ADM1030_TR_TR                  0x60 /* Local Temp Trange  */

/* Therm to Fan  */
#define ADM1030_REG_TTF 		0x3f
#define ADM1030_TTF_EN                  0x80 /* Therm to fan enable */

#define ADM1030_REG_FAN_SPEED   	0x08
#define ADM1030_REG_FTAC		0x10

#define ADM1030_REG_TEMP_INT		0x0a
#define ADM1030_REG_TEMP_EXT		0x0b


+++++++++++++++++++++++++ end adm1030.h ++++++++++++++++++++++++++++++++





[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux