* [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC
@ 2025-10-30 16:34 Ajith Anandhan
2025-10-30 16:34 ` [RFC PATCH 1/3] dt-bindings: iio: adc: Add TI ADS1120 binding Ajith Anandhan
` (4 more replies)
0 siblings, 5 replies; 24+ messages in thread
From: Ajith Anandhan @ 2025-10-30 16:34 UTC (permalink / raw)
To: linux-iio
Cc: jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt,
devicetree, linux-kernel, Ajith Anandhan
This RFC patch series adds support for the Texas Instruments ADS1120,
a precision 16-bit delta-sigma ADC with SPI interface.
The driver provides:
- 4 single-ended voltage input channels
- Programmable gain amplifier (1 to 128)
- Configurable data rates (20 to 1000 SPS)
- Single-shot conversion mode
I'm looking for feedback on:
1. The implementation approach for single-shot conversions
2. Any other suggestions for improvement
Datasheet: https://www.ti.com/lit/gpn/ads1120
Ajith Anandhan (3):
dt-bindings: iio: adc: Add TI ADS1120 binding
iio: adc: Add support for TI ADS1120
MAINTAINERS: Add entry for TI ADS1120 ADC driver
.../bindings/iio/adc/ti,ads1120.yaml | 50 ++
MAINTAINERS | 7 +
drivers/iio/adc/Kconfig | 10 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ti-ads1120.c | 509 ++++++++++++++++++
5 files changed, 577 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml
create mode 100644 drivers/iio/adc/ti-ads1120.c
--
2.34.1
^ permalink raw reply [flat|nested] 24+ messages in thread* [RFC PATCH 1/3] dt-bindings: iio: adc: Add TI ADS1120 binding 2025-10-30 16:34 [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC Ajith Anandhan @ 2025-10-30 16:34 ` Ajith Anandhan 2025-10-30 17:12 ` Jonathan Cameron 2025-10-30 16:34 ` [RFC PATCH 2/3] iio: adc: Add support for TI ADS1120 Ajith Anandhan ` (3 subsequent siblings) 4 siblings, 1 reply; 24+ messages in thread From: Ajith Anandhan @ 2025-10-30 16:34 UTC (permalink / raw) To: linux-iio Cc: jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel, Ajith Anandhan Add device tree binding documentation for the Texas Instruments ADS1120. The binding defines required properties like compatible, reg, and SPI configuration parameters. Link: https://www.ti.com/lit/gpn/ads1120 Signed-off-by: Ajith Anandhan <ajithanandhan0406@gmail.com> --- .../bindings/iio/adc/ti,ads1120.yaml | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml new file mode 100644 index 000000000..09285c981 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/ti,ads1120.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments ADS1120 4-channel, 16-bit, 2kSPS ADC + +maintainers: + - Ajith Anandhan <ajithanandhan0406@gmail.com> + +properties: + compatible: + const: ti,ads1120 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 4000000 + + spi-cpha: true + + "#io-channel-cells": + const: 1 + +required: + - compatible + - reg + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "ti,ads1120"; + reg = <0>; + spi-max-frequency = <4000000>; + spi-cpha; + }; + }; +... + -- 2.34.1 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 1/3] dt-bindings: iio: adc: Add TI ADS1120 binding 2025-10-30 16:34 ` [RFC PATCH 1/3] dt-bindings: iio: adc: Add TI ADS1120 binding Ajith Anandhan @ 2025-10-30 17:12 ` Jonathan Cameron 2025-10-30 20:04 ` David Lechner 2025-11-01 11:53 ` Ajith Anandhan 0 siblings, 2 replies; 24+ messages in thread From: Jonathan Cameron @ 2025-10-30 17:12 UTC (permalink / raw) To: Ajith Anandhan Cc: linux-iio, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On Thu, 30 Oct 2025 22:04:09 +0530 Ajith Anandhan <ajithanandhan0406@gmail.com> wrote: > Add device tree binding documentation for the Texas Instruments > ADS1120. > > The binding defines required properties like compatible, reg, and > SPI configuration parameters. > > Link: https://www.ti.com/lit/gpn/ads1120 Datasheet: https://www.ti.com/lit/gpn/ads1120 Is a somewhat official tag for these. Though better to put it in the dt-binding doc itself as well or instead of here. > Signed-off-by: Ajith Anandhan <ajithanandhan0406@gmail.com> > --- > .../bindings/iio/adc/ti,ads1120.yaml | 50 +++++++++++++++++++ > 1 file changed, 50 insertions(+) > create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml > > diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml > new file mode 100644 > index 000000000..09285c981 > --- /dev/null > +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml > @@ -0,0 +1,50 @@ > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/iio/adc/ti,ads1120.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: Texas Instruments ADS1120 4-channel, 16-bit, 2kSPS ADC > + > +maintainers: > + - Ajith Anandhan <ajithanandhan0406@gmail.com> > + > +properties: > + compatible: > + const: ti,ads1120 > + > + reg: > + maxItems: 1 > + > + spi-max-frequency: > + maximum: 4000000 > + > + spi-cpha: true > + > + "#io-channel-cells": > + const: 1 Power supplies should be here and required (even if real boards rely on stub regulators). Looks like there is an optional reference as well - so include that but not as required (use internal ref if not supplied). There is a data ready pin as well so I'd expect an interrupt. All these should be in the binding from the start as we want it to be as complete as possible. The driver doesn't have to use everything the binding supplies. > + > +required: > + - compatible > + - reg > + > +allOf: > + - $ref: /schemas/spi/spi-peripheral-props.yaml# > + > +unevaluatedProperties: false > + > +examples: > + - | > + spi { > + #address-cells = <1>; > + #size-cells = <0>; > + > + adc@0 { > + compatible = "ti,ads1120"; > + reg = <0>; > + spi-max-frequency = <4000000>; > + spi-cpha; > + }; > + }; > +... > + ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 1/3] dt-bindings: iio: adc: Add TI ADS1120 binding 2025-10-30 17:12 ` Jonathan Cameron @ 2025-10-30 20:04 ` David Lechner 2025-11-01 12:24 ` Ajith Anandhan 2025-11-01 11:53 ` Ajith Anandhan 1 sibling, 1 reply; 24+ messages in thread From: David Lechner @ 2025-10-30 20:04 UTC (permalink / raw) To: Jonathan Cameron, Ajith Anandhan Cc: linux-iio, jic23, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On 10/30/25 12:12 PM, Jonathan Cameron wrote: > On Thu, 30 Oct 2025 22:04:09 +0530 > Ajith Anandhan <ajithanandhan0406@gmail.com> wrote: > >> Add device tree binding documentation for the Texas Instruments >> ADS1120. >> >> The binding defines required properties like compatible, reg, and >> SPI configuration parameters. >> >> Link: https://www.ti.com/lit/gpn/ads1120 > Datasheet: https://www.ti.com/lit/gpn/ads1120 > > Is a somewhat official tag for these. Though better to put it in the dt-binding > doc itself as well or instead of here. > >> Signed-off-by: Ajith Anandhan <ajithanandhan0406@gmail.com> >> --- >> .../bindings/iio/adc/ti,ads1120.yaml | 50 +++++++++++++++++++ >> 1 file changed, 50 insertions(+) >> create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml >> >> diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml >> new file mode 100644 >> index 000000000..09285c981 >> --- /dev/null >> +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml >> @@ -0,0 +1,50 @@ >> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) >> +%YAML 1.2 >> +--- >> +$id: http://devicetree.org/schemas/iio/adc/ti,ads1120.yaml# >> +$schema: http://devicetree.org/meta-schemas/core.yaml# >> + >> +title: Texas Instruments ADS1120 4-channel, 16-bit, 2kSPS ADC >> + >> +maintainers: >> + - Ajith Anandhan <ajithanandhan0406@gmail.com> >> + >> +properties: >> + compatible: >> + const: ti,ads1120 >> + >> + reg: >> + maxItems: 1 >> + >> + spi-max-frequency: >> + maximum: 4000000 >> + >> + spi-cpha: true >> + >> + "#io-channel-cells": >> + const: 1 > > Power supplies should be here and required (even if real boards > rely on stub regulators). > > Looks like there is an optional reference as well - so include that > but not as required (use internal ref if not supplied). Actually, there are two. REF{P,N}1 is an alternative function of the AIN{0,3} pins. It is also possible that the analog power supply can be used as a reference source instead of the internal one. This came up recently and we glossed over it. However, I think it would make sense to have a flag property that means "the AVSS supply is of sufficient quality that it is better than the internal reference supply", e.g. ti,avdss-is-ref. And drivers can use this info to decide if they want to select it as the reference voltage or not. > > There is a data ready pin as well so I'd expect an interrupt. There is actually two DRDY pins. One is shared with DOUT, so we should have two interrupts and interrupt-names so we know which pin is actually wired up. > > All these should be in the binding from the start as we want it > to be as complete as possible. The driver doesn't have to use everything > the binding supplies. > Another trivial one is an optional clocks property for the external clock. It doesn't need clock-names since there is only one. Additional bindings needed when this is used with temperature sensors are not so trivial though, so we don't need to add those until someone actually needs them. ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 1/3] dt-bindings: iio: adc: Add TI ADS1120 binding 2025-10-30 20:04 ` David Lechner @ 2025-11-01 12:24 ` Ajith Anandhan 0 siblings, 0 replies; 24+ messages in thread From: Ajith Anandhan @ 2025-11-01 12:24 UTC (permalink / raw) To: David Lechner, Jonathan Cameron Cc: linux-iio, jic23, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On 10/31/25 1:34 AM, David Lechner wrote: > On 10/30/25 12:12 PM, Jonathan Cameron wrote: >> On Thu, 30 Oct 2025 22:04:09 +0530 >> Ajith Anandhan<ajithanandhan0406@gmail.com> wrote: >> >>> Add device tree binding documentation for the Texas Instruments >>> ADS1120. >>> >>> The binding defines required properties like compatible, reg, and >>> SPI configuration parameters. >>> >>> Link:https://www.ti.com/lit/gpn/ads1120 >> Datasheet:https://www.ti.com/lit/gpn/ads1120 >> >> Is a somewhat official tag for these. Though better to put it in the dt-binding >> doc itself as well or instead of here. >> >>> Signed-off-by: Ajith Anandhan<ajithanandhan0406@gmail.com> >>> --- >>> .../bindings/iio/adc/ti,ads1120.yaml | 50 +++++++++++++++++++ >>> 1 file changed, 50 insertions(+) >>> create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml >>> >>> diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml >>> new file mode 100644 >>> index 000000000..09285c981 >>> --- /dev/null >>> +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml >>> @@ -0,0 +1,50 @@ >>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) >>> +%YAML 1.2 >>> +--- >>> +$id:http://devicetree.org/schemas/iio/adc/ti,ads1120.yaml# >>> +$schema:http://devicetree.org/meta-schemas/core.yaml# >>> + >>> +title: Texas Instruments ADS1120 4-channel, 16-bit, 2kSPS ADC >>> + >>> +maintainers: >>> + - Ajith Anandhan<ajithanandhan0406@gmail.com> >>> + >>> +properties: >>> + compatible: >>> + const: ti,ads1120 >>> + >>> + reg: >>> + maxItems: 1 >>> + >>> + spi-max-frequency: >>> + maximum: 4000000 >>> + >>> + spi-cpha: true >>> + >>> + "#io-channel-cells": >>> + const: 1 >> Power supplies should be here and required (even if real boards >> rely on stub regulators). >> >> Looks like there is an optional reference as well - so include that >> but not as required (use internal ref if not supplied). > Actually, there are two. REF{P,N}1 is an alternative function of > the AIN{0,3} pins. I'll add avdd-supply as required and vref-supply as optional to support both external reference configurations (REFP0/REFN0 and REFP1/REFN1). > > It is also possible that the analog power supply can be used as > a reference source instead of the internal one. > > This came up recently and we glossed over it. However, I think > it would make sense to have a flag property that means "the > AVSS supply is of sufficient quality that it is better than > the internal reference supply", e.g. ti,avdss-is-ref. And > drivers can use this info to decide if they want to select it > as the reference voltage or not. I'll add the ti,avdss-is-ref boolean property to let the driver know when AVDD is suitable as a reference source. > >> There is a data ready pin as well so I'd expect an interrupt. > There is actually two DRDY pins. One is shared with DOUT, so we > should have two interrupts and interrupt-names so we know which > pin is actually wired up. Thanks for the clarification. I'll add support for up to two interrupts with interrupt-names ("drdy" and "dout") to handle both pin configurations. > >> All these should be in the binding from the start as we want it >> to be as complete as possible. The driver doesn't have to use everything >> the binding supplies. >> > Another trivial one is an optional clocks property for the external > clock. It doesn't need clock-names since there is only one. Will add the optional clocks property for external clock support. > > Additional bindings needed when this is used with temperature sensors > are not so trivial though, so we don't need to add those until someone > actually needs them. I'll incorporate all these changes in v2. Thank you both for the thorough review! -- Best Regards, Ajith Anandhan. ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 1/3] dt-bindings: iio: adc: Add TI ADS1120 binding 2025-10-30 17:12 ` Jonathan Cameron 2025-10-30 20:04 ` David Lechner @ 2025-11-01 11:53 ` Ajith Anandhan 1 sibling, 0 replies; 24+ messages in thread From: Ajith Anandhan @ 2025-11-01 11:53 UTC (permalink / raw) To: Jonathan Cameron Cc: linux-iio, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On 10/30/25 10:42 PM, Jonathan Cameron wrote: > On Thu, 30 Oct 2025 22:04:09 +0530 > Ajith Anandhan <ajithanandhan0406@gmail.com> wrote: > >> Add device tree binding documentation for the Texas Instruments >> ADS1120. >> >> The binding defines required properties like compatible, reg, and >> SPI configuration parameters. >> >> Link: https://www.ti.com/lit/gpn/ads1120 > Datasheet: https://www.ti.com/lit/gpn/ads1120 > > Is a somewhat official tag for these. Though better to put it in the dt-binding > doc itself as well or instead of here. > >> Signed-off-by: Ajith Anandhan <ajithanandhan0406@gmail.com> >> --- >> .../bindings/iio/adc/ti,ads1120.yaml | 50 +++++++++++++++++++ >> 1 file changed, 50 insertions(+) >> create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml >> >> diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml >> new file mode 100644 >> index 000000000..09285c981 >> --- /dev/null >> +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml >> @@ -0,0 +1,50 @@ >> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) >> +%YAML 1.2 >> +--- >> +$id: http://devicetree.org/schemas/iio/adc/ti,ads1120.yaml# >> +$schema: http://devicetree.org/meta-schemas/core.yaml# >> + >> +title: Texas Instruments ADS1120 4-channel, 16-bit, 2kSPS ADC >> + >> +maintainers: >> + - Ajith Anandhan <ajithanandhan0406@gmail.com> >> + >> +properties: >> + compatible: >> + const: ti,ads1120 >> + >> + reg: >> + maxItems: 1 >> + >> + spi-max-frequency: >> + maximum: 4000000 >> + >> + spi-cpha: true >> + >> + "#io-channel-cells": >> + const: 1 > Power supplies should be here and required (even if real boards > rely on stub regulators). > > Looks like there is an optional reference as well - so include that > but not as required (use internal ref if not supplied). > > There is a data ready pin as well so I'd expect an interrupt. > > All these should be in the binding from the start as we want it > to be as complete as possible. The driver doesn't have to use everything > the binding supplies. > >> + >> +required: >> + - compatible >> + - reg >> + >> +allOf: >> + - $ref: /schemas/spi/spi-peripheral-props.yaml# >> + >> +unevaluatedProperties: false >> + >> +examples: >> + - | >> + spi { >> + #address-cells = <1>; >> + #size-cells = <0>; >> + >> + adc@0 { >> + compatible = "ti,ads1120"; >> + reg = <0>; >> + spi-max-frequency = <4000000>; >> + spi-cpha; >> + }; >> + }; >> +... >> + Hi Jonathan, Thank you for the detailed review and helpful suggestions. I'll update the binding to include the following: - Add required power supply property (avdd-supply) and optional vref-supply. - Add interrupt and interrupt-names for DRDY and DOUT. - Move the datasheet link into the dt-binding description. I'll incorporate these changes and resend as a V2 (non-RFC) patch series. -- BR, Ajith Anandhan. ^ permalink raw reply [flat|nested] 24+ messages in thread
* [RFC PATCH 2/3] iio: adc: Add support for TI ADS1120 2025-10-30 16:34 [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC Ajith Anandhan 2025-10-30 16:34 ` [RFC PATCH 1/3] dt-bindings: iio: adc: Add TI ADS1120 binding Ajith Anandhan @ 2025-10-30 16:34 ` Ajith Anandhan 2025-10-30 17:54 ` Jonathan Cameron 2025-10-30 21:13 ` David Lechner 2025-10-30 16:34 ` [RFC PATCH 3/3] MAINTAINERS: Add entry for TI ADS1120 ADC driver Ajith Anandhan ` (2 subsequent siblings) 4 siblings, 2 replies; 24+ messages in thread From: Ajith Anandhan @ 2025-10-30 16:34 UTC (permalink / raw) To: linux-iio Cc: jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel, Ajith Anandhan Add driver for the Texas Instruments ADS1120, a precision 16-bit analog-to-digital converter with an SPI interface. The driver provides: - 4 single-ended voltage input channels - Programmable gain amplifier (1 to 128) - Configurable data rates (20 to 1000 SPS) - Single-shot conversion mode Link: https://www.ti.com/lit/gpn/ads1120 Signed-off-by: Ajith Anandhan <ajithanandhan0406@gmail.com> --- drivers/iio/adc/Kconfig | 10 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ti-ads1120.c | 509 +++++++++++++++++++++++++++++++++++ 3 files changed, 520 insertions(+) create mode 100644 drivers/iio/adc/ti-ads1120.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 58a14e683..afb7895dd 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1655,6 +1655,16 @@ config TI_ADS1119 This driver can also be built as a module. If so, the module will be called ti-ads1119. +config TI_ADS1120 + tristate "Texas Instruments ADS1120" + depends on SPI + help + Say yes here to build support for Texas Instruments ADS1120 + 4-channel, 2kSPS, 16-bit delta-sigma ADC. + + This driver can also be built as module. If so, the module + will be called ti-ads1120. + config TI_ADS124S08 tristate "Texas Instruments ADS124S08" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index d008f78dc..49c56b459 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -144,6 +144,7 @@ obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o +obj-$(CONFIG_TI_ADS1120) += ti-ads1120.o obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o diff --git a/drivers/iio/adc/ti-ads1120.c b/drivers/iio/adc/ti-ads1120.c new file mode 100644 index 000000000..07a6fb309 --- /dev/null +++ b/drivers/iio/adc/ti-ads1120.c @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TI ADS1120 4-channel, 2kSPS, 16-bit delta-sigma ADC + * + * Datasheet: https://www.ti.com/lit/gpn/ads1120 + * + * Copyright (C) 2025 Ajith Anandhan <ajithanandhan0406@gmail.com> + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/spi/spi.h> + +#include <linux/iio/iio.h> + +/* Commands */ +#define ADS1120_CMD_RESET 0x06 +#define ADS1120_CMD_START 0x08 +#define ADS1120_CMD_PWRDWN 0x02 +#define ADS1120_CMD_RDATA 0x10 +#define ADS1120_CMD_RREG 0x20 +#define ADS1120_CMD_WREG 0x40 + +/* Registers */ +#define ADS1120_REG_CONFIG0 0x00 +#define ADS1120_REG_CONFIG1 0x01 +#define ADS1120_REG_CONFIG2 0x02 +#define ADS1120_REG_CONFIG3 0x03 + +/* Config Register 0 bit definitions */ +#define ADS1120_MUX_MASK GENMASK(7, 4) +#define ADS1120_MUX_AIN0_AVSS 0x80 +#define ADS1120_MUX_AIN1_AVSS 0x90 +#define ADS1120_MUX_AIN2_AVSS 0xa0 +#define ADS1120_MUX_AIN3_AVSS 0xb0 + +#define ADS1120_GAIN_MASK GENMASK(3, 1) +#define ADS1120_GAIN_1 0x00 +#define ADS1120_GAIN_2 0x02 +#define ADS1120_GAIN_4 0x04 +#define ADS1120_GAIN_8 0x06 +#define ADS1120_GAIN_16 0x08 +#define ADS1120_GAIN_32 0x0a +#define ADS1120_GAIN_64 0x0c +#define ADS1120_GAIN_128 0x0e + +#define ADS1120_PGA_BYPASS BIT(0) + +/* Config Register 1 bit definitions */ +#define ADS1120_DR_MASK GENMASK(7, 5) +#define ADS1120_DR_20SPS 0x00 +#define ADS1120_DR_45SPS 0x20 +#define ADS1120_DR_90SPS 0x40 +#define ADS1120_DR_175SPS 0x60 +#define ADS1120_DR_330SPS 0x80 +#define ADS1120_DR_600SPS 0xa0 +#define ADS1120_DR_1000SPS 0xc0 + +#define ADS1120_MODE_MASK GENMASK(4, 3) +#define ADS1120_MODE_NORMAL 0x00 + +#define ADS1120_CM_SINGLE 0x00 +#define ADS1120_CM_CONTINUOUS BIT(2) + +#define ADS1120_TS_EN BIT(1) +#define ADS1120_BCS_EN BIT(0) + +/* Config Register 2 bit definitions */ +#define ADS1120_VREF_MASK GENMASK(7, 6) +#define ADS1120_VREF_INTERNAL 0x00 +#define ADS1120_VREF_EXT_REFP0 0x40 +#define ADS1120_VREF_EXT_AIN0 0x80 +#define ADS1120_VREF_AVDD 0xc0 + +#define ADS1120_REJECT_MASK GENMASK(5, 4) +#define ADS1120_REJECT_OFF 0x00 +#define ADS1120_REJECT_50_60 0x10 +#define ADS1120_REJECT_50 0x20 +#define ADS1120_REJECT_60 0x30 + +#define ADS1120_PSW_EN BIT(3) + +#define ADS1120_IDAC_MASK GENMASK(2, 0) + +/* Config Register 3 bit definitions */ +#define ADS1120_IDAC1_MASK GENMASK(7, 5) +#define ADS1120_IDAC2_MASK GENMASK(4, 2) +#define ADS1120_DRDYM_EN BIT(1) + +/* Default configuration values */ +#define ADS1120_DEFAULT_GAIN 1 +#define ADS1120_DEFAULT_DATARATE 175 + +struct ads1120_state { + struct spi_device *spi; + struct mutex lock; + /* + * Used to correctly align data. + * Ensure natural alignment for potential future timestamp support. + */ + u8 data[4] __aligned(IIO_DMA_MINALIGN); + + u8 config[4]; + int current_channel; + int gain; + int datarate; + int conv_time_ms; +}; + +struct ads1120_datarate { + int rate; + int conv_time_ms; + u8 reg_value; +}; + +static const struct ads1120_datarate ads1120_datarates[] = { + { 20, 51, ADS1120_DR_20SPS }, + { 45, 24, ADS1120_DR_45SPS }, + { 90, 13, ADS1120_DR_90SPS }, + { 175, 7, ADS1120_DR_175SPS }, + { 330, 4, ADS1120_DR_330SPS }, + { 600, 3, ADS1120_DR_600SPS }, + { 1000, 2, ADS1120_DR_1000SPS }, +}; + +static const int ads1120_gain_values[] = { 1, 2, 4, 8, 16, 32, 64, 128 }; + +#define ADS1120_CHANNEL(index) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec ads1120_channels[] = { + ADS1120_CHANNEL(0), + ADS1120_CHANNEL(1), + ADS1120_CHANNEL(2), + ADS1120_CHANNEL(3), +}; + +static int ads1120_write_cmd(struct ads1120_state *st, u8 cmd) +{ + st->data[0] = cmd; + return spi_write(st->spi, st->data, 1); +} + +static int ads1120_write_reg(struct ads1120_state *st, u8 reg, u8 value) +{ + u8 buf[2]; + + if (reg > ADS1120_REG_CONFIG3) + return -EINVAL; + + buf[0] = ADS1120_CMD_WREG | (reg << 2); + buf[1] = value; + + return spi_write(st->spi, buf, 2); +} + +static int ads1120_reset(struct ads1120_state *st) +{ + int ret; + + ret = ads1120_write_cmd(st, ADS1120_CMD_RESET); + if (ret) + return ret; + + /* + * Datasheet specifies reset takes 50us + 32 * t(CLK) + * where t(CLK) = 1/4.096MHz. Use 200us to be safe. + */ + usleep_range(200, 300); + + return 0; +} + +static int ads1120_set_channel(struct ads1120_state *st, int channel) +{ + u8 mux_val; + u8 config0; + + if (channel < 0 || channel > 3) + return -EINVAL; + + /* Map channel to AINx/AVSS single-ended input */ + mux_val = ADS1120_MUX_AIN0_AVSS + (channel << 4); + + config0 = (st->config[0] & ~ADS1120_MUX_MASK) | mux_val; + st->config[0] = config0; + + return ads1120_write_reg(st, ADS1120_REG_CONFIG0, config0); +} + +static int ads1120_set_gain(struct ads1120_state *st, int gain_val) +{ + u8 gain_bits; + u8 config0; + int i; + + /* Find gain in supported values */ + for (i = 0; i < ARRAY_SIZE(ads1120_gain_values); i++) { + if (ads1120_gain_values[i] == gain_val) { + gain_bits = i << 1; + goto found; + } + } + return -EINVAL; + +found: + config0 = (st->config[0] & ~ADS1120_GAIN_MASK) | gain_bits; + st->config[0] = config0; + st->gain = gain_val; + + return ads1120_write_reg(st, ADS1120_REG_CONFIG0, config0); +} + +static int ads1120_set_datarate(struct ads1120_state *st, int rate) +{ + u8 config1; + int i; + + /* Find data rate in supported values */ + for (i = 0; i < ARRAY_SIZE(ads1120_datarates); i++) { + if (ads1120_datarates[i].rate == rate) { + config1 = (st->config[1] & ~ADS1120_DR_MASK) | + ads1120_datarates[i].reg_value; + st->config[1] = config1; + st->datarate = rate; + st->conv_time_ms = ads1120_datarates[i].conv_time_ms; + + return ads1120_write_reg(st, ADS1120_REG_CONFIG1, + config1); + } + } + + return -EINVAL; +} + +static int ads1120_read_raw_adc(struct ads1120_state *st, int *val) +{ + u8 rx_buf[4]; + u8 tx_buf[4] = { ADS1120_CMD_RDATA, 0xff, 0xff, 0xff }; + int ret; + struct spi_transfer xfer = { + .tx_buf = tx_buf, + .rx_buf = rx_buf, + .len = 4, + }; + + ret = spi_sync_transfer(st->spi, &xfer, 1); + if (ret) + return ret; + + /* + * Data format: 16-bit two's complement MSB first + * rx_buf[0] is echo of command + * rx_buf[1] is MSB of data + * rx_buf[2] is LSB of data + */ + *val = (s16)((rx_buf[1] << 8) | rx_buf[2]); + + return 0; +} + +static int ads1120_read_measurement(struct ads1120_state *st, int channel, + int *val) +{ + int ret; + + ret = ads1120_set_channel(st, channel); + if (ret) + return ret; + + /* Start single-shot conversion */ + ret = ads1120_write_cmd(st, ADS1120_CMD_START); + if (ret) + return ret; + + /* Wait for conversion to complete */ + msleep(st->conv_time_ms); + + /* Read the result */ + ret = ads1120_read_raw_adc(st, val); + if (ret) + return ret; + + st->current_channel = channel; + + return 0; +} + +static int ads1120_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ads1120_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + ret = ads1120_read_measurement(st, chan->channel, val); + mutex_unlock(&st->lock); + if (ret) + return ret; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = st->gain; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->datarate; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int ads1120_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ads1120_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + mutex_lock(&st->lock); + ret = ads1120_set_gain(st, val); + mutex_unlock(&st->lock); + return ret; + + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&st->lock); + ret = ads1120_set_datarate(st, val); + mutex_unlock(&st->lock); + return ret; + + default: + return -EINVAL; + } +} + +static int ads1120_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + static const int datarates[] = { 20, 45, 90, 175, 330, 600, 1000 }; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = ads1120_gain_values; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(ads1120_gain_values); + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = datarates; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(datarates); + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static const struct iio_info ads1120_info = { + .read_raw = ads1120_read_raw, + .write_raw = ads1120_write_raw, + .read_avail = ads1120_read_avail, +}; + +static int ads1120_init(struct ads1120_state *st) +{ + int ret; + + /* Reset device to default state */ + ret = ads1120_reset(st); + if (ret) { + dev_err(&st->spi->dev, "Failed to reset device\n"); + return ret; + } + + /* + * Configure Register 0: + * - Input MUX: AIN0/AVSS (updated per channel read) + * - Gain: 1 + * - PGA bypassed (lower power consumption) + */ + st->config[0] = ADS1120_MUX_AIN0_AVSS | ADS1120_GAIN_1 | + ADS1120_PGA_BYPASS; + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG0, st->config[0]); + if (ret) + return ret; + + /* + * Configure Register 1: + * - Data rate: 175 SPS + * - Mode: Normal + * - Conversion mode: Single-shot + * - Temperature sensor: Disabled + * - Burnout current: Disabled + */ + st->config[1] = ADS1120_DR_175SPS | ADS1120_MODE_NORMAL | + ADS1120_CM_SINGLE; + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG1, st->config[1]); + if (ret) + return ret; + + /* + * Configure Register 2: + * - Voltage reference: AVDD + * - 50/60Hz rejection: Off + * - Power switch: Off + * - IDAC: Off + */ + st->config[2] = ADS1120_VREF_AVDD | ADS1120_REJECT_OFF; + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG2, st->config[2]); + if (ret) + return ret; + + /* + * Configure Register 3: + * - IDAC1: Disabled + * - IDAC2: Disabled + * - DRDY mode: DOUT/DRDY pin behavior + */ + st->config[3] = ADS1120_DRDYM_EN; + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG3, st->config[3]); + if (ret) + return ret; + + /* Set default operating parameters */ + st->gain = ADS1120_DEFAULT_GAIN; + st->datarate = ADS1120_DEFAULT_DATARATE; + st->conv_time_ms = 7; /* For 175 SPS */ + st->current_channel = -1; + + return 0; +} + +static int ads1120_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ads1120_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + mutex_init(&st->lock); + + indio_dev->name = "ads1120"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ads1120_channels; + indio_dev->num_channels = ARRAY_SIZE(ads1120_channels); + indio_dev->info = &ads1120_info; + + ret = ads1120_init(st); + if (ret) { + dev_err(&spi->dev, "Failed to initialize device: %d\n", ret); + return ret; + } + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id ads1120_of_match[] = { + { .compatible = "ti,ads1120" }, + { } +}; +MODULE_DEVICE_TABLE(of, ads1120_of_match); + +static const struct spi_device_id ads1120_id[] = { + { "ads1120" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads1120_id); + +static struct spi_driver ads1120_driver = { + .driver = { + .name = "ads1120", + .of_match_table = ads1120_of_match, + }, + .probe = ads1120_probe, + .id_table = ads1120_id, +}; +module_spi_driver(ads1120_driver); + +MODULE_AUTHOR("Ajith Anandhan <ajithanandhan0406@gmail.com>"); +MODULE_DESCRIPTION("Texas Instruments ADS1120 ADC driver"); +MODULE_LICENSE("GPL"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 2/3] iio: adc: Add support for TI ADS1120 2025-10-30 16:34 ` [RFC PATCH 2/3] iio: adc: Add support for TI ADS1120 Ajith Anandhan @ 2025-10-30 17:54 ` Jonathan Cameron 2025-11-07 14:40 ` Ajith Anandhan 2025-10-30 21:13 ` David Lechner 1 sibling, 1 reply; 24+ messages in thread From: Jonathan Cameron @ 2025-10-30 17:54 UTC (permalink / raw) To: Ajith Anandhan Cc: linux-iio, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On Thu, 30 Oct 2025 22:04:10 +0530 Ajith Anandhan <ajithanandhan0406@gmail.com> wrote: > Add driver for the Texas Instruments ADS1120, a precision 16-bit > analog-to-digital converter with an SPI interface. > > The driver provides: > - 4 single-ended voltage input channels > - Programmable gain amplifier (1 to 128) > - Configurable data rates (20 to 1000 SPS) > - Single-shot conversion mode > > Link: https://www.ti.com/lit/gpn/ads1120 Datasheet: > Signed-off-by: Ajith Anandhan <ajithanandhan0406@gmail.com> Hi Ajith Various comments inline. Mostly superficial stuff but the DMA safety of SPI buffers needs fixing. There is a useful talk from this given by Wolfram Sang if you want to understand more about this https://www.youtube.com/watch?v=JDwaMClvV-s Thanks, Jonathan > diff --git a/drivers/iio/adc/ti-ads1120.c b/drivers/iio/adc/ti-ads1120.c > new file mode 100644 > index 000000000..07a6fb309 > --- /dev/null > +++ b/drivers/iio/adc/ti-ads1120.c > @@ -0,0 +1,509 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * TI ADS1120 4-channel, 2kSPS, 16-bit delta-sigma ADC > + * > + * Datasheet: https://www.ti.com/lit/gpn/ads1120 > + * > + * Copyright (C) 2025 Ajith Anandhan <ajithanandhan0406@gmail.com> > + */ > + > +#include <linux/delay.h> > +#include <linux/device.h> > +#include <linux/err.h> > +#include <linux/kernel.h> Figure out what you are actually using. There is ongoing effort to not include kernel.h in drivers because there is usually a small set of more appropriate fine grained includes. > +#include <linux/module.h> > +#include <linux/mod_devicetable.h> > +#include <linux/mutex.h> > +#include <linux/spi/spi.h> > + > +#include <linux/iio/iio.h> > + > +/* Commands */ > +#define ADS1120_CMD_RESET 0x06 > +#define ADS1120_CMD_START 0x08 > +#define ADS1120_CMD_PWRDWN 0x02 > +#define ADS1120_CMD_RDATA 0x10 > +#define ADS1120_CMD_RREG 0x20 > +#define ADS1120_CMD_WREG 0x40 > + > +/* Registers */ > +#define ADS1120_REG_CONFIG0 0x00 > +#define ADS1120_REG_CONFIG1 0x01 > +#define ADS1120_REG_CONFIG2 0x02 > +#define ADS1120_REG_CONFIG3 0x03 > + > +/* Config Register 0 bit definitions */ > +#define ADS1120_MUX_MASK GENMASK(7, 4) > +#define ADS1120_MUX_AIN0_AVSS 0x80 > +#define ADS1120_MUX_AIN1_AVSS 0x90 > +#define ADS1120_MUX_AIN2_AVSS 0xa0 > +#define ADS1120_MUX_AIN3_AVSS 0xb0 > + > +#define ADS1120_GAIN_MASK GENMASK(3, 1) Better to name these so it's obvious which register they are in. #define ADS1120_CFG0_GAIN_MSK or similar. Saves anyone checking every time that it's being written to the appropriate register. > +#define ADS1120_GAIN_1 0x00 > +#define ADS1120_GAIN_2 0x02 > +#define ADS1120_GAIN_4 0x04 > +#define ADS1120_GAIN_8 0x06 > +#define ADS1120_GAIN_16 0x08 > +#define ADS1120_GAIN_32 0x0a > +#define ADS1120_GAIN_64 0x0c > +#define ADS1120_GAIN_128 0x0e Same as called out for DR below. (applies in other places as well). > + > +#define ADS1120_PGA_BYPASS BIT(0) > + > +/* Config Register 1 bit definitions */ > +#define ADS1120_DR_MASK GENMASK(7, 5) > +#define ADS1120_DR_20SPS 0x00 > +#define ADS1120_DR_45SPS 0x20 > +#define ADS1120_DR_90SPS 0x40 > +#define ADS1120_DR_175SPS 0x60 > +#define ADS1120_DR_330SPS 0x80 > +#define ADS1120_DR_600SPS 0xa0 > +#define ADS1120_DR_1000SPS 0xc0 Define these as values of the field (So not shifted) #define ADS1120_DR_20SPS 0 #define ADS1120_DR_45SPS 1 etc. Then use FIELD_PREP(ADS1120_DR_MASK, ADIS1120_DR_20SPS) and similar to set them. > + > +#define ADS1120_MODE_MASK GENMASK(4, 3) > +#define ADS1120_MODE_NORMAL 0x00 No other values for a 2 bit field? Define all values even if you only use one for now. Makes it easier to review the values > + > +#define ADS1120_CM_SINGLE 0x00 > +#define ADS1120_CM_CONTINUOUS BIT(2) > + > +#define ADS1120_TS_EN BIT(1) > +#define ADS1120_BCS_EN BIT(0) > + > +/* Config Register 2 bit definitions */ > +#define ADS1120_VREF_MASK GENMASK(7, 6) > +#define ADS1120_VREF_INTERNAL 0x00 > +#define ADS1120_VREF_EXT_REFP0 0x40 > +#define ADS1120_VREF_EXT_AIN0 0x80 > +#define ADS1120_VREF_AVDD 0xc0 > + > +#define ADS1120_REJECT_MASK GENMASK(5, 4) > +#define ADS1120_REJECT_OFF 0x00 > +#define ADS1120_REJECT_50_60 0x10 > +#define ADS1120_REJECT_50 0x20 > +#define ADS1120_REJECT_60 0x30 > + > +#define ADS1120_PSW_EN BIT(3) > + > +#define ADS1120_IDAC_MASK GENMASK(2, 0) > + > +/* Config Register 3 bit definitions */ > +#define ADS1120_IDAC1_MASK GENMASK(7, 5) > +#define ADS1120_IDAC2_MASK GENMASK(4, 2) > +#define ADS1120_DRDYM_EN BIT(1) > + > +/* Default configuration values */ > +#define ADS1120_DEFAULT_GAIN 1 > +#define ADS1120_DEFAULT_DATARATE 175 These should be just handled by writing the registers in init. The defines in of themselves don't help over seeing the default set in code. > + > +struct ads1120_state { > + struct spi_device *spi; > + struct mutex lock; Locks should always have comments to say what data they protect. > + /* > + * Used to correctly align data. > + * Ensure natural alignment for potential future timestamp support. You don't do that except by coincidence of IIO_DMA_MINALIGN being large enough. So this comment is misleading - Drop it. > + */ > + u8 data[4] __aligned(IIO_DMA_MINALIGN); Unless everything after this is used for DMA buffers you have defeated the point of the __aligned 'trick'. We need to ensure nothing that isn't used for DMA and can potentially be modified in parallel with this is not in the cache line. Probably a case of just moving data to the end of the structure. > + > + u8 config[4]; > + int current_channel; > + int gain; > + int datarate; > + int conv_time_ms; > +}; > + > +struct ads1120_datarate { > + int rate; > + int conv_time_ms; > + u8 reg_value; > +}; > + > +static const struct ads1120_datarate ads1120_datarates[] = { > + { 20, 51, ADS1120_DR_20SPS }, As above, use the field values not the shifted ones. > + { 45, 24, ADS1120_DR_45SPS }, > + { 90, 13, ADS1120_DR_90SPS }, > + { 175, 7, ADS1120_DR_175SPS }, > + { 330, 4, ADS1120_DR_330SPS }, > + { 600, 3, ADS1120_DR_600SPS }, > + { 1000, 2, ADS1120_DR_1000SPS }, > +}; > +static int ads1120_write_cmd(struct ads1120_state *st, u8 cmd) > +{ > + st->data[0] = cmd; > + return spi_write(st->spi, st->data, 1); > +} > + > +static int ads1120_write_reg(struct ads1120_state *st, u8 reg, u8 value) > +{ > + u8 buf[2]; > + > + if (reg > ADS1120_REG_CONFIG3) > + return -EINVAL; > + > + buf[0] = ADS1120_CMD_WREG | (reg << 2); > + buf[1] = value; > + > + return spi_write(st->spi, buf, 2); sizeof(buf); However DMA safety thing applies here as well so you can't just use a buffer in the stack. Can use spi_write_then_read() though as that bounce buffers. > +} > + > +static int ads1120_set_channel(struct ads1120_state *st, int channel) > +{ > + u8 mux_val; > + u8 config0; > + > + if (channel < 0 || channel > 3) > + return -EINVAL; > + > + /* Map channel to AINx/AVSS single-ended input */ > + mux_val = ADS1120_MUX_AIN0_AVSS + (channel << 4); > + > + config0 = (st->config[0] & ~ADS1120_MUX_MASK) | mux_val; > + st->config[0] = config0; FIELD_MODIFY() after the defines are field values (not shifted version) and that << 4 is gone. > + > + return ads1120_write_reg(st, ADS1120_REG_CONFIG0, config0); > +} > + > +static int ads1120_set_gain(struct ads1120_state *st, int gain_val) > +{ > + u8 gain_bits; > + u8 config0; > + int i; > + > + /* Find gain in supported values */ > + for (i = 0; i < ARRAY_SIZE(ads1120_gain_values); i++) { > + if (ads1120_gain_values[i] == gain_val) { > + gain_bits = i << 1; gain_bits = BIT(i); > + goto found; Avoid this goto by the following simplifying code flow. break; > + } > + } if (i == ARRAY_SIZE(ads1120_gain_values) return -EINVAL; config0 = ... > + return -EINVAL; > + > +found: > + config0 = (st->config[0] & ~ADS1120_GAIN_MASK) | gain_bits; > + st->config[0] = config0; FIELD_MODIFY() > + st->gain = gain_val; Similar to below - I'd not store this explicitly. Where it is used is not a fast path so add loop to look it up there. It's much easier to be sure there is no getting out of sync with only one store of information, even if it does bloat the code a it. > + > + return ads1120_write_reg(st, ADS1120_REG_CONFIG0, config0); > +} > + > +static int ads1120_set_datarate(struct ads1120_state *st, int rate) > +{ > + u8 config1; > + int i; > + > + /* Find data rate in supported values */ > + for (i = 0; i < ARRAY_SIZE(ads1120_datarates); i++) { > + if (ads1120_datarates[i].rate == rate) { Flip logic to reduce indent if (ads1120_datarates[i].rate != rate) continue; config1 =... > + config1 = (st->config[1] & ~ADS1120_DR_MASK) | > + ads1120_datarates[i].reg_value; FIELD_MODIFY() once reg_value is the field value not a shifted version of it. And operate directly on st->config[1] > + st->config[1] = config1; > + st->datarate = rate; > + st->conv_time_ms = ads1120_datarates[i].conv_time_ms; > + > + return ads1120_write_reg(st, ADS1120_REG_CONFIG1, > + config1); > + } > + } > + > + return -EINVAL; > +} > + > +static int ads1120_read_raw_adc(struct ads1120_state *st, int *val) > +{ > + u8 rx_buf[4]; > + u8 tx_buf[4] = { ADS1120_CMD_RDATA, 0xff, 0xff, 0xff }; > + int ret; > + struct spi_transfer xfer = { > + .tx_buf = tx_buf, > + .rx_buf = rx_buf, > + .len = 4, > + }; > + > + ret = spi_sync_transfer(st->spi, &xfer, 1); SPI requires DMA safe buffers. 2 ways to make that true here. 1. Put a buffer at the end of iio_priv() structure and mark it __aligned(IIO_DMA_MINALIGN); 2. Allocate on the heap here. (Can't use spi_write_then read() here if those '0xff's are required values). > + if (ret) > + return ret; > + > + /* > + * Data format: 16-bit two's complement MSB first > + * rx_buf[0] is echo of command > + * rx_buf[1] is MSB of data > + * rx_buf[2] is LSB of data > + */ > + *val = (s16)((rx_buf[1] << 8) | rx_buf[2]); *val = sign_extend32(get_unaligned_be16(&rx_buf[1]), 15); or something along those lines. > + > + return 0; > +} > + > +static int ads1120_read_measurement(struct ads1120_state *st, int channel, > + int *val) > +{ > + int ret; > + > + ret = ads1120_set_channel(st, channel); > + if (ret) > + return ret; > + > + /* Start single-shot conversion */ This all seems fairly standard so not sure what your RFC question was looking for feedback on wrt to how you did single conversions? > + ret = ads1120_write_cmd(st, ADS1120_CMD_START); > + if (ret) > + return ret; > + > + /* Wait for conversion to complete */ > + msleep(st->conv_time_ms); > + > + /* Read the result */ > + ret = ads1120_read_raw_adc(st, val); > + if (ret) > + return ret; > + > + st->current_channel = channel; > + > + return 0; > +} > + > +static int ads1120_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int *val, int *val2, long mask) > +{ > + struct ads1120_state *st = iio_priv(indio_dev); > + int ret; > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + mutex_lock(&st->lock); > + ret = ads1120_read_measurement(st, chan->channel, val); guard() as below. > + mutex_unlock(&st->lock); > + if (ret) > + return ret; > + return IIO_VAL_INT; > + > + case IIO_CHAN_INFO_SCALE: > + *val = st->gain; > + return IIO_VAL_INT; > + > + case IIO_CHAN_INFO_SAMP_FREQ: > + *val = st->datarate; > + return IIO_VAL_INT; > + > + default: > + return -EINVAL; > + } > +} > + > +static int ads1120_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int val, int val2, long mask) > +{ > + struct ads1120_state *st = iio_priv(indio_dev); > + int ret; > + > + switch (mask) { > + case IIO_CHAN_INFO_SCALE: > + mutex_lock(&st->lock); include cleanup.h and use guard(mutex)(&st->lock; return ads1120_set_gain(st, val); here. Similar for other cases. > + ret = ads1120_set_gain(st, val); > + mutex_unlock(&st->lock); > + return ret; > + > + case IIO_CHAN_INFO_SAMP_FREQ: > + mutex_lock(&st->lock); > + ret = ads1120_set_datarate(st, val); > + mutex_unlock(&st->lock); > + return ret; > + > + default: > + return -EINVAL; > + } > +} > + > +static int ads1120_read_avail(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + const int **vals, int *type, int *length, > + long mask) > +{ > + static const int datarates[] = { 20, 45, 90, 175, 330, 600, 1000 }; I'd put this up alongside the register defines. Much easier to see it aligns with those that way. > + > + switch (mask) { > + case IIO_CHAN_INFO_SCALE: > + *vals = ads1120_gain_values; > + *type = IIO_VAL_INT; > + *length = ARRAY_SIZE(ads1120_gain_values); > + return IIO_AVAIL_LIST; > + > + case IIO_CHAN_INFO_SAMP_FREQ: > + *vals = datarates; > + *type = IIO_VAL_INT; > + *length = ARRAY_SIZE(datarates); > + return IIO_AVAIL_LIST; > + > + default: > + return -EINVAL; > + } > +} ... > + > +static int ads1120_init(struct ads1120_state *st) > +{ > + int ret; > + > + /* Reset device to default state */ I think that is is obvious enough from the function name that I'd drop this comment. > + ret = ads1120_reset(st); > + if (ret) { > + dev_err(&st->spi->dev, "Failed to reset device\n"); > + return ret; return dev_err_probe() > + } > + > + /* > + * Configure Register 0: > + * - Input MUX: AIN0/AVSS (updated per channel read) > + * - Gain: 1 > + * - PGA bypassed (lower power consumption) > + */ > + st->config[0] = ADS1120_MUX_AIN0_AVSS | ADS1120_GAIN_1 | > + ADS1120_PGA_BYPASS; > + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG0, st->config[0]); > + if (ret) > + return ret; > + > + /* > + * Configure Register 1: > + * - Data rate: 175 SPS > + * - Mode: Normal > + * - Conversion mode: Single-shot > + * - Temperature sensor: Disabled > + * - Burnout current: Disabled If you want to make this more self documenting you can use the definition changes above and st->config[1] = FIELD_PREP(ADS1120_CFG1_DR_MASK, ADS1120_CFG1_DR_175_SPS) | FIELD_PREP(ADS1120_CFG1_MODE_MASK, ADS1120_CFG1_MODE_NORMAL) | FIELD_PREP(ADS1120_CFG1_CONTINOUS, 0) | FIELD_PREP(ADS1120_CFG1_TEMP, 0) | FIELD_PREP(ADS1120_CFG1_BCS, 0); So provide field writes with 0 rather than setting them by their absence. > + */ > + st->config[1] = ADS1120_DR_175SPS | ADS1120_MODE_NORMAL | > + ADS1120_CM_SINGLE; > + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG1, st->config[1]); > + if (ret) > + return ret; > + > + /* > + * Configure Register 2: > + * - Voltage reference: AVDD > + * - 50/60Hz rejection: Off > + * - Power switch: Off > + * - IDAC: Off > + */ > + st->config[2] = ADS1120_VREF_AVDD | ADS1120_REJECT_OFF; Currently config[2] and config[3] are unused outside this function. Might make sense to use local variables for now. > + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG2, st->config[2]); > + if (ret) > + return ret; > + > + /* > + * Configure Register 3: > + * - IDAC1: Disabled > + * - IDAC2: Disabled > + * - DRDY mode: DOUT/DRDY pin behavior > + */ > + st->config[3] = ADS1120_DRDYM_EN; > + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG3, st->config[3]); > + if (ret) > + return ret; > + > + /* Set default operating parameters */ > + st->gain = ADS1120_DEFAULT_GAIN; > + st->datarate = ADS1120_DEFAULT_DATARATE; > + st->conv_time_ms = 7; /* For 175 SPS */ I'd set these alongside where you do the writes. Where possible just retrieve the value from what is cached in the config[] registers rather than having two ways to get to related info. > + st->current_channel = -1; > + > + return 0; > +} > + > +static int ads1120_probe(struct spi_device *spi) > +{ There are enough uses of spi->dev that I'd add a local variable struct device *dev = &spi->dev; > + struct iio_dev *indio_dev; > + struct ads1120_state *st; > + int ret; > + > + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); > + if (!indio_dev) > + return -ENOMEM; > + > + st = iio_priv(indio_dev); > + st->spi = spi; > + > + mutex_init(&st->lock); ret = devm_mutex_init(&spi->dev, st->lock); if (ret) return ret; This helper is so easy to use it makes sense to use it here even though the lock debugging it enables is unlikely to be particularly useful. > + > + indio_dev->name = "ads1120"; > + indio_dev->modes = INDIO_DIRECT_MODE; > + indio_dev->channels = ads1120_channels; > + indio_dev->num_channels = ARRAY_SIZE(ads1120_channels); > + indio_dev->info = &ads1120_info; > + > + ret = ads1120_init(st); > + if (ret) { > + dev_err(&spi->dev, "Failed to initialize device: %d\n", ret); > + return ret; For errors in code only called form probe path use return dev_err_probe(&spi->dev, ret, "Failed to initialize device\n"); Whilst this particular path presumably doesn't defer it is still a useful helper and pretty prints the return value + takes a few lines less than what you currently have. > + } > + > + return devm_iio_device_register(&spi->dev, indio_dev); > +} ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 2/3] iio: adc: Add support for TI ADS1120 2025-10-30 17:54 ` Jonathan Cameron @ 2025-11-07 14:40 ` Ajith Anandhan 2025-11-09 14:02 ` Jonathan Cameron 0 siblings, 1 reply; 24+ messages in thread From: Ajith Anandhan @ 2025-11-07 14:40 UTC (permalink / raw) To: Jonathan Cameron Cc: linux-iio, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On 10/30/25 11:24 PM, Jonathan Cameron wrote: > On Thu, 30 Oct 2025 22:04:10 +0530 > Ajith Anandhan <ajithanandhan0406@gmail.com> wrote: > >> Add driver for the Texas Instruments ADS1120, a precision 16-bit >> analog-to-digital converter with an SPI interface. >> >> The driver provides: >> - 4 single-ended voltage input channels >> - Programmable gain amplifier (1 to 128) >> - Configurable data rates (20 to 1000 SPS) >> - Single-shot conversion mode >> >> Link: https://www.ti.com/lit/gpn/ads1120 > Datasheet: > >> Signed-off-by: Ajith Anandhan <ajithanandhan0406@gmail.com> > Hi Ajith > > Various comments inline. Mostly superficial stuff but the DMA safety > of SPI buffers needs fixing. There is a useful talk from this given > by Wolfram Sang if you want to understand more about this > https://www.youtube.com/watch?v=JDwaMClvV-s > > Thanks, > > Jonathan > > > Thank you for the detailed review and the helpful video reference! >> diff --git a/drivers/iio/adc/ti-ads1120.c b/drivers/iio/adc/ti-ads1120.c >> new file mode 100644 >> index 000000000..07a6fb309 >> --- /dev/null >> +++ b/drivers/iio/adc/ti-ads1120.c >> @@ -0,0 +1,509 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> +/* >> + * TI ADS1120 4-channel, 2kSPS, 16-bit delta-sigma ADC >> + * >> + * Datasheet: https://www.ti.com/lit/gpn/ads1120 >> + * >> + * Copyright (C) 2025 Ajith Anandhan <ajithanandhan0406@gmail.com> >> + */ >> + >> +#include <linux/delay.h> >> +#include <linux/device.h> >> +#include <linux/err.h> >> +#include <linux/kernel.h> > Figure out what you are actually using. There is ongoing effort to not include > kernel.h in drivers because there is usually a small set of more appropriate > fine grained includes. > Sure. I'll replace kernel.h with the specific includes needed. >> +#include <linux/module.h> >> +#include <linux/mod_devicetable.h> >> +#include <linux/mutex.h> >> +#include <linux/spi/spi.h> >> + >> +#include <linux/iio/iio.h> >> + >> +/* Commands */ >> +#define ADS1120_CMD_RESET 0x06 >> +#define ADS1120_CMD_START 0x08 >> +#define ADS1120_CMD_PWRDWN 0x02 >> +#define ADS1120_CMD_RDATA 0x10 >> +#define ADS1120_CMD_RREG 0x20 >> +#define ADS1120_CMD_WREG 0x40 >> + >> +/* Registers */ >> +#define ADS1120_REG_CONFIG0 0x00 >> +#define ADS1120_REG_CONFIG1 0x01 >> +#define ADS1120_REG_CONFIG2 0x02 >> +#define ADS1120_REG_CONFIG3 0x03 >> + >> +/* Config Register 0 bit definitions */ >> +#define ADS1120_MUX_MASK GENMASK(7, 4) >> +#define ADS1120_MUX_AIN0_AVSS 0x80 >> +#define ADS1120_MUX_AIN1_AVSS 0x90 >> +#define ADS1120_MUX_AIN2_AVSS 0xa0 >> +#define ADS1120_MUX_AIN3_AVSS 0xb0 >> + >> +#define ADS1120_GAIN_MASK GENMASK(3, 1) > Better to name these so it's obvious which register they are in. > #define ADS1120_CFG0_GAIN_MSK or similar. > Saves anyone checking every time that it's being written to > the appropriate register. I'll rename all masks to include register names. >> +#define ADS1120_GAIN_1 0x00 >> +#define ADS1120_GAIN_2 0x02 >> +#define ADS1120_GAIN_4 0x04 >> +#define ADS1120_GAIN_8 0x06 >> +#define ADS1120_GAIN_16 0x08 >> +#define ADS1120_GAIN_32 0x0a >> +#define ADS1120_GAIN_64 0x0c >> +#define ADS1120_GAIN_128 0x0e > Same as called out for DR below. (applies in other places > as well). Sure. >> + >> +#define ADS1120_PGA_BYPASS BIT(0) >> + >> +/* Config Register 1 bit definitions */ >> +#define ADS1120_DR_MASK GENMASK(7, 5) >> +#define ADS1120_DR_20SPS 0x00 >> +#define ADS1120_DR_45SPS 0x20 >> +#define ADS1120_DR_90SPS 0x40 >> +#define ADS1120_DR_175SPS 0x60 >> +#define ADS1120_DR_330SPS 0x80 >> +#define ADS1120_DR_600SPS 0xa0 >> +#define ADS1120_DR_1000SPS 0xc0 > Define these as values of the field (So not shifted) > > #define ADS1120_DR_20SPS 0 > #define ADS1120_DR_45SPS 1 > etc. > Then use FIELD_PREP(ADS1120_DR_MASK, ADIS1120_DR_20SPS) > and similar to set them. I'll use the FIELD_PREP and fix the values. > >> + >> +#define ADS1120_MODE_MASK GENMASK(4, 3) >> +#define ADS1120_MODE_NORMAL 0x00 > No other values for a 2 bit field? Define all values even > if you only use one for now. Makes it easier to review the > values I'll define all the available values. >> + >> +#define ADS1120_CM_SINGLE 0x00 >> +#define ADS1120_CM_CONTINUOUS BIT(2) >> + >> +#define ADS1120_TS_EN BIT(1) >> +#define ADS1120_BCS_EN BIT(0) >> + >> +/* Config Register 2 bit definitions */ >> +#define ADS1120_VREF_MASK GENMASK(7, 6) >> +#define ADS1120_VREF_INTERNAL 0x00 >> +#define ADS1120_VREF_EXT_REFP0 0x40 >> +#define ADS1120_VREF_EXT_AIN0 0x80 >> +#define ADS1120_VREF_AVDD 0xc0 >> + >> +#define ADS1120_REJECT_MASK GENMASK(5, 4) >> +#define ADS1120_REJECT_OFF 0x00 >> +#define ADS1120_REJECT_50_60 0x10 >> +#define ADS1120_REJECT_50 0x20 >> +#define ADS1120_REJECT_60 0x30 >> + >> +#define ADS1120_PSW_EN BIT(3) >> + >> +#define ADS1120_IDAC_MASK GENMASK(2, 0) >> + >> +/* Config Register 3 bit definitions */ >> +#define ADS1120_IDAC1_MASK GENMASK(7, 5) >> +#define ADS1120_IDAC2_MASK GENMASK(4, 2) >> +#define ADS1120_DRDYM_EN BIT(1) >> + >> +/* Default configuration values */ >> +#define ADS1120_DEFAULT_GAIN 1 >> +#define ADS1120_DEFAULT_DATARATE 175 > These should be just handled by writing the registers in init. > The defines in of themselves don't help over seeing the default > set in code. Sure.I'll write it at init. > >> + >> +struct ads1120_state { >> + struct spi_device *spi; >> + struct mutex lock; > Locks should always have comments to say what data they protect. I'll add the appropriate comments. > >> + /* >> + * Used to correctly align data. >> + * Ensure natural alignment for potential future timestamp support. > You don't do that except by coincidence of IIO_DMA_MINALIGN being large > enough. So this comment is misleading - Drop it. Sure. > >> + */ >> + u8 data[4] __aligned(IIO_DMA_MINALIGN); > Unless everything after this is used for DMA buffers you have defeated > the point of the __aligned 'trick'. We need to ensure nothing that isn't > used for DMA and can potentially be modified in parallel with this > is not in the cache line. Probably a case of just moving data to the > end of the structure. I'll move the data buffer to the end of the struct. > >> + >> + u8 config[4]; >> + int current_channel; >> + int gain; >> + int datarate; >> + int conv_time_ms; >> +}; >> + >> +struct ads1120_datarate { >> + int rate; >> + int conv_time_ms; >> + u8 reg_value; >> +}; >> + >> +static const struct ads1120_datarate ads1120_datarates[] = { >> + { 20, 51, ADS1120_DR_20SPS }, > As above, use the field values not the shifted ones. Agreed. > >> + { 45, 24, ADS1120_DR_45SPS }, >> + { 90, 13, ADS1120_DR_90SPS }, >> + { 175, 7, ADS1120_DR_175SPS }, >> + { 330, 4, ADS1120_DR_330SPS }, >> + { 600, 3, ADS1120_DR_600SPS }, >> + { 1000, 2, ADS1120_DR_1000SPS }, >> +}; >> +static int ads1120_write_cmd(struct ads1120_state *st, u8 cmd) >> +{ >> + st->data[0] = cmd; >> + return spi_write(st->spi, st->data, 1); >> +} >> + >> +static int ads1120_write_reg(struct ads1120_state *st, u8 reg, u8 value) >> +{ >> + u8 buf[2]; >> + >> + if (reg > ADS1120_REG_CONFIG3) >> + return -EINVAL; >> + >> + buf[0] = ADS1120_CMD_WREG | (reg << 2); >> + buf[1] = value; >> + >> + return spi_write(st->spi, buf, 2); > sizeof(buf); > > However DMA safety thing applies here as well so you can't just use > a buffer in the stack. Can use spi_write_then_read() though as that bounce > buffers. I prefer to use the global buffer for now. > >> +} >> + >> +static int ads1120_set_channel(struct ads1120_state *st, int channel) >> +{ >> + u8 mux_val; >> + u8 config0; >> + >> + if (channel < 0 || channel > 3) >> + return -EINVAL; >> + >> + /* Map channel to AINx/AVSS single-ended input */ >> + mux_val = ADS1120_MUX_AIN0_AVSS + (channel << 4); >> + >> + config0 = (st->config[0] & ~ADS1120_MUX_MASK) | mux_val; >> + st->config[0] = config0; > FIELD_MODIFY() after the defines are field values (not shifted version) > and that << 4 is gone. I'll use the FIELD_MODIFY with proper arguments. > >> + >> + return ads1120_write_reg(st, ADS1120_REG_CONFIG0, config0); >> +} >> + >> +static int ads1120_set_gain(struct ads1120_state *st, int gain_val) >> +{ >> + u8 gain_bits; >> + u8 config0; >> + int i; >> + >> + /* Find gain in supported values */ >> + for (i = 0; i < ARRAY_SIZE(ads1120_gain_values); i++) { >> + if (ads1120_gain_values[i] == gain_val) { >> + gain_bits = i << 1; > gain_bits = BIT(i); > >> + goto found; > Avoid this goto by the following simplifying code flow. I'll refactor it. > > break; >> + } >> + } > if (i == ARRAY_SIZE(ads1120_gain_values) > return -EINVAL; > > config0 = ... > >> + return -EINVAL; >> + >> +found: >> + config0 = (st->config[0] & ~ADS1120_GAIN_MASK) | gain_bits; >> + st->config[0] = config0; > FIELD_MODIFY() Sure. > >> + st->gain = gain_val; > Similar to below - I'd not store this explicitly. Where it is used > is not a fast path so add loop to look it up there. > > It's much easier to be sure there is no getting out of sync with > only one store of information, even if it does bloat the code a it. MSTM. I'll correct it. > >> + >> + return ads1120_write_reg(st, ADS1120_REG_CONFIG0, config0); >> +} >> + >> +static int ads1120_set_datarate(struct ads1120_state *st, int rate) >> +{ >> + u8 config1; >> + int i; >> + >> + /* Find data rate in supported values */ >> + for (i = 0; i < ARRAY_SIZE(ads1120_datarates); i++) { >> + if (ads1120_datarates[i].rate == rate) { > Flip logic to reduce indent > if (ads1120_datarates[i].rate != rate) > continue; > > config1 =... I'll follow the same. >> + config1 = (st->config[1] & ~ADS1120_DR_MASK) | >> + ads1120_datarates[i].reg_value; > FIELD_MODIFY() once reg_value is the field value not a shifted version of it. > And operate directly on st->config[1] Agreed. > >> + st->config[1] = config1; >> + st->datarate = rate; >> + st->conv_time_ms = ads1120_datarates[i].conv_time_ms; >> + >> + return ads1120_write_reg(st, ADS1120_REG_CONFIG1, >> + config1); >> + } >> + } >> + >> + return -EINVAL; >> +} >> + >> +static int ads1120_read_raw_adc(struct ads1120_state *st, int *val) >> +{ >> + u8 rx_buf[4]; >> + u8 tx_buf[4] = { ADS1120_CMD_RDATA, 0xff, 0xff, 0xff }; >> + int ret; >> + struct spi_transfer xfer = { >> + .tx_buf = tx_buf, >> + .rx_buf = rx_buf, >> + .len = 4, >> + }; >> + >> + ret = spi_sync_transfer(st->spi, &xfer, 1); > SPI requires DMA safe buffers. 2 ways to make that true > here. > 1. Put a buffer at the end of iio_priv() structure and mark it > __aligned(IIO_DMA_MINALIGN); > 2. Allocate on the heap here. > (Can't use spi_write_then read() here if those '0xff's are required values). I have chose to move(option 1) the buffer to the end of structure. >> + if (ret) >> + return ret; >> + >> + /* >> + * Data format: 16-bit two's complement MSB first >> + * rx_buf[0] is echo of command >> + * rx_buf[1] is MSB of data >> + * rx_buf[2] is LSB of data >> + */ >> + *val = (s16)((rx_buf[1] << 8) | rx_buf[2]); > *val = sign_extend32(get_unaligned_be16(&rx_buf[1]), 15); > or something along those lines. I'll fix. > >> + >> + return 0; >> +} >> + >> +static int ads1120_read_measurement(struct ads1120_state *st, int channel, >> + int *val) >> +{ >> + int ret; >> + >> + ret = ads1120_set_channel(st, channel); >> + if (ret) >> + return ret; >> + >> + /* Start single-shot conversion */ > This all seems fairly standard so not sure what your RFC question was > looking for feedback on wrt to how you did single conversions? I was indeed concerned about using the polling(adding wait) method to read adc values. That's the reason i have asked it in the cover letter. > >> + ret = ads1120_write_cmd(st, ADS1120_CMD_START); >> + if (ret) >> + return ret; >> + >> + /* Wait for conversion to complete */ >> + msleep(st->conv_time_ms); >> + >> + /* Read the result */ >> + ret = ads1120_read_raw_adc(st, val); >> + if (ret) >> + return ret; >> + >> + st->current_channel = channel; >> + >> + return 0; >> +} >> + >> +static int ads1120_read_raw(struct iio_dev *indio_dev, >> + struct iio_chan_spec const *chan, >> + int *val, int *val2, long mask) >> +{ >> + struct ads1120_state *st = iio_priv(indio_dev); >> + int ret; >> + >> + switch (mask) { >> + case IIO_CHAN_INFO_RAW: >> + mutex_lock(&st->lock); >> + ret = ads1120_read_measurement(st, chan->channel, val); > guard() as below. Sure. > >> + mutex_unlock(&st->lock); >> + if (ret) >> + return ret; >> + return IIO_VAL_INT; >> + >> + case IIO_CHAN_INFO_SCALE: >> + *val = st->gain; >> + return IIO_VAL_INT; >> + >> + case IIO_CHAN_INFO_SAMP_FREQ: >> + *val = st->datarate; >> + return IIO_VAL_INT; >> + >> + default: >> + return -EINVAL; >> + } >> +} >> + >> +static int ads1120_write_raw(struct iio_dev *indio_dev, >> + struct iio_chan_spec const *chan, >> + int val, int val2, long mask) >> +{ >> + struct ads1120_state *st = iio_priv(indio_dev); >> + int ret; >> + >> + switch (mask) { >> + case IIO_CHAN_INFO_SCALE: >> + mutex_lock(&st->lock); > include cleanup.h and use > > guard(mutex)(&st->lock; > return ads1120_set_gain(st, val); > here. Similar for other cases. I'll include cleanup.h and use guard instead of handling mutex directly. > >> + ret = ads1120_set_gain(st, val); >> + mutex_unlock(&st->lock); >> + return ret; >> + >> + case IIO_CHAN_INFO_SAMP_FREQ: >> + mutex_lock(&st->lock); >> + ret = ads1120_set_datarate(st, val); >> + mutex_unlock(&st->lock); >> + return ret; >> + >> + default: >> + return -EINVAL; >> + } >> +} >> + >> +static int ads1120_read_avail(struct iio_dev *indio_dev, >> + struct iio_chan_spec const *chan, >> + const int **vals, int *type, int *length, >> + long mask) >> +{ >> + static const int datarates[] = { 20, 45, 90, 175, 330, 600, 1000 }; > I'd put this up alongside the register defines. Much easier to see it aligns > with those that way. I'll fix. >> + >> + switch (mask) { >> + case IIO_CHAN_INFO_SCALE: >> + *vals = ads1120_gain_values; >> + *type = IIO_VAL_INT; >> + *length = ARRAY_SIZE(ads1120_gain_values); >> + return IIO_AVAIL_LIST; >> + >> + case IIO_CHAN_INFO_SAMP_FREQ: >> + *vals = datarates; >> + *type = IIO_VAL_INT; >> + *length = ARRAY_SIZE(datarates); >> + return IIO_AVAIL_LIST; >> + >> + default: >> + return -EINVAL; >> + } >> +} > ... > >> + >> +static int ads1120_init(struct ads1120_state *st) >> +{ >> + int ret; >> + >> + /* Reset device to default state */ > I think that is is obvious enough from the function name that I'd > drop this comment. I'll remove the comment. > >> + ret = ads1120_reset(st); >> + if (ret) { >> + dev_err(&st->spi->dev, "Failed to reset device\n"); >> + return ret; > return dev_err_probe() Noted. > >> + } >> + >> + /* >> + * Configure Register 0: >> + * - Input MUX: AIN0/AVSS (updated per channel read) >> + * - Gain: 1 >> + * - PGA bypassed (lower power consumption) >> + */ >> + st->config[0] = ADS1120_MUX_AIN0_AVSS | ADS1120_GAIN_1 | >> + ADS1120_PGA_BYPASS; >> + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG0, st->config[0]); >> + if (ret) >> + return ret; >> + >> + /* >> + * Configure Register 1: >> + * - Data rate: 175 SPS >> + * - Mode: Normal >> + * - Conversion mode: Single-shot >> + * - Temperature sensor: Disabled >> + * - Burnout current: Disabled > If you want to make this more self documenting you can use > the definition changes above and > st->config[1] = FIELD_PREP(ADS1120_CFG1_DR_MASK, ADS1120_CFG1_DR_175_SPS) | > FIELD_PREP(ADS1120_CFG1_MODE_MASK, ADS1120_CFG1_MODE_NORMAL) | > FIELD_PREP(ADS1120_CFG1_CONTINOUS, 0) | > FIELD_PREP(ADS1120_CFG1_TEMP, 0) | > FIELD_PREP(ADS1120_CFG1_BCS, 0); > So provide field writes with 0 rather than setting them by their absence. I'll use FIELD_PREP and add the fields with 0 to show their disable status. > > > >> + */ >> + st->config[1] = ADS1120_DR_175SPS | ADS1120_MODE_NORMAL | >> + ADS1120_CM_SINGLE; >> + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG1, st->config[1]); >> + if (ret) >> + return ret; >> + >> + /* >> + * Configure Register 2: >> + * - Voltage reference: AVDD >> + * - 50/60Hz rejection: Off >> + * - Power switch: Off >> + * - IDAC: Off >> + */ >> + st->config[2] = ADS1120_VREF_AVDD | ADS1120_REJECT_OFF; > Currently config[2] and config[3] are unused outside this function. > Might make sense to use local variables for now. I'll use local variables for config[2] and config[3]. > >> + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG2, st->config[2]); >> + if (ret) >> + return ret; >> + >> + /* >> + * Configure Register 3: >> + * - IDAC1: Disabled >> + * - IDAC2: Disabled >> + * - DRDY mode: DOUT/DRDY pin behavior >> + */ >> + st->config[3] = ADS1120_DRDYM_EN; >> + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG3, st->config[3]); >> + if (ret) >> + return ret; >> + >> + /* Set default operating parameters */ >> + st->gain = ADS1120_DEFAULT_GAIN; >> + st->datarate = ADS1120_DEFAULT_DATARATE; >> + st->conv_time_ms = 7; /* For 175 SPS */ > I'd set these alongside where you do the writes. > Where possible just retrieve the value from what is cached in the config[] > registers rather than having two ways to get to related info. Sure, I'll fix. > >> + st->current_channel = -1; >> + >> + return 0; >> +} >> + >> +static int ads1120_probe(struct spi_device *spi) >> +{ > There are enough uses of spi->dev that I'd add a local variable > struct device *dev = &spi->dev; > I'll add local var to handle it. >> + struct iio_dev *indio_dev; >> + struct ads1120_state *st; >> + int ret; >> + >> + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); >> + if (!indio_dev) >> + return -ENOMEM; >> + >> + st = iio_priv(indio_dev); >> + st->spi = spi; >> + >> + mutex_init(&st->lock); > ret = devm_mutex_init(&spi->dev, st->lock); > if (ret) > return ret; > > This helper is so easy to use it makes sense to use it here even though > the lock debugging it enables is unlikely to be particularly useful. Agreed. > >> + >> + indio_dev->name = "ads1120"; >> + indio_dev->modes = INDIO_DIRECT_MODE; >> + indio_dev->channels = ads1120_channels; >> + indio_dev->num_channels = ARRAY_SIZE(ads1120_channels); >> + indio_dev->info = &ads1120_info; >> + >> + ret = ads1120_init(st); >> + if (ret) { >> + dev_err(&spi->dev, "Failed to initialize device: %d\n", ret); >> + return ret; > For errors in code only called form probe path use > return dev_err_probe(&spi->dev, ret, "Failed to initialize device\n"); > > Whilst this particular path presumably doesn't defer it is still a useful > helper and pretty prints the return value + takes a few lines less than what > you currently have. Agreed. I'll follow the same. > >> + } >> + >> + return devm_iio_device_register(&spi->dev, indio_dev); >> +} I’ll post v2 with these fixes shortly. BR, Ajith. ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 2/3] iio: adc: Add support for TI ADS1120 2025-11-07 14:40 ` Ajith Anandhan @ 2025-11-09 14:02 ` Jonathan Cameron 0 siblings, 0 replies; 24+ messages in thread From: Jonathan Cameron @ 2025-11-09 14:02 UTC (permalink / raw) To: Ajith Anandhan Cc: Jonathan Cameron, linux-iio, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On Fri, 7 Nov 2025 20:10:15 +0530 Ajith Anandhan <ajithanandhan0406@gmail.com> wrote: > On 10/30/25 11:24 PM, Jonathan Cameron wrote: > > On Thu, 30 Oct 2025 22:04:10 +0530 > > Ajith Anandhan <ajithanandhan0406@gmail.com> wrote: > > > >> Add driver for the Texas Instruments ADS1120, a precision 16-bit > >> analog-to-digital converter with an SPI interface. > >> > >> The driver provides: > >> - 4 single-ended voltage input channels > >> - Programmable gain amplifier (1 to 128) > >> - Configurable data rates (20 to 1000 SPS) > >> - Single-shot conversion mode > >> > >> Link: https://www.ti.com/lit/gpn/ads1120 > > Datasheet: > > > >> Signed-off-by: Ajith Anandhan <ajithanandhan0406@gmail.com> > > Hi Ajith > > > > Various comments inline. Mostly superficial stuff but the DMA safety > > of SPI buffers needs fixing. There is a useful talk from this given > > by Wolfram Sang if you want to understand more about this > > https://www.youtube.com/watch?v=JDwaMClvV-s > > > > Thanks, > > > > Jonathan Hi Ajith, A small process thing around efficiency. Crop your reply to only include things where you are answering questions or wish the discussion to focus. If you accept changes, just put that stuff in the change log for the next version. Save a lot of scrolling and makes it a lot less likely important stuff will be lost in the noise! > >> +static int ads1120_read_measurement(struct ads1120_state *st, int channel, > >> + int *val) > >> +{ > >> + int ret; > >> + > >> + ret = ads1120_set_channel(st, channel); > >> + if (ret) > >> + return ret; > >> + > >> + /* Start single-shot conversion */ > > This all seems fairly standard so not sure what your RFC question was > > looking for feedback on wrt to how you did single conversions? > > I was indeed concerned about using the polling(adding wait) method to > read adc values. > > That's the reason i have asked it in the cover letter. Ok. A bit more detail next time on what you want feedback on will help focus things. > > > > >> + ret = ads1120_write_cmd(st, ADS1120_CMD_START); > >> + if (ret) Thanks, Jonathan ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 2/3] iio: adc: Add support for TI ADS1120 2025-10-30 16:34 ` [RFC PATCH 2/3] iio: adc: Add support for TI ADS1120 Ajith Anandhan 2025-10-30 17:54 ` Jonathan Cameron @ 2025-10-30 21:13 ` David Lechner 2025-11-07 15:50 ` Ajith Anandhan 1 sibling, 1 reply; 24+ messages in thread From: David Lechner @ 2025-10-30 21:13 UTC (permalink / raw) To: Ajith Anandhan, linux-iio Cc: jic23, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On 10/30/25 11:34 AM, Ajith Anandhan wrote: > Add driver for the Texas Instruments ADS1120, a precision 16-bit > analog-to-digital converter with an SPI interface. > > The driver provides: > - 4 single-ended voltage input channels > - Programmable gain amplifier (1 to 128) > - Configurable data rates (20 to 1000 SPS) I don't think there is much point in having a configureble data rate until we add buffered reads since direct mode is using single-shot conversions. So we can save that for a later patch/series that also implementes buffered reads based off of the DRDY interrupt. Maybe I'm wrong though and being able to specify the conversion time for a single sample is still important. For example, if it has some filtering effect. Otherwise, I would just set it to the "best" value for single-shot mode (if there is one) and always use that one rate. > - Single-shot conversion mode > > Link: https://www.ti.com/lit/gpn/ads1120 > Signed-off-by: Ajith Anandhan <ajithanandhan0406@gmail.com> > --- > drivers/iio/adc/Kconfig | 10 + > drivers/iio/adc/Makefile | 1 + > drivers/iio/adc/ti-ads1120.c | 509 +++++++++++++++++++++++++++++++++++ > 3 files changed, 520 insertions(+) > create mode 100644 drivers/iio/adc/ti-ads1120.c > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 58a14e683..afb7895dd 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -1655,6 +1655,16 @@ config TI_ADS1119 > This driver can also be built as a module. If so, the module will be > called ti-ads1119. > > +config TI_ADS1120 > + tristate "Texas Instruments ADS1120" > + depends on SPI > + help > + Say yes here to build support for Texas Instruments ADS1120 > + 4-channel, 2kSPS, 16-bit delta-sigma ADC. > + > + This driver can also be built as module. If so, the module > + will be called ti-ads1120. > + > config TI_ADS124S08 > tristate "Texas Instruments ADS124S08" > depends on SPI > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index d008f78dc..49c56b459 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -144,6 +144,7 @@ obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o > obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o > obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o > obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o > +obj-$(CONFIG_TI_ADS1120) += ti-ads1120.o > obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o > obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o > obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o > diff --git a/drivers/iio/adc/ti-ads1120.c b/drivers/iio/adc/ti-ads1120.c > new file mode 100644 > index 000000000..07a6fb309 > --- /dev/null > +++ b/drivers/iio/adc/ti-ads1120.c > @@ -0,0 +1,509 @@ > +// SPDX-License-Identifier: GPL-2.0 Prefer more specific GPL-2.0-only or GPL-2.0-or-later (your choice). > +/* > + * TI ADS1120 4-channel, 2kSPS, 16-bit delta-sigma ADC > + * > + * Datasheet: https://www.ti.com/lit/gpn/ads1120 > + * > + * Copyright (C) 2025 Ajith Anandhan <ajithanandhan0406@gmail.com> > + */ > + > +#include <linux/delay.h> > +#include <linux/device.h> > +#include <linux/err.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/mod_devicetable.h> > +#include <linux/mutex.h> > +#include <linux/spi/spi.h> > + > +#include <linux/iio/iio.h> > + > +/* Commands */ > +#define ADS1120_CMD_RESET 0x06 > +#define ADS1120_CMD_START 0x08 > +#define ADS1120_CMD_PWRDWN 0x02 > +#define ADS1120_CMD_RDATA 0x10 > +#define ADS1120_CMD_RREG 0x20 > +#define ADS1120_CMD_WREG 0x40 > + > +/* Registers */ > +#define ADS1120_REG_CONFIG0 0x00 > +#define ADS1120_REG_CONFIG1 0x01 > +#define ADS1120_REG_CONFIG2 0x02 > +#define ADS1120_REG_CONFIG3 0x03 > + > +/* Config Register 0 bit definitions */ > +#define ADS1120_MUX_MASK GENMASK(7, 4) > +#define ADS1120_MUX_AIN0_AVSS 0x80 > +#define ADS1120_MUX_AIN1_AVSS 0x90 > +#define ADS1120_MUX_AIN2_AVSS 0xa0 > +#define ADS1120_MUX_AIN3_AVSS 0xb0 > + > +#define ADS1120_GAIN_MASK GENMASK(3, 1) > +#define ADS1120_GAIN_1 0x00 > +#define ADS1120_GAIN_2 0x02 > +#define ADS1120_GAIN_4 0x04 > +#define ADS1120_GAIN_8 0x06 > +#define ADS1120_GAIN_16 0x08 > +#define ADS1120_GAIN_32 0x0a > +#define ADS1120_GAIN_64 0x0c > +#define ADS1120_GAIN_128 0x0e > + > +#define ADS1120_PGA_BYPASS BIT(0) > + > +/* Config Register 1 bit definitions */ > +#define ADS1120_DR_MASK GENMASK(7, 5) > +#define ADS1120_DR_20SPS 0x00 > +#define ADS1120_DR_45SPS 0x20 > +#define ADS1120_DR_90SPS 0x40 > +#define ADS1120_DR_175SPS 0x60 > +#define ADS1120_DR_330SPS 0x80 > +#define ADS1120_DR_600SPS 0xa0 > +#define ADS1120_DR_1000SPS 0xc0 > + > +#define ADS1120_MODE_MASK GENMASK(4, 3) > +#define ADS1120_MODE_NORMAL 0x00 > + > +#define ADS1120_CM_SINGLE 0x00 > +#define ADS1120_CM_CONTINUOUS BIT(2) > + > +#define ADS1120_TS_EN BIT(1) > +#define ADS1120_BCS_EN BIT(0) > + > +/* Config Register 2 bit definitions */ > +#define ADS1120_VREF_MASK GENMASK(7, 6) > +#define ADS1120_VREF_INTERNAL 0x00 > +#define ADS1120_VREF_EXT_REFP0 0x40 > +#define ADS1120_VREF_EXT_AIN0 0x80 > +#define ADS1120_VREF_AVDD 0xc0 > + > +#define ADS1120_REJECT_MASK GENMASK(5, 4) > +#define ADS1120_REJECT_OFF 0x00 > +#define ADS1120_REJECT_50_60 0x10 > +#define ADS1120_REJECT_50 0x20 > +#define ADS1120_REJECT_60 0x30 > + > +#define ADS1120_PSW_EN BIT(3) > + > +#define ADS1120_IDAC_MASK GENMASK(2, 0) > + > +/* Config Register 3 bit definitions */ > +#define ADS1120_IDAC1_MASK GENMASK(7, 5) > +#define ADS1120_IDAC2_MASK GENMASK(4, 2) > +#define ADS1120_DRDYM_EN BIT(1) > + > +/* Default configuration values */ > +#define ADS1120_DEFAULT_GAIN 1 > +#define ADS1120_DEFAULT_DATARATE 175 > + > +struct ads1120_state { > + struct spi_device *spi; > + struct mutex lock; > + /* > + * Used to correctly align data. > + * Ensure natural alignment for potential future timestamp support. > + */ > + u8 data[4] __aligned(IIO_DMA_MINALIGN); > + > + u8 config[4]; > + int current_channel; > + int gain; Since inputs are multiplexed, we can make this gain a per-channel value, no? It also sounds like that certain mux input have to have the PGA bypassed which means they are limited to only 3 gain values. So these would have a different scale_available attribute. > + int datarate; > + int conv_time_ms; > +}; > + > +struct ads1120_datarate { > + int rate; > + int conv_time_ms; > + u8 reg_value; > +}; > + > +static const struct ads1120_datarate ads1120_datarates[] = { > + { 20, 51, ADS1120_DR_20SPS }, > + { 45, 24, ADS1120_DR_45SPS }, > + { 90, 13, ADS1120_DR_90SPS }, > + { 175, 7, ADS1120_DR_175SPS }, > + { 330, 4, ADS1120_DR_330SPS }, > + { 600, 3, ADS1120_DR_600SPS }, > + { 1000, 2, ADS1120_DR_1000SPS }, > +}; > + > +static const int ads1120_gain_values[] = { 1, 2, 4, 8, 16, 32, 64, 128 }; > + > +#define ADS1120_CHANNEL(index) \ > +{ \ > + .type = IIO_VOLTAGE, \ > + .indexed = 1, \ > + .channel = index, \ > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ > + BIT(IIO_CHAN_INFO_SCALE), \ > + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), is also need to actually make use of the function you implemented. ;-) > +} > + > +static const struct iio_chan_spec ads1120_channels[] = { > + ADS1120_CHANNEL(0), > + ADS1120_CHANNEL(1), > + ADS1120_CHANNEL(2), > + ADS1120_CHANNEL(3), The mux has 15 possible values, so I would expect 15 channels to coorespond to all possible combinations. 8 differnetial channels for the first 8, then these 4 single-ended channels. Then a few more differential channels for the 3 diagnostic inputs. > +}; > + > +static int ads1120_write_cmd(struct ads1120_state *st, u8 cmd) > +{ > + st->data[0] = cmd; > + return spi_write(st->spi, st->data, 1); > +} > + > +static int ads1120_write_reg(struct ads1120_state *st, u8 reg, u8 value) > +{ > + u8 buf[2]; > + > + if (reg > ADS1120_REG_CONFIG3) > + return -EINVAL; > + > + buf[0] = ADS1120_CMD_WREG | (reg << 2); > + buf[1] = value; > + > + return spi_write(st->spi, buf, 2); > +} Can we use the regmap framework instead of writing our own? > + > +static int ads1120_reset(struct ads1120_state *st) > +{ > + int ret; > + > + ret = ads1120_write_cmd(st, ADS1120_CMD_RESET); > + if (ret) > + return ret; > + > + /* > + * Datasheet specifies reset takes 50us + 32 * t(CLK) > + * where t(CLK) = 1/4.096MHz. Use 200us to be safe. > + */ > + usleep_range(200, 300); fsleep(200); > + > + return 0; > +} > + > +static int ads1120_set_channel(struct ads1120_state *st, int channel) > +{ > + u8 mux_val; > + u8 config0; > + > + if (channel < 0 || channel > 3) > + return -EINVAL; > + > + /* Map channel to AINx/AVSS single-ended input */ > + mux_val = ADS1120_MUX_AIN0_AVSS + (channel << 4); > + > + config0 = (st->config[0] & ~ADS1120_MUX_MASK) | mux_val; > + st->config[0] = config0; > + > + return ads1120_write_reg(st, ADS1120_REG_CONFIG0, config0); > +} > + > +static int ads1120_set_gain(struct ads1120_state *st, int gain_val) > +{ > + u8 gain_bits; > + u8 config0; > + int i; > + > + /* Find gain in supported values */ > + for (i = 0; i < ARRAY_SIZE(ads1120_gain_values); i++) { > + if (ads1120_gain_values[i] == gain_val) { > + gain_bits = i << 1; > + goto found; > + } > + } > + return -EINVAL; > + > +found: > + config0 = (st->config[0] & ~ADS1120_GAIN_MASK) | gain_bits; > + st->config[0] = config0; > + st->gain = gain_val; > + > + return ads1120_write_reg(st, ADS1120_REG_CONFIG0, config0); > +} > + > +static int ads1120_set_datarate(struct ads1120_state *st, int rate) > +{ > + u8 config1; > + int i; > + > + /* Find data rate in supported values */ > + for (i = 0; i < ARRAY_SIZE(ads1120_datarates); i++) { > + if (ads1120_datarates[i].rate == rate) { > + config1 = (st->config[1] & ~ADS1120_DR_MASK) | > + ads1120_datarates[i].reg_value; > + st->config[1] = config1; > + st->datarate = rate; > + st->conv_time_ms = ads1120_datarates[i].conv_time_ms; > + > + return ads1120_write_reg(st, ADS1120_REG_CONFIG1, > + config1); > + } > + } > + > + return -EINVAL; > +} > + > +static int ads1120_read_raw_adc(struct ads1120_state *st, int *val) > +{ > + u8 rx_buf[4]; > + u8 tx_buf[4] = { ADS1120_CMD_RDATA, 0xff, 0xff, 0xff }; > + int ret; > + struct spi_transfer xfer = { > + .tx_buf = tx_buf, > + .rx_buf = rx_buf, > + .len = 4, > + }; This should be split into two transfers (still only one SPI message). Then we don't need to pad the buffers. Also, it seems to have one more byte than needed. Only to to tx one byte then rx two bytes. > + > + ret = spi_sync_transfer(st->spi, &xfer, 1); > + if (ret) > + return ret; > + > + /* > + * Data format: 16-bit two's complement MSB first > + * rx_buf[0] is echo of command > + * rx_buf[1] is MSB of data > + * rx_buf[2] is LSB of data > + */ > + *val = (s16)((rx_buf[1] << 8) | rx_buf[2]); > + > + return 0; > +} > + > +static int ads1120_read_measurement(struct ads1120_state *st, int channel, > + int *val) > +{ > + int ret; > + > + ret = ads1120_set_channel(st, channel); > + if (ret) > + return ret; > + > + /* Start single-shot conversion */ > + ret = ads1120_write_cmd(st, ADS1120_CMD_START); > + if (ret) > + return ret; > + > + /* Wait for conversion to complete */ > + msleep(st->conv_time_ms); > + > + /* Read the result */ > + ret = ads1120_read_raw_adc(st, val); > + if (ret) > + return ret; > + This could actually all be done in a single SPI message with 3 transers. The spi_transfer struct has delay fields that can provide the delay instead of msleep(). > + st->current_channel = channel; > + > + return 0; > +} > + > +static int ads1120_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int *val, int *val2, long mask) > +{ > + struct ads1120_state *st = iio_priv(indio_dev); > + int ret; > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + mutex_lock(&st->lock); > + ret = ads1120_read_measurement(st, chan->channel, val); > + mutex_unlock(&st->lock); > + if (ret) > + return ret; > + return IIO_VAL_INT; > + > + case IIO_CHAN_INFO_SCALE: > + *val = st->gain; > + return IIO_VAL_INT; > + > + case IIO_CHAN_INFO_SAMP_FREQ: > + *val = st->datarate; > + return IIO_VAL_INT; > + > + default: > + return -EINVAL; > + } > +} > + > +static int ads1120_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int val, int val2, long mask) > +{ > + struct ads1120_state *st = iio_priv(indio_dev); > + int ret; > + > + switch (mask) { > + case IIO_CHAN_INFO_SCALE: > + mutex_lock(&st->lock); > + ret = ads1120_set_gain(st, val); > + mutex_unlock(&st->lock); > + return ret; > + > + case IIO_CHAN_INFO_SAMP_FREQ: > + mutex_lock(&st->lock); > + ret = ads1120_set_datarate(st, val); > + mutex_unlock(&st->lock); > + return ret; > + > + default: > + return -EINVAL; > + } > +} > + > +static int ads1120_read_avail(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + const int **vals, int *type, int *length, > + long mask) > +{ > + static const int datarates[] = { 20, 45, 90, 175, 330, 600, 1000 }; > + > + switch (mask) { > + case IIO_CHAN_INFO_SCALE: > + *vals = ads1120_gain_values; > + *type = IIO_VAL_INT; > + *length = ARRAY_SIZE(ads1120_gain_values); Scale also depends on the reference voltage, so it isn't quite so simple. > + return IIO_AVAIL_LIST; > + > + case IIO_CHAN_INFO_SAMP_FREQ: > + *vals = datarates; > + *type = IIO_VAL_INT; > + *length = ARRAY_SIZE(datarates); > + return IIO_AVAIL_LIST; > + > + default: > + return -EINVAL; > + } > +} > + > +static const struct iio_info ads1120_info = { > + .read_raw = ads1120_read_raw, > + .write_raw = ads1120_write_raw, > + .read_avail = ads1120_read_avail, > +}; > + > +static int ads1120_init(struct ads1120_state *st) > +{ > + int ret; > + > + /* Reset device to default state */ > + ret = ads1120_reset(st); > + if (ret) { > + dev_err(&st->spi->dev, "Failed to reset device\n"); > + return ret; > + } > + > + /* > + * Configure Register 0: > + * - Input MUX: AIN0/AVSS (updated per channel read) > + * - Gain: 1 > + * - PGA bypassed (lower power consumption) Should extend the comment to say that if gain is set higher than 4, this value is ignored, so it is safe to leave it set all the time. > + */ > + st->config[0] = ADS1120_MUX_AIN0_AVSS | ADS1120_GAIN_1 | > + ADS1120_PGA_BYPASS; > + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG0, st->config[0]); > + if (ret) > + return ret; > + > + /* > + * Configure Register 1: > + * - Data rate: 175 SPS > + * - Mode: Normal > + * - Conversion mode: Single-shot > + * - Temperature sensor: Disabled > + * - Burnout current: Disabled > + */ > + st->config[1] = ADS1120_DR_175SPS | ADS1120_MODE_NORMAL | > + ADS1120_CM_SINGLE; > + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG1, st->config[1]); > + if (ret) > + return ret; > + > + /* > + * Configure Register 2: > + * - Voltage reference: AVDD > + * - 50/60Hz rejection: Off > + * - Power switch: Off > + * - IDAC: Off > + */ > + st->config[2] = ADS1120_VREF_AVDD | ADS1120_REJECT_OFF; > + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG2, st->config[2]); > + if (ret) > + return ret; > + > + /* > + * Configure Register 3: * - Internal voltage reference * - No FIR filter * - Power switch always open > + * - IDAC1: Disabled > + * - IDAC2: Disabled > + * - DRDY mode: DOUT/DRDY pin behavior > + */ > + st->config[3] = ADS1120_DRDYM_EN; It doesn't make sense to enable the DRDY pin on the DOUT line unless we know that it is wired up to an interrupt. > + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG3, st->config[3]); > + if (ret) > + return ret; > + > + /* Set default operating parameters */ > + st->gain = ADS1120_DEFAULT_GAIN; > + st->datarate = ADS1120_DEFAULT_DATARATE; > + st->conv_time_ms = 7; /* For 175 SPS */ > + st->current_channel = -1; > + > + return 0; > +} > + > +static int ads1120_probe(struct spi_device *spi) > +{ > + struct iio_dev *indio_dev; > + struct ads1120_state *st; > + int ret; > + > + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); > + if (!indio_dev) > + return -ENOMEM; > + > + st = iio_priv(indio_dev); > + st->spi = spi; > + > + mutex_init(&st->lock); > + > + indio_dev->name = "ads1120"; > + indio_dev->modes = INDIO_DIRECT_MODE; > + indio_dev->channels = ads1120_channels; > + indio_dev->num_channels = ARRAY_SIZE(ads1120_channels); > + indio_dev->info = &ads1120_info; > + > + ret = ads1120_init(st); > + if (ret) { > + dev_err(&spi->dev, "Failed to initialize device: %d\n", ret); > + return ret; > + } > + > + return devm_iio_device_register(&spi->dev, indio_dev); > +} > + > +static const struct of_device_id ads1120_of_match[] = { > + { .compatible = "ti,ads1120" }, > + { } > +}; > +MODULE_DEVICE_TABLE(of, ads1120_of_match); > + > +static const struct spi_device_id ads1120_id[] = { > + { "ads1120" }, > + { } > +}; > +MODULE_DEVICE_TABLE(spi, ads1120_id); > + > +static struct spi_driver ads1120_driver = { > + .driver = { > + .name = "ads1120", > + .of_match_table = ads1120_of_match, > + }, > + .probe = ads1120_probe, > + .id_table = ads1120_id, > +}; > +module_spi_driver(ads1120_driver); > + > +MODULE_AUTHOR("Ajith Anandhan <ajithanandhan0406@gmail.com>"); > +MODULE_DESCRIPTION("Texas Instruments ADS1120 ADC driver"); > +MODULE_LICENSE("GPL"); ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 2/3] iio: adc: Add support for TI ADS1120 2025-10-30 21:13 ` David Lechner @ 2025-11-07 15:50 ` Ajith Anandhan 2025-11-07 16:18 ` David Lechner 0 siblings, 1 reply; 24+ messages in thread From: Ajith Anandhan @ 2025-11-07 15:50 UTC (permalink / raw) To: David Lechner, linux-iio Cc: jic23, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On 10/31/25 2:43 AM, David Lechner wrote: > On 10/30/25 11:34 AM, Ajith Anandhan wrote: >> Add driver for the Texas Instruments ADS1120, a precision 16-bit >> analog-to-digital converter with an SPI interface. >> >> The driver provides: >> - 4 single-ended voltage input channels >> - Programmable gain amplifier (1 to 128) >> - Configurable data rates (20 to 1000 SPS) > I don't think there is much point in having a configureble data rate > until we add buffered reads since direct mode is using single-shot > conversions. So we can save that for a later patch/series that also > implementes buffered reads based off of the DRDY interrupt. > > Maybe I'm wrong though and being able to specify the conversion > time for a single sample is still important. For example, if it > has some filtering effect. > > Otherwise, I would just set it to the "best" value for single-shot > mode (if there is one) and always use that one rate. You're right. I'll remove the configurable data rate for now and just use a fixed optimal rate (20 SPS as default since its highest accuracy). We'll add this back when implementing buffered reads with DRDY interrupt support in a future patch series. > >> - Single-shot conversion mode >> >> Link: https://www.ti.com/lit/gpn/ads1120 >> Signed-off-by: Ajith Anandhan <ajithanandhan0406@gmail.com> >> --- >> drivers/iio/adc/Kconfig | 10 + >> drivers/iio/adc/Makefile | 1 + >> drivers/iio/adc/ti-ads1120.c | 509 +++++++++++++++++++++++++++++++++++ >> 3 files changed, 520 insertions(+) >> create mode 100644 drivers/iio/adc/ti-ads1120.c >> >> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig >> index 58a14e683..afb7895dd 100644 >> --- a/drivers/iio/adc/Kconfig >> +++ b/drivers/iio/adc/Kconfig >> @@ -1655,6 +1655,16 @@ config TI_ADS1119 >> This driver can also be built as a module. If so, the module will be >> called ti-ads1119. >> >> +config TI_ADS1120 >> + tristate "Texas Instruments ADS1120" >> + depends on SPI >> + help >> + Say yes here to build support for Texas Instruments ADS1120 >> + 4-channel, 2kSPS, 16-bit delta-sigma ADC. >> + >> + This driver can also be built as module. If so, the module >> + will be called ti-ads1120. >> + >> config TI_ADS124S08 >> tristate "Texas Instruments ADS124S08" >> depends on SPI >> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile >> index d008f78dc..49c56b459 100644 >> --- a/drivers/iio/adc/Makefile >> +++ b/drivers/iio/adc/Makefile >> @@ -144,6 +144,7 @@ obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o >> obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o >> obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o >> obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o >> +obj-$(CONFIG_TI_ADS1120) += ti-ads1120.o >> obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o >> obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o >> obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o >> diff --git a/drivers/iio/adc/ti-ads1120.c b/drivers/iio/adc/ti-ads1120.c >> new file mode 100644 >> index 000000000..07a6fb309 >> --- /dev/null >> +++ b/drivers/iio/adc/ti-ads1120.c >> @@ -0,0 +1,509 @@ >> +// SPDX-License-Identifier: GPL-2.0 > Prefer more specific GPL-2.0-only or GPL-2.0-or-later (your choice). Noted. > >> +/* >> + * TI ADS1120 4-channel, 2kSPS, 16-bit delta-sigma ADC >> + * >> + * Datasheet: https://www.ti.com/lit/gpn/ads1120 >> + * >> + * Copyright (C) 2025 Ajith Anandhan <ajithanandhan0406@gmail.com> >> + */ >> + >> +#include <linux/delay.h> >> +#include <linux/device.h> >> +#include <linux/err.h> >> +#include <linux/kernel.h> >> +#include <linux/module.h> >> +#include <linux/mod_devicetable.h> >> +#include <linux/mutex.h> >> +#include <linux/spi/spi.h> >> + >> +#include <linux/iio/iio.h> >> + >> +/* Commands */ >> +#define ADS1120_CMD_RESET 0x06 >> +#define ADS1120_CMD_START 0x08 >> +#define ADS1120_CMD_PWRDWN 0x02 >> +#define ADS1120_CMD_RDATA 0x10 >> +#define ADS1120_CMD_RREG 0x20 >> +#define ADS1120_CMD_WREG 0x40 >> + >> +/* Registers */ >> +#define ADS1120_REG_CONFIG0 0x00 >> +#define ADS1120_REG_CONFIG1 0x01 >> +#define ADS1120_REG_CONFIG2 0x02 >> +#define ADS1120_REG_CONFIG3 0x03 >> + >> +/* Config Register 0 bit definitions */ >> +#define ADS1120_MUX_MASK GENMASK(7, 4) >> +#define ADS1120_MUX_AIN0_AVSS 0x80 >> +#define ADS1120_MUX_AIN1_AVSS 0x90 >> +#define ADS1120_MUX_AIN2_AVSS 0xa0 >> +#define ADS1120_MUX_AIN3_AVSS 0xb0 >> + >> +#define ADS1120_GAIN_MASK GENMASK(3, 1) >> +#define ADS1120_GAIN_1 0x00 >> +#define ADS1120_GAIN_2 0x02 >> +#define ADS1120_GAIN_4 0x04 >> +#define ADS1120_GAIN_8 0x06 >> +#define ADS1120_GAIN_16 0x08 >> +#define ADS1120_GAIN_32 0x0a >> +#define ADS1120_GAIN_64 0x0c >> +#define ADS1120_GAIN_128 0x0e >> + >> +#define ADS1120_PGA_BYPASS BIT(0) >> + >> +/* Config Register 1 bit definitions */ >> +#define ADS1120_DR_MASK GENMASK(7, 5) >> +#define ADS1120_DR_20SPS 0x00 >> +#define ADS1120_DR_45SPS 0x20 >> +#define ADS1120_DR_90SPS 0x40 >> +#define ADS1120_DR_175SPS 0x60 >> +#define ADS1120_DR_330SPS 0x80 >> +#define ADS1120_DR_600SPS 0xa0 >> +#define ADS1120_DR_1000SPS 0xc0 >> + >> +#define ADS1120_MODE_MASK GENMASK(4, 3) >> +#define ADS1120_MODE_NORMAL 0x00 >> + >> +#define ADS1120_CM_SINGLE 0x00 >> +#define ADS1120_CM_CONTINUOUS BIT(2) >> + >> +#define ADS1120_TS_EN BIT(1) >> +#define ADS1120_BCS_EN BIT(0) >> + >> +/* Config Register 2 bit definitions */ >> +#define ADS1120_VREF_MASK GENMASK(7, 6) >> +#define ADS1120_VREF_INTERNAL 0x00 >> +#define ADS1120_VREF_EXT_REFP0 0x40 >> +#define ADS1120_VREF_EXT_AIN0 0x80 >> +#define ADS1120_VREF_AVDD 0xc0 >> + >> +#define ADS1120_REJECT_MASK GENMASK(5, 4) >> +#define ADS1120_REJECT_OFF 0x00 >> +#define ADS1120_REJECT_50_60 0x10 >> +#define ADS1120_REJECT_50 0x20 >> +#define ADS1120_REJECT_60 0x30 >> + >> +#define ADS1120_PSW_EN BIT(3) >> + >> +#define ADS1120_IDAC_MASK GENMASK(2, 0) >> + >> +/* Config Register 3 bit definitions */ >> +#define ADS1120_IDAC1_MASK GENMASK(7, 5) >> +#define ADS1120_IDAC2_MASK GENMASK(4, 2) >> +#define ADS1120_DRDYM_EN BIT(1) >> + >> +/* Default configuration values */ >> +#define ADS1120_DEFAULT_GAIN 1 >> +#define ADS1120_DEFAULT_DATARATE 175 >> + >> +struct ads1120_state { >> + struct spi_device *spi; >> + struct mutex lock; >> + /* >> + * Used to correctly align data. >> + * Ensure natural alignment for potential future timestamp support. >> + */ >> + u8 data[4] __aligned(IIO_DMA_MINALIGN); >> + >> + u8 config[4]; >> + int current_channel; >> + int gain; > Since inputs are multiplexed, we can make this gain a per-channel value, no? Yes we can, However i want to keep the initial version simple so would it be fine to support per-channel gain configurationin upcoming patches? > > It also sounds like that certain mux input have to have the PGA bypassed > which means they are limited to only 3 gain values. So these would have > a different scale_available attribute. Since, I'm gonna enable all the 15 channels. I believe we have to have all gains(for differential channels). Correct me if i'm wrong. > >> + int datarate; >> + int conv_time_ms; >> +}; >> + >> +struct ads1120_datarate { >> + int rate; >> + int conv_time_ms; >> + u8 reg_value; >> +}; >> + >> +static const struct ads1120_datarate ads1120_datarates[] = { >> + { 20, 51, ADS1120_DR_20SPS }, >> + { 45, 24, ADS1120_DR_45SPS }, >> + { 90, 13, ADS1120_DR_90SPS }, >> + { 175, 7, ADS1120_DR_175SPS }, >> + { 330, 4, ADS1120_DR_330SPS }, >> + { 600, 3, ADS1120_DR_600SPS }, >> + { 1000, 2, ADS1120_DR_1000SPS }, >> +}; >> + >> +static const int ads1120_gain_values[] = { 1, 2, 4, 8, 16, 32, 64, 128 }; >> + >> +#define ADS1120_CHANNEL(index) \ >> +{ \ >> + .type = IIO_VOLTAGE, \ >> + .indexed = 1, \ >> + .channel = index, \ >> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ >> + BIT(IIO_CHAN_INFO_SCALE), \ >> + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ > .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), > > is also need to actually make use of the function you implemented. ;-) Sure. I'll fix. > >> +} >> + >> +static const struct iio_chan_spec ads1120_channels[] = { >> + ADS1120_CHANNEL(0), >> + ADS1120_CHANNEL(1), >> + ADS1120_CHANNEL(2), >> + ADS1120_CHANNEL(3), > The mux has 15 possible values, so I would expect 15 channels > to coorespond to all possible combinations. 8 differnetial > channels for the first 8, then these 4 single-ended channels. > Then a few more differential channels for the 3 diagnostic > inputs. Sure, I'll add all the 15 channels. > >> +}; >> + >> +static int ads1120_write_cmd(struct ads1120_state *st, u8 cmd) >> +{ >> + st->data[0] = cmd; >> + return spi_write(st->spi, st->data, 1); >> +} >> + >> +static int ads1120_write_reg(struct ads1120_state *st, u8 reg, u8 value) >> +{ >> + u8 buf[2]; >> + >> + if (reg > ADS1120_REG_CONFIG3) >> + return -EINVAL; >> + >> + buf[0] = ADS1120_CMD_WREG | (reg << 2); >> + buf[1] = value; >> + >> + return spi_write(st->spi, buf, 2); >> +} > Can we use the regmap framework instead of writing our own? I’d like to keep the first version simple so i will add the regmap support in the later patch since the single ended has less spi transaction to handle. > >> + >> +static int ads1120_reset(struct ads1120_state *st) >> +{ >> + int ret; >> + >> + ret = ads1120_write_cmd(st, ADS1120_CMD_RESET); >> + if (ret) >> + return ret; >> + >> + /* >> + * Datasheet specifies reset takes 50us + 32 * t(CLK) >> + * where t(CLK) = 1/4.096MHz. Use 200us to be safe. >> + */ >> + usleep_range(200, 300); > fsleep(200); I'll use fsleep instead of usleep_range. > >> + >> + return 0; >> +} >> + >> +static int ads1120_set_channel(struct ads1120_state *st, int channel) >> +{ >> + u8 mux_val; >> + u8 config0; >> + >> + if (channel < 0 || channel > 3) >> + return -EINVAL; >> + >> + /* Map channel to AINx/AVSS single-ended input */ >> + mux_val = ADS1120_MUX_AIN0_AVSS + (channel << 4); >> + >> + config0 = (st->config[0] & ~ADS1120_MUX_MASK) | mux_val; >> + st->config[0] = config0; >> + >> + return ads1120_write_reg(st, ADS1120_REG_CONFIG0, config0); >> +} >> + >> +static int ads1120_set_gain(struct ads1120_state *st, int gain_val) >> +{ >> + u8 gain_bits; >> + u8 config0; >> + int i; >> + >> + /* Find gain in supported values */ >> + for (i = 0; i < ARRAY_SIZE(ads1120_gain_values); i++) { >> + if (ads1120_gain_values[i] == gain_val) { >> + gain_bits = i << 1; >> + goto found; >> + } >> + } >> + return -EINVAL; >> + >> +found: >> + config0 = (st->config[0] & ~ADS1120_GAIN_MASK) | gain_bits; >> + st->config[0] = config0; >> + st->gain = gain_val; >> + >> + return ads1120_write_reg(st, ADS1120_REG_CONFIG0, config0); >> +} >> + >> +static int ads1120_set_datarate(struct ads1120_state *st, int rate) >> +{ >> + u8 config1; >> + int i; >> + >> + /* Find data rate in supported values */ >> + for (i = 0; i < ARRAY_SIZE(ads1120_datarates); i++) { >> + if (ads1120_datarates[i].rate == rate) { >> + config1 = (st->config[1] & ~ADS1120_DR_MASK) | >> + ads1120_datarates[i].reg_value; >> + st->config[1] = config1; >> + st->datarate = rate; >> + st->conv_time_ms = ads1120_datarates[i].conv_time_ms; >> + >> + return ads1120_write_reg(st, ADS1120_REG_CONFIG1, >> + config1); >> + } >> + } >> + >> + return -EINVAL; >> +} >> + >> +static int ads1120_read_raw_adc(struct ads1120_state *st, int *val) >> +{ >> + u8 rx_buf[4]; >> + u8 tx_buf[4] = { ADS1120_CMD_RDATA, 0xff, 0xff, 0xff }; >> + int ret; >> + struct spi_transfer xfer = { >> + .tx_buf = tx_buf, >> + .rx_buf = rx_buf, >> + .len = 4, >> + }; > This should be split into two transfers (still only one SPI message). > Then we don't need to pad the buffers. Also, it seems to have one > more byte than needed. Only to to tx one byte then rx two bytes. Sure, I'll fix. >> + >> + ret = spi_sync_transfer(st->spi, &xfer, 1); >> + if (ret) >> + return ret; >> + >> + /* >> + * Data format: 16-bit two's complement MSB first >> + * rx_buf[0] is echo of command >> + * rx_buf[1] is MSB of data >> + * rx_buf[2] is LSB of data >> + */ >> + *val = (s16)((rx_buf[1] << 8) | rx_buf[2]); >> + >> + return 0; >> +} >> + >> +static int ads1120_read_measurement(struct ads1120_state *st, int channel, >> + int *val) >> +{ >> + int ret; >> + >> + ret = ads1120_set_channel(st, channel); >> + if (ret) >> + return ret; >> + > > >> + /* Start single-shot conversion */ >> + ret = ads1120_write_cmd(st, ADS1120_CMD_START); >> + if (ret) >> + return ret; >> + >> + /* Wait for conversion to complete */ >> + msleep(st->conv_time_ms); >> + >> + /* Read the result */ >> + ret = ads1120_read_raw_adc(st, val); >> + if (ret) >> + return ret; >> + > This could actually all be done in a single SPI message with 3 > transers. The spi_transfer struct has delay fields that can > provide the delay instead of msleep(). Good point. I'll defer this improvement to a subsequent patch. > >> + st->current_channel = channel; >> + >> + return 0; >> +} >> + >> +static int ads1120_read_raw(struct iio_dev *indio_dev, >> + struct iio_chan_spec const *chan, >> + int *val, int *val2, long mask) >> +{ >> + struct ads1120_state *st = iio_priv(indio_dev); >> + int ret; >> + >> + switch (mask) { >> + case IIO_CHAN_INFO_RAW: >> + mutex_lock(&st->lock); >> + ret = ads1120_read_measurement(st, chan->channel, val); >> + mutex_unlock(&st->lock); >> + if (ret) >> + return ret; >> + return IIO_VAL_INT; >> + >> + case IIO_CHAN_INFO_SCALE: >> + *val = st->gain; >> + return IIO_VAL_INT; >> + >> + case IIO_CHAN_INFO_SAMP_FREQ: >> + *val = st->datarate; >> + return IIO_VAL_INT; >> + >> + default: >> + return -EINVAL; >> + } >> +} >> + >> +static int ads1120_write_raw(struct iio_dev *indio_dev, >> + struct iio_chan_spec const *chan, >> + int val, int val2, long mask) >> +{ >> + struct ads1120_state *st = iio_priv(indio_dev); >> + int ret; >> + >> + switch (mask) { >> + case IIO_CHAN_INFO_SCALE: >> + mutex_lock(&st->lock); >> + ret = ads1120_set_gain(st, val); >> + mutex_unlock(&st->lock); >> + return ret; >> + >> + case IIO_CHAN_INFO_SAMP_FREQ: >> + mutex_lock(&st->lock); >> + ret = ads1120_set_datarate(st, val); >> + mutex_unlock(&st->lock); >> + return ret; >> + >> + default: >> + return -EINVAL; >> + } >> +} >> + >> +static int ads1120_read_avail(struct iio_dev *indio_dev, >> + struct iio_chan_spec const *chan, >> + const int **vals, int *type, int *length, >> + long mask) >> +{ >> + static const int datarates[] = { 20, 45, 90, 175, 330, 600, 1000 }; >> + >> + switch (mask) { >> + case IIO_CHAN_INFO_SCALE: >> + *vals = ads1120_gain_values; >> + *type = IIO_VAL_INT; >> + *length = ARRAY_SIZE(ads1120_gain_values); > Scale also depends on the reference voltage, so it isn't quite so simple. Yeah sure. I'll fix. > >> + return IIO_AVAIL_LIST; >> + >> + case IIO_CHAN_INFO_SAMP_FREQ: >> + *vals = datarates; >> + *type = IIO_VAL_INT; >> + *length = ARRAY_SIZE(datarates); >> + return IIO_AVAIL_LIST; >> + >> + default: >> + return -EINVAL; >> + } >> +} >> + >> +static const struct iio_info ads1120_info = { >> + .read_raw = ads1120_read_raw, >> + .write_raw = ads1120_write_raw, >> + .read_avail = ads1120_read_avail, >> +}; >> + >> +static int ads1120_init(struct ads1120_state *st) >> +{ >> + int ret; >> + >> + /* Reset device to default state */ >> + ret = ads1120_reset(st); >> + if (ret) { >> + dev_err(&st->spi->dev, "Failed to reset device\n"); >> + return ret; >> + } >> + >> + /* >> + * Configure Register 0: >> + * - Input MUX: AIN0/AVSS (updated per channel read) >> + * - Gain: 1 >> + * - PGA bypassed (lower power consumption) > Should extend the comment to say that if gain is set higher than 4, > this value is ignored, so it is safe to leave it set all the time. Noted. > >> + */ >> + st->config[0] = ADS1120_MUX_AIN0_AVSS | ADS1120_GAIN_1 | >> + ADS1120_PGA_BYPASS; >> + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG0, st->config[0]); >> + if (ret) >> + return ret; >> + >> + /* >> + * Configure Register 1: >> + * - Data rate: 175 SPS >> + * - Mode: Normal >> + * - Conversion mode: Single-shot >> + * - Temperature sensor: Disabled >> + * - Burnout current: Disabled >> + */ >> + st->config[1] = ADS1120_DR_175SPS | ADS1120_MODE_NORMAL | >> + ADS1120_CM_SINGLE; >> + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG1, st->config[1]); >> + if (ret) >> + return ret; >> + >> + /* >> + * Configure Register 2: >> + * - Voltage reference: AVDD >> + * - 50/60Hz rejection: Off >> + * - Power switch: Off >> + * - IDAC: Off >> + */ >> + st->config[2] = ADS1120_VREF_AVDD | ADS1120_REJECT_OFF; >> + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG2, st->config[2]); >> + if (ret) >> + return ret; >> + >> + /* >> + * Configure Register 3: > * - Internal voltage reference > * - No FIR filter > * - Power switch always open > >> + * - IDAC1: Disabled >> + * - IDAC2: Disabled >> + * - DRDY mode: DOUT/DRDY pin behavior >> + */ >> + st->config[3] = ADS1120_DRDYM_EN; > It doesn't make sense to enable the DRDY pin on the DOUT line unless > we know that it is wired up to an interrupt. Thanks for pointing it out. I'll address the this in V2 patch. >> + ret = ads1120_write_reg(st, ADS1120_REG_CONFIG3, st->config[3]); >> + if (ret) >> + return ret; >> + >> + /* Set default operating parameters */ >> + st->gain = ADS1120_DEFAULT_GAIN; >> + st->datarate = ADS1120_DEFAULT_DATARATE; >> + st->conv_time_ms = 7; /* For 175 SPS */ >> + st->current_channel = -1; >> + >> + return 0; >> +} >> + >> +static int ads1120_probe(struct spi_device *spi) >> +{ >> + struct iio_dev *indio_dev; >> + struct ads1120_state *st; >> + int ret; >> + >> + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); >> + if (!indio_dev) >> + return -ENOMEM; >> + >> + st = iio_priv(indio_dev); >> + st->spi = spi; >> + >> + mutex_init(&st->lock); >> + >> + indio_dev->name = "ads1120"; >> + indio_dev->modes = INDIO_DIRECT_MODE; >> + indio_dev->channels = ads1120_channels; >> + indio_dev->num_channels = ARRAY_SIZE(ads1120_channels); >> + indio_dev->info = &ads1120_info; >> + >> + ret = ads1120_init(st); >> + if (ret) { >> + dev_err(&spi->dev, "Failed to initialize device: %d\n", ret); >> + return ret; >> + } >> + >> + return devm_iio_device_register(&spi->dev, indio_dev); >> +} >> + >> +static const struct of_device_id ads1120_of_match[] = { >> + { .compatible = "ti,ads1120" }, >> + { } >> +}; >> +MODULE_DEVICE_TABLE(of, ads1120_of_match); >> + >> +static const struct spi_device_id ads1120_id[] = { >> + { "ads1120" }, >> + { } >> +}; >> +MODULE_DEVICE_TABLE(spi, ads1120_id); >> + >> +static struct spi_driver ads1120_driver = { >> + .driver = { >> + .name = "ads1120", >> + .of_match_table = ads1120_of_match, >> + }, >> + .probe = ads1120_probe, >> + .id_table = ads1120_id, >> +}; >> +module_spi_driver(ads1120_driver); >> + >> +MODULE_AUTHOR("Ajith Anandhan <ajithanandhan0406@gmail.com>"); >> +MODULE_DESCRIPTION("Texas Instruments ADS1120 ADC driver"); >> +MODULE_LICENSE("GPL"); ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 2/3] iio: adc: Add support for TI ADS1120 2025-11-07 15:50 ` Ajith Anandhan @ 2025-11-07 16:18 ` David Lechner 2025-11-09 8:45 ` Ajith Anandhan 0 siblings, 1 reply; 24+ messages in thread From: David Lechner @ 2025-11-07 16:18 UTC (permalink / raw) To: Ajith Anandhan, linux-iio Cc: jic23, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On 11/7/25 9:50 AM, Ajith Anandhan wrote: > On 10/31/25 2:43 AM, David Lechner wrote: >> On 10/30/25 11:34 AM, Ajith Anandhan wrote: >>> Add driver for the Texas Instruments ADS1120, a precision 16-bit >>> analog-to-digital converter with an SPI interface. >>> One note about the review process. Any suggestions you agree with, you don't need to reply to specifically. You can trim out those parts in your reply. It saves time for those reading the replies. >>> +struct ads1120_state { >>> + struct spi_device *spi; >>> + struct mutex lock; >>> + /* >>> + * Used to correctly align data. >>> + * Ensure natural alignment for potential future timestamp support. >>> + */ >>> + u8 data[4] __aligned(IIO_DMA_MINALIGN); >>> + >>> + u8 config[4]; >>> + int current_channel; >>> + int gain; >> Since inputs are multiplexed, we can make this gain a per-channel value, no? > > Yes we can, However i want to keep the initial version simple so would it be > > fine to support per-channel gain configurationin upcoming patches? Absolutely. I really appreciate splitting things up like that as it makes it much easier to review. > >> >> It also sounds like that certain mux input have to have the PGA bypassed >> which means they are limited to only 3 gain values. So these would have >> a different scale_available attribute. > > Since, I'm gonna enable all the 15 channels. I believe we have to have all > > gains(for differential channels). Correct me if i'm wrong. Yes, that is how I understood the datasheet. Differential channels have all gains. Single-ended channels and diagnostic channels only get the low gains (1, 2, 4). >>> +static int ads1120_write_reg(struct ads1120_state *st, u8 reg, u8 value) >>> +{ >>> + u8 buf[2]; >>> + >>> + if (reg > ADS1120_REG_CONFIG3) >>> + return -EINVAL; >>> + >>> + buf[0] = ADS1120_CMD_WREG | (reg << 2); >>> + buf[1] = value; >>> + >>> + return spi_write(st->spi, buf, 2); >>> +} >> Can we use the regmap framework instead of writing our own? > > I’d like to keep the first version simple so i will add the regmap support in the > > later patch since the single ended has less spi transaction to handle. It would be less churn to implement the regmap right away. Typically we try to avoid churn if we can help it. So this would be an exception to the general suggestion that splitting things up is better. ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 2/3] iio: adc: Add support for TI ADS1120 2025-11-07 16:18 ` David Lechner @ 2025-11-09 8:45 ` Ajith Anandhan 0 siblings, 0 replies; 24+ messages in thread From: Ajith Anandhan @ 2025-11-09 8:45 UTC (permalink / raw) To: David Lechner, linux-iio Cc: jic23, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On 11/7/25 9:48 PM, David Lechner wrote: > On 11/7/25 9:50 AM, Ajith Anandhan wrote: >> On 10/31/25 2:43 AM, David Lechner wrote: >>> On 10/30/25 11:34 AM, Ajith Anandhan wrote: >>>> Add driver for the Texas Instruments ADS1120, a precision 16-bit >>>> analog-to-digital converter with an SPI interface. >>>> > One note about the review process. Any suggestions you agree with, you > don't need to reply to specifically. You can trim out those parts in > your reply. It saves time for those reading the replies. > >>>> +struct ads1120_state { >>>> + struct spi_device *spi; >>>> + struct mutex lock; >>>> + /* >>>> + * Used to correctly align data. >>>> + * Ensure natural alignment for potential future timestamp support. >>>> + */ >>>> + u8 data[4] __aligned(IIO_DMA_MINALIGN); >>>> + >>>> + u8 config[4]; >>>> + int current_channel; >>>> + int gain; >>> Since inputs are multiplexed, we can make this gain a per-channel value, no? >> Yes we can, However i want to keep the initial version simple so would it be >> >> fine to support per-channel gain configurationin upcoming patches? > Absolutely. I really appreciate splitting things up like that as it makes > it much easier to review. > >>> It also sounds like that certain mux input have to have the PGA bypassed >>> which means they are limited to only 3 gain values. So these would have >>> a different scale_available attribute. >> Since, I'm gonna enable all the 15 channels. I believe we have to have all >> >> gains(for differential channels). Correct me if i'm wrong. > Yes, that is how I understood the datasheet. Differential channels have all > gains. Single-ended channels and diagnostic channels only get the low gains > (1, 2, 4). > > >>>> +static int ads1120_write_reg(struct ads1120_state *st, u8 reg, u8 value) >>>> +{ >>>> + u8 buf[2]; >>>> + >>>> + if (reg > ADS1120_REG_CONFIG3) >>>> + return -EINVAL; >>>> + >>>> + buf[0] = ADS1120_CMD_WREG | (reg << 2); >>>> + buf[1] = value; >>>> + >>>> + return spi_write(st->spi, buf, 2); >>>> +} >>> Can we use the regmap framework instead of writing our own? >> I’d like to keep the first version simple so i will add the regmap support in the >> >> later patch since the single ended has less spi transaction to handle. > It would be less churn to implement the regmap right away. Typically > we try to avoid churn if we can help it. So this would be an exception > to the general suggestion that splitting things up is better. Got it, I’ll add regmap support and address all review comments in the v2 patch series. BR, Ajith. ^ permalink raw reply [flat|nested] 24+ messages in thread
* [RFC PATCH 3/3] MAINTAINERS: Add entry for TI ADS1120 ADC driver 2025-10-30 16:34 [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC Ajith Anandhan 2025-10-30 16:34 ` [RFC PATCH 1/3] dt-bindings: iio: adc: Add TI ADS1120 binding Ajith Anandhan 2025-10-30 16:34 ` [RFC PATCH 2/3] iio: adc: Add support for TI ADS1120 Ajith Anandhan @ 2025-10-30 16:34 ` Ajith Anandhan 2025-10-30 17:55 ` Jonathan Cameron 2025-10-30 16:40 ` [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC Krzysztof Kozlowski 2025-10-31 8:37 ` Andy Shevchenko 4 siblings, 1 reply; 24+ messages in thread From: Ajith Anandhan @ 2025-10-30 16:34 UTC (permalink / raw) To: linux-iio Cc: jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel, Ajith Anandhan Add a new MAINTAINERS entry for the Texas Instruments ADS1120 ADC driver and its device tree binding. Signed-off-by: Ajith Anandhan <ajithanandhan0406@gmail.com> --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3da2c26a7..1efe88fc9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25613,6 +25613,13 @@ S: Maintained F: Documentation/devicetree/bindings/iio/adc/ti,ads1119.yaml F: drivers/iio/adc/ti-ads1119.c +TI ADS1120 ADC DRIVER +M: Ajith Anandhan <ajithanandhan0406@gmail.com> +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml +F: drivers/iio/adc/ti-ads1120.c + TI ADS7924 ADC DRIVER M: Hugo Villeneuve <hvilleneuve@dimonoff.com> L: linux-iio@vger.kernel.org -- 2.34.1 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 3/3] MAINTAINERS: Add entry for TI ADS1120 ADC driver 2025-10-30 16:34 ` [RFC PATCH 3/3] MAINTAINERS: Add entry for TI ADS1120 ADC driver Ajith Anandhan @ 2025-10-30 17:55 ` Jonathan Cameron 2025-11-07 13:43 ` Ajith Anandhan 0 siblings, 1 reply; 24+ messages in thread From: Jonathan Cameron @ 2025-10-30 17:55 UTC (permalink / raw) To: Ajith Anandhan Cc: linux-iio, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On Thu, 30 Oct 2025 22:04:11 +0530 Ajith Anandhan <ajithanandhan0406@gmail.com> wrote: > Add a new MAINTAINERS entry for the Texas Instruments ADS1120 > ADC driver and its device tree binding. blank line before tag block. > Signed-off-by: Ajith Anandhan <ajithanandhan0406@gmail.com> Just bring this in along with the code, it doesn't need a separate commit. Thanks, Jonathan > --- > MAINTAINERS | 7 +++++++ > 1 file changed, 7 insertions(+) > > diff --git a/MAINTAINERS b/MAINTAINERS > index 3da2c26a7..1efe88fc9 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -25613,6 +25613,13 @@ S: Maintained > F: Documentation/devicetree/bindings/iio/adc/ti,ads1119.yaml > F: drivers/iio/adc/ti-ads1119.c > > +TI ADS1120 ADC DRIVER > +M: Ajith Anandhan <ajithanandhan0406@gmail.com> > +L: linux-iio@vger.kernel.org > +S: Maintained > +F: Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml > +F: drivers/iio/adc/ti-ads1120.c > + > TI ADS7924 ADC DRIVER > M: Hugo Villeneuve <hvilleneuve@dimonoff.com> > L: linux-iio@vger.kernel.org ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 3/3] MAINTAINERS: Add entry for TI ADS1120 ADC driver 2025-10-30 17:55 ` Jonathan Cameron @ 2025-11-07 13:43 ` Ajith Anandhan 0 siblings, 0 replies; 24+ messages in thread From: Ajith Anandhan @ 2025-11-07 13:43 UTC (permalink / raw) To: Jonathan Cameron Cc: linux-iio, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On 10/30/25 11:25 PM, Jonathan Cameron wrote: > On Thu, 30 Oct 2025 22:04:11 +0530 > Ajith Anandhan <ajithanandhan0406@gmail.com> wrote: > >> Add a new MAINTAINERS entry for the Texas Instruments ADS1120 >> ADC driver and its device tree binding. > blank line before tag block. Noted. >> Signed-off-by: Ajith Anandhan <ajithanandhan0406@gmail.com> > Just bring this in along with the code, it doesn't need a separate > commit. > > Thanks, > > Jonathan I will add along with the code. >> --- >> MAINTAINERS | 7 +++++++ >> 1 file changed, 7 insertions(+) >> >> diff --git a/MAINTAINERS b/MAINTAINERS >> index 3da2c26a7..1efe88fc9 100644 >> --- a/MAINTAINERS >> +++ b/MAINTAINERS >> @@ -25613,6 +25613,13 @@ S: Maintained >> F: Documentation/devicetree/bindings/iio/adc/ti,ads1119.yaml >> F: drivers/iio/adc/ti-ads1119.c >> >> +TI ADS1120 ADC DRIVER >> +M: Ajith Anandhan <ajithanandhan0406@gmail.com> >> +L: linux-iio@vger.kernel.org >> +S: Maintained >> +F: Documentation/devicetree/bindings/iio/adc/ti,ads1120.yaml >> +F: drivers/iio/adc/ti-ads1120.c >> + >> TI ADS7924 ADC DRIVER >> M: Hugo Villeneuve <hvilleneuve@dimonoff.com> >> L: linux-iio@vger.kernel.org ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC 2025-10-30 16:34 [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC Ajith Anandhan ` (2 preceding siblings ...) 2025-10-30 16:34 ` [RFC PATCH 3/3] MAINTAINERS: Add entry for TI ADS1120 ADC driver Ajith Anandhan @ 2025-10-30 16:40 ` Krzysztof Kozlowski [not found] ` <CABPXPSKzOhGicdPLoMFy8xvd0Xx5_D2P2pduteY3QhDRV4d2Ow@mail.gmail.com> 2025-10-31 8:37 ` Andy Shevchenko 4 siblings, 1 reply; 24+ messages in thread From: Krzysztof Kozlowski @ 2025-10-30 16:40 UTC (permalink / raw) To: Ajith Anandhan, linux-iio Cc: jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On 30/10/2025 17:34, Ajith Anandhan wrote: > This RFC patch series adds support for the Texas Instruments ADS1120, > a precision 16-bit delta-sigma ADC with SPI interface. > > The driver provides: > - 4 single-ended voltage input channels > - Programmable gain amplifier (1 to 128) > - Configurable data rates (20 to 1000 SPS) > - Single-shot conversion mode > > I'm looking for feedback on: > 1. The implementation approach for single-shot conversions > 2. Any other suggestions for improvement No need to call your patches RFC then. It only stops from merging and some people will not review the code (RFC means not ready for inclusion). Best regards, Krzysztof ^ permalink raw reply [flat|nested] 24+ messages in thread
[parent not found: <CABPXPSKzOhGicdPLoMFy8xvd0Xx5_D2P2pduteY3QhDRV4d2Ow@mail.gmail.com>]
* Re: [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC [not found] ` <CABPXPSKzOhGicdPLoMFy8xvd0Xx5_D2P2pduteY3QhDRV4d2Ow@mail.gmail.com> @ 2025-10-30 16:58 ` Ajith Anandhan 2025-10-30 17:08 ` Jonathan Cameron 2025-10-30 19:44 ` Krzysztof Kozlowski 1 sibling, 1 reply; 24+ messages in thread From: Ajith Anandhan @ 2025-10-30 16:58 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: linux-iio, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel Hi Krzysztof, Thank you for the feedback! I’ll resend this as a regular PATCH series shortly. I appreciate you taking the time to review. Best regards, Ajith On Thu, Oct 30, 2025 at 10:19 PM Ajith Anandhan <ajithanandhan0406@gmail.com> wrote: > > Thank you for the feedback! I’ll resend this as regular PATCH series shortly. I appreciate you taking the time to review. > > Best regards, > Ajith > > On Thu, Oct 30, 2025 at 10:10 PM Krzysztof Kozlowski <krzk@kernel.org> wrote: >> >> On 30/10/2025 17:34, Ajith Anandhan wrote: >> > This RFC patch series adds support for the Texas Instruments ADS1120, >> > a precision 16-bit delta-sigma ADC with SPI interface. >> > >> > The driver provides: >> > - 4 single-ended voltage input channels >> > - Programmable gain amplifier (1 to 128) >> > - Configurable data rates (20 to 1000 SPS) >> > - Single-shot conversion mode >> > >> > I'm looking for feedback on: >> > 1. The implementation approach for single-shot conversions >> > 2. Any other suggestions for improvement >> >> >> No need to call your patches RFC then. It only stops from merging and >> some people will not review the code (RFC means not ready for inclusion). >> >> Best regards, >> Krzysztof ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC 2025-10-30 16:58 ` Ajith Anandhan @ 2025-10-30 17:08 ` Jonathan Cameron 0 siblings, 0 replies; 24+ messages in thread From: Jonathan Cameron @ 2025-10-30 17:08 UTC (permalink / raw) To: Ajith Anandhan Cc: Krzysztof Kozlowski, linux-iio, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On Thu, 30 Oct 2025 22:28:10 +0530 Ajith Anandhan <ajithanandhan0406@gmail.com> wrote: > Hi Krzysztof, > > Thank you for the feedback! I’ll resend this as a regular PATCH series > shortly. I appreciate you taking the time to review. Given you've sent it out, leave it as RFC for a day or so. > > Best regards, > Ajith > > On Thu, Oct 30, 2025 at 10:19 PM Ajith Anandhan > <ajithanandhan0406@gmail.com> wrote: > > > > Thank you for the feedback! I’ll resend this as regular PATCH series shortly. I appreciate you taking the time to review. > > > > Best regards, > > Ajith > > > > On Thu, Oct 30, 2025 at 10:10 PM Krzysztof Kozlowski <krzk@kernel.org> wrote: > >> > >> On 30/10/2025 17:34, Ajith Anandhan wrote: > >> > This RFC patch series adds support for the Texas Instruments ADS1120, > >> > a precision 16-bit delta-sigma ADC with SPI interface. > >> > > >> > The driver provides: > >> > - 4 single-ended voltage input channels > >> > - Programmable gain amplifier (1 to 128) > >> > - Configurable data rates (20 to 1000 SPS) > >> > - Single-shot conversion mode > >> > > >> > I'm looking for feedback on: > >> > 1. The implementation approach for single-shot conversions > >> > 2. Any other suggestions for improvement > >> > >> > >> No need to call your patches RFC then. It only stops from merging and > >> some people will not review the code (RFC means not ready for inclusion). > >> > >> Best regards, > >> Krzysztof > ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC [not found] ` <CABPXPSKzOhGicdPLoMFy8xvd0Xx5_D2P2pduteY3QhDRV4d2Ow@mail.gmail.com> 2025-10-30 16:58 ` Ajith Anandhan @ 2025-10-30 19:44 ` Krzysztof Kozlowski 1 sibling, 0 replies; 24+ messages in thread From: Krzysztof Kozlowski @ 2025-10-30 19:44 UTC (permalink / raw) To: Ajith Anandhan Cc: linux-iio, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On 30/10/2025 17:49, Ajith Anandhan wrote: > Thank you for the feedback! I’ll resend this as regular PATCH series > shortly. I appreciate you taking the time to review. Please avoid top-posting. Don't resend now. Next version will be v2 and send it after you receive review. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC 2025-10-30 16:34 [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC Ajith Anandhan ` (3 preceding siblings ...) 2025-10-30 16:40 ` [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC Krzysztof Kozlowski @ 2025-10-31 8:37 ` Andy Shevchenko 2025-11-01 11:37 ` Ajith Anandhan 4 siblings, 1 reply; 24+ messages in thread From: Andy Shevchenko @ 2025-10-31 8:37 UTC (permalink / raw) To: Ajith Anandhan Cc: linux-iio, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On Thu, Oct 30, 2025 at 10:04:08PM +0530, Ajith Anandhan wrote: > This RFC patch series adds support for the Texas Instruments ADS1120, > a precision 16-bit delta-sigma ADC with SPI interface. > > The driver provides: > - 4 single-ended voltage input channels > - Programmable gain amplifier (1 to 128) > - Configurable data rates (20 to 1000 SPS) > - Single-shot conversion mode > > I'm looking for feedback on: > 1. The implementation approach for single-shot conversions > 2. Any other suggestions for improvement > > Datasheet: https://www.ti.com/lit/gpn/ads1120 The cover letter missed to answer the Q: Why a new driver? Have you checked the existing drivers? Do we have a similar enough one that may be extended to support this chip? -- With Best Regards, Andy Shevchenko ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC 2025-10-31 8:37 ` Andy Shevchenko @ 2025-11-01 11:37 ` Ajith Anandhan 2025-11-03 7:51 ` Andy Shevchenko 0 siblings, 1 reply; 24+ messages in thread From: Ajith Anandhan @ 2025-11-01 11:37 UTC (permalink / raw) To: Andy Shevchenko Cc: linux-iio, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On 10/31/25 2:07 PM, Andy Shevchenko wrote: > On Thu, Oct 30, 2025 at 10:04:08PM +0530, Ajith Anandhan wrote: >> This RFC patch series adds support for the Texas Instruments ADS1120, >> a precision 16-bit delta-sigma ADC with SPI interface. >> >> The driver provides: >> - 4 single-ended voltage input channels >> - Programmable gain amplifier (1 to 128) >> - Configurable data rates (20 to 1000 SPS) >> - Single-shot conversion mode >> >> I'm looking for feedback on: >> 1. The implementation approach for single-shot conversions >> 2. Any other suggestions for improvement >> >> Datasheet: https://www.ti.com/lit/gpn/ads1120 > The cover letter missed to answer the Q: Why a new driver? Have you checked the > existing drivers? Do we have a similar enough one that may be extended to > support this chip? > Hi Andy, Thank you for the feedback. I evaluated the following existing driver before creating a new one: ads124s08.c - TI ADS124S08 - This is the closest match (both are delta-sigma, SPI-based) - However, significant differences exist: * Different register layout (ADS124S08 has more registers) * Different command set ADS124S08 has built-in MUX for differential inputs * Different register addressing and bit fields and conversion timing and data retrieval. would require extensive conditional code paths that might reduce maintainability for both devices. A separate, focused driver seemed cleaner. BR, Ajith Anandhan. ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC 2025-11-01 11:37 ` Ajith Anandhan @ 2025-11-03 7:51 ` Andy Shevchenko 0 siblings, 0 replies; 24+ messages in thread From: Andy Shevchenko @ 2025-11-03 7:51 UTC (permalink / raw) To: Ajith Anandhan Cc: linux-iio, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt, devicetree, linux-kernel On Sat, Nov 01, 2025 at 05:07:38PM +0530, Ajith Anandhan wrote: > On 10/31/25 2:07 PM, Andy Shevchenko wrote: > > On Thu, Oct 30, 2025 at 10:04:08PM +0530, Ajith Anandhan wrote: > > > This RFC patch series adds support for the Texas Instruments ADS1120, > > > a precision 16-bit delta-sigma ADC with SPI interface. > > > > > > The driver provides: > > > - 4 single-ended voltage input channels > > > - Programmable gain amplifier (1 to 128) > > > - Configurable data rates (20 to 1000 SPS) > > > - Single-shot conversion mode > > > > > > I'm looking for feedback on: > > > 1. The implementation approach for single-shot conversions > > > 2. Any other suggestions for improvement > > > > > > Datasheet: https://www.ti.com/lit/gpn/ads1120 > > The cover letter missed to answer the Q: Why a new driver? Have you checked the > > existing drivers? Do we have a similar enough one that may be extended to > > support this chip? > > > Thank you for the feedback. > > I evaluated the following existing driver before creating a new one: > > ads124s08.c - TI ADS124S08 > > - This is the closest match (both are delta-sigma, SPI-based) > > - However, significant differences exist: > > * Different register layout (ADS124S08 has more registers) > > * Different command set ADS124S08 has built-in MUX for differential > inputs > > * Different register addressing and bit fields and conversion timing and > data retrieval. > > would require extensive conditional code paths that might reduce > maintainability for both devices. A separate, focused driver seemed cleaner. Good, please add this summary to the cover letter of next version. -- With Best Regards, Andy Shevchenko ^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2025-11-09 14:02 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-30 16:34 [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC Ajith Anandhan
2025-10-30 16:34 ` [RFC PATCH 1/3] dt-bindings: iio: adc: Add TI ADS1120 binding Ajith Anandhan
2025-10-30 17:12 ` Jonathan Cameron
2025-10-30 20:04 ` David Lechner
2025-11-01 12:24 ` Ajith Anandhan
2025-11-01 11:53 ` Ajith Anandhan
2025-10-30 16:34 ` [RFC PATCH 2/3] iio: adc: Add support for TI ADS1120 Ajith Anandhan
2025-10-30 17:54 ` Jonathan Cameron
2025-11-07 14:40 ` Ajith Anandhan
2025-11-09 14:02 ` Jonathan Cameron
2025-10-30 21:13 ` David Lechner
2025-11-07 15:50 ` Ajith Anandhan
2025-11-07 16:18 ` David Lechner
2025-11-09 8:45 ` Ajith Anandhan
2025-10-30 16:34 ` [RFC PATCH 3/3] MAINTAINERS: Add entry for TI ADS1120 ADC driver Ajith Anandhan
2025-10-30 17:55 ` Jonathan Cameron
2025-11-07 13:43 ` Ajith Anandhan
2025-10-30 16:40 ` [RFC PATCH 0/3] iio: adc: Add support for TI ADS1120 ADC Krzysztof Kozlowski
[not found] ` <CABPXPSKzOhGicdPLoMFy8xvd0Xx5_D2P2pduteY3QhDRV4d2Ow@mail.gmail.com>
2025-10-30 16:58 ` Ajith Anandhan
2025-10-30 17:08 ` Jonathan Cameron
2025-10-30 19:44 ` Krzysztof Kozlowski
2025-10-31 8:37 ` Andy Shevchenko
2025-11-01 11:37 ` Ajith Anandhan
2025-11-03 7:51 ` Andy Shevchenko
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).