.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | | - * vcnl4000.c - Support for Vishay VCNL4000/4010/4020/4200 combined ambient |
---|
| 3 | + * vcnl4000.c - Support for Vishay VCNL4000/4010/4020/4040/4200 combined ambient |
---|
3 | 4 | * light and proximity sensor |
---|
4 | 5 | * |
---|
5 | 6 | * 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. |
---|
| 7 | + * Copyright 2019 Pursim SPC |
---|
| 8 | + * Copyright 2020 Mathieu Othacehe <m.othacehe@gmail.com> |
---|
10 | 9 | * |
---|
11 | 10 | * IIO driver for: |
---|
12 | 11 | * VCNL4000/10/20 (7-bit I2C slave address 0x13) |
---|
| 12 | + * VCNL4040 (7-bit I2C slave address 0x60) |
---|
13 | 13 | * VCNL4200 (7-bit I2C slave address 0x51) |
---|
14 | 14 | * |
---|
15 | 15 | * TODO: |
---|
16 | 16 | * allow to adjust IR current |
---|
17 | | - * proximity threshold and event handling |
---|
18 | | - * periodic ALS/proximity measurement (VCNL4010/20) |
---|
19 | | - * interrupts (VCNL4010/20, VCNL4200) |
---|
| 17 | + * interrupts (VCNL4040, VCNL4200) |
---|
20 | 18 | */ |
---|
21 | 19 | |
---|
22 | 20 | #include <linux/module.h> |
---|
23 | 21 | #include <linux/i2c.h> |
---|
24 | 22 | #include <linux/err.h> |
---|
25 | 23 | #include <linux/delay.h> |
---|
| 24 | +#include <linux/pm_runtime.h> |
---|
| 25 | +#include <linux/interrupt.h> |
---|
26 | 26 | |
---|
| 27 | +#include <linux/iio/buffer.h> |
---|
| 28 | +#include <linux/iio/events.h> |
---|
27 | 29 | #include <linux/iio/iio.h> |
---|
28 | 30 | #include <linux/iio/sysfs.h> |
---|
| 31 | +#include <linux/iio/trigger.h> |
---|
| 32 | +#include <linux/iio/trigger_consumer.h> |
---|
| 33 | +#include <linux/iio/triggered_buffer.h> |
---|
29 | 34 | |
---|
30 | 35 | #define VCNL4000_DRV_NAME "vcnl4000" |
---|
31 | 36 | #define VCNL4000_PROD_ID 0x01 |
---|
32 | 37 | #define VCNL4010_PROD_ID 0x02 /* for VCNL4020, VCNL4010 */ |
---|
| 38 | +#define VCNL4040_PROD_ID 0x86 |
---|
33 | 39 | #define VCNL4200_PROD_ID 0x58 |
---|
34 | 40 | |
---|
35 | 41 | #define VCNL4000_COMMAND 0x80 /* Command register */ |
---|
36 | 42 | #define VCNL4000_PROD_REV 0x81 /* Product ID and Revision ID */ |
---|
| 43 | +#define VCNL4010_PROX_RATE 0x82 /* Proximity rate */ |
---|
37 | 44 | #define VCNL4000_LED_CURRENT 0x83 /* IR LED current for proximity mode */ |
---|
38 | 45 | #define VCNL4000_AL_PARAM 0x84 /* Ambient light parameter register */ |
---|
| 46 | +#define VCNL4010_ALS_PARAM 0x84 /* ALS rate */ |
---|
39 | 47 | #define VCNL4000_AL_RESULT_HI 0x85 /* Ambient light result register, MSB */ |
---|
40 | 48 | #define VCNL4000_AL_RESULT_LO 0x86 /* Ambient light result register, LSB */ |
---|
41 | 49 | #define VCNL4000_PS_RESULT_HI 0x87 /* Proximity result register, MSB */ |
---|
42 | 50 | #define VCNL4000_PS_RESULT_LO 0x88 /* Proximity result register, LSB */ |
---|
43 | 51 | #define VCNL4000_PS_MEAS_FREQ 0x89 /* Proximity test signal frequency */ |
---|
| 52 | +#define VCNL4010_INT_CTRL 0x89 /* Interrupt control */ |
---|
44 | 53 | #define VCNL4000_PS_MOD_ADJ 0x8a /* Proximity modulator timing adjustment */ |
---|
| 54 | +#define VCNL4010_LOW_THR_HI 0x8a /* Low threshold, MSB */ |
---|
| 55 | +#define VCNL4010_LOW_THR_LO 0x8b /* Low threshold, LSB */ |
---|
| 56 | +#define VCNL4010_HIGH_THR_HI 0x8c /* High threshold, MSB */ |
---|
| 57 | +#define VCNL4010_HIGH_THR_LO 0x8d /* High threshold, LSB */ |
---|
| 58 | +#define VCNL4010_ISR 0x8e /* Interrupt status */ |
---|
45 | 59 | |
---|
46 | 60 | #define VCNL4200_AL_CONF 0x00 /* Ambient light configuration */ |
---|
47 | 61 | #define VCNL4200_PS_CONF1 0x03 /* Proximity configuration */ |
---|
.. | .. |
---|
49 | 63 | #define VCNL4200_AL_DATA 0x09 /* Ambient light data */ |
---|
50 | 64 | #define VCNL4200_DEV_ID 0x0e /* Device ID, slave address and version */ |
---|
51 | 65 | |
---|
| 66 | +#define VCNL4040_DEV_ID 0x0c /* Device ID and version */ |
---|
| 67 | + |
---|
52 | 68 | /* Bit masks for COMMAND register */ |
---|
53 | 69 | #define VCNL4000_AL_RDY BIT(6) /* ALS data ready? */ |
---|
54 | 70 | #define VCNL4000_PS_RDY BIT(5) /* proximity data ready? */ |
---|
55 | 71 | #define VCNL4000_AL_OD BIT(4) /* start on-demand ALS measurement */ |
---|
56 | 72 | #define VCNL4000_PS_OD BIT(3) /* start on-demand proximity measurement */ |
---|
| 73 | +#define VCNL4000_ALS_EN BIT(2) /* start ALS measurement */ |
---|
| 74 | +#define VCNL4000_PROX_EN BIT(1) /* start proximity measurement */ |
---|
| 75 | +#define VCNL4000_SELF_TIMED_EN BIT(0) /* start self-timed measurement */ |
---|
| 76 | + |
---|
| 77 | +/* Bit masks for interrupt registers. */ |
---|
| 78 | +#define VCNL4010_INT_THR_SEL BIT(0) /* Select threshold interrupt source */ |
---|
| 79 | +#define VCNL4010_INT_THR_EN BIT(1) /* Threshold interrupt type */ |
---|
| 80 | +#define VCNL4010_INT_ALS_EN BIT(2) /* Enable on ALS data ready */ |
---|
| 81 | +#define VCNL4010_INT_PROX_EN BIT(3) /* Enable on proximity data ready */ |
---|
| 82 | + |
---|
| 83 | +#define VCNL4010_INT_THR_HIGH 0 /* High threshold exceeded */ |
---|
| 84 | +#define VCNL4010_INT_THR_LOW 1 /* Low threshold exceeded */ |
---|
| 85 | +#define VCNL4010_INT_ALS 2 /* ALS data ready */ |
---|
| 86 | +#define VCNL4010_INT_PROXIMITY 3 /* Proximity data ready */ |
---|
| 87 | + |
---|
| 88 | +#define VCNL4010_INT_THR \ |
---|
| 89 | + (BIT(VCNL4010_INT_THR_LOW) | BIT(VCNL4010_INT_THR_HIGH)) |
---|
| 90 | +#define VCNL4010_INT_DRDY \ |
---|
| 91 | + (BIT(VCNL4010_INT_PROXIMITY) | BIT(VCNL4010_INT_ALS)) |
---|
| 92 | + |
---|
| 93 | +static const int vcnl4010_prox_sampling_frequency[][2] = { |
---|
| 94 | + {1, 950000}, |
---|
| 95 | + {3, 906250}, |
---|
| 96 | + {7, 812500}, |
---|
| 97 | + {16, 625000}, |
---|
| 98 | + {31, 250000}, |
---|
| 99 | + {62, 500000}, |
---|
| 100 | + {125, 0}, |
---|
| 101 | + {250, 0}, |
---|
| 102 | +}; |
---|
| 103 | + |
---|
| 104 | +#define VCNL4000_SLEEP_DELAY_MS 2000 /* before we enter pm_runtime_suspend */ |
---|
57 | 105 | |
---|
58 | 106 | enum vcnl4000_device_ids { |
---|
59 | 107 | VCNL4000, |
---|
60 | 108 | VCNL4010, |
---|
| 109 | + VCNL4040, |
---|
61 | 110 | VCNL4200, |
---|
62 | 111 | }; |
---|
63 | 112 | |
---|
.. | .. |
---|
77 | 126 | struct mutex vcnl4000_lock; |
---|
78 | 127 | struct vcnl4200_channel vcnl4200_al; |
---|
79 | 128 | struct vcnl4200_channel vcnl4200_ps; |
---|
| 129 | + uint32_t near_level; |
---|
80 | 130 | }; |
---|
81 | 131 | |
---|
82 | 132 | struct vcnl4000_chip_spec { |
---|
83 | 133 | const char *prod; |
---|
| 134 | + struct iio_chan_spec const *channels; |
---|
| 135 | + const int num_channels; |
---|
| 136 | + const struct iio_info *info; |
---|
| 137 | + bool irq_support; |
---|
84 | 138 | int (*init)(struct vcnl4000_data *data); |
---|
85 | 139 | int (*measure_light)(struct vcnl4000_data *data, int *val); |
---|
86 | 140 | int (*measure_proximity)(struct vcnl4000_data *data, int *val); |
---|
| 141 | + int (*set_power_state)(struct vcnl4000_data *data, bool on); |
---|
87 | 142 | }; |
---|
88 | 143 | |
---|
89 | 144 | static const struct i2c_device_id vcnl4000_id[] = { |
---|
90 | 145 | { "vcnl4000", VCNL4000 }, |
---|
91 | 146 | { "vcnl4010", VCNL4010 }, |
---|
92 | 147 | { "vcnl4020", VCNL4010 }, |
---|
| 148 | + { "vcnl4040", VCNL4040 }, |
---|
93 | 149 | { "vcnl4200", VCNL4200 }, |
---|
94 | 150 | { } |
---|
95 | 151 | }; |
---|
96 | 152 | MODULE_DEVICE_TABLE(i2c, vcnl4000_id); |
---|
| 153 | + |
---|
| 154 | +static int vcnl4000_set_power_state(struct vcnl4000_data *data, bool on) |
---|
| 155 | +{ |
---|
| 156 | + /* no suspend op */ |
---|
| 157 | + return 0; |
---|
| 158 | +} |
---|
97 | 159 | |
---|
98 | 160 | static int vcnl4000_init(struct vcnl4000_data *data) |
---|
99 | 161 | { |
---|
.. | .. |
---|
123 | 185 | data->al_scale = 250000; |
---|
124 | 186 | mutex_init(&data->vcnl4000_lock); |
---|
125 | 187 | |
---|
126 | | - return 0; |
---|
| 188 | + return data->chip_spec->set_power_state(data, true); |
---|
127 | 189 | }; |
---|
| 190 | + |
---|
| 191 | +static int vcnl4200_set_power_state(struct vcnl4000_data *data, bool on) |
---|
| 192 | +{ |
---|
| 193 | + u16 val = on ? 0 /* power on */ : 1 /* shut down */; |
---|
| 194 | + int ret; |
---|
| 195 | + |
---|
| 196 | + ret = i2c_smbus_write_word_data(data->client, VCNL4200_AL_CONF, val); |
---|
| 197 | + if (ret < 0) |
---|
| 198 | + return ret; |
---|
| 199 | + |
---|
| 200 | + ret = i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF1, val); |
---|
| 201 | + if (ret < 0) |
---|
| 202 | + return ret; |
---|
| 203 | + |
---|
| 204 | + if (on) { |
---|
| 205 | + /* Wait at least one integration cycle before fetching data */ |
---|
| 206 | + data->vcnl4200_al.last_measurement = ktime_get(); |
---|
| 207 | + data->vcnl4200_ps.last_measurement = ktime_get(); |
---|
| 208 | + } |
---|
| 209 | + |
---|
| 210 | + return 0; |
---|
| 211 | +} |
---|
128 | 212 | |
---|
129 | 213 | static int vcnl4200_init(struct vcnl4000_data *data) |
---|
130 | 214 | { |
---|
131 | | - int ret; |
---|
| 215 | + int ret, id; |
---|
132 | 216 | |
---|
133 | 217 | ret = i2c_smbus_read_word_data(data->client, VCNL4200_DEV_ID); |
---|
134 | 218 | if (ret < 0) |
---|
135 | 219 | return ret; |
---|
136 | 220 | |
---|
137 | | - if ((ret & 0xff) != VCNL4200_PROD_ID) |
---|
138 | | - return -ENODEV; |
---|
| 221 | + id = ret & 0xff; |
---|
| 222 | + |
---|
| 223 | + if (id != VCNL4200_PROD_ID) { |
---|
| 224 | + ret = i2c_smbus_read_word_data(data->client, VCNL4040_DEV_ID); |
---|
| 225 | + if (ret < 0) |
---|
| 226 | + return ret; |
---|
| 227 | + |
---|
| 228 | + id = ret & 0xff; |
---|
| 229 | + |
---|
| 230 | + if (id != VCNL4040_PROD_ID) |
---|
| 231 | + return -ENODEV; |
---|
| 232 | + } |
---|
| 233 | + |
---|
| 234 | + dev_dbg(&data->client->dev, "device id 0x%x", id); |
---|
139 | 235 | |
---|
140 | 236 | data->rev = (ret >> 8) & 0xf; |
---|
141 | 237 | |
---|
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 | 238 | data->vcnl4200_al.reg = VCNL4200_AL_DATA; |
---|
152 | 239 | data->vcnl4200_ps.reg = VCNL4200_PS_DATA; |
---|
153 | | - /* Default wait time is 50ms, add 20% tolerance. */ |
---|
154 | | - data->vcnl4200_al.sampling_rate = ktime_set(0, 60000 * 1000); |
---|
155 | | - /* Default wait time is 4.8ms, add 20% tolerance. */ |
---|
156 | | - data->vcnl4200_ps.sampling_rate = ktime_set(0, 5760 * 1000); |
---|
157 | | - data->vcnl4200_al.last_measurement = ktime_set(0, 0); |
---|
158 | | - data->vcnl4200_ps.last_measurement = ktime_set(0, 0); |
---|
| 240 | + switch (id) { |
---|
| 241 | + case VCNL4200_PROD_ID: |
---|
| 242 | + /* Default wait time is 50ms, add 20% tolerance. */ |
---|
| 243 | + data->vcnl4200_al.sampling_rate = ktime_set(0, 60000 * 1000); |
---|
| 244 | + /* Default wait time is 4.8ms, add 20% tolerance. */ |
---|
| 245 | + data->vcnl4200_ps.sampling_rate = ktime_set(0, 5760 * 1000); |
---|
| 246 | + data->al_scale = 24000; |
---|
| 247 | + break; |
---|
| 248 | + case VCNL4040_PROD_ID: |
---|
| 249 | + /* Default wait time is 80ms, add 20% tolerance. */ |
---|
| 250 | + data->vcnl4200_al.sampling_rate = ktime_set(0, 96000 * 1000); |
---|
| 251 | + /* Default wait time is 5ms, add 20% tolerance. */ |
---|
| 252 | + data->vcnl4200_ps.sampling_rate = ktime_set(0, 6000 * 1000); |
---|
| 253 | + data->al_scale = 120000; |
---|
| 254 | + break; |
---|
| 255 | + } |
---|
159 | 256 | mutex_init(&data->vcnl4200_al.lock); |
---|
160 | 257 | mutex_init(&data->vcnl4200_ps.lock); |
---|
161 | 258 | |
---|
| 259 | + ret = data->chip_spec->set_power_state(data, true); |
---|
| 260 | + if (ret < 0) |
---|
| 261 | + return ret; |
---|
| 262 | + |
---|
162 | 263 | return 0; |
---|
163 | 264 | }; |
---|
| 265 | + |
---|
| 266 | +static int vcnl4000_read_data(struct vcnl4000_data *data, u8 data_reg, int *val) |
---|
| 267 | +{ |
---|
| 268 | + s32 ret; |
---|
| 269 | + |
---|
| 270 | + ret = i2c_smbus_read_word_swapped(data->client, data_reg); |
---|
| 271 | + if (ret < 0) |
---|
| 272 | + return ret; |
---|
| 273 | + |
---|
| 274 | + *val = ret; |
---|
| 275 | + return 0; |
---|
| 276 | +} |
---|
| 277 | + |
---|
| 278 | +static int vcnl4000_write_data(struct vcnl4000_data *data, u8 data_reg, int val) |
---|
| 279 | +{ |
---|
| 280 | + if (val > U16_MAX) |
---|
| 281 | + return -ERANGE; |
---|
| 282 | + |
---|
| 283 | + return i2c_smbus_write_word_swapped(data->client, data_reg, val); |
---|
| 284 | +} |
---|
| 285 | + |
---|
164 | 286 | |
---|
165 | 287 | static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask, |
---|
166 | 288 | u8 rdy_mask, u8 data_reg, int *val) |
---|
.. | .. |
---|
192 | 314 | goto fail; |
---|
193 | 315 | } |
---|
194 | 316 | |
---|
195 | | - ret = i2c_smbus_read_word_swapped(data->client, data_reg); |
---|
| 317 | + ret = vcnl4000_read_data(data, data_reg, val); |
---|
196 | 318 | if (ret < 0) |
---|
197 | 319 | goto fail; |
---|
198 | 320 | |
---|
199 | 321 | mutex_unlock(&data->vcnl4000_lock); |
---|
200 | | - *val = ret; |
---|
201 | 322 | |
---|
202 | 323 | return 0; |
---|
203 | 324 | |
---|
.. | .. |
---|
257 | 378 | return vcnl4200_measure(data, &data->vcnl4200_ps, val); |
---|
258 | 379 | } |
---|
259 | 380 | |
---|
260 | | -static const struct vcnl4000_chip_spec vcnl4000_chip_spec_cfg[] = { |
---|
261 | | - [VCNL4000] = { |
---|
262 | | - .prod = "VCNL4000", |
---|
263 | | - .init = vcnl4000_init, |
---|
264 | | - .measure_light = vcnl4000_measure_light, |
---|
265 | | - .measure_proximity = vcnl4000_measure_proximity, |
---|
266 | | - }, |
---|
267 | | - [VCNL4010] = { |
---|
268 | | - .prod = "VCNL4010/4020", |
---|
269 | | - .init = vcnl4000_init, |
---|
270 | | - .measure_light = vcnl4000_measure_light, |
---|
271 | | - .measure_proximity = vcnl4000_measure_proximity, |
---|
272 | | - }, |
---|
273 | | - [VCNL4200] = { |
---|
274 | | - .prod = "VCNL4200", |
---|
275 | | - .init = vcnl4200_init, |
---|
276 | | - .measure_light = vcnl4200_measure_light, |
---|
277 | | - .measure_proximity = vcnl4200_measure_proximity, |
---|
278 | | - }, |
---|
279 | | -}; |
---|
| 381 | +static int vcnl4010_read_proxy_samp_freq(struct vcnl4000_data *data, int *val, |
---|
| 382 | + int *val2) |
---|
| 383 | +{ |
---|
| 384 | + int ret; |
---|
280 | 385 | |
---|
281 | | -static const struct iio_chan_spec vcnl4000_channels[] = { |
---|
282 | | - { |
---|
283 | | - .type = IIO_LIGHT, |
---|
284 | | - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
---|
285 | | - BIT(IIO_CHAN_INFO_SCALE), |
---|
286 | | - }, { |
---|
287 | | - .type = IIO_PROXIMITY, |
---|
288 | | - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), |
---|
| 386 | + ret = i2c_smbus_read_byte_data(data->client, VCNL4010_PROX_RATE); |
---|
| 387 | + if (ret < 0) |
---|
| 388 | + return ret; |
---|
| 389 | + |
---|
| 390 | + if (ret >= ARRAY_SIZE(vcnl4010_prox_sampling_frequency)) |
---|
| 391 | + return -EINVAL; |
---|
| 392 | + |
---|
| 393 | + *val = vcnl4010_prox_sampling_frequency[ret][0]; |
---|
| 394 | + *val2 = vcnl4010_prox_sampling_frequency[ret][1]; |
---|
| 395 | + |
---|
| 396 | + return 0; |
---|
| 397 | +} |
---|
| 398 | + |
---|
| 399 | +static bool vcnl4010_is_in_periodic_mode(struct vcnl4000_data *data) |
---|
| 400 | +{ |
---|
| 401 | + int ret; |
---|
| 402 | + |
---|
| 403 | + ret = i2c_smbus_read_byte_data(data->client, VCNL4000_COMMAND); |
---|
| 404 | + if (ret < 0) |
---|
| 405 | + return false; |
---|
| 406 | + |
---|
| 407 | + return !!(ret & VCNL4000_SELF_TIMED_EN); |
---|
| 408 | +} |
---|
| 409 | + |
---|
| 410 | +static int vcnl4000_set_pm_runtime_state(struct vcnl4000_data *data, bool on) |
---|
| 411 | +{ |
---|
| 412 | + struct device *dev = &data->client->dev; |
---|
| 413 | + int ret; |
---|
| 414 | + |
---|
| 415 | + if (on) { |
---|
| 416 | + ret = pm_runtime_get_sync(dev); |
---|
| 417 | + if (ret < 0) |
---|
| 418 | + pm_runtime_put_noidle(dev); |
---|
| 419 | + } else { |
---|
| 420 | + pm_runtime_mark_last_busy(dev); |
---|
| 421 | + ret = pm_runtime_put_autosuspend(dev); |
---|
289 | 422 | } |
---|
290 | | -}; |
---|
| 423 | + |
---|
| 424 | + return ret; |
---|
| 425 | +} |
---|
291 | 426 | |
---|
292 | 427 | static int vcnl4000_read_raw(struct iio_dev *indio_dev, |
---|
293 | 428 | struct iio_chan_spec const *chan, |
---|
.. | .. |
---|
298 | 433 | |
---|
299 | 434 | switch (mask) { |
---|
300 | 435 | case IIO_CHAN_INFO_RAW: |
---|
| 436 | + ret = vcnl4000_set_pm_runtime_state(data, true); |
---|
| 437 | + if (ret < 0) |
---|
| 438 | + return ret; |
---|
| 439 | + |
---|
301 | 440 | switch (chan->type) { |
---|
302 | 441 | case IIO_LIGHT: |
---|
303 | 442 | ret = data->chip_spec->measure_light(data, val); |
---|
304 | | - if (ret < 0) |
---|
305 | | - return ret; |
---|
306 | | - return IIO_VAL_INT; |
---|
| 443 | + if (!ret) |
---|
| 444 | + ret = IIO_VAL_INT; |
---|
| 445 | + break; |
---|
307 | 446 | case IIO_PROXIMITY: |
---|
308 | 447 | ret = data->chip_spec->measure_proximity(data, val); |
---|
309 | | - if (ret < 0) |
---|
310 | | - return ret; |
---|
311 | | - return IIO_VAL_INT; |
---|
| 448 | + if (!ret) |
---|
| 449 | + ret = IIO_VAL_INT; |
---|
| 450 | + break; |
---|
312 | 451 | default: |
---|
313 | | - return -EINVAL; |
---|
| 452 | + ret = -EINVAL; |
---|
314 | 453 | } |
---|
| 454 | + vcnl4000_set_pm_runtime_state(data, false); |
---|
| 455 | + return ret; |
---|
315 | 456 | case IIO_CHAN_INFO_SCALE: |
---|
316 | 457 | if (chan->type != IIO_LIGHT) |
---|
317 | 458 | return -EINVAL; |
---|
.. | .. |
---|
324 | 465 | } |
---|
325 | 466 | } |
---|
326 | 467 | |
---|
| 468 | +static int vcnl4010_read_raw(struct iio_dev *indio_dev, |
---|
| 469 | + struct iio_chan_spec const *chan, |
---|
| 470 | + int *val, int *val2, long mask) |
---|
| 471 | +{ |
---|
| 472 | + int ret; |
---|
| 473 | + struct vcnl4000_data *data = iio_priv(indio_dev); |
---|
| 474 | + |
---|
| 475 | + switch (mask) { |
---|
| 476 | + case IIO_CHAN_INFO_RAW: |
---|
| 477 | + case IIO_CHAN_INFO_SCALE: |
---|
| 478 | + ret = iio_device_claim_direct_mode(indio_dev); |
---|
| 479 | + if (ret) |
---|
| 480 | + return ret; |
---|
| 481 | + |
---|
| 482 | + /* Protect against event capture. */ |
---|
| 483 | + if (vcnl4010_is_in_periodic_mode(data)) { |
---|
| 484 | + ret = -EBUSY; |
---|
| 485 | + } else { |
---|
| 486 | + ret = vcnl4000_read_raw(indio_dev, chan, val, val2, |
---|
| 487 | + mask); |
---|
| 488 | + } |
---|
| 489 | + |
---|
| 490 | + iio_device_release_direct_mode(indio_dev); |
---|
| 491 | + return ret; |
---|
| 492 | + case IIO_CHAN_INFO_SAMP_FREQ: |
---|
| 493 | + switch (chan->type) { |
---|
| 494 | + case IIO_PROXIMITY: |
---|
| 495 | + ret = vcnl4010_read_proxy_samp_freq(data, val, val2); |
---|
| 496 | + if (ret < 0) |
---|
| 497 | + return ret; |
---|
| 498 | + return IIO_VAL_INT_PLUS_MICRO; |
---|
| 499 | + default: |
---|
| 500 | + return -EINVAL; |
---|
| 501 | + } |
---|
| 502 | + default: |
---|
| 503 | + return -EINVAL; |
---|
| 504 | + } |
---|
| 505 | +} |
---|
| 506 | + |
---|
| 507 | +static int vcnl4010_read_avail(struct iio_dev *indio_dev, |
---|
| 508 | + struct iio_chan_spec const *chan, |
---|
| 509 | + const int **vals, int *type, int *length, |
---|
| 510 | + long mask) |
---|
| 511 | +{ |
---|
| 512 | + switch (mask) { |
---|
| 513 | + case IIO_CHAN_INFO_SAMP_FREQ: |
---|
| 514 | + *vals = (int *)vcnl4010_prox_sampling_frequency; |
---|
| 515 | + *type = IIO_VAL_INT_PLUS_MICRO; |
---|
| 516 | + *length = 2 * ARRAY_SIZE(vcnl4010_prox_sampling_frequency); |
---|
| 517 | + return IIO_AVAIL_LIST; |
---|
| 518 | + default: |
---|
| 519 | + return -EINVAL; |
---|
| 520 | + } |
---|
| 521 | +} |
---|
| 522 | + |
---|
| 523 | +static int vcnl4010_write_proxy_samp_freq(struct vcnl4000_data *data, int val, |
---|
| 524 | + int val2) |
---|
| 525 | +{ |
---|
| 526 | + unsigned int i; |
---|
| 527 | + int index = -1; |
---|
| 528 | + |
---|
| 529 | + for (i = 0; i < ARRAY_SIZE(vcnl4010_prox_sampling_frequency); i++) { |
---|
| 530 | + if (val == vcnl4010_prox_sampling_frequency[i][0] && |
---|
| 531 | + val2 == vcnl4010_prox_sampling_frequency[i][1]) { |
---|
| 532 | + index = i; |
---|
| 533 | + break; |
---|
| 534 | + } |
---|
| 535 | + } |
---|
| 536 | + |
---|
| 537 | + if (index < 0) |
---|
| 538 | + return -EINVAL; |
---|
| 539 | + |
---|
| 540 | + return i2c_smbus_write_byte_data(data->client, VCNL4010_PROX_RATE, |
---|
| 541 | + index); |
---|
| 542 | +} |
---|
| 543 | + |
---|
| 544 | +static int vcnl4010_write_raw(struct iio_dev *indio_dev, |
---|
| 545 | + struct iio_chan_spec const *chan, |
---|
| 546 | + int val, int val2, long mask) |
---|
| 547 | +{ |
---|
| 548 | + int ret; |
---|
| 549 | + struct vcnl4000_data *data = iio_priv(indio_dev); |
---|
| 550 | + |
---|
| 551 | + ret = iio_device_claim_direct_mode(indio_dev); |
---|
| 552 | + if (ret) |
---|
| 553 | + return ret; |
---|
| 554 | + |
---|
| 555 | + /* Protect against event capture. */ |
---|
| 556 | + if (vcnl4010_is_in_periodic_mode(data)) { |
---|
| 557 | + ret = -EBUSY; |
---|
| 558 | + goto end; |
---|
| 559 | + } |
---|
| 560 | + |
---|
| 561 | + switch (mask) { |
---|
| 562 | + case IIO_CHAN_INFO_SAMP_FREQ: |
---|
| 563 | + switch (chan->type) { |
---|
| 564 | + case IIO_PROXIMITY: |
---|
| 565 | + ret = vcnl4010_write_proxy_samp_freq(data, val, val2); |
---|
| 566 | + goto end; |
---|
| 567 | + default: |
---|
| 568 | + ret = -EINVAL; |
---|
| 569 | + goto end; |
---|
| 570 | + } |
---|
| 571 | + default: |
---|
| 572 | + ret = -EINVAL; |
---|
| 573 | + goto end; |
---|
| 574 | + } |
---|
| 575 | + |
---|
| 576 | +end: |
---|
| 577 | + iio_device_release_direct_mode(indio_dev); |
---|
| 578 | + return ret; |
---|
| 579 | +} |
---|
| 580 | + |
---|
| 581 | +static int vcnl4010_read_event(struct iio_dev *indio_dev, |
---|
| 582 | + const struct iio_chan_spec *chan, |
---|
| 583 | + enum iio_event_type type, |
---|
| 584 | + enum iio_event_direction dir, |
---|
| 585 | + enum iio_event_info info, |
---|
| 586 | + int *val, int *val2) |
---|
| 587 | +{ |
---|
| 588 | + int ret; |
---|
| 589 | + struct vcnl4000_data *data = iio_priv(indio_dev); |
---|
| 590 | + |
---|
| 591 | + switch (info) { |
---|
| 592 | + case IIO_EV_INFO_VALUE: |
---|
| 593 | + switch (dir) { |
---|
| 594 | + case IIO_EV_DIR_RISING: |
---|
| 595 | + ret = vcnl4000_read_data(data, VCNL4010_HIGH_THR_HI, |
---|
| 596 | + val); |
---|
| 597 | + if (ret < 0) |
---|
| 598 | + return ret; |
---|
| 599 | + return IIO_VAL_INT; |
---|
| 600 | + case IIO_EV_DIR_FALLING: |
---|
| 601 | + ret = vcnl4000_read_data(data, VCNL4010_LOW_THR_HI, |
---|
| 602 | + val); |
---|
| 603 | + if (ret < 0) |
---|
| 604 | + return ret; |
---|
| 605 | + return IIO_VAL_INT; |
---|
| 606 | + default: |
---|
| 607 | + return -EINVAL; |
---|
| 608 | + } |
---|
| 609 | + default: |
---|
| 610 | + return -EINVAL; |
---|
| 611 | + } |
---|
| 612 | +} |
---|
| 613 | + |
---|
| 614 | +static int vcnl4010_write_event(struct iio_dev *indio_dev, |
---|
| 615 | + const struct iio_chan_spec *chan, |
---|
| 616 | + enum iio_event_type type, |
---|
| 617 | + enum iio_event_direction dir, |
---|
| 618 | + enum iio_event_info info, |
---|
| 619 | + int val, int val2) |
---|
| 620 | +{ |
---|
| 621 | + int ret; |
---|
| 622 | + struct vcnl4000_data *data = iio_priv(indio_dev); |
---|
| 623 | + |
---|
| 624 | + switch (info) { |
---|
| 625 | + case IIO_EV_INFO_VALUE: |
---|
| 626 | + switch (dir) { |
---|
| 627 | + case IIO_EV_DIR_RISING: |
---|
| 628 | + ret = vcnl4000_write_data(data, VCNL4010_HIGH_THR_HI, |
---|
| 629 | + val); |
---|
| 630 | + if (ret < 0) |
---|
| 631 | + return ret; |
---|
| 632 | + return IIO_VAL_INT; |
---|
| 633 | + case IIO_EV_DIR_FALLING: |
---|
| 634 | + ret = vcnl4000_write_data(data, VCNL4010_LOW_THR_HI, |
---|
| 635 | + val); |
---|
| 636 | + if (ret < 0) |
---|
| 637 | + return ret; |
---|
| 638 | + return IIO_VAL_INT; |
---|
| 639 | + default: |
---|
| 640 | + return -EINVAL; |
---|
| 641 | + } |
---|
| 642 | + default: |
---|
| 643 | + return -EINVAL; |
---|
| 644 | + } |
---|
| 645 | +} |
---|
| 646 | + |
---|
| 647 | +static bool vcnl4010_is_thr_enabled(struct vcnl4000_data *data) |
---|
| 648 | +{ |
---|
| 649 | + int ret; |
---|
| 650 | + |
---|
| 651 | + ret = i2c_smbus_read_byte_data(data->client, VCNL4010_INT_CTRL); |
---|
| 652 | + if (ret < 0) |
---|
| 653 | + return false; |
---|
| 654 | + |
---|
| 655 | + return !!(ret & VCNL4010_INT_THR_EN); |
---|
| 656 | +} |
---|
| 657 | + |
---|
| 658 | +static int vcnl4010_read_event_config(struct iio_dev *indio_dev, |
---|
| 659 | + const struct iio_chan_spec *chan, |
---|
| 660 | + enum iio_event_type type, |
---|
| 661 | + enum iio_event_direction dir) |
---|
| 662 | +{ |
---|
| 663 | + struct vcnl4000_data *data = iio_priv(indio_dev); |
---|
| 664 | + |
---|
| 665 | + switch (chan->type) { |
---|
| 666 | + case IIO_PROXIMITY: |
---|
| 667 | + return vcnl4010_is_thr_enabled(data); |
---|
| 668 | + default: |
---|
| 669 | + return -EINVAL; |
---|
| 670 | + } |
---|
| 671 | +} |
---|
| 672 | + |
---|
| 673 | +static int vcnl4010_config_threshold(struct iio_dev *indio_dev, bool state) |
---|
| 674 | +{ |
---|
| 675 | + struct vcnl4000_data *data = iio_priv(indio_dev); |
---|
| 676 | + int ret; |
---|
| 677 | + int icr; |
---|
| 678 | + int command; |
---|
| 679 | + |
---|
| 680 | + if (state) { |
---|
| 681 | + ret = iio_device_claim_direct_mode(indio_dev); |
---|
| 682 | + if (ret) |
---|
| 683 | + return ret; |
---|
| 684 | + |
---|
| 685 | + /* Enable periodic measurement of proximity data. */ |
---|
| 686 | + command = VCNL4000_SELF_TIMED_EN | VCNL4000_PROX_EN; |
---|
| 687 | + |
---|
| 688 | + /* |
---|
| 689 | + * Enable interrupts on threshold, for proximity data by |
---|
| 690 | + * default. |
---|
| 691 | + */ |
---|
| 692 | + icr = VCNL4010_INT_THR_EN; |
---|
| 693 | + } else { |
---|
| 694 | + if (!vcnl4010_is_thr_enabled(data)) |
---|
| 695 | + return 0; |
---|
| 696 | + |
---|
| 697 | + command = 0; |
---|
| 698 | + icr = 0; |
---|
| 699 | + } |
---|
| 700 | + |
---|
| 701 | + ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, |
---|
| 702 | + command); |
---|
| 703 | + if (ret < 0) |
---|
| 704 | + goto end; |
---|
| 705 | + |
---|
| 706 | + ret = i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL, icr); |
---|
| 707 | + |
---|
| 708 | +end: |
---|
| 709 | + if (state) |
---|
| 710 | + iio_device_release_direct_mode(indio_dev); |
---|
| 711 | + |
---|
| 712 | + return ret; |
---|
| 713 | +} |
---|
| 714 | + |
---|
| 715 | +static int vcnl4010_write_event_config(struct iio_dev *indio_dev, |
---|
| 716 | + const struct iio_chan_spec *chan, |
---|
| 717 | + enum iio_event_type type, |
---|
| 718 | + enum iio_event_direction dir, |
---|
| 719 | + int state) |
---|
| 720 | +{ |
---|
| 721 | + switch (chan->type) { |
---|
| 722 | + case IIO_PROXIMITY: |
---|
| 723 | + return vcnl4010_config_threshold(indio_dev, state); |
---|
| 724 | + default: |
---|
| 725 | + return -EINVAL; |
---|
| 726 | + } |
---|
| 727 | +} |
---|
| 728 | + |
---|
| 729 | +static ssize_t vcnl4000_read_near_level(struct iio_dev *indio_dev, |
---|
| 730 | + uintptr_t priv, |
---|
| 731 | + const struct iio_chan_spec *chan, |
---|
| 732 | + char *buf) |
---|
| 733 | +{ |
---|
| 734 | + struct vcnl4000_data *data = iio_priv(indio_dev); |
---|
| 735 | + |
---|
| 736 | + return sprintf(buf, "%u\n", data->near_level); |
---|
| 737 | +} |
---|
| 738 | + |
---|
| 739 | +static const struct iio_chan_spec_ext_info vcnl4000_ext_info[] = { |
---|
| 740 | + { |
---|
| 741 | + .name = "nearlevel", |
---|
| 742 | + .shared = IIO_SEPARATE, |
---|
| 743 | + .read = vcnl4000_read_near_level, |
---|
| 744 | + }, |
---|
| 745 | + { /* sentinel */ } |
---|
| 746 | +}; |
---|
| 747 | + |
---|
| 748 | +static const struct iio_event_spec vcnl4000_event_spec[] = { |
---|
| 749 | + { |
---|
| 750 | + .type = IIO_EV_TYPE_THRESH, |
---|
| 751 | + .dir = IIO_EV_DIR_RISING, |
---|
| 752 | + .mask_separate = BIT(IIO_EV_INFO_VALUE), |
---|
| 753 | + }, { |
---|
| 754 | + .type = IIO_EV_TYPE_THRESH, |
---|
| 755 | + .dir = IIO_EV_DIR_FALLING, |
---|
| 756 | + .mask_separate = BIT(IIO_EV_INFO_VALUE), |
---|
| 757 | + }, { |
---|
| 758 | + .type = IIO_EV_TYPE_THRESH, |
---|
| 759 | + .dir = IIO_EV_DIR_EITHER, |
---|
| 760 | + .mask_separate = BIT(IIO_EV_INFO_ENABLE), |
---|
| 761 | + } |
---|
| 762 | +}; |
---|
| 763 | + |
---|
| 764 | +static const struct iio_chan_spec vcnl4000_channels[] = { |
---|
| 765 | + { |
---|
| 766 | + .type = IIO_LIGHT, |
---|
| 767 | + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
---|
| 768 | + BIT(IIO_CHAN_INFO_SCALE), |
---|
| 769 | + }, { |
---|
| 770 | + .type = IIO_PROXIMITY, |
---|
| 771 | + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), |
---|
| 772 | + .ext_info = vcnl4000_ext_info, |
---|
| 773 | + } |
---|
| 774 | +}; |
---|
| 775 | + |
---|
| 776 | +static const struct iio_chan_spec vcnl4010_channels[] = { |
---|
| 777 | + { |
---|
| 778 | + .type = IIO_LIGHT, |
---|
| 779 | + .scan_index = -1, |
---|
| 780 | + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
---|
| 781 | + BIT(IIO_CHAN_INFO_SCALE), |
---|
| 782 | + }, { |
---|
| 783 | + .type = IIO_PROXIMITY, |
---|
| 784 | + .scan_index = 0, |
---|
| 785 | + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
---|
| 786 | + BIT(IIO_CHAN_INFO_SAMP_FREQ), |
---|
| 787 | + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), |
---|
| 788 | + .event_spec = vcnl4000_event_spec, |
---|
| 789 | + .num_event_specs = ARRAY_SIZE(vcnl4000_event_spec), |
---|
| 790 | + .ext_info = vcnl4000_ext_info, |
---|
| 791 | + .scan_type = { |
---|
| 792 | + .sign = 'u', |
---|
| 793 | + .realbits = 16, |
---|
| 794 | + .storagebits = 16, |
---|
| 795 | + .endianness = IIO_CPU, |
---|
| 796 | + }, |
---|
| 797 | + }, |
---|
| 798 | + IIO_CHAN_SOFT_TIMESTAMP(1), |
---|
| 799 | +}; |
---|
| 800 | + |
---|
327 | 801 | static const struct iio_info vcnl4000_info = { |
---|
328 | 802 | .read_raw = vcnl4000_read_raw, |
---|
329 | 803 | }; |
---|
| 804 | + |
---|
| 805 | +static const struct iio_info vcnl4010_info = { |
---|
| 806 | + .read_raw = vcnl4010_read_raw, |
---|
| 807 | + .read_avail = vcnl4010_read_avail, |
---|
| 808 | + .write_raw = vcnl4010_write_raw, |
---|
| 809 | + .read_event_value = vcnl4010_read_event, |
---|
| 810 | + .write_event_value = vcnl4010_write_event, |
---|
| 811 | + .read_event_config = vcnl4010_read_event_config, |
---|
| 812 | + .write_event_config = vcnl4010_write_event_config, |
---|
| 813 | +}; |
---|
| 814 | + |
---|
| 815 | +static const struct vcnl4000_chip_spec vcnl4000_chip_spec_cfg[] = { |
---|
| 816 | + [VCNL4000] = { |
---|
| 817 | + .prod = "VCNL4000", |
---|
| 818 | + .init = vcnl4000_init, |
---|
| 819 | + .measure_light = vcnl4000_measure_light, |
---|
| 820 | + .measure_proximity = vcnl4000_measure_proximity, |
---|
| 821 | + .set_power_state = vcnl4000_set_power_state, |
---|
| 822 | + .channels = vcnl4000_channels, |
---|
| 823 | + .num_channels = ARRAY_SIZE(vcnl4000_channels), |
---|
| 824 | + .info = &vcnl4000_info, |
---|
| 825 | + .irq_support = false, |
---|
| 826 | + }, |
---|
| 827 | + [VCNL4010] = { |
---|
| 828 | + .prod = "VCNL4010/4020", |
---|
| 829 | + .init = vcnl4000_init, |
---|
| 830 | + .measure_light = vcnl4000_measure_light, |
---|
| 831 | + .measure_proximity = vcnl4000_measure_proximity, |
---|
| 832 | + .set_power_state = vcnl4000_set_power_state, |
---|
| 833 | + .channels = vcnl4010_channels, |
---|
| 834 | + .num_channels = ARRAY_SIZE(vcnl4010_channels), |
---|
| 835 | + .info = &vcnl4010_info, |
---|
| 836 | + .irq_support = true, |
---|
| 837 | + }, |
---|
| 838 | + [VCNL4040] = { |
---|
| 839 | + .prod = "VCNL4040", |
---|
| 840 | + .init = vcnl4200_init, |
---|
| 841 | + .measure_light = vcnl4200_measure_light, |
---|
| 842 | + .measure_proximity = vcnl4200_measure_proximity, |
---|
| 843 | + .set_power_state = vcnl4200_set_power_state, |
---|
| 844 | + .channels = vcnl4000_channels, |
---|
| 845 | + .num_channels = ARRAY_SIZE(vcnl4000_channels), |
---|
| 846 | + .info = &vcnl4000_info, |
---|
| 847 | + .irq_support = false, |
---|
| 848 | + }, |
---|
| 849 | + [VCNL4200] = { |
---|
| 850 | + .prod = "VCNL4200", |
---|
| 851 | + .init = vcnl4200_init, |
---|
| 852 | + .measure_light = vcnl4200_measure_light, |
---|
| 853 | + .measure_proximity = vcnl4200_measure_proximity, |
---|
| 854 | + .set_power_state = vcnl4200_set_power_state, |
---|
| 855 | + .channels = vcnl4000_channels, |
---|
| 856 | + .num_channels = ARRAY_SIZE(vcnl4000_channels), |
---|
| 857 | + .info = &vcnl4000_info, |
---|
| 858 | + .irq_support = false, |
---|
| 859 | + }, |
---|
| 860 | +}; |
---|
| 861 | + |
---|
| 862 | +static irqreturn_t vcnl4010_irq_thread(int irq, void *p) |
---|
| 863 | +{ |
---|
| 864 | + struct iio_dev *indio_dev = p; |
---|
| 865 | + struct vcnl4000_data *data = iio_priv(indio_dev); |
---|
| 866 | + unsigned long isr; |
---|
| 867 | + int ret; |
---|
| 868 | + |
---|
| 869 | + ret = i2c_smbus_read_byte_data(data->client, VCNL4010_ISR); |
---|
| 870 | + if (ret < 0) |
---|
| 871 | + goto end; |
---|
| 872 | + |
---|
| 873 | + isr = ret; |
---|
| 874 | + |
---|
| 875 | + if (isr & VCNL4010_INT_THR) { |
---|
| 876 | + if (test_bit(VCNL4010_INT_THR_LOW, &isr)) { |
---|
| 877 | + iio_push_event(indio_dev, |
---|
| 878 | + IIO_UNMOD_EVENT_CODE( |
---|
| 879 | + IIO_PROXIMITY, |
---|
| 880 | + 1, |
---|
| 881 | + IIO_EV_TYPE_THRESH, |
---|
| 882 | + IIO_EV_DIR_FALLING), |
---|
| 883 | + iio_get_time_ns(indio_dev)); |
---|
| 884 | + } |
---|
| 885 | + |
---|
| 886 | + if (test_bit(VCNL4010_INT_THR_HIGH, &isr)) { |
---|
| 887 | + iio_push_event(indio_dev, |
---|
| 888 | + IIO_UNMOD_EVENT_CODE( |
---|
| 889 | + IIO_PROXIMITY, |
---|
| 890 | + 1, |
---|
| 891 | + IIO_EV_TYPE_THRESH, |
---|
| 892 | + IIO_EV_DIR_RISING), |
---|
| 893 | + iio_get_time_ns(indio_dev)); |
---|
| 894 | + } |
---|
| 895 | + |
---|
| 896 | + i2c_smbus_write_byte_data(data->client, VCNL4010_ISR, |
---|
| 897 | + isr & VCNL4010_INT_THR); |
---|
| 898 | + } |
---|
| 899 | + |
---|
| 900 | + if (isr & VCNL4010_INT_DRDY && iio_buffer_enabled(indio_dev)) |
---|
| 901 | + iio_trigger_poll_chained(indio_dev->trig); |
---|
| 902 | + |
---|
| 903 | +end: |
---|
| 904 | + return IRQ_HANDLED; |
---|
| 905 | +} |
---|
| 906 | + |
---|
| 907 | +static irqreturn_t vcnl4010_trigger_handler(int irq, void *p) |
---|
| 908 | +{ |
---|
| 909 | + struct iio_poll_func *pf = p; |
---|
| 910 | + struct iio_dev *indio_dev = pf->indio_dev; |
---|
| 911 | + struct vcnl4000_data *data = iio_priv(indio_dev); |
---|
| 912 | + const unsigned long *active_scan_mask = indio_dev->active_scan_mask; |
---|
| 913 | + u16 buffer[8] __aligned(8) = {0}; /* 1x16-bit + naturally aligned ts */ |
---|
| 914 | + bool data_read = false; |
---|
| 915 | + unsigned long isr; |
---|
| 916 | + int val = 0; |
---|
| 917 | + int ret; |
---|
| 918 | + |
---|
| 919 | + ret = i2c_smbus_read_byte_data(data->client, VCNL4010_ISR); |
---|
| 920 | + if (ret < 0) |
---|
| 921 | + goto end; |
---|
| 922 | + |
---|
| 923 | + isr = ret; |
---|
| 924 | + |
---|
| 925 | + if (test_bit(0, active_scan_mask)) { |
---|
| 926 | + if (test_bit(VCNL4010_INT_PROXIMITY, &isr)) { |
---|
| 927 | + ret = vcnl4000_read_data(data, |
---|
| 928 | + VCNL4000_PS_RESULT_HI, |
---|
| 929 | + &val); |
---|
| 930 | + if (ret < 0) |
---|
| 931 | + goto end; |
---|
| 932 | + |
---|
| 933 | + buffer[0] = val; |
---|
| 934 | + data_read = true; |
---|
| 935 | + } |
---|
| 936 | + } |
---|
| 937 | + |
---|
| 938 | + ret = i2c_smbus_write_byte_data(data->client, VCNL4010_ISR, |
---|
| 939 | + isr & VCNL4010_INT_DRDY); |
---|
| 940 | + if (ret < 0) |
---|
| 941 | + goto end; |
---|
| 942 | + |
---|
| 943 | + if (!data_read) |
---|
| 944 | + goto end; |
---|
| 945 | + |
---|
| 946 | + iio_push_to_buffers_with_timestamp(indio_dev, buffer, |
---|
| 947 | + iio_get_time_ns(indio_dev)); |
---|
| 948 | + |
---|
| 949 | +end: |
---|
| 950 | + iio_trigger_notify_done(indio_dev->trig); |
---|
| 951 | + return IRQ_HANDLED; |
---|
| 952 | +} |
---|
| 953 | + |
---|
| 954 | +static int vcnl4010_buffer_postenable(struct iio_dev *indio_dev) |
---|
| 955 | +{ |
---|
| 956 | + struct vcnl4000_data *data = iio_priv(indio_dev); |
---|
| 957 | + int ret; |
---|
| 958 | + int cmd; |
---|
| 959 | + |
---|
| 960 | + /* Do not enable the buffer if we are already capturing events. */ |
---|
| 961 | + if (vcnl4010_is_in_periodic_mode(data)) |
---|
| 962 | + return -EBUSY; |
---|
| 963 | + |
---|
| 964 | + ret = i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL, |
---|
| 965 | + VCNL4010_INT_PROX_EN); |
---|
| 966 | + if (ret < 0) |
---|
| 967 | + return ret; |
---|
| 968 | + |
---|
| 969 | + cmd = VCNL4000_SELF_TIMED_EN | VCNL4000_PROX_EN; |
---|
| 970 | + return i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, cmd); |
---|
| 971 | +} |
---|
| 972 | + |
---|
| 973 | +static int vcnl4010_buffer_predisable(struct iio_dev *indio_dev) |
---|
| 974 | +{ |
---|
| 975 | + struct vcnl4000_data *data = iio_priv(indio_dev); |
---|
| 976 | + int ret; |
---|
| 977 | + |
---|
| 978 | + ret = i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL, 0); |
---|
| 979 | + if (ret < 0) |
---|
| 980 | + return ret; |
---|
| 981 | + |
---|
| 982 | + return i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, 0); |
---|
| 983 | +} |
---|
| 984 | + |
---|
| 985 | +static const struct iio_buffer_setup_ops vcnl4010_buffer_ops = { |
---|
| 986 | + .postenable = &vcnl4010_buffer_postenable, |
---|
| 987 | + .predisable = &vcnl4010_buffer_predisable, |
---|
| 988 | +}; |
---|
| 989 | + |
---|
| 990 | +static const struct iio_trigger_ops vcnl4010_trigger_ops = { |
---|
| 991 | + .validate_device = iio_trigger_validate_own_device, |
---|
| 992 | +}; |
---|
| 993 | + |
---|
| 994 | +static int vcnl4010_probe_trigger(struct iio_dev *indio_dev) |
---|
| 995 | +{ |
---|
| 996 | + struct vcnl4000_data *data = iio_priv(indio_dev); |
---|
| 997 | + struct i2c_client *client = data->client; |
---|
| 998 | + struct iio_trigger *trigger; |
---|
| 999 | + |
---|
| 1000 | + trigger = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", |
---|
| 1001 | + indio_dev->name, indio_dev->id); |
---|
| 1002 | + if (!trigger) |
---|
| 1003 | + return -ENOMEM; |
---|
| 1004 | + |
---|
| 1005 | + trigger->dev.parent = &client->dev; |
---|
| 1006 | + trigger->ops = &vcnl4010_trigger_ops; |
---|
| 1007 | + iio_trigger_set_drvdata(trigger, indio_dev); |
---|
| 1008 | + |
---|
| 1009 | + return devm_iio_trigger_register(&client->dev, trigger); |
---|
| 1010 | +} |
---|
330 | 1011 | |
---|
331 | 1012 | static int vcnl4000_probe(struct i2c_client *client, |
---|
332 | 1013 | const struct i2c_device_id *id) |
---|
.. | .. |
---|
352 | 1033 | dev_dbg(&client->dev, "%s Ambient light/proximity sensor, Rev: %02x\n", |
---|
353 | 1034 | data->chip_spec->prod, data->rev); |
---|
354 | 1035 | |
---|
355 | | - indio_dev->dev.parent = &client->dev; |
---|
356 | | - indio_dev->info = &vcnl4000_info; |
---|
357 | | - indio_dev->channels = vcnl4000_channels; |
---|
358 | | - indio_dev->num_channels = ARRAY_SIZE(vcnl4000_channels); |
---|
| 1036 | + if (device_property_read_u32(&client->dev, "proximity-near-level", |
---|
| 1037 | + &data->near_level)) |
---|
| 1038 | + data->near_level = 0; |
---|
| 1039 | + |
---|
| 1040 | + indio_dev->info = data->chip_spec->info; |
---|
| 1041 | + indio_dev->channels = data->chip_spec->channels; |
---|
| 1042 | + indio_dev->num_channels = data->chip_spec->num_channels; |
---|
359 | 1043 | indio_dev->name = VCNL4000_DRV_NAME; |
---|
360 | 1044 | indio_dev->modes = INDIO_DIRECT_MODE; |
---|
361 | 1045 | |
---|
362 | | - return devm_iio_device_register(&client->dev, indio_dev); |
---|
| 1046 | + if (client->irq && data->chip_spec->irq_support) { |
---|
| 1047 | + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, |
---|
| 1048 | + NULL, |
---|
| 1049 | + vcnl4010_trigger_handler, |
---|
| 1050 | + &vcnl4010_buffer_ops); |
---|
| 1051 | + if (ret < 0) { |
---|
| 1052 | + dev_err(&client->dev, |
---|
| 1053 | + "unable to setup iio triggered buffer\n"); |
---|
| 1054 | + return ret; |
---|
| 1055 | + } |
---|
| 1056 | + |
---|
| 1057 | + ret = devm_request_threaded_irq(&client->dev, client->irq, |
---|
| 1058 | + NULL, vcnl4010_irq_thread, |
---|
| 1059 | + IRQF_TRIGGER_FALLING | |
---|
| 1060 | + IRQF_ONESHOT, |
---|
| 1061 | + "vcnl4010_irq", |
---|
| 1062 | + indio_dev); |
---|
| 1063 | + if (ret < 0) { |
---|
| 1064 | + dev_err(&client->dev, "irq request failed\n"); |
---|
| 1065 | + return ret; |
---|
| 1066 | + } |
---|
| 1067 | + |
---|
| 1068 | + ret = vcnl4010_probe_trigger(indio_dev); |
---|
| 1069 | + if (ret < 0) |
---|
| 1070 | + return ret; |
---|
| 1071 | + } |
---|
| 1072 | + |
---|
| 1073 | + ret = pm_runtime_set_active(&client->dev); |
---|
| 1074 | + if (ret < 0) |
---|
| 1075 | + goto fail_poweroff; |
---|
| 1076 | + |
---|
| 1077 | + ret = iio_device_register(indio_dev); |
---|
| 1078 | + if (ret < 0) |
---|
| 1079 | + goto fail_poweroff; |
---|
| 1080 | + |
---|
| 1081 | + pm_runtime_enable(&client->dev); |
---|
| 1082 | + pm_runtime_set_autosuspend_delay(&client->dev, VCNL4000_SLEEP_DELAY_MS); |
---|
| 1083 | + pm_runtime_use_autosuspend(&client->dev); |
---|
| 1084 | + |
---|
| 1085 | + return 0; |
---|
| 1086 | +fail_poweroff: |
---|
| 1087 | + data->chip_spec->set_power_state(data, false); |
---|
| 1088 | + return ret; |
---|
363 | 1089 | } |
---|
| 1090 | + |
---|
| 1091 | +static const struct of_device_id vcnl_4000_of_match[] = { |
---|
| 1092 | + { |
---|
| 1093 | + .compatible = "vishay,vcnl4000", |
---|
| 1094 | + .data = (void *)VCNL4000, |
---|
| 1095 | + }, |
---|
| 1096 | + { |
---|
| 1097 | + .compatible = "vishay,vcnl4010", |
---|
| 1098 | + .data = (void *)VCNL4010, |
---|
| 1099 | + }, |
---|
| 1100 | + { |
---|
| 1101 | + .compatible = "vishay,vcnl4020", |
---|
| 1102 | + .data = (void *)VCNL4010, |
---|
| 1103 | + }, |
---|
| 1104 | + { |
---|
| 1105 | + .compatible = "vishay,vcnl4040", |
---|
| 1106 | + .data = (void *)VCNL4040, |
---|
| 1107 | + }, |
---|
| 1108 | + { |
---|
| 1109 | + .compatible = "vishay,vcnl4200", |
---|
| 1110 | + .data = (void *)VCNL4200, |
---|
| 1111 | + }, |
---|
| 1112 | + {}, |
---|
| 1113 | +}; |
---|
| 1114 | +MODULE_DEVICE_TABLE(of, vcnl_4000_of_match); |
---|
| 1115 | + |
---|
| 1116 | +static int vcnl4000_remove(struct i2c_client *client) |
---|
| 1117 | +{ |
---|
| 1118 | + struct iio_dev *indio_dev = i2c_get_clientdata(client); |
---|
| 1119 | + struct vcnl4000_data *data = iio_priv(indio_dev); |
---|
| 1120 | + |
---|
| 1121 | + pm_runtime_dont_use_autosuspend(&client->dev); |
---|
| 1122 | + pm_runtime_disable(&client->dev); |
---|
| 1123 | + iio_device_unregister(indio_dev); |
---|
| 1124 | + pm_runtime_set_suspended(&client->dev); |
---|
| 1125 | + |
---|
| 1126 | + return data->chip_spec->set_power_state(data, false); |
---|
| 1127 | +} |
---|
| 1128 | + |
---|
| 1129 | +static int __maybe_unused vcnl4000_runtime_suspend(struct device *dev) |
---|
| 1130 | +{ |
---|
| 1131 | + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); |
---|
| 1132 | + struct vcnl4000_data *data = iio_priv(indio_dev); |
---|
| 1133 | + |
---|
| 1134 | + return data->chip_spec->set_power_state(data, false); |
---|
| 1135 | +} |
---|
| 1136 | + |
---|
| 1137 | +static int __maybe_unused vcnl4000_runtime_resume(struct device *dev) |
---|
| 1138 | +{ |
---|
| 1139 | + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); |
---|
| 1140 | + struct vcnl4000_data *data = iio_priv(indio_dev); |
---|
| 1141 | + |
---|
| 1142 | + return data->chip_spec->set_power_state(data, true); |
---|
| 1143 | +} |
---|
| 1144 | + |
---|
| 1145 | +static const struct dev_pm_ops vcnl4000_pm_ops = { |
---|
| 1146 | + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, |
---|
| 1147 | + pm_runtime_force_resume) |
---|
| 1148 | + SET_RUNTIME_PM_OPS(vcnl4000_runtime_suspend, |
---|
| 1149 | + vcnl4000_runtime_resume, NULL) |
---|
| 1150 | +}; |
---|
364 | 1151 | |
---|
365 | 1152 | static struct i2c_driver vcnl4000_driver = { |
---|
366 | 1153 | .driver = { |
---|
367 | 1154 | .name = VCNL4000_DRV_NAME, |
---|
| 1155 | + .pm = &vcnl4000_pm_ops, |
---|
| 1156 | + .of_match_table = vcnl_4000_of_match, |
---|
368 | 1157 | }, |
---|
369 | 1158 | .probe = vcnl4000_probe, |
---|
370 | 1159 | .id_table = vcnl4000_id, |
---|
| 1160 | + .remove = vcnl4000_remove, |
---|
371 | 1161 | }; |
---|
372 | 1162 | |
---|
373 | 1163 | module_i2c_driver(vcnl4000_driver); |
---|
374 | 1164 | |
---|
375 | 1165 | MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); |
---|
| 1166 | +MODULE_AUTHOR("Mathieu Othacehe <m.othacehe@gmail.com>"); |
---|
376 | 1167 | MODULE_DESCRIPTION("Vishay VCNL4000 proximity/ambient light sensor driver"); |
---|
377 | 1168 | MODULE_LICENSE("GPL"); |
---|