linux/drivers/iio/light/vcnl4000.c
<<
>>
Prefs
   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
  60enum vcnl4000_device_ids {
  61        VCNL4000,
  62        VCNL4010,
  63        VCNL4040,
  64        VCNL4200,
  65};
  66
  67struct vcnl4200_channel {
  68        u8 reg;
  69        ktime_t last_measurement;
  70        ktime_t sampling_rate;
  71        struct mutex lock;
  72};
  73
  74struct 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
  85struct 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
  92static const struct i2c_device_id vcnl4000_id[] = {
  93        { "vcnl4000", VCNL4000 },
  94        { "vcnl4010", VCNL4010 },
  95        { "vcnl4020", VCNL4010 },
  96        { "vcnl4040", VCNL4040 },
  97        { "vcnl4200", VCNL4200 },
  98        { }
  99};
 100MODULE_DEVICE_TABLE(i2c, vcnl4000_id);
 101
 102static 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
 133static 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                /* Integration time is 50ms, but the experiments */
 171                /* show 54ms in total. */
 172                data->vcnl4200_al.sampling_rate = ktime_set(0, 54000 * 1000);
 173                data->vcnl4200_ps.sampling_rate = ktime_set(0, 4200 * 1000);
 174                data->al_scale = 24000;
 175                break;
 176        case VCNL4040_PROD_ID:
 177                /* Integration time is 80ms, add 10ms. */
 178                data->vcnl4200_al.sampling_rate = ktime_set(0, 100000 * 1000);
 179                data->vcnl4200_ps.sampling_rate = ktime_set(0, 100000 * 1000);
 180                data->al_scale = 120000;
 181                break;
 182        }
 183        data->vcnl4200_al.last_measurement = ktime_set(0, 0);
 184        data->vcnl4200_ps.last_measurement = ktime_set(0, 0);
 185        mutex_init(&data->vcnl4200_al.lock);
 186        mutex_init(&data->vcnl4200_ps.lock);
 187
 188        return 0;
 189};
 190
 191static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask,
 192                                u8 rdy_mask, u8 data_reg, int *val)
 193{
 194        int tries = 20;
 195        __be16 buf;
 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_i2c_block_data(data->client,
 223                data_reg, sizeof(buf), (u8 *) &buf);
 224        if (ret < 0)
 225                goto fail;
 226
 227        mutex_unlock(&data->vcnl4000_lock);
 228        *val = be16_to_cpu(buf);
 229
 230        return 0;
 231
 232fail:
 233        mutex_unlock(&data->vcnl4000_lock);
 234        return ret;
 235}
 236
 237static int vcnl4200_measure(struct vcnl4000_data *data,
 238                struct vcnl4200_channel *chan, int *val)
 239{
 240        int ret;
 241        s64 delta;
 242        ktime_t next_measurement;
 243
 244        mutex_lock(&chan->lock);
 245
 246        next_measurement = ktime_add(chan->last_measurement,
 247                        chan->sampling_rate);
 248        delta = ktime_us_delta(next_measurement, ktime_get());
 249        if (delta > 0)
 250                usleep_range(delta, delta + 500);
 251        chan->last_measurement = ktime_get();
 252
 253        mutex_unlock(&chan->lock);
 254
 255        ret = i2c_smbus_read_word_data(data->client, chan->reg);
 256        if (ret < 0)
 257                return ret;
 258
 259        *val = ret;
 260
 261        return 0;
 262}
 263
 264static int vcnl4000_measure_light(struct vcnl4000_data *data, int *val)
 265{
 266        return vcnl4000_measure(data,
 267                        VCNL4000_AL_OD, VCNL4000_AL_RDY,
 268                        VCNL4000_AL_RESULT_HI, val);
 269}
 270
 271static int vcnl4200_measure_light(struct vcnl4000_data *data, int *val)
 272{
 273        return vcnl4200_measure(data, &data->vcnl4200_al, val);
 274}
 275
 276static int vcnl4000_measure_proximity(struct vcnl4000_data *data, int *val)
 277{
 278        return vcnl4000_measure(data,
 279                        VCNL4000_PS_OD, VCNL4000_PS_RDY,
 280                        VCNL4000_PS_RESULT_HI, val);
 281}
 282
 283static int vcnl4200_measure_proximity(struct vcnl4000_data *data, int *val)
 284{
 285        return vcnl4200_measure(data, &data->vcnl4200_ps, val);
 286}
 287
 288static const struct vcnl4000_chip_spec vcnl4000_chip_spec_cfg[] = {
 289        [VCNL4000] = {
 290                .prod = "VCNL4000",
 291                .init = vcnl4000_init,
 292                .measure_light = vcnl4000_measure_light,
 293                .measure_proximity = vcnl4000_measure_proximity,
 294        },
 295        [VCNL4010] = {
 296                .prod = "VCNL4010/4020",
 297                .init = vcnl4000_init,
 298                .measure_light = vcnl4000_measure_light,
 299                .measure_proximity = vcnl4000_measure_proximity,
 300        },
 301        [VCNL4040] = {
 302                .prod = "VCNL4040",
 303                .init = vcnl4200_init,
 304                .measure_light = vcnl4200_measure_light,
 305                .measure_proximity = vcnl4200_measure_proximity,
 306        },
 307        [VCNL4200] = {
 308                .prod = "VCNL4200",
 309                .init = vcnl4200_init,
 310                .measure_light = vcnl4200_measure_light,
 311                .measure_proximity = vcnl4200_measure_proximity,
 312        },
 313};
 314
 315static const struct iio_chan_spec vcnl4000_channels[] = {
 316        {
 317                .type = IIO_LIGHT,
 318                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 319                        BIT(IIO_CHAN_INFO_SCALE),
 320        }, {
 321                .type = IIO_PROXIMITY,
 322                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
 323        }
 324};
 325
 326static int vcnl4000_read_raw(struct iio_dev *indio_dev,
 327                                struct iio_chan_spec const *chan,
 328                                int *val, int *val2, long mask)
 329{
 330        int ret;
 331        struct vcnl4000_data *data = iio_priv(indio_dev);
 332
 333        switch (mask) {
 334        case IIO_CHAN_INFO_RAW:
 335                switch (chan->type) {
 336                case IIO_LIGHT:
 337                        ret = data->chip_spec->measure_light(data, val);
 338                        if (ret < 0)
 339                                return ret;
 340                        return IIO_VAL_INT;
 341                case IIO_PROXIMITY:
 342                        ret = data->chip_spec->measure_proximity(data, val);
 343                        if (ret < 0)
 344                                return ret;
 345                        return IIO_VAL_INT;
 346                default:
 347                        return -EINVAL;
 348                }
 349        case IIO_CHAN_INFO_SCALE:
 350                if (chan->type != IIO_LIGHT)
 351                        return -EINVAL;
 352
 353                *val = 0;
 354                *val2 = data->al_scale;
 355                return IIO_VAL_INT_PLUS_MICRO;
 356        default:
 357                return -EINVAL;
 358        }
 359}
 360
 361static const struct iio_info vcnl4000_info = {
 362        .read_raw = vcnl4000_read_raw,
 363};
 364
 365static int vcnl4000_probe(struct i2c_client *client,
 366                          const struct i2c_device_id *id)
 367{
 368        struct vcnl4000_data *data;
 369        struct iio_dev *indio_dev;
 370        int ret;
 371
 372        indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
 373        if (!indio_dev)
 374                return -ENOMEM;
 375
 376        data = iio_priv(indio_dev);
 377        i2c_set_clientdata(client, indio_dev);
 378        data->client = client;
 379        data->id = id->driver_data;
 380        data->chip_spec = &vcnl4000_chip_spec_cfg[data->id];
 381
 382        ret = data->chip_spec->init(data);
 383        if (ret < 0)
 384                return ret;
 385
 386        dev_dbg(&client->dev, "%s Ambient light/proximity sensor, Rev: %02x\n",
 387                data->chip_spec->prod, data->rev);
 388
 389        indio_dev->dev.parent = &client->dev;
 390        indio_dev->info = &vcnl4000_info;
 391        indio_dev->channels = vcnl4000_channels;
 392        indio_dev->num_channels = ARRAY_SIZE(vcnl4000_channels);
 393        indio_dev->name = VCNL4000_DRV_NAME;
 394        indio_dev->modes = INDIO_DIRECT_MODE;
 395
 396        return devm_iio_device_register(&client->dev, indio_dev);
 397}
 398
 399static const struct of_device_id vcnl_4000_of_match[] = {
 400        {
 401                .compatible = "vishay,vcnl4000",
 402                .data = (void *)VCNL4000,
 403        },
 404        {
 405                .compatible = "vishay,vcnl4010",
 406                .data = (void *)VCNL4010,
 407        },
 408        {
 409                .compatible = "vishay,vcnl4020",
 410                .data = (void *)VCNL4010,
 411        },
 412        {
 413                .compatible = "vishay,vcnl4040",
 414                .data = (void *)VCNL4040,
 415        },
 416        {
 417                .compatible = "vishay,vcnl4200",
 418                .data = (void *)VCNL4200,
 419        },
 420        {},
 421};
 422MODULE_DEVICE_TABLE(of, vcnl_4000_of_match);
 423
 424static struct i2c_driver vcnl4000_driver = {
 425        .driver = {
 426                .name   = VCNL4000_DRV_NAME,
 427                .of_match_table = vcnl_4000_of_match,
 428        },
 429        .probe  = vcnl4000_probe,
 430        .id_table = vcnl4000_id,
 431};
 432
 433module_i2c_driver(vcnl4000_driver);
 434
 435MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
 436MODULE_DESCRIPTION("Vishay VCNL4000 proximity/ambient light sensor driver");
 437MODULE_LICENSE("GPL");
 438