2021-05-03 08:00:13 +02:00
// SPDX-License-Identifier: GPL-2.0
/*
* Sensirion SPS30 particulate matter sensor serial driver
*
* Copyright ( c ) 2021 Tomasz Duszynski < tomasz . duszynski @ octakon . com >
*/
# include <linux/completion.h>
# include <linux/device.h>
# include <linux/errno.h>
# include <linux/iio/iio.h>
# include <linux/minmax.h>
# include <linux/mod_devicetable.h>
# include <linux/module.h>
# include <linux/serdev.h>
# include <linux/types.h>
# include "sps30.h"
# define SPS30_SERIAL_DEV_NAME "sps30"
# define SPS30_SERIAL_SOF_EOF 0x7e
# define SPS30_SERIAL_TIMEOUT msecs_to_jiffies(20)
# define SPS30_SERIAL_MAX_BUF_SIZE 263
# define SPS30_SERIAL_ESCAPE_CHAR 0x7d
# define SPS30_SERIAL_FRAME_MIN_SIZE 7
# define SPS30_SERIAL_FRAME_ADR_OFFSET 1
# define SPS30_SERIAL_FRAME_CMD_OFFSET 2
# define SPS30_SERIAL_FRAME_MOSI_LEN_OFFSET 3
# define SPS30_SERIAL_FRAME_MISO_STATE_OFFSET 3
# define SPS30_SERIAL_FRAME_MISO_LEN_OFFSET 4
# define SPS30_SERIAL_FRAME_MISO_DATA_OFFSET 5
# define SPS30_SERIAL_START_MEAS 0x00
# define SPS30_SERIAL_STOP_MEAS 0x01
# define SPS30_SERIAL_READ_MEAS 0x03
# define SPS30_SERIAL_RESET 0xd3
# define SPS30_SERIAL_CLEAN_FAN 0x56
# define SPS30_SERIAL_PERIOD 0x80
# define SPS30_SERIAL_DEV_INFO 0xd0
# define SPS30_SERIAL_READ_VERSION 0xd1
struct sps30_serial_priv {
struct completion new_frame ;
unsigned char buf [ SPS30_SERIAL_MAX_BUF_SIZE ] ;
size_t num ;
bool escaped ;
bool done ;
} ;
static int sps30_serial_xfer ( struct sps30_state * state , const unsigned char * buf , size_t size )
{
struct serdev_device * serdev = to_serdev_device ( state - > dev ) ;
struct sps30_serial_priv * priv = state - > priv ;
int ret ;
priv - > num = 0 ;
priv - > escaped = false ;
priv - > done = false ;
ret = serdev_device_write ( serdev , buf , size , SPS30_SERIAL_TIMEOUT ) ;
if ( ret < 0 )
return ret ;
if ( ret ! = size )
return - EIO ;
ret = wait_for_completion_interruptible_timeout ( & priv - > new_frame , SPS30_SERIAL_TIMEOUT ) ;
if ( ret < 0 )
return ret ;
if ( ! ret )
return - ETIMEDOUT ;
return 0 ;
}
static const struct {
unsigned char byte ;
unsigned char byte2 ;
} sps30_serial_bytes [ ] = {
{ 0x11 , 0x31 } ,
{ 0x13 , 0x33 } ,
{ 0x7e , 0x5e } ,
{ 0x7d , 0x5d } ,
} ;
static int sps30_serial_put_byte ( unsigned char * buf , unsigned char byte )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( sps30_serial_bytes ) ; i + + ) {
if ( sps30_serial_bytes [ i ] . byte ! = byte )
continue ;
buf [ 0 ] = SPS30_SERIAL_ESCAPE_CHAR ;
buf [ 1 ] = sps30_serial_bytes [ i ] . byte2 ;
return 2 ;
}
buf [ 0 ] = byte ;
return 1 ;
}
static char sps30_serial_get_byte ( bool escaped , unsigned char byte2 )
{
int i ;
if ( ! escaped )
return byte2 ;
for ( i = 0 ; i < ARRAY_SIZE ( sps30_serial_bytes ) ; i + + ) {
if ( sps30_serial_bytes [ i ] . byte2 ! = byte2 )
continue ;
return sps30_serial_bytes [ i ] . byte ;
}
return 0 ;
}
static unsigned char sps30_serial_calc_chksum ( const unsigned char * buf , size_t num )
{
unsigned int chksum = 0 ;
size_t i ;
for ( i = 0 ; i < num ; i + + )
chksum + = buf [ i ] ;
return ~ chksum ;
}
static int sps30_serial_prep_frame ( unsigned char * buf , unsigned char cmd ,
const unsigned char * arg , size_t arg_size )
{
unsigned char chksum ;
int num = 0 ;
size_t i ;
buf [ num + + ] = SPS30_SERIAL_SOF_EOF ;
buf [ num + + ] = 0 ;
num + = sps30_serial_put_byte ( buf + num , cmd ) ;
num + = sps30_serial_put_byte ( buf + num , arg_size ) ;
for ( i = 0 ; i < arg_size ; i + + )
num + = sps30_serial_put_byte ( buf + num , arg [ i ] ) ;
/* SOF isn't checksummed */
chksum = sps30_serial_calc_chksum ( buf + 1 , num - 1 ) ;
num + = sps30_serial_put_byte ( buf + num , chksum ) ;
buf [ num + + ] = SPS30_SERIAL_SOF_EOF ;
return num ;
}
static bool sps30_serial_frame_valid ( struct sps30_state * state , const unsigned char * buf )
{
struct sps30_serial_priv * priv = state - > priv ;
unsigned char chksum ;
if ( ( priv - > num < SPS30_SERIAL_FRAME_MIN_SIZE ) | |
( priv - > num ! = SPS30_SERIAL_FRAME_MIN_SIZE +
priv - > buf [ SPS30_SERIAL_FRAME_MISO_LEN_OFFSET ] ) ) {
dev_err ( state - > dev , " frame has invalid number of bytes \n " ) ;
return false ;
}
if ( ( priv - > buf [ SPS30_SERIAL_FRAME_ADR_OFFSET ] ! = buf [ SPS30_SERIAL_FRAME_ADR_OFFSET ] ) | |
( priv - > buf [ SPS30_SERIAL_FRAME_CMD_OFFSET ] ! = buf [ SPS30_SERIAL_FRAME_CMD_OFFSET ] ) ) {
dev_err ( state - > dev , " frame has wrong ADR and CMD bytes \n " ) ;
return false ;
}
if ( priv - > buf [ SPS30_SERIAL_FRAME_MISO_STATE_OFFSET ] ) {
dev_err ( state - > dev , " frame with non-zero state received (0x%02x) \n " ,
priv - > buf [ SPS30_SERIAL_FRAME_MISO_STATE_OFFSET ] ) ;
return false ;
}
/* SOF, checksum and EOF are not checksummed */
chksum = sps30_serial_calc_chksum ( priv - > buf + 1 , priv - > num - 3 ) ;
if ( priv - > buf [ priv - > num - 2 ] ! = chksum ) {
dev_err ( state - > dev , " frame integrity check failed \n " ) ;
return false ;
}
return true ;
}
static int sps30_serial_command ( struct sps30_state * state , unsigned char cmd ,
const void * arg , size_t arg_size , void * rsp , size_t rsp_size )
{
struct sps30_serial_priv * priv = state - > priv ;
unsigned char buf [ SPS30_SERIAL_MAX_BUF_SIZE ] ;
int ret , size ;
size = sps30_serial_prep_frame ( buf , cmd , arg , arg_size ) ;
ret = sps30_serial_xfer ( state , buf , size ) ;
if ( ret )
return ret ;
if ( ! sps30_serial_frame_valid ( state , buf ) )
return - EIO ;
if ( rsp ) {
rsp_size = min_t ( size_t , priv - > buf [ SPS30_SERIAL_FRAME_MISO_LEN_OFFSET ] , rsp_size ) ;
memcpy ( rsp , & priv - > buf [ SPS30_SERIAL_FRAME_MISO_DATA_OFFSET ] , rsp_size ) ;
}
return rsp_size ;
}
static int sps30_serial_receive_buf ( struct serdev_device * serdev ,
const unsigned char * buf , size_t size )
{
struct iio_dev * indio_dev = dev_get_drvdata ( & serdev - > dev ) ;
struct sps30_serial_priv * priv ;
struct sps30_state * state ;
unsigned char byte ;
size_t i ;
if ( ! indio_dev )
return 0 ;
state = iio_priv ( indio_dev ) ;
priv = state - > priv ;
/* just in case device put some unexpected data on the bus */
if ( priv - > done )
return size ;
/* wait for the start of frame */
if ( ! priv - > num & & size & & buf [ 0 ] ! = SPS30_SERIAL_SOF_EOF )
return 1 ;
if ( priv - > num + size > = ARRAY_SIZE ( priv - > buf ) )
size = ARRAY_SIZE ( priv - > buf ) - priv - > num ;
for ( i = 0 ; i < size ; i + + ) {
byte = buf [ i ] ;
/* remove stuffed bytes on-the-fly */
if ( byte = = SPS30_SERIAL_ESCAPE_CHAR ) {
priv - > escaped = true ;
continue ;
}
byte = sps30_serial_get_byte ( priv - > escaped , byte ) ;
if ( priv - > escaped & & ! byte )
dev_warn ( state - > dev , " unrecognized escaped char (0x%02x) \n " , byte ) ;
priv - > buf [ priv - > num + + ] = byte ;
/* EOF received */
if ( ! priv - > escaped & & byte = = SPS30_SERIAL_SOF_EOF ) {
if ( priv - > num < SPS30_SERIAL_FRAME_MIN_SIZE )
continue ;
priv - > done = true ;
complete ( & priv - > new_frame ) ;
i + + ;
break ;
}
priv - > escaped = false ;
}
return i ;
}
static const struct serdev_device_ops sps30_serial_device_ops = {
. receive_buf = sps30_serial_receive_buf ,
. write_wakeup = serdev_device_write_wakeup ,
} ;
static int sps30_serial_start_meas ( struct sps30_state * state )
{
/* request BE IEEE754 formatted data */
unsigned char buf [ ] = { 0x01 , 0x03 } ;
return sps30_serial_command ( state , SPS30_SERIAL_START_MEAS , buf , sizeof ( buf ) , NULL , 0 ) ;
}
static int sps30_serial_stop_meas ( struct sps30_state * state )
{
return sps30_serial_command ( state , SPS30_SERIAL_STOP_MEAS , NULL , 0 , NULL , 0 ) ;
}
static int sps30_serial_reset ( struct sps30_state * state )
{
int ret ;
ret = sps30_serial_command ( state , SPS30_SERIAL_RESET , NULL , 0 , NULL , 0 ) ;
msleep ( 500 ) ;
return ret ;
}
static int sps30_serial_read_meas ( struct sps30_state * state , __be32 * meas , size_t num )
{
int ret ;
/* measurements are ready within a second */
if ( msleep_interruptible ( 1000 ) )
return - EINTR ;
ret = sps30_serial_command ( state , SPS30_SERIAL_READ_MEAS , NULL , 0 , meas , num * sizeof ( num ) ) ;
if ( ret < 0 )
return ret ;
/* if measurements aren't ready sensor returns empty frame */
if ( ret = = SPS30_SERIAL_FRAME_MIN_SIZE )
return - ETIMEDOUT ;
if ( ret ! = num * sizeof ( * meas ) )
return - EIO ;
return 0 ;
}
static int sps30_serial_clean_fan ( struct sps30_state * state )
{
return sps30_serial_command ( state , SPS30_SERIAL_CLEAN_FAN , NULL , 0 , NULL , 0 ) ;
}
static int sps30_serial_read_cleaning_period ( struct sps30_state * state , __be32 * period )
{
unsigned char buf [ ] = { 0x00 } ;
int ret ;
ret = sps30_serial_command ( state , SPS30_SERIAL_PERIOD , buf , sizeof ( buf ) ,
period , sizeof ( * period ) ) ;
if ( ret < 0 )
return ret ;
if ( ret ! = sizeof ( * period ) )
return - EIO ;
return 0 ;
}
static int sps30_serial_write_cleaning_period ( struct sps30_state * state , __be32 period )
{
unsigned char buf [ 5 ] = { 0x00 } ;
memcpy ( buf + 1 , & period , sizeof ( period ) ) ;
return sps30_serial_command ( state , SPS30_SERIAL_PERIOD , buf , sizeof ( buf ) , NULL , 0 ) ;
}
static int sps30_serial_show_info ( struct sps30_state * state )
{
/*
* tell device do return serial number and add extra nul byte just in case
* serial number isn ' t a valid string
*/
unsigned char buf [ 32 + 1 ] = { 0x03 } ;
struct device * dev = state - > dev ;
int ret ;
ret = sps30_serial_command ( state , SPS30_SERIAL_DEV_INFO , buf , 1 , buf , sizeof ( buf ) - 1 ) ;
if ( ret < 0 )
return ret ;
if ( ret ! = sizeof ( buf ) - 1 )
return - EIO ;
dev_info ( dev , " serial number: %s \n " , buf ) ;
ret = sps30_serial_command ( state , SPS30_SERIAL_READ_VERSION , NULL , 0 , buf , sizeof ( buf ) - 1 ) ;
if ( ret < 0 )
return ret ;
if ( ret < 2 )
return - EIO ;
dev_info ( dev , " fw version: %u.%u \n " , buf [ 0 ] , buf [ 1 ] ) ;
return 0 ;
}
static const struct sps30_ops sps30_serial_ops = {
. start_meas = sps30_serial_start_meas ,
. stop_meas = sps30_serial_stop_meas ,
. read_meas = sps30_serial_read_meas ,
. reset = sps30_serial_reset ,
. clean_fan = sps30_serial_clean_fan ,
. read_cleaning_period = sps30_serial_read_cleaning_period ,
. write_cleaning_period = sps30_serial_write_cleaning_period ,
. show_info = sps30_serial_show_info ,
} ;
static int sps30_serial_probe ( struct serdev_device * serdev )
{
struct device * dev = & serdev - > dev ;
struct sps30_serial_priv * priv ;
int ret ;
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
init_completion ( & priv - > new_frame ) ;
serdev_device_set_client_ops ( serdev , & sps30_serial_device_ops ) ;
ret = devm_serdev_device_open ( dev , serdev ) ;
if ( ret )
return ret ;
serdev_device_set_baudrate ( serdev , 115200 ) ;
serdev_device_set_flow_control ( serdev , false ) ;
ret = serdev_device_set_parity ( serdev , SERDEV_PARITY_NONE ) ;
if ( ret )
return ret ;
return sps30_probe ( dev , SPS30_SERIAL_DEV_NAME , priv , & sps30_serial_ops ) ;
}
static const struct of_device_id sps30_serial_of_match [ ] = {
{ . compatible = " sensirion,sps30 " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , sps30_serial_of_match ) ;
static struct serdev_device_driver sps30_serial_driver = {
. driver = {
. name = KBUILD_MODNAME ,
. of_match_table = sps30_serial_of_match ,
} ,
. probe = sps30_serial_probe ,
} ;
module_serdev_device_driver ( sps30_serial_driver ) ;
MODULE_AUTHOR ( " Tomasz Duszynski <tomasz.duszynski@octakon.com> " ) ;
MODULE_DESCRIPTION ( " Sensirion SPS30 particulate matter sensor serial driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
2022-06-04 16:53:05 +01:00
MODULE_IMPORT_NS ( IIO_SPS30 ) ;