ADM1030 Driver

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

 



OK,

I just read the Documentation/i2c/porting-clients and sysfs-interface,
and I saw that I am far from the standard (in terms of naming and coding
conventions...) 
Will work on it tomorrow...

Alex.

On Wed, 2004-04-07 at 21:45, Alexandre d'Alton wrote:
> 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