linux/drivers/iio/light/vcnl4000.c
<<
>>
Prefs
   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        __be16 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_separate = BIT(IIO_CHAN_INFO_RAW) |
  97                        BIT(IIO_CHAN_INFO_SCALE),
  98        }, {
  99                .type = IIO_PROXIMITY,
 100                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
 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 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 = devm_iio_device_alloc(&client->dev, 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                return ret;
 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        return devm_iio_device_register(&client->dev, indio_dev);
 183}
 184
 185static struct i2c_driver vcnl4000_driver = {
 186        .driver = {
 187                .name   = VCNL4000_DRV_NAME,
 188                .owner  = THIS_MODULE,
 189        },
 190        .probe  = vcnl4000_probe,
 191        .id_table = vcnl4000_id,
 192};
 193
 194module_i2c_driver(vcnl4000_driver);
 195
 196MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
 197MODULE_DESCRIPTION("Vishay VCNL4000 proximity/ambient light sensor driver");
 198MODULE_LICENSE("GPL");
 199