Skip to content

Commit ac101e6

Browse files
Ivan Mikhaylovjic23
authored andcommitted
iio: proximity: Add driver support for vcnl3020 proximity sensor
Proximity sensor driver based on light/vcnl4000.c code. For now supports only the single on-demand measurement. The VCNL3020 is a fully integrated proximity sensor. Fully integrated means that the infrared emitter is included in the package. It has 16-bit resolution. It includes a signal processing IC and features standard I2C communication interface. It features an interrupt function. Datasheet: http://www.vishay.com/docs/84150/vcnl3020.pdf Signed-off-by: Ivan Mikhaylov <[email protected]> Reviewed-by: Andy Shevchenko <[email protected]> Signed-off-by: Jonathan Cameron <[email protected]>
1 parent 9ecd118 commit ac101e6

File tree

3 files changed

+270
-0
lines changed

3 files changed

+270
-0
lines changed

drivers/iio/proximity/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,17 @@ config SRF08
140140
To compile this driver as a module, choose M here: the
141141
module will be called srf08.
142142

143+
config VCNL3020
144+
tristate "VCNL3020 proximity sensor"
145+
select REGMAP_I2C
146+
depends on I2C
147+
help
148+
Say Y here if you want to build a driver for the Vishay VCNL3020
149+
proximity sensor.
150+
151+
To compile this driver as a module, choose M here: the
152+
module will be called vcnl3020.
153+
143154
config VL53L0X_I2C
144155
tristate "STMicroelectronics VL53L0X ToF ranger sensor (I2C)"
145156
depends on I2C

drivers/iio/proximity/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ obj-$(CONFIG_SRF04) += srf04.o
1414
obj-$(CONFIG_SRF08) += srf08.o
1515
obj-$(CONFIG_SX9310) += sx9310.o
1616
obj-$(CONFIG_SX9500) += sx9500.o
17+
obj-$(CONFIG_VCNL3020) += vcnl3020.o
1718
obj-$(CONFIG_VL53L0X_I2C) += vl53l0x-i2c.o
1819

drivers/iio/proximity/vcnl3020.c

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Support for Vishay VCNL3020 proximity sensor on i2c bus.
4+
* Based on Vishay VCNL4000 driver code.
5+
*
6+
* TODO: interrupts.
7+
*/
8+
9+
#include <linux/module.h>
10+
#include <linux/i2c.h>
11+
#include <linux/err.h>
12+
#include <linux/delay.h>
13+
#include <linux/regmap.h>
14+
15+
#include <linux/iio/iio.h>
16+
#include <linux/iio/sysfs.h>
17+
18+
#define VCNL3020_PROD_ID 0x21
19+
20+
#define VCNL_COMMAND 0x80 /* Command register */
21+
#define VCNL_PROD_REV 0x81 /* Product ID and Revision ID */
22+
#define VCNL_PROXIMITY_RATE 0x82 /* Rate of Proximity Measurement */
23+
#define VCNL_LED_CURRENT 0x83 /* IR LED current for proximity mode */
24+
#define VCNL_PS_RESULT_HI 0x87 /* Proximity result register, MSB */
25+
#define VCNL_PS_RESULT_LO 0x88 /* Proximity result register, LSB */
26+
#define VCNL_PS_ICR 0x89 /* Interrupt Control Register */
27+
#define VCNL_PS_LO_THR_HI 0x8a /* High byte of low threshold value */
28+
#define VCNL_PS_LO_THR_LO 0x8b /* Low byte of low threshold value */
29+
#define VCNL_PS_HI_THR_HI 0x8c /* High byte of high threshold value */
30+
#define VCNL_PS_HI_THR_LO 0x8d /* Low byte of high threshold value */
31+
#define VCNL_ISR 0x8e /* Interrupt Status Register */
32+
#define VCNL_PS_MOD_ADJ 0x8f /* Proximity Modulator Timing Adjustment */
33+
34+
/* Bit masks for COMMAND register */
35+
#define VCNL_PS_RDY BIT(5) /* proximity data ready? */
36+
#define VCNL_PS_OD BIT(3) /* start on-demand proximity
37+
* measurement
38+
*/
39+
40+
#define VCNL_ON_DEMAND_TIMEOUT_US 100000
41+
#define VCNL_POLL_US 20000
42+
43+
/**
44+
* struct vcnl3020_data - vcnl3020 specific data.
45+
* @regmap: device register map.
46+
* @dev: vcnl3020 device.
47+
* @rev: revision id.
48+
* @lock: lock for protecting access to device hardware registers.
49+
*/
50+
struct vcnl3020_data {
51+
struct regmap *regmap;
52+
struct device *dev;
53+
u8 rev;
54+
struct mutex lock;
55+
};
56+
57+
/**
58+
* struct vcnl3020_property - vcnl3020 property.
59+
* @name: property name.
60+
* @reg: i2c register offset.
61+
* @conversion_func: conversion function.
62+
*/
63+
struct vcnl3020_property {
64+
const char *name;
65+
u32 reg;
66+
u32 (*conversion_func)(u32 *val);
67+
};
68+
69+
static u32 microamp_to_reg(u32 *val)
70+
{
71+
/*
72+
* An example of conversion from uA to reg val:
73+
* 200000 uA == 200 mA == 20
74+
*/
75+
return *val /= 10000;
76+
};
77+
78+
static struct vcnl3020_property vcnl3020_led_current_property = {
79+
.name = "vishay,led-current-microamp",
80+
.reg = VCNL_LED_CURRENT,
81+
.conversion_func = microamp_to_reg,
82+
};
83+
84+
static int vcnl3020_get_and_apply_property(struct vcnl3020_data *data,
85+
struct vcnl3020_property prop)
86+
{
87+
int rc;
88+
u32 val;
89+
90+
rc = device_property_read_u32(data->dev, prop.name, &val);
91+
if (rc)
92+
return 0;
93+
94+
if (prop.conversion_func)
95+
prop.conversion_func(&val);
96+
97+
rc = regmap_write(data->regmap, prop.reg, val);
98+
if (rc) {
99+
dev_err(data->dev, "Error (%d) setting property (%s)\n",
100+
rc, prop.name);
101+
}
102+
103+
return rc;
104+
}
105+
106+
static int vcnl3020_init(struct vcnl3020_data *data)
107+
{
108+
int rc;
109+
unsigned int reg;
110+
111+
rc = regmap_read(data->regmap, VCNL_PROD_REV, &reg);
112+
if (rc) {
113+
dev_err(data->dev,
114+
"Error (%d) reading product revision\n", rc);
115+
return rc;
116+
}
117+
118+
if (reg != VCNL3020_PROD_ID) {
119+
dev_err(data->dev,
120+
"Product id (%x) did not match vcnl3020 (%x)\n", reg,
121+
VCNL3020_PROD_ID);
122+
return -ENODEV;
123+
}
124+
125+
data->rev = reg;
126+
mutex_init(&data->lock);
127+
128+
return vcnl3020_get_and_apply_property(data,
129+
vcnl3020_led_current_property);
130+
};
131+
132+
static int vcnl3020_measure_proximity(struct vcnl3020_data *data, int *val)
133+
{
134+
int rc;
135+
unsigned int reg;
136+
__be16 res;
137+
138+
mutex_lock(&data->lock);
139+
140+
rc = regmap_write(data->regmap, VCNL_COMMAND, VCNL_PS_OD);
141+
if (rc)
142+
goto err_unlock;
143+
144+
/* wait for data to become ready */
145+
rc = regmap_read_poll_timeout(data->regmap, VCNL_COMMAND, reg,
146+
reg & VCNL_PS_RDY, VCNL_POLL_US,
147+
VCNL_ON_DEMAND_TIMEOUT_US);
148+
if (rc) {
149+
dev_err(data->dev,
150+
"Error (%d) reading vcnl3020 command register\n", rc);
151+
goto err_unlock;
152+
}
153+
154+
/* high & low result bytes read */
155+
rc = regmap_bulk_read(data->regmap, VCNL_PS_RESULT_HI, &res,
156+
sizeof(res));
157+
if (rc)
158+
goto err_unlock;
159+
160+
*val = be16_to_cpu(res);
161+
162+
err_unlock:
163+
mutex_unlock(&data->lock);
164+
165+
return rc;
166+
}
167+
168+
static const struct iio_chan_spec vcnl3020_channels[] = {
169+
{
170+
.type = IIO_PROXIMITY,
171+
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
172+
},
173+
};
174+
175+
static int vcnl3020_read_raw(struct iio_dev *indio_dev,
176+
struct iio_chan_spec const *chan, int *val,
177+
int *val2, long mask)
178+
{
179+
int rc;
180+
struct vcnl3020_data *data = iio_priv(indio_dev);
181+
182+
switch (mask) {
183+
case IIO_CHAN_INFO_RAW:
184+
rc = vcnl3020_measure_proximity(data, val);
185+
if (rc)
186+
return rc;
187+
return IIO_VAL_INT;
188+
default:
189+
return -EINVAL;
190+
}
191+
}
192+
193+
static const struct iio_info vcnl3020_info = {
194+
.read_raw = vcnl3020_read_raw,
195+
};
196+
197+
static const struct regmap_config vcnl3020_regmap_config = {
198+
.reg_bits = 8,
199+
.val_bits = 8,
200+
.max_register = VCNL_PS_MOD_ADJ,
201+
};
202+
203+
static int vcnl3020_probe(struct i2c_client *client)
204+
{
205+
struct vcnl3020_data *data;
206+
struct iio_dev *indio_dev;
207+
struct regmap *regmap;
208+
int rc;
209+
210+
regmap = devm_regmap_init_i2c(client, &vcnl3020_regmap_config);
211+
if (IS_ERR(regmap)) {
212+
dev_err(&client->dev, "regmap_init failed\n");
213+
return PTR_ERR(regmap);
214+
}
215+
216+
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
217+
if (!indio_dev)
218+
return -ENOMEM;
219+
220+
data = iio_priv(indio_dev);
221+
i2c_set_clientdata(client, indio_dev);
222+
data->regmap = regmap;
223+
data->dev = &client->dev;
224+
225+
rc = vcnl3020_init(data);
226+
if (rc)
227+
return rc;
228+
229+
indio_dev->dev.parent = &client->dev;
230+
indio_dev->info = &vcnl3020_info;
231+
indio_dev->channels = vcnl3020_channels;
232+
indio_dev->num_channels = ARRAY_SIZE(vcnl3020_channels);
233+
indio_dev->name = "vcnl3020";
234+
indio_dev->modes = INDIO_DIRECT_MODE;
235+
236+
return devm_iio_device_register(&client->dev, indio_dev);
237+
}
238+
239+
static const struct of_device_id vcnl3020_of_match[] = {
240+
{
241+
.compatible = "vishay,vcnl3020",
242+
},
243+
{}
244+
};
245+
MODULE_DEVICE_TABLE(of, vcnl3020_of_match);
246+
247+
static struct i2c_driver vcnl3020_driver = {
248+
.driver = {
249+
.name = "vcnl3020",
250+
.of_match_table = vcnl3020_of_match,
251+
},
252+
.probe_new = vcnl3020_probe,
253+
};
254+
module_i2c_driver(vcnl3020_driver);
255+
256+
MODULE_AUTHOR("Ivan Mikhaylov <[email protected]>");
257+
MODULE_DESCRIPTION("Vishay VCNL3020 proximity sensor driver");
258+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)