* [PATCH 0/2] iio: dac: add support for AD5413
@ 2026-10-16 19:23 Bruce Tsao via B4 Relay
2026-10-16 19:23 ` [PATCH 1/2] dt-bindings: iio: dac: Document Analog Devices AD5413 single-channel DAC Bruce Tsao via B4 Relay
2026-10-16 19:23 ` [PATCH 2/2] iio: dac: add AD5413 driver Bruce Tsao via B4 Relay
0 siblings, 2 replies; 7+ messages in thread
From: Bruce Tsao via B4 Relay @ 2026-10-16 19:23 UTC (permalink / raw)
To: Jonathan Cameron
Cc: linux-iio, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
linux-kernel, Bruce Tsao
This series adds support for the Analog Devices AD5413: a single-channel,
14-bit, serial-input DAC with software-selectable voltage (+/-10.5 V) and
current (0-24 mA) outputs. The SPI interface runs in mode 1 and carries a
CRC-8 on every frame, which the device has enabled out of reset.
Patch 1 documents the device-tree binding; patch 2 adds the IIO driver.
Tested on an EVAL-AD5413SDZ with a Raspberry Pi 5 and an NXP i.MX93 FRDM
board (probe, voltage- and current-output, scale/offset, powerdown/resume).
Signed-off-by: Bruce Tsao <bruce.tsao@analog.com>
---
Bruce Tsao (2):
dt-bindings: iio: dac: Document Analog Devices AD5413 single-channel DAC
iio: dac: add AD5413 driver
.../devicetree/bindings/iio/dac/adi,ad5413.yaml | 101 ++++
drivers/iio/dac/Kconfig | 11 +
drivers/iio/dac/Makefile | 1 +
drivers/iio/dac/ad5413.c | 666 +++++++++++++++++++++
4 files changed, 779 insertions(+)
---
base-commit: a3b3580713f3ac5a32dc2874ee546828977a1d68
change-id: 20261017-ad5413-upstream-f8664fff4fdb
Best regards,
--
Bruce Tsao <bruce.tsao@analog.com>
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH 1/2] dt-bindings: iio: dac: Document Analog Devices AD5413 single-channel DAC 2026-10-16 19:23 [PATCH 0/2] iio: dac: add support for AD5413 Bruce Tsao via B4 Relay @ 2026-10-16 19:23 ` Bruce Tsao via B4 Relay 2026-10-16 19:36 ` sashiko-bot 2026-10-16 23:02 ` Jonathan Cameron 2026-10-16 19:23 ` [PATCH 2/2] iio: dac: add AD5413 driver Bruce Tsao via B4 Relay 1 sibling, 2 replies; 7+ messages in thread From: Bruce Tsao via B4 Relay @ 2026-10-16 19:23 UTC (permalink / raw) To: Jonathan Cameron Cc: linux-iio, David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel, Bruce Tsao From: Bruce Tsao <bruce.tsao@analog.com> Document the devicetree bindings for the Analog Devices AD5413, a single-channel precision DAC controlled via SPI. The device supports voltage or current output modes and programmable slew-rate control. Signed-off-by: Bruce Tsao <bruce.tsao@analog.com> --- .../devicetree/bindings/iio/dac/adi,ad5413.yaml | 101 +++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/dac/adi,ad5413.yaml b/Documentation/devicetree/bindings/iio/dac/adi,ad5413.yaml new file mode 100644 index 000000000000..74e869304aab --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/adi,ad5413.yaml @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/dac/adi,ad5413.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD5413 DAC + +description: + Analog Devices AD5413 is a single-channel precision DAC supporting voltage + or current output modes with programmable slew-rate control. + The output mode is selected via the adi,chan-current-mode property. + Optional reset GPIO may be provided. + +maintainers: + - bruce.tsao@analog.com + +properties: + compatible: + const: adi,ad5413 + + spi-max-frequency: + maximum: 50000000 + + spi-cpha: true + + spi-cpol: false + + reset-gpios: + maxItems: 1 + description: Optional reset GPIO (active low). + + avdd-supply: + description: Analog positive supply (AVDD) + + dvdd-supply: + description: Digital positive supply (DVDD) + + avss-supply: + description: Analog negative supply (AVSS) + + adi,chan-current-mode: + type: boolean + description: Select current output mode. If absent, voltage mode is used. + + adi,slew-time-us: + description: Output digital slew control time in microseconds + minimum: 0 + maximum: 1000000 + default: 0 + +required: + - compatible + - spi-cpha + - avdd-supply + - dvdd-supply + - avss-supply + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + spi { + #address-cells = <1>; + #size-cells = <0>; + + dac@0 { + compatible = "adi,ad5413"; + reg = <0>; + spi-max-frequency = <1000000>; + spi-cpha; + reset-gpios = <&gpio 0 GPIO_ACTIVE_LOW>; + avdd-supply = <&avdd>; + dvdd-supply = <&dvdd>; + avss-supply = <&avss>; + adi,slew-time-us = <125>; + }; + }; + + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + + dac@0 { + compatible = "adi,ad5413"; + reg = <0>; + spi-max-frequency = <1000000>; + spi-cpha; + avdd-supply = <&avdd>; + dvdd-supply = <&dvdd>; + avss-supply = <&avss>; + + adi,chan-current-mode; + }; + }; -- 2.53.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 1/2] dt-bindings: iio: dac: Document Analog Devices AD5413 single-channel DAC 2026-10-16 19:23 ` [PATCH 1/2] dt-bindings: iio: dac: Document Analog Devices AD5413 single-channel DAC Bruce Tsao via B4 Relay @ 2026-10-16 19:36 ` sashiko-bot 2026-10-16 23:02 ` Jonathan Cameron 1 sibling, 0 replies; 7+ messages in thread From: sashiko-bot @ 2026-10-16 19:36 UTC (permalink / raw) To: Bruce Tsao; +Cc: devicetree, robh, conor+dt Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [Medium] dt-bindings: iio: dac: adi,ad5413: missing reg property and requirement - [Medium] dt-bindings: iio: dac: adi,ad5413: invalid maintainers and spi-cpol schema - [Medium] dt-bindings: iio: dac: adi,ad5413: custom boolean mode property duplicates standard range bindings -- commit b9fdb51ef057c689f916309a04134bf25c49ed72 Author: Bruce Tsao <bruce.tsao@analog.com> dt-bindings: iio: dac: Document Analog Devices AD5413 single-channel DAC This commit documents the devicetree bindings for the Analog Devices AD5413 single-channel SPI DAC, including power supplies, reset GPIO, output mode, and slew-rate control. > diff --git a/Documentation/devicetree/bindings/iio/dac/adi,ad5413.yaml b/Documentation/devicetree/bindings/iio/dac/adi,ad5413.yaml > new file mode 100644 > index 0000000000000..74e869304aab9 > --- /dev/null > +++ b/Documentation/devicetree/bindings/iio/dac/adi,ad5413.yaml > @@ -0,0 +1,101 @@ > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/iio/dac/adi,ad5413.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: Analog Devices AD5413 DAC > + > +description: > + Analog Devices AD5413 is a single-channel precision DAC supporting voltage > + or current output modes with programmable slew-rate control. > + The output mode is selected via the adi,chan-current-mode property. > + Optional reset GPIO may be provided. > + > +maintainers: > + - bruce.tsao@analog.com > + > +properties: > + compatible: > + const: adi,ad5413 > + > + spi-max-frequency: > + maximum: 50000000 > + > + spi-cpha: true > + > + spi-cpol: false [Severity: Medium] Will this schema pass make dt_binding_check? In the maintainers block, the entry uses a bare email address rather than the required Full Name <email@domain> format: maintainers: - Bruce Tsao <bruce.tsao@analog.com> Also, in the top-level properties block, spi-cpol is set to false. In DT schema, boolean false property schemas are only valid inside conditional if/then/else blocks to disallow a property defined at the top level. Since spi-cpol is not defined in spi-peripheral-props.yaml and this binding sets unevaluatedProperties: false, should spi-cpol: false simply be omitted? > + > + reset-gpios: > + maxItems: 1 > + description: Optional reset GPIO (active low). > + > + avdd-supply: > + description: Analog positive supply (AVDD) > + > + dvdd-supply: > + description: Digital positive supply (DVDD) > + > + avss-supply: > + description: Analog negative supply (AVSS) > + > + adi,chan-current-mode: > + type: boolean > + description: Select current output mode. If absent, voltage mode is used. [Severity: Medium] Should the output mode and range be described using standard IIO DAC range properties rather than a custom adi,chan-current-mode boolean? In drivers/iio/dac/ad5413.c, the hardware supports a 4-bit range configuration field (AD5413_REG_DAC_CONFIG_RANGE_MSK = GENMASK(3, 0)) with multiple voltage and current ranges, while the driver currently hardcodes only two of them (AD5413_RANGE_PLUSMINUS_10_5V = 0x3 and AD5413_RANGE_0MA_24MA = 0x9) based on this boolean. Could this use output-range-microvolt and output-range-microamp from Documentation/devicetree/bindings/iio/dac/dac.yaml (or adi,range-microvolt and adi,range-microamp as in Documentation/devicetree/bindings/iio/dac/adi,ad5758.yaml) so the binding describes the hardware capabilities rather than the current driver limitation? > + > + adi,slew-time-us: > + description: Output digital slew control time in microseconds > + minimum: 0 > + maximum: 1000000 > + default: 0 > + > +required: > + - compatible > + - spi-cpha > + - avdd-supply > + - dvdd-supply > + - avss-supply [Severity: Medium] Should reg be defined with maxItems: 1 under properties and listed in required? The binding references /schemas/spi/spi-peripheral-props.yaml# under allOf, which defines reg with minItems: 1 and maxItems: 256 and does not mark reg as required. Because AD5413 is a single-chip-select SPI device and both examples specify reg = <0>, omitting reg from properties and required leaves the chip-select count unconstrained and optional during DT validation. > + > +allOf: > + - $ref: /schemas/spi/spi-peripheral-props.yaml# > + > +unevaluatedProperties: false > + > +examples: > + - | > + #include <dt-bindings/gpio/gpio.h> > + > + spi { > + #address-cells = <1>; > + #size-cells = <0>; > + > + dac@0 { > + compatible = "adi,ad5413"; > + reg = <0>; > + spi-max-frequency = <1000000>; > + spi-cpha; > + reset-gpios = <&gpio 0 GPIO_ACTIVE_LOW>; > + avdd-supply = <&avdd>; > + dvdd-supply = <&dvdd>; > + avss-supply = <&avss>; > + adi,slew-time-us = <125>; > + }; > + }; > + > + - | > + spi { > + #address-cells = <1>; > + #size-cells = <0>; > + > + dac@0 { > + compatible = "adi,ad5413"; > + reg = <0>; > + spi-max-frequency = <1000000>; > + spi-cpha; > + avdd-supply = <&avdd>; > + dvdd-supply = <&dvdd>; > + avss-supply = <&avss>; > + > + adi,chan-current-mode; > + }; > + }; In both examples above, could the standard properties (avdd-supply, avss-supply, dvdd-supply, reset-gpios, spi-cpha, spi-max-frequency) be placed in canonical natural-sort order after reg per Documentation/devicetree/bindings/dts-coding-style.rst? -- Sashiko AI review · https://sashiko.dev/#/patchset/20261017-ad5413-upstream-v1-0-61f40c8fef25@analog.com?part=1 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 1/2] dt-bindings: iio: dac: Document Analog Devices AD5413 single-channel DAC 2026-10-16 19:23 ` [PATCH 1/2] dt-bindings: iio: dac: Document Analog Devices AD5413 single-channel DAC Bruce Tsao via B4 Relay 2026-10-16 19:36 ` sashiko-bot @ 2026-10-16 23:02 ` Jonathan Cameron 1 sibling, 0 replies; 7+ messages in thread From: Jonathan Cameron @ 2026-10-16 23:02 UTC (permalink / raw) To: Bruce Tsao via B4 Relay Cc: bruce.tsao, linux-iio, David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel On Sat, 17 Oct 2026 03:23:53 +0800 Bruce Tsao via B4 Relay <devnull+bruce.tsao.analog.com@kernel.org> wrote: > From: Bruce Tsao <bruce.tsao@analog.com> > > Document the devicetree bindings for the Analog Devices AD5413, a > single-channel precision DAC controlled via SPI. The device supports > voltage or current output modes and programmable slew-rate control. > > Signed-off-by: Bruce Tsao <bruce.tsao@analog.com> Hi Bruce, Welcome to IIO. > --- > .../devicetree/bindings/iio/dac/adi,ad5413.yaml | 101 +++++++++++++++++++++ > 1 file changed, 101 insertions(+) > > diff --git a/Documentation/devicetree/bindings/iio/dac/adi,ad5413.yaml b/Documentation/devicetree/bindings/iio/dac/adi,ad5413.yaml > new file mode 100644 > index 000000000000..74e869304aab > --- /dev/null > +++ b/Documentation/devicetree/bindings/iio/dac/adi,ad5413.yaml > @@ -0,0 +1,101 @@ > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/iio/dac/adi,ad5413.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: Analog Devices AD5413 DAC > + > +description: > + Analog Devices AD5413 is a single-channel precision DAC supporting voltage > + or current output modes with programmable slew-rate control. > + The output mode is selected via the adi,chan-current-mode property. > + Optional reset GPIO may be provided. New line per sentence isn't really adding anything. I'd reflow this a a paragraph. > + > +maintainers: > + - bruce.tsao@analog.com > + > +properties: > + compatible: > + const: adi,ad5413 > + > + spi-max-frequency: > + maximum: 50000000 > + > + spi-cpha: true > + > + spi-cpol: false > + > + reset-gpios: > + maxItems: 1 > + description: Optional reset GPIO (active low). From a quick glance looks like we also have LDAC SYNC and FAULT. + a reference out that probably wants to be modeled as a supply. Upshot is this looks like an incomplete binding and generally we try to make them as complete as possible from the start, even if the driver doesn't yet use everything. > + > + avdd-supply: > + description: Analog positive supply (AVDD) > + > + dvdd-supply: > + description: Digital positive supply (DVDD) > + > + avss-supply: > + description: Analog negative supply (AVSS) > + > + adi,chan-current-mode: > + type: boolean > + description: Select current output mode. If absent, voltage mode is used. We have some existing dt bindings for multi mode channels on adi parts. See adi,ch-func in various other bindings. Whilst we've had various discussions about generalizing that to a generic dac binding so far we haven't done so. Maybe now is the time... > + > + adi,slew-time-us: > + description: Output digital slew control time in microseconds > + minimum: 0 > + maximum: 1000000 > + default: 0 > + > +required: > + - compatible > + - spi-cpha > + - avdd-supply > + - dvdd-supply > + - avss-supply > + > +allOf: > + - $ref: /schemas/spi/spi-peripheral-props.yaml# > + > +unevaluatedProperties: false > + > +examples: > + - | > + #include <dt-bindings/gpio/gpio.h> > + > + spi { > + #address-cells = <1>; > + #size-cells = <0>; > + > + dac@0 { > + compatible = "adi,ad5413"; > + reg = <0>; > + spi-max-frequency = <1000000>; > + spi-cpha; > + reset-gpios = <&gpio 0 GPIO_ACTIVE_LOW>; > + avdd-supply = <&avdd>; > + dvdd-supply = <&dvdd>; > + avss-supply = <&avss>; > + adi,slew-time-us = <125>; > + }; > + }; > + > + - | > + spi { > + #address-cells = <1>; > + #size-cells = <0>; > + > + dac@0 { > + compatible = "adi,ad5413"; > + reg = <0>; > + spi-max-frequency = <1000000>; > + spi-cpha; > + avdd-supply = <&avdd>; > + dvdd-supply = <&dvdd>; > + avss-supply = <&avss>; > + > + adi,chan-current-mode; > + }; > + }; > ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 2/2] iio: dac: add AD5413 driver 2026-10-16 19:23 [PATCH 0/2] iio: dac: add support for AD5413 Bruce Tsao via B4 Relay 2026-10-16 19:23 ` [PATCH 1/2] dt-bindings: iio: dac: Document Analog Devices AD5413 single-channel DAC Bruce Tsao via B4 Relay @ 2026-10-16 19:23 ` Bruce Tsao via B4 Relay 2026-10-16 19:33 ` sashiko-bot 2026-10-16 23:40 ` Jonathan Cameron 1 sibling, 2 replies; 7+ messages in thread From: Bruce Tsao via B4 Relay @ 2026-10-16 19:23 UTC (permalink / raw) To: Jonathan Cameron Cc: linux-iio, David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel, Bruce Tsao From: Bruce Tsao <bruce.tsao@analog.com> The AD5413 is a single-channel, 14-bit, voltage and current output digital-to-analog converter (DAC) with programmable output ranges, slew rate control and on-chip diagnostics. Add an IIO driver supporting the +-10.5 V voltage range and the 0-24 mA current range, output enable, slew rate control and device reset over the SPI interface. The device powers up with the SPI CRC enabled, so every frame carries a CRC-8 byte and the interface is configured for SPI mode 1. This lets the driver bring the device up directly from a cold boot without any pre-conditioning. Signed-off-by: Bruce Tsao <bruce.tsao@analog.com> --- drivers/iio/dac/Kconfig | 11 + drivers/iio/dac/Makefile | 1 + drivers/iio/dac/ad5413.c | 666 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 678 insertions(+) diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index e373885e7c7a..402370c67e87 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -91,6 +91,17 @@ config AD5380 To compile this driver as module choose M here: the module will be called ad5380. +config AD5413 + tristate "Analog Devices AD5413 DAC driver" + depends on SPI_MASTER + select CRC8 + help + Say yes here to build support for Analog Devices AD5413 single channel + Digital to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5413. + config AD5421 tristate "Analog Devices AD5421 DAC driver" depends on SPI diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 2cd0fe23db9e..0157face1f67 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_AD3552R_LIB) += ad3552r-common.o obj-$(CONFIG_AD3552R) += ad3552r.o obj-$(CONFIG_AD5360) += ad5360.o obj-$(CONFIG_AD5380) += ad5380.o +obj-$(CONFIG_AD5413) += ad5413.o obj-$(CONFIG_AD5421) += ad5421.o obj-$(CONFIG_AD5624R_SPI) += ad5624r_spi.o obj-$(CONFIG_AD5064) += ad5064.o diff --git a/drivers/iio/dac/ad5413.c b/drivers/iio/dac/ad5413.c new file mode 100644 index 000000000000..96cce32fc640 --- /dev/null +++ b/drivers/iio/dac/ad5413.c @@ -0,0 +1,666 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5413 Digital to analog converters driver + * + * Copyright 2026 Analog Devices Inc. + */ +#include <linux/bitfield.h> +#include <linux/crc8.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iopoll.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/spi/spi.h> +#include <linux/units.h> +#include <linux/util_macros.h> + +/* AD5413 registers definition */ +#define AD5413_REG_NOP 0x00 +#define AD5413_REG_DAC_INPUT 0x01 +#define AD5413_REG_DAC_OUTPUT 0x02 +#define AD5413_REG_CLEAR_CODE 0x03 +#define AD5413_REG_USER_GAIN 0x04 +#define AD5413_REG_USER_OFFSET 0x05 +#define AD5413_REG_DAC_CONFIG 0x06 +#define AD5413_REG_DAC_CONFIG_SR_STEP_MSK GENMASK(15, 13) +#define AD5413_REG_DAC_CONFIG_SR_CLOCK_MSK GENMASK(12, 9) +#define AD5413_REG_DAC_CONFIG_SR_EN_MSK BIT(8) +#define AD5413_REG_DAC_CONFIG_RSET_EXT_EN_MSK BIT(7) +#define AD5413_REG_DAC_CONFIG_OUT_EN_MSK BIT(6) +#define AD5413_REG_DAC_CONFIG_INT_EN_MSK BIT(5) +#define AD5413_REG_DAC_CONFIG_OVRNG_EN_MSK BIT(4) +#define AD5413_REG_DAC_CONFIG_RANGE_MSK GENMASK(3, 0) +#define AD5413_REG_SW_LDAC 0x07 +#define AD5413_REG_KEY 0x08 +#define AD5413_REG_KEY_REG_ADDR_MASK GENMASK(20, 16) +#define AD5413_REG_KEY_CODE_MASK GENMASK(15, 0) +#define AD5413_REG_GP_CONFIG1 0x09 +#define AD5413_REG_GP_CONFIG2 0x0A +#define AD5413_REG_DIGITAL_DIAG_CONFIG 0x10 +#define AD5413_REG_FAULT_PIN_CONFIG 0x12 +#define AD5413_REG_TWO_STAGE_READBACK_SELECT 0x13 +#define AD5413_REG_DIGITAL_DIAG_RESULTS 0x14 +#define AD5413_REG_DIGITAL_DIAG_CAL_MEM_UNREFRESHED_MSK BIT(15) +#define AD5413_REG_DIGITAL_DIAG_SLEW_BUSY_MSK BIT(14) +#define AD5413_REG_ANALOG_DIAG_RESULTS 0x15 +#define AD5413_REG_STATUS 0x16 +#define AD5413_REG_CHIP_ID 0x17 +#define AD5413_REG_FREQ_MONITOR 0x18 +#define AD5413_REG_DEVICE_ID_3 0x1C + +/* Special Key Codes (bits[15:0]) */ +#define AD5413_REG_KEY_CODE_RESET_1 0x15FA +#define AD5413_REG_KEY_CODE_RESET_2 0xAF51 +#define AD5413_REG_KEY_CODE_CALIB_MEM_REFRESH 0xFCBA + +/* DIGITAL_DIAG_RESULTS Register (Addr = 0x14) Busy Flags */ + +#define AD5413_WR_FLAG_MSK(x) (0x80u | ((x) & 0x1F)) + +/* CRC-8 polynomial x^8 + x^2 + x + 1, enabled by default on the device */ +#define AD5413_CRC8_POLY 0x07 + +#define AD5413_FULL_SCALE_MICRO (16383ULL * MICRO) + +struct ad5413_range { + int reg; + int min; + int max; +}; + +struct ad5413_state { + struct spi_device *spi; + struct regulator_bulk_data supplies[3]; + struct mutex lock; /* Protects all register access via SPI */ + const struct ad5413_range *out_range; + unsigned int slew_time; + bool pwr_down; + u8 crc8_tbl[CRC8_TABLE_SIZE]; + __be32 d32[3] __aligned(IIO_DMA_MINALIGN); +}; + +/* + * Output ranges corresponding to DAC_CONFIG[3:0] bits. + * Defined by the following symbolic identifiers: + * AD5413_RANGE_PLUSMINUS_10_5V - ±10.5 V voltage range + * AD5413_RANGE_0MA_24MA - 0 mA to 24 mA current range + */ +enum ad5413_output_range { + AD5413_RANGE_PLUSMINUS_10_5V = 0x3, + AD5413_RANGE_0MA_24MA = 0x9, +}; + +static const struct ad5413_range ad5413_voltage_range[] = { { + .reg = AD5413_RANGE_PLUSMINUS_10_5V, + .min = -10500000, + .max = 10500000, +} }; + +static const struct ad5413_range ad5413_current_range[] = { { + .reg = AD5413_RANGE_0MA_24MA, + .min = 0, + .max = 24000, +} }; + +static const int ad5413_sr_clk[16] = { + 240000, 200000, 150000, 128000, 64000, 32000, 16000, 8000, + 4000, 2000, 1000, 512, 256, 128, 64, 16, +}; + +static const int ad5413_sr_step[8] = { + 4, 12, 64, 120, 256, 500, 1820, 2048, +}; + +static int ad5413_spi_reg_read(struct ad5413_state *st, unsigned int addr, + u16 *val) +{ + struct spi_transfer t[] = { + { + .tx_buf = &st->d32[0], + .len = 4, + .cs_change = 1, + }, + { + .tx_buf = &st->d32[1], + .rx_buf = &st->d32[2], + .len = 4, + }, + }; + u8 sel[3], nop[3]; + int ret; + + /* Two-stage readback: select the register to read back. */ + sel[0] = AD5413_WR_FLAG_MSK(AD5413_REG_TWO_STAGE_READBACK_SELECT); + sel[1] = 0; + sel[2] = addr; + st->d32[0] = cpu_to_be32((sel[0] << 24) | (sel[1] << 16) | + (sel[2] << 8) | + crc8(st->crc8_tbl, sel, sizeof(sel), 0)); + + /* NOP frame clocks the selected register contents out on SDO. */ + nop[0] = AD5413_WR_FLAG_MSK(AD5413_REG_NOP); + nop[1] = 0; + nop[2] = 0; + st->d32[1] = cpu_to_be32((nop[0] << 24) | (nop[1] << 16) | + (nop[2] << 8) | + crc8(st->crc8_tbl, nop, sizeof(nop), 0)); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret < 0) + return ret; + + /* + * The readback CRC covers the live FAULT pin status bit, which can + * toggle between capture and clock-out, so it is not validated here. + */ + *val = be32_to_cpu(st->d32[2]) >> 8; + + return 0; +} + +static int ad5413_spi_reg_write(struct ad5413_state *st, unsigned int addr, + unsigned int val) +{ + u8 buf[3] = { }; + + buf[0] = AD5413_WR_FLAG_MSK(addr); + buf[1] = val >> 8; + buf[2] = val; + + st->d32[0] = cpu_to_be32((buf[0] << 24) | (buf[1] << 16) | + (buf[2] << 8) | + crc8(st->crc8_tbl, buf, sizeof(buf), 0)); + + return spi_write(st->spi, &st->d32[0], sizeof(st->d32[0])); +} + +static int ad5413_spi_write_mask(struct ad5413_state *st, unsigned int addr, + unsigned long mask, unsigned int val) +{ + u16 regval; + int ret; + + ret = ad5413_spi_reg_read(st, addr, ®val); + if (ret) + return ret; + + return ad5413_spi_reg_write(st, addr, (regval & ~mask) | val); +} + +static int ad5413_wait_for_task_complete(struct ad5413_state *st, + unsigned int reg, unsigned int mask) +{ + u16 val; + int ret, err; + + err = read_poll_timeout(ad5413_spi_reg_read, ret, + ret || !(val & mask), + 100, 1000, false, st, reg, &val); + if (ret) + return ret; + + if (err) { + dev_err(&st->spi->dev, + "Timeout waiting for bit 0x%x to clear in reg 0x%x\n", + mask, reg); + return err; + } + + return 0; +} + +static int ad5413_calib_mem_refresh(struct ad5413_state *st) +{ + int ret; + + ret = ad5413_spi_reg_write(st, AD5413_REG_KEY, + AD5413_REG_KEY_CODE_CALIB_MEM_REFRESH); + if (ret < 0) + return dev_err_probe(&st->spi->dev, ret, + "Failed to initiate a calibration memory refresh\n"); + + /* Wait to allow time for the internal calibrations to complete */ + return ad5413_wait_for_task_complete(st, + AD5413_REG_DIGITAL_DIAG_RESULTS, + AD5413_REG_DIGITAL_DIAG_CAL_MEM_UNREFRESHED_MSK); +} + +static int ad5413_slew_rate_set(struct ad5413_state *st, + unsigned int sr_clk_idx, + unsigned int sr_step_idx) +{ + unsigned int mode; + unsigned long mask; + int ret; + + mask = AD5413_REG_DAC_CONFIG_SR_EN_MSK | + AD5413_REG_DAC_CONFIG_SR_CLOCK_MSK | + AD5413_REG_DAC_CONFIG_SR_STEP_MSK; + mode = FIELD_PREP(AD5413_REG_DAC_CONFIG_SR_EN_MSK, 1) | + FIELD_PREP(AD5413_REG_DAC_CONFIG_SR_STEP_MSK, sr_step_idx) | + FIELD_PREP(AD5413_REG_DAC_CONFIG_SR_CLOCK_MSK, sr_clk_idx); + + ret = ad5413_spi_write_mask(st, AD5413_REG_DAC_CONFIG, mask, mode); + if (ret < 0) + return ret; + + /* Wait to allow time for the internal calibrations to complete */ + return ad5413_wait_for_task_complete(st, + AD5413_REG_DIGITAL_DIAG_RESULTS, + AD5413_REG_DIGITAL_DIAG_SLEW_BUSY_MSK); +} + +static int ad5413_slew_rate_config(struct ad5413_state *st) +{ + unsigned int sr_clk_idx = 0, sr_step_idx = 0; + int i, res; + s64 diff_new, diff_old = S64_MAX; + u64 sr_step, calc_slew_time; + + /* + * The slew time can be determined by using the formula: + * Slew Time = (Full Scale Out / (Step Size x Update Clk Freq)) + * where Slew time is expressed in microseconds + * Given the desired slew time, the following algorithm determines the + * best match for the step size and the update clock frequency. + */ + for (i = 0; i < ARRAY_SIZE(ad5413_sr_clk); i++) { + /* + * Go through each valid update clock freq and determine a raw + * value for the step size by using the formula: + * Step Size = Full Scale Out / (Update Clk Freq * Slew Time) + */ + sr_step = div64_u64(AD5413_FULL_SCALE_MICRO, + (u64)ad5413_sr_clk[i] * st->slew_time); + /* + * After a raw value for step size was determined, find the + * closest valid match + */ + res = find_closest(sr_step, ad5413_sr_step, ARRAY_SIZE(ad5413_sr_step)); + + /* Calculate the slew time */ + calc_slew_time = div_u64(AD5413_FULL_SCALE_MICRO, + ad5413_sr_step[res] * ad5413_sr_clk[i]); + /* + * Determine with how many microseconds the calculated slew time + * is different from the desired slew time and store the diff + * for the next iteration + */ + diff_new = abs(st->slew_time - calc_slew_time); + if (diff_new < diff_old) { + diff_old = diff_new; + sr_clk_idx = i; + sr_step_idx = res; + } + } + + return ad5413_slew_rate_set(st, sr_clk_idx, sr_step_idx); +} + +static int ad5413_set_out_range(struct ad5413_state *st, int range) +{ + int ret; + + ret = ad5413_spi_write_mask(st, + AD5413_REG_DAC_CONFIG, + AD5413_REG_DAC_CONFIG_RANGE_MSK, + FIELD_PREP(AD5413_REG_DAC_CONFIG_RANGE_MSK, + range)); + if (ret < 0) + return ret; + + /* Wait to allow time for the internal calibrations to complete */ + return ad5413_wait_for_task_complete(st, + AD5413_REG_DIGITAL_DIAG_RESULTS, + AD5413_REG_DIGITAL_DIAG_CAL_MEM_UNREFRESHED_MSK); +} + +static int ad5413_internal_buffers_en(struct ad5413_state *st, bool enable) +{ + int ret; + + ret = ad5413_spi_write_mask(st, + AD5413_REG_DAC_CONFIG, + AD5413_REG_DAC_CONFIG_INT_EN_MSK, + FIELD_PREP(AD5413_REG_DAC_CONFIG_INT_EN_MSK, + enable)); + if (ret < 0) + return ret; + + /* Wait to allow time for the internal calibrations to complete */ + return ad5413_wait_for_task_complete(st, + AD5413_REG_DIGITAL_DIAG_RESULTS, + AD5413_REG_DIGITAL_DIAG_CAL_MEM_UNREFRESHED_MSK); +} + +static int ad5413_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad5413_state *st = iio_priv(indio_dev); + u16 regval; + int ret; + + guard(mutex)(&st->lock); + if (readval) { + ret = ad5413_spi_reg_read(st, reg, ®val); + if (ret) + return ret; + + *readval = regval; + return 0; + } + + return ad5413_spi_reg_write(st, reg, writeval); +} + +static int ad5413_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long info) +{ + struct ad5413_state *st = iio_priv(indio_dev); + int max, min, ret; + u16 regval; + + switch (info) { + case IIO_CHAN_INFO_RAW: { + guard(mutex)(&st->lock); + ret = ad5413_spi_reg_read(st, AD5413_REG_DAC_INPUT, ®val); + if (ret) + return ret; + + /* 14-bit DAC data is in bits [15:2] of the register. */ + *val = regval >> 2; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + min = st->out_range->min; + max = st->out_range->max; + *val = (max - min) / 1000; + *val2 = 14; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + min = st->out_range->min; + max = st->out_range->max; + *val = div_s64((s64)min * BIT(14), max - min); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad5413_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long info) +{ + struct ad5413_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: { + guard(mutex)(&st->lock); + /* 14-bit DAC data is in bits [15:2] of the register. */ + ret = ad5413_spi_reg_write(st, AD5413_REG_DAC_INPUT, val << 2); + return ret; + } + default: + return -EINVAL; + } +} + +static ssize_t ad5413_read_powerdown(struct iio_dev *indio_dev, uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad5413_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", st->pwr_down); +} + +static ssize_t ad5413_write_powerdown(struct iio_dev *indio_dev, uintptr_t priv, + struct iio_chan_spec const *chan, + const char *buf, size_t len) +{ + struct ad5413_state *st = iio_priv(indio_dev); + bool pwr_down; + unsigned int dac_config_mode, val; + unsigned long dac_config_msk; + int ret; + + ret = kstrtobool(buf, &pwr_down); + if (ret) + return ret; + + val = pwr_down ? 0 : 1; + + dac_config_mode = FIELD_PREP(AD5413_REG_DAC_CONFIG_OUT_EN_MSK, (val)) | + FIELD_PREP(AD5413_REG_DAC_CONFIG_INT_EN_MSK, (val)); + dac_config_msk = AD5413_REG_DAC_CONFIG_OUT_EN_MSK | + AD5413_REG_DAC_CONFIG_INT_EN_MSK; + + guard(mutex)(&st->lock); + ret = ad5413_spi_write_mask(st, AD5413_REG_DAC_CONFIG, dac_config_msk, + dac_config_mode); + if (ret < 0) + return ret; + + st->pwr_down = pwr_down; + + return len; +} + +static const struct iio_info ad5413_info = { + .read_raw = ad5413_read_raw, + .write_raw = ad5413_write_raw, + .debugfs_reg_access = &ad5413_reg_access, +}; + +static const struct iio_chan_spec_ext_info ad5413_ext_info[] = { + { + .name = "powerdown", + .read = ad5413_read_powerdown, + .write = ad5413_write_powerdown, + .shared = IIO_SHARED_BY_TYPE, + }, + { } +}; + +#define AD5413_DAC_CHAN(_chan_type) \ + { \ + .type = (_chan_type), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .indexed = 1, \ + .output = 1, \ + .ext_info = ad5413_ext_info, \ + } + +static const struct iio_chan_spec ad5413_voltage_ch[] = { + AD5413_DAC_CHAN(IIO_VOLTAGE), +}; + +static const struct iio_chan_spec ad5413_current_ch[] = { + AD5413_DAC_CHAN(IIO_CURRENT), +}; + +static int ad5413_parse_fw(struct ad5413_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + if (device_property_read_bool(dev, "adi,chan-current-mode")) + st->out_range = &ad5413_current_range[0]; + else + st->out_range = &ad5413_voltage_range[0]; + + st->slew_time = 0; + if (device_property_present(dev, "adi,slew-time-us")) { + ret = device_property_read_u32(dev, + "adi,slew-time-us", + &st->slew_time); + if (ret) + return dev_err_probe(dev, ret, "Failed to read adi,slew-time-us\n"); + + if (st->slew_time > 1 * MICRO) + return dev_err_probe(dev, -EINVAL, "adi,slew-time-us out of range\n"); + } + + return 0; +} + +static int ad5413_init(struct ad5413_state *st) +{ + struct reset_control *reset; + u16 regval; + int ret; + + reset = devm_reset_control_get_optional_exclusive(&st->spi->dev, NULL); + if (IS_ERR(reset)) + return PTR_ERR(reset); + + if (reset) { + /* Hard reset via the reset controller */ + ret = reset_control_assert(reset); + if (ret) + return ret; + + fsleep(100); + + ret = reset_control_deassert(reset); + if (ret) + return ret; + + fsleep(100); + } else { + /* + * CRC is enabled by default after power-on/reset. The SPI + * helpers always append the CRC byte, so the device accepts + * these soft-reset key frames without any bootstrap step. + */ + ret = ad5413_spi_reg_write(st, AD5413_REG_KEY, + AD5413_REG_KEY_CODE_RESET_1); + if (ret) + return ret; + + ret = ad5413_spi_reg_write(st, AD5413_REG_KEY, + AD5413_REG_KEY_CODE_RESET_2); + if (ret) + return ret; + + fsleep(100); + } + + ret = ad5413_calib_mem_refresh(st); + if (ret < 0) + return ret; + + ret = ad5413_spi_reg_read(st, AD5413_REG_DIGITAL_DIAG_RESULTS, ®val); + if (ret) + return ret; + + ret = ad5413_spi_reg_write(st, AD5413_REG_DIGITAL_DIAG_RESULTS, regval); + if (ret < 0) + return ret; + + ret = ad5413_set_out_range(st, st->out_range->reg); + if (ret < 0) + return ret; + + if (st->slew_time) { + ret = ad5413_slew_rate_config(st); + if (ret < 0) + return ret; + } + + ret = ad5413_internal_buffers_en(st, 1); + if (ret < 0) + return ret; + + return ad5413_spi_write_mask(st, + AD5413_REG_DAC_CONFIG, + AD5413_REG_DAC_CONFIG_OUT_EN_MSK, + FIELD_PREP(AD5413_REG_DAC_CONFIG_OUT_EN_MSK, 1)); +} + +static const char * const ad5413_supply_names[] = { + "avdd", + "dvdd", + "avss", +}; + +static int ad5413_probe(struct spi_device *spi) +{ + struct ad5413_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->spi = spi; + + ret = devm_mutex_init(&spi->dev, &st->lock); + if (ret) + return ret; + + crc8_populate_msb(st->crc8_tbl, AD5413_CRC8_POLY); + + indio_dev->name = "ad5413"; + indio_dev->info = &ad5413_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = 1; + + ret = ad5413_parse_fw(st); + if (ret < 0) + return ret; + + if (st->out_range->reg == AD5413_RANGE_PLUSMINUS_10_5V) + indio_dev->channels = ad5413_voltage_ch; + else + indio_dev->channels = ad5413_current_ch; + + ret = devm_regulator_bulk_get_enable(&spi->dev, + ARRAY_SIZE(ad5413_supply_names), + ad5413_supply_names); + if (ret) + return dev_err_probe(&spi->dev, ret, "failed to enable supplies\n"); + + ret = ad5413_init(st); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, "AD5413 init failed\n"); + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ad5413_id[] = { + { .name = "ad5413" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5413_id); + +static const struct of_device_id ad5413_of_match[] = { + { .compatible = "adi,ad5413" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5413_of_match); + +static struct spi_driver ad5413_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ad5413_of_match, + }, + .probe = ad5413_probe, + .id_table = ad5413_id, +}; +module_spi_driver(ad5413_driver); + +MODULE_AUTHOR("Bruce Tsao <bruce.tsao@analog.com>"); +MODULE_DESCRIPTION("Analog Devices AD5413 DAC"); +MODULE_LICENSE("GPL"); -- 2.53.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 2/2] iio: dac: add AD5413 driver 2026-10-16 19:23 ` [PATCH 2/2] iio: dac: add AD5413 driver Bruce Tsao via B4 Relay @ 2026-10-16 19:33 ` sashiko-bot 2026-10-16 23:40 ` Jonathan Cameron 1 sibling, 0 replies; 7+ messages in thread From: sashiko-bot @ 2026-10-16 19:33 UTC (permalink / raw) To: Bruce Tsao; +Cc: devicetree, robh, conor+dt Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] iio: dac: ad5413: missing range check in ad5413_write_raw() - [Low] iio: dac: ad5413: unused supplies array in struct ad5413_state -- commit bcfddef4b6cda1b4c239a6094a38aa835c2860bd Author: Bruce Tsao <bruce.tsao@analog.com> iio: dac: add AD5413 driver This commit adds an IIO driver for the Analog Devices AD5413 single-channel 14-bit voltage and current output DAC over SPI. > diff --git a/drivers/iio/dac/ad5413.c b/drivers/iio/dac/ad5413.c > new file mode 100644 > index 0000000000000..96cce32fc640e > --- /dev/null > +++ b/drivers/iio/dac/ad5413.c > @@ -0,0 +1,666 @@ [ ... ] > +struct ad5413_state { > + struct spi_device *spi; > + struct regulator_bulk_data supplies[3]; [Severity: Low] Is supplies[3] in struct ad5413_state needed? ad5413_probe() enables the regulators using devm_regulator_bulk_get_enable() with ad5413_supply_names, so this array appears to be unused. > + struct mutex lock; /* Protects all register access via SPI */ > + const struct ad5413_range *out_range; > + unsigned int slew_time; > + bool pwr_down; > + u8 crc8_tbl[CRC8_TABLE_SIZE]; > + __be32 d32[3] __aligned(IIO_DMA_MINALIGN); > +}; [ ... ] > +static int ad5413_spi_reg_write(struct ad5413_state *st, unsigned int addr, > + unsigned int val) > +{ > + u8 buf[3] = { }; > + > + buf[0] = AD5413_WR_FLAG_MSK(addr); > + buf[1] = val >> 8; > + buf[2] = val; > + > + st->d32[0] = cpu_to_be32((buf[0] << 24) | (buf[1] << 16) | > + (buf[2] << 8) | > + crc8(st->crc8_tbl, buf, sizeof(buf), 0)); > + > + return spi_write(st->spi, &st->d32[0], sizeof(st->d32[0])); > +} [ ... ] > +static int ad5413_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, int val, int val2, > + long info) > +{ > + struct ad5413_state *st = iio_priv(indio_dev); > + int ret; > + > + switch (info) { > + case IIO_CHAN_INFO_RAW: { > + guard(mutex)(&st->lock); > + /* 14-bit DAC data is in bits [15:2] of the register. */ > + ret = ad5413_spi_reg_write(st, AD5413_REG_DAC_INPUT, val << 2); > + return ret; > + } [Severity: Medium] Should ad5413_write_raw() validate that val is within the 14-bit range (0 <= val < BIT(14)) before shifting and writing to AD5413_REG_DAC_INPUT? When userspace writes a negative value or a value >= BIT(14) to the raw sysfs attribute, evaluating val << 2 on a negative signed int or a large positive int triggers signed left-shift undefined behavior, and ad5413_spi_reg_write() silently truncates the shifted value into the lower 16 bits of buf[1] and buf[2], programming an unintended DAC output instead of returning -EINVAL. > + default: > + return -EINVAL; > + } > +} [ ... ] -- Sashiko AI review · https://sashiko.dev/#/patchset/20261017-ad5413-upstream-v1-0-61f40c8fef25@analog.com?part=2 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 2/2] iio: dac: add AD5413 driver 2026-10-16 19:23 ` [PATCH 2/2] iio: dac: add AD5413 driver Bruce Tsao via B4 Relay 2026-10-16 19:33 ` sashiko-bot @ 2026-10-16 23:40 ` Jonathan Cameron 1 sibling, 0 replies; 7+ messages in thread From: Jonathan Cameron @ 2026-10-16 23:40 UTC (permalink / raw) To: Bruce Tsao via B4 Relay Cc: bruce.tsao, linux-iio, David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel On Sat, 17 Oct 2026 03:23:54 +0800 Bruce Tsao via B4 Relay <devnull+bruce.tsao.analog.com@kernel.org> wrote: > From: Bruce Tsao <bruce.tsao@analog.com> > > The AD5413 is a single-channel, 14-bit, voltage and current output > digital-to-analog converter (DAC) with programmable output ranges, > slew rate control and on-chip diagnostics. > > Add an IIO driver supporting the +-10.5 V voltage range and the > 0-24 mA current range, output enable, slew rate control and device > reset over the SPI interface. > > The device powers up with the SPI CRC enabled, so every frame carries > a CRC-8 byte and the interface is configured for SPI mode 1. This lets > the driver bring the device up directly from a cold boot without any > pre-conditioning. > > Signed-off-by: Bruce Tsao <bruce.tsao@analog.com> Hi Bruce Good state for a v1. Main thing in my feedback is take another look at the endian conversions. It think those have evolved into being way more complex than they need to be. Jonathan > diff --git a/drivers/iio/dac/ad5413.c b/drivers/iio/dac/ad5413.c > new file mode 100644 > index 000000000000..96cce32fc640 > --- /dev/null > +++ b/drivers/iio/dac/ad5413.c > @@ -0,0 +1,666 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * AD5413 Digital to analog converters driver > + * > + * Copyright 2026 Analog Devices Inc. > + */ > +#include <linux/bitfield.h> > +#include <linux/crc8.h> > +#include <linux/delay.h> > +#include <linux/iio/iio.h> > +#include <linux/iopoll.h> > +#include <linux/math64.h> > +#include <linux/module.h> > +#include <linux/property.h> > +#include <linux/regulator/consumer.h> > +#include <linux/reset.h> > +#include <linux/spi/spi.h> > +#include <linux/units.h> > +#include <linux/util_macros.h> > + > +/* AD5413 registers definition */ > +#define AD5413_REG_NOP 0x00 > +#define AD5413_REG_DAC_INPUT 0x01 > +#define AD5413_REG_DAC_OUTPUT 0x02 > +#define AD5413_REG_CLEAR_CODE 0x03 > +#define AD5413_REG_USER_GAIN 0x04 > +#define AD5413_REG_USER_OFFSET 0x05 > +#define AD5413_REG_DAC_CONFIG 0x06 > +#define AD5413_REG_DAC_CONFIG_SR_STEP_MSK GENMASK(15, 13) Mixing fields and register defines can make for hard reading. I sugest using a bit of indenting to make them visually different. #define AD5413_REG_DAC_CONFIG 0x06 #define AD5413_REG_DAC_CONFIG_SR_STEP_MSK GENMASK(15, 13) #define AD5413_REG_DAC_CONFIG_SR_CLOCK_MSK GENMASK(12, 9) #define AD5413_REG_DAC_CONFIG_SR_EN_MSK BIT(8) #define AD5413_REG_DAC_CONFIG_RSET_EXT_EN_MSK BIT(7) #define AD5413_REG_DAC_CONFIG_OUT_EN_MSK BIT(6) #define AD5413_REG_DAC_CONFIG_INT_EN_MSK BIT(5) #define AD5413_REG_DAC_CONFIG_OVRNG_EN_MSK BIT(4) #define AD5413_REG_DAC_CONFIG_RANGE_MSK GENMASK(3, 0) define AD5413_REG_SW_LDAC 0x07 etc. If you have values of fields defined, then indent them even further. > +#define AD5413_REG_DAC_CONFIG_SR_CLOCK_MSK GENMASK(12, 9) > +#define AD5413_REG_DAC_CONFIG_SR_EN_MSK BIT(8) > +#define AD5413_REG_DAC_CONFIG_RSET_EXT_EN_MSK BIT(7) > +#define AD5413_REG_DAC_CONFIG_OUT_EN_MSK BIT(6) > +#define AD5413_REG_DAC_CONFIG_INT_EN_MSK BIT(5) > +#define AD5413_REG_DAC_CONFIG_OVRNG_EN_MSK BIT(4) > +#define AD5413_REG_DAC_CONFIG_RANGE_MSK GENMASK(3, 0) > +#define AD5413_REG_SW_LDAC 0x07 > +#define AD5413_REG_KEY 0x08 > +#define AD5413_REG_KEY_REG_ADDR_MASK GENMASK(20, 16) > +#define AD5413_REG_KEY_CODE_MASK GENMASK(15, 0) > +#define AD5413_REG_GP_CONFIG1 0x09 > +#define AD5413_REG_GP_CONFIG2 0x0A > +#define AD5413_REG_DIGITAL_DIAG_CONFIG 0x10 > +#define AD5413_REG_FAULT_PIN_CONFIG 0x12 > +#define AD5413_REG_TWO_STAGE_READBACK_SELECT 0x13 > +#define AD5413_REG_DIGITAL_DIAG_RESULTS 0x14 > +#define AD5413_REG_DIGITAL_DIAG_CAL_MEM_UNREFRESHED_MSK BIT(15) > +#define AD5413_REG_DIGITAL_DIAG_SLEW_BUSY_MSK BIT(14) > +#define AD5413_REG_ANALOG_DIAG_RESULTS 0x15 > +#define AD5413_REG_STATUS 0x16 > +#define AD5413_REG_CHIP_ID 0x17 > +#define AD5413_REG_FREQ_MONITOR 0x18 > +#define AD5413_REG_DEVICE_ID_3 0x1C > + > +/* Special Key Codes (bits[15:0]) */ > +#define AD5413_REG_KEY_CODE_RESET_1 0x15FA > +#define AD5413_REG_KEY_CODE_RESET_2 0xAF51 > +#define AD5413_REG_KEY_CODE_CALIB_MEM_REFRESH 0xFCBA > + > +/* DIGITAL_DIAG_RESULTS Register (Addr = 0x14) Busy Flags */ > + > +#define AD5413_WR_FLAG_MSK(x) (0x80u | ((x) & 0x1F)) > + > +/* CRC-8 polynomial x^8 + x^2 + x + 1, enabled by default on the device */ > +#define AD5413_CRC8_POLY 0x07 > + > +#define AD5413_FULL_SCALE_MICRO (16383ULL * MICRO) > + > +struct ad5413_range { > + int reg; > + int min; > + int max; > +}; > + > +struct ad5413_state { > + struct spi_device *spi; > + struct regulator_bulk_data supplies[3]; > + struct mutex lock; /* Protects all register access via SPI */ Not necessary to protect data accesses in general as spi will do that. The point you want to make is about rmw sequences. > + const struct ad5413_range *out_range; > + unsigned int slew_time; > + bool pwr_down; > + u8 crc8_tbl[CRC8_TABLE_SIZE]; > + __be32 d32[3] __aligned(IIO_DMA_MINALIGN); As below. I'd throw in a d8 array to simplify the data mangling. > +}; > + > +/* > + * Output ranges corresponding to DAC_CONFIG[3:0] bits. > + * Defined by the following symbolic identifiers: > + * AD5413_RANGE_PLUSMINUS_10_5V - ±10.5 V voltage range > + * AD5413_RANGE_0MA_24MA - 0 mA to 24 mA current range > + */ > +enum ad5413_output_range { > + AD5413_RANGE_PLUSMINUS_10_5V = 0x3, > + AD5413_RANGE_0MA_24MA = 0x9, > +}; > + > +static const struct ad5413_range ad5413_voltage_range[] = { { Given only one element, why the array? > + .reg = AD5413_RANGE_PLUSMINUS_10_5V, > + .min = -10500000, > + .max = 10500000, > +} }; > + > +static const struct ad5413_range ad5413_current_range[] = { { Same on this one. > + .reg = AD5413_RANGE_0MA_24MA, > + .min = 0, > + .max = 24000, > +} }; > +static int ad5413_spi_reg_read(struct ad5413_state *st, unsigned int addr, > + u16 *val) > +{ > + struct spi_transfer t[] = { > + { > + .tx_buf = &st->d32[0], > + .len = 4, > + .cs_change = 1, > + }, > + { > + .tx_buf = &st->d32[1], > + .rx_buf = &st->d32[2], > + .len = 4, > + }, > + }; > + u8 sel[3], nop[3]; > + int ret; > + > + /* Two-stage readback: select the register to read back. */ > + sel[0] = AD5413_WR_FLAG_MSK(AD5413_REG_TWO_STAGE_READBACK_SELECT); > + sel[1] = 0; > + sel[2] = addr; > + st->d32[0] = cpu_to_be32((sel[0] << 24) | (sel[1] << 16) | > + (sel[2] << 8) | > + crc8(st->crc8_tbl, sel, sizeof(sel), 0)); See below. I suspect similarly overly complicated endian dance. > + > + /* NOP frame clocks the selected register contents out on SDO. */ > + nop[0] = AD5413_WR_FLAG_MSK(AD5413_REG_NOP); > + nop[1] = 0; > + nop[2] = 0; > + st->d32[1] = cpu_to_be32((nop[0] << 24) | (nop[1] << 16) | > + (nop[2] << 8) | > + crc8(st->crc8_tbl, nop, sizeof(nop), 0)); > + > + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); > + if (ret < 0) > + return ret; > + > + /* > + * The readback CRC covers the live FAULT pin status bit, which can > + * toggle between capture and clock-out, so it is not validated here. > + */ > + *val = be32_to_cpu(st->d32[2]) >> 8; > + > + return 0; > +} > + > +static int ad5413_spi_reg_write(struct ad5413_state *st, unsigned int addr, > + unsigned int val) > +{ > + u8 buf[3] = { }; u8 buf[3] = { [0] = AD... > + > + buf[0] = AD5413_WR_FLAG_MSK(addr); > + buf[1] = val >> 8; > + buf[2] = val; This is fun as that's a put_unaligned_be16 which you then do another endian swap on. > + > + st->d32[0] = cpu_to_be32((buf[0] << 24) | (buf[1] << 16) | > + (buf[2] << 8) | Ok. I'm lost. In both cass we start with buf[0] = AD5413_WR_FLAG_MSK(addr), buf[1] = (val >> 8) & 0xff; buf[2] = val & 0xff; Then you build a native endian integer. So on a little endian cpu that means v[0] = crc8(buf..) v[1] = val & 0xff; v[2] = (val >> 8) & 0xff; v[3] = AD5413_WR_FLAG_MSK(addr) d32 is that swapped (pretend it's u8 array) d32[0] = AD5413_WR_FLAG_MSK(addr); d32[1] = (val >> 8) & 0xff; d32[2] = val & 0xff; d32[3] = crc8(buf..) on a big endian v[0] = AD5413_WR_FLAG_MSK(addr) v[1] = (val >> 8) & 0xff; v[2] = val & 0xff; v[3] = crc8(buf..) d32 is not swapped. (again pretend u8 array d32[0] = AD5413_WR_FLAG_MSK(addr); d32[1] = (val >> 8) & 0xff; d32[2] = val & 0xff; d32[3] = crc8(buf..) (snap which is good). Fun bit, is the 3 bytes are right for computing the crc8 so... This is all equivalent of: static int ad5413_spi_reg_write(struct ad5413_state *st, unsigned int addr, unsigned int val) { st->d8[0] = AD5413_WR_FLAG_MSK(addr); put_unaligned_be16(&st->d8[1], val); st->d8[3] = crc8(st->crc8_tbl, buf, 3, 0); return spi_write(st->spi, st->d8, sizeof(st->d8)); } As long as you have a st->d8[4] array to use (and I didn't unwind that all incorrectly). > + crc8(st->crc8_tbl, buf, sizeof(buf), 0)); > + > + return spi_write(st->spi, &st->d32[0], sizeof(st->d32[0])); > +} > + > +static int ad5413_spi_write_mask(struct ad5413_state *st, unsigned int addr, > + unsigned long mask, unsigned int val) > +{ > + u16 regval; > + int ret; > + > + ret = ad5413_spi_reg_read(st, addr, ®val); > + if (ret) > + return ret; > + > + return ad5413_spi_reg_write(st, addr, (regval & ~mask) | val); > +} > + > +static int ad5413_slew_rate_config(struct ad5413_state *st) > +{ > + unsigned int sr_clk_idx = 0, sr_step_idx = 0; > + int i, res; > + s64 diff_new, diff_old = S64_MAX; > + u64 sr_step, calc_slew_time; > + > + /* > + * The slew time can be determined by using the formula: > + * Slew Time = (Full Scale Out / (Step Size x Update Clk Freq)) > + * where Slew time is expressed in microseconds > + * Given the desired slew time, the following algorithm determines the > + * best match for the step size and the update clock frequency. > + */ > + for (i = 0; i < ARRAY_SIZE(ad5413_sr_clk); i++) { for (int i = 0; is acceptable these days and makes the scope explicit. > + /* > + * Go through each valid update clock freq and determine a raw > + * value for the step size by using the formula: > + * Step Size = Full Scale Out / (Update Clk Freq * Slew Time) > + */ > + sr_step = div64_u64(AD5413_FULL_SCALE_MICRO, > + (u64)ad5413_sr_clk[i] * st->slew_time); > + /* > + * After a raw value for step size was determined, find the > + * closest valid match > + */ > + res = find_closest(sr_step, ad5413_sr_step, ARRAY_SIZE(ad5413_sr_step)); > + > + /* Calculate the slew time */ > + calc_slew_time = div_u64(AD5413_FULL_SCALE_MICRO, > + ad5413_sr_step[res] * ad5413_sr_clk[i]); > + /* > + * Determine with how many microseconds the calculated slew time > + * is different from the desired slew time and store the diff > + * for the next iteration > + */ > + diff_new = abs(st->slew_time - calc_slew_time); > + if (diff_new < diff_old) { > + diff_old = diff_new; > + sr_clk_idx = i; > + sr_step_idx = res; > + } > + } > + > + return ad5413_slew_rate_set(st, sr_clk_idx, sr_step_idx); > +} > + > +static int ad5413_internal_buffers_en(struct ad5413_state *st, bool enable) > +{ > + int ret; > + > + ret = ad5413_spi_write_mask(st, > + AD5413_REG_DAC_CONFIG, > + AD5413_REG_DAC_CONFIG_INT_EN_MSK, > + FIELD_PREP(AD5413_REG_DAC_CONFIG_INT_EN_MSK, > + enable)); > + if (ret < 0) > + return ret; I'd not expect a function with that name to return > 0 so perhaps just if (ret)? > + > + /* Wait to allow time for the internal calibrations to complete */ The function name and parameters are probably clear enough that you can drop the comment. > + return ad5413_wait_for_task_complete(st, > + AD5413_REG_DIGITAL_DIAG_RESULTS, > + AD5413_REG_DIGITAL_DIAG_CAL_MEM_UNREFRESHED_MSK); > +} > +static int ad5413_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, int *val, > + int *val2, long info) > +{ > + struct ad5413_state *st = iio_priv(indio_dev); > + int max, min, ret; > + u16 regval; > + > + switch (info) { > + case IIO_CHAN_INFO_RAW: { > + guard(mutex)(&st->lock); > + ret = ad5413_spi_reg_read(st, AD5413_REG_DAC_INPUT, ®val); > + if (ret) > + return ret; > + > + /* 14-bit DAC data is in bits [15:2] of the register. */ > + *val = regval >> 2; > + return IIO_VAL_INT; > + } > + case IIO_CHAN_INFO_SCALE: > + min = st->out_range->min; > + max = st->out_range->max; > + *val = (max - min) / 1000; > + *val2 = 14; > + return IIO_VAL_FRACTIONAL_LOG2; > + case IIO_CHAN_INFO_OFFSET: > + min = st->out_range->min; > + max = st->out_range->max; > + *val = div_s64((s64)min * BIT(14), max - min); > + return IIO_VAL_INT; > + default: > + return -EINVAL; > + } > +} > + > +static int ad5413_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, int val, int val2, > + long info) > +{ > + struct ad5413_state *st = iio_priv(indio_dev); > + int ret; > + > + switch (info) { > + case IIO_CHAN_INFO_RAW: { > + guard(mutex)(&st->lock); > + /* 14-bit DAC data is in bits [15:2] of the register. */ > + ret = ad5413_spi_reg_write(st, AD5413_REG_DAC_INPUT, val << 2); > + return ret; return ad54... > + } > + default: > + return -EINVAL; > + } > +} > +static ssize_t ad5413_write_powerdown(struct iio_dev *indio_dev, uintptr_t priv, > + struct iio_chan_spec const *chan, > + const char *buf, size_t len) > +{ > + struct ad5413_state *st = iio_priv(indio_dev); > + bool pwr_down; > + unsigned int dac_config_mode, val; > + unsigned long dac_config_msk; Where nothing else dictates ordering, reverse xmas tree preferred. > + int ret; > + > + ret = kstrtobool(buf, &pwr_down); > + if (ret) > + return ret; > + > + val = pwr_down ? 0 : 1; > + > + dac_config_mode = FIELD_PREP(AD5413_REG_DAC_CONFIG_OUT_EN_MSK, (val)) | > + FIELD_PREP(AD5413_REG_DAC_CONFIG_INT_EN_MSK, (val)); > + dac_config_msk = AD5413_REG_DAC_CONFIG_OUT_EN_MSK | > + AD5413_REG_DAC_CONFIG_INT_EN_MSK; > + > + guard(mutex)(&st->lock); > + ret = ad5413_spi_write_mask(st, AD5413_REG_DAC_CONFIG, dac_config_msk, > + dac_config_mode); > + if (ret < 0) > + return ret; > + > + st->pwr_down = pwr_down; > + > + return len; > +} > +static const struct iio_chan_spec ad5413_voltage_ch[] = { > + AD5413_DAC_CHAN(IIO_VOLTAGE), > +}; > + > +static const struct iio_chan_spec ad5413_current_ch[] = { > + AD5413_DAC_CHAN(IIO_CURRENT), Why an array? For these, given you are just copying them in place maybe use a macro that has (struct iio_chan_spec) { values } so that you can just have the assignment in code without the need for these two template arrays. > +}; > + > +static int ad5413_parse_fw(struct ad5413_state *st) > +{ > + struct device *dev = &st->spi->dev; > + int ret; > + > + if (device_property_read_bool(dev, "adi,chan-current-mode")) > + st->out_range = &ad5413_current_range[0]; I ask this above, but it was motivated by this. If there is only one structure why do we want an array for these? > + else > + st->out_range = &ad5413_voltage_range[0]; > + > + st->slew_time = 0; > + if (device_property_present(dev, "adi,slew-time-us")) { > + ret = device_property_read_u32(dev, > + "adi,slew-time-us", > + &st->slew_time); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to read adi,slew-time-us\n"); > + > + if (st->slew_time > 1 * MICRO) > + return dev_err_probe(dev, -EINVAL, "adi,slew-time-us out of range\n"); > + } > + > + return 0; > +} > + > +static int ad5413_init(struct ad5413_state *st) > +{ > + struct reset_control *reset; > + u16 regval; > + int ret; > + > + reset = devm_reset_control_get_optional_exclusive(&st->spi->dev, NULL); > + if (IS_ERR(reset)) > + return PTR_ERR(reset); > + > + if (reset) { > + /* Hard reset via the reset controller */ > + ret = reset_control_assert(reset); > + if (ret) > + return ret; > + > + fsleep(100); For most sleeps a comment that refers to the datasheet is useful so if people hit problems in future they understand why the sleeps take particular values. If the datasheet is uninformative on a particular value just say something about having found it to work in practice. > + > + ret = reset_control_deassert(reset); > + if (ret) > + return ret; > + > + fsleep(100); > + } else { > + /* > + * CRC is enabled by default after power-on/reset. The SPI > + * helpers always append the CRC byte, so the device accepts > + * these soft-reset key frames without any bootstrap step. > + */ > + ret = ad5413_spi_reg_write(st, AD5413_REG_KEY, > + AD5413_REG_KEY_CODE_RESET_1); > + if (ret) > + return ret; > + > + ret = ad5413_spi_reg_write(st, AD5413_REG_KEY, > + AD5413_REG_KEY_CODE_RESET_2); > + if (ret) > + return ret; > + > + fsleep(100); > + } > + > + ret = ad5413_calib_mem_refresh(st); > + if (ret < 0) > + return ret; > + > + ret = ad5413_spi_reg_read(st, AD5413_REG_DIGITAL_DIAG_RESULTS, ®val); > + if (ret) > + return ret; > + > + ret = ad5413_spi_reg_write(st, AD5413_REG_DIGITAL_DIAG_RESULTS, regval); Whilst it doesn't matter in practice, I'd rather this only wrote the write 1 to clear bits rather than also writing the read only ones up the top. Acts as documentation about what this is doing. > + if (ret < 0) > + return ret; > + > + ret = ad5413_set_out_range(st, st->out_range->reg); > + if (ret < 0) > + return ret; > + > + if (st->slew_time) { > + ret = ad5413_slew_rate_config(st); > + if (ret < 0) > + return ret; > + } > + > + ret = ad5413_internal_buffers_en(st, 1); > + if (ret < 0) > + return ret; > + > + return ad5413_spi_write_mask(st, > + AD5413_REG_DAC_CONFIG, > + AD5413_REG_DAC_CONFIG_OUT_EN_MSK, > + FIELD_PREP(AD5413_REG_DAC_CONFIG_OUT_EN_MSK, 1)); > +} } ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-16 23:40 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-10-16 19:23 [PATCH 0/2] iio: dac: add support for AD5413 Bruce Tsao via B4 Relay 2026-10-16 19:23 ` [PATCH 1/2] dt-bindings: iio: dac: Document Analog Devices AD5413 single-channel DAC Bruce Tsao via B4 Relay 2026-10-16 19:36 ` sashiko-bot 2026-10-16 23:02 ` Jonathan Cameron 2026-10-16 19:23 ` [PATCH 2/2] iio: dac: add AD5413 driver Bruce Tsao via B4 Relay 2026-10-16 19:33 ` sashiko-bot 2026-10-16 23:40 ` Jonathan Cameron
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox