* [PATCH v10 0/8] Add support for AD485x DAS Family
@ 2025-01-17 13:06 Antoniu Miclaus
2025-01-17 13:06 ` [PATCH v10 1/8] iio: backend: add API for interface get Antoniu Miclaus
` (7 more replies)
0 siblings, 8 replies; 28+ messages in thread
From: Antoniu Miclaus @ 2025-01-17 13:06 UTC (permalink / raw)
To: jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel,
linux-pwm
Cc: Antoniu Miclaus
Add support for AD485X fully buffered, 8-channel simultaneous sampling,
16/20-bit, 1 MSPS data acquisition system (DAS) with differential, wide
common-mode range inputs.
Most of the review comments which make sense in v9 were addressed. Some of them
might have been ommitted, especially those that are a matter of preference.
Since we reached v10, I tried to cover everything that was pointed out until now.
Antoniu Miclaus (8):
iio: backend: add API for interface get
iio: backend: add support for data size set
iio: backend: add API for oversampling
iio: adc: adi-axi-adc: add interface type
iio: adc: adi-axi-adc: set data format
iio: adc: adi-axi-adc: add oversampling
dt-bindings: iio: adc: add ad4851
iio: adc: ad4851: add ad485x driver
.../bindings/iio/adc/adi,ad4851.yaml | 153 ++
drivers/iio/adc/Kconfig | 14 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ad4851.c | 1297 +++++++++++++++++
drivers/iio/adc/adi-axi-adc.c | 88 ++
drivers/iio/industrialio-backend.c | 60 +
include/linux/iio/backend.h | 19 +
7 files changed, 1632 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4851.yaml
create mode 100644 drivers/iio/adc/ad4851.c
--
2.48.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v10 1/8] iio: backend: add API for interface get 2025-01-17 13:06 [PATCH v10 0/8] Add support for AD485x DAS Family Antoniu Miclaus @ 2025-01-17 13:06 ` Antoniu Miclaus 2025-01-17 16:08 ` Nuno Sá 2025-01-17 13:06 ` [PATCH v10 2/8] iio: backend: add support for data size set Antoniu Miclaus ` (6 subsequent siblings) 7 siblings, 1 reply; 28+ messages in thread From: Antoniu Miclaus @ 2025-01-17 13:06 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> --- no changes in v10. 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.48.1 ^ permalink raw reply related [flat|nested] 28+ messages in thread
* Re: [PATCH v10 1/8] iio: backend: add API for interface get 2025-01-17 13:06 ` [PATCH v10 1/8] iio: backend: add API for interface get Antoniu Miclaus @ 2025-01-17 16:08 ` Nuno Sá 0 siblings, 0 replies; 28+ messages in thread From: Nuno Sá @ 2025-01-17 16:08 UTC (permalink / raw) To: Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm Cc: David Lechner On Fri, 2025-01-17 at 15:06 +0200, Antoniu Miclaus 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> > --- Reviewed-by: Nuno Sa <nuno.sa@analog.com> > no changes in v10. > 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] 28+ messages in thread
* [PATCH v10 2/8] iio: backend: add support for data size set 2025-01-17 13:06 [PATCH v10 0/8] Add support for AD485x DAS Family Antoniu Miclaus 2025-01-17 13:06 ` [PATCH v10 1/8] iio: backend: add API for interface get Antoniu Miclaus @ 2025-01-17 13:06 ` Antoniu Miclaus 2025-01-17 16:09 ` Nuno Sá 2025-01-17 13:06 ` [PATCH v10 3/8] iio: backend: add API for oversampling Antoniu Miclaus ` (5 subsequent siblings) 7 siblings, 1 reply; 28+ messages in thread From: Antoniu Miclaus @ 2025-01-17 13:06 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 v10. 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.48.1 ^ permalink raw reply related [flat|nested] 28+ messages in thread
* Re: [PATCH v10 2/8] iio: backend: add support for data size set 2025-01-17 13:06 ` [PATCH v10 2/8] iio: backend: add support for data size set Antoniu Miclaus @ 2025-01-17 16:09 ` Nuno Sá 0 siblings, 0 replies; 28+ messages in thread From: Nuno Sá @ 2025-01-17 16:09 UTC (permalink / raw) To: Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm Cc: David Lechner On Fri, 2025-01-17 at 15:06 +0200, Antoniu Miclaus wrote: > 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> > --- Reviewed-by: Nuno Sa <nuno.sa@analog.com> > no changes in v10. > 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); ^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v10 3/8] iio: backend: add API for oversampling 2025-01-17 13:06 [PATCH v10 0/8] Add support for AD485x DAS Family Antoniu Miclaus 2025-01-17 13:06 ` [PATCH v10 1/8] iio: backend: add API for interface get Antoniu Miclaus 2025-01-17 13:06 ` [PATCH v10 2/8] iio: backend: add support for data size set Antoniu Miclaus @ 2025-01-17 13:06 ` Antoniu Miclaus 2025-01-17 16:17 ` Nuno Sá 2025-01-17 13:06 ` [PATCH v10 4/8] iio: adc: adi-axi-adc: add interface type Antoniu Miclaus ` (4 subsequent siblings) 7 siblings, 1 reply; 28+ messages in thread From: Antoniu Miclaus @ 2025-01-17 13:06 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> --- no changes in v10. 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.48.1 ^ permalink raw reply related [flat|nested] 28+ messages in thread
* Re: [PATCH v10 3/8] iio: backend: add API for oversampling 2025-01-17 13:06 ` [PATCH v10 3/8] iio: backend: add API for oversampling Antoniu Miclaus @ 2025-01-17 16:17 ` Nuno Sá 2025-01-18 16:42 ` Jonathan Cameron 0 siblings, 1 reply; 28+ messages in thread From: Nuno Sá @ 2025-01-17 16:17 UTC (permalink / raw) To: Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm Cc: David Lechner On Fri, 2025-01-17 at 15:06 +0200, Antoniu Miclaus wrote: > Add backend support for setting oversampling ratio. > > Reviewed-by: David Lechner <dlechner@baylibre.com> > Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> > --- > no changes in v10. > 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"); > + Hmm, I'm very late to the party so don't bother in sending another revision unless you have too. But if you do, I would prefer to have this through a write_raw() interface. Meaning we would only have write_raw() as a backend op and then you could add this as a convenient inline helper built on top of write_raw(). So this would be inline with what happens with read_raw(). Anyways, we can clean it up afterwards since we already have a .set_sample_rate() op that could use a similar approach. - Nuno Sá ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v10 3/8] iio: backend: add API for oversampling 2025-01-17 16:17 ` Nuno Sá @ 2025-01-18 16:42 ` Jonathan Cameron 0 siblings, 0 replies; 28+ messages in thread From: Jonathan Cameron @ 2025-01-18 16:42 UTC (permalink / raw) To: Nuno Sá Cc: Antoniu Miclaus, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm, David Lechner On Fri, 17 Jan 2025 16:17:13 +0000 Nuno Sá <noname.nuno@gmail.com> wrote: > On Fri, 2025-01-17 at 15:06 +0200, Antoniu Miclaus wrote: > > Add backend support for setting oversampling ratio. > > > > Reviewed-by: David Lechner <dlechner@baylibre.com> > > Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> > > --- > > no changes in v10. > > 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"); > > + > > Hmm, I'm very late to the party so don't bother in sending another revision > unless you have too. But if you do, I would prefer to have this through a > write_raw() interface. Meaning we would only have write_raw() as a backend op > and then you could add this as a convenient inline helper built on top of > write_raw(). So this would be inline with what happens with read_raw(). Anyways, > we can clean it up afterwards since we already have a .set_sample_rate() op that > could use a similar approach. I'm not against this, but just to mention that will run into the question of whether to support the more complex value types. I guess for now perhaps pass in val, val2 and the write type and just reject anything that isn't supported by a particular backend. > > - Nuno Sá > > ^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v10 4/8] iio: adc: adi-axi-adc: add interface type 2025-01-17 13:06 [PATCH v10 0/8] Add support for AD485x DAS Family Antoniu Miclaus ` (2 preceding siblings ...) 2025-01-17 13:06 ` [PATCH v10 3/8] iio: backend: add API for oversampling Antoniu Miclaus @ 2025-01-17 13:06 ` Antoniu Miclaus 2025-01-17 16:18 ` Nuno Sá 2025-01-17 13:06 ` [PATCH v10 5/8] iio: adc: adi-axi-adc: set data format Antoniu Miclaus ` (3 subsequent siblings) 7 siblings, 1 reply; 28+ messages in thread From: Antoniu Miclaus @ 2025-01-17 13:06 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 v10. 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.48.1 ^ permalink raw reply related [flat|nested] 28+ messages in thread
* Re: [PATCH v10 4/8] iio: adc: adi-axi-adc: add interface type 2025-01-17 13:06 ` [PATCH v10 4/8] iio: adc: adi-axi-adc: add interface type Antoniu Miclaus @ 2025-01-17 16:18 ` Nuno Sá 0 siblings, 0 replies; 28+ messages in thread From: Nuno Sá @ 2025-01-17 16:18 UTC (permalink / raw) To: Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm Cc: David Lechner On Fri, 2025-01-17 at 15:06 +0200, Antoniu Miclaus wrote: > 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> > --- Reviewed-by: Nuno Sa <nuno.sa@analog.com> > no changes in v10. > 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), > }; ^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v10 5/8] iio: adc: adi-axi-adc: set data format 2025-01-17 13:06 [PATCH v10 0/8] Add support for AD485x DAS Family Antoniu Miclaus ` (3 preceding siblings ...) 2025-01-17 13:06 ` [PATCH v10 4/8] iio: adc: adi-axi-adc: add interface type Antoniu Miclaus @ 2025-01-17 13:06 ` Antoniu Miclaus 2025-01-17 16:20 ` Nuno Sá 2025-01-17 13:07 ` [PATCH v10 6/8] iio: adc: adi-axi-adc: add oversampling Antoniu Miclaus ` (2 subsequent siblings) 7 siblings, 1 reply; 28+ messages in thread From: Antoniu Miclaus @ 2025-01-17 13:06 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> --- no changes in v10. 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.48.1 ^ permalink raw reply related [flat|nested] 28+ messages in thread
* Re: [PATCH v10 5/8] iio: adc: adi-axi-adc: set data format 2025-01-17 13:06 ` [PATCH v10 5/8] iio: adc: adi-axi-adc: set data format Antoniu Miclaus @ 2025-01-17 16:20 ` Nuno Sá 2025-01-17 17:50 ` David Lechner 0 siblings, 1 reply; 28+ messages in thread From: Nuno Sá @ 2025-01-17 16:20 UTC (permalink / raw) To: Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm On Fri, 2025-01-17 at 15:06 +0200, Antoniu Miclaus wrote: > Add support for selecting the data format within the AXI ADC ip. > > Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> > --- Reviewed-by: Nuno Sa <nuno.sa@analog.com> > no changes in v10. > 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), > }; ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v10 5/8] iio: adc: adi-axi-adc: set data format 2025-01-17 16:20 ` Nuno Sá @ 2025-01-17 17:50 ` David Lechner 2025-01-18 14:47 ` Nuno Sá 0 siblings, 1 reply; 28+ messages in thread From: David Lechner @ 2025-01-17 17:50 UTC (permalink / raw) To: Nuno Sá, Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm On 1/17/25 10:20 AM, Nuno Sá wrote: > On Fri, 2025-01-17 at 15:06 +0200, Antoniu Miclaus wrote: >> Add support for selecting the data format within the AXI ADC ip. >> >> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> >> --- > > Reviewed-by: Nuno Sa <nuno.sa@analog.com> > >> no changes in v10. >> 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), >> }; > > Since these register values are specific to the AD485X variant of the AXI ADC, I still feel like it would be better if we added a new compatible string like we did for AD355X on the AXI DAC. These functions accessing the CNTRL_3 register aren't applicable to the generic AXI ADC IP block, but only to the AXI AD485X IP core [1]. The AXI AD7606X IP core [2] that we are working on also uses this same register for other purposes, so we will have a conflict. We are planning on adding a new AXI ADC compatible string for AD7606X [3], so I think we should do the same here. [1]: http://analogdevicesinc.github.io/hdl/library/axi_ad485x/index.html [2]: http://analogdevicesinc.github.io/hdl/library/axi_ad7606x/index.html [3]: https://lore.kernel.org/linux-iio/20241210-ad7606_add_iio_backend_software_mode-v2-2-6619c3e50d81@baylibre.com/ ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v10 5/8] iio: adc: adi-axi-adc: set data format 2025-01-17 17:50 ` David Lechner @ 2025-01-18 14:47 ` Nuno Sá 0 siblings, 0 replies; 28+ messages in thread From: Nuno Sá @ 2025-01-18 14:47 UTC (permalink / raw) To: David Lechner, Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm On Fri, 2025-01-17 at 11:50 -0600, David Lechner wrote: > On 1/17/25 10:20 AM, Nuno Sá wrote: > > On Fri, 2025-01-17 at 15:06 +0200, Antoniu Miclaus wrote: > > > Add support for selecting the data format within the AXI ADC ip. > > > > > > Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> > > > --- > > > > Reviewed-by: Nuno Sa <nuno.sa@analog.com> > > > > > no changes in v10. > > > 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), > > > }; > > > > > > Since these register values are specific to the AD485X variant of the AXI ADC, > I still feel like it would be better if we added a new compatible string like > we did for AD355X on the AXI DAC. > Yeah, my first intent was to avoid new compatibles on the backend side (as it can grow pretty wildly). But obviously that if you have two different IPs with different meanings for the same registers, there's nothing we can do. At some point, I thought about havng the backend provide a set of custom registers and then each frontend would now what to do with them. But different compatibles has the clear advantage to make differences between IPs clear and to actually control what a frontend can or cannot do. Anyways, I hope that for simple enough cases we can "live" with the generic compatible or asking HDL for more registers identifying/exposing capabilities of the core. > These functions accessing the CNTRL_3 register aren't applicable to the generic > AXI ADC IP block, but only to the AXI AD485X IP core [1]. The AXI AD7606X IP > core [2] that we are working on also uses this same register for other purposes, > so we will on have a conflict. We are planning on adding a new AXI ADC compatible > string for AD7606X [3], so I think we should do the same here. Given that we'll have another device messing with the same registers, I think it's clear we also need a new compatible here (so we are consistent at the very least). - Nuno Sá ^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v10 6/8] iio: adc: adi-axi-adc: add oversampling 2025-01-17 13:06 [PATCH v10 0/8] Add support for AD485x DAS Family Antoniu Miclaus ` (4 preceding siblings ...) 2025-01-17 13:06 ` [PATCH v10 5/8] iio: adc: adi-axi-adc: set data format Antoniu Miclaus @ 2025-01-17 13:07 ` Antoniu Miclaus 2025-01-17 16:22 ` Nuno Sá 2025-01-17 13:07 ` [PATCH v10 7/8] dt-bindings: iio: adc: add ad4851 Antoniu Miclaus 2025-01-17 13:07 ` [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver Antoniu Miclaus 7 siblings, 1 reply; 28+ messages in thread From: Antoniu Miclaus @ 2025-01-17 13:07 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 v10. 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.48.1 ^ permalink raw reply related [flat|nested] 28+ messages in thread
* Re: [PATCH v10 6/8] iio: adc: adi-axi-adc: add oversampling 2025-01-17 13:07 ` [PATCH v10 6/8] iio: adc: adi-axi-adc: add oversampling Antoniu Miclaus @ 2025-01-17 16:22 ` Nuno Sá 0 siblings, 0 replies; 28+ messages in thread From: Nuno Sá @ 2025-01-17 16:22 UTC (permalink / raw) To: Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm On Fri, 2025-01-17 at 15:07 +0200, Antoniu Miclaus wrote: > Add support for enabling/disabling oversampling. > > Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> > --- > no changes in v10. > 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); > + } > +} > + So whatever the ration we just set the bits? This is odd enough that deserves a comment IMO... (did not looked at the datasheet/wiki tbh :)) - Nuno Sá > ^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v10 7/8] dt-bindings: iio: adc: add ad4851 2025-01-17 13:06 [PATCH v10 0/8] Add support for AD485x DAS Family Antoniu Miclaus ` (5 preceding siblings ...) 2025-01-17 13:07 ` [PATCH v10 6/8] iio: adc: adi-axi-adc: add oversampling Antoniu Miclaus @ 2025-01-17 13:07 ` Antoniu Miclaus 2025-01-17 13:07 ` [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver Antoniu Miclaus 7 siblings, 0 replies; 28+ messages in thread From: Antoniu Miclaus @ 2025-01-17 13:07 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 v10: - improve channel properties description. .../bindings/iio/adc/adi,ad4851.yaml | 153 ++++++++++++++++++ 1 file changed, 153 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..c6676d91b4e6 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4851.yaml @@ -0,0 +1,153 @@ +# 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, as specified in the datasheet (from 0 to 7). + minimum: 0 + maximum: 7 + + diff-channels: + description: + Each channel can be configured as a bipolar differential channel. + The ADC uses the same positive and negative inputs for this. + This property must be specified as 'reg' (or the channel number) for + both positive and negative inputs (i.e. diff-channels = <reg reg>). + Since the configuration is bipolar differential, the 'bipolar' + property is required. + items: + minimum: 0 + maximum: 7 + + 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 0>; + bipolar; + }; + + channel@1 { + reg = <1>; + }; + }; + }; +... -- 2.48.1 ^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver 2025-01-17 13:06 [PATCH v10 0/8] Add support for AD485x DAS Family Antoniu Miclaus ` (6 preceding siblings ...) 2025-01-17 13:07 ` [PATCH v10 7/8] dt-bindings: iio: adc: add ad4851 Antoniu Miclaus @ 2025-01-17 13:07 ` Antoniu Miclaus 2025-01-17 21:45 ` David Lechner 2025-01-18 15:10 ` Nuno Sá 7 siblings, 2 replies; 28+ messages in thread From: Antoniu Miclaus @ 2025-01-17 13:07 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 v10: - use u64 for scale_tbl cast. - use IIO_VAL_FRACTIONAL_LOG2 - add comment for not using bulk_read - use _u and _b suffix for scan_type structures. - add comment for not setting scan_type in macro definitions. - fix some wrapping. - rework vrefbuf_en and vrefio_en handling. - add depends on PWM in Kconfig - add unipolar/bipolar suffix where requested. - rename REFSEL REFBUF macros. - use indio_dev->channels[i].channel for iio_backend_chan_enable - drop initialization for 'buf' - use st->bipolar_ch[chan->channel], not chan->differential - use u32 for softspan_val - move info_mask_separate above as suggested. - handle sign in parse channel function and use `u` as default in macro. - check `reg` range. - use fwnode_property_read_bool - drop redundant `reg` parsing - rework channel2 statement in channel macro and parse_channels. - set ad4851_channels->has_ext_scan_type = 1 drivers/iio/adc/Kconfig | 14 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ad4851.c | 1293 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1308 insertions(+) create mode 100644 drivers/iio/adc/ad4851.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 849c90203071..afd83fddda76 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -61,6 +61,20 @@ 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 + depends on PWM + 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..a39d863bf62d --- /dev/null +++ b/drivers/iio/adc/ad4851.c @@ -0,0 +1,1293 @@ +// 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 BIT(2) +#define AD4851_REFSEL 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_unipolar[] = { + { 2500, 0x0 }, + { 5000, 0x2 }, + { 6250, 0x4 }, + { 10000, 0x6 }, + { 12500, 0x8 }, + { 20000, 0xA }, + { 25000, 0xC }, + { 40000, 0xE }, +}; + +static const struct ad4851_scale ad4851_scale_table_bipolar[] = { + { 5000, 0x1 }, + { 10000, 0x3 }, + { 12500, 0x5 }, + { 20000, 0x7 }, + { 25000, 0x9 }, + { 40000, 0xB }, + { 50000, 0xD }, + { 80000, 0xF }, +}; + +static const unsigned int ad4851_scale_avail_unipolar[] = { + 2500, + 5000, + 6250, + 10000, + 12500, + 20000, + 25000, + 40000, +}; + +static const unsigned int ad4851_scale_avail_bipolar[] = { + 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_unipolar[AD4841_MAX_SCALE_AVAIL][2]; + unsigned int scales_bipolar[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 = ((u64)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_unipolar); i++) { + __ad4851_get_scale(indio_dev, ad4851_scale_avail_unipolar[i], &val1, &val2); + st->scales_unipolar[i][0] = val1; + st->scales_unipolar[i][1] = val2; + } + + for (i = 0; i < ARRAY_SIZE(ad4851_scale_avail_bipolar); i++) { + __ad4851_get_scale(indio_dev, ad4851_scale_avail_bipolar[i], &val1, &val2); + st->scales_bipolar[i][0] = val1; + st->scales_bipolar[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); + if (ret) + return ret; + } + + if (st->vrefio_en) { + ret = regmap_set_bits(st->regmap, AD4851_REG_DEVICE_CTRL, + AD4851_REFSEL); + 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, + indio_dev->channels[i].channel); + 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), ®_val); + if (ret) + return ret; + + gain = reg_val << 8; + + ret = regmap_read(st->regmap, AD4851_REG_CHX_GAIN_LSB(ch), ®_val); + if (ret) + return ret; + + gain |= reg_val; + + *val = gain; + *val2 = 15; + + return IIO_VAL_FRACTIONAL_LOG2; +} + +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); + /* + * After testing, the bulk_write operations doesn't work as expected + * here since the cs needs to be raised after each byte transaction. + */ + 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]; + 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); + /* + * After testing, the bulk_write operations doesn't work as expected + * here since the cs needs to be raised after each byte transaction. + */ + 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 (st->bipolar_ch[chan->channel]) { + scale_table = ad4851_scale_table_bipolar; + table_size = ARRAY_SIZE(ad4851_scale_table_bipolar); + } else { + scale_table = ad4851_scale_table_unipolar; + table_size = ARRAY_SIZE(ad4851_scale_table_unipolar); + } + + 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; + u32 softspan_val; + int i, ret; + + if (st->bipolar_ch[chan->channel]) { + scale_table = ad4851_scale_table_bipolar; + table_size = ARRAY_SIZE(ad4851_scale_table_bipolar); + } else { + scale_table = ad4851_scale_table_unipolar; + table_size = ARRAY_SIZE(ad4851_scale_table_unipolar); + } + + 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_FRACTIONAL; + 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_bipolar; + *type = IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length = ARRAY_SIZE(ad4851_scale_avail_bipolar) * 2; + } else { + *vals = (const int *)st->scales_unipolar; + *type = IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length = ARRAY_SIZE(ad4851_scale_avail_unipolar) * 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_u[] = { + [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_b[] = { + [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_separate_available = 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), \ + .indexed = 1, \ + .differential = diff, \ + .channel = ch, \ + .channel2 = ch, \ + .scan_index = index, + +/* + * In case of AD4858_IIO_CHANNEL the scan_type is handled dynamically during the + * parse_channels function. + */ +#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 = '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", ®); + if (ret || reg >= AD4851_MAX_CH_NR) + return dev_err_probe(dev, ret, + "Missing/Invalid channel number\n"); + if (fwnode_property_present(child, "diff-channels")) { + *channels = ad4851_chan_diff; + channels->scan_index = index++; + channels->channel = reg; + channels->channel2 = reg; + + } else { + *channels = ad4851_chan; + channels->scan_index = index++; + channels->channel = reg; + } + channels++; + + st->bipolar_ch[reg] = fwnode_property_read_bool(child, "bipolar"); + + if (st->bipolar_ch[reg]) { + channels->scan_type.sign = 's'; + } else { + 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); + 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) { + ad4851_channels->has_ext_scan_type = 1; + if (fwnode_property_present(child, "bipolar")) { + ad4851_channels->ext_scan_type = ad4851_scan_type_20_b; + ad4851_channels->num_ext_scan_type = ARRAY_SIZE(ad4851_scan_type_20_b); + + } else { + ad4851_channels->ext_scan_type = ad4851_scan_type_20_u; + ad4851_channels->num_ext_scan_type = ARRAY_SIZE(ad4851_scan_type_20_u); + } + 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"); + + st->vrefbuf_en = ret != -ENODEV; + + 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"); + + st->vrefio_en = ret != -ENODEV; + + 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, ®map_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.48.1 ^ permalink raw reply related [flat|nested] 28+ messages in thread
* Re: [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver 2025-01-17 13:07 ` [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver Antoniu Miclaus @ 2025-01-17 21:45 ` David Lechner 2025-01-18 16:57 ` Jonathan Cameron 2025-01-20 12:37 ` Miclaus, Antoniu 2025-01-18 15:10 ` Nuno Sá 1 sibling, 2 replies; 28+ messages in thread From: David Lechner @ 2025-01-17 21:45 UTC (permalink / raw) To: Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm On 1/17/25 7:07 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> > --- ... > +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]); This is ignoring possible error return. > + > + tmp = ((u64)scale_tbl * MICRO) >> scan_type->realbits; > + *val = tmp / MICRO; > + *val2 = tmp % 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; Needs to be: *val = st->cnv_trigger_rate_hz; *val2 = st->osr; for IIO_VAL_FRACTIONAL. > + return IIO_VAL_FRACTIONAL; > + 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); Since we are using IIO_VAL_FRACTIONAL on read, we should handle val2 here to allow for a decimal component. val * st->osr + val2 * st->osr / MICRO Also, should return -EINVAL if (val < 0 || val2 < 0) to avoid negative values being turned into large numbers when casting to unsigned. > + 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; > + } > +} > + ... > + > +#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_separate_available = 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), \ > + .indexed = 1, \ > + .differential = diff, \ > + .channel = ch, \ > + .channel2 = ch, \ > + .scan_index = index, channel, channel2 and scan_index get written over in ad4851_parse_channels() so can be omitted here. > + > +/* > + * In case of AD4858_IIO_CHANNEL the scan_type is handled dynamically during the > + * parse_channels function. > + */ > +#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 = '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", ®); > + if (ret || reg >= AD4851_MAX_CH_NR) > + return dev_err_probe(dev, ret, > + "Missing/Invalid channel number\n"); This allows returning 0 on error which will lead to an invalid state. Probably best to split this into two messages/returns. > + if (fwnode_property_present(child, "diff-channels")) { > + *channels = ad4851_chan_diff; > + channels->scan_index = index++; > + channels->channel = reg; > + channels->channel2 = reg; Typically we don't set channel == channel2 for differential channels. > + > + } else { > + *channels = ad4851_chan; > + channels->scan_index = index++; > + channels->channel = reg; These two lines are the same in each branch, so can be pulled out of the if statement to avoid duplicating them. > + } > + channels++; > + > + st->bipolar_ch[reg] = fwnode_property_read_bool(child, "bipolar"); > + > + if (st->bipolar_ch[reg]) { > + channels->scan_type.sign = 's'; > + } else { > + ret = regmap_write(st->regmap, AD4851_REG_CHX_SOFTSPAN(reg), > + AD4851_SOFTSPAN_0V_40V); > + if (ret) > + return ret; > + } > + } > + > + *ad4851_channels = channels; At this point, channels is pointing to memory we didn't allocate (because of channels++). As in the previous review, I suggest we just get rid of the output parameter since indio_dev->channels already has the correct pointer. It's less chance for mistakes like this and avoids needing to provide an unused arg in ad4857_parse_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); The only difference between these is the diff bit, so seems simpler to just have one template and dynamically set the bit instead. > + > + return ad4851_parse_channels(indio_dev, &ad4851_channels, ad4851_chan, > + ad4851_chan_diff); > +} > +... > +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, Inconsistent use of & on function pointer. > + .read_avail = ad4851_read_avail, > +}; > + ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver 2025-01-17 21:45 ` David Lechner @ 2025-01-18 16:57 ` Jonathan Cameron 2025-01-18 17:09 ` David Lechner 2025-01-20 12:37 ` Miclaus, Antoniu 1 sibling, 1 reply; 28+ messages in thread From: Jonathan Cameron @ 2025-01-18 16:57 UTC (permalink / raw) To: David Lechner Cc: Antoniu Miclaus, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm On Fri, 17 Jan 2025 15:45:35 -0600 David Lechner <dlechner@baylibre.com> wrote: > On 1/17/25 7:07 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> > > --- > > ... > > > +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]); > > This is ignoring possible error return. > > > + > > + tmp = ((u64)scale_tbl * MICRO) >> scan_type->realbits; > > + *val = tmp / MICRO; > > + *val2 = tmp % 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; > > Needs to be: > > *val = st->cnv_trigger_rate_hz; > *val2 = st->osr; > > for IIO_VAL_FRACTIONAL. > > > + return IIO_VAL_FRACTIONAL; > > + 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); > > Since we are using IIO_VAL_FRACTIONAL on read, we should handle val2 here to > allow for a decimal component. > > val * st->osr + val2 * st->osr / MICRO > > > Also, should return -EINVAL if (val < 0 || val2 < 0) to avoid negative values > being turned into large numbers when casting to unsigned. > > > > + 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; > > + } > > +} > > + > > ... > > > + > > +#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_separate_available = 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), \ > > + .indexed = 1, \ > > + .differential = diff, \ > > + .channel = ch, \ > > + .channel2 = ch, \ > > + .scan_index = index, > > channel, channel2 and scan_index get written over in ad4851_parse_channels() > so can be omitted here. > > > + > > +/* > > + * In case of AD4858_IIO_CHANNEL the scan_type is handled dynamically during the > > + * parse_channels function. > > + */ > > +#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 = '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", ®); > > + if (ret || reg >= AD4851_MAX_CH_NR) > > + return dev_err_probe(dev, ret, > > + "Missing/Invalid channel number\n"); > > This allows returning 0 on error which will lead to an invalid state. Probably > best to split this into two messages/returns. > > > + if (fwnode_property_present(child, "diff-channels")) { > > + *channels = ad4851_chan_diff; > > + channels->scan_index = index++; > > + channels->channel = reg; > > + channels->channel2 = reg; > > Typically we don't set channel == channel2 for differential channels. So i guess this is tripping up on these being dedicated pairs labelled +IN1,-IN1 on the datasheet. The binding documents those as matching the diff-channels - hence both channels and reg are the same. So maybe best bet is to enforce that in the driver by checking it is true. It is a slightly weird description but only alternative would be to invent some more channel numbers for the negative sides which is less than ideal. We could go that way though. Some comments alongside a sanity check is probably the best way to handle this and no surprise us in the future. > > > + > > + } else { > > + *channels = ad4851_chan; > > + channels->scan_index = index++; > > + channels->channel = reg; > > These two lines are the same in each branch, so can be pulled out of the if > statement to avoid duplicating them. > > > + } > > + channels++; > > + > > + st->bipolar_ch[reg] = fwnode_property_read_bool(child, "bipolar"); > > + > > + if (st->bipolar_ch[reg]) { > > + channels->scan_type.sign = 's'; > > + } else { > > + ret = regmap_write(st->regmap, AD4851_REG_CHX_SOFTSPAN(reg), > > + AD4851_SOFTSPAN_0V_40V); > > + if (ret) > > + return ret; > > + } > > + } > > + > > + *ad4851_channels = channels; > > At this point, channels is pointing to memory we didn't allocate (because of > channels++). As in the previous review, I suggest we just get rid of the output > parameter since indio_dev->channels already has the correct pointer. > > It's less chance for mistakes like this and avoids needing to provide an unused > arg in ad4857_parse_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); > > The only difference between these is the diff bit, so seems simpler to just > have one template and dynamically set the bit instead. > > > + > > + return ad4851_parse_channels(indio_dev, &ad4851_channels, ad4851_chan, > > + ad4851_chan_diff); > > +} > > +... > > > +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, > > Inconsistent use of & on function pointer. > > > + .read_avail = ad4851_read_avail, > > +}; > > + ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver 2025-01-18 16:57 ` Jonathan Cameron @ 2025-01-18 17:09 ` David Lechner 2025-01-18 17:41 ` Jonathan Cameron 0 siblings, 1 reply; 28+ messages in thread From: David Lechner @ 2025-01-18 17:09 UTC (permalink / raw) To: Jonathan Cameron Cc: Antoniu Miclaus, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm On 1/18/25 10:57 AM, Jonathan Cameron wrote: > On Fri, 17 Jan 2025 15:45:35 -0600 > David Lechner <dlechner@baylibre.com> wrote: > >> On 1/17/25 7:07 AM, Antoniu Miclaus wrote: ... >>> + if (fwnode_property_present(child, "diff-channels")) { >>> + *channels = ad4851_chan_diff; >>> + channels->scan_index = index++; >>> + channels->channel = reg; >>> + channels->channel2 = reg; >> >> Typically we don't set channel == channel2 for differential channels. > So i guess this is tripping up on these being dedicated pairs labelled > +IN1,-IN1 on the datasheet. The binding documents those as matching > the diff-channels - hence both channels and reg are the same. > So maybe best bet is to enforce that in the driver by checking it is > true. Are you saying that in_voltage0-voltage0_raw in userspace is OK? > > It is a slightly weird description but only alternative would be to > invent some more channel numbers for the negative sides which is > less than ideal. We could go that way though. > > Some comments alongside a sanity check is probably the best way to > handle this and no surprise us in the future. > ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver 2025-01-18 17:09 ` David Lechner @ 2025-01-18 17:41 ` Jonathan Cameron 0 siblings, 0 replies; 28+ messages in thread From: Jonathan Cameron @ 2025-01-18 17:41 UTC (permalink / raw) To: David Lechner Cc: Antoniu Miclaus, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm On Sat, 18 Jan 2025 11:09:12 -0600 David Lechner <dlechner@baylibre.com> wrote: > On 1/18/25 10:57 AM, Jonathan Cameron wrote: > > On Fri, 17 Jan 2025 15:45:35 -0600 > > David Lechner <dlechner@baylibre.com> wrote: > > > >> On 1/17/25 7:07 AM, Antoniu Miclaus wrote: > > ... > > >>> + if (fwnode_property_present(child, "diff-channels")) { > >>> + *channels = ad4851_chan_diff; > >>> + channels->scan_index = index++; > >>> + channels->channel = reg; > >>> + channels->channel2 = reg; > >> > >> Typically we don't set channel == channel2 for differential channels. > > So i guess this is tripping up on these being dedicated pairs labelled > > +IN1,-IN1 on the datasheet. The binding documents those as matching > > the diff-channels - hence both channels and reg are the same. > > So maybe best bet is to enforce that in the driver by checking it is > > true. > > Are you saying that in_voltage0-voltage0_raw in userspace is OK? That is indeed the question. It's odd.. Also causes us problems because we do have a few devices where we actually do read a single pin wired to either side of a differential ADC as part of calibration where in_voltage0-voltage0_raw has a different meaning to here. So, gut feeling - too odd and I think every time we've met this before we have gone with inventing some more channels so that the inputs have different numbers. I can't immediately point at an example though. Do we need to handle that at the binding level? I'm not sure if it is clearer to insist we do in both places or let the binding stick to datasheet numbering and just deal with inventing channel numbers in the driver. Jonathan > > > > > It is a slightly weird description but only alternative would be to > > invent some more channel numbers for the negative sides which is > > less than ideal. We could go that way though. > > > > Some comments alongside a sanity check is probably the best way to > > handle this and no surprise us in the future. > > ^ permalink raw reply [flat|nested] 28+ messages in thread
* RE: [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver 2025-01-17 21:45 ` David Lechner 2025-01-18 16:57 ` Jonathan Cameron @ 2025-01-20 12:37 ` Miclaus, Antoniu 2025-01-20 17:37 ` David Lechner 1 sibling, 1 reply; 28+ messages in thread From: Miclaus, Antoniu @ 2025-01-20 12:37 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 > > + } > > + channels++; > > + > > + st->bipolar_ch[reg] = fwnode_property_read_bool(child, > "bipolar"); > > + > > + if (st->bipolar_ch[reg]) { > > + channels->scan_type.sign = 's'; > > + } else { > > + ret = regmap_write(st->regmap, > AD4851_REG_CHX_SOFTSPAN(reg), > > + AD4851_SOFTSPAN_0V_40V); > > + if (ret) > > + return ret; > > + } > > + } > > + > > + *ad4851_channels = channels; > > At this point, channels is pointing to memory we didn't allocate (because of > channels++). As in the previous review, I suggest we just get rid of the output > parameter since indio_dev->channels already has the correct pointer. > > It's less chance for mistakes like this and avoids needing to provide an unused > arg in ad4857_parse_channels(). Hmm, how can I then do the assignments in `ad4858_parse_channels` ? drivers/iio/adc/ad4851.c:1055:42: error: assignment of member ‘has_ext_scan_type’ in read-only object 1055 | indio_dev->channels->has_ext_scan_type = 1; | ^ drivers/iio/adc/ad4851.c:1057:39: error: assignment of member ‘ext_scan_type’ in read-only object 1057 | indio_dev->channels->ext_scan_type = ad4851_scan_type_20_b; | ^ drivers/iio/adc/ad4851.c:1058:43: error: assignment of member ‘num_ext_scan_type’ in read-only object 1058 | indio_dev->channels->num_ext_scan_type = ARRAY_SIZE(ad4851_scan_type_20_b); | ^ drivers/iio/adc/ad4851.c:1061:39: error: assignment of member ‘ext_scan_type’ in read-only object 1061 | indio_dev->channels->ext_scan_type = ad4851_scan_type_20_u; | ^ drivers/iio/adc/ad4851.c:1062:43: error: assignment of member ‘num_ext_scan_type’ in read-only object 1062 | indio_dev->channels->num_ext_scan_type = ARRAY_SIZE(ad4851_scan_type_20_u); | ^ > > > + > > + return 0; > > +} > > + .... ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver 2025-01-20 12:37 ` Miclaus, Antoniu @ 2025-01-20 17:37 ` David Lechner 2025-01-21 10:24 ` Nuno Sá 0 siblings, 1 reply; 28+ messages in thread From: David Lechner @ 2025-01-20 17:37 UTC (permalink / raw) To: Miclaus, Antoniu, 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/20/25 6:37 AM, Miclaus, Antoniu wrote: >>> + } >>> + channels++; >>> + >>> + st->bipolar_ch[reg] = fwnode_property_read_bool(child, >> "bipolar"); >>> + >>> + if (st->bipolar_ch[reg]) { >>> + channels->scan_type.sign = 's'; >>> + } else { >>> + ret = regmap_write(st->regmap, >> AD4851_REG_CHX_SOFTSPAN(reg), >>> + AD4851_SOFTSPAN_0V_40V); >>> + if (ret) >>> + return ret; >>> + } >>> + } >>> + >>> + *ad4851_channels = channels; >> >> At this point, channels is pointing to memory we didn't allocate (because of >> channels++). As in the previous review, I suggest we just get rid of the output >> parameter since indio_dev->channels already has the correct pointer. >> >> It's less chance for mistakes like this and avoids needing to provide an unused >> arg in ad4857_parse_channels(). > > Hmm, how can I then do the assignments in `ad4858_parse_channels` ? > > drivers/iio/adc/ad4851.c:1055:42: error: assignment of member ‘has_ext_scan_type’ in read-only object > 1055 | indio_dev->channels->has_ext_scan_type = 1; > | ^ > drivers/iio/adc/ad4851.c:1057:39: error: assignment of member ‘ext_scan_type’ in read-only object > 1057 | indio_dev->channels->ext_scan_type = ad4851_scan_type_20_b; > | ^ > drivers/iio/adc/ad4851.c:1058:43: error: assignment of member ‘num_ext_scan_type’ in read-only object > 1058 | indio_dev->channels->num_ext_scan_type = ARRAY_SIZE(ad4851_scan_type_20_b); > | ^ > drivers/iio/adc/ad4851.c:1061:39: error: assignment of member ‘ext_scan_type’ in read-only object > 1061 | indio_dev->channels->ext_scan_type = ad4851_scan_type_20_u; > | ^ > drivers/iio/adc/ad4851.c:1062:43: error: assignment of member ‘num_ext_scan_type’ in read-only object > 1062 | indio_dev->channels->num_ext_scan_type = ARRAY_SIZE(ad4851_scan_type_20_u); > | ^ I would be tempted to just not have a second loop of device_for_each_child_node_scoped(dev, child) in ad4858_parse_channels() and instead do everything in ad4851_parse_channels() and just pass a boolean parameter to conditionally handle the difference between the two types of chips. Or you use a cast to remove the const qualifier. ad4851_channels = (struct iio_chan_spec *)indio_dev->channels; ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver 2025-01-20 17:37 ` David Lechner @ 2025-01-21 10:24 ` Nuno Sá 0 siblings, 0 replies; 28+ messages in thread From: Nuno Sá @ 2025-01-21 10:24 UTC (permalink / raw) To: David Lechner, Miclaus, Antoniu, 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 Mon, 2025-01-20 at 11:37 -0600, David Lechner wrote: > On 1/20/25 6:37 AM, Miclaus, Antoniu wrote: > > > > + } > > > > + channels++; > > > > + > > > > + st->bipolar_ch[reg] = fwnode_property_read_bool(child, > > > "bipolar"); > > > > + > > > > + if (st->bipolar_ch[reg]) { > > > > + channels->scan_type.sign = 's'; > > > > + } else { > > > > + ret = regmap_write(st->regmap, > > > AD4851_REG_CHX_SOFTSPAN(reg), > > > > + AD4851_SOFTSPAN_0V_40V); > > > > + if (ret) > > > > + return ret; > > > > + } > > > > + } > > > > + > > > > + *ad4851_channels = channels; > > > > > > At this point, channels is pointing to memory we didn't allocate (because > > > of > > > channels++). As in the previous review, I suggest we just get rid of the > > > output > > > parameter since indio_dev->channels already has the correct pointer. > > > > > > It's less chance for mistakes like this and avoids needing to provide an > > > unused > > > arg in ad4857_parse_channels(). > > > > Hmm, how can I then do the assignments in `ad4858_parse_channels` ? > > > > drivers/iio/adc/ad4851.c:1055:42: error: assignment of member > > ‘has_ext_scan_type’ in read-only object > > 1055 | indio_dev->channels->has_ext_scan_type = 1; > > | ^ > > drivers/iio/adc/ad4851.c:1057:39: error: assignment of member > > ‘ext_scan_type’ in read-only object > > 1057 | indio_dev->channels->ext_scan_type = ad4851_scan_type_20_b; > > | ^ > > drivers/iio/adc/ad4851.c:1058:43: error: assignment of member > > ‘num_ext_scan_type’ in read-only object > > 1058 | indio_dev->channels->num_ext_scan_type = > > ARRAY_SIZE(ad4851_scan_type_20_b); > > | ^ > > drivers/iio/adc/ad4851.c:1061:39: error: assignment of member > > ‘ext_scan_type’ in read-only object > > 1061 | indio_dev->channels->ext_scan_type = ad4851_scan_type_20_u; > > | ^ > > drivers/iio/adc/ad4851.c:1062:43: error: assignment of member > > ‘num_ext_scan_type’ in read-only object > > 1062 | indio_dev->channels->num_ext_scan_type = > > ARRAY_SIZE(ad4851_scan_type_20_u); > > | ^ > > I would be tempted to just not have a second loop of > > device_for_each_child_node_scoped(dev, child) > > in ad4858_parse_channels() and instead do everything in > ad4851_parse_channels() > and just pass a boolean parameter to conditionally handle the difference > between the two types of chips. > > Or you use a cast to remove the const qualifier. > > ad4851_channels = (struct iio_chan_spec *)indio_dev->channels; Hmm a bit nasty IMO :). But up to you both anyways... - Nuno Sá ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver 2025-01-17 13:07 ` [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver Antoniu Miclaus 2025-01-17 21:45 ` David Lechner @ 2025-01-18 15:10 ` Nuno Sá 2025-01-18 17:37 ` David Lechner 1 sibling, 1 reply; 28+ messages in thread From: Nuno Sá @ 2025-01-18 15:10 UTC (permalink / raw) To: Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm On Fri, 2025-01-17 at 15:07 +0200, 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 v10: > - use u64 for scale_tbl cast. > - use IIO_VAL_FRACTIONAL_LOG2 > - add comment for not using bulk_read > - use _u and _b suffix for scan_type structures. > - add comment for not setting scan_type in macro definitions. > - fix some wrapping. > - rework vrefbuf_en and vrefio_en handling. > - add depends on PWM in Kconfig > - add unipolar/bipolar suffix where requested. > - rename REFSEL REFBUF macros. > - use indio_dev->channels[i].channel for iio_backend_chan_enable > - drop initialization for 'buf' > - use st->bipolar_ch[chan->channel], not chan->differential > - use u32 for softspan_val > - move info_mask_separate above as suggested. > - handle sign in parse channel function and use `u` as default in macro. > - check `reg` range. > - use fwnode_property_read_bool > - drop redundant `reg` parsing > - rework channel2 statement in channel macro and parse_channels. > - set ad4851_channels->has_ext_scan_type = 1 > drivers/iio/adc/Kconfig | 14 + > drivers/iio/adc/Makefile | 1 + > drivers/iio/adc/ad4851.c | 1293 ++++++++++++++++++++++++++++++++++++++ > 3 files changed, 1308 insertions(+) > create mode 100644 drivers/iio/adc/ad4851.c > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 849c90203071..afd83fddda76 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -61,6 +61,20 @@ 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 > + depends on PWM > + 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..a39d863bf62d > --- /dev/null > +++ b/drivers/iio/adc/ad4851.c > @@ -0,0 +1,1293 @@ > +// 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 BIT(2) > +#define AD4851_REFSEL 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_unipolar[] = { > + { 2500, 0x0 }, > + { 5000, 0x2 }, > + { 6250, 0x4 }, > + { 10000, 0x6 }, > + { 12500, 0x8 }, > + { 20000, 0xA }, > + { 25000, 0xC }, > + { 40000, 0xE }, > +}; > + > +static const struct ad4851_scale ad4851_scale_table_bipolar[] = { > + { 5000, 0x1 }, > + { 10000, 0x3 }, > + { 12500, 0x5 }, > + { 20000, 0x7 }, > + { 25000, 0x9 }, > + { 40000, 0xB }, > + { 50000, 0xD }, > + { 80000, 0xF }, > +}; > + > +static const unsigned int ad4851_scale_avail_unipolar[] = { > + 2500, > + 5000, > + 6250, > + 10000, > + 12500, > + 20000, > + 25000, > + 40000, > +}; > + > +static const unsigned int ad4851_scale_avail_bipolar[] = { > + 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_unipolar[AD4841_MAX_SCALE_AVAIL][2]; > + unsigned int scales_bipolar[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); > + no need for the above lock. I guess you're not disabling regmap internal lock... > + if (readval) > + return regmap_read(st->regmap, reg, readval); > + > + return regmap_write(st->regmap, reg, writeval); > +} > + ... > > +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; > + } nit: I would instead do if (st->osr == osr) return 0; ... > + > + 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); > + if (ret) > + return ret; > + } > + > + if (st->vrefio_en) { > + ret = regmap_set_bits(st->regmap, AD4851_REG_DEVICE_CTRL, > + AD4851_REFSEL); > + 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; > +} > Your status seems to be a bitmap now, can you take a look in here and see if you can do something similar? https://elixir.bootlin.com/linux/v6.12.6/source/drivers/iio/adc/ad9467.c#L633 Anyways, look at the bitmap API. It might have helpers to make the above function simpler. > + > +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, > + indio_dev->channels[i].channel); > + 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); > + } you can use the lighter forms __set_bit()/__clear_bit(). But going one step further, maybe __assign_bit()? > + } > + > + 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); status is just a bool and it seems your iterating on it? Am I missing something? > + 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_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_FRACTIONAL; > + 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); Maybe this was discussed already and I missed it but I'm a bit puzzled. Don't we still need OFFSET for differential channels? How do you express negative voltages? - Nuno Sá ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver 2025-01-18 15:10 ` Nuno Sá @ 2025-01-18 17:37 ` David Lechner 2025-01-20 9:44 ` Nuno Sá 0 siblings, 1 reply; 28+ messages in thread From: David Lechner @ 2025-01-18 17:37 UTC (permalink / raw) To: Nuno Sá, Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm On 1/18/25 9:10 AM, Nuno Sá wrote: > On Fri, 2025-01-17 at 15:07 +0200, 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> >> --- ... > ... > >> +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_FRACTIONAL; >> + 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); > > Maybe this was discussed already and I missed it but I'm a bit puzzled. Don't we > still need OFFSET for differential channels? How do you express negative voltages? > > - Nuno Sá > > It was discussed in early revisions of the series. :-) There was an OFFSET back then, but we removed it because chip uses twos complement encoding for bipolar single-ended and (bipolar) differential. We have 's' and 'u' set in the scan_type.sign in those cases. The current implementation looks correct to me in this regard. ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver 2025-01-18 17:37 ` David Lechner @ 2025-01-20 9:44 ` Nuno Sá 0 siblings, 0 replies; 28+ messages in thread From: Nuno Sá @ 2025-01-20 9:44 UTC (permalink / raw) To: David Lechner, Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, devicetree, linux-kernel, linux-pwm On Sat, 2025-01-18 at 11:37 -0600, David Lechner wrote: > On 1/18/25 9:10 AM, Nuno Sá wrote: > > On Fri, 2025-01-17 at 15:07 +0200, 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> > > > --- > > ... > > > ... > > > > > +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_FRACTIONAL; > > > + 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); > > > > Maybe this was discussed already and I missed it but I'm a bit puzzled. > > Don't we > > still need OFFSET for differential channels? How do you express negative > > voltages? > > > > - Nuno Sá > > > > > > It was discussed in early revisions of the series. :-) > > There was an OFFSET back then, but we removed it because chip uses twos > complement encoding for bipolar single-ended and (bipolar) differential. We > have 's' and 'u' set in the scan_type.sign in those cases. The current > implementation looks correct to me in this regard. > Yeah, my bad. I was also the one suggesting the OFFSET (IIRC) in internal review as I assumed this was typical "straight" binary encoding. I did bothered to check the datasheet this time and all looks good. Sorry for the noise... - Nuno Sá ^ permalink raw reply [flat|nested] 28+ messages in thread
end of thread, other threads:[~2025-01-21 10:24 UTC | newest] Thread overview: 28+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-01-17 13:06 [PATCH v10 0/8] Add support for AD485x DAS Family Antoniu Miclaus 2025-01-17 13:06 ` [PATCH v10 1/8] iio: backend: add API for interface get Antoniu Miclaus 2025-01-17 16:08 ` Nuno Sá 2025-01-17 13:06 ` [PATCH v10 2/8] iio: backend: add support for data size set Antoniu Miclaus 2025-01-17 16:09 ` Nuno Sá 2025-01-17 13:06 ` [PATCH v10 3/8] iio: backend: add API for oversampling Antoniu Miclaus 2025-01-17 16:17 ` Nuno Sá 2025-01-18 16:42 ` Jonathan Cameron 2025-01-17 13:06 ` [PATCH v10 4/8] iio: adc: adi-axi-adc: add interface type Antoniu Miclaus 2025-01-17 16:18 ` Nuno Sá 2025-01-17 13:06 ` [PATCH v10 5/8] iio: adc: adi-axi-adc: set data format Antoniu Miclaus 2025-01-17 16:20 ` Nuno Sá 2025-01-17 17:50 ` David Lechner 2025-01-18 14:47 ` Nuno Sá 2025-01-17 13:07 ` [PATCH v10 6/8] iio: adc: adi-axi-adc: add oversampling Antoniu Miclaus 2025-01-17 16:22 ` Nuno Sá 2025-01-17 13:07 ` [PATCH v10 7/8] dt-bindings: iio: adc: add ad4851 Antoniu Miclaus 2025-01-17 13:07 ` [PATCH v10 8/8] iio: adc: ad4851: add ad485x driver Antoniu Miclaus 2025-01-17 21:45 ` David Lechner 2025-01-18 16:57 ` Jonathan Cameron 2025-01-18 17:09 ` David Lechner 2025-01-18 17:41 ` Jonathan Cameron 2025-01-20 12:37 ` Miclaus, Antoniu 2025-01-20 17:37 ` David Lechner 2025-01-21 10:24 ` Nuno Sá 2025-01-18 15:10 ` Nuno Sá 2025-01-18 17:37 ` David Lechner 2025-01-20 9:44 ` Nuno Sá
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).