| // 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/array_size.h> | |
| #include <linux/bitops.h> | |
| #include <linux/cleanup.h> | |
| #include <linux/completion.h> | |
| #include <linux/delay.h> | |
| #include <linux/dev_printk.h> | |
| #include <linux/err.h> | |
| #include <linux/i2c.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/jiffies.h> | |
| #include <linux/math.h> | |
| #include <linux/module.h> | |
| #include <linux/mutex.h> | |
| #include <linux/regmap.h> | |
| #include <linux/types.h> | |
| #include <linux/unaligned.h> | |
| #include <linux/util_macros.h> | |
| #include <linux/iio/iio.h> | |
| #include <linux/iio/sysfs.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_REG_HOSTOUT(x) ((x) + 0x13) | |
| #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) | |
| { | |
| unsigned long timeout; | |
| struct device *dev = &data->client->dev; | |
| u32 resp; | |
| int err; | |
| int expected_seq; | |
| guard(mutex)(&data->mutex); | |
| expected_seq = (data->rsp_seq + 1) & SI1133_MAX_CMD_CTR; | |
| if (cmd == SI1133_CMD_FORCE) { | |
| /* Flush pending IRQs from a previous timeout. */ | |
| regmap_read(data->regmap, SI1133_REG_IRQ_STATUS, &resp); | |
| regmap_write(data->regmap, SI1133_REG_IRQ_ENABLE, | |
| SI1133_IRQ_CHANNEL_ENABLE); | |
| 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); | |
| return err; | |
| } | |
| if (cmd == SI1133_CMD_FORCE) { | |
| /* wait for irq */ | |
| timeout = msecs_to_jiffies(SI1133_COMPLETION_TIMEOUT_MS); | |
| if (!wait_for_completion_timeout(&data->completion, timeout)) { | |
| regmap_write(data->regmap, SI1133_REG_IRQ_ENABLE, 0); | |
| return -ETIMEDOUT; | |
| } | |
| err = regmap_read(data->regmap, SI1133_REG_RESPONSE0, &resp); | |
| if (err) | |
| return err; | |
| } 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); | |
| /* | |
| * Reset counter on err to prevent software and hardware | |
| * counters being out of sync. | |
| */ | |
| si1133_cmd_reset_counter(data); | |
| return err; | |
| } | |
| } | |
| 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; | |
| } | |
| 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 = 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[] = { | |
| { .name = "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"); |