Message ID | 1510068983-25769-2-git-send-email-chf.fritz@googlemail.com (mailing list archive) |
---|---|
State | Superseded, archived |
Headers | show |
On Tue, 7 Nov 2017, Christoph Fritz wrote: > This patch adds support for Intersil isl76683 light sensor. nitpicking in subject: maybe 'Add support...', start uppercase more comments below > Signed-off-by: Christoph Fritz <chf.fritz@googlemail.com> > --- > drivers/iio/light/Kconfig | 12 + > drivers/iio/light/Makefile | 1 + > drivers/iio/light/isl76683.c | 693 +++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 706 insertions(+) > create mode 100644 drivers/iio/light/isl76683.c > > diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig > index 2356ed9..4f0882c 100644 > --- a/drivers/iio/light/Kconfig > +++ b/drivers/iio/light/Kconfig > @@ -194,6 +194,18 @@ config ISL29125 > To compile this driver as a module, choose M here: the module will be > called isl29125. > > +config ISL76683 > + tristate "Intersil ISL76683 light sensor" > + depends on I2C > + select IIO_BUFFER > + select IIO_TRIGGERED_BUFFER > + help > + Say Y here if you want to build a driver for the Intersil ISL76683 > + light sensor for I2C. > + > + To compile this driver as a module, choose M here: the module will be > + called isl76683. > + > config HID_SENSOR_ALS > depends on HID_SENSOR_HUB > select IIO_BUFFER > diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile > index fa32fa4..886a51f 100644 > --- a/drivers/iio/light/Makefile > +++ b/drivers/iio/light/Makefile > @@ -22,6 +22,7 @@ obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o > obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o > obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o > obj-$(CONFIG_ISL29125) += isl29125.o > +obj-$(CONFIG_ISL76683) += isl76683.o > obj-$(CONFIG_JSA1212) += jsa1212.o > obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o > obj-$(CONFIG_LTR501) += ltr501.o > diff --git a/drivers/iio/light/isl76683.c b/drivers/iio/light/isl76683.c > new file mode 100644 > index 0000000..b730276 > --- /dev/null > +++ b/drivers/iio/light/isl76683.c > @@ -0,0 +1,693 @@ > +/* > + * IIO driver for the light sensor ISL76683. > + * > + * Copyright (c) 2017 Christoph Fritz <chf.fritz@googlemail.com> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + * > + * Datasheet: > + * http://www.intersil.com/content/dam/Intersil/documents/isl7/isl76683.pdf > + */ > + > +#include <linux/module.h> > +#include <linux/i2c.h> > +#include <linux/pm_runtime.h> > +#include <linux/regmap.h> > +#include <linux/util_macros.h> > +#include <linux/iio/iio.h> > +#include <linux/iio/sysfs.h> > +#include <linux/iio/buffer.h> > +#include <linux/iio/trigger.h> > +#include <linux/iio/trigger_consumer.h> > +#include <linux/iio/triggered_buffer.h> > + > +#define ISL76683_REG_CMD 0x00 > +#define ISL76683_REG_CTRL 0x01 > +#define ISL76683_REG_THR_HI 0x02 > +#define ISL76683_REG_THR_LO 0x03 > +#define ISL76683_REG_SENSOR_L 0x04 maybe L -> LSB, M -> MSB to make it more clear OTOH SENSOR_M is not really used, so could drop as well and maybe add a comment to denote that data is 16-bit little endian > +#define ISL76683_REG_SENSOR_M 0x05 > +#define ISL76683_REG_CLEAR_INT 0x40 > +#define ISL76683_REGMAP_MAX 0x40 > + > +#define ISL76683_CMD_ENABLE BIT(7) > +#define ISL76683_CMD_PWRDWN BIT(6) > +#define ISL76683_WIDTH_MASK 0x3 > +#define ISL76683_PHOTOD_SHFT 2 > +#define ISL76683_PHOTOD_MASK (0x3 << ISL76683_PHOTOD_SHFT) could use GENMASK() > +#define ISL76683_INTPERS_MASK 0x3 > +#define ISL76683_LUXRANGE_SHFT 2 > +#define ISL76683_LUXRANGE_MASK (0x3 << ISL76683_LUXRANGE_SHFT) > +#define ISL76683_LUXRANGE_STR "1000 4000 16000 64000" > + > +enum isl76683_dmode { > + ISL76683_DIODE_0 = 0, > + ISL76683_DIODE_IR, > + ISL76683_DIODE_DIFF, > +}; > + > +enum isl76683_lux_range { > + ISL76683_LUX_1000 = 0, > + ISL76683_LUX_4000, > + ISL76683_LUX_16000, > + ISL76683_LUX_64000, > +}; > + > +static const int isl76683_lux_ranges_available[] = { > + 1000, 4000, 16000, 64000}; > + > +#define ISL76683_LUX_RANGE_DEFAULT ISL76683_LUX_1000 > +#define ISL76683_DIODE_MAX ISL76683_DIODE_DIFF > +#define ISL76683_DIODE_DEFAULT ISL76683_DIODE_0 > +#define ISL76683_WIDTH_DEFAULT 0x0 > +#define ISL76683_RESOLUTION_DEFAULT 16 > +#define ISL76683_EXT_RESISTOR_DEFAULT 100 > +#define ISL76683_KOHM_MIN 1 > +#define ISL76683_KOHM_MAX 1000 > +#define ISL76683_INTPERS_DEFAULT 0x0 > +#define ISL76683_THR_DEFAULT 0x7f > + > +struct isl76683_chip { > + enum isl76683_lux_range luxrange; > + int external_resistor; > + enum isl76683_dmode photodiode; > + struct i2c_client *client; > + struct regmap *rmp; > + struct completion irq_complete; > + struct iio_trigger *trig; > + bool trig_enabled; > + struct mutex lock; > + __le16 *buffer; isl76683_trigger_handler() stores the already endianness-converted value into buffer, so type should be u16? why do we need buffer at all? > + s64 time_ns; > + bool buffer_running; > +}; > + > +static bool isl76683_readable_reg(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case ISL76683_REG_CMD: > + case ISL76683_REG_CTRL: > + case ISL76683_REG_THR_HI: > + case ISL76683_REG_THR_LO: > + case ISL76683_REG_SENSOR_L: > + case ISL76683_REG_SENSOR_M: > + case ISL76683_REG_CLEAR_INT: > + return true; > + default: > + return false; > + } > +} > + > +static bool isl76683_writeable_reg(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case ISL76683_REG_CMD: > + case ISL76683_REG_CTRL: > + case ISL76683_REG_THR_HI: > + case ISL76683_REG_THR_LO: > + return true; > + default: > + return false; > + } > +} > + > +static bool isl76683_is_volatile_reg(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case ISL76683_REG_SENSOR_L: > + case ISL76683_REG_SENSOR_M: > + case ISL76683_REG_CLEAR_INT: > + return true; > + default: > + return false; > + } > +} > + > +static const struct regmap_config isl76683_regmap_config = { > + .reg_bits = 8, > + .val_bits = 8, > + .max_register = ISL76683_REGMAP_MAX, > + .readable_reg = isl76683_readable_reg, > + .writeable_reg = isl76683_writeable_reg, > + .volatile_reg = isl76683_is_volatile_reg, > + .cache_type = REGCACHE_RBTREE, > +}; > + > +static int isl76683_set_config(struct isl76683_chip *chip) > +{ > + int ret; > + > + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CTRL, > + ISL76683_LUXRANGE_MASK | ISL76683_INTPERS_MASK, > + (chip->luxrange << ISL76683_LUXRANGE_SHFT) | > + ISL76683_INTPERS_DEFAULT); > + if (ret < 0) > + return ret; > + > + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CMD, > + ISL76683_PHOTOD_MASK | ISL76683_WIDTH_MASK, > + (chip->photodiode << ISL76683_PHOTOD_SHFT) | > + ISL76683_WIDTH_DEFAULT); > + if (ret < 0) > + return ret; > + > + ret = regmap_write(chip->rmp, ISL76683_REG_THR_HI, > + ISL76683_THR_DEFAULT); > + if (ret < 0) > + return ret; > + > + ret = regmap_write(chip->rmp, ISL76683_REG_THR_LO, > + ISL76683_THR_DEFAULT); could just return regmap_write(...); > + if (ret < 0) > + > + return 0; > +} > + > +static int isl76683_power(struct isl76683_chip *chip, bool on) > +{ > + int ret; > + > + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CMD, > + ISL76683_CMD_ENABLE | ISL76683_CMD_PWRDWN, > + 0x0); > + if (ret < 0) > + return ret; why are these bits first set to 0 and then bits are written with a separate command? is this a requirement of the chip to clear and set? > + > + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CMD, > + ISL76683_CMD_ENABLE | ISL76683_CMD_PWRDWN, > + on ? ISL76683_CMD_ENABLE : ISL76683_CMD_PWRDWN); > + if (ret < 0) > + return ret; > + > + return on ? isl76683_set_config(chip) : 0; > +} > + > +static int isl76683_reset(struct isl76683_chip *chip) > +{ > + int ret; > + > + ret = isl76683_power(chip, false); > + if (ret < 0) > + return ret; is an explicit delay needed here? just curious... > + > + return isl76683_power(chip, true); > +} > + > +static int isl76683_read_lux(struct isl76683_chip *chip, > + bool is_processed, int *val) > +{ > + unsigned int sensor_data, range, fsr; > + __le16 sensor_raw; > + int ret; > + > + ret = regmap_bulk_read(chip->rmp, ISL76683_REG_SENSOR_L, > + &sensor_raw, sizeof(sensor_raw)); > + if (ret) > + return ret; > + > + sensor_data = le16_to_cpu(sensor_raw); > + > + if (!is_processed) { in this case sensor_data is probably NOT in lux? function name read_lux() is misleading? > + *val = sensor_data; > + return 0; > + } > + > + /* range values taken from datasheet (table 9) */ > + if (chip->luxrange == ISL76683_LUX_1000) > + range = 973; > + else if (chip->luxrange == ISL76683_LUX_4000) > + range = 3892; > + else if (chip->luxrange == ISL76683_LUX_16000) > + range = 15568; > + else if (chip->luxrange == ISL76683_LUX_64000) > + range = 62272; > + else > + return -EINVAL; > + > + /* equations from datasheet (EQ.3 and EQ.4) */ > + fsr = (100 * range) / chip->external_resistor; > + *val = (fsr * sensor_data) / (1 << ISL76683_RESOLUTION_DEFAULT); > + > + return 0; > +} > + > +static irqreturn_t isl76683_interrupt_handler(int irq, void *private) > +{ > + struct iio_dev *indio_dev = private; > + struct isl76683_chip *chip = iio_priv(indio_dev); > + > + chip->time_ns = iio_get_time_ns(indio_dev); > + > + if (chip->trig_enabled) > + iio_trigger_poll(chip->trig); > + > + if (!completion_done(&chip->irq_complete)) > + complete(&chip->irq_complete); > + > + return IRQ_HANDLED; > +} > + > +static inline int isl76683_start_measurement(struct isl76683_chip *chip) > +{ > + int dummy; > + > + /* dummy read is clearing irq and triggers new measurement */ > + return regmap_read(chip->rmp, ISL76683_REG_CLEAR_INT, &dummy); > +} > + > +static int isl76683_singleshot_conversion(struct isl76683_chip *chip, > + const struct iio_chan_spec *chan, > + bool is_processed, int *val) > +{ > + long timeout; > + int ret; > + > + if (chip->buffer_running) > + return -EAGAIN; > + > + switch (chan->channel2) { > + case IIO_MOD_LIGHT_BOTH: > + chip->photodiode = ISL76683_DIODE_DIFF; > + break; > + case IIO_MOD_LIGHT_IR: > + chip->photodiode = ISL76683_DIODE_IR; > + break; > + default: > + chip->photodiode = ISL76683_DIODE_0; > + } > + > + ret = isl76683_set_config(chip); > + if (ret) > + return ret; > + > + ret = isl76683_start_measurement(chip); > + if (ret < 0) > + return ret; > + > + /* wait for measurement to complete */ > + timeout = wait_for_completion_interruptible_timeout( > + &chip->irq_complete, > + msecs_to_jiffies(5000)); > + if (timeout == 0) { > + dev_err(&chip->client->dev, "measurement timed out\n"); > + return -ETIMEDOUT; > + } else if (timeout < 0) { > + dev_err(&chip->client->dev, "wait for measurement failed\n"); > + return -EINTR; > + } > + > + ret = isl76683_read_lux(chip, is_processed, val); > + if (ret) { > + dev_err(&chip->client->dev, "%s: Error %d reading lux\n", > + __func__, ret); > + return ret; > + } > + > + return IIO_VAL_INT; > +} > + > +static irqreturn_t isl76683_trigger_handler(int irq, void *p) > +{ > + struct iio_poll_func *pf = p; > + struct iio_dev *indio_dev = pf->indio_dev; > + struct isl76683_chip *chip = iio_priv(indio_dev); > + __le16 buf; > + int ret; > + > + ret = regmap_bulk_read(chip->rmp, ISL76683_REG_SENSOR_L, &buf, 2); sizeof(buf) > + if (ret) > + return ret; > + > + chip->buffer[0] = le16_to_cpu(buf); > + > + iio_push_to_buffers_with_timestamp(indio_dev, chip->buffer, > + chip->time_ns); > + > + iio_trigger_notify_done(indio_dev->trig); > + > + ret = isl76683_start_measurement(chip); > + if (ret < 0) > + return ret; I think even if this fails IRQ_HANDLED should be returned > + > + return IRQ_HANDLED; > +} > + > +static int isl76683_buffer_preenable(struct iio_dev *indio_dev) > +{ > + struct isl76683_chip *chip = iio_priv(indio_dev); > + > + mutex_lock(&chip->lock); > + chip->buffer_running = true; > + mutex_unlock(&chip->lock); should set_config() be also protected by the mutex? > + chip->photodiode = indio_dev->channels[0].channel2; > + return isl76683_set_config(chip); > +} > + > +static int isl76683_buffer_postdisable(struct iio_dev *indio_dev) > +{ > + struct isl76683_chip *chip = iio_priv(indio_dev); > + > + chip->buffer_running = false; > + return 0; > +} > + > +static const struct iio_buffer_setup_ops isl76683_buffer_setup_ops = { > + .preenable = &isl76683_buffer_preenable, > + .postdisable = &isl76683_buffer_postdisable, > + .predisable = iio_triggered_buffer_predisable, > + .postenable = iio_triggered_buffer_postenable, > + .validate_scan_mask = &iio_validate_scan_mask_onehot, > +}; > + > +static int isl76683_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int *val, int *val2, long mask) > +{ > + struct isl76683_chip *chip = iio_priv(indio_dev); > + int ret; > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + mutex_lock(&chip->lock); > + ret = isl76683_singleshot_conversion(chip, chan, false, val); > + mutex_unlock(&chip->lock); > + return ret; > + case IIO_CHAN_INFO_PROCESSED: > + mutex_lock(&chip->lock); > + ret = isl76683_singleshot_conversion(chip, chan, true, val); > + mutex_unlock(&chip->lock); > + return ret; > + case IIO_CHAN_INFO_HARDWAREGAIN: use _SCALE? > + *val = isl76683_lux_ranges_available[chip->luxrange]; > + return IIO_VAL_INT; > + } > + > + return -EINVAL; > +} > + > +static int isl76683_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int val, int val2, long mask) > +{ > + struct isl76683_chip *chip = iio_priv(indio_dev); > + int ret; > + > + switch (mask) { > + case IIO_CHAN_INFO_HARDWAREGAIN: > + mutex_lock(&chip->lock); > + chip->luxrange = find_closest(val, > + isl76683_lux_ranges_available, > + ARRAY_SIZE(isl76683_lux_ranges_available)); > + ret = isl76683_set_config(chip); > + mutex_unlock(&chip->lock); > + return ret; > + } > + > + return -EINVAL; > +} > + > +static IIO_CONST_ATTR(in_illuminance_hardwaregain_available, > + ISL76683_LUXRANGE_STR); > + > +static struct attribute *isl76683_attributes[] = { > + &iio_const_attr_in_illuminance_hardwaregain_available.dev_attr.attr, > + NULL > +}; > + > +static const struct attribute_group isl76683_attribute_group = { > + .attrs = isl76683_attributes, > +}; > + > +#define ISL76683_CHANNEL_2(_ch2, _si) { \ why _2? > + .type = IIO_LIGHT, \ > + .modified = 1, \ > + .channel2 = _ch2, \ > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ > + BIT(IIO_CHAN_INFO_PROCESSED), \ for INFO_RAW the measurement is not in lux, hence IIO_INTENSITY should be used? why have _RAW and _PROCESSED? the computation in the _PROCESSED path should ideally go into the _SCALE factor > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ I think this should be _SCALE rather > + .scan_index = _si, \ > + .scan_type = { \ > + .sign = 'u', \ > + .realbits = 16, \ > + .storagebits = 16, \ > + .endianness = IIO_CPU, \ you can/should specify IIO_LE and save the endianness conversion in isl76683_trigger_handler() > + }, \ > +} > + > +static const struct iio_chan_spec isl76683_channels[] = { > + { > + .type = IIO_LIGHT, > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | > + BIT(IIO_CHAN_INFO_PROCESSED), > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN), > + .scan_index = 0, > + .scan_type = { > + .sign = 'u', > + .realbits = 16, > + .storagebits = 16, > + .endianness = IIO_CPU, > + }, > + }, > + ISL76683_CHANNEL_2(IIO_MOD_LIGHT_IR, 1), > + ISL76683_CHANNEL_2(IIO_MOD_LIGHT_BOTH, 2), > + IIO_CHAN_SOFT_TIMESTAMP(3), > +}; > + > +static int isl76683_update_scan_mode(struct iio_dev *indio_dev, > + const unsigned long *scan_mask) > +{ > + struct isl76683_chip *chip = iio_priv(indio_dev); > + > + kfree(chip->buffer); > + chip->buffer = kzalloc(indio_dev->scan_bytes, GFP_KERNEL); most drivers do static allocation of the buffer, i.e. 2 bytes + 6 bytes padding + 8 bytes timestamp > + if (chip->buffer == NULL) > + return -ENOMEM; > + > + return 0; > +} > + > +static const struct iio_info isl76683_info = { > + .update_scan_mode = isl76683_update_scan_mode, > + .read_raw = isl76683_read_raw, > + .write_raw = isl76683_write_raw, > + .attrs = &isl76683_attribute_group, > + .driver_module = THIS_MODULE, THIS_MODULE not needed anymore > +}; > + > +static int isl76683_set_trigger_state(struct iio_trigger *trig, bool enable) > +{ > + struct isl76683_chip *chip = iio_trigger_get_drvdata(trig); > + int ret; > + > + if (enable) { > + chip->trig_enabled = true; > + ret = isl76683_start_measurement(chip); > + if (ret < 0) > + return ret; > + } else > + chip->trig_enabled = false; > + > + return 0; > +} > + > +static const struct iio_trigger_ops isl76683_trigger_ops = { > + .owner = THIS_MODULE, > + .set_trigger_state = isl76683_set_trigger_state, > + .validate_device = iio_trigger_validate_own_device, > +}; > + > +static int isl76683_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct isl76683_chip *chip; > + struct iio_dev *indio_dev; > + struct device_node *np = client->dev.of_node; > + int rs = ISL76683_EXT_RESISTOR_DEFAULT; > + int v, ret; > + > + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); > + if (!indio_dev) > + return -ENOMEM; > + > + chip = iio_priv(indio_dev); > + > + i2c_set_clientdata(client, indio_dev); > + chip->client = client; > + > + if (np) { > + ret = of_property_read_u32(np, "isil,external-resistor", &v); > + if (ret || v < ISL76683_KOHM_MIN || v > ISL76683_KOHM_MAX) > + dev_warn(&client->dev, > + "assuming %i kOhm resistor\n", rs); I'd rather fail if the value is incorrect (matter of taste) > + else > + rs = v; > + } > + > + chip->luxrange = ISL76683_LUX_RANGE_DEFAULT; > + chip->external_resistor = rs; > + chip->photodiode = ISL76683_DIODE_DEFAULT; > + chip->buffer_running = false; > + > + chip->rmp = devm_regmap_init_i2c(client, &isl76683_regmap_config); > + if (IS_ERR(chip->rmp)) { > + ret = PTR_ERR(chip->rmp); > + dev_err(&client->dev, "%s: Error %d initializing regmap\n", > + __func__, ret); > + return ret; > + } > + > + mutex_init(&chip->lock); > + > + init_completion(&chip->irq_complete); > + > + if (!client->irq) { > + dev_err(&client->dev, "no interrupt configured\n"); > + return -EINVAL; > + } > + > + indio_dev->dev.parent = &client->dev; > + indio_dev->info = &isl76683_info; > + indio_dev->channels = isl76683_channels; > + indio_dev->num_channels = ARRAY_SIZE(isl76683_channels); > + indio_dev->name = id->name; > + indio_dev->modes = INDIO_DIRECT_MODE; > + > + chip->trig_enabled = false; > + chip->trig = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", > + indio_dev->name, indio_dev->id); > + if (!chip->trig) > + return -ENOMEM; > + > + chip->trig->ops = &isl76683_trigger_ops; > + chip->trig->dev.parent = &client->dev; > + iio_trigger_set_drvdata(chip->trig, chip); > + > + ret = devm_request_irq(&client->dev, client->irq, > + isl76683_interrupt_handler, > + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, > + "isl76683_event", indio_dev); > + if (ret) { > + dev_err(&client->dev, "irq request error\n"); > + return ret; > + } > + > + ret = devm_iio_trigger_register(&client->dev, chip->trig); > + if (ret) { > + dev_err(&client->dev, "iio_trigger register error\n"); > + return ret; > + } > + > + ret = isl76683_reset(chip); > + if (ret) { > + dev_err(&client->dev, "reset failed\n"); > + return ret; > + } > + > + ret = isl76683_read_lux(chip, false, &v); > + if (ret) { > + dev_err(&client->dev, "initial dummy readout failed\n"); > + return ret; > + } > + > + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, > + &isl76683_trigger_handler, &isl76683_buffer_setup_ops); > + if (ret) > + return ret; > + > + ret = devm_iio_device_register(&client->dev, indio_dev); > + if (ret) { > + dev_err(&client->dev, > + "%s(): iio registration failed with error %d\n", > + __func__, ret); > + return ret; > + } > + > + return ret; > +} > + > +static int isl76683_remove(struct i2c_client *client) > +{ > + struct iio_dev *indio_dev = i2c_get_clientdata(client); > + struct isl76683_chip *chip = iio_priv(indio_dev); > + > + pm_runtime_disable(&client->dev); > + pm_runtime_set_suspended(&client->dev); > + isl76683_power(chip, false); > + kfree(chip->buffer); > + > + return 0; > +} > + > +#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM) > +static int isl76683_runtime_suspend(struct device *dev) > +{ > + struct isl76683_chip *chip = > + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); > + int ret; > + > + mutex_lock(&chip->lock); > + ret = isl76683_power(chip, false); > + regcache_mark_dirty(chip->rmp); > + mutex_unlock(&chip->lock); > + > + return ret; > +} > + > +static int isl76683_runtime_resume(struct device *dev) > +{ > + struct isl76683_chip *chip = > + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); > + int ret; > + > + mutex_lock(&chip->lock); > + ret = isl76683_power(chip, true); > + mutex_unlock(&chip->lock); > + > + return ret; > +} > +#endif > + > +static const struct dev_pm_ops isl76683_pm_ops = { > + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, > + pm_runtime_force_resume) > + SET_RUNTIME_PM_OPS(isl76683_runtime_suspend, > + isl76683_runtime_resume, NULL) > +}; > + > +static const struct i2c_device_id isl76683_id[] = { > + {"isl76683", 0}, > + {} > +}; > +MODULE_DEVICE_TABLE(i2c, isl76683_id); > + > +static const struct of_device_id isl76683_of_match[] = { > + { .compatible = "isil,isl76683", }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, isl76683_of_match); > + > +static struct i2c_driver isl76683_driver = { > + .driver = { > + .name = "isl76683", > + .of_match_table = isl76683_of_match, > + .pm = &isl76683_pm_ops, > + }, > + .probe = isl76683_probe, > + .remove = isl76683_remove, > + .id_table = isl76683_id, > +}; > + > +module_i2c_driver(isl76683_driver); > + > +MODULE_DESCRIPTION("ISL76683 Ambient Light Sensor driver"); > +MODULE_LICENSE("GPL"); > +MODULE_AUTHOR("Christoph Fritz <chf.fritz@googlemail.com>"); >
Hi Peter, thanks for your input, please see my comments and questions below. On Tue, 2017-11-07 at 17:28 +0100, Peter Meerwald-Stadler wrote: > On Tue, 7 Nov 2017, Christoph Fritz wrote: > > > This patch adds support for Intersil isl76683 light sensor. > > nitpicking in subject: maybe 'Add support...', start uppercase > more comments below OK > > > Signed-off-by: Christoph Fritz <chf.fritz@googlemail.com> > > --- > > drivers/iio/light/Kconfig | 12 + > > drivers/iio/light/Makefile | 1 + > > drivers/iio/light/isl76683.c | 693 +++++++++++++++++++++++++++++++++++++++++++ > > 3 files changed, 706 insertions(+) > > create mode 100644 drivers/iio/light/isl76683.c > > > > diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig > > index 2356ed9..4f0882c 100644 > > --- a/drivers/iio/light/Kconfig > > +++ b/drivers/iio/light/Kconfig > > @@ -194,6 +194,18 @@ config ISL29125 > > To compile this driver as a module, choose M here: the module will be > > called isl29125. > > > > +config ISL76683 > > + tristate "Intersil ISL76683 light sensor" > > + depends on I2C > > + select IIO_BUFFER > > + select IIO_TRIGGERED_BUFFER > > + help > > + Say Y here if you want to build a driver for the Intersil ISL76683 > > + light sensor for I2C. > > + > > + To compile this driver as a module, choose M here: the module will be > > + called isl76683. > > + > > config HID_SENSOR_ALS > > depends on HID_SENSOR_HUB > > select IIO_BUFFER > > diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile > > index fa32fa4..886a51f 100644 > > --- a/drivers/iio/light/Makefile > > +++ b/drivers/iio/light/Makefile > > @@ -22,6 +22,7 @@ obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o > > obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o > > obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o > > obj-$(CONFIG_ISL29125) += isl29125.o > > +obj-$(CONFIG_ISL76683) += isl76683.o > > obj-$(CONFIG_JSA1212) += jsa1212.o > > obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o > > obj-$(CONFIG_LTR501) += ltr501.o > > diff --git a/drivers/iio/light/isl76683.c b/drivers/iio/light/isl76683.c > > new file mode 100644 > > index 0000000..b730276 > > --- /dev/null > > +++ b/drivers/iio/light/isl76683.c > > @@ -0,0 +1,693 @@ > > +/* > > + * IIO driver for the light sensor ISL76683. > > + * > > + * Copyright (c) 2017 Christoph Fritz <chf.fritz@googlemail.com> > > + * > > + * This program is free software; you can redistribute it and/or modify > > + * it under the terms of the GNU General Public License as published by > > + * the Free Software Foundation; either version 2 of the License, or > > + * (at your option) any later version. > > + * > > + * This program is distributed in the hope that it will be useful, but WITHOUT > > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > > + * more details. > > + * > > + * Datasheet: > > + * http://www.intersil.com/content/dam/Intersil/documents/isl7/isl76683.pdf > > + */ > > + > > +#include <linux/module.h> > > +#include <linux/i2c.h> > > +#include <linux/pm_runtime.h> > > +#include <linux/regmap.h> > > +#include <linux/util_macros.h> > > +#include <linux/iio/iio.h> > > +#include <linux/iio/sysfs.h> > > +#include <linux/iio/buffer.h> > > +#include <linux/iio/trigger.h> > > +#include <linux/iio/trigger_consumer.h> > > +#include <linux/iio/triggered_buffer.h> > > + > > +#define ISL76683_REG_CMD 0x00 > > +#define ISL76683_REG_CTRL 0x01 > > +#define ISL76683_REG_THR_HI 0x02 > > +#define ISL76683_REG_THR_LO 0x03 > > +#define ISL76683_REG_SENSOR_L 0x04 > > maybe L -> LSB, M -> MSB to make it more clear > OTOH SENSOR_M is not really used, so could drop as well and maybe add a > comment to denote that data is 16-bit little endian I'll change it to ISL76683_REG_SENSOR_LSB and _MSB ISL76683_REG_SENSOR_MSB is used in regmap readable and volatile definition and is read by regmap_bulk_read(chip->rmp, ISL76683_REG_SENSOR_LSB, &sensor_raw, sizeof(sensor_raw)); because sizeof(sensor_raw) is 2 > > > +#define ISL76683_REG_SENSOR_M 0x05 > > +#define ISL76683_REG_CLEAR_INT 0x40 > > +#define ISL76683_REGMAP_MAX 0x40 > > + > > +#define ISL76683_CMD_ENABLE BIT(7) > > +#define ISL76683_CMD_PWRDWN BIT(6) > > +#define ISL76683_WIDTH_MASK 0x3 > > +#define ISL76683_PHOTOD_SHFT 2 > > +#define ISL76683_PHOTOD_MASK (0x3 << ISL76683_PHOTOD_SHFT) > > could use GENMASK() OK > > > +#define ISL76683_INTPERS_MASK 0x3 > > +#define ISL76683_LUXRANGE_SHFT 2 > > +#define ISL76683_LUXRANGE_MASK (0x3 << ISL76683_LUXRANGE_SHFT) > > +#define ISL76683_LUXRANGE_STR "1000 4000 16000 64000" > > + > > +enum isl76683_dmode { > > + ISL76683_DIODE_0 = 0, > > + ISL76683_DIODE_IR, > > + ISL76683_DIODE_DIFF, > > +}; > > + > > +enum isl76683_lux_range { > > + ISL76683_LUX_1000 = 0, > > + ISL76683_LUX_4000, > > + ISL76683_LUX_16000, > > + ISL76683_LUX_64000, > > +}; > > + > > +static const int isl76683_lux_ranges_available[] = { > > + 1000, 4000, 16000, 64000}; > > + > > +#define ISL76683_LUX_RANGE_DEFAULT ISL76683_LUX_1000 > > +#define ISL76683_DIODE_MAX ISL76683_DIODE_DIFF > > +#define ISL76683_DIODE_DEFAULT ISL76683_DIODE_0 > > +#define ISL76683_WIDTH_DEFAULT 0x0 > > +#define ISL76683_RESOLUTION_DEFAULT 16 > > +#define ISL76683_EXT_RESISTOR_DEFAULT 100 > > +#define ISL76683_KOHM_MIN 1 > > +#define ISL76683_KOHM_MAX 1000 > > +#define ISL76683_INTPERS_DEFAULT 0x0 > > +#define ISL76683_THR_DEFAULT 0x7f > > + > > +struct isl76683_chip { > > + enum isl76683_lux_range luxrange; > > + int external_resistor; > > + enum isl76683_dmode photodiode; > > + struct i2c_client *client; > > + struct regmap *rmp; > > + struct completion irq_complete; > > + struct iio_trigger *trig; > > + bool trig_enabled; > > + struct mutex lock; > > + __le16 *buffer; > > isl76683_trigger_handler() stores the already endianness-converted value > into buffer, so type should be u16? OK > why do we need buffer at all? to keep the buffer data small in either case when scan_elements/timestamp is used or not, see isl76683_update_scan_mode() > > + s64 time_ns; > > + bool buffer_running; > > +}; > > + > > +static bool isl76683_readable_reg(struct device *dev, unsigned int reg) > > +{ > > + switch (reg) { > > + case ISL76683_REG_CMD: > > + case ISL76683_REG_CTRL: > > + case ISL76683_REG_THR_HI: > > + case ISL76683_REG_THR_LO: > > + case ISL76683_REG_SENSOR_L: > > + case ISL76683_REG_SENSOR_M: > > + case ISL76683_REG_CLEAR_INT: > > + return true; > > + default: > > + return false; > > + } > > +} > > + > > +static bool isl76683_writeable_reg(struct device *dev, unsigned int reg) > > +{ > > + switch (reg) { > > + case ISL76683_REG_CMD: > > + case ISL76683_REG_CTRL: > > + case ISL76683_REG_THR_HI: > > + case ISL76683_REG_THR_LO: > > + return true; > > + default: > > + return false; > > + } > > +} > > + > > +static bool isl76683_is_volatile_reg(struct device *dev, unsigned int reg) > > +{ > > + switch (reg) { > > + case ISL76683_REG_SENSOR_L: > > + case ISL76683_REG_SENSOR_M: > > + case ISL76683_REG_CLEAR_INT: > > + return true; > > + default: > > + return false; > > + } > > +} > > + > > +static const struct regmap_config isl76683_regmap_config = { > > + .reg_bits = 8, > > + .val_bits = 8, > > + .max_register = ISL76683_REGMAP_MAX, > > + .readable_reg = isl76683_readable_reg, > > + .writeable_reg = isl76683_writeable_reg, > > + .volatile_reg = isl76683_is_volatile_reg, > > + .cache_type = REGCACHE_RBTREE, > > +}; > > + > > +static int isl76683_set_config(struct isl76683_chip *chip) > > +{ > > + int ret; > > + > > + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CTRL, > > + ISL76683_LUXRANGE_MASK | ISL76683_INTPERS_MASK, > > + (chip->luxrange << ISL76683_LUXRANGE_SHFT) | > > + ISL76683_INTPERS_DEFAULT); > > + if (ret < 0) > > + return ret; > > + > > + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CMD, > > + ISL76683_PHOTOD_MASK | ISL76683_WIDTH_MASK, > > + (chip->photodiode << ISL76683_PHOTOD_SHFT) | > > + ISL76683_WIDTH_DEFAULT); > > + if (ret < 0) > > + return ret; > > + > > + ret = regmap_write(chip->rmp, ISL76683_REG_THR_HI, > > + ISL76683_THR_DEFAULT); > > + if (ret < 0) > > + return ret; > > + > > + ret = regmap_write(chip->rmp, ISL76683_REG_THR_LO, > > + ISL76683_THR_DEFAULT); > > could just > return regmap_write(...); ok, but then it looks like this because of 80 chars max per line: return regmap_write(chip->rmp, ISL76683_REG_THR_LO, chip->threshold_low); } > > > + if (ret < 0) > > + > > + return 0; > > +} > > + > > +static int isl76683_power(struct isl76683_chip *chip, bool on) > > +{ > > + int ret; > > + > > + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CMD, > > + ISL76683_CMD_ENABLE | ISL76683_CMD_PWRDWN, > > + 0x0); > > + if (ret < 0) > > + return ret; > > why are these bits first set to 0 and then bits are written with a > separate command? > is this a requirement of the chip to clear and set? yes, that's the "proper" way as the data-sheet (page 9, bullet 2.) recommends. > > > + > > + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CMD, > > + ISL76683_CMD_ENABLE | ISL76683_CMD_PWRDWN, > > + on ? ISL76683_CMD_ENABLE : ISL76683_CMD_PWRDWN); > > + if (ret < 0) > > + return ret; > > + > > + return on ? isl76683_set_config(chip) : 0; > > +} > > + > > +static int isl76683_reset(struct isl76683_chip *chip) > > +{ > > + int ret; > > + > > + ret = isl76683_power(chip, false); > > + if (ret < 0) > > + return ret; > > is an explicit delay needed here? just curious... nope, haven't found any info in the data-sheet which would recommend any delay and here it works fine for me > > > + > > + return isl76683_power(chip, true); > > +} > > + > > +static int isl76683_read_lux(struct isl76683_chip *chip, > > + bool is_processed, int *val) > > +{ > > + unsigned int sensor_data, range, fsr; > > + __le16 sensor_raw; > > + int ret; > > + > > + ret = regmap_bulk_read(chip->rmp, ISL76683_REG_SENSOR_L, > > + &sensor_raw, sizeof(sensor_raw)); > > + if (ret) > > + return ret; > > + > > + sensor_data = le16_to_cpu(sensor_raw); > > + > > + if (!is_processed) { > > in this case sensor_data is probably NOT in lux? > function name read_lux() is misleading? is isl76683_get_sensordata() better? > > > + *val = sensor_data; > > + return 0; > > + } > > + > > + /* range values taken from datasheet (table 9) */ > > + if (chip->luxrange == ISL76683_LUX_1000) > > + range = 973; > > + else if (chip->luxrange == ISL76683_LUX_4000) > > + range = 3892; > > + else if (chip->luxrange == ISL76683_LUX_16000) > > + range = 15568; > > + else if (chip->luxrange == ISL76683_LUX_64000) > > + range = 62272; > > + else > > + return -EINVAL; > > + > > + /* equations from datasheet (EQ.3 and EQ.4) */ > > + fsr = (100 * range) / chip->external_resistor; > > + *val = (fsr * sensor_data) / (1 << ISL76683_RESOLUTION_DEFAULT); > > + > > + return 0; > > +} <snip> > > + > > +static irqreturn_t isl76683_trigger_handler(int irq, void *p) > > +{ > > + struct iio_poll_func *pf = p; > > + struct iio_dev *indio_dev = pf->indio_dev; > > + struct isl76683_chip *chip = iio_priv(indio_dev); > > + __le16 buf; > > + int ret; > > + > > + ret = regmap_bulk_read(chip->rmp, ISL76683_REG_SENSOR_L, &buf, 2); > > sizeof(buf) OK I used 2 to fit into 80 chars > > > + if (ret) > > + return ret; > > + > > + chip->buffer[0] = le16_to_cpu(buf); > > + > > + iio_push_to_buffers_with_timestamp(indio_dev, chip->buffer, > > + chip->time_ns); > > + > > + iio_trigger_notify_done(indio_dev->trig); > > + > > + ret = isl76683_start_measurement(chip); > > + if (ret < 0) > > + return ret; > > I think even if this fails IRQ_HANDLED should be returned OK > > > + > > + return IRQ_HANDLED; > > +} > > + > > +static int isl76683_buffer_preenable(struct iio_dev *indio_dev) > > +{ > > + struct isl76683_chip *chip = iio_priv(indio_dev); > > + > > + mutex_lock(&chip->lock); > > + chip->buffer_running = true; > > + mutex_unlock(&chip->lock); > > should set_config() be also protected by the mutex? OK > > > + chip->photodiode = indio_dev->channels[0].channel2; > > + return isl76683_set_config(chip); > > +} > > + > > +static int isl76683_buffer_postdisable(struct iio_dev *indio_dev) > > +{ > > + struct isl76683_chip *chip = iio_priv(indio_dev); > > + > > + chip->buffer_running = false; > > + return 0; > > +} > > + > > +static const struct iio_buffer_setup_ops isl76683_buffer_setup_ops = { > > + .preenable = &isl76683_buffer_preenable, > > + .postdisable = &isl76683_buffer_postdisable, > > + .predisable = iio_triggered_buffer_predisable, > > + .postenable = iio_triggered_buffer_postenable, > > + .validate_scan_mask = &iio_validate_scan_mask_onehot, > > +}; > > + > > +static int isl76683_read_raw(struct iio_dev *indio_dev, > > + struct iio_chan_spec const *chan, > > + int *val, int *val2, long mask) > > +{ > > + struct isl76683_chip *chip = iio_priv(indio_dev); > > + int ret; > > + > > + switch (mask) { > > + case IIO_CHAN_INFO_RAW: > > + mutex_lock(&chip->lock); > > + ret = isl76683_singleshot_conversion(chip, chan, false, val); > > + mutex_unlock(&chip->lock); > > + return ret; > > + case IIO_CHAN_INFO_PROCESSED: > > + mutex_lock(&chip->lock); > > + ret = isl76683_singleshot_conversion(chip, chan, true, val); > > + mutex_unlock(&chip->lock); > > + return ret; > > + case IIO_CHAN_INFO_HARDWAREGAIN: > > use _SCALE? OK > > > + *val = isl76683_lux_ranges_available[chip->luxrange]; > > + return IIO_VAL_INT; > > + } > > + > > + return -EINVAL; > > +} > > + > > +static int isl76683_write_raw(struct iio_dev *indio_dev, > > + struct iio_chan_spec const *chan, > > + int val, int val2, long mask) > > +{ > > + struct isl76683_chip *chip = iio_priv(indio_dev); > > + int ret; > > + > > + switch (mask) { > > + case IIO_CHAN_INFO_HARDWAREGAIN: > > + mutex_lock(&chip->lock); > > + chip->luxrange = find_closest(val, > > + isl76683_lux_ranges_available, > > + ARRAY_SIZE(isl76683_lux_ranges_available)); > > + ret = isl76683_set_config(chip); > > + mutex_unlock(&chip->lock); > > + return ret; > > + } > > + > > + return -EINVAL; > > +} > > + > > +static IIO_CONST_ATTR(in_illuminance_hardwaregain_available, > > + ISL76683_LUXRANGE_STR); > > + > > +static struct attribute *isl76683_attributes[] = { > > + &iio_const_attr_in_illuminance_hardwaregain_available.dev_attr.attr, > > + NULL > > +}; > > + > > +static const struct attribute_group isl76683_attribute_group = { > > + .attrs = isl76683_attributes, > > +}; > > + > > +#define ISL76683_CHANNEL_2(_ch2, _si) { \ > > why _2? to be more clear about what the purpose here is: "setting channel 2". But obviously this doesn't work :) ... so I'll change it to ISL76683_CHANNEL > > > + .type = IIO_LIGHT, \ > > + .modified = 1, \ > > + .channel2 = _ch2, \ > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ > > + BIT(IIO_CHAN_INFO_PROCESSED), \ > > for INFO_RAW the measurement is not in lux, hence IIO_INTENSITY should be > used? why have _RAW and _PROCESSED? > > the computation in the _PROCESSED path should ideally go into the _SCALE > factor > > > > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ > > I think this should be _SCALE rather > > > + .scan_index = _si, \ > > + .scan_type = { \ > > + .sign = 'u', \ > > + .realbits = 16, \ > > + .storagebits = 16, \ > > + .endianness = IIO_CPU, \ > > you can/should specify IIO_LE and save the endianness conversion in > isl76683_trigger_handler() So buffer should still be __le16* and not u16*? > > > + }, \ > > +} > > + > > +static const struct iio_chan_spec isl76683_channels[] = { > > + { > > + .type = IIO_LIGHT, > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | > > + BIT(IIO_CHAN_INFO_PROCESSED), > > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN), > > + .scan_index = 0, > > + .scan_type = { > > + .sign = 'u', > > + .realbits = 16, > > + .storagebits = 16, > > + .endianness = IIO_CPU, > > + }, > > + }, > > + ISL76683_CHANNEL_2(IIO_MOD_LIGHT_IR, 1), > > + ISL76683_CHANNEL_2(IIO_MOD_LIGHT_BOTH, 2), > > + IIO_CHAN_SOFT_TIMESTAMP(3), > > +}; > > + > > +static int isl76683_update_scan_mode(struct iio_dev *indio_dev, > > + const unsigned long *scan_mask) > > +{ > > + struct isl76683_chip *chip = iio_priv(indio_dev); > > + > > + kfree(chip->buffer); > > + chip->buffer = kzalloc(indio_dev->scan_bytes, GFP_KERNEL); > > most drivers do static allocation of the buffer, i.e. 2 bytes + 6 bytes > padding + 8 bytes timestamp is this a "have to"? > > > + if (chip->buffer == NULL) > > + return -ENOMEM; > > + > > + return 0; > > +} > > + > > +static const struct iio_info isl76683_info = { > > + .update_scan_mode = isl76683_update_scan_mode, > > + .read_raw = isl76683_read_raw, > > + .write_raw = isl76683_write_raw, > > + .attrs = &isl76683_attribute_group, > > + .driver_module = THIS_MODULE, > > THIS_MODULE not needed anymore OK > > > +}; > > + > > +static int isl76683_set_trigger_state(struct iio_trigger *trig, bool enable) > > +{ > > + struct isl76683_chip *chip = iio_trigger_get_drvdata(trig); > > + int ret; > > + > > + if (enable) { > > + chip->trig_enabled = true; > > + ret = isl76683_start_measurement(chip); > > + if (ret < 0) > > + return ret; > > + } else > > + chip->trig_enabled = false; > > + > > + return 0; > > +} > > + > > +static const struct iio_trigger_ops isl76683_trigger_ops = { > > + .owner = THIS_MODULE, > > + .set_trigger_state = isl76683_set_trigger_state, > > + .validate_device = iio_trigger_validate_own_device, > > +}; > > + > > +static int isl76683_probe(struct i2c_client *client, > > + const struct i2c_device_id *id) > > +{ > > + struct isl76683_chip *chip; > > + struct iio_dev *indio_dev; > > + struct device_node *np = client->dev.of_node; > > + int rs = ISL76683_EXT_RESISTOR_DEFAULT; > > + int v, ret; > > + > > + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); > > + if (!indio_dev) > > + return -ENOMEM; > > + > > + chip = iio_priv(indio_dev); > > + > > + i2c_set_clientdata(client, indio_dev); > > + chip->client = client; > > + > > + if (np) { > > + ret = of_property_read_u32(np, "isil,external-resistor", &v); > > + if (ret || v < ISL76683_KOHM_MIN || v > ISL76683_KOHM_MAX) > > + dev_warn(&client->dev, > > + "assuming %i kOhm resistor\n", rs); > > I'd rather fail if the value is incorrect (matter of taste) it's only for IIO_CHAN_INFO_PROCESSED, so I like it to let it slide: Okay for you? <snip> -- 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 Tue, 7 Nov 2017 16:36:21 +0100 Christoph Fritz <chf.fritz@googlemail.com> wrote: > This patch adds support for Intersil isl76683 light sensor. > > Signed-off-by: Christoph Fritz <chf.fritz@googlemail.com> Sorry, bit of a rushed review as boarding about to start for a transatlantic flight... Anyhow, a few comments inline in addition to Peter's. I'll take a proper look at V2. Jonathan > --- > drivers/iio/light/Kconfig | 12 + > drivers/iio/light/Makefile | 1 + > drivers/iio/light/isl76683.c | 693 +++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 706 insertions(+) > create mode 100644 drivers/iio/light/isl76683.c > > diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig > index 2356ed9..4f0882c 100644 > --- a/drivers/iio/light/Kconfig > +++ b/drivers/iio/light/Kconfig > @@ -194,6 +194,18 @@ config ISL29125 > To compile this driver as a module, choose M here: the module will be > called isl29125. > > +config ISL76683 > + tristate "Intersil ISL76683 light sensor" > + depends on I2C > + select IIO_BUFFER > + select IIO_TRIGGERED_BUFFER > + help > + Say Y here if you want to build a driver for the Intersil ISL76683 > + light sensor for I2C. > + > + To compile this driver as a module, choose M here: the module will be > + called isl76683. > + > config HID_SENSOR_ALS > depends on HID_SENSOR_HUB > select IIO_BUFFER > diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile > index fa32fa4..886a51f 100644 > --- a/drivers/iio/light/Makefile > +++ b/drivers/iio/light/Makefile > @@ -22,6 +22,7 @@ obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o > obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o > obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o > obj-$(CONFIG_ISL29125) += isl29125.o > +obj-$(CONFIG_ISL76683) += isl76683.o > obj-$(CONFIG_JSA1212) += jsa1212.o > obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o > obj-$(CONFIG_LTR501) += ltr501.o > diff --git a/drivers/iio/light/isl76683.c b/drivers/iio/light/isl76683.c > new file mode 100644 > index 0000000..b730276 > --- /dev/null > +++ b/drivers/iio/light/isl76683.c > @@ -0,0 +1,693 @@ > +/* > + * IIO driver for the light sensor ISL76683. > + * > + * Copyright (c) 2017 Christoph Fritz <chf.fritz@googlemail.com> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + * > + * Datasheet: > + * http://www.intersil.com/content/dam/Intersil/documents/isl7/isl76683.pdf > + */ > + > +#include <linux/module.h> > +#include <linux/i2c.h> > +#include <linux/pm_runtime.h> > +#include <linux/regmap.h> > +#include <linux/util_macros.h> > +#include <linux/iio/iio.h> > +#include <linux/iio/sysfs.h> > +#include <linux/iio/buffer.h> > +#include <linux/iio/trigger.h> > +#include <linux/iio/trigger_consumer.h> > +#include <linux/iio/triggered_buffer.h> > + > +#define ISL76683_REG_CMD 0x00 > +#define ISL76683_REG_CTRL 0x01 > +#define ISL76683_REG_THR_HI 0x02 > +#define ISL76683_REG_THR_LO 0x03 > +#define ISL76683_REG_SENSOR_L 0x04 > +#define ISL76683_REG_SENSOR_M 0x05 > +#define ISL76683_REG_CLEAR_INT 0x40 > +#define ISL76683_REGMAP_MAX 0x40 > + > +#define ISL76683_CMD_ENABLE BIT(7) > +#define ISL76683_CMD_PWRDWN BIT(6) > +#define ISL76683_WIDTH_MASK 0x3 > +#define ISL76683_PHOTOD_SHFT 2 > +#define ISL76683_PHOTOD_MASK (0x3 << ISL76683_PHOTOD_SHFT) > +#define ISL76683_INTPERS_MASK 0x3 > +#define ISL76683_LUXRANGE_SHFT 2 > +#define ISL76683_LUXRANGE_MASK (0x3 << ISL76683_LUXRANGE_SHFT) > +#define ISL76683_LUXRANGE_STR "1000 4000 16000 64000" > + > +enum isl76683_dmode { > + ISL76683_DIODE_0 = 0, > + ISL76683_DIODE_IR, > + ISL76683_DIODE_DIFF, > +}; > + > +enum isl76683_lux_range { > + ISL76683_LUX_1000 = 0, > + ISL76683_LUX_4000, > + ISL76683_LUX_16000, > + ISL76683_LUX_64000, > +}; > + > +static const int isl76683_lux_ranges_available[] = { > + 1000, 4000, 16000, 64000}; > + > +#define ISL76683_LUX_RANGE_DEFAULT ISL76683_LUX_1000 > +#define ISL76683_DIODE_MAX ISL76683_DIODE_DIFF > +#define ISL76683_DIODE_DEFAULT ISL76683_DIODE_0 > +#define ISL76683_WIDTH_DEFAULT 0x0 > +#define ISL76683_RESOLUTION_DEFAULT 16 > +#define ISL76683_EXT_RESISTOR_DEFAULT 100 > +#define ISL76683_KOHM_MIN 1 > +#define ISL76683_KOHM_MAX 1000 > +#define ISL76683_INTPERS_DEFAULT 0x0 > +#define ISL76683_THR_DEFAULT 0x7f > + > +struct isl76683_chip { > + enum isl76683_lux_range luxrange; > + int external_resistor; > + enum isl76683_dmode photodiode; > + struct i2c_client *client; > + struct regmap *rmp; > + struct completion irq_complete; > + struct iio_trigger *trig; > + bool trig_enabled; > + struct mutex lock; > + __le16 *buffer; > + s64 time_ns; > + bool buffer_running; > +}; > + > +static bool isl76683_readable_reg(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case ISL76683_REG_CMD: > + case ISL76683_REG_CTRL: > + case ISL76683_REG_THR_HI: > + case ISL76683_REG_THR_LO: > + case ISL76683_REG_SENSOR_L: > + case ISL76683_REG_SENSOR_M: > + case ISL76683_REG_CLEAR_INT: > + return true; > + default: > + return false; > + } > +} > + > +static bool isl76683_writeable_reg(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case ISL76683_REG_CMD: > + case ISL76683_REG_CTRL: > + case ISL76683_REG_THR_HI: > + case ISL76683_REG_THR_LO: > + return true; > + default: > + return false; > + } > +} > + > +static bool isl76683_is_volatile_reg(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case ISL76683_REG_SENSOR_L: > + case ISL76683_REG_SENSOR_M: > + case ISL76683_REG_CLEAR_INT: > + return true; > + default: > + return false; > + } > +} > + > +static const struct regmap_config isl76683_regmap_config = { > + .reg_bits = 8, > + .val_bits = 8, > + .max_register = ISL76683_REGMAP_MAX, > + .readable_reg = isl76683_readable_reg, > + .writeable_reg = isl76683_writeable_reg, > + .volatile_reg = isl76683_is_volatile_reg, > + .cache_type = REGCACHE_RBTREE, > +}; > + > +static int isl76683_set_config(struct isl76683_chip *chip) > +{ > + int ret; > + > + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CTRL, > + ISL76683_LUXRANGE_MASK | ISL76683_INTPERS_MASK, > + (chip->luxrange << ISL76683_LUXRANGE_SHFT) | > + ISL76683_INTPERS_DEFAULT); > + if (ret < 0) > + return ret; > + > + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CMD, > + ISL76683_PHOTOD_MASK | ISL76683_WIDTH_MASK, > + (chip->photodiode << ISL76683_PHOTOD_SHFT) | > + ISL76683_WIDTH_DEFAULT); > + if (ret < 0) > + return ret; > + > + ret = regmap_write(chip->rmp, ISL76683_REG_THR_HI, > + ISL76683_THR_DEFAULT); > + if (ret < 0) > + return ret; > + > + ret = regmap_write(chip->rmp, ISL76683_REG_THR_LO, > + ISL76683_THR_DEFAULT); > + if (ret < 0) > + return ret; > + > + return 0; > +} > + > +static int isl76683_power(struct isl76683_chip *chip, bool on) > +{ > + int ret; > + > + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CMD, > + ISL76683_CMD_ENABLE | ISL76683_CMD_PWRDWN, > + 0x0); > + if (ret < 0) > + return ret; > + > + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CMD, > + ISL76683_CMD_ENABLE | ISL76683_CMD_PWRDWN, > + on ? ISL76683_CMD_ENABLE : ISL76683_CMD_PWRDWN); > + if (ret < 0) > + return ret; > + > + return on ? isl76683_set_config(chip) : 0; > +} > + > +static int isl76683_reset(struct isl76683_chip *chip) > +{ > + int ret; > + > + ret = isl76683_power(chip, false); > + if (ret < 0) > + return ret; > + > + return isl76683_power(chip, true); > +} > + > +static int isl76683_read_lux(struct isl76683_chip *chip, > + bool is_processed, int *val) > +{ > + unsigned int sensor_data, range, fsr; > + __le16 sensor_raw; > + int ret; > + > + ret = regmap_bulk_read(chip->rmp, ISL76683_REG_SENSOR_L, > + &sensor_raw, sizeof(sensor_raw)); > + if (ret) > + return ret; > + > + sensor_data = le16_to_cpu(sensor_raw); > + > + if (!is_processed) { > + *val = sensor_data; > + return 0; > + } > + > + /* range values taken from datasheet (table 9) */ > + if (chip->luxrange == ISL76683_LUX_1000) > + range = 973; > + else if (chip->luxrange == ISL76683_LUX_4000) > + range = 3892; > + else if (chip->luxrange == ISL76683_LUX_16000) > + range = 15568; > + else if (chip->luxrange == ISL76683_LUX_64000) > + range = 62272; > + else > + return -EINVAL; > + > + /* equations from datasheet (EQ.3 and EQ.4) */ > + fsr = (100 * range) / chip->external_resistor; > + *val = (fsr * sensor_data) / (1 << ISL76683_RESOLUTION_DEFAULT); > + > + return 0; > +} > + > +static irqreturn_t isl76683_interrupt_handler(int irq, void *private) > +{ > + struct iio_dev *indio_dev = private; > + struct isl76683_chip *chip = iio_priv(indio_dev); > + > + chip->time_ns = iio_get_time_ns(indio_dev); > + > + if (chip->trig_enabled) > + iio_trigger_poll(chip->trig); > + > + if (!completion_done(&chip->irq_complete)) > + complete(&chip->irq_complete); > + > + return IRQ_HANDLED; > +} > + > +static inline int isl76683_start_measurement(struct isl76683_chip *chip) > +{ > + int dummy; > + > + /* dummy read is clearing irq and triggers new measurement */ > + return regmap_read(chip->rmp, ISL76683_REG_CLEAR_INT, &dummy); > +} > + > +static int isl76683_singleshot_conversion(struct isl76683_chip *chip, > + const struct iio_chan_spec *chan, > + bool is_processed, int *val) > +{ > + long timeout; > + int ret; > + > + if (chip->buffer_running) Don't reinvent the wheel. (this is racy btw). iio_claim_direct_mode etc will do this in a non racy fashion for you. > + return -EAGAIN; > + > + switch (chan->channel2) { > + case IIO_MOD_LIGHT_BOTH: > + chip->photodiode = ISL76683_DIODE_DIFF; > + break; > + case IIO_MOD_LIGHT_IR: > + chip->photodiode = ISL76683_DIODE_IR; > + break; > + default: > + chip->photodiode = ISL76683_DIODE_0; > + } > + > + ret = isl76683_set_config(chip); > + if (ret) > + return ret; > + > + ret = isl76683_start_measurement(chip); > + if (ret < 0) > + return ret; > + > + /* wait for measurement to complete */ > + timeout = wait_for_completion_interruptible_timeout( > + &chip->irq_complete, > + msecs_to_jiffies(5000)); > + if (timeout == 0) { > + dev_err(&chip->client->dev, "measurement timed out\n"); > + return -ETIMEDOUT; > + } else if (timeout < 0) { > + dev_err(&chip->client->dev, "wait for measurement failed\n"); > + return -EINTR; > + } > + > + ret = isl76683_read_lux(chip, is_processed, val); > + if (ret) { > + dev_err(&chip->client->dev, "%s: Error %d reading lux\n", > + __func__, ret); > + return ret; > + } > + > + return IIO_VAL_INT; > +} > + > +static irqreturn_t isl76683_trigger_handler(int irq, void *p) > +{ > + struct iio_poll_func *pf = p; > + struct iio_dev *indio_dev = pf->indio_dev; > + struct isl76683_chip *chip = iio_priv(indio_dev); > + __le16 buf; > + int ret; > + > + ret = regmap_bulk_read(chip->rmp, ISL76683_REG_SENSOR_L, &buf, 2); > + if (ret) > + return ret; > + > + chip->buffer[0] = le16_to_cpu(buf); > + > + iio_push_to_buffers_with_timestamp(indio_dev, chip->buffer, > + chip->time_ns); > + > + iio_trigger_notify_done(indio_dev->trig); > + > + ret = isl76683_start_measurement(chip); > + if (ret < 0) > + return ret; > + > + return IRQ_HANDLED; > +} > + > +static int isl76683_buffer_preenable(struct iio_dev *indio_dev) > +{ > + struct isl76683_chip *chip = iio_priv(indio_dev); > + > + mutex_lock(&chip->lock); > + chip->buffer_running = true; > + mutex_unlock(&chip->lock); > + chip->photodiode = indio_dev->channels[0].channel2; > + return isl76683_set_config(chip); > +} > + > +static int isl76683_buffer_postdisable(struct iio_dev *indio_dev) > +{ > + struct isl76683_chip *chip = iio_priv(indio_dev); > + > + chip->buffer_running = false; > + return 0; > +} > + > +static const struct iio_buffer_setup_ops isl76683_buffer_setup_ops = { > + .preenable = &isl76683_buffer_preenable, > + .postdisable = &isl76683_buffer_postdisable, > + .predisable = iio_triggered_buffer_predisable, > + .postenable = iio_triggered_buffer_postenable, > + .validate_scan_mask = &iio_validate_scan_mask_onehot, > +}; > + > +static int isl76683_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int *val, int *val2, long mask) > +{ > + struct isl76683_chip *chip = iio_priv(indio_dev); > + int ret; > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + mutex_lock(&chip->lock); > + ret = isl76683_singleshot_conversion(chip, chan, false, val); > + mutex_unlock(&chip->lock); > + return ret; > + case IIO_CHAN_INFO_PROCESSED: > + mutex_lock(&chip->lock); > + ret = isl76683_singleshot_conversion(chip, chan, true, val); > + mutex_unlock(&chip->lock); > + return ret; > + case IIO_CHAN_INFO_HARDWAREGAIN: > + *val = isl76683_lux_ranges_available[chip->luxrange]; > + return IIO_VAL_INT; > + } > + > + return -EINVAL; > +} > + > +static int isl76683_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int val, int val2, long mask) > +{ > + struct isl76683_chip *chip = iio_priv(indio_dev); > + int ret; > + > + switch (mask) { > + case IIO_CHAN_INFO_HARDWAREGAIN: > + mutex_lock(&chip->lock); > + chip->luxrange = find_closest(val, > + isl76683_lux_ranges_available, > + ARRAY_SIZE(isl76683_lux_ranges_available)); > + ret = isl76683_set_config(chip); > + mutex_unlock(&chip->lock); > + return ret; > + } > + > + return -EINVAL; > +} > + > +static IIO_CONST_ATTR(in_illuminance_hardwaregain_available, > + ISL76683_LUXRANGE_STR); > + > +static struct attribute *isl76683_attributes[] = { > + &iio_const_attr_in_illuminance_hardwaregain_available.dev_attr.attr, > + NULL > +}; > + > +static const struct attribute_group isl76683_attribute_group = { > + .attrs = isl76683_attributes, > +}; > + > +#define ISL76683_CHANNEL_2(_ch2, _si) { \ > + .type = IIO_LIGHT, \ > + .modified = 1, \ > + .channel2 = _ch2, \ > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ > + BIT(IIO_CHAN_INFO_PROCESSED), \ > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ > + .scan_index = _si, \ > + .scan_type = { \ > + .sign = 'u', \ > + .realbits = 16, \ > + .storagebits = 16, \ > + .endianness = IIO_CPU, \ > + }, \ > +} > + > +static const struct iio_chan_spec isl76683_channels[] = { > + { > + .type = IIO_LIGHT, > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | > + BIT(IIO_CHAN_INFO_PROCESSED), > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN), > + .scan_index = 0, > + .scan_type = { > + .sign = 'u', > + .realbits = 16, > + .storagebits = 16, > + .endianness = IIO_CPU, > + }, > + }, > + ISL76683_CHANNEL_2(IIO_MOD_LIGHT_IR, 1), > + ISL76683_CHANNEL_2(IIO_MOD_LIGHT_BOTH, 2), > + IIO_CHAN_SOFT_TIMESTAMP(3), > +}; > + > +static int isl76683_update_scan_mode(struct iio_dev *indio_dev, > + const unsigned long *scan_mask) > +{ > + struct isl76683_chip *chip = iio_priv(indio_dev); > + > + kfree(chip->buffer); > + chip->buffer = kzalloc(indio_dev->scan_bytes, GFP_KERNEL); > + if (chip->buffer == NULL) > + return -ENOMEM; I would be lazy and just make the buffer big enough for all cases. You will want to play the __cachline_aligned game to make it dma safe at the end of the chip structure. > + > + return 0; > +} > + > +static const struct iio_info isl76683_info = { > + .update_scan_mode = isl76683_update_scan_mode, > + .read_raw = isl76683_read_raw, > + .write_raw = isl76683_write_raw, > + .attrs = &isl76683_attribute_group, > + .driver_module = THIS_MODULE, This will not work with latest IIO tree (or linux-next) Just drop setting this field as it no longer exists. > +}; > + > +static int isl76683_set_trigger_state(struct iio_trigger *trig, bool enable) > +{ > + struct isl76683_chip *chip = iio_trigger_get_drvdata(trig); > + int ret; > + > + if (enable) { > + chip->trig_enabled = true; This is unusual enough that I'd like a comment here on how you would end up in a state where this is necessary. > + ret = isl76683_start_measurement(chip); > + if (ret < 0) > + return ret; > + } else > + chip->trig_enabled = false; > + > + return 0; > +} > + > +static const struct iio_trigger_ops isl76683_trigger_ops = { > + .owner = THIS_MODULE, > + .set_trigger_state = isl76683_set_trigger_state, > + .validate_device = iio_trigger_validate_own_device, > +}; > + > +static int isl76683_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct isl76683_chip *chip; > + struct iio_dev *indio_dev; > + struct device_node *np = client->dev.of_node; > + int rs = ISL76683_EXT_RESISTOR_DEFAULT; > + int v, ret; > + > + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); > + if (!indio_dev) > + return -ENOMEM; > + > + chip = iio_priv(indio_dev); > + > + i2c_set_clientdata(client, indio_dev); > + chip->client = client; > + > + if (np) { > + ret = of_property_read_u32(np, "isil,external-resistor", &v); > + if (ret || v < ISL76683_KOHM_MIN || v > ISL76683_KOHM_MAX) > + dev_warn(&client->dev, > + "assuming %i kOhm resistor\n", rs); > + else > + rs = v; > + } > + > + chip->luxrange = ISL76683_LUX_RANGE_DEFAULT; > + chip->external_resistor = rs; > + chip->photodiode = ISL76683_DIODE_DEFAULT; > + chip->buffer_running = false; > + > + chip->rmp = devm_regmap_init_i2c(client, &isl76683_regmap_config); > + if (IS_ERR(chip->rmp)) { > + ret = PTR_ERR(chip->rmp); > + dev_err(&client->dev, "%s: Error %d initializing regmap\n", > + __func__, ret); > + return ret; > + } > + > + mutex_init(&chip->lock); > + > + init_completion(&chip->irq_complete); > + > + if (!client->irq) { > + dev_err(&client->dev, "no interrupt configured\n"); > + return -EINVAL; > + } > + > + indio_dev->dev.parent = &client->dev; > + indio_dev->info = &isl76683_info; > + indio_dev->channels = isl76683_channels; > + indio_dev->num_channels = ARRAY_SIZE(isl76683_channels); > + indio_dev->name = id->name; > + indio_dev->modes = INDIO_DIRECT_MODE; > + > + chip->trig_enabled = false; > + chip->trig = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", > + indio_dev->name, indio_dev->id); > + if (!chip->trig) > + return -ENOMEM; > + > + chip->trig->ops = &isl76683_trigger_ops; > + chip->trig->dev.parent = &client->dev; > + iio_trigger_set_drvdata(chip->trig, chip); > + > + ret = devm_request_irq(&client->dev, client->irq, > + isl76683_interrupt_handler, > + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, > + "isl76683_event", indio_dev); > + if (ret) { > + dev_err(&client->dev, "irq request error\n"); > + return ret; > + } > + > + ret = devm_iio_trigger_register(&client->dev, chip->trig); > + if (ret) { > + dev_err(&client->dev, "iio_trigger register error\n"); > + return ret; > + } > + > + ret = isl76683_reset(chip); > + if (ret) { > + dev_err(&client->dev, "reset failed\n"); > + return ret; > + } > + > + ret = isl76683_read_lux(chip, false, &v); > + if (ret) { > + dev_err(&client->dev, "initial dummy readout failed\n"); > + return ret; > + } > + > + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, > + &isl76683_trigger_handler, &isl76683_buffer_setup_ops); > + if (ret) > + return ret; > + > + ret = devm_iio_device_register(&client->dev, indio_dev); > + if (ret) { > + dev_err(&client->dev, > + "%s(): iio registration failed with error %d\n", > + __func__, ret); > + return ret; Drop this return ret. > + } > + > + return ret; > +} > + > +static int isl76683_remove(struct i2c_client *client) > +{ > + struct iio_dev *indio_dev = i2c_get_clientdata(client); > + struct isl76683_chip *chip = iio_priv(indio_dev); > + > + pm_runtime_disable(&client->dev); > + pm_runtime_set_suspended(&client->dev); > + isl76683_power(chip, false); > + kfree(chip->buffer); > + > + return 0; > +} > + > +#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM) > +static int isl76683_runtime_suspend(struct device *dev) > +{ > + struct isl76683_chip *chip = > + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); > + int ret; > + > + mutex_lock(&chip->lock); > + ret = isl76683_power(chip, false); > + regcache_mark_dirty(chip->rmp); > + mutex_unlock(&chip->lock); > + > + return ret; > +} > + > +static int isl76683_runtime_resume(struct device *dev) > +{ > + struct isl76683_chip *chip = > + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); > + int ret; > + > + mutex_lock(&chip->lock); > + ret = isl76683_power(chip, true); > + mutex_unlock(&chip->lock); > + > + return ret; > +} > +#endif > + > +static const struct dev_pm_ops isl76683_pm_ops = { > + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, > + pm_runtime_force_resume) > + SET_RUNTIME_PM_OPS(isl76683_runtime_suspend, > + isl76683_runtime_resume, NULL) > +}; > + > +static const struct i2c_device_id isl76683_id[] = { > + {"isl76683", 0}, > + {} > +}; > +MODULE_DEVICE_TABLE(i2c, isl76683_id); > + > +static const struct of_device_id isl76683_of_match[] = { > + { .compatible = "isil,isl76683", }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, isl76683_of_match); > + > +static struct i2c_driver isl76683_driver = { > + .driver = { > + .name = "isl76683", > + .of_match_table = isl76683_of_match, > + .pm = &isl76683_pm_ops, > + }, > + .probe = isl76683_probe, > + .remove = isl76683_remove, > + .id_table = isl76683_id, > +}; > + > +module_i2c_driver(isl76683_driver); > + > +MODULE_DESCRIPTION("ISL76683 Ambient Light Sensor driver"); > +MODULE_LICENSE("GPL"); > +MODULE_AUTHOR("Christoph Fritz <chf.fritz@googlemail.com>"); -- 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..4f0882c 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -194,6 +194,18 @@ config ISL29125 To compile this driver as a module, choose M here: the module will be called isl29125. +config ISL76683 + tristate "Intersil ISL76683 light sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build a driver for the Intersil ISL76683 + light sensor for I2C. + + To compile this driver as a module, choose M here: the module will be + called isl76683. + config HID_SENSOR_ALS depends on HID_SENSOR_HUB select IIO_BUFFER diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index fa32fa4..886a51f 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o obj-$(CONFIG_ISL29125) += isl29125.o +obj-$(CONFIG_ISL76683) += isl76683.o obj-$(CONFIG_JSA1212) += jsa1212.o obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o obj-$(CONFIG_LTR501) += ltr501.o diff --git a/drivers/iio/light/isl76683.c b/drivers/iio/light/isl76683.c new file mode 100644 index 0000000..b730276 --- /dev/null +++ b/drivers/iio/light/isl76683.c @@ -0,0 +1,693 @@ +/* + * IIO driver for the light sensor ISL76683. + * + * Copyright (c) 2017 Christoph Fritz <chf.fritz@googlemail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * Datasheet: + * http://www.intersil.com/content/dam/Intersil/documents/isl7/isl76683.pdf + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/util_macros.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define ISL76683_REG_CMD 0x00 +#define ISL76683_REG_CTRL 0x01 +#define ISL76683_REG_THR_HI 0x02 +#define ISL76683_REG_THR_LO 0x03 +#define ISL76683_REG_SENSOR_L 0x04 +#define ISL76683_REG_SENSOR_M 0x05 +#define ISL76683_REG_CLEAR_INT 0x40 +#define ISL76683_REGMAP_MAX 0x40 + +#define ISL76683_CMD_ENABLE BIT(7) +#define ISL76683_CMD_PWRDWN BIT(6) +#define ISL76683_WIDTH_MASK 0x3 +#define ISL76683_PHOTOD_SHFT 2 +#define ISL76683_PHOTOD_MASK (0x3 << ISL76683_PHOTOD_SHFT) +#define ISL76683_INTPERS_MASK 0x3 +#define ISL76683_LUXRANGE_SHFT 2 +#define ISL76683_LUXRANGE_MASK (0x3 << ISL76683_LUXRANGE_SHFT) +#define ISL76683_LUXRANGE_STR "1000 4000 16000 64000" + +enum isl76683_dmode { + ISL76683_DIODE_0 = 0, + ISL76683_DIODE_IR, + ISL76683_DIODE_DIFF, +}; + +enum isl76683_lux_range { + ISL76683_LUX_1000 = 0, + ISL76683_LUX_4000, + ISL76683_LUX_16000, + ISL76683_LUX_64000, +}; + +static const int isl76683_lux_ranges_available[] = { + 1000, 4000, 16000, 64000}; + +#define ISL76683_LUX_RANGE_DEFAULT ISL76683_LUX_1000 +#define ISL76683_DIODE_MAX ISL76683_DIODE_DIFF +#define ISL76683_DIODE_DEFAULT ISL76683_DIODE_0 +#define ISL76683_WIDTH_DEFAULT 0x0 +#define ISL76683_RESOLUTION_DEFAULT 16 +#define ISL76683_EXT_RESISTOR_DEFAULT 100 +#define ISL76683_KOHM_MIN 1 +#define ISL76683_KOHM_MAX 1000 +#define ISL76683_INTPERS_DEFAULT 0x0 +#define ISL76683_THR_DEFAULT 0x7f + +struct isl76683_chip { + enum isl76683_lux_range luxrange; + int external_resistor; + enum isl76683_dmode photodiode; + struct i2c_client *client; + struct regmap *rmp; + struct completion irq_complete; + struct iio_trigger *trig; + bool trig_enabled; + struct mutex lock; + __le16 *buffer; + s64 time_ns; + bool buffer_running; +}; + +static bool isl76683_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISL76683_REG_CMD: + case ISL76683_REG_CTRL: + case ISL76683_REG_THR_HI: + case ISL76683_REG_THR_LO: + case ISL76683_REG_SENSOR_L: + case ISL76683_REG_SENSOR_M: + case ISL76683_REG_CLEAR_INT: + return true; + default: + return false; + } +} + +static bool isl76683_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISL76683_REG_CMD: + case ISL76683_REG_CTRL: + case ISL76683_REG_THR_HI: + case ISL76683_REG_THR_LO: + return true; + default: + return false; + } +} + +static bool isl76683_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISL76683_REG_SENSOR_L: + case ISL76683_REG_SENSOR_M: + case ISL76683_REG_CLEAR_INT: + return true; + default: + return false; + } +} + +static const struct regmap_config isl76683_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ISL76683_REGMAP_MAX, + .readable_reg = isl76683_readable_reg, + .writeable_reg = isl76683_writeable_reg, + .volatile_reg = isl76683_is_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static int isl76683_set_config(struct isl76683_chip *chip) +{ + int ret; + + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CTRL, + ISL76683_LUXRANGE_MASK | ISL76683_INTPERS_MASK, + (chip->luxrange << ISL76683_LUXRANGE_SHFT) | + ISL76683_INTPERS_DEFAULT); + if (ret < 0) + return ret; + + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CMD, + ISL76683_PHOTOD_MASK | ISL76683_WIDTH_MASK, + (chip->photodiode << ISL76683_PHOTOD_SHFT) | + ISL76683_WIDTH_DEFAULT); + if (ret < 0) + return ret; + + ret = regmap_write(chip->rmp, ISL76683_REG_THR_HI, + ISL76683_THR_DEFAULT); + if (ret < 0) + return ret; + + ret = regmap_write(chip->rmp, ISL76683_REG_THR_LO, + ISL76683_THR_DEFAULT); + if (ret < 0) + return ret; + + return 0; +} + +static int isl76683_power(struct isl76683_chip *chip, bool on) +{ + int ret; + + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CMD, + ISL76683_CMD_ENABLE | ISL76683_CMD_PWRDWN, + 0x0); + if (ret < 0) + return ret; + + ret = regmap_update_bits(chip->rmp, ISL76683_REG_CMD, + ISL76683_CMD_ENABLE | ISL76683_CMD_PWRDWN, + on ? ISL76683_CMD_ENABLE : ISL76683_CMD_PWRDWN); + if (ret < 0) + return ret; + + return on ? isl76683_set_config(chip) : 0; +} + +static int isl76683_reset(struct isl76683_chip *chip) +{ + int ret; + + ret = isl76683_power(chip, false); + if (ret < 0) + return ret; + + return isl76683_power(chip, true); +} + +static int isl76683_read_lux(struct isl76683_chip *chip, + bool is_processed, int *val) +{ + unsigned int sensor_data, range, fsr; + __le16 sensor_raw; + int ret; + + ret = regmap_bulk_read(chip->rmp, ISL76683_REG_SENSOR_L, + &sensor_raw, sizeof(sensor_raw)); + if (ret) + return ret; + + sensor_data = le16_to_cpu(sensor_raw); + + if (!is_processed) { + *val = sensor_data; + return 0; + } + + /* range values taken from datasheet (table 9) */ + if (chip->luxrange == ISL76683_LUX_1000) + range = 973; + else if (chip->luxrange == ISL76683_LUX_4000) + range = 3892; + else if (chip->luxrange == ISL76683_LUX_16000) + range = 15568; + else if (chip->luxrange == ISL76683_LUX_64000) + range = 62272; + else + return -EINVAL; + + /* equations from datasheet (EQ.3 and EQ.4) */ + fsr = (100 * range) / chip->external_resistor; + *val = (fsr * sensor_data) / (1 << ISL76683_RESOLUTION_DEFAULT); + + return 0; +} + +static irqreturn_t isl76683_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct isl76683_chip *chip = iio_priv(indio_dev); + + chip->time_ns = iio_get_time_ns(indio_dev); + + if (chip->trig_enabled) + iio_trigger_poll(chip->trig); + + if (!completion_done(&chip->irq_complete)) + complete(&chip->irq_complete); + + return IRQ_HANDLED; +} + +static inline int isl76683_start_measurement(struct isl76683_chip *chip) +{ + int dummy; + + /* dummy read is clearing irq and triggers new measurement */ + return regmap_read(chip->rmp, ISL76683_REG_CLEAR_INT, &dummy); +} + +static int isl76683_singleshot_conversion(struct isl76683_chip *chip, + const struct iio_chan_spec *chan, + bool is_processed, int *val) +{ + long timeout; + int ret; + + if (chip->buffer_running) + return -EAGAIN; + + switch (chan->channel2) { + case IIO_MOD_LIGHT_BOTH: + chip->photodiode = ISL76683_DIODE_DIFF; + break; + case IIO_MOD_LIGHT_IR: + chip->photodiode = ISL76683_DIODE_IR; + break; + default: + chip->photodiode = ISL76683_DIODE_0; + } + + ret = isl76683_set_config(chip); + if (ret) + return ret; + + ret = isl76683_start_measurement(chip); + if (ret < 0) + return ret; + + /* wait for measurement to complete */ + timeout = wait_for_completion_interruptible_timeout( + &chip->irq_complete, + msecs_to_jiffies(5000)); + if (timeout == 0) { + dev_err(&chip->client->dev, "measurement timed out\n"); + return -ETIMEDOUT; + } else if (timeout < 0) { + dev_err(&chip->client->dev, "wait for measurement failed\n"); + return -EINTR; + } + + ret = isl76683_read_lux(chip, is_processed, val); + if (ret) { + dev_err(&chip->client->dev, "%s: Error %d reading lux\n", + __func__, ret); + return ret; + } + + return IIO_VAL_INT; +} + +static irqreturn_t isl76683_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct isl76683_chip *chip = iio_priv(indio_dev); + __le16 buf; + int ret; + + ret = regmap_bulk_read(chip->rmp, ISL76683_REG_SENSOR_L, &buf, 2); + if (ret) + return ret; + + chip->buffer[0] = le16_to_cpu(buf); + + iio_push_to_buffers_with_timestamp(indio_dev, chip->buffer, + chip->time_ns); + + iio_trigger_notify_done(indio_dev->trig); + + ret = isl76683_start_measurement(chip); + if (ret < 0) + return ret; + + return IRQ_HANDLED; +} + +static int isl76683_buffer_preenable(struct iio_dev *indio_dev) +{ + struct isl76683_chip *chip = iio_priv(indio_dev); + + mutex_lock(&chip->lock); + chip->buffer_running = true; + mutex_unlock(&chip->lock); + chip->photodiode = indio_dev->channels[0].channel2; + return isl76683_set_config(chip); +} + +static int isl76683_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct isl76683_chip *chip = iio_priv(indio_dev); + + chip->buffer_running = false; + return 0; +} + +static const struct iio_buffer_setup_ops isl76683_buffer_setup_ops = { + .preenable = &isl76683_buffer_preenable, + .postdisable = &isl76683_buffer_postdisable, + .predisable = iio_triggered_buffer_predisable, + .postenable = iio_triggered_buffer_postenable, + .validate_scan_mask = &iio_validate_scan_mask_onehot, +}; + +static int isl76683_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct isl76683_chip *chip = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&chip->lock); + ret = isl76683_singleshot_conversion(chip, chan, false, val); + mutex_unlock(&chip->lock); + return ret; + case IIO_CHAN_INFO_PROCESSED: + mutex_lock(&chip->lock); + ret = isl76683_singleshot_conversion(chip, chan, true, val); + mutex_unlock(&chip->lock); + return ret; + case IIO_CHAN_INFO_HARDWAREGAIN: + *val = isl76683_lux_ranges_available[chip->luxrange]; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int isl76683_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct isl76683_chip *chip = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + mutex_lock(&chip->lock); + chip->luxrange = find_closest(val, + isl76683_lux_ranges_available, + ARRAY_SIZE(isl76683_lux_ranges_available)); + ret = isl76683_set_config(chip); + mutex_unlock(&chip->lock); + return ret; + } + + return -EINVAL; +} + +static IIO_CONST_ATTR(in_illuminance_hardwaregain_available, + ISL76683_LUXRANGE_STR); + +static struct attribute *isl76683_attributes[] = { + &iio_const_attr_in_illuminance_hardwaregain_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group isl76683_attribute_group = { + .attrs = isl76683_attributes, +}; + +#define ISL76683_CHANNEL_2(_ch2, _si) { \ + .type = IIO_LIGHT, \ + .modified = 1, \ + .channel2 = _ch2, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_chan_spec isl76683_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + ISL76683_CHANNEL_2(IIO_MOD_LIGHT_IR, 1), + ISL76683_CHANNEL_2(IIO_MOD_LIGHT_BOTH, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static int isl76683_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct isl76683_chip *chip = iio_priv(indio_dev); + + kfree(chip->buffer); + chip->buffer = kzalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (chip->buffer == NULL) + return -ENOMEM; + + return 0; +} + +static const struct iio_info isl76683_info = { + .update_scan_mode = isl76683_update_scan_mode, + .read_raw = isl76683_read_raw, + .write_raw = isl76683_write_raw, + .attrs = &isl76683_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int isl76683_set_trigger_state(struct iio_trigger *trig, bool enable) +{ + struct isl76683_chip *chip = iio_trigger_get_drvdata(trig); + int ret; + + if (enable) { + chip->trig_enabled = true; + ret = isl76683_start_measurement(chip); + if (ret < 0) + return ret; + } else + chip->trig_enabled = false; + + return 0; +} + +static const struct iio_trigger_ops isl76683_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = isl76683_set_trigger_state, + .validate_device = iio_trigger_validate_own_device, +}; + +static int isl76683_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct isl76683_chip *chip; + struct iio_dev *indio_dev; + struct device_node *np = client->dev.of_node; + int rs = ISL76683_EXT_RESISTOR_DEFAULT; + int v, ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + chip->client = client; + + if (np) { + ret = of_property_read_u32(np, "isil,external-resistor", &v); + if (ret || v < ISL76683_KOHM_MIN || v > ISL76683_KOHM_MAX) + dev_warn(&client->dev, + "assuming %i kOhm resistor\n", rs); + else + rs = v; + } + + chip->luxrange = ISL76683_LUX_RANGE_DEFAULT; + chip->external_resistor = rs; + chip->photodiode = ISL76683_DIODE_DEFAULT; + chip->buffer_running = false; + + chip->rmp = devm_regmap_init_i2c(client, &isl76683_regmap_config); + if (IS_ERR(chip->rmp)) { + ret = PTR_ERR(chip->rmp); + dev_err(&client->dev, "%s: Error %d initializing regmap\n", + __func__, ret); + return ret; + } + + mutex_init(&chip->lock); + + init_completion(&chip->irq_complete); + + if (!client->irq) { + dev_err(&client->dev, "no interrupt configured\n"); + return -EINVAL; + } + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &isl76683_info; + indio_dev->channels = isl76683_channels; + indio_dev->num_channels = ARRAY_SIZE(isl76683_channels); + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + chip->trig_enabled = false; + chip->trig = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", + indio_dev->name, indio_dev->id); + if (!chip->trig) + return -ENOMEM; + + chip->trig->ops = &isl76683_trigger_ops; + chip->trig->dev.parent = &client->dev; + iio_trigger_set_drvdata(chip->trig, chip); + + ret = devm_request_irq(&client->dev, client->irq, + isl76683_interrupt_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "isl76683_event", indio_dev); + if (ret) { + dev_err(&client->dev, "irq request error\n"); + return ret; + } + + ret = devm_iio_trigger_register(&client->dev, chip->trig); + if (ret) { + dev_err(&client->dev, "iio_trigger register error\n"); + return ret; + } + + ret = isl76683_reset(chip); + if (ret) { + dev_err(&client->dev, "reset failed\n"); + return ret; + } + + ret = isl76683_read_lux(chip, false, &v); + if (ret) { + dev_err(&client->dev, "initial dummy readout failed\n"); + return ret; + } + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, + &isl76683_trigger_handler, &isl76683_buffer_setup_ops); + if (ret) + return ret; + + ret = devm_iio_device_register(&client->dev, indio_dev); + if (ret) { + dev_err(&client->dev, + "%s(): iio registration failed with error %d\n", + __func__, ret); + return ret; + } + + return ret; +} + +static int isl76683_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct isl76683_chip *chip = iio_priv(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + isl76683_power(chip, false); + kfree(chip->buffer); + + return 0; +} + +#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM) +static int isl76683_runtime_suspend(struct device *dev) +{ + struct isl76683_chip *chip = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + int ret; + + mutex_lock(&chip->lock); + ret = isl76683_power(chip, false); + regcache_mark_dirty(chip->rmp); + mutex_unlock(&chip->lock); + + return ret; +} + +static int isl76683_runtime_resume(struct device *dev) +{ + struct isl76683_chip *chip = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + int ret; + + mutex_lock(&chip->lock); + ret = isl76683_power(chip, true); + mutex_unlock(&chip->lock); + + return ret; +} +#endif + +static const struct dev_pm_ops isl76683_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(isl76683_runtime_suspend, + isl76683_runtime_resume, NULL) +}; + +static const struct i2c_device_id isl76683_id[] = { + {"isl76683", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, isl76683_id); + +static const struct of_device_id isl76683_of_match[] = { + { .compatible = "isil,isl76683", }, + { }, +}; +MODULE_DEVICE_TABLE(of, isl76683_of_match); + +static struct i2c_driver isl76683_driver = { + .driver = { + .name = "isl76683", + .of_match_table = isl76683_of_match, + .pm = &isl76683_pm_ops, + }, + .probe = isl76683_probe, + .remove = isl76683_remove, + .id_table = isl76683_id, +}; + +module_i2c_driver(isl76683_driver); + +MODULE_DESCRIPTION("ISL76683 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Christoph Fritz <chf.fritz@googlemail.com>");
This patch adds support for Intersil isl76683 light sensor. Signed-off-by: Christoph Fritz <chf.fritz@googlemail.com> --- drivers/iio/light/Kconfig | 12 + drivers/iio/light/Makefile | 1 + drivers/iio/light/isl76683.c | 693 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 706 insertions(+) create mode 100644 drivers/iio/light/isl76683.c