xref: /linux/drivers/iio/light/vcnl4000.c (revision 2669d723ac6a01857273848a090c747f044a62d3)
162a1efb9SPeter Meerwald /*
262a1efb9SPeter Meerwald  * vcnl4000.c - Support for Vishay VCNL4000 combined ambient light and
362a1efb9SPeter Meerwald  * proximity sensor
462a1efb9SPeter Meerwald  *
562a1efb9SPeter Meerwald  * Copyright 2012 Peter Meerwald <pmeerw@pmeerw.net>
662a1efb9SPeter Meerwald  *
762a1efb9SPeter Meerwald  * This file is subject to the terms and conditions of version 2 of
862a1efb9SPeter Meerwald  * the GNU General Public License.  See the file COPYING in the main
962a1efb9SPeter Meerwald  * directory of this archive for more details.
1062a1efb9SPeter Meerwald  *
1162a1efb9SPeter Meerwald  * IIO driver for VCNL4000 (7-bit I2C slave address 0x13)
1262a1efb9SPeter Meerwald  *
1362a1efb9SPeter Meerwald  * TODO:
1462a1efb9SPeter Meerwald  *   allow to adjust IR current
1562a1efb9SPeter Meerwald  *   proximity threshold and event handling
1662a1efb9SPeter Meerwald  */
1762a1efb9SPeter Meerwald 
1862a1efb9SPeter Meerwald #include <linux/module.h>
1962a1efb9SPeter Meerwald #include <linux/i2c.h>
2062a1efb9SPeter Meerwald #include <linux/err.h>
2162a1efb9SPeter Meerwald #include <linux/delay.h>
2262a1efb9SPeter Meerwald 
2362a1efb9SPeter Meerwald #include <linux/iio/iio.h>
2462a1efb9SPeter Meerwald #include <linux/iio/sysfs.h>
2562a1efb9SPeter Meerwald 
2662a1efb9SPeter Meerwald #define VCNL4000_DRV_NAME "vcnl4000"
2762a1efb9SPeter Meerwald 
2862a1efb9SPeter Meerwald #define VCNL4000_COMMAND	0x80 /* Command register */
2962a1efb9SPeter Meerwald #define VCNL4000_PROD_REV	0x81 /* Product ID and Revision ID */
3062a1efb9SPeter Meerwald #define VCNL4000_LED_CURRENT	0x83 /* IR LED current for proximity mode */
3162a1efb9SPeter Meerwald #define VCNL4000_AL_PARAM	0x84 /* Ambient light parameter register */
3262a1efb9SPeter Meerwald #define VCNL4000_AL_RESULT_HI	0x85 /* Ambient light result register, MSB */
3362a1efb9SPeter Meerwald #define VCNL4000_AL_RESULT_LO	0x86 /* Ambient light result register, LSB */
3462a1efb9SPeter Meerwald #define VCNL4000_PS_RESULT_HI	0x87 /* Proximity result register, MSB */
3562a1efb9SPeter Meerwald #define VCNL4000_PS_RESULT_LO	0x88 /* Proximity result register, LSB */
3662a1efb9SPeter Meerwald #define VCNL4000_PS_MEAS_FREQ	0x89 /* Proximity test signal frequency */
3762a1efb9SPeter Meerwald #define VCNL4000_PS_MOD_ADJ	0x8a /* Proximity modulator timing adjustment */
3862a1efb9SPeter Meerwald 
3962a1efb9SPeter Meerwald /* Bit masks for COMMAND register */
4062a1efb9SPeter Meerwald #define VCNL4000_AL_RDY		0x40 /* ALS data ready? */
4162a1efb9SPeter Meerwald #define VCNL4000_PS_RDY		0x20 /* proximity data ready? */
4262a1efb9SPeter Meerwald #define VCNL4000_AL_OD		0x10 /* start on-demand ALS measurement */
4362a1efb9SPeter Meerwald #define VCNL4000_PS_OD		0x08 /* start on-demand proximity measurement */
4462a1efb9SPeter Meerwald 
4562a1efb9SPeter Meerwald struct vcnl4000_data {
4662a1efb9SPeter Meerwald 	struct i2c_client *client;
4762a1efb9SPeter Meerwald };
4862a1efb9SPeter Meerwald 
4962a1efb9SPeter Meerwald static const struct i2c_device_id vcnl4000_id[] = {
5062a1efb9SPeter Meerwald 	{ "vcnl4000", 0 },
5162a1efb9SPeter Meerwald 	{ }
5262a1efb9SPeter Meerwald };
5362a1efb9SPeter Meerwald MODULE_DEVICE_TABLE(i2c, vcnl4000_id);
5462a1efb9SPeter Meerwald 
5562a1efb9SPeter Meerwald static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask,
5662a1efb9SPeter Meerwald 				u8 rdy_mask, u8 data_reg, int *val)
5762a1efb9SPeter Meerwald {
5862a1efb9SPeter Meerwald 	int tries = 20;
5962a1efb9SPeter Meerwald 	u16 buf;
6062a1efb9SPeter Meerwald 	int ret;
6162a1efb9SPeter Meerwald 
6262a1efb9SPeter Meerwald 	ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND,
6362a1efb9SPeter Meerwald 					req_mask);
6462a1efb9SPeter Meerwald 	if (ret < 0)
6562a1efb9SPeter Meerwald 		return ret;
6662a1efb9SPeter Meerwald 
6762a1efb9SPeter Meerwald 	/* wait for data to become ready */
6862a1efb9SPeter Meerwald 	while (tries--) {
6962a1efb9SPeter Meerwald 		ret = i2c_smbus_read_byte_data(data->client, VCNL4000_COMMAND);
7062a1efb9SPeter Meerwald 		if (ret < 0)
7162a1efb9SPeter Meerwald 			return ret;
7262a1efb9SPeter Meerwald 		if (ret & rdy_mask)
7362a1efb9SPeter Meerwald 			break;
7462a1efb9SPeter Meerwald 		msleep(20); /* measurement takes up to 100 ms */
7562a1efb9SPeter Meerwald 	}
7662a1efb9SPeter Meerwald 
7762a1efb9SPeter Meerwald 	if (tries < 0) {
7862a1efb9SPeter Meerwald 		dev_err(&data->client->dev,
7962a1efb9SPeter Meerwald 			"vcnl4000_measure() failed, data not ready\n");
8062a1efb9SPeter Meerwald 		return -EIO;
8162a1efb9SPeter Meerwald 	}
8262a1efb9SPeter Meerwald 
8362a1efb9SPeter Meerwald 	ret = i2c_smbus_read_i2c_block_data(data->client,
8462a1efb9SPeter Meerwald 		data_reg, sizeof(buf), (u8 *) &buf);
8562a1efb9SPeter Meerwald 	if (ret < 0)
8662a1efb9SPeter Meerwald 		return ret;
8762a1efb9SPeter Meerwald 
8862a1efb9SPeter Meerwald 	*val = be16_to_cpu(buf);
8962a1efb9SPeter Meerwald 
9062a1efb9SPeter Meerwald 	return 0;
9162a1efb9SPeter Meerwald }
9262a1efb9SPeter Meerwald 
9362a1efb9SPeter Meerwald static const struct iio_chan_spec vcnl4000_channels[] = {
9462a1efb9SPeter Meerwald 	{
9562a1efb9SPeter Meerwald 		.type = IIO_LIGHT,
96bb7c5940SJonathan Cameron 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
97bb7c5940SJonathan Cameron 			BIT(IIO_CHAN_INFO_SCALE),
9862a1efb9SPeter Meerwald 	}, {
9962a1efb9SPeter Meerwald 		.type = IIO_PROXIMITY,
100bb7c5940SJonathan Cameron 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
10162a1efb9SPeter Meerwald 	}
10262a1efb9SPeter Meerwald };
10362a1efb9SPeter Meerwald 
10462a1efb9SPeter Meerwald static int vcnl4000_read_raw(struct iio_dev *indio_dev,
10562a1efb9SPeter Meerwald 				struct iio_chan_spec const *chan,
10662a1efb9SPeter Meerwald 				int *val, int *val2, long mask)
10762a1efb9SPeter Meerwald {
10862a1efb9SPeter Meerwald 	int ret = -EINVAL;
10962a1efb9SPeter Meerwald 	struct vcnl4000_data *data = iio_priv(indio_dev);
11062a1efb9SPeter Meerwald 
11162a1efb9SPeter Meerwald 	switch (mask) {
11262a1efb9SPeter Meerwald 	case IIO_CHAN_INFO_RAW:
11362a1efb9SPeter Meerwald 		switch (chan->type) {
11462a1efb9SPeter Meerwald 		case IIO_LIGHT:
11562a1efb9SPeter Meerwald 			ret = vcnl4000_measure(data,
11662a1efb9SPeter Meerwald 				VCNL4000_AL_OD, VCNL4000_AL_RDY,
11762a1efb9SPeter Meerwald 				VCNL4000_AL_RESULT_HI, val);
11862a1efb9SPeter Meerwald 			if (ret < 0)
11962a1efb9SPeter Meerwald 				return ret;
12062a1efb9SPeter Meerwald 			ret = IIO_VAL_INT;
12162a1efb9SPeter Meerwald 			break;
12262a1efb9SPeter Meerwald 		case IIO_PROXIMITY:
12362a1efb9SPeter Meerwald 			ret = vcnl4000_measure(data,
12462a1efb9SPeter Meerwald 				VCNL4000_PS_OD, VCNL4000_PS_RDY,
12562a1efb9SPeter Meerwald 				VCNL4000_PS_RESULT_HI, val);
12662a1efb9SPeter Meerwald 			if (ret < 0)
12762a1efb9SPeter Meerwald 				return ret;
12862a1efb9SPeter Meerwald 			ret = IIO_VAL_INT;
12962a1efb9SPeter Meerwald 			break;
13062a1efb9SPeter Meerwald 		default:
13162a1efb9SPeter Meerwald 			break;
13262a1efb9SPeter Meerwald 		}
13362a1efb9SPeter Meerwald 		break;
13462a1efb9SPeter Meerwald 	case IIO_CHAN_INFO_SCALE:
13562a1efb9SPeter Meerwald 		if (chan->type == IIO_LIGHT) {
13662a1efb9SPeter Meerwald 			*val = 0;
13762a1efb9SPeter Meerwald 			*val2 = 250000;
13862a1efb9SPeter Meerwald 			ret = IIO_VAL_INT_PLUS_MICRO;
13962a1efb9SPeter Meerwald 		}
14062a1efb9SPeter Meerwald 		break;
14162a1efb9SPeter Meerwald 	default:
14262a1efb9SPeter Meerwald 		break;
14362a1efb9SPeter Meerwald 	}
14462a1efb9SPeter Meerwald 
14562a1efb9SPeter Meerwald 	return ret;
14662a1efb9SPeter Meerwald }
14762a1efb9SPeter Meerwald 
14862a1efb9SPeter Meerwald static const struct iio_info vcnl4000_info = {
14962a1efb9SPeter Meerwald 	.read_raw = vcnl4000_read_raw,
15062a1efb9SPeter Meerwald 	.driver_module = THIS_MODULE,
15162a1efb9SPeter Meerwald };
15262a1efb9SPeter Meerwald 
153fc52692cSGreg Kroah-Hartman static int vcnl4000_probe(struct i2c_client *client,
15462a1efb9SPeter Meerwald 			  const struct i2c_device_id *id)
15562a1efb9SPeter Meerwald {
15662a1efb9SPeter Meerwald 	struct vcnl4000_data *data;
15762a1efb9SPeter Meerwald 	struct iio_dev *indio_dev;
15862a1efb9SPeter Meerwald 	int ret;
15962a1efb9SPeter Meerwald 
160*2669d723SPeter Meerwald 	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
16162a1efb9SPeter Meerwald 	if (!indio_dev)
16262a1efb9SPeter Meerwald 		return -ENOMEM;
16362a1efb9SPeter Meerwald 
16462a1efb9SPeter Meerwald 	data = iio_priv(indio_dev);
16562a1efb9SPeter Meerwald 	i2c_set_clientdata(client, indio_dev);
16662a1efb9SPeter Meerwald 	data->client = client;
16762a1efb9SPeter Meerwald 
16862a1efb9SPeter Meerwald 	ret = i2c_smbus_read_byte_data(data->client, VCNL4000_PROD_REV);
16962a1efb9SPeter Meerwald 	if (ret < 0)
170*2669d723SPeter Meerwald 		return ret;
17162a1efb9SPeter Meerwald 
17262a1efb9SPeter Meerwald 	dev_info(&client->dev, "VCNL4000 Ambient light/proximity sensor, Prod %02x, Rev: %02x\n",
17362a1efb9SPeter Meerwald 		ret >> 4, ret & 0xf);
17462a1efb9SPeter Meerwald 
17562a1efb9SPeter Meerwald 	indio_dev->dev.parent = &client->dev;
17662a1efb9SPeter Meerwald 	indio_dev->info = &vcnl4000_info;
17762a1efb9SPeter Meerwald 	indio_dev->channels = vcnl4000_channels;
17862a1efb9SPeter Meerwald 	indio_dev->num_channels = ARRAY_SIZE(vcnl4000_channels);
17962a1efb9SPeter Meerwald 	indio_dev->name = VCNL4000_DRV_NAME;
18062a1efb9SPeter Meerwald 	indio_dev->modes = INDIO_DIRECT_MODE;
18162a1efb9SPeter Meerwald 
18262a1efb9SPeter Meerwald 	ret = iio_device_register(indio_dev);
18362a1efb9SPeter Meerwald 	if (ret < 0)
184*2669d723SPeter Meerwald 		return ret;
18562a1efb9SPeter Meerwald 
18662a1efb9SPeter Meerwald 	return 0;
18762a1efb9SPeter Meerwald }
18862a1efb9SPeter Meerwald 
189fc52692cSGreg Kroah-Hartman static int vcnl4000_remove(struct i2c_client *client)
19062a1efb9SPeter Meerwald {
191*2669d723SPeter Meerwald 	iio_device_unregister(i2c_get_clientdata(client));
19262a1efb9SPeter Meerwald 	return 0;
19362a1efb9SPeter Meerwald }
19462a1efb9SPeter Meerwald 
19562a1efb9SPeter Meerwald static struct i2c_driver vcnl4000_driver = {
19662a1efb9SPeter Meerwald 	.driver = {
19762a1efb9SPeter Meerwald 		.name   = VCNL4000_DRV_NAME,
19862a1efb9SPeter Meerwald 		.owner  = THIS_MODULE,
19962a1efb9SPeter Meerwald 	},
20062a1efb9SPeter Meerwald 	.probe  = vcnl4000_probe,
201fc52692cSGreg Kroah-Hartman 	.remove = vcnl4000_remove,
20262a1efb9SPeter Meerwald 	.id_table = vcnl4000_id,
20362a1efb9SPeter Meerwald };
20462a1efb9SPeter Meerwald 
20562a1efb9SPeter Meerwald module_i2c_driver(vcnl4000_driver);
20662a1efb9SPeter Meerwald 
20762a1efb9SPeter Meerwald MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
20862a1efb9SPeter Meerwald MODULE_DESCRIPTION("Vishay VCNL4000 proximity/ambient light sensor driver");
20962a1efb9SPeter Meerwald MODULE_LICENSE("GPL");
210