2018-02-17 21:36:46 -08:00
// SPDX-License-Identifier: GPL-2.0+
2015-09-13 20:26:14 -07:00
/*
* vz89x . c - Support for SGX Sensortech MiCS VZ89X VOC sensors
*
2018-02-17 21:36:46 -08:00
* Copyright ( C ) 2015 - 2018
* Author : Matt Ranostay < matt . ranostay @ konsulko . com >
2015-09-13 20:26:14 -07:00
*/
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/init.h>
# include <linux/i2c.h>
2020-09-10 18:32:33 +01:00
# include <linux/mod_devicetable.h>
2015-09-13 20:26:14 -07:00
# include <linux/iio/iio.h>
# include <linux/iio/sysfs.h>
# define VZ89X_REG_MEASUREMENT 0x09
2016-08-24 23:44:47 -07:00
# define VZ89X_REG_MEASUREMENT_RD_SIZE 6
# define VZ89X_REG_MEASUREMENT_WR_SIZE 3
2015-09-13 20:26:14 -07:00
# define VZ89X_VOC_CO2_IDX 0
# define VZ89X_VOC_SHORT_IDX 1
# define VZ89X_VOC_TVOC_IDX 2
# define VZ89X_VOC_RESISTANCE_IDX 3
2016-08-24 23:44:48 -07:00
# define VZ89TE_REG_MEASUREMENT 0x0c
# define VZ89TE_REG_MEASUREMENT_RD_SIZE 7
# define VZ89TE_REG_MEASUREMENT_WR_SIZE 6
# define VZ89TE_VOC_TVOC_IDX 0
# define VZ89TE_VOC_CO2_IDX 1
# define VZ89TE_VOC_RESISTANCE_IDX 2
2016-08-24 23:44:47 -07:00
enum {
VZ89X ,
2016-08-24 23:44:48 -07:00
VZ89TE ,
2016-08-24 23:44:47 -07:00
} ;
struct vz89x_chip_data ;
2015-09-13 20:26:14 -07:00
struct vz89x_data {
struct i2c_client * client ;
2016-08-24 23:44:47 -07:00
const struct vz89x_chip_data * chip ;
2015-09-13 20:26:14 -07:00
struct mutex lock ;
2015-12-01 21:47:20 -08:00
int ( * xfer ) ( struct vz89x_data * data , u8 cmd ) ;
2015-09-13 20:26:14 -07:00
2016-08-24 23:44:49 -07:00
bool is_valid ;
2015-12-01 21:47:20 -08:00
unsigned long last_update ;
2016-08-24 23:44:48 -07:00
u8 buffer [ VZ89TE_REG_MEASUREMENT_RD_SIZE ] ;
2016-08-24 23:44:47 -07:00
} ;
struct vz89x_chip_data {
bool ( * valid ) ( struct vz89x_data * data ) ;
const struct iio_chan_spec * channels ;
u8 num_channels ;
u8 cmd ;
u8 read_size ;
u8 write_size ;
2015-09-13 20:26:14 -07:00
} ;
static const struct iio_chan_spec vz89x_channels [ ] = {
{
. type = IIO_CONCENTRATION ,
. channel2 = IIO_MOD_CO2 ,
. modified = 1 ,
. info_mask_separate =
BIT ( IIO_CHAN_INFO_OFFSET ) | BIT ( IIO_CHAN_INFO_RAW ) ,
. address = VZ89X_VOC_CO2_IDX ,
} ,
{
. type = IIO_CONCENTRATION ,
. channel2 = IIO_MOD_VOC ,
. modified = 1 ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) ,
. address = VZ89X_VOC_SHORT_IDX ,
. extend_name = " short " ,
} ,
{
. type = IIO_CONCENTRATION ,
. channel2 = IIO_MOD_VOC ,
. modified = 1 ,
. info_mask_separate =
BIT ( IIO_CHAN_INFO_OFFSET ) | BIT ( IIO_CHAN_INFO_RAW ) ,
. address = VZ89X_VOC_TVOC_IDX ,
} ,
{
. type = IIO_RESISTANCE ,
. info_mask_separate =
BIT ( IIO_CHAN_INFO_RAW ) | BIT ( IIO_CHAN_INFO_SCALE ) ,
. address = VZ89X_VOC_RESISTANCE_IDX ,
2016-08-24 23:44:48 -07:00
. scan_index = - 1 ,
. scan_type = {
. endianness = IIO_LE ,
} ,
} ,
} ;
static const struct iio_chan_spec vz89te_channels [ ] = {
{
. type = IIO_CONCENTRATION ,
. channel2 = IIO_MOD_VOC ,
. modified = 1 ,
. info_mask_separate =
BIT ( IIO_CHAN_INFO_OFFSET ) | BIT ( IIO_CHAN_INFO_RAW ) ,
. address = VZ89TE_VOC_TVOC_IDX ,
} ,
{
. type = IIO_CONCENTRATION ,
. channel2 = IIO_MOD_CO2 ,
. modified = 1 ,
. info_mask_separate =
BIT ( IIO_CHAN_INFO_OFFSET ) | BIT ( IIO_CHAN_INFO_RAW ) ,
. address = VZ89TE_VOC_CO2_IDX ,
} ,
{
. type = IIO_RESISTANCE ,
. info_mask_separate =
BIT ( IIO_CHAN_INFO_RAW ) | BIT ( IIO_CHAN_INFO_SCALE ) ,
. address = VZ89TE_VOC_RESISTANCE_IDX ,
. scan_index = - 1 ,
. scan_type = {
. endianness = IIO_BE ,
} ,
2015-09-13 20:26:14 -07:00
} ,
} ;
static IIO_CONST_ATTR ( in_concentration_co2_scale , " 0.00000698689 " ) ;
static IIO_CONST_ATTR ( in_concentration_voc_scale , " 0.00000000436681223 " ) ;
static struct attribute * vz89x_attributes [ ] = {
& iio_const_attr_in_concentration_co2_scale . dev_attr . attr ,
& iio_const_attr_in_concentration_voc_scale . dev_attr . attr ,
NULL ,
} ;
static const struct attribute_group vz89x_attrs_group = {
. attrs = vz89x_attributes ,
} ;
2015-09-22 21:25:09 -07:00
/*
* Chipset sometime updates in the middle of a reading causing it to reset the
* data pointer , and causing invalid reading of previous data .
* We can check for this by reading MSB of the resistance reading that is
* always zero , and by also confirming the VOC_short isn ' t zero .
*/
2016-08-24 23:44:47 -07:00
static bool vz89x_measurement_is_valid ( struct vz89x_data * data )
2015-09-22 21:25:09 -07:00
{
if ( data - > buffer [ VZ89X_VOC_SHORT_IDX ] = = 0 )
2016-09-01 08:38:38 +08:00
return true ;
2015-09-22 21:25:09 -07:00
2016-08-24 23:44:47 -07:00
return ! ! ( data - > buffer [ data - > chip - > read_size - 1 ] > 0 ) ;
2015-09-22 21:25:09 -07:00
}
2016-08-24 23:44:48 -07:00
/* VZ89TE device has a modified CRC-8 two complement check */
static bool vz89te_measurement_is_valid ( struct vz89x_data * data )
{
u8 crc = 0 ;
int i , sum = 0 ;
for ( i = 0 ; i < ( data - > chip - > read_size - 1 ) ; i + + ) {
sum = crc + data - > buffer [ i ] ;
crc = sum ;
crc + = sum / 256 ;
}
return ! ( ( 0xff - crc ) = = data - > buffer [ data - > chip - > read_size - 1 ] ) ;
}
2015-12-01 21:47:20 -08:00
static int vz89x_i2c_xfer ( struct vz89x_data * data , u8 cmd )
2015-09-13 20:26:14 -07:00
{
2016-08-24 23:44:47 -07:00
const struct vz89x_chip_data * chip = data - > chip ;
2015-12-01 21:47:20 -08:00
struct i2c_client * client = data - > client ;
struct i2c_msg msg [ 2 ] ;
2015-09-13 20:26:14 -07:00
int ret ;
2016-08-24 23:44:48 -07:00
u8 buf [ 6 ] = { cmd , 0 , 0 , 0 , 0 , 0xf3 } ;
2015-09-13 20:26:14 -07:00
2015-12-01 21:47:20 -08:00
msg [ 0 ] . addr = client - > addr ;
msg [ 0 ] . flags = client - > flags ;
2016-08-24 23:44:47 -07:00
msg [ 0 ] . len = chip - > write_size ;
2015-12-01 21:47:20 -08:00
msg [ 0 ] . buf = ( char * ) & buf ;
msg [ 1 ] . addr = client - > addr ;
msg [ 1 ] . flags = client - > flags | I2C_M_RD ;
2016-08-24 23:44:47 -07:00
msg [ 1 ] . len = chip - > read_size ;
2015-12-01 21:47:20 -08:00
msg [ 1 ] . buf = ( char * ) & data - > buffer ;
ret = i2c_transfer ( client - > adapter , msg , 2 ) ;
2015-09-13 20:26:14 -07:00
2015-12-01 21:47:20 -08:00
return ( ret = = 2 ) ? 0 : ret ;
}
static int vz89x_smbus_xfer ( struct vz89x_data * data , u8 cmd )
{
struct i2c_client * client = data - > client ;
int ret ;
int i ;
ret = i2c_smbus_write_word_data ( client , cmd , 0 ) ;
2015-09-13 20:26:14 -07:00
if ( ret < 0 )
return ret ;
2016-08-24 23:44:47 -07:00
for ( i = 0 ; i < data - > chip - > read_size ; i + + ) {
2015-12-01 21:47:20 -08:00
ret = i2c_smbus_read_byte ( client ) ;
2015-09-13 20:26:14 -07:00
if ( ret < 0 )
return ret ;
data - > buffer [ i ] = ret ;
}
2015-12-01 21:47:20 -08:00
return 0 ;
}
static int vz89x_get_measurement ( struct vz89x_data * data )
{
2016-08-24 23:44:47 -07:00
const struct vz89x_chip_data * chip = data - > chip ;
2015-12-01 21:47:20 -08:00
int ret ;
/* sensor can only be polled once a second max per datasheet */
if ( ! time_after ( jiffies , data - > last_update + HZ ) )
2016-08-24 23:44:49 -07:00
return data - > is_valid ? 0 : - EAGAIN ;
data - > is_valid = false ;
data - > last_update = jiffies ;
2015-12-01 21:47:20 -08:00
2016-08-24 23:44:47 -07:00
ret = data - > xfer ( data , chip - > cmd ) ;
2015-12-01 21:47:20 -08:00
if ( ret < 0 )
return ret ;
2016-08-24 23:44:47 -07:00
ret = chip - > valid ( data ) ;
2015-09-22 21:25:09 -07:00
if ( ret )
return - EAGAIN ;
2016-08-24 23:44:49 -07:00
data - > is_valid = true ;
2015-09-13 20:26:14 -07:00
return 0 ;
}
2016-08-24 23:44:48 -07:00
static int vz89x_get_resistance_reading ( struct vz89x_data * data ,
struct iio_chan_spec const * chan ,
int * val )
2015-09-13 20:26:14 -07:00
{
2021-12-09 17:17:30 +01:00
u8 * tmp = & data - > buffer [ chan - > address ] ;
2015-09-13 20:26:14 -07:00
2016-08-24 23:44:48 -07:00
switch ( chan - > scan_type . endianness ) {
case IIO_LE :
* val = le32_to_cpup ( ( __le32 * ) tmp ) & GENMASK ( 23 , 0 ) ;
break ;
case IIO_BE :
* val = be32_to_cpup ( ( __be32 * ) tmp ) > > 8 ;
break ;
default :
return - EINVAL ;
}
return 0 ;
2015-09-13 20:26:14 -07:00
}
static int vz89x_read_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan , int * val ,
int * val2 , long mask )
{
struct vz89x_data * data = iio_priv ( indio_dev ) ;
int ret = - EINVAL ;
switch ( mask ) {
case IIO_CHAN_INFO_RAW :
mutex_lock ( & data - > lock ) ;
ret = vz89x_get_measurement ( data ) ;
mutex_unlock ( & data - > lock ) ;
if ( ret )
return ret ;
2016-08-24 23:44:48 -07:00
switch ( chan - > type ) {
case IIO_CONCENTRATION :
2015-09-13 20:26:14 -07:00
* val = data - > buffer [ chan - > address ] ;
return IIO_VAL_INT ;
2016-08-24 23:44:48 -07:00
case IIO_RESISTANCE :
ret = vz89x_get_resistance_reading ( data , chan , val ) ;
if ( ! ret )
return IIO_VAL_INT ;
break ;
2015-09-13 20:26:14 -07:00
default :
return - EINVAL ;
}
break ;
case IIO_CHAN_INFO_SCALE :
switch ( chan - > type ) {
case IIO_RESISTANCE :
* val = 10 ;
return IIO_VAL_INT ;
default :
return - EINVAL ;
}
break ;
case IIO_CHAN_INFO_OFFSET :
2016-08-24 23:44:48 -07:00
switch ( chan - > channel2 ) {
case IIO_MOD_CO2 :
2015-09-13 20:26:14 -07:00
* val = 44 ;
* val2 = 250000 ;
return IIO_VAL_INT_PLUS_MICRO ;
2016-08-24 23:44:48 -07:00
case IIO_MOD_VOC :
2015-09-13 20:26:14 -07:00
* val = - 13 ;
return IIO_VAL_INT ;
default :
return - EINVAL ;
}
}
return ret ;
}
static const struct iio_info vz89x_info = {
. attrs = & vz89x_attrs_group ,
. read_raw = vz89x_read_raw ,
} ;
2016-08-24 23:44:47 -07:00
static const struct vz89x_chip_data vz89x_chips [ ] = {
{
. valid = vz89x_measurement_is_valid ,
. cmd = VZ89X_REG_MEASUREMENT ,
. read_size = VZ89X_REG_MEASUREMENT_RD_SIZE ,
. write_size = VZ89X_REG_MEASUREMENT_WR_SIZE ,
. channels = vz89x_channels ,
. num_channels = ARRAY_SIZE ( vz89x_channels ) ,
} ,
2016-08-24 23:44:48 -07:00
{
. valid = vz89te_measurement_is_valid ,
. cmd = VZ89TE_REG_MEASUREMENT ,
. read_size = VZ89TE_REG_MEASUREMENT_RD_SIZE ,
. write_size = VZ89TE_REG_MEASUREMENT_WR_SIZE ,
. channels = vz89te_channels ,
. num_channels = ARRAY_SIZE ( vz89te_channels ) ,
} ,
2016-08-24 23:44:47 -07:00
} ;
static const struct of_device_id vz89x_dt_ids [ ] = {
{ . compatible = " sgx,vz89x " , . data = ( void * ) VZ89X } ,
2016-08-24 23:44:48 -07:00
{ . compatible = " sgx,vz89te " , . data = ( void * ) VZ89TE } ,
2016-08-24 23:44:47 -07:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , vz89x_dt_ids ) ;
2022-11-18 23:36:55 +01:00
static int vz89x_probe ( struct i2c_client * client )
2015-09-13 20:26:14 -07:00
{
2022-11-18 23:36:55 +01:00
const struct i2c_device_id * id = i2c_client_get_device_id ( client ) ;
2020-09-10 18:32:32 +01:00
struct device * dev = & client - > dev ;
2015-09-13 20:26:14 -07:00
struct iio_dev * indio_dev ;
struct vz89x_data * data ;
2016-08-24 23:44:47 -07:00
int chip_id ;
2015-09-13 20:26:14 -07:00
2020-09-10 18:32:32 +01:00
indio_dev = devm_iio_device_alloc ( dev , sizeof ( * data ) ) ;
2015-09-13 20:26:14 -07:00
if ( ! indio_dev )
return - ENOMEM ;
data = iio_priv ( indio_dev ) ;
2015-12-01 21:47:20 -08:00
if ( i2c_check_functionality ( client - > adapter , I2C_FUNC_I2C ) )
data - > xfer = vz89x_i2c_xfer ;
else if ( i2c_check_functionality ( client - > adapter ,
I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE ) )
data - > xfer = vz89x_smbus_xfer ;
else
2016-02-26 22:13:49 -08:00
return - EOPNOTSUPP ;
2015-12-01 21:47:20 -08:00
2020-09-10 18:32:33 +01:00
if ( ! dev_fwnode ( dev ) )
2016-08-24 23:44:47 -07:00
chip_id = id - > driver_data ;
else
2020-09-10 18:32:33 +01:00
chip_id = ( unsigned long ) device_get_match_data ( dev ) ;
2016-08-24 23:44:47 -07:00
2015-09-13 20:26:14 -07:00
i2c_set_clientdata ( client , indio_dev ) ;
data - > client = client ;
2016-08-24 23:44:47 -07:00
data - > chip = & vz89x_chips [ chip_id ] ;
2015-09-13 20:26:14 -07:00
data - > last_update = jiffies - HZ ;
mutex_init ( & data - > lock ) ;
2017-03-30 18:11:22 +05:30
indio_dev - > info = & vz89x_info ;
2020-09-10 18:32:32 +01:00
indio_dev - > name = dev_name ( dev ) ;
2015-09-13 20:26:14 -07:00
indio_dev - > modes = INDIO_DIRECT_MODE ;
2016-08-24 23:44:47 -07:00
indio_dev - > channels = data - > chip - > channels ;
indio_dev - > num_channels = data - > chip - > num_channels ;
2015-09-13 20:26:14 -07:00
2020-09-10 18:32:32 +01:00
return devm_iio_device_register ( dev , indio_dev ) ;
2015-09-13 20:26:14 -07:00
}
static const struct i2c_device_id vz89x_id [ ] = {
2016-08-24 23:44:47 -07:00
{ " vz89x " , VZ89X } ,
2016-08-24 23:44:48 -07:00
{ " vz89te " , VZ89TE } ,
2015-09-13 20:26:14 -07:00
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , vz89x_id ) ;
static struct i2c_driver vz89x_driver = {
. driver = {
. name = " vz89x " ,
2020-09-10 18:32:33 +01:00
. of_match_table = vz89x_dt_ids ,
2015-09-13 20:26:14 -07:00
} ,
2022-11-18 23:36:55 +01:00
. probe_new = vz89x_probe ,
2015-09-13 20:26:14 -07:00
. id_table = vz89x_id ,
} ;
module_i2c_driver ( vz89x_driver ) ;
2018-02-17 21:36:46 -08:00
MODULE_AUTHOR ( " Matt Ranostay <matt.ranostay@konsulko.com> " ) ;
2015-09-13 20:26:14 -07:00
MODULE_DESCRIPTION ( " SGX Sensortech MiCS VZ89X VOC sensors " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;