forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1075 lines
25 KiB
1075 lines
25 KiB
// SPDX-License-Identifier: GPL-2.0+ |
|
/* |
|
* si1133.c - Support for Silabs SI1133 combined ambient |
|
* light and UV index sensors |
|
* |
|
* Copyright 2018 Maxime Roussin-Belanger <[email protected]> |
|
*/ |
|
|
|
#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 <asm/unaligned.h> |
|
|
|
#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 |
|
#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(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} |
|
} |
|
}; |
|
|
|
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 & 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 |
|
*/ |
|
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 &= 0xF; |
|
|
|
switch (resp) { |
|
case SI1133_ERR_OUTPUT_BUFFER_OVERFLOW: |
|
dev_warn(dev, "Output buffer overflow: %#02hhx\n", cmd); |
|
return -EOVERFLOW; |
|
case SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION: |
|
dev_warn(dev, "Saturation of the ADC or overflow of accumulation: %#02hhx\n", |
|
cmd); |
|
return -EOVERFLOW; |
|
case SI1133_ERR_INVALID_LOCATION_CMD: |
|
dev_warn(dev, |
|
"Parameter access to an invalid location: %#02hhx\n", |
|
cmd); |
|
return -EINVAL; |
|
case SI1133_ERR_INVALID_CMD: |
|
dev_warn(dev, "Invalid command %#02hhx\n", cmd); |
|
return -EINVAL; |
|
default: |
|
dev_warn(dev, "Unknown error %#02hhx\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 %#02hhx, 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 %#02hhx, 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] &= 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; |
|
|
|
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 %#02hhx rev %#02hhx mfr %#02hhx\n", |
|
part_id, rev_id, mfr_id); |
|
if (part_id != SI1133_PART_ID) { |
|
dev_err(&iio_dev->dev, |
|
"Part ID mismatch got %#02hhx, expected %#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) |
|
{ |
|
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 <[email protected]>"); |
|
MODULE_DESCRIPTION("Silabs SI1133, UV index sensor and ambient light sensor driver"); |
|
MODULE_LICENSE("GPL");
|
|
|