[PATCH v3 2/2] iio: dac: dac8163: Add driver for DAC8163
Lukas Metz <[email protected]> Sun, 02 Aug 2026 18:07:23 +0200
| Newsgroups | org.kernel.vger.linux-iio,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
The DAC756x, DAC816x, and DAC856x devices are low-power, voltage-output, dual-channel, 12-, 14-, and 16-bit digital-to-analog converters (DACs), respectively. These devices include a 2.5-V, 4-ppm/=C2=B0C internal reference, giving a full-scale output voltage range of 2.5 V or 5 V. Signed-off-by: Lukas Metz <[email protected]> =2D-- MAINTAINERS | 1 + drivers/iio/dac/Kconfig | 16 ++ drivers/iio/dac/Makefile | 1 + drivers/iio/dac/ti-dac8163.c | 449 ++++++++++++++++++++++++++++++++++++++= +++++ 4 files changed, 467 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 314f235332f5..5512f5eaab44 100644 =2D-- a/MAINTAINERS +++ b/MAINTAINERS @@ -26399,6 +26399,7 @@ M: Lukas Metz <[email protected]> L: [email protected] S: Maintained F: Documentation/devicetree/bindings/iio/dac/ti,dac8163.yaml +F: drivers/iio/dac/ti-dac8163.c =20 TI DATA TRANSFORM AND HASHING ENGINE (DTHE) V2 CRYPTO DRIVER M: T Pratham <[email protected]> diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index db9f5c711b3d..aa80b871995b 100644 =2D-- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -632,6 +632,22 @@ config TI_DAC7612 =20 If compiled as a module, it will be called ti-dac7612. =20 +config TI_DAC8163 + tristate "Texas Instruments 12/14/16-bit 2-channel DAC driver" + depends on SPI_MASTER + select REGMAP_SPI + help + Driver for the Texas Instruments digital-to-analog converter + family dacxx6x compatible with the following variants + - DAC7562 (2 channels, 12 bits, resets to zero) + - DAC7563 (2 channels, 12 bits, resets to mid-scale) + - DAC8162 (2 channels, 14 bits, resets to zero) + - DAC8163 (2 channels, 14 bits, resets to mid-scale) + - DAC8562 (2 channels, 16 bits, resets to zero) + - DAC8563 (2 channels, 16 bits, resets to mid-scale) + + If compiled as a module, it will be called ti-dac8163. + config VF610_DAC tristate "Vybrid vf610 DAC driver" depends on HAS_IOMEM diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 2a80bbf4e80a..359cde446623 100644 =2D-- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -62,4 +62,5 @@ obj-$(CONFIG_TI_DAC082S085) +=3D ti-dac082s085.o obj-$(CONFIG_TI_DAC5571) +=3D ti-dac5571.o obj-$(CONFIG_TI_DAC7311) +=3D ti-dac7311.o obj-$(CONFIG_TI_DAC7612) +=3D ti-dac7612.o +obj-$(CONFIG_TI_DAC8163) +=3D ti-dac8163.o obj-$(CONFIG_VF610_DAC) +=3D vf610_dac.o diff --git a/drivers/iio/dac/ti-dac8163.c b/drivers/iio/dac/ti-dac8163.c new file mode 100644 index 000000000000..35af7828dc22 =2D-- /dev/null +++ b/drivers/iio/dac/ti-dac8163.c @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DAC8163 IIO driver (SPI) + * https://www.ti.com/de/lit/gpn/dac8163 + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> +#include <linux/stddef.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <linux/iio/iio.h> + +#define COMMAND_MASK GENMASK(6, 3) +#define ADDRESS_MASK GENMASK(2, 0) + +#define CMD_WRITE_INPUT_REG 0x0 +#define CMD_UPDATE_DAC 0x1 +#define CMD_WRITE_UPDATE_ALL 0x2 +#define CMD_WRITE_UPDATE 0x3 +#define CMD_POWER_MODE 0x4 +#define CMD_SOFT_RST 0x5 +#define CMD_LDAC_MODE 0x6 +#define CMD_REF 0x7 + +#define LDAC_CHANNEL_A_MASK BIT(0) +#define LDAC_CHANNEL_B_MASK BIT(1) +#define VREF_MASK BIT(0) + +#define DAC8163_INTERNAL_REF_mV 2500 +#define DAC8163_RES_12_BIT 12 +#define DAC8163_RES_14_BIT 14 +#define DAC8163_RES_16_BIT 16 + +enum dac8163_reset_types { + OUTPUT_ONLY_RESET =3D 0, + FULL_RESET =3D 1, +}; + +enum dac8163_ldac_modes { + LDAC_ACTIVE =3D 0, + LDAC_INACTIVE =3D 1, +}; + +enum dac8163_voltage_reference { + VREF_EXTERNAL =3D 0, + VREF_INTERNAL =3D 1, +}; + +struct dac8163_state { + struct regmap *regmap; + struct regulator *vref; + + int vref_mV; + int gain; +}; + +struct dac8163_chip_info { + const char *name; + const struct iio_chan_spec channels[2]; + const struct regmap_config regmap_config; +}; + +#define DAC8163_CHAN(id, resolution) \ + { \ + .type =3D IIO_VOLTAGE, \ + .channel =3D (id), \ + .output =3D 1, \ + .indexed =3D 1, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type =3D BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type =3D { \ + .realbits =3D (resolution), \ + .shift =3D 16 - (resolution), \ + }, \ + } + +#define DAC8163_MID_SCALE(resolution) \ + (BIT((resolution) - 1) << (16 - (resolution))) + +static bool dac8163_reg_false(struct device *dev, unsigned int ref) +{ + return false; +} + +static const struct reg_default dacxxx2_reg_defaults[] =3D { + { + .reg =3D FIELD_PREP_CONST(COMMAND_MASK, CMD_WRITE_UPDATE) | + FIELD_PREP_CONST(ADDRESS_MASK, 0), + .def =3D 0, + }, + { + .reg =3D FIELD_PREP_CONST(COMMAND_MASK, CMD_WRITE_UPDATE) | + FIELD_PREP_CONST(ADDRESS_MASK, 1), + .def =3D 0, + }, +}; + +static const struct regmap_config dacxxx2_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 16, + .max_register =3D CMD_REF << 3, + .cache_type =3D REGCACHE_MAPLE, + .volatile_reg =3D dac8163_reg_false, + .reg_defaults =3D dacxxx2_reg_defaults, + .num_reg_defaults =3D ARRAY_SIZE(dacxxx2_reg_defaults), +}; + +static const struct reg_default dac7563_reg_defaults[] =3D { + { + .reg =3D FIELD_PREP_CONST(COMMAND_MASK, CMD_WRITE_UPDATE) | + FIELD_PREP_CONST(ADDRESS_MASK, 0), + .def =3D DAC8163_MID_SCALE(12), + }, + { + .reg =3D FIELD_PREP_CONST(COMMAND_MASK, CMD_WRITE_UPDATE) | + FIELD_PREP_CONST(ADDRESS_MASK, 1), + .def =3D DAC8163_MID_SCALE(12), + }, +}; + +static const struct regmap_config dac7563_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 16, + .max_register =3D CMD_REF << 3, + .cache_type =3D REGCACHE_MAPLE, + .volatile_reg =3D dac8163_reg_false, + .reg_defaults =3D dac7563_reg_defaults, + .num_reg_defaults =3D ARRAY_SIZE(dac7563_reg_defaults), +}; + +static const struct reg_default dac8163_reg_defaults[] =3D { + { + .reg =3D FIELD_PREP_CONST(COMMAND_MASK, CMD_WRITE_UPDATE) | + FIELD_PREP_CONST(ADDRESS_MASK, 0), + .def =3D DAC8163_MID_SCALE(14), + }, + { + .reg =3D FIELD_PREP_CONST(COMMAND_MASK, CMD_WRITE_UPDATE) | + FIELD_PREP_CONST(ADDRESS_MASK, 1), + .def =3D DAC8163_MID_SCALE(14), + }, +}; + +static const struct regmap_config dac8163_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 16, + .max_register =3D CMD_REF << 3, + .cache_type =3D REGCACHE_MAPLE, + .volatile_reg =3D dac8163_reg_false, + .reg_defaults =3D dac8163_reg_defaults, + .num_reg_defaults =3D ARRAY_SIZE(dac8163_reg_defaults), +}; + +static const struct reg_default dac8563_reg_defaults[] =3D { + { + .reg =3D FIELD_PREP_CONST(COMMAND_MASK, CMD_WRITE_UPDATE) | + FIELD_PREP_CONST(ADDRESS_MASK, 0), + .def =3D DAC8163_MID_SCALE(16), + }, + { + .reg =3D FIELD_PREP_CONST(COMMAND_MASK, CMD_WRITE_UPDATE) | + FIELD_PREP_CONST(ADDRESS_MASK, 1), + .def =3D DAC8163_MID_SCALE(16), + }, +}; + +static const struct regmap_config dac8563_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 16, + .max_register =3D CMD_REF << 3, + .cache_type =3D REGCACHE_MAPLE, + .volatile_reg =3D dac8163_reg_false, + .reg_defaults =3D dac8563_reg_defaults, + .num_reg_defaults =3D ARRAY_SIZE(dac8563_reg_defaults), +}; + +static const struct dac8163_chip_info dac7562_chip_info =3D { + .name =3D "dac7562", + .channels =3D { + DAC8163_CHAN(0, DAC8163_RES_12_BIT), + DAC8163_CHAN(1, DAC8163_RES_12_BIT), + }, + .regmap_config =3D dacxxx2_regmap_config, +}; + +static const struct dac8163_chip_info dac7563_chip_info =3D { + .name =3D "dac7563", + .channels =3D { + DAC8163_CHAN(0, DAC8163_RES_12_BIT), + DAC8163_CHAN(1, DAC8163_RES_12_BIT), + }, + .regmap_config =3D dac7563_regmap_config, +}; + +static const struct dac8163_chip_info dac8162_chip_info =3D { + .name =3D "dac8162", + .channels =3D { + DAC8163_CHAN(0, DAC8163_RES_14_BIT), + DAC8163_CHAN(1, DAC8163_RES_14_BIT), + }, + .regmap_config =3D dacxxx2_regmap_config, +}; + +static const struct dac8163_chip_info dac8163_chip_info =3D { + .name =3D "dac8163", + .channels =3D { + DAC8163_CHAN(0, DAC8163_RES_14_BIT), + DAC8163_CHAN(1, DAC8163_RES_14_BIT), + }, + .regmap_config =3D dac8163_regmap_config, +}; + +static const struct dac8163_chip_info dac8562_chip_info =3D { + .name =3D "dac8562", + .channels =3D { + DAC8163_CHAN(0, DAC8163_RES_16_BIT), + DAC8163_CHAN(1, DAC8163_RES_16_BIT), + }, + .regmap_config =3D dacxxx2_regmap_config, +}; + +static const struct dac8163_chip_info dac8563_chip_info =3D { + .name =3D "dac8563", + .channels =3D { + DAC8163_CHAN(0, DAC8163_RES_16_BIT), + DAC8163_CHAN(1, DAC8163_RES_16_BIT), + }, + .regmap_config =3D dac8563_regmap_config, +}; + +static int dac8163_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dac8163_state *st =3D iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + ret =3D regmap_read(st->regmap, + FIELD_PREP(COMMAND_MASK, CMD_WRITE_UPDATE) | + FIELD_PREP(ADDRESS_MASK, chan->channel), + val); + if (ret) + return ret; + *val >>=3D chan->scan_type.shift; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: { + *val =3D st->vref_mV * st->gain; + *val2 =3D chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + default: + return -EINVAL; + } +} + +static int dac8163_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct dac8163_state *st =3D iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + if (val2 !=3D 0) + return -EINVAL; + + if (val < 0 || val >=3D BIT(chan->scan_type.realbits)) + return -ERANGE; + + return regmap_write(st->regmap, + FIELD_PREP(COMMAND_MASK, CMD_WRITE_UPDATE) | + FIELD_PREP(ADDRESS_MASK, chan->channel), + (u16)val << chan->scan_type.shift); + } + default: + return -EINVAL; + } +} + +static const struct iio_info dac8163_iio_info =3D { + .write_raw =3D dac8163_write_raw, + .read_raw =3D dac8163_read_raw, +}; + +static int dac8163_probe(struct spi_device *spi) +{ + const struct dac8163_chip_info *info; + struct gpio_desc *ldac_gpio; + struct iio_dev *indio_dev; + struct dac8163_state *st; + bool internal_reference; + int ret; + + info =3D spi_get_device_match_data(spi); + if (!info) + return -ENODEV; + + indio_dev =3D devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st =3D iio_priv(indio_dev); + + indio_dev->name =3D info->name; + indio_dev->modes =3D INDIO_DIRECT_MODE; + indio_dev->info =3D &dac8163_iio_info; + indio_dev->channels =3D info->channels; + indio_dev->num_channels =3D ARRAY_SIZE(info->channels); + + st->regmap =3D devm_regmap_init_spi(spi, &info->regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(&spi->dev, PTR_ERR(st->regmap), + "failed to initialize regmap\n"); + + // for now we keep the ldac pin asserted permanently so that the output + // is updated immediately after a write to the channels raw attribute + ldac_gpio =3D devm_gpiod_get_optional(&spi->dev, "ldac", + GPIOD_OUT_HIGH); + if (IS_ERR(ldac_gpio)) + return dev_err_probe(&spi->dev, PTR_ERR(ldac_gpio), + "failed to get ldac gpio"); + + ret =3D devm_regulator_get_enable(&spi->dev, "avdd"); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "failed to get avdd voltage\n"); + + if (device_property_present(&spi->dev, "vrefin-supply")) { + ret =3D devm_regulator_get_enable_read_voltage(&spi->dev, + "vrefin"); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "failed to get vrefin voltage\n"); + + st->vref_mV =3D ret / (MICRO / MILLI); + internal_reference =3D false; + st->gain =3D 1; + } else { + st->vref_mV =3D DAC8163_INTERNAL_REF_mV; + internal_reference =3D true; + st->gain =3D 2; + } + + ret =3D regmap_write(st->regmap, FIELD_PREP(COMMAND_MASK, CMD_SOFT_RST), + FULL_RESET); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "failed to reset device\n"); + + ret =3D regmap_write(st->regmap, FIELD_PREP(COMMAND_MASK, CMD_LDAC_MODE)= , + FIELD_PREP(LDAC_CHANNEL_A_MASK, LDAC_INACTIVE) | + FIELD_PREP(LDAC_CHANNEL_B_MASK, LDAC_INACTIVE)); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "failed to set ldac mode\n"); + + ret =3D regmap_write(st->regmap, FIELD_PREP(COMMAND_MASK, CMD_REF), + FIELD_PREP(VREF_MASK, internal_reference)); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "failed to select reference voltage\n"); + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id dac8163_of_match[] =3D { + { + .compatible =3D "ti,dac7562", + .data =3D &dac7562_chip_info, + }, + { + .compatible =3D "ti,dac7563", + .data =3D &dac7563_chip_info, + }, + { + .compatible =3D "ti,dac8162", + .data =3D &dac8162_chip_info, + }, + { + .compatible =3D "ti,dac8163", + .data =3D &dac8163_chip_info, + }, + { + .compatible =3D "ti,dac8562", + .data =3D &dac8562_chip_info, + }, + { + .compatible =3D "ti,dac8563", + .data =3D &dac8563_chip_info, + }, + { } +}; +MODULE_DEVICE_TABLE(of, dac8163_of_match); + +static const struct spi_device_id dac8163_id_table[] =3D { + { + .name =3D "dac7562", + .driver_data =3D (kernel_ulong_t)&dac7562_chip_info, + }, + { + .name =3D "dac7563", + .driver_data =3D (kernel_ulong_t)&dac7563_chip_info, + }, + { + .name =3D "dac8162", + .driver_data =3D (kernel_ulong_t)&dac8162_chip_info, + }, + { + .name =3D "dac8163", + .driver_data =3D (kernel_ulong_t)&dac8163_chip_info, + }, + { + .name =3D "dac8562", + .driver_data =3D (kernel_ulong_t)&dac8562_chip_info, + }, + { + .name =3D "dac8563", + .driver_data =3D (kernel_ulong_t)&dac8563_chip_info, + }, + { } +}; +MODULE_DEVICE_TABLE(spi, dac8163_id_table); + +static struct spi_driver dac8163_driver =3D { + .driver =3D { + .name =3D "dac8163", + .of_match_table =3D dac8163_of_match, + }, + .probe =3D dac8163_probe, + .id_table =3D dac8163_id_table, +}; +module_spi_driver(dac8163_driver); + +MODULE_AUTHOR("Lukas Metz <[email protected]>"); +MODULE_DESCRIPTION("Texas Instruments 12/14/16-bit 2-channel DAC driver")= ; +MODULE_LICENSE("GPL"); =2D-=20 2.43.0