diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index b27719cefcf9..182bd18c4bb2 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -516,6 +516,8 @@ config US5182D config VCNL4000 tristate "VCNL4000/4010/4020/4200 combined ALS and proximity sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER depends on I2C help Say Y here if you want to build a driver for the Vishay VCNL4000, diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c index f6198c67e87e..2a4b3d331055 100644 --- a/drivers/iio/light/vcnl4000.c +++ b/drivers/iio/light/vcnl4000.c @@ -5,6 +5,7 @@ * * Copyright 2012 Peter Meerwald * Copyright 2019 Pursim SPC + * Copyright 2020 Mathieu Othacehe * * IIO driver for: * VCNL4000/10/20 (7-bit I2C slave address 0x13) @@ -13,8 +14,7 @@ * * TODO: * allow to adjust IR current - * periodic ALS/proximity measurement (VCNL4010/20) - * interrupts (VCNL4010/20/40, VCNL4200) + * interrupts (VCNL4040, VCNL4200) */ #include @@ -24,9 +24,13 @@ #include #include +#include #include #include #include +#include +#include +#include #define VCNL4000_DRV_NAME "vcnl4000" #define VCNL4000_PROD_ID 0x01 @@ -772,17 +776,26 @@ static const struct iio_chan_spec vcnl4000_channels[] = { static const struct iio_chan_spec vcnl4010_channels[] = { { .type = IIO_LIGHT, + .scan_index = -1, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), }, { .type = IIO_PROXIMITY, + .scan_index = 0, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SAMP_FREQ), .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), .event_spec = vcnl4000_event_spec, .num_event_specs = ARRAY_SIZE(vcnl4000_event_spec), .ext_info = vcnl4000_ext_info, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, }, + IIO_CHAN_SOFT_TIMESTAMP(1), }; static const struct iio_info vcnl4000_info = { @@ -884,10 +897,139 @@ static irqreturn_t vcnl4010_irq_thread(int irq, void *p) isr & VCNL4010_INT_THR); } + if (isr & VCNL4010_INT_DRDY && iio_buffer_enabled(indio_dev)) + iio_trigger_poll_chained(indio_dev->trig); + end: return IRQ_HANDLED; } +static irqreturn_t vcnl4010_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct vcnl4000_data *data = iio_priv(indio_dev); + const unsigned long *active_scan_mask = indio_dev->active_scan_mask; + u16 buffer[8] = {0}; /* 1x16-bit + ts */ + bool data_read = false; + unsigned long isr; + int val = 0; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, VCNL4010_ISR); + if (ret < 0) + goto end; + + isr = ret; + + if (test_bit(0, active_scan_mask)) { + if (test_bit(VCNL4010_INT_PROXIMITY, &isr)) { + ret = vcnl4000_read_data(data, + VCNL4000_PS_RESULT_HI, + &val); + if (ret < 0) + goto end; + + buffer[0] = val; + data_read = true; + } + } + + ret = i2c_smbus_write_byte_data(data->client, VCNL4010_ISR, + isr & VCNL4010_INT_DRDY); + if (ret < 0) + goto end; + + if (!data_read) + goto end; + + iio_push_to_buffers_with_timestamp(indio_dev, buffer, + iio_get_time_ns(indio_dev)); + +end: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int vcnl4010_buffer_postenable(struct iio_dev *indio_dev) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + int ret; + int cmd; + + ret = iio_triggered_buffer_postenable(indio_dev); + if (ret) + return ret; + + /* Do not enable the buffer if we are already capturing events. */ + if (vcnl4010_is_in_periodic_mode(data)) { + ret = -EBUSY; + goto end; + } + + ret = i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL, + VCNL4010_INT_PROX_EN); + if (ret < 0) + goto end; + + cmd = VCNL4000_SELF_TIMED_EN | VCNL4000_PROX_EN; + ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, cmd); + if (ret < 0) + goto end; + + return 0; +end: + iio_triggered_buffer_predisable(indio_dev); + + return ret; +} + +static int vcnl4010_buffer_predisable(struct iio_dev *indio_dev) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + int ret, ret_disable; + + ret = i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL, 0); + if (ret < 0) + goto end; + + ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, 0); + +end: + ret_disable = iio_triggered_buffer_predisable(indio_dev); + if (ret == 0) + ret = ret_disable; + + return ret; +} + +static const struct iio_buffer_setup_ops vcnl4010_buffer_ops = { + .postenable = &vcnl4010_buffer_postenable, + .predisable = &vcnl4010_buffer_predisable, +}; + +static const struct iio_trigger_ops vcnl4010_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static int vcnl4010_probe_trigger(struct iio_dev *indio_dev) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + struct iio_trigger *trigger; + + trigger = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", + indio_dev->name, indio_dev->id); + if (!trigger) + return -ENOMEM; + + trigger->dev.parent = &client->dev; + trigger->ops = &vcnl4010_trigger_ops; + iio_trigger_set_drvdata(trigger, indio_dev); + + return devm_iio_trigger_register(&client->dev, trigger); +} + static int vcnl4000_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -924,6 +1066,16 @@ static int vcnl4000_probe(struct i2c_client *client, indio_dev->modes = INDIO_DIRECT_MODE; if (client->irq && data->chip_spec->irq_support) { + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, + NULL, + vcnl4010_trigger_handler, + &vcnl4010_buffer_ops); + if (ret < 0) { + dev_err(&client->dev, + "unable to setup iio triggered buffer\n"); + return ret; + } + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, vcnl4010_irq_thread, IRQF_TRIGGER_FALLING | @@ -934,6 +1086,10 @@ static int vcnl4000_probe(struct i2c_client *client, dev_err(&client->dev, "irq request failed\n"); return ret; } + + ret = vcnl4010_probe_trigger(indio_dev); + if (ret < 0) + return ret; } ret = pm_runtime_set_active(&client->dev); @@ -1029,5 +1185,6 @@ static struct i2c_driver vcnl4000_driver = { module_i2c_driver(vcnl4000_driver); MODULE_AUTHOR("Peter Meerwald "); +MODULE_AUTHOR("Mathieu Othacehe "); MODULE_DESCRIPTION("Vishay VCNL4000 proximity/ambient light sensor driver"); MODULE_LICENSE("GPL");