diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
index 98e80e73fab5..d1634580f781 100644
--- a/drivers/iio/dac/Kconfig
+++ b/drivers/iio/dac/Kconfig
@@ -147,6 +147,27 @@ config AD9739A
 	  To compile this driver as a module, choose M here: the module will be
 	  called ad9739a.
 
+config ADI_AXI_DAC
+	tristate "Analog Devices Generic AXI DAC IP core driver"
+	select IIO_BUFFER
+	select IIO_BUFFER_DMAENGINE
+	select REGMAP_MMIO
+	select IIO_BACKEND
+	help
+      Say yes here to build support for Analog Devices Generic
+      AXI DAC IP core. The IP core is used for interfacing with
+      digital-to-analog (DAC) converters that require either a high-speed
+      serial interface (JESD204B/C) or a source synchronous parallel
+      interface (LVDS/CMOS).
+      Typically (for such devices) SPI will be used for configuration only,
+      while this IP core handles the streaming of data into memory via DMA.
+
+      Link: https://wiki.analog.com/resources/fpga/docs/axi_dac_ip
+      If unsure, say N (but it's safe to say "Y").
+
+      To compile this driver as a module, choose M here: the
+      module will be called adi-axi-dac.
+
 config LTC2688
 	tristate "Analog Devices LTC2688 DAC spi driver"
 	depends on SPI
diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile
index 7e39e0c218ca..8432a81a19dc 100644
--- a/drivers/iio/dac/Makefile
+++ b/drivers/iio/dac/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_AD7293) += ad7293.o
 obj-$(CONFIG_AD7303) += ad7303.o
 obj-$(CONFIG_AD8801) += ad8801.o
 obj-$(CONFIG_AD9739A) += ad9739a.o
+obj-$(CONFIG_ADI_AXI_DAC) += adi-axi-dac.o
 obj-$(CONFIG_CIO_DAC) += cio-dac.o
 obj-$(CONFIG_DPOT_DAC) += dpot-dac.o
 obj-$(CONFIG_DS4424) += ds4424.o
diff --git a/drivers/iio/dac/adi-axi-dac.c b/drivers/iio/dac/adi-axi-dac.c
new file mode 100644
index 000000000000..58d01ecc0618
--- /dev/null
+++ b/drivers/iio/dac/adi-axi-dac.c
@@ -0,0 +1,510 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices Generic AXI DAC IP core
+ * Link: https://wiki.analog.com/resources/fpga/docs/axi_dac_ip
+ *
+ * Copyright 2016-2024 Analog Devices Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#include <linux/fpga/adi-axi-common.h>
+
+#include <linux/iio/backend.h>
+#include <linux/iio/buffer-dmaengine.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+
+/*
+ * Register definitions:
+ *   https://wiki.analog.com/resources/fpga/docs/axi_dac_ip#register_map
+ */
+
+/* Base controls */
+#define AXI_DAC_REG_CONFIG		0x0C
+#define	   AXI_DAC_DISABLE		BIT(6)
+
+ /* DAC controls */
+#define AXI_DAC_REG_RSTN		0x0040
+#define   AXI_DAC_RSTN_CE_N		BIT(2)
+#define   AXI_DAC_RSTN_MMCM_RSTN	BIT(1)
+#define   AXI_DAC_RSTN_RSTN		BIT(0)
+#define AXI_DAC_REG_CNTRL_1		0x0044
+#define   AXI_DAC_SYNC			BIT(0)
+#define AXI_DAC_REG_RATECNTRL		0x004C
+#define AXI_DAC_DRP_STATUS		0x0074
+#define   AXI_DAC_DRP_LOCKED		BIT(17)
+
+/* DAC Channel controls */
+#define AXI_DAC_REG_CHAN_CNTRL_1(c)	(0x0400 + (c) * 0x40)
+#define AXI_DAC_REG_CHAN_CNTRL_3(c)	(0x0400 + (c) * 0x40)
+#define   AXI_DAC_SCALE_SIGN		BIT(15)
+#define   AXI_DAC_SCALE_INT		BIT(14)
+#define   AXI_DAC_SCALE_INT_NEG		GENMASK(15, 14)
+#define   AXI_DAC_SCALE_FRAC		GENMASK(13, 0)
+#define AXI_DAC_REG_CHAN_CNTRL_2(c)	(0x0404 + (c) * 0x40)
+#define AXI_DAC_REG_CHAN_CNTRL_4(c)	(0x040c + (c) * 0x40)
+#define   AXI_DAC_PHASE			GENMASK(31, 16)
+#define   AXI_DAC_FREQUENCY		GENMASK(15, 0)
+#define AXI_DAC_REG_CHAN_CNTRL_7(c)	(0x0418 + (c) * 0x40)
+#define   AXI_DAC_DATA_SEL		GENMASK(3, 0)
+
+/* 360 degrees in rad */
+#define AXI_DAC_2_PI_MEGA		6283190
+enum {
+	AXI_DAC_DATA_INTERNAL_TONE,
+	AXI_DAC_DATA_DMA = 2,
+};
+
+struct axi_dac_state {
+	struct regmap *regmap;
+	struct device *dev;
+	/* Lock for synchronizing device register access. */
+	struct mutex lock;
+	u64 dac_clk;
+	u32 reg_config;
+};
+
+static int axi_dac_frequency_read(struct iio_backend *back,
+				  const struct iio_chan_spec *chan, int *val,
+				  int *val2, unsigned int tone_idx,
+				  unsigned long long sample_freq)
+{
+	struct axi_dac_state *st = iio_backend_get_priv(back);
+	u32 reg, __val;
+	u16 freq;
+	int ret;
+
+	if (tone_idx > 1) {
+		dev_err(st->dev, "Not a valid tone: %u\n", tone_idx);
+		return -EINVAL;
+	}
+
+	if (tone_idx)
+		reg = AXI_DAC_REG_CHAN_CNTRL_4(chan->channel);
+	else
+		reg = AXI_DAC_REG_CHAN_CNTRL_2(chan->channel);
+
+	ret = regmap_read(st->regmap, reg, &__val);
+	if (ret)
+		return ret;
+
+	freq = FIELD_GET(AXI_DAC_FREQUENCY, __val);
+	*val = DIV_ROUND_CLOSEST_ULL(__val * sample_freq, BIT(16));
+
+	return 0;
+}
+
+static int axi_dac_scale_read(struct iio_backend *back,
+			      const struct iio_chan_spec *chan, int *val,
+			      int *val2, unsigned int tone_idx)
+{
+	struct axi_dac_state *st = iio_backend_get_priv(back);
+	u8 sign, integer;
+	u32 reg, __val;
+	u16 frac;
+	int ret;
+
+	if (tone_idx > 1) {
+		dev_err(st->dev, "Not a valid tone: %u\n", tone_idx);
+		return -EINVAL;
+	}
+
+	if (tone_idx)
+		reg = AXI_DAC_REG_CHAN_CNTRL_3(chan->channel);
+	else
+		reg = AXI_DAC_REG_CHAN_CNTRL_1(chan->channel);
+
+	ret = regmap_read(st->regmap, reg, &__val);
+	if (ret)
+		return ret;
+
+	frac = FIELD_GET(AXI_DAC_SCALE_FRAC, __val);
+	sign = FIELD_GET(AXI_DAC_SCALE_SIGN, __val);
+	integer = FIELD_GET(AXI_DAC_SCALE_INT, __val);
+
+	*val2 = DIV_ROUND_CLOSEST_ULL((u64)frac * MICRO, AXI_DAC_SCALE_INT);
+
+	if (integer && sign)
+		*val = -1;
+	else if (integer)
+		*val = 1;
+	else if (sign)
+		*val2 *= -1;
+
+	return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int axi_dac_phase_read(struct iio_backend *back,
+			      const struct iio_chan_spec *chan, int *val,
+			      int *val2, unsigned int tone_idx)
+{
+	struct axi_dac_state *st = iio_backend_get_priv(back);
+	unsigned int tmp;
+	u32 reg, __val;
+	u16 phase;
+	int ret;
+
+	if (tone_idx > 1) {
+		dev_err(st->dev, "Not a valid tone: %u\n", tone_idx);
+		return -EINVAL;
+	}
+
+	if (tone_idx)
+		reg = AXI_DAC_REG_CHAN_CNTRL_4(chan->channel);
+	else
+		reg = AXI_DAC_REG_CHAN_CNTRL_2(chan->channel);
+
+	ret = regmap_read(st->regmap, reg, &__val);
+	if (ret)
+		return ret;
+
+	phase = FIELD_GET(AXI_DAC_PHASE, __val);
+	tmp = DIV_ROUND_CLOSEST_ULL((u64)phase * AXI_DAC_2_PI_MEGA, U16_MAX);
+	*val = tmp / MICRO;
+	*val2 = tmp % MICRO;
+
+	return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int axi_dac_frequency_write(struct iio_backend *back,
+				   const struct iio_chan_spec *chan, int val,
+				   int val2, unsigned int tone_idx,
+				   unsigned long long sample_freq)
+{
+	struct axi_dac_state *st = iio_backend_get_priv(back);
+	u16 freq;
+	u32 reg;
+	int ret;
+
+	if (val < 0 || !sample_freq || val > sample_freq / 2) {
+		dev_err(st->dev, "Invalid frequency: %d, sampling freq: %llu\n",
+			val, sample_freq);
+		return -EINVAL;
+	}
+
+	if (tone_idx > 1) {
+		dev_err(st->dev, "Not a valid tone: %u\n", tone_idx);
+		return -EINVAL;
+	}
+
+	if (tone_idx)
+		reg = AXI_DAC_REG_CHAN_CNTRL_4(chan->channel);
+	else
+		reg = AXI_DAC_REG_CHAN_CNTRL_2(chan->channel);
+
+	freq = DIV64_U64_ROUND_CLOSEST((u64)val * BIT(16), sample_freq);
+
+	guard(mutex)(&st->lock);
+	ret = regmap_update_bits(st->regmap,  reg, AXI_DAC_FREQUENCY, freq);
+	if (ret)
+		return ret;
+
+	/* synchronize channels */
+	return regmap_set_bits(st->regmap, AXI_DAC_REG_CNTRL_1, AXI_DAC_SYNC);
+}
+
+static int axi_dac_scale_write(struct iio_backend *back,
+			       const struct iio_chan_spec *chan, int val,
+			       int val2, unsigned int tone_idx)
+{
+	struct axi_dac_state *st = iio_backend_get_priv(back);
+	u32 scale = 0, tmp, reg;
+	int ret;
+
+	if (tone_idx > 1) {
+		dev_err(st->dev, "Not a valid tone: %u\n", tone_idx);
+		return -EINVAL;
+	}
+
+	/*  format is 1.1.14 (sign, integer and fractional bits) */
+	switch (val) {
+	case 1:
+		scale = FIELD_PREP(AXI_DAC_SCALE_INT, 1);
+		break;
+	case -1:
+		scale = FIELD_PREP(AXI_DAC_SCALE_INT_NEG, 3);
+		break;
+	case 0:
+		if (val2 < 0) {
+			scale = FIELD_PREP(AXI_DAC_SCALE_SIGN, 1);
+			val2 *= -1;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	tmp = DIV_ROUND_CLOSEST_ULL((u64)val2 * AXI_DAC_SCALE_INT, MICRO);
+	scale |= FIELD_PREP(AXI_DAC_SCALE_FRAC, tmp);
+
+	if (tone_idx)
+		reg = AXI_DAC_REG_CHAN_CNTRL_3(chan->channel);
+	else
+		reg = AXI_DAC_REG_CHAN_CNTRL_1(chan->channel);
+
+	guard(mutex)(&st->lock);
+	ret = regmap_write(st->regmap, reg, scale);
+	if (ret)
+		return ret;
+
+	/* synchronize channels */
+	return regmap_set_bits(st->regmap, AXI_DAC_REG_CNTRL_1, AXI_DAC_SYNC);
+}
+
+static int axi_dac_phase_write(struct iio_backend *back,
+			       const struct iio_chan_spec *chan, int val,
+			       int val2, unsigned int tone_idx)
+{
+	struct axi_dac_state *st = iio_backend_get_priv(back);
+	u32 phase, reg;
+	int ret;
+
+	if (tone_idx > 1) {
+		dev_err(st->dev, "Not a valid tone: %u\n", tone_idx);
+		return -EINVAL;
+	}
+
+	phase = val * MILLI + val2;
+
+	phase = DIV_ROUND_CLOSEST_ULL((u64)phase * U16_MAX, AXI_DAC_2_PI_MEGA);
+
+	if (tone_idx)
+		reg = AXI_DAC_REG_CHAN_CNTRL_4(chan->channel);
+	else
+		reg = AXI_DAC_REG_CHAN_CNTRL_2(chan->channel);
+
+	guard(mutex)(&st->lock);
+	ret = regmap_update_bits(st->regmap, reg, AXI_DAC_PHASE,
+				 FIELD_PREP(AXI_DAC_PHASE, phase));
+	if (ret)
+		return ret;
+
+	/* synchronize channels */
+	return regmap_set_bits(st->regmap, AXI_DAC_REG_CNTRL_1, AXI_DAC_SYNC);
+}
+
+static int axi_dac_data_source_set(struct iio_backend *back, unsigned int chan,
+				   enum iio_backend_data_source data)
+{
+	struct axi_dac_state *st = iio_backend_get_priv(back);
+
+	switch (data) {
+	case IIO_BACKEND_INTERNAL_CW:
+		return regmap_update_bits(st->regmap,
+					  AXI_DAC_REG_CHAN_CNTRL_7(chan),
+					  AXI_DAC_DATA_SEL,
+					  AXI_DAC_DATA_INTERNAL_TONE);
+	case IIO_BACKEND_EXTERNAL:
+		return regmap_update_bits(st->regmap,
+					  AXI_DAC_REG_CHAN_CNTRL_7(chan),
+					  AXI_DAC_DATA_SEL, AXI_DAC_DATA_DMA);
+	default:
+		return -EINVAL;
+	}
+}
+
+static struct iio_buffer *axi_dac_request_buffer(struct iio_backend *back,
+						 struct iio_dev *indio_dev)
+{
+	struct axi_dac_state *st = iio_backend_get_priv(back);
+	struct iio_buffer *buffer;
+	const char *dma_name;
+	int ret;
+
+	if (device_property_read_string(st->dev, "dma-names", &dma_name))
+		dma_name = "tx";
+
+	buffer = iio_dmaengine_buffer_alloc(st->dev, dma_name);
+	if (IS_ERR(buffer)) {
+		dev_err(st->dev, "Could not get DMA buffer, %ld\n",
+			PTR_ERR(buffer));
+		return ERR_CAST(buffer);
+	}
+
+	indio_dev->modes |= INDIO_BUFFER_HARDWARE;
+	iio_buffer_set_dir(buffer, IIO_BUFFER_DIRECTION_OUT);
+
+	ret = iio_device_attach_buffer(indio_dev, buffer);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return buffer;
+}
+
+static void axi_dac_free_buffer(struct iio_backend *back,
+				struct iio_buffer *buffer)
+{
+	iio_dmaengine_buffer_free(buffer);
+}
+
+static int axi_dac_enable(struct iio_backend *back)
+{
+	struct axi_dac_state *st = iio_backend_get_priv(back);
+	unsigned int __val;
+	int ret;
+
+	guard(mutex)(&st->lock);
+	ret = regmap_set_bits(st->regmap, AXI_DAC_REG_RSTN,
+			      AXI_DAC_RSTN_MMCM_RSTN);
+	if (ret)
+		return ret;
+	/*
+	 * Make sure the DRP (Dynamic Reconfiguration Port) is locked. Not all
+	 * designs really use it but if they don't we still get the lock bit
+	 * set. So let's do it all the time so the code is generic.
+	 */
+	ret = regmap_read_poll_timeout(st->regmap, AXI_DAC_DRP_STATUS, __val,
+				       __val & AXI_DAC_DRP_LOCKED, 100, 1000);
+	if (ret)
+		return ret;
+
+	return regmap_set_bits(st->regmap, AXI_DAC_REG_RSTN,
+			       AXI_DAC_RSTN_RSTN | AXI_DAC_RSTN_MMCM_RSTN);
+}
+
+static void axi_dac_disable(struct iio_backend *back)
+{
+	struct axi_dac_state *st = iio_backend_get_priv(back);
+
+	guard(mutex)(&st->lock);
+	regmap_write(st->regmap, AXI_DAC_REG_RSTN, 0);
+}
+
+static const struct iio_backend_ops axi_dac_generic = {
+	.enable = axi_dac_enable,
+	.disable = axi_dac_disable,
+	.request_buffer = axi_dac_request_buffer,
+	.free_buffer = axi_dac_free_buffer,
+	.read_frequency = axi_dac_frequency_read,
+	.write_frequency = axi_dac_frequency_write,
+	.read_scale = axi_dac_scale_read,
+	.write_scale = axi_dac_scale_write,
+	.read_phase = axi_dac_phase_read,
+	.write_phase = axi_dac_phase_write,
+	.data_source_set = axi_dac_data_source_set,
+};
+
+static const struct regmap_config axi_dac_regmap_config = {
+	.val_bits = 32,
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.max_register = 0x0800,
+};
+
+static int axi_dac_probe(struct platform_device *pdev)
+{
+	const unsigned int *expected_ver;
+	struct axi_dac_state *st;
+	void __iomem *base;
+	unsigned int ver;
+	struct clk *clk;
+	int ret;
+
+	st = devm_kzalloc(&pdev->dev, sizeof(*st), GFP_KERNEL);
+	if (!st)
+		return -ENOMEM;
+
+	expected_ver = device_get_match_data(&pdev->dev);
+	if (!expected_ver)
+		return -ENODEV;
+
+	clk = devm_clk_get_enabled(&pdev->dev, NULL);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	st->dev = &pdev->dev;
+	st->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+					   &axi_dac_regmap_config);
+	if (IS_ERR(st->regmap))
+		return PTR_ERR(st->regmap);
+
+	/*
+	 * Force disable the core. Up to the frontend to enable us. And we can
+	 * still read/write registers...
+	 */
+	ret = regmap_write(st->regmap, AXI_DAC_REG_RSTN, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(st->regmap, ADI_AXI_REG_VERSION, &ver);
+	if (ret)
+		return ret;
+
+	if (ADI_AXI_PCORE_VER_MAJOR(ver) != ADI_AXI_PCORE_VER_MAJOR(*expected_ver)) {
+		dev_err(&pdev->dev,
+			"Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n",
+			ADI_AXI_PCORE_VER_MAJOR(*expected_ver),
+			ADI_AXI_PCORE_VER_MINOR(*expected_ver),
+			ADI_AXI_PCORE_VER_PATCH(*expected_ver),
+			ADI_AXI_PCORE_VER_MAJOR(ver),
+			ADI_AXI_PCORE_VER_MINOR(ver),
+			ADI_AXI_PCORE_VER_PATCH(ver));
+		return -ENODEV;
+	}
+
+	/* Let's get the core read only configuration */
+	ret = regmap_read(st->regmap, AXI_DAC_REG_CONFIG, &st->reg_config);
+	if (ret)
+		return ret;
+
+	/*
+	 * Default rate to 1 so the sampling frequency will be the same as the
+	 * digital interface clock which should be the same as the frontend
+	 * sampling clock/frequency. When this is not true, the frontend will
+	 * have to explicitly set a new frequency and thus rate.
+	 */
+	ret = regmap_write(st->regmap, AXI_DAC_REG_RATECNTRL, 1);
+	if (ret)
+		return ret;
+
+	mutex_init(&st->lock);
+	ret = devm_iio_backend_register(&pdev->dev, &axi_dac_generic, st);
+	if (ret)
+		return ret;
+
+	dev_info(&pdev->dev, "AXI DAC IP core (%d.%.2d.%c) probed\n",
+		 ADI_AXI_PCORE_VER_MAJOR(ver),
+		 ADI_AXI_PCORE_VER_MINOR(ver),
+		 ADI_AXI_PCORE_VER_PATCH(ver));
+
+	return 0;
+}
+
+static unsigned int axi_dac_9_1_b_info = ADI_AXI_PCORE_VER(9, 1, 'b');
+
+static const struct of_device_id axi_dac_of_match[] = {
+	{ .compatible = "adi,axi-dac-9.1.b", .data = &axi_dac_9_1_b_info },
+	{}
+};
+MODULE_DEVICE_TABLE(of, axi_dac_of_match);
+
+static struct platform_driver axi_dac_driver = {
+	.driver = {
+		.name = "adi-axi-dac",
+		.of_match_table = axi_dac_of_match,
+	},
+	.probe = axi_dac_probe,
+};
+module_platform_driver(axi_dac_driver);
+
+MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
+MODULE_DESCRIPTION("Analog Devices Generic AXI DAC IP core driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(IIO_DMAENGINE_BUFFER);
+MODULE_IMPORT_NS(IIO_BACKEND);
