diff mbox series

[v10,2/2] iio: proximity: Add driver support for vcnl3020 proximity sensor

Message ID 20200421194005.6295-3-i.mikhaylov@yadro.com (mailing list archive)
State New, archived
Headers show
Series iio: proximity: driver for vcnl3020 | expand

Commit Message

Ivan Mikhaylov April 21, 2020, 7:40 p.m. UTC
Proximity sensor driver based on light/vcnl4000.c code.
For now supports only the single on-demand measurement.

The VCNL3020 is a fully integrated proximity sensor. Fully
integrated means that the infrared emitter is included in the
package. It has 16-bit resolution. It includes a signal
processing IC and features standard I2C communication
interface. It features an interrupt function.

Datasheet: http://www.vishay.com/docs/84150/vcnl3020.pdf
Signed-off-by: Ivan Mikhaylov <i.mikhaylov@yadro.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
---
 drivers/iio/proximity/Kconfig    |  11 ++
 drivers/iio/proximity/Makefile   |   1 +
 drivers/iio/proximity/vcnl3020.c | 235 +++++++++++++++++++++++++++++++
 3 files changed, 247 insertions(+)
 create mode 100644 drivers/iio/proximity/vcnl3020.c

Comments

Andy Shevchenko April 21, 2020, 8:23 p.m. UTC | #1
On Tue, Apr 21, 2020 at 10:39 PM Ivan Mikhaylov <i.mikhaylov@yadro.com> wrote:
>
> Proximity sensor driver based on light/vcnl4000.c code.
> For now supports only the single on-demand measurement.
>
> The VCNL3020 is a fully integrated proximity sensor. Fully
> integrated means that the infrared emitter is included in the
> package. It has 16-bit resolution. It includes a signal
> processing IC and features standard I2C communication
> interface. It features an interrupt function.

...

> +static int vcnl3020_get_and_apply_property(struct vcnl3020_data *data,
> +                                          const char *prop, u32 reg)
> +{
> +       int rc;
> +       u32 val;
> +
> +       rc = device_property_read_u32(data->dev, prop, &val);
> +       if (rc)
> +               return 0;
> +

> +       /* An example of conversion from uA to reg val:
> +        * 200000 uA == 200 mA == 20
> +        */
> +       if (!strcmp(prop, "vishay,led-current-microamp"))
> +               val /= 10000;

I probably missed the point why this function is needed at all, since
we always call only for a single property.

On top of that, why do we have this nasty strcmp()? Can't we simple do
something like

static int vcnl3020_get_and_apply_property(struct vcnl3020_data *data,
const char *prop, u32 reg, u32 div)
{
...
  val /= div;
...
}

static int vcnl3020_get_and_apply_led_current_property(struct
vcnl3020_data *data)
{
  /*
   * An example of conversion from uA to reg val:
   * 200000 uA == 200 mA == 20
   */
// Note by the way comments style
  return vcnl3020_get_and_apply_property(data, "vishay,led-current-microamp",
                                              VCNL_LED_CURRENT, 10000);
}

?

> +       rc = regmap_write(data->regmap, reg, val);
> +       if (rc) {
> +               dev_err(data->dev, "Error (%d) setting property (%s)\n",
> +                       rc, prop);
> +       }
> +
> +       return rc;
> +}
Ivan Mikhaylov April 22, 2020, 12:46 p.m. UTC | #2
On Tue, 2020-04-21 at 23:23 +0300, Andy Shevchenko wrote:
> On Tue, Apr 21, 2020 at 10:39 PM Ivan Mikhaylov <i.mikhaylov@yadro.com> wrote:
> > Proximity sensor driver based on light/vcnl4000.c code.
> > For now supports only the single on-demand measurement.
> > 
> > The VCNL3020 is a fully integrated proximity sensor. Fully
> > integrated means that the infrared emitter is included in the
> > package. It has 16-bit resolution. It includes a signal
> > processing IC and features standard I2C communication
> > interface. It features an interrupt function.
> 
> ...
> 
> > +static int vcnl3020_get_and_apply_property(struct vcnl3020_data *data,
> > +                                          const char *prop, u32 reg)
> > +{
> > +       int rc;
> > +       u32 val;
> > +
> > +       rc = device_property_read_u32(data->dev, prop, &val);
> > +       if (rc)
> > +               return 0;
> > +
> > +       /* An example of conversion from uA to reg val:
> > +        * 200000 uA == 200 mA == 20
> > +        */
> > +       if (!strcmp(prop, "vishay,led-current-microamp"))
> > +               val /= 10000;
> 
> I probably missed the point why this function is needed at all, since
> we always call only for a single property.

Andy, I'm planning to add more properties but not for now, this driver the
placeholder for upcoming work what I've. And I wanted something like:

rc = vcnl3020_get_and_apply_property(option1);
if (rc)
	return rc;

rc = vcnl3020_get_and_apply_property(option2);
if (rc)
	return rc;
etc.

And all other work about conversion will be hidden in get_and_apply_property. 

> On top of that, why do we have this nasty strcmp()? Can't we simple do
> something like
> static int vcnl3020_get_and_apply_property(struct vcnl3020_data *data,
> const char *prop, u32 reg, u32 div)
> {
> ...
>   val /= div;
> ...
> }
> 
> static int vcnl3020_get_and_apply_led_current_property(struct
> vcnl3020_data *data)
> {
>   /*
>    * An example of conversion from uA to reg val:
>    * 200000 uA == 200 mA == 20
>    */
> // Note by the way comments style
>   return vcnl3020_get_and_apply_property(data, "vishay,led-current-microamp",
>                                               VCNL_LED_CURRENT, 10000);
> }
> 
> ?

I think your approach with wrapper is better, I'll think how I can do that
without additional 'div' param.

Thanks.
diff mbox series

Patch

diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig
index d53601447da4..b8d2b17e60ac 100644
--- a/drivers/iio/proximity/Kconfig
+++ b/drivers/iio/proximity/Kconfig
@@ -112,6 +112,17 @@  config SRF08
 	  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
diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile
index 0bb5f9de13d6..8245978ced30 100644
--- a/drivers/iio/proximity/Makefile
+++ b/drivers/iio/proximity/Makefile
@@ -12,5 +12,6 @@  obj-$(CONFIG_RFD77402)		+= rfd77402.o
 obj-$(CONFIG_SRF04)		+= srf04.o
 obj-$(CONFIG_SRF08)		+= srf08.o
 obj-$(CONFIG_SX9500)		+= sx9500.o
+obj-$(CONFIG_VCNL3020)		+= vcnl3020.o
 obj-$(CONFIG_VL53L0X_I2C)	+= vl53l0x-i2c.o
 
diff --git a/drivers/iio/proximity/vcnl3020.c b/drivers/iio/proximity/vcnl3020.c
new file mode 100644
index 000000000000..de6e0da5760c
--- /dev/null
+++ b/drivers/iio/proximity/vcnl3020.c
@@ -0,0 +1,235 @@ 
+// 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;
+};
+
+static int vcnl3020_get_and_apply_property(struct vcnl3020_data *data,
+					   const char *prop, u32 reg)
+{
+	int rc;
+	u32 val;
+
+	rc = device_property_read_u32(data->dev, prop, &val);
+	if (rc)
+		return 0;
+
+	/* An example of conversion from uA to reg val:
+	 * 200000 uA == 200 mA == 20
+	 */
+	if (!strcmp(prop, "vishay,led-current-microamp"))
+		val /= 10000;
+
+	rc = regmap_write(data->regmap, reg, val);
+	if (rc) {
+		dev_err(data->dev, "Error (%d) setting property (%s)\n",
+			rc, prop);
+	}
+
+	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,
+					       "vishay,led-current-microamp",
+					       VCNL_LED_CURRENT);
+};
+
+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->dev.parent = &client->dev;
+	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");