Message ID | 1509924300-14634-2-git-send-email-pmeerw@pmeerw.net (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Mon, 6 Nov 2017 00:24:59 +0100 Peter Meerwald-Stadler <pmeerw@pmeerw.net> wrote: > Driver for 20-bit ALS and UV B sensor with I2C interface exposing > the following API: > in_uvindex_input > in_illuminance_raw > in_illuminance_scale > in_illuminance_scale_available > in_intensity_uv_raw > in_intensity_uv_scale > in_intensity_uv_scale_available > integration_time > integration_time_available > > Signed-off-by: Peter Meerwald-Stadler <pmeerw@pmeerw.net> Looks good. I'll pick this up when I'm next on the right computer rather than my work laptop. Thanks, Jonathan > --- > drivers/iio/light/Kconfig | 10 + > drivers/iio/light/Makefile | 1 + > drivers/iio/light/zopt2201.c | 568 +++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 579 insertions(+) > create mode 100644 drivers/iio/light/zopt2201.c > > diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig > index 2356ed9..6a5835f 100644 > --- a/drivers/iio/light/Kconfig > +++ b/drivers/iio/light/Kconfig > @@ -425,4 +425,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 fa32fa4..d99abdf 100644 > --- a/drivers/iio/light/Makefile > +++ b/drivers/iio/light/Makefile > @@ -40,3 +40,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/zopt2201.c b/drivers/iio/light/zopt2201.c > new file mode 100644 > index 0000000..041ac9e > --- /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"); -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Sat, 11 Nov 2017 00:26:04 +0000 Jonathan Cameron <Jonathan.Cameron@huawei.com> wrote: > On Mon, 6 Nov 2017 00:24:59 +0100 > Peter Meerwald-Stadler <pmeerw@pmeerw.net> wrote: > > > Driver for 20-bit ALS and UV B sensor with I2C interface exposing > > the following API: > > in_uvindex_input > > in_illuminance_raw > > in_illuminance_scale > > in_illuminance_scale_available > > in_intensity_uv_raw > > in_intensity_uv_scale > > in_intensity_uv_scale_available > > integration_time > > integration_time_available > > > > Signed-off-by: Peter Meerwald-Stadler <pmeerw@pmeerw.net> > Looks good. I'll pick this up when I'm next on the right computer rather than > my work laptop. Applied to the togreg branch of iio.git and pushed out as testing for the autobuilders to play with it. Thanks, Jonathan > > Thanks, > > Jonathan > > > --- > > drivers/iio/light/Kconfig | 10 + > > drivers/iio/light/Makefile | 1 + > > drivers/iio/light/zopt2201.c | 568 +++++++++++++++++++++++++++++++++++++++++++ > > 3 files changed, 579 insertions(+) > > create mode 100644 drivers/iio/light/zopt2201.c > > > > diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig > > index 2356ed9..6a5835f 100644 > > --- a/drivers/iio/light/Kconfig > > +++ b/drivers/iio/light/Kconfig > > @@ -425,4 +425,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 fa32fa4..d99abdf 100644 > > --- a/drivers/iio/light/Makefile > > +++ b/drivers/iio/light/Makefile > > @@ -40,3 +40,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/zopt2201.c b/drivers/iio/light/zopt2201.c > > new file mode 100644 > > index 0000000..041ac9e > > --- /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"); > > -- > To unsubscribe from this list: send the line "unsubscribe linux-iio" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 2356ed9..6a5835f 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -425,4 +425,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 fa32fa4..d99abdf 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -40,3 +40,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/zopt2201.c b/drivers/iio/light/zopt2201.c new file mode 100644 index 0000000..041ac9e --- /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");
Driver for 20-bit ALS and UV B sensor with I2C interface exposing the following API: in_uvindex_input in_illuminance_raw in_illuminance_scale in_illuminance_scale_available in_intensity_uv_raw in_intensity_uv_scale in_intensity_uv_scale_available integration_time integration_time_available Signed-off-by: Peter Meerwald-Stadler <pmeerw@pmeerw.net> --- drivers/iio/light/Kconfig | 10 + drivers/iio/light/Makefile | 1 + drivers/iio/light/zopt2201.c | 568 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 579 insertions(+) create mode 100644 drivers/iio/light/zopt2201.c