drivers: dac: added driver for TI DACx3608

The DAC53608 and DAC43608 (DACx3608) are lowpower, eight-channel,
voltage-output, 10-bit or 8-bit digital-to-analog converters (DACs)
respectively. They support I2C with a wide power supply range
from 1.8 V to 5.5 V, and a full scale output voltage range of
1.8 V to 5.5 V. The DACx3608 also includes per channel, user
programmable, power down registers.

Signed-off-by: Matija Tudan <mtudan@mobilisis.hr>
diff --git a/drivers/dac/CMakeLists.txt b/drivers/dac/CMakeLists.txt
index 0b5cee3..0e5fdf3 100644
--- a/drivers/dac/CMakeLists.txt
+++ b/drivers/dac/CMakeLists.txt
@@ -7,5 +7,6 @@
 zephyr_library_sources_ifdef(CONFIG_DAC_STM32		dac_stm32.c)
 zephyr_library_sources_ifdef(CONFIG_DAC_SAM0		dac_sam0.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_SHELL		dac_shell.c)
 zephyr_library_sources_ifdef(CONFIG_USERSPACE		dac_handlers.c)
diff --git a/drivers/dac/Kconfig b/drivers/dac/Kconfig
index f30b3b4..254e245 100644
--- a/drivers/dac/Kconfig
+++ b/drivers/dac/Kconfig
@@ -32,4 +32,6 @@
 
 source "drivers/dac/Kconfig.dacx0508"
 
+source "drivers/dac/Kconfig.dacx3608"
+
 endif # DAC
diff --git a/drivers/dac/Kconfig.dacx3608 b/drivers/dac/Kconfig.dacx3608
new file mode 100644
index 0000000..de8f218
--- /dev/null
+++ b/drivers/dac/Kconfig.dacx3608
@@ -0,0 +1,18 @@
+# DAC configuration options
+
+# Copyright (c) 2020 Matija Tudan
+#
+# SPDX-License-Identifier: Apache-2.0
+
+config DAC_DACX3608
+	bool "TI DACX3608 DAC driver"
+	depends on I2C
+	help
+	  Enable the driver for the TI DACX3608.
+
+config DAC_DACX3608_INIT_PRIORITY
+	int "Init priority"
+	depends on DAC_DACX3608
+	default 80
+	help
+	  DACX3608 DAC device driver initialization priority.
diff --git a/drivers/dac/dac_dacx3608.c b/drivers/dac/dac_dacx3608.c
new file mode 100644
index 0000000..d0e36a1
--- /dev/null
+++ b/drivers/dac/dac_dacx3608.c
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2020 Matija Tudan
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <zephyr.h>
+#include <kernel.h>
+#include <drivers/i2c.h>
+#include <drivers/dac.h>
+#include <sys/util.h>
+#include <sys/byteorder.h>
+#include <sys/__assert.h>
+#include <logging/log.h>
+
+LOG_MODULE_REGISTER(dac_dacx3608, CONFIG_DAC_LOG_LEVEL);
+
+/* Register addresses */
+#define DACX3608_REG_DEVICE_CONFIG  0x01U
+#define DACX3608_REG_STATUS_TRIGGER 0x02U
+#define DACX3608_REG_BRDCAST        0x03U
+#define DACX3608_REG_DACA_DATA      0x08U
+
+#define DAC43608_DEVICE_ID      0x500	/* STATUS_TRIGGER[DEVICE_ID] */
+#define DAC53608_DEVICE_ID      0x300	/* STATUS_TRIGGER[DEVICE_ID] */
+#define DACX3608_SW_RST         0x0A	/* STATUS_TRIGGER[SW_RST] */
+#define DACX3608_POR_DELAY      5
+#define DACX3608_MAX_CHANNEL    8
+
+struct dacx3608_config {
+	const char *i2c_bus;
+	uint16_t i2c_addr;
+	uint8_t resolution;
+};
+
+struct dacx3608_data {
+	const struct device *i2c;
+	uint8_t configured;
+};
+
+static int dacx3608_reg_read(const struct device *dev, uint8_t reg,
+			      uint16_t *val)
+{
+	struct dacx3608_data *data = dev->data;
+	const struct dacx3608_config *cfg = dev->config;
+
+	if (i2c_burst_read(data->i2c, cfg->i2c_addr,
+			   reg, (uint8_t *) val, 2) < 0) {
+		LOG_ERR("I2C read failed");
+		return -EIO;
+	}
+
+	*val = sys_be16_to_cpu(*val);
+
+	return 0;
+}
+
+static int dacx3608_reg_write(const struct device *dev, uint8_t reg,
+			       uint16_t val)
+{
+	struct dacx3608_data *data = dev->data;
+	const struct dacx3608_config *cfg = dev->config;
+	uint8_t buf[3] = {reg, val >> 8, val & 0xFF};
+
+	return i2c_write(data->i2c, buf, sizeof(buf), cfg->i2c_addr);
+}
+
+int dacx3608_reg_update(const struct device *dev, uint8_t reg,
+			 uint16_t mask, bool setting)
+{
+	uint16_t regval;
+	int ret;
+
+	ret = dacx3608_reg_read(dev, reg, &regval);
+	if (ret) {
+		return -EIO;
+	}
+
+	if (setting) {
+		regval |= mask;
+	} else {
+		regval &= ~mask;
+	}
+
+	ret = dacx3608_reg_write(dev, reg, regval);
+	if (ret) {
+		return ret;
+	}
+
+	return 0;
+}
+
+static int dacx3608_channel_setup(const struct device *dev,
+				   const struct dac_channel_cfg *channel_cfg)
+{
+	const struct dacx3608_config *config = dev->config;
+	struct dacx3608_data *data = dev->data;
+	bool setting = false;
+	int ret;
+
+	if (channel_cfg->channel_id > DACX3608_MAX_CHANNEL - 1) {
+		LOG_ERR("Unsupported channel %d", channel_cfg->channel_id);
+		return -ENOTSUP;
+	}
+
+	if (channel_cfg->resolution != config->resolution) {
+		LOG_ERR("Unsupported resolution %d", channel_cfg->resolution);
+		return -ENOTSUP;
+	}
+
+	if (data->configured & BIT(channel_cfg->channel_id)) {
+		LOG_DBG("Channel %d already configured", channel_cfg->channel_id);
+		return 0;
+	}
+
+	/* Clear PDNn bit */
+	ret = dacx3608_reg_update(dev, DACX3608_REG_DEVICE_CONFIG,
+				BIT(channel_cfg->channel_id), setting);
+	if (ret) {
+		LOG_ERR("Unable to update DEVICE_CONFIG register");
+		return -EIO;
+	}
+
+	data->configured |= BIT(channel_cfg->channel_id);
+
+	LOG_DBG("Channel %d initialized", channel_cfg->channel_id);
+
+	return 0;
+}
+
+static int dacx3608_write_value(const struct device *dev, uint8_t channel,
+				uint32_t value)
+{
+	const struct dacx3608_config *config = dev->config;
+	struct dacx3608_data *data = dev->data;
+	uint16_t regval;
+	int ret;
+
+	if (channel > DACX3608_MAX_CHANNEL - 1) {
+		LOG_ERR("Unsupported channel %d", channel);
+		return -ENOTSUP;
+	}
+
+	if (!(data->configured & BIT(channel))) {
+		LOG_ERR("Channel %d not initialized", channel);
+		return -EINVAL;
+	}
+
+	if (value >= (1 << (config->resolution))) {
+		LOG_ERR("Value %d out of range", value);
+		return -EINVAL;
+	}
+
+	/*
+	 * Shift passed value two times left because first two bits are Don't Care
+	 *
+	 * DACn_DATA register format:
+	 *
+	 * | 15 14 13 12 |      11 10 9 8 7 6 5 4 3 2      |    1 0     |
+	 * |-------------|---------------------------------|------------|
+	 * | Don't Care  |  DAC53608[9:0] / DAC43608[7:0]  | Don't Care |
+	 */
+	regval = value << 2;
+	regval &= 0xFFFF;
+
+	ret = dacx3608_reg_write(dev, DACX3608_REG_DACA_DATA + channel, regval);
+	if (ret) {
+		LOG_ERR("Unable to set value %d on channel %d", value, channel);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int dacx3608_soft_reset(const struct device *dev)
+{
+	uint16_t regval = DACX3608_SW_RST;
+	int ret;
+
+	ret = dacx3608_reg_write(dev, DACX3608_REG_STATUS_TRIGGER, regval);
+	if (ret) {
+		return -EIO;
+	}
+	k_msleep(DACX3608_POR_DELAY);
+
+	return 0;
+}
+
+static int dacx3608_device_id_check(const struct device *dev)
+{
+	uint16_t dev_id;
+	int ret;
+
+	ret = dacx3608_reg_read(dev, DACX3608_REG_STATUS_TRIGGER, dev_id);
+	if (ret) {
+		LOG_ERR("Unable to read device ID");
+		return -EIO;
+	}
+
+	switch (dev_id) {
+	case DAC43608_DEVICE_ID:
+	case DAC53608_DEVICE_ID:
+		LOG_DBG("Device ID %#4x", dev_id);
+		break;
+	default:
+		LOG_ERR("Unknown Device ID %#4x", dev_id);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int dacx3608_init(const struct device *dev)
+{
+	const struct dacx3608_config *config = dev->config;
+	struct dacx3608_data *data = dev->data;
+	int ret;
+
+	data->i2c = device_get_binding(config->i2c_bus);
+	if (!data->i2c) {
+		LOG_ERR("Could not find I2C device");
+		return -EINVAL;
+	}
+
+	ret = dacx3608_soft_reset(dev);
+	if (ret) {
+		LOG_ERR("Soft-reset failed");
+		return ret;
+	}
+
+	ret = dacx3608_device_id_check(dev);
+	if (ret) {
+		return ret;
+	}
+
+	data->configured = 0;
+
+	LOG_DBG("Init complete");
+
+	return 0;
+}
+
+static const struct dac_driver_api dacx3608_driver_api = {
+	.channel_setup = dacx3608_channel_setup,
+	.write_value = dacx3608_write_value,
+};
+
+#define INST_DT_DACX3608(inst, t) DT_INST(inst, ti_dac##t)
+
+#define DACX3608_DEVICE(t, n, res) \
+	static struct dacx3608_data dac##t##_data_##n; \
+	static const struct dacx3608_config dac##t##_config_##n = { \
+		.i2c_bus = DT_BUS_LABEL(INST_DT_DACX3608(n, t)), \
+		.i2c_addr = DT_REG_ADDR(INST_DT_DACX3608(n, t)), \
+		.resolution = res, \
+	}; \
+	DEVICE_DT_DEFINE(INST_DT_DACX3608(n, t), \
+				&dacx3608_init, device_pm_control_nop, \
+				&dac##t##_data_##n, \
+				&dac##t##_config_##n, POST_KERNEL, \
+				CONFIG_DAC_DACX3608_INIT_PRIORITY, \
+				&dacx3608_driver_api)
+
+/*
+ * DAC43608: 8-bit
+ */
+#define DAC43608_DEVICE(n) DACX3608_DEVICE(43608, n, 8)
+
+/*
+ * DAC53608: 10-bit
+ */
+#define DAC53608_DEVICE(n) DACX3608_DEVICE(53608, n, 10)
+
+#define CALL_WITH_ARG(arg, expr) expr(arg)
+
+#define INST_DT_DACX3608_FOREACH(t, inst_expr) \
+	UTIL_LISTIFY(DT_NUM_INST_STATUS_OKAY(ti_dac##t), \
+		     CALL_WITH_ARG, inst_expr)
+
+INST_DT_DACX3608_FOREACH(43608, DAC43608_DEVICE);
+INST_DT_DACX3608_FOREACH(53608, DAC53608_DEVICE);
diff --git a/dts/bindings/dac/ti,dac43608.yaml b/dts/bindings/dac/ti,dac43608.yaml
new file mode 100644
index 0000000..37f362c
--- /dev/null
+++ b/dts/bindings/dac/ti,dac43608.yaml
@@ -0,0 +1,8 @@
+# Copyright (c) 2020 Matija Tudan
+# SPDX-License-Identifier: Apache-2.0
+
+description: TI DAC43608 8-bit 8 channel DAC
+
+compatible: "ti,dac43608"
+
+include: ti,dacx3608-base.yaml
diff --git a/dts/bindings/dac/ti,dac53608.yaml b/dts/bindings/dac/ti,dac53608.yaml
new file mode 100644
index 0000000..6ddc262
--- /dev/null
+++ b/dts/bindings/dac/ti,dac53608.yaml
@@ -0,0 +1,8 @@
+# Copyright (c) 2020 Matija Tudan
+# SPDX-License-Identifier: Apache-2.0
+
+description: TI DAC53608 10-bit 8 channel DAC
+
+compatible: "ti,dac53608"
+
+include: ti,dacx3608-base.yaml
diff --git a/dts/bindings/dac/ti,dacx3608-base.yaml b/dts/bindings/dac/ti,dacx3608-base.yaml
new file mode 100644
index 0000000..660b75d
--- /dev/null
+++ b/dts/bindings/dac/ti,dacx3608-base.yaml
@@ -0,0 +1,11 @@
+# Copyright (c) 2020 Matija Tudan
+# SPDX-License-Identifier: Apache-2.0
+
+include: [dac-controller.yaml, i2c-device.yaml]
+
+properties:
+    "#io-channel-cells":
+      const: 1
+
+io-channel-cells:
+    - output