2021-10-08 12:17:05 +02:00
// SPDX-License-Identifier: GPL-2.0
/*
* Sensirion SCD4X carbon dioxide sensor i2c driver
*
* Copyright ( C ) 2021 Protonic Holland
* Author : Roan van Dijk < roan @ protonic . nl >
*
* I2C slave address : 0x62
*
* Datasheets :
* https : //www.sensirion.com/file/datasheet_scd4x
*/
# include <asm/unaligned.h>
# include <linux/crc8.h>
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/i2c.h>
# include <linux/iio/buffer.h>
# include <linux/iio/iio.h>
# include <linux/iio/sysfs.h>
# include <linux/iio/trigger.h>
# include <linux/iio/trigger_consumer.h>
# include <linux/iio/triggered_buffer.h>
# include <linux/iio/types.h>
# include <linux/kernel.h>
# include <linux/mutex.h>
# include <linux/string.h>
# include <linux/sysfs.h>
# include <linux/types.h>
# define SCD4X_CRC8_POLYNOMIAL 0x31
# define SCD4X_TIMEOUT_ERR 1000
# define SCD4X_READ_BUF_SIZE 9
# define SCD4X_COMMAND_BUF_SIZE 2
# define SCD4X_WRITE_BUF_SIZE 5
# define SCD4X_FRC_MIN_PPM 0
# define SCD4X_FRC_MAX_PPM 2000
# define SCD4X_READY_MASK 0x01
/*Commands SCD4X*/
enum scd4x_cmd {
CMD_START_MEAS = 0x21b1 ,
CMD_READ_MEAS = 0xec05 ,
CMD_STOP_MEAS = 0x3f86 ,
CMD_SET_TEMP_OFFSET = 0x241d ,
CMD_GET_TEMP_OFFSET = 0x2318 ,
CMD_FRC = 0x362f ,
CMD_SET_ASC = 0x2416 ,
CMD_GET_ASC = 0x2313 ,
CMD_GET_DATA_READY = 0xe4b8 ,
} ;
enum scd4x_channel_idx {
SCD4X_CO2 ,
SCD4X_TEMP ,
SCD4X_HR ,
} ;
struct scd4x_state {
struct i2c_client * client ;
/* maintain access to device, to prevent concurrent reads/writes */
struct mutex lock ;
struct regulator * vdd ;
} ;
DECLARE_CRC8_TABLE ( scd4x_crc8_table ) ;
static int scd4x_i2c_xfer ( struct scd4x_state * state , char * txbuf , int txsize ,
char * rxbuf , int rxsize )
{
struct i2c_client * client = state - > client ;
int ret ;
ret = i2c_master_send ( client , txbuf , txsize ) ;
if ( ret < 0 )
return ret ;
if ( ret ! = txsize )
return - EIO ;
if ( rxsize = = 0 )
return 0 ;
ret = i2c_master_recv ( client , rxbuf , rxsize ) ;
if ( ret < 0 )
return ret ;
if ( ret ! = rxsize )
return - EIO ;
return 0 ;
}
static int scd4x_send_command ( struct scd4x_state * state , enum scd4x_cmd cmd )
{
char buf [ SCD4X_COMMAND_BUF_SIZE ] ;
int ret ;
/*
* Measurement needs to be stopped before sending commands .
* Except stop and start command .
*/
if ( ( cmd ! = CMD_STOP_MEAS ) & & ( cmd ! = CMD_START_MEAS ) ) {
ret = scd4x_send_command ( state , CMD_STOP_MEAS ) ;
if ( ret )
return ret ;
/* execution time for stopping measurement */
msleep_interruptible ( 500 ) ;
}
put_unaligned_be16 ( cmd , buf ) ;
ret = scd4x_i2c_xfer ( state , buf , 2 , buf , 0 ) ;
if ( ret )
return ret ;
if ( ( cmd ! = CMD_STOP_MEAS ) & & ( cmd ! = CMD_START_MEAS ) ) {
ret = scd4x_send_command ( state , CMD_START_MEAS ) ;
if ( ret )
return ret ;
}
return 0 ;
}
static int scd4x_read ( struct scd4x_state * state , enum scd4x_cmd cmd ,
void * response , int response_sz )
{
struct i2c_client * client = state - > client ;
char buf [ SCD4X_READ_BUF_SIZE ] ;
char * rsp = response ;
int i , ret ;
char crc ;
/*
* Measurement needs to be stopped before sending commands .
* Except for reading measurement and data ready command .
*/
if ( ( cmd ! = CMD_GET_DATA_READY ) & & ( cmd ! = CMD_READ_MEAS ) ) {
ret = scd4x_send_command ( state , CMD_STOP_MEAS ) ;
if ( ret )
return ret ;
/* execution time for stopping measurement */
msleep_interruptible ( 500 ) ;
}
/* CRC byte for every 2 bytes of data */
response_sz + = response_sz / 2 ;
put_unaligned_be16 ( cmd , buf ) ;
ret = scd4x_i2c_xfer ( state , buf , 2 , buf , response_sz ) ;
if ( ret )
return ret ;
for ( i = 0 ; i < response_sz ; i + = 3 ) {
crc = crc8 ( scd4x_crc8_table , buf + i , 2 , CRC8_INIT_VALUE ) ;
if ( crc ! = buf [ i + 2 ] ) {
dev_err ( & client - > dev , " CRC error \n " ) ;
return - EIO ;
}
* rsp + + = buf [ i ] ;
* rsp + + = buf [ i + 1 ] ;
}
/* start measurement */
if ( ( cmd ! = CMD_GET_DATA_READY ) & & ( cmd ! = CMD_READ_MEAS ) ) {
ret = scd4x_send_command ( state , CMD_START_MEAS ) ;
if ( ret )
return ret ;
}
return 0 ;
}
static int scd4x_write ( struct scd4x_state * state , enum scd4x_cmd cmd , uint16_t arg )
{
char buf [ SCD4X_WRITE_BUF_SIZE ] ;
int ret ;
char crc ;
put_unaligned_be16 ( cmd , buf ) ;
put_unaligned_be16 ( arg , buf + 2 ) ;
crc = crc8 ( scd4x_crc8_table , buf + 2 , 2 , CRC8_INIT_VALUE ) ;
buf [ 4 ] = crc ;
/* measurement needs to be stopped before sending commands */
ret = scd4x_send_command ( state , CMD_STOP_MEAS ) ;
if ( ret )
return ret ;
/* execution time */
msleep_interruptible ( 500 ) ;
ret = scd4x_i2c_xfer ( state , buf , SCD4X_WRITE_BUF_SIZE , buf , 0 ) ;
if ( ret )
return ret ;
/* start measurement, except for forced calibration command */
if ( cmd ! = CMD_FRC ) {
ret = scd4x_send_command ( state , CMD_START_MEAS ) ;
if ( ret )
return ret ;
}
return 0 ;
}
static int scd4x_write_and_fetch ( struct scd4x_state * state , enum scd4x_cmd cmd ,
uint16_t arg , void * response , int response_sz )
{
struct i2c_client * client = state - > client ;
char buf [ SCD4X_READ_BUF_SIZE ] ;
char * rsp = response ;
int i , ret ;
char crc ;
ret = scd4x_write ( state , CMD_FRC , arg ) ;
if ( ret )
goto err ;
/* execution time */
msleep_interruptible ( 400 ) ;
/* CRC byte for every 2 bytes of data */
response_sz + = response_sz / 2 ;
ret = i2c_master_recv ( client , buf , response_sz ) ;
if ( ret < 0 )
goto err ;
if ( ret ! = response_sz ) {
ret = - EIO ;
goto err ;
}
for ( i = 0 ; i < response_sz ; i + = 3 ) {
crc = crc8 ( scd4x_crc8_table , buf + i , 2 , CRC8_INIT_VALUE ) ;
if ( crc ! = buf [ i + 2 ] ) {
dev_err ( & client - > dev , " CRC error \n " ) ;
ret = - EIO ;
goto err ;
}
* rsp + + = buf [ i ] ;
* rsp + + = buf [ i + 1 ] ;
}
return scd4x_send_command ( state , CMD_START_MEAS ) ;
err :
/*
* on error try to start the measurement ,
* puts sensor back into continuous measurement
*/
scd4x_send_command ( state , CMD_START_MEAS ) ;
return ret ;
}
static int scd4x_read_meas ( struct scd4x_state * state , uint16_t * meas )
{
int i , ret ;
__be16 buf [ 3 ] ;
ret = scd4x_read ( state , CMD_READ_MEAS , buf , sizeof ( buf ) ) ;
if ( ret )
return ret ;
for ( i = 0 ; i < ARRAY_SIZE ( buf ) ; i + + )
meas [ i ] = be16_to_cpu ( buf [ i ] ) ;
return 0 ;
}
static int scd4x_wait_meas_poll ( struct scd4x_state * state )
{
struct i2c_client * client = state - > client ;
int tries = 6 ;
int ret ;
do {
__be16 bval ;
uint16_t val ;
ret = scd4x_read ( state , CMD_GET_DATA_READY , & bval , sizeof ( bval ) ) ;
if ( ret )
return - EIO ;
val = be16_to_cpu ( bval ) ;
/* new measurement available */
if ( val & 0x7FF )
return 0 ;
msleep_interruptible ( 1000 ) ;
} while ( - - tries ) ;
/* try to start sensor on timeout */
ret = scd4x_send_command ( state , CMD_START_MEAS ) ;
if ( ret )
dev_err ( & client - > dev , " failed to start measurement: %d \n " , ret ) ;
return - ETIMEDOUT ;
}
static int scd4x_read_poll ( struct scd4x_state * state , uint16_t * buf )
{
int ret ;
ret = scd4x_wait_meas_poll ( state ) ;
if ( ret )
return ret ;
return scd4x_read_meas ( state , buf ) ;
}
static int scd4x_read_channel ( struct scd4x_state * state , int chan )
{
int ret ;
uint16_t buf [ 3 ] ;
ret = scd4x_read_poll ( state , buf ) ;
if ( ret )
return ret ;
return buf [ chan ] ;
}
static int scd4x_read_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan , int * val ,
int * val2 , long mask )
{
struct scd4x_state * state = iio_priv ( indio_dev ) ;
int ret ;
__be16 tmp ;
switch ( mask ) {
case IIO_CHAN_INFO_RAW :
ret = iio_device_claim_direct_mode ( indio_dev ) ;
if ( ret )
return ret ;
mutex_lock ( & state - > lock ) ;
ret = scd4x_read_channel ( state , chan - > address ) ;
mutex_unlock ( & state - > lock ) ;
iio_device_release_direct_mode ( indio_dev ) ;
if ( ret < 0 )
return ret ;
* val = ret ;
return IIO_VAL_INT ;
case IIO_CHAN_INFO_SCALE :
2021-10-21 16:00:18 +02:00
if ( chan - > type = = IIO_CONCENTRATION ) {
* val = 0 ;
* val2 = 100 ;
return IIO_VAL_INT_PLUS_MICRO ;
} else if ( chan - > type = = IIO_TEMP ) {
2021-10-08 12:17:05 +02:00
* val = 175000 ;
* val2 = 65536 ;
return IIO_VAL_FRACTIONAL ;
} else if ( chan - > type = = IIO_HUMIDITYRELATIVE ) {
* val = 100000 ;
* val2 = 65536 ;
return IIO_VAL_FRACTIONAL ;
}
return - EINVAL ;
case IIO_CHAN_INFO_OFFSET :
* val = - 16852 ;
* val2 = 114286 ;
return IIO_VAL_INT_PLUS_MICRO ;
case IIO_CHAN_INFO_CALIBBIAS :
mutex_lock ( & state - > lock ) ;
ret = scd4x_read ( state , CMD_GET_TEMP_OFFSET , & tmp , sizeof ( tmp ) ) ;
mutex_unlock ( & state - > lock ) ;
if ( ret )
return ret ;
* val = be16_to_cpu ( tmp ) ;
return IIO_VAL_INT ;
default :
return - EINVAL ;
}
}
static int scd4x_write_raw ( struct iio_dev * indio_dev , struct iio_chan_spec const * chan ,
int val , int val2 , long mask )
{
struct scd4x_state * state = iio_priv ( indio_dev ) ;
int ret = 0 ;
switch ( mask ) {
case IIO_CHAN_INFO_CALIBBIAS :
mutex_lock ( & state - > lock ) ;
ret = scd4x_write ( state , CMD_SET_TEMP_OFFSET , val ) ;
mutex_unlock ( & state - > lock ) ;
return ret ;
default :
return - EINVAL ;
}
}
static ssize_t calibration_auto_enable_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct iio_dev * indio_dev = dev_to_iio_dev ( dev ) ;
struct scd4x_state * state = iio_priv ( indio_dev ) ;
int ret ;
__be16 bval ;
u16 val ;
mutex_lock ( & state - > lock ) ;
ret = scd4x_read ( state , CMD_GET_ASC , & bval , sizeof ( bval ) ) ;
mutex_unlock ( & state - > lock ) ;
if ( ret ) {
dev_err ( dev , " failed to read automatic calibration " ) ;
return ret ;
}
val = ( be16_to_cpu ( bval ) & SCD4X_READY_MASK ) ? 1 : 0 ;
2021-12-16 19:52:16 +01:00
return sysfs_emit ( buf , " %d \n " , val ) ;
2021-10-08 12:17:05 +02:00
}
static ssize_t calibration_auto_enable_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t len )
{
struct iio_dev * indio_dev = dev_to_iio_dev ( dev ) ;
struct scd4x_state * state = iio_priv ( indio_dev ) ;
bool val ;
int ret ;
uint16_t value ;
ret = kstrtobool ( buf , & val ) ;
if ( ret )
return ret ;
value = val ;
mutex_lock ( & state - > lock ) ;
ret = scd4x_write ( state , CMD_SET_ASC , value ) ;
mutex_unlock ( & state - > lock ) ;
if ( ret )
dev_err ( dev , " failed to set automatic calibration " ) ;
return ret ? : len ;
}
static ssize_t calibration_forced_value_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t len )
{
struct iio_dev * indio_dev = dev_to_iio_dev ( dev ) ;
struct scd4x_state * state = iio_priv ( indio_dev ) ;
uint16_t val , arg ;
int ret ;
ret = kstrtou16 ( buf , 0 , & arg ) ;
if ( ret )
return ret ;
if ( arg < SCD4X_FRC_MIN_PPM | | arg > SCD4X_FRC_MAX_PPM )
return - EINVAL ;
mutex_lock ( & state - > lock ) ;
ret = scd4x_write_and_fetch ( state , CMD_FRC , arg , & val , sizeof ( val ) ) ;
mutex_unlock ( & state - > lock ) ;
2022-02-28 18:52:23 -08:00
if ( ret )
return ret ;
2021-10-08 12:17:05 +02:00
if ( val = = 0xff ) {
dev_err ( dev , " forced calibration has failed " ) ;
return - EINVAL ;
}
2022-02-28 18:52:23 -08:00
return len ;
2021-10-08 12:17:05 +02:00
}
static IIO_DEVICE_ATTR_RW ( calibration_auto_enable , 0 ) ;
static IIO_DEVICE_ATTR_WO ( calibration_forced_value , 0 ) ;
static IIO_CONST_ATTR ( calibration_forced_value_available ,
__stringify ( [ SCD4X_FRC_MIN_PPM 1 SCD4X_FRC_MAX_PPM ] ) ) ;
static struct attribute * scd4x_attrs [ ] = {
& iio_dev_attr_calibration_auto_enable . dev_attr . attr ,
& iio_dev_attr_calibration_forced_value . dev_attr . attr ,
& iio_const_attr_calibration_forced_value_available . dev_attr . attr ,
NULL
} ;
static const struct attribute_group scd4x_attr_group = {
. attrs = scd4x_attrs ,
} ;
static const struct iio_info scd4x_info = {
. attrs = & scd4x_attr_group ,
. read_raw = scd4x_read_raw ,
. write_raw = scd4x_write_raw ,
} ;
static const struct iio_chan_spec scd4x_channels [ ] = {
{
. type = IIO_CONCENTRATION ,
. channel2 = IIO_MOD_CO2 ,
. modified = 1 ,
2021-10-21 16:00:18 +02:00
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) |
BIT ( IIO_CHAN_INFO_SCALE ) ,
2021-10-08 12:17:05 +02:00
. address = SCD4X_CO2 ,
. scan_index = SCD4X_CO2 ,
. scan_type = {
. sign = ' u ' ,
. realbits = 16 ,
. storagebits = 16 ,
. endianness = IIO_BE ,
} ,
} ,
{
. type = IIO_TEMP ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) |
BIT ( IIO_CHAN_INFO_SCALE ) |
BIT ( IIO_CHAN_INFO_OFFSET ) |
BIT ( IIO_CHAN_INFO_CALIBBIAS ) ,
. address = SCD4X_TEMP ,
. scan_index = SCD4X_TEMP ,
. scan_type = {
. sign = ' u ' ,
. realbits = 16 ,
. storagebits = 16 ,
. endianness = IIO_BE ,
} ,
} ,
{
. type = IIO_HUMIDITYRELATIVE ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) |
BIT ( IIO_CHAN_INFO_SCALE ) ,
. address = SCD4X_HR ,
. scan_index = SCD4X_HR ,
. scan_type = {
. sign = ' u ' ,
. realbits = 16 ,
. storagebits = 16 ,
. endianness = IIO_BE ,
} ,
} ,
} ;
2022-06-21 21:26:53 +01:00
static int scd4x_suspend ( struct device * dev )
2021-10-08 12:17:05 +02:00
{
struct iio_dev * indio_dev = dev_get_drvdata ( dev ) ;
struct scd4x_state * state = iio_priv ( indio_dev ) ;
int ret ;
ret = scd4x_send_command ( state , CMD_STOP_MEAS ) ;
if ( ret )
return ret ;
return regulator_disable ( state - > vdd ) ;
}
2022-06-21 21:26:53 +01:00
static int scd4x_resume ( struct device * dev )
2021-10-08 12:17:05 +02:00
{
struct iio_dev * indio_dev = dev_get_drvdata ( dev ) ;
struct scd4x_state * state = iio_priv ( indio_dev ) ;
int ret ;
ret = regulator_enable ( state - > vdd ) ;
if ( ret )
return ret ;
return scd4x_send_command ( state , CMD_START_MEAS ) ;
}
2022-06-21 21:26:53 +01:00
static DEFINE_SIMPLE_DEV_PM_OPS ( scd4x_pm_ops , scd4x_suspend , scd4x_resume ) ;
2021-10-08 12:17:05 +02:00
static void scd4x_stop_meas ( void * state )
{
scd4x_send_command ( state , CMD_STOP_MEAS ) ;
}
static void scd4x_disable_regulator ( void * data )
{
struct scd4x_state * state = data ;
regulator_disable ( state - > vdd ) ;
}
static irqreturn_t scd4x_trigger_handler ( int irq , void * p )
{
struct iio_poll_func * pf = p ;
struct iio_dev * indio_dev = pf - > indio_dev ;
struct scd4x_state * state = iio_priv ( indio_dev ) ;
struct {
uint16_t data [ 3 ] ;
int64_t ts __aligned ( 8 ) ;
} scan ;
int ret ;
memset ( & scan , 0 , sizeof ( scan ) ) ;
mutex_lock ( & state - > lock ) ;
ret = scd4x_read_poll ( state , scan . data ) ;
mutex_unlock ( & state - > lock ) ;
if ( ret )
goto out ;
iio_push_to_buffers_with_timestamp ( indio_dev , & scan , iio_get_time_ns ( indio_dev ) ) ;
out :
iio_trigger_notify_done ( indio_dev - > trig ) ;
return IRQ_HANDLED ;
}
static int scd4x_probe ( struct i2c_client * client , const struct i2c_device_id * id )
{
static const unsigned long scd4x_scan_masks [ ] = { 0x07 , 0x00 } ;
struct device * dev = & client - > dev ;
struct iio_dev * indio_dev ;
struct scd4x_state * state ;
int ret ;
indio_dev = devm_iio_device_alloc ( dev , sizeof ( * state ) ) ;
if ( ! indio_dev )
return - ENOMEM ;
state = iio_priv ( indio_dev ) ;
mutex_init ( & state - > lock ) ;
state - > client = client ;
crc8_populate_msb ( scd4x_crc8_table , SCD4X_CRC8_POLYNOMIAL ) ;
indio_dev - > info = & scd4x_info ;
indio_dev - > name = client - > name ;
indio_dev - > channels = scd4x_channels ;
indio_dev - > num_channels = ARRAY_SIZE ( scd4x_channels ) ;
indio_dev - > modes = INDIO_DIRECT_MODE ;
indio_dev - > available_scan_masks = scd4x_scan_masks ;
state - > vdd = devm_regulator_get ( dev , " vdd " ) ;
if ( IS_ERR ( state - > vdd ) )
return dev_err_probe ( dev , PTR_ERR ( state - > vdd ) , " failed to get regulator \n " ) ;
ret = regulator_enable ( state - > vdd ) ;
if ( ret )
return ret ;
ret = devm_add_action_or_reset ( dev , scd4x_disable_regulator , state ) ;
if ( ret )
return ret ;
ret = scd4x_send_command ( state , CMD_STOP_MEAS ) ;
if ( ret ) {
dev_err ( dev , " failed to stop measurement: %d \n " , ret ) ;
return ret ;
}
/* execution time */
msleep_interruptible ( 500 ) ;
ret = devm_iio_triggered_buffer_setup ( dev , indio_dev , NULL , scd4x_trigger_handler , NULL ) ;
if ( ret )
return ret ;
ret = scd4x_send_command ( state , CMD_START_MEAS ) ;
if ( ret ) {
dev_err ( dev , " failed to start measurement: %d \n " , ret ) ;
return ret ;
}
ret = devm_add_action_or_reset ( dev , scd4x_stop_meas , state ) ;
if ( ret )
return ret ;
return devm_iio_device_register ( dev , indio_dev ) ;
}
static const struct of_device_id scd4x_dt_ids [ ] = {
{ . compatible = " sensirion,scd40 " } ,
{ . compatible = " sensirion,scd41 " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , scd4x_dt_ids ) ;
static struct i2c_driver scd4x_i2c_driver = {
. driver = {
. name = KBUILD_MODNAME ,
. of_match_table = scd4x_dt_ids ,
2022-06-21 21:26:53 +01:00
. pm = pm_sleep_ptr ( & scd4x_pm_ops ) ,
2021-10-08 12:17:05 +02:00
} ,
. probe = scd4x_probe ,
} ;
module_i2c_driver ( scd4x_i2c_driver ) ;
MODULE_AUTHOR ( " Roan van Dijk <roan@protonic.nl> " ) ;
MODULE_DESCRIPTION ( " Sensirion SCD4X carbon dioxide sensor core driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;