* [PATCH v3 0/8] iio: adc: new ti-ads112c14 driver
@ 2026-07-10 22:50 David Lechner
2026-07-10 22:50 ` [PATCH v3 1/8] dt-bindings: iio: adc: Add reference-sources property David Lechner
` (7 more replies)
0 siblings, 8 replies; 17+ messages in thread
From: David Lechner @ 2026-07-10 22:50 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: Chris Hall, Patrick Edwards, Kurt Borja, Nguyen Minh Tien,
linux-iio, devicetree, linux-kernel, David Lechner, Conor Dooley
This adds support for TI ADS112C14 and ADS122C14 ADC chips.
The closest thing we've seen to this in the kernel already is ads124s08.
However, that has a completely different register map and the DT
bindings are incomplete and the driver is extremely basic. So I've just
started from scratch here.
We've also had a similar submission recently for ADS1220 [1]. That chip
is in a similar situation to ads124s08 in that it has a different
register map (but the submitted DT bindings are better than the ones for
ads124s08, even if still a bit incomplete). And literally as I was
writing the previous sentence, another series [2] was sent for yet
another similar family of chips (ADS1262). That one is even more complex
in the feature set than the ones I am working on.
[1]: https://lore.kernel.org/linux-iio/20260610151342.44274-1-zizuzacker@gmail.com/
[2]: https://lore.kernel.org/linux-iio/20260612-ads126x-v1-0-894c788d03ed@gmail.com/
All of these chips have in common that they are designed for use with
RTDs and thermocouples and so they look very similar to each other in
terms of wiring and feature set, even if the register maps are
different. They are in the gray area where we could either keep them
separate because they are just different enough, or we could do like
we've done before with ad_sigma_delta and have a bit of an abstraction
layer for the register differences and otherwise try to share as much
code as possible. Normally, I would lean towards keeping them separate,
but in this case, I'm considering trying to share code because the
devicetree bindings for the inputs is complex and is going to be mostly
the same across all of these chips.
After seeing Kurt's v2 though (that doesn't attempt to share code), it
seems like the chips are different enough that sharing code might be
more complicated/messy than I initially thought. So I'm happy to keep
going that route.
This series includes just basic support for reading single measurements
from the ADC and gain selection via the scale attribute. I plan to
follow this up with additional series to add support for buffered reads,
filtering/oversampling configuration, event support, gpio controller
support and perhaps a few other things that are slipping my mind right
now.
The most interesting part about this (that I alluded to above) is the
way channels are handled. These are multipling ADCs with differential
and single-ended inputs. But what sets them apart from other similar
chips is that since they are designed for use with RTDs, there can also
be a current output required to excite the RTD and this current output
might be different for different channels. So the way I conceptualized
the channels is that the devicetree specifies the conditions needed
to take a particular measurement rather than being purely a physical
channel.
This makes things more flexible, but does make the driver a bit more
complex. For example, knowing when the current output needs to be
enabled or disabled. For now, I have chosen a lazy-enable where they
are not turned on until the first measurement is taken that requires
them, but then they stay on until another measurement is taken that
doesn't require them. This can lead to some oddness with the diagnostic
channels that may be measuring something that indirectly requires the
current output (i.e. the external reference voltage when it is connected
to a resistor rather than a power supply). This means you need to take
a measurement that requires the current output to be enabled before the
diagnostic channels will give accurate readings.
I have also pushed a branch to [3] that contains the start of some
documentation for this driver that can give some more insight into how
the implementation works. It still needs some work and also documents
some things that haven't been implemented yet, so I haven't included it
in this series yet.
[3]: https://github.com/dlech/linux/blob/b4/iio-adc-ti-ads122c14/Documentation/iio/ads112c14.rst
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
Changes in v3:
- Mostly cosmetic changes and a few bug fixes to address review feedback.
- See individual patches for details of changes.
- Link to v2: https://patch.msgid.link/20260625-iio-adc-ti-ads122c14-v2-0-ceb9b0b561cb@baylibre.com
Changes in v2:
- Added patches for adding properties to adc.yaml.
- Some of these are coming from: https://lore.kernel.org/linux-iio/20260622-new-channel-props-v2-0-aafd5369f253@gmail.com/
- For now, I have stuck with one channel per single-channel pin or
diff-channels pin pair rather than some of the other ideas that were
discussed. Handling burn out current enable will be handled in a later
series. I'm leaning towards something like the _burnoutraw attribute
that Jonathan suggested.
- See individual patches for details of changes (mostly renaming DT
properties, fixing some driver bugs and style issues).
- Link to v1: https://patch.msgid.link/20260615-iio-adc-ti-ads122c14-v1-0-e6bdadf7cb2b@baylibre.com
---
David Lechner (TI) (5):
dt-bindings: iio: adc: add input-chopping property
dt-bindings: iio: adc: add ti,ads122c14
iio: adc: add ti-ads112c14 driver
iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel
iio: adc: ti-ads112c14: add measurement channel support
Kurt Borja (3):
dt-bindings: iio: adc: Add reference-sources property
dt-bindings: iio: adc: Add excitation current sources properties
dt-bindings: iio: adc: Add burn-out current properties
Documentation/devicetree/bindings/iio/adc/adc.yaml | 40 +
.../devicetree/bindings/iio/adc/ti,ads112c14.yaml | 207 ++++
MAINTAINERS | 7 +
drivers/iio/adc/Kconfig | 12 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ti-ads112c14.c | 1203 ++++++++++++++++++++
6 files changed, 1470 insertions(+)
---
base-commit: 093239070573637ad2b4cb56abc9c4c7ee109294
change-id: 20260514-iio-adc-ti-ads122c14-d0b92479334e
Best regards,
--
David Lechner (TI) <dlechner@baylibre.com>
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v3 1/8] dt-bindings: iio: adc: Add reference-sources property
2026-07-10 22:50 [PATCH v3 0/8] iio: adc: new ti-ads112c14 driver David Lechner
@ 2026-07-10 22:50 ` David Lechner
2026-07-10 22:50 ` [PATCH v3 2/8] dt-bindings: iio: adc: Add excitation current sources properties David Lechner
` (6 subsequent siblings)
7 siblings, 0 replies; 17+ messages in thread
From: David Lechner @ 2026-07-10 22:50 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: Chris Hall, Patrick Edwards, Kurt Borja, Nguyen Minh Tien,
linux-iio, devicetree, linux-kernel, David Lechner, Conor Dooley
From: Kurt Borja <kuurtb@gmail.com>
Some ADCs have configurable voltage reference sources for each channel.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
v3 changes:
* Removed minItems.
v2 changes (compared to Kurt's RFC v2):
* Drop maxItems.
* Drop 2nd paragraph of description.
* Make reference-sources plural since it is an array.
---
Documentation/devicetree/bindings/iio/adc/adc.yaml | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/adc/adc.yaml b/Documentation/devicetree/bindings/iio/adc/adc.yaml
index b9bc02b5b07a..8f608bf0b24d 100644
--- a/Documentation/devicetree/bindings/iio/adc/adc.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adc.yaml
@@ -73,6 +73,13 @@ properties:
device design and can interact with other characteristics such as
settling time.
+ reference-sources:
+ $ref: /schemas/types.yaml#/definitions/string-array
+ description:
+ Indicates the voltage reference source or sources for this channel. Some
+ ADCs usually allow choosing between internal reference sources or a pair
+ of external pins.
+
anyOf:
- oneOf:
- required:
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v3 2/8] dt-bindings: iio: adc: Add excitation current sources properties
2026-07-10 22:50 [PATCH v3 0/8] iio: adc: new ti-ads112c14 driver David Lechner
2026-07-10 22:50 ` [PATCH v3 1/8] dt-bindings: iio: adc: Add reference-sources property David Lechner
@ 2026-07-10 22:50 ` David Lechner
2026-07-10 22:50 ` [PATCH v3 3/8] dt-bindings: iio: adc: Add burn-out current properties David Lechner
` (5 subsequent siblings)
7 siblings, 0 replies; 17+ messages in thread
From: David Lechner @ 2026-07-10 22:50 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: Chris Hall, Patrick Edwards, Kurt Borja, Nguyen Minh Tien,
linux-iio, devicetree, linux-kernel, David Lechner, Conor Dooley
From: Kurt Borja <kuurtb@gmail.com>
Some ADCs incorporate current sources that provide excitation current to
resistive temperature devices (RTDs), thermistors, diodes and other
resistive sensors that require constant current biasing.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
v2 changes (compared to Kurt's RFC v2):
* Make units nanoamps instead of microamps to fit known devices.
* Drop bit about allowing single value for multiple channels.
* Add bit about semantics of the array index.
---
Documentation/devicetree/bindings/iio/adc/adc.yaml | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/adc/adc.yaml b/Documentation/devicetree/bindings/iio/adc/adc.yaml
index 8f608bf0b24d..a21ed9ae4bab 100644
--- a/Documentation/devicetree/bindings/iio/adc/adc.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adc.yaml
@@ -80,6 +80,26 @@ properties:
ADCs usually allow choosing between internal reference sources or a pair
of external pins.
+ excitation-channels:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ description:
+ Excitation current sources provide current to resistive temperature
+ devices (RTDs), thermistors, diodes and other resistive sensors that
+ require constant current biasing.
+
+ This array describes the mux configuration of the excitation current
+ sources.
+
+ excitation-current-nanoamp:
+ description:
+ Excitation current sources provide current to resistive temperature
+ devices (RTDs), thermistors, diodes and other resistive sensors that
+ require constant current biasing.
+
+ This array describes the current configuration of the excitation current
+ sources. The index in the array corresponds to the same index in the
+ excitation-channels array.
+
anyOf:
- oneOf:
- required:
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v3 3/8] dt-bindings: iio: adc: Add burn-out current properties
2026-07-10 22:50 [PATCH v3 0/8] iio: adc: new ti-ads112c14 driver David Lechner
2026-07-10 22:50 ` [PATCH v3 1/8] dt-bindings: iio: adc: Add reference-sources property David Lechner
2026-07-10 22:50 ` [PATCH v3 2/8] dt-bindings: iio: adc: Add excitation current sources properties David Lechner
@ 2026-07-10 22:50 ` David Lechner
2026-07-10 22:50 ` [PATCH v3 4/8] dt-bindings: iio: adc: add input-chopping property David Lechner (TI)
` (4 subsequent siblings)
7 siblings, 0 replies; 17+ messages in thread
From: David Lechner @ 2026-07-10 22:50 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: Chris Hall, Patrick Edwards, Kurt Borja, Nguyen Minh Tien,
linux-iio, devicetree, linux-kernel, David Lechner, Conor Dooley
From: Kurt Borja <kuurtb@gmail.com>
Some ADCs incorporate burn-out current sources that provide current to
the channel's input pins for open-circuit or short-circuit detection.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
v3 changes:
* Removed maxItems.
v2 changes (compared to Kurt's RFC v2):
* Changed units from microamps to nanoamps to fit known devices.
---
Documentation/devicetree/bindings/iio/adc/adc.yaml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/adc/adc.yaml b/Documentation/devicetree/bindings/iio/adc/adc.yaml
index a21ed9ae4bab..308857f11220 100644
--- a/Documentation/devicetree/bindings/iio/adc/adc.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adc.yaml
@@ -100,6 +100,11 @@ properties:
sources. The index in the array corresponds to the same index in the
excitation-channels array.
+ burn-out-current-nanoamp:
+ description:
+ Burn-out current sources provide current to the channel's input pins for
+ open-circuit or short-circuit detection.
+
anyOf:
- oneOf:
- required:
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v3 4/8] dt-bindings: iio: adc: add input-chopping property
2026-07-10 22:50 [PATCH v3 0/8] iio: adc: new ti-ads112c14 driver David Lechner
` (2 preceding siblings ...)
2026-07-10 22:50 ` [PATCH v3 3/8] dt-bindings: iio: adc: Add burn-out current properties David Lechner
@ 2026-07-10 22:50 ` David Lechner (TI)
2026-07-10 22:50 ` [PATCH v3 5/8] dt-bindings: iio: adc: add ti,ads122c14 David Lechner (TI)
` (3 subsequent siblings)
7 siblings, 0 replies; 17+ messages in thread
From: David Lechner (TI) @ 2026-07-10 22:50 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: Chris Hall, Patrick Edwards, Kurt Borja, Nguyen Minh Tien,
linux-iio, devicetree, linux-kernel, David Lechner
Add a generic input-chopping property to adc.yaml. This is a feature
seen frequently in ADCs that are designed to measure resistive loads,
such as RTDs and strain gauges. Enabling this can reduce offset errors
by swapping the positive and negative input channels on every other
conversion. This can have side-effects in timing and filter response, so
it is not always desirable to enable this feature in some applications.
Therefore, it is best to make this a property that can be enabled or
disabled in the device tree.
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
v3 changes:
* Renamed property to input-chopping to reflect more common terminology.
* Removed "allowed" from the description.
* Extended description to explain when it should not be used.
v2 changes:
* New patch.
---
Documentation/devicetree/bindings/iio/adc/adc.yaml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/adc/adc.yaml b/Documentation/devicetree/bindings/iio/adc/adc.yaml
index 308857f11220..c408feebd46d 100644
--- a/Documentation/devicetree/bindings/iio/adc/adc.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adc.yaml
@@ -80,6 +80,14 @@ properties:
ADCs usually allow choosing between internal reference sources or a pair
of external pins.
+ input-chopping:
+ type: boolean
+ description:
+ If set, the positive and negative input channels are swapped on every
+ other conversion to reduce offset error. This generally comes at the
+ expense of added settling time between conversions, so should be omitted
+ on systems with high-frequency signals or high data rate requirements.
+
excitation-channels:
$ref: /schemas/types.yaml#/definitions/uint32-array
description:
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v3 5/8] dt-bindings: iio: adc: add ti,ads122c14
2026-07-10 22:50 [PATCH v3 0/8] iio: adc: new ti-ads112c14 driver David Lechner
` (3 preceding siblings ...)
2026-07-10 22:50 ` [PATCH v3 4/8] dt-bindings: iio: adc: add input-chopping property David Lechner (TI)
@ 2026-07-10 22:50 ` David Lechner (TI)
2026-07-10 22:59 ` sashiko-bot
2026-07-10 23:12 ` David Lechner
2026-07-10 22:50 ` [PATCH v3 6/8] iio: adc: add ti-ads112c14 driver David Lechner (TI)
` (2 subsequent siblings)
7 siblings, 2 replies; 17+ messages in thread
From: David Lechner (TI) @ 2026-07-10 22:50 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: Chris Hall, Patrick Edwards, Kurt Borja, Nguyen Minh Tien,
linux-iio, devicetree, linux-kernel, David Lechner
Add new bindings for ti,ads122c14 and similar devices.
This is an ADC that is primarily intended for use with temperature
sensors. There are a few unusual properties because of this. In
particular, the reference voltage source and current output requirements
can be different for each measurement, so these are included in the
channel bindings.
The REFP/REFN reference voltage is usually just connected to a resistor
that is being driven by the ADC's current outputs, so there is special
property for this case rather than requiring a regulator to be defined
to represent that.
ti,vref-source is reused from ti,tlv320adcx140.yaml (otherwise might
have preferred an enum of strings).
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
v3 changes:
* Fixed channel@ pattern.
* Adapted to input-chopping property rename.
* Made sure to use items: on array properties.
* Added reg to required list.
* Fixed missing space in example comment.
v2 changes:
* Added ti, vendor prefix when appropriate.
* Others are now standardized properties from adc.yaml.
* Moved top-level unevaluatedProperties.
* Fixed some issues with the channel@ matching and reg property.
* Dropped description on bipolar property.
* Renamed current-chopping to input-channel-rotation.
* Drop header file and use string enum instead for reference-sources.
* Added burn-out-current-nanoamp property.
* Drop allOf: and just use oneOf: directly.
* Fix inconsistent indentation in the examples.
---
.../devicetree/bindings/iio/adc/ti,ads112c14.yaml | 207 +++++++++++++++++++++
MAINTAINERS | 6 +
2 files changed, 213 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml
new file mode 100644
index 000000000000..a2ee473c8e61
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml
@@ -0,0 +1,207 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/ti,ads112c14.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments' ADS112C14 and similar ADC chips
+
+description: |
+ Supports the following Texas Instruments' ADC chips:
+ - ADS112C14 (16-bit)
+ - ADS122C14 (24-bit)
+
+ https://www.ti.com/lit/ds/symlink/ads122c14.pdf
+
+ These chips are primarily designed for use with temperature sensors such as
+ RTDs and thermocouples. The channel bindings reflect this in that each channel
+ represents the conditions required to make a measurement rather than strictly
+ just the physical input channels.
+
+maintainers:
+ - David Lechner <dlechner@baylibre.com>
+
+properties:
+ compatible:
+ enum:
+ - ti,ads112c14
+ - ti,ads122c14
+
+ reg:
+ items:
+ - minimum: 0x40
+ maximum: 0x47
+
+ clocks:
+ maxItems: 1
+ description: Optional external clock connected to GPIO3 pin.
+
+ avdd-supply: true
+ dvdd-supply: true
+
+ refp-supply: true
+ refn-supply: true
+
+ ti,refp-refn-resistor-ohms:
+ description:
+ The resistance of the external resistor between REFP and REFN when using
+ resistor bridge driven by current outputs for RTD measurements.
+
+ interrupts:
+ minItems: 1
+ items:
+ - description: FAULT interrupt (GPIO2 pin)
+ - description: DRDY interrupt (GPIO3 pin)
+
+ interrupt-names:
+ minItems: 1
+ maxItems: 2
+ items:
+ enum: [fault, drdy]
+
+ gpio-controller: true
+ '#gpio-cells':
+ const: 2
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+patternProperties:
+ ^channel@[0-9a-f]$:
+ $ref: adc.yaml
+
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ items:
+ - maximum: 15 # arbitrary limit, channel@ can be any combination of AIN0-AIN7
+
+ single-channel:
+ maximum: 7
+
+ diff-channels:
+ items:
+ maximum: 7
+
+ bipolar: true
+
+ input-chopping: true
+
+ excitation-channels:
+ maxItems: 2
+ items:
+ maximum: 7
+
+ excitation-current-nanoamp:
+ maxItems: 2
+ items:
+ enum: [1000, 10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000,
+ 90000, 100000, 200000, 300000, 400000, 500000, 600000, 700000,
+ 800000, 900000, 1000000]
+
+ burn-out-current-nanoamp:
+ items:
+ - enum: [200, 1000, 10000]
+
+ reference-sources:
+ items:
+ - enum: [internal-2.5v, internal-1.25v, external, avdd]
+ default: internal-2.5v
+
+ dependencies:
+ excitation-channels: [ excitation-current-nanoamp ]
+ excitation-current-nanoamp: [ excitation-channels ]
+
+ oneOf:
+ - required: [ single-channel ]
+ - required: [ diff-channels ]
+
+unevaluatedProperties: false
+
+required:
+ - compatible
+ - reg
+ - avdd-supply
+ - dvdd-supply
+
+dependencies:
+ refn-supply: [ refp-supply ]
+
+oneOf:
+ - required: [ refp-supply ]
+ - required: [ "ti,refp-refn-resistor-ohms" ]
+ - properties:
+ refp-supply: false
+ refn-supply: false
+ ti,refp-refn-resistor-ohms: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@40 {
+ compatible = "ti,ads112c14";
+ reg = <0x40>;
+
+ avdd-supply = <&avdd>;
+ dvdd-supply = <&dvdd>;
+
+ /* 3-Wire RTD: Two IDACs, One Measurement (AIN1-AIN2) */
+
+ ti,refp-refn-resistor-ohms = <500>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ channel@0 {
+ reg = <0>;
+ diff-channels = <1>, <2>;
+ input-chopping;
+ excitation-channels = <0>, <3>;
+ excitation-current-nanoamp = <500000>, <500000>;
+ reference-sources = "external";
+ label = "rtd";
+ };
+ };
+ };
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@40 {
+ compatible = "ti,ads112c14";
+ reg = <0x40>;
+
+ avdd-supply = <&avdd>;
+ dvdd-supply = <&dvdd>;
+
+ /* Resistive Bridge Measurement With a Thermistor for Temperature Compensation */
+
+ refp-supply = <&avdd>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ channel@0 {
+ reg = <0>;
+ diff-channels = <6>, <7>;
+ bipolar;
+ reference-sources = "external";
+ label = "bridge";
+ };
+
+ channel@1 {
+ reg = <1>;
+ diff-channels = <1>, <2>;
+ reference-sources = "internal-2.5v";
+ label = "thermistor";
+ };
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 2b1ec46c5919..3e114e0c9c5f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26958,6 +26958,12 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/adc/ti,ads1119.yaml
F: drivers/iio/adc/ti-ads1119.c
+TI ADS112C14 ADC DRIVER
+M: David Lechner <dlechner@baylibre.com>
+L: linux-iio@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml
+
TI ADS1018 ADC DRIVER
M: Kurt Borja <kuurtb@gmail.com>
L: linux-iio@vger.kernel.org
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v3 6/8] iio: adc: add ti-ads112c14 driver
2026-07-10 22:50 [PATCH v3 0/8] iio: adc: new ti-ads112c14 driver David Lechner
` (4 preceding siblings ...)
2026-07-10 22:50 ` [PATCH v3 5/8] dt-bindings: iio: adc: add ti,ads122c14 David Lechner (TI)
@ 2026-07-10 22:50 ` David Lechner (TI)
2026-07-10 23:00 ` sashiko-bot
2026-07-12 9:42 ` Andy Shevchenko
2026-07-10 22:50 ` [PATCH v3 7/8] iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel David Lechner (TI)
2026-07-10 22:50 ` [PATCH v3 8/8] iio: adc: ti-ads112c14: add measurement channel support David Lechner (TI)
7 siblings, 2 replies; 17+ messages in thread
From: David Lechner (TI) @ 2026-07-10 22:50 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: Chris Hall, Patrick Edwards, Kurt Borja, Nguyen Minh Tien,
linux-iio, devicetree, linux-kernel, David Lechner
Add a new driver for the TI ADS112C14/ADS122C14 ADC chips.
This first step is adding a very basic driver that only supports power
on/reset and reading the system monitor channels.
ADS112C14_SYS_MON_CHANNEL_SHORT is the last channel rather than being in
logical order by address to keep the voltage channels together and in
case we find we need to add variants of this channel with different
voltage reference later.
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
A few other notes for review that didn't seem worth putting in the
commit message:
* I intentionally did not use bulk regmap because later we may need to
get the voltage of the avdd supply.
* I left some comments in the code where the code might look funny (e.g.
to reduce future diff) or does not exactly match the datasheet, in
which case later changes will address that.
v3 changes:
* Reordered some things to reudce future diffs.
* Fixed missing space in comment.
* Added device ID check.
* Added ADS112C14_SYS_MON_CHANNEL_BASE macro.
* Drop linux/mod_devicetable.h import.
v2 changes:
* Add some indent to register field macro defines.
* Drop ads112c14_internal_ref_uV array (use macros instead).
* Don't keep pointer to i2c client.
* Consistently check channel < 100 instead of >= 100 sometimes.
* Check return of i2c_get_match_data().
* Fix bug on checking -EREMOTEIO.
* Don't use &client->dev when we already have dev local variable.
* Make some changes to reduce diff in later patches.
---
MAINTAINERS | 1 +
drivers/iio/adc/Kconfig | 12 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ti-ads112c14.c | 566 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 580 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 3e114e0c9c5f..7077ed7c9efe 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26963,6 +26963,7 @@ M: David Lechner <dlechner@baylibre.com>
L: linux-iio@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml
+F: drivers/iio/adc/ti-ads112c14.c
TI ADS1018 ADC DRIVER
M: Kurt Borja <kuurtb@gmail.com>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index d1b198cb8a80..642c42b1fae7 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1798,6 +1798,18 @@ config TI_ADS1119
This driver can also be built as a module. If so, the module will be
called ti-ads1119.
+config TI_ADS112C14
+ tristate "Texas Instruments ADS112C14/ADS122C14"
+ depends on I2C
+ select REGMAP
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for Texas Instruments ADS112C14,
+ ADS122C14 ADC chips.
+
+ This driver can also be built as a module. If so, the module will be
+ called ti-ads112c14.
+
config TI_ADS124S08
tristate "Texas Instruments ADS124S08"
depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 0f90b75577ff..3778c7b0b8b5 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -154,6 +154,7 @@ obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o
obj-$(CONFIG_TI_ADS1018) += ti-ads1018.o
obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o
obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o
+obj-$(CONFIG_TI_ADS112C14) += ti-ads112c14.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-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
new file mode 100644
index 000000000000..4d2e7d37be82
--- /dev/null
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -0,0 +1,566 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IIO driver for Texas Instruments ADS112C14 and similar ADCs.
+ *
+ * Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com/
+ * Copyright (C) 2026 Baylibre Inc.
+ *
+ * Datasheet: https://www.ti.com/lit/ds/symlink/ads122c14.pdf
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sysfs.h>
+#include <linux/time64.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+#include <linux/units.h>
+
+/* Datasheet t_d(RST) - time to wait after reset before next I2C use. */
+#define ADS112C14_DELAY_RESET_US 500
+
+#define ADS112C14_CMD_RDATA 0x00
+#define ADS112C14_CMD_RREG 0x40
+#define ADS112C14_CMD_WREG 0x80
+
+#define ADS112C14_REG_DEVICE_ID 0x00
+#define ADS112C14_DEVICE_ID_BITS GENMASK(3, 0)
+
+#define ADS112C14_REG_REVISION_ID 0x01
+
+#define ADS112C14_REG_STATUS_MSB 0x02
+#define ADS112C14_STATUS_MSB_RESETN BIT(7)
+#define ADS112C14_STATUS_MSB_AVDD_UVN BIT(6)
+#define ADS112C14_STATUS_MSB_REF_UVN BIT(5)
+#define ADS112C14_STATUS_MSB_REG_MAP_CRC_FAULTN BIT(3)
+#define ADS112C14_STATUS_MSB_MEM_FAULTN BIT(2)
+#define ADS112C14_STATUS_MSB_REG_WRITE_FAULTN BIT(1)
+#define ADS112C14_STATUS_MSB_DRDY BIT(0)
+
+#define ADS112C14_REG_STATUS_LSB 0x03
+#define ADS112C14_STATUS_LSB_CONV_COUNT GENMASK(7, 4)
+#define ADS112C14_STATUS_LSB_GPIO3_DAT_IN BIT(3)
+#define ADS112C14_STATUS_LSB_GPIO2_DAT_IN BIT(2)
+#define ADS112C14_STATUS_LSB_GPIO1_DAT_IN BIT(1)
+#define ADS112C14_STATUS_LSB_GPIO0_DAT_IN BIT(0)
+
+#define ADS112C14_REG_CONVERSION_CTRL 0x04
+#define ADS112C14_CONVERSION_CTRL_RESET GENMASK(7, 2)
+#define ADS112C14_CONVERSION_CTRL_START BIT(1)
+#define ADS112C14_CONVERSION_CTRL_STOP BIT(0)
+
+#define ADS112C14_REG_DEVICE_CFG 0x05
+#define ADS112C14_DEVICE_CFG_PWDN BIT(7)
+#define ADS112C14_DEVICE_CFG_STBY_MODE BIT(6)
+#define ADS112C14_DEVICE_CFG_BOCS GENMASK(5, 4)
+#define ADS112C14_DEVICE_CFG_CLK_SEL BIT(3)
+#define ADS112C14_DEVICE_CFG_CONV_MODE BIT(2)
+#define ADS112C14_DEVICE_CFG_SPEED_MODE GENMASK(1, 0)
+
+#define ADS112C14_REG_DATA_RATE_CFG 0x06
+#define ADS112C14_DATA_RATE_CFG_DELAY GENMASK(7, 4)
+#define ADS112C14_DATA_RATE_CFG_GC_EN BIT(3)
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR GENMASK(2, 0)
+
+#define ADS112C14_REG_MUX_CFG 0x07
+#define ADS112C14_MUX_CFG_AINP GENMASK(7, 4)
+#define ADS112C14_MUX_CFG_AINN GENMASK(3, 0)
+
+#define ADS112C14_REG_GAIN_CFG 0x08
+#define ADS112C14_GAIN_CFG_SPARE BIT(7)
+#define ADS112C14_GAIN_CFG_SYS_MON GENMASK(6, 4)
+#define ADS112C14_GAIN_CFG_GAIN GENMASK(3, 0)
+
+#define ADS112C14_REG_REFERENCE_CFG 0x09
+#define ADS112C14_REFERENCE_CFG_REF_UV_EN BIT(7)
+#define ADS112C14_REFERENCE_CFG_REFP_BUF_EN BIT(5)
+#define ADS112C14_REFERENCE_CFG_REFN_BUF_EN BIT(4)
+#define ADS112C14_REFERENCE_CFG_REF_VAL BIT(2)
+#define ADS112C14_REFERENCE_CFG_REF_VAL_1_25V 0
+#define ADS112C14_REFERENCE_CFG_REF_VAL_2_5V 1
+#define ADS112C14_REFERENCE_CFG_REF_SEL GENMASK(1, 0)
+#define ADS112C14_REFERENCE_CFG_REF_SEL_INTERNAL 0
+#define ADS112C14_REFERENCE_CFG_REF_SEL_EXTERNAL 1
+#define ADS112C14_REFERENCE_CFG_REF_SEL_AVDD 2
+
+#define ADS112C14_REG_DIGITAL_CFG 0x0A
+#define ADS112C14_DIGITAL_CFG_REG_MAP_CRC_EN BIT(6)
+#define ADS112C14_DIGITAL_CFG_I2C_CRC_EN BIT(5)
+#define ADS112C14_DIGITAL_CFG_STATUS_EN BIT(4)
+#define ADS112C14_DIGITAL_CFG_FAULT_PIN_BEHAVIOR BIT(3)
+#define ADS112C14_DIGITAL_CFG_CODING BIT(1)
+
+#define ADS112C14_REG_GPIO_CFG 0x0B
+#define ADS112C14_GPIO_CFG_GPIO3_CFG GENMASK(7, 6)
+#define ADS112C14_GPIO_CFG_GPIO2_CFG GENMASK(5, 4)
+#define ADS112C14_GPIO_CFG_GPIO1_CFG GENMASK(3, 2)
+#define ADS112C14_GPIO_CFG_GPIO0_CFG GENMASK(1, 0)
+
+#define ADS112C14_REG_GPIO_DATA_OUTPUT 0x0C
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC BIT(7)
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO2_SRC BIT(6)
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_DAT_OUT BIT(3)
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO2_DAT_OUT BIT(2)
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO1_DAT_OUT BIT(1)
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO0_DAT_OUT BIT(0)
+
+#define ADS112C14_REG_IDAC_MAG_CFG 0x0D
+#define ADS112C14_IDAC_MAG_CFG_I2MAG GENMASK(7, 4)
+#define ADS112C14_IDAC_MAG_CFG_I1MAG GENMASK(3, 0)
+
+#define ADS112C14_REG_IDAC_MUX_CFG 0x0E
+#define ADS112C14_IDAC_MUX_CFG_IUNIT BIT(7)
+#define ADS112C14_IDAC_MUX_CFG_I2MUX GENMASK(6, 4)
+#define ADS112C14_IDAC_MUX_CFG_I1MUX GENMASK(2, 0)
+
+#define ADS112C14_REG_REG_MAP_CRC 0x0F
+
+#define ADS112C14_INT_REF0_mV 1250
+#define ADS112C14_INT_REF1_mV 2500
+
+struct ads112c14_chip_info {
+ const char *name;
+ u8 device_id;
+ u32 resolution_bits;
+};
+
+/* Fixed channels for system monitor measurements. */
+
+#define ADS112C14_SYS_MON_CHANNEL_BASE 100
+
+enum {
+ ADS112C14_SYS_MON_CHANNEL_TEMP = ADS112C14_SYS_MON_CHANNEL_BASE,
+ ADS112C14_SYS_MON_CHANNEL_EXT_REF,
+ ADS112C14_SYS_MON_CHANNEL_AVDD,
+ ADS112C14_SYS_MON_CHANNEL_DVDD,
+ ADS112C14_SYS_MON_CHANNEL_SHORT,
+};
+
+static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .indexed = 1,
+ .channel = ADS112C14_SYS_MON_CHANNEL_TEMP,
+ .address = 2,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
+ | BIT(IIO_CHAN_INFO_SCALE)
+ | BIT(IIO_CHAN_INFO_OFFSET),
+ },
+ {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = ADS112C14_SYS_MON_CHANNEL_EXT_REF,
+ .address = 3,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
+ | BIT(IIO_CHAN_INFO_SCALE),
+ },
+ {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = ADS112C14_SYS_MON_CHANNEL_AVDD,
+ .address = 4,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
+ | BIT(IIO_CHAN_INFO_SCALE),
+ },
+ {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = ADS112C14_SYS_MON_CHANNEL_DVDD,
+ .address = 5,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
+ | BIT(IIO_CHAN_INFO_SCALE),
+ },
+ {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = ADS112C14_SYS_MON_CHANNEL_SHORT,
+ .channel2 = ADS112C14_SYS_MON_CHANNEL_SHORT,
+ .differential = 1,
+ .address = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
+ | BIT(IIO_CHAN_INFO_SCALE),
+ },
+};
+
+struct ads112c14_data {
+ const struct ads112c14_chip_info *chip_info;
+ struct regmap *regmap;
+};
+
+static bool ads112c14_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ADS112C14_REG_DEVICE_ID:
+ case ADS112C14_REG_REVISION_ID:
+ case ADS112C14_REG_STATUS_LSB:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static bool ads112c14_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ADS112C14_REG_STATUS_MSB:
+ case ADS112C14_REG_STATUS_LSB:
+ case ADS112C14_REG_CONVERSION_CTRL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct reg_default ads112c14_reg_defaults[] = {
+ { ADS112C14_REG_DEVICE_CFG, 0 },
+ { ADS112C14_REG_DATA_RATE_CFG, 0 },
+ { ADS112C14_REG_MUX_CFG, 0 },
+ { ADS112C14_REG_GAIN_CFG, FIELD_PREP_CONST(ADS112C14_GAIN_CFG_GAIN, 1) },
+ { ADS112C14_REG_REFERENCE_CFG, 0 },
+ { ADS112C14_REG_DIGITAL_CFG, 0 },
+ { ADS112C14_REG_GPIO_CFG, 0 },
+ { ADS112C14_REG_GPIO_DATA_OUTPUT, 0 },
+ { ADS112C14_REG_IDAC_MAG_CFG, 0 },
+ { ADS112C14_REG_IDAC_MUX_CFG, FIELD_PREP_CONST(ADS112C14_IDAC_MUX_CFG_I2MUX, 1) },
+};
+
+static const struct regmap_config ads112c14_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .read_flag_mask = ADS112C14_CMD_RREG,
+ .write_flag_mask = ADS112C14_CMD_WREG,
+ .max_register = ADS112C14_REG_REG_MAP_CRC,
+ .writeable_reg = ads112c14_writeable_reg,
+ .volatile_reg = ads112c14_volatile_reg,
+ .reg_defaults = ads112c14_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(ads112c14_reg_defaults),
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
+ const struct iio_chan_spec *chan)
+{
+ int ret;
+
+ /* TODO: GAIN is useful for shorted PGA inputs. */
+ /* All SYS_MON channels use GAIN of 1 to keep it simple. */
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_GAIN_CFG,
+ ADS112C14_GAIN_CFG_SYS_MON |
+ ADS112C14_GAIN_CFG_GAIN,
+ FIELD_PREP(ADS112C14_GAIN_CFG_SYS_MON, chan->address) |
+ FIELD_PREP(ADS112C14_GAIN_CFG_GAIN, 1));
+ if (ret)
+ return ret;
+
+ /* All SYS_MON channels use signed data to keep it simple. */
+ ret = regmap_clear_bits(data->regmap, ADS112C14_REG_DIGITAL_CFG,
+ ADS112C14_DIGITAL_CFG_CODING);
+ if (ret)
+ return ret;
+
+ /*
+ * REVISIT: if we implement regulator support for the REFOUT pin, we
+ * might need to make this voltage match what is required by that. In
+ * that case, we could also adjust GAIN so that we still get the same
+ * range.
+ */
+ /*
+ * NB: SYS_MON channels ignore REF_SEL except for the shorted input
+ * channel, so we set it here to internal reference to be consistent.
+ * If we ever need to make a measurement of shorted input with other
+ * reference source, we could add additional channels for that.
+ */
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_REFERENCE_CFG,
+ ADS112C14_REFERENCE_CFG_REF_VAL |
+ ADS112C14_REFERENCE_CFG_REF_SEL,
+ FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_VAL,
+ ADS112C14_REFERENCE_CFG_REF_VAL_2_5V) |
+ FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_SEL,
+ ADS112C14_REFERENCE_CFG_REF_SEL_INTERNAL));
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ads112c14_single_conversion(struct ads112c14_data *data,
+ const struct iio_chan_spec *chan,
+ u8 *buf)
+{
+ struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap));
+ u32 reg_val;
+ int ret;
+
+ if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
+ /* Not implemented yet. */
+ return -EINVAL;
+ } else {
+ ret = ads112c14_prepare_sys_mon_channel(data, chan);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
+ ADS112C14_CONVERSION_CTRL_START);
+ if (ret)
+ return ret;
+
+ ret = regmap_read_poll_timeout(data->regmap,
+ ADS112C14_REG_STATUS_MSB, reg_val,
+ FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val),
+ 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC);
+ if (ret)
+ return ret;
+
+ return i2c_smbus_read_i2c_block_data(client, ADS112C14_CMD_RDATA,
+ BITS_TO_BYTES(data->chip_info->resolution_bits),
+ buf);
+}
+
+static int ads112c14_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ u32 vref_uV, fsr_bits;
+
+ /* Selecting V_REF source is not implemented yet. */
+ vref_uV = ADS112C14_INT_REF1_mV * (MICRO / MILLI);
+
+ if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
+ /* Not implemented yet. */
+ return -EINVAL;
+ } else {
+ /* All SYS_MON channels are using signed coding. */
+ fsr_bits = data->chip_info->resolution_bits - 1;
+ }
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW: {
+ u8 buf[3];
+ int ret;
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = ads112c14_single_conversion(data, chan, buf);
+ iio_device_release_direct(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ switch (data->chip_info->resolution_bits) {
+ case 16:
+ *val = get_unaligned_be16(buf);
+ break;
+ case 24:
+ *val = get_unaligned_be24(buf);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *val = sign_extend32(*val, fsr_bits);
+
+ return IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type == IIO_TEMP) {
+ /* TS_TC (typical) = 405 uV/°C */
+ *val = MILLI * vref_uV / 405;
+ *val2 = fsr_bits;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ }
+
+ *val = vref_uV / (MICRO / MILLI);
+ /*
+ * Last 3 SYS_MON channels (ext ref, AVDD, DVDD) need to be
+ * multiplied by 8 to account for internal attenuation of / 8.
+ */
+ *val2 = fsr_bits - (chan->address >= 3 ? 3 : 0);
+ return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_CHAN_INFO_OFFSET:
+ /* Only the temperature channel has an offset. */
+ if (chan->type != IIO_TEMP)
+ return -EINVAL;
+ /*
+ * Die temperature [°C] = 25°C + (Measured voltage – TS_Offset) / TS_TC
+ * TS_TC (typical) = 405 uV/°C
+ * TS_Offset (typical) = 119.5 mV
+ */
+ *val = div_s64((s64)(25 * 405 - 119500) * BIT(fsr_bits), vref_uV);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ads112c14_read_label(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, char *label)
+{
+ const char *label_source;
+
+ /* System monitor channels. */
+ switch (chan->channel) {
+ case ADS112C14_SYS_MON_CHANNEL_TEMP:
+ label_source = "Internal temperature sensor";
+ break;
+ case ADS112C14_SYS_MON_CHANNEL_EXT_REF:
+ label_source = "External reference";
+ break;
+ case ADS112C14_SYS_MON_CHANNEL_AVDD:
+ label_source = "AVDD";
+ break;
+ case ADS112C14_SYS_MON_CHANNEL_DVDD:
+ label_source = "DVDD";
+ break;
+ case ADS112C14_SYS_MON_CHANNEL_SHORT:
+ label_source = "Internal short (internal reference source)";
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return sysfs_emit(label, "%s\n", label_source);
+}
+
+static const struct iio_info ads112c14_info = {
+ .read_raw = ads112c14_read_raw,
+ .read_label = ads112c14_read_label,
+};
+
+static int ads112c14_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ const struct ads112c14_chip_info *info;
+ struct iio_dev *indio_dev;
+ struct ads112c14_data *data;
+ u32 reg_val;
+ int ret;
+
+ info = i2c_get_match_data(client);
+ if (!info)
+ return dev_err_probe(dev, -EINVAL, "missing match data\n");
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->chip_info = info;
+
+ ret = devm_regulator_get_enable(dev, "dvdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
+
+ ret = devm_regulator_get_enable(dev, "avdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
+
+ data->regmap = devm_regmap_init_i2c(client, &ads112c14_regmap_config);
+ if (IS_ERR(data->regmap))
+ return dev_err_probe(dev, PTR_ERR(data->regmap),
+ "failed to init regmap\n");
+
+ /* Write magic reset value (0x16) to ensure known state. */
+ ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
+ FIELD_PREP(ADS112C14_CONVERSION_CTRL_RESET, 0x16));
+ /*
+ * The reset may cause an -EREMOTEIO error because of failing to get the
+ * I2C ACK at the end of the message. The device still gets reset so it
+ * is safe to ignore this error.
+ */
+ if (ret == -EREMOTEIO)
+ ret = 0;
+ if (ret)
+ return ret;
+
+ fsleep(ADS112C14_DELAY_RESET_US);
+
+ ret = regmap_read(data->regmap, ADS112C14_REG_STATUS_MSB, ®_val);
+ if (ret)
+ return ret;
+
+ if (FIELD_GET(ADS112C14_STATUS_MSB_RESETN, reg_val))
+ return dev_err_probe(dev, -EIO, "reset failed\n");
+
+ /*
+ * Clear reset bit to prepare for next probe. And clear AVDD fault since
+ * that happens on every reset.
+ */
+ ret = regmap_write(data->regmap, ADS112C14_REG_STATUS_MSB,
+ ADS112C14_STATUS_MSB_RESETN |
+ ADS112C14_STATUS_MSB_AVDD_UVN);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(data->regmap, ADS112C14_REG_DEVICE_ID, ®_val);
+ if (ret)
+ return ret;
+
+ if (FIELD_GET(ADS112C14_DEVICE_ID_BITS, reg_val) != info->device_id)
+ dev_info(dev, "device ID mismatch, expected 0xX%X, got 0x%lX\n",
+ info->device_id,
+ FIELD_GET(ADS112C14_DEVICE_ID_BITS, reg_val));
+
+ /* Place in single-shot conversion mode to make ready for raw read. */
+ ret = regmap_set_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
+ ADS112C14_DEVICE_CFG_CONV_MODE);
+ if (ret)
+ return ret;
+
+ indio_dev->name = info->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = ads112c14_sys_mon_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ads112c14_sys_mon_channels);
+ indio_dev->info = &ads112c14_info;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct ads112c14_chip_info ads112c14_chip_info = {
+ .name = "ads112c14",
+ .device_id = 0xE,
+ .resolution_bits = 16,
+};
+
+static const struct ads112c14_chip_info ads122c14_chip_info = {
+ .name = "ads122c14",
+ .device_id = 0xF,
+ .resolution_bits = 24,
+};
+
+static const struct of_device_id ads112c14_of_match[] = {
+ { .compatible = "ti,ads112c14", .data = &ads112c14_chip_info },
+ { .compatible = "ti,ads122c14", .data = &ads122c14_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ads112c14_of_match);
+
+static const struct i2c_device_id ads112c14_id[] = {
+ { .name = "ads112c14", .driver_data = (kernel_ulong_t)&ads112c14_chip_info },
+ { .name = "ads122c14", .driver_data = (kernel_ulong_t)&ads122c14_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ads112c14_id);
+
+static struct i2c_driver ads112c14_driver = {
+ .driver = {
+ .name = "ads112c14",
+ .of_match_table = ads112c14_of_match,
+ },
+ .probe = ads112c14_probe,
+ .id_table = ads112c14_id,
+};
+module_i2c_driver(ads112c14_driver);
+
+MODULE_AUTHOR("David Lechner (TI) <dlechner@baylibre.com>");
+MODULE_DESCRIPTION("TI ADS112C14 I2C ADC driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v3 7/8] iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel
2026-07-10 22:50 [PATCH v3 0/8] iio: adc: new ti-ads112c14 driver David Lechner
` (5 preceding siblings ...)
2026-07-10 22:50 ` [PATCH v3 6/8] iio: adc: add ti-ads112c14 driver David Lechner (TI)
@ 2026-07-10 22:50 ` David Lechner (TI)
2026-07-10 23:04 ` sashiko-bot
2026-07-11 9:11 ` Andy Shevchenko
2026-07-10 22:50 ` [PATCH v3 8/8] iio: adc: ti-ads112c14: add measurement channel support David Lechner (TI)
7 siblings, 2 replies; 17+ messages in thread
From: David Lechner (TI) @ 2026-07-10 22:50 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: Chris Hall, Patrick Edwards, Kurt Borja, Nguyen Minh Tien,
linux-iio, devicetree, linux-kernel, David Lechner
Implement support for the programmable gain amplifier on the internal
short SYS_MON channel. This channel is used for calibration, so it is
useful to be able to set the PGA to the same gain as the external
channels. The gain setting is implemented via the `_scale` attribute.
In the future, we may want to support different reference voltages for
this channel, so the scale_available table is populated during probe
rather than being a static table.
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
v3 changes:
* Use IIO_VAL_DECIMAL64_PICO for scale.
v2 changes:
* Make some changes to reduce diff in next patch.
* Add some local variables to reduce line wrap.
---
drivers/iio/adc/ti-ads112c14.c | 128 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 125 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index 4d2e7d37be82..2ce4411a0d86 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -126,6 +126,15 @@
#define ADS112C14_INT_REF0_mV 1250
#define ADS112C14_INT_REF1_mV 2500
+/*
+ * Available gains as tenths (e.g. value 5 == 0.5 gain). Indexes correspond to
+ * ADS112C14_GAIN_CFG_GAIN values.
+ */
+static const u32 ads112c14_pga_gains_x10[] = {
+ 5, 10, 20, 40, 50, 80, 100, 160,
+ 200, 320, 500, 640, 1000, 1280, 2000, 2560,
+};
+
struct ads112c14_chip_info {
const char *name;
u8 device_id;
@@ -187,12 +196,15 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
.address = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
| BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
},
};
struct ads112c14_data {
const struct ads112c14_chip_info *chip_info;
struct regmap *regmap;
+ u8 sys_mon_chan_short_gain_val;
+ s64 sys_mon_chan_short_scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)];
};
static bool ads112c14_writeable_reg(struct device *dev, unsigned int reg)
@@ -248,15 +260,21 @@ static const struct regmap_config ads112c14_regmap_config = {
static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
const struct iio_chan_spec *chan)
{
+ u32 gain_val;
int ret;
- /* TODO: GAIN is useful for shorted PGA inputs. */
- /* All SYS_MON channels use GAIN of 1 to keep it simple. */
+ /*
+ * All SYS_MON channels use GAIN of 1 to keep it simple. Other than
+ * the internal short channel, where it is useful in practice.
+ */
+ gain_val = chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT ?
+ data->sys_mon_chan_short_gain_val : 1;
+
ret = regmap_update_bits(data->regmap, ADS112C14_REG_GAIN_CFG,
ADS112C14_GAIN_CFG_SYS_MON |
ADS112C14_GAIN_CFG_GAIN,
FIELD_PREP(ADS112C14_GAIN_CFG_SYS_MON, chan->address) |
- FIELD_PREP(ADS112C14_GAIN_CFG_GAIN, 1));
+ FIELD_PREP(ADS112C14_GAIN_CFG_GAIN, gain_val));
if (ret)
return ret;
@@ -331,6 +349,7 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
{
struct ads112c14_data *data = iio_priv(indio_dev);
u32 vref_uV, fsr_bits;
+ s64 scale_avail;
/* Selecting V_REF source is not implemented yet. */
vref_uV = ADS112C14_INT_REF1_mV * (MICRO / MILLI);
@@ -379,6 +398,15 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_FRACTIONAL_LOG2;
}
+ if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
+ u8 idx = data->sys_mon_chan_short_gain_val;
+
+ scale_avail = data->sys_mon_chan_short_scale_available[idx];
+ iio_val_s64_decompose(scale_avail, val, val2);
+
+ return IIO_VAL_DECIMAL64_PICO;
+ }
+
*val = vref_uV / (MICRO / MILLI);
/*
* Last 3 SYS_MON channels (ext ref, AVDD, DVDD) need to be
@@ -402,6 +430,69 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
}
}
+static int ads112c14_read_avail(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan, const int **vals,
+ int *type, int *length, long mask)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+
+ if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
+ *vals = (const int *)data->sys_mon_chan_short_scale_available;
+ *length = 2 * ARRAY_SIZE(data->sys_mon_chan_short_scale_available);
+ *type = IIO_VAL_DECIMAL64_PICO;
+ return IIO_AVAIL_LIST;
+ }
+
+ return -EINVAL;
+}
+
+static int ads112c14_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ const s64 *scale_avail;
+ u8 *gain_val;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE: {
+ if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
+ scale_avail = data->sys_mon_chan_short_scale_available;
+ gain_val = &data->sys_mon_chan_short_gain_val;
+ } else {
+ return -EINVAL;
+ }
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ for (u32 i = 0; i < ARRAY_SIZE(ads112c14_pga_gains_x10); i++) {
+ if (iio_val_s64_compose(val, val2) == scale_avail[i]) {
+ *gain_val = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ads112c14_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_DECIMAL64_PICO;
+ default:
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+}
+
static int ads112c14_read_label(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, char *label)
{
@@ -433,9 +524,35 @@ static int ads112c14_read_label(struct iio_dev *indio_dev,
static const struct iio_info ads112c14_info = {
.read_raw = ads112c14_read_raw,
+ .read_avail = ads112c14_read_avail,
+ .write_raw = ads112c14_write_raw,
+ .write_raw_get_fmt = ads112c14_write_raw_get_fmt,
.read_label = ads112c14_read_label,
};
+static void ads112c14_populate_scale_available(s64 *scale_avail, u32 full_scale,
+ u32 fsr_bits)
+{
+ for (u32 i = 0; i < ARRAY_SIZE(ads112c14_pga_gains_x10); i++) {
+ u64 gain_x10 = ads112c14_pga_gains_x10[i];
+
+ scale_avail[i] = div64_u64((u64)PICO * 10U * full_scale,
+ gain_x10 * BIT(fsr_bits));
+ }
+}
+
+static void ads112c14_populate_tables(struct ads112c14_data *data)
+{
+ u32 full_scale, fsr_bits;
+
+ /* For now, assuming all sys_mon channels are using 2.5V reference. */
+ full_scale = ADS112C14_INT_REF1_mV;
+ fsr_bits = data->chip_info->resolution_bits - 1;
+
+ ads112c14_populate_scale_available(data->sys_mon_chan_short_scale_available,
+ full_scale, fsr_bits);
+}
+
static int ads112c14_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -491,6 +608,9 @@ static int ads112c14_probe(struct i2c_client *client)
if (FIELD_GET(ADS112C14_STATUS_MSB_RESETN, reg_val))
return dev_err_probe(dev, -EIO, "reset failed\n");
+ /* Default gain after reset is 1. */
+ data->sys_mon_chan_short_gain_val = 1;
+
/*
* Clear reset bit to prepare for next probe. And clear AVDD fault since
* that happens on every reset.
@@ -516,6 +636,8 @@ static int ads112c14_probe(struct i2c_client *client)
if (ret)
return ret;
+ ads112c14_populate_tables(data);
+
indio_dev->name = info->name;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = ads112c14_sys_mon_channels;
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v3 8/8] iio: adc: ti-ads112c14: add measurement channel support
2026-07-10 22:50 [PATCH v3 0/8] iio: adc: new ti-ads112c14 driver David Lechner
` (6 preceding siblings ...)
2026-07-10 22:50 ` [PATCH v3 7/8] iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel David Lechner (TI)
@ 2026-07-10 22:50 ` David Lechner (TI)
2026-07-10 23:08 ` sashiko-bot
2026-07-11 13:00 ` Andy Shevchenko
7 siblings, 2 replies; 17+ messages in thread
From: David Lechner (TI) @ 2026-07-10 22:50 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: Chris Hall, Patrick Edwards, Kurt Borja, Nguyen Minh Tien,
linux-iio, devicetree, linux-kernel, David Lechner
Add support for parsing devicetree properties for measurement channels
and doing direct reads on these.
There are quite a lot of conditions that have to be met for each
measurement to be made, so quite a bit of state and algorithms are
required to handle it.
Channels are created dynamically since the number of possibilities is
unreasonably large.
Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
v3 changes:
* Checked error when getting "label" property.
* Removed call of fwnode_device_is_available().
* Used IIO_VAL_DECIMAL64_PICO for scale.
* Added switch in ads112c14_read_avail() to reduce future diff.
v2 changes:
* Adapted for changes in DT bindings.
* Fixed bug in IDAC current register value calculation.
* Fix uninitialized variable bug.
* Fix bug in data->num_measurements calculation.
* Use IIO_RESISTANCE instead of IIO_VOLTAGE when external reference is
used and it is a resistor rather than a voltage source.
* Fix bug with negative input mux selection on single-ended measurements.
* Fixed return checks of devm_regulator_get_enable_read_voltage().
---
drivers/iio/adc/ti-ads112c14.c | 551 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 533 insertions(+), 18 deletions(-)
diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index 2ce4411a0d86..a310abd69d8f 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -9,11 +9,14 @@
*/
#include <linux/bitfield.h>
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
+#include <linux/device/devres.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/math64.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/regmap.h>
@@ -61,6 +64,10 @@
#define ADS112C14_DEVICE_CFG_PWDN BIT(7)
#define ADS112C14_DEVICE_CFG_STBY_MODE BIT(6)
#define ADS112C14_DEVICE_CFG_BOCS GENMASK(5, 4)
+#define ADS112C14_DEVICE_CFG_BOCS_DISABLED 0
+#define ADS112C14_DEVICE_CFG_BOCS_200_nA 1
+#define ADS112C14_DEVICE_CFG_BOCS_1_uA 2
+#define ADS112C14_DEVICE_CFG_BOCS_10_uA 3
#define ADS112C14_DEVICE_CFG_CLK_SEL BIT(3)
#define ADS112C14_DEVICE_CFG_CONV_MODE BIT(2)
#define ADS112C14_DEVICE_CFG_SPEED_MODE GENMASK(1, 0)
@@ -73,6 +80,7 @@
#define ADS112C14_REG_MUX_CFG 0x07
#define ADS112C14_MUX_CFG_AINP GENMASK(7, 4)
#define ADS112C14_MUX_CFG_AINN GENMASK(3, 0)
+#define ADS112C14_MUX_CFG_AIN_GND 8
#define ADS112C14_REG_GAIN_CFG 0x08
#define ADS112C14_GAIN_CFG_SPARE BIT(7)
@@ -126,6 +134,20 @@
#define ADS112C14_INT_REF0_mV 1250
#define ADS112C14_INT_REF1_mV 2500
+enum {
+ ADS112C14_VREF_SOURCE_INTERNAL_2_5V,
+ ADS112C14_VREF_SOURCE_INTERNAL_1_25V,
+ ADS112C14_VREF_SOURCE_EXTERNAL,
+ ADS112C14_VREF_SOURCE_AVDD,
+};
+
+static const char * const ads112c14_vref_source_names[] = {
+ [ADS112C14_VREF_SOURCE_INTERNAL_2_5V] = "internal-2.5v",
+ [ADS112C14_VREF_SOURCE_INTERNAL_1_25V] = "internal-1.25v",
+ [ADS112C14_VREF_SOURCE_EXTERNAL] = "external",
+ [ADS112C14_VREF_SOURCE_AVDD] = "avdd",
+};
+
/*
* Available gains as tenths (e.g. value 5 == 0.5 gain). Indexes correspond to
* ADS112C14_GAIN_CFG_GAIN values.
@@ -200,9 +222,32 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
},
};
+struct ads112c14_measurement {
+ const char *label;
+ u32 vref_source;
+ u8 iunit;
+ u8 idac1_mag;
+ u8 idac2_mag;
+ u8 idac1_mux;
+ u8 idac2_mux;
+ u8 iadc_count;
+ u8 gain_val;
+ u8 burnout;
+ bool global_chop;
+ bool bipolar;
+ s64 scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)];
+};
+
struct ads112c14_data {
const struct ads112c14_chip_info *chip_info;
struct regmap *regmap;
+ u32 avdd_uV;
+ u32 ext_ref_uV;
+ bool refp_is_avdd;
+ bool refn_is_gnd;
+ u32 ext_ref_ohms;
+ struct ads112c14_measurement *measurements;
+ u32 num_measurements;
u8 sys_mon_chan_short_gain_val;
s64 sys_mon_chan_short_scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)];
};
@@ -257,12 +302,113 @@ static const struct regmap_config ads112c14_regmap_config = {
.cache_type = REGCACHE_MAPLE,
};
+static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data,
+ const struct iio_chan_spec *chan)
+{
+ struct ads112c14_measurement *measurement = &data->measurements[chan->scan_index];
+ u32 refp_buf_en, refn_buf_en, ref_val, ref_sel;
+ int ret;
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_MUX_CFG,
+ ADS112C14_MUX_CFG_AINP | ADS112C14_MUX_CFG_AINN,
+ FIELD_PREP(ADS112C14_MUX_CFG_AINP, chan->channel) |
+ FIELD_PREP(ADS112C14_MUX_CFG_AINN, chan->channel2));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_DIGITAL_CFG,
+ ADS112C14_DIGITAL_CFG_CODING,
+ FIELD_PREP(ADS112C14_DIGITAL_CFG_CODING,
+ measurement->bipolar ? 0 : 1));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_GAIN_CFG,
+ ADS112C14_GAIN_CFG_SYS_MON |
+ ADS112C14_GAIN_CFG_GAIN,
+ FIELD_PREP(ADS112C14_GAIN_CFG_SYS_MON, 0) |
+ FIELD_PREP(ADS112C14_GAIN_CFG_GAIN,
+ measurement->gain_val));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_IDAC_MAG_CFG,
+ ADS112C14_IDAC_MAG_CFG_I2MAG |
+ ADS112C14_IDAC_MAG_CFG_I1MAG,
+ FIELD_PREP(ADS112C14_IDAC_MAG_CFG_I2MAG,
+ measurement->idac2_mag) |
+ FIELD_PREP(ADS112C14_IDAC_MAG_CFG_I1MAG,
+ measurement->idac1_mag));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_IDAC_MUX_CFG,
+ ADS112C14_IDAC_MUX_CFG_IUNIT |
+ ADS112C14_IDAC_MUX_CFG_I2MUX |
+ ADS112C14_IDAC_MUX_CFG_I1MUX,
+ FIELD_PREP(ADS112C14_IDAC_MUX_CFG_IUNIT,
+ measurement->iunit) |
+ FIELD_PREP(ADS112C14_IDAC_MUX_CFG_I2MUX,
+ measurement->idac2_mux) |
+ FIELD_PREP(ADS112C14_IDAC_MUX_CFG_I1MUX,
+ measurement->idac1_mux));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG,
+ ADS112C14_DATA_RATE_CFG_GC_EN,
+ FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN,
+ measurement->global_chop));
+ if (ret)
+ return ret;
+
+ refp_buf_en = !data->refp_is_avdd &&
+ measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL;
+ refn_buf_en = !data->refn_is_gnd &&
+ measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL;
+
+ ref_val = measurement->vref_source == ADS112C14_VREF_SOURCE_INTERNAL_2_5V ?
+ ADS112C14_REFERENCE_CFG_REF_VAL_2_5V :
+ ADS112C14_REFERENCE_CFG_REF_VAL_1_25V;
+
+ switch (measurement->vref_source) {
+ case ADS112C14_VREF_SOURCE_AVDD:
+ ref_sel = ADS112C14_REFERENCE_CFG_REF_SEL_AVDD;
+ break;
+ case ADS112C14_VREF_SOURCE_EXTERNAL:
+ ref_sel = ADS112C14_REFERENCE_CFG_REF_SEL_EXTERNAL;
+ break;
+ default:
+ ref_sel = ADS112C14_REFERENCE_CFG_REF_SEL_INTERNAL;
+ break;
+ }
+
+ return regmap_update_bits(data->regmap, ADS112C14_REG_REFERENCE_CFG,
+ ADS112C14_REFERENCE_CFG_REFP_BUF_EN |
+ ADS112C14_REFERENCE_CFG_REFN_BUF_EN |
+ ADS112C14_REFERENCE_CFG_REF_VAL |
+ ADS112C14_REFERENCE_CFG_REF_SEL,
+ FIELD_PREP(ADS112C14_REFERENCE_CFG_REFP_BUF_EN,
+ refp_buf_en) |
+ FIELD_PREP(ADS112C14_REFERENCE_CFG_REFN_BUF_EN,
+ refn_buf_en) |
+ FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_VAL,
+ ref_val) |
+ FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_SEL,
+ ref_sel));
+}
+
static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
const struct iio_chan_spec *chan)
{
u32 gain_val;
int ret;
+ /*
+ * NB: IDAC registers are left as-is in case they are generating current
+ * needed for the external reference measurement.
+ */
+
/*
* All SYS_MON channels use GAIN of 1 to keep it simple. Other than
* the internal short channel, where it is useful in practice.
@@ -318,8 +464,9 @@ static int ads112c14_single_conversion(struct ads112c14_data *data,
int ret;
if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
- /* Not implemented yet. */
- return -EINVAL;
+ ret = ads112c14_prepare_measurement_channel(data, chan);
+ if (ret)
+ return ret;
} else {
ret = ads112c14_prepare_sys_mon_channel(data, chan);
if (ret)
@@ -348,6 +495,7 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
int *val2, long mask)
{
struct ads112c14_data *data = iio_priv(indio_dev);
+ struct ads112c14_measurement *measurement = NULL;
u32 vref_uV, fsr_bits;
s64 scale_avail;
@@ -355,8 +503,8 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
vref_uV = ADS112C14_INT_REF1_mV * (MICRO / MILLI);
if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
- /* Not implemented yet. */
- return -EINVAL;
+ measurement = &data->measurements[chan->scan_index];
+ fsr_bits = data->chip_info->resolution_bits - measurement->bipolar;
} else {
/* All SYS_MON channels are using signed coding. */
fsr_bits = data->chip_info->resolution_bits - 1;
@@ -386,7 +534,8 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
return -EINVAL;
}
- *val = sign_extend32(*val, fsr_bits);
+ if (!measurement || measurement->bipolar)
+ *val = sign_extend32(*val, fsr_bits);
return IIO_VAL_INT;
}
@@ -398,6 +547,13 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_FRACTIONAL_LOG2;
}
+ if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
+ scale_avail = measurement->scale_available[measurement->gain_val];
+ iio_val_s64_decompose(scale_avail, val, val2);
+
+ return IIO_VAL_DECIMAL64_PICO;
+ }
+
if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
u8 idx = data->sys_mon_chan_short_gain_val;
@@ -436,14 +592,29 @@ static int ads112c14_read_avail(struct iio_dev *indio_dev,
{
struct ads112c14_data *data = iio_priv(indio_dev);
- if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
- *vals = (const int *)data->sys_mon_chan_short_scale_available;
- *length = 2 * ARRAY_SIZE(data->sys_mon_chan_short_scale_available);
- *type = IIO_VAL_DECIMAL64_PICO;
- return IIO_AVAIL_LIST;
- }
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
+ struct ads112c14_measurement *measurement;
+
+ measurement = &data->measurements[chan->scan_index];
+ *vals = (const int *)measurement->scale_available;
+ *length = 2 * ARRAY_SIZE(measurement->scale_available);
+ *type = IIO_VAL_DECIMAL64_PICO;
+ return IIO_AVAIL_LIST;
+ }
+
+ if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
+ *vals = (const int *)data->sys_mon_chan_short_scale_available;
+ *length = 2 * ARRAY_SIZE(data->sys_mon_chan_short_scale_available);
+ *type = IIO_VAL_DECIMAL64_PICO;
+ return IIO_AVAIL_LIST;
+ }
- return -EINVAL;
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
}
static int ads112c14_write_raw(struct iio_dev *indio_dev,
@@ -456,7 +627,13 @@ static int ads112c14_write_raw(struct iio_dev *indio_dev,
switch (mask) {
case IIO_CHAN_INFO_SCALE: {
- if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
+ if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
+ struct ads112c14_measurement *measurement;
+
+ measurement = &data->measurements[chan->scan_index];
+ scale_avail = measurement->scale_available;
+ gain_val = &measurement->gain_val;
+ } else if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
scale_avail = data->sys_mon_chan_short_scale_available;
gain_val = &data->sys_mon_chan_short_gain_val;
} else {
@@ -496,8 +673,21 @@ static int ads112c14_write_raw_get_fmt(struct iio_dev *indio_dev,
static int ads112c14_read_label(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, char *label)
{
+ struct ads112c14_data *data = iio_priv(indio_dev);
const char *label_source;
+ /* measurement channels */
+ if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
+ struct ads112c14_measurement *measurement;
+
+ measurement = &data->measurements[chan->scan_index];
+
+ if (!measurement->label)
+ return -EINVAL;
+
+ return sysfs_emit(label, "%s\n", measurement->label);
+ }
+
/* System monitor channels. */
switch (chan->channel) {
case ADS112C14_SYS_MON_CHANNEL_TEMP:
@@ -530,6 +720,240 @@ static const struct iio_info ads112c14_info = {
.read_label = ads112c14_read_label,
};
+static int ads112c14_populate_idac_mag(u32 current_nA, u8 *idac_mag)
+{
+ u32 current_uA = current_nA / (NANO / MICRO);
+
+ /* Convert microamps to IMAG bits */
+ if (current_uA == 1)
+ *idac_mag = 1;
+ else if (in_range(current_uA, 10, 100) && current_uA % 10 == 0)
+ *idac_mag = current_uA / 10 + 1;
+ else
+ return dev_err_probe(NULL, -EINVAL,
+ "invalid excitation-current-nanoamp value\n");
+
+ return 0;
+}
+
+static int ads112c14_parse_channels(struct iio_dev *indio_dev,
+ bool *need_avdd_ref, bool *need_ext_ref)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ struct device *dev = indio_dev->dev.parent;
+ struct iio_chan_spec *channels;
+ u32 num_child_nodes, i, pair[2];
+ int ret;
+
+ *need_avdd_ref = false;
+ *need_ext_ref = false;
+
+ num_child_nodes = device_get_named_child_node_count(dev, "channel");
+
+ data->measurements = devm_kcalloc(dev, num_child_nodes,
+ sizeof(*data->measurements), GFP_KERNEL);
+ if (!data->measurements)
+ return -ENOMEM;
+
+ channels = devm_kcalloc(dev, num_child_nodes +
+ ARRAY_SIZE(ads112c14_sys_mon_channels),
+ sizeof(*channels), GFP_KERNEL);
+ if (!channels)
+ return -ENOMEM;
+
+ i = 0;
+ device_for_each_named_child_node_scoped(dev, child, "channel") {
+ struct ads112c14_measurement *measurement = &data->measurements[i];
+ struct iio_chan_spec *spec = &channels[i];
+
+ spec->indexed = 1;
+ spec->scan_index = i;
+ measurement->gain_val = 1;
+
+ if (fwnode_property_present(child, "label")) {
+ ret = fwnode_property_read_string(child, "label", &measurement->label);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read label property\n");
+ }
+
+ if (fwnode_property_present(child, "single-channel")) {
+ ret = fwnode_property_read_u32(child, "single-channel",
+ &pair[0]);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read single-channel property\n");
+
+ if (pair[0] >= 8)
+ return dev_err_probe(dev, -EINVAL,
+ "single-channel value must be between 0 and 7\n");
+
+ spec->channel = pair[0];
+ /*
+ * NB: channel2 is unused by iio core code in this case.
+ * Let's us avoid special case for negative input mux
+ * for single-ended channels when taking measurements.
+ */
+ spec->channel2 = ADS112C14_MUX_CFG_AIN_GND;
+ } else if (fwnode_property_present(child, "diff-channels")) {
+ ret = fwnode_property_read_u32_array(child, "diff-channels",
+ pair, ARRAY_SIZE(pair));
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read diff-channels property\n");
+
+ if (pair[0] >= 8 || pair[1] >= 8)
+ return dev_err_probe(dev, -EINVAL,
+ "diff-channels values must be between 0 and 7\n");
+
+ spec->differential = 1;
+ spec->channel = pair[0];
+ spec->channel2 = pair[1];
+ } else {
+ return dev_err_probe(dev, -EINVAL,
+ "channel node missing channel type property\n");
+ }
+
+ if (fwnode_property_present(child, "excitation-channels")) {
+ ret = fwnode_property_count_u32(child, "excitation-channels");
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "failed to read excitation-channels property\n");
+
+ if (ret < 1 || ret > 2)
+ return dev_err_probe(dev, -EINVAL,
+ "excitation-channels property must have 1 or 2 values\n");
+
+ measurement->iadc_count = ret;
+ pair[1] = 0;
+
+ ret = fwnode_property_read_u32_array(child, "excitation-channels",
+ pair, measurement->iadc_count);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read excitation-channels property\n");
+
+ if (pair[0] >= 8 || pair[1] >= 8)
+ return dev_err_probe(dev, -EINVAL,
+ "excitation-channels values must be between 0 and 7\n");
+
+ measurement->idac1_mux = pair[0];
+ measurement->idac2_mux = measurement->iadc_count > 1 ? pair[1] : 0;
+
+ ret = fwnode_property_read_u32_array(child, "excitation-current-nanoamp",
+ pair, measurement->iadc_count);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read excitation-current-nanoamp property\n");
+
+ if (pair[0] <= 100000 && (measurement->iadc_count == 1 || pair[1] <= 100000)) {
+ /*
+ * If both values are 100uA or less, then we can
+ * use IUNIT = 1uA for better precision.
+ */
+ ret = ads112c14_populate_idac_mag(pair[0],
+ &measurement->idac1_mag);
+ if (ret)
+ return ret;
+
+ if (measurement->iadc_count > 1) {
+ ret = ads112c14_populate_idac_mag(pair[1],
+ &measurement->idac2_mag);
+ if (ret)
+ return ret;
+ }
+ } else {
+ /*
+ * Otherwise, IUINT is 10uA (flag set) and so
+ * IxMAG is 1/10 of the actual current.
+ */
+ measurement->iunit = 1;
+
+ ret = ads112c14_populate_idac_mag(pair[0] / 10,
+ &measurement->idac1_mag);
+ if (ret)
+ return ret;
+
+ if (measurement->iadc_count > 1) {
+ ret = ads112c14_populate_idac_mag(pair[1] / 10,
+ &measurement->idac2_mag);
+ if (ret)
+ return ret;
+ }
+ }
+ }
+
+ measurement->global_chop = fwnode_property_read_bool(child,
+ "input-chopping");
+
+ if (fwnode_property_present(child, "burn-out-current-nanoamp")) {
+ u32 burnout_nA;
+
+ ret = fwnode_property_read_u32(child, "burn-out-current-nanoamp",
+ &burnout_nA);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read burn-out-current-nanoamp property\n");
+
+ switch (burnout_nA) {
+ case 200:
+ measurement->burnout = ADS112C14_DEVICE_CFG_BOCS_200_nA;
+ break;
+ case 1000:
+ measurement->burnout = ADS112C14_DEVICE_CFG_BOCS_1_uA;
+ break;
+ case 10000:
+ measurement->burnout = ADS112C14_DEVICE_CFG_BOCS_10_uA;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "invalid burn-out-current-nanoamp value\n");
+ }
+ }
+
+ measurement->bipolar = fwnode_property_read_bool(child, "bipolar");
+
+ if (fwnode_property_present(child, "reference-sources")) {
+ ret = fwnode_property_match_property_string(child,
+ "reference-sources", ads112c14_vref_source_names,
+ ARRAY_SIZE(ads112c14_vref_source_names));
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "invalid reference-sources value\n");
+
+ measurement->vref_source = ret;
+ }
+
+ if (measurement->vref_source == ADS112C14_VREF_SOURCE_AVDD)
+ *need_avdd_ref = true;
+ if (measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL)
+ *need_ext_ref = true;
+
+ spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE);
+ spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE);
+
+ /*
+ * If reference source is resistor rather than voltage supply,
+ * then the measurement is effectively a resistance measurement.
+ */
+ spec->type = (measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL &&
+ data->ext_ref_ohms) ? IIO_RESISTANCE : IIO_VOLTAGE;
+
+ if (spec->type == IIO_RESISTANCE)
+ spec->differential = 0;
+
+ i++;
+ }
+
+ data->num_measurements = i;
+ memcpy(channels + i, ads112c14_sys_mon_channels, sizeof(ads112c14_sys_mon_channels));
+
+ indio_dev->channels = channels;
+ indio_dev->num_channels = i + ARRAY_SIZE(ads112c14_sys_mon_channels);
+
+ return 0;
+}
+
static void ads112c14_populate_scale_available(s64 *scale_avail, u32 full_scale,
u32 fsr_bits)
{
@@ -545,6 +969,33 @@ static void ads112c14_populate_tables(struct ads112c14_data *data)
{
u32 full_scale, fsr_bits;
+ for (u32 i = 0; i < data->num_measurements; i++) {
+ struct ads112c14_measurement *measurement = &data->measurements[i];
+
+ switch (measurement->vref_source) {
+ case ADS112C14_VREF_SOURCE_EXTERNAL:
+ if (data->ext_ref_ohms)
+ full_scale = data->ext_ref_ohms;
+ else
+ full_scale = data->ext_ref_uV / (MICRO / MILLI);
+ break;
+ case ADS112C14_VREF_SOURCE_AVDD:
+ full_scale = data->avdd_uV / (MICRO / MILLI);
+ break;
+ case ADS112C14_VREF_SOURCE_INTERNAL_1_25V:
+ full_scale = ADS112C14_INT_REF0_mV;
+ break;
+ default:
+ full_scale = ADS112C14_INT_REF1_mV;
+ break;
+ }
+
+ fsr_bits = data->chip_info->resolution_bits - measurement->bipolar;
+
+ ads112c14_populate_scale_available(measurement->scale_available,
+ full_scale, fsr_bits);
+ }
+
/* For now, assuming all sys_mon channels are using 2.5V reference. */
full_scale = ADS112C14_INT_REF1_mV;
fsr_bits = data->chip_info->resolution_bits - 1;
@@ -559,6 +1010,9 @@ static int ads112c14_probe(struct i2c_client *client)
const struct ads112c14_chip_info *info;
struct iio_dev *indio_dev;
struct ads112c14_data *data;
+ bool need_avdd_ref, need_ext_ref;
+ u32 refp_uV = 0;
+ u32 refn_uV = 0;
u32 reg_val;
int ret;
@@ -573,13 +1027,76 @@ static int ads112c14_probe(struct i2c_client *client)
data = iio_priv(indio_dev);
data->chip_info = info;
+ if (device_property_present(dev, "ti,refp-refn-resistor-ohms")) {
+ ret = device_property_read_u32(dev, "ti,refp-refn-resistor-ohms",
+ &data->ext_ref_ohms);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read ti,refp-refn-resistor-ohms property\n");
+ }
+
+ ret = ads112c14_parse_channels(indio_dev, &need_avdd_ref, &need_ext_ref);
+ if (ret)
+ return ret;
+
ret = devm_regulator_get_enable(dev, "dvdd");
if (ret)
return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
- ret = devm_regulator_get_enable(dev, "avdd");
- if (ret)
- return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
+ if (need_avdd_ref) {
+ ret = devm_regulator_get_enable_read_voltage(dev, "avdd");
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to get avdd voltage\n");
+
+ data->avdd_uV = ret;
+ } else {
+ ret = devm_regulator_get_enable(dev, "avdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
+ }
+
+ if (device_property_present(dev, "refp-supply")) {
+ ret = devm_regulator_get_enable_read_voltage(dev, "refp");
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to get refp voltage\n");
+
+ refp_uV = ret;
+
+ struct fwnode_handle *refp_fwnode __free(fwnode_handle) =
+ fwnode_find_reference(dev->fwnode, "refp-supply", 0);
+ if (IS_ERR(refp_fwnode))
+ return dev_err_probe(dev, PTR_ERR(refp_fwnode),
+ "failed to get refp fwnode\n");
+
+ struct fwnode_handle *avdd_fwnode __free(fwnode_handle) =
+ fwnode_find_reference(dev->fwnode, "avdd-supply", 0);
+ if (IS_ERR(avdd_fwnode))
+ return dev_err_probe(dev, PTR_ERR(avdd_fwnode),
+ "failed to get avdd fwnode\n");
+
+ /* REFP buffer should not be enabled when connected to AVDD */
+ data->refp_is_avdd = refp_fwnode == avdd_fwnode;
+ }
+
+ if (device_property_present(dev, "refn-supply")) {
+ ret = devm_regulator_get_enable_read_voltage(dev, "refn");
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to get refn voltage\n");
+
+ refn_uV = ret;
+ } else {
+ data->refn_is_gnd = true;
+ }
+
+ data->ext_ref_uV = refp_uV - refn_uV;
+
+ if (data->ext_ref_uV && data->ext_ref_ohms)
+ return dev_err_probe(dev, -EINVAL,
+ "ti,refp-refn-resistor-ohms property should not be present when refp-supply or refn-supply is present\n");
+
+ if (need_ext_ref && !data->ext_ref_uV && !data->ext_ref_ohms)
+ return dev_err_probe(dev, -EINVAL,
+ "external reference measurements require either refp-supply or ti,refp-refn-resistor-ohms property\n");
data->regmap = devm_regmap_init_i2c(client, &ads112c14_regmap_config);
if (IS_ERR(data->regmap))
@@ -640,8 +1157,6 @@ static int ads112c14_probe(struct i2c_client *client)
indio_dev->name = info->name;
indio_dev->modes = INDIO_DIRECT_MODE;
- indio_dev->channels = ads112c14_sys_mon_channels;
- indio_dev->num_channels = ARRAY_SIZE(ads112c14_sys_mon_channels);
indio_dev->info = &ads112c14_info;
return devm_iio_device_register(dev, indio_dev);
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v3 5/8] dt-bindings: iio: adc: add ti,ads122c14
2026-07-10 22:50 ` [PATCH v3 5/8] dt-bindings: iio: adc: add ti,ads122c14 David Lechner (TI)
@ 2026-07-10 22:59 ` sashiko-bot
2026-07-10 23:12 ` David Lechner
1 sibling, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-10 22:59 UTC (permalink / raw)
To: David Lechner (TI); +Cc: robh, devicetree, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] The binding defines GPIO provider capabilities (`gpio-controller`, `#gpio-cells`) but fails to include them in the `required` list and examples.
- [Medium] The `interrupt-names` property uses an object schema for `items` (`enum`), which fails to enforce positional ordering and contradicts the strict positional definition of the `interrupts` property.
- [Medium] The commit message claims that the `ti,vref-source` property is reused, but the code actually implements the standard `reference-sources` property instead.
--
--- Patch [5]: [PATCH v3 5/8] dt-bindings: iio: adc: add ti,ads122c14 ---
commit f62252330aecb9a95c850b342f0d43957821ef3a
Author: David Lechner (TI) <dlechner@baylibre.com>
dt-bindings: iio: adc: add ti,ads122c14
Adds bindings for Texas Instruments' ADS112C14 and ADS122C14 ADC chips
primarily designed for use with temperature sensors.
> The REFP/REFN reference voltage is usually just connected to a resistor
> that is being driven by the ADC's current outputs, so there is special
> property for this case rather than requiring a regulator to be defined
> to represent that.
>
> ti,vref-source is reused from ti,tlv320adcx140.yaml (otherwise might
> have preferred an enum of strings).
[Severity: Medium]
The commit message mentions reusing ti,vref-source, but the schema actually
implements the standard reference-sources property instead. Could the commit
message be updated to match the code?
> diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml
> new file mode 100644
> index 0000000000000..a2ee473c8e61e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml
[ ... ]
> + interrupt-names:
> + minItems: 1
> + maxItems: 2
> + items:
> + enum: [fault, drdy]
[Severity: Medium]
Does this items definition for interrupt-names allow invalid orderings?
By using an object schema instead of an array of constraints, this fails
to enforce positional ordering. It appears the schema could pass device trees
where interrupt-names are reversed (["drdy", "fault"]) or duplicated
(["fault", "fault"]).
It also allows ["drdy"] when only one interrupt is provided, contradicting
the interrupts definition which requires the first item to be the fault
interrupt.
[ ... ]
> +required:
> + - compatible
> + - reg
> + - avdd-supply
> + - dvdd-supply
[Severity: Medium]
Since the binding defines GPIO provider capabilities (gpio-controller and
#gpio-cells), should they be included in this required list?
Omitting them allows incomplete device tree nodes that advertise GPIO
capabilities without the corresponding required properties to pass schema
validation.
[ ... ]
> +examples:
> + - |
> + i2c {
[Severity: Medium]
Are the GPIO provider properties missing from the examples section?
If gpio-controller and #gpio-cells become required properties, the examples
will need to include them to pass schema validation.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260710-iio-adc-ti-ads122c14-v3-0-746d52cbf1d0@baylibre.com?part=5
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 6/8] iio: adc: add ti-ads112c14 driver
2026-07-10 22:50 ` [PATCH v3 6/8] iio: adc: add ti-ads112c14 driver David Lechner (TI)
@ 2026-07-10 23:00 ` sashiko-bot
2026-07-12 9:42 ` Andy Shevchenko
1 sibling, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-10 23:00 UTC (permalink / raw)
To: David Lechner (TI); +Cc: devicetree, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] ADC is incorrectly configured in continuous conversion mode instead of single-shot mode.
- [Low] Typographical error in device ID mismatch format string.
--
--- Patch [6]: [PATCH v3 6/8] iio: adc: add ti-ads112c14 driver ---
commit 38cae330f1112ca57db5aaa7a3a80adecc45b5cb
Author: David Lechner (TI) <dlechner@baylibre.com>
iio: adc: add ti-ads112c14 driver
Add a new driver for the TI ADS112C14/ADS122C14 ADC chips, adding basic
support for power on/reset and reading the system monitor channels.
> diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
> new file mode 100644
> index 0000000000000..4d2e7d37be829
> --- /dev/null
> +++ b/drivers/iio/adc/ti-ads112c14.c
[ ... ]
> + ret = regmap_read(data->regmap, ADS112C14_REG_DEVICE_ID, ®_val);
> + if (ret)
> + return ret;
> +
> + if (FIELD_GET(ADS112C14_DEVICE_ID_BITS, reg_val) != info->device_id)
[Severity: Low]
This isn't a bug, but there is a typo in the format string (0xX%X) which will
cause malformed log output like "0xX0E".
> + dev_info(dev, "device ID mismatch, expected 0xX%X, got 0x%lX\n",
> + info->device_id,
> + FIELD_GET(ADS112C14_DEVICE_ID_BITS, reg_val));
> +
[Severity: Medium]
The comment here says "Place in single-shot conversion mode", but does using
regmap_set_bits() actually place the device in continuous mode instead?
Setting the ADS112C14_DEVICE_CFG_CONV_MODE bit to 1 enables continuous
conversion mode on these chips.
Could this cause race conditions in ads112c14_single_conversion() where a DRDY
signal is asserted for a previous conversion while we expect a result for a
newly selected channel?
> + /* Place in single-shot conversion mode to make ready for raw read. */
> + ret = regmap_set_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
> + ADS112C14_DEVICE_CFG_CONV_MODE);
> + if (ret)
> + return ret;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260710-iio-adc-ti-ads122c14-v3-0-746d52cbf1d0@baylibre.com?part=6
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 7/8] iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel
2026-07-10 22:50 ` [PATCH v3 7/8] iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel David Lechner (TI)
@ 2026-07-10 23:04 ` sashiko-bot
2026-07-11 9:11 ` Andy Shevchenko
1 sibling, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-10 23:04 UTC (permalink / raw)
To: David Lechner (TI); +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The `read_avail` callback incorrectly casts a native `s64` array to `const int *` for the IIO core, which corrupts scale values on big-endian architectures.
--
--- Patch [7]: [PATCH v3 7/8] iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel ---
commit 6c0e6ddc837dab31f5440831baff3fcb1a43f658
Author: David Lechner (TI) <dlechner@baylibre.com>
iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel
This commit implements support for the programmable gain amplifier on the
internal short SYS_MON channel. The scale_available table is populated
during probe to allow the PGA to be set to the same gain as external
channels.
> diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
> index 4d2e7d37be829..2ce4411a0d86f 100644
> --- a/drivers/iio/adc/ti-ads112c14.c
> +++ b/drivers/iio/adc/ti-ads112c14.c
[ ... ]
> @@ -402,6 +430,69 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
> }
> }
>
> +static int ads112c14_read_avail(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan, const int **vals,
> + int *type, int *length, long mask)
> +{
> + struct ads112c14_data *data = iio_priv(indio_dev);
> +
> + if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
> + *vals = (const int *)data->sys_mon_chan_short_scale_available;
> + *length = 2 * ARRAY_SIZE(data->sys_mon_chan_short_scale_available);
> + *type = IIO_VAL_DECIMAL64_PICO;
[Severity: Medium]
Does casting a native s64 array to a const int pointer corrupt the scale
values on big-endian architectures?
When the IIO core processes these values as 32-bit pairs, vals[0] becomes
the most significant 32 bits and vals[1] the least significant on big-endian
systems. If the core later composes this via iio_val_s64_compose(vals[0],
vals[1]), the halves will be swapped, resulting in a mangled 64-bit scale
value.
Should the driver explicitly decompose the 64-bit values into an integer
array before handing them to the IIO core to avoid endianness issues?
> + return IIO_AVAIL_LIST;
> + }
> +
> + return -EINVAL;
> +}
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260710-iio-adc-ti-ads122c14-v3-0-746d52cbf1d0@baylibre.com?part=7
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 8/8] iio: adc: ti-ads112c14: add measurement channel support
2026-07-10 22:50 ` [PATCH v3 8/8] iio: adc: ti-ads112c14: add measurement channel support David Lechner (TI)
@ 2026-07-10 23:08 ` sashiko-bot
2026-07-11 13:00 ` Andy Shevchenko
1 sibling, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-10 23:08 UTC (permalink / raw)
To: David Lechner (TI); +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [Medium] The parsed `burn-out-current-nanoamp` Devicetree property is never applied to the hardware registers.
Pre-existing issues:
- [High] Short I2C block reads leak uninitialized stack memory to userspace.
- [High] Casting an `s64` array to `const int *` for `IIO_AVAIL_LIST` produces corrupted values on big-endian architectures.
--
--- Patch [8]: [PATCH v3 8/8] iio: adc: ti-ads112c14: add measurement channel support ---
commit d188e14a67542447de91b1747691ed2a77615e7a
Author: David Lechner (TI) <dlechner@baylibre.com>
iio: adc: ti-ads112c14: add measurement channel support
Add support for parsing devicetree properties for measurement channels
and doing direct reads on these.
> diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
> index 2ce4411a0d86f..a310abd69d8fa 100644
> --- a/drivers/iio/adc/ti-ads112c14.c
> +++ b/drivers/iio/adc/ti-ads112c14.c
[ ... ]
> @@ -257,12 +302,113 @@ static const struct regmap_config ads112c14_regmap_config = {
[ ... ]
> +static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data,
> + const struct iio_chan_spec *chan)
> +{
> + struct ads112c14_measurement *measurement = &data->measurements[chan->scan_index];
> + u32 refp_buf_en, refn_buf_en, ref_val, ref_sel;
> + int ret;
> +
> + ret = regmap_update_bits(data->regmap, ADS112C14_REG_MUX_CFG,
[ ... ]
> + ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG,
> + ADS112C14_DATA_RATE_CFG_GC_EN,
> + FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN,
> + measurement->global_chop));
> + if (ret)
> + return ret;
> +
[Severity: Medium]
Is the burn-out current setting being applied to the hardware?
In ads112c14_parse_channels(), the burn-out-current-nanoamp property is
parsed and stored in measurement->burnout. However, it does not appear
to be applied to the ADS112C14_REG_DEVICE_CFG register during this channel
configuration step, which might mean the feature remains unconfigured.
> + refp_buf_en = !data->refp_is_avdd &&
> + measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL;
> + refn_buf_en = !data->refn_is_gnd &&
[ ... ]
> @@ -386,7 +534,8 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
> return -EINVAL;
> }
>
> - *val = sign_extend32(*val, fsr_bits);
> + if (!measurement || measurement->bipolar)
> + *val = sign_extend32(*val, fsr_bits);
>
> return IIO_VAL_INT;
> }
[Severity: High]
This is a pre-existing issue, but does this sequence safely handle short I2C
block reads?
In ads112c14_read_raw(), an uninitialized buffer is declared on the stack
and passed for conversion:
ads112c14_read_raw()
...
u8 buf[3];
...
ret = ads112c14_single_conversion(data, chan, buf);
Inside ads112c14_single_conversion(), the read is performed:
return i2c_smbus_read_i2c_block_data(client, ..., buf);
If the block data read returns a positive value less than the requested
number of bytes, checking ret < 0 will not catch the error. The partially
uninitialized stack memory in buf could then be parsed using
get_unaligned_be16() or get_unaligned_be24() and returned to userspace.
[ ... ]
> @@ -436,14 +592,29 @@ static int ads112c14_read_avail(struct iio_dev *indio_dev,
> {
> struct ads112c14_data *data = iio_priv(indio_dev);
>
> - if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
> - *vals = (const int *)data->sys_mon_chan_short_scale_available;
> - *length = 2 * ARRAY_SIZE(data->sys_mon_chan_short_scale_available);
> - *type = IIO_VAL_DECIMAL64_PICO;
> - return IIO_AVAIL_LIST;
> - }
> + switch (mask) {
> + case IIO_CHAN_INFO_SCALE:
> + if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
> + struct ads112c14_measurement *measurement;
> +
> + measurement = &data->measurements[chan->scan_index];
> + *vals = (const int *)measurement->scale_available;
[Severity: High]
This is a pre-existing issue, but will casting an s64 array to a const int *
cause incorrect scale values on big-endian architectures?
The IIO core reads this list as pairs of 32-bit integers, combining them
into an s64 by using the first integer as the lower 32 bits and the second
as the upper 32 bits.
On big-endian systems, the upper 32 bits of the s64 are stored first in
memory, meaning the halves could be swapped when read by the IIO core.
> + *length = 2 * ARRAY_SIZE(measurement->scale_available);
> + *type = IIO_VAL_DECIMAL64_PICO;
> + return IIO_AVAIL_LIST;
> + }
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260710-iio-adc-ti-ads122c14-v3-0-746d52cbf1d0@baylibre.com?part=8
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 5/8] dt-bindings: iio: adc: add ti,ads122c14
2026-07-10 22:50 ` [PATCH v3 5/8] dt-bindings: iio: adc: add ti,ads122c14 David Lechner (TI)
2026-07-10 22:59 ` sashiko-bot
@ 2026-07-10 23:12 ` David Lechner
1 sibling, 0 replies; 17+ messages in thread
From: David Lechner @ 2026-07-10 23:12 UTC (permalink / raw)
To: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, sashiko-reviews
Cc: Chris Hall, Patrick Edwards, Kurt Borja, Nguyen Minh Tien,
linux-iio, devicetree, linux-kernel
Replying to https://sashiko.dev/#/patchset/20260710-iio-adc-ti-ads122c14-v3-0-746d52cbf1d0@baylibre.com?part=5
On 7/10/26 5:50 PM, David Lechner (TI) wrote:
> Add new bindings for ti,ads122c14 and similar devices.
>
> This is an ADC that is primarily intended for use with temperature
> sensors. There are a few unusual properties because of this. In
> particular, the reference voltage source and current output requirements
> can be different for each measurement, so these are included in the
> channel bindings.
>
> The REFP/REFN reference voltage is usually just connected to a resistor
> that is being driven by the ADC's current outputs, so there is special
> property for this case rather than requiring a regulator to be defined
> to represent that.
>
> ti,vref-source is reused from ti,tlv320adcx140.yaml (otherwise might
> have preferred an enum of strings).
This paragraph is leftover from v1 and should be deleted. (If someone
called it out in v2, I missed it.)
> +
> + interrupts:
> + minItems: 1
> + items:
> + - description: FAULT interrupt (GPIO2 pin)
> + - description: DRDY interrupt (GPIO3 pin)
> +
> + interrupt-names:
> + minItems: 1
> + maxItems: 2
> + items:
> + enum: [fault, drdy]
I know the descriptions are a inconsistent with allowing names to
be in any order. I don't really know a better way to write that
though that allows both
interrupts = <0>;
interrupt-names = "fault";
and
interrupts = <0>;
interrupt-names = "drdy";
They are just descriptions, so does it really matter?
> +
> + gpio-controller: true
> + '#gpio-cells':
> + const: 2
> +
No, gpio-conroller should not be required. It is rare to use the GPIOs on
this chip and the pins are shared with other functions.
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 7/8] iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel
2026-07-10 22:50 ` [PATCH v3 7/8] iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel David Lechner (TI)
2026-07-10 23:04 ` sashiko-bot
@ 2026-07-11 9:11 ` Andy Shevchenko
1 sibling, 0 replies; 17+ messages in thread
From: Andy Shevchenko @ 2026-07-11 9:11 UTC (permalink / raw)
To: David Lechner (TI)
Cc: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Chris Hall, Patrick Edwards,
Kurt Borja, Nguyen Minh Tien, linux-iio, devicetree, linux-kernel
On Fri, Jul 10, 2026 at 05:50:40PM -0500, David Lechner (TI) wrote:
> Implement support for the programmable gain amplifier on the internal
> short SYS_MON channel. This channel is used for calibration, so it is
> useful to be able to set the PGA to the same gain as the external
> channels. The gain setting is implemented via the `_scale` attribute.
>
> In the future, we may want to support different reference voltages for
> this channel, so the scale_available table is populated during probe
> rather than being a static table.
...
> +/*
> + * Available gains as tenths (e.g. value 5 == 0.5 gain). Indexes correspond to
> + * ADS112C14_GAIN_CFG_GAIN values.
> + */
> +static const u32 ads112c14_pga_gains_x10[] = {
> + 5, 10, 20, 40, 50, 80, 100, 160,
> + 200, 320, 500, 640, 1000, 1280, 2000, 2560,
Since the line lengths are quite different, I would add tail comments with
indices.
5, 10, 20, 40, 50, 80, 100, 160, /* 0 - 7 */
200, 320, 500, 640, 1000, 1280, 2000, 2560, /* 8 - 15 */
(or in hexadecimal, depending on the datasheet).
> +};
...
> static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
> const struct iio_chan_spec *chan)
> {
> + u32 gain_val;
> int ret;
>
> - /* TODO: GAIN is useful for shorted PGA inputs. */
> - /* All SYS_MON channels use GAIN of 1 to keep it simple. */
> + /*
> + * All SYS_MON channels use GAIN of 1 to keep it simple. Other than
> + * the internal short channel, where it is useful in practice.
> + */
> + gain_val = chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT ?
> + data->sys_mon_chan_short_gain_val : 1;
Hmm... What about
if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT)
gain_val = FIELD_PREP(ADS112C14_GAIN_CFG_GAIN, data->sys_mon_chan_short_gain_val);
else
gain_val = FIELD_PREP_CONST(ADS112C14_GAIN_CFG_GAIN, 1);
> ret = regmap_update_bits(data->regmap, ADS112C14_REG_GAIN_CFG,
> ADS112C14_GAIN_CFG_SYS_MON |
> ADS112C14_GAIN_CFG_GAIN,
> FIELD_PREP(ADS112C14_GAIN_CFG_SYS_MON, chan->address) |
> - FIELD_PREP(ADS112C14_GAIN_CFG_GAIN, 1));
> + FIELD_PREP(ADS112C14_GAIN_CFG_GAIN, gain_val));
gain_val);
On the second thought this might require to have a separate mon_val to make
this consistent. Up to you.
> if (ret)
> return ret;
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 8/8] iio: adc: ti-ads112c14: add measurement channel support
2026-07-10 22:50 ` [PATCH v3 8/8] iio: adc: ti-ads112c14: add measurement channel support David Lechner (TI)
2026-07-10 23:08 ` sashiko-bot
@ 2026-07-11 13:00 ` Andy Shevchenko
1 sibling, 0 replies; 17+ messages in thread
From: Andy Shevchenko @ 2026-07-11 13:00 UTC (permalink / raw)
To: David Lechner (TI)
Cc: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Chris Hall, Patrick Edwards,
Kurt Borja, Nguyen Minh Tien, linux-iio, devicetree, linux-kernel
On Fri, Jul 10, 2026 at 05:50:41PM -0500, David Lechner (TI) wrote:
> Add support for parsing devicetree properties for measurement channels
> and doing direct reads on these.
>
> There are quite a lot of conditions that have to be met for each
> measurement to be made, so quite a bit of state and algorithms are
> required to handle it.
>
> Channels are created dynamically since the number of possibilities is
> unreasonably large.
...
> +struct ads112c14_measurement {
> + const char *label;
> + u32 vref_source;
> + u8 iunit;
> + u8 idac1_mag;
> + u8 idac2_mag;
> + u8 idac1_mux;
> + u8 idac2_mux;
I would group this slightly differently:
u8 idac1_mag;
u8 idac2_mag;
u8 idac1_mux;
u8 idac2_mux;
u8 iunit;
I haven't seen the code, but names suggest that most likely one would read
*[12] together or close enough, and less probably mixed with 'iunit' reads.
Current layout might lead to interesting code generation complications on
the unaligned-intolerable architectures.
> + u8 iadc_count;
> + u8 gain_val;
> + u8 burnout;
> + bool global_chop;
> + bool bipolar;
> + s64 scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)];
Also this can be moved upper, but I think it won't save any bytes in this
layout.
> +};
...
> struct ads112c14_data {
> const struct ads112c14_chip_info *chip_info;
> struct regmap *regmap;
> + u32 avdd_uV;
> + u32 ext_ref_uV;
> + bool refp_is_avdd;
> + bool refn_is_gnd;
Here is a definite gain in memory if booleans combined with u8 below
> + u32 ext_ref_ohms;
> + struct ads112c14_measurement *measurements;
> + u32 num_measurements;
Something like
struct ads112c14_measurement *measurements;
u32 num_measurements;
u32 avdd_uV;
u32 ext_ref_uV;
u32 ext_ref_ohms;
bool refp_is_avdd;
bool refn_is_gnd;
? (Don't forget to run `pahole`.)
> u8 sys_mon_chan_short_gain_val;
> s64 sys_mon_chan_short_scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)];
> };
...
> + return regmap_update_bits(data->regmap, ADS112C14_REG_REFERENCE_CFG,
> + ADS112C14_REFERENCE_CFG_REFP_BUF_EN |
> + ADS112C14_REFERENCE_CFG_REFN_BUF_EN |
> + ADS112C14_REFERENCE_CFG_REF_VAL |
> + ADS112C14_REFERENCE_CFG_REF_SEL,
> + FIELD_PREP(ADS112C14_REFERENCE_CFG_REFP_BUF_EN,
> + refp_buf_en) |
> + FIELD_PREP(ADS112C14_REFERENCE_CFG_REFN_BUF_EN,
> + refn_buf_en) |
> + FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_VAL,
> + ref_val) |
> + FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_SEL,
> + ref_sel));
Personally I would go over 80 here.
FIELD_PREP(ADS112C14_REFERENCE_CFG_REFP_BUF_EN, refp_buf_en) |
FIELD_PREP(ADS112C14_REFERENCE_CFG_REFN_BUF_EN, refn_buf_en) |
FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_VAL, ref_val) |
FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_SEL, ref_sel));
All are less than 100.
> +}
...
> + /* measurement channels */
> + if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
> + struct ads112c14_measurement *measurement;
> +
> + measurement = &data->measurements[chan->scan_index];
> +
I would drop this blank line. It's naturally looking to have them coupled.
> + if (!measurement->label)
> + return -EINVAL;
> +
> + return sysfs_emit(label, "%s\n", measurement->label);
> + }
...
> + if (pair[0] <= 100000 && (measurement->iadc_count == 1 || pair[1] <= 100000)) {
100 * (NANO / MICRO)
in the similar way how you done elsewhere in the code.
> + /*
> + * If both values are 100uA or less, then we can
> + * use IUNIT = 1uA for better precision.
> + */
> + ret = ads112c14_populate_idac_mag(pair[0],
> + &measurement->idac1_mag);
> + if (ret)
> + return ret;
> +
> + if (measurement->iadc_count > 1) {
> + ret = ads112c14_populate_idac_mag(pair[1],
> + &measurement->idac2_mag);
> + if (ret)
> + return ret;
> + }
> + } else {
> + /*
> + * Otherwise, IUINT is 10uA (flag set) and so
> + * IxMAG is 1/10 of the actual current.
> + */
> + measurement->iunit = 1;
> +
> + ret = ads112c14_populate_idac_mag(pair[0] / 10,
> + &measurement->idac1_mag);
> + if (ret)
> + return ret;
> +
> + if (measurement->iadc_count > 1) {
> + ret = ads112c14_populate_idac_mag(pair[1] / 10,
> + &measurement->idac2_mag);
> + if (ret)
> + return ret;
> + }
> + }
> + }
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3 6/8] iio: adc: add ti-ads112c14 driver
2026-07-10 22:50 ` [PATCH v3 6/8] iio: adc: add ti-ads112c14 driver David Lechner (TI)
2026-07-10 23:00 ` sashiko-bot
@ 2026-07-12 9:42 ` Andy Shevchenko
1 sibling, 0 replies; 17+ messages in thread
From: Andy Shevchenko @ 2026-07-12 9:42 UTC (permalink / raw)
To: David Lechner (TI)
Cc: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Chris Hall, Patrick Edwards,
Kurt Borja, Nguyen Minh Tien, linux-iio, devicetree, linux-kernel
On Fri, Jul 10, 2026 at 05:50:39PM -0500, David Lechner (TI) wrote:
> Add a new driver for the TI ADS112C14/ADS122C14 ADC chips.
>
> This first step is adding a very basic driver that only supports power
> on/reset and reading the system monitor channels.
>
> ADS112C14_SYS_MON_CHANNEL_SHORT is the last channel rather than being in
> logical order by address to keep the voltage channels together and in
> case we find we need to add variants of this channel with different
> voltage reference later.
...
> +static int ads112c14_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + const struct ads112c14_chip_info *info;
> + struct iio_dev *indio_dev;
> + struct ads112c14_data *data;
> + u32 reg_val;
> + int ret;
> +
> + info = i2c_get_match_data(client);
> + if (!info)
> + return dev_err_probe(dev, -EINVAL, "missing match data\n");
I think -ENODEV suits better here.
> + indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + data = iio_priv(indio_dev);
> + data->chip_info = info;
> +
> + ret = devm_regulator_get_enable(dev, "dvdd");
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
> +
> + ret = devm_regulator_get_enable(dev, "avdd");
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
> +
> + data->regmap = devm_regmap_init_i2c(client, &ads112c14_regmap_config);
> + if (IS_ERR(data->regmap))
> + return dev_err_probe(dev, PTR_ERR(data->regmap),
> + "failed to init regmap\n");
Do we have a guarantee that device is powered on here?
> + /* Write magic reset value (0x16) to ensure known state. */
> + ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
> + FIELD_PREP(ADS112C14_CONVERSION_CTRL_RESET, 0x16));
> + /*
> + * The reset may cause an -EREMOTEIO error because of failing to get the
> + * I2C ACK at the end of the message. The device still gets reset so it
> + * is safe to ignore this error.
> + */
> + if (ret == -EREMOTEIO)
> + ret = 0;
> + if (ret)
> + return ret;
> +
> + fsleep(ADS112C14_DELAY_RESET_US);
> +
> + ret = regmap_read(data->regmap, ADS112C14_REG_STATUS_MSB, ®_val);
> + if (ret)
> + return ret;
> +
> + if (FIELD_GET(ADS112C14_STATUS_MSB_RESETN, reg_val))
> + return dev_err_probe(dev, -EIO, "reset failed\n");
> +
> + /*
> + * Clear reset bit to prepare for next probe. And clear AVDD fault since
> + * that happens on every reset.
> + */
> + ret = regmap_write(data->regmap, ADS112C14_REG_STATUS_MSB,
> + ADS112C14_STATUS_MSB_RESETN |
> + ADS112C14_STATUS_MSB_AVDD_UVN);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(data->regmap, ADS112C14_REG_DEVICE_ID, ®_val);
> + if (ret)
> + return ret;
> +
> + if (FIELD_GET(ADS112C14_DEVICE_ID_BITS, reg_val) != info->device_id)
> + dev_info(dev, "device ID mismatch, expected 0xX%X, got 0x%lX\n",
> + info->device_id,
> + FIELD_GET(ADS112C14_DEVICE_ID_BITS, reg_val));
> +
> + /* Place in single-shot conversion mode to make ready for raw read. */
> + ret = regmap_set_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
> + ADS112C14_DEVICE_CFG_CONV_MODE);
> + if (ret)
> + return ret;
> +
> + indio_dev->name = info->name;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> + indio_dev->channels = ads112c14_sys_mon_channels;
> + indio_dev->num_channels = ARRAY_SIZE(ads112c14_sys_mon_channels);
> + indio_dev->info = &ads112c14_info;
> +
> + return devm_iio_device_register(dev, indio_dev);
> +}
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-07-12 9:43 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10 22:50 [PATCH v3 0/8] iio: adc: new ti-ads112c14 driver David Lechner
2026-07-10 22:50 ` [PATCH v3 1/8] dt-bindings: iio: adc: Add reference-sources property David Lechner
2026-07-10 22:50 ` [PATCH v3 2/8] dt-bindings: iio: adc: Add excitation current sources properties David Lechner
2026-07-10 22:50 ` [PATCH v3 3/8] dt-bindings: iio: adc: Add burn-out current properties David Lechner
2026-07-10 22:50 ` [PATCH v3 4/8] dt-bindings: iio: adc: add input-chopping property David Lechner (TI)
2026-07-10 22:50 ` [PATCH v3 5/8] dt-bindings: iio: adc: add ti,ads122c14 David Lechner (TI)
2026-07-10 22:59 ` sashiko-bot
2026-07-10 23:12 ` David Lechner
2026-07-10 22:50 ` [PATCH v3 6/8] iio: adc: add ti-ads112c14 driver David Lechner (TI)
2026-07-10 23:00 ` sashiko-bot
2026-07-12 9:42 ` Andy Shevchenko
2026-07-10 22:50 ` [PATCH v3 7/8] iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel David Lechner (TI)
2026-07-10 23:04 ` sashiko-bot
2026-07-11 9:11 ` Andy Shevchenko
2026-07-10 22:50 ` [PATCH v3 8/8] iio: adc: ti-ads112c14: add measurement channel support David Lechner (TI)
2026-07-10 23:08 ` sashiko-bot
2026-07-11 13:00 ` Andy Shevchenko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox