New chip driver: ds1621
PATCH 1/1 New chip driver: ds1621
Hello Greg,
The following patch against kernel 2.6.4-mm1 adds the ds1621 driver (an I2C sensor). I have ported it from the 2.4 version.
It has been reviewed by Jean Delvare, partly on IRC, and it is "compliant" with Mark Hoffman's refactoring.
Thanks,
Aurelien
drivers/i2c/chips/ds1621.c +345 -0
@@ -0,0 +1,345 @@ +/* +ds1621.c - Part of lm_sensors, Linux kernel modules for hardware +monitoring +Christian W. Zuckschwerdt <[email protected]> 2000-11-23 +based on lm75.c by Frodo Looijaard <[email protected]> +Ported to Linux 2.6 by Aurelien Jarno <[email protected]> with +the help of Jean Delvare <[email protected]> + +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/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/i2c-sensor.h> +#include "lm75.h" + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {I2C_CLIENT_END }; +static unsigned short normal_i2c_range[] = {0x48 ,0x4f ,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 (ds1621); +static int polarity =-1 ; +MODULE_PARM (polarity,"i" ); +MODULE_PARM_DESC (polarity,"Output's polarity: 0 = active high, 1 = active low" ); + +/* Many DS1621 constants specified below */ +/* Config register used for detection */ +/* 7 6 5 4 3 2 1 0 */ +/* |Done|THF |TLF |NVB | 1 | 0 |POL |1SHOT| */ +#define DS1621_REG_CONFIG_MASK 0x0C +#define DS1621_REG_CONFIG_VAL 0x08 +#define DS1621_REG_CONFIG_POLARITY 0x02 +#define DS1621_REG_CONFIG_1SHOT 0x01 +#define DS1621_REG_CONFIG_DONE 0x80 + +/* The DS1621 registers */ +#define DS1621_REG_TEMP 0xAA/* word, RO */ +#define DS1621_REG_TEMP_MIN 0xA1/* word, RW */ +#define DS1621_REG_TEMP_MAX 0xA2/* word, RW */ +#define DS1621_REG_CONF 0xAC/* byte, RW */ +#define DS1621_COM_START 0xEE/* no data */ + +/* The DS1621 configuration register */ +#define DS1621_ALARM_TEMP_HIGH 0x40 +#define DS1621_ALARM_TEMP_LOW 0x20 + +/* Conversions. Rounding and limit checking is only done on the TO_REG +variants. Note that you should be a bit careful with which arguments +these macros are called: arguments may be evaluated more than once. +Fixing this is just not worth it. */ +#define ALARMS_FROM_REG (val) ((val) & \ + (DS1621_ALARM_TEMP_HIGH | DS1621_ALARM_TEMP_LOW)) + +/* Each client has this additional data */ +struct ds1621_data { +struct semaphore update_lock; +char valid;/* !=0 if following fields are valid */ +unsigned long last_updated;/* In jiffies */ + +u16 temp, temp_min, temp_max;/* Register values, word */ +u8 conf;/* Register encoding, combined */ +}; + +static int ds1621_attach_adapter (struct i2c_adapter *adapter); +static int ds1621_detect (struct i2c_adapter *adapter,int address, +int kind); +static void ds1621_init_client (struct i2c_client *client); +static int ds1621_detach_client (struct i2c_client *client); +static struct ds1621_data *ds1621_update_client (struct device *dev); + +/* This is the driver that will be inserted */ +static struct i2c_driver ds1621_driver = { + .owner =THIS_MODULE , + .name ="ds1621" , + .id =I2C_DRIVERID_DS1621 , + .flags =I2C_DF_NOTIFY , + .attach_adapter = ds1621_attach_adapter, + .detach_client = ds1621_detach_client, +}; + +static int ds1621_id =0 ; + +static u16 swap_bytes (u16 val) +{ +return (val >>8 ) | (val <<8 ); +} + +/* All registers are word-sized, except for the configuration register. +DS1621 uses a high-byte first convention, which is exactly opposite to +the usual practice. */ +static int ds1621_read_value (struct i2c_client *client,u8 reg) +{ +if (reg =DS1621_REG_CONF ) +return i2c_smbus_read_byte_data (client, reg); +else +return swap_bytes (i2c_smbus_read_word_data (client, reg)); +} + +/* All registers are word-sized, except for the configuration register. +DS1621 uses a high-byte first convention, which is exactly opposite to +the usual practice. */ +static int ds1621_write_value (struct i2c_client *client,u8 reg,u16 value) +{ +if (reg =DS1621_REG_CONF ) +return i2c_smbus_write_byte_data (client, reg, value); +else +return i2c_smbus_write_word_data (client, reg, +swap_bytes (value)); +} + +static void ds1621_init_client (struct i2c_client *client) +{ +int reg =ds1621_read_value (client,DS1621_REG_CONF ); +/* switch to continous conversion mode */ + reg &= ~DS1621_REG_CONFIG_1SHOT ; + +/* setup output polarity */ +if (polarity =0 ) + reg &= ~DS1621_REG_CONFIG_POLARITY ; +else if (polarity =1 ) + reg |=DS1621_REG_CONFIG_POLARITY ; + +ds1621_write_value (client,DS1621_REG_CONF , reg); + +/* start conversion */ +i2c_smbus_write_byte (client,DS1621_COM_START ); +} + +#define show (value) \ +static ssize_t show_##value(struct device *dev, char *buf) \ +{ \ + struct ds1621_data *data = ds1621_update_client(dev); \ + return sprintf(buf, "%d\n", LM75_TEMP_FROM_REG(data->value)); \ +} + +show (temp); +show (temp_min); +show (temp_max); + +#define set_temp (suffix, value, reg) \ +static ssize_t set_temp_##suffix(struct device *dev, const char *buf, \ + size_t count) \ +{ \ + struct i2c_client *client = to_i2c_client(dev); \ + struct ds1621_data *data = ds1621_update_client(dev); \ + data->value = LM75_TEMP_TO_REG(simple_strtoul(buf, NULL, 10)); \ + ds1621_write_value(client, reg, data->value); \ + return count; \ +} + +set_temp (min, temp_min,DS1621_REG_TEMP_MIN ); +set_temp (max, temp_max,DS1621_REG_TEMP_MAX ); + +static ssize_t show_alarms (struct device *dev,char *buf) +{ +struct ds1621_data *data =ds1621_update_client (dev); +return sprintf (buf,"%d\n" ,ALARMS_FROM_REG (data->conf)); +} + +static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL); +static DEVICE_ATTR(temp1_input, S_IRUGO , show_temp, NULL); +static DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO , show_temp_min, set_temp_min); +static DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max); + + +static int ds1621_attach_adapter (struct i2c_adapter *adapter) +{ +return i2c_detect (adapter, &addr_data, ds1621_detect); +} + +/* This function is called by i2c_detect */ +int ds1621_detect (struct i2c_adapter *adapter,int address, +int kind) +{ +int conf, temp; +struct i2c_client *new_client; +struct ds1621_data *data; +int err =0 ; + +if (!i2c_check_functionality (adapter,I2C_FUNC_SMBUS_BYTE_DATA + |I2C_FUNC_SMBUS_WORD_DATA + |I2C_FUNC_SMBUS_WRITE_BYTE )) + 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 ds1621_{read,write}_value. */ +if (!(new_client =kmalloc (sizeof (struct i2c_client ) + +sizeof (struct ds1621_data ), +GFP_KERNEL ))) { + err = -ENOMEM ; + goto exit; + } +memset (new_client,0 ,sizeof (struct i2c_client ) + +sizeof (struct ds1621_data )); + + data = (struct ds1621_data *) (new_client +1 ); +i2c_set_clientdata (new_client, data); + new_client->addr = address; + new_client->adapter = adapter; + new_client->driver = &ds1621_driver; + new_client->flags =0 ; + + +/* Now, we do the remaining detection. It is lousy. */ +if (kind <0 ) { + conf =ds1621_read_value (new_client,DS1621_REG_CONF ); +if ((conf &DS1621_REG_CONFIG_MASK ) !=DS1621_REG_CONFIG_VAL ) + goto exit_free; + temp =ds1621_read_value (new_client,DS1621_REG_TEMP ); +if (temp &0x007f ) + goto exit_free; + temp =ds1621_read_value (new_client,DS1621_REG_TEMP_MIN ); +if (temp &0x007f ) + goto exit_free; + temp =ds1621_read_value (new_client,DS1621_REG_TEMP_MAX ); +if (temp &0x007f ) + goto exit_free; + } + +/* Determine the chip type - only one kind supported! */ +if (kind <=0 ) + kind = ds1621; + +/* Fill in remaining client fields and put it into the global list */ +strlcpy (new_client->name,"ds1621" ,I2C_NAME_SIZE ); + + new_client->id = ds1621_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 DS1621 chip */ +ds1621_init_client (new_client); + +/* Register sysfs hooks */ +device_create_file (&new_client->dev, &dev_attr_alarms); +device_create_file (&new_client->dev, &dev_attr_temp1_input); +device_create_file (&new_client->dev, &dev_attr_temp1_min); +device_create_file (&new_client->dev, &dev_attr_temp1_max); + +return 0 ; + +/* OK, this is not exactly good programming practice, usually. But it is +very code-efficient in this case. */ + exit_free: +kfree (new_client); + exit: +return err; +} + +static int ds1621_detach_client (struct i2c_client *client) +{ +int err; + +if ((err =i2c_detach_client (client))) { +dev_err (&client->dev, +"ds1621.o: Client deregistration failed, client not detached.\n" ); +return err; + } + +kfree (client); + +return 0 ; +} + + +static struct ds1621_data *ds1621_update_client (struct device *dev) +{ +struct i2c_client *client =to_i2c_client (dev); +struct ds1621_data *data =i2c_get_clientdata (client); +u8 new_conf; + +down (&data->update_lock); + +if ((jiffies - data->last_updated >HZ +HZ /2 ) || + (jiffies < data->last_updated) || !data->valid) { + +dev_dbg (&client->dev,"Starting ds1621 update\n" ); + + data->conf =ds1621_read_value (client,DS1621_REG_CONF ); + + data->temp =ds1621_read_value (client,DS1621_REG_TEMP ); + + data->temp_min =ds1621_read_value (client, +DS1621_REG_TEMP_MIN ); + data->temp_max =ds1621_read_value (client, +DS1621_REG_TEMP_MAX ); + +/* reset alarms if neccessary */ + new_conf = data->conf; +if (data->temp < data->temp_min) + new_conf &= ~DS1621_ALARM_TEMP_LOW ; +if (data->temp > data->temp_max) + new_conf &= ~DS1621_ALARM_TEMP_HIGH ; +if (data->conf != new_conf) +ds1621_write_value (client,DS1621_REG_CONF , + new_conf); + + data->last_updated = jiffies; + data->valid =1 ; + } + +up (&data->update_lock); + +return data; +} + +static int __init ds1621_init (void ) +{ +return i2c_add_driver (&ds1621_driver); +} + +static void __exit ds1621_exit(void) +{ +i2c_del_driver (&ds1621_driver); +} + + +MODULE_AUTHOR ("Christian W. Zuckschwerdt <[email protected]>" ); +MODULE_DESCRIPTION ("DS1621 driver" ); +MODULE_LICENSE ("GPL" ); + +module_init (ds1621_init); +module_exit (ds1621_exit);
drivers/i2c/chips/Kconfig +11 -0
@@ -33,6 +33,17 @@ This driver can also be built as a module. If so, the module will be called asb100. +config SENSORS_DS1621 + tristate "Dallas Semiconductor DS1621 and DS1625" + depends on I2C && EXPERIMENTAL + select I2C_SENSOR + help + If you say yes here you get support for Dallas Semiconductor + DS1621 and DS1625 sensor chips. + + This driver can also be built as a module. If so, the module + will be called ds1621. + config SENSORS_FSCHER tristate "FSC Hermes" depends on I2C && EXPERIMENTAL
drivers/i2c/chips/Makefile +1 -0
@@ -8,6 +8,7 @@ obj-$(CONFIG_SENSORS_W83781D) += w83781d.o obj-$(CONFIG_SENSORS_ADM1021) += adm1021.o +obj-$(CONFIG_SENSORS_DS1621) += ds1621.o obj-$(CONFIG_SENSORS_EEPROM) += eeprom.o obj-$(CONFIG_SENSORS_FSCHER) += fscher.o obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
PATCH 1/1 New chip driver: ds1621
Quoted 7 lines · Aurelien Jarno
Hello Greg,
The following patch against kernel 2.6.4-mm1 adds the ds1621 driver (an I2C sensor). I have ported it from the 2.4 version.
It has been reviewed by Jean Delvare, partly on IRC, and it is "compliant" with Mark Hoffman's refactoring.
Looks nice, applied, thanks.
greg k-h