vcnl4000.c revision 62a1efb9f868690d68b11ffb22dc598e547aa184
1/*
2 * vcnl4000.c - Support for Vishay VCNL4000 combined ambient light and
3 * proximity sensor
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 *
11 * IIO driver for VCNL4000 (7-bit I2C slave address 0x13)
12 *
13 * TODO:
14 *   allow to adjust IR current
15 *   proximity threshold and event handling
16 */
17
18#include <linux/module.h>
19#include <linux/i2c.h>
20#include <linux/err.h>
21#include <linux/delay.h>
22
23#include <linux/iio/iio.h>
24#include <linux/iio/sysfs.h>
25
26#define VCNL4000_DRV_NAME "vcnl4000"
27
28#define VCNL4000_COMMAND	0x80 /* Command register */
29#define VCNL4000_PROD_REV	0x81 /* Product ID and Revision ID */
30#define VCNL4000_LED_CURRENT	0x83 /* IR LED current for proximity mode */
31#define VCNL4000_AL_PARAM	0x84 /* Ambient light parameter register */
32#define VCNL4000_AL_RESULT_HI	0x85 /* Ambient light result register, MSB */
33#define VCNL4000_AL_RESULT_LO	0x86 /* Ambient light result register, LSB */
34#define VCNL4000_PS_RESULT_HI	0x87 /* Proximity result register, MSB */
35#define VCNL4000_PS_RESULT_LO	0x88 /* Proximity result register, LSB */
36#define VCNL4000_PS_MEAS_FREQ	0x89 /* Proximity test signal frequency */
37#define VCNL4000_PS_MOD_ADJ	0x8a /* Proximity modulator timing adjustment */
38
39/* Bit masks for COMMAND register */
40#define VCNL4000_AL_RDY		0x40 /* ALS data ready? */
41#define VCNL4000_PS_RDY		0x20 /* proximity data ready? */
42#define VCNL4000_AL_OD		0x10 /* start on-demand ALS measurement */
43#define VCNL4000_PS_OD		0x08 /* start on-demand proximity measurement */
44
45struct vcnl4000_data {
46	struct i2c_client *client;
47};
48
49static const struct i2c_device_id vcnl4000_id[] = {
50	{ "vcnl4000", 0 },
51	{ }
52};
53MODULE_DEVICE_TABLE(i2c, vcnl4000_id);
54
55static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask,
56				u8 rdy_mask, u8 data_reg, int *val)
57{
58	int tries = 20;
59	u16 buf;
60	int ret;
61
62	ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND,
63					req_mask);
64	if (ret < 0)
65		return ret;
66
67	/* wait for data to become ready */
68	while (tries--) {
69		ret = i2c_smbus_read_byte_data(data->client, VCNL4000_COMMAND);
70		if (ret < 0)
71			return ret;
72		if (ret & rdy_mask)
73			break;
74		msleep(20); /* measurement takes up to 100 ms */
75	}
76
77	if (tries < 0) {
78		dev_err(&data->client->dev,
79			"vcnl4000_measure() failed, data not ready\n");
80		return -EIO;
81	}
82
83	ret = i2c_smbus_read_i2c_block_data(data->client,
84		data_reg, sizeof(buf), (u8 *) &buf);
85	if (ret < 0)
86		return ret;
87
88	*val = be16_to_cpu(buf);
89
90	return 0;
91}
92
93static const struct iio_chan_spec vcnl4000_channels[] = {
94	{
95		.type = IIO_LIGHT,
96		.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT |
97			IIO_CHAN_INFO_SCALE_SEPARATE_BIT,
98	}, {
99		.type = IIO_PROXIMITY,
100		.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,
101	}
102};
103
104static int vcnl4000_read_raw(struct iio_dev *indio_dev,
105				struct iio_chan_spec const *chan,
106				int *val, int *val2, long mask)
107{
108	int ret = -EINVAL;
109	struct vcnl4000_data *data = iio_priv(indio_dev);
110
111	switch (mask) {
112	case IIO_CHAN_INFO_RAW:
113		switch (chan->type) {
114		case IIO_LIGHT:
115			ret = vcnl4000_measure(data,
116				VCNL4000_AL_OD, VCNL4000_AL_RDY,
117				VCNL4000_AL_RESULT_HI, val);
118			if (ret < 0)
119				return ret;
120			ret = IIO_VAL_INT;
121			break;
122		case IIO_PROXIMITY:
123			ret = vcnl4000_measure(data,
124				VCNL4000_PS_OD, VCNL4000_PS_RDY,
125				VCNL4000_PS_RESULT_HI, val);
126			if (ret < 0)
127				return ret;
128			ret = IIO_VAL_INT;
129			break;
130		default:
131			break;
132		}
133		break;
134	case IIO_CHAN_INFO_SCALE:
135		if (chan->type == IIO_LIGHT) {
136			*val = 0;
137			*val2 = 250000;
138			ret = IIO_VAL_INT_PLUS_MICRO;
139		}
140		break;
141	default:
142		break;
143	}
144
145	return ret;
146}
147
148static const struct iio_info vcnl4000_info = {
149	.read_raw = vcnl4000_read_raw,
150	.driver_module = THIS_MODULE,
151};
152
153static int __devinit vcnl4000_probe(struct i2c_client *client,
154					const struct i2c_device_id *id)
155{
156	struct vcnl4000_data *data;
157	struct iio_dev *indio_dev;
158	int ret;
159
160	indio_dev = iio_device_alloc(sizeof(*data));
161	if (!indio_dev)
162		return -ENOMEM;
163
164	data = iio_priv(indio_dev);
165	i2c_set_clientdata(client, indio_dev);
166	data->client = client;
167
168	ret = i2c_smbus_read_byte_data(data->client, VCNL4000_PROD_REV);
169	if (ret < 0)
170		goto error_free_dev;
171
172	dev_info(&client->dev, "VCNL4000 Ambient light/proximity sensor, Prod %02x, Rev: %02x\n",
173		ret >> 4, ret & 0xf);
174
175	indio_dev->dev.parent = &client->dev;
176	indio_dev->info = &vcnl4000_info;
177	indio_dev->channels = vcnl4000_channels;
178	indio_dev->num_channels = ARRAY_SIZE(vcnl4000_channels);
179	indio_dev->name = VCNL4000_DRV_NAME;
180	indio_dev->modes = INDIO_DIRECT_MODE;
181
182	ret = iio_device_register(indio_dev);
183	if (ret < 0)
184		goto error_free_dev;
185
186	return 0;
187
188error_free_dev:
189	iio_device_free(indio_dev);
190	return ret;
191}
192
193static int __devexit vcnl4000_remove(struct i2c_client *client)
194{
195	struct iio_dev *indio_dev = i2c_get_clientdata(client);
196
197	iio_device_unregister(indio_dev);
198	iio_device_free(indio_dev);
199
200	return 0;
201}
202
203static struct i2c_driver vcnl4000_driver = {
204	.driver = {
205		.name   = VCNL4000_DRV_NAME,
206		.owner  = THIS_MODULE,
207	},
208	.probe  = vcnl4000_probe,
209	.remove = __devexit_p(vcnl4000_remove),
210	.id_table = vcnl4000_id,
211};
212
213module_i2c_driver(vcnl4000_driver);
214
215MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
216MODULE_DESCRIPTION("Vishay VCNL4000 proximity/ambient light sensor driver");
217MODULE_LICENSE("GPL");
218