aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/iio/light
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iio/light')
-rw-r--r--drivers/iio/light/Kconfig34
-rw-r--r--drivers/iio/light/Makefile4
-rw-r--r--drivers/iio/light/cros_ec_light_prox.c2
-rw-r--r--drivers/iio/light/st_uvis25.h37
-rw-r--r--drivers/iio/light/st_uvis25_core.c359
-rw-r--r--drivers/iio/light/st_uvis25_i2c.c69
-rw-r--r--drivers/iio/light/st_uvis25_spi.c68
-rw-r--r--drivers/iio/light/zopt2201.c568
8 files changed, 1139 insertions, 2 deletions
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index 2356ed9285df..93fd421b10d7 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -334,6 +334,30 @@ config STK3310
Choosing M will build the driver as a module. If so, the module
will be called stk3310.
+config ST_UVIS25
+ tristate "STMicroelectronics UVIS25 sensor driver"
+ depends on (I2C || SPI)
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ select ST_UVIS25_I2C if (I2C)
+ select ST_UVIS25_SPI if (SPI_MASTER)
+ help
+ Say yes here to build support for STMicroelectronics UVIS25
+ uv sensor
+
+ To compile this driver as a module, choose M here: the module
+ will be called st_uvis25.
+
+config ST_UVIS25_I2C
+ tristate
+ depends on ST_UVIS25
+ select REGMAP_I2C
+
+config ST_UVIS25_SPI
+ tristate
+ depends on ST_UVIS25
+ select REGMAP_SPI
+
config TCS3414
tristate "TAOS TCS3414 digital color sensor"
depends on I2C
@@ -425,4 +449,14 @@ config VL6180
To compile this driver as a module, choose M here: the
module will be called vl6180.
+config ZOPT2201
+ tristate "ZOPT2201 ALS and UV B sensor"
+ depends on I2C
+ help
+ Say Y here if you want to build a driver for the IDT
+ ZOPT2201 ambient light and UV B sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called zopt2201.
+
endmenu
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index c263469b7ce9..f714067a7816 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -33,6 +33,9 @@ obj-$(CONFIG_RPR0521) += rpr0521.o
obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o
obj-$(CONFIG_SI1145) += si1145.o
obj-$(CONFIG_STK3310) += stk3310.o
+obj-$(CONFIG_ST_UVIS25) += st_uvis25_core.o
+obj-$(CONFIG_ST_UVIS25_I2C) += st_uvis25_i2c.o
+obj-$(CONFIG_ST_UVIS25_SPI) += st_uvis25_spi.o
obj-$(CONFIG_TCS3414) += tcs3414.o
obj-$(CONFIG_TCS3472) += tcs3472.o
obj-$(CONFIG_TSL2583) += tsl2583.o
@@ -41,3 +44,4 @@ obj-$(CONFIG_US5182D) += us5182d.o
obj-$(CONFIG_VCNL4000) += vcnl4000.o
obj-$(CONFIG_VEML6070) += veml6070.o
obj-$(CONFIG_VL6180) += vl6180.o
+obj-$(CONFIG_ZOPT2201) += zopt2201.o
diff --git a/drivers/iio/light/cros_ec_light_prox.c b/drivers/iio/light/cros_ec_light_prox.c
index b2a46b390d5c..acfad4aeb27a 100644
--- a/drivers/iio/light/cros_ec_light_prox.c
+++ b/drivers/iio/light/cros_ec_light_prox.c
@@ -181,7 +181,6 @@ static int cros_ec_light_prox_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
- struct cros_ec_device *ec_device;
struct iio_dev *indio_dev;
struct cros_ec_light_prox_state *state;
struct iio_chan_spec *channel;
@@ -191,7 +190,6 @@ static int cros_ec_light_prox_probe(struct platform_device *pdev)
dev_warn(dev, "No CROS EC device found.\n");
return -EINVAL;
}
- ec_device = ec_dev->ec_dev;
indio_dev = devm_iio_device_alloc(dev, sizeof(*state));
if (!indio_dev)
diff --git a/drivers/iio/light/st_uvis25.h b/drivers/iio/light/st_uvis25.h
new file mode 100644
index 000000000000..5e970ab480cd
--- /dev/null
+++ b/drivers/iio/light/st_uvis25.h
@@ -0,0 +1,37 @@
+/*
+ * STMicroelectronics uvis25 sensor driver
+ *
+ * Copyright 2017 STMicroelectronics Inc.
+ *
+ * Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#ifndef ST_UVIS25_H
+#define ST_UVIS25_H
+
+#define ST_UVIS25_DEV_NAME "uvis25"
+
+#include <linux/iio/iio.h>
+
+/**
+ * struct st_uvis25_hw - ST UVIS25 sensor instance
+ * @regmap: Register map of the device.
+ * @trig: The trigger in use by the driver.
+ * @enabled: Status of the sensor (false->off, true->on).
+ * @irq: Device interrupt line (I2C or SPI).
+ */
+struct st_uvis25_hw {
+ struct regmap *regmap;
+
+ struct iio_trigger *trig;
+ bool enabled;
+ int irq;
+};
+
+extern const struct dev_pm_ops st_uvis25_pm_ops;
+
+int st_uvis25_probe(struct device *dev, int irq, struct regmap *regmap);
+
+#endif /* ST_UVIS25_H */
diff --git a/drivers/iio/light/st_uvis25_core.c b/drivers/iio/light/st_uvis25_core.c
new file mode 100644
index 000000000000..302635836e6b
--- /dev/null
+++ b/drivers/iio/light/st_uvis25_core.c
@@ -0,0 +1,359 @@
+/*
+ * STMicroelectronics uvis25 sensor driver
+ *
+ * Copyright 2017 STMicroelectronics Inc.
+ *
+ * Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/iio/sysfs.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/interrupt.h>
+#include <linux/irqreturn.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/buffer.h>
+#include <linux/regmap.h>
+
+#include "st_uvis25.h"
+
+#define ST_UVIS25_REG_WHOAMI_ADDR 0x0f
+#define ST_UVIS25_REG_WHOAMI_VAL 0xca
+#define ST_UVIS25_REG_CTRL1_ADDR 0x20
+#define ST_UVIS25_REG_ODR_MASK BIT(0)
+#define ST_UVIS25_REG_BDU_MASK BIT(1)
+#define ST_UVIS25_REG_CTRL2_ADDR 0x21
+#define ST_UVIS25_REG_BOOT_MASK BIT(7)
+#define ST_UVIS25_REG_CTRL3_ADDR 0x22
+#define ST_UVIS25_REG_HL_MASK BIT(7)
+#define ST_UVIS25_REG_STATUS_ADDR 0x27
+#define ST_UVIS25_REG_UV_DA_MASK BIT(0)
+#define ST_UVIS25_REG_OUT_ADDR 0x28
+
+static const struct iio_chan_spec st_uvis25_channels[] = {
+ {
+ .type = IIO_UVINDEX,
+ .address = ST_UVIS25_REG_OUT_ADDR,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 8,
+ .storagebits = 8,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static int st_uvis25_check_whoami(struct st_uvis25_hw *hw)
+{
+ int err, data;
+
+ err = regmap_read(hw->regmap, ST_UVIS25_REG_WHOAMI_ADDR, &data);
+ if (err < 0) {
+ dev_err(regmap_get_device(hw->regmap),
+ "failed to read whoami register\n");
+ return err;
+ }
+
+ if (data != ST_UVIS25_REG_WHOAMI_VAL) {
+ dev_err(regmap_get_device(hw->regmap),
+ "wrong whoami {%02x vs %02x}\n",
+ data, ST_UVIS25_REG_WHOAMI_VAL);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int st_uvis25_set_enable(struct st_uvis25_hw *hw, bool enable)
+{
+ int err;
+
+ err = regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR,
+ ST_UVIS25_REG_ODR_MASK, enable);
+ if (err < 0)
+ return err;
+
+ hw->enabled = enable;
+
+ return 0;
+}
+
+static int st_uvis25_read_oneshot(struct st_uvis25_hw *hw, u8 addr, int *val)
+{
+ int err;
+
+ err = st_uvis25_set_enable(hw, true);
+ if (err < 0)
+ return err;
+
+ msleep(1500);
+
+ /*
+ * in order to avoid possible race conditions with interrupt
+ * generation, disable the sensor first and then poll output
+ * register. That sequence guarantees the interrupt will be reset
+ * when irq line is unmasked
+ */
+ err = st_uvis25_set_enable(hw, false);
+ if (err < 0)
+ return err;
+
+ err = regmap_read(hw->regmap, addr, val);
+
+ return err < 0 ? err : IIO_VAL_INT;
+}
+
+static int st_uvis25_read_raw(struct iio_dev *iio_dev,
+ struct iio_chan_spec const *ch,
+ int *val, int *val2, long mask)
+{
+ int ret;
+
+ ret = iio_device_claim_direct_mode(iio_dev);
+ if (ret)
+ return ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED: {
+ struct st_uvis25_hw *hw = iio_priv(iio_dev);
+
+ /*
+ * mask irq line during oneshot read since the sensor
+ * does not export the capability to disable data-ready line
+ * in the register map and it is enabled by default.
+ * If the line is unmasked during read_raw() it will be set
+ * active and never reset since the trigger is disabled
+ */
+ if (hw->irq > 0)
+ disable_irq(hw->irq);
+ ret = st_uvis25_read_oneshot(hw, ch->address, val);
+ if (hw->irq > 0)
+ enable_irq(hw->irq);
+ break;
+ }
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ iio_device_release_direct_mode(iio_dev);
+
+ return ret;
+}
+
+static irqreturn_t st_uvis25_trigger_handler_thread(int irq, void *private)
+{
+ struct st_uvis25_hw *hw = private;
+ int err, status;
+
+ err = regmap_read(hw->regmap, ST_UVIS25_REG_STATUS_ADDR, &status);
+ if (err < 0)
+ return IRQ_HANDLED;
+
+ if (!(status & ST_UVIS25_REG_UV_DA_MASK))
+ return IRQ_NONE;
+
+ iio_trigger_poll_chained(hw->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int st_uvis25_allocate_trigger(struct iio_dev *iio_dev)
+{
+ struct st_uvis25_hw *hw = iio_priv(iio_dev);
+ struct device *dev = regmap_get_device(hw->regmap);
+ bool irq_active_low = false;
+ unsigned long irq_type;
+ int err;
+
+ irq_type = irqd_get_trigger_type(irq_get_irq_data(hw->irq));
+
+ switch (irq_type) {
+ case IRQF_TRIGGER_HIGH:
+ case IRQF_TRIGGER_RISING:
+ break;
+ case IRQF_TRIGGER_LOW:
+ case IRQF_TRIGGER_FALLING:
+ irq_active_low = true;
+ break;
+ default:
+ dev_info(dev, "mode %lx unsupported\n", irq_type);
+ return -EINVAL;
+ }
+
+ err = regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL3_ADDR,
+ ST_UVIS25_REG_HL_MASK, irq_active_low);
+ if (err < 0)
+ return err;
+
+ err = devm_request_threaded_irq(dev, hw->irq, NULL,
+ st_uvis25_trigger_handler_thread,
+ irq_type | IRQF_ONESHOT,
+ iio_dev->name, hw);
+ if (err) {
+ dev_err(dev, "failed to request trigger irq %d\n",
+ hw->irq);
+ return err;
+ }
+
+ hw->trig = devm_iio_trigger_alloc(dev, "%s-trigger",
+ iio_dev->name);
+ if (!hw->trig)
+ return -ENOMEM;
+
+ iio_trigger_set_drvdata(hw->trig, iio_dev);
+ hw->trig->dev.parent = dev;
+
+ return devm_iio_trigger_register(dev, hw->trig);
+}
+
+static int st_uvis25_buffer_preenable(struct iio_dev *iio_dev)
+{
+ return st_uvis25_set_enable(iio_priv(iio_dev), true);
+}
+
+static int st_uvis25_buffer_postdisable(struct iio_dev *iio_dev)
+{
+ return st_uvis25_set_enable(iio_priv(iio_dev), false);
+}
+
+static const struct iio_buffer_setup_ops st_uvis25_buffer_ops = {
+ .preenable = st_uvis25_buffer_preenable,
+ .postenable = iio_triggered_buffer_postenable,
+ .predisable = iio_triggered_buffer_predisable,
+ .postdisable = st_uvis25_buffer_postdisable,
+};
+
+static irqreturn_t st_uvis25_buffer_handler_thread(int irq, void *p)
+{
+ u8 buffer[ALIGN(sizeof(u8), sizeof(s64)) + sizeof(s64)];
+ struct iio_poll_func *pf = p;
+ struct iio_dev *iio_dev = pf->indio_dev;
+ struct st_uvis25_hw *hw = iio_priv(iio_dev);
+ int err;
+
+ err = regmap_read(hw->regmap, ST_UVIS25_REG_OUT_ADDR, (int *)buffer);
+ if (err < 0)
+ goto out;
+
+ iio_push_to_buffers_with_timestamp(iio_dev, buffer,
+ iio_get_time_ns(iio_dev));
+
+out:
+ iio_trigger_notify_done(hw->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int st_uvis25_allocate_buffer(struct iio_dev *iio_dev)
+{
+ struct st_uvis25_hw *hw = iio_priv(iio_dev);
+
+ return devm_iio_triggered_buffer_setup(regmap_get_device(hw->regmap),
+ iio_dev, NULL,
+ st_uvis25_buffer_handler_thread,
+ &st_uvis25_buffer_ops);
+}
+
+static const struct iio_info st_uvis25_info = {
+ .read_raw = st_uvis25_read_raw,
+};
+
+static int st_uvis25_init_sensor(struct st_uvis25_hw *hw)
+{
+ int err;
+
+ err = regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL2_ADDR,
+ ST_UVIS25_REG_BOOT_MASK, 1);
+ if (err < 0)
+ return err;
+
+ msleep(2000);
+
+ return regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR,
+ ST_UVIS25_REG_BDU_MASK, 1);
+}
+
+int st_uvis25_probe(struct device *dev, int irq, struct regmap *regmap)
+{
+ struct st_uvis25_hw *hw;
+ struct iio_dev *iio_dev;
+ int err;
+
+ iio_dev = devm_iio_device_alloc(dev, sizeof(*hw));
+ if (!iio_dev)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, (void *)iio_dev);
+
+ hw = iio_priv(iio_dev);
+ hw->irq = irq;
+ hw->regmap = regmap;
+
+ err = st_uvis25_check_whoami(hw);
+ if (err < 0)
+ return err;
+
+ iio_dev->modes = INDIO_DIRECT_MODE;
+ iio_dev->dev.parent = dev;
+ iio_dev->channels = st_uvis25_channels;
+ iio_dev->num_channels = ARRAY_SIZE(st_uvis25_channels);
+ iio_dev->name = ST_UVIS25_DEV_NAME;
+ iio_dev->info = &st_uvis25_info;
+
+ err = st_uvis25_init_sensor(hw);
+ if (err < 0)
+ return err;
+
+ if (hw->irq > 0) {
+ err = st_uvis25_allocate_buffer(iio_dev);
+ if (err < 0)
+ return err;
+
+ err = st_uvis25_allocate_trigger(iio_dev);
+ if (err)
+ return err;
+ }
+
+ return devm_iio_device_register(dev, iio_dev);
+}
+EXPORT_SYMBOL(st_uvis25_probe);
+
+static int __maybe_unused st_uvis25_suspend(struct device *dev)
+{
+ struct iio_dev *iio_dev = dev_get_drvdata(dev);
+ struct st_uvis25_hw *hw = iio_priv(iio_dev);
+
+ return regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR,
+ ST_UVIS25_REG_ODR_MASK, 0);
+}
+
+static int __maybe_unused st_uvis25_resume(struct device *dev)
+{
+ struct iio_dev *iio_dev = dev_get_drvdata(dev);
+ struct st_uvis25_hw *hw = iio_priv(iio_dev);
+
+ if (hw->enabled)
+ return regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR,
+ ST_UVIS25_REG_ODR_MASK, 1);
+
+ return 0;
+}
+
+const struct dev_pm_ops st_uvis25_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(st_uvis25_suspend, st_uvis25_resume)
+};
+EXPORT_SYMBOL(st_uvis25_pm_ops);
+
+MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>");
+MODULE_DESCRIPTION("STMicroelectronics uvis25 sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/light/st_uvis25_i2c.c b/drivers/iio/light/st_uvis25_i2c.c
new file mode 100644
index 000000000000..afd6eb01a202
--- /dev/null
+++ b/drivers/iio/light/st_uvis25_i2c.c
@@ -0,0 +1,69 @@
+/*
+ * STMicroelectronics uvis25 i2c driver
+ *
+ * Copyright 2017 STMicroelectronics Inc.
+ *
+ * Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+
+#include "st_uvis25.h"
+
+#define UVIS25_I2C_AUTO_INCREMENT BIT(7)
+
+static const struct regmap_config st_uvis25_i2c_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .write_flag_mask = UVIS25_I2C_AUTO_INCREMENT,
+ .read_flag_mask = UVIS25_I2C_AUTO_INCREMENT,
+};
+
+static int st_uvis25_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_i2c(client, &st_uvis25_i2c_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "Failed to register i2c regmap %d\n",
+ (int)PTR_ERR(regmap));
+ return PTR_ERR(regmap);
+ }
+
+ return st_uvis25_probe(&client->dev, client->irq, regmap);
+}
+
+static const struct of_device_id st_uvis25_i2c_of_match[] = {
+ { .compatible = "st,uvis25", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, st_uvis25_i2c_of_match);
+
+static const struct i2c_device_id st_uvis25_i2c_id_table[] = {
+ { ST_UVIS25_DEV_NAME },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, st_uvis25_i2c_id_table);
+
+static struct i2c_driver st_uvis25_driver = {
+ .driver = {
+ .name = "st_uvis25_i2c",
+ .pm = &st_uvis25_pm_ops,
+ .of_match_table = of_match_ptr(st_uvis25_i2c_of_match),
+ },
+ .probe = st_uvis25_i2c_probe,
+ .id_table = st_uvis25_i2c_id_table,
+};
+module_i2c_driver(st_uvis25_driver);
+
+MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>");
+MODULE_DESCRIPTION("STMicroelectronics uvis25 i2c driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/light/st_uvis25_spi.c b/drivers/iio/light/st_uvis25_spi.c
new file mode 100644
index 000000000000..cdfee5e84d5e
--- /dev/null
+++ b/drivers/iio/light/st_uvis25_spi.c
@@ -0,0 +1,68 @@
+/*
+ * STMicroelectronics uvis25 spi driver
+ *
+ * Copyright 2017 STMicroelectronics Inc.
+ *
+ * Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+
+#include "st_uvis25.h"
+
+#define UVIS25_SENSORS_SPI_READ BIT(7)
+#define UVIS25_SPI_AUTO_INCREMENT BIT(6)
+
+static const struct regmap_config st_uvis25_spi_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .read_flag_mask = UVIS25_SENSORS_SPI_READ | UVIS25_SPI_AUTO_INCREMENT,
+ .write_flag_mask = UVIS25_SPI_AUTO_INCREMENT,
+};
+
+static int st_uvis25_spi_probe(struct spi_device *spi)
+{
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_spi(spi, &st_uvis25_spi_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&spi->dev, "Failed to register spi regmap %d\n",
+ (int)PTR_ERR(regmap));
+ return PTR_ERR(regmap);
+ }
+
+ return st_uvis25_probe(&spi->dev, spi->irq, regmap);
+}
+
+static const struct of_device_id st_uvis25_spi_of_match[] = {
+ { .compatible = "st,uvis25", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, st_uvis25_spi_of_match);
+
+static const struct spi_device_id st_uvis25_spi_id_table[] = {
+ { ST_UVIS25_DEV_NAME },
+ {},
+};
+MODULE_DEVICE_TABLE(spi, st_uvis25_spi_id_table);
+
+static struct spi_driver st_uvis25_driver = {
+ .driver = {
+ .name = "st_uvis25_spi",
+ .pm = &st_uvis25_pm_ops,
+ .of_match_table = of_match_ptr(st_uvis25_spi_of_match),
+ },
+ .probe = st_uvis25_spi_probe,
+ .id_table = st_uvis25_spi_id_table,
+};
+module_spi_driver(st_uvis25_driver);
+
+MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>");
+MODULE_DESCRIPTION("STMicroelectronics uvis25 spi driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/light/zopt2201.c b/drivers/iio/light/zopt2201.c
new file mode 100644
index 000000000000..041ac9effdb0
--- /dev/null
+++ b/drivers/iio/light/zopt2201.c
@@ -0,0 +1,568 @@
+/*
+ * zopt2201.c - Support for IDT ZOPT2201 ambient light and UV B sensor
+ *
+ * Copyright 2017 Peter Meerwald-Stadler <pmeerw@pmeerw.net>
+ *
+ * This file is subject to the terms and conditions of version 2 of
+ * the GNU General Public License. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Datasheet: https://www.idt.com/document/dst/zopt2201-datasheet
+ * 7-bit I2C slave addresses 0x53 (default) or 0x52 (programmed)
+ *
+ * TODO: interrupt support, ALS/UVB raw mode
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define ZOPT2201_DRV_NAME "zopt2201"
+
+/* Registers */
+#define ZOPT2201_MAIN_CTRL 0x00
+#define ZOPT2201_LS_MEAS_RATE 0x04
+#define ZOPT2201_LS_GAIN 0x05
+#define ZOPT2201_PART_ID 0x06
+#define ZOPT2201_MAIN_STATUS 0x07
+#define ZOPT2201_ALS_DATA 0x0d /* LSB first, 13 to 20 bits */
+#define ZOPT2201_UVB_DATA 0x10 /* LSB first, 13 to 20 bits */
+#define ZOPT2201_UV_COMP_DATA 0x13 /* LSB first, 13 to 20 bits */
+#define ZOPT2201_COMP_DATA 0x16 /* LSB first, 13 to 20 bits */
+#define ZOPT2201_INT_CFG 0x19
+#define ZOPT2201_INT_PST 0x1a
+
+#define ZOPT2201_MAIN_CTRL_LS_MODE BIT(3) /* 0 .. ALS, 1 .. UV B */
+#define ZOPT2201_MAIN_CTRL_LS_EN BIT(1)
+
+/* Values for ZOPT2201_LS_MEAS_RATE resolution / bit width */
+#define ZOPT2201_MEAS_RES_20BIT 0 /* takes 400 ms */
+#define ZOPT2201_MEAS_RES_19BIT 1 /* takes 200 ms */
+#define ZOPT2201_MEAS_RES_18BIT 2 /* takes 100 ms, default */
+#define ZOPT2201_MEAS_RES_17BIT 3 /* takes 50 ms */
+#define ZOPT2201_MEAS_RES_16BIT 4 /* takes 25 ms */
+#define ZOPT2201_MEAS_RES_13BIT 5 /* takes 3.125 ms */
+#define ZOPT2201_MEAS_RES_SHIFT 4
+
+/* Values for ZOPT2201_LS_MEAS_RATE measurement rate */
+#define ZOPT2201_MEAS_FREQ_25MS 0
+#define ZOPT2201_MEAS_FREQ_50MS 1
+#define ZOPT2201_MEAS_FREQ_100MS 2 /* default */
+#define ZOPT2201_MEAS_FREQ_200MS 3
+#define ZOPT2201_MEAS_FREQ_500MS 4
+#define ZOPT2201_MEAS_FREQ_1000MS 5
+#define ZOPT2201_MEAS_FREQ_2000MS 6
+
+/* Values for ZOPT2201_LS_GAIN */
+#define ZOPT2201_LS_GAIN_1 0
+#define ZOPT2201_LS_GAIN_3 1
+#define ZOPT2201_LS_GAIN_6 2
+#define ZOPT2201_LS_GAIN_9 3
+#define ZOPT2201_LS_GAIN_18 4
+
+/* Values for ZOPT2201_MAIN_STATUS */
+#define ZOPT2201_MAIN_STATUS_POWERON BIT(5)
+#define ZOPT2201_MAIN_STATUS_INT BIT(4)
+#define ZOPT2201_MAIN_STATUS_DRDY BIT(3)
+
+#define ZOPT2201_PART_NUMBER 0xb2
+
+struct zopt2201_data {
+ struct i2c_client *client;
+ struct mutex lock;
+ u8 gain;
+ u8 res;
+ u8 rate;
+};
+
+static const struct {
+ unsigned int gain; /* gain factor */
+ unsigned int scale; /* micro lux per count */
+} zopt2201_gain_als[] = {
+ { 1, 19200000 },
+ { 3, 6400000 },
+ { 6, 3200000 },
+ { 9, 2133333 },
+ { 18, 1066666 },
+};
+
+static const struct {
+ unsigned int gain; /* gain factor */
+ unsigned int scale; /* micro W/m2 per count */
+} zopt2201_gain_uvb[] = {
+ { 1, 460800 },
+ { 3, 153600 },
+ { 6, 76800 },
+ { 9, 51200 },
+ { 18, 25600 },
+};
+
+static const struct {
+ unsigned int bits; /* sensor resolution in bits */
+ unsigned long us; /* measurement time in micro seconds */
+} zopt2201_resolution[] = {
+ { 20, 400000 },
+ { 19, 200000 },
+ { 18, 100000 },
+ { 17, 50000 },
+ { 16, 25000 },
+ { 13, 3125 },
+};
+
+static const struct {
+ unsigned int scale, uscale; /* scale factor as integer + micro */
+ u8 gain; /* gain register value */
+ u8 res; /* resolution register value */
+} zopt2201_scale_als[] = {
+ { 19, 200000, 0, 5 },
+ { 6, 400000, 1, 5 },
+ { 3, 200000, 2, 5 },
+ { 2, 400000, 0, 4 },
+ { 2, 133333, 3, 5 },
+ { 1, 200000, 0, 3 },
+ { 1, 66666, 4, 5 },
+ { 0, 800000, 1, 4 },
+ { 0, 600000, 0, 2 },
+ { 0, 400000, 2, 4 },
+ { 0, 300000, 0, 1 },
+ { 0, 266666, 3, 4 },
+ { 0, 200000, 2, 3 },
+ { 0, 150000, 0, 0 },
+ { 0, 133333, 4, 4 },
+ { 0, 100000, 2, 2 },
+ { 0, 66666, 4, 3 },
+ { 0, 50000, 2, 1 },
+ { 0, 33333, 4, 2 },
+ { 0, 25000, 2, 0 },
+ { 0, 16666, 4, 1 },
+ { 0, 8333, 4, 0 },
+};
+
+static const struct {
+ unsigned int scale, uscale; /* scale factor as integer + micro */
+ u8 gain; /* gain register value */
+ u8 res; /* resolution register value */
+} zopt2201_scale_uvb[] = {
+ { 0, 460800, 0, 5 },
+ { 0, 153600, 1, 5 },
+ { 0, 76800, 2, 5 },
+ { 0, 57600, 0, 4 },
+ { 0, 51200, 3, 5 },
+ { 0, 28800, 0, 3 },
+ { 0, 25600, 4, 5 },
+ { 0, 19200, 1, 4 },
+ { 0, 14400, 0, 2 },
+ { 0, 9600, 2, 4 },
+ { 0, 7200, 0, 1 },
+ { 0, 6400, 3, 4 },
+ { 0, 4800, 2, 3 },
+ { 0, 3600, 0, 0 },
+ { 0, 3200, 4, 4 },
+ { 0, 2400, 2, 2 },
+ { 0, 1600, 4, 3 },
+ { 0, 1200, 2, 1 },
+ { 0, 800, 4, 2 },
+ { 0, 600, 2, 0 },
+ { 0, 400, 4, 1 },
+ { 0, 200, 4, 0 },
+};
+
+static int zopt2201_enable_mode(struct zopt2201_data *data, bool uvb_mode)
+{
+ u8 out = ZOPT2201_MAIN_CTRL_LS_EN;
+
+ if (uvb_mode)
+ out |= ZOPT2201_MAIN_CTRL_LS_MODE;
+
+ return i2c_smbus_write_byte_data(data->client, ZOPT2201_MAIN_CTRL, out);
+}
+
+static int zopt2201_read(struct zopt2201_data *data, u8 reg)
+{
+ struct i2c_client *client = data->client;
+ int tries = 10;
+ u8 buf[3];
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = zopt2201_enable_mode(data, reg == ZOPT2201_UVB_DATA);
+ if (ret < 0)
+ goto fail;
+
+ while (tries--) {
+ unsigned long t = zopt2201_resolution[data->res].us;
+
+ if (t <= 20000)
+ usleep_range(t, t + 1000);
+ else
+ msleep(t / 1000);
+ ret = i2c_smbus_read_byte_data(client, ZOPT2201_MAIN_STATUS);
+ if (ret < 0)
+ goto fail;
+ if (ret & ZOPT2201_MAIN_STATUS_DRDY)
+ break;
+ }
+
+ if (tries < 0) {
+ ret = -ETIMEDOUT;
+ goto fail;
+ }
+
+ ret = i2c_smbus_read_i2c_block_data(client, reg, sizeof(buf), buf);
+ if (ret < 0)
+ goto fail;
+
+ ret = i2c_smbus_write_byte_data(client, ZOPT2201_MAIN_CTRL, 0x00);
+ if (ret < 0)
+ goto fail;
+ mutex_unlock(&data->lock);
+
+ return (buf[2] << 16) | (buf[1] << 8) | buf[0];
+
+fail:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static const struct iio_chan_spec zopt2201_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .address = ZOPT2201_ALS_DATA,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
+ },
+ {
+ .type = IIO_INTENSITY,
+ .modified = 1,
+ .channel2 = IIO_MOD_LIGHT_UV,
+ .address = ZOPT2201_UVB_DATA,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
+ },
+ {
+ .type = IIO_UVINDEX,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ },
+};
+
+static int zopt2201_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct zopt2201_data *data = iio_priv(indio_dev);
+ u64 tmp;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = zopt2201_read(data, chan->address);
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_PROCESSED:
+ ret = zopt2201_read(data, ZOPT2201_UVB_DATA);
+ if (ret < 0)
+ return ret;
+ *val = ret * 18 *
+ (1 << (20 - zopt2201_resolution[data->res].bits)) /
+ zopt2201_gain_uvb[data->gain].gain;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->address) {
+ case ZOPT2201_ALS_DATA:
+ *val = zopt2201_gain_als[data->gain].scale;
+ break;
+ case ZOPT2201_UVB_DATA:
+ *val = zopt2201_gain_uvb[data->gain].scale;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *val2 = 1000000;
+ *val2 *= (1 << (zopt2201_resolution[data->res].bits - 13));
+ tmp = div_s64(*val * 1000000ULL, *val2);
+ *val = div_s64_rem(tmp, 1000000, val2);
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_INT_TIME:
+ *val = 0;
+ *val2 = zopt2201_resolution[data->res].us;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int zopt2201_set_resolution(struct zopt2201_data *data, u8 res)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(data->client, ZOPT2201_LS_MEAS_RATE,
+ (res << ZOPT2201_MEAS_RES_SHIFT) |
+ data->rate);
+ if (ret < 0)
+ return ret;
+
+ data->res = res;
+
+ return 0;
+}
+
+static int zopt2201_write_resolution(struct zopt2201_data *data,
+ int val, int val2)
+{
+ int i, ret;
+
+ if (val != 0)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(zopt2201_resolution); i++)
+ if (val2 == zopt2201_resolution[i].us) {
+ mutex_lock(&data->lock);
+ ret = zopt2201_set_resolution(data, i);
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+
+ return -EINVAL;
+}
+
+static int zopt2201_set_gain(struct zopt2201_data *data, u8 gain)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(data->client, ZOPT2201_LS_GAIN, gain);
+ if (ret < 0)
+ return ret;
+
+ data->gain = gain;
+
+ return 0;
+}
+
+static int zopt2201_write_scale_als_by_idx(struct zopt2201_data *data, int idx)
+{
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = zopt2201_set_resolution(data, zopt2201_scale_als[idx].res);
+ if (ret < 0)
+ goto unlock;
+
+ ret = zopt2201_set_gain(data, zopt2201_scale_als[idx].gain);
+
+unlock:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static int zopt2201_write_scale_als(struct zopt2201_data *data,
+ int val, int val2)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(zopt2201_scale_als); i++)
+ if (val == zopt2201_scale_als[i].scale &&
+ val2 == zopt2201_scale_als[i].uscale) {
+ return zopt2201_write_scale_als_by_idx(data, i);
+ }
+
+ return -EINVAL;
+}
+
+static int zopt2201_write_scale_uvb_by_idx(struct zopt2201_data *data, int idx)
+{
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = zopt2201_set_resolution(data, zopt2201_scale_als[idx].res);
+ if (ret < 0)
+ goto unlock;
+
+ ret = zopt2201_set_gain(data, zopt2201_scale_als[idx].gain);
+
+unlock:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static int zopt2201_write_scale_uvb(struct zopt2201_data *data,
+ int val, int val2)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(zopt2201_scale_uvb); i++)
+ if (val == zopt2201_scale_uvb[i].scale &&
+ val2 == zopt2201_scale_uvb[i].uscale)
+ return zopt2201_write_scale_uvb_by_idx(data, i);
+
+ return -EINVAL;
+}
+
+static int zopt2201_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct zopt2201_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_INT_TIME:
+ return zopt2201_write_resolution(data, val, val2);
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->address) {
+ case ZOPT2201_ALS_DATA:
+ return zopt2201_write_scale_als(data, val, val2);
+ case ZOPT2201_UVB_DATA:
+ return zopt2201_write_scale_uvb(data, val, val2);
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static ssize_t zopt2201_show_int_time_available(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ size_t len = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(zopt2201_resolution); i++)
+ len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06lu ",
+ zopt2201_resolution[i].us);
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static IIO_DEV_ATTR_INT_TIME_AVAIL(zopt2201_show_int_time_available);
+
+static ssize_t zopt2201_show_als_scale_avail(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ ssize_t len = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(zopt2201_scale_als); i++)
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06u ",
+ zopt2201_scale_als[i].scale,
+ zopt2201_scale_als[i].uscale);
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static ssize_t zopt2201_show_uvb_scale_avail(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ ssize_t len = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(zopt2201_scale_uvb); i++)
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06u ",
+ zopt2201_scale_uvb[i].scale,
+ zopt2201_scale_uvb[i].uscale);
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR(in_illuminance_scale_available, 0444,
+ zopt2201_show_als_scale_avail, NULL, 0);
+static IIO_DEVICE_ATTR(in_intensity_uv_scale_available, 0444,
+ zopt2201_show_uvb_scale_avail, NULL, 0);
+
+static struct attribute *zopt2201_attributes[] = {
+ &iio_dev_attr_integration_time_available.dev_attr.attr,
+ &iio_dev_attr_in_illuminance_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_intensity_uv_scale_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group zopt2201_attribute_group = {
+ .attrs = zopt2201_attributes,
+};
+
+static const struct iio_info zopt2201_info = {
+ .read_raw = zopt2201_read_raw,
+ .write_raw = zopt2201_write_raw,
+ .attrs = &zopt2201_attribute_group,
+};
+
+static int zopt2201_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct zopt2201_data *data;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK))
+ return -EOPNOTSUPP;
+
+ ret = i2c_smbus_read_byte_data(client, ZOPT2201_PART_ID);
+ if (ret < 0)
+ return ret;
+ if (ret != ZOPT2201_PART_NUMBER)
+ return -ENODEV;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+ mutex_init(&data->lock);
+
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->info = &zopt2201_info;
+ indio_dev->channels = zopt2201_channels;
+ indio_dev->num_channels = ARRAY_SIZE(zopt2201_channels);
+ indio_dev->name = ZOPT2201_DRV_NAME;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ data->rate = ZOPT2201_MEAS_FREQ_100MS;
+ ret = zopt2201_set_resolution(data, ZOPT2201_MEAS_RES_18BIT);
+ if (ret < 0)
+ return ret;
+
+ ret = zopt2201_set_gain(data, ZOPT2201_LS_GAIN_3);
+ if (ret < 0)
+ return ret;
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id zopt2201_id[] = {
+ { "zopt2201", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, zopt2201_id);
+
+static struct i2c_driver zopt2201_driver = {
+ .driver = {
+ .name = ZOPT2201_DRV_NAME,
+ },
+ .probe = zopt2201_probe,
+ .id_table = zopt2201_id,
+};
+
+module_i2c_driver(zopt2201_driver);
+
+MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("IDT ZOPT2201 ambient light and UV B sensor driver");
+MODULE_LICENSE("GPL");