devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v9 1/8] iio: backend: add API for interface get
@ 2024-12-20 12:01 Antoniu Miclaus
  2024-12-20 12:01 ` [PATCH v9 2/8] iio: backend: add support for data size set Antoniu Miclaus
                   ` (7 more replies)
  0 siblings, 8 replies; 19+ messages in thread
From: Antoniu Miclaus @ 2024-12-20 12:01 UTC (permalink / raw)
  To: jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel,
	linux-pwm
  Cc: Antoniu Miclaus, David Lechner

Add backend support for obtaining the interface type used.

Reviewed-by: David Lechner <dlechner@baylibre.com>
Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
changes in v9:
 - rebase on iio/testing and use quotes for IIO_BACKEND
 drivers/iio/industrialio-backend.c | 24 ++++++++++++++++++++++++
 include/linux/iio/backend.h        | 11 +++++++++++
 2 files changed, 35 insertions(+)

diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c
index 363281272035..8bf3d570da1b 100644
--- a/drivers/iio/industrialio-backend.c
+++ b/drivers/iio/industrialio-backend.c
@@ -636,6 +636,30 @@ ssize_t iio_backend_ext_info_set(struct iio_dev *indio_dev, uintptr_t private,
 }
 EXPORT_SYMBOL_NS_GPL(iio_backend_ext_info_set, "IIO_BACKEND");
 
+/**
+ * iio_backend_interface_type_get - get the interface type used.
+ * @back: Backend device
+ * @type: Interface type
+ *
+ * RETURNS:
+ * 0 on success, negative error number on failure.
+ */
+int iio_backend_interface_type_get(struct iio_backend *back,
+				   enum iio_backend_interface_type *type)
+{
+	int ret;
+
+	ret = iio_backend_op_call(back, interface_type_get, type);
+	if (ret)
+		return ret;
+
+	if (*type >= IIO_BACKEND_INTERFACE_MAX)
+		return -EINVAL;
+
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(iio_backend_interface_type_get, "IIO_BACKEND");
+
 /**
  * iio_backend_extend_chan_spec - Extend an IIO channel
  * @back: Backend device
diff --git a/include/linux/iio/backend.h b/include/linux/iio/backend.h
index 10be00f3b120..a0ea6c29d7ba 100644
--- a/include/linux/iio/backend.h
+++ b/include/linux/iio/backend.h
@@ -70,6 +70,12 @@ enum iio_backend_sample_trigger {
 	IIO_BACKEND_SAMPLE_TRIGGER_MAX
 };
 
+enum iio_backend_interface_type {
+	IIO_BACKEND_INTERFACE_SERIAL_LVDS,
+	IIO_BACKEND_INTERFACE_SERIAL_CMOS,
+	IIO_BACKEND_INTERFACE_MAX
+};
+
 /**
  * struct iio_backend_ops - operations structure for an iio_backend
  * @enable: Enable backend.
@@ -88,6 +94,7 @@ enum iio_backend_sample_trigger {
  * @extend_chan_spec: Extend an IIO channel.
  * @ext_info_set: Extended info setter.
  * @ext_info_get: Extended info getter.
+ * @interface_type_get: Interface type.
  * @read_raw: Read a channel attribute from a backend device
  * @debugfs_print_chan_status: Print channel status into a buffer.
  * @debugfs_reg_access: Read or write register value of backend.
@@ -128,6 +135,8 @@ struct iio_backend_ops {
 			    const char *buf, size_t len);
 	int (*ext_info_get)(struct iio_backend *back, uintptr_t private,
 			    const struct iio_chan_spec *chan, char *buf);
+	int (*interface_type_get)(struct iio_backend *back,
+				  enum iio_backend_interface_type *type);
 	int (*read_raw)(struct iio_backend *back,
 			struct iio_chan_spec const *chan, int *val, int *val2,
 			long mask);
@@ -186,6 +195,8 @@ ssize_t iio_backend_ext_info_set(struct iio_dev *indio_dev, uintptr_t private,
 				 const char *buf, size_t len);
 ssize_t iio_backend_ext_info_get(struct iio_dev *indio_dev, uintptr_t private,
 				 const struct iio_chan_spec *chan, char *buf);
+int iio_backend_interface_type_get(struct iio_backend *back,
+				   enum iio_backend_interface_type *type);
 int iio_backend_read_raw(struct iio_backend *back,
 			 struct iio_chan_spec const *chan, int *val, int *val2,
 			 long mask);
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v9 2/8] iio: backend: add support for data size set
  2024-12-20 12:01 [PATCH v9 1/8] iio: backend: add API for interface get Antoniu Miclaus
@ 2024-12-20 12:01 ` Antoniu Miclaus
  2024-12-20 12:01 ` [PATCH v9 3/8] iio: backend: add API for oversampling Antoniu Miclaus
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 19+ messages in thread
From: Antoniu Miclaus @ 2024-12-20 12:01 UTC (permalink / raw)
  To: jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel,
	linux-pwm
  Cc: Antoniu Miclaus, David Lechner

Add backend support for setting the data size used.
This setting can be adjusted within the IP cores interfacing devices.

Reviewed-by: David Lechner <dlechner@baylibre.com>
Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
no changes in v9.
 drivers/iio/industrialio-backend.c | 21 +++++++++++++++++++++
 include/linux/iio/backend.h        |  3 +++
 2 files changed, 24 insertions(+)

diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c
index 8bf3d570da1b..2088afa7a55c 100644
--- a/drivers/iio/industrialio-backend.c
+++ b/drivers/iio/industrialio-backend.c
@@ -660,6 +660,27 @@ int iio_backend_interface_type_get(struct iio_backend *back,
 }
 EXPORT_SYMBOL_NS_GPL(iio_backend_interface_type_get, "IIO_BACKEND");
 
+/**
+ * iio_backend_data_size_set - set the data width/size in the data bus.
+ * @back: Backend device
+ * @size: Size in bits
+ *
+ * Some frontend devices can dynamically control the word/data size on the
+ * interface/data bus. Hence, the backend device needs to be aware of it so
+ * data can be correctly transferred.
+ *
+ * Return:
+ * 0 on success, negative error number on failure.
+ */
+int iio_backend_data_size_set(struct iio_backend *back, unsigned int size)
+{
+	if (!size)
+		return -EINVAL;
+
+	return iio_backend_op_call(back, data_size_set, size);
+}
+EXPORT_SYMBOL_NS_GPL(iio_backend_data_size_set, "IIO_BACKEND");
+
 /**
  * iio_backend_extend_chan_spec - Extend an IIO channel
  * @back: Backend device
diff --git a/include/linux/iio/backend.h b/include/linux/iio/backend.h
index a0ea6c29d7ba..9ae861a21472 100644
--- a/include/linux/iio/backend.h
+++ b/include/linux/iio/backend.h
@@ -95,6 +95,7 @@ enum iio_backend_interface_type {
  * @ext_info_set: Extended info setter.
  * @ext_info_get: Extended info getter.
  * @interface_type_get: Interface type.
+ * @data_size_set: Data size.
  * @read_raw: Read a channel attribute from a backend device
  * @debugfs_print_chan_status: Print channel status into a buffer.
  * @debugfs_reg_access: Read or write register value of backend.
@@ -137,6 +138,7 @@ struct iio_backend_ops {
 			    const struct iio_chan_spec *chan, char *buf);
 	int (*interface_type_get)(struct iio_backend *back,
 				  enum iio_backend_interface_type *type);
+	int (*data_size_set)(struct iio_backend *back, unsigned int size);
 	int (*read_raw)(struct iio_backend *back,
 			struct iio_chan_spec const *chan, int *val, int *val2,
 			long mask);
@@ -197,6 +199,7 @@ ssize_t iio_backend_ext_info_get(struct iio_dev *indio_dev, uintptr_t private,
 				 const struct iio_chan_spec *chan, char *buf);
 int iio_backend_interface_type_get(struct iio_backend *back,
 				   enum iio_backend_interface_type *type);
+int iio_backend_data_size_set(struct iio_backend *back, unsigned int size);
 int iio_backend_read_raw(struct iio_backend *back,
 			 struct iio_chan_spec const *chan, int *val, int *val2,
 			 long mask);
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v9 3/8] iio: backend: add API for oversampling
  2024-12-20 12:01 [PATCH v9 1/8] iio: backend: add API for interface get Antoniu Miclaus
  2024-12-20 12:01 ` [PATCH v9 2/8] iio: backend: add support for data size set Antoniu Miclaus
@ 2024-12-20 12:01 ` Antoniu Miclaus
  2024-12-20 12:01 ` [PATCH v9 4/8] iio: adc: adi-axi-adc: add interface type Antoniu Miclaus
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 19+ messages in thread
From: Antoniu Miclaus @ 2024-12-20 12:01 UTC (permalink / raw)
  To: jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel,
	linux-pwm
  Cc: Antoniu Miclaus, David Lechner

Add backend support for setting oversampling ratio.

Reviewed-by: David Lechner <dlechner@baylibre.com>
Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
changes in v9:
 - use quotes on "IIO_BACKEND"
 drivers/iio/industrialio-backend.c | 15 +++++++++++++++
 include/linux/iio/backend.h        |  5 +++++
 2 files changed, 20 insertions(+)

diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c
index 2088afa7a55c..d4ad36f54090 100644
--- a/drivers/iio/industrialio-backend.c
+++ b/drivers/iio/industrialio-backend.c
@@ -681,6 +681,21 @@ int iio_backend_data_size_set(struct iio_backend *back, unsigned int size)
 }
 EXPORT_SYMBOL_NS_GPL(iio_backend_data_size_set, "IIO_BACKEND");
 
+/**
+ * iio_backend_oversampling_ratio_set - set the oversampling ratio
+ * @back: Backend device
+ * @ratio: The oversampling ratio - value 1 corresponds to no oversampling.
+ *
+ * Return:
+ * 0 on success, negative error number on failure.
+ */
+int iio_backend_oversampling_ratio_set(struct iio_backend *back,
+				       unsigned int ratio)
+{
+	return iio_backend_op_call(back, oversampling_ratio_set, ratio);
+}
+EXPORT_SYMBOL_NS_GPL(iio_backend_oversampling_ratio_set, "IIO_BACKEND");
+
 /**
  * iio_backend_extend_chan_spec - Extend an IIO channel
  * @back: Backend device
diff --git a/include/linux/iio/backend.h b/include/linux/iio/backend.h
index 9ae861a21472..e45b7dfbec35 100644
--- a/include/linux/iio/backend.h
+++ b/include/linux/iio/backend.h
@@ -96,6 +96,7 @@ enum iio_backend_interface_type {
  * @ext_info_get: Extended info getter.
  * @interface_type_get: Interface type.
  * @data_size_set: Data size.
+ * @oversampling_ratio_set: Set Oversampling ratio.
  * @read_raw: Read a channel attribute from a backend device
  * @debugfs_print_chan_status: Print channel status into a buffer.
  * @debugfs_reg_access: Read or write register value of backend.
@@ -139,6 +140,8 @@ struct iio_backend_ops {
 	int (*interface_type_get)(struct iio_backend *back,
 				  enum iio_backend_interface_type *type);
 	int (*data_size_set)(struct iio_backend *back, unsigned int size);
+	int (*oversampling_ratio_set)(struct iio_backend *back,
+				      unsigned int ratio);
 	int (*read_raw)(struct iio_backend *back,
 			struct iio_chan_spec const *chan, int *val, int *val2,
 			long mask);
@@ -200,6 +203,8 @@ ssize_t iio_backend_ext_info_get(struct iio_dev *indio_dev, uintptr_t private,
 int iio_backend_interface_type_get(struct iio_backend *back,
 				   enum iio_backend_interface_type *type);
 int iio_backend_data_size_set(struct iio_backend *back, unsigned int size);
+int iio_backend_oversampling_ratio_set(struct iio_backend *back,
+				       unsigned int ratio);
 int iio_backend_read_raw(struct iio_backend *back,
 			 struct iio_chan_spec const *chan, int *val, int *val2,
 			 long mask);
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v9 4/8] iio: adc: adi-axi-adc: add interface type
  2024-12-20 12:01 [PATCH v9 1/8] iio: backend: add API for interface get Antoniu Miclaus
  2024-12-20 12:01 ` [PATCH v9 2/8] iio: backend: add support for data size set Antoniu Miclaus
  2024-12-20 12:01 ` [PATCH v9 3/8] iio: backend: add API for oversampling Antoniu Miclaus
@ 2024-12-20 12:01 ` Antoniu Miclaus
  2024-12-20 12:01 ` [PATCH v9 5/8] iio: adc: adi-axi-adc: set data format Antoniu Miclaus
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 19+ messages in thread
From: Antoniu Miclaus @ 2024-12-20 12:01 UTC (permalink / raw)
  To: jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel,
	linux-pwm
  Cc: Antoniu Miclaus, David Lechner

Add support for getting the interface (CMOS or LVDS) used by the AXI ADC
IP.

Reviewed-by: David Lechner <dlechner@baylibre.com>
Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
no changes in v9.
 drivers/iio/adc/adi-axi-adc.c | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c
index c7357601f0f8..d2e1dc63775c 100644
--- a/drivers/iio/adc/adi-axi-adc.c
+++ b/drivers/iio/adc/adi-axi-adc.c
@@ -39,6 +39,9 @@
 #define   ADI_AXI_REG_RSTN_MMCM_RSTN		BIT(1)
 #define   ADI_AXI_REG_RSTN_RSTN			BIT(0)
 
+#define ADI_AXI_ADC_REG_CONFIG			0x000c
+#define   ADI_AXI_ADC_REG_CONFIG_CMOS_OR_LVDS_N	BIT(7)
+
 #define ADI_AXI_ADC_REG_CTRL			0x0044
 #define    ADI_AXI_ADC_CTRL_DDR_EDGESEL_MASK	BIT(1)
 
@@ -290,6 +293,25 @@ static int axi_adc_chan_disable(struct iio_backend *back, unsigned int chan)
 				 ADI_AXI_REG_CHAN_CTRL_ENABLE);
 }
 
+static int axi_adc_interface_type_get(struct iio_backend *back,
+				      enum iio_backend_interface_type *type)
+{
+	struct adi_axi_adc_state *st = iio_backend_get_priv(back);
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(st->regmap, ADI_AXI_ADC_REG_CONFIG, &val);
+	if (ret)
+		return ret;
+
+	if (val & ADI_AXI_ADC_REG_CONFIG_CMOS_OR_LVDS_N)
+		*type = IIO_BACKEND_INTERFACE_SERIAL_CMOS;
+	else
+		*type = IIO_BACKEND_INTERFACE_SERIAL_LVDS;
+
+	return 0;
+}
+
 static struct iio_buffer *axi_adc_request_buffer(struct iio_backend *back,
 						 struct iio_dev *indio_dev)
 {
@@ -337,6 +359,7 @@ static const struct iio_backend_ops adi_axi_adc_ops = {
 	.iodelay_set = axi_adc_iodelays_set,
 	.test_pattern_set = axi_adc_test_pattern_set,
 	.chan_status = axi_adc_chan_status,
+	.interface_type_get = axi_adc_interface_type_get,
 	.debugfs_reg_access = iio_backend_debugfs_ptr(axi_adc_reg_access),
 	.debugfs_print_chan_status = iio_backend_debugfs_ptr(axi_adc_debugfs_print_chan_status),
 };
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v9 5/8] iio: adc: adi-axi-adc: set data format
  2024-12-20 12:01 [PATCH v9 1/8] iio: backend: add API for interface get Antoniu Miclaus
                   ` (2 preceding siblings ...)
  2024-12-20 12:01 ` [PATCH v9 4/8] iio: adc: adi-axi-adc: add interface type Antoniu Miclaus
@ 2024-12-20 12:01 ` Antoniu Miclaus
  2024-12-20 12:01 ` [PATCH v9 6/8] iio: adc: adi-axi-adc: add oversampling Antoniu Miclaus
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 19+ messages in thread
From: Antoniu Miclaus @ 2024-12-20 12:01 UTC (permalink / raw)
  To: jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel,
	linux-pwm
  Cc: Antoniu Miclaus

Add support for selecting the data format within the AXI ADC ip.

Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
changes in v9:
 - use shorter naming for macro definitions.
 drivers/iio/adc/adi-axi-adc.c | 46 +++++++++++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)

diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c
index d2e1dc63775c..3c213ca5ff8e 100644
--- a/drivers/iio/adc/adi-axi-adc.c
+++ b/drivers/iio/adc/adi-axi-adc.c
@@ -45,6 +45,12 @@
 #define ADI_AXI_ADC_REG_CTRL			0x0044
 #define    ADI_AXI_ADC_CTRL_DDR_EDGESEL_MASK	BIT(1)
 
+#define ADI_AXI_ADC_REG_CNTRL_3			0x004c
+#define   AD485X_CNTRL_3_PACKET_FORMAT_MSK	GENMASK(1, 0)
+#define   AD485X_PACKET_FORMAT_20BIT		0x0
+#define   AD485X_PACKET_FORMAT_24BIT		0x1
+#define   AD485X_PACKET_FORMAT_32BIT		0x2
+
 #define ADI_AXI_ADC_REG_DRP_STATUS		0x0074
 #define   ADI_AXI_ADC_DRP_LOCKED		BIT(17)
 
@@ -312,6 +318,45 @@ static int axi_adc_interface_type_get(struct iio_backend *back,
 	return 0;
 }
 
+static int axi_adc_data_size_set(struct iio_backend *back, unsigned int size)
+{
+	struct adi_axi_adc_state *st = iio_backend_get_priv(back);
+	unsigned int val;
+
+	switch (size) {
+	/*
+	 * There are two different variants of the AXI AD485X IP block, a 16-bit
+	 * and a 20-bit variant.
+	 * The 0x0 value (AD485X_PACKET_FORMAT_20BIT) is corresponding also to
+	 * the 16-bit variant of the IP block.
+	 */
+	case 16:
+	case 20:
+		val = AD485X_PACKET_FORMAT_20BIT;
+		break;
+	case 24:
+		val = AD485X_PACKET_FORMAT_24BIT;
+		break;
+	/*
+	 * The 0x2 (AD485X_PACKET_FORMAT_32BIT) corresponds only to the 20-bit
+	 * variant of the IP block. Setting this value properly is ensured by
+	 * the upper layers of the drivers calling the axi-adc functions.
+	 * Also, for 16-bit IP block, the 0x2 (AD485X_PACKET_FORMAT_32BIT)
+	 * value is handled as maximum size available which is 24-bit for this
+	 * configuration.
+	 */
+	case 32:
+		val = AD485X_PACKET_FORMAT_32BIT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return regmap_update_bits(st->regmap, ADI_AXI_ADC_REG_CNTRL_3,
+				  AD485X_CNTRL_3_PACKET_FORMAT_MSK,
+				  FIELD_PREP(AD485X_CNTRL_3_PACKET_FORMAT_MSK, val));
+}
+
 static struct iio_buffer *axi_adc_request_buffer(struct iio_backend *back,
 						 struct iio_dev *indio_dev)
 {
@@ -360,6 +405,7 @@ static const struct iio_backend_ops adi_axi_adc_ops = {
 	.test_pattern_set = axi_adc_test_pattern_set,
 	.chan_status = axi_adc_chan_status,
 	.interface_type_get = axi_adc_interface_type_get,
+	.data_size_set = axi_adc_data_size_set,
 	.debugfs_reg_access = iio_backend_debugfs_ptr(axi_adc_reg_access),
 	.debugfs_print_chan_status = iio_backend_debugfs_ptr(axi_adc_debugfs_print_chan_status),
 };
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v9 6/8] iio: adc: adi-axi-adc: add oversampling
  2024-12-20 12:01 [PATCH v9 1/8] iio: backend: add API for interface get Antoniu Miclaus
                   ` (3 preceding siblings ...)
  2024-12-20 12:01 ` [PATCH v9 5/8] iio: adc: adi-axi-adc: set data format Antoniu Miclaus
@ 2024-12-20 12:01 ` Antoniu Miclaus
  2024-12-20 12:01 ` [PATCH v9 7/8] dt-bindings: iio: adc: add ad4851 Antoniu Miclaus
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 19+ messages in thread
From: Antoniu Miclaus @ 2024-12-20 12:01 UTC (permalink / raw)
  To: jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel,
	linux-pwm
  Cc: Antoniu Miclaus

Add support for enabling/disabling oversampling.

Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
no changes in v9.
 drivers/iio/adc/adi-axi-adc.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c
index 3c213ca5ff8e..ce88650bbb62 100644
--- a/drivers/iio/adc/adi-axi-adc.c
+++ b/drivers/iio/adc/adi-axi-adc.c
@@ -46,6 +46,7 @@
 #define    ADI_AXI_ADC_CTRL_DDR_EDGESEL_MASK	BIT(1)
 
 #define ADI_AXI_ADC_REG_CNTRL_3			0x004c
+#define   AD485X_CNTRL_3_OS_EN_MSK		BIT(2)
 #define   AD485X_CNTRL_3_PACKET_FORMAT_MSK	GENMASK(1, 0)
 #define   AD485X_PACKET_FORMAT_20BIT		0x0
 #define   AD485X_PACKET_FORMAT_24BIT		0x1
@@ -357,6 +358,23 @@ static int axi_adc_data_size_set(struct iio_backend *back, unsigned int size)
 				  FIELD_PREP(AD485X_CNTRL_3_PACKET_FORMAT_MSK, val));
 }
 
+static int axi_adc_oversampling_ratio_set(struct iio_backend *back,
+					  unsigned int ratio)
+{
+	struct adi_axi_adc_state *st = iio_backend_get_priv(back);
+
+	switch (ratio) {
+	case 0:
+		return -EINVAL;
+	case 1:
+		return regmap_clear_bits(st->regmap, ADI_AXI_ADC_REG_CNTRL_3,
+					 AD485X_CNTRL_3_OS_EN_MSK);
+	default:
+		return regmap_set_bits(st->regmap, ADI_AXI_ADC_REG_CNTRL_3,
+				       AD485X_CNTRL_3_OS_EN_MSK);
+	}
+}
+
 static struct iio_buffer *axi_adc_request_buffer(struct iio_backend *back,
 						 struct iio_dev *indio_dev)
 {
@@ -406,6 +424,7 @@ static const struct iio_backend_ops adi_axi_adc_ops = {
 	.chan_status = axi_adc_chan_status,
 	.interface_type_get = axi_adc_interface_type_get,
 	.data_size_set = axi_adc_data_size_set,
+	.oversampling_ratio_set = axi_adc_oversampling_ratio_set,
 	.debugfs_reg_access = iio_backend_debugfs_ptr(axi_adc_reg_access),
 	.debugfs_print_chan_status = iio_backend_debugfs_ptr(axi_adc_debugfs_print_chan_status),
 };
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v9 7/8] dt-bindings: iio: adc: add ad4851
  2024-12-20 12:01 [PATCH v9 1/8] iio: backend: add API for interface get Antoniu Miclaus
                   ` (4 preceding siblings ...)
  2024-12-20 12:01 ` [PATCH v9 6/8] iio: adc: adi-axi-adc: add oversampling Antoniu Miclaus
@ 2024-12-20 12:01 ` Antoniu Miclaus
  2024-12-23 11:40   ` Jonathan Cameron
  2024-12-20 12:01 ` [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver Antoniu Miclaus
  2024-12-23 11:33 ` [PATCH v9 1/8] iio: backend: add API for interface get Jonathan Cameron
  7 siblings, 1 reply; 19+ messages in thread
From: Antoniu Miclaus @ 2024-12-20 12:01 UTC (permalink / raw)
  To: jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel,
	linux-pwm
  Cc: Antoniu Miclaus, Conor Dooley

Add devicetree bindings for ad485x family.

Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
changes in v9:
 - add back diff-channels property.
 .../bindings/iio/adc/adi,ad4851.yaml          | 142 ++++++++++++++++++
 1 file changed, 142 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4851.yaml

diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4851.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4851.yaml
new file mode 100644
index 000000000000..5911f1cfc39d
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4851.yaml
@@ -0,0 +1,142 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright 2024 Analog Devices Inc.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/adi,ad4851.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices AD485X family
+
+maintainers:
+  - Sergiu Cuciurean <sergiu.cuciurean@analog.com>
+  - Dragos Bogdan <dragos.bogdan@analog.com>
+  - Antoniu Miclaus <antoniu.miclaus@analog.com>
+
+description: |
+  Analog Devices AD485X fully buffered, 8-channel simultaneous sampling,
+  16/20-bit, 1 MSPS data acquisition system (DAS) with differential, wide
+  common-mode range inputs.
+
+  https://www.analog.com/media/en/technical-documentation/data-sheets/ad4855.pdf
+  https://www.analog.com/media/en/technical-documentation/data-sheets/ad4856.pdf
+  https://www.analog.com/media/en/technical-documentation/data-sheets/ad4857.pdf
+  https://www.analog.com/media/en/technical-documentation/data-sheets/ad4858.pdf
+
+$ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+  compatible:
+    enum:
+      - adi,ad4851
+      - adi,ad4852
+      - adi,ad4853
+      - adi,ad4854
+      - adi,ad4855
+      - adi,ad4856
+      - adi,ad4857
+      - adi,ad4858
+      - adi,ad4858i
+
+  reg:
+    maxItems: 1
+
+  vcc-supply: true
+
+  vee-supply: true
+
+  vdd-supply: true
+
+  vddh-supply: true
+
+  vddl-supply: true
+
+  vio-supply: true
+
+  vrefbuf-supply: true
+
+  vrefio-supply: true
+
+  pwms:
+    description: PWM connected to the CNV pin.
+    maxItems: 1
+
+  io-backends:
+    maxItems: 1
+
+  pd-gpios:
+    maxItems: 1
+
+  spi-max-frequency:
+    maximum: 25000000
+
+  '#address-cells':
+    const: 1
+
+  '#size-cells':
+    const: 0
+
+patternProperties:
+  "^channel(@[0-7])?$":
+    $ref: adc.yaml
+    type: object
+    description: Represents the channels which are connected to the ADC.
+
+    properties:
+      reg:
+        description: The channel number in single-ended mode.
+        minimum: 0
+        maximum: 7
+
+      diff-channels: true
+
+      bipolar: true
+
+    required:
+      - reg
+
+    additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - vcc-supply
+  - vee-supply
+  - vdd-supply
+  - vio-supply
+  - pwms
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        adc@0{
+            #address-cells = <1>;
+            #size-cells = <0>;
+            compatible = "adi,ad4858";
+            reg = <0>;
+            spi-max-frequency = <10000000>;
+            vcc-supply = <&vcc>;
+            vdd-supply = <&vdd>;
+            vee-supply = <&vee>;
+            vddh-supply = <&vddh>;
+            vddl-supply = <&vddl>;
+            vio-supply = <&vio>;
+            pwms = <&pwm_gen 0 0>;
+            io-backends = <&iio_backend>;
+
+            channel@0 {
+              reg = <0>;
+              diff-channels = <0 8>;
+              bipolar;
+            };
+
+            channel@1 {
+              reg = <1>;
+            };
+        };
+    };
+...
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver
  2024-12-20 12:01 [PATCH v9 1/8] iio: backend: add API for interface get Antoniu Miclaus
                   ` (5 preceding siblings ...)
  2024-12-20 12:01 ` [PATCH v9 7/8] dt-bindings: iio: adc: add ad4851 Antoniu Miclaus
@ 2024-12-20 12:01 ` Antoniu Miclaus
  2024-12-23 12:00   ` Jonathan Cameron
                     ` (2 more replies)
  2024-12-23 11:33 ` [PATCH v9 1/8] iio: backend: add API for interface get Jonathan Cameron
  7 siblings, 3 replies; 19+ messages in thread
From: Antoniu Miclaus @ 2024-12-20 12:01 UTC (permalink / raw)
  To: jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel,
	linux-pwm
  Cc: Antoniu Miclaus

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@analog.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
+	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[] = {
+	{ 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[] = {
+	{ 5000, 0x1 },
+	{ 10000, 0x3 },
+	{ 12500, 0x5 },
+	{ 20000, 0x7 },
+	{ 25000, 0x9 },
+	{ 40000, 0xB },
+	{ 50000, 0xD },
+	{ 80000, 0xF },
+};
+
+static const unsigned int ad4851_scale_avail_se[] = {
+	2500,
+	5000,
+	6250,
+	10000,
+	12500,
+	20000,
+	25000,
+	40000,
+};
+
+static const unsigned int ad4851_scale_avail_diff[] = {
+	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,
+					 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;
+	}
+
+	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);
+		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 };
+	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) {
+		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;
+	int ret;
+
+	if (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;
+		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);
+	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),		\
+	.indexed = 1,								\
+	.differential = diff,							\
+	.channel = ch,								\
+	.channel2 = ch + (diff * 8),						\
+	.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',					\
+		.realbits = 16,							\
+		.storagebits = 16,						\
+	},									\
+}
+
+static int ad4851_parse_channels(struct iio_dev *indio_dev,
+				 struct iio_chan_spec **ad4851_channels,
+				 const struct iio_chan_spec ad4851_chan,
+				 const struct iio_chan_spec ad4851_chan_diff)
+{
+	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);
+
+	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) {
+		ret = fwnode_property_read_u32(child, "reg", &reg);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "Missing channel number\n");
+		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;
+
+		} else {
+			*channels = ad4851_chan;
+			channels->scan_index = index++;
+			channels->channel = reg;
+		}
+		channels++;
+
+		if (fwnode_property_present(child, "bipolar")) {
+			st->bipolar_ch[reg] = true;
+		} 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");
+		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);
+		}
+		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,
+	.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@analog.com>");
+MODULE_AUTHOR("Dragos Bogdan <dragos.bogdan@analog.com>");
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD4851 DAS driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_BACKEND");
-- 
2.47.1


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* Re: [PATCH v9 1/8] iio: backend: add API for interface get
  2024-12-20 12:01 [PATCH v9 1/8] iio: backend: add API for interface get Antoniu Miclaus
                   ` (6 preceding siblings ...)
  2024-12-20 12:01 ` [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver Antoniu Miclaus
@ 2024-12-23 11:33 ` Jonathan Cameron
  7 siblings, 0 replies; 19+ messages in thread
From: Jonathan Cameron @ 2024-12-23 11:33 UTC (permalink / raw)
  To: Antoniu Miclaus
  Cc: robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm,
	David Lechner

On Fri, 20 Dec 2024 14:01:27 +0200
Antoniu Miclaus <antoniu.miclaus@analog.com> wrote:

> Add backend support for obtaining the interface type used.
> 
> Reviewed-by: David Lechner <dlechner@baylibre.com>
> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
Hi Antoniu,

There was a specific request in v8 reviews for a cover letter with
reasoning for some of the recent changes.  I'm not seeing that so
perhaps just reply with a cover letter type email to this first patch.

Given David's done some good review of this I'll be waiting for
his replies anyway before considering pick it up and perhaps such
an email will answer some questions before they are asked!

FWIW, this patch is fine, I'm just replying here as no cover letter
to reply to!

Jonathan

> ---
> changes in v9:
>  - rebase on iio/testing and use quotes for IIO_BACKEND
>  drivers/iio/industrialio-backend.c | 24 ++++++++++++++++++++++++
>  include/linux/iio/backend.h        | 11 +++++++++++
>  2 files changed, 35 insertions(+)
> 
> diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c
> index 363281272035..8bf3d570da1b 100644
> --- a/drivers/iio/industrialio-backend.c
> +++ b/drivers/iio/industrialio-backend.c
> @@ -636,6 +636,30 @@ ssize_t iio_backend_ext_info_set(struct iio_dev *indio_dev, uintptr_t private,
>  }
>  EXPORT_SYMBOL_NS_GPL(iio_backend_ext_info_set, "IIO_BACKEND");
>  
> +/**
> + * iio_backend_interface_type_get - get the interface type used.
> + * @back: Backend device
> + * @type: Interface type
> + *
> + * RETURNS:
> + * 0 on success, negative error number on failure.
> + */
> +int iio_backend_interface_type_get(struct iio_backend *back,
> +				   enum iio_backend_interface_type *type)
> +{
> +	int ret;
> +
> +	ret = iio_backend_op_call(back, interface_type_get, type);
> +	if (ret)
> +		return ret;
> +
> +	if (*type >= IIO_BACKEND_INTERFACE_MAX)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(iio_backend_interface_type_get, "IIO_BACKEND");
> +
>  /**
>   * iio_backend_extend_chan_spec - Extend an IIO channel
>   * @back: Backend device
> diff --git a/include/linux/iio/backend.h b/include/linux/iio/backend.h
> index 10be00f3b120..a0ea6c29d7ba 100644
> --- a/include/linux/iio/backend.h
> +++ b/include/linux/iio/backend.h
> @@ -70,6 +70,12 @@ enum iio_backend_sample_trigger {
>  	IIO_BACKEND_SAMPLE_TRIGGER_MAX
>  };
>  
> +enum iio_backend_interface_type {
> +	IIO_BACKEND_INTERFACE_SERIAL_LVDS,
> +	IIO_BACKEND_INTERFACE_SERIAL_CMOS,
> +	IIO_BACKEND_INTERFACE_MAX
> +};
> +
>  /**
>   * struct iio_backend_ops - operations structure for an iio_backend
>   * @enable: Enable backend.
> @@ -88,6 +94,7 @@ enum iio_backend_sample_trigger {
>   * @extend_chan_spec: Extend an IIO channel.
>   * @ext_info_set: Extended info setter.
>   * @ext_info_get: Extended info getter.
> + * @interface_type_get: Interface type.
>   * @read_raw: Read a channel attribute from a backend device
>   * @debugfs_print_chan_status: Print channel status into a buffer.
>   * @debugfs_reg_access: Read or write register value of backend.
> @@ -128,6 +135,8 @@ struct iio_backend_ops {
>  			    const char *buf, size_t len);
>  	int (*ext_info_get)(struct iio_backend *back, uintptr_t private,
>  			    const struct iio_chan_spec *chan, char *buf);
> +	int (*interface_type_get)(struct iio_backend *back,
> +				  enum iio_backend_interface_type *type);
>  	int (*read_raw)(struct iio_backend *back,
>  			struct iio_chan_spec const *chan, int *val, int *val2,
>  			long mask);
> @@ -186,6 +195,8 @@ ssize_t iio_backend_ext_info_set(struct iio_dev *indio_dev, uintptr_t private,
>  				 const char *buf, size_t len);
>  ssize_t iio_backend_ext_info_get(struct iio_dev *indio_dev, uintptr_t private,
>  				 const struct iio_chan_spec *chan, char *buf);
> +int iio_backend_interface_type_get(struct iio_backend *back,
> +				   enum iio_backend_interface_type *type);
>  int iio_backend_read_raw(struct iio_backend *back,
>  			 struct iio_chan_spec const *chan, int *val, int *val2,
>  			 long mask);


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v9 7/8] dt-bindings: iio: adc: add ad4851
  2024-12-20 12:01 ` [PATCH v9 7/8] dt-bindings: iio: adc: add ad4851 Antoniu Miclaus
@ 2024-12-23 11:40   ` Jonathan Cameron
  2025-01-08 16:48     ` David Lechner
  0 siblings, 1 reply; 19+ messages in thread
From: Jonathan Cameron @ 2024-12-23 11:40 UTC (permalink / raw)
  To: Antoniu Miclaus
  Cc: robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm,
	Conor Dooley

On Fri, 20 Dec 2024 14:01:33 +0200
Antoniu Miclaus <antoniu.miclaus@analog.com> wrote:

> Add devicetree bindings for ad485x family.
> 
> Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
Mixed single end and differential channel devices that have per channel
child nodes need a more complex description to avoid reg have different
and possibly clashing meanings for the two types of channel.

See inline.

Thanks,

Jonathan

> ---
> changes in v9:
>  - add back diff-channels property.
>  .../bindings/iio/adc/adi,ad4851.yaml          | 142 ++++++++++++++++++
>  1 file changed, 142 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4851.yaml
> 
> diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4851.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4851.yaml
> new file mode 100644
> index 000000000000..5911f1cfc39d
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4851.yaml
> @@ -0,0 +1,142 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +# Copyright 2024 Analog Devices Inc.
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/adc/adi,ad4851.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices AD485X family
> +
> +maintainers:
> +  - Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> +  - Dragos Bogdan <dragos.bogdan@analog.com>
> +  - Antoniu Miclaus <antoniu.miclaus@analog.com>
> +
> +description: |
> +  Analog Devices AD485X fully buffered, 8-channel simultaneous sampling,
> +  16/20-bit, 1 MSPS data acquisition system (DAS) with differential, wide
> +  common-mode range inputs.
> +
> +  https://www.analog.com/media/en/technical-documentation/data-sheets/ad4855.pdf
> +  https://www.analog.com/media/en/technical-documentation/data-sheets/ad4856.pdf
> +  https://www.analog.com/media/en/technical-documentation/data-sheets/ad4857.pdf
> +  https://www.analog.com/media/en/technical-documentation/data-sheets/ad4858.pdf
> +
> +$ref: /schemas/spi/spi-peripheral-props.yaml#
> +
> +properties:
> +  compatible:
> +    enum:
> +      - adi,ad4851
> +      - adi,ad4852
> +      - adi,ad4853
> +      - adi,ad4854
> +      - adi,ad4855
> +      - adi,ad4856
> +      - adi,ad4857
> +      - adi,ad4858
> +      - adi,ad4858i
> +
> +  reg:
> +    maxItems: 1
> +
> +  vcc-supply: true
> +
> +  vee-supply: true
> +
> +  vdd-supply: true
> +
> +  vddh-supply: true
> +
> +  vddl-supply: true
> +
> +  vio-supply: true
> +
> +  vrefbuf-supply: true
> +
> +  vrefio-supply: true
> +
> +  pwms:
> +    description: PWM connected to the CNV pin.
> +    maxItems: 1
> +
> +  io-backends:
> +    maxItems: 1
> +
> +  pd-gpios:
> +    maxItems: 1
> +
> +  spi-max-frequency:
> +    maximum: 25000000
> +
> +  '#address-cells':
> +    const: 1
> +
> +  '#size-cells':
> +    const: 0
> +
> +patternProperties:
> +  "^channel(@[0-7])?$":
> +    $ref: adc.yaml
> +    type: object
> +    description: Represents the channels which are connected to the ADC.
> +
> +    properties:
> +      reg:
> +        description: The channel number in single-ended mode.

And what is it in differential mode?  (usually just an index).
Which is a problem if you have a mix.  That's why we added
single-channel.

So for devices where reg isn't enough, you can use:
reg: index of channel, not real meaning just makes them unique.
single-channel: The channel number for single ended.
diff-channels: The channel numbers for differential channels.

> +        minimum: 0
> +        maximum: 7
> +
> +      diff-channels: true

Should be constraints / information on the values possible.
I've no idea what the 8 is in your example for instance.

> +
> +      bipolar: true
> +
> +    required:
> +      - reg
> +
> +    additionalProperties: false
> +
> +required:
> +  - compatible
> +  - reg
> +  - vcc-supply
> +  - vee-supply
> +  - vdd-supply
> +  - vio-supply
> +  - pwms
> +
> +unevaluatedProperties: false
> +
> +examples:
> +  - |
> +    spi {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        adc@0{
> +            #address-cells = <1>;
> +            #size-cells = <0>;
> +            compatible = "adi,ad4858";
> +            reg = <0>;
> +            spi-max-frequency = <10000000>;
> +            vcc-supply = <&vcc>;
> +            vdd-supply = <&vdd>;
> +            vee-supply = <&vee>;
> +            vddh-supply = <&vddh>;
> +            vddl-supply = <&vddl>;
> +            vio-supply = <&vio>;
> +            pwms = <&pwm_gen 0 0>;
> +            io-backends = <&iio_backend>;
> +
> +            channel@0 {
> +              reg = <0>;
> +              diff-channels = <0 8>;
> +              bipolar;
> +            };
> +
> +            channel@1 {
> +              reg = <1>;
> +            };
> +        };
> +    };
> +...


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver
  2024-12-20 12:01 ` [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver Antoniu Miclaus
@ 2024-12-23 12:00   ` Jonathan Cameron
  2025-01-08 17:06     ` David Lechner
  2025-01-08 23:24   ` David Lechner
  2025-01-17  9:59   ` Uwe Kleine-König
  2 siblings, 1 reply; 19+ messages in thread
From: Jonathan Cameron @ 2024-12-23 12:00 UTC (permalink / raw)
  To: Antoniu Miclaus
  Cc: robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm

On Fri, 20 Dec 2024 14:01:34 +0200
Antoniu Miclaus <antoniu.miclaus@analog.com> 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@analog.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.

A few comments inline.  The one about checking for existence of voltage
regulators is (I think) a bug. The rest more superficial stuff given
you are going to be doing a v10.  I might otherwise have just tweaked
some of these whilst applying.

Thanks,

Jonathan


> 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 @@

...


> +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;

u64 for the cast so we know how big it is.


> +	*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_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;
Why not use IIO_VAL_FRACTIONAL_LOG2;  Then you can make the power of 2 explicit.

> +
> +	return IIO_VAL_FRACTIONAL;
> +}

...

> +
> +static int ad4851_set_calibbias(struct ad4851_state *st, int ch, int val)
> +{
> +	u8 buf[3] = { 0 };
> +	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]);

Add a comment somewhere on why this can't be a bulk read.  Otherwise someone
will change it as it's obvious low hanging fruit for a cleanup.
Perhaps up by the register definitions.  Obviously even better, figure out why
and make it work as it's probably a timing of cs_change thing.

> +}

> +
> +static const struct iio_scan_type ad4851_scan_type_20_0[] = {

the _0 / _1 postfix isn't particularly intuitive. Maybe consider u vs b
for unipolar vs bipolar or something along those lines?

> +	[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),		\
> +	.indexed = 1,								\
> +	.differential = diff,							\
> +	.channel = ch,								\
> +	.channel2 = ch + (diff * 8),						\
> +	.scan_index = index,
> +
> +#define AD4858_IIO_CHANNEL(index, ch, diff)					\
> +{										\
> +	AD4851_IIO_CHANNEL(index, ch, diff)					\

Add a comment on why you don't set the scan type stuff up for just this case.

> +}
> +
> +#define AD4857_IIO_CHANNEL(index, ch, diff)					\
> +{										\
> +	AD4851_IIO_CHANNEL(index, ch, diff)					\
> +	.scan_type = {								\
> +		.sign = diff ? 's' : 'u',					\
> +		.realbits = 16,							\
> +		.storagebits = 16,						\
> +	},									\
> +}
> +
> +static int ad4851_parse_channels(struct iio_dev *indio_dev,
> +				 struct iio_chan_spec **ad4851_channels,
> +				 const struct iio_chan_spec ad4851_chan,
> +				 const struct iio_chan_spec ad4851_chan_diff)
> +{
> +	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);
> +
> +	channels = devm_kcalloc(dev, num_channels,
> +				sizeof(*channels), GFP_KERNEL);

This is an oddly short wrap.  Check the whole driver for places where the wrap
can be neater.  Aim for 80 char limit except when readability is substantially
better if you go up to 100.

> +	if (!channels)
> +		return -ENOMEM;
> +
> +	indio_dev->channels = channels;
> +	indio_dev->num_channels = num_channels;
> +
> +	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");
> +		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;
> +
> +		} else {
> +			*channels = ad4851_chan;
> +			channels->scan_index = index++;
> +			channels->channel = reg;
> +		}
> +		channels++;
> +
> +		if (fwnode_property_present(child, "bipolar")) {
> +			st->bipolar_ch[reg] = true;
> +		} 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 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);

Wrap this line. It's easy to keep it under 80 chars and that is still the preference
for cases that don't hurt readability in IIO.  Check for similar lines and
wrap them all.

> +	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");
> +		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);
> +		}
> +		ad4851_channels++;
> +	}
> +
> +	return 0;
> +}

> +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)

I'm fairly sure that call never returns a positive.  Will return 0 for success so I think this
should be 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)

Same here.

> +		st->vrefio_en = true;
> +	else
> +		st->vrefio_en = false;
> +
...

> +}

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v9 7/8] dt-bindings: iio: adc: add ad4851
  2024-12-23 11:40   ` Jonathan Cameron
@ 2025-01-08 16:48     ` David Lechner
  0 siblings, 0 replies; 19+ messages in thread
From: David Lechner @ 2025-01-08 16:48 UTC (permalink / raw)
  To: Jonathan Cameron, Antoniu Miclaus
  Cc: robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm,
	Conor Dooley

On 12/23/24 5:40 AM, Jonathan Cameron wrote:
> On Fri, 20 Dec 2024 14:01:33 +0200
> Antoniu Miclaus <antoniu.miclaus@analog.com> wrote:
> 
>> Add devicetree bindings for ad485x family.
>>
>> Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
>> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
> Mixed single end and differential channel devices that have per channel
> child nodes need a more complex description to avoid reg have different
> and possibly clashing meanings for the two types of channel.
> 
> See inline.
> 

...

>> +patternProperties:
>> +  "^channel(@[0-7])?$":
>> +    $ref: adc.yaml
>> +    type: object
>> +    description: Represents the channels which are connected to the ADC.
>> +
>> +    properties:
>> +      reg:
>> +        description: The channel number in single-ended mode.
> 
> And what is it in differential mode?  (usually just an index).
> Which is a problem if you have a mix.  That's why we added
> single-channel.

This chip isn't a mix-and match like many chips with muxes that use this binding
so I think it should just be diff-channels = <0 0>; similar to adi,ad7606. The
pins on the chip are labeled IN0+ and IN0- so it makes sense that both the
positive and negative inputs to be 0.

So, this should be fine with or without a single-channel property since reg
should always have the same number as diff-channels and single-channel.

> 
> So for devices where reg isn't enough, you can use:
> reg: index of channel, not real meaning just makes them unique.
> single-channel: The channel number for single ended.
> diff-channels: The channel numbers for differential channels.
> 
>> +        minimum: 0
>> +        maximum: 7
>> +
>> +      diff-channels: true
> 
> Should be constraints / information on the values possible.
> I've no idea what the 8 is in your example for instance.
> 

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver
  2024-12-23 12:00   ` Jonathan Cameron
@ 2025-01-08 17:06     ` David Lechner
  0 siblings, 0 replies; 19+ messages in thread
From: David Lechner @ 2025-01-08 17:06 UTC (permalink / raw)
  To: Jonathan Cameron, Antoniu Miclaus
  Cc: robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm

On 12/23/24 6:00 AM, Jonathan Cameron wrote:
> On Fri, 20 Dec 2024 14:01:34 +0200
> Antoniu Miclaus <antoniu.miclaus@analog.com> wrote:
> 

...

>> +	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)
> 
> I'm fairly sure that call never returns a positive.  Will return 0 for success so I think this
> should be if (ret == 0)

Even better, make it 1 line instead of 4 and use the specific error code we are
checking for:

		st->vrefbuf_en = ret != -ENODEV;
> 
>> +		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)
> 
> Same here.
  ^
> 
>> +		st->vrefio_en = true;
>> +	else
>> +		st->vrefio_en = false;
>> +
> ...
> 
>> +}
> 


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver
  2024-12-20 12:01 ` [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver Antoniu Miclaus
  2024-12-23 12:00   ` Jonathan Cameron
@ 2025-01-08 23:24   ` David Lechner
  2025-01-14 12:01     ` Miclaus, Antoniu
  2025-01-17  9:59   ` Uwe Kleine-König
  2 siblings, 1 reply; 19+ messages in thread
From: David Lechner @ 2025-01-08 23:24 UTC (permalink / raw)
  To: Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, devicetree,
	linux-kernel, linux-pwm

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@analog.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

> +					 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 !)

> +
> +	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@analog.com>");
> +MODULE_AUTHOR("Dragos Bogdan <dragos.bogdan@analog.com>");
> +MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
> +MODULE_DESCRIPTION("Analog Devices AD4851 DAS driver");
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_BACKEND");


^ permalink raw reply	[flat|nested] 19+ messages in thread

* RE: [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver
  2025-01-08 23:24   ` David Lechner
@ 2025-01-14 12:01     ` Miclaus, Antoniu
  2025-01-14 13:20       ` Jonathan Cameron
  0 siblings, 1 reply; 19+ messages in thread
From: Miclaus, Antoniu @ 2025-01-14 12:01 UTC (permalink / raw)
  To: David Lechner, jic23@kernel.org, robh@kernel.org,
	conor+dt@kernel.org, linux-iio@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-pwm@vger.kernel.org


> -----Original Message-----
> From: David Lechner <dlechner@baylibre.com>
> Sent: Thursday, January 9, 2025 1:25 AM
> To: Miclaus, Antoniu <Antoniu.Miclaus@analog.com>; jic23@kernel.org;
> robh@kernel.org; conor+dt@kernel.org; linux-iio@vger.kernel.org;
> devicetree@vger.kernel.org; linux-kernel@vger.kernel.org; linux-
> pwm@vger.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@analog.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@analog.com>");
> > +MODULE_AUTHOR("Dragos Bogdan <dragos.bogdan@analog.com>");
> > +MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
> > +MODULE_DESCRIPTION("Analog Devices AD4851 DAS driver");
> > +MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_BACKEND");


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver
  2025-01-14 12:01     ` Miclaus, Antoniu
@ 2025-01-14 13:20       ` Jonathan Cameron
  2025-01-14 15:51         ` David Lechner
  0 siblings, 1 reply; 19+ messages in thread
From: Jonathan Cameron @ 2025-01-14 13:20 UTC (permalink / raw)
  To: Miclaus, Antoniu
  Cc: David Lechner, jic23@kernel.org, robh@kernel.org,
	conor+dt@kernel.org, linux-iio@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-pwm@vger.kernel.org


Hi Antoniu

For future replies please crop to only the bits you are reply to.
Took me a couple of goes to find the reply, so in some cases
the important parts can be completely missed by a reader if
the rest isn't cropped down.

...

> > > +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_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;
> > > +

Thanks,

Jonathan

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver
  2025-01-14 13:20       ` Jonathan Cameron
@ 2025-01-14 15:51         ` David Lechner
  0 siblings, 0 replies; 19+ messages in thread
From: David Lechner @ 2025-01-14 15:51 UTC (permalink / raw)
  To: Jonathan Cameron, Miclaus, Antoniu
  Cc: jic23@kernel.org, robh@kernel.org, conor+dt@kernel.org,
	linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org

On 1/14/25 7:20 AM, Jonathan Cameron wrote:
> 
> Hi Antoniu
> 
> For future replies please crop to only the bits you are reply to.
> Took me a couple of goes to find the reply, so in some cases
> the important parts can be completely missed by a reader if
> the rest isn't cropped down.
> 
> ...
> 
>>>> +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.

Oops, I was expecting it to be symmetric with regmap_clear_bits() above. But
of course you are correct.

>>
>>>> +					 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_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.


I see. The macro name is wrong, which made me think it was doing something
different. It should be AD4851_REF_SEL rather than AD4851_REFSEL_PD.


>>>> +
>>>> +	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;
>>>> +
> 
> Thanks,
> 
> Jonathan


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver
  2024-12-20 12:01 ` [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver Antoniu Miclaus
  2024-12-23 12:00   ` Jonathan Cameron
  2025-01-08 23:24   ` David Lechner
@ 2025-01-17  9:59   ` Uwe Kleine-König
  2025-01-17 15:43     ` Jonathan Cameron
  2 siblings, 1 reply; 19+ messages in thread
From: Uwe Kleine-König @ 2025-01-17  9:59 UTC (permalink / raw)
  To: Antoniu Miclaus
  Cc: jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel,
	linux-pwm

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

Hello,

On Fri, Dec 20, 2024 at 02:01:34PM +0200, Antoniu Miclaus wrote:
> +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;
> +}

This can be simplified (I guess) using something like:

	if (ratio >= 2 && ratio <= 65536 && is_power_of_2(ratio))
		return ilog2(ratio) - 1;

	return -EINVAL;

> +static void __ad4851_get_scale(struct iio_dev *indio_dev, int scale_tbl,
> +			       unsigned int *val, unsigned int *val2)
> +{
> [...]
> +}
> +
> +static int ad4851_scale_fill(struct iio_dev *indio_dev)
> +{
> [...]
> +}
> +
> +static int ad4851_set_oversampling_ratio(struct iio_dev *indio_dev,
> +					 const struct iio_chan_spec *chan,
> +					 unsigned int osr)
> +{
> [...]
> +}
> +
> +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];

With the suggestion above this gets:

	*val = 2 << FIELD_GET(AD4851_OS_RATIO_MSK, osr);

(or 
	*val = 1 << (FIELD_GET(AD4851_OS_RATIO_MSK, osr) + 1);

). Then you can drop ad4851_oversampling_ratios[].

> +
> +	st->osr = *val;
> +
> +	return IIO_VAL_INT;
> +}

Best regards
Uwe

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver
  2025-01-17  9:59   ` Uwe Kleine-König
@ 2025-01-17 15:43     ` Jonathan Cameron
  0 siblings, 0 replies; 19+ messages in thread
From: Jonathan Cameron @ 2025-01-17 15:43 UTC (permalink / raw)
  To: Uwe Kleine-König
  Cc: Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, devicetree,
	linux-kernel, linux-pwm

On Fri, 17 Jan 2025 10:59:04 +0100
Uwe Kleine-König <u.kleine-koenig@baylibre.com> wrote:

> Hello,
> 
> On Fri, Dec 20, 2024 at 02:01:34PM +0200, Antoniu Miclaus wrote:
> > +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;
> > +}  
> 
> This can be simplified (I guess) using something like:
> 
> 	if (ratio >= 2 && ratio <= 65536 && is_power_of_2(ratio))
> 		return ilog2(ratio) - 1;
> 
> 	return -EINVAL;
Hi Uwe,

Only at the cost of providing custom handling to compute the above
array in order to provide it to userspace via the read_avail() callback.

We could do what you have here and provide the array but that would
be less clear than just looking it up.

> 
> > +static void __ad4851_get_scale(struct iio_dev *indio_dev, int scale_tbl,
> > +			       unsigned int *val, unsigned int *val2)
> > +{
> > [...]
> > +}
> > +
> > +static int ad4851_scale_fill(struct iio_dev *indio_dev)
> > +{
> > [...]
> > +}
> > +
> > +static int ad4851_set_oversampling_ratio(struct iio_dev *indio_dev,
> > +					 const struct iio_chan_spec *chan,
> > +					 unsigned int osr)
> > +{
> > [...]
> > +}
> > +
> > +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];  
> 
> With the suggestion above this gets:
> 
> 	*val = 2 << FIELD_GET(AD4851_OS_RATIO_MSK, osr);
> 
> (or 
> 	*val = 1 << (FIELD_GET(AD4851_OS_RATIO_MSK, osr) + 1);
> 
> ). Then you can drop ad4851_oversampling_ratios[].
You missed the usage in as4851_read_avail() which is the reason it exists.
These others are just convenient given that it already exists.

Jonathan


> 
> > +
> > +	st->osr = *val;
> > +
> > +	return IIO_VAL_INT;
> > +}  
> 
> Best regards
> Uwe
> 


^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2025-01-17 15:43 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-12-20 12:01 [PATCH v9 1/8] iio: backend: add API for interface get Antoniu Miclaus
2024-12-20 12:01 ` [PATCH v9 2/8] iio: backend: add support for data size set Antoniu Miclaus
2024-12-20 12:01 ` [PATCH v9 3/8] iio: backend: add API for oversampling Antoniu Miclaus
2024-12-20 12:01 ` [PATCH v9 4/8] iio: adc: adi-axi-adc: add interface type Antoniu Miclaus
2024-12-20 12:01 ` [PATCH v9 5/8] iio: adc: adi-axi-adc: set data format Antoniu Miclaus
2024-12-20 12:01 ` [PATCH v9 6/8] iio: adc: adi-axi-adc: add oversampling Antoniu Miclaus
2024-12-20 12:01 ` [PATCH v9 7/8] dt-bindings: iio: adc: add ad4851 Antoniu Miclaus
2024-12-23 11:40   ` Jonathan Cameron
2025-01-08 16:48     ` David Lechner
2024-12-20 12:01 ` [PATCH v9 8/8] iio: adc: ad4851: add ad485x driver Antoniu Miclaus
2024-12-23 12:00   ` Jonathan Cameron
2025-01-08 17:06     ` David Lechner
2025-01-08 23:24   ` David Lechner
2025-01-14 12:01     ` Miclaus, Antoniu
2025-01-14 13:20       ` Jonathan Cameron
2025-01-14 15:51         ` David Lechner
2025-01-17  9:59   ` Uwe Kleine-König
2025-01-17 15:43     ` Jonathan Cameron
2024-12-23 11:33 ` [PATCH v9 1/8] iio: backend: add API for interface get Jonathan Cameron

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).