1 /*
2  * Copyright (c) 2024 Jan Kubiznak <jan.kubiznak@deveritec.com>
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stdint.h>
8 #include <zephyr/kernel.h>
9 #include <zephyr/drivers/i2c.h>
10 #include <zephyr/drivers/dac.h>
11 #include <zephyr/logging/log.h>
12 #include <zephyr/sys/byteorder.h>
13 
14 LOG_MODULE_REGISTER(dac_ad569x, CONFIG_DAC_LOG_LEVEL);
15 
16 #define AD569X_CTRL_GAIN(x)  FIELD_PREP(BIT(11), x)
17 #define AD569X_CTRL_REF(x)   FIELD_PREP(BIT(12), x)
18 #define AD569X_CTRL_PD(x)    FIELD_PREP(BIT_MASK(2) << 13, x)
19 #define AD569X_CTRL_RESET(x) FIELD_PREP(BIT(15), x)
20 
21 #define AD569X_CMD_WRITE            0x10
22 #define AD569X_CMD_UPDATE           0x20
23 #define AD569X_CMD_WRITE_AND_UPDATE 0x30
24 #define AD569X_CMD_CONFIGURE        0x40
25 
26 #define AD569X_CTRL_NO_RESET      0x00
27 #define AD569X_CTRL_PERFORM_RESET 0x01
28 
29 struct ad569x_config {
30 	struct i2c_dt_spec bus;
31 	uint8_t resolution;
32 	uint8_t gain;
33 	uint8_t voltage_reference;
34 	uint8_t power_down_mode;
35 };
36 
ad569x_write(const struct device * dev,uint8_t command,uint16_t value)37 static int ad569x_write(const struct device *dev, uint8_t command, uint16_t value)
38 {
39 	const struct ad569x_config *config = dev->config;
40 
41 	uint8_t tx_data[3];
42 
43 	tx_data[0] = command;
44 	sys_put_be16(value, tx_data + 1);
45 
46 	return i2c_write_dt(&config->bus, tx_data, sizeof(tx_data));
47 }
48 
ad569x_read(const struct device * dev,uint16_t * value)49 static int ad569x_read(const struct device *dev, uint16_t *value)
50 {
51 	const struct ad569x_config *config = dev->config;
52 
53 	uint8_t rx_data[2];
54 	int ret;
55 
56 	ret = i2c_read_dt(&config->bus, rx_data, sizeof(rx_data));
57 	if (ret != 0) {
58 		return ret;
59 	}
60 
61 	*value = sys_get_be16(rx_data);
62 
63 	return ret;
64 }
65 
ad569x_channel_setup(const struct device * dev,const struct dac_channel_cfg * channel_cfg)66 static int ad569x_channel_setup(const struct device *dev, const struct dac_channel_cfg *channel_cfg)
67 {
68 	const struct ad569x_config *config = dev->config;
69 
70 	if (channel_cfg->channel_id > 0) {
71 		LOG_ERR("invalid channel %d", channel_cfg->channel_id);
72 		return -EINVAL;
73 	}
74 
75 	if (channel_cfg->resolution != config->resolution) {
76 		LOG_ERR("invalid resolution %d", channel_cfg->resolution);
77 		return -EINVAL;
78 	}
79 
80 	if (channel_cfg->internal) {
81 		LOG_ERR("Internal channels not supported");
82 		return -ENOTSUP;
83 	}
84 
85 	return 0;
86 }
87 
ad569x_sw_reset(const struct device * dev)88 static int ad569x_sw_reset(const struct device *dev)
89 {
90 	uint16_t reg = AD569X_CTRL_RESET(AD569X_CTRL_PERFORM_RESET);
91 	int ret;
92 
93 	LOG_DBG("reset %s", dev->name);
94 
95 	/* Ignore return value, since device gives NAK after receiving RESET request */
96 	ad569x_write(dev, AD569X_CMD_CONFIGURE, reg);
97 
98 	/* Check that DAC output is reset */
99 	ret = ad569x_read(dev, &reg);
100 	if (ret != 0) {
101 		LOG_ERR("failed to read value");
102 		return ret;
103 	}
104 
105 	if (reg != 0) {
106 		LOG_ERR("failed to reset DAC output");
107 		return -EIO;
108 	}
109 
110 	return 0;
111 }
112 
ad569x_write_value(const struct device * dev,uint8_t channel,uint32_t value)113 static int ad569x_write_value(const struct device *dev, uint8_t channel, uint32_t value)
114 {
115 	const struct ad569x_config *config = dev->config;
116 
117 	if (channel > 0) {
118 		LOG_ERR("invalid channel %d", channel);
119 		return -EINVAL;
120 	}
121 
122 	if (value > (BIT(config->resolution) - 1)) {
123 		LOG_ERR("invalid value %d", value);
124 		return -EINVAL;
125 	}
126 
127 	value <<= 16 - config->resolution;
128 
129 	return ad569x_write(dev, AD569X_CMD_WRITE_AND_UPDATE, value);
130 }
131 
ad569x_init(const struct device * dev)132 static int ad569x_init(const struct device *dev)
133 {
134 	const struct ad569x_config *config = dev->config;
135 	int ret;
136 
137 	if (!i2c_is_ready_dt(&config->bus)) {
138 		return -ENODEV;
139 	}
140 
141 	ret = ad569x_sw_reset(dev);
142 	if (ret != 0) {
143 		LOG_ERR("failed to perform sw reset");
144 		return ret;
145 	}
146 
147 	LOG_DBG("configure %s: gain %d, voltage reference %d, power down mode %d", dev->name,
148 		config->gain, config->voltage_reference, config->power_down_mode);
149 
150 	uint16_t ctrl_reg = AD569X_CTRL_GAIN(config->gain) |
151 			    AD569X_CTRL_REF(config->voltage_reference) |
152 			    AD569X_CTRL_PD(config->power_down_mode);
153 
154 	ret = ad569x_write(dev, AD569X_CMD_CONFIGURE, ctrl_reg);
155 	if (ret != 0) {
156 		LOG_ERR("failed to configure the device");
157 		return ret;
158 	}
159 
160 	return 0;
161 }
162 
163 static DEVICE_API(dac, ad569x_driver_api) = {
164 	.channel_setup = ad569x_channel_setup,
165 	.write_value = ad569x_write_value,
166 };
167 
168 #define INST_DT_AD569X(index, name, res)                                                           \
169 	static const struct ad569x_config config_##name##_##index = {                              \
170 		.bus = I2C_DT_SPEC_INST_GET(index),                                                \
171 		.resolution = res,                                                                 \
172 		.gain = DT_INST_ENUM_IDX(index, gain),                                             \
173 		.voltage_reference = DT_INST_ENUM_IDX(index, voltage_reference),                   \
174 		.power_down_mode = DT_INST_ENUM_IDX(index, power_down_mode),                       \
175 	};                                                                                         \
176                                                                                                    \
177 	DEVICE_DT_INST_DEFINE(index, ad569x_init, NULL, NULL, &config_##name##_##index,            \
178 			      POST_KERNEL, CONFIG_DAC_INIT_PRIORITY, &ad569x_driver_api);
179 
180 #define DT_DRV_COMPAT adi_ad5691
181 #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
182 #define DAC_AD5691_RESOLUTION 12
183 DT_INST_FOREACH_STATUS_OKAY_VARGS(INST_DT_AD569X, DT_DRV_COMPAT, DAC_AD5691_RESOLUTION)
184 #endif
185 #undef DT_DRV_COMPAT
186 
187 #define DT_DRV_COMPAT adi_ad5692
188 #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
189 #define DAC_AD5692_RESOLUTION 14
190 DT_INST_FOREACH_STATUS_OKAY_VARGS(INST_DT_AD569X, DT_DRV_COMPAT, DAC_AD5692_RESOLUTION)
191 #endif
192 #undef DT_DRV_COMPAT
193 
194 #define DT_DRV_COMPAT adi_ad5693
195 #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
196 #define DAC_AD5693_RESOLUTION 16
197 DT_INST_FOREACH_STATUS_OKAY_VARGS(INST_DT_AD569X, DT_DRV_COMPAT, DAC_AD5693_RESOLUTION)
198 #endif
199 #undef DT_DRV_COMPAT
200