iio: dac: ad5706r: Add support for AD5706R DAC

Add support for the Analog Devices AD5706R, a 4-channel 16-bit
current output digital-to-analog converter with SPI interface.

Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Signed-off-by: Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
Signed-off-by: Jonathan Cameron <jic23@kernel.org>
This commit is contained in:
Alexis Czezar Torreno
2026-05-31 10:59:32 +01:00
committed by Jonathan Cameron
parent 2e43816512
commit 081ce276d8
4 changed files with 266 additions and 0 deletions
+1
View File
@@ -1504,6 +1504,7 @@ L: linux-iio@vger.kernel.org
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/dac/adi,ad5706r.yaml
F: drivers/iio/dac/ad5706r.c
ANALOG DEVICES INC AD7091R DRIVER
M: Marcelo Schmitt <marcelo.schmitt@analog.com>
+11
View File
@@ -178,6 +178,17 @@ config AD5624R_SPI
Say yes here to build support for Analog Devices AD5624R, AD5644R and
AD5664R converters (DAC). This driver uses the common SPI interface.
config AD5706R
tristate "Analog Devices AD5706R DAC driver"
depends on SPI
select REGMAP
help
Say yes here to build support for Analog Devices AD5706R 4-channel,
16-bit current output DAC.
To compile this driver as a module, choose M here: the
module will be called ad5706r.
config AD9739A
tristate "Analog Devices AD9739A RF DAC spi driver"
depends on SPI
+1
View File
@@ -21,6 +21,7 @@ obj-$(CONFIG_AD5449) += ad5449.o
obj-$(CONFIG_AD5592R_BASE) += ad5592r-base.o
obj-$(CONFIG_AD5592R) += ad5592r.o
obj-$(CONFIG_AD5593R) += ad5593r.o
obj-$(CONFIG_AD5706R) += ad5706r.o
obj-$(CONFIG_AD5755) += ad5755.o
obj-$(CONFIG_AD5758) += ad5758.o
obj-$(CONFIG_AD5761) += ad5761.o
+253
View File
@@ -0,0 +1,253 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AD5706R 16-bit Current Output Digital to Analog Converter
*
* Copyright 2026 Analog Devices Inc.
*/
#include <linux/array_size.h>
#include <linux/bits.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/iio/iio.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include <linux/types.h>
#include <linux/unaligned.h>
/* SPI frame layout */
#define AD5706R_RD_MASK BIT(15)
#define AD5706R_ADDR_MASK GENMASK(11, 0)
/* Registers */
#define AD5706R_REG_DAC_INPUT_A_CH(x) (0x60 + ((x) * 2))
#define AD5706R_REG_DAC_DATA_READBACK_CH(x) (0x68 + ((x) * 2))
#define AD5706R_DAC_RESOLUTION 16
#define AD5706R_DAC_MAX_CODE GENMASK(15, 0)
#define AD5706R_MULTIBYTE_REG_START 0x14
#define AD5706R_MULTIBYTE_REG_END 0x71
#define AD5706R_MAX_REG 0x77
struct ad5706r_state {
struct spi_device *spi;
struct regmap *regmap;
u8 tx_buf[4] __aligned(IIO_DMA_MINALIGN);
u8 rx_buf[4];
};
static int ad5706r_reg_len(unsigned int reg)
{
if (reg >= AD5706R_MULTIBYTE_REG_START && reg <= AD5706R_MULTIBYTE_REG_END)
return 2;
return 1;
}
static int ad5706r_regmap_write(void *context, const void *data, size_t count)
{
struct ad5706r_state *st = context;
unsigned int num_bytes;
u16 reg, val;
if (count != 4)
return -EINVAL;
reg = get_unaligned_be16(data);
val = get_unaligned_be16(data + 2);
num_bytes = ad5706r_reg_len(reg);
struct spi_transfer xfer = {
.tx_buf = st->tx_buf,
.len = num_bytes + 2,
};
put_unaligned_be16(reg, &st->tx_buf[0]);
if (num_bytes == 1)
st->tx_buf[2] = (u8)val;
else if (num_bytes == 2)
put_unaligned_be16(val, &st->tx_buf[2]);
else
return -EINVAL;
return spi_sync_transfer(st->spi, &xfer, 1);
}
static int ad5706r_regmap_read(void *context, const void *reg_buf,
size_t reg_size, void *val_buf, size_t val_size)
{
struct ad5706r_state *st = context;
unsigned int num_bytes;
u16 reg, cmd, val;
int ret;
if (reg_size != 2 || val_size != 2)
return -EINVAL;
reg = get_unaligned_be16(reg_buf);
num_bytes = ad5706r_reg_len(reg);
/* Full duplex, device responds immediately after command */
struct spi_transfer xfer = {
.tx_buf = st->tx_buf,
.rx_buf = st->rx_buf,
.len = 2 + num_bytes,
};
cmd = AD5706R_RD_MASK | (reg & AD5706R_ADDR_MASK);
put_unaligned_be16(cmd, &st->tx_buf[0]);
put_unaligned_be16(0, &st->tx_buf[2]);
ret = spi_sync_transfer(st->spi, &xfer, 1);
if (ret)
return ret;
/* Extract value from response (skip 2-byte command echo) */
if (num_bytes == 1)
val = st->rx_buf[2];
else if (num_bytes == 2)
val = get_unaligned_be16(&st->rx_buf[2]);
else
return -EINVAL;
put_unaligned_be16(val, val_buf);
return 0;
}
static int ad5706r_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct ad5706r_state *st = iio_priv(indio_dev);
unsigned int reg, reg_val;
int ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
reg = AD5706R_REG_DAC_DATA_READBACK_CH(chan->channel);
ret = regmap_read(st->regmap, reg, &reg_val);
if (ret)
return ret;
*val = reg_val;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
*val = 50;
*val2 = AD5706R_DAC_RESOLUTION;
return IIO_VAL_FRACTIONAL_LOG2;
default:
return -EINVAL;
}
}
static int ad5706r_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct ad5706r_state *st = iio_priv(indio_dev);
unsigned int reg;
switch (mask) {
case IIO_CHAN_INFO_RAW:
if (val < 0 || val > AD5706R_DAC_MAX_CODE)
return -EINVAL;
reg = AD5706R_REG_DAC_INPUT_A_CH(chan->channel);
return regmap_write(st->regmap, reg, val);
default:
return -EINVAL;
}
}
static const struct regmap_bus ad5706r_regmap_bus = {
.write = ad5706r_regmap_write,
.read = ad5706r_regmap_read,
.reg_format_endian_default = REGMAP_ENDIAN_BIG,
.val_format_endian_default = REGMAP_ENDIAN_BIG,
};
static const struct regmap_config ad5706r_regmap_config = {
.reg_bits = 16,
.val_bits = 16,
.max_register = AD5706R_MAX_REG,
};
static const struct iio_info ad5706r_info = {
.read_raw = ad5706r_read_raw,
.write_raw = ad5706r_write_raw,
};
#define AD5706R_CHAN(_channel) { \
.type = IIO_CURRENT, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
BIT(IIO_CHAN_INFO_SCALE), \
.output = 1, \
.indexed = 1, \
.channel = _channel, \
}
static const struct iio_chan_spec ad5706r_channels[] = {
AD5706R_CHAN(0),
AD5706R_CHAN(1),
AD5706R_CHAN(2),
AD5706R_CHAN(3),
};
static int ad5706r_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
struct iio_dev *indio_dev;
struct ad5706r_state *st;
indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
if (!indio_dev)
return -ENOMEM;
st = iio_priv(indio_dev);
st->spi = spi;
st->regmap = devm_regmap_init(dev, &ad5706r_regmap_bus,
st, &ad5706r_regmap_config);
if (IS_ERR(st->regmap))
return dev_err_probe(dev, PTR_ERR(st->regmap),
"Failed to init regmap\n");
indio_dev->name = "ad5706r";
indio_dev->info = &ad5706r_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = ad5706r_channels;
indio_dev->num_channels = ARRAY_SIZE(ad5706r_channels);
return devm_iio_device_register(dev, indio_dev);
}
static const struct of_device_id ad5706r_of_match[] = {
{ .compatible = "adi,ad5706r" },
{ }
};
MODULE_DEVICE_TABLE(of, ad5706r_of_match);
static const struct spi_device_id ad5706r_id[] = {
{ "ad5706r" },
{ }
};
MODULE_DEVICE_TABLE(spi, ad5706r_id);
static struct spi_driver ad5706r_driver = {
.driver = {
.name = "ad5706r",
.of_match_table = ad5706r_of_match,
},
.probe = ad5706r_probe,
.id_table = ad5706r_id,
};
module_spi_driver(ad5706r_driver);
MODULE_AUTHOR("Alexis Czezar Torreno <alexisczezar.torreno@analog.com>");
MODULE_DESCRIPTION("AD5706R 16-bit Current Output DAC driver");
MODULE_LICENSE("GPL");