iio:light: Add support for STMicro VL6180 sensor
Subsystems (2)
PATCH 1/1 iio:light: Add support for STMicro VL6180 sensor
This patch adds support for STMicro VL6180 - ALS and proximity sensor. Sensor is capable of measuring the light intensity as well as object distance using TOF(Time of Flight) technology.
--- .../devicetree/bindings/iio/light/vl6180.txt | 16 + drivers/iio/light/Kconfig | 10 + drivers/iio/light/Makefile | 1 + drivers/iio/light/vl6180.c | 544 +++++++++++++++++++++ 4 files changed, 571 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/vl6180.txt create mode 100644 drivers/iio/light/vl6180.c
Documentation/devicetree/bindings/iio/light/vl6180.txt new file +16 -0
@@ -0,0 +1,16 @@ +STMicro VL6180 - ALS and proximity sensor + +Link to datasheet: http://www.st.com/resource/en/datasheet/vl6180x.pdf + +Required properties: + + -compatible: should be "st,vl6180" + -reg: the I2C address of the sensor + +Example: + +vl6180@29 { + compatible = "st,vl6180"; + reg = <0x29>; +}; +
drivers/iio/light/Kconfig +10 -0
@@ -352,4 +352,14 @@ config VEML6070 To compile this driver as a module, choose M here: the module will be called veml6070. +config VL6180 + tristate "VL6180 ALS and proximity sensor" + depends on I2C + help + Say Y here if you want to build a driver for the STMicroelectronics + VL6180 combined ambient light and proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called vl6180. + endmenu
drivers/iio/light/Makefile +1 -0
@@ -33,3 +33,4 @@ obj-$(CONFIG_TSL4531) += tsl4531.o obj-$(CONFIG_US5182D) += us5182d.o obj-$(CONFIG_VCNL4000) += vcnl4000.o obj-$(CONFIG_VEML6070) += veml6070.o +obj-$(CONFIG_VL6180) += vl6180.o
drivers/iio/light/vl6180.c new file +544 -0
544 lines not on this page · raw message
Signature · 6 lines
2.7.4 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
A message that is not in the archive (sent off-list or not yet received): [email protected]
Rob Herring
Acked-byQuoted 6 lines · Manivannan Sadhasivam
This patch adds support for STMicro VL6180 - ALS and proximity sensor. Sensor is capable of measuring the light intensity as well as object distance using TOF(Time of Flight) technology.
Signed-off-by Manivannan Sadhasivam <[email protected]>--- .../devicetree/bindings/iio/light/vl6180.txt | 16 +
Quoted 6 lines
drivers/iio/light/Kconfig | 10 + drivers/iio/light/Makefile | 1 + drivers/iio/light/vl6180.c | 544 +++++++++++++++++++++ 4 files changed, 571 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/vl6180.txt create mode 100644 drivers/iio/light/vl6180.c
Quoted 4 lines · Manivannan Sadhasivam
This patch adds support for STMicro VL6180 - ALS and proximity sensor. Sensor is capable of measuring the light intensity as well as object distance using TOF(Time of Flight) technology.
Signed-off-by Manivannan Sadhasivam <[email protected]>
Few more comments from me.
Jonathan
Quoted 188 lines
--- .../devicetree/bindings/iio/light/vl6180.txt | 16 + drivers/iio/light/Kconfig | 10 + drivers/iio/light/Makefile | 1 + drivers/iio/light/vl6180.c | 544 +++++++++++++++++++++ 4 files changed, 571 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/vl6180.txt create mode 100644 drivers/iio/light/vl6180.cDocumentation/devicetree/bindings/iio/light/vl6180.txt new file +16
-0@@ -0,0 +1,16 @@ +STMicro VL6180 - ALS and proximity sensor + +Link to datasheet: http://www.st.com/resource/en/datasheet/vl6180x.pdf + +Required properties: + + -compatible: should be "st,vl6180" + -reg: the I2C address of the sensor + +Example: + +vl6180@29 { + compatible = "st,vl6180"; + reg = <0x29>; +}; +drivers/iio/light/Kconfig +10
-0@@ -352,4 +352,14 @@ config VEML6070 To compile this driver as a module, choose M here: the module will be called veml6070. +config VL6180 + tristate "VL6180 ALS and proximity sensor" + depends on I2C + help + Say Y here if you want to build a driver for the STMicroelectronics + VL6180 combined ambient light and proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called vl6180. + endmenudrivers/iio/light/Makefile +1
-0@@ -33,3 +33,4 @@ obj-$(CONFIG_TSL4531) += tsl4531.o obj-$(CONFIG_US5182D) += us5182d.o obj-$(CONFIG_VCNL4000) += vcnl4000.o obj-$(CONFIG_VEML6070) += veml6070.o +obj-$(CONFIG_VL6180) += vl6180.odrivers/iio/light/vl6180.c new file +123
-0@@ -0,0 +1,544 @@ +/* +* vl6180.c - Support for STMicroelectronics VL6180 ALS and proximity sensor +* +* Copyright 2017 Peter Meerwald-Stadler <[email protected]> +* Copyright 2017 Manivannan Sadhasivam <[email protected]> +* +* This file is subject to the terms and conditions of version 2 of +* the GNU General Public License. See the file COPYING in the main +* directory of this archive for more details. +* +* IIO driver for VL6180 (7-bit I2C slave addresses 0x29) +* +* Range: 0 to 100mm +* ALS: < 1 Lux up to 100 kLux +* IR: 850nm +* +* TODO: irq, threshold events, continuous mode, hardware buffer +*/ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define VL6180_DRV_NAME "vl6180" + +/* Device identification register and value */ +#define VL6180_MODEL_ID 0x000 +#define VL6180_MODEL_ID_VAL 0xb4 + +/* Configuration registers */ +#define VL6180_INTR_CONFIG 0x014 +#define VL6180_INTR_CLEAR 0x015 +#define VL6180_OUT_OF_RESET 0x016 +#define VL6180_HOLD 0x017 +#define VL6180_RANGE_START 0x018 +#define VL6180_ALS_START 0x038 +#define VL6180_ALS_GAIN 0x03f +#define VL6180_ALS_IT 0x040 + +/* Status registers */ +#define VL6180_RANGE_STATUS 0x04d +#define VL6180_ALS_STATUS 0x04e +#define VL6180_INTR_STATUS 0x04f + +/* Result value registers */ +#define VL6180_ALS_VALUE 0x050 +#define VL6180_RANGE_VALUE 0x062 +#define VL6180_RANGE_RATE 0x066 + +/* bits of the RANGE_START and ALS_START register */ +#define VL6180_MODE_CONT BIT(1)/* continuous mode */ +#define VL6180_STARTSTOP BIT(0)/* start measurement, auto-reset */ + +/* bits of the INTR_STATUS and INTR_CONFIG register */ +#define VL6180_ALS_READY BIT(5) +#define VL6180_RANGE_READY BIT(2) + +/* bits of the INTR_CLEAR register */ +#define VL6180_CLEAR_ERROR BIT(2) +#define VL6180_CLEAR_ALS BIT(1) +#define VL6180_CLEAR_RANGE BIT(0) + +/* bits of the HOLD register */ +#define VL6180_HOLD_ON BIT(0) + +/* default value for the ALS_IT register */ +#define VL6180_ALS_IT_100 0x063/* 100 ms */ + +/* values for the ALS_GAIN register */ +#define VL6180_ALS_GAIN_1 0x46 +#define VL6180_ALS_GAIN_1_25 0x45 +#define VL6180_ALS_GAIN_1_67 0x44 +#define VL6180_ALS_GAIN_2_5 0x43 +#define VL6180_ALS_GAIN_5 0x42 +#define VL6180_ALS_GAIN_10 0x41 +#define VL6180_ALS_GAIN_20 0x40 +#define VL6180_ALS_GAIN_40 0x47 + +struct vl6180_data { +struct i2c_client *client; +struct mutex lock; +}; + +enum {VL6180_ALS ,VL6180_RANGE ,VL6180_PROX }; + +/** +* struct vl6180_chan_regs - Registers for accessing channels +* @drdy_mask: Data ready bits in status register +* @start_reg: Conversion start registeir +* @value_reg: Result value register +*/ +struct vl6180_chan_regs { +u8 drdy_mask; +u16 start_reg, value_reg; +bool word; +}; + +static const struct vl6180_chan_regs vl6180_chan_regs_table[] = { + [VL6180_ALS ] = { + .drdy_mask =VL6180_ALS_READY , + .start_reg =VL6180_ALS_START , + .value_reg =VL6180_ALS_VALUE , + .word = true, + }, + [VL6180_RANGE ] = { + .drdy_mask =VL6180_RANGE_READY , + .start_reg =VL6180_RANGE_START , + .value_reg =VL6180_RANGE_VALUE , + .word = false, + }, + [VL6180_PROX ] = { + .drdy_mask =VL6180_RANGE_READY , + .start_reg =VL6180_RANGE_START , + .value_reg =VL6180_RANGE_RATE , + .word = true, + }, +};
drivers/iio/light/vl6180.c line 133
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ +static int vl6180_read (struct i2c_client *client,u16 cmd,void *databuf, +u8 len) +{ +u16 cmdbuf =cpu_to_be16 (cmd); +struct i2c_msg msgs[2 ] = { + { .addr = client->addr, .len =2 , .buf = (u8 *) &cmdbuf }, + { .addr = client->addr, .len = len, .buf = databuf, + .flags =I2C_M_RD } }; +int ret;
Hmm. could wrap this up in regmap rather neatly I think, but fair enough to keep it all obvious here if you prefer.
Quoted 31 lines
drivers/iio/light/vl6180.c new file +31
-0+ + ret =i2c_transfer (client->adapter, msgs,2 ); +if (ret <0 ) +dev_err (&client->dev,"failed reading register 0x%04x\n" , cmd); + +return ret; +} + +static int vl6180_read_byte (struct i2c_client *client,u16 cmd) +{ +u8 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 ) +return ret; + +return data; +} + +static int vl6180_read_word (struct i2c_client *client,u16 cmd) +{ +__be16 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 ) +return ret; + +return be16_to_cpu (data); +}
drivers/iio/light/vl6180.c line 174
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ +static int vl6180_write_byte (struct i2c_client *client,u16 cmd,u8 val) +{ +u8 buf[3 ]; +struct i2c_msg msgs[1 ] = { + { .addr = client->addr, .len =3 , .buf = buf } }; +int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ;
Here you end up with a mix that makes it harder to force the endianness than below, but it can still be done by using cpu_to_be16p and again makes that conversion explicit.
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ buf[2 ] = val; + + ret =i2c_transfer (client->adapter, msgs,1 ); +if (ret <0 ) { +dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } + +return 0 ; +}
drivers/iio/light/vl6180.c line 194
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ +static int vl6180_write_word (struct i2c_client *client,u16 cmd,u16 val) +{ +u8 buf[4 ]; +struct i2c_msg msgs[1 ] = { + { .addr = client->addr, .len =4 , .buf = buf } }; +int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ;
Could be done 'slightly' more efficiently by using the fact these are clearly u16s. Then in the case where the system is of the same endianness these become simple copies. cpu_to_be16 should do the trick (assuming I've got the write one;)
Make buf __be16 and this becomes explicit in the code which is always a nice bonus.
Quoted 23 lines
drivers/iio/light/vl6180.c new file +23
-0+ buf[2 ] = val >>8 ; + buf[3 ] = val &0xff ; + + ret =i2c_transfer (client->adapter, msgs,1 ); +if (ret <0 ) { +dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } + +return 0 ; +} + +static int vl6180_measure (struct vl6180_data *data,int addr) +{ +struct i2c_client *client = data->client; +int tries =20 , ret; +u16 value; + +mutex_lock (&data->lock); +/* Start single shot measurement */ + ret =vl6180_write_byte (client, + vl6180_chan_regs_table[addr].start_reg,VL6180_STARTSTOP ); +if (ret <0 )
drivers/iio/light/vl6180.c line 227
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ goto fail; + +while (tries--) { + ret =vl6180_read_byte (client,VL6180_INTR_STATUS ); +if (ret <0 ) + goto fail; + +if (ret & vl6180_chan_regs_table[addr].drdy_mask) +break ; +msleep (20 );
Presumably we can know approximately how long this is going to take depending on state and perhaps reduce the bus traffic by guessing 'about' right.
Quoted 39 lines
drivers/iio/light/vl6180.c new file +39
-0+ } + +if (tries <0 ) { + ret = -EIO ; + goto fail; + } + +/* Read result value from appropriate registers */ + ret = vl6180_chan_regs_table[addr].word ? +vl6180_read_word (client, vl6180_chan_regs_table[addr].value_reg) : +vl6180_read_byte (client, vl6180_chan_regs_table[addr].value_reg); +if (ret <0 ) + goto fail; + value = ret; + +/* Clear the interrupt flag after data read */ + ret =vl6180_write_byte (client,VL6180_INTR_CLEAR , +VL6180_CLEAR_ERROR |VL6180_CLEAR_ALS |VL6180_CLEAR_RANGE ); +if (ret <0 ) + goto fail; + + ret = value; + +fail: +mutex_unlock (&data->lock); + +return ret; +} + +static const struct iio_chan_spec vl6180_channels[] = { + { + .type =IIO_LIGHT , + .address =VL6180_ALS , + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_INT_TIME ) | +BIT (IIO_CHAN_INFO_SCALE ) | +BIT (IIO_CHAN_INFO_HARDWAREGAIN ), + }, { + .type =IIO_DISTANCE ,
drivers/iio/light/vl6180.c line 276
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ .address = VL6180_RANGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_PROXIMITY, + .address = VL6180_PROX, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; +
As Peter raised, not that obvious what these are. Please add a comment explaining.
Quoted 147 lines
drivers/iio/light/vl6180.c new file +147
-0+static const int vl6180_als_gain[8 ][4 ] = { + {1 ,0 ,70 ,VL6180_ALS_GAIN_1 }, + {1 ,250000 ,69 ,VL6180_ALS_GAIN_1_25 }, + {1 ,670000 ,68 ,VL6180_ALS_GAIN_1_67 }, + {2 ,500000 ,67 ,VL6180_ALS_GAIN_2_5 }, + {5 ,0 ,66 ,VL6180_ALS_GAIN_5 }, + {10 ,0 ,65 ,VL6180_ALS_GAIN_10 }, + {20 ,0 ,64 ,VL6180_ALS_GAIN_20 }, + {40 ,0 ,71 ,VL6180_ALS_GAIN_40 } +}; + + +static int vl6180_read_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int *val,int *val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); +int ret, i; + +switch (mask) { +case IIO_CHAN_INFO_RAW : + ret =vl6180_measure (data, chan->address); +if (ret <0 ) +return ret; + *val = ret; + +return IIO_VAL_INT ; +case IIO_CHAN_INFO_INT_TIME : + ret =vl6180_read_word (data->client,VL6180_ALS_IT ); +if (ret <0 ) +return ret; + *val =0 ;/* 1 count = 1ms (0 = 1ms) */ + *val2 = (ret +1 ) *1000 ;/* convert to seconds */ + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_SCALE : +switch (chan->type) { +case IIO_LIGHT : + *val =0 ;/* one ALS count is 0.32 Lux */ + *val2 =320000 ; +break ; +case IIO_DISTANCE : + *val =0 ;/* sensor reports mm, scale to meter */ + *val2 =1000 ; +break ; +default : +return -EINVAL ; + } + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_HARDWAREGAIN : + ret =vl6180_read_byte (data->client,VL6180_ALS_GAIN ); +if (ret <0 ) +return -EINVAL ; +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (ret == vl6180_als_gain[i][2 ]) { + *val = vl6180_als_gain[i][0 ]; + *val2 = vl6180_als_gain[i][1 ]; + } + } + +return IIO_VAL_INT_PLUS_MICRO ; +default : +return -EINVAL ; + } +} + +static IIO_CONST_ATTR(als_gain_available, "1 1.25 1.67 2.5 5 10 20 40"); + +static struct attribute *vl6180_attributes[] = { + &iio_const_attr_als_gain_available.dev_attr.attr, +NULL +}; + +static const struct attribute_group vl6180_attribute_group = { + .attrs = vl6180_attributes, +}; + +/* HOLD is needed before updading any config registers */ +static int vl6180_hold (struct vl6180_data *data,bool hold) +{ +return vl6180_write_byte (data->client,VL6180_HOLD , + hold ?VL6180_HOLD_ON :0 ); +} + +static int vl6180_set_als_gain (struct vl6180_data *data,int val,int val2) +{ +int i, ret; + +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (val == vl6180_als_gain[i][0 ] && + val2 == vl6180_als_gain[i][1 ]) { +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_byte (data->client,VL6180_ALS_GAIN , + vl6180_als_gain[i][3 ]); +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); +return ret; + } + } + +return -EINVAL ; +} + +static int vl6180_set_it (struct vl6180_data *data,int val2) +{ +int ret; + +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_word (data->client,VL6180_ALS_IT , + (val2 -500 ) /1000 );/* write value in ms */ +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); + +return ret; +} + +static int vl6180_write_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int val,int val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); + +switch (mask) { +case IIO_CHAN_INFO_INT_TIME : +if (val !=0 || val2 <500 || val2 >=512500 ) +return -EINVAL ; + +return vl6180_set_it (data, val2); +case IIO_CHAN_INFO_HARDWAREGAIN : +if (chan->type !=IIO_LIGHT ) +return -EINVAL ; + +return vl6180_set_als_gain (data, val, val2); +default : +return -EINVAL ; + } +} +
drivers/iio/light/vl6180.c line 433
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+static const struct iio_info vl6180_info = { + .read_raw = vl6180_read_raw, + .write_raw = vl6180_write_raw, + .attrs = &vl6180_attribute_group, + .driver_module =THIS_MODULE , +}; + +/** +* Following settings are enabled: +* 1. ALS and Range ready interrupts
I'd prefer to see these comments next to the place they are implemented. Much less likely to end up out of date there.
Quoted 19 lines
drivers/iio/light/vl6180.c new file +19
-0+* 2. ALS integration time: 100ms +* 3. ALS gain: 1 +*/ +static int vl6180_init (struct vl6180_data *data) +{ +struct i2c_client *client = data->client; +int ret; + + ret =vl6180_read_byte (client,VL6180_MODEL_ID ); +if (ret <0 ) +return ret; + +if (ret !=VL6180_MODEL_ID_VAL ) { +dev_err (&client->dev,"invalid model ID %02x\n" , ret); +return -ENODEV ; + } + + ret =vl6180_hold (data, true); +if (ret <0 )
drivers/iio/light/vl6180.c line 462
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+return ret; + + ret =vl6180_read_byte (client,VL6180_OUT_OF_RESET ); +if (ret <0 ) +return ret; + +if (ret !=0x01 ) { +dev_err (&client->dev,"device is not fresh out of reset\n" ); +return -EINVAL ; + }
Why would it necessarily be fresh out of reset? If it is in some fashion supposed to be guaranteed here, please add a comment explaining why.
Quoted 41 lines
drivers/iio/light/vl6180.c new file +41
-0+ + ret =vl6180_write_byte (client,VL6180_INTR_CONFIG , +VL6180_ALS_READY |VL6180_RANGE_READY ); +if (ret <0 ) +return ret; + + ret =vl6180_write_word (client,VL6180_ALS_IT ,VL6180_ALS_IT_100 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_ALS_GAIN ,VL6180_ALS_GAIN_1 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_OUT_OF_RESET ,0x00 ); +if (ret <0 ) +return ret; + +return vl6180_hold (data, false); +} + +static int vl6180_probe (struct i2c_client *client, +const struct i2c_device_id *id) +{ +struct vl6180_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); +i2c_set_clientdata (client, indio_dev); + data->client = client; +mutex_init (&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &vl6180_info; + indio_dev->channels = vl6180_channels; + indio_dev->num_channels =ARRAY_SIZE (vl6180_channels);
drivers/iio/light/vl6180.c line 513
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ indio_dev->name =VL6180_DRV_NAME ; + indio_dev->modes =INDIO_DIRECT_MODE ; + + ret =vl6180_init (data); +if (ret <0 ) +return ret; + + ret =devm_iio_device_register (&client->dev, indio_dev); +if (ret <0 ) +return ret;
return devm_iio_device_register directly.
The static checkers will fire on this anyway, but best to fix it now.
Quoted 22 lines
drivers/iio/light/vl6180.c new file +21
-0+ +return ret; +} + +static const struct of_device_id vl6180_of_match[] = { + { .compatible ="st,vl6180" , }, + { }, +}; +MODULE_DEVICE_TABLE (of, vl6180_of_match); + +static const struct i2c_device_id vl6180_id[] = { + {"vl6180" ,0 }, + { } +}; +MODULE_DEVICE_TABLE (i2c, vl6180_id); + +static struct i2c_driver vl6180_driver = { + .driver = { + .name =VL6180_DRV_NAME , + .of_match_table =of_match_ptr (vl6180_of_match), + },
drivers/iio/light/vl6180.c line 544
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ .probe = vl6180_probe, + .id_table = vl6180_id, +}; + +module_i2c_driver (vl6180_driver); + +MODULE_AUTHOR ("Peter Meerwald-Stadler <[email protected]>" ); +MODULE_AUTHOR ("Manivannan Sadhasivam <[email protected]>" ); +MODULE_DESCRIPTION ("STMicro VL6180 ALS and proximity sensor driver" ); +MODULE_LICENSE ("GPL" );
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
A message that is not in the archive (sent off-list or not yet received): 2e6ed211-baf2-9877-fbb4-b39aa75f4dee-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org
Quoted 6 lines · Jonathan Cameron
Quoted 4 lines · Manivannan Sadhasivam
This patch adds support for STMicro VL6180 - ALS and proximity sensor. Sensor is capable of measuring the light intensity as well as object distance using TOF(Time of Flight) technology.
Signed-off-by Manivannan Sadhasivam <[email protected]>Few more comments from me.
One more thing (from browsing datasheet). I would have expected to see some control of the gpios as it seems to have a very explicit start up sequence described. Is this something you are doing some other way?
Jonathan
Quoted 642 lines
Jonathan
Quoted 188 lines
--- .../devicetree/bindings/iio/light/vl6180.txt | 16 + drivers/iio/light/Kconfig | 10 + drivers/iio/light/Makefile | 1 + drivers/iio/light/vl6180.c | 544 +++++++++++++++++++++ 4 files changed, 571 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/vl6180.txt create mode 100644 drivers/iio/light/vl6180.cDocumentation/devicetree/bindings/iio/light/vl6180.txt new file +16
-0@@ -0,0 +1,16 @@ +STMicro VL6180 - ALS and proximity sensor + +Link to datasheet: http://www.st.com/resource/en/datasheet/vl6180x.pdf + +Required properties: + + -compatible: should be "st,vl6180" + -reg: the I2C address of the sensor + +Example: + +vl6180@29 { + compatible = "st,vl6180"; + reg = <0x29>; +}; +drivers/iio/light/Kconfig +10
-0@@ -352,4 +352,14 @@ config VEML6070 To compile this driver as a module, choose M here: the module will be called veml6070. +config VL6180 + tristate "VL6180 ALS and proximity sensor" + depends on I2C + help + Say Y here if you want to build a driver for the STMicroelectronics + VL6180 combined ambient light and proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called vl6180. + endmenudrivers/iio/light/Makefile +1
-0@@ -33,3 +33,4 @@ obj-$(CONFIG_TSL4531) += tsl4531.o obj-$(CONFIG_US5182D) += us5182d.o obj-$(CONFIG_VCNL4000) += vcnl4000.o obj-$(CONFIG_VEML6070) += veml6070.o +obj-$(CONFIG_VL6180) += vl6180.odrivers/iio/light/vl6180.c new file +123
-0@@ -0,0 +1,544 @@ +/* +* vl6180.c - Support for STMicroelectronics VL6180 ALS and proximity sensor +* +* Copyright 2017 Peter Meerwald-Stadler <[email protected]> +* Copyright 2017 Manivannan Sadhasivam <[email protected]> +* +* This file is subject to the terms and conditions of version 2 of +* the GNU General Public License. See the file COPYING in the main +* directory of this archive for more details. +* +* IIO driver for VL6180 (7-bit I2C slave addresses 0x29) +* +* Range: 0 to 100mm +* ALS: < 1 Lux up to 100 kLux +* IR: 850nm +* +* TODO: irq, threshold events, continuous mode, hardware buffer +*/ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define VL6180_DRV_NAME "vl6180" + +/* Device identification register and value */ +#define VL6180_MODEL_ID 0x000 +#define VL6180_MODEL_ID_VAL 0xb4 + +/* Configuration registers */ +#define VL6180_INTR_CONFIG 0x014 +#define VL6180_INTR_CLEAR 0x015 +#define VL6180_OUT_OF_RESET 0x016 +#define VL6180_HOLD 0x017 +#define VL6180_RANGE_START 0x018 +#define VL6180_ALS_START 0x038 +#define VL6180_ALS_GAIN 0x03f +#define VL6180_ALS_IT 0x040 + +/* Status registers */ +#define VL6180_RANGE_STATUS 0x04d +#define VL6180_ALS_STATUS 0x04e +#define VL6180_INTR_STATUS 0x04f + +/* Result value registers */ +#define VL6180_ALS_VALUE 0x050 +#define VL6180_RANGE_VALUE 0x062 +#define VL6180_RANGE_RATE 0x066 + +/* bits of the RANGE_START and ALS_START register */ +#define VL6180_MODE_CONT BIT(1)/* continuous mode */ +#define VL6180_STARTSTOP BIT(0)/* start measurement, auto-reset */ + +/* bits of the INTR_STATUS and INTR_CONFIG register */ +#define VL6180_ALS_READY BIT(5) +#define VL6180_RANGE_READY BIT(2) + +/* bits of the INTR_CLEAR register */ +#define VL6180_CLEAR_ERROR BIT(2) +#define VL6180_CLEAR_ALS BIT(1) +#define VL6180_CLEAR_RANGE BIT(0) + +/* bits of the HOLD register */ +#define VL6180_HOLD_ON BIT(0) + +/* default value for the ALS_IT register */ +#define VL6180_ALS_IT_100 0x063/* 100 ms */ + +/* values for the ALS_GAIN register */ +#define VL6180_ALS_GAIN_1 0x46 +#define VL6180_ALS_GAIN_1_25 0x45 +#define VL6180_ALS_GAIN_1_67 0x44 +#define VL6180_ALS_GAIN_2_5 0x43 +#define VL6180_ALS_GAIN_5 0x42 +#define VL6180_ALS_GAIN_10 0x41 +#define VL6180_ALS_GAIN_20 0x40 +#define VL6180_ALS_GAIN_40 0x47 + +struct vl6180_data { +struct i2c_client *client; +struct mutex lock; +}; + +enum {VL6180_ALS ,VL6180_RANGE ,VL6180_PROX }; + +/** +* struct vl6180_chan_regs - Registers for accessing channels +* @drdy_mask: Data ready bits in status register +* @start_reg: Conversion start registeir +* @value_reg: Result value register +*/ +struct vl6180_chan_regs { +u8 drdy_mask; +u16 start_reg, value_reg; +bool word; +}; + +static const struct vl6180_chan_regs vl6180_chan_regs_table[] = { + [VL6180_ALS ] = { + .drdy_mask =VL6180_ALS_READY , + .start_reg =VL6180_ALS_START , + .value_reg =VL6180_ALS_VALUE , + .word = true, + }, + [VL6180_RANGE ] = { + .drdy_mask =VL6180_RANGE_READY , + .start_reg =VL6180_RANGE_START , + .value_reg =VL6180_RANGE_VALUE , + .word = false, + }, + [VL6180_PROX ] = { + .drdy_mask =VL6180_RANGE_READY , + .start_reg =VL6180_RANGE_START , + .value_reg =VL6180_RANGE_RATE , + .word = true, + }, +};drivers/iio/light/vl6180.c line 133
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ +static int vl6180_read (struct i2c_client *client,u16 cmd,void *databuf, +u8 len) +{ +u16 cmdbuf =cpu_to_be16 (cmd); +struct i2c_msg msgs[2 ] = { + { .addr = client->addr, .len =2 , .buf = (u8 *) &cmdbuf }, + { .addr = client->addr, .len = len, .buf = databuf, + .flags =I2C_M_RD } }; +int ret;Hmm. could wrap this up in regmap rather neatly I think, but fair enough to keep it all obvious here if you prefer.
Quoted 31 lines
drivers/iio/light/vl6180.c new file +31
-0+ + ret =i2c_transfer (client->adapter, msgs,2 ); +if (ret <0 ) +dev_err (&client->dev,"failed reading register 0x%04x\n" , cmd); + +return ret; +} + +static int vl6180_read_byte (struct i2c_client *client,u16 cmd) +{ +u8 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 ) +return ret; + +return data; +} + +static int vl6180_read_word (struct i2c_client *client,u16 cmd) +{ +__be16 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 ) +return ret; + +return be16_to_cpu (data); +}drivers/iio/light/vl6180.c line 174
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ +static int vl6180_write_byte (struct i2c_client *client,u16 cmd,u8 val) +{ +u8 buf[3 ]; +struct i2c_msg msgs[1 ] = { + { .addr = client->addr, .len =3 , .buf = buf } }; +int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ;Here you end up with a mix that makes it harder to force the endianness than below, but it can still be done by using
cpu_to_be16pand again makes that conversion explicit.Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ buf[2 ] = val; + + ret =i2c_transfer (client->adapter, msgs,1 ); +if (ret <0 ) { +dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } + +return 0 ; +}drivers/iio/light/vl6180.c line 194
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ +static int vl6180_write_word (struct i2c_client *client,u16 cmd,u16 val) +{ +u8 buf[4 ]; +struct i2c_msg msgs[1 ] = { + { .addr = client->addr, .len =4 , .buf = buf } }; +int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ;Could be done 'slightly' more efficiently by using the fact these are clearly u16s. Then in the case where the system is of the same endianness these become simple copies.
cpu_to_be16should do the trick (assuming I've got the write one;)Make buf
__be16and this becomes explicit in the code which is always a nice bonus.Quoted 23 lines
drivers/iio/light/vl6180.c new file +23
-0+ buf[2 ] = val >>8 ; + buf[3 ] = val &0xff ; + + ret =i2c_transfer (client->adapter, msgs,1 ); +if (ret <0 ) { +dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } + +return 0 ; +} + +static int vl6180_measure (struct vl6180_data *data,int addr) +{ +struct i2c_client *client = data->client; +int tries =20 , ret; +u16 value; + +mutex_lock (&data->lock); +/* Start single shot measurement */ + ret =vl6180_write_byte (client, + vl6180_chan_regs_table[addr].start_reg,VL6180_STARTSTOP ); +if (ret <0 )drivers/iio/light/vl6180.c line 227
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ goto fail; + +while (tries--) { + ret =vl6180_read_byte (client,VL6180_INTR_STATUS ); +if (ret <0 ) + goto fail; + +if (ret & vl6180_chan_regs_table[addr].drdy_mask) +break ; +msleep (20 );Presumably we can know approximately how long this is going to take depending on state and perhaps reduce the bus traffic by guessing 'about' right.
Quoted 39 lines
drivers/iio/light/vl6180.c new file +39
-0+ } + +if (tries <0 ) { + ret = -EIO ; + goto fail; + } + +/* Read result value from appropriate registers */ + ret = vl6180_chan_regs_table[addr].word ? +vl6180_read_word (client, vl6180_chan_regs_table[addr].value_reg) : +vl6180_read_byte (client, vl6180_chan_regs_table[addr].value_reg); +if (ret <0 ) + goto fail; + value = ret; + +/* Clear the interrupt flag after data read */ + ret =vl6180_write_byte (client,VL6180_INTR_CLEAR , +VL6180_CLEAR_ERROR |VL6180_CLEAR_ALS |VL6180_CLEAR_RANGE ); +if (ret <0 ) + goto fail; + + ret = value; + +fail: +mutex_unlock (&data->lock); + +return ret; +} + +static const struct iio_chan_spec vl6180_channels[] = { + { + .type =IIO_LIGHT , + .address =VL6180_ALS , + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_INT_TIME ) | +BIT (IIO_CHAN_INFO_SCALE ) | +BIT (IIO_CHAN_INFO_HARDWAREGAIN ), + }, { + .type =IIO_DISTANCE ,drivers/iio/light/vl6180.c line 276
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ .address = VL6180_RANGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_PROXIMITY, + .address = VL6180_PROX, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; +As Peter raised, not that obvious what these are. Please add a comment explaining.
Quoted 147 lines
drivers/iio/light/vl6180.c new file +147
-0+static const int vl6180_als_gain[8 ][4 ] = { + {1 ,0 ,70 ,VL6180_ALS_GAIN_1 }, + {1 ,250000 ,69 ,VL6180_ALS_GAIN_1_25 }, + {1 ,670000 ,68 ,VL6180_ALS_GAIN_1_67 }, + {2 ,500000 ,67 ,VL6180_ALS_GAIN_2_5 }, + {5 ,0 ,66 ,VL6180_ALS_GAIN_5 }, + {10 ,0 ,65 ,VL6180_ALS_GAIN_10 }, + {20 ,0 ,64 ,VL6180_ALS_GAIN_20 }, + {40 ,0 ,71 ,VL6180_ALS_GAIN_40 } +}; + + +static int vl6180_read_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int *val,int *val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); +int ret, i; + +switch (mask) { +case IIO_CHAN_INFO_RAW : + ret =vl6180_measure (data, chan->address); +if (ret <0 ) +return ret; + *val = ret; + +return IIO_VAL_INT ; +case IIO_CHAN_INFO_INT_TIME : + ret =vl6180_read_word (data->client,VL6180_ALS_IT ); +if (ret <0 ) +return ret; + *val =0 ;/* 1 count = 1ms (0 = 1ms) */ + *val2 = (ret +1 ) *1000 ;/* convert to seconds */ + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_SCALE : +switch (chan->type) { +case IIO_LIGHT : + *val =0 ;/* one ALS count is 0.32 Lux */ + *val2 =320000 ; +break ; +case IIO_DISTANCE : + *val =0 ;/* sensor reports mm, scale to meter */ + *val2 =1000 ; +break ; +default : +return -EINVAL ; + } + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_HARDWAREGAIN : + ret =vl6180_read_byte (data->client,VL6180_ALS_GAIN ); +if (ret <0 ) +return -EINVAL ; +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (ret == vl6180_als_gain[i][2 ]) { + *val = vl6180_als_gain[i][0 ]; + *val2 = vl6180_als_gain[i][1 ]; + } + } + +return IIO_VAL_INT_PLUS_MICRO ; +default : +return -EINVAL ; + } +} + +static IIO_CONST_ATTR(als_gain_available, "1 1.25 1.67 2.5 5 10 20 40"); + +static struct attribute *vl6180_attributes[] = { + &iio_const_attr_als_gain_available.dev_attr.attr, +NULL +}; + +static const struct attribute_group vl6180_attribute_group = { + .attrs = vl6180_attributes, +}; + +/* HOLD is needed before updading any config registers */ +static int vl6180_hold (struct vl6180_data *data,bool hold) +{ +return vl6180_write_byte (data->client,VL6180_HOLD , + hold ?VL6180_HOLD_ON :0 ); +} + +static int vl6180_set_als_gain (struct vl6180_data *data,int val,int val2) +{ +int i, ret; + +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (val == vl6180_als_gain[i][0 ] && + val2 == vl6180_als_gain[i][1 ]) { +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_byte (data->client,VL6180_ALS_GAIN , + vl6180_als_gain[i][3 ]); +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); +return ret; + } + } + +return -EINVAL ; +} + +static int vl6180_set_it (struct vl6180_data *data,int val2) +{ +int ret; + +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_word (data->client,VL6180_ALS_IT , + (val2 -500 ) /1000 );/* write value in ms */ +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); + +return ret; +} + +static int vl6180_write_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int val,int val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); + +switch (mask) { +case IIO_CHAN_INFO_INT_TIME : +if (val !=0 || val2 <500 || val2 >=512500 ) +return -EINVAL ; + +return vl6180_set_it (data, val2); +case IIO_CHAN_INFO_HARDWAREGAIN : +if (chan->type !=IIO_LIGHT ) +return -EINVAL ; + +return vl6180_set_als_gain (data, val, val2); +default : +return -EINVAL ; + } +} +drivers/iio/light/vl6180.c line 433
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+static const struct iio_info vl6180_info = { + .read_raw = vl6180_read_raw, + .write_raw = vl6180_write_raw, + .attrs = &vl6180_attribute_group, + .driver_module =THIS_MODULE , +}; + +/** +* Following settings are enabled: +* 1. ALS and Range ready interrupts I'd prefer to see these comments next to the place they are implemented. Much less likely to end up out of date there.
Quoted 19 lines
drivers/iio/light/vl6180.c new file +19
-0+* 2. ALS integration time: 100ms +* 3. ALS gain: 1 +*/ +static int vl6180_init (struct vl6180_data *data) +{ +struct i2c_client *client = data->client; +int ret; + + ret =vl6180_read_byte (client,VL6180_MODEL_ID ); +if (ret <0 ) +return ret; + +if (ret !=VL6180_MODEL_ID_VAL ) { +dev_err (&client->dev,"invalid model ID %02x\n" , ret); +return -ENODEV ; + } + + ret =vl6180_hold (data, true); +if (ret <0 )drivers/iio/light/vl6180.c line 462
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+return ret; + + ret =vl6180_read_byte (client,VL6180_OUT_OF_RESET ); +if (ret <0 ) +return ret; + +if (ret !=0x01 ) { +dev_err (&client->dev,"device is not fresh out of reset\n" ); +return -EINVAL ; + }Why would it necessarily be fresh out of reset? If it is in some fashion supposed to be guaranteed here, please add a comment explaining why.
Quoted 41 lines
drivers/iio/light/vl6180.c new file +41
-0+ + ret =vl6180_write_byte (client,VL6180_INTR_CONFIG , +VL6180_ALS_READY |VL6180_RANGE_READY ); +if (ret <0 ) +return ret; + + ret =vl6180_write_word (client,VL6180_ALS_IT ,VL6180_ALS_IT_100 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_ALS_GAIN ,VL6180_ALS_GAIN_1 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_OUT_OF_RESET ,0x00 ); +if (ret <0 ) +return ret; + +return vl6180_hold (data, false); +} + +static int vl6180_probe (struct i2c_client *client, +const struct i2c_device_id *id) +{ +struct vl6180_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); +i2c_set_clientdata (client, indio_dev); + data->client = client; +mutex_init (&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &vl6180_info; + indio_dev->channels = vl6180_channels; + indio_dev->num_channels =ARRAY_SIZE (vl6180_channels);drivers/iio/light/vl6180.c line 513
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ indio_dev->name =VL6180_DRV_NAME ; + indio_dev->modes =INDIO_DIRECT_MODE ; + + ret =vl6180_init (data); +if (ret <0 ) +return ret; + + ret =devm_iio_device_register (&client->dev, indio_dev); +if (ret <0 ) +return ret;return
devm_iio_device_registerdirectly.The static checkers will fire on this anyway, but best to fix it now.
Quoted 22 lines
drivers/iio/light/vl6180.c new file +21
-0+ +return ret; +} + +static const struct of_device_id vl6180_of_match[] = { + { .compatible ="st,vl6180" , }, + { }, +}; +MODULE_DEVICE_TABLE (of, vl6180_of_match); + +static const struct i2c_device_id vl6180_id[] = { + {"vl6180" ,0 }, + { } +}; +MODULE_DEVICE_TABLE (i2c, vl6180_id); + +static struct i2c_driver vl6180_driver = { + .driver = { + .name =VL6180_DRV_NAME , + .of_match_table =of_match_ptr (vl6180_of_match), + },drivers/iio/light/vl6180.c line 544
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ .probe = vl6180_probe, + .id_table = vl6180_id, +}; + +module_i2c_driver (vl6180_driver); + +MODULE_AUTHOR ("Peter Meerwald-Stadler <[email protected]>" ); +MODULE_AUTHOR ("Manivannan Sadhasivam <[email protected]>" ); +MODULE_DESCRIPTION ("STMicro VL6180 ALS and proximity sensor driver" ); +MODULE_LICENSE ("GPL" );--
To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
A message that is not in the archive (sent off-list or not yet received): e205a6eb-e7cf-2f19-5b81-15dd70d573a7-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org
Quoted 11 lines · Jonathan Cameron
Quoted 6 lines · Jonathan Cameron
Quoted 4 lines · Manivannan Sadhasivam
This patch adds support for STMicro VL6180 - ALS and proximity sensor. Sensor is capable of measuring the light intensity as well as object distance using TOF(Time of Flight) technology.
Signed-off-by Manivannan Sadhasivam <[email protected]>Few more comments from me.
One more thing (from browsing datasheet). I would have expected to see some control of the gpios as it seems to have a very explicit start up sequence described. Is this something you are doing some other way?
Hi Jonathan,
Though the use of GPIO0 during start-up is recommended in the datasheet, it is also mentioned that it could be optional. Most of the breakout boards available has the GPIO0 connected to AVDD, which ensures proper hw reset.
-Mani
Quoted 644 lines
Jonathan
Quoted 642 lines
Jonathan
Quoted 188 lines
--- .../devicetree/bindings/iio/light/vl6180.txt | 16 + drivers/iio/light/Kconfig | 10 + drivers/iio/light/Makefile | 1 + drivers/iio/light/vl6180.c | 544 +++++++++++++++++++++ 4 files changed, 571 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/vl6180.txt create mode 100644 drivers/iio/light/vl6180.cDocumentation/devicetree/bindings/iio/light/vl6180.txt new file +16
-0@@ -0,0 +1,16 @@ +STMicro VL6180 - ALS and proximity sensor + +Link to datasheet: http://www.st.com/resource/en/datasheet/vl6180x.pdf + +Required properties: + + -compatible: should be "st,vl6180" + -reg: the I2C address of the sensor + +Example: + +vl6180@29 { + compatible = "st,vl6180"; + reg = <0x29>; +}; +drivers/iio/light/Kconfig +10
-0@@ -352,4 +352,14 @@ config VEML6070 To compile this driver as a module, choose M here: the module will be called veml6070. +config VL6180 + tristate "VL6180 ALS and proximity sensor" + depends on I2C + help + Say Y here if you want to build a driver for the STMicroelectronics + VL6180 combined ambient light and proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called vl6180. + endmenudrivers/iio/light/Makefile +1
-0@@ -33,3 +33,4 @@ obj-$(CONFIG_TSL4531) += tsl4531.o obj-$(CONFIG_US5182D) += us5182d.o obj-$(CONFIG_VCNL4000) += vcnl4000.o obj-$(CONFIG_VEML6070) += veml6070.o +obj-$(CONFIG_VL6180) += vl6180.odrivers/iio/light/vl6180.c new file +123
-0@@ -0,0 +1,544 @@ +/* +* vl6180.c - Support for STMicroelectronics VL6180 ALS and proximity sensor +* +* Copyright 2017 Peter Meerwald-Stadler <[email protected]> +* Copyright 2017 Manivannan Sadhasivam <[email protected]> +* +* This file is subject to the terms and conditions of version 2 of +* the GNU General Public License. See the file COPYING in the main +* directory of this archive for more details. +* +* IIO driver for VL6180 (7-bit I2C slave addresses 0x29) +* +* Range: 0 to 100mm +* ALS: < 1 Lux up to 100 kLux +* IR: 850nm +* +* TODO: irq, threshold events, continuous mode, hardware buffer +*/ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define VL6180_DRV_NAME "vl6180" + +/* Device identification register and value */ +#define VL6180_MODEL_ID 0x000 +#define VL6180_MODEL_ID_VAL 0xb4 + +/* Configuration registers */ +#define VL6180_INTR_CONFIG 0x014 +#define VL6180_INTR_CLEAR 0x015 +#define VL6180_OUT_OF_RESET 0x016 +#define VL6180_HOLD 0x017 +#define VL6180_RANGE_START 0x018 +#define VL6180_ALS_START 0x038 +#define VL6180_ALS_GAIN 0x03f +#define VL6180_ALS_IT 0x040 + +/* Status registers */ +#define VL6180_RANGE_STATUS 0x04d +#define VL6180_ALS_STATUS 0x04e +#define VL6180_INTR_STATUS 0x04f + +/* Result value registers */ +#define VL6180_ALS_VALUE 0x050 +#define VL6180_RANGE_VALUE 0x062 +#define VL6180_RANGE_RATE 0x066 + +/* bits of the RANGE_START and ALS_START register */ +#define VL6180_MODE_CONT BIT(1)/* continuous mode */ +#define VL6180_STARTSTOP BIT(0)/* start measurement, auto-reset */ + +/* bits of the INTR_STATUS and INTR_CONFIG register */ +#define VL6180_ALS_READY BIT(5) +#define VL6180_RANGE_READY BIT(2) + +/* bits of the INTR_CLEAR register */ +#define VL6180_CLEAR_ERROR BIT(2) +#define VL6180_CLEAR_ALS BIT(1) +#define VL6180_CLEAR_RANGE BIT(0) + +/* bits of the HOLD register */ +#define VL6180_HOLD_ON BIT(0) + +/* default value for the ALS_IT register */ +#define VL6180_ALS_IT_100 0x063/* 100 ms */ + +/* values for the ALS_GAIN register */ +#define VL6180_ALS_GAIN_1 0x46 +#define VL6180_ALS_GAIN_1_25 0x45 +#define VL6180_ALS_GAIN_1_67 0x44 +#define VL6180_ALS_GAIN_2_5 0x43 +#define VL6180_ALS_GAIN_5 0x42 +#define VL6180_ALS_GAIN_10 0x41 +#define VL6180_ALS_GAIN_20 0x40 +#define VL6180_ALS_GAIN_40 0x47 + +struct vl6180_data { +struct i2c_client *client; +struct mutex lock; +}; + +enum {VL6180_ALS ,VL6180_RANGE ,VL6180_PROX }; + +/** +* struct vl6180_chan_regs - Registers for accessing channels +* @drdy_mask: Data ready bits in status register +* @start_reg: Conversion start registeir +* @value_reg: Result value register +*/ +struct vl6180_chan_regs { +u8 drdy_mask; +u16 start_reg, value_reg; +bool word; +}; + +static const struct vl6180_chan_regs vl6180_chan_regs_table[] = { + [VL6180_ALS ] = { + .drdy_mask =VL6180_ALS_READY , + .start_reg =VL6180_ALS_START , + .value_reg =VL6180_ALS_VALUE , + .word = true, + }, + [VL6180_RANGE ] = { + .drdy_mask =VL6180_RANGE_READY , + .start_reg =VL6180_RANGE_START , + .value_reg =VL6180_RANGE_VALUE , + .word = false, + }, + [VL6180_PROX ] = { + .drdy_mask =VL6180_RANGE_READY , + .start_reg =VL6180_RANGE_START , + .value_reg =VL6180_RANGE_RATE , + .word = true, + }, +};drivers/iio/light/vl6180.c line 133
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ +static int vl6180_read (struct i2c_client *client,u16 cmd,void *databuf, +u8 len) +{ +u16 cmdbuf =cpu_to_be16 (cmd); +struct i2c_msg msgs[2 ] = { + { .addr = client->addr, .len =2 , .buf = (u8 *) &cmdbuf }, + { .addr = client->addr, .len = len, .buf = databuf, + .flags =I2C_M_RD } }; +int ret;Hmm. could wrap this up in regmap rather neatly I think, but fair enough to keep it all obvious here if you prefer.
Quoted 31 lines
drivers/iio/light/vl6180.c new file +31
-0+ + ret =i2c_transfer (client->adapter, msgs,2 ); +if (ret <0 ) +dev_err (&client->dev,"failed reading register 0x%04x\n" , cmd); + +return ret; +} + +static int vl6180_read_byte (struct i2c_client *client,u16 cmd) +{ +u8 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 ) +return ret; + +return data; +} + +static int vl6180_read_word (struct i2c_client *client,u16 cmd) +{ +__be16 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 ) +return ret; + +return be16_to_cpu (data); +}drivers/iio/light/vl6180.c line 174
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ +static int vl6180_write_byte (struct i2c_client *client,u16 cmd,u8 val) +{ +u8 buf[3 ]; +struct i2c_msg msgs[1 ] = { + { .addr = client->addr, .len =3 , .buf = buf } }; +int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ;Here you end up with a mix that makes it harder to force the endianness than below, but it can still be done by using
cpu_to_be16pand again makes that conversion explicit.Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ buf[2 ] = val; + + ret =i2c_transfer (client->adapter, msgs,1 ); +if (ret <0 ) { +dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } + +return 0 ; +}drivers/iio/light/vl6180.c line 194
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ +static int vl6180_write_word (struct i2c_client *client,u16 cmd,u16 val) +{ +u8 buf[4 ]; +struct i2c_msg msgs[1 ] = { + { .addr = client->addr, .len =4 , .buf = buf } }; +int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ;Could be done 'slightly' more efficiently by using the fact these are clearly u16s. Then in the case where the system is of the same endianness these become simple copies.
cpu_to_be16should do the trick (assuming I've got the write one;)Make buf
__be16and this becomes explicit in the code which is always a nice bonus.Quoted 23 lines
drivers/iio/light/vl6180.c new file +23
-0+ buf[2 ] = val >>8 ; + buf[3 ] = val &0xff ; + + ret =i2c_transfer (client->adapter, msgs,1 ); +if (ret <0 ) { +dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } + +return 0 ; +} + +static int vl6180_measure (struct vl6180_data *data,int addr) +{ +struct i2c_client *client = data->client; +int tries =20 , ret; +u16 value; + +mutex_lock (&data->lock); +/* Start single shot measurement */ + ret =vl6180_write_byte (client, + vl6180_chan_regs_table[addr].start_reg,VL6180_STARTSTOP ); +if (ret <0 )drivers/iio/light/vl6180.c line 227
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ goto fail; + +while (tries--) { + ret =vl6180_read_byte (client,VL6180_INTR_STATUS ); +if (ret <0 ) + goto fail; + +if (ret & vl6180_chan_regs_table[addr].drdy_mask) +break ; +msleep (20 );Presumably we can know approximately how long this is going to take depending on state and perhaps reduce the bus traffic by guessing 'about' right.
Quoted 39 lines
drivers/iio/light/vl6180.c new file +39
-0+ } + +if (tries <0 ) { + ret = -EIO ; + goto fail; + } + +/* Read result value from appropriate registers */ + ret = vl6180_chan_regs_table[addr].word ? +vl6180_read_word (client, vl6180_chan_regs_table[addr].value_reg) : +vl6180_read_byte (client, vl6180_chan_regs_table[addr].value_reg); +if (ret <0 ) + goto fail; + value = ret; + +/* Clear the interrupt flag after data read */ + ret =vl6180_write_byte (client,VL6180_INTR_CLEAR , +VL6180_CLEAR_ERROR |VL6180_CLEAR_ALS |VL6180_CLEAR_RANGE ); +if (ret <0 ) + goto fail; + + ret = value; + +fail: +mutex_unlock (&data->lock); + +return ret; +} + +static const struct iio_chan_spec vl6180_channels[] = { + { + .type =IIO_LIGHT , + .address =VL6180_ALS , + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_INT_TIME ) | +BIT (IIO_CHAN_INFO_SCALE ) | +BIT (IIO_CHAN_INFO_HARDWAREGAIN ), + }, { + .type =IIO_DISTANCE ,drivers/iio/light/vl6180.c line 276
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ .address = VL6180_RANGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_PROXIMITY, + .address = VL6180_PROX, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; +As Peter raised, not that obvious what these are. Please add a comment explaining.
Quoted 147 lines
drivers/iio/light/vl6180.c new file +147
-0+static const int vl6180_als_gain[8 ][4 ] = { + {1 ,0 ,70 ,VL6180_ALS_GAIN_1 }, + {1 ,250000 ,69 ,VL6180_ALS_GAIN_1_25 }, + {1 ,670000 ,68 ,VL6180_ALS_GAIN_1_67 }, + {2 ,500000 ,67 ,VL6180_ALS_GAIN_2_5 }, + {5 ,0 ,66 ,VL6180_ALS_GAIN_5 }, + {10 ,0 ,65 ,VL6180_ALS_GAIN_10 }, + {20 ,0 ,64 ,VL6180_ALS_GAIN_20 }, + {40 ,0 ,71 ,VL6180_ALS_GAIN_40 } +}; + + +static int vl6180_read_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int *val,int *val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); +int ret, i; + +switch (mask) { +case IIO_CHAN_INFO_RAW : + ret =vl6180_measure (data, chan->address); +if (ret <0 ) +return ret; + *val = ret; + +return IIO_VAL_INT ; +case IIO_CHAN_INFO_INT_TIME : + ret =vl6180_read_word (data->client,VL6180_ALS_IT ); +if (ret <0 ) +return ret; + *val =0 ;/* 1 count = 1ms (0 = 1ms) */ + *val2 = (ret +1 ) *1000 ;/* convert to seconds */ + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_SCALE : +switch (chan->type) { +case IIO_LIGHT : + *val =0 ;/* one ALS count is 0.32 Lux */ + *val2 =320000 ; +break ; +case IIO_DISTANCE : + *val =0 ;/* sensor reports mm, scale to meter */ + *val2 =1000 ; +break ; +default : +return -EINVAL ; + } + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_HARDWAREGAIN : + ret =vl6180_read_byte (data->client,VL6180_ALS_GAIN ); +if (ret <0 ) +return -EINVAL ; +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (ret == vl6180_als_gain[i][2 ]) { + *val = vl6180_als_gain[i][0 ]; + *val2 = vl6180_als_gain[i][1 ]; + } + } + +return IIO_VAL_INT_PLUS_MICRO ; +default : +return -EINVAL ; + } +} + +static IIO_CONST_ATTR(als_gain_available, "1 1.25 1.67 2.5 5 10 20 40"); + +static struct attribute *vl6180_attributes[] = { + &iio_const_attr_als_gain_available.dev_attr.attr, +NULL +}; + +static const struct attribute_group vl6180_attribute_group = { + .attrs = vl6180_attributes, +}; + +/* HOLD is needed before updading any config registers */ +static int vl6180_hold (struct vl6180_data *data,bool hold) +{ +return vl6180_write_byte (data->client,VL6180_HOLD , + hold ?VL6180_HOLD_ON :0 ); +} + +static int vl6180_set_als_gain (struct vl6180_data *data,int val,int val2) +{ +int i, ret; + +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (val == vl6180_als_gain[i][0 ] && + val2 == vl6180_als_gain[i][1 ]) { +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_byte (data->client,VL6180_ALS_GAIN , + vl6180_als_gain[i][3 ]); +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); +return ret; + } + } + +return -EINVAL ; +} + +static int vl6180_set_it (struct vl6180_data *data,int val2) +{ +int ret; + +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_word (data->client,VL6180_ALS_IT , + (val2 -500 ) /1000 );/* write value in ms */ +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); + +return ret; +} + +static int vl6180_write_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int val,int val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); + +switch (mask) { +case IIO_CHAN_INFO_INT_TIME : +if (val !=0 || val2 <500 || val2 >=512500 ) +return -EINVAL ; + +return vl6180_set_it (data, val2); +case IIO_CHAN_INFO_HARDWAREGAIN : +if (chan->type !=IIO_LIGHT ) +return -EINVAL ; + +return vl6180_set_als_gain (data, val, val2); +default : +return -EINVAL ; + } +} +drivers/iio/light/vl6180.c line 433
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+static const struct iio_info vl6180_info = { + .read_raw = vl6180_read_raw, + .write_raw = vl6180_write_raw, + .attrs = &vl6180_attribute_group, + .driver_module =THIS_MODULE , +}; + +/** +* Following settings are enabled: +* 1. ALS and Range ready interrupts I'd prefer to see these comments next to the place they are implemented. Much less likely to end up out of date there.
Quoted 19 lines
drivers/iio/light/vl6180.c new file +19
-0+* 2. ALS integration time: 100ms +* 3. ALS gain: 1 +*/ +static int vl6180_init (struct vl6180_data *data) +{ +struct i2c_client *client = data->client; +int ret; + + ret =vl6180_read_byte (client,VL6180_MODEL_ID ); +if (ret <0 ) +return ret; + +if (ret !=VL6180_MODEL_ID_VAL ) { +dev_err (&client->dev,"invalid model ID %02x\n" , ret); +return -ENODEV ; + } + + ret =vl6180_hold (data, true); +if (ret <0 )drivers/iio/light/vl6180.c line 462
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+return ret; + + ret =vl6180_read_byte (client,VL6180_OUT_OF_RESET ); +if (ret <0 ) +return ret; + +if (ret !=0x01 ) { +dev_err (&client->dev,"device is not fresh out of reset\n" ); +return -EINVAL ; + }Why would it necessarily be fresh out of reset? If it is in some fashion supposed to be guaranteed here, please add a comment explaining why.
Quoted 41 lines
drivers/iio/light/vl6180.c new file +41
-0+ + ret =vl6180_write_byte (client,VL6180_INTR_CONFIG , +VL6180_ALS_READY |VL6180_RANGE_READY ); +if (ret <0 ) +return ret; + + ret =vl6180_write_word (client,VL6180_ALS_IT ,VL6180_ALS_IT_100 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_ALS_GAIN ,VL6180_ALS_GAIN_1 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_OUT_OF_RESET ,0x00 ); +if (ret <0 ) +return ret; + +return vl6180_hold (data, false); +} + +static int vl6180_probe (struct i2c_client *client, +const struct i2c_device_id *id) +{ +struct vl6180_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); +i2c_set_clientdata (client, indio_dev); + data->client = client; +mutex_init (&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &vl6180_info; + indio_dev->channels = vl6180_channels; + indio_dev->num_channels =ARRAY_SIZE (vl6180_channels);drivers/iio/light/vl6180.c line 513
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ indio_dev->name =VL6180_DRV_NAME ; + indio_dev->modes =INDIO_DIRECT_MODE ; + + ret =vl6180_init (data); +if (ret <0 ) +return ret; + + ret =devm_iio_device_register (&client->dev, indio_dev); +if (ret <0 ) +return ret;return
devm_iio_device_registerdirectly.The static checkers will fire on this anyway, but best to fix it now.
Quoted 22 lines
drivers/iio/light/vl6180.c new file +21
-0+ +return ret; +} + +static const struct of_device_id vl6180_of_match[] = { + { .compatible ="st,vl6180" , }, + { }, +}; +MODULE_DEVICE_TABLE (of, vl6180_of_match); + +static const struct i2c_device_id vl6180_id[] = { + {"vl6180" ,0 }, + { } +}; +MODULE_DEVICE_TABLE (i2c, vl6180_id); + +static struct i2c_driver vl6180_driver = { + .driver = { + .name =VL6180_DRV_NAME , + .of_match_table =of_match_ptr (vl6180_of_match), + },drivers/iio/light/vl6180.c line 544
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ .probe = vl6180_probe, + .id_table = vl6180_id, +}; + +module_i2c_driver (vl6180_driver); + +MODULE_AUTHOR ("Peter Meerwald-Stadler <[email protected]>" ); +MODULE_AUTHOR ("Manivannan Sadhasivam <[email protected]>" ); +MODULE_DESCRIPTION ("STMicro VL6180 ALS and proximity sensor driver" ); +MODULE_LICENSE ("GPL" );--
To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 7 lines · Jonathan Cameron
Quoted 4 lines · Manivannan Sadhasivam
This patch adds support for STMicro VL6180 - ALS and proximity sensor. Sensor is capable of measuring the light intensity as well as object distance using TOF(Time of Flight) technology.
Signed-off-by Manivannan Sadhasivam <[email protected]>Few more comments from me.
Thanks Jonathan for your review.
Quoted 193 lines
Jonathan
Quoted 192 lines
--- .../devicetree/bindings/iio/light/vl6180.txt | 16 + drivers/iio/light/Kconfig | 10 + drivers/iio/light/Makefile | 1 + drivers/iio/light/vl6180.c | 544 +++++++++++++++++++++ 4 files changed, 571 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/vl6180.txt create mode 100644 drivers/iio/light/vl6180.cDocumentation/devicetree/bindings/iio/light/vl6180.txt new file +16
-0@@ -0,0 +1,16 @@ +STMicro VL6180 - ALS and proximity sensor + +Link to datasheet: http://www.st.com/resource/en/datasheet/vl6180x.pdf + +Required properties: + + -compatible: should be "st,vl6180" + -reg: the I2C address of the sensor + +Example: + +vl6180@29 { + compatible = "st,vl6180"; + reg = <0x29>; +}; +drivers/iio/light/Kconfig +10
-0@@ -352,4 +352,14 @@ config VEML6070 To compile this driver as a module, choose M here: the module will be called veml6070. +config VL6180 + tristate "VL6180 ALS and proximity sensor" + depends on I2C + help + Say Y here if you want to build a driver for the STMicroelectronics + VL6180 combined ambient light and proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called vl6180. + endmenudrivers/iio/light/Makefile +1
-0@@ -33,3 +33,4 @@ obj-$(CONFIG_TSL4531) += tsl4531.o obj-$(CONFIG_US5182D) += us5182d.o obj-$(CONFIG_VCNL4000) += vcnl4000.o obj-$(CONFIG_VEML6070) += veml6070.o +obj-$(CONFIG_VL6180) += vl6180.odrivers/iio/light/vl6180.c new file +127
-0@@ -0,0 +1,544 @@ +/* +* vl6180.c - Support for STMicroelectronics VL6180 ALS and proximity sensor +* +* Copyright 2017 Peter Meerwald-Stadler <[email protected]> +* Copyright 2017 Manivannan Sadhasivam <[email protected]> +* +* This file is subject to the terms and conditions of version 2 of +* the GNU General Public License. See the file COPYING in the main +* directory of this archive for more details. +* +* IIO driver for VL6180 (7-bit I2C slave addresses 0x29) +* +* Range: 0 to 100mm +* ALS: < 1 Lux up to 100 kLux +* IR: 850nm +* +* TODO: irq, threshold events, continuous mode, hardware buffer +*/ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define VL6180_DRV_NAME "vl6180" + +/* Device identification register and value */ +#define VL6180_MODEL_ID 0x000 +#define VL6180_MODEL_ID_VAL 0xb4 + +/* Configuration registers */ +#define VL6180_INTR_CONFIG 0x014 +#define VL6180_INTR_CLEAR 0x015 +#define VL6180_OUT_OF_RESET 0x016 +#define VL6180_HOLD 0x017 +#define VL6180_RANGE_START 0x018 +#define VL6180_ALS_START 0x038 +#define VL6180_ALS_GAIN 0x03f +#define VL6180_ALS_IT 0x040 + +/* Status registers */ +#define VL6180_RANGE_STATUS 0x04d +#define VL6180_ALS_STATUS 0x04e +#define VL6180_INTR_STATUS 0x04f + +/* Result value registers */ +#define VL6180_ALS_VALUE 0x050 +#define VL6180_RANGE_VALUE 0x062 +#define VL6180_RANGE_RATE 0x066 + +/* bits of the RANGE_START and ALS_START register */ +#define VL6180_MODE_CONT BIT(1)/* continuous mode */ +#define VL6180_STARTSTOP BIT(0)/* start measurement, auto-reset */ + +/* bits of the INTR_STATUS and INTR_CONFIG register */ +#define VL6180_ALS_READY BIT(5) +#define VL6180_RANGE_READY BIT(2) + +/* bits of the INTR_CLEAR register */ +#define VL6180_CLEAR_ERROR BIT(2) +#define VL6180_CLEAR_ALS BIT(1) +#define VL6180_CLEAR_RANGE BIT(0) + +/* bits of the HOLD register */ +#define VL6180_HOLD_ON BIT(0) + +/* default value for the ALS_IT register */ +#define VL6180_ALS_IT_100 0x063/* 100 ms */ + +/* values for the ALS_GAIN register */ +#define VL6180_ALS_GAIN_1 0x46 +#define VL6180_ALS_GAIN_1_25 0x45 +#define VL6180_ALS_GAIN_1_67 0x44 +#define VL6180_ALS_GAIN_2_5 0x43 +#define VL6180_ALS_GAIN_5 0x42 +#define VL6180_ALS_GAIN_10 0x41 +#define VL6180_ALS_GAIN_20 0x40 +#define VL6180_ALS_GAIN_40 0x47 + +struct vl6180_data { +struct i2c_client *client; +struct mutex lock; +}; + +enum {VL6180_ALS ,VL6180_RANGE ,VL6180_PROX }; + +/** +* struct vl6180_chan_regs - Registers for accessing channels +* @drdy_mask: Data ready bits in status register +* @start_reg: Conversion start registeir +* @value_reg: Result value register +*/ +struct vl6180_chan_regs { +u8 drdy_mask; +u16 start_reg, value_reg; +bool word; +}; + +static const struct vl6180_chan_regs vl6180_chan_regs_table[] = { + [VL6180_ALS ] = { + .drdy_mask =VL6180_ALS_READY , + .start_reg =VL6180_ALS_START , + .value_reg =VL6180_ALS_VALUE , + .word = true, + }, + [VL6180_RANGE ] = { + .drdy_mask =VL6180_RANGE_READY , + .start_reg =VL6180_RANGE_START , + .value_reg =VL6180_RANGE_VALUE , + .word = false, + }, + [VL6180_PROX ] = { + .drdy_mask =VL6180_RANGE_READY , + .start_reg =VL6180_RANGE_START , + .value_reg =VL6180_RANGE_RATE , + .word = true, + }, +}; + +static int vl6180_read (struct i2c_client *client,u16 cmd,void *databuf, +u8 len) +{
Quoted 8 lines
drivers/iio/light/vl6180.c line 133
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+u16 cmdbuf =cpu_to_be16 (cmd); +struct i2c_msg msgs[2 ] = { + { .addr = client->addr, .len =2 , .buf = (u8 *) &cmdbuf }, + { .addr = client->addr, .len = len, .buf = databuf, + .flags =I2C_M_RD } }; +int ret;Hmm. could wrap this up in regmap rather neatly I think, but fair enough to keep it all obvious here if you prefer.
I'd prefer to keep regmap away here as we're not caching any register and this way seems to be more efficient to me!
Quoted 36 lines
Quoted 36 lines
drivers/iio/light/vl6180.c new file +36
-0+ + ret =i2c_transfer (client->adapter, msgs,2 ); +if (ret <0 ) +dev_err (&client->dev,"failed reading register 0x%04x\n" , cmd); + +return ret; +} + +static int vl6180_read_byte (struct i2c_client *client,u16 cmd) +{ +u8 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 ) +return ret; + +return data; +} + +static int vl6180_read_word (struct i2c_client *client,u16 cmd) +{ +__be16 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 ) +return ret; + +return be16_to_cpu (data); +} + +static int vl6180_write_byte (struct i2c_client *client,u16 cmd,u8 val) +{ +u8 buf[3 ]; +struct i2c_msg msgs[1 ] = {
Quoted 8 lines
drivers/iio/light/vl6180.c line 174
Quoted 5 lines
drivers/iio/light/vl6180.c new file +5
-0+ { .addr = client->addr, .len = 3, .buf = buf } }; +int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ;Here you end up with a mix that makes it harder to force the endianness than below, but it can still be done by using
cpu_to_be16pand again makes that conversion explicit.
Ack
Quoted 25 lines
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ buf[2 ] = val; + + ret =i2c_transfer (client->adapter, msgs,1 ); +if (ret <0 ) { +dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } + +return 0 ; +}drivers/iio/light/vl6180.c line 194
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ +static int vl6180_write_word (struct i2c_client *client,u16 cmd,u16 val) +{ +u8 buf[4 ]; +struct i2c_msg msgs[1 ] = { + { .addr = client->addr, .len =4 , .buf = buf } }; +int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ;Could be done 'slightly' more efficiently by using the fact these are clearly u16s. Then in the case where the system is of the same endianness these become simple copies.
cpu_to_be16should do the trick (assuming I've got the write one;)
Quoted 2 lines
Make buf
__be16and this becomes explicit in the code which is always a nice bonus.
True. Like Peter also suggested, use of cpu_to_be16 is more efficient here. I'll incorporate in the next version.
Quoted 27 lines
Quoted 27 lines
drivers/iio/light/vl6180.c new file +27
-0+ buf[2 ] = val >>8 ; + buf[3 ] = val &0xff ; + + ret =i2c_transfer (client->adapter, msgs,1 ); +if (ret <0 ) { +dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } + +return 0 ; +} + +static int vl6180_measure (struct vl6180_data *data,int addr) +{ +struct i2c_client *client = data->client; +int tries =20 , ret; +u16 value; + +mutex_lock (&data->lock); +/* Start single shot measurement */ + ret =vl6180_write_byte (client, + vl6180_chan_regs_table[addr].start_reg,VL6180_STARTSTOP ); +if (ret <0 ) + goto fail; + +while (tries--) { + ret =vl6180_read_byte (client,VL6180_INTR_STATUS );
Quoted 8 lines
drivers/iio/light/vl6180.c line 227
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+if (ret <0 ) + goto fail; + +if (ret & vl6180_chan_regs_table[addr].drdy_mask) +break ; +msleep (20 );Presumably we can know approximately how long this is going to take depending on state and perhaps reduce the bus traffic by guessing 'about' right.
Sensor update rate is 40 to 60Hz. Hence, 20ms seems to be good enough.
Quoted 43 lines
Quoted 43 lines
drivers/iio/light/vl6180.c new file +43
-0+ } + +if (tries <0 ) { + ret = -EIO ; + goto fail; + } + +/* Read result value from appropriate registers */ + ret = vl6180_chan_regs_table[addr].word ? +vl6180_read_word (client, vl6180_chan_regs_table[addr].value_reg) : +vl6180_read_byte (client, vl6180_chan_regs_table[addr].value_reg); +if (ret <0 ) + goto fail; + value = ret; + +/* Clear the interrupt flag after data read */ + ret =vl6180_write_byte (client,VL6180_INTR_CLEAR , +VL6180_CLEAR_ERROR |VL6180_CLEAR_ALS |VL6180_CLEAR_RANGE ); +if (ret <0 ) + goto fail; + + ret = value; + +fail: +mutex_unlock (&data->lock); + +return ret; +} + +static const struct iio_chan_spec vl6180_channels[] = { + { + .type =IIO_LIGHT , + .address =VL6180_ALS , + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_INT_TIME ) | +BIT (IIO_CHAN_INFO_SCALE ) | +BIT (IIO_CHAN_INFO_HARDWAREGAIN ), + }, { + .type =IIO_DISTANCE , + .address =VL6180_RANGE , + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_SCALE ), + }, {
Quoted 8 lines
drivers/iio/light/vl6180.c line 276
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+ .type = IIO_PROXIMITY, + .address = VL6180_PROX, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; +As Peter raised, not that obvious what these are. Please add a comment explaining.
Ack
Quoted 151 lines
Quoted 151 lines
drivers/iio/light/vl6180.c new file +151
-0+static const int vl6180_als_gain[8 ][4 ] = { + {1 ,0 ,70 ,VL6180_ALS_GAIN_1 }, + {1 ,250000 ,69 ,VL6180_ALS_GAIN_1_25 }, + {1 ,670000 ,68 ,VL6180_ALS_GAIN_1_67 }, + {2 ,500000 ,67 ,VL6180_ALS_GAIN_2_5 }, + {5 ,0 ,66 ,VL6180_ALS_GAIN_5 }, + {10 ,0 ,65 ,VL6180_ALS_GAIN_10 }, + {20 ,0 ,64 ,VL6180_ALS_GAIN_20 }, + {40 ,0 ,71 ,VL6180_ALS_GAIN_40 } +}; + + +static int vl6180_read_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int *val,int *val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); +int ret, i; + +switch (mask) { +case IIO_CHAN_INFO_RAW : + ret =vl6180_measure (data, chan->address); +if (ret <0 ) +return ret; + *val = ret; + +return IIO_VAL_INT ; +case IIO_CHAN_INFO_INT_TIME : + ret =vl6180_read_word (data->client,VL6180_ALS_IT ); +if (ret <0 ) +return ret; + *val =0 ;/* 1 count = 1ms (0 = 1ms) */ + *val2 = (ret +1 ) *1000 ;/* convert to seconds */ + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_SCALE : +switch (chan->type) { +case IIO_LIGHT : + *val =0 ;/* one ALS count is 0.32 Lux */ + *val2 =320000 ; +break ; +case IIO_DISTANCE : + *val =0 ;/* sensor reports mm, scale to meter */ + *val2 =1000 ; +break ; +default : +return -EINVAL ; + } + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_HARDWAREGAIN : + ret =vl6180_read_byte (data->client,VL6180_ALS_GAIN ); +if (ret <0 ) +return -EINVAL ; +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (ret == vl6180_als_gain[i][2 ]) { + *val = vl6180_als_gain[i][0 ]; + *val2 = vl6180_als_gain[i][1 ]; + } + } + +return IIO_VAL_INT_PLUS_MICRO ; +default : +return -EINVAL ; + } +} + +static IIO_CONST_ATTR(als_gain_available, "1 1.25 1.67 2.5 5 10 20 40"); + +static struct attribute *vl6180_attributes[] = { + &iio_const_attr_als_gain_available.dev_attr.attr, +NULL +}; + +static const struct attribute_group vl6180_attribute_group = { + .attrs = vl6180_attributes, +}; + +/* HOLD is needed before updading any config registers */ +static int vl6180_hold (struct vl6180_data *data,bool hold) +{ +return vl6180_write_byte (data->client,VL6180_HOLD , + hold ?VL6180_HOLD_ON :0 ); +} + +static int vl6180_set_als_gain (struct vl6180_data *data,int val,int val2) +{ +int i, ret; + +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (val == vl6180_als_gain[i][0 ] && + val2 == vl6180_als_gain[i][1 ]) { +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_byte (data->client,VL6180_ALS_GAIN , + vl6180_als_gain[i][3 ]); +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); +return ret; + } + } + +return -EINVAL ; +} + +static int vl6180_set_it (struct vl6180_data *data,int val2) +{ +int ret; + +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_word (data->client,VL6180_ALS_IT , + (val2 -500 ) /1000 );/* write value in ms */ +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); + +return ret; +} + +static int vl6180_write_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int val,int val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); + +switch (mask) { +case IIO_CHAN_INFO_INT_TIME : +if (val !=0 || val2 <500 || val2 >=512500 ) +return -EINVAL ; + +return vl6180_set_it (data, val2); +case IIO_CHAN_INFO_HARDWAREGAIN : +if (chan->type !=IIO_LIGHT ) +return -EINVAL ; + +return vl6180_set_als_gain (data, val, val2); +default : +return -EINVAL ; + } +} + +static const struct iio_info vl6180_info = { + .read_raw = vl6180_read_raw, + .write_raw = vl6180_write_raw, + .attrs = &vl6180_attribute_group,
Quoted 8 lines
drivers/iio/light/vl6180.c line 433
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+ .driver_module = THIS_MODULE, +}; + +/** +* Following settings are enabled: +* 1. ALS and Range ready interrupts I'd prefer to see these comments next to the place they are implemented. Much less likely to end up out of date there.
Ack
Quoted 23 lines
Quoted 23 lines
drivers/iio/light/vl6180.c new file +23
-0+* 2. ALS integration time: 100ms +* 3. ALS gain: 1 +*/ +static int vl6180_init (struct vl6180_data *data) +{ +struct i2c_client *client = data->client; +int ret; + + ret =vl6180_read_byte (client,VL6180_MODEL_ID ); +if (ret <0 ) +return ret; + +if (ret !=VL6180_MODEL_ID_VAL ) { +dev_err (&client->dev,"invalid model ID %02x\n" , ret); +return -ENODEV ; + } + + ret =vl6180_hold (data, true); +if (ret <0 ) +return ret; + + ret =vl6180_read_byte (client,VL6180_OUT_OF_RESET ); +if (ret <0 )
Quoted 8 lines
drivers/iio/light/vl6180.c line 462
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+return ret; + +if (ret !=0x01 ) { +dev_err (&client->dev,"device is not fresh out of reset\n" ); +return -EINVAL ; + }Why would it necessarily be fresh out of reset? If it is in some fashion supposed to be guaranteed here, please add a comment explaining why.
Device sometimes suffers from false reset condition. Hence, this check ensures that the device always starts up in the fresh reset condition.
Will add a comment here.
Quoted 53 lines
Quoted 41 lines
drivers/iio/light/vl6180.c new file +41
-0+ + ret =vl6180_write_byte (client,VL6180_INTR_CONFIG , +VL6180_ALS_READY |VL6180_RANGE_READY ); +if (ret <0 ) +return ret; + + ret =vl6180_write_word (client,VL6180_ALS_IT ,VL6180_ALS_IT_100 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_ALS_GAIN ,VL6180_ALS_GAIN_1 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_OUT_OF_RESET ,0x00 ); +if (ret <0 ) +return ret; + +return vl6180_hold (data, false); +} + +static int vl6180_probe (struct i2c_client *client, +const struct i2c_device_id *id) +{ +struct vl6180_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); +i2c_set_clientdata (client, indio_dev); + data->client = client; +mutex_init (&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &vl6180_info; + indio_dev->channels = vl6180_channels; + indio_dev->num_channels =ARRAY_SIZE (vl6180_channels);drivers/iio/light/vl6180.c line 513
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ indio_dev->name =VL6180_DRV_NAME ; + indio_dev->modes =INDIO_DIRECT_MODE ; + + ret =vl6180_init (data); +if (ret <0 ) +return ret; + + ret =devm_iio_device_register (&client->dev, indio_dev); +if (ret <0 ) +return ret;return
devm_iio_device_registerdirectly.
Quoted 2 lines
The static checkers will fire on this anyway, but best to fix it now.
Ack
Quoted 33 lines
Quoted 32 lines
drivers/iio/light/vl6180.c new file +31
-0+ +return ret; +} + +static const struct of_device_id vl6180_of_match[] = { + { .compatible ="st,vl6180" , }, + { }, +}; +MODULE_DEVICE_TABLE (of, vl6180_of_match); + +static const struct i2c_device_id vl6180_id[] = { + {"vl6180" ,0 }, + { } +}; +MODULE_DEVICE_TABLE (i2c, vl6180_id); + +static struct i2c_driver vl6180_driver = { + .driver = { + .name =VL6180_DRV_NAME , + .of_match_table =of_match_ptr (vl6180_of_match), + }, + .probe = vl6180_probe, + .id_table = vl6180_id, +}; + +module_i2c_driver (vl6180_driver); + +MODULE_AUTHOR ("Peter Meerwald-Stadler <[email protected]>" ); +MODULE_AUTHOR ("Manivannan Sadhasivam <[email protected]>" ); +MODULE_DESCRIPTION ("STMicro VL6180 ALS and proximity sensor driver" ); +MODULE_LICENSE ("GPL" );
Jonathan Cameron
maintainerQuoted 565 lines · Mani Sadhasivam
Quoted 7 lines · Jonathan Cameron
Quoted 4 lines · Manivannan Sadhasivam
This patch adds support for STMicro VL6180 - ALS and proximity sensor. Sensor is capable of measuring the light intensity as well as object distance using TOF(Time of Flight) technology.
Signed-off-by Manivannan Sadhasivam <[email protected]>Few more comments from me.
Thanks Jonathan for your review.
Quoted 193 lines
Jonathan
Quoted 192 lines
--- .../devicetree/bindings/iio/light/vl6180.txt | 16 + drivers/iio/light/Kconfig | 10 + drivers/iio/light/Makefile | 1 + drivers/iio/light/vl6180.c | 544 +++++++++++++++++++++ 4 files changed, 571 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/vl6180.txt create mode 100644 drivers/iio/light/vl6180.cDocumentation/devicetree/bindings/iio/light/vl6180.txt new file +16
-0@@ -0,0 +1,16 @@ +STMicro VL6180 - ALS and proximity sensor + +Link to datasheet: http://www.st.com/resource/en/datasheet/vl6180x.pdf + +Required properties: + + -compatible: should be "st,vl6180" + -reg: the I2C address of the sensor + +Example: + +vl6180@29 { + compatible = "st,vl6180"; + reg = <0x29>; +}; +drivers/iio/light/Kconfig +10
-0@@ -352,4 +352,14 @@ config VEML6070 To compile this driver as a module, choose M here: the module will be called veml6070. +config VL6180 + tristate "VL6180 ALS and proximity sensor" + depends on I2C + help + Say Y here if you want to build a driver for the STMicroelectronics + VL6180 combined ambient light and proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called vl6180. + endmenudrivers/iio/light/Makefile +1
-0@@ -33,3 +33,4 @@ obj-$(CONFIG_TSL4531) += tsl4531.o obj-$(CONFIG_US5182D) += us5182d.o obj-$(CONFIG_VCNL4000) += vcnl4000.o obj-$(CONFIG_VEML6070) += veml6070.o +obj-$(CONFIG_VL6180) += vl6180.odrivers/iio/light/vl6180.c new file +127
-0@@ -0,0 +1,544 @@ +/* +* vl6180.c - Support for STMicroelectronics VL6180 ALS and proximity sensor +* +* Copyright 2017 Peter Meerwald-Stadler <[email protected]> +* Copyright 2017 Manivannan Sadhasivam <[email protected]> +* +* This file is subject to the terms and conditions of version 2 of +* the GNU General Public License. See the file COPYING in the main +* directory of this archive for more details. +* +* IIO driver for VL6180 (7-bit I2C slave addresses 0x29) +* +* Range: 0 to 100mm +* ALS: < 1 Lux up to 100 kLux +* IR: 850nm +* +* TODO: irq, threshold events, continuous mode, hardware buffer +*/ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define VL6180_DRV_NAME "vl6180" + +/* Device identification register and value */ +#define VL6180_MODEL_ID 0x000 +#define VL6180_MODEL_ID_VAL 0xb4 + +/* Configuration registers */ +#define VL6180_INTR_CONFIG 0x014 +#define VL6180_INTR_CLEAR 0x015 +#define VL6180_OUT_OF_RESET 0x016 +#define VL6180_HOLD 0x017 +#define VL6180_RANGE_START 0x018 +#define VL6180_ALS_START 0x038 +#define VL6180_ALS_GAIN 0x03f +#define VL6180_ALS_IT 0x040 + +/* Status registers */ +#define VL6180_RANGE_STATUS 0x04d +#define VL6180_ALS_STATUS 0x04e +#define VL6180_INTR_STATUS 0x04f + +/* Result value registers */ +#define VL6180_ALS_VALUE 0x050 +#define VL6180_RANGE_VALUE 0x062 +#define VL6180_RANGE_RATE 0x066 + +/* bits of the RANGE_START and ALS_START register */ +#define VL6180_MODE_CONT BIT(1)/* continuous mode */ +#define VL6180_STARTSTOP BIT(0)/* start measurement, auto-reset */ + +/* bits of the INTR_STATUS and INTR_CONFIG register */ +#define VL6180_ALS_READY BIT(5) +#define VL6180_RANGE_READY BIT(2) + +/* bits of the INTR_CLEAR register */ +#define VL6180_CLEAR_ERROR BIT(2) +#define VL6180_CLEAR_ALS BIT(1) +#define VL6180_CLEAR_RANGE BIT(0) + +/* bits of the HOLD register */ +#define VL6180_HOLD_ON BIT(0) + +/* default value for the ALS_IT register */ +#define VL6180_ALS_IT_100 0x063/* 100 ms */ + +/* values for the ALS_GAIN register */ +#define VL6180_ALS_GAIN_1 0x46 +#define VL6180_ALS_GAIN_1_25 0x45 +#define VL6180_ALS_GAIN_1_67 0x44 +#define VL6180_ALS_GAIN_2_5 0x43 +#define VL6180_ALS_GAIN_5 0x42 +#define VL6180_ALS_GAIN_10 0x41 +#define VL6180_ALS_GAIN_20 0x40 +#define VL6180_ALS_GAIN_40 0x47 + +struct vl6180_data { +struct i2c_client *client; +struct mutex lock; +}; + +enum {VL6180_ALS ,VL6180_RANGE ,VL6180_PROX }; + +/** +* struct vl6180_chan_regs - Registers for accessing channels +* @drdy_mask: Data ready bits in status register +* @start_reg: Conversion start registeir +* @value_reg: Result value register +*/ +struct vl6180_chan_regs { +u8 drdy_mask; +u16 start_reg, value_reg; +bool word; +}; + +static const struct vl6180_chan_regs vl6180_chan_regs_table[] = { + [VL6180_ALS ] = { + .drdy_mask =VL6180_ALS_READY , + .start_reg =VL6180_ALS_START , + .value_reg =VL6180_ALS_VALUE , + .word = true, + }, + [VL6180_RANGE ] = { + .drdy_mask =VL6180_RANGE_READY , + .start_reg =VL6180_RANGE_START , + .value_reg =VL6180_RANGE_VALUE , + .word = false, + }, + [VL6180_PROX ] = { + .drdy_mask =VL6180_RANGE_READY , + .start_reg =VL6180_RANGE_START , + .value_reg =VL6180_RANGE_RATE , + .word = true, + }, +}; + +static int vl6180_read (struct i2c_client *client,u16 cmd,void *databuf, +u8 len) +{Quoted 8 lines
drivers/iio/light/vl6180.c line 133
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+u16 cmdbuf =cpu_to_be16 (cmd); +struct i2c_msg msgs[2 ] = { + { .addr = client->addr, .len =2 , .buf = (u8 *) &cmdbuf }, + { .addr = client->addr, .len = len, .buf = databuf, + .flags =I2C_M_RD } }; +int ret;Hmm. could wrap this up in regmap rather neatly I think, but fair enough to keep it all obvious here if you prefer.
I'd prefer to keep regmap away here as we're not caching any register and this way seems to be more efficient to me!
Quoted 36 lines
Quoted 36 lines
drivers/iio/light/vl6180.c new file +36
-0+ + ret =i2c_transfer (client->adapter, msgs,2 ); +if (ret <0 ) +dev_err (&client->dev,"failed reading register 0x%04x\n" , cmd); + +return ret; +} + +static int vl6180_read_byte (struct i2c_client *client,u16 cmd) +{ +u8 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 ) +return ret; + +return data; +} + +static int vl6180_read_word (struct i2c_client *client,u16 cmd) +{ +__be16 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 ) +return ret; + +return be16_to_cpu (data); +} + +static int vl6180_write_byte (struct i2c_client *client,u16 cmd,u8 val) +{ +u8 buf[3 ]; +struct i2c_msg msgs[1 ] = {Quoted 8 lines
drivers/iio/light/vl6180.c line 174
Quoted 5 lines
drivers/iio/light/vl6180.c new file +5
-0+ { .addr = client->addr, .len = 3, .buf = buf } }; +int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ;Here you end up with a mix that makes it harder to force the endianness than below, but it can still be done by using
cpu_to_be16pand again makes that conversion explicit.Ack
Quoted 25 lines
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ buf[2 ] = val; + + ret =i2c_transfer (client->adapter, msgs,1 ); +if (ret <0 ) { +dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } + +return 0 ; +}drivers/iio/light/vl6180.c line 194
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ +static int vl6180_write_word (struct i2c_client *client,u16 cmd,u16 val) +{ +u8 buf[4 ]; +struct i2c_msg msgs[1 ] = { + { .addr = client->addr, .len =4 , .buf = buf } }; +int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ;Could be done 'slightly' more efficiently by using the fact these are clearly u16s. Then in the case where the system is of the same endianness these become simple copies.
cpu_to_be16should do the trick (assuming I've got the write one;)Quoted 2 lines
Make buf
__be16and this becomes explicit in the code which is always a nice bonus.True. Like Peter also suggested, use of
cpu_to_be16is more efficient here. I'll incorporate in the next version.Quoted 27 lines
Quoted 27 lines
drivers/iio/light/vl6180.c new file +27
-0+ buf[2 ] = val >>8 ; + buf[3 ] = val &0xff ; + + ret =i2c_transfer (client->adapter, msgs,1 ); +if (ret <0 ) { +dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } + +return 0 ; +} + +static int vl6180_measure (struct vl6180_data *data,int addr) +{ +struct i2c_client *client = data->client; +int tries =20 , ret; +u16 value; + +mutex_lock (&data->lock); +/* Start single shot measurement */ + ret =vl6180_write_byte (client, + vl6180_chan_regs_table[addr].start_reg,VL6180_STARTSTOP ); +if (ret <0 ) + goto fail; + +while (tries--) { + ret =vl6180_read_byte (client,VL6180_INTR_STATUS );Quoted 8 lines
drivers/iio/light/vl6180.c line 227
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+if (ret <0 ) + goto fail; + +if (ret & vl6180_chan_regs_table[addr].drdy_mask) +break ; +msleep (20 );Presumably we can know approximately how long this is going to take depending on state and perhaps reduce the bus traffic by guessing 'about' right.
Sensor update rate is 40 to 60Hz. Hence, 20ms seems to be good enough.
Quoted 43 lines
Quoted 43 lines
drivers/iio/light/vl6180.c new file +43
-0+ } + +if (tries <0 ) { + ret = -EIO ; + goto fail; + } + +/* Read result value from appropriate registers */ + ret = vl6180_chan_regs_table[addr].word ? +vl6180_read_word (client, vl6180_chan_regs_table[addr].value_reg) : +vl6180_read_byte (client, vl6180_chan_regs_table[addr].value_reg); +if (ret <0 ) + goto fail; + value = ret; + +/* Clear the interrupt flag after data read */ + ret =vl6180_write_byte (client,VL6180_INTR_CLEAR , +VL6180_CLEAR_ERROR |VL6180_CLEAR_ALS |VL6180_CLEAR_RANGE ); +if (ret <0 ) + goto fail; + + ret = value; + +fail: +mutex_unlock (&data->lock); + +return ret; +} + +static const struct iio_chan_spec vl6180_channels[] = { + { + .type =IIO_LIGHT , + .address =VL6180_ALS , + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_INT_TIME ) | +BIT (IIO_CHAN_INFO_SCALE ) | +BIT (IIO_CHAN_INFO_HARDWAREGAIN ), + }, { + .type =IIO_DISTANCE , + .address =VL6180_RANGE , + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_SCALE ), + }, {Quoted 8 lines
drivers/iio/light/vl6180.c line 276
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+ .type = IIO_PROXIMITY, + .address = VL6180_PROX, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; +As Peter raised, not that obvious what these are. Please add a comment explaining.
Ack
Quoted 151 lines
Quoted 151 lines
drivers/iio/light/vl6180.c new file +151
-0+static const int vl6180_als_gain[8 ][4 ] = { + {1 ,0 ,70 ,VL6180_ALS_GAIN_1 }, + {1 ,250000 ,69 ,VL6180_ALS_GAIN_1_25 }, + {1 ,670000 ,68 ,VL6180_ALS_GAIN_1_67 }, + {2 ,500000 ,67 ,VL6180_ALS_GAIN_2_5 }, + {5 ,0 ,66 ,VL6180_ALS_GAIN_5 }, + {10 ,0 ,65 ,VL6180_ALS_GAIN_10 }, + {20 ,0 ,64 ,VL6180_ALS_GAIN_20 }, + {40 ,0 ,71 ,VL6180_ALS_GAIN_40 } +}; + + +static int vl6180_read_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int *val,int *val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); +int ret, i; + +switch (mask) { +case IIO_CHAN_INFO_RAW : + ret =vl6180_measure (data, chan->address); +if (ret <0 ) +return ret; + *val = ret; + +return IIO_VAL_INT ; +case IIO_CHAN_INFO_INT_TIME : + ret =vl6180_read_word (data->client,VL6180_ALS_IT ); +if (ret <0 ) +return ret; + *val =0 ;/* 1 count = 1ms (0 = 1ms) */ + *val2 = (ret +1 ) *1000 ;/* convert to seconds */ + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_SCALE : +switch (chan->type) { +case IIO_LIGHT : + *val =0 ;/* one ALS count is 0.32 Lux */ + *val2 =320000 ; +break ; +case IIO_DISTANCE : + *val =0 ;/* sensor reports mm, scale to meter */ + *val2 =1000 ; +break ; +default : +return -EINVAL ; + } + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_HARDWAREGAIN : + ret =vl6180_read_byte (data->client,VL6180_ALS_GAIN ); +if (ret <0 ) +return -EINVAL ; +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (ret == vl6180_als_gain[i][2 ]) { + *val = vl6180_als_gain[i][0 ]; + *val2 = vl6180_als_gain[i][1 ]; + } + } + +return IIO_VAL_INT_PLUS_MICRO ; +default : +return -EINVAL ; + } +} + +static IIO_CONST_ATTR(als_gain_available, "1 1.25 1.67 2.5 5 10 20 40"); + +static struct attribute *vl6180_attributes[] = { + &iio_const_attr_als_gain_available.dev_attr.attr, +NULL +}; + +static const struct attribute_group vl6180_attribute_group = { + .attrs = vl6180_attributes, +}; + +/* HOLD is needed before updading any config registers */ +static int vl6180_hold (struct vl6180_data *data,bool hold) +{ +return vl6180_write_byte (data->client,VL6180_HOLD , + hold ?VL6180_HOLD_ON :0 ); +} + +static int vl6180_set_als_gain (struct vl6180_data *data,int val,int val2) +{ +int i, ret; + +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (val == vl6180_als_gain[i][0 ] && + val2 == vl6180_als_gain[i][1 ]) { +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_byte (data->client,VL6180_ALS_GAIN , + vl6180_als_gain[i][3 ]); +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); +return ret; + } + } + +return -EINVAL ; +} + +static int vl6180_set_it (struct vl6180_data *data,int val2) +{ +int ret; + +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_word (data->client,VL6180_ALS_IT , + (val2 -500 ) /1000 );/* write value in ms */ +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); + +return ret; +} + +static int vl6180_write_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int val,int val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); + +switch (mask) { +case IIO_CHAN_INFO_INT_TIME : +if (val !=0 || val2 <500 || val2 >=512500 ) +return -EINVAL ; + +return vl6180_set_it (data, val2); +case IIO_CHAN_INFO_HARDWAREGAIN : +if (chan->type !=IIO_LIGHT ) +return -EINVAL ; + +return vl6180_set_als_gain (data, val, val2); +default : +return -EINVAL ; + } +} + +static const struct iio_info vl6180_info = { + .read_raw = vl6180_read_raw, + .write_raw = vl6180_write_raw, + .attrs = &vl6180_attribute_group,Quoted 8 lines
drivers/iio/light/vl6180.c line 433
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+ .driver_module = THIS_MODULE, +}; + +/** +* Following settings are enabled: +* 1. ALS and Range ready interrupts I'd prefer to see these comments next to the place they are implemented. Much less likely to end up out of date there.
Ack
Quoted 23 lines
Quoted 23 lines
drivers/iio/light/vl6180.c new file +23
-0+* 2. ALS integration time: 100ms +* 3. ALS gain: 1 +*/ +static int vl6180_init (struct vl6180_data *data) +{ +struct i2c_client *client = data->client; +int ret; + + ret =vl6180_read_byte (client,VL6180_MODEL_ID ); +if (ret <0 ) +return ret; + +if (ret !=VL6180_MODEL_ID_VAL ) { +dev_err (&client->dev,"invalid model ID %02x\n" , ret); +return -ENODEV ; + } + + ret =vl6180_hold (data, true); +if (ret <0 ) +return ret; + + ret =vl6180_read_byte (client,VL6180_OUT_OF_RESET ); +if (ret <0 )Quoted 8 lines
drivers/iio/light/vl6180.c line 462
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+return ret; + +if (ret !=0x01 ) { +dev_err (&client->dev,"device is not fresh out of reset\n" ); +return -EINVAL ; + }Why would it necessarily be fresh out of reset? If it is in some fashion supposed to be guaranteed here, please add a comment explaining why.
Quoted 3 lines
Device sometimes suffers from false reset condition. Hence, this check ensures that the device always starts up in the fresh reset condition.
Will add a comment here.
Ensures it or checks that it is the case?
What about a module remove and reprobe? Should work in that case as well.
Quoted 89 lines
Quoted 53 lines
Quoted 41 lines
drivers/iio/light/vl6180.c new file +41
-0+ + ret =vl6180_write_byte (client,VL6180_INTR_CONFIG , +VL6180_ALS_READY |VL6180_RANGE_READY ); +if (ret <0 ) +return ret; + + ret =vl6180_write_word (client,VL6180_ALS_IT ,VL6180_ALS_IT_100 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_ALS_GAIN ,VL6180_ALS_GAIN_1 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_OUT_OF_RESET ,0x00 ); +if (ret <0 ) +return ret; + +return vl6180_hold (data, false); +} + +static int vl6180_probe (struct i2c_client *client, +const struct i2c_device_id *id) +{ +struct vl6180_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); +i2c_set_clientdata (client, indio_dev); + data->client = client; +mutex_init (&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &vl6180_info; + indio_dev->channels = vl6180_channels; + indio_dev->num_channels =ARRAY_SIZE (vl6180_channels);drivers/iio/light/vl6180.c line 513
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ indio_dev->name =VL6180_DRV_NAME ; + indio_dev->modes =INDIO_DIRECT_MODE ; + + ret =vl6180_init (data); +if (ret <0 ) +return ret; + + ret =devm_iio_device_register (&client->dev, indio_dev); +if (ret <0 ) +return ret;return
devm_iio_device_registerdirectly.Quoted 2 lines
The static checkers will fire on this anyway, but best to fix it now.
Ack
Quoted 33 lines
Quoted 32 lines
drivers/iio/light/vl6180.c new file +31
-0+ +return ret; +} + +static const struct of_device_id vl6180_of_match[] = { + { .compatible ="st,vl6180" , }, + { }, +}; +MODULE_DEVICE_TABLE (of, vl6180_of_match); + +static const struct i2c_device_id vl6180_id[] = { + {"vl6180" ,0 }, + { } +}; +MODULE_DEVICE_TABLE (i2c, vl6180_id); + +static struct i2c_driver vl6180_driver = { + .driver = { + .name =VL6180_DRV_NAME , + .of_match_table =of_match_ptr (vl6180_of_match), + }, + .probe = vl6180_probe, + .id_table = vl6180_id, +}; + +module_i2c_driver (vl6180_driver); + +MODULE_AUTHOR ("Peter Meerwald-Stadler <[email protected]>" ); +MODULE_AUTHOR ("Manivannan Sadhasivam <[email protected]>" ); +MODULE_DESCRIPTION ("STMicro VL6180 ALS and proximity sensor driver" ); +MODULE_LICENSE ("GPL" );
Quoted 566 lines · Jonathan Cameron
Quoted 565 lines · Mani Sadhasivam
Quoted 7 lines · Jonathan Cameron
Quoted 4 lines · Manivannan Sadhasivam
This patch adds support for STMicro VL6180 - ALS and proximity sensor. Sensor is capable of measuring the light intensity as well as object distance using TOF(Time of Flight) technology.
Signed-off-by Manivannan Sadhasivam <[email protected]>Few more comments from me.
Thanks Jonathan for your review.
Quoted 193 lines
Jonathan
Quoted 192 lines
--- .../devicetree/bindings/iio/light/vl6180.txt | 16 + drivers/iio/light/Kconfig | 10 + drivers/iio/light/Makefile | 1 + drivers/iio/light/vl6180.c | 544 +++++++++++++++++++++ 4 files changed, 571 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/vl6180.txt create mode 100644 drivers/iio/light/vl6180.cDocumentation/devicetree/bindings/iio/light/vl6180.txt new file +16
-0@@ -0,0 +1,16 @@ +STMicro VL6180 - ALS and proximity sensor + +Link to datasheet: http://www.st.com/resource/en/datasheet/vl6180x.pdf + +Required properties: + + -compatible: should be "st,vl6180" + -reg: the I2C address of the sensor + +Example: + +vl6180@29 { + compatible = "st,vl6180"; + reg = <0x29>; +}; +drivers/iio/light/Kconfig +10
-0@@ -352,4 +352,14 @@ config VEML6070 To compile this driver as a module, choose M here: the module will be called veml6070. +config VL6180 + tristate "VL6180 ALS and proximity sensor" + depends on I2C + help + Say Y here if you want to build a driver for the STMicroelectronics + VL6180 combined ambient light and proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called vl6180. + endmenudrivers/iio/light/Makefile +1
-0@@ -33,3 +33,4 @@ obj-$(CONFIG_TSL4531) += tsl4531.o obj-$(CONFIG_US5182D) += us5182d.o obj-$(CONFIG_VCNL4000) += vcnl4000.o obj-$(CONFIG_VEML6070) += veml6070.o +obj-$(CONFIG_VL6180) += vl6180.odrivers/iio/light/vl6180.c new file +127
-0@@ -0,0 +1,544 @@ +/* +* vl6180.c - Support for STMicroelectronics VL6180 ALS and proximity sensor +* +* Copyright 2017 Peter Meerwald-Stadler <[email protected]> +* Copyright 2017 Manivannan Sadhasivam <[email protected]> +* +* This file is subject to the terms and conditions of version 2 of +* the GNU General Public License. See the file COPYING in the main +* directory of this archive for more details. +* +* IIO driver for VL6180 (7-bit I2C slave addresses 0x29) +* +* Range: 0 to 100mm +* ALS: < 1 Lux up to 100 kLux +* IR: 850nm +* +* TODO: irq, threshold events, continuous mode, hardware buffer +*/ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define VL6180_DRV_NAME "vl6180" + +/* Device identification register and value */ +#define VL6180_MODEL_ID 0x000 +#define VL6180_MODEL_ID_VAL 0xb4 + +/* Configuration registers */ +#define VL6180_INTR_CONFIG 0x014 +#define VL6180_INTR_CLEAR 0x015 +#define VL6180_OUT_OF_RESET 0x016 +#define VL6180_HOLD 0x017 +#define VL6180_RANGE_START 0x018 +#define VL6180_ALS_START 0x038 +#define VL6180_ALS_GAIN 0x03f +#define VL6180_ALS_IT 0x040 + +/* Status registers */ +#define VL6180_RANGE_STATUS 0x04d +#define VL6180_ALS_STATUS 0x04e +#define VL6180_INTR_STATUS 0x04f + +/* Result value registers */ +#define VL6180_ALS_VALUE 0x050 +#define VL6180_RANGE_VALUE 0x062 +#define VL6180_RANGE_RATE 0x066 + +/* bits of the RANGE_START and ALS_START register */ +#define VL6180_MODE_CONT BIT(1)/* continuous mode */ +#define VL6180_STARTSTOP BIT(0)/* start measurement, auto-reset */ + +/* bits of the INTR_STATUS and INTR_CONFIG register */ +#define VL6180_ALS_READY BIT(5) +#define VL6180_RANGE_READY BIT(2) + +/* bits of the INTR_CLEAR register */ +#define VL6180_CLEAR_ERROR BIT(2) +#define VL6180_CLEAR_ALS BIT(1) +#define VL6180_CLEAR_RANGE BIT(0) + +/* bits of the HOLD register */ +#define VL6180_HOLD_ON BIT(0) + +/* default value for the ALS_IT register */ +#define VL6180_ALS_IT_100 0x063/* 100 ms */ + +/* values for the ALS_GAIN register */ +#define VL6180_ALS_GAIN_1 0x46 +#define VL6180_ALS_GAIN_1_25 0x45 +#define VL6180_ALS_GAIN_1_67 0x44 +#define VL6180_ALS_GAIN_2_5 0x43 +#define VL6180_ALS_GAIN_5 0x42 +#define VL6180_ALS_GAIN_10 0x41 +#define VL6180_ALS_GAIN_20 0x40 +#define VL6180_ALS_GAIN_40 0x47 + +struct vl6180_data { +struct i2c_client *client; +struct mutex lock; +}; + +enum {VL6180_ALS ,VL6180_RANGE ,VL6180_PROX }; + +/** +* struct vl6180_chan_regs - Registers for accessing channels +* @drdy_mask: Data ready bits in status register +* @start_reg: Conversion start registeir +* @value_reg: Result value register +*/ +struct vl6180_chan_regs { +u8 drdy_mask; +u16 start_reg, value_reg; +bool word; +}; + +static const struct vl6180_chan_regs vl6180_chan_regs_table[] = { + [VL6180_ALS ] = { + .drdy_mask =VL6180_ALS_READY , + .start_reg =VL6180_ALS_START , + .value_reg =VL6180_ALS_VALUE , + .word = true, + }, + [VL6180_RANGE ] = { + .drdy_mask =VL6180_RANGE_READY , + .start_reg =VL6180_RANGE_START , + .value_reg =VL6180_RANGE_VALUE , + .word = false, + }, + [VL6180_PROX ] = { + .drdy_mask =VL6180_RANGE_READY , + .start_reg =VL6180_RANGE_START , + .value_reg =VL6180_RANGE_RATE , + .word = true, + }, +}; + +static int vl6180_read (struct i2c_client *client,u16 cmd,void *databuf, +u8 len) +{Quoted 8 lines
drivers/iio/light/vl6180.c line 133
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+u16 cmdbuf =cpu_to_be16 (cmd); +struct i2c_msg msgs[2 ] = { + { .addr = client->addr, .len =2 , .buf = (u8 *) &cmdbuf }, + { .addr = client->addr, .len = len, .buf = databuf, + .flags =I2C_M_RD } }; +int ret;Hmm. could wrap this up in regmap rather neatly I think, but fair enough to keep it all obvious here if you prefer.
I'd prefer to keep regmap away here as we're not caching any register and this way seems to be more efficient to me!
Quoted 36 lines
Quoted 36 lines
drivers/iio/light/vl6180.c new file +36
-0+ + ret =i2c_transfer (client->adapter, msgs,2 ); +if (ret <0 ) +dev_err (&client->dev,"failed reading register 0x%04x\n" , cmd); + +return ret; +} + +static int vl6180_read_byte (struct i2c_client *client,u16 cmd) +{ +u8 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 ) +return ret; + +return data; +} + +static int vl6180_read_word (struct i2c_client *client,u16 cmd) +{ +__be16 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 ) +return ret; + +return be16_to_cpu (data); +} + +static int vl6180_write_byte (struct i2c_client *client,u16 cmd,u8 val) +{ +u8 buf[3 ]; +struct i2c_msg msgs[1 ] = {Quoted 8 lines
drivers/iio/light/vl6180.c line 174
Quoted 5 lines
drivers/iio/light/vl6180.c new file +5
-0+ { .addr = client->addr, .len = 3, .buf = buf } }; +int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ;Here you end up with a mix that makes it harder to force the endianness than below, but it can still be done by using
cpu_to_be16pand again makes that conversion explicit.Ack
Quoted 25 lines
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ buf[2 ] = val; + + ret =i2c_transfer (client->adapter, msgs,1 ); +if (ret <0 ) { +dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } + +return 0 ; +}drivers/iio/light/vl6180.c line 194
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ +static int vl6180_write_word (struct i2c_client *client,u16 cmd,u16 val) +{ +u8 buf[4 ]; +struct i2c_msg msgs[1 ] = { + { .addr = client->addr, .len =4 , .buf = buf } }; +int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ;Could be done 'slightly' more efficiently by using the fact these are clearly u16s. Then in the case where the system is of the same endianness these become simple copies.
cpu_to_be16should do the trick (assuming I've got the write one;)Quoted 2 lines
Make buf
__be16and this becomes explicit in the code which is always a nice bonus.True. Like Peter also suggested, use of
cpu_to_be16is more efficient here. I'll incorporate in the next version.Quoted 27 lines
Quoted 27 lines
drivers/iio/light/vl6180.c new file +27
-0+ buf[2 ] = val >>8 ; + buf[3 ] = val &0xff ; + + ret =i2c_transfer (client->adapter, msgs,1 ); +if (ret <0 ) { +dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } + +return 0 ; +} + +static int vl6180_measure (struct vl6180_data *data,int addr) +{ +struct i2c_client *client = data->client; +int tries =20 , ret; +u16 value; + +mutex_lock (&data->lock); +/* Start single shot measurement */ + ret =vl6180_write_byte (client, + vl6180_chan_regs_table[addr].start_reg,VL6180_STARTSTOP ); +if (ret <0 ) + goto fail; + +while (tries--) { + ret =vl6180_read_byte (client,VL6180_INTR_STATUS );Quoted 8 lines
drivers/iio/light/vl6180.c line 227
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+if (ret <0 ) + goto fail; + +if (ret & vl6180_chan_regs_table[addr].drdy_mask) +break ; +msleep (20 );Presumably we can know approximately how long this is going to take depending on state and perhaps reduce the bus traffic by guessing 'about' right.
Sensor update rate is 40 to 60Hz. Hence, 20ms seems to be good enough.
Quoted 43 lines
Quoted 43 lines
drivers/iio/light/vl6180.c new file +43
-0+ } + +if (tries <0 ) { + ret = -EIO ; + goto fail; + } + +/* Read result value from appropriate registers */ + ret = vl6180_chan_regs_table[addr].word ? +vl6180_read_word (client, vl6180_chan_regs_table[addr].value_reg) : +vl6180_read_byte (client, vl6180_chan_regs_table[addr].value_reg); +if (ret <0 ) + goto fail; + value = ret; + +/* Clear the interrupt flag after data read */ + ret =vl6180_write_byte (client,VL6180_INTR_CLEAR , +VL6180_CLEAR_ERROR |VL6180_CLEAR_ALS |VL6180_CLEAR_RANGE ); +if (ret <0 ) + goto fail; + + ret = value; + +fail: +mutex_unlock (&data->lock); + +return ret; +} + +static const struct iio_chan_spec vl6180_channels[] = { + { + .type =IIO_LIGHT , + .address =VL6180_ALS , + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_INT_TIME ) | +BIT (IIO_CHAN_INFO_SCALE ) | +BIT (IIO_CHAN_INFO_HARDWAREGAIN ), + }, { + .type =IIO_DISTANCE , + .address =VL6180_RANGE , + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_SCALE ), + }, {Quoted 8 lines
drivers/iio/light/vl6180.c line 276
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+ .type = IIO_PROXIMITY, + .address = VL6180_PROX, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; +As Peter raised, not that obvious what these are. Please add a comment explaining.
Ack
Quoted 151 lines
Quoted 151 lines
drivers/iio/light/vl6180.c new file +151
-0+static const int vl6180_als_gain[8 ][4 ] = { + {1 ,0 ,70 ,VL6180_ALS_GAIN_1 }, + {1 ,250000 ,69 ,VL6180_ALS_GAIN_1_25 }, + {1 ,670000 ,68 ,VL6180_ALS_GAIN_1_67 }, + {2 ,500000 ,67 ,VL6180_ALS_GAIN_2_5 }, + {5 ,0 ,66 ,VL6180_ALS_GAIN_5 }, + {10 ,0 ,65 ,VL6180_ALS_GAIN_10 }, + {20 ,0 ,64 ,VL6180_ALS_GAIN_20 }, + {40 ,0 ,71 ,VL6180_ALS_GAIN_40 } +}; + + +static int vl6180_read_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int *val,int *val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); +int ret, i; + +switch (mask) { +case IIO_CHAN_INFO_RAW : + ret =vl6180_measure (data, chan->address); +if (ret <0 ) +return ret; + *val = ret; + +return IIO_VAL_INT ; +case IIO_CHAN_INFO_INT_TIME : + ret =vl6180_read_word (data->client,VL6180_ALS_IT ); +if (ret <0 ) +return ret; + *val =0 ;/* 1 count = 1ms (0 = 1ms) */ + *val2 = (ret +1 ) *1000 ;/* convert to seconds */ + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_SCALE : +switch (chan->type) { +case IIO_LIGHT : + *val =0 ;/* one ALS count is 0.32 Lux */ + *val2 =320000 ; +break ; +case IIO_DISTANCE : + *val =0 ;/* sensor reports mm, scale to meter */ + *val2 =1000 ; +break ; +default : +return -EINVAL ; + } + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_HARDWAREGAIN : + ret =vl6180_read_byte (data->client,VL6180_ALS_GAIN ); +if (ret <0 ) +return -EINVAL ; +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (ret == vl6180_als_gain[i][2 ]) { + *val = vl6180_als_gain[i][0 ]; + *val2 = vl6180_als_gain[i][1 ]; + } + } + +return IIO_VAL_INT_PLUS_MICRO ; +default : +return -EINVAL ; + } +} + +static IIO_CONST_ATTR(als_gain_available, "1 1.25 1.67 2.5 5 10 20 40"); + +static struct attribute *vl6180_attributes[] = { + &iio_const_attr_als_gain_available.dev_attr.attr, +NULL +}; + +static const struct attribute_group vl6180_attribute_group = { + .attrs = vl6180_attributes, +}; + +/* HOLD is needed before updading any config registers */ +static int vl6180_hold (struct vl6180_data *data,bool hold) +{ +return vl6180_write_byte (data->client,VL6180_HOLD , + hold ?VL6180_HOLD_ON :0 ); +} + +static int vl6180_set_als_gain (struct vl6180_data *data,int val,int val2) +{ +int i, ret; + +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (val == vl6180_als_gain[i][0 ] && + val2 == vl6180_als_gain[i][1 ]) { +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_byte (data->client,VL6180_ALS_GAIN , + vl6180_als_gain[i][3 ]); +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); +return ret; + } + } + +return -EINVAL ; +} + +static int vl6180_set_it (struct vl6180_data *data,int val2) +{ +int ret; + +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_word (data->client,VL6180_ALS_IT , + (val2 -500 ) /1000 );/* write value in ms */ +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); + +return ret; +} + +static int vl6180_write_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int val,int val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); + +switch (mask) { +case IIO_CHAN_INFO_INT_TIME : +if (val !=0 || val2 <500 || val2 >=512500 ) +return -EINVAL ; + +return vl6180_set_it (data, val2); +case IIO_CHAN_INFO_HARDWAREGAIN : +if (chan->type !=IIO_LIGHT ) +return -EINVAL ; + +return vl6180_set_als_gain (data, val, val2); +default : +return -EINVAL ; + } +} + +static const struct iio_info vl6180_info = { + .read_raw = vl6180_read_raw, + .write_raw = vl6180_write_raw, + .attrs = &vl6180_attribute_group,Quoted 8 lines
drivers/iio/light/vl6180.c line 433
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+ .driver_module = THIS_MODULE, +}; + +/** +* Following settings are enabled: +* 1. ALS and Range ready interrupts I'd prefer to see these comments next to the place they are implemented. Much less likely to end up out of date there.
Ack
Quoted 31 lines
Quoted 19 lines
drivers/iio/light/vl6180.c new file +19
-0+* 2. ALS integration time: 100ms +* 3. ALS gain: 1 +*/ +static int vl6180_init (struct vl6180_data *data) +{ +struct i2c_client *client = data->client; +int ret; + + ret =vl6180_read_byte (client,VL6180_MODEL_ID ); +if (ret <0 ) +return ret; + +if (ret !=VL6180_MODEL_ID_VAL ) { +dev_err (&client->dev,"invalid model ID %02x\n" , ret); +return -ENODEV ; + } + + ret =vl6180_hold (data, true); +if (ret <0 )drivers/iio/light/vl6180.c line 462
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+return ret; + + ret =vl6180_read_byte (client,VL6180_OUT_OF_RESET ); +if (ret <0 ) +return ret; + +if (ret !=0x01 ) { +dev_err (&client->dev,"device is not fresh out of reset\n" ); +return -EINVAL ; + }Why would it necessarily be fresh out of reset? If it is in some fashion supposed to be guaranteed here, please add a comment explaining why.
Quoted 6 lines
Quoted 3 lines
Device sometimes suffers from false reset condition. Hence, this check ensures that the device always starts up in the fresh reset condition.
Will add a comment here.Ensures it or checks that it is the case?
What about a module remove and reprobe? Should work in that case as well.
If the device is not fresh out of reset it won't get registered, by this way we can prevent
the device to run in false reset condition.
When the module is removed and installed again without resetting the device, this check
will get into act.
Quoted 90 lines
Quoted 89 lines
Quoted 53 lines
Quoted 41 lines
drivers/iio/light/vl6180.c new file +41
-0+ + ret =vl6180_write_byte (client,VL6180_INTR_CONFIG , +VL6180_ALS_READY |VL6180_RANGE_READY ); +if (ret <0 ) +return ret; + + ret =vl6180_write_word (client,VL6180_ALS_IT ,VL6180_ALS_IT_100 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_ALS_GAIN ,VL6180_ALS_GAIN_1 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_OUT_OF_RESET ,0x00 ); +if (ret <0 ) +return ret; + +return vl6180_hold (data, false); +} + +static int vl6180_probe (struct i2c_client *client, +const struct i2c_device_id *id) +{ +struct vl6180_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); +i2c_set_clientdata (client, indio_dev); + data->client = client; +mutex_init (&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &vl6180_info; + indio_dev->channels = vl6180_channels; + indio_dev->num_channels =ARRAY_SIZE (vl6180_channels);drivers/iio/light/vl6180.c line 513
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ indio_dev->name =VL6180_DRV_NAME ; + indio_dev->modes =INDIO_DIRECT_MODE ; + + ret =vl6180_init (data); +if (ret <0 ) +return ret; + + ret =devm_iio_device_register (&client->dev, indio_dev); +if (ret <0 ) +return ret;return
devm_iio_device_registerdirectly.Quoted 2 lines
The static checkers will fire on this anyway, but best to fix it now.
Ack
Quoted 33 lines
Quoted 32 lines
drivers/iio/light/vl6180.c new file +31
-0+ +return ret; +} + +static const struct of_device_id vl6180_of_match[] = { + { .compatible ="st,vl6180" , }, + { }, +}; +MODULE_DEVICE_TABLE (of, vl6180_of_match); + +static const struct i2c_device_id vl6180_id[] = { + {"vl6180" ,0 }, + { } +}; +MODULE_DEVICE_TABLE (i2c, vl6180_id); + +static struct i2c_driver vl6180_driver = { + .driver = { + .name =VL6180_DRV_NAME , + .of_match_table =of_match_ptr (vl6180_of_match), + }, + .probe = vl6180_probe, + .id_table = vl6180_id, +}; + +module_i2c_driver (vl6180_driver); + +MODULE_AUTHOR ("Peter Meerwald-Stadler <[email protected]>" ); +MODULE_AUTHOR ("Manivannan Sadhasivam <[email protected]>" ); +MODULE_DESCRIPTION ("STMicro VL6180 ALS and proximity sensor driver" ); +MODULE_LICENSE ("GPL" );
Jonathan Cameron
maintainer<snip>
Quoted 34 lines
Quoted 34 lines
Quoted 34 lines
Quoted 23 lines
Quoted 23 lines
+ * 2. ALS integration time: 100ms
+ * 3. ALS gain: 1
+ */drivers/iio/light/vl6180.c new file +20
-0+static int vl6180_init (struct vl6180_data *data) +{ +struct i2c_client *client = data->client; +int ret; + + ret =vl6180_read_byte (client,VL6180_MODEL_ID ); +if (ret <0 ) +return ret; + +if (ret !=VL6180_MODEL_ID_VAL ) { +dev_err (&client->dev,"invalid model ID %02x\n" , ret); +return -ENODEV ; + } + + ret =vl6180_hold (data, true); +if (ret <0 ) +return ret; + + ret =vl6180_read_byte (client,VL6180_OUT_OF_RESET ); +if (ret <0 )Quoted 8 lines
drivers/iio/light/vl6180.c line 462
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+return ret; + +if (ret !=0x01 ) { +dev_err (&client->dev,"device is not fresh out of reset\n" ); +return -EINVAL ; + }Why would it necessarily be fresh out of reset? If it is in some fashion supposed to be guaranteed here, please add a comment explaining why.
Device sometimes suffers from false reset condition. Hence, this check ensures that the device always starts up in the fresh reset condition.
Will add a comment here.
Quoted 6 lines
Quoted 3 lines
Ensures it or checks that it is the case?
What about a module remove and reprobe? Should work in that case as well.
If the device is not fresh out of reset it won't get registered, by this way we can prevent
the device to run in false reset condition.
Remove and probe needs to work if at all possible. It's a common enough thing to do for one reason or another.
A classic option would be to allow provision of a gpios to manually perform the reset. Sounds like that's not an option on your board however.
If not, is it possible to get the device to a known state by simply writing all it's registers? If it's really impossible to continue in this state we need detailed explanation of why here in comments in the code.
Quoted 99 lines
When the module is removed and installed again without resetting the device, this check
will get into act.Quoted 81 lines
Quoted 81 lines
Quoted 53 lines
Quoted 41 lines
drivers/iio/light/vl6180.c new file +41
-0+ + ret =vl6180_write_byte (client,VL6180_INTR_CONFIG , +VL6180_ALS_READY |VL6180_RANGE_READY ); +if (ret <0 ) +return ret; + + ret =vl6180_write_word (client,VL6180_ALS_IT ,VL6180_ALS_IT_100 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_ALS_GAIN ,VL6180_ALS_GAIN_1 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_OUT_OF_RESET ,0x00 ); +if (ret <0 ) +return ret; + +return vl6180_hold (data, false); +} + +static int vl6180_probe (struct i2c_client *client, +const struct i2c_device_id *id) +{ +struct vl6180_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); +i2c_set_clientdata (client, indio_dev); + data->client = client; +mutex_init (&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &vl6180_info; + indio_dev->channels = vl6180_channels; + indio_dev->num_channels =ARRAY_SIZE (vl6180_channels);drivers/iio/light/vl6180.c line 513
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ indio_dev->name =VL6180_DRV_NAME ; + indio_dev->modes =INDIO_DIRECT_MODE ; + + ret =vl6180_init (data); +if (ret <0 ) +return ret; + + ret =devm_iio_device_register (&client->dev, indio_dev); +if (ret <0 ) +return ret;return
devm_iio_device_registerdirectly.Quoted 2 lines
The static checkers will fire on this anyway, but best to fix it now.
Ack
Quoted 25 lines
Quoted 25 lines
drivers/iio/light/vl6180.c new file +25
-0+ +return ret; +} + +static const struct of_device_id vl6180_of_match[] = { + { .compatible ="st,vl6180" , }, + { }, +}; +MODULE_DEVICE_TABLE (of, vl6180_of_match); + +static const struct i2c_device_id vl6180_id[] = { + {"vl6180" ,0 }, + { } +}; +MODULE_DEVICE_TABLE (i2c, vl6180_id); + +static struct i2c_driver vl6180_driver = { + .driver = { + .name =VL6180_DRV_NAME , + .of_match_table =of_match_ptr (vl6180_of_match), + }, + .probe = vl6180_probe, + .id_table = vl6180_id, +}; +Quoted 9 lines
Quoted 8 lines
Quoted 8 lines
drivers/iio/light/vl6180.c line 544
Quoted 7 lines
drivers/iio/light/vl6180.c new file +6
-0+module_i2c_driver (vl6180_driver); + +MODULE_AUTHOR ("Peter Meerwald-Stadler <[email protected]>" ); +MODULE_AUTHOR ("Manivannan Sadhasivam <[email protected]>" ); +MODULE_DESCRIPTION ("STMicro VL6180 ALS and proximity sensor driver" ); +MODULE_LICENSE ("GPL" );--
To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 44 lines · Jonathan Cameron
<snip>
Quoted 34 lines
Quoted 34 lines
Quoted 34 lines
Quoted 23 lines
Quoted 23 lines
+ * 2. ALS integration time: 100ms
+ * 3. ALS gain: 1
+ */(quoted diff) +20
-0+static int vl6180_init(struct vl6180_data *data) +{ + struct i2c_client *client = data->client; + int ret; + + ret = vl6180_read_byte(client, VL6180_MODEL_ID); + if (ret < 0) + return ret; + + if (ret != VL6180_MODEL_ID_VAL) { + dev_err(&client->dev, "invalid model ID %02x\n", ret); + return -ENODEV; + } + + ret = vl6180_hold(data, true); + if (ret < 0) + return ret; + + ret = vl6180_read_byte(client, VL6180_OUT_OF_RESET); + if (ret < 0)Quoted 8 lines
Review
Quoted 6 lines
(quoted diff) +6
-0+ return ret; + + if (ret != 0x01) { + dev_err(&client->dev, "device is not fresh out of reset\n"); + return -EINVAL; + }Why would it necessarily be fresh out of reset? If it is in some fashion supposed to be guaranteed here, please add a comment explaining why.
Device sometimes suffers from false reset condition. Hence, this check ensures that the device always starts up in the fresh reset condition.
Will add a comment here.Quoted 6 lines
Quoted 3 lines
Ensures it or checks that it is the case?
What about a module remove and reprobe? Should work in that case as well.
If the device is not fresh out of reset it won't get registered, by this way we can prevent
the device to run in false reset condition.Remove and probe needs to work if at all possible. It's a common enough thing to do for one reason or another.
Quoted 6 lines
A classic option would be to allow provision of a gpios to manually perform the reset. Sounds like that's not an option on your board however.
If not, is it possible to get the device to a known state by simply writing all it's registers? If it's really impossible to continue in this state we need detailed explanation of why here in comments in the code.
Alright. I couldn't find a case where false reset might cause an issue. So, it'd be better to skip the settings and allow the driver to work instead of error out scenario.
Is this fine for you Jonathan?
Quoted 100 lines
Quoted 99 lines
When the module is removed and installed again without resetting the device, this check
will get into act.Quoted 81 lines
Quoted 81 lines
Quoted 53 lines
Quoted 41 lines
(quoted diff) +41
-0+ + ret = vl6180_write_byte(client, VL6180_INTR_CONFIG, + VL6180_ALS_READY | VL6180_RANGE_READY); + if (ret < 0) + return ret; + + ret = vl6180_write_word(client, VL6180_ALS_IT, VL6180_ALS_IT_100); + if (ret < 0) + return ret; + + ret = vl6180_write_byte(client, VL6180_ALS_GAIN, VL6180_ALS_GAIN_1); + if (ret < 0) + return ret; + + ret = vl6180_write_byte(client, VL6180_OUT_OF_RESET, 0x00); + if (ret < 0) + return ret; + + return vl6180_hold(data, false); +} + +static int vl6180_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct vl6180_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); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &vl6180_info; + indio_dev->channels = vl6180_channels; + indio_dev->num_channels = ARRAY_SIZE(vl6180_channels);Review
Quoted 10 lines
(quoted diff) +10
-0+ indio_dev->name = VL6180_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = vl6180_init(data); + if (ret < 0) + return ret; + + ret = devm_iio_device_register(&client->dev, indio_dev); + if (ret < 0) + return ret;return
devm_iio_device_registerdirectly.Quoted 2 lines
The static checkers will fire on this anyway, but best to fix it now.
Ack
Quoted 25 lines
Quoted 25 lines
(quoted diff) +25
-0+ + return ret; +} + +static const struct of_device_id vl6180_of_match[] = { + { .compatible = "st,vl6180", }, + { }, +}; +MODULE_DEVICE_TABLE(of, vl6180_of_match); + +static const struct i2c_device_id vl6180_id[] = { + { "vl6180", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vl6180_id); + +static struct i2c_driver vl6180_driver = { + .driver = { + .name = VL6180_DRV_NAME, + .of_match_table = of_match_ptr(vl6180_of_match), + }, + .probe = vl6180_probe, + .id_table = vl6180_id, +}; +Quoted 9 lines
Quoted 8 lines
Quoted 8 lines
Review
Quoted 7 lines
(quoted diff) +6
-0+module_i2c_driver(vl6180_driver); + +MODULE_AUTHOR("Peter Meerwald-Stadler <[email protected]>"); +MODULE_AUTHOR("Manivannan Sadhasivam <[email protected]>"); +MODULE_DESCRIPTION("STMicro VL6180 ALS and proximity sensor driver"); +MODULE_LICENSE("GPL");--
To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 8 lines
Quoted 7 lines
A classic option would be to allow provision of a gpios to manually perform the reset. Sounds like that's not an option on your board however.
If not, is it possible to get the device to a known state by simply writing all it's registers? If it's really impossible to continue in this state we need detailed explanation of why here in comments in the code.
Quoted 5 lines
Alright. I couldn't find a case where false reset might cause an issue. So, it'd be better to skip the settings and allow the driver to work instead of error out scenario.
Is this fine for you Jonathan?
I think the fresh-out-of-reset thing is informative really we are writing the most relevant config registers anyway on probe
I'd remove the check of the register or make it a log message only; the clearing of the register could be dependent on the check, however
regards, p.
Signature · 2 lines
Peter Meerwald-Stadler Mobile: +43 664 24 44 418
Quoted 17 lines · Peter Meerwald-Stadler
Quoted 2 lines
Quoted 2 lines
A classic option would be to allow provision of a gpios to manually
perform
Quoted 3 lines
Quoted 3 lines
the reset. Sounds like that's not an option on your board however.
If not, is it possible to get the device to a known state by simply
writing
Quoted 1 line
Quoted 1 line
all it's registers? If it's really impossible to continue in this
state
Quoted 3 lines
Quoted 1 line
we need detailed explanation of why here in comments in the code.
Alright. I couldn't find a case where false reset might cause an
issue. So,
Quoted 1 line
it'd be better to skip the settings and allow the driver to work
instead of
Quoted 2 lines
error out scenario.
Quoted 8 lines
Quoted 1 line
Is this fine for you Jonathan?
I think the fresh-out-of-reset thing is informative really we are writing the most relevant config registers anyway on probe
I'd remove the check of the register or make it a log message only; the
clearing of the register could be dependent on the check, however
Logging makes sense as might allow some future issue to be tracked down.
Quoted 2 lines
regards, p.
Signature · 1 line
Sent from my Android device with K-9 Mail. Please excuse my brevity.
Quoted 3 lines
This patch adds support for STMicro VL6180 - ALS and proximity sensor. Sensor is capable of measuring the light intensity as well as object distance using TOF(Time of Flight) technology.
I think this mention 'range', so: ALS, range and proximity sensor space after TOF
Quoted 1 line
Signed-off-by Manivannan Sadhasivam <[email protected]>
can you please add my Signed-off-by?
thanks for stepping up to complete this, comments below...
regards, p.
Quoted 15 lines
--- .../devicetree/bindings/iio/light/vl6180.txt | 16 + drivers/iio/light/Kconfig | 10 + drivers/iio/light/Makefile | 1 + drivers/iio/light/vl6180.c | 544 +++++++++++++++++++++ 4 files changed, 571 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/vl6180.txt create mode 100644 drivers/iio/light/vl6180.cDocumentation/devicetree/bindings/iio/light/vl6180.txt new file +0
-0
Documentation/devicetree/bindings/iio/light/vl6180.txt line 1
Quoted 1 line
Documentation/devicetree/bindings/iio/light/vl6180.txt new file +1
-0@@ -0,0 +1,16 @@ +STMicro VL6180 - ALS and proximity sensor
ALS, range and proximity
Quoted 20 lines
Documentation/devicetree/bindings/iio/light/vl6180.txt new file +15
-0+ +Link to datasheet: http://www.st.com/resource/en/datasheet/vl6180x.pdf + +Required properties: + + -compatible: should be "st,vl6180" + -reg: the I2C address of the sensor + +Example: + +vl6180@29 { + compatible = "st,vl6180"; + reg = <0x29>; +}; +drivers/iio/light/Kconfig +0
-0
drivers/iio/light/Kconfig config VEML6070 · line 356
Quoted 5 lines
drivers/iio/light/Kconfig +2
-0@@ -352,4 +352,14 @@ config VEML6070 To compile this driver as a module, choose M here: the module will be called veml6070. +config VL6180 + tristate "VL6180 ALS and proximity sensor"
ALS, range and proximity
drivers/iio/light/Kconfig config VEML6070 · line 360
Quoted 4 lines
drivers/iio/light/Kconfig +4
-0+ depends on I2C + help + Say Y here if you want to build a driver for the STMicroelectronics + VL6180 combined ambient light and proximity sensor.
ambient light, range and proximity sensor
Quoted 20 lines
drivers/iio/light/Kconfig +4
-0+ + To compile this driver as a module, choose M here: the + module will be called vl6180. + endmenudrivers/iio/light/Makefile +1
-0@@ -33,3 +33,4 @@ obj-$(CONFIG_TSL4531) += tsl4531.o obj-$(CONFIG_US5182D) += us5182d.o obj-$(CONFIG_VCNL4000) += vcnl4000.o obj-$(CONFIG_VEML6070) += veml6070.o +obj-$(CONFIG_VL6180) += vl6180.odrivers/iio/light/vl6180.c new file +0
-0
drivers/iio/light/vl6180.c line 2
Quoted 2 lines
drivers/iio/light/vl6180.c new file +2
-0@@ -0,0 +1,544 @@ +/* +* vl6180.c - Support for STMicroelectronics VL6180 ALS and proximity sensor
ALS, range and proximity
drivers/iio/light/vl6180.c line 11
Quoted 9 lines
drivers/iio/light/vl6180.c new file +9
-0+* +* Copyright 2017 Peter Meerwald-Stadler <[email protected]> +* Copyright 2017 Manivannan Sadhasivam <[email protected]> +* +* This file is subject to the terms and conditions of version 2 of +* the GNU General Public License. See the file COPYING in the main +* directory of this archive for more details. +* +* IIO driver for VL6180 (7-bit I2C slave addresses 0x29)
address
Quoted 52 lines
drivers/iio/light/vl6180.c new file +52
-0+* +* Range: 0 to 100mm +* ALS: < 1 Lux up to 100 kLux +* IR: 850nm +* +* TODO: irq, threshold events, continuous mode, hardware buffer +*/ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define VL6180_DRV_NAME "vl6180" + +/* Device identification register and value */ +#define VL6180_MODEL_ID 0x000 +#define VL6180_MODEL_ID_VAL 0xb4 + +/* Configuration registers */ +#define VL6180_INTR_CONFIG 0x014 +#define VL6180_INTR_CLEAR 0x015 +#define VL6180_OUT_OF_RESET 0x016 +#define VL6180_HOLD 0x017 +#define VL6180_RANGE_START 0x018 +#define VL6180_ALS_START 0x038 +#define VL6180_ALS_GAIN 0x03f +#define VL6180_ALS_IT 0x040 + +/* Status registers */ +#define VL6180_RANGE_STATUS 0x04d +#define VL6180_ALS_STATUS 0x04e +#define VL6180_INTR_STATUS 0x04f + +/* Result value registers */ +#define VL6180_ALS_VALUE 0x050 +#define VL6180_RANGE_VALUE 0x062 +#define VL6180_RANGE_RATE 0x066 + +/* bits of the RANGE_START and ALS_START register */ +#define VL6180_MODE_CONT BIT(1)/* continuous mode */ +#define VL6180_STARTSTOP BIT(0)/* start measurement, auto-reset */ + +/* bits of the INTR_STATUS and INTR_CONFIG register */ +#define VL6180_ALS_READY BIT(5) +#define VL6180_RANGE_READY BIT(2) +
drivers/iio/light/vl6180.c line 73
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+/* bits of the INTR_CLEAR register */ +#define VL6180_CLEAR_ERROR BIT(2) +#define VL6180_CLEAR_ALS BIT(1) +#define VL6180_CLEAR_RANGE BIT(0) + +/* bits of the HOLD register */ +#define VL6180_HOLD_ON BIT(0) + +/* default value for the ALS_IT register */ +#define VL6180_ALS_IT_100 0x063/* 100 ms */
0x63
Quoted 12 lines
drivers/iio/light/vl6180.c new file +12
-0+ +/* values for the ALS_GAIN register */ +#define VL6180_ALS_GAIN_1 0x46 +#define VL6180_ALS_GAIN_1_25 0x45 +#define VL6180_ALS_GAIN_1_67 0x44 +#define VL6180_ALS_GAIN_2_5 0x43 +#define VL6180_ALS_GAIN_5 0x42 +#define VL6180_ALS_GAIN_10 0x41 +#define VL6180_ALS_GAIN_20 0x40 +#define VL6180_ALS_GAIN_40 0x47 + +struct vl6180_data {
drivers/iio/light/vl6180.c line 95
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+struct i2c_client *client; +struct mutex lock; +}; + +enum {VL6180_ALS ,VL6180_RANGE ,VL6180_PROX }; + +/** +* struct vl6180_chan_regs - Registers for accessing channels +* @drdy_mask: Data ready bits in status register +* @start_reg: Conversion start registeir
register
drivers/iio/light/vl6180.c line 96
Quoted 1 line
drivers/iio/light/vl6180.c new file +1
-0+* @value_reg: Result value register
word missing
Quoted 22 lines
drivers/iio/light/vl6180.c new file +22
-0+*/ +struct vl6180_chan_regs { +u8 drdy_mask; +u16 start_reg, value_reg; +bool word; +}; + +static const struct vl6180_chan_regs vl6180_chan_regs_table[] = { + [VL6180_ALS ] = { + .drdy_mask =VL6180_ALS_READY , + .start_reg =VL6180_ALS_START , + .value_reg =VL6180_ALS_VALUE , + .word = true, + }, + [VL6180_RANGE ] = { + .drdy_mask =VL6180_RANGE_READY , + .start_reg =VL6180_RANGE_START , + .value_reg =VL6180_RANGE_VALUE , + .word = false, + }, + [VL6180_PROX ] = { + .drdy_mask =VL6180_RANGE_READY ,
drivers/iio/light/vl6180.c line 128
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ .start_reg = VL6180_RANGE_START, + .value_reg = VL6180_RANGE_RATE, + .word = true, + }, +}; + +static int vl6180_read (struct i2c_client *client,u16 cmd,void *databuf, +u8 len) +{ +u16 cmdbuf =cpu_to_be16 (cmd);
__be16 cmdbuf
drivers/iio/light/vl6180.c line 130
Quoted 2 lines
drivers/iio/light/vl6180.c new file +2
-0+struct i2c_msg msgs[2 ] = { + { .addr = client->addr, .len =2 , .buf = (u8 *) &cmdbuf },
sizeof(cmdbuf) instead of 2
drivers/iio/light/vl6180.c line 135
Quoted 5 lines
drivers/iio/light/vl6180.c new file +5
-0+ { .addr = client->addr, .len = len, .buf = databuf, + .flags = I2C_M_RD } }; +int ret; + + ret =i2c_transfer (client->adapter, msgs,2 );
ARRAY_SIZE(msgs) instread of 2
Quoted 25 lines
drivers/iio/light/vl6180.c new file +25
-0+if (ret <0 ) +dev_err (&client->dev,"failed reading register 0x%04x\n" , cmd); + +return ret; +} + +static int vl6180_read_byte (struct i2c_client *client,u16 cmd) +{ +u8 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 ) +return ret; + +return data; +} + +static int vl6180_read_word (struct i2c_client *client,u16 cmd) +{ +__be16 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 )
drivers/iio/light/vl6180.c line 170
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+return ret; + +return be16_to_cpu (data); +} + +static int vl6180_write_byte (struct i2c_client *client,u16 cmd,u8 val) +{ +u8 buf[3 ]; +struct i2c_msg msgs[1 ] = { + { .addr = client->addr, .len =3 , .buf = buf } };
sizeof(buf) instead of 3
Quoted 8 lines
drivers/iio/light/vl6180.c new file +8
-0+int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ; + buf[2 ] = val; + + ret =i2c_transfer (client->adapter, msgs,1 ); +if (ret <0 ) {
drivers/iio/light/vl6180.c line 188
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } + +return 0 ; +} + +static int vl6180_write_word (struct i2c_client *client,u16 cmd,u16 val) +{ +u8 buf[4 ];
__be16 buf[2]
drivers/iio/light/vl6180.c line 190
Quoted 2 lines
drivers/iio/light/vl6180.c new file +2
-0+struct i2c_msg msgs[1 ] = { + { .addr = client->addr, .len =4 , .buf = buf } };
sizeof(buf) instead of 4
drivers/iio/light/vl6180.c line 196
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ; + buf[2 ] = val >>8 ; + buf[3 ] = val &0xff ;
buf[0] = cpu_to_be16(cmd);
buf[1] = cpu_to_be16(val);
drivers/iio/light/vl6180.c line 198
Quoted 2 lines
drivers/iio/light/vl6180.c new file +2
-0+ + ret =i2c_transfer (client->adapter, msgs,1 );
ARRAY_SIZE(msgs)
Quoted 77 lines
drivers/iio/light/vl6180.c new file +77
-0+if (ret <0 ) { +dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } + +return 0 ; +} + +static int vl6180_measure (struct vl6180_data *data,int addr) +{ +struct i2c_client *client = data->client; +int tries =20 , ret; +u16 value; + +mutex_lock (&data->lock); +/* Start single shot measurement */ + ret =vl6180_write_byte (client, + vl6180_chan_regs_table[addr].start_reg,VL6180_STARTSTOP ); +if (ret <0 ) + goto fail; + +while (tries--) { + ret =vl6180_read_byte (client,VL6180_INTR_STATUS ); +if (ret <0 ) + goto fail; + +if (ret & vl6180_chan_regs_table[addr].drdy_mask) +break ; +msleep (20 ); + } + +if (tries <0 ) { + ret = -EIO ; + goto fail; + } + +/* Read result value from appropriate registers */ + ret = vl6180_chan_regs_table[addr].word ? +vl6180_read_word (client, vl6180_chan_regs_table[addr].value_reg) : +vl6180_read_byte (client, vl6180_chan_regs_table[addr].value_reg); +if (ret <0 ) + goto fail; + value = ret; + +/* Clear the interrupt flag after data read */ + ret =vl6180_write_byte (client,VL6180_INTR_CLEAR , +VL6180_CLEAR_ERROR |VL6180_CLEAR_ALS |VL6180_CLEAR_RANGE ); +if (ret <0 ) + goto fail; + + ret = value; + +fail: +mutex_unlock (&data->lock); + +return ret; +} + +static const struct iio_chan_spec vl6180_channels[] = { + { + .type =IIO_LIGHT , + .address =VL6180_ALS , + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_INT_TIME ) | +BIT (IIO_CHAN_INFO_SCALE ) | +BIT (IIO_CHAN_INFO_HARDWAREGAIN ), + }, { + .type =IIO_DISTANCE , + .address =VL6180_RANGE , + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_SCALE ), + }, { + .type =IIO_PROXIMITY , + .address =VL6180_PROX , + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ), + } +};
drivers/iio/light/vl6180.c line 285
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ +static const int vl6180_als_gain[8 ][4 ] = { + {1 ,0 ,70 ,VL6180_ALS_GAIN_1 }, + {1 ,250000 ,69 ,VL6180_ALS_GAIN_1_25 }, + {1 ,670000 ,68 ,VL6180_ALS_GAIN_1_67 }, + {2 ,500000 ,67 ,VL6180_ALS_GAIN_2_5 }, + {5 ,0 ,66 ,VL6180_ALS_GAIN_5 }, + {10 ,0 ,65 ,VL6180_ALS_GAIN_10 }, + {20 ,0 ,64 ,VL6180_ALS_GAIN_20 }, + {40 ,0 ,71 ,VL6180_ALS_GAIN_40 }
what are columns 3 and 4?
same value always?
drivers/iio/light/vl6180.c line 287
Quoted 2 lines
drivers/iio/light/vl6180.c new file +2
-0+}; +
drop newline
Quoted 58 lines
drivers/iio/light/vl6180.c new file +58
-0+ +static int vl6180_read_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int *val,int *val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); +int ret, i; + +switch (mask) { +case IIO_CHAN_INFO_RAW : + ret =vl6180_measure (data, chan->address); +if (ret <0 ) +return ret; + *val = ret; + +return IIO_VAL_INT ; +case IIO_CHAN_INFO_INT_TIME : + ret =vl6180_read_word (data->client,VL6180_ALS_IT ); +if (ret <0 ) +return ret; + *val =0 ;/* 1 count = 1ms (0 = 1ms) */ + *val2 = (ret +1 ) *1000 ;/* convert to seconds */ + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_SCALE : +switch (chan->type) { +case IIO_LIGHT : + *val =0 ;/* one ALS count is 0.32 Lux */ + *val2 =320000 ; +break ; +case IIO_DISTANCE : + *val =0 ;/* sensor reports mm, scale to meter */ + *val2 =1000 ; +break ; +default : +return -EINVAL ; + } + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_HARDWAREGAIN : + ret =vl6180_read_byte (data->client,VL6180_ALS_GAIN ); +if (ret <0 ) +return -EINVAL ; +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (ret == vl6180_als_gain[i][2 ]) { + *val = vl6180_als_gain[i][0 ]; + *val2 = vl6180_als_gain[i][1 ]; + } + } + +return IIO_VAL_INT_PLUS_MICRO ; +default : +return -EINVAL ; + } +} + +static IIO_CONST_ATTR(als_gain_available, "1 1.25 1.67 2.5 5 10 20 40"); +
drivers/iio/light/vl6180.c line 355
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+static struct attribute *vl6180_attributes[] = { + &iio_const_attr_als_gain_available.dev_attr.attr, +NULL +}; + +static const struct attribute_group vl6180_attribute_group = { + .attrs = vl6180_attributes, +}; + +/* HOLD is needed before updading any config registers */
updating
Quoted 178 lines
drivers/iio/light/vl6180.c new file +178
-0+static int vl6180_hold (struct vl6180_data *data,bool hold) +{ +return vl6180_write_byte (data->client,VL6180_HOLD , + hold ?VL6180_HOLD_ON :0 ); +} + +static int vl6180_set_als_gain (struct vl6180_data *data,int val,int val2) +{ +int i, ret; + +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (val == vl6180_als_gain[i][0 ] && + val2 == vl6180_als_gain[i][1 ]) { +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_byte (data->client,VL6180_ALS_GAIN , + vl6180_als_gain[i][3 ]); +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); +return ret; + } + } + +return -EINVAL ; +} + +static int vl6180_set_it (struct vl6180_data *data,int val2) +{ +int ret; + +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_word (data->client,VL6180_ALS_IT , + (val2 -500 ) /1000 );/* write value in ms */ +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); + +return ret; +} + +static int vl6180_write_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int val,int val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); + +switch (mask) { +case IIO_CHAN_INFO_INT_TIME : +if (val !=0 || val2 <500 || val2 >=512500 ) +return -EINVAL ; + +return vl6180_set_it (data, val2); +case IIO_CHAN_INFO_HARDWAREGAIN : +if (chan->type !=IIO_LIGHT ) +return -EINVAL ; + +return vl6180_set_als_gain (data, val, val2); +default : +return -EINVAL ; + } +} + +static const struct iio_info vl6180_info = { + .read_raw = vl6180_read_raw, + .write_raw = vl6180_write_raw, + .attrs = &vl6180_attribute_group, + .driver_module =THIS_MODULE , +}; + +/** +* Following settings are enabled: +* 1. ALS and Range ready interrupts +* 2. ALS integration time: 100ms +* 3. ALS gain: 1 +*/ +static int vl6180_init (struct vl6180_data *data) +{ +struct i2c_client *client = data->client; +int ret; + + ret =vl6180_read_byte (client,VL6180_MODEL_ID ); +if (ret <0 ) +return ret; + +if (ret !=VL6180_MODEL_ID_VAL ) { +dev_err (&client->dev,"invalid model ID %02x\n" , ret); +return -ENODEV ; + } + + ret =vl6180_hold (data, true); +if (ret <0 ) +return ret; + + ret =vl6180_read_byte (client,VL6180_OUT_OF_RESET ); +if (ret <0 ) +return ret; + +if (ret !=0x01 ) { +dev_err (&client->dev,"device is not fresh out of reset\n" ); +return -EINVAL ; + } + + ret =vl6180_write_byte (client,VL6180_INTR_CONFIG , +VL6180_ALS_READY |VL6180_RANGE_READY ); +if (ret <0 ) +return ret; + + ret =vl6180_write_word (client,VL6180_ALS_IT ,VL6180_ALS_IT_100 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_ALS_GAIN ,VL6180_ALS_GAIN_1 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_OUT_OF_RESET ,0x00 ); +if (ret <0 ) +return ret; + +return vl6180_hold (data, false); +} + +static int vl6180_probe (struct i2c_client *client, +const struct i2c_device_id *id) +{ +struct vl6180_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); +i2c_set_clientdata (client, indio_dev); + data->client = client; +mutex_init (&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &vl6180_info; + indio_dev->channels = vl6180_channels; + indio_dev->num_channels =ARRAY_SIZE (vl6180_channels); + indio_dev->name =VL6180_DRV_NAME ; + indio_dev->modes =INDIO_DIRECT_MODE ; + + ret =vl6180_init (data); +if (ret <0 ) +return ret; + + ret =devm_iio_device_register (&client->dev, indio_dev); +if (ret <0 ) +return ret; + +return ret; +} + +static const struct of_device_id vl6180_of_match[] = { + { .compatible ="st,vl6180" , }, + { }, +}; +MODULE_DEVICE_TABLE (of, vl6180_of_match); + +static const struct i2c_device_id vl6180_id[] = { + {"vl6180" ,0 }, + { } +}; +MODULE_DEVICE_TABLE (i2c, vl6180_id); + +static struct i2c_driver vl6180_driver = { + .driver = { + .name =VL6180_DRV_NAME , + .of_match_table =of_match_ptr (vl6180_of_match),
drivers/iio/light/vl6180.c line 543
Quoted 10 lines
drivers/iio/light/vl6180.c new file +10
-0+ }, + .probe = vl6180_probe, + .id_table = vl6180_id, +}; + +module_i2c_driver (vl6180_driver); + +MODULE_AUTHOR ("Peter Meerwald-Stadler <[email protected]>" ); +MODULE_AUTHOR ("Manivannan Sadhasivam <[email protected]>" ); +MODULE_DESCRIPTION ("STMicro VL6180 ALS and proximity sensor driver" );
ALS, range and proximity
Quoted 2 lines
drivers/iio/light/vl6180.c new file +1
-0+MODULE_LICENSE ("GPL" );
Signature · 2 lines
Peter Meerwald-Stadler Mobile: +43 664 24 44 418
Quoted 4 lines · Peter Meerwald-Stadler
Quoted 3 lines
This patch adds support for STMicro VL6180 - ALS and proximity sensor. Sensor is capable of measuring the light intensity as well as object distance using TOF(Time of Flight) technology.
Thanks Peter for your review.
Quoted 3 lines
I think this mention 'range', so: ALS, range and proximity sensor space after TOF
Ack
Quoted 4 lines
Quoted 1 line
Signed-off-by Manivannan Sadhasivam <[email protected]>can you please add my Signed-off-by?
Sure. I'll add it in next version
Quoted 13 lines
thanks for stepping up to complete this, comments below...
regards, p.
Quoted 9 lines
--- .../devicetree/bindings/iio/light/vl6180.txt | 16 + drivers/iio/light/Kconfig | 10 + drivers/iio/light/Makefile | 1 + drivers/iio/light/vl6180.c | 544 +++++++++++++++++++++ 4 files changed, 571 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/vl6180.txt create mode 100644 drivers/iio/light/vl6180.c
Quoted 10 lines
Documentation/devicetree/bindings/iio/light/vl6180.txt line 1
Quoted 7 lines
Documentation/devicetree/bindings/iio/light/vl6180.txt new file +1
-0@@ -0,0 +1,16 @@ +STMicro VL6180 - ALS and proximity sensorALS, range and proximity
Ack
Quoted 23 lines
Quoted 23 lines
Documentation/devicetree/bindings/iio/light/vl6180.txt new file +15
-0+ +Link to datasheet: http://www.st.com/resource/en/datasheet/vl6180x.pdf + +Required properties: + + -compatible: should be "st,vl6180" + -reg: the I2C address of the sensor + +Example: + +vl6180@29 { + compatible = "st,vl6180"; + reg = <0x29>; +}; +drivers/iio/light/Kconfig +0
-0@@ -352,4 +352,14 @@ config VEML6070 To compile this driver as a module, choose M here: the module will be called veml6070.
Quoted 5 lines
drivers/iio/light/Kconfig config VEML6070 · line 356
Quoted 2 lines
drivers/iio/light/Kconfig +2
-0+config VL6180 + tristate "VL6180 ALS and proximity sensor"ALS, range and proximity
Ack
Quoted 7 lines
drivers/iio/light/Kconfig config VEML6070 · line 360
Quoted 4 lines
drivers/iio/light/Kconfig +4
-0+ depends on I2C + help + Say Y here if you want to build a driver for the STMicroelectronics + VL6180 combined ambient light and proximity sensor.ambient light, range and proximity sensor
Ack
Quoted 16 lines
Quoted 16 lines
drivers/iio/light/Kconfig +4
-0+ + To compile this driver as a module, choose M here: the + module will be called vl6180. + endmenudrivers/iio/light/Makefile +1
-0@@ -33,3 +33,4 @@ obj-$(CONFIG_TSL4531) += tsl4531.o obj-$(CONFIG_US5182D) += us5182d.o obj-$(CONFIG_VCNL4000) += vcnl4000.o obj-$(CONFIG_VEML6070) += veml6070.o +obj-$(CONFIG_VL6180) += vl6180.odrivers/iio/light/vl6180.c new file +0
-0
Quoted 9 lines
drivers/iio/light/vl6180.c line 2
Quoted 6 lines
index 0000000..de80d96
drivers/iio/light/vl6180.c new file +2
-0@@ -0,0 +1,544 @@ +/* +* vl6180.c - Support for STMicroelectronics VL6180 ALS and proximity sensor ALS, range and proximity
Ack
Quoted 10 lines
drivers/iio/light/vl6180.c line 11
Quoted 9 lines
drivers/iio/light/vl6180.c new file +9
-0+* +* Copyright 2017 Peter Meerwald-Stadler <[email protected]> +* Copyright 2017 Manivannan Sadhasivam <[email protected]> +* +* This file is subject to the terms and conditions of version 2 of +* the GNU General Public License. See the file COPYING in the main +* directory of this archive for more details. +* +* IIO driver for VL6180 (7-bit I2C slave addresses 0x29)
Quoted 2 lines
address
Ack
Quoted 56 lines
Quoted 56 lines
drivers/iio/light/vl6180.c new file +56
-0+* +* Range: 0 to 100mm +* ALS: < 1 Lux up to 100 kLux +* IR: 850nm +* +* TODO: irq, threshold events, continuous mode, hardware buffer +*/ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define VL6180_DRV_NAME "vl6180" + +/* Device identification register and value */ +#define VL6180_MODEL_ID 0x000 +#define VL6180_MODEL_ID_VAL 0xb4 + +/* Configuration registers */ +#define VL6180_INTR_CONFIG 0x014 +#define VL6180_INTR_CLEAR 0x015 +#define VL6180_OUT_OF_RESET 0x016 +#define VL6180_HOLD 0x017 +#define VL6180_RANGE_START 0x018 +#define VL6180_ALS_START 0x038 +#define VL6180_ALS_GAIN 0x03f +#define VL6180_ALS_IT 0x040 + +/* Status registers */ +#define VL6180_RANGE_STATUS 0x04d +#define VL6180_ALS_STATUS 0x04e +#define VL6180_INTR_STATUS 0x04f + +/* Result value registers */ +#define VL6180_ALS_VALUE 0x050 +#define VL6180_RANGE_VALUE 0x062 +#define VL6180_RANGE_RATE 0x066 + +/* bits of the RANGE_START and ALS_START register */ +#define VL6180_MODE_CONT BIT(1)/* continuous mode */ +#define VL6180_STARTSTOP BIT(0)/* start measurement, auto-reset */ + +/* bits of the INTR_STATUS and INTR_CONFIG register */ +#define VL6180_ALS_READY BIT(5) +#define VL6180_RANGE_READY BIT(2) + +/* bits of the INTR_CLEAR register */ +#define VL6180_CLEAR_ERROR BIT(2) +#define VL6180_CLEAR_ALS BIT(1) +#define VL6180_CLEAR_RANGE BIT(0)
Quoted 9 lines
drivers/iio/light/vl6180.c line 73
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+ +/* bits of the HOLD register */ +#define VL6180_HOLD_ON BIT(0) + +/* default value for the ALS_IT register */ +#define VL6180_ALS_IT_100 0x063/* 100 ms */
0x63
Ack
Quoted 16 lines
Quoted 16 lines
drivers/iio/light/vl6180.c new file +16
-0+ +/* values for the ALS_GAIN register */ +#define VL6180_ALS_GAIN_1 0x46 +#define VL6180_ALS_GAIN_1_25 0x45 +#define VL6180_ALS_GAIN_1_67 0x44 +#define VL6180_ALS_GAIN_2_5 0x43 +#define VL6180_ALS_GAIN_5 0x42 +#define VL6180_ALS_GAIN_10 0x41 +#define VL6180_ALS_GAIN_20 0x40 +#define VL6180_ALS_GAIN_40 0x47 + +struct vl6180_data { +struct i2c_client *client; +struct mutex lock; +}; +
Quoted 9 lines
drivers/iio/light/vl6180.c line 95
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+enum {VL6180_ALS ,VL6180_RANGE ,VL6180_PROX }; + +/** +* struct vl6180_chan_regs - Registers for accessing channels +* @drdy_mask: Data ready bits in status register +* @start_reg: Conversion start registeir register
ACk
Quoted 4 lines
drivers/iio/light/vl6180.c line 96
Quoted 1 line
drivers/iio/light/vl6180.c new file +1
-0+* @value_reg: Result value register word missing
Ack
Quoted 26 lines
Quoted 26 lines
drivers/iio/light/vl6180.c new file +26
-0+*/ +struct vl6180_chan_regs { +u8 drdy_mask; +u16 start_reg, value_reg; +bool word; +}; + +static const struct vl6180_chan_regs vl6180_chan_regs_table[] = { + [VL6180_ALS ] = { + .drdy_mask =VL6180_ALS_READY , + .start_reg =VL6180_ALS_START , + .value_reg =VL6180_ALS_VALUE , + .word = true, + }, + [VL6180_RANGE ] = { + .drdy_mask =VL6180_RANGE_READY , + .start_reg =VL6180_RANGE_START , + .value_reg =VL6180_RANGE_VALUE , + .word = false, + }, + [VL6180_PROX ] = { + .drdy_mask =VL6180_RANGE_READY , + .start_reg =VL6180_RANGE_START , + .value_reg =VL6180_RANGE_RATE , + .word = true, + },
Quoted 9 lines
drivers/iio/light/vl6180.c line 128
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+}; + +static int vl6180_read (struct i2c_client *client,u16 cmd,void *databuf, +u8 len) +{ +u16 cmdbuf =cpu_to_be16 (cmd);
__be16cmdbuf
Ack
Quoted 5 lines
drivers/iio/light/vl6180.c line 130
Quoted 2 lines
drivers/iio/light/vl6180.c new file +2
-0+struct i2c_msg msgs[2 ] = { + { .addr = client->addr, .len =2 , .buf = (u8 *) &cmdbuf },sizeof(cmdbuf) instead of 2
Ack
Quoted 8 lines
drivers/iio/light/vl6180.c line 135
Quoted 5 lines
drivers/iio/light/vl6180.c new file +5
-0+ { .addr = client->addr, .len = len, .buf = databuf, + .flags = I2C_M_RD } }; +int ret; + + ret =i2c_transfer (client->adapter, msgs,2 );
ARRAY_SIZE(msgs)instread of 2
Ack
Quoted 29 lines
Quoted 29 lines
drivers/iio/light/vl6180.c new file +29
-0+if (ret <0 ) +dev_err (&client->dev,"failed reading register 0x%04x\n" , cmd); + +return ret; +} + +static int vl6180_read_byte (struct i2c_client *client,u16 cmd) +{ +u8 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 ) +return ret; + +return data; +} + +static int vl6180_read_word (struct i2c_client *client,u16 cmd) +{ +__be16 data; +int ret; + + ret =vl6180_read (client, cmd, &data,sizeof (data)); +if (ret <0 ) +return ret; + +return be16_to_cpu (data); +}
Quoted 9 lines
drivers/iio/light/vl6180.c line 170
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+ +static int vl6180_write_byte (struct i2c_client *client,u16 cmd,u8 val) +{ +u8 buf[3 ]; +struct i2c_msg msgs[1 ] = { + { .addr = client->addr, .len =3 , .buf = buf } };sizeof(buf) instead of 3
Ack
Quoted 12 lines
Quoted 12 lines
drivers/iio/light/vl6180.c new file +12
-0+int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ; + buf[2 ] = val; + + ret =i2c_transfer (client->adapter, msgs,1 ); +if (ret <0 ) { +dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } +
Quoted 9 lines
drivers/iio/light/vl6180.c line 188
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+return 0 ; +} + +static int vl6180_write_word (struct i2c_client *client,u16 cmd,u16 val) +{ +u8 buf[4 ];
__be16buf[2]
Ack
Quoted 5 lines
drivers/iio/light/vl6180.c line 190
Quoted 2 lines
drivers/iio/light/vl6180.c new file +2
-0+struct i2c_msg msgs[1 ] = { + { .addr = client->addr, .len =4 , .buf = buf } };sizeof(buf) instead of 4
Ack
Quoted 8 lines
drivers/iio/light/vl6180.c line 196
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+int ret; + + buf[0 ] = cmd >>8 ; + buf[1 ] = cmd &0xff ; + buf[2 ] = val >>8 ; + buf[3 ] = val &0xff ;buf[0] =
cpu_to_be16(cmd);
buf[1] =cpu_to_be16(val);
Ack
Quoted 5 lines
drivers/iio/light/vl6180.c line 198
Quoted 2 lines
drivers/iio/light/vl6180.c new file +2
-0+ + ret =i2c_transfer (client->adapter, msgs,1 );
ARRAY_SIZE(msgs)
Ack
Quoted 82 lines
Quoted 82 lines
drivers/iio/light/vl6180.c new file +82
-0+if (ret <0 ) { +dev_err (&client->dev,"failed writing register 0x%04x\n" , cmd); +return ret; + } + +return 0 ; +} + +static int vl6180_measure (struct vl6180_data *data,int addr) +{ +struct i2c_client *client = data->client; +int tries =20 , ret; +u16 value; + +mutex_lock (&data->lock); +/* Start single shot measurement */ + ret =vl6180_write_byte (client, + vl6180_chan_regs_table[addr].start_reg,VL6180_STARTSTOP ); +if (ret <0 ) + goto fail; + +while (tries--) { + ret =vl6180_read_byte (client,VL6180_INTR_STATUS ); +if (ret <0 ) + goto fail; + +if (ret & vl6180_chan_regs_table[addr].drdy_mask) +break ; +msleep (20 ); + } + +if (tries <0 ) { + ret = -EIO ; + goto fail; + } + +/* Read result value from appropriate registers */ + ret = vl6180_chan_regs_table[addr].word ? +vl6180_read_word (client, vl6180_chan_regs_table[addr].value_reg) : +vl6180_read_byte (client, vl6180_chan_regs_table[addr].value_reg); +if (ret <0 ) + goto fail; + value = ret; + +/* Clear the interrupt flag after data read */ + ret =vl6180_write_byte (client,VL6180_INTR_CLEAR , +VL6180_CLEAR_ERROR |VL6180_CLEAR_ALS |VL6180_CLEAR_RANGE ); +if (ret <0 ) + goto fail; + + ret = value; + +fail: +mutex_unlock (&data->lock); + +return ret; +} + +static const struct iio_chan_spec vl6180_channels[] = { + { + .type =IIO_LIGHT , + .address =VL6180_ALS , + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_INT_TIME ) | +BIT (IIO_CHAN_INFO_SCALE ) | +BIT (IIO_CHAN_INFO_HARDWAREGAIN ), + }, { + .type =IIO_DISTANCE , + .address =VL6180_RANGE , + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_SCALE ), + }, { + .type =IIO_PROXIMITY , + .address =VL6180_PROX , + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ), + } +}; + +static const int vl6180_als_gain[8 ][4 ] = { + {1 ,0 ,70 ,VL6180_ALS_GAIN_1 }, + {1 ,250000 ,69 ,VL6180_ALS_GAIN_1_25 }, + {1 ,670000 ,68 ,VL6180_ALS_GAIN_1_67 },
Quoted 9 lines
drivers/iio/light/vl6180.c line 285
Quoted 5 lines
drivers/iio/light/vl6180.c new file +5
-0+ { 2, 500000, 67, VL6180_ALS_GAIN_2_5 }, + { 5, 0, 66, VL6180_ALS_GAIN_5 }, + { 10, 0, 65, VL6180_ALS_GAIN_10 }, + { 20, 0, 64, VL6180_ALS_GAIN_20 }, + { 40, 0, 71, VL6180_ALS_GAIN_40 }what are columns 3 and 4?
same value always?
Same values but the data read from register is in decimal, so for comparision purposes I've kept both decimal as well as hexadecimal values in separate columns. I thought that instead of converting the values from decimal to hex this would be better.
Quoted 5 lines
drivers/iio/light/vl6180.c line 287
Quoted 2 lines
drivers/iio/light/vl6180.c new file +2
-0+}; +drop newline
Ack
Quoted 62 lines
Quoted 62 lines
drivers/iio/light/vl6180.c new file +62
-0+ +static int vl6180_read_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int *val,int *val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); +int ret, i; + +switch (mask) { +case IIO_CHAN_INFO_RAW : + ret =vl6180_measure (data, chan->address); +if (ret <0 ) +return ret; + *val = ret; + +return IIO_VAL_INT ; +case IIO_CHAN_INFO_INT_TIME : + ret =vl6180_read_word (data->client,VL6180_ALS_IT ); +if (ret <0 ) +return ret; + *val =0 ;/* 1 count = 1ms (0 = 1ms) */ + *val2 = (ret +1 ) *1000 ;/* convert to seconds */ + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_SCALE : +switch (chan->type) { +case IIO_LIGHT : + *val =0 ;/* one ALS count is 0.32 Lux */ + *val2 =320000 ; +break ; +case IIO_DISTANCE : + *val =0 ;/* sensor reports mm, scale to meter */ + *val2 =1000 ; +break ; +default : +return -EINVAL ; + } + +return IIO_VAL_INT_PLUS_MICRO ; +case IIO_CHAN_INFO_HARDWAREGAIN : + ret =vl6180_read_byte (data->client,VL6180_ALS_GAIN ); +if (ret <0 ) +return -EINVAL ; +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (ret == vl6180_als_gain[i][2 ]) { + *val = vl6180_als_gain[i][0 ]; + *val2 = vl6180_als_gain[i][1 ]; + } + } + +return IIO_VAL_INT_PLUS_MICRO ; +default : +return -EINVAL ; + } +} + +static IIO_CONST_ATTR(als_gain_available, "1 1.25 1.67 2.5 5 10 20 40"); + +static struct attribute *vl6180_attributes[] = { + &iio_const_attr_als_gain_available.dev_attr.attr, +NULL +};
Quoted 9 lines
drivers/iio/light/vl6180.c line 355
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+ +static const struct attribute_group vl6180_attribute_group = { + .attrs = vl6180_attributes, +}; + +/* HOLD is needed before updading any config registers */ updating
Ack
Quoted 182 lines
Quoted 182 lines
drivers/iio/light/vl6180.c new file +182
-0+static int vl6180_hold (struct vl6180_data *data,bool hold) +{ +return vl6180_write_byte (data->client,VL6180_HOLD , + hold ?VL6180_HOLD_ON :0 ); +} + +static int vl6180_set_als_gain (struct vl6180_data *data,int val,int val2) +{ +int i, ret; + +for (i =0 ; i <ARRAY_SIZE (vl6180_als_gain); i++) { +if (val == vl6180_als_gain[i][0 ] && + val2 == vl6180_als_gain[i][1 ]) { +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_byte (data->client,VL6180_ALS_GAIN , + vl6180_als_gain[i][3 ]); +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); +return ret; + } + } + +return -EINVAL ; +} + +static int vl6180_set_it (struct vl6180_data *data,int val2) +{ +int ret; + +mutex_lock (&data->lock); + ret =vl6180_hold (data, true); +if (ret <0 ) + goto fail; + ret =vl6180_write_word (data->client,VL6180_ALS_IT , + (val2 -500 ) /1000 );/* write value in ms */ +fail: +vl6180_hold (data, false); +mutex_unlock (&data->lock); + +return ret; +} + +static int vl6180_write_raw (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +int val,int val2,long mask) +{ +struct vl6180_data *data =iio_priv (indio_dev); + +switch (mask) { +case IIO_CHAN_INFO_INT_TIME : +if (val !=0 || val2 <500 || val2 >=512500 ) +return -EINVAL ; + +return vl6180_set_it (data, val2); +case IIO_CHAN_INFO_HARDWAREGAIN : +if (chan->type !=IIO_LIGHT ) +return -EINVAL ; + +return vl6180_set_als_gain (data, val, val2); +default : +return -EINVAL ; + } +} + +static const struct iio_info vl6180_info = { + .read_raw = vl6180_read_raw, + .write_raw = vl6180_write_raw, + .attrs = &vl6180_attribute_group, + .driver_module =THIS_MODULE , +}; + +/** +* Following settings are enabled: +* 1. ALS and Range ready interrupts +* 2. ALS integration time: 100ms +* 3. ALS gain: 1 +*/ +static int vl6180_init (struct vl6180_data *data) +{ +struct i2c_client *client = data->client; +int ret; + + ret =vl6180_read_byte (client,VL6180_MODEL_ID ); +if (ret <0 ) +return ret; + +if (ret !=VL6180_MODEL_ID_VAL ) { +dev_err (&client->dev,"invalid model ID %02x\n" , ret); +return -ENODEV ; + } + + ret =vl6180_hold (data, true); +if (ret <0 ) +return ret; + + ret =vl6180_read_byte (client,VL6180_OUT_OF_RESET ); +if (ret <0 ) +return ret; + +if (ret !=0x01 ) { +dev_err (&client->dev,"device is not fresh out of reset\n" ); +return -EINVAL ; + } + + ret =vl6180_write_byte (client,VL6180_INTR_CONFIG , +VL6180_ALS_READY |VL6180_RANGE_READY ); +if (ret <0 ) +return ret; + + ret =vl6180_write_word (client,VL6180_ALS_IT ,VL6180_ALS_IT_100 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_ALS_GAIN ,VL6180_ALS_GAIN_1 ); +if (ret <0 ) +return ret; + + ret =vl6180_write_byte (client,VL6180_OUT_OF_RESET ,0x00 ); +if (ret <0 ) +return ret; + +return vl6180_hold (data, false); +} + +static int vl6180_probe (struct i2c_client *client, +const struct i2c_device_id *id) +{ +struct vl6180_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); +i2c_set_clientdata (client, indio_dev); + data->client = client; +mutex_init (&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &vl6180_info; + indio_dev->channels = vl6180_channels; + indio_dev->num_channels =ARRAY_SIZE (vl6180_channels); + indio_dev->name =VL6180_DRV_NAME ; + indio_dev->modes =INDIO_DIRECT_MODE ; + + ret =vl6180_init (data); +if (ret <0 ) +return ret; + + ret =devm_iio_device_register (&client->dev, indio_dev); +if (ret <0 ) +return ret; + +return ret; +} + +static const struct of_device_id vl6180_of_match[] = { + { .compatible ="st,vl6180" , }, + { }, +}; +MODULE_DEVICE_TABLE (of, vl6180_of_match); + +static const struct i2c_device_id vl6180_id[] = { + {"vl6180" ,0 }, + { } +}; +MODULE_DEVICE_TABLE (i2c, vl6180_id); + +static struct i2c_driver vl6180_driver = { + .driver = { + .name =VL6180_DRV_NAME , + .of_match_table =of_match_ptr (vl6180_of_match), + }, + .probe = vl6180_probe, + .id_table = vl6180_id, +};
Quoted 9 lines
drivers/iio/light/vl6180.c line 543
Quoted 6 lines
drivers/iio/light/vl6180.c new file +6
-0+ +module_i2c_driver (vl6180_driver); + +MODULE_AUTHOR ("Peter Meerwald-Stadler <[email protected]>" ); +MODULE_AUTHOR ("Manivannan Sadhasivam <[email protected]>" ); +MODULE_DESCRIPTION ("STMicro VL6180 ALS and proximity sensor driver" );ALS, range and proximity
Ack
Quoted 7 lines
Quoted 2 lines
drivers/iio/light/vl6180.c new file +1
-0+MODULE_LICENSE ("GPL" );Signature · 2 lines
Peter Meerwald-Stadler Mobile: +43 664 24 44 418