diff mbox series

[1/2] iio: adc: ad4695: implement triggered buffer

Message ID 20240807-iio-adc-ad4695-buffered-read-v1-1-bdafc39b2283@baylibre.com (mailing list archive)
State Changes Requested
Headers show
Series iio: adc: ad4695: implement triggered buffer | expand

Commit Message

David Lechner Aug. 7, 2024, 8:02 p.m. UTC
This implements buffered reads for the ad4695 driver using the typical
triggered buffer implementation, including adding a soft timestamp
channel.

The chip has 4 different modes for doing conversions. The driver is
using the advanced sequencer mode since that is the only mode that
allows individual configuration of all aspects each channel (e.g.
bipolar config currently and oversampling to be added in the future).

Signed-off-by: David Lechner <dlechner@baylibre.com>
---
 drivers/iio/adc/ad4695.c | 233 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 230 insertions(+), 3 deletions(-)

Comments

kernel test robot Aug. 8, 2024, 8:52 a.m. UTC | #1
Hi David,

kernel test robot noticed the following build warnings:

[auto build test WARNING on 7cad163c39cb642ed587d3eeb37a5637ee02740f]

url:    https://github.com/intel-lab-lkp/linux/commits/David-Lechner/iio-adc-ad4695-implement-triggered-buffer/20240808-063333
base:   7cad163c39cb642ed587d3eeb37a5637ee02740f
patch link:    https://lore.kernel.org/r/20240807-iio-adc-ad4695-buffered-read-v1-1-bdafc39b2283%40baylibre.com
patch subject: [PATCH 1/2] iio: adc: ad4695: implement triggered buffer
config: hexagon-allmodconfig (https://download.01.org/0day-ci/archive/20240808/202408081623.ua9EBfoZ-lkp@intel.com/config)
compiler: clang version 20.0.0git (https://github.com/llvm/llvm-project 408d82d352eb98e2d0a804c66d359cd7a49228fe)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240808/202408081623.ua9EBfoZ-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202408081623.ua9EBfoZ-lkp@intel.com/

All warnings (new ones prefixed by >>):

   In file included from drivers/iio/adc/ad4695.c:24:
   In file included from include/linux/iio/triggered_buffer.h:6:
   In file included from include/linux/interrupt.h:11:
   In file included from include/linux/hardirq.h:11:
   In file included from ./arch/hexagon/include/generated/asm/hardirq.h:1:
   In file included from include/asm-generic/hardirq.h:17:
   In file included from include/linux/irq.h:20:
   In file included from include/linux/io.h:14:
   In file included from arch/hexagon/include/asm/io.h:328:
   include/asm-generic/io.h:548:31: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     548 |         val = __raw_readb(PCI_IOBASE + addr);
         |                           ~~~~~~~~~~ ^
   include/asm-generic/io.h:561:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     561 |         val = __le16_to_cpu((__le16 __force)__raw_readw(PCI_IOBASE + addr));
         |                                                         ~~~~~~~~~~ ^
   include/uapi/linux/byteorder/little_endian.h:37:51: note: expanded from macro '__le16_to_cpu'
      37 | #define __le16_to_cpu(x) ((__force __u16)(__le16)(x))
         |                                                   ^
   In file included from drivers/iio/adc/ad4695.c:24:
   In file included from include/linux/iio/triggered_buffer.h:6:
   In file included from include/linux/interrupt.h:11:
   In file included from include/linux/hardirq.h:11:
   In file included from ./arch/hexagon/include/generated/asm/hardirq.h:1:
   In file included from include/asm-generic/hardirq.h:17:
   In file included from include/linux/irq.h:20:
   In file included from include/linux/io.h:14:
   In file included from arch/hexagon/include/asm/io.h:328:
   include/asm-generic/io.h:574:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     574 |         val = __le32_to_cpu((__le32 __force)__raw_readl(PCI_IOBASE + addr));
         |                                                         ~~~~~~~~~~ ^
   include/uapi/linux/byteorder/little_endian.h:35:51: note: expanded from macro '__le32_to_cpu'
      35 | #define __le32_to_cpu(x) ((__force __u32)(__le32)(x))
         |                                                   ^
   In file included from drivers/iio/adc/ad4695.c:24:
   In file included from include/linux/iio/triggered_buffer.h:6:
   In file included from include/linux/interrupt.h:11:
   In file included from include/linux/hardirq.h:11:
   In file included from ./arch/hexagon/include/generated/asm/hardirq.h:1:
   In file included from include/asm-generic/hardirq.h:17:
   In file included from include/linux/irq.h:20:
   In file included from include/linux/io.h:14:
   In file included from arch/hexagon/include/asm/io.h:328:
   include/asm-generic/io.h:585:33: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     585 |         __raw_writeb(value, PCI_IOBASE + addr);
         |                             ~~~~~~~~~~ ^
   include/asm-generic/io.h:595:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     595 |         __raw_writew((u16 __force)cpu_to_le16(value), PCI_IOBASE + addr);
         |                                                       ~~~~~~~~~~ ^
   include/asm-generic/io.h:605:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     605 |         __raw_writel((u32 __force)cpu_to_le32(value), PCI_IOBASE + addr);
         |                                                       ~~~~~~~~~~ ^
   In file included from drivers/iio/adc/ad4695.c:28:
   In file included from include/linux/regulator/consumer.h:35:
   In file included from include/linux/suspend.h:5:
   In file included from include/linux/swap.h:9:
   In file included from include/linux/memcontrol.h:21:
   In file included from include/linux/mm.h:2228:
   include/linux/vmstat.h:514:36: warning: arithmetic between different enumeration types ('enum node_stat_item' and 'enum lru_list') [-Wenum-enum-conversion]
     514 |         return node_stat_name(NR_LRU_BASE + lru) + 3; // skip "nr_"
         |                               ~~~~~~~~~~~ ^ ~~~
>> drivers/iio/adc/ad4695.c:454:8: warning: result of comparison of constant 18446744073709551615 with expression of type 'typeof (_Generic((mask), char: (unsigned char)0, unsigned char: (unsigned char)0, signed char: (unsigned char)0, unsigned short: (unsigned short)0, short: (unsigned short)0, unsigned int: (unsigned int)0, int: (unsigned int)0, unsigned long: (unsigned long)0, long: (unsigned long)0, unsigned long long: (unsigned long long)0, long long: (unsigned long long)0, default: (mask)))' (aka 'unsigned int') is always false [-Wtautological-constant-out-of-range-compare]
     454 |         val = FIELD_PREP(mask, temp_chan_en ? 1 : 0);
         |               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/bitfield.h:115:3: note: expanded from macro 'FIELD_PREP'
     115 |                 __BF_FIELD_CHECK(_mask, 0ULL, _val, "FIELD_PREP: ");    \
         |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/bitfield.h:72:53: note: expanded from macro '__BF_FIELD_CHECK'
      72 |                 BUILD_BUG_ON_MSG(__bf_cast_unsigned(_mask, _mask) >     \
         |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
      73 |                                  __bf_cast_unsigned(_reg, ~0ull),       \
         |                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      74 |                                  _pfx "type of reg too small for mask"); \
         |                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/build_bug.h:39:58: note: expanded from macro 'BUILD_BUG_ON_MSG'
      39 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
         |                                     ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~
   include/linux/compiler_types.h:510:22: note: expanded from macro 'compiletime_assert'
     510 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
         |         ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/compiler_types.h:498:23: note: expanded from macro '_compiletime_assert'
     498 |         __compiletime_assert(condition, msg, prefix, suffix)
         |         ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/compiler_types.h:490:9: note: expanded from macro '__compiletime_assert'
     490 |                 if (!(condition))                                       \
         |                       ^~~~~~~~~
   8 warnings generated.


vim +454 drivers/iio/adc/ad4695.c

   373	
   374	static int ad4695_buffer_preenable(struct iio_dev *indio_dev)
   375	{
   376		struct ad4695_state *st = iio_priv(indio_dev);
   377		struct spi_transfer *xfer;
   378		u8 temp_chan_bit = st->chip_info->num_voltage_inputs;
   379		bool temp_chan_en = false;
   380		u32 reg, mask, val, bit, num_xfer, num_slots;
   381		int ret;
   382	
   383		/*
   384		 * We are using the advanced sequencer since it is the only way to read
   385		 * multiple channels that allows individual configuration of each
   386		 * voltage input channel. Slot 0 in the advanced sequencer is used to
   387		 * account for the gap between trigger polls - we don't read data from
   388		 * this slot. Each enabled voltage channel is assigned a slot starting
   389		 * with slot 1.
   390		 */
   391		num_slots = 1;
   392	
   393		memset(st->buf_read_xfer, 0, sizeof(st->buf_read_xfer));
   394	
   395		/* First xfer is only to trigger conversion of slot 1, so no rx. */
   396		xfer = &st->buf_read_xfer[0];
   397		xfer->cs_change = 1;
   398		xfer->delay.value = AD4695_T_CNVL_NS;
   399		xfer->delay.unit = SPI_DELAY_UNIT_NSECS;
   400		xfer->cs_change_delay.value = AD4695_T_CONVERT_NS;
   401		xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
   402		num_xfer = 1;
   403	
   404		iio_for_each_active_channel(indio_dev, bit) {
   405			xfer = &st->buf_read_xfer[num_xfer];
   406			xfer->bits_per_word = 16;
   407			xfer->rx_buf = &st->buf[(num_xfer - 1) * 2];
   408			xfer->len = 2;
   409			xfer->cs_change = 1;
   410			xfer->cs_change_delay.value = AD4695_T_CONVERT_NS;
   411			xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
   412	
   413			if (bit == temp_chan_bit) {
   414				temp_chan_en = true;
   415			} else {
   416				reg = AD4695_REG_AS_SLOT(num_slots);
   417				val = FIELD_PREP(AD4695_REG_AS_SLOT_INX, bit);
   418	
   419				ret = regmap_write(st->regmap, reg, val);
   420				if (ret)
   421					return ret;
   422	
   423				num_slots++;
   424			}
   425	
   426			num_xfer++;
   427		}
   428	
   429		/*
   430		 * Don't keep CS asserted after last xfer. Also triggers conversion of
   431		 * slot 0.
   432		 */
   433		xfer->cs_change = 0;
   434	
   435		/**
   436		 * The advanced sequencer requires that at least 2 slots are enabled.
   437		 * Since slot 0 is always used for other purposes, we need only 1
   438		 * enabled voltage channel to meet this requirement. This error will
   439		 * only happen if only the temperature channel is enabled.
   440		 */
   441		if (num_slots < 2) {
   442			dev_err_ratelimited(&indio_dev->dev,
   443				"Buffered read requires at least 1 voltage channel enabled\n");
   444			return -EINVAL;
   445		}
   446	
   447		/*
   448		 * Temperature channel isn't included in the sequence, but rather
   449		 * controlled by setting a bit in the TEMP_CTRL register.
   450		 */
   451	
   452		reg = AD4695_REG_TEMP_CTRL;
   453		mask = AD4695_REG_TEMP_CTRL_TEMP_EN;
 > 454		val = FIELD_PREP(mask, temp_chan_en ? 1 : 0);
   455	
   456		ret = regmap_update_bits(st->regmap, reg, mask, val);
   457		if (ret)
   458			return ret;
   459	
   460		spi_message_init_with_transfers(&st->buf_read_msg, st->buf_read_xfer,
   461						num_xfer);
   462	
   463		ret = spi_optimize_message(st->spi, &st->buf_read_msg);
   464		if (ret)
   465			return ret;
   466	
   467		/* This triggers conversion of slot 0. */
   468		ret = ad4695_enter_advanced_sequencer_mode(st, num_slots);
   469		if (ret) {
   470			spi_unoptimize_message(&st->buf_read_msg);
   471			return ret;
   472		}
   473	
   474		return 0;
   475	}
   476
Nuno Sá Aug. 9, 2024, 2:24 p.m. UTC | #2
On Wed, 2024-08-07 at 15:02 -0500, David Lechner wrote:
> This implements buffered reads for the ad4695 driver using the typical
> triggered buffer implementation, including adding a soft timestamp
> channel.
> 
> The chip has 4 different modes for doing conversions. The driver is
> using the advanced sequencer mode since that is the only mode that
> allows individual configuration of all aspects each channel (e.g.
> bipolar config currently and oversampling to be added in the future).
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---

Hi David,

Just two nit comments...

Reviewed-by: Nuno Sa <nuno.sa@analog.com>

>  drivers/iio/adc/ad4695.c | 233 ++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 230 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/iio/adc/ad4695.c b/drivers/iio/adc/ad4695.c
> index 007ecb951bc3..a3bd5be36134 100644
> --- a/drivers/iio/adc/ad4695.c
> +++ b/drivers/iio/adc/ad4695.c

...

> 
>  
> +static int ad4695_buffer_preenable(struct iio_dev *indio_dev)
> +{
> +	struct ad4695_state *st = iio_priv(indio_dev);
> +	struct spi_transfer *xfer;
> +	u8 temp_chan_bit = st->chip_info->num_voltage_inputs;
> +	bool temp_chan_en = false;
> +	u32 reg, mask, val, bit, num_xfer, num_slots;
> +	int ret;
> +
> +	/*
> +	 * We are using the advanced sequencer since it is the only way to read
> +	 * multiple channels that allows individual configuration of each
> +	 * voltage input channel. Slot 0 in the advanced sequencer is used to
> +	 * account for the gap between trigger polls - we don't read data from
> +	 * this slot. Each enabled voltage channel is assigned a slot starting
> +	 * with slot 1.
> +	 */
> +	num_slots = 1;
> +
> +	memset(st->buf_read_xfer, 0, sizeof(st->buf_read_xfer));
> +
> +	/* First xfer is only to trigger conversion of slot 1, so no rx. */
> +	xfer = &st->buf_read_xfer[0];
> +	xfer->cs_change = 1;
> +	xfer->delay.value = AD4695_T_CNVL_NS;
> +	xfer->delay.unit = SPI_DELAY_UNIT_NSECS;
> +	xfer->cs_change_delay.value = AD4695_T_CONVERT_NS;
> +	xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
> +	num_xfer = 1;
> +
> +	iio_for_each_active_channel(indio_dev, bit) {
> +		xfer = &st->buf_read_xfer[num_xfer];
> +		xfer->bits_per_word = 16;
> +		xfer->rx_buf = &st->buf[(num_xfer - 1) * 2];
> +		xfer->len = 2;
> +		xfer->cs_change = 1;
> +		xfer->cs_change_delay.value = AD4695_T_CONVERT_NS;
> +		xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
> +
> +		if (bit == temp_chan_bit) {
> +			temp_chan_en = true;
> +		} else {
> +			reg = AD4695_REG_AS_SLOT(num_slots);
> +			val = FIELD_PREP(AD4695_REG_AS_SLOT_INX, bit);
> +
> +			ret = regmap_write(st->regmap, reg, val);
> +			if (ret)
> +				return ret;
> +
> +			num_slots++;
> +		}
> +
> +		num_xfer++;
> +	}
> +
> +	/*
> +	 * Don't keep CS asserted after last xfer. Also triggers conversion of
> +	 * slot 0.
> +	 */
> +	xfer->cs_change = 0;
> +
> +	/**
> +	 * The advanced sequencer requires that at least 2 slots are enabled.
> +	 * Since slot 0 is always used for other purposes, we need only 1
> +	 * enabled voltage channel to meet this requirement. This error will
> +	 * only happen if only the temperature channel is enabled.
> +	 */
> +	if (num_slots < 2) {
> +		dev_err_ratelimited(&indio_dev->dev,
> +			"Buffered read requires at least 1 voltage channel
> enabled\n");

This one is intriguing... Why the ratelimited variant? Normally you'd use that in IRQ
routines where the log could be flooded.
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * Temperature channel isn't included in the sequence, but rather
> +	 * controlled by setting a bit in the TEMP_CTRL register.
> +	 */
> +
> +	reg = AD4695_REG_TEMP_CTRL;
> +	mask = AD4695_REG_TEMP_CTRL_TEMP_EN;
> +	val = FIELD_PREP(mask, temp_chan_en ? 1 : 0);
> +
> +	ret = regmap_update_bits(st->regmap, reg, mask, val);
> +	if (ret)
> +		return ret;
> +
> +	spi_message_init_with_transfers(&st->buf_read_msg, st->buf_read_xfer,
> +					num_xfer);
> +
> +	ret = spi_optimize_message(st->spi, &st->buf_read_msg);
> +	if (ret)
> +		return ret;
> +
> +	/* This triggers conversion of slot 0. */
> +	ret = ad4695_enter_advanced_sequencer_mode(st, num_slots);
> +	if (ret) {
> +		spi_unoptimize_message(&st->buf_read_msg);
> +		return ret;
> +	}

Could save one line with (unless ad4695_enter_advanced_sequencer_mode() does not
return 0 on success)

ret = ad4695_enter_advanced_sequencer_mode(st, num_slots);
if (ret)
	spi_unoptimize_message(&st->buf_read_msg);

return ret;

- Nuno Sá
David Lechner Aug. 9, 2024, 4:01 p.m. UTC | #3
On 8/9/24 9:24 AM, Nuno Sá wrote:
> On Wed, 2024-08-07 at 15:02 -0500, David Lechner wrote:
>> This implements buffered reads for the ad4695 driver using the typical
>> triggered buffer implementation, including adding a soft timestamp
>> channel.
>>
>> The chip has 4 different modes for doing conversions. The driver is
>> using the advanced sequencer mode since that is the only mode that
>> allows individual configuration of all aspects each channel (e.g.
>> bipolar config currently and oversampling to be added in the future).
>>
>> Signed-off-by: David Lechner <dlechner@baylibre.com>
>> ---
> 
> Hi David,
> 
> Just two nit comments...
> 
> Reviewed-by: Nuno Sa <nuno.sa@analog.com>
> 
>>  drivers/iio/adc/ad4695.c | 233 ++++++++++++++++++++++++++++++++++++++++++++++-
>>  1 file changed, 230 insertions(+), 3 deletions(-)
>>
>> diff --git a/drivers/iio/adc/ad4695.c b/drivers/iio/adc/ad4695.c
>> index 007ecb951bc3..a3bd5be36134 100644
>> --- a/drivers/iio/adc/ad4695.c
>> +++ b/drivers/iio/adc/ad4695.c
> 
> ...
> 
>>
>>  
>> +static int ad4695_buffer_preenable(struct iio_dev *indio_dev)
>> +{
>> +	struct ad4695_state *st = iio_priv(indio_dev);
>> +	struct spi_transfer *xfer;
>> +	u8 temp_chan_bit = st->chip_info->num_voltage_inputs;
>> +	bool temp_chan_en = false;
>> +	u32 reg, mask, val, bit, num_xfer, num_slots;
>> +	int ret;
>> +
>> +	/*
>> +	 * We are using the advanced sequencer since it is the only way to read
>> +	 * multiple channels that allows individual configuration of each
>> +	 * voltage input channel. Slot 0 in the advanced sequencer is used to
>> +	 * account for the gap between trigger polls - we don't read data from
>> +	 * this slot. Each enabled voltage channel is assigned a slot starting
>> +	 * with slot 1.
>> +	 */
>> +	num_slots = 1;
>> +
>> +	memset(st->buf_read_xfer, 0, sizeof(st->buf_read_xfer));
>> +
>> +	/* First xfer is only to trigger conversion of slot 1, so no rx. */
>> +	xfer = &st->buf_read_xfer[0];
>> +	xfer->cs_change = 1;
>> +	xfer->delay.value = AD4695_T_CNVL_NS;
>> +	xfer->delay.unit = SPI_DELAY_UNIT_NSECS;
>> +	xfer->cs_change_delay.value = AD4695_T_CONVERT_NS;
>> +	xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
>> +	num_xfer = 1;
>> +
>> +	iio_for_each_active_channel(indio_dev, bit) {
>> +		xfer = &st->buf_read_xfer[num_xfer];
>> +		xfer->bits_per_word = 16;
>> +		xfer->rx_buf = &st->buf[(num_xfer - 1) * 2];
>> +		xfer->len = 2;
>> +		xfer->cs_change = 1;
>> +		xfer->cs_change_delay.value = AD4695_T_CONVERT_NS;
>> +		xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
>> +
>> +		if (bit == temp_chan_bit) {
>> +			temp_chan_en = true;
>> +		} else {
>> +			reg = AD4695_REG_AS_SLOT(num_slots);
>> +			val = FIELD_PREP(AD4695_REG_AS_SLOT_INX, bit);
>> +
>> +			ret = regmap_write(st->regmap, reg, val);
>> +			if (ret)
>> +				return ret;
>> +
>> +			num_slots++;
>> +		}
>> +
>> +		num_xfer++;
>> +	}
>> +
>> +	/*
>> +	 * Don't keep CS asserted after last xfer. Also triggers conversion of
>> +	 * slot 0.
>> +	 */
>> +	xfer->cs_change = 0;
>> +
>> +	/**
>> +	 * The advanced sequencer requires that at least 2 slots are enabled.
>> +	 * Since slot 0 is always used for other purposes, we need only 1
>> +	 * enabled voltage channel to meet this requirement. This error will
>> +	 * only happen if only the temperature channel is enabled.
>> +	 */
>> +	if (num_slots < 2) {
>> +		dev_err_ratelimited(&indio_dev->dev,
>> +			"Buffered read requires at least 1 voltage channel
>> enabled\n");
> 
> This one is intriguing... Why the ratelimited variant? Normally you'd use that in IRQ
> routines where the log could be flooded.

IIO Oscilloscope does a lot of retries of buffered reads very quickly,
so was getting a minor flood (10-20 repeats). I'm not sure that
ratelimited actually helped in this case though.

I suppose we could just drop this and expect people to read the docs
if they get an EINVAL when attempting to enable the buffer. Or just
make it dev_err() since it isn't 100s of repeats.

>> +		return -EINVAL;
>> +	}
>> +
>> +	/*
>> +	 * Temperature channel isn't included in the sequence, but rather
>> +	 * controlled by setting a bit in the TEMP_CTRL register.
>> +	 */
>> +
>> +	reg = AD4695_REG_TEMP_CTRL;
>> +	mask = AD4695_REG_TEMP_CTRL_TEMP_EN;
>> +	val = FIELD_PREP(mask, temp_chan_en ? 1 : 0);
>> +
>> +	ret = regmap_update_bits(st->regmap, reg, mask, val);
>> +	if (ret)
>> +		return ret;
>> +
>> +	spi_message_init_with_transfers(&st->buf_read_msg, st->buf_read_xfer,
>> +					num_xfer);
>> +
>> +	ret = spi_optimize_message(st->spi, &st->buf_read_msg);
>> +	if (ret)
>> +		return ret;
>> +
>> +	/* This triggers conversion of slot 0. */
>> +	ret = ad4695_enter_advanced_sequencer_mode(st, num_slots);
>> +	if (ret) {
>> +		spi_unoptimize_message(&st->buf_read_msg);
>> +		return ret;
>> +	}
> 
> Could save one line with (unless ad4695_enter_advanced_sequencer_mode() does not
> return 0 on success)

sure

> 
> ret = ad4695_enter_advanced_sequencer_mode(st, num_slots);
> if (ret)
> 	spi_unoptimize_message(&st->buf_read_msg);
> 
> return ret;
> 
> - Nuno Sá
>
Jonathan Cameron Aug. 10, 2024, 9:20 a.m. UTC | #4
On Thu, 8 Aug 2024 16:52:09 +0800
kernel test robot <lkp@intel.com> wrote:

> Hi David,
> 
> kernel test robot noticed the following build warnings:
> 
> [auto build test WARNING on 7cad163c39cb642ed587d3eeb37a5637ee02740f]
> 
> url:    https://github.com/intel-lab-lkp/linux/commits/David-Lechner/iio-adc-ad4695-implement-triggered-buffer/20240808-063333
> base:   7cad163c39cb642ed587d3eeb37a5637ee02740f
> patch link:    https://lore.kernel.org/r/20240807-iio-adc-ad4695-buffered-read-v1-1-bdafc39b2283%40baylibre.com
> patch subject: [PATCH 1/2] iio: adc: ad4695: implement triggered buffer
> config: hexagon-allmodconfig (https://download.01.org/0day-ci/archive/20240808/202408081623.ua9EBfoZ-lkp@intel.com/config)
> compiler: clang version 20.0.0git (https://github.com/llvm/llvm-project 408d82d352eb98e2d0a804c66d359cd7a49228fe)
> reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240808/202408081623.ua9EBfoZ-lkp@intel.com/reproduce)
> 
> If you fix the issue in a separate patch/commit (i.e. not just a new version of
> the same patch/commit), kindly add following tags
> | Reported-by: kernel test robot <lkp@intel.com>
> | Closes: https://lore.kernel.org/oe-kbuild-all/202408081623.ua9EBfoZ-lkp@intel.com/
> 
> All warnings (new ones prefixed by >>):
> 
>    In file included from drivers/iio/adc/ad4695.c:24:
>    In file included from include/linux/iio/triggered_buffer.h:6:
>    In file included from include/linux/interrupt.h:11:
>    In file included from include/linux/hardirq.h:11:
>    In file included from ./arch/hexagon/include/generated/asm/hardirq.h:1:
>    In file included from include/asm-generic/hardirq.h:17:
>    In file included from include/linux/irq.h:20:
>    In file included from include/linux/io.h:14:
>    In file included from arch/hexagon/include/asm/io.h:328:
>    include/asm-generic/io.h:548:31: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
>      548 |         val = __raw_readb(PCI_IOBASE + addr);
>          |                           ~~~~~~~~~~ ^
>    include/asm-generic/io.h:561:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
>      561 |         val = __le16_to_cpu((__le16 __force)__raw_readw(PCI_IOBASE + addr));
>          |                                                         ~~~~~~~~~~ ^
>    include/uapi/linux/byteorder/little_endian.h:37:51: note: expanded from macro '__le16_to_cpu'
>       37 | #define __le16_to_cpu(x) ((__force __u16)(__le16)(x))
>          |                                                   ^
>    In file included from drivers/iio/adc/ad4695.c:24:
>    In file included from include/linux/iio/triggered_buffer.h:6:
>    In file included from include/linux/interrupt.h:11:
>    In file included from include/linux/hardirq.h:11:
>    In file included from ./arch/hexagon/include/generated/asm/hardirq.h:1:
>    In file included from include/asm-generic/hardirq.h:17:
>    In file included from include/linux/irq.h:20:
>    In file included from include/linux/io.h:14:
>    In file included from arch/hexagon/include/asm/io.h:328:
>    include/asm-generic/io.h:574:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
>      574 |         val = __le32_to_cpu((__le32 __force)__raw_readl(PCI_IOBASE + addr));
>          |                                                         ~~~~~~~~~~ ^
>    include/uapi/linux/byteorder/little_endian.h:35:51: note: expanded from macro '__le32_to_cpu'
>       35 | #define __le32_to_cpu(x) ((__force __u32)(__le32)(x))
>          |                                                   ^
>    In file included from drivers/iio/adc/ad4695.c:24:
>    In file included from include/linux/iio/triggered_buffer.h:6:
>    In file included from include/linux/interrupt.h:11:
>    In file included from include/linux/hardirq.h:11:
>    In file included from ./arch/hexagon/include/generated/asm/hardirq.h:1:
>    In file included from include/asm-generic/hardirq.h:17:
>    In file included from include/linux/irq.h:20:
>    In file included from include/linux/io.h:14:
>    In file included from arch/hexagon/include/asm/io.h:328:
>    include/asm-generic/io.h:585:33: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
>      585 |         __raw_writeb(value, PCI_IOBASE + addr);
>          |                             ~~~~~~~~~~ ^
>    include/asm-generic/io.h:595:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
>      595 |         __raw_writew((u16 __force)cpu_to_le16(value), PCI_IOBASE + addr);
>          |                                                       ~~~~~~~~~~ ^
>    include/asm-generic/io.h:605:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
>      605 |         __raw_writel((u32 __force)cpu_to_le32(value), PCI_IOBASE + addr);
>          |                                                       ~~~~~~~~~~ ^
>    In file included from drivers/iio/adc/ad4695.c:28:
>    In file included from include/linux/regulator/consumer.h:35:
>    In file included from include/linux/suspend.h:5:
>    In file included from include/linux/swap.h:9:
>    In file included from include/linux/memcontrol.h:21:
>    In file included from include/linux/mm.h:2228:
>    include/linux/vmstat.h:514:36: warning: arithmetic between different enumeration types ('enum node_stat_item' and 'enum lru_list') [-Wenum-enum-conversion]
>      514 |         return node_stat_name(NR_LRU_BASE + lru) + 3; // skip "nr_"
>          |                               ~~~~~~~~~~~ ^ ~~~
> >> drivers/iio/adc/ad4695.c:454:8: warning: result of comparison of constant 18446744073709551615 with expression of type 'typeof (_Generic((mask), char: (unsigned char)0, unsigned char: (unsigned char)0, signed char: (unsigned char)0, unsigned short: (unsigned short)0, short: (unsigned short)0, unsigned int: (unsigned int)0, int: (unsigned int)0, unsigned long: (unsigned long)0, long: (unsigned long)0, unsigned long long: (unsigned long long)0, long long: (unsigned long long)0, default: (mask)))' (aka 'unsigned int') is always false [-Wtautological-constant-out-of-range-compare]  
>      454 |         val = FIELD_PREP(mask, temp_chan_en ? 1 : 0);
>          |               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>    include/linux/bitfield.h:115:3: note: expanded from macro 'FIELD_PREP'
>      115 |                 __BF_FIELD_CHECK(_mask, 0ULL, _val, "FIELD_PREP: ");    \
>          |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>    include/linux/bitfield.h:72:53: note: expanded from macro '__BF_FIELD_CHECK'
>       72 |                 BUILD_BUG_ON_MSG(__bf_cast_unsigned(_mask, _mask) >     \
>          |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
>       73 |                                  __bf_cast_unsigned(_reg, ~0ull),       \
>          |                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>       74 |                                  _pfx "type of reg too small for mask"); \
>          |                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>    include/linux/build_bug.h:39:58: note: expanded from macro 'BUILD_BUG_ON_MSG'
>       39 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
>          |                                     ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~
>    include/linux/compiler_types.h:510:22: note: expanded from macro 'compiletime_assert'
>      510 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
>          |         ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>    include/linux/compiler_types.h:498:23: note: expanded from macro '_compiletime_assert'
>      498 |         __compiletime_assert(condition, msg, prefix, suffix)
>          |         ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>    include/linux/compiler_types.h:490:9: note: expanded from macro '__compiletime_assert'
>      490 |                 if (!(condition))                                       \
>          |                       ^~~~~~~~~
>    8 warnings generated.
> 
> 
> vim +454 drivers/iio/adc/ad4695.c
> 
>    373	
>    374	static int ad4695_buffer_preenable(struct iio_dev *indio_dev)
>    375	{
>    376		struct ad4695_state *st = iio_priv(indio_dev);
>    377		struct spi_transfer *xfer;
>    378		u8 temp_chan_bit = st->chip_info->num_voltage_inputs;
>    379		bool temp_chan_en = false;
>    380		u32 reg, mask, val, bit, num_xfer, num_slots;
>    381		int ret;
>    382	
>    383		/*
>    384		 * We are using the advanced sequencer since it is the only way to read
>    385		 * multiple channels that allows individual configuration of each
>    386		 * voltage input channel. Slot 0 in the advanced sequencer is used to
>    387		 * account for the gap between trigger polls - we don't read data from
>    388		 * this slot. Each enabled voltage channel is assigned a slot starting
>    389		 * with slot 1.
>    390		 */
>    391		num_slots = 1;
>    392	
>    393		memset(st->buf_read_xfer, 0, sizeof(st->buf_read_xfer));
>    394	
>    395		/* First xfer is only to trigger conversion of slot 1, so no rx. */
>    396		xfer = &st->buf_read_xfer[0];
>    397		xfer->cs_change = 1;
>    398		xfer->delay.value = AD4695_T_CNVL_NS;
>    399		xfer->delay.unit = SPI_DELAY_UNIT_NSECS;
>    400		xfer->cs_change_delay.value = AD4695_T_CONVERT_NS;
>    401		xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
>    402		num_xfer = 1;
>    403	
>    404		iio_for_each_active_channel(indio_dev, bit) {
>    405			xfer = &st->buf_read_xfer[num_xfer];
>    406			xfer->bits_per_word = 16;
>    407			xfer->rx_buf = &st->buf[(num_xfer - 1) * 2];
>    408			xfer->len = 2;
>    409			xfer->cs_change = 1;
>    410			xfer->cs_change_delay.value = AD4695_T_CONVERT_NS;
>    411			xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
>    412	
>    413			if (bit == temp_chan_bit) {
>    414				temp_chan_en = true;
>    415			} else {
>    416				reg = AD4695_REG_AS_SLOT(num_slots);
>    417				val = FIELD_PREP(AD4695_REG_AS_SLOT_INX, bit);
>    418	
>    419				ret = regmap_write(st->regmap, reg, val);
>    420				if (ret)
>    421					return ret;
>    422	
>    423				num_slots++;
>    424			}
>    425	
>    426			num_xfer++;
>    427		}
>    428	
>    429		/*
>    430		 * Don't keep CS asserted after last xfer. Also triggers conversion of
>    431		 * slot 0.
>    432		 */
>    433		xfer->cs_change = 0;
>    434	
>    435		/**
>    436		 * The advanced sequencer requires that at least 2 slots are enabled.
>    437		 * Since slot 0 is always used for other purposes, we need only 1
>    438		 * enabled voltage channel to meet this requirement. This error will
>    439		 * only happen if only the temperature channel is enabled.
>    440		 */
>    441		if (num_slots < 2) {
>    442			dev_err_ratelimited(&indio_dev->dev,
>    443				"Buffered read requires at least 1 voltage channel enabled\n");
>    444			return -EINVAL;
>    445		}
>    446	
>    447		/*
>    448		 * Temperature channel isn't included in the sequence, but rather
>    449		 * controlled by setting a bit in the TEMP_CTRL register.
>    450		 */
>    451	
>    452		reg = AD4695_REG_TEMP_CTRL;
>    453		mask = AD4695_REG_TEMP_CTRL_TEMP_EN;
>  > 454		val = FIELD_PREP(mask, temp_chan_en ? 1 : 0);
  
Probably a case of the compiler somehow failing to squash the local variable.
I'd just go with
			val = FIELD_PREP(AD4695_REG_TEMP_CTRL_TEMP_EN,
					 temp_chan_en ? 1 : 0);
>    455	
>    456		ret = regmap_update_bits(st->regmap, reg, mask, val);
>    457		if (ret)
>    458			return ret;
>    459	
>    460		spi_message_init_with_transfers(&st->buf_read_msg, st->buf_read_xfer,
>    461						num_xfer);
>    462	
>    463		ret = spi_optimize_message(st->spi, &st->buf_read_msg);
>    464		if (ret)
>    465			return ret;
>    466	
>    467		/* This triggers conversion of slot 0. */
>    468		ret = ad4695_enter_advanced_sequencer_mode(st, num_slots);
>    469		if (ret) {
>    470			spi_unoptimize_message(&st->buf_read_msg);
>    471			return ret;
>    472		}
>    473	
>    474		return 0;
>    475	}
>    476	
>
Jonathan Cameron Aug. 10, 2024, 9:35 a.m. UTC | #5
On Wed,  7 Aug 2024 15:02:10 -0500
David Lechner <dlechner@baylibre.com> wrote:

> This implements buffered reads for the ad4695 driver using the typical
> triggered buffer implementation, including adding a soft timestamp
> channel.
> 
> The chip has 4 different modes for doing conversions. The driver is
> using the advanced sequencer mode since that is the only mode that
> allows individual configuration of all aspects each channel (e.g.
> bipolar config currently and oversampling to be added in the future).
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>

Main thing in here is I think you can use available_scan_masks
to avoid the need for the error path on just the temperature channel
being enabled.

Jonathan

> +/**
> + * ad4695_enter_advanced_sequencer_mode - Put the ADC in advanced sequencer mode
> + * @st: The driver state
> + * @n: The number of slots to use - must be >= 2, <= 128
> + *
> + * As per the datasheet, to enable advanced sequencer, we need to set
> + * STD_SEQ_EN=0, NUM_SLOTS_AS=n-1 and CYC_CTRL=0 (Table 15). Setting SPI_MODE=1
> + * triggers the first conversion using the channel in AS_SLOT0.
> + *
> + * Return: 0 on success, a negative error code on failure
> + */
> +static int ad4695_enter_advanced_sequencer_mode(struct ad4695_state *st, u32 n)
> +{
> +	u32 mask, val;
> +	int ret;
> +
> +	mask = AD4695_REG_SEQ_CTRL_STD_SEQ_EN;
> +	val = FIELD_PREP(AD4695_REG_SEQ_CTRL_STD_SEQ_EN, 0);
> +
> +	mask |= AD4695_REG_SEQ_CTRL_NUM_SLOTS_AS;
> +	val |= FIELD_PREP(AD4695_REG_SEQ_CTRL_NUM_SLOTS_AS, n - 1);
> +
> +	ret = regmap_update_bits(st->regmap, AD4695_REG_SEQ_CTRL, mask, val);
> +	if (ret)
> +		return ret;
> +
> +	mask = AD4695_REG_SETUP_SPI_MODE;
> +	val = FIELD_PREP(AD4695_REG_SETUP_SPI_MODE, 1);
> +
> +	mask |= AD4695_REG_SETUP_SPI_CYC_CTRL;
> +	val |= FIELD_PREP(AD4695_REG_SETUP_SPI_CYC_CTRL, 0);

I'd just combine the two parts of mask and val.  If it were a long
complex list then fair enough to keep them as individual parts, but
not needed for 2 items.

> +
> +	return regmap_update_bits(st->regmap, AD4695_REG_SETUP, mask, val);
> +}
> +
> +/**
> + * ad4695_exit_conversion_mode - Exit conversion mode
> + * @st: The AD4695 state
> + *
> + * Sends SPI command to exit conversion mode.
> + *
> + * Return: 0 on success, a negative error code on failure
> + */
> +static int ad4695_exit_conversion_mode(struct ad4695_state *st)
> +{
> +	struct spi_transfer xfer = { };
	struct spi_transfer xfer = {
		.tx_buf = &st->cnv_cmd2,
		.len = 1,
		.delay.value ...

	};

Might as well fill it in from the start.
Doesn't matter that the data is filled in just after this even if
that doesn't feel quite right, the code is so close I don't think
it will confuse readers and it's a common pattern.

> +
> +	st->cnv_cmd2 = AD4695_CMD_EXIT_CNV_MODE << 3;
> +	xfer.tx_buf = &st->cnv_cmd2;
> +	xfer.len = 1;
> +	xfer.delay.value = AD4695_T_REGCONFIG_NS;
> +	xfer.delay.unit = SPI_DELAY_UNIT_NSECS;
> +
> +	return spi_sync_transfer(st->spi, &xfer, 1);
> +}
> +
>  static int ad4695_set_ref_voltage(struct ad4695_state *st, int vref_mv)
>  {
>  	u8 val;
> @@ -296,6 +371,147 @@ static int ad4695_write_chn_cfg(struct ad4695_state *st,
>  				  mask, val);
>  }
>  
> +static int ad4695_buffer_preenable(struct iio_dev *indio_dev)
> +{
> +	struct ad4695_state *st = iio_priv(indio_dev);
> +	struct spi_transfer *xfer;
> +	u8 temp_chan_bit = st->chip_info->num_voltage_inputs;
> +	bool temp_chan_en = false;
> +	u32 reg, mask, val, bit, num_xfer, num_slots;
> +	int ret;
> +
> +	/*
> +	 * We are using the advanced sequencer since it is the only way to read
> +	 * multiple channels that allows individual configuration of each
> +	 * voltage input channel. Slot 0 in the advanced sequencer is used to
> +	 * account for the gap between trigger polls - we don't read data from
> +	 * this slot. Each enabled voltage channel is assigned a slot starting
> +	 * with slot 1.
> +	 */
> +	num_slots = 1;
> +
> +	memset(st->buf_read_xfer, 0, sizeof(st->buf_read_xfer));
> +
> +	/* First xfer is only to trigger conversion of slot 1, so no rx. */
> +	xfer = &st->buf_read_xfer[0];
> +	xfer->cs_change = 1;
> +	xfer->delay.value = AD4695_T_CNVL_NS;
> +	xfer->delay.unit = SPI_DELAY_UNIT_NSECS;
> +	xfer->cs_change_delay.value = AD4695_T_CONVERT_NS;
> +	xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
> +	num_xfer = 1;
> +
> +	iio_for_each_active_channel(indio_dev, bit) {
> +		xfer = &st->buf_read_xfer[num_xfer];
> +		xfer->bits_per_word = 16;
> +		xfer->rx_buf = &st->buf[(num_xfer - 1) * 2];
> +		xfer->len = 2;
> +		xfer->cs_change = 1;
> +		xfer->cs_change_delay.value = AD4695_T_CONVERT_NS;
> +		xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
> +
> +		if (bit == temp_chan_bit) {
> +			temp_chan_en = true;
> +		} else {
> +			reg = AD4695_REG_AS_SLOT(num_slots);
> +			val = FIELD_PREP(AD4695_REG_AS_SLOT_INX, bit);
> +
> +			ret = regmap_write(st->regmap, reg, val);
> +			if (ret)
> +				return ret;
> +
> +			num_slots++;
> +		}
> +
> +		num_xfer++;
> +	}
> +
> +	/*
> +	 * Don't keep CS asserted after last xfer. Also triggers conversion of
> +	 * slot 0.
> +	 */
> +	xfer->cs_change = 0;
> +
> +	/**
> +	 * The advanced sequencer requires that at least 2 slots are enabled.
> +	 * Since slot 0 is always used for other purposes, we need only 1
> +	 * enabled voltage channel to meet this requirement. This error will
> +	 * only happen if only the temperature channel is enabled.
> +	 */
> +	if (num_slots < 2) {

Can you use available_scanmasks to let the IIO core figure out it needs
to enable (and then hide) an extra channel?

Either that or spin up a channel to meet the requirement, just don't
capture it - Given this is an unlikely case, better to leave it to the
IIO core buffer demux handling than to bother handling locally.


> +		dev_err_ratelimited(&indio_dev->dev,
> +			"Buffered read requires at least 1 voltage channel enabled\n");
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * Temperature channel isn't included in the sequence, but rather
> +	 * controlled by setting a bit in the TEMP_CTRL register.
> +	 */
> +
> +	reg = AD4695_REG_TEMP_CTRL;
> +	mask = AD4695_REG_TEMP_CTRL_TEMP_EN;
> +	val = FIELD_PREP(mask, temp_chan_en ? 1 : 0);
This is the line the bot didn't like. The local variables reg and
mask don't add anything anyway, so get rid of them and use the
values inline.

> +
> +	ret = regmap_update_bits(st->regmap, reg, mask, val);
> +	if (ret)
> +		return ret;
> +
> +	spi_message_init_with_transfers(&st->buf_read_msg, st->buf_read_xfer,
> +					num_xfer);
> +
> +	ret = spi_optimize_message(st->spi, &st->buf_read_msg);
> +	if (ret)
> +		return ret;
> +
> +	/* This triggers conversion of slot 0. */
> +	ret = ad4695_enter_advanced_sequencer_mode(st, num_slots);
> +	if (ret) {
> +		spi_unoptimize_message(&st->buf_read_msg);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
David Lechner Aug. 12, 2024, 5:03 p.m. UTC | #6
On 8/10/24 4:35 AM, Jonathan Cameron wrote:
> On Wed,  7 Aug 2024 15:02:10 -0500
> David Lechner <dlechner@baylibre.com> wrote:
> 
>> This implements buffered reads for the ad4695 driver using the typical
>> triggered buffer implementation, including adding a soft timestamp
>> channel.
>>
>> The chip has 4 different modes for doing conversions. The driver is
>> using the advanced sequencer mode since that is the only mode that
>> allows individual configuration of all aspects each channel (e.g.
>> bipolar config currently and oversampling to be added in the future).
>>
>> Signed-off-by: David Lechner <dlechner@baylibre.com>
> 
> Main thing in here is I think you can use available_scan_masks
> to avoid the need for the error path on just the temperature channel
> being enabled.
> 
I had not thought about doing it that way, but now that I am
thinking about it, it seems like we would need to have a scan
mask in the list for every possible combination of channels.
This would be 10s of thousands of possible scan masks for 16
channel chips so that doesn't seem like the best way to go.

But adding some special handling to make the temperature
channel just work should be easy enough to add.
Nuno Sá Aug. 13, 2024, 7:28 a.m. UTC | #7
On Mon, 2024-08-12 at 12:03 -0500, David Lechner wrote:
> On 8/10/24 4:35 AM, Jonathan Cameron wrote:
> > On Wed,  7 Aug 2024 15:02:10 -0500
> > David Lechner <dlechner@baylibre.com> wrote:
> > 
> > > This implements buffered reads for the ad4695 driver using the typical
> > > triggered buffer implementation, including adding a soft timestamp
> > > channel.
> > > 
> > > The chip has 4 different modes for doing conversions. The driver is
> > > using the advanced sequencer mode since that is the only mode that
> > > allows individual configuration of all aspects each channel (e.g.
> > > bipolar config currently and oversampling to be added in the future).
> > > 
> > > Signed-off-by: David Lechner <dlechner@baylibre.com>
> > 
> > Main thing in here is I think you can use available_scan_masks
> > to avoid the need for the error path on just the temperature channel
> > being enabled.
> > 
> I had not thought about doing it that way, but now that I am
> thinking about it, it seems like we would need to have a scan
> mask in the list for every possible combination of channels.
> This would be 10s of thousands of possible scan masks for 16
> channel chips so that doesn't seem like the best way to go.
> 
> But adding some special handling to make the temperature
> channel just work should be easy enough to add.
> 

Not sure if the following is meaningful to this usecase but I used to think like you
but then realized that iio_scan_mask_match() will do bitmap_subset(). So you only
need to enable a subset of the available scan mask for things to work (and with that
you should no longer need an insane number of combinations). The core will then take
care of demuxing the actual enabled channels. AFAIR, strict scan matching is only
used for HW buffering.

- Nuno Sá
Jonathan Cameron Aug. 14, 2024, 6:56 p.m. UTC | #8
On Tue, 13 Aug 2024 09:28:06 +0200
Nuno Sá <noname.nuno@gmail.com> wrote:

> On Mon, 2024-08-12 at 12:03 -0500, David Lechner wrote:
> > On 8/10/24 4:35 AM, Jonathan Cameron wrote:  
> > > On Wed,  7 Aug 2024 15:02:10 -0500
> > > David Lechner <dlechner@baylibre.com> wrote:
> > >   
> > > > This implements buffered reads for the ad4695 driver using the typical
> > > > triggered buffer implementation, including adding a soft timestamp
> > > > channel.
> > > > 
> > > > The chip has 4 different modes for doing conversions. The driver is
> > > > using the advanced sequencer mode since that is the only mode that
> > > > allows individual configuration of all aspects each channel (e.g.
> > > > bipolar config currently and oversampling to be added in the future).
> > > > 
> > > > Signed-off-by: David Lechner <dlechner@baylibre.com>  
> > > 
> > > Main thing in here is I think you can use available_scan_masks
> > > to avoid the need for the error path on just the temperature channel
> > > being enabled.
> > >   
> > I had not thought about doing it that way, but now that I am
> > thinking about it, it seems like we would need to have a scan
> > mask in the list for every possible combination of channels.
> > This would be 10s of thousands of possible scan masks for 16
> > channel chips so that doesn't seem like the best way to go.

Indeed not my best suggestion.

> > 
> > But adding some special handling to make the temperature
> > channel just work should be easy enough to add.
> >   
> 
> Not sure if the following is meaningful to this usecase but I used to think like you
> but then realized that iio_scan_mask_match() will do bitmap_subset(). So you only
> need to enable a subset of the available scan mask for things to work (and with that
> you should no longer need an insane number of combinations). The core will then take
> care of demuxing the actual enabled channels. AFAIR, strict scan matching is only
> used for HW buffering.

Hmm. The validate_scan_mask callback also doesn't work as that's really about
restricting cases where we are onehot or similar (so restricting number of channels
or that sort of thing).

So, I think this is a driver problem to hide it.

Just have some logic in the driver that enables a dummy channel if only the
temperature is enabled and throw it away (probably fine to put it in the
data passed to iio_push_to_buffers() and rely on the it either being
treated as garbage, or dropped depending on whether it is in a hole, or
on the end of the scan.  That should give the intuitive interface
we expect (no restrictions to bite us that don't have to be there - see onehot
case where we have no choice) without adding too much complexity.

Jonathan

> 
> - Nuno Sá
diff mbox series

Patch

diff --git a/drivers/iio/adc/ad4695.c b/drivers/iio/adc/ad4695.c
index 007ecb951bc3..a3bd5be36134 100644
--- a/drivers/iio/adc/ad4695.c
+++ b/drivers/iio/adc/ad4695.c
@@ -11,6 +11,7 @@ 
  * Copyright 2024 BayLibre, SAS
  */
 
+#include <linux/align.h>
 #include <linux/bitfield.h>
 #include <linux/bits.h>
 #include <linux/compiler.h>
@@ -18,7 +19,10 @@ 
 #include <linux/device.h>
 #include <linux/err.h>
 #include <linux/gpio/consumer.h>
+#include <linux/iio/buffer.h>
 #include <linux/iio/iio.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
 #include <linux/property.h>
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
@@ -61,6 +65,7 @@ 
 #define AD4695_REG_GPIO_CTRL				0x0026
 #define AD4695_REG_GP_MODE				0x0027
 #define AD4695_REG_TEMP_CTRL				0x0029
+#define   AD4695_REG_TEMP_CTRL_TEMP_EN			  BIT(0)
 #define AD4695_REG_CONFIG_IN(n)				(0x0030 | (n))
 #define   AD4695_REG_CONFIG_IN_MODE			  BIT(6)
 #define   AD4695_REG_CONFIG_IN_PAIR			  GENMASK(5, 4)
@@ -80,13 +85,18 @@ 
 #define AD4695_CMD_VOLTAGE_CHAN(n)	(0x10 | (n))
 
 /* timing specs */
+#define AD4695_T_CNVL_NS		80
 #define AD4695_T_CONVERT_NS		415
 #define AD4695_T_WAKEUP_HW_MS		3
 #define AD4695_T_WAKEUP_SW_MS		3
 #define AD4695_T_REFBUF_MS		100
+#define AD4695_T_REGCONFIG_NS		20
 #define AD4695_REG_ACCESS_SCLK_HZ	(10 * MEGA)
 
+/* Max number of voltage input channels. */
 #define AD4695_MAX_CHANNELS		16
+/* Max size of 1 raw sample in bytes. */
+#define AD4695_MAX_CHANNEL_SIZE		2
 
 enum ad4695_in_pair {
 	AD4695_IN_PAIR_REFGND,
@@ -112,15 +122,21 @@  struct ad4695_state {
 	struct spi_device *spi;
 	struct regmap *regmap;
 	struct gpio_desc *reset_gpio;
-	struct iio_chan_spec iio_chan[AD4695_MAX_CHANNELS + 1];
+	/* voltages channels plus temperature and timestamp */
+	struct iio_chan_spec iio_chan[AD4695_MAX_CHANNELS + 2];
 	struct ad4695_channel_config channels_cfg[AD4695_MAX_CHANNELS];
 	const struct ad4695_chip_info *chip_info;
 	/* Reference voltage. */
 	unsigned int vref_mv;
 	/* Common mode input pin voltage. */
 	unsigned int com_mv;
+	/* 1 per voltage and temperature chan plus 1 xfer to trigger 1st CNV */
+	struct spi_transfer buf_read_xfer[AD4695_MAX_CHANNELS + 2];
+	struct spi_message buf_read_msg;
 	/* Raw conversion data received. */
-	u16 raw_data __aligned(IIO_DMA_MINALIGN);
+	u8 buf[ALIGN((AD4695_MAX_CHANNELS + 2) * AD4695_MAX_CHANNEL_SIZE,
+		     sizeof(s64)) + sizeof(s64)] __aligned(IIO_DMA_MINALIGN);
+	u16 raw_data;
 	/* Commands to send for single conversion. */
 	u16 cnv_cmd;
 	u8 cnv_cmd2;
@@ -193,6 +209,9 @@  static const struct iio_chan_spec ad4695_temp_channel_template = {
 	},
 };
 
+static const struct iio_chan_spec ad4695_soft_timestamp_channel_template =
+	IIO_CHAN_SOFT_TIMESTAMP(0);
+
 static const char * const ad4695_power_supplies[] = {
 	"avdd", "vio"
 };
@@ -254,6 +273,62 @@  static int ad4695_set_single_cycle_mode(struct ad4695_state *st,
 			       AD4695_REG_SETUP_SPI_CYC_CTRL);
 }
 
+/**
+ * ad4695_enter_advanced_sequencer_mode - Put the ADC in advanced sequencer mode
+ * @st: The driver state
+ * @n: The number of slots to use - must be >= 2, <= 128
+ *
+ * As per the datasheet, to enable advanced sequencer, we need to set
+ * STD_SEQ_EN=0, NUM_SLOTS_AS=n-1 and CYC_CTRL=0 (Table 15). Setting SPI_MODE=1
+ * triggers the first conversion using the channel in AS_SLOT0.
+ *
+ * Return: 0 on success, a negative error code on failure
+ */
+static int ad4695_enter_advanced_sequencer_mode(struct ad4695_state *st, u32 n)
+{
+	u32 mask, val;
+	int ret;
+
+	mask = AD4695_REG_SEQ_CTRL_STD_SEQ_EN;
+	val = FIELD_PREP(AD4695_REG_SEQ_CTRL_STD_SEQ_EN, 0);
+
+	mask |= AD4695_REG_SEQ_CTRL_NUM_SLOTS_AS;
+	val |= FIELD_PREP(AD4695_REG_SEQ_CTRL_NUM_SLOTS_AS, n - 1);
+
+	ret = regmap_update_bits(st->regmap, AD4695_REG_SEQ_CTRL, mask, val);
+	if (ret)
+		return ret;
+
+	mask = AD4695_REG_SETUP_SPI_MODE;
+	val = FIELD_PREP(AD4695_REG_SETUP_SPI_MODE, 1);
+
+	mask |= AD4695_REG_SETUP_SPI_CYC_CTRL;
+	val |= FIELD_PREP(AD4695_REG_SETUP_SPI_CYC_CTRL, 0);
+
+	return regmap_update_bits(st->regmap, AD4695_REG_SETUP, mask, val);
+}
+
+/**
+ * ad4695_exit_conversion_mode - Exit conversion mode
+ * @st: The AD4695 state
+ *
+ * Sends SPI command to exit conversion mode.
+ *
+ * Return: 0 on success, a negative error code on failure
+ */
+static int ad4695_exit_conversion_mode(struct ad4695_state *st)
+{
+	struct spi_transfer xfer = { };
+
+	st->cnv_cmd2 = AD4695_CMD_EXIT_CNV_MODE << 3;
+	xfer.tx_buf = &st->cnv_cmd2;
+	xfer.len = 1;
+	xfer.delay.value = AD4695_T_REGCONFIG_NS;
+	xfer.delay.unit = SPI_DELAY_UNIT_NSECS;
+
+	return spi_sync_transfer(st->spi, &xfer, 1);
+}
+
 static int ad4695_set_ref_voltage(struct ad4695_state *st, int vref_mv)
 {
 	u8 val;
@@ -296,6 +371,147 @@  static int ad4695_write_chn_cfg(struct ad4695_state *st,
 				  mask, val);
 }
 
+static int ad4695_buffer_preenable(struct iio_dev *indio_dev)
+{
+	struct ad4695_state *st = iio_priv(indio_dev);
+	struct spi_transfer *xfer;
+	u8 temp_chan_bit = st->chip_info->num_voltage_inputs;
+	bool temp_chan_en = false;
+	u32 reg, mask, val, bit, num_xfer, num_slots;
+	int ret;
+
+	/*
+	 * We are using the advanced sequencer since it is the only way to read
+	 * multiple channels that allows individual configuration of each
+	 * voltage input channel. Slot 0 in the advanced sequencer is used to
+	 * account for the gap between trigger polls - we don't read data from
+	 * this slot. Each enabled voltage channel is assigned a slot starting
+	 * with slot 1.
+	 */
+	num_slots = 1;
+
+	memset(st->buf_read_xfer, 0, sizeof(st->buf_read_xfer));
+
+	/* First xfer is only to trigger conversion of slot 1, so no rx. */
+	xfer = &st->buf_read_xfer[0];
+	xfer->cs_change = 1;
+	xfer->delay.value = AD4695_T_CNVL_NS;
+	xfer->delay.unit = SPI_DELAY_UNIT_NSECS;
+	xfer->cs_change_delay.value = AD4695_T_CONVERT_NS;
+	xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
+	num_xfer = 1;
+
+	iio_for_each_active_channel(indio_dev, bit) {
+		xfer = &st->buf_read_xfer[num_xfer];
+		xfer->bits_per_word = 16;
+		xfer->rx_buf = &st->buf[(num_xfer - 1) * 2];
+		xfer->len = 2;
+		xfer->cs_change = 1;
+		xfer->cs_change_delay.value = AD4695_T_CONVERT_NS;
+		xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
+
+		if (bit == temp_chan_bit) {
+			temp_chan_en = true;
+		} else {
+			reg = AD4695_REG_AS_SLOT(num_slots);
+			val = FIELD_PREP(AD4695_REG_AS_SLOT_INX, bit);
+
+			ret = regmap_write(st->regmap, reg, val);
+			if (ret)
+				return ret;
+
+			num_slots++;
+		}
+
+		num_xfer++;
+	}
+
+	/*
+	 * Don't keep CS asserted after last xfer. Also triggers conversion of
+	 * slot 0.
+	 */
+	xfer->cs_change = 0;
+
+	/**
+	 * The advanced sequencer requires that at least 2 slots are enabled.
+	 * Since slot 0 is always used for other purposes, we need only 1
+	 * enabled voltage channel to meet this requirement. This error will
+	 * only happen if only the temperature channel is enabled.
+	 */
+	if (num_slots < 2) {
+		dev_err_ratelimited(&indio_dev->dev,
+			"Buffered read requires at least 1 voltage channel enabled\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * Temperature channel isn't included in the sequence, but rather
+	 * controlled by setting a bit in the TEMP_CTRL register.
+	 */
+
+	reg = AD4695_REG_TEMP_CTRL;
+	mask = AD4695_REG_TEMP_CTRL_TEMP_EN;
+	val = FIELD_PREP(mask, temp_chan_en ? 1 : 0);
+
+	ret = regmap_update_bits(st->regmap, reg, mask, val);
+	if (ret)
+		return ret;
+
+	spi_message_init_with_transfers(&st->buf_read_msg, st->buf_read_xfer,
+					num_xfer);
+
+	ret = spi_optimize_message(st->spi, &st->buf_read_msg);
+	if (ret)
+		return ret;
+
+	/* This triggers conversion of slot 0. */
+	ret = ad4695_enter_advanced_sequencer_mode(st, num_slots);
+	if (ret) {
+		spi_unoptimize_message(&st->buf_read_msg);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ad4695_buffer_postdisable(struct iio_dev *indio_dev)
+{
+	struct ad4695_state *st = iio_priv(indio_dev);
+	int ret;
+
+	ret = ad4695_exit_conversion_mode(st);
+	if (ret)
+		return ret;
+
+	spi_unoptimize_message(&st->buf_read_msg);
+
+	return 0;
+}
+
+static const struct iio_buffer_setup_ops ad4695_buffer_setup_ops = {
+	.preenable = ad4695_buffer_preenable,
+	.postdisable = ad4695_buffer_postdisable,
+};
+
+static irqreturn_t ad4695_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct ad4695_state *st = iio_priv(indio_dev);
+	int ret;
+
+	ret = spi_sync(st->spi, &st->buf_read_msg);
+	if (ret)
+		goto out;
+
+	iio_push_to_buffers_with_timestamp(indio_dev, st->buf, pf->timestamp);
+
+out:
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
 /**
  * ad4695_read_one_sample - Read a single sample using single-cycle mode
  * @st: The AD4695 state
@@ -527,6 +743,10 @@  static int ad4695_parse_channel_cfg(struct ad4695_state *st)
 	/* Temperature channel must be next scan index after voltage channels. */
 	st->iio_chan[i] = ad4695_temp_channel_template;
 	st->iio_chan[i].scan_index = i;
+	i++;
+
+	st->iio_chan[i] = ad4695_soft_timestamp_channel_template;
+	st->iio_chan[i].scan_index = i;
 
 	return 0;
 }
@@ -695,7 +915,14 @@  static int ad4695_probe(struct spi_device *spi)
 	indio_dev->info = &ad4695_info;
 	indio_dev->modes = INDIO_DIRECT_MODE;
 	indio_dev->channels = st->iio_chan;
-	indio_dev->num_channels = st->chip_info->num_voltage_inputs + 1;
+	indio_dev->num_channels = st->chip_info->num_voltage_inputs + 2;
+
+	ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+					      iio_pollfunc_store_time,
+					      ad4695_trigger_handler,
+					      &ad4695_buffer_setup_ops);
+	if (ret)
+		return ret;
 
 	return devm_iio_device_register(dev, indio_dev);
 }