mirror of
https://github.com/torvalds/linux.git
synced 2024-11-23 20:51:44 +00:00
d4f2a1c6c1
Since:
commit cbacb5ab0a
("docs: printk-formats: Stop encouraging use of
unnecessary %h[xudi] and %hh[xudi]")
use of these format strings has been discouraged.
Use the 0x02x form as the length specifier when used with # includes
the 0x prefix and is very unlikely to be what was intended by the author.
As there are not that many in IIO, this is part of an effort to clear
them out so we don't have any instances that might get copied into
new drivers.
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Cc: Maxime Roussin-Bélanger <maxime.roussinbelanger@gmail.com>
Cc: Nathan Chancellor <nathan@kernel.org>
Reviewed-by: Nathan Chancellor <nathan@kernel.org>
Link: https://lore.kernel.org/r/20210603180612.3635250-3-jic23@kernel.org
1076 lines
25 KiB
C
1076 lines
25 KiB
C
// 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 <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: 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] &= 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 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)
|
|
{
|
|
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");
|