1019 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1019 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * isl29501.c: ISL29501 Time of Flight sensor driver.
 | |
|  *
 | |
|  * Copyright (C) 2018
 | |
|  * Author: Mathieu Othacehe <m.othacehe@gmail.com>
 | |
|  *
 | |
|  * 7-bit I2C slave address: 0x57
 | |
|  */
 | |
| 
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/i2c.h>
 | |
| #include <linux/err.h>
 | |
| #include <linux/of_device.h>
 | |
| #include <linux/iio/iio.h>
 | |
| #include <linux/iio/sysfs.h>
 | |
| 
 | |
| #include <linux/iio/trigger_consumer.h>
 | |
| #include <linux/iio/buffer.h>
 | |
| #include <linux/iio/triggered_buffer.h>
 | |
| 
 | |
| /* Control, setting and status registers */
 | |
| #define ISL29501_DEVICE_ID			0x00
 | |
| #define ISL29501_ID				0x0A
 | |
| 
 | |
| /* Sampling control registers */
 | |
| #define ISL29501_INTEGRATION_PERIOD		0x10
 | |
| #define ISL29501_SAMPLE_PERIOD			0x11
 | |
| 
 | |
| /* Closed loop calibration registers */
 | |
| #define ISL29501_CROSSTALK_I_MSB		0x24
 | |
| #define ISL29501_CROSSTALK_I_LSB		0x25
 | |
| #define ISL29501_CROSSTALK_I_EXPONENT		0x26
 | |
| #define ISL29501_CROSSTALK_Q_MSB		0x27
 | |
| #define ISL29501_CROSSTALK_Q_LSB		0x28
 | |
| #define ISL29501_CROSSTALK_Q_EXPONENT		0x29
 | |
| #define ISL29501_CROSSTALK_GAIN_MSB		0x2A
 | |
| #define ISL29501_CROSSTALK_GAIN_LSB		0x2B
 | |
| #define ISL29501_MAGNITUDE_REF_EXP		0x2C
 | |
| #define ISL29501_MAGNITUDE_REF_MSB		0x2D
 | |
| #define ISL29501_MAGNITUDE_REF_LSB		0x2E
 | |
| #define ISL29501_PHASE_OFFSET_MSB		0x2F
 | |
| #define ISL29501_PHASE_OFFSET_LSB		0x30
 | |
| 
 | |
| /* Analog control registers */
 | |
| #define ISL29501_DRIVER_RANGE			0x90
 | |
| #define ISL29501_EMITTER_DAC			0x91
 | |
| 
 | |
| #define ISL29501_COMMAND_REGISTER		0xB0
 | |
| 
 | |
| /* Commands */
 | |
| #define ISL29501_EMUL_SAMPLE_START_PIN		0x49
 | |
| #define ISL29501_RESET_ALL_REGISTERS		0xD7
 | |
| #define ISL29501_RESET_INT_SM			0xD1
 | |
| 
 | |
| /* Ambiant light and temperature corrections */
 | |
| #define ISL29501_TEMP_REFERENCE			0x31
 | |
| #define ISL29501_PHASE_EXPONENT			0x33
 | |
| #define ISL29501_TEMP_COEFF_A			0x34
 | |
| #define ISL29501_TEMP_COEFF_B			0x39
 | |
| #define ISL29501_AMBIANT_COEFF_A		0x36
 | |
| #define ISL29501_AMBIANT_COEFF_B		0x3B
 | |
| 
 | |
| /* Data output registers */
 | |
| #define ISL29501_DISTANCE_MSB_DATA		0xD1
 | |
| #define ISL29501_DISTANCE_LSB_DATA		0xD2
 | |
| #define ISL29501_PRECISION_MSB			0xD3
 | |
| #define ISL29501_PRECISION_LSB			0xD4
 | |
| #define ISL29501_MAGNITUDE_EXPONENT		0xD5
 | |
| #define ISL29501_MAGNITUDE_MSB			0xD6
 | |
| #define ISL29501_MAGNITUDE_LSB			0xD7
 | |
| #define ISL29501_PHASE_MSB			0xD8
 | |
| #define ISL29501_PHASE_LSB			0xD9
 | |
| #define ISL29501_I_RAW_EXPONENT			0xDA
 | |
| #define ISL29501_I_RAW_MSB			0xDB
 | |
| #define ISL29501_I_RAW_LSB			0xDC
 | |
| #define ISL29501_Q_RAW_EXPONENT			0xDD
 | |
| #define ISL29501_Q_RAW_MSB			0xDE
 | |
| #define ISL29501_Q_RAW_LSB			0xDF
 | |
| #define ISL29501_DIE_TEMPERATURE		0xE2
 | |
| #define ISL29501_AMBIENT_LIGHT			0xE3
 | |
| #define ISL29501_GAIN_MSB			0xE6
 | |
| #define ISL29501_GAIN_LSB			0xE7
 | |
| 
 | |
| #define ISL29501_MAX_EXP_VAL 15
 | |
| 
 | |
| #define ISL29501_INT_TIME_AVAILABLE \
 | |
| 	"0.00007 0.00014 0.00028 0.00057 0.00114 " \
 | |
| 	"0.00228 0.00455 0.00910 0.01820 0.03640 " \
 | |
| 	"0.07281 0.14561"
 | |
| 
 | |
| #define ISL29501_CURRENT_SCALE_AVAILABLE \
 | |
| 	"0.0039 0.0078 0.0118 0.0157 0.0196 " \
 | |
| 	"0.0235 0.0275 0.0314 0.0352 0.0392 " \
 | |
| 	"0.0431 0.0471 0.0510 0.0549 0.0588"
 | |
| 
 | |
| enum isl29501_correction_coeff {
 | |
| 	COEFF_TEMP_A,
 | |
| 	COEFF_TEMP_B,
 | |
| 	COEFF_LIGHT_A,
 | |
| 	COEFF_LIGHT_B,
 | |
| 	COEFF_MAX,
 | |
| };
 | |
| 
 | |
| struct isl29501_private {
 | |
| 	struct i2c_client *client;
 | |
| 	struct mutex lock;
 | |
| 	/* Exact representation of correction coefficients. */
 | |
| 	unsigned int shadow_coeffs[COEFF_MAX];
 | |
| };
 | |
| 
 | |
| enum isl29501_register_name {
 | |
| 	REG_DISTANCE,
 | |
| 	REG_PHASE,
 | |
| 	REG_TEMPERATURE,
 | |
| 	REG_AMBIENT_LIGHT,
 | |
| 	REG_GAIN,
 | |
| 	REG_GAIN_BIAS,
 | |
| 	REG_PHASE_EXP,
 | |
| 	REG_CALIB_PHASE_TEMP_A,
 | |
| 	REG_CALIB_PHASE_TEMP_B,
 | |
| 	REG_CALIB_PHASE_LIGHT_A,
 | |
| 	REG_CALIB_PHASE_LIGHT_B,
 | |
| 	REG_DISTANCE_BIAS,
 | |
| 	REG_TEMPERATURE_BIAS,
 | |
| 	REG_INT_TIME,
 | |
| 	REG_SAMPLE_TIME,
 | |
| 	REG_DRIVER_RANGE,
 | |
| 	REG_EMITTER_DAC,
 | |
| };
 | |
| 
 | |
| struct isl29501_register_desc {
 | |
| 	u8 msb;
 | |
| 	u8 lsb;
 | |
| };
 | |
| 
 | |
| static const struct isl29501_register_desc isl29501_registers[] = {
 | |
| 	[REG_DISTANCE] = {
 | |
| 		.msb = ISL29501_DISTANCE_MSB_DATA,
 | |
| 		.lsb = ISL29501_DISTANCE_LSB_DATA,
 | |
| 	},
 | |
| 	[REG_PHASE] = {
 | |
| 		.msb = ISL29501_PHASE_MSB,
 | |
| 		.lsb = ISL29501_PHASE_LSB,
 | |
| 	},
 | |
| 	[REG_TEMPERATURE] = {
 | |
| 		.lsb = ISL29501_DIE_TEMPERATURE,
 | |
| 	},
 | |
| 	[REG_AMBIENT_LIGHT] = {
 | |
| 		.lsb = ISL29501_AMBIENT_LIGHT,
 | |
| 	},
 | |
| 	[REG_GAIN] = {
 | |
| 		.msb = ISL29501_GAIN_MSB,
 | |
| 		.lsb = ISL29501_GAIN_LSB,
 | |
| 	},
 | |
| 	[REG_GAIN_BIAS] = {
 | |
| 		.msb = ISL29501_CROSSTALK_GAIN_MSB,
 | |
| 		.lsb = ISL29501_CROSSTALK_GAIN_LSB,
 | |
| 	},
 | |
| 	[REG_PHASE_EXP] = {
 | |
| 		.lsb = ISL29501_PHASE_EXPONENT,
 | |
| 	},
 | |
| 	[REG_CALIB_PHASE_TEMP_A] = {
 | |
| 		.lsb = ISL29501_TEMP_COEFF_A,
 | |
| 	},
 | |
| 	[REG_CALIB_PHASE_TEMP_B] = {
 | |
| 		.lsb = ISL29501_TEMP_COEFF_B,
 | |
| 	},
 | |
| 	[REG_CALIB_PHASE_LIGHT_A] = {
 | |
| 		.lsb = ISL29501_AMBIANT_COEFF_A,
 | |
| 	},
 | |
| 	[REG_CALIB_PHASE_LIGHT_B] = {
 | |
| 		.lsb = ISL29501_AMBIANT_COEFF_B,
 | |
| 	},
 | |
| 	[REG_DISTANCE_BIAS] = {
 | |
| 		.msb = ISL29501_PHASE_OFFSET_MSB,
 | |
| 		.lsb = ISL29501_PHASE_OFFSET_LSB,
 | |
| 	},
 | |
| 	[REG_TEMPERATURE_BIAS] = {
 | |
| 		.lsb = ISL29501_TEMP_REFERENCE,
 | |
| 	},
 | |
| 	[REG_INT_TIME] = {
 | |
| 		.lsb = ISL29501_INTEGRATION_PERIOD,
 | |
| 	},
 | |
| 	[REG_SAMPLE_TIME] = {
 | |
| 		.lsb = ISL29501_SAMPLE_PERIOD,
 | |
| 	},
 | |
| 	[REG_DRIVER_RANGE] = {
 | |
| 		.lsb = ISL29501_DRIVER_RANGE,
 | |
| 	},
 | |
| 	[REG_EMITTER_DAC] = {
 | |
| 		.lsb = ISL29501_EMITTER_DAC,
 | |
| 	},
 | |
| };
 | |
| 
 | |
| static int isl29501_register_read(struct isl29501_private *isl29501,
 | |
| 				  enum isl29501_register_name name,
 | |
| 				  u32 *val)
 | |
| {
 | |
| 	const struct isl29501_register_desc *reg = &isl29501_registers[name];
 | |
| 	u8 msb = 0, lsb = 0;
 | |
| 	s32 ret;
 | |
| 
 | |
| 	mutex_lock(&isl29501->lock);
 | |
| 	if (reg->msb) {
 | |
| 		ret = i2c_smbus_read_byte_data(isl29501->client, reg->msb);
 | |
| 		if (ret < 0)
 | |
| 			goto err;
 | |
| 		msb = ret;
 | |
| 	}
 | |
| 
 | |
| 	if (reg->lsb) {
 | |
| 		ret = i2c_smbus_read_byte_data(isl29501->client, reg->lsb);
 | |
| 		if (ret < 0)
 | |
| 			goto err;
 | |
| 		lsb = ret;
 | |
| 	}
 | |
| 	mutex_unlock(&isl29501->lock);
 | |
| 
 | |
| 	*val = (msb << 8) + lsb;
 | |
| 
 | |
| 	return 0;
 | |
| err:
 | |
| 	mutex_unlock(&isl29501->lock);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static u32 isl29501_register_write(struct isl29501_private *isl29501,
 | |
| 				   enum isl29501_register_name name,
 | |
| 				   u32 value)
 | |
| {
 | |
| 	const struct isl29501_register_desc *reg = &isl29501_registers[name];
 | |
| 	int ret;
 | |
| 
 | |
| 	if (!reg->msb && value > U8_MAX)
 | |
| 		return -ERANGE;
 | |
| 
 | |
| 	if (value > U16_MAX)
 | |
| 		return -ERANGE;
 | |
| 
 | |
| 	mutex_lock(&isl29501->lock);
 | |
| 	if (reg->msb) {
 | |
| 		ret = i2c_smbus_write_byte_data(isl29501->client,
 | |
| 						reg->msb, value >> 8);
 | |
| 		if (ret < 0)
 | |
| 			goto err;
 | |
| 	}
 | |
| 
 | |
| 	ret = i2c_smbus_write_byte_data(isl29501->client, reg->lsb, value);
 | |
| 
 | |
| err:
 | |
| 	mutex_unlock(&isl29501->lock);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t isl29501_read_ext(struct iio_dev *indio_dev,
 | |
| 				 uintptr_t private,
 | |
| 				 const struct iio_chan_spec *chan,
 | |
| 				 char *buf)
 | |
| {
 | |
| 	struct isl29501_private *isl29501 = iio_priv(indio_dev);
 | |
| 	enum isl29501_register_name reg = private;
 | |
| 	int ret;
 | |
| 	u32 value, gain, coeff, exp;
 | |
| 
 | |
| 	switch (reg) {
 | |
| 	case REG_GAIN:
 | |
| 	case REG_GAIN_BIAS:
 | |
| 		ret = isl29501_register_read(isl29501, reg, &gain);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		value = gain;
 | |
| 		break;
 | |
| 	case REG_CALIB_PHASE_TEMP_A:
 | |
| 	case REG_CALIB_PHASE_TEMP_B:
 | |
| 	case REG_CALIB_PHASE_LIGHT_A:
 | |
| 	case REG_CALIB_PHASE_LIGHT_B:
 | |
| 		ret = isl29501_register_read(isl29501, REG_PHASE_EXP, &exp);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		ret = isl29501_register_read(isl29501, reg, &coeff);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		value = coeff << exp;
 | |
| 		break;
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	return sprintf(buf, "%u\n", value);
 | |
| }
 | |
| 
 | |
| static int isl29501_set_shadow_coeff(struct isl29501_private *isl29501,
 | |
| 				     enum isl29501_register_name reg,
 | |
| 				     unsigned int val)
 | |
| {
 | |
| 	enum isl29501_correction_coeff coeff;
 | |
| 
 | |
| 	switch (reg) {
 | |
| 	case REG_CALIB_PHASE_TEMP_A:
 | |
| 		coeff = COEFF_TEMP_A;
 | |
| 		break;
 | |
| 	case REG_CALIB_PHASE_TEMP_B:
 | |
| 		coeff = COEFF_TEMP_B;
 | |
| 		break;
 | |
| 	case REG_CALIB_PHASE_LIGHT_A:
 | |
| 		coeff = COEFF_LIGHT_A;
 | |
| 		break;
 | |
| 	case REG_CALIB_PHASE_LIGHT_B:
 | |
| 		coeff = COEFF_LIGHT_B;
 | |
| 		break;
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 	isl29501->shadow_coeffs[coeff] = val;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int isl29501_write_coeff(struct isl29501_private *isl29501,
 | |
| 				enum isl29501_correction_coeff coeff,
 | |
| 				int val)
 | |
| {
 | |
| 	enum isl29501_register_name reg;
 | |
| 
 | |
| 	switch (coeff) {
 | |
| 	case COEFF_TEMP_A:
 | |
| 		reg = REG_CALIB_PHASE_TEMP_A;
 | |
| 		break;
 | |
| 	case COEFF_TEMP_B:
 | |
| 		reg = REG_CALIB_PHASE_TEMP_B;
 | |
| 		break;
 | |
| 	case COEFF_LIGHT_A:
 | |
| 		reg = REG_CALIB_PHASE_LIGHT_A;
 | |
| 		break;
 | |
| 	case COEFF_LIGHT_B:
 | |
| 		reg = REG_CALIB_PHASE_LIGHT_B;
 | |
| 		break;
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	return isl29501_register_write(isl29501, reg, val);
 | |
| }
 | |
| 
 | |
| static unsigned int isl29501_find_corr_exp(unsigned int val,
 | |
| 					   unsigned int max_exp,
 | |
| 					   unsigned int max_mantissa)
 | |
| {
 | |
| 	unsigned int exp = 1;
 | |
| 
 | |
| 	/*
 | |
| 	 * Correction coefficients are represented under
 | |
| 	 * mantissa * 2^exponent form, where mantissa and exponent
 | |
| 	 * are stored in two separate registers of the sensor.
 | |
| 	 *
 | |
| 	 * Compute and return the lowest exponent such as:
 | |
| 	 *	     mantissa = value / 2^exponent
 | |
| 	 *
 | |
| 	 *  where mantissa < max_mantissa.
 | |
| 	 */
 | |
| 	if (val <= max_mantissa)
 | |
| 		return 0;
 | |
| 
 | |
| 	while ((val >> exp) > max_mantissa) {
 | |
| 		exp++;
 | |
| 
 | |
| 		if (exp > max_exp)
 | |
| 			return max_exp;
 | |
| 	}
 | |
| 
 | |
| 	return exp;
 | |
| }
 | |
| 
 | |
| static ssize_t isl29501_write_ext(struct iio_dev *indio_dev,
 | |
| 				  uintptr_t private,
 | |
| 				  const struct iio_chan_spec *chan,
 | |
| 				  const char *buf, size_t len)
 | |
| {
 | |
| 	struct isl29501_private *isl29501 = iio_priv(indio_dev);
 | |
| 	enum isl29501_register_name reg = private;
 | |
| 	unsigned int val;
 | |
| 	int max_exp = 0;
 | |
| 	int ret;
 | |
| 	int i;
 | |
| 
 | |
| 	ret = kstrtouint(buf, 10, &val);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	switch (reg) {
 | |
| 	case REG_GAIN_BIAS:
 | |
| 		if (val > U16_MAX)
 | |
| 			return -ERANGE;
 | |
| 
 | |
| 		ret = isl29501_register_write(isl29501, reg, val);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		break;
 | |
| 	case REG_CALIB_PHASE_TEMP_A:
 | |
| 	case REG_CALIB_PHASE_TEMP_B:
 | |
| 	case REG_CALIB_PHASE_LIGHT_A:
 | |
| 	case REG_CALIB_PHASE_LIGHT_B:
 | |
| 
 | |
| 		if (val > (U8_MAX << ISL29501_MAX_EXP_VAL))
 | |
| 			return -ERANGE;
 | |
| 
 | |
| 		/* Store the correction coefficient under its exact form. */
 | |
| 		ret = isl29501_set_shadow_coeff(isl29501, reg, val);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		/*
 | |
| 		 * Find the highest exponent needed to represent
 | |
| 		 * correction coefficients.
 | |
| 		 */
 | |
| 		for (i = 0; i < COEFF_MAX; i++) {
 | |
| 			int corr;
 | |
| 			int corr_exp;
 | |
| 
 | |
| 			corr = isl29501->shadow_coeffs[i];
 | |
| 			corr_exp = isl29501_find_corr_exp(corr,
 | |
| 							  ISL29501_MAX_EXP_VAL,
 | |
| 							  U8_MAX / 2);
 | |
| 			dev_dbg(&isl29501->client->dev,
 | |
| 				"found exp of corr(%d) = %d\n", corr, corr_exp);
 | |
| 
 | |
| 			max_exp = max(max_exp, corr_exp);
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Represent every correction coefficient under
 | |
| 		 * mantissa * 2^max_exponent form and force the
 | |
| 		 * writing of those coefficients on the sensor.
 | |
| 		 */
 | |
| 		for (i = 0; i < COEFF_MAX; i++) {
 | |
| 			int corr;
 | |
| 			int mantissa;
 | |
| 
 | |
| 			corr = isl29501->shadow_coeffs[i];
 | |
| 			if (!corr)
 | |
| 				continue;
 | |
| 
 | |
| 			mantissa = corr >> max_exp;
 | |
| 
 | |
| 			ret = isl29501_write_coeff(isl29501, i, mantissa);
 | |
| 			if (ret < 0)
 | |
| 				return ret;
 | |
| 		}
 | |
| 
 | |
| 		ret = isl29501_register_write(isl29501, REG_PHASE_EXP, max_exp);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		break;
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	return len;
 | |
| }
 | |
| 
 | |
| #define _ISL29501_EXT_INFO(_name, _ident) { \
 | |
| 	.name = _name, \
 | |
| 	.read = isl29501_read_ext, \
 | |
| 	.write = isl29501_write_ext, \
 | |
| 	.private = _ident, \
 | |
| 	.shared = IIO_SEPARATE, \
 | |
| }
 | |
| 
 | |
| static const struct iio_chan_spec_ext_info isl29501_ext_info[] = {
 | |
| 	_ISL29501_EXT_INFO("agc_gain", REG_GAIN),
 | |
| 	_ISL29501_EXT_INFO("agc_gain_bias", REG_GAIN_BIAS),
 | |
| 	_ISL29501_EXT_INFO("calib_phase_temp_a", REG_CALIB_PHASE_TEMP_A),
 | |
| 	_ISL29501_EXT_INFO("calib_phase_temp_b", REG_CALIB_PHASE_TEMP_B),
 | |
| 	_ISL29501_EXT_INFO("calib_phase_light_a", REG_CALIB_PHASE_LIGHT_A),
 | |
| 	_ISL29501_EXT_INFO("calib_phase_light_b", REG_CALIB_PHASE_LIGHT_B),
 | |
| 	{ },
 | |
| };
 | |
| 
 | |
| #define ISL29501_DISTANCE_SCAN_INDEX 0
 | |
| #define ISL29501_TIMESTAMP_SCAN_INDEX 1
 | |
| 
 | |
| static const struct iio_chan_spec isl29501_channels[] = {
 | |
| 	{
 | |
| 		.type = IIO_PROXIMITY,
 | |
| 		.scan_index = ISL29501_DISTANCE_SCAN_INDEX,
 | |
| 		.info_mask_separate =
 | |
| 			BIT(IIO_CHAN_INFO_RAW)   |
 | |
| 			BIT(IIO_CHAN_INFO_SCALE) |
 | |
| 			BIT(IIO_CHAN_INFO_CALIBBIAS),
 | |
| 		.scan_type = {
 | |
| 			.sign = 'u',
 | |
| 			.realbits = 16,
 | |
| 			.storagebits = 16,
 | |
| 			.endianness = IIO_CPU,
 | |
| 		},
 | |
| 		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
 | |
| 				BIT(IIO_CHAN_INFO_SAMP_FREQ),
 | |
| 		.ext_info = isl29501_ext_info,
 | |
| 	},
 | |
| 	{
 | |
| 		.type = IIO_PHASE,
 | |
| 		.scan_index = -1,
 | |
| 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 | |
| 				BIT(IIO_CHAN_INFO_SCALE),
 | |
| 	},
 | |
| 	{
 | |
| 		.type = IIO_CURRENT,
 | |
| 		.scan_index = -1,
 | |
| 		.output = 1,
 | |
| 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 | |
| 				BIT(IIO_CHAN_INFO_SCALE),
 | |
| 	},
 | |
| 	{
 | |
| 		.type = IIO_TEMP,
 | |
| 		.scan_index = -1,
 | |
| 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 | |
| 				BIT(IIO_CHAN_INFO_SCALE)     |
 | |
| 				BIT(IIO_CHAN_INFO_CALIBBIAS),
 | |
| 	},
 | |
| 	{
 | |
| 		.type = IIO_INTENSITY,
 | |
| 		.scan_index = -1,
 | |
| 		.modified = 1,
 | |
| 		.channel2 = IIO_MOD_LIGHT_CLEAR,
 | |
| 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 | |
| 				BIT(IIO_CHAN_INFO_SCALE),
 | |
| 	},
 | |
| 	IIO_CHAN_SOFT_TIMESTAMP(ISL29501_TIMESTAMP_SCAN_INDEX),
 | |
| };
 | |
| 
 | |
| static int isl29501_reset_registers(struct isl29501_private *isl29501)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = i2c_smbus_write_byte_data(isl29501->client,
 | |
| 					ISL29501_COMMAND_REGISTER,
 | |
| 					ISL29501_RESET_ALL_REGISTERS);
 | |
| 	if (ret < 0) {
 | |
| 		dev_err(&isl29501->client->dev,
 | |
| 			"cannot reset registers %d\n", ret);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = i2c_smbus_write_byte_data(isl29501->client,
 | |
| 					ISL29501_COMMAND_REGISTER,
 | |
| 					ISL29501_RESET_INT_SM);
 | |
| 	if (ret < 0)
 | |
| 		dev_err(&isl29501->client->dev,
 | |
| 			"cannot reset state machine %d\n", ret);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int isl29501_begin_acquisition(struct isl29501_private *isl29501)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = i2c_smbus_write_byte_data(isl29501->client,
 | |
| 					ISL29501_COMMAND_REGISTER,
 | |
| 					ISL29501_EMUL_SAMPLE_START_PIN);
 | |
| 	if (ret < 0)
 | |
| 		dev_err(&isl29501->client->dev,
 | |
| 			"cannot begin acquisition %d\n", ret);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static IIO_CONST_ATTR_INT_TIME_AVAIL(ISL29501_INT_TIME_AVAILABLE);
 | |
| static IIO_CONST_ATTR(out_current_scale_available,
 | |
| 		      ISL29501_CURRENT_SCALE_AVAILABLE);
 | |
| 
 | |
| static struct attribute *isl29501_attributes[] = {
 | |
| 	&iio_const_attr_integration_time_available.dev_attr.attr,
 | |
| 	&iio_const_attr_out_current_scale_available.dev_attr.attr,
 | |
| 	NULL
 | |
| };
 | |
| 
 | |
| static const struct attribute_group isl29501_attribute_group = {
 | |
| 	.attrs = isl29501_attributes,
 | |
| };
 | |
| 
 | |
| static const int isl29501_current_scale_table[][2] = {
 | |
| 	{0, 3900}, {0, 7800}, {0, 11800}, {0, 15700},
 | |
| 	{0, 19600}, {0, 23500}, {0, 27500}, {0, 31400},
 | |
| 	{0, 35200}, {0, 39200}, {0, 43100}, {0, 47100},
 | |
| 	{0, 51000}, {0, 54900}, {0, 58800},
 | |
| };
 | |
| 
 | |
| static const int isl29501_int_time[][2] = {
 | |
| 	{0, 70},    /* 0.07 ms */
 | |
| 	{0, 140},   /* 0.14 ms */
 | |
| 	{0, 280},   /* 0.28 ms */
 | |
| 	{0, 570},   /* 0.57 ms */
 | |
| 	{0, 1140},  /* 1.14 ms */
 | |
| 	{0, 2280},  /* 2.28 ms */
 | |
| 	{0, 4550},  /* 4.55 ms */
 | |
| 	{0, 9100},  /* 9.11 ms */
 | |
| 	{0, 18200}, /* 18.2 ms */
 | |
| 	{0, 36400}, /* 36.4 ms */
 | |
| 	{0, 72810}, /* 72.81 ms */
 | |
| 	{0, 145610} /* 145.28 ms */
 | |
| };
 | |
| 
 | |
| static int isl29501_get_raw(struct isl29501_private *isl29501,
 | |
| 			    const struct iio_chan_spec *chan,
 | |
| 			    int *raw)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	switch (chan->type) {
 | |
| 	case IIO_PROXIMITY:
 | |
| 		ret = isl29501_register_read(isl29501, REG_DISTANCE, raw);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		return IIO_VAL_INT;
 | |
| 	case IIO_INTENSITY:
 | |
| 		ret = isl29501_register_read(isl29501,
 | |
| 					     REG_AMBIENT_LIGHT,
 | |
| 					     raw);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		return IIO_VAL_INT;
 | |
| 	case IIO_PHASE:
 | |
| 		ret = isl29501_register_read(isl29501, REG_PHASE, raw);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		return IIO_VAL_INT;
 | |
| 	case IIO_CURRENT:
 | |
| 		ret = isl29501_register_read(isl29501, REG_EMITTER_DAC, raw);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		return IIO_VAL_INT;
 | |
| 	case IIO_TEMP:
 | |
| 		ret = isl29501_register_read(isl29501, REG_TEMPERATURE, raw);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		return IIO_VAL_INT;
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int isl29501_get_scale(struct isl29501_private *isl29501,
 | |
| 			      const struct iio_chan_spec *chan,
 | |
| 			      int *val, int *val2)
 | |
| {
 | |
| 	int ret;
 | |
| 	u32 current_scale;
 | |
| 
 | |
| 	switch (chan->type) {
 | |
| 	case IIO_PROXIMITY:
 | |
| 		/* distance = raw_distance * 33.31 / 65536 (m) */
 | |
| 		*val = 3331;
 | |
| 		*val2 = 6553600;
 | |
| 
 | |
| 		return IIO_VAL_FRACTIONAL;
 | |
| 	case IIO_PHASE:
 | |
| 		/* phase = raw_phase * 2pi / 65536 (rad) */
 | |
| 		*val = 0;
 | |
| 		*val2 = 95874;
 | |
| 
 | |
| 		return IIO_VAL_INT_PLUS_NANO;
 | |
| 	case IIO_INTENSITY:
 | |
| 		/* light = raw_light * 35 / 10000 (mA) */
 | |
| 		*val = 35;
 | |
| 		*val2 = 10000;
 | |
| 
 | |
| 		return IIO_VAL_FRACTIONAL;
 | |
| 	case IIO_CURRENT:
 | |
| 		ret = isl29501_register_read(isl29501,
 | |
| 					     REG_DRIVER_RANGE,
 | |
| 					     ¤t_scale);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		if (current_scale > ARRAY_SIZE(isl29501_current_scale_table))
 | |
| 			return -EINVAL;
 | |
| 
 | |
| 		if (!current_scale) {
 | |
| 			*val = 0;
 | |
| 			*val2 = 0;
 | |
| 			return IIO_VAL_INT;
 | |
| 		}
 | |
| 
 | |
| 		*val = isl29501_current_scale_table[current_scale - 1][0];
 | |
| 		*val2 = isl29501_current_scale_table[current_scale - 1][1];
 | |
| 
 | |
| 		return IIO_VAL_INT_PLUS_MICRO;
 | |
| 	case IIO_TEMP:
 | |
| 		/* temperature = raw_temperature * 125 / 100000 (milli °C) */
 | |
| 		*val = 125;
 | |
| 		*val2 = 100000;
 | |
| 
 | |
| 		return IIO_VAL_FRACTIONAL;
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int isl29501_get_calibbias(struct isl29501_private *isl29501,
 | |
| 				  const struct iio_chan_spec *chan,
 | |
| 				  int *bias)
 | |
| {
 | |
| 	switch (chan->type) {
 | |
| 	case IIO_PROXIMITY:
 | |
| 		return isl29501_register_read(isl29501,
 | |
| 					      REG_DISTANCE_BIAS,
 | |
| 					      bias);
 | |
| 	case IIO_TEMP:
 | |
| 		return isl29501_register_read(isl29501,
 | |
| 					      REG_TEMPERATURE_BIAS,
 | |
| 					      bias);
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int isl29501_get_inttime(struct isl29501_private *isl29501,
 | |
| 				int *val, int *val2)
 | |
| {
 | |
| 	int ret;
 | |
| 	u32 inttime;
 | |
| 
 | |
| 	ret = isl29501_register_read(isl29501, REG_INT_TIME, &inttime);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	if (inttime >= ARRAY_SIZE(isl29501_int_time))
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	*val = isl29501_int_time[inttime][0];
 | |
| 	*val2 = isl29501_int_time[inttime][1];
 | |
| 
 | |
| 	return IIO_VAL_INT_PLUS_MICRO;
 | |
| }
 | |
| 
 | |
| static int isl29501_get_freq(struct isl29501_private *isl29501,
 | |
| 			     int *val, int *val2)
 | |
| {
 | |
| 	int ret;
 | |
| 	int sample_time;
 | |
| 	unsigned long long freq;
 | |
| 	u32 temp;
 | |
| 
 | |
| 	ret = isl29501_register_read(isl29501, REG_SAMPLE_TIME, &sample_time);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	/* freq = 1 / (0.000450 * (sample_time + 1) * 10^-6) */
 | |
| 	freq = 1000000ULL * 1000000ULL;
 | |
| 
 | |
| 	do_div(freq, 450 * (sample_time + 1));
 | |
| 
 | |
| 	temp = do_div(freq, 1000000);
 | |
| 	*val = freq;
 | |
| 	*val2 = temp;
 | |
| 
 | |
| 	return IIO_VAL_INT_PLUS_MICRO;
 | |
| }
 | |
| 
 | |
| static int isl29501_read_raw(struct iio_dev *indio_dev,
 | |
| 			     struct iio_chan_spec const *chan, int *val,
 | |
| 			     int *val2, long mask)
 | |
| {
 | |
| 	struct isl29501_private *isl29501 = iio_priv(indio_dev);
 | |
| 
 | |
| 	switch (mask) {
 | |
| 	case IIO_CHAN_INFO_RAW:
 | |
| 		return isl29501_get_raw(isl29501, chan, val);
 | |
| 	case IIO_CHAN_INFO_SCALE:
 | |
| 		return isl29501_get_scale(isl29501, chan, val, val2);
 | |
| 	case IIO_CHAN_INFO_INT_TIME:
 | |
| 		return isl29501_get_inttime(isl29501, val, val2);
 | |
| 	case IIO_CHAN_INFO_SAMP_FREQ:
 | |
| 		return isl29501_get_freq(isl29501, val, val2);
 | |
| 	case IIO_CHAN_INFO_CALIBBIAS:
 | |
| 		return isl29501_get_calibbias(isl29501, chan, val);
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int isl29501_set_raw(struct isl29501_private *isl29501,
 | |
| 			    const struct iio_chan_spec *chan,
 | |
| 			    int raw)
 | |
| {
 | |
| 	switch (chan->type) {
 | |
| 	case IIO_CURRENT:
 | |
| 		return isl29501_register_write(isl29501, REG_EMITTER_DAC, raw);
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int isl29501_set_inttime(struct isl29501_private *isl29501,
 | |
| 				int val, int val2)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	for (i = 0; i < ARRAY_SIZE(isl29501_int_time); i++) {
 | |
| 		if (isl29501_int_time[i][0] == val &&
 | |
| 		    isl29501_int_time[i][1] == val2) {
 | |
| 			return isl29501_register_write(isl29501,
 | |
| 						       REG_INT_TIME,
 | |
| 						       i);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return -EINVAL;
 | |
| }
 | |
| 
 | |
| static int isl29501_set_scale(struct isl29501_private *isl29501,
 | |
| 			      const struct iio_chan_spec *chan,
 | |
| 			      int val, int val2)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	if (chan->type != IIO_CURRENT)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	for (i = 0; i < ARRAY_SIZE(isl29501_current_scale_table); i++) {
 | |
| 		if (isl29501_current_scale_table[i][0] == val &&
 | |
| 		    isl29501_current_scale_table[i][1] == val2) {
 | |
| 			return isl29501_register_write(isl29501,
 | |
| 						       REG_DRIVER_RANGE,
 | |
| 						       i + 1);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return -EINVAL;
 | |
| }
 | |
| 
 | |
| static int isl29501_set_calibbias(struct isl29501_private *isl29501,
 | |
| 				  const struct iio_chan_spec *chan,
 | |
| 				  int bias)
 | |
| {
 | |
| 	switch (chan->type) {
 | |
| 	case IIO_PROXIMITY:
 | |
| 		return isl29501_register_write(isl29501,
 | |
| 					      REG_DISTANCE_BIAS,
 | |
| 					      bias);
 | |
| 	case IIO_TEMP:
 | |
| 		return isl29501_register_write(isl29501,
 | |
| 					       REG_TEMPERATURE_BIAS,
 | |
| 					       bias);
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int isl29501_set_freq(struct isl29501_private *isl29501,
 | |
| 			     int val, int val2)
 | |
| {
 | |
| 	int freq;
 | |
| 	unsigned long long sample_time;
 | |
| 
 | |
| 	/* sample_freq = 1 / (0.000450 * (sample_time + 1) * 10^-6) */
 | |
| 	freq = val * 1000000 + val2 % 1000000;
 | |
| 	sample_time = 2222ULL * 1000000ULL;
 | |
| 	do_div(sample_time, freq);
 | |
| 
 | |
| 	sample_time -= 1;
 | |
| 
 | |
| 	if (sample_time > 255)
 | |
| 		return -ERANGE;
 | |
| 
 | |
| 	return isl29501_register_write(isl29501, REG_SAMPLE_TIME, sample_time);
 | |
| }
 | |
| 
 | |
| static int isl29501_write_raw(struct iio_dev *indio_dev,
 | |
| 			      struct iio_chan_spec const *chan,
 | |
| 			      int val, int val2, long mask)
 | |
| {
 | |
| 	struct isl29501_private *isl29501 = iio_priv(indio_dev);
 | |
| 
 | |
| 	switch (mask) {
 | |
| 	case IIO_CHAN_INFO_RAW:
 | |
| 		return isl29501_set_raw(isl29501, chan, val);
 | |
| 	case IIO_CHAN_INFO_INT_TIME:
 | |
| 		return isl29501_set_inttime(isl29501, val, val2);
 | |
| 	case IIO_CHAN_INFO_SAMP_FREQ:
 | |
| 		return isl29501_set_freq(isl29501, val, val2);
 | |
| 	case IIO_CHAN_INFO_SCALE:
 | |
| 		return isl29501_set_scale(isl29501, chan, val, val2);
 | |
| 	case IIO_CHAN_INFO_CALIBBIAS:
 | |
| 		return isl29501_set_calibbias(isl29501, chan, val);
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static const struct iio_info isl29501_info = {
 | |
| 	.read_raw = &isl29501_read_raw,
 | |
| 	.write_raw = &isl29501_write_raw,
 | |
| 	.attrs = &isl29501_attribute_group,
 | |
| };
 | |
| 
 | |
| static int isl29501_init_chip(struct isl29501_private *isl29501)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = i2c_smbus_read_byte_data(isl29501->client, ISL29501_DEVICE_ID);
 | |
| 	if (ret < 0) {
 | |
| 		dev_err(&isl29501->client->dev, "Error reading device id\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	if (ret != ISL29501_ID) {
 | |
| 		dev_err(&isl29501->client->dev,
 | |
| 			"Wrong chip id, got %x expected %x\n",
 | |
| 			ret, ISL29501_DEVICE_ID);
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	ret = isl29501_reset_registers(isl29501);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	return isl29501_begin_acquisition(isl29501);
 | |
| }
 | |
| 
 | |
| static irqreturn_t isl29501_trigger_handler(int irq, void *p)
 | |
| {
 | |
| 	struct iio_poll_func *pf = p;
 | |
| 	struct iio_dev *indio_dev = pf->indio_dev;
 | |
| 	struct isl29501_private *isl29501 = iio_priv(indio_dev);
 | |
| 	const unsigned long *active_mask = indio_dev->active_scan_mask;
 | |
| 	u32 buffer[4] __aligned(8) = {}; /* 1x16-bit + naturally aligned ts */
 | |
| 
 | |
| 	if (test_bit(ISL29501_DISTANCE_SCAN_INDEX, active_mask))
 | |
| 		isl29501_register_read(isl29501, REG_DISTANCE, buffer);
 | |
| 
 | |
| 	iio_push_to_buffers_with_timestamp(indio_dev, buffer, pf->timestamp);
 | |
| 	iio_trigger_notify_done(indio_dev->trig);
 | |
| 
 | |
| 	return IRQ_HANDLED;
 | |
| }
 | |
| 
 | |
| static int isl29501_probe(struct i2c_client *client,
 | |
| 			  const struct i2c_device_id *id)
 | |
| {
 | |
| 	struct iio_dev *indio_dev;
 | |
| 	struct isl29501_private *isl29501;
 | |
| 	int ret;
 | |
| 
 | |
| 	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*isl29501));
 | |
| 	if (!indio_dev)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	isl29501 = iio_priv(indio_dev);
 | |
| 
 | |
| 	i2c_set_clientdata(client, indio_dev);
 | |
| 	isl29501->client = client;
 | |
| 
 | |
| 	mutex_init(&isl29501->lock);
 | |
| 
 | |
| 	ret = isl29501_init_chip(isl29501);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	indio_dev->modes = INDIO_DIRECT_MODE;
 | |
| 	indio_dev->channels = isl29501_channels;
 | |
| 	indio_dev->num_channels = ARRAY_SIZE(isl29501_channels);
 | |
| 	indio_dev->name = client->name;
 | |
| 	indio_dev->info = &isl29501_info;
 | |
| 
 | |
| 	ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev,
 | |
| 					      iio_pollfunc_store_time,
 | |
| 					      isl29501_trigger_handler,
 | |
| 					      NULL);
 | |
| 	if (ret < 0) {
 | |
| 		dev_err(&client->dev, "unable to setup iio triggered buffer\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return devm_iio_device_register(&client->dev, indio_dev);
 | |
| }
 | |
| 
 | |
| static const struct i2c_device_id isl29501_id[] = {
 | |
| 	{"isl29501", 0},
 | |
| 	{}
 | |
| };
 | |
| 
 | |
| MODULE_DEVICE_TABLE(i2c, isl29501_id);
 | |
| 
 | |
| #if defined(CONFIG_OF)
 | |
| static const struct of_device_id isl29501_i2c_matches[] = {
 | |
| 	{ .compatible = "renesas,isl29501" },
 | |
| 	{ }
 | |
| };
 | |
| MODULE_DEVICE_TABLE(of, isl29501_i2c_matches);
 | |
| #endif
 | |
| 
 | |
| static struct i2c_driver isl29501_driver = {
 | |
| 	.driver = {
 | |
| 		.name	= "isl29501",
 | |
| 	},
 | |
| 	.id_table	= isl29501_id,
 | |
| 	.probe		= isl29501_probe,
 | |
| };
 | |
| module_i2c_driver(isl29501_driver);
 | |
| 
 | |
| MODULE_AUTHOR("Mathieu Othacehe <m.othacehe@gmail.com>");
 | |
| MODULE_DESCRIPTION("ISL29501 Time of Flight sensor driver");
 | |
| MODULE_LICENSE("GPL v2");
 |