2019-05-30 02:57:44 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2017-01-09 05:55:26 +03:00
/*
* tmp007 . c - Support for TI TMP007 IR thermopile sensor with integrated math engine
*
* Copyright ( c ) 2017 Manivannan Sadhasivam < manivannanece23 @ gmail . com >
*
* Driver for the Texas Instruments I2C 16 - bit IR thermopile sensor
*
* ( 7 - bit I2C slave address ( 0x40 - 0x47 ) , changeable via ADR pins )
*
2017-01-23 18:55:16 +03:00
* Note :
* 1. This driver assumes that the sensor has been calibrated beforehand
* 2. Limit threshold events are enabled at the start
* 3. Operating mode : INT
2017-01-09 05:55:26 +03:00
*/
# include <linux/err.h>
# include <linux/i2c.h>
# include <linux/delay.h>
# include <linux/module.h>
# include <linux/pm.h>
# include <linux/bitops.h>
# include <linux/of.h>
2017-01-23 18:55:16 +03:00
# include <linux/irq.h>
# include <linux/interrupt.h>
2017-01-09 05:55:26 +03:00
# include <linux/iio/iio.h>
# include <linux/iio/sysfs.h>
2017-01-23 18:55:16 +03:00
# include <linux/iio/events.h>
2017-01-09 05:55:26 +03:00
# define TMP007_TDIE 0x01
# define TMP007_CONFIG 0x02
# define TMP007_TOBJECT 0x03
# define TMP007_STATUS 0x04
# define TMP007_STATUS_MASK 0x05
2017-01-23 18:55:16 +03:00
# define TMP007_TOBJ_HIGH_LIMIT 0x06
# define TMP007_TOBJ_LOW_LIMIT 0x07
# define TMP007_TDIE_HIGH_LIMIT 0x08
# define TMP007_TDIE_LOW_LIMIT 0x09
2017-01-09 05:55:26 +03:00
# define TMP007_MANUFACTURER_ID 0x1e
# define TMP007_DEVICE_ID 0x1f
# define TMP007_CONFIG_CONV_EN BIT(12)
# define TMP007_CONFIG_TC_EN BIT(6)
# define TMP007_CONFIG_CR_MASK GENMASK(11, 9)
2017-01-23 18:55:16 +03:00
# define TMP007_CONFIG_ALERT_EN BIT(8)
2017-01-09 05:55:26 +03:00
# define TMP007_CONFIG_CR_SHIFT 9
2017-01-23 18:55:16 +03:00
/* Status register flags */
# define TMP007_STATUS_ALERT BIT(15)
2017-01-09 05:55:26 +03:00
# define TMP007_STATUS_CONV_READY BIT(14)
2017-01-23 18:55:16 +03:00
# define TMP007_STATUS_OHF BIT(13)
# define TMP007_STATUS_OLF BIT(12)
# define TMP007_STATUS_LHF BIT(11)
# define TMP007_STATUS_LLF BIT(10)
2017-01-09 05:55:26 +03:00
# define TMP007_STATUS_DATA_VALID BIT(9)
# define TMP007_MANUFACTURER_MAGIC 0x5449
# define TMP007_DEVICE_MAGIC 0x0078
# define TMP007_TEMP_SHIFT 2
struct tmp007_data {
struct i2c_client * client ;
2017-01-23 18:55:16 +03:00
struct mutex lock ;
2017-01-09 05:55:26 +03:00
u16 config ;
2017-01-23 18:55:16 +03:00
u16 status_mask ;
2017-01-09 05:55:26 +03:00
} ;
static const int tmp007_avgs [ 5 ] [ 2 ] = { { 4 , 0 } , { 2 , 0 } , { 1 , 0 } ,
{ 0 , 500000 } , { 0 , 250000 } } ;
static int tmp007_read_temperature ( struct tmp007_data * data , u8 reg )
{
s32 ret ;
int tries = 50 ;
while ( tries - - > 0 ) {
ret = i2c_smbus_read_word_swapped ( data - > client ,
TMP007_STATUS ) ;
if ( ret < 0 )
return ret ;
if ( ( ret & TMP007_STATUS_CONV_READY ) & &
! ( ret & TMP007_STATUS_DATA_VALID ) )
break ;
msleep ( 100 ) ;
}
if ( tries < 0 )
return - EIO ;
return i2c_smbus_read_word_swapped ( data - > client , reg ) ;
}
static int tmp007_powerdown ( struct tmp007_data * data )
{
return i2c_smbus_write_word_swapped ( data - > client , TMP007_CONFIG ,
data - > config & ~ TMP007_CONFIG_CONV_EN ) ;
}
static int tmp007_read_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * channel , int * val ,
int * val2 , long mask )
{
struct tmp007_data * data = iio_priv ( indio_dev ) ;
s32 ret ;
int conv_rate ;
switch ( mask ) {
case IIO_CHAN_INFO_RAW :
switch ( channel - > channel2 ) {
case IIO_MOD_TEMP_AMBIENT : /* LSB: 0.03125 degree Celsius */
ret = i2c_smbus_read_word_swapped ( data - > client , TMP007_TDIE ) ;
if ( ret < 0 )
return ret ;
break ;
case IIO_MOD_TEMP_OBJECT :
ret = tmp007_read_temperature ( data , TMP007_TOBJECT ) ;
if ( ret < 0 )
return ret ;
break ;
default :
return - EINVAL ;
}
* val = sign_extend32 ( ret , 15 ) > > TMP007_TEMP_SHIFT ;
return IIO_VAL_INT ;
case IIO_CHAN_INFO_SCALE :
* val = 31 ;
* val2 = 250000 ;
return IIO_VAL_INT_PLUS_MICRO ;
case IIO_CHAN_INFO_SAMP_FREQ :
conv_rate = ( data - > config & TMP007_CONFIG_CR_MASK )
> > TMP007_CONFIG_CR_SHIFT ;
* val = tmp007_avgs [ conv_rate ] [ 0 ] ;
* val2 = tmp007_avgs [ conv_rate ] [ 1 ] ;
return IIO_VAL_INT_PLUS_MICRO ;
default :
return - EINVAL ;
}
}
static int tmp007_write_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * channel , int val ,
int val2 , long mask )
{
struct tmp007_data * data = iio_priv ( indio_dev ) ;
int i ;
u16 tmp ;
if ( mask = = IIO_CHAN_INFO_SAMP_FREQ ) {
for ( i = 0 ; i < ARRAY_SIZE ( tmp007_avgs ) ; i + + ) {
if ( ( val = = tmp007_avgs [ i ] [ 0 ] ) & &
( val2 = = tmp007_avgs [ i ] [ 1 ] ) ) {
tmp = data - > config & ~ TMP007_CONFIG_CR_MASK ;
tmp | = ( i < < TMP007_CONFIG_CR_SHIFT ) ;
return i2c_smbus_write_word_swapped ( data - > client ,
TMP007_CONFIG ,
data - > config = tmp ) ;
}
}
}
return - EINVAL ;
}
2017-01-23 18:55:16 +03:00
static irqreturn_t tmp007_interrupt_handler ( int irq , void * private )
{
struct iio_dev * indio_dev = private ;
struct tmp007_data * data = iio_priv ( indio_dev ) ;
int ret ;
ret = i2c_smbus_read_word_swapped ( data - > client , TMP007_STATUS ) ;
if ( ( ret < 0 ) | | ! ( ret & ( TMP007_STATUS_OHF | TMP007_STATUS_OLF |
TMP007_STATUS_LHF | TMP007_STATUS_LLF ) ) )
return IRQ_NONE ;
if ( ret & TMP007_STATUS_OHF )
iio_push_event ( indio_dev ,
IIO_MOD_EVENT_CODE ( IIO_TEMP , 0 ,
IIO_MOD_TEMP_OBJECT ,
IIO_EV_TYPE_THRESH ,
IIO_EV_DIR_RISING ) ,
iio_get_time_ns ( indio_dev ) ) ;
if ( ret & TMP007_STATUS_OLF )
iio_push_event ( indio_dev ,
IIO_MOD_EVENT_CODE ( IIO_TEMP , 0 ,
IIO_MOD_TEMP_OBJECT ,
IIO_EV_TYPE_THRESH ,
IIO_EV_DIR_FALLING ) ,
iio_get_time_ns ( indio_dev ) ) ;
if ( ret & TMP007_STATUS_LHF )
iio_push_event ( indio_dev ,
IIO_MOD_EVENT_CODE ( IIO_TEMP , 0 ,
IIO_MOD_TEMP_AMBIENT ,
IIO_EV_TYPE_THRESH ,
IIO_EV_DIR_RISING ) ,
iio_get_time_ns ( indio_dev ) ) ;
if ( ret & TMP007_STATUS_LLF )
iio_push_event ( indio_dev ,
IIO_MOD_EVENT_CODE ( IIO_TEMP , 0 ,
IIO_MOD_TEMP_AMBIENT ,
IIO_EV_TYPE_THRESH ,
IIO_EV_DIR_FALLING ) ,
iio_get_time_ns ( indio_dev ) ) ;
return IRQ_HANDLED ;
}
static int tmp007_write_event_config ( struct iio_dev * indio_dev ,
const struct iio_chan_spec * chan , enum iio_event_type type ,
enum iio_event_direction dir , int state )
{
struct tmp007_data * data = iio_priv ( indio_dev ) ;
unsigned int status_mask ;
int ret ;
switch ( chan - > channel2 ) {
case IIO_MOD_TEMP_AMBIENT :
if ( dir = = IIO_EV_DIR_RISING )
status_mask = TMP007_STATUS_LHF ;
else
status_mask = TMP007_STATUS_LLF ;
break ;
case IIO_MOD_TEMP_OBJECT :
if ( dir = = IIO_EV_DIR_RISING )
status_mask = TMP007_STATUS_OHF ;
else
status_mask = TMP007_STATUS_OLF ;
break ;
default :
return - EINVAL ;
}
mutex_lock ( & data - > lock ) ;
ret = i2c_smbus_read_word_swapped ( data - > client , TMP007_STATUS_MASK ) ;
mutex_unlock ( & data - > lock ) ;
if ( ret < 0 )
return ret ;
if ( state )
ret | = status_mask ;
else
ret & = ~ status_mask ;
return i2c_smbus_write_word_swapped ( data - > client , TMP007_STATUS_MASK ,
data - > status_mask = ret ) ;
}
static int tmp007_read_event_config ( struct iio_dev * indio_dev ,
const struct iio_chan_spec * chan , enum iio_event_type type ,
enum iio_event_direction dir )
{
struct tmp007_data * data = iio_priv ( indio_dev ) ;
unsigned int mask ;
switch ( chan - > channel2 ) {
case IIO_MOD_TEMP_AMBIENT :
if ( dir = = IIO_EV_DIR_RISING )
mask = TMP007_STATUS_LHF ;
else
mask = TMP007_STATUS_LLF ;
break ;
case IIO_MOD_TEMP_OBJECT :
if ( dir = = IIO_EV_DIR_RISING )
mask = TMP007_STATUS_OHF ;
else
mask = TMP007_STATUS_OLF ;
break ;
default :
return - EINVAL ;
}
return ! ! ( data - > status_mask & mask ) ;
}
static int tmp007_read_thresh ( struct iio_dev * indio_dev ,
const struct iio_chan_spec * chan , enum iio_event_type type ,
enum iio_event_direction dir , enum iio_event_info info ,
int * val , int * val2 )
{
struct tmp007_data * data = iio_priv ( indio_dev ) ;
int ret ;
u8 reg ;
switch ( chan - > channel2 ) {
case IIO_MOD_TEMP_AMBIENT : /* LSB: 0.5 degree Celsius */
if ( dir = = IIO_EV_DIR_RISING )
reg = TMP007_TDIE_HIGH_LIMIT ;
else
reg = TMP007_TDIE_LOW_LIMIT ;
break ;
case IIO_MOD_TEMP_OBJECT :
if ( dir = = IIO_EV_DIR_RISING )
reg = TMP007_TOBJ_HIGH_LIMIT ;
else
reg = TMP007_TOBJ_LOW_LIMIT ;
break ;
default :
return - EINVAL ;
}
ret = i2c_smbus_read_word_swapped ( data - > client , reg ) ;
if ( ret < 0 )
return ret ;
/* Shift length 7 bits = 6(15:6) + 1(0.5 LSB) */
* val = sign_extend32 ( ret , 15 ) > > 7 ;
return IIO_VAL_INT ;
}
static int tmp007_write_thresh ( struct iio_dev * indio_dev ,
const struct iio_chan_spec * chan , enum iio_event_type type ,
enum iio_event_direction dir , enum iio_event_info info ,
int val , int val2 )
{
struct tmp007_data * data = iio_priv ( indio_dev ) ;
u8 reg ;
switch ( chan - > channel2 ) {
case IIO_MOD_TEMP_AMBIENT :
if ( dir = = IIO_EV_DIR_RISING )
reg = TMP007_TDIE_HIGH_LIMIT ;
else
reg = TMP007_TDIE_LOW_LIMIT ;
break ;
case IIO_MOD_TEMP_OBJECT :
if ( dir = = IIO_EV_DIR_RISING )
reg = TMP007_TOBJ_HIGH_LIMIT ;
else
reg = TMP007_TOBJ_LOW_LIMIT ;
break ;
default :
return - EINVAL ;
}
/* Full scale threshold value is +/- 256 degree Celsius */
if ( val < - 256 | | val > 255 )
return - EINVAL ;
/* Shift length 7 bits = 6(15:6) + 1(0.5 LSB) */
return i2c_smbus_write_word_swapped ( data - > client , reg , ( val < < 7 ) ) ;
}
2017-01-09 05:55:26 +03:00
static IIO_CONST_ATTR ( sampling_frequency_available , " 4 2 1 0.5 0.25 " ) ;
static struct attribute * tmp007_attributes [ ] = {
& iio_const_attr_sampling_frequency_available . dev_attr . attr ,
NULL
} ;
static const struct attribute_group tmp007_attribute_group = {
. attrs = tmp007_attributes ,
} ;
2017-01-23 18:55:16 +03:00
static const struct iio_event_spec tmp007_obj_event [ ] = {
{
. type = IIO_EV_TYPE_THRESH ,
. dir = IIO_EV_DIR_RISING ,
. mask_separate = BIT ( IIO_EV_INFO_VALUE ) |
BIT ( IIO_EV_INFO_ENABLE ) ,
} ,
{
. type = IIO_EV_TYPE_THRESH ,
. dir = IIO_EV_DIR_FALLING ,
. mask_separate = BIT ( IIO_EV_INFO_VALUE ) |
BIT ( IIO_EV_INFO_ENABLE ) ,
} ,
} ;
static const struct iio_event_spec tmp007_die_event [ ] = {
{
. type = IIO_EV_TYPE_THRESH ,
. dir = IIO_EV_DIR_RISING ,
. mask_separate = BIT ( IIO_EV_INFO_VALUE ) |
BIT ( IIO_EV_INFO_ENABLE ) ,
} ,
{
. type = IIO_EV_TYPE_THRESH ,
. dir = IIO_EV_DIR_FALLING ,
. mask_separate = BIT ( IIO_EV_INFO_VALUE ) |
BIT ( IIO_EV_INFO_ENABLE ) ,
} ,
} ;
2017-01-09 05:55:26 +03:00
static const struct iio_chan_spec tmp007_channels [ ] = {
{
. type = IIO_TEMP ,
. modified = 1 ,
. channel2 = IIO_MOD_TEMP_AMBIENT ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) |
BIT ( IIO_CHAN_INFO_SCALE ) ,
. info_mask_shared_by_all = BIT ( IIO_CHAN_INFO_SAMP_FREQ ) ,
2017-01-23 18:55:16 +03:00
. event_spec = tmp007_die_event ,
. num_event_specs = ARRAY_SIZE ( tmp007_die_event ) ,
2017-01-09 05:55:26 +03:00
} ,
{
. type = IIO_TEMP ,
. modified = 1 ,
. channel2 = IIO_MOD_TEMP_OBJECT ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) |
BIT ( IIO_CHAN_INFO_SCALE ) ,
. info_mask_shared_by_all = BIT ( IIO_CHAN_INFO_SAMP_FREQ ) ,
2017-01-23 18:55:16 +03:00
. event_spec = tmp007_obj_event ,
. num_event_specs = ARRAY_SIZE ( tmp007_obj_event ) ,
2017-01-09 05:55:26 +03:00
}
} ;
static const struct iio_info tmp007_info = {
. read_raw = tmp007_read_raw ,
. write_raw = tmp007_write_raw ,
2017-01-23 18:55:16 +03:00
. read_event_config = tmp007_read_event_config ,
. write_event_config = tmp007_write_event_config ,
. read_event_value = tmp007_read_thresh ,
. write_event_value = tmp007_write_thresh ,
2017-01-09 05:55:26 +03:00
. attrs = & tmp007_attribute_group ,
} ;
static bool tmp007_identify ( struct i2c_client * client )
{
int manf_id , dev_id ;
manf_id = i2c_smbus_read_word_swapped ( client , TMP007_MANUFACTURER_ID ) ;
if ( manf_id < 0 )
return false ;
dev_id = i2c_smbus_read_word_swapped ( client , TMP007_DEVICE_ID ) ;
if ( dev_id < 0 )
return false ;
return ( manf_id = = TMP007_MANUFACTURER_MAGIC & & dev_id = = TMP007_DEVICE_MAGIC ) ;
}
static int tmp007_probe ( struct i2c_client * client ,
const struct i2c_device_id * tmp007_id )
{
struct tmp007_data * data ;
struct iio_dev * indio_dev ;
int ret ;
if ( ! i2c_check_functionality ( client - > adapter , I2C_FUNC_SMBUS_WORD_DATA ) )
return - EOPNOTSUPP ;
if ( ! tmp007_identify ( client ) ) {
dev_err ( & client - > dev , " TMP007 not found \n " ) ;
return - ENODEV ;
}
indio_dev = devm_iio_device_alloc ( & client - > dev , sizeof ( * data ) ) ;
if ( ! indio_dev )
return - ENOMEM ;
data = iio_priv ( indio_dev ) ;
i2c_set_clientdata ( client , indio_dev ) ;
data - > client = client ;
2017-01-23 18:55:16 +03:00
mutex_init ( & data - > lock ) ;
2017-01-09 05:55:26 +03:00
indio_dev - > dev . parent = & client - > dev ;
2017-01-30 13:18:25 +03:00
indio_dev - > name = " tmp007 " ;
2017-01-09 05:55:26 +03:00
indio_dev - > modes = INDIO_DIRECT_MODE ;
indio_dev - > info = & tmp007_info ;
indio_dev - > channels = tmp007_channels ;
indio_dev - > num_channels = ARRAY_SIZE ( tmp007_channels ) ;
/*
* Set Configuration register :
* 1. Conversion ON
2017-01-23 18:55:16 +03:00
* 2. ALERT enable
2017-01-09 05:55:26 +03:00
* 3. Transient correction enable
*/
ret = i2c_smbus_read_word_swapped ( data - > client , TMP007_CONFIG ) ;
if ( ret < 0 )
return ret ;
data - > config = ret ;
2017-01-23 18:55:16 +03:00
data - > config | = ( TMP007_CONFIG_CONV_EN | TMP007_CONFIG_ALERT_EN | TMP007_CONFIG_TC_EN ) ;
2017-01-09 05:55:26 +03:00
ret = i2c_smbus_write_word_swapped ( data - > client , TMP007_CONFIG ,
data - > config ) ;
if ( ret < 0 )
return ret ;
/*
2017-01-23 18:55:16 +03:00
* Only the following flags can activate ALERT pin . Data conversion / validity flags
* flags can still be polled for getting temperature data
*
2017-01-09 05:55:26 +03:00
* Set Status Mask register :
2017-01-23 18:55:16 +03:00
* 1. Object temperature high limit enable
* 2. Object temperature low limit enable
* 3. TDIE temperature high limit enable
* 4. TDIE temperature low limit enable
2017-01-09 05:55:26 +03:00
*/
ret = i2c_smbus_read_word_swapped ( data - > client , TMP007_STATUS_MASK ) ;
if ( ret < 0 )
goto error_powerdown ;
2017-01-23 18:55:16 +03:00
data - > status_mask = ret ;
data - > status_mask | = ( TMP007_STATUS_OHF | TMP007_STATUS_OLF
| TMP007_STATUS_LHF | TMP007_STATUS_LLF ) ;
2017-01-09 05:55:26 +03:00
2017-01-23 18:55:16 +03:00
ret = i2c_smbus_write_word_swapped ( data - > client , TMP007_STATUS_MASK , data - > status_mask ) ;
2017-01-09 05:55:26 +03:00
if ( ret < 0 )
goto error_powerdown ;
2017-01-23 18:55:16 +03:00
if ( client - > irq ) {
ret = devm_request_threaded_irq ( & client - > dev , client - > irq ,
NULL , tmp007_interrupt_handler ,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT ,
tmp007_id - > name , indio_dev ) ;
if ( ret ) {
dev_err ( & client - > dev , " irq request error %d \n " , - ret ) ;
goto error_powerdown ;
}
}
2017-01-09 05:55:26 +03:00
return iio_device_register ( indio_dev ) ;
error_powerdown :
tmp007_powerdown ( data ) ;
return ret ;
}
static int tmp007_remove ( struct i2c_client * client )
{
struct iio_dev * indio_dev = i2c_get_clientdata ( client ) ;
struct tmp007_data * data = iio_priv ( indio_dev ) ;
iio_device_unregister ( indio_dev ) ;
tmp007_powerdown ( data ) ;
return 0 ;
}
# ifdef CONFIG_PM_SLEEP
static int tmp007_suspend ( struct device * dev )
{
struct tmp007_data * data = iio_priv ( i2c_get_clientdata (
to_i2c_client ( dev ) ) ) ;
return tmp007_powerdown ( data ) ;
}
static int tmp007_resume ( struct device * dev )
{
struct tmp007_data * data = iio_priv ( i2c_get_clientdata (
to_i2c_client ( dev ) ) ) ;
return i2c_smbus_write_word_swapped ( data - > client , TMP007_CONFIG ,
data - > config | TMP007_CONFIG_CONV_EN ) ;
}
# endif
static SIMPLE_DEV_PM_OPS ( tmp007_pm_ops , tmp007_suspend , tmp007_resume ) ;
static const struct of_device_id tmp007_of_match [ ] = {
{ . compatible = " ti,tmp007 " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , tmp007_of_match ) ;
static const struct i2c_device_id tmp007_id [ ] = {
{ " tmp007 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , tmp007_id ) ;
static struct i2c_driver tmp007_driver = {
. driver = {
. name = " tmp007 " ,
. of_match_table = of_match_ptr ( tmp007_of_match ) ,
. pm = & tmp007_pm_ops ,
} ,
. probe = tmp007_probe ,
. remove = tmp007_remove ,
. id_table = tmp007_id ,
} ;
module_i2c_driver ( tmp007_driver ) ;
MODULE_AUTHOR ( " Manivannan Sadhasivam <manivannanece23@gmail.com> " ) ;
MODULE_DESCRIPTION ( " TI TMP007 IR thermopile sensor driver " ) ;
MODULE_LICENSE ( " GPL " ) ;