Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH v2 10/12] iio: imu: inv_icm42600: add accurate timestamping
From: Jonathan Cameron @ 2020-05-31 13:04 UTC (permalink / raw)
  To: Jean-Baptiste Maneyrol
  Cc: robh+dt, robh, mchehab+huawei, davem, gregkh, linux-iio,
	devicetree, linux-kernel
In-Reply-To: <20200527185711.21331-11-jmaneyrol@invensense.com>

On Wed, 27 May 2020 20:57:09 +0200
Jean-Baptiste Maneyrol <jmaneyrol@invensense.com> wrote:

> Add a timestamping mechanism for buffer that provides accurate
> event timestamps when using watermark. This mechanism estimates
> device internal clock by comparing FIFO interrupts delta time and
> device elapsed time computed by parsing FIFO data.
> 
> Take interrupt timestamp in hard irq handler and add IIO device
> specific timestamp structures in device private allocation.

I haven't checked the maths or anything, but basic principle seems
sound.   I'm wondering if we want to document somewhere what the
various ways we do this sort of timestamp generation are.  They
give me a headache normally and it would be good to point people
to a reference.  Still that's a job for another day.

I commented on the (lack) of need for force 8 byte alignment inline.

Jonathan

> 
> Signed-off-by: Jean-Baptiste Maneyrol <jmaneyrol@invensense.com>
> ---
>  drivers/iio/imu/inv_icm42600/Makefile         |   1 +
>  drivers/iio/imu/inv_icm42600/inv_icm42600.h   |   5 +
>  .../iio/imu/inv_icm42600/inv_icm42600_accel.c |  40 +++-
>  .../imu/inv_icm42600/inv_icm42600_buffer.c    |  28 +++
>  .../iio/imu/inv_icm42600/inv_icm42600_core.c  |  17 +-
>  .../iio/imu/inv_icm42600/inv_icm42600_gyro.c  |  40 +++-
>  .../imu/inv_icm42600/inv_icm42600_timestamp.c | 195 ++++++++++++++++++
>  .../imu/inv_icm42600/inv_icm42600_timestamp.h |  85 ++++++++
>  8 files changed, 398 insertions(+), 13 deletions(-)
>  create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c
>  create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h
> 
> diff --git a/drivers/iio/imu/inv_icm42600/Makefile b/drivers/iio/imu/inv_icm42600/Makefile
> index 0f49f6df3647..291714d9aa54 100644
> --- a/drivers/iio/imu/inv_icm42600/Makefile
> +++ b/drivers/iio/imu/inv_icm42600/Makefile
> @@ -6,6 +6,7 @@ inv-icm42600-y += inv_icm42600_gyro.o
>  inv-icm42600-y += inv_icm42600_accel.o
>  inv-icm42600-y += inv_icm42600_temp.o
>  inv-icm42600-y += inv_icm42600_buffer.o
> +inv-icm42600-y += inv_icm42600_timestamp.o
>  
>  obj-$(CONFIG_INV_ICM42600_I2C) += inv-icm42600-i2c.o
>  inv-icm42600-i2c-y += inv_icm42600_i2c.o
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h
> index 4d5811562a61..2de0dd7675fb 100644
> --- a/drivers/iio/imu/inv_icm42600/inv_icm42600.h
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h
> @@ -126,6 +126,7 @@ struct inv_icm42600_suspended {
>   *  @indio_accel:	accelerometer IIO device.
>   *  @buffer:		data transfer buffer aligned for DMA.
>   *  @fifo:		FIFO management structure.
> + *  @timestamp:		interrupt timestamps.
>   */
>  struct inv_icm42600_state {
>  	struct mutex lock;
> @@ -141,6 +142,10 @@ struct inv_icm42600_state {
>  	struct iio_dev *indio_accel;
>  	uint8_t buffer[2] ____cacheline_aligned;
>  	struct inv_icm42600_fifo fifo;
> +	struct {
> +		int64_t gyro;
> +		int64_t accel;
> +	} timestamp;
>  };
>  
>  /* Virtual register addresses: @bank on MSB (4 upper bits), @address on LSB */
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
> index c73ce204efc6..ec1d124c1471 100644
> --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
> @@ -17,6 +17,7 @@
>  #include "inv_icm42600.h"
>  #include "inv_icm42600_temp.h"
>  #include "inv_icm42600_buffer.h"
> +#include "inv_icm42600_timestamp.h"
>  
>  #define INV_ICM42600_ACCEL_CHAN(_modifier, _index, _ext_info)		\
>  	{								\
> @@ -50,6 +51,7 @@ enum inv_icm42600_accel_scan {
>  	INV_ICM42600_ACCEL_SCAN_Y,
>  	INV_ICM42600_ACCEL_SCAN_Z,
>  	INV_ICM42600_ACCEL_SCAN_TEMP,
> +	INV_ICM42600_ACCEL_SCAN_TIMESTAMP,
>  };
>  
>  static const struct iio_chan_spec_ext_info inv_icm42600_accel_ext_infos[] = {
> @@ -65,13 +67,15 @@ static const struct iio_chan_spec inv_icm42600_accel_channels[] = {
>  	INV_ICM42600_ACCEL_CHAN(IIO_MOD_Z, INV_ICM42600_ACCEL_SCAN_Z,
>  				inv_icm42600_accel_ext_infos),
>  	INV_ICM42600_TEMP_CHAN(INV_ICM42600_ACCEL_SCAN_TEMP),
> +	IIO_CHAN_SOFT_TIMESTAMP(INV_ICM42600_ACCEL_SCAN_TIMESTAMP),
>  };
>  
> -/* IIO buffer data: 8 bytes */
> +/* IIO buffer data: 16 bytes */
>  struct inv_icm42600_accel_buffer {
>  	struct inv_icm42600_fifo_sensor_data accel;
>  	int8_t temp;
>  	uint8_t padding;
> +	int64_t timestamp;

So this falls into the open question I have in the cleanup set on timestamp
alignment.  What you have here guarantees that we have the correct spacing, but
it allows the alignment of the whole structure to be 4 bytes on x86_32
I don't 'think' that's a problem because the relevant unaligned 8 bytes write
and read should be fine at 4 byte alignment.   Most other archs will
do 8 byte alignment anyway.

Though I'd explain that here to avoid confusion against the fix set where
I will probably be more paranoid and mark all of them __aligned(8) as it
makes it harder to get it wrong.

>  };
>  
>  #define INV_ICM42600_SCAN_MASK_ACCEL_3AXIS				\
> @@ -92,6 +96,7 @@ static int inv_icm42600_accel_update_scan_mode(struct iio_dev *indio_dev,
>  					       const unsigned long *scan_mask)
>  {
>  	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	struct inv_icm42600_timestamp *ts = iio_priv(indio_dev);
>  	struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
>  	unsigned int fifo_en = 0;
>  	unsigned int sleep_temp = 0;
> @@ -119,6 +124,7 @@ static int inv_icm42600_accel_update_scan_mode(struct iio_dev *indio_dev,
>  	}
>  
>  	/* update data FIFO write */
> +	inv_icm42600_timestamp_apply_odr(ts, 0, 0, 0);
>  	ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en);
>  	if (ret)
>  		goto out_unlock;
> @@ -299,9 +305,11 @@ static int inv_icm42600_accel_read_odr(struct inv_icm42600_state *st,
>  	return IIO_VAL_INT_PLUS_MICRO;
>  }
>  
> -static int inv_icm42600_accel_write_odr(struct inv_icm42600_state *st,
> +static int inv_icm42600_accel_write_odr(struct iio_dev *indio_dev,
>  					int val, int val2)
>  {
> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	struct inv_icm42600_timestamp *ts = iio_priv(indio_dev);
>  	struct device *dev = regmap_get_device(st->map);
>  	unsigned int idx;
>  	struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
> @@ -320,6 +328,11 @@ static int inv_icm42600_accel_write_odr(struct inv_icm42600_state *st,
>  	pm_runtime_get_sync(dev);
>  	mutex_lock(&st->lock);
>  
> +	ret = inv_icm42600_timestamp_update_odr(ts, inv_icm42600_odr_to_period(conf.odr),
> +						iio_buffer_enabled(indio_dev));
> +	if (ret)
> +		goto out_unlock;
> +
>  	ret = inv_icm42600_set_accel_conf(st, &conf, NULL);
>  	if (ret)
>  		goto out_unlock;
> @@ -609,7 +622,7 @@ static int inv_icm42600_accel_write_raw(struct iio_dev *indio_dev,
>  		iio_device_release_direct_mode(indio_dev);
>  		return ret;
>  	case IIO_CHAN_INFO_SAMP_FREQ:
> -		return inv_icm42600_accel_write_odr(st, val, val2);
> +		return inv_icm42600_accel_write_odr(indio_dev, val, val2);
>  	case IIO_CHAN_INFO_CALIBBIAS:
>  		ret = iio_device_claim_direct_mode(indio_dev);
>  		if (ret)
> @@ -692,6 +705,8 @@ int inv_icm42600_accel_init(struct inv_icm42600_state *st)
>  {
>  	struct device *dev = regmap_get_device(st->map);
>  	const char *name;
> +	struct inv_icm42600_timestamp *ts;
> +	uint32_t period;
>  	struct iio_dev *indio_dev;
>  	struct iio_buffer *buffer;
>  
> @@ -699,7 +714,7 @@ int inv_icm42600_accel_init(struct inv_icm42600_state *st)
>  	if (!name)
>  		return -ENOMEM;
>  
> -	indio_dev = devm_iio_device_alloc(dev, 0);
> +	indio_dev = devm_iio_device_alloc(dev, sizeof(*ts));
>  	if (!indio_dev)
>  		return -ENOMEM;
>  
> @@ -707,6 +722,10 @@ int inv_icm42600_accel_init(struct inv_icm42600_state *st)
>  	if (!buffer)
>  		return -ENOMEM;
>  
> +	ts = iio_priv(indio_dev);
> +	period = inv_icm42600_odr_to_period(st->conf.accel.odr);
> +	inv_icm42600_timestamp_init(ts, period);
> +
>  	iio_device_set_drvdata(indio_dev, st);
>  	indio_dev->dev.parent = dev;
>  	indio_dev->name = name;
> @@ -726,16 +745,19 @@ int inv_icm42600_accel_init(struct inv_icm42600_state *st)
>  int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev)
>  {
>  	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	struct inv_icm42600_timestamp *ts = iio_priv(indio_dev);
>  	ssize_t i, size;
> +	unsigned int no;
>  	const void *accel, *gyro, *timestamp;
>  	const int8_t *temp;
>  	unsigned int odr;
> +	int64_t ts_val;
>  	struct inv_icm42600_accel_buffer buffer = {
>  		.padding = 0,
>  	};
>  
>  	/* parse all fifo packets */
> -	for (i = 0; i < st->fifo.count; i += size) {
> +	for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) {
>  		size = inv_icm42600_fifo_decode_packet(&st->fifo.data[i],
>  				&accel, &gyro, &temp, &timestamp, &odr);
>  		/* quit if error or FIFO is empty */
> @@ -746,10 +768,16 @@ int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev)
>  		if (accel == NULL || !inv_icm42600_fifo_is_data_valid(accel))
>  			continue;
>  
> +		/* update odr */
> +		if (odr & INV_ICM42600_SENSOR_ACCEL)
> +			inv_icm42600_timestamp_apply_odr(ts, st->fifo.period,
> +							 st->fifo.nb.total, no);
> +
>  		/* fill and push data buffer */
>  		memcpy(&buffer.accel, accel, sizeof(buffer.accel));
>  		buffer.temp = temp ? *temp : 0;
> -		iio_push_to_buffers(indio_dev, &buffer);
> +		ts_val = inv_icm42600_timestamp_pop(ts);
> +		iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val);
>  	}
>  
>  	return 0;
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
> index c91075f62231..3c8b1b19de15 100644
> --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
> @@ -14,6 +14,7 @@
>  #include <linux/iio/buffer.h>
>  
>  #include "inv_icm42600.h"
> +#include "inv_icm42600_timestamp.h"
>  #include "inv_icm42600_buffer.h"
>  
>  /* FIFO header: 1 byte */
> @@ -356,6 +357,7 @@ static int inv_icm42600_buffer_postdisable(struct iio_dev *indio_dev)
>  	struct device *dev = regmap_get_device(st->map);
>  	unsigned int sensor;
>  	unsigned int *watermark;
> +	struct inv_icm42600_timestamp *ts;
>  	struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
>  	unsigned int sleep_temp = 0;
>  	unsigned int sleep_sensor = 0;
> @@ -365,9 +367,11 @@ static int inv_icm42600_buffer_postdisable(struct iio_dev *indio_dev)
>  	if (indio_dev == st->indio_gyro) {
>  		sensor = INV_ICM42600_SENSOR_GYRO;
>  		watermark = &st->fifo.watermark.gyro;
> +		ts = iio_priv(st->indio_gyro);
>  	} else if (indio_dev == st->indio_accel) {
>  		sensor = INV_ICM42600_SENSOR_ACCEL;
>  		watermark = &st->fifo.watermark.accel;
> +		ts = iio_priv(st->indio_accel);
>  	} else {
>  		return -EINVAL;
>  	}
> @@ -395,6 +399,8 @@ static int inv_icm42600_buffer_postdisable(struct iio_dev *indio_dev)
>  	if (!st->fifo.on)
>  		ret = inv_icm42600_set_temp_conf(st, false, &sleep_temp);
>  
> +	inv_icm42600_timestamp_reset(ts);
> +
>  out_unlock:
>  	mutex_unlock(&st->lock);
>  
> @@ -480,17 +486,26 @@ int inv_icm42600_buffer_fifo_read(struct inv_icm42600_state *st,
>  
>  int inv_icm42600_buffer_fifo_parse(struct inv_icm42600_state *st)
>  {
> +	struct inv_icm42600_timestamp *ts;
>  	int ret;
>  
>  	if (st->fifo.nb.total == 0)
>  		return 0;
>  
> +	/* handle gyroscope timestamp and FIFO data parsing */
> +	ts = iio_priv(st->indio_gyro);
> +	inv_icm42600_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total,
> +					 st->fifo.nb.gyro, st->timestamp.gyro);
>  	if (st->fifo.nb.gyro > 0) {
>  		ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro);
>  		if (ret)
>  			return ret;
>  	}
>  
> +	/* handle accelerometer timestamp and FIFO data parsing */
> +	ts = iio_priv(st->indio_accel);
> +	inv_icm42600_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total,
> +					 st->fifo.nb.accel, st->timestamp.accel);
>  	if (st->fifo.nb.accel > 0) {
>  		ret = inv_icm42600_accel_parse_fifo(st->indio_accel);
>  		if (ret)
> @@ -503,8 +518,13 @@ int inv_icm42600_buffer_fifo_parse(struct inv_icm42600_state *st)
>  int inv_icm42600_buffer_hwfifo_flush(struct inv_icm42600_state *st,
>  				     unsigned int count)
>  {
> +	struct inv_icm42600_timestamp *ts;
> +	int64_t gyro_ts, accel_ts;
>  	int ret;
>  
> +	gyro_ts = iio_get_time_ns(st->indio_gyro);
> +	accel_ts = iio_get_time_ns(st->indio_accel);
> +
>  	ret = inv_icm42600_buffer_fifo_read(st, count);
>  	if (ret)
>  		return ret;
> @@ -513,12 +533,20 @@ int inv_icm42600_buffer_hwfifo_flush(struct inv_icm42600_state *st,
>  		return 0;
>  
>  	if (st->fifo.nb.gyro > 0) {
> +		ts = iio_priv(st->indio_gyro);
> +		inv_icm42600_timestamp_interrupt(ts, st->fifo.period,
> +						 st->fifo.nb.total, st->fifo.nb.gyro,
> +						 gyro_ts);
>  		ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro);
>  		if (ret)
>  			return ret;
>  	}
>  
>  	if (st->fifo.nb.accel > 0) {
> +		ts = iio_priv(st->indio_accel);
> +		inv_icm42600_timestamp_interrupt(ts, st->fifo.period,
> +						 st->fifo.nb.total, st->fifo.nb.accel,
> +						 accel_ts);
>  		ret = inv_icm42600_accel_parse_fifo(st->indio_accel);
>  		if (ret)
>  			return ret;
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
> index 6f1c1eb83953..c0d676219fc7 100644
> --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
> @@ -19,6 +19,7 @@
>  
>  #include "inv_icm42600.h"
>  #include "inv_icm42600_buffer.h"
> +#include "inv_icm42600_timestamp.h"
>  
>  static const struct regmap_range_cfg inv_icm42600_regmap_ranges[] = {
>  	{
> @@ -413,6 +414,16 @@ static int inv_icm42600_setup(struct inv_icm42600_state *st,
>  	return inv_icm42600_set_conf(st, hw->conf);
>  }
>  
> +static irqreturn_t inv_icm42600_irq_timestamp(int irq, void *_data)
> +{
> +	struct inv_icm42600_state *st = _data;
> +
> +	st->timestamp.gyro = iio_get_time_ns(st->indio_gyro);
> +	st->timestamp.accel = iio_get_time_ns(st->indio_accel);
> +
> +	return IRQ_WAKE_THREAD;
> +}
> +
>  static irqreturn_t inv_icm42600_irq_handler(int irq, void *_data)
>  {
>  	struct inv_icm42600_state *st = _data;
> @@ -493,7 +504,7 @@ static int inv_icm42600_irq_init(struct inv_icm42600_state *st, int irq,
>  	if (ret)
>  		return ret;
>  
> -	return devm_request_threaded_irq(dev, irq, NULL,
> +	return devm_request_threaded_irq(dev, irq, inv_icm42600_irq_timestamp,
>  					 inv_icm42600_irq_handler, irq_type,
>  					 "inv_icm42600", st);
>  }
> @@ -613,6 +624,10 @@ int inv_icm42600_core_probe(struct regmap *regmap, int chip, int irq,
>  	if (ret)
>  		return ret;
>  
> +	ret = inv_icm42600_timestamp_setup(st);
> +	if (ret)
> +		return ret;
> +
>  	ret = inv_icm42600_buffer_init(st);
>  	if (ret)
>  		return ret;
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
> index b05c33876b8d..76d92a550278 100644
> --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
> @@ -17,6 +17,7 @@
>  #include "inv_icm42600.h"
>  #include "inv_icm42600_temp.h"
>  #include "inv_icm42600_buffer.h"
> +#include "inv_icm42600_timestamp.h"
>  
>  #define INV_ICM42600_GYRO_CHAN(_modifier, _index, _ext_info)		\
>  	{								\
> @@ -50,6 +51,7 @@ enum inv_icm42600_gyro_scan {
>  	INV_ICM42600_GYRO_SCAN_Y,
>  	INV_ICM42600_GYRO_SCAN_Z,
>  	INV_ICM42600_GYRO_SCAN_TEMP,
> +	INV_ICM42600_GYRO_SCAN_TIMESTAMP,
>  };
>  
>  static const struct iio_chan_spec_ext_info inv_icm42600_gyro_ext_infos[] = {
> @@ -65,13 +67,15 @@ static const struct iio_chan_spec inv_icm42600_gyro_channels[] = {
>  	INV_ICM42600_GYRO_CHAN(IIO_MOD_Z, INV_ICM42600_GYRO_SCAN_Z,
>  			       inv_icm42600_gyro_ext_infos),
>  	INV_ICM42600_TEMP_CHAN(INV_ICM42600_GYRO_SCAN_TEMP),
> +	IIO_CHAN_SOFT_TIMESTAMP(INV_ICM42600_GYRO_SCAN_TIMESTAMP),
>  };
>  
> -/* IIO buffer data: 8 bytes */
> +/* IIO buffer data: 16 bytes */
>  struct inv_icm42600_gyro_buffer {
>  	struct inv_icm42600_fifo_sensor_data gyro;
>  	int8_t temp;
>  	uint8_t padding;
> +	int64_t timestamp;
>  };
>  
>  #define INV_ICM42600_SCAN_MASK_GYRO_3AXIS				\
> @@ -92,6 +96,7 @@ static int inv_icm42600_gyro_update_scan_mode(struct iio_dev *indio_dev,
>  					      const unsigned long *scan_mask)
>  {
>  	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	struct inv_icm42600_timestamp *ts = iio_priv(indio_dev);
>  	struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
>  	unsigned int fifo_en = 0;
>  	unsigned int sleep_gyro = 0;
> @@ -119,6 +124,7 @@ static int inv_icm42600_gyro_update_scan_mode(struct iio_dev *indio_dev,
>  	}
>  
>  	/* update data FIFO write */
> +	inv_icm42600_timestamp_apply_odr(ts, 0, 0, 0);
>  	ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en);
>  	if (ret)
>  		goto out_unlock;
> @@ -311,9 +317,11 @@ static int inv_icm42600_gyro_read_odr(struct inv_icm42600_state *st,
>  	return IIO_VAL_INT_PLUS_MICRO;
>  }
>  
> -static int inv_icm42600_gyro_write_odr(struct inv_icm42600_state *st,
> +static int inv_icm42600_gyro_write_odr(struct iio_dev *indio_dev,
>  				       int val, int val2)
>  {
> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	struct inv_icm42600_timestamp *ts = iio_priv(indio_dev);
>  	struct device *dev = regmap_get_device(st->map);
>  	unsigned int idx;
>  	struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
> @@ -332,6 +340,11 @@ static int inv_icm42600_gyro_write_odr(struct inv_icm42600_state *st,
>  	pm_runtime_get_sync(dev);
>  	mutex_lock(&st->lock);
>  
> +	ret = inv_icm42600_timestamp_update_odr(ts, inv_icm42600_odr_to_period(conf.odr),
> +						iio_buffer_enabled(indio_dev));
> +	if (ret)
> +		goto out_unlock;
> +
>  	ret = inv_icm42600_set_gyro_conf(st, &conf, NULL);
>  	if (ret)
>  		goto out_unlock;
> @@ -620,7 +633,7 @@ static int inv_icm42600_gyro_write_raw(struct iio_dev *indio_dev,
>  		iio_device_release_direct_mode(indio_dev);
>  		return ret;
>  	case IIO_CHAN_INFO_SAMP_FREQ:
> -		return inv_icm42600_gyro_write_odr(st, val, val2);
> +		return inv_icm42600_gyro_write_odr(indio_dev, val, val2);
>  	case IIO_CHAN_INFO_CALIBBIAS:
>  		ret = iio_device_claim_direct_mode(indio_dev);
>  		if (ret)
> @@ -703,6 +716,8 @@ int inv_icm42600_gyro_init(struct inv_icm42600_state *st)
>  {
>  	struct device *dev = regmap_get_device(st->map);
>  	const char *name;
> +	struct inv_icm42600_timestamp *ts;
> +	uint32_t period;
>  	struct iio_dev *indio_dev;
>  	struct iio_buffer *buffer;
>  
> @@ -710,7 +725,7 @@ int inv_icm42600_gyro_init(struct inv_icm42600_state *st)
>  	if (!name)
>  		return -ENOMEM;
>  
> -	indio_dev = devm_iio_device_alloc(dev, 0);
> +	indio_dev = devm_iio_device_alloc(dev, sizeof(*ts));
>  	if (!indio_dev)
>  		return -ENOMEM;
>  
> @@ -718,6 +733,10 @@ int inv_icm42600_gyro_init(struct inv_icm42600_state *st)
>  	if (!buffer)
>  		return -ENOMEM;
>  
> +	ts = iio_priv(indio_dev);
> +	period = inv_icm42600_odr_to_period(st->conf.gyro.odr);
> +	inv_icm42600_timestamp_init(ts, period);
> +
>  	iio_device_set_drvdata(indio_dev, st);
>  	indio_dev->dev.parent = dev;
>  	indio_dev->name = name;
> @@ -737,16 +756,19 @@ int inv_icm42600_gyro_init(struct inv_icm42600_state *st)
>  int inv_icm42600_gyro_parse_fifo(struct iio_dev *indio_dev)
>  {
>  	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	struct inv_icm42600_timestamp *ts = iio_priv(indio_dev);
>  	ssize_t i, size;
> +	unsigned int no;
>  	const void *accel, *gyro, *timestamp;
>  	const int8_t *temp;
>  	unsigned int odr;
> +	int64_t ts_val;
>  	struct inv_icm42600_gyro_buffer buffer = {
>  		.padding = 0,
>  	};
>  
>  	/* parse all fifo packets */
> -	for (i = 0; i < st->fifo.count; i += size) {
> +	for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) {
>  		size = inv_icm42600_fifo_decode_packet(&st->fifo.data[i],
>  				&accel, &gyro, &temp, &timestamp, &odr);
>  		/* quit if error or FIFO is empty */
> @@ -757,10 +779,16 @@ int inv_icm42600_gyro_parse_fifo(struct iio_dev *indio_dev)
>  		if (gyro == NULL || !inv_icm42600_fifo_is_data_valid(gyro))
>  			continue;
>  
> +		/* update odr */
> +		if (odr & INV_ICM42600_SENSOR_GYRO)
> +			inv_icm42600_timestamp_apply_odr(ts, st->fifo.period,
> +							 st->fifo.nb.total, no);
> +
>  		/* fill and push data buffer */
>  		memcpy(&buffer.gyro, gyro, sizeof(buffer.gyro));
>  		buffer.temp = temp ? *temp : 0;
> -		iio_push_to_buffers(indio_dev, &buffer);
> +		ts_val = inv_icm42600_timestamp_pop(ts);
> +		iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val);
>  	}
>  
>  	return 0;
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c
> new file mode 100644
> index 000000000000..7f2dc41f807b
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c
> @@ -0,0 +1,195 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2020 Invensense, Inc.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/regmap.h>
> +#include <linux/math64.h>
> +
> +#include "inv_icm42600.h"
> +#include "inv_icm42600_timestamp.h"
> +
> +/* internal chip period is 32kHz, 31250ns */
> +#define INV_ICM42600_TIMESTAMP_PERIOD		31250
> +/* allow a jitter of +/- 2% */
> +#define INV_ICM42600_TIMESTAMP_JITTER		2
> +/* compute min and max periods accepted */
> +#define INV_ICM42600_TIMESTAMP_MIN_PERIOD(_p)		\
> +	(((_p) * (100 - INV_ICM42600_TIMESTAMP_JITTER)) / 100)
> +#define INV_ICM42600_TIMESTAMP_MAX_PERIOD(_p)		\
> +	(((_p) * (100 + INV_ICM42600_TIMESTAMP_JITTER)) / 100)
> +
> +/* Add a new value inside an accumulator and update the estimate value */
> +static void inv_update_acc(struct inv_icm42600_timestamp_acc *acc, uint32_t val)
> +{
> +	uint64_t sum = 0;
> +	size_t i;
> +
> +	acc->values[acc->idx++] = val;
> +	if (acc->idx >= ARRAY_SIZE(acc->values))
> +		acc->idx = 0;
> +
> +	/* compute the mean of all stored values, use 0 as empty slot */
> +	for (i = 0; i < ARRAY_SIZE(acc->values); ++i) {
> +		if (acc->values[i] == 0)
> +			break;
> +		sum += acc->values[i];
> +	}
> +
> +	acc->val = div_u64(sum, i);
> +}
> +
> +void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts,
> +				 uint32_t period)
> +{
> +	/* initial odr for sensor after reset is 1kHz */
> +	const uint32_t default_period = 1000000;
> +
> +	/* current multiplier and period values after reset */
> +	ts->mult = default_period / INV_ICM42600_TIMESTAMP_PERIOD;
> +	ts->period = default_period;
> +	/* new set multiplier is the one from chip initialization */
> +	ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD;
> +
> +	/* use theoretical value for chip period */
> +	inv_update_acc(&ts->chip_period, INV_ICM42600_TIMESTAMP_PERIOD);
> +}
> +
> +int inv_icm42600_timestamp_setup(struct inv_icm42600_state *st)
> +{
> +	unsigned int val;
> +
> +	/* enable timestamp register */
> +	val = INV_ICM42600_TMST_CONFIG_TMST_TO_REGS_EN |
> +	      INV_ICM42600_TMST_CONFIG_TMST_EN;
> +	return regmap_update_bits(st->map, INV_ICM42600_REG_TMST_CONFIG,
> +				  INV_ICM42600_TMST_CONFIG_MASK, val);
> +}
> +
> +int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts,
> +				      uint32_t period, bool fifo)
> +{
> +	/* when FIFO is on, prevent odr change if one is already pending */
> +	if (fifo && ts->new_mult != 0)
> +		return -EAGAIN;
> +
> +	ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD;
> +
> +	return 0;
> +}
> +
> +static bool inv_validate_period(uint32_t period, uint32_t mult)
> +{
> +	const uint32_t chip_period = INV_ICM42600_TIMESTAMP_PERIOD;
> +	uint32_t period_min, period_max;
> +
> +	/* check that period is acceptable */
> +	period_min = INV_ICM42600_TIMESTAMP_MIN_PERIOD(chip_period) * mult;
> +	period_max = INV_ICM42600_TIMESTAMP_MAX_PERIOD(chip_period) * mult;
> +	if (period > period_min && period < period_max)
> +		return true;
> +	else
> +		return false;
> +}
> +
> +static bool inv_compute_chip_period(struct inv_icm42600_timestamp *ts,
> +				    uint32_t mult, uint32_t period)
> +{
> +	uint32_t new_chip_period;
> +
> +	if (!inv_validate_period(period, mult))
> +		return false;
> +
> +	/* update chip internal period estimation */
> +	new_chip_period = period / mult;
> +	inv_update_acc(&ts->chip_period, new_chip_period);
> +
> +	return true;
> +}
> +
> +void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts,
> +				      uint32_t fifo_period, size_t fifo_nb,
> +				      size_t sensor_nb, int64_t timestamp)
> +{
> +	struct inv_icm42600_timestamp_interval *it;
> +	int64_t delta, interval;
> +	const uint32_t fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD;
> +	uint32_t period = ts->period;
> +	int32_t m;
> +	bool valid = false;
> +
> +	if (fifo_nb == 0)
> +		return;
> +
> +	/* update interrupt timestamp and compute chip and sensor periods */
> +	it = &ts->it;
> +	it->lo = it->up;
> +	it->up = timestamp;
> +	delta = it->up - it->lo;
> +	if (it->lo != 0) {
> +		/* compute period: delta time divided by number of samples */
> +		period = div_s64(delta, fifo_nb);
> +		valid = inv_compute_chip_period(ts, fifo_mult, period);
> +		/* update sensor period if chip internal period is updated */
> +		if (valid)
> +			ts->period = ts->mult * ts->chip_period.val;
> +	}
> +
> +	/* no previous data, compute theoritical value from interrupt */
> +	if (ts->timestamp == 0) {
> +		/* elapsed time: sensor period * sensor samples number */
> +		interval = (int64_t)ts->period * (int64_t)sensor_nb;
> +		ts->timestamp = it->up - interval;
> +		return;
> +	}
> +
> +	/* if interrupt interval is valid, sync with interrupt timestamp */
> +	if (valid) {
> +		/* compute measured fifo_period */
> +		fifo_period = fifo_mult * ts->chip_period.val;
> +		/* delta time between last sample and last interrupt */
> +		delta = it->lo - ts->timestamp;
> +		/* if there are multiple samples, go back to first one */
> +		while (delta >= (fifo_period * 3 / 2))
> +			delta -= fifo_period;
> +		/* compute maximal adjustment value */
> +		m = INV_ICM42600_TIMESTAMP_MAX_PERIOD(ts->period) - ts->period;
> +		if (delta > m)
> +			delta = m;
> +		else if (delta < -m)
> +			delta = -m;
> +		ts->timestamp += delta;
> +	}
> +}
> +
> +void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts,
> +				      uint32_t fifo_period, size_t fifo_nb,
> +				      unsigned int fifo_no)
> +{
> +	int64_t interval;
> +	uint32_t fifo_mult;
> +
> +	if (ts->new_mult == 0)
> +		return;
> +
> +	/* update to new multiplier and update period */
> +	ts->mult = ts->new_mult;
> +	ts->new_mult = 0;
> +	ts->period = ts->mult * ts->chip_period.val;
> +
> +	/*
> +	 * After ODR change the time interval with the previous sample is
> +	 * undertermined (depends when the change occures). So we compute the
> +	 * timestamp from the current interrupt using the new FIFO period, the
> +	 * total number of samples and the current sample numero.
> +	 */
> +	if (ts->timestamp != 0) {
> +		/* compute measured fifo period */
> +		fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD;
> +		fifo_period = fifo_mult * ts->chip_period.val;
> +		/* computes time interval between interrupt and this sample */
> +		interval = (int64_t)(fifo_nb - fifo_no) * (int64_t)fifo_period;
> +		ts->timestamp = ts->it.up - interval;
> +	}
> +}
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h b/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h
> new file mode 100644
> index 000000000000..4e4f331d4fe4
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h
> @@ -0,0 +1,85 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2020 Invensense, Inc.
> + */
> +
> +#ifndef INV_ICM42600_TIMESTAMP_H_
> +#define INV_ICM42600_TIMESTAMP_H_
> +
> +#include <linux/kernel.h>
> +
> +struct inv_icm42600_state;
> +
> +/**
> + * struct inv_icm42600_timestamp_interval - timestamps interval
> + * @lo:	interval lower bound
> + * @up:	interval upper bound
> + */
> +struct inv_icm42600_timestamp_interval {
> +	int64_t lo;
> +	int64_t up;
> +};
> +
> +/**
> + * struct inv_icm42600_timestamp_acc - accumulator for computing an estimation
> + * @val:	current estimation of the value, the mean of all values
> + * @idx:	current index of the next free place in values table
> + * @values:	table of all measured values, use for computing the mean
> + */
> +struct inv_icm42600_timestamp_acc {
> +	uint32_t val;
> +	size_t idx;
> +	uint32_t values[32];
> +};
> +
> +/**
> + * struct inv_icm42600_timestamp - timestamp management states
> + * @it:			interrupts interval timestamps
> + * @timestamp:		store last timestamp for computing next data timestamp
> + * @mult:		current internal period multiplier
> + * @new_mult:		new set internal period multiplier (not yet effective)
> + * @period:		measured current period of the sensor
> + * @chip_period:	accumulator for computing internal chip period
> + */
> +struct inv_icm42600_timestamp {
> +	struct inv_icm42600_timestamp_interval it;
> +	int64_t timestamp;
> +	uint32_t mult;
> +	uint32_t new_mult;
> +	uint32_t period;
> +	struct inv_icm42600_timestamp_acc chip_period;
> +};
> +
> +void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts,
> +				 uint32_t period);
> +
> +int inv_icm42600_timestamp_setup(struct inv_icm42600_state *st);
> +
> +int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts,
> +				      uint32_t period, bool fifo);
> +
> +void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts,
> +				      uint32_t fifo_period, size_t fifo_nb,
> +				      size_t sensor_nb, int64_t timestamp);
> +
> +static inline int64_t
> +inv_icm42600_timestamp_pop(struct inv_icm42600_timestamp *ts)
> +{
> +	ts->timestamp += ts->period;
> +	return ts->timestamp;
> +}
> +
> +void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts,
> +				      uint32_t fifo_period, size_t fifo_nb,
> +				      unsigned int fifo_no);
> +
> +static inline void
> +inv_icm42600_timestamp_reset(struct inv_icm42600_timestamp *ts)
> +{
> +	const struct inv_icm42600_timestamp_interval interval_init = {0LL, 0LL};
> +
> +	ts->it = interval_init;
> +	ts->timestamp = 0;
> +}
> +
> +#endif


^ permalink raw reply

* Re: [PATCH v2 2/3] media: rockchip: Introduce driver for Rockhip's camera interface
From: kbuild test robot @ 2020-05-31 12:16 UTC (permalink / raw)
  To: Maxime Chevallier, Mauro Carvalho Chehab, Robin Murphy,
	Rob Herring, Mark Rutland, Heiko Stuebner, Hans Verkuil
  Cc: kbuild-all, clang-built-linux, linux-media, Maxime Chevallier,
	devicetree, linux-arm-kernel
In-Reply-To: <20200529130405.929429-3-maxime.chevallier@bootlin.com>

[-- Attachment #1: Type: text/plain, Size: 3247 bytes --]

Hi Maxime,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on linuxtv-media/master]
[also build test WARNING on rockchip/for-next robh/for-next v5.7-rc7 next-20200529]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url:    https://github.com/0day-ci/linux/commits/Maxime-Chevallier/media-rockchip-Introduce-driver-for-the-camera-interface-on-PX30/20200531-081952
base:   git://linuxtv.org/media_tree.git master
config: x86_64-allyesconfig (attached as .config)
compiler: clang version 11.0.0 (https://github.com/llvm/llvm-project 2388a096e7865c043e83ece4e26654bd3d1a20d5)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # install x86_64 cross compiling tool for clang build
        # apt-get install binutils-x86-64-linux-gnu
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross ARCH=x86_64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kbuild test robot <lkp@intel.com>

All warnings (new ones prefixed by >>, old ones prefixed by <<):

In file included from drivers/media/platform/rockchip/cif/dev.c:22:
>> drivers/media/platform/rockchip/cif/regs.h:113:9: warning: 'FRAME_END' macro redefined [-Wmacro-redefined]
#define FRAME_END                       (0x01 << 0)
^
arch/x86/include/asm/frame.h:89:9: note: previous definition is here
#define FRAME_END
^
>> drivers/media/platform/rockchip/cif/dev.c:253:13: warning: variable 'v4l2_dev' is uninitialized when used here [-Wuninitialized]
v4l2_info(v4l2_dev, "No reserved memory region assign to CIFn");
^~~~~~~~
include/media/v4l2-common.h:67:25: note: expanded from macro 'v4l2_info'
v4l2_printk(KERN_INFO, dev, fmt , ## arg)
^~~
include/media/v4l2-common.h:58:28: note: expanded from macro 'v4l2_printk'
printk(level "%s: " fmt, (dev)->name , ## arg)
^~~
drivers/media/platform/rockchip/cif/dev.c:216:30: note: initialize the variable 'v4l2_dev' to silence this warning
struct v4l2_device *v4l2_dev;
^
= NULL
2 warnings generated.
--
In file included from drivers/media/platform/rockchip/cif/capture.c:22:
>> drivers/media/platform/rockchip/cif/regs.h:113:9: warning: 'FRAME_END' macro redefined [-Wmacro-redefined]
#define FRAME_END                       (0x01 << 0)
^
arch/x86/include/asm/frame.h:89:9: note: previous definition is here
#define FRAME_END
^
1 warning generated.

vim +/FRAME_END +113 drivers/media/platform/rockchip/cif/regs.h

   110	
   111	/* CIF INTSTAT */
   112	#define INTSTAT_CLS			(0x3FF)
 > 113	#define FRAME_END			(0x01 << 0)
   114	#define LINE_ERR			(0x01 << 2)
   115	#define PST_INF_FRAME_END		(0x01 << 9)
   116	#define FRAME_END_CLR			(0x01 << 0)
   117	#define LINE_ERR_CLR			(0x01 << 2)
   118	#define PST_INF_FRAME_END_CLR		(0x01 << 9)
   119	#define INTSTAT_ERR			(0xFC)
   120	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org

[-- Attachment #2: .config.gz --]
[-- Type: application/gzip, Size: 73692 bytes --]

^ permalink raw reply

* Re: [PATCH v2 09/12] iio: imu: inv_icm42600: add buffer support in iio devices
From: Jonathan Cameron @ 2020-05-31 12:56 UTC (permalink / raw)
  To: Jean-Baptiste Maneyrol
  Cc: robh+dt, robh, mchehab+huawei, davem, gregkh, linux-iio,
	devicetree, linux-kernel
In-Reply-To: <20200527185711.21331-10-jmaneyrol@invensense.com>

On Wed, 27 May 2020 20:57:08 +0200
Jean-Baptiste Maneyrol <jmaneyrol@invensense.com> wrote:

> Add all FIFO parsing and reading functions. Add accel and gyro
> kfifo buffer and FIFO data parsing. Use device interrupt for
> reading data FIFO and launching accel and gyro parsing.
> 
> Support hwfifo watermark by multiplexing gyro and accel settings.
> Support hwfifo flush.

Both of these are complex given the interactions of the two sensors
types and to be honest I couldn't figure out exactly what the intent was.
Needs more docs!

Thanks,

Jonathan

> 
> Signed-off-by: Jean-Baptiste Maneyrol <jmaneyrol@invensense.com>
> ---
>  drivers/iio/imu/inv_icm42600/Kconfig          |   1 +
>  drivers/iio/imu/inv_icm42600/Makefile         |   1 +
>  drivers/iio/imu/inv_icm42600/inv_icm42600.h   |   8 +
>  .../iio/imu/inv_icm42600/inv_icm42600_accel.c | 160 ++++-
>  .../imu/inv_icm42600/inv_icm42600_buffer.c    | 555 ++++++++++++++++++
>  .../imu/inv_icm42600/inv_icm42600_buffer.h    |  98 ++++
>  .../iio/imu/inv_icm42600/inv_icm42600_core.c  |  30 +
>  .../iio/imu/inv_icm42600/inv_icm42600_gyro.c  | 160 ++++-
>  8 files changed, 1011 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
>  create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h
> 
> diff --git a/drivers/iio/imu/inv_icm42600/Kconfig b/drivers/iio/imu/inv_icm42600/Kconfig
> index 22390a72f0a3..50cbcfcb6cf1 100644
> --- a/drivers/iio/imu/inv_icm42600/Kconfig
> +++ b/drivers/iio/imu/inv_icm42600/Kconfig
> @@ -2,6 +2,7 @@
>  
>  config INV_ICM42600
>  	tristate
> +	select IIO_BUFFER
>  
>  config INV_ICM42600_I2C
>  	tristate "InvenSense ICM-426xx I2C driver"
> diff --git a/drivers/iio/imu/inv_icm42600/Makefile b/drivers/iio/imu/inv_icm42600/Makefile
> index 48965824f00c..0f49f6df3647 100644
> --- a/drivers/iio/imu/inv_icm42600/Makefile
> +++ b/drivers/iio/imu/inv_icm42600/Makefile
> @@ -5,6 +5,7 @@ inv-icm42600-y += inv_icm42600_core.o
>  inv-icm42600-y += inv_icm42600_gyro.o
>  inv-icm42600-y += inv_icm42600_accel.o
>  inv-icm42600-y += inv_icm42600_temp.o
> +inv-icm42600-y += inv_icm42600_buffer.o
>  
>  obj-$(CONFIG_INV_ICM42600_I2C) += inv-icm42600-i2c.o
>  inv-icm42600-i2c-y += inv_icm42600_i2c.o
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h
> index 43749f56426c..4d5811562a61 100644
> --- a/drivers/iio/imu/inv_icm42600/inv_icm42600.h
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h
> @@ -14,6 +14,8 @@
>  #include <linux/pm.h>
>  #include <linux/iio/iio.h>
>  
> +#include "inv_icm42600_buffer.h"
> +
>  enum inv_icm42600_chip {
>  	INV_CHIP_ICM42600,
>  	INV_CHIP_ICM42602,
> @@ -123,6 +125,7 @@ struct inv_icm42600_suspended {
>   *  @indio_gyro:	gyroscope IIO device.
>   *  @indio_accel:	accelerometer IIO device.
>   *  @buffer:		data transfer buffer aligned for DMA.
> + *  @fifo:		FIFO management structure.
>   */
>  struct inv_icm42600_state {
>  	struct mutex lock;
> @@ -137,6 +140,7 @@ struct inv_icm42600_state {
>  	struct iio_dev *indio_gyro;
>  	struct iio_dev *indio_accel;
>  	uint8_t buffer[2] ____cacheline_aligned;
> +	struct inv_icm42600_fifo fifo;
>  };
>  
>  /* Virtual register addresses: @bank on MSB (4 upper bits), @address on LSB */
> @@ -377,6 +381,10 @@ int inv_icm42600_core_probe(struct regmap *regmap, int chip, int irq,
>  
>  int inv_icm42600_gyro_init(struct inv_icm42600_state *st);
>  
> +int inv_icm42600_gyro_parse_fifo(struct iio_dev *indio_dev);
> +
>  int inv_icm42600_accel_init(struct inv_icm42600_state *st);
>  
> +int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev);
> +
>  #endif
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
> index 6a615d7ffb24..c73ce204efc6 100644
> --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
> @@ -11,9 +11,12 @@
>  #include <linux/delay.h>
>  #include <linux/math64.h>
>  #include <linux/iio/iio.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/kfifo_buf.h>
>  
>  #include "inv_icm42600.h"
>  #include "inv_icm42600_temp.h"
> +#include "inv_icm42600_buffer.h"
>  
>  #define INV_ICM42600_ACCEL_CHAN(_modifier, _index, _ext_info)		\
>  	{								\
> @@ -64,6 +67,76 @@ static const struct iio_chan_spec inv_icm42600_accel_channels[] = {
>  	INV_ICM42600_TEMP_CHAN(INV_ICM42600_ACCEL_SCAN_TEMP),
>  };
>  
> +/* IIO buffer data: 8 bytes */
> +struct inv_icm42600_accel_buffer {
> +	struct inv_icm42600_fifo_sensor_data accel;
> +	int8_t temp;
> +	uint8_t padding;
> +};
> +
> +#define INV_ICM42600_SCAN_MASK_ACCEL_3AXIS				\
> +	(BIT(INV_ICM42600_ACCEL_SCAN_X) |				\
> +	BIT(INV_ICM42600_ACCEL_SCAN_Y) |				\
> +	BIT(INV_ICM42600_ACCEL_SCAN_Z))
> +
> +#define INV_ICM42600_SCAN_MASK_TEMP	BIT(INV_ICM42600_ACCEL_SCAN_TEMP)
> +
> +static const unsigned long inv_icm42600_accel_scan_masks[] = {
> +	/* 3-axis accel + temperature */
> +	INV_ICM42600_SCAN_MASK_ACCEL_3AXIS | INV_ICM42600_SCAN_MASK_TEMP,
> +	0,
> +};
> +
> +/* enable accelerometer sensor and FIFO write */
> +static int inv_icm42600_accel_update_scan_mode(struct iio_dev *indio_dev,
> +					       const unsigned long *scan_mask)
> +{
> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
> +	unsigned int fifo_en = 0;
> +	unsigned int sleep_temp = 0;
> +	unsigned int sleep_accel = 0;
> +	unsigned int sleep;
> +	int ret;
> +
> +	mutex_lock(&st->lock);
> +
> +	if (*scan_mask & INV_ICM42600_SCAN_MASK_TEMP) {
> +		/* enable temp sensor */
> +		ret = inv_icm42600_set_temp_conf(st, true, &sleep_temp);
> +		if (ret)
> +			goto out_unlock;
> +		fifo_en |= INV_ICM42600_SENSOR_TEMP;
> +	}
> +
> +	if (*scan_mask & INV_ICM42600_SCAN_MASK_ACCEL_3AXIS) {
> +		/* enable accel sensor */
> +		conf.mode = INV_ICM42600_SENSOR_MODE_LOW_NOISE;
> +		ret = inv_icm42600_set_accel_conf(st, &conf, &sleep_accel);
> +		if (ret)
> +			goto out_unlock;
> +		fifo_en |= INV_ICM42600_SENSOR_ACCEL;
> +	}
> +
> +	/* update data FIFO write */
> +	ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en);
> +	if (ret)
> +		goto out_unlock;
> +
> +	ret = inv_icm42600_buffer_update_watermark(st);
> +
> +out_unlock:
> +	mutex_unlock(&st->lock);
> +	/* sleep maximum required time */
> +	if (sleep_accel > sleep_temp)
> +		sleep = sleep_accel;
> +	else
> +		sleep = sleep_temp;
> +	if (sleep)
> +		msleep(sleep);
> +	return ret;
> +}
> +
>  static int inv_icm42600_accel_read_sensor(struct inv_icm42600_state *st,
>  					  struct iio_chan_spec const *chan,
>  					  int16_t *val)
> @@ -248,7 +321,12 @@ static int inv_icm42600_accel_write_odr(struct inv_icm42600_state *st,
>  	mutex_lock(&st->lock);
>  
>  	ret = inv_icm42600_set_accel_conf(st, &conf, NULL);
> +	if (ret)
> +		goto out_unlock;
> +	inv_icm42600_buffer_update_fifo_period(st);
> +	inv_icm42600_buffer_update_watermark(st);
>  
> +out_unlock:
>  	mutex_unlock(&st->lock);
>  	pm_runtime_mark_last_busy(dev);
>  	pm_runtime_put_autosuspend(dev);
> @@ -563,12 +641,51 @@ static int inv_icm42600_accel_write_raw_get_fmt(struct iio_dev *indio_dev,
>  	}
>  }
>  
> +static int inv_icm42600_accel_hwfifo_set_watermark(struct iio_dev *indio_dev,
> +						   unsigned int val)
> +{
> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	int ret;
> +
> +	mutex_lock(&st->lock);
> +
> +	st->fifo.watermark.accel = val;
> +	ret = inv_icm42600_buffer_update_watermark(st);
> +
> +	mutex_unlock(&st->lock);
> +
> +	return ret;
> +}
> +
> +static int inv_icm42600_accel_hwfifo_flush(struct iio_dev *indio_dev,
> +					   unsigned int count)
> +{
> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	int ret;
> +
> +	if (count == 0)
> +		return 0;
> +
> +	mutex_lock(&st->lock);
> +
> +	ret = inv_icm42600_buffer_hwfifo_flush(st, count);
> +	if (!ret)
> +		ret = st->fifo.nb.accel;
> +
> +	mutex_unlock(&st->lock);
> +
> +	return ret;
> +}
> +
>  static const struct iio_info inv_icm42600_accel_info = {
>  	.read_raw = inv_icm42600_accel_read_raw,
>  	.read_avail = inv_icm42600_accel_read_avail,
>  	.write_raw = inv_icm42600_accel_write_raw,
>  	.write_raw_get_fmt = inv_icm42600_accel_write_raw_get_fmt,
>  	.debugfs_reg_access = inv_icm42600_debugfs_reg,
> +	.update_scan_mode = inv_icm42600_accel_update_scan_mode,
> +	.hwfifo_set_watermark = inv_icm42600_accel_hwfifo_set_watermark,
> +	.hwfifo_flush_to_buffer = inv_icm42600_accel_hwfifo_flush,
>  };
>  
>  int inv_icm42600_accel_init(struct inv_icm42600_state *st)
> @@ -576,6 +693,7 @@ int inv_icm42600_accel_init(struct inv_icm42600_state *st)
>  	struct device *dev = regmap_get_device(st->map);
>  	const char *name;
>  	struct iio_dev *indio_dev;
> +	struct iio_buffer *buffer;
>  
>  	name = devm_kasprintf(dev, GFP_KERNEL, "%s-accel", st->name);
>  	if (!name)
> @@ -585,14 +703,54 @@ int inv_icm42600_accel_init(struct inv_icm42600_state *st)
>  	if (!indio_dev)
>  		return -ENOMEM;
>  
> +	buffer = devm_iio_kfifo_allocate(dev);
> +	if (!buffer)
> +		return -ENOMEM;
> +
>  	iio_device_set_drvdata(indio_dev, st);
>  	indio_dev->dev.parent = dev;
>  	indio_dev->name = name;
>  	indio_dev->info = &inv_icm42600_accel_info;
> -	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
>  	indio_dev->channels = inv_icm42600_accel_channels;
>  	indio_dev->num_channels = ARRAY_SIZE(inv_icm42600_accel_channels);
> +	indio_dev->available_scan_masks = inv_icm42600_accel_scan_masks;
> +	indio_dev->setup_ops = &inv_icm42600_buffer_ops;
> +
> +	iio_device_attach_buffer(indio_dev, buffer);
>  
>  	st->indio_accel = indio_dev;
>  	return devm_iio_device_register(dev, st->indio_accel);
>  }
> +
> +int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev)
> +{
> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	ssize_t i, size;
> +	const void *accel, *gyro, *timestamp;
> +	const int8_t *temp;
> +	unsigned int odr;
> +	struct inv_icm42600_accel_buffer buffer = {
> +		.padding = 0,
> +	};
> +
> +	/* parse all fifo packets */
> +	for (i = 0; i < st->fifo.count; i += size) {
> +		size = inv_icm42600_fifo_decode_packet(&st->fifo.data[i],
> +				&accel, &gyro, &temp, &timestamp, &odr);
> +		/* quit if error or FIFO is empty */
> +		if (size <= 0)
> +			return size;
> +
> +		/* skip packet if no accel data or data is invalid */
> +		if (accel == NULL || !inv_icm42600_fifo_is_data_valid(accel))
> +			continue;
> +
> +		/* fill and push data buffer */
> +		memcpy(&buffer.accel, accel, sizeof(buffer.accel));
> +		buffer.temp = temp ? *temp : 0;
> +		iio_push_to_buffers(indio_dev, &buffer);
> +	}
> +
> +	return 0;
> +}
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
> new file mode 100644
> index 000000000000..c91075f62231
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
> @@ -0,0 +1,555 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2020 Invensense, Inc.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/device.h>
> +#include <linux/mutex.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/delay.h>
> +#include <linux/math64.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/buffer.h>
> +
> +#include "inv_icm42600.h"
> +#include "inv_icm42600_buffer.h"
> +
> +/* FIFO header: 1 byte */
> +#define INV_ICM42600_FIFO_HEADER_MSG		BIT(7)
> +#define INV_ICM42600_FIFO_HEADER_ACCEL		BIT(6)
> +#define INV_ICM42600_FIFO_HEADER_GYRO		BIT(5)
> +#define INV_ICM42600_FIFO_HEADER_TMST_FSYNC	GENMASK(3, 2)
> +#define INV_ICM42600_FIFO_HEADER_ODR_ACCEL	BIT(1)
> +#define INV_ICM42600_FIFO_HEADER_ODR_GYRO	BIT(0)
> +
> +struct inv_icm42600_fifo_1sensor_packet {
> +	uint8_t header;
> +	struct inv_icm42600_fifo_sensor_data data;
> +	int8_t temp;
> +} __packed;
> +#define INV_ICM42600_FIFO_1SENSOR_PACKET_SIZE		8
> +
> +struct inv_icm42600_fifo_2sensors_packet {
> +	uint8_t header;
> +	struct inv_icm42600_fifo_sensor_data accel;
> +	struct inv_icm42600_fifo_sensor_data gyro;
> +	int8_t temp;
> +	__be16 timestamp;
> +} __packed;
> +#define INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE		16
> +
> +ssize_t inv_icm42600_fifo_decode_packet(const void *packet, const void **accel,
> +					const void **gyro, const int8_t **temp,
> +					const void **timestamp, unsigned int *odr)
> +{
> +	const struct inv_icm42600_fifo_1sensor_packet *pack1 = packet;
> +	const struct inv_icm42600_fifo_2sensors_packet *pack2 = packet;
> +	uint8_t header = *((const uint8_t *)packet);
> +
> +	/* FIFO empty */
> +	if (header & INV_ICM42600_FIFO_HEADER_MSG) {
> +		*accel = NULL;
> +		*gyro = NULL;
> +		*temp = NULL;
> +		*timestamp = NULL;
> +		*odr = 0;
> +		return 0;
> +	}
> +
> +	/* handle odr flags */
> +	*odr = 0;
> +	if (header & INV_ICM42600_FIFO_HEADER_ODR_GYRO)
> +		*odr |= INV_ICM42600_SENSOR_GYRO;
> +	if (header & INV_ICM42600_FIFO_HEADER_ODR_ACCEL)
> +		*odr |= INV_ICM42600_SENSOR_ACCEL;
> +
> +	/* accel + gyro */
> +	if ((header & INV_ICM42600_FIFO_HEADER_ACCEL) &&
> +	    (header & INV_ICM42600_FIFO_HEADER_GYRO)) {
> +		*accel = &pack2->accel;
> +		*gyro = &pack2->gyro;
> +		*temp = &pack2->temp;
> +		*timestamp = &pack2->timestamp;
> +		return INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE;
> +	}
> +
> +	/* accel only */
> +	if (header & INV_ICM42600_FIFO_HEADER_ACCEL) {
> +		*accel = &pack1->data;
> +		*gyro = NULL;
> +		*temp = &pack1->temp;
> +		*timestamp = NULL;
> +		return INV_ICM42600_FIFO_1SENSOR_PACKET_SIZE;
> +	}
> +
> +	/* gyro only */
> +	if (header & INV_ICM42600_FIFO_HEADER_GYRO) {
> +		*accel = NULL;
> +		*gyro = &pack1->data;
> +		*temp = &pack1->temp;
> +		*timestamp = NULL;
> +		return INV_ICM42600_FIFO_1SENSOR_PACKET_SIZE;
> +	}
> +
> +	/* invalid packet if here */
> +	return -EINVAL;
> +}
> +
> +void inv_icm42600_buffer_update_fifo_period(struct inv_icm42600_state *st)
> +{
> +	uint32_t period_gyro, period_accel, period;
> +
> +	if (st->fifo.en & INV_ICM42600_SENSOR_GYRO)
> +		period_gyro = inv_icm42600_odr_to_period(st->conf.gyro.odr);
> +	else
> +		period_gyro = U32_MAX;
> +
> +	if (st->fifo.en & INV_ICM42600_SENSOR_ACCEL)
> +		period_accel = inv_icm42600_odr_to_period(st->conf.accel.odr);
> +	else
> +		period_accel = U32_MAX;
> +
> +	if (period_gyro <= period_accel)
> +		period = period_gyro;
> +	else
> +		period = period_accel;
> +
> +	st->fifo.period = period;
> +}
> +
> +int inv_icm42600_buffer_set_fifo_en(struct inv_icm42600_state *st,
> +				    unsigned int fifo_en)
> +{
> +	unsigned int mask, val;
> +	int ret;
> +
> +	/* update only FIFO EN bits */
> +	mask = INV_ICM42600_FIFO_CONFIG1_TMST_FSYNC_EN |
> +		INV_ICM42600_FIFO_CONFIG1_TEMP_EN |
> +		INV_ICM42600_FIFO_CONFIG1_GYRO_EN |
> +		INV_ICM42600_FIFO_CONFIG1_ACCEL_EN;
> +
> +	val = 0;
> +	if (fifo_en & INV_ICM42600_SENSOR_GYRO)
> +		val |= INV_ICM42600_FIFO_CONFIG1_GYRO_EN;
> +	if (fifo_en & INV_ICM42600_SENSOR_ACCEL)
> +		val |= INV_ICM42600_FIFO_CONFIG1_ACCEL_EN;
> +	if (fifo_en & INV_ICM42600_SENSOR_TEMP)
> +		val |= INV_ICM42600_FIFO_CONFIG1_TEMP_EN;
> +
> +	ret = regmap_update_bits(st->map, INV_ICM42600_REG_FIFO_CONFIG1,
> +				 mask, val);
> +	if (ret)
> +		return ret;
> +
> +	st->fifo.en = fifo_en;
> +	inv_icm42600_buffer_update_fifo_period(st);
> +
> +	return 0;
> +}
> +
> +static size_t inv_icm42600_get_packet_size(unsigned int fifo_en)
> +{
> +	size_t packet_size;
> +
> +	if ((fifo_en & INV_ICM42600_SENSOR_GYRO) &&
> +	    (fifo_en & INV_ICM42600_SENSOR_ACCEL))
> +		packet_size = INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE;
> +	else
> +		packet_size = INV_ICM42600_FIFO_1SENSOR_PACKET_SIZE;
> +
> +	return packet_size;
> +}
> +
> +static unsigned int inv_icm42600_wm_truncate(unsigned int watermark,
> +					     size_t packet_size)
> +{
> +	size_t wm_size;
> +	unsigned int wm;
> +
> +	wm_size = watermark * packet_size;
> +	if (wm_size > INV_ICM42600_FIFO_WATERMARK_MAX)
> +		wm_size = INV_ICM42600_FIFO_WATERMARK_MAX;
> +
> +	wm = wm_size / packet_size;
> +
> +	return wm;
> +}
> +

I think some overview docs on how this is working would be good.
Set out the aims for the watermark selected and how it is achieved.

> +int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st)
> +{
> +	size_t packet_size, wm_size;
> +	unsigned int wm_gyro, wm_accel, watermark;
> +	uint32_t period_gyro, period_accel, period;
> +	int64_t latency_gyro, latency_accel, latency;
> +	bool restore;
> +	__le16 raw_wm;
> +	int ret;
> +
> +	packet_size = inv_icm42600_get_packet_size(st->fifo.en);
> +
> +	/* get minimal latency, depending on sensor watermark and odr */
> +	wm_gyro = inv_icm42600_wm_truncate(st->fifo.watermark.gyro,
> +					   packet_size);
> +	wm_accel = inv_icm42600_wm_truncate(st->fifo.watermark.accel,
> +					    packet_size);
> +	period_gyro = inv_icm42600_odr_to_period(st->conf.gyro.odr);
> +	period_accel = inv_icm42600_odr_to_period(st->conf.accel.odr);
> +	latency_gyro = (int64_t)period_gyro * (int64_t)wm_gyro;
> +	latency_accel = (int64_t)period_accel * (int64_t)wm_accel;
> +	if (latency_gyro == 0) {
> +		latency = latency_accel;
> +		watermark = wm_accel;
> +	} else if (latency_accel == 0) {
> +		latency = latency_gyro;
> +		watermark = wm_gyro;
> +	} else {
> +		/* compute the smallest latency that is a multiple of both */
> +		if (latency_gyro <= latency_accel) {
> +			latency = latency_gyro;
> +			latency -= latency_accel % latency_gyro;
> +		} else {
> +			latency = latency_accel;
> +			latency -= latency_gyro % latency_accel;
> +		}
> +		/* use the shortest period */
> +		if (period_gyro <= period_accel)
> +			period = period_gyro;
> +		else
> +			period = period_accel;
> +		/* all this works because periods are multiple of each others */
> +		watermark = div_s64(latency, period);
> +		if (watermark < 1)
> +			watermark = 1;
> +	}
> +	wm_size = watermark * packet_size;
> +
> +	/* changing FIFO watermark requires to turn off watermark interrupt */
> +	ret = regmap_update_bits_check(st->map, INV_ICM42600_REG_INT_SOURCE0,
> +				       INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN,
> +				       0, &restore);
> +	if (ret)
> +		return ret;
> +
> +	raw_wm = INV_ICM42600_FIFO_WATERMARK_VAL(wm_size);
> +	memcpy(st->buffer, &raw_wm, sizeof(raw_wm));
> +	ret = regmap_bulk_write(st->map, INV_ICM42600_REG_FIFO_WATERMARK,
> +				st->buffer, sizeof(raw_wm));
> +	if (ret)
> +		return ret;
> +
> +	/* restore watermark interrupt */
> +	if (restore) {
> +		ret = regmap_update_bits(st->map, INV_ICM42600_REG_INT_SOURCE0,
> +					 INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN,
> +					 INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int inv_icm42600_buffer_preenable(struct iio_dev *indio_dev)
> +{
> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	struct device *dev = regmap_get_device(st->map);
> +
> +	pm_runtime_get_sync(dev);
> +
> +	return 0;
> +}
> +
> +/*
> + * update_scan_mode callback is turning sensors on and setting data FIFO enable
> + * bits.
> + */
> +static int inv_icm42600_buffer_postenable(struct iio_dev *indio_dev)
> +{
> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	int ret;
> +
> +	mutex_lock(&st->lock);
> +
> +	/* exit if FIFO is already on */
> +	if (st->fifo.on) {
> +		ret = 0;
> +		goto out_on;
> +	}
> +
> +	/* set FIFO threshold interrupt */
> +	ret = regmap_update_bits(st->map, INV_ICM42600_REG_INT_SOURCE0,
> +				 INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN,
> +				 INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN);
> +	if (ret)
> +		goto out_unlock;
> +
> +	/* flush FIFO data */
> +	ret = regmap_write(st->map, INV_ICM42600_REG_SIGNAL_PATH_RESET,
> +			   INV_ICM42600_SIGNAL_PATH_RESET_FIFO_FLUSH);
> +	if (ret)
> +		goto out_unlock;
> +
> +	/* set FIFO in streaming mode */
> +	ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG,
> +			   INV_ICM42600_FIFO_CONFIG_STREAM);
> +	if (ret)
> +		goto out_unlock;
> +
> +	/* workaround: first read of FIFO count after reset is always 0 */
> +	ret = regmap_bulk_read(st->map, INV_ICM42600_REG_FIFO_COUNT, st->buffer, 2);
> +	if (ret)
> +		goto out_unlock;
> +
> +out_on:
> +	/* increase FIFO on counter */
> +	st->fifo.on++;
> +out_unlock:
> +	mutex_unlock(&st->lock);
> +	return ret;
> +}
> +
> +static int inv_icm42600_buffer_predisable(struct iio_dev *indio_dev)
> +{
> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	int ret;
> +
> +	mutex_lock(&st->lock);
> +
> +	/* exit if there are several sensors using the FIFO */
> +	if (st->fifo.on > 1) {
> +		ret = 0;
> +		goto out_off;
> +	}
> +
> +	/* set FIFO in bypass mode */
> +	ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG,
> +			   INV_ICM42600_FIFO_CONFIG_BYPASS);
> +	if (ret)
> +		goto out_unlock;
> +
> +	/* flush FIFO data */
> +	ret = regmap_write(st->map, INV_ICM42600_REG_SIGNAL_PATH_RESET,
> +			   INV_ICM42600_SIGNAL_PATH_RESET_FIFO_FLUSH);
> +	if (ret)
> +		goto out_unlock;
> +
> +	/* disable FIFO threshold interrupt */
> +	ret = regmap_update_bits(st->map, INV_ICM42600_REG_INT_SOURCE0,
> +				 INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN, 0);
> +	if (ret)
> +		goto out_unlock;
> +
> +out_off:
> +	/* decrease FIFO on counter */
> +	st->fifo.on--;
> +out_unlock:
> +	mutex_unlock(&st->lock);
> +	return ret;
> +}
> +
> +static int inv_icm42600_buffer_postdisable(struct iio_dev *indio_dev)
> +{
> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	struct device *dev = regmap_get_device(st->map);
> +	unsigned int sensor;
> +	unsigned int *watermark;
> +	struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
> +	unsigned int sleep_temp = 0;
> +	unsigned int sleep_sensor = 0;
> +	unsigned int sleep;
> +	int ret;
> +
> +	if (indio_dev == st->indio_gyro) {
> +		sensor = INV_ICM42600_SENSOR_GYRO;
> +		watermark = &st->fifo.watermark.gyro;
> +	} else if (indio_dev == st->indio_accel) {
> +		sensor = INV_ICM42600_SENSOR_ACCEL;
> +		watermark = &st->fifo.watermark.accel;
> +	} else {
> +		return -EINVAL;
> +	}
> +
> +	mutex_lock(&st->lock);
> +
> +	ret = inv_icm42600_buffer_set_fifo_en(st, st->fifo.en & ~sensor);
> +	if (ret)
> +		goto out_unlock;
> +
> +	*watermark = 0;
> +	ret = inv_icm42600_buffer_update_watermark(st);
> +	if (ret)
> +		goto out_unlock;
> +
> +	conf.mode = INV_ICM42600_SENSOR_MODE_OFF;
> +	if (sensor == INV_ICM42600_SENSOR_GYRO)
> +		ret = inv_icm42600_set_gyro_conf(st, &conf, &sleep_sensor);
> +	else
> +		ret = inv_icm42600_set_accel_conf(st, &conf, &sleep_sensor);
> +	if (ret)
> +		goto out_unlock;
> +
> +	/* if FIFO is off, turn temperature off */
> +	if (!st->fifo.on)
> +		ret = inv_icm42600_set_temp_conf(st, false, &sleep_temp);
> +
> +out_unlock:
> +	mutex_unlock(&st->lock);
> +
> +	/* sleep maximum required time */
> +	if (sleep_sensor > sleep_temp)
> +		sleep = sleep_sensor;
> +	else
> +		sleep = sleep_temp;
> +	if (sleep)
> +		msleep(sleep);
> +
> +	pm_runtime_mark_last_busy(dev);
> +	pm_runtime_put_autosuspend(dev);
> +
> +	return ret;
> +}
> +
> +const struct iio_buffer_setup_ops inv_icm42600_buffer_ops = {
> +	.preenable = inv_icm42600_buffer_preenable,
> +	.postenable = inv_icm42600_buffer_postenable,

We've been slowly eroding the difference between preenable and posteenable.
Would be good to understand why you need to define both?

> +	.predisable = inv_icm42600_buffer_predisable,
> +	.postdisable = inv_icm42600_buffer_postdisable,
> +};
> +
> +int inv_icm42600_buffer_fifo_read(struct inv_icm42600_state *st,
> +				  unsigned int max)
> +{
> +	size_t max_count;
> +	__be16 *raw_fifo_count;
> +	ssize_t i, size;
> +	const void *accel, *gyro, *timestamp;
> +	const int8_t *temp;
> +	unsigned int odr;
> +	int ret;
> +
> +	/* reset all samples counters */
> +	st->fifo.count = 0;
> +	st->fifo.nb.gyro = 0;
> +	st->fifo.nb.accel = 0;
> +	st->fifo.nb.total = 0;
> +
> +	/* compute maximum FIFO read size */
> +	if (max == 0)
> +		max_count = sizeof(st->fifo.data);
> +	else
> +		max_count = max * inv_icm42600_get_packet_size(st->fifo.en);
> +
> +	/* read FIFO count value */
> +	raw_fifo_count = (__be16 *)st->buffer;
> +	ret = regmap_bulk_read(st->map, INV_ICM42600_REG_FIFO_COUNT,
> +			       raw_fifo_count, sizeof(*raw_fifo_count));
> +	if (ret)
> +		return ret;
> +	st->fifo.count = be16_to_cpup(raw_fifo_count);
> +
> +	/* check and clamp FIFO count value */
> +	if (st->fifo.count == 0)
> +		return 0;
> +	if (st->fifo.count > max_count)
> +		st->fifo.count = max_count;
> +
> +	/* read all FIFO data in internal buffer */
> +	ret = regmap_noinc_read(st->map, INV_ICM42600_REG_FIFO_DATA,
> +				st->fifo.data, st->fifo.count);
> +	if (ret)
> +		return ret;
> +
> +	/* compute number of samples for each sensor */
> +	for (i = 0; i < st->fifo.count; i += size) {
> +		size = inv_icm42600_fifo_decode_packet(&st->fifo.data[i],
> +				&accel, &gyro, &temp, &timestamp, &odr);
> +		if (size <= 0)
> +			break;
> +		if (gyro != NULL && inv_icm42600_fifo_is_data_valid(gyro))
> +			st->fifo.nb.gyro++;
> +		if (accel != NULL && inv_icm42600_fifo_is_data_valid(accel))
> +			st->fifo.nb.accel++;
> +		st->fifo.nb.total++;
> +	}
> +
> +	return 0;
> +}
> +
> +int inv_icm42600_buffer_fifo_parse(struct inv_icm42600_state *st)
> +{
> +	int ret;
> +
> +	if (st->fifo.nb.total == 0)
> +		return 0;
> +
> +	if (st->fifo.nb.gyro > 0) {
> +		ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (st->fifo.nb.accel > 0) {
> +		ret = inv_icm42600_accel_parse_fifo(st->indio_accel);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +int inv_icm42600_buffer_hwfifo_flush(struct inv_icm42600_state *st,
> +				     unsigned int count)
> +{
> +	int ret;
> +
> +	ret = inv_icm42600_buffer_fifo_read(st, count);
> +	if (ret)
> +		return ret;
Definitely searching my memory for how this works in the core, so
I may have it wrong.

This is a bit unusual (I think).  The intent of the flush
is to read up to 'n' bytes because someone just did a read on the buffer
or select, and there was data in the hwfifo capable of satisfying the read
even though we haven't yet reached the watermark.

Given both sensor types are coming from one buffer, do we have a potential
issue here or under serving even though data is available?

The case I worry may be served late is when an poll / select
is waiting for sufficient data.

So what should we be doing?  We want to guarantee to provide data
for each sensor type if it's in the hwfifo. As such we could keep reading
until we have enough, but that could cause some issues if the two data rates
are very different (overflow on the other kfifo)

Maybe what you have here is the best we can do. 

I'm assuming the watermark level has a similar problem.  One value represents
the sum of the two types of data.

> +
> +	if (st->fifo.nb.total == 0)
> +		return 0;
> +
> +	if (st->fifo.nb.gyro > 0) {
> +		ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (st->fifo.nb.accel > 0) {
> +		ret = inv_icm42600_accel_parse_fifo(st->indio_accel);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +int inv_icm42600_buffer_init(struct inv_icm42600_state *st)
> +{
> +	unsigned int val;
> +	int ret;
> +
> +	/*
> +	 * Default FIFO configuration (bits 7 to 5)
> +	 * - use invalid value
> +	 * - FIFO count in bytes
> +	 * - FIFO count in big endian
> +	 */
> +	val = INV_ICM42600_INTF_CONFIG0_FIFO_COUNT_ENDIAN;
> +	ret = regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG0,
> +				 GENMASK(7, 5), val);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Enable FIFO partial read and continuous watermark interrupt.
> +	 * Disable all FIFO EN bits.
> +	 */
> +	val = INV_ICM42600_FIFO_CONFIG1_RESUME_PARTIAL_RD |
> +	      INV_ICM42600_FIFO_CONFIG1_WM_GT_TH;
> +	return regmap_update_bits(st->map, INV_ICM42600_REG_FIFO_CONFIG1,
> +				  GENMASK(6, 5) | GENMASK(3, 0), val);
> +}
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h
> new file mode 100644
> index 000000000000..de2a3949dcc7
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h
> @@ -0,0 +1,98 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2020 Invensense, Inc.
> + */
> +
> +#ifndef INV_ICM42600_BUFFER_H_
> +#define INV_ICM42600_BUFFER_H_
> +
> +#include <linux/kernel.h>
> +#include <linux/bits.h>
> +
> +struct inv_icm42600_state;
> +
> +#define INV_ICM42600_SENSOR_GYRO	BIT(0)
> +#define INV_ICM42600_SENSOR_ACCEL	BIT(1)
> +#define INV_ICM42600_SENSOR_TEMP	BIT(2)
> +
> +/**
> + * struct inv_icm42600_fifo - FIFO state variables
> + * @on:		reference counter for FIFO on.
> + * @en:		bits field of INV_ICM42600_SENSOR_* for FIFO EN bits.
> + * @period:	FIFO internal period.
> + * @watermark:	watermark configuration values for accel and gyro.
> + * @count:	number of bytes in the FIFO data buffer.
> + * @nb:		gyro, accel and total samples in the FIFO data buffer.
> + * @data:	FIFO data buffer aligned for DMA (2kB + 32 bytes of read cache).
> + */
> +struct inv_icm42600_fifo {
> +	unsigned int on;
> +	unsigned int en;
> +	uint32_t period;
> +	struct {
> +		unsigned int gyro;
> +		unsigned int accel;
> +	} watermark;
> +	size_t count;
> +	struct {
> +		size_t gyro;
> +		size_t accel;
> +		size_t total;
> +	} nb;
> +	uint8_t data[2080] ____cacheline_aligned;
> +};
> +
> +/* FIFO data packet */
> +struct inv_icm42600_fifo_sensor_data {
> +	__be16 x;
> +	__be16 y;
> +	__be16 z;
> +} __packed;

Why packed?  Should be anyway I think.

> +#define INV_ICM42600_FIFO_DATA_INVALID		-32768
> +
> +static inline int16_t inv_icm42600_fifo_get_sensor_data(__be16 d)
> +{
> +	return be16_to_cpu(d);
> +}
> +
> +static inline bool
> +inv_icm42600_fifo_is_data_valid(const struct inv_icm42600_fifo_sensor_data *s)
> +{
> +	int16_t x, y, z;
> +
> +	x = inv_icm42600_fifo_get_sensor_data(s->x);
> +	y = inv_icm42600_fifo_get_sensor_data(s->y);
> +	z = inv_icm42600_fifo_get_sensor_data(s->z);
> +
> +	if (x == INV_ICM42600_FIFO_DATA_INVALID &&
> +	    y == INV_ICM42600_FIFO_DATA_INVALID &&
> +	    z == INV_ICM42600_FIFO_DATA_INVALID)
> +		return false;
> +
> +	return true;
> +}
> +
> +ssize_t inv_icm42600_fifo_decode_packet(const void *packet, const void **accel,
> +					const void **gyro, const int8_t **temp,
> +					const void **timestamp, unsigned int *odr);
> +
> +extern const struct iio_buffer_setup_ops inv_icm42600_buffer_ops;
> +
> +int inv_icm42600_buffer_init(struct inv_icm42600_state *st);
> +
> +void inv_icm42600_buffer_update_fifo_period(struct inv_icm42600_state *st);
> +
> +int inv_icm42600_buffer_set_fifo_en(struct inv_icm42600_state *st,
> +				    unsigned int fifo_en);
> +
> +int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st);
> +
> +int inv_icm42600_buffer_fifo_read(struct inv_icm42600_state *st,
> +				  unsigned int max);
> +
> +int inv_icm42600_buffer_fifo_parse(struct inv_icm42600_state *st);
> +
> +int inv_icm42600_buffer_hwfifo_flush(struct inv_icm42600_state *st,
> +				     unsigned int count);
> +
> +#endif
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
> index 246c1eb52231..6f1c1eb83953 100644
> --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
> @@ -18,6 +18,7 @@
>  #include <linux/iio/iio.h>
>  
>  #include "inv_icm42600.h"
> +#include "inv_icm42600_buffer.h"
>  
>  static const struct regmap_range_cfg inv_icm42600_regmap_ranges[] = {
>  	{
> @@ -429,6 +430,18 @@ static irqreturn_t inv_icm42600_irq_handler(int irq, void *_data)
>  	if (status & INV_ICM42600_INT_STATUS_FIFO_FULL)
>  		dev_warn(dev, "FIFO full data lost!\n");
>  
> +	/* FIFO threshold reached */
> +	if (status & INV_ICM42600_INT_STATUS_FIFO_THS) {
> +		ret = inv_icm42600_buffer_fifo_read(st, 0);
> +		if (ret) {
> +			dev_err(dev, "FIFO read error %d\n", ret);
> +			goto out_unlock;
> +		}
> +		ret = inv_icm42600_buffer_fifo_parse(st);
> +		if (ret)
> +			dev_err(dev, "FIFO parsing error %d\n", ret);
> +	}
> +
>  out_unlock:
>  	mutex_unlock(&st->lock);
>  	return IRQ_HANDLED;
> @@ -600,6 +613,10 @@ int inv_icm42600_core_probe(struct regmap *regmap, int chip, int irq,
>  	if (ret)
>  		return ret;
>  
> +	ret = inv_icm42600_buffer_init(st);
> +	if (ret)
> +		return ret;
> +
>  	ret = inv_icm42600_gyro_init(st);
>  	if (ret)
>  		return ret;
> @@ -645,6 +662,14 @@ static int __maybe_unused inv_icm42600_suspend(struct device *dev)
>  		goto out_unlock;
>  	}
>  
> +	/* disable FIFO data streaming */
> +	if (st->fifo.on) {
> +		ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG,
> +				   INV_ICM42600_FIFO_CONFIG_BYPASS);
> +		if (ret)
> +			goto out_unlock;
> +	}
> +
>  	ret = inv_icm42600_set_pwr_mgmt0(st, INV_ICM42600_SENSOR_MODE_OFF,
>  					 INV_ICM42600_SENSOR_MODE_OFF, false,
>  					 NULL);
> @@ -684,6 +709,11 @@ static int __maybe_unused inv_icm42600_resume(struct device *dev)
>  	if (ret)
>  		goto out_unlock;
>  
> +	/* restore FIFO data streaming */
> +	if (st->fifo.on)
> +		ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG,
> +				   INV_ICM42600_FIFO_CONFIG_STREAM);
> +
>  out_unlock:
>  	mutex_unlock(&st->lock);
>  	return ret;
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
> index 38654e0d217b..b05c33876b8d 100644
> --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
> @@ -11,9 +11,12 @@
>  #include <linux/delay.h>
>  #include <linux/math64.h>
>  #include <linux/iio/iio.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/kfifo_buf.h>
>  
>  #include "inv_icm42600.h"
>  #include "inv_icm42600_temp.h"
> +#include "inv_icm42600_buffer.h"
>  
>  #define INV_ICM42600_GYRO_CHAN(_modifier, _index, _ext_info)		\
>  	{								\
> @@ -64,6 +67,76 @@ static const struct iio_chan_spec inv_icm42600_gyro_channels[] = {
>  	INV_ICM42600_TEMP_CHAN(INV_ICM42600_GYRO_SCAN_TEMP),
>  };
>  
> +/* IIO buffer data: 8 bytes */
> +struct inv_icm42600_gyro_buffer {
> +	struct inv_icm42600_fifo_sensor_data gyro;
> +	int8_t temp;
> +	uint8_t padding;
> +};
> +
> +#define INV_ICM42600_SCAN_MASK_GYRO_3AXIS				\
> +	(BIT(INV_ICM42600_GYRO_SCAN_X) |				\
> +	BIT(INV_ICM42600_GYRO_SCAN_Y) |					\
> +	BIT(INV_ICM42600_GYRO_SCAN_Z))
> +
> +#define INV_ICM42600_SCAN_MASK_TEMP	BIT(INV_ICM42600_GYRO_SCAN_TEMP)
> +
> +static const unsigned long inv_icm42600_gyro_scan_masks[] = {
> +	/* 3-axis gyro + temperature */
> +	INV_ICM42600_SCAN_MASK_GYRO_3AXIS | INV_ICM42600_SCAN_MASK_TEMP,
> +	0,
> +};
> +
> +/* enable gyroscope sensor and FIFO write */
> +static int inv_icm42600_gyro_update_scan_mode(struct iio_dev *indio_dev,
> +					      const unsigned long *scan_mask)
> +{
> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
> +	unsigned int fifo_en = 0;
> +	unsigned int sleep_gyro = 0;
> +	unsigned int sleep_temp = 0;
> +	unsigned int sleep;
> +	int ret;
> +
> +	mutex_lock(&st->lock);
> +
> +	if (*scan_mask & INV_ICM42600_SCAN_MASK_TEMP) {
> +		/* enable temp sensor */
> +		ret = inv_icm42600_set_temp_conf(st, true, &sleep_temp);
> +		if (ret)
> +			goto out_unlock;
> +		fifo_en |= INV_ICM42600_SENSOR_TEMP;
> +	}
> +
> +	if (*scan_mask & INV_ICM42600_SCAN_MASK_GYRO_3AXIS) {
> +		/* enable gyro sensor */
> +		conf.mode = INV_ICM42600_SENSOR_MODE_LOW_NOISE;
> +		ret = inv_icm42600_set_gyro_conf(st, &conf, &sleep_gyro);
> +		if (ret)
> +			goto out_unlock;
> +		fifo_en |= INV_ICM42600_SENSOR_GYRO;
> +	}
> +
> +	/* update data FIFO write */
> +	ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en);
> +	if (ret)
> +		goto out_unlock;
> +
> +	ret = inv_icm42600_buffer_update_watermark(st);
> +
> +out_unlock:
> +	mutex_unlock(&st->lock);
> +	/* sleep maximum required time */
> +	if (sleep_gyro > sleep_temp)
> +		sleep = sleep_gyro;
> +	else
> +		sleep = sleep_temp;
> +	if (sleep)
> +		msleep(sleep);
> +	return ret;
> +}
> +
>  static int inv_icm42600_gyro_read_sensor(struct inv_icm42600_state *st,
>  					 struct iio_chan_spec const *chan,
>  					 int16_t *val)
> @@ -260,7 +333,12 @@ static int inv_icm42600_gyro_write_odr(struct inv_icm42600_state *st,
>  	mutex_lock(&st->lock);
>  
>  	ret = inv_icm42600_set_gyro_conf(st, &conf, NULL);
> +	if (ret)
> +		goto out_unlock;
> +	inv_icm42600_buffer_update_fifo_period(st);
> +	inv_icm42600_buffer_update_watermark(st);
>  
> +out_unlock:
>  	mutex_unlock(&st->lock);
>  	pm_runtime_mark_last_busy(dev);
>  	pm_runtime_put_autosuspend(dev);
> @@ -574,12 +652,51 @@ static int inv_icm42600_gyro_write_raw_get_fmt(struct iio_dev *indio_dev,
>  	}
>  }
>  
> +static int inv_icm42600_gyro_hwfifo_set_watermark(struct iio_dev *indio_dev,
> +						  unsigned int val)
> +{
> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	int ret;
> +
> +	mutex_lock(&st->lock);
> +
> +	st->fifo.watermark.gyro = val;
> +	ret = inv_icm42600_buffer_update_watermark(st);
> +
> +	mutex_unlock(&st->lock);
> +
> +	return ret;
> +}
> +
> +static int inv_icm42600_gyro_hwfifo_flush(struct iio_dev *indio_dev,
> +					  unsigned int count)
> +{

Nothing to do with this patch, but I realised reading this that we have
some 'unusual' use of the word flush here.  It's a straight forward
read function so not sure why we called it flush.

> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	int ret;
> +
> +	if (count == 0)
> +		return 0;
> +
> +	mutex_lock(&st->lock);
> +
> +	ret = inv_icm42600_buffer_hwfifo_flush(st, count);
> +	if (!ret)
> +		ret = st->fifo.nb.gyro;
> +
> +	mutex_unlock(&st->lock);
> +
> +	return ret;
> +}
> +
>  static const struct iio_info inv_icm42600_gyro_info = {
>  	.read_raw = inv_icm42600_gyro_read_raw,
>  	.read_avail = inv_icm42600_gyro_read_avail,
>  	.write_raw = inv_icm42600_gyro_write_raw,
>  	.write_raw_get_fmt = inv_icm42600_gyro_write_raw_get_fmt,
>  	.debugfs_reg_access = inv_icm42600_debugfs_reg,
> +	.update_scan_mode = inv_icm42600_gyro_update_scan_mode,
> +	.hwfifo_set_watermark = inv_icm42600_gyro_hwfifo_set_watermark,
> +	.hwfifo_flush_to_buffer = inv_icm42600_gyro_hwfifo_flush,
>  };
>  
>  int inv_icm42600_gyro_init(struct inv_icm42600_state *st)
> @@ -587,6 +704,7 @@ int inv_icm42600_gyro_init(struct inv_icm42600_state *st)
>  	struct device *dev = regmap_get_device(st->map);
>  	const char *name;
>  	struct iio_dev *indio_dev;
> +	struct iio_buffer *buffer;
>  
>  	name = devm_kasprintf(dev, GFP_KERNEL, "%s-gyro", st->name);
>  	if (!name)
> @@ -596,14 +714,54 @@ int inv_icm42600_gyro_init(struct inv_icm42600_state *st)
>  	if (!indio_dev)
>  		return -ENOMEM;
>  
> +	buffer = devm_iio_kfifo_allocate(dev);
> +	if (!buffer)
> +		return -ENOMEM;
> +
>  	iio_device_set_drvdata(indio_dev, st);
>  	indio_dev->dev.parent = dev;
>  	indio_dev->name = name;
>  	indio_dev->info = &inv_icm42600_gyro_info;
> -	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
>  	indio_dev->channels = inv_icm42600_gyro_channels;
>  	indio_dev->num_channels = ARRAY_SIZE(inv_icm42600_gyro_channels);
> +	indio_dev->available_scan_masks = inv_icm42600_gyro_scan_masks;
> +	indio_dev->setup_ops = &inv_icm42600_buffer_ops;
> +
> +	iio_device_attach_buffer(indio_dev, buffer);
>  
>  	st->indio_gyro = indio_dev;
>  	return devm_iio_device_register(dev, st->indio_gyro);
>  }
> +
> +int inv_icm42600_gyro_parse_fifo(struct iio_dev *indio_dev)
> +{
> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	ssize_t i, size;
> +	const void *accel, *gyro, *timestamp;
> +	const int8_t *temp;
> +	unsigned int odr;
> +	struct inv_icm42600_gyro_buffer buffer = {
> +		.padding = 0,

Might be worth a comment here or where the structure is defined
on why we make padding explicit.

> +	};
> +
> +	/* parse all fifo packets */
> +	for (i = 0; i < st->fifo.count; i += size) {
> +		size = inv_icm42600_fifo_decode_packet(&st->fifo.data[i],
> +				&accel, &gyro, &temp, &timestamp, &odr);
> +		/* quit if error or FIFO is empty */
> +		if (size <= 0)
> +			return size;
> +
> +		/* skip packet if no gyro data or data is invalid */
> +		if (gyro == NULL || !inv_icm42600_fifo_is_data_valid(gyro))
> +			continue;
> +
> +		/* fill and push data buffer */
> +		memcpy(&buffer.gyro, gyro, sizeof(buffer.gyro));
> +		buffer.temp = temp ? *temp : 0;
> +		iio_push_to_buffers(indio_dev, &buffer);
> +	}
> +
> +	return 0;
> +}


^ permalink raw reply

* Re: [PATCH v2 08/12] iio: imu: inv_icm42600: add device interrupt
From: Jonathan Cameron @ 2020-05-31 12:16 UTC (permalink / raw)
  To: Jean-Baptiste Maneyrol
  Cc: robh+dt, robh, mchehab+huawei, davem, gregkh, linux-iio,
	devicetree, linux-kernel
In-Reply-To: <20200527185711.21331-9-jmaneyrol@invensense.com>

On Wed, 27 May 2020 20:57:07 +0200
Jean-Baptiste Maneyrol <jmaneyrol@invensense.com> wrote:

> Add INT1 interrupt support. Support interrupt edge and level,
> active high or low. Push-pull or open-drain configurations.
> 
> Interrupt will be used to read data from the FIFO.
> 
> Signed-off-by: Jean-Baptiste Maneyrol <jmaneyrol@invensense.com>

Some nitpicks inline - all cases where a blank line would slightly
help readability.

J

> ---
>  drivers/iio/imu/inv_icm42600/inv_icm42600.h   |  2 +-
>  .../iio/imu/inv_icm42600/inv_icm42600_core.c  | 96 ++++++++++++++++++-
>  .../iio/imu/inv_icm42600/inv_icm42600_i2c.c   |  3 +-
>  .../iio/imu/inv_icm42600/inv_icm42600_spi.c   |  3 +-
>  4 files changed, 100 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h
> index c534acae0308..43749f56426c 100644
> --- a/drivers/iio/imu/inv_icm42600/inv_icm42600.h
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h
> @@ -372,7 +372,7 @@ int inv_icm42600_set_temp_conf(struct inv_icm42600_state *st, bool enable,
>  int inv_icm42600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg,
>  			     unsigned int writeval, unsigned int *readval);
>  
> -int inv_icm42600_core_probe(struct regmap *regmap, int chip,
> +int inv_icm42600_core_probe(struct regmap *regmap, int chip, int irq,
>  			    inv_icm42600_bus_setup bus_setup);
>  
>  int inv_icm42600_gyro_init(struct inv_icm42600_state *st);
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
> index e7f7835aca9b..246c1eb52231 100644
> --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
> @@ -9,8 +9,11 @@
>  #include <linux/slab.h>
>  #include <linux/delay.h>
>  #include <linux/mutex.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
>  #include <linux/regulator/consumer.h>
>  #include <linux/pm_runtime.h>
> +#include <linux/property.h>
>  #include <linux/regmap.h>
>  #include <linux/iio/iio.h>
>  
> @@ -409,6 +412,79 @@ static int inv_icm42600_setup(struct inv_icm42600_state *st,
>  	return inv_icm42600_set_conf(st, hw->conf);
>  }
>  
> +static irqreturn_t inv_icm42600_irq_handler(int irq, void *_data)
> +{
> +	struct inv_icm42600_state *st = _data;
> +	struct device *dev = regmap_get_device(st->map);
> +	unsigned int status;
> +	int ret;
> +
> +	mutex_lock(&st->lock);
> +
> +	ret = regmap_read(st->map, INV_ICM42600_REG_INT_STATUS, &status);
> +	if (ret)
> +		goto out_unlock;
> +
> +	/* FIFO full */
> +	if (status & INV_ICM42600_INT_STATUS_FIFO_FULL)
> +		dev_warn(dev, "FIFO full data lost!\n");
> +
> +out_unlock:
> +	mutex_unlock(&st->lock);
> +	return IRQ_HANDLED;
> +}
> +
> +/**
> + * inv_icm42600_irq_init() - initialize int pin and interrupt handler
> + * @st:		driver internal state
> + * @irq:	irq number
> + * @irq_type:	irq trigger type
> + * @open_drain:	true if irq is open drain, false for push-pull
> + *
> + * Returns 0 on success, a negative error code otherwise.
> + */
> +static int inv_icm42600_irq_init(struct inv_icm42600_state *st, int irq,
> +				 int irq_type, bool open_drain)
> +{
> +	struct device *dev = regmap_get_device(st->map);
> +	unsigned int val;
> +	int ret;
> +
> +	/* configure INT1 interrupt: default is active low on edge */
> +	switch (irq_type) {
> +	case IRQF_TRIGGER_RISING:
> +	case IRQF_TRIGGER_HIGH:
> +		val = INV_ICM42600_INT_CONFIG_INT1_ACTIVE_HIGH;
> +		break;
> +	default:
> +		val = INV_ICM42600_INT_CONFIG_INT1_ACTIVE_LOW;
> +		break;
> +	}

blank line here

> +	switch (irq_type) {
> +	case IRQF_TRIGGER_LOW:
> +	case IRQF_TRIGGER_HIGH:
> +		val |= INV_ICM42600_INT_CONFIG_INT1_LATCHED;
> +		break;
> +	default:
> +		break;
> +	}

blank line here.

> +	if (!open_drain)
> +		val |= INV_ICM42600_INT_CONFIG_INT1_PUSH_PULL;

blank line here

> +	ret = regmap_write(st->map, INV_ICM42600_REG_INT_CONFIG, val);
> +	if (ret)
> +		return ret;
> +
> +	/* Deassert async reset for proper INT pin operation (cf datasheet) */
> +	ret = regmap_update_bits(st->map, INV_ICM42600_REG_INT_CONFIG1,
> +				 INV_ICM42600_INT_CONFIG1_ASYNC_RESET, 0);
> +	if (ret)
> +		return ret;
> +
> +	return devm_request_threaded_irq(dev, irq, NULL,
> +					 inv_icm42600_irq_handler, irq_type,
> +					 "inv_icm42600", st);
> +}
> +
>  static int inv_icm42600_enable_regulator_vddio(struct inv_icm42600_state *st)
>  {
>  	int ret;
> @@ -453,11 +529,14 @@ static void inv_icm42600_disable_pm(void *_data)
>  	pm_runtime_disable(dev);
>  }
>  
> -int inv_icm42600_core_probe(struct regmap *regmap, int chip,
> +int inv_icm42600_core_probe(struct regmap *regmap, int chip, int irq,
>  			    inv_icm42600_bus_setup bus_setup)
>  {
>  	struct device *dev = regmap_get_device(regmap);
>  	struct inv_icm42600_state *st;
> +	struct irq_data *irq_desc;
> +	int irq_type;
> +	bool open_drain;
>  	int ret;
>  
>  	if (chip < 0 || chip >= INV_CHIP_NB) {
> @@ -465,6 +544,17 @@ int inv_icm42600_core_probe(struct regmap *regmap, int chip,
>  		return -ENODEV;
>  	}
>  
> +	/* get irq properties, set trigger falling by default */
> +	irq_desc = irq_get_irq_data(irq);
> +	if (!irq_desc) {
> +		dev_err(dev, "could not find IRQ %d\n", irq);
> +		return -EINVAL;
> +	}

nitpick: Blank line here

> +	irq_type = irqd_get_trigger_type(irq_desc);
> +	if (!irq_type)
> +		irq_type = IRQF_TRIGGER_FALLING;

blank line here.

> +	open_drain = device_property_read_bool(dev, "drive-open-drain");
> +
>  	st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
>  	if (!st)
>  		return -ENOMEM;
> @@ -518,6 +608,10 @@ int inv_icm42600_core_probe(struct regmap *regmap, int chip,
>  	if (ret)
>  		return ret;
>  
> +	ret = inv_icm42600_irq_init(st, irq, irq_type, open_drain);
> +	if (ret)
> +		return ret;
> +
>  	/* setup runtime power management */
>  	ret = pm_runtime_set_active(dev);
>  	if (ret)
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c
> index 4789cead23b3..85b1934cec60 100644
> --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c
> @@ -64,7 +64,8 @@ static int inv_icm42600_probe(struct i2c_client *client)
>  	if (IS_ERR(regmap))
>  		return PTR_ERR(regmap);
>  
> -	return inv_icm42600_core_probe(regmap, chip, inv_icm42600_i2c_bus_setup);
> +	return inv_icm42600_core_probe(regmap, chip, client->irq,
> +				       inv_icm42600_i2c_bus_setup);
>  }
>  
>  static const struct of_device_id inv_icm42600_of_matches[] = {
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c
> index a9c5e2fdbe2a..323789697a08 100644
> --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c
> @@ -63,7 +63,8 @@ static int inv_icm42600_probe(struct spi_device *spi)
>  	if (IS_ERR(regmap))
>  		return PTR_ERR(regmap);
>  
> -	return inv_icm42600_core_probe(regmap, chip, inv_icm42600_spi_bus_setup);
> +	return inv_icm42600_core_probe(regmap, chip, spi->irq,
> +				       inv_icm42600_spi_bus_setup);
>  }
>  
>  static const struct of_device_id inv_icm42600_of_matches[] = {


^ permalink raw reply

* Re: [PATCH v2 04/12] iio: imu: inv_icm42600: add gyroscope IIO device
From: Jonathan Cameron @ 2020-05-31 11:54 UTC (permalink / raw)
  To: Jean-Baptiste Maneyrol
  Cc: robh+dt, robh, mchehab+huawei, davem, gregkh, linux-iio,
	devicetree, linux-kernel
In-Reply-To: <20200527185711.21331-5-jmaneyrol@invensense.com>

On Wed, 27 May 2020 20:57:03 +0200
Jean-Baptiste Maneyrol <jmaneyrol@invensense.com> wrote:

> Add IIO device for gyroscope sensor with data polling interface.
> Attributes: raw, scale, sampling_frequency, calibbias.
> 
> Gyroscope in low noise mode.
> 
> Signed-off-by: Jean-Baptiste Maneyrol <jmaneyrol@invensense.com>

Unusual to have a calibration offset specified in output units,
which contributes a lot of the complexity in here.
Normally those are strictly front end (output of some calibration DAC).
So if they have units (and often they don't) I'd expect them to be
the same as _raw.

We need to tidy up the docs on this though as it doesn't express
any sort of preference.  It's hard to be specific as often the calibration
scales are defined - they are just like tweaking a POT on an analog
sensor board.

A few trivial other things inline, including a suggestion to modify
the layering of the driver a tiny bit during probe.

Thanks,

Jonathan

> ---
>  drivers/iio/imu/inv_icm42600/inv_icm42600.h   |   6 +
>  .../iio/imu/inv_icm42600/inv_icm42600_core.c  |   4 +
>  .../iio/imu/inv_icm42600/inv_icm42600_gyro.c  | 600 ++++++++++++++++++
>  3 files changed, 610 insertions(+)
>  create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
> 
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h
> index 14c8ef152418..c1023d59b37b 100644
> --- a/drivers/iio/imu/inv_icm42600/inv_icm42600.h
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h
> @@ -120,6 +120,8 @@ struct inv_icm42600_suspended {
>   *  @orientation:	sensor chip orientation relative to main hardware.
>   *  @conf:		chip sensors configurations.
>   *  @suspended:		suspended sensors configuration.
> + *  @indio_gyro:	gyroscope IIO device.
> + *  @buffer:		data transfer buffer aligned for DMA.
>   */
>  struct inv_icm42600_state {
>  	struct mutex lock;
> @@ -131,6 +133,8 @@ struct inv_icm42600_state {
>  	struct iio_mount_matrix orientation;
>  	struct inv_icm42600_conf conf;
>  	struct inv_icm42600_suspended suspended;
> +	struct iio_dev *indio_gyro;
> +	uint8_t buffer[2] ____cacheline_aligned;
>  };
>  
>  /* Virtual register addresses: @bank on MSB (4 upper bits), @address on LSB */
> @@ -369,4 +373,6 @@ int inv_icm42600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg,
>  int inv_icm42600_core_probe(struct regmap *regmap, int chip,
>  			    inv_icm42600_bus_setup bus_setup);
>  
> +int inv_icm42600_gyro_init(struct inv_icm42600_state *st);
> +
>  #endif
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
> index 81b171d6782c..dccb7bcc782e 100644
> --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
> @@ -510,6 +510,10 @@ int inv_icm42600_core_probe(struct regmap *regmap, int chip,
>  	if (ret)
>  		return ret;
>  
> +	ret = inv_icm42600_gyro_init(st);
> +	if (ret)
> +		return ret;
> +
>  	/* setup runtime power management */
>  	ret = pm_runtime_set_active(dev);
>  	if (ret)
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
> new file mode 100644
> index 000000000000..9d9672989b23
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
> @@ -0,0 +1,600 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2020 Invensense, Inc.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/device.h>
> +#include <linux/mutex.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/delay.h>
> +#include <linux/math64.h>
> +#include <linux/iio/iio.h>
> +
> +#include "inv_icm42600.h"
> +
> +#define INV_ICM42600_GYRO_CHAN(_modifier, _index, _ext_info)		\
> +	{								\
> +		.type = IIO_ANGL_VEL,					\
> +		.modified = 1,						\
> +		.channel2 = _modifier,					\
> +		.info_mask_separate =					\
> +			BIT(IIO_CHAN_INFO_RAW) |			\
> +			BIT(IIO_CHAN_INFO_CALIBBIAS),			\
> +		.info_mask_shared_by_type =				\
> +			BIT(IIO_CHAN_INFO_SCALE),			\
> +		.info_mask_shared_by_type_available =			\
> +			BIT(IIO_CHAN_INFO_SCALE) |			\
> +			BIT(IIO_CHAN_INFO_CALIBBIAS),			\
> +		.info_mask_shared_by_all =				\
> +			BIT(IIO_CHAN_INFO_SAMP_FREQ),			\
> +		.info_mask_shared_by_all_available =			\
> +			BIT(IIO_CHAN_INFO_SAMP_FREQ),			\
> +		.scan_index = _index,					\
> +		.scan_type = {						\
> +			.sign = 's',					\
> +			.realbits = 16,					\
> +			.storagebits = 16,				\
> +			.endianness = IIO_BE,				\
> +		},							\
> +		.ext_info = _ext_info,					\
> +	}
> +
> +enum inv_icm42600_gyro_scan {
> +	INV_ICM42600_GYRO_SCAN_X,
> +	INV_ICM42600_GYRO_SCAN_Y,
> +	INV_ICM42600_GYRO_SCAN_Z,
> +};
> +
> +static const struct iio_chan_spec_ext_info inv_icm42600_gyro_ext_infos[] = {
> +	IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm42600_get_mount_matrix),
> +	{},
> +};
> +
> +static const struct iio_chan_spec inv_icm42600_gyro_channels[] = {
> +	INV_ICM42600_GYRO_CHAN(IIO_MOD_X, INV_ICM42600_GYRO_SCAN_X,
> +			       inv_icm42600_gyro_ext_infos),
> +	INV_ICM42600_GYRO_CHAN(IIO_MOD_Y, INV_ICM42600_GYRO_SCAN_Y,
> +			       inv_icm42600_gyro_ext_infos),
> +	INV_ICM42600_GYRO_CHAN(IIO_MOD_Z, INV_ICM42600_GYRO_SCAN_Z,
> +			       inv_icm42600_gyro_ext_infos),
> +};
> +
> +static int inv_icm42600_gyro_read_sensor(struct inv_icm42600_state *st,
> +					 struct iio_chan_spec const *chan,
> +					 int16_t *val)
> +{
> +	struct device *dev = regmap_get_device(st->map);
> +	struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
> +	unsigned int reg;
> +	__be16 *data;
> +	int ret;
> +
> +	if (chan->type != IIO_ANGL_VEL)
> +		return -EINVAL;
> +
> +	switch (chan->channel2) {
> +	case IIO_MOD_X:
> +		reg = INV_ICM42600_REG_GYRO_DATA_X;
> +		break;
> +	case IIO_MOD_Y:
> +		reg = INV_ICM42600_REG_GYRO_DATA_Y;
> +		break;
> +	case IIO_MOD_Z:
> +		reg = INV_ICM42600_REG_GYRO_DATA_Z;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	pm_runtime_get_sync(dev);
> +	mutex_lock(&st->lock);
> +
> +	/* enable gyro sensor */
> +	conf.mode = INV_ICM42600_SENSOR_MODE_LOW_NOISE;
> +	ret = inv_icm42600_set_gyro_conf(st, &conf, NULL);
> +	if (ret)
> +		goto exit;
> +
> +	/* read gyro register data */
> +	data = (__be16 *)&st->buffer[0];
> +	ret = regmap_bulk_read(st->map, reg, data, sizeof(*data));
> +	if (ret)
> +		goto exit;
> +
> +	*val = (int16_t)be16_to_cpup(data);
> +	if (*val == INV_ICM42600_DATA_INVALID)
> +		ret = -EINVAL;
> +exit:
> +	mutex_unlock(&st->lock);
> +	pm_runtime_mark_last_busy(dev);
> +	pm_runtime_put_autosuspend(dev);
> +	return ret;
> +}
> +
> +/* IIO format int + nano */
> +static const int inv_icm42600_gyro_scale[] = {
> +	/* +/- 2000dps => 0.001065264 rad/s */
> +	[2 * INV_ICM42600_GYRO_FS_2000DPS] = 0,
> +	[2 * INV_ICM42600_GYRO_FS_2000DPS + 1] = 1065264,
> +	/* +/- 1000dps => 0.000532632 rad/s */
> +	[2 * INV_ICM42600_GYRO_FS_1000DPS] = 0,
> +	[2 * INV_ICM42600_GYRO_FS_1000DPS + 1] = 532632,
> +	/* +/- 500dps => 0.000266316 rad/s */
> +	[2 * INV_ICM42600_GYRO_FS_500DPS] = 0,
> +	[2 * INV_ICM42600_GYRO_FS_500DPS + 1] = 266316,
> +	/* +/- 250dps => 0.000133158 rad/s */
> +	[2 * INV_ICM42600_GYRO_FS_250DPS] = 0,
> +	[2 * INV_ICM42600_GYRO_FS_250DPS + 1] = 133158,
> +	/* +/- 125dps => 0.000066579 rad/s */
> +	[2 * INV_ICM42600_GYRO_FS_125DPS] = 0,
> +	[2 * INV_ICM42600_GYRO_FS_125DPS + 1] = 66579,
> +	/* +/- 62.5dps => 0.000033290 rad/s */
> +	[2 * INV_ICM42600_GYRO_FS_62_5DPS] = 0,
> +	[2 * INV_ICM42600_GYRO_FS_62_5DPS + 1] = 33290,
> +	/* +/- 31.25dps => 0.000016645 rad/s */
> +	[2 * INV_ICM42600_GYRO_FS_31_25DPS] = 0,
> +	[2 * INV_ICM42600_GYRO_FS_31_25DPS + 1] = 16645,
> +	/* +/- 15.625dps => 0.000008322 rad/s */
> +	[2 * INV_ICM42600_GYRO_FS_15_625DPS] = 0,
> +	[2 * INV_ICM42600_GYRO_FS_15_625DPS + 1] = 8322,
> +};
> +
> +static int inv_icm42600_gyro_read_scale(struct inv_icm42600_state *st,
> +					int *val, int *val2)
> +{
> +	unsigned int idx;
> +
> +	idx = st->conf.gyro.fs;
> +
> +	*val = inv_icm42600_gyro_scale[2 * idx];
> +	*val2 = inv_icm42600_gyro_scale[2 * idx + 1];
> +	return IIO_VAL_INT_PLUS_NANO;
> +}
> +
> +static int inv_icm42600_gyro_write_scale(struct inv_icm42600_state *st,
> +					 int val, int val2)
> +{
> +	struct device *dev = regmap_get_device(st->map);
> +	unsigned int idx;
> +	struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
> +	int ret;
> +
> +	for (idx = 0; idx < ARRAY_SIZE(inv_icm42600_gyro_scale); idx += 2) {
> +		if (val == inv_icm42600_gyro_scale[idx] &&
> +		    val2 == inv_icm42600_gyro_scale[idx + 1])
> +			break;
> +	}
> +	if (idx >= ARRAY_SIZE(inv_icm42600_gyro_scale))
> +		return -EINVAL;
> +
> +	conf.fs = idx / 2;
> +
> +	pm_runtime_get_sync(dev);
> +	mutex_lock(&st->lock);
> +
> +	ret = inv_icm42600_set_gyro_conf(st, &conf, NULL);
> +
> +	mutex_unlock(&st->lock);
> +	pm_runtime_mark_last_busy(dev);
> +	pm_runtime_put_autosuspend(dev);
> +
> +	return ret;
> +}
> +
> +/* IIO format int + micro */
> +static const int inv_icm42600_gyro_odr[] = {
> +	/* 12.5Hz */
> +	12, 500000,
> +	/* 25Hz */
> +	25, 0,
> +	/* 50Hz */
> +	50, 0,
> +	/* 100Hz */
> +	100, 0,
> +	/* 200Hz */
> +	200, 0,
> +	/* 1kHz */
> +	1000, 0,
> +	/* 2kHz */
> +	2000, 0,
> +	/* 4kHz */
> +	4000, 0,
> +};
> +
> +static const int inv_icm42600_gyro_odr_conv[] = {
> +	INV_ICM42600_ODR_12_5HZ,
> +	INV_ICM42600_ODR_25HZ,
> +	INV_ICM42600_ODR_50HZ,
> +	INV_ICM42600_ODR_100HZ,
> +	INV_ICM42600_ODR_200HZ,
> +	INV_ICM42600_ODR_1KHZ_LN,
> +	INV_ICM42600_ODR_2KHZ_LN,
> +	INV_ICM42600_ODR_4KHZ_LN,
> +};
> +
> +static int inv_icm42600_gyro_read_odr(struct inv_icm42600_state *st,
> +				      int *val, int *val2)
> +{
> +	unsigned int odr;
> +	unsigned int i;
> +
> +	odr = st->conf.gyro.odr;
> +
> +	for (i = 0; i < ARRAY_SIZE(inv_icm42600_gyro_odr_conv); ++i) {
> +		if (inv_icm42600_gyro_odr_conv[i] == odr)
> +			break;
> +	}
> +	if (i >= ARRAY_SIZE(inv_icm42600_gyro_odr_conv))
> +		return -EINVAL;
> +
> +	*val = inv_icm42600_gyro_odr[2 * i];
> +	*val2 = inv_icm42600_gyro_odr[2 * i + 1];
> +
> +	return IIO_VAL_INT_PLUS_MICRO;
> +}
> +
> +static int inv_icm42600_gyro_write_odr(struct inv_icm42600_state *st,
> +				       int val, int val2)
> +{
> +	struct device *dev = regmap_get_device(st->map);
> +	unsigned int idx;
> +	struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
> +	int ret;
> +
> +	for (idx = 0; idx < ARRAY_SIZE(inv_icm42600_gyro_odr); idx += 2) {
> +		if (val == inv_icm42600_gyro_odr[idx] &&
> +		    val2 == inv_icm42600_gyro_odr[idx + 1])
> +			break;
> +	}
> +	if (idx >= ARRAY_SIZE(inv_icm42600_gyro_odr))
> +		return -EINVAL;
> +
> +	conf.odr = inv_icm42600_gyro_odr_conv[idx / 2];
> +
> +	pm_runtime_get_sync(dev);
> +	mutex_lock(&st->lock);
> +
> +	ret = inv_icm42600_set_gyro_conf(st, &conf, NULL);
> +
> +	mutex_unlock(&st->lock);
> +	pm_runtime_mark_last_busy(dev);
> +	pm_runtime_put_autosuspend(dev);
> +
> +	return ret;
> +}
> +
> +/*
> + * Calibration bias values, IIO range format int + nano.
> + * Value is limited to +/-64dps coded on 12 bits signed. Step is 1/32 dps.
> + */
> +static int inv_icm42600_gyro_calibbias[] = {
> +	-1, 117010721,		/* min: -1.117010721 rad/s */
> +	0, 545415,		/* step: 0.000545415 rad/s */
> +	1, 116465306,		/* max: 1.116465306 rad/s */
> +};
> +
> +static int inv_icm42600_gyro_read_offset(struct inv_icm42600_state *st,
> +					 struct iio_chan_spec const *chan,
> +					 int *val, int *val2)
> +{
> +	struct device *dev = regmap_get_device(st->map);
> +	int64_t val64;
> +	int32_t bias;
> +	unsigned int reg;
> +	int16_t offset;
> +	uint8_t data[2];
> +	int ret;
> +
> +	if (chan->type != IIO_ANGL_VEL)
> +		return -EINVAL;
> +
> +	switch (chan->channel2) {
> +	case IIO_MOD_X:
> +		reg = INV_ICM42600_REG_OFFSET_USER0;
> +		break;
> +	case IIO_MOD_Y:
> +		reg = INV_ICM42600_REG_OFFSET_USER1;
> +		break;
> +	case IIO_MOD_Z:
> +		reg = INV_ICM42600_REG_OFFSET_USER3;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	pm_runtime_get_sync(dev);
> +	mutex_lock(&st->lock);
> +
> +	ret = regmap_bulk_read(st->map, reg, st->buffer, sizeof(data));
> +	memcpy(data, st->buffer, sizeof(data));
> +
> +	mutex_unlock(&st->lock);
> +	pm_runtime_mark_last_busy(dev);
> +	pm_runtime_put_autosuspend(dev);
> +	if (ret)
> +		return ret;
> +
> +	/* 12 bits signed value */
> +	switch (chan->channel2) {
> +	case IIO_MOD_X:
> +		offset = sign_extend32(((data[1] & 0x0F) << 8) | data[0], 11);
> +		break;
> +	case IIO_MOD_Y:
> +		offset = sign_extend32(((data[0] & 0xF0) << 4) | data[1], 11);
> +		break;
> +	case IIO_MOD_Z:
> +		offset = sign_extend32(((data[1] & 0x0F) << 8) | data[0], 11);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * convert raw offset to dps then to rad/s
> +	 * 12 bits signed raw max 64 to dps: 64 / 2048
> +	 * dps to rad: Pi / 180
> +	 * result in nano (1000000000)
> +	 * (offset * 64 * Pi * 1000000000) / (2048 * 180)
> +	 */
> +	val64 = (int64_t)offset * 64LL * 3141592653LL;
> +	/* for rounding, add + or - divisor (2048 * 180) divided by 2 */
> +	if (val64 >= 0)
> +		val64 += 2048 * 180 / 2;
> +	else
> +		val64 -= 2048 * 180 / 2;
> +	bias = div_s64(val64, 2048 * 180);
> +	*val = bias / 1000000000L;
> +	*val2 = bias % 1000000000L;
> +
> +	return IIO_VAL_INT_PLUS_NANO;
> +}
> +
> +static int inv_icm42600_gyro_write_offset(struct inv_icm42600_state *st,
> +					  struct iio_chan_spec const *chan,
> +					  int val, int val2)
> +{
> +	struct device *dev = regmap_get_device(st->map);
> +	int64_t val64, min, max;
> +	unsigned int reg, regval;
> +	int16_t offset;
> +	int ret;
> +
> +	if (chan->type != IIO_ANGL_VEL)
> +		return -EINVAL;
> +
> +	switch (chan->channel2) {
> +	case IIO_MOD_X:
> +		reg = INV_ICM42600_REG_OFFSET_USER0;
> +		break;
> +	case IIO_MOD_Y:
> +		reg = INV_ICM42600_REG_OFFSET_USER1;
> +		break;
> +	case IIO_MOD_Z:
> +		reg = INV_ICM42600_REG_OFFSET_USER3;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	/* inv_icm42600_gyro_calibbias: min - step - max in nano */
> +	min = (int64_t)inv_icm42600_gyro_calibbias[0] * 1000000000LL +
> +	      (int64_t)inv_icm42600_gyro_calibbias[1];
> +	max = (int64_t)inv_icm42600_gyro_calibbias[4] * 1000000000LL +
> +	      (int64_t)inv_icm42600_gyro_calibbias[5];
> +	val64 = (int64_t)val * 1000000000LL + (int64_t)val2;
> +	if (val64 < min || val64 > max)
> +		return -EINVAL;
> +
> +	/*
> +	 * convert rad/s to dps then to raw value
> +	 * rad to dps: 180 / Pi
> +	 * dps to raw 12 bits signed, max 64: 2048 / 64
> +	 * val in nano (1000000000)
> +	 * val * 180 * 2048 / (Pi * 1000000000 * 64)
> +	 */
> +	val64 = val64 * 180LL * 2048LL;
> +	/* for rounding, add + or - divisor (3141592653 * 64) divided by 2 */
> +	if (val64 >= 0)
> +		val64 += 3141592653LL * 64LL / 2LL;
> +	else
> +		val64 -= 3141592653LL * 64LL / 2LL;
> +	offset = div64_s64(val64, 3141592653LL * 64LL);
> +
> +	/* clamp value limited to 12 bits signed */
> +	if (offset < -2048)
> +		offset = -2048;
> +	else if (offset > 2047)
> +		offset = 2047;
> +
> +	pm_runtime_get_sync(dev);
> +	mutex_lock(&st->lock);
> +
> +	switch (chan->channel2) {
> +	case IIO_MOD_X:
> +		/* OFFSET_USER1 register is shared */
> +		ret = regmap_read(st->map, INV_ICM42600_REG_OFFSET_USER1,
> +				  &regval);
> +		if (ret)
> +			goto out_unlock;
> +		st->buffer[0] = offset & 0xFF;
> +		st->buffer[1] = (regval & 0xF0) | ((offset & 0xF00) >> 8);
> +		break;
> +	case IIO_MOD_Y:
> +		/* OFFSET_USER1 register is shared */
> +		ret = regmap_read(st->map, INV_ICM42600_REG_OFFSET_USER1,
> +				  &regval);
> +		if (ret)
> +			goto out_unlock;
> +		st->buffer[0] = ((offset & 0xF00) >> 4) | (regval & 0x0F);
> +		st->buffer[1] = offset & 0xFF;
> +		break;
> +	case IIO_MOD_Z:
> +		/* OFFSET_USER4 register is shared */
> +		ret = regmap_read(st->map, INV_ICM42600_REG_OFFSET_USER4,
> +				  &regval);
> +		if (ret)
> +			goto out_unlock;
> +		st->buffer[0] = offset & 0xFF;
> +		st->buffer[1] = (regval & 0xF0) | ((offset & 0xF00) >> 8);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		goto out_unlock;
> +	}
> +
> +	ret = regmap_bulk_write(st->map, reg, st->buffer, 2);
> +
> +out_unlock:
> +	mutex_unlock(&st->lock);
> +	pm_runtime_mark_last_busy(dev);
> +	pm_runtime_put_autosuspend(dev);
> +	return ret;
> +}
> +
> +static int inv_icm42600_gyro_read_raw(struct iio_dev *indio_dev,
> +				      struct iio_chan_spec const *chan,
> +				      int *val, int *val2, long mask)
> +{
> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	int16_t data;
> +	int ret;
> +
> +	if (chan->type != IIO_ANGL_VEL)
> +		return -EINVAL;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		ret = iio_device_claim_direct_mode(indio_dev);
> +		if (ret)
> +			return ret;
> +		ret = inv_icm42600_gyro_read_sensor(st, chan, &data);
> +		iio_device_release_direct_mode(indio_dev);
> +		if (ret)
> +			return ret;
> +		*val = data;
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_SCALE:
> +		return inv_icm42600_gyro_read_scale(st, val, val2);
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		return inv_icm42600_gyro_read_odr(st, val, val2);
> +	case IIO_CHAN_INFO_CALIBBIAS:
> +		return inv_icm42600_gyro_read_offset(st, chan, val, val2);
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int inv_icm42600_gyro_read_avail(struct iio_dev *indio_dev,
> +					struct iio_chan_spec const *chan,
> +					const int **vals,
> +					int *type, int *length, long mask)
> +{
> +	if (chan->type != IIO_ANGL_VEL)
> +		return -EINVAL;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_SCALE:
> +		*vals = inv_icm42600_gyro_scale;
> +		*type = IIO_VAL_INT_PLUS_NANO;
> +		*length = ARRAY_SIZE(inv_icm42600_gyro_scale);
> +		return IIO_AVAIL_LIST;
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		*vals = inv_icm42600_gyro_odr;
> +		*type = IIO_VAL_INT_PLUS_MICRO;
> +		*length = ARRAY_SIZE(inv_icm42600_gyro_odr);
> +		return IIO_AVAIL_LIST;
> +	case IIO_CHAN_INFO_CALIBBIAS:
> +		*vals = inv_icm42600_gyro_calibbias;
> +		*type = IIO_VAL_INT_PLUS_NANO;
> +		return IIO_AVAIL_RANGE;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int inv_icm42600_gyro_write_raw(struct iio_dev *indio_dev,
> +				       struct iio_chan_spec const *chan,
> +				       int val, int val2, long mask)
> +{
> +	struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
> +	int ret;
> +
> +	if (chan->type != IIO_ANGL_VEL)
> +		return -EINVAL;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_SCALE:
> +		ret = iio_device_claim_direct_mode(indio_dev);
> +		if (ret)
> +			return ret;
> +		ret = inv_icm42600_gyro_write_scale(st, val, val2);
> +		iio_device_release_direct_mode(indio_dev);
> +		return ret;
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		return inv_icm42600_gyro_write_odr(st, val, val2);
> +	case IIO_CHAN_INFO_CALIBBIAS:
> +		ret = iio_device_claim_direct_mode(indio_dev);
> +		if (ret)
> +			return ret;
> +		ret = inv_icm42600_gyro_write_offset(st, chan, val, val2);
> +		iio_device_release_direct_mode(indio_dev);
> +		return ret;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int inv_icm42600_gyro_write_raw_get_fmt(struct iio_dev *indio_dev,
> +					       struct iio_chan_spec const *chan,
> +					       long mask)
> +{
> +	if (chan->type != IIO_ANGL_VEL)
> +		return -EINVAL;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_SCALE:
> +		return IIO_VAL_INT_PLUS_NANO;
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		return IIO_VAL_INT_PLUS_MICRO;
> +	case IIO_CHAN_INFO_CALIBBIAS:
> +		return IIO_VAL_INT_PLUS_NANO;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static const struct iio_info inv_icm42600_gyro_info = {
> +	.read_raw = inv_icm42600_gyro_read_raw,
> +	.read_avail = inv_icm42600_gyro_read_avail,
> +	.write_raw = inv_icm42600_gyro_write_raw,
> +	.write_raw_get_fmt = inv_icm42600_gyro_write_raw_get_fmt,
> +	.debugfs_reg_access = inv_icm42600_debugfs_reg,
> +};
> +
> +int inv_icm42600_gyro_init(struct inv_icm42600_state *st)

This feels like the layering would be clearer if this
returned the struct iio_dev * and the assignment happened in the
core driver.

Then state parameter can be const and it'll be obvious it has
no side effects on the state structure.

> +{
> +	struct device *dev = regmap_get_device(st->map);
> +	const char *name;
> +	struct iio_dev *indio_dev;
> +
> +	name = devm_kasprintf(dev, GFP_KERNEL, "%s-gyro", st->name);
> +	if (!name)
> +		return -ENOMEM;
> +
> +	indio_dev = devm_iio_device_alloc(dev, 0);
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	iio_device_set_drvdata(indio_dev, st);
> +	indio_dev->dev.parent = dev;
> +	indio_dev->name = name;
> +	indio_dev->info = &inv_icm42600_gyro_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = inv_icm42600_gyro_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(inv_icm42600_gyro_channels);
> +
> +	st->indio_gyro = indio_dev;
> +	return devm_iio_device_register(dev, st->indio_gyro);
> +}


^ permalink raw reply

* Re: [PATCH v2 02/12] iio: imu: inv_icm42600: add I2C driver for inv_icm42600 driver
From: Jonathan Cameron @ 2020-05-31 11:36 UTC (permalink / raw)
  To: Jean-Baptiste Maneyrol
  Cc: robh+dt, robh, mchehab+huawei, davem, gregkh, linux-iio,
	devicetree, linux-kernel
In-Reply-To: <20200527185711.21331-3-jmaneyrol@invensense.com>

On Wed, 27 May 2020 20:57:01 +0200
Jean-Baptiste Maneyrol <jmaneyrol@invensense.com> wrote:

> Add I2C driver for InvenSense ICM-426xxx devices.
> 
> Configure bus signal slew rates as indicated in the datasheet.
> 
> Signed-off-by: Jean-Baptiste Maneyrol <jmaneyrol@invensense.com>

Looks fine to me.

J

> ---
>  .../iio/imu/inv_icm42600/inv_icm42600_i2c.c   | 100 ++++++++++++++++++
>  1 file changed, 100 insertions(+)
>  create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c
> 
> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c
> new file mode 100644
> index 000000000000..4789cead23b3
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c
> @@ -0,0 +1,100 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2020 InvenSense, Inc.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/i2c.h>
> +#include <linux/regmap.h>
> +#include <linux/property.h>
> +
> +#include "inv_icm42600.h"
> +
> +static int inv_icm42600_i2c_bus_setup(struct inv_icm42600_state *st)
> +{
> +	unsigned int mask, val;
> +	int ret;
> +
> +	/* setup interface registers */
> +	ret = regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG6,
> +				 INV_ICM42600_INTF_CONFIG6_MASK,
> +				 INV_ICM42600_INTF_CONFIG6_I3C_EN);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG4,
> +				 INV_ICM42600_INTF_CONFIG4_I3C_BUS_ONLY, 0);
> +	if (ret)
> +		return ret;
> +
> +	/* set slew rates for I2C and SPI */
> +	mask = INV_ICM42600_DRIVE_CONFIG_I2C_MASK |
> +	       INV_ICM42600_DRIVE_CONFIG_SPI_MASK;
> +	val = INV_ICM42600_DRIVE_CONFIG_I2C(INV_ICM42600_SLEW_RATE_12_36NS) |
> +	      INV_ICM42600_DRIVE_CONFIG_SPI(INV_ICM42600_SLEW_RATE_12_36NS);
> +	ret = regmap_update_bits(st->map, INV_ICM42600_REG_DRIVE_CONFIG,
> +				 mask, val);
> +	if (ret)
> +		return ret;
> +
> +	/* disable SPI bus */
> +	return regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG0,
> +				  INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_MASK,
> +				  INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_SPI_DIS);
> +}
> +
> +static int inv_icm42600_probe(struct i2c_client *client)
> +{
> +	const void *match;
> +	enum inv_icm42600_chip chip;
> +	struct regmap *regmap;
> +
> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
> +		return -ENOTSUPP;
> +
> +	match = device_get_match_data(&client->dev);
> +	if (!match)
> +		return -EINVAL;
> +	chip = (enum inv_icm42600_chip)match;
> +
> +	regmap = devm_regmap_init_i2c(client, &inv_icm42600_regmap_config);
> +	if (IS_ERR(regmap))
> +		return PTR_ERR(regmap);
> +
> +	return inv_icm42600_core_probe(regmap, chip, inv_icm42600_i2c_bus_setup);
> +}
> +
> +static const struct of_device_id inv_icm42600_of_matches[] = {
> +	{
> +		.compatible = "invensense,icm42600",
> +		.data = (void *)INV_CHIP_ICM42600,
> +	}, {
> +		.compatible = "invensense,icm42602",
> +		.data = (void *)INV_CHIP_ICM42602,
> +	}, {
> +		.compatible = "invensense,icm42605",
> +		.data = (void *)INV_CHIP_ICM42605,
> +	}, {
> +		.compatible = "invensense,icm42622",
> +		.data = (void *)INV_CHIP_ICM42622,
> +	},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, inv_icm42600_of_matches);
> +
> +static struct i2c_driver inv_icm42600_driver = {
> +	.driver = {
> +		.name = "inv-icm42600-i2c",
> +		.of_match_table = inv_icm42600_of_matches,
> +		.pm = &inv_icm42600_pm_ops,
> +	},
> +	.probe_new = inv_icm42600_probe,
> +};
> +module_i2c_driver(inv_icm42600_driver);
> +
> +MODULE_AUTHOR("InvenSense, Inc.");
> +MODULE_DESCRIPTION("InvenSense ICM-426xx I2C driver");
> +MODULE_LICENSE("GPL");


^ permalink raw reply

* Re: [PATCH v2 01/12] iio: imu: inv_icm42600: add core of new inv_icm42600 driver
From: Jonathan Cameron @ 2020-05-31 11:34 UTC (permalink / raw)
  To: Jean-Baptiste Maneyrol
  Cc: robh+dt, robh, mchehab+huawei, davem, gregkh, linux-iio,
	devicetree, linux-kernel
In-Reply-To: <20200527185711.21331-2-jmaneyrol@invensense.com>

On Wed, 27 May 2020 20:57:00 +0200
Jean-Baptiste Maneyrol <jmaneyrol@invensense.com> wrote:

> Core component of a new driver for InvenSense ICM-426xx devices.
> It includes registers definition, main probe/setup, and device
> utility functions.
> 
> ICM-426xx devices are latest generation of 6-axis IMU,
> gyroscope+accelerometer and temperature sensor. This device
> includes a 2K FIFO, supports I2C/I3C/SPI, and provides
> intelligent motion features like pedometer, tilt detection,
> and tap detection.
> 
> Signed-off-by: Jean-Baptiste Maneyrol <jmaneyrol@invensense.com>

A few things inline.

Either I'm missing something or I'm guessing vddio is not controllable
on your test board.

> ---
>  drivers/iio/imu/inv_icm42600/inv_icm42600.h   | 372 ++++++++++
>  .../iio/imu/inv_icm42600/inv_icm42600_core.c  | 635 ++++++++++++++++++
>  2 files changed, 1007 insertions(+)
>  create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600.h
>  create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
> 

...

> diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
> new file mode 100644
> index 000000000000..81b171d6782c
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c

> +const struct iio_mount_matrix *
> +inv_icm42600_get_mount_matrix(const struct iio_dev *indio_dev,
> +			      const struct iio_chan_spec *chan)
> +{
> +	const struct inv_icm42600_state *st =
> +			iio_device_get_drvdata((struct iio_dev *)indio_dev);

If you review my patch to the core, I can get that applied and we can drop
the ugly cast from here!

Just waiting for someone to sanity check it.
> +
> +	return &st->orientation;
> +}
...

> +/* Runtime suspend will turn off sensors that are enabled by iio devices. */
> +static int __maybe_unused inv_icm42600_runtime_suspend(struct device *dev)
> +{
> +	struct inv_icm42600_state *st = dev_get_drvdata(dev);
> +	int ret;
> +
> +	mutex_lock(&st->lock);
> +
> +	/* disable all sensors */
> +	ret = inv_icm42600_set_pwr_mgmt0(st, INV_ICM42600_SENSOR_MODE_OFF,
> +					 INV_ICM42600_SENSOR_MODE_OFF, false,
> +					 NULL);
> +	if (ret)
> +		goto error_unlock;
> +
> +	regulator_disable(st->vddio_supply);

Don't seem to turn this on again in runtime_resume..
Why?  Definitely needs at least a comment.

> +
> +error_unlock:
> +	mutex_unlock(&st->lock);
> +	return ret;
> +}
> +
> +/* Sensors are enabled by iio devices, no need to turn them back on here. */
> +static int __maybe_unused inv_icm42600_runtime_resume(struct device *dev)
> +{
> +	struct inv_icm42600_state *st = dev_get_drvdata(dev);
> +	int ret;
> +
> +	mutex_lock(&st->lock);
> +
> +	ret = inv_icm42600_enable_regulator_vddio(st);
> +
> +	mutex_unlock(&st->lock);
> +	return ret;
> +}
> +
> +const struct dev_pm_ops inv_icm42600_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(inv_icm42600_suspend, inv_icm42600_resume)
> +	SET_RUNTIME_PM_OPS(inv_icm42600_runtime_suspend,
> +			   inv_icm42600_runtime_resume, NULL)
> +};
> +EXPORT_SYMBOL_GPL(inv_icm42600_pm_ops);
> +
> +MODULE_AUTHOR("InvenSense, Inc.");
> +MODULE_DESCRIPTION("InvenSense ICM-426xx device driver");
> +MODULE_LICENSE("GPL");


^ permalink raw reply

* [PATCH] arm: allwinner: a20: Add Drejo DS167 initial support
From: stulluk @ 2020-05-31 11:05 UTC (permalink / raw)
  To: mripard; +Cc: robh+dt, wens, devicetree, linux-kernel, linux-sunxi,
	Sertac TULLUK

From: Sertac TULLUK <stulluk@gmail.com>

Drejo DS167 is an Allwinner A20 based IoT device,
which support
- Allwinner A20 Cortex-A7
- Mali-400MP2 GPU
- AXP209 PMIC
- 1GB DDR3 RAM
- 8GB eMMC
- 10/100M Ethernet
- SATA
- HDMI
- 10.1inch and 7.0inch LVDS LCD and Touch screens
- CSI: OV5640 sensor
- USB OTG
- 2x USB2.0
- built-in KNX Transceiver
- 3x Dry Contact Input
- 3x Relay output
- IR RX/TX
- UART3
- SPI1
- RTC Battery
- 8x GPIO
- Analogue + Digital Microphone
- PAM8620 speaker Amplifier
- 12V DC power supply
- 3.7V Battery Operable

Signed-off-by: Sertac TULLUK <stulluk@gmail.com>
---
 arch/arm/boot/dts/Makefile                    |   2 +
 .../boot/dts/sun7i-a20-drejo-ds167-emmc.dts   |  69 +++++
 arch/arm/boot/dts/sun7i-a20-drejo-ds167.dts   | 288 ++++++++++++++++++
 3 files changed, 359 insertions(+)
 create mode 100644 arch/arm/boot/dts/sun7i-a20-drejo-ds167-emmc.dts
 create mode 100644 arch/arm/boot/dts/sun7i-a20-drejo-ds167.dts

diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile
index 3823090d07e7..d81e685dee38 100644
--- a/arch/arm/boot/dts/Makefile
+++ b/arch/arm/boot/dts/Makefile
@@ -1097,6 +1097,8 @@ dtb-$(CONFIG_MACH_SUN7I) += \
 	sun7i-a20-bananapro.dtb \
 	sun7i-a20-cubieboard2.dtb \
 	sun7i-a20-cubietruck.dtb \
+	sun7i-a20-drejo-ds167.dtb \
+        sun7i-a20-drejo-ds167-emmc.dtb \
 	sun7i-a20-hummingbird.dtb \
 	sun7i-a20-itead-ibox.dtb \
 	sun7i-a20-i12-tvbox.dtb \
diff --git a/arch/arm/boot/dts/sun7i-a20-drejo-ds167-emmc.dts b/arch/arm/boot/dts/sun7i-a20-drejo-ds167-emmc.dts
new file mode 100644
index 000000000000..b6147eb013b0
--- /dev/null
+++ b/arch/arm/boot/dts/sun7i-a20-drejo-ds167-emmc.dts
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2020 Sertac TULLUK
+ *
+ * Sertac TULLUK <stulluk@gmail.com>
+ *
+ * This file is dual-licensed: you can use it either under the terms
+ * of the GPL or the X11 license, at your option. Note that this dual
+ * licensing only applies to this file, and not this project as a
+ * whole.
+ *
+ *  a) This file is free software; you can redistribute it and/or
+ *     modify it under the terms of the GNU General Public License as
+ *     published by the Free Software Foundation; either version 2 of the
+ *     License, or (at your option) any later version.
+ *
+ *     This file is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ * Or, alternatively,
+ *
+ *  b) Permission is hereby granted, free of charge, to any person
+ *     obtaining a copy of this software and associated documentation
+ *     files (the "Software"), to deal in the Software without
+ *     restriction, including without limitation the rights to use,
+ *     copy, modify, merge, publish, distribute, sublicense, and/or
+ *     sell copies of the Software, and to permit persons to whom the
+ *     Software is furnished to do so, subject to the following
+ *     conditions:
+ *
+ *     The above copyright notice and this permission notice shall be
+ *     included in all copies or substantial portions of the Software.
+ *
+ *     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ *     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ *     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ *     OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "sun7i-a20-drejo-ds167.dts"
+
+/ {
+	model = "drejo DS167-eMMC";
+	compatible = "drejo,sun7i-a20-drejo-ds167-emmc", "allwinner,sun7i-a20";
+
+	mmc2_pwrseq: pwrseq {
+		compatible = "mmc-pwrseq-emmc";
+		reset-gpios = <&pio 2 24 GPIO_ACTIVE_LOW>;
+	};
+};
+
+&mmc2 {
+	vmmc-supply = <&reg_vcc3v3>;
+	bus-width = <4>;
+	non-removable;
+	mmc-pwrseq = <&mmc2_pwrseq>;
+	status = "okay";
+
+	emmc: emmc@0 {
+		reg = <0>;
+		compatible = "mmc-card";
+		broken-hpi;
+	};
+};
diff --git a/arch/arm/boot/dts/sun7i-a20-drejo-ds167.dts b/arch/arm/boot/dts/sun7i-a20-drejo-ds167.dts
new file mode 100644
index 000000000000..79db92f88251
--- /dev/null
+++ b/arch/arm/boot/dts/sun7i-a20-drejo-ds167.dts
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2020 Sertac TULLUK
+ *
+ * Sertac TULLUK <stulluk@gmail.com>
+ *
+ * This file is dual-licensed: you can use it either under the terms
+ * of the GPL or the X11 license, at your option. Note that this dual
+ * licensing only applies to this file, and not this project as a
+ * whole.
+ *
+ *  a) This file is free software; you can redistribute it and/or
+ *     modify it under the terms of the GNU General Public License as
+ *     published by the Free Software Foundation; either version 2 of the
+ *     License, or (at your option) any later version.
+ *
+ *     This file is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ * Or, alternatively,
+ *
+ *  b) Permission is hereby granted, free of charge, to any person
+ *     obtaining a copy of this software and associated documentation
+ *     files (the "Software"), to deal in the Software without
+ *     restriction, including without limitation the rights to use,
+ *     copy, modify, merge, publish, distribute, sublicense, and/or
+ *     sell copies of the Software, and to permit persons to whom the
+ *     Software is furnished to do so, subject to the following
+ *     conditions:
+ *
+ *     The above copyright notice and this permission notice shall be
+ *     included in all copies or substantial portions of the Software.
+ *
+ *     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ *     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ *     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ *     OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/dts-v1/;
+#include "sun7i-a20.dtsi"
+#include "sunxi-common-regulators.dtsi"
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+
+/ {
+	model = "drejo DS167";
+	compatible = "drejo,sun7i-a20-drejo-ds167", "allwinner,sun7i-a20";
+
+	aliases {
+		serial0 = &uart0;
+		serial1 = &uart3;
+
+		spi0 = &spi1;
+		spi1 = &spi2;
+	};
+
+	chosen {
+		stdout-path = "serial0:115200n8";
+	};
+
+	hdmi-connector {
+		compatible = "hdmi-connector";
+		type = "a";
+
+		port {
+			hdmi_con_in: endpoint {
+				remote-endpoint = <&hdmi_out_con>;
+			};
+		};
+	};
+
+	leds {
+		compatible = "gpio-leds";
+		pinctrl-names = "default";
+		pinctrl-0 = <&led_pins_ds167>;
+
+		red {
+			label = "ds167-status";
+			gpios = <&pio 8 9 GPIO_ACTIVE_LOW>;  /* PI9 STATUS LED NEAR A20 SERTAC */
+			default-state = "on";
+		};
+	};
+};
+
+&ahci {
+	target-supply = <&reg_ahci_5v>;
+	status = "okay";
+};
+
+&codec {
+	status = "okay";
+};
+
+&cpu0 {
+	cpu-supply = <&reg_dcdc2>;
+};
+
+&de {
+	status = "okay";
+};
+
+&ehci0 {
+	status = "okay";
+};
+
+&ehci1 {
+	status = "okay";
+};
+
+&gmac {
+	pinctrl-names = "default";
+	pinctrl-0 = <&gmac_mii_pins>, <&gmac_txerr>;
+	phy-handle = <&phy1>;
+	phy-mode = "mii";
+	status = "okay";
+};
+
+&hdmi {
+	status = "okay";
+};
+
+&hdmi_out {
+	hdmi_out_con: endpoint {
+		remote-endpoint = <&hdmi_con_in>;
+	};
+};
+
+&i2c0 {
+	status = "okay";
+
+	axp209: pmic@34 {
+		reg = <0x34>;
+		interrupt-parent = <&nmi_intc>;
+		interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
+	};
+};
+
+&i2c2 {
+	status = "okay";
+};
+
+&lradc {
+	vref-supply = <&reg_vcc3v0>;
+	status = "okay";
+};
+
+&gmac_mdio {
+	phy1: ethernet-phy@1 {
+		reg = <1>;
+	};
+};
+
+&mmc0 {
+	vmmc-supply = <&reg_vcc3v3>;
+	bus-width = <4>;
+	cd-gpios = <&pio 7 1 GPIO_ACTIVE_LOW>; /* PH1 */
+	status = "okay";
+};
+
+&ohci0 {
+	status = "okay";
+};
+
+&ohci1 {
+	status = "okay";
+};
+
+&otg_sram {
+	status = "okay";
+};
+
+&pio {
+	gmac_txerr: gmac-txerr-pin {
+		pins = "PA17";
+		function = "gmac";
+	};
+
+	led_pins_ds167: led-pins {  
+		pins = "PI9";   /* Status led, on schematic, this is LED_EN */
+		function = "gpio_out";
+		drive-strength = <20>;
+	};
+};
+
+&pwm {
+      pinctrl-names = "default";
+      pinctrl-0 = <&pwm0_pin>, <&pwm1_pin>;
+      status = "okay";
+};
+
+#include "axp209.dtsi"
+
+&ac_power_supply {
+	status = "okay";
+};
+
+&battery_power_supply {
+	status = "okay";
+};
+
+&reg_dcdc2 {
+	regulator-always-on;
+	regulator-min-microvolt = <1000000>;
+	regulator-max-microvolt = <1400000>;
+	regulator-name = "vdd-cpu";
+};
+
+&reg_dcdc3 {
+	regulator-always-on;
+	regulator-min-microvolt = <1000000>;
+	regulator-max-microvolt = <1400000>;
+	regulator-name = "vdd-int-dll";
+};
+
+&reg_ldo2 {
+	regulator-always-on;
+	regulator-min-microvolt = <3000000>;
+	regulator-max-microvolt = <3000000>;
+	regulator-name = "avcc";
+};
+
+&reg_ahci_5v {
+	status = "okay";
+};
+
+&reg_usb0_vbus {
+	status = "okay";
+};
+
+&reg_usb1_vbus {
+	status = "okay";
+};
+
+&reg_usb2_vbus {
+	status = "okay";
+};
+
+&spi1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi1_pi_pins>,
+		    <&spi1_cs0_pi_pin>;
+	status = "okay";
+};
+
+&spi2 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi2_pc_pins>,
+		    <&spi2_cs0_pc_pin>;
+	status = "okay";
+};
+
+&uart0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart0_pb_pins>;
+	status = "okay";
+};
+
+&uart3 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart3_pg_pins>;
+	status = "okay";
+};
+
+&usb_otg {
+	dr_mode = "otg";
+	status = "okay";
+};
+
+&usb_power_supply {
+	status = "okay";
+};
+
+&usbphy {
+	usb0_id_det-gpios = <&pio 7 4 (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)>; /* PH4 */
+	usb0_vbus_det-gpios = <&pio 7 5 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>; /* PH5 */
+	usb0_vbus-supply = <&reg_usb0_vbus>;
+	usb1_vbus-supply = <&reg_usb1_vbus>;
+	usb2_vbus-supply = <&reg_usb2_vbus>;
+	status = "okay";
+};
-- 
2.17.1


^ permalink raw reply related

* Re: [PATCH v7 3/5] dt-bindings: iio: magnetometer: ak8975: add gpio reset support
From: Jonathan Cameron @ 2020-05-31 11:01 UTC (permalink / raw)
  To: Jonathan Albrieux
  Cc: linux-kernel, ~postmarketos/upstreaming, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Rob Herring,
	open list:IIO SUBSYSTEM AND DRIVERS,
	open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS
In-Reply-To: <20200528145930.11860-1-jonathan.albrieux@gmail.com>

On Thu, 28 May 2020 16:59:28 +0200
Jonathan Albrieux <jonathan.albrieux@gmail.com> wrote:

> Add reset-gpio support.
> 
> Without reset's deassertion during ak8975_power_on(), driver's probe fails
> on ak8975_who_i_am() while checking for device identity for AK09911 chip.
> 
> AK09911 has an active low reset gpio to handle register's reset.
> AK09911 datasheet says that, if not used, reset pin should be connected
> to VID. This patch emulates this situation.
> 
> Signed-off-by: Jonathan Albrieux <jonathan.albrieux@gmail.com>
> Reviewed-by: Rob Herring <robh@kernel.org>

Applied to the togreg branch of iio.git and pushed out as testing
for the autobuilders to play with it.

thanks,

Jonathan

> ---
>  .../bindings/iio/magnetometer/asahi-kasei,ak8975.yaml      | 7 +++++++
>  1 file changed, 7 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/iio/magnetometer/asahi-kasei,ak8975.yaml b/Documentation/devicetree/bindings/iio/magnetometer/asahi-kasei,ak8975.yaml
> index 55b18784e503..e8af53d60759 100644
> --- a/Documentation/devicetree/bindings/iio/magnetometer/asahi-kasei,ak8975.yaml
> +++ b/Documentation/devicetree/bindings/iio/magnetometer/asahi-kasei,ak8975.yaml
> @@ -47,6 +47,11 @@ properties:
>    mount-matrix:
>      description: an optional 3x3 mounting rotation matrix.
>  
> +  reset-gpios:
> +    description: |
> +      an optional pin needed for AK09911 to set the reset state. This should
> +      be usually active low
> +
>  required:
>    - compatible
>    - reg
> @@ -54,6 +59,7 @@ required:
>  examples:
>    - |
>      #include <dt-bindings/interrupt-controller/irq.h>
> +    #include <dt-bindings/gpio/gpio.h>
>      i2c {
>          #address-cells = <1>;
>          #size-cells = <0>;
> @@ -64,6 +70,7 @@ examples:
>              interrupt-parent = <&gpio6>;
>              interrupts = <15 IRQ_TYPE_EDGE_RISING>;
>              vdd-supply = <&ldo_3v3_gnss>;
> +            reset-gpios = <&msmgpio 111 GPIO_ACTIVE_LOW>;
>              mount-matrix = "-0.984807753012208",  /* x0 */
>                             "0",                   /* y0 */
>                             "-0.173648177666930",  /* z0 */


^ permalink raw reply

* Re: [PATCH V6 1/7] iio: adc: Convert the QCOM SPMI ADC bindings to .yaml format
From: Jonathan Cameron @ 2020-05-31 10:44 UTC (permalink / raw)
  To: Jishnu Prakash
  Cc: agross, bjorn.andersson, devicetree, linux-kernel, mka,
	linus.walleij, Jonathan.Cameron, andy.shevchenko, amit.kucheria,
	smohanad, kgunda, aghayal, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, Rob Herring, Mark Rutland, Amit Kucheria,
	linux-iio, linux-arm-msm, linux-arm-msm-owner
In-Reply-To: <20200531113612.7bff6147@archlinux>

On Sun, 31 May 2020 11:36:12 +0100
Jonathan Cameron <jic23@kernel.org> wrote:

> On Thu, 28 May 2020 22:24:23 +0530
> Jishnu Prakash <jprakash@codeaurora.org> wrote:
> 
> > Convert the adc bindings from .txt to .yaml format.
> > 
> > Signed-off-by: Jishnu Prakash <jprakash@codeaurora.org>
> > Reviewed-by: Amit Kucheria <amit.kucheria@linaro.org>
> > Reviewed-by: Rob Herring <robh@kernel.org>
> > Acked-by: Linus Walleij <linus.walleij@linaro.org>  
> 
> Jishnu, Patch is fine, but I'd like to have seen a cover
> letter and clear statement of changes from v5.
> 

Applied to the togreg branch of iio.git and pushed out as
testing. Note we've missed the merge window now for IIO so this
will be in the following cycle.

Thanks,

J
> Thanks,
> 
> Jonathan
> 
> > ---
> >  .../devicetree/bindings/iio/adc/qcom,spmi-vadc.txt | 173 --------------
> >  .../bindings/iio/adc/qcom,spmi-vadc.yaml           | 252 +++++++++++++++++++++
> >  2 files changed, 252 insertions(+), 173 deletions(-)
> >  delete mode 100644 Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.txt
> >  create mode 100644 Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml
> > 
> > diff --git a/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.txt b/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.txt
> > deleted file mode 100644
> > index c878768..0000000
> > --- a/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.txt
> > +++ /dev/null
> > @@ -1,173 +0,0 @@
> > -Qualcomm's SPMI PMIC ADC
> > -
> > -- SPMI PMIC voltage ADC (VADC) provides interface to clients to read
> > -  voltage. The VADC is a 15-bit sigma-delta ADC.
> > -- SPMI PMIC5 voltage ADC (ADC) provides interface to clients to read
> > -  voltage. The VADC is a 16-bit sigma-delta ADC.
> > -
> > -VADC node:
> > -
> > -- compatible:
> > -    Usage: required
> > -    Value type: <string>
> > -    Definition: Should contain "qcom,spmi-vadc".
> > -                Should contain "qcom,spmi-adc5" for PMIC5 ADC driver.
> > -                Should contain "qcom,spmi-adc-rev2" for PMIC rev2 ADC driver.
> > -                Should contain "qcom,pms405-adc" for PMS405 PMIC
> > -
> > -- reg:
> > -    Usage: required
> > -    Value type: <prop-encoded-array>
> > -    Definition: VADC base address in the SPMI PMIC register map.
> > -
> > -- #address-cells:
> > -    Usage: required
> > -    Value type: <u32>
> > -    Definition: Must be one. Child node 'reg' property should define ADC
> > -            channel number.
> > -
> > -- #size-cells:
> > -    Usage: required
> > -    Value type: <u32>
> > -    Definition: Must be zero.
> > -
> > -- #io-channel-cells:
> > -    Usage: required
> > -    Value type: <u32>
> > -    Definition: Must be one. For details about IIO bindings see:
> > -            Documentation/devicetree/bindings/iio/iio-bindings.txt
> > -
> > -- interrupts:
> > -    Usage: optional
> > -    Value type: <prop-encoded-array>
> > -    Definition: End of conversion interrupt.
> > -
> > -Channel node properties:
> > -
> > -- reg:
> > -    Usage: required
> > -    Value type: <u32>
> > -    Definition: ADC channel number.
> > -            See include/dt-bindings/iio/qcom,spmi-vadc.h
> > -
> > -- label:
> > -    Usage: required for "qcom,spmi-adc5" and "qcom,spmi-adc-rev2"
> > -    Value type: <empty>
> > -    Definition: ADC input of the platform as seen in the schematics.
> > -            For thermistor inputs connected to generic AMUX or GPIO inputs
> > -            these can vary across platform for the same pins. Hence select
> > -            the platform schematics name for this channel.
> > -
> > -- qcom,decimation:
> > -    Usage: optional
> > -    Value type: <u32>
> > -    Definition: This parameter is used to decrease ADC sampling rate.
> > -            Quicker measurements can be made by reducing decimation ratio.
> > -            - For compatible property "qcom,spmi-vadc", valid values are
> > -              512, 1024, 2048, 4096. If property is not found, default value
> > -              of 512 will be used.
> > -            - For compatible property "qcom,spmi-adc5", valid values are 250, 420
> > -              and 840. If property is not found, default value of 840 is used.
> > -            - For compatible property "qcom,spmi-adc-rev2", valid values are 256,
> > -              512 and 1024. If property is not present, default value is 1024.
> > -
> > -- qcom,pre-scaling:
> > -    Usage: optional
> > -    Value type: <u32 array>
> > -    Definition: Used for scaling the channel input signal before the signal is
> > -            fed to VADC. The configuration for this node is to know the
> > -            pre-determined ratio and use it for post scaling. Select one from
> > -            the following options.
> > -            <1 1>, <1 3>, <1 4>, <1 6>, <1 20>, <1 8>, <10 81>, <1 10>
> > -            If property is not found default value depending on chip will be used.
> > -
> > -- qcom,ratiometric:
> > -    Usage: optional
> > -    Value type: <empty>
> > -    Definition: Channel calibration type.
> > -            - For compatible property "qcom,spmi-vadc", if this property is
> > -              specified VADC will use the VDD reference (1.8V) and GND for
> > -              channel calibration. If property is not found, channel will be
> > -              calibrated with 0.625V and 1.25V reference channels, also
> > -              known as absolute calibration.
> > -            - For compatible property "qcom,spmi-adc5" and "qcom,spmi-adc-rev2",
> > -              if this property is specified VADC will use the VDD reference
> > -              (1.875V) and GND for channel calibration. If property is not found,
> > -              channel will be calibrated with 0V and 1.25V reference channels,
> > -              also known as absolute calibration.
> > -
> > -- qcom,hw-settle-time:
> > -    Usage: optional
> > -    Value type: <u32>
> > -    Definition: Time between AMUX getting configured and the ADC starting
> > -            conversion. The 'hw_settle_time' is an index used from valid values
> > -            and programmed in hardware to achieve the hardware settling delay.
> > -            - For compatible property "qcom,spmi-vadc" and "qcom,spmi-adc-rev2",
> > -              Delay = 100us * (hw_settle_time) for hw_settle_time < 11,
> > -              and 2ms * (hw_settle_time - 10) otherwise.
> > -              Valid values are: 0, 100, 200, 300, 400, 500, 600, 700, 800,
> > -              900 us and 1, 2, 4, 6, 8, 10 ms.
> > -              If property is not found, channel will use 0us.
> > -            - For compatible property "qcom,spmi-adc5", delay = 15us for
> > -              value 0, 100us * (value) for values < 11,
> > -              and 2ms * (value - 10) otherwise.
> > -              Valid values are: 15, 100, 200, 300, 400, 500, 600, 700, 800,
> > -              900 us and 1, 2, 4, 6, 8, 10 ms
> > -              Certain controller digital versions have valid values of
> > -              15, 100, 200, 300, 400, 500, 600, 700, 1, 2, 4, 8, 16, 32, 64, 128 ms
> > -              If property is not found, channel will use 15us.
> > -
> > -- qcom,avg-samples:
> > -    Usage: optional
> > -    Value type: <u32>
> > -    Definition: Number of samples to be used for measurement.
> > -            Averaging provides the option to obtain a single measurement
> > -            from the ADC that is an average of multiple samples. The value
> > -            selected is 2^(value).
> > -            - For compatible property "qcom,spmi-vadc", valid values
> > -              are: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512
> > -              If property is not found, 1 sample will be used.
> > -            - For compatible property "qcom,spmi-adc5" and "qcom,spmi-adc-rev2",
> > -              valid values are: 1, 2, 4, 8, 16
> > -              If property is not found, 1 sample will be used.
> > -
> > -NOTE:
> > -
> > -For compatible property "qcom,spmi-vadc" following channels, also known as
> > -reference point channels, are used for result calibration and their channel
> > -configuration nodes should be defined:
> > -VADC_REF_625MV and/or VADC_SPARE1(based on PMIC version) VADC_REF_1250MV,
> > -VADC_GND_REF and VADC_VDD_VADC.
> > -
> > -Example:
> > -
> > -#include <dt-bindings/iio/qcom,spmi-vadc.h>
> > -#include <linux/irq.h>
> > -/* ... */
> > -
> > -	/* VADC node */
> > -	pmic_vadc: vadc@3100 {
> > -		compatible = "qcom,spmi-vadc";
> > -		reg = <0x3100>;
> > -		interrupts = <0x0 0x31 0x0 IRQ_TYPE_EDGE_RISING>;
> > -		#address-cells = <1>;
> > -		#size-cells = <0>;
> > -		#io-channel-cells = <1>;
> > -		io-channel-ranges;
> > -
> > -		/* Channel node */
> > -		adc-chan@VADC_LR_MUX10_USB_ID {
> > -			reg = <VADC_LR_MUX10_USB_ID>;
> > -			qcom,decimation = <512>;
> > -			qcom,ratiometric;
> > -			qcom,hw-settle-time = <200>;
> > -			qcom,avg-samples = <1>;
> > -			qcom,pre-scaling = <1 3>;
> > -		};
> > -	};
> > -
> > -	/* IIO client node */
> > -	usb {
> > -		io-channels = <&pmic_vadc VADC_LR_MUX10_USB_ID>;
> > -		io-channel-names = "vadc";
> > -	};
> > diff --git a/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml b/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml
> > new file mode 100644
> > index 0000000..de8d243
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml
> > @@ -0,0 +1,252 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/iio/adc/qcom,spmi-vadc.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Qualcomm's SPMI PMIC ADC
> > +
> > +maintainers:
> > +  - Andy Gross <agross@kernel.org>
> > +  - Bjorn Andersson <bjorn.andersson@linaro.org>
> > +
> > +description: |
> > +  SPMI PMIC voltage ADC (VADC) provides interface to clients to read
> > +  voltage. The VADC is a 15-bit sigma-delta ADC.
> > +  SPMI PMIC5 voltage ADC (ADC) provides interface to clients to read
> > +  voltage. The VADC is a 16-bit sigma-delta ADC.
> > +
> > +properties:
> > +  compatible:
> > +    oneOf:
> > +      - items:
> > +          - const: qcom,pms405-adc
> > +          - const: qcom,spmi-adc-rev2
> > +
> > +      - items:
> > +        - enum:
> > +          - qcom,spmi-vadc
> > +          - qcom,spmi-adc5
> > +          - qcom,spmi-adc-rev2
> > +
> > +  reg:
> > +    description: VADC base address in the SPMI PMIC register map
> > +    maxItems: 1
> > +
> > +  '#address-cells':
> > +    const: 1
> > +
> > +  '#size-cells':
> > +    const: 0
> > +
> > +  '#io-channel-cells':
> > +    const: 1
> > +
> > +  interrupts:
> > +    maxItems: 1
> > +    description:
> > +      End of conversion interrupt.
> > +
> > +required:
> > +  - compatible
> > +  - reg
> > +  - '#address-cells'
> > +  - '#size-cells'
> > +  - '#io-channel-cells'
> > +
> > +patternProperties:
> > +  "^.*@[0-9a-f]+$":
> > +    type: object
> > +    description: |
> > +      Represents the external channels which are connected to the ADC.
> > +      For compatible property "qcom,spmi-vadc" following channels, also known as
> > +      reference point channels, are used for result calibration and their channel
> > +      configuration nodes should be defined:
> > +      VADC_REF_625MV and/or VADC_SPARE1(based on PMIC version) VADC_REF_1250MV,
> > +      VADC_GND_REF and VADC_VDD_VADC.
> > +
> > +    properties:
> > +      reg:
> > +        description: |
> > +          ADC channel number.
> > +          See include/dt-bindings/iio/qcom,spmi-vadc.h
> > +
> > +      label:
> > +        $ref: /schemas/types.yaml#/definitions/string
> > +        description: |
> > +            ADC input of the platform as seen in the schematics.
> > +            For thermistor inputs connected to generic AMUX or GPIO inputs
> > +            these can vary across platform for the same pins. Hence select
> > +            the platform schematics name for this channel.
> > +
> > +      qcom,decimation:
> > +        $ref: /schemas/types.yaml#/definitions/uint32
> > +        description: |
> > +            This parameter is used to decrease ADC sampling rate.
> > +            Quicker measurements can be made by reducing decimation ratio.
> > +
> > +      qcom,pre-scaling:
> > +        description: |
> > +            Used for scaling the channel input signal before the signal is
> > +            fed to VADC. The configuration for this node is to know the
> > +            pre-determined ratio and use it for post scaling. It is a pair of
> > +            integers, denoting the numerator and denominator of the fraction by which
> > +            input signal is multiplied. For example, <1 3> indicates the signal is scaled
> > +            down to 1/3 of its value before ADC measurement.
> > +            If property is not found default value depending on chip will be used.
> > +        allOf:
> > +          - $ref: /schemas/types.yaml#/definitions/uint32-array
> > +        oneOf:
> > +          - items:
> > +            - const: 1
> > +            - enum: [ 1, 3, 4, 6, 20, 8, 10 ]
> > +
> > +          - items:
> > +            - const: 10
> > +            - const: 81
> > +
> > +      qcom,ratiometric:
> > +        description: |
> > +            Channel calibration type.
> > +            - For compatible property "qcom,spmi-vadc", if this property is
> > +              specified VADC will use the VDD reference (1.8V) and GND for
> > +              channel calibration. If property is not found, channel will be
> > +              calibrated with 0.625V and 1.25V reference channels, also
> > +              known as absolute calibration.
> > +            - For compatible property "qcom,spmi-adc5" and "qcom,spmi-adc-rev2",
> > +              if this property is specified VADC will use the VDD reference (1.875V)
> > +              and GND for channel calibration. If property is not found, channel
> > +              will be calibrated with 0V and 1.25V reference channels, also known
> > +              as absolute calibration.
> > +        type: boolean
> > +
> > +      qcom,hw-settle-time:
> > +        $ref: /schemas/types.yaml#/definitions/uint32
> > +        description: |
> > +            Time between AMUX getting configured and the ADC starting
> > +            conversion. The 'hw_settle_time' is an index used from valid values
> > +            and programmed in hardware to achieve the hardware settling delay.
> > +
> > +      qcom,avg-samples:
> > +        $ref: /schemas/types.yaml#/definitions/uint32
> > +        description: |
> > +            Number of samples to be used for measurement.
> > +            Averaging provides the option to obtain a single measurement
> > +            from the ADC that is an average of multiple samples. The value
> > +            selected is 2^(value).
> > +
> > +    required:
> > +      - reg
> > +
> > +allOf:
> > +  - if:
> > +      properties:
> > +        compatible:
> > +          contains:
> > +            const: qcom,spmi-vadc
> > +
> > +    then:
> > +      patternProperties:
> > +        "^.*@[0-9a-f]+$":
> > +          properties:
> > +            qcom,decimation:
> > +              enum: [ 512, 1024, 2048, 4096 ]
> > +              default: 512
> > +
> > +            qcom,hw-settle-time:
> > +              enum: [ 0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1, 2,
> > +                      4, 6, 8, 10 ]
> > +              default: 0
> > +
> > +            qcom,avg-samples:
> > +              enum: [ 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 ]
> > +              default: 1
> > +
> > +  - if:
> > +      properties:
> > +        compatible:
> > +          contains:
> > +            const: qcom,spmi-adc-rev2
> > +
> > +    then:
> > +      patternProperties:
> > +        "^.*@[0-9a-f]+$":
> > +          properties:
> > +            qcom,decimation:
> > +              enum: [ 256, 512, 1024 ]
> > +              default: 1024
> > +
> > +            qcom,hw-settle-time:
> > +              enum: [ 0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1, 2,
> > +                      4, 6, 8, 10 ]
> > +              default: 0
> > +
> > +            qcom,avg-samples:
> > +              enum: [ 1, 2, 4, 8, 16 ]
> > +              default: 1
> > +
> > +  - if:
> > +      properties:
> > +        compatible:
> > +          contains:
> > +            const: qcom,spmi-adc5
> > +
> > +    then:
> > +      patternProperties:
> > +        "^.*@[0-9a-f]+$":
> > +          properties:
> > +            qcom,decimation:
> > +              enum: [ 250, 420, 840 ]
> > +              default: 840
> > +
> > +            qcom,hw-settle-time:
> > +              enum: [ 15, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1, 2,
> > +                      4, 6, 8, 10, 16, 32, 64, 128 ]
> > +              default: 15
> > +
> > +            qcom,avg-samples:
> > +              enum: [ 1, 2, 4, 8, 16 ]
> > +              default: 1
> > +
> > +examples:
> > +  - |
> > +    spmi_bus {
> > +      #address-cells = <1>;
> > +      #size-cells = <0>;
> > +      /* VADC node */
> > +      pmic_vadc: adc@3100 {
> > +        compatible = "qcom,spmi-vadc";
> > +        reg = <0x3100>;
> > +        interrupts = <0x0 0x31 0x0 0x1>;
> > +        #address-cells = <1>;
> > +        #size-cells = <0>;
> > +        #io-channel-cells = <1>;
> > +        io-channel-ranges;
> > +
> > +        /* Channel node */
> > +        adc-chan@39 {
> > +          reg = <0x39>;
> > +          qcom,decimation = <512>;
> > +          qcom,ratiometric;
> > +          qcom,hw-settle-time = <200>;
> > +          qcom,avg-samples = <1>;
> > +          qcom,pre-scaling = <1 3>;
> > +        };
> > +
> > +        adc-chan@9 {
> > +          reg = <0x9>;
> > +        };
> > +
> > +        adc-chan@a {
> > +          reg = <0xa>;
> > +        };
> > +
> > +        adc-chan@e {
> > +          reg = <0xe>;
> > +        };
> > +
> > +        adc-chan@f {
> > +          reg = <0xf>;
> > +        };
> > +      };
> > +    };  
> 


^ permalink raw reply

* Re: [PATCH V6 6/7] iio: adc: Update debug prints
From: Jonathan Cameron @ 2020-05-31 10:53 UTC (permalink / raw)
  To: Jishnu Prakash
  Cc: agross, bjorn.andersson, devicetree, linux-kernel, mka,
	linus.walleij, Jonathan.Cameron, andy.shevchenko, amit.kucheria,
	smohanad, kgunda, aghayal, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, linux-arm-msm, linux-iio,
	linux-arm-msm-owner
In-Reply-To: <1590684869-15400-7-git-send-email-jprakash@codeaurora.org>

On Thu, 28 May 2020 22:24:28 +0530
Jishnu Prakash <jprakash@codeaurora.org> wrote:

> Change pr_err/pr_debug statements to dev_err/dev_dbg for
> increased clarity.
> 
> Signed-off-by: Jishnu Prakash <jprakash@codeaurora.org>
> Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>

Applied. Thanks, J
> ---
>  drivers/iio/adc/qcom-spmi-adc5.c | 18 +++++++++---------
>  1 file changed, 9 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c
> index 3022313..0f9af66 100644
> --- a/drivers/iio/adc/qcom-spmi-adc5.c
> +++ b/drivers/iio/adc/qcom-spmi-adc5.c
> @@ -246,11 +246,11 @@ static int adc5_read_voltage_data(struct adc5_chip *adc, u16 *data)
>  	*data = (rslt_msb << 8) | rslt_lsb;
>  
>  	if (*data == ADC5_USR_DATA_CHECK) {
> -		pr_err("Invalid data:0x%x\n", *data);
> +		dev_err(adc->dev, "Invalid data:0x%x\n", *data);
>  		return -EINVAL;
>  	}
>  
> -	pr_debug("voltage raw code:0x%x\n", *data);
> +	dev_dbg(adc->dev, "voltage raw code:0x%x\n", *data);
>  
>  	return 0;
>  }
> @@ -382,24 +382,24 @@ static int adc5_do_conversion(struct adc5_chip *adc,
>  
>  	ret = adc5_configure(adc, prop);
>  	if (ret) {
> -		pr_err("ADC configure failed with %d\n", ret);
> +		dev_err(adc->dev, "ADC configure failed with %d\n", ret);
>  		goto unlock;
>  	}
>  
>  	if (adc->poll_eoc) {
>  		ret = adc5_poll_wait_eoc(adc);
>  		if (ret) {
> -			pr_err("EOC bit not set\n");
> +			dev_err(adc->dev, "EOC bit not set\n");
>  			goto unlock;
>  		}
>  	} else {
>  		ret = wait_for_completion_timeout(&adc->complete,
>  							ADC5_CONV_TIMEOUT);
>  		if (!ret) {
> -			pr_debug("Did not get completion timeout.\n");
> +			dev_dbg(adc->dev, "Did not get completion timeout.\n");
>  			ret = adc5_poll_wait_eoc(adc);
>  			if (ret) {
> -				pr_err("EOC bit not set\n");
> +				dev_err(adc->dev, "EOC bit not set\n");
>  				goto unlock;
>  			}
>  		}
> @@ -721,7 +721,7 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
>  	channel_name = of_get_property(node,
>  				"label", NULL) ? : node->name;
>  	if (!channel_name) {
> -		pr_err("Invalid channel name\n");
> +		dev_err(dev, "Invalid channel name\n");
>  		return -EINVAL;
>  	}
>  	prop->datasheet_name = channel_name;
> @@ -764,7 +764,7 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
>  			return ret;
>  		}
>  
> -		pr_debug("dig_ver:minor:%d, major:%d\n", dig_version[0],
> +		dev_dbg(dev, "dig_ver:minor:%d, major:%d\n", dig_version[0],
>  						dig_version[1]);
>  		/* Digital controller >= 5.3 have hw_settle_2 option */
>  		if ((dig_version[0] >= ADC5_HW_SETTLE_DIFF_MINOR &&
> @@ -966,7 +966,7 @@ static int adc5_probe(struct platform_device *pdev)
>  
>  	ret = adc5_get_dt_data(adc, node);
>  	if (ret) {
> -		pr_err("adc get dt data failed\n");
> +		dev_err(dev, "adc get dt data failed\n");
>  		return ret;
>  	}
>  


^ permalink raw reply

* Re: [PATCH V6 7/7] iio: adc: Add a common read function for PMIC5 and PMIC7
From: Jonathan Cameron @ 2020-05-31 10:52 UTC (permalink / raw)
  To: Jishnu Prakash
  Cc: agross, bjorn.andersson, devicetree, linux-kernel, mka,
	linus.walleij, Jonathan.Cameron, andy.shevchenko, amit.kucheria,
	smohanad, kgunda, aghayal, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, linux-arm-msm, linux-iio,
	linux-arm-msm-owner
In-Reply-To: <1590684869-15400-8-git-send-email-jprakash@codeaurora.org>

On Thu, 28 May 2020 22:24:29 +0530
Jishnu Prakash <jprakash@codeaurora.org> wrote:

> Add a common function used for read_raw callback for both PMIC5
> and PMIC7 ADCs.
> 
> Signed-off-by: Jishnu Prakash <jprakash@codeaurora.org>

Hmm. I'm not completely sold on this one.   Suggestions below.

Jonathan


> ---
>  drivers/iio/adc/qcom-spmi-adc5.c | 53 +++++++++++++++++++---------------------
>  1 file changed, 25 insertions(+), 28 deletions(-)
> 
> diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c
> index 0f9af66..fe49741 100644
> --- a/drivers/iio/adc/qcom-spmi-adc5.c
> +++ b/drivers/iio/adc/qcom-spmi-adc5.c
> @@ -449,6 +449,13 @@ static int adc7_do_conversion(struct adc5_chip *adc,
>  	return ret;
>  }
>  
> +struct adc_do_conversion {
> +	int (*adc_do_conversion)(struct adc5_chip *adc,
> +			struct adc5_channel_prop *prop,
> +			struct iio_chan_spec const *chan,
> +			u16 *data_volt, u16 *data_cur);

Why use a structure for this? It's just a function pointer.
If they form is too long you can always use a typedef.

This is fine if you have other stuff coming shortly that will add
to this structure but for now it's just a bit confusing.

Directly passing the function pointer will reduce the amount
of coded added here and make the argument in favour of refactoring
rather stronger.


> +};
> +
>  static irqreturn_t adc5_isr(int irq, void *dev_id)
>  {
>  	struct adc5_chip *adc = dev_id;
> @@ -487,9 +494,9 @@ static int adc7_of_xlate(struct iio_dev *indio_dev,
>  	return -EINVAL;
>  }
>  
> -static int adc5_read_raw(struct iio_dev *indio_dev,
> +static int adc_read_raw_common(struct iio_dev *indio_dev,
>  			 struct iio_chan_spec const *chan, int *val, int *val2,
> -			 long mask)
> +			 long mask, struct adc_do_conversion do_conv)
>  {
>  	struct adc5_chip *adc = iio_priv(indio_dev);
>  	struct adc5_channel_prop *prop;
> @@ -500,8 +507,8 @@ static int adc5_read_raw(struct iio_dev *indio_dev,
>  
>  	switch (mask) {
>  	case IIO_CHAN_INFO_PROCESSED:
> -		ret = adc5_do_conversion(adc, prop, chan,
> -				&adc_code_volt, &adc_code_cur);
> +		ret = do_conv.adc_do_conversion(adc, prop, chan,
> +					&adc_code_volt, &adc_code_cur);
>  		if (ret)
>  			return ret;
>  
> @@ -518,36 +525,26 @@ static int adc5_read_raw(struct iio_dev *indio_dev,
>  	}
>  }
>  
> -static int adc7_read_raw(struct iio_dev *indio_dev,
> +static int adc5_read_raw(struct iio_dev *indio_dev,
>  			 struct iio_chan_spec const *chan, int *val, int *val2,
>  			 long mask)
>  {
> -	struct adc5_chip *adc = iio_priv(indio_dev);
> -	struct adc5_channel_prop *prop;
> -	u16 adc_code_volt, adc_code_cur;
> -	int ret;
> -
> -	prop = &adc->chan_props[chan->address];
> -
> -	switch (mask) {
> -	case IIO_CHAN_INFO_PROCESSED:
> -		ret = adc7_do_conversion(adc, prop, chan,
> -					&adc_code_volt, &adc_code_cur);
> -		if (ret)
> -			return ret;
> +	struct adc_do_conversion do_conv;
>  
> -		ret = qcom_adc5_hw_scale(prop->scale_fn_type,
> -			&adc5_prescale_ratios[prop->prescale],
> -			adc->data,
> -			adc_code_volt, val);
> +	do_conv.adc_do_conversion = adc5_do_conversion;
> +	return adc_read_raw_common(indio_dev, chan, val, val2,
> +				mask, do_conv);
> +}
>  
> -		if (ret)
> -			return ret;
> +static int adc7_read_raw(struct iio_dev *indio_dev,
> +			 struct iio_chan_spec const *chan, int *val, int *val2,
> +			 long mask)
> +{
> +	struct adc_do_conversion do_conv;
>  
> -		return IIO_VAL_INT;
> -	default:
> -		return -EINVAL;
> -	}
> +	do_conv.adc_do_conversion = adc7_do_conversion;
> +	return adc_read_raw_common(indio_dev, chan, val, val2,
> +				mask, do_conv);
>  }
>  
>  static const struct iio_info adc5_info = {


^ permalink raw reply

* Re: [PATCH V6 5/7] iio: adc: Update return value checks
From: Jonathan Cameron @ 2020-05-31 10:49 UTC (permalink / raw)
  To: Jishnu Prakash
  Cc: agross, bjorn.andersson, devicetree, linux-kernel, mka,
	linus.walleij, Jonathan.Cameron, andy.shevchenko, amit.kucheria,
	smohanad, kgunda, aghayal, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, linux-arm-msm, linux-iio,
	linux-arm-msm-owner
In-Reply-To: <1590684869-15400-6-git-send-email-jprakash@codeaurora.org>

On Thu, 28 May 2020 22:24:27 +0530
Jishnu Prakash <jprakash@codeaurora.org> wrote:

> Clean up some return value checks to make code more compact.
> 
> Signed-off-by: Jishnu Prakash <jprakash@codeaurora.org>

Applied.

Thanks,

J
> ---
>  drivers/iio/adc/qcom-spmi-adc5.c | 10 ++++------
>  1 file changed, 4 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c
> index dcc7599..3022313 100644
> --- a/drivers/iio/adc/qcom-spmi-adc5.c
> +++ b/drivers/iio/adc/qcom-spmi-adc5.c
> @@ -301,7 +301,7 @@ static int adc5_configure(struct adc5_chip *adc,
>  
>  	/* Read registers 0x42 through 0x46 */
>  	ret = adc5_read(adc, ADC5_USR_DIG_PARAM, buf, sizeof(buf));
> -	if (ret < 0)
> +	if (ret)
>  		return ret;
>  
>  	/* Digital param selection */
> @@ -388,7 +388,7 @@ static int adc5_do_conversion(struct adc5_chip *adc,
>  
>  	if (adc->poll_eoc) {
>  		ret = adc5_poll_wait_eoc(adc);
> -		if (ret < 0) {
> +		if (ret) {
>  			pr_err("EOC bit not set\n");
>  			goto unlock;
>  		}
> @@ -398,7 +398,7 @@ static int adc5_do_conversion(struct adc5_chip *adc,
>  		if (!ret) {
>  			pr_debug("Did not get completion timeout.\n");
>  			ret = adc5_poll_wait_eoc(adc);
> -			if (ret < 0) {
> +			if (ret) {
>  				pr_err("EOC bit not set\n");
>  				goto unlock;
>  			}
> @@ -516,8 +516,6 @@ static int adc5_read_raw(struct iio_dev *indio_dev,
>  	default:
>  		return -EINVAL;
>  	}
> -
> -	return 0;
>  }
>  
>  static int adc7_read_raw(struct iio_dev *indio_dev,
> @@ -761,7 +759,7 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
>  
>  		ret = adc5_read(adc, ADC5_USR_REVISION1, dig_version,
>  							sizeof(dig_version));
> -		if (ret < 0) {
> +		if (ret) {
>  			dev_err(dev, "Invalid dig version read %d\n", ret);
>  			return ret;
>  		}


^ permalink raw reply

* Re: [PATCH V6 3/7] iio: adc: Add info property under adc_data
From: Jonathan Cameron @ 2020-05-31 10:48 UTC (permalink / raw)
  To: Jishnu Prakash
  Cc: agross, bjorn.andersson, devicetree, linux-kernel, mka,
	linus.walleij, Jonathan.Cameron, andy.shevchenko, amit.kucheria,
	smohanad, kgunda, aghayal, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, linux-arm-msm, linux-iio,
	linux-arm-msm-owner
In-Reply-To: <1590684869-15400-4-git-send-email-jprakash@codeaurora.org>

On Thu, 28 May 2020 22:24:25 +0530
Jishnu Prakash <jprakash@codeaurora.org> wrote:

> Add info property under adc_data to support adding ADC variants
> which may use different iio_info than the one defined for PMIC5.
> 
> Signed-off-by: Jishnu Prakash <jprakash@codeaurora.org>

Applied.

Thanks,

Jonathan

> ---
>  drivers/iio/adc/qcom-spmi-adc5.c   | 4 +++-
>  drivers/iio/adc/qcom-vadc-common.h | 1 +
>  2 files changed, 4 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c
> index 21fdcde..0fa1d37 100644
> --- a/drivers/iio/adc/qcom-spmi-adc5.c
> +++ b/drivers/iio/adc/qcom-spmi-adc5.c
> @@ -629,6 +629,7 @@ static const struct adc5_data adc5_data_pmic = {
>  	.full_scale_code_volt = 0x70e4,
>  	.full_scale_code_cur = 0x2710,
>  	.adc_chans = adc5_chans_pmic,
> +	.info = &adc5_info,
>  	.decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
>  				{250, 420, 840},
>  	.hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
> @@ -643,6 +644,7 @@ static const struct adc5_data adc5_data_pmic_rev2 = {
>  	.full_scale_code_volt = 0x4000,
>  	.full_scale_code_cur = 0x1800,
>  	.adc_chans = adc5_chans_rev2,
> +	.info = &adc5_info,
>  	.decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
>  				{256, 512, 1024},
>  	.hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
> @@ -777,7 +779,7 @@ static int adc5_probe(struct platform_device *pdev)
>  	indio_dev->dev.of_node = node;
>  	indio_dev->name = pdev->name;
>  	indio_dev->modes = INDIO_DIRECT_MODE;
> -	indio_dev->info = &adc5_info;
> +	indio_dev->info = adc->data->info;
>  	indio_dev->channels = adc->iio_chans;
>  	indio_dev->num_channels = adc->nchannels;
>  
> diff --git a/drivers/iio/adc/qcom-vadc-common.h b/drivers/iio/adc/qcom-vadc-common.h
> index e074902a..6a7553f 100644
> --- a/drivers/iio/adc/qcom-vadc-common.h
> +++ b/drivers/iio/adc/qcom-vadc-common.h
> @@ -136,6 +136,7 @@ struct adc5_data {
>  	const u32	full_scale_code_volt;
>  	const u32	full_scale_code_cur;
>  	const struct adc5_channels *adc_chans;
> +	const struct iio_info *info;
>  	unsigned int	*decimation;
>  	unsigned int	*hw_settle_1;
>  	unsigned int	*hw_settle_2;


^ permalink raw reply

* Re: [PATCH V6 4/7] iio: adc: Add support for PMIC7 ADC
From: Jonathan Cameron @ 2020-05-31 10:47 UTC (permalink / raw)
  To: Jishnu Prakash
  Cc: agross, bjorn.andersson, devicetree, linux-kernel, mka,
	linus.walleij, Jonathan.Cameron, andy.shevchenko, amit.kucheria,
	smohanad, kgunda, aghayal, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, linux-arm-msm, linux-iio,
	linux-arm-msm-owner
In-Reply-To: <1590684869-15400-5-git-send-email-jprakash@codeaurora.org>

On Thu, 28 May 2020 22:24:26 +0530
Jishnu Prakash <jprakash@codeaurora.org> wrote:

> The ADC architecture on PMIC7 is changed as compared to PMIC5. The
> major change from PMIC5 is that all SW communication to ADC goes through
> PMK8350, which communicates with other PMICs through PBS when the ADC
> on PMK8350 works in master mode. The SID register is used to identify the
> PMICs with which the PBS needs to communicate. Add support for the same.
> 
> Signed-off-by: Jishnu Prakash <jprakash@codeaurora.org>
> Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>

one nitpick inline. But otherwise looks good to me.
Nitpick is trivial so I'll ignore it.

Applied to the togreg branch of iio.git and pushed out as testing
for the autobuilders to play with it.

Thanks,

Jonathan


> ---
>  drivers/iio/adc/qcom-spmi-adc5.c   | 215 +++++++++++++++++++++++++++++-
>  drivers/iio/adc/qcom-vadc-common.c | 262 +++++++++++++++++++++++++++++++++++++
>  drivers/iio/adc/qcom-vadc-common.h |  14 ++
>  3 files changed, 488 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c
> index 0fa1d37..dcc7599 100644
> --- a/drivers/iio/adc/qcom-spmi-adc5.c
> +++ b/drivers/iio/adc/qcom-spmi-adc5.c
> @@ -1,6 +1,6 @@
>  // SPDX-License-Identifier: GPL-2.0
>  /*
> - * Copyright (c) 2018, The Linux Foundation. All rights reserved.
> + * Copyright (c) 2018, 2020, The Linux Foundation. All rights reserved.
>   */
>  
>  #include <linux/bitops.h>
> @@ -23,6 +23,7 @@
>  
>  #define ADC5_USR_REVISION1			0x0
>  #define ADC5_USR_STATUS1			0x8
> +#define ADC5_USR_STATUS1_CONV_FAULT		BIT(7)
>  #define ADC5_USR_STATUS1_REQ_STS		BIT(1)
>  #define ADC5_USR_STATUS1_EOC			BIT(0)
>  #define ADC5_USR_STATUS1_REQ_STS_EOC_MASK	0x3
> @@ -65,6 +66,9 @@
>  
>  #define ADC5_USR_IBAT_DATA1			0x53
>  
> +#define ADC_CHANNEL_OFFSET			0x8
> +#define ADC_CHANNEL_MASK			GENMASK(7, 0)
> +
>  /*
>   * Conversion time varies based on the decimation, clock rate, fast average
>   * samples and measurements queued across different VADC peripherals.
> @@ -79,6 +83,11 @@
>  #define ADC5_HW_SETTLE_DIFF_MINOR		3
>  #define ADC5_HW_SETTLE_DIFF_MAJOR		5
>  
> +/* For PMIC7 */
> +#define ADC_APP_SID				0x40
> +#define ADC_APP_SID_MASK			GENMASK(3, 0)
> +#define ADC7_CONV_TIMEOUT			msecs_to_jiffies(10)
> +
>  enum adc5_cal_method {
>  	ADC5_NO_CAL = 0,
>  	ADC5_RATIOMETRIC_CAL,
> @@ -96,6 +105,7 @@ enum adc5_cal_val {
>   * @cal_method: calibration method.
>   * @cal_val: calibration value
>   * @decimation: sampling rate supported for the channel.
> + * @sid: slave id of PMIC owning the channel, for PMIC7.
>   * @prescale: channel scaling performed on the input signal.
>   * @hw_settle_time: the time between AMUX being configured and the
>   *	start of conversion.
> @@ -110,6 +120,7 @@ struct adc5_channel_prop {
>  	enum adc5_cal_method	cal_method;
>  	enum adc5_cal_val	cal_val;
>  	unsigned int		decimation;
> +	unsigned int		sid;
>  	unsigned int		prescale;
>  	unsigned int		hw_settle_time;
>  	unsigned int		avg_samples;
> @@ -165,6 +176,11 @@ static int adc5_write(struct adc5_chip *adc, u16 offset, u8 *data, int len)
>  	return regmap_bulk_write(adc->regmap, adc->base + offset, data, len);
>  }
>  
> +static int adc5_masked_write(struct adc5_chip *adc, u16 offset, u8 mask, u8 val)
> +{
> +	return regmap_update_bits(adc->regmap, adc->base + offset, mask, val);
> +}
> +
>  static int adc5_prescaling_from_dt(u32 num, u32 den)
>  {
>  	unsigned int pre;
> @@ -314,6 +330,47 @@ static int adc5_configure(struct adc5_chip *adc,
>  	return adc5_write(adc, ADC5_USR_DIG_PARAM, buf, sizeof(buf));
>  }
>  
> +static int adc7_configure(struct adc5_chip *adc,
> +			struct adc5_channel_prop *prop)
> +{
> +	int ret;
> +	u8 conv_req = 0, buf[4];
> +
> +	ret = adc5_masked_write(adc, ADC_APP_SID, ADC_APP_SID_MASK, prop->sid);
> +	if (ret)
> +		return ret;
> +
> +	ret = adc5_read(adc, ADC5_USR_DIG_PARAM, buf, sizeof(buf));
> +	if (ret)
> +		return ret;
> +
> +	/* Digital param selection */
> +	adc5_update_dig_param(adc, prop, &buf[0]);
> +
> +	/* Update fast average sample value */
> +	buf[1] &= ~ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK;
> +	buf[1] |= prop->avg_samples;
> +
> +	/* Select ADC channel */
> +	buf[2] = prop->channel;
> +
> +	/* Select HW settle delay for channel */
> +	buf[3] &= ~ADC5_USR_HW_SETTLE_DELAY_MASK;
> +	buf[3] |= prop->hw_settle_time;
> +
> +	/* Select CONV request */
> +	conv_req = ADC5_USR_CONV_REQ_REQ;
> +
> +	if (!adc->poll_eoc)
> +		reinit_completion(&adc->complete);
> +
> +	ret = adc5_write(adc, ADC5_USR_DIG_PARAM, buf, sizeof(buf));
> +	if (ret)
> +		return ret;
> +
> +	return adc5_write(adc, ADC5_USR_CONV_REQ, &conv_req, 1);
> +}
> +
>  static int adc5_do_conversion(struct adc5_chip *adc,
>  			struct adc5_channel_prop *prop,
>  			struct iio_chan_spec const *chan,
> @@ -355,6 +412,43 @@ static int adc5_do_conversion(struct adc5_chip *adc,
>  	return ret;
>  }
>  
> +static int adc7_do_conversion(struct adc5_chip *adc,
> +			struct adc5_channel_prop *prop,
> +			struct iio_chan_spec const *chan,
> +			u16 *data_volt, u16 *data_cur)
> +{
> +	int ret;
> +	u8 status;
> +
> +	mutex_lock(&adc->lock);
> +
> +	ret = adc7_configure(adc, prop);
> +	if (ret) {
> +		dev_err(adc->dev, "ADC configure failed with %d\n", ret);
> +		goto unlock;
> +	}
> +
> +	/* No support for polling mode at present */
> +	wait_for_completion_timeout(&adc->complete, ADC7_CONV_TIMEOUT);
> +
> +	ret = adc5_read(adc, ADC5_USR_STATUS1, &status, 1);
> +	if (ret)
> +		goto unlock;
> +
> +	if (status & ADC5_USR_STATUS1_CONV_FAULT) {
> +		dev_err(adc->dev, "Unexpected conversion fault\n");
> +		ret = -EIO;
> +		goto unlock;
> +	}
> +
> +	ret = adc5_read_voltage_data(adc, data_volt);
> +
> +unlock:
> +	mutex_unlock(&adc->lock);
> +
> +	return ret;
> +}
> +
>  static irqreturn_t adc5_isr(int irq, void *dev_id)
>  {
>  	struct adc5_chip *adc = dev_id;
> @@ -377,6 +471,22 @@ static int adc5_of_xlate(struct iio_dev *indio_dev,
>  	return -EINVAL;
>  }
>  
> +static int adc7_of_xlate(struct iio_dev *indio_dev,
> +				const struct of_phandle_args *iiospec)
> +{
> +	struct adc5_chip *adc = iio_priv(indio_dev);
> +	int i, v_channel;
> +
> +	for (i = 0; i < adc->nchannels; i++) {
> +		v_channel = (adc->chan_props[i].sid << ADC_CHANNEL_OFFSET) |
> +			adc->chan_props[i].channel;
> +		if (v_channel == iiospec->args[0])
> +			return i;
> +	}
> +
> +	return -EINVAL;
> +}
> +
>  static int adc5_read_raw(struct iio_dev *indio_dev,
>  			 struct iio_chan_spec const *chan, int *val, int *val2,
>  			 long mask)
> @@ -410,11 +520,48 @@ static int adc5_read_raw(struct iio_dev *indio_dev,
>  	return 0;
>  }
>  
> +static int adc7_read_raw(struct iio_dev *indio_dev,
> +			 struct iio_chan_spec const *chan, int *val, int *val2,
> +			 long mask)
> +{
> +	struct adc5_chip *adc = iio_priv(indio_dev);
> +	struct adc5_channel_prop *prop;
> +	u16 adc_code_volt, adc_code_cur;
> +	int ret;
> +
> +	prop = &adc->chan_props[chan->address];
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_PROCESSED:
> +		ret = adc7_do_conversion(adc, prop, chan,
> +					&adc_code_volt, &adc_code_cur);
> +		if (ret)
> +			return ret;
> +
> +		ret = qcom_adc5_hw_scale(prop->scale_fn_type,
> +			&adc5_prescale_ratios[prop->prescale],
> +			adc->data,
> +			adc_code_volt, val);
> +
> +		if (ret)
> +			return ret;
> +
> +		return IIO_VAL_INT;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
>  static const struct iio_info adc5_info = {
>  	.read_raw = adc5_read_raw,
>  	.of_xlate = adc5_of_xlate,
>  };
>  
> +static const struct iio_info adc7_info = {
> +	.read_raw = adc7_read_raw,
> +	.of_xlate = adc7_of_xlate,
> +};
> +
>  struct adc5_channels {
>  	const char *datasheet_name;
>  	unsigned int prescale_index;
> @@ -477,6 +624,39 @@ static const struct adc5_channels adc5_chans_pmic[ADC5_MAX_CHANNEL] = {
>  					SCALE_HW_CALIB_PM5_SMB_TEMP)
>  };
>  
> +static const struct adc5_channels adc7_chans_pmic[ADC5_MAX_CHANNEL] = {
> +	[ADC7_REF_GND]		= ADC5_CHAN_VOLT("ref_gnd", 0,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC7_1P25VREF]		= ADC5_CHAN_VOLT("vref_1p25", 0,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC7_VPH_PWR]		= ADC5_CHAN_VOLT("vph_pwr", 1,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC7_VBAT_SNS]		= ADC5_CHAN_VOLT("vbat_sns", 3,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC7_DIE_TEMP]		= ADC5_CHAN_TEMP("die_temp", 0,
> +					SCALE_HW_CALIB_PMIC_THERM_PM7)
> +	[ADC7_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_pu2", 0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC7_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_pu2", 0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC7_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_pu2", 0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC7_AMUX_THM4_100K_PU] = ADC5_CHAN_TEMP("amux_thm4_pu2", 0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC7_AMUX_THM5_100K_PU] = ADC5_CHAN_TEMP("amux_thm5_pu2", 0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC7_AMUX_THM6_100K_PU] = ADC5_CHAN_TEMP("amux_thm6_pu2", 0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC7_GPIO1_100K_PU]	= ADC5_CHAN_TEMP("gpio1_pu2", 0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC7_GPIO2_100K_PU]	= ADC5_CHAN_TEMP("gpio2_pu2", 0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC7_GPIO3_100K_PU]	= ADC5_CHAN_TEMP("gpio3_pu2", 0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC7_GPIO4_100K_PU]	= ADC5_CHAN_TEMP("gpio4_pu2", 0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +};
> +
>  static const struct adc5_channels adc5_chans_rev2[ADC5_MAX_CHANNEL] = {
>  	[ADC5_REF_GND]		= ADC5_CHAN_VOLT("ref_gnd", 0,
>  					SCALE_HW_CALIB_DEFAULT)
> @@ -511,6 +691,7 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
>  {
>  	const char *name = node->name, *channel_name;
>  	u32 chan, value, varr[2];
> +	u32 sid = 0;
>  	int ret;
>  	struct device *dev = adc->dev;
>  
> @@ -520,6 +701,15 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
>  		return ret;
>  	}
>  
> +	/* Value read from "reg" is virtual channel number */
> +
> +	/* virtual channel number = sid << 8 | channel number */
> +
> +	if (adc->data->info == &adc7_info) {
> +		sid = chan >> ADC_CHANNEL_OFFSET;
> +		chan = chan & ADC_CHANNEL_MASK;
> +	}
> +
>  	if (chan > ADC5_PARALLEL_ISENSE_VBAT_IDATA ||
>  	    !data->adc_chans[chan].datasheet_name) {
>  		dev_err(dev, "%s invalid channel number %d\n", name, chan);
> @@ -528,6 +718,7 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
>  
>  	/* the channel has DT description */
>  	prop->channel = chan;
> +	prop->sid = sid;
>  
>  	channel_name = of_get_property(node,
>  				"label", NULL) ? : node->name;
> @@ -578,8 +769,9 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
>  		pr_debug("dig_ver:minor:%d, major:%d\n", dig_version[0],
>  						dig_version[1]);
>  		/* Digital controller >= 5.3 have hw_settle_2 option */
> -		if (dig_version[0] >= ADC5_HW_SETTLE_DIFF_MINOR &&
> -			dig_version[1] >= ADC5_HW_SETTLE_DIFF_MAJOR)
> +		if ((dig_version[0] >= ADC5_HW_SETTLE_DIFF_MINOR &&
> +			dig_version[1] >= ADC5_HW_SETTLE_DIFF_MAJOR) ||
> +			adc->data->info == &adc7_info)
>  			ret = adc5_hw_settle_time_from_dt(value,
>  							data->hw_settle_2);
>  		else
> @@ -640,6 +832,18 @@ static const struct adc5_data adc5_data_pmic = {
>  				1, 2, 4, 8, 16, 32, 64, 128},
>  };
>  
> +static const struct adc5_data adc7_data_pmic = {
> +	.full_scale_code_volt = 0x70e4,
> +	.adc_chans = adc7_chans_pmic,
> +	.info = &adc7_info,
> +	.decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
> +				{85, 340, 1360},
> +	.hw_settle_2 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
> +				{15, 100, 200, 300, 400, 500, 600, 700,
> +				1000, 2000, 4000, 8000, 16000, 32000,
> +				64000, 128000},
> +};
> +
>  static const struct adc5_data adc5_data_pmic_rev2 = {
>  	.full_scale_code_volt = 0x4000,
>  	.full_scale_code_cur = 0x1800,
> @@ -661,6 +865,10 @@ static const struct of_device_id adc5_match_table[] = {
>  		.data = &adc5_data_pmic,
>  	},
>  	{
> +		.compatible = "qcom,spmi-adc7",
> +		.data = &adc7_data_pmic,
> +	},
> +	{
>  		.compatible = "qcom,spmi-adc-rev2",
>  		.data = &adc5_data_pmic_rev2,
>  	},
> @@ -754,6 +962,7 @@ static int adc5_probe(struct platform_device *pdev)
>  	adc->regmap = regmap;
>  	adc->dev = dev;
>  	adc->base = reg;
> +

nitpick but should not be in this patch.  Please try to clean these out
as they just add noise during review.

>  	init_completion(&adc->complete);
>  	mutex_init(&adc->lock);
>  
> diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c
> index 2bb78d1..5113aaa 100644
> --- a/drivers/iio/adc/qcom-vadc-common.c
> +++ b/drivers/iio/adc/qcom-vadc-common.c
> @@ -89,6 +89,195 @@ static const struct vadc_map_pt adcmap_100k_104ef_104fb_1875_vref[] = {
>  	{ 46,	125000 },
>  };
>  
> +static const struct vadc_map_pt adcmap7_die_temp[] = {
> +	{ 433700, 1967},
> +	{ 473100, 1964},
> +	{ 512400, 1957},
> +	{ 551500, 1949},
> +	{ 590500, 1940},
> +	{ 629300, 1930},
> +	{ 667900, 1921},
> +	{ 706400, 1910},
> +	{ 744600, 1896},
> +	{ 782500, 1878},
> +	{ 820100, 1859},
> +	{ 857300, 0},
> +};
> +
> +/*
> + * Resistance to temperature table for 100k pull up for NTCG104EF104.
> + */
> +static const struct vadc_map_pt adcmap7_100k[] = {
> +	{ 4250657, -40960 },
> +	{ 3962085, -39936 },
> +	{ 3694875, -38912 },
> +	{ 3447322, -37888 },
> +	{ 3217867, -36864 },
> +	{ 3005082, -35840 },
> +	{ 2807660, -34816 },
> +	{ 2624405, -33792 },
> +	{ 2454218, -32768 },
> +	{ 2296094, -31744 },
> +	{ 2149108, -30720 },
> +	{ 2012414, -29696 },
> +	{ 1885232, -28672 },
> +	{ 1766846, -27648 },
> +	{ 1656598, -26624 },
> +	{ 1553884, -25600 },
> +	{ 1458147, -24576 },
> +	{ 1368873, -23552 },
> +	{ 1285590, -22528 },
> +	{ 1207863, -21504 },
> +	{ 1135290, -20480 },
> +	{ 1067501, -19456 },
> +	{ 1004155, -18432 },
> +	{ 944935, -17408 },
> +	{ 889550, -16384 },
> +	{ 837731, -15360 },
> +	{ 789229, -14336 },
> +	{ 743813, -13312 },
> +	{ 701271, -12288 },
> +	{ 661405, -11264 },
> +	{ 624032, -10240 },
> +	{ 588982, -9216 },
> +	{ 556100, -8192 },
> +	{ 525239, -7168 },
> +	{ 496264, -6144 },
> +	{ 469050, -5120 },
> +	{ 443480, -4096 },
> +	{ 419448, -3072 },
> +	{ 396851, -2048 },
> +	{ 375597, -1024 },
> +	{ 355598, 0 },
> +	{ 336775, 1024 },
> +	{ 319052, 2048 },
> +	{ 302359, 3072 },
> +	{ 286630, 4096 },
> +	{ 271806, 5120 },
> +	{ 257829, 6144 },
> +	{ 244646, 7168 },
> +	{ 232209, 8192 },
> +	{ 220471, 9216 },
> +	{ 209390, 10240 },
> +	{ 198926, 11264 },
> +	{ 189040, 12288 },
> +	{ 179698, 13312 },
> +	{ 170868, 14336 },
> +	{ 162519, 15360 },
> +	{ 154622, 16384 },
> +	{ 147150, 17408 },
> +	{ 140079, 18432 },
> +	{ 133385, 19456 },
> +	{ 127046, 20480 },
> +	{ 121042, 21504 },
> +	{ 115352, 22528 },
> +	{ 109960, 23552 },
> +	{ 104848, 24576 },
> +	{ 100000, 25600 },
> +	{ 95402, 26624 },
> +	{ 91038, 27648 },
> +	{ 86897, 28672 },
> +	{ 82965, 29696 },
> +	{ 79232, 30720 },
> +	{ 75686, 31744 },
> +	{ 72316, 32768 },
> +	{ 69114, 33792 },
> +	{ 66070, 34816 },
> +	{ 63176, 35840 },
> +	{ 60423, 36864 },
> +	{ 57804, 37888 },
> +	{ 55312, 38912 },
> +	{ 52940, 39936 },
> +	{ 50681, 40960 },
> +	{ 48531, 41984 },
> +	{ 46482, 43008 },
> +	{ 44530, 44032 },
> +	{ 42670, 45056 },
> +	{ 40897, 46080 },
> +	{ 39207, 47104 },
> +	{ 37595, 48128 },
> +	{ 36057, 49152 },
> +	{ 34590, 50176 },
> +	{ 33190, 51200 },
> +	{ 31853, 52224 },
> +	{ 30577, 53248 },
> +	{ 29358, 54272 },
> +	{ 28194, 55296 },
> +	{ 27082, 56320 },
> +	{ 26020, 57344 },
> +	{ 25004, 58368 },
> +	{ 24033, 59392 },
> +	{ 23104, 60416 },
> +	{ 22216, 61440 },
> +	{ 21367, 62464 },
> +	{ 20554, 63488 },
> +	{ 19776, 64512 },
> +	{ 19031, 65536 },
> +	{ 18318, 66560 },
> +	{ 17636, 67584 },
> +	{ 16982, 68608 },
> +	{ 16355, 69632 },
> +	{ 15755, 70656 },
> +	{ 15180, 71680 },
> +	{ 14628, 72704 },
> +	{ 14099, 73728 },
> +	{ 13592, 74752 },
> +	{ 13106, 75776 },
> +	{ 12640, 76800 },
> +	{ 12192, 77824 },
> +	{ 11762, 78848 },
> +	{ 11350, 79872 },
> +	{ 10954, 80896 },
> +	{ 10574, 81920 },
> +	{ 10209, 82944 },
> +	{ 9858, 83968 },
> +	{ 9521, 84992 },
> +	{ 9197, 86016 },
> +	{ 8886, 87040 },
> +	{ 8587, 88064 },
> +	{ 8299, 89088 },
> +	{ 8023, 90112 },
> +	{ 7757, 91136 },
> +	{ 7501, 92160 },
> +	{ 7254, 93184 },
> +	{ 7017, 94208 },
> +	{ 6789, 95232 },
> +	{ 6570, 96256 },
> +	{ 6358, 97280 },
> +	{ 6155, 98304 },
> +	{ 5959, 99328 },
> +	{ 5770, 100352 },
> +	{ 5588, 101376 },
> +	{ 5412, 102400 },
> +	{ 5243, 103424 },
> +	{ 5080, 104448 },
> +	{ 4923, 105472 },
> +	{ 4771, 106496 },
> +	{ 4625, 107520 },
> +	{ 4484, 108544 },
> +	{ 4348, 109568 },
> +	{ 4217, 110592 },
> +	{ 4090, 111616 },
> +	{ 3968, 112640 },
> +	{ 3850, 113664 },
> +	{ 3736, 114688 },
> +	{ 3626, 115712 },
> +	{ 3519, 116736 },
> +	{ 3417, 117760 },
> +	{ 3317, 118784 },
> +	{ 3221, 119808 },
> +	{ 3129, 120832 },
> +	{ 3039, 121856 },
> +	{ 2952, 122880 },
> +	{ 2868, 123904 },
> +	{ 2787, 124928 },
> +	{ 2709, 125952 },
> +	{ 2633, 126976 },
> +	{ 2560, 128000 },
> +	{ 2489, 129024 },
> +	{ 2420, 130048 }
> +};
> +
>  static int qcom_vadc_scale_hw_calib_volt(
>  				const struct vadc_prescale_ratio *prescale,
>  				const struct adc5_data *data,
> @@ -97,6 +286,10 @@ static int qcom_vadc_scale_hw_calib_therm(
>  				const struct vadc_prescale_ratio *prescale,
>  				const struct adc5_data *data,
>  				u16 adc_code, int *result_mdec);
> +static int qcom_vadc7_scale_hw_calib_therm(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc5_data *data,
> +				u16 adc_code, int *result_mdec);
>  static int qcom_vadc_scale_hw_smb_temp(
>  				const struct vadc_prescale_ratio *prescale,
>  				const struct adc5_data *data,
> @@ -109,12 +302,20 @@ static int qcom_vadc_scale_hw_calib_die_temp(
>  				const struct vadc_prescale_ratio *prescale,
>  				const struct adc5_data *data,
>  				u16 adc_code, int *result_mdec);
> +static int qcom_vadc7_scale_hw_calib_die_temp(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc5_data *data,
> +				u16 adc_code, int *result_mdec);
>  
>  static struct qcom_adc5_scale_type scale_adc5_fn[] = {
>  	[SCALE_HW_CALIB_DEFAULT] = {qcom_vadc_scale_hw_calib_volt},
>  	[SCALE_HW_CALIB_THERM_100K_PULLUP] = {qcom_vadc_scale_hw_calib_therm},
>  	[SCALE_HW_CALIB_XOTHERM] = {qcom_vadc_scale_hw_calib_therm},
> +	[SCALE_HW_CALIB_THERM_100K_PU_PM7] = {
> +					qcom_vadc7_scale_hw_calib_therm},
>  	[SCALE_HW_CALIB_PMIC_THERM] = {qcom_vadc_scale_hw_calib_die_temp},
> +	[SCALE_HW_CALIB_PMIC_THERM_PM7] = {
> +					qcom_vadc7_scale_hw_calib_die_temp},
>  	[SCALE_HW_CALIB_PM5_CHG_TEMP] = {qcom_vadc_scale_hw_chg5_temp},
>  	[SCALE_HW_CALIB_PM5_SMB_TEMP] = {qcom_vadc_scale_hw_smb_temp},
>  };
> @@ -291,6 +492,32 @@ static int qcom_vadc_scale_code_voltage_factor(u16 adc_code,
>  	return (int) voltage;
>  }
>  
> +static int qcom_vadc7_scale_hw_calib_therm(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc5_data *data,
> +				u16 adc_code, int *result_mdec)
> +{
> +	s64 resistance = adc_code;
> +	int ret, result;
> +
> +	if (adc_code >= RATIO_MAX_ADC7)
> +		return -EINVAL;
> +
> +	/* (ADC code * R_PULLUP (100Kohm)) / (full_scale_code - ADC code)*/
> +	resistance *= R_PU_100K;
> +	resistance = div64_s64(resistance, RATIO_MAX_ADC7 - adc_code);
> +
> +	ret = qcom_vadc_map_voltage_temp(adcmap7_100k,
> +				 ARRAY_SIZE(adcmap7_100k),
> +				 resistance, &result);
> +	if (ret)
> +		return ret;
> +
> +	*result_mdec = result;
> +
> +	return 0;
> +}
> +
>  static int qcom_vadc_scale_hw_calib_volt(
>  				const struct vadc_prescale_ratio *prescale,
>  				const struct adc5_data *data,
> @@ -330,6 +557,41 @@ static int qcom_vadc_scale_hw_calib_die_temp(
>  	return 0;
>  }
>  
> +static int qcom_vadc7_scale_hw_calib_die_temp(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc5_data *data,
> +				u16 adc_code, int *result_mdec)
> +{
> +
> +	int voltage, vtemp0, temp, i;
> +
> +	voltage = qcom_vadc_scale_code_voltage_factor(adc_code,
> +				prescale, data, 1);
> +
> +	if (adcmap7_die_temp[0].x > voltage) {
> +		*result_mdec = DIE_TEMP_ADC7_SCALE_1;
> +		return 0;
> +	}
> +
> +	if (adcmap7_die_temp[ARRAY_SIZE(adcmap7_die_temp) - 1].x <= voltage) {
> +		*result_mdec = DIE_TEMP_ADC7_MAX;
> +		return 0;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(adcmap7_die_temp); i++)
> +		if (adcmap7_die_temp[i].x > voltage)
> +			break;
> +
> +	vtemp0 = adcmap7_die_temp[i - 1].x;
> +	voltage = voltage - vtemp0;
> +	temp = div64_s64(voltage * DIE_TEMP_ADC7_SCALE_FACTOR,
> +		adcmap7_die_temp[i - 1].y);
> +	temp += DIE_TEMP_ADC7_SCALE_1 + (DIE_TEMP_ADC7_SCALE_2 * (i - 1));
> +	*result_mdec = temp;
> +
> +	return 0;
> +}
> +
>  static int qcom_vadc_scale_hw_smb_temp(
>  				const struct vadc_prescale_ratio *prescale,
>  				const struct adc5_data *data,
> diff --git a/drivers/iio/adc/qcom-vadc-common.h b/drivers/iio/adc/qcom-vadc-common.h
> index 6a7553f..17b2fc4 100644
> --- a/drivers/iio/adc/qcom-vadc-common.h
> +++ b/drivers/iio/adc/qcom-vadc-common.h
> @@ -49,6 +49,14 @@
>  #define ADC5_FULL_SCALE_CODE			0x70e4
>  #define ADC5_USR_DATA_CHECK			0x8000
>  
> +#define R_PU_100K				100000
> +#define RATIO_MAX_ADC7				BIT(14)
> +
> +#define DIE_TEMP_ADC7_SCALE_1			-60000
> +#define DIE_TEMP_ADC7_SCALE_2			20000
> +#define DIE_TEMP_ADC7_SCALE_FACTOR		1000
> +#define DIE_TEMP_ADC7_MAX			160000
> +
>  /**
>   * struct vadc_map_pt - Map the graph representation for ADC channel
>   * @x: Represent the ADC digitized code.
> @@ -110,8 +118,12 @@ struct vadc_prescale_ratio {
>   *	lookup table. The hardware applies offset/slope to adc code.
>   * SCALE_HW_CALIB_XOTHERM: Returns XO thermistor voltage in millidegC using
>   *	100k pullup. The hardware applies offset/slope to adc code.
> + * SCALE_HW_CALIB_THERM_100K_PU_PM7: Returns temperature in millidegC using
> + *	lookup table for PMIC7. The hardware applies offset/slope to adc code.
>   * SCALE_HW_CALIB_PMIC_THERM: Returns result in milli degree's Centigrade.
>   *	The hardware applies offset/slope to adc code.
> + * SCALE_HW_CALIB_PMIC_THERM: Returns result in milli degree's Centigrade.
> + *	The hardware applies offset/slope to adc code. This is for PMIC7.
>   * SCALE_HW_CALIB_PM5_CHG_TEMP: Returns result in millidegrees for PMIC5
>   *	charger temperature.
>   * SCALE_HW_CALIB_PM5_SMB_TEMP: Returns result in millidegrees for PMIC5
> @@ -126,7 +138,9 @@ enum vadc_scale_fn_type {
>  	SCALE_HW_CALIB_DEFAULT,
>  	SCALE_HW_CALIB_THERM_100K_PULLUP,
>  	SCALE_HW_CALIB_XOTHERM,
> +	SCALE_HW_CALIB_THERM_100K_PU_PM7,
>  	SCALE_HW_CALIB_PMIC_THERM,
> +	SCALE_HW_CALIB_PMIC_THERM_PM7,
>  	SCALE_HW_CALIB_PM5_CHG_TEMP,
>  	SCALE_HW_CALIB_PM5_SMB_TEMP,
>  	SCALE_HW_CALIB_INVALID,


^ permalink raw reply

* Re: [PATCH V6 2/7] iio: adc: Add PMIC7 ADC bindings
From: Jonathan Cameron @ 2020-05-31 10:45 UTC (permalink / raw)
  To: Jishnu Prakash
  Cc: agross, bjorn.andersson, devicetree, linux-kernel, mka,
	linus.walleij, Jonathan.Cameron, andy.shevchenko, amit.kucheria,
	smohanad, kgunda, aghayal, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, Rob Herring, Mark Rutland, Amit Kucheria,
	linux-iio, linux-arm-msm, linux-arm-msm-owner
In-Reply-To: <1590684869-15400-3-git-send-email-jprakash@codeaurora.org>

On Thu, 28 May 2020 22:24:24 +0530
Jishnu Prakash <jprakash@codeaurora.org> wrote:

> Add documentation for PMIC7 ADC peripheral.
> For the PMIC7-type PMICs, ADC peripheral is present in HW for the
> following PMICs: PMK8350, PM8350, PM8350b, PMR735a and PMR735b.
> Of these, only the ADC peripheral on PMK8350 is exposed directly to SW.
> If SW needs to communicate with ADCs on other PMICs, it specifies the
> PMIC to PMK8350 through the newly added SID register and communication
> between PMK8350 ADC and other PMIC ADCs is carried out through
> PBS(Programmable Boot Sequence) at the firmware level.
> 
> In addition, add definitions for ADC channels and virtual channel
> definitions (combination of ADC channel number and PMIC SID number)
> per PMIC, to be used by ADC clients for PMIC7.
> 
> Signed-off-by: Jishnu Prakash <jprakash@codeaurora.org>
> Reviewed-by: Amit Kucheria <amit.kucheria@linaro.org>
> Reviewed-by: Rob Herring <robh@kernel.org>
Applied.

Thanks,

J
> ---
>  .../bindings/iio/adc/qcom,spmi-vadc.yaml           | 38 ++++++++--
>  include/dt-bindings/iio/qcom,spmi-adc7-pm8350.h    | 67 ++++++++++++++++
>  include/dt-bindings/iio/qcom,spmi-adc7-pm8350b.h   | 88 ++++++++++++++++++++++
>  include/dt-bindings/iio/qcom,spmi-adc7-pmk8350.h   | 46 +++++++++++
>  include/dt-bindings/iio/qcom,spmi-adc7-pmr735a.h   | 28 +++++++
>  include/dt-bindings/iio/qcom,spmi-adc7-pmr735b.h   | 28 +++++++
>  include/dt-bindings/iio/qcom,spmi-vadc.h           | 78 ++++++++++++++++++-
>  7 files changed, 366 insertions(+), 7 deletions(-)
>  create mode 100644 include/dt-bindings/iio/qcom,spmi-adc7-pm8350.h
>  create mode 100644 include/dt-bindings/iio/qcom,spmi-adc7-pm8350b.h
>  create mode 100644 include/dt-bindings/iio/qcom,spmi-adc7-pmk8350.h
>  create mode 100644 include/dt-bindings/iio/qcom,spmi-adc7-pmr735a.h
>  create mode 100644 include/dt-bindings/iio/qcom,spmi-adc7-pmr735b.h
> 
> diff --git a/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml b/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml
> index de8d243..e6263b6 100644
> --- a/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml
> +++ b/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml
> @@ -13,7 +13,7 @@ maintainers:
>  description: |
>    SPMI PMIC voltage ADC (VADC) provides interface to clients to read
>    voltage. The VADC is a 15-bit sigma-delta ADC.
> -  SPMI PMIC5 voltage ADC (ADC) provides interface to clients to read
> +  SPMI PMIC5/PMIC7 voltage ADC (ADC) provides interface to clients to read
>    voltage. The VADC is a 16-bit sigma-delta ADC.
>  
>  properties:
> @@ -28,6 +28,7 @@ properties:
>            - qcom,spmi-vadc
>            - qcom,spmi-adc5
>            - qcom,spmi-adc-rev2
> +          - qcom,spmi-adc7
>  
>    reg:
>      description: VADC base address in the SPMI PMIC register map
> @@ -70,6 +71,8 @@ patternProperties:
>          description: |
>            ADC channel number.
>            See include/dt-bindings/iio/qcom,spmi-vadc.h
> +          For PMIC7 ADC, the channel numbers are specified separately per PMIC
> +          in the PMIC-specific files in include/dt-bindings/iio/.
>  
>        label:
>          $ref: /schemas/types.yaml#/definitions/string
> @@ -113,11 +116,11 @@ patternProperties:
>                channel calibration. If property is not found, channel will be
>                calibrated with 0.625V and 1.25V reference channels, also
>                known as absolute calibration.
> -            - For compatible property "qcom,spmi-adc5" and "qcom,spmi-adc-rev2",
> -              if this property is specified VADC will use the VDD reference (1.875V)
> -              and GND for channel calibration. If property is not found, channel
> -              will be calibrated with 0V and 1.25V reference channels, also known
> -              as absolute calibration.
> +            - For compatible property "qcom,spmi-adc5", "qcom,spmi-adc7" and
> +              "qcom,spmi-adc-rev2", if this property is specified VADC will use
> +              the VDD reference (1.875V) and GND for channel calibration. If
> +              property is not found, channel will be calibrated with 0V and 1.25V
> +              reference channels, also known as absolute calibration.
>          type: boolean
>  
>        qcom,hw-settle-time:
> @@ -208,6 +211,29 @@ allOf:
>                enum: [ 1, 2, 4, 8, 16 ]
>                default: 1
>  
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: qcom,spmi-adc7
> +
> +    then:
> +      patternProperties:
> +        "^.*@[0-9a-f]+$":
> +          properties:
> +            qcom,decimation:
> +              enum: [ 85, 340, 1360 ]
> +              default: 1360
> +
> +            qcom,hw-settle-time:
> +              enum: [ 15, 100, 200, 300, 400, 500, 600, 700, 1000, 2000, 4000,
> +                      8000, 16000, 32000, 64000, 128000 ]
> +              default: 15
> +
> +            qcom,avg-samples:
> +              enum: [ 1, 2, 4, 8, 16 ]
> +              default: 1
> +
>  examples:
>    - |
>      spmi_bus {
> diff --git a/include/dt-bindings/iio/qcom,spmi-adc7-pm8350.h b/include/dt-bindings/iio/qcom,spmi-adc7-pm8350.h
> new file mode 100644
> index 0000000..9426f27
> --- /dev/null
> +++ b/include/dt-bindings/iio/qcom,spmi-adc7-pm8350.h
> @@ -0,0 +1,67 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2020, The Linux Foundation. All rights reserved.
> + */
> +
> +#ifndef _DT_BINDINGS_QCOM_SPMI_VADC_PM8350_H
> +#define _DT_BINDINGS_QCOM_SPMI_VADC_PM8350_H
> +
> +#ifndef PM8350_SID
> +#define PM8350_SID					1
> +#endif
> +
> +/* ADC channels for PM8350_ADC for PMIC7 */
> +#define PM8350_ADC7_REF_GND			(PM8350_SID << 8 | 0x0)
> +#define PM8350_ADC7_1P25VREF			(PM8350_SID << 8 | 0x01)
> +#define PM8350_ADC7_VREF_VADC			(PM8350_SID << 8 | 0x02)
> +#define PM8350_ADC7_DIE_TEMP			(PM8350_SID << 8 | 0x03)
> +
> +#define PM8350_ADC7_AMUX_THM1			(PM8350_SID << 8 | 0x04)
> +#define PM8350_ADC7_AMUX_THM2			(PM8350_SID << 8 | 0x05)
> +#define PM8350_ADC7_AMUX_THM3			(PM8350_SID << 8 | 0x06)
> +#define PM8350_ADC7_AMUX_THM4			(PM8350_SID << 8 | 0x07)
> +#define PM8350_ADC7_AMUX_THM5			(PM8350_SID << 8 | 0x08)
> +#define PM8350_ADC7_GPIO1			(PM8350_SID << 8 | 0x0a)
> +#define PM8350_ADC7_GPIO2			(PM8350_SID << 8 | 0x0b)
> +#define PM8350_ADC7_GPIO3			(PM8350_SID << 8 | 0x0c)
> +#define PM8350_ADC7_GPIO4			(PM8350_SID << 8 | 0x0d)
> +
> +/* 30k pull-up1 */
> +#define PM8350_ADC7_AMUX_THM1_30K_PU		(PM8350_SID << 8 | 0x24)
> +#define PM8350_ADC7_AMUX_THM2_30K_PU		(PM8350_SID << 8 | 0x25)
> +#define PM8350_ADC7_AMUX_THM3_30K_PU		(PM8350_SID << 8 | 0x26)
> +#define PM8350_ADC7_AMUX_THM4_30K_PU		(PM8350_SID << 8 | 0x27)
> +#define PM8350_ADC7_AMUX_THM5_30K_PU		(PM8350_SID << 8 | 0x28)
> +#define PM8350_ADC7_GPIO1_30K_PU		(PM8350_SID << 8 | 0x2a)
> +#define PM8350_ADC7_GPIO2_30K_PU		(PM8350_SID << 8 | 0x2b)
> +#define PM8350_ADC7_GPIO3_30K_PU		(PM8350_SID << 8 | 0x2c)
> +#define PM8350_ADC7_GPIO4_30K_PU		(PM8350_SID << 8 | 0x2d)
> +
> +/* 100k pull-up2 */
> +#define PM8350_ADC7_AMUX_THM1_100K_PU		(PM8350_SID << 8 | 0x44)
> +#define PM8350_ADC7_AMUX_THM2_100K_PU		(PM8350_SID << 8 | 0x45)
> +#define PM8350_ADC7_AMUX_THM3_100K_PU		(PM8350_SID << 8 | 0x46)
> +#define PM8350_ADC7_AMUX_THM4_100K_PU		(PM8350_SID << 8 | 0x47)
> +#define PM8350_ADC7_AMUX_THM5_100K_PU		(PM8350_SID << 8 | 0x48)
> +#define PM8350_ADC7_GPIO1_100K_PU		(PM8350_SID << 8 | 0x4a)
> +#define PM8350_ADC7_GPIO2_100K_PU		(PM8350_SID << 8 | 0x4b)
> +#define PM8350_ADC7_GPIO3_100K_PU		(PM8350_SID << 8 | 0x4c)
> +#define PM8350_ADC7_GPIO4_100K_PU		(PM8350_SID << 8 | 0x4d)
> +
> +/* 400k pull-up3 */
> +#define PM8350_ADC7_AMUX_THM1_400K_PU		(PM8350_SID << 8 | 0x64)
> +#define PM8350_ADC7_AMUX_THM2_400K_PU		(PM8350_SID << 8 | 0x65)
> +#define PM8350_ADC7_AMUX_THM3_400K_PU		(PM8350_SID << 8 | 0x66)
> +#define PM8350_ADC7_AMUX_THM4_400K_PU		(PM8350_SID << 8 | 0x67)
> +#define PM8350_ADC7_AMUX_THM5_400K_PU		(PM8350_SID << 8 | 0x68)
> +#define PM8350_ADC7_GPIO1_400K_PU		(PM8350_SID << 8 | 0x6a)
> +#define PM8350_ADC7_GPIO2_400K_PU		(PM8350_SID << 8 | 0x6b)
> +#define PM8350_ADC7_GPIO3_400K_PU		(PM8350_SID << 8 | 0x6c)
> +#define PM8350_ADC7_GPIO4_400K_PU		(PM8350_SID << 8 | 0x6d)
> +
> +/* 1/3 Divider */
> +#define PM8350_ADC7_GPIO4_DIV3			(PM8350_SID << 8 | 0x8d)
> +
> +#define PM8350_ADC7_VPH_PWR			(PM8350_SID << 8 | 0x8e)
> +
> +#endif /* _DT_BINDINGS_QCOM_SPMI_VADC_PM8350_H */
> diff --git a/include/dt-bindings/iio/qcom,spmi-adc7-pm8350b.h b/include/dt-bindings/iio/qcom,spmi-adc7-pm8350b.h
> new file mode 100644
> index 0000000..dc2497c
> --- /dev/null
> +++ b/include/dt-bindings/iio/qcom,spmi-adc7-pm8350b.h
> @@ -0,0 +1,88 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2020 The Linux Foundation. All rights reserved.
> + */
> +
> +#ifndef _DT_BINDINGS_QCOM_SPMI_VADC_PM8350B_H
> +#define _DT_BINDINGS_QCOM_SPMI_VADC_PM8350B_H
> +
> +#ifndef PM8350B_SID
> +#define PM8350B_SID					3
> +#endif
> +
> +/* ADC channels for PM8350B_ADC for PMIC7 */
> +#define PM8350B_ADC7_REF_GND			(PM8350B_SID << 8 | 0x0)
> +#define PM8350B_ADC7_1P25VREF			(PM8350B_SID << 8 | 0x01)
> +#define PM8350B_ADC7_VREF_VADC			(PM8350B_SID << 8 | 0x02)
> +#define PM8350B_ADC7_DIE_TEMP			(PM8350B_SID << 8 | 0x03)
> +
> +#define PM8350B_ADC7_AMUX_THM1			(PM8350B_SID << 8 | 0x04)
> +#define PM8350B_ADC7_AMUX_THM2			(PM8350B_SID << 8 | 0x05)
> +#define PM8350B_ADC7_AMUX_THM3			(PM8350B_SID << 8 | 0x06)
> +#define PM8350B_ADC7_AMUX_THM4			(PM8350B_SID << 8 | 0x07)
> +#define PM8350B_ADC7_AMUX_THM5			(PM8350B_SID << 8 | 0x08)
> +#define PM8350B_ADC7_AMUX_THM6			(PM8350B_SID << 8 | 0x09)
> +#define PM8350B_ADC7_GPIO1			(PM8350B_SID << 8 | 0x0a)
> +#define PM8350B_ADC7_GPIO2			(PM8350B_SID << 8 | 0x0b)
> +#define PM8350B_ADC7_GPIO3			(PM8350B_SID << 8 | 0x0c)
> +#define PM8350B_ADC7_GPIO4			(PM8350B_SID << 8 | 0x0d)
> +
> +#define PM8350B_ADC7_CHG_TEMP			(PM8350B_SID << 8 | 0x10)
> +#define PM8350B_ADC7_USB_IN_V_16		(PM8350B_SID << 8 | 0x11)
> +#define PM8350B_ADC7_VDC_16			(PM8350B_SID << 8 | 0x12)
> +#define PM8350B_ADC7_CC1_ID			(PM8350B_SID << 8 | 0x13)
> +#define PM8350B_ADC7_VREF_BAT_THERM		(PM8350B_SID << 8 | 0x15)
> +#define PM8350B_ADC7_IIN_FB			(PM8350B_SID << 8 | 0x17)
> +
> +/* 30k pull-up1 */
> +#define PM8350B_ADC7_AMUX_THM1_30K_PU		(PM8350B_SID << 8 | 0x24)
> +#define PM8350B_ADC7_AMUX_THM2_30K_PU		(PM8350B_SID << 8 | 0x25)
> +#define PM8350B_ADC7_AMUX_THM3_30K_PU		(PM8350B_SID << 8 | 0x26)
> +#define PM8350B_ADC7_AMUX_THM4_30K_PU		(PM8350B_SID << 8 | 0x27)
> +#define PM8350B_ADC7_AMUX_THM5_30K_PU		(PM8350B_SID << 8 | 0x28)
> +#define PM8350B_ADC7_AMUX_THM6_30K_PU		(PM8350B_SID << 8 | 0x29)
> +#define PM8350B_ADC7_GPIO1_30K_PU		(PM8350B_SID << 8 | 0x2a)
> +#define PM8350B_ADC7_GPIO2_30K_PU		(PM8350B_SID << 8 | 0x2b)
> +#define PM8350B_ADC7_GPIO3_30K_PU		(PM8350B_SID << 8 | 0x2c)
> +#define PM8350B_ADC7_GPIO4_30K_PU		(PM8350B_SID << 8 | 0x2d)
> +#define PM8350B_ADC7_CC1_ID_30K_PU		(PM8350B_SID << 8 | 0x33)
> +
> +/* 100k pull-up2 */
> +#define PM8350B_ADC7_AMUX_THM1_100K_PU		(PM8350B_SID << 8 | 0x44)
> +#define PM8350B_ADC7_AMUX_THM2_100K_PU		(PM8350B_SID << 8 | 0x45)
> +#define PM8350B_ADC7_AMUX_THM3_100K_PU		(PM8350B_SID << 8 | 0x46)
> +#define PM8350B_ADC7_AMUX_THM4_100K_PU		(PM8350B_SID << 8 | 0x47)
> +#define PM8350B_ADC7_AMUX_THM5_100K_PU		(PM8350B_SID << 8 | 0x48)
> +#define PM8350B_ADC7_AMUX_THM6_100K_PU		(PM8350B_SID << 8 | 0x49)
> +#define PM8350B_ADC7_GPIO1_100K_PU		(PM8350B_SID << 8 | 0x4a)
> +#define PM8350B_ADC7_GPIO2_100K_PU		(PM8350B_SID << 8 | 0x4b)
> +#define PM8350B_ADC7_GPIO3_100K_PU		(PM8350B_SID << 8 | 0x4c)
> +#define PM8350B_ADC7_GPIO4_100K_PU		(PM8350B_SID << 8 | 0x4d)
> +#define PM8350B_ADC7_CC1_ID_100K_PU		(PM8350B_SID << 8 | 0x53)
> +
> +/* 400k pull-up3 */
> +#define PM8350B_ADC7_AMUX_THM1_400K_PU		(PM8350B_SID << 8 | 0x64)
> +#define PM8350B_ADC7_AMUX_THM2_400K_PU		(PM8350B_SID << 8 | 0x65)
> +#define PM8350B_ADC7_AMUX_THM3_400K_PU		(PM8350B_SID << 8 | 0x66)
> +#define PM8350B_ADC7_AMUX_THM4_400K_PU		(PM8350B_SID << 8 | 0x67)
> +#define PM8350B_ADC7_AMUX_THM5_400K_PU		(PM8350B_SID << 8 | 0x68)
> +#define PM8350B_ADC7_AMUX_THM6_400K_PU		(PM8350B_SID << 8 | 0x69)
> +#define PM8350B_ADC7_GPIO1_400K_PU		(PM8350B_SID << 8 | 0x6a)
> +#define PM8350B_ADC7_GPIO2_400K_PU		(PM8350B_SID << 8 | 0x6b)
> +#define PM8350B_ADC7_GPIO3_400K_PU		(PM8350B_SID << 8 | 0x6c)
> +#define PM8350B_ADC7_GPIO4_400K_PU		(PM8350B_SID << 8 | 0x6d)
> +#define PM8350B_ADC7_CC1_ID_400K_PU		(PM8350B_SID << 8 | 0x73)
> +
> +/* 1/3 Divider */
> +#define PM8350B_ADC7_GPIO1_DIV3			(PM8350B_SID << 8 | 0x8a)
> +#define PM8350B_ADC7_GPIO2_DIV3			(PM8350B_SID << 8 | 0x8b)
> +#define PM8350B_ADC7_GPIO3_DIV3			(PM8350B_SID << 8 | 0x8c)
> +#define PM8350B_ADC7_GPIO4_DIV3			(PM8350B_SID << 8 | 0x8d)
> +
> +#define PM8350B_ADC7_VPH_PWR			(PM8350B_SID << 8 | 0x8e)
> +#define PM8350B_ADC7_VBAT_SNS			(PM8350B_SID << 8 | 0x8f)
> +
> +#define PM8350B_ADC7_SBUx			(PM8350B_SID << 8 | 0x94)
> +#define PM8350B_ADC7_VBAT_2S_MID		(PM8350B_SID << 8 | 0x96)
> +
> +#endif /* _DT_BINDINGS_QCOM_SPMI_VADC_PM8350B_H */
> diff --git a/include/dt-bindings/iio/qcom,spmi-adc7-pmk8350.h b/include/dt-bindings/iio/qcom,spmi-adc7-pmk8350.h
> new file mode 100644
> index 0000000..6c29687
> --- /dev/null
> +++ b/include/dt-bindings/iio/qcom,spmi-adc7-pmk8350.h
> @@ -0,0 +1,46 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2020 The Linux Foundation. All rights reserved.
> + */
> +
> +#ifndef _DT_BINDINGS_QCOM_SPMI_VADC_PMK8350_H
> +#define _DT_BINDINGS_QCOM_SPMI_VADC_PMK8350_H
> +
> +#ifndef PMK8350_SID
> +#define PMK8350_SID					0
> +#endif
> +
> +/* ADC channels for PMK8350_ADC for PMIC7 */
> +#define PMK8350_ADC7_REF_GND			(PMK8350_SID << 8 | 0x0)
> +#define PMK8350_ADC7_1P25VREF			(PMK8350_SID << 8 | 0x01)
> +#define PMK8350_ADC7_VREF_VADC			(PMK8350_SID << 8 | 0x02)
> +#define PMK8350_ADC7_DIE_TEMP			(PMK8350_SID << 8 | 0x03)
> +
> +#define PMK8350_ADC7_AMUX_THM1			(PMK8350_SID << 8 | 0x04)
> +#define PMK8350_ADC7_AMUX_THM2			(PMK8350_SID << 8 | 0x05)
> +#define PMK8350_ADC7_AMUX_THM3			(PMK8350_SID << 8 | 0x06)
> +#define PMK8350_ADC7_AMUX_THM4			(PMK8350_SID << 8 | 0x07)
> +#define PMK8350_ADC7_AMUX_THM5			(PMK8350_SID << 8 | 0x08)
> +
> +/* 30k pull-up1 */
> +#define PMK8350_ADC7_AMUX_THM1_30K_PU		(PMK8350_SID << 8 | 0x24)
> +#define PMK8350_ADC7_AMUX_THM2_30K_PU		(PMK8350_SID << 8 | 0x25)
> +#define PMK8350_ADC7_AMUX_THM3_30K_PU		(PMK8350_SID << 8 | 0x26)
> +#define PMK8350_ADC7_AMUX_THM4_30K_PU		(PMK8350_SID << 8 | 0x27)
> +#define PMK8350_ADC7_AMUX_THM5_30K_PU		(PMK8350_SID << 8 | 0x28)
> +
> +/* 100k pull-up2 */
> +#define PMK8350_ADC7_AMUX_THM1_100K_PU		(PMK8350_SID << 8 | 0x44)
> +#define PMK8350_ADC7_AMUX_THM2_100K_PU		(PMK8350_SID << 8 | 0x45)
> +#define PMK8350_ADC7_AMUX_THM3_100K_PU		(PMK8350_SID << 8 | 0x46)
> +#define PMK8350_ADC7_AMUX_THM4_100K_PU		(PMK8350_SID << 8 | 0x47)
> +#define PMK8350_ADC7_AMUX_THM5_100K_PU		(PMK8350_SID << 8 | 0x48)
> +
> +/* 400k pull-up3 */
> +#define PMK8350_ADC7_AMUX_THM1_400K_PU		(PMK8350_SID << 8 | 0x64)
> +#define PMK8350_ADC7_AMUX_THM2_400K_PU		(PMK8350_SID << 8 | 0x65)
> +#define PMK8350_ADC7_AMUX_THM3_400K_PU		(PMK8350_SID << 8 | 0x66)
> +#define PMK8350_ADC7_AMUX_THM4_400K_PU		(PMK8350_SID << 8 | 0x67)
> +#define PMK8350_ADC7_AMUX_THM5_400K_PU		(PMK8350_SID << 8 | 0x68)
> +
> +#endif /* _DT_BINDINGS_QCOM_SPMI_VADC_PMK8350_H */
> diff --git a/include/dt-bindings/iio/qcom,spmi-adc7-pmr735a.h b/include/dt-bindings/iio/qcom,spmi-adc7-pmr735a.h
> new file mode 100644
> index 0000000..d6df1b1
> --- /dev/null
> +++ b/include/dt-bindings/iio/qcom,spmi-adc7-pmr735a.h
> @@ -0,0 +1,28 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2020 The Linux Foundation. All rights reserved.
> + */
> +
> +#ifndef _DT_BINDINGS_QCOM_SPMI_VADC_PMR735A_H
> +#define _DT_BINDINGS_QCOM_SPMI_VADC_PMR735A_H
> +
> +#ifndef PMR735A_SID
> +#define PMR735A_SID					4
> +#endif
> +
> +/* ADC channels for PMR735A_ADC for PMIC7 */
> +#define PMR735A_ADC7_REF_GND			(PMR735A_SID << 8 | 0x0)
> +#define PMR735A_ADC7_1P25VREF			(PMR735A_SID << 8 | 0x01)
> +#define PMR735A_ADC7_VREF_VADC			(PMR735A_SID << 8 | 0x02)
> +#define PMR735A_ADC7_DIE_TEMP			(PMR735A_SID << 8 | 0x03)
> +
> +#define PMR735A_ADC7_GPIO1			(PMR735A_SID << 8 | 0x0a)
> +#define PMR735A_ADC7_GPIO2			(PMR735A_SID << 8 | 0x0b)
> +#define PMR735A_ADC7_GPIO3			(PMR735A_SID << 8 | 0x0c)
> +
> +/* 100k pull-up2 */
> +#define PMR735A_ADC7_GPIO1_100K_PU		(PMR735A_SID << 8 | 0x4a)
> +#define PMR735A_ADC7_GPIO2_100K_PU		(PMR735A_SID << 8 | 0x4b)
> +#define PMR735A_ADC7_GPIO3_100K_PU		(PMR735A_SID << 8 | 0x4c)
> +
> +#endif /* _DT_BINDINGS_QCOM_SPMI_VADC_PMR735A_H */
> diff --git a/include/dt-bindings/iio/qcom,spmi-adc7-pmr735b.h b/include/dt-bindings/iio/qcom,spmi-adc7-pmr735b.h
> new file mode 100644
> index 0000000..8da0e7d
> --- /dev/null
> +++ b/include/dt-bindings/iio/qcom,spmi-adc7-pmr735b.h
> @@ -0,0 +1,28 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2020 The Linux Foundation. All rights reserved.
> + */
> +
> +#ifndef _DT_BINDINGS_QCOM_SPMI_VADC_PMR735B_H
> +#define _DT_BINDINGS_QCOM_SPMI_VADC_PMR735B_H
> +
> +#ifndef PMR735B_SID
> +#define PMR735B_SID					5
> +#endif
> +
> +/* ADC channels for PMR735B_ADC for PMIC7 */
> +#define PMR735B_ADC7_REF_GND			(PMR735B_SID << 8 | 0x0)
> +#define PMR735B_ADC7_1P25VREF			(PMR735B_SID << 8 | 0x01)
> +#define PMR735B_ADC7_VREF_VADC			(PMR735B_SID << 8 | 0x02)
> +#define PMR735B_ADC7_DIE_TEMP			(PMR735B_SID << 8 | 0x03)
> +
> +#define PMR735B_ADC7_GPIO1			(PMR735B_SID << 8 | 0x0a)
> +#define PMR735B_ADC7_GPIO2			(PMR735B_SID << 8 | 0x0b)
> +#define PMR735B_ADC7_GPIO3			(PMR735B_SID << 8 | 0x0c)
> +
> +/* 100k pull-up2 */
> +#define PMR735B_ADC7_GPIO1_100K_PU		(PMR735B_SID << 8 | 0x4a)
> +#define PMR735B_ADC7_GPIO2_100K_PU		(PMR735B_SID << 8 | 0x4b)
> +#define PMR735B_ADC7_GPIO3_100K_PU		(PMR735B_SID << 8 | 0x4c)
> +
> +#endif /* _DT_BINDINGS_QCOM_SPMI_VADC_PMR735B_H */
> diff --git a/include/dt-bindings/iio/qcom,spmi-vadc.h b/include/dt-bindings/iio/qcom,spmi-vadc.h
> index 61d556d..08adfe2 100644
> --- a/include/dt-bindings/iio/qcom,spmi-vadc.h
> +++ b/include/dt-bindings/iio/qcom,spmi-vadc.h
> @@ -1,6 +1,6 @@
>  /* SPDX-License-Identifier: GPL-2.0 */
>  /*
> - * Copyright (c) 2012-2014,2018 The Linux Foundation. All rights reserved.
> + * Copyright (c) 2012-2014,2018,2020 The Linux Foundation. All rights reserved.
>   */
>  
>  #ifndef _DT_BINDINGS_QCOM_SPMI_VADC_H
> @@ -221,4 +221,80 @@
>  
>  #define ADC5_MAX_CHANNEL			0xc0
>  
> +/* ADC channels for ADC for PMIC7 */
> +
> +#define ADC7_REF_GND				0x00
> +#define ADC7_1P25VREF				0x01
> +#define ADC7_VREF_VADC				0x02
> +#define ADC7_DIE_TEMP				0x03
> +
> +#define ADC7_AMUX_THM1				0x04
> +#define ADC7_AMUX_THM2				0x05
> +#define ADC7_AMUX_THM3				0x06
> +#define ADC7_AMUX_THM4				0x07
> +#define ADC7_AMUX_THM5				0x08
> +#define ADC7_AMUX_THM6				0x09
> +#define ADC7_GPIO1				0x0a
> +#define ADC7_GPIO2				0x0b
> +#define ADC7_GPIO3				0x0c
> +#define ADC7_GPIO4				0x0d
> +
> +#define ADC7_CHG_TEMP				0x10
> +#define ADC7_USB_IN_V_16			0x11
> +#define ADC7_VDC_16				0x12
> +#define ADC7_CC1_ID				0x13
> +#define ADC7_VREF_BAT_THERM			0x15
> +#define ADC7_IIN_FB				0x17
> +
> +/* 30k pull-up1 */
> +#define ADC7_AMUX_THM1_30K_PU			0x24
> +#define ADC7_AMUX_THM2_30K_PU			0x25
> +#define ADC7_AMUX_THM3_30K_PU			0x26
> +#define ADC7_AMUX_THM4_30K_PU			0x27
> +#define ADC7_AMUX_THM5_30K_PU			0x28
> +#define ADC7_AMUX_THM6_30K_PU			0x29
> +#define ADC7_GPIO1_30K_PU			0x2a
> +#define ADC7_GPIO2_30K_PU			0x2b
> +#define ADC7_GPIO3_30K_PU			0x2c
> +#define ADC7_GPIO4_30K_PU			0x2d
> +#define ADC7_CC1_ID_30K_PU			0x33
> +
> +/* 100k pull-up2 */
> +#define ADC7_AMUX_THM1_100K_PU			0x44
> +#define ADC7_AMUX_THM2_100K_PU			0x45
> +#define ADC7_AMUX_THM3_100K_PU			0x46
> +#define ADC7_AMUX_THM4_100K_PU			0x47
> +#define ADC7_AMUX_THM5_100K_PU			0x48
> +#define ADC7_AMUX_THM6_100K_PU			0x49
> +#define ADC7_GPIO1_100K_PU			0x4a
> +#define ADC7_GPIO2_100K_PU			0x4b
> +#define ADC7_GPIO3_100K_PU			0x4c
> +#define ADC7_GPIO4_100K_PU			0x4d
> +#define ADC7_CC1_ID_100K_PU			0x53
> +
> +/* 400k pull-up3 */
> +#define ADC7_AMUX_THM1_400K_PU			0x64
> +#define ADC7_AMUX_THM2_400K_PU			0x65
> +#define ADC7_AMUX_THM3_400K_PU			0x66
> +#define ADC7_AMUX_THM4_400K_PU			0x67
> +#define ADC7_AMUX_THM5_400K_PU			0x68
> +#define ADC7_AMUX_THM6_400K_PU			0x69
> +#define ADC7_GPIO1_400K_PU			0x6a
> +#define ADC7_GPIO2_400K_PU			0x6b
> +#define ADC7_GPIO3_400K_PU			0x6c
> +#define ADC7_GPIO4_400K_PU			0x6d
> +#define ADC7_CC1_ID_400K_PU			0x73
> +
> +/* 1/3 Divider */
> +#define ADC7_GPIO1_DIV3				0x8a
> +#define ADC7_GPIO2_DIV3				0x8b
> +#define ADC7_GPIO3_DIV3				0x8c
> +#define ADC7_GPIO4_DIV3				0x8d
> +
> +#define ADC7_VPH_PWR				0x8e
> +#define ADC7_VBAT_SNS				0x8f
> +
> +#define ADC7_SBUx				0x94
> +#define ADC7_VBAT_2S_MID			0x96
> +
>  #endif /* _DT_BINDINGS_QCOM_SPMI_VADC_H */


^ permalink raw reply

* Re: [PATCH V6 1/7] iio: adc: Convert the QCOM SPMI ADC bindings to .yaml format
From: Jonathan Cameron @ 2020-05-31 10:36 UTC (permalink / raw)
  To: Jishnu Prakash
  Cc: agross, bjorn.andersson, devicetree, linux-kernel, mka,
	linus.walleij, Jonathan.Cameron, andy.shevchenko, amit.kucheria,
	smohanad, kgunda, aghayal, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, Rob Herring, Mark Rutland, Amit Kucheria,
	linux-iio, linux-arm-msm, linux-arm-msm-owner
In-Reply-To: <1590684869-15400-2-git-send-email-jprakash@codeaurora.org>

On Thu, 28 May 2020 22:24:23 +0530
Jishnu Prakash <jprakash@codeaurora.org> wrote:

> Convert the adc bindings from .txt to .yaml format.
> 
> Signed-off-by: Jishnu Prakash <jprakash@codeaurora.org>
> Reviewed-by: Amit Kucheria <amit.kucheria@linaro.org>
> Reviewed-by: Rob Herring <robh@kernel.org>
> Acked-by: Linus Walleij <linus.walleij@linaro.org>

Jishnu, Patch is fine, but I'd like to have seen a cover
letter and clear statement of changes from v5.

Thanks,

Jonathan

> ---
>  .../devicetree/bindings/iio/adc/qcom,spmi-vadc.txt | 173 --------------
>  .../bindings/iio/adc/qcom,spmi-vadc.yaml           | 252 +++++++++++++++++++++
>  2 files changed, 252 insertions(+), 173 deletions(-)
>  delete mode 100644 Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.txt
>  create mode 100644 Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml
> 
> diff --git a/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.txt b/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.txt
> deleted file mode 100644
> index c878768..0000000
> --- a/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.txt
> +++ /dev/null
> @@ -1,173 +0,0 @@
> -Qualcomm's SPMI PMIC ADC
> -
> -- SPMI PMIC voltage ADC (VADC) provides interface to clients to read
> -  voltage. The VADC is a 15-bit sigma-delta ADC.
> -- SPMI PMIC5 voltage ADC (ADC) provides interface to clients to read
> -  voltage. The VADC is a 16-bit sigma-delta ADC.
> -
> -VADC node:
> -
> -- compatible:
> -    Usage: required
> -    Value type: <string>
> -    Definition: Should contain "qcom,spmi-vadc".
> -                Should contain "qcom,spmi-adc5" for PMIC5 ADC driver.
> -                Should contain "qcom,spmi-adc-rev2" for PMIC rev2 ADC driver.
> -                Should contain "qcom,pms405-adc" for PMS405 PMIC
> -
> -- reg:
> -    Usage: required
> -    Value type: <prop-encoded-array>
> -    Definition: VADC base address in the SPMI PMIC register map.
> -
> -- #address-cells:
> -    Usage: required
> -    Value type: <u32>
> -    Definition: Must be one. Child node 'reg' property should define ADC
> -            channel number.
> -
> -- #size-cells:
> -    Usage: required
> -    Value type: <u32>
> -    Definition: Must be zero.
> -
> -- #io-channel-cells:
> -    Usage: required
> -    Value type: <u32>
> -    Definition: Must be one. For details about IIO bindings see:
> -            Documentation/devicetree/bindings/iio/iio-bindings.txt
> -
> -- interrupts:
> -    Usage: optional
> -    Value type: <prop-encoded-array>
> -    Definition: End of conversion interrupt.
> -
> -Channel node properties:
> -
> -- reg:
> -    Usage: required
> -    Value type: <u32>
> -    Definition: ADC channel number.
> -            See include/dt-bindings/iio/qcom,spmi-vadc.h
> -
> -- label:
> -    Usage: required for "qcom,spmi-adc5" and "qcom,spmi-adc-rev2"
> -    Value type: <empty>
> -    Definition: ADC input of the platform as seen in the schematics.
> -            For thermistor inputs connected to generic AMUX or GPIO inputs
> -            these can vary across platform for the same pins. Hence select
> -            the platform schematics name for this channel.
> -
> -- qcom,decimation:
> -    Usage: optional
> -    Value type: <u32>
> -    Definition: This parameter is used to decrease ADC sampling rate.
> -            Quicker measurements can be made by reducing decimation ratio.
> -            - For compatible property "qcom,spmi-vadc", valid values are
> -              512, 1024, 2048, 4096. If property is not found, default value
> -              of 512 will be used.
> -            - For compatible property "qcom,spmi-adc5", valid values are 250, 420
> -              and 840. If property is not found, default value of 840 is used.
> -            - For compatible property "qcom,spmi-adc-rev2", valid values are 256,
> -              512 and 1024. If property is not present, default value is 1024.
> -
> -- qcom,pre-scaling:
> -    Usage: optional
> -    Value type: <u32 array>
> -    Definition: Used for scaling the channel input signal before the signal is
> -            fed to VADC. The configuration for this node is to know the
> -            pre-determined ratio and use it for post scaling. Select one from
> -            the following options.
> -            <1 1>, <1 3>, <1 4>, <1 6>, <1 20>, <1 8>, <10 81>, <1 10>
> -            If property is not found default value depending on chip will be used.
> -
> -- qcom,ratiometric:
> -    Usage: optional
> -    Value type: <empty>
> -    Definition: Channel calibration type.
> -            - For compatible property "qcom,spmi-vadc", if this property is
> -              specified VADC will use the VDD reference (1.8V) and GND for
> -              channel calibration. If property is not found, channel will be
> -              calibrated with 0.625V and 1.25V reference channels, also
> -              known as absolute calibration.
> -            - For compatible property "qcom,spmi-adc5" and "qcom,spmi-adc-rev2",
> -              if this property is specified VADC will use the VDD reference
> -              (1.875V) and GND for channel calibration. If property is not found,
> -              channel will be calibrated with 0V and 1.25V reference channels,
> -              also known as absolute calibration.
> -
> -- qcom,hw-settle-time:
> -    Usage: optional
> -    Value type: <u32>
> -    Definition: Time between AMUX getting configured and the ADC starting
> -            conversion. The 'hw_settle_time' is an index used from valid values
> -            and programmed in hardware to achieve the hardware settling delay.
> -            - For compatible property "qcom,spmi-vadc" and "qcom,spmi-adc-rev2",
> -              Delay = 100us * (hw_settle_time) for hw_settle_time < 11,
> -              and 2ms * (hw_settle_time - 10) otherwise.
> -              Valid values are: 0, 100, 200, 300, 400, 500, 600, 700, 800,
> -              900 us and 1, 2, 4, 6, 8, 10 ms.
> -              If property is not found, channel will use 0us.
> -            - For compatible property "qcom,spmi-adc5", delay = 15us for
> -              value 0, 100us * (value) for values < 11,
> -              and 2ms * (value - 10) otherwise.
> -              Valid values are: 15, 100, 200, 300, 400, 500, 600, 700, 800,
> -              900 us and 1, 2, 4, 6, 8, 10 ms
> -              Certain controller digital versions have valid values of
> -              15, 100, 200, 300, 400, 500, 600, 700, 1, 2, 4, 8, 16, 32, 64, 128 ms
> -              If property is not found, channel will use 15us.
> -
> -- qcom,avg-samples:
> -    Usage: optional
> -    Value type: <u32>
> -    Definition: Number of samples to be used for measurement.
> -            Averaging provides the option to obtain a single measurement
> -            from the ADC that is an average of multiple samples. The value
> -            selected is 2^(value).
> -            - For compatible property "qcom,spmi-vadc", valid values
> -              are: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512
> -              If property is not found, 1 sample will be used.
> -            - For compatible property "qcom,spmi-adc5" and "qcom,spmi-adc-rev2",
> -              valid values are: 1, 2, 4, 8, 16
> -              If property is not found, 1 sample will be used.
> -
> -NOTE:
> -
> -For compatible property "qcom,spmi-vadc" following channels, also known as
> -reference point channels, are used for result calibration and their channel
> -configuration nodes should be defined:
> -VADC_REF_625MV and/or VADC_SPARE1(based on PMIC version) VADC_REF_1250MV,
> -VADC_GND_REF and VADC_VDD_VADC.
> -
> -Example:
> -
> -#include <dt-bindings/iio/qcom,spmi-vadc.h>
> -#include <linux/irq.h>
> -/* ... */
> -
> -	/* VADC node */
> -	pmic_vadc: vadc@3100 {
> -		compatible = "qcom,spmi-vadc";
> -		reg = <0x3100>;
> -		interrupts = <0x0 0x31 0x0 IRQ_TYPE_EDGE_RISING>;
> -		#address-cells = <1>;
> -		#size-cells = <0>;
> -		#io-channel-cells = <1>;
> -		io-channel-ranges;
> -
> -		/* Channel node */
> -		adc-chan@VADC_LR_MUX10_USB_ID {
> -			reg = <VADC_LR_MUX10_USB_ID>;
> -			qcom,decimation = <512>;
> -			qcom,ratiometric;
> -			qcom,hw-settle-time = <200>;
> -			qcom,avg-samples = <1>;
> -			qcom,pre-scaling = <1 3>;
> -		};
> -	};
> -
> -	/* IIO client node */
> -	usb {
> -		io-channels = <&pmic_vadc VADC_LR_MUX10_USB_ID>;
> -		io-channel-names = "vadc";
> -	};
> diff --git a/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml b/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml
> new file mode 100644
> index 0000000..de8d243
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml
> @@ -0,0 +1,252 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/adc/qcom,spmi-vadc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Qualcomm's SPMI PMIC ADC
> +
> +maintainers:
> +  - Andy Gross <agross@kernel.org>
> +  - Bjorn Andersson <bjorn.andersson@linaro.org>
> +
> +description: |
> +  SPMI PMIC voltage ADC (VADC) provides interface to clients to read
> +  voltage. The VADC is a 15-bit sigma-delta ADC.
> +  SPMI PMIC5 voltage ADC (ADC) provides interface to clients to read
> +  voltage. The VADC is a 16-bit sigma-delta ADC.
> +
> +properties:
> +  compatible:
> +    oneOf:
> +      - items:
> +          - const: qcom,pms405-adc
> +          - const: qcom,spmi-adc-rev2
> +
> +      - items:
> +        - enum:
> +          - qcom,spmi-vadc
> +          - qcom,spmi-adc5
> +          - qcom,spmi-adc-rev2
> +
> +  reg:
> +    description: VADC base address in the SPMI PMIC register map
> +    maxItems: 1
> +
> +  '#address-cells':
> +    const: 1
> +
> +  '#size-cells':
> +    const: 0
> +
> +  '#io-channel-cells':
> +    const: 1
> +
> +  interrupts:
> +    maxItems: 1
> +    description:
> +      End of conversion interrupt.
> +
> +required:
> +  - compatible
> +  - reg
> +  - '#address-cells'
> +  - '#size-cells'
> +  - '#io-channel-cells'
> +
> +patternProperties:
> +  "^.*@[0-9a-f]+$":
> +    type: object
> +    description: |
> +      Represents the external channels which are connected to the ADC.
> +      For compatible property "qcom,spmi-vadc" following channels, also known as
> +      reference point channels, are used for result calibration and their channel
> +      configuration nodes should be defined:
> +      VADC_REF_625MV and/or VADC_SPARE1(based on PMIC version) VADC_REF_1250MV,
> +      VADC_GND_REF and VADC_VDD_VADC.
> +
> +    properties:
> +      reg:
> +        description: |
> +          ADC channel number.
> +          See include/dt-bindings/iio/qcom,spmi-vadc.h
> +
> +      label:
> +        $ref: /schemas/types.yaml#/definitions/string
> +        description: |
> +            ADC input of the platform as seen in the schematics.
> +            For thermistor inputs connected to generic AMUX or GPIO inputs
> +            these can vary across platform for the same pins. Hence select
> +            the platform schematics name for this channel.
> +
> +      qcom,decimation:
> +        $ref: /schemas/types.yaml#/definitions/uint32
> +        description: |
> +            This parameter is used to decrease ADC sampling rate.
> +            Quicker measurements can be made by reducing decimation ratio.
> +
> +      qcom,pre-scaling:
> +        description: |
> +            Used for scaling the channel input signal before the signal is
> +            fed to VADC. The configuration for this node is to know the
> +            pre-determined ratio and use it for post scaling. It is a pair of
> +            integers, denoting the numerator and denominator of the fraction by which
> +            input signal is multiplied. For example, <1 3> indicates the signal is scaled
> +            down to 1/3 of its value before ADC measurement.
> +            If property is not found default value depending on chip will be used.
> +        allOf:
> +          - $ref: /schemas/types.yaml#/definitions/uint32-array
> +        oneOf:
> +          - items:
> +            - const: 1
> +            - enum: [ 1, 3, 4, 6, 20, 8, 10 ]
> +
> +          - items:
> +            - const: 10
> +            - const: 81
> +
> +      qcom,ratiometric:
> +        description: |
> +            Channel calibration type.
> +            - For compatible property "qcom,spmi-vadc", if this property is
> +              specified VADC will use the VDD reference (1.8V) and GND for
> +              channel calibration. If property is not found, channel will be
> +              calibrated with 0.625V and 1.25V reference channels, also
> +              known as absolute calibration.
> +            - For compatible property "qcom,spmi-adc5" and "qcom,spmi-adc-rev2",
> +              if this property is specified VADC will use the VDD reference (1.875V)
> +              and GND for channel calibration. If property is not found, channel
> +              will be calibrated with 0V and 1.25V reference channels, also known
> +              as absolute calibration.
> +        type: boolean
> +
> +      qcom,hw-settle-time:
> +        $ref: /schemas/types.yaml#/definitions/uint32
> +        description: |
> +            Time between AMUX getting configured and the ADC starting
> +            conversion. The 'hw_settle_time' is an index used from valid values
> +            and programmed in hardware to achieve the hardware settling delay.
> +
> +      qcom,avg-samples:
> +        $ref: /schemas/types.yaml#/definitions/uint32
> +        description: |
> +            Number of samples to be used for measurement.
> +            Averaging provides the option to obtain a single measurement
> +            from the ADC that is an average of multiple samples. The value
> +            selected is 2^(value).
> +
> +    required:
> +      - reg
> +
> +allOf:
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: qcom,spmi-vadc
> +
> +    then:
> +      patternProperties:
> +        "^.*@[0-9a-f]+$":
> +          properties:
> +            qcom,decimation:
> +              enum: [ 512, 1024, 2048, 4096 ]
> +              default: 512
> +
> +            qcom,hw-settle-time:
> +              enum: [ 0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1, 2,
> +                      4, 6, 8, 10 ]
> +              default: 0
> +
> +            qcom,avg-samples:
> +              enum: [ 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 ]
> +              default: 1
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: qcom,spmi-adc-rev2
> +
> +    then:
> +      patternProperties:
> +        "^.*@[0-9a-f]+$":
> +          properties:
> +            qcom,decimation:
> +              enum: [ 256, 512, 1024 ]
> +              default: 1024
> +
> +            qcom,hw-settle-time:
> +              enum: [ 0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1, 2,
> +                      4, 6, 8, 10 ]
> +              default: 0
> +
> +            qcom,avg-samples:
> +              enum: [ 1, 2, 4, 8, 16 ]
> +              default: 1
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: qcom,spmi-adc5
> +
> +    then:
> +      patternProperties:
> +        "^.*@[0-9a-f]+$":
> +          properties:
> +            qcom,decimation:
> +              enum: [ 250, 420, 840 ]
> +              default: 840
> +
> +            qcom,hw-settle-time:
> +              enum: [ 15, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1, 2,
> +                      4, 6, 8, 10, 16, 32, 64, 128 ]
> +              default: 15
> +
> +            qcom,avg-samples:
> +              enum: [ 1, 2, 4, 8, 16 ]
> +              default: 1
> +
> +examples:
> +  - |
> +    spmi_bus {
> +      #address-cells = <1>;
> +      #size-cells = <0>;
> +      /* VADC node */
> +      pmic_vadc: adc@3100 {
> +        compatible = "qcom,spmi-vadc";
> +        reg = <0x3100>;
> +        interrupts = <0x0 0x31 0x0 0x1>;
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +        #io-channel-cells = <1>;
> +        io-channel-ranges;
> +
> +        /* Channel node */
> +        adc-chan@39 {
> +          reg = <0x39>;
> +          qcom,decimation = <512>;
> +          qcom,ratiometric;
> +          qcom,hw-settle-time = <200>;
> +          qcom,avg-samples = <1>;
> +          qcom,pre-scaling = <1 3>;
> +        };
> +
> +        adc-chan@9 {
> +          reg = <0x9>;
> +        };
> +
> +        adc-chan@a {
> +          reg = <0xa>;
> +        };
> +
> +        adc-chan@e {
> +          reg = <0xe>;
> +        };
> +
> +        adc-chan@f {
> +          reg = <0xf>;
> +        };
> +      };
> +    };


^ permalink raw reply

* Re: [PATCH v2 4/4] dt-bindings: iio: scd30: add device binding file
From: Jonathan Cameron @ 2020-05-31 10:19 UTC (permalink / raw)
  To: Tomasz Duszynski
  Cc: linux-iio, linux-kernel, devicetree, robh+dt, andy.shevchenko,
	pmeerw
In-Reply-To: <20200530213630.87159-5-tomasz.duszynski@octakon.com>

On Sat, 30 May 2020 23:36:30 +0200
Tomasz Duszynski <tomasz.duszynski@octakon.com> wrote:

> Add SCD30 sensor binding file.
> 
> Signed-off-by: Tomasz Duszynski <tomasz.duszynski@octakon.com>
> ---
>  .../iio/chemical/sensirion,scd30.yaml         | 68 +++++++++++++++++++
>  MAINTAINERS                                   |  1 +
>  2 files changed, 69 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/chemical/sensirion,scd30.yaml
> 
> diff --git a/Documentation/devicetree/bindings/iio/chemical/sensirion,scd30.yaml b/Documentation/devicetree/bindings/iio/chemical/sensirion,scd30.yaml
> new file mode 100644
> index 000000000000..34cc3925d64d
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/chemical/sensirion,scd30.yaml
> @@ -0,0 +1,68 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/chemical/sensirion,scd30.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Sensirion SCD30 carbon dioxide sensor
> +
> +maintainers:
> +  - Tomasz Duszynski <tomasz.duszynski@octakon.com>
> +
> +description: |
> +  Air quality sensor capable of measuring co2 concentration, temperature
> +  and relative humidity.
> +
> +properties:
> +  compatible:
> +    enum:
> +      - sensirion,scd30
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  vdd-supply: true
> +
> +  sensirion,sel-gpios:
> +    description: GPIO connected to the SEL line
> +    maxItems: 1
> +
> +  sensirion,pwm-gpios:
> +    description: GPIO connected to the PWM line
> +    maxItems: 1
> +
> +required:
> +  - compatible
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    # include <dt-bindings/interrupt-controller/irq.h>
> +    i2c {
> +      #address-cells = <1>;
> +      #size-cells = <0>;
> +
> +      scd30@61 {

Nodes should have generic names.  Not sure we have an appropriate
one in the spec, but as main focus of people using this will be
c02 herpas

	c02@61?  

Rob may well have a better suggestion!

> +        compatible = "sensirion,scd30";
> +        reg = <0x61>;
> +        vdd-supply = <&vdd>;
> +        interrupt-parent = <&gpio0>;
> +        interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
> +      };
> +    };
> +  - |
> +    # include <dt-bindings/interrupt-controller/irq.h>
> +    serial {
> +      scd30 {
> +        compatible = "sensirion,scd30";
> +        vdd-supply = <&vdd>;
> +        interrupt-parent = <&gpio0>;
> +        interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
> +      };
> +    };
> +
> +...
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5db4b446c8ba..0ab9cf39e051 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15140,6 +15140,7 @@ F:	include/uapi/linux/phantom.h
>  SENSIRION SCD30 CARBON DIOXIDE SENSOR DRIVER
>  M:	Tomasz Duszynski <tomasz.duszynski@octakon.com>
>  S:	Maintained
> +F:	Documentation/devicetree/bindings/iio/chemical/sensirion,scd30.yaml
>  F:	drivers/iio/chemical/scd30.h
>  F:	drivers/iio/chemical/scd30_core.c
>  F:	drivers/iio/chemical/scd30_i2c.c


^ permalink raw reply

* Re: [PATCH v2 1/4] iio: chemical: scd30: add core driver
From: Jonathan Cameron @ 2020-05-31 10:16 UTC (permalink / raw)
  To: Tomasz Duszynski
  Cc: linux-iio, linux-kernel, devicetree, robh+dt, andy.shevchenko,
	pmeerw
In-Reply-To: <20200531105840.27e17f3d@archlinux>

On Sun, 31 May 2020 10:58:40 +0100
Jonathan Cameron <jic23@kernel.org> wrote:

> On Sat, 30 May 2020 23:36:27 +0200
> Tomasz Duszynski <tomasz.duszynski@octakon.com> wrote:
> 
> > Add Sensirion SCD30 carbon dioxide core driver.
> > 
> > Signed-off-by: Tomasz Duszynski <tomasz.duszynski@octakon.com>  
> 
> Hi Tomasz
> 
> A few things inline.  Includes the alignment issue on 
> x86_32 that I fell into whilst trying to fix timestamp
> alignment issues.  Suggested resolution inline for that.
> 
> Thanks,
> 
> Jonathan
> 

Update below after looking at the way this works with the serial dev.

> > +int scd30_probe(struct device *dev, int irq, const char *name, void *priv,
> > +		scd30_command_t command)
> > +{
> > +	static const unsigned long scd30_scan_masks[] = { 0x07, 0x00 };
> > +	struct scd30_state *state;
> > +	struct iio_dev *indio_dev;
> > +	int ret;
> > +	u16 val;
> > +
> > +	indio_dev = devm_iio_device_alloc(dev, sizeof(*state));
> > +	if (!indio_dev)
> > +		return -ENOMEM;
> > +
> > +	state = iio_priv(indio_dev);
> > +	state->dev = dev;  
> 
> Doesn't seem to be used.
> 
> > +	state->priv = priv;  
> 
> What's this for?  At least at first glance I can't find it being used
> anywhere.

Ah. Used in the serial module.  Maybe add a comment to the structure definition
about that.

As is the dev etc.  Is it possible to use the 

> 
> > +	state->irq = irq;
> > +	state->pressure_comp = SCD30_PRESSURE_COMP_DEFAULT;
> > +	state->meas_interval = SCD30_MEAS_INTERVAL_DEFAULT;
> > +	state->command = command;
> > +	mutex_init(&state->lock);
> > +	init_completion(&state->meas_ready);
> > +
> > +	dev_set_drvdata(dev, indio_dev);
> > +
> > +	indio_dev->dev.parent = dev;  
> 
> Side note that there is a series moving this into the core under revision at
> the moment.  Hopefully I'll remember to fix this up when applying your patch
> if that one has gone in ahead of it.
> 
> > +	indio_dev->info = &scd30_info;
> > +	indio_dev->name = name;
> > +	indio_dev->channels = scd30_channels;
> > +	indio_dev->num_channels = ARRAY_SIZE(scd30_channels);
> > +	indio_dev->modes = INDIO_DIRECT_MODE;
> > +	indio_dev->available_scan_masks = scd30_scan_masks;
> > +
> > +	state->vdd = devm_regulator_get(dev, "vdd");
> > +	if (IS_ERR(state->vdd)) {
> > +		if (PTR_ERR(state->vdd) == -EPROBE_DEFER)
> > +			return -EPROBE_DEFER;
> > +
> > +		dev_err(dev, "failed to get regulator\n");
> > +		return PTR_ERR(state->vdd);
> > +	}
> > +
> > +	ret = regulator_enable(state->vdd);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = devm_add_action_or_reset(dev, scd30_disable_regulator, state);
> > +	if (ret)
> > +		return ret;
> > +  
...

^ permalink raw reply

* Re: [PATCH v2 3/4] iio: chemical: scd30: add serial interface driver
From: Jonathan Cameron @ 2020-05-31 10:15 UTC (permalink / raw)
  To: Tomasz Duszynski
  Cc: linux-iio, linux-kernel, devicetree, robh+dt, andy.shevchenko,
	pmeerw
In-Reply-To: <20200530213630.87159-4-tomasz.duszynski@octakon.com>

On Sat, 30 May 2020 23:36:29 +0200
Tomasz Duszynski <tomasz.duszynski@octakon.com> wrote:

> Add serial interface driver for the SCD30 sensor.
> 
> Signed-off-by: Tomasz Duszynski <tomasz.duszynski@octakon.com>

Ah Now I see why you had those extra elements in the iio_priv
structure.

Hmm. serdev_device callbacks using the top level device drvdata
is a bit annoying.  Really feels to me like they should have
their own priv data for those callbacks given the device
drvdata gets used for so many other things.

Oh well. Guess this is the best we can do!

Jonathan

> ---
>  MAINTAINERS                         |   1 +
>  drivers/iio/chemical/Kconfig        |  11 ++
>  drivers/iio/chemical/Makefile       |   1 +
>  drivers/iio/chemical/scd30_serial.c | 266 ++++++++++++++++++++++++++++
>  4 files changed, 279 insertions(+)
>  create mode 100644 drivers/iio/chemical/scd30_serial.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 13aed3473b7e..5db4b446c8ba 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15143,6 +15143,7 @@ S:	Maintained
>  F:	drivers/iio/chemical/scd30.h
>  F:	drivers/iio/chemical/scd30_core.c
>  F:	drivers/iio/chemical/scd30_i2c.c
> +F:	drivers/iio/chemical/scd30_serial.c
>  
>  SENSIRION SPS30 AIR POLLUTION SENSOR DRIVER
>  M:	Tomasz Duszynski <tduszyns@gmail.com>
> diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig
> index 970d34888c2e..10bb431bc3ce 100644
> --- a/drivers/iio/chemical/Kconfig
> +++ b/drivers/iio/chemical/Kconfig
> @@ -107,6 +107,17 @@ config SCD30_I2C
>  	  To compile this driver as a module, choose M here: the module will
>  	  be called scd30_i2c.
>  
> +config SCD30_SERIAL
> +	tristate "SCD30 carbon dioxide sensor serial driver"
> +	depends on SCD30_CORE && SERIAL_DEV_BUS
> +	select CRC16
> +	help
> +	  Say Y here to build support for the Sensirion SCD30 serial interface
> +	  driver.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called scd30_serial.
> +
>  config SENSIRION_SGP30
>  	tristate "Sensirion SGPxx gas sensors"
>  	depends on I2C
> diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile
> index 0966ca34e34b..fef63dd5bf92 100644
> --- a/drivers/iio/chemical/Makefile
> +++ b/drivers/iio/chemical/Makefile
> @@ -14,6 +14,7 @@ obj-$(CONFIG_IAQCORE)		+= ams-iaq-core.o
>  obj-$(CONFIG_PMS7003) += pms7003.o
>  obj-$(CONFIG_SCD30_CORE) += scd30_core.o
>  obj-$(CONFIG_SCD30_I2C) += scd30_i2c.o
> +obj-$(CONFIG_SCD30_SERIAL) += scd30_serial.o
>  obj-$(CONFIG_SENSIRION_SGP30)	+= sgp30.o
>  obj-$(CONFIG_SPS30) += sps30.o
>  obj-$(CONFIG_VZ89X)		+= vz89x.o
> diff --git a/drivers/iio/chemical/scd30_serial.c b/drivers/iio/chemical/scd30_serial.c
> new file mode 100644
> index 000000000000..07d7d3110fe0
> --- /dev/null
> +++ b/drivers/iio/chemical/scd30_serial.c
> @@ -0,0 +1,266 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Sensirion SCD30 carbon dioxide sensor serial driver
> + *
> + * Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com>
> + */
> +#include <linux/crc16.h>
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <linux/iio/iio.h>
> +#include <linux/jiffies.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/property.h>
> +#include <linux/serdev.h>
> +#include <linux/string.h>
> +#include <linux/types.h>
> +#include <asm/unaligned.h>
> +
> +#include "scd30.h"
> +
> +#define SCD30_SERDEV_ADDR 0x61
> +#define SCD30_SERDEV_WRITE 0x06
> +#define SCD30_SERDEV_READ 0x03
> +#define SCD30_SERDEV_MAX_BUF_SIZE 17
> +#define SCD30_SERDEV_RX_HEADER_SIZE 3
> +#define SCD30_SERDEV_CRC_SIZE 2
> +#define SCD30_SERDEV_TIMEOUT msecs_to_jiffies(200)
> +
> +struct scd30_serdev_priv {
> +	struct completion meas_ready;
> +	char *buf;
> +	int num_expected;
> +	int num;
> +};
> +
> +static u16 scd30_serdev_cmd_lookup_tbl[] = {
> +	[CMD_START_MEAS] = 0x0036,
> +	[CMD_STOP_MEAS] = 0x0037,
> +	[CMD_MEAS_INTERVAL] = 0x0025,
> +	[CMD_MEAS_READY] = 0x0027,
> +	[CMD_READ_MEAS] = 0x0028,
> +	[CMD_ASC] = 0x003a,
> +	[CMD_FRC] = 0x0039,
> +	[CMD_TEMP_OFFSET] = 0x003b,
> +	[CMD_FW_VERSION] = 0x0020,
> +	[CMD_RESET] = 0x0034,
> +};
> +
> +static u16 scd30_serdev_calc_crc(const char *buf, int size)
> +{
> +	return crc16(0xffff, buf, size);
> +}
> +
> +static int scd30_serdev_xfer(struct scd30_state *state, char *txbuf, int txsize,
> +			     char *rxbuf, int rxsize)
> +{
> +	struct serdev_device *serdev = to_serdev_device(state->dev);
> +	struct scd30_serdev_priv *priv = state->priv;
> +	int ret;
> +
> +	priv->buf = rxbuf;
> +	priv->num_expected = rxsize;
> +	priv->num = 0;
> +
> +	ret = serdev_device_write(serdev, txbuf, txsize, SCD30_SERDEV_TIMEOUT);
> +	if (ret < txsize)
> +		return ret < 0 ? ret : -EIO;
> +
> +	ret = wait_for_completion_interruptible_timeout(&priv->meas_ready,
> +							SCD30_SERDEV_TIMEOUT);
> +	if (ret > 0)
> +		ret = 0;
> +	else if (!ret)
> +		ret = -ETIMEDOUT;
> +
> +	return ret;
> +}
> +
> +static int scd30_serdev_command(struct scd30_state *state, enum scd30_cmd cmd,
> +				u16 arg, void *response, int size)
> +{
> +	/*
> +	 * Communication over serial line is based on modbus protocol (or rather
> +	 * its variation called modbus over serial to be precise). Upon
> +	 * receiving a request device should reply with response.
> +	 *
> +	 * Frame below represents a request message. Each field takes
> +	 * exactly one byte.
> +	 *
> +	 * +------+------+-----+-----+-------+-------+-----+-----+
> +	 * | dev  | op   | reg | reg | byte1 | byte0 | crc | crc |
> +	 * | addr | code | msb | lsb |       |       | lsb | msb |
> +	 * +------+------+-----+-----+-------+-------+-----+-----+
> +	 *
> +	 * The message device replies with depends on the 'op code' field from
> +	 * the request. In case it was set to SCD30_SERDEV_WRITE sensor should
> +	 * reply with unchanged request. Otherwise 'op code' was set to
> +	 * SCD30_SERDEV_READ and response looks like the one below. As with
> +	 * request, each field takes one byte.
> +	 *
> +	 * +------+------+--------+-------+-----+-------+-----+-----+
> +	 * | dev  | op   | num of | byte0 | ... | byteN | crc | crc |
> +	 * | addr | code | bytes  |       |     |       | lsb | msb |
> +	 * +------+------+--------+-------+-----+-------+-----+-----+
> +	 */
> +	char txbuf[SCD30_SERDEV_MAX_BUF_SIZE] = { SCD30_SERDEV_ADDR },
> +	     rxbuf[SCD30_SERDEV_MAX_BUF_SIZE], *rsp = response;
> +	int ret, rxsize, txsize = 2;
> +	u16 crc;
> +
> +	put_unaligned_be16(scd30_serdev_cmd_lookup_tbl[cmd], txbuf + txsize);
> +	txsize += 2;
> +
> +	if (rsp) {
> +		txbuf[1] = SCD30_SERDEV_READ;
> +		if (cmd == CMD_READ_MEAS)
> +			/* number of u16 words to read */
> +			put_unaligned_be16(size / 2, txbuf + txsize);
> +		else
> +			put_unaligned_be16(0x0001, txbuf + txsize);
> +		txsize += 2;
> +		crc = scd30_serdev_calc_crc(txbuf, txsize);
> +		put_unaligned_le16(crc, txbuf + txsize);
> +		txsize += 2;
> +		rxsize = SCD30_SERDEV_RX_HEADER_SIZE + size +
> +			 SCD30_SERDEV_CRC_SIZE;
> +	} else {
> +		if ((cmd == CMD_STOP_MEAS) || (cmd == CMD_RESET))
> +			arg = 0x0001;
> +
> +		txbuf[1] = SCD30_SERDEV_WRITE;
> +		put_unaligned_be16(arg, txbuf + txsize);
> +		txsize += 2;
> +		crc = scd30_serdev_calc_crc(txbuf, txsize);
> +		put_unaligned_le16(crc, txbuf + txsize);
> +		txsize += 2;
> +		rxsize = txsize;
> +	}
> +
> +	ret = scd30_serdev_xfer(state, txbuf, txsize, rxbuf, rxsize);
> +	if (ret)
> +		return ret;
> +
> +	switch (txbuf[1]) {
> +	case SCD30_SERDEV_WRITE:
> +		if (memcmp(txbuf, txbuf, txsize)) {
> +			dev_err(state->dev, "wrong message received\n");
> +			return -EIO;
> +		}
> +		break;
> +	case SCD30_SERDEV_READ:
> +		if (rxbuf[2] != (rxsize -
> +				 SCD30_SERDEV_RX_HEADER_SIZE -
> +				 SCD30_SERDEV_CRC_SIZE)) {
> +			dev_err(state->dev,
> +				"received data size does not match header\n");
> +			return -EIO;
> +		}
> +
> +		rxsize -= SCD30_SERDEV_CRC_SIZE;
> +		crc = get_unaligned_le16(rxbuf + rxsize);
> +		if (crc != scd30_serdev_calc_crc(rxbuf, rxsize)) {
> +			dev_err(state->dev, "data integrity check failed\n");
> +			return -EIO;
> +		}
> +
> +		rxsize -= SCD30_SERDEV_RX_HEADER_SIZE;
> +		memcpy(rsp, rxbuf + SCD30_SERDEV_RX_HEADER_SIZE, rxsize);
> +		break;
> +	default:
> +		dev_err(state->dev, "received unknown op code\n");
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +static int scd30_serdev_receive_buf(struct serdev_device *serdev,
> +				    const unsigned char *buf, size_t size)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(&serdev->dev);
> +	struct scd30_serdev_priv *priv;
> +	struct scd30_state *state;
> +	int num;
> +
> +	if (!indio_dev)
> +		return 0;
> +
> +	state = iio_priv(indio_dev);
> +	priv = state->priv;
> +
> +	/* just in case sensor puts some unexpected bytes on the bus */
> +	if (!priv->buf)
> +		return 0;
> +
> +	if (priv->num + size >= priv->num_expected)
> +		num = priv->num_expected - priv->num;
> +	else
> +		num = size;
> +
> +	memcpy(priv->buf + priv->num, buf, num);
> +	priv->num += num;
> +
> +	if (priv->num == priv->num_expected) {
> +		priv->buf = NULL;
> +		complete(&priv->meas_ready);
> +	}
> +
> +	return num;
> +}
> +
> +static const struct serdev_device_ops scd30_serdev_ops = {
> +	.receive_buf = scd30_serdev_receive_buf,
> +	.write_wakeup = serdev_device_write_wakeup,
> +};
> +
> +static int scd30_serdev_probe(struct serdev_device *serdev)
> +{
> +	struct device *dev = &serdev->dev;
> +	struct scd30_serdev_priv *priv;
> +	int irq, ret;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	init_completion(&priv->meas_ready);
> +	serdev_device_set_client_ops(serdev, &scd30_serdev_ops);
> +
> +	ret = devm_serdev_device_open(dev, serdev);
> +	if (ret)
> +		return ret;
> +
> +	serdev_device_set_baudrate(serdev, 19200);
> +	serdev_device_set_flow_control(serdev, false);
> +
> +	ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
> +	if (ret)
> +		return ret;
> +
> +	irq = fwnode_irq_get(dev_fwnode(dev), 0);
> +
> +	return scd30_probe(dev, irq, KBUILD_MODNAME, priv,
> +			   scd30_serdev_command);
> +}
> +
> +static const struct of_device_id scd30_serdev_of_match[] = {
> +	{ .compatible = "sensirion,scd30" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, scd30_serdev_of_match);
> +
> +static struct serdev_device_driver scd30_serdev_driver = {
> +	.driver = {
> +		.name = KBUILD_MODNAME,
> +		.of_match_table = scd30_serdev_of_match,
> +		.pm = &scd30_pm_ops,
> +	},
> +	.probe = scd30_serdev_probe,
> +};
> +module_serdev_device_driver(scd30_serdev_driver);
> +
> +MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>");
> +MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor serial driver");
> +MODULE_LICENSE("GPL v2");


^ permalink raw reply

* Re: [PATCH v2 2/4] iio: chemical: scd30: add I2C interface driver
From: Jonathan Cameron @ 2020-05-31 10:02 UTC (permalink / raw)
  To: Tomasz Duszynski
  Cc: linux-iio, linux-kernel, devicetree, robh+dt, andy.shevchenko,
	pmeerw
In-Reply-To: <20200530213630.87159-3-tomasz.duszynski@octakon.com>

On Sat, 30 May 2020 23:36:28 +0200
Tomasz Duszynski <tomasz.duszynski@octakon.com> wrote:

> Add I2C interface driver for the SCD30 sensor.
> 
> Signed-off-by: Tomasz Duszynski <tomasz.duszynski@octakon.com>
Looks good to me.

J
> ---
>  MAINTAINERS                      |   1 +
>  drivers/iio/chemical/Kconfig     |  11 +++
>  drivers/iio/chemical/Makefile    |   1 +
>  drivers/iio/chemical/scd30_i2c.c | 134 +++++++++++++++++++++++++++++++
>  4 files changed, 147 insertions(+)
>  create mode 100644 drivers/iio/chemical/scd30_i2c.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 41a509cca6f1..13aed3473b7e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15142,6 +15142,7 @@ M:	Tomasz Duszynski <tomasz.duszynski@octakon.com>
>  S:	Maintained
>  F:	drivers/iio/chemical/scd30.h
>  F:	drivers/iio/chemical/scd30_core.c
> +F:	drivers/iio/chemical/scd30_i2c.c
>  
>  SENSIRION SPS30 AIR POLLUTION SENSOR DRIVER
>  M:	Tomasz Duszynski <tduszyns@gmail.com>
> diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig
> index 99e852b67e55..970d34888c2e 100644
> --- a/drivers/iio/chemical/Kconfig
> +++ b/drivers/iio/chemical/Kconfig
> @@ -96,6 +96,17 @@ config SCD30_CORE
>  	  To compile this driver as a module, choose M here: the module will
>  	  be called scd30_core.
>  
> +config SCD30_I2C
> +	tristate "SCD30 carbon dioxide sensor I2C driver"
> +	depends on SCD30_CORE && I2C
> +	select CRC8
> +	help
> +	  Say Y here to build support for the Sensirion SCD30 I2C interface
> +	  driver.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called scd30_i2c.
> +
>  config SENSIRION_SGP30
>  	tristate "Sensirion SGPxx gas sensors"
>  	depends on I2C
> diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile
> index c9804b041ecd..0966ca34e34b 100644
> --- a/drivers/iio/chemical/Makefile
> +++ b/drivers/iio/chemical/Makefile
> @@ -13,6 +13,7 @@ obj-$(CONFIG_CCS811)		+= ccs811.o
>  obj-$(CONFIG_IAQCORE)		+= ams-iaq-core.o
>  obj-$(CONFIG_PMS7003) += pms7003.o
>  obj-$(CONFIG_SCD30_CORE) += scd30_core.o
> +obj-$(CONFIG_SCD30_I2C) += scd30_i2c.o
>  obj-$(CONFIG_SENSIRION_SGP30)	+= sgp30.o
>  obj-$(CONFIG_SPS30) += sps30.o
>  obj-$(CONFIG_VZ89X)		+= vz89x.o
> diff --git a/drivers/iio/chemical/scd30_i2c.c b/drivers/iio/chemical/scd30_i2c.c
> new file mode 100644
> index 000000000000..a6b532b83669
> --- /dev/null
> +++ b/drivers/iio/chemical/scd30_i2c.c
> @@ -0,0 +1,134 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Sensirion SCD30 carbon dioxide sensor i2c driver
> + *
> + * Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com>
> + *
> + * I2C slave address: 0x61
> + */
> +#include <linux/crc8.h>
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <linux/i2c.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/types.h>
> +#include <asm/unaligned.h>
> +
> +#include "scd30.h"
> +
> +#define SCD30_I2C_MAX_BUF_SIZE 18
> +#define SCD30_I2C_CRC8_POLYNOMIAL 0x31
> +
> +static u16 scd30_i2c_cmd_lookup_tbl[] = {
> +	[CMD_START_MEAS] = 0x0010,
> +	[CMD_STOP_MEAS] = 0x0104,
> +	[CMD_MEAS_INTERVAL] = 0x4600,
> +	[CMD_MEAS_READY] = 0x0202,
> +	[CMD_READ_MEAS] = 0x0300,
> +	[CMD_ASC] = 0x5306,
> +	[CMD_FRC] = 0x5204,
> +	[CMD_TEMP_OFFSET] = 0x5403,
> +	[CMD_FW_VERSION] = 0xd100,
> +	[CMD_RESET] = 0xd304,
> +};
> +
> +DECLARE_CRC8_TABLE(scd30_i2c_crc8_tbl);
> +
> +static int scd30_i2c_xfer(struct scd30_state *state, char *txbuf, int txsize,
> +			  char *rxbuf, int rxsize)
> +{
> +	struct i2c_client *client = to_i2c_client(state->dev);
> +	int ret;
> +
> +	/*
> +	 * repeated start is not supported hence instead of sending two i2c
> +	 * messages in a row we send one by one
> +	 */
> +	ret = i2c_master_send(client, txbuf, txsize);
> +	if (ret != txsize)
> +		return ret < 0 ? ret : -EIO;
> +
> +	if (!rxbuf)
> +		return 0;
> +
> +	ret = i2c_master_recv(client, rxbuf, rxsize);
> +	if (ret != rxsize)
> +		return ret < 0 ? ret : -EIO;
> +
> +	return 0;
> +}
> +
> +static int scd30_i2c_command(struct scd30_state *state, enum scd30_cmd cmd,
> +			     u16 arg, void *response, int size)
> +{
> +	char crc, buf[SCD30_I2C_MAX_BUF_SIZE], *rsp = response;
> +	int i, ret;
> +
> +	put_unaligned_be16(scd30_i2c_cmd_lookup_tbl[cmd], buf);
> +	i = 2;
> +
> +	if (rsp) {
> +		/* each two bytes are followed by a crc8 */
> +		size += size / 2;
> +	} else {
> +		put_unaligned_be16(arg, buf + i);
> +		crc = crc8(scd30_i2c_crc8_tbl, buf + i, 2, CRC8_INIT_VALUE);
> +		i += 2;
> +		buf[i] = crc;
> +		i += 1;
> +
> +		/* commands below don't take an argument */
> +		if ((cmd == CMD_STOP_MEAS) || (cmd == CMD_RESET))
> +			i -= 3;
> +	}
> +
> +	ret = scd30_i2c_xfer(state, buf, i, buf, size);
> +	if (ret)
> +		return ret;
> +
> +	/* validate received data and strip off crc bytes */
> +	for (i = 0; i < size; i += 3) {
> +		crc = crc8(scd30_i2c_crc8_tbl, buf + i, 2, CRC8_INIT_VALUE);
> +		if (crc != buf[i + 2]) {
> +			dev_err(state->dev, "data integrity check failed\n");
> +			return -EIO;
> +		}
> +
> +		*rsp++ = buf[i];
> +		*rsp++ = buf[i + 1];
> +	}
> +
> +	return 0;
> +}
> +
> +static int scd30_i2c_probe(struct i2c_client *client)
> +{
> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
> +		return -EOPNOTSUPP;
> +
> +	crc8_populate_msb(scd30_i2c_crc8_tbl, SCD30_I2C_CRC8_POLYNOMIAL);
> +
> +	return scd30_probe(&client->dev, client->irq, client->name, NULL,
> +			   scd30_i2c_command);
> +}
> +
> +static const struct of_device_id scd30_i2c_of_match[] = {
> +	{ .compatible = "sensirion,scd30" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, scd30_i2c_of_match);
> +
> +static struct i2c_driver scd30_i2c_driver = {
> +	.driver = {
> +		.name = KBUILD_MODNAME,
> +		.of_match_table = scd30_i2c_of_match,
> +		.pm = &scd30_pm_ops,
> +	},
> +	.probe_new = scd30_i2c_probe,
> +};
> +module_i2c_driver(scd30_i2c_driver);
> +
> +MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>");
> +MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor i2c driver");
> +MODULE_LICENSE("GPL v2");


^ permalink raw reply

* Re: [PATCH v2 1/4] iio: chemical: scd30: add core driver
From: Jonathan Cameron @ 2020-05-31  9:58 UTC (permalink / raw)
  To: Tomasz Duszynski
  Cc: linux-iio, linux-kernel, devicetree, robh+dt, andy.shevchenko,
	pmeerw
In-Reply-To: <20200530213630.87159-2-tomasz.duszynski@octakon.com>

On Sat, 30 May 2020 23:36:27 +0200
Tomasz Duszynski <tomasz.duszynski@octakon.com> wrote:

> Add Sensirion SCD30 carbon dioxide core driver.
> 
> Signed-off-by: Tomasz Duszynski <tomasz.duszynski@octakon.com>

Hi Tomasz

A few things inline.  Includes the alignment issue on 
x86_32 that I fell into whilst trying to fix timestamp
alignment issues.  Suggested resolution inline for that.

Thanks,

Jonathan

> ---
>  Documentation/ABI/testing/sysfs-bus-iio-scd30 |  20 +
>  MAINTAINERS                                   |   6 +
>  drivers/iio/chemical/Kconfig                  |  11 +
>  drivers/iio/chemical/Makefile                 |   1 +
>  drivers/iio/chemical/scd30.h                  |  75 ++
>  drivers/iio/chemical/scd30_core.c             | 764 ++++++++++++++++++
>  6 files changed, 877 insertions(+)
>  create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-scd30
>  create mode 100644 drivers/iio/chemical/scd30.h
>  create mode 100644 drivers/iio/chemical/scd30_core.c
> 
> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-scd30 b/Documentation/ABI/testing/sysfs-bus-iio-scd30
> new file mode 100644
> index 000000000000..a05b1d28e94a
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-bus-iio-scd30
> @@ -0,0 +1,20 @@
> +What:		/sys/bus/iio/devices/iio:deviceX/calibration
> +Date:		June 2020
> +KernelVersion:	5.8
> +Contact:	linux-iio@vger.kernel.org
> +Description:
> +		Contaminants build-up in the measurement chamber or optical
> +		elements deterioration leads to sensor drift.
> +
> +		One can compensate for sensor drift by using either automatic
> +		self calibration (asc) or forced recalibration (frc). If used
> +		at once one will overwrite the other.
> +
> +		Writing 1 or 0 to this attribute will respectively activate or
> +		deactivate asc.
> +
> +		Picking value from the range [400 1 2000] and writing it to the
> +		sensor will set frc.
Seems to me like this would be more intuitive as two separate parameters
perhaps:
calibration_auto_enable
calibration_forced_value
?

> +
> +		Upon reading current asc status and frc value are returned
> +		respectively.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 60ed2963efaa..41a509cca6f1 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15137,6 +15137,12 @@ S:	Maintained
>  F:	drivers/misc/phantom.c
>  F:	include/uapi/linux/phantom.h
> 
> +SENSIRION SCD30 CARBON DIOXIDE SENSOR DRIVER
> +M:	Tomasz Duszynski <tomasz.duszynski@octakon.com>
> +S:	Maintained
> +F:	drivers/iio/chemical/scd30.h
> +F:	drivers/iio/chemical/scd30_core.c
> +
>  SENSIRION SPS30 AIR POLLUTION SENSOR DRIVER
>  M:	Tomasz Duszynski <tduszyns@gmail.com>
>  S:	Maintained
> diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig
> index 7f21afd73b1c..99e852b67e55 100644
> --- a/drivers/iio/chemical/Kconfig
> +++ b/drivers/iio/chemical/Kconfig
> @@ -85,6 +85,17 @@ config PMS7003
>  	  To compile this driver as a module, choose M here: the module will
>  	  be called pms7003.
> 
> +config SCD30_CORE
> +	tristate "SCD30 carbon dioxide sensor driver"
> +	select IIO_BUFFER
> +	select IIO_TRIGGERED_BUFFER
> +	help
> +	  Say Y here to build support for the Sensirion SCD30 sensor with carbon
> +	  dioxide, relative humidity and temperature sensing capabilities.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called scd30_core.
> +
>  config SENSIRION_SGP30
>  	tristate "Sensirion SGPxx gas sensors"
>  	depends on I2C
> diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile
> index aba4167db745..c9804b041ecd 100644
> --- a/drivers/iio/chemical/Makefile
> +++ b/drivers/iio/chemical/Makefile
> @@ -12,6 +12,7 @@ obj-$(CONFIG_BME680_SPI) += bme680_spi.o
>  obj-$(CONFIG_CCS811)		+= ccs811.o
>  obj-$(CONFIG_IAQCORE)		+= ams-iaq-core.o
>  obj-$(CONFIG_PMS7003) += pms7003.o
> +obj-$(CONFIG_SCD30_CORE) += scd30_core.o
>  obj-$(CONFIG_SENSIRION_SGP30)	+= sgp30.o
>  obj-$(CONFIG_SPS30) += sps30.o
>  obj-$(CONFIG_VZ89X)		+= vz89x.o
> diff --git a/drivers/iio/chemical/scd30.h b/drivers/iio/chemical/scd30.h
> new file mode 100644
> index 000000000000..9b25f7423142
> --- /dev/null
> +++ b/drivers/iio/chemical/scd30.h
> @@ -0,0 +1,75 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef _SCD30_H
> +#define _SCD30_H
> +
> +#include <linux/completion.h>
> +#include <linux/device.h>
> +#include <linux/mutex.h>
> +#include <linux/pm.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/types.h>
> +
> +struct scd30_state;
> +
> +enum scd30_cmd {
> +	/* start continuous measurement with pressure compensation */
> +	CMD_START_MEAS,
> +	/* stop continuous measurement */
> +	CMD_STOP_MEAS,
> +	/* set/get measurement interval */
> +	CMD_MEAS_INTERVAL,
> +	/* check whether new measurement is ready */
> +	CMD_MEAS_READY,
> +	/* get measurement */
> +	CMD_READ_MEAS,
> +	/* turn on/off automatic self calibration */
> +	CMD_ASC,
> +	/* set/get forced recalibration value */
> +	CMD_FRC,
> +	/* set/get temperature offset */
> +	CMD_TEMP_OFFSET,
> +	/* get firmware version */
> +	CMD_FW_VERSION,
> +	/* reset sensor */
> +	CMD_RESET,
> +	/*
> +	 * Command for altitude compensation was omitted intentionally because
> +	 * the same can be achieved by means of CMD_START_MEAS which takes
> +	 * pressure above the sea level as an argument.
> +	 */
> +};
> +
> +#define SCD30_MEAS_COUNT 3
> +
> +typedef int (*scd30_command_t)(struct scd30_state *state, enum scd30_cmd cmd,
> +			       u16 arg, void *response, int size);
> +
> +struct scd30_state {
> +	/* serialize access to the device */
> +	struct mutex lock;
> +	struct device *dev;
> +	struct regulator *vdd;
> +	struct completion meas_ready;
> +	void *priv;
> +	int irq;
> +	/*
> +	 * no way to retrieve current ambient pressure compensation value from
> +	 * the sensor so keep one around
> +	 */
> +	u16 pressure_comp;
> +	u16 meas_interval;
> +	int meas[SCD30_MEAS_COUNT];
> +
> +	scd30_command_t command;
> +};
> +
> +int scd30_suspend(struct device *dev);
> +int scd30_resume(struct device *dev);
> +
> +static __maybe_unused SIMPLE_DEV_PM_OPS(scd30_pm_ops, scd30_suspend,
> +					scd30_resume);
> +
> +int scd30_probe(struct device *dev, int irq, const char *name, void *priv,
> +		scd30_command_t command);
> +
> +#endif
> diff --git a/drivers/iio/chemical/scd30_core.c b/drivers/iio/chemical/scd30_core.c
> new file mode 100644
> index 000000000000..3b7d0a7ea7ae
> --- /dev/null
> +++ b/drivers/iio/chemical/scd30_core.c
> @@ -0,0 +1,764 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Sensirion SCD30 carbon dioxide sensor core driver
> + *
> + * Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com>
> + */
> +#include <linux/bits.h>
> +#include <linux/completion.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <linux/export.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/iio/trigger.h>
> +#include <linux/iio/trigger_consumer.h>
> +#include <linux/iio/triggered_buffer.h>
> +#include <linux/iio/types.h>
> +#include <linux/interrupt.h>
> +#include <linux/irqreturn.h>
> +#include <linux/jiffies.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/string.h>
> +#include <linux/sysfs.h>
> +#include <linux/types.h>
> +#include <asm/byteorder.h>
> +
> +#include "scd30.h"
> +
> +#define SCD30_PRESSURE_COMP_MIN_MBAR 700
> +#define SCD30_PRESSURE_COMP_MAX_MBAR 1400
> +#define SCD30_PRESSURE_COMP_DEFAULT 1013
> +#define SCD30_MEAS_INTERVAL_MIN_S 2
> +#define SCD30_MEAS_INTERVAL_MAX_S 1800
> +#define SCD30_MEAS_INTERVAL_DEFAULT SCD30_MEAS_INTERVAL_MIN_S
> +#define SCD30_FRC_MIN_PPM 400
> +#define SCD30_FRC_MAX_PPM 2000
> +#define SCD30_TEMP_OFFSET_MAX 655360
> +#define SCD30_EXTRA_TIMEOUT_PER_S 250
> +
> +enum {
> +	SCD30_CONC,
> +	SCD30_TEMP,
> +	SCD30_HR,
> +};
> +
> +static int scd30_command_write(struct scd30_state *state, enum scd30_cmd cmd,
> +			       u16 arg)
> +{
> +	return state->command(state, cmd, arg, NULL, 0);
> +}
> +
> +static int scd30_command_read(struct scd30_state *state, enum scd30_cmd cmd,
> +			      u16 *val)
> +{
> +	int ret;
> +
> +	ret = state->command(state, cmd, 0, val, sizeof(*val));
> +	*val = be16_to_cpup((__be16 *)val);

Please use a local variable for the __be16 as it makes thing more readable
and easier to check for endian problems.

> +
> +	return ret;
> +}
> +
> +static int scd30_reset(struct scd30_state *state)
> +{
> +	int ret;
> +	u16 val;
> +
> +	ret = scd30_command_write(state, CMD_RESET, 0);
> +	if (ret)
> +		return ret;
> +
> +	/* sensor boots up within 2 secs */
> +	msleep(2000);
> +	/*
> +	 * Power-on-reset causes sensor to produce some glitch on i2c bus and
> +	 * some controllers end up in error state. Try to recover by placing
> +	 * any data on the bus.
> +	 */
> +	scd30_command_read(state, CMD_MEAS_READY, &val);
> +
> +	return 0;
> +}
> +
> +/* simplified float to fixed point conversion with a scaling factor of 0.01 */
> +static int scd30_float_to_fp(int float32)
> +{
> +	int fraction, shift,
> +	    mantissa = float32 & GENMASK(22, 0),
> +	    sign = float32 & BIT(31) ? -1 : 1,
> +	    exp = (float32 & ~BIT(31)) >> 23;
> +
> +	/* special case 0 */
> +	if (!exp && !mantissa)
> +		return 0;
> +
> +	exp -= 127;
> +	if (exp < 0) {
> +		exp = -exp;
> +		/* return values ranging from 1 to 99 */
> +		return sign * ((((BIT(23) + mantissa) * 100) >> 23) >> exp);
> +	}
> +
> +	/* return values starting at 100 */
> +	shift = 23 - exp;
> +	float32 = BIT(exp) + (mantissa >> shift);
> +	fraction = mantissa & GENMASK(shift - 1, 0);
> +
> +	return sign * (float32 * 100 + ((fraction * 100) >> shift));
> +}
> +
> +static int scd30_read_meas(struct scd30_state *state)
> +{
> +	int i, ret;
> +
> +	ret = state->command(state, CMD_READ_MEAS, 0, state->meas,
> +			     sizeof(state->meas));
> +	if (ret)
> +		return ret;
> +
> +	be32_to_cpu_array(state->meas, state->meas, ARRAY_SIZE(state->meas));
> +
> +	for (i = 0; i < ARRAY_SIZE(state->meas); i++)
> +		state->meas[i] = scd30_float_to_fp(state->meas[i]);
> +
> +	return 0;
> +}
> +
> +static int scd30_wait_meas_irq(struct scd30_state *state)
> +{
> +	int ret, timeout;
> +
> +	timeout = state->meas_interval * (1000 + SCD30_EXTRA_TIMEOUT_PER_S);
> +	timeout = msecs_to_jiffies(timeout);
> +	reinit_completion(&state->meas_ready);
> +	enable_irq(state->irq);
> +	ret = wait_for_completion_interruptible_timeout(&state->meas_ready,
> +							timeout);
> +	if (ret > 0)
> +		ret = 0;
> +	else if (!ret)
> +		ret = -ETIMEDOUT;
> +
> +	disable_irq(state->irq);
> +
> +	return ret;
> +}
> +
> +static int scd30_wait_meas_poll(struct scd30_state *state)
> +{
> +	int timeout = state->meas_interval * SCD30_EXTRA_TIMEOUT_PER_S;
> +	int tries = 5;
> +
> +	do {
> +		int ret;
> +		u16 val;
> +
> +		ret = scd30_command_read(state, CMD_MEAS_READY, &val);
> +		if (ret)
> +			return -EIO;
> +
> +		/* new measurement available */
> +		if (val)
> +			break;
> +
> +		msleep_interruptible(timeout);
> +	} while (--tries);
> +
> +	return tries ? 0 : -ETIMEDOUT;
> +}
> +
> +static int scd30_read_poll(struct scd30_state *state)
> +{
> +	int ret;
> +
> +	ret = scd30_wait_meas_poll(state);
> +	if (ret)
> +		return ret;
> +
> +	return scd30_read_meas(state);
> +}
> +
> +static int scd30_read(struct scd30_state *state)
> +{
> +	if (state->irq > 0)
> +		return scd30_wait_meas_irq(state);
> +
> +	return scd30_read_poll(state);
> +}
> +
> +static int scd30_read_raw(struct iio_dev *indio_dev,
> +			  struct iio_chan_spec const *chan,
> +			  int *val, int *val2, long mask)
> +{
> +	struct scd30_state *state = iio_priv(indio_dev);
> +	int ret, meas[SCD30_MEAS_COUNT];
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +	case IIO_CHAN_INFO_PROCESSED:
> +		ret = iio_device_claim_direct_mode(indio_dev);
> +		if (ret)
> +			return ret;
> +
> +		mutex_lock(&state->lock);
> +		ret = scd30_read(state);
> +		memcpy(meas, state->meas, SCD30_MEAS_COUNT * sizeof(*meas));

The local copy seems a bit excessive.  This isn't likely to be a particularly
fast path so perhaps skip the copy but hold the locks until we are
done with the buffer?

> +		mutex_unlock(&state->lock);
> +		iio_device_release_direct_mode(indio_dev);
> +		if (ret)
> +			return ret;
> +
> +		switch (chan->type) {
> +		case IIO_CONCENTRATION:
> +			*val = meas[chan->address] / 1000000;
> +			*val2 = meas[chan->address] % 1000000;
> +
> +			return IIO_VAL_INT_PLUS_MICRO;
> +		case IIO_TEMP:
> +		case IIO_HUMIDITYRELATIVE:
> +			*val = meas[chan->address] * 10;
> +
> +			return IIO_VAL_INT;
> +		default:
> +			return -EINVAL;
> +		}
> +	case IIO_CHAN_INFO_SCALE:
> +		switch (chan->type) {
> +		case IIO_CONCENTRATION:
> +			*val = 0;
> +			*val2 = 1;
> +
> +			return IIO_VAL_INT_PLUS_MICRO;
> +		case IIO_TEMP:
> +		case IIO_HUMIDITYRELATIVE:
> +			*val = 10;
> +
> +			return IIO_VAL_INT;
> +		default:
> +			return -EINVAL;
> +		}
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		*val = 0;
> +		*val2 = 0;
> +
> +		mutex_lock(&state->lock);
> +		ret = scd30_command_read(state, CMD_MEAS_INTERVAL, (u16 *)val2);

See below. I'll assume you'll fix all of these.

> +		mutex_unlock(&state->lock);
> +		if (ret)
> +			return ret;
> +
> +		*val2 = 1000000000 / *val2;
> +
> +		return IIO_VAL_INT_PLUS_NANO;
> +	case IIO_CHAN_INFO_CALIBSCALE:
> +		mutex_lock(&state->lock);
> +		*val = state->pressure_comp / 10;
> +		*val2 = (state->pressure_comp % 10) * 100000;
> +		mutex_unlock(&state->lock);
> +
> +		return IIO_VAL_INT_PLUS_MICRO;
> +	case IIO_CHAN_INFO_CALIBBIAS:
> +		*val = 0;
> +		mutex_lock(&state->lock);
> +		ret = scd30_command_read(state, CMD_TEMP_OFFSET, (u16 *)val);

Reading a u16 directly into a int is not a good idea.  What you get will
depend  on the endianness of the machine.

Use an intermediate variable of the right size.

> +		mutex_unlock(&state->lock);
> +
> +		return IIO_VAL_INT;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int scd30_write_raw(struct iio_dev *indio_dev,
> +			   struct iio_chan_spec const *chan,
> +			   int val, int val2, long mask)
> +{
> +	struct scd30_state *state = iio_priv(indio_dev);
> +	int ret = -EINVAL;
> +
> +	mutex_lock(&state->lock);
> +	switch (mask) {
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		if (val)
> +			break;
> +
> +		val = 1000000000 / val2;
> +		if (val < SCD30_MEAS_INTERVAL_MIN_S ||
> +		    val > SCD30_MEAS_INTERVAL_MAX_S)
> +			break;
> +
> +		ret = scd30_command_write(state, CMD_MEAS_INTERVAL, val);
> +		if (ret)
> +			break;
> +
> +		state->meas_interval = val;
> +		break;
> +	case IIO_CHAN_INFO_CALIBSCALE:
> +		val = (val * 1000000 + val2) / 100000;
> +		if (val < SCD30_PRESSURE_COMP_MIN_MBAR ||
> +		    val > SCD30_PRESSURE_COMP_MAX_MBAR)
> +			break;
> +
> +		ret = scd30_command_write(state, CMD_START_MEAS, val);
> +		if (ret)
> +			break;
> +
> +		state->pressure_comp = val;
> +		break;
> +	case IIO_CHAN_INFO_CALIBBIAS:
> +		if (val < 0 || val > SCD30_TEMP_OFFSET_MAX)
> +			break;
> +		/*
> +		 * Manufacturer does not explicitly specify min/max sensible
> +		 * values hence check is omitted for simplicity.
> +		 */
> +		ret = scd30_command_write(state, CMD_TEMP_OFFSET / 10, val);
> +	}
> +	mutex_unlock(&state->lock);
> +
> +	return ret;
> +}
> +
> +static int scd30_write_raw_get_fmt(struct iio_dev *indio_dev,
> +				   struct iio_chan_spec const *chan, long mask)
> +{
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +	case IIO_CHAN_INFO_CALIBBIAS:
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_CALIBSCALE:
> +		return IIO_VAL_INT_PLUS_MICRO;
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		return IIO_VAL_INT_PLUS_NANO;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static const int scd30_pressure_calibscale_available[] = {
> +	SCD30_PRESSURE_COMP_MIN_MBAR / 10, 0,
> +	0, 100000,
> +	SCD30_PRESSURE_COMP_MAX_MBAR / 10, 0,
> +};
> +
> +static const int scd30_temp_calibbias_available[] = {
> +	0, 10, SCD30_TEMP_OFFSET_MAX,
> +};
> +
> +static int scd30_read_avail(struct iio_dev *indio_dev,
> +			    struct iio_chan_spec const *chan, const int **vals,
> +			    int *type, int *length, long mask)
> +{
> +	switch (mask) {
> +	case IIO_CHAN_INFO_CALIBSCALE:
> +		*vals = scd30_pressure_calibscale_available;
> +		*type = IIO_VAL_INT_PLUS_MICRO;
> +
> +		return IIO_AVAIL_RANGE;
> +	case IIO_CHAN_INFO_CALIBBIAS:
> +		*vals = scd30_temp_calibbias_available;
> +		*type = IIO_VAL_INT;
> +
> +		return IIO_AVAIL_RANGE;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static ssize_t sampling_frequency_available_show(struct device *dev,
> +						 struct device_attribute *attr,
> +						 char *buf)
> +{
> +	int i = SCD30_MEAS_INTERVAL_MIN_S;
> +	ssize_t len = 0;
> +
> +	do {
> +		len += scnprintf(buf + len, PAGE_SIZE - len, "0.%09u ",
> +				 1000000000 / i);
> +		/*
> +		 * Not all values fit PAGE_SIZE buffer hence print every 6th
> +		 * (each frequency differs by 6s in time domain from the
> +		 * adjecent). Unlisted but valid ones are still accepted.

adjacent

Hmm. Maybe we need to think about some description for inverse of linear
cases as they are likely to be fairly common.
This will work in meantime.

> +		 */
> +		i += 6;
> +	} while (i <= SCD30_MEAS_INTERVAL_MAX_S);
> +
> +	buf[len - 1] = '\n';
> +
> +	return len;
> +}
> +
> +static ssize_t calibration_show(struct device *dev,
> +				struct device_attribute *attr, char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct scd30_state *state = iio_priv(indio_dev);
> +	u16 asc, frc;
> +	int ret;
> +
> +	mutex_lock(&state->lock);
> +	ret = scd30_command_read(state, CMD_ASC, &asc);
> +	if (ret)
> +		goto out;
> +
> +	ret = scd30_command_read(state, CMD_FRC, &frc);
> +out:
> +	mutex_unlock(&state->lock);
> +
> +	return ret ?: sprintf(buf, "%d %d\n", asc, frc);
> +}
> +
> +static ssize_t calibration_store(struct device *dev,
> +				 struct device_attribute *attr, const char *buf,
> +				 size_t len)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct scd30_state *state = iio_priv(indio_dev);
> +	int ret;
> +	u16 val;

As commented above, this interface doesn't win on the
obvious front so needs a rethink!

> +
> +	ret = kstrtou16(buf, 0, &val);
> +	if (ret)
> +		return ret;
> +
> +	mutex_lock(&state->lock);
> +	if (val == 0 || val == 1)
> +		ret = scd30_command_write(state, CMD_ASC, val);
> +	else if (val >= SCD30_FRC_MIN_PPM && val <= SCD30_FRC_MAX_PPM)
> +		ret = scd30_command_write(state, CMD_FRC, val);
> +	else
> +		ret = -EINVAL;
> +	mutex_unlock(&state->lock);
> +
> +	return ret ?: len;
> +}
> +
> +static IIO_DEVICE_ATTR_RO(sampling_frequency_available, 0);
> +static IIO_DEVICE_ATTR_RW(calibration, 0);
> +
> +static struct attribute *scd30_attrs[] = {
> +	&iio_dev_attr_sampling_frequency_available.dev_attr.attr,
> +	&iio_dev_attr_calibration.dev_attr.attr,
> +	NULL
> +};
> +
> +static const struct attribute_group scd30_attr_group = {
> +	.attrs = scd30_attrs,
> +};
> +
> +static const struct iio_info scd30_info = {
> +	.attrs = &scd30_attr_group,
> +	.read_raw = scd30_read_raw,
> +	.write_raw = scd30_write_raw,
> +	.write_raw_get_fmt = scd30_write_raw_get_fmt,
> +	.read_avail = scd30_read_avail,
> +};
> +
> +#define SCD30_CHAN_SCAN_TYPE(_sign, _realbits) .scan_type = { \
> +	.sign = _sign, \
> +	.realbits = _realbits, \
> +	.storagebits = 32, \
> +	.endianness = IIO_CPU, \
> +}
> +
> +static const struct iio_chan_spec scd30_channels[] = {
> +	{
> +		.type = IIO_PRESSURE,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_CALIBSCALE),
> +		.info_mask_separate_available = BIT(IIO_CHAN_INFO_CALIBSCALE),
> +		.scan_index = -1,
> +	},
> +	{
> +		.type = IIO_CONCENTRATION,
> +		.channel2 = IIO_MOD_CO2,
> +		.address = SCD30_CONC,
> +		.scan_index = SCD30_CONC,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> +				      BIT(IIO_CHAN_INFO_SCALE),
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
> +		.modified = 1,
> +
> +		SCD30_CHAN_SCAN_TYPE('u', 16),
> +	},
> +	{
> +		.type = IIO_TEMP,
> +		.address = SCD30_TEMP,
> +		.scan_index = SCD30_TEMP,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
> +				      BIT(IIO_CHAN_INFO_CALIBBIAS) |
> +				      BIT(IIO_CHAN_INFO_SCALE),

Combination of processed and scale is unusual.  Normally scale provides
a conversion factor or a _RAW reading.  

I 'think' these units are otherwise fine (milli degrees centigrade)


> +		.info_mask_separate_available = BIT(IIO_CHAN_INFO_CALIBBIAS),
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
> +
> +		SCD30_CHAN_SCAN_TYPE('s', 14),
> +	},
> +	{
> +		.type = IIO_HUMIDITYRELATIVE,
> +		.address = SCD30_HR,
> +		.scan_index = SCD30_HR,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
> +				      BIT(IIO_CHAN_INFO_SCALE),

As above. Not normal to see scale and processed.

> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
> +
> +		SCD30_CHAN_SCAN_TYPE('u', 14),
> +	},
> +	IIO_CHAN_SOFT_TIMESTAMP(3),
> +};
> +
> +int __maybe_unused scd30_suspend(struct device *dev)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct scd30_state *state  = iio_priv(indio_dev);
> +	int ret;
> +
> +	ret = scd30_command_write(state, CMD_STOP_MEAS, 0);
> +	if (ret)
> +		return ret;
> +
> +	return regulator_disable(state->vdd);
> +}
> +EXPORT_SYMBOL(scd30_suspend);
> +
> +int __maybe_unused scd30_resume(struct device *dev)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct scd30_state *state = iio_priv(indio_dev);
> +	int ret;
> +
> +	ret = regulator_enable(state->vdd);
> +	if (ret)
> +		return ret;
> +
> +	return scd30_command_write(state, CMD_START_MEAS, state->pressure_comp);
> +}
> +EXPORT_SYMBOL(scd30_resume);
> +
> +static void scd30_stop_meas(void *data)
> +{
> +	struct scd30_state *state = data;
> +
> +	scd30_command_write(state, CMD_STOP_MEAS, 0);
> +}
> +
> +static void scd30_disable_regulator(void *data)
> +{
> +	struct scd30_state *state = data;
> +
> +	regulator_disable(state->vdd);
> +}
> +
> +static irqreturn_t scd30_irq_handler(int irq, void *priv)
> +{
> +	struct iio_dev *indio_dev = priv;
> +
> +	if (iio_buffer_enabled(indio_dev)) {

There is a potential quirk here.  It's possible that
this device is using a different trigger, but another device
is registered to use this one.  If that happens this check
will be a bit counter intuitive.

As such you might want to provide the validate callback so
that this device is the only device allowed to use it's
own trigger.

> +		iio_trigger_poll(indio_dev->trig);
> +
> +		return IRQ_HANDLED;
> +	}
> +
> +	return IRQ_WAKE_THREAD;
> +}
> +
> +static irqreturn_t scd30_irq_thread_handler(int irq, void *priv)
> +{
> +	struct iio_dev *indio_dev = priv;
> +	struct scd30_state *state = iio_priv(indio_dev);
> +	int ret;
> +
> +	ret = scd30_read_meas(state);
> +	if (ret)
> +		goto out;
> +
> +	complete_all(&state->meas_ready);
> +out:
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t scd30_trigger_handler(int irq, void *p)
> +{
> +	struct iio_poll_func *pf = p;
> +	struct iio_dev *indio_dev = pf->indio_dev;
> +	struct scd30_state *state = iio_priv(indio_dev);
> +	struct {
> +		int data[SCD30_MEAS_COUNT];
> +		u64 ts;

Turns out I was wrong when suggesting this approach for drivers.  On x86_32
this will result in there not being any padding between the
data and the timestamp (and in IIO rule of naturally aligned there
needs to be 4 bytes there).  Result is that this structure is
too short.  (thanks btw to Andy who pointed out this issue!)

So, to force that my current preference is.

	struct {
		int data[SCD30_MEAS_COUNT];
		s64 ts __aligned(8);
	} scan;

However, given we do have a hole in the structure there is
a kernel data leak.  So either you need to zero it here,
or move it into the iio_priv() structure.  Doing that
will ensure it is zeroed at allocation.
	
> +	} scan;
> +	int ret;
> +
> +	mutex_lock(&state->lock);
> +	if (!iio_trigger_using_own(indio_dev))
> +		ret = scd30_read_poll(state);
> +	else
> +		ret = scd30_read_meas(state);
> +	memcpy(scan.data, state->meas, sizeof(state->meas));
> +	mutex_unlock(&state->lock);
> +	if (ret)
> +		goto out;
> +
> +	iio_push_to_buffers_with_timestamp(indio_dev, &scan,
> +					   iio_get_time_ns(indio_dev));
> +out:
> +	iio_trigger_notify_done(indio_dev->trig);
> +	return IRQ_HANDLED;
> +}
> +
> +static int scd30_set_trigger_state(struct iio_trigger *trig, bool state)
> +{
> +	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
> +	struct scd30_state *st = iio_priv(indio_dev);
> +
> +	if (state)
> +		enable_irq(st->irq);
> +	else
> +		disable_irq(st->irq);
> +
> +	return 0;
> +}
> +
> +static const struct iio_trigger_ops scd30_trigger_ops = {
> +	.set_trigger_state = scd30_set_trigger_state,
> +};
> +
> +static int scd30_setup_trigger(struct iio_dev *indio_dev)
> +{
> +	struct scd30_state *state = iio_priv(indio_dev);
> +	struct device *dev = indio_dev->dev.parent;
> +	struct iio_trigger *trig;
> +	int ret;
> +
> +	trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
> +				      indio_dev->id);
> +	if (!trig) {
> +		dev_err(dev, "failed to allocate trigger\n");
> +		return -ENOMEM;
> +	}
> +
> +	trig->dev.parent = dev;
> +	trig->ops = &scd30_trigger_ops;
> +	iio_trigger_set_drvdata(trig, indio_dev);
> +
> +	ret = devm_iio_trigger_register(dev, trig);
> +	if (ret)
> +		return ret;
> +
> +	indio_dev->trig = iio_trigger_get(trig);
> +
> +	ret = devm_request_threaded_irq(dev, state->irq, scd30_irq_handler,
> +					scd30_irq_thread_handler,
> +					IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> +					indio_dev->name, indio_dev);
> +	if (ret)
> +		dev_err(dev, "failed to request irq\n");
> +
> +	disable_irq(state->irq);

Given there is a gap between the request above and this disable, this
disable needs a comment explaining why it is here.

I'm assuming it's an optimization?

> +
> +	return ret;
> +}
> +
> +int scd30_probe(struct device *dev, int irq, const char *name, void *priv,
> +		scd30_command_t command)
> +{
> +	static const unsigned long scd30_scan_masks[] = { 0x07, 0x00 };
> +	struct scd30_state *state;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +	u16 val;
> +
> +	indio_dev = devm_iio_device_alloc(dev, sizeof(*state));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	state = iio_priv(indio_dev);
> +	state->dev = dev;

Doesn't seem to be used.

> +	state->priv = priv;

What's this for?  At least at first glance I can't find it being used
anywhere.

> +	state->irq = irq;
> +	state->pressure_comp = SCD30_PRESSURE_COMP_DEFAULT;
> +	state->meas_interval = SCD30_MEAS_INTERVAL_DEFAULT;
> +	state->command = command;
> +	mutex_init(&state->lock);
> +	init_completion(&state->meas_ready);
> +
> +	dev_set_drvdata(dev, indio_dev);
> +
> +	indio_dev->dev.parent = dev;

Side note that there is a series moving this into the core under revision at
the moment.  Hopefully I'll remember to fix this up when applying your patch
if that one has gone in ahead of it.

> +	indio_dev->info = &scd30_info;
> +	indio_dev->name = name;
> +	indio_dev->channels = scd30_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(scd30_channels);
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->available_scan_masks = scd30_scan_masks;
> +
> +	state->vdd = devm_regulator_get(dev, "vdd");
> +	if (IS_ERR(state->vdd)) {
> +		if (PTR_ERR(state->vdd) == -EPROBE_DEFER)
> +			return -EPROBE_DEFER;
> +
> +		dev_err(dev, "failed to get regulator\n");
> +		return PTR_ERR(state->vdd);
> +	}
> +
> +	ret = regulator_enable(state->vdd);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_add_action_or_reset(dev, scd30_disable_regulator, state);
> +	if (ret)
> +		return ret;
> +

A comment here on why it makes sense to register this here.  What
started mesurement? It seems that happens well below here so
we should really call this after that start all.

> +	ret = devm_add_action_or_reset(dev, scd30_stop_meas, state);
> +	if (ret)
> +		return ret;
> +
> +	ret = scd30_reset(state);
> +	if (ret) {
> +		dev_err(dev, "failed to reset device: %d\n", ret);
> +		return ret;
> +	}
> +
> +	if (state->irq > 0) {
> +		ret = scd30_setup_trigger(indio_dev);
> +		if (ret) {
> +			dev_err(dev, "failed to setup trigger: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
> +					      scd30_trigger_handler, NULL);
> +	if (ret)
> +		return ret;
> +
> +	ret = scd30_command_read(state, CMD_FW_VERSION, &val);
> +	if (ret) {
> +		dev_err(dev, "failed to read firmware version: %d\n", ret);
> +		return ret;
> +	}
> +	dev_info(dev, "firmware version: %d.%d\n", val >> 8, (char)val);
> +
> +	ret = scd30_command_write(state, CMD_MEAS_INTERVAL,
> +				  state->meas_interval);
> +	if (ret) {
> +		dev_err(dev, "failed to set measurement interval: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = scd30_command_write(state, CMD_START_MEAS, state->pressure_comp);
> +	if (ret) {
> +		dev_err(dev, "failed to start measurement: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return devm_iio_device_register(dev, indio_dev);
> +}
> +EXPORT_SYMBOL(scd30_probe);
> +
> +MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>");
> +MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor core driver");
> +MODULE_LICENSE("GPL v2");
> --
> 2.26.2
> 


^ permalink raw reply

* Re: [PATCH v2 2/3] media: rockchip: Introduce driver for Rockhip's camera interface
From: kbuild test robot @ 2020-05-31  4:39 UTC (permalink / raw)
  To: Maxime Chevallier, Mauro Carvalho Chehab, Robin Murphy,
	Rob Herring, Mark Rutland, Heiko Stuebner, Hans Verkuil
  Cc: kbuild-all, linux-media, Maxime Chevallier, devicetree,
	linux-arm-kernel
In-Reply-To: <20200529130405.929429-3-maxime.chevallier@bootlin.com>

[-- Attachment #1: Type: text/plain, Size: 3528 bytes --]

Hi Maxime,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on linuxtv-media/master]
[also build test WARNING on robh/for-next v5.7-rc7 next-20200529]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url:    https://github.com/0day-ci/linux/commits/Maxime-Chevallier/media-rockchip-Introduce-driver-for-the-camera-interface-on-PX30/20200531-081952
base:   git://linuxtv.org/media_tree.git master
config: i386-allyesconfig (attached as .config)
compiler: gcc-9 (Debian 9.3.0-13) 9.3.0
reproduce (this is a W=1 build):
        # save the attached .config to linux build tree
        make W=1 ARCH=i386 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kbuild test robot <lkp@intel.com>

All warnings (new ones prefixed by >>, old ones prefixed by <<):

In file included from drivers/media/platform/rockchip/cif/dev.c:22:
>> drivers/media/platform/rockchip/cif/regs.h:113: warning: "FRAME_END" redefined
113 | #define FRAME_END   (0x01 << 0)
|
In file included from arch/x86/include/asm/paravirt.h:18,
from arch/x86/include/asm/spinlock.h:10,
from include/linux/spinlock.h:89,
from include/linux/rwsem.h:16,
from include/linux/notifier.h:15,
from include/linux/clk.h:14,
from drivers/media/platform/rockchip/cif/dev.c:8:
arch/x86/include/asm/frame.h:60: note: this is the location of the previous definition
60 | #define FRAME_END "pop %" _ASM_BP "n"
|
In file included from include/media/v4l2-subdev.h:15,
from include/media/v4l2-device.h:13,
from drivers/media/platform/rockchip/cif/dev.h:15,
from drivers/media/platform/rockchip/cif/dev.c:21:
drivers/media/platform/rockchip/cif/dev.c: In function 'rkcif_plat_probe':
include/media/v4l2-common.h:58:32: warning: 'v4l2_dev' may be used uninitialized in this function [-Wmaybe-uninitialized]
58 |  printk(level "%s: " fmt, (dev)->name , ## arg)
|                                ^~
drivers/media/platform/rockchip/cif/dev.c:216:22: note: 'v4l2_dev' was declared here
216 |  struct v4l2_device *v4l2_dev;
|                      ^~~~~~~~
--
In file included from drivers/media/platform/rockchip/cif/capture.c:22:
>> drivers/media/platform/rockchip/cif/regs.h:113: warning: "FRAME_END" redefined
113 | #define FRAME_END   (0x01 << 0)
|
In file included from arch/x86/include/asm/paravirt.h:18,
from arch/x86/include/asm/spinlock.h:10,
from include/linux/spinlock.h:89,
from include/linux/wait.h:9,
from include/linux/pid.h:6,
from include/linux/sched.h:14,
from include/linux/ratelimit.h:6,
from include/linux/dev_printk.h:16,
from include/linux/device.h:15,
from include/linux/pm_runtime.h:11,
from drivers/media/platform/rockchip/cif/capture.c:10:
arch/x86/include/asm/frame.h:60: note: this is the location of the previous definition
60 | #define FRAME_END "pop %" _ASM_BP "n"
|

vim +/FRAME_END +113 drivers/media/platform/rockchip/cif/regs.h

   110	
   111	/* CIF INTSTAT */
   112	#define INTSTAT_CLS			(0x3FF)
 > 113	#define FRAME_END			(0x01 << 0)
   114	#define LINE_ERR			(0x01 << 2)
   115	#define PST_INF_FRAME_END		(0x01 << 9)
   116	#define FRAME_END_CLR			(0x01 << 0)
   117	#define LINE_ERR_CLR			(0x01 << 2)
   118	#define PST_INF_FRAME_END_CLR		(0x01 << 9)
   119	#define INTSTAT_ERR			(0xFC)
   120	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org

[-- Attachment #2: .config.gz --]
[-- Type: application/gzip, Size: 72524 bytes --]

^ permalink raw reply

* [PATCH v3 4/4] pinctrl: bcm2835: Add support for wake-up interrupts
From: Florian Fainelli @ 2020-05-31  0:11 UTC (permalink / raw)
  To: linux-kernel
  Cc: Florian Fainelli, Linus Walleij, Rob Herring, Ray Jui,
	Scott Branden,
	maintainer:BROADCOM BCM281XX/BCM11XXX/BCM216XX ARM ARCHITE...,
	Nicolas Saenz Julienne, Stefan Wahren, Geert Uytterhoeven,
	Matti Vaittinen, open list:PIN CONTROL SUBSYSTEM,
	open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
	moderated list:BROADCOM BCM2711/BCM2835 ARM ARCHITECTURE,
	moderated list:BROADCOM BCM2711/BCM2835 ARM ARCHITECTURE
In-Reply-To: <20200531001101.24945-1-f.fainelli@gmail.com>

Leverage the IRQCHIP_MASK_ON_SUSPEND flag in order to avoid having to
specifically treat the GPIO interrupts during suspend and resume, and
simply implement an irq_set_wake() callback that is responsible for
enabling the parent wake-up interrupt as a wake-up interrupt.

To avoid allocating unnecessary resources for other chips, the wake-up
interrupts are only initialized if we have a brcm,bcm7211-gpio
compatibility string.

Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
 drivers/pinctrl/bcm/pinctrl-bcm2835.c | 76 ++++++++++++++++++++++++++-
 1 file changed, 75 insertions(+), 1 deletion(-)

diff --git a/drivers/pinctrl/bcm/pinctrl-bcm2835.c b/drivers/pinctrl/bcm/pinctrl-bcm2835.c
index 1b00d93aa66e..1d21129f7751 100644
--- a/drivers/pinctrl/bcm/pinctrl-bcm2835.c
+++ b/drivers/pinctrl/bcm/pinctrl-bcm2835.c
@@ -19,6 +19,7 @@
 #include <linux/irq.h>
 #include <linux/irqdesc.h>
 #include <linux/init.h>
+#include <linux/interrupt.h>
 #include <linux/of_address.h>
 #include <linux/of.h>
 #include <linux/of_irq.h>
@@ -76,6 +77,7 @@
 struct bcm2835_pinctrl {
 	struct device *dev;
 	void __iomem *base;
+	int *wake_irq;
 
 	/* note: locking assumes each bank will have its own unsigned long */
 	unsigned long enabled_irq_map[BCM2835_NUM_BANKS];
@@ -435,6 +437,11 @@ static void bcm2835_gpio_irq_handler(struct irq_desc *desc)
 	chained_irq_exit(host_chip, desc);
 }
 
+static irqreturn_t bcm2835_gpio_wake_irq_handler(int irq, void *dev_id)
+{
+	return IRQ_HANDLED;
+}
+
 static inline void __bcm2835_gpio_irq_config(struct bcm2835_pinctrl *pc,
 	unsigned reg, unsigned offset, bool enable)
 {
@@ -634,6 +641,34 @@ static void bcm2835_gpio_irq_ack(struct irq_data *data)
 	bcm2835_gpio_set_bit(pc, GPEDS0, gpio);
 }
 
+static int bcm2835_gpio_irq_set_wake(struct irq_data *data, unsigned int on)
+{
+	struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+	struct bcm2835_pinctrl *pc = gpiochip_get_data(chip);
+	unsigned gpio = irqd_to_hwirq(data);
+	unsigned int irqgroup;
+	int ret = -EINVAL;
+
+	if (!pc->wake_irq)
+		return ret;
+
+	if (gpio <= 27)
+		irqgroup = 0;
+	else if (gpio >= 28 && gpio <= 45)
+		irqgroup = 1;
+	else if (gpio >= 46 && gpio <= 57)
+		irqgroup = 2;
+	else
+		return ret;
+
+	if (on)
+		ret = enable_irq_wake(pc->wake_irq[irqgroup]);
+	else
+		ret = disable_irq_wake(pc->wake_irq[irqgroup]);
+
+	return ret;
+}
+
 static struct irq_chip bcm2835_gpio_irq_chip = {
 	.name = MODULE_NAME,
 	.irq_enable = bcm2835_gpio_irq_enable,
@@ -642,6 +677,8 @@ static struct irq_chip bcm2835_gpio_irq_chip = {
 	.irq_ack = bcm2835_gpio_irq_ack,
 	.irq_mask = bcm2835_gpio_irq_disable,
 	.irq_unmask = bcm2835_gpio_irq_enable,
+	.irq_set_wake = bcm2835_gpio_irq_set_wake,
+	.flags = IRQCHIP_MASK_ON_SUSPEND,
 };
 
 static int bcm2835_pctl_get_groups_count(struct pinctrl_dev *pctldev)
@@ -1154,6 +1191,7 @@ static int bcm2835_pinctrl_probe(struct platform_device *pdev)
 	struct resource iomem;
 	int err, i;
 	const struct of_device_id *match;
+	int is_7211 = 0;
 
 	BUILD_BUG_ON(ARRAY_SIZE(bcm2835_gpio_pins) != BCM2711_NUM_GPIOS);
 	BUILD_BUG_ON(ARRAY_SIZE(bcm2835_gpio_groups) != BCM2711_NUM_GPIOS);
@@ -1180,6 +1218,7 @@ static int bcm2835_pinctrl_probe(struct platform_device *pdev)
 		return -EINVAL;
 
 	pdata = match->data;
+	is_7211 = of_device_is_compatible(np, "brcm,bcm7211-gpio");
 
 	pc->gpio_chip = *pdata->gpio_chip;
 	pc->gpio_chip.parent = dev;
@@ -1214,6 +1253,15 @@ static int bcm2835_pinctrl_probe(struct platform_device *pdev)
 				     GFP_KERNEL);
 	if (!girq->parents)
 		return -ENOMEM;
+
+	if (is_7211) {
+		pc->wake_irq = devm_kcalloc(dev, BCM2835_NUM_IRQS,
+					    sizeof(*pc->wake_irq),
+					    GFP_KERNEL);
+		if (!pc->wake_irq)
+			return -ENOMEM;
+	}
+
 	/*
 	 * Use the same handler for all groups: this is necessary
 	 * since we use one gpiochip to cover all lines - the
@@ -1221,8 +1269,34 @@ static int bcm2835_pinctrl_probe(struct platform_device *pdev)
 	 * bank that was firing the IRQ and look up the per-group
 	 * and bank data.
 	 */
-	for (i = 0; i < BCM2835_NUM_IRQS; i++)
+	for (i = 0; i < BCM2835_NUM_IRQS; i++) {
+		int len;
+		char *name;
+
 		girq->parents[i] = irq_of_parse_and_map(np, i);
+		if (!is_7211)
+			continue;
+
+		/* Skip over the all banks interrupts */
+		pc->wake_irq[i] = irq_of_parse_and_map(np, i +
+						       BCM2835_NUM_IRQS + 1);
+
+		len = strlen(dev_name(pc->dev)) + 16;
+		name = devm_kzalloc(pc->dev, len, GFP_KERNEL);
+		if (!name)
+			return -ENOMEM;
+
+		snprintf(name, len, "%s:bank%d", dev_name(pc->dev), i);
+
+		/* These are optional interrupts */
+		err = devm_request_irq(dev, pc->wake_irq[i],
+				       bcm2835_gpio_wake_irq_handler,
+				       IRQF_SHARED, name, pc);
+		if (err)
+			dev_warn(dev, "unable to request wake IRQ %d\n",
+				 pc->wake_irq[i]);
+	}
+
 	girq->default_type = IRQ_TYPE_NONE;
 	girq->handler = handle_level_irq;
 
-- 
2.17.1


^ permalink raw reply related

* [PATCH v3 2/4] dt-bindings: pinctrl: Document optional BCM7211 wake-up interrupts
From: Florian Fainelli @ 2020-05-31  0:10 UTC (permalink / raw)
  To: linux-kernel
  Cc: Florian Fainelli, Linus Walleij, Rob Herring, Ray Jui,
	Scott Branden,
	maintainer:BROADCOM BCM281XX/BCM11XXX/BCM216XX ARM ARCHITE...,
	Nicolas Saenz Julienne, Stefan Wahren, Geert Uytterhoeven,
	Matti Vaittinen, open list:PIN CONTROL SUBSYSTEM,
	open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
	moderated list:BROADCOM BCM2711/BCM2835 ARM ARCHITECTURE,
	moderated list:BROADCOM BCM2711/BCM2835 ARM ARCHITECTURE
In-Reply-To: <20200531001101.24945-1-f.fainelli@gmail.com>

BCM7211 supports wake-up interrupts in the form of optional interrupt
lines, one per bank, plus the "all banks" interrupt line.

Acked-by: Rob Herring <robh@kernel.org>
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
 .../devicetree/bindings/pinctrl/brcm,bcm2835-gpio.txt         | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/pinctrl/brcm,bcm2835-gpio.txt b/Documentation/devicetree/bindings/pinctrl/brcm,bcm2835-gpio.txt
index dfc67b90591c..5682b2010e50 100644
--- a/Documentation/devicetree/bindings/pinctrl/brcm,bcm2835-gpio.txt
+++ b/Documentation/devicetree/bindings/pinctrl/brcm,bcm2835-gpio.txt
@@ -16,7 +16,9 @@ Required properties:
   second cell is used to specify optional parameters:
   - bit 0 specifies polarity (0 for normal, 1 for inverted)
 - interrupts : The interrupt outputs from the controller. One interrupt per
-  individual bank followed by the "all banks" interrupt.
+  individual bank followed by the "all banks" interrupt. For BCM7211, an
+  additional set of per-bank interrupt line and an "all banks" wake-up
+  interrupt may be specified.
 - interrupt-controller: Marks the device node as an interrupt controller.
 - #interrupt-cells : Should be 2.
   The first cell is the GPIO number.
-- 
2.17.1


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox