OSDN Git Service

iio: light: introduce si1133
authorMaxime Roussin-Bélanger <maxime.roussinbelanger@gmail.com>
Thu, 19 Jul 2018 20:26:25 +0000 (16:26 -0400)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Mon, 23 Jul 2018 18:18:11 +0000 (19:18 +0100)
e-mail received from Silicon Lab to confirm that the licensing
isn't a problem.

"
Dear Maxime Roussin-Belanger,

The LUX calculation code only works with Si1133.
As long as the software is used with Silicon Lab's sensor product,
I don't see any problem.

Regards,
Tony
"

Signed-off-by: Maxime Roussin-Bélanger <maxime.roussinbelanger@gmail.com>
Reviewed-by: Jean-Francois Dagenais <jeff.dagenais@gmail.com>
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Documentation/ABI/testing/sysfs-bus-iio-light-si1133 [new file with mode: 0644]
drivers/iio/light/Kconfig
drivers/iio/light/Makefile
drivers/iio/light/si1133.c [new file with mode: 0644]

diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-si1133 b/Documentation/ABI/testing/sysfs-bus-iio-light-si1133
new file mode 100644 (file)
index 0000000..6f130cd
--- /dev/null
@@ -0,0 +1,22 @@
+What:          /sys/bus/iio/devices/iio:deviceX/in_intensity_ir_small_raw
+KernelVersion: 4.18
+Contact:       linux-iio@vger.kernel.org
+Description:
+               Unit-less infrared intensity. The intensity is measured from 1
+               dark photodiode. "small" indicate the surface area capturing
+               infrared.
+
+What:          /sys/bus/iio/devices/iio:deviceX/in_intensity_ir_large_raw
+KernelVersion: 4.18
+Contact:       linux-iio@vger.kernel.org
+Description:
+               Unit-less infrared intensity. The intensity is measured from 4
+               dark photodiodes. "large" indicate the surface area capturing
+               infrared.
+
+What:          /sys/bus/iio/devices/iio:deviceX/in_intensity_large_raw
+KernelVersion: 4.18
+Contact:       linux-iio@vger.kernel.org
+Description:
+               Unit-less light intensity with more diodes.
+
index c7ef8d1..f17f701 100644 (file)
@@ -1,3 +1,4 @@
+
 #
 # Light sensors
 #
@@ -319,6 +320,17 @@ config PA12203001
          This driver can also be built as a module.  If so, the module
          will be called pa12203001.
 
+config SI1133
+       tristate "SI1133 UV Index Sensor and Ambient Light Sensor"
+       depends on I2C
+       select REGMAP_I2C
+         help
+         Say Y here if you want to build a driver for the Silicon Labs SI1133
+         UV Index Sensor and Ambient Light Sensor chip.
+
+         To compile this driver as a module, choose M here: the module will be
+         called si1133.
+
 config SI1145
        tristate "SI1132 and SI1141/2/3/5/6/7 combined ALS, UV index and proximity sensor"
        depends on I2C
index 80943af..86337b1 100644 (file)
@@ -32,6 +32,7 @@ obj-$(CONFIG_OPT3001)         += opt3001.o
 obj-$(CONFIG_PA12203001)       += pa12203001.o
 obj-$(CONFIG_RPR0521)          += rpr0521.o
 obj-$(CONFIG_SENSORS_TSL2563)  += tsl2563.o
+obj-$(CONFIG_SI1133)           += si1133.o
 obj-$(CONFIG_SI1145)           += si1145.o
 obj-$(CONFIG_STK3310)          += stk3310.o
 obj-$(CONFIG_ST_UVIS25)                += st_uvis25_core.o
diff --git a/drivers/iio/light/si1133.c b/drivers/iio/light/si1133.c
new file mode 100644 (file)
index 0000000..d3fbeb3
--- /dev/null
@@ -0,0 +1,1068 @@
+// 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>
+
+#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
+
+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(u32 input, u8 fraction, u16 mag,
+                                            s8 shift)
+{
+       return ((input << fraction) / mag) << shift;
+}
+
+static int si1133_calculate_output(u32 x, u32 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(u32 x, u32 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;
+               }
+       } 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;
+
+       __be16 resp;
+
+       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(resp),
+                              (u8 *)&resp);
+       if (err)
+               return err;
+
+       *val = be16_to_cpu(resp);
+
+       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;
+       u32 high_vis;
+       u32 low_vis;
+       u32 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 = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2];
+       low_vis = (buffer[3] << 16) | (buffer[4] << 8) | buffer[5];
+       ir = (buffer[6] << 16) | (buffer[7] << 8) | buffer[8];
+
+       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");