2019-05-20 10:19:02 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2005-04-17 02:20:36 +04:00
/*
2012-01-15 09:57:29 +04:00
* Copyright ( C ) 2001 - 2004 Aurelien Jarno < aurelien @ aurel32 . net >
* Ported to Linux 2.6 by Aurelien Jarno < aurelien @ aurel32 . net > with
2014-01-29 23:40:08 +04:00
* the help of Jean Delvare < jdelvare @ suse . de >
2012-01-15 09:57:29 +04:00
*/
2005-04-17 02:20:36 +04:00
2010-10-20 10:51:45 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2005-04-17 02:20:36 +04:00
# 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>
2010-10-28 22:31:49 +04:00
# include <linux/err.h>
# include <linux/hwmon.h>
2005-04-17 02:20:36 +04:00
/* Insmod parameters */
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 " ) ;
2012-01-15 09:57:29 +04:00
/*
* The PCF8591 control byte
* 7 6 5 4 3 2 1 0
* | 0 | AOEF | AIP | 0 | AINC | AICH |
*/
2005-04-17 02:20:36 +04:00
/* Analog Output Enable Flag (analog output active if 1) */
# define PCF8591_CONTROL_AOEF 0x40
2009-03-30 23:46:43 +04:00
2012-01-15 09:57:29 +04:00
/*
* Analog Input Programming
* 0x00 = four single ended inputs
* 0x10 = three differential inputs
* 0x20 = single ended and differential mixed
* 0x30 = two differential inputs
*/
2005-04-17 02:20:36 +04:00
# define PCF8591_CONTROL_AIP_MASK 0x30
/* Autoincrement Flag (switch on if 1) */
# define PCF8591_CONTROL_AINC 0x04
2012-01-15 09:57:29 +04:00
/*
* Channel selection
* 0x00 = channel 0
* 0x01 = channel 1
* 0x02 = channel 2
* 0x03 = channel 3
*/
2005-04-17 02:20:36 +04:00
# 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 */
2012-01-15 09:57:29 +04:00
# define REG_TO_SIGNED(reg) (((reg) & 0x80) ? ((reg) - 256) : (reg))
2005-04-17 02:20:36 +04:00
struct pcf8591_data {
2010-10-28 22:31:49 +04:00
struct device * hwmon_dev ;
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) \
2012-01-15 09:57:29 +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 ) ;
2016-12-22 15:05:03 +03:00
static ssize_t out0_output_show ( 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 ) ;
}
2016-12-22 15:05:03 +03:00
static ssize_t out0_output_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
2005-04-17 02:20:36 +04:00
{
2012-01-15 09:57:29 +04:00
unsigned long val ;
2005-04-17 02:20:36 +04:00
struct i2c_client * client = to_i2c_client ( dev ) ;
struct pcf8591_data * data = i2c_get_clientdata ( client ) ;
2012-01-15 09:57:29 +04:00
int err ;
err = kstrtoul ( buf , 10 , & val ) ;
if ( err )
return err ;
val / = 10 ;
if ( val > 255 )
return - EINVAL ;
data - > aout = val ;
i2c_smbus_write_byte_data ( client , data - > control , data - > aout ) ;
return count ;
2005-04-17 02:20:36 +04:00
}
2016-12-22 15:05:03 +03:00
static DEVICE_ATTR_RW ( out0_output ) ;
2005-04-17 02:20:36 +04:00
2016-12-22 15:05:03 +03:00
static ssize_t out0_enable_show ( struct device * dev ,
2012-01-15 09:57:29 +04:00
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 ) ) ) ;
}
2016-12-22 15:05:03 +03:00
static ssize_t out0_enable_store ( 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 ) ;
2012-01-15 09:57:29 +04:00
unsigned long val ;
int err ;
err = kstrtoul ( buf , 10 , & val ) ;
if ( err )
return err ;
2005-04-17 02:20:36 +04:00
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 ;
}
2016-12-22 15:05:03 +03:00
static DEVICE_ATTR_RW ( out0_enable ) ;
2005-04-17 02:20:36 +04:00
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
*/
2020-08-13 19:02:22 +03:00
static int pcf8591_probe ( struct i2c_client * client )
2008-07-16 21:30:06 +04:00
{
struct pcf8591_data * data ;
int err ;
2005-04-17 02:20:36 +04:00
2012-06-02 22:20:20 +04:00
data = devm_kzalloc ( & client - > dev , sizeof ( struct pcf8591_data ) ,
GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
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 )
2012-06-02 22:20:20 +04:00
return err ;
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 ) {
2012-01-15 09:57:29 +04:00
err = device_create_file ( & client - > dev , & dev_attr_in2_input ) ;
if ( err )
2006-09-04 00:20:24 +04:00
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 ) {
2012-01-15 09:57:29 +04:00
err = device_create_file ( & client - > dev , & dev_attr_in3_input ) ;
if ( err )
2006-09-04 00:20:24 +04:00
goto exit_sysfs_remove ;
}
2010-10-28 22:31:49 +04:00
data - > hwmon_dev = hwmon_device_register ( & client - > dev ) ;
if ( IS_ERR ( data - > hwmon_dev ) ) {
err = PTR_ERR ( data - > hwmon_dev ) ;
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
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
{
2010-10-28 22:31:49 +04:00
struct pcf8591_data * data = i2c_get_clientdata ( client ) ;
hwmon_device_unregister ( data - > hwmon_dev ) ;
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
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
2012-01-15 09:57:29 +04:00
/*
* The first byte transmitted contains the conversion code of the
* previous read cycle . FLUSH IT !
*/
2005-04-17 02:20:36 +04:00
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
2012-01-15 09:57:29 +04:00
/*
* The first byte transmitted contains the conversion code of
* the previous read cycle . FLUSH IT !
*/
2005-04-17 02:20:36 +04:00
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 ) ) )
2012-01-05 22:50:18 +04:00
return 10 * REG_TO_SIGNED ( value ) ;
2005-04-17 02:20:36 +04:00
else
2012-01-05 22:50:18 +04:00
return 10 * value ;
2005-04-17 02:20:36 +04:00
}
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 " ,
} ,
2020-08-13 19:02:22 +03:00
. probe_new = pcf8591_probe ,
2008-07-16 21:30:06 +04:00
. remove = pcf8591_remove ,
. id_table = pcf8591_id ,
} ;
2005-04-17 02:20:36 +04:00
static int __init pcf8591_init ( void )
{
if ( input_mode < 0 | | input_mode > 3 ) {
2010-10-20 10:51:45 +04:00
pr_warn ( " invalid input_mode (%d) \n " , input_mode ) ;
2005-04-17 02:20:36 +04:00
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 ) ;