summaryrefslogtreecommitdiffstats
path: root/drivers/iio/proximity
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
commit5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch)
treea94efe259b9009378be6d90eb30d2b019d95c194 /drivers/iio/proximity
parentInitial commit. (diff)
downloadlinux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz
linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--drivers/iio/proximity/Kconfig165
-rw-r--r--drivers/iio/proximity/Makefile19
-rw-r--r--drivers/iio/proximity/as3935.c488
-rw-r--r--drivers/iio/proximity/isl29501.c1018
-rw-r--r--drivers/iio/proximity/mb1232.c272
-rw-r--r--drivers/iio/proximity/ping.c333
-rw-r--r--drivers/iio/proximity/pulsedlight-lidar-lite-v2.c380
-rw-r--r--drivers/iio/proximity/rfd77402.c348
-rw-r--r--drivers/iio/proximity/srf04.c408
-rw-r--r--drivers/iio/proximity/srf08.c559
-rw-r--r--drivers/iio/proximity/sx9310.c1075
-rw-r--r--drivers/iio/proximity/sx9500.c1074
-rw-r--r--drivers/iio/proximity/vcnl3020.c257
-rw-r--r--drivers/iio/proximity/vl53l0x-i2c.c244
14 files changed, 6640 insertions, 0 deletions
diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig
new file mode 100644
index 000000000..12672a0e8
--- /dev/null
+++ b/drivers/iio/proximity/Kconfig
@@ -0,0 +1,165 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Proximity sensors
+#
+
+menu "Lightning sensors"
+
+config AS3935
+ tristate "AS3935 Franklin lightning sensor"
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ depends on SPI
+ help
+ Say Y here to build SPI interface support for the Austrian
+ Microsystems AS3935 lightning detection sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called as3935
+
+endmenu
+
+menu "Proximity and distance sensors"
+
+config ISL29501
+ tristate "Intersil ISL29501 Time Of Flight sensor"
+ depends on I2C
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ select IIO_KFIFO_BUF
+ help
+ Say Y here if you want to build a driver for the Intersil ISL29501
+ Time of Flight sensor.
+
+ To compile this driver as a module, choose M here: the module will be
+ called isl29501.
+
+config LIDAR_LITE_V2
+ tristate "PulsedLight LIDAR sensor"
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ depends on I2C
+ help
+ Say Y to build a driver for PulsedLight LIDAR range finding
+ sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pulsedlight-lite-v2
+
+config MB1232
+ tristate "MaxSonar I2CXL family ultrasonic sensors"
+ depends on I2C
+ help
+ Say Y to build a driver for the ultrasonic sensors I2CXL of
+ MaxBotix which have an i2c interface. It can be used to measure
+ the distance of objects. Supported types are mb1202, mb1212,
+ mb1222, mb1232, mb1242, mb7040, mb7137
+
+ To compile this driver as a module, choose M here: the
+ module will be called mb1232.
+
+config PING
+ tristate "Parallax GPIO bitbanged ranger sensors"
+ depends on GPIOLIB
+ help
+ Say Y here to build a driver for GPIO bitbanged ranger sensors
+ with just one GPIO for the trigger and echo. This driver can be
+ used to measure the distance of objects.
+
+ Actually supported are:
+ - Parallax PING))) (ultrasonic)
+ - Parallax LaserPING (time-of-flight)
+
+ To compile this driver as a module, choose M here: the
+ module will be called ping.
+
+config RFD77402
+ tristate "RFD77402 ToF sensor"
+ depends on I2C
+ help
+ Say Y to build a driver for the RFD77402 Time-of-Flight (distance)
+ sensor module with I2C interface.
+
+ To compile this driver as a module, choose M here: the
+ module will be called rfd77402.
+
+config SRF04
+ tristate "GPIO bitbanged ultrasonic ranger sensor (SRF04, MB1000)"
+ depends on GPIOLIB
+ help
+ Say Y here to build a driver for GPIO bitbanged ultrasonic
+ ranger sensor. This driver can be used to measure the distance
+ of objects. It is using two GPIOs.
+ Actually Supported types are:
+ - Devantech SRF04
+ - Maxbotix mb1000
+ - Maxbotix mb1010
+ - Maxbotix mb1020
+ - Maxbotix mb1030
+ - Maxbotix mb1040
+
+ To compile this driver as a module, choose M here: the
+ module will be called srf04.
+
+config SX9310
+ tristate "SX9310/SX9311 Semtech proximity sensor"
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ select REGMAP_I2C
+ depends on I2C
+ help
+ Say Y here to build a driver for Semtech's SX9310/SX9311 capacitive
+ proximity/button sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sx9310.
+
+config SX9500
+ tristate "SX9500 Semtech proximity sensor"
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ select REGMAP_I2C
+ depends on I2C
+ help
+ Say Y here to build a driver for Semtech's SX9500 capacitive
+ proximity/button sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sx9500.
+
+config SRF08
+ tristate "Devantech SRF02/SRF08/SRF10 ultrasonic ranger sensor"
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ depends on I2C
+ help
+ Say Y here to build a driver for Devantech SRF02/SRF08/SRF10
+ ultrasonic ranger sensors with i2c interface.
+ This driver can be used to measure the distance of objects.
+
+ To compile this driver as a module, choose M here: the
+ module will be called srf08.
+
+config VCNL3020
+ tristate "VCNL3020 proximity sensor"
+ select REGMAP_I2C
+ depends on I2C
+ help
+ Say Y here if you want to build a driver for the Vishay VCNL3020
+ proximity sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called vcnl3020.
+
+config VL53L0X_I2C
+ tristate "STMicroelectronics VL53L0X ToF ranger sensor (I2C)"
+ depends on I2C
+ help
+ Say Y here to build a driver for STMicroelectronics VL53L0X
+ ToF ranger sensors with i2c interface.
+ This driver can be used to measure the distance of objects.
+
+ To compile this driver as a module, choose M here: the
+ module will be called vl53l0x-i2c.
+
+endmenu
diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile
new file mode 100644
index 000000000..9c1aca1a8
--- /dev/null
+++ b/drivers/iio/proximity/Makefile
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for IIO proximity sensors
+#
+
+# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_AS3935) += as3935.o
+obj-$(CONFIG_ISL29501) += isl29501.o
+obj-$(CONFIG_LIDAR_LITE_V2) += pulsedlight-lidar-lite-v2.o
+obj-$(CONFIG_MB1232) += mb1232.o
+obj-$(CONFIG_PING) += ping.o
+obj-$(CONFIG_RFD77402) += rfd77402.o
+obj-$(CONFIG_SRF04) += srf04.o
+obj-$(CONFIG_SRF08) += srf08.o
+obj-$(CONFIG_SX9310) += sx9310.o
+obj-$(CONFIG_SX9500) += sx9500.o
+obj-$(CONFIG_VCNL3020) += vcnl3020.o
+obj-$(CONFIG_VL53L0X_I2C) += vl53l0x-i2c.o
+
diff --git a/drivers/iio/proximity/as3935.c b/drivers/iio/proximity/as3935.c
new file mode 100644
index 000000000..98330e26a
--- /dev/null
+++ b/drivers/iio/proximity/as3935.c
@@ -0,0 +1,488 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * as3935.c - Support for AS3935 Franklin lightning sensor
+ *
+ * Copyright (C) 2014, 2017-2018
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ */
+
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/spi/spi.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define AS3935_AFE_GAIN 0x00
+#define AS3935_AFE_MASK 0x3F
+#define AS3935_AFE_GAIN_MAX 0x1F
+#define AS3935_AFE_PWR_BIT BIT(0)
+
+#define AS3935_NFLWDTH 0x01
+#define AS3935_NFLWDTH_MASK 0x7f
+
+#define AS3935_INT 0x03
+#define AS3935_INT_MASK 0x0f
+#define AS3935_DISTURB_INT BIT(2)
+#define AS3935_EVENT_INT BIT(3)
+#define AS3935_NOISE_INT BIT(0)
+
+#define AS3935_DATA 0x07
+#define AS3935_DATA_MASK 0x3F
+
+#define AS3935_TUNE_CAP 0x08
+#define AS3935_DEFAULTS 0x3C
+#define AS3935_CALIBRATE 0x3D
+
+#define AS3935_READ_DATA BIT(14)
+#define AS3935_ADDRESS(x) ((x) << 8)
+
+#define MAX_PF_CAP 120
+#define TUNE_CAP_DIV 8
+
+struct as3935_state {
+ struct spi_device *spi;
+ struct iio_trigger *trig;
+ struct mutex lock;
+ struct delayed_work work;
+
+ unsigned long noise_tripped;
+ u32 tune_cap;
+ u32 nflwdth_reg;
+ /* Ensure timestamp is naturally aligned */
+ struct {
+ u8 chan;
+ s64 timestamp __aligned(8);
+ } scan;
+ u8 buf[2] ____cacheline_aligned;
+};
+
+static const struct iio_chan_spec as3935_channels[] = {
+ {
+ .type = IIO_PROXIMITY,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_PROCESSED) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 6,
+ .storagebits = 8,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static int as3935_read(struct as3935_state *st, unsigned int reg, int *val)
+{
+ u8 cmd;
+ int ret;
+
+ cmd = (AS3935_READ_DATA | AS3935_ADDRESS(reg)) >> 8;
+ ret = spi_w8r8(st->spi, cmd);
+ if (ret < 0)
+ return ret;
+ *val = ret;
+
+ return 0;
+}
+
+static int as3935_write(struct as3935_state *st,
+ unsigned int reg,
+ unsigned int val)
+{
+ u8 *buf = st->buf;
+
+ buf[0] = AS3935_ADDRESS(reg) >> 8;
+ buf[1] = val;
+
+ return spi_write(st->spi, buf, 2);
+}
+
+static ssize_t as3935_sensor_sensitivity_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct as3935_state *st = iio_priv(dev_to_iio_dev(dev));
+ int val, ret;
+
+ ret = as3935_read(st, AS3935_AFE_GAIN, &val);
+ if (ret)
+ return ret;
+ val = (val & AS3935_AFE_MASK) >> 1;
+
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t as3935_sensor_sensitivity_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct as3935_state *st = iio_priv(dev_to_iio_dev(dev));
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul((const char *) buf, 10, &val);
+ if (ret)
+ return -EINVAL;
+
+ if (val > AS3935_AFE_GAIN_MAX)
+ return -EINVAL;
+
+ as3935_write(st, AS3935_AFE_GAIN, val << 1);
+
+ return len;
+}
+
+static ssize_t as3935_noise_level_tripped_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct as3935_state *st = iio_priv(dev_to_iio_dev(dev));
+ int ret;
+
+ mutex_lock(&st->lock);
+ ret = sprintf(buf, "%d\n", !time_after(jiffies, st->noise_tripped + HZ));
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static IIO_DEVICE_ATTR(sensor_sensitivity, S_IRUGO | S_IWUSR,
+ as3935_sensor_sensitivity_show, as3935_sensor_sensitivity_store, 0);
+
+static IIO_DEVICE_ATTR(noise_level_tripped, S_IRUGO,
+ as3935_noise_level_tripped_show, NULL, 0);
+
+static struct attribute *as3935_attributes[] = {
+ &iio_dev_attr_sensor_sensitivity.dev_attr.attr,
+ &iio_dev_attr_noise_level_tripped.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group as3935_attribute_group = {
+ .attrs = as3935_attributes,
+};
+
+static int as3935_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long m)
+{
+ struct as3935_state *st = iio_priv(indio_dev);
+ int ret;
+
+
+ switch (m) {
+ case IIO_CHAN_INFO_PROCESSED:
+ case IIO_CHAN_INFO_RAW:
+ *val2 = 0;
+ ret = as3935_read(st, AS3935_DATA, val);
+ if (ret)
+ return ret;
+
+ /* storm out of range */
+ if (*val == AS3935_DATA_MASK)
+ return -EINVAL;
+
+ if (m == IIO_CHAN_INFO_RAW)
+ return IIO_VAL_INT;
+
+ if (m == IIO_CHAN_INFO_PROCESSED)
+ *val *= 1000;
+ break;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 1000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return IIO_VAL_INT;
+}
+
+static const struct iio_info as3935_info = {
+ .attrs = &as3935_attribute_group,
+ .read_raw = &as3935_read_raw,
+};
+
+static irqreturn_t as3935_trigger_handler(int irq, void *private)
+{
+ struct iio_poll_func *pf = private;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct as3935_state *st = iio_priv(indio_dev);
+ int val, ret;
+
+ ret = as3935_read(st, AS3935_DATA, &val);
+ if (ret)
+ goto err_read;
+
+ st->scan.chan = val & AS3935_DATA_MASK;
+ iio_push_to_buffers_with_timestamp(indio_dev, &st->scan,
+ iio_get_time_ns(indio_dev));
+err_read:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static const struct iio_trigger_ops iio_interrupt_trigger_ops = {
+};
+
+static void as3935_event_work(struct work_struct *work)
+{
+ struct as3935_state *st;
+ int val;
+ int ret;
+
+ st = container_of(work, struct as3935_state, work.work);
+
+ ret = as3935_read(st, AS3935_INT, &val);
+ if (ret) {
+ dev_warn(&st->spi->dev, "read error\n");
+ return;
+ }
+
+ val &= AS3935_INT_MASK;
+
+ switch (val) {
+ case AS3935_EVENT_INT:
+ iio_trigger_poll_chained(st->trig);
+ break;
+ case AS3935_DISTURB_INT:
+ case AS3935_NOISE_INT:
+ mutex_lock(&st->lock);
+ st->noise_tripped = jiffies;
+ mutex_unlock(&st->lock);
+ dev_warn(&st->spi->dev, "noise level is too high\n");
+ break;
+ }
+}
+
+static irqreturn_t as3935_interrupt_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct as3935_state *st = iio_priv(indio_dev);
+
+ /*
+ * Delay work for >2 milliseconds after an interrupt to allow
+ * estimated distance to recalculated.
+ */
+
+ schedule_delayed_work(&st->work, msecs_to_jiffies(3));
+
+ return IRQ_HANDLED;
+}
+
+static void calibrate_as3935(struct as3935_state *st)
+{
+ as3935_write(st, AS3935_DEFAULTS, 0x96);
+ as3935_write(st, AS3935_CALIBRATE, 0x96);
+ as3935_write(st, AS3935_TUNE_CAP,
+ BIT(5) | (st->tune_cap / TUNE_CAP_DIV));
+
+ mdelay(2);
+ as3935_write(st, AS3935_TUNE_CAP, (st->tune_cap / TUNE_CAP_DIV));
+ as3935_write(st, AS3935_NFLWDTH, st->nflwdth_reg);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int as3935_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct as3935_state *st = iio_priv(indio_dev);
+ int val, ret;
+
+ mutex_lock(&st->lock);
+ ret = as3935_read(st, AS3935_AFE_GAIN, &val);
+ if (ret)
+ goto err_suspend;
+ val |= AS3935_AFE_PWR_BIT;
+
+ ret = as3935_write(st, AS3935_AFE_GAIN, val);
+
+err_suspend:
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static int as3935_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct as3935_state *st = iio_priv(indio_dev);
+ int val, ret;
+
+ mutex_lock(&st->lock);
+ ret = as3935_read(st, AS3935_AFE_GAIN, &val);
+ if (ret)
+ goto err_resume;
+ val &= ~AS3935_AFE_PWR_BIT;
+ ret = as3935_write(st, AS3935_AFE_GAIN, val);
+
+ calibrate_as3935(st);
+
+err_resume:
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(as3935_pm_ops, as3935_suspend, as3935_resume);
+#define AS3935_PM_OPS (&as3935_pm_ops)
+
+#else
+#define AS3935_PM_OPS NULL
+#endif
+
+static void as3935_stop_work(void *data)
+{
+ struct iio_dev *indio_dev = data;
+ struct as3935_state *st = iio_priv(indio_dev);
+
+ cancel_delayed_work_sync(&st->work);
+}
+
+static int as3935_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct iio_trigger *trig;
+ struct as3935_state *st;
+ int ret;
+
+ /* Be sure lightning event interrupt is specified */
+ if (!spi->irq) {
+ dev_err(dev, "unable to get event interrupt\n");
+ return -EINVAL;
+ }
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->spi = spi;
+
+ spi_set_drvdata(spi, indio_dev);
+ mutex_init(&st->lock);
+
+ ret = device_property_read_u32(dev,
+ "ams,tuning-capacitor-pf", &st->tune_cap);
+ if (ret) {
+ st->tune_cap = 0;
+ dev_warn(dev, "no tuning-capacitor-pf set, defaulting to %d",
+ st->tune_cap);
+ }
+
+ if (st->tune_cap > MAX_PF_CAP) {
+ dev_err(dev, "wrong tuning-capacitor-pf setting of %d\n",
+ st->tune_cap);
+ return -EINVAL;
+ }
+
+ ret = device_property_read_u32(dev,
+ "ams,nflwdth", &st->nflwdth_reg);
+ if (!ret && st->nflwdth_reg > AS3935_NFLWDTH_MASK) {
+ dev_err(dev, "invalid nflwdth setting of %d\n",
+ st->nflwdth_reg);
+ return -EINVAL;
+ }
+
+ indio_dev->name = spi_get_device_id(spi)->name;
+ indio_dev->channels = as3935_channels;
+ indio_dev->num_channels = ARRAY_SIZE(as3935_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &as3935_info;
+
+ trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
+ indio_dev->name, indio_dev->id);
+
+ if (!trig)
+ return -ENOMEM;
+
+ st->trig = trig;
+ st->noise_tripped = jiffies - HZ;
+ trig->dev.parent = indio_dev->dev.parent;
+ iio_trigger_set_drvdata(trig, indio_dev);
+ trig->ops = &iio_interrupt_trigger_ops;
+
+ ret = devm_iio_trigger_register(dev, trig);
+ if (ret) {
+ dev_err(dev, "failed to register trigger\n");
+ return ret;
+ }
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ as3935_trigger_handler, NULL);
+
+ if (ret) {
+ dev_err(dev, "cannot setup iio trigger\n");
+ return ret;
+ }
+
+ calibrate_as3935(st);
+
+ INIT_DELAYED_WORK(&st->work, as3935_event_work);
+ ret = devm_add_action(dev, as3935_stop_work, indio_dev);
+ if (ret)
+ return ret;
+
+ ret = devm_request_irq(dev, spi->irq,
+ &as3935_interrupt_handler,
+ IRQF_TRIGGER_RISING,
+ dev_name(dev),
+ indio_dev);
+
+ if (ret) {
+ dev_err(dev, "unable to request irq\n");
+ return ret;
+ }
+
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret < 0) {
+ dev_err(dev, "unable to register device\n");
+ return ret;
+ }
+ return 0;
+}
+
+static const struct of_device_id as3935_of_match[] = {
+ { .compatible = "ams,as3935", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, as3935_of_match);
+
+static const struct spi_device_id as3935_id[] = {
+ {"as3935", 0},
+ {},
+};
+MODULE_DEVICE_TABLE(spi, as3935_id);
+
+static struct spi_driver as3935_driver = {
+ .driver = {
+ .name = "as3935",
+ .of_match_table = as3935_of_match,
+ .pm = AS3935_PM_OPS,
+ },
+ .probe = as3935_probe,
+ .id_table = as3935_id,
+};
+module_spi_driver(as3935_driver);
+
+MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
+MODULE_DESCRIPTION("AS3935 lightning sensor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/proximity/isl29501.c b/drivers/iio/proximity/isl29501.c
new file mode 100644
index 000000000..5b6ea7837
--- /dev/null
+++ b/drivers/iio/proximity/isl29501.c
@@ -0,0 +1,1018 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * isl29501.c: ISL29501 Time of Flight sensor driver.
+ *
+ * Copyright (C) 2018
+ * Author: Mathieu Othacehe <m.othacehe@gmail.com>
+ *
+ * 7-bit I2C slave address: 0x57
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/of_device.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+
+/* Control, setting and status registers */
+#define ISL29501_DEVICE_ID 0x00
+#define ISL29501_ID 0x0A
+
+/* Sampling control registers */
+#define ISL29501_INTEGRATION_PERIOD 0x10
+#define ISL29501_SAMPLE_PERIOD 0x11
+
+/* Closed loop calibration registers */
+#define ISL29501_CROSSTALK_I_MSB 0x24
+#define ISL29501_CROSSTALK_I_LSB 0x25
+#define ISL29501_CROSSTALK_I_EXPONENT 0x26
+#define ISL29501_CROSSTALK_Q_MSB 0x27
+#define ISL29501_CROSSTALK_Q_LSB 0x28
+#define ISL29501_CROSSTALK_Q_EXPONENT 0x29
+#define ISL29501_CROSSTALK_GAIN_MSB 0x2A
+#define ISL29501_CROSSTALK_GAIN_LSB 0x2B
+#define ISL29501_MAGNITUDE_REF_EXP 0x2C
+#define ISL29501_MAGNITUDE_REF_MSB 0x2D
+#define ISL29501_MAGNITUDE_REF_LSB 0x2E
+#define ISL29501_PHASE_OFFSET_MSB 0x2F
+#define ISL29501_PHASE_OFFSET_LSB 0x30
+
+/* Analog control registers */
+#define ISL29501_DRIVER_RANGE 0x90
+#define ISL29501_EMITTER_DAC 0x91
+
+#define ISL29501_COMMAND_REGISTER 0xB0
+
+/* Commands */
+#define ISL29501_EMUL_SAMPLE_START_PIN 0x49
+#define ISL29501_RESET_ALL_REGISTERS 0xD7
+#define ISL29501_RESET_INT_SM 0xD1
+
+/* Ambiant light and temperature corrections */
+#define ISL29501_TEMP_REFERENCE 0x31
+#define ISL29501_PHASE_EXPONENT 0x33
+#define ISL29501_TEMP_COEFF_A 0x34
+#define ISL29501_TEMP_COEFF_B 0x39
+#define ISL29501_AMBIANT_COEFF_A 0x36
+#define ISL29501_AMBIANT_COEFF_B 0x3B
+
+/* Data output registers */
+#define ISL29501_DISTANCE_MSB_DATA 0xD1
+#define ISL29501_DISTANCE_LSB_DATA 0xD2
+#define ISL29501_PRECISION_MSB 0xD3
+#define ISL29501_PRECISION_LSB 0xD4
+#define ISL29501_MAGNITUDE_EXPONENT 0xD5
+#define ISL29501_MAGNITUDE_MSB 0xD6
+#define ISL29501_MAGNITUDE_LSB 0xD7
+#define ISL29501_PHASE_MSB 0xD8
+#define ISL29501_PHASE_LSB 0xD9
+#define ISL29501_I_RAW_EXPONENT 0xDA
+#define ISL29501_I_RAW_MSB 0xDB
+#define ISL29501_I_RAW_LSB 0xDC
+#define ISL29501_Q_RAW_EXPONENT 0xDD
+#define ISL29501_Q_RAW_MSB 0xDE
+#define ISL29501_Q_RAW_LSB 0xDF
+#define ISL29501_DIE_TEMPERATURE 0xE2
+#define ISL29501_AMBIENT_LIGHT 0xE3
+#define ISL29501_GAIN_MSB 0xE6
+#define ISL29501_GAIN_LSB 0xE7
+
+#define ISL29501_MAX_EXP_VAL 15
+
+#define ISL29501_INT_TIME_AVAILABLE \
+ "0.00007 0.00014 0.00028 0.00057 0.00114 " \
+ "0.00228 0.00455 0.00910 0.01820 0.03640 " \
+ "0.07281 0.14561"
+
+#define ISL29501_CURRENT_SCALE_AVAILABLE \
+ "0.0039 0.0078 0.0118 0.0157 0.0196 " \
+ "0.0235 0.0275 0.0314 0.0352 0.0392 " \
+ "0.0431 0.0471 0.0510 0.0549 0.0588"
+
+enum isl29501_correction_coeff {
+ COEFF_TEMP_A,
+ COEFF_TEMP_B,
+ COEFF_LIGHT_A,
+ COEFF_LIGHT_B,
+ COEFF_MAX,
+};
+
+struct isl29501_private {
+ struct i2c_client *client;
+ struct mutex lock;
+ /* Exact representation of correction coefficients. */
+ unsigned int shadow_coeffs[COEFF_MAX];
+};
+
+enum isl29501_register_name {
+ REG_DISTANCE,
+ REG_PHASE,
+ REG_TEMPERATURE,
+ REG_AMBIENT_LIGHT,
+ REG_GAIN,
+ REG_GAIN_BIAS,
+ REG_PHASE_EXP,
+ REG_CALIB_PHASE_TEMP_A,
+ REG_CALIB_PHASE_TEMP_B,
+ REG_CALIB_PHASE_LIGHT_A,
+ REG_CALIB_PHASE_LIGHT_B,
+ REG_DISTANCE_BIAS,
+ REG_TEMPERATURE_BIAS,
+ REG_INT_TIME,
+ REG_SAMPLE_TIME,
+ REG_DRIVER_RANGE,
+ REG_EMITTER_DAC,
+};
+
+struct isl29501_register_desc {
+ u8 msb;
+ u8 lsb;
+};
+
+static const struct isl29501_register_desc isl29501_registers[] = {
+ [REG_DISTANCE] = {
+ .msb = ISL29501_DISTANCE_MSB_DATA,
+ .lsb = ISL29501_DISTANCE_LSB_DATA,
+ },
+ [REG_PHASE] = {
+ .msb = ISL29501_PHASE_MSB,
+ .lsb = ISL29501_PHASE_LSB,
+ },
+ [REG_TEMPERATURE] = {
+ .lsb = ISL29501_DIE_TEMPERATURE,
+ },
+ [REG_AMBIENT_LIGHT] = {
+ .lsb = ISL29501_AMBIENT_LIGHT,
+ },
+ [REG_GAIN] = {
+ .msb = ISL29501_GAIN_MSB,
+ .lsb = ISL29501_GAIN_LSB,
+ },
+ [REG_GAIN_BIAS] = {
+ .msb = ISL29501_CROSSTALK_GAIN_MSB,
+ .lsb = ISL29501_CROSSTALK_GAIN_LSB,
+ },
+ [REG_PHASE_EXP] = {
+ .lsb = ISL29501_PHASE_EXPONENT,
+ },
+ [REG_CALIB_PHASE_TEMP_A] = {
+ .lsb = ISL29501_TEMP_COEFF_A,
+ },
+ [REG_CALIB_PHASE_TEMP_B] = {
+ .lsb = ISL29501_TEMP_COEFF_B,
+ },
+ [REG_CALIB_PHASE_LIGHT_A] = {
+ .lsb = ISL29501_AMBIANT_COEFF_A,
+ },
+ [REG_CALIB_PHASE_LIGHT_B] = {
+ .lsb = ISL29501_AMBIANT_COEFF_B,
+ },
+ [REG_DISTANCE_BIAS] = {
+ .msb = ISL29501_PHASE_OFFSET_MSB,
+ .lsb = ISL29501_PHASE_OFFSET_LSB,
+ },
+ [REG_TEMPERATURE_BIAS] = {
+ .lsb = ISL29501_TEMP_REFERENCE,
+ },
+ [REG_INT_TIME] = {
+ .lsb = ISL29501_INTEGRATION_PERIOD,
+ },
+ [REG_SAMPLE_TIME] = {
+ .lsb = ISL29501_SAMPLE_PERIOD,
+ },
+ [REG_DRIVER_RANGE] = {
+ .lsb = ISL29501_DRIVER_RANGE,
+ },
+ [REG_EMITTER_DAC] = {
+ .lsb = ISL29501_EMITTER_DAC,
+ },
+};
+
+static int isl29501_register_read(struct isl29501_private *isl29501,
+ enum isl29501_register_name name,
+ u32 *val)
+{
+ const struct isl29501_register_desc *reg = &isl29501_registers[name];
+ u8 msb = 0, lsb = 0;
+ s32 ret;
+
+ mutex_lock(&isl29501->lock);
+ if (reg->msb) {
+ ret = i2c_smbus_read_byte_data(isl29501->client, reg->msb);
+ if (ret < 0)
+ goto err;
+ msb = ret;
+ }
+
+ if (reg->lsb) {
+ ret = i2c_smbus_read_byte_data(isl29501->client, reg->lsb);
+ if (ret < 0)
+ goto err;
+ lsb = ret;
+ }
+ mutex_unlock(&isl29501->lock);
+
+ *val = (msb << 8) + lsb;
+
+ return 0;
+err:
+ mutex_unlock(&isl29501->lock);
+
+ return ret;
+}
+
+static u32 isl29501_register_write(struct isl29501_private *isl29501,
+ enum isl29501_register_name name,
+ u32 value)
+{
+ const struct isl29501_register_desc *reg = &isl29501_registers[name];
+ int ret;
+
+ if (!reg->msb && value > U8_MAX)
+ return -ERANGE;
+
+ if (value > U16_MAX)
+ return -ERANGE;
+
+ mutex_lock(&isl29501->lock);
+ if (reg->msb) {
+ ret = i2c_smbus_write_byte_data(isl29501->client,
+ reg->msb, value >> 8);
+ if (ret < 0)
+ goto err;
+ }
+
+ ret = i2c_smbus_write_byte_data(isl29501->client, reg->lsb, value);
+
+err:
+ mutex_unlock(&isl29501->lock);
+ return ret;
+}
+
+static ssize_t isl29501_read_ext(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct isl29501_private *isl29501 = iio_priv(indio_dev);
+ enum isl29501_register_name reg = private;
+ int ret;
+ u32 value, gain, coeff, exp;
+
+ switch (reg) {
+ case REG_GAIN:
+ case REG_GAIN_BIAS:
+ ret = isl29501_register_read(isl29501, reg, &gain);
+ if (ret < 0)
+ return ret;
+
+ value = gain;
+ break;
+ case REG_CALIB_PHASE_TEMP_A:
+ case REG_CALIB_PHASE_TEMP_B:
+ case REG_CALIB_PHASE_LIGHT_A:
+ case REG_CALIB_PHASE_LIGHT_B:
+ ret = isl29501_register_read(isl29501, REG_PHASE_EXP, &exp);
+ if (ret < 0)
+ return ret;
+
+ ret = isl29501_register_read(isl29501, reg, &coeff);
+ if (ret < 0)
+ return ret;
+
+ value = coeff << exp;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return sprintf(buf, "%u\n", value);
+}
+
+static int isl29501_set_shadow_coeff(struct isl29501_private *isl29501,
+ enum isl29501_register_name reg,
+ unsigned int val)
+{
+ enum isl29501_correction_coeff coeff;
+
+ switch (reg) {
+ case REG_CALIB_PHASE_TEMP_A:
+ coeff = COEFF_TEMP_A;
+ break;
+ case REG_CALIB_PHASE_TEMP_B:
+ coeff = COEFF_TEMP_B;
+ break;
+ case REG_CALIB_PHASE_LIGHT_A:
+ coeff = COEFF_LIGHT_A;
+ break;
+ case REG_CALIB_PHASE_LIGHT_B:
+ coeff = COEFF_LIGHT_B;
+ break;
+ default:
+ return -EINVAL;
+ }
+ isl29501->shadow_coeffs[coeff] = val;
+
+ return 0;
+}
+
+static int isl29501_write_coeff(struct isl29501_private *isl29501,
+ enum isl29501_correction_coeff coeff,
+ int val)
+{
+ enum isl29501_register_name reg;
+
+ switch (coeff) {
+ case COEFF_TEMP_A:
+ reg = REG_CALIB_PHASE_TEMP_A;
+ break;
+ case COEFF_TEMP_B:
+ reg = REG_CALIB_PHASE_TEMP_B;
+ break;
+ case COEFF_LIGHT_A:
+ reg = REG_CALIB_PHASE_LIGHT_A;
+ break;
+ case COEFF_LIGHT_B:
+ reg = REG_CALIB_PHASE_LIGHT_B;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return isl29501_register_write(isl29501, reg, val);
+}
+
+static unsigned int isl29501_find_corr_exp(unsigned int val,
+ unsigned int max_exp,
+ unsigned int max_mantissa)
+{
+ unsigned int exp = 1;
+
+ /*
+ * Correction coefficients are represented under
+ * mantissa * 2^exponent form, where mantissa and exponent
+ * are stored in two separate registers of the sensor.
+ *
+ * Compute and return the lowest exponent such as:
+ * mantissa = value / 2^exponent
+ *
+ * where mantissa < max_mantissa.
+ */
+ if (val <= max_mantissa)
+ return 0;
+
+ while ((val >> exp) > max_mantissa) {
+ exp++;
+
+ if (exp > max_exp)
+ return max_exp;
+ }
+
+ return exp;
+}
+
+static ssize_t isl29501_write_ext(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct isl29501_private *isl29501 = iio_priv(indio_dev);
+ enum isl29501_register_name reg = private;
+ unsigned int val;
+ int max_exp = 0;
+ int ret;
+ int i;
+
+ ret = kstrtouint(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ switch (reg) {
+ case REG_GAIN_BIAS:
+ if (val > U16_MAX)
+ return -ERANGE;
+
+ ret = isl29501_register_write(isl29501, reg, val);
+ if (ret < 0)
+ return ret;
+
+ break;
+ case REG_CALIB_PHASE_TEMP_A:
+ case REG_CALIB_PHASE_TEMP_B:
+ case REG_CALIB_PHASE_LIGHT_A:
+ case REG_CALIB_PHASE_LIGHT_B:
+
+ if (val > (U8_MAX << ISL29501_MAX_EXP_VAL))
+ return -ERANGE;
+
+ /* Store the correction coefficient under its exact form. */
+ ret = isl29501_set_shadow_coeff(isl29501, reg, val);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Find the highest exponent needed to represent
+ * correction coefficients.
+ */
+ for (i = 0; i < COEFF_MAX; i++) {
+ int corr;
+ int corr_exp;
+
+ corr = isl29501->shadow_coeffs[i];
+ corr_exp = isl29501_find_corr_exp(corr,
+ ISL29501_MAX_EXP_VAL,
+ U8_MAX / 2);
+ dev_dbg(&isl29501->client->dev,
+ "found exp of corr(%d) = %d\n", corr, corr_exp);
+
+ max_exp = max(max_exp, corr_exp);
+ }
+
+ /*
+ * Represent every correction coefficient under
+ * mantissa * 2^max_exponent form and force the
+ * writing of those coefficients on the sensor.
+ */
+ for (i = 0; i < COEFF_MAX; i++) {
+ int corr;
+ int mantissa;
+
+ corr = isl29501->shadow_coeffs[i];
+ if (!corr)
+ continue;
+
+ mantissa = corr >> max_exp;
+
+ ret = isl29501_write_coeff(isl29501, i, mantissa);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = isl29501_register_write(isl29501, REG_PHASE_EXP, max_exp);
+ if (ret < 0)
+ return ret;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return len;
+}
+
+#define _ISL29501_EXT_INFO(_name, _ident) { \
+ .name = _name, \
+ .read = isl29501_read_ext, \
+ .write = isl29501_write_ext, \
+ .private = _ident, \
+ .shared = IIO_SEPARATE, \
+}
+
+static const struct iio_chan_spec_ext_info isl29501_ext_info[] = {
+ _ISL29501_EXT_INFO("agc_gain", REG_GAIN),
+ _ISL29501_EXT_INFO("agc_gain_bias", REG_GAIN_BIAS),
+ _ISL29501_EXT_INFO("calib_phase_temp_a", REG_CALIB_PHASE_TEMP_A),
+ _ISL29501_EXT_INFO("calib_phase_temp_b", REG_CALIB_PHASE_TEMP_B),
+ _ISL29501_EXT_INFO("calib_phase_light_a", REG_CALIB_PHASE_LIGHT_A),
+ _ISL29501_EXT_INFO("calib_phase_light_b", REG_CALIB_PHASE_LIGHT_B),
+ { },
+};
+
+#define ISL29501_DISTANCE_SCAN_INDEX 0
+#define ISL29501_TIMESTAMP_SCAN_INDEX 1
+
+static const struct iio_chan_spec isl29501_channels[] = {
+ {
+ .type = IIO_PROXIMITY,
+ .scan_index = ISL29501_DISTANCE_SCAN_INDEX,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_CALIBBIAS),
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_CPU,
+ },
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = isl29501_ext_info,
+ },
+ {
+ .type = IIO_PHASE,
+ .scan_index = -1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+ {
+ .type = IIO_CURRENT,
+ .scan_index = -1,
+ .output = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+ {
+ .type = IIO_TEMP,
+ .scan_index = -1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_CALIBBIAS),
+ },
+ {
+ .type = IIO_INTENSITY,
+ .scan_index = -1,
+ .modified = 1,
+ .channel2 = IIO_MOD_LIGHT_CLEAR,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(ISL29501_TIMESTAMP_SCAN_INDEX),
+};
+
+static int isl29501_reset_registers(struct isl29501_private *isl29501)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(isl29501->client,
+ ISL29501_COMMAND_REGISTER,
+ ISL29501_RESET_ALL_REGISTERS);
+ if (ret < 0) {
+ dev_err(&isl29501->client->dev,
+ "cannot reset registers %d\n", ret);
+ return ret;
+ }
+
+ ret = i2c_smbus_write_byte_data(isl29501->client,
+ ISL29501_COMMAND_REGISTER,
+ ISL29501_RESET_INT_SM);
+ if (ret < 0)
+ dev_err(&isl29501->client->dev,
+ "cannot reset state machine %d\n", ret);
+
+ return ret;
+}
+
+static int isl29501_begin_acquisition(struct isl29501_private *isl29501)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(isl29501->client,
+ ISL29501_COMMAND_REGISTER,
+ ISL29501_EMUL_SAMPLE_START_PIN);
+ if (ret < 0)
+ dev_err(&isl29501->client->dev,
+ "cannot begin acquisition %d\n", ret);
+
+ return ret;
+}
+
+static IIO_CONST_ATTR_INT_TIME_AVAIL(ISL29501_INT_TIME_AVAILABLE);
+static IIO_CONST_ATTR(out_current_scale_available,
+ ISL29501_CURRENT_SCALE_AVAILABLE);
+
+static struct attribute *isl29501_attributes[] = {
+ &iio_const_attr_integration_time_available.dev_attr.attr,
+ &iio_const_attr_out_current_scale_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group isl29501_attribute_group = {
+ .attrs = isl29501_attributes,
+};
+
+static const int isl29501_current_scale_table[][2] = {
+ {0, 3900}, {0, 7800}, {0, 11800}, {0, 15700},
+ {0, 19600}, {0, 23500}, {0, 27500}, {0, 31400},
+ {0, 35200}, {0, 39200}, {0, 43100}, {0, 47100},
+ {0, 51000}, {0, 54900}, {0, 58800},
+};
+
+static const int isl29501_int_time[][2] = {
+ {0, 70}, /* 0.07 ms */
+ {0, 140}, /* 0.14 ms */
+ {0, 280}, /* 0.28 ms */
+ {0, 570}, /* 0.57 ms */
+ {0, 1140}, /* 1.14 ms */
+ {0, 2280}, /* 2.28 ms */
+ {0, 4550}, /* 4.55 ms */
+ {0, 9100}, /* 9.11 ms */
+ {0, 18200}, /* 18.2 ms */
+ {0, 36400}, /* 36.4 ms */
+ {0, 72810}, /* 72.81 ms */
+ {0, 145610} /* 145.28 ms */
+};
+
+static int isl29501_get_raw(struct isl29501_private *isl29501,
+ const struct iio_chan_spec *chan,
+ int *raw)
+{
+ int ret;
+
+ switch (chan->type) {
+ case IIO_PROXIMITY:
+ ret = isl29501_register_read(isl29501, REG_DISTANCE, raw);
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+ case IIO_INTENSITY:
+ ret = isl29501_register_read(isl29501,
+ REG_AMBIENT_LIGHT,
+ raw);
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+ case IIO_PHASE:
+ ret = isl29501_register_read(isl29501, REG_PHASE, raw);
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+ case IIO_CURRENT:
+ ret = isl29501_register_read(isl29501, REG_EMITTER_DAC, raw);
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+ case IIO_TEMP:
+ ret = isl29501_register_read(isl29501, REG_TEMPERATURE, raw);
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int isl29501_get_scale(struct isl29501_private *isl29501,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2)
+{
+ int ret;
+ u32 current_scale;
+
+ switch (chan->type) {
+ case IIO_PROXIMITY:
+ /* distance = raw_distance * 33.31 / 65536 (m) */
+ *val = 3331;
+ *val2 = 6553600;
+
+ return IIO_VAL_FRACTIONAL;
+ case IIO_PHASE:
+ /* phase = raw_phase * 2pi / 65536 (rad) */
+ *val = 0;
+ *val2 = 95874;
+
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_INTENSITY:
+ /* light = raw_light * 35 / 10000 (mA) */
+ *val = 35;
+ *val2 = 10000;
+
+ return IIO_VAL_FRACTIONAL;
+ case IIO_CURRENT:
+ ret = isl29501_register_read(isl29501,
+ REG_DRIVER_RANGE,
+ &current_scale);
+ if (ret < 0)
+ return ret;
+
+ if (current_scale > ARRAY_SIZE(isl29501_current_scale_table))
+ return -EINVAL;
+
+ if (!current_scale) {
+ *val = 0;
+ *val2 = 0;
+ return IIO_VAL_INT;
+ }
+
+ *val = isl29501_current_scale_table[current_scale - 1][0];
+ *val2 = isl29501_current_scale_table[current_scale - 1][1];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_TEMP:
+ /* temperature = raw_temperature * 125 / 100000 (milli °C) */
+ *val = 125;
+ *val2 = 100000;
+
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int isl29501_get_calibbias(struct isl29501_private *isl29501,
+ const struct iio_chan_spec *chan,
+ int *bias)
+{
+ switch (chan->type) {
+ case IIO_PROXIMITY:
+ return isl29501_register_read(isl29501,
+ REG_DISTANCE_BIAS,
+ bias);
+ case IIO_TEMP:
+ return isl29501_register_read(isl29501,
+ REG_TEMPERATURE_BIAS,
+ bias);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int isl29501_get_inttime(struct isl29501_private *isl29501,
+ int *val, int *val2)
+{
+ int ret;
+ u32 inttime;
+
+ ret = isl29501_register_read(isl29501, REG_INT_TIME, &inttime);
+ if (ret < 0)
+ return ret;
+
+ if (inttime >= ARRAY_SIZE(isl29501_int_time))
+ return -EINVAL;
+
+ *val = isl29501_int_time[inttime][0];
+ *val2 = isl29501_int_time[inttime][1];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int isl29501_get_freq(struct isl29501_private *isl29501,
+ int *val, int *val2)
+{
+ int ret;
+ int sample_time;
+ unsigned long long freq;
+ u32 temp;
+
+ ret = isl29501_register_read(isl29501, REG_SAMPLE_TIME, &sample_time);
+ if (ret < 0)
+ return ret;
+
+ /* freq = 1 / (0.000450 * (sample_time + 1) * 10^-6) */
+ freq = 1000000ULL * 1000000ULL;
+
+ do_div(freq, 450 * (sample_time + 1));
+
+ temp = do_div(freq, 1000000);
+ *val = freq;
+ *val2 = temp;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int isl29501_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct isl29501_private *isl29501 = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return isl29501_get_raw(isl29501, chan, val);
+ case IIO_CHAN_INFO_SCALE:
+ return isl29501_get_scale(isl29501, chan, val, val2);
+ case IIO_CHAN_INFO_INT_TIME:
+ return isl29501_get_inttime(isl29501, val, val2);
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return isl29501_get_freq(isl29501, val, val2);
+ case IIO_CHAN_INFO_CALIBBIAS:
+ return isl29501_get_calibbias(isl29501, chan, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int isl29501_set_raw(struct isl29501_private *isl29501,
+ const struct iio_chan_spec *chan,
+ int raw)
+{
+ switch (chan->type) {
+ case IIO_CURRENT:
+ return isl29501_register_write(isl29501, REG_EMITTER_DAC, raw);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int isl29501_set_inttime(struct isl29501_private *isl29501,
+ int val, int val2)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(isl29501_int_time); i++) {
+ if (isl29501_int_time[i][0] == val &&
+ isl29501_int_time[i][1] == val2) {
+ return isl29501_register_write(isl29501,
+ REG_INT_TIME,
+ i);
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int isl29501_set_scale(struct isl29501_private *isl29501,
+ const struct iio_chan_spec *chan,
+ int val, int val2)
+{
+ int i;
+
+ if (chan->type != IIO_CURRENT)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(isl29501_current_scale_table); i++) {
+ if (isl29501_current_scale_table[i][0] == val &&
+ isl29501_current_scale_table[i][1] == val2) {
+ return isl29501_register_write(isl29501,
+ REG_DRIVER_RANGE,
+ i + 1);
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int isl29501_set_calibbias(struct isl29501_private *isl29501,
+ const struct iio_chan_spec *chan,
+ int bias)
+{
+ switch (chan->type) {
+ case IIO_PROXIMITY:
+ return isl29501_register_write(isl29501,
+ REG_DISTANCE_BIAS,
+ bias);
+ case IIO_TEMP:
+ return isl29501_register_write(isl29501,
+ REG_TEMPERATURE_BIAS,
+ bias);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int isl29501_set_freq(struct isl29501_private *isl29501,
+ int val, int val2)
+{
+ int freq;
+ unsigned long long sample_time;
+
+ /* sample_freq = 1 / (0.000450 * (sample_time + 1) * 10^-6) */
+ freq = val * 1000000 + val2 % 1000000;
+ sample_time = 2222ULL * 1000000ULL;
+ do_div(sample_time, freq);
+
+ sample_time -= 1;
+
+ if (sample_time > 255)
+ return -ERANGE;
+
+ return isl29501_register_write(isl29501, REG_SAMPLE_TIME, sample_time);
+}
+
+static int isl29501_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct isl29501_private *isl29501 = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return isl29501_set_raw(isl29501, chan, val);
+ case IIO_CHAN_INFO_INT_TIME:
+ return isl29501_set_inttime(isl29501, val, val2);
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return isl29501_set_freq(isl29501, val, val2);
+ case IIO_CHAN_INFO_SCALE:
+ return isl29501_set_scale(isl29501, chan, val, val2);
+ case IIO_CHAN_INFO_CALIBBIAS:
+ return isl29501_set_calibbias(isl29501, chan, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info isl29501_info = {
+ .read_raw = &isl29501_read_raw,
+ .write_raw = &isl29501_write_raw,
+ .attrs = &isl29501_attribute_group,
+};
+
+static int isl29501_init_chip(struct isl29501_private *isl29501)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(isl29501->client, ISL29501_DEVICE_ID);
+ if (ret < 0) {
+ dev_err(&isl29501->client->dev, "Error reading device id\n");
+ return ret;
+ }
+
+ if (ret != ISL29501_ID) {
+ dev_err(&isl29501->client->dev,
+ "Wrong chip id, got %x expected %x\n",
+ ret, ISL29501_DEVICE_ID);
+ return -ENODEV;
+ }
+
+ ret = isl29501_reset_registers(isl29501);
+ if (ret < 0)
+ return ret;
+
+ return isl29501_begin_acquisition(isl29501);
+}
+
+static irqreturn_t isl29501_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct isl29501_private *isl29501 = iio_priv(indio_dev);
+ const unsigned long *active_mask = indio_dev->active_scan_mask;
+ u32 buffer[4] __aligned(8) = {}; /* 1x16-bit + naturally aligned ts */
+
+ if (test_bit(ISL29501_DISTANCE_SCAN_INDEX, active_mask))
+ isl29501_register_read(isl29501, REG_DISTANCE, buffer);
+
+ iio_push_to_buffers_with_timestamp(indio_dev, buffer, pf->timestamp);
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int isl29501_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct iio_dev *indio_dev;
+ struct isl29501_private *isl29501;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*isl29501));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ isl29501 = iio_priv(indio_dev);
+
+ i2c_set_clientdata(client, indio_dev);
+ isl29501->client = client;
+
+ mutex_init(&isl29501->lock);
+
+ ret = isl29501_init_chip(isl29501);
+ if (ret < 0)
+ return ret;
+
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = isl29501_channels;
+ indio_dev->num_channels = ARRAY_SIZE(isl29501_channels);
+ indio_dev->name = client->name;
+ indio_dev->info = &isl29501_info;
+
+ ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev,
+ iio_pollfunc_store_time,
+ isl29501_trigger_handler,
+ NULL);
+ if (ret < 0) {
+ dev_err(&client->dev, "unable to setup iio triggered buffer\n");
+ return ret;
+ }
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id isl29501_id[] = {
+ {"isl29501", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, isl29501_id);
+
+#if defined(CONFIG_OF)
+static const struct of_device_id isl29501_i2c_matches[] = {
+ { .compatible = "renesas,isl29501" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, isl29501_i2c_matches);
+#endif
+
+static struct i2c_driver isl29501_driver = {
+ .driver = {
+ .name = "isl29501",
+ },
+ .id_table = isl29501_id,
+ .probe = isl29501_probe,
+};
+module_i2c_driver(isl29501_driver);
+
+MODULE_AUTHOR("Mathieu Othacehe <m.othacehe@gmail.com>");
+MODULE_DESCRIPTION("ISL29501 Time of Flight sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/proximity/mb1232.c b/drivers/iio/proximity/mb1232.c
new file mode 100644
index 000000000..ad4b1fb26
--- /dev/null
+++ b/drivers/iio/proximity/mb1232.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * mb1232.c - Support for MaxBotix I2CXL-MaxSonar-EZ series ultrasonic
+ * ranger with i2c interface
+ * actually tested with mb1232 type
+ *
+ * Copyright (c) 2019 Andreas Klinger <ak@it-klinger.de>
+ *
+ * For details about the device see:
+ * https://www.maxbotix.com/documents/I2CXL-MaxSonar-EZ_Datasheet.pdf
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/of_irq.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+/* registers of MaxSonar device */
+#define MB1232_RANGE_COMMAND 0x51 /* Command for reading range */
+#define MB1232_ADDR_UNLOCK_1 0xAA /* Command 1 for changing address */
+#define MB1232_ADDR_UNLOCK_2 0xA5 /* Command 2 for changing address */
+
+struct mb1232_data {
+ struct i2c_client *client;
+
+ struct mutex lock;
+
+ /*
+ * optionally a gpio can be used to announce when ranging has
+ * finished
+ * since we are just using the falling trigger of it we request
+ * only the interrupt for announcing when data is ready to be read
+ */
+ struct completion ranging;
+ int irqnr;
+ /* Ensure correct alignment of data to push to IIO buffer */
+ struct {
+ s16 distance;
+ s64 ts __aligned(8);
+ } scan;
+};
+
+static irqreturn_t mb1232_handle_irq(int irq, void *dev_id)
+{
+ struct iio_dev *indio_dev = dev_id;
+ struct mb1232_data *data = iio_priv(indio_dev);
+
+ complete(&data->ranging);
+
+ return IRQ_HANDLED;
+}
+
+static s16 mb1232_read_distance(struct mb1232_data *data)
+{
+ struct i2c_client *client = data->client;
+ int ret;
+ s16 distance;
+ __be16 buf;
+
+ mutex_lock(&data->lock);
+
+ reinit_completion(&data->ranging);
+
+ ret = i2c_smbus_write_byte(client, MB1232_RANGE_COMMAND);
+ if (ret < 0) {
+ dev_err(&client->dev, "write command - err: %d\n", ret);
+ goto error_unlock;
+ }
+
+ if (data->irqnr >= 0) {
+ /* it cannot take more than 100 ms */
+ ret = wait_for_completion_killable_timeout(&data->ranging,
+ HZ/10);
+ if (ret < 0)
+ goto error_unlock;
+ else if (ret == 0) {
+ ret = -ETIMEDOUT;
+ goto error_unlock;
+ }
+ } else {
+ /* use simple sleep if announce irq is not connected */
+ msleep(15);
+ }
+
+ ret = i2c_master_recv(client, (char *)&buf, sizeof(buf));
+ if (ret < 0) {
+ dev_err(&client->dev, "i2c_master_recv: ret=%d\n", ret);
+ goto error_unlock;
+ }
+
+ distance = __be16_to_cpu(buf);
+ /* check for not returning misleading error codes */
+ if (distance < 0) {
+ dev_err(&client->dev, "distance=%d\n", distance);
+ ret = -EINVAL;
+ goto error_unlock;
+ }
+
+ mutex_unlock(&data->lock);
+
+ return distance;
+
+error_unlock:
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static irqreturn_t mb1232_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct mb1232_data *data = iio_priv(indio_dev);
+
+ data->scan.distance = mb1232_read_distance(data);
+ if (data->scan.distance < 0)
+ goto err;
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
+ pf->timestamp);
+
+err:
+ iio_trigger_notify_done(indio_dev->trig);
+ return IRQ_HANDLED;
+}
+
+static int mb1232_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int *val,
+ int *val2, long mask)
+{
+ struct mb1232_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (channel->type != IIO_DISTANCE)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = mb1232_read_distance(data);
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ /* 1 LSB is 1 cm */
+ *val = 0;
+ *val2 = 10000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_chan_spec mb1232_channels[] = {
+ {
+ .type = IIO_DISTANCE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_CPU,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct iio_info mb1232_info = {
+ .read_raw = mb1232_read_raw,
+};
+
+static int mb1232_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct iio_dev *indio_dev;
+ struct mb1232_data *data;
+ int ret;
+ struct device *dev = &client->dev;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_BYTE |
+ I2C_FUNC_SMBUS_WRITE_BYTE))
+ return -ENODEV;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+
+ indio_dev->info = &mb1232_info;
+ indio_dev->name = id->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = mb1232_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mb1232_channels);
+
+ mutex_init(&data->lock);
+
+ init_completion(&data->ranging);
+
+ data->irqnr = irq_of_parse_and_map(dev->of_node, 0);
+ if (data->irqnr <= 0) {
+ /* usage of interrupt is optional */
+ data->irqnr = -1;
+ } else {
+ ret = devm_request_irq(dev, data->irqnr, mb1232_handle_irq,
+ IRQF_TRIGGER_FALLING, id->name, indio_dev);
+ if (ret < 0) {
+ dev_err(dev, "request_irq: %d\n", ret);
+ return ret;
+ }
+ }
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time, mb1232_trigger_handler, NULL);
+ if (ret < 0) {
+ dev_err(dev, "setup of iio triggered buffer failed\n");
+ return ret;
+ }
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id of_mb1232_match[] = {
+ { .compatible = "maxbotix,mb1202", },
+ { .compatible = "maxbotix,mb1212", },
+ { .compatible = "maxbotix,mb1222", },
+ { .compatible = "maxbotix,mb1232", },
+ { .compatible = "maxbotix,mb1242", },
+ { .compatible = "maxbotix,mb7040", },
+ { .compatible = "maxbotix,mb7137", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, of_mb1232_match);
+
+static const struct i2c_device_id mb1232_id[] = {
+ { "maxbotix-mb1202", },
+ { "maxbotix-mb1212", },
+ { "maxbotix-mb1222", },
+ { "maxbotix-mb1232", },
+ { "maxbotix-mb1242", },
+ { "maxbotix-mb7040", },
+ { "maxbotix-mb7137", },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mb1232_id);
+
+static struct i2c_driver mb1232_driver = {
+ .driver = {
+ .name = "maxbotix-mb1232",
+ .of_match_table = of_mb1232_match,
+ },
+ .probe = mb1232_probe,
+ .id_table = mb1232_id,
+};
+module_i2c_driver(mb1232_driver);
+
+MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>");
+MODULE_DESCRIPTION("Maxbotix I2CXL-MaxSonar i2c ultrasonic ranger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/proximity/ping.c b/drivers/iio/proximity/ping.c
new file mode 100644
index 000000000..1283ac1c2
--- /dev/null
+++ b/drivers/iio/proximity/ping.c
@@ -0,0 +1,333 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * PING: ultrasonic sensor for distance measuring by using only one GPIOs
+ *
+ * Copyright (c) 2019 Andreas Klinger <ak@it-klinger.de>
+ *
+ * For details about the devices see:
+ * http://parallax.com/sites/default/files/downloads/28041-LaserPING-2m-Rangefinder-Guide.pdf
+ * http://parallax.com/sites/default/files/downloads/28015-PING-Documentation-v1.6.pdf
+ *
+ * the measurement cycle as timing diagram looks like:
+ *
+ * GPIO ___ ________________________
+ * ping: __/ \____________/ \________________
+ * ^ ^ ^ ^
+ * |<->| interrupt interrupt
+ * udelay(5) (ts_rising) (ts_falling)
+ * |<---------------------->|
+ * . pulse time measured .
+ * . --> one round trip of ultra sonic waves
+ * ultra . .
+ * sonic _ _ _. .
+ * burst: _________/ \_/ \_/ \_________________________________________
+ * .
+ * ultra .
+ * sonic _ _ _.
+ * echo: __________________________________/ \_/ \_/ \________________
+ */
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+struct ping_cfg {
+ unsigned long trigger_pulse_us; /* length of trigger pulse */
+ int laserping_error; /* support error code in */
+ /* pulse width of laser */
+ /* ping sensors */
+ s64 timeout_ns; /* timeout in ns */
+};
+
+struct ping_data {
+ struct device *dev;
+ struct gpio_desc *gpiod_ping;
+ struct mutex lock;
+ int irqnr;
+ ktime_t ts_rising;
+ ktime_t ts_falling;
+ struct completion rising;
+ struct completion falling;
+ const struct ping_cfg *cfg;
+};
+
+static const struct ping_cfg pa_ping_cfg = {
+ .trigger_pulse_us = 5,
+ .laserping_error = 0,
+ .timeout_ns = 18500000, /* 3 meters */
+};
+
+static const struct ping_cfg pa_laser_ping_cfg = {
+ .trigger_pulse_us = 5,
+ .laserping_error = 1,
+ .timeout_ns = 15500000, /* 2 meters plus error codes */
+};
+
+static irqreturn_t ping_handle_irq(int irq, void *dev_id)
+{
+ struct iio_dev *indio_dev = dev_id;
+ struct ping_data *data = iio_priv(indio_dev);
+ ktime_t now = ktime_get();
+
+ if (gpiod_get_value(data->gpiod_ping)) {
+ data->ts_rising = now;
+ complete(&data->rising);
+ } else {
+ data->ts_falling = now;
+ complete(&data->falling);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int ping_read(struct iio_dev *indio_dev)
+{
+ struct ping_data *data = iio_priv(indio_dev);
+ int ret;
+ ktime_t ktime_dt;
+ s64 dt_ns;
+ u32 time_ns, distance_mm;
+ struct platform_device *pdev = to_platform_device(data->dev);
+
+ /*
+ * just one read-echo-cycle can take place at a time
+ * ==> lock against concurrent reading calls
+ */
+ mutex_lock(&data->lock);
+
+ reinit_completion(&data->rising);
+ reinit_completion(&data->falling);
+
+ gpiod_set_value(data->gpiod_ping, 1);
+ udelay(data->cfg->trigger_pulse_us);
+ gpiod_set_value(data->gpiod_ping, 0);
+
+ ret = gpiod_direction_input(data->gpiod_ping);
+ if (ret < 0) {
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+
+ data->irqnr = gpiod_to_irq(data->gpiod_ping);
+ if (data->irqnr < 0) {
+ dev_err(data->dev, "gpiod_to_irq: %d\n", data->irqnr);
+ mutex_unlock(&data->lock);
+ return data->irqnr;
+ }
+
+ ret = request_irq(data->irqnr, ping_handle_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ pdev->name, indio_dev);
+ if (ret < 0) {
+ dev_err(data->dev, "request_irq: %d\n", ret);
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+
+ /* it should not take more than 20 ms until echo is rising */
+ ret = wait_for_completion_killable_timeout(&data->rising, HZ/50);
+ if (ret < 0)
+ goto err_reset_direction;
+ else if (ret == 0) {
+ ret = -ETIMEDOUT;
+ goto err_reset_direction;
+ }
+
+ /* it cannot take more than 50 ms until echo is falling */
+ ret = wait_for_completion_killable_timeout(&data->falling, HZ/20);
+ if (ret < 0)
+ goto err_reset_direction;
+ else if (ret == 0) {
+ ret = -ETIMEDOUT;
+ goto err_reset_direction;
+ }
+
+ ktime_dt = ktime_sub(data->ts_falling, data->ts_rising);
+
+ free_irq(data->irqnr, indio_dev);
+
+ ret = gpiod_direction_output(data->gpiod_ping, GPIOD_OUT_LOW);
+ if (ret < 0) {
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+
+ mutex_unlock(&data->lock);
+
+ dt_ns = ktime_to_ns(ktime_dt);
+ if (dt_ns > data->cfg->timeout_ns) {
+ dev_dbg(data->dev, "distance out of range: dt=%lldns\n",
+ dt_ns);
+ return -EIO;
+ }
+
+ time_ns = dt_ns;
+
+ /*
+ * read error code of laser ping sensor and give users chance to
+ * figure out error by using dynamic debuggging
+ */
+ if (data->cfg->laserping_error) {
+ if ((time_ns > 12500000) && (time_ns <= 13500000)) {
+ dev_dbg(data->dev, "target too close or to far\n");
+ return -EIO;
+ }
+ if ((time_ns > 13500000) && (time_ns <= 14500000)) {
+ dev_dbg(data->dev, "internal sensor error\n");
+ return -EIO;
+ }
+ if ((time_ns > 14500000) && (time_ns <= 15500000)) {
+ dev_dbg(data->dev, "internal sensor timeout\n");
+ return -EIO;
+ }
+ }
+
+ /*
+ * the speed as function of the temperature is approximately:
+ *
+ * speed = 331,5 + 0,6 * Temp
+ * with Temp in °C
+ * and speed in m/s
+ *
+ * use 343,5 m/s as ultrasonic speed at 20 °C here in absence of the
+ * temperature
+ *
+ * therefore:
+ * time 343,5 time * 232
+ * distance = ------ * ------- = ------------
+ * 10^6 2 1350800
+ * with time in ns
+ * and distance in mm (one way)
+ *
+ * because we limit to 3 meters the multiplication with 232 just
+ * fits into 32 bit
+ */
+ distance_mm = time_ns * 232 / 1350800;
+
+ return distance_mm;
+
+err_reset_direction:
+ free_irq(data->irqnr, indio_dev);
+ mutex_unlock(&data->lock);
+
+ if (gpiod_direction_output(data->gpiod_ping, GPIOD_OUT_LOW))
+ dev_dbg(data->dev, "error in gpiod_direction_output\n");
+ return ret;
+}
+
+static int ping_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int *val,
+ int *val2, long info)
+{
+ int ret;
+
+ if (channel->type != IIO_DISTANCE)
+ return -EINVAL;
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ ret = ping_read(indio_dev);
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ /*
+ * maximum resolution in datasheet is 1 mm
+ * 1 LSB is 1 mm
+ */
+ *val = 0;
+ *val2 = 1000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info ping_iio_info = {
+ .read_raw = ping_read_raw,
+};
+
+static const struct iio_chan_spec ping_chan_spec[] = {
+ {
+ .type = IIO_DISTANCE,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+};
+
+static const struct of_device_id of_ping_match[] = {
+ { .compatible = "parallax,ping", .data = &pa_ping_cfg},
+ { .compatible = "parallax,laserping", .data = &pa_laser_ping_cfg},
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, of_ping_match);
+
+static int ping_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ping_data *data;
+ struct iio_dev *indio_dev;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(struct ping_data));
+ if (!indio_dev) {
+ dev_err(dev, "failed to allocate IIO device\n");
+ return -ENOMEM;
+ }
+
+ data = iio_priv(indio_dev);
+ data->dev = dev;
+ data->cfg = of_device_get_match_data(dev);
+
+ mutex_init(&data->lock);
+ init_completion(&data->rising);
+ init_completion(&data->falling);
+
+ data->gpiod_ping = devm_gpiod_get(dev, "ping", GPIOD_OUT_LOW);
+ if (IS_ERR(data->gpiod_ping)) {
+ dev_err(dev, "failed to get ping-gpios: err=%ld\n",
+ PTR_ERR(data->gpiod_ping));
+ return PTR_ERR(data->gpiod_ping);
+ }
+
+ if (gpiod_cansleep(data->gpiod_ping)) {
+ dev_err(data->dev, "cansleep-GPIOs not supported\n");
+ return -ENODEV;
+ }
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ indio_dev->name = "ping";
+ indio_dev->info = &ping_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = ping_chan_spec;
+ indio_dev->num_channels = ARRAY_SIZE(ping_chan_spec);
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static struct platform_driver ping_driver = {
+ .probe = ping_probe,
+ .driver = {
+ .name = "ping-gpio",
+ .of_match_table = of_ping_match,
+ },
+};
+
+module_platform_driver(ping_driver);
+
+MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>");
+MODULE_DESCRIPTION("PING sensors for distance measuring using one GPIOs");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ping");
diff --git a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c
new file mode 100644
index 000000000..d854b8d5f
--- /dev/null
+++ b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c
@@ -0,0 +1,380 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * pulsedlight-lidar-lite-v2.c - Support for PulsedLight LIDAR sensor
+ *
+ * Copyright (C) 2015, 2017-2018
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ *
+ * TODO: interrupt mode, and signal strength reporting
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pm_runtime.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
+#define LIDAR_REG_CONTROL 0x00
+#define LIDAR_REG_CONTROL_ACQUIRE BIT(2)
+
+#define LIDAR_REG_STATUS 0x01
+#define LIDAR_REG_STATUS_INVALID BIT(3)
+#define LIDAR_REG_STATUS_READY BIT(0)
+
+#define LIDAR_REG_DATA_HBYTE 0x0f
+#define LIDAR_REG_DATA_LBYTE 0x10
+#define LIDAR_REG_DATA_WORD_READ BIT(7)
+
+#define LIDAR_REG_PWR_CONTROL 0x65
+
+#define LIDAR_DRV_NAME "lidar"
+
+struct lidar_data {
+ struct iio_dev *indio_dev;
+ struct i2c_client *client;
+
+ int (*xfer)(struct lidar_data *data, u8 reg, u8 *val, int len);
+ int i2c_enabled;
+
+ /* Ensure timestamp is naturally aligned */
+ struct {
+ u16 chan;
+ s64 timestamp __aligned(8);
+ } scan;
+};
+
+static const struct iio_chan_spec lidar_channels[] = {
+ {
+ .type = IIO_DISTANCE,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 16,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static int lidar_i2c_xfer(struct lidar_data *data, u8 reg, u8 *val, int len)
+{
+ struct i2c_client *client = data->client;
+ struct i2c_msg msg[2];
+ int ret;
+
+ msg[0].addr = client->addr;
+ msg[0].flags = client->flags | I2C_M_STOP;
+ msg[0].len = 1;
+ msg[0].buf = (char *) &reg;
+
+ msg[1].addr = client->addr;
+ msg[1].flags = client->flags | I2C_M_RD;
+ msg[1].len = len;
+ msg[1].buf = (char *) val;
+
+ ret = i2c_transfer(client->adapter, msg, 2);
+
+ return (ret == 2) ? 0 : -EIO;
+}
+
+static int lidar_smbus_xfer(struct lidar_data *data, u8 reg, u8 *val, int len)
+{
+ struct i2c_client *client = data->client;
+ int ret;
+
+ /*
+ * Device needs a STOP condition between address write, and data read
+ * so in turn i2c_smbus_read_byte_data cannot be used
+ */
+
+ while (len--) {
+ ret = i2c_smbus_write_byte(client, reg++);
+ if (ret < 0) {
+ dev_err(&client->dev, "cannot write addr value");
+ return ret;
+ }
+
+ ret = i2c_smbus_read_byte(client);
+ if (ret < 0) {
+ dev_err(&client->dev, "cannot read data value");
+ return ret;
+ }
+
+ *(val++) = ret;
+ }
+
+ return 0;
+}
+
+static int lidar_read_byte(struct lidar_data *data, u8 reg)
+{
+ int ret;
+ u8 val;
+
+ ret = data->xfer(data, reg, &val, 1);
+ if (ret < 0)
+ return ret;
+
+ return val;
+}
+
+static inline int lidar_write_control(struct lidar_data *data, int val)
+{
+ return i2c_smbus_write_byte_data(data->client, LIDAR_REG_CONTROL, val);
+}
+
+static inline int lidar_write_power(struct lidar_data *data, int val)
+{
+ return i2c_smbus_write_byte_data(data->client,
+ LIDAR_REG_PWR_CONTROL, val);
+}
+
+static int lidar_read_measurement(struct lidar_data *data, u16 *reg)
+{
+ __be16 value;
+ int ret = data->xfer(data, LIDAR_REG_DATA_HBYTE |
+ (data->i2c_enabled ? LIDAR_REG_DATA_WORD_READ : 0),
+ (u8 *) &value, 2);
+
+ if (!ret)
+ *reg = be16_to_cpu(value);
+
+ return ret;
+}
+
+static int lidar_get_measurement(struct lidar_data *data, u16 *reg)
+{
+ struct i2c_client *client = data->client;
+ int tries = 10;
+ int ret;
+
+ pm_runtime_get_sync(&client->dev);
+
+ /* start sample */
+ ret = lidar_write_control(data, LIDAR_REG_CONTROL_ACQUIRE);
+ if (ret < 0) {
+ dev_err(&client->dev, "cannot send start measurement command");
+ pm_runtime_put_noidle(&client->dev);
+ return ret;
+ }
+
+ while (tries--) {
+ usleep_range(1000, 2000);
+
+ ret = lidar_read_byte(data, LIDAR_REG_STATUS);
+ if (ret < 0)
+ break;
+
+ /* return -EINVAL since laser is likely pointed out of range */
+ if (ret & LIDAR_REG_STATUS_INVALID) {
+ *reg = 0;
+ ret = -EINVAL;
+ break;
+ }
+
+ /* sample ready to read */
+ if (!(ret & LIDAR_REG_STATUS_READY)) {
+ ret = lidar_read_measurement(data, reg);
+ break;
+ }
+ ret = -EIO;
+ }
+ pm_runtime_mark_last_busy(&client->dev);
+ pm_runtime_put_autosuspend(&client->dev);
+
+ return ret;
+}
+
+static int lidar_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct lidar_data *data = iio_priv(indio_dev);
+ int ret = -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW: {
+ u16 reg;
+
+ if (iio_device_claim_direct_mode(indio_dev))
+ return -EBUSY;
+
+ ret = lidar_get_measurement(data, &reg);
+ if (!ret) {
+ *val = reg;
+ ret = IIO_VAL_INT;
+ }
+ iio_device_release_direct_mode(indio_dev);
+ break;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = 10000;
+ ret = IIO_VAL_INT_PLUS_MICRO;
+ break;
+ }
+
+ return ret;
+}
+
+static irqreturn_t lidar_trigger_handler(int irq, void *private)
+{
+ struct iio_poll_func *pf = private;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct lidar_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = lidar_get_measurement(data, &data->scan.chan);
+ if (!ret) {
+ iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
+ iio_get_time_ns(indio_dev));
+ } else if (ret != -EINVAL) {
+ dev_err(&data->client->dev, "cannot read LIDAR measurement");
+ }
+
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static const struct iio_info lidar_info = {
+ .read_raw = lidar_read_raw,
+};
+
+static int lidar_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct lidar_data *data;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+ data = iio_priv(indio_dev);
+
+ if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ data->xfer = lidar_i2c_xfer;
+ data->i2c_enabled = 1;
+ } else if (i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE))
+ data->xfer = lidar_smbus_xfer;
+ else
+ return -EOPNOTSUPP;
+
+ indio_dev->info = &lidar_info;
+ indio_dev->name = LIDAR_DRV_NAME;
+ indio_dev->channels = lidar_channels;
+ indio_dev->num_channels = ARRAY_SIZE(lidar_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ i2c_set_clientdata(client, indio_dev);
+
+ data->client = client;
+ data->indio_dev = indio_dev;
+
+ ret = iio_triggered_buffer_setup(indio_dev, NULL,
+ lidar_trigger_handler, NULL);
+ if (ret)
+ return ret;
+
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto error_unreg_buffer;
+
+ pm_runtime_set_autosuspend_delay(&client->dev, 1000);
+ pm_runtime_use_autosuspend(&client->dev);
+
+ ret = pm_runtime_set_active(&client->dev);
+ if (ret)
+ goto error_unreg_buffer;
+ pm_runtime_enable(&client->dev);
+ pm_runtime_idle(&client->dev);
+
+ return 0;
+
+error_unreg_buffer:
+ iio_triggered_buffer_cleanup(indio_dev);
+
+ return ret;
+}
+
+static int lidar_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+ iio_device_unregister(indio_dev);
+ iio_triggered_buffer_cleanup(indio_dev);
+
+ pm_runtime_disable(&client->dev);
+ pm_runtime_set_suspended(&client->dev);
+
+ return 0;
+}
+
+static const struct i2c_device_id lidar_id[] = {
+ {"lidar-lite-v2", 0},
+ {"lidar-lite-v3", 0},
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, lidar_id);
+
+static const struct of_device_id lidar_dt_ids[] = {
+ { .compatible = "pulsedlight,lidar-lite-v2" },
+ { .compatible = "grmn,lidar-lite-v3" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lidar_dt_ids);
+
+#ifdef CONFIG_PM
+static int lidar_pm_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct lidar_data *data = iio_priv(indio_dev);
+
+ return lidar_write_power(data, 0x0f);
+}
+
+static int lidar_pm_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct lidar_data *data = iio_priv(indio_dev);
+ int ret = lidar_write_power(data, 0);
+
+ /* regulator and FPGA needs settling time */
+ usleep_range(15000, 20000);
+
+ return ret;
+}
+#endif
+
+static const struct dev_pm_ops lidar_pm_ops = {
+ SET_RUNTIME_PM_OPS(lidar_pm_runtime_suspend,
+ lidar_pm_runtime_resume, NULL)
+};
+
+static struct i2c_driver lidar_driver = {
+ .driver = {
+ .name = LIDAR_DRV_NAME,
+ .of_match_table = lidar_dt_ids,
+ .pm = &lidar_pm_ops,
+ },
+ .probe = lidar_probe,
+ .remove = lidar_remove,
+ .id_table = lidar_id,
+};
+module_i2c_driver(lidar_driver);
+
+MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
+MODULE_DESCRIPTION("PulsedLight LIDAR sensor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/proximity/rfd77402.c b/drivers/iio/proximity/rfd77402.c
new file mode 100644
index 000000000..7a0472323
--- /dev/null
+++ b/drivers/iio/proximity/rfd77402.c
@@ -0,0 +1,348 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * rfd77402.c - Support for RF Digital RFD77402 Time-of-Flight (distance) sensor
+ *
+ * Copyright 2017 Peter Meerwald-Stadler <pmeerw@pmeerw.net>
+ *
+ * 7-bit I2C slave address 0x4c
+ *
+ * TODO: interrupt
+ * https://media.digikey.com/pdf/Data%20Sheets/RF%20Digital%20PDFs/RFD77402.pdf
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+
+#include <linux/iio/iio.h>
+
+#define RFD77402_DRV_NAME "rfd77402"
+
+#define RFD77402_ICSR 0x00 /* Interrupt Control Status Register */
+#define RFD77402_ICSR_INT_MODE BIT(2)
+#define RFD77402_ICSR_INT_POL BIT(3)
+#define RFD77402_ICSR_RESULT BIT(4)
+#define RFD77402_ICSR_M2H_MSG BIT(5)
+#define RFD77402_ICSR_H2M_MSG BIT(6)
+#define RFD77402_ICSR_RESET BIT(7)
+
+#define RFD77402_CMD_R 0x04
+#define RFD77402_CMD_SINGLE 0x01
+#define RFD77402_CMD_STANDBY 0x10
+#define RFD77402_CMD_MCPU_OFF 0x11
+#define RFD77402_CMD_MCPU_ON 0x12
+#define RFD77402_CMD_RESET BIT(6)
+#define RFD77402_CMD_VALID BIT(7)
+
+#define RFD77402_STATUS_R 0x06
+#define RFD77402_STATUS_PM_MASK GENMASK(4, 0)
+#define RFD77402_STATUS_STANDBY 0x00
+#define RFD77402_STATUS_MCPU_OFF 0x10
+#define RFD77402_STATUS_MCPU_ON 0x18
+
+#define RFD77402_RESULT_R 0x08
+#define RFD77402_RESULT_DIST_MASK GENMASK(12, 2)
+#define RFD77402_RESULT_ERR_MASK GENMASK(14, 13)
+#define RFD77402_RESULT_VALID BIT(15)
+
+#define RFD77402_PMU_CFG 0x14
+#define RFD77402_PMU_MCPU_INIT BIT(9)
+
+#define RFD77402_I2C_INIT_CFG 0x1c
+#define RFD77402_I2C_ADDR_INCR BIT(0)
+#define RFD77402_I2C_DATA_INCR BIT(2)
+#define RFD77402_I2C_HOST_DEBUG BIT(5)
+#define RFD77402_I2C_MCPU_DEBUG BIT(6)
+
+#define RFD77402_CMD_CFGR_A 0x0c
+#define RFD77402_CMD_CFGR_B 0x0e
+#define RFD77402_HFCFG_0 0x20
+#define RFD77402_HFCFG_1 0x22
+#define RFD77402_HFCFG_2 0x24
+#define RFD77402_HFCFG_3 0x26
+
+#define RFD77402_MOD_CHIP_ID 0x28
+
+/* magic configuration values from datasheet */
+static const struct {
+ u8 reg;
+ u16 val;
+} rf77402_tof_config[] = {
+ {RFD77402_CMD_CFGR_A, 0xe100},
+ {RFD77402_CMD_CFGR_B, 0x10ff},
+ {RFD77402_HFCFG_0, 0x07d0},
+ {RFD77402_HFCFG_1, 0x5008},
+ {RFD77402_HFCFG_2, 0xa041},
+ {RFD77402_HFCFG_3, 0x45d4},
+};
+
+struct rfd77402_data {
+ struct i2c_client *client;
+ /* Serialize reads from the sensor */
+ struct mutex lock;
+};
+
+static const struct iio_chan_spec rfd77402_channels[] = {
+ {
+ .type = IIO_DISTANCE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+};
+
+static int rfd77402_set_state(struct rfd77402_data *data, u8 state, u16 check)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(data->client, RFD77402_CMD_R,
+ state | RFD77402_CMD_VALID);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(10000, 20000);
+
+ ret = i2c_smbus_read_word_data(data->client, RFD77402_STATUS_R);
+ if (ret < 0)
+ return ret;
+ if ((ret & RFD77402_STATUS_PM_MASK) != check)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int rfd77402_measure(struct rfd77402_data *data)
+{
+ int ret;
+ int tries = 10;
+
+ ret = rfd77402_set_state(data, RFD77402_CMD_MCPU_ON,
+ RFD77402_STATUS_MCPU_ON);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_write_byte_data(data->client, RFD77402_CMD_R,
+ RFD77402_CMD_SINGLE |
+ RFD77402_CMD_VALID);
+ if (ret < 0)
+ goto err;
+
+ while (tries-- > 0) {
+ ret = i2c_smbus_read_byte_data(data->client, RFD77402_ICSR);
+ if (ret < 0)
+ goto err;
+ if (ret & RFD77402_ICSR_RESULT)
+ break;
+ msleep(20);
+ }
+
+ if (tries < 0) {
+ ret = -ETIMEDOUT;
+ goto err;
+ }
+
+ ret = i2c_smbus_read_word_data(data->client, RFD77402_RESULT_R);
+ if (ret < 0)
+ goto err;
+
+ if ((ret & RFD77402_RESULT_ERR_MASK) ||
+ !(ret & RFD77402_RESULT_VALID)) {
+ ret = -EIO;
+ goto err;
+ }
+
+ return (ret & RFD77402_RESULT_DIST_MASK) >> 2;
+
+err:
+ rfd77402_set_state(data, RFD77402_CMD_MCPU_OFF,
+ RFD77402_STATUS_MCPU_OFF);
+ return ret;
+}
+
+static int rfd77402_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct rfd77402_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&data->lock);
+ ret = rfd77402_measure(data);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ /* 1 LSB is 1 mm */
+ *val = 0;
+ *val2 = 1000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info rfd77402_info = {
+ .read_raw = rfd77402_read_raw,
+};
+
+static int rfd77402_init(struct rfd77402_data *data)
+{
+ int ret, i;
+
+ ret = rfd77402_set_state(data, RFD77402_CMD_STANDBY,
+ RFD77402_STATUS_STANDBY);
+ if (ret < 0)
+ return ret;
+
+ /* configure INT pad as push-pull, active low */
+ ret = i2c_smbus_write_byte_data(data->client, RFD77402_ICSR,
+ RFD77402_ICSR_INT_MODE);
+ if (ret < 0)
+ return ret;
+
+ /* I2C configuration */
+ ret = i2c_smbus_write_word_data(data->client, RFD77402_I2C_INIT_CFG,
+ RFD77402_I2C_ADDR_INCR |
+ RFD77402_I2C_DATA_INCR |
+ RFD77402_I2C_HOST_DEBUG |
+ RFD77402_I2C_MCPU_DEBUG);
+ if (ret < 0)
+ return ret;
+
+ /* set initialization */
+ ret = i2c_smbus_write_word_data(data->client, RFD77402_PMU_CFG, 0x0500);
+ if (ret < 0)
+ return ret;
+
+ ret = rfd77402_set_state(data, RFD77402_CMD_MCPU_OFF,
+ RFD77402_STATUS_MCPU_OFF);
+ if (ret < 0)
+ return ret;
+
+ /* set initialization */
+ ret = i2c_smbus_write_word_data(data->client, RFD77402_PMU_CFG, 0x0600);
+ if (ret < 0)
+ return ret;
+
+ ret = rfd77402_set_state(data, RFD77402_CMD_MCPU_ON,
+ RFD77402_STATUS_MCPU_ON);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(rf77402_tof_config); i++) {
+ ret = i2c_smbus_write_word_data(data->client,
+ rf77402_tof_config[i].reg,
+ rf77402_tof_config[i].val);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = rfd77402_set_state(data, RFD77402_CMD_STANDBY,
+ RFD77402_STATUS_STANDBY);
+
+ return ret;
+}
+
+static int rfd77402_powerdown(struct rfd77402_data *data)
+{
+ return rfd77402_set_state(data, RFD77402_CMD_STANDBY,
+ RFD77402_STATUS_STANDBY);
+}
+
+static int rfd77402_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct rfd77402_data *data;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ ret = i2c_smbus_read_word_data(client, RFD77402_MOD_CHIP_ID);
+ if (ret < 0)
+ return ret;
+ if (ret != 0xad01 && ret != 0xad02) /* known chip ids */
+ 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->info = &rfd77402_info;
+ indio_dev->channels = rfd77402_channels;
+ indio_dev->num_channels = ARRAY_SIZE(rfd77402_channels);
+ indio_dev->name = RFD77402_DRV_NAME;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = rfd77402_init(data);
+ if (ret < 0)
+ return ret;
+
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto err_powerdown;
+
+ return 0;
+
+err_powerdown:
+ rfd77402_powerdown(data);
+ return ret;
+}
+
+static int rfd77402_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+ iio_device_unregister(indio_dev);
+ rfd77402_powerdown(iio_priv(indio_dev));
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int rfd77402_suspend(struct device *dev)
+{
+ struct rfd77402_data *data = iio_priv(i2c_get_clientdata(
+ to_i2c_client(dev)));
+
+ return rfd77402_powerdown(data);
+}
+
+static int rfd77402_resume(struct device *dev)
+{
+ struct rfd77402_data *data = iio_priv(i2c_get_clientdata(
+ to_i2c_client(dev)));
+
+ return rfd77402_init(data);
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(rfd77402_pm_ops, rfd77402_suspend, rfd77402_resume);
+
+static const struct i2c_device_id rfd77402_id[] = {
+ { "rfd77402", 0},
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, rfd77402_id);
+
+static struct i2c_driver rfd77402_driver = {
+ .driver = {
+ .name = RFD77402_DRV_NAME,
+ .pm = &rfd77402_pm_ops,
+ },
+ .probe = rfd77402_probe,
+ .remove = rfd77402_remove,
+ .id_table = rfd77402_id,
+};
+
+module_i2c_driver(rfd77402_driver);
+
+MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("RFD77402 Time-of-Flight sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/proximity/srf04.c b/drivers/iio/proximity/srf04.c
new file mode 100644
index 000000000..420c37c72
--- /dev/null
+++ b/drivers/iio/proximity/srf04.c
@@ -0,0 +1,408 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SRF04: ultrasonic sensor for distance measuring by using GPIOs
+ *
+ * Copyright (c) 2017 Andreas Klinger <ak@it-klinger.de>
+ *
+ * For details about the device see:
+ * https://www.robot-electronics.co.uk/htm/srf04tech.htm
+ *
+ * the measurement cycle as timing diagram looks like:
+ *
+ * +---+
+ * GPIO | |
+ * trig: --+ +------------------------------------------------------
+ * ^ ^
+ * |<->|
+ * udelay(trigger_pulse_us)
+ *
+ * ultra +-+ +-+ +-+
+ * sonic | | | | | |
+ * burst: ---------+ +-+ +-+ +-----------------------------------------
+ * .
+ * ultra . +-+ +-+ +-+
+ * sonic . | | | | | |
+ * echo: ----------------------------------+ +-+ +-+ +----------------
+ * . .
+ * +------------------------+
+ * GPIO | |
+ * echo: -------------------+ +---------------
+ * ^ ^
+ * interrupt interrupt
+ * (ts_rising) (ts_falling)
+ * |<---------------------->|
+ * pulse time measured
+ * --> one round trip of ultra sonic waves
+ */
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+struct srf04_cfg {
+ unsigned long trigger_pulse_us;
+};
+
+struct srf04_data {
+ struct device *dev;
+ struct gpio_desc *gpiod_trig;
+ struct gpio_desc *gpiod_echo;
+ struct gpio_desc *gpiod_power;
+ struct mutex lock;
+ int irqnr;
+ ktime_t ts_rising;
+ ktime_t ts_falling;
+ struct completion rising;
+ struct completion falling;
+ const struct srf04_cfg *cfg;
+ int startup_time_ms;
+};
+
+static const struct srf04_cfg srf04_cfg = {
+ .trigger_pulse_us = 10,
+};
+
+static const struct srf04_cfg mb_lv_cfg = {
+ .trigger_pulse_us = 20,
+};
+
+static irqreturn_t srf04_handle_irq(int irq, void *dev_id)
+{
+ struct iio_dev *indio_dev = dev_id;
+ struct srf04_data *data = iio_priv(indio_dev);
+ ktime_t now = ktime_get();
+
+ if (gpiod_get_value(data->gpiod_echo)) {
+ data->ts_rising = now;
+ complete(&data->rising);
+ } else {
+ data->ts_falling = now;
+ complete(&data->falling);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int srf04_read(struct srf04_data *data)
+{
+ int ret;
+ ktime_t ktime_dt;
+ u64 dt_ns;
+ u32 time_ns, distance_mm;
+
+ if (data->gpiod_power)
+ pm_runtime_get_sync(data->dev);
+
+ /*
+ * just one read-echo-cycle can take place at a time
+ * ==> lock against concurrent reading calls
+ */
+ mutex_lock(&data->lock);
+
+ reinit_completion(&data->rising);
+ reinit_completion(&data->falling);
+
+ gpiod_set_value(data->gpiod_trig, 1);
+ udelay(data->cfg->trigger_pulse_us);
+ gpiod_set_value(data->gpiod_trig, 0);
+
+ if (data->gpiod_power) {
+ pm_runtime_mark_last_busy(data->dev);
+ pm_runtime_put_autosuspend(data->dev);
+ }
+
+ /* it should not take more than 20 ms until echo is rising */
+ ret = wait_for_completion_killable_timeout(&data->rising, HZ/50);
+ if (ret < 0) {
+ mutex_unlock(&data->lock);
+ return ret;
+ } else if (ret == 0) {
+ mutex_unlock(&data->lock);
+ return -ETIMEDOUT;
+ }
+
+ /* it cannot take more than 50 ms until echo is falling */
+ ret = wait_for_completion_killable_timeout(&data->falling, HZ/20);
+ if (ret < 0) {
+ mutex_unlock(&data->lock);
+ return ret;
+ } else if (ret == 0) {
+ mutex_unlock(&data->lock);
+ return -ETIMEDOUT;
+ }
+
+ ktime_dt = ktime_sub(data->ts_falling, data->ts_rising);
+
+ mutex_unlock(&data->lock);
+
+ dt_ns = ktime_to_ns(ktime_dt);
+ /*
+ * measuring more than 6,45 meters is beyond the capabilities of
+ * the supported sensors
+ * ==> filter out invalid results for not measuring echos of
+ * another us sensor
+ *
+ * formula:
+ * distance 6,45 * 2 m
+ * time = ---------- = ------------ = 40438871 ns
+ * speed 319 m/s
+ *
+ * using a minimum speed at -20 °C of 319 m/s
+ */
+ if (dt_ns > 40438871)
+ return -EIO;
+
+ time_ns = dt_ns;
+
+ /*
+ * the speed as function of the temperature is approximately:
+ *
+ * speed = 331,5 + 0,6 * Temp
+ * with Temp in °C
+ * and speed in m/s
+ *
+ * use 343,5 m/s as ultrasonic speed at 20 °C here in absence of the
+ * temperature
+ *
+ * therefore:
+ * time 343,5 time * 106
+ * distance = ------ * ------- = ------------
+ * 10^6 2 617176
+ * with time in ns
+ * and distance in mm (one way)
+ *
+ * because we limit to 6,45 meters the multiplication with 106 just
+ * fits into 32 bit
+ */
+ distance_mm = time_ns * 106 / 617176;
+
+ return distance_mm;
+}
+
+static int srf04_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int *val,
+ int *val2, long info)
+{
+ struct srf04_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (channel->type != IIO_DISTANCE)
+ return -EINVAL;
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ ret = srf04_read(data);
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ /*
+ * theoretical maximum resolution is 3 mm
+ * 1 LSB is 1 mm
+ */
+ *val = 0;
+ *val2 = 1000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info srf04_iio_info = {
+ .read_raw = srf04_read_raw,
+};
+
+static const struct iio_chan_spec srf04_chan_spec[] = {
+ {
+ .type = IIO_DISTANCE,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+};
+
+static const struct of_device_id of_srf04_match[] = {
+ { .compatible = "devantech,srf04", .data = &srf04_cfg},
+ { .compatible = "maxbotix,mb1000", .data = &mb_lv_cfg},
+ { .compatible = "maxbotix,mb1010", .data = &mb_lv_cfg},
+ { .compatible = "maxbotix,mb1020", .data = &mb_lv_cfg},
+ { .compatible = "maxbotix,mb1030", .data = &mb_lv_cfg},
+ { .compatible = "maxbotix,mb1040", .data = &mb_lv_cfg},
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, of_srf04_match);
+
+static int srf04_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct srf04_data *data;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(struct srf04_data));
+ if (!indio_dev) {
+ dev_err(dev, "failed to allocate IIO device\n");
+ return -ENOMEM;
+ }
+
+ data = iio_priv(indio_dev);
+ data->dev = dev;
+ data->cfg = of_match_device(of_srf04_match, dev)->data;
+
+ mutex_init(&data->lock);
+ init_completion(&data->rising);
+ init_completion(&data->falling);
+
+ data->gpiod_trig = devm_gpiod_get(dev, "trig", GPIOD_OUT_LOW);
+ if (IS_ERR(data->gpiod_trig)) {
+ dev_err(dev, "failed to get trig-gpios: err=%ld\n",
+ PTR_ERR(data->gpiod_trig));
+ return PTR_ERR(data->gpiod_trig);
+ }
+
+ data->gpiod_echo = devm_gpiod_get(dev, "echo", GPIOD_IN);
+ if (IS_ERR(data->gpiod_echo)) {
+ dev_err(dev, "failed to get echo-gpios: err=%ld\n",
+ PTR_ERR(data->gpiod_echo));
+ return PTR_ERR(data->gpiod_echo);
+ }
+
+ data->gpiod_power = devm_gpiod_get_optional(dev, "power",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(data->gpiod_power)) {
+ dev_err(dev, "failed to get power-gpios: err=%ld\n",
+ PTR_ERR(data->gpiod_power));
+ return PTR_ERR(data->gpiod_power);
+ }
+ if (data->gpiod_power) {
+
+ if (of_property_read_u32(dev->of_node, "startup-time-ms",
+ &data->startup_time_ms))
+ data->startup_time_ms = 100;
+ dev_dbg(dev, "using power gpio: startup-time-ms=%d\n",
+ data->startup_time_ms);
+ }
+
+ if (gpiod_cansleep(data->gpiod_echo)) {
+ dev_err(data->dev, "cansleep-GPIOs not supported\n");
+ return -ENODEV;
+ }
+
+ data->irqnr = gpiod_to_irq(data->gpiod_echo);
+ if (data->irqnr < 0) {
+ dev_err(data->dev, "gpiod_to_irq: %d\n", data->irqnr);
+ return data->irqnr;
+ }
+
+ ret = devm_request_irq(dev, data->irqnr, srf04_handle_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ pdev->name, indio_dev);
+ if (ret < 0) {
+ dev_err(data->dev, "request_irq: %d\n", ret);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ indio_dev->name = "srf04";
+ indio_dev->info = &srf04_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = srf04_chan_spec;
+ indio_dev->num_channels = ARRAY_SIZE(srf04_chan_spec);
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0) {
+ dev_err(data->dev, "iio_device_register: %d\n", ret);
+ return ret;
+ }
+
+ if (data->gpiod_power) {
+ pm_runtime_set_autosuspend_delay(data->dev, 1000);
+ pm_runtime_use_autosuspend(data->dev);
+
+ ret = pm_runtime_set_active(data->dev);
+ if (ret) {
+ dev_err(data->dev, "pm_runtime_set_active: %d\n", ret);
+ iio_device_unregister(indio_dev);
+ }
+
+ pm_runtime_enable(data->dev);
+ pm_runtime_idle(data->dev);
+ }
+
+ return ret;
+}
+
+static int srf04_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct srf04_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+
+ if (data->gpiod_power) {
+ pm_runtime_disable(data->dev);
+ pm_runtime_set_suspended(data->dev);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused srf04_pm_runtime_suspend(struct device *dev)
+{
+ struct platform_device *pdev = container_of(dev,
+ struct platform_device, dev);
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct srf04_data *data = iio_priv(indio_dev);
+
+ gpiod_set_value(data->gpiod_power, 0);
+
+ return 0;
+}
+
+static int __maybe_unused srf04_pm_runtime_resume(struct device *dev)
+{
+ struct platform_device *pdev = container_of(dev,
+ struct platform_device, dev);
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct srf04_data *data = iio_priv(indio_dev);
+
+ gpiod_set_value(data->gpiod_power, 1);
+ msleep(data->startup_time_ms);
+
+ return 0;
+}
+
+static const struct dev_pm_ops srf04_pm_ops = {
+ SET_RUNTIME_PM_OPS(srf04_pm_runtime_suspend,
+ srf04_pm_runtime_resume, NULL)
+};
+
+static struct platform_driver srf04_driver = {
+ .probe = srf04_probe,
+ .remove = srf04_remove,
+ .driver = {
+ .name = "srf04-gpio",
+ .of_match_table = of_srf04_match,
+ .pm = &srf04_pm_ops,
+ },
+};
+
+module_platform_driver(srf04_driver);
+
+MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>");
+MODULE_DESCRIPTION("SRF04 ultrasonic sensor for distance measuring using GPIOs");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:srf04");
diff --git a/drivers/iio/proximity/srf08.c b/drivers/iio/proximity/srf08.c
new file mode 100644
index 000000000..9b0886760
--- /dev/null
+++ b/drivers/iio/proximity/srf08.c
@@ -0,0 +1,559 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * srf08.c - Support for Devantech SRFxx ultrasonic ranger
+ * with i2c interface
+ * actually supported are srf02, srf08, srf10
+ *
+ * Copyright (c) 2016, 2017 Andreas Klinger <ak@it-klinger.de>
+ *
+ * For details about the device see:
+ * https://www.robot-electronics.co.uk/htm/srf08tech.html
+ * https://www.robot-electronics.co.uk/htm/srf10tech.htm
+ * https://www.robot-electronics.co.uk/htm/srf02tech.htm
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+/* registers of SRF08 device */
+#define SRF08_WRITE_COMMAND 0x00 /* Command Register */
+#define SRF08_WRITE_MAX_GAIN 0x01 /* Max Gain Register: 0 .. 31 */
+#define SRF08_WRITE_RANGE 0x02 /* Range Register: 0 .. 255 */
+#define SRF08_READ_SW_REVISION 0x00 /* Software Revision */
+#define SRF08_READ_LIGHT 0x01 /* Light Sensor during last echo */
+#define SRF08_READ_ECHO_1_HIGH 0x02 /* Range of first echo received */
+#define SRF08_READ_ECHO_1_LOW 0x03 /* Range of first echo received */
+
+#define SRF08_CMD_RANGING_CM 0x51 /* Ranging Mode - Result in cm */
+
+enum srf08_sensor_type {
+ SRF02,
+ SRF08,
+ SRF10,
+ SRF_MAX_TYPE
+};
+
+struct srf08_chip_info {
+ const int *sensitivity_avail;
+ int num_sensitivity_avail;
+ int sensitivity_default;
+
+ /* default value of Range in mm */
+ int range_default;
+};
+
+struct srf08_data {
+ struct i2c_client *client;
+
+ /*
+ * Gain in the datasheet is called sensitivity here to distinct it
+ * from the gain used with amplifiers of adc's
+ */
+ int sensitivity;
+
+ /* max. Range in mm */
+ int range_mm;
+ struct mutex lock;
+
+ /* Ensure timestamp is naturally aligned */
+ struct {
+ s16 chan;
+ s64 timestamp __aligned(8);
+ } scan;
+
+ /* Sensor-Type */
+ enum srf08_sensor_type sensor_type;
+
+ /* Chip-specific information */
+ const struct srf08_chip_info *chip_info;
+};
+
+/*
+ * in the documentation one can read about the "Gain" of the device
+ * which is used here for amplifying the signal and filtering out unwanted
+ * ones.
+ * But with ADC's this term is already used differently and that's why it
+ * is called "Sensitivity" here.
+ */
+static const struct srf08_chip_info srf02_chip_info = {
+ .sensitivity_avail = NULL,
+ .num_sensitivity_avail = 0,
+ .sensitivity_default = 0,
+
+ .range_default = 0,
+};
+
+static const int srf08_sensitivity_avail[] = {
+ 94, 97, 100, 103, 107, 110, 114, 118,
+ 123, 128, 133, 139, 145, 152, 159, 168,
+ 177, 187, 199, 212, 227, 245, 265, 288,
+ 317, 352, 395, 450, 524, 626, 777, 1025
+ };
+
+static const struct srf08_chip_info srf08_chip_info = {
+ .sensitivity_avail = srf08_sensitivity_avail,
+ .num_sensitivity_avail = ARRAY_SIZE(srf08_sensitivity_avail),
+ .sensitivity_default = 1025,
+
+ .range_default = 6020,
+};
+
+static const int srf10_sensitivity_avail[] = {
+ 40, 40, 50, 60, 70, 80, 100, 120,
+ 140, 200, 250, 300, 350, 400, 500, 600,
+ 700,
+ };
+
+static const struct srf08_chip_info srf10_chip_info = {
+ .sensitivity_avail = srf10_sensitivity_avail,
+ .num_sensitivity_avail = ARRAY_SIZE(srf10_sensitivity_avail),
+ .sensitivity_default = 700,
+
+ .range_default = 6020,
+};
+
+static int srf08_read_ranging(struct srf08_data *data)
+{
+ struct i2c_client *client = data->client;
+ int ret, i;
+ int waittime;
+
+ mutex_lock(&data->lock);
+
+ ret = i2c_smbus_write_byte_data(data->client,
+ SRF08_WRITE_COMMAND, SRF08_CMD_RANGING_CM);
+ if (ret < 0) {
+ dev_err(&client->dev, "write command - err: %d\n", ret);
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+
+ /*
+ * we read here until a correct version number shows up as
+ * suggested by the documentation
+ *
+ * with an ultrasonic speed of 343 m/s and a roundtrip of it
+ * sleep the expected duration and try to read from the device
+ * if nothing useful is read try it in a shorter grid
+ *
+ * polling for not more than 20 ms should be enough
+ */
+ waittime = 1 + data->range_mm / 172;
+ msleep(waittime);
+ for (i = 0; i < 4; i++) {
+ ret = i2c_smbus_read_byte_data(data->client,
+ SRF08_READ_SW_REVISION);
+
+ /* check if a valid version number is read */
+ if (ret < 255 && ret > 0)
+ break;
+ msleep(5);
+ }
+
+ if (ret >= 255 || ret <= 0) {
+ dev_err(&client->dev, "device not ready\n");
+ mutex_unlock(&data->lock);
+ return -EIO;
+ }
+
+ ret = i2c_smbus_read_word_swapped(data->client,
+ SRF08_READ_ECHO_1_HIGH);
+ if (ret < 0) {
+ dev_err(&client->dev, "cannot read distance: ret=%d\n", ret);
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static irqreturn_t srf08_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct srf08_data *data = iio_priv(indio_dev);
+ s16 sensor_data;
+
+ sensor_data = srf08_read_ranging(data);
+ if (sensor_data < 0)
+ goto err;
+
+ mutex_lock(&data->lock);
+
+ data->scan.chan = sensor_data;
+ iio_push_to_buffers_with_timestamp(indio_dev,
+ &data->scan, pf->timestamp);
+
+ mutex_unlock(&data->lock);
+err:
+ iio_trigger_notify_done(indio_dev->trig);
+ return IRQ_HANDLED;
+}
+
+static int srf08_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int *val,
+ int *val2, long mask)
+{
+ struct srf08_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (channel->type != IIO_DISTANCE)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = srf08_read_ranging(data);
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ /* 1 LSB is 1 cm */
+ *val = 0;
+ *val2 = 10000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static ssize_t srf08_show_range_mm_available(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "[0.043 0.043 11.008]\n");
+}
+
+static IIO_DEVICE_ATTR(sensor_max_range_available, S_IRUGO,
+ srf08_show_range_mm_available, NULL, 0);
+
+static ssize_t srf08_show_range_mm(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct srf08_data *data = iio_priv(indio_dev);
+
+ return sprintf(buf, "%d.%03d\n", data->range_mm / 1000,
+ data->range_mm % 1000);
+}
+
+/*
+ * set the range of the sensor to an even multiple of 43 mm
+ * which corresponds to 1 LSB in the register
+ *
+ * register value corresponding range
+ * 0x00 43 mm
+ * 0x01 86 mm
+ * 0x02 129 mm
+ * ...
+ * 0xFF 11008 mm
+ */
+static ssize_t srf08_write_range_mm(struct srf08_data *data, unsigned int val)
+{
+ int ret;
+ struct i2c_client *client = data->client;
+ unsigned int mod;
+ u8 regval;
+
+ ret = val / 43 - 1;
+ mod = val % 43;
+
+ if (mod || (ret < 0) || (ret > 255))
+ return -EINVAL;
+
+ regval = ret;
+
+ mutex_lock(&data->lock);
+
+ ret = i2c_smbus_write_byte_data(client, SRF08_WRITE_RANGE, regval);
+ if (ret < 0) {
+ dev_err(&client->dev, "write_range - err: %d\n", ret);
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+
+ data->range_mm = val;
+
+ mutex_unlock(&data->lock);
+
+ return 0;
+}
+
+static ssize_t srf08_store_range_mm(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct srf08_data *data = iio_priv(indio_dev);
+ int ret;
+ int integer, fract;
+
+ ret = iio_str_to_fixpoint(buf, 100, &integer, &fract);
+ if (ret)
+ return ret;
+
+ ret = srf08_write_range_mm(data, integer * 1000 + fract);
+ if (ret < 0)
+ return ret;
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR(sensor_max_range, S_IRUGO | S_IWUSR,
+ srf08_show_range_mm, srf08_store_range_mm, 0);
+
+static ssize_t srf08_show_sensitivity_available(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int i, len = 0;
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct srf08_data *data = iio_priv(indio_dev);
+
+ for (i = 0; i < data->chip_info->num_sensitivity_avail; i++)
+ if (data->chip_info->sensitivity_avail[i])
+ len += sprintf(buf + len, "%d ",
+ data->chip_info->sensitivity_avail[i]);
+
+ len += sprintf(buf + len, "\n");
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR(sensor_sensitivity_available, S_IRUGO,
+ srf08_show_sensitivity_available, NULL, 0);
+
+static ssize_t srf08_show_sensitivity(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct srf08_data *data = iio_priv(indio_dev);
+ int len;
+
+ len = sprintf(buf, "%d\n", data->sensitivity);
+
+ return len;
+}
+
+static ssize_t srf08_write_sensitivity(struct srf08_data *data,
+ unsigned int val)
+{
+ struct i2c_client *client = data->client;
+ int ret, i;
+ u8 regval;
+
+ if (!val)
+ return -EINVAL;
+
+ for (i = 0; i < data->chip_info->num_sensitivity_avail; i++)
+ if (val && (val == data->chip_info->sensitivity_avail[i])) {
+ regval = i;
+ break;
+ }
+
+ if (i >= data->chip_info->num_sensitivity_avail)
+ return -EINVAL;
+
+ mutex_lock(&data->lock);
+
+ ret = i2c_smbus_write_byte_data(client, SRF08_WRITE_MAX_GAIN, regval);
+ if (ret < 0) {
+ dev_err(&client->dev, "write_sensitivity - err: %d\n", ret);
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+
+ data->sensitivity = val;
+
+ mutex_unlock(&data->lock);
+
+ return 0;
+}
+
+static ssize_t srf08_store_sensitivity(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct srf08_data *data = iio_priv(indio_dev);
+ int ret;
+ unsigned int val;
+
+ ret = kstrtouint(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ ret = srf08_write_sensitivity(data, val);
+ if (ret < 0)
+ return ret;
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR(sensor_sensitivity, S_IRUGO | S_IWUSR,
+ srf08_show_sensitivity, srf08_store_sensitivity, 0);
+
+static struct attribute *srf08_attributes[] = {
+ &iio_dev_attr_sensor_max_range.dev_attr.attr,
+ &iio_dev_attr_sensor_max_range_available.dev_attr.attr,
+ &iio_dev_attr_sensor_sensitivity.dev_attr.attr,
+ &iio_dev_attr_sensor_sensitivity_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group srf08_attribute_group = {
+ .attrs = srf08_attributes,
+};
+
+static const struct iio_chan_spec srf08_channels[] = {
+ {
+ .type = IIO_DISTANCE,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_CPU,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct iio_info srf08_info = {
+ .read_raw = srf08_read_raw,
+ .attrs = &srf08_attribute_group,
+};
+
+/*
+ * srf02 don't have an adjustable range or sensitivity,
+ * so we don't need attributes at all
+ */
+static const struct iio_info srf02_info = {
+ .read_raw = srf08_read_raw,
+};
+
+static int srf08_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct iio_dev *indio_dev;
+ struct srf08_data *data;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_BYTE_DATA |
+ I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
+ I2C_FUNC_SMBUS_READ_WORD_DATA))
+ 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;
+ data->sensor_type = (enum srf08_sensor_type)id->driver_data;
+
+ switch (data->sensor_type) {
+ case SRF02:
+ data->chip_info = &srf02_chip_info;
+ indio_dev->info = &srf02_info;
+ break;
+ case SRF08:
+ data->chip_info = &srf08_chip_info;
+ indio_dev->info = &srf08_info;
+ break;
+ case SRF10:
+ data->chip_info = &srf10_chip_info;
+ indio_dev->info = &srf08_info;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ indio_dev->name = id->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = srf08_channels;
+ indio_dev->num_channels = ARRAY_SIZE(srf08_channels);
+
+ mutex_init(&data->lock);
+
+ ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev,
+ iio_pollfunc_store_time, srf08_trigger_handler, NULL);
+ if (ret < 0) {
+ dev_err(&client->dev, "setup of iio triggered buffer failed\n");
+ return ret;
+ }
+
+ if (data->chip_info->range_default) {
+ /*
+ * set default range of device in mm here
+ * these register values cannot be read from the hardware
+ * therefore set driver specific default values
+ *
+ * srf02 don't have a default value so it'll be omitted
+ */
+ ret = srf08_write_range_mm(data,
+ data->chip_info->range_default);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (data->chip_info->sensitivity_default) {
+ /*
+ * set default sensitivity of device here
+ * these register values cannot be read from the hardware
+ * therefore set driver specific default values
+ *
+ * srf02 don't have a default value so it'll be omitted
+ */
+ ret = srf08_write_sensitivity(data,
+ data->chip_info->sensitivity_default);
+ if (ret < 0)
+ return ret;
+ }
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct of_device_id of_srf08_match[] = {
+ { .compatible = "devantech,srf02", (void *)SRF02},
+ { .compatible = "devantech,srf08", (void *)SRF08},
+ { .compatible = "devantech,srf10", (void *)SRF10},
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, of_srf08_match);
+
+static const struct i2c_device_id srf08_id[] = {
+ { "srf02", SRF02 },
+ { "srf08", SRF08 },
+ { "srf10", SRF10 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, srf08_id);
+
+static struct i2c_driver srf08_driver = {
+ .driver = {
+ .name = "srf08",
+ .of_match_table = of_srf08_match,
+ },
+ .probe = srf08_probe,
+ .id_table = srf08_id,
+};
+module_i2c_driver(srf08_driver);
+
+MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>");
+MODULE_DESCRIPTION("Devantech SRF02/SRF08/SRF10 i2c ultrasonic ranger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/proximity/sx9310.c b/drivers/iio/proximity/sx9310.c
new file mode 100644
index 000000000..6d3f4ab8c
--- /dev/null
+++ b/drivers/iio/proximity/sx9310.c
@@ -0,0 +1,1075 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2018 Google LLC.
+ *
+ * Driver for Semtech's SX9310/SX9311 capacitive proximity/button solution.
+ * Based on SX9500 driver and Semtech driver using the input framework
+ * <https://my.syncplicity.com/share/teouwsim8niiaud/
+ * linux-driver-SX9310_NoSmartHSensing>.
+ * Reworked in April 2019 by Evan Green <evgreen@chromium.org>
+ * and in January 2020 by Daniel Campello <campello@chromium.org>.
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
+/* Register definitions. */
+#define SX9310_REG_IRQ_SRC 0x00
+#define SX9310_REG_STAT0 0x01
+#define SX9310_REG_STAT1 0x02
+#define SX9310_REG_STAT1_COMPSTAT_MASK GENMASK(3, 0)
+#define SX9310_REG_IRQ_MSK 0x03
+#define SX9310_CONVDONE_IRQ BIT(3)
+#define SX9310_FAR_IRQ BIT(5)
+#define SX9310_CLOSE_IRQ BIT(6)
+#define SX9310_REG_IRQ_FUNC 0x04
+
+#define SX9310_REG_PROX_CTRL0 0x10
+#define SX9310_REG_PROX_CTRL0_SENSOREN_MASK GENMASK(3, 0)
+#define SX9310_REG_PROX_CTRL0_SCANPERIOD_MASK GENMASK(7, 4)
+#define SX9310_REG_PROX_CTRL0_SCANPERIOD_15MS 0x01
+#define SX9310_REG_PROX_CTRL1 0x11
+#define SX9310_REG_PROX_CTRL2 0x12
+#define SX9310_REG_PROX_CTRL2_COMBMODE_CS1_CS2 (0x02 << 6)
+#define SX9310_REG_PROX_CTRL2_SHIELDEN_DYNAMIC (0x01 << 2)
+#define SX9310_REG_PROX_CTRL3 0x13
+#define SX9310_REG_PROX_CTRL3_GAIN0_X8 (0x03 << 2)
+#define SX9310_REG_PROX_CTRL3_GAIN12_X4 0x02
+#define SX9310_REG_PROX_CTRL4 0x14
+#define SX9310_REG_PROX_CTRL4_RESOLUTION_FINEST 0x07
+#define SX9310_REG_PROX_CTRL5 0x15
+#define SX9310_REG_PROX_CTRL5_RANGE_SMALL (0x03 << 6)
+#define SX9310_REG_PROX_CTRL5_STARTUPSENS_CS1 (0x01 << 2)
+#define SX9310_REG_PROX_CTRL5_RAWFILT_1P25 0x02
+#define SX9310_REG_PROX_CTRL6 0x16
+#define SX9310_REG_PROX_CTRL6_AVGTHRESH_DEFAULT 0x20
+#define SX9310_REG_PROX_CTRL7 0x17
+#define SX9310_REG_PROX_CTRL7_AVGNEGFILT_2 (0x01 << 3)
+#define SX9310_REG_PROX_CTRL7_AVGPOSFILT_512 0x05
+#define SX9310_REG_PROX_CTRL8 0x18
+#define SX9310_REG_PROX_CTRL9 0x19
+#define SX9310_REG_PROX_CTRL8_9_PTHRESH_28 (0x08 << 3)
+#define SX9310_REG_PROX_CTRL8_9_PTHRESH_96 (0x11 << 3)
+#define SX9310_REG_PROX_CTRL8_9_BODYTHRESH_900 0x03
+#define SX9310_REG_PROX_CTRL8_9_BODYTHRESH_1500 0x05
+#define SX9310_REG_PROX_CTRL10 0x1a
+#define SX9310_REG_PROX_CTRL10_HYST_6PCT (0x01 << 4)
+#define SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_2 0x01
+#define SX9310_REG_PROX_CTRL11 0x1b
+#define SX9310_REG_PROX_CTRL12 0x1c
+#define SX9310_REG_PROX_CTRL13 0x1d
+#define SX9310_REG_PROX_CTRL14 0x1e
+#define SX9310_REG_PROX_CTRL15 0x1f
+#define SX9310_REG_PROX_CTRL16 0x20
+#define SX9310_REG_PROX_CTRL17 0x21
+#define SX9310_REG_PROX_CTRL18 0x22
+#define SX9310_REG_PROX_CTRL19 0x23
+#define SX9310_REG_SAR_CTRL0 0x2a
+#define SX9310_REG_SAR_CTRL0_SARDEB_4_SAMPLES (0x02 << 5)
+#define SX9310_REG_SAR_CTRL0_SARHYST_8 (0x02 << 3)
+#define SX9310_REG_SAR_CTRL1 0x2b
+/* Each increment of the slope register is 0.0078125. */
+#define SX9310_REG_SAR_CTRL1_SLOPE(_hnslope) (_hnslope / 78125)
+#define SX9310_REG_SAR_CTRL2 0x2c
+#define SX9310_REG_SAR_CTRL2_SAROFFSET_DEFAULT 0x3c
+
+#define SX9310_REG_SENSOR_SEL 0x30
+#define SX9310_REG_USE_MSB 0x31
+#define SX9310_REG_USE_LSB 0x32
+#define SX9310_REG_AVG_MSB 0x33
+#define SX9310_REG_AVG_LSB 0x34
+#define SX9310_REG_DIFF_MSB 0x35
+#define SX9310_REG_DIFF_LSB 0x36
+#define SX9310_REG_OFFSET_MSB 0x37
+#define SX9310_REG_OFFSET_LSB 0x38
+#define SX9310_REG_SAR_MSB 0x39
+#define SX9310_REG_SAR_LSB 0x3a
+#define SX9310_REG_I2C_ADDR 0x40
+#define SX9310_REG_PAUSE 0x41
+#define SX9310_REG_WHOAMI 0x42
+#define SX9310_WHOAMI_VALUE 0x01
+#define SX9311_WHOAMI_VALUE 0x02
+#define SX9310_REG_RESET 0x7f
+#define SX9310_SOFT_RESET 0xde
+
+
+/* 4 hardware channels, as defined in STAT0: COMB, CS2, CS1 and CS0. */
+#define SX9310_NUM_CHANNELS 4
+static_assert(SX9310_NUM_CHANNELS < BITS_PER_LONG);
+
+struct sx9310_data {
+ /* Serialize access to registers and channel configuration */
+ struct mutex mutex;
+ struct i2c_client *client;
+ struct iio_trigger *trig;
+ struct regmap *regmap;
+ struct regulator_bulk_data supplies[2];
+ /*
+ * Last reading of the proximity status for each channel.
+ * We only send an event to user space when this changes.
+ */
+ unsigned long chan_prox_stat;
+ bool trigger_enabled;
+ /* Ensure correct alignment of timestamp when present. */
+ struct {
+ __be16 channels[SX9310_NUM_CHANNELS];
+ s64 ts __aligned(8);
+ } buffer;
+ /* Remember enabled channels and sample rate during suspend. */
+ unsigned int suspend_ctrl0;
+ struct completion completion;
+ unsigned long chan_read;
+ unsigned long chan_event;
+ unsigned int whoami;
+};
+
+static const struct iio_event_spec sx9310_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_EITHER,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+#define SX9310_NAMED_CHANNEL(idx, name) \
+ { \
+ .type = IIO_PROXIMITY, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .indexed = 1, \
+ .channel = idx, \
+ .extend_name = name, \
+ .address = SX9310_REG_DIFF_MSB, \
+ .event_spec = sx9310_events, \
+ .num_event_specs = ARRAY_SIZE(sx9310_events), \
+ .scan_index = idx, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 12, \
+ .storagebits = 16, \
+ .endianness = IIO_BE, \
+ }, \
+ }
+#define SX9310_CHANNEL(idx) SX9310_NAMED_CHANNEL(idx, NULL)
+
+static const struct iio_chan_spec sx9310_channels[] = {
+ SX9310_CHANNEL(0), /* CS0 */
+ SX9310_CHANNEL(1), /* CS1 */
+ SX9310_CHANNEL(2), /* CS2 */
+ SX9310_NAMED_CHANNEL(3, "comb"), /* COMB */
+
+ IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+/*
+ * Each entry contains the integer part (val) and the fractional part, in micro
+ * seconds. It conforms to the IIO output IIO_VAL_INT_PLUS_MICRO.
+ */
+static const struct {
+ int val;
+ int val2;
+} sx9310_samp_freq_table[] = {
+ { 500, 0 }, /* 0000: Min (no idle time) */
+ { 66, 666666 }, /* 0001: 15 ms */
+ { 33, 333333 }, /* 0010: 30 ms (Typ.) */
+ { 22, 222222 }, /* 0011: 45 ms */
+ { 16, 666666 }, /* 0100: 60 ms */
+ { 11, 111111 }, /* 0101: 90 ms */
+ { 8, 333333 }, /* 0110: 120 ms */
+ { 5, 0 }, /* 0111: 200 ms */
+ { 2, 500000 }, /* 1000: 400 ms */
+ { 1, 666666 }, /* 1001: 600 ms */
+ { 1, 250000 }, /* 1010: 800 ms */
+ { 1, 0 }, /* 1011: 1 s */
+ { 0, 500000 }, /* 1100: 2 s */
+ { 0, 333333 }, /* 1101: 3 s */
+ { 0, 250000 }, /* 1110: 4 s */
+ { 0, 200000 }, /* 1111: 5 s */
+};
+static const unsigned int sx9310_scan_period_table[] = {
+ 2, 15, 30, 45, 60, 90, 120, 200,
+ 400, 600, 800, 1000, 2000, 3000, 4000, 5000,
+};
+
+static ssize_t sx9310_show_samp_freq_avail(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ size_t len = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sx9310_samp_freq_table); i++)
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%d ",
+ sx9310_samp_freq_table[i].val,
+ sx9310_samp_freq_table[i].val2);
+ buf[len - 1] = '\n';
+ return len;
+}
+static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(sx9310_show_samp_freq_avail);
+
+static const struct regmap_range sx9310_writable_reg_ranges[] = {
+ regmap_reg_range(SX9310_REG_IRQ_MSK, SX9310_REG_IRQ_FUNC),
+ regmap_reg_range(SX9310_REG_PROX_CTRL0, SX9310_REG_PROX_CTRL19),
+ regmap_reg_range(SX9310_REG_SAR_CTRL0, SX9310_REG_SAR_CTRL2),
+ regmap_reg_range(SX9310_REG_SENSOR_SEL, SX9310_REG_SENSOR_SEL),
+ regmap_reg_range(SX9310_REG_OFFSET_MSB, SX9310_REG_OFFSET_LSB),
+ regmap_reg_range(SX9310_REG_PAUSE, SX9310_REG_PAUSE),
+ regmap_reg_range(SX9310_REG_RESET, SX9310_REG_RESET),
+};
+
+static const struct regmap_access_table sx9310_writeable_regs = {
+ .yes_ranges = sx9310_writable_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(sx9310_writable_reg_ranges),
+};
+
+static const struct regmap_range sx9310_readable_reg_ranges[] = {
+ regmap_reg_range(SX9310_REG_IRQ_SRC, SX9310_REG_IRQ_FUNC),
+ regmap_reg_range(SX9310_REG_PROX_CTRL0, SX9310_REG_PROX_CTRL19),
+ regmap_reg_range(SX9310_REG_SAR_CTRL0, SX9310_REG_SAR_CTRL2),
+ regmap_reg_range(SX9310_REG_SENSOR_SEL, SX9310_REG_SAR_LSB),
+ regmap_reg_range(SX9310_REG_I2C_ADDR, SX9310_REG_WHOAMI),
+ regmap_reg_range(SX9310_REG_RESET, SX9310_REG_RESET),
+};
+
+static const struct regmap_access_table sx9310_readable_regs = {
+ .yes_ranges = sx9310_readable_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(sx9310_readable_reg_ranges),
+};
+
+static const struct regmap_range sx9310_volatile_reg_ranges[] = {
+ regmap_reg_range(SX9310_REG_IRQ_SRC, SX9310_REG_STAT1),
+ regmap_reg_range(SX9310_REG_USE_MSB, SX9310_REG_DIFF_LSB),
+ regmap_reg_range(SX9310_REG_SAR_MSB, SX9310_REG_SAR_LSB),
+ regmap_reg_range(SX9310_REG_RESET, SX9310_REG_RESET),
+};
+
+static const struct regmap_access_table sx9310_volatile_regs = {
+ .yes_ranges = sx9310_volatile_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(sx9310_volatile_reg_ranges),
+};
+
+static const struct regmap_config sx9310_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = SX9310_REG_RESET,
+ .cache_type = REGCACHE_RBTREE,
+
+ .wr_table = &sx9310_writeable_regs,
+ .rd_table = &sx9310_readable_regs,
+ .volatile_table = &sx9310_volatile_regs,
+};
+
+static int sx9310_update_chan_en(struct sx9310_data *data,
+ unsigned long chan_read,
+ unsigned long chan_event)
+{
+ int ret;
+ unsigned long channels = chan_read | chan_event;
+
+ if ((data->chan_read | data->chan_event) != channels) {
+ ret = regmap_update_bits(data->regmap, SX9310_REG_PROX_CTRL0,
+ SX9310_REG_PROX_CTRL0_SENSOREN_MASK,
+ channels);
+ if (ret)
+ return ret;
+ }
+ data->chan_read = chan_read;
+ data->chan_event = chan_event;
+ return 0;
+}
+
+static int sx9310_get_read_channel(struct sx9310_data *data, int channel)
+{
+ return sx9310_update_chan_en(data, data->chan_read | BIT(channel),
+ data->chan_event);
+}
+
+static int sx9310_put_read_channel(struct sx9310_data *data, int channel)
+{
+ return sx9310_update_chan_en(data, data->chan_read & ~BIT(channel),
+ data->chan_event);
+}
+
+static int sx9310_get_event_channel(struct sx9310_data *data, int channel)
+{
+ return sx9310_update_chan_en(data, data->chan_read,
+ data->chan_event | BIT(channel));
+}
+
+static int sx9310_put_event_channel(struct sx9310_data *data, int channel)
+{
+ return sx9310_update_chan_en(data, data->chan_read,
+ data->chan_event & ~BIT(channel));
+}
+
+static int sx9310_enable_irq(struct sx9310_data *data, unsigned int irq)
+{
+ if (!data->client->irq)
+ return 0;
+ return regmap_update_bits(data->regmap, SX9310_REG_IRQ_MSK, irq, irq);
+}
+
+static int sx9310_disable_irq(struct sx9310_data *data, unsigned int irq)
+{
+ if (!data->client->irq)
+ return 0;
+ return regmap_update_bits(data->regmap, SX9310_REG_IRQ_MSK, irq, 0);
+}
+
+static int sx9310_read_prox_data(struct sx9310_data *data,
+ const struct iio_chan_spec *chan, __be16 *val)
+{
+ int ret;
+
+ ret = regmap_write(data->regmap, SX9310_REG_SENSOR_SEL, chan->channel);
+ if (ret)
+ return ret;
+
+ return regmap_bulk_read(data->regmap, chan->address, val, sizeof(*val));
+}
+
+/*
+ * If we have no interrupt support, we have to wait for a scan period
+ * after enabling a channel to get a result.
+ */
+static int sx9310_wait_for_sample(struct sx9310_data *data)
+{
+ int ret;
+ unsigned int val;
+
+ ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL0, &val);
+ if (ret)
+ return ret;
+
+ val = FIELD_GET(SX9310_REG_PROX_CTRL0_SCANPERIOD_MASK, val);
+
+ msleep(sx9310_scan_period_table[val]);
+
+ return 0;
+}
+
+static int sx9310_read_proximity(struct sx9310_data *data,
+ const struct iio_chan_spec *chan, int *val)
+{
+ int ret;
+ __be16 rawval;
+
+ mutex_lock(&data->mutex);
+
+ ret = sx9310_get_read_channel(data, chan->channel);
+ if (ret)
+ goto out;
+
+ ret = sx9310_enable_irq(data, SX9310_CONVDONE_IRQ);
+ if (ret)
+ goto out_put_channel;
+
+ mutex_unlock(&data->mutex);
+
+ if (data->client->irq) {
+ ret = wait_for_completion_interruptible(&data->completion);
+ reinit_completion(&data->completion);
+ } else {
+ ret = sx9310_wait_for_sample(data);
+ }
+
+ mutex_lock(&data->mutex);
+
+ if (ret)
+ goto out_disable_irq;
+
+ ret = sx9310_read_prox_data(data, chan, &rawval);
+ if (ret)
+ goto out_disable_irq;
+
+ *val = sign_extend32(be16_to_cpu(rawval),
+ chan->address == SX9310_REG_DIFF_MSB ? 11 : 15);
+
+ ret = sx9310_disable_irq(data, SX9310_CONVDONE_IRQ);
+ if (ret)
+ goto out_put_channel;
+
+ ret = sx9310_put_read_channel(data, chan->channel);
+ if (ret)
+ goto out;
+
+ mutex_unlock(&data->mutex);
+
+ return IIO_VAL_INT;
+
+out_disable_irq:
+ sx9310_disable_irq(data, SX9310_CONVDONE_IRQ);
+out_put_channel:
+ sx9310_put_read_channel(data, chan->channel);
+out:
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int sx9310_read_samp_freq(struct sx9310_data *data, int *val, int *val2)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL0, &regval);
+ if (ret)
+ return ret;
+
+ regval = FIELD_GET(SX9310_REG_PROX_CTRL0_SCANPERIOD_MASK, regval);
+ *val = sx9310_samp_freq_table[regval].val;
+ *val2 = sx9310_samp_freq_table[regval].val2;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int sx9310_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan, int *val,
+ int *val2, long mask)
+{
+ struct sx9310_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (chan->type != IIO_PROXIMITY)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = sx9310_read_proximity(data, chan, val);
+ iio_device_release_direct_mode(indio_dev);
+ return ret;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return sx9310_read_samp_freq(data, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int sx9310_set_samp_freq(struct sx9310_data *data, int val, int val2)
+{
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(sx9310_samp_freq_table); i++)
+ if (val == sx9310_samp_freq_table[i].val &&
+ val2 == sx9310_samp_freq_table[i].val2)
+ break;
+
+ if (i == ARRAY_SIZE(sx9310_samp_freq_table))
+ return -EINVAL;
+
+ mutex_lock(&data->mutex);
+
+ ret = regmap_update_bits(
+ data->regmap, SX9310_REG_PROX_CTRL0,
+ SX9310_REG_PROX_CTRL0_SCANPERIOD_MASK,
+ FIELD_PREP(SX9310_REG_PROX_CTRL0_SCANPERIOD_MASK, i));
+
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int sx9310_write_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan, int val, int val2,
+ long mask)
+{
+ struct sx9310_data *data = iio_priv(indio_dev);
+
+ if (chan->type != IIO_PROXIMITY)
+ return -EINVAL;
+
+ if (mask != IIO_CHAN_INFO_SAMP_FREQ)
+ return -EINVAL;
+
+ return sx9310_set_samp_freq(data, val, val2);
+}
+
+static irqreturn_t sx9310_irq_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct sx9310_data *data = iio_priv(indio_dev);
+
+ if (data->trigger_enabled)
+ iio_trigger_poll(data->trig);
+
+ /*
+ * Even if no event is enabled, we need to wake the thread to clear the
+ * interrupt state by reading SX9310_REG_IRQ_SRC.
+ * It is not possible to do that here because regmap_read takes a mutex.
+ */
+ return IRQ_WAKE_THREAD;
+}
+
+static void sx9310_push_events(struct iio_dev *indio_dev)
+{
+ int ret;
+ unsigned int val, chan;
+ struct sx9310_data *data = iio_priv(indio_dev);
+ s64 timestamp = iio_get_time_ns(indio_dev);
+ unsigned long prox_changed;
+
+ /* Read proximity state on all channels */
+ ret = regmap_read(data->regmap, SX9310_REG_STAT0, &val);
+ if (ret) {
+ dev_err(&data->client->dev, "i2c transfer error in irq\n");
+ return;
+ }
+
+ /*
+ * Only iterate over channels with changes on proximity status that have
+ * events enabled.
+ */
+ prox_changed = (data->chan_prox_stat ^ val) & data->chan_event;
+
+ for_each_set_bit(chan, &prox_changed, SX9310_NUM_CHANNELS) {
+ int dir;
+ u64 ev;
+
+ dir = (val & BIT(chan)) ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING;
+ ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, chan,
+ IIO_EV_TYPE_THRESH, dir);
+
+ iio_push_event(indio_dev, ev, timestamp);
+ }
+ data->chan_prox_stat = val;
+}
+
+static irqreturn_t sx9310_irq_thread_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct sx9310_data *data = iio_priv(indio_dev);
+ int ret;
+ unsigned int val;
+
+ mutex_lock(&data->mutex);
+
+ ret = regmap_read(data->regmap, SX9310_REG_IRQ_SRC, &val);
+ if (ret) {
+ dev_err(&data->client->dev, "i2c transfer error in irq\n");
+ goto out;
+ }
+
+ if (val & (SX9310_FAR_IRQ | SX9310_CLOSE_IRQ))
+ sx9310_push_events(indio_dev);
+
+ if (val & SX9310_CONVDONE_IRQ)
+ complete(&data->completion);
+
+out:
+ mutex_unlock(&data->mutex);
+
+ return IRQ_HANDLED;
+}
+
+static int sx9310_read_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ struct sx9310_data *data = iio_priv(indio_dev);
+
+ return !!(data->chan_event & BIT(chan->channel));
+}
+
+static int sx9310_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir, int state)
+{
+ struct sx9310_data *data = iio_priv(indio_dev);
+ unsigned int eventirq = SX9310_FAR_IRQ | SX9310_CLOSE_IRQ;
+ int ret;
+
+ /* If the state hasn't changed, there's nothing to do. */
+ if (!!(data->chan_event & BIT(chan->channel)) == state)
+ return 0;
+
+ mutex_lock(&data->mutex);
+ if (state) {
+ ret = sx9310_get_event_channel(data, chan->channel);
+ if (ret)
+ goto out_unlock;
+ if (!(data->chan_event & ~BIT(chan->channel))) {
+ ret = sx9310_enable_irq(data, eventirq);
+ if (ret)
+ sx9310_put_event_channel(data, chan->channel);
+ }
+ } else {
+ ret = sx9310_put_event_channel(data, chan->channel);
+ if (ret)
+ goto out_unlock;
+ if (!data->chan_event) {
+ ret = sx9310_disable_irq(data, eventirq);
+ if (ret)
+ sx9310_get_event_channel(data, chan->channel);
+ }
+ }
+
+out_unlock:
+ mutex_unlock(&data->mutex);
+ return ret;
+}
+
+static struct attribute *sx9310_attributes[] = {
+ &iio_dev_attr_sampling_frequency_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group sx9310_attribute_group = {
+ .attrs = sx9310_attributes,
+};
+
+static const struct iio_info sx9310_info = {
+ .attrs = &sx9310_attribute_group,
+ .read_raw = sx9310_read_raw,
+ .write_raw = sx9310_write_raw,
+ .read_event_config = sx9310_read_event_config,
+ .write_event_config = sx9310_write_event_config,
+};
+
+static int sx9310_set_trigger_state(struct iio_trigger *trig, bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct sx9310_data *data = iio_priv(indio_dev);
+ int ret = 0;
+
+ mutex_lock(&data->mutex);
+
+ if (state)
+ ret = sx9310_enable_irq(data, SX9310_CONVDONE_IRQ);
+ else if (!data->chan_read)
+ ret = sx9310_disable_irq(data, SX9310_CONVDONE_IRQ);
+ if (ret)
+ goto out;
+
+ data->trigger_enabled = state;
+
+out:
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static const struct iio_trigger_ops sx9310_trigger_ops = {
+ .set_trigger_state = sx9310_set_trigger_state,
+};
+
+static irqreturn_t sx9310_trigger_handler(int irq, void *private)
+{
+ struct iio_poll_func *pf = private;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct sx9310_data *data = iio_priv(indio_dev);
+ __be16 val;
+ int bit, ret, i = 0;
+
+ mutex_lock(&data->mutex);
+
+ for_each_set_bit(bit, indio_dev->active_scan_mask,
+ indio_dev->masklength) {
+ ret = sx9310_read_prox_data(data, &indio_dev->channels[bit],
+ &val);
+ if (ret)
+ goto out;
+
+ data->buffer.channels[i++] = val;
+ }
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer,
+ pf->timestamp);
+
+out:
+ mutex_unlock(&data->mutex);
+
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int sx9310_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct sx9310_data *data = iio_priv(indio_dev);
+ unsigned long channels = 0;
+ int bit, ret;
+
+ mutex_lock(&data->mutex);
+ for_each_set_bit(bit, indio_dev->active_scan_mask,
+ indio_dev->masklength)
+ __set_bit(indio_dev->channels[bit].channel, &channels);
+
+ ret = sx9310_update_chan_en(data, channels, data->chan_event);
+ mutex_unlock(&data->mutex);
+ return ret;
+}
+
+static int sx9310_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct sx9310_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = sx9310_update_chan_en(data, 0, data->chan_event);
+ mutex_unlock(&data->mutex);
+ return ret;
+}
+
+static const struct iio_buffer_setup_ops sx9310_buffer_setup_ops = {
+ .preenable = sx9310_buffer_preenable,
+ .postdisable = sx9310_buffer_postdisable,
+};
+
+struct sx9310_reg_default {
+ u8 reg;
+ u8 def;
+};
+
+static const struct sx9310_reg_default sx9310_default_regs[] = {
+ { SX9310_REG_IRQ_MSK, 0x00 },
+ { SX9310_REG_IRQ_FUNC, 0x00 },
+ /*
+ * The lower 4 bits should not be set as it enable sensors measurements.
+ * Turning the detection on before the configuration values are set to
+ * good values can cause the device to return erroneous readings.
+ */
+ { SX9310_REG_PROX_CTRL0, SX9310_REG_PROX_CTRL0_SCANPERIOD_15MS },
+ { SX9310_REG_PROX_CTRL1, 0x00 },
+ { SX9310_REG_PROX_CTRL2, SX9310_REG_PROX_CTRL2_COMBMODE_CS1_CS2 |
+ SX9310_REG_PROX_CTRL2_SHIELDEN_DYNAMIC },
+ { SX9310_REG_PROX_CTRL3, SX9310_REG_PROX_CTRL3_GAIN0_X8 |
+ SX9310_REG_PROX_CTRL3_GAIN12_X4 },
+ { SX9310_REG_PROX_CTRL4, SX9310_REG_PROX_CTRL4_RESOLUTION_FINEST },
+ { SX9310_REG_PROX_CTRL5, SX9310_REG_PROX_CTRL5_RANGE_SMALL |
+ SX9310_REG_PROX_CTRL5_STARTUPSENS_CS1 |
+ SX9310_REG_PROX_CTRL5_RAWFILT_1P25 },
+ { SX9310_REG_PROX_CTRL6, SX9310_REG_PROX_CTRL6_AVGTHRESH_DEFAULT },
+ { SX9310_REG_PROX_CTRL7, SX9310_REG_PROX_CTRL7_AVGNEGFILT_2 |
+ SX9310_REG_PROX_CTRL7_AVGPOSFILT_512 },
+ { SX9310_REG_PROX_CTRL8, SX9310_REG_PROX_CTRL8_9_PTHRESH_96 |
+ SX9310_REG_PROX_CTRL8_9_BODYTHRESH_1500 },
+ { SX9310_REG_PROX_CTRL9, SX9310_REG_PROX_CTRL8_9_PTHRESH_28 |
+ SX9310_REG_PROX_CTRL8_9_BODYTHRESH_900 },
+ { SX9310_REG_PROX_CTRL10, SX9310_REG_PROX_CTRL10_HYST_6PCT |
+ SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_2 },
+ { SX9310_REG_PROX_CTRL11, 0x00 },
+ { SX9310_REG_PROX_CTRL12, 0x00 },
+ { SX9310_REG_PROX_CTRL13, 0x00 },
+ { SX9310_REG_PROX_CTRL14, 0x00 },
+ { SX9310_REG_PROX_CTRL15, 0x00 },
+ { SX9310_REG_PROX_CTRL16, 0x00 },
+ { SX9310_REG_PROX_CTRL17, 0x00 },
+ { SX9310_REG_PROX_CTRL18, 0x00 },
+ { SX9310_REG_PROX_CTRL19, 0x00 },
+ { SX9310_REG_SAR_CTRL0, SX9310_REG_SAR_CTRL0_SARDEB_4_SAMPLES |
+ SX9310_REG_SAR_CTRL0_SARHYST_8 },
+ { SX9310_REG_SAR_CTRL1, SX9310_REG_SAR_CTRL1_SLOPE(10781250) },
+ { SX9310_REG_SAR_CTRL2, SX9310_REG_SAR_CTRL2_SAROFFSET_DEFAULT },
+};
+
+/* Activate all channels and perform an initial compensation. */
+static int sx9310_init_compensation(struct iio_dev *indio_dev)
+{
+ struct sx9310_data *data = iio_priv(indio_dev);
+ int ret;
+ unsigned int val;
+ unsigned int ctrl0;
+
+ ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL0, &ctrl0);
+ if (ret)
+ return ret;
+
+ /* run the compensation phase on all channels */
+ ret = regmap_write(data->regmap, SX9310_REG_PROX_CTRL0,
+ ctrl0 | SX9310_REG_PROX_CTRL0_SENSOREN_MASK);
+ if (ret)
+ return ret;
+
+ ret = regmap_read_poll_timeout(data->regmap, SX9310_REG_STAT1, val,
+ !(val & SX9310_REG_STAT1_COMPSTAT_MASK),
+ 20000, 2000000);
+ if (ret) {
+ if (ret == -ETIMEDOUT)
+ dev_err(&data->client->dev,
+ "initial compensation timed out: 0x%02x\n",
+ val);
+ return ret;
+ }
+
+ regmap_write(data->regmap, SX9310_REG_PROX_CTRL0, ctrl0);
+ return ret;
+}
+
+static int sx9310_init_device(struct iio_dev *indio_dev)
+{
+ struct sx9310_data *data = iio_priv(indio_dev);
+ const struct sx9310_reg_default *initval;
+ int ret;
+ unsigned int i, val;
+
+ ret = regmap_write(data->regmap, SX9310_REG_RESET, SX9310_SOFT_RESET);
+ if (ret)
+ return ret;
+
+ usleep_range(1000, 2000); /* power-up time is ~1ms. */
+
+ /* Clear reset interrupt state by reading SX9310_REG_IRQ_SRC. */
+ ret = regmap_read(data->regmap, SX9310_REG_IRQ_SRC, &val);
+ if (ret)
+ return ret;
+
+ /* Program some sane defaults. */
+ for (i = 0; i < ARRAY_SIZE(sx9310_default_regs); i++) {
+ initval = &sx9310_default_regs[i];
+ ret = regmap_write(data->regmap, initval->reg, initval->def);
+ if (ret)
+ return ret;
+ }
+
+ return sx9310_init_compensation(indio_dev);
+}
+
+static int sx9310_set_indio_dev_name(struct device *dev,
+ struct iio_dev *indio_dev,
+ unsigned int whoami)
+{
+ unsigned int long ddata;
+
+ ddata = (uintptr_t)device_get_match_data(dev);
+ if (ddata != whoami) {
+ dev_err(dev, "WHOAMI does not match device data: %u\n", whoami);
+ return -ENODEV;
+ }
+
+ switch (whoami) {
+ case SX9310_WHOAMI_VALUE:
+ indio_dev->name = "sx9310";
+ break;
+ case SX9311_WHOAMI_VALUE:
+ indio_dev->name = "sx9311";
+ break;
+ default:
+ dev_err(dev, "unexpected WHOAMI response: %u\n", whoami);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void sx9310_regulator_disable(void *_data)
+{
+ struct sx9310_data *data = _data;
+
+ regulator_bulk_disable(ARRAY_SIZE(data->supplies), data->supplies);
+}
+
+static int sx9310_probe(struct i2c_client *client)
+{
+ int ret;
+ struct device *dev = &client->dev;
+ struct iio_dev *indio_dev;
+ struct sx9310_data *data;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ data->supplies[0].supply = "vdd";
+ data->supplies[1].supply = "svdd";
+ mutex_init(&data->mutex);
+ init_completion(&data->completion);
+
+ data->regmap = devm_regmap_init_i2c(client, &sx9310_regmap_config);
+ if (IS_ERR(data->regmap))
+ return PTR_ERR(data->regmap);
+
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->supplies),
+ data->supplies);
+ if (ret)
+ return ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(data->supplies), data->supplies);
+ if (ret)
+ return ret;
+ /* Must wait for Tpor time after initial power up */
+ usleep_range(1000, 1100);
+
+ ret = devm_add_action_or_reset(dev, sx9310_regulator_disable, data);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(data->regmap, SX9310_REG_WHOAMI, &data->whoami);
+ if (ret) {
+ dev_err(dev, "error in reading WHOAMI register: %d", ret);
+ return ret;
+ }
+
+ ret = sx9310_set_indio_dev_name(dev, indio_dev, data->whoami);
+ if (ret)
+ return ret;
+
+ ACPI_COMPANION_SET(&indio_dev->dev, ACPI_COMPANION(dev));
+ indio_dev->channels = sx9310_channels;
+ indio_dev->num_channels = ARRAY_SIZE(sx9310_channels);
+ indio_dev->info = &sx9310_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ i2c_set_clientdata(client, indio_dev);
+
+ ret = sx9310_init_device(indio_dev);
+ if (ret)
+ return ret;
+
+ if (client->irq) {
+ ret = devm_request_threaded_irq(dev, client->irq,
+ sx9310_irq_handler,
+ sx9310_irq_thread_handler,
+ IRQF_ONESHOT,
+ "sx9310_event", indio_dev);
+ if (ret)
+ return ret;
+
+ data->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
+ indio_dev->name,
+ indio_dev->id);
+ if (!data->trig)
+ return -ENOMEM;
+
+ data->trig->dev.parent = dev;
+ data->trig->ops = &sx9310_trigger_ops;
+ iio_trigger_set_drvdata(data->trig, indio_dev);
+
+ ret = devm_iio_trigger_register(dev, data->trig);
+ if (ret)
+ return ret;
+ }
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ sx9310_trigger_handler,
+ &sx9310_buffer_setup_ops);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static int __maybe_unused sx9310_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct sx9310_data *data = iio_priv(indio_dev);
+ u8 ctrl0;
+ int ret;
+
+ disable_irq_nosync(data->client->irq);
+
+ mutex_lock(&data->mutex);
+ ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL0,
+ &data->suspend_ctrl0);
+ if (ret)
+ goto out;
+
+ ctrl0 = data->suspend_ctrl0 & ~SX9310_REG_PROX_CTRL0_SENSOREN_MASK;
+ ret = regmap_write(data->regmap, SX9310_REG_PROX_CTRL0, ctrl0);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(data->regmap, SX9310_REG_PAUSE, 0);
+
+out:
+ mutex_unlock(&data->mutex);
+ return ret;
+}
+
+static int __maybe_unused sx9310_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct sx9310_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = regmap_write(data->regmap, SX9310_REG_PAUSE, 1);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(data->regmap, SX9310_REG_PROX_CTRL0,
+ data->suspend_ctrl0);
+
+out:
+ mutex_unlock(&data->mutex);
+ if (ret)
+ return ret;
+
+ enable_irq(data->client->irq);
+ return 0;
+}
+
+static const struct dev_pm_ops sx9310_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(sx9310_suspend, sx9310_resume)
+};
+
+static const struct acpi_device_id sx9310_acpi_match[] = {
+ { "STH9310", SX9310_WHOAMI_VALUE },
+ { "STH9311", SX9311_WHOAMI_VALUE },
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, sx9310_acpi_match);
+
+static const struct of_device_id sx9310_of_match[] = {
+ { .compatible = "semtech,sx9310", (void *)SX9310_WHOAMI_VALUE },
+ { .compatible = "semtech,sx9311", (void *)SX9311_WHOAMI_VALUE },
+ {}
+};
+MODULE_DEVICE_TABLE(of, sx9310_of_match);
+
+static const struct i2c_device_id sx9310_id[] = {
+ { "sx9310", SX9310_WHOAMI_VALUE },
+ { "sx9311", SX9311_WHOAMI_VALUE },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, sx9310_id);
+
+static struct i2c_driver sx9310_driver = {
+ .driver = {
+ .name = "sx9310",
+ .acpi_match_table = sx9310_acpi_match,
+ .of_match_table = sx9310_of_match,
+ .pm = &sx9310_pm_ops,
+
+ /*
+ * Lots of i2c transfers in probe + over 200 ms waiting in
+ * sx9310_init_compensation() mean a slow probe; prefer async
+ * so we don't delay boot if we're builtin to the kernel.
+ */
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe_new = sx9310_probe,
+ .id_table = sx9310_id,
+};
+module_i2c_driver(sx9310_driver);
+
+MODULE_AUTHOR("Gwendal Grignou <gwendal@chromium.org>");
+MODULE_AUTHOR("Daniel Campello <campello@chromium.org>");
+MODULE_DESCRIPTION("Driver for Semtech SX9310/SX9311 proximity sensor");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/proximity/sx9500.c b/drivers/iio/proximity/sx9500.c
new file mode 100644
index 000000000..acb821cba
--- /dev/null
+++ b/drivers/iio/proximity/sx9500.c
@@ -0,0 +1,1074 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2014 Intel Corporation
+ *
+ * Driver for Semtech's SX9500 capacitive proximity/button solution.
+ * Datasheet available at
+ * <http://www.semtech.com/images/datasheet/sx9500.pdf>.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/acpi.h>
+#include <linux/gpio/consumer.h>
+#include <linux/regmap.h>
+#include <linux/pm.h>
+#include <linux/delay.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
+#define SX9500_DRIVER_NAME "sx9500"
+#define SX9500_IRQ_NAME "sx9500_event"
+
+/* Register definitions. */
+#define SX9500_REG_IRQ_SRC 0x00
+#define SX9500_REG_STAT 0x01
+#define SX9500_REG_IRQ_MSK 0x03
+
+#define SX9500_REG_PROX_CTRL0 0x06
+#define SX9500_REG_PROX_CTRL1 0x07
+#define SX9500_REG_PROX_CTRL2 0x08
+#define SX9500_REG_PROX_CTRL3 0x09
+#define SX9500_REG_PROX_CTRL4 0x0a
+#define SX9500_REG_PROX_CTRL5 0x0b
+#define SX9500_REG_PROX_CTRL6 0x0c
+#define SX9500_REG_PROX_CTRL7 0x0d
+#define SX9500_REG_PROX_CTRL8 0x0e
+
+#define SX9500_REG_SENSOR_SEL 0x20
+#define SX9500_REG_USE_MSB 0x21
+#define SX9500_REG_USE_LSB 0x22
+#define SX9500_REG_AVG_MSB 0x23
+#define SX9500_REG_AVG_LSB 0x24
+#define SX9500_REG_DIFF_MSB 0x25
+#define SX9500_REG_DIFF_LSB 0x26
+#define SX9500_REG_OFFSET_MSB 0x27
+#define SX9500_REG_OFFSET_LSB 0x28
+
+#define SX9500_REG_RESET 0x7f
+
+/* Write this to REG_RESET to do a soft reset. */
+#define SX9500_SOFT_RESET 0xde
+
+#define SX9500_SCAN_PERIOD_MASK GENMASK(6, 4)
+#define SX9500_SCAN_PERIOD_SHIFT 4
+
+/*
+ * These serve for identifying IRQ source in the IRQ_SRC register, and
+ * also for masking the IRQs in the IRQ_MSK register.
+ */
+#define SX9500_CLOSE_IRQ BIT(6)
+#define SX9500_FAR_IRQ BIT(5)
+#define SX9500_CONVDONE_IRQ BIT(3)
+
+#define SX9500_PROXSTAT_SHIFT 4
+#define SX9500_COMPSTAT_MASK GENMASK(3, 0)
+
+#define SX9500_NUM_CHANNELS 4
+#define SX9500_CHAN_MASK GENMASK(SX9500_NUM_CHANNELS - 1, 0)
+
+struct sx9500_data {
+ struct mutex mutex;
+ struct i2c_client *client;
+ struct iio_trigger *trig;
+ struct regmap *regmap;
+ struct gpio_desc *gpiod_rst;
+ /*
+ * Last reading of the proximity status for each channel. We
+ * only send an event to user space when this changes.
+ */
+ bool prox_stat[SX9500_NUM_CHANNELS];
+ bool event_enabled[SX9500_NUM_CHANNELS];
+ bool trigger_enabled;
+ u16 *buffer;
+ /* Remember enabled channels and sample rate during suspend. */
+ unsigned int suspend_ctrl0;
+ struct completion completion;
+ int data_rdy_users, close_far_users;
+ int channel_users[SX9500_NUM_CHANNELS];
+};
+
+static const struct iio_event_spec sx9500_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_EITHER,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+#define SX9500_CHANNEL(idx) \
+ { \
+ .type = IIO_PROXIMITY, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .indexed = 1, \
+ .channel = idx, \
+ .event_spec = sx9500_events, \
+ .num_event_specs = ARRAY_SIZE(sx9500_events), \
+ .scan_index = idx, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .shift = 0, \
+ }, \
+ }
+
+static const struct iio_chan_spec sx9500_channels[] = {
+ SX9500_CHANNEL(0),
+ SX9500_CHANNEL(1),
+ SX9500_CHANNEL(2),
+ SX9500_CHANNEL(3),
+ IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static const struct {
+ int val;
+ int val2;
+} sx9500_samp_freq_table[] = {
+ {33, 333333},
+ {16, 666666},
+ {11, 111111},
+ {8, 333333},
+ {6, 666666},
+ {5, 0},
+ {3, 333333},
+ {2, 500000},
+};
+
+static const unsigned int sx9500_scan_period_table[] = {
+ 30, 60, 90, 120, 150, 200, 300, 400,
+};
+
+static const struct regmap_range sx9500_writable_reg_ranges[] = {
+ regmap_reg_range(SX9500_REG_IRQ_MSK, SX9500_REG_IRQ_MSK),
+ regmap_reg_range(SX9500_REG_PROX_CTRL0, SX9500_REG_PROX_CTRL8),
+ regmap_reg_range(SX9500_REG_SENSOR_SEL, SX9500_REG_SENSOR_SEL),
+ regmap_reg_range(SX9500_REG_OFFSET_MSB, SX9500_REG_OFFSET_LSB),
+ regmap_reg_range(SX9500_REG_RESET, SX9500_REG_RESET),
+};
+
+static const struct regmap_access_table sx9500_writeable_regs = {
+ .yes_ranges = sx9500_writable_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(sx9500_writable_reg_ranges),
+};
+
+/*
+ * All allocated registers are readable, so we just list unallocated
+ * ones.
+ */
+static const struct regmap_range sx9500_non_readable_reg_ranges[] = {
+ regmap_reg_range(SX9500_REG_STAT + 1, SX9500_REG_STAT + 1),
+ regmap_reg_range(SX9500_REG_IRQ_MSK + 1, SX9500_REG_PROX_CTRL0 - 1),
+ regmap_reg_range(SX9500_REG_PROX_CTRL8 + 1, SX9500_REG_SENSOR_SEL - 1),
+ regmap_reg_range(SX9500_REG_OFFSET_LSB + 1, SX9500_REG_RESET - 1),
+};
+
+static const struct regmap_access_table sx9500_readable_regs = {
+ .no_ranges = sx9500_non_readable_reg_ranges,
+ .n_no_ranges = ARRAY_SIZE(sx9500_non_readable_reg_ranges),
+};
+
+static const struct regmap_range sx9500_volatile_reg_ranges[] = {
+ regmap_reg_range(SX9500_REG_IRQ_SRC, SX9500_REG_STAT),
+ regmap_reg_range(SX9500_REG_USE_MSB, SX9500_REG_OFFSET_LSB),
+ regmap_reg_range(SX9500_REG_RESET, SX9500_REG_RESET),
+};
+
+static const struct regmap_access_table sx9500_volatile_regs = {
+ .yes_ranges = sx9500_volatile_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(sx9500_volatile_reg_ranges),
+};
+
+static const struct regmap_config sx9500_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = SX9500_REG_RESET,
+ .cache_type = REGCACHE_RBTREE,
+
+ .wr_table = &sx9500_writeable_regs,
+ .rd_table = &sx9500_readable_regs,
+ .volatile_table = &sx9500_volatile_regs,
+};
+
+static int sx9500_inc_users(struct sx9500_data *data, int *counter,
+ unsigned int reg, unsigned int bitmask)
+{
+ (*counter)++;
+ if (*counter != 1)
+ /* Bit is already active, nothing to do. */
+ return 0;
+
+ return regmap_update_bits(data->regmap, reg, bitmask, bitmask);
+}
+
+static int sx9500_dec_users(struct sx9500_data *data, int *counter,
+ unsigned int reg, unsigned int bitmask)
+{
+ (*counter)--;
+ if (*counter != 0)
+ /* There are more users, do not deactivate. */
+ return 0;
+
+ return regmap_update_bits(data->regmap, reg, bitmask, 0);
+}
+
+static int sx9500_inc_chan_users(struct sx9500_data *data, int chan)
+{
+ return sx9500_inc_users(data, &data->channel_users[chan],
+ SX9500_REG_PROX_CTRL0, BIT(chan));
+}
+
+static int sx9500_dec_chan_users(struct sx9500_data *data, int chan)
+{
+ return sx9500_dec_users(data, &data->channel_users[chan],
+ SX9500_REG_PROX_CTRL0, BIT(chan));
+}
+
+static int sx9500_inc_data_rdy_users(struct sx9500_data *data)
+{
+ return sx9500_inc_users(data, &data->data_rdy_users,
+ SX9500_REG_IRQ_MSK, SX9500_CONVDONE_IRQ);
+}
+
+static int sx9500_dec_data_rdy_users(struct sx9500_data *data)
+{
+ return sx9500_dec_users(data, &data->data_rdy_users,
+ SX9500_REG_IRQ_MSK, SX9500_CONVDONE_IRQ);
+}
+
+static int sx9500_inc_close_far_users(struct sx9500_data *data)
+{
+ return sx9500_inc_users(data, &data->close_far_users,
+ SX9500_REG_IRQ_MSK,
+ SX9500_CLOSE_IRQ | SX9500_FAR_IRQ);
+}
+
+static int sx9500_dec_close_far_users(struct sx9500_data *data)
+{
+ return sx9500_dec_users(data, &data->close_far_users,
+ SX9500_REG_IRQ_MSK,
+ SX9500_CLOSE_IRQ | SX9500_FAR_IRQ);
+}
+
+static int sx9500_read_prox_data(struct sx9500_data *data,
+ const struct iio_chan_spec *chan,
+ int *val)
+{
+ int ret;
+ __be16 regval;
+
+ ret = regmap_write(data->regmap, SX9500_REG_SENSOR_SEL, chan->channel);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_bulk_read(data->regmap, SX9500_REG_USE_MSB, &regval, 2);
+ if (ret < 0)
+ return ret;
+
+ *val = be16_to_cpu(regval);
+
+ return IIO_VAL_INT;
+}
+
+/*
+ * If we have no interrupt support, we have to wait for a scan period
+ * after enabling a channel to get a result.
+ */
+static int sx9500_wait_for_sample(struct sx9500_data *data)
+{
+ int ret;
+ unsigned int val;
+
+ ret = regmap_read(data->regmap, SX9500_REG_PROX_CTRL0, &val);
+ if (ret < 0)
+ return ret;
+
+ val = (val & SX9500_SCAN_PERIOD_MASK) >> SX9500_SCAN_PERIOD_SHIFT;
+
+ msleep(sx9500_scan_period_table[val]);
+
+ return 0;
+}
+
+static int sx9500_read_proximity(struct sx9500_data *data,
+ const struct iio_chan_spec *chan,
+ int *val)
+{
+ int ret;
+
+ mutex_lock(&data->mutex);
+
+ ret = sx9500_inc_chan_users(data, chan->channel);
+ if (ret < 0)
+ goto out;
+
+ ret = sx9500_inc_data_rdy_users(data);
+ if (ret < 0)
+ goto out_dec_chan;
+
+ mutex_unlock(&data->mutex);
+
+ if (data->client->irq > 0)
+ ret = wait_for_completion_interruptible(&data->completion);
+ else
+ ret = sx9500_wait_for_sample(data);
+
+ mutex_lock(&data->mutex);
+
+ if (ret < 0)
+ goto out_dec_data_rdy;
+
+ ret = sx9500_read_prox_data(data, chan, val);
+ if (ret < 0)
+ goto out_dec_data_rdy;
+
+ ret = sx9500_dec_data_rdy_users(data);
+ if (ret < 0)
+ goto out_dec_chan;
+
+ ret = sx9500_dec_chan_users(data, chan->channel);
+ if (ret < 0)
+ goto out;
+
+ ret = IIO_VAL_INT;
+
+ goto out;
+
+out_dec_data_rdy:
+ sx9500_dec_data_rdy_users(data);
+out_dec_chan:
+ sx9500_dec_chan_users(data, chan->channel);
+out:
+ mutex_unlock(&data->mutex);
+ reinit_completion(&data->completion);
+
+ return ret;
+}
+
+static int sx9500_read_samp_freq(struct sx9500_data *data,
+ int *val, int *val2)
+{
+ int ret;
+ unsigned int regval;
+
+ mutex_lock(&data->mutex);
+ ret = regmap_read(data->regmap, SX9500_REG_PROX_CTRL0, &regval);
+ mutex_unlock(&data->mutex);
+
+ if (ret < 0)
+ return ret;
+
+ regval = (regval & SX9500_SCAN_PERIOD_MASK) >> SX9500_SCAN_PERIOD_SHIFT;
+ *val = sx9500_samp_freq_table[regval].val;
+ *val2 = sx9500_samp_freq_table[regval].val2;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int sx9500_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long mask)
+{
+ struct sx9500_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (chan->type) {
+ case IIO_PROXIMITY:
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+ ret = sx9500_read_proximity(data, chan, val);
+ iio_device_release_direct_mode(indio_dev);
+ return ret;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return sx9500_read_samp_freq(data, val, val2);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int sx9500_set_samp_freq(struct sx9500_data *data,
+ int val, int val2)
+{
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(sx9500_samp_freq_table); i++)
+ if (val == sx9500_samp_freq_table[i].val &&
+ val2 == sx9500_samp_freq_table[i].val2)
+ break;
+
+ if (i == ARRAY_SIZE(sx9500_samp_freq_table))
+ return -EINVAL;
+
+ mutex_lock(&data->mutex);
+
+ ret = regmap_update_bits(data->regmap, SX9500_REG_PROX_CTRL0,
+ SX9500_SCAN_PERIOD_MASK,
+ i << SX9500_SCAN_PERIOD_SHIFT);
+
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int sx9500_write_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int val, int val2, long mask)
+{
+ struct sx9500_data *data = iio_priv(indio_dev);
+
+ switch (chan->type) {
+ case IIO_PROXIMITY:
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return sx9500_set_samp_freq(data, val, val2);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static irqreturn_t sx9500_irq_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct sx9500_data *data = iio_priv(indio_dev);
+
+ if (data->trigger_enabled)
+ iio_trigger_poll(data->trig);
+
+ /*
+ * Even if no event is enabled, we need to wake the thread to
+ * clear the interrupt state by reading SX9500_REG_IRQ_SRC. It
+ * is not possible to do that here because regmap_read takes a
+ * mutex.
+ */
+ return IRQ_WAKE_THREAD;
+}
+
+static void sx9500_push_events(struct iio_dev *indio_dev)
+{
+ int ret;
+ unsigned int val, chan;
+ struct sx9500_data *data = iio_priv(indio_dev);
+
+ ret = regmap_read(data->regmap, SX9500_REG_STAT, &val);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "i2c transfer error in irq\n");
+ return;
+ }
+
+ val >>= SX9500_PROXSTAT_SHIFT;
+ for (chan = 0; chan < SX9500_NUM_CHANNELS; chan++) {
+ int dir;
+ u64 ev;
+ bool new_prox = val & BIT(chan);
+
+ if (!data->event_enabled[chan])
+ continue;
+ if (new_prox == data->prox_stat[chan])
+ /* No change on this channel. */
+ continue;
+
+ dir = new_prox ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING;
+ ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, chan,
+ IIO_EV_TYPE_THRESH, dir);
+ iio_push_event(indio_dev, ev, iio_get_time_ns(indio_dev));
+ data->prox_stat[chan] = new_prox;
+ }
+}
+
+static irqreturn_t sx9500_irq_thread_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct sx9500_data *data = iio_priv(indio_dev);
+ int ret;
+ unsigned int val;
+
+ mutex_lock(&data->mutex);
+
+ ret = regmap_read(data->regmap, SX9500_REG_IRQ_SRC, &val);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "i2c transfer error in irq\n");
+ goto out;
+ }
+
+ if (val & (SX9500_CLOSE_IRQ | SX9500_FAR_IRQ))
+ sx9500_push_events(indio_dev);
+
+ if (val & SX9500_CONVDONE_IRQ)
+ complete(&data->completion);
+
+out:
+ mutex_unlock(&data->mutex);
+
+ return IRQ_HANDLED;
+}
+
+static int sx9500_read_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ struct sx9500_data *data = iio_priv(indio_dev);
+
+ if (chan->type != IIO_PROXIMITY || type != IIO_EV_TYPE_THRESH ||
+ dir != IIO_EV_DIR_EITHER)
+ return -EINVAL;
+
+ return data->event_enabled[chan->channel];
+}
+
+static int sx9500_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ int state)
+{
+ struct sx9500_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (chan->type != IIO_PROXIMITY || type != IIO_EV_TYPE_THRESH ||
+ dir != IIO_EV_DIR_EITHER)
+ return -EINVAL;
+
+ mutex_lock(&data->mutex);
+
+ if (state == 1) {
+ ret = sx9500_inc_chan_users(data, chan->channel);
+ if (ret < 0)
+ goto out_unlock;
+ ret = sx9500_inc_close_far_users(data);
+ if (ret < 0)
+ goto out_undo_chan;
+ } else {
+ ret = sx9500_dec_chan_users(data, chan->channel);
+ if (ret < 0)
+ goto out_unlock;
+ ret = sx9500_dec_close_far_users(data);
+ if (ret < 0)
+ goto out_undo_chan;
+ }
+
+ data->event_enabled[chan->channel] = state;
+ goto out_unlock;
+
+out_undo_chan:
+ if (state == 1)
+ sx9500_dec_chan_users(data, chan->channel);
+ else
+ sx9500_inc_chan_users(data, chan->channel);
+out_unlock:
+ mutex_unlock(&data->mutex);
+ return ret;
+}
+
+static int sx9500_update_scan_mode(struct iio_dev *indio_dev,
+ const unsigned long *scan_mask)
+{
+ struct sx9500_data *data = iio_priv(indio_dev);
+
+ mutex_lock(&data->mutex);
+ kfree(data->buffer);
+ data->buffer = kzalloc(indio_dev->scan_bytes, GFP_KERNEL);
+ mutex_unlock(&data->mutex);
+
+ if (data->buffer == NULL)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(
+ "2.500000 3.333333 5 6.666666 8.333333 11.111111 16.666666 33.333333");
+
+static struct attribute *sx9500_attributes[] = {
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group sx9500_attribute_group = {
+ .attrs = sx9500_attributes,
+};
+
+static const struct iio_info sx9500_info = {
+ .attrs = &sx9500_attribute_group,
+ .read_raw = &sx9500_read_raw,
+ .write_raw = &sx9500_write_raw,
+ .read_event_config = &sx9500_read_event_config,
+ .write_event_config = &sx9500_write_event_config,
+ .update_scan_mode = &sx9500_update_scan_mode,
+};
+
+static int sx9500_set_trigger_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct sx9500_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+
+ if (state)
+ ret = sx9500_inc_data_rdy_users(data);
+ else
+ ret = sx9500_dec_data_rdy_users(data);
+ if (ret < 0)
+ goto out;
+
+ data->trigger_enabled = state;
+
+out:
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static const struct iio_trigger_ops sx9500_trigger_ops = {
+ .set_trigger_state = sx9500_set_trigger_state,
+};
+
+static irqreturn_t sx9500_trigger_handler(int irq, void *private)
+{
+ struct iio_poll_func *pf = private;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct sx9500_data *data = iio_priv(indio_dev);
+ int val, bit, ret, i = 0;
+
+ mutex_lock(&data->mutex);
+
+ for_each_set_bit(bit, indio_dev->active_scan_mask,
+ indio_dev->masklength) {
+ ret = sx9500_read_prox_data(data, &indio_dev->channels[bit],
+ &val);
+ if (ret < 0)
+ goto out;
+
+ data->buffer[i++] = val;
+ }
+
+ iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
+ iio_get_time_ns(indio_dev));
+
+out:
+ mutex_unlock(&data->mutex);
+
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int sx9500_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct sx9500_data *data = iio_priv(indio_dev);
+ int ret = 0, i;
+
+ mutex_lock(&data->mutex);
+
+ for (i = 0; i < SX9500_NUM_CHANNELS; i++)
+ if (test_bit(i, indio_dev->active_scan_mask)) {
+ ret = sx9500_inc_chan_users(data, i);
+ if (ret)
+ break;
+ }
+
+ if (ret)
+ for (i = i - 1; i >= 0; i--)
+ if (test_bit(i, indio_dev->active_scan_mask))
+ sx9500_dec_chan_users(data, i);
+
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int sx9500_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct sx9500_data *data = iio_priv(indio_dev);
+ int ret = 0, i;
+
+ mutex_lock(&data->mutex);
+
+ for (i = 0; i < SX9500_NUM_CHANNELS; i++)
+ if (test_bit(i, indio_dev->active_scan_mask)) {
+ ret = sx9500_dec_chan_users(data, i);
+ if (ret)
+ break;
+ }
+
+ if (ret)
+ for (i = i - 1; i >= 0; i--)
+ if (test_bit(i, indio_dev->active_scan_mask))
+ sx9500_inc_chan_users(data, i);
+
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static const struct iio_buffer_setup_ops sx9500_buffer_setup_ops = {
+ .postenable = sx9500_buffer_postenable,
+ .predisable = sx9500_buffer_predisable,
+};
+
+struct sx9500_reg_default {
+ u8 reg;
+ u8 def;
+};
+
+static const struct sx9500_reg_default sx9500_default_regs[] = {
+ {
+ .reg = SX9500_REG_PROX_CTRL1,
+ /* Shield enabled, small range. */
+ .def = 0x43,
+ },
+ {
+ .reg = SX9500_REG_PROX_CTRL2,
+ /* x8 gain, 167kHz frequency, finest resolution. */
+ .def = 0x77,
+ },
+ {
+ .reg = SX9500_REG_PROX_CTRL3,
+ /* Doze enabled, 2x scan period doze, no raw filter. */
+ .def = 0x40,
+ },
+ {
+ .reg = SX9500_REG_PROX_CTRL4,
+ /* Average threshold. */
+ .def = 0x30,
+ },
+ {
+ .reg = SX9500_REG_PROX_CTRL5,
+ /*
+ * Debouncer off, lowest average negative filter,
+ * highest average postive filter.
+ */
+ .def = 0x0f,
+ },
+ {
+ .reg = SX9500_REG_PROX_CTRL6,
+ /* Proximity detection threshold: 280 */
+ .def = 0x0e,
+ },
+ {
+ .reg = SX9500_REG_PROX_CTRL7,
+ /*
+ * No automatic compensation, compensate each pin
+ * independently, proximity hysteresis: 32, close
+ * debouncer off, far debouncer off.
+ */
+ .def = 0x00,
+ },
+ {
+ .reg = SX9500_REG_PROX_CTRL8,
+ /* No stuck timeout, no periodic compensation. */
+ .def = 0x00,
+ },
+ {
+ .reg = SX9500_REG_PROX_CTRL0,
+ /* Scan period: 30ms, all sensors disabled. */
+ .def = 0x00,
+ },
+};
+
+/* Activate all channels and perform an initial compensation. */
+static int sx9500_init_compensation(struct iio_dev *indio_dev)
+{
+ struct sx9500_data *data = iio_priv(indio_dev);
+ int i, ret;
+ unsigned int val;
+
+ ret = regmap_update_bits(data->regmap, SX9500_REG_PROX_CTRL0,
+ SX9500_CHAN_MASK, SX9500_CHAN_MASK);
+ if (ret < 0)
+ return ret;
+
+ for (i = 10; i >= 0; i--) {
+ usleep_range(10000, 20000);
+ ret = regmap_read(data->regmap, SX9500_REG_STAT, &val);
+ if (ret < 0)
+ goto out;
+ if (!(val & SX9500_COMPSTAT_MASK))
+ break;
+ }
+
+ if (i < 0) {
+ dev_err(&data->client->dev, "initial compensation timed out");
+ ret = -ETIMEDOUT;
+ }
+
+out:
+ regmap_update_bits(data->regmap, SX9500_REG_PROX_CTRL0,
+ SX9500_CHAN_MASK, 0);
+ return ret;
+}
+
+static int sx9500_init_device(struct iio_dev *indio_dev)
+{
+ struct sx9500_data *data = iio_priv(indio_dev);
+ int ret, i;
+ unsigned int val;
+
+ if (data->gpiod_rst) {
+ gpiod_set_value_cansleep(data->gpiod_rst, 0);
+ usleep_range(1000, 2000);
+ gpiod_set_value_cansleep(data->gpiod_rst, 1);
+ usleep_range(1000, 2000);
+ }
+
+ ret = regmap_write(data->regmap, SX9500_REG_IRQ_MSK, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(data->regmap, SX9500_REG_RESET,
+ SX9500_SOFT_RESET);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(data->regmap, SX9500_REG_IRQ_SRC, &val);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(sx9500_default_regs); i++) {
+ ret = regmap_write(data->regmap,
+ sx9500_default_regs[i].reg,
+ sx9500_default_regs[i].def);
+ if (ret < 0)
+ return ret;
+ }
+
+ return sx9500_init_compensation(indio_dev);
+}
+
+static const struct acpi_gpio_params reset_gpios = { 0, 0, false };
+static const struct acpi_gpio_params interrupt_gpios = { 2, 0, false };
+
+static const struct acpi_gpio_mapping acpi_sx9500_gpios[] = {
+ { "reset-gpios", &reset_gpios, 1 },
+ /*
+ * Some platforms have a bug in ACPI GPIO description making IRQ
+ * GPIO to be output only. Ask the GPIO core to ignore this limit.
+ */
+ { "interrupt-gpios", &interrupt_gpios, 1, ACPI_GPIO_QUIRK_NO_IO_RESTRICTION },
+ { },
+};
+
+static void sx9500_gpio_probe(struct i2c_client *client,
+ struct sx9500_data *data)
+{
+ struct gpio_desc *gpiod_int;
+ struct device *dev;
+ int ret;
+
+ if (!client)
+ return;
+
+ dev = &client->dev;
+
+ ret = devm_acpi_dev_add_driver_gpios(dev, acpi_sx9500_gpios);
+ if (ret)
+ dev_dbg(dev, "Unable to add GPIO mapping table\n");
+
+ if (client->irq <= 0) {
+ gpiod_int = devm_gpiod_get(dev, "interrupt", GPIOD_IN);
+ if (IS_ERR(gpiod_int))
+ dev_err(dev, "gpio get irq failed\n");
+ else
+ client->irq = gpiod_to_irq(gpiod_int);
+ }
+
+ data->gpiod_rst = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(data->gpiod_rst)) {
+ dev_warn(dev, "gpio get reset pin failed\n");
+ data->gpiod_rst = NULL;
+ }
+}
+
+static int sx9500_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret;
+ struct iio_dev *indio_dev;
+ struct sx9500_data *data;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (indio_dev == NULL)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ mutex_init(&data->mutex);
+ init_completion(&data->completion);
+ data->trigger_enabled = false;
+
+ data->regmap = devm_regmap_init_i2c(client, &sx9500_regmap_config);
+ if (IS_ERR(data->regmap))
+ return PTR_ERR(data->regmap);
+
+ indio_dev->name = SX9500_DRIVER_NAME;
+ indio_dev->channels = sx9500_channels;
+ indio_dev->num_channels = ARRAY_SIZE(sx9500_channels);
+ indio_dev->info = &sx9500_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ i2c_set_clientdata(client, indio_dev);
+
+ sx9500_gpio_probe(client, data);
+
+ ret = sx9500_init_device(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ if (client->irq <= 0)
+ dev_warn(&client->dev, "no valid irq found\n");
+ else {
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ sx9500_irq_handler, sx9500_irq_thread_handler,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ SX9500_IRQ_NAME, indio_dev);
+ if (ret < 0)
+ return ret;
+
+ data->trig = devm_iio_trigger_alloc(&client->dev,
+ "%s-dev%d", indio_dev->name, indio_dev->id);
+ if (!data->trig)
+ return -ENOMEM;
+
+ data->trig->dev.parent = &client->dev;
+ data->trig->ops = &sx9500_trigger_ops;
+ iio_trigger_set_drvdata(data->trig, indio_dev);
+
+ ret = iio_trigger_register(data->trig);
+ if (ret)
+ return ret;
+ }
+
+ ret = iio_triggered_buffer_setup(indio_dev, NULL,
+ sx9500_trigger_handler,
+ &sx9500_buffer_setup_ops);
+ if (ret < 0)
+ goto out_trigger_unregister;
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0)
+ goto out_buffer_cleanup;
+
+ return 0;
+
+out_buffer_cleanup:
+ iio_triggered_buffer_cleanup(indio_dev);
+out_trigger_unregister:
+ if (client->irq > 0)
+ iio_trigger_unregister(data->trig);
+
+ return ret;
+}
+
+static int sx9500_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct sx9500_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ iio_triggered_buffer_cleanup(indio_dev);
+ if (client->irq > 0)
+ iio_trigger_unregister(data->trig);
+ kfree(data->buffer);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int sx9500_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct sx9500_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = regmap_read(data->regmap, SX9500_REG_PROX_CTRL0,
+ &data->suspend_ctrl0);
+ if (ret < 0)
+ goto out;
+
+ /*
+ * Scan period doesn't matter because when all the sensors are
+ * deactivated the device is in sleep mode.
+ */
+ ret = regmap_write(data->regmap, SX9500_REG_PROX_CTRL0, 0);
+
+out:
+ mutex_unlock(&data->mutex);
+ return ret;
+}
+
+static int sx9500_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct sx9500_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = regmap_write(data->regmap, SX9500_REG_PROX_CTRL0,
+ data->suspend_ctrl0);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops sx9500_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(sx9500_suspend, sx9500_resume)
+};
+
+static const struct acpi_device_id sx9500_acpi_match[] = {
+ {"SSX9500", 0},
+ {"SASX9500", 0},
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, sx9500_acpi_match);
+
+static const struct of_device_id sx9500_of_match[] = {
+ { .compatible = "semtech,sx9500", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sx9500_of_match);
+
+static const struct i2c_device_id sx9500_id[] = {
+ {"sx9500", 0},
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, sx9500_id);
+
+static struct i2c_driver sx9500_driver = {
+ .driver = {
+ .name = SX9500_DRIVER_NAME,
+ .acpi_match_table = ACPI_PTR(sx9500_acpi_match),
+ .of_match_table = of_match_ptr(sx9500_of_match),
+ .pm = &sx9500_pm_ops,
+ },
+ .probe = sx9500_probe,
+ .remove = sx9500_remove,
+ .id_table = sx9500_id,
+};
+module_i2c_driver(sx9500_driver);
+
+MODULE_AUTHOR("Vlad Dogaru <vlad.dogaru@intel.com>");
+MODULE_DESCRIPTION("Driver for Semtech SX9500 proximity sensor");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/proximity/vcnl3020.c b/drivers/iio/proximity/vcnl3020.c
new file mode 100644
index 000000000..37264f801
--- /dev/null
+++ b/drivers/iio/proximity/vcnl3020.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Support for Vishay VCNL3020 proximity sensor on i2c bus.
+ * Based on Vishay VCNL4000 driver code.
+ *
+ * TODO: interrupts.
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/regmap.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define VCNL3020_PROD_ID 0x21
+
+#define VCNL_COMMAND 0x80 /* Command register */
+#define VCNL_PROD_REV 0x81 /* Product ID and Revision ID */
+#define VCNL_PROXIMITY_RATE 0x82 /* Rate of Proximity Measurement */
+#define VCNL_LED_CURRENT 0x83 /* IR LED current for proximity mode */
+#define VCNL_PS_RESULT_HI 0x87 /* Proximity result register, MSB */
+#define VCNL_PS_RESULT_LO 0x88 /* Proximity result register, LSB */
+#define VCNL_PS_ICR 0x89 /* Interrupt Control Register */
+#define VCNL_PS_LO_THR_HI 0x8a /* High byte of low threshold value */
+#define VCNL_PS_LO_THR_LO 0x8b /* Low byte of low threshold value */
+#define VCNL_PS_HI_THR_HI 0x8c /* High byte of high threshold value */
+#define VCNL_PS_HI_THR_LO 0x8d /* Low byte of high threshold value */
+#define VCNL_ISR 0x8e /* Interrupt Status Register */
+#define VCNL_PS_MOD_ADJ 0x8f /* Proximity Modulator Timing Adjustment */
+
+/* Bit masks for COMMAND register */
+#define VCNL_PS_RDY BIT(5) /* proximity data ready? */
+#define VCNL_PS_OD BIT(3) /* start on-demand proximity
+ * measurement
+ */
+
+#define VCNL_ON_DEMAND_TIMEOUT_US 100000
+#define VCNL_POLL_US 20000
+
+/**
+ * struct vcnl3020_data - vcnl3020 specific data.
+ * @regmap: device register map.
+ * @dev: vcnl3020 device.
+ * @rev: revision id.
+ * @lock: lock for protecting access to device hardware registers.
+ */
+struct vcnl3020_data {
+ struct regmap *regmap;
+ struct device *dev;
+ u8 rev;
+ struct mutex lock;
+};
+
+/**
+ * struct vcnl3020_property - vcnl3020 property.
+ * @name: property name.
+ * @reg: i2c register offset.
+ * @conversion_func: conversion function.
+ */
+struct vcnl3020_property {
+ const char *name;
+ u32 reg;
+ u32 (*conversion_func)(u32 *val);
+};
+
+static u32 microamp_to_reg(u32 *val)
+{
+ /*
+ * An example of conversion from uA to reg val:
+ * 200000 uA == 200 mA == 20
+ */
+ return *val /= 10000;
+};
+
+static struct vcnl3020_property vcnl3020_led_current_property = {
+ .name = "vishay,led-current-microamp",
+ .reg = VCNL_LED_CURRENT,
+ .conversion_func = microamp_to_reg,
+};
+
+static int vcnl3020_get_and_apply_property(struct vcnl3020_data *data,
+ struct vcnl3020_property prop)
+{
+ int rc;
+ u32 val;
+
+ rc = device_property_read_u32(data->dev, prop.name, &val);
+ if (rc)
+ return 0;
+
+ if (prop.conversion_func)
+ prop.conversion_func(&val);
+
+ rc = regmap_write(data->regmap, prop.reg, val);
+ if (rc) {
+ dev_err(data->dev, "Error (%d) setting property (%s)\n",
+ rc, prop.name);
+ }
+
+ return rc;
+}
+
+static int vcnl3020_init(struct vcnl3020_data *data)
+{
+ int rc;
+ unsigned int reg;
+
+ rc = regmap_read(data->regmap, VCNL_PROD_REV, &reg);
+ if (rc) {
+ dev_err(data->dev,
+ "Error (%d) reading product revision\n", rc);
+ return rc;
+ }
+
+ if (reg != VCNL3020_PROD_ID) {
+ dev_err(data->dev,
+ "Product id (%x) did not match vcnl3020 (%x)\n", reg,
+ VCNL3020_PROD_ID);
+ return -ENODEV;
+ }
+
+ data->rev = reg;
+ mutex_init(&data->lock);
+
+ return vcnl3020_get_and_apply_property(data,
+ vcnl3020_led_current_property);
+};
+
+static int vcnl3020_measure_proximity(struct vcnl3020_data *data, int *val)
+{
+ int rc;
+ unsigned int reg;
+ __be16 res;
+
+ mutex_lock(&data->lock);
+
+ rc = regmap_write(data->regmap, VCNL_COMMAND, VCNL_PS_OD);
+ if (rc)
+ goto err_unlock;
+
+ /* wait for data to become ready */
+ rc = regmap_read_poll_timeout(data->regmap, VCNL_COMMAND, reg,
+ reg & VCNL_PS_RDY, VCNL_POLL_US,
+ VCNL_ON_DEMAND_TIMEOUT_US);
+ if (rc) {
+ dev_err(data->dev,
+ "Error (%d) reading vcnl3020 command register\n", rc);
+ goto err_unlock;
+ }
+
+ /* high & low result bytes read */
+ rc = regmap_bulk_read(data->regmap, VCNL_PS_RESULT_HI, &res,
+ sizeof(res));
+ if (rc)
+ goto err_unlock;
+
+ *val = be16_to_cpu(res);
+
+err_unlock:
+ mutex_unlock(&data->lock);
+
+ return rc;
+}
+
+static const struct iio_chan_spec vcnl3020_channels[] = {
+ {
+ .type = IIO_PROXIMITY,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ },
+};
+
+static int vcnl3020_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ int rc;
+ struct vcnl3020_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ rc = vcnl3020_measure_proximity(data, val);
+ if (rc)
+ return rc;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info vcnl3020_info = {
+ .read_raw = vcnl3020_read_raw,
+};
+
+static const struct regmap_config vcnl3020_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = VCNL_PS_MOD_ADJ,
+};
+
+static int vcnl3020_probe(struct i2c_client *client)
+{
+ struct vcnl3020_data *data;
+ struct iio_dev *indio_dev;
+ struct regmap *regmap;
+ int rc;
+
+ regmap = devm_regmap_init_i2c(client, &vcnl3020_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "regmap_init failed\n");
+ return PTR_ERR(regmap);
+ }
+
+ 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->regmap = regmap;
+ data->dev = &client->dev;
+
+ rc = vcnl3020_init(data);
+ if (rc)
+ return rc;
+
+ indio_dev->info = &vcnl3020_info;
+ indio_dev->channels = vcnl3020_channels;
+ indio_dev->num_channels = ARRAY_SIZE(vcnl3020_channels);
+ indio_dev->name = "vcnl3020";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct of_device_id vcnl3020_of_match[] = {
+ {
+ .compatible = "vishay,vcnl3020",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, vcnl3020_of_match);
+
+static struct i2c_driver vcnl3020_driver = {
+ .driver = {
+ .name = "vcnl3020",
+ .of_match_table = vcnl3020_of_match,
+ },
+ .probe_new = vcnl3020_probe,
+};
+module_i2c_driver(vcnl3020_driver);
+
+MODULE_AUTHOR("Ivan Mikhaylov <i.mikhaylov@yadro.com>");
+MODULE_DESCRIPTION("Vishay VCNL3020 proximity sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/proximity/vl53l0x-i2c.c b/drivers/iio/proximity/vl53l0x-i2c.c
new file mode 100644
index 000000000..3d3ab8642
--- /dev/null
+++ b/drivers/iio/proximity/vl53l0x-i2c.c
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support for ST VL53L0X FlightSense ToF Ranging Sensor on a i2c bus.
+ *
+ * Copyright (C) 2016 STMicroelectronics Imaging Division.
+ * Copyright (C) 2018 Song Qiang <songqiang1304521@gmail.com>
+ * Copyright (C) 2020 Ivan Drobyshevskyi <drobyshevskyi@gmail.com>
+ *
+ * Datasheet available at
+ * <https://www.st.com/resource/en/datasheet/vl53l0x.pdf>
+ *
+ * Default 7-bit i2c slave address 0x29.
+ *
+ * TODO: FIFO buffer, continuous mode, range selection, sensor ID check.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+
+#define VL_REG_SYSRANGE_START 0x00
+
+#define VL_REG_SYSRANGE_MODE_MASK GENMASK(3, 0)
+#define VL_REG_SYSRANGE_MODE_SINGLESHOT 0x00
+#define VL_REG_SYSRANGE_MODE_START_STOP BIT(0)
+#define VL_REG_SYSRANGE_MODE_BACKTOBACK BIT(1)
+#define VL_REG_SYSRANGE_MODE_TIMED BIT(2)
+#define VL_REG_SYSRANGE_MODE_HISTOGRAM BIT(3)
+
+#define VL_REG_SYSTEM_INTERRUPT_CONFIG_GPIO 0x0A
+#define VL_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY BIT(2)
+
+#define VL_REG_SYSTEM_INTERRUPT_CLEAR 0x0B
+
+#define VL_REG_RESULT_INT_STATUS 0x13
+#define VL_REG_RESULT_RANGE_STATUS 0x14
+#define VL_REG_RESULT_RANGE_STATUS_COMPLETE BIT(0)
+
+struct vl53l0x_data {
+ struct i2c_client *client;
+ struct completion completion;
+};
+
+static irqreturn_t vl53l0x_handle_irq(int irq, void *priv)
+{
+ struct iio_dev *indio_dev = priv;
+ struct vl53l0x_data *data = iio_priv(indio_dev);
+
+ complete(&data->completion);
+
+ return IRQ_HANDLED;
+}
+
+static int vl53l0x_configure_irq(struct i2c_client *client,
+ struct iio_dev *indio_dev)
+{
+ struct vl53l0x_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = devm_request_irq(&client->dev, client->irq, vl53l0x_handle_irq,
+ IRQF_TRIGGER_FALLING, indio_dev->name, indio_dev);
+ if (ret) {
+ dev_err(&client->dev, "devm_request_irq error: %d\n", ret);
+ return ret;
+ }
+
+ ret = i2c_smbus_write_byte_data(data->client,
+ VL_REG_SYSTEM_INTERRUPT_CONFIG_GPIO,
+ VL_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY);
+ if (ret < 0)
+ dev_err(&client->dev, "failed to configure IRQ: %d\n", ret);
+
+ return ret;
+}
+
+static void vl53l0x_clear_irq(struct vl53l0x_data *data)
+{
+ struct device *dev = &data->client->dev;
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(data->client,
+ VL_REG_SYSTEM_INTERRUPT_CLEAR, 1);
+ if (ret < 0)
+ dev_err(dev, "failed to clear error irq: %d\n", ret);
+
+ ret = i2c_smbus_write_byte_data(data->client,
+ VL_REG_SYSTEM_INTERRUPT_CLEAR, 0);
+ if (ret < 0)
+ dev_err(dev, "failed to clear range irq: %d\n", ret);
+
+ ret = i2c_smbus_read_byte_data(data->client, VL_REG_RESULT_INT_STATUS);
+ if (ret < 0 || ret & 0x07)
+ dev_err(dev, "failed to clear irq: %d\n", ret);
+}
+
+static int vl53l0x_read_proximity(struct vl53l0x_data *data,
+ const struct iio_chan_spec *chan,
+ int *val)
+{
+ struct i2c_client *client = data->client;
+ u16 tries = 20;
+ u8 buffer[12];
+ int ret;
+ unsigned long time_left;
+
+ ret = i2c_smbus_write_byte_data(client, VL_REG_SYSRANGE_START, 1);
+ if (ret < 0)
+ return ret;
+
+ if (data->client->irq) {
+ reinit_completion(&data->completion);
+
+ time_left = wait_for_completion_timeout(&data->completion, HZ/10);
+ if (time_left == 0)
+ return -ETIMEDOUT;
+
+ vl53l0x_clear_irq(data);
+ } else {
+ do {
+ ret = i2c_smbus_read_byte_data(client,
+ VL_REG_RESULT_RANGE_STATUS);
+ if (ret < 0)
+ return ret;
+
+ if (ret & VL_REG_RESULT_RANGE_STATUS_COMPLETE)
+ break;
+
+ usleep_range(1000, 5000);
+ } while (--tries);
+ if (!tries)
+ return -ETIMEDOUT;
+ }
+
+ ret = i2c_smbus_read_i2c_block_data(client, VL_REG_RESULT_RANGE_STATUS,
+ 12, buffer);
+ if (ret < 0)
+ return ret;
+ else if (ret != 12)
+ return -EREMOTEIO;
+
+ /* Values should be between 30~1200 in millimeters. */
+ *val = (buffer[10] << 8) + buffer[11];
+
+ return 0;
+}
+
+static const struct iio_chan_spec vl53l0x_channels[] = {
+ {
+ .type = IIO_DISTANCE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+};
+
+static int vl53l0x_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long mask)
+{
+ struct vl53l0x_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (chan->type != IIO_DISTANCE)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = vl53l0x_read_proximity(data, chan, val);
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = 1000;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info vl53l0x_info = {
+ .read_raw = vl53l0x_read_raw,
+};
+
+static int vl53l0x_probe(struct i2c_client *client)
+{
+ struct vl53l0x_data *data;
+ struct iio_dev *indio_dev;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ i2c_set_clientdata(client, indio_dev);
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK |
+ I2C_FUNC_SMBUS_BYTE_DATA))
+ return -EOPNOTSUPP;
+
+ indio_dev->name = "vl53l0x";
+ indio_dev->info = &vl53l0x_info;
+ indio_dev->channels = vl53l0x_channels;
+ indio_dev->num_channels = ARRAY_SIZE(vl53l0x_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ /* usage of interrupt is optional */
+ if (client->irq) {
+ int ret;
+
+ init_completion(&data->completion);
+
+ ret = vl53l0x_configure_irq(client, indio_dev);
+ if (ret)
+ return ret;
+ }
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct of_device_id st_vl53l0x_dt_match[] = {
+ { .compatible = "st,vl53l0x", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, st_vl53l0x_dt_match);
+
+static struct i2c_driver vl53l0x_driver = {
+ .driver = {
+ .name = "vl53l0x-i2c",
+ .of_match_table = st_vl53l0x_dt_match,
+ },
+ .probe_new = vl53l0x_probe,
+};
+module_i2c_driver(vl53l0x_driver);
+
+MODULE_AUTHOR("Song Qiang <songqiang1304521@gmail.com>");
+MODULE_DESCRIPTION("ST vl53l0x ToF ranging sensor driver");
+MODULE_LICENSE("GPL v2");