* [PATCH v5 0/6] iio: adc: add support for ADE9000 Energy Monitoring IC @ 2025-08-22 16:01 Antoniu Miclaus 2025-08-22 16:01 ` [PATCH v5 1/6] iio: add IIO_ALTCURRENT channel type Antoniu Miclaus ` (5 more replies) 0 siblings, 6 replies; 14+ messages in thread From: Antoniu Miclaus @ 2025-08-22 16:01 UTC (permalink / raw) To: jic23, robh, conor+dt, linux-iio, linux-kernel, devicetree Cc: Antoniu Miclaus This patch series adds support for the Analog Devices ADE9000, a highly accurate, fully integrated, multiphase energy and power quality monitoring device. The ADE9000 is capable of measuring energy consumption and power quality parameters in industrial and commercial applications. The series includes: 1. New IIO modifiers for power and energy measurement devices, including support for active/reactive/apparent power, RMS masurements. 2. Device tree bindings for the ADE9000, supporting waveform buffer configuration, phase configuration, and trigger settings. 3. Complete driver implementation supporting: - Multi-phase energy measurement (3-phase support) - Power quality monitoring (voltage swell/dip detection) - Waveform buffer capture with configurable triggering - Energy accumulation with configurable time windows - IIO buffer interface for continuous data streaming - Event-based notifications for power quality events The driver provides a comprehensive interface for energy monitoring applications through the IIO framework, enabling userspace applications to monitor power consumption, quality, and waveform data. The driver will be extended in the future to support multiple parts such as ade9039. Antoniu Miclaus (6): iio: add IIO_ALTCURRENT channel type iio: add power and energy measurement modifiers dt-bindings: iio: adc: add ade9000 iio: adc: add ade9000 support docs: iio: add documentation for ade9000 driver Documentation: ABI: iio: add sinc4+lp Documentation/ABI/testing/sysfs-bus-iio | 30 + .../bindings/iio/adc/adi,ade9000.yaml | 97 + Documentation/iio/ade9000.rst | 286 +++ Documentation/iio/index.rst | 1 + drivers/iio/adc/Kconfig | 19 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ade9000.c | 2011 +++++++++++++++++ drivers/iio/industrialio-core.c | 6 + include/linux/iio/types.h | 1 + include/uapi/linux/iio/types.h | 5 + tools/iio/iio_event_monitor.c | 2 + 11 files changed, 2459 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml create mode 100644 Documentation/iio/ade9000.rst create mode 100644 drivers/iio/adc/ade9000.c -- 2.43.0 ^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v5 1/6] iio: add IIO_ALTCURRENT channel type 2025-08-22 16:01 [PATCH v5 0/6] iio: adc: add support for ADE9000 Energy Monitoring IC Antoniu Miclaus @ 2025-08-22 16:01 ` Antoniu Miclaus 2025-08-22 16:01 ` [PATCH v5 2/6] iio: add power and energy measurement modifiers Antoniu Miclaus ` (4 subsequent siblings) 5 siblings, 0 replies; 14+ messages in thread From: Antoniu Miclaus @ 2025-08-22 16:01 UTC (permalink / raw) To: jic23, robh, conor+dt, linux-iio, linux-kernel, devicetree Cc: Antoniu Miclaus Add support for IIO_ALTCURRENT channel type to distinguish AC current measurements from DC current measurements. This follows the same pattern as IIO_VOLTAGE and IIO_ALTVOLTAGE. Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> --- no changes in v5. drivers/iio/industrialio-core.c | 1 + include/uapi/linux/iio/types.h | 1 + tools/iio/iio_event_monitor.c | 2 ++ 3 files changed, 4 insertions(+) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index f13c3aa470d7..8c9098668772 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -97,6 +97,7 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_COLORTEMP] = "colortemp", [IIO_CHROMATICITY] = "chromaticity", [IIO_ATTENTION] = "attention", + [IIO_ALTCURRENT] = "altcurrent", }; static const char * const iio_modifier_names[] = { diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h index 3eb0821af7a4..3c3cc1497a1e 100644 --- a/include/uapi/linux/iio/types.h +++ b/include/uapi/linux/iio/types.h @@ -52,6 +52,7 @@ enum iio_chan_type { IIO_COLORTEMP, IIO_CHROMATICITY, IIO_ATTENTION, + IIO_ALTCURRENT, }; enum iio_modifier { diff --git a/tools/iio/iio_event_monitor.c b/tools/iio/iio_event_monitor.c index eab7b082f19d..d26aff649f3f 100644 --- a/tools/iio/iio_event_monitor.c +++ b/tools/iio/iio_event_monitor.c @@ -64,6 +64,7 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_COLORTEMP] = "colortemp", [IIO_CHROMATICITY] = "chromaticity", [IIO_ATTENTION] = "attention", + [IIO_ALTCURRENT] = "altcurrent", }; static const char * const iio_ev_type_text[] = { @@ -187,6 +188,7 @@ static bool event_is_known(struct iio_event_data *event) case IIO_COLORTEMP: case IIO_CHROMATICITY: case IIO_ATTENTION: + case IIO_ALTCURRENT: break; default: return false; -- 2.43.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v5 2/6] iio: add power and energy measurement modifiers 2025-08-22 16:01 [PATCH v5 0/6] iio: adc: add support for ADE9000 Energy Monitoring IC Antoniu Miclaus 2025-08-22 16:01 ` [PATCH v5 1/6] iio: add IIO_ALTCURRENT channel type Antoniu Miclaus @ 2025-08-22 16:01 ` Antoniu Miclaus 2025-08-22 16:01 ` [PATCH v5 3/6] dt-bindings: iio: adc: add ade9000 Antoniu Miclaus ` (3 subsequent siblings) 5 siblings, 0 replies; 14+ messages in thread From: Antoniu Miclaus @ 2025-08-22 16:01 UTC (permalink / raw) To: jic23, robh, conor+dt, linux-iio, linux-kernel, devicetree Cc: Antoniu Miclaus Add new IIO modifiers to support power and energy measurement devices: Power modifiers: - IIO_MOD_ACTIVE: Real power consumed by the load - IIO_MOD_REACTIVE: Power that oscillates between source and load - IIO_MOD_APPARENT: Magnitude of complex power - IIO_MOD_FUND_REACTIVE: Reactive power at fundamental frequency - IIO_MOD_FACTOR: Power factor (ratio of active to apparent power) Signal quality modifiers: - IIO_MOD_RMS: Root Mean Square value These modifiers enable proper representation of power measurement devices like energy meters and power analyzers. Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> --- no changes in v5. Documentation/ABI/testing/sysfs-bus-iio | 29 +++++++++++++++++++++++++ drivers/iio/industrialio-core.c | 5 +++++ include/linux/iio/types.h | 1 + include/uapi/linux/iio/types.h | 4 ++++ 4 files changed, 39 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 2fb2cea4b192..78da68826307 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -167,7 +167,18 @@ Description: is required is a consistent labeling. Units after application of scale and offset are millivolts. +What: /sys/bus/iio/devices/iio:deviceX/in_altvoltageY_rms_raw +KernelVersion: 6.18 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled) Root Mean Square (RMS) voltage measurement from + channel Y. Units after application of scale and offset are + millivolts. + What: /sys/bus/iio/devices/iio:deviceX/in_powerY_raw +What: /sys/bus/iio/devices/iio:deviceX/in_powerY_active_raw +What: /sys/bus/iio/devices/iio:deviceX/in_powerY_reactive_raw +What: /sys/bus/iio/devices/iio:deviceX/in_powerY_apparent_raw KernelVersion: 4.5 Contact: linux-iio@vger.kernel.org Description: @@ -176,6 +187,13 @@ Description: unique to allow association with event codes. Units after application of scale and offset are milliwatts. +What: /sys/bus/iio/devices/iio:deviceX/in_powerY_powerfactor +KernelVersion: 6.18 +Contact: linux-iio@vger.kernel.org +Description: + Power factor measurement from channel Y. Power factor is the + ratio of active power to apparent power. The value is unitless. + What: /sys/bus/iio/devices/iio:deviceX/in_capacitanceY_raw KernelVersion: 3.2 Contact: linux-iio@vger.kernel.org @@ -1569,6 +1587,9 @@ Description: What: /sys/.../iio:deviceX/in_energy_input What: /sys/.../iio:deviceX/in_energy_raw +What: /sys/.../iio:deviceX/in_energyY_active_raw +What: /sys/.../iio:deviceX/in_energyY_reactive_raw +What: /sys/.../iio:deviceX/in_energyY_apparent_raw KernelVersion: 4.0 Contact: linux-iio@vger.kernel.org Description: @@ -1707,6 +1728,14 @@ Description: component of the signal while the 'q' channel contains the quadrature component. +What: /sys/bus/iio/devices/iio:deviceX/in_altcurrentY_rms_raw +KernelVersion: 6.18 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled no bias removal etc.) Root Mean Square (RMS) current + measurement from channel Y. Units after application of scale and + offset are milliamps. + What: /sys/.../iio:deviceX/in_energy_en What: /sys/.../iio:deviceX/in_distance_en What: /sys/.../iio:deviceX/in_velocity_sqrt(x^2+y^2+z^2)_en diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 8c9098668772..9e372ed38552 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -153,6 +153,10 @@ static const char * const iio_modifier_names[] = { [IIO_MOD_PITCH] = "pitch", [IIO_MOD_YAW] = "yaw", [IIO_MOD_ROLL] = "roll", + [IIO_MOD_RMS] = "rms", + [IIO_MOD_ACTIVE] = "active", + [IIO_MOD_REACTIVE] = "reactive", + [IIO_MOD_APPARENT] = "apparent", }; /* relies on pairs of these shared then separate */ @@ -190,6 +194,7 @@ static const char * const iio_chan_info_postfix[] = { [IIO_CHAN_INFO_ZEROPOINT] = "zeropoint", [IIO_CHAN_INFO_TROUGH] = "trough_raw", [IIO_CHAN_INFO_CONVDELAY] = "convdelay", + [IIO_CHAN_INFO_POWERFACTOR] = "powerfactor", }; /** * iio_device_id() - query the unique ID for the device diff --git a/include/linux/iio/types.h b/include/linux/iio/types.h index ad2761efcc83..34eebad12d2c 100644 --- a/include/linux/iio/types.h +++ b/include/linux/iio/types.h @@ -70,6 +70,7 @@ enum iio_chan_info_enum { IIO_CHAN_INFO_ZEROPOINT, IIO_CHAN_INFO_TROUGH, IIO_CHAN_INFO_CONVDELAY, + IIO_CHAN_INFO_POWERFACTOR, }; #endif /* _IIO_TYPES_H_ */ diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h index 3c3cc1497a1e..6d269b844271 100644 --- a/include/uapi/linux/iio/types.h +++ b/include/uapi/linux/iio/types.h @@ -109,6 +109,10 @@ enum iio_modifier { IIO_MOD_ROLL, IIO_MOD_LIGHT_UVA, IIO_MOD_LIGHT_UVB, + IIO_MOD_RMS, + IIO_MOD_ACTIVE, + IIO_MOD_REACTIVE, + IIO_MOD_APPARENT, }; enum iio_event_type { -- 2.43.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v5 3/6] dt-bindings: iio: adc: add ade9000 2025-08-22 16:01 [PATCH v5 0/6] iio: adc: add support for ADE9000 Energy Monitoring IC Antoniu Miclaus 2025-08-22 16:01 ` [PATCH v5 1/6] iio: add IIO_ALTCURRENT channel type Antoniu Miclaus 2025-08-22 16:01 ` [PATCH v5 2/6] iio: add power and energy measurement modifiers Antoniu Miclaus @ 2025-08-22 16:01 ` Antoniu Miclaus 2025-08-25 13:10 ` Jonathan Cameron 2025-08-26 22:46 ` Rob Herring 2025-08-22 16:01 ` [PATCH v5 4/6] iio: adc: add ade9000 support Antoniu Miclaus ` (2 subsequent siblings) 5 siblings, 2 replies; 14+ messages in thread From: Antoniu Miclaus @ 2025-08-22 16:01 UTC (permalink / raw) To: jic23, robh, conor+dt, linux-iio, linux-kernel, devicetree Cc: Antoniu Miclaus Add devicetree bindings support for ade9000. Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> --- changes in v5: - remove clock-output-names property (simplified clock output) - make interrupts, reset-gpios, and interrupt-names optional (removed from required list) - improve interrupt-names description to allow any subset of irq0, irq1, dready - fix typo in description ("ADE9000 s a" -> "ADE9000 is a") - fix spacing in description ("analog-to- digital" -> "analog-to-digital") - uncomment clock example in device tree example .../bindings/iio/adc/adi,ade9000.yaml | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml new file mode 100644 index 000000000000..a1513ad41651 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml @@ -0,0 +1,97 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright 2025 Analog Devices Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,ade9000.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices ADE9000 High Performance, Polyphase Energy Metering driver + +maintainers: + - Antoniu Miclaus <antoniu.miclaus@analog.com> + +description: | + The ADE9000 is a highly accurate, fully integrated, multiphase energy and power + quality monitoring device. Superior analog performance and a digital signal + processing (DSP) core enable accurate energy monitoring over a wide dynamic + range. An integrated high end reference ensures low drift over temperature + with a combined drift of less than ±25 ppm/°C maximum for the entire channel + including a programmable gain amplifier (PGA) and an analog-to-digital + converter (ADC). + + https://www.analog.com/media/en/technical-documentation/data-sheets/ADE9000.pdf + +$ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + enum: + - adi,ade9000 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 20000000 + + interrupts: + maxItems: 3 + + interrupt-names: + description: Optional interrupt names. Any subset of irq0, irq1, dready. + maxItems: 3 + + reset-gpios: + description: + Must be the device tree identifier of the RESET pin. As the line is + active low, it should be marked GPIO_ACTIVE_LOW. + maxItems: 1 + + vdd-supply: true + + vref-supply: true + + clocks: + description: External clock source when not using crystal + maxItems: 1 + + clock-names: + items: + - const: clkin + + "#clock-cells": + description: + ADE9000 can provide clock output via CLKOUT pin with external buffer. + const: 0 + +required: + - compatible + - reg + - vdd-supply + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "adi,ade9000"; + reg = <0>; + spi-max-frequency = <7000000>; + + #clock-cells = <0>; + reset-gpios = <&gpio 4 GPIO_ACTIVE_LOW>; + interrupts = <2 IRQ_TYPE_EDGE_FALLING>, <3 IRQ_TYPE_EDGE_FALLING>, <4 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "irq0", "irq1", "dready"; + interrupt-parent = <&gpio>; + clocks = <&ext_clock_24576khz>; + clock-names = "clkin"; + vdd-supply = <&vdd_reg>; + }; + }; -- 2.43.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v5 3/6] dt-bindings: iio: adc: add ade9000 2025-08-22 16:01 ` [PATCH v5 3/6] dt-bindings: iio: adc: add ade9000 Antoniu Miclaus @ 2025-08-25 13:10 ` Jonathan Cameron 2025-08-26 22:46 ` Rob Herring 1 sibling, 0 replies; 14+ messages in thread From: Jonathan Cameron @ 2025-08-25 13:10 UTC (permalink / raw) To: Antoniu Miclaus; +Cc: robh, conor+dt, linux-iio, linux-kernel, devicetree On Fri, 22 Aug 2025 16:01:52 +0000 Antoniu Miclaus <antoniu.miclaus@analog.com> wrote: > Add devicetree bindings support for ade9000. > > Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> one minor thing inline. > --- > changes in v5: > - remove clock-output-names property (simplified clock output) > - make interrupts, reset-gpios, and interrupt-names optional (removed from required list) > - improve interrupt-names description to allow any subset of irq0, irq1, dready > - fix typo in description ("ADE9000 s a" -> "ADE9000 is a") > - fix spacing in description ("analog-to- digital" -> "analog-to-digital") > - uncomment clock example in device tree example > .../bindings/iio/adc/adi,ade9000.yaml | 97 +++++++++++++++++++ > 1 file changed, 97 insertions(+) > create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml > > diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml > new file mode 100644 > index 000000000000..a1513ad41651 > --- /dev/null > +++ b/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml > @@ -0,0 +1,97 @@ > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) > +# Copyright 2025 Analog Devices Inc. > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/iio/adc/adi,ade9000.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: Analog Devices ADE9000 High Performance, Polyphase Energy Metering driver > + > +maintainers: > + - Antoniu Miclaus <antoniu.miclaus@analog.com> > + > +description: | > + The ADE9000 is a highly accurate, fully integrated, multiphase energy and power > + quality monitoring device. Superior analog performance and a digital signal > + processing (DSP) core enable accurate energy monitoring over a wide dynamic > + range. An integrated high end reference ensures low drift over temperature > + with a combined drift of less than ±25 ppm/°C maximum for the entire channel > + including a programmable gain amplifier (PGA) and an analog-to-digital > + converter (ADC). > + > + https://www.analog.com/media/en/technical-documentation/data-sheets/ADE9000.pdf > + > +$ref: /schemas/spi/spi-peripheral-props.yaml# > + > +properties: > + compatible: > + enum: > + - adi,ade9000 > + > + reg: > + maxItems: 1 > + > + spi-max-frequency: > + maximum: 20000000 > + > + interrupts: > + maxItems: 3 > + > + interrupt-names: > + description: Optional interrupt names. Any subset of irq0, irq1, dready. The binding should enforce that list of possible values. Lots of examples in tree. Basically an enum combined with maxItems / minItems to say 1 to 3 of them. I'm not sure if that is latest preferred way of doing this though. > + maxItems: 3 > + > + reset-gpios: > + description: > + Must be the device tree identifier of the RESET pin. As the line is > + active low, it should be marked GPIO_ACTIVE_LOW. > + maxItems: 1 > + > + vdd-supply: true > + > + vref-supply: true > + > + clocks: > + description: External clock source when not using crystal > + maxItems: 1 > + > + clock-names: > + items: > + - const: clkin > + > + "#clock-cells": > + description: > + ADE9000 can provide clock output via CLKOUT pin with external buffer. > + const: 0 > + > +required: > + - compatible > + - reg > + - vdd-supply > + > +unevaluatedProperties: false > + > +examples: > + - | > + #include <dt-bindings/gpio/gpio.h> > + #include <dt-bindings/interrupt-controller/irq.h> > + > + spi { > + #address-cells = <1>; > + #size-cells = <0>; > + > + adc@0 { > + compatible = "adi,ade9000"; > + reg = <0>; > + spi-max-frequency = <7000000>; > + > + #clock-cells = <0>; > + reset-gpios = <&gpio 4 GPIO_ACTIVE_LOW>; > + interrupts = <2 IRQ_TYPE_EDGE_FALLING>, <3 IRQ_TYPE_EDGE_FALLING>, <4 IRQ_TYPE_EDGE_FALLING>; > + interrupt-names = "irq0", "irq1", "dready"; > + interrupt-parent = <&gpio>; > + clocks = <&ext_clock_24576khz>; > + clock-names = "clkin"; > + vdd-supply = <&vdd_reg>; > + }; > + }; ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v5 3/6] dt-bindings: iio: adc: add ade9000 2025-08-22 16:01 ` [PATCH v5 3/6] dt-bindings: iio: adc: add ade9000 Antoniu Miclaus 2025-08-25 13:10 ` Jonathan Cameron @ 2025-08-26 22:46 ` Rob Herring 1 sibling, 0 replies; 14+ messages in thread From: Rob Herring @ 2025-08-26 22:46 UTC (permalink / raw) To: Antoniu Miclaus; +Cc: jic23, conor+dt, linux-iio, linux-kernel, devicetree On Fri, Aug 22, 2025 at 04:01:52PM +0000, Antoniu Miclaus wrote: > Add devicetree bindings support for ade9000. > > Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> > --- > changes in v5: > - remove clock-output-names property (simplified clock output) > - make interrupts, reset-gpios, and interrupt-names optional (removed from required list) > - improve interrupt-names description to allow any subset of irq0, irq1, dready > - fix typo in description ("ADE9000 s a" -> "ADE9000 is a") > - fix spacing in description ("analog-to- digital" -> "analog-to-digital") > - uncomment clock example in device tree example > .../bindings/iio/adc/adi,ade9000.yaml | 97 +++++++++++++++++++ > 1 file changed, 97 insertions(+) > create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml > > diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml > new file mode 100644 > index 000000000000..a1513ad41651 > --- /dev/null > +++ b/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml > @@ -0,0 +1,97 @@ > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) > +# Copyright 2025 Analog Devices Inc. > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/iio/adc/adi,ade9000.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: Analog Devices ADE9000 High Performance, Polyphase Energy Metering driver Binding is for some h/w device, not a driver. > + > +maintainers: > + - Antoniu Miclaus <antoniu.miclaus@analog.com> > + > +description: | > + The ADE9000 is a highly accurate, fully integrated, multiphase energy and power > + quality monitoring device. Superior analog performance and a digital signal > + processing (DSP) core enable accurate energy monitoring over a wide dynamic > + range. An integrated high end reference ensures low drift over temperature > + with a combined drift of less than ±25 ppm/°C maximum for the entire channel > + including a programmable gain amplifier (PGA) and an analog-to-digital > + converter (ADC). > + > + https://www.analog.com/media/en/technical-documentation/data-sheets/ADE9000.pdf > + > +$ref: /schemas/spi/spi-peripheral-props.yaml# > + > +properties: > + compatible: > + enum: > + - adi,ade9000 > + > + reg: > + maxItems: 1 > + > + spi-max-frequency: > + maximum: 20000000 > + > + interrupts: > + maxItems: 3 > + > + interrupt-names: > + description: Optional interrupt names. Any subset of irq0, irq1, dready. > + maxItems: 3 This doesn't work for a subset. > + > + reset-gpios: > + description: > + Must be the device tree identifier of the RESET pin. As the line is > + active low, it should be marked GPIO_ACTIVE_LOW. > + maxItems: 1 > + > + vdd-supply: true > + > + vref-supply: true > + > + clocks: > + description: External clock source when not using crystal > + maxItems: 1 > + > + clock-names: > + items: > + - const: clkin > + > + "#clock-cells": > + description: > + ADE9000 can provide clock output via CLKOUT pin with external buffer. > + const: 0 > + > +required: > + - compatible > + - reg > + - vdd-supply > + > +unevaluatedProperties: false > + > +examples: > + - | > + #include <dt-bindings/gpio/gpio.h> > + #include <dt-bindings/interrupt-controller/irq.h> > + > + spi { > + #address-cells = <1>; > + #size-cells = <0>; > + > + adc@0 { > + compatible = "adi,ade9000"; > + reg = <0>; > + spi-max-frequency = <7000000>; > + > + #clock-cells = <0>; > + reset-gpios = <&gpio 4 GPIO_ACTIVE_LOW>; > + interrupts = <2 IRQ_TYPE_EDGE_FALLING>, <3 IRQ_TYPE_EDGE_FALLING>, <4 IRQ_TYPE_EDGE_FALLING>; > + interrupt-names = "irq0", "irq1", "dready"; > + interrupt-parent = <&gpio>; > + clocks = <&ext_clock_24576khz>; > + clock-names = "clkin"; > + vdd-supply = <&vdd_reg>; > + }; > + }; > -- > 2.43.0 > ^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v5 4/6] iio: adc: add ade9000 support 2025-08-22 16:01 [PATCH v5 0/6] iio: adc: add support for ADE9000 Energy Monitoring IC Antoniu Miclaus ` (2 preceding siblings ...) 2025-08-22 16:01 ` [PATCH v5 3/6] dt-bindings: iio: adc: add ade9000 Antoniu Miclaus @ 2025-08-22 16:01 ` Antoniu Miclaus 2025-08-25 1:19 ` kernel test robot ` (3 more replies) 2025-08-22 16:01 ` [PATCH v5 5/6] docs: iio: add documentation for ade9000 driver Antoniu Miclaus 2025-08-22 16:01 ` [PATCH v5 6/6] Documentation: ABI: iio: add sinc4+lp Antoniu Miclaus 5 siblings, 4 replies; 14+ messages in thread From: Antoniu Miclaus @ 2025-08-22 16:01 UTC (permalink / raw) To: jic23, robh, conor+dt, linux-iio, linux-kernel, devicetree Cc: Antoniu Miclaus Add driver support for the ade9000. highly accurate, fully integrated, multiphase energy and power quality monitoring device. Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> --- changes in v5: - replace IIO_CHAN_INFO_HARDWAREGAIN with IIO_CHAN_INFO_CALIBSCALE for gain registers - replace IIO_CHAN_INFO_OFFSET with IIO_CHAN_INFO_CALIBBIAS for power channel calibration offsets - change IIO_CHAN_INFO_SAMP_FREQ to IIO_CHAN_INFO_FREQUENCY for line frequency - add shared PGA scale and frequency configuration via info_mask_shared_by_all - remove per-channel HARDWAREGAIN and SAMP_FREQ from voltage channels - simplify power channel attributes - make interrupts optional (driver works without IRQs but with reduced functionality) - remove unnecessary parentheses from return statements with bitwise operations - use dev_err_probe() for consistent error handling and improve error messages - add local device variables to reduce repetitive dereferencing - remove unnecessary reinit_completion() and spi_set_drvdata() calls - fix software reset to return immediately instead of waiting for completion - move init_completion() before IRQ setup to avoid race conditions - add devm_mutex_init() for proper mutex initialization - simplify clock output registration (use fixed "clkout" name instead of generated names) - improve code clarity with comments - update frequency validation logic (50Hz/60Hz switch statement instead of ternary) - reorganize probe function structure and variable initialization order drivers/iio/adc/Kconfig | 19 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ade9000.c | 2011 +++++++++++++++++++++++++++++++++++++ 3 files changed, 2031 insertions(+) create mode 100644 drivers/iio/adc/ade9000.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 6de2abad0197..53bdd34a5899 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -507,6 +507,25 @@ config AD9467 To compile this driver as a module, choose M here: the module will be called ad9467. +config ADE9000 + tristate "Analog Devices ADE9000 Multiphase Energy, and Power Quality Monitoring IC Driver" + depends on SPI + select REGMAP_SPI + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build support for the Analog Devices ADE9000, + a highly accurate, multiphase energy and power quality monitoring + integrated circuit. + + The device features high-precision analog-to-digital converters + and digital signal processing to compute RMS values, power factor, + frequency, and harmonic analysis. It supports SPI communication + and provides buffered data output through the IIO framework. + + To compile this driver as a module, choose M here: the module will + be called ade9000. + config ADI_AXI_ADC tristate "Analog Devices Generic AXI ADC IP core driver" depends on MICROBLAZE || NIOS2 || ARCH_ZYNQ || ARCH_ZYNQMP || ARCH_INTEL_SOCFPGA || COMPILE_TEST diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 1c6ca5fd4b6d..e3ef416a3b5b 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -46,6 +46,7 @@ obj-$(CONFIG_AD7944) += ad7944.o obj-$(CONFIG_AD7949) += ad7949.o obj-$(CONFIG_AD799X) += ad799x.o obj-$(CONFIG_AD9467) += ad9467.o +obj-$(CONFIG_ADE9000) += ade9000.o obj-$(CONFIG_ADI_AXI_ADC) += adi-axi-adc.o obj-$(CONFIG_ASPEED_ADC) += aspeed_adc.o obj-$(CONFIG_AT91_ADC) += at91_adc.o diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c new file mode 100644 index 000000000000..41f766e00de0 --- /dev/null +++ b/drivers/iio/adc/ade9000.c @@ -0,0 +1,2011 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** + * ADE9000 driver + * + * Copyright 2025 Analog Devices Inc. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/events.h> +#include <linux/iio/sysfs.h> +#include <linux/interrupt.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> +#include <linux/unaligned.h> + +/* Address of ADE9000 registers */ +#define ADE9000_REG_AIGAIN 0x000 +#define ADE9000_REG_AVGAIN 0x00B +#define ADE9000_REG_AIRMSOS 0x00C +#define ADE9000_REG_AVRMSOS 0x00D +#define ADE9000_REG_APGAIN 0x00E +#define ADE9000_REG_AWATTOS 0x00F +#define ADE9000_REG_AVAROS 0x010 +#define ADE9000_REG_AFVAROS 0x012 +#define ADE9000_REG_CONFIG0 0x060 +#define ADE9000_REG_DICOEFF 0x072 +#define ADE9000_REG_AI_PCF 0x20A +#define ADE9000_REG_AV_PCF 0x20B +#define ADE9000_REG_AIRMS 0x20C +#define ADE9000_REG_AVRMS 0x20D +#define ADE9000_REG_AWATT 0x210 +#define ADE9000_REG_AVAR 0x211 +#define ADE9000_REG_AVA 0x212 +#define ADE9000_REG_AFVAR 0x214 +#define ADE9000_REG_APF 0x216 +#define ADE9000_REG_BI_PCF 0x22A +#define ADE9000_REG_BV_PCF 0x22B +#define ADE9000_REG_BIRMS 0x22C +#define ADE9000_REG_BVRMS 0x22D +#define ADE9000_REG_CI_PCF 0x24A +#define ADE9000_REG_CV_PCF 0x24B +#define ADE9000_REG_CIRMS 0x24C +#define ADE9000_REG_CVRMS 0x24D +#define ADE9000_REG_AWATT_ACC 0x2E5 +#define ADE9000_REG_AWATTHR_LO 0x2E6 +#define ADE9000_REG_AVAHR_LO 0x2FA +#define ADE9000_REG_AFVARHR_LO 0x30E +#define ADE9000_REG_BWATTHR_LO 0x322 +#define ADE9000_REG_BVAHR_LO 0x336 +#define ADE9000_REG_BFVARHR_LO 0x34A +#define ADE9000_REG_CWATTHR_LO 0x35E +#define ADE9000_REG_CVAHR_LO 0x372 +#define ADE9000_REG_CFVARHR_LO 0x386 +#define ADE9000_REG_STATUS0 0x402 +#define ADE9000_REG_STATUS1 0x403 +#define ADE9000_REG_MASK0 0x405 +#define ADE9000_REG_MASK1 0x406 +#define ADE9000_REG_EVENT_MASK 0x407 +#define ADE9000_REG_VLEVEL 0x40F +#define ADE9000_REG_DIP_LVL 0x410 +#define ADE9000_REG_DIPA 0x411 +#define ADE9000_REG_DIPB 0x412 +#define ADE9000_REG_DIPC 0x413 +#define ADE9000_REG_SWELL_LVL 0x414 +#define ADE9000_REG_SWELLA 0x415 +#define ADE9000_REG_SWELLB 0x416 +#define ADE9000_REG_SWELLC 0x417 +#define ADE9000_REG_APERIOD 0x418 +#define ADE9000_REG_BPERIOD 0x419 +#define ADE9000_REG_CPERIOD 0x41A +#define ADE9000_REG_RUN 0x480 +#define ADE9000_REG_CONFIG1 0x481 +#define ADE9000_REG_ACCMODE 0x492 +#define ADE9000_REG_CONFIG3 0x493 +#define ADE9000_REG_ZXTOUT 0x498 +#define ADE9000_REG_ZX_LP_SEL 0x49A +#define ADE9000_REG_WFB_CFG 0x4A0 +#define ADE9000_REG_WFB_PG_IRQEN 0x4A1 +#define ADE9000_REG_WFB_TRG_CFG 0x4A2 +#define ADE9000_REG_WFB_TRG_STAT 0x4A3 +#define ADE9000_REG_CONFIG2 0x4AF +#define ADE9000_REG_EP_CFG 0x4B0 +#define ADE9000_REG_EGY_TIME 0x4B2 +#define ADE9000_REG_PGA_GAIN 0x4B9 +#define ADE9000_REG_VERSION 0x4FE +#define ADE9000_REG_WF_BUFF 0x800 +#define ADE9000_REG_WF_HALF_BUFF 0xC00 + +#define ADE9000_REG_ADDR_MASK GENMASK(15, 4) +#define ADE9000_REG_READ_BIT_MASK BIT(3) +#define ADE9000_RX_DEPTH 6 +#define ADE9000_TX_DEPTH 10 + +#define ADE9000_WF_CAP_EN_MASK BIT(4) +#define ADE9000_WF_CAP_SEL_MASK BIT(5) +#define ADE9000_WF_MODE_MASK GENMASK(7, 6) +#define ADE9000_WF_SRC_MASK GENMASK(9, 8) +#define ADE9000_WF_IN_EN_MASK BIT(12) + +/* External reference selection bit in CONFIG1 */ +#define ADE9000_EXT_REF_MASK BIT(15) + +/* + * Configuration registers + */ +#define ADE9000_PGA_GAIN 0x0000 + +/* Default configuration */ + +#define ADE9000_CONFIG0 0x00000000 + +/* CF3/ZX pin outputs Zero crossing, CF4 = DREADY */ +#define ADE9000_CONFIG1 0x000E + +/* Default High pass corner frequency of 1.25Hz */ +#define ADE9000_CONFIG2 0x0A00 + +/* Peak and overcurrent detection disabled */ +#define ADE9000_CONFIG3 0x0000 + +/* + * 50Hz operation, 3P4W Wye configuration, signed accumulation + * Clear bit 8 i.e. ACCMODE=0x00xx for 50Hz operation + * ACCMODE=0x0x9x for 3Wire delta when phase B is used as reference + */ +#define ADE9000_ACCMODE 0x0000 +#define ADE9000_ACCMODE_60HZ 0x0100 + +/*Line period and zero crossing obtained from VA */ +#define ADE9000_ZX_LP_SEL 0x0000 + +/* Interrupt mask values for initialization */ +#define ADE9000_MASK0_EGYRDY_INT_EN BIT(0) +#define ADE9000_MASK1_ALL_INT_DIS 0x00000000 + +/* Events disabled */ +#define ADE9000_EVENT_DISABLE 0x00000000 + +/* + * Assuming Vnom=1/2 of full scale. + * Refer to Technical reference manual for detailed calculations. + */ +#define ADE9000_VLEVEL 0x0022EA28 + +/* Set DICOEFF= 0xFFFFE000 when integrator is enabled */ +#define ADE9000_DICOEFF 0x00000000 + +/* DSP ON */ +#define ADE9000_RUN_ON 0xFFFFFFFF + +/* + * Energy Accumulation Settings + * Enable energy accumulation, accumulate samples at 8ksps + * latch energy accumulation after EGYRDY + * If accumulation is changed to half line cycle mode, change EGY_TIME + */ +#define ADE9000_EP_CFG 0x0011 + +/* Accumulate 4000 samples */ +#define ADE9000_EGY_TIME 7999 + +/* + * Constant Definitions + * ADE9000 FDSP: 8000sps, ADE9000 FDSP: 4000sps + */ +#define ADE9000_FDSP 4000 +#define ADE9000_DEFAULT_CLK_FREQ_HZ 24576000 +#define ADE9000_WFB_CFG 0x03E9 +#define ADE9000_WFB_PAGE_SIZE 128 +#define ADE9000_WFB_NR_OF_PAGES 16 +#define ADE9000_WFB_MAX_CHANNELS 8 +#define ADE9000_WFB_BYTES_IN_SAMPLE 4 +#define ADE9000_WFB_SAMPLES_IN_PAGE \ + (ADE9000_WFB_PAGE_SIZE / ADE9000_WFB_MAX_CHANNELS) +#define ADE9000_WFB_MAX_SAMPLES_CHAN \ + (ADE9000_WFB_SAMPLES_IN_PAGE * ADE9000_WFB_NR_OF_PAGES) +#define ADE9000_WFB_FULL_BUFF_NR_SAMPLES \ + (ADE9000_WFB_PAGE_SIZE * ADE9000_WFB_NR_OF_PAGES) +#define ADE9000_WFB_FULL_BUFF_SIZE \ + (ADE9000_WFB_FULL_BUFF_NR_SAMPLES * ADE9000_WFB_BYTES_IN_SAMPLE) + +#define ADE9000_SWRST_BIT BIT(0) + +/* Status and Mask register bits*/ +#define ADE9000_ST0_WFB_TRIG_BIT BIT(16) +#define ADE9000_ST0_PAGE_FULL_BIT BIT(17) +#define ADE9000_ST0_EGYRDY BIT(0) + +#define ADE9000_ST1_ZXTOVA_BIT BIT(6) +#define ADE9000_ST1_ZXTOVB_BIT BIT(7) +#define ADE9000_ST1_ZXTOVC_BIT BIT(8) +#define ADE9000_ST1_ZXVA_BIT BIT(9) +#define ADE9000_ST1_ZXVB_BIT BIT(10) +#define ADE9000_ST1_ZXVC_BIT BIT(11) +#define ADE9000_ST1_ZXIA_BIT BIT(13) +#define ADE9000_ST1_ZXIB_BIT BIT(14) +#define ADE9000_ST1_ZXIC_BIT BIT(15) +#define ADE9000_ST1_RSTDONE_BIT BIT(16) +#define ADE9000_ST1_SEQERR_BIT BIT(18) +#define ADE9000_ST1_SWELLA_BIT BIT(20) +#define ADE9000_ST1_SWELLB_BIT BIT(21) +#define ADE9000_ST1_SWELLC_BIT BIT(22) +#define ADE9000_ST1_DIPA_BIT BIT(23) +#define ADE9000_ST1_DIPB_BIT BIT(24) +#define ADE9000_ST1_DIPC_BIT BIT(25) +#define ADE9000_ST1_ERROR0_BIT BIT(28) +#define ADE9000_ST1_ERROR1_BIT BIT(29) +#define ADE9000_ST1_ERROR2_BIT BIT(30) +#define ADE9000_ST1_ERROR3_BIT BIT(31) +#define ADE9000_ST_ERROR \ + (ADE9000_ST1_ERROR0 | ADE9000_ST1_ERROR1 | \ + ADE9000_ST1_ERROR2 | ADE9000_ST1_ERROR3) +#define ADE9000_ST1_CROSSING_FIRST 6 +#define ADE9000_ST1_CROSSING_DEPTH 25 + +#define ADE9000_WFB_TRG_DIP_BIT BIT(0) +#define ADE9000_WFB_TRG_SWELL_BIT BIT(1) +#define ADE9000_WFB_TRG_ZXIA_BIT BIT(3) +#define ADE9000_WFB_TRG_ZXIB_BIT BIT(4) +#define ADE9000_WFB_TRG_ZXIC_BIT BIT(5) +#define ADE9000_WFB_TRG_ZXVA_BIT BIT(6) +#define ADE9000_WFB_TRG_ZXVB_BIT BIT(7) +#define ADE9000_WFB_TRG_ZXVC_BIT BIT(8) + +/* Stop when waveform buffer is full */ +#define ADE9000_WFB_FULL_MODE 0x0 +/* Continuous fill—stop only on enabled trigger events */ +#define ADE9000_WFB_EN_TRIG_MODE 0x1 +/* Continuous filling—center capture around enabled trigger events */ +#define ADE9000_WFB_C_EN_TRIG_MODE 0x2 +/* Continuous fill—used as streaming mode for continuous data output */ +#define ADE9000_WFB_STREAMING_MODE 0x3 + +#define ADE9000_LAST_PAGE_BIT BIT(15) +#define ADE9000_MIDDLE_PAGE_BIT BIT(7) + +/* + * Full scale Codes referred from Datasheet. Respective digital codes are + * produced when ADC inputs are at full scale. + */ +#define ADE9000_RMS_FULL_SCALE_CODES 52866837 +#define ADE9000_WATT_FULL_SCALE_CODES 20694066 +#define ADE9000_PCF_FULL_SCALE_CODES 74770000 + +/* Phase and channel definitions */ +#define ADE9000_PHASE_A_NR 0 +#define ADE9000_PHASE_B_NR 1 +#define ADE9000_PHASE_C_NR 2 + +#define ADE9000_SCAN_POS_IA BIT(0) +#define ADE9000_SCAN_POS_VA BIT(1) +#define ADE9000_SCAN_POS_IB BIT(2) +#define ADE9000_SCAN_POS_VB BIT(3) +#define ADE9000_SCAN_POS_IC BIT(4) +#define ADE9000_SCAN_POS_VC BIT(5) + +/* Waveform buffer configuration values */ +enum ade9000_wfb_cfg { + ADE9000_WFB_CFG_ALL_CHAN = 0x0, + ADE9000_WFB_CFG_IA_VA = 0x1, + ADE9000_WFB_CFG_IB_VB = 0x2, + ADE9000_WFB_CFG_IC_VC = 0x3, + ADE9000_WFB_CFG_IA = 0x8, + ADE9000_WFB_CFG_VA = 0x9, + ADE9000_WFB_CFG_IB = 0xA, + ADE9000_WFB_CFG_VB = 0xB, + ADE9000_WFB_CFG_IC = 0xC, + ADE9000_WFB_CFG_VC = 0xD, +}; + +#define ADE9000_PHASE_B_POS_BIT BIT(5) +#define ADE9000_PHASE_C_POS_BIT BIT(6) + +#define ADE9000_MAX_PHASE_NR 3 +#define AD9000_CHANNELS_PER_PHASE 10 + +#define ADE9000_ADDR_ADJUST(addr, chan) \ + (((chan) == 0 ? 0 : (chan) == 1 ? 2 : 4) << 4 | (addr)) + +struct ade9000_state { + struct completion reset_completion; + struct mutex lock; /* Protects SPI transactions */ + u8 wf_src; + u32 wfb_trg; + u8 wfb_nr_activ_chan; + u32 wfb_nr_samples; + struct spi_device *spi; + struct clk *clkin; + struct clk_hw clkout_hw; + struct spi_transfer xfer[2]; + struct spi_message spi_msg; + struct regmap *regmap; + union{ + u8 byte[ADE9000_WFB_FULL_BUFF_SIZE]; + __be32 word[ADE9000_WFB_FULL_BUFF_NR_SAMPLES]; + } rx_buff __aligned(IIO_DMA_MINALIGN); + u8 tx_buff[2] __aligned(IIO_DMA_MINALIGN); + u8 tx[ADE9000_TX_DEPTH] __aligned(IIO_DMA_MINALIGN); + u8 rx[ADE9000_RX_DEPTH]; + unsigned int bulk_read_buf[2]; +}; + +static unsigned long ade9000_clkout_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + /* CLKOUT provides the same frequency as the crystal/external clock */ + return parent_rate ? parent_rate : ADE9000_DEFAULT_CLK_FREQ_HZ; +} + +static const struct clk_ops ade9000_clkout_ops = { + .recalc_rate = ade9000_clkout_recalc_rate, +}; + +static const struct iio_event_spec ade9000_events[] = { + { + /* Energy ready event - datasheet: EGYRDY interrupt */ + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_NONE, /* Non-directional event */ + .mask_shared_by_all = BIT(IIO_EV_INFO_ENABLE), + }, + { + /* Sequence error event - datasheet: SEQERR interrupt */ + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, /* Non-directional event */ + .mask_shared_by_all = BIT(IIO_EV_INFO_ENABLE), + }, + { + /* Threshold events - datasheet: zero crossing timeout, sag/swell */ + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_NONE, /* Timeout events (ZXTOUT register) */ + .mask_separate = BIT(IIO_EV_INFO_ENABLE), /* Per-channel enable */ + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE), /* Shared threshold value */ + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, /* for swell */ + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, /* for dip */ + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const char * const ade9000_filter_type_items[] = { + "sinc4", "sinc4+lp", +}; + +static const int ade9000_filter_type_values[] = { + 0, 2, +}; + +static int ade9000_filter_type_get(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 val; + int ret, i; + + ret = regmap_read(st->regmap, ADE9000_REG_WFB_CFG, &val); + if (ret) + return ret; + + val = FIELD_GET(ADE9000_WF_SRC_MASK, val); + + for (i = 0; i < ARRAY_SIZE(ade9000_filter_type_values); i++) { + if (ade9000_filter_type_values[i] == val) + return i; + } + + return -EINVAL; +} + +static int ade9000_filter_type_set(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int index) +{ + struct ade9000_state *st = iio_priv(indio_dev); + int ret, val; + + if (index >= ARRAY_SIZE(ade9000_filter_type_values)) + return -EINVAL; + + val = ade9000_filter_type_values[index]; + + /* Update the WFB_CFG register with the new filter type */ + ret = regmap_update_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_SRC_MASK, + FIELD_PREP(ADE9000_WF_SRC_MASK, val)); + if (ret) + return ret; + + /* Update cached value */ + st->wf_src = val; + + return 0; +} + +static const struct iio_enum ade9000_filter_type_enum = { + .items = ade9000_filter_type_items, + .num_items = ARRAY_SIZE(ade9000_filter_type_items), + .get = ade9000_filter_type_get, + .set = ade9000_filter_type_set, +}; + +static const struct iio_chan_spec_ext_info ade9000_ext_info[] = { + IIO_ENUM("filter_type", IIO_SHARED_BY_ALL, &ade9000_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_ALL, &ade9000_filter_type_enum), + {} +}; + +#define ADE9000_CURRENT_CHANNEL(num) { \ + .type = IIO_CURRENT, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AI_PCF, num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + .event_spec = &ade9000_events[0], \ + .num_event_specs = 1, \ + .scan_index = num, \ + .indexed = 1, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADE9000_VOLTAGE_CHANNEL(num) { \ + .type = IIO_VOLTAGE, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AV_PCF, num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_FREQUENCY), \ + .event_spec = ade9000_events, \ + .num_event_specs = ARRAY_SIZE(ade9000_events), \ + .scan_index = num + 1, /* interleave with current channels */ \ + .indexed = 1, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = ade9000_ext_info, \ +} + +#define ADE9000_ALTCURRENT_RMS_CHANNEL(num) { \ + .type = IIO_ALTCURRENT, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AIRMS, num), \ + .channel2 = IIO_MOD_RMS, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = -1 \ +} + +#define ADE9000_ALTVOLTAGE_RMS_CHANNEL(num) { \ + .type = IIO_ALTVOLTAGE, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVRMS, num), \ + .channel2 = IIO_MOD_RMS, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_index = -1 \ +} + +#define ADE9000_POWER_ACTIVE_CHANNEL(num) { \ + .type = IIO_POWER, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AWATT, num), \ + .channel2 = IIO_MOD_ACTIVE, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_POWERFACTOR), \ + .scan_index = -1 \ +} + +#define ADE9000_POWER_REACTIVE_CHANNEL(num) { \ + .type = IIO_POWER, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVAR, num), \ + .channel2 = IIO_MOD_REACTIVE, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .scan_index = -1 \ +} + +#define ADE9000_POWER_APPARENT_CHANNEL(num) { \ + .type = IIO_POWER, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVA, num), \ + .channel2 = IIO_MOD_APPARENT, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = -1 \ +} + + #define ADE9000_ENERGY_ACTIVE_CHANNEL(num, addr) { \ + .type = IIO_ENERGY, \ + .channel = num, \ + .address = addr, \ + .channel2 = IIO_MOD_ACTIVE, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = -1 \ +} + +#define ADE9000_ENERGY_APPARENT_CHANNEL(num, addr) { \ + .type = IIO_ENERGY, \ + .channel = num, \ + .address = addr, \ + .channel2 = IIO_MOD_APPARENT, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = -1 \ +} + +#define ADE9000_ENERGY_REACTIVE_CHANNEL(num, addr) { \ + .type = IIO_ENERGY, \ + .channel = num, \ + .address = addr, \ + .channel2 = IIO_MOD_REACTIVE, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = -1 \ +} + +static const struct iio_chan_spec ade9000_channels[] = { + /* Phase A channels */ + ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AWATTHR_LO), + ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AVAHR_LO), + ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AFVARHR_LO), + /* Phase B channels */ + ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BWATTHR_LO), + ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BVAHR_LO), + ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BFVARHR_LO), + /* Phase C channels */ + ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CWATTHR_LO), + ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CVAHR_LO), + ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CFVARHR_LO), + /* System frequency (50Hz/60Hz) and PGA gain configuration channel */ + { + .type = IIO_ALTVOLTAGE, + .channel = 0, + .indexed = 1, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_FREQUENCY) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = -1 + }, +}; + +static const struct reg_sequence ade9000_reg_sequence[] = { + { ADE9000_REG_PGA_GAIN, ADE9000_PGA_GAIN }, + { ADE9000_REG_CONFIG0, ADE9000_CONFIG0 }, + { ADE9000_REG_CONFIG1, ADE9000_CONFIG1 }, + { ADE9000_REG_CONFIG2, ADE9000_CONFIG2 }, + { ADE9000_REG_CONFIG3, ADE9000_CONFIG3 }, + { ADE9000_REG_ACCMODE, ADE9000_ACCMODE }, + { ADE9000_REG_ZX_LP_SEL, ADE9000_ZX_LP_SEL }, + { ADE9000_REG_MASK0, ADE9000_MASK0_EGYRDY_INT_EN }, + { ADE9000_REG_MASK1, ADE9000_MASK1_ALL_INT_DIS }, + { ADE9000_REG_EVENT_MASK, ADE9000_EVENT_DISABLE }, + { ADE9000_REG_WFB_CFG, ADE9000_WFB_CFG }, + { ADE9000_REG_VLEVEL, ADE9000_VLEVEL }, + { ADE9000_REG_DICOEFF, ADE9000_DICOEFF }, + { ADE9000_REG_EGY_TIME, ADE9000_EGY_TIME }, + { ADE9000_REG_EP_CFG, ADE9000_EP_CFG }, + { ADE9000_REG_RUN, ADE9000_RUN_ON } +}; + +static int ade9000_spi_write_reg(void *context, unsigned int reg, + unsigned int val) +{ + struct ade9000_state *st = context; + u16 addr; + int ret, len; + + guard(mutex)(&st->lock); + + addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, reg); + put_unaligned_be16(addr, st->tx); + + if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) { + put_unaligned_be16(val, &st->tx[2]); + len = 4; + } else { + put_unaligned_be32(val, &st->tx[2]); + len = 6; + } + + ret = spi_write(st->spi, st->tx, len); + if (ret) + dev_err(&st->spi->dev, "problem when writing register 0x%x\n", reg); + + return ret; +} + +static int ade9000_spi_read_reg(void *context, unsigned int reg, + unsigned int *val) +{ + struct ade9000_state *st = context; + u16 addr; + int ret, rx_len; + + guard(mutex)(&st->lock); + + addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, reg) | + ADE9000_REG_READ_BIT_MASK; + + put_unaligned_be16(addr, st->tx); + + /* Skip CRC bytes - only read actual data */ + if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) + rx_len = 2; + else + rx_len = 4; + + ret = spi_write_then_read(st->spi, st->tx, 2, st->rx, rx_len); + if (ret) { + dev_err(&st->spi->dev, "error reading register 0x%x\n", reg); + return ret; + } + + if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) + *val = get_unaligned_be16(st->rx); + else + *val = get_unaligned_be32(st->rx); + + return 0; +} + +static bool ade9000_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADE9000_REG_STATUS0: + case ADE9000_REG_STATUS1: + case ADE9000_REG_MASK0: + case ADE9000_REG_MASK1: + case ADE9000_REG_WFB_PG_IRQEN: + return false; + default: + return true; + } +} + +static int ade9000_configure_scan(struct iio_dev *indio_dev, u32 wfb_addr) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u16 addr; + + addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, wfb_addr) | + ADE9000_REG_READ_BIT_MASK; + + put_unaligned_be16(addr, st->tx_buff); + + st->xfer[0].tx_buf = &st->tx_buff[0]; + st->xfer[0].len = 2; + + st->xfer[1].rx_buf = st->rx_buff.byte; + + /* Always use streaming mode */ + st->xfer[1].len = (st->wfb_nr_samples / 2) * 4; + + spi_message_init_with_transfers(&st->spi_msg, st->xfer, ARRAY_SIZE(st->xfer)); + return 0; +} + +static int ade9000_iio_push_streaming(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + u32 current_page, i; + int ret; + + guard(mutex)(&st->lock); + + ret = spi_sync(st->spi, &st->spi_msg); + if (ret) { + dev_err(dev, "SPI fail in trigger handler\n"); + return ret; + } + + /* In streaming mode, only half the buffer is filled per interrupt */ + for (i = 0; i < st->wfb_nr_samples / 2; i += st->wfb_nr_activ_chan) + iio_push_to_buffers(indio_dev, &st->rx_buff.word[i]); + + ret = regmap_read(st->regmap, ADE9000_REG_WFB_PG_IRQEN, ¤t_page); + if (ret) { + dev_err(dev, "IRQ0 WFB read fail\n"); + return ret; + } + + if (current_page & ADE9000_MIDDLE_PAGE_BIT) { + ret = regmap_write(st->regmap, ADE9000_REG_WFB_PG_IRQEN, + ADE9000_LAST_PAGE_BIT); + if (ret) { + dev_err(dev, "IRQ0 WFB write fail\n"); + return ret; + } + + ret = ade9000_configure_scan(indio_dev, + ADE9000_REG_WF_HALF_BUFF); + if (ret) + return ret; + } else { + ret = regmap_write(st->regmap, ADE9000_REG_WFB_PG_IRQEN, + ADE9000_MIDDLE_PAGE_BIT); + if (ret) { + dev_err(dev, "IRQ0 WFB write fail"); + return IRQ_HANDLED; + } + + ret = ade9000_configure_scan(indio_dev, + ADE9000_REG_WF_BUFF); + if (ret) + return ret; + } + + return 0; +} + +static int ade9000_iio_push_buffer(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + int ret; + u32 i; + + guard(mutex)(&st->lock); + + ret = spi_sync(st->spi, &st->spi_msg); + if (ret) { + dev_err(&st->spi->dev, "SPI fail in trigger handler\n"); + return ret; + } + + for (i = 0; i < st->wfb_nr_samples; i += st->wfb_nr_activ_chan) + iio_push_to_buffers(indio_dev, &st->rx_buff.word[i]); + + return 0; +} + +static irqreturn_t ade9000_irq0_thread(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct ade9000_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + s64 timestamp = iio_get_time_ns(indio_dev); + u32 handled_irq = 0; + u32 interrupts, status; + int ret; + + ret = regmap_read(st->regmap, ADE9000_REG_STATUS0, &status); + if (ret) { + dev_err(dev, "IRQ0 read status fail\n"); + return IRQ_HANDLED; + } + + ret = regmap_read(st->regmap, ADE9000_REG_MASK0, &interrupts); + if (ret) { + dev_err(dev, "IRQ0 read mask fail\n"); + return IRQ_HANDLED; + } + + if ((status & ADE9000_ST0_PAGE_FULL_BIT) && + (interrupts & ADE9000_ST0_PAGE_FULL_BIT)) { + /* Always use streaming mode */ + ret = ade9000_iio_push_streaming(indio_dev); + if (ret) { + dev_err(dev, "IRQ0 IIO push fail\n"); + return IRQ_HANDLED; + } + + handled_irq |= ADE9000_ST0_PAGE_FULL_BIT; + } + + if ((status & ADE9000_ST0_WFB_TRIG_BIT) && + (interrupts & ADE9000_ST0_WFB_TRIG_BIT)) { + ret = regmap_update_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_CAP_EN_MASK, 0); + if (ret) { + dev_err(dev, "IRQ0 WFB fail\n"); + return IRQ_HANDLED; + } + + if (iio_buffer_enabled(indio_dev)) { + ret = ade9000_iio_push_buffer(indio_dev); + if (ret) { + dev_err(dev, "IRQ0 IIO push fail @ WFB TRIG\n"); + return IRQ_HANDLED; + } + } + + handled_irq |= ADE9000_ST0_WFB_TRIG_BIT; + } + + if ((status & ADE9000_ST0_EGYRDY) && + (interrupts & ADE9000_ST0_EGYRDY)) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_ENERGY, + ADE9000_ST0_EGYRDY, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_NONE), + timestamp); + + handled_irq |= ADE9000_ST0_EGYRDY; + } + + ret = regmap_write(st->regmap, ADE9000_REG_STATUS0, handled_irq); + if (ret) + dev_err(dev, "IRQ0 write status fail\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t ade9000_irq1_thread(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct ade9000_state *st = iio_priv(indio_dev); + unsigned int bit = ADE9000_ST1_CROSSING_FIRST; + s64 timestamp = iio_get_time_ns(indio_dev); + u32 handled_irq = 0; + u32 interrupts; + u32 result; + u32 status; + u32 tmp; + unsigned long interrupt_bits; + int ret; + + if (!completion_done(&st->reset_completion)) { + ret = regmap_read(st->regmap, ADE9000_REG_STATUS1, &result); + if (ret) { + dev_err(&st->spi->dev, "IRQ1 read status fail\n"); + return ret; + } + + if (result & ADE9000_ST1_RSTDONE_BIT) + complete(&st->reset_completion); + else + dev_err(&st->spi->dev, "Error testing reset done\n"); + + return IRQ_HANDLED; + } + + ret = regmap_read(st->regmap, ADE9000_REG_STATUS1, &status); + if (ret) { + dev_err(&st->spi->dev, "IRQ1 read status fail\n"); + return IRQ_HANDLED; + } + + ret = regmap_read(st->regmap, ADE9000_REG_MASK1, &interrupts); + if (ret) { + dev_err(&st->spi->dev, "IRQ1 read status fail\n"); + return IRQ_HANDLED; + } + + interrupt_bits = interrupts; + for_each_set_bit_from(bit, &interrupt_bits, + ADE9000_ST1_CROSSING_DEPTH){ + tmp = status & BIT(bit); + + switch (tmp) { + case ADE9000_ST1_ZXVA_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + ADE9000_ST1_ZXVA_BIT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + handled_irq |= ADE9000_ST1_ZXVA_BIT; + break; + case ADE9000_ST1_ZXTOVA_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + ADE9000_ST1_ZXTOVA_BIT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + handled_irq |= ADE9000_ST1_ZXTOVA_BIT; + break; + case ADE9000_ST1_ZXIA_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CURRENT, + ADE9000_ST1_ZXIA_BIT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + handled_irq |= ADE9000_ST1_ZXIA_BIT; + break; + case ADE9000_ST1_ZXVB_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + ADE9000_ST1_ZXVB_BIT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + handled_irq |= ADE9000_ST1_ZXVB_BIT; + break; + case ADE9000_ST1_ZXTOVB_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + ADE9000_ST1_ZXTOVB_BIT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + handled_irq |= ADE9000_ST1_ZXTOVB_BIT; + break; + case ADE9000_ST1_ZXIB_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CURRENT, + ADE9000_ST1_ZXIB_BIT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + handled_irq |= ADE9000_ST1_ZXIB_BIT; + break; + case ADE9000_ST1_ZXVC_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + ADE9000_ST1_ZXVC_BIT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + handled_irq |= ADE9000_ST1_ZXVC_BIT; + break; + case ADE9000_ST1_ZXTOVC_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + ADE9000_ST1_ZXTOVC_BIT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + handled_irq |= ADE9000_ST1_ZXTOVC_BIT; + break; + case ADE9000_ST1_ZXIC_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CURRENT, + ADE9000_ST1_ZXIC_BIT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + handled_irq |= ADE9000_ST1_ZXIC_BIT; + break; + case ADE9000_ST1_SWELLA_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + ADE9000_ST1_SWELLA_BIT >> 20, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + handled_irq |= ADE9000_ST1_SWELLA_BIT; + break; + case ADE9000_ST1_SWELLB_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + ADE9000_ST1_SWELLB_BIT >> 20, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + handled_irq |= ADE9000_ST1_SWELLB_BIT; + break; + case ADE9000_ST1_SWELLC_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + ADE9000_ST1_SWELLC_BIT >> 20, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + handled_irq |= ADE9000_ST1_SWELLC_BIT; + break; + case ADE9000_ST1_DIPA_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + ADE9000_ST1_DIPA_BIT >> 20, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + handled_irq |= ADE9000_ST1_DIPA_BIT; + break; + case ADE9000_ST1_DIPB_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + ADE9000_ST1_DIPB_BIT >> 20, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + handled_irq |= ADE9000_ST1_DIPB_BIT; + break; + case ADE9000_ST1_DIPC_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + ADE9000_ST1_DIPC_BIT >> 20, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + handled_irq |= ADE9000_ST1_DIPC_BIT; + break; + case ADE9000_ST1_SEQERR_BIT: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + ADE9000_ST1_SEQERR_BIT >> 12, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_NONE), + timestamp); + handled_irq |= ADE9000_ST1_SEQERR_BIT; + break; + default: + return IRQ_HANDLED; + } + } + + ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, handled_irq); + if (ret) + return ret; + + return IRQ_HANDLED; +} + +static irqreturn_t ade9000_dready_thread(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + + /* Handle data ready interrupt from C4/EVENT/DREADY pin */ + if (!iio_device_claim_buffer_mode(indio_dev)) { + ade9000_iio_push_buffer(indio_dev); + iio_device_release_buffer_mode(indio_dev); + } + + return IRQ_HANDLED; +} + +static int ade9000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct ade9000_state *st = iio_priv(indio_dev); + unsigned int reg, measured; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_FREQUENCY: + if (chan->type == IIO_VOLTAGE) { + int period_reg; + int period; + + switch (chan->channel) { + case ADE9000_PHASE_A_NR: + period_reg = ADE9000_REG_APERIOD; + break; + case ADE9000_PHASE_B_NR: + period_reg = ADE9000_REG_BPERIOD; + break; + case ADE9000_PHASE_C_NR: + period_reg = ADE9000_REG_CPERIOD; + break; + default: + return -EINVAL; + } + ret = regmap_read(st->regmap, period_reg, &period); + if (ret) + return ret; + *val = 4000 * 65536; + *val2 = period + 1; + return IIO_VAL_FRACTIONAL; + } + + ret = regmap_read(st->regmap, ADE9000_REG_ACCMODE, ®); + if (ret) + return ret; + *val = (reg & ADE9000_ACCMODE_60HZ) ? 60 : 50; + return IIO_VAL_INT; + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_ENERGY) { + u16 lo_reg = chan->address; + + ret = regmap_bulk_read(st->regmap, lo_reg, + st->bulk_read_buf, 2); + if (ret) + return ret; + + *val = st->bulk_read_buf[0]; /* Lower 32 bits */ + *val2 = st->bulk_read_buf[1]; /* Upper 32 bits */ + return IIO_VAL_INT_64; + } + + ret = iio_device_claim_direct(indio_dev); + if (ret) + return ret; + + ret = regmap_read(st->regmap, chan->address, &measured); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + *val = measured; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_POWERFACTOR: + ret = iio_device_claim_direct(indio_dev); + if (ret) + return ret; + + /* Map power channel to corresponding power factor register */ + reg = ADE9000_ADDR_ADJUST(ADE9000_REG_APF, chan->channel); + ret = regmap_read(st->regmap, reg, &measured); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + *val = measured; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + if (chan->info_mask_shared_by_all) { + /* Shared PGA gain read - only for channel with shared frequency */ + ret = regmap_read(st->regmap, ADE9000_REG_PGA_GAIN, ®); + if (ret) + return ret; + *val = min(1 << ((reg >> (8 + chan->channel)) & GENMASK(1, 0)), 4); + return IIO_VAL_INT; + } else if (chan->type == IIO_CURRENT || chan->type == IIO_VOLTAGE || + chan->type == IIO_ALTVOLTAGE) { + switch (chan->address) { + case ADE9000_REG_AI_PCF: + case ADE9000_REG_AV_PCF: + case ADE9000_REG_BI_PCF: + case ADE9000_REG_BV_PCF: + case ADE9000_REG_CI_PCF: + case ADE9000_REG_CV_PCF: + *val = 1; + *val2 = ADE9000_PCF_FULL_SCALE_CODES; + return IIO_VAL_FRACTIONAL; + case ADE9000_REG_AIRMS: + case ADE9000_REG_AVRMS: + case ADE9000_REG_BIRMS: + case ADE9000_REG_BVRMS: + case ADE9000_REG_CIRMS: + case ADE9000_REG_CVRMS: + *val = 1; + *val2 = ADE9000_RMS_FULL_SCALE_CODES; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + } else if (chan->type == IIO_POWER) { + *val = 1; + *val2 = ADE9000_WATT_FULL_SCALE_CODES; + return IIO_VAL_FRACTIONAL; + } else { + return -EINVAL; + } + break; + default: + return -EINVAL; + } +} + +static int ade9000_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 addr; + u32 tmp; + + switch (mask) { + case IIO_CHAN_INFO_FREQUENCY: + switch (val) { + case 50: + return regmap_write(st->regmap, ADE9000_REG_ACCMODE, + ADE9000_ACCMODE); + case 60: + return regmap_write(st->regmap, ADE9000_REG_ACCMODE, + ADE9000_ACCMODE_60HZ); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_CURRENT: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AIRMSOS, + chan->channel), val); + case IIO_VOLTAGE: + case IIO_ALTVOLTAGE: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AVRMSOS, + chan->channel), val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_POWER: + tmp = chan->address; + tmp &= ~ADE9000_PHASE_B_POS_BIT; + tmp &= ~ADE9000_PHASE_C_POS_BIT; + + switch (tmp) { + case ADE9000_REG_AWATTOS: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AWATTOS, + chan->channel), val); + case ADE9000_REG_AVAR: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AVAROS, + chan->channel), val); + case ADE9000_REG_AFVAR: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AFVAROS, + chan->channel), val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBSCALE: + /* + * Calibration gain registers for fine-tuning measurements. + * These are separate from PGA gain and applied in the digital domain. + */ + switch (chan->type) { + case IIO_CURRENT: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AIGAIN, + chan->channel), val); + case IIO_VOLTAGE: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AVGAIN, + chan->channel), val); + case IIO_POWER: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_APGAIN, + chan->channel), val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + /* Only shared PGA scale is writable, per-channel scales are read-only */ + if (!(chan->info_mask_shared_by_all)) + return -EINVAL; + + /* + * PGA (Programmable Gain Amplifier) settings affect the analog + * input stage scaling, shared by all channels. This is different + * from the per-channel calibration gains above. + */ + if (val > 4 || val < 1 || val == 3) + return -EINVAL; + addr = ADE9000_REG_PGA_GAIN; + /* + * PGA gain settings: 1x, 2x, 4x (3x not supported) + * Each channel uses 2 bits in PGA_GAIN register: + * - Channel 0: bits [9:8] + * - Channel 1: bits [11:10] + * - Channel 2: bits [13:12] + * Convert gain (1,2,4) to register value (0,1,2) using ilog2() + */ + val = ilog2(val) << (chan->channel * 2 + 8); + tmp = GENMASK(1, 0) << (chan->channel * 2 + 8); + return regmap_update_bits(st->regmap, addr, tmp, val); + default: + return -EINVAL; + } +} + +static int ade9000_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int tx_val, + unsigned int *rx_val) +{ + struct ade9000_state *st = iio_priv(indio_dev); + + if (rx_val) + return regmap_read(st->regmap, reg, rx_val); + + return regmap_write(st->regmap, reg, tx_val); +} + +static int ade9000_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 interrupts0, interrupts1, number; + int ret; + + ret = regmap_read(st->regmap, ADE9000_REG_MASK0, &interrupts0); + if (ret) + return ret; + + ret = regmap_read(st->regmap, ADE9000_REG_MASK1, &interrupts1); + if (ret) + return ret; + + if (type == IIO_EV_TYPE_MAG) + return interrupts0 & ADE9000_ST0_EGYRDY; + + if (type == IIO_EV_TYPE_CHANGE) + return interrupts1 & ADE9000_ST1_SEQERR_BIT; + + number = chan->channel; + + switch (number) { + case ADE9000_PHASE_A_NR: + if (chan->type == IIO_VOLTAGE) { + if (dir == IIO_EV_DIR_EITHER) + return interrupts1 & ADE9000_ST1_ZXVA_BIT; + if (dir == IIO_EV_DIR_NONE) + return interrupts1 & ADE9000_ST1_ZXTOVA_BIT; + if (dir == IIO_EV_DIR_RISING) + return interrupts1 & ADE9000_ST1_SWELLA_BIT; + if (dir == IIO_EV_DIR_FALLING) + return interrupts1 & ADE9000_ST1_DIPA_BIT; + } else if (chan->type == IIO_CURRENT) { + return interrupts1 & ADE9000_ST1_ZXIA_BIT; + } + break; + case ADE9000_PHASE_B_NR: + if (chan->type == IIO_VOLTAGE) { + if (dir == IIO_EV_DIR_EITHER) + return interrupts1 & ADE9000_ST1_ZXVB_BIT; + if (dir == IIO_EV_DIR_NONE) + return interrupts1 & ADE9000_ST1_ZXTOVB_BIT; + if (dir == IIO_EV_DIR_RISING) + return interrupts1 & ADE9000_ST1_SWELLB_BIT; + if (dir == IIO_EV_DIR_FALLING) + return interrupts1 & ADE9000_ST1_DIPB_BIT; + } else if (chan->type == IIO_CURRENT) { + return interrupts1 & ADE9000_ST1_ZXIB_BIT; + } + break; + case ADE9000_PHASE_C_NR: + if (chan->type == IIO_VOLTAGE) { + if (dir == IIO_EV_DIR_EITHER) + return interrupts1 & ADE9000_ST1_ZXVC_BIT; + if (dir == IIO_EV_DIR_NONE) + return interrupts1 & ADE9000_ST1_ZXTOVC_BIT; + if (dir == IIO_EV_DIR_RISING) + return interrupts1 & ADE9000_ST1_SWELLC_BIT; + if (dir == IIO_EV_DIR_FALLING) + return interrupts1 & ADE9000_ST1_DIPC_BIT; + } else if (chan->type == IIO_CURRENT) { + return interrupts1 & ADE9000_ST1_ZXIC_BIT; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ade9000_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 interrupts, tmp; + int ret; + + /* Clear all pending events in STATUS1 register (write 1 to clear) */ + ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, GENMASK(31, 0)); + if (ret) + return ret; + + if (type == IIO_EV_TYPE_MAG) { + ret = regmap_update_bits(st->regmap, ADE9000_REG_STATUS0, + ADE9000_ST0_EGYRDY, ADE9000_ST0_EGYRDY); + if (ret) + return ret; + return regmap_update_bits(st->regmap, ADE9000_REG_MASK0, + ADE9000_ST0_EGYRDY, + state ? ADE9000_ST1_SEQERR_BIT : 0); + } + + if (type == IIO_EV_TYPE_CHANGE) + return regmap_update_bits(st->regmap, ADE9000_REG_MASK1, + ADE9000_ST1_SEQERR_BIT, + state ? ADE9000_ST1_SEQERR_BIT : 0); + + if (dir == IIO_EV_DIR_EITHER) { + static const struct { + u32 irq; + u32 wfb_trg; + } trig_arr[6] = { + { + .irq = ADE9000_ST1_ZXVA_BIT, + .wfb_trg = ADE9000_WFB_TRG_ZXVA_BIT + }, { + .irq = ADE9000_ST1_ZXIA_BIT, + .wfb_trg = ADE9000_WFB_TRG_ZXIA_BIT + }, { + .irq = ADE9000_ST1_ZXVB_BIT, + .wfb_trg = ADE9000_WFB_TRG_ZXVB_BIT + }, { + .irq = ADE9000_ST1_ZXIB_BIT, + .wfb_trg = ADE9000_WFB_TRG_ZXIB_BIT + }, { + .irq = ADE9000_ST1_ZXVC_BIT, + .wfb_trg = ADE9000_WFB_TRG_ZXVC_BIT + }, { + .irq = ADE9000_ST1_ZXIC_BIT, + .wfb_trg = ADE9000_WFB_TRG_ZXIC_BIT + }, + }; + if (state) { + interrupts |= trig_arr[chan->channel * 2 + chan->type].irq; + st->wfb_trg |= trig_arr[chan->channel * 2 + chan->type].wfb_trg; + } else { + interrupts &= ~trig_arr[chan->channel * 2 + chan->type].irq; + st->wfb_trg &= ~trig_arr[chan->channel * 2 + chan->type].wfb_trg; + } + } + + if (dir == IIO_EV_DIR_NONE) { + switch (chan->channel) { + case ADE9000_PHASE_A_NR: + tmp |= ADE9000_ST1_ZXTOVA_BIT; + break; + case ADE9000_PHASE_B_NR: + tmp |= ADE9000_ST1_ZXTOVB_BIT; + break; + case ADE9000_PHASE_C_NR: + tmp |= ADE9000_ST1_ZXTOVC_BIT; + break; + default: + break; + } + + if (state) + interrupts |= tmp; + else + interrupts &= ~tmp; + } else if (dir == IIO_EV_DIR_RISING) { + switch (chan->channel) { + case ADE9000_PHASE_A_NR: + tmp |= ADE9000_ST1_SWELLA_BIT; + break; + case ADE9000_PHASE_B_NR: + tmp |= ADE9000_ST1_SWELLB_BIT; + break; + case ADE9000_PHASE_C_NR: + tmp |= ADE9000_ST1_SWELLC_BIT; + break; + default: + break; + } + + if (state) { + interrupts |= tmp; + st->wfb_trg |= ADE9000_WFB_TRG_SWELL_BIT; + } else { + interrupts &= ~tmp; + st->wfb_trg &= ~ADE9000_WFB_TRG_SWELL_BIT; + } + } else if (dir == IIO_EV_DIR_FALLING) { + switch (chan->channel) { + case ADE9000_PHASE_A_NR: + tmp |= ADE9000_ST1_DIPA_BIT; + break; + case ADE9000_PHASE_B_NR: + tmp |= ADE9000_ST1_DIPB_BIT; + break; + case ADE9000_PHASE_C_NR: + tmp |= ADE9000_ST1_DIPC_BIT; + break; + default: + break; + } + + if (state) { + interrupts |= tmp; + st->wfb_trg |= ADE9000_WFB_TRG_DIP_BIT; + } else { + interrupts &= ~tmp; + st->wfb_trg &= ~ADE9000_WFB_TRG_DIP_BIT; + } + } + + return regmap_update_bits(st->regmap, ADE9000_REG_MASK1, interrupts, + interrupts); +} + +static int ade9000_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct ade9000_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_FALLING: + return regmap_write(st->regmap, ADE9000_REG_DIP_LVL, val); + case IIO_EV_DIR_RISING: + return regmap_write(st->regmap, ADE9000_REG_SWELL_LVL, val); + case IIO_EV_DIR_NONE: + return regmap_write(st->regmap, ADE9000_REG_ZXTOUT, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ade9000_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct ade9000_state *st = iio_priv(indio_dev); + unsigned int data; + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_FALLING: + ret = regmap_read(st->regmap, ADE9000_REG_DIP_LVL, &data); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + case IIO_EV_DIR_RISING: + ret = regmap_read(st->regmap, ADE9000_REG_SWELL_LVL, &data); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + case IIO_EV_DIR_NONE: + ret = regmap_read(st->regmap, ADE9000_REG_ZXTOUT, &data); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ade9000_waveform_buffer_config(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 wfb_cfg_val = 0; + u32 active_scans; + + bitmap_to_arr32(&active_scans, indio_dev->active_scan_mask, + indio_dev->masklength); + + switch (active_scans) { + case ADE9000_SCAN_POS_IA | ADE9000_SCAN_POS_VA: + wfb_cfg_val = ADE9000_WFB_CFG_IA_VA; + st->wfb_nr_activ_chan = 2; + break; + case ADE9000_SCAN_POS_IB | ADE9000_SCAN_POS_VB: + wfb_cfg_val = ADE9000_WFB_CFG_IB_VB; + st->wfb_nr_activ_chan = 2; + break; + case ADE9000_SCAN_POS_IC | ADE9000_SCAN_POS_VC: + wfb_cfg_val = ADE9000_WFB_CFG_IC_VC; + st->wfb_nr_activ_chan = 2; + break; + case ADE9000_SCAN_POS_IA: + wfb_cfg_val = ADE9000_WFB_CFG_IA; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_VA: + wfb_cfg_val = ADE9000_WFB_CFG_VA; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_IB: + wfb_cfg_val = ADE9000_WFB_CFG_IB; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_VB: + wfb_cfg_val = ADE9000_WFB_CFG_VB; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_IC: + wfb_cfg_val = ADE9000_WFB_CFG_IC; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_VC: + wfb_cfg_val = ADE9000_WFB_CFG_VC; + st->wfb_nr_activ_chan = 1; + break; + case (ADE9000_SCAN_POS_IA | ADE9000_SCAN_POS_VA | ADE9000_SCAN_POS_IB | + ADE9000_SCAN_POS_VB | ADE9000_SCAN_POS_IC | ADE9000_SCAN_POS_VC): + wfb_cfg_val = ADE9000_WFB_CFG_ALL_CHAN; + st->wfb_nr_activ_chan = 6; + break; + default: + dev_err(&st->spi->dev, "Unsupported combination of scans\n"); + return -EINVAL; + } + + wfb_cfg_val |= FIELD_PREP(ADE9000_WF_SRC_MASK, st->wf_src); + + return regmap_write(st->regmap, ADE9000_REG_WFB_CFG, wfb_cfg_val); +} + +static int ade9000_waveform_buffer_interrupt_setup(struct ade9000_state *st) +{ + int ret; + + ret = regmap_write(st->regmap, ADE9000_REG_WFB_TRG_CFG, 0x0); + if (ret) + return ret; + + /* Always use streaming mode setup */ + ret = regmap_write(st->regmap, ADE9000_REG_WFB_PG_IRQEN, + ADE9000_MIDDLE_PAGE_BIT); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADE9000_REG_STATUS0, GENMASK(31, 0)); + if (ret) + return ret; + + return regmap_set_bits(st->regmap, ADE9000_REG_MASK0, + ADE9000_ST0_PAGE_FULL_BIT); +} + +static int ade9000_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + int ret; + + ret = ade9000_waveform_buffer_config(indio_dev); + if (ret) + return ret; + + st->wfb_nr_samples = ADE9000_WFB_MAX_SAMPLES_CHAN * st->wfb_nr_activ_chan; + + ret = ade9000_configure_scan(indio_dev, ADE9000_REG_WF_BUFF); + if (ret) + return ret; + + ret = ade9000_waveform_buffer_interrupt_setup(st); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_CAP_EN_MASK, + ADE9000_WF_CAP_EN_MASK); + if (ret) { + dev_err(&st->spi->dev, "Post-enable waveform buffer enable fail\n"); + return ret; + } + + return 0; +} + +static int ade9000_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + u32 interrupts; + int ret; + + ret = regmap_update_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_CAP_EN_MASK, 0); + if (ret) { + dev_err(dev, "Post-disable waveform buffer disable fail\n"); + return ret; + } + + ret = regmap_write(st->regmap, ADE9000_REG_WFB_TRG_CFG, 0x0); + if (ret) + return ret; + + interrupts = ADE9000_ST0_WFB_TRIG_BIT | ADE9000_ST0_PAGE_FULL_BIT; + + ret = regmap_update_bits(st->regmap, ADE9000_REG_MASK0, interrupts, 0); + if (ret) { + dev_err(dev, "Post-disable update maks0 fail\n"); + return ret; + } + + return regmap_write(st->regmap, ADE9000_REG_STATUS0, GENMASK(31, 0)); +} + +static int ade9000_reset(struct ade9000_state *st) +{ + struct device *dev = &st->spi->dev; + struct gpio_desc *gpio_reset; + int ret; + + gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(gpio_reset)) + return PTR_ERR(gpio_reset); + + if (gpio_reset) { + fsleep(10); + gpiod_set_value_cansleep(gpio_reset, 0); + fsleep(50000); + } else { + ret = regmap_update_bits(st->regmap, ADE9000_REG_CONFIG1, + ADE9000_SWRST_BIT, ADE9000_SWRST_BIT); + if (ret) + return ret; + fsleep(90); + return 0; + } + + if (!wait_for_completion_timeout(&st->reset_completion, + msecs_to_jiffies(1000))) { + dev_err(dev, "Reset timeout after 1s\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int ade9000_setup(struct ade9000_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + ret = regmap_multi_reg_write(st->regmap, ade9000_reg_sequence, + ARRAY_SIZE(ade9000_reg_sequence)); + if (ret) + return dev_err_probe(dev, ret, "Failed to write register sequence"); + + fsleep(2000); + + /* Clear all pending status bits by writing 1s */ + ret = regmap_write(st->regmap, ADE9000_REG_STATUS0, GENMASK(31, 0)); + if (ret) + return dev_err_probe(dev, ret, "Failed to clear STATUS0"); + + ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, GENMASK(31, 0)); + if (ret) + return dev_err_probe(dev, ret, "Failed to clear STATUS1"); + + return 0; +} + +static const struct iio_buffer_setup_ops ade9000_buffer_ops = { + .preenable = &ade9000_buffer_preenable, + .postdisable = &ade9000_buffer_postdisable, +}; + +static const struct iio_info ade9000_info = { + .read_raw = ade9000_read_raw, + .write_raw = ade9000_write_raw, + .debugfs_reg_access = ade9000_reg_access, + .write_event_config = ade9000_write_event_config, + .read_event_config = ade9000_read_event_config, + .write_event_value = ade9000_write_event_value, + .read_event_value = ade9000_read_event_value, +}; + +static const struct regmap_config ade9000_regmap_config = { + .reg_bits = 16, + .val_bits = 32, + .zero_flag_mask = true, + .cache_type = REGCACHE_RBTREE, + .reg_read = ade9000_spi_read_reg, + .reg_write = ade9000_spi_write_reg, + .volatile_reg = ade9000_is_volatile_reg, +}; + +static int ade9000_setup_clkout(struct device *dev, struct ade9000_state *st) +{ + struct clk_init_data clk_init = {}; + struct clk *clkout; + int ret; + + /* + * Only provide clock output when using external CMOS clock. + * When using crystal, CLKOUT is connected to crystal and shouldn't + * be used as clock provider for other devices. + */ + if (!device_property_present(dev, "#clock-cells") || !st->clkin) + return 0; + + clk_init.name = "clkout"; + clk_init.ops = &ade9000_clkout_ops; + clk_init.flags = CLK_GET_RATE_NOCACHE; + clk_init.num_parents = 0; + + st->clkout_hw.init = &clk_init; + + clkout = devm_clk_register(dev, &st->clkout_hw); + if (IS_ERR(clkout)) + return dev_err_probe(dev, PTR_ERR(clkout), "Failed to register clkout"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &st->clkout_hw); + if (ret) + return dev_err_probe(dev, ret, "Failed to add clock provider"); + + return 0; +} + +static int ade9000_request_irq(struct device *dev, const char *name, + irq_handler_t handler, void *dev_id) +{ + int irq, ret; + + irq = fwnode_irq_get_byname(dev_fwnode(dev), name); + if (irq < 0) + return 0; + + ret = devm_request_threaded_irq(dev, irq, NULL, handler, + IRQF_ONESHOT, KBUILD_MODNAME, dev_id); + if (ret) + return dev_err_probe(dev, ret, "Failed to request %s irq", name); + + return 0; +} + +static int ade9000_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ade9000_state *st; + struct regmap *regmap; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return dev_err_probe(dev, -ENOMEM, "Unable to allocate ADE9000 IIO"); + + st = iio_priv(indio_dev); + + regmap = devm_regmap_init(dev, NULL, st, &ade9000_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Unable to allocate ADE9000 regmap"); + + st->regmap = regmap; + st->spi = spi; + + init_completion(&st->reset_completion); + + ret = ade9000_request_irq(dev, "irq0", ade9000_irq0_thread, indio_dev); + if (ret) + return ret; + + ret = ade9000_request_irq(dev, "irq1", ade9000_irq1_thread, indio_dev); + if (ret) + return ret; + + ret = ade9000_request_irq(dev, "dready", ade9000_dready_thread, indio_dev); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + /* External CMOS clock input (optional - crystal can be used instead) */ + st->clkin = devm_clk_get_optional_enabled(dev, "clkin"); + if (IS_ERR(st->clkin)) + return dev_err_probe(dev, PTR_ERR(st->clkin), "Failed to get and enable clkin"); + + ret = ade9000_setup_clkout(dev, st); + if (ret) + return ret; + + indio_dev->name = "ade9000"; + indio_dev->info = &ade9000_info; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->setup_ops = &ade9000_buffer_ops; + + ret = devm_regulator_get_enable(&spi->dev, "vdd"); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to get and enable vdd regulator\n"); + + ret = devm_regulator_get_enable_optional(dev, "vref"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, + "Failed to get and enable vref regulator\n"); + + /* Configure reference selection based on vref regulator availability */ + if (ret != -ENODEV) { + ret = regmap_update_bits(st->regmap, ADE9000_REG_CONFIG1, + ADE9000_EXT_REF_MASK, + ADE9000_EXT_REF_MASK); + if (ret) + return ret; + } + + indio_dev->channels = ade9000_channels; + indio_dev->num_channels = ARRAY_SIZE(ade9000_channels); + + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, + &ade9000_buffer_ops); + if (ret) + return dev_err_probe(dev, ret, "Failed to setup IIO buffer"); + + ret = ade9000_reset(st); + if (ret) + return ret; + + ret = ade9000_setup(st); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +}; + +static const struct spi_device_id ade9000_id[] = { + { "ade9000", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ade9000_id); + +static const struct of_device_id ade9000_of_match[] = { + { .compatible = "adi,ade9000" }, + { } +}; +MODULE_DEVICE_TABLE(of, ade9000_of_match); + +static struct spi_driver ade9000_driver = { + .driver = { + .name = "ade9000", + .of_match_table = ade9000_of_match, + }, + .probe = ade9000_probe, + .id_table = ade9000_id, +}; +module_spi_driver(ade9000_driver); + +MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>"); +MODULE_DESCRIPTION("Analog Devices ADE9000"); +MODULE_LICENSE("GPL"); -- 2.43.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v5 4/6] iio: adc: add ade9000 support 2025-08-22 16:01 ` [PATCH v5 4/6] iio: adc: add ade9000 support Antoniu Miclaus @ 2025-08-25 1:19 ` kernel test robot 2025-08-25 12:27 ` Marcelo Schmitt ` (2 subsequent siblings) 3 siblings, 0 replies; 14+ messages in thread From: kernel test robot @ 2025-08-25 1:19 UTC (permalink / raw) To: Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, linux-kernel, devicetree Cc: oe-kbuild-all, Antoniu Miclaus Hi Antoniu, kernel test robot noticed the following build errors: [auto build test ERROR on jic23-iio/togreg] [also build test ERROR on robh/for-next linus/master v6.17-rc3 next-20250822] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Antoniu-Miclaus/iio-add-IIO_ALTCURRENT-channel-type/20250823-001017 base: https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git togreg patch link: https://lore.kernel.org/r/20250822160157.5092-5-antoniu.miclaus%40analog.com patch subject: [PATCH v5 4/6] iio: adc: add ade9000 support config: i386-randconfig-r123-20250824 (https://download.01.org/0day-ci/archive/20250825/202508250933.cmESvJJG-lkp@intel.com/config) compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250825/202508250933.cmESvJJG-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202508250933.cmESvJJG-lkp@intel.com/ All errors (new ones prefixed by >>, old ones prefixed by <<): >> ERROR: modpost: "devm_clk_register" [drivers/iio/adc/ade9000.ko] undefined! -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v5 4/6] iio: adc: add ade9000 support 2025-08-22 16:01 ` [PATCH v5 4/6] iio: adc: add ade9000 support Antoniu Miclaus 2025-08-25 1:19 ` kernel test robot @ 2025-08-25 12:27 ` Marcelo Schmitt 2025-08-25 13:00 ` Dan Carpenter 2025-08-25 13:48 ` Jonathan Cameron 3 siblings, 0 replies; 14+ messages in thread From: Marcelo Schmitt @ 2025-08-25 12:27 UTC (permalink / raw) To: Antoniu Miclaus Cc: jic23, robh, conor+dt, linux-iio, linux-kernel, devicetree Hi Antoniu, This is still not a complete review, though, if going to re-spin, you may consider changing a few minor things. On 08/22, Antoniu Miclaus wrote: > Add driver support for the ade9000. highly accurate, > fully integrated, multiphase energy and power quality > monitoring device. > > Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> > --- ... > +#define ADE9000_PHASE_B_POS_BIT BIT(5) > +#define ADE9000_PHASE_C_POS_BIT BIT(6) > + > +#define ADE9000_MAX_PHASE_NR 3 > +#define AD9000_CHANNELS_PER_PHASE 10 > + > +#define ADE9000_ADDR_ADJUST(addr, chan) \ > + (((chan) == 0 ? 0 : (chan) == 1 ? 2 : 4) << 4 | (addr)) Found it a bit hard to understand the reason why this macro is like that. I wonder if a comment to help understand it but not sure. Also, couldn't come up with any suggestion for an alternative. Guess this device's register layout is just a bit unusual. > + ... > +static int ade9000_filter_type_get(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan) > +{ > + struct ade9000_state *st = iio_priv(indio_dev); > + u32 val; > + int ret, i; nitpicking: unsigned int i; ? > + > + ret = regmap_read(st->regmap, ADE9000_REG_WFB_CFG, &val); > + if (ret) > + return ret; > + > + val = FIELD_GET(ADE9000_WF_SRC_MASK, val); > + > + for (i = 0; i < ARRAY_SIZE(ade9000_filter_type_values); i++) { > + if (ade9000_filter_type_values[i] == val) > + return i; > + } > + > + return -EINVAL; > +} > + ... > +static const struct iio_enum ade9000_filter_type_enum = { > + .items = ade9000_filter_type_items, > + .num_items = ARRAY_SIZE(ade9000_filter_type_items), > + .get = ade9000_filter_type_get, > + .set = ade9000_filter_type_set, > +}; > + > +static const struct iio_chan_spec_ext_info ade9000_ext_info[] = { > + IIO_ENUM("filter_type", IIO_SHARED_BY_ALL, &ade9000_filter_type_enum), > + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_ALL, &ade9000_filter_type_enum), > + {} nitpicking: these sentinels have been standardized to use a space between the brackets { } https://lore.kernel.org/linux-iio/20250411-iio-sentinel-normalization-v1-1-d293de3e3d93@baylibre.com/ > +}; > + ... > + > +#define ADE9000_VOLTAGE_CHANNEL(num) { \ > + .type = IIO_VOLTAGE, \ > + .channel = num, \ > + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AV_PCF, num), \ > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ > + BIT(IIO_CHAN_INFO_SCALE) | \ > + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ > + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ duplicated IIO_CHAN_INFO_CALIBSCALE ? > + BIT(IIO_CHAN_INFO_FREQUENCY), \ > + .event_spec = ade9000_events, \ > + .num_event_specs = ARRAY_SIZE(ade9000_events), \ > + .scan_index = num + 1, /* interleave with current channels */ \ > + .indexed = 1, \ > + .scan_type = { \ > + .sign = 's', \ > + .realbits = 32, \ > + .storagebits = 32, \ > + .endianness = IIO_BE, \ > + }, \ > + .ext_info = ade9000_ext_info, \ > +} > + ... > + case ADE9000_ST1_SEQERR_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, > + ADE9000_ST1_SEQERR_BIT >> 12, > + IIO_EV_TYPE_CHANGE, > + IIO_EV_DIR_NONE), > + timestamp); > + handled_irq |= ADE9000_ST1_SEQERR_BIT; > + break; > + default: > + return IRQ_HANDLED; > + } > + } > + > + ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, handled_irq); > + if (ret) > + return ret; maybe if (ret) dev_err(&st->spi->dev, "IRQ1 write status1 fail\n"); return IRQ_HANDLED; ? So the IRQ always gets handled. > + > + return IRQ_HANDLED; > +} > + Best regards, Marcelo ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v5 4/6] iio: adc: add ade9000 support 2025-08-22 16:01 ` [PATCH v5 4/6] iio: adc: add ade9000 support Antoniu Miclaus 2025-08-25 1:19 ` kernel test robot 2025-08-25 12:27 ` Marcelo Schmitt @ 2025-08-25 13:00 ` Dan Carpenter 2025-08-25 13:48 ` Jonathan Cameron 3 siblings, 0 replies; 14+ messages in thread From: Dan Carpenter @ 2025-08-25 13:00 UTC (permalink / raw) To: oe-kbuild, Antoniu Miclaus, jic23, robh, conor+dt, linux-iio, linux-kernel, devicetree Cc: lkp, oe-kbuild-all, Antoniu Miclaus Hi Antoniu, kernel test robot noticed the following build warnings: https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Antoniu-Miclaus/iio-add-IIO_ALTCURRENT-channel-type/20250823-001017 base: https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git togreg patch link: https://lore.kernel.org/r/20250822160157.5092-5-antoniu.miclaus%40analog.com patch subject: [PATCH v5 4/6] iio: adc: add ade9000 support config: arm-randconfig-r072-20250824 (https://download.01.org/0day-ci/archive/20250825/202508250158.KQ6WdkKh-lkp@intel.com/config) compiler: arm-linux-gnueabi-gcc (GCC) 8.5.0 If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Reported-by: Dan Carpenter <dan.carpenter@linaro.org> | Closes: https://lore.kernel.org/r/202508250158.KQ6WdkKh-lkp@intel.com/ New smatch warnings: drivers/iio/adc/ade9000.c:1009 ade9000_irq1_thread() warn: right shifting more than type allows 16 vs 20 drivers/iio/adc/ade9000.c:1505 ade9000_write_event_config() error: uninitialized symbol 'tmp'. drivers/iio/adc/ade9000.c:1554 ade9000_write_event_config() error: uninitialized symbol 'interrupts'. Old smatch warnings: drivers/iio/adc/ade9000.c:1018 ade9000_irq1_thread() warn: right shifting more than type allows 16 vs 20 drivers/iio/adc/ade9000.c:1027 ade9000_irq1_thread() warn: right shifting more than type allows 16 vs 20 drivers/iio/adc/ade9000.c:1036 ade9000_irq1_thread() warn: right shifting more than type allows 16 vs 20 drivers/iio/adc/ade9000.c:1045 ade9000_irq1_thread() warn: right shifting more than type allows 16 vs 20 drivers/iio/adc/ade9000.c:1054 ade9000_irq1_thread() warn: right shifting more than type allows 16 vs 20 vim +1009 drivers/iio/adc/ade9000.c b695e630eecec70 Antoniu Miclaus 2025-08-22 998 case ADE9000_ST1_ZXIC_BIT: b695e630eecec70 Antoniu Miclaus 2025-08-22 999 iio_push_event(indio_dev, b695e630eecec70 Antoniu Miclaus 2025-08-22 1000 IIO_UNMOD_EVENT_CODE(IIO_CURRENT, b695e630eecec70 Antoniu Miclaus 2025-08-22 1001 ADE9000_ST1_ZXIC_BIT, b695e630eecec70 Antoniu Miclaus 2025-08-22 1002 IIO_EV_TYPE_THRESH, b695e630eecec70 Antoniu Miclaus 2025-08-22 1003 IIO_EV_DIR_EITHER), b695e630eecec70 Antoniu Miclaus 2025-08-22 1004 timestamp); b695e630eecec70 Antoniu Miclaus 2025-08-22 1005 handled_irq |= ADE9000_ST1_ZXIC_BIT; b695e630eecec70 Antoniu Miclaus 2025-08-22 1006 break; b695e630eecec70 Antoniu Miclaus 2025-08-22 1007 case ADE9000_ST1_SWELLA_BIT: b695e630eecec70 Antoniu Miclaus 2025-08-22 1008 iio_push_event(indio_dev, b695e630eecec70 Antoniu Miclaus 2025-08-22 @1009 IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, b695e630eecec70 Antoniu Miclaus 2025-08-22 1010 ADE9000_ST1_SWELLA_BIT >> 20, This is a kind of macro expansion bug: include/linux/iio/events.h 27 #define _IIO_EVENT_CODE(chan_type, diff, modifier, direction, \ 28 type, chan, chan1, chan2) \ 29 (((u64)type << 56) | ((u64)diff << 55) | \ 30 ((u64)direction << 48) | ((u64)modifier << 40) | \ 31 ((u64)chan_type << 32) | (((u16)chan2) << 16) | ((u16)chan1) | \ 32 ((u16)chan)) There should be parenthese around "chan" on line 32. ((u16)(chan))) Otherwise it's a precendent bug and we end up doing the cast before we do the ">> 20". Probably around the others as well? Probably best if someone tests this. drivers/iio/adc/ade9000.c 1425 static int ade9000_write_event_config(struct iio_dev *indio_dev, 1426 const struct iio_chan_spec *chan, 1427 enum iio_event_type type, 1428 enum iio_event_direction dir, 1429 bool state) 1430 { 1431 struct ade9000_state *st = iio_priv(indio_dev); 1432 u32 interrupts, tmp; 1433 int ret; 1434 1435 /* Clear all pending events in STATUS1 register (write 1 to clear) */ 1436 ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, GENMASK(31, 0)); 1437 if (ret) 1438 return ret; 1439 1440 if (type == IIO_EV_TYPE_MAG) { 1441 ret = regmap_update_bits(st->regmap, ADE9000_REG_STATUS0, 1442 ADE9000_ST0_EGYRDY, ADE9000_ST0_EGYRDY); 1443 if (ret) 1444 return ret; 1445 return regmap_update_bits(st->regmap, ADE9000_REG_MASK0, 1446 ADE9000_ST0_EGYRDY, 1447 state ? ADE9000_ST1_SEQERR_BIT : 0); 1448 } 1449 1450 if (type == IIO_EV_TYPE_CHANGE) 1451 return regmap_update_bits(st->regmap, ADE9000_REG_MASK1, 1452 ADE9000_ST1_SEQERR_BIT, 1453 state ? ADE9000_ST1_SEQERR_BIT : 0); 1454 1455 if (dir == IIO_EV_DIR_EITHER) { 1456 static const struct { 1457 u32 irq; 1458 u32 wfb_trg; 1459 } trig_arr[6] = { 1460 { 1461 .irq = ADE9000_ST1_ZXVA_BIT, 1462 .wfb_trg = ADE9000_WFB_TRG_ZXVA_BIT 1463 }, { 1464 .irq = ADE9000_ST1_ZXIA_BIT, 1465 .wfb_trg = ADE9000_WFB_TRG_ZXIA_BIT 1466 }, { 1467 .irq = ADE9000_ST1_ZXVB_BIT, 1468 .wfb_trg = ADE9000_WFB_TRG_ZXVB_BIT 1469 }, { 1470 .irq = ADE9000_ST1_ZXIB_BIT, 1471 .wfb_trg = ADE9000_WFB_TRG_ZXIB_BIT 1472 }, { 1473 .irq = ADE9000_ST1_ZXVC_BIT, 1474 .wfb_trg = ADE9000_WFB_TRG_ZXVC_BIT 1475 }, { 1476 .irq = ADE9000_ST1_ZXIC_BIT, 1477 .wfb_trg = ADE9000_WFB_TRG_ZXIC_BIT 1478 }, 1479 }; 1480 if (state) { 1481 interrupts |= trig_arr[chan->channel * 2 + chan->type].irq; 1482 st->wfb_trg |= trig_arr[chan->channel * 2 + chan->type].wfb_trg; 1483 } else { 1484 interrupts &= ~trig_arr[chan->channel * 2 + chan->type].irq; 1485 st->wfb_trg &= ~trig_arr[chan->channel * 2 + chan->type].wfb_trg; 1486 } 1487 } 1488 1489 if (dir == IIO_EV_DIR_NONE) { 1490 switch (chan->channel) { 1491 case ADE9000_PHASE_A_NR: 1492 tmp |= ADE9000_ST1_ZXTOVA_BIT; You can |= an uninitialized variable. 1493 break; 1494 case ADE9000_PHASE_B_NR: 1495 tmp |= ADE9000_ST1_ZXTOVB_BIT; 1496 break; 1497 case ADE9000_PHASE_C_NR: 1498 tmp |= ADE9000_ST1_ZXTOVC_BIT; 1499 break; 1500 default: 1501 break; 1502 } 1503 1504 if (state) 1505 interrupts |= tmp; Same. 1506 else 1507 interrupts &= ~tmp; 1508 } else if (dir == IIO_EV_DIR_RISING) { 1509 switch (chan->channel) { 1510 case ADE9000_PHASE_A_NR: 1511 tmp |= ADE9000_ST1_SWELLA_BIT; 1512 break; 1513 case ADE9000_PHASE_B_NR: 1514 tmp |= ADE9000_ST1_SWELLB_BIT; 1515 break; 1516 case ADE9000_PHASE_C_NR: 1517 tmp |= ADE9000_ST1_SWELLC_BIT; 1518 break; 1519 default: 1520 break; 1521 } 1522 1523 if (state) { 1524 interrupts |= tmp; 1525 st->wfb_trg |= ADE9000_WFB_TRG_SWELL_BIT; 1526 } else { 1527 interrupts &= ~tmp; 1528 st->wfb_trg &= ~ADE9000_WFB_TRG_SWELL_BIT; 1529 } 1530 } else if (dir == IIO_EV_DIR_FALLING) { 1531 switch (chan->channel) { 1532 case ADE9000_PHASE_A_NR: 1533 tmp |= ADE9000_ST1_DIPA_BIT; 1534 break; 1535 case ADE9000_PHASE_B_NR: 1536 tmp |= ADE9000_ST1_DIPB_BIT; 1537 break; 1538 case ADE9000_PHASE_C_NR: 1539 tmp |= ADE9000_ST1_DIPC_BIT; 1540 break; 1541 default: 1542 break; 1543 } 1544 1545 if (state) { 1546 interrupts |= tmp; 1547 st->wfb_trg |= ADE9000_WFB_TRG_DIP_BIT; 1548 } else { 1549 interrupts &= ~tmp; 1550 st->wfb_trg &= ~ADE9000_WFB_TRG_DIP_BIT; 1551 } 1552 } 1553 1554 return regmap_update_bits(st->regmap, ADE9000_REG_MASK1, interrupts, 1555 interrupts); 1556 } -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v5 4/6] iio: adc: add ade9000 support 2025-08-22 16:01 ` [PATCH v5 4/6] iio: adc: add ade9000 support Antoniu Miclaus ` (2 preceding siblings ...) 2025-08-25 13:00 ` Dan Carpenter @ 2025-08-25 13:48 ` Jonathan Cameron 3 siblings, 0 replies; 14+ messages in thread From: Jonathan Cameron @ 2025-08-25 13:48 UTC (permalink / raw) To: Antoniu Miclaus; +Cc: robh, conor+dt, linux-iio, linux-kernel, devicetree On Fri, 22 Aug 2025 16:01:53 +0000 Antoniu Miclaus <antoniu.miclaus@analog.com> wrote: > Add driver support for the ade9000. highly accurate, > fully integrated, multiphase energy and power quality > monitoring device. > > Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> Various comments inline. Thanks, Jonathan > diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c > new file mode 100644 > index 000000000000..41f766e00de0 > --- /dev/null > +++ b/drivers/iio/adc/ade9000.c > @@ -0,0 +1,2011 @@ > +/* Peak and overcurrent detection disabled */ > +#define ADE9000_CONFIG3 0x0000 > + > +/* > + * 50Hz operation, 3P4W Wye configuration, signed accumulation No idea what a 3P4W Wye configuration is... Spell that out. > + * Clear bit 8 i.e. ACCMODE=0x00xx for 50Hz operation > + * ACCMODE=0x0x9x for 3Wire delta when phase B is used as reference > + */ > > + > +static const struct iio_event_spec ade9000_events[] = { > + { > + /* Energy ready event - datasheet: EGYRDY interrupt */ I'll address these alongside the docs. > + .type = IIO_EV_TYPE_MAG, > + .dir = IIO_EV_DIR_NONE, /* Non-directional event */ > + .mask_shared_by_all = BIT(IIO_EV_INFO_ENABLE), > + }, > + { > + /* Sequence error event - datasheet: SEQERR interrupt */ > + .type = IIO_EV_TYPE_CHANGE, > + .dir = IIO_EV_DIR_NONE, /* Non-directional event */ > + .mask_shared_by_all = BIT(IIO_EV_INFO_ENABLE), > + }, > + { > + /* Threshold events - datasheet: zero crossing timeout, sag/swell */ > + .type = IIO_EV_TYPE_THRESH, > + .dir = IIO_EV_DIR_NONE, /* Timeout events (ZXTOUT register) */ > + .mask_separate = BIT(IIO_EV_INFO_ENABLE), /* Per-channel enable */ > + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE), /* Shared threshold value */ > + }, > + { > + .type = IIO_EV_TYPE_THRESH, > + .dir = IIO_EV_DIR_EITHER, > + .mask_separate = BIT(IIO_EV_INFO_ENABLE), > + }, > + { > + .type = IIO_EV_TYPE_THRESH, > + .dir = IIO_EV_DIR_RISING, /* for swell */ > + .mask_separate = BIT(IIO_EV_INFO_ENABLE), > + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE), > + }, > + { > + .type = IIO_EV_TYPE_THRESH, > + .dir = IIO_EV_DIR_FALLING, /* for dip */ > + .mask_separate = BIT(IIO_EV_INFO_ENABLE), > + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE), > + }, > +}; > +#define ADE9000_POWER_ACTIVE_CHANNEL(num) { \ > + .type = IIO_POWER, \ > + .channel = num, \ > + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AWATT, num), \ > + .channel2 = IIO_MOD_ACTIVE, \ > + .modified = 1, \ > + .indexed = 1, \ > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ > + BIT(IIO_CHAN_INFO_SCALE) | \ > + BIT(IIO_CHAN_INFO_CALIBBIAS) | \ > + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ > + BIT(IIO_CHAN_INFO_POWERFACTOR), \ You have power factor documented as in_power0_powerfactor which is not what this will generate. Check the ABI docs vs what the driver generates closely. > + .scan_index = -1 \ > +} > +static int ade9000_spi_write_reg(void *context, unsigned int reg, > + unsigned int val) > +{ > + struct ade9000_state *st = context; > + u16 addr; > + int ret, len; > + > + guard(mutex)(&st->lock); > + > + addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, reg); > + put_unaligned_be16(addr, st->tx); > + > + if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) { > + put_unaligned_be16(val, &st->tx[2]); > + len = 4; > + } else { > + put_unaligned_be32(val, &st->tx[2]); > + len = 6; > + } > + > + ret = spi_write(st->spi, st->tx, len); See below. I'd make this spi_write_then_read() and use local variables + a read size of 0. > + if (ret) > + dev_err(&st->spi->dev, "problem when writing register 0x%x\n", reg); > + > + return ret; > +} > + > +static int ade9000_spi_read_reg(void *context, unsigned int reg, > + unsigned int *val) > +{ > + struct ade9000_state *st = context; > + u16 addr; > + int ret, rx_len; > + > + guard(mutex)(&st->lock); > + > + addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, reg) | > + ADE9000_REG_READ_BIT_MASK; > + > + put_unaligned_be16(addr, st->tx); > + > + /* Skip CRC bytes - only read actual data */ > + if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) > + rx_len = 2; > + else > + rx_len = 4; > + > + ret = spi_write_then_read(st->spi, st->tx, 2, st->rx, rx_len); Using write then read doesn't need to use a DMA safe buffer (it is documented as always bouncing the data) so you could use variables on the stack to keep the data local for read_reg. Could also use write_then_read with a zero size read to allow the same in *spi_write_reg() > + if (ret) { > + dev_err(&st->spi->dev, "error reading register 0x%x\n", reg); > + return ret; > + } > + > + if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) > + *val = get_unaligned_be16(st->rx); > + else > + *val = get_unaligned_be32(st->rx); > + > + return 0; > +} > + > +static int ade9000_configure_scan(struct iio_dev *indio_dev, u32 wfb_addr) > +{ > + struct ade9000_state *st = iio_priv(indio_dev); > + u16 addr; > + > + addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, wfb_addr) | > + ADE9000_REG_READ_BIT_MASK; > + > + put_unaligned_be16(addr, st->tx_buff); > + > + st->xfer[0].tx_buf = &st->tx_buff[0]; > + st->xfer[0].len = 2; > + > + st->xfer[1].rx_buf = st->rx_buff.byte; > + > + /* Always use streaming mode */ > + st->xfer[1].len = (st->wfb_nr_samples / 2) * 4; > + > + spi_message_init_with_transfers(&st->spi_msg, st->xfer, ARRAY_SIZE(st->xfer)); > + return 0; Never fails, return type of void to make that clear. > +} > +static irqreturn_t ade9000_irq1_thread(int irq, void *data) > +{ > + struct iio_dev *indio_dev = data; > + struct ade9000_state *st = iio_priv(indio_dev); > + unsigned int bit = ADE9000_ST1_CROSSING_FIRST; > + s64 timestamp = iio_get_time_ns(indio_dev); > + u32 handled_irq = 0; > + u32 interrupts; u32 interrupts, result, status, tmp; > + u32 result; > + u32 status; > + u32 tmp; > + unsigned long interrupt_bits; > + int ret; > + > + if (!completion_done(&st->reset_completion)) { > + ret = regmap_read(st->regmap, ADE9000_REG_STATUS1, &result); > + if (ret) { > + dev_err(&st->spi->dev, "IRQ1 read status fail\n"); > + return ret; Don't return errno in an irq thread handler. They aren't irqreturn_t > + } > + > + if (result & ADE9000_ST1_RSTDONE_BIT) > + complete(&st->reset_completion); > + else > + dev_err(&st->spi->dev, "Error testing reset done\n"); > + > + return IRQ_HANDLED; > + } > + > + ret = regmap_read(st->regmap, ADE9000_REG_STATUS1, &status); > + if (ret) { > + dev_err(&st->spi->dev, "IRQ1 read status fail\n"); > + return IRQ_HANDLED; > + } > + > + ret = regmap_read(st->regmap, ADE9000_REG_MASK1, &interrupts); > + if (ret) { > + dev_err(&st->spi->dev, "IRQ1 read status fail\n"); > + return IRQ_HANDLED; > + } > + > + interrupt_bits = interrupts; > + for_each_set_bit_from(bit, &interrupt_bits, > + ADE9000_ST1_CROSSING_DEPTH){ > + tmp = status & BIT(bit); > + > + switch (tmp) { > + case ADE9000_ST1_ZXVA_BIT: There are a lot of these, maybe a lookup table with event code and field in handled_irq? Use a marker like no event code to indicate the bits we don't handle. > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, > + ADE9000_ST1_ZXVA_BIT, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_EITHER), > + timestamp); > + handled_irq |= ADE9000_ST1_ZXVA_BIT; These are all the same as handled_irq |= tmp; ? maybe can take that out of the switch statement? If you do a lookup table on bit, that will end up even nicer. > + break; > + case ADE9000_ST1_ZXTOVA_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, > + ADE9000_ST1_ZXTOVA_BIT, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_EITHER), > + timestamp); > + handled_irq |= ADE9000_ST1_ZXTOVA_BIT; > + break; > + case ADE9000_ST1_ZXIA_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_CURRENT, > + ADE9000_ST1_ZXIA_BIT, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_EITHER), > + timestamp); > + handled_irq |= ADE9000_ST1_ZXIA_BIT; > + break; > + case ADE9000_ST1_ZXVB_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, > + ADE9000_ST1_ZXVB_BIT, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_EITHER), > + timestamp); > + handled_irq |= ADE9000_ST1_ZXVB_BIT; > + break; > + case ADE9000_ST1_ZXTOVB_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, > + ADE9000_ST1_ZXTOVB_BIT, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_EITHER), > + timestamp); > + handled_irq |= ADE9000_ST1_ZXTOVB_BIT; > + break; > + case ADE9000_ST1_ZXIB_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_CURRENT, > + ADE9000_ST1_ZXIB_BIT, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_EITHER), > + timestamp); > + handled_irq |= ADE9000_ST1_ZXIB_BIT; > + break; > + case ADE9000_ST1_ZXVC_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, > + ADE9000_ST1_ZXVC_BIT, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_EITHER), > + timestamp); > + handled_irq |= ADE9000_ST1_ZXVC_BIT; > + break; > + case ADE9000_ST1_ZXTOVC_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, > + ADE9000_ST1_ZXTOVC_BIT, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_EITHER), > + timestamp); > + handled_irq |= ADE9000_ST1_ZXTOVC_BIT; > + break; > + case ADE9000_ST1_ZXIC_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_CURRENT, > + ADE9000_ST1_ZXIC_BIT, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_EITHER), > + timestamp); > + handled_irq |= ADE9000_ST1_ZXIC_BIT; > + break; > + case ADE9000_ST1_SWELLA_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, > + ADE9000_ST1_SWELLA_BIT >> 20, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_RISING), > + timestamp); > + handled_irq |= ADE9000_ST1_SWELLA_BIT; > + break; > + case ADE9000_ST1_SWELLB_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, > + ADE9000_ST1_SWELLB_BIT >> 20, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_RISING), > + timestamp); > + handled_irq |= ADE9000_ST1_SWELLB_BIT; > + break; > + case ADE9000_ST1_SWELLC_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, > + ADE9000_ST1_SWELLC_BIT >> 20, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_RISING), > + timestamp); > + handled_irq |= ADE9000_ST1_SWELLC_BIT; > + break; > + case ADE9000_ST1_DIPA_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, > + ADE9000_ST1_DIPA_BIT >> 20, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_FALLING), > + timestamp); > + handled_irq |= ADE9000_ST1_DIPA_BIT; > + break; > + case ADE9000_ST1_DIPB_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, > + ADE9000_ST1_DIPB_BIT >> 20, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_FALLING), > + timestamp); > + handled_irq |= ADE9000_ST1_DIPB_BIT; > + break; > + case ADE9000_ST1_DIPC_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, > + ADE9000_ST1_DIPC_BIT >> 20, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_FALLING), > + timestamp); > + handled_irq |= ADE9000_ST1_DIPC_BIT; > + break; > + case ADE9000_ST1_SEQERR_BIT: > + iio_push_event(indio_dev, > + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, > + ADE9000_ST1_SEQERR_BIT >> 12, > + IIO_EV_TYPE_CHANGE, > + IIO_EV_DIR_NONE), > + timestamp); > + handled_irq |= ADE9000_ST1_SEQERR_BIT; > + break; > + default: > + return IRQ_HANDLED; > + } > + } > + > + ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, handled_irq); > + if (ret) > + return ret; > + > + return IRQ_HANDLED; > +} > + > +static int ade9000_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int *val, > + int *val2, > + long mask) > +{ > + struct ade9000_state *st = iio_priv(indio_dev); > + unsigned int reg, measured; > + int ret; > + > + switch (mask) { > + case IIO_CHAN_INFO_FREQUENCY: > + if (chan->type == IIO_VOLTAGE) { > + int period_reg; > + int period; > + > + switch (chan->channel) { > + case ADE9000_PHASE_A_NR: > + period_reg = ADE9000_REG_APERIOD; > + break; > + case ADE9000_PHASE_B_NR: > + period_reg = ADE9000_REG_BPERIOD; > + break; > + case ADE9000_PHASE_C_NR: > + period_reg = ADE9000_REG_CPERIOD; > + break; > + default: > + return -EINVAL; > + } > + ret = regmap_read(st->regmap, period_reg, &period); > + if (ret) > + return ret; > + *val = 4000 * 65536; Why this particular multiplier? Maybe a spec reference. > + *val2 = period + 1; > + return IIO_VAL_FRACTIONAL; > + } > + > + ret = regmap_read(st->regmap, ADE9000_REG_ACCMODE, ®); > + if (ret) > + return ret; > + *val = (reg & ADE9000_ACCMODE_60HZ) ? 60 : 50; > + return IIO_VAL_INT; > + case IIO_CHAN_INFO_RAW: > + if (chan->type == IIO_ENERGY) { > + u16 lo_reg = chan->address; > + > + ret = regmap_bulk_read(st->regmap, lo_reg, > + st->bulk_read_buf, 2); > + if (ret) > + return ret; > + > + *val = st->bulk_read_buf[0]; /* Lower 32 bits */ > + *val2 = st->bulk_read_buf[1]; /* Upper 32 bits */ > + return IIO_VAL_INT_64; > + } > + > + ret = iio_device_claim_direct(indio_dev); > + if (ret) > + return ret; > + > + ret = regmap_read(st->regmap, chan->address, &measured); > + iio_device_release_direct(indio_dev); > + if (ret) > + return ret; > + > + *val = measured; > + > + return IIO_VAL_INT; > + > + case IIO_CHAN_INFO_POWERFACTOR: > + ret = iio_device_claim_direct(indio_dev); > + if (ret) > + return ret; > + > + /* Map power channel to corresponding power factor register */ > + reg = ADE9000_ADDR_ADJUST(ADE9000_REG_APF, chan->channel); > + ret = regmap_read(st->regmap, reg, &measured); > + iio_device_release_direct(indio_dev); > + if (ret) > + return ret; > + > + *val = measured; > + > + return IIO_VAL_INT; > + > + case IIO_CHAN_INFO_SCALE: > + if (chan->info_mask_shared_by_all) { > + /* Shared PGA gain read - only for channel with shared frequency */ > + ret = regmap_read(st->regmap, ADE9000_REG_PGA_GAIN, ®); > + if (ret) > + return ret; > + *val = min(1 << ((reg >> (8 + chan->channel)) & GENMASK(1, 0)), 4); > + return IIO_VAL_INT; return, so no need for else to follow. same for later else ifs > + } else if (chan->type == IIO_CURRENT || chan->type == IIO_VOLTAGE || > + chan->type == IIO_ALTVOLTAGE) { > + switch (chan->address) { > + case ADE9000_REG_AI_PCF: > + case ADE9000_REG_AV_PCF: > + case ADE9000_REG_BI_PCF: > + case ADE9000_REG_BV_PCF: > + case ADE9000_REG_CI_PCF: > + case ADE9000_REG_CV_PCF: > + *val = 1; > + *val2 = ADE9000_PCF_FULL_SCALE_CODES; > + return IIO_VAL_FRACTIONAL; > + case ADE9000_REG_AIRMS: > + case ADE9000_REG_AVRMS: > + case ADE9000_REG_BIRMS: > + case ADE9000_REG_BVRMS: > + case ADE9000_REG_CIRMS: > + case ADE9000_REG_CVRMS: > + *val = 1; > + *val2 = ADE9000_RMS_FULL_SCALE_CODES; > + return IIO_VAL_FRACTIONAL; > + default: > + return -EINVAL; > + } > + } else if (chan->type == IIO_POWER) { > + *val = 1; > + *val2 = ADE9000_WATT_FULL_SCALE_CODES; > + return IIO_VAL_FRACTIONAL; > + } else { > + return -EINVAL; > + } > + break; > + default: > + return -EINVAL; > + } > +} > + > +static int ade9000_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int val, > + int val2, > + long mask) > +{ > + struct ade9000_state *st = iio_priv(indio_dev); > + u32 addr; > + u32 tmp; u32 addr, tmp; saves us a line for no lost of readability. > + > + switch (mask) { > + case IIO_CHAN_INFO_FREQUENCY: > +} > +static int ade9000_read_event_config(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, > + enum iio_event_type type, > + enum iio_event_direction dir) > +{ > + struct ade9000_state *st = iio_priv(indio_dev); > + u32 interrupts0, interrupts1, number; > + int ret; > + > + ret = regmap_read(st->regmap, ADE9000_REG_MASK0, &interrupts0); > + if (ret) > + return ret; > + > + ret = regmap_read(st->regmap, ADE9000_REG_MASK1, &interrupts1); > + if (ret) > + return ret; > + > + if (type == IIO_EV_TYPE_MAG) > + return interrupts0 & ADE9000_ST0_EGYRDY; Might as well do that before reading interrupts1. Save a bit of bus traffic without complicating the code. Even better, only read interrupts0 at all if type == IIO_EV_TYPE_MAG. > + > + if (type == IIO_EV_TYPE_CHANGE) > + return interrupts1 & ADE9000_ST1_SEQERR_BIT; > + > + number = chan->channel; > + > + switch (number) { > + case ADE9000_PHASE_A_NR: > + if (chan->type == IIO_VOLTAGE) { > + if (dir == IIO_EV_DIR_EITHER) > + return interrupts1 & ADE9000_ST1_ZXVA_BIT; > + if (dir == IIO_EV_DIR_NONE) > + return interrupts1 & ADE9000_ST1_ZXTOVA_BIT; > + if (dir == IIO_EV_DIR_RISING) > + return interrupts1 & ADE9000_ST1_SWELLA_BIT; > + if (dir == IIO_EV_DIR_FALLING) > + return interrupts1 & ADE9000_ST1_DIPA_BIT; > + } else if (chan->type == IIO_CURRENT) { > + return interrupts1 & ADE9000_ST1_ZXIA_BIT; > + } > + break; > + case ADE9000_PHASE_B_NR: > + if (chan->type == IIO_VOLTAGE) { > + if (dir == IIO_EV_DIR_EITHER) > + return interrupts1 & ADE9000_ST1_ZXVB_BIT; > + if (dir == IIO_EV_DIR_NONE) > + return interrupts1 & ADE9000_ST1_ZXTOVB_BIT; > + if (dir == IIO_EV_DIR_RISING) > + return interrupts1 & ADE9000_ST1_SWELLB_BIT; > + if (dir == IIO_EV_DIR_FALLING) > + return interrupts1 & ADE9000_ST1_DIPB_BIT; > + } else if (chan->type == IIO_CURRENT) { > + return interrupts1 & ADE9000_ST1_ZXIB_BIT; > + } > + break; > + case ADE9000_PHASE_C_NR: > + if (chan->type == IIO_VOLTAGE) { > + if (dir == IIO_EV_DIR_EITHER) > + return interrupts1 & ADE9000_ST1_ZXVC_BIT; > + if (dir == IIO_EV_DIR_NONE) > + return interrupts1 & ADE9000_ST1_ZXTOVC_BIT; > + if (dir == IIO_EV_DIR_RISING) > + return interrupts1 & ADE9000_ST1_SWELLC_BIT; > + if (dir == IIO_EV_DIR_FALLING) > + return interrupts1 & ADE9000_ST1_DIPC_BIT; > + } else if (chan->type == IIO_CURRENT) { > + return interrupts1 & ADE9000_ST1_ZXIC_BIT; > + } > + break; > + default: > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int ade9000_write_event_config(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, > + enum iio_event_type type, > + enum iio_event_direction dir, > + bool state) > +{ > + struct ade9000_state *st = iio_priv(indio_dev); > + u32 interrupts, tmp; > + int ret; > + > + /* Clear all pending events in STATUS1 register (write 1 to clear) */ > + ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, GENMASK(31, 0)); > + if (ret) > + return ret; > + > + if (type == IIO_EV_TYPE_MAG) { > + ret = regmap_update_bits(st->regmap, ADE9000_REG_STATUS0, > + ADE9000_ST0_EGYRDY, ADE9000_ST0_EGYRDY); regmap_set_bits() exists for this case. > + if (ret) > + return ret; > + return regmap_update_bits(st->regmap, ADE9000_REG_MASK0, > + ADE9000_ST0_EGYRDY, MASK and field don't seem to match even wrt to which register they are in. Having fixed that regmap_assign_bits() probably useufl here. > + state ? ADE9000_ST1_SEQERR_BIT : 0); > + } > + > + if (type == IIO_EV_TYPE_CHANGE) > + return regmap_update_bits(st->regmap, ADE9000_REG_MASK1, > + ADE9000_ST1_SEQERR_BIT, > + state ? ADE9000_ST1_SEQERR_BIT : 0); return regmap_assign_bits(st->regmap, ADE9000_REG_MASK1, ADE9000_ST1_SEQERR_BIT, state); is a little simpler and avoids chance of register / field mismatch like above. > + > + if (dir == IIO_EV_DIR_EITHER) { > + static const struct { > + u32 irq; > + u32 wfb_trg; > + } trig_arr[6] = { > + { > + .irq = ADE9000_ST1_ZXVA_BIT, > + .wfb_trg = ADE9000_WFB_TRG_ZXVA_BIT > + }, { > + .irq = ADE9000_ST1_ZXIA_BIT, > + .wfb_trg = ADE9000_WFB_TRG_ZXIA_BIT > + }, { > + .irq = ADE9000_ST1_ZXVB_BIT, > + .wfb_trg = ADE9000_WFB_TRG_ZXVB_BIT > + }, { > + .irq = ADE9000_ST1_ZXIB_BIT, > + .wfb_trg = ADE9000_WFB_TRG_ZXIB_BIT > + }, { > + .irq = ADE9000_ST1_ZXVC_BIT, > + .wfb_trg = ADE9000_WFB_TRG_ZXVC_BIT > + }, { > + .irq = ADE9000_ST1_ZXIC_BIT, > + .wfb_trg = ADE9000_WFB_TRG_ZXIC_BIT > + }, > + }; > + if (state) { > + interrupts |= trig_arr[chan->channel * 2 + chan->type].irq; > + st->wfb_trg |= trig_arr[chan->channel * 2 + chan->type].wfb_trg; > + } else { > + interrupts &= ~trig_arr[chan->channel * 2 + chan->type].irq; > + st->wfb_trg &= ~trig_arr[chan->channel * 2 + chan->type].wfb_trg; > + } > + } > + > + if (dir == IIO_EV_DIR_NONE) { > + switch (chan->channel) { > + case ADE9000_PHASE_A_NR: > + tmp |= ADE9000_ST1_ZXTOVA_BIT; > + break; > + case ADE9000_PHASE_B_NR: > + tmp |= ADE9000_ST1_ZXTOVB_BIT; > + break; > + case ADE9000_PHASE_C_NR: > + tmp |= ADE9000_ST1_ZXTOVC_BIT; > + break; > + default: > + break; > + } > + > + if (state) > + interrupts |= tmp; > + else > + interrupts &= ~tmp; > + } else if (dir == IIO_EV_DIR_RISING) { > + switch (chan->channel) { > + case ADE9000_PHASE_A_NR: > + tmp |= ADE9000_ST1_SWELLA_BIT; > + break; > + case ADE9000_PHASE_B_NR: > + tmp |= ADE9000_ST1_SWELLB_BIT; > + break; > + case ADE9000_PHASE_C_NR: > + tmp |= ADE9000_ST1_SWELLC_BIT; > + break; > + default: > + break; > + } > + > + if (state) { > + interrupts |= tmp; > + st->wfb_trg |= ADE9000_WFB_TRG_SWELL_BIT; > + } else { > + interrupts &= ~tmp; > + st->wfb_trg &= ~ADE9000_WFB_TRG_SWELL_BIT; > + } > + } else if (dir == IIO_EV_DIR_FALLING) { > + switch (chan->channel) { > + case ADE9000_PHASE_A_NR: > + tmp |= ADE9000_ST1_DIPA_BIT; > + break; > + case ADE9000_PHASE_B_NR: > + tmp |= ADE9000_ST1_DIPB_BIT; > + break; > + case ADE9000_PHASE_C_NR: > + tmp |= ADE9000_ST1_DIPC_BIT; > + break; > + default: > + break; > + } > + > + if (state) { > + interrupts |= tmp; > + st->wfb_trg |= ADE9000_WFB_TRG_DIP_BIT; > + } else { > + interrupts &= ~tmp; > + st->wfb_trg &= ~ADE9000_WFB_TRG_DIP_BIT; > + } > + } > + > + return regmap_update_bits(st->regmap, ADE9000_REG_MASK1, interrupts, > + interrupts); return regmap_set_bits() check the rest of the driver for places where these clear/set/assign regmap functions can simplifiy things. > +} > +static int ade9000_reset(struct ade9000_state *st) > +{ > + struct device *dev = &st->spi->dev; > + struct gpio_desc *gpio_reset; > + int ret; > + > + gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); > + if (IS_ERR(gpio_reset)) > + return PTR_ERR(gpio_reset); > + > + if (gpio_reset) { > + fsleep(10); > + gpiod_set_value_cansleep(gpio_reset, 0); > + fsleep(50000); > + } else { > + ret = regmap_update_bits(st->regmap, ADE9000_REG_CONFIG1, > + ADE9000_SWRST_BIT, ADE9000_SWRST_BIT); > + if (ret) > + return ret; > + fsleep(90); > + return 0; This code structure is more complex than it needs to be. There is no sharing of the code that follows between the if / else so either drag that into the if (gpio_reset) branch and return early or flip the logic and do the if (!gpio_reset) first allowing the fsleep(10) gpio_set_value_cansleep() etc to be indented less. That is if (gpio_reset) { fsleep(10); gpiod_set_value_cansleep(gpio_reset, 0); fsleep(50000); if (!wait_for_completion_timeout(&st->reset_completion, msecs_to_jiffies(1000))) { dev_err(dev, "Reset timeout after 1s\n"); return -ETIMEDOUT; } return 0; } ret = regmap_update_bits(st->regmap, ADE9000_REG_CONFIG1, ADE9000_SWRST_BIT, ADE9000_SWRST_BIT); if (ret) return ret; fsleep(90); return 0; Or the other way around. > + } > + > + if (!wait_for_completion_timeout(&st->reset_completion, > + msecs_to_jiffies(1000))) { > + dev_err(dev, "Reset timeout after 1s\n"); > + return -ETIMEDOUT; > + } > + > + return 0; > +} ^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v5 5/6] docs: iio: add documentation for ade9000 driver 2025-08-22 16:01 [PATCH v5 0/6] iio: adc: add support for ADE9000 Energy Monitoring IC Antoniu Miclaus ` (3 preceding siblings ...) 2025-08-22 16:01 ` [PATCH v5 4/6] iio: adc: add ade9000 support Antoniu Miclaus @ 2025-08-22 16:01 ` Antoniu Miclaus 2025-08-25 14:14 ` Jonathan Cameron 2025-08-22 16:01 ` [PATCH v5 6/6] Documentation: ABI: iio: add sinc4+lp Antoniu Miclaus 5 siblings, 1 reply; 14+ messages in thread From: Antoniu Miclaus @ 2025-08-22 16:01 UTC (permalink / raw) To: jic23, robh, conor+dt, linux-iio, linux-kernel, devicetree Cc: Antoniu Miclaus Add documentation for ade9000 driver which describes the driver device files and shows how the user may use the ABI for various scenarios (configuration, measurement, etc.). Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> --- Documentation/iio/ade9000.rst | 286 ++++++++++++++++++++++++++++++++++ Documentation/iio/index.rst | 1 + 2 files changed, 287 insertions(+) create mode 100644 Documentation/iio/ade9000.rst diff --git a/Documentation/iio/ade9000.rst b/Documentation/iio/ade9000.rst new file mode 100644 index 000000000000..fcb4a36c0282 --- /dev/null +++ b/Documentation/iio/ade9000.rst @@ -0,0 +1,286 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=============== +ADE9000 driver +=============== + +This driver supports Analog Device's ADE9000 energy measurement IC on SPI bus. + +1. Supported devices +==================== + +* `ADE9000 <https://www.analog.com/media/en/technical-documentation/data-sheets/ADE9000.pdf>`_ + +The ADE9000 is a highly accurate, fully integrated, multiphase energy and power +quality monitoring device. Superior analog performance and a digital signal +processing (DSP) core enable accurate energy monitoring over a wide dynamic +range. An integrated high end reference ensures low drift over temperature +with a combined drift of less than ±25 ppm/°C maximum for the entire channel +including a programmable gain amplifier (PGA) and an analog-to-digital +converter (ADC). + +2. Device attributes +==================== + +Power and energy measurements are provided for voltage, current, active power, +reactive power, apparent power, and power factor across three phases. + +Each IIO device has a device folder under ``/sys/bus/iio/devices/iio:deviceX``, +where X is the IIO index of the device. Under these folders reside a set of +device files, depending on the characteristics and features of the hardware +device in question. These files are consistently generalized and documented in +the IIO ABI documentation. + +The following tables show the ADE9000 related device files, found in the +specific device folder path ``/sys/bus/iio/devices/iio:deviceX``. + ++---------------------------------------------------+----------------------------------------------------------+ +| Current measurement related device files | Description | ++---------------------------------------------------+----------------------------------------------------------+ +| in_current[0-2]_raw | Raw current measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_current[0-2]_scale | Scale for current channels. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_current[0-2]_calibscale | Calibration gain for current channels (AIGAIN reg). | ++---------------------------------------------------+----------------------------------------------------------+ +| in_current[0-2]_offset | Offset correction for current channels (IRMSOS reg). | ++---------------------------------------------------+----------------------------------------------------------+ +| in_altcurrent[0-2]_rms_raw | RMS current measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ + ++---------------------------------------------------+----------------------------------------------------------+ +| Voltage measurement related device files | Description | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage[0-2]_raw | Raw voltage measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage[0-2]_scale | Scale for voltage channels. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage[0-2]_calibscale | Calibration gain for voltage channels (AVGAIN reg). | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage[0-2]_offset | Offset correction for voltage channels (VRMSOS reg). | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage[0-2]_frequency | Measured line frequency for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_altvoltage[0-2]_rms_raw | RMS voltage measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ + ++---------------------------------------------------+----------------------------------------------------------+ +| Power measurement related device files | Description | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_raw | Raw active power measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_active_raw | Active power measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_reactive_raw | Reactive power measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_apparent_raw | Apparent power measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_powerfactor | Power factor for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_scale | Scale for power channels. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_calibscale | Calibration gain for power channels (APGAIN reg). | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_calibbias | Calibration offset for power channels (xWATTOS regs). | ++---------------------------------------------------+----------------------------------------------------------+ + ++---------------------------------------------------+----------------------------------------------------------+ +| Energy measurement related device files | Description | ++---------------------------------------------------+----------------------------------------------------------+ +| in_energy[0-2]_raw | Raw energy measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_energy[0-2]_scale | Scale for energy channels. | ++---------------------------------------------------+----------------------------------------------------------+ + ++------------------------------+------------------------------------------------------------------+ +| Shared device attributes | Description | ++------------------------------+------------------------------------------------------------------+ +| name | Name of the IIO device. | ++------------------------------+------------------------------------------------------------------+ +| frequency | System line frequency configuration (50Hz/60Hz). | ++------------------------------+------------------------------------------------------------------+ +| scale | Shared PGA gain setting (1x, 2x, 4x) affecting all channels. | ++------------------------------+------------------------------------------------------------------+ + +3. Calibration and scaling +=========================== + +The ADE9000 provides multiple levels of gain and offset correction: + +**PGA Gain (shared)** + The programmable gain amplifier affects the analog input stage for all channels. + Controlled via the shared ``scale`` attribute with values 1, 2, or 4. + +**Calibration Gain (per-channel)** + Fine-tuning calibration gains applied in the digital domain for each channel type. + Controlled via ``calibscale`` attributes (AIGAIN, AVGAIN, APGAIN registers). + +**Calibration Offset (per-channel)** + Hardware calibration offsets applied by the device for power measurements. + Controlled via ``calibbias`` attributes for power channels. + +**Correction Offsets (per-channel)** + RMS offset corrections for current and voltage measurements. + Controlled via ``offset`` attributes (IRMSOS, VRMSOS registers). + +4. Event attributes +=================== + +The ADE9000 provides various interrupts that are mapped to IIO events. +Event functionality is only available if the corresponding interrupts are +connected in the device tree. + ++---------------------------------------------------+----------------------------------------------------------+ +| IIO Event Attribute | ADE9000 Datasheet Equivalent | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage0_thresh_either_en | Zero crossing detection interrupt (ZXVA) | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage0_thresh_rising_en | Swell detection interrupt (SWELLA) | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage0_thresh_falling_en | Sag/dip detection interrupt (DIPA) | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage0_thresh_none_en | Zero crossing timeout interrupt (ZXTOVA) | ++---------------------------------------------------+----------------------------------------------------------+ +| events_mag_none_en | Energy ready interrupt (EGYRDY) | ++---------------------------------------------------+----------------------------------------------------------+ +| events_change_none_en | Sequence error interrupt (SEQERR) | ++---------------------------------------------------+----------------------------------------------------------+ +| events_thresh_none_value | Zero crossing timeout threshold (ZXTOUT register) | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage0_thresh_falling_value | Sag/dip threshold (DIP_LVL register) | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage0_thresh_rising_value | Swell threshold (SWELL_LVL register) | ++---------------------------------------------------+----------------------------------------------------------+ + +Event directions: +- ``rising``: Upper threshold crossing (swell detection) +- ``falling``: Lower threshold crossing (sag/dip detection) +- ``either``: Any threshold crossing (zero crossing detection) +- ``none``: Timeout or non-directional events + +**Note**: Event attributes are only available if the corresponding interrupts +(irq0, irq1, dready) are specified in the device tree. The driver works without +interrupts but with reduced functionality. + +5. Device buffers +================= + +This driver supports IIO buffers for waveform capture. Buffer functionality +requires the dready interrupt to be connected. + +The device supports capturing voltage and current waveforms for power quality +analysis. The waveform buffer can be configured to capture data from different +channel combinations. + +Supported channel combinations for buffered capture: +- Phase A: voltage and current (IA + VA) +- Phase B: voltage and current (IB + VB) +- Phase C: voltage and current (IC + VC) +- All phases: all voltage and current channels +- Individual channels: IA, VA, IB, VB, IC, VC + +Usage examples +-------------- + +Enable waveform capture for Phase A: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> echo 1 > scan_elements/in_current0_en + root:/sys/bus/iio/devices/iio:device0> echo 1 > scan_elements/in_voltage0_en + +Set buffer length and enable: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> echo 100 > buffer/length + root:/sys/bus/iio/devices/iio:device0> echo 1 > buffer/enable + +6. Clock output +=============== + +The ADE9000 can provide a clock output via the CLKOUT pin when using an external +crystal/clock source. This feature is enabled by specifying ``#clock-cells = <0>`` +in the device tree. The output clock will be registered as "clkout" and can be +referenced by other devices. + +7. Usage examples +================= + +Show device name: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> cat name + ade9000 + +Read voltage measurements: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> cat in_voltage0_raw + 12345 + root:/sys/bus/iio/devices/iio:device0> cat in_voltage0_scale + 0.000030517 + +- Phase A voltage = in_voltage0_raw * in_voltage0_scale = 0.3769 V + +Read power measurements: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> cat in_power0_active_raw + 5678 + root:/sys/bus/iio/devices/iio:device0> cat in_power0_scale + 0.000244140 + +- Phase A active power = in_power0_active_raw * in_power0_scale = 1.386 W + +Configure PGA gain (affects all channels): + +.. code-block:: bash + + # Set PGA gain to 2x + root:/sys/bus/iio/devices/iio:device0> echo 2 > scale + # Read current gain setting + root:/sys/bus/iio/devices/iio:device0> cat scale + 2 + +Configure line frequency: + +.. code-block:: bash + + # Set to 60Hz operation + root:/sys/bus/iio/devices/iio:device0> echo 60 > frequency + # Read current frequency setting + root:/sys/bus/iio/devices/iio:device0> cat frequency + 60 + +Configure calibration gains: + +.. code-block:: bash + + # Set current channel 0 calibration gain + root:/sys/bus/iio/devices/iio:device0> echo 0x800000 > in_current0_calibscale + # Set voltage channel 0 calibration gain + root:/sys/bus/iio/devices/iio:device0> echo 0x7FFFFF > in_voltage0_calibscale + +Configure voltage event thresholds (requires interrupts): + +.. code-block:: bash + + # Set sag detection threshold + root:/sys/bus/iio/devices/iio:device0> echo 180000 > events/in_voltage0_thresh_falling_value + # Enable sag detection + root:/sys/bus/iio/devices/iio:device0> echo 1 > events/in_voltage0_thresh_falling_en + + # Set swell detection threshold + root:/sys/bus/iio/devices/iio:device0> echo 260000 > events/in_voltage0_thresh_rising_value + # Enable swell detection + root:/sys/bus/iio/devices/iio:device0> echo 1 > events/in_voltage0_thresh_rising_en + +8. IIO Interfacing Tools +======================== + +See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO +interfacing tools. \ No newline at end of file diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst index c106402a91f7..792c815286f4 100644 --- a/Documentation/iio/index.rst +++ b/Documentation/iio/index.rst @@ -28,6 +28,7 @@ Industrial I/O Kernel Drivers ad7606 ad7625 ad7944 + ade9000 adis16475 adis16480 adis16550 -- 2.43.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v5 5/6] docs: iio: add documentation for ade9000 driver 2025-08-22 16:01 ` [PATCH v5 5/6] docs: iio: add documentation for ade9000 driver Antoniu Miclaus @ 2025-08-25 14:14 ` Jonathan Cameron 0 siblings, 0 replies; 14+ messages in thread From: Jonathan Cameron @ 2025-08-25 14:14 UTC (permalink / raw) To: Antoniu Miclaus; +Cc: robh, conor+dt, linux-iio, linux-kernel, devicetree On Fri, 22 Aug 2025 16:01:54 +0000 Antoniu Miclaus <antoniu.miclaus@analog.com> wrote: > Add documentation for ade9000 driver which describes the driver > device files and shows how the user may use the ABI for various > scenarios (configuration, measurement, etc.). > > Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> > --- > Documentation/iio/ade9000.rst | 286 ++++++++++++++++++++++++++++++++++ > Documentation/iio/index.rst | 1 + > 2 files changed, 287 insertions(+) > create mode 100644 Documentation/iio/ade9000.rst > > diff --git a/Documentation/iio/ade9000.rst b/Documentation/iio/ade9000.rst > new file mode 100644 > index 000000000000..fcb4a36c0282 > --- /dev/null > +++ b/Documentation/iio/ade9000.rst > @@ -0,0 +1,286 @@ > +.. SPDX-License-Identifier: GPL-2.0 > + > +=============== > +ADE9000 driver > +=============== > + > +This driver supports Analog Device's ADE9000 energy measurement IC on SPI bus. > + > +1. Supported devices > +==================== > + > +* `ADE9000 <https://www.analog.com/media/en/technical-documentation/data-sheets/ADE9000.pdf>`_ > + > +The ADE9000 is a highly accurate, fully integrated, multiphase energy and power > +quality monitoring device. Superior analog performance and a digital signal > +processing (DSP) core enable accurate energy monitoring over a wide dynamic > +range. An integrated high end reference ensures low drift over temperature > +with a combined drift of less than ±25 ppm/°C maximum for the entire channel > +including a programmable gain amplifier (PGA) and an analog-to-digital > +converter (ADC). > + > +2. Device attributes > +==================== > + > +Power and energy measurements are provided for voltage, current, active power, > +reactive power, apparent power, and power factor across three phases. > + > +Each IIO device has a device folder under ``/sys/bus/iio/devices/iio:deviceX``, > +where X is the IIO index of the device. Under these folders reside a set of > +device files, depending on the characteristics and features of the hardware > +device in question. These files are consistently generalized and documented in > +the IIO ABI documentation. > + > +The following tables show the ADE9000 related device files, found in the > +specific device folder path ``/sys/bus/iio/devices/iio:deviceX``. > + > ++---------------------------------------------------+----------------------------------------------------------+ > +| Current measurement related device files | Description | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_current[0-2]_raw | Raw current measurement for phases A, B, C. | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_current[0-2]_scale | Scale for current channels. | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_current[0-2]_calibscale | Calibration gain for current channels (AIGAIN reg). | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_current[0-2]_offset | Offset correction for current channels (IRMSOS reg). | So this is not an offset of the _raw reading above. I.e. userspace should do (_raw + _offset) * _scale I think? Even for the RMS calc there is a 2^15 multiplier involved. So think this one needs a rethink. Definitely a calibbias not an offset as it's not something for userspace to apply but rather a calibration tweak. Possibly something new is needed to indicate this only affects the RMS calculation. I'm open to suggestions on how to do that. > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_altcurrent[0-2]_rms_raw | RMS current measurement for phases A, B, C. | Add something kernel on the calc, particularly the influence of what you currently have as offset above. That is not obvious. > ++---------------------------------------------------+----------------------------------------------------------+ > + > ++---------------------------------------------------+----------------------------------------------------------+ > +| Voltage measurement related device files | Description | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_voltage[0-2]_raw | Raw voltage measurement for phases A, B, C. | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_voltage[0-2]_scale | Scale for voltage channels. | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_voltage[0-2]_calibscale | Calibration gain for voltage channels (AVGAIN reg). | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_voltage[0-2]_offset | Offset correction for voltage channels (VRMSOS reg). | As above. > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_voltage[0-2]_frequency | Measured line frequency for phases A, B, C. | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_altvoltage[0-2]_rms_raw | RMS voltage measurement for phases A, B, C. | > ++---------------------------------------------------+----------------------------------------------------------+ > + > ++---------------------------------------------------+----------------------------------------------------------+ > +| Power measurement related device files | Description | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_power[0-2]_raw | Raw active power measurement for phases A, B, C. | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_power[0-2]_active_raw | Active power measurement for phases A, B, C. | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_power[0-2]_reactive_raw | Reactive power measurement for phases A, B, C. | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_power[0-2]_apparent_raw | Apparent power measurement for phases A, B, C. | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_power[0-2]_powerfactor | Power factor for phases A, B, C. | Currently I'm fairly sure this is _active_powerfactor which is not where it should be. please check this doc again vs attributes on the running driver. > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_power[0-2]_scale | Scale for power channels. | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_power[0-2]_calibscale | Calibration gain for power channels (APGAIN reg). | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_power[0-2]_calibbias | Calibration offset for power channels (xWATTOS regs). | > ++---------------------------------------------------+----------------------------------------------------------+ > + > ++---------------------------------------------------+----------------------------------------------------------+ > +| Energy measurement related device files | Description | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_energy[0-2]_raw | Raw energy measurement for phases A, B, C. | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_energy[0-2]_scale | Scale for energy channels. | > ++---------------------------------------------------+----------------------------------------------------------+ > + > ++------------------------------+------------------------------------------------------------------+ > +| Shared device attributes | Description | > ++------------------------------+------------------------------------------------------------------+ > +| name | Name of the IIO device. | > ++------------------------------+------------------------------------------------------------------+ > +| frequency | System line frequency configuration (50Hz/60Hz). | > ++------------------------------+------------------------------------------------------------------+ > +| scale | Shared PGA gain setting (1x, 2x, 4x) affecting all channels. | > ++------------------------------+------------------------------------------------------------------+ Looks like something odd with formatting in the table here. > + > +3. Calibration and scaling > +=========================== > + > +The ADE9000 provides multiple levels of gain and offset correction: > + > +**PGA Gain (shared)** > + The programmable gain amplifier affects the analog input stage for all channels. > + Controlled via the shared ``scale`` attribute with values 1, 2, or 4. > + > +**Calibration Gain (per-channel)** > + Fine-tuning calibration gains applied in the digital domain for each channel type. > + Controlled via ``calibscale`` attributes (AIGAIN, AVGAIN, APGAIN registers). > + > +**Calibration Offset (per-channel)** > + Hardware calibration offsets applied by the device for power measurements. > + Controlled via ``calibbias`` attributes for power channels. > + > +**Correction Offsets (per-channel)** > + RMS offset corrections for current and voltage measurements. > + Controlled via ``offset`` attributes (IRMSOS, VRMSOS registers). As per the above - these only affect RMS not the direct measurements so need a rethink. > + > +4. Event attributes > +=================== > + > +The ADE9000 provides various interrupts that are mapped to IIO events. > +Event functionality is only available if the corresponding interrupts are > +connected in the device tree. > + > ++---------------------------------------------------+----------------------------------------------------------+ > +| IIO Event Attribute | ADE9000 Datasheet Equivalent | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_voltage0_thresh_either_en | Zero crossing detection interrupt (ZXVA) | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_voltage0_thresh_rising_en | Swell detection interrupt (SWELLA) | These seem to be on something to do with RMS not the main voltage channel. They aren't indicating the peak went out of range but rather the RMS value did. Given RMS is a channel modifier, maybe in_voltage0_rms_thresh_rising is enough? Does the RMS half cycle update change how we should represent this? I'm not sure and looking for inputs from others more familiar with energy meters. I had to dig to find what RMS 1/2 was. > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_voltage0_thresh_falling_en | Sag/dip detection interrupt (DIPA) | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_voltage0_thresh_none_en | Zero crossing timeout interrupt (ZXTOVA) | Not seeing how a timeout maps to a threshold without a direction. This is a failure to see an event for some time. The 'for sometime' could be done with a period control but I'm not sure on what. Maybe a new event direction in_voltage0_notthresh_either_en to say a threshold was not crossed? > ++---------------------------------------------------+----------------------------------------------------------+ > +| events_mag_none_en | Energy ready interrupt (EGYRDY) | Not an event in IIO terms. Could map this to a trigger as it's indicating a data update of small set of the channels. However we'd have to deal with buffering on our slowest channel. Do we need to support a dataready on something so slow? Just poll it enough? Also what is the events_ prefix here? > ++---------------------------------------------------+----------------------------------------------------------+ > +| events_change_none_en | Sequence error interrupt (SEQERR) | Why this mapping? If this is an error condition then maybe use the EV_TYPE_FAULT and add something alongside the openwire support we have already upstream. > ++---------------------------------------------------+----------------------------------------------------------+ > +| events_thresh_none_value | Zero crossing timeout threshold (ZXTOUT register) | I'd group these so the _value entries come right next the _en entries as then we can consider them together. This smells like period which we already have for events when they need to be true for a 'while'. Note the units though so you'll have to deal with mapping to those. > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_voltage0_thresh_falling_value | Sag/dip threshold (DIP_LVL register) | > ++---------------------------------------------------+----------------------------------------------------------+ > +| in_voltage0_thresh_rising_value | Swell threshold (SWELL_LVL register) | > ++---------------------------------------------------+----------------------------------------------------------+ > + > +Event directions: > +- ``rising``: Upper threshold crossing (swell detection) > +- ``falling``: Lower threshold crossing (sag/dip detection) > +- ``either``: Any threshold crossing (zero crossing detection) > +- ``none``: Timeout or non-directional events > + > +**Note**: Event attributes are only available if the corresponding interrupts > +(irq0, irq1, dready) are specified in the device tree. The driver works without > +interrupts but with reduced functionality. > + > +5. Device buffers > +================= > + > +This driver supports IIO buffers for waveform capture. Buffer functionality > +requires the dready interrupt to be connected. > + > +The device supports capturing voltage and current waveforms for power quality > +analysis. The waveform buffer can be configured to capture data from different > +channel combinations. > + > +Supported channel combinations for buffered capture: > +- Phase A: voltage and current (IA + VA) > +- Phase B: voltage and current (IB + VB) > +- Phase C: voltage and current (IC + VC) > +- All phases: all voltage and current channels > +- Individual channels: IA, VA, IB, VB, IC, VC > + > +Usage examples > +-------------- > + > +Enable waveform capture for Phase A: > + > +.. code-block:: bash > + > + root:/sys/bus/iio/devices/iio:device0> echo 1 > scan_elements/in_current0_en > + root:/sys/bus/iio/devices/iio:device0> echo 1 > scan_elements/in_voltage0_en > + > +Set buffer length and enable: > + > +.. code-block:: bash > + > + root:/sys/bus/iio/devices/iio:device0> echo 100 > buffer/length > + root:/sys/bus/iio/devices/iio:device0> echo 1 > buffer/enable > + > +6. Clock output > +=============== > + > +The ADE9000 can provide a clock output via the CLKOUT pin when using an external > +crystal/clock source. This feature is enabled by specifying ``#clock-cells = <0>`` > +in the device tree. The output clock will be registered as "clkout" and can be > +referenced by other devices. > + > +7. Usage examples > +================= > + > +Show device name: > + > +.. code-block:: bash > + > + root:/sys/bus/iio/devices/iio:device0> cat name > + ade9000 > + > +Read voltage measurements: > + > +.. code-block:: bash > + > + root:/sys/bus/iio/devices/iio:device0> cat in_voltage0_raw > + 12345 > + root:/sys/bus/iio/devices/iio:device0> cat in_voltage0_scale > + 0.000030517 > + > +- Phase A voltage = in_voltage0_raw * in_voltage0_scale = 0.3769 V > + > +Read power measurements: > + > +.. code-block:: bash > + > + root:/sys/bus/iio/devices/iio:device0> cat in_power0_active_raw > + 5678 > + root:/sys/bus/iio/devices/iio:device0> cat in_power0_scale > + 0.000244140 > + > +- Phase A active power = in_power0_active_raw * in_power0_scale = 1.386 W > + > +Configure PGA gain (affects all channels): > + > +.. code-block:: bash > + > + # Set PGA gain to 2x > + root:/sys/bus/iio/devices/iio:device0> echo 2 > scale > + # Read current gain setting > + root:/sys/bus/iio/devices/iio:device0> cat scale > + 2 > + > +Configure line frequency: > + > +.. code-block:: bash > + > + # Set to 60Hz operation > + root:/sys/bus/iio/devices/iio:device0> echo 60 > frequency > + # Read current frequency setting > + root:/sys/bus/iio/devices/iio:device0> cat frequency > + 60 > + > +Configure calibration gains: > + > +.. code-block:: bash > + > + # Set current channel 0 calibration gain > + root:/sys/bus/iio/devices/iio:device0> echo 0x800000 > in_current0_calibscale > + # Set voltage channel 0 calibration gain > + root:/sys/bus/iio/devices/iio:device0> echo 0x7FFFFF > in_voltage0_calibscale > + > +Configure voltage event thresholds (requires interrupts): > + > +.. code-block:: bash > + > + # Set sag detection threshold > + root:/sys/bus/iio/devices/iio:device0> echo 180000 > events/in_voltage0_thresh_falling_value > + # Enable sag detection > + root:/sys/bus/iio/devices/iio:device0> echo 1 > events/in_voltage0_thresh_falling_en > + > + # Set swell detection threshold > + root:/sys/bus/iio/devices/iio:device0> echo 260000 > events/in_voltage0_thresh_rising_value > + # Enable swell detection > + root:/sys/bus/iio/devices/iio:device0> echo 1 > events/in_voltage0_thresh_rising_en > + > +8. IIO Interfacing Tools > +======================== > + > +See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO > +interfacing tools. > \ No newline at end of file fix that. > diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst > index c106402a91f7..792c815286f4 100644 > --- a/Documentation/iio/index.rst > +++ b/Documentation/iio/index.rst > @@ -28,6 +28,7 @@ Industrial I/O Kernel Drivers > ad7606 > ad7625 > ad7944 > + ade9000 > adis16475 > adis16480 > adis16550 ^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v5 6/6] Documentation: ABI: iio: add sinc4+lp 2025-08-22 16:01 [PATCH v5 0/6] iio: adc: add support for ADE9000 Energy Monitoring IC Antoniu Miclaus ` (4 preceding siblings ...) 2025-08-22 16:01 ` [PATCH v5 5/6] docs: iio: add documentation for ade9000 driver Antoniu Miclaus @ 2025-08-22 16:01 ` Antoniu Miclaus 5 siblings, 0 replies; 14+ messages in thread From: Antoniu Miclaus @ 2025-08-22 16:01 UTC (permalink / raw) To: jic23, robh, conor+dt, linux-iio, linux-kernel, devicetree Cc: Antoniu Miclaus Add new filter type to the sysfs-bus-iio ABI documentation: - "sinc4+lp" for Sinc4 + Low Pass Filter Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com> --- no changes in v5. Documentation/ABI/testing/sysfs-bus-iio | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 78da68826307..cb300135b4c4 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -2319,6 +2319,7 @@ Description: time. * "sinc4" - Sinc 4. Excellent noise performance. Long 1st conversion time. + * "sinc4+lp" - Sinc4 + Low Pass Filter. * "sinc4+sinc1" - Sinc4 + averaging by 8. Low 1st conversion time. * "sinc5" - The digital sinc5 filter. Excellent noise -- 2.43.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
end of thread, other threads:[~2025-08-26 22:46 UTC | newest] Thread overview: 14+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-08-22 16:01 [PATCH v5 0/6] iio: adc: add support for ADE9000 Energy Monitoring IC Antoniu Miclaus 2025-08-22 16:01 ` [PATCH v5 1/6] iio: add IIO_ALTCURRENT channel type Antoniu Miclaus 2025-08-22 16:01 ` [PATCH v5 2/6] iio: add power and energy measurement modifiers Antoniu Miclaus 2025-08-22 16:01 ` [PATCH v5 3/6] dt-bindings: iio: adc: add ade9000 Antoniu Miclaus 2025-08-25 13:10 ` Jonathan Cameron 2025-08-26 22:46 ` Rob Herring 2025-08-22 16:01 ` [PATCH v5 4/6] iio: adc: add ade9000 support Antoniu Miclaus 2025-08-25 1:19 ` kernel test robot 2025-08-25 12:27 ` Marcelo Schmitt 2025-08-25 13:00 ` Dan Carpenter 2025-08-25 13:48 ` Jonathan Cameron 2025-08-22 16:01 ` [PATCH v5 5/6] docs: iio: add documentation for ade9000 driver Antoniu Miclaus 2025-08-25 14:14 ` Jonathan Cameron 2025-08-22 16:01 ` [PATCH v5 6/6] Documentation: ABI: iio: add sinc4+lp Antoniu Miclaus
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).