drivers: dac: Add TI DACx0501 driver

Adds a DAC driver for Texas Instruments DACx0501 family of devices

Signed-off-by: Eran Gal <erang@google.com>
Co-authored-by: Martin Jäger <17674105+martinjaeger@users.noreply.github.com>
diff --git a/drivers/dac/CMakeLists.txt b/drivers/dac/CMakeLists.txt
index 4e1f72b..cf71fff 100644
--- a/drivers/dac/CMakeLists.txt
+++ b/drivers/dac/CMakeLists.txt
@@ -10,6 +10,7 @@
 zephyr_library_sources_ifdef(CONFIG_DAC_STM32		dac_stm32.c)
 zephyr_library_sources_ifdef(CONFIG_DAC_SAM		dac_sam.c)
 zephyr_library_sources_ifdef(CONFIG_DAC_SAM0		dac_sam0.c)
+zephyr_library_sources_ifdef(CONFIG_DAC_DACX0501	dac_dacx0501.c)
 zephyr_library_sources_ifdef(CONFIG_DAC_DACX0508	dac_dacx0508.c)
 zephyr_library_sources_ifdef(CONFIG_DAC_DACX3608	dac_dacx3608.c)
 zephyr_library_sources_ifdef(CONFIG_DAC_LTC166X     dac_ltc166x.c)
diff --git a/drivers/dac/Kconfig b/drivers/dac/Kconfig
index 97edb2c..4f1d95d 100644
--- a/drivers/dac/Kconfig
+++ b/drivers/dac/Kconfig
@@ -37,6 +37,8 @@
 
 source "drivers/dac/Kconfig.sam0"
 
+source "drivers/dac/Kconfig.dacx0501"
+
 source "drivers/dac/Kconfig.dacx0508"
 
 source "drivers/dac/Kconfig.dacx3608"
diff --git a/drivers/dac/Kconfig.dacx0501 b/drivers/dac/Kconfig.dacx0501
new file mode 100644
index 0000000..0481816
--- /dev/null
+++ b/drivers/dac/Kconfig.dacx0501
@@ -0,0 +1,23 @@
+# DAC configuration options
+
+# Copyright (c) 2023 Google, LLC.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+config DAC_DACX0501
+	bool "TI DACx0501 DAC driver"
+	default y
+	depends on DT_HAS_TI_DACX0501_ENABLED
+	select I2C
+	help
+	  Enable the driver for the TI DACx0501.
+
+if DAC_DACX0501
+
+config DAC_DACX0501_INIT_PRIORITY
+	int "Init priority"
+	default 80
+	help
+	  TI DACx0501 DAC device driver initialization priority. Must be greater than I2C_INIT_PRIORITY.
+
+endif # DAC_DACX0501
diff --git a/drivers/dac/dac_dacx0501.c b/drivers/dac/dac_dacx0501.c
new file mode 100644
index 0000000..f8ee999
--- /dev/null
+++ b/drivers/dac/dac_dacx0501.c
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2024 Google LLC.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @brief Driver for Texas Instruments DACx0501 series
+ *
+ * Device driver for the  Texas Instruments DACx0501 series of devices: DAC60501, DAC70501 and
+ * DAC80501: Digital to Analog Converters with a single channel output and with 12, 14 and 16
+ * bits resolution respectively. Data sheet can be found here:
+ * https://www.ti.com/lit/ds/symlink/dac80501.pdf
+ */
+
+#include <zephyr/kernel.h>
+#include <zephyr/drivers/i2c.h>
+#include <zephyr/drivers/dac.h>
+#include <zephyr/logging/log.h>
+#include <zephyr/sys/byteorder.h>
+
+LOG_MODULE_REGISTER(dac_dacx0501, CONFIG_DAC_LOG_LEVEL);
+
+#define DACX0501_REG_DEVICE_ID 0x01U
+#define DACX0501_REG_SYNC      0x02U
+#define DACX0501_REG_CONFIG    0x03U
+#define DACX0501_REG_GAIN      0x04U
+#define DACX0501_REG_TRIGGER   0x05U
+#define DACX0501_REG_STATUS    0x07U
+#define DACX0501_REG_DAC       0x08U
+
+#define DACX0501_MASK_DEVICE_ID_RES      GENMASK(14, 12)
+#define DACX0501_MASK_CONFIG_REF_PWDWN   BIT(8)
+#define DACX0501_MASK_CONFIG_DAC_PWDWN   BIT(0)
+#define DACX0501_MASK_GAIN_BUFF_GAIN     BIT(0)
+#define DACX0501_MASK_GAIN_REFDIV_EN     BIT(8)
+#define DACX0501_MASK_TRIGGER_SOFT_RESET (BIT(1) | BIT(3))
+#define DACX0501_MASK_STATUS_REF_ALM     BIT(0)
+
+/* Specifies the source of the reference voltage. */
+enum voltage_reference_source {
+	REF_INTERNAL, /* Internal 2.5V voltage reference. */
+	REF_EXTERNAL, /* External pin voltage reference. */
+};
+
+/* Specifies the reference voltage multiplier. */
+enum output_gain {
+	VM_MUL2, /* Multiplies by 2. */
+	VM_MUL1, /* Multiplies by 1. */
+	VM_DIV2, /* Multiplies by 0.5 */
+};
+
+struct dacx0501_config {
+	struct i2c_dt_spec i2c_spec;
+	enum voltage_reference_source voltage_reference;
+	enum output_gain output_gain;
+};
+
+struct dacx0501_data {
+	/* Number of bits in the DAC register: Either 12, 14 or 16. */
+	uint8_t resolution;
+};
+
+static int dacx0501_reg_read(const struct device *dev, const uint8_t addr, uint16_t *data)
+{
+	const struct dacx0501_config *config = dev->config;
+	uint8_t raw_data[2];
+	int status;
+
+	status = i2c_write_read_dt(&config->i2c_spec, &addr, sizeof(addr), raw_data,
+				   sizeof(raw_data));
+	if (status != 0) {
+		return status;
+	}
+
+	/* DAC is big endian. */
+	*data = sys_get_be16(raw_data);
+	return 0;
+}
+
+static int dacx0501_reg_write(const struct device *dev, uint8_t addr, uint16_t data)
+{
+	const struct dacx0501_config *config = dev->config;
+	uint8_t write_cmd[3] = {addr};
+
+	/* DAC is big endian. */
+	sys_put_be16(data, write_cmd + 1);
+
+	return i2c_write_dt(&config->i2c_spec, write_cmd, sizeof(write_cmd));
+}
+
+static int dacx0501_channel_setup(const struct device *dev,
+				  const struct dac_channel_cfg *channel_cfg)
+{
+	struct dacx0501_data *data = dev->data;
+
+	/* DACx0501 series only has a single output channel. */
+	if (channel_cfg->channel_id != 0) {
+		LOG_ERR("Unsupported channel %d", channel_cfg->channel_id);
+		return -ENOTSUP;
+	}
+
+	if (channel_cfg->resolution != data->resolution) {
+		LOG_ERR("Unsupported resolution %d. Actual: %d", channel_cfg->resolution,
+			data->resolution);
+		return -ENOTSUP;
+	}
+
+	return 0;
+}
+
+static int dacx0501_write_value(const struct device *dev, uint8_t channel, uint32_t value)
+{
+	struct dacx0501_data *data = dev->data;
+
+	if (channel != 0) {
+		LOG_ERR("dacx0501: Unsupported channel %d", channel);
+		return -ENOTSUP;
+	}
+
+	if (value >= (1 << data->resolution)) {
+		LOG_ERR("dacx0501: Value %d out of range", value);
+		return -EINVAL;
+	}
+
+	value <<= (16 - data->resolution);
+
+	return dacx0501_reg_write(dev, DACX0501_REG_DAC, value);
+}
+
+static int dacx0501_init(const struct device *dev)
+{
+	const struct dacx0501_config *config = dev->config;
+	struct dacx0501_data *data = dev->data;
+	uint16_t device_id;
+	int status;
+
+	if (!i2c_is_ready_dt(&config->i2c_spec)) {
+		LOG_ERR("I2C bus %s not ready", config->i2c_spec.bus->name);
+		return -ENODEV;
+	}
+
+	status = dacx0501_reg_read(dev, DACX0501_REG_DEVICE_ID, &device_id);
+	if (status != 0) {
+		LOG_ERR("read DEVICE_ID register failed");
+		return status;
+	}
+
+	/* See DEVICE_ID register RES field in the data sheet. */
+	data->resolution = 16 - 2 * FIELD_GET(DACX0501_MASK_DEVICE_ID_RES, device_id);
+
+	status = dacx0501_reg_write(dev, DACX0501_REG_CONFIG,
+				    FIELD_PREP(DACX0501_MASK_CONFIG_REF_PWDWN,
+					       config->voltage_reference == REF_EXTERNAL));
+	if (status != 0) {
+		LOG_ERR("write CONFIG register failed");
+		return status;
+	}
+
+	status = dacx0501_reg_write(
+		dev, DACX0501_REG_GAIN,
+		FIELD_PREP(DACX0501_MASK_GAIN_REFDIV_EN, config->output_gain == VM_DIV2) |
+			FIELD_PREP(DACX0501_MASK_GAIN_BUFF_GAIN, config->output_gain == VM_MUL2));
+	if (status != 0) {
+		LOG_ERR("GAIN Register update failed");
+		return status;
+	}
+
+	return 0;
+}
+
+static const struct dac_driver_api dacx0501_driver_api = {
+	.channel_setup = dacx0501_channel_setup,
+	.write_value = dacx0501_write_value,
+};
+
+#define DT_DRV_COMPAT ti_dacx0501
+
+#define DACX0501_DEFINE(n)                                                                         \
+	static struct dacx0501_data dacx0501_data_##n = {};                                        \
+	static const struct dacx0501_config dacx0501_config_##n = {                                \
+		.i2c_spec = I2C_DT_SPEC_INST_GET(n),                                               \
+		.voltage_reference =                                                               \
+			_CONCAT(REF_, DT_STRING_UPPER_TOKEN(DT_DRV_INST(n), voltage_reference)),   \
+		.output_gain = _CONCAT(VM_, DT_STRING_UPPER_TOKEN(DT_DRV_INST(n), output_gain)),   \
+	};                                                                                         \
+	DEVICE_DT_INST_DEFINE(n, &dacx0501_init, NULL, &dacx0501_data_##n, &dacx0501_config_##n,   \
+			      POST_KERNEL, CONFIG_DAC_DACX0501_INIT_PRIORITY,                      \
+			      &dacx0501_driver_api);
+
+DT_INST_FOREACH_STATUS_OKAY(DACX0501_DEFINE)
diff --git a/dts/bindings/dac/ti,dacx0501.yaml b/dts/bindings/dac/ti,dacx0501.yaml
new file mode 100644
index 0000000..5325100
--- /dev/null
+++ b/dts/bindings/dac/ti,dacx0501.yaml
@@ -0,0 +1,30 @@
+# Copyright (c) 2024 Google, LLC.
+# SPDX-License-Identifier: Apache-2.0
+
+include: base.yaml
+
+description: TI DACx0501 12 to 16 bit DAC series for DAC60501, DAC70501 and DAC80501 devices.
+
+compatible: "ti,dacx0501"
+
+properties:
+  voltage-reference:
+    type: string
+    required: true
+    enum:
+      - "internal"
+      - "external"
+    description: |
+      DAC voltage reference select: either internal (2.5 V) or external
+
+  output-gain:
+    type: string
+    required: true
+    enum:
+      - "mul2"
+      - "mul1"
+      - "div2"
+    description: |
+      This setting can be used to control the output voltage range within the supported bit
+      resolution. mul2 will double the output range but lower the resolution, while div2 will
+      lower the range but double the resolution.
diff --git a/tests/drivers/build_all/dac/app.overlay b/tests/drivers/build_all/dac/app.overlay
index 8f7c918..ea90be6 100644
--- a/tests/drivers/build_all/dac/app.overlay
+++ b/tests/drivers/build_all/dac/app.overlay
@@ -68,6 +68,14 @@
 				voltage_reference = <0>;
 				power_down_mode = <0>;
 			};
+
+			test_i2c_dacx0501:dacx0501@62 {
+				compatible = "ti,dacx0501";
+				status = "okay";
+				reg = <0x62>;
+				voltage-reference = "internal";
+				output-gain = "mul2";
+			};
 		};
 
 		test_spi: spi@33334444 {