root/drivers/iio/light/vcnl4000.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. vcnl4000_init
  2. vcnl4200_init
  3. vcnl4000_measure
  4. vcnl4200_measure
  5. vcnl4000_measure_light
  6. vcnl4200_measure_light
  7. vcnl4000_measure_proximity
  8. vcnl4200_measure_proximity
  9. vcnl4000_read_raw
  10. vcnl4000_probe

   1 // SPDX-License-Identifier: GPL-2.0-only
   2 /*
   3  * vcnl4000.c - Support for Vishay VCNL4000/4010/4020/4040/4200 combined ambient
   4  * light and proximity sensor
   5  *
   6  * Copyright 2012 Peter Meerwald <pmeerw@pmeerw.net>
   7  * Copyright 2019 Pursim SPC
   8  *
   9  * IIO driver for:
  10  *   VCNL4000/10/20 (7-bit I2C slave address 0x13)
  11  *   VCNL4040 (7-bit I2C slave address 0x60)
  12  *   VCNL4200 (7-bit I2C slave address 0x51)
  13  *
  14  * TODO:
  15  *   allow to adjust IR current
  16  *   proximity threshold and event handling
  17  *   periodic ALS/proximity measurement (VCNL4010/20)
  18  *   interrupts (VCNL4010/20/40, VCNL4200)
  19  */
  20 
  21 #include <linux/module.h>
  22 #include <linux/i2c.h>
  23 #include <linux/err.h>
  24 #include <linux/delay.h>
  25 
  26 #include <linux/iio/iio.h>
  27 #include <linux/iio/sysfs.h>
  28 
  29 #define VCNL4000_DRV_NAME "vcnl4000"
  30 #define VCNL4000_PROD_ID        0x01
  31 #define VCNL4010_PROD_ID        0x02 /* for VCNL4020, VCNL4010 */
  32 #define VCNL4040_PROD_ID        0x86
  33 #define VCNL4200_PROD_ID        0x58
  34 
  35 #define VCNL4000_COMMAND        0x80 /* Command register */
  36 #define VCNL4000_PROD_REV       0x81 /* Product ID and Revision ID */
  37 #define VCNL4000_LED_CURRENT    0x83 /* IR LED current for proximity mode */
  38 #define VCNL4000_AL_PARAM       0x84 /* Ambient light parameter register */
  39 #define VCNL4000_AL_RESULT_HI   0x85 /* Ambient light result register, MSB */
  40 #define VCNL4000_AL_RESULT_LO   0x86 /* Ambient light result register, LSB */
  41 #define VCNL4000_PS_RESULT_HI   0x87 /* Proximity result register, MSB */
  42 #define VCNL4000_PS_RESULT_LO   0x88 /* Proximity result register, LSB */
  43 #define VCNL4000_PS_MEAS_FREQ   0x89 /* Proximity test signal frequency */
  44 #define VCNL4000_PS_MOD_ADJ     0x8a /* Proximity modulator timing adjustment */
  45 
  46 #define VCNL4200_AL_CONF        0x00 /* Ambient light configuration */
  47 #define VCNL4200_PS_CONF1       0x03 /* Proximity configuration */
  48 #define VCNL4200_PS_DATA        0x08 /* Proximity data */
  49 #define VCNL4200_AL_DATA        0x09 /* Ambient light data */
  50 #define VCNL4200_DEV_ID         0x0e /* Device ID, slave address and version */
  51 
  52 #define VCNL4040_DEV_ID         0x0c /* Device ID and version */
  53 
  54 /* Bit masks for COMMAND register */
  55 #define VCNL4000_AL_RDY         BIT(6) /* ALS data ready? */
  56 #define VCNL4000_PS_RDY         BIT(5) /* proximity data ready? */
  57 #define VCNL4000_AL_OD          BIT(4) /* start on-demand ALS measurement */
  58 #define VCNL4000_PS_OD          BIT(3) /* start on-demand proximity measurement */
  59 
  60 enum vcnl4000_device_ids {
  61         VCNL4000,
  62         VCNL4010,
  63         VCNL4040,
  64         VCNL4200,
  65 };
  66 
  67 struct vcnl4200_channel {
  68         u8 reg;
  69         ktime_t last_measurement;
  70         ktime_t sampling_rate;
  71         struct mutex lock;
  72 };
  73 
  74 struct vcnl4000_data {
  75         struct i2c_client *client;
  76         enum vcnl4000_device_ids id;
  77         int rev;
  78         int al_scale;
  79         const struct vcnl4000_chip_spec *chip_spec;
  80         struct mutex vcnl4000_lock;
  81         struct vcnl4200_channel vcnl4200_al;
  82         struct vcnl4200_channel vcnl4200_ps;
  83 };
  84 
  85 struct vcnl4000_chip_spec {
  86         const char *prod;
  87         int (*init)(struct vcnl4000_data *data);
  88         int (*measure_light)(struct vcnl4000_data *data, int *val);
  89         int (*measure_proximity)(struct vcnl4000_data *data, int *val);
  90 };
  91 
  92 static const struct i2c_device_id vcnl4000_id[] = {
  93         { "vcnl4000", VCNL4000 },
  94         { "vcnl4010", VCNL4010 },
  95         { "vcnl4020", VCNL4010 },
  96         { "vcnl4040", VCNL4040 },
  97         { "vcnl4200", VCNL4200 },
  98         { }
  99 };
 100 MODULE_DEVICE_TABLE(i2c, vcnl4000_id);
 101 
 102 static int vcnl4000_init(struct vcnl4000_data *data)
 103 {
 104         int ret, prod_id;
 105 
 106         ret = i2c_smbus_read_byte_data(data->client, VCNL4000_PROD_REV);
 107         if (ret < 0)
 108                 return ret;
 109 
 110         prod_id = ret >> 4;
 111         switch (prod_id) {
 112         case VCNL4000_PROD_ID:
 113                 if (data->id != VCNL4000)
 114                         dev_warn(&data->client->dev,
 115                                         "wrong device id, use vcnl4000");
 116                 break;
 117         case VCNL4010_PROD_ID:
 118                 if (data->id != VCNL4010)
 119                         dev_warn(&data->client->dev,
 120                                         "wrong device id, use vcnl4010/4020");
 121                 break;
 122         default:
 123                 return -ENODEV;
 124         }
 125 
 126         data->rev = ret & 0xf;
 127         data->al_scale = 250000;
 128         mutex_init(&data->vcnl4000_lock);
 129 
 130         return 0;
 131 };
 132 
 133 static int vcnl4200_init(struct vcnl4000_data *data)
 134 {
 135         int ret, id;
 136 
 137         ret = i2c_smbus_read_word_data(data->client, VCNL4200_DEV_ID);
 138         if (ret < 0)
 139                 return ret;
 140 
 141         id = ret & 0xff;
 142 
 143         if (id != VCNL4200_PROD_ID) {
 144                 ret = i2c_smbus_read_word_data(data->client, VCNL4040_DEV_ID);
 145                 if (ret < 0)
 146                         return ret;
 147 
 148                 id = ret & 0xff;
 149 
 150                 if (id != VCNL4040_PROD_ID)
 151                         return -ENODEV;
 152         }
 153 
 154         dev_dbg(&data->client->dev, "device id 0x%x", id);
 155 
 156         data->rev = (ret >> 8) & 0xf;
 157 
 158         /* Set defaults and enable both channels */
 159         ret = i2c_smbus_write_word_data(data->client, VCNL4200_AL_CONF, 0);
 160         if (ret < 0)
 161                 return ret;
 162         ret = i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF1, 0);
 163         if (ret < 0)
 164                 return ret;
 165 
 166         data->vcnl4200_al.reg = VCNL4200_AL_DATA;
 167         data->vcnl4200_ps.reg = VCNL4200_PS_DATA;
 168         switch (id) {
 169         case VCNL4200_PROD_ID:
 170                 /* Default wait time is 50ms, add 20% tolerance. */
 171                 data->vcnl4200_al.sampling_rate = ktime_set(0, 60000 * 1000);
 172                 /* Default wait time is 4.8ms, add 20% tolerance. */
 173                 data->vcnl4200_ps.sampling_rate = ktime_set(0, 5760 * 1000);
 174                 data->al_scale = 24000;
 175                 break;
 176         case VCNL4040_PROD_ID:
 177                 /* Default wait time is 80ms, add 20% tolerance. */
 178                 data->vcnl4200_al.sampling_rate = ktime_set(0, 96000 * 1000);
 179                 /* Default wait time is 5ms, add 20% tolerance. */
 180                 data->vcnl4200_ps.sampling_rate = ktime_set(0, 6000 * 1000);
 181                 data->al_scale = 120000;
 182                 break;
 183         }
 184         data->vcnl4200_al.last_measurement = ktime_set(0, 0);
 185         data->vcnl4200_ps.last_measurement = ktime_set(0, 0);
 186         mutex_init(&data->vcnl4200_al.lock);
 187         mutex_init(&data->vcnl4200_ps.lock);
 188 
 189         return 0;
 190 };
 191 
 192 static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask,
 193                                 u8 rdy_mask, u8 data_reg, int *val)
 194 {
 195         int tries = 20;
 196         int ret;
 197 
 198         mutex_lock(&data->vcnl4000_lock);
 199 
 200         ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND,
 201                                         req_mask);
 202         if (ret < 0)
 203                 goto fail;
 204 
 205         /* wait for data to become ready */
 206         while (tries--) {
 207                 ret = i2c_smbus_read_byte_data(data->client, VCNL4000_COMMAND);
 208                 if (ret < 0)
 209                         goto fail;
 210                 if (ret & rdy_mask)
 211                         break;
 212                 msleep(20); /* measurement takes up to 100 ms */
 213         }
 214 
 215         if (tries < 0) {
 216                 dev_err(&data->client->dev,
 217                         "vcnl4000_measure() failed, data not ready\n");
 218                 ret = -EIO;
 219                 goto fail;
 220         }
 221 
 222         ret = i2c_smbus_read_word_swapped(data->client, data_reg);
 223         if (ret < 0)
 224                 goto fail;
 225 
 226         mutex_unlock(&data->vcnl4000_lock);
 227         *val = ret;
 228 
 229         return 0;
 230 
 231 fail:
 232         mutex_unlock(&data->vcnl4000_lock);
 233         return ret;
 234 }
 235 
 236 static int vcnl4200_measure(struct vcnl4000_data *data,
 237                 struct vcnl4200_channel *chan, int *val)
 238 {
 239         int ret;
 240         s64 delta;
 241         ktime_t next_measurement;
 242 
 243         mutex_lock(&chan->lock);
 244 
 245         next_measurement = ktime_add(chan->last_measurement,
 246                         chan->sampling_rate);
 247         delta = ktime_us_delta(next_measurement, ktime_get());
 248         if (delta > 0)
 249                 usleep_range(delta, delta + 500);
 250         chan->last_measurement = ktime_get();
 251 
 252         mutex_unlock(&chan->lock);
 253 
 254         ret = i2c_smbus_read_word_data(data->client, chan->reg);
 255         if (ret < 0)
 256                 return ret;
 257 
 258         *val = ret;
 259 
 260         return 0;
 261 }
 262 
 263 static int vcnl4000_measure_light(struct vcnl4000_data *data, int *val)
 264 {
 265         return vcnl4000_measure(data,
 266                         VCNL4000_AL_OD, VCNL4000_AL_RDY,
 267                         VCNL4000_AL_RESULT_HI, val);
 268 }
 269 
 270 static int vcnl4200_measure_light(struct vcnl4000_data *data, int *val)
 271 {
 272         return vcnl4200_measure(data, &data->vcnl4200_al, val);
 273 }
 274 
 275 static int vcnl4000_measure_proximity(struct vcnl4000_data *data, int *val)
 276 {
 277         return vcnl4000_measure(data,
 278                         VCNL4000_PS_OD, VCNL4000_PS_RDY,
 279                         VCNL4000_PS_RESULT_HI, val);
 280 }
 281 
 282 static int vcnl4200_measure_proximity(struct vcnl4000_data *data, int *val)
 283 {
 284         return vcnl4200_measure(data, &data->vcnl4200_ps, val);
 285 }
 286 
 287 static const struct vcnl4000_chip_spec vcnl4000_chip_spec_cfg[] = {
 288         [VCNL4000] = {
 289                 .prod = "VCNL4000",
 290                 .init = vcnl4000_init,
 291                 .measure_light = vcnl4000_measure_light,
 292                 .measure_proximity = vcnl4000_measure_proximity,
 293         },
 294         [VCNL4010] = {
 295                 .prod = "VCNL4010/4020",
 296                 .init = vcnl4000_init,
 297                 .measure_light = vcnl4000_measure_light,
 298                 .measure_proximity = vcnl4000_measure_proximity,
 299         },
 300         [VCNL4040] = {
 301                 .prod = "VCNL4040",
 302                 .init = vcnl4200_init,
 303                 .measure_light = vcnl4200_measure_light,
 304                 .measure_proximity = vcnl4200_measure_proximity,
 305         },
 306         [VCNL4200] = {
 307                 .prod = "VCNL4200",
 308                 .init = vcnl4200_init,
 309                 .measure_light = vcnl4200_measure_light,
 310                 .measure_proximity = vcnl4200_measure_proximity,
 311         },
 312 };
 313 
 314 static const struct iio_chan_spec vcnl4000_channels[] = {
 315         {
 316                 .type = IIO_LIGHT,
 317                 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 318                         BIT(IIO_CHAN_INFO_SCALE),
 319         }, {
 320                 .type = IIO_PROXIMITY,
 321                 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
 322         }
 323 };
 324 
 325 static int vcnl4000_read_raw(struct iio_dev *indio_dev,
 326                                 struct iio_chan_spec const *chan,
 327                                 int *val, int *val2, long mask)
 328 {
 329         int ret;
 330         struct vcnl4000_data *data = iio_priv(indio_dev);
 331 
 332         switch (mask) {
 333         case IIO_CHAN_INFO_RAW:
 334                 switch (chan->type) {
 335                 case IIO_LIGHT:
 336                         ret = data->chip_spec->measure_light(data, val);
 337                         if (ret < 0)
 338                                 return ret;
 339                         return IIO_VAL_INT;
 340                 case IIO_PROXIMITY:
 341                         ret = data->chip_spec->measure_proximity(data, val);
 342                         if (ret < 0)
 343                                 return ret;
 344                         return IIO_VAL_INT;
 345                 default:
 346                         return -EINVAL;
 347                 }
 348         case IIO_CHAN_INFO_SCALE:
 349                 if (chan->type != IIO_LIGHT)
 350                         return -EINVAL;
 351 
 352                 *val = 0;
 353                 *val2 = data->al_scale;
 354                 return IIO_VAL_INT_PLUS_MICRO;
 355         default:
 356                 return -EINVAL;
 357         }
 358 }
 359 
 360 static const struct iio_info vcnl4000_info = {
 361         .read_raw = vcnl4000_read_raw,
 362 };
 363 
 364 static int vcnl4000_probe(struct i2c_client *client,
 365                           const struct i2c_device_id *id)
 366 {
 367         struct vcnl4000_data *data;
 368         struct iio_dev *indio_dev;
 369         int ret;
 370 
 371         indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
 372         if (!indio_dev)
 373                 return -ENOMEM;
 374 
 375         data = iio_priv(indio_dev);
 376         i2c_set_clientdata(client, indio_dev);
 377         data->client = client;
 378         data->id = id->driver_data;
 379         data->chip_spec = &vcnl4000_chip_spec_cfg[data->id];
 380 
 381         ret = data->chip_spec->init(data);
 382         if (ret < 0)
 383                 return ret;
 384 
 385         dev_dbg(&client->dev, "%s Ambient light/proximity sensor, Rev: %02x\n",
 386                 data->chip_spec->prod, data->rev);
 387 
 388         indio_dev->dev.parent = &client->dev;
 389         indio_dev->info = &vcnl4000_info;
 390         indio_dev->channels = vcnl4000_channels;
 391         indio_dev->num_channels = ARRAY_SIZE(vcnl4000_channels);
 392         indio_dev->name = VCNL4000_DRV_NAME;
 393         indio_dev->modes = INDIO_DIRECT_MODE;
 394 
 395         return devm_iio_device_register(&client->dev, indio_dev);
 396 }
 397 
 398 static const struct of_device_id vcnl_4000_of_match[] = {
 399         {
 400                 .compatible = "vishay,vcnl4000",
 401                 .data = (void *)VCNL4000,
 402         },
 403         {
 404                 .compatible = "vishay,vcnl4010",
 405                 .data = (void *)VCNL4010,
 406         },
 407         {
 408                 .compatible = "vishay,vcnl4020",
 409                 .data = (void *)VCNL4010,
 410         },
 411         {
 412                 .compatible = "vishay,vcnl4040",
 413                 .data = (void *)VCNL4040,
 414         },
 415         {
 416                 .compatible = "vishay,vcnl4200",
 417                 .data = (void *)VCNL4200,
 418         },
 419         {},
 420 };
 421 MODULE_DEVICE_TABLE(of, vcnl_4000_of_match);
 422 
 423 static struct i2c_driver vcnl4000_driver = {
 424         .driver = {
 425                 .name   = VCNL4000_DRV_NAME,
 426                 .of_match_table = vcnl_4000_of_match,
 427         },
 428         .probe  = vcnl4000_probe,
 429         .id_table = vcnl4000_id,
 430 };
 431 
 432 module_i2c_driver(vcnl4000_driver);
 433 
 434 MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
 435 MODULE_DESCRIPTION("Vishay VCNL4000 proximity/ambient light sensor driver");
 436 MODULE_LICENSE("GPL");

/* [<][>][^][v][top][bottom][index][help] */