| // 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: %#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->dev.parent = &client->dev; |
| 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"); |