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