2018-12-13 15:43:23 +01:00
// SPDX-License-Identifier: GPL-2.0
/*
* sgp30 . c - Support for Sensirion SGP Gas Sensors
*
* Copyright ( C ) 2018 Andreas Brauchli < andreas . brauchli @ sensirion . com >
*
* I2C slave address : 0x58
*
* Datasheets :
* https : //www.sensirion.com/file/datasheet_sgp30
* https : //www.sensirion.com/file/datasheet_sgpc3
*
* TODO :
* - baseline support
* - humidity compensation
* - power mode switching ( SGPC3 )
*/
# include <linux/crc8.h>
# include <linux/delay.h>
# include <linux/kthread.h>
# include <linux/module.h>
2020-09-10 18:32:31 +01:00
# include <linux/mod_devicetable.h>
2018-12-13 15:43:23 +01:00
# include <linux/mutex.h>
# include <linux/i2c.h>
# include <linux/iio/iio.h>
# include <linux/iio/sysfs.h>
# define SGP_WORD_LEN 2
# define SGP_CRC8_POLYNOMIAL 0x31
# define SGP_CRC8_INIT 0xff
# define SGP_CRC8_LEN 1
# define SGP_CMD(cmd_word) cpu_to_be16(cmd_word)
# define SGP_CMD_DURATION_US 12000
# define SGP_MEASUREMENT_DURATION_US 50000
# define SGP_CMD_LEN SGP_WORD_LEN
# define SGP_CMD_MAX_BUF_SIZE (SGP_CMD_LEN + 2 * SGP_WORD_LEN)
# define SGP_MEASUREMENT_LEN 2
# define SGP30_MEASURE_INTERVAL_HZ 1
# define SGPC3_MEASURE_INTERVAL_HZ 2
# define SGP_VERS_PRODUCT(data) ((((data)->feature_set) & 0xf000) >> 12)
# define SGP_VERS_RESERVED(data) ((((data)->feature_set) & 0x0800) >> 11)
# define SGP_VERS_GEN(data) ((((data)->feature_set) & 0x0600) >> 9)
# define SGP_VERS_ENG_BIT(data) ((((data)->feature_set) & 0x0100) >> 8)
# define SGP_VERS_MAJOR(data) ((((data)->feature_set) & 0x00e0) >> 5)
# define SGP_VERS_MINOR(data) (((data)->feature_set) & 0x001f)
DECLARE_CRC8_TABLE ( sgp_crc8_table ) ;
enum sgp_product_id {
SGP30 = 0 ,
SGPC3 ,
} ;
enum sgp30_channel_idx {
SGP30_IAQ_TVOC_IDX = 0 ,
SGP30_IAQ_CO2EQ_IDX ,
SGP30_SIG_ETOH_IDX ,
SGP30_SIG_H2_IDX ,
} ;
enum sgpc3_channel_idx {
SGPC3_IAQ_TVOC_IDX = 10 ,
SGPC3_SIG_ETOH_IDX ,
} ;
enum sgp_cmd {
SGP_CMD_IAQ_INIT = SGP_CMD ( 0x2003 ) ,
SGP_CMD_IAQ_MEASURE = SGP_CMD ( 0x2008 ) ,
SGP_CMD_GET_FEATURE_SET = SGP_CMD ( 0x202f ) ,
SGP_CMD_GET_SERIAL_ID = SGP_CMD ( 0x3682 ) ,
SGP30_CMD_MEASURE_SIGNAL = SGP_CMD ( 0x2050 ) ,
SGPC3_CMD_MEASURE_RAW = SGP_CMD ( 0x2046 ) ,
} ;
struct sgp_version {
u8 major ;
u8 minor ;
} ;
struct sgp_crc_word {
__be16 value ;
u8 crc8 ;
} __attribute__ ( ( __packed__ ) ) ;
union sgp_reading {
u8 start ;
struct sgp_crc_word raw_words [ 4 ] ;
} ;
enum _iaq_buffer_state {
IAQ_BUFFER_EMPTY = 0 ,
IAQ_BUFFER_DEFAULT_VALS ,
IAQ_BUFFER_VALID ,
} ;
struct sgp_data {
struct i2c_client * client ;
struct task_struct * iaq_thread ;
struct mutex data_lock ;
unsigned long iaq_init_start_jiffies ;
unsigned long iaq_defval_skip_jiffies ;
u16 product_id ;
u16 feature_set ;
unsigned long measure_interval_jiffies ;
enum sgp_cmd iaq_init_cmd ;
enum sgp_cmd measure_iaq_cmd ;
enum sgp_cmd measure_gas_signals_cmd ;
union sgp_reading buffer ;
union sgp_reading iaq_buffer ;
enum _iaq_buffer_state iaq_buffer_state ;
} ;
struct sgp_device {
const struct iio_chan_spec * channels ;
int num_channels ;
} ;
static const struct sgp_version supported_versions_sgp30 [ ] = {
{
. major = 1 ,
. minor = 0 ,
} ,
} ;
static const struct sgp_version supported_versions_sgpc3 [ ] = {
{
. major = 0 ,
. minor = 4 ,
} ,
} ;
static const struct iio_chan_spec sgp30_channels [ ] = {
{
. type = IIO_CONCENTRATION ,
. channel2 = IIO_MOD_VOC ,
. modified = 1 ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_PROCESSED ) ,
. address = SGP30_IAQ_TVOC_IDX ,
} ,
{
. type = IIO_CONCENTRATION ,
. channel2 = IIO_MOD_CO2 ,
. modified = 1 ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_PROCESSED ) ,
. address = SGP30_IAQ_CO2EQ_IDX ,
} ,
{
. type = IIO_CONCENTRATION ,
. channel2 = IIO_MOD_ETHANOL ,
. modified = 1 ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) ,
. address = SGP30_SIG_ETOH_IDX ,
} ,
{
. type = IIO_CONCENTRATION ,
. channel2 = IIO_MOD_H2 ,
. modified = 1 ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) ,
. address = SGP30_SIG_H2_IDX ,
} ,
} ;
static const struct iio_chan_spec sgpc3_channels [ ] = {
{
. type = IIO_CONCENTRATION ,
. channel2 = IIO_MOD_VOC ,
. modified = 1 ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_PROCESSED ) ,
. address = SGPC3_IAQ_TVOC_IDX ,
} ,
{
. type = IIO_CONCENTRATION ,
. channel2 = IIO_MOD_ETHANOL ,
. modified = 1 ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) ,
. address = SGPC3_SIG_ETOH_IDX ,
} ,
} ;
static const struct sgp_device sgp_devices [ ] = {
[ SGP30 ] = {
. channels = sgp30_channels ,
. num_channels = ARRAY_SIZE ( sgp30_channels ) ,
} ,
[ SGPC3 ] = {
. channels = sgpc3_channels ,
. num_channels = ARRAY_SIZE ( sgpc3_channels ) ,
} ,
} ;
/**
* sgp_verify_buffer ( ) - verify the checksums of the data buffer words
*
* @ data : SGP data
* @ buf : Raw data buffer
* @ word_count : Num data words stored in the buffer , excluding CRC bytes
*
* Return : 0 on success , negative error otherwise .
*/
static int sgp_verify_buffer ( const struct sgp_data * data ,
union sgp_reading * buf , size_t word_count )
{
size_t size = word_count * ( SGP_WORD_LEN + SGP_CRC8_LEN ) ;
int i ;
u8 crc ;
u8 * data_buf = & buf - > start ;
for ( i = 0 ; i < size ; i + = SGP_WORD_LEN + SGP_CRC8_LEN ) {
crc = crc8 ( sgp_crc8_table , & data_buf [ i ] , SGP_WORD_LEN ,
SGP_CRC8_INIT ) ;
if ( crc ! = data_buf [ i + SGP_WORD_LEN ] ) {
dev_err ( & data - > client - > dev , " CRC error \n " ) ;
return - EIO ;
}
}
return 0 ;
}
/**
* sgp_read_cmd ( ) - reads data from sensor after issuing a command
* The caller must hold data - > data_lock for the duration of the call .
* @ data : SGP data
* @ cmd : SGP Command to issue
* @ buf : Raw data buffer to use
* @ word_count : Num words to read , excluding CRC bytes
2020-07-16 14:59:03 +01:00
* @ duration_us : Time taken to sensor to take a reading and data to be ready .
2018-12-13 15:43:23 +01:00
*
* Return : 0 on success , negative error otherwise .
*/
static int sgp_read_cmd ( struct sgp_data * data , enum sgp_cmd cmd ,
union sgp_reading * buf , size_t word_count ,
unsigned long duration_us )
{
int ret ;
struct i2c_client * client = data - > client ;
size_t size = word_count * ( SGP_WORD_LEN + SGP_CRC8_LEN ) ;
u8 * data_buf ;
ret = i2c_master_send ( client , ( const char * ) & cmd , SGP_CMD_LEN ) ;
if ( ret ! = SGP_CMD_LEN )
return - EIO ;
usleep_range ( duration_us , duration_us + 1000 ) ;
if ( word_count = = 0 )
return 0 ;
data_buf = & buf - > start ;
ret = i2c_master_recv ( client , data_buf , size ) ;
if ( ret < 0 )
return ret ;
if ( ret ! = size )
return - EIO ;
return sgp_verify_buffer ( data , buf , word_count ) ;
}
/**
* sgp_measure_iaq ( ) - measure and retrieve IAQ values from sensor
* The caller must hold data - > data_lock for the duration of the call .
* @ data : SGP data
*
* Return : 0 on success , - EBUSY on default values , negative error
* otherwise .
*/
static int sgp_measure_iaq ( struct sgp_data * data )
{
int ret ;
/* data contains default values */
bool default_vals = ! time_after ( jiffies , data - > iaq_init_start_jiffies +
data - > iaq_defval_skip_jiffies ) ;
ret = sgp_read_cmd ( data , data - > measure_iaq_cmd , & data - > iaq_buffer ,
SGP_MEASUREMENT_LEN , SGP_MEASUREMENT_DURATION_US ) ;
if ( ret < 0 )
return ret ;
data - > iaq_buffer_state = IAQ_BUFFER_DEFAULT_VALS ;
if ( default_vals )
return - EBUSY ;
data - > iaq_buffer_state = IAQ_BUFFER_VALID ;
return 0 ;
}
static void sgp_iaq_thread_sleep_until ( const struct sgp_data * data ,
unsigned long sleep_jiffies )
{
const long IAQ_POLL = 50000 ;
while ( ! time_after ( jiffies , sleep_jiffies ) ) {
usleep_range ( IAQ_POLL , IAQ_POLL + 10000 ) ;
if ( kthread_should_stop ( ) | | data - > iaq_init_start_jiffies = = 0 )
return ;
}
}
static int sgp_iaq_threadfn ( void * p )
{
struct sgp_data * data = ( struct sgp_data * ) p ;
unsigned long next_update_jiffies ;
int ret ;
while ( ! kthread_should_stop ( ) ) {
mutex_lock ( & data - > data_lock ) ;
if ( data - > iaq_init_start_jiffies = = 0 ) {
ret = sgp_read_cmd ( data , data - > iaq_init_cmd , NULL , 0 ,
SGP_CMD_DURATION_US ) ;
if ( ret < 0 )
goto unlock_sleep_continue ;
data - > iaq_init_start_jiffies = jiffies ;
}
ret = sgp_measure_iaq ( data ) ;
if ( ret & & ret ! = - EBUSY ) {
dev_warn ( & data - > client - > dev ,
" IAQ measurement error [%d] \n " , ret ) ;
}
unlock_sleep_continue :
next_update_jiffies = jiffies + data - > measure_interval_jiffies ;
mutex_unlock ( & data - > data_lock ) ;
sgp_iaq_thread_sleep_until ( data , next_update_jiffies ) ;
}
return 0 ;
}
static int sgp_read_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan , int * val ,
int * val2 , long mask )
{
struct sgp_data * data = iio_priv ( indio_dev ) ;
struct sgp_crc_word * words ;
int ret ;
switch ( mask ) {
case IIO_CHAN_INFO_PROCESSED :
mutex_lock ( & data - > data_lock ) ;
if ( data - > iaq_buffer_state ! = IAQ_BUFFER_VALID ) {
mutex_unlock ( & data - > data_lock ) ;
return - EBUSY ;
}
words = data - > iaq_buffer . raw_words ;
switch ( chan - > address ) {
case SGP30_IAQ_TVOC_IDX :
case SGPC3_IAQ_TVOC_IDX :
* val = 0 ;
* val2 = be16_to_cpu ( words [ 1 ] . value ) ;
ret = IIO_VAL_INT_PLUS_NANO ;
break ;
case SGP30_IAQ_CO2EQ_IDX :
* val = 0 ;
* val2 = be16_to_cpu ( words [ 0 ] . value ) ;
ret = IIO_VAL_INT_PLUS_MICRO ;
break ;
default :
ret = - EINVAL ;
break ;
}
mutex_unlock ( & data - > data_lock ) ;
break ;
case IIO_CHAN_INFO_RAW :
mutex_lock ( & data - > data_lock ) ;
if ( chan - > address = = SGPC3_SIG_ETOH_IDX ) {
if ( data - > iaq_buffer_state = = IAQ_BUFFER_EMPTY )
ret = - EBUSY ;
else
ret = 0 ;
words = data - > iaq_buffer . raw_words ;
} else {
ret = sgp_read_cmd ( data , data - > measure_gas_signals_cmd ,
& data - > buffer , SGP_MEASUREMENT_LEN ,
SGP_MEASUREMENT_DURATION_US ) ;
words = data - > buffer . raw_words ;
}
if ( ret ) {
mutex_unlock ( & data - > data_lock ) ;
return ret ;
}
switch ( chan - > address ) {
case SGP30_SIG_ETOH_IDX :
* val = be16_to_cpu ( words [ 1 ] . value ) ;
ret = IIO_VAL_INT ;
break ;
case SGPC3_SIG_ETOH_IDX :
case SGP30_SIG_H2_IDX :
* val = be16_to_cpu ( words [ 0 ] . value ) ;
ret = IIO_VAL_INT ;
break ;
default :
ret = - EINVAL ;
break ;
}
mutex_unlock ( & data - > data_lock ) ;
break ;
default :
return - EINVAL ;
}
return ret ;
}
static int sgp_check_compat ( struct sgp_data * data ,
unsigned int product_id )
{
2020-09-10 18:32:30 +01:00
struct device * dev = & data - > client - > dev ;
2018-12-13 15:43:23 +01:00
const struct sgp_version * supported_versions ;
u16 ix , num_fs ;
u16 product , generation , major , minor ;
/* driver does not match product */
generation = SGP_VERS_GEN ( data ) ;
if ( generation ! = 0 ) {
2020-09-10 18:32:30 +01:00
dev_err ( dev ,
2018-12-13 15:43:23 +01:00
" incompatible product generation %d != 0 " , generation ) ;
return - ENODEV ;
}
product = SGP_VERS_PRODUCT ( data ) ;
if ( product ! = product_id ) {
2020-09-10 18:32:30 +01:00
dev_err ( dev , " sensor reports a different product: 0x%04hx \n " ,
2018-12-13 15:43:23 +01:00
product ) ;
return - ENODEV ;
}
if ( SGP_VERS_RESERVED ( data ) )
2020-09-10 18:32:30 +01:00
dev_warn ( dev , " reserved bit is set \n " ) ;
2018-12-13 15:43:23 +01:00
/* engineering samples are not supported: no interface guarantees */
if ( SGP_VERS_ENG_BIT ( data ) )
return - ENODEV ;
switch ( product ) {
case SGP30 :
supported_versions = supported_versions_sgp30 ;
num_fs = ARRAY_SIZE ( supported_versions_sgp30 ) ;
break ;
case SGPC3 :
supported_versions = supported_versions_sgpc3 ;
num_fs = ARRAY_SIZE ( supported_versions_sgpc3 ) ;
break ;
default :
return - ENODEV ;
}
major = SGP_VERS_MAJOR ( data ) ;
minor = SGP_VERS_MINOR ( data ) ;
for ( ix = 0 ; ix < num_fs ; ix + + ) {
if ( major = = supported_versions [ ix ] . major & &
minor > = supported_versions [ ix ] . minor )
return 0 ;
}
2020-09-10 18:32:30 +01:00
dev_err ( dev , " unsupported sgp version: %d.%d \n " , major , minor ) ;
2018-12-13 15:43:23 +01:00
return - ENODEV ;
}
static void sgp_init ( struct sgp_data * data )
{
data - > iaq_init_cmd = SGP_CMD_IAQ_INIT ;
data - > iaq_init_start_jiffies = 0 ;
data - > iaq_buffer_state = IAQ_BUFFER_EMPTY ;
switch ( SGP_VERS_PRODUCT ( data ) ) {
case SGP30 :
data - > measure_interval_jiffies = SGP30_MEASURE_INTERVAL_HZ * HZ ;
data - > measure_iaq_cmd = SGP_CMD_IAQ_MEASURE ;
data - > measure_gas_signals_cmd = SGP30_CMD_MEASURE_SIGNAL ;
data - > product_id = SGP30 ;
data - > iaq_defval_skip_jiffies = 15 * HZ ;
break ;
case SGPC3 :
data - > measure_interval_jiffies = SGPC3_MEASURE_INTERVAL_HZ * HZ ;
data - > measure_iaq_cmd = SGPC3_CMD_MEASURE_RAW ;
data - > measure_gas_signals_cmd = SGPC3_CMD_MEASURE_RAW ;
data - > product_id = SGPC3 ;
data - > iaq_defval_skip_jiffies =
43 * data - > measure_interval_jiffies ;
break ;
2019-10-13 17:54:12 +01:00
}
2018-12-13 15:43:23 +01:00
}
static const struct iio_info sgp_info = {
. read_raw = sgp_read_raw ,
} ;
static const struct of_device_id sgp_dt_ids [ ] = {
{ . compatible = " sensirion,sgp30 " , . data = ( void * ) SGP30 } ,
{ . compatible = " sensirion,sgpc3 " , . data = ( void * ) SGPC3 } ,
{ }
} ;
static int sgp_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
2020-09-10 18:32:30 +01:00
struct device * dev = & client - > dev ;
2018-12-13 15:43:23 +01:00
struct iio_dev * indio_dev ;
struct sgp_data * data ;
unsigned long product_id ;
int ret ;
2020-09-10 18:32:30 +01:00
indio_dev = devm_iio_device_alloc ( dev , sizeof ( * data ) ) ;
2018-12-13 15:43:23 +01:00
if ( ! indio_dev )
return - ENOMEM ;
2020-09-10 18:32:31 +01:00
if ( dev_fwnode ( dev ) )
product_id = ( unsigned long ) device_get_match_data ( dev ) ;
2018-12-13 15:43:23 +01:00
else
product_id = id - > driver_data ;
data = iio_priv ( indio_dev ) ;
i2c_set_clientdata ( client , indio_dev ) ;
data - > client = client ;
crc8_populate_msb ( sgp_crc8_table , SGP_CRC8_POLYNOMIAL ) ;
mutex_init ( & data - > data_lock ) ;
/* get feature set version and write it to client data */
ret = sgp_read_cmd ( data , SGP_CMD_GET_FEATURE_SET , & data - > buffer , 1 ,
SGP_CMD_DURATION_US ) ;
if ( ret < 0 )
return ret ;
data - > feature_set = be16_to_cpu ( data - > buffer . raw_words [ 0 ] . value ) ;
ret = sgp_check_compat ( data , product_id ) ;
if ( ret )
return ret ;
indio_dev - > info = & sgp_info ;
indio_dev - > name = id - > name ;
indio_dev - > modes = INDIO_DIRECT_MODE ;
indio_dev - > channels = sgp_devices [ product_id ] . channels ;
indio_dev - > num_channels = sgp_devices [ product_id ] . num_channels ;
sgp_init ( data ) ;
2020-09-10 18:32:30 +01:00
ret = devm_iio_device_register ( dev , indio_dev ) ;
2018-12-13 15:43:23 +01:00
if ( ret ) {
2020-09-10 18:32:30 +01:00
dev_err ( dev , " failed to register iio device \n " ) ;
2018-12-13 15:43:23 +01:00
return ret ;
}
data - > iaq_thread = kthread_run ( sgp_iaq_threadfn , data ,
" %s-iaq " , data - > client - > name ) ;
return 0 ;
}
static int sgp_remove ( struct i2c_client * client )
{
struct iio_dev * indio_dev = i2c_get_clientdata ( client ) ;
struct sgp_data * data = iio_priv ( indio_dev ) ;
if ( data - > iaq_thread )
kthread_stop ( data - > iaq_thread ) ;
return 0 ;
}
static const struct i2c_device_id sgp_id [ ] = {
{ " sgp30 " , SGP30 } ,
{ " sgpc3 " , SGPC3 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , sgp_id ) ;
MODULE_DEVICE_TABLE ( of , sgp_dt_ids ) ;
static struct i2c_driver sgp_driver = {
. driver = {
. name = " sgp30 " ,
2020-09-10 18:32:31 +01:00
. of_match_table = sgp_dt_ids ,
2018-12-13 15:43:23 +01:00
} ,
. probe = sgp_probe ,
. remove = sgp_remove ,
. id_table = sgp_id ,
} ;
module_i2c_driver ( sgp_driver ) ;
MODULE_AUTHOR ( " Andreas Brauchli <andreas.brauchli@sensirion.com> " ) ;
MODULE_AUTHOR ( " Pascal Sachs <pascal.sachs@sensirion.com> " ) ;
MODULE_DESCRIPTION ( " Sensirion SGP gas sensors " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;