2019-05-28 09:57:06 -07:00
// SPDX-License-Identifier: GPL-2.0-only
2017-11-23 23:59:05 +01:00
/*
* STMicroelectronics uvis25 sensor driver
*
* Copyright 2017 STMicroelectronics Inc .
*
* Lorenzo Bianconi < lorenzo . bianconi83 @ gmail . com >
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/device.h>
# include <linux/iio/sysfs.h>
# include <linux/delay.h>
# include <linux/pm.h>
# include <linux/interrupt.h>
# include <linux/irqreturn.h>
# include <linux/iio/trigger.h>
# include <linux/iio/trigger_consumer.h>
# include <linux/iio/triggered_buffer.h>
# include <linux/iio/buffer.h>
# include <linux/regmap.h>
# include "st_uvis25.h"
# define ST_UVIS25_REG_WHOAMI_ADDR 0x0f
# define ST_UVIS25_REG_WHOAMI_VAL 0xca
# define ST_UVIS25_REG_CTRL1_ADDR 0x20
# define ST_UVIS25_REG_ODR_MASK BIT(0)
# define ST_UVIS25_REG_BDU_MASK BIT(1)
# define ST_UVIS25_REG_CTRL2_ADDR 0x21
# define ST_UVIS25_REG_BOOT_MASK BIT(7)
# define ST_UVIS25_REG_CTRL3_ADDR 0x22
# define ST_UVIS25_REG_HL_MASK BIT(7)
# define ST_UVIS25_REG_STATUS_ADDR 0x27
# define ST_UVIS25_REG_UV_DA_MASK BIT(0)
# define ST_UVIS25_REG_OUT_ADDR 0x28
static const struct iio_chan_spec st_uvis25_channels [ ] = {
{
. type = IIO_UVINDEX ,
. address = ST_UVIS25_REG_OUT_ADDR ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_PROCESSED ) ,
. scan_index = 0 ,
. scan_type = {
. sign = ' u ' ,
. realbits = 8 ,
. storagebits = 8 ,
} ,
} ,
IIO_CHAN_SOFT_TIMESTAMP ( 1 ) ,
} ;
static int st_uvis25_check_whoami ( struct st_uvis25_hw * hw )
{
int err , data ;
err = regmap_read ( hw - > regmap , ST_UVIS25_REG_WHOAMI_ADDR , & data ) ;
if ( err < 0 ) {
dev_err ( regmap_get_device ( hw - > regmap ) ,
" failed to read whoami register \n " ) ;
return err ;
}
if ( data ! = ST_UVIS25_REG_WHOAMI_VAL ) {
dev_err ( regmap_get_device ( hw - > regmap ) ,
" wrong whoami {%02x vs %02x} \n " ,
data , ST_UVIS25_REG_WHOAMI_VAL ) ;
return - ENODEV ;
}
return 0 ;
}
static int st_uvis25_set_enable ( struct st_uvis25_hw * hw , bool enable )
{
int err ;
err = regmap_update_bits ( hw - > regmap , ST_UVIS25_REG_CTRL1_ADDR ,
ST_UVIS25_REG_ODR_MASK , enable ) ;
if ( err < 0 )
return err ;
hw - > enabled = enable ;
return 0 ;
}
static int st_uvis25_read_oneshot ( struct st_uvis25_hw * hw , u8 addr , int * val )
{
int err ;
err = st_uvis25_set_enable ( hw , true ) ;
if ( err < 0 )
return err ;
msleep ( 1500 ) ;
/*
* in order to avoid possible race conditions with interrupt
* generation , disable the sensor first and then poll output
* register . That sequence guarantees the interrupt will be reset
* when irq line is unmasked
*/
err = st_uvis25_set_enable ( hw , false ) ;
if ( err < 0 )
return err ;
err = regmap_read ( hw - > regmap , addr , val ) ;
return err < 0 ? err : IIO_VAL_INT ;
}
static int st_uvis25_read_raw ( struct iio_dev * iio_dev ,
struct iio_chan_spec const * ch ,
int * val , int * val2 , long mask )
{
int ret ;
ret = iio_device_claim_direct_mode ( iio_dev ) ;
if ( ret )
return ret ;
switch ( mask ) {
case IIO_CHAN_INFO_PROCESSED : {
struct st_uvis25_hw * hw = iio_priv ( iio_dev ) ;
/*
* mask irq line during oneshot read since the sensor
* does not export the capability to disable data - ready line
* in the register map and it is enabled by default .
* If the line is unmasked during read_raw ( ) it will be set
* active and never reset since the trigger is disabled
*/
if ( hw - > irq > 0 )
disable_irq ( hw - > irq ) ;
ret = st_uvis25_read_oneshot ( hw , ch - > address , val ) ;
if ( hw - > irq > 0 )
enable_irq ( hw - > irq ) ;
break ;
}
default :
ret = - EINVAL ;
break ;
}
iio_device_release_direct_mode ( iio_dev ) ;
return ret ;
}
static irqreturn_t st_uvis25_trigger_handler_thread ( int irq , void * private )
{
struct st_uvis25_hw * hw = private ;
int err , status ;
err = regmap_read ( hw - > regmap , ST_UVIS25_REG_STATUS_ADDR , & status ) ;
if ( err < 0 )
return IRQ_HANDLED ;
if ( ! ( status & ST_UVIS25_REG_UV_DA_MASK ) )
return IRQ_NONE ;
2023-03-02 14:04:36 +01:00
iio_trigger_poll_nested ( hw - > trig ) ;
2017-11-23 23:59:05 +01:00
return IRQ_HANDLED ;
}
static int st_uvis25_allocate_trigger ( struct iio_dev * iio_dev )
{
struct st_uvis25_hw * hw = iio_priv ( iio_dev ) ;
struct device * dev = regmap_get_device ( hw - > regmap ) ;
bool irq_active_low = false ;
unsigned long irq_type ;
int err ;
irq_type = irqd_get_trigger_type ( irq_get_irq_data ( hw - > irq ) ) ;
switch ( irq_type ) {
case IRQF_TRIGGER_HIGH :
case IRQF_TRIGGER_RISING :
break ;
case IRQF_TRIGGER_LOW :
case IRQF_TRIGGER_FALLING :
irq_active_low = true ;
break ;
default :
dev_info ( dev , " mode %lx unsupported \n " , irq_type ) ;
return - EINVAL ;
}
err = regmap_update_bits ( hw - > regmap , ST_UVIS25_REG_CTRL3_ADDR ,
ST_UVIS25_REG_HL_MASK , irq_active_low ) ;
if ( err < 0 )
return err ;
err = devm_request_threaded_irq ( dev , hw - > irq , NULL ,
st_uvis25_trigger_handler_thread ,
irq_type | IRQF_ONESHOT ,
iio_dev - > name , hw ) ;
if ( err ) {
dev_err ( dev , " failed to request trigger irq %d \n " ,
hw - > irq ) ;
return err ;
}
hw - > trig = devm_iio_trigger_alloc ( dev , " %s-trigger " ,
iio_dev - > name ) ;
if ( ! hw - > trig )
return - ENOMEM ;
iio_trigger_set_drvdata ( hw - > trig , iio_dev ) ;
return devm_iio_trigger_register ( dev , hw - > trig ) ;
}
static int st_uvis25_buffer_preenable ( struct iio_dev * iio_dev )
{
return st_uvis25_set_enable ( iio_priv ( iio_dev ) , true ) ;
}
static int st_uvis25_buffer_postdisable ( struct iio_dev * iio_dev )
{
return st_uvis25_set_enable ( iio_priv ( iio_dev ) , false ) ;
}
static const struct iio_buffer_setup_ops st_uvis25_buffer_ops = {
. preenable = st_uvis25_buffer_preenable ,
. postdisable = st_uvis25_buffer_postdisable ,
} ;
static irqreturn_t st_uvis25_buffer_handler_thread ( int irq , void * p )
{
struct iio_poll_func * pf = p ;
struct iio_dev * iio_dev = pf - > indio_dev ;
struct st_uvis25_hw * hw = iio_priv ( iio_dev ) ;
2020-09-20 12:27:36 +01:00
unsigned int val ;
2017-11-23 23:59:05 +01:00
int err ;
2020-09-20 12:27:36 +01:00
err = regmap_read ( hw - > regmap , ST_UVIS25_REG_OUT_ADDR , & val ) ;
2017-11-23 23:59:05 +01:00
if ( err < 0 )
goto out ;
2020-09-20 12:27:36 +01:00
hw - > scan . chan = val ;
iio_push_to_buffers_with_timestamp ( iio_dev , & hw - > scan ,
2017-11-23 23:59:05 +01:00
iio_get_time_ns ( iio_dev ) ) ;
out :
iio_trigger_notify_done ( hw - > trig ) ;
return IRQ_HANDLED ;
}
static int st_uvis25_allocate_buffer ( struct iio_dev * iio_dev )
{
struct st_uvis25_hw * hw = iio_priv ( iio_dev ) ;
return devm_iio_triggered_buffer_setup ( regmap_get_device ( hw - > regmap ) ,
iio_dev , NULL ,
st_uvis25_buffer_handler_thread ,
& st_uvis25_buffer_ops ) ;
}
static const struct iio_info st_uvis25_info = {
. read_raw = st_uvis25_read_raw ,
} ;
static int st_uvis25_init_sensor ( struct st_uvis25_hw * hw )
{
int err ;
err = regmap_update_bits ( hw - > regmap , ST_UVIS25_REG_CTRL2_ADDR ,
ST_UVIS25_REG_BOOT_MASK , 1 ) ;
if ( err < 0 )
return err ;
msleep ( 2000 ) ;
return regmap_update_bits ( hw - > regmap , ST_UVIS25_REG_CTRL1_ADDR ,
ST_UVIS25_REG_BDU_MASK , 1 ) ;
}
int st_uvis25_probe ( struct device * dev , int irq , struct regmap * regmap )
{
struct st_uvis25_hw * hw ;
struct iio_dev * iio_dev ;
int err ;
iio_dev = devm_iio_device_alloc ( dev , sizeof ( * hw ) ) ;
if ( ! iio_dev )
return - ENOMEM ;
dev_set_drvdata ( dev , ( void * ) iio_dev ) ;
hw = iio_priv ( iio_dev ) ;
hw - > irq = irq ;
hw - > regmap = regmap ;
err = st_uvis25_check_whoami ( hw ) ;
if ( err < 0 )
return err ;
iio_dev - > modes = INDIO_DIRECT_MODE ;
iio_dev - > channels = st_uvis25_channels ;
iio_dev - > num_channels = ARRAY_SIZE ( st_uvis25_channels ) ;
iio_dev - > name = ST_UVIS25_DEV_NAME ;
iio_dev - > info = & st_uvis25_info ;
err = st_uvis25_init_sensor ( hw ) ;
if ( err < 0 )
return err ;
if ( hw - > irq > 0 ) {
err = st_uvis25_allocate_buffer ( iio_dev ) ;
if ( err < 0 )
return err ;
err = st_uvis25_allocate_trigger ( iio_dev ) ;
if ( err )
return err ;
}
return devm_iio_device_register ( dev , iio_dev ) ;
}
2022-01-30 20:57:00 +00:00
EXPORT_SYMBOL_NS ( st_uvis25_probe , IIO_UVIS25 ) ;
2017-11-23 23:59:05 +01:00
2022-08-07 19:56:18 +01:00
static int st_uvis25_suspend ( struct device * dev )
2017-11-23 23:59:05 +01:00
{
struct iio_dev * iio_dev = dev_get_drvdata ( dev ) ;
struct st_uvis25_hw * hw = iio_priv ( iio_dev ) ;
return regmap_update_bits ( hw - > regmap , ST_UVIS25_REG_CTRL1_ADDR ,
ST_UVIS25_REG_ODR_MASK , 0 ) ;
}
2022-08-07 19:56:18 +01:00
static int st_uvis25_resume ( struct device * dev )
2017-11-23 23:59:05 +01:00
{
struct iio_dev * iio_dev = dev_get_drvdata ( dev ) ;
struct st_uvis25_hw * hw = iio_priv ( iio_dev ) ;
if ( hw - > enabled )
return regmap_update_bits ( hw - > regmap , ST_UVIS25_REG_CTRL1_ADDR ,
ST_UVIS25_REG_ODR_MASK , 1 ) ;
return 0 ;
}
2022-08-07 19:56:18 +01:00
EXPORT_NS_SIMPLE_DEV_PM_OPS ( st_uvis25_pm_ops , st_uvis25_suspend , st_uvis25_resume , IIO_UVIS25 ) ;
2017-11-23 23:59:05 +01:00
MODULE_AUTHOR ( " Lorenzo Bianconi <lorenzo.bianconi83@gmail.com> " ) ;
MODULE_DESCRIPTION ( " STMicroelectronics uvis25 sensor driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;