brintos

brintos / linux-shallow public Read only

0
0
Text · 15.4 KiB · d1ddf85 Raw
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, &reg);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