]> Git Repo - J-linux.git/blob - drivers/iio/proximity/vcnl3020.c
Merge remote-tracking branch 'spi/for-5.14' into spi-linus
[J-linux.git] / drivers / iio / proximity / vcnl3020.c
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 static const int vcnl3020_prox_sampling_frequency[][2] = {
44         {1, 950000},
45         {3, 906250},
46         {7, 812500},
47         {16, 625000},
48         {31, 250000},
49         {62, 500000},
50         {125, 0},
51         {250, 0},
52 };
53
54 /**
55  * struct vcnl3020_data - vcnl3020 specific data.
56  * @regmap:     device register map.
57  * @dev:        vcnl3020 device.
58  * @rev:        revision id.
59  * @lock:       lock for protecting access to device hardware registers.
60  */
61 struct vcnl3020_data {
62         struct regmap *regmap;
63         struct device *dev;
64         u8 rev;
65         struct mutex lock;
66 };
67
68 /**
69  * struct vcnl3020_property - vcnl3020 property.
70  * @name:       property name.
71  * @reg:        i2c register offset.
72  * @conversion_func:    conversion function.
73  */
74 struct vcnl3020_property {
75         const char *name;
76         u32 reg;
77         u32 (*conversion_func)(u32 *val);
78 };
79
80 static u32 microamp_to_reg(u32 *val)
81 {
82         /*
83          * An example of conversion from uA to reg val:
84          * 200000 uA == 200 mA == 20
85          */
86         return *val /= 10000;
87 };
88
89 static struct vcnl3020_property vcnl3020_led_current_property = {
90         .name = "vishay,led-current-microamp",
91         .reg = VCNL_LED_CURRENT,
92         .conversion_func = microamp_to_reg,
93 };
94
95 static int vcnl3020_get_and_apply_property(struct vcnl3020_data *data,
96                                            struct vcnl3020_property prop)
97 {
98         int rc;
99         u32 val;
100
101         rc = device_property_read_u32(data->dev, prop.name, &val);
102         if (rc)
103                 return 0;
104
105         if (prop.conversion_func)
106                 prop.conversion_func(&val);
107
108         rc = regmap_write(data->regmap, prop.reg, val);
109         if (rc) {
110                 dev_err(data->dev, "Error (%d) setting property (%s)\n",
111                         rc, prop.name);
112         }
113
114         return rc;
115 }
116
117 static int vcnl3020_init(struct vcnl3020_data *data)
118 {
119         int rc;
120         unsigned int reg;
121
122         rc = regmap_read(data->regmap, VCNL_PROD_REV, &reg);
123         if (rc) {
124                 dev_err(data->dev,
125                         "Error (%d) reading product revision\n", rc);
126                 return rc;
127         }
128
129         if (reg != VCNL3020_PROD_ID) {
130                 dev_err(data->dev,
131                         "Product id (%x) did not match vcnl3020 (%x)\n", reg,
132                         VCNL3020_PROD_ID);
133                 return -ENODEV;
134         }
135
136         data->rev = reg;
137         mutex_init(&data->lock);
138
139         return vcnl3020_get_and_apply_property(data,
140                                                vcnl3020_led_current_property);
141 };
142
143 static int vcnl3020_measure_proximity(struct vcnl3020_data *data, int *val)
144 {
145         int rc;
146         unsigned int reg;
147         __be16 res;
148
149         mutex_lock(&data->lock);
150
151         rc = regmap_write(data->regmap, VCNL_COMMAND, VCNL_PS_OD);
152         if (rc)
153                 goto err_unlock;
154
155         /* wait for data to become ready */
156         rc = regmap_read_poll_timeout(data->regmap, VCNL_COMMAND, reg,
157                                       reg & VCNL_PS_RDY, VCNL_POLL_US,
158                                       VCNL_ON_DEMAND_TIMEOUT_US);
159         if (rc) {
160                 dev_err(data->dev,
161                         "Error (%d) reading vcnl3020 command register\n", rc);
162                 goto err_unlock;
163         }
164
165         /* high & low result bytes read */
166         rc = regmap_bulk_read(data->regmap, VCNL_PS_RESULT_HI, &res,
167                               sizeof(res));
168         if (rc)
169                 goto err_unlock;
170
171         *val = be16_to_cpu(res);
172
173 err_unlock:
174         mutex_unlock(&data->lock);
175
176         return rc;
177 }
178
179 static int vcnl3020_read_proxy_samp_freq(struct vcnl3020_data *data, int *val,
180                                          int *val2)
181 {
182         int rc;
183         unsigned int prox_rate;
184
185         rc = regmap_read(data->regmap, VCNL_PROXIMITY_RATE, &prox_rate);
186         if (rc)
187                 return rc;
188
189         if (prox_rate >= ARRAY_SIZE(vcnl3020_prox_sampling_frequency))
190                 return -EINVAL;
191
192         *val = vcnl3020_prox_sampling_frequency[prox_rate][0];
193         *val2 = vcnl3020_prox_sampling_frequency[prox_rate][1];
194
195         return 0;
196 }
197
198 static int vcnl3020_write_proxy_samp_freq(struct vcnl3020_data *data, int val,
199                                           int val2)
200 {
201         unsigned int i;
202         int index = -1;
203
204         for (i = 0; i < ARRAY_SIZE(vcnl3020_prox_sampling_frequency); i++) {
205                 if (val == vcnl3020_prox_sampling_frequency[i][0] &&
206                     val2 == vcnl3020_prox_sampling_frequency[i][1]) {
207                         index = i;
208                         break;
209                 }
210         }
211
212         if (index < 0)
213                 return -EINVAL;
214
215         return regmap_write(data->regmap, VCNL_PROXIMITY_RATE, index);
216 }
217
218 static const struct iio_chan_spec vcnl3020_channels[] = {
219         {
220                 .type = IIO_PROXIMITY,
221                 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
222                                       BIT(IIO_CHAN_INFO_SAMP_FREQ),
223                 .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
224         },
225 };
226
227 static int vcnl3020_read_raw(struct iio_dev *indio_dev,
228                              struct iio_chan_spec const *chan, int *val,
229                              int *val2, long mask)
230 {
231         int rc;
232         struct vcnl3020_data *data = iio_priv(indio_dev);
233
234         switch (mask) {
235         case IIO_CHAN_INFO_RAW:
236                 rc = vcnl3020_measure_proximity(data, val);
237                 if (rc)
238                         return rc;
239                 return IIO_VAL_INT;
240         case IIO_CHAN_INFO_SAMP_FREQ:
241                 rc = vcnl3020_read_proxy_samp_freq(data, val, val2);
242                 if (rc < 0)
243                         return rc;
244                 return IIO_VAL_INT_PLUS_MICRO;
245         default:
246                 return -EINVAL;
247         }
248 }
249
250 static int vcnl3020_write_raw(struct iio_dev *indio_dev,
251                               struct iio_chan_spec const *chan,
252                               int val, int val2, long mask)
253 {
254         int rc;
255         struct vcnl3020_data *data = iio_priv(indio_dev);
256
257         switch (mask) {
258         case IIO_CHAN_INFO_SAMP_FREQ:
259                 rc = iio_device_claim_direct_mode(indio_dev);
260                 if (rc)
261                         return rc;
262                 rc = vcnl3020_write_proxy_samp_freq(data, val, val2);
263                 iio_device_release_direct_mode(indio_dev);
264                 return rc;
265         default:
266                 return -EINVAL;
267         }
268 }
269
270 static int vcnl3020_read_avail(struct iio_dev *indio_dev,
271                                struct iio_chan_spec const *chan,
272                                const int **vals, int *type, int *length,
273                                long mask)
274 {
275         switch (mask) {
276         case IIO_CHAN_INFO_SAMP_FREQ:
277                 *vals = (int *)vcnl3020_prox_sampling_frequency;
278                 *type = IIO_VAL_INT_PLUS_MICRO;
279                 *length = 2 * ARRAY_SIZE(vcnl3020_prox_sampling_frequency);
280                 return IIO_AVAIL_LIST;
281         default:
282                 return -EINVAL;
283         }
284 }
285
286 static const struct iio_info vcnl3020_info = {
287         .read_raw = vcnl3020_read_raw,
288         .write_raw = vcnl3020_write_raw,
289         .read_avail = vcnl3020_read_avail,
290 };
291
292 static const struct regmap_config vcnl3020_regmap_config = {
293         .reg_bits       = 8,
294         .val_bits       = 8,
295         .max_register   = VCNL_PS_MOD_ADJ,
296 };
297
298 static int vcnl3020_probe(struct i2c_client *client)
299 {
300         struct vcnl3020_data *data;
301         struct iio_dev *indio_dev;
302         struct regmap *regmap;
303         int rc;
304
305         regmap = devm_regmap_init_i2c(client, &vcnl3020_regmap_config);
306         if (IS_ERR(regmap)) {
307                 dev_err(&client->dev, "regmap_init failed\n");
308                 return PTR_ERR(regmap);
309         }
310
311         indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
312         if (!indio_dev)
313                 return -ENOMEM;
314
315         data = iio_priv(indio_dev);
316         i2c_set_clientdata(client, indio_dev);
317         data->regmap = regmap;
318         data->dev = &client->dev;
319
320         rc = vcnl3020_init(data);
321         if (rc)
322                 return rc;
323
324         indio_dev->info = &vcnl3020_info;
325         indio_dev->channels = vcnl3020_channels;
326         indio_dev->num_channels = ARRAY_SIZE(vcnl3020_channels);
327         indio_dev->name = "vcnl3020";
328         indio_dev->modes = INDIO_DIRECT_MODE;
329
330         return devm_iio_device_register(&client->dev, indio_dev);
331 }
332
333 static const struct of_device_id vcnl3020_of_match[] = {
334         {
335                 .compatible = "vishay,vcnl3020",
336         },
337         {}
338 };
339 MODULE_DEVICE_TABLE(of, vcnl3020_of_match);
340
341 static struct i2c_driver vcnl3020_driver = {
342         .driver = {
343                 .name   = "vcnl3020",
344                 .of_match_table = vcnl3020_of_match,
345         },
346         .probe_new  = vcnl3020_probe,
347 };
348 module_i2c_driver(vcnl3020_driver);
349
350 MODULE_AUTHOR("Ivan Mikhaylov <[email protected]>");
351 MODULE_DESCRIPTION("Vishay VCNL3020 proximity sensor driver");
352 MODULE_LICENSE("GPL");
This page took 0.050119 seconds and 4 git commands to generate.