iio: adc: Add Nuvoton MA35D1 EADC support
Subsystems (3)
v4 cover iio: adc: Add Nuvoton MA35D1 EADC support
From: Chi-Wen Weng <[email protected]>
This series adds IIO support for the Enhanced Analog-to-Digital Converter (EADC) found on Nuvoton MA35D1 SoCs.
The EADC provides eight external analog inputs which may be used as single-ended inputs or as four fixed differential pairs: 0-4, 1-5, 2-6 and 3-7.
The initial driver supports direct conversion and triggered buffered capture using sample module 0. Although the hardware provides multiple sample modules, this version of the driver uses only sample module 0 and therefore supports one active voltage channel per buffered scan.
The series is based on Linux v7.4-rc1.
Changes in v4:
- Use the common ADC diff-channels property to describe differential inputs and keep reg as the logical channel index.
- Restrict differential inputs to the four hardware-supported pairs.
- Restrict channel child nodes to the ADC properties supported by the MA35D1 EADC.
- Prevent single-channel and diff-channels from being specified together.
- Document that each single-ended input and differential input pair may be described only once.
- Remove interrupt-names and simplify the interrupt descriptions.
- Update the binding example to demonstrate separation of the logical channel index from the physical ADC inputs.
- Update channel parsing to match the revised binding semantics.
- Store physical ADC inputs in the IIO channel/channel2 fields and use address for the logical DT channel index.
- Reject duplicate physical single-ended inputs and differential input pairs.
- Use the physical positive input when programming the sample-module channel selection and the logical index for firmware-node channel translation.
- Use interrupt index 0 instead of looking up ADINT0 by name.
- Add the standard one-hot scan-mask validation callback.
- Use iio_for_each_active_channel() when selecting the buffered channel.
- Use a fixed scan structure for the single-channel buffered layout.
- Limit conversion results to the 12-bit ADC data width.
- Always report -ETIMEDOUT when a conversion times out.
- Make timeout recovery best effort so interrupt disable, control reset, IRQ synchronization and stale status cleanup are all attempted even if an earlier recovery step fails.
- Use devm_regulator_get_enable_read_voltage() for the optional external reference supply.
- Rework clock management to explicitly balance prepare/enable state across probe, suspend, resume, error paths and driver teardown.
- Improve suspend and resume error-path handling.
- Remove the redundant explicit REGCACHE_NONE setting.
- Split interrupt status read failures from interrupt-source checks and handle failures when clearing the interrupt status.
- Use symbolic definitions for the ADC resolution and differential scale width.
- Use cleanup guards for mutex handling in buffered capture and keep trigger notification outside the locked section.
- Use more specific error codes for channel capacity, duplicate channel definitions and out-of-range physical inputs.
- Apply the requested regmap, control-flow and coding-style cleanups.
Changes in v3:
- Document all four ADINT0 through ADINT3 hardware interrupt outputs and add interrupt-names.
- Document ADINT3 as the shared conversion and result-compare interrupt output.
- Remove the redundant minItems/maxItems constraints from the fixed interrupts items list.
- Rework the channel binding for the fixed differential pairs and use single-channel for single-ended inputs.
- Add #io-channel-cells so the EADC can be referenced by DT IIO consumers, and define the cell as the logical channel index.
- Add firmware-node channel translation so sparse or reordered channel child nodes are mapped by their logical channel index.
- Restrict unsupported generic ADC channel properties including bipolar, common-mode-channel, settling-time-us and oversampling-ratio.
- Explicitly configure INTPOS for end-of-conversion interrupt generation.
- Explicitly configure the ADINT0 sample-module source and clear unused sample-module routing.
- Reset the EADC control circuits during hardware initialization and clear inherited ADC interrupt enables, result-compare interrupt enables and sample-module trigger sources.
- Add timeout recovery using the EADC control-circuit reset, followed by IRQ synchronization and stale status/completion cleanup.
- Use iio_push_to_buffers_with_ts() for triggered buffered capture.
- Avoid mixing cleanup guards with goto paths in the trigger handler.
- Add ARCH_MA35 || COMPILE_TEST and HAS_IOMEM Kconfig dependencies.
- Drop redundant linux/bitops.h and linux/mod_devicetable.h includes.
- Use direct regmap helpers, explicit REGCACHE_NONE and regmap_assign_bits() where appropriate.
- Use fixed-width register offsets and kernel time/unit helpers.
- Use size_add() for channel array allocation.
- Request ADINT0 by interrupt name.
- Improve system suspend and resume handling.
- Address review comments from Jonathan Cameron, Rob Herring, Andy Shevchenko, Uwe Kleine-König and automated review/testing.
Changes in v2:
- Reworked the driver to use regmap.
- Added optional external reference supply support and IIO scale reporting.
- Added optional reset handling.
- Added externally triggered buffered capture.
- Added signed differential conversion handling.
- Added fixed differential-pair validation.
- Added system sleep support.
- Added optional DMA properties to the binding.
Patch 1 adds the devicetree binding.
Patch 2 adds the IIO driver.
Chi-Wen Weng (2):
dt-bindings: iio: adc: Add Nuvoton MA35D1 EADC
iio: adc: Add Nuvoton MA35D1 EADC driver
.../bindings/iio/adc/nuvoton,ma35d1-eadc.yaml | 163 ++++ drivers/iio/adc/Kconfig | 14 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ma35d1_eadc.c | 912 ++++++++++++++++++ 4 files changed, 1090 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml create mode 100644 drivers/iio/adc/ma35d1_eadc.c
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
Signature · 1 line
2.25.1
PATCH v4 1/2 dt-bindings: iio: adc: Add Nuvoton MA35D1 EADC
From: Chi-Wen Weng <[email protected]>
Add a binding for the Enhanced Analog-to-Digital Converter found on Nuvoton MA35D1 SoCs.
The controller has eight external analog inputs. They can be used as single-ended inputs or as the four fixed differential pairs 0-4, 1-5, 2-6 and 3-7.
Use the common ADC channel properties to describe the physical input independently of the logical channel index.
--- .../bindings/iio/adc/nuvoton,ma35d1-eadc.yaml | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml new file +163 -0
@@ -0,0 +1,163 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/nuvoton,ma35d1-eadc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton MA35D1 Enhanced Analog-to-Digital Converter + +maintainers: + - Chi-Wen Weng <[email protected]> + +description: | + The Nuvoton MA35D1 Enhanced Analog-to-Digital Converter (EADC) is a + 12-bit successive approximation converter with eight external analog + inputs and one internally connected VBAT input. + + The external inputs can be used as eight single-ended channels or as + four fixed differential pairs: 0-4, 1-5, 2-6 and 3-7. The internally + connected VBAT input is not described by a channel child node. + + The controller provides four interrupt outputs, ADINT0 through ADINT3. + +properties: + compatible: + const: nuvoton,ma35d1-eadc + + reg: + maxItems: 1 + + interrupts: + items: + - description: ADINT0 + - description: ADINT1 + - description: ADINT2 + - description: ADINT3 + + clocks: + maxItems: 1 + + resets: + maxItems: 1 + + vref-supply: + description: + Optional external reference voltage connected to the VREF pin. If this + supply is absent, the controller uses its internal 1.6 V reference. + + dmas: + maxItems: 1 + + dma-names: + const: rx + + '#io-channel-cells': + const: 1 + description: + The cell selects the logical EADC channel identified by the reg value + of the corresponding channel child node. + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +patternProperties: + '^channel@[0-7]$': + type: object + $ref: /schemas/iio/adc/adc.yaml# + description: | + External EADC channel description. The reg property identifies the + logical IIO channel index. + + A single-ended channel may use single-channel to select an external + analog input independently of reg. If single-channel and diff-channels + are both absent, the external analog input is selected by reg. + + A differential channel uses diff-channels to select one of the fixed + hardware input pairs: 0-4, 1-5, 2-6 or 3-7. Each single-ended input + and each differential input pair may be described at most once. + + properties: + reg: + maximum: 7 + + single-channel: + maximum: 7 + + diff-channels: + oneOf: + - items: + - const: 0 + - const: 4 + - items: + - const: 1 + - const: 5 + - items: + - const: 2 + - const: 6 + - items: + - const: 3 + - const: 7 + + allOf: + - not: + required: + - single-channel + - diff-channels + + required: + - reg + + additionalProperties: false + +required: + - compatible + - reg + - interrupts + - clocks + - '#io-channel-cells' + - '#address-cells' + - '#size-cells' + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/nuvoton,ma35d1-clk.h> + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/reset/nuvoton,ma35d1-reset.h> + + soc { + #address-cells = <2>; + #size-cells = <2>; + + adc@40430000 { + compatible = "nuvoton,ma35d1-eadc"; + reg = <0x0 0x40430000 0x0 0x10000>; + interrupts = <GIC_SPI 65 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk EADC_GATE>; + resets = <&sys MA35D1_RESET_EADC0>; + + #io-channel-cells = <1>; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + single-channel = <2>; + }; + + channel@1 { + reg = <1>; + diff-channels = <0 4>; + }; + }; + }; + +...
Signature · 1 line
2.25.1
[email protected]
AI reviewRe: [PATCH v4 1/2] dt-bindings: iio: adc: Add Nuvoton MA35D1 EADC
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] dt-bindings: iio: adc: ma35d1-eadc lacks io-channel specifier for VBAT --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] dt-bindings: iio: adc: ma35d1-eadc lacks io-channel specifier for VBAT --
commit 10ca0224f7b8ba3a2de52a1dfd8d73f64b596541
Author: Chi-Wen Weng <[email protected]>
dt-bindings: iio: adc: Add Nuvoton MA35D1 EADC
This commit adds the Device Tree binding schema for the Nuvoton MA35D1 Enhanced Analog-to-Digital Converter (EADC), describing its eight external analog inputs (usable as single-ended channels or four fixed differential pairs) and one internal VBAT input.
Quoted 27 lines
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml new file +21
-0@@ -0,0 +1,163 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/nuvoton,ma35d1-eadc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton MA35D1 Enhanced Analog-to-Digital Converter + +maintainers: + - Chi-Wen Weng <[email protected]> + +description: | + The Nuvoton MA35D1 Enhanced Analog-to-Digital Converter (EADC) is a + 12-bit successive approximation converter with eight external analog + inputs and one internally connected VBAT input. + + The external inputs can be used as eight single-ended channels or as + four fixed differential pairs: 0-4, 1-5, 2-6 and 3-7. The internally + connected VBAT input is not described by a channel child node. + + The controller provides four interrupt outputs, ADINT0 through ADINT3.
[ ... ]
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml line 58
Quoted 5 lines
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml new file +5
-0+ '#io-channel-cells': + const: 1 + description: + The cell selects the logical EADC channel identified by the reg value + of the corresponding channel child node.
[Severity: Medium]
As reported by Jonathan Cameron on v3 [1], how can an IIO consumer in Device Tree reference the internal VBAT input if #io-channel-cells in Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml only selects the logical EADC channel identified by the reg value of a channel@[0-7] child node, while description states that the internally connected VBAT input is not described by a channel child node?
Also, should the top-level description block use > rather than | since it only needs to preserve paragraph breaks?
[1] https://lore.kernel.org/r/20261101001934.23588aa2@jic23-hlaptop
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Jonathan Cameron
maintainerRe: [PATCH v4 1/2] dt-bindings: iio: adc: Add Nuvoton MA35D1 EADC
In reply to Chi-Wen WengQuoted 5 lines · On Wed, 4 Nov 2026 17:11:35 +0800 Chi-Wen Weng <[email protected]> wrote:
From: Chi-Wen Weng <[email protected]>
Add a binding for the Enhanced Analog-to-Digital Converter found on Nuvoton MA35D1 SoCs.
Quoted 8 lines
The controller has eight external analog inputs. They can be used as single-ended inputs or as the four fixed differential pairs 0-4, 1-5, 2-6 and 3-7.
Use the common ADC channel properties to describe the physical input independently of the logical channel index.
Signed-off-by Chi-Wen Weng <[email protected]>
A couple of really trivial things inline.
Thanks,
Jonathan
Quoted 13 lines
--- .../bindings/iio/adc/nuvoton,ma35d1-eadc.yaml | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yamlDocumentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml new file +2
-0@@ -0,0 +1,163 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml line 12
Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml new file +10
-0+--- +$id: http://devicetree.org/schemas/iio/adc/nuvoton,ma35d1-eadc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton MA35D1 Enhanced Analog-to-Digital Converter + +maintainers: + - Chi-Wen Weng <[email protected]> + +description: |
Similar to below. I think this is overconstraining the formatting.
Quoted 5 lines
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml new file +5
-0+ The Nuvoton MA35D1 Enhanced Analog-to-Digital Converter (EADC) is a + 12-bit successive approximation converter with eight external analog + inputs and one internally connected VBAT input. + + The external inputs can be used as eight single-ended channels or as
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml line 70
Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml new file +9
-0+ four fixed differential pairs: 0-4, 1-5, 2-6 and 3-7. The internally + connected VBAT input is not described by a channel child node. + + The controller provides four interrupt outputs, ADINT0 through ADINT3. +patternProperties: + '^channel@[0-7]$': + type: object + $ref: /schemas/iio/adc/adc.yaml# + description: |
IIRC > instead of | is appropriate here. You want to maintain paragraphs but otherwise allow rewraapping.
Quoted 13 lines
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml new file +12
-0+ External EADC channel description. The reg property identifies the + logical IIO channel index. + + A single-ended channel may use single-channel to select an external + analog input independently of reg. If single-channel and diff-channels + are both absent, the external analog input is selected by reg. + + A differential channel uses diff-channels to select one of the fixed + hardware input pairs: 0-4, 1-5, 2-6 or 3-7. Each single-ended input + and each differential input pair may be described at most once. + +...
Rob Herring
maintainerRe: [PATCH v4 1/2] dt-bindings: iio: adc: Add Nuvoton MA35D1 EADC
In reply to Chi-Wen WengQuoted 125 lines · Chi-Wen Weng
From: Chi-Wen Weng <[email protected]>
Add a binding for the Enhanced Analog-to-Digital Converter found on Nuvoton MA35D1 SoCs.
The controller has eight external analog inputs. They can be used as single-ended inputs or as the four fixed differential pairs 0-4, 1-5, 2-6 and 3-7.
Use the common ADC channel properties to describe the physical input independently of the logical channel index.
Signed-off-by Chi-Wen Weng <[email protected]>--- .../bindings/iio/adc/nuvoton,ma35d1-eadc.yaml | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yamlDocumentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml new file +101
-0@@ -0,0 +1,163 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/nuvoton,ma35d1-eadc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton MA35D1 Enhanced Analog-to-Digital Converter + +maintainers: + - Chi-Wen Weng <[email protected]> + +description: | + The Nuvoton MA35D1 Enhanced Analog-to-Digital Converter (EADC) is a + 12-bit successive approximation converter with eight external analog + inputs and one internally connected VBAT input. + + The external inputs can be used as eight single-ended channels or as + four fixed differential pairs: 0-4, 1-5, 2-6 and 3-7. The internally + connected VBAT input is not described by a channel child node. + + The controller provides four interrupt outputs, ADINT0 through ADINT3. + +properties: + compatible: + const: nuvoton,ma35d1-eadc + + reg: + maxItems: 1 + + interrupts: + items: + - description: ADINT0 + - description: ADINT1 + - description: ADINT2 + - description: ADINT3 + + clocks: + maxItems: 1 + + resets: + maxItems: 1 + + vref-supply: + description: + Optional external reference voltage connected to the VREF pin. If this + supply is absent, the controller uses its internal 1.6 V reference. + + dmas: + maxItems: 1 + + dma-names: + const: rx + + '#io-channel-cells': + const: 1 + description: + The cell selects the logical EADC channel identified by the reg value + of the corresponding channel child node. + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +patternProperties: + '^channel@[0-7]$': + type: object + $ref: /schemas/iio/adc/adc.yaml# + description: | + External EADC channel description. The reg property identifies the + logical IIO channel index. + + A single-ended channel may use single-channel to select an external + analog input independently of reg. If single-channel and diff-channels + are both absent, the external analog input is selected by reg. + + A differential channel uses diff-channels to select one of the fixed + hardware input pairs: 0-4, 1-5, 2-6 or 3-7. Each single-ended input + and each differential input pair may be described at most once. + + properties: + reg: + maximum: 7 + + single-channel: + maximum: 7 + + diff-channels: + oneOf: + - items: + - const: 0 + - const: 4 + - items: + - const: 1 + - const: 5 + - items: + - const: 2 + - const: 6 + - items: + - const: 3
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml line 111
Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml new file +10
-0+ - const: 7 + + allOf: + - not: + required: + - single-channel + - diff-channels + + required: + - reg
The allOf and required are effectively covered by adc.yaml already.
Rob
Re: [PATCH v4 1/2] dt-bindings: iio: adc: Add Nuvoton MA35D1 EADC
Rob Herring 於 2026/11/11 上午 03:08 寫道:
Quoted 139 lines · Chi-Wen Weng
Quoted 125 lines
From: Chi-Wen Weng <[email protected]>
Add a binding for the Enhanced Analog-to-Digital Converter found on Nuvoton MA35D1 SoCs.
The controller has eight external analog inputs. They can be used as single-ended inputs or as the four fixed differential pairs 0-4, 1-5, 2-6 and 3-7.
Use the common ADC channel properties to describe the physical input independently of the logical channel index.
Signed-off-by Chi-Wen Weng <[email protected]>--- .../bindings/iio/adc/nuvoton,ma35d1-eadc.yaml | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+)create mode 100644
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yamlDocumentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml new file +101
-0@@ -0,0 +1,163 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/nuvoton,ma35d1-eadc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton MA35D1 Enhanced Analog-to-Digital Converter + +maintainers: + - Chi-Wen Weng <[email protected]> + +description: | + The Nuvoton MA35D1 Enhanced Analog-to-Digital Converter (EADC) is a + 12-bit successive approximation converter with eight external analog + inputs and one internally connected VBAT input. + + The external inputs can be used as eight single-ended channels or as + four fixed differential pairs: 0-4, 1-5, 2-6 and 3-7. The internally + connected VBAT input is not described by a channel child node. + + The controller provides four interrupt outputs, ADINT0 through ADINT3. + +properties: + compatible: + const: nuvoton,ma35d1-eadc + + reg: + maxItems: 1 + + interrupts: + items: + - description: ADINT0 + - description: ADINT1 + - description: ADINT2 + - description: ADINT3 + + clocks: + maxItems: 1 + + resets: + maxItems: 1 + + vref-supply: + description: + Optional external reference voltage connected to the VREF pin. If this + supply is absent, the controller uses its internal 1.6 V reference. + + dmas: + maxItems: 1 + + dma-names: + const: rx + + '#io-channel-cells': + const: 1 + description: + The cell selects the logical EADC channel identified by the reg value + of the corresponding channel child node. + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +patternProperties: + '^channel@[0-7]$': + type: object + $ref: /schemas/iio/adc/adc.yaml# + description: | + External EADC channel description. The reg property identifies the + logical IIO channel index. + + A single-ended channel may use single-channel to select an external + analog input independently of reg. If single-channel and diff-channels + are both absent, the external analog input is selected by reg. + + A differential channel uses diff-channels to select one of the fixed + hardware input pairs: 0-4, 1-5, 2-6 or 3-7. Each single-ended input + and each differential input pair may be described at most once. + + properties: + reg: + maximum: 7 + + single-channel: + maximum: 7 + + diff-channels: + oneOf: + - items: + - const: 0 + - const: 4 + - items: + - const: 1 + - const: 5 + - items: + - const: 2 + - const: 6 + - items: + - const: 3Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml line 111
Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml new file +10
-0+ - const: 7 + + allOf: + - not: + required: + - single-channel + - diff-channels + + required: + - regThe allOf and required are effectively covered by
adc.yamlalready.
Quoted 1 line
Rob
Hi Rob,
Quoted 1 line
The allOf and required are effectively covered by
adc.yamlalready.
The channel-level required list is redundant; I'll remove it.
For the allOf, adc.yaml currently has:
anyOf:
- oneOf:
- required:
- reg
- diff-channels
- required:
- reg
- single-channel
- required:
- reg
When both single-channel and diff-channels are present, the inner oneOf fails, but the reg-only branch still satisfies the outer anyOf. Removing the local constraint would therefore allow both properties together, which the driver rejects.
Should I keep the local check, or move the mutual-exclusion constraint into adc.yaml?
Thanks,
Chi-Wen
Rob Herring
maintainerRe: [PATCH v4 1/2] dt-bindings: iio: adc: Add Nuvoton MA35D1 EADC
Quoted 163 lines · Chi-Wen Weng
Rob Herring 於 2026/11/11 上午 03:08 寫道:
Quoted 139 lines · Chi-Wen Weng
Quoted 125 lines
From: Chi-Wen Weng <[email protected]>
Add a binding for the Enhanced Analog-to-Digital Converter found on Nuvoton MA35D1 SoCs.
The controller has eight external analog inputs. They can be used as single-ended inputs or as the four fixed differential pairs 0-4, 1-5, 2-6 and 3-7.
Use the common ADC channel properties to describe the physical input independently of the logical channel index.
Signed-off-by Chi-Wen Weng <[email protected]>--- .../bindings/iio/adc/nuvoton,ma35d1-eadc.yaml | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+)create mode 100644
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yamlDocumentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml new file +101
-0@@ -0,0 +1,163 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/nuvoton,ma35d1-eadc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton MA35D1 Enhanced Analog-to-Digital Converter + +maintainers: + - Chi-Wen Weng <[email protected]> + +description: | + The Nuvoton MA35D1 Enhanced Analog-to-Digital Converter (EADC) is a + 12-bit successive approximation converter with eight external analog + inputs and one internally connected VBAT input. + + The external inputs can be used as eight single-ended channels or as + four fixed differential pairs: 0-4, 1-5, 2-6 and 3-7. The internally + connected VBAT input is not described by a channel child node. + + The controller provides four interrupt outputs, ADINT0 through ADINT3. + +properties: + compatible: + const: nuvoton,ma35d1-eadc + + reg: + maxItems: 1 + + interrupts: + items: + - description: ADINT0 + - description: ADINT1 + - description: ADINT2 + - description: ADINT3 + + clocks: + maxItems: 1 + + resets: + maxItems: 1 + + vref-supply: + description: + Optional external reference voltage connected to the VREF pin. If this + supply is absent, the controller uses its internal 1.6 V reference. + + dmas: + maxItems: 1 + + dma-names: + const: rx + + '#io-channel-cells': + const: 1 + description: + The cell selects the logical EADC channel identified by the reg value + of the corresponding channel child node. + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +patternProperties: + '^channel@[0-7]$': + type: object + $ref: /schemas/iio/adc/adc.yaml# + description: | + External EADC channel description. The reg property identifies the + logical IIO channel index. + + A single-ended channel may use single-channel to select an external + analog input independently of reg. If single-channel and diff-channels + are both absent, the external analog input is selected by reg. + + A differential channel uses diff-channels to select one of the fixed + hardware input pairs: 0-4, 1-5, 2-6 or 3-7. Each single-ended input + and each differential input pair may be described at most once. + + properties: + reg: + maximum: 7 + + single-channel: + maximum: 7 + + diff-channels: + oneOf: + - items: + - const: 0 + - const: 4 + - items: + - const: 1 + - const: 5 + - items: + - const: 2 + - const: 6 + - items: + - const: 3Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml line 111
Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml new file +10
-0+ - const: 7 + + allOf: + - not: + required: + - single-channel + - diff-channels + + required: + - regThe allOf and required are effectively covered by
adc.yamlalready.Quoted 1 line
Rob
Hi Rob,
Quoted 1 line
The allOf and required are effectively covered by
adc.yamlalready.The channel-level required list is redundant; I'll remove it.
For the allOf,
adc.yamlcurrently has:anyOf:
- oneOf:
- required:
- reg
- diff-channels
- required:
- reg
- single-channel
- required:
- reg
Quoted 4 lines
When both single-channel and diff-channels are present, the inner oneOf fails, but the reg-only branch still satisfies the outer anyOf. Removing the local constraint would therefore allow both properties together, which the driver rejects.
Oh, right.
Quoted 3 lines
Should I keep the local check, or move the mutual-exclusion constraint into
adc.yaml?
I think what you have here should replace what's in adc.yaml.
Rob
PATCH v4 2/2 iio: adc: Add Nuvoton MA35D1 EADC driver
In reply to Chi-Wen WengFrom: Chi-Wen Weng <[email protected]>
Add an IIO driver for the Enhanced Analog-to-Digital Converter found on Nuvoton MA35D1 SoCs.
Support single-ended and fixed differential input pairs, direct conversion, and triggered buffered capture using sample module 0.
The hardware supports multiple sample modules, but this initial driver uses sample module 0 only and therefore supports one active voltage channel per buffered scan.
--- drivers/iio/adc/Kconfig | 14 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ma35d1_eadc.c | 912 ++++++++++++++++++++++++++++++++++ 3 files changed, 927 insertions(+) create mode 100644 drivers/iio/adc/ma35d1_eadc.c
drivers/iio/adc/Kconfig +14 -0
@@ -1032,6 +1032,20 @@ config LTC2497 To compile this driver as a module, choose M here: the module will be called ltc2497. +config MA35D1_EADC + tristate "Nuvoton MA35D1 EADC driver" + depends on ARCH_MA35 || COMPILE_TEST + depends on HAS_IOMEM + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_MMIO + help + Say yes here to build support for the Nuvoton MA35D1 + Enhanced Analog to Digital Converter. + + To compile this driver as a module, choose M here: the module will be + called ma35d1_eadc. + config MAX1027 tristate "Maxim max1027 ADC driver" depends on SPI
drivers/iio/adc/Makefile +1 -0
@@ -88,6 +88,7 @@ obj-$(CONFIG_LTC2471) += ltc2471.o obj-$(CONFIG_LTC2485) += ltc2485.o obj-$(CONFIG_LTC2496) += ltc2496.o ltc2497-core.o obj-$(CONFIG_LTC2497) += ltc2497.o ltc2497-core.o +obj-$(CONFIG_MA35D1_EADC) += ma35d1_eadc.o obj-$(CONFIG_MAX1027) += max1027.o obj-$(CONFIG_MAX11100) += max11100.o obj-$(CONFIG_MAX1118) += max1118.o
drivers/iio/adc/ma35d1_eadc.c new file +912 -0
912 lines not on this page · raw message
Signature · 1 line
2.25.1
[email protected]
AI reviewRe: [PATCH v4 2/2] iio: adc: Add Nuvoton MA35D1 EADC driver
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] iio: adc: ma35d1: unclocked MMIO access when disabling buffer or suspending --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] iio: adc: ma35d1: unclocked MMIO access when disabling buffer or suspending --
commit 42a7961767520f06d7be9ff697854a2064ddd67f
Author: Chi-Wen Weng <[email protected]>
iio: adc: Add Nuvoton MA35D1 EADC driver
This commit adds an IIO ADC driver for the Enhanced Analog-to-Digital Converter on Nuvoton MA35D1 SoCs, supporting single-ended and differential inputs, direct conversions, and triggered buffered capture using sample module 0.
Quoted 6 lines
drivers/iio/adc/ma35d1_eadc.c new file +0
-0@@ -0,0 +1,912 @@
[ ... ]
Quoted 7 lines
drivers/iio/adc/ma35d1_eadc.c new file +7
-0+static void ma35d1_adc_hw_disable_action (void *data) +{ +struct ma35d1_adc *adc = data; + +if (adc->clk_enabled) +ma35d1_adc_hw_disable (adc); +}
[ ... ]
Quoted 16 lines
drivers/iio/adc/ma35d1_eadc.c new file +16
-0+static int ma35d1_adc_buffer_predisable (struct iio_dev *indio_dev) +{ +struct ma35d1_adc *adc =iio_priv (indio_dev); +int ret; + +guard (mutex)(&adc->lock); + + ret =ma35d1_adc_disable_irq (adc); +if (ret) +return ret; + +synchronize_irq (adc->irq); + +return regmap_write (adc->regmap,MA35D1_EADC_STATUS2 , +MA35D1_EADC_STATUS2_ADIF0 ); +}
[ ... ]
Quoted 63 lines
drivers/iio/adc/ma35d1_eadc.c new file +63
-0+static int ma35d1_adc_suspend (struct device *dev) +{ +struct iio_dev *indio_dev =dev_get_drvdata (dev); +struct ma35d1_adc *adc =iio_priv (indio_dev); +struct regmap *map = adc->regmap; +int ret; + + ret =iio_device_suspend_triggering (indio_dev); +if (ret) +return ret; + +mutex_lock (&adc->lock); + + ret =ma35d1_adc_disable_irq (adc); +if (ret) + goto resume_triggering; + +synchronize_irq (adc->irq); + + ret =regmap_clear_bits (map,MA35D1_EADC_CTL ,MA35D1_EADC_CTL_ADCEN ); +if (ret) { +/* +* ADCIEN0 is enabled only while a conversion is active. Holding +* adc->lock here guarantees that no conversion is in progress, so +* leave it disabled. The next conversion enables it again. +*/ + goto resume_triggering; + } + + adc->suspended = true; +clk_disable_unprepare (adc->clk); + adc->clk_enabled = false; +mutex_unlock (&adc->lock); + +return 0 ; + +resume_triggering: +mutex_unlock (&adc->lock); +iio_device_resume_triggering (indio_dev); + +return ret; +} + +static int ma35d1_adc_resume (struct device *dev) +{ +struct iio_dev *indio_dev =dev_get_drvdata (dev); +struct ma35d1_adc *adc =iio_priv (indio_dev); +int ret; + +mutex_lock (&adc->lock); + + ret =clk_prepare_enable (adc->clk); +if (ret) + goto unlock; + + adc->clk_enabled = true; + ret =ma35d1_adc_hw_init (adc); +if (ret) { +clk_disable_unprepare (adc->clk); + adc->clk_enabled = false; + goto unlock; + } +
drivers/iio/adc/ma35d1_eadc.c line 889
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+ adc->suspended = false; +mutex_unlock (&adc->lock); + +return iio_device_resume_triggering (indio_dev); + +unlock: +mutex_unlock (&adc->lock); + +return ret; +}
[Severity: High]
Can ma35d1_adc_buffer_predisable() and ma35d1_adc_suspend() access EADC MMIO registers while adc->clk is disabled?
During system suspend, ma35d1_adc_suspend() sets adc->suspended = true, calls clk_disable_unprepare(adc->clk), and sets adc->clk_enabled = false. If a subsequent ma35d1_adc_resume() fails in clk_prepare_enable() or ma35d1_adc_hw_init(), its error path leaves adc->clk_enabled = false and adc->suspended = true while any previously enabled triggered buffer remains active.
When userspace later disables the buffer or unbinds the driver (or if the driver is unbound while suspended, since devm_iio_device_register() teardown runs iio_device_unregister() -> iio_disable_all_buffers() -> ma35d1_adc_buffer_predisable() before ma35d1_adc_hw_disable_action()), ma35d1_adc_buffer_predisable() unconditionally calls ma35d1_adc_disable_irq() and regmap_write(adc->regmap, MA35D1_EADC_STATUS2, MA35D1_EADC_STATUS2_ADIF0) without checking adc->suspended or adc->clk_enabled, accessing MMIO registers with the peripheral clock gated.
Similarly, if another suspend cycle runs after a failed resume, ma35d1_adc_suspend() does not check whether the device is already suspended before accessing registers, calling clk_disable_unprepare(adc->clk) a second time, and calling iio_device_suspend_triggering(indio_dev) again.
Should ma35d1_adc_buffer_predisable() and ma35d1_adc_suspend() check adc->suspended (or adc->clk_enabled, as ma35d1_adc_hw_disable_action() does) before touching the hardware registers and disabling the clock?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Re: [PATCH v4 2/2] iio: adc: Add Nuvoton MA35D1 EADC driver
In reply to Chi-Wen WengQuoted 9 lines · Chi-Wen Weng
Add an IIO driver for the Enhanced Analog-to-Digital Converter found on Nuvoton MA35D1 SoCs.
Support single-ended and fixed differential input pairs, direct conversion, and triggered buffered capture using sample module 0.
The hardware supports multiple sample modules, but this initial driver uses sample module 0 only and therefore supports one active voltage channel per buffered scan.
...
Quoted 4 lines
drivers/iio/adc/ma35d1_eadc.c new file +4
-0+struct ma35d1_adc { +struct regmap *regmap; +struct clk *clk; +struct reset_control *rst;
drivers/iio/adc/ma35d1_eadc.c line 97
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+struct completion completion; +/* Protect conversion state and register accesses from PM transitions. */ +struct mutex lock; +const struct iio_chan_spec *scan_chan; +unsigned int vref_mV; +int irq; +bool clk_enabled; +bool external_vref; +bool suspended; +};
Now when it has optimal layout it's better to regroup the fields with some ties.
struct ma35d1_adc {
struct regmap *regmap;
struct clk *clk;
struct reset_control *rst;
struct completion completion;
/* Protect conversion state and register accesses from PM transitions. */
struct mutex lock;
const struct iio_chan_spec *scan_chan;
int irq;
unsigned int vref_mV;
bool external_vref;
bool clk_enabled;
bool suspended;
};...
drivers/iio/adc/ma35d1_eadc.c line 103
Quoted 5 lines
drivers/iio/adc/ma35d1_eadc.c new file +5
-0+static const struct regmap_config ma35d1_adc_regmap_config = { + .reg_bits =32 , + .val_bits =32 , + .reg_stride =4 , + .max_register =MA35D1_EADC_REFADJCTL ,
Add a comment why no cache is enabled.
Quoted 1 line
+};
...
drivers/iio/adc/ma35d1_eadc.c line 161
Quoted 5 lines
drivers/iio/adc/ma35d1_eadc.c new file +4
-0+static int ma35d1_adc_hw_init (struct ma35d1_adc *adc) +{ +struct regmap *map = adc->regmap; +unsigned int i;
Not used outside the loops, just declare inside respective for:s.
Quoted 1 line
+}
...
Quoted 9 lines
drivers/iio/adc/ma35d1_eadc.c new file +9
-0+static int ma35d1_adc_config_channel (struct ma35d1_adc *adc, +const struct iio_chan_spec *chan) +{ +struct regmap *map = adc->regmap; +unsigned int mask; +int ret; + + mask =MA35D1_EADC_CTL_DIFFEN |MA35D1_EADC_CTL_DMOF ; + ret =regmap_assign_bits (map,MA35D1_EADC_CTL , mask, chan->differential);
drivers/iio/adc/ma35d1_eadc.c line 269
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+if (ret) +return ret; + +/* +* INTPOS = 0 generates ADIF0 at end of conversion. TRGSEL = 0 disables +* the hardware trigger; conversions are started through SWTRG0. +*/ + mask =MA35D1_EADC_SCTL_CHSEL_MASK | +MA35D1_EADC_SCTL_TRGSEL_MASK | +MA35D1_EADC_SCTL_INTPOS ;
Use room on the previous line(s).
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+ ret =regmap_update_bits (map,MA35D1_EADC_SCTL (0 ), mask, +FIELD_PREP (MA35D1_EADC_SCTL_CHSEL_MASK , + chan->channel)); +if (ret) +return ret; + +return regmap_update_bits (map,MA35D1_EADC_INTSRC0 , +MA35D1_EADC_INTSRC0_MASK , +MA35D1_EADC_INTSRC0_SPLIEN0 ); +}
...
Quoted 18 lines
drivers/iio/adc/ma35d1_eadc.c new file +18
-0+static int ma35d1_adc_read_conversion (struct ma35d1_adc *adc, +const struct iio_chan_spec *chan,u16 *raw) +{ +struct regmap *map = adc->regmap; +unsigned int val; +long timeout; +int ret; + +reinit_completion (&adc->completion); + + ret =regmap_write (map,MA35D1_EADC_STATUS2 ,MA35D1_EADC_STATUS2_ADIF0 ); +if (ret) +return ret; + + ret =ma35d1_adc_config_channel (adc, chan); +if (ret) +return ret; +
drivers/iio/adc/ma35d1_eadc.c line 335
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+ ret =regmap_set_bits (map,MA35D1_EADC_CTL ,MA35D1_EADC_CTL_ADCIEN0 ); +if (ret) +return ret; + + ret =regmap_write (map,MA35D1_EADC_SWTRG ,BIT (0 )); +if (ret) + goto disable_irq; + + timeout =wait_for_completion_timeout (&adc->completion, +MA35D1_EADC_TIMEOUT );
Make it a single (yes, a bit longer) line.
Quoted 28 lines
drivers/iio/adc/ma35d1_eadc.c new file +28
-0+if (!timeout) { +ma35d1_adc_abort_conversion (adc); +return -ETIMEDOUT ; + } + + ret =ma35d1_adc_disable_irq (adc); +if (ret) +return ret; + + ret =regmap_read (map,MA35D1_EADC_DAT (0 ), &val); +if (ret) +return ret; + +if (!(val &MA35D1_EADC_DAT_VALID )) +return -EIO ; + +if (val &MA35D1_EADC_DAT_OV ) +return -EOVERFLOW ; + + *raw =FIELD_GET (MA35D1_EADC_DAT_RESULT_12BIT , val); + +return 0 ; + +disable_irq: +ma35d1_adc_disable_irq (adc); + +return ret; +}
...
Quoted 4 lines
drivers/iio/adc/ma35d1_eadc.c new file +4
-0+static irqreturn_t ma35d1_adc_isr (int irq,void *data) +{ +struct iio_dev *indio_dev = data; +struct ma35d1_adc *adc =iio_priv (indio_dev);
drivers/iio/adc/ma35d1_eadc.c line 378
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+struct regmap *map = adc->regmap; +unsigned int status; +int ret; + + ret =regmap_read (map,MA35D1_EADC_STATUS2 , &status); +if (ret) +return IRQ_NONE ; + +if (!(status &MA35D1_EADC_STATUS2_ADIF0 )) +return IRQ_NONE ;
regmap_test_bits() ? It will hide status OTOH. Up to you.
Quoted 9 lines
drivers/iio/adc/ma35d1_eadc.c new file +9
-0+ ret =regmap_write (map,MA35D1_EADC_STATUS2 ,MA35D1_EADC_STATUS2_ADIF0 ); +if (ret) +dev_err_ratelimited (regmap_get_device (map), +"failed to clear ADC interrupt status: %d\n" , ret); + +complete (&adc->completion); + +return IRQ_HANDLED ; +}
...
Quoted 25 lines
drivers/iio/adc/ma35d1_eadc.c new file +25
-0+static int ma35d1_adc_read_raw (struct iio_dev *indio_dev, +const struct iio_chan_spec *chan, +int *val,int *val2,long mask) +{ +struct ma35d1_adc *adc =iio_priv (indio_dev); +u16 raw; +int ret; + +switch (mask) { +case IIO_CHAN_INFO_RAW : { +IIO_DEV_ACQUIRE_DIRECT_MODE (indio_dev, claim); +if (IIO_DEV_ACQUIRE_FAILED (claim)) +return -EBUSY ; + +guard (mutex)(&adc->lock); + +if (adc->suspended) +return -EBUSY ; + + ret =ma35d1_adc_read_conversion (adc, chan, &raw); +if (ret) +return ret; + +if (chan->differential) + *val =ma35d1_adc_sign_extend (raw);
drivers/iio/adc/ma35d1_eadc.c line 429
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+else + *val = raw; + +return IIO_VAL_INT ; + } +case IIO_CHAN_INFO_SCALE : + *val = adc->vref_mV; +/* Differential mode has a VREF / 2048 LSB; single-ended uses /4096. */ + *val2 = chan->differential ?MA35D1_EADC_DIFF_SCALE_BITS : +MA35D1_EADC_RESOLUTION ;
Broken indentation.
Quoted 5 lines
drivers/iio/adc/ma35d1_eadc.c new file +5
-0+return IIO_VAL_FRACTIONAL_LOG2 ; +default : +return -EINVAL ; + } +}
...
drivers/iio/adc/ma35d1_eadc.c line 442
Quoted 7 lines
drivers/iio/adc/ma35d1_eadc.c new file +6
-0+static int ma35d1_adc_update_scan_mode (struct iio_dev *indio_dev, +const unsigned long *scan_mask) +{ +struct ma35d1_adc *adc =iio_priv (indio_dev); +int bit; + adc->scan_chan =NULL ;
Should we update this even in case of wrong channel type?
Quoted 14 lines
drivers/iio/adc/ma35d1_eadc.c new file +14
-0+iio_for_each_active_channel (indio_dev, bit) { +const struct iio_chan_spec *chan = &indio_dev->channels[bit]; + +if (chan->type !=IIO_VOLTAGE ) +return -EINVAL ; + + adc->scan_chan = chan; + } + +if (!adc->scan_chan) +return -EINVAL ; + +return 0 ; +}
...
drivers/iio/adc/ma35d1_eadc.c line 487
Quoted 2 lines
drivers/iio/adc/ma35d1_eadc.c new file +2
-0+return regmap_write (adc->regmap,MA35D1_EADC_STATUS2 , +MA35D1_EADC_STATUS2_ADIF0 );
struct regmap *map = adc->regmap; ... return regmap_write(map, MA35D1_EADC_STATUS2, MA35D1_EADC_STATUS2_ADIF0);
*yes, it's 81 characters long.
...
Quoted 45 lines
drivers/iio/adc/ma35d1_eadc.c new file +45
-0+static int ma35d1_adc_parse_channels (struct iio_dev *indio_dev, +struct device *dev) +{ +DECLARE_BITMAP (used_single_inputs,MA35D1_EADC_MAX_EXT_CHANNELS ); +DECLARE_BITMAP (used_diff_pairs,MA35D1_EADC_NUM_DIFF_PAIRS ); +DECLARE_BITMAP (used_regs,MA35D1_EADC_MAX_EXT_CHANNELS ); +struct iio_chan_spec *channels; +unsigned int scan_index; +int num_channels; +int ret; + +bitmap_zero (used_single_inputs,MA35D1_EADC_MAX_EXT_CHANNELS ); +bitmap_zero (used_diff_pairs,MA35D1_EADC_NUM_DIFF_PAIRS ); +bitmap_zero (used_regs,MA35D1_EADC_MAX_EXT_CHANNELS ); + + num_channels =device_get_child_node_count (dev); +if (!num_channels) +return dev_err_probe (dev, -ENOENT ,"no ADC channels configured\n" ); + +if (num_channels >MA35D1_EADC_MAX_EXT_CHANNELS ) +return dev_err_probe (dev, -ENOSPC ,"too many ADC channels\n" ); + + channels =devm_kcalloc (dev,size_add (num_channels,1 ), +sizeof (*channels),GFP_KERNEL ); +if (!channels) +return -ENOMEM ; + + scan_index =0 ; +device_for_each_child_node_scoped (dev, child) { +bool has_diff, has_single; +u32 reg, vinp, vinn; + + ret =fwnode_property_read_u32 (child,"reg" , ®); +if (ret) +return dev_err_probe (dev, ret, +"missing channel reg property\n" ); + +if (reg >=MA35D1_EADC_MAX_EXT_CHANNELS ) +return dev_err_probe (dev, -ENOSPC , +"invalid ADC channel index %u\n" , reg); + +if (test_and_set_bit (reg, used_regs)) +return dev_err_probe (dev, -EEXIST , +"duplicate ADC channel index %u\n" , reg); +
drivers/iio/adc/ma35d1_eadc.c line 606
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +9
-0+ has_single =fwnode_property_present (child,"single-channel" ); + has_diff =fwnode_property_present (child,"diff-channels" ); +if (has_single && has_diff) +return dev_err_probe (dev, -EINVAL , +"channel %u has conflicting input properties\n" , + reg); + +if (has_diff) { +u32 inputs[2 ];
No need, move this above instead of vinn and vinp. Or use custom data:
struct {
u32 p;
u32 n;
} vin;if you want to have distinct names.
Quoted 83 lines
drivers/iio/adc/ma35d1_eadc.c new file +83
-0+ ret =fwnode_property_read_u32_array (child,"diff-channels" , + inputs,ARRAY_SIZE (inputs)); +if (ret) +return dev_err_probe (dev, ret, +"invalid diff-channels for index %u\n" , + reg); + + vinp = inputs[0 ]; + vinn = inputs[1 ]; +if (vinp >=4 || vinn != vinp +4 ) +return dev_err_probe (dev, -ECHRNG , +"invalid differential inputs %u-%u\n" , + vinp, vinn); + +if (test_and_set_bit (vinp, used_diff_pairs)) +return dev_err_probe (dev, -EEXIST , +"duplicate differential inputs %u-%u\n" , + vinp, vinn); + + channels[scan_index] = (struct iio_chan_spec ) { + .type =IIO_VOLTAGE , + .indexed =1 , + .differential =1 , + .channel = vinp, + .channel2 = vinn, + .address = reg, + .scan_index = scan_index, + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_SCALE ), + .scan_type = { + .format =IIO_SCAN_FORMAT_SIGNED_INT , + .realbits =MA35D1_EADC_RESOLUTION , + .storagebits =16 , + .endianness =IIO_CPU , + }, + }; + }else { +if (has_single) { + ret =fwnode_property_read_u32 (child,"single-channel" , + &vinp); +if (ret) +return dev_err_probe (dev, ret, +"invalid single-channel for index %u\n" , + reg); + }else { + vinp = reg; + } + +if (vinp >=MA35D1_EADC_MAX_EXT_CHANNELS ) +return dev_err_probe (dev, -ECHRNG , +"invalid ADC input %u\n" , vinp); + +if (test_and_set_bit (vinp, used_single_inputs)) +return dev_err_probe (dev, -EEXIST , +"duplicate ADC input %u\n" , vinp); + + channels[scan_index] = (struct iio_chan_spec ) { + .type =IIO_VOLTAGE , + .indexed =1 , + .channel = vinp, + .address = reg, + .scan_index = scan_index, + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_SCALE ), + .scan_type = { + .format =IIO_SCAN_FORMAT_UNSIGNED_INT , + .realbits =MA35D1_EADC_RESOLUTION , + .storagebits =16 , + .endianness =IIO_CPU , + }, + }; + } + + scan_index++; + } + + channels[scan_index] =IIO_CHAN_SOFT_TIMESTAMP (scan_index); + + indio_dev->channels = channels; + indio_dev->num_channels = scan_index +1 ; + +return 0 ; +}
...
drivers/iio/adc/ma35d1_eadc.c line 699
Quoted 9 lines
drivers/iio/adc/ma35d1_eadc.c new file +8
-0+static int ma35d1_adc_init_vref (struct ma35d1_adc *adc,struct device *dev) +{ +int ret; + + ret =devm_regulator_get_enable_read_voltage (dev,"vref" ); +if (ret == -ENODEV ) { + adc->vref_mV = MA35D1_EADC_INTERNAL_VREF_mV; +return 0 ;
Can be deduplicated. See below how.
drivers/iio/adc/ma35d1_eadc.c line 708
Quoted 9 lines
drivers/iio/adc/ma35d1_eadc.c new file +9
-0+ } +if (ret <0 ) +return dev_err_probe (dev, ret,"failed to get VREF supply\n" ); + +if (!ret) +return dev_err_probe (dev, -EINVAL ,"invalid VREF voltage\n" ); + + adc->external_vref = true; + adc->vref_mV = ret / (MICRO /MILLI );
if (ret == -ENODEV) {
adc->vref_mV = MA35D1_EADC_INTERNAL_VREF_mV;
} else if (ret < 0) {
return dev_err_probe(dev, ret, "failed to get VREF supply\n");
} else {
adc->external_vref = true;
adc->vref_mV = ret / (MICRO / MILLI);
}Quoted 2 lines
(quoted diff) +2
-0+ return 0; +}
...
drivers/iio/adc/ma35d1_eadc.c line 762
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +9
-0+ adc->clk =devm_clk_get (dev,NULL ); +if (IS_ERR (adc->clk)) +return dev_err_probe (dev,PTR_ERR (adc->clk), +"failed to get ADC clock\n" ); + + ret =clk_prepare_enable (adc->clk); +if (ret) +return dev_err_probe (dev, ret,"failed to enable ADC clock\n" ); + adc->clk_enabled = true;
Repeats what __clk_is_enabled() provides.
Quoted 3 lines
drivers/iio/adc/ma35d1_eadc.c new file +3
-0+ ret =devm_add_action_or_reset (dev, ma35d1_adc_clk_disable_action, adc); +if (ret) +return ret;
...
drivers/iio/adc/ma35d1_eadc.c line 866
Quoted 7 lines
drivers/iio/adc/ma35d1_eadc.c new file +6
-0+static int ma35d1_adc_resume (struct device *dev) +{ +struct iio_dev *indio_dev =dev_get_drvdata (dev); +struct ma35d1_adc *adc =iio_priv (indio_dev); +int ret; +mutex_lock (&adc->lock);
What's wrong with guard()()? Yes, for resume triggering support this needs to be wrapped. Same Q to other explicit mutex_lock() use cases.
Quoted 25 lines
drivers/iio/adc/ma35d1_eadc.c new file +25
-0+ ret =clk_prepare_enable (adc->clk); +if (ret) + goto unlock; + + adc->clk_enabled = true; + ret =ma35d1_adc_hw_init (adc); +if (ret) { +clk_disable_unprepare (adc->clk); + adc->clk_enabled = false; + goto unlock; + } + + adc->suspended = false; +mutex_unlock (&adc->lock); + +return iio_device_resume_triggering (indio_dev); + +unlock: +mutex_unlock (&adc->lock); + +return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ma35d1_adc_pm_ops, + ma35d1_adc_suspend, ma35d1_adc_resume);
Signature · 2 lines
With Best Regards, Andy Shevchenko
Re: [PATCH v4 2/2] iio: adc: Add Nuvoton MA35D1 EADC driver
Quoted 2 lines
Repeats what
__clk_is_enabled()provides.
I would prefer to keep the local flag here. It tracks this driver's clk_prepare_enable()/clk_disable_unprepare() balance, while __clk_is_enabled() reports the clock core/hardware state. For a shared clock, it may still return true after this driver has disabled its own clock reference.
The flag is also used to avoid EADC MMIO accesses after this driver has disabled the clock during suspend.
Thanks,
Chi-Wen
Jonathan Cameron
maintainerRe: [PATCH v4 2/2] iio: adc: Add Nuvoton MA35D1 EADC driver
In reply to Chi-Wen WengQuoted 5 lines · On Wed, 4 Nov 2026 17:11:36 +0800 Chi-Wen Weng <[email protected]> wrote:
From: Chi-Wen Weng <[email protected]>
Add an IIO driver for the Enhanced Analog-to-Digital Converter found on Nuvoton MA35D1 SoCs.
Quoted 8 lines
Support single-ended and fixed differential input pairs, direct conversion, and triggered buffered capture using sample module 0.
The hardware supports multiple sample modules, but this initial driver uses sample module 0 only and therefore supports one active voltage channel per buffered scan.
Signed-off-by Chi-Wen Weng <[email protected]>
Hi Chi-Wen
Mostly I suspect I have overlapped with Andy, but maybe there are a few additional things in here to tidy up for v5! Thanks,
Jonathan
Quoted 28 lines
drivers/iio/adc/ma35d1_eadc.c new file +22
-0+#define MA35D1_EADC_DAT (n) (0x000 + (n) * 0x004) +#define MA35D1_EADC_CTL 0x050 +#define MA35D1_EADC_SWTRG 0x054 +#define MA35D1_EADC_SCTL (n) (0x080 + (n) * 0x004) +#define MA35D1_EADC_INTSRC0 0x0d0 +#define MA35D1_EADC_CMP (n) (0x0e0 + (n) * 0x004) +#define MA35D1_EADC_STATUS2 0x0f8 +#define MA35D1_EADC_SELSMP0 0x140 +#define MA35D1_EADC_REFADJCTL 0x150 + +#define MA35D1_EADC_CTL_ADCEN BIT(0) +#define MA35D1_EADC_CTL_EADCRST BIT(1) +#define MA35D1_EADC_CTL_ADCIEN0 BIT(2) +#define MA35D1_EADC_CTL_ADCIEN_MASK GENMASK(5, 2) +#define MA35D1_EADC_CTL_DIFFEN BIT(8) +#define MA35D1_EADC_CTL_DMOF BIT(9) +#define MA35D1_EADC_CTL_VREFSEL_MASK GENMASK(11, 10) +#define MA35D1_EADC_CTL_VREFSEL_1V6 FIELD_PREP(MA35D1_EADC_CTL_VREFSEL_MASK, 0) + +#define MA35D1_EADC_SCTL_CHSEL_MASK GENMASK(3, 0) +#define MA35D1_EADC_SCTL_TRGSEL_MASK GENMASK(21, 16) +#define MA35D1_EADC_SCTL_INTPOS BIT(22)
drivers/iio/adc/ma35d1_eadc.c line 68
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+ +#define MA35D1_EADC_RESOLUTION 12 +#define MA35D1_EADC_DIFF_SCALE_BITS (MA35D1_EADC_RESOLUTION - 1) + +#define MA35D1_EADC_DAT_RESULT_12BIT GENMASK(MA35D1_EADC_RESOLUTION - 1, 0) +#define MA35D1_EADC_DAT_OV BIT(16) +#define MA35D1_EADC_DAT_VALID BIT(17) + +#define MA35D1_EADC_INTSRC0_MASK GENMASK(8, 0) +#define MA35D1_EADC_INTSRC0_SPLIEN0 BIT(0)
I'm a little unclear on this. The MASK above implies a field yet, fields don't contain bits. So what is going on here? If you need a full register mask, then define all the fields and build it from those.
Quoted 1 line
+#define
MA35D1_EADC_STATUS2_ADIF0BIT(0)
I generally prefer to see register addresses, fields and values all together as it makes it easier to check naming (or datasheet values). A visual trick that works well is to use some indenting to make it clear what is what.
#define MA35D1_EADC_STATUS2 0x0f8 /* The register*/
#define MAX35D1_EADC_STATUS2_FIELD0 GENMASK(X, Y)
#define MAX35D1_EADC_STATU2_FIELD0_VAL0 0
Obviously the value bit only applies if you need defines to interpret the field values. I picked a bad register to illustrate this with as it doesn't have those :(
drivers/iio/adc/ma35d1_eadc.c line 109
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +9
-0+#define MA35D1_EADC_CMP_EADCMPIE BIT(1) +#define MA35D1_EADC_REFADJCTL_PDREF BIT(0) +#define MA35D1_EADC_SELSMP0_SMPT0_MASK GENMASK(1, 0) +#define MA35D1_EADC_SELSMP_LONG FIELD_PREP(MA35D1_EADC_SELSMP0_SMPT0_MASK, 3) + +static int ma35d1_adc_disable_irq (struct ma35d1_adc *adc) +{ +return regmap_clear_bits (adc->regmap,MA35D1_EADC_CTL ,MA35D1_EADC_CTL_ADCIEN0 ); +}
Obviously not used nearly as much, but just for visual pairing I'd be tempted to add an adc_enable_irq()
helper as well.
Quoted 19 lines
drivers/iio/adc/ma35d1_eadc.c new file +19
-0+ +static int ma35d1_adc_config_channel (struct ma35d1_adc *adc, +const struct iio_chan_spec *chan) +{ +struct regmap *map = adc->regmap; +unsigned int mask; +int ret; + + mask =MA35D1_EADC_CTL_DIFFEN |MA35D1_EADC_CTL_DMOF ; + ret =regmap_assign_bits (map,MA35D1_EADC_CTL , mask, chan->differential); +if (ret) +return ret; + +/* +* INTPOS = 0 generates ADIF0 at end of conversion. TRGSEL = 0 disables +* the hardware trigger; conversions are started through SWTRG0. +*/ + mask =MA35D1_EADC_SCTL_CHSEL_MASK | + MA35D1_EADC_SCTL_TRGSEL_MASK |
drivers/iio/adc/ma35d1_eadc.c line 278
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+MA35D1_EADC_SCTL_INTPOS ; + ret =regmap_update_bits (map,MA35D1_EADC_SCTL (0 ), mask, +FIELD_PREP (MA35D1_EADC_SCTL_CHSEL_MASK , + chan->channel)); +if (ret) +return ret; + +return regmap_update_bits (map,MA35D1_EADC_INTSRC0 , +MA35D1_EADC_INTSRC0_MASK , +MA35D1_EADC_INTSRC0_SPLIEN0 );
This code was the reason I looked more closely at what that _MASK was above. Normally when we use regmap_update_bits() it is to set a value in a field. It wasn't clear there was a field here. I'd expect this to look more like the SCTL handling just above where the mask includes all the specific fields that make up that GENMASK(8, 0)
Quoted 44 lines
drivers/iio/adc/ma35d1_eadc.c new file +44
-0+} + +static void ma35d1_adc_abort_conversion (struct ma35d1_adc *adc) +{ +struct regmap *map = adc->regmap; +struct device *dev =regmap_get_device (map); +int ret; + + ret =ma35d1_adc_disable_irq (adc); +if (ret) +dev_err (dev,"failed to disable ADC interrupt: %d\n" , ret); + +/* +* Reset the EADC control circuits to terminate a conversion which may +* still be in progress. EADCRST is cleared automatically by hardware +* when the reset completes and the EADC register values are preserved. +*/ + ret =ma35d1_adc_reset_control (adc); +if (ret) +dev_err (dev,"failed to reset timed out conversion: %d\n" , ret); + +/* Drain an interrupt which may already have been latched by the GIC. */ +synchronize_irq (adc->irq); + + ret =regmap_write (map,MA35D1_EADC_STATUS2 ,MA35D1_EADC_STATUS2_ADIF0 ); +if (ret) +dev_err (dev,"failed to clear ADC interrupt status: %d\n" , ret); +} + +static int ma35d1_adc_read_conversion (struct ma35d1_adc *adc, +const struct iio_chan_spec *chan,u16 *raw) +{ +struct regmap *map = adc->regmap; +unsigned int val; +long timeout; +int ret; + +reinit_completion (&adc->completion); + + ret =regmap_write (map,MA35D1_EADC_STATUS2 ,MA35D1_EADC_STATUS2_ADIF0 ); +if (ret) +return ret; + + ret =ma35d1_adc_config_channel (adc, chan);
drivers/iio/adc/ma35d1_eadc.c line 332
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+if (ret) +return ret; + + ret =regmap_set_bits (map,MA35D1_EADC_CTL ,MA35D1_EADC_CTL_ADCIEN0 ); +if (ret) +return ret; + + ret =regmap_write (map,MA35D1_EADC_SWTRG ,BIT (0 )); +if (ret) + goto disable_irq;
That handling is only used here. I'd pull the code up here.
drivers/iio/adc/ma35d1_eadc.c line 337
Quoted 5 lines
drivers/iio/adc/ma35d1_eadc.c new file +5
-0+ + timeout =wait_for_completion_timeout (&adc->completion, +MA35D1_EADC_TIMEOUT ); +if (!timeout) { +ma35d1_adc_abort_conversion (adc);
However, I'm not sure if the disable irq should be here? It is in side there so maybe add a comment just to avoid confusion as I guess the careful ordering of the abort_conversion function matters.
Quoted 13 lines
drivers/iio/adc/ma35d1_eadc.c new file +13
-0+return -ETIMEDOUT ; + } + + ret =ma35d1_adc_disable_irq (adc); +if (ret) +return ret; + + ret =regmap_read (map,MA35D1_EADC_DAT (0 ), &val); +if (ret) +return ret; + +if (!(val &MA35D1_EADC_DAT_VALID )) +return -EIO ;
drivers/iio/adc/ma35d1_eadc.c line 360
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+ +if (val &MA35D1_EADC_DAT_OV ) +return -EOVERFLOW ; + + *raw =FIELD_GET (MA35D1_EADC_DAT_RESULT_12BIT , val); + +return 0 ; + +disable_irq: +ma35d1_adc_disable_irq (adc);
See above. I suggest adding an _enable_irq() helper. The reason for that was this code as I couldn't immediately spot what this paired with. There are also few places where this is relevant. At least one seems to leave it on (the abort_conversion one)
drivers/iio/adc/ma35d1_eadc.c line 393
Quoted 9 lines
drivers/iio/adc/ma35d1_eadc.c new file +8
-0+ +return ret; +} + +static int ma35d1_adc_sign_extend (u16 raw) +{ +return sign_extend32 (raw,MA35D1_EADC_DIFF_SCALE_BITS ); +}
Is there enough value in this helper to make it worth the reader having to go see what it does?
Marginal I think. If this was to avoid a long line, don't worry too much about it as only ever so slightly over 80 chars.
Quoted 46 lines
drivers/iio/adc/ma35d1_eadc.c new file +46
-0+ +static int ma35d1_adc_read_raw (struct iio_dev *indio_dev, +const struct iio_chan_spec *chan, +int *val,int *val2,long mask) +{ +struct ma35d1_adc *adc =iio_priv (indio_dev); +u16 raw; +int ret; + +switch (mask) { +case IIO_CHAN_INFO_RAW : { +IIO_DEV_ACQUIRE_DIRECT_MODE (indio_dev, claim); +if (IIO_DEV_ACQUIRE_FAILED (claim)) +return -EBUSY ; + +guard (mutex)(&adc->lock); + +if (adc->suspended) +return -EBUSY ; + + ret =ma35d1_adc_read_conversion (adc, chan, &raw); +if (ret) +return ret; + +if (chan->differential) + *val =ma35d1_adc_sign_extend (raw); +else + *val = raw; + +return IIO_VAL_INT ; + } +case IIO_CHAN_INFO_SCALE : + *val = adc->vref_mV; +/* Differential mode has a VREF / 2048 LSB; single-ended uses /4096. */ + *val2 = chan->differential ?MA35D1_EADC_DIFF_SCALE_BITS : +MA35D1_EADC_RESOLUTION ; +return IIO_VAL_FRACTIONAL_LOG2 ; +default : +return -EINVAL ; + } +} + +static int ma35d1_adc_update_scan_mode (struct iio_dev *indio_dev, +const unsigned long *scan_mask) +{ +struct ma35d1_adc *adc =iio_priv (indio_dev);
drivers/iio/adc/ma35d1_eadc.c line 449
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+int bit; + + adc->scan_chan =NULL ; +iio_for_each_active_channel (indio_dev, bit) { +const struct iio_chan_spec *chan = &indio_dev->channels[bit]; + +if (chan->type !=IIO_VOLTAGE ) +return -EINVAL ; + + adc->scan_chan = chan;
This seems to hint you can be other than onehot. I think not, so just break out of the loop here.
Quoted 52 lines
drivers/iio/adc/ma35d1_eadc.c new file +51
-0+ } + +if (!adc->scan_chan) +return -EINVAL ; + +return 0 ; +} + +static int ma35d1_adc_parse_channels (struct iio_dev *indio_dev, +struct device *dev) +{ +DECLARE_BITMAP (used_single_inputs,MA35D1_EADC_MAX_EXT_CHANNELS ); +DECLARE_BITMAP (used_diff_pairs,MA35D1_EADC_NUM_DIFF_PAIRS ); +DECLARE_BITMAP (used_regs,MA35D1_EADC_MAX_EXT_CHANNELS ); +struct iio_chan_spec *channels; +unsigned int scan_index; +int num_channels; +int ret; + +bitmap_zero (used_single_inputs,MA35D1_EADC_MAX_EXT_CHANNELS ); +bitmap_zero (used_diff_pairs,MA35D1_EADC_NUM_DIFF_PAIRS ); +bitmap_zero (used_regs,MA35D1_EADC_MAX_EXT_CHANNELS ); + + num_channels =device_get_child_node_count (dev); +if (!num_channels) +return dev_err_probe (dev, -ENOENT ,"no ADC channels configured\n" ); + +if (num_channels >MA35D1_EADC_MAX_EXT_CHANNELS ) +return dev_err_probe (dev, -ENOSPC ,"too many ADC channels\n" ); + + channels =devm_kcalloc (dev,size_add (num_channels,1 ), +sizeof (*channels),GFP_KERNEL ); +if (!channels) +return -ENOMEM ; + + scan_index =0 ; +device_for_each_child_node_scoped (dev, child) { +bool has_diff, has_single; +u32 reg, vinp, vinn; + + ret =fwnode_property_read_u32 (child,"reg" , ®); +if (ret) +return dev_err_probe (dev, ret, +"missing channel reg property\n" ); + +if (reg >=MA35D1_EADC_MAX_EXT_CHANNELS ) +return dev_err_probe (dev, -ENOSPC , +"invalid ADC channel index %u\n" , reg); + +if (test_and_set_bit (reg, used_regs)) + return dev_err_probe(dev, -EEXIST,
drivers/iio/adc/ma35d1_eadc.c line 605
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+ "duplicate ADC channel index %u\n", reg); + + has_single =fwnode_property_present (child,"single-channel" ); + has_diff =fwnode_property_present (child,"diff-channels" ); +if (has_single && has_diff) +return dev_err_probe (dev, -EINVAL , +"channel %u has conflicting input properties\n" , + reg); + +if (has_diff) {
The indent is getting a bit large, perhaps a pair of helpers to cover differential vs single would make this more readable?
Quoted 88 lines
drivers/iio/adc/ma35d1_eadc.c new file +87
-0+u32 inputs[2 ]; + + ret =fwnode_property_read_u32_array (child,"diff-channels" , + inputs,ARRAY_SIZE (inputs)); +if (ret) +return dev_err_probe (dev, ret, +"invalid diff-channels for index %u\n" , + reg); + + vinp = inputs[0 ]; + vinn = inputs[1 ]; +if (vinp >=4 || vinn != vinp +4 ) +return dev_err_probe (dev, -ECHRNG , +"invalid differential inputs %u-%u\n" , + vinp, vinn); + +if (test_and_set_bit (vinp, used_diff_pairs)) +return dev_err_probe (dev, -EEXIST , +"duplicate differential inputs %u-%u\n" , + vinp, vinn); + + channels[scan_index] = (struct iio_chan_spec ) { + .type =IIO_VOLTAGE , + .indexed =1 , + .differential =1 , + .channel = vinp, + .channel2 = vinn, + .address = reg, + .scan_index = scan_index, + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_SCALE ), + .scan_type = { + .format =IIO_SCAN_FORMAT_SIGNED_INT , + .realbits =MA35D1_EADC_RESOLUTION , + .storagebits =16 , + .endianness =IIO_CPU , + }, + }; + }else { +if (has_single) { + ret =fwnode_property_read_u32 (child,"single-channel" , + &vinp); +if (ret) +return dev_err_probe (dev, ret, +"invalid single-channel for index %u\n" , + reg); + }else { + vinp = reg; + } + +if (vinp >=MA35D1_EADC_MAX_EXT_CHANNELS ) +return dev_err_probe (dev, -ECHRNG , +"invalid ADC input %u\n" , vinp); + +if (test_and_set_bit (vinp, used_single_inputs)) +return dev_err_probe (dev, -EEXIST , +"duplicate ADC input %u\n" , vinp); + + channels[scan_index] = (struct iio_chan_spec ) { + .type =IIO_VOLTAGE , + .indexed =1 , + .channel = vinp, + .address = reg, + .scan_index = scan_index, + .info_mask_separate =BIT (IIO_CHAN_INFO_RAW ) | +BIT (IIO_CHAN_INFO_SCALE ), + .scan_type = { + .format =IIO_SCAN_FORMAT_UNSIGNED_INT , + .realbits =MA35D1_EADC_RESOLUTION , + .storagebits =16 , + .endianness =IIO_CPU , + }, + }; + } + + scan_index++; + } + + channels[scan_index] =IIO_CHAN_SOFT_TIMESTAMP (scan_index); + + indio_dev->channels = channels; + indio_dev->num_channels = scan_index +1 ; + +return 0 ; +} +static int ma35d1_adc_suspend (struct device *dev) +{
drivers/iio/adc/ma35d1_eadc.c
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+struct iio_dev *indio_dev =dev_get_drvdata (dev); +struct ma35d1_adc *adc =iio_priv (indio_dev); +struct regmap *map = adc->regmap; +int ret; + + ret =iio_device_suspend_triggering (indio_dev); +if (ret) +return ret; + +mutex_lock (&adc->lock);
This one is trickier than the case below. A simple refactor to pull out everything from here onwards though and you can do
ret = iio_device_suspend_triggering(indio_dev); if (ret) return ret; ret = whateverhelper(); if (ret) iio_device_resume_triggering(indio_dev) return ret;
I see Andy raised this as well, but wanted to add a few more breadcrumbs on how I'd suggest improving the code to meet his suggestion.
Quoted 28 lines
drivers/iio/adc/ma35d1_eadc.c new file +28
-0+ + ret =ma35d1_adc_disable_irq (adc); +if (ret) + goto resume_triggering; + +synchronize_irq (adc->irq); + + ret =regmap_clear_bits (map,MA35D1_EADC_CTL ,MA35D1_EADC_CTL_ADCEN ); +if (ret) { +/* +* ADCIEN0 is enabled only while a conversion is active. Holding +* adc->lock here guarantees that no conversion is in progress, so +* leave it disabled. The next conversion enables it again. +*/ + goto resume_triggering; + } + + adc->suspended = true; +clk_disable_unprepare (adc->clk); + adc->clk_enabled = false; +mutex_unlock (&adc->lock); + +return 0 ; + +resume_triggering: +mutex_unlock (&adc->lock); +iio_device_resume_triggering (indio_dev); +
drivers/iio/adc/ma35d1_eadc.c line 866
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+return ret; +} + +static int ma35d1_adc_resume (struct device *dev) +{ +struct iio_dev *indio_dev =dev_get_drvdata (dev); +struct ma35d1_adc *adc =iio_priv (indio_dev); +int ret; + +mutex_lock (&adc->lock);
scoped_guard() or a suitable helper will simplify the code flow here.
Quoted 23 lines
drivers/iio/adc/ma35d1_eadc.c new file +23
-0+ + ret =clk_prepare_enable (adc->clk); +if (ret) + goto unlock; + + adc->clk_enabled = true; + ret =ma35d1_adc_hw_init (adc); +if (ret) { +clk_disable_unprepare (adc->clk); + adc->clk_enabled = false; + goto unlock; + } + + adc->suspended = false; +mutex_unlock (&adc->lock); + +return iio_device_resume_triggering (indio_dev); + +unlock: +mutex_unlock (&adc->lock); + +return ret; +}
Re: [PATCH v4 2/2] iio: adc: Add Nuvoton MA35D1 EADC driver
In reply to Chi-Wen WengQuoted 13 lines
drivers/iio/adc/ma35d1_eadc.c new file +13
-0+static void ma35d1_adc_trigger_capture (struct iio_poll_func *pf) +{ +struct iio_dev *indio_dev = pf->indio_dev; +struct ma35d1_adc *adc =iio_priv (indio_dev); +struct { +u16 channel; +aligned_s64 timestamp; + } scan = { }; +u16 raw; +int ret; + +guard (mutex)(&adc->lock); +
drivers/iio/adc/ma35d1_eadc.c line 512
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+if (adc->suspended || !adc->scan_chan) +return ; + + ret =ma35d1_adc_read_conversion (adc, adc->scan_chan, &raw); +if (ret) +return ; + + scan.channel = raw; +iio_push_to_buffers_with_ts (indio_dev, &scan,sizeof (scan), pf->timestamp); +}
Do you need 'raw'? Seems like you could read into scan.channel directly:
ret = ma35d1_adc_read_conversion(adc, adc->scan_chan, &scan.channel);
drivers/iio/adc/ma35d1_eadc.c line 576
Quoted 9 lines
drivers/iio/adc/ma35d1_eadc.c new file +9
-0+ num_channels =device_get_child_node_count (dev); +if (!num_channels) +return dev_err_probe (dev, -ENOENT ,"no ADC channels configured\n" ); + +if (num_channels >MA35D1_EADC_MAX_EXT_CHANNELS ) +return dev_err_probe (dev, -ENOSPC ,"too many ADC channels\n" ); + + channels =devm_kcalloc (dev,size_add (num_channels,1 ), +sizeof (*channels),GFP_KERNEL );
Couldn't size_add() just be regular +?
(Question to Andy who originally suggested it)
We've just checked that num_channels is small and won't overflow.
Re: [PATCH v4 2/2] iio: adc: Add Nuvoton MA35D1 EADC driver
On Tue, Nov 10, 2026 at 08:08:03PM -0600, Victor Chernyakin wrote:
...
Quoted 4 lines
Quoted 4 lines
drivers/iio/adc/ma35d1_eadc.c new file +4
-0+if (!num_channels) +return dev_err_probe (dev, -ENOENT ,"no ADC channels configured\n" ); + +if (num_channels >MA35D1_EADC_MAX_EXT_CHANNELS )
Quoted 8 lines
drivers/iio/adc/ma35d1_eadc.c line 576
Quoted 4 lines
drivers/iio/adc/ma35d1_eadc.c new file +4
-0+return dev_err_probe (dev, -ENOSPC ,"too many ADC channels\n" ); + + channels =devm_kcalloc (dev,size_add (num_channels,1 ), +sizeof (*channels),GFP_KERNEL );Couldn't
size_add()just be regular +?
(Question to Andy who originally suggested it)
We've just checked thatnum_channelsis small and won't overflow.
While that's true, the other aspect(s) of this is in play. Namely we want to have the same pattern over the kernel that makes code robust (tomorrow some cases can be modified unnoticed with opening such an issue again) along with the potential of finding those patterns and update in case of mass-updates of low-level API, and I have heard for the matter of fact that devm_*alloc*() are in the plans to move to something like k*alloc_obj*() or k*alloc_flex().
Signature · 2 lines
With Best Regards, Andy Shevchenko
Re: [PATCH v4 2/2] iio: adc: Add Nuvoton MA35D1 EADC driver
Andy Shevchenko 於 2026/11/11 下午 10:05 寫道:
Quoted 17 lines · Victor Chernyakin
...
Quoted 4 lines
Quoted 4 lines
drivers/iio/adc/ma35d1_eadc.c new file +4
-0+if (!num_channels) +return dev_err_probe (dev, -ENOENT ,"no ADC channels configured\n" ); + +if (num_channels >MA35D1_EADC_MAX_EXT_CHANNELS )Quoted 8 lines
drivers/iio/adc/ma35d1_eadc.c line 576
Quoted 4 lines
drivers/iio/adc/ma35d1_eadc.c new file +4
-0+return dev_err_probe (dev, -ENOSPC ,"too many ADC channels\n" ); + + channels =devm_kcalloc (dev,size_add (num_channels,1 ), +sizeof (*channels),GFP_KERNEL );Couldn't
size_add()just be regular +?
(Question to Andy who originally suggested it)
We've just checked thatnum_channelsis small and won't overflow.
Quoted 7 lines
While that's true, the other aspect(s) of this is in play. Namely we want to have the same pattern over the kernel that makes code robust (tomorrow some cases can be modified unnoticed with opening such an issue again) along with the potential of finding those patterns and update in case of mass-updates of low-level API, and I have heard for the matter of fact that devm_*alloc*() are in the plans to move to something like k*alloc_obj*() or k*alloc_flex().
Thanks Victor and Andy for the discussion.
I'll keep size_add() for consistency and future-proofing, as Andy suggested.
Thanks,
Chi-Wen
Re: [PATCH v4 2/2] iio: adc: Add Nuvoton MA35D1 EADC driver
In reply to Chi-Wen WengQuoted 4 lines
drivers/iio/adc/ma35d1_eadc.c new file +4
-0+static int ma35d1_adc_parse_channels (struct iio_dev *indio_dev, +struct device *dev) +{ +DECLARE_BITMAP (used_single_inputs,MA35D1_EADC_MAX_EXT_CHANNELS );
drivers/iio/adc/ma35d1_eadc.c line 566
Quoted 10 lines
drivers/iio/adc/ma35d1_eadc.c new file +10
-0+DECLARE_BITMAP (used_diff_pairs,MA35D1_EADC_NUM_DIFF_PAIRS ); +DECLARE_BITMAP (used_regs,MA35D1_EADC_MAX_EXT_CHANNELS ); +struct iio_chan_spec *channels; +unsigned int scan_index; +int num_channels; +int ret; + +bitmap_zero (used_single_inputs,MA35D1_EADC_MAX_EXT_CHANNELS ); +bitmap_zero (used_diff_pairs,MA35D1_EADC_NUM_DIFF_PAIRS ); +bitmap_zero (used_regs,MA35D1_EADC_MAX_EXT_CHANNELS );
The bitmaps can be initialized directly:
DECLARE_BITMAP(used_single_inputs, MA35D1_EADC_MAX_EXT_CHANNELS) = { }; DECLARE_BITMAP(used_diff_pairs, MA35D1_EADC_NUM_DIFF_PAIRS) = { }; DECLARE_BITMAP(used_regs, MA35D1_EADC_MAX_EXT_CHANNELS) = { };
There are several drivers in IIO that do it this way, you can check by grepping for DECLARE_BITMAP.