From 2ac66a26c09aca39d50c938a421775f2c5de2695 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Fri, 21 Mar 2025 13:41:37 +0100 Subject: [PATCH 1/9] Add support for AD4052 device family The AD4052/AD4058/AD4050/AD4056 are versatile, 16-bit/12-bit, successive approximation register (SAR) analog-to-digital converter (ADC). The series starts with marking iio_dev as const in iio_buffer_enabled, to not discard the qualifier when calling from get_current_can_type. This is required since the size of storage bytes varies if the offload buffer is used or not. The scan_type also depends if the oversampling feature is enabled, since the 16-bit device increases the SPI word size from 16-bit to 24-bit. Also due to this, the spi message optimization is balanced on the buffer ops, instead of once per probe. SPI messages related to exiting the ADC mode, and reading raw values are never optimized. The device has autonomous monitoring capabilities, that are exposed as IIO events. Since register access requires leaving the monitoring state and returning, device access is blocked until the IIO event is disabled. An auxiliary method ad4052_iio_device_claim_direct manages the IIO claim direct as well as the required wait_event boolean. The device has an internal sampling rate for the autonomous modes, exposed as the sample_rate attribute. The device contains two required outputs: * gp0: Threshold event interrupt on the rising edge. * gp1: ADC conversion ready signal on the falling edge. The user should either invert the signal or set the IRQ as falling edge. And one optional input: * cnv: Triggers a conversion, can be replaced by shortening the CNV and SPI CS trace. The devices utilizes PM to enter the low power mode. The driver can be used with SPI controllers with and without offload support. A FPGA design is available: https://analogdevicesinc.github.io/hdl/projects/ad4052_ardz/ The devices datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ad4050-ad4056.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/ad4052-ad4058.pdf The unique monitoring capabilities and multiple GPIOs where the decision factor to have a standalone driver for this device family. Non-implemented features: * Status word: First byte of the SPI transfer aligned to the register address. * Averaging mode: Similar to burst averaging mode used in the oversampling, but requiring a sequence of CNV triggers for each conversion. * Monitor mode: Similar to trigger mode used in the monitoring mode, but doesn't exit to configuration mode on event, being awkward to expose to user space. An auxiliary method ad4052_iio_device_claim_direct manages the IIO claim direct as well as the required wait_event boolean. The device has an internal sampling rate for the autonomous modes, exposed as the sample_rate attribute. The device contains two required outputs: * gp0: Threshold event interrupt on the rising edge. * gp1: ADC conversion ready signal on the falling edge. The user should either invert the signal or set the IRQ as falling edge. And one optional input: * cnv: Triggers a conversion, can be replaced by shortening the CNV and SPI CS trace. The devices utilizes PM to enter the low power mode. The driver can be used with SPI controllers with and without offload support. A FPGA design is available: https://analogdevicesinc.github.io/hdl/projects/ad4052_ardz/ The devices datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ad4050-ad4056.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/ad4052-ad4058.pdf The unique monitoring capabilities and multiple GPIOs where the decision factor to have a standalone driver for this device family. Non-implemented features: * Status word: First byte of the SPI transfer aligned to the register address. * Averaging mode: Similar to burst averaging mode used in the oversampling, but requiring a sequence of CNV triggers for each conversion. * Monitor mode: Similar to trigger mode used in the monitoring mode, but doesn't exit to configuration mode on event, being awkward to expose to user space. Signed-off-by: Jorge Marques From b17bb2bf41152dfd30912ff3833ca695d4169b38 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Fri, 21 Mar 2025 13:38:37 +0100 Subject: [PATCH 2/9] Documentation: ABI: add events sampling frequency in sysfs-bus-iio Some devices have an internal clock used by the events to space the conversions. Examples of devices with this internal clock are: max1363 and ad4052. The max1363 introduced the option in 168c9d95a94077a42e5bf5a4a660b45ccee4fc63 (3.10). Signed-off-by: Jorge Marques --- 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 6b78b20b57bef3..24b328d321ed33 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -94,6 +94,7 @@ Description: What: /sys/bus/iio/devices/iio:deviceX/sampling_frequency What: /sys/bus/iio/devices/iio:deviceX/in_intensity_sampling_frequency What: /sys/bus/iio/devices/iio:deviceX/buffer/sampling_frequency +What: /sys/bus/iio/devices/iio:deviceX/events/sampling_frequency What: /sys/bus/iio/devices/triggerX/sampling_frequency KernelVersion: 2.6.35 Contact: linux-iio@vger.kernel.org From 989d385c9afa4d9d25c9e13bc1eb4c067b9e6be8 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Fri, 21 Mar 2025 13:39:11 +0100 Subject: [PATCH 3/9] Documentation: ABI: add oversampling frequency in sysfs-bus-iio Some devices have an internal clock used to space the conversion trigger for the oversampling filter. Examples of devices with this internal clock are: ad4052 and ad7606c. Signed-off-by: Jorge Marques --- Documentation/ABI/testing/sysfs-bus-iio | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 24b328d321ed33..92770078443991 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -139,6 +139,21 @@ Contact: linux-iio@vger.kernel.org Description: Hardware dependent values supported by the oversampling filter. +What: /sys/bus/iio/devices/iio:deviceX/oversampling_frequency +KernelVersion: 6.15 +Contact: linux-iio@vger.kernel.org +Description: + Some devices have internal clocks for the ADC oversampling. + Sets the resulting sampling frequency to trigger a conversion + used by the oversampling filter. + +What: /sys/bus/iio/devices/iio:deviceX/oversampling_frequency_available +KernelVersion: 6.15 +Contact: linux-iio@vger.kernel.org +Description: + Hardware dependent values supported by the oversampling + frequency. + What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_raw What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_supply_raw What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_i_raw From 1265b93f8ea1e3869408fb978b416f6830920127 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Tue, 25 Feb 2025 17:23:30 +0100 Subject: [PATCH 4/9] iio: code: mark iio_dev as const in iio_buffer_enabled The iio_dev struct is never modified inside the method, mark it as const. This allows to be called from get_current_scan_type, and is useful when the scan_type depends on the buffer state. Signed-off-by: Jorge Marques --- drivers/iio/industrialio-core.c | 2 +- include/linux/iio/iio.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 8667ea1baa292a..e09692acb7ab28 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -207,7 +207,7 @@ EXPORT_SYMBOL_GPL(iio_device_id); * * Returns: True, if the buffer is enabled. */ -bool iio_buffer_enabled(struct iio_dev *indio_dev) +bool iio_buffer_enabled(const struct iio_dev *indio_dev) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index bb952a190b77d8..d3537e136fe27e 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -633,7 +633,7 @@ struct iio_dev { int iio_device_id(struct iio_dev *indio_dev); int iio_device_get_current_mode(struct iio_dev *indio_dev); -bool iio_buffer_enabled(struct iio_dev *indio_dev); +bool iio_buffer_enabled(const struct iio_dev *indio_dev); const struct iio_chan_spec *iio_find_channel_from_si(struct iio_dev *indio_dev, int si); From a06505c8e84f899d70d90ba5efce3e6ca67e45d2 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Fri, 1 Nov 2024 14:03:33 -0300 Subject: [PATCH 5/9] dt-bindings: iio: adc: Add adi,ad4052 Add dt-bindings for AD4052 family, devices AD4050/AD4052/AD4056/AD4058, low-power with monitor capabilities SAR ADCs. Each variant of the family differs in speed and resolution, resulting in different scan types and spi word sizes, that are matched by the compatible with the chip_info. The device conatins one input (cnv) and two outputs (gp0, gp1). Signed-off-by: Jorge Marques --- .../bindings/iio/adc/adi,ad4052.yaml | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4052.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4052.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4052.yaml new file mode 100644 index 00000000000000..a0510d485f130c --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4052.yaml @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright 2025 Analog Devices Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,ad4052.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD4052 ADC family device driver + +maintainers: + - Jorge Marques + +description: | + Analog Devices AD4052 Single Channel Precision SAR ADC family + + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4050-ad4056.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4052-ad4058.pdf + +properties: + compatible: + enum: + - adi,ad4050 + - adi,ad4052 + - adi,ad4056 + - adi,ad4058 + + reg: + maxItems: 1 + + interrupts: + items: + - description: Signal coming from the GP0 pin (threshold). + - description: Signal coming from the GP1 pin (data ready). + + interrupt-names: + items: + - const: gp0 + - const: gp1 + + gpio-controller: true + + "#gpio-cells": + const: 2 + description: | + The first cell is the GPn number: 0 to 1. + The second cell takes standard GPIO flags. + + cnv-gpios: + description: The Convert Input (CNV). If omitted, CNV is tied to SPI CS. + maxItems: 1 + + spi-max-frequency: + maximum: 62500000 + + vdd-supply: + description: Analog power supply. + + vio-supply: + description: Digital interface logic power supply. + + vref-supply: + description: Reference voltage to set the ADC full-scale range. + +required: + - compatible + - reg + - vdd-supply + - vio-supply + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "adi,ad4052"; + reg = <0>; + vdd-supply = <&adc_vdd>; + vio-supply = <&adc_vio>; + spi-max-frequency = <25000000>; + + interrupt-parent = <&gpio>; + interrupts = <0 0 IRQ_TYPE_EDGE_RISING>, + <0 1 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "gp0", "gp1"; + cnv-gpios = <&gpio 2 GPIO_ACTIVE_HIGH>; + }; + }; +... From 12165ac4a9a3678e61ce7300aa05d6145aa3591b Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Thu, 31 Oct 2024 19:57:22 -0300 Subject: [PATCH 6/9] arch: arm: boot: add AD4052 dts for Coraz7s The AD4052 CNV pin is driven by a GPIO for single shot readings and by a PWM for buffer readings. The functional-mode entry allows to set Sample Mode (0) or Burst Averaging Mode (1). During runtime, it is possible to enter Trigger Mode through IIO Events. Signed-off-by: Jorge Marques --- .../boot/dts/xilinx/zynq-coraz7s-ad4052.dts | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 arch/arm/boot/dts/xilinx/zynq-coraz7s-ad4052.dts diff --git a/arch/arm/boot/dts/xilinx/zynq-coraz7s-ad4052.dts b/arch/arm/boot/dts/xilinx/zynq-coraz7s-ad4052.dts new file mode 100644 index 00000000000000..96adab56b7e985 --- /dev/null +++ b/arch/arm/boot/dts/xilinx/zynq-coraz7s-ad4052.dts @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD4052 + * + * hdl_project: + * + * Copyright (C) 2025 Analog Devices Inc. + */ + +/dts-v1/; +#include "zynq-coraz7s.dtsi" +#include +#include + +/ { + adc_vdd: regulator-vref-adc { + compatible = "regulator-fixed"; + regulator-name = "adc-vdd"; + regulator-min-microvolt = <3600000>; + regulator-max-microvolt = <3600000>; + regulator-always-on; + }; + + adc_vio: regulator-vio-adc { + compatible = "regulator-fixed"; + regulator-name = "adc_vio"; + regulator-min-microvolt = <3600000>; + regulator-max-microvolt = <3600000>; + regulator-always-on; + }; +}; + +&fpga_axi { + axi_iic: i2c@41600000 { + compatible = "xlnx,axi-iic-1.01.b", "xlnx,xps-iic-2.00.a"; + reg = <0x41600000 0x10000>; + interrupt-parent = <&intc>; + interrupts = <0 55 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 15>; + clock-names = "s_axi_aclk"; + + #size-cells = <0>; + #address-cells = <1>; + + eeprom1: eeprom@52 { + compatible = "atmel,24c32"; + reg = <0x52>; + }; + + }; + + rx_dma: rx-dmac@44a30000 { + compatible = "adi,axi-dmac-1.00.a"; + reg = <0x44a30000 0x1000>; + #dma-cells = <1>; + interrupt-parent = <&intc>; + interrupts = <0 57 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 15>; + }; + + spi_clk: axi-clkgen@44a70000 { + compatible = "adi,axi-clkgen-2.00.a"; + reg = <0x44a70000 0x10000>; + #clock-cells = <0>; + clocks = <&clkc 15>, <&clkc 15>; + clock-names = "s_axi_aclk", "clkin1"; + clock-output-names = "spi_clk"; + }; + + adc_trigger: pwm@44b00000 { + compatible = "adi,axi-pwmgen-2.00.a"; + reg = <0x44b00000 0x1000>; + #pwm-cells = <2>; + clocks = <&spi_clk>; + }; + + axi_spi_engine: spi@44a00000 { + compatible = "adi,axi-spi-engine-1.00.a"; + reg = <0x44a00000 0x1FF>; + interrupt-parent = <&intc>; + interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clkc 15>, <&spi_clk>; + clock-names = "s_axi_aclk", "spi_clk"; + + dmas = <&rx_dma 0>; + dma-names = "offload0-rx"; + trigger-sources = <&ad4052>; + + #address-cells = <0x1>; + #size-cells = <0x0>; + + ad4052: ad4052@0 { + compatible = "adi,ad4052"; + reg = <0>; + vdd-supply = <&adc_vdd>; + vio-supply = <&adc_vio>; + spi-max-frequency = <31250000>; + + #trigger-source-cells = <0>; + pwms = <&adc_trigger 0 10000 0>; + + interrupt-parent = <&gpio0>; + interrupts = <86 IRQ_TYPE_EDGE_RISING>, + <87 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "gp0", "gp1"; + cnv-gpios = <&gpio0 88 GPIO_ACTIVE_HIGH>; + }; + }; +}; From d77c3e46a6170bde75d08ee3b0f353a36a9c559e Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Fri, 21 Feb 2025 20:46:20 +0100 Subject: [PATCH 7/9] docs: iio: new docs for ad4052 driver This adds a new page to document how to use the ad4052 ADC driver. Signed-off-by: Jorge Marques --- Documentation/iio/ad4052.rst | 95 ++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 Documentation/iio/ad4052.rst diff --git a/Documentation/iio/ad4052.rst b/Documentation/iio/ad4052.rst new file mode 100644 index 00000000000000..410aaa437ed5fe --- /dev/null +++ b/Documentation/iio/ad4052.rst @@ -0,0 +1,95 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +============= +AD4052 driver +============= + +ADC driver for Analog Devices Inc. AD4052 and similar devices. +The module name is ``ad4052``. + +Supported devices +================= + +The following chips are supported by this driver: + +* `AD4050 `_ +* `AD4052 `_ +* `AD4056 `_ +* `AD4058 `_ + +Wiring modes +============ + +The ADC uses SPI 4-wire mode, and contain two programmable GPIOs and +a CNV pin. + +The CNV pin is exposed as the ``cnv-gpios`` and triggers a ADC conversion. +GP1 is ADC conversion ready signal and GP0 Threshold event interrupt, both +exposed as interrupts. + +Omit ``cnv-gpios`` and tie CNV and CS together to use the rising edge +of the CS as the CNV signal. + +Device attributes +================= + +The ADC contain only one channels, and the following attributes: + +.. list-table:: Driver attributes + :header-rows: 1 + + * - Attribute + - Description + * - ``in_voltage0_raw`` + - Raw ADC voltage value + * - ``in_voltage0_oversampling_ratio`` + - Enable the device's burst averaging mode to over sample using + the internal sample rate. + * - ``in_voltage0_oversampling_ratio_available`` + - List of available oversampling values. Value 0 disable the burst + averaging mode. + * - ``conversion_frequency`` + - Device internal sample rate used in the burst averaging mode. + * - ``conversion_frequency_available`` + - List of available sample rates. + +Threshold events +================ + +The ADC supports a monitoring mode to raise threshold events. +The driver supports a single interrupt for both rising and falling +readings. + +The feature is enabled/disabled by setting ``thresh_either_en``. +During monitor mode, the device continuously operates in autonomous mode until +put back in configuration mode, due to this, the device returns busy until the +feature is disabled. + +Low-power mode +============== + +The device enters low-power mode on idle to save power. +Enabling an event puts the device out of the low-power since the ADC +autonomously samples to assert the event condition. + +SPI offload support +=================== + +To be able to achieve the maximum sample rate, the driver can be used with the +`AXI SPI Engine`_ to provide SPI offload support. + +.. _AXI SPI Engine: http://analogdevicesinc.github.io/hdl/projects/ad4052_ardz/index.html + +When SPI offload is being used, additional attributes are present: + +.. list-table:: Additional attributes + :header-rows: 1 + + * - Attribute + - Description + * - ``in_voltage0_sampling_frequency`` + - Set the sampling frequency. + * - ``in_voltage0_sampling_frequency_available`` + - Get the sampling frequency range. + +The scan type is different when the buffer with offload support is enabled. From aba61545feb1cd90ecd29a9c0fd8efb38300d930 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Mon, 10 Mar 2025 09:43:21 +0100 Subject: [PATCH 8/9] iio: adc: add support for ad4052 The AD4052/AD4058/AD4050/AD4056 are versatile, 16-bit/12-bit, successive approximation register (SAR) analog-to-digital converter (ADC) that enables low-power, high-density data acquisition solutions without sacrificing precision. This ADC offers a unique balance of performance and power efficiency, plus innovative features for seamlessly switching between high-resolution and low-power modes tailored to the immediate needs of the system. The AD4052/AD4058/AD4050/AD4056 are ideal for battery-powered, compact data acquisition and edge sensing applications. Signed-off-by: Jorge Marques --- drivers/iio/adc/Kconfig | 14 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ad4052.c | 1421 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1436 insertions(+) create mode 100644 drivers/iio/adc/ad4052.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 45d83809304bdf..6a860e721ae340 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -48,6 +48,20 @@ config AD4000 To compile this driver as a module, choose M here: the module will be called ad4000. +config AD4052 + tristate "Analog Devices AD4052 Driver" + depends on SPI + select SPI_OFFLOAD + select IIO_BUFFER + select IIO_BUFFER_DMAENGINE + select REGMAP_SPI + help + Say yes here to build support for Analog Devices AD4052 SPI analog + to digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad4052. + config AD4134 tristate "Analog Device AD4134 ADC Driver" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 929d5b9799bb5e..9fa3d83d35d1a3 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_AD4130) += ad4130.o obj-$(CONFIG_AD_PULSAR) += ad_pulsar.o obj-$(CONFIG_AD400X) += ad400x.o obj-$(CONFIG_AD4000) += ad4000.o +obj-$(CONFIG_AD4052) += ad4052.o obj-$(CONFIG_AD4134) += ad4134.o obj-$(CONFIG_AD4630) += ad4630.o obj-$(CONFIG_AD6676) += ad6676.o diff --git a/drivers/iio/adc/ad4052.c b/drivers/iio/adc/ad4052.c new file mode 100644 index 00000000000000..2eee7be9ccc8c3 --- /dev/null +++ b/drivers/iio/adc/ad4052.c @@ -0,0 +1,1421 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD4052 SPI ADC driver + * + * Copyright 2025 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD4052_REG_INTERFACE_CONFIG_A 0x00 +#define AD4052_REG_DEVICE_CONFIG 0x02 +#define AD4052_REG_PROD_ID_1 0x05 +#define AD4052_REG_DEVICE_GRADE 0x06 +#define AD4052_REG_SCRATCH_PAD 0x0A +#define AD4052_REG_VENDOR_H 0x0D +#define AD4052_REG_STREAM_MODE 0x0E +#define AD4052_REG_INTERFACE_STATUS 0x11 +#define AD4052_REG_MODE_SET 0x20 +#define AD4052_REG_ADC_MODES 0x21 +#define AD4052_REG_AVG_CONFIG 0x23 +#define AD4052_REG_GP_CONF 0x24 +#define AD4052_REG_INTR_CONF 0x25 +#define AD4052_REG_TIMER_CONFIG 0x27 +#define AD4052_REG_MAX_LIMIT 0x29 +#define AD4052_REG_MIN_LIMIT 0x2B +#define AD4052_REG_MAX_HYST 0x2C +#define AD4052_REG_MIN_HYST 0x2D +#define AD4052_REG_MON_VAL 0x2F +#define AD4052_REG_FUSE_CRC 0x40 +#define AD4052_REG_DEVICE_STATUS 0x41 +#define AD4052_REG_MIN_SAMPLE 0x45 +#define AD4052_MAX_REG 0x45 +#define AD4052_ADC_MODES_MODE_MSK GENMASK(1, 0) +#define AD4052_GP_CONF_MODE_MSK_0 GENMASK(2, 0) +#define AD4052_GP_CONF_MODE_MSK_1 GENMASK(6, 4) +#define AD4052_INTR_CONF_EN_MSK_0 GENMASK(1, 0) +#define AD4052_INTR_CONF_EN_MSK_1 GENMASK(5, 4) +#define AD4052_MODE_SET_ENTER_ADC BIT(0) +#define AD4052_ADC_MODES_DATA_FORMAT BIT(7) +#define AD4052_DEVICE_CONFIG_POWER_MODE_MSK GENMASK(1, 0) +#define AD4052_DEVICE_CONFIG_LOW_POWER_MODE 3 +#define AD4052_DEVICE_STATUS_DEVICE_RESET BIT(6) +#define AD4052_TIMER_CONFIG_FS_MASK GENMASK(7, 4) +#define AD4052_TIMER_CONFIG_300KSPS 0x2 + +#define AD4052_SPI_VENDOR 0x0456 + +#define AD4050_MAX_AVG 0x7 +#define AD4052_MAX_AVG 0xB +#define AD4052_MAX_RATE(x) ((x) == AD4052_500KSPS ? 500000 : 2000000) +#define AD4052_FS_OFFSET(g) ((g) == AD4052_500KSPS ? 2 : 0) +#define AD4052_FS(g) ((&ad4052_conversion_freqs[AD4052_FS_OFFSET(g)])) +#define AD4052_FS_LEN(g) (ARRAY_SIZE(ad4052_conversion_freqs) - (AD4052_FS_OFFSET(g))) +#define AD4052_T_CNVH_NS 10 + +enum ad4052_grade { + AD4052_2MSPS, + AD4052_500KSPS, +}; + +enum ad4052_operation_mode { + AD4052_SAMPLE_MODE = 0, + AD4052_BURST_AVERAGING_MODE = 1, + AD4052_MONITOR_MODE = 3, +}; + +enum ad4052_gp_mode { + AD4052_GP_DISABLED, + AD4052_GP_INTR, + AD4052_GP_DRDY, +}; + +enum ad4052_interrupt_en { + AD4052_INTR_EN_NEITHER, + AD4052_INTR_EN_MIN, + AD4052_INTR_EN_MAX, + AD4052_INTR_EN_EITHER, +}; + +struct ad4052_chip_info { + const struct iio_chan_spec channels[1]; + const struct iio_chan_spec offload_channels[1]; + const char *name; + u16 prod_id; + u8 max_avg; + u8 grade; +}; + +enum { + AD4052_SCAN_TYPE_SAMPLE, + AD4052_SCAN_TYPE_BURST_AVG, + AD4052_SCAN_TYPE_OFFLOAD_SAMPLE, + AD4052_SCAN_TYPE_OFFLOAD_BURST_AVG, +}; + +static const struct iio_scan_type ad4052_scan_type_12_s[] = { + [AD4052_SCAN_TYPE_SAMPLE] = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + [AD4052_SCAN_TYPE_BURST_AVG] = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + [AD4052_SCAN_TYPE_OFFLOAD_SAMPLE] = { + .sign = 's', + .realbits = 16, + .storagebits = 32, + .endianness = IIO_CPU, + }, + [AD4052_SCAN_TYPE_OFFLOAD_BURST_AVG] = { + .sign = 's', + .realbits = 16, + .storagebits = 32, + .endianness = IIO_CPU, + }, +}; + +static const struct iio_scan_type ad4052_scan_type_16_s[] = { + [AD4052_SCAN_TYPE_SAMPLE] = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + [AD4052_SCAN_TYPE_BURST_AVG] = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + [AD4052_SCAN_TYPE_OFFLOAD_SAMPLE] = { + .sign = 's', + .realbits = 16, + .storagebits = 32, + .endianness = IIO_CPU, + }, + [AD4052_SCAN_TYPE_OFFLOAD_BURST_AVG] = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, +}; + +struct ad4052_state { + const struct ad4052_bus_ops *ops; + const struct ad4052_chip_info *chip; + enum ad4052_operation_mode mode; + struct spi_offload *offload; + struct spi_offload_trigger *offload_trigger; + struct spi_device *spi; + struct spi_transfer offload_xfer; + struct spi_message offload_msg; + struct pwm_device *cnv_pwm; + struct spi_transfer xfer; + struct spi_message msg; + struct gpio_desc *cnv_gp; + struct completion completion; + struct regmap *regmap; + u16 oversampling_frequency; + u16 events_frequency; + bool wait_event; + int gp1_irq; + u8 data_format; + union { + __be16 d16; + __be32 d32; + } __aligned(IIO_DMA_MINALIGN); + u8 buf_reset_pattern[18]; +}; + +static const struct regmap_range ad4052_regmap_rd_ranges[] = { + regmap_reg_range(AD4052_REG_INTERFACE_CONFIG_A, AD4052_REG_DEVICE_GRADE), + regmap_reg_range(AD4052_REG_SCRATCH_PAD, AD4052_REG_INTERFACE_STATUS), + regmap_reg_range(AD4052_REG_MODE_SET, AD4052_REG_MON_VAL), + regmap_reg_range(AD4052_REG_FUSE_CRC, AD4052_REG_MIN_SAMPLE), +}; + +static const struct regmap_access_table ad4052_regmap_rd_table = { + .yes_ranges = ad4052_regmap_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(ad4052_regmap_rd_ranges), +}; + +static const struct regmap_range ad4052_regmap_wr_ranges[] = { + regmap_reg_range(AD4052_REG_INTERFACE_CONFIG_A, AD4052_REG_DEVICE_CONFIG), + regmap_reg_range(AD4052_REG_SCRATCH_PAD, AD4052_REG_SCRATCH_PAD), + regmap_reg_range(AD4052_REG_STREAM_MODE, AD4052_REG_INTERFACE_STATUS), + regmap_reg_range(AD4052_REG_MODE_SET, AD4052_REG_MON_VAL), + regmap_reg_range(AD4052_REG_FUSE_CRC, AD4052_REG_DEVICE_STATUS), +}; + +static const struct regmap_access_table ad4052_regmap_wr_table = { + .yes_ranges = ad4052_regmap_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(ad4052_regmap_wr_ranges), +}; + +static const struct iio_event_spec ad4052_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_shared_by_all = BIT(IIO_EV_INFO_ENABLE) + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS) + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS) + } +}; + +static const char *const ad4052_conversion_freqs[] = { + "2000000", "1000000", "300000", "100000", "33300", + "10000", "3000", "500", "333", "250", "200", + "166", "140", "124", "111", +}; + +static int ad4052_conversion_frequency_set(struct ad4052_state *st, u8 val) +{ + int ret; + + val += AD4052_FS_OFFSET(st->chip->grade); + return regmap_write(st->regmap, AD4052_REG_TIMER_CONFIG, + FIELD_PREP(AD4052_TIMER_CONFIG_FS_MASK, val)); + + return ret; +} + +static int ad4052_oversampling_frequency_get(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad4052_state *st = iio_priv(indio_dev); + + return st->oversampling_frequency - AD4052_FS_OFFSET(st->chip->grade); +} + +static int ad4052_oversampling_frequency_set(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int val) +{ + struct ad4052_state *st = iio_priv(indio_dev); + + if (iio_device_claim_direct_mode(indio_dev)) + return -EBUSY; + + if (st->wait_event) { + iio_device_release_direct_mode(indio_dev); + return -EBUSY; + } + + val += AD4052_FS_OFFSET(st->chip->grade); + st->oversampling_frequency = val; + + iio_device_release_direct_mode(indio_dev); + return 0; +} + +static ssize_t ad4052_events_frequency_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ad4052_state *st = iio_priv(dev_to_iio_dev(dev)); + + return sprintf(buf, "%s\n", ad4052_conversion_freqs[st->events_frequency]); +} + +static ssize_t ad4052_events_frequency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad4052_state *st = iio_priv(indio_dev); + int ret; + + if (iio_device_claim_direct_mode(indio_dev)) + return -EBUSY; + + if (st->wait_event) { + iio_device_release_direct_mode(indio_dev); + return -EBUSY; + } + + ret = __sysfs_match_string(AD4052_FS(st->chip->grade), + AD4052_FS_LEN(st->chip->grade), buf); + if (ret < 0) + return ret; + + st->events_frequency = ret; + + iio_device_release_direct_mode(indio_dev); + return len; +} + +static ssize_t ad4052_events_frequency_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ad4052_state *st = iio_priv(dev_to_iio_dev(dev)); + int len = 0; + + for (u8 i = AD4052_FS_OFFSET(st->chip->grade); + i < AD4052_FS_LEN(st->chip->grade); i++) + len += sprintf(buf + len, "%s ", ad4052_conversion_freqs[i]); + + return sprintf(buf + len, "\n") + len; +} + +static IIO_DEVICE_ATTR(sampling_frequency, 0644, + ad4052_events_frequency_show, + ad4052_events_frequency_store, 0); + +static IIO_DEVICE_ATTR(sampling_frequency_available, 0444, + ad4052_events_frequency_available_show, + NULL, 0); + +static struct attribute *ad4052_event_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad4052_event_attribute_group = { + .attrs = ad4052_event_attributes, +}; + +static const struct iio_enum AD4052_500KSPS_conversion_freq_enum = { + .items = AD4052_FS(AD4052_500KSPS), + .num_items = AD4052_FS_LEN(AD4052_500KSPS), + .set = ad4052_oversampling_frequency_set, + .get = ad4052_oversampling_frequency_get, +}; + +static const struct iio_enum AD4052_2MSPS_conversion_freq_enum = { + .items = AD4052_FS(AD4052_2MSPS), + .num_items = AD4052_FS_LEN(AD4052_2MSPS), + .set = ad4052_oversampling_frequency_set, + .get = ad4052_oversampling_frequency_get, +}; + +#define AD4052_EXT_INFO(grade) \ +static struct iio_chan_spec_ext_info grade##_ext_info[] = { \ + IIO_ENUM("oversampling_frequency", IIO_SHARED_BY_ALL, \ + &grade##_conversion_freq_enum), \ + IIO_ENUM_AVAILABLE("oversampling_frequency", IIO_SHARED_BY_ALL, \ + &grade##_conversion_freq_enum), \ + { } \ +} + +AD4052_EXT_INFO(AD4052_2MSPS); +AD4052_EXT_INFO(AD4052_500KSPS); + +#define AD4052_CHAN(bits, grade) { \ + .type = IIO_VOLTAGE, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .indexed = 1, \ + .channel = 0, \ + .event_spec = ad4052_events, \ + .num_event_specs = ARRAY_SIZE(ad4052_events), \ + .has_ext_scan_type = 1, \ + .ext_scan_type = ad4052_scan_type_##bits##_s, \ + .num_ext_scan_type = ARRAY_SIZE(ad4052_scan_type_##bits##_s), \ + .ext_info = grade##_ext_info, \ +} + +#define AD4052_OFFLOAD_CHAN(bits, grade) { \ + .type = IIO_VOLTAGE, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .indexed = 1, \ + .channel = 0, \ + .event_spec = ad4052_events, \ + .num_event_specs = ARRAY_SIZE(ad4052_events), \ + .has_ext_scan_type = 1, \ + .ext_scan_type = ad4052_scan_type_##bits##_s, \ + .num_ext_scan_type = ARRAY_SIZE(ad4052_scan_type_##bits##_s), \ + .ext_info = grade##_ext_info, \ +} + +static const struct ad4052_chip_info ad4050_chip_info = { + .name = "ad4050", + .channels = { AD4052_CHAN(12, AD4052_2MSPS) }, + .offload_channels = { AD4052_OFFLOAD_CHAN(12, AD4052_2MSPS) }, + .prod_id = 0x70, + .max_avg = AD4050_MAX_AVG, + .grade = AD4052_2MSPS, +}; + +static const struct ad4052_chip_info ad4052_chip_info = { + .name = "ad4052", + .channels = { AD4052_CHAN(16, AD4052_2MSPS) }, + .offload_channels = { AD4052_OFFLOAD_CHAN(16, AD4052_2MSPS) }, + .prod_id = 0x72, + .max_avg = AD4052_MAX_AVG, + .grade = AD4052_2MSPS, +}; + +static const struct ad4052_chip_info ad4056_chip_info = { + .name = "ad4056", + .channels = { AD4052_CHAN(12, AD4052_500KSPS) }, + .offload_channels = { AD4052_OFFLOAD_CHAN(12, AD4052_500KSPS) }, + .prod_id = 0x70, + .max_avg = AD4050_MAX_AVG, + .grade = AD4052_500KSPS, +}; + +static const struct ad4052_chip_info ad4058_chip_info = { + .name = "ad4058", + .channels = { AD4052_CHAN(16, AD4052_500KSPS) }, + .offload_channels = { AD4052_OFFLOAD_CHAN(16, AD4052_500KSPS) }, + .prod_id = 0x72, + .max_avg = AD4052_MAX_AVG, + .grade = AD4052_500KSPS, +}; + +static void ad4052_update_xfer_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct ad4052_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + struct spi_transfer *xfer = &st->xfer; + + scan_type = iio_get_current_scan_type(indio_dev, chan); + + xfer->bits_per_word = scan_type->realbits; + xfer->len = BITS_TO_BYTES(scan_type->storagebits); +} + +static int ad4052_update_xfer_offload(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct ad4052_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + struct spi_transfer *xfer = &st->xfer; + + scan_type = iio_get_current_scan_type(indio_dev, chan); + + xfer = &st->offload_xfer; + xfer->bits_per_word = scan_type->realbits; + xfer->len = BITS_TO_BYTES(scan_type->storagebits); + + spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1); + st->offload_msg.offload = st->offload; + + return spi_optimize_message(st->spi, &st->offload_msg); +} + +static int ad4052_set_oversampling_ratio(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int val) +{ + struct ad4052_state *st = iio_priv(indio_dev); + int ret; + + if ((val) < 1 || (val) > BIT(st->chip->max_avg + 1)) + return -EINVAL; + + /* 1 disables oversampling */ + if (val == 1) { + st->mode = AD4052_SAMPLE_MODE; + } else { + val = ilog2(val); + st->mode = AD4052_BURST_AVERAGING_MODE; + ret = regmap_write(st->regmap, AD4052_REG_AVG_CONFIG, val - 1); + if (ret) + return ret; + } + + ad4052_update_xfer_raw(indio_dev, chan); + + return 0; +} + +static int ad4052_get_oversampling_ratio(struct ad4052_state *st, + unsigned int *val) +{ + int ret; + + if (st->mode == AD4052_SAMPLE_MODE) { + *val = 1; + return 0; + } + + ret = regmap_read(st->regmap, AD4052_REG_AVG_CONFIG, val); + if (ret) + return ret; + + *val = BIT(*val + 1); + + return 0; +} + +static int ad4052_check_ids(struct ad4052_state *st) +{ + int ret; + u16 val; + + ret = regmap_bulk_read(st->regmap, AD4052_REG_PROD_ID_1, &st->d16, + sizeof(st->d16)); + if (ret) + return ret; + + val = be16_to_cpu(st->d16); + if (val != st->chip->prod_id) + dev_info(&st->spi->dev, + "Production ID x%x does not match known values", val); + + ret = regmap_bulk_read(st->regmap, AD4052_REG_VENDOR_H, &st->d16, + sizeof(st->d16)); + if (ret) + return ret; + + val = be16_to_cpu(st->d16); + if (val != AD4052_SPI_VENDOR) + return -ENODEV; + + return 0; +} + +static int ad4052_exit_command(struct ad4052_state *st) +{ + struct spi_device *spi = st->spi; + const u8 val = 0xA8; + + return spi_write(spi, &val, 1); +} + +static int ad4052_set_operation_mode(struct ad4052_state *st, + enum ad4052_operation_mode mode) +{ + int ret; + + if (mode == AD4052_BURST_AVERAGING_MODE) { + ret = ad4052_conversion_frequency_set(st, st->oversampling_frequency); + if (ret) + return ret; + } + + ret = regmap_update_bits(st->regmap, AD4052_REG_ADC_MODES, + AD4052_ADC_MODES_MODE_MSK, mode); + if (ret) + return ret; + + return regmap_write(st->regmap, AD4052_REG_MODE_SET, + AD4052_MODE_SET_ENTER_ADC); +} + +static int ad4052_set_sampling_freq(struct ad4052_state *st, unsigned int freq) +{ + struct pwm_state pwm_st; + + if (freq <= 0 || freq > AD4052_MAX_RATE(st->chip->grade)) + return -EINVAL; + + pwm_get_state(st->cnv_pwm, &pwm_st); + pwm_st.period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, freq); + return pwm_apply_might_sleep(st->cnv_pwm, &pwm_st); +} + +static int ad4052_soft_reset(struct ad4052_state *st) +{ + int ret; + + memset(st->buf_reset_pattern, 0xFF, sizeof(st->buf_reset_pattern)); + for (int i = 0; i < 3; i++) + st->buf_reset_pattern[6 * (i + 1) - 1] = 0xFE; + + ret = spi_write(st->spi, st->buf_reset_pattern, + sizeof(st->buf_reset_pattern)); + if (ret) + return ret; + + /* Wait AD4052 reset delay */ + fsleep(5000); + + return 0; +} + +static int ad4052_setup(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct ad4052_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + + scan_type = iio_get_current_scan_type(indio_dev, chan); + + u8 val = FIELD_PREP(AD4052_GP_CONF_MODE_MSK_0, AD4052_GP_INTR) | + FIELD_PREP(AD4052_GP_CONF_MODE_MSK_1, AD4052_GP_DRDY); + int ret; + + ret = regmap_update_bits(st->regmap, AD4052_REG_GP_CONF, + AD4052_GP_CONF_MODE_MSK_1 | AD4052_GP_CONF_MODE_MSK_0, + val); + if (ret) + return ret; + + val = FIELD_PREP(AD4052_INTR_CONF_EN_MSK_0, (AD4052_INTR_EN_EITHER)) | + FIELD_PREP(AD4052_INTR_CONF_EN_MSK_1, (AD4052_INTR_EN_NEITHER)); + + ret = regmap_update_bits(st->regmap, AD4052_REG_INTR_CONF, + AD4052_INTR_CONF_EN_MSK_0 | AD4052_INTR_CONF_EN_MSK_1, + val); + if (ret) + return ret; + + val = 0; + if (scan_type->sign == 's') + val |= AD4052_ADC_MODES_DATA_FORMAT; + + st->data_format = val; + + if (st->chip->grade == AD4052_500KSPS) { + ret = regmap_write(st->regmap, AD4052_REG_TIMER_CONFIG, + FIELD_PREP(AD4052_TIMER_CONFIG_FS_MASK, + AD4052_TIMER_CONFIG_300KSPS)); + if (ret) + return ret; + } + + return regmap_write(st->regmap, AD4052_REG_ADC_MODES, val); +} + +static irqreturn_t ad4052_irq_handler_thresh(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static irqreturn_t ad4052_irq_handler_drdy(int irq, void *private) +{ + struct ad4052_state *st = private; + + complete(&st->completion); + + return IRQ_HANDLED; +} + +static int ad4052_request_irq(struct iio_dev *indio_dev) +{ + struct ad4052_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + int ret = 0; + + ret = fwnode_irq_get_byname(dev_fwnode(&st->spi->dev), "gp0"); + if (ret <= 0) + return ret ? ret : -EINVAL; + + ret = devm_request_threaded_irq(dev, ret, NULL, + ad4052_irq_handler_thresh, + IRQF_ONESHOT, indio_dev->name, + indio_dev); + if (ret) + return ret; + + ret = fwnode_irq_get_byname(dev_fwnode(&st->spi->dev), "gp1"); + if (ret <= 0) + return ret ? ret : -EINVAL; + + st->gp1_irq = ret; + return devm_request_threaded_irq(dev, ret, NULL, + ad4052_irq_handler_drdy, + IRQF_ONESHOT, indio_dev->name, + st); +} + +static const int ad4052_oversampling_avail[] = { + 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, +}; + +static int ad4052_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *len, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = ad4052_oversampling_avail; + *len = ARRAY_SIZE(ad4052_oversampling_avail); + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int __ad4052_read_chan_raw(struct ad4052_state *st, int *val) +{ + struct spi_device *spi = st->spi; + int ret; + struct spi_transfer t_cnv = { + .len = 0 + }; + + reinit_completion(&st->completion); + + if (st->cnv_gp) { + gpiod_set_value_cansleep(st->cnv_gp, 1); + gpiod_set_value_cansleep(st->cnv_gp, 0); + } else { + ret = spi_sync_transfer(spi, &t_cnv, 1); + if (ret) + return ret; + } + /* + * Single sample read should be used only for oversampling and + * sampling frequency pairs that take less than 1 sec. + */ + ret = wait_for_completion_timeout(&st->completion, + msecs_to_jiffies(1000)); + if (!ret) + return -ETIMEDOUT; + + ret = spi_sync_transfer(spi, &st->xfer, 1); + if (ret) + return ret; + + if (st->xfer.len == 2) { + *val = be16_to_cpu(st->d16); + if (st->data_format & AD4052_ADC_MODES_DATA_FORMAT) + *val = sign_extend32(*val, 15); + } else { + *val = be32_to_cpu(st->d32); + if (st->data_format & AD4052_ADC_MODES_DATA_FORMAT) + *val = sign_extend32(*val, 23); + } + + return ret; +} + +static int ad4052_read_chan_raw(struct iio_dev *indio_dev, int *val) +{ + struct ad4052_state *st = iio_priv(indio_dev); + int ret; + + ret = pm_runtime_resume_and_get(&st->spi->dev); + if (ret) + return ret; + + ret = ad4052_set_operation_mode(st, st->mode); + if (ret) + goto out_error; + + ret = __ad4052_read_chan_raw(st, val); + if (ret) + goto out_error; + + ret = ad4052_exit_command(st); + +out_error: + pm_runtime_mark_last_busy(&st->spi->dev); + pm_runtime_put_autosuspend(&st->spi->dev); + return ret; +} + +static int ad4052_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ad4052_state *st = iio_priv(indio_dev); + struct pwm_state pwm_st; + int ret; + + if (iio_device_claim_direct_mode(indio_dev)) + return -EBUSY; + + if (st->wait_event) { + iio_device_release_direct_mode(indio_dev); + return -EBUSY; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ad4052_read_chan_raw(indio_dev, val); + if (ret) + goto out_release; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + ret = ad4052_get_oversampling_ratio(st, val); + if (ret) + goto out_release; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = pwm_get_state_hw(st->cnv_pwm, &pwm_st); + if (!pwm_st.enabled) + pwm_get_state(st->cnv_pwm, &pwm_st); + + *val = DIV_ROUND_UP_ULL(NSEC_PER_SEC, pwm_st.period); + + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + } + +out_release: + iio_device_release_direct_mode(indio_dev); + return ret; +} + +static int ad4052_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long info) +{ + struct ad4052_state *st = iio_priv(indio_dev); + int ret; + + if (iio_device_claim_direct_mode(indio_dev)) + return -EBUSY; + + if (st->wait_event) { + iio_device_release_direct_mode(indio_dev); + return -EBUSY; + } + + switch (info) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + ret = ad4052_set_oversampling_ratio(indio_dev, chan, val); + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = ad4052_set_sampling_freq(st, val); + break; + default: + ret = -EINVAL; + } + + iio_device_release_direct_mode(indio_dev); + return ret; +} + +static int ad4052_monitor_mode_enable(struct ad4052_state *st) +{ + int ret; + + ret = pm_runtime_resume_and_get(&st->spi->dev); + if (ret) + return ret; + + ret = ad4052_conversion_frequency_set(st, st->events_frequency); + if (ret) + goto out_error; + + ret = ad4052_set_operation_mode(st, AD4052_MONITOR_MODE); + if (ret) + goto out_error; + + return ret; +out_error: + pm_runtime_mark_last_busy(&st->spi->dev); + pm_runtime_put_autosuspend(&st->spi->dev); + return ret; +} + +static int ad4052_monitor_mode_disable(struct ad4052_state *st) +{ + int ret; + + pm_runtime_mark_last_busy(&st->spi->dev); + pm_runtime_put_autosuspend(&st->spi->dev); + + ret = ad4052_exit_command(st); + + if (ret) + pm_runtime_resume_and_get(&st->spi->dev); + + return ret; +} + +static int ad4052_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 ad4052_state *st = iio_priv(indio_dev); + + return st->wait_event; +} + +static int ad4052_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state) +{ + struct ad4052_state *st = iio_priv(indio_dev); + int ret = -EBUSY; + + if (iio_device_claim_direct_mode(indio_dev)) + return -EBUSY; + + if (st->wait_event == state) { + iio_device_release_direct_mode(indio_dev); + return 0; + } + + if (state) + ret = ad4052_monitor_mode_enable(st); + else + ret = ad4052_monitor_mode_disable(st); + + if (!ret) + st->wait_event = state; + + iio_device_release_direct_mode(indio_dev); + return ret; +} + +static int ad4052_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 ad4052_state *st = iio_priv(indio_dev); + u8 reg, size = 1; + int ret; + + if (iio_device_claim_direct_mode(indio_dev)) + return -EBUSY; + + if (st->wait_event) { + iio_device_release_direct_mode(indio_dev); + return -EBUSY; + } + + switch (info) { + case IIO_EV_INFO_VALUE: + if (dir == IIO_EV_DIR_RISING) + reg = AD4052_REG_MAX_LIMIT; + else + reg = AD4052_REG_MIN_LIMIT; + size = 2; + break; + case IIO_EV_INFO_HYSTERESIS: + if (dir == IIO_EV_DIR_RISING) + reg = AD4052_REG_MAX_HYST; + else + reg = AD4052_REG_MIN_HYST; + break; + default: + ret = -EINVAL; + goto out_release; + } + + ret = regmap_bulk_read(st->regmap, reg, &st->d32, size); + if (ret) + goto out_release; + + if (reg == AD4052_REG_MAX_LIMIT || reg == AD4052_REG_MIN_LIMIT) { + *val = be16_to_cpu(st->d16); + if (st->data_format & AD4052_ADC_MODES_DATA_FORMAT) + *val = sign_extend32(*val, 11); + } else { + *val = be32_to_cpu(st->d32); + } + +out_release: + iio_device_release_direct_mode(indio_dev); + return ret ? ret : IIO_VAL_INT; +} + +static int ad4052_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 ad4052_state *st = iio_priv(indio_dev); + int ret = -EINVAL; + u8 reg, size = 1; + + if (iio_device_claim_direct_mode(indio_dev)) + return -EBUSY; + + if (st->wait_event) { + iio_device_release_direct_mode(indio_dev); + return -EBUSY; + } + + switch (type) { + case IIO_EV_TYPE_THRESH: + switch (info) { + case IIO_EV_INFO_VALUE: + if (st->data_format & AD4052_ADC_MODES_DATA_FORMAT) { + if (val > 2047 || val < -2048) + goto out_release; + } else if (val > 4095 || val < 0) { + goto out_release; + } + if (dir == IIO_EV_DIR_RISING) + reg = AD4052_REG_MAX_LIMIT; + else + reg = AD4052_REG_MIN_LIMIT; + size = 2; + st->d16 = cpu_to_be16(val); + break; + case IIO_EV_INFO_HYSTERESIS: + if (val & BIT(7)) + goto out_release; + if (dir == IIO_EV_DIR_RISING) + reg = AD4052_REG_MAX_HYST; + else + reg = AD4052_REG_MIN_HYST; + st->d16 = cpu_to_be16(val >> 8); + break; + default: + goto out_release; + } + break; + default: + goto out_release; + } + + ret = regmap_bulk_write(st->regmap, reg, &st->d16, size); + +out_release: + iio_device_release_direct_mode(indio_dev); + return ret; +} + +static int ad4052_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4052_state *st = iio_priv(indio_dev); + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_DATA_READY, + }; + int ret; + + if (st->wait_event) + return -EBUSY; + + ret = pm_runtime_resume_and_get(&st->spi->dev); + if (ret) + return ret; + + ret = ad4052_set_operation_mode(st, st->mode); + if (ret) + goto out_error; + + ret = ad4052_update_xfer_offload(indio_dev, indio_dev->channels); + if (ret) + goto out_error; + + /* SPI Offload handles the data ready irq */ + disable_irq(st->gp1_irq); + + ret = spi_offload_trigger_enable(st->offload, st->offload_trigger, + &config); + if (ret) + goto out_offload_error; + + ret = pwm_enable(st->cnv_pwm); + if (ret) + goto out_pwm_error; + + return 0; + +out_pwm_error: + spi_offload_trigger_disable(st->offload, st->offload_trigger); +out_offload_error: + enable_irq(st->gp1_irq); + spi_unoptimize_message(&st->offload_msg); + ad4052_exit_command(st); +out_error: + pm_runtime_mark_last_busy(&st->spi->dev); + pm_runtime_put_autosuspend(&st->spi->dev); + + return ret; +} + +static int ad4052_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4052_state *st = iio_priv(indio_dev); + int ret; + + pwm_disable(st->cnv_pwm); + + spi_offload_trigger_disable(st->offload, st->offload_trigger); + spi_unoptimize_message(&st->offload_msg); + enable_irq(st->gp1_irq); + + ret = ad4052_exit_command(st); + + pm_runtime_mark_last_busy(&st->spi->dev); + pm_runtime_put_autosuspend(&st->spi->dev); + + return ret; +} + +static const struct iio_buffer_setup_ops ad4052_buffer_setup_ops = { + .postenable = &ad4052_buffer_postenable, + .predisable = &ad4052_buffer_predisable, +}; + +static int ad4052_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad4052_state *st = iio_priv(indio_dev); + int ret; + + if (iio_device_claim_direct_mode(indio_dev)) + return -EBUSY; + + if (st->wait_event) { + iio_device_release_direct_mode(indio_dev); + return -EBUSY; + } + + if (readval) + ret = regmap_read(st->regmap, reg, readval); + else + ret = regmap_write(st->regmap, reg, writeval); + + iio_device_release_direct_mode(indio_dev); + return ret; +} + +static int ad4052_get_current_scan_type(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad4052_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) + return st->mode == AD4052_BURST_AVERAGING_MODE ? + AD4052_SCAN_TYPE_OFFLOAD_BURST_AVG : + AD4052_SCAN_TYPE_OFFLOAD_SAMPLE; + + return st->mode == AD4052_BURST_AVERAGING_MODE ? + AD4052_SCAN_TYPE_BURST_AVG : + AD4052_SCAN_TYPE_SAMPLE; +} + +static const struct iio_info ad4052_info = { + .read_raw = ad4052_read_raw, + .write_raw = ad4052_write_raw, + .read_avail = ad4052_read_avail, + .read_event_config = &ad4052_read_event_config, + .write_event_config = &ad4052_write_event_config, + .read_event_value = &ad4052_read_event_value, + .write_event_value = &ad4052_write_event_value, + .event_attrs = &ad4052_event_attribute_group, + .get_current_scan_type = &ad4052_get_current_scan_type, + .debugfs_reg_access = &ad4052_debugfs_reg_access, +}; + +static const struct regmap_config ad4052_regmap_config = { + .name = "ad4052", + .reg_bits = 8, + .val_bits = 8, + .max_register = AD4052_MAX_REG, + .rd_table = &ad4052_regmap_rd_table, + .wr_table = &ad4052_regmap_wr_table, + .read_flag_mask = BIT(7), + .can_sleep = true, +}; + +static const struct spi_offload_config ad4052_offload_config = { + .capability_flags = SPI_OFFLOAD_CAP_TRIGGER | + SPI_OFFLOAD_CAP_RX_STREAM_DMA, +}; + +static void ad4052_pwm_disable(void *pwm) +{ + pwm_disable(pwm); +} + +static bool ad4052_offload_trigger_match(struct spi_offload_trigger *trigger, + enum spi_offload_trigger_type type, + u64 *args, u32 nargs) +{ + return type == SPI_OFFLOAD_TRIGGER_DATA_READY; +} + +static const struct spi_offload_trigger_ops ad4052_offload_trigger_ops = { + .match = ad4052_offload_trigger_match, +}; + +static int ad4052_request_offload(struct iio_dev *indio_dev) +{ + struct ad4052_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + struct dma_chan *rx_dma; + struct spi_offload_trigger_info trigger_info = { + .fwnode = dev_fwnode(dev), + .ops = &ad4052_offload_trigger_ops, + .priv = st, + }; + struct pwm_state pwm_st; + int ret; + + indio_dev->setup_ops = &ad4052_buffer_setup_ops; + + ret = devm_spi_offload_trigger_register(dev, &trigger_info); + if (ret) + return dev_err_probe(dev, ret, + "failed to register offload trigger\n"); + + st->offload_trigger = devm_spi_offload_trigger_get(dev, st->offload, + SPI_OFFLOAD_TRIGGER_DATA_READY); + if (IS_ERR(st->offload_trigger)) + return PTR_ERR(st->offload_trigger); + + st->cnv_pwm = devm_pwm_get(dev, NULL); + if (IS_ERR(st->cnv_pwm)) + return dev_err_probe(dev, PTR_ERR(st->cnv_pwm), + "failed to get CNV PWM\n"); + + pwm_init_state(st->cnv_pwm, &pwm_st); + + pwm_st.enabled = false; + pwm_st.duty_cycle = AD4052_T_CNVH_NS * 2; + pwm_st.period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, + AD4052_MAX_RATE(st->chip->grade)); + + ret = pwm_apply_might_sleep(st->cnv_pwm, &pwm_st); + if (ret) + return dev_err_probe(dev, ret, "failed to apply CNV PWM\n"); + + ret = devm_add_action_or_reset(dev, ad4052_pwm_disable, st->cnv_pwm); + if (ret) + return ret; + + rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload); + if (IS_ERR(rx_dma)) + return PTR_ERR(rx_dma); + + return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma, + IIO_BUFFER_DIRECTION_IN); +} + +static int ad4052_probe(struct spi_device *spi) +{ + const struct ad4052_chip_info *chip; + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad4052_state *st; + int ret = 0; + + chip = spi_get_device_match_data(spi); + if (!chip) + return dev_err_probe(dev, -ENODEV, + "Could not find chip info data\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + spi_set_drvdata(spi, st); + init_completion(&st->completion); + + st->regmap = devm_regmap_init_spi(spi, &ad4052_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to initialize regmap\n"); + + st->mode = AD4052_SAMPLE_MODE; + st->wait_event = false; + st->chip = chip; + st->oversampling_frequency = AD4052_FS_OFFSET(st->chip->grade); + st->events_frequency = AD4052_FS_OFFSET(st->chip->grade); + + st->cnv_gp = devm_gpiod_get_optional(dev, "cnv", GPIOD_OUT_LOW); + if (IS_ERR(st->cnv_gp)) + return dev_err_probe(dev, PTR_ERR(st->cnv_gp), + "Failed to get cnv gpio\n"); + + indio_dev->modes = INDIO_BUFFER_HARDWARE | INDIO_DIRECT_MODE; + indio_dev->num_channels = 1; + indio_dev->info = &ad4052_info; + indio_dev->name = chip->name; + + st->offload = devm_spi_offload_get(dev, spi, &ad4052_offload_config); + if (IS_ERR(st->offload)) + return PTR_ERR(st->offload); + + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get offload\n"); + + if (ret == -ENODEV) { + st->offload_trigger = NULL; + indio_dev->channels = chip->channels; + } else { + indio_dev->channels = chip->offload_channels; + ret = ad4052_request_offload(indio_dev); + if (ret) + return dev_err_probe(dev, ret, + "Failed to configure offload\n"); + } + + st->xfer.rx_buf = &st->d32; + + ret = ad4052_soft_reset(st); + if (ret) + return dev_err_probe(dev, ret, "AD4052 failed to soft reset\n"); + + ret = ad4052_check_ids(st); + if (ret) + return dev_err_probe(dev, ret, + "AD4052 fields assertions failed\n"); + + ret = ad4052_setup(indio_dev, indio_dev->channels); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4052_REG_DEVICE_STATUS, + AD4052_DEVICE_STATUS_DEVICE_RESET); + if (ret) + return ret; + + ret = ad4052_request_irq(indio_dev); + if (ret) + return ret; + + ad4052_update_xfer_raw(indio_dev, indio_dev->channels); + + pm_runtime_set_active(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable pm_runtime\n"); + + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + + return devm_iio_device_register(dev, indio_dev); +} + +static int ad4052_runtime_suspend(struct device *dev) +{ + struct ad4052_state *st = dev_get_drvdata(dev); + + return regmap_write(st->regmap, AD4052_REG_DEVICE_CONFIG, + FIELD_PREP(AD4052_DEVICE_CONFIG_POWER_MODE_MSK, + AD4052_DEVICE_CONFIG_LOW_POWER_MODE)); +} + +static int ad4052_runtime_resume(struct device *dev) +{ + struct ad4052_state *st = dev_get_drvdata(dev); + int ret; + + ret = regmap_write(st->regmap, AD4052_REG_DEVICE_CONFIG, + FIELD_PREP(AD4052_DEVICE_CONFIG_POWER_MODE_MSK, 0)); + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ad4052_pm_ops, ad4052_runtime_suspend, + ad4052_runtime_resume, NULL); + +static const struct spi_device_id ad4052_id_table[] = { + {"ad4050", (kernel_ulong_t)&ad4050_chip_info }, + {"ad4052", (kernel_ulong_t)&ad4052_chip_info }, + {"ad4056", (kernel_ulong_t)&ad4056_chip_info }, + {"ad4058", (kernel_ulong_t)&ad4058_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad4052_id_table); + +static const struct of_device_id ad4052_of_match[] = { + { .compatible = "adi,ad4050", .data = &ad4050_chip_info }, + { .compatible = "adi,ad4052", .data = &ad4052_chip_info }, + { .compatible = "adi,ad4056", .data = &ad4056_chip_info }, + { .compatible = "adi,ad4058", .data = &ad4058_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad4052_of_match); + +static struct spi_driver ad4052_driver = { + .driver = { + .name = "ad4052", + .of_match_table = ad4052_of_match, + .pm = pm_ptr(&ad4052_pm_ops), + }, + .probe = ad4052_probe, + .id_table = ad4052_id_table, +}; +module_spi_driver(ad4052_driver); + +MODULE_AUTHOR("Jorge Marques "); +MODULE_DESCRIPTION("Analog Devices AD4052"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(IIO_DMAENGINE_BUFFER); From 13df15e250a913b1dcebd5e412b9c66e9dd9ce0d Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Fri, 21 Feb 2025 21:02:28 +0100 Subject: [PATCH 9/9] Kconfig.adi: imply AD4052 Add entry for the AD4052 ADC family driver. Signed-off-by: Jorge Marques --- drivers/iio/Kconfig.adi | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/Kconfig.adi b/drivers/iio/Kconfig.adi index 79544a6c70801f..3dd47f89739028 100644 --- a/drivers/iio/Kconfig.adi +++ b/drivers/iio/Kconfig.adi @@ -38,6 +38,7 @@ config IIO_ALL_ADI_DRIVERS imply ADXL380_SPI if SPI imply AD4000 + imply AD4052 imply AD4630 imply AD4695 imply AD4130