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 */ 50 struct 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 */ 63 struct vcnl3020_property { 64 const char *name; 65 u32 reg; 66 u32 (*conversion_func)(u32 *val); 67 }; 68 69 static 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 78 static 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 84 static 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 106 static 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 132 static 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 162 err_unlock: 163 mutex_unlock(&data->lock); 164 165 return rc; 166 } 167 168 static const struct iio_chan_spec vcnl3020_channels[] = { 169 { 170 .type = IIO_PROXIMITY, 171 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 172 }, 173 }; 174 175 static 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 193 static const struct iio_info vcnl3020_info = { 194 .read_raw = vcnl3020_read_raw, 195 }; 196 197 static const struct regmap_config vcnl3020_regmap_config = { 198 .reg_bits = 8, 199 .val_bits = 8, 200 .max_register = VCNL_PS_MOD_ADJ, 201 }; 202 203 static 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->dev.parent = &client->dev; 230 indio_dev->info = &vcnl3020_info; 231 indio_dev->channels = vcnl3020_channels; 232 indio_dev->num_channels = ARRAY_SIZE(vcnl3020_channels); 233 indio_dev->name = "vcnl3020"; 234 indio_dev->modes = INDIO_DIRECT_MODE; 235 236 return devm_iio_device_register(&client->dev, indio_dev); 237 } 238 239 static const struct of_device_id vcnl3020_of_match[] = { 240 { 241 .compatible = "vishay,vcnl3020", 242 }, 243 {} 244 }; 245 MODULE_DEVICE_TABLE(of, vcnl3020_of_match); 246 247 static struct i2c_driver vcnl3020_driver = { 248 .driver = { 249 .name = "vcnl3020", 250 .of_match_table = vcnl3020_of_match, 251 }, 252 .probe_new = vcnl3020_probe, 253 }; 254 module_i2c_driver(vcnl3020_driver); 255 256 MODULE_AUTHOR("Ivan Mikhaylov <i.mikhaylov@yadro.com>"); 257 MODULE_DESCRIPTION("Vishay VCNL3020 proximity sensor driver"); 258 MODULE_LICENSE("GPL"); 259