672 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Support for Vishay VCNL3020 proximity sensor on i2c bus.4 * Based on Vishay VCNL4000 driver code.5 */6 7#include <linux/module.h>8#include <linux/i2c.h>9#include <linux/err.h>10#include <linux/delay.h>11#include <linux/regmap.h>12#include <linux/interrupt.h>13 14#include <linux/iio/iio.h>15#include <linux/iio/events.h>16 17#define VCNL3020_PROD_ID 0x2118 19#define VCNL_COMMAND 0x80 /* Command register */20#define VCNL_PROD_REV 0x81 /* Product ID and Revision ID */21#define VCNL_PROXIMITY_RATE 0x82 /* Rate of Proximity Measurement */22#define VCNL_LED_CURRENT 0x83 /* IR LED current for proximity mode */23#define VCNL_PS_RESULT_HI 0x87 /* Proximity result register, MSB */24#define VCNL_PS_RESULT_LO 0x88 /* Proximity result register, LSB */25#define VCNL_PS_ICR 0x89 /* Interrupt Control Register */26#define VCNL_PS_LO_THR_HI 0x8a /* High byte of low threshold value */27#define VCNL_PS_LO_THR_LO 0x8b /* Low byte of low threshold value */28#define VCNL_PS_HI_THR_HI 0x8c /* High byte of high threshold value */29#define VCNL_PS_HI_THR_LO 0x8d /* Low byte of high threshold value */30#define VCNL_ISR 0x8e /* Interrupt Status Register */31#define VCNL_PS_MOD_ADJ 0x8f /* Proximity Modulator Timing Adjustment */32 33/* Bit masks for COMMAND register */34#define VCNL_PS_RDY BIT(5) /* proximity data ready? */35#define VCNL_PS_OD BIT(3) /* start on-demand proximity36 * measurement37 */38 39/* Enables periodic proximity measurement */40#define VCNL_PS_EN BIT(1)41 42/* Enables state machine and LP oscillator for self timed measurements */43#define VCNL_PS_SELFTIMED_EN BIT(0)44 45/* Bit masks for ICR */46 47/* Enable interrupts on low or high thresholds */48#define VCNL_ICR_THRES_EN BIT(1)49 50/* Bit masks for ISR */51#define VCNL_INT_TH_HI BIT(0) /* High threshold hit */52#define VCNL_INT_TH_LOW BIT(1) /* Low threshold hit */53 54#define VCNL_ON_DEMAND_TIMEOUT_US 10000055#define VCNL_POLL_US 2000056 57static const int vcnl3020_prox_sampling_frequency[][2] = {58 {1, 950000},59 {3, 906250},60 {7, 812500},61 {16, 625000},62 {31, 250000},63 {62, 500000},64 {125, 0},65 {250, 0},66};67 68/**69 * struct vcnl3020_data - vcnl3020 specific data.70 * @regmap: device register map.71 * @dev: vcnl3020 device.72 * @rev: revision id.73 * @lock: lock for protecting access to device hardware registers.74 * @buf: __be16 buffer.75 */76struct vcnl3020_data {77 struct regmap *regmap;78 struct device *dev;79 u8 rev;80 struct mutex lock;81 __be16 buf;82};83 84/**85 * struct vcnl3020_property - vcnl3020 property.86 * @name: property name.87 * @reg: i2c register offset.88 * @conversion_func: conversion function.89 */90struct vcnl3020_property {91 const char *name;92 u32 reg;93 u32 (*conversion_func)(u32 *val);94};95 96static u32 microamp_to_reg(u32 *val)97{98 /*99 * An example of conversion from uA to reg val:100 * 200000 uA == 200 mA == 20101 */102 return *val /= 10000;103};104 105static struct vcnl3020_property vcnl3020_led_current_property = {106 .name = "vishay,led-current-microamp",107 .reg = VCNL_LED_CURRENT,108 .conversion_func = microamp_to_reg,109};110 111static int vcnl3020_get_and_apply_property(struct vcnl3020_data *data,112 struct vcnl3020_property prop)113{114 int rc;115 u32 val;116 117 rc = device_property_read_u32(data->dev, prop.name, &val);118 if (rc)119 return 0;120 121 if (prop.conversion_func)122 prop.conversion_func(&val);123 124 rc = regmap_write(data->regmap, prop.reg, val);125 if (rc) {126 dev_err(data->dev, "Error (%d) setting property (%s)\n",127 rc, prop.name);128 }129 130 return rc;131}132 133static int vcnl3020_init(struct vcnl3020_data *data)134{135 int rc;136 unsigned int reg;137 138 rc = regmap_read(data->regmap, VCNL_PROD_REV, ®);139 if (rc) {140 dev_err(data->dev,141 "Error (%d) reading product revision\n", rc);142 return rc;143 }144 145 if (reg != VCNL3020_PROD_ID) {146 dev_err(data->dev,147 "Product id (%x) did not match vcnl3020 (%x)\n", reg,148 VCNL3020_PROD_ID);149 return -ENODEV;150 }151 152 data->rev = reg;153 mutex_init(&data->lock);154 155 return vcnl3020_get_and_apply_property(data,156 vcnl3020_led_current_property);157};158 159static bool vcnl3020_is_in_periodic_mode(struct vcnl3020_data *data)160{161 int rc;162 unsigned int cmd;163 164 rc = regmap_read(data->regmap, VCNL_COMMAND, &cmd);165 if (rc) {166 dev_err(data->dev,167 "Error (%d) reading command register\n", rc);168 return false;169 }170 171 return !!(cmd & VCNL_PS_SELFTIMED_EN);172}173 174static int vcnl3020_measure_proximity(struct vcnl3020_data *data, int *val)175{176 int rc;177 unsigned int reg;178 179 mutex_lock(&data->lock);180 181 /* Protect against event capture. */182 if (vcnl3020_is_in_periodic_mode(data)) {183 rc = -EBUSY;184 goto err_unlock;185 }186 187 rc = regmap_write(data->regmap, VCNL_COMMAND, VCNL_PS_OD);188 if (rc)189 goto err_unlock;190 191 /* wait for data to become ready */192 rc = regmap_read_poll_timeout(data->regmap, VCNL_COMMAND, reg,193 reg & VCNL_PS_RDY, VCNL_POLL_US,194 VCNL_ON_DEMAND_TIMEOUT_US);195 if (rc) {196 dev_err(data->dev,197 "Error (%d) reading vcnl3020 command register\n", rc);198 goto err_unlock;199 }200 201 /* high & low result bytes read */202 rc = regmap_bulk_read(data->regmap, VCNL_PS_RESULT_HI, &data->buf,203 sizeof(data->buf));204 if (rc)205 goto err_unlock;206 207 *val = be16_to_cpu(data->buf);208 209err_unlock:210 mutex_unlock(&data->lock);211 212 return rc;213}214 215static int vcnl3020_read_proxy_samp_freq(struct vcnl3020_data *data, int *val,216 int *val2)217{218 int rc;219 unsigned int prox_rate;220 221 rc = regmap_read(data->regmap, VCNL_PROXIMITY_RATE, &prox_rate);222 if (rc)223 return rc;224 225 if (prox_rate >= ARRAY_SIZE(vcnl3020_prox_sampling_frequency))226 return -EINVAL;227 228 *val = vcnl3020_prox_sampling_frequency[prox_rate][0];229 *val2 = vcnl3020_prox_sampling_frequency[prox_rate][1];230 231 return 0;232}233 234static int vcnl3020_write_proxy_samp_freq(struct vcnl3020_data *data, int val,235 int val2)236{237 unsigned int i;238 int index = -1;239 int rc;240 241 mutex_lock(&data->lock);242 243 /* Protect against event capture. */244 if (vcnl3020_is_in_periodic_mode(data)) {245 rc = -EBUSY;246 goto err_unlock;247 }248 249 for (i = 0; i < ARRAY_SIZE(vcnl3020_prox_sampling_frequency); i++) {250 if (val == vcnl3020_prox_sampling_frequency[i][0] &&251 val2 == vcnl3020_prox_sampling_frequency[i][1]) {252 index = i;253 break;254 }255 }256 257 if (index < 0) {258 rc = -EINVAL;259 goto err_unlock;260 }261 262 rc = regmap_write(data->regmap, VCNL_PROXIMITY_RATE, index);263 if (rc)264 dev_err(data->dev,265 "Error (%d) writing proximity rate register\n", rc);266 267err_unlock:268 mutex_unlock(&data->lock);269 270 return rc;271}272 273static bool vcnl3020_is_thr_enabled(struct vcnl3020_data *data)274{275 int rc;276 unsigned int icr;277 278 rc = regmap_read(data->regmap, VCNL_PS_ICR, &icr);279 if (rc) {280 dev_err(data->dev,281 "Error (%d) reading ICR register\n", rc);282 return false;283 }284 285 return !!(icr & VCNL_ICR_THRES_EN);286}287 288static int vcnl3020_read_event(struct iio_dev *indio_dev,289 const struct iio_chan_spec *chan,290 enum iio_event_type type,291 enum iio_event_direction dir,292 enum iio_event_info info,293 int *val, int *val2)294{295 int rc;296 struct vcnl3020_data *data = iio_priv(indio_dev);297 298 switch (info) {299 case IIO_EV_INFO_VALUE:300 switch (dir) {301 case IIO_EV_DIR_RISING:302 rc = regmap_bulk_read(data->regmap, VCNL_PS_HI_THR_HI,303 &data->buf, sizeof(data->buf));304 if (rc < 0)305 return rc;306 *val = be16_to_cpu(data->buf);307 return IIO_VAL_INT;308 case IIO_EV_DIR_FALLING:309 rc = regmap_bulk_read(data->regmap, VCNL_PS_LO_THR_HI,310 &data->buf, sizeof(data->buf));311 if (rc < 0)312 return rc;313 *val = be16_to_cpu(data->buf);314 return IIO_VAL_INT;315 default:316 return -EINVAL;317 }318 default:319 return -EINVAL;320 }321}322 323static int vcnl3020_write_event(struct iio_dev *indio_dev,324 const struct iio_chan_spec *chan,325 enum iio_event_type type,326 enum iio_event_direction dir,327 enum iio_event_info info,328 int val, int val2)329{330 int rc;331 struct vcnl3020_data *data = iio_priv(indio_dev);332 333 mutex_lock(&data->lock);334 335 switch (info) {336 case IIO_EV_INFO_VALUE:337 switch (dir) {338 case IIO_EV_DIR_RISING:339 /* 16 bit word/ low * high */340 data->buf = cpu_to_be16(val);341 rc = regmap_bulk_write(data->regmap, VCNL_PS_HI_THR_HI,342 &data->buf, sizeof(data->buf));343 if (rc < 0)344 goto err_unlock;345 rc = IIO_VAL_INT;346 goto err_unlock;347 case IIO_EV_DIR_FALLING:348 data->buf = cpu_to_be16(val);349 rc = regmap_bulk_write(data->regmap, VCNL_PS_LO_THR_HI,350 &data->buf, sizeof(data->buf));351 if (rc < 0)352 goto err_unlock;353 rc = IIO_VAL_INT;354 goto err_unlock;355 default:356 rc = -EINVAL;357 goto err_unlock;358 }359 default:360 rc = -EINVAL;361 goto err_unlock;362 }363err_unlock:364 mutex_unlock(&data->lock);365 366 return rc;367}368 369static int vcnl3020_enable_periodic(struct iio_dev *indio_dev,370 struct vcnl3020_data *data)371{372 int rc;373 int cmd;374 375 mutex_lock(&data->lock);376 377 /* Enable periodic measurement of proximity data. */378 cmd = VCNL_PS_EN | VCNL_PS_SELFTIMED_EN;379 380 rc = regmap_write(data->regmap, VCNL_COMMAND, cmd);381 if (rc) {382 dev_err(data->dev,383 "Error (%d) writing command register\n", rc);384 goto err_unlock;385 }386 387 /*388 * Enable interrupts on threshold, for proximity data by389 * default.390 */391 rc = regmap_write(data->regmap, VCNL_PS_ICR, VCNL_ICR_THRES_EN);392 if (rc)393 dev_err(data->dev,394 "Error (%d) reading ICR register\n", rc);395 396err_unlock:397 mutex_unlock(&data->lock);398 399 return rc;400}401 402static int vcnl3020_disable_periodic(struct iio_dev *indio_dev,403 struct vcnl3020_data *data)404{405 int rc;406 407 mutex_lock(&data->lock);408 409 rc = regmap_write(data->regmap, VCNL_COMMAND, 0);410 if (rc) {411 dev_err(data->dev,412 "Error (%d) writing command register\n", rc);413 goto err_unlock;414 }415 416 rc = regmap_write(data->regmap, VCNL_PS_ICR, 0);417 if (rc) {418 dev_err(data->dev,419 "Error (%d) writing ICR register\n", rc);420 goto err_unlock;421 }422 423 /* Clear interrupt flag bit */424 rc = regmap_write(data->regmap, VCNL_ISR, 0);425 if (rc)426 dev_err(data->dev,427 "Error (%d) writing ISR register\n", rc);428 429err_unlock:430 mutex_unlock(&data->lock);431 432 return rc;433}434 435static int vcnl3020_config_threshold(struct iio_dev *indio_dev, bool state)436{437 struct vcnl3020_data *data = iio_priv(indio_dev);438 439 if (state) {440 return vcnl3020_enable_periodic(indio_dev, data);441 } else {442 if (!vcnl3020_is_thr_enabled(data))443 return 0;444 return vcnl3020_disable_periodic(indio_dev, data);445 }446}447 448static int vcnl3020_write_event_config(struct iio_dev *indio_dev,449 const struct iio_chan_spec *chan,450 enum iio_event_type type,451 enum iio_event_direction dir,452 int state)453{454 switch (chan->type) {455 case IIO_PROXIMITY:456 return vcnl3020_config_threshold(indio_dev, state);457 default:458 return -EINVAL;459 }460}461 462static int vcnl3020_read_event_config(struct iio_dev *indio_dev,463 const struct iio_chan_spec *chan,464 enum iio_event_type type,465 enum iio_event_direction dir)466{467 struct vcnl3020_data *data = iio_priv(indio_dev);468 469 switch (chan->type) {470 case IIO_PROXIMITY:471 return vcnl3020_is_thr_enabled(data);472 default:473 return -EINVAL;474 }475}476 477static const struct iio_event_spec vcnl3020_event_spec[] = {478 {479 .type = IIO_EV_TYPE_THRESH,480 .dir = IIO_EV_DIR_RISING,481 .mask_separate = BIT(IIO_EV_INFO_VALUE),482 }, {483 .type = IIO_EV_TYPE_THRESH,484 .dir = IIO_EV_DIR_FALLING,485 .mask_separate = BIT(IIO_EV_INFO_VALUE),486 }, {487 .type = IIO_EV_TYPE_THRESH,488 .dir = IIO_EV_DIR_EITHER,489 .mask_separate = BIT(IIO_EV_INFO_ENABLE),490 },491};492 493static const struct iio_chan_spec vcnl3020_channels[] = {494 {495 .type = IIO_PROXIMITY,496 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |497 BIT(IIO_CHAN_INFO_SAMP_FREQ),498 .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),499 .event_spec = vcnl3020_event_spec,500 .num_event_specs = ARRAY_SIZE(vcnl3020_event_spec),501 },502};503 504static int vcnl3020_read_raw(struct iio_dev *indio_dev,505 struct iio_chan_spec const *chan, int *val,506 int *val2, long mask)507{508 int rc;509 struct vcnl3020_data *data = iio_priv(indio_dev);510 511 switch (mask) {512 case IIO_CHAN_INFO_RAW:513 rc = vcnl3020_measure_proximity(data, val);514 if (rc)515 return rc;516 return IIO_VAL_INT;517 case IIO_CHAN_INFO_SAMP_FREQ:518 rc = vcnl3020_read_proxy_samp_freq(data, val, val2);519 if (rc < 0)520 return rc;521 return IIO_VAL_INT_PLUS_MICRO;522 default:523 return -EINVAL;524 }525}526 527static int vcnl3020_write_raw(struct iio_dev *indio_dev,528 struct iio_chan_spec const *chan,529 int val, int val2, long mask)530{531 struct vcnl3020_data *data = iio_priv(indio_dev);532 533 switch (mask) {534 case IIO_CHAN_INFO_SAMP_FREQ:535 return vcnl3020_write_proxy_samp_freq(data, val, val2);536 default:537 return -EINVAL;538 }539}540 541static int vcnl3020_read_avail(struct iio_dev *indio_dev,542 struct iio_chan_spec const *chan,543 const int **vals, int *type, int *length,544 long mask)545{546 switch (mask) {547 case IIO_CHAN_INFO_SAMP_FREQ:548 *vals = (int *)vcnl3020_prox_sampling_frequency;549 *type = IIO_VAL_INT_PLUS_MICRO;550 *length = 2 * ARRAY_SIZE(vcnl3020_prox_sampling_frequency);551 return IIO_AVAIL_LIST;552 default:553 return -EINVAL;554 }555}556 557static const struct iio_info vcnl3020_info = {558 .read_raw = vcnl3020_read_raw,559 .write_raw = vcnl3020_write_raw,560 .read_avail = vcnl3020_read_avail,561 .read_event_value = vcnl3020_read_event,562 .write_event_value = vcnl3020_write_event,563 .read_event_config = vcnl3020_read_event_config,564 .write_event_config = vcnl3020_write_event_config,565};566 567static const struct regmap_config vcnl3020_regmap_config = {568 .reg_bits = 8,569 .val_bits = 8,570 .max_register = VCNL_PS_MOD_ADJ,571};572 573static irqreturn_t vcnl3020_handle_irq_thread(int irq, void *p)574{575 struct iio_dev *indio_dev = p;576 struct vcnl3020_data *data = iio_priv(indio_dev);577 unsigned int isr;578 int rc;579 580 rc = regmap_read(data->regmap, VCNL_ISR, &isr);581 if (rc) {582 dev_err(data->dev, "Error (%d) reading reg (0x%x)\n",583 rc, VCNL_ISR);584 return IRQ_HANDLED;585 }586 587 if (!(isr & VCNL_ICR_THRES_EN))588 return IRQ_NONE;589 590 iio_push_event(indio_dev,591 IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1,592 IIO_EV_TYPE_THRESH,593 IIO_EV_DIR_RISING),594 iio_get_time_ns(indio_dev));595 596 rc = regmap_write(data->regmap, VCNL_ISR, isr & VCNL_ICR_THRES_EN);597 if (rc)598 dev_err(data->dev, "Error (%d) writing in reg (0x%x)\n",599 rc, VCNL_ISR);600 601 return IRQ_HANDLED;602}603 604static int vcnl3020_probe(struct i2c_client *client)605{606 struct vcnl3020_data *data;607 struct iio_dev *indio_dev;608 struct regmap *regmap;609 int rc;610 611 regmap = devm_regmap_init_i2c(client, &vcnl3020_regmap_config);612 if (IS_ERR(regmap)) {613 dev_err(&client->dev, "regmap_init failed\n");614 return PTR_ERR(regmap);615 }616 617 indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));618 if (!indio_dev)619 return -ENOMEM;620 621 data = iio_priv(indio_dev);622 i2c_set_clientdata(client, indio_dev);623 data->regmap = regmap;624 data->dev = &client->dev;625 626 rc = vcnl3020_init(data);627 if (rc)628 return rc;629 630 indio_dev->info = &vcnl3020_info;631 indio_dev->channels = vcnl3020_channels;632 indio_dev->num_channels = ARRAY_SIZE(vcnl3020_channels);633 indio_dev->name = "vcnl3020";634 indio_dev->modes = INDIO_DIRECT_MODE;635 636 if (client->irq) {637 rc = devm_request_threaded_irq(&client->dev, client->irq,638 NULL, vcnl3020_handle_irq_thread,639 IRQF_ONESHOT, indio_dev->name,640 indio_dev);641 if (rc) {642 dev_err(&client->dev,643 "Error (%d) irq request failed (%u)\n", rc,644 client->irq);645 return rc;646 }647 }648 649 return devm_iio_device_register(&client->dev, indio_dev);650}651 652static const struct of_device_id vcnl3020_of_match[] = {653 {654 .compatible = "vishay,vcnl3020",655 },656 {}657};658MODULE_DEVICE_TABLE(of, vcnl3020_of_match);659 660static struct i2c_driver vcnl3020_driver = {661 .driver = {662 .name = "vcnl3020",663 .of_match_table = vcnl3020_of_match,664 },665 .probe = vcnl3020_probe,666};667module_i2c_driver(vcnl3020_driver);668 669MODULE_AUTHOR("Ivan Mikhaylov <i.mikhaylov@yadro.com>");670MODULE_DESCRIPTION("Vishay VCNL3020 proximity sensor driver");671MODULE_LICENSE("GPL");672