2018-07-19 23:26:25 +03:00
// SPDX-License-Identifier: GPL-2.0+
/*
* si1133 . c - Support for Silabs SI1133 combined ambient
* light and UV index sensors
*
* Copyright 2018 Maxime Roussin - Belanger < maxime . roussinbelanger @ gmail . com >
*/
# include <linux/delay.h>
# include <linux/i2c.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/regmap.h>
# include <linux/iio/iio.h>
# include <linux/iio/sysfs.h>
# include <linux/util_macros.h>
2020-04-21 03:31:28 +03:00
# include <asm/unaligned.h>
2018-07-19 23:26:25 +03:00
# define SI1133_REG_PART_ID 0x00
# define SI1133_REG_REV_ID 0x01
# define SI1133_REG_MFR_ID 0x02
# define SI1133_REG_INFO0 0x03
# define SI1133_REG_INFO1 0x04
# define SI1133_PART_ID 0x33
# define SI1133_REG_HOSTIN0 0x0A
# define SI1133_REG_COMMAND 0x0B
# define SI1133_REG_IRQ_ENABLE 0x0F
# define SI1133_REG_RESPONSE1 0x10
# define SI1133_REG_RESPONSE0 0x11
# define SI1133_REG_IRQ_STATUS 0x12
# define SI1133_REG_MEAS_RATE 0x1A
# define SI1133_IRQ_CHANNEL_ENABLE 0xF
# define SI1133_CMD_RESET_CTR 0x00
# define SI1133_CMD_RESET_SW 0x01
# define SI1133_CMD_FORCE 0x11
# define SI1133_CMD_START_AUTONOMOUS 0x13
# define SI1133_CMD_PARAM_SET 0x80
# define SI1133_CMD_PARAM_QUERY 0x40
# define SI1133_CMD_PARAM_MASK 0x3F
# define SI1133_CMD_ERR_MASK BIT(4)
# define SI1133_CMD_SEQ_MASK 0xF
# define SI1133_MAX_CMD_CTR 0xF
# define SI1133_PARAM_REG_CHAN_LIST 0x01
# define SI1133_PARAM_REG_ADCCONFIG(x) ((x) * 4) + 2
# define SI1133_PARAM_REG_ADCSENS(x) ((x) * 4) + 3
# define SI1133_PARAM_REG_ADCPOST(x) ((x) * 4) + 4
# define SI1133_ADCMUX_MASK 0x1F
# define SI1133_ADCCONFIG_DECIM_RATE(x) (x) << 5
# define SI1133_ADCSENS_SCALE_MASK 0x70
# define SI1133_ADCSENS_SCALE_SHIFT 4
# define SI1133_ADCSENS_HSIG_MASK BIT(7)
# define SI1133_ADCSENS_HSIG_SHIFT 7
# define SI1133_ADCSENS_HW_GAIN_MASK 0xF
# define SI1133_ADCSENS_NB_MEAS(x) fls(x) << SI1133_ADCSENS_SCALE_SHIFT
# define SI1133_ADCPOST_24BIT_EN BIT(6)
# define SI1133_ADCPOST_POSTSHIFT_BITQTY(x) (x & GENMASK(2, 0)) << 3
# define SI1133_PARAM_ADCMUX_SMALL_IR 0x0
# define SI1133_PARAM_ADCMUX_MED_IR 0x1
# define SI1133_PARAM_ADCMUX_LARGE_IR 0x2
# define SI1133_PARAM_ADCMUX_WHITE 0xB
# define SI1133_PARAM_ADCMUX_LARGE_WHITE 0xD
# define SI1133_PARAM_ADCMUX_UV 0x18
# define SI1133_PARAM_ADCMUX_UV_DEEP 0x19
# define SI1133_ERR_INVALID_CMD 0x0
# define SI1133_ERR_INVALID_LOCATION_CMD 0x1
# define SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION 0x2
# define SI1133_ERR_OUTPUT_BUFFER_OVERFLOW 0x3
# define SI1133_COMPLETION_TIMEOUT_MS 500
# define SI1133_CMD_MINSLEEP_US_LOW 5000
# define SI1133_CMD_MINSLEEP_US_HIGH 7500
# define SI1133_CMD_TIMEOUT_MS 25
# define SI1133_CMD_LUX_TIMEOUT_MS 5000
# define SI1133_CMD_TIMEOUT_US SI1133_CMD_TIMEOUT_MS * 1000
# define SI1133_REG_HOSTOUT(x) (x) + 0x13
# define SI1133_MEASUREMENT_FREQUENCY 1250
# define SI1133_X_ORDER_MASK 0x0070
# define SI1133_Y_ORDER_MASK 0x0007
# define si1133_get_x_order(m) ((m) & SI1133_X_ORDER_MASK) >> 4
# define si1133_get_y_order(m) ((m) & SI1133_Y_ORDER_MASK)
# define SI1133_LUX_ADC_MASK 0xE
# define SI1133_ADC_THRESHOLD 16000
# define SI1133_INPUT_FRACTION_HIGH 7
# define SI1133_INPUT_FRACTION_LOW 15
# define SI1133_LUX_OUTPUT_FRACTION 12
# define SI1133_LUX_BUFFER_SIZE 9
2020-02-19 20:40:08 +03:00
# define SI1133_MEASURE_BUFFER_SIZE 3
2018-07-19 23:26:25 +03:00
static const int si1133_scale_available [ ] = {
1 , 2 , 4 , 8 , 16 , 32 , 64 , 128 } ;
static IIO_CONST_ATTR ( scale_available , " 1 2 4 8 16 32 64 128 " ) ;
static IIO_CONST_ATTR_INT_TIME_AVAIL ( " 0.0244 0.0488 0.0975 0.195 0.390 0.780 "
" 1.560 3.120 6.24 12.48 25.0 50.0 " ) ;
/* A.K.A. HW_GAIN in datasheet */
enum si1133_int_time {
_24_4_us = 0 ,
_48_8_us = 1 ,
_97_5_us = 2 ,
_195_0_us = 3 ,
_390_0_us = 4 ,
_780_0_us = 5 ,
_1_560_0_us = 6 ,
_3_120_0_us = 7 ,
_6_240_0_us = 8 ,
_12_480_0_us = 9 ,
_25_ms = 10 ,
_50_ms = 11 ,
} ;
/* Integration time in milliseconds, nanoseconds */
static const int si1133_int_time_table [ ] [ 2 ] = {
[ _24_4_us ] = { 0 , 24400 } ,
[ _48_8_us ] = { 0 , 48800 } ,
[ _97_5_us ] = { 0 , 97500 } ,
[ _195_0_us ] = { 0 , 195000 } ,
[ _390_0_us ] = { 0 , 390000 } ,
[ _780_0_us ] = { 0 , 780000 } ,
[ _1_560_0_us ] = { 1 , 560000 } ,
[ _3_120_0_us ] = { 3 , 120000 } ,
[ _6_240_0_us ] = { 6 , 240000 } ,
[ _12_480_0_us ] = { 12 , 480000 } ,
[ _25_ms ] = { 25 , 000000 } ,
[ _50_ms ] = { 50 , 000000 } ,
} ;
static const struct regmap_range si1133_reg_ranges [ ] = {
regmap_reg_range ( 0x00 , 0x02 ) ,
regmap_reg_range ( 0x0A , 0x0B ) ,
regmap_reg_range ( 0x0F , 0x0F ) ,
regmap_reg_range ( 0x10 , 0x12 ) ,
regmap_reg_range ( 0x13 , 0x2C ) ,
} ;
static const struct regmap_range si1133_reg_ro_ranges [ ] = {
regmap_reg_range ( 0x00 , 0x02 ) ,
regmap_reg_range ( 0x10 , 0x2C ) ,
} ;
static const struct regmap_range si1133_precious_ranges [ ] = {
regmap_reg_range ( 0x12 , 0x12 ) ,
} ;
static const struct regmap_access_table si1133_write_ranges_table = {
. yes_ranges = si1133_reg_ranges ,
. n_yes_ranges = ARRAY_SIZE ( si1133_reg_ranges ) ,
. no_ranges = si1133_reg_ro_ranges ,
. n_no_ranges = ARRAY_SIZE ( si1133_reg_ro_ranges ) ,
} ;
static const struct regmap_access_table si1133_read_ranges_table = {
. yes_ranges = si1133_reg_ranges ,
. n_yes_ranges = ARRAY_SIZE ( si1133_reg_ranges ) ,
} ;
static const struct regmap_access_table si1133_precious_table = {
. yes_ranges = si1133_precious_ranges ,
. n_yes_ranges = ARRAY_SIZE ( si1133_precious_ranges ) ,
} ;
static const struct regmap_config si1133_regmap_config = {
. reg_bits = 8 ,
. val_bits = 8 ,
. max_register = 0x2C ,
. wr_table = & si1133_write_ranges_table ,
. rd_table = & si1133_read_ranges_table ,
. precious_table = & si1133_precious_table ,
} ;
struct si1133_data {
struct regmap * regmap ;
struct i2c_client * client ;
/* Lock protecting one command at a time can be processed */
struct mutex mutex ;
int rsp_seq ;
u8 scan_mask ;
u8 adc_sens [ 6 ] ;
u8 adc_config [ 6 ] ;
struct completion completion ;
} ;
struct si1133_coeff {
s16 info ;
u16 mag ;
} ;
struct si1133_lux_coeff {
struct si1133_coeff coeff_high [ 4 ] ;
struct si1133_coeff coeff_low [ 9 ] ;
} ;
static const struct si1133_lux_coeff lux_coeff = {
{
{ 0 , 209 } ,
{ 1665 , 93 } ,
{ 2064 , 65 } ,
{ - 2671 , 234 }
} ,
{
{ 0 , 0 } ,
{ 1921 , 29053 } ,
{ - 1022 , 36363 } ,
{ 2320 , 20789 } ,
{ - 367 , 57909 } ,
{ - 1774 , 38240 } ,
{ - 608 , 46775 } ,
{ - 1503 , 51831 } ,
{ - 1886 , 58928 }
}
} ;
2020-02-19 20:40:08 +03:00
static int si1133_calculate_polynomial_inner ( s32 input , u8 fraction , u16 mag ,
2018-07-19 23:26:25 +03:00
s8 shift )
{
return ( ( input < < fraction ) / mag ) < < shift ;
}
2020-02-19 20:40:08 +03:00
static int si1133_calculate_output ( s32 x , s32 y , u8 x_order , u8 y_order ,
2018-07-19 23:26:25 +03:00
u8 input_fraction , s8 sign ,
const struct si1133_coeff * coeffs )
{
s8 shift ;
int x1 = 1 ;
int x2 = 1 ;
int y1 = 1 ;
int y2 = 1 ;
shift = ( ( u16 ) coeffs - > info & 0xFF00 ) > > 8 ;
shift ^ = 0xFF ;
shift + = 1 ;
shift = - shift ;
if ( x_order > 0 ) {
x1 = si1133_calculate_polynomial_inner ( x , input_fraction ,
coeffs - > mag , shift ) ;
if ( x_order > 1 )
x2 = x1 ;
}
if ( y_order > 0 ) {
y1 = si1133_calculate_polynomial_inner ( y , input_fraction ,
coeffs - > mag , shift ) ;
if ( y_order > 1 )
y2 = y1 ;
}
return sign * x1 * x2 * y1 * y2 ;
}
/*
* The algorithm is from :
* https : //siliconlabs.github.io/Gecko_SDK_Doc/efm32zg/html/si1133_8c_source.html#l00716
*/
2020-02-19 20:40:08 +03:00
static int si1133_calc_polynomial ( s32 x , s32 y , u8 input_fraction , u8 num_coeff ,
2018-07-19 23:26:25 +03:00
const struct si1133_coeff * coeffs )
{
u8 x_order , y_order ;
u8 counter ;
s8 sign ;
int output = 0 ;
for ( counter = 0 ; counter < num_coeff ; counter + + ) {
if ( coeffs - > info < 0 )
sign = - 1 ;
else
sign = 1 ;
x_order = si1133_get_x_order ( coeffs - > info ) ;
y_order = si1133_get_y_order ( coeffs - > info ) ;
if ( ( x_order = = 0 ) & & ( y_order = = 0 ) )
output + =
sign * coeffs - > mag < < SI1133_LUX_OUTPUT_FRACTION ;
else
output + = si1133_calculate_output ( x , y , x_order ,
y_order ,
input_fraction , sign ,
coeffs ) ;
coeffs + + ;
}
return abs ( output ) ;
}
static int si1133_cmd_reset_sw ( struct si1133_data * data )
{
struct device * dev = & data - > client - > dev ;
unsigned int resp ;
unsigned long timeout ;
int err ;
err = regmap_write ( data - > regmap , SI1133_REG_COMMAND ,
SI1133_CMD_RESET_SW ) ;
if ( err )
return err ;
timeout = jiffies + msecs_to_jiffies ( SI1133_CMD_TIMEOUT_MS ) ;
while ( true ) {
err = regmap_read ( data - > regmap , SI1133_REG_RESPONSE0 , & resp ) ;
if ( err = = - ENXIO ) {
usleep_range ( SI1133_CMD_MINSLEEP_US_LOW ,
SI1133_CMD_MINSLEEP_US_HIGH ) ;
continue ;
}
if ( ( resp & SI1133_MAX_CMD_CTR ) = = SI1133_MAX_CMD_CTR )
break ;
if ( time_after ( jiffies , timeout ) ) {
dev_warn ( dev , " Timeout on reset ctr resp: %d \n " , resp ) ;
return - ETIMEDOUT ;
}
}
if ( ! err )
data - > rsp_seq = SI1133_MAX_CMD_CTR ;
return err ;
}
static int si1133_parse_response_err ( struct device * dev , u32 resp , u8 cmd )
{
resp & = 0xF ;
switch ( resp ) {
case SI1133_ERR_OUTPUT_BUFFER_OVERFLOW :
2021-06-03 21:06:10 +03:00
dev_warn ( dev , " Output buffer overflow: 0x%02x \n " , cmd ) ;
2018-07-19 23:26:25 +03:00
return - EOVERFLOW ;
case SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION :
2021-06-03 21:06:10 +03:00
dev_warn ( dev , " Saturation of the ADC or overflow of accumulation: 0x%02x \n " ,
2018-07-19 23:26:25 +03:00
cmd ) ;
return - EOVERFLOW ;
case SI1133_ERR_INVALID_LOCATION_CMD :
dev_warn ( dev ,
2021-06-03 21:06:10 +03:00
" Parameter access to an invalid location: 0x%02x \n " ,
2018-07-19 23:26:25 +03:00
cmd ) ;
return - EINVAL ;
case SI1133_ERR_INVALID_CMD :
2021-06-03 21:06:10 +03:00
dev_warn ( dev , " Invalid command 0x%02x \n " , cmd ) ;
2018-07-19 23:26:25 +03:00
return - EINVAL ;
default :
2021-06-03 21:06:10 +03:00
dev_warn ( dev , " Unknown error 0x%02x \n " , cmd ) ;
2018-07-19 23:26:25 +03:00
return - EINVAL ;
}
}
static int si1133_cmd_reset_counter ( struct si1133_data * data )
{
int err = regmap_write ( data - > regmap , SI1133_REG_COMMAND ,
SI1133_CMD_RESET_CTR ) ;
if ( err )
return err ;
data - > rsp_seq = 0 ;
return 0 ;
}
static int si1133_command ( struct si1133_data * data , u8 cmd )
{
struct device * dev = & data - > client - > dev ;
u32 resp ;
int err ;
int expected_seq ;
mutex_lock ( & data - > mutex ) ;
expected_seq = ( data - > rsp_seq + 1 ) & SI1133_MAX_CMD_CTR ;
if ( cmd = = SI1133_CMD_FORCE )
reinit_completion ( & data - > completion ) ;
err = regmap_write ( data - > regmap , SI1133_REG_COMMAND , cmd ) ;
if ( err ) {
2021-06-03 21:06:10 +03:00
dev_warn ( dev , " Failed to write command 0x%02x, ret=%d \n " , cmd ,
2018-07-19 23:26:25 +03:00
err ) ;
goto out ;
}
if ( cmd = = SI1133_CMD_FORCE ) {
/* wait for irq */
if ( ! wait_for_completion_timeout ( & data - > completion ,
msecs_to_jiffies ( SI1133_COMPLETION_TIMEOUT_MS ) ) ) {
err = - ETIMEDOUT ;
goto out ;
}
2018-07-31 23:13:21 +03:00
err = regmap_read ( data - > regmap , SI1133_REG_RESPONSE0 , & resp ) ;
if ( err )
goto out ;
2018-07-19 23:26:25 +03:00
} else {
err = regmap_read_poll_timeout ( data - > regmap ,
SI1133_REG_RESPONSE0 , resp ,
( resp & SI1133_CMD_SEQ_MASK ) = =
expected_seq | |
( resp & SI1133_CMD_ERR_MASK ) ,
SI1133_CMD_MINSLEEP_US_LOW ,
SI1133_CMD_TIMEOUT_MS * 1000 ) ;
if ( err ) {
dev_warn ( dev ,
2021-06-03 21:06:10 +03:00
" Failed to read command 0x%02x, ret=%d \n " ,
2018-07-19 23:26:25 +03:00
cmd , err ) ;
goto out ;
}
}
if ( resp & SI1133_CMD_ERR_MASK ) {
err = si1133_parse_response_err ( dev , resp , cmd ) ;
si1133_cmd_reset_counter ( data ) ;
} else {
data - > rsp_seq = expected_seq ;
}
out :
mutex_unlock ( & data - > mutex ) ;
return err ;
}
static int si1133_param_set ( struct si1133_data * data , u8 param , u32 value )
{
int err = regmap_write ( data - > regmap , SI1133_REG_HOSTIN0 , value ) ;
if ( err )
return err ;
return si1133_command ( data , SI1133_CMD_PARAM_SET |
( param & SI1133_CMD_PARAM_MASK ) ) ;
}
static int si1133_param_query ( struct si1133_data * data , u8 param , u32 * result )
{
int err = si1133_command ( data , SI1133_CMD_PARAM_QUERY |
( param & SI1133_CMD_PARAM_MASK ) ) ;
if ( err )
return err ;
return regmap_read ( data - > regmap , SI1133_REG_RESPONSE1 , result ) ;
}
# define SI1133_CHANNEL(_ch, _type) \
. type = _type , \
. channel = _ch , \
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) , \
. info_mask_shared_by_all = BIT ( IIO_CHAN_INFO_INT_TIME ) | \
BIT ( IIO_CHAN_INFO_SCALE ) | \
BIT ( IIO_CHAN_INFO_HARDWAREGAIN ) , \
static const struct iio_chan_spec si1133_channels [ ] = {
{
. type = IIO_LIGHT ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_PROCESSED ) ,
. channel = 0 ,
} ,
{
SI1133_CHANNEL ( SI1133_PARAM_ADCMUX_WHITE , IIO_INTENSITY )
. channel2 = IIO_MOD_LIGHT_BOTH ,
} ,
{
SI1133_CHANNEL ( SI1133_PARAM_ADCMUX_LARGE_WHITE , IIO_INTENSITY )
. channel2 = IIO_MOD_LIGHT_BOTH ,
. extend_name = " large " ,
} ,
{
SI1133_CHANNEL ( SI1133_PARAM_ADCMUX_SMALL_IR , IIO_INTENSITY )
. extend_name = " small " ,
. modified = 1 ,
. channel2 = IIO_MOD_LIGHT_IR ,
} ,
{
SI1133_CHANNEL ( SI1133_PARAM_ADCMUX_MED_IR , IIO_INTENSITY )
. modified = 1 ,
. channel2 = IIO_MOD_LIGHT_IR ,
} ,
{
SI1133_CHANNEL ( SI1133_PARAM_ADCMUX_LARGE_IR , IIO_INTENSITY )
. extend_name = " large " ,
. modified = 1 ,
. channel2 = IIO_MOD_LIGHT_IR ,
} ,
{
SI1133_CHANNEL ( SI1133_PARAM_ADCMUX_UV , IIO_UVINDEX )
} ,
{
SI1133_CHANNEL ( SI1133_PARAM_ADCMUX_UV_DEEP , IIO_UVINDEX )
. modified = 1 ,
. channel2 = IIO_MOD_LIGHT_DUV ,
}
} ;
static int si1133_get_int_time_index ( int milliseconds , int nanoseconds )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( si1133_int_time_table ) ; i + + ) {
if ( milliseconds = = si1133_int_time_table [ i ] [ 0 ] & &
nanoseconds = = si1133_int_time_table [ i ] [ 1 ] )
return i ;
}
return - EINVAL ;
}
static int si1133_set_integration_time ( struct si1133_data * data , u8 adc ,
int milliseconds , int nanoseconds )
{
int index ;
index = si1133_get_int_time_index ( milliseconds , nanoseconds ) ;
if ( index < 0 )
return index ;
data - > adc_sens [ adc ] & = 0xF0 ;
data - > adc_sens [ adc ] | = index ;
return si1133_param_set ( data , SI1133_PARAM_REG_ADCSENS ( 0 ) ,
data - > adc_sens [ adc ] ) ;
}
static int si1133_set_chlist ( struct si1133_data * data , u8 scan_mask )
{
/* channel list already set, no need to reprogram */
if ( data - > scan_mask = = scan_mask )
return 0 ;
data - > scan_mask = scan_mask ;
return si1133_param_set ( data , SI1133_PARAM_REG_CHAN_LIST , scan_mask ) ;
}
static int si1133_chan_set_adcconfig ( struct si1133_data * data , u8 adc ,
u8 adc_config )
{
int err ;
err = si1133_param_set ( data , SI1133_PARAM_REG_ADCCONFIG ( adc ) ,
adc_config ) ;
if ( err )
return err ;
data - > adc_config [ adc ] = adc_config ;
return 0 ;
}
static int si1133_update_adcconfig ( struct si1133_data * data , uint8_t adc ,
u8 mask , u8 shift , u8 value )
{
u32 adc_config ;
int err ;
err = si1133_param_query ( data , SI1133_PARAM_REG_ADCCONFIG ( adc ) ,
& adc_config ) ;
if ( err )
return err ;
adc_config & = ~ mask ;
adc_config | = ( value < < shift ) ;
return si1133_chan_set_adcconfig ( data , adc , adc_config ) ;
}
static int si1133_set_adcmux ( struct si1133_data * data , u8 adc , u8 mux )
{
if ( ( mux & data - > adc_config [ adc ] ) = = mux )
return 0 ; /* mux already set to correct value */
return si1133_update_adcconfig ( data , adc , SI1133_ADCMUX_MASK , 0 , mux ) ;
}
static int si1133_force_measurement ( struct si1133_data * data )
{
return si1133_command ( data , SI1133_CMD_FORCE ) ;
}
static int si1133_bulk_read ( struct si1133_data * data , u8 start_reg , u8 length ,
u8 * buffer )
{
int err ;
err = si1133_force_measurement ( data ) ;
if ( err )
return err ;
return regmap_bulk_read ( data - > regmap , start_reg , buffer , length ) ;
}
static int si1133_measure ( struct si1133_data * data ,
struct iio_chan_spec const * chan ,
int * val )
{
int err ;
2020-02-19 20:40:08 +03:00
u8 buffer [ SI1133_MEASURE_BUFFER_SIZE ] ;
2018-07-19 23:26:25 +03:00
err = si1133_set_adcmux ( data , 0 , chan - > channel ) ;
if ( err )
return err ;
/* Deactivate lux measurements if they were active */
err = si1133_set_chlist ( data , BIT ( 0 ) ) ;
if ( err )
return err ;
2020-02-19 20:40:08 +03:00
err = si1133_bulk_read ( data , SI1133_REG_HOSTOUT ( 0 ) , sizeof ( buffer ) ,
buffer ) ;
2018-07-19 23:26:25 +03:00
if ( err )
return err ;
2020-04-21 03:31:28 +03:00
* val = sign_extend32 ( get_unaligned_be24 ( & buffer [ 0 ] ) , 23 ) ;
2018-07-19 23:26:25 +03:00
return err ;
}
static irqreturn_t si1133_threaded_irq_handler ( int irq , void * private )
{
struct iio_dev * iio_dev = private ;
struct si1133_data * data = iio_priv ( iio_dev ) ;
u32 irq_status ;
int err ;
err = regmap_read ( data - > regmap , SI1133_REG_IRQ_STATUS , & irq_status ) ;
if ( err ) {
dev_err_ratelimited ( & iio_dev - > dev , " Error reading IRQ \n " ) ;
goto out ;
}
if ( irq_status ! = data - > scan_mask )
return IRQ_NONE ;
out :
complete ( & data - > completion ) ;
return IRQ_HANDLED ;
}
static int si1133_scale_to_swgain ( int scale_integer , int scale_fractional )
{
scale_integer = find_closest ( scale_integer , si1133_scale_available ,
ARRAY_SIZE ( si1133_scale_available ) ) ;
if ( scale_integer < 0 | |
scale_integer > ARRAY_SIZE ( si1133_scale_available ) | |
scale_fractional ! = 0 )
return - EINVAL ;
return scale_integer ;
}
static int si1133_chan_set_adcsens ( struct si1133_data * data , u8 adc ,
u8 adc_sens )
{
int err ;
err = si1133_param_set ( data , SI1133_PARAM_REG_ADCSENS ( adc ) , adc_sens ) ;
if ( err )
return err ;
data - > adc_sens [ adc ] = adc_sens ;
return 0 ;
}
static int si1133_update_adcsens ( struct si1133_data * data , u8 mask ,
u8 shift , u8 value )
{
int err ;
u32 adc_sens ;
err = si1133_param_query ( data , SI1133_PARAM_REG_ADCSENS ( 0 ) ,
& adc_sens ) ;
if ( err )
return err ;
adc_sens & = ~ mask ;
adc_sens | = ( value < < shift ) ;
return si1133_chan_set_adcsens ( data , 0 , adc_sens ) ;
}
static int si1133_get_lux ( struct si1133_data * data , int * val )
{
int err ;
int lux ;
2020-02-19 20:40:08 +03:00
s32 high_vis ;
s32 low_vis ;
s32 ir ;
2018-07-19 23:26:25 +03:00
u8 buffer [ SI1133_LUX_BUFFER_SIZE ] ;
/* Activate lux channels */
err = si1133_set_chlist ( data , SI1133_LUX_ADC_MASK ) ;
if ( err )
return err ;
err = si1133_bulk_read ( data , SI1133_REG_HOSTOUT ( 0 ) ,
SI1133_LUX_BUFFER_SIZE , buffer ) ;
if ( err )
return err ;
2020-04-21 03:31:28 +03:00
high_vis = sign_extend32 ( get_unaligned_be24 ( & buffer [ 0 ] ) , 23 ) ;
2020-02-19 20:40:08 +03:00
2020-04-21 03:31:28 +03:00
low_vis = sign_extend32 ( get_unaligned_be24 ( & buffer [ 3 ] ) , 23 ) ;
2020-02-19 20:40:08 +03:00
2020-04-21 03:31:28 +03:00
ir = sign_extend32 ( get_unaligned_be24 ( & buffer [ 6 ] ) , 23 ) ;
2018-07-19 23:26:25 +03:00
if ( high_vis > SI1133_ADC_THRESHOLD | | ir > SI1133_ADC_THRESHOLD )
lux = si1133_calc_polynomial ( high_vis , ir ,
SI1133_INPUT_FRACTION_HIGH ,
ARRAY_SIZE ( lux_coeff . coeff_high ) ,
& lux_coeff . coeff_high [ 0 ] ) ;
else
lux = si1133_calc_polynomial ( low_vis , ir ,
SI1133_INPUT_FRACTION_LOW ,
ARRAY_SIZE ( lux_coeff . coeff_low ) ,
& lux_coeff . coeff_low [ 0 ] ) ;
* val = lux > > SI1133_LUX_OUTPUT_FRACTION ;
return err ;
}
static int si1133_read_raw ( struct iio_dev * iio_dev ,
struct iio_chan_spec const * chan ,
int * val , int * val2 , long mask )
{
struct si1133_data * data = iio_priv ( iio_dev ) ;
u8 adc_sens = data - > adc_sens [ 0 ] ;
int err ;
switch ( mask ) {
case IIO_CHAN_INFO_PROCESSED :
switch ( chan - > type ) {
case IIO_LIGHT :
err = si1133_get_lux ( data , val ) ;
if ( err )
return err ;
return IIO_VAL_INT ;
default :
return - EINVAL ;
}
case IIO_CHAN_INFO_RAW :
switch ( chan - > type ) {
case IIO_INTENSITY :
case IIO_UVINDEX :
err = si1133_measure ( data , chan , val ) ;
if ( err )
return err ;
return IIO_VAL_INT ;
default :
return - EINVAL ;
}
case IIO_CHAN_INFO_INT_TIME :
switch ( chan - > type ) {
case IIO_INTENSITY :
case IIO_UVINDEX :
adc_sens & = SI1133_ADCSENS_HW_GAIN_MASK ;
* val = si1133_int_time_table [ adc_sens ] [ 0 ] ;
* val2 = si1133_int_time_table [ adc_sens ] [ 1 ] ;
return IIO_VAL_INT_PLUS_MICRO ;
default :
return - EINVAL ;
}
case IIO_CHAN_INFO_SCALE :
switch ( chan - > type ) {
case IIO_INTENSITY :
case IIO_UVINDEX :
adc_sens & = SI1133_ADCSENS_SCALE_MASK ;
adc_sens > > = SI1133_ADCSENS_SCALE_SHIFT ;
* val = BIT ( adc_sens ) ;
return IIO_VAL_INT ;
default :
return - EINVAL ;
}
case IIO_CHAN_INFO_HARDWAREGAIN :
switch ( chan - > type ) {
case IIO_INTENSITY :
case IIO_UVINDEX :
adc_sens > > = SI1133_ADCSENS_HSIG_SHIFT ;
* val = adc_sens ;
return IIO_VAL_INT ;
default :
return - EINVAL ;
}
default :
return - EINVAL ;
}
}
static int si1133_write_raw ( struct iio_dev * iio_dev ,
struct iio_chan_spec const * chan ,
int val , int val2 , long mask )
{
struct si1133_data * data = iio_priv ( iio_dev ) ;
switch ( mask ) {
case IIO_CHAN_INFO_SCALE :
switch ( chan - > type ) {
case IIO_INTENSITY :
case IIO_UVINDEX :
val = si1133_scale_to_swgain ( val , val2 ) ;
if ( val < 0 )
return val ;
return si1133_update_adcsens ( data ,
SI1133_ADCSENS_SCALE_MASK ,
SI1133_ADCSENS_SCALE_SHIFT ,
val ) ;
default :
return - EINVAL ;
}
case IIO_CHAN_INFO_INT_TIME :
return si1133_set_integration_time ( data , 0 , val , val2 ) ;
case IIO_CHAN_INFO_HARDWAREGAIN :
switch ( chan - > type ) {
case IIO_INTENSITY :
case IIO_UVINDEX :
2018-07-30 15:59:18 +03:00
if ( val ! = 0 & & val ! = 1 )
2018-07-19 23:26:25 +03:00
return - EINVAL ;
return si1133_update_adcsens ( data ,
SI1133_ADCSENS_HSIG_MASK ,
SI1133_ADCSENS_HSIG_SHIFT ,
val ) ;
default :
return - EINVAL ;
}
default :
return - EINVAL ;
}
}
static struct attribute * si1133_attributes [ ] = {
& iio_const_attr_integration_time_available . dev_attr . attr ,
& iio_const_attr_scale_available . dev_attr . attr ,
NULL ,
} ;
static const struct attribute_group si1133_attribute_group = {
. attrs = si1133_attributes ,
} ;
static const struct iio_info si1133_info = {
. read_raw = si1133_read_raw ,
. write_raw = si1133_write_raw ,
. attrs = & si1133_attribute_group ,
} ;
/*
* si1133_init_lux_channels - Configure 3 different channels ( adc ) ( 1 , 2 and 3 )
* The channel configuration for the lux measurement was taken from :
* https : //siliconlabs.github.io/Gecko_SDK_Doc/efm32zg/html/si1133_8c_source.html#l00578
*
* Reserved the channel 0 for the other raw measurements
*/
static int si1133_init_lux_channels ( struct si1133_data * data )
{
int err ;
err = si1133_chan_set_adcconfig ( data , 1 ,
SI1133_ADCCONFIG_DECIM_RATE ( 1 ) |
SI1133_PARAM_ADCMUX_LARGE_WHITE ) ;
if ( err )
return err ;
err = si1133_param_set ( data , SI1133_PARAM_REG_ADCPOST ( 1 ) ,
SI1133_ADCPOST_24BIT_EN |
SI1133_ADCPOST_POSTSHIFT_BITQTY ( 0 ) ) ;
if ( err )
return err ;
err = si1133_chan_set_adcsens ( data , 1 , SI1133_ADCSENS_HSIG_MASK |
SI1133_ADCSENS_NB_MEAS ( 64 ) | _48_8_us ) ;
if ( err )
return err ;
err = si1133_chan_set_adcconfig ( data , 2 ,
SI1133_ADCCONFIG_DECIM_RATE ( 1 ) |
SI1133_PARAM_ADCMUX_LARGE_WHITE ) ;
if ( err )
return err ;
err = si1133_param_set ( data , SI1133_PARAM_REG_ADCPOST ( 2 ) ,
SI1133_ADCPOST_24BIT_EN |
SI1133_ADCPOST_POSTSHIFT_BITQTY ( 2 ) ) ;
if ( err )
return err ;
err = si1133_chan_set_adcsens ( data , 2 , SI1133_ADCSENS_HSIG_MASK |
SI1133_ADCSENS_NB_MEAS ( 1 ) | _3_120_0_us ) ;
if ( err )
return err ;
err = si1133_chan_set_adcconfig ( data , 3 ,
SI1133_ADCCONFIG_DECIM_RATE ( 1 ) |
SI1133_PARAM_ADCMUX_MED_IR ) ;
if ( err )
return err ;
err = si1133_param_set ( data , SI1133_PARAM_REG_ADCPOST ( 3 ) ,
SI1133_ADCPOST_24BIT_EN |
SI1133_ADCPOST_POSTSHIFT_BITQTY ( 2 ) ) ;
if ( err )
return err ;
return si1133_chan_set_adcsens ( data , 3 , SI1133_ADCSENS_HSIG_MASK |
SI1133_ADCSENS_NB_MEAS ( 64 ) | _48_8_us ) ;
}
static int si1133_initialize ( struct si1133_data * data )
{
int err ;
err = si1133_cmd_reset_sw ( data ) ;
if ( err )
return err ;
/* Turn off autonomous mode */
err = si1133_param_set ( data , SI1133_REG_MEAS_RATE , 0 ) ;
if ( err )
return err ;
err = si1133_init_lux_channels ( data ) ;
if ( err )
return err ;
return regmap_write ( data - > regmap , SI1133_REG_IRQ_ENABLE ,
SI1133_IRQ_CHANNEL_ENABLE ) ;
}
static int si1133_validate_ids ( struct iio_dev * iio_dev )
{
struct si1133_data * data = iio_priv ( iio_dev ) ;
unsigned int part_id , rev_id , mfr_id ;
int err ;
err = regmap_read ( data - > regmap , SI1133_REG_PART_ID , & part_id ) ;
if ( err )
return err ;
err = regmap_read ( data - > regmap , SI1133_REG_REV_ID , & rev_id ) ;
if ( err )
return err ;
err = regmap_read ( data - > regmap , SI1133_REG_MFR_ID , & mfr_id ) ;
if ( err )
return err ;
dev_info ( & iio_dev - > dev ,
2021-06-03 21:06:09 +03:00
" Device ID part 0x%02x rev 0x%02x mfr 0x%02x \n " ,
2018-07-19 23:26:25 +03:00
part_id , rev_id , mfr_id ) ;
if ( part_id ! = SI1133_PART_ID ) {
dev_err ( & iio_dev - > dev ,
2021-06-03 21:06:09 +03:00
" Part ID mismatch got 0x%02x, expected 0x%02x \n " ,
2018-07-19 23:26:25 +03:00
part_id , SI1133_PART_ID ) ;
return - ENODEV ;
}
return 0 ;
}
static int si1133_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct si1133_data * data ;
struct iio_dev * iio_dev ;
int err ;
iio_dev = devm_iio_device_alloc ( & client - > dev , sizeof ( * data ) ) ;
if ( ! iio_dev )
return - ENOMEM ;
data = iio_priv ( iio_dev ) ;
init_completion ( & data - > completion ) ;
data - > regmap = devm_regmap_init_i2c ( client , & si1133_regmap_config ) ;
if ( IS_ERR ( data - > regmap ) ) {
err = PTR_ERR ( data - > regmap ) ;
dev_err ( & client - > dev , " Failed to initialise regmap: %d \n " , err ) ;
return err ;
}
i2c_set_clientdata ( client , iio_dev ) ;
data - > client = client ;
iio_dev - > name = id - > name ;
iio_dev - > channels = si1133_channels ;
iio_dev - > num_channels = ARRAY_SIZE ( si1133_channels ) ;
iio_dev - > info = & si1133_info ;
iio_dev - > modes = INDIO_DIRECT_MODE ;
mutex_init ( & data - > mutex ) ;
err = si1133_validate_ids ( iio_dev ) ;
if ( err )
return err ;
err = si1133_initialize ( data ) ;
if ( err ) {
dev_err ( & client - > dev ,
" Error when initializing chip: %d \n " , err ) ;
return err ;
}
if ( ! client - > irq ) {
dev_err ( & client - > dev ,
" Required interrupt not provided, cannot proceed \n " ) ;
return - EINVAL ;
}
err = devm_request_threaded_irq ( & client - > dev , client - > irq ,
NULL ,
si1133_threaded_irq_handler ,
IRQF_ONESHOT | IRQF_SHARED ,
client - > name , iio_dev ) ;
if ( err ) {
dev_warn ( & client - > dev , " Request irq %d failed: %i \n " ,
client - > irq , err ) ;
return err ;
}
return devm_iio_device_register ( & client - > dev , iio_dev ) ;
}
static const struct i2c_device_id si1133_ids [ ] = {
{ " si1133 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , si1133_ids ) ;
static struct i2c_driver si1133_driver = {
. driver = {
. name = " si1133 " ,
} ,
. probe = si1133_probe ,
. id_table = si1133_ids ,
} ;
module_i2c_driver ( si1133_driver ) ;
MODULE_AUTHOR ( " Maxime Roussin-Belanger <maxime.roussinbelanger@gmail.com> " ) ;
MODULE_DESCRIPTION ( " Silabs SI1133, UV index sensor and ambient light sensor driver " ) ;
MODULE_LICENSE ( " GPL " ) ;