Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * vl6180.c - Support for STMicroelectronics VL6180 ALS, range and proximity
  4 * sensor
  5 *
  6 * Copyright 2017 Peter Meerwald-Stadler <pmeerw@pmeerw.net>
  7 * Copyright 2017 Manivannan Sadhasivam <manivannanece23@gmail.com>
  8 *
  9 * IIO driver for VL6180 (7-bit I2C slave address 0x29)
 10 *
 11 * Range: 0 to 100mm
 12 * ALS: < 1 Lux up to 100 kLux
 13 * IR: 850nm
 14 *
 15 * TODO: irq, threshold events, continuous mode, hardware buffer
 16 */
 17
 18#include <linux/module.h>
 19#include <linux/mod_devicetable.h>
 20#include <linux/i2c.h>
 21#include <linux/mutex.h>
 22#include <linux/err.h>
 23#include <linux/of.h>
 24#include <linux/delay.h>
 25#include <linux/util_macros.h>
 26
 27#include <linux/iio/iio.h>
 28#include <linux/iio/sysfs.h>
 29
 30#define VL6180_DRV_NAME "vl6180"
 31
 32/* Device identification register and value */
 33#define VL6180_MODEL_ID	0x000
 34#define VL6180_MODEL_ID_VAL 0xb4
 35
 36/* Configuration registers */
 37#define VL6180_INTR_CONFIG 0x014
 38#define VL6180_INTR_CLEAR 0x015
 39#define VL6180_OUT_OF_RESET 0x016
 40#define VL6180_HOLD 0x017
 41#define VL6180_RANGE_START 0x018
 42#define VL6180_ALS_START 0x038
 43#define VL6180_ALS_GAIN 0x03f
 44#define VL6180_ALS_IT 0x040
 45
 46/* Status registers */
 47#define VL6180_RANGE_STATUS 0x04d
 48#define VL6180_ALS_STATUS 0x04e
 49#define VL6180_INTR_STATUS 0x04f
 50
 51/* Result value registers */
 52#define VL6180_ALS_VALUE 0x050
 53#define VL6180_RANGE_VALUE 0x062
 54#define VL6180_RANGE_RATE 0x066
 55
 56/* bits of the RANGE_START and ALS_START register */
 57#define VL6180_MODE_CONT BIT(1) /* continuous mode */
 58#define VL6180_STARTSTOP BIT(0) /* start measurement, auto-reset */
 59
 60/* bits of the INTR_STATUS and INTR_CONFIG register */
 61#define VL6180_ALS_READY BIT(5)
 62#define VL6180_RANGE_READY BIT(2)
 63
 64/* bits of the INTR_CLEAR register */
 65#define VL6180_CLEAR_ERROR BIT(2)
 66#define VL6180_CLEAR_ALS BIT(1)
 67#define VL6180_CLEAR_RANGE BIT(0)
 68
 69/* bits of the HOLD register */
 70#define VL6180_HOLD_ON BIT(0)
 71
 72/* default value for the ALS_IT register */
 73#define VL6180_ALS_IT_100 0x63 /* 100 ms */
 74
 75/* values for the ALS_GAIN register */
 76#define VL6180_ALS_GAIN_1 0x46
 77#define VL6180_ALS_GAIN_1_25 0x45
 78#define VL6180_ALS_GAIN_1_67 0x44
 79#define VL6180_ALS_GAIN_2_5 0x43
 80#define VL6180_ALS_GAIN_5 0x42
 81#define VL6180_ALS_GAIN_10 0x41
 82#define VL6180_ALS_GAIN_20 0x40
 83#define VL6180_ALS_GAIN_40 0x47
 84
 85struct vl6180_data {
 86	struct i2c_client *client;
 87	struct mutex lock;
 88	unsigned int als_gain_milli;
 89	unsigned int als_it_ms;
 90};
 91
 92enum { VL6180_ALS, VL6180_RANGE, VL6180_PROX };
 93
 94/**
 95 * struct vl6180_chan_regs - Registers for accessing channels
 96 * @drdy_mask:			Data ready bit in status register
 97 * @start_reg:			Conversion start register
 98 * @value_reg:			Result value register
 99 * @word:			Register word length
100 */
101struct vl6180_chan_regs {
102	u8 drdy_mask;
103	u16 start_reg, value_reg;
104	bool word;
105};
106
107static const struct vl6180_chan_regs vl6180_chan_regs_table[] = {
108	[VL6180_ALS] = {
109		.drdy_mask = VL6180_ALS_READY,
110		.start_reg = VL6180_ALS_START,
111		.value_reg = VL6180_ALS_VALUE,
112		.word = true,
113	},
114	[VL6180_RANGE] = {
115		.drdy_mask = VL6180_RANGE_READY,
116		.start_reg = VL6180_RANGE_START,
117		.value_reg = VL6180_RANGE_VALUE,
118		.word = false,
119	},
120	[VL6180_PROX] = {
121		.drdy_mask = VL6180_RANGE_READY,
122		.start_reg = VL6180_RANGE_START,
123		.value_reg = VL6180_RANGE_RATE,
124		.word = true,
125	},
126};
127
128static int vl6180_read(struct i2c_client *client, u16 cmd, void *databuf,
129		       u8 len)
130{
131	__be16 cmdbuf = cpu_to_be16(cmd);
132	struct i2c_msg msgs[2] = {
133		{ .addr = client->addr, .len = sizeof(cmdbuf), .buf = (u8 *) &cmdbuf },
134		{ .addr = client->addr, .len = len, .buf = databuf,
135		  .flags = I2C_M_RD } };
136	int ret;
137
138	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
139	if (ret < 0)
140		dev_err(&client->dev, "failed reading register 0x%04x\n", cmd);
141
142	return ret;
143}
144
145static int vl6180_read_byte(struct i2c_client *client, u16 cmd)
146{
147	u8 data;
148	int ret;
149
150	ret = vl6180_read(client, cmd, &data, sizeof(data));
151	if (ret < 0)
152		return ret;
153
154	return data;
155}
156
157static int vl6180_read_word(struct i2c_client *client, u16 cmd)
158{
159	__be16 data;
160	int ret;
161
162	ret = vl6180_read(client, cmd, &data, sizeof(data));
163	if (ret < 0)
164		return ret;
165
166	return be16_to_cpu(data);
167}
168
169static int vl6180_write_byte(struct i2c_client *client, u16 cmd, u8 val)
170{
171	u8 buf[3];
172	struct i2c_msg msgs[1] = {
173		{ .addr = client->addr, .len = sizeof(buf), .buf = (u8 *) &buf } };
174	int ret;
175
176	buf[0] = cmd >> 8;
177	buf[1] = cmd & 0xff;
178	buf[2] = val;
179
180	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
181	if (ret < 0) {
182		dev_err(&client->dev, "failed writing register 0x%04x\n", cmd);
183		return ret;
184	}
185
186	return 0;
187}
188
189static int vl6180_write_word(struct i2c_client *client, u16 cmd, u16 val)
190{
191	__be16 buf[2];
192	struct i2c_msg msgs[1] = {
193		{ .addr = client->addr, .len = sizeof(buf), .buf = (u8 *) &buf } };
194	int ret;
195
196	buf[0] = cpu_to_be16(cmd);
197	buf[1] = cpu_to_be16(val);
198
199	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
200	if (ret < 0) {
201		dev_err(&client->dev, "failed writing register 0x%04x\n", cmd);
202		return ret;
203	}
204
205	return 0;
206}
207
208static int vl6180_measure(struct vl6180_data *data, int addr)
209{
210	struct i2c_client *client = data->client;
211	int tries = 20, ret;
212	u16 value;
213
214	mutex_lock(&data->lock);
215	/* Start single shot measurement */
216	ret = vl6180_write_byte(client,
217		vl6180_chan_regs_table[addr].start_reg, VL6180_STARTSTOP);
218	if (ret < 0)
219		goto fail;
220
221	while (tries--) {
222		ret = vl6180_read_byte(client, VL6180_INTR_STATUS);
223		if (ret < 0)
224			goto fail;
225
226		if (ret & vl6180_chan_regs_table[addr].drdy_mask)
227			break;
228		msleep(20);
229	}
230
231	if (tries < 0) {
232		ret = -EIO;
233		goto fail;
234	}
235
236	/* Read result value from appropriate registers */
237	ret = vl6180_chan_regs_table[addr].word ?
238		vl6180_read_word(client, vl6180_chan_regs_table[addr].value_reg) :
239		vl6180_read_byte(client, vl6180_chan_regs_table[addr].value_reg);
240	if (ret < 0)
241		goto fail;
242	value = ret;
243
244	/* Clear the interrupt flag after data read */
245	ret = vl6180_write_byte(client, VL6180_INTR_CLEAR,
246		VL6180_CLEAR_ERROR | VL6180_CLEAR_ALS | VL6180_CLEAR_RANGE);
247	if (ret < 0)
248		goto fail;
249
250	ret = value;
251
252fail:
253	mutex_unlock(&data->lock);
254
255	return ret;
256}
257
258static const struct iio_chan_spec vl6180_channels[] = {
259	{
260		.type = IIO_LIGHT,
261		.address = VL6180_ALS,
262		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
263			BIT(IIO_CHAN_INFO_INT_TIME) |
264			BIT(IIO_CHAN_INFO_SCALE) |
265			BIT(IIO_CHAN_INFO_HARDWAREGAIN),
266	}, {
267		.type = IIO_DISTANCE,
268		.address = VL6180_RANGE,
269		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
270			BIT(IIO_CHAN_INFO_SCALE),
271	}, {
272		.type = IIO_PROXIMITY,
273		.address = VL6180_PROX,
274		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
275	}
276};
277
278/*
279 * Available Ambient Light Sensor gain settings, 1/1000th, and
280 * corresponding setting for the VL6180_ALS_GAIN register
281 */
282static const int vl6180_als_gain_tab[8] = {
283	1000, 1250, 1670, 2500, 5000, 10000, 20000, 40000
284};
285static const u8 vl6180_als_gain_tab_bits[8] = {
286	VL6180_ALS_GAIN_1,    VL6180_ALS_GAIN_1_25,
287	VL6180_ALS_GAIN_1_67, VL6180_ALS_GAIN_2_5,
288	VL6180_ALS_GAIN_5,    VL6180_ALS_GAIN_10,
289	VL6180_ALS_GAIN_20,   VL6180_ALS_GAIN_40
290};
291
292static int vl6180_read_raw(struct iio_dev *indio_dev,
293				struct iio_chan_spec const *chan,
294				int *val, int *val2, long mask)
295{
296	struct vl6180_data *data = iio_priv(indio_dev);
297	int ret;
298
299	switch (mask) {
300	case IIO_CHAN_INFO_RAW:
301		ret = vl6180_measure(data, chan->address);
302		if (ret < 0)
303			return ret;
304		*val = ret;
305
306		return IIO_VAL_INT;
307	case IIO_CHAN_INFO_INT_TIME:
308		*val = data->als_it_ms;
309		*val2 = 1000;
310
311		return IIO_VAL_FRACTIONAL;
312
313	case IIO_CHAN_INFO_SCALE:
314		switch (chan->type) {
315		case IIO_LIGHT:
316			/* one ALS count is 0.32 Lux @ gain 1, IT 100 ms */
317			*val = 32000; /* 0.32 * 1000 * 100 */
318			*val2 = data->als_gain_milli * data->als_it_ms;
319
320			return IIO_VAL_FRACTIONAL;
321
322		case IIO_DISTANCE:
323			*val = 0; /* sensor reports mm, scale to meter */
324			*val2 = 1000;
325			break;
326		default:
327			return -EINVAL;
328		}
329
330		return IIO_VAL_INT_PLUS_MICRO;
331	case IIO_CHAN_INFO_HARDWAREGAIN:
332		*val = data->als_gain_milli;
333		*val2 = 1000;
334
335		return IIO_VAL_FRACTIONAL;
336
337	default:
338		return -EINVAL;
339	}
340}
341
342static IIO_CONST_ATTR(als_gain_available, "1 1.25 1.67 2.5 5 10 20 40");
343
344static struct attribute *vl6180_attributes[] = {
345	&iio_const_attr_als_gain_available.dev_attr.attr,
346	NULL
347};
348
349static const struct attribute_group vl6180_attribute_group = {
350	.attrs = vl6180_attributes,
351};
352
353/* HOLD is needed before updating any config registers */
354static int vl6180_hold(struct vl6180_data *data, bool hold)
355{
356	return vl6180_write_byte(data->client, VL6180_HOLD,
357		hold ? VL6180_HOLD_ON : 0);
358}
359
360static int vl6180_set_als_gain(struct vl6180_data *data, int val, int val2)
361{
362	int i, ret, gain;
363
364	if (val < 1 || val > 40)
365		return -EINVAL;
366
367	gain = (val * 1000000 + val2) / 1000;
368	if (gain < 1 || gain > 40000)
369		return -EINVAL;
370
371	i = find_closest(gain, vl6180_als_gain_tab,
372			 ARRAY_SIZE(vl6180_als_gain_tab));
373
374	mutex_lock(&data->lock);
375	ret = vl6180_hold(data, true);
376	if (ret < 0)
377		goto fail;
378
379	ret = vl6180_write_byte(data->client, VL6180_ALS_GAIN,
380				vl6180_als_gain_tab_bits[i]);
381
382	if (ret >= 0)
383		data->als_gain_milli = vl6180_als_gain_tab[i];
384
385fail:
386	vl6180_hold(data, false);
387	mutex_unlock(&data->lock);
388	return ret;
389}
390
391static int vl6180_set_it(struct vl6180_data *data, int val, int val2)
392{
393	int ret, it_ms;
394
395	it_ms = DIV_ROUND_CLOSEST(val2, 1000); /* round to ms */
396	if (val != 0 || it_ms < 1 || it_ms > 512)
397		return -EINVAL;
398
399	mutex_lock(&data->lock);
400	ret = vl6180_hold(data, true);
401	if (ret < 0)
402		goto fail;
403
404	ret = vl6180_write_word(data->client, VL6180_ALS_IT, it_ms - 1);
405
406	if (ret >= 0)
407		data->als_it_ms = it_ms;
408
409fail:
410	vl6180_hold(data, false);
411	mutex_unlock(&data->lock);
412
413	return ret;
414}
415
416static int vl6180_write_raw(struct iio_dev *indio_dev,
417			     struct iio_chan_spec const *chan,
418			     int val, int val2, long mask)
419{
420	struct vl6180_data *data = iio_priv(indio_dev);
421
422	switch (mask) {
423	case IIO_CHAN_INFO_INT_TIME:
424		return vl6180_set_it(data, val, val2);
425
426	case IIO_CHAN_INFO_HARDWAREGAIN:
427		if (chan->type != IIO_LIGHT)
428			return -EINVAL;
429
430		return vl6180_set_als_gain(data, val, val2);
431	default:
432		return -EINVAL;
433	}
434}
435
436static const struct iio_info vl6180_info = {
437	.read_raw = vl6180_read_raw,
438	.write_raw = vl6180_write_raw,
439	.attrs = &vl6180_attribute_group,
440};
441
442static int vl6180_init(struct vl6180_data *data)
443{
444	struct i2c_client *client = data->client;
445	int ret;
446
447	ret = vl6180_read_byte(client, VL6180_MODEL_ID);
448	if (ret < 0)
449		return ret;
450
451	if (ret != VL6180_MODEL_ID_VAL) {
452		dev_err(&client->dev, "invalid model ID %02x\n", ret);
453		return -ENODEV;
454	}
455
456	ret = vl6180_hold(data, true);
457	if (ret < 0)
458		return ret;
459
460	ret = vl6180_read_byte(client, VL6180_OUT_OF_RESET);
461	if (ret < 0)
462		return ret;
463
464	/*
465	 * Detect false reset condition here. This bit is always set when the
466	 * system comes out of reset.
467	 */
468	if (ret != 0x01)
469		dev_info(&client->dev, "device is not fresh out of reset\n");
470
471	/* Enable ALS and Range ready interrupts */
472	ret = vl6180_write_byte(client, VL6180_INTR_CONFIG,
473				VL6180_ALS_READY | VL6180_RANGE_READY);
474	if (ret < 0)
475		return ret;
476
477	/* ALS integration time: 100ms */
478	data->als_it_ms = 100;
479	ret = vl6180_write_word(client, VL6180_ALS_IT, VL6180_ALS_IT_100);
480	if (ret < 0)
481		return ret;
482
483	/* ALS gain: 1 */
484	data->als_gain_milli = 1000;
485	ret = vl6180_write_byte(client, VL6180_ALS_GAIN, VL6180_ALS_GAIN_1);
486	if (ret < 0)
487		return ret;
488
489	ret = vl6180_write_byte(client, VL6180_OUT_OF_RESET, 0x00);
490	if (ret < 0)
491		return ret;
492
493	return vl6180_hold(data, false);
494}
495
496static int vl6180_probe(struct i2c_client *client,
497			  const struct i2c_device_id *id)
498{
499	struct vl6180_data *data;
500	struct iio_dev *indio_dev;
501	int ret;
502
503	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
504	if (!indio_dev)
505		return -ENOMEM;
506
507	data = iio_priv(indio_dev);
508	i2c_set_clientdata(client, indio_dev);
509	data->client = client;
510	mutex_init(&data->lock);
511
512	indio_dev->info = &vl6180_info;
513	indio_dev->channels = vl6180_channels;
514	indio_dev->num_channels = ARRAY_SIZE(vl6180_channels);
515	indio_dev->name = VL6180_DRV_NAME;
516	indio_dev->modes = INDIO_DIRECT_MODE;
517
518	ret = vl6180_init(data);
519	if (ret < 0)
520		return ret;
521
522	return devm_iio_device_register(&client->dev, indio_dev);
523}
524
525static const struct of_device_id vl6180_of_match[] = {
526	{ .compatible = "st,vl6180", },
527	{ },
528};
529MODULE_DEVICE_TABLE(of, vl6180_of_match);
530
531static const struct i2c_device_id vl6180_id[] = {
532	{ "vl6180", 0 },
533	{ }
534};
535MODULE_DEVICE_TABLE(i2c, vl6180_id);
536
537static struct i2c_driver vl6180_driver = {
538	.driver = {
539		.name   = VL6180_DRV_NAME,
540		.of_match_table = vl6180_of_match,
541	},
542	.probe  = vl6180_probe,
543	.id_table = vl6180_id,
544};
545
546module_i2c_driver(vl6180_driver);
547
548MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>");
549MODULE_AUTHOR("Manivannan Sadhasivam <manivannanece23@gmail.com>");
550MODULE_DESCRIPTION("STMicro VL6180 ALS, range and proximity sensor driver");
551MODULE_LICENSE("GPL");