Devicetree
 help / color / mirror / Atom feed
From: Lukas Metz <lukas.metz@gmx.net>
To: "Jonathan Cameron" <jic23@kernel.org>,
	"David Lechner" <dlechner@baylibre.com>,
	"Nuno Sá" <nuno.sa@analog.com>,
	"Andy Shevchenko" <andy@kernel.org>,
	"Rob Herring" <robh@kernel.org>,
	"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
	"Conor Dooley" <conor+dt@kernel.org>
Cc: linux-kernel@vger.kernel.org, linux-iio@vger.kernel.org,
	 devicetree@vger.kernel.org, Lukas Metz <lukas.metz@gmx.net>
Subject: [PATCH v2 2/2] iio: dac: dac8163: Add driver for DAC8163
Date: Wed, 08 Jul 2026 11:52:44 +0200	[thread overview]
Message-ID: <20260708-dac8163-work-v2-2-3acd1bf20182@gmx.net> (raw)
In-Reply-To: <20260708-dac8163-work-v2-0-3acd1bf20182@gmx.net>

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/°C internal
reference, giving a full-scale output voltage range of 2.5 V or 5 V.

Signed-off-by: Lukas Metz <lukas.metz@gmx.net>
---
 MAINTAINERS                  |   1 +
 drivers/iio/dac/Kconfig      |  15 ++
 drivers/iio/dac/Makefile     |   1 +
 drivers/iio/dac/ti-dac8163.c | 373 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 390 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 314f235332f5..5512f5eaab44 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26399,6 +26399,7 @@ M:	Lukas Metz <lukas.metz@gmx.net>
 L:	linux-iio@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/iio/dac/ti,dac8163.yaml
+F:	drivers/iio/dac/ti-dac8163.c
 
 TI DATA TRANSFORM AND HASHING ENGINE (DTHE) V2 CRYPTO DRIVER
 M:	T Pratham <t-pratham@ti.com>
diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
index db9f5c711b3d..de3b8fa5ffbf 100644
--- a/drivers/iio/dac/Kconfig
+++ b/drivers/iio/dac/Kconfig
@@ -632,6 +632,21 @@ config TI_DAC7612
 
 	  If compiled as a module, it will be called ti-dac7612.
 
+config TI_DAC8163
+	tristate "Texas Instruments 12/14/16-bit 2-channel DAC driver"
+	depends on SPI_MASTER
+	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
--- a/drivers/iio/dac/Makefile
+++ b/drivers/iio/dac/Makefile
@@ -62,4 +62,5 @@ obj-$(CONFIG_TI_DAC082S085) += ti-dac082s085.o
 obj-$(CONFIG_TI_DAC5571) += ti-dac5571.o
 obj-$(CONFIG_TI_DAC7311) += ti-dac7311.o
 obj-$(CONFIG_TI_DAC7612) += ti-dac7612.o
+obj-$(CONFIG_TI_DAC8163) += ti-dac8163.o
 obj-$(CONFIG_VF610_DAC) += 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..6be0aac2e875
--- /dev/null
+++ b/drivers/iio/dac/ti-dac8163.c
@@ -0,0 +1,373 @@
+// 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/errno.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mod_devicetable.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_SET(x, y) \
+	(FIELD_PREP(COMMAND_MASK, (x)) | FIELD_PREP(ADDRESS_MASK, (y)))
+
+#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
+
+enum dac8163_reset_types {
+	OUTPUT_ONLY_RESET = 0,
+	FULL_RESET = 1,
+};
+
+enum dac8163_ldac_modes {
+	LDAC_ACTIVE = 0,
+	LDAC_INACTIVE = 1,
+};
+
+enum dac8163_voltage_reference {
+	VREF_EXTERNAL = 0,
+	VREF_INTERNAL = 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];
+	u16 default_output_reg;
+};
+
+#define DAC8163_CHAN(id, resolution)					\
+	{								\
+		.type = IIO_VOLTAGE,					\
+		.channel = (id),					\
+		.output = 1,						\
+		.indexed = 1,						\
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
+		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),	\
+		.scan_type = {						\
+			.realbits = (resolution),			\
+			.shift = 16 - (resolution)			\
+		},							\
+	}
+
+#define DAC8163_MID_SCALE(resolution) \
+	(BIT((resolution) - 1) << (16 - (resolution)))
+
+static const struct dac8163_chip_info dac7562_chip_info = {
+	.name = "dac7562",
+	.channels = {
+		DAC8163_CHAN(0, 12),
+		DAC8163_CHAN(1, 12),
+	},
+	.default_output_reg = 0,
+};
+
+static const struct dac8163_chip_info dac7563_chip_info = {
+	.name = "dac7563",
+	.channels = {
+		DAC8163_CHAN(0, 12),
+		DAC8163_CHAN(1, 12),
+	},
+	.default_output_reg = DAC8163_MID_SCALE(12),
+};
+
+static const struct dac8163_chip_info dac8162_chip_info = {
+	.name = "dac8162",
+	.channels = {
+		DAC8163_CHAN(0, 14),
+		DAC8163_CHAN(1, 14),
+	},
+	.default_output_reg = 0,
+};
+
+static const struct dac8163_chip_info dac8163_chip_info = {
+	.name = "dac8163",
+	.channels = {
+		DAC8163_CHAN(0, 14),
+		DAC8163_CHAN(1, 14),
+	},
+	.default_output_reg = DAC8163_MID_SCALE(14),
+};
+
+static const struct dac8163_chip_info dac8562_chip_info = {
+	.name = "dac8562",
+	.channels = {
+		DAC8163_CHAN(0, 16),
+		DAC8163_CHAN(1, 16),
+	},
+	.default_output_reg = 0,
+};
+
+static const struct dac8163_chip_info dac8563_chip_info = {
+	.name = "dac8563",
+	.channels = {
+		DAC8163_CHAN(0, 16),
+		DAC8163_CHAN(1, 16),
+	},
+	.default_output_reg = DAC8163_MID_SCALE(16),
+};
+
+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 = iio_priv(indio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = regmap_read(st->regmap,
+				  CMD_SET(CMD_WRITE_UPDATE, chan->channel),
+				  val);
+		if (ret)
+			return ret;
+		*val >>= chan->scan_type.shift;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		*val = st->vref_mV * st->gain;
+		*val2 = 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 = iio_priv(indio_dev);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		if (val2 != 0)
+			return -EINVAL;
+
+		if (val < 0 || val >= BIT(chan->scan_type.realbits))
+			return -ERANGE;
+
+		return regmap_write(st->regmap,
+				    CMD_SET(CMD_WRITE_UPDATE, chan->channel),
+				    (u16)val << chan->scan_type.shift);
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info dac8163_iio_info = {
+	.write_raw = dac8163_write_raw,
+	.read_raw = dac8163_read_raw
+};
+
+static bool dac8163_reg_false(struct device *dev, unsigned int ref)
+{
+	return false;
+}
+
+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 = spi_get_device_match_data(spi);
+	if (!info)
+		return -ENODEV;
+
+	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	st = iio_priv(indio_dev);
+
+	indio_dev->name = info->name;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->info = &dac8163_iio_info;
+	indio_dev->channels = info->channels;
+	indio_dev->num_channels = ARRAY_SIZE(info->channels);
+
+	const struct reg_default reg_defaults[] = {
+		{
+			.reg = CMD_SET(CMD_WRITE_UPDATE, 0),
+			.def = info->default_output_reg,
+		},
+		{
+			.reg = CMD_SET(CMD_WRITE_UPDATE, 1),
+			.def = info->default_output_reg,
+		},
+	};
+
+	const struct regmap_config regmap_config = {
+		.reg_bits = 8,
+		.val_bits = 16,
+
+		.max_register = CMD_REF << 3,
+		.cache_type = REGCACHE_MAPLE,
+
+		.volatile_reg = dac8163_reg_false,
+
+		.reg_defaults = reg_defaults,
+		.num_reg_defaults = ARRAY_SIZE(reg_defaults),
+	};
+
+	st->regmap = devm_regmap_init_spi(spi, &regmap_config);
+	if (IS_ERR(st->regmap))
+		return PTR_ERR(st->regmap);
+
+	// 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 = devm_gpiod_get_optional(&spi->dev, "ldac",
+					    GPIOD_OUT_HIGH);
+	if (IS_ERR(ldac_gpio))
+		return PTR_ERR(ldac_gpio);
+
+	if (device_property_present(&spi->dev, "vrefin-supply")) {
+		ret = devm_regulator_get_enable_read_voltage(&spi->dev,
+							     "vrefin");
+		if (ret < 0)
+			return ret;
+
+		st->vref_mV = ret / (MICRO / MILLI);
+		internal_reference = false;
+		st->gain = 1;
+	} else {
+		st->vref_mV = DAC8163_INTERNAL_REF_mV;
+		internal_reference = true;
+		st->gain = 2;
+	}
+
+	ret = devm_regulator_get_enable(&spi->dev, "avdd");
+	if (ret < 0)
+		return ret;
+
+	ret = regmap_write(st->regmap, FIELD_PREP(COMMAND_MASK, CMD_SOFT_RST),
+			   FULL_RESET);
+
+	if (ret < 0)
+		return ret;
+
+	ret = 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 ret;
+
+	ret = regmap_write(st->regmap, FIELD_PREP(COMMAND_MASK, CMD_REF),
+			   FIELD_PREP(VREF_MASK, internal_reference));
+	if (ret < 0)
+		return ret;
+
+	return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct of_device_id dac8163_of_match[] = {
+	{
+		.compatible = "ti,dac7562",
+		.data = &dac7562_chip_info,
+	},
+	{
+		.compatible = "ti,dac7563",
+		.data = &dac7563_chip_info,
+	},
+	{
+		.compatible = "ti,dac8162",
+		.data = &dac8162_chip_info,
+	},
+	{
+		.compatible = "ti,dac8163",
+		.data = &dac8163_chip_info,
+	},
+	{
+		.compatible = "ti,dac8562",
+		.data = &dac8562_chip_info,
+	},
+	{
+		.compatible = "ti,dac8563",
+		.data = &dac8563_chip_info,
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, dac8163_of_match);
+
+static const struct spi_device_id dac8163_id_table[] = {
+	{
+		.name = "dac7562",
+		.driver_data = (kernel_ulong_t)&dac7562_chip_info
+	},
+	{
+		.name = "dac7563",
+		.driver_data = (kernel_ulong_t)&dac7563_chip_info
+	},
+	{
+		.name = "dac8162",
+		.driver_data = (kernel_ulong_t)&dac8162_chip_info
+	},
+	{
+		.name = "dac8163",
+		.driver_data = (kernel_ulong_t)&dac8163_chip_info
+	},
+	{
+		.name = "dac8562",
+		.driver_data = (kernel_ulong_t)&dac8562_chip_info
+	},
+	{
+		.name = "dac8563",
+		.driver_data = (kernel_ulong_t)&dac8563_chip_info
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, dac8163_id_table);
+
+static struct spi_driver dac8163_driver = {
+	.driver = {
+		.name = "dac8163",
+		.of_match_table = dac8163_of_match,
+	},
+	.probe = dac8163_probe,
+	.id_table = dac8163_id_table,
+};
+module_spi_driver(dac8163_driver);
+
+MODULE_AUTHOR("Lukas Metz <lukas.metz@gmx.net>");
+MODULE_DESCRIPTION("Texas Instruments 12/14/16-bit 2-channel DAC driver");
+MODULE_LICENSE("GPL");

-- 
2.43.0


  parent reply	other threads:[~2026-07-08  9:53 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-08  9:52 [PATCH v2 0/2] Add driver for DAC8163: Lukas Metz
2026-07-08  9:52 ` [PATCH v2 1/2] dt-bindings: iio: dac: Add DAC8163 Lukas Metz
2026-07-08 10:06   ` sashiko-bot
2026-07-08 16:37   ` Conor Dooley
2026-07-08  9:52 ` Lukas Metz [this message]
2026-07-08 10:04   ` [PATCH v2 2/2] iio: dac: dac8163: Add driver for DAC8163 sashiko-bot
2026-07-08 11:32   ` Andy Shevchenko
2026-07-09 10:03     ` Lukas
2026-07-09 10:19       ` Andy Shevchenko
2026-07-08 15:58   ` Siratul Islam
2026-07-08 17:33     ` Jonathan Cameron
2026-07-08 19:04       ` Siratul Islam

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=20260708-dac8163-work-v2-2-3acd1bf20182@gmx.net \
    --to=lukas.metz@gmx.net \
    --cc=andy@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dlechner@baylibre.com \
    --cc=jic23@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=nuno.sa@analog.com \
    --cc=robh@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