2005-04-17 02:20:36 +04:00
/*
Copyright ( C ) 2001 - 2004 Aurelien Jarno < aurelien @ aurel32 . net >
2009-03-30 23:46:43 +04:00
Ported to Linux 2.6 by Aurelien Jarno < aurelien @ aurel32 . net > with
2005-04-17 02:20:36 +04:00
the help of Jean Delvare < khali @ linux - fr . 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 0213 9 , USA .
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/i2c.h>
2006-01-19 01:16:04 +03:00
# include <linux/mutex.h>
2005-04-17 02:20:36 +04:00
/* Addresses to scan */
2008-01-27 20:14:47 +03:00
static const unsigned short normal_i2c [ ] = { 0x48 , 0x49 , 0x4a , 0x4b , 0x4c ,
2005-04-17 02:20:36 +04:00
0x4d , 0x4e , 0x4f , I2C_CLIENT_END } ;
/* Insmod parameters */
2005-07-31 23:49:03 +04:00
I2C_CLIENT_INSMOD_1 ( pcf8591 ) ;
2005-04-17 02:20:36 +04:00
static int input_mode ;
module_param ( input_mode , int , 0 ) ;
MODULE_PARM_DESC ( input_mode ,
" Analog input mode: \n "
" 0 = four single ended inputs \n "
" 1 = three differential inputs \n "
" 2 = single ended and differential mixed \n "
" 3 = two differential inputs \n " ) ;
/* The PCF8591 control byte
2009-03-30 23:46:43 +04:00
7 6 5 4 3 2 1 0
2005-04-17 02:20:36 +04:00
| 0 | AOEF | AIP | 0 | AINC | AICH | */
/* Analog Output Enable Flag (analog output active if 1) */
# define PCF8591_CONTROL_AOEF 0x40
2009-03-30 23:46:43 +04:00
/* Analog Input Programming
2005-04-17 02:20:36 +04:00
0x00 = four single ended inputs
0x10 = three differential inputs
0x20 = single ended and differential mixed
0x30 = two differential inputs */
# define PCF8591_CONTROL_AIP_MASK 0x30
/* Autoincrement Flag (switch on if 1) */
# define PCF8591_CONTROL_AINC 0x04
/* Channel selection
2009-03-30 23:46:43 +04:00
0x00 = channel 0
2005-04-17 02:20:36 +04:00
0x01 = channel 1
0x02 = channel 2
0x03 = channel 3 */
# define PCF8591_CONTROL_AICH_MASK 0x03
/* Initial values */
# define PCF8591_INIT_CONTROL ((input_mode << 4) | PCF8591_CONTROL_AOEF)
# define PCF8591_INIT_AOUT 0 /* DAC out = 0 */
/* Conversions */
# define REG_TO_SIGNED(reg) (((reg) & 0x80)?((reg) - 256):(reg))
struct pcf8591_data {
2006-01-19 01:16:04 +03:00
struct mutex update_lock ;
2005-04-17 02:20:36 +04:00
u8 control ;
u8 aout ;
} ;
static void pcf8591_init_client ( struct i2c_client * client ) ;
static int pcf8591_read_channel ( struct device * dev , int channel ) ;
/* following are the sysfs callback functions */
# define show_in_channel(channel) \
2005-05-17 14:42:25 +04:00
static ssize_t show_in # # channel # # _input ( struct device * dev , struct device_attribute * attr , char * buf ) \
2005-04-17 02:20:36 +04:00
{ \
return sprintf ( buf , " %d \n " , pcf8591_read_channel ( dev , channel ) ) ; \
} \
static DEVICE_ATTR ( in # # channel # # _input , S_IRUGO , \
show_in # # channel # # _input , NULL ) ;
show_in_channel ( 0 ) ;
show_in_channel ( 1 ) ;
show_in_channel ( 2 ) ;
show_in_channel ( 3 ) ;
2005-05-17 14:42:25 +04:00
static ssize_t show_out0_ouput ( struct device * dev , struct device_attribute * attr , char * buf )
2005-04-17 02:20:36 +04:00
{
struct pcf8591_data * data = i2c_get_clientdata ( to_i2c_client ( dev ) ) ;
return sprintf ( buf , " %d \n " , data - > aout * 10 ) ;
}
2005-05-17 14:42:25 +04:00
static ssize_t set_out0_output ( struct device * dev , struct device_attribute * attr , const char * buf , size_t count )
2005-04-17 02:20:36 +04:00
{
unsigned int value ;
struct i2c_client * client = to_i2c_client ( dev ) ;
struct pcf8591_data * data = i2c_get_clientdata ( client ) ;
if ( ( value = ( simple_strtoul ( buf , NULL , 10 ) + 5 ) / 10 ) < = 255 ) {
data - > aout = value ;
i2c_smbus_write_byte_data ( client , data - > control , data - > aout ) ;
return count ;
}
return - EINVAL ;
}
2009-03-30 23:46:43 +04:00
static DEVICE_ATTR ( out0_output , S_IWUSR | S_IRUGO ,
2005-04-17 02:20:36 +04:00
show_out0_ouput , set_out0_output ) ;
2005-05-17 14:42:25 +04:00
static ssize_t show_out0_enable ( struct device * dev , struct device_attribute * attr , char * buf )
2005-04-17 02:20:36 +04:00
{
struct pcf8591_data * data = i2c_get_clientdata ( to_i2c_client ( dev ) ) ;
return sprintf ( buf , " %u \n " , ! ( ! ( data - > control & PCF8591_CONTROL_AOEF ) ) ) ;
}
2005-05-17 14:42:25 +04:00
static ssize_t set_out0_enable ( struct device * dev , struct device_attribute * attr , const char * buf , size_t count )
2005-04-17 02:20:36 +04:00
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct pcf8591_data * data = i2c_get_clientdata ( client ) ;
unsigned long val = simple_strtoul ( buf , NULL , 10 ) ;
2006-01-19 01:16:04 +03:00
mutex_lock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
if ( val )
data - > control | = PCF8591_CONTROL_AOEF ;
else
data - > control & = ~ PCF8591_CONTROL_AOEF ;
i2c_smbus_write_byte ( client , data - > control ) ;
2006-01-19 01:16:04 +03:00
mutex_unlock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
return count ;
}
2009-03-30 23:46:43 +04:00
static DEVICE_ATTR ( out0_enable , S_IWUSR | S_IRUGO ,
2005-04-17 02:20:36 +04:00
show_out0_enable , set_out0_enable ) ;
2006-09-04 00:20:24 +04:00
static struct attribute * pcf8591_attributes [ ] = {
& dev_attr_out0_enable . attr ,
& dev_attr_out0_output . attr ,
& dev_attr_in0_input . attr ,
& dev_attr_in1_input . attr ,
NULL
} ;
static const struct attribute_group pcf8591_attr_group = {
. attrs = pcf8591_attributes ,
} ;
static struct attribute * pcf8591_attributes_opt [ ] = {
& dev_attr_in2_input . attr ,
& dev_attr_in3_input . attr ,
NULL
} ;
static const struct attribute_group pcf8591_attr_group_opt = {
. attrs = pcf8591_attributes_opt ,
} ;
2005-04-17 02:20:36 +04:00
/*
* Real code
*/
2008-07-16 21:30:06 +04:00
/* Return 0 if detection is successful, -ENODEV otherwise */
static int pcf8591_detect ( struct i2c_client * client , int kind ,
struct i2c_board_info * info )
2005-04-17 02:20:36 +04:00
{
2008-07-16 21:30:06 +04:00
struct i2c_adapter * adapter = client - > adapter ;
2005-04-17 02:20:36 +04:00
if ( ! i2c_check_functionality ( adapter , I2C_FUNC_SMBUS_BYTE
| I2C_FUNC_SMBUS_WRITE_BYTE_DATA ) )
2008-07-16 21:30:06 +04:00
return - ENODEV ;
/* Now, we would do the remaining detection. But the PCF8591 is plainly
impossible to detect ! Stupid chip . */
strlcpy ( info - > type , " pcf8591 " , I2C_NAME_SIZE ) ;
return 0 ;
}
static int pcf8591_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct pcf8591_data * data ;
int err ;
2005-04-17 02:20:36 +04:00
2005-10-18 01:09:43 +04:00
if ( ! ( data = kzalloc ( sizeof ( struct pcf8591_data ) , GFP_KERNEL ) ) ) {
2005-04-17 02:20:36 +04:00
err = - ENOMEM ;
goto exit ;
}
2009-03-30 23:46:43 +04:00
2008-07-15 00:38:36 +04:00
i2c_set_clientdata ( client , data ) ;
2006-01-19 01:16:04 +03:00
mutex_init ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
/* Initialize the PCF8591 chip */
2008-07-15 00:38:36 +04:00
pcf8591_init_client ( client ) ;
2005-04-17 02:20:36 +04:00
/* Register sysfs hooks */
2008-07-15 00:38:36 +04:00
err = sysfs_create_group ( & client - > dev . kobj , & pcf8591_attr_group ) ;
2006-09-04 00:20:24 +04:00
if ( err )
2008-07-16 21:30:06 +04:00
goto exit_kfree ;
2005-04-17 02:20:36 +04:00
/* Register input2 if not in "two differential inputs" mode */
2006-09-04 00:20:24 +04:00
if ( input_mode ! = 3 ) {
2008-07-15 00:38:36 +04:00
if ( ( err = device_create_file ( & client - > dev ,
2006-09-04 00:20:24 +04:00
& dev_attr_in2_input ) ) )
goto exit_sysfs_remove ;
}
2005-04-17 02:20:36 +04:00
/* Register input3 only in "four single ended inputs" mode */
2006-09-04 00:20:24 +04:00
if ( input_mode = = 0 ) {
2008-07-15 00:38:36 +04:00
if ( ( err = device_create_file ( & client - > dev ,
2006-09-04 00:20:24 +04:00
& dev_attr_in3_input ) ) )
goto exit_sysfs_remove ;
}
2005-04-17 02:20:36 +04:00
return 0 ;
2006-09-04 00:20:24 +04:00
exit_sysfs_remove :
2008-07-15 00:38:36 +04:00
sysfs_remove_group ( & client - > dev . kobj , & pcf8591_attr_group_opt ) ;
sysfs_remove_group ( & client - > dev . kobj , & pcf8591_attr_group ) ;
2005-04-17 02:20:36 +04:00
exit_kfree :
kfree ( data ) ;
exit :
return err ;
}
2008-07-16 21:30:06 +04:00
static int pcf8591_remove ( struct i2c_client * client )
2005-04-17 02:20:36 +04:00
{
2006-09-04 00:20:24 +04:00
sysfs_remove_group ( & client - > dev . kobj , & pcf8591_attr_group_opt ) ;
sysfs_remove_group ( & client - > dev . kobj , & pcf8591_attr_group ) ;
2005-04-17 02:20:36 +04:00
kfree ( i2c_get_clientdata ( client ) ) ;
return 0 ;
}
/* Called when we have found a new PCF8591. */
static void pcf8591_init_client ( struct i2c_client * client )
{
struct pcf8591_data * data = i2c_get_clientdata ( client ) ;
data - > control = PCF8591_INIT_CONTROL ;
data - > aout = PCF8591_INIT_AOUT ;
i2c_smbus_write_byte_data ( client , data - > control , data - > aout ) ;
2009-03-30 23:46:43 +04:00
/* The first byte transmitted contains the conversion code of the
2005-04-17 02:20:36 +04:00
previous read cycle . FLUSH IT ! */
i2c_smbus_read_byte ( client ) ;
}
static int pcf8591_read_channel ( struct device * dev , int channel )
{
u8 value ;
struct i2c_client * client = to_i2c_client ( dev ) ;
struct pcf8591_data * data = i2c_get_clientdata ( client ) ;
2006-01-19 01:16:04 +03:00
mutex_lock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
if ( ( data - > control & PCF8591_CONTROL_AICH_MASK ) ! = channel ) {
data - > control = ( data - > control & ~ PCF8591_CONTROL_AICH_MASK )
| channel ;
i2c_smbus_write_byte ( client , data - > control ) ;
2009-03-30 23:46:43 +04:00
/* The first byte transmitted contains the conversion code of
2005-04-17 02:20:36 +04:00
the previous read cycle . FLUSH IT ! */
i2c_smbus_read_byte ( client ) ;
}
value = i2c_smbus_read_byte ( client ) ;
2006-01-19 01:16:04 +03:00
mutex_unlock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
if ( ( channel = = 2 & & input_mode = = 2 ) | |
( channel ! = 3 & & ( input_mode = = 1 | | input_mode = = 3 ) ) )
return ( 10 * REG_TO_SIGNED ( value ) ) ;
else
return ( 10 * value ) ;
}
2008-07-16 21:30:06 +04:00
static const struct i2c_device_id pcf8591_id [ ] = {
{ " pcf8591 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , pcf8591_id ) ;
static struct i2c_driver pcf8591_driver = {
. driver = {
. name = " pcf8591 " ,
} ,
. probe = pcf8591_probe ,
. remove = pcf8591_remove ,
. id_table = pcf8591_id ,
. class = I2C_CLASS_HWMON , /* Nearest choice */
. detect = pcf8591_detect ,
. address_data = & addr_data ,
} ;
2005-04-17 02:20:36 +04:00
static int __init pcf8591_init ( void )
{
if ( input_mode < 0 | | input_mode > 3 ) {
printk ( KERN_WARNING " pcf8591: invalid input_mode (%d) \n " ,
input_mode ) ;
input_mode = 0 ;
}
return i2c_add_driver ( & pcf8591_driver ) ;
}
static void __exit pcf8591_exit ( void )
{
i2c_del_driver ( & pcf8591_driver ) ;
}
MODULE_AUTHOR ( " Aurelien Jarno <aurelien@aurel32.net> " ) ;
MODULE_DESCRIPTION ( " PCF8591 driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( pcf8591_init ) ;
module_exit ( pcf8591_exit ) ;