1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Support for Vishay VCNL3020 proximity sensor on i2c bus. 4 * Based on Vishay VCNL4000 driver code. 5 * 6 * TODO: interrupts. 7 */ 8 9#include <linux/module.h> 10#include <linux/i2c.h> 11#include <linux/err.h> 12#include <linux/delay.h> 13#include <linux/regmap.h> 14 15#include <linux/iio/iio.h> 16#include <linux/iio/sysfs.h> 17 18#define VCNL3020_PROD_ID 0x21 19 20#define VCNL_COMMAND 0x80 /* Command register */ 21#define VCNL_PROD_REV 0x81 /* Product ID and Revision ID */ 22#define VCNL_PROXIMITY_RATE 0x82 /* Rate of Proximity Measurement */ 23#define VCNL_LED_CURRENT 0x83 /* IR LED current for proximity mode */ 24#define VCNL_PS_RESULT_HI 0x87 /* Proximity result register, MSB */ 25#define VCNL_PS_RESULT_LO 0x88 /* Proximity result register, LSB */ 26#define VCNL_PS_ICR 0x89 /* Interrupt Control Register */ 27#define VCNL_PS_LO_THR_HI 0x8a /* High byte of low threshold value */ 28#define VCNL_PS_LO_THR_LO 0x8b /* Low byte of low threshold value */ 29#define VCNL_PS_HI_THR_HI 0x8c /* High byte of high threshold value */ 30#define VCNL_PS_HI_THR_LO 0x8d /* Low byte of high threshold value */ 31#define VCNL_ISR 0x8e /* Interrupt Status Register */ 32#define VCNL_PS_MOD_ADJ 0x8f /* Proximity Modulator Timing Adjustment */ 33 34/* Bit masks for COMMAND register */ 35#define VCNL_PS_RDY BIT(5) /* proximity data ready? */ 36#define VCNL_PS_OD BIT(3) /* start on-demand proximity 37 * measurement 38 */ 39 40#define VCNL_ON_DEMAND_TIMEOUT_US 100000 41#define VCNL_POLL_US 20000 42 43/** 44 * struct vcnl3020_data - vcnl3020 specific data. 45 * @regmap: device register map. 46 * @dev: vcnl3020 device. 47 * @rev: revision id. 48 * @lock: lock for protecting access to device hardware registers. 49 */ 50struct vcnl3020_data { 51 struct regmap *regmap; 52 struct device *dev; 53 u8 rev; 54 struct mutex lock; 55}; 56 57/** 58 * struct vcnl3020_property - vcnl3020 property. 59 * @name: property name. 60 * @reg: i2c register offset. 61 * @conversion_func: conversion function. 62 */ 63struct vcnl3020_property { 64 const char *name; 65 u32 reg; 66 u32 (*conversion_func)(u32 *val); 67}; 68 69static u32 microamp_to_reg(u32 *val) 70{ 71 /* 72 * An example of conversion from uA to reg val: 73 * 200000 uA == 200 mA == 20 74 */ 75 return *val /= 10000; 76}; 77 78static struct vcnl3020_property vcnl3020_led_current_property = { 79 .name = "vishay,led-current-microamp", 80 .reg = VCNL_LED_CURRENT, 81 .conversion_func = microamp_to_reg, 82}; 83 84static int vcnl3020_get_and_apply_property(struct vcnl3020_data *data, 85 struct vcnl3020_property prop) 86{ 87 int rc; 88 u32 val; 89 90 rc = device_property_read_u32(data->dev, prop.name, &val); 91 if (rc) 92 return 0; 93 94 if (prop.conversion_func) 95 prop.conversion_func(&val); 96 97 rc = regmap_write(data->regmap, prop.reg, val); 98 if (rc) { 99 dev_err(data->dev, "Error (%d) setting property (%s)\n", 100 rc, prop.name); 101 } 102 103 return rc; 104} 105 106static int vcnl3020_init(struct vcnl3020_data *data) 107{ 108 int rc; 109 unsigned int reg; 110 111 rc = regmap_read(data->regmap, VCNL_PROD_REV, ®); 112 if (rc) { 113 dev_err(data->dev, 114 "Error (%d) reading product revision\n", rc); 115 return rc; 116 } 117 118 if (reg != VCNL3020_PROD_ID) { 119 dev_err(data->dev, 120 "Product id (%x) did not match vcnl3020 (%x)\n", reg, 121 VCNL3020_PROD_ID); 122 return -ENODEV; 123 } 124 125 data->rev = reg; 126 mutex_init(&data->lock); 127 128 return vcnl3020_get_and_apply_property(data, 129 vcnl3020_led_current_property); 130}; 131 132static int vcnl3020_measure_proximity(struct vcnl3020_data *data, int *val) 133{ 134 int rc; 135 unsigned int reg; 136 __be16 res; 137 138 mutex_lock(&data->lock); 139 140 rc = regmap_write(data->regmap, VCNL_COMMAND, VCNL_PS_OD); 141 if (rc) 142 goto err_unlock; 143 144 /* wait for data to become ready */ 145 rc = regmap_read_poll_timeout(data->regmap, VCNL_COMMAND, reg, 146 reg & VCNL_PS_RDY, VCNL_POLL_US, 147 VCNL_ON_DEMAND_TIMEOUT_US); 148 if (rc) { 149 dev_err(data->dev, 150 "Error (%d) reading vcnl3020 command register\n", rc); 151 goto err_unlock; 152 } 153 154 /* high & low result bytes read */ 155 rc = regmap_bulk_read(data->regmap, VCNL_PS_RESULT_HI, &res, 156 sizeof(res)); 157 if (rc) 158 goto err_unlock; 159 160 *val = be16_to_cpu(res); 161 162err_unlock: 163 mutex_unlock(&data->lock); 164 165 return rc; 166} 167 168static const struct iio_chan_spec vcnl3020_channels[] = { 169 { 170 .type = IIO_PROXIMITY, 171 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 172 }, 173}; 174 175static int vcnl3020_read_raw(struct iio_dev *indio_dev, 176 struct iio_chan_spec const *chan, int *val, 177 int *val2, long mask) 178{ 179 int rc; 180 struct vcnl3020_data *data = iio_priv(indio_dev); 181 182 switch (mask) { 183 case IIO_CHAN_INFO_RAW: 184 rc = vcnl3020_measure_proximity(data, val); 185 if (rc) 186 return rc; 187 return IIO_VAL_INT; 188 default: 189 return -EINVAL; 190 } 191} 192 193static const struct iio_info vcnl3020_info = { 194 .read_raw = vcnl3020_read_raw, 195}; 196 197static const struct regmap_config vcnl3020_regmap_config = { 198 .reg_bits = 8, 199 .val_bits = 8, 200 .max_register = VCNL_PS_MOD_ADJ, 201}; 202 203static int vcnl3020_probe(struct i2c_client *client) 204{ 205 struct vcnl3020_data *data; 206 struct iio_dev *indio_dev; 207 struct regmap *regmap; 208 int rc; 209 210 regmap = devm_regmap_init_i2c(client, &vcnl3020_regmap_config); 211 if (IS_ERR(regmap)) { 212 dev_err(&client->dev, "regmap_init failed\n"); 213 return PTR_ERR(regmap); 214 } 215 216 indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); 217 if (!indio_dev) 218 return -ENOMEM; 219 220 data = iio_priv(indio_dev); 221 i2c_set_clientdata(client, indio_dev); 222 data->regmap = regmap; 223 data->dev = &client->dev; 224 225 rc = vcnl3020_init(data); 226 if (rc) 227 return rc; 228 229 indio_dev->info = &vcnl3020_info; 230 indio_dev->channels = vcnl3020_channels; 231 indio_dev->num_channels = ARRAY_SIZE(vcnl3020_channels); 232 indio_dev->name = "vcnl3020"; 233 indio_dev->modes = INDIO_DIRECT_MODE; 234 235 return devm_iio_device_register(&client->dev, indio_dev); 236} 237 238static const struct of_device_id vcnl3020_of_match[] = { 239 { 240 .compatible = "vishay,vcnl3020", 241 }, 242 {} 243}; 244MODULE_DEVICE_TABLE(of, vcnl3020_of_match); 245 246static struct i2c_driver vcnl3020_driver = { 247 .driver = { 248 .name = "vcnl3020", 249 .of_match_table = vcnl3020_of_match, 250 }, 251 .probe_new = vcnl3020_probe, 252}; 253module_i2c_driver(vcnl3020_driver); 254 255MODULE_AUTHOR("Ivan Mikhaylov <i.mikhaylov@yadro.com>"); 256MODULE_DESCRIPTION("Vishay VCNL3020 proximity sensor driver"); 257MODULE_LICENSE("GPL"); 258