// 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>
#include <linux/unaligned.h>
#define SI1133_REG_PART_ID 0 x00
#define SI1133_REG_REV_ID 0 x01
#define SI1133_REG_MFR_ID 0 x02
#define SI1133_REG_INFO0 0 x03
#define SI1133_REG_INFO1 0 x04
#define SI1133_PART_ID 0 x33
#define SI1133_REG_HOSTIN0 0 x0A
#define SI1133_REG_COMMAND 0 x0B
#define SI1133_REG_IRQ_ENABLE 0 x0F
#define SI1133_REG_RESPONSE1 0 x10
#define SI1133_REG_RESPONSE0 0 x11
#define SI1133_REG_IRQ_STATUS 0 x12
#define SI1133_REG_MEAS_RATE 0 x1A
#define SI1133_IRQ_CHANNEL_ENABLE 0 xF
#define SI1133_CMD_RESET_CTR 0 x00
#define SI1133_CMD_RESET_SW 0 x01
#define SI1133_CMD_FORCE 0 x11
#define SI1133_CMD_START_AUTONOMOUS 0 x13
#define SI1133_CMD_PARAM_SET 0 x80
#define SI1133_CMD_PARAM_QUERY 0 x40
#define SI1133_CMD_PARAM_MASK 0 x3F
#define SI1133_CMD_ERR_MASK BIT(4 )
#define SI1133_CMD_SEQ_MASK 0 xF
#define SI1133_MAX_CMD_CTR 0 xF
#define SI1133_PARAM_REG_CHAN_LIST 0 x01
#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 0 x1F
#define SI1133_ADCCONFIG_DECIM_RATE(x) (x) << 5
#define SI1133_ADCSENS_SCALE_MASK 0 x70
#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 0 xF
#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 0 x0
#define SI1133_PARAM_ADCMUX_MED_IR 0 x1
#define SI1133_PARAM_ADCMUX_LARGE_IR 0 x2
#define SI1133_PARAM_ADCMUX_WHITE 0 xB
#define SI1133_PARAM_ADCMUX_LARGE_WHITE 0 xD
#define SI1133_PARAM_ADCMUX_UV 0 x18
#define SI1133_PARAM_ADCMUX_UV_DEEP 0 x19
#define SI1133_ERR_INVALID_CMD 0 x0
#define SI1133_ERR_INVALID_LOCATION_CMD 0 x1
#define SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION 0 x2
#define SI1133_ERR_OUTPUT_BUFFER_OVERFLOW 0 x3
#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) + 0 x13
#define SI1133_MEASUREMENT_FREQUENCY 1250
#define SI1133_X_ORDER_MASK 0 x0070
#define SI1133_Y_ORDER_MASK 0 x0007
#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 0 xE
#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
#define SI1133_MEASURE_BUFFER_SIZE 3
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(0 x00, 0 x02),
regmap_reg_range(0 x0A, 0 x0B),
regmap_reg_range(0 x0F, 0 x0F),
regmap_reg_range(0 x10, 0 x12),
regmap_reg_range(0 x13, 0 x2C),
};
static const struct regmap_range si1133_reg_ro_ranges[] = {
regmap_reg_range(0 x00, 0 x02),
regmap_reg_range(0 x10, 0 x2C),
};
static const struct regmap_range si1133_precious_ranges[] = {
regmap_reg_range(0 x12, 0 x12),
};
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 = 0 x2C,
.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 }
}
};
static int si1133_calculate_polynomial_inner(s32 input, u8 fraction, u16 mag,
s8 shift)
{
return ((input << fraction) / mag) << shift;
}
static int si1133_calculate_output(s32 x, s32 y, u8 x_order, u8 y_order,
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 & 0 xFF00) >> 8 ;
shift ^= 0 xFF;
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
*/
static int si1133_calc_polynomial(s32 x, s32 y, u8 input_fraction, u8 num_coeff,
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 &= 0 xF;
switch (resp) {
case SI1133_ERR_OUTPUT_BUFFER_OVERFLOW:
dev_warn(dev, "Output buffer overflow: 0x%02x\n" , cmd);
return -EOVERFLOW;
case SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION:
dev_warn(dev, "Saturation of the ADC or overflow of accumulation: 0x%02x\n" ,
cmd);
return -EOVERFLOW;
case SI1133_ERR_INVALID_LOCATION_CMD:
dev_warn(dev,
"Parameter access to an invalid location: 0x%02x\n" ,
cmd);
return -EINVAL;
case SI1133_ERR_INVALID_CMD:
dev_warn(dev, "Invalid command 0x%02x\n" , cmd);
return -EINVAL;
default :
dev_warn(dev, "Unknown error 0x%02x\n" , cmd);
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) {
dev_warn(dev, "Failed to write command 0x%02x, ret=%d\n" , cmd,
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;
}
err = regmap_read(data->regmap, SI1133_REG_RESPONSE0, &resp);
if (err)
goto out;
} 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,
"Failed to read command 0x%02x, ret=%d\n" ,
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] &= 0 xF0;
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;
u8 buffer[SI1133_MEASURE_BUFFER_SIZE];
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;
err = si1133_bulk_read(data, SI1133_REG_HOSTOUT(0 ), sizeof (buffer),
buffer);
if (err)
return err;
*val = sign_extend32(get_unaligned_be24(&buffer[0 ]), 23 );
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;
s32 high_vis;
s32 low_vis;
s32 ir;
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;
high_vis = sign_extend32(get_unaligned_be24(&buffer[0 ]), 23 );
low_vis = sign_extend32(get_unaligned_be24(&buffer[3 ]), 23 );
ir = sign_extend32(get_unaligned_be24(&buffer[6 ]), 23 );
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:
if (val != 0 && val != 1 )
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,
"Device ID part 0x%02x rev 0x%02x mfr 0x%02x\n" ,
part_id, rev_id, mfr_id);
if (part_id != SI1133_PART_ID) {
dev_err(&iio_dev->dev,
"Part ID mismatch got 0x%02x, expected 0x%02x\n" ,
part_id, SI1133_PART_ID);
return -ENODEV;
}
return 0 ;
}
static int si1133_probe(struct i2c_client *client)
{
const struct i2c_device_id *id = i2c_client_get_device_id(client);
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" },
{ }
};
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" );
Messung V0.5 in Prozent C=95 H=97 G=95
¤ Dauer der Verarbeitung: 0.4 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland