From patchwork Fri Jun 22 20:48:59 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Maxime_Roussin-B=C3=A9langer?= X-Patchwork-Id: 10483019 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 4B38A60383 for ; Fri, 22 Jun 2018 20:49:28 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 3B2BF28DF2 for ; Fri, 22 Jun 2018 20:49:28 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 2EA7A28F85; Fri, 22 Jun 2018 20:49:28 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.8 required=2.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI, T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 7EBC828DF2 for ; Fri, 22 Jun 2018 20:49:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754190AbeFVUtZ (ORCPT ); Fri, 22 Jun 2018 16:49:25 -0400 Received: from mail-qk0-f195.google.com ([209.85.220.195]:35904 "EHLO mail-qk0-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754178AbeFVUtY (ORCPT ); Fri, 22 Jun 2018 16:49:24 -0400 Received: by mail-qk0-f195.google.com with SMTP id a195-v6so4427725qkg.3 for ; Fri, 22 Jun 2018 13:49:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=FcYVaR5R89AQKMWcGDbUeFNcyZqQvoDlxnzVdhJt7sg=; b=pQSUsf03MwrfkTKzwsdwj0+W9cj2X5T3SyqC3vqIKirx1VIU2KaMJz+Sh0iKhOhEay nr2AKburPxwE6BRvTBS9oNA90MBzJHyLb8vk62EKmxTN/Uf+s9JBKZKNCAs5NsG3qALR kP2+9CXGWH3upms+I7VOqa2ncUpvPXspPKnB6h2SB/MYNkok8VYKvva4W3Rw65sB1ksP qyfDjYLi1q4JMCQtSlOrqvLr/DiGEolF8aAhY3Cy5Z2Aujy/N8sJNsTgfMfcv4nZLtP5 G6+7ie9nQfSAFsfdX9TzJxuGwqseCPJkE5Tx3E78dOXJIq8AnmY4i9OIjTYct/BQzkI1 p44w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=FcYVaR5R89AQKMWcGDbUeFNcyZqQvoDlxnzVdhJt7sg=; b=l5iwDSORkT2P4ckpP9ccsTI5owkMovgaVJj4M3EXwon3YbR0X2p3i1tmCfZonhnKME 9haoDVl9Ncp/6S77WHSHIsd1xz/fSkpQMMAved4fLSPp+H0VizxEN8C++5ryB5YLYWCl ISMsPgMm51KplFEyp/KsEJCvn03Ktk5cSnD2/YjH6Ee5e15E2lWLBJLnyIg6Jl0hha7C ld31QDgiGv2HEmNHfgfEAVvJBxZM5eC7jRR4iBR2ilU3qsgLOQSLvKu64T+nLQoJPbZy Tiz1lL3duw9Jw9i/vn1IKTCyIA33bb0I2cWF9h5mNZmtAL0xVX+5IhzWw81cCZksmkms SC8Q== X-Gm-Message-State: APt69E19+SypWd+NNJb2wB3E4YZNbuwgah7YLcAfr3xGpUkQF3JbK+1j VK3yfcNMUMn2Rl9RBeyoFLk= X-Google-Smtp-Source: AAOMgpcq+MpTmWVBenuSzBndtVSAOkwFtywLcnLk1oAc4SLuWy9EUoKRM7/e7ECzvNep9f8GJ6tr9g== X-Received: by 2002:a37:ec6:: with SMTP id 189-v6mr2666846qko.438.1529700563505; Fri, 22 Jun 2018 13:49:23 -0700 (PDT) Received: from mbedesk.Sonatest.net (ipagstaticip-d73c7528-4de5-0861-800b-03d8b15e3869.sdsl.bell.ca. [174.94.156.236]) by smtp.gmail.com with ESMTPSA id t40-v6sm4523189qtt.45.2018.06.22.13.49.22 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 22 Jun 2018 13:49:22 -0700 (PDT) From: =?UTF-8?q?Maxime=20Roussin-B=C3=A9langer?= To: Jonathan Cameron , Hartmut Knaack , Lars-Peter Clausen , Peter Meerwald-Stadler Cc: linux-iio@vger.kernel.org, dagenaisj@sonatest.com, =?UTF-8?q?Maxime=20Roussin-B=C3=A9langer?= Subject: [PATCH] iio: light: introduce si1133 Date: Fri, 22 Jun 2018 16:48:59 -0400 Message-Id: <20180622204859.9667-1-maxime.roussinbelanger@gmail.com> X-Mailer: git-send-email 2.17.1 MIME-Version: 1.0 Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Signed-off-by: Maxime Roussin-BĂ©langer Reviewed-by: Jean-Francois Dagenais --- drivers/iio/light/Kconfig | 11 + drivers/iio/light/Makefile | 1 + drivers/iio/light/si1133.c | 835 +++++++++++++++++++++++++++++++++++++ 3 files changed, 847 insertions(+) create mode 100644 drivers/iio/light/si1133.c diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 2356ed9285df..4ca3911c1449 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -322,6 +322,17 @@ config SI1145 To compile this driver as a module, choose M here: the module will be called si1145. +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 STK3310 tristate "STK3310 ALS and proximity sensor" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index fa32fa459e2e..23ccd38291ec 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_PA12203001) += pa12203001.o obj-$(CONFIG_RPR0521) += rpr0521.o obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o obj-$(CONFIG_SI1145) += si1145.o +obj-$(CONFIG_SI1133) += si1133.o obj-$(CONFIG_STK3310) += stk3310.o obj-$(CONFIG_TCS3414) += tcs3414.o obj-$(CONFIG_TCS3472) += tcs3472.o diff --git a/drivers/iio/light/si1133.c b/drivers/iio/light/si1133.c new file mode 100644 index 000000000000..62e62e2dea1b --- /dev/null +++ b/drivers/iio/light/si1133.c @@ -0,0 +1,835 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * si1133.c - Support for Silabs SI1133 combined ambient + * light and UV index sensors + * + * Copyright 2018 Maxime Roussin-Belanger + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#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_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_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_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_ADCMUX_MASK 0x1F +#define SI1133_ADCSENS_SCALE_MASK 0x70 +#define SI1133_ADCSENS_SCALE_SHIFT 4 + +#define SI1133_ADCSENS_HSIG_MASK 0x80 +#define SI1133_ADCSENS_HSIG_SHIFT 7 + +#define SI1133_ADCSENS_HW_GAIN_MASK 0xF + +#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_CMD_MINSLEEP_US_LOW 5000 +#define SI1133_CMD_MINSLEEP_US_HIGH 7500 +#define SI1133_CMD_TIMEOUT_MS 25 +#define SI1133_MAX_RETRIES 25 + +#define SI1133_REG_HOSTOUT(x) ((x) + 0x13) + +#define SI1133_MAX_CMD_CTR 0xF + +#define SI1133_MEASUREMENT_FREQUENCY 1250 + +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"); +/* Integration time in milliseconds, nanoseconds */ +static const int si1133_int_time_table[][2] = { + {0, 24400}, {0, 48800}, {0, 97500}, {0, 195000}, + {0, 390000}, {0, 780000}, {1, 560000}, {3, 120000}, + {6, 240000}, {12, 480000}, {25, 000000}, {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_part_info - si1133 chip info + * @part: part number + * @iio_info callbacks + * @channels: list of channels + * @num_channels: number of channels + */ +struct si1133_part_info { + u8 part; + const struct iio_info *iio_info; + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +struct si1133_data { + struct regmap *regmap; + struct i2c_client *client; + const struct si1133_part_info *part_info; + + struct mutex mutex; + + int rsp_seq; + unsigned long scan_mask; + unsigned int adc_sens; + unsigned int adc_config; + unsigned int meas_rate; + + struct completion completion; +}; + +/** + * si1133_cmd_reset_sw() - Reset the chip + * + * Wait till command reset completes or timeout + */ +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, unsigned int resp, + u8 cmd) +{ + int ret = 0; + + resp &= 0xF; + + switch (resp) { + case SI1133_ERR_OUTPUT_BUFFER_OVERFLOW: + dev_warn(dev, "Output buffer overflow: %#02hhx\n", cmd); + ret = -EOVERFLOW; + break; + case SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION: + dev_warn(dev, "Saturation of the ADC or overflow of accumulation: %#02hhx\n", + cmd); + ret = -EOVERFLOW; + break; + case SI1133_ERR_INVALID_LOCATION_CMD: + dev_warn(dev, "Parameter access to an invalid location: %#02hhx\n", + cmd); + ret = -EINVAL; + break; + case SI1133_ERR_INVALID_CMD: + dev_warn(dev, "Invalid command %#02hhx\n", cmd); + ret = -EINVAL; + break; + default: + dev_warn(dev, "Unknown error %#02hhx\n", cmd); + return -EINVAL; + } + + return ret; +} + +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; + unsigned int resp; + int err; + int expected_seq; + + mutex_lock(&data->mutex); + + expected_seq = (data->rsp_seq + 1) & SI1133_MAX_CMD_CTR; + + 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; + } + + 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 (resp & SI1133_CMD_ERR_MASK) { + err = si1133_parse_response_err(dev, resp, cmd); + si1133_cmd_reset_counter(data); + } else { + data->rsp_seq = resp & SI1133_CMD_SEQ_MASK; + } + +out: + mutex_unlock(&data->mutex); + + return err; +} + +static int si1133_param_set(struct si1133_data *data, u8 param, + unsigned int 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, + unsigned int *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_ST { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + } + +#define SI1133_CHANNEL(_si, _ch, _type) \ + .type = IIO_LIGHT, \ + .indexed = 1, \ + .channel = _ch, \ + .scan_index = _si, \ + .scan_type = SI1133_ST, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_INT_TIME) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ + +static const struct iio_chan_spec si1133_channels[] = { + { + SI1133_CHANNEL(0, SI1133_PARAM_ADCMUX_WHITE, IIO_LIGHT) + }, + { + SI1133_CHANNEL(1, SI1133_PARAM_ADCMUX_LARGE_WHITE, IIO_LIGHT) + .extend_name = "large", + }, + { + SI1133_CHANNEL(2, SI1133_PARAM_ADCMUX_SMALL_IR, IIO_LIGHT) + .extend_name = "small", + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + }, + { + SI1133_CHANNEL(3, SI1133_PARAM_ADCMUX_MED_IR, IIO_LIGHT) + .extend_name = "med", + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + }, + { + SI1133_CHANNEL(4, SI1133_PARAM_ADCMUX_LARGE_IR, IIO_LIGHT) + .extend_name = "large", + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + }, + { + SI1133_CHANNEL(5, SI1133_PARAM_ADCMUX_UV, IIO_UVINDEX) + }, + { + SI1133_CHANNEL(6, SI1133_PARAM_ADCMUX_UV_DEEP, IIO_UVINDEX) + .extend_name = "deep", + } +}; + +static int si1133_read_samp_freq(struct si1133_data *data, int *val, int *val2) +{ + *val = SI1133_MEASUREMENT_FREQUENCY; + *val2 = data->meas_rate; + return IIO_VAL_FRACTIONAL; +} + +/* Set the samp freq in driver private data */ +static int si1133_store_samp_freq(struct si1133_data *data, int val) +{ + unsigned int meas_rate; + + if (val <= 0 || val > SI1133_MEASUREMENT_FREQUENCY) + return -ERANGE; + meas_rate = SI1133_MEASUREMENT_FREQUENCY / val; + + data->meas_rate = meas_rate; + + return 0; +} + +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, + int milliseconds, int nanoseconds) +{ + int index; + + index = si1133_get_int_time_index(milliseconds, nanoseconds); + if (index < 0) + return index; + + data->adc_sens &= 0xF0; + data->adc_sens |= index; + + return si1133_param_set(data, SI1133_PARAM_REG_ADCSENS(0), + data->adc_sens); +} + +static int si1133_set_chlist(struct iio_dev *indio_dev, unsigned long scan_mask) +{ + struct si1133_data *data = iio_priv(indio_dev); + + /* 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_set_adcmux(struct si1133_data *data, unsigned int mux) +{ + if ((mux & data->adc_config) == mux) + return 0; /* mux already set to correct value */ + + data->adc_config &= ~SI1133_ADCMUX_MASK; + data->adc_config |= mux; + + return si1133_param_set(data, SI1133_PARAM_REG_ADCCONFIG(0), + data->adc_config); +} + +static int si1133_measure(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct si1133_data *data = iio_priv(indio_dev); + int err; + int retries; + + __be16 resp; + + err = si1133_set_chlist(indio_dev, BIT(0)); + if (err) + return err; + + err = si1133_set_adcmux(data, chan->channel); + if (err) + return err; + + retries = SI1133_MAX_RETRIES; + + err = si1133_command(data, SI1133_CMD_FORCE); + if (err) + return err; + + reinit_completion(&data->completion); + + if (!wait_for_completion_timeout(&data->completion, + msecs_to_jiffies(SI1133_CMD_TIMEOUT_MS))) { + return -ETIMEDOUT; + } + + err = regmap_bulk_read(data->regmap, SI1133_REG_HOSTOUT(0), &resp, + sizeof(__be16)); + if (err) + return err; + + *val = be16_to_cpu(resp); + + return err; +} + +static irqreturn_t si1133_threaded_irq_handler(int irq, void *private) +{ + unsigned int irq_status; + + struct iio_dev *indio_dev = private; + struct si1133_data *data = iio_priv(indio_dev); + int err = regmap_read(data->regmap, SI1133_REG_IRQ_STATUS, &irq_status); + + if (err) { + dev_err_ratelimited(&indio_dev->dev, "Error reading IRQ\n"); + return IRQ_HANDLED; + } + + if (irq_status & data->scan_mask) { + complete(&data->completion); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static unsigned long si1133_swgain_to_scale(unsigned int regval) +{ + return BIT(regval); +} + +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_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct si1133_data *data = iio_priv(indio_dev); + unsigned int adc_sens = data->adc_sens; + int err; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_UVINDEX: + case IIO_LIGHT: + err = iio_device_claim_direct_mode(indio_dev); + if (err) + return err; + err = si1133_measure(indio_dev, chan, val); + iio_device_release_direct_mode(indio_dev); + + if (err) + return err; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_UVINDEX: + case IIO_LIGHT: + 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_LIGHT: + case IIO_UVINDEX: + adc_sens &= SI1133_ADCSENS_SCALE_MASK; + adc_sens >>= SI1133_ADCSENS_SCALE_SHIFT; + + *val = si1133_swgain_to_scale(adc_sens); + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_HARDWAREGAIN: + switch (chan->type) { + case IIO_LIGHT: + case IIO_UVINDEX: + adc_sens >>= SI1133_ADCSENS_HSIG_SHIFT; + + *val = adc_sens; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + return si1133_read_samp_freq(data, val, val2); + default: + return -EINVAL; + } + + return 0; +} + +static int si1133_set_adcsens(struct iio_dev *indio_dev, + unsigned int mask, unsigned int shift, + unsigned int value) +{ + struct si1133_data *data = iio_priv(indio_dev); + unsigned int adc_sens; + int err = si1133_param_query(data, SI1133_PARAM_REG_ADCSENS(0), + &adc_sens); + + if (err) + return err; + + adc_sens &= ~mask; + adc_sens |= (value << shift); + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCSENS(0), adc_sens); + if (err) { + iio_device_release_direct_mode(indio_dev); + return err; + } + + data->adc_sens = adc_sens; + + return 0; +} + +static int si1133_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct si1133_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_LIGHT: + case IIO_UVINDEX: + val = si1133_scale_to_swgain(val, val2); + if (val < 0) + return val; + + return si1133_set_adcsens(indio_dev, + SI1133_ADCSENS_SCALE_MASK, + SI1133_ADCSENS_SCALE_SHIFT, + val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + return si1133_store_samp_freq(data, val); + case IIO_CHAN_INFO_INT_TIME: + return si1133_set_integration_time(data, val, val2); + case IIO_CHAN_INFO_HARDWAREGAIN: + switch (chan->type) { + case IIO_LIGHT: + case IIO_UVINDEX: + if (val != 0 || val != 1) + return -EINVAL; + + return si1133_set_adcsens(indio_dev, + SI1133_ADCSENS_HSIG_MASK, + SI1133_ADCSENS_HSIG_SHIFT, + val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } + + return 0; +} + +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, + .driver_module = THIS_MODULE, + .attrs = &si1133_attribute_group, +}; + +static const struct si1133_part_info si1133_part_info[] = { + [0] = { + 0x33, &si1133_info, si1133_channels, ARRAY_SIZE(si1133_channels) + }, +}; + +static int si1133_initialize(struct si1133_data *data) +{ + int err; + + data->scan_mask = 0x01; + + 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; + + /* Initialize sampling freq to 1 Hz */ + err = si1133_store_samp_freq(data, 1); + if (err) + return err; + + /* Activate first channel */ + err = si1133_param_set(data, SI1133_PARAM_REG_CHAN_LIST, + data->scan_mask); + if (err < 0) + return err; + + err = regmap_write(data->regmap, SI1133_REG_IRQ_ENABLE, 1); + if (err) + return err; + + return 0; +} + +static int si1133_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct si1133_data *data; + struct iio_dev *indio_dev; + unsigned int part_id, rev_id, mfr_id; + int err; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_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, indio_dev); + data->client = client; + data->part_info = &si1133_part_info[id->driver_data]; + + 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(&client->dev, "device ID part %#02hhx rev %#02hhx mfr %#02hhx\n", + part_id, rev_id, mfr_id); + if (part_id != data->part_info->part) { + dev_err(&client->dev, "part ID mismatch got %#02hhx, expected %#02x\n", + part_id, data->part_info->part); + return -ENODEV; + } + + indio_dev->dev.parent = &client->dev; + indio_dev->name = id->name; + indio_dev->channels = data->part_info->channels; + indio_dev->num_channels = data->part_info->num_channels; + indio_dev->info = data->part_info->iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + mutex_init(&data->mutex); + + err = si1133_initialize(data); + if (err) { + dev_err(&client->dev, "Error when initializing chip : %d", err); + return err; + } + + if (client->irq) { + err = devm_request_threaded_irq(&client->dev, client->irq, + NULL, + si1133_threaded_irq_handler, + IRQF_ONESHOT | IRQF_SHARED, + client->name, indio_dev); + + if (err) { + dev_warn(&client->dev, "Unable to request irq: %d for use\n", + client->irq); + return err; + } + } else { + dev_err(&client->dev, "Required interrupt not provided, cannot proceed"); + return -EINVAL; + } + + err = devm_iio_device_register(&client->dev, indio_dev); + if (err) { + dev_err(&client->dev, "iio registration fails with error %d\n", + err); + return err; + } + + return 0; +} + +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 "); +MODULE_DESCRIPTION("Silabs SI1133, UV index sensor and ambient light sensor driver"); +MODULE_LICENSE("GPL");