mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org>
To: linux-iio@vger.kernel.org, linux-kernel@vger.kernel.org,
	 linux@analog.com, devicetree@vger.kernel.org,
	linux-pwm@vger.kernel.org,  linux-doc@vger.kernel.org,
	 Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Cc: "Olivier Moysan" <olivier.moysan@foss.st.com>,
	"Jonathan Cameron" <jic23@kernel.org>,
	"David Lechner" <dlechner@baylibre.com>,
	"Andy Shevchenko" <andy@kernel.org>,
	"Michael Hennerich" <Michael.Hennerich@analog.com>,
	"Rob Herring" <robh@kernel.org>,
	"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
	"Conor Dooley" <conor+dt@kernel.org>,
	"Uwe Kleine-König" <ukleinek@kernel.org>,
	"Jonathan Corbet" <corbet@lwn.net>,
	"Shuah Khan" <skhan@linuxfoundation.org>,
	"Randy Dunlap" <rdunlap@infradead.org>,
	"Rodrigo Alencar" <rodrigo.alencar@analog.com>
Subject: [PATCH v2 06/10] iio: frequency: ad9910: add axi backend driver
Date: Thu, 22 Oct 2026 12:19:04 -0300	[thread overview]
Message-ID: <20261022-ad9910-backend-support-v2-6-0ff7f0bff326@analog.com> (raw)
In-Reply-To: <20261022-ad9910-backend-support-v2-0-0ff7f0bff326@analog.com>

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Add the driver for the AD9910 AXI backend IP core, which interfaces with
the data and control pins of the AD9910 DDS. It registers as an IIO
backend that provides:

- a DMA output buffer that streams data into the parallel data port;
- the parallel data destination (F[1:0] pins) and data rate, derived
  from the PDCLK frequency;
- the PROFILE[2:0] pins control, through profile channel enables.

It also registers a PWM chip with a single output driving the DRCTL
pin, which the AD9910 driver uses to control the digital ramp generator
direction. The PWM implements the waveform callbacks, with period, duty
length and duty offset programmed in SYNC_CLK cycles. The duty offset
is realized by a delay applied once, before DRCTL starts toggling. The
waveform is reprogrammed when the SYNC_CLK rate changes, so that the PWM
output keeps its timing.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 MAINTAINERS                                |   1 +
 drivers/iio/frequency/Kconfig              |  17 +
 drivers/iio/frequency/Makefile             |   1 +
 drivers/iio/frequency/ad9910-axi-backend.c | 525 +++++++++++++++++++++++++++++
 4 files changed, 544 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 64aa7b539ca3..abfbbf0b6b4e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1734,6 +1734,7 @@ F:	Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910
 F:	Documentation/devicetree/bindings/iio/frequency/adi,ad9910-axi-backend.yaml
 F:	Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
 F:	Documentation/iio/ad9910.rst
+F:	drivers/iio/frequency/ad9910-axi-backend.c
 F:	drivers/iio/frequency/ad9910.c
 F:	drivers/iio/frequency/ad9910.h
 
diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig
index 994d199080eb..f7f5c3cce005 100644
--- a/drivers/iio/frequency/Kconfig
+++ b/drivers/iio/frequency/Kconfig
@@ -42,6 +42,23 @@ config AD9910
 	  To compile this driver as a module, choose M here: the
 	  module will be called ad9910.
 
+config AD9910_AXI_BACKEND
+	tristate "Analog Devices AD9910 AXI Backend driver"
+	depends on AD9910
+	depends on HAS_IOMEM && PWM
+	select IIO_BACKEND
+	select IIO_BUFFER
+	select IIO_BUFFER_DMAENGINE
+	select REGMAP_MMIO
+	help
+	  Say yes here to build support for the AXI Backend Driver for the
+	  Analog Devices AD9910 DDS. The IP core is used to drive digital ramp
+	  control pins and interface with the parallel data port of the AD9910
+	  device.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ad9910-axi-backend.
+
 endmenu
 
 #
diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile
index e3b1d36ed620..bfac9e205e81 100644
--- a/drivers/iio/frequency/Makefile
+++ b/drivers/iio/frequency/Makefile
@@ -6,6 +6,7 @@
 # When adding new entries keep the list in alphabetical order
 obj-$(CONFIG_AD9523) += ad9523.o
 obj-$(CONFIG_AD9910) += ad9910.o
+obj-$(CONFIG_AD9910_AXI_BACKEND) += ad9910-axi-backend.o
 obj-$(CONFIG_ADF41513) += adf41513.o
 obj-$(CONFIG_ADF4350) += adf4350.o
 obj-$(CONFIG_ADF4371) += adf4371.o
diff --git a/drivers/iio/frequency/ad9910-axi-backend.c b/drivers/iio/frequency/ad9910-axi-backend.c
new file mode 100644
index 000000000000..f5374aeb2afe
--- /dev/null
+++ b/drivers/iio/frequency/ad9910-axi-backend.c
@@ -0,0 +1,525 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AD9910 AXI Backend driver
+ *
+ * Copyright 2026 Analog Devices Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/kstrtox.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#include <linux/iio/backend.h>
+#include <linux/iio/buffer-dmaengine.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+
+#include "ad9910.h"
+
+/* Register addresses */
+#define AD9910_AXI_REG_RESET		0x040
+#define AD9910_AXI_REG_DRG		0x084
+#define AD9910_AXI_REG_PROFILE		0x088
+#define AD9910_AXI_REG_DRCTL_PERIOD	0x08C
+#define AD9910_AXI_REG_DRCTL_WIDTH	0x090
+#define AD9910_AXI_REG_DRCTL_DELAY	0x094
+#define AD9910_AXI_REG_BURST_COUNT	0x098
+#define AD9910_AXI_REG_BURST_DELAY	0x09C
+#define AD9910_AXI_REG_RAMP_CFG		0x0A0
+
+#define AD9910_AXI_REG_UPDATE_CTRL	0x104
+#define AD9910_AXI_REG_PAR_RATE		0x108
+#define AD9910_AXI_REG_F_CFG		0x10C
+
+#define AD9910_AXI_REG_RESET_CORE_MSK		BIT(0)
+
+#define AD9910_AXI_REG_DRG_DRCTL_MSK		BIT(2)
+#define AD9910_AXI_REG_DRG_TOGGLE_EN_MSK	BIT(3)
+
+#define AD9910_AXI_REG_RAMP_CFG_ONE_SHOT_MSK	GENMASK(1, 0)
+#define AD9910_AXI_REG_BURST_COUNT_MSK		GENMASK(19, 0)
+
+#define AD9910_AXI_REG_UPDATE_RATE_MSK		BIT(0)
+#define AD9910_AXI_REG_UPDATE_PAR_IF_EN_MSK	BIT(1)
+#define AD9910_AXI_REG_UPDATE_EXT_TRIG_MSK	BIT(2)
+
+#define AD9910_AXI_REG_PROFILE_MSK		GENMASK(2, 0)
+#define AD9910_AXI_REG_IO_UPDATE_MSK		BIT(0)
+#define AD9910_AXI_REG_F_CFG_MSK		GENMASK(1, 0)
+
+#define AD9910_AXI_PD_CLK_MAX_FREQ_HZ		(250ULL * HZ_PER_MHZ)
+#define AD9910_AXI_PAR_RATE_MAX			U16_MAX
+#define AD9910_AXI_ONE_SHOT_BURST		1
+
+struct ad9910_axi_state {
+	struct device *dev;
+	struct regmap *regmap;
+	/*
+	 * lock to protect multiple accesses to the device registers and global
+	 * data/variables.
+	 */
+	struct mutex lock;
+	u64 pd_clk_freq_uhz;
+	/* last applied DRCTL waveform, kept when the SYNC_CLK rate changes */
+	struct pwm_waveform drctl_wf;
+};
+
+/* DRCTL waveform in SYNC_CLK cycles, a zero period means disabled */
+struct ad9910_axi_drctl_wfhw {
+	u32 period;
+	u32 width;
+	u32 delay;
+};
+
+static u32 ad9910_axi_ns_to_cycles(struct ad9910_axi_state *st, u64 ns)
+{
+	return min(mul_u64_u64_div_u64(ns, st->pd_clk_freq_uhz,
+				       1ULL * NANO * MICROHZ_PER_HZ), U32_MAX);
+}
+
+static u64 ad9910_axi_cycles_to_ns(struct ad9910_axi_state *st, u32 cycles)
+{
+	return mul_u64_u64_div_u64_roundup((u64)cycles * NANO, MICROHZ_PER_HZ,
+					   st->pd_clk_freq_uhz);
+}
+
+static int ad9910_axi_drctl_tohw(struct ad9910_axi_state *st,
+				 const struct pwm_waveform *wf,
+				 struct ad9910_axi_drctl_wfhw *wfhw)
+{
+	if (!wf->period_length_ns) {
+		*wfhw = (struct ad9910_axi_drctl_wfhw){ };
+		return 0;
+	}
+
+	wfhw->period = ad9910_axi_ns_to_cycles(st, wf->period_length_ns);
+	if (!wfhw->period) {
+		/* round up to the shortest period, with no duty and no delay */
+		*wfhw = (struct ad9910_axi_drctl_wfhw){ .period = 1 };
+		return 1;
+	}
+
+	wfhw->width = min(ad9910_axi_ns_to_cycles(st, wf->duty_length_ns),
+			  wfhw->period);
+	/*
+	 * The delay is applied once, before DRCTL starts toggling, which
+	 * results in the duty offset of the periodic waveform. A delay of a
+	 * whole period is the same as no delay at all.
+	 */
+	wfhw->delay = ad9910_axi_ns_to_cycles(st, wf->duty_offset_ns);
+	if (wfhw->delay >= wfhw->period)
+		wfhw->delay = 0;
+
+	return 0;
+}
+
+static void ad9910_axi_drctl_fromhw(struct ad9910_axi_state *st,
+				    const struct ad9910_axi_drctl_wfhw *wfhw,
+				    struct pwm_waveform *wf)
+{
+	if (!wfhw->period) {
+		*wf = (struct pwm_waveform){ };
+		return;
+	}
+
+	wf->period_length_ns = ad9910_axi_cycles_to_ns(st, wfhw->period);
+	wf->duty_length_ns = ad9910_axi_cycles_to_ns(st, wfhw->width);
+	wf->duty_offset_ns = ad9910_axi_cycles_to_ns(st, wfhw->delay);
+}
+
+static int ad9910_axi_drctl_write(struct ad9910_axi_state *st,
+				  const struct ad9910_axi_drctl_wfhw *wfhw)
+{
+	int ret;
+
+	/*
+	 * DRCTL toggling restarts, applying the delay, when enabled or when
+	 * its period or width changes. Changing the delay alone does not
+	 * restart it, so disable toggling while updating the waveform.
+	 */
+	ret = regmap_clear_bits(st->regmap, AD9910_AXI_REG_DRG,
+				AD9910_AXI_REG_DRG_TOGGLE_EN_MSK);
+	if (ret || !wfhw->period)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD9910_AXI_REG_DRCTL_DELAY, wfhw->delay);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD9910_AXI_REG_DRCTL_WIDTH, wfhw->width);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(st->regmap, AD9910_AXI_REG_DRCTL_PERIOD, wfhw->period);
+	if (ret)
+		return ret;
+
+	/*
+	 * Register values reach the SYNC_CLK domain in snapshots, taken every
+	 * 64 AXI clock cycles. Make sure toggling being disabled is captured
+	 * by one of them before enabling it again.
+	 */
+	fsleep(2);
+
+	return regmap_set_bits(st->regmap, AD9910_AXI_REG_DRG,
+			       AD9910_AXI_REG_DRG_TOGGLE_EN_MSK);
+}
+
+static int ad9910_axi_chan_enable(struct iio_backend *back, unsigned int chan)
+{
+	struct ad9910_axi_state *st = iio_backend_get_priv(back);
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	switch (chan) {
+	/*
+	 * Profiles are mutually exclusive, as the PROFILE[2:0] pins always
+	 * select one of them. Enabling a profile channel selects it, which
+	 * implicitly disables the previously selected one, hence there is
+	 * nothing to do when disabling a profile channel.
+	 */
+	case AD9910_CHAN_IDX_PROFILE_0 ... AD9910_CHAN_IDX_PROFILE_7:
+		chan -= AD9910_CHAN_IDX_PROFILE_0;
+		return regmap_update_bits(st->regmap, AD9910_AXI_REG_PROFILE,
+					  AD9910_AXI_REG_PROFILE_MSK,
+					  FIELD_PREP(AD9910_AXI_REG_PROFILE_MSK, chan));
+	case AD9910_CHAN_IDX_PARALLEL_AMP ... AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE:
+		/*
+		 * F[1:0] destination follows the parallel channel order:
+		 * amplitude, phase, frequency and polar.
+		 */
+		chan = min(chan - AD9910_CHAN_IDX_PARALLEL_AMP,
+			   FIELD_MAX(AD9910_AXI_REG_F_CFG_MSK));
+		ret = regmap_update_bits(st->regmap, AD9910_AXI_REG_F_CFG,
+					 AD9910_AXI_REG_F_CFG_MSK, chan);
+		if (ret)
+			return ret;
+
+		return regmap_set_bits(st->regmap, AD9910_AXI_REG_UPDATE_CTRL,
+				       AD9910_AXI_REG_UPDATE_PAR_IF_EN_MSK);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad9910_axi_chan_disable(struct iio_backend *back, unsigned int chan)
+{
+	struct ad9910_axi_state *st = iio_backend_get_priv(back);
+
+	switch (chan) {
+	case AD9910_CHAN_IDX_PROFILE_0 ... AD9910_CHAN_IDX_PROFILE_7:
+		return 0;
+	case AD9910_CHAN_IDX_PARALLEL_AMP ... AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE:
+		return regmap_clear_bits(st->regmap, AD9910_AXI_REG_UPDATE_CTRL,
+					 AD9910_AXI_REG_UPDATE_PAR_IF_EN_MSK);
+	default:
+		return -EINVAL;
+	}
+}
+
+static struct iio_buffer *ad9910_axi_request_buffer(struct iio_backend *back,
+						    struct iio_dev *indio_dev)
+{
+	struct ad9910_axi_state *st = iio_backend_get_priv(back);
+	const char *dma_name;
+
+	if (device_property_read_string(st->dev, "dma-names", &dma_name))
+		dma_name = "tx";
+
+	return iio_dmaengine_buffer_setup_ext(st->dev, indio_dev, dma_name,
+					      IIO_BUFFER_DIRECTION_OUT);
+}
+
+static void ad9910_axi_free_buffer(struct iio_backend *back,
+				   struct iio_buffer *buffer)
+{
+	iio_dmaengine_buffer_teardown(buffer);
+}
+
+static int ad9910_axi_set_sample_rate(struct iio_backend *back,
+				      unsigned int chan, u64 sample_rate)
+{
+	struct ad9910_axi_state *st = iio_backend_get_priv(back);
+	struct ad9910_axi_drctl_wfhw wfhw;
+	u64 div, pd_clk_freq_uhz;
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	switch (chan) {
+	case AD9910_CHAN_IDX_PHY:
+		if (!sample_rate || sample_rate > 4 * AD9910_AXI_PD_CLK_MAX_FREQ_HZ)
+			return -EINVAL;
+
+		pd_clk_freq_uhz = sample_rate * MICROHZ_PER_HZ >> 2;
+		if (pd_clk_freq_uhz == st->pd_clk_freq_uhz)
+			return 0;
+
+		st->pd_clk_freq_uhz = pd_clk_freq_uhz;
+		if (!st->drctl_wf.period_length_ns)
+			return 0;
+
+		/*
+		 * DRCTL is clocked by SYNC_CLK, reprogram it so that the PWM
+		 * output keeps the applied waveform.
+		 */
+		ad9910_axi_drctl_tohw(st, &st->drctl_wf, &wfhw);
+		return ad9910_axi_drctl_write(st, &wfhw);
+	case AD9910_CHAN_IDX_PARALLEL_AMP ... AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE:
+		if (!sample_rate || sample_rate > AD9910_AXI_PD_CLK_MAX_FREQ_HZ)
+			return -EINVAL;
+
+		sample_rate *= MICROHZ_PER_HZ;
+		div = DIV64_U64_ROUND_CLOSEST(st->pd_clk_freq_uhz, sample_rate);
+		div = clamp(div, 1U, AD9910_AXI_PAR_RATE_MAX);
+		ret = regmap_write(st->regmap, AD9910_AXI_REG_PAR_RATE, div - 1);
+		if (ret)
+			return ret;
+
+		return regmap_set_bits(st->regmap, AD9910_AXI_REG_UPDATE_CTRL,
+				       AD9910_AXI_REG_UPDATE_RATE_MSK);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad9910_axi_read_raw(struct iio_backend *back,
+			       struct iio_chan_spec const *chan, int *val,
+			       int *val2, long mask)
+{
+	struct ad9910_axi_state *st = iio_backend_get_priv(back);
+	u32 div;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		switch (chan->address) {
+		case AD9910_CHAN_IDX_PARALLEL_AMP ... AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE: {
+			guard(mutex)(&st->lock);
+
+			ret = regmap_read(st->regmap, AD9910_AXI_REG_PAR_RATE,
+					  &div);
+			if (ret)
+				return ret;
+
+			iio_val_s64_decompose(div64_u64(st->pd_clk_freq_uhz,
+							(u64)div + 1),
+					      val, val2);
+			return IIO_VAL_DECIMAL64_MICRO;
+		}
+		default:
+			return -EINVAL;
+		}
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ad9910_axi_reg_access(struct iio_backend *back, unsigned int reg,
+				 unsigned int writeval, unsigned int *readval)
+{
+	struct ad9910_axi_state *st = iio_backend_get_priv(back);
+
+	if (readval)
+		return regmap_read(st->regmap, reg, readval);
+
+	return regmap_write(st->regmap, reg, writeval);
+}
+
+static const struct iio_backend_ops ad9910_axi_iio_back_ops = {
+	.chan_enable = ad9910_axi_chan_enable,
+	.chan_disable = ad9910_axi_chan_disable,
+	.request_buffer = ad9910_axi_request_buffer,
+	.free_buffer = ad9910_axi_free_buffer,
+	.set_sample_rate = ad9910_axi_set_sample_rate,
+	.read_raw = ad9910_axi_read_raw,
+	.debugfs_reg_access = iio_backend_debugfs_ptr(ad9910_axi_reg_access),
+};
+
+static const struct iio_backend_info ad9910_axi_back_info = {
+	.name = "ad9910-axi",
+	.ops = &ad9910_axi_iio_back_ops,
+};
+
+static const struct regmap_config ad9910_axi_regmap_config = {
+	.val_bits = 32,
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.max_register = 0x0800,
+};
+
+static int ad9910_axi_pwm_round_waveform_tohw(struct pwm_chip *chip,
+					      struct pwm_device *pwm,
+					      const struct pwm_waveform *wf,
+					      void *wfhw)
+{
+	struct ad9910_axi_state *st = pwmchip_get_drvdata(chip);
+
+	guard(mutex)(&st->lock);
+
+	return ad9910_axi_drctl_tohw(st, wf, wfhw);
+}
+
+static int ad9910_axi_pwm_round_waveform_fromhw(struct pwm_chip *chip,
+						struct pwm_device *pwm,
+						const void *wfhw,
+						struct pwm_waveform *wf)
+{
+	struct ad9910_axi_state *st = pwmchip_get_drvdata(chip);
+
+	guard(mutex)(&st->lock);
+
+	ad9910_axi_drctl_fromhw(st, wfhw, wf);
+
+	return 0;
+}
+
+static int ad9910_axi_pwm_read_waveform(struct pwm_chip *chip,
+					struct pwm_device *pwm, void *_wfhw)
+{
+	struct ad9910_axi_state *st = pwmchip_get_drvdata(chip);
+	struct ad9910_axi_drctl_wfhw *wfhw = _wfhw;
+	u32 drg;
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	ret = regmap_read(st->regmap, AD9910_AXI_REG_DRG, &drg);
+	if (ret)
+		return ret;
+
+	if (!FIELD_GET(AD9910_AXI_REG_DRG_TOGGLE_EN_MSK, drg)) {
+		*wfhw = (struct ad9910_axi_drctl_wfhw){ };
+		return 0;
+	}
+
+	ret = regmap_read(st->regmap, AD9910_AXI_REG_DRCTL_PERIOD, &wfhw->period);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(st->regmap, AD9910_AXI_REG_DRCTL_WIDTH, &wfhw->width);
+	if (ret)
+		return ret;
+
+	return regmap_read(st->regmap, AD9910_AXI_REG_DRCTL_DELAY, &wfhw->delay);
+}
+
+static int ad9910_axi_pwm_write_waveform(struct pwm_chip *chip,
+					 struct pwm_device *pwm,
+					 const void *_wfhw)
+{
+	struct ad9910_axi_state *st = pwmchip_get_drvdata(chip);
+	const struct ad9910_axi_drctl_wfhw *wfhw = _wfhw;
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	ret = ad9910_axi_drctl_write(st, wfhw);
+	if (ret)
+		return ret;
+
+	ad9910_axi_drctl_fromhw(st, wfhw, &st->drctl_wf);
+
+	return 0;
+}
+
+static const struct pwm_ops ad9910_axi_pwm_ops = {
+	.sizeof_wfhw = sizeof(struct ad9910_axi_drctl_wfhw),
+	.round_waveform_tohw = ad9910_axi_pwm_round_waveform_tohw,
+	.round_waveform_fromhw = ad9910_axi_pwm_round_waveform_fromhw,
+	.read_waveform = ad9910_axi_pwm_read_waveform,
+	.write_waveform = ad9910_axi_pwm_write_waveform,
+};
+
+static int ad9910_axi_setup(struct ad9910_axi_state *st)
+{
+	int ret;
+
+	st->pd_clk_freq_uhz = AD9910_AXI_PD_CLK_MAX_FREQ_HZ * MICROHZ_PER_HZ;
+
+	ret = regmap_set_bits(st->regmap, AD9910_AXI_REG_RESET,
+			      AD9910_AXI_REG_RESET_CORE_MSK);
+	if (ret)
+		return ret;
+
+	return regmap_clear_bits(st->regmap, AD9910_AXI_REG_RESET,
+				 AD9910_AXI_REG_RESET_CORE_MSK);
+}
+
+static int ad9910_axi_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct ad9910_axi_state *st;
+	struct pwm_chip *chip;
+	void __iomem *base;
+	struct clk *clk;
+	int ret;
+
+	chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*st));
+	if (IS_ERR(chip))
+		return PTR_ERR(chip);
+
+	st = pwmchip_get_drvdata(chip);
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	clk = devm_clk_get_enabled(dev, NULL);
+	if (IS_ERR(clk))
+		return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n");
+
+	st->dev = dev;
+	st->regmap = devm_regmap_init_mmio(dev, base,
+					   &ad9910_axi_regmap_config);
+	if (IS_ERR(st->regmap))
+		return dev_err_probe(dev, PTR_ERR(st->regmap),
+				     "failed to init register map\n");
+
+	ret = devm_mutex_init(dev, &st->lock);
+	if (ret)
+		return ret;
+
+	ret = ad9910_axi_setup(st);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to setup device\n");
+
+	chip->ops = &ad9910_axi_pwm_ops;
+
+	ret = devm_pwmchip_add(dev, chip);
+	if (ret)
+		return dev_err_probe(dev, ret, "Could not register PWM chip\n");
+
+	return devm_iio_backend_register(dev, &ad9910_axi_back_info, st);
+}
+
+static const struct of_device_id ad9910_axi_of_match[] = {
+	{ .compatible = "adi,ad9910-axi-backend" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ad9910_axi_of_match);
+
+static struct platform_driver ad9910_axi_driver = {
+	.driver = {
+		.name = "ad9910-axi-backend",
+		.of_match_table = ad9910_axi_of_match,
+	},
+	.probe = ad9910_axi_probe,
+};
+module_platform_driver(ad9910_axi_driver);
+
+MODULE_AUTHOR("Rodrigo Alencar <rodrigo.alencar@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD9910 AXI Backend driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
+MODULE_IMPORT_NS("IIO_BACKEND");

-- 
2.43.0



  parent reply	other threads:[~2026-10-22 15:19 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-22 15:18 [PATCH v2 00/10] iio: frequency: ad9910: add IIO backend support Rodrigo Alencar via B4 Relay
2026-10-22 15:18 ` [PATCH v2 01/10] iio: frequency: ad9910: powerdown fix Rodrigo Alencar via B4 Relay
2026-10-22 15:19 ` [PATCH v2 02/10] iio: backend: add devm_iio_backend_get_optional() Rodrigo Alencar via B4 Relay
2026-10-22 15:19 ` [PATCH v2 03/10] dt-bindings: iio: frequency: add adi,ad9910-axi-backend Rodrigo Alencar via B4 Relay
2026-10-22 18:38   ` Conor Dooley
2026-10-23 11:15     ` Rodrigo Alencar
2026-10-23 17:39       ` Conor Dooley
2026-10-22 15:19 ` [PATCH v2 04/10] dt-bindings: iio: frequency: ad9910: add io-backends property Rodrigo Alencar via B4 Relay
2026-10-22 15:19 ` [PATCH v2 05/10] iio: frequency: ad9910: move channel indexes into a shared header Rodrigo Alencar via B4 Relay
2026-10-22 15:19 ` Rodrigo Alencar via B4 Relay [this message]
2026-10-26  0:29   ` [PATCH v2 06/10] iio: frequency: ad9910: add axi backend driver Jonathan Cameron
2026-10-30 12:05   ` Nuno Sá
2026-10-22 15:19 ` [PATCH v2 07/10] iio: frequency: ad9910: support drctl control Rodrigo Alencar via B4 Relay
2026-10-23 10:40   ` Rodrigo Alencar
2026-10-23 14:48     ` Nuno Sá
2026-10-22 15:19 ` [PATCH v2 08/10] iio: frequency: ad9910: support iio backend Rodrigo Alencar via B4 Relay
2026-10-30 12:05   ` Nuno Sá
2026-10-22 15:19 ` [PATCH v2 09/10] iio: ABI: add integration_time for output channels Rodrigo Alencar via B4 Relay
2026-10-22 15:19 ` [PATCH v2 10/10] docs: iio: ad9910: document DRCTL pin control Rodrigo Alencar via B4 Relay

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261022-ad9910-backend-support-v2-6-0ff7f0bff326@analog.com \
    --to=devnull+rodrigo.alencar.analog.com@kernel.org \
    --cc=Michael.Hennerich@analog.com \
    --cc=andy@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=corbet@lwn.net \
    --cc=devicetree@vger.kernel.org \
    --cc=dlechner@baylibre.com \
    --cc=jic23@kernel.org \
    --cc=jonathan.cameron@oss.qualcomm.com \
    --cc=krzk+dt@kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pwm@vger.kernel.org \
    --cc=linux@analog.com \
    --cc=olivier.moysan@foss.st.com \
    --cc=rdunlap@infradead.org \
    --cc=robh@kernel.org \
    --cc=rodrigo.alencar@analog.com \
    --cc=skhan@linuxfoundation.org \
    --cc=ukleinek@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®