Commit | Line | Data |
---|---|---|
62a1efb9 | 1 | /* |
be38866f | 2 | * vcnl4000.c - Support for Vishay VCNL4000/4010/4020/4200 combined ambient |
d978bfdd | 3 | * light and proximity sensor |
62a1efb9 PM |
4 | * |
5 | * Copyright 2012 Peter Meerwald <pmeerw@pmeerw.net> | |
6 | * | |
7 | * This file is subject to the terms and conditions of version 2 of | |
8 | * the GNU General Public License. See the file COPYING in the main | |
9 | * directory of this archive for more details. | |
10 | * | |
be38866f TN |
11 | * IIO driver for: |
12 | * VCNL4000/10/20 (7-bit I2C slave address 0x13) | |
13 | * VCNL4200 (7-bit I2C slave address 0x51) | |
62a1efb9 PM |
14 | * |
15 | * TODO: | |
16 | * allow to adjust IR current | |
17 | * proximity threshold and event handling | |
d978bfdd | 18 | * periodic ALS/proximity measurement (VCNL4010/20) |
be38866f | 19 | * interrupts (VCNL4010/20, VCNL4200) |
62a1efb9 PM |
20 | */ |
21 | ||
22 | #include <linux/module.h> | |
23 | #include <linux/i2c.h> | |
24 | #include <linux/err.h> | |
25 | #include <linux/delay.h> | |
26 | ||
27 | #include <linux/iio/iio.h> | |
28 | #include <linux/iio/sysfs.h> | |
29 | ||
30 | #define VCNL4000_DRV_NAME "vcnl4000" | |
1ebc787a TN |
31 | #define VCNL4000_PROD_ID 0x01 |
32 | #define VCNL4010_PROD_ID 0x02 /* for VCNL4020, VCNL4010 */ | |
be38866f | 33 | #define VCNL4200_PROD_ID 0x58 |
62a1efb9 PM |
34 | |
35 | #define VCNL4000_COMMAND 0x80 /* Command register */ | |
36 | #define VCNL4000_PROD_REV 0x81 /* Product ID and Revision ID */ | |
37 | #define VCNL4000_LED_CURRENT 0x83 /* IR LED current for proximity mode */ | |
38 | #define VCNL4000_AL_PARAM 0x84 /* Ambient light parameter register */ | |
39 | #define VCNL4000_AL_RESULT_HI 0x85 /* Ambient light result register, MSB */ | |
40 | #define VCNL4000_AL_RESULT_LO 0x86 /* Ambient light result register, LSB */ | |
41 | #define VCNL4000_PS_RESULT_HI 0x87 /* Proximity result register, MSB */ | |
42 | #define VCNL4000_PS_RESULT_LO 0x88 /* Proximity result register, LSB */ | |
43 | #define VCNL4000_PS_MEAS_FREQ 0x89 /* Proximity test signal frequency */ | |
44 | #define VCNL4000_PS_MOD_ADJ 0x8a /* Proximity modulator timing adjustment */ | |
45 | ||
be38866f TN |
46 | #define VCNL4200_AL_CONF 0x00 /* Ambient light configuration */ |
47 | #define VCNL4200_PS_CONF1 0x03 /* Proximity configuration */ | |
48 | #define VCNL4200_PS_DATA 0x08 /* Proximity data */ | |
49 | #define VCNL4200_AL_DATA 0x09 /* Ambient light data */ | |
50 | #define VCNL4200_DEV_ID 0x0e /* Device ID, slave address and version */ | |
51 | ||
62a1efb9 | 52 | /* Bit masks for COMMAND register */ |
ff6a5259 PMS |
53 | #define VCNL4000_AL_RDY BIT(6) /* ALS data ready? */ |
54 | #define VCNL4000_PS_RDY BIT(5) /* proximity data ready? */ | |
55 | #define VCNL4000_AL_OD BIT(4) /* start on-demand ALS measurement */ | |
56 | #define VCNL4000_PS_OD BIT(3) /* start on-demand proximity measurement */ | |
62a1efb9 | 57 | |
1ebc787a TN |
58 | enum vcnl4000_device_ids { |
59 | VCNL4000, | |
50c50b97 | 60 | VCNL4010, |
be38866f TN |
61 | VCNL4200, |
62 | }; | |
63 | ||
64 | struct vcnl4200_channel { | |
65 | u8 reg; | |
66 | ktime_t last_measurement; | |
67 | ktime_t sampling_rate; | |
68 | struct mutex lock; | |
1ebc787a TN |
69 | }; |
70 | ||
62a1efb9 PM |
71 | struct vcnl4000_data { |
72 | struct i2c_client *client; | |
1ebc787a TN |
73 | enum vcnl4000_device_ids id; |
74 | int rev; | |
75 | int al_scale; | |
76 | const struct vcnl4000_chip_spec *chip_spec; | |
be38866f TN |
77 | struct mutex vcnl4000_lock; |
78 | struct vcnl4200_channel vcnl4200_al; | |
79 | struct vcnl4200_channel vcnl4200_ps; | |
62a1efb9 PM |
80 | }; |
81 | ||
1ebc787a TN |
82 | struct vcnl4000_chip_spec { |
83 | const char *prod; | |
84 | int (*init)(struct vcnl4000_data *data); | |
85 | int (*measure_light)(struct vcnl4000_data *data, int *val); | |
86 | int (*measure_proximity)(struct vcnl4000_data *data, int *val); | |
87 | }; | |
88 | ||
62a1efb9 | 89 | static const struct i2c_device_id vcnl4000_id[] = { |
1ebc787a | 90 | { "vcnl4000", VCNL4000 }, |
50c50b97 TN |
91 | { "vcnl4010", VCNL4010 }, |
92 | { "vcnl4020", VCNL4010 }, | |
be38866f | 93 | { "vcnl4200", VCNL4200 }, |
62a1efb9 PM |
94 | { } |
95 | }; | |
96 | MODULE_DEVICE_TABLE(i2c, vcnl4000_id); | |
97 | ||
1ebc787a TN |
98 | static int vcnl4000_init(struct vcnl4000_data *data) |
99 | { | |
100 | int ret, prod_id; | |
101 | ||
102 | ret = i2c_smbus_read_byte_data(data->client, VCNL4000_PROD_REV); | |
103 | if (ret < 0) | |
104 | return ret; | |
105 | ||
106 | prod_id = ret >> 4; | |
58bf9ace TN |
107 | switch (prod_id) { |
108 | case VCNL4000_PROD_ID: | |
109 | if (data->id != VCNL4000) | |
110 | dev_warn(&data->client->dev, | |
111 | "wrong device id, use vcnl4000"); | |
112 | break; | |
113 | case VCNL4010_PROD_ID: | |
114 | if (data->id != VCNL4010) | |
115 | dev_warn(&data->client->dev, | |
116 | "wrong device id, use vcnl4010/4020"); | |
117 | break; | |
118 | default: | |
1ebc787a | 119 | return -ENODEV; |
58bf9ace | 120 | } |
1ebc787a TN |
121 | |
122 | data->rev = ret & 0xf; | |
123 | data->al_scale = 250000; | |
be38866f TN |
124 | mutex_init(&data->vcnl4000_lock); |
125 | ||
126 | return 0; | |
127 | }; | |
128 | ||
129 | static int vcnl4200_init(struct vcnl4000_data *data) | |
130 | { | |
131 | int ret; | |
132 | ||
133 | ret = i2c_smbus_read_word_data(data->client, VCNL4200_DEV_ID); | |
134 | if (ret < 0) | |
135 | return ret; | |
136 | ||
137 | if ((ret & 0xff) != VCNL4200_PROD_ID) | |
138 | return -ENODEV; | |
139 | ||
140 | data->rev = (ret >> 8) & 0xf; | |
141 | ||
142 | /* Set defaults and enable both channels */ | |
143 | ret = i2c_smbus_write_byte_data(data->client, VCNL4200_AL_CONF, 0x00); | |
144 | if (ret < 0) | |
145 | return ret; | |
146 | ret = i2c_smbus_write_byte_data(data->client, VCNL4200_PS_CONF1, 0x00); | |
147 | if (ret < 0) | |
148 | return ret; | |
149 | ||
150 | data->al_scale = 24000; | |
151 | data->vcnl4200_al.reg = VCNL4200_AL_DATA; | |
152 | data->vcnl4200_ps.reg = VCNL4200_PS_DATA; | |
153 | /* Integration time is 50ms, but the experiments show 54ms in total. */ | |
154 | data->vcnl4200_al.sampling_rate = ktime_set(0, 54000 * 1000); | |
155 | data->vcnl4200_ps.sampling_rate = ktime_set(0, 4200 * 1000); | |
156 | data->vcnl4200_al.last_measurement = ktime_set(0, 0); | |
157 | data->vcnl4200_ps.last_measurement = ktime_set(0, 0); | |
158 | mutex_init(&data->vcnl4200_al.lock); | |
159 | mutex_init(&data->vcnl4200_ps.lock); | |
1ebc787a TN |
160 | |
161 | return 0; | |
162 | }; | |
163 | ||
62a1efb9 PM |
164 | static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask, |
165 | u8 rdy_mask, u8 data_reg, int *val) | |
166 | { | |
167 | int tries = 20; | |
91f197e0 | 168 | __be16 buf; |
62a1efb9 PM |
169 | int ret; |
170 | ||
be38866f | 171 | mutex_lock(&data->vcnl4000_lock); |
ff34ed6d | 172 | |
62a1efb9 PM |
173 | ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, |
174 | req_mask); | |
175 | if (ret < 0) | |
ff34ed6d | 176 | goto fail; |
62a1efb9 PM |
177 | |
178 | /* wait for data to become ready */ | |
179 | while (tries--) { | |
180 | ret = i2c_smbus_read_byte_data(data->client, VCNL4000_COMMAND); | |
181 | if (ret < 0) | |
ff34ed6d | 182 | goto fail; |
62a1efb9 PM |
183 | if (ret & rdy_mask) |
184 | break; | |
185 | msleep(20); /* measurement takes up to 100 ms */ | |
186 | } | |
187 | ||
188 | if (tries < 0) { | |
189 | dev_err(&data->client->dev, | |
190 | "vcnl4000_measure() failed, data not ready\n"); | |
ff34ed6d PMS |
191 | ret = -EIO; |
192 | goto fail; | |
62a1efb9 PM |
193 | } |
194 | ||
195 | ret = i2c_smbus_read_i2c_block_data(data->client, | |
196 | data_reg, sizeof(buf), (u8 *) &buf); | |
197 | if (ret < 0) | |
ff34ed6d | 198 | goto fail; |
62a1efb9 | 199 | |
be38866f | 200 | mutex_unlock(&data->vcnl4000_lock); |
62a1efb9 PM |
201 | *val = be16_to_cpu(buf); |
202 | ||
203 | return 0; | |
ff34ed6d PMS |
204 | |
205 | fail: | |
be38866f | 206 | mutex_unlock(&data->vcnl4000_lock); |
ff34ed6d | 207 | return ret; |
62a1efb9 PM |
208 | } |
209 | ||
be38866f TN |
210 | static int vcnl4200_measure(struct vcnl4000_data *data, |
211 | struct vcnl4200_channel *chan, int *val) | |
212 | { | |
213 | int ret; | |
214 | s64 delta; | |
215 | ktime_t next_measurement; | |
216 | ||
217 | mutex_lock(&chan->lock); | |
218 | ||
219 | next_measurement = ktime_add(chan->last_measurement, | |
220 | chan->sampling_rate); | |
221 | delta = ktime_us_delta(next_measurement, ktime_get()); | |
222 | if (delta > 0) | |
223 | usleep_range(delta, delta + 500); | |
224 | chan->last_measurement = ktime_get(); | |
225 | ||
226 | mutex_unlock(&chan->lock); | |
227 | ||
228 | ret = i2c_smbus_read_word_data(data->client, chan->reg); | |
229 | if (ret < 0) | |
230 | return ret; | |
231 | ||
232 | *val = ret; | |
233 | ||
234 | return 0; | |
235 | } | |
236 | ||
1ebc787a TN |
237 | static int vcnl4000_measure_light(struct vcnl4000_data *data, int *val) |
238 | { | |
239 | return vcnl4000_measure(data, | |
240 | VCNL4000_AL_OD, VCNL4000_AL_RDY, | |
241 | VCNL4000_AL_RESULT_HI, val); | |
242 | } | |
243 | ||
be38866f TN |
244 | static int vcnl4200_measure_light(struct vcnl4000_data *data, int *val) |
245 | { | |
246 | return vcnl4200_measure(data, &data->vcnl4200_al, val); | |
247 | } | |
248 | ||
1ebc787a TN |
249 | static int vcnl4000_measure_proximity(struct vcnl4000_data *data, int *val) |
250 | { | |
251 | return vcnl4000_measure(data, | |
252 | VCNL4000_PS_OD, VCNL4000_PS_RDY, | |
253 | VCNL4000_PS_RESULT_HI, val); | |
254 | } | |
255 | ||
be38866f TN |
256 | static int vcnl4200_measure_proximity(struct vcnl4000_data *data, int *val) |
257 | { | |
258 | return vcnl4200_measure(data, &data->vcnl4200_ps, val); | |
259 | } | |
260 | ||
1ebc787a TN |
261 | static const struct vcnl4000_chip_spec vcnl4000_chip_spec_cfg[] = { |
262 | [VCNL4000] = { | |
263 | .prod = "VCNL4000", | |
264 | .init = vcnl4000_init, | |
265 | .measure_light = vcnl4000_measure_light, | |
266 | .measure_proximity = vcnl4000_measure_proximity, | |
267 | }, | |
50c50b97 TN |
268 | [VCNL4010] = { |
269 | .prod = "VCNL4010/4020", | |
270 | .init = vcnl4000_init, | |
271 | .measure_light = vcnl4000_measure_light, | |
272 | .measure_proximity = vcnl4000_measure_proximity, | |
273 | }, | |
be38866f TN |
274 | [VCNL4200] = { |
275 | .prod = "VCNL4200", | |
276 | .init = vcnl4200_init, | |
277 | .measure_light = vcnl4200_measure_light, | |
278 | .measure_proximity = vcnl4200_measure_proximity, | |
279 | }, | |
1ebc787a TN |
280 | }; |
281 | ||
62a1efb9 PM |
282 | static const struct iio_chan_spec vcnl4000_channels[] = { |
283 | { | |
284 | .type = IIO_LIGHT, | |
bb7c5940 JC |
285 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
286 | BIT(IIO_CHAN_INFO_SCALE), | |
62a1efb9 PM |
287 | }, { |
288 | .type = IIO_PROXIMITY, | |
bb7c5940 | 289 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), |
62a1efb9 PM |
290 | } |
291 | }; | |
292 | ||
293 | static int vcnl4000_read_raw(struct iio_dev *indio_dev, | |
294 | struct iio_chan_spec const *chan, | |
295 | int *val, int *val2, long mask) | |
296 | { | |
5d693139 | 297 | int ret; |
62a1efb9 PM |
298 | struct vcnl4000_data *data = iio_priv(indio_dev); |
299 | ||
300 | switch (mask) { | |
301 | case IIO_CHAN_INFO_RAW: | |
302 | switch (chan->type) { | |
303 | case IIO_LIGHT: | |
1ebc787a | 304 | ret = data->chip_spec->measure_light(data, val); |
62a1efb9 PM |
305 | if (ret < 0) |
306 | return ret; | |
5d693139 | 307 | return IIO_VAL_INT; |
62a1efb9 | 308 | case IIO_PROXIMITY: |
1ebc787a | 309 | ret = data->chip_spec->measure_proximity(data, val); |
62a1efb9 PM |
310 | if (ret < 0) |
311 | return ret; | |
5d693139 | 312 | return IIO_VAL_INT; |
62a1efb9 | 313 | default: |
5d693139 | 314 | return -EINVAL; |
62a1efb9 | 315 | } |
62a1efb9 | 316 | case IIO_CHAN_INFO_SCALE: |
5d693139 PMS |
317 | if (chan->type != IIO_LIGHT) |
318 | return -EINVAL; | |
319 | ||
320 | *val = 0; | |
1ebc787a | 321 | *val2 = data->al_scale; |
5d693139 | 322 | return IIO_VAL_INT_PLUS_MICRO; |
62a1efb9 | 323 | default: |
5d693139 | 324 | return -EINVAL; |
62a1efb9 | 325 | } |
62a1efb9 PM |
326 | } |
327 | ||
328 | static const struct iio_info vcnl4000_info = { | |
329 | .read_raw = vcnl4000_read_raw, | |
62a1efb9 PM |
330 | }; |
331 | ||
fc52692c GKH |
332 | static int vcnl4000_probe(struct i2c_client *client, |
333 | const struct i2c_device_id *id) | |
62a1efb9 PM |
334 | { |
335 | struct vcnl4000_data *data; | |
336 | struct iio_dev *indio_dev; | |
1ebc787a | 337 | int ret; |
62a1efb9 | 338 | |
2669d723 | 339 | indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); |
62a1efb9 PM |
340 | if (!indio_dev) |
341 | return -ENOMEM; | |
342 | ||
343 | data = iio_priv(indio_dev); | |
344 | i2c_set_clientdata(client, indio_dev); | |
345 | data->client = client; | |
1ebc787a TN |
346 | data->id = id->driver_data; |
347 | data->chip_spec = &vcnl4000_chip_spec_cfg[data->id]; | |
62a1efb9 | 348 | |
1ebc787a | 349 | ret = data->chip_spec->init(data); |
62a1efb9 | 350 | if (ret < 0) |
2669d723 | 351 | return ret; |
62a1efb9 | 352 | |
d978bfdd | 353 | dev_dbg(&client->dev, "%s Ambient light/proximity sensor, Rev: %02x\n", |
1ebc787a | 354 | data->chip_spec->prod, data->rev); |
62a1efb9 PM |
355 | |
356 | indio_dev->dev.parent = &client->dev; | |
357 | indio_dev->info = &vcnl4000_info; | |
358 | indio_dev->channels = vcnl4000_channels; | |
359 | indio_dev->num_channels = ARRAY_SIZE(vcnl4000_channels); | |
360 | indio_dev->name = VCNL4000_DRV_NAME; | |
361 | indio_dev->modes = INDIO_DIRECT_MODE; | |
362 | ||
691dab42 | 363 | return devm_iio_device_register(&client->dev, indio_dev); |
62a1efb9 PM |
364 | } |
365 | ||
366 | static struct i2c_driver vcnl4000_driver = { | |
367 | .driver = { | |
368 | .name = VCNL4000_DRV_NAME, | |
62a1efb9 PM |
369 | }, |
370 | .probe = vcnl4000_probe, | |
62a1efb9 PM |
371 | .id_table = vcnl4000_id, |
372 | }; | |
373 | ||
374 | module_i2c_driver(vcnl4000_driver); | |
375 | ||
376 | MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); | |
377 | MODULE_DESCRIPTION("Vishay VCNL4000 proximity/ambient light sensor driver"); | |
378 | MODULE_LICENSE("GPL"); |