lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID:
 <CY4PR03MB3399F8E7AFA00340321BB2A69B182@CY4PR03MB3399.namprd03.prod.outlook.com>
Date: Tue, 14 Jan 2025 12:01:41 +0000
From: "Miclaus, Antoniu" <Antoniu.Miclaus@...log.com>
To: David Lechner <dlechner@...libre.com>,
        "jic23@...nel.org"
	<jic23@...nel.org>,
        "robh@...nel.org" <robh@...nel.org>,
        "conor+dt@...nel.org" <conor+dt@...nel.org>,
        "linux-iio@...r.kernel.org"
	<linux-iio@...r.kernel.org>,
        "devicetree@...r.kernel.org"
	<devicetree@...r.kernel.org>,
        "linux-kernel@...r.kernel.org"
	<linux-kernel@...r.kernel.org>,
        "linux-pwm@...r.kernel.org"
	<linux-pwm@...r.kernel.org>
Subject: RE: [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver


> -----Original Message-----
> From: David Lechner <dlechner@...libre.com>
> Sent: Thursday, January 9, 2025 1:25 AM
> To: Miclaus, Antoniu <Antoniu.Miclaus@...log.com>; jic23@...nel.org;
> robh@...nel.org; conor+dt@...nel.org; linux-iio@...r.kernel.org;
> devicetree@...r.kernel.org; linux-kernel@...r.kernel.org; linux-
> pwm@...r.kernel.org
> Subject: Re: [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver
> 
> [External]
> 
> On 12/20/24 6:01 AM, Antoniu Miclaus wrote:
> > Add support for the AD485X a fully buffered, 8-channel simultaneous
> > sampling, 16/20-bit, 1 MSPS data acquisition system (DAS) with
> > differential, wide common-mode range inputs.
> >
> > Signed-off-by: Antoniu Miclaus <antoniu.miclaus@...log.com>
> > ---
> > changes in v9:
> >  - add back diff-channels property parsing.
> >  - shrink code in one line where possible.
> >  - use struct device *dev = &st->spi->dev
> >  - split elements that are assigned from those which aren't in the places
> >    mentioned by the review.
> >  - avoid code duplication on num_channels
> >  - parse bipolar and diff-channels properties separately.
> >  - update comment on parse_channels
> >  - use devm_regulator_get_enable_optional for vrefbuf and vrefio
> >  - add devm_add_action_or_reset(&st->spi->dev, ad4851_pwm_disable, st-
> >cnv)
> >    after the pwm is turned on.
> >  drivers/iio/adc/Kconfig  |   13 +
> >  drivers/iio/adc/Makefile |    1 +
> >  drivers/iio/adc/ad4851.c | 1290
> ++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 1304 insertions(+)
> >  create mode 100644 drivers/iio/adc/ad4851.c
> >
> > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> > index 849c90203071..00ef9ed289e9 100644
> > --- a/drivers/iio/adc/Kconfig
> > +++ b/drivers/iio/adc/Kconfig
> > @@ -61,6 +61,19 @@ config AD4695
> >  	  To compile this driver as a module, choose M here: the module will be
> >  	  called ad4695.
> >
> > +config AD4851
> > +	tristate "Analog Device AD4851 DAS Driver"
> > +	depends on SPI
> 
> The driver won't work without a PWM, so needs depends or select PWM here.
> 
> > +	select REGMAP_SPI
> > +	select IIO_BACKEND
> > +	help
> > +	  Say yes here to build support for Analog Devices AD4851, AD4852,
> > +	  AD4853, AD4854, AD4855, AD4856, AD4857, AD4858, AD4858I
> high speed
> > +	  data acquisition system (DAS).
> > +
> > +	  To compile this driver as a module, choose M here: the module will be
> > +	  called ad4851.
> > +
> >  config AD7091R
> >  	tristate
> >
> > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> > index ee19afba62b7..e4d8ba12f841 100644
> > --- a/drivers/iio/adc/Makefile
> > +++ b/drivers/iio/adc/Makefile
> > @@ -9,6 +9,7 @@ obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o
> >  obj-$(CONFIG_AD4000) += ad4000.o
> >  obj-$(CONFIG_AD4130) += ad4130.o
> >  obj-$(CONFIG_AD4695) += ad4695.o
> > +obj-$(CONFIG_AD4851) += ad4851.o
> >  obj-$(CONFIG_AD7091R) += ad7091r-base.o
> >  obj-$(CONFIG_AD7091R5) += ad7091r5.o
> >  obj-$(CONFIG_AD7091R8) += ad7091r8.o
> > diff --git a/drivers/iio/adc/ad4851.c b/drivers/iio/adc/ad4851.c
> > new file mode 100644
> > index 000000000000..c5525433990c
> > --- /dev/null
> > +++ b/drivers/iio/adc/ad4851.c
> > @@ -0,0 +1,1290 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Analog Devices AD4851 DAS driver
> > + *
> > + * Copyright 2024 Analog Devices Inc.
> > + */
> > +
> > +#include <linux/array_size.h>
> > +#include <linux/bitfield.h>
> > +#include <linux/bits.h>
> > +#include <linux/delay.h>
> > +#include <linux/device.h>
> > +#include <linux/err.h>
> > +#include <linux/minmax.h>
> > +#include <linux/mod_devicetable.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/pwm.h>
> > +#include <linux/regmap.h>
> > +#include <linux/regulator/consumer.h>
> > +#include <linux/spi/spi.h>
> > +#include <linux/types.h>
> > +#include <linux/unaligned.h>
> > +#include <linux/units.h>
> > +
> > +#include <linux/iio/backend.h>
> > +#include <linux/iio/iio.h>
> > +
> > +#define AD4851_REG_INTERFACE_CONFIG_A	0x00
> > +#define AD4851_REG_INTERFACE_CONFIG_B	0x01
> > +#define AD4851_REG_PRODUCT_ID_L		0x04
> > +#define AD4851_REG_PRODUCT_ID_H		0x05
> > +#define AD4851_REG_DEVICE_CTRL		0x25
> > +#define AD4851_REG_PACKET		0x26
> > +#define AD4851_REG_OVERSAMPLE		0x27
> > +
> > +#define AD4851_REG_CH_CONFIG_BASE	0x2A
> > +#define AD4851_REG_CHX_SOFTSPAN(ch)	((0x12 * (ch)) +
> AD4851_REG_CH_CONFIG_BASE)
> > +#define AD4851_REG_CHX_OFFSET(ch)
> 	(AD4851_REG_CHX_SOFTSPAN(ch) + 0x01)
> > +#define AD4851_REG_CHX_OFFSET_LSB(ch)
> 	AD4851_REG_CHX_OFFSET(ch)
> > +#define AD4851_REG_CHX_OFFSET_MID(ch)
> 	(AD4851_REG_CHX_OFFSET_LSB(ch) + 0x01)
> > +#define AD4851_REG_CHX_OFFSET_MSB(ch)
> 	(AD4851_REG_CHX_OFFSET_MID(ch) + 0x01)
> > +#define AD4851_REG_CHX_GAIN(ch)
> 	(AD4851_REG_CHX_OFFSET(ch) + 0x03)
> > +#define AD4851_REG_CHX_GAIN_LSB(ch)	AD4851_REG_CHX_GAIN(ch)
> > +#define AD4851_REG_CHX_GAIN_MSB(ch)	(AD4851_REG_CHX_GAIN(ch)
> + 0x01)
> > +#define AD4851_REG_CHX_PHASE(ch)	(AD4851_REG_CHX_GAIN(ch)
> + 0x02)
> > +#define AD4851_REG_CHX_PHASE_LSB(ch)
> 	AD4851_REG_CHX_PHASE(ch)
> > +#define AD4851_REG_CHX_PHASE_MSB(ch)
> 	(AD4851_REG_CHX_PHASE_LSB(ch) + 0x01)
> > +
> > +#define AD4851_REG_TESTPAT_0(c)		(0x38 + (c) * 0x12)
> > +#define AD4851_REG_TESTPAT_1(c)		(0x39 + (c) * 0x12)
> > +#define AD4851_REG_TESTPAT_2(c)		(0x3A + (c) * 0x12)
> > +#define AD4851_REG_TESTPAT_3(c)		(0x3B + (c) * 0x12)
> > +
> > +#define AD4851_SW_RESET			(BIT(7) | BIT(0))
> > +#define AD4851_SDO_ENABLE		BIT(4)
> > +#define AD4851_SINGLE_INSTRUCTION	BIT(7)
> > +#define AD4851_REFBUF_PD		BIT(2)
> > +#define AD4851_REFSEL_PD		BIT(1)
> > +#define AD4851_ECHO_CLOCK_MODE		BIT(0)
> > +
> > +#define AD4851_PACKET_FORMAT_0		0
> > +#define AD4851_PACKET_FORMAT_1		1
> > +#define AD4851_PACKET_FORMAT_MASK	GENMASK(1, 0)
> > +
> > +#define AD4851_OS_EN_MSK		BIT(7)
> > +#define AD4851_OS_RATIO_MSK		GENMASK(3, 0)
> > +
> > +#define AD4851_TEST_PAT			BIT(2)
> > +
> > +#define AD4858_PACKET_SIZE_20		0
> > +#define AD4858_PACKET_SIZE_24		1
> > +#define AD4858_PACKET_SIZE_32		2
> > +
> > +#define AD4857_PACKET_SIZE_16		0
> > +#define AD4857_PACKET_SIZE_24		1
> > +
> > +#define AD4851_TESTPAT_0_DEFAULT	0x2A
> > +#define AD4851_TESTPAT_1_DEFAULT	0x3C
> > +#define AD4851_TESTPAT_2_DEFAULT	0xCE
> > +#define AD4851_TESTPAT_3_DEFAULT(c)	(0x0A + (0x10 * (c)))
> > +
> > +#define AD4851_SOFTSPAN_0V_2V5		0
> > +#define AD4851_SOFTSPAN_N2V5_2V5	1
> > +#define AD4851_SOFTSPAN_0V_5V		2
> > +#define AD4851_SOFTSPAN_N5V_5V		3
> > +#define AD4851_SOFTSPAN_0V_6V25		4
> > +#define AD4851_SOFTSPAN_N6V25_6V25	5
> > +#define AD4851_SOFTSPAN_0V_10V		6
> > +#define AD4851_SOFTSPAN_N10V_10V	7
> > +#define AD4851_SOFTSPAN_0V_12V5		8
> > +#define AD4851_SOFTSPAN_N12V5_12V5	9
> > +#define AD4851_SOFTSPAN_0V_20V		10
> > +#define AD4851_SOFTSPAN_N20V_20V	11
> > +#define AD4851_SOFTSPAN_0V_25V		12
> > +#define AD4851_SOFTSPAN_N25V_25V	13
> > +#define AD4851_SOFTSPAN_0V_40V		14
> > +#define AD4851_SOFTSPAN_N40V_40V	15
> > +
> > +#define AD4851_MAX_LANES		8
> > +#define AD4851_MAX_IODELAY		32
> > +
> > +#define AD4851_T_CNVH_NS		40
> > +#define AD4851_T_CNVH_NS_MARGIN		10
> > +
> > +#define AD4841_MAX_SCALE_AVAIL		8
> > +
> > +#define AD4851_MAX_CH_NR		8
> > +#define AD4851_CH_START			0
> > +
> > +struct ad4851_scale {
> > +	unsigned int scale_val;
> > +	u8 reg_val;
> > +};
> > +
> > +static const struct ad4851_scale ad4851_scale_table_se[] = {
> 
> This should be called unipolar intead of se (single-ended). It is possible to
> have bipolar single-ended.
> 
> > +	{ 2500, 0x0 },
> > +	{ 5000, 0x2 },
> > +	{ 6250, 0x4 },
> > +	{ 10000, 0x6 },
> > +	{ 12500, 0x8 },
> > +	{ 20000, 0xA },
> > +	{ 25000, 0xC },
> > +	{ 40000, 0xE },
> > +};
> > +
> > +static const struct ad4851_scale ad4851_scale_table_diff[] = {
> 
> This should be called bippolar instead of diff for the same reason.
> 
> > +	{ 5000, 0x1 },
> > +	{ 10000, 0x3 },
> > +	{ 12500, 0x5 },
> > +	{ 20000, 0x7 },
> > +	{ 25000, 0x9 },
> > +	{ 40000, 0xB },
> > +	{ 50000, 0xD },
> > +	{ 80000, 0xF },
> > +};
> > +
> > +static const unsigned int ad4851_scale_avail_se[] = {
> 
> ditto
> 
> > +	2500,
> > +	5000,
> > +	6250,
> > +	10000,
> > +	12500,
> > +	20000,
> > +	25000,
> > +	40000,
> > +};
> > +
> > +static const unsigned int ad4851_scale_avail_diff[] = {
> 
> ditto
> 
> > +	5000,
> > +	10000,
> > +	12500,
> > +	20000,
> > +	25000,
> > +	40000,
> > +	50000,
> > +	80000,
> > +};
> > +
> > +struct ad4851_chip_info {
> > +	const char *name;
> > +	unsigned int product_id;
> > +	int num_scales;
> > +	unsigned long max_sample_rate_hz;
> > +	unsigned int resolution;
> > +	int (*parse_channels)(struct iio_dev *indio_dev);
> > +};
> > +
> > +enum {
> > +	AD4851_SCAN_TYPE_NORMAL,
> > +	AD4851_SCAN_TYPE_RESOLUTION_BOOST,
> > +};
> > +
> > +struct ad4851_state {
> > +	struct spi_device *spi;
> > +	struct pwm_device *cnv;
> > +	struct iio_backend *back;
> > +	/*
> > +	 * Synchronize access to members the of driver state, and ensure
> > +	 * atomicity of consecutive regmap operations.
> > +	 */
> > +	struct mutex lock;
> > +	struct regmap *regmap;
> > +	const struct ad4851_chip_info *info;
> > +	struct gpio_desc *pd_gpio;
> > +	bool resolution_boost_enabled;
> > +	unsigned long cnv_trigger_rate_hz;
> > +	unsigned int osr;
> > +	bool vrefbuf_en;
> > +	bool vrefio_en;
> > +	bool bipolar_ch[AD4851_MAX_CH_NR];
> > +	unsigned int scales_se[AD4841_MAX_SCALE_AVAIL][2];
> > +	unsigned int scales_diff[AD4841_MAX_SCALE_AVAIL][2];
> > +};
> > +
> > +static int ad4851_reg_access(struct iio_dev *indio_dev,
> > +			     unsigned int reg,
> > +			     unsigned int writeval,
> > +			     unsigned int *readval)
> > +{
> > +	struct ad4851_state *st = iio_priv(indio_dev);
> > +
> > +	guard(mutex)(&st->lock);
> > +
> > +	if (readval)
> > +		return regmap_read(st->regmap, reg, readval);
> > +
> > +	return regmap_write(st->regmap, reg, writeval);
> > +}
> > +
> > +static int ad4851_set_sampling_freq(struct ad4851_state *st, unsigned int
> freq)
> > +{
> > +	struct pwm_state cnv_state = {
> > +		.duty_cycle = AD4851_T_CNVH_NS +
> AD4851_T_CNVH_NS_MARGIN,
> > +		.enabled = true,
> > +	};
> > +	int ret;
> > +
> > +	freq = clamp(freq, 1, st->info->max_sample_rate_hz);
> > +
> > +	cnv_state.period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, freq);
> > +
> > +	ret = pwm_apply_might_sleep(st->cnv, &cnv_state);
> > +	if (ret)
> > +		return ret;
> > +
> > +	st->cnv_trigger_rate_hz = freq;
> > +
> > +	return 0;
> > +}
> > +
> > +static const int ad4851_oversampling_ratios[] = {
> > +	1, 2, 4, 8, 16,	32, 64, 128,
> > +	256, 512, 1024, 2048, 4096, 8192, 16384, 32768,
> > +	65536,
> > +};
> > +
> > +static int ad4851_osr_to_regval(unsigned int ratio)
> > +{
> > +	int i;
> > +
> > +	for (i = 1; i < ARRAY_SIZE(ad4851_oversampling_ratios); i++)
> > +		if (ratio == ad4851_oversampling_ratios[i])
> > +			return i - 1;
> > +
> > +	return -EINVAL;
> > +}
> > +
> > +static void __ad4851_get_scale(struct iio_dev *indio_dev, int scale_tbl,
> > +			       unsigned int *val, unsigned int *val2)
> > +{
> > +	const struct iio_scan_type *scan_type;
> > +	unsigned int tmp;
> > +
> > +	scan_type = iio_get_current_scan_type(indio_dev, &indio_dev-
> >channels[0]);
> > +
> > +	tmp = ((unsigned long long)scale_tbl * MICRO) >> scan_type->realbits;
> > +	*val = tmp / MICRO;
> > +	*val2 = tmp % MICRO;
> > +}
> > +
> > +static int ad4851_scale_fill(struct iio_dev *indio_dev)
> > +{
> > +	struct ad4851_state *st = iio_priv(indio_dev);
> > +	unsigned int i, val1, val2;
> > +
> > +	for (i = 0; i < ARRAY_SIZE(ad4851_scale_avail_se); i++) {
> > +		__ad4851_get_scale(indio_dev, ad4851_scale_avail_se[i],
> &val1, &val2);
> > +		st->scales_se[i][0] = val1;
> > +		st->scales_se[i][1] = val2;
> > +	}
> > +
> > +	for (i = 0; i < ARRAY_SIZE(ad4851_scale_avail_diff); i++) {
> > +		__ad4851_get_scale(indio_dev, ad4851_scale_avail_diff[i],
> &val1, &val2);
> > +		st->scales_diff[i][0] = val1;
> > +		st->scales_diff[i][1] = val2;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int ad4851_set_oversampling_ratio(struct iio_dev *indio_dev,
> > +					 const struct iio_chan_spec *chan,
> > +					 unsigned int osr)
> > +{
> > +	struct ad4851_state *st = iio_priv(indio_dev);
> > +	int val, ret;
> > +
> > +	guard(mutex)(&st->lock);
> > +
> > +	if (osr == 1) {
> > +		ret = regmap_clear_bits(st->regmap,
> AD4851_REG_OVERSAMPLE,
> > +					AD4851_OS_EN_MSK);
> > +		if (ret)
> > +			return ret;
> > +	} else {
> > +		val = ad4851_osr_to_regval(osr);
> > +		if (val < 0)
> > +			return -EINVAL;
> > +
> > +		ret = regmap_update_bits(st->regmap,
> AD4851_REG_OVERSAMPLE,
> > +					 AD4851_OS_EN_MSK |
> > +					 AD4851_OS_RATIO_MSK,
> > +					 FIELD_PREP(AD4851_OS_EN_MSK,
> 1) |
> > +
> FIELD_PREP(AD4851_OS_RATIO_MSK, val));
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	ret = iio_backend_oversampling_ratio_set(st->back, osr);
> > +	if (ret)
> > +		return ret;
> > +
> > +	switch (st->info->resolution) {
> > +	case 20:
> > +		switch (osr) {
> > +		case 0:
> > +			return -EINVAL;
> > +		case 1:
> > +			val = 20;
> > +			break;
> > +		default:
> > +			val = 24;
> > +			break;
> > +		}
> > +		break;
> > +	case 16:
> > +		val = 16;
> > +		break;
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +
> > +	ret = iio_backend_data_size_set(st->back, val);
> > +	if (ret)
> > +		return ret;
> > +
> > +	if (osr == 1 || st->info->resolution == 16) {
> > +		ret = regmap_clear_bits(st->regmap, AD4851_REG_PACKET,
> > +					AD4851_PACKET_FORMAT_MASK);
> > +		if (ret)
> > +			return ret;
> > +
> > +		st->resolution_boost_enabled = false;
> > +	} else {
> > +		ret = regmap_update_bits(st->regmap, AD4851_REG_PACKET,
> 
> regmap_set_bits

Why? Packet format is two bits wide according to the register map.

> > +					 AD4851_PACKET_FORMAT_MASK,
> > +
> FIELD_PREP(AD4851_PACKET_FORMAT_MASK, 1));
> > +		if (ret)
> > +			return ret;
> > +
> > +		st->resolution_boost_enabled = true;
> > +	}
> > +
> > +	if (st->osr != osr) {
> > +		ret = ad4851_scale_fill(indio_dev);
> > +		if (ret)
> > +			return ret;
> > +
> > +		st->osr = osr;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int ad4851_get_oversampling_ratio(struct ad4851_state *st,
> unsigned int *val)
> > +{
> > +	unsigned int osr;
> > +	int ret;
> > +
> > +	guard(mutex)(&st->lock);
> > +
> > +	ret = regmap_read(st->regmap, AD4851_REG_OVERSAMPLE, &osr);
> > +	if (ret)
> > +		return ret;
> > +
> > +	if (!FIELD_GET(AD4851_OS_EN_MSK, osr))
> > +		*val = 1;
> > +	else
> > +		*val =
> ad4851_oversampling_ratios[FIELD_GET(AD4851_OS_RATIO_MSK, osr) + 1];
> > +
> > +	st->osr = *val;
> > +
> > +	return IIO_VAL_INT;
> > +}
> > +
> > +static void ad4851_pwm_disable(void *data)
> > +{
> > +	pwm_disable(data);
> > +}
> > +
> > +static int ad4851_setup(struct ad4851_state *st)
> > +{
> > +	unsigned int product_id;
> > +	int ret;
> > +
> > +	if (st->pd_gpio) {
> > +		/* To initiate a global reset, bring the PD pin high twice */
> > +		gpiod_set_value(st->pd_gpio, 1);
> > +		fsleep(1);
> > +		gpiod_set_value(st->pd_gpio, 0);
> > +		fsleep(1);
> > +		gpiod_set_value(st->pd_gpio, 1);
> > +		fsleep(1);
> > +		gpiod_set_value(st->pd_gpio, 0);
> > +		fsleep(1000);
> > +	} else {
> > +		ret = regmap_set_bits(st->regmap,
> AD4851_REG_INTERFACE_CONFIG_A,
> > +				      AD4851_SW_RESET);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	if (st->vrefbuf_en) {
> > +		ret = regmap_set_bits(st->regmap,
> AD4851_REG_DEVICE_CTRL,
> > +				      AD4851_REFBUF_PD);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	if (st->vrefio_en) {
> > +		ret = regmap_set_bits(st->regmap,
> AD4851_REG_DEVICE_CTRL,
> > +				      AD4851_REFSEL_PD);
> > +		if (ret)
> > +			return ret;
> > +	}
> 
> PD stands for power down, so should we be powering down if not enabled?
> (i.e.
> if is missing !)
We power down the internal reference if the external one is used. Not sure what is wrong here.
> > +
> > +	ret = regmap_write(st->regmap,
> AD4851_REG_INTERFACE_CONFIG_B,
> > +			   AD4851_SINGLE_INSTRUCTION);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = regmap_write(st->regmap,
> AD4851_REG_INTERFACE_CONFIG_A,
> > +			   AD4851_SDO_ENABLE);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = regmap_read(st->regmap, AD4851_REG_PRODUCT_ID_L,
> &product_id);
> > +	if (ret)
> > +		return ret;
> > +
> > +	if (product_id != st->info->product_id)
> > +		dev_info(&st->spi->dev, "Unknown product ID: 0x%02X\n",
> > +			 product_id);
> > +
> > +	ret = regmap_set_bits(st->regmap, AD4851_REG_DEVICE_CTRL,
> > +			      AD4851_ECHO_CLOCK_MODE);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return regmap_write(st->regmap, AD4851_REG_PACKET, 0);
> > +}
> > +
> > +static int ad4851_find_opt(bool *field, u32 size, u32 *ret_start)
> > +{
> > +	unsigned int i, cnt = 0, max_cnt = 0, max_start = 0;
> > +	int start;
> > +
> > +	for (i = 0, start = -1; i < size; i++) {
> > +		if (field[i] == 0) {
> > +			if (start == -1)
> > +				start = i;
> > +			cnt++;
> > +		} else {
> > +			if (cnt > max_cnt) {
> > +				max_cnt = cnt;
> > +				max_start = start;
> > +			}
> > +			start = -1;
> > +			cnt = 0;
> > +		}
> > +	}
> > +	/*
> > +	 * Find the longest consecutive sequence of false values from field
> > +	 * and return starting index.
> > +	 */
> > +	if (cnt > max_cnt) {
> > +		max_cnt = cnt;
> > +		max_start = start;
> > +	}
> > +
> > +	if (!max_cnt)
> > +		return -ENOENT;
> > +
> > +	*ret_start = max_start;
> > +
> > +	return max_cnt;
> > +}
> > +
> > +static int ad4851_calibrate(struct iio_dev *indio_dev)
> > +{
> > +	struct ad4851_state *st = iio_priv(indio_dev);
> > +	unsigned int opt_delay, num_lanes, delay, i, s, c;
> > +	enum iio_backend_interface_type interface_type;
> > +	DECLARE_BITMAP(pn_status, AD4851_MAX_LANES *
> AD4851_MAX_IODELAY);
> > +	bool status;
> > +	int ret;
> > +
> > +	ret = iio_backend_interface_type_get(st->back, &interface_type);
> > +	if (ret)
> > +		return ret;
> > +
> > +	switch (interface_type) {
> > +	case IIO_BACKEND_INTERFACE_SERIAL_CMOS:
> > +		num_lanes = indio_dev->num_channels;
> > +		break;
> > +	case IIO_BACKEND_INTERFACE_SERIAL_LVDS:
> > +		num_lanes = 1;
> > +		break;
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (st->info->resolution == 16) {
> > +		ret = iio_backend_data_size_set(st->back, 24);
> > +		if (ret)
> > +			return ret;
> > +
> > +		ret = regmap_write(st->regmap, AD4851_REG_PACKET,
> > +				   AD4851_TEST_PAT |
> AD4857_PACKET_SIZE_24);
> > +		if (ret)
> > +			return ret;
> > +	} else {
> > +		ret = iio_backend_data_size_set(st->back, 32);
> > +		if (ret)
> > +			return ret;
> > +
> > +		ret = regmap_write(st->regmap, AD4851_REG_PACKET,
> > +				   AD4851_TEST_PAT |
> AD4858_PACKET_SIZE_32);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	for (i = 0; i < indio_dev->num_channels; i++) {
> > +		ret = regmap_write(st->regmap, AD4851_REG_TESTPAT_0(i),
> > +				   AD4851_TESTPAT_0_DEFAULT);
> > +		if (ret)
> > +			return ret;
> > +
> > +		ret = regmap_write(st->regmap, AD4851_REG_TESTPAT_1(i),
> > +				   AD4851_TESTPAT_1_DEFAULT);
> > +		if (ret)
> > +			return ret;
> > +
> > +		ret = regmap_write(st->regmap, AD4851_REG_TESTPAT_2(i),
> > +				   AD4851_TESTPAT_2_DEFAULT);
> > +		if (ret)
> > +			return ret;
> > +
> > +		ret = regmap_write(st->regmap, AD4851_REG_TESTPAT_3(i),
> > +				   AD4851_TESTPAT_3_DEFAULT(i));
> > +		if (ret)
> > +			return ret;
> > +
> > +		ret = iio_backend_chan_enable(st->back, i);
> 
> Right now, i might not correspond to the channel number if channles were
> skipped
> in the devicetree. Safer would be to use indio_dev->channels[i].channel.
> 
> Or, as I suggest in the channel parsing function below, we could just make sure
> indio_dev->num_channels is always 8, then this code here is fine.
> 
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	for (i = 0; i < num_lanes; i++) {
> > +		for (delay = 0; delay < AD4851_MAX_IODELAY; delay++) {
> > +			ret = iio_backend_iodelay_set(st->back, i, delay);
> > +			if (ret)
> > +				return ret;
> > +
> > +			ret = iio_backend_chan_status(st->back, i, &status);
> > +			if (ret)
> > +				return ret;
> > +
> > +			if (status)
> > +				set_bit(i * AD4851_MAX_IODELAY + delay,
> pn_status);
> > +			else
> > +				clear_bit(i * AD4851_MAX_IODELAY + delay,
> pn_status);
> > +		}
> > +	}
> > +
> > +	for (i = 0; i < num_lanes; i++) {
> > +		status = test_bit(i * AD4851_MAX_IODELAY, pn_status);
> > +		c = ad4851_find_opt(&status, AD4851_MAX_IODELAY, &s);
> > +		if (c < 0)
> > +			return c;
> > +
> > +		opt_delay = s + c / 2;
> > +		ret = iio_backend_iodelay_set(st->back, i, opt_delay);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	for (i = 0; i < indio_dev->num_channels; i++) {
> > +		ret = iio_backend_chan_disable(st->back, i);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	ret = iio_backend_data_size_set(st->back, 20);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return regmap_write(st->regmap, AD4851_REG_PACKET, 0);
> > +}
> > +
> > +static int ad4851_get_calibscale(struct ad4851_state *st, int ch, int *val, int
> *val2)
> > +{
> > +	unsigned int reg_val;
> > +	int gain;
> > +	int ret;
> > +
> > +	guard(mutex)(&st->lock);
> > +
> > +	ret = regmap_read(st->regmap, AD4851_REG_CHX_GAIN_MSB(ch),
> &reg_val);
> > +	if (ret)
> > +		return ret;
> > +
> > +	gain = reg_val << 8;
> > +
> > +	ret = regmap_read(st->regmap, AD4851_REG_CHX_GAIN_LSB(ch),
> &reg_val);
> > +	if (ret)
> > +		return ret;
> > +
> > +	gain |= reg_val;
> > +
> > +	*val = gain;
> > +	*val2 = 32768;
> > +
> > +	return IIO_VAL_FRACTIONAL;
> > +}
> > +
> > +static int ad4851_set_calibscale(struct ad4851_state *st, int ch, int val,
> > +				 int val2)
> > +{
> > +	u64 gain;
> > +	u8 buf[2];
> > +	int ret;
> > +
> > +	if (val < 0 || val2 < 0)
> > +		return -EINVAL;
> > +
> > +	gain = val * MICRO + val2;
> > +	gain = DIV_U64_ROUND_CLOSEST(gain * 32768, MICRO);
> > +
> > +	put_unaligned_be16(gain, buf);
> > +
> > +	guard(mutex)(&st->lock);
> > +
> > +	ret = regmap_write(st->regmap, AD4851_REG_CHX_GAIN_MSB(ch),
> buf[0]);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return regmap_write(st->regmap, AD4851_REG_CHX_GAIN_LSB(ch),
> buf[1]);
> > +}
> > +
> > +static int ad4851_get_calibbias(struct ad4851_state *st, int ch, int *val)
> > +{
> > +	unsigned int lsb, mid, msb;
> > +	int ret;
> > +
> > +	guard(mutex)(&st->lock);
> > +
> > +	ret = regmap_read(st->regmap, AD4851_REG_CHX_OFFSET_MSB(ch),
> &msb);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = regmap_read(st->regmap, AD4851_REG_CHX_OFFSET_MID(ch),
> &mid);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = regmap_read(st->regmap, AD4851_REG_CHX_OFFSET_LSB(ch),
> &lsb);
> > +	if (ret)
> > +		return ret;
> > +
> > +	if (st->info->resolution == 16) {
> > +		*val = msb << 8;
> > +		*val |= mid;
> > +		*val = sign_extend32(*val, 15);
> > +	} else {
> > +		*val = msb << 12;
> > +		*val |= mid << 4;
> > +		*val |= lsb >> 4;
> > +		*val = sign_extend32(*val, 19);
> > +	}
> > +
> > +	return IIO_VAL_INT;
> > +}
> > +
> > +static int ad4851_set_calibbias(struct ad4851_state *st, int ch, int val)
> > +{
> > +	u8 buf[3] = { 0 };
> 
> 0 can be omitted
> 
> > +	int ret;
> > +
> > +	if (val < 0)
> > +		return -EINVAL;
> > +
> > +	if (st->info->resolution == 16)
> > +		put_unaligned_be16(val, buf);
> > +	else
> > +		put_unaligned_be24(val << 4, buf);
> > +
> > +	guard(mutex)(&st->lock);
> > +
> > +	ret = regmap_write(st->regmap, AD4851_REG_CHX_OFFSET_LSB(ch),
> buf[2]);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = regmap_write(st->regmap, AD4851_REG_CHX_OFFSET_MID(ch),
> buf[1]);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return regmap_write(st->regmap,
> AD4851_REG_CHX_OFFSET_MSB(ch), buf[0]);
> > +}
> > +
> > +static int ad4851_set_scale(struct iio_dev *indio_dev,
> > +			    const struct iio_chan_spec *chan, int val, int val2)
> > +{
> > +	struct ad4851_state *st = iio_priv(indio_dev);
> > +	unsigned int scale_val[2];
> > +	unsigned int i;
> > +	const struct ad4851_scale *scale_table;
> > +	size_t table_size;
> > +
> > +	if (chan->differential) {
> 
> This should check st->bipolar_ch[chan->channel], not chan->differential
> 
> > +		scale_table = ad4851_scale_table_diff;
> > +		table_size = ARRAY_SIZE(ad4851_scale_table_diff);
> > +	} else {
> > +		scale_table = ad4851_scale_table_se;
> > +		table_size = ARRAY_SIZE(ad4851_scale_table_se);
> > +	}
> > +
> > +	for (i = 0; i < table_size; i++) {
> > +		__ad4851_get_scale(indio_dev, scale_table[i].scale_val,
> > +				   &scale_val[0], &scale_val[1]);
> > +		if (scale_val[0] != val || scale_val[1] != val2)
> > +			continue;
> > +
> > +		return regmap_write(st->regmap,
> > +				    AD4851_REG_CHX_SOFTSPAN(chan-
> >channel),
> > +				    scale_table[i].reg_val);
> > +	}
> > +
> > +	return -EINVAL;
> > +}
> > +
> > +static int ad4851_get_scale(struct iio_dev *indio_dev,
> > +			    const struct iio_chan_spec *chan, int *val,
> > +			    int *val2)
> > +{
> > +	struct ad4851_state *st = iio_priv(indio_dev);
> > +	const struct ad4851_scale *scale_table;
> > +	size_t table_size;
> > +	int i, softspan_val;
> 
> Technically, softspan_val should be u32. And i should be size_t since it gets
> compared to table_size.
> 
> > +	int ret;
> > +
> > +	if (chan->differential) {
> 
> This should check st->bipolar_ch[chan->channel], not chan->differential
> 
> > +		scale_table = ad4851_scale_table_diff;
> > +		table_size = ARRAY_SIZE(ad4851_scale_table_diff);
> > +	} else {
> > +		scale_table = ad4851_scale_table_se;
> > +		table_size = ARRAY_SIZE(ad4851_scale_table_se);
> > +	}
> > +
> > +	ret = regmap_read(st->regmap, AD4851_REG_CHX_SOFTSPAN(chan-
> >channel),
> > +			  &softspan_val);
> > +	if (ret)
> > +		return ret;
> > +
> > +	for (i = 0; i < table_size; i++) {
> > +		if (softspan_val == scale_table[i].reg_val)
> > +			break;
> > +	}
> > +
> > +	if (i == table_size)
> > +		return -EIO;
> > +
> > +	__ad4851_get_scale(indio_dev, scale_table[i].scale_val, val, val2);
> > +
> > +	return IIO_VAL_INT_PLUS_MICRO;
> > +}
> > +
> > +static int ad4851_read_raw(struct iio_dev *indio_dev,
> > +			   const struct iio_chan_spec *chan,
> > +			   int *val, int *val2, long info)
> > +{
> > +	struct ad4851_state *st = iio_priv(indio_dev);
> > +
> > +	switch (info) {
> > +	case IIO_CHAN_INFO_SAMP_FREQ:
> > +		*val = st->cnv_trigger_rate_hz / st->osr;
> 
> osr can be large here (2^16) so could be a good idea to use
> IIO_VAL_FRACTIONAL
> instead to get a more accurate value.
> 
> > +		return IIO_VAL_INT;
> > +	case IIO_CHAN_INFO_CALIBSCALE:
> > +		return ad4851_get_calibscale(st, chan->channel, val, val2);
> > +	case IIO_CHAN_INFO_SCALE:
> > +		return ad4851_get_scale(indio_dev, chan, val, val2);
> > +	case IIO_CHAN_INFO_CALIBBIAS:
> > +		return ad4851_get_calibbias(st, chan->channel, val);
> > +	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> > +		return ad4851_get_oversampling_ratio(st, val);
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +}
> > +
> > +static int ad4851_write_raw(struct iio_dev *indio_dev,
> > +			    struct iio_chan_spec const *chan,
> > +			    int val, int val2, long info)
> > +{
> > +	struct ad4851_state *st = iio_priv(indio_dev);
> > +
> > +	switch (info) {
> > +	case IIO_CHAN_INFO_SAMP_FREQ:
> > +		return ad4851_set_sampling_freq(st, val * st->osr);
> 
> As above, osr can be large, so we should probably also take into consideration
> val2 to allow for better accuracy.
> 
> > +	case IIO_CHAN_INFO_SCALE:
> > +		return ad4851_set_scale(indio_dev, chan, val, val2);
> > +	case IIO_CHAN_INFO_CALIBSCALE:
> > +		return ad4851_set_calibscale(st, chan->channel, val, val2);
> > +	case IIO_CHAN_INFO_CALIBBIAS:
> > +		return ad4851_set_calibbias(st, chan->channel, val);
> > +	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> > +		return ad4851_set_oversampling_ratio(indio_dev, chan, val);
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +}
> > +
> > +static int ad4851_update_scan_mode(struct iio_dev *indio_dev,
> > +				   const unsigned long *scan_mask)
> > +{
> > +	struct ad4851_state *st = iio_priv(indio_dev);
> > +	unsigned int c;
> > +	int ret;
> > +
> > +	for (c = 0; c < indio_dev->num_channels; c++) {
> > +		if (test_bit(c, scan_mask))
> > +			ret = iio_backend_chan_enable(st->back, c);
> > +		else
> > +			ret = iio_backend_chan_disable(st->back, c);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int ad4851_read_avail(struct iio_dev *indio_dev,
> > +			     struct iio_chan_spec const *chan,
> > +			     const int **vals, int *type, int *length,
> > +			     long mask)
> > +{
> > +	struct ad4851_state *st = iio_priv(indio_dev);
> > +
> > +	switch (mask) {
> > +	case IIO_CHAN_INFO_SCALE:
> > +		if (st->bipolar_ch[chan->channel]) {
> > +			*vals = (const int *)st->scales_diff;
> > +			*type = IIO_VAL_INT_PLUS_MICRO;
> > +			/* Values are stored in a 2D matrix */
> > +			*length = ARRAY_SIZE(ad4851_scale_avail_diff) * 2;
> > +		} else {
> > +			*vals = (const int *)st->scales_se;
> > +			*type = IIO_VAL_INT_PLUS_MICRO;
> > +			/* Values are stored in a 2D matrix */
> > +			*length = ARRAY_SIZE(ad4851_scale_avail_se) * 2;
> > +		}
> > +		return IIO_AVAIL_LIST;
> > +	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> > +		*vals = ad4851_oversampling_ratios;
> > +		*length = ARRAY_SIZE(ad4851_oversampling_ratios);
> > +		*type = IIO_VAL_INT;
> > +		return IIO_AVAIL_LIST;
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +}
> > +
> > +static const struct iio_scan_type ad4851_scan_type_20_0[] = {
> > +	[AD4851_SCAN_TYPE_NORMAL] = {
> > +		.sign = 'u',
> > +		.realbits = 20,
> > +		.storagebits = 32,
> > +	},
> > +	[AD4851_SCAN_TYPE_RESOLUTION_BOOST] = {
> > +		.sign = 'u',
> > +		.realbits = 24,
> > +		.storagebits = 32,
> > +	},
> > +};
> > +
> > +static const struct iio_scan_type ad4851_scan_type_20_1[] = {
> > +	[AD4851_SCAN_TYPE_NORMAL] = {
> > +		.sign = 's',
> > +		.realbits = 20,
> > +		.storagebits = 32,
> > +	},
> > +	[AD4851_SCAN_TYPE_RESOLUTION_BOOST] = {
> > +		.sign = 's',
> > +		.realbits = 24,
> > +		.storagebits = 32,
> > +	},
> > +};
> > +
> > +static int ad4851_get_current_scan_type(const struct iio_dev *indio_dev,
> > +					const struct iio_chan_spec *chan)
> > +{
> > +	struct ad4851_state *st = iio_priv(indio_dev);
> > +
> > +	return st->resolution_boost_enabled ?
> AD4851_SCAN_TYPE_RESOLUTION_BOOST
> > +					    : AD4851_SCAN_TYPE_NORMAL;
> > +}
> > +
> > +#define AD4851_IIO_CHANNEL(index, ch, diff)
> 		\
> > +	.type = IIO_VOLTAGE,
> 	\
> > +	.info_mask_separate = BIT(IIO_CHAN_INFO_CALIBSCALE) |
> 		\
> > +		BIT(IIO_CHAN_INFO_CALIBBIAS) |
> 		\
> > +		BIT(IIO_CHAN_INFO_SCALE),
> 	\
> > +	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
> 		\
> > +		BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
> 		\
> > +	.info_mask_shared_by_all_available =
> 	\
> > +		BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
> 		\
> > +	.info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
> 	\
> 
> Would be more logical to move this line up right after info_mask_separate.
> 
> > +	.indexed = 1,
> 	\
> > +	.differential = diff,
> 	\
> > +	.channel = ch,
> 	\
> > +	.channel2 = ch + (diff * 8),
> 	\
> 
> Hard-coding 8 here could be a bit fragile, e.g. if we ever add a 16-channel
> chip. We should at least add a comment here or use the
> AD4851_MAX_CH_NR macro.
> 
> Also, diff * (ch + AD4851_MAX_CH_NR) would seem more logical so that the
> value
> is 0 for single-ended inputs.
> 
> > +	.scan_index = index,
> > +
> > +#define AD4858_IIO_CHANNEL(index, ch, diff)
> 		\
> > +{
> 	\
> > +	AD4851_IIO_CHANNEL(index, ch, diff)
> 	\
> > +}
> > +
> > +#define AD4857_IIO_CHANNEL(index, ch, diff)
> 		\
> > +{
> 	\
> > +	AD4851_IIO_CHANNEL(index, ch, diff)
> 	\
> > +	.scan_type = {
> 	\
> > +		.sign = diff ? 's' : 'u',					\
> 
> The sign depends on unipolar or bipolar for this chip, not differential or
> single-ended. We can just make the default here 'u' and let the channel parseer
> write over it if it is signed.
> 
> > +		.realbits = 16,
> 	\
> > +		.storagebits = 16,
> 	\
> > +	},
> 	\
> > +}
> > +
> > +static int ad4851_parse_channels(struct iio_dev *indio_dev,
> > +				 struct iio_chan_spec **ad4851_channels,
> 
> This parameter is reduandant and can be removed. The caller can just use
> indio_dev->channels to get the same pointer.
> 
> > +				 const struct iio_chan_spec ad4851_chan,
> > +				 const struct iio_chan_spec
> ad4851_chan_diff)
> 
> Better to pass structs as pointers instead of copying them.
> 
> > +{
> > +	struct ad4851_state *st = iio_priv(indio_dev);
> > +	struct device *dev = &st->spi->dev;
> > +	struct iio_chan_spec *channels;
> > +	unsigned int num_channels, reg;
> > +	unsigned int index = 0;
> > +	int ret;
> > +
> > +	num_channels = device_get_child_node_count(dev);
> > +	if (num_channels > AD4851_MAX_CH_NR)
> > +		return dev_err_probe(dev, -EINVAL, "Too many channels:
> %u\n",
> > +				     num_channels);
> 
> Since this isn't the kind of chip where we can mix and match different input
> pins to make different channels, I would suggest to always create the max
> number of channels, then tweak the channel info based on channel nodes in
> the
> DT. Any channels that don't have a channel node in DT can just default to
> unipolar single-ended.
> 
> > +
> > +	channels = devm_kcalloc(dev, num_channels,
> > +				sizeof(*channels), GFP_KERNEL);
> > +	if (!channels)
> > +		return -ENOMEM;
> > +
> > +	indio_dev->channels = channels;
> > +	indio_dev->num_channels = num_channels;
> > +
> > +	device_for_each_child_node_scoped(dev, child) {
> 
> Then this just becomes a regular for loop so that a unique scan index is
> assigned to each channel.
> 
> > +		ret = fwnode_property_read_u32(child, "reg", &reg);
> > +		if (ret)
> > +			return dev_err_probe(dev, ret,
> > +					     "Missing channel number\n");
> 
> reg needs to be checked that it is in range, otherwise we get out of bounds
> array access later.
> 
> > +		if (fwnode_property_present(child, "diff-channels")) {
> > +			*channels = ad4851_chan_diff;
> > +			channels->scan_index = index++;
> > +			channels->channel = reg;
> > +			channels->channel2 = reg + AD4851_MAX_CH_NR;
> 
> This looks a bit fragile. If AD4851_MAX_CH_NR was ever increased for a new
> chip
> with more channels, then it would break userspace for other chips. Maybe use
> the chip-specific max here instead of the global max.
> 
> > +
> > +		} else {
> > +			*channels = ad4851_chan;
> > +			channels->scan_index = index++;
> > +			channels->channel = reg;
> > +		}
> > +		channels++;
> > +
> > +		if (fwnode_property_present(child, "bipolar")) {
> 
> IIRC, fwnode_property_read_bool() is prefered for boolean flags.
> 
> > +			st->bipolar_ch[reg] = true;
> 
> This also needs to set the sign in scan_type to 's'.
> 
> > +		} else {
> > +			st->bipolar_ch[reg] = false;
> > +			ret = regmap_write(st->regmap,
> AD4851_REG_CHX_SOFTSPAN(reg),
> > +					   AD4851_SOFTSPAN_0V_40V);
> > +			if (ret)
> > +				return ret;
> > +		}
> > +	}
> > +
> > +	*ad4851_channels = channels;
> > +
> > +	return 0;
> > +}
> > +
> > +static int ad4857_parse_channels(struct iio_dev *indio_dev)
> > +{
> > +	struct iio_chan_spec *ad4851_channels;
> > +	const struct iio_chan_spec ad4851_chan = AD4857_IIO_CHANNEL(0,
> 0, 0);
> > +	const struct iio_chan_spec ad4851_chan_diff =
> AD4857_IIO_CHANNEL(0, 0, 1);
> > +
> > +	return ad4851_parse_channels(indio_dev, &ad4851_channels,
> ad4851_chan, ad4851_chan_diff);
> > +}
> > +
> > +static int ad4858_parse_channels(struct iio_dev *indio_dev)
> > +{
> > +	struct ad4851_state *st = iio_priv(indio_dev);
> > +	struct device *dev = &st->spi->dev;
> > +	struct iio_chan_spec *ad4851_channels;
> > +	const struct iio_chan_spec ad4851_chan = AD4858_IIO_CHANNEL(0,
> 0, 0);
> > +	const struct iio_chan_spec ad4851_chan_diff =
> AD4858_IIO_CHANNEL(0, 0, 1);
> > +	unsigned int reg;
> > +	int ret;
> > +
> > +	ret = ad4851_parse_channels(indio_dev, &ad4851_channels,
> ad4851_chan, ad4851_chan_diff);
> > +	if (ret)
> > +		return ret;
> > +
> > +	device_for_each_child_node_scoped(dev, child) {
> > +		ret = fwnode_property_read_u32(child, "reg", &reg);
> > +		if (ret)
> > +			return dev_err_probe(dev, ret,
> > +					     "Missing channel number\n");
> 
> reg is unused here so can be removed. It was already checked in
> ad4851_parse_channels().
> 
> Also need to set ad4851_channels->has_ext_scan_type = 1 here.
> 
> > +		if (fwnode_property_present(child, "bipolar")) {
> > +			ad4851_channels->ext_scan_type =
> ad4851_scan_type_20_1;
> > +			ad4851_channels->num_ext_scan_type =
> ARRAY_SIZE(ad4851_scan_type_20_1);
> > +
> > +		} else {
> > +			ad4851_channels->ext_scan_type =
> ad4851_scan_type_20_0;
> > +			ad4851_channels->num_ext_scan_type =
> ARRAY_SIZE(ad4851_scan_type_20_0);
> > +		}
> 
> Might want to warn if channel is differential but not bipolar. It couldn't work
> with that config since negative differences are always possible even if the
> individual inputs are both positive.
> 
> > +		ad4851_channels++;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +/*
> > + * parse_channels() function handles the rest of the channel related
> attributes
> > + * that are usually are stored in the chip info structure.
> > + */
> > +static const struct ad4851_chip_info ad4851_info = {
> > +	.name = "ad4851",
> > +	.product_id = 0x67,
> > +	.max_sample_rate_hz = 250 * KILO,
> > +	.resolution = 16,
> > +	.parse_channels = ad4857_parse_channels,
> > +};
> > +
> > +static const struct ad4851_chip_info ad4852_info = {
> > +	.name = "ad4852",
> > +	.product_id = 0x66,
> > +	.max_sample_rate_hz = 250 * KILO,
> > +	.resolution = 20,
> > +	.parse_channels = ad4858_parse_channels,
> > +};
> > +
> > +static const struct ad4851_chip_info ad4853_info = {
> > +	.name = "ad4853",
> > +	.product_id = 0x65,
> > +	.max_sample_rate_hz = 1 * MEGA,
> > +	.resolution = 16,
> > +	.parse_channels = ad4857_parse_channels,
> > +};
> > +
> > +static const struct ad4851_chip_info ad4854_info = {
> > +	.name = "ad4854",
> > +	.product_id = 0x64,
> > +	.max_sample_rate_hz = 1 * MEGA,
> > +	.resolution = 20,
> > +	.parse_channels = ad4858_parse_channels,
> > +};
> > +
> > +static const struct ad4851_chip_info ad4855_info = {
> > +	.name = "ad4855",
> > +	.product_id = 0x63,
> > +	.max_sample_rate_hz = 250 * KILO,
> > +	.resolution = 16,
> > +	.parse_channels = ad4857_parse_channels,
> > +};
> > +
> > +static const struct ad4851_chip_info ad4856_info = {
> > +	.name = "ad4856",
> > +	.product_id = 0x62,
> > +	.max_sample_rate_hz = 250 * KILO,
> > +	.resolution = 20,
> > +	.parse_channels = ad4858_parse_channels,
> > +};
> > +
> > +static const struct ad4851_chip_info ad4857_info = {
> > +	.name = "ad4857",
> > +	.product_id = 0x61,
> > +	.max_sample_rate_hz = 1 * MEGA,
> > +	.resolution = 16,
> > +	.parse_channels = ad4857_parse_channels,
> > +};
> > +
> > +static const struct ad4851_chip_info ad4858_info = {
> > +	.name = "ad4858",
> > +	.product_id = 0x60,
> > +	.max_sample_rate_hz = 1 * MEGA,
> > +	.resolution = 20,
> > +	.parse_channels = ad4858_parse_channels,
> > +};
> > +
> > +static const struct ad4851_chip_info ad4858i_info = {
> > +	.name = "ad4858i",
> > +	.product_id = 0x6F,
> > +	.max_sample_rate_hz = 1 * MEGA,
> > +	.resolution = 20,
> > +	.parse_channels = ad4858_parse_channels,
> > +};
> > +
> > +static const struct iio_info ad4851_iio_info = {
> > +	.debugfs_reg_access = ad4851_reg_access,
> > +	.read_raw = ad4851_read_raw,
> > +	.write_raw = ad4851_write_raw,
> > +	.update_scan_mode = ad4851_update_scan_mode,
> > +	.get_current_scan_type = &ad4851_get_current_scan_type,
> 
> Odd &.
> 
> > +	.read_avail = ad4851_read_avail,
> > +};
> > +
> > +static const struct regmap_config regmap_config = {
> > +	.reg_bits = 16,
> > +	.val_bits = 8,
> > +	.read_flag_mask = BIT(7),
> > +};
> > +
> > +static const char * const ad4851_power_supplies[] = {
> > +	"vcc",	"vdd", "vee", "vio",
> > +};
> > +
> > +static int ad4851_probe(struct spi_device *spi)
> > +{
> > +	struct iio_dev *indio_dev;
> > +	struct device *dev = &spi->dev;
> > +	struct ad4851_state *st;
> > +	int ret;
> > +
> > +	indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> > +	if (!indio_dev)
> > +		return -ENOMEM;
> > +
> > +	st = iio_priv(indio_dev);
> > +	st->spi = spi;
> > +
> > +	ret = devm_mutex_init(dev, &st->lock);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = devm_regulator_bulk_get_enable(dev,
> > +
> ARRAY_SIZE(ad4851_power_supplies),
> > +					     ad4851_power_supplies);
> > +	if (ret)
> > +		return dev_err_probe(dev, ret,
> > +				     "failed to get and enable supplies\n");
> > +
> > +	ret = devm_regulator_get_enable_optional(dev, "vddh");
> > +	if (ret < 0 && ret != -ENODEV)
> > +		return dev_err_probe(dev, ret, "failed to enable vddh
> voltage\n");
> > +
> > +	ret = devm_regulator_get_enable_optional(dev, "vddl");
> > +	if (ret < 0 && ret != -ENODEV)
> > +		return dev_err_probe(dev, ret, "failed to enable vddl
> voltage\n");
> > +
> > +	ret = devm_regulator_get_enable_optional(dev, "vrefbuf");
> > +	if (ret < 0 && ret != -ENODEV)
> > +		return dev_err_probe(dev, ret, "failed to enable vrefbuf
> voltage\n");
> > +
> > +	if (ret > 0)
> > +		st->vrefbuf_en = true;
> > +	else
> > +		st->vrefbuf_en = false;
> > +
> > +	ret = devm_regulator_get_enable_optional(dev, "vrefio");
> > +	if (ret < 0 && ret != -ENODEV)
> > +		return dev_err_probe(dev, ret, "failed to enable vrefio
> voltage\n");
> > +
> > +	if (ret > 0)
> > +		st->vrefio_en = true;
> > +	else
> > +		st->vrefio_en = false;
> > +
> > +	st->pd_gpio = devm_gpiod_get_optional(dev, "pd",
> GPIOD_OUT_LOW);
> > +	if (IS_ERR(st->pd_gpio))
> > +		return dev_err_probe(dev, PTR_ERR(st->pd_gpio),
> > +				     "Error on requesting pd GPIO\n");
> > +
> > +	st->cnv = devm_pwm_get(dev, NULL);
> > +	if (IS_ERR(st->cnv))
> > +		return dev_err_probe(dev, PTR_ERR(st->cnv),
> > +				     "Error on requesting pwm\n");
> > +
> > +	st->info = spi_get_device_match_data(spi);
> > +	if (!st->info)
> > +		return -ENODEV;
> > +
> > +	st->regmap = devm_regmap_init_spi(spi, &regmap_config);
> > +	if (IS_ERR(st->regmap))
> > +		return PTR_ERR(st->regmap);
> > +
> > +	ret = ad4851_set_sampling_freq(st, HZ_PER_MHZ);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = devm_add_action_or_reset(&st->spi->dev,
> ad4851_pwm_disable,
> > +				       st->cnv);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = ad4851_setup(st);
> > +	if (ret)
> > +		return ret;
> > +
> > +	indio_dev->name = st->info->name;
> > +	indio_dev->info = &ad4851_iio_info;
> > +	indio_dev->modes = INDIO_DIRECT_MODE;
> > +
> > +	ret = st->info->parse_channels(indio_dev);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = ad4851_scale_fill(indio_dev);
> > +	if (ret)
> > +		return ret;
> > +
> > +	st->back = devm_iio_backend_get(dev, NULL);
> > +	if (IS_ERR(st->back))
> > +		return PTR_ERR(st->back);
> > +
> > +	ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = devm_iio_backend_enable(dev, st->back);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = ad4851_calibrate(indio_dev);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return devm_iio_device_register(dev, indio_dev);
> > +}
> > +
> > +static const struct of_device_id ad4851_of_match[] = {
> > +	{ .compatible = "adi,ad4851", .data = &ad4851_info, },
> > +	{ .compatible = "adi,ad4852", .data = &ad4852_info, },
> > +	{ .compatible = "adi,ad4853", .data = &ad4853_info, },
> > +	{ .compatible = "adi,ad4854", .data = &ad4854_info, },
> > +	{ .compatible = "adi,ad4855", .data = &ad4855_info, },
> > +	{ .compatible = "adi,ad4856", .data = &ad4856_info, },
> > +	{ .compatible = "adi,ad4857", .data = &ad4857_info, },
> > +	{ .compatible = "adi,ad4858", .data = &ad4858_info, },
> > +	{ .compatible = "adi,ad4858i", .data = &ad4858i_info, },
> > +	{ }
> > +};
> > +
> > +static const struct spi_device_id ad4851_spi_id[] = {
> > +	{ "ad4851", (kernel_ulong_t)&ad4851_info },
> > +	{ "ad4852", (kernel_ulong_t)&ad4852_info },
> > +	{ "ad4853", (kernel_ulong_t)&ad4853_info },
> > +	{ "ad4854", (kernel_ulong_t)&ad4854_info },
> > +	{ "ad4855", (kernel_ulong_t)&ad4855_info },
> > +	{ "ad4856", (kernel_ulong_t)&ad4856_info },
> > +	{ "ad4857", (kernel_ulong_t)&ad4857_info },
> > +	{ "ad4858", (kernel_ulong_t)&ad4858_info },
> > +	{ "ad4858i", (kernel_ulong_t)&ad4858i_info },
> > +	{ }
> > +};
> > +MODULE_DEVICE_TABLE(spi, ad4851_spi_id);
> > +
> > +static struct spi_driver ad4851_driver = {
> > +	.probe = ad4851_probe,
> > +	.driver = {
> > +		.name = "ad4851",
> > +		.of_match_table = ad4851_of_match,
> > +	},
> > +	.id_table = ad4851_spi_id,
> > +};
> > +module_spi_driver(ad4851_driver);
> > +
> > +MODULE_AUTHOR("Sergiu Cuciurean <sergiu.cuciurean@...log.com>");
> > +MODULE_AUTHOR("Dragos Bogdan <dragos.bogdan@...log.com>");
> > +MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@...log.com>");
> > +MODULE_DESCRIPTION("Analog Devices AD4851 DAS driver");
> > +MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_BACKEND");

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ